diff --git a/.gitmodules b/.gitmodules index dc92d0a4b..ed533f8d4 100644 --- a/.gitmodules +++ b/.gitmodules @@ -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 diff --git a/src/audio_core/CMakeLists.txt b/src/audio_core/CMakeLists.txt index 89575a53e..2971c42a2 100644 --- a/src/audio_core/CMakeLists.txt +++ b/src/audio_core/CMakeLists.txt @@ -1,54 +1,218 @@ 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 - - $<$:cubeb_sink.cpp cubeb_sink.h> - $<$: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 $<$:-Werror=unused-but-set-parameter> $<$:-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) diff --git a/src/audio_core/algorithm/filter.cpp b/src/audio_core/algorithm/filter.cpp deleted file mode 100644 index 96e37991f..000000000 --- a/src/audio_core/algorithm/filter.cpp +++ /dev/null @@ -1,79 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#define _USE_MATH_DEFINES - -#include -#include -#include -#include -#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& 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(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(2 * index + 1) / (4.0 * static_cast(total_count)); - return 1.0 / (2.0 * std::cos(pole)); -} - -CascadingFilter CascadingFilter::LowPass(double cutoff, std::size_t cascade_size) { - std::vector 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 filters_) : filters(std::move(filters_)) {} - -void CascadingFilter::Process(std::vector& signal) { - for (auto& filter : filters) { - filter.Process(signal); - } -} - -} // namespace AudioCore diff --git a/src/audio_core/algorithm/filter.h b/src/audio_core/algorithm/filter.h deleted file mode 100644 index 2586f0079..000000000 --- a/src/audio_core/algorithm/filter.h +++ /dev/null @@ -1,61 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include -#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& 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, 3> in; - /// Output History - std::array, 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 filters_); - - void Process(std::vector& signal); - -private: - std::vector filters; -}; - -} // namespace AudioCore diff --git a/src/audio_core/algorithm/interpolate.cpp b/src/audio_core/algorithm/interpolate.cpp deleted file mode 100644 index d2a4cd53f..000000000 --- a/src/audio_core/algorithm/interpolate.cpp +++ /dev/null @@ -1,232 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#define _USE_MATH_DEFINES - -#include -#include -#include -#include - -#include "audio_core/algorithm/interpolate.h" -#include "common/common_types.h" -#include "common/logging/log.h" - -namespace AudioCore { - -constexpr std::array 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 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 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 Interpolate(InterpolationState& state, std::vector 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(ratio * 0x8000)}; - const std::array& 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 output; - output.reserve(static_cast(static_cast(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(std::clamp(left >> 15, SHRT_MIN, SHRT_MAX))); - output.emplace_back(static_cast(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& lut = [pitch] { - if (pitch > 0xaaaa) { - return curve_lut0; - } - if (pitch <= 0x8000) { - return curve_lut1; - } - return curve_lut2; - }(); - - std::size_t index{}; - - for (std::size_t i = 0; i < sample_count; i++) { - const std::size_t lut_index{(static_cast(fraction) >> 8) * 4}; - const auto l0 = lut[lut_index + 0]; - const auto l1 = lut[lut_index + 1]; - const auto l2 = lut[lut_index + 2]; - const auto l3 = lut[lut_index + 3]; - - const auto s0 = static_cast(input[index + 0]); - const auto s1 = static_cast(input[index + 1]); - const auto s2 = static_cast(input[index + 2]); - const auto s3 = static_cast(input[index + 3]); - - output[i] = (l0 * s0 + l1 * s1 + l2 * s2 + l3 * s3) >> 15; - fraction += pitch; - index += (fraction >> 15); - fraction &= 0x7fff; - } -} - -} // namespace AudioCore diff --git a/src/audio_core/algorithm/interpolate.h b/src/audio_core/algorithm/interpolate.h deleted file mode 100644 index 5e59f4d70..000000000 --- a/src/audio_core/algorithm/interpolate.h +++ /dev/null @@ -1,43 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include - -#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, 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 Interpolate(InterpolationState& state, std::vector 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 Interpolate(InterpolationState& state, std::vector input, - u32 input_rate, u32 output_rate) { - const double ratio = static_cast(input_rate) / static_cast(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 diff --git a/src/audio_core/audio_core.cpp b/src/audio_core/audio_core.cpp new file mode 100644 index 000000000..78e615a10 --- /dev/null +++ b/src/audio_core/audio_core.cpp @@ -0,0 +1,68 @@ +// 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(system)} { + CreateSinks(); + // Must be created after the sinks + adsp = std::make_unique(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 diff --git a/src/audio_core/audio_core.h b/src/audio_core/audio_core.h new file mode 100644 index 000000000..0f7d61ee4 --- /dev/null +++ b/src/audio_core/audio_core.h @@ -0,0 +1,100 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#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 audio_manager; + /// Sink used for audio renderer and audio out + std::unique_ptr output_sink; + /// Sink used for audio input + std::unique_ptr input_sink; + /// The ADSP in the sysmodule + std::unique_ptr adsp; + /// Current size of the stream queue + std::atomic estimated_queue{0}; +}; + +} // namespace AudioCore diff --git a/src/audio_core/audio_event.cpp b/src/audio_core/audio_event.cpp new file mode 100644 index 000000000..424049c7a --- /dev/null +++ b/src/audio_core/audio_event.cpp @@ -0,0 +1,61 @@ +// 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& 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 diff --git a/src/audio_core/audio_event.h b/src/audio_core/audio_event.h new file mode 100644 index 000000000..82dd32dca --- /dev/null +++ b/src/audio_core/audio_event.h @@ -0,0 +1,92 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include +#include + +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& 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, 4> events_signalled; + /// Event to signal the audio manager + std::condition_variable_any manager_event; +}; + +} // namespace AudioCore diff --git a/src/audio_core/audio_in_manager.cpp b/src/audio_core/audio_in_manager.cpp new file mode 100644 index 000000000..4aadb7fd6 --- /dev/null +++ b/src/audio_core/audio_in_manager.cpp @@ -0,0 +1,91 @@ +// 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& 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 diff --git a/src/audio_core/audio_in_manager.h b/src/audio_core/audio_in_manager.h new file mode 100644 index 000000000..75b73a0b6 --- /dev/null +++ b/src/audio_core/audio_in_manager.h @@ -0,0 +1,92 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include + +#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& names, + u32 max_count, bool filter); + + /// Core system + Core::System& system; + /// Array of session ids + std::array session_ids{}; + /// Array of resource user ids + std::array applet_resource_user_ids{}; + /// Pointer to each open session + std::array, 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 diff --git a/src/audio_core/audio_manager.cpp b/src/audio_core/audio_manager.cpp new file mode 100644 index 000000000..2f1bba9c3 --- /dev/null +++ b/src/audio_core/audio_manager.cpp @@ -0,0 +1,80 @@ +// 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 diff --git a/src/audio_core/audio_manager.h b/src/audio_core/audio_manager.h new file mode 100644 index 000000000..70316e9cb --- /dev/null +++ b/src/audio_core/audio_manager.h @@ -0,0 +1,101 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include +#include + +#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; + +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 running{}; + /// Unused + bool needs_update{}; + /// Events to be set and signalled + Event events{}; + /// Callbacks for each manager + std::array buffer_events{}; + /// General lock + std::mutex lock{}; + /// Main thread for waiting and callbacks + std::jthread thread; +}; + +} // namespace AudioCore diff --git a/src/audio_core/audio_out.cpp b/src/audio_core/audio_out.cpp deleted file mode 100644 index 83ec0221f..000000000 --- a/src/audio_core/audio_out.cpp +++ /dev/null @@ -1,62 +0,0 @@ -// 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( - core_timing, sample_rate, ChannelsToStreamFormat(num_channels), std::move(release_callback), - sink->AcquireSinkStream(sample_rate, num_channels, name), std::move(name)); -} - -std::vector AudioOut::GetTagsAndReleaseBuffers(StreamPtr stream, - std::size_t max_count) { - return stream->GetTagsAndReleaseBuffers(max_count); -} - -std::vector 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&& data) { - return stream->QueueBuffer(std::make_shared(tag, std::move(data))); -} - -} // namespace AudioCore diff --git a/src/audio_core/audio_out.h b/src/audio_core/audio_out.h deleted file mode 100644 index 6856373f1..000000000 --- a/src/audio_core/audio_out.h +++ /dev/null @@ -1,49 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include -#include - -#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 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 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&& data); - -private: - SinkPtr sink; -}; - -} // namespace AudioCore diff --git a/src/audio_core/audio_out_manager.cpp b/src/audio_core/audio_out_manager.cpp new file mode 100644 index 000000000..71d67de64 --- /dev/null +++ b/src/audio_core/audio_out_manager.cpp @@ -0,0 +1,81 @@ +// 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& names) const { + names.push_back({"DeviceOut"}); + return 1; +} + +} // namespace AudioCore::AudioOut diff --git a/src/audio_core/audio_out_manager.h b/src/audio_core/audio_out_manager.h new file mode 100644 index 000000000..24981e08f --- /dev/null +++ b/src/audio_core/audio_out_manager.h @@ -0,0 +1,89 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#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& names) const; + + /// Core system + Core::System& system; + /// Array of session ids + std::array session_ids{}; + /// Array of resource user ids + std::array applet_resource_user_ids{}; + /// Pointer to each open session + std::array, 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 diff --git a/src/audio_core/audio_render_manager.cpp b/src/audio_core/audio_render_manager.cpp new file mode 100644 index 000000000..7a846835b --- /dev/null +++ b/src/audio_core/audio_render_manager.cpp @@ -0,0 +1,70 @@ +// 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(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 diff --git a/src/audio_core/audio_render_manager.h b/src/audio_core/audio_render_manager.h new file mode 100644 index 000000000..6a508ec56 --- /dev/null +++ b/src/audio_core/audio_render_manager.h @@ -0,0 +1,103 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include + +#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 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 system_manager{}; +}; + +} // namespace AudioRenderer +} // namespace AudioCore diff --git a/src/audio_core/audio_renderer.cpp b/src/audio_core/audio_renderer.cpp deleted file mode 100644 index 9191ca093..000000000 --- a/src/audio_core/audio_renderer.cpp +++ /dev/null @@ -1,343 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include -#include -#include - -#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(std::clamp(value, s32{std::numeric_limits::min()}, - s32{std::numeric_limits::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((static_cast(l_channel) * l_mix_amount) + - (static_cast(r_channel) * r_mix_amount))); -} - -[[maybe_unused, nodiscard]] static constexpr std::tuple 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(fl_channel) + - center_mix_amount * static_cast(fc_channel) + - back_mix_amount * static_cast(bl_channel); - - const auto right = front_mix_amount * static_cast(fr_channel) + - center_mix_amount * static_cast(fc_channel) + - back_mix_amount * static_cast(br_channel); - - return {ClampToS16(static_cast(left)), ClampToS16(static_cast(right))}; -} - -[[nodiscard]] static constexpr std::tuple Mix6To2WithCoefficients( - s16 fl_channel, s16 fr_channel, s16 fc_channel, s16 lf_channel, s16 bl_channel, s16 br_channel, - const std::array& coeff) { - const auto left = - static_cast(fl_channel) * coeff[0] + static_cast(fc_channel) * coeff[1] + - static_cast(lf_channel) * coeff[2] + static_cast(bl_channel) * coeff[3]; - - const auto right = - static_cast(fr_channel) * coeff[0] + static_cast(fc_channel) * coeff[1] + - static_cast(lf_channel) * coeff[2] + static_cast(br_channel) * coeff[3]; - - return {ClampToS16(static_cast(left)), ClampToS16(static_cast(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(); - 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& input_params, - std::vector& 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 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> 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(GetSampleRate()); - const f32 sample_count = static_cast(GetSampleCount()); - const f32 consume_rate = sample_rate / (sample_count * (sample_count / 240)); - const s32 ms = (1000 / static_cast(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 diff --git a/src/audio_core/audio_renderer.h b/src/audio_core/audio_renderer.h deleted file mode 100644 index a67ffd592..000000000 --- a/src/audio_core/audio_renderer.h +++ /dev/null @@ -1,78 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include -#include -#include - -#include "audio_core/behavior_info.h" -#include "audio_core/command_generator.h" -#include "audio_core/common.h" -#include "audio_core/effect_context.h" -#include "audio_core/memory_pool.h" -#include "audio_core/mix_context.h" -#include "audio_core/sink_context.h" -#include "audio_core/splitter_context.h" -#include "audio_core/stream.h" -#include "audio_core/voice_context.h" -#include "common/common_funcs.h" -#include "common/common_types.h" -#include "common/swap.h" -#include "core/hle/result.h" - -namespace Core::Timing { -class CoreTiming; -} - -namespace Core::Memory { -class Memory; -} - -namespace AudioCore { -using DSPStateHolder = std::array; - -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& input_params, - std::vector& 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 memory_pool_info; - VoiceContext voice_context; - EffectContext effect_context; - MixContext mix_context; - SinkContext sink_context; - SplitterContext splitter_context; - std::vector voices; - std::unique_ptr 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 process_event; - std::mutex mutex; -}; - -} // namespace AudioCore diff --git a/src/audio_core/behavior_info.cpp b/src/audio_core/behavior_info.cpp deleted file mode 100644 index ea7e45617..000000000 --- a/src/audio_core/behavior_info.cpp +++ /dev/null @@ -1,104 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include -#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& 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(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(error_count); - std::copy(errors.begin(), errors.begin() + error_count, dst.errors.begin()); -} - -} // namespace AudioCore diff --git a/src/audio_core/behavior_info.h b/src/audio_core/behavior_info.h deleted file mode 100644 index b8c3159b9..000000000 --- a/src/audio_core/behavior_info.h +++ /dev/null @@ -1,71 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include - -#include -#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 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& 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 errors{}; - std::size_t error_count{}; -}; - -} // namespace AudioCore diff --git a/src/audio_core/buffer.h b/src/audio_core/buffer.h deleted file mode 100644 index ac001629f..000000000 --- a/src/audio_core/buffer.h +++ /dev/null @@ -1,44 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include - -#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&& samples_) : tag{tag_}, samples{std::move(samples_)} {} - - /// Returns the raw audio data for the buffer - std::vector& GetSamples() { - return samples; - } - - /// Returns the raw audio data for the buffer - const std::vector& 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 samples; -}; - -using BufferPtr = std::shared_ptr; - -} // namespace AudioCore diff --git a/src/audio_core/codec.cpp b/src/audio_core/codec.cpp deleted file mode 100644 index 868b7a173..000000000 --- a/src/audio_core/codec.cpp +++ /dev/null @@ -1,77 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include - -#include "audio_core/codec.h" - -namespace AudioCore::Codec { - -std::vector 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 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 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(val, -32768, 32767); - // Advance output feedback. - yn2 = yn1; - yn1 = val; - return static_cast(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(yn1); - state.yn2 = static_cast(yn2); - - return ret; -} - -} // namespace AudioCore::Codec diff --git a/src/audio_core/codec.h b/src/audio_core/codec.h deleted file mode 100644 index 5a058b368..000000000 --- a/src/audio_core/codec.h +++ /dev/null @@ -1,43 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include - -#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; - -/** - * @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 DecodeADPCM(const u8* data, std::size_t size, const ADPCM_Coeff& coeff, - ADPCMState& state); - -}; // namespace AudioCore::Codec diff --git a/src/audio_core/command_generator.cpp b/src/audio_core/command_generator.cpp deleted file mode 100644 index f97520820..000000000 --- a/src/audio_core/command_generator.cpp +++ /dev/null @@ -1,1369 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include -#include -#include - -#include "audio_core/algorithm/interpolate.h" -#include "audio_core/command_generator.h" -#include "audio_core/effect_context.h" -#include "audio_core/mix_context.h" -#include "audio_core/voice_context.h" -#include "common/common_types.h" -#include "core/memory.h" - -namespace AudioCore { -namespace { -constexpr std::size_t MIX_BUFFER_SIZE = 0x3f00; -constexpr std::size_t SCALED_MIX_BUFFER_SIZE = MIX_BUFFER_SIZE << 15ULL; -using DelayLineTimes = std::array; - -constexpr DelayLineTimes FDN_MIN_DELAY_LINE_TIMES{5.0f, 6.0f, 13.0f, 14.0f}; -constexpr DelayLineTimes FDN_MAX_DELAY_LINE_TIMES{45.704f, 82.782f, 149.94f, 271.58f}; -constexpr DelayLineTimes DECAY0_MAX_DELAY_LINE_TIMES{17.0f, 13.0f, 9.0f, 7.0f}; -constexpr DelayLineTimes DECAY1_MAX_DELAY_LINE_TIMES{19.0f, 11.0f, 10.0f, 6.0f}; -constexpr std::array EARLY_TAP_TIMES{ - 0.017136f, 0.059154f, 0.161733f, 0.390186f, 0.425262f, 0.455411f, 0.689737f, - 0.745910f, 0.833844f, 0.859502f, 0.000000f, 0.075024f, 0.168788f, 0.299901f, - 0.337443f, 0.371903f, 0.599011f, 0.716741f, 0.817859f, 0.851664f}; -constexpr std::array EARLY_GAIN{ - 0.67096f, 0.61027f, 1.0f, 0.35680f, 0.68361f, 0.65978f, 0.51939f, - 0.24712f, 0.45945f, 0.45021f, 0.64196f, 0.54879f, 0.92925f, 0.38270f, - 0.72867f, 0.69794f, 0.5464f, 0.24563f, 0.45214f, 0.44042f}; - -template -void ApplyMix(std::span output, std::span input, s32 gain, s32 sample_count) { - for (std::size_t i = 0; i < static_cast(sample_count); i += N) { - for (std::size_t j = 0; j < N; j++) { - output[i + j] += - static_cast((static_cast(input[i + j]) * gain + 0x4000) >> 15); - } - } -} - -s32 ApplyMixRamp(std::span output, std::span input, float gain, float delta, - s32 sample_count) { - // XC2 passes in NaN mix volumes, causing further issues as we handle everything as s32 rather - // than float, so the NaN propogation is lost. As the samples get further modified for - // volume etc, they can get out of NaN range, so a later heuristic for catching this is - // more difficult. Handle it here by setting these samples to silence. - if (std::isnan(gain)) { - gain = 0.0f; - delta = 0.0f; - } - - s32 x = 0; - for (s32 i = 0; i < sample_count; i++) { - x = static_cast(static_cast(input[i]) * gain); - output[i] += x; - gain += delta; - } - return x; -} - -void ApplyGain(std::span output, std::span input, s32 gain, s32 delta, - s32 sample_count) { - for (s32 i = 0; i < sample_count; i++) { - output[i] = static_cast((static_cast(input[i]) * gain + 0x4000) >> 15); - gain += delta; - } -} - -void ApplyGainWithoutDelta(std::span output, std::span input, s32 gain, - s32 sample_count) { - for (s32 i = 0; i < sample_count; i++) { - output[i] = static_cast((static_cast(input[i]) * gain + 0x4000) >> 15); - } -} - -s32 ApplyMixDepop(std::span output, s32 first_sample, s32 delta, s32 sample_count) { - const bool positive = first_sample > 0; - auto final_sample = std::abs(first_sample); - for (s32 i = 0; i < sample_count; i++) { - final_sample = static_cast((static_cast(final_sample) * delta) >> 15); - if (positive) { - output[i] += final_sample; - } else { - output[i] -= final_sample; - } - } - if (positive) { - return final_sample; - } else { - return -final_sample; - } -} - -float Pow10(float x) { - if (x >= 0.0f) { - return 1.0f; - } else if (x <= -5.3f) { - return 0.0f; - } - return std::pow(10.0f, x); -} - -float SinD(float degrees) { - return std::sin(degrees * std::numbers::pi_v / 180.0f); -} - -float CosD(float degrees) { - return std::cos(degrees * std::numbers::pi_v / 180.0f); -} - -float ToFloat(s32 sample) { - return static_cast(sample) / 65536.f; -} - -s32 ToS32(float sample) { - constexpr auto min = -8388608.0f; - constexpr auto max = 8388607.f; - float rescaled_sample = sample * 65536.0f; - if (rescaled_sample < min) { - rescaled_sample = min; - } - if (rescaled_sample > max) { - rescaled_sample = max; - } - return static_cast(rescaled_sample); -} - -constexpr std::array REVERB_TAP_INDEX_1CH{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - -constexpr std::array REVERB_TAP_INDEX_2CH{0, 0, 0, 1, 1, 1, 1, 0, 0, 0, - 1, 1, 1, 0, 0, 0, 0, 1, 1, 1}; - -constexpr std::array REVERB_TAP_INDEX_4CH{0, 0, 0, 1, 1, 1, 1, 2, 2, 2, - 1, 1, 1, 0, 0, 0, 0, 3, 3, 3}; - -constexpr std::array REVERB_TAP_INDEX_6CH{4, 0, 0, 1, 1, 1, 1, 2, 2, 2, - 1, 1, 1, 0, 0, 0, 0, 3, 3, 3}; - -template -void ApplyReverbGeneric( - I3dl2ReverbState& state, - const std::array, AudioCommon::MAX_CHANNEL_COUNT>& input, - const std::array, AudioCommon::MAX_CHANNEL_COUNT>& output, s32 sample_count) { - - auto GetTapLookup = []() { - if constexpr (CHANNEL_COUNT == 1) { - return REVERB_TAP_INDEX_1CH; - } else if constexpr (CHANNEL_COUNT == 2) { - return REVERB_TAP_INDEX_2CH; - } else if constexpr (CHANNEL_COUNT == 4) { - return REVERB_TAP_INDEX_4CH; - } else if constexpr (CHANNEL_COUNT == 6) { - return REVERB_TAP_INDEX_6CH; - } - }; - - const auto& tap_index_lut = GetTapLookup(); - for (s32 sample = 0; sample < sample_count; sample++) { - std::array out_samples{}; - std::array fsamp{}; - std::array mixed{}; - std::array osamp{}; - - // Mix everything into a single sample - s32 temp_mixed_sample = 0; - for (std::size_t i = 0; i < CHANNEL_COUNT; i++) { - temp_mixed_sample += input[i][sample]; - } - const auto current_sample = ToFloat(temp_mixed_sample); - const auto early_tap = state.early_delay_line.TapOut(state.early_to_late_taps); - - for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_TAPS; i++) { - const auto tapped_samp = - state.early_delay_line.TapOut(state.early_tap_steps[i]) * EARLY_GAIN[i]; - out_samples[tap_index_lut[i]] += tapped_samp; - - if constexpr (CHANNEL_COUNT == 6) { - // handle lfe - out_samples[5] += tapped_samp; - } - } - - state.lowpass_0 = current_sample * state.lowpass_2 + state.lowpass_0 * state.lowpass_1; - state.early_delay_line.Tick(state.lowpass_0); - - for (std::size_t i = 0; i < CHANNEL_COUNT; i++) { - out_samples[i] *= state.early_gain; - } - - // Two channel seems to apply a latet gain, we require to save this - f32 filter{}; - for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT; i++) { - filter = state.fdn_delay_line[i].GetOutputSample(); - const auto computed = filter * state.lpf_coefficients[0][i] + state.shelf_filter[i]; - state.shelf_filter[i] = - filter * state.lpf_coefficients[1][i] + computed * state.lpf_coefficients[2][i]; - fsamp[i] = computed; - } - - // Mixing matrix - mixed[0] = fsamp[1] + fsamp[2]; - mixed[1] = -fsamp[0] - fsamp[3]; - mixed[2] = fsamp[0] - fsamp[3]; - mixed[3] = fsamp[1] - fsamp[2]; - - if constexpr (CHANNEL_COUNT == 2) { - for (auto& mix : mixed) { - mix *= (filter * state.late_gain); - } - } - - for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT; i++) { - const auto late = early_tap * state.late_gain; - osamp[i] = state.decay_delay_line0[i].Tick(late + mixed[i]); - osamp[i] = state.decay_delay_line1[i].Tick(osamp[i]); - state.fdn_delay_line[i].Tick(osamp[i]); - } - - if constexpr (CHANNEL_COUNT == 1) { - output[0][sample] = ToS32(state.dry_gain * ToFloat(input[0][sample]) + - (out_samples[0] + osamp[0] + osamp[1])); - } else if constexpr (CHANNEL_COUNT == 2 || CHANNEL_COUNT == 4) { - for (std::size_t i = 0; i < CHANNEL_COUNT; i++) { - output[i][sample] = - ToS32(state.dry_gain * ToFloat(input[i][sample]) + (out_samples[i] + osamp[i])); - } - } else if constexpr (CHANNEL_COUNT == 6) { - const auto temp_center = state.center_delay_line.Tick(0.5f * (osamp[2] - osamp[3])); - for (std::size_t i = 0; i < 4; i++) { - output[i][sample] = - ToS32(state.dry_gain * ToFloat(input[i][sample]) + (out_samples[i] + osamp[i])); - } - output[4][sample] = - ToS32(state.dry_gain * ToFloat(input[4][sample]) + (out_samples[4] + temp_center)); - output[5][sample] = - ToS32(state.dry_gain * ToFloat(input[5][sample]) + (out_samples[5] + osamp[3])); - } - } -} - -} // namespace - -CommandGenerator::CommandGenerator(AudioCommon::AudioRendererParameter& worker_params_, - VoiceContext& voice_context_, MixContext& mix_context_, - SplitterContext& splitter_context_, - EffectContext& effect_context_, Core::Memory::Memory& memory_) - : worker_params(worker_params_), voice_context(voice_context_), mix_context(mix_context_), - splitter_context(splitter_context_), effect_context(effect_context_), memory(memory_), - mix_buffer((worker_params.mix_buffer_count + AudioCommon::MAX_CHANNEL_COUNT) * - worker_params.sample_count), - sample_buffer(MIX_BUFFER_SIZE), - depop_buffer((worker_params.mix_buffer_count + AudioCommon::MAX_CHANNEL_COUNT) * - worker_params.sample_count) {} -CommandGenerator::~CommandGenerator() = default; - -void CommandGenerator::ClearMixBuffers() { - std::fill(mix_buffer.begin(), mix_buffer.end(), 0); - std::fill(sample_buffer.begin(), sample_buffer.end(), 0); - // std::fill(depop_buffer.begin(), depop_buffer.end(), 0); -} - -void CommandGenerator::GenerateVoiceCommands() { - if (dumping_frame) { - LOG_DEBUG(Audio, "(DSP_TRACE) GenerateVoiceCommands"); - } - // Grab all our voices - const auto voice_count = voice_context.GetVoiceCount(); - for (std::size_t i = 0; i < voice_count; i++) { - auto& voice_info = voice_context.GetSortedInfo(i); - // Update voices and check if we should queue them - if (voice_info.ShouldSkip() || !voice_info.UpdateForCommandGeneration(voice_context)) { - continue; - } - - // Queue our voice - GenerateVoiceCommand(voice_info); - } - // Update our splitters - splitter_context.UpdateInternalState(); -} - -void CommandGenerator::GenerateVoiceCommand(ServerVoiceInfo& voice_info) { - auto& in_params = voice_info.GetInParams(); - const auto channel_count = in_params.channel_count; - - for (s32 channel = 0; channel < channel_count; channel++) { - const auto resource_id = in_params.voice_channel_resource_id[channel]; - auto& dsp_state = voice_context.GetDspSharedState(resource_id); - auto& channel_resource = voice_context.GetChannelResource(resource_id); - - // Decode our samples for our channel - GenerateDataSourceCommand(voice_info, dsp_state, channel); - - if (in_params.should_depop) { - in_params.last_volume = 0.0f; - } else if (in_params.splitter_info_id != AudioCommon::NO_SPLITTER || - in_params.mix_id != AudioCommon::NO_MIX) { - // Apply a biquad filter if needed - GenerateBiquadFilterCommandForVoice(voice_info, dsp_state, - worker_params.mix_buffer_count, channel); - // Base voice volume ramping - GenerateVolumeRampCommand(in_params.last_volume, in_params.volume, channel, - in_params.node_id); - in_params.last_volume = in_params.volume; - - if (in_params.mix_id != AudioCommon::NO_MIX) { - // If we're using a mix id - auto& mix_info = mix_context.GetInfo(in_params.mix_id); - const auto& dest_mix_params = mix_info.GetInParams(); - - // Voice Mixing - GenerateVoiceMixCommand( - channel_resource.GetCurrentMixVolume(), channel_resource.GetLastMixVolume(), - dsp_state, dest_mix_params.buffer_offset, dest_mix_params.buffer_count, - worker_params.mix_buffer_count + channel, in_params.node_id); - - // Update last mix volumes - channel_resource.UpdateLastMixVolumes(); - } else if (in_params.splitter_info_id != AudioCommon::NO_SPLITTER) { - s32 base = channel; - while (auto* destination_data = - GetDestinationData(in_params.splitter_info_id, base)) { - base += channel_count; - - if (!destination_data->IsConfigured()) { - continue; - } - if (destination_data->GetMixId() >= static_cast(mix_context.GetCount())) { - continue; - } - - const auto& mix_info = mix_context.GetInfo(destination_data->GetMixId()); - const auto& dest_mix_params = mix_info.GetInParams(); - GenerateVoiceMixCommand( - destination_data->CurrentMixVolumes(), destination_data->LastMixVolumes(), - dsp_state, dest_mix_params.buffer_offset, dest_mix_params.buffer_count, - worker_params.mix_buffer_count + channel, in_params.node_id); - destination_data->MarkDirty(); - } - } - // Update biquad filter enabled states - for (std::size_t i = 0; i < AudioCommon::MAX_BIQUAD_FILTERS; i++) { - in_params.was_biquad_filter_enabled[i] = in_params.biquad_filter[i].enabled; - } - } - } -} - -void CommandGenerator::GenerateSubMixCommands() { - const auto mix_count = mix_context.GetCount(); - for (std::size_t i = 0; i < mix_count; i++) { - auto& mix_info = mix_context.GetSortedInfo(i); - const auto& in_params = mix_info.GetInParams(); - if (!in_params.in_use || in_params.mix_id == AudioCommon::FINAL_MIX) { - continue; - } - GenerateSubMixCommand(mix_info); - } -} - -void CommandGenerator::GenerateFinalMixCommands() { - GenerateFinalMixCommand(); -} - -void CommandGenerator::PreCommand() { - if (!dumping_frame) { - return; - } - for (std::size_t i = 0; i < splitter_context.GetInfoCount(); i++) { - const auto& base = splitter_context.GetInfo(i); - std::string graph = fmt::format("b[{}]", i); - const auto* head = base.GetHead(); - while (head != nullptr) { - graph += fmt::format("->{}", head->GetMixId()); - head = head->GetNextDestination(); - } - LOG_DEBUG(Audio, "(DSP_TRACE) SplitterGraph splitter_info={}, {}", i, graph); - } -} - -void CommandGenerator::PostCommand() { - if (!dumping_frame) { - return; - } - dumping_frame = false; -} - -void CommandGenerator::GenerateDataSourceCommand(ServerVoiceInfo& voice_info, VoiceState& dsp_state, - s32 channel) { - const auto& in_params = voice_info.GetInParams(); - const auto depop = in_params.should_depop; - - if (depop) { - if (in_params.mix_id != AudioCommon::NO_MIX) { - auto& mix_info = mix_context.GetInfo(in_params.mix_id); - const auto& mix_in = mix_info.GetInParams(); - GenerateDepopPrepareCommand(dsp_state, mix_in.buffer_count, mix_in.buffer_offset); - } else if (in_params.splitter_info_id != AudioCommon::NO_SPLITTER) { - s32 index{}; - while (const auto* destination = - GetDestinationData(in_params.splitter_info_id, index++)) { - if (!destination->IsConfigured()) { - continue; - } - auto& mix_info = mix_context.GetInfo(destination->GetMixId()); - const auto& mix_in = mix_info.GetInParams(); - GenerateDepopPrepareCommand(dsp_state, mix_in.buffer_count, mix_in.buffer_offset); - } - } - } else { - switch (in_params.sample_format) { - case SampleFormat::Pcm8: - case SampleFormat::Pcm16: - case SampleFormat::Pcm32: - case SampleFormat::PcmFloat: - DecodeFromWaveBuffers(voice_info, GetChannelMixBuffer(channel), dsp_state, channel, - worker_params.sample_rate, worker_params.sample_count, - in_params.node_id); - break; - case SampleFormat::Adpcm: - ASSERT(channel == 0 && in_params.channel_count == 1); - DecodeFromWaveBuffers(voice_info, GetChannelMixBuffer(0), dsp_state, 0, - worker_params.sample_rate, worker_params.sample_count, - in_params.node_id); - break; - default: - ASSERT_MSG(false, "Unimplemented sample format={}", in_params.sample_format); - } - } -} - -void CommandGenerator::GenerateBiquadFilterCommandForVoice(ServerVoiceInfo& voice_info, - VoiceState& dsp_state, - [[maybe_unused]] s32 mix_buffer_count, - [[maybe_unused]] s32 channel) { - for (std::size_t i = 0; i < AudioCommon::MAX_BIQUAD_FILTERS; i++) { - const auto& in_params = voice_info.GetInParams(); - auto& biquad_filter = in_params.biquad_filter[i]; - // Check if biquad filter is actually used - if (!biquad_filter.enabled) { - continue; - } - - // Reinitialize our biquad filter state if it was enabled previously - if (!in_params.was_biquad_filter_enabled[i]) { - dsp_state.biquad_filter_state.fill(0); - } - - // Generate biquad filter - // GenerateBiquadFilterCommand(mix_buffer_count, biquad_filter, - // dsp_state.biquad_filter_state, - // mix_buffer_count + channel, mix_buffer_count + channel, - // worker_params.sample_count, voice_info.GetInParams().node_id); - } -} - -void CommandGenerator::GenerateBiquadFilterCommand([[maybe_unused]] s32 mix_buffer_id, - const BiquadFilterParameter& params, - std::array& state, - std::size_t input_offset, - std::size_t output_offset, s32 sample_count, - s32 node_id) { - if (dumping_frame) { - LOG_DEBUG(Audio, - "(DSP_TRACE) GenerateBiquadFilterCommand node_id={}, " - "input_mix_buffer={}, output_mix_buffer={}", - node_id, input_offset, output_offset); - } - std::span input = GetMixBuffer(input_offset); - std::span output = GetMixBuffer(output_offset); - - // Biquad filter parameters - const auto [n0, n1, n2] = params.numerator; - const auto [d0, d1] = params.denominator; - - // Biquad filter states - auto [s0, s1] = state; - - constexpr s64 int32_min = std::numeric_limits::min(); - constexpr s64 int32_max = std::numeric_limits::max(); - - for (int i = 0; i < sample_count; ++i) { - const auto sample = static_cast(input[i]); - const auto f = (sample * n0 + s0 + 0x4000) >> 15; - const auto y = std::clamp(f, int32_min, int32_max); - s0 = sample * n1 + y * d0 + s1; - s1 = sample * n2 + y * d1; - output[i] = static_cast(y); - } - - state = {s0, s1}; -} - -void CommandGenerator::GenerateDepopPrepareCommand(VoiceState& dsp_state, - std::size_t mix_buffer_count, - std::size_t mix_buffer_offset) { - for (std::size_t i = 0; i < mix_buffer_count; i++) { - auto& sample = dsp_state.previous_samples[i]; - if (sample != 0) { - depop_buffer[mix_buffer_offset + i] += sample; - sample = 0; - } - } -} - -void CommandGenerator::GenerateDepopForMixBuffersCommand(std::size_t mix_buffer_count, - std::size_t mix_buffer_offset, - s32 sample_rate) { - const std::size_t end_offset = - std::min(mix_buffer_offset + mix_buffer_count, GetTotalMixBufferCount()); - const s32 delta = sample_rate == 48000 ? 0x7B29 : 0x78CB; - for (std::size_t i = mix_buffer_offset; i < end_offset; i++) { - if (depop_buffer[i] == 0) { - continue; - } - - depop_buffer[i] = - ApplyMixDepop(GetMixBuffer(i), depop_buffer[i], delta, worker_params.sample_count); - } -} - -void CommandGenerator::GenerateEffectCommand(ServerMixInfo& mix_info) { - const std::size_t effect_count = effect_context.GetCount(); - const auto buffer_offset = mix_info.GetInParams().buffer_offset; - for (std::size_t i = 0; i < effect_count; i++) { - const auto index = mix_info.GetEffectOrder(i); - if (index == AudioCommon::NO_EFFECT_ORDER) { - break; - } - auto* info = effect_context.GetInfo(index); - const auto type = info->GetType(); - - // TODO(ogniK): Finish remaining effects - switch (type) { - case EffectType::Aux: - GenerateAuxCommand(buffer_offset, info, info->IsEnabled()); - break; - case EffectType::I3dl2Reverb: - GenerateI3dl2ReverbEffectCommand(buffer_offset, info, info->IsEnabled()); - break; - case EffectType::BiquadFilter: - GenerateBiquadFilterEffectCommand(buffer_offset, info, info->IsEnabled()); - break; - default: - break; - } - - info->UpdateForCommandGeneration(); - } -} - -void CommandGenerator::GenerateI3dl2ReverbEffectCommand(s32 mix_buffer_offset, EffectBase* info, - bool enabled) { - auto* reverb = dynamic_cast(info); - const auto& params = reverb->GetParams(); - auto& state = reverb->GetState(); - const auto channel_count = params.channel_count; - - if (channel_count != 1 && channel_count != 2 && channel_count != 4 && channel_count != 6) { - return; - } - - std::array, AudioCommon::MAX_CHANNEL_COUNT> input{}; - std::array, AudioCommon::MAX_CHANNEL_COUNT> output{}; - - const auto status = params.status; - for (s32 i = 0; i < channel_count; i++) { - input[i] = GetMixBuffer(mix_buffer_offset + params.input[i]); - output[i] = GetMixBuffer(mix_buffer_offset + params.output[i]); - } - - if (enabled) { - if (status == ParameterStatus::Initialized) { - InitializeI3dl2Reverb(reverb->GetParams(), state, info->GetWorkBuffer()); - } else if (status == ParameterStatus::Updating) { - UpdateI3dl2Reverb(reverb->GetParams(), state, false); - } - } - - if (enabled) { - switch (channel_count) { - case 1: - ApplyReverbGeneric<1>(state, input, output, worker_params.sample_count); - break; - case 2: - ApplyReverbGeneric<2>(state, input, output, worker_params.sample_count); - break; - case 4: - ApplyReverbGeneric<4>(state, input, output, worker_params.sample_count); - break; - case 6: - ApplyReverbGeneric<6>(state, input, output, worker_params.sample_count); - break; - } - } else { - for (s32 i = 0; i < channel_count; i++) { - // Only copy if the buffer input and output do not match! - if ((mix_buffer_offset + params.input[i]) != (mix_buffer_offset + params.output[i])) { - std::memcpy(output[i].data(), input[i].data(), - worker_params.sample_count * sizeof(s32)); - } - } - } -} - -void CommandGenerator::GenerateBiquadFilterEffectCommand(s32 mix_buffer_offset, EffectBase* info, - bool enabled) { - if (!enabled) { - return; - } - const auto& params = dynamic_cast(info)->GetParams(); - const auto channel_count = params.channel_count; - for (s32 i = 0; i < channel_count; i++) { - // TODO(ogniK): Actually implement biquad filter - if (params.input[i] != params.output[i]) { - std::span input = GetMixBuffer(mix_buffer_offset + params.input[i]); - std::span output = GetMixBuffer(mix_buffer_offset + params.output[i]); - ApplyMix<1>(output, input, 32768, worker_params.sample_count); - } - } -} - -void CommandGenerator::GenerateAuxCommand(s32 mix_buffer_offset, EffectBase* info, bool enabled) { - auto* aux = dynamic_cast(info); - const auto& params = aux->GetParams(); - if (aux->GetSendBuffer() != 0 && aux->GetRecvBuffer() != 0) { - const auto max_channels = params.count; - u32 offset{}; - for (u32 channel = 0; channel < max_channels; channel++) { - u32 write_count = 0; - if (channel == (max_channels - 1)) { - write_count = offset + worker_params.sample_count; - } - - const auto input_index = params.input_mix_buffers[channel] + mix_buffer_offset; - const auto output_index = params.output_mix_buffers[channel] + mix_buffer_offset; - - if (enabled) { - AuxInfoDSP send_info{}; - AuxInfoDSP recv_info{}; - memory.ReadBlock(aux->GetSendInfo(), &send_info, sizeof(AuxInfoDSP)); - memory.ReadBlock(aux->GetRecvInfo(), &recv_info, sizeof(AuxInfoDSP)); - - WriteAuxBuffer(send_info, aux->GetSendBuffer(), params.sample_count, - GetMixBuffer(input_index), worker_params.sample_count, offset, - write_count); - memory.WriteBlock(aux->GetSendInfo(), &send_info, sizeof(AuxInfoDSP)); - - const auto samples_read = ReadAuxBuffer( - recv_info, aux->GetRecvBuffer(), params.sample_count, - GetMixBuffer(output_index), worker_params.sample_count, offset, write_count); - memory.WriteBlock(aux->GetRecvInfo(), &recv_info, sizeof(AuxInfoDSP)); - - if (samples_read != static_cast(worker_params.sample_count) && - samples_read <= params.sample_count) { - std::memset(GetMixBuffer(output_index).data(), 0, - params.sample_count - samples_read); - } - } else { - AuxInfoDSP empty{}; - memory.WriteBlock(aux->GetSendInfo(), &empty, sizeof(AuxInfoDSP)); - memory.WriteBlock(aux->GetRecvInfo(), &empty, sizeof(AuxInfoDSP)); - if (output_index != input_index) { - std::memcpy(GetMixBuffer(output_index).data(), GetMixBuffer(input_index).data(), - worker_params.sample_count * sizeof(s32)); - } - } - - offset += worker_params.sample_count; - } - } -} - -ServerSplitterDestinationData* CommandGenerator::GetDestinationData(s32 splitter_id, s32 index) { - if (splitter_id == AudioCommon::NO_SPLITTER) { - return nullptr; - } - return splitter_context.GetDestinationData(splitter_id, index); -} - -s32 CommandGenerator::WriteAuxBuffer(AuxInfoDSP& dsp_info, VAddr send_buffer, u32 max_samples, - std::span data, u32 sample_count, u32 write_offset, - u32 write_count) { - if (max_samples == 0) { - return 0; - } - u32 offset = dsp_info.write_offset + write_offset; - if (send_buffer == 0 || offset > max_samples) { - return 0; - } - - s32 data_offset{}; - u32 remaining = sample_count; - while (remaining > 0) { - // Get position in buffer - const auto base = send_buffer + (offset * sizeof(u32)); - const auto samples_to_grab = std::min(max_samples - offset, remaining); - // Write to output - memory.WriteBlock(base, (data.data() + data_offset), samples_to_grab * sizeof(u32)); - offset = (offset + samples_to_grab) % max_samples; - remaining -= samples_to_grab; - data_offset += samples_to_grab; - } - - if (write_count != 0) { - dsp_info.write_offset = (dsp_info.write_offset + write_count) % max_samples; - } - return sample_count; -} - -s32 CommandGenerator::ReadAuxBuffer(AuxInfoDSP& recv_info, VAddr recv_buffer, u32 max_samples, - std::span out_data, u32 sample_count, u32 read_offset, - u32 read_count) { - if (max_samples == 0) { - return 0; - } - - u32 offset = recv_info.read_offset + read_offset; - if (recv_buffer == 0 || offset > max_samples) { - return 0; - } - - u32 remaining = sample_count; - s32 data_offset{}; - while (remaining > 0) { - const auto base = recv_buffer + (offset * sizeof(u32)); - const auto samples_to_grab = std::min(max_samples - offset, remaining); - std::vector buffer(samples_to_grab); - memory.ReadBlock(base, buffer.data(), buffer.size() * sizeof(u32)); - std::memcpy(out_data.data() + data_offset, buffer.data(), buffer.size() * sizeof(u32)); - offset = (offset + samples_to_grab) % max_samples; - remaining -= samples_to_grab; - data_offset += samples_to_grab; - } - - if (read_count != 0) { - recv_info.read_offset = (recv_info.read_offset + read_count) % max_samples; - } - return sample_count; -} - -void CommandGenerator::InitializeI3dl2Reverb(I3dl2ReverbParams& info, I3dl2ReverbState& state, - std::vector& work_buffer) { - // Reset state - state.lowpass_0 = 0.0f; - state.lowpass_1 = 0.0f; - state.lowpass_2 = 0.0f; - - state.early_delay_line.Reset(); - state.early_tap_steps.fill(0); - state.early_gain = 0.0f; - state.late_gain = 0.0f; - state.early_to_late_taps = 0; - for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT; i++) { - state.fdn_delay_line[i].Reset(); - state.decay_delay_line0[i].Reset(); - state.decay_delay_line1[i].Reset(); - } - state.last_reverb_echo = 0.0f; - state.center_delay_line.Reset(); - for (auto& coef : state.lpf_coefficients) { - coef.fill(0.0f); - } - state.shelf_filter.fill(0.0f); - state.dry_gain = 0.0f; - - const auto sample_rate = info.sample_rate / 1000; - f32* work_buffer_ptr = reinterpret_cast(work_buffer.data()); - - s32 delay_samples{}; - for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT; i++) { - delay_samples = - AudioCommon::CalculateDelaySamples(sample_rate, FDN_MAX_DELAY_LINE_TIMES[i]); - state.fdn_delay_line[i].Initialize(delay_samples, work_buffer_ptr); - work_buffer_ptr += delay_samples + 1; - - delay_samples = - AudioCommon::CalculateDelaySamples(sample_rate, DECAY0_MAX_DELAY_LINE_TIMES[i]); - state.decay_delay_line0[i].Initialize(delay_samples, 0.0f, work_buffer_ptr); - work_buffer_ptr += delay_samples + 1; - - delay_samples = - AudioCommon::CalculateDelaySamples(sample_rate, DECAY1_MAX_DELAY_LINE_TIMES[i]); - state.decay_delay_line1[i].Initialize(delay_samples, 0.0f, work_buffer_ptr); - work_buffer_ptr += delay_samples + 1; - } - delay_samples = AudioCommon::CalculateDelaySamples(sample_rate, 5.0f); - state.center_delay_line.Initialize(delay_samples, work_buffer_ptr); - work_buffer_ptr += delay_samples + 1; - - delay_samples = AudioCommon::CalculateDelaySamples(sample_rate, 400.0f); - state.early_delay_line.Initialize(delay_samples, work_buffer_ptr); - - UpdateI3dl2Reverb(info, state, true); -} - -void CommandGenerator::UpdateI3dl2Reverb(I3dl2ReverbParams& info, I3dl2ReverbState& state, - bool should_clear) { - - state.dry_gain = info.dry_gain; - state.shelf_filter.fill(0.0f); - state.lowpass_0 = 0.0f; - state.early_gain = Pow10(std::min(info.room + info.reflection, 5000.0f) / 2000.0f); - state.late_gain = Pow10(std::min(info.room + info.reverb, 5000.0f) / 2000.0f); - - const auto sample_rate = info.sample_rate / 1000; - const f32 hf_gain = Pow10(info.room_hf / 2000.0f); - if (hf_gain >= 1.0f) { - state.lowpass_2 = 1.0f; - state.lowpass_1 = 0.0f; - } else { - const auto a = 1.0f - hf_gain; - const auto b = 2.0f * (2.0f - hf_gain * CosD(256.0f * info.hf_reference / - static_cast(info.sample_rate))); - const auto c = std::sqrt(b * b - 4.0f * a * a); - - state.lowpass_1 = (b - c) / (2.0f * a); - state.lowpass_2 = 1.0f - state.lowpass_1; - } - state.early_to_late_taps = AudioCommon::CalculateDelaySamples( - sample_rate, 1000.0f * (info.reflection_delay + info.reverb_delay)); - - state.last_reverb_echo = 0.6f * info.diffusion * 0.01f; - for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT; i++) { - const auto length = - FDN_MIN_DELAY_LINE_TIMES[i] + - (info.density / 100.0f) * (FDN_MAX_DELAY_LINE_TIMES[i] - FDN_MIN_DELAY_LINE_TIMES[i]); - state.fdn_delay_line[i].SetDelay(AudioCommon::CalculateDelaySamples(sample_rate, length)); - - const auto delay_sample_counts = state.fdn_delay_line[i].GetDelay() + - state.decay_delay_line0[i].GetDelay() + - state.decay_delay_line1[i].GetDelay(); - - float a = (-60.0f * static_cast(delay_sample_counts)) / - (info.decay_time * static_cast(info.sample_rate)); - float b = a / info.hf_decay_ratio; - float c = CosD(128.0f * 0.5f * info.hf_reference / static_cast(info.sample_rate)) / - SinD(128.0f * 0.5f * info.hf_reference / static_cast(info.sample_rate)); - float d = Pow10((b - a) / 40.0f); - float e = Pow10((b + a) / 40.0f) * 0.7071f; - - state.lpf_coefficients[0][i] = e * ((d * c) + 1.0f) / (c + d); - state.lpf_coefficients[1][i] = e * (1.0f - (d * c)) / (c + d); - state.lpf_coefficients[2][i] = (c - d) / (c + d); - - state.decay_delay_line0[i].SetCoefficient(state.last_reverb_echo); - state.decay_delay_line1[i].SetCoefficient(-0.9f * state.last_reverb_echo); - } - - if (should_clear) { - for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT; i++) { - state.fdn_delay_line[i].Clear(); - state.decay_delay_line0[i].Clear(); - state.decay_delay_line1[i].Clear(); - } - state.early_delay_line.Clear(); - state.center_delay_line.Clear(); - } - - const auto max_early_delay = state.early_delay_line.GetMaxDelay(); - const auto reflection_time = 1000.0f * (0.9998f * info.reverb_delay + 0.02f); - for (std::size_t tap = 0; tap < AudioCommon::I3DL2REVERB_TAPS; tap++) { - const auto length = AudioCommon::CalculateDelaySamples( - sample_rate, 1000.0f * info.reflection_delay + reflection_time * EARLY_TAP_TIMES[tap]); - state.early_tap_steps[tap] = std::min(length, max_early_delay); - } -} - -void CommandGenerator::GenerateVolumeRampCommand(float last_volume, float current_volume, - s32 channel, s32 node_id) { - const auto last = static_cast(last_volume * 32768.0f); - const auto current = static_cast(current_volume * 32768.0f); - const auto delta = static_cast((static_cast(current) - static_cast(last)) / - static_cast(worker_params.sample_count)); - - if (dumping_frame) { - LOG_DEBUG(Audio, - "(DSP_TRACE) GenerateVolumeRampCommand node_id={}, input={}, output={}, " - "last_volume={}, current_volume={}", - node_id, GetMixChannelBufferOffset(channel), GetMixChannelBufferOffset(channel), - last_volume, current_volume); - } - // Apply generic gain on samples - ApplyGain(GetChannelMixBuffer(channel), GetChannelMixBuffer(channel), last, delta, - worker_params.sample_count); -} - -void CommandGenerator::GenerateVoiceMixCommand(const MixVolumeBuffer& mix_volumes, - const MixVolumeBuffer& last_mix_volumes, - VoiceState& dsp_state, s32 mix_buffer_offset, - s32 mix_buffer_count, s32 voice_index, s32 node_id) { - // Loop all our mix buffers - for (s32 i = 0; i < mix_buffer_count; i++) { - if (last_mix_volumes[i] != 0.0f || mix_volumes[i] != 0.0f) { - const auto delta = static_cast((mix_volumes[i] - last_mix_volumes[i])) / - static_cast(worker_params.sample_count); - - if (dumping_frame) { - LOG_DEBUG(Audio, - "(DSP_TRACE) GenerateVoiceMixCommand node_id={}, input={}, " - "output={}, last_volume={}, current_volume={}", - node_id, voice_index, mix_buffer_offset + i, last_mix_volumes[i], - mix_volumes[i]); - } - - dsp_state.previous_samples[i] = - ApplyMixRamp(GetMixBuffer(mix_buffer_offset + i), GetMixBuffer(voice_index), - last_mix_volumes[i], delta, worker_params.sample_count); - } else { - dsp_state.previous_samples[i] = 0; - } - } -} - -void CommandGenerator::GenerateSubMixCommand(ServerMixInfo& mix_info) { - if (dumping_frame) { - LOG_DEBUG(Audio, "(DSP_TRACE) GenerateSubMixCommand"); - } - const auto& in_params = mix_info.GetInParams(); - GenerateDepopForMixBuffersCommand(in_params.buffer_count, in_params.buffer_offset, - in_params.sample_rate); - - GenerateEffectCommand(mix_info); - - GenerateMixCommands(mix_info); -} - -void CommandGenerator::GenerateMixCommands(ServerMixInfo& mix_info) { - if (!mix_info.HasAnyConnection()) { - return; - } - const auto& in_params = mix_info.GetInParams(); - if (in_params.dest_mix_id != AudioCommon::NO_MIX) { - const auto& dest_mix = mix_context.GetInfo(in_params.dest_mix_id); - const auto& dest_in_params = dest_mix.GetInParams(); - - const auto buffer_count = in_params.buffer_count; - - for (s32 i = 0; i < buffer_count; i++) { - for (s32 j = 0; j < dest_in_params.buffer_count; j++) { - const auto mixed_volume = in_params.volume * in_params.mix_volume[i][j]; - if (mixed_volume != 0.0f) { - GenerateMixCommand(dest_in_params.buffer_offset + j, - in_params.buffer_offset + i, mixed_volume, - in_params.node_id); - } - } - } - } else if (in_params.splitter_id != AudioCommon::NO_SPLITTER) { - s32 base{}; - while (const auto* destination_data = GetDestinationData(in_params.splitter_id, base++)) { - if (!destination_data->IsConfigured()) { - continue; - } - - const auto& dest_mix = mix_context.GetInfo(destination_data->GetMixId()); - const auto& dest_in_params = dest_mix.GetInParams(); - const auto mix_index = (base - 1) % in_params.buffer_count + in_params.buffer_offset; - for (std::size_t i = 0; i < static_cast(dest_in_params.buffer_count); - i++) { - const auto mixed_volume = in_params.volume * destination_data->GetMixVolume(i); - if (mixed_volume != 0.0f) { - GenerateMixCommand(dest_in_params.buffer_offset + i, mix_index, mixed_volume, - in_params.node_id); - } - } - } - } -} - -void CommandGenerator::GenerateMixCommand(std::size_t output_offset, std::size_t input_offset, - float volume, s32 node_id) { - - if (dumping_frame) { - LOG_DEBUG(Audio, - "(DSP_TRACE) GenerateMixCommand node_id={}, input={}, output={}, volume={}", - node_id, input_offset, output_offset, volume); - } - - std::span output = GetMixBuffer(output_offset); - std::span input = GetMixBuffer(input_offset); - - const s32 gain = static_cast(volume * 32768.0f); - // Mix with loop unrolling - if (worker_params.sample_count % 4 == 0) { - ApplyMix<4>(output, input, gain, worker_params.sample_count); - } else if (worker_params.sample_count % 2 == 0) { - ApplyMix<2>(output, input, gain, worker_params.sample_count); - } else { - ApplyMix<1>(output, input, gain, worker_params.sample_count); - } -} - -void CommandGenerator::GenerateFinalMixCommand() { - if (dumping_frame) { - LOG_DEBUG(Audio, "(DSP_TRACE) GenerateFinalMixCommand"); - } - auto& mix_info = mix_context.GetFinalMixInfo(); - const auto& in_params = mix_info.GetInParams(); - - GenerateDepopForMixBuffersCommand(in_params.buffer_count, in_params.buffer_offset, - in_params.sample_rate); - - GenerateEffectCommand(mix_info); - - for (s32 i = 0; i < in_params.buffer_count; i++) { - const s32 gain = static_cast(in_params.volume * 32768.0f); - if (dumping_frame) { - LOG_DEBUG( - Audio, - "(DSP_TRACE) ApplyGainWithoutDelta node_id={}, input={}, output={}, volume={}", - in_params.node_id, in_params.buffer_offset + i, in_params.buffer_offset + i, - in_params.volume); - } - ApplyGainWithoutDelta(GetMixBuffer(in_params.buffer_offset + i), - GetMixBuffer(in_params.buffer_offset + i), gain, - worker_params.sample_count); - } -} - -template -s32 CommandGenerator::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) { - const auto& in_params = voice_info.GetInParams(); - const auto& wave_buffer = in_params.wave_buffer[dsp_state.wave_buffer_index]; - if (wave_buffer.buffer_address == 0) { - return 0; - } - if (wave_buffer.buffer_size == 0) { - return 0; - } - if (sample_end_offset < sample_start_offset) { - return 0; - } - const auto samples_remaining = (sample_end_offset - sample_start_offset) - dsp_state.offset; - const auto start_offset = - ((dsp_state.offset + sample_start_offset) * in_params.channel_count) * sizeof(T); - const auto buffer_pos = wave_buffer.buffer_address + start_offset; - const auto samples_processed = std::min(sample_count, samples_remaining); - - const auto channel_count = in_params.channel_count; - std::vector buffer(samples_processed * channel_count); - memory.ReadBlock(buffer_pos, buffer.data(), buffer.size() * sizeof(T)); - - if constexpr (std::is_floating_point_v) { - for (std::size_t i = 0; i < static_cast(samples_processed); i++) { - sample_buffer[mix_offset + i] = static_cast(buffer[i * channel_count + channel] * - std::numeric_limits::max()); - } - } else if constexpr (sizeof(T) == 1) { - for (std::size_t i = 0; i < static_cast(samples_processed); i++) { - sample_buffer[mix_offset + i] = - static_cast(static_cast(buffer[i * channel_count + channel] / - std::numeric_limits::max()) * - std::numeric_limits::max()); - } - } else if constexpr (sizeof(T) == 2) { - for (std::size_t i = 0; i < static_cast(samples_processed); i++) { - sample_buffer[mix_offset + i] = buffer[i * channel_count + channel]; - } - } else { - for (std::size_t i = 0; i < static_cast(samples_processed); i++) { - sample_buffer[mix_offset + i] = - static_cast(static_cast(buffer[i * channel_count + channel] / - std::numeric_limits::max()) * - std::numeric_limits::max()); - } - } - - return samples_processed; -} - -s32 CommandGenerator::DecodeAdpcm(ServerVoiceInfo& voice_info, VoiceState& dsp_state, - s32 sample_start_offset, s32 sample_end_offset, s32 sample_count, - [[maybe_unused]] s32 channel, std::size_t mix_offset) { - const auto& in_params = voice_info.GetInParams(); - const auto& wave_buffer = in_params.wave_buffer[dsp_state.wave_buffer_index]; - if (wave_buffer.buffer_address == 0) { - return 0; - } - if (wave_buffer.buffer_size == 0) { - return 0; - } - if (sample_end_offset < sample_start_offset) { - return 0; - } - - static constexpr std::array SIGNED_NIBBLES{ - 0, 1, 2, 3, 4, 5, 6, 7, -8, -7, -6, -5, -4, -3, -2, -1, - }; - - constexpr std::size_t FRAME_LEN = 8; - constexpr std::size_t NIBBLES_PER_SAMPLE = 16; - constexpr std::size_t SAMPLES_PER_FRAME = 14; - - auto frame_header = dsp_state.context.header; - s32 idx = (frame_header >> 4) & 0xf; - s32 scale = frame_header & 0xf; - s16 yn1 = dsp_state.context.yn1; - s16 yn2 = dsp_state.context.yn2; - - Codec::ADPCM_Coeff coeffs; - memory.ReadBlock(in_params.additional_params_address, coeffs.data(), - sizeof(Codec::ADPCM_Coeff)); - - s32 coef1 = coeffs[idx * 2]; - s32 coef2 = coeffs[idx * 2 + 1]; - - const auto samples_remaining = (sample_end_offset - sample_start_offset) - dsp_state.offset; - const auto samples_processed = std::min(sample_count, samples_remaining); - const auto sample_pos = dsp_state.offset + sample_start_offset; - - const auto samples_remaining_in_frame = sample_pos % SAMPLES_PER_FRAME; - auto position_in_frame = ((sample_pos / SAMPLES_PER_FRAME) * NIBBLES_PER_SAMPLE) + - samples_remaining_in_frame + (samples_remaining_in_frame != 0 ? 2 : 0); - - const auto decode_sample = [&](const int nibble) -> s16 { - const int xn = nibble * (1 << 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(val, -32768, 32767); - // Advance output feedback. - yn2 = yn1; - yn1 = static_cast(val); - return yn1; - }; - - std::size_t buffer_offset{}; - std::vector buffer( - std::max((samples_processed / FRAME_LEN) * SAMPLES_PER_FRAME, FRAME_LEN)); - memory.ReadBlock(wave_buffer.buffer_address + (position_in_frame / 2), buffer.data(), - buffer.size()); - std::size_t cur_mix_offset = mix_offset; - - auto remaining_samples = samples_processed; - while (remaining_samples > 0) { - if (position_in_frame % NIBBLES_PER_SAMPLE == 0) { - // Read header - frame_header = buffer[buffer_offset++]; - idx = (frame_header >> 4) & 0xf; - scale = frame_header & 0xf; - coef1 = coeffs[idx * 2]; - coef2 = coeffs[idx * 2 + 1]; - position_in_frame += 2; - - // Decode entire frame - if (remaining_samples >= static_cast(SAMPLES_PER_FRAME)) { - for (std::size_t i = 0; i < SAMPLES_PER_FRAME / 2; i++) { - // Sample 1 - const s32 s0 = SIGNED_NIBBLES[buffer[buffer_offset] >> 4]; - const s32 s1 = SIGNED_NIBBLES[buffer[buffer_offset++] & 0xf]; - const s16 sample_1 = decode_sample(s0); - const s16 sample_2 = decode_sample(s1); - sample_buffer[cur_mix_offset++] = sample_1; - sample_buffer[cur_mix_offset++] = sample_2; - } - remaining_samples -= static_cast(SAMPLES_PER_FRAME); - position_in_frame += SAMPLES_PER_FRAME; - continue; - } - } - // Decode mid frame - s32 current_nibble = buffer[buffer_offset]; - if (position_in_frame++ & 0x1) { - current_nibble &= 0xf; - buffer_offset++; - } else { - current_nibble >>= 4; - } - const s16 sample = decode_sample(SIGNED_NIBBLES[current_nibble]); - sample_buffer[cur_mix_offset++] = sample; - remaining_samples--; - } - - dsp_state.context.header = frame_header; - dsp_state.context.yn1 = yn1; - dsp_state.context.yn2 = yn2; - - return samples_processed; -} - -std::span CommandGenerator::GetMixBuffer(std::size_t index) { - return std::span(mix_buffer.data() + (index * worker_params.sample_count), - worker_params.sample_count); -} - -std::span CommandGenerator::GetMixBuffer(std::size_t index) const { - return std::span(mix_buffer.data() + (index * worker_params.sample_count), - worker_params.sample_count); -} - -std::size_t CommandGenerator::GetMixChannelBufferOffset(s32 channel) const { - return worker_params.mix_buffer_count + channel; -} - -std::size_t CommandGenerator::GetTotalMixBufferCount() const { - return worker_params.mix_buffer_count + AudioCommon::MAX_CHANNEL_COUNT; -} - -std::span CommandGenerator::GetChannelMixBuffer(s32 channel) { - return GetMixBuffer(worker_params.mix_buffer_count + channel); -} - -std::span CommandGenerator::GetChannelMixBuffer(s32 channel) const { - return GetMixBuffer(worker_params.mix_buffer_count + channel); -} - -void CommandGenerator::DecodeFromWaveBuffers(ServerVoiceInfo& voice_info, std::span output, - VoiceState& dsp_state, s32 channel, - s32 target_sample_rate, s32 sample_count, - s32 node_id) { - const auto& in_params = voice_info.GetInParams(); - if (dumping_frame) { - LOG_DEBUG(Audio, - "(DSP_TRACE) DecodeFromWaveBuffers, node_id={}, channel={}, " - "format={}, sample_count={}, sample_rate={}, mix_id={}, splitter_id={}", - node_id, channel, in_params.sample_format, sample_count, in_params.sample_rate, - in_params.mix_id, in_params.splitter_info_id); - } - ASSERT_OR_EXECUTE(output.data() != nullptr, { return; }); - - const auto resample_rate = static_cast( - static_cast(in_params.sample_rate) / static_cast(target_sample_rate) * - static_cast(static_cast(in_params.pitch * 32768.0f))); - if (dsp_state.fraction + sample_count * resample_rate > - static_cast(SCALED_MIX_BUFFER_SIZE - 4ULL)) { - return; - } - - auto min_required_samples = - std::min(static_cast(SCALED_MIX_BUFFER_SIZE) - dsp_state.fraction, resample_rate); - if (min_required_samples >= sample_count) { - min_required_samples = sample_count; - } - - std::size_t temp_mix_offset{}; - s32 samples_output{}; - auto samples_remaining = sample_count; - while (samples_remaining > 0) { - const auto samples_to_output = std::min(samples_remaining, min_required_samples); - const auto samples_to_read = (samples_to_output * resample_rate + dsp_state.fraction) >> 15; - - if (!in_params.behavior_flags.is_pitch_and_src_skipped) { - // Append sample histtory for resampler - for (std::size_t i = 0; i < AudioCommon::MAX_SAMPLE_HISTORY; i++) { - sample_buffer[temp_mix_offset + i] = dsp_state.sample_history[i]; - } - temp_mix_offset += 4; - } - - s32 samples_read{}; - while (samples_read < samples_to_read) { - const auto& wave_buffer = in_params.wave_buffer[dsp_state.wave_buffer_index]; - // No more data can be read - if (!dsp_state.is_wave_buffer_valid[dsp_state.wave_buffer_index]) { - break; - } - - if (in_params.sample_format == SampleFormat::Adpcm && dsp_state.offset == 0 && - wave_buffer.context_address != 0 && wave_buffer.context_size != 0) { - memory.ReadBlock(wave_buffer.context_address, &dsp_state.context, - sizeof(ADPCMContext)); - } - - s32 samples_offset_start; - s32 samples_offset_end; - if (dsp_state.loop_count > 0 && wave_buffer.loop_start_sample != 0 && - wave_buffer.loop_end_sample != 0 && - wave_buffer.loop_start_sample <= wave_buffer.loop_end_sample) { - samples_offset_start = wave_buffer.loop_start_sample; - samples_offset_end = wave_buffer.loop_end_sample; - } else { - samples_offset_start = wave_buffer.start_sample_offset; - samples_offset_end = wave_buffer.end_sample_offset; - } - - s32 samples_decoded{0}; - switch (in_params.sample_format) { - case SampleFormat::Pcm8: - samples_decoded = - DecodePcm(voice_info, dsp_state, samples_offset_start, samples_offset_end, - samples_to_read - samples_read, channel, temp_mix_offset); - break; - case SampleFormat::Pcm16: - samples_decoded = - DecodePcm(voice_info, dsp_state, samples_offset_start, samples_offset_end, - samples_to_read - samples_read, channel, temp_mix_offset); - break; - case SampleFormat::Pcm32: - samples_decoded = - DecodePcm(voice_info, dsp_state, samples_offset_start, samples_offset_end, - samples_to_read - samples_read, channel, temp_mix_offset); - break; - case SampleFormat::PcmFloat: - samples_decoded = - DecodePcm(voice_info, dsp_state, samples_offset_start, samples_offset_end, - samples_to_read - samples_read, channel, temp_mix_offset); - break; - case SampleFormat::Adpcm: - samples_decoded = - DecodeAdpcm(voice_info, dsp_state, samples_offset_start, samples_offset_end, - samples_to_read - samples_read, channel, temp_mix_offset); - break; - default: - ASSERT_MSG(false, "Unimplemented sample format={}", in_params.sample_format); - } - - temp_mix_offset += samples_decoded; - samples_read += samples_decoded; - dsp_state.offset += samples_decoded; - dsp_state.played_sample_count += samples_decoded; - - if (dsp_state.offset >= (samples_offset_end - samples_offset_start) || - samples_decoded == 0) { - // Reset our sample offset - dsp_state.offset = 0; - if (wave_buffer.is_looping) { - dsp_state.loop_count++; - if (wave_buffer.loop_count > 0 && - (dsp_state.loop_count > wave_buffer.loop_count || samples_decoded == 0)) { - // End of our buffer - voice_info.SetWaveBufferCompleted(dsp_state, wave_buffer); - } - - if (samples_decoded == 0) { - break; - } - - if (in_params.behavior_flags.is_played_samples_reset_at_loop_point.Value()) { - dsp_state.played_sample_count = 0; - } - } else { - // Update our wave buffer states - voice_info.SetWaveBufferCompleted(dsp_state, wave_buffer); - } - } - } - - if (in_params.behavior_flags.is_pitch_and_src_skipped.Value()) { - // No need to resample - std::memcpy(output.data() + samples_output, sample_buffer.data(), - samples_read * sizeof(s32)); - } else { - std::fill(sample_buffer.begin() + temp_mix_offset, - sample_buffer.begin() + temp_mix_offset + (samples_to_read - samples_read), - 0); - AudioCore::Resample(output.data() + samples_output, sample_buffer.data(), resample_rate, - dsp_state.fraction, samples_to_output); - // Resample - for (std::size_t i = 0; i < AudioCommon::MAX_SAMPLE_HISTORY; i++) { - dsp_state.sample_history[i] = sample_buffer[samples_to_read + i]; - } - } - samples_remaining -= samples_to_output; - samples_output += samples_to_output; - } -} - -} // namespace AudioCore diff --git a/src/audio_core/command_generator.h b/src/audio_core/command_generator.h deleted file mode 100644 index 8077e7768..000000000 --- a/src/audio_core/command_generator.h +++ /dev/null @@ -1,110 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include -#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; - -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 GetChannelMixBuffer(s32 channel); - [[nodiscard]] std::span GetChannelMixBuffer(s32 channel) const; - [[nodiscard]] std::span GetMixBuffer(std::size_t index); - [[nodiscard]] std::span 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& 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 data, u32 sample_count, u32 write_offset, - u32 write_count); - s32 ReadAuxBuffer(AuxInfoDSP& recv_info, VAddr recv_buffer, u32 max_samples, - std::span out_data, u32 sample_count, u32 read_offset, u32 read_count); - - void InitializeI3dl2Reverb(I3dl2ReverbParams& info, I3dl2ReverbState& state, - std::vector& work_buffer); - void UpdateI3dl2Reverb(I3dl2ReverbParams& info, I3dl2ReverbState& state, bool should_clear); - // DSP Code - template - 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 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 mix_buffer{}; - std::vector sample_buffer{}; - std::vector depop_buffer{}; - bool dumping_frame{false}; -}; -} // namespace AudioCore diff --git a/src/audio_core/common.h b/src/audio_core/common.h deleted file mode 100644 index 056a0ac70..000000000 --- a/src/audio_core/common.h +++ /dev/null @@ -1,132 +0,0 @@ -// 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(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::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 -constexpr Fractional ToFractional(T x) { - return static_cast(x * static_cast(0x4000)); -} - -constexpr Fractional MultiplyFractional(Fractional lhs, Fractional rhs) { - return static_cast(static_cast(lhs) * rhs >> 14); -} - -constexpr s32 FractionalToFixed(Fractional x) { - const auto s = x & (1 << 13); - return static_cast(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 diff --git a/src/audio_core/common/audio_renderer_parameter.h b/src/audio_core/common/audio_renderer_parameter.h new file mode 100644 index 000000000..2f62c383b --- /dev/null +++ b/src/audio_core/common/audio_renderer_parameter.h @@ -0,0 +1,60 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#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 depop_buffer; + AudioRenderer::UpsamplerManager* upsampler_manager; + AudioRenderer::MemoryPoolInfo* memory_pool_info; +}; + +} // namespace AudioCore diff --git a/src/audio_core/common/common.h b/src/audio_core/common/common.h new file mode 100644 index 000000000..6abd9be45 --- /dev/null +++ b/src/audio_core/common/common.h @@ -0,0 +1,138 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#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::min(); +constexpr s32 UnusedSplitterId = -1; +constexpr s32 UnusedMixId = std::numeric_limits::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 inputs, std::span outputs) { + constexpr auto old_center{static_cast(OldChannels::Center)}; + constexpr auto new_center{static_cast(Channels::Center)}; + constexpr auto old_lfe{static_cast(OldChannels::LFE)}; + constexpr auto new_lfe{static_cast(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 diff --git a/src/audio_core/common/feature_support.h b/src/audio_core/common/feature_support.h new file mode 100644 index 000000000..55c9e690d --- /dev/null +++ b/src/audio_core/common/feature_support.h @@ -0,0 +1,105 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include + +#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, static_cast(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(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 diff --git a/src/audio_core/common/wave_buffer.h b/src/audio_core/common/wave_buffer.h new file mode 100644 index 000000000..fc478ef79 --- /dev/null +++ b/src/audio_core/common/wave_buffer.h @@ -0,0 +1,35 @@ +// 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 diff --git a/src/audio_core/common/workbuffer_allocator.h b/src/audio_core/common/workbuffer_allocator.h new file mode 100644 index 000000000..fb89f97fe --- /dev/null +++ b/src/audio_core/common/workbuffer_allocator.h @@ -0,0 +1,100 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#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 buffer_, u64 size_) + : buffer{reinterpret_cast(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 + std::span 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(reinterpret_cast(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 diff --git a/src/audio_core/cubeb_sink.cpp b/src/audio_core/cubeb_sink.cpp deleted file mode 100644 index e48c1ee8e..000000000 --- a/src/audio_core/cubeb_sink.cpp +++ /dev/null @@ -1,249 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include -#include -#include -#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 -#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& 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 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(left + (clev * center / 1000) + - (slev * surround_left / 1000))); - buf.push_back(static_cast(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 device_list; - - cubeb* ctx{}; - cubeb_stream* stream_backend{}; - u32 num_channels{}; - - Common::RingBuffer queue; - std::array last_frame{}; - std::atomic 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(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(user_data); - auto* buffer = static_cast(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 ListCubebSinkDevices() { - std::vector 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 diff --git a/src/audio_core/cubeb_sink.h b/src/audio_core/cubeb_sink.h deleted file mode 100644 index c124b7ee8..000000000 --- a/src/audio_core/cubeb_sink.h +++ /dev/null @@ -1,35 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include - -#include - -#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 sink_streams; - -#ifdef _WIN32 - u32 com_init_result = 0; -#endif -}; - -std::vector ListCubebSinkDevices(); - -} // namespace AudioCore diff --git a/src/audio_core/delay_line.cpp b/src/audio_core/delay_line.cpp deleted file mode 100644 index b1626a71b..000000000 --- a/src/audio_core/delay_line.cpp +++ /dev/null @@ -1,107 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include -#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 diff --git a/src/audio_core/delay_line.h b/src/audio_core/delay_line.h deleted file mode 100644 index 05fda536f..000000000 --- a/src/audio_core/delay_line.h +++ /dev/null @@ -1,49 +0,0 @@ -// 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 diff --git a/src/audio_core/device/audio_buffer.h b/src/audio_core/device/audio_buffer.h new file mode 100644 index 000000000..cae7fa970 --- /dev/null +++ b/src/audio_core/device/audio_buffer.h @@ -0,0 +1,21 @@ +// 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 diff --git a/src/audio_core/device/audio_buffers.h b/src/audio_core/device/audio_buffers.h new file mode 100644 index 000000000..5d1979ea0 --- /dev/null +++ b/src/audio_core/device/audio_buffers.h @@ -0,0 +1,304 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include + +#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 +class AudioBuffers { +public: + explicit AudioBuffers(size_t limit) : append_limit{static_cast(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& 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 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& 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(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(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(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 buffers_flushed{}; + + buffers_released = GetRegisteredAppendedBuffers(buffers_flushed, append_limit); + + if (registered_count > 0) { + return false; + } + + if (static_cast(released_count + appended_count) > append_limit) { + return false; + } + + return true; + } + +private: + /// Buffer lock + mutable std::recursive_mutex lock{}; + /// The audio buffers + std::array 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 diff --git a/src/audio_core/device/device_session.cpp b/src/audio_core/device/device_session.cpp new file mode 100644 index 000000000..095fc96ce --- /dev/null +++ b/src/audio_core/device/device_session.cpp @@ -0,0 +1,114 @@ +// 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 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 samples{}; + stream->AppendBuffer(new_buffer, samples); + } else { + std::vector 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 diff --git a/src/audio_core/device/device_session.h b/src/audio_core/device/device_session.h new file mode 100644 index 000000000..4a031b765 --- /dev/null +++ b/src/audio_core/device/device_session.h @@ -0,0 +1,126 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#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 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 diff --git a/src/audio_core/effect_context.cpp b/src/audio_core/effect_context.cpp deleted file mode 100644 index 79bcd1192..000000000 --- a/src/audio_core/effect_context.cpp +++ /dev/null @@ -1,320 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include -#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(); }); -} -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(); - break; - case EffectType::BufferMixer: - effects[i] = std::make_unique(); - break; - case EffectType::Aux: - effects[i] = std::make_unique(); - break; - case EffectType::Delay: - effects[i] = std::make_unique(); - break; - case EffectType::Reverb: - effects[i] = std::make_unique(); - break; - case EffectType::I3dl2Reverb: - effects[i] = std::make_unique(); - break; - case EffectType::BiquadFilter: - effects[i] = std::make_unique(); - break; - default: - ASSERT_MSG(false, "Unimplemented effect {}", effect); - effects[i] = std::make_unique(); - } - 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& EffectBase::GetWorkBuffer() { - return work_buffer; -} - -const std::vector& 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(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(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(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(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(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(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 diff --git a/src/audio_core/effect_context.h b/src/audio_core/effect_context.h deleted file mode 100644 index cb47df472..000000000 --- a/src/audio_core/effect_context.h +++ /dev/null @@ -1,349 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include -#include -#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 input{}; - std::array output{}; - std::array 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 input_mix_buffers{}; - std::array output_mix_buffers{}; - u32_le count{}; - s32_le sample_rate{}; - s32_le sample_count{}; - s32_le mix_buffer_count{}; - u64_le send_buffer_info{}; - u64_le send_buffer_base{}; - - u64_le return_buffer_info{}; - u64_le return_buffer_base{}; -}; -static_assert(sizeof(AuxInfo) == 0x60, "AuxInfo is an invalid size"); - -struct I3dl2ReverbParams { - std::array input{}; - std::array 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 input{}; - std::array output{}; - std::array numerator; - std::array denominator; - s8 channel_count{}; - ParameterStatus status{}; -}; -static_assert(sizeof(BiquadFilterParams) == 0x18, "BiquadFilterParams is an invalid size"); - -struct DelayParams { - std::array input{}; - std::array 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 input{}; - std::array 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 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& GetWorkBuffer(); - [[nodiscard]] const std::vector& GetWorkBuffer() const; - -protected: - UsageState usage{UsageState::Invalid}; - EffectType effect_type{}; - s32 mix_id{}; - s32 processing_order{}; - bool enabled = false; - std::vector work_buffer{}; -}; - -template -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 early_tap_steps{}; - f32 early_gain{}; - f32 late_gain{}; - - u32 early_to_late_taps{}; - std::array fdn_delay_line{}; - std::array decay_delay_line0{}; - std::array decay_delay_line1{}; - f32 last_reverb_echo{}; - DelayLineBase center_delay_line{}; - std::array, 3> lpf_coefficients{}; - std::array shelf_filter{}; - f32 dry_gain{}; -}; - -class EffectI3dl2Reverb : public EffectGeneric { -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 { -public: - explicit EffectBiquadFilter(); - ~EffectBiquadFilter() override; - - void Update(EffectInfo::InParams& in_params) override; - void UpdateForCommandGeneration() override; -}; - -class EffectAuxInfo : public EffectGeneric { -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 { -public: - explicit EffectDelay(); - ~EffectDelay() override; - - void Update(EffectInfo::InParams& in_params) override; - void UpdateForCommandGeneration() override; - -private: - bool skipped = false; -}; - -class EffectBufferMixer : public EffectGeneric { -public: - explicit EffectBufferMixer(); - ~EffectBufferMixer() override; - - void Update(EffectInfo::InParams& in_params) override; - void UpdateForCommandGeneration() override; -}; - -class EffectReverb : public EffectGeneric { -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> effects; -}; -} // namespace AudioCore diff --git a/src/audio_core/in/audio_in.cpp b/src/audio_core/in/audio_in.cpp new file mode 100644 index 000000000..c946895d6 --- /dev/null +++ b/src/audio_core/in/audio_in.cpp @@ -0,0 +1,100 @@ +// 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 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 diff --git a/src/audio_core/in/audio_in.h b/src/audio_core/in/audio_in.h new file mode 100644 index 000000000..6253891d5 --- /dev/null +++ b/src/audio_core/in/audio_in.h @@ -0,0 +1,147 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#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 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 diff --git a/src/audio_core/in/audio_in_system.cpp b/src/audio_core/in/audio_in_system.cpp new file mode 100644 index 000000000..ec5d37ed4 --- /dev/null +++ b/src/audio_core/in/audio_in_system.cpp @@ -0,0 +1,213 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#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(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 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 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 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 diff --git a/src/audio_core/in/audio_in_system.h b/src/audio_core/in/audio_in_system.h new file mode 100644 index 000000000..165e35d83 --- /dev/null +++ b/src/audio_core/in/audio_in_system.h @@ -0,0 +1,275 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include + +#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 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 session; + /// Audio buffers in use by this system + AudioBuffers 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::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 diff --git a/src/audio_core/info_updater.cpp b/src/audio_core/info_updater.cpp deleted file mode 100644 index 0065e6e53..000000000 --- a/src/audio_core/info_updater.cpp +++ /dev/null @@ -1,511 +0,0 @@ -// 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& in_params_, std::vector& out_params_, - BehaviorInfo& behavior_info_) - : in_params(in_params_), out_params(out_params_), behavior_info(behavior_info_) { - ASSERT( - AudioCommon::CanConsumeBuffer(in_params.size(), 0, sizeof(AudioCommon::UpdateDataHeader))); - std::memcpy(&input_header, in_params.data(), sizeof(AudioCommon::UpdateDataHeader)); - output_header.total_size = sizeof(AudioCommon::UpdateDataHeader); -} - -InfoUpdater::~InfoUpdater() = default; - -bool InfoUpdater::UpdateBehaviorInfo(BehaviorInfo& in_behavior_info) { - if (input_header.size.behavior != sizeof(BehaviorInfo::InParams)) { - LOG_ERROR(Audio, "Behavior info is an invalid size, expecting 0x{:X} but got 0x{:X}", - sizeof(BehaviorInfo::InParams), input_header.size.behavior); - return false; - } - - if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, - sizeof(BehaviorInfo::InParams))) { - LOG_ERROR(Audio, "Buffer is an invalid size!"); - return false; - } - - BehaviorInfo::InParams behavior_in{}; - std::memcpy(&behavior_in, in_params.data() + input_offset, sizeof(BehaviorInfo::InParams)); - input_offset += sizeof(BehaviorInfo::InParams); - - // Make sure it's an audio revision we can actually support - if (!AudioCommon::IsValidRevision(behavior_in.revision)) { - LOG_ERROR(Audio, "Invalid input revision, revision=0x{:08X}", behavior_in.revision); - return false; - } - - // Make sure that our behavior info revision matches the input - if (in_behavior_info.GetUserRevision() != behavior_in.revision) { - LOG_ERROR(Audio, - "User revision differs from input revision, expecting 0x{:08X} but got 0x{:08X}", - in_behavior_info.GetUserRevision(), behavior_in.revision); - return false; - } - - // Update behavior info flags - in_behavior_info.ClearError(); - in_behavior_info.UpdateFlags(behavior_in.flags); - - return true; -} - -bool InfoUpdater::UpdateMemoryPools(std::vector& memory_pool_info) { - const auto memory_pool_count = memory_pool_info.size(); - const auto total_memory_pool_in = sizeof(ServerMemoryPoolInfo::InParams) * memory_pool_count; - const auto total_memory_pool_out = sizeof(ServerMemoryPoolInfo::OutParams) * memory_pool_count; - - if (input_header.size.memory_pool != total_memory_pool_in) { - LOG_ERROR(Audio, "Memory pools are an invalid size, expecting 0x{:X} but got 0x{:X}", - total_memory_pool_in, input_header.size.memory_pool); - return false; - } - - if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, total_memory_pool_in)) { - LOG_ERROR(Audio, "Buffer is an invalid size!"); - return false; - } - - std::vector mempool_in(memory_pool_count); - std::vector mempool_out(memory_pool_count); - - std::memcpy(mempool_in.data(), in_params.data() + input_offset, total_memory_pool_in); - input_offset += total_memory_pool_in; - - // Update our memory pools - for (std::size_t i = 0; i < memory_pool_count; i++) { - if (!memory_pool_info[i].Update(mempool_in[i], mempool_out[i])) { - LOG_ERROR(Audio, "Failed to update memory pool {}!", i); - return false; - } - } - - if (!AudioCommon::CanConsumeBuffer(out_params.size(), output_offset, - sizeof(BehaviorInfo::InParams))) { - LOG_ERROR(Audio, "Buffer is an invalid size!"); - return false; - } - - std::memcpy(out_params.data() + output_offset, mempool_out.data(), total_memory_pool_out); - output_offset += total_memory_pool_out; - output_header.size.memory_pool = static_cast(total_memory_pool_out); - return true; -} - -bool InfoUpdater::UpdateVoiceChannelResources(VoiceContext& voice_context) { - const auto voice_count = voice_context.GetVoiceCount(); - const auto voice_size = voice_count * sizeof(VoiceChannelResource::InParams); - std::vector resources_in(voice_count); - - if (input_header.size.voice_channel_resource != voice_size) { - LOG_ERROR(Audio, "VoiceChannelResource is an invalid size, expecting 0x{:X} but got 0x{:X}", - voice_size, input_header.size.voice_channel_resource); - return false; - } - - if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, voice_size)) { - LOG_ERROR(Audio, "Buffer is an invalid size!"); - return false; - } - - std::memcpy(resources_in.data(), in_params.data() + input_offset, voice_size); - input_offset += voice_size; - - // Update our channel resources - for (std::size_t i = 0; i < voice_count; i++) { - // Grab our channel resource - auto& resource = voice_context.GetChannelResource(i); - resource.Update(resources_in[i]); - } - - return true; -} - -bool InfoUpdater::UpdateVoices(VoiceContext& voice_context, - [[maybe_unused]] std::vector& memory_pool_info, - [[maybe_unused]] VAddr audio_codec_dsp_addr) { - const auto voice_count = voice_context.GetVoiceCount(); - std::vector voice_in(voice_count); - std::vector voice_out(voice_count); - - const auto voice_in_size = voice_count * sizeof(VoiceInfo::InParams); - const auto voice_out_size = voice_count * sizeof(VoiceInfo::OutParams); - - if (input_header.size.voice != voice_in_size) { - LOG_ERROR(Audio, "Voices are an invalid size, expecting 0x{:X} but got 0x{:X}", - voice_in_size, input_header.size.voice); - return false; - } - - if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, voice_in_size)) { - LOG_ERROR(Audio, "Buffer is an invalid size!"); - return false; - } - - std::memcpy(voice_in.data(), in_params.data() + input_offset, voice_in_size); - input_offset += voice_in_size; - - // Set all voices to not be in use - for (std::size_t i = 0; i < voice_count; i++) { - voice_context.GetInfo(i).GetInParams().in_use = false; - } - - // Update our voices - for (std::size_t i = 0; i < voice_count; i++) { - auto& voice_in_params = voice_in[i]; - const auto channel_count = static_cast(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 voice_states{}; - ASSERT(static_cast(voice_in_params.id) < voice_count); - - // Grab our current voice info - auto& voice_info = voice_context.GetInfo(static_cast(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(voice_out_size); - return true; -} - -bool InfoUpdater::UpdateEffects(EffectContext& effect_context, bool is_active) { - const auto effect_count = effect_context.GetCount(); - std::vector effect_in(effect_count); - std::vector effect_out(effect_count); - - const auto total_effect_in = effect_count * sizeof(EffectInfo::InParams); - const auto total_effect_out = effect_count * sizeof(EffectInfo::OutParams); - - if (input_header.size.effect != total_effect_in) { - LOG_ERROR(Audio, "Effects are an invalid size, expecting 0x{:X} but got 0x{:X}", - total_effect_in, input_header.size.effect); - return false; - } - - if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, total_effect_in)) { - LOG_ERROR(Audio, "Buffer is an invalid size!"); - return false; - } - - std::memcpy(effect_in.data(), in_params.data() + input_offset, total_effect_in); - input_offset += total_effect_in; - - // Update effects - for (std::size_t i = 0; i < effect_count; i++) { - auto* info = effect_context.GetInfo(i); - 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(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 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(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 sink_in_params(sink_count); - const auto total_sink_in = sink_count * sizeof(SinkInfo::InParams); - - if (input_header.size.sink != total_sink_in) { - LOG_ERROR(Audio, "Sinks are an invalid size, expecting 0x{:X} but got 0x{:X}", - total_sink_in, input_header.size.effect); - return false; - } - - if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, total_sink_in)) { - LOG_ERROR(Audio, "Buffer is an invalid size!"); - return false; - } - - std::memcpy(sink_in_params.data(), in_params.data() + input_offset, total_sink_in); - input_offset += total_sink_in; - - // TODO(ogniK): Properly update sinks - if (!sink_in_params.empty()) { - sink_context.UpdateMainSink(sink_in_params[0]); - } - - output_header.size.sink = static_cast(0x20 * sink_count); - output_offset += 0x20 * sink_count; - return true; -} - -bool InfoUpdater::UpdatePerformanceBuffer() { - output_header.size.performance = 0x10; - output_offset += 0x10; - return true; -} - -bool InfoUpdater::UpdateErrorInfo([[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 diff --git a/src/audio_core/info_updater.h b/src/audio_core/info_updater.h deleted file mode 100644 index 17e66b036..000000000 --- a/src/audio_core/info_updater.h +++ /dev/null @@ -1,57 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include "audio_core/common.h" -#include "common/common_types.h" - -namespace AudioCore { - -class BehaviorInfo; -class ServerMemoryPoolInfo; -class VoiceContext; -class EffectContext; -class MixContext; -class SinkContext; -class SplitterContext; - -class InfoUpdater { -public: - // TODO(ogniK): Pass process handle when we support it - InfoUpdater(const std::vector& in_params_, std::vector& out_params_, - BehaviorInfo& behavior_info_); - ~InfoUpdater(); - - bool UpdateBehaviorInfo(BehaviorInfo& in_behavior_info); - bool UpdateMemoryPools(std::vector& memory_pool_info); - bool UpdateVoiceChannelResources(VoiceContext& voice_context); - bool UpdateVoices(VoiceContext& voice_context, - std::vector& memory_pool_info, - VAddr audio_codec_dsp_addr); - bool UpdateEffects(EffectContext& effect_context, bool is_active); - bool UpdateSplitterInfo(SplitterContext& splitter_context); - 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& in_params; - std::vector& out_params; - BehaviorInfo& behavior_info; - - AudioCommon::UpdateDataHeader input_header{}; - AudioCommon::UpdateDataHeader output_header{}; - - std::size_t input_offset{sizeof(AudioCommon::UpdateDataHeader)}; - std::size_t output_offset{sizeof(AudioCommon::UpdateDataHeader)}; -}; - -} // namespace AudioCore diff --git a/src/audio_core/memory_pool.cpp b/src/audio_core/memory_pool.cpp deleted file mode 100644 index 627e5f15e..000000000 --- a/src/audio_core/memory_pool.cpp +++ /dev/null @@ -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 diff --git a/src/audio_core/memory_pool.h b/src/audio_core/memory_pool.h deleted file mode 100644 index e71bc025b..000000000 --- a/src/audio_core/memory_pool.h +++ /dev/null @@ -1,51 +0,0 @@ -// 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 diff --git a/src/audio_core/mix_context.cpp b/src/audio_core/mix_context.cpp deleted file mode 100644 index bcaa7afab..000000000 --- a/src/audio_core/mix_context.cpp +++ /dev/null @@ -1,297 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include - -#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(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(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(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 diff --git a/src/audio_core/mix_context.h b/src/audio_core/mix_context.h deleted file mode 100644 index 3939c77e9..000000000 --- a/src/audio_core/mix_context.h +++ /dev/null @@ -1,113 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include -#include "audio_core/common.h" -#include "audio_core/splitter_context.h" -#include "common/common_funcs.h" -#include "common/common_types.h" - -namespace AudioCore { -class BehaviorInfo; -class 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, AudioCommon::MAX_MIX_BUFFERS> - mix_volume{}; - s32_le dest_mix_id{}; - s32_le splitter_id{}; - INSERT_PADDING_WORDS(1); - }; - static_assert(sizeof(MixInfo::InParams) == 0x930, "MixInfo::InParams is an invalid size"); -}; - -class ServerMixInfo { -public: - struct InParams { - float volume{}; - s32 sample_rate{}; - s32 buffer_count{}; - bool in_use{}; - s32 mix_id{}; - u32 node_id{}; - std::array, AudioCommon::MAX_MIX_BUFFERS> - mix_volume{}; - s32 dest_mix_id{}; - s32 splitter_id{}; - s32 buffer_offset{}; - s32 final_mix_distance{}; - }; - ServerMixInfo(); - ~ServerMixInfo(); - - [[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 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 infos{}; - std::vector sorted_info{}; -}; -} // namespace AudioCore diff --git a/src/audio_core/null_sink.h b/src/audio_core/null_sink.h deleted file mode 100644 index 37b2f7eff..000000000 --- a/src/audio_core/null_sink.h +++ /dev/null @@ -1,32 +0,0 @@ -// 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& /*samples*/) override {} - - std::size_t SamplesInQueue(u32 /*num_channels*/) const override { - return 0; - } - - void Flush() override {} - } null_sink_stream; -}; - -} // namespace AudioCore diff --git a/src/audio_core/out/audio_out.cpp b/src/audio_core/out/audio_out.cpp new file mode 100644 index 000000000..9a8d8a742 --- /dev/null +++ b/src/audio_core/out/audio_out.cpp @@ -0,0 +1,100 @@ +// 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 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 diff --git a/src/audio_core/out/audio_out.h b/src/audio_core/out/audio_out.h new file mode 100644 index 000000000..f6b921645 --- /dev/null +++ b/src/audio_core/out/audio_out.h @@ -0,0 +1,147 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#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 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 diff --git a/src/audio_core/out/audio_out_system.cpp b/src/audio_core/out/audio_out_system.cpp new file mode 100644 index 000000000..35afddf06 --- /dev/null +++ b/src/audio_core/out/audio_out_system.cpp @@ -0,0 +1,207 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include + +#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(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 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 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 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 diff --git a/src/audio_core/out/audio_out_system.h b/src/audio_core/out/audio_out_system.h new file mode 100644 index 000000000..4ca2f3417 --- /dev/null +++ b/src/audio_core/out/audio_out_system.h @@ -0,0 +1,257 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include + +#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 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 session; + /// Audio buffers in use by this system + AudioBuffers 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::Stopped}; + /// Name of this system + std::string name{}; + /// Volume of this system + f32 volume{1.0f}; +}; + +} // namespace AudioCore::AudioOut diff --git a/src/audio_core/renderer/adsp/adsp.cpp b/src/audio_core/renderer/adsp/adsp.cpp new file mode 100644 index 000000000..e05a22d86 --- /dev/null +++ b/src/audio_core/renderer/adsp/adsp.cpp @@ -0,0 +1,118 @@ +// 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(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(RenderMessage::AudioRenderer_RenderResponse), + static_cast(response)); + } + + ClearCommandBuffers(); +} + +void ADSP::ClearCommandBuffers() { + render_mailbox.ClearCommandBuffers(); +} + +} // namespace AudioCore::AudioRenderer::ADSP diff --git a/src/audio_core/renderer/adsp/adsp.h b/src/audio_core/renderer/adsp/adsp.h new file mode 100644 index 000000000..4dfcef4a5 --- /dev/null +++ b/src/audio_core/renderer/adsp/adsp.h @@ -0,0 +1,173 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#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 running{false}; + /// Output sink used by the ADSP + Sink::Sink& sink; + /// AudioRenderer app + std::unique_ptr 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 diff --git a/src/audio_core/renderer/adsp/audio_renderer.cpp b/src/audio_core/renderer/adsp/audio_renderer.cpp new file mode 100644 index 000000000..3967ccfe6 --- /dev/null +++ b/src/audio_core/renderer/adsp/audio_renderer.cpp @@ -0,0 +1,226 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include + +#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 buffers_reset{}; + std::array 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(message)); + break; + } + } +} + +} // namespace AudioCore::AudioRenderer::ADSP diff --git a/src/audio_core/renderer/adsp/audio_renderer.h b/src/audio_core/renderer/adsp/audio_renderer.h new file mode 100644 index 000000000..b6ced9d2b --- /dev/null +++ b/src/audio_core/renderer/adsp/audio_renderer.h @@ -0,0 +1,203 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include + +#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 host_messages{}; + /// AudioRenderer message queue + + Common::ReaderWriterQueue adsp_messages{}; + /// Command buffers + + std::array 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 running{}; + /// The active mailbox + AudioRenderer_Mailbox* mailbox{}; + /// The command lists to process + std::array command_list_processors{}; + /// The output sink the AudioRenderer will use + Sink::Sink& sink; + /// The streams which will receive the processed samples + std::array streams; +}; + +} // namespace AudioRenderer::ADSP +} // namespace AudioCore diff --git a/src/audio_core/renderer/adsp/command_buffer.h b/src/audio_core/renderer/adsp/command_buffer.h new file mode 100644 index 000000000..880b279d8 --- /dev/null +++ b/src/audio_core/renderer/adsp/command_buffer.h @@ -0,0 +1,21 @@ +// 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 diff --git a/src/audio_core/renderer/adsp/command_list_processor.cpp b/src/audio_core/renderer/adsp/command_list_processor.cpp new file mode 100644 index 000000000..e3bf2d7ec --- /dev/null +++ b/src/audio_core/renderer/adsp/command_list_processor.cpp @@ -0,0 +1,109 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include + +#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(buffer); + commands = reinterpret_cast(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(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(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 diff --git a/src/audio_core/renderer/adsp/command_list_processor.h b/src/audio_core/renderer/adsp/command_list_processor.h new file mode 100644 index 000000000..3f99173e3 --- /dev/null +++ b/src/audio_core/renderer/adsp/command_list_processor.h @@ -0,0 +1,118 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#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 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 diff --git a/src/audio_core/renderer/audio_device.cpp b/src/audio_core/renderer/audio_device.cpp new file mode 100644 index 000000000..d5886e55e --- /dev/null +++ b/src/audio_core/renderer/audio_device.cpp @@ -0,0 +1,52 @@ +// 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& out_buffer, + const size_t max_count) { + std::span names{}; + + if (CheckFeatureSupported(SupportTags::AudioUsbDeviceOutput, user_revision)) { + names = usb_device_names; + } else { + names = device_names; + } + + u32 out_count{static_cast(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& out_buffer, + const size_t max_count) { + u32 out_count{static_cast(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 diff --git a/src/audio_core/renderer/audio_device.h b/src/audio_core/renderer/audio_device.h new file mode 100644 index 000000000..1f449f261 --- /dev/null +++ b/src/audio_core/renderer/audio_device.h @@ -0,0 +1,88 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#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 name; + + AudioDeviceName(const char* name_) { + std::strncpy(name.data(), name_, name.size()); + } + }; + + std::array usb_device_names{"AudioStereoJackOutput", + "AudioBuiltInSpeakerOutput", "AudioTvOutput", + "AudioUsbDeviceOutput"}; + std::array device_names{"AudioStereoJackOutput", + "AudioBuiltInSpeakerOutput", "AudioTvOutput"}; + std::array 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& 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& 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 diff --git a/src/audio_core/renderer/audio_renderer.cpp b/src/audio_core/renderer/audio_renderer.cpp new file mode 100644 index 000000000..51aa17599 --- /dev/null +++ b/src/audio_core/renderer/audio_renderer.cpp @@ -0,0 +1,67 @@ +// 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 input, std::span performance, + std::span output) { + return system.Update(input, performance, output); +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/audio_renderer.h b/src/audio_core/renderer/audio_renderer.h new file mode 100644 index 000000000..90c6f9727 --- /dev/null +++ b/src/audio_core/renderer/audio_renderer.h @@ -0,0 +1,97 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#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 input, std::span performance, + std::span 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 diff --git a/src/audio_core/renderer/behavior/behavior_info.cpp b/src/audio_core/renderer/behavior/behavior_info.cpp new file mode 100644 index 000000000..c5d4d66d8 --- /dev/null +++ b/src/audio_core/renderer/behavior/behavior_info.cpp @@ -0,0 +1,191 @@ +// 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(static_cast('0') + process_revision)); +} + +u32 BehaviorInfo::GetUserRevisionNum() const { + return user_revision; +} + +u32 BehaviorInfo::GetUserRevision() const { + return Common::MakeMagic('R', 'E', 'V', + static_cast(static_cast('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 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 diff --git a/src/audio_core/renderer/behavior/behavior_info.h b/src/audio_core/renderer/behavior/behavior_info.h new file mode 100644 index 000000000..7333c297f --- /dev/null +++ b/src/audio_core/renderer/behavior/behavior_info.h @@ -0,0 +1,376 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#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 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 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 errors{}; + /// Error count + u32 error_count{}; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/behavior/info_updater.cpp b/src/audio_core/renderer/behavior/info_updater.cpp new file mode 100644 index 000000000..06a37e1a6 --- /dev/null +++ b/src/audio_core/renderer/behavior/info_updater.cpp @@ -0,0 +1,539 @@ +// 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 input_, std::span 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( + input_origin.data())}, + out_header{reinterpret_cast(output_origin.data())}, + expected_input_size{input_.size()}, expected_output_size{output_.size()}, + process_handle{process_handle_}, behaviour{behaviour_} { + std::construct_at(out_header, behaviour.GetProcessRevision()); +} + +Result InfoUpdater::UpdateVoiceChannelResources(VoiceContext& voice_context) { + const auto voice_count{voice_context.GetCount()}; + std::span in_params{ + reinterpret_cast(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(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 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 in_params{ + reinterpret_cast(input), voice_count}; + std::span out_params{reinterpret_cast(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 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, 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(sizeof(VoiceInfo::InParameter))}; + auto consumed_output_size{voice_count * static_cast(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 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 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 in_params{ + reinterpret_cast(input), effect_count}; + std::span out_params{ + reinterpret_cast(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(sizeof(EffectInfoBase::InParameterVersion1))}; + auto consumed_output_size{effect_count * + static_cast(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 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 in_params{ + reinterpret_cast(input), effect_count}; + std::span out_params{ + reinterpret_cast(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(sizeof(EffectInfoBase::InParameterVersion2))}; + auto consumed_output_size{effect_count * + static_cast(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(input)}; + mix_count = in_dirty_params->count; + input += sizeof(MixInfo::InDirtyParameter); + consumed_input_size = static_cast(sizeof(MixInfo::InDirtyParameter) + + mix_count * sizeof(MixInfo::InParameter)); + } else { + mix_count = mix_context.GetCount(); + consumed_input_size = static_cast(mix_count * sizeof(MixInfo::InParameter)); + } + + if (mix_buffer_count == 0) { + return Service::Audio::ERR_INVALID_UPDATE_DATA; + } + + std::span in_params{ + reinterpret_cast(input), static_cast(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(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 memory_pools, + const u32 memory_pool_count) { + PoolMapper pool_mapper(process_handle, memory_pools, memory_pool_count, + behaviour.IsMemoryForceMappingEnabled()); + + std::span in_params{ + reinterpret_cast(input), memory_pool_count}; + std::span out_params{ + reinterpret_cast(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(reinterpret_cast(sink_info)); + break; + case SinkInfoBase::Type::DeviceSink: + std::construct_at(reinterpret_cast(sink_info)); + break; + case SinkInfoBase::Type::CircularBufferSink: + std::construct_at( + reinterpret_cast(sink_info)); + break; + default: + LOG_ERROR(Service_Audio, "Invalid sink type {}", static_cast(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(sizeof(SinkInfoBase::InParameter))}; + const auto consumed_output_size{sink_count * static_cast(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 memory_pools, + const u32 memory_pool_count) { + PoolMapper pool_mapper(process_handle, memory_pools, memory_pool_count, + behaviour.IsMemoryForceMappingEnabled()); + std::span in_params{ + reinterpret_cast(input), memory_pool_count}; + std::span out_params{ + reinterpret_cast(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(sizeof(MemoryPoolInfo::InParameter))}; + const auto consumed_output_size{memory_pool_count * + static_cast(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 performance_output, + const u64 performance_output_size, + PerformanceManager* performance_manager) { + auto in_params{reinterpret_cast(input)}; + auto out_params{reinterpret_cast(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(sizeof(PerformanceManager::InParameter))}; + const auto consumed_output_size{static_cast(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(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(output)}; + behaviour_.CopyErrorInfo(out_params->errors, out_params->error_count); + + const auto consumed_output_size{static_cast(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(output)}; + out_params->frames_elapsed = elapsed_frames; + + const auto consumed_output_size{static_cast(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 diff --git a/src/audio_core/renderer/behavior/info_updater.h b/src/audio_core/renderer/behavior/info_updater.h new file mode 100644 index 000000000..f0b445d9c --- /dev/null +++ b/src/audio_core/renderer/behavior/info_updater.h @@ -0,0 +1,205 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#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 input, std::span 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 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 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 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 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 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 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 memory_pools, u32 memory_pool_count); + + /// Input buffer + u8 const* input; + /// Input buffer start + std::span input_origin; + /// Output buffer start + u8* output; + /// Output buffer start + std::span 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 diff --git a/src/audio_core/renderer/command/command_buffer.cpp b/src/audio_core/renderer/command/command_buffer.cpp new file mode 100644 index 000000000..40074cf14 --- /dev/null +++ b/src/audio_core/renderer/command/command_buffer.cpp @@ -0,0 +1,714 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/behavior/behavior_info.h" +#include "audio_core/renderer/command/command_buffer.h" +#include "audio_core/renderer/command/command_list_header.h" +#include "audio_core/renderer/command/command_processing_time_estimator.h" +#include "audio_core/renderer/effect/biquad_filter.h" +#include "audio_core/renderer/effect/delay.h" +#include "audio_core/renderer/effect/reverb.h" +#include "audio_core/renderer/memory/memory_pool_info.h" +#include "audio_core/renderer/mix/mix_info.h" +#include "audio_core/renderer/sink/circular_buffer_sink_info.h" +#include "audio_core/renderer/sink/device_sink_info.h" +#include "audio_core/renderer/sink/sink_info_base.h" +#include "audio_core/renderer/voice/voice_info.h" +#include "audio_core/renderer/voice/voice_state.h" + +namespace AudioCore::AudioRenderer { + +template +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(reinterpret_cast(&command_list[size]))}; + + cmd.magic = CommandMagic; + cmd.enabled = true; + cmd.type = Id; + cmd.size = sizeof(T); + cmd.node_id = node_id; + + return cmd; +} + +template +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( + 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(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( + 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(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( + 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(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( + 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(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(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(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(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(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(node_id)}; + + cmd.precision = precision; + cmd.input_index = buffer_offset + input_index; + cmd.output_index = buffer_offset + input_index; + cmd.volume = volume; + + GenerateEnd(cmd); +} + +void CommandBuffer::GenerateVolumeRampCommand(const s32 node_id, VoiceInfo& voice_info, + const s16 buffer_count, const u8 precision) { + auto& cmd{GenerateStart(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(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(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(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(node_id)}; + + const auto& parameter{ + *reinterpret_cast(effect_info.GetParameter())}; + const auto state{ + reinterpret_cast(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(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(node_id)}; + + cmd.input_index = input_index; + cmd.output_index = output_index; + cmd.volume = volume; + cmd.precision = precision; + + GenerateEnd(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(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(cmd); +} + +void CommandBuffer::GenerateMixRampGroupedCommand(const s32 node_id, const s16 buffer_count, + const s16 input_index, s16 output_index, + std::span volumes, + std::span prev_volumes, + const CpuAddr prev_samples, const u8 precision) { + auto& cmd{GenerateStart(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(cmd); +} + +void CommandBuffer::GenerateDepopPrepareCommand(const s32 node_id, const VoiceState& voice_state, + std::span buffer, const s16 buffer_count, + s16 buffer_offset, const bool was_playing) { + auto& cmd{GenerateStart(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(cmd); +} + +void CommandBuffer::GenerateDepopForMixBuffersCommand(const s32 node_id, const MixInfo& mix_info, + std::span depop_buffer) { + auto& cmd{GenerateStart(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(cmd); +} + +void CommandBuffer::GenerateDelayCommand(const s32 node_id, EffectInfoBase& effect_info, + const s16 buffer_offset) { + auto& cmd{GenerateStart(node_id)}; + + const auto& parameter{ + *reinterpret_cast(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(cmd); +} + +void CommandBuffer::GenerateUpsampleCommand(const s32 node_id, const s16 buffer_offset, + UpsamplerInfo& upsampler_info, const u32 input_count, + std::span inputs, const s16 buffer_count, + const u32 sample_count_, const u32 sample_rate_) { + auto& cmd{GenerateStart(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(cmd); +} + +void CommandBuffer::GenerateDownMix6chTo2chCommand(const s32 node_id, std::span inputs, + const s16 buffer_offset, + std::span downmix_coeff) { + auto& cmd{GenerateStart(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(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(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(cmd); +} + +void CommandBuffer::GenerateDeviceSinkCommand(const s32 node_id, const s16 buffer_offset, + SinkInfoBase& sink_info, const u32 session_id, + std::span samples_buffer) { + auto& cmd{GenerateStart(node_id)}; + const auto& parameter{ + *reinterpret_cast(sink_info.GetParameter())}; + auto state{*reinterpret_cast(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(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(cmd); +} + +void CommandBuffer::GenerateCircularBufferSinkCommand(const s32 node_id, SinkInfoBase& sink_info, + const s16 buffer_offset) { + auto& cmd{GenerateStart(node_id)}; + const auto& parameter{*reinterpret_cast( + sink_info.GetParameter())}; + auto state{ + *reinterpret_cast(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(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(node_id)}; + + const auto& parameter{ + *reinterpret_cast(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(cmd); +} + +void CommandBuffer::GenerateI3dl2ReverbCommand(const s32 node_id, EffectInfoBase& effect_info, + const s16 buffer_offset) { + auto& cmd{GenerateStart(node_id)}; + + const auto& parameter{ + *reinterpret_cast(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(cmd); +} + +void CommandBuffer::GeneratePerformanceCommand(const s32 node_id, const PerformanceState state, + const PerformanceEntryAddresses& entry_addresses) { + auto& cmd{GenerateStart(node_id)}; + + cmd.state = state; + cmd.entry_address = entry_addresses; + + GenerateEnd(cmd); +} + +void CommandBuffer::GenerateClearMixCommand(const s32 node_id) { + auto& cmd{GenerateStart(node_id)}; + GenerateEnd(cmd); +} + +void CommandBuffer::GenerateCopyMixBufferCommand(const s32 node_id, EffectInfoBase& effect_info, + const s16 buffer_offset, const s8 channel) { + auto& cmd{GenerateStart(node_id)}; + + const auto& parameter{ + *reinterpret_cast(effect_info.GetParameter())}; + cmd.input_index = buffer_offset + parameter.inputs[channel]; + cmd.output_index = buffer_offset + parameter.outputs[channel]; + + GenerateEnd(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(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(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(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(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(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(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(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(cmd); +} + +void CommandBuffer::GenerateCompressorCommand(s16 buffer_offset, EffectInfoBase& effect_info, + s32 node_id) { + auto& cmd{GenerateStart(node_id)}; + + auto& parameter{ + *reinterpret_cast(effect_info.GetParameter())}; + auto state{reinterpret_cast(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(cmd); +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/command_buffer.h b/src/audio_core/renderer/command/command_buffer.h new file mode 100644 index 000000000..496b0e50a --- /dev/null +++ b/src/audio_core/renderer/command/command_buffer.h @@ -0,0 +1,466 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#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 volumes, + std::span 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 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 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 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 inputs, s16 buffer_offset, + std::span 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 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 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 + T& GenerateStart(const s32 node_id); + template + void GenerateEnd(T& cmd); +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/command_generator.cpp b/src/audio_core/renderer/command/command_generator.cpp new file mode 100644 index 000000000..2ea50d128 --- /dev/null +++ b/src/audio_core/renderer/command/command_generator.cpp @@ -0,0 +1,796 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/common/audio_renderer_parameter.h" +#include "audio_core/renderer/behavior/behavior_info.h" +#include "audio_core/renderer/command/command_buffer.h" +#include "audio_core/renderer/command/command_generator.h" +#include "audio_core/renderer/command/command_list_header.h" +#include "audio_core/renderer/effect/aux_.h" +#include "audio_core/renderer/effect/biquad_filter.h" +#include "audio_core/renderer/effect/buffer_mixer.h" +#include "audio_core/renderer/effect/capture.h" +#include "audio_core/renderer/effect/effect_context.h" +#include "audio_core/renderer/effect/light_limiter.h" +#include "audio_core/renderer/mix/mix_context.h" +#include "audio_core/renderer/performance/detail_aspect.h" +#include "audio_core/renderer/performance/entry_aspect.h" +#include "audio_core/renderer/sink/device_sink_info.h" +#include "audio_core/renderer/sink/sink_context.h" +#include "audio_core/renderer/splitter/splitter_context.h" +#include "audio_core/renderer/voice/voice_context.h" +#include "common/alignment.h" + +namespace AudioCore::AudioRenderer { + +CommandGenerator::CommandGenerator(CommandBuffer& command_buffer_, + const CommandListHeader& command_list_header_, + const AudioRendererSystemContext& render_context_, + VoiceContext& voice_context_, MixContext& mix_context_, + EffectContext& effect_context_, SinkContext& sink_context_, + SplitterContext& splitter_context_, + PerformanceManager* performance_manager_) + : command_buffer{command_buffer_}, command_header{command_list_header_}, + render_context{render_context_}, voice_context{voice_context_}, mix_context{mix_context_}, + effect_context{effect_context_}, sink_context{sink_context_}, + splitter_context{splitter_context_}, performance_manager{performance_manager_} { + command_buffer.GenerateClearMixCommand(InvalidNodeId); +} + +void CommandGenerator::GenerateDataSourceCommand(VoiceInfo& voice_info, + const VoiceState& voice_state, const s8 channel) { + if (voice_info.mix_id == UnusedMixId) { + if (voice_info.splitter_id != UnusedSplitterId) { + auto destination{splitter_context.GetDesintationData(voice_info.splitter_id, 0)}; + u32 dest_id{0}; + while (destination != nullptr) { + if (destination->IsConfigured()) { + auto mix_id{destination->GetMixId()}; + if (mix_id < mix_context.GetCount()) { + auto mix_info{mix_context.GetInfo(mix_id)}; + command_buffer.GenerateDepopPrepareCommand( + voice_info.node_id, voice_state, render_context.depop_buffer, + mix_info->buffer_count, mix_info->buffer_offset, + voice_info.was_playing); + } + } + dest_id++; + destination = splitter_context.GetDesintationData(voice_info.splitter_id, dest_id); + } + } + } else { + auto mix_info{mix_context.GetInfo(voice_info.mix_id)}; + command_buffer.GenerateDepopPrepareCommand( + voice_info.node_id, voice_state, render_context.depop_buffer, mix_info->buffer_count, + mix_info->buffer_offset, voice_info.was_playing); + } + + if (voice_info.was_playing) { + return; + } + + if (render_context.behavior->IsWaveBufferVer2Supported()) { + switch (voice_info.sample_format) { + case SampleFormat::PcmInt16: + command_buffer.GeneratePcmInt16Version2Command( + voice_info.node_id, voice_info, voice_state, render_context.mix_buffer_count, + channel); + break; + case SampleFormat::PcmFloat: + command_buffer.GeneratePcmFloatVersion2Command( + voice_info.node_id, voice_info, voice_state, render_context.mix_buffer_count, + channel); + break; + case SampleFormat::Adpcm: + command_buffer.GenerateAdpcmVersion2Command(voice_info.node_id, voice_info, voice_state, + render_context.mix_buffer_count, channel); + break; + default: + LOG_ERROR(Service_Audio, "Invalid SampleFormat {}", + static_cast(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(voice_info.sample_format)); + break; + } + } +} + +void CommandGenerator::GenerateVoiceMixCommand(std::span mix_volumes, + std::span 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(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(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(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(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(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(effect_info.GetStateBuffer())}; + + if (render_context.behavior->IsEffectInfoVersion2Supported()) { + const auto& parameter{ + *reinterpret_cast(effect_info.GetParameter())}; + const auto& result_state{*reinterpret_cast( + &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(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(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(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(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(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(sink_info.GetType())); + break; + } + + sink_info.UpdateForCommandGeneration(); +} + +void CommandGenerator::GenerateDeviceSinkCommand(const s16 buffer_offset, SinkInfoBase& sink_info) { + auto& parameter{ + *reinterpret_cast(sink_info.GetParameter())}; + auto state{*reinterpret_cast(sink_info.GetState())}; + + if (render_context.channels == 2 && parameter.downmix_enabled) { + command_buffer.GenerateDownMix6chTo2chCommand(InvalidNodeId, parameter.inputs, + buffer_offset, parameter.downmix_coeff); + } + + if (state.upsampler_info != nullptr) { + command_buffer.GenerateUpsampleCommand( + InvalidNodeId, buffer_offset, *state.upsampler_info, parameter.input_count, + parameter.inputs, command_header.buffer_count, command_header.sample_count, + command_header.sample_rate); + } + + command_buffer.GenerateDeviceSinkCommand(InvalidNodeId, buffer_offset, sink_info, + render_context.session_id, + command_header.samples_buffer); +} + +void CommandGenerator::GeneratePerformanceCommand( + s32 node_id, PerformanceState state, const PerformanceEntryAddresses& entry_addresses) { + command_buffer.GeneratePerformanceCommand(node_id, state, entry_addresses); +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/command_generator.h b/src/audio_core/renderer/command/command_generator.h new file mode 100644 index 000000000..d80d9b0d8 --- /dev/null +++ b/src/audio_core/renderer/command/command_generator.h @@ -0,0 +1,349 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#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 mix_volumes, + std::span prev_mix_volumes, + const VoiceState& voice_state, s16 output_index, s16 buffer_count, + s16 input_index, s32 node_id); + + /** + * Generate a biquad filter command for a voice. + * + * @param voice_info - Voice info this command is generated from. + * @param voice_state - State used by the AudioRenderer across calls. + * @param buffer_count - Number of active mix buffers. + * @param channel - Channel index of this command. + * @param node_id - Node id of the voice this command is generated for. + */ + void GenerateBiquadFilterCommandForVoice(VoiceInfo& voice_info, const VoiceState& voice_state, + s16 buffer_count, s8 channel, s32 node_id); + + /** + * Generate commands for a voice. + * Includes a data source, biquad filter, volume and mixing. + * + * @param voice_info - Voice info these commands are generated from. + */ + void GenerateVoiceCommand(VoiceInfo& voice_info); + + /** + * Generate commands for all voices. + */ + void GenerateVoiceCommands(); + + /** + * Generate a mixing command. + * + * @param buffer_offset - Base mix buffer offset to use. + * @param effect_info_base - BufferMixer effect info. + * @param node_id - Node id of the mix this command is generated for. + */ + void GenerateBufferMixerCommand(s16 buffer_offset, EffectInfoBase& effect_info_base, + s32 node_id); + + /** + * Generate a delay effect command. + * + * @param buffer_offset - Base mix buffer offset to use. + * @param effect_info_base - Delay effect info. + * @param node_id - Node id of the mix this command is generated for. + */ + void GenerateDelayCommand(s16 buffer_offset, EffectInfoBase& effect_info_base, s32 node_id); + + /** + * Generate a reverb effect command. + * + * @param buffer_offset - Base mix buffer offset to use. + * @param effect_info_base - Reverb effect info. + * @param node_id - Node id of the mix this command is generated for. + * @param long_size_pre_delay_supported - Use a longer pre-delay time before reverb starts. + */ + void GenerateReverbCommand(s16 buffer_offset, EffectInfoBase& effect_info_base, s32 node_id, + bool long_size_pre_delay_supported); + + /** + * Generate an I3DL2 reverb effect command. + * + * @param buffer_offset - Base mix buffer offset to use. + * @param effect_info_base - I3DL2Reverb effect info. + * @param node_id - Node id of the mix this command is generated for. + */ + void GenerateI3dl2ReverbEffectCommand(s16 buffer_offset, EffectInfoBase& effect_info, + s32 node_id); + + /** + * Generate an aux effect command. + * + * @param buffer_offset - Base mix buffer offset to use. + * @param effect_info_base - Aux effect info. + * @param node_id - Node id of the mix this command is generated for. + */ + void GenerateAuxCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id); + + /** + * Generate a biquad filter effect command. + * + * @param buffer_offset - Base mix buffer offset to use. + * @param effect_info_base - Aux effect info. + * @param node_id - Node id of the mix this command is generated for. + */ + void GenerateBiquadFilterEffectCommand(s16 buffer_offset, EffectInfoBase& effect_info, + s32 node_id); + + /** + * Generate a light limiter effect command. + * + * @param buffer_offset - Base mix buffer offset to use. + * @param effect_info_base - Limiter effect info. + * @param node_id - Node id of the mix this command is generated for. + * @param effect_index - Index for the statistics state. + */ + void GenerateLightLimiterEffectCommand(s16 buffer_offset, EffectInfoBase& effect_info, + s32 node_id, u32 effect_index); + + /** + * Generate a capture effect command. + * Writes a mix buffer back to game memory. + * + * @param buffer_offset - Base mix buffer offset to use. + * @param effect_info_base - Capture effect info. + * @param node_id - Node id of the mix this command is generated for. + */ + void GenerateCaptureCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id); + + /** + * Generate a compressor effect command. + * + * @param buffer_offset - Base mix buffer offset to use. + * @param effect_info_base - Compressor effect info. + * @param node_id - Node id of the mix this command is generated for. + */ + void GenerateCompressorCommand(const s16 buffer_offset, EffectInfoBase& effect_info, + const s32 node_id); + + /** + * Generate all effect commands for a mix. + * + * @param mix_info - Mix to generate effects from. + */ + void GenerateEffectCommand(MixInfo& mix_info); + + /** + * Generate all mix commands. + * + * @param mix_info - Mix to generate effects from. + */ + void GenerateMixCommands(MixInfo& mix_info); + + /** + * Generate a submix command. + * Generates all effects and all mixing commands. + * + * @param mix_info - Mix to generate effects from. + */ + void GenerateSubMixCommand(MixInfo& mix_info); + + /** + * Generate all submix command. + */ + void GenerateSubMixCommands(); + + /** + * Generate the final mix. + */ + void GenerateFinalMixCommand(); + + /** + * Generate the final mix commands. + */ + void GenerateFinalMixCommands(); + + /** + * Generate all sink commands. + */ + void GenerateSinkCommands(); + + /** + * Generate a sink command. + * Sends samples out to the backend, or a game-supplied circular buffer. + * + * @param buffer_offset - Base mix buffer offset to use. + * @param sink_info - Sink info to generate the commands from. + */ + void GenerateSinkCommand(s16 buffer_offset, SinkInfoBase& sink_info); + + /** + * Generate a device sink command. + * Sends samples out to the backend. + * + * @param buffer_offset - Base mix buffer offset to use. + * @param sink_info - Sink info to generate the commands from. + */ + void GenerateDeviceSinkCommand(s16 buffer_offset, SinkInfoBase& sink_info); + + /** + * Generate a performance command. + * Used to report performance metrics of the AudioRenderer back to the game. + * + * @param buffer_offset - Base mix buffer offset to use. + * @param sink_info - Sink info to generate the commands from. + */ + void GeneratePerformanceCommand(s32 node_id, PerformanceState state, + const PerformanceEntryAddresses& entry_addresses); + +private: + /// Commands will be written by this buffer + CommandBuffer& command_buffer; + /// Header information for the commands generated + const CommandListHeader& command_header; + /// Various things to control generation + const AudioRendererSystemContext& render_context; + /// Used for generating voices + VoiceContext& voice_context; + /// Used for generating mixes + MixContext& mix_context; + /// Used for generating effects + EffectContext& effect_context; + /// Used for generating sinks + SinkContext& sink_context; + /// Used for generating submixes + SplitterContext& splitter_context; + /// Used for generating performance + PerformanceManager* performance_manager; +}; + +} // namespace AudioRenderer +} // namespace AudioCore diff --git a/src/audio_core/renderer/command/command_list_header.h b/src/audio_core/renderer/command/command_list_header.h new file mode 100644 index 000000000..988530b1f --- /dev/null +++ b/src/audio_core/renderer/command/command_list_header.h @@ -0,0 +1,22 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "audio_core/common/common.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { + +struct CommandListHeader { + u64 buffer_size; + u32 command_count; + std::span samples_buffer; + s16 buffer_count; + u32 sample_count; + u32 sample_rate; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/command_processing_time_estimator.cpp b/src/audio_core/renderer/command/command_processing_time_estimator.cpp new file mode 100644 index 000000000..3091f587a --- /dev/null +++ b/src/audio_core/renderer/command/command_processing_time_estimator.cpp @@ -0,0 +1,3620 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/command/command_processing_time_estimator.h" + +namespace AudioCore::AudioRenderer { + +u32 CommandProcessingTimeEstimatorVersion1::Estimate( + const PcmInt16DataSourceVersion1Command& command) const { + return static_cast(command.pitch * 0.25f * 1.2f); +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate( + const PcmInt16DataSourceVersion2Command& command) const { + return static_cast(command.pitch * 0.25f * 1.2f); +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate( + [[maybe_unused]] const PcmFloatDataSourceVersion1Command& command) const { + return 0; +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate( + [[maybe_unused]] const PcmFloatDataSourceVersion2Command& command) const { + return 0; +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate( + const AdpcmDataSourceVersion1Command& command) const { + return static_cast(command.pitch * 0.25f * 1.2f); +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate( + const AdpcmDataSourceVersion2Command& command) const { + return static_cast(command.pitch * 0.25f * 1.2f); +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate( + [[maybe_unused]] const VolumeCommand& command) const { + return static_cast((static_cast(sample_count) * 8.8f) * 1.2f); +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate( + [[maybe_unused]] const VolumeRampCommand& command) const { + return static_cast((static_cast(sample_count) * 9.8f) * 1.2f); +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate( + [[maybe_unused]] const BiquadFilterCommand& command) const { + return static_cast((static_cast(sample_count) * 58.0f) * 1.2f); +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate( + [[maybe_unused]] const MixCommand& command) const { + return static_cast((static_cast(sample_count) * 10.0f) * 1.2f); +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate( + [[maybe_unused]] const MixRampCommand& command) const { + return static_cast((static_cast(sample_count) * 14.4f) * 1.2f); +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate(const MixRampGroupedCommand& command) const { + u32 count{0}; + for (u32 i = 0; i < command.buffer_count; i++) { + if (command.volumes[i] != 0.0f || command.prev_volumes[i] != 0.0f) { + count++; + } + } + + return static_cast(((static_cast(sample_count) * 14.4f) * 1.2f) * + static_cast(count)); +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate( + [[maybe_unused]] const DepopPrepareCommand& command) const { + return 1080; +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate( + const DepopForMixBuffersCommand& command) const { + return static_cast((static_cast(sample_count) * 8.9f) * + static_cast(command.count)); +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate(const DelayCommand& command) const { + return static_cast((static_cast(sample_count) * command.parameter.channel_count) * + 202.5f); +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate( + [[maybe_unused]] const UpsampleCommand& command) const { + return 357915; +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate( + [[maybe_unused]] const DownMix6chTo2chCommand& command) const { + return 16108; +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate(const AuxCommand& command) const { + if (command.enabled) { + return 15956; + } + return 3765; +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate( + [[maybe_unused]] const DeviceSinkCommand& command) const { + return 10042; +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate( + [[maybe_unused]] const CircularBufferSinkCommand& command) const { + return 55; +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate(const ReverbCommand& command) const { + if (command.enabled) { + return static_cast( + (command.parameter.channel_count * static_cast(sample_count) * 750) * 1.2f); + } + return 0; +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate(const I3dl2ReverbCommand& command) const { + if (command.enabled) { + return static_cast( + (command.parameter.channel_count * static_cast(sample_count) * 530) * 1.2f); + } + return 0; +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate( + [[maybe_unused]] const PerformanceCommand& command) const { + return 1454; +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate( + [[maybe_unused]] const ClearMixBufferCommand& command) const { + return static_cast( + ((static_cast(sample_count) * 0.83f) * static_cast(buffer_count)) * 1.2f); +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate( + [[maybe_unused]] const CopyMixBufferCommand& command) const { + return 0; +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate( + [[maybe_unused]] const LightLimiterVersion1Command& command) const { + return 0; +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate( + [[maybe_unused]] const LightLimiterVersion2Command& command) const { + return 0; +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate( + [[maybe_unused]] const MultiTapBiquadFilterCommand& command) const { + return 0; +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate( + [[maybe_unused]] const CaptureCommand& command) const { + return 0; +} + +u32 CommandProcessingTimeEstimatorVersion1::Estimate( + [[maybe_unused]] const CompressorCommand& command) const { + return 0; +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate( + const PcmInt16DataSourceVersion1Command& command) const { + switch (sample_count) { + case 160: + return static_cast( + (static_cast(command.sample_rate) / 200.0f / static_cast(sample_count)) * + (command.pitch * 2.0f) * 749.269f + + 6138.94f); + case 240: + return static_cast( + (static_cast(command.sample_rate) / 200.0f / static_cast(sample_count)) * + (command.pitch * 2.0f) * 1195.456f + + 7797.047f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate( + const PcmInt16DataSourceVersion2Command& command) const { + switch (sample_count) { + case 160: + return static_cast( + (static_cast(command.sample_rate) / 200.0f / static_cast(sample_count)) * + (command.pitch * 2.0f) * 749.269f + + 6138.94f); + case 240: + return static_cast( + (static_cast(command.sample_rate) / 200.0f / static_cast(sample_count)) * + (command.pitch * 2.0f) * 1195.456f + + 7797.047f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate( + const PcmFloatDataSourceVersion1Command& command) const { + switch (sample_count) { + case 160: + return static_cast( + (static_cast(command.sample_rate) / 200.0f / static_cast(sample_count)) * + (command.pitch * 2.0f) * 749.269f + + 6138.94f); + case 240: + return static_cast( + (static_cast(command.sample_rate) / 200.0f / static_cast(sample_count)) * + (command.pitch * 2.0f) * 1195.456f + + 7797.047f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate( + const PcmFloatDataSourceVersion2Command& command) const { + switch (sample_count) { + case 160: + return static_cast( + (static_cast(command.sample_rate) / 200.0f / static_cast(sample_count)) * + (command.pitch * 2.0f) * 749.269f + + 6138.94f); + case 240: + return static_cast( + (static_cast(command.sample_rate) / 200.0f / static_cast(sample_count)) * + (command.pitch * 2.0f) * 1195.456f + + 7797.047f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate( + const AdpcmDataSourceVersion1Command& command) const { + switch (sample_count) { + case 160: + return static_cast( + (static_cast(command.sample_rate) / 200.0f / static_cast(sample_count)) * + (command.pitch * 2.0f) * 2125.588f + + 9039.47f); + case 240: + return static_cast( + (static_cast(command.sample_rate) / 200.0f / static_cast(sample_count)) * + (command.pitch * 2.0f) * 3564.088 + + 6225.471); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate( + const AdpcmDataSourceVersion2Command& command) const { + switch (sample_count) { + case 160: + return static_cast( + (static_cast(command.sample_rate) / 200.0f / static_cast(sample_count)) * + (command.pitch * 2.0f) * 2125.588f + + 9039.47f); + case 240: + return static_cast( + (static_cast(command.sample_rate) / 200.0f / static_cast(sample_count)) * + (command.pitch * 2.0f) * 3564.088 + + 6225.471); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate( + [[maybe_unused]] const VolumeCommand& command) const { + switch (sample_count) { + case 160: + return static_cast(1280.3f); + case 240: + return static_cast(1737.8f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate( + [[maybe_unused]] const VolumeRampCommand& command) const { + switch (sample_count) { + case 160: + return static_cast(1403.9f); + case 240: + return static_cast(1884.3f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate( + [[maybe_unused]] const BiquadFilterCommand& command) const { + switch (sample_count) { + case 160: + return static_cast(4813.2f); + case 240: + return static_cast(6915.4f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate( + [[maybe_unused]] const MixCommand& command) const { + switch (sample_count) { + case 160: + return static_cast(1342.2f); + case 240: + return static_cast(1833.2f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate( + [[maybe_unused]] const MixRampCommand& command) const { + switch (sample_count) { + case 160: + return static_cast(1859.0f); + case 240: + return static_cast(2286.1f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate(const MixRampGroupedCommand& command) const { + u32 count{0}; + for (u32 i = 0; i < command.buffer_count; i++) { + if (command.volumes[i] != 0.0f || command.prev_volumes[i] != 0.0f) { + count++; + } + } + + switch (sample_count) { + case 160: + return static_cast((static_cast(sample_count) * 7.245f) * + static_cast(count)); + case 240: + return static_cast((static_cast(sample_count) * 7.245f) * + static_cast(count)); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate( + [[maybe_unused]] const DepopPrepareCommand& command) const { + switch (sample_count) { + case 160: + return static_cast(306.62f); + case 240: + return static_cast(293.22f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate( + [[maybe_unused]] const DepopForMixBuffersCommand& command) const { + switch (sample_count) { + case 160: + return static_cast(762.96f); + case 240: + return static_cast(726.96f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate(const DelayCommand& command) const { + switch (sample_count) { + case 160: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast(41635.555f); + case 2: + return static_cast(97861.211f); + case 4: + return static_cast(192515.516f); + case 6: + return static_cast(301755.969f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast(578.529f); + case 2: + return static_cast(663.064f); + case 4: + return static_cast(703.983f); + case 6: + return static_cast(760.032f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + case 240: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast(8770.345f); + case 2: + return static_cast(25741.18f); + case 4: + return static_cast(47551.168f); + case 6: + return static_cast(81629.219f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast(521.283f); + case 2: + return static_cast(585.396f); + case 4: + return static_cast(629.884f); + case 6: + return static_cast(713.57f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate( + [[maybe_unused]] const UpsampleCommand& command) const { + switch (sample_count) { + case 160: + return static_cast(292000.0f); + case 240: + return static_cast(0.0f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate( + [[maybe_unused]] const DownMix6chTo2chCommand& command) const { + switch (sample_count) { + case 160: + return static_cast(10009.0f); + case 240: + return static_cast(14577.0f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate(const AuxCommand& command) const { + // Is this function bugged, returning the wrong time? + // Surely the larger time should be returned when enabled... + // CMP W8, #0 + // MOV W8, #0x60; // 489.163f + // MOV W10, #0x64; // 7177.936f + // CSEL X8, X10, X8, EQ + + switch (sample_count) { + case 160: + if (command.enabled) { + return static_cast(489.163f); + } + return static_cast(7177.936f); + case 240: + if (command.enabled) { + return static_cast(485.562f); + } + return static_cast(9499.822f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate(const DeviceSinkCommand& command) const { + switch (command.input_count) { + case 2: + switch (sample_count) { + case 160: + return static_cast(9261.545f); + case 240: + return static_cast(9336.054f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } + case 6: + switch (sample_count) { + case 160: + return static_cast(9336.054f); + case 240: + return static_cast(9566.728f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } + default: + LOG_ERROR(Service_Audio, "Invalid input count {}", command.input_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate( + const CircularBufferSinkCommand& command) const { + switch (sample_count) { + case 160: + return static_cast(static_cast(command.input_count) * 853.629f + 1284.517f); + case 240: + return static_cast(static_cast(command.input_count) * 1726.021f + 1369.683f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate(const ReverbCommand& command) const { + switch (sample_count) { + case 160: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast(97192.227f); + case 2: + return static_cast(103278.555f); + case 4: + return static_cast(109579.039f); + case 6: + return static_cast(115065.438f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast(492.009f); + case 2: + return static_cast(554.463f); + case 4: + return static_cast(595.864f); + case 6: + return static_cast(656.617f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + case 240: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast(136463.641f); + case 2: + return static_cast(145749.047f); + case 4: + return static_cast(154796.938f); + case 6: + return static_cast(161968.406f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast(495.789f); + case 2: + return static_cast(527.163f); + case 4: + return static_cast(598.752f); + case 6: + return static_cast(666.025f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate(const I3dl2ReverbCommand& command) const { + switch (sample_count) { + case 160: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast(138836.484f); + case 2: + return static_cast(135428.172f); + case 4: + return static_cast(199181.844f); + case 6: + return static_cast(247345.906f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast(718.704f); + case 2: + return static_cast(751.296f); + case 4: + return static_cast(797.464f); + case 6: + return static_cast(867.426f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + case 240: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast(199952.734f); + case 2: + return static_cast(195199.5f); + case 4: + return static_cast(290575.875f); + case 6: + return static_cast(363494.531f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast(534.24f); + case 2: + return static_cast(570.874f); + case 4: + return static_cast(660.933f); + case 6: + return static_cast(694.596f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate( + [[maybe_unused]] const PerformanceCommand& command) const { + switch (sample_count) { + case 160: + return static_cast(489.35f); + case 240: + return static_cast(491.18f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate( + [[maybe_unused]] const ClearMixBufferCommand& command) const { + switch (sample_count) { + case 160: + return static_cast(static_cast(buffer_count) * 260.4f + 139.65f); + case 240: + return static_cast(static_cast(buffer_count) * 668.85f + 193.2f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate( + [[maybe_unused]] const CopyMixBufferCommand& command) const { + switch (sample_count) { + case 160: + return static_cast(836.32f); + case 240: + return static_cast(1000.9f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate( + [[maybe_unused]] const LightLimiterVersion1Command& command) const { + return 0; +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate( + [[maybe_unused]] const LightLimiterVersion2Command& command) const { + return 0; +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate( + [[maybe_unused]] const MultiTapBiquadFilterCommand& command) const { + return 0; +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate( + [[maybe_unused]] const CaptureCommand& command) const { + return 0; +} + +u32 CommandProcessingTimeEstimatorVersion2::Estimate( + [[maybe_unused]] const CompressorCommand& command) const { + return 0; +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate( + const PcmInt16DataSourceVersion1Command& command) const { + switch (sample_count) { + case 160: + return static_cast( + ((static_cast(command.sample_rate) / 200.0f / static_cast(sample_count)) * + (command.pitch * 0.000030518f)) * + 427.52f + + 6329.442f); + case 240: + return static_cast( + ((static_cast(command.sample_rate) / 200.0f / static_cast(sample_count)) * + (command.pitch * 0.000030518f)) * + 710.143f + + 7853.286f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate( + const PcmInt16DataSourceVersion2Command& command) const { + switch (sample_count) { + case 160: + switch (command.src_quality) { + case SrcQuality::Medium: + return static_cast((((static_cast(command.sample_rate) / 200.0f / + static_cast(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 427.52f + + 6329.442f); + case SrcQuality::High: + return static_cast((((static_cast(command.sample_rate) / 200.0f / + static_cast(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 371.876f + + 8049.415f); + case SrcQuality::Low: + return static_cast((((static_cast(command.sample_rate) / 200.0f / + static_cast(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 423.43f + + 5062.659f); + default: + LOG_ERROR(Service_Audio, "Invalid SRC quality {}", + static_cast(command.src_quality)); + return 0; + } + + case 240: + switch (command.src_quality) { + case SrcQuality::Medium: + return static_cast((((static_cast(command.sample_rate) / 200.0f / + static_cast(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 710.143f + + 7853.286f); + case SrcQuality::High: + return static_cast((((static_cast(command.sample_rate) / 200.0f / + static_cast(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 610.487f + + 10138.842f); + case SrcQuality::Low: + return static_cast((((static_cast(command.sample_rate) / 200.0f / + static_cast(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 676.722f + + 5810.962f); + default: + LOG_ERROR(Service_Audio, "Invalid SRC quality {}", + static_cast(command.src_quality)); + return 0; + } + + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate( + const PcmFloatDataSourceVersion1Command& command) const { + switch (sample_count) { + case 160: + return static_cast( + ((static_cast(command.sample_rate) / 200.0f / static_cast(sample_count)) * + (command.pitch * 0.000030518f)) * + 1672.026f + + 7681.211f); + case 240: + return static_cast( + ((static_cast(command.sample_rate) / 200.0f / static_cast(sample_count)) * + (command.pitch * 0.000030518f)) * + 2550.414f + + 9663.969f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate( + const PcmFloatDataSourceVersion2Command& command) const { + switch (sample_count) { + case 160: + switch (command.src_quality) { + case SrcQuality::Medium: + return static_cast((((static_cast(command.sample_rate) / 200.0f / + static_cast(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 1672.026f + + 7681.211f); + case SrcQuality::High: + return static_cast((((static_cast(command.sample_rate) / 200.0f / + static_cast(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 1672.982f + + 9038.011f); + case SrcQuality::Low: + return static_cast((((static_cast(command.sample_rate) / 200.0f / + static_cast(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 1673.216f + + 6027.577f); + default: + LOG_ERROR(Service_Audio, "Invalid SRC quality {}", + static_cast(command.src_quality)); + return 0; + } + + case 240: + switch (command.src_quality) { + case SrcQuality::Medium: + return static_cast((((static_cast(command.sample_rate) / 200.0f / + static_cast(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 2550.414f + + 9663.969f); + case SrcQuality::High: + return static_cast((((static_cast(command.sample_rate) / 200.0f / + static_cast(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 2522.303f + + 11758.571f); + case SrcQuality::Low: + return static_cast((((static_cast(command.sample_rate) / 200.0f / + static_cast(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 2537.061f + + 7369.309f); + default: + LOG_ERROR(Service_Audio, "Invalid SRC quality {}", + static_cast(command.src_quality)); + return 0; + } + + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate( + const AdpcmDataSourceVersion1Command& command) const { + switch (sample_count) { + case 160: + return static_cast( + ((static_cast(command.sample_rate) / 200.0f / static_cast(sample_count)) * + (command.pitch * 0.000030518f)) * + 1827.665f + + 7913.808f); + case 240: + return static_cast( + ((static_cast(command.sample_rate) / 200.0f / static_cast(sample_count)) * + (command.pitch * 0.000030518f)) * + 2756.372f + + 9736.702f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate( + const AdpcmDataSourceVersion2Command& command) const { + switch (sample_count) { + case 160: + switch (command.src_quality) { + case SrcQuality::Medium: + return static_cast((((static_cast(command.sample_rate) / 200.0f / + static_cast(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 1827.665f + + 7913.808f); + case SrcQuality::High: + return static_cast((((static_cast(command.sample_rate) / 200.0f / + static_cast(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 1829.285f + + 9607.814f); + case SrcQuality::Low: + return static_cast((((static_cast(command.sample_rate) / 200.0f / + static_cast(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 1824.609f + + 6517.476f); + default: + LOG_ERROR(Service_Audio, "Invalid SRC quality {}", + static_cast(command.src_quality)); + return 0; + } + + case 240: + switch (command.src_quality) { + case SrcQuality::Medium: + return static_cast((((static_cast(command.sample_rate) / 200.0f / + static_cast(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 2756.372f + + 9736.702f); + case SrcQuality::High: + return static_cast((((static_cast(command.sample_rate) / 200.0f / + static_cast(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 2731.308f + + 12154.379f); + case SrcQuality::Low: + return static_cast((((static_cast(command.sample_rate) / 200.0f / + static_cast(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 2732.152f + + 7929.442f); + default: + LOG_ERROR(Service_Audio, "Invalid SRC quality {}", + static_cast(command.src_quality)); + return 0; + } + + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate( + [[maybe_unused]] const VolumeCommand& command) const { + switch (sample_count) { + case 160: + return static_cast(1311.1f); + case 240: + return static_cast(1713.6f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate( + [[maybe_unused]] const VolumeRampCommand& command) const { + switch (sample_count) { + case 160: + return static_cast(1425.3f); + case 240: + return static_cast(1700.0f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate( + [[maybe_unused]] const BiquadFilterCommand& command) const { + switch (sample_count) { + case 160: + return static_cast(4173.2f); + case 240: + return static_cast(5585.1f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate( + [[maybe_unused]] const MixCommand& command) const { + switch (sample_count) { + case 160: + return static_cast(1402.8f); + case 240: + return static_cast(1853.2f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate( + [[maybe_unused]] const MixRampCommand& command) const { + switch (sample_count) { + case 160: + return static_cast(1968.7f); + case 240: + return static_cast(2459.4f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate(const MixRampGroupedCommand& command) const { + u32 count{0}; + for (u32 i = 0; i < command.buffer_count; i++) { + if (command.volumes[i] != 0.0f || command.prev_volumes[i] != 0.0f) { + count++; + } + } + + switch (sample_count) { + case 160: + return static_cast((static_cast(sample_count) * 6.708f) * + static_cast(count)); + case 240: + return static_cast((static_cast(sample_count) * 6.443f) * + static_cast(count)); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate( + [[maybe_unused]] const DepopPrepareCommand& command) const { + return 0; +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate( + [[maybe_unused]] const DepopForMixBuffersCommand& command) const { + switch (sample_count) { + case 160: + return static_cast(739.64f); + case 240: + return static_cast(910.97f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate(const DelayCommand& command) const { + switch (sample_count) { + case 160: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast(8929.042f); + case 2: + return static_cast(25500.75f); + case 4: + return static_cast(47759.617f); + case 6: + return static_cast(82203.07f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast(1295.206f); + case 2: + return static_cast(1213.6f); + case 4: + return static_cast(942.028f); + case 6: + return static_cast(1001.553f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + case 240: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast(11941.051f); + case 2: + return static_cast(37197.371f); + case 4: + return static_cast(69749.836f); + case 6: + return static_cast(120042.398f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast(997.668f); + case 2: + return static_cast(977.634f); + case 4: + return static_cast(792.309f); + case 6: + return static_cast(875.427f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate( + [[maybe_unused]] const UpsampleCommand& command) const { + switch (sample_count) { + case 160: + return static_cast(312990.0f); + case 240: + return static_cast(0.0f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate( + [[maybe_unused]] const DownMix6chTo2chCommand& command) const { + switch (sample_count) { + case 160: + return static_cast(9949.7f); + case 240: + return static_cast(14679.0f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate(const AuxCommand& command) const { + switch (sample_count) { + case 160: + if (command.enabled) { + return static_cast(7182.136f); + } + return static_cast(472.111f); + case 240: + if (command.enabled) { + return static_cast(9435.961f); + } + return static_cast(462.619f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate(const DeviceSinkCommand& command) const { + switch (command.input_count) { + case 2: + switch (sample_count) { + case 160: + return static_cast(8979.956f); + case 240: + return static_cast(9221.907f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } + case 6: + switch (sample_count) { + case 160: + return static_cast(9177.903f); + case 240: + return static_cast(9725.897f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } + default: + LOG_ERROR(Service_Audio, "Invalid input count {}", command.input_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate( + const CircularBufferSinkCommand& command) const { + switch (sample_count) { + case 160: + return static_cast(static_cast(command.input_count) * 531.069f + 0.0f); + case 240: + return static_cast(static_cast(command.input_count) * 770.257f + 0.0f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate(const ReverbCommand& command) const { + switch (sample_count) { + case 160: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast(81475.055f); + case 2: + return static_cast(84975.0f); + case 4: + return static_cast(91625.148f); + case 6: + return static_cast(95332.266f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast(536.298f); + case 2: + return static_cast(588.798f); + case 4: + return static_cast(643.702f); + case 6: + return static_cast(705.999f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + case 240: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast(120174.469f); + case 2: + return static_cast(125262.219f); + case 4: + return static_cast(135751.234f); + case 6: + return static_cast(141129.234f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast(617.641f); + case 2: + return static_cast(659.536f); + case 4: + return static_cast(711.438f); + case 6: + return static_cast(778.071f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate(const I3dl2ReverbCommand& command) const { + switch (sample_count) { + case 160: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast(116754.984f); + case 2: + return static_cast(125912.055f); + case 4: + return static_cast(146336.031f); + case 6: + return static_cast(165812.656f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast(735.0f); + case 2: + return static_cast(766.615f); + case 4: + return static_cast(834.067f); + case 6: + return static_cast(875.437f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + case 240: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast(170292.344f); + case 2: + return static_cast(183875.625f); + case 4: + return static_cast(214696.188f); + case 6: + return static_cast(243846.766f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast(508.473f); + case 2: + return static_cast(582.445f); + case 4: + return static_cast(626.419f); + case 6: + return static_cast(682.468f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate( + [[maybe_unused]] const PerformanceCommand& command) const { + switch (sample_count) { + case 160: + return static_cast(498.17f); + case 240: + return static_cast(489.42f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate( + [[maybe_unused]] const ClearMixBufferCommand& command) const { + switch (sample_count) { + case 160: + return static_cast(static_cast(buffer_count - 1) * 266.645f + 0.0f); + case 240: + return static_cast(static_cast(buffer_count - 1) * 440.681f + 0.0f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate( + [[maybe_unused]] const CopyMixBufferCommand& command) const { + switch (sample_count) { + case 160: + return static_cast(842.59f); + case 240: + return static_cast(986.72f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate( + const LightLimiterVersion1Command& command) const { + switch (sample_count) { + case 160: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast(21392.383f); + case 2: + return static_cast(26829.389f); + case 4: + return static_cast(32405.152f); + case 6: + return static_cast(52218.586f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast(897.004f); + case 2: + return static_cast(931.549f); + case 4: + return static_cast(975.387f); + case 6: + return static_cast(1016.778f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + case 240: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast(30555.504f); + case 2: + return static_cast(39010.785f); + case 4: + return static_cast(48270.18f); + case 6: + return static_cast(76711.875f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast(874.429f); + case 2: + return static_cast(921.553f); + case 4: + return static_cast(945.262f); + case 6: + return static_cast(992.26f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate( + const LightLimiterVersion2Command& command) const { + switch (sample_count) { + case 160: + if (command.enabled) { + if (command.parameter.statistics_enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast(23308.928f); + case 2: + return static_cast(29954.062f); + case 4: + return static_cast(35807.477f); + case 6: + return static_cast(58339.773f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast(21392.383f); + case 2: + return static_cast(26829.389f); + case 4: + return static_cast(32405.152f); + case 6: + return static_cast(52218.586f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast(897.004f); + case 2: + return static_cast(931.549f); + case 4: + return static_cast(975.387f); + case 6: + return static_cast(1016.778f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + case 240: + if (command.enabled) { + if (command.parameter.statistics_enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast(33526.121f); + case 2: + return static_cast(43549.355f); + case 4: + return static_cast(52190.281f); + case 6: + return static_cast(85526.516f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast(30555.504f); + case 2: + return static_cast(39010.785f); + case 4: + return static_cast(48270.18f); + case 6: + return static_cast(76711.875f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast(874.429f); + case 2: + return static_cast(921.553f); + case 4: + return static_cast(945.262f); + case 6: + return static_cast(992.26f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate( + [[maybe_unused]] const MultiTapBiquadFilterCommand& command) const { + return 0; +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate( + [[maybe_unused]] const CaptureCommand& command) const { + return 0; +} + +u32 CommandProcessingTimeEstimatorVersion3::Estimate( + [[maybe_unused]] const CompressorCommand& command) const { + return 0; +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate( + const PcmInt16DataSourceVersion1Command& command) const { + switch (sample_count) { + case 160: + return static_cast( + ((static_cast(command.sample_rate) / 200.0f / static_cast(sample_count)) * + (command.pitch * 0.000030518f)) * + 427.52f + + 6329.442f); + case 240: + return static_cast( + ((static_cast(command.sample_rate) / 200.0f / static_cast(sample_count)) * + (command.pitch * 0.000030518f)) * + 710.143f + + 7853.286f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate( + const PcmInt16DataSourceVersion2Command& command) const { + switch (sample_count) { + case 160: + switch (command.src_quality) { + case SrcQuality::Medium: + return static_cast((((static_cast(command.sample_rate) / 200.0f / + static_cast(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 427.52f + + 6329.442f); + case SrcQuality::High: + return static_cast((((static_cast(command.sample_rate) / 200.0f / + static_cast(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 371.876f + + 8049.415f); + case SrcQuality::Low: + return static_cast((((static_cast(command.sample_rate) / 200.0f / + static_cast(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 423.43f + + 5062.659f); + default: + LOG_ERROR(Service_Audio, "Invalid SRC quality {}", + static_cast(command.src_quality)); + return 0; + } + + case 240: + switch (command.src_quality) { + case SrcQuality::Medium: + return static_cast((((static_cast(command.sample_rate) / 200.0f / + static_cast(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 710.143f + + 7853.286f); + case SrcQuality::High: + return static_cast((((static_cast(command.sample_rate) / 200.0f / + static_cast(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 610.487f + + 10138.842f); + case SrcQuality::Low: + return static_cast((((static_cast(command.sample_rate) / 200.0f / + static_cast(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 676.722f + + 5810.962f); + default: + LOG_ERROR(Service_Audio, "Invalid SRC quality {}", + static_cast(command.src_quality)); + return 0; + } + + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate( + const PcmFloatDataSourceVersion1Command& command) const { + switch (sample_count) { + case 160: + return static_cast( + ((static_cast(command.sample_rate) / 200.0f / static_cast(sample_count)) * + (command.pitch * 0.000030518f)) * + 1672.026f + + 7681.211f); + case 240: + return static_cast( + ((static_cast(command.sample_rate) / 200.0f / static_cast(sample_count)) * + (command.pitch * 0.000030518f)) * + 2550.414f + + 9663.969f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate( + const PcmFloatDataSourceVersion2Command& command) const { + switch (sample_count) { + case 160: + switch (command.src_quality) { + case SrcQuality::Medium: + return static_cast((((static_cast(command.sample_rate) / 200.0f / + static_cast(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 1672.026f + + 7681.211f); + case SrcQuality::High: + return static_cast((((static_cast(command.sample_rate) / 200.0f / + static_cast(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 1672.982f + + 9038.011f); + case SrcQuality::Low: + return static_cast((((static_cast(command.sample_rate) / 200.0f / + static_cast(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 1673.216f + + 6027.577f); + default: + LOG_ERROR(Service_Audio, "Invalid SRC quality {}", + static_cast(command.src_quality)); + return 0; + } + + case 240: + switch (command.src_quality) { + case SrcQuality::Medium: + return static_cast((((static_cast(command.sample_rate) / 200.0f / + static_cast(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 2550.414f + + 9663.969f); + case SrcQuality::High: + return static_cast((((static_cast(command.sample_rate) / 200.0f / + static_cast(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 2522.303f + + 11758.571f); + case SrcQuality::Low: + return static_cast((((static_cast(command.sample_rate) / 200.0f / + static_cast(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 2537.061f + + 7369.309f); + default: + LOG_ERROR(Service_Audio, "Invalid SRC quality {}", + static_cast(command.src_quality)); + return 0; + } + + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate( + const AdpcmDataSourceVersion1Command& command) const { + switch (sample_count) { + case 160: + return static_cast( + ((static_cast(command.sample_rate) / 200.0f / static_cast(sample_count)) * + (command.pitch * 0.000030518f)) * + 1827.665f + + 7913.808f); + case 240: + return static_cast( + ((static_cast(command.sample_rate) / 200.0f / static_cast(sample_count)) * + (command.pitch * 0.000030518f)) * + 2756.372f + + 9736.702f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate( + const AdpcmDataSourceVersion2Command& command) const { + switch (sample_count) { + case 160: + switch (command.src_quality) { + case SrcQuality::Medium: + return static_cast((((static_cast(command.sample_rate) / 200.0f / + static_cast(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 1827.665f + + 7913.808f); + case SrcQuality::High: + return static_cast((((static_cast(command.sample_rate) / 200.0f / + static_cast(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 1829.285f + + 9607.814f); + case SrcQuality::Low: + return static_cast((((static_cast(command.sample_rate) / 200.0f / + static_cast(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 1824.609f + + 6517.476f); + default: + LOG_ERROR(Service_Audio, "Invalid SRC quality {}", + static_cast(command.src_quality)); + return 0; + } + + case 240: + switch (command.src_quality) { + case SrcQuality::Medium: + return static_cast((((static_cast(command.sample_rate) / 200.0f / + static_cast(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 2756.372f + + 9736.702f); + case SrcQuality::High: + return static_cast((((static_cast(command.sample_rate) / 200.0f / + static_cast(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 2731.308f + + 12154.379f); + case SrcQuality::Low: + return static_cast((((static_cast(command.sample_rate) / 200.0f / + static_cast(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 2732.152f + + 7929.442f); + default: + LOG_ERROR(Service_Audio, "Invalid SRC quality {}", + static_cast(command.src_quality)); + return 0; + } + + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate( + [[maybe_unused]] const VolumeCommand& command) const { + switch (sample_count) { + case 160: + return static_cast(1311.1f); + case 240: + return static_cast(1713.6f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate( + [[maybe_unused]] const VolumeRampCommand& command) const { + switch (sample_count) { + case 160: + return static_cast(1425.3f); + case 240: + return static_cast(1700.0f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate( + [[maybe_unused]] const BiquadFilterCommand& command) const { + switch (sample_count) { + case 160: + return static_cast(4173.2f); + case 240: + return static_cast(5585.1f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate( + [[maybe_unused]] const MixCommand& command) const { + switch (sample_count) { + case 160: + return static_cast(1402.8f); + case 240: + return static_cast(1853.2f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate( + [[maybe_unused]] const MixRampCommand& command) const { + switch (sample_count) { + case 160: + return static_cast(1968.7f); + case 240: + return static_cast(2459.4f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate(const MixRampGroupedCommand& command) const { + u32 count{0}; + for (u32 i = 0; i < command.buffer_count; i++) { + if (command.volumes[i] != 0.0f || command.prev_volumes[i] != 0.0f) { + count++; + } + } + + switch (sample_count) { + case 160: + return static_cast((static_cast(sample_count) * 6.708f) * + static_cast(count)); + case 240: + return static_cast((static_cast(sample_count) * 6.443f) * + static_cast(count)); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate( + [[maybe_unused]] const DepopPrepareCommand& command) const { + return 0; +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate( + [[maybe_unused]] const DepopForMixBuffersCommand& command) const { + switch (sample_count) { + case 160: + return static_cast(739.64f); + case 240: + return static_cast(910.97f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate(const DelayCommand& command) const { + switch (sample_count) { + case 160: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast(8929.042f); + case 2: + return static_cast(25500.75f); + case 4: + return static_cast(47759.617f); + case 6: + return static_cast(82203.07f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast(1295.206f); + case 2: + return static_cast(1213.6f); + case 4: + return static_cast(942.028f); + case 6: + return static_cast(1001.553f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + case 240: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast(11941.051f); + case 2: + return static_cast(37197.371f); + case 4: + return static_cast(69749.836f); + case 6: + return static_cast(120042.398f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast(997.668f); + case 2: + return static_cast(977.634f); + case 4: + return static_cast(792.309f); + case 6: + return static_cast(875.427f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate( + [[maybe_unused]] const UpsampleCommand& command) const { + switch (sample_count) { + case 160: + return static_cast(312990.0f); + case 240: + return static_cast(0.0f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate( + [[maybe_unused]] const DownMix6chTo2chCommand& command) const { + switch (sample_count) { + case 160: + return static_cast(9949.7f); + case 240: + return static_cast(14679.0f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate(const AuxCommand& command) const { + switch (sample_count) { + case 160: + if (command.enabled) { + return static_cast(7182.136f); + } + return static_cast(472.111f); + case 240: + if (command.enabled) { + return static_cast(9435.961f); + } + return static_cast(462.619f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate(const DeviceSinkCommand& command) const { + switch (command.input_count) { + case 2: + switch (sample_count) { + case 160: + return static_cast(8979.956f); + case 240: + return static_cast(9221.907f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } + case 6: + switch (sample_count) { + case 160: + return static_cast(9177.903f); + case 240: + return static_cast(9725.897f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } + default: + LOG_ERROR(Service_Audio, "Invalid input count {}", command.input_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate( + const CircularBufferSinkCommand& command) const { + switch (sample_count) { + case 160: + return static_cast(static_cast(command.input_count) * 531.069f + 0.0f); + case 240: + return static_cast(static_cast(command.input_count) * 770.257f + 0.0f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate(const ReverbCommand& command) const { + switch (sample_count) { + case 160: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast(81475.055f); + case 2: + return static_cast(84975.0f); + case 4: + return static_cast(91625.148f); + case 6: + return static_cast(95332.266f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast(536.298f); + case 2: + return static_cast(588.798f); + case 4: + return static_cast(643.702f); + case 6: + return static_cast(705.999f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + case 240: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast(120174.469f); + case 2: + return static_cast(125262.219f); + case 4: + return static_cast(135751.234f); + case 6: + return static_cast(141129.234f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast(617.641f); + case 2: + return static_cast(659.536f); + case 4: + return static_cast(711.438f); + case 6: + return static_cast(778.071f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate(const I3dl2ReverbCommand& command) const { + switch (sample_count) { + case 160: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast(116754.984f); + case 2: + return static_cast(125912.055f); + case 4: + return static_cast(146336.031f); + case 6: + return static_cast(165812.656f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast(735.0f); + case 2: + return static_cast(766.615f); + case 4: + return static_cast(834.067f); + case 6: + return static_cast(875.437f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + case 240: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast(170292.344f); + case 2: + return static_cast(183875.625f); + case 4: + return static_cast(214696.188f); + case 6: + return static_cast(243846.766f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast(508.473f); + case 2: + return static_cast(582.445f); + case 4: + return static_cast(626.419f); + case 6: + return static_cast(682.468f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate( + [[maybe_unused]] const PerformanceCommand& command) const { + switch (sample_count) { + case 160: + return static_cast(498.17f); + case 240: + return static_cast(489.42f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate( + [[maybe_unused]] const ClearMixBufferCommand& command) const { + switch (sample_count) { + case 160: + return static_cast(static_cast(buffer_count - 1) * 266.645f + 0.0f); + case 240: + return static_cast(static_cast(buffer_count - 1) * 440.681f + 0.0f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate( + [[maybe_unused]] const CopyMixBufferCommand& command) const { + switch (sample_count) { + case 160: + return static_cast(842.59f); + case 240: + return static_cast(986.72f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate( + const LightLimiterVersion1Command& command) const { + switch (sample_count) { + case 160: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast(21392.383f); + case 2: + return static_cast(26829.389f); + case 4: + return static_cast(32405.152f); + case 6: + return static_cast(52218.586f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast(897.004f); + case 2: + return static_cast(931.549f); + case 4: + return static_cast(975.387f); + case 6: + return static_cast(1016.778f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + case 240: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast(30555.504f); + case 2: + return static_cast(39010.785f); + case 4: + return static_cast(48270.18f); + case 6: + return static_cast(76711.875f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast(874.429f); + case 2: + return static_cast(921.553f); + case 4: + return static_cast(945.262f); + case 6: + return static_cast(992.26f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate( + const LightLimiterVersion2Command& command) const { + switch (sample_count) { + case 160: + if (command.enabled) { + if (command.parameter.statistics_enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast(23308.928f); + case 2: + return static_cast(29954.062f); + case 4: + return static_cast(35807.477f); + case 6: + return static_cast(58339.773f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast(21392.383f); + case 2: + return static_cast(26829.389f); + case 4: + return static_cast(32405.152f); + case 6: + return static_cast(52218.586f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast(897.004f); + case 2: + return static_cast(931.549f); + case 4: + return static_cast(975.387f); + case 6: + return static_cast(1016.778f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + case 240: + if (command.enabled) { + if (command.parameter.statistics_enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast(33526.121f); + case 2: + return static_cast(43549.355f); + case 4: + return static_cast(52190.281f); + case 6: + return static_cast(85526.516f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast(30555.504f); + case 2: + return static_cast(39010.785f); + case 4: + return static_cast(48270.18f); + case 6: + return static_cast(76711.875f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast(874.429f); + case 2: + return static_cast(921.553f); + case 4: + return static_cast(945.262f); + case 6: + return static_cast(992.26f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate( + [[maybe_unused]] const MultiTapBiquadFilterCommand& command) const { + switch (sample_count) { + case 160: + return static_cast(7424.5f); + case 240: + return static_cast(9730.4f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate(const CaptureCommand& command) const { + switch (sample_count) { + case 160: + if (command.enabled) { + return static_cast(426.982f); + } + return static_cast(4261.005f); + case 240: + if (command.enabled) { + return static_cast(435.204f); + } + return static_cast(5858.265f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion4::Estimate( + [[maybe_unused]] const CompressorCommand& command) const { + return 0; +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate( + const PcmInt16DataSourceVersion1Command& command) const { + switch (sample_count) { + case 160: + return static_cast( + ((static_cast(command.sample_rate) / 200.0f / static_cast(sample_count)) * + (command.pitch * 0.000030518f)) * + 427.52f + + 6329.442f); + case 240: + return static_cast( + ((static_cast(command.sample_rate) / 200.0f / static_cast(sample_count)) * + (command.pitch * 0.000030518f)) * + 710.143f + + 7853.286f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate( + const PcmInt16DataSourceVersion2Command& command) const { + switch (sample_count) { + case 160: + switch (command.src_quality) { + case SrcQuality::Medium: + return static_cast((((static_cast(command.sample_rate) / 200.0f / + static_cast(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 427.52f + + 6329.442f); + case SrcQuality::High: + return static_cast((((static_cast(command.sample_rate) / 200.0f / + static_cast(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 371.876f + + 8049.415f); + case SrcQuality::Low: + return static_cast((((static_cast(command.sample_rate) / 200.0f / + static_cast(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 423.43f + + 5062.659f); + default: + LOG_ERROR(Service_Audio, "Invalid SRC quality {}", + static_cast(command.src_quality)); + return 0; + } + + case 240: + switch (command.src_quality) { + case SrcQuality::Medium: + return static_cast((((static_cast(command.sample_rate) / 200.0f / + static_cast(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 710.143f + + 7853.286f); + case SrcQuality::High: + return static_cast((((static_cast(command.sample_rate) / 200.0f / + static_cast(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 610.487f + + 10138.842f); + case SrcQuality::Low: + return static_cast((((static_cast(command.sample_rate) / 200.0f / + static_cast(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 676.722f + + 5810.962f); + default: + LOG_ERROR(Service_Audio, "Invalid SRC quality {}", + static_cast(command.src_quality)); + return 0; + } + + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate( + const PcmFloatDataSourceVersion1Command& command) const { + switch (sample_count) { + case 160: + return static_cast( + ((static_cast(command.sample_rate) / 200.0f / static_cast(sample_count)) * + (command.pitch * 0.000030518f)) * + 1672.026f + + 7681.211f); + case 240: + return static_cast( + ((static_cast(command.sample_rate) / 200.0f / static_cast(sample_count)) * + (command.pitch * 0.000030518f)) * + 2550.414f + + 9663.969f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate( + const PcmFloatDataSourceVersion2Command& command) const { + switch (sample_count) { + case 160: + switch (command.src_quality) { + case SrcQuality::Medium: + return static_cast((((static_cast(command.sample_rate) / 200.0f / + static_cast(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 1672.026f + + 7681.211f); + case SrcQuality::High: + return static_cast((((static_cast(command.sample_rate) / 200.0f / + static_cast(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 1672.982f + + 9038.011f); + case SrcQuality::Low: + return static_cast((((static_cast(command.sample_rate) / 200.0f / + static_cast(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 1673.216f + + 6027.577f); + default: + LOG_ERROR(Service_Audio, "Invalid SRC quality {}", + static_cast(command.src_quality)); + return 0; + } + + case 240: + switch (command.src_quality) { + case SrcQuality::Medium: + return static_cast((((static_cast(command.sample_rate) / 200.0f / + static_cast(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 2550.414f + + 9663.969f); + case SrcQuality::High: + return static_cast((((static_cast(command.sample_rate) / 200.0f / + static_cast(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 2522.303f + + 11758.571f); + case SrcQuality::Low: + return static_cast((((static_cast(command.sample_rate) / 200.0f / + static_cast(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 2537.061f + + 7369.309f); + default: + LOG_ERROR(Service_Audio, "Invalid SRC quality {}", + static_cast(command.src_quality)); + return 0; + } + + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate( + const AdpcmDataSourceVersion1Command& command) const { + switch (sample_count) { + case 160: + return static_cast( + ((static_cast(command.sample_rate) / 200.0f / static_cast(sample_count)) * + (command.pitch * 0.000030518f)) * + 1827.665f + + 7913.808f); + case 240: + return static_cast( + ((static_cast(command.sample_rate) / 200.0f / static_cast(sample_count)) * + (command.pitch * 0.000030518f)) * + 2756.372f + + 9736.702f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate( + const AdpcmDataSourceVersion2Command& command) const { + switch (sample_count) { + case 160: + switch (command.src_quality) { + case SrcQuality::Medium: + return static_cast((((static_cast(command.sample_rate) / 200.0f / + static_cast(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 1827.665f + + 7913.808f); + case SrcQuality::High: + return static_cast((((static_cast(command.sample_rate) / 200.0f / + static_cast(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 1829.285f + + 9607.814f); + case SrcQuality::Low: + return static_cast((((static_cast(command.sample_rate) / 200.0f / + static_cast(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 1824.609f + + 6517.476f); + default: + LOG_ERROR(Service_Audio, "Invalid SRC quality {}", + static_cast(command.src_quality)); + return 0; + } + + case 240: + switch (command.src_quality) { + case SrcQuality::Medium: + return static_cast((((static_cast(command.sample_rate) / 200.0f / + static_cast(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 2756.372f + + 9736.702f); + case SrcQuality::High: + return static_cast((((static_cast(command.sample_rate) / 200.0f / + static_cast(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 2731.308f + + 12154.379f); + case SrcQuality::Low: + return static_cast((((static_cast(command.sample_rate) / 200.0f / + static_cast(sample_count)) * + (command.pitch * 0.000030518f)) - + 1.0f) * + 2732.152f + + 7929.442f); + default: + LOG_ERROR(Service_Audio, "Invalid SRC quality {}", + static_cast(command.src_quality)); + return 0; + } + + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate( + [[maybe_unused]] const VolumeCommand& command) const { + switch (sample_count) { + case 160: + return static_cast(1311.1f); + case 240: + return static_cast(1713.6f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate( + [[maybe_unused]] const VolumeRampCommand& command) const { + switch (sample_count) { + case 160: + return static_cast(1425.3f); + case 240: + return static_cast(1700.0f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate( + [[maybe_unused]] const BiquadFilterCommand& command) const { + switch (sample_count) { + case 160: + return static_cast(4173.2f); + case 240: + return static_cast(5585.1f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate( + [[maybe_unused]] const MixCommand& command) const { + switch (sample_count) { + case 160: + return static_cast(1402.8f); + case 240: + return static_cast(1853.2f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate( + [[maybe_unused]] const MixRampCommand& command) const { + switch (sample_count) { + case 160: + return static_cast(1968.7f); + case 240: + return static_cast(2459.4f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate(const MixRampGroupedCommand& command) const { + u32 count{0}; + for (u32 i = 0; i < command.buffer_count; i++) { + if (command.volumes[i] != 0.0f || command.prev_volumes[i] != 0.0f) { + count++; + } + } + + switch (sample_count) { + case 160: + return static_cast((static_cast(sample_count) * 6.708f) * + static_cast(count)); + case 240: + return static_cast((static_cast(sample_count) * 6.443f) * + static_cast(count)); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate( + [[maybe_unused]] const DepopPrepareCommand& command) const { + return 0; +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate( + [[maybe_unused]] const DepopForMixBuffersCommand& command) const { + switch (sample_count) { + case 160: + return static_cast(739.64f); + case 240: + return static_cast(910.97f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate(const DelayCommand& command) const { + switch (sample_count) { + case 160: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast(8929.042f); + case 2: + return static_cast(25500.75f); + case 4: + return static_cast(47759.617f); + case 6: + return static_cast(82203.07f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast(1295.206f); + case 2: + return static_cast(1213.6f); + case 4: + return static_cast(942.028f); + case 6: + return static_cast(1001.553f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + case 240: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast(11941.051f); + case 2: + return static_cast(37197.371f); + case 4: + return static_cast(69749.836f); + case 6: + return static_cast(120042.398f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast(997.668f); + case 2: + return static_cast(977.634f); + case 4: + return static_cast(792.309f); + case 6: + return static_cast(875.427f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate( + [[maybe_unused]] const UpsampleCommand& command) const { + switch (sample_count) { + case 160: + return static_cast(312990.0f); + case 240: + return static_cast(0.0f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate( + [[maybe_unused]] const DownMix6chTo2chCommand& command) const { + switch (sample_count) { + case 160: + return static_cast(9949.7f); + case 240: + return static_cast(14679.0f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate(const AuxCommand& command) const { + switch (sample_count) { + case 160: + if (command.enabled) { + return static_cast(7182.136f); + } + return static_cast(472.111f); + case 240: + if (command.enabled) { + return static_cast(9435.961f); + } + return static_cast(462.619f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate(const DeviceSinkCommand& command) const { + switch (command.input_count) { + case 2: + switch (sample_count) { + case 160: + return static_cast(8979.956f); + case 240: + return static_cast(9221.907f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } + case 6: + switch (sample_count) { + case 160: + return static_cast(9177.903f); + case 240: + return static_cast(9725.897f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } + default: + LOG_ERROR(Service_Audio, "Invalid input count {}", command.input_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate( + const CircularBufferSinkCommand& command) const { + switch (sample_count) { + case 160: + return static_cast(static_cast(command.input_count) * 531.069f + 0.0f); + case 240: + return static_cast(static_cast(command.input_count) * 770.257f + 0.0f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate(const ReverbCommand& command) const { + switch (sample_count) { + case 160: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast(81475.055f); + case 2: + return static_cast(84975.0f); + case 4: + return static_cast(91625.148f); + case 6: + return static_cast(95332.266f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast(536.298f); + case 2: + return static_cast(588.798f); + case 4: + return static_cast(643.702f); + case 6: + return static_cast(705.999f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + case 240: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast(120174.469f); + case 2: + return static_cast(125262.219f); + case 4: + return static_cast(135751.234f); + case 6: + return static_cast(141129.234f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast(617.641f); + case 2: + return static_cast(659.536f); + case 4: + return static_cast(711.438f); + case 6: + return static_cast(778.071f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate(const I3dl2ReverbCommand& command) const { + switch (sample_count) { + case 160: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast(116754.984f); + case 2: + return static_cast(125912.055f); + case 4: + return static_cast(146336.031f); + case 6: + return static_cast(165812.656f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast(735.0f); + case 2: + return static_cast(766.615f); + case 4: + return static_cast(834.067f); + case 6: + return static_cast(875.437f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + case 240: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast(170292.344f); + case 2: + return static_cast(183875.625f); + case 4: + return static_cast(214696.188f); + case 6: + return static_cast(243846.766f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast(508.473f); + case 2: + return static_cast(582.445f); + case 4: + return static_cast(626.419f); + case 6: + return static_cast(682.468f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate( + [[maybe_unused]] const PerformanceCommand& command) const { + switch (sample_count) { + case 160: + return static_cast(498.17f); + case 240: + return static_cast(489.42f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate( + [[maybe_unused]] const ClearMixBufferCommand& command) const { + switch (sample_count) { + case 160: + return static_cast(static_cast(buffer_count - 1) * 266.645f + 0.0f); + case 240: + return static_cast(static_cast(buffer_count - 1) * 440.681f + 0.0f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate( + [[maybe_unused]] const CopyMixBufferCommand& command) const { + switch (sample_count) { + case 160: + return static_cast(842.59f); + case 240: + return static_cast(986.72f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate( + const LightLimiterVersion1Command& command) const { + switch (sample_count) { + case 160: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast(21508.01f); + case 2: + return static_cast(23120.453f); + case 4: + return static_cast(26270.053f); + case 6: + return static_cast(40471.902f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast(897.004f); + case 2: + return static_cast(931.549f); + case 4: + return static_cast(975.387f); + case 6: + return static_cast(1016.778f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + case 240: + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast(30565.961f); + case 2: + return static_cast(32812.91f); + case 4: + return static_cast(37354.852f); + case 6: + return static_cast(58486.699f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast(874.429f); + case 2: + return static_cast(921.553f); + case 4: + return static_cast(945.262f); + case 6: + return static_cast(992.26f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate( + const LightLimiterVersion2Command& command) const { + switch (sample_count) { + case 160: + if (command.enabled) { + if (command.parameter.processing_mode == LightLimiterInfo::ProcessingMode::Mode0) { + if (command.parameter.statistics_enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast(23639.584f); + case 2: + return static_cast(24666.725f); + case 4: + return static_cast(28876.459f); + case 6: + return static_cast(47096.078f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } else { + if (command.parameter.statistics_enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast(21508.01f); + case 2: + return static_cast(23120.453f); + case 4: + return static_cast(26270.053f); + case 6: + return static_cast(40471.902f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + } + } else if (command.parameter.processing_mode == + LightLimiterInfo::ProcessingMode::Mode1) { + if (command.parameter.statistics_enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast(23639.584f); + case 2: + return static_cast(29954.062f); + case 4: + return static_cast(35807.477f); + case 6: + return static_cast(58339.773f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } else { + if (command.parameter.statistics_enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast(23639.584f); + case 2: + return static_cast(29954.062f); + case 4: + return static_cast(35807.477f); + case 6: + return static_cast(58339.773f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + } + } else { + LOG_ERROR(Service_Audio, "Invalid processing mode {}", + command.parameter.processing_mode); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast(897.004f); + case 2: + return static_cast(931.549f); + case 4: + return static_cast(975.387f); + case 6: + return static_cast(1016.778f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + case 240: + if (command.enabled) { + if (command.parameter.processing_mode == LightLimiterInfo::ProcessingMode::Mode0) { + if (command.parameter.statistics_enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast(33875.023f); + case 2: + return static_cast(35199.938f); + case 4: + return static_cast(41371.230f); + case 6: + return static_cast(68370.914f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } else { + switch (command.parameter.channel_count) { + case 1: + return static_cast(30565.961f); + case 2: + return static_cast(32812.91f); + case 4: + return static_cast(37354.852f); + case 6: + return static_cast(58486.699f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + } else if (command.parameter.processing_mode == + LightLimiterInfo::ProcessingMode::Mode1) { + if (command.parameter.statistics_enabled) { + switch (command.parameter.channel_count) { + case 1: + return static_cast(33942.980f); + case 2: + return static_cast(28698.893f); + case 4: + return static_cast(34774.277f); + case 6: + return static_cast(61897.773f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } else { + switch (command.parameter.channel_count) { + case 1: + return static_cast(30610.248f); + case 2: + return static_cast(26322.408f); + case 4: + return static_cast(30369.000f); + case 6: + return static_cast(51892.090f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", + command.parameter.channel_count); + return 0; + } + } + } else { + LOG_ERROR(Service_Audio, "Invalid processing mode {}", + command.parameter.processing_mode); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + return static_cast(874.429f); + case 2: + return static_cast(921.553f); + case 4: + return static_cast(945.262f); + case 6: + return static_cast(992.26f); + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate( + [[maybe_unused]] const MultiTapBiquadFilterCommand& command) const { + switch (sample_count) { + case 160: + return static_cast(7424.5f); + case 240: + return static_cast(9730.4f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate(const CaptureCommand& command) const { + switch (sample_count) { + case 160: + if (command.enabled) { + return static_cast(426.982f); + } + return static_cast(4261.005f); + case 240: + if (command.enabled) { + return static_cast(435.204f); + } + return static_cast(5858.265f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } +} + +u32 CommandProcessingTimeEstimatorVersion5::Estimate(const CompressorCommand& command) const { + if (command.enabled) { + switch (command.parameter.channel_count) { + case 1: + switch (sample_count) { + case 160: + return static_cast(34430.570f); + case 240: + return static_cast(51095.348f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } + case 2: + switch (sample_count) { + case 160: + return static_cast(44253.320f); + case 240: + return static_cast(65693.094f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } + case 4: + switch (sample_count) { + case 160: + return static_cast(63827.457f); + case 240: + return static_cast(95382.852f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } + case 6: + switch (sample_count) { + case 160: + return static_cast(83361.484f); + case 240: + return static_cast(124509.906f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } + } + switch (command.parameter.channel_count) { + case 1: + switch (sample_count) { + case 160: + return static_cast(630.115f); + case 240: + return static_cast(840.136f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } + case 2: + switch (sample_count) { + case 160: + return static_cast(638.274f); + case 240: + return static_cast(826.098f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } + case 4: + switch (sample_count) { + case 160: + return static_cast(705.862f); + case 240: + return static_cast(901.876f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } + case 6: + switch (sample_count) { + case 160: + return static_cast(782.019f); + case 240: + return static_cast(965.286f); + default: + LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count); + return 0; + } + default: + LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count); + return 0; + } +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/command_processing_time_estimator.h b/src/audio_core/renderer/command/command_processing_time_estimator.h new file mode 100644 index 000000000..452217196 --- /dev/null +++ b/src/audio_core/renderer/command/command_processing_time_estimator.h @@ -0,0 +1,254 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "audio_core/renderer/command/commands.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +/** + * Estimate the processing time required for all commands. + */ +class ICommandProcessingTimeEstimator { +public: + virtual ~ICommandProcessingTimeEstimator() = default; + + virtual u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const = 0; + virtual u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const = 0; + virtual u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const = 0; + virtual u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const = 0; + virtual u32 Estimate(const AdpcmDataSourceVersion1Command& command) const = 0; + virtual u32 Estimate(const AdpcmDataSourceVersion2Command& command) const = 0; + virtual u32 Estimate(const VolumeCommand& command) const = 0; + virtual u32 Estimate(const VolumeRampCommand& command) const = 0; + virtual u32 Estimate(const BiquadFilterCommand& command) const = 0; + virtual u32 Estimate(const MixCommand& command) const = 0; + virtual u32 Estimate(const MixRampCommand& command) const = 0; + virtual u32 Estimate(const MixRampGroupedCommand& command) const = 0; + virtual u32 Estimate(const DepopPrepareCommand& command) const = 0; + virtual u32 Estimate(const DepopForMixBuffersCommand& command) const = 0; + virtual u32 Estimate(const DelayCommand& command) const = 0; + virtual u32 Estimate(const UpsampleCommand& command) const = 0; + virtual u32 Estimate(const DownMix6chTo2chCommand& command) const = 0; + virtual u32 Estimate(const AuxCommand& command) const = 0; + virtual u32 Estimate(const DeviceSinkCommand& command) const = 0; + virtual u32 Estimate(const CircularBufferSinkCommand& command) const = 0; + virtual u32 Estimate(const ReverbCommand& command) const = 0; + virtual u32 Estimate(const I3dl2ReverbCommand& command) const = 0; + virtual u32 Estimate(const PerformanceCommand& command) const = 0; + virtual u32 Estimate(const ClearMixBufferCommand& command) const = 0; + virtual u32 Estimate(const CopyMixBufferCommand& command) const = 0; + virtual u32 Estimate(const LightLimiterVersion1Command& command) const = 0; + virtual u32 Estimate(const LightLimiterVersion2Command& command) const = 0; + virtual u32 Estimate(const MultiTapBiquadFilterCommand& command) const = 0; + virtual u32 Estimate(const CaptureCommand& command) const = 0; + virtual u32 Estimate(const CompressorCommand& command) const = 0; +}; + +class CommandProcessingTimeEstimatorVersion1 final : public ICommandProcessingTimeEstimator { +public: + CommandProcessingTimeEstimatorVersion1(u32 sample_count_, u32 buffer_count_) + : sample_count{sample_count_}, buffer_count{buffer_count_} {} + + u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const override; + u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const override; + u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const override; + u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const override; + u32 Estimate(const AdpcmDataSourceVersion1Command& command) const override; + u32 Estimate(const AdpcmDataSourceVersion2Command& command) const override; + u32 Estimate(const VolumeCommand& command) const override; + u32 Estimate(const VolumeRampCommand& command) const override; + u32 Estimate(const BiquadFilterCommand& command) const override; + u32 Estimate(const MixCommand& command) const override; + u32 Estimate(const MixRampCommand& command) const override; + u32 Estimate(const MixRampGroupedCommand& command) const override; + u32 Estimate(const DepopPrepareCommand& command) const override; + u32 Estimate(const DepopForMixBuffersCommand& command) const override; + u32 Estimate(const DelayCommand& command) const override; + u32 Estimate(const UpsampleCommand& command) const override; + u32 Estimate(const DownMix6chTo2chCommand& command) const override; + u32 Estimate(const AuxCommand& command) const override; + u32 Estimate(const DeviceSinkCommand& command) const override; + u32 Estimate(const CircularBufferSinkCommand& command) const override; + u32 Estimate(const ReverbCommand& command) const override; + u32 Estimate(const I3dl2ReverbCommand& command) const override; + u32 Estimate(const PerformanceCommand& command) const override; + u32 Estimate(const ClearMixBufferCommand& command) const override; + u32 Estimate(const CopyMixBufferCommand& command) const override; + u32 Estimate(const LightLimiterVersion1Command& command) const override; + u32 Estimate(const LightLimiterVersion2Command& command) const override; + u32 Estimate(const MultiTapBiquadFilterCommand& command) const override; + u32 Estimate(const CaptureCommand& command) const override; + u32 Estimate(const CompressorCommand& command) const override; + +private: + u32 sample_count{}; + u32 buffer_count{}; +}; + +class CommandProcessingTimeEstimatorVersion2 final : public ICommandProcessingTimeEstimator { +public: + CommandProcessingTimeEstimatorVersion2(u32 sample_count_, u32 buffer_count_) + : sample_count{sample_count_}, buffer_count{buffer_count_} {} + + u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const override; + u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const override; + u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const override; + u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const override; + u32 Estimate(const AdpcmDataSourceVersion1Command& command) const override; + u32 Estimate(const AdpcmDataSourceVersion2Command& command) const override; + u32 Estimate(const VolumeCommand& command) const override; + u32 Estimate(const VolumeRampCommand& command) const override; + u32 Estimate(const BiquadFilterCommand& command) const override; + u32 Estimate(const MixCommand& command) const override; + u32 Estimate(const MixRampCommand& command) const override; + u32 Estimate(const MixRampGroupedCommand& command) const override; + u32 Estimate(const DepopPrepareCommand& command) const override; + u32 Estimate(const DepopForMixBuffersCommand& command) const override; + u32 Estimate(const DelayCommand& command) const override; + u32 Estimate(const UpsampleCommand& command) const override; + u32 Estimate(const DownMix6chTo2chCommand& command) const override; + u32 Estimate(const AuxCommand& command) const override; + u32 Estimate(const DeviceSinkCommand& command) const override; + u32 Estimate(const CircularBufferSinkCommand& command) const override; + u32 Estimate(const ReverbCommand& command) const override; + u32 Estimate(const I3dl2ReverbCommand& command) const override; + u32 Estimate(const PerformanceCommand& command) const override; + u32 Estimate(const ClearMixBufferCommand& command) const override; + u32 Estimate(const CopyMixBufferCommand& command) const override; + u32 Estimate(const LightLimiterVersion1Command& command) const override; + u32 Estimate(const LightLimiterVersion2Command& command) const override; + u32 Estimate(const MultiTapBiquadFilterCommand& command) const override; + u32 Estimate(const CaptureCommand& command) const override; + u32 Estimate(const CompressorCommand& command) const override; + +private: + u32 sample_count{}; + u32 buffer_count{}; +}; + +class CommandProcessingTimeEstimatorVersion3 final : public ICommandProcessingTimeEstimator { +public: + CommandProcessingTimeEstimatorVersion3(u32 sample_count_, u32 buffer_count_) + : sample_count{sample_count_}, buffer_count{buffer_count_} {} + + u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const override; + u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const override; + u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const override; + u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const override; + u32 Estimate(const AdpcmDataSourceVersion1Command& command) const override; + u32 Estimate(const AdpcmDataSourceVersion2Command& command) const override; + u32 Estimate(const VolumeCommand& command) const override; + u32 Estimate(const VolumeRampCommand& command) const override; + u32 Estimate(const BiquadFilterCommand& command) const override; + u32 Estimate(const MixCommand& command) const override; + u32 Estimate(const MixRampCommand& command) const override; + u32 Estimate(const MixRampGroupedCommand& command) const override; + u32 Estimate(const DepopPrepareCommand& command) const override; + u32 Estimate(const DepopForMixBuffersCommand& command) const override; + u32 Estimate(const DelayCommand& command) const override; + u32 Estimate(const UpsampleCommand& command) const override; + u32 Estimate(const DownMix6chTo2chCommand& command) const override; + u32 Estimate(const AuxCommand& command) const override; + u32 Estimate(const DeviceSinkCommand& command) const override; + u32 Estimate(const CircularBufferSinkCommand& command) const override; + u32 Estimate(const ReverbCommand& command) const override; + u32 Estimate(const I3dl2ReverbCommand& command) const override; + u32 Estimate(const PerformanceCommand& command) const override; + u32 Estimate(const ClearMixBufferCommand& command) const override; + u32 Estimate(const CopyMixBufferCommand& command) const override; + u32 Estimate(const LightLimiterVersion1Command& command) const override; + u32 Estimate(const LightLimiterVersion2Command& command) const override; + u32 Estimate(const MultiTapBiquadFilterCommand& command) const override; + u32 Estimate(const CaptureCommand& command) const override; + u32 Estimate(const CompressorCommand& command) const override; + +private: + u32 sample_count{}; + u32 buffer_count{}; +}; + +class CommandProcessingTimeEstimatorVersion4 final : public ICommandProcessingTimeEstimator { +public: + CommandProcessingTimeEstimatorVersion4(u32 sample_count_, u32 buffer_count_) + : sample_count{sample_count_}, buffer_count{buffer_count_} {} + + u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const override; + u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const override; + u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const override; + u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const override; + u32 Estimate(const AdpcmDataSourceVersion1Command& command) const override; + u32 Estimate(const AdpcmDataSourceVersion2Command& command) const override; + u32 Estimate(const VolumeCommand& command) const override; + u32 Estimate(const VolumeRampCommand& command) const override; + u32 Estimate(const BiquadFilterCommand& command) const override; + u32 Estimate(const MixCommand& command) const override; + u32 Estimate(const MixRampCommand& command) const override; + u32 Estimate(const MixRampGroupedCommand& command) const override; + u32 Estimate(const DepopPrepareCommand& command) const override; + u32 Estimate(const DepopForMixBuffersCommand& command) const override; + u32 Estimate(const DelayCommand& command) const override; + u32 Estimate(const UpsampleCommand& command) const override; + u32 Estimate(const DownMix6chTo2chCommand& command) const override; + u32 Estimate(const AuxCommand& command) const override; + u32 Estimate(const DeviceSinkCommand& command) const override; + u32 Estimate(const CircularBufferSinkCommand& command) const override; + u32 Estimate(const ReverbCommand& command) const override; + u32 Estimate(const I3dl2ReverbCommand& command) const override; + u32 Estimate(const PerformanceCommand& command) const override; + u32 Estimate(const ClearMixBufferCommand& command) const override; + u32 Estimate(const CopyMixBufferCommand& command) const override; + u32 Estimate(const LightLimiterVersion1Command& command) const override; + u32 Estimate(const LightLimiterVersion2Command& command) const override; + u32 Estimate(const MultiTapBiquadFilterCommand& command) const override; + u32 Estimate(const CaptureCommand& command) const override; + u32 Estimate(const CompressorCommand& command) const override; + +private: + u32 sample_count{}; + u32 buffer_count{}; +}; + +class CommandProcessingTimeEstimatorVersion5 final : public ICommandProcessingTimeEstimator { +public: + CommandProcessingTimeEstimatorVersion5(u32 sample_count_, u32 buffer_count_) + : sample_count{sample_count_}, buffer_count{buffer_count_} {} + + u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const override; + u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const override; + u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const override; + u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const override; + u32 Estimate(const AdpcmDataSourceVersion1Command& command) const override; + u32 Estimate(const AdpcmDataSourceVersion2Command& command) const override; + u32 Estimate(const VolumeCommand& command) const override; + u32 Estimate(const VolumeRampCommand& command) const override; + u32 Estimate(const BiquadFilterCommand& command) const override; + u32 Estimate(const MixCommand& command) const override; + u32 Estimate(const MixRampCommand& command) const override; + u32 Estimate(const MixRampGroupedCommand& command) const override; + u32 Estimate(const DepopPrepareCommand& command) const override; + u32 Estimate(const DepopForMixBuffersCommand& command) const override; + u32 Estimate(const DelayCommand& command) const override; + u32 Estimate(const UpsampleCommand& command) const override; + u32 Estimate(const DownMix6chTo2chCommand& command) const override; + u32 Estimate(const AuxCommand& command) const override; + u32 Estimate(const DeviceSinkCommand& command) const override; + u32 Estimate(const CircularBufferSinkCommand& command) const override; + u32 Estimate(const ReverbCommand& command) const override; + u32 Estimate(const I3dl2ReverbCommand& command) const override; + u32 Estimate(const PerformanceCommand& command) const override; + u32 Estimate(const ClearMixBufferCommand& command) const override; + u32 Estimate(const CopyMixBufferCommand& command) const override; + u32 Estimate(const LightLimiterVersion1Command& command) const override; + u32 Estimate(const LightLimiterVersion2Command& command) const override; + u32 Estimate(const MultiTapBiquadFilterCommand& command) const override; + u32 Estimate(const CaptureCommand& command) const override; + u32 Estimate(const CompressorCommand& command) const override; + +private: + u32 sample_count{}; + u32 buffer_count{}; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/commands.h b/src/audio_core/renderer/command/commands.h new file mode 100644 index 000000000..6d8b8546d --- /dev/null +++ b/src/audio_core/renderer/command/commands.h @@ -0,0 +1,32 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "audio_core/renderer/command/data_source/adpcm.h" +#include "audio_core/renderer/command/data_source/pcm_float.h" +#include "audio_core/renderer/command/data_source/pcm_int16.h" +#include "audio_core/renderer/command/effect/aux_.h" +#include "audio_core/renderer/command/effect/biquad_filter.h" +#include "audio_core/renderer/command/effect/capture.h" +#include "audio_core/renderer/command/effect/compressor.h" +#include "audio_core/renderer/command/effect/delay.h" +#include "audio_core/renderer/command/effect/i3dl2_reverb.h" +#include "audio_core/renderer/command/effect/light_limiter.h" +#include "audio_core/renderer/command/effect/multi_tap_biquad_filter.h" +#include "audio_core/renderer/command/effect/reverb.h" +#include "audio_core/renderer/command/icommand.h" +#include "audio_core/renderer/command/mix/clear_mix.h" +#include "audio_core/renderer/command/mix/copy_mix.h" +#include "audio_core/renderer/command/mix/depop_for_mix_buffers.h" +#include "audio_core/renderer/command/mix/depop_prepare.h" +#include "audio_core/renderer/command/mix/mix.h" +#include "audio_core/renderer/command/mix/mix_ramp.h" +#include "audio_core/renderer/command/mix/mix_ramp_grouped.h" +#include "audio_core/renderer/command/mix/volume.h" +#include "audio_core/renderer/command/mix/volume_ramp.h" +#include "audio_core/renderer/command/performance/performance.h" +#include "audio_core/renderer/command/resample/downmix_6ch_to_2ch.h" +#include "audio_core/renderer/command/resample/upsample.h" +#include "audio_core/renderer/command/sink/circular_buffer.h" +#include "audio_core/renderer/command/sink/device.h" diff --git a/src/audio_core/renderer/command/data_source/adpcm.cpp b/src/audio_core/renderer/command/data_source/adpcm.cpp new file mode 100644 index 000000000..e66ed2990 --- /dev/null +++ b/src/audio_core/renderer/command/data_source/adpcm.cpp @@ -0,0 +1,84 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include + +#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(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(voice_state)}, + .wave_buffers{wave_buffers}, + .channel{0}, + .channel_count{1}, + .src_quality{src_quality}, + .pitch{pitch}, + .source_sample_rate{sample_rate}, + .target_sample_rate{processor.target_sample_rate}, + .sample_count{processor.sample_count}, + .data_address{data_address}, + .data_size{data_size}, + .IsVoicePlayedSampleCountResetAtLoopPointSupported{(flags & 1) != 0}, + .IsVoicePitchAndSrcSkippedSupported{(flags & 2) != 0}, + }; + + DecodeFromWaveBuffers(*processor.memory, args); +} + +bool AdpcmDataSourceVersion2Command::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/data_source/adpcm.h b/src/audio_core/renderer/command/data_source/adpcm.h new file mode 100644 index 000000000..a9cf9cee4 --- /dev/null +++ b/src/audio_core/renderer/command/data_source/adpcm.h @@ -0,0 +1,119 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#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 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 wave_buffers; + /// Voice state, updated each call and written back to game + CpuAddr voice_state; + /// Coefficients data address + CpuAddr data_address; + /// Coefficients data size + u64 data_size; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/data_source/decode.cpp b/src/audio_core/renderer/command/data_source/decode.cpp new file mode 100644 index 000000000..ff5d31bd6 --- /dev/null +++ b/src/audio_core/renderer/command/data_source/decode.cpp @@ -0,0 +1,428 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include + +#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 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 +static u32 DecodePcm(Core::Memory::Memory& memory, std::span out_buffer, + const DecodeArg& req) { + constexpr s32 min{std::numeric_limits::min()}; + constexpr s32 max{std::numeric_limits::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(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 samples(size); + memory.ReadBlockUnsafe(source, samples.data(), size_bytes); + + if constexpr (std::is_floating_point_v) { + for (u32 i = 0; i < samples_to_decode; i++) { + auto sample{static_cast(samples[i * channel_count + req.target_channel] * + std::numeric_limits::max())}; + out_buffer[i] = static_cast(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 samples(samples_to_decode); + memory.ReadBlockUnsafe(source, samples.data(), samples_to_decode * sizeof(T)); + + if constexpr (std::is_floating_point_v) { + for (u32 i = 0; i < samples_to_decode; i++) { + auto sample{static_cast(samples[i * channel_count + req.target_channel] * + std::numeric_limits::max())}; + out_buffer[i] = static_cast(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 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 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((header >> 4U) & 0xFU)}; + u8 scale{static_cast(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 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(sample, -0x8000, 0x7FFF); + yn1 = yn0; + yn0 = static_cast(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(args.src_quality)]}; + if (static_cast(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 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( + memory, {&temp_buffer[temp_buffer_pos], TempBufferSize - temp_buffer_pos}, + decode_arg); + break; + + case SampleFormat::PcmFloat: + samples_decoded = DecodePcm( + 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(args.sample_format)); + samples_decoded = 0; + break; + } + + played_sample_count += samples_decoded; + samples_read += samples_decoded; + temp_buffer_pos += samples_decoded; + offset += samples_decoded; + + if (samples_decoded == 0 || offset >= end_offset - start_offset) { + offset = 0; + if (!wavebuffer.loop) { + voice_state.wave_buffer_valid[wavebuffer_index] = false; + voice_state.loop_count = 0; + + if (wavebuffer.stream_ended) { + played_sample_count = 0; + } + + wavebuffer_index = (wavebuffer_index + 1) % MaxWaveBuffers; + wavebuffers_consumed++; + } else { + voice_state.loop_count++; + if (wavebuffer.loop_count > 0 && + (voice_state.loop_count > wavebuffer.loop_count || samples_decoded == 0)) { + voice_state.wave_buffer_valid[wavebuffer_index] = false; + voice_state.loop_count = 0; + + if (wavebuffer.stream_ended) { + played_sample_count = 0; + } + + wavebuffer_index = (wavebuffer_index + 1) % MaxWaveBuffers; + wavebuffers_consumed++; + } + + if (samples_decoded == 0) { + is_buffer_starved = true; + break; + } + + if (args.IsVoicePlayedSampleCountResetAtLoopPointSupported) { + played_sample_count = 0; + } + } + } + } + + if (args.IsVoicePitchAndSrcSkippedSupported) { + if (samples_read > output_buffer.size()) { + LOG_ERROR(Service_Audio, "Attempting to write past the end of output buffer!"); + } + for (u32 i = 0; i < samples_read; i++) { + output_buffer[i] = temp_buffer[i]; + } + } else { + std::memset(&temp_buffer[temp_buffer_pos], 0, + (samples_to_read - samples_read) * sizeof(s16)); + + Resample(output_buffer, temp_buffer, sample_rate_ratio, fraction, samples_to_write, + args.src_quality); + + std::memcpy(voice_state.sample_history.data(), &temp_buffer[samples_to_read], + pitch * sizeof(s16)); + } + + remaining_sample_count -= samples_to_write; + if (remaining_sample_count != 0 && is_buffer_starved) { + LOG_ERROR(Service_Audio, "Samples remaining but buffer is starving??"); + break; + } + + output_buffer = output_buffer.subspan(samples_to_write); + } + + voice_state.wave_buffers_consumed = wavebuffers_consumed; + voice_state.played_sample_count = played_sample_count; + voice_state.wave_buffer_index = wavebuffer_index; + voice_state.offset = offset; + voice_state.fraction = fraction; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/data_source/decode.h b/src/audio_core/renderer/command/data_source/decode.h new file mode 100644 index 000000000..4d63d6fa8 --- /dev/null +++ b/src/audio_core/renderer/command/data_source/decode.h @@ -0,0 +1,59 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#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 output; + VoiceState* voice_state; + std::span 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 coefficients; + VoiceState::AdpcmContext* adpcm_context; + s8 target_channel; + u32 offset; + u32 samples_to_read; +}; + +/** + * Decode wavebuffers according to the given args. + * + * @param memory - Core memory to read data from. + * @param args - The wavebuffer data, and information for how to decode it. + */ +void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuffersArgs& args); + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/data_source/pcm_float.cpp b/src/audio_core/renderer/command/data_source/pcm_float.cpp new file mode 100644 index 000000000..be77fab69 --- /dev/null +++ b/src/audio_core/renderer/command/data_source/pcm_float.cpp @@ -0,0 +1,86 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/adsp/command_list_processor.h" +#include "audio_core/renderer/command/data_source/decode.h" +#include "audio_core/renderer/command/data_source/pcm_float.h" + +namespace AudioCore::AudioRenderer { + +void PcmFloatDataSourceVersion1Command::Dump(const ADSP::CommandListProcessor& processor, + std::string& string) { + string += + fmt::format("PcmFloatDataSourceVersion1Command\n\toutput_index {:02X} channel {} " + "channel count {} source sample rate {} target sample rate {} src quality {}\n", + output_index, channel_index, channel_count, sample_rate, + processor.target_sample_rate, src_quality); +} + +void PcmFloatDataSourceVersion1Command::Process(const ADSP::CommandListProcessor& processor) { + auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count, + processor.sample_count); + + DecodeFromWaveBuffersArgs args{ + .sample_format{SampleFormat::PcmFloat}, + .output{out_buffer}, + .voice_state{reinterpret_cast(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(voice_state)}, + .wave_buffers{wave_buffers}, + .channel{channel_index}, + .channel_count{channel_count}, + .src_quality{src_quality}, + .pitch{pitch}, + .source_sample_rate{sample_rate}, + .target_sample_rate{processor.target_sample_rate}, + .sample_count{processor.sample_count}, + .data_address{0}, + .data_size{0}, + .IsVoicePlayedSampleCountResetAtLoopPointSupported{(flags & 1) != 0}, + .IsVoicePitchAndSrcSkippedSupported{(flags & 2) != 0}, + }; + + DecodeFromWaveBuffers(*processor.memory, args); +} + +bool PcmFloatDataSourceVersion2Command::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/data_source/pcm_float.h b/src/audio_core/renderer/command/data_source/pcm_float.h new file mode 100644 index 000000000..e4af77c20 --- /dev/null +++ b/src/audio_core/renderer/command/data_source/pcm_float.h @@ -0,0 +1,113 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#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 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 wave_buffers; + /// Voice state, updated each call and written back to game + CpuAddr voice_state; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/data_source/pcm_int16.cpp b/src/audio_core/renderer/command/data_source/pcm_int16.cpp new file mode 100644 index 000000000..7a27463e4 --- /dev/null +++ b/src/audio_core/renderer/command/data_source/pcm_int16.cpp @@ -0,0 +1,87 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include + +#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(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(voice_state)}, + .wave_buffers{wave_buffers}, + .channel{channel_index}, + .channel_count{channel_count}, + .src_quality{src_quality}, + .pitch{pitch}, + .source_sample_rate{sample_rate}, + .target_sample_rate{processor.target_sample_rate}, + .sample_count{processor.sample_count}, + .data_address{0}, + .data_size{0}, + .IsVoicePlayedSampleCountResetAtLoopPointSupported{(flags & 1) != 0}, + .IsVoicePitchAndSrcSkippedSupported{(flags & 2) != 0}, + }; + + DecodeFromWaveBuffers(*processor.memory, args); +} + +bool PcmInt16DataSourceVersion2Command::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/data_source/pcm_int16.h b/src/audio_core/renderer/command/data_source/pcm_int16.h new file mode 100644 index 000000000..5de1ad60d --- /dev/null +++ b/src/audio_core/renderer/command/data_source/pcm_int16.h @@ -0,0 +1,110 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#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 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 wave_buffers; + /// Voice state, updated each call and written back to game + CpuAddr voice_state; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/effect/aux_.cpp b/src/audio_core/renderer/command/effect/aux_.cpp new file mode 100644 index 000000000..e76db893f --- /dev/null +++ b/src/audio_core/renderer/command/effect/aux_.cpp @@ -0,0 +1,207 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/adsp/command_list_processor.h" +#include "audio_core/renderer/command/effect/aux_.h" +#include "audio_core/renderer/effect/aux_.h" +#include "core/memory.h" + +namespace AudioCore::AudioRenderer { +/** + * Reset an AuxBuffer. + * + * @param memory - Core memory for writing. + * @param aux_info - Memory address pointing to the AuxInfo to reset. + */ +static void ResetAuxBufferDsp(Core::Memory::Memory& memory, const CpuAddr aux_info) { + if (aux_info == 0) { + LOG_ERROR(Service_Audio, "Aux info is 0!"); + return; + } + + auto info{reinterpret_cast(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 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 output, + const u32 count_, const u32 read_offset, const u32 update_count) { + if (count_max == 0) { + return 0; + } + + if (count_ > count_max) { + LOG_ERROR(Service_Audio, "count must be smaller than count_max! count {}, count_max {}", + count_, count_max); + return 0; + } + + if (output.empty()) { + LOG_ERROR(Service_Audio, "output buffer is empty!"); + return 0; + } + + if (return_buffer == 0) { + LOG_ERROR(Service_Audio, "return_buffer is 0!"); + return 0; + } + + AuxInfo::AuxInfoDsp return_info{}; + memory.ReadBlockUnsafe(return_info_, &return_info, sizeof(AuxInfo::AuxInfoDsp)); + + u32 target_read_offset{return_info.read_offset + read_offset}; + if (target_read_offset > count_max) { + return 0; + } + + u32 read_count{count_}; + u32 read_pos{0}; + while (read_count > 0) { + u32 to_read{std::min(count_max - target_read_offset, read_count)}; + + if (to_read > 0) { + memory.ReadBlockUnsafe(return_buffer + target_read_offset * sizeof(s32), + &output[read_pos], to_read * sizeof(s32)); + } + + target_read_offset = (target_read_offset + to_read) % count_max; + read_count -= to_read; + read_pos += to_read; + } + + if (update_count) { + return_info.read_offset = (return_info.read_offset + update_count) % count_max; + } + + memory.WriteBlockUnsafe(return_info_, &return_info, sizeof(AuxInfo::AuxInfoDsp)); + + return count_; +} + +void AuxCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, + std::string& string) { + string += fmt::format("AuxCommand\n\tenabled {} input {:02X} output {:02X}\n", effect_enabled, + input, output); +} + +void AuxCommand::Process(const ADSP::CommandListProcessor& processor) { + auto input_buffer{ + processor.mix_buffers.subspan(input * processor.sample_count, processor.sample_count)}; + auto output_buffer{ + processor.mix_buffers.subspan(output * processor.sample_count, processor.sample_count)}; + + if (effect_enabled) { + WriteAuxBufferDsp(*processor.memory, send_buffer_info, processor.sample_count, send_buffer, + count_max, input_buffer, processor.sample_count, write_offset, + update_count); + + auto read{ReadAuxBufferDsp(*processor.memory, return_buffer_info, return_buffer, count_max, + output_buffer, processor.sample_count, write_offset, + update_count)}; + + if (read != processor.sample_count) { + std::memset(&output_buffer[read], 0, processor.sample_count - read); + } + } else { + ResetAuxBufferDsp(*processor.memory, send_buffer_info); + ResetAuxBufferDsp(*processor.memory, return_buffer_info); + if (input != output) { + std::memcpy(output_buffer.data(), input_buffer.data(), output_buffer.size_bytes()); + } + } +} + +bool AuxCommand::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/effect/aux_.h b/src/audio_core/renderer/command/effect/aux_.h new file mode 100644 index 000000000..825c93732 --- /dev/null +++ b/src/audio_core/renderer/command/effect/aux_.h @@ -0,0 +1,66 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "audio_core/renderer/command/icommand.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +namespace ADSP { +class CommandListProcessor; +} + +/** + * AudioRenderer command to read and write an auxiliary buffer, writing the input mix buffer to game + * memory, and reading into the output buffer from game memory. + */ +struct AuxCommand : ICommand { + /** + * Print this command's information to a string. + * + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; + + /** + * Process this command. + * + * @param processor - The CommandListProcessor processing this command. + */ + void Process(const ADSP::CommandListProcessor& processor) override; + + /** + * Verify this command's data is valid. + * + * @param processor - The CommandListProcessor processing this command. + * @return True if the command is valid, otherwise false. + */ + bool Verify(const ADSP::CommandListProcessor& processor) override; + + /// Input mix buffer index + s16 input; + /// Output mix buffer index + s16 output; + /// Meta info for writing + CpuAddr send_buffer_info; + /// Meta info for reading + CpuAddr return_buffer_info; + /// Game memory write buffer + CpuAddr send_buffer; + /// Game memory read buffer + CpuAddr return_buffer; + /// Max samples to read/write + u32 count_max; + /// Current read/write offset + u32 write_offset; + /// Number of samples to update per call + u32 update_count; + /// is this effect enabled? + bool effect_enabled; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/effect/biquad_filter.cpp b/src/audio_core/renderer/command/effect/biquad_filter.cpp new file mode 100644 index 000000000..1baae74fd --- /dev/null +++ b/src/audio_core/renderer/command/effect/biquad_filter.cpp @@ -0,0 +1,118 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/adsp/command_list_processor.h" +#include "audio_core/renderer/command/effect/biquad_filter.h" +#include "audio_core/renderer/voice/voice_state.h" + +namespace AudioCore::AudioRenderer { +/** + * Biquad filter float implementation. + * + * @param output - Output container for filtered samples. + * @param input - Input container for samples to be filtered. + * @param b - Feedforward coefficients. + * @param a - Feedback coefficients. + * @param state - State to track previous samples between calls. + * @param sample_count - Number of samples to process. + */ +void ApplyBiquadFilterFloat(std::span output, std::span input, + std::array& b_, std::array& a_, + VoiceState::BiquadFilterState& state, const u32 sample_count) { + constexpr s64 min{std::numeric_limits::min()}; + constexpr s64 max{std::numeric_limits::max()}; + std::array 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 a{Common::FixedPoint<50, 14>::from_base(a_[0]).to_double(), + Common::FixedPoint<50, 14>::from_base(a_[1]).to_double()}; + std::array 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(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(std::clamp(static_cast(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 output, std::span input, + std::array& b_, std::array& a_, + VoiceState::BiquadFilterState& state, const u32 sample_count) { + constexpr s64 min{std::numeric_limits::min()}; + constexpr s64 max{std::numeric_limits::max()}; + std::array, 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, 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(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(state)}; + if (needs_init) { + std::memset(state_, 0, sizeof(VoiceState::BiquadFilterState)); + } + + auto input_buffer{ + processor.mix_buffers.subspan(input * processor.sample_count, processor.sample_count)}; + auto output_buffer{ + processor.mix_buffers.subspan(output * processor.sample_count, processor.sample_count)}; + + if (use_float_processing) { + ApplyBiquadFilterFloat(output_buffer, input_buffer, biquad.b, biquad.a, *state_, + processor.sample_count); + } else { + ApplyBiquadFilterInt(output_buffer, input_buffer, biquad.b, biquad.a, *state_, + processor.sample_count); + } +} + +bool BiquadFilterCommand::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/effect/biquad_filter.h b/src/audio_core/renderer/command/effect/biquad_filter.h new file mode 100644 index 000000000..4c9c42d29 --- /dev/null +++ b/src/audio_core/renderer/command/effect/biquad_filter.h @@ -0,0 +1,74 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#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 output, std::span input, + std::array& b, std::array& a, + VoiceState::BiquadFilterState& state, const u32 sample_count); + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/effect/capture.cpp b/src/audio_core/renderer/command/effect/capture.cpp new file mode 100644 index 000000000..042fd286e --- /dev/null +++ b/src/audio_core/renderer/command/effect/capture.cpp @@ -0,0 +1,142 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/adsp/command_list_processor.h" +#include "audio_core/renderer/command/effect/capture.h" +#include "audio_core/renderer/effect/aux_.h" +#include "core/memory.h" + +namespace AudioCore::AudioRenderer { +/** + * Reset an AuxBuffer. + * + * @param memory - Core memory for writing. + * @param aux_info - Memory address pointing to the AuxInfo to reset. + */ +static void ResetAuxBufferDsp(Core::Memory::Memory& memory, const CpuAddr aux_info) { + if (aux_info == 0) { + LOG_ERROR(Service_Audio, "Aux info is 0!"); + return; + } + + memory.Write32(VAddr(aux_info + offsetof(AuxInfo::AuxInfoDsp, read_offset)), 0); + memory.Write32(VAddr(aux_info + offsetof(AuxInfo::AuxInfoDsp, write_offset)), 0); + memory.Write32(VAddr(aux_info + offsetof(AuxInfo::AuxInfoDsp, total_sample_count)), 0); +} + +/** + * Write the given input mix buffer to the memory at send_buffer, and update send_info_ if + * update_count is set, to notify the game that an update happened. + * + * @param memory - Core memory for writing. + * @param send_info_ - Header information for where to write the mix buffer. + * @param send_buffer - Memory address to write the mix buffer to. + * @param count_max - Maximum number of samples in the receiving buffer. + * @param input - Input mix buffer to write. + * @param write_count_ - Number of samples to write. + * @param write_offset - Current offset to begin writing the receiving buffer at. + * @param update_count - If non-zero, send_info_ will be updated. + * @return Number of samples written. + */ +static u32 WriteAuxBufferDsp(Core::Memory::Memory& memory, const CpuAddr send_info_, + const CpuAddr send_buffer, u32 count_max, std::span input, + const u32 write_count_, const u32 write_offset, + const u32 update_count) { + if (write_count_ > count_max) { + LOG_ERROR(Service_Audio, + "write_count must be smaller than count_max! write_count {}, count_max {}", + write_count_, count_max); + return 0; + } + + if (send_info_ == 0) { + LOG_ERROR(Service_Audio, "send_info is 0!"); + return 0; + } + + if (input.empty()) { + LOG_ERROR(Service_Audio, "input buffer is empty!"); + return 0; + } + + if (send_buffer == 0) { + LOG_ERROR(Service_Audio, "send_buffer is 0!"); + return 0; + } + + if (count_max == 0) { + return 0; + } + + AuxInfo::AuxBufferInfo send_info{}; + memory.ReadBlockUnsafe(send_info_, &send_info, sizeof(AuxInfo::AuxBufferInfo)); + + u32 target_write_offset{send_info.dsp_info.write_offset + write_offset}; + if (target_write_offset > count_max || write_count_ == 0) { + return 0; + } + + u32 write_count{write_count_}; + u32 write_pos{0}; + while (write_count > 0) { + u32 to_write{std::min(count_max - target_write_offset, write_count)}; + + if (to_write > 0) { + memory.WriteBlockUnsafe(send_buffer + target_write_offset * sizeof(s32), + &input[write_pos], to_write * sizeof(s32)); + } + + target_write_offset = (target_write_offset + to_write) % count_max; + write_count -= to_write; + write_pos += to_write; + } + + if (update_count) { + const auto count_diff{send_info.dsp_info.total_sample_count - + send_info.cpu_info.total_sample_count}; + if (count_diff >= count_max) { + auto dsp_lost_count{send_info.dsp_info.lost_sample_count + update_count}; + if (dsp_lost_count - send_info.cpu_info.lost_sample_count < + send_info.dsp_info.lost_sample_count - send_info.cpu_info.lost_sample_count) { + dsp_lost_count = send_info.cpu_info.lost_sample_count - 1; + } + send_info.dsp_info.lost_sample_count = dsp_lost_count; + } + + send_info.dsp_info.write_offset = + (send_info.dsp_info.write_offset + update_count + count_max) % count_max; + + auto new_sample_count{send_info.dsp_info.total_sample_count + update_count}; + if (new_sample_count - send_info.cpu_info.total_sample_count < count_diff) { + new_sample_count = send_info.cpu_info.total_sample_count - 1; + } + send_info.dsp_info.total_sample_count = new_sample_count; + } + + memory.WriteBlockUnsafe(send_info_, &send_info, sizeof(AuxInfo::AuxBufferInfo)); + + return write_count_; +} + +void CaptureCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, + std::string& string) { + string += fmt::format("CaptureCommand\n\tenabled {} input {:02X} output {:02X}", effect_enabled, + input, output); +} + +void CaptureCommand::Process(const ADSP::CommandListProcessor& processor) { + if (effect_enabled) { + auto input_buffer{ + processor.mix_buffers.subspan(input * processor.sample_count, processor.sample_count)}; + WriteAuxBufferDsp(*processor.memory, send_buffer_info, send_buffer, count_max, input_buffer, + processor.sample_count, write_offset, update_count); + } else { + ResetAuxBufferDsp(*processor.memory, send_buffer_info); + } +} + +bool CaptureCommand::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/effect/capture.h b/src/audio_core/renderer/command/effect/capture.h new file mode 100644 index 000000000..8670acb24 --- /dev/null +++ b/src/audio_core/renderer/command/effect/capture.h @@ -0,0 +1,62 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "audio_core/renderer/command/icommand.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +namespace ADSP { +class CommandListProcessor; +} + +/** + * AudioRenderer command for capturing a mix buffer. That is, writing it back to a given game memory + * address. + */ +struct CaptureCommand : ICommand { + /** + * Print this command's information to a string. + * + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; + + /** + * Process this command. + * + * @param processor - The CommandListProcessor processing this command. + */ + void Process(const ADSP::CommandListProcessor& processor) override; + + /** + * Verify this command's data is valid. + * + * @param processor - The CommandListProcessor processing this command. + * @return True if the command is valid, otherwise false. + */ + bool Verify(const ADSP::CommandListProcessor& processor) override; + + /// Input mix buffer index + s16 input; + /// Output mix buffer index + s16 output; + /// Meta info for writing + CpuAddr send_buffer_info; + /// Game memory write buffer + CpuAddr send_buffer; + /// Max samples to read/write + u32 count_max; + /// Current read/write offset + u32 write_offset; + /// Number of samples to update per call + u32 update_count; + /// is this effect enabled? + bool effect_enabled; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/effect/compressor.cpp b/src/audio_core/renderer/command/effect/compressor.cpp new file mode 100644 index 000000000..2ebc140f1 --- /dev/null +++ b/src/audio_core/renderer/command/effect/compressor.cpp @@ -0,0 +1,156 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include + +#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> input_buffers, + std::vector> 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( + static_cast(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> input_buffers(parameter.channel_count); + std::vector> 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(state)}; + + if (effect_enabled) { + if (parameter.state == CompressorInfo::ParameterState::Updating) { + SetCompressorEffectParameter(parameter, *state_); + } else if (parameter.state == CompressorInfo::ParameterState::Initialized) { + InitializeCompressorEffect(parameter, *state_); + } + } + + ApplyCompressorEffect(parameter, *state_, effect_enabled, input_buffers, output_buffers, + processor.sample_count); +} + +bool CompressorCommand::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/effect/compressor.h b/src/audio_core/renderer/command/effect/compressor.h new file mode 100644 index 000000000..f8e96cb43 --- /dev/null +++ b/src/audio_core/renderer/command/effect/compressor.h @@ -0,0 +1,60 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#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 inputs; + /// Output mix buffer offsets for each channel + std::array outputs; + /// Input parameters + CompressorInfo::ParameterVersion2 parameter; + /// State, updated each call + CpuAddr state; + /// Game-supplied workbuffer (Unused) + CpuAddr workbuffer; + /// Is this effect enabled? + bool effect_enabled; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/effect/delay.cpp b/src/audio_core/renderer/command/effect/delay.cpp new file mode 100644 index 000000000..a4e408d40 --- /dev/null +++ b/src/audio_core/renderer/command/effect/delay.cpp @@ -0,0 +1,238 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/adsp/command_list_processor.h" +#include "audio_core/renderer/command/effect/delay.h" + +namespace AudioCore::AudioRenderer { +/** + * Update the DelayInfo state according to the given parameters. + * + * @param params - Input parameters to update the state. + * @param state - State to be updated. + */ +static void SetDelayEffectParameter(const DelayInfo::ParameterVersion1& params, + DelayInfo::State& state) { + auto channel_spread{params.channel_spread}; + state.feedback_gain = params.feedback_gain * 0.97998046875f; + state.delay_feedback_gain = state.feedback_gain * (1.0f - channel_spread); + if (params.channel_count == 4 || params.channel_count == 6) { + channel_spread >>= 1; + } + state.delay_feedback_cross_gain = channel_spread * state.feedback_gain; + state.lowpass_feedback_gain = params.lowpass_amount * 0.949951171875f; + state.lowpass_gain = 1.0f - state.lowpass_feedback_gain; +} + +/** + * Initialize a new DelayInfo state according to the given parameters. + * + * @param params - Input parameters to update the state. + * @param state - State to be updated. + * @param workbuffer - Game-supplied memory for the state. (Unused) + */ +static void InitializeDelayEffect(const DelayInfo::ParameterVersion1& params, + DelayInfo::State& state, + [[maybe_unused]] const CpuAddr workbuffer) { + state = {}; + + for (u32 channel = 0; channel < params.channel_count; channel++) { + Common::FixedPoint<32, 32> sample_count_max{0.064f}; + sample_count_max *= params.sample_rate.to_int_floor() * params.delay_time_max; + + Common::FixedPoint<18, 14> delay_time{params.delay_time}; + delay_time *= params.sample_rate / 1000; + Common::FixedPoint<32, 32> sample_count{delay_time}; + + if (sample_count > sample_count_max) { + sample_count = sample_count_max; + } + + state.delay_lines[channel].sample_count_max = sample_count_max.to_int_floor(); + state.delay_lines[channel].sample_count = sample_count.to_int_floor(); + state.delay_lines[channel].buffer.resize(state.delay_lines[channel].sample_count, 0); + if (state.delay_lines[channel].buffer.size() == 0) { + state.delay_lines[channel].buffer.push_back(0); + } + state.delay_lines[channel].buffer_pos = 0; + state.delay_lines[channel].decay_rate = 1.0f; + } + + SetDelayEffectParameter(params, state); +} + +/** + * Delay effect impl, according to the parameters and current state, on the input mix buffers, + * saving the results to the output mix buffers. + * + * @tparam NumChannels - Number of channels to process. 1-6. + * @param params - Input parameters to use. + * @param state - State to use, must be initialized (see InitializeDelayEffect). + * @param inputs - Input mix buffers to performan the delay on. + * @param outputs - Output mix buffers to receive the delayed samples. + * @param sample_count - Number of samples to process. + */ +template +static void ApplyDelay(const DelayInfo::ParameterVersion1& params, DelayInfo::State& state, + std::vector>& inputs, + std::vector>& outputs, const u32 sample_count) { + for (u32 sample_index = 0; sample_index < sample_count; sample_index++) { + std::array, NumChannels> input_samples{}; + for (u32 channel = 0; channel < NumChannels; channel++) { + input_samples[channel] = inputs[channel][sample_index] * 64; + } + + std::array, NumChannels> delay_samples{}; + for (u32 channel = 0; channel < NumChannels; channel++) { + delay_samples[channel] = state.delay_lines[channel].Read(); + } + + // clang-format off + std::array, 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, 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>& inputs, + std::vector>& 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> input_buffers(parameter.channel_count); + std::vector> 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(state)}; + + if (effect_enabled) { + if (parameter.state == DelayInfo::ParameterState::Updating) { + SetDelayEffectParameter(parameter, *state_); + } else if (parameter.state == DelayInfo::ParameterState::Initialized) { + InitializeDelayEffect(parameter, *state_, workbuffer); + } + } + ApplyDelayEffect(parameter, *state_, effect_enabled, input_buffers, output_buffers, + processor.sample_count); +} + +bool DelayCommand::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/effect/delay.h b/src/audio_core/renderer/command/effect/delay.h new file mode 100644 index 000000000..b7a15ae6b --- /dev/null +++ b/src/audio_core/renderer/command/effect/delay.h @@ -0,0 +1,60 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#include "audio_core/renderer/command/icommand.h" +#include "audio_core/renderer/effect/delay.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +namespace ADSP { +class CommandListProcessor; +} + +/** + * AudioRenderer command for a delay effect. Delays inputs mix buffers according to the parameters + * and state, outputs receives the delayed samples. + */ +struct DelayCommand : ICommand { + /** + * Print this command's information to a string. + * + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; + + /** + * Process this command. + * + * @param processor - The CommandListProcessor processing this command. + */ + void Process(const ADSP::CommandListProcessor& processor) override; + + /** + * Verify this command's data is valid. + * + * @param processor - The CommandListProcessor processing this command. + * @return True if the command is valid, otherwise false. + */ + bool Verify(const ADSP::CommandListProcessor& processor) override; + + /// Input mix buffer offsets for each channel + std::array inputs; + /// Output mix buffer offsets for each channel + std::array outputs; + /// Input parameters + DelayInfo::ParameterVersion1 parameter; + /// State, updated each call + CpuAddr state; + /// Game-supplied workbuffer (Unused) + CpuAddr workbuffer; + /// Is this effect enabled? + bool effect_enabled; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/effect/i3dl2_reverb.cpp b/src/audio_core/renderer/command/effect/i3dl2_reverb.cpp new file mode 100644 index 000000000..c4bf3943a --- /dev/null +++ b/src/audio_core/renderer/command/effect/i3dl2_reverb.cpp @@ -0,0 +1,437 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include + +#include "audio_core/renderer/adsp/command_list_processor.h" +#include "audio_core/renderer/command/effect/i3dl2_reverb.h" + +namespace AudioCore::AudioRenderer { + +constexpr std::array MinDelayLineTimes{ + 5.0f, + 6.0f, + 13.0f, + 14.0f, +}; +constexpr std::array MaxDelayLineTimes{ + 45.7042007446f, + 82.7817001343f, + 149.938293457f, + 271.575805664f, +}; +constexpr std::array Decay0MaxDelayLineTimes{17.0f, 13.0f, + 9.0f, 7.0f}; +constexpr std::array Decay1MaxDelayLineTimes{19.0f, 11.0f, + 10.0f, 6.0f}; +constexpr std::array EarlyTapTimes{ + 0.0171360000968f, + 0.0591540001333f, + 0.161733001471f, + 0.390186011791f, + 0.425262004137f, + 0.455410987139f, + 0.689737021923f, + 0.74590998888f, + 0.833844006062f, + 0.859502017498f, + 0.0f, + 0.0750240013003f, + 0.168788000941f, + 0.299901008606f, + 0.337442994118f, + 0.371903002262f, + 0.599011003971f, + 0.716741025448f, + 0.817858994007f, + 0.85166400671f, +}; + +constexpr std::array EarlyGains{ + 0.67096f, 0.61027f, 1.0f, 0.3568f, 0.68361f, 0.65978f, 0.51939f, + 0.24712f, 0.45945f, 0.45021f, 0.64196f, 0.54879f, 0.92925f, 0.3827f, + 0.72867f, 0.69794f, 0.5464f, 0.24563f, 0.45214f, 0.44042f}; + +/** + * Update the I3dl2ReverbInfo state according to the given parameters. + * + * @param params - Input parameters to update the state. + * @param state - State to be updated. + * @param reset - If enabled, the state buffers will be reset. Only set this on initialize. + */ +static void UpdateI3dl2ReverbEffectParameter(const I3dl2ReverbInfo::ParameterVersion1& params, + I3dl2ReverbInfo::State& state, const bool reset) { + const auto pow_10 = [](f32 val) -> f32 { + return (val >= 0.0f) ? 1.0f : (val <= -5.3f) ? 0.0f : std::pow(10.0f, val); + }; + const auto sin = [](f32 degrees) -> f32 { + return std::sin(degrees * std::numbers::pi_v / 180.0f); + }; + const auto cos = [](f32 degrees) -> f32 { + return std::cos(degrees * std::numbers::pi_v / 180.0f); + }; + + Common::FixedPoint<50, 14> delay{static_cast(params.sample_rate) / 1000.0f}; + + state.dry_gain = params.dry_gain; + Common::FixedPoint<50, 14> early_gain{ + std::min(params.room_gain + params.reflection_gain, 5000.0f) / 2000.0f}; + state.early_gain = pow_10(early_gain.to_float()); + Common::FixedPoint<50, 14> late_gain{std::min(params.room_gain + params.reverb_gain, 5000.0f) / + 2000.0f}; + state.late_gain = pow_10(late_gain.to_float()); + + Common::FixedPoint<50, 14> hf_gain{pow_10(params.room_HF_gain / 2000.0f)}; + if (hf_gain >= 1.0f) { + state.lowpass_1 = 0.0f; + state.lowpass_2 = 1.0f; + } else { + const auto reference_hf{(params.reference_HF * 256.0f) / + static_cast(params.sample_rate)}; + const Common::FixedPoint<50, 14> a{1.0f - hf_gain.to_float()}; + const Common::FixedPoint<50, 14> b{2.0f + (-cos(reference_hf) * (hf_gain * 2.0f))}; + const Common::FixedPoint<50, 14> c{ + std::sqrt(std::pow(b.to_float(), 2.0f) + (std::pow(a.to_float(), 2.0f) * -4.0f))}; + + state.lowpass_1 = std::min(((b - c) / (a * 2.0f)).to_float(), 0.99723f); + state.lowpass_2 = 1.0f - state.lowpass_1; + } + + state.early_to_late_taps = + (((params.reflection_delay + params.late_reverb_delay_time) * 1000.0f) * delay).to_int(); + state.last_reverb_echo = params.late_reverb_diffusion * 0.6f * 0.01f; + + for (u32 i = 0; i < I3dl2ReverbInfo::MaxDelayLines; i++) { + auto curr_delay{ + ((MinDelayLineTimes[i] + (params.late_reverb_density / 100.0f) * + (MaxDelayLineTimes[i] - MinDelayLineTimes[i])) * + delay) + .to_int()}; + state.fdn_delay_lines[i].SetDelay(curr_delay); + + const auto a{ + (static_cast(state.fdn_delay_lines[i].delay + state.decay_delay_lines0[i].delay + + state.decay_delay_lines1[i].delay) * + -60.0f) / + (params.late_reverb_decay_time * static_cast(params.sample_rate))}; + const auto b{a / params.late_reverb_HF_decay_ratio}; + const auto c{ + cos(((params.reference_HF * 0.5f) * 128.0f) / static_cast(params.sample_rate)) / + sin(((params.reference_HF * 0.5f) * 128.0f) / static_cast(params.sample_rate))}; + const auto d{pow_10((b - a) / 40.0f)}; + const auto e{pow_10((b + a) / 40.0f) * 0.7071f}; + + state.lowpass_coeff[i][0] = ((c * d + 1.0f) * e) / (c + d); + state.lowpass_coeff[i][1] = ((1.0f - (c * d)) * e) / (c + d); + state.lowpass_coeff[i][2] = (c - d) / (c + d); + + state.decay_delay_lines0[i].wet_gain = state.last_reverb_echo; + state.decay_delay_lines1[i].wet_gain = state.last_reverb_echo * -0.9f; + } + + if (reset) { + state.shelf_filter.fill(0.0f); + state.lowpass_0 = 0.0f; + for (u32 i = 0; i < I3dl2ReverbInfo::MaxDelayLines; i++) { + std::ranges::fill(state.fdn_delay_lines[i].buffer, 0); + std::ranges::fill(state.decay_delay_lines0[i].buffer, 0); + std::ranges::fill(state.decay_delay_lines1[i].buffer, 0); + } + std::ranges::fill(state.center_delay_line.buffer, 0); + std::ranges::fill(state.early_delay_line.buffer, 0); + } + + const auto reflection_time{(params.late_reverb_delay_time * 0.9998f + 0.02f) * 1000.0f}; + const auto reflection_delay{params.reflection_delay * 1000.0f}; + for (u32 i = 0; i < I3dl2ReverbInfo::MaxDelayTaps; i++) { + auto length{((reflection_delay + reflection_time * EarlyTapTimes[i]) * delay).to_int()}; + if (length >= state.early_delay_line.max_delay) { + length = state.early_delay_line.max_delay; + } + state.early_tap_steps[i] = length; + } +} + +/** + * Initialize a new I3dl2ReverbInfo state according to the given parameters. + * + * @param params - Input parameters to update the state. + * @param state - State to be updated. + * @param workbuffer - Game-supplied memory for the state. (Unused) + */ +static void InitializeI3dl2ReverbEffect(const I3dl2ReverbInfo::ParameterVersion1& params, + I3dl2ReverbInfo::State& state, const CpuAddr workbuffer) { + state = {}; + Common::FixedPoint<50, 14> delay{static_cast(params.sample_rate) / 1000}; + + for (u32 i = 0; i < I3dl2ReverbInfo::MaxDelayLines; i++) { + auto fdn_delay_time{(MaxDelayLineTimes[i] * delay).to_uint_floor()}; + state.fdn_delay_lines[i].Initialize(fdn_delay_time); + + auto decay0_delay_time{(Decay0MaxDelayLineTimes[i] * delay).to_uint_floor()}; + state.decay_delay_lines0[i].Initialize(decay0_delay_time); + + auto decay1_delay_time{(Decay1MaxDelayLineTimes[i] * delay).to_uint_floor()}; + state.decay_delay_lines1[i].Initialize(decay1_delay_time); + } + + const auto center_delay_time{(5 * delay).to_uint_floor()}; + state.center_delay_line.Initialize(center_delay_time); + + const auto early_delay_time{(400 * delay).to_uint_floor()}; + state.early_delay_line.Initialize(early_delay_time); + + UpdateI3dl2ReverbEffectParameter(params, state, true); +} + +/** + * Pass-through the effect, copying input to output directly, with no reverb applied. + * + * @param inputs - Array of input mix buffers to copy. + * @param outputs - Array of output mix buffers to receive copy. + * @param channel_count - Number of channels in inputs and outputs. + * @param sample_count - Number of samples within each channel (unused). + */ +static void ApplyI3dl2ReverbEffectBypass(std::span> inputs, + std::span> outputs, const u32 channel_count, + [[maybe_unused]] const u32 sample_count) { + for (u32 i = 0; i < channel_count; i++) { + if (inputs[i].data() != outputs[i].data()) { + std::memcpy(outputs[i].data(), inputs[i].data(), outputs[i].size_bytes()); + } + } +} + +/** + * Tick the delay lines, reading and returning their current output, and writing a new decaying + * sample (mix). + * + * @param decay0 - The first decay line. + * @param decay1 - The second decay line. + * @param fdn - Feedback delay network. + * @param mix - The new calculated sample to be written and decayed. + * @return The next delayed and decayed sample. + */ +static Common::FixedPoint<50, 14> Axfx2AllPassTick(I3dl2ReverbInfo::I3dl2DelayLine& decay0, + I3dl2ReverbInfo::I3dl2DelayLine& decay1, + I3dl2ReverbInfo::I3dl2DelayLine& fdn, + const Common::FixedPoint<50, 14> mix) { + auto val{decay0.Read()}; + auto mixed{mix - (val * decay0.wet_gain)}; + auto out{decay0.Tick(mixed) + (mixed * decay0.wet_gain)}; + + val = decay1.Read(); + mixed = out - (val * decay1.wet_gain); + out = decay1.Tick(mixed) + (mixed * decay1.wet_gain); + + fdn.Tick(out); + return out; +} + +/** + * Impl. Apply a I3DL2 reverb according to the current state, on the input mix buffers, + * saving the results to the output mix buffers. + * + * @tparam NumChannels - Number of channels to process. 1-6. + Inputs/outputs should have this many buffers. + * @param state - State to use, must be initialized (see InitializeI3dl2ReverbEffect). + * @param inputs - Input mix buffers to perform the reverb on. + * @param outputs - Output mix buffers to receive the reverbed samples. + * @param sample_count - Number of samples to process. + */ +template +static void ApplyI3dl2ReverbEffect(I3dl2ReverbInfo::State& state, + std::span> inputs, + std::span> outputs, const u32 sample_count) { + constexpr std::array OutTapIndexes1Ch{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }; + constexpr std::array OutTapIndexes2Ch{ + 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, + }; + constexpr std::array OutTapIndexes4Ch{ + 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 0, 0, 0, 0, 3, 3, 3, + }; + constexpr std::array OutTapIndexes6Ch{ + 2, 0, 0, 1, 1, 1, 1, 4, 4, 4, 1, 1, 1, 0, 0, 0, 0, 5, 5, 5, + }; + + std::span tap_indexes{}; + if constexpr (NumChannels == 1) { + tap_indexes = OutTapIndexes1Ch; + } else if constexpr (NumChannels == 2) { + tap_indexes = OutTapIndexes2Ch; + } else if constexpr (NumChannels == 4) { + tap_indexes = OutTapIndexes4Ch; + } else if constexpr (NumChannels == 6) { + tap_indexes = OutTapIndexes6Ch; + } + + for (u32 sample_index = 0; sample_index < sample_count; sample_index++) { + Common::FixedPoint<50, 14> early_to_late_tap{ + state.early_delay_line.TapOut(state.early_to_late_taps)}; + std::array, NumChannels> output_samples{}; + + for (u32 early_tap = 0; early_tap < I3dl2ReverbInfo::MaxDelayTaps; early_tap++) { + output_samples[tap_indexes[early_tap]] += + state.early_delay_line.TapOut(state.early_tap_steps[early_tap]) * + EarlyGains[early_tap]; + if constexpr (NumChannels == 6) { + output_samples[static_cast(Channels::LFE)] += + state.early_delay_line.TapOut(state.early_tap_steps[early_tap]) * + EarlyGains[early_tap]; + } + } + + Common::FixedPoint<50, 14> current_sample{}; + for (u32 channel = 0; channel < NumChannels; channel++) { + current_sample += inputs[channel][sample_index]; + } + + state.lowpass_0 = + (current_sample * state.lowpass_2 + state.lowpass_0 * state.lowpass_1).to_float(); + state.early_delay_line.Tick(state.lowpass_0); + + for (u32 channel = 0; channel < NumChannels; channel++) { + output_samples[channel] *= state.early_gain; + } + + std::array, I3dl2ReverbInfo::MaxDelayLines> filtered_samples{}; + for (u32 delay_line = 0; delay_line < I3dl2ReverbInfo::MaxDelayLines; delay_line++) { + filtered_samples[delay_line] = + state.fdn_delay_lines[delay_line].Read() * state.lowpass_coeff[delay_line][0] + + state.shelf_filter[delay_line]; + state.shelf_filter[delay_line] = + (filtered_samples[delay_line] * state.lowpass_coeff[delay_line][2] + + state.fdn_delay_lines[delay_line].Read() * state.lowpass_coeff[delay_line][1]) + .to_float(); + } + + const std::array, I3dl2ReverbInfo::MaxDelayLines> mix_matrix{ + filtered_samples[1] + filtered_samples[2] + early_to_late_tap * state.late_gain, + -filtered_samples[0] - filtered_samples[3] + early_to_late_tap * state.late_gain, + filtered_samples[0] - filtered_samples[3] + early_to_late_tap * state.late_gain, + filtered_samples[1] - filtered_samples[2] + early_to_late_tap * state.late_gain, + }; + + std::array, I3dl2ReverbInfo::MaxDelayLines> allpass_samples{}; + for (u32 delay_line = 0; delay_line < I3dl2ReverbInfo::MaxDelayLines; delay_line++) { + allpass_samples[delay_line] = Axfx2AllPassTick( + state.decay_delay_lines0[delay_line], state.decay_delay_lines1[delay_line], + state.fdn_delay_lines[delay_line], mix_matrix[delay_line]); + } + + if constexpr (NumChannels == 6) { + const std::array, MaxChannels> allpass_outputs{ + allpass_samples[0], allpass_samples[1], allpass_samples[2] - allpass_samples[3], + allpass_samples[3], allpass_samples[2], allpass_samples[3], + }; + + for (u32 channel = 0; channel < NumChannels; channel++) { + Common::FixedPoint<50, 14> allpass{}; + + if (channel == static_cast(Channels::Center)) { + allpass = state.center_delay_line.Tick(allpass_outputs[channel] * 0.5f); + } else { + allpass = allpass_outputs[channel]; + } + + auto out_sample{output_samples[channel] + allpass + + state.dry_gain * static_cast(inputs[channel][sample_index])}; + + outputs[channel][sample_index] = + static_cast(std::clamp(out_sample.to_float(), -8388600.0f, 8388600.0f)); + } + } else { + for (u32 channel = 0; channel < NumChannels; channel++) { + auto out_sample{output_samples[channel] + allpass_samples[channel] + + state.dry_gain * static_cast(inputs[channel][sample_index])}; + outputs[channel][sample_index] = + static_cast(std::clamp(out_sample.to_float(), -8388600.0f, 8388600.0f)); + } + } + } +} + +/** + * Apply a I3DL2 reverb if enabled, according to the current state, on the input mix buffers, + * saving the results to the output mix buffers. + * + * @param params - Input parameters to use. + * @param state - State to use, must be initialized (see InitializeI3dl2ReverbEffect). + * @param enabled - If enabled, delay will be applied, otherwise input is copied to output. + * @param inputs - Input mix buffers to performan the delay on. + * @param outputs - Output mix buffers to receive the delayed samples. + * @param sample_count - Number of samples to process. + */ +static void ApplyI3dl2ReverbEffect(const I3dl2ReverbInfo::ParameterVersion1& params, + I3dl2ReverbInfo::State& state, const bool enabled, + std::span> inputs, + std::span> outputs, const u32 sample_count) { + if (enabled) { + switch (params.channel_count) { + case 0: + return; + case 1: + ApplyI3dl2ReverbEffect<1>(state, inputs, outputs, sample_count); + break; + case 2: + ApplyI3dl2ReverbEffect<2>(state, inputs, outputs, sample_count); + break; + case 4: + ApplyI3dl2ReverbEffect<4>(state, inputs, outputs, sample_count); + break; + case 6: + ApplyI3dl2ReverbEffect<6>(state, inputs, outputs, sample_count); + break; + default: + ApplyI3dl2ReverbEffectBypass(inputs, outputs, params.channel_count, sample_count); + break; + } + } else { + ApplyI3dl2ReverbEffectBypass(inputs, outputs, params.channel_count, sample_count); + } +} + +void I3dl2ReverbCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, + std::string& string) { + string += fmt::format("I3dl2ReverbCommand\n\tenabled {} \n\tinputs: ", effect_enabled); + for (u32 i = 0; i < parameter.channel_count; i++) { + string += fmt::format("{:02X}, ", inputs[i]); + } + string += "\n\toutputs: "; + for (u32 i = 0; i < parameter.channel_count; i++) { + string += fmt::format("{:02X}, ", outputs[i]); + } + string += "\n"; +} + +void I3dl2ReverbCommand::Process(const ADSP::CommandListProcessor& processor) { + std::vector> input_buffers(parameter.channel_count); + std::vector> output_buffers(parameter.channel_count); + + for (u32 i = 0; i < parameter.channel_count; i++) { + input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count, + processor.sample_count); + output_buffers[i] = processor.mix_buffers.subspan(outputs[i] * processor.sample_count, + processor.sample_count); + } + + auto state_{reinterpret_cast(state)}; + + if (effect_enabled) { + if (parameter.state == I3dl2ReverbInfo::ParameterState::Updating) { + UpdateI3dl2ReverbEffectParameter(parameter, *state_, false); + } else if (parameter.state == I3dl2ReverbInfo::ParameterState::Initialized) { + InitializeI3dl2ReverbEffect(parameter, *state_, workbuffer); + } + } + ApplyI3dl2ReverbEffect(parameter, *state_, effect_enabled, input_buffers, output_buffers, + processor.sample_count); +} + +bool I3dl2ReverbCommand::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/effect/i3dl2_reverb.h b/src/audio_core/renderer/command/effect/i3dl2_reverb.h new file mode 100644 index 000000000..243877056 --- /dev/null +++ b/src/audio_core/renderer/command/effect/i3dl2_reverb.h @@ -0,0 +1,60 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#include "audio_core/renderer/command/icommand.h" +#include "audio_core/renderer/effect/i3dl2.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +namespace ADSP { +class CommandListProcessor; +} + +/** + * AudioRenderer command for a I3DL2Reverb effect. Apply a reverb to inputs mix buffer according to + * the I3DL2 spec, outputs receives the results. + */ +struct I3dl2ReverbCommand : ICommand { + /** + * Print this command's information to a string. + * + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; + + /** + * Process this command. + * + * @param processor - The CommandListProcessor processing this command. + */ + void Process(const ADSP::CommandListProcessor& processor) override; + + /** + * Verify this command's data is valid. + * + * @param processor - The CommandListProcessor processing this command. + * @return True if the command is valid, otherwise false. + */ + bool Verify(const ADSP::CommandListProcessor& processor) override; + + /// Input mix buffer offsets for each channel + std::array inputs; + /// Output mix buffer offsets for each channel + std::array outputs; + /// Input parameters + I3dl2ReverbInfo::ParameterVersion1 parameter; + /// State, updated each call + CpuAddr state; + /// Game-supplied workbuffer (Unused) + CpuAddr workbuffer; + /// Is this effect enabled? + bool effect_enabled; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/effect/light_limiter.cpp b/src/audio_core/renderer/command/effect/light_limiter.cpp new file mode 100644 index 000000000..e8fb0e2fc --- /dev/null +++ b/src/audio_core/renderer/command/effect/light_limiter.cpp @@ -0,0 +1,222 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/adsp/command_list_processor.h" +#include "audio_core/renderer/command/effect/light_limiter.h" + +namespace AudioCore::AudioRenderer { +/** + * Update the LightLimiterInfo state according to the given parameters. + * A no-op. + * + * @param params - Input parameters to update the state. + * @param state - State to be updated. + */ +static void UpdateLightLimiterEffectParameter(const LightLimiterInfo::ParameterVersion2& params, + LightLimiterInfo::State& state) {} + +/** + * Initialize a new LightLimiterInfo state according to the given parameters. + * + * @param params - Input parameters to update the state. + * @param state - State to be updated. + * @param workbuffer - Game-supplied memory for the state. (Unused) + */ +static void InitializeLightLimiterEffect(const LightLimiterInfo::ParameterVersion2& params, + LightLimiterInfo::State& state, const CpuAddr workbuffer) { + state = {}; + state.samples_average.fill(0.0f); + state.compression_gain.fill(1.0f); + state.look_ahead_sample_offsets.fill(0); + for (u32 i = 0; i < params.channel_count; i++) { + state.look_ahead_sample_buffers[i].resize(params.look_ahead_samples_max, 0.0f); + } +} + +/** + * Apply a light limiter effect if enabled, according to the current state, on the input mix + * buffers, saving the results to the output mix buffers. + * + * @param params - Input parameters to use. + * @param state - State to use, must be initialized (see InitializeLightLimiterEffect). + * @param enabled - If enabled, limiter will be applied, otherwise input is copied to output. + * @param inputs - Input mix buffers to perform the limiter on. + * @param outputs - Output mix buffers to receive the limited samples. + * @param sample_count - Number of samples to process. + * @params statistics - Optional output statistics, only used with version 2. + */ +static void ApplyLightLimiterEffect(const LightLimiterInfo::ParameterVersion2& params, + LightLimiterInfo::State& state, const bool enabled, + std::vector>& inputs, + std::vector>& outputs, const u32 sample_count, + LightLimiterInfo::StatisticsInternal* statistics) { + constexpr s64 min{std::numeric_limits::min()}; + constexpr s64 max{std::numeric_limits::max()}; + + const auto recip_estimate = [](f64 a) -> f64 { + s32 q, s; + f64 r; + q = (s32)(a * 512.0); /* a in units of 1/512 rounded down */ + r = 1.0 / (((f64)q + 0.5) / 512.0); /* reciprocal r */ + s = (s32)(256.0 * r + 0.5); /* r in units of 1/256 rounded to nearest */ + return ((f64)s / 256.0); + }; + + if (enabled) { + if (statistics && params.statistics_reset_required) { + for (u32 i = 0; i < params.channel_count; i++) { + statistics->channel_compression_gain_min[i] = 1.0f; + statistics->channel_max_sample[i] = 0; + } + } + + for (u32 sample_index = 0; sample_index < sample_count; sample_index++) { + for (u32 channel = 0; channel < params.channel_count; channel++) { + auto sample{(Common::FixedPoint<49, 15>(inputs[channel][sample_index]) / + Common::FixedPoint<49, 15>::one) * + params.input_gain}; + auto abs_sample{sample}; + if (sample < 0.0f) { + abs_sample = -sample; + } + auto coeff{abs_sample > state.samples_average[channel] ? params.attack_coeff + : params.release_coeff}; + state.samples_average[channel] += + ((abs_sample - state.samples_average[channel]) * coeff).to_float(); + + // Reciprocal estimate + auto new_average_sample{Common::FixedPoint<49, 15>( + recip_estimate(state.samples_average[channel].to_double()))}; + if (params.processing_mode != LightLimiterInfo::ProcessingMode::Mode1) { + // Two Newton-Raphson steps + auto temp{2.0 - (state.samples_average[channel] * new_average_sample)}; + new_average_sample = 2.0 - (state.samples_average[channel] * temp); + } + + auto above_threshold{state.samples_average[channel] > params.threshold}; + auto attenuation{above_threshold ? params.threshold * new_average_sample : 1.0f}; + coeff = attenuation < state.compression_gain[channel] ? params.attack_coeff + : params.release_coeff; + state.compression_gain[channel] += + (attenuation - state.compression_gain[channel]) * coeff; + + auto lookahead_sample{ + state.look_ahead_sample_buffers[channel] + [state.look_ahead_sample_offsets[channel]]}; + + state.look_ahead_sample_buffers[channel][state.look_ahead_sample_offsets[channel]] = + sample; + state.look_ahead_sample_offsets[channel] = + (state.look_ahead_sample_offsets[channel] + 1) % params.look_ahead_samples_min; + + outputs[channel][sample_index] = static_cast( + std::clamp((lookahead_sample * state.compression_gain[channel] * + params.output_gain * Common::FixedPoint<49, 15>::one) + .to_long(), + min, max)); + + if (statistics) { + statistics->channel_max_sample[channel] = + std::max(statistics->channel_max_sample[channel], abs_sample.to_float()); + statistics->channel_compression_gain_min[channel] = + std::min(statistics->channel_compression_gain_min[channel], + state.compression_gain[channel].to_float()); + } + } + } + } else { + for (u32 i = 0; i < params.channel_count; i++) { + if (params.inputs[i] != params.outputs[i]) { + std::memcpy(outputs[i].data(), inputs[i].data(), outputs[i].size_bytes()); + } + } + } +} + +void LightLimiterVersion1Command::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, + std::string& string) { + string += fmt::format("LightLimiterVersion1Command\n\tinputs: "); + for (u32 i = 0; i < MaxChannels; i++) { + string += fmt::format("{:02X}, ", inputs[i]); + } + string += "\n\toutputs: "; + for (u32 i = 0; i < MaxChannels; i++) { + string += fmt::format("{:02X}, ", outputs[i]); + } + string += "\n"; +} + +void LightLimiterVersion1Command::Process(const ADSP::CommandListProcessor& processor) { + std::vector> input_buffers(parameter.channel_count); + std::vector> output_buffers(parameter.channel_count); + + for (u32 i = 0; i < parameter.channel_count; i++) { + input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count, + processor.sample_count); + output_buffers[i] = processor.mix_buffers.subspan(outputs[i] * processor.sample_count, + processor.sample_count); + } + + auto state_{reinterpret_cast(state)}; + + if (effect_enabled) { + if (parameter.state == LightLimiterInfo::ParameterState::Updating) { + UpdateLightLimiterEffectParameter(parameter, *state_); + } else if (parameter.state == LightLimiterInfo::ParameterState::Initialized) { + InitializeLightLimiterEffect(parameter, *state_, workbuffer); + } + } + + LightLimiterInfo::StatisticsInternal* statistics{nullptr}; + ApplyLightLimiterEffect(parameter, *state_, effect_enabled, input_buffers, output_buffers, + processor.sample_count, statistics); +} + +bool LightLimiterVersion1Command::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +void LightLimiterVersion2Command::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, + std::string& string) { + string += fmt::format("LightLimiterVersion2Command\n\tinputs: \n"); + for (u32 i = 0; i < MaxChannels; i++) { + string += fmt::format("{:02X}, ", inputs[i]); + } + string += "\n\toutputs: "; + for (u32 i = 0; i < MaxChannels; i++) { + string += fmt::format("{:02X}, ", outputs[i]); + } + string += "\n"; +} + +void LightLimiterVersion2Command::Process(const ADSP::CommandListProcessor& processor) { + std::vector> input_buffers(parameter.channel_count); + std::vector> output_buffers(parameter.channel_count); + + for (u32 i = 0; i < parameter.channel_count; i++) { + input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count, + processor.sample_count); + output_buffers[i] = processor.mix_buffers.subspan(outputs[i] * processor.sample_count, + processor.sample_count); + } + + auto state_{reinterpret_cast(state)}; + + if (effect_enabled) { + if (parameter.state == LightLimiterInfo::ParameterState::Updating) { + UpdateLightLimiterEffectParameter(parameter, *state_); + } else if (parameter.state == LightLimiterInfo::ParameterState::Initialized) { + InitializeLightLimiterEffect(parameter, *state_, workbuffer); + } + } + + auto statistics{reinterpret_cast(result_state)}; + ApplyLightLimiterEffect(parameter, *state_, effect_enabled, input_buffers, output_buffers, + processor.sample_count, statistics); +} + +bool LightLimiterVersion2Command::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/effect/light_limiter.h b/src/audio_core/renderer/command/effect/light_limiter.h new file mode 100644 index 000000000..5d98272c7 --- /dev/null +++ b/src/audio_core/renderer/command/effect/light_limiter.h @@ -0,0 +1,103 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#include "audio_core/renderer/command/icommand.h" +#include "audio_core/renderer/effect/light_limiter.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +namespace ADSP { +class CommandListProcessor; +} + +/** + * AudioRenderer command for limiting volume between a high and low threshold. + * Version 1. + */ +struct LightLimiterVersion1Command : ICommand { + /** + * Print this command's information to a string. + * + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; + + /** + * Process this command. + * + * @param processor - The CommandListProcessor processing this command. + */ + void Process(const ADSP::CommandListProcessor& processor) override; + + /** + * Verify this command's data is valid. + * + * @param processor - The CommandListProcessor processing this command. + * @return True if the command is valid, otherwise false. + */ + bool Verify(const ADSP::CommandListProcessor& processor) override; + + /// Input mix buffer offsets for each channel + std::array inputs; + /// Output mix buffer offsets for each channel + std::array outputs; + /// Input parameters + LightLimiterInfo::ParameterVersion2 parameter; + /// State, updated each call + CpuAddr state; + /// Game-supplied workbuffer (Unused) + CpuAddr workbuffer; + /// Is this effect enabled? + bool effect_enabled; +}; + +/** + * AudioRenderer command for limiting volume between a high and low threshold. + * Version 2 with output statistics. + */ +struct LightLimiterVersion2Command : ICommand { + /** + * Print this command's information to a string. + * + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; + + /** + * Process this command. + * + * @param processor - The CommandListProcessor processing this command. + */ + void Process(const ADSP::CommandListProcessor& processor) override; + + /** + * Verify this command's data is valid. + * + * @param processor - The CommandListProcessor processing this command. + */ + bool Verify(const ADSP::CommandListProcessor& processor) override; + + /// Input mix buffer offsets for each channel + std::array inputs; + /// Output mix buffer offsets for each channel + std::array outputs; + /// Input parameters + LightLimiterInfo::ParameterVersion2 parameter; + /// State, updated each call + CpuAddr state; + /// Game-supplied workbuffer (Unused) + CpuAddr workbuffer; + /// Optional statistics, sent back to the sysmodule + CpuAddr result_state; + /// Is this effect enabled? + bool effect_enabled; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/effect/multi_tap_biquad_filter.cpp b/src/audio_core/renderer/command/effect/multi_tap_biquad_filter.cpp new file mode 100644 index 000000000..b3c3ba4ba --- /dev/null +++ b/src/audio_core/renderer/command/effect/multi_tap_biquad_filter.cpp @@ -0,0 +1,45 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/adsp/command_list_processor.h" +#include "audio_core/renderer/command/effect/biquad_filter.h" +#include "audio_core/renderer/command/effect/multi_tap_biquad_filter.h" + +namespace AudioCore::AudioRenderer { + +void MultiTapBiquadFilterCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, + std::string& string) { + string += fmt::format( + "MultiTapBiquadFilterCommand\n\tinput {:02X}\n\toutput {:02X}\n\tneeds_init ({}, {})\n", + input, output, needs_init[0], needs_init[1]); +} + +void MultiTapBiquadFilterCommand::Process(const ADSP::CommandListProcessor& processor) { + if (filter_tap_count > MaxBiquadFilters) { + LOG_ERROR(Service_Audio, "Too many filter taps! {}", filter_tap_count); + filter_tap_count = MaxBiquadFilters; + } + + auto input_buffer{ + processor.mix_buffers.subspan(input * processor.sample_count, processor.sample_count)}; + auto output_buffer{ + processor.mix_buffers.subspan(output * processor.sample_count, processor.sample_count)}; + + // TODO: Fix this, currently just applies the filter to the input twice, + // and doesn't chain the biquads together at all. + for (u32 i = 0; i < filter_tap_count; i++) { + auto state{reinterpret_cast(states[i])}; + if (needs_init[i]) { + std::memset(state, 0, sizeof(VoiceState::BiquadFilterState)); + } + + ApplyBiquadFilterFloat(output_buffer, input_buffer, biquads[i].b, biquads[i].a, *state, + processor.sample_count); + } +} + +bool MultiTapBiquadFilterCommand::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/effect/multi_tap_biquad_filter.h b/src/audio_core/renderer/command/effect/multi_tap_biquad_filter.h new file mode 100644 index 000000000..99c2c0830 --- /dev/null +++ b/src/audio_core/renderer/command/effect/multi_tap_biquad_filter.h @@ -0,0 +1,59 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#include "audio_core/renderer/command/icommand.h" +#include "audio_core/renderer/voice/voice_info.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +namespace ADSP { +class CommandListProcessor; +} + +/** + * AudioRenderer command for applying multiple biquads at once. + */ +struct MultiTapBiquadFilterCommand : ICommand { + /** + * Print this command's information to a string. + * + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; + + /** + * Process this command. + * + * @param processor - The CommandListProcessor processing this command. + */ + void Process(const ADSP::CommandListProcessor& processor) override; + + /** + * Verify this command's data is valid. + * + * @param processor - The CommandListProcessor processing this command. + * @return True if the command is valid, otherwise false. + */ + bool Verify(const ADSP::CommandListProcessor& processor) override; + + /// Input mix buffer index + s16 input; + /// Output mix buffer index + s16 output; + /// Biquad parameters + std::array biquads; + /// Biquad states, updated each call + std::array states; + /// If each biquad needs initialisation + std::array needs_init; + /// Number of active biquads + u8 filter_tap_count; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/effect/reverb.cpp b/src/audio_core/renderer/command/effect/reverb.cpp new file mode 100644 index 000000000..fe2b1eb43 --- /dev/null +++ b/src/audio_core/renderer/command/effect/reverb.cpp @@ -0,0 +1,440 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include + +#include "audio_core/renderer/adsp/command_list_processor.h" +#include "audio_core/renderer/command/effect/reverb.h" + +namespace AudioCore::AudioRenderer { + +constexpr std::array FdnMaxDelayLineTimes = { + 53.9532470703125f, + 79.19256591796875f, + 116.23876953125f, + 170.61529541015625f, +}; + +constexpr std::array DecayMaxDelayLineTimes = { + 7.0f, + 9.0f, + 13.0f, + 17.0f, +}; + +constexpr std::array, ReverbInfo::NumEarlyModes> + EarlyDelayTimes = { + {{0.000000f, 3.500000f, 2.799988f, 3.899963f, 2.699951f, 13.399963f, 7.899963f, 8.399963f, + 9.899963f, 12.000000f, 12.500000f}, + {0.000000f, 11.799988f, 5.500000f, 11.199951f, 10.399963f, 38.099976f, 22.199951f, + 29.599976f, 21.199951f, 24.799988f, 40.000000f}, + {0.000000f, 41.500000f, 20.500000f, 41.299988f, 0.000000f, 29.500000f, 33.799988f, + 45.199951f, 46.799988f, 0.000000f, 50.000000f}, + {33.099976f, 43.299988f, 22.799988f, 37.899963f, 14.899963f, 35.299988f, 17.899963f, + 34.199951f, 0.000000f, 43.299988f, 50.000000f}, + {0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, + 0.000000f, 0.000000f, 0.000000f}}, +}; + +constexpr std::array, ReverbInfo::NumEarlyModes> + EarlyDelayGains = {{ + {0.699951f, 0.679993f, 0.699951f, 0.679993f, 0.699951f, 0.679993f, 0.699951f, 0.679993f, + 0.679993f, 0.679993f}, + {0.699951f, 0.679993f, 0.699951f, 0.679993f, 0.699951f, 0.679993f, 0.679993f, 0.679993f, + 0.679993f, 0.679993f}, + {0.500000f, 0.699951f, 0.699951f, 0.679993f, 0.500000f, 0.679993f, 0.679993f, 0.699951f, + 0.679993f, 0.000000f}, + {0.929993f, 0.919983f, 0.869995f, 0.859985f, 0.939941f, 0.809998f, 0.799988f, 0.769958f, + 0.759949f, 0.649963f}, + {0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, + 0.000000f, 0.000000f}, + }}; + +constexpr std::array, ReverbInfo::NumLateModes> + FdnDelayTimes = {{ + {53.953247f, 79.192566f, 116.238770f, 130.615295f}, + {53.953247f, 79.192566f, 116.238770f, 170.615295f}, + {5.000000f, 10.000000f, 5.000000f, 10.000000f}, + {47.029968f, 71.000000f, 103.000000f, 170.000000f}, + {53.953247f, 79.192566f, 116.238770f, 170.615295f}, + }}; + +constexpr std::array, ReverbInfo::NumLateModes> + DecayDelayTimes = {{ + {7.000000f, 9.000000f, 13.000000f, 17.000000f}, + {7.000000f, 9.000000f, 13.000000f, 17.000000f}, + {1.000000f, 1.000000f, 1.000000f, 1.000000f}, + {7.000000f, 7.000000f, 13.000000f, 9.000000f}, + {7.000000f, 9.000000f, 13.000000f, 17.000000f}, + }}; + +/** + * Update the ReverbInfo state according to the given parameters. + * + * @param params - Input parameters to update the state. + * @param state - State to be updated. + */ +static void UpdateReverbEffectParameter(const ReverbInfo::ParameterVersion2& params, + ReverbInfo::State& state) { + const auto pow_10 = [](f32 val) -> f32 { + return (val >= 0.0f) ? 1.0f : (val <= -5.3f) ? 0.0f : std::pow(10.0f, val); + }; + const auto cos = [](f32 degrees) -> f32 { + return std::cos(degrees * std::numbers::pi_v / 180.0f); + }; + + static bool unk_initialized{false}; + static Common::FixedPoint<50, 14> unk_value{}; + + const auto sample_rate{Common::FixedPoint<50, 14>::from_base(params.sample_rate)}; + const auto pre_delay_time{Common::FixedPoint<50, 14>::from_base(params.pre_delay)}; + + for (u32 i = 0; i < ReverbInfo::MaxDelayTaps; i++) { + auto early_delay{ + ((pre_delay_time + EarlyDelayTimes[params.early_mode][i]) * sample_rate).to_int()}; + early_delay = std::min(early_delay, state.pre_delay_line.sample_count_max); + state.early_delay_times[i] = early_delay + 1; + state.early_gains[i] = Common::FixedPoint<50, 14>::from_base(params.early_gain) * + EarlyDelayGains[params.early_mode][i]; + } + + if (params.channel_count == 2) { + state.early_gains[4] * 0.5f; + state.early_gains[5] * 0.5f; + } + + auto pre_time{ + ((pre_delay_time + EarlyDelayTimes[params.early_mode][10]) * sample_rate).to_int()}; + state.pre_delay_time = std::min(pre_time, state.pre_delay_line.sample_count_max); + + if (!unk_initialized) { + unk_value = cos((1280.0f / sample_rate).to_float()); + unk_initialized = true; + } + + for (u32 i = 0; i < ReverbInfo::MaxDelayLines; i++) { + const auto fdn_delay{(FdnDelayTimes[params.late_mode][i] * sample_rate).to_int()}; + state.fdn_delay_lines[i].sample_count = + std::min(fdn_delay, state.fdn_delay_lines[i].sample_count_max); + state.fdn_delay_lines[i].buffer_end = + &state.fdn_delay_lines[i].buffer[state.fdn_delay_lines[i].sample_count - 1]; + + const auto decay_delay{(DecayDelayTimes[params.late_mode][i] * sample_rate).to_int()}; + state.decay_delay_lines[i].sample_count = + std::min(decay_delay, state.decay_delay_lines[i].sample_count_max); + state.decay_delay_lines[i].buffer_end = + &state.decay_delay_lines[i].buffer[state.decay_delay_lines[i].sample_count - 1]; + + state.decay_delay_lines[i].decay = + 0.5999755859375f * (1.0f - Common::FixedPoint<50, 14>::from_base(params.colouration)); + + auto a{(Common::FixedPoint<50, 14>(state.fdn_delay_lines[i].sample_count_max) + + state.decay_delay_lines[i].sample_count_max) * + -3}; + auto b{a / (Common::FixedPoint<50, 14>::from_base(params.decay_time) * sample_rate)}; + Common::FixedPoint<50, 14> c{0.0f}; + Common::FixedPoint<50, 14> d{0.0f}; + auto hf_decay_ratio{Common::FixedPoint<50, 14>::from_base(params.high_freq_decay_ratio)}; + + if (hf_decay_ratio > 0.99493408203125f) { + c = 0.0f; + d = 1.0f; + } else { + const auto e{ + pow_10(((((1.0f / hf_decay_ratio) - 1.0f) * 2) / 100 * (b / 10)).to_float())}; + const auto f{1.0f - e}; + const auto g{2.0f - (unk_value * e * 2)}; + const auto h{std::sqrt(std::pow(g.to_float(), 2.0f) - (std::pow(f, 2.0f) * 4))}; + + c = (g - h) / (f * 2.0f); + d = 1.0f - c; + } + + state.hf_decay_prev_gain[i] = c; + state.hf_decay_gain[i] = pow_10((b / 1000).to_float()) * d * 0.70709228515625f; + state.prev_feedback_output[i] = 0; + } +} + +/** + * Initialize a new ReverbInfo state according to the given parameters. + * + * @param params - Input parameters to update the state. + * @param state - State to be updated. + * @param workbuffer - Game-supplied memory for the state. (Unused) + * @param long_size_pre_delay_supported - Use a longer pre-delay time before reverb begins. + */ +static void InitializeReverbEffect(const ReverbInfo::ParameterVersion2& params, + ReverbInfo::State& state, const CpuAddr workbuffer, + const bool long_size_pre_delay_supported) { + state = {}; + + auto delay{Common::FixedPoint<50, 14>::from_base(params.sample_rate)}; + + for (u32 i = 0; i < ReverbInfo::MaxDelayLines; i++) { + auto fdn_delay_time{(FdnMaxDelayLineTimes[i] * delay).to_uint_floor()}; + state.fdn_delay_lines[i].Initialize(fdn_delay_time, 1.0f); + + auto decay_delay_time{(DecayMaxDelayLineTimes[i] * delay).to_uint_floor()}; + state.decay_delay_lines[i].Initialize(decay_delay_time, 0.0f); + } + + const auto pre_delay{long_size_pre_delay_supported ? 350.0f : 150.0f}; + const auto pre_delay_line{(pre_delay * delay).to_uint_floor()}; + state.pre_delay_line.Initialize(pre_delay_line, 1.0f); + + const auto center_delay_time{(5 * delay).to_uint_floor()}; + state.center_delay_line.Initialize(center_delay_time, 1.0f); + + UpdateReverbEffectParameter(params, state); + + for (u32 i = 0; i < ReverbInfo::MaxDelayLines; i++) { + std::ranges::fill(state.fdn_delay_lines[i].buffer, 0); + std::ranges::fill(state.decay_delay_lines[i].buffer, 0); + } + std::ranges::fill(state.center_delay_line.buffer, 0); + std::ranges::fill(state.pre_delay_line.buffer, 0); +} + +/** + * Pass-through the effect, copying input to output directly, with no reverb applied. + * + * @param inputs - Array of input mix buffers to copy. + * @param outputs - Array of output mix buffers to receive copy. + * @param channel_count - Number of channels in inputs and outputs. + * @param sample_count - Number of samples within each channel. + */ +static void ApplyReverbEffectBypass(std::span> inputs, + std::span> outputs, const u32 channel_count, + const u32 sample_count) { + for (u32 i = 0; i < channel_count; i++) { + if (inputs[i].data() != outputs[i].data()) { + std::memcpy(outputs[i].data(), inputs[i].data(), outputs[i].size_bytes()); + } + } +} + +/** + * Tick the delay lines, reading and returning their current output, and writing a new decaying + * sample (mix). + * + * @param decay - The decay line. + * @param fdn - Feedback delay network. + * @param mix - The new calculated sample to be written and decayed. + * @return The next delayed and decayed sample. + */ +static Common::FixedPoint<50, 14> Axfx2AllPassTick(ReverbInfo::ReverbDelayLine& decay, + ReverbInfo::ReverbDelayLine& fdn, + const Common::FixedPoint<50, 14> mix) { + const auto val{decay.Read()}; + const auto mixed{mix - (val * decay.decay)}; + const auto out{decay.Tick(mixed) + (mixed * decay.decay)}; + + fdn.Tick(out); + return out; +} + +/** + * Impl. Apply a Reverb according to the current state, on the input mix buffers, + * saving the results to the output mix buffers. + * + * @tparam NumChannels - Number of channels to process. 1-6. + Inputs/outputs should have this many buffers. + * @param params - Input parameters to update the state. + * @param state - State to use, must be initialized (see InitializeReverbEffect). + * @param inputs - Input mix buffers to perform the reverb on. + * @param outputs - Output mix buffers to receive the reverbed samples. + * @param sample_count - Number of samples to process. + */ +template +static void ApplyReverbEffect(const ReverbInfo::ParameterVersion2& params, ReverbInfo::State& state, + std::vector>& inputs, + std::vector>& outputs, const u32 sample_count) { + constexpr std::array OutTapIndexes1Ch{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }; + constexpr std::array OutTapIndexes2Ch{ + 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, + }; + constexpr std::array OutTapIndexes4Ch{ + 0, 0, 1, 1, 0, 1, 2, 2, 3, 3, + }; + constexpr std::array OutTapIndexes6Ch{ + 0, 0, 1, 1, 2, 2, 4, 4, 5, 5, + }; + + std::span tap_indexes{}; + if constexpr (NumChannels == 1) { + tap_indexes = OutTapIndexes1Ch; + } else if constexpr (NumChannels == 2) { + tap_indexes = OutTapIndexes2Ch; + } else if constexpr (NumChannels == 4) { + tap_indexes = OutTapIndexes4Ch; + } else if constexpr (NumChannels == 6) { + tap_indexes = OutTapIndexes6Ch; + } + + for (u32 sample_index = 0; sample_index < sample_count; sample_index++) { + std::array, NumChannels> output_samples{}; + + for (u32 early_tap = 0; early_tap < ReverbInfo::MaxDelayTaps; early_tap++) { + const auto sample{state.pre_delay_line.TapOut(state.early_delay_times[early_tap]) * + state.early_gains[early_tap]}; + output_samples[tap_indexes[early_tap]] += sample; + if constexpr (NumChannels == 6) { + output_samples[static_cast(Channels::LFE)] += sample; + } + } + + if constexpr (NumChannels == 6) { + output_samples[static_cast(Channels::LFE)] *= 0.2f; + } + + Common::FixedPoint<50, 14> input_sample{}; + for (u32 channel = 0; channel < NumChannels; channel++) { + input_sample += inputs[channel][sample_index]; + } + + input_sample *= 64; + input_sample *= Common::FixedPoint<50, 14>::from_base(params.base_gain); + state.pre_delay_line.Write(input_sample); + + for (u32 i = 0; i < ReverbInfo::MaxDelayLines; i++) { + state.prev_feedback_output[i] = + state.prev_feedback_output[i] * state.hf_decay_prev_gain[i] + + state.fdn_delay_lines[i].Read() * state.hf_decay_gain[i]; + } + + Common::FixedPoint<50, 14> pre_delay_sample{ + state.pre_delay_line.Read() * Common::FixedPoint<50, 14>::from_base(params.late_gain)}; + + std::array, ReverbInfo::MaxDelayLines> mix_matrix{ + state.prev_feedback_output[2] + state.prev_feedback_output[1] + pre_delay_sample, + -state.prev_feedback_output[0] - state.prev_feedback_output[3] + pre_delay_sample, + state.prev_feedback_output[0] - state.prev_feedback_output[3] + pre_delay_sample, + state.prev_feedback_output[1] - state.prev_feedback_output[2] + pre_delay_sample, + }; + + std::array, ReverbInfo::MaxDelayLines> allpass_samples{}; + for (u32 i = 0; i < ReverbInfo::MaxDelayLines; i++) { + allpass_samples[i] = Axfx2AllPassTick(state.decay_delay_lines[i], + state.fdn_delay_lines[i], mix_matrix[i]); + } + + const auto dry_gain{Common::FixedPoint<50, 14>::from_base(params.dry_gain)}; + const auto wet_gain{Common::FixedPoint<50, 14>::from_base(params.wet_gain)}; + + if constexpr (NumChannels == 6) { + const std::array, MaxChannels> allpass_outputs{ + allpass_samples[0], allpass_samples[1], allpass_samples[2] - allpass_samples[3], + allpass_samples[3], allpass_samples[2], allpass_samples[3], + }; + + for (u32 channel = 0; channel < NumChannels; channel++) { + auto in_sample{inputs[channel][sample_index] * dry_gain}; + + Common::FixedPoint<50, 14> allpass{}; + if (channel == static_cast(Channels::Center)) { + allpass = state.center_delay_line.Tick(allpass_outputs[channel] * 0.5f); + } else { + allpass = allpass_outputs[channel]; + } + + auto out_sample{((output_samples[channel] + allpass) * wet_gain) / 64}; + outputs[channel][sample_index] = (in_sample + out_sample).to_int(); + } + } else { + for (u32 channel = 0; channel < NumChannels; channel++) { + auto in_sample{inputs[channel][sample_index] * dry_gain}; + auto out_sample{((output_samples[channel] + allpass_samples[channel]) * wet_gain) / + 64}; + outputs[channel][sample_index] = (in_sample + out_sample).to_int(); + } + } + } +} + +/** + * Apply a Reverb if enabled, according to the current state, on the input mix buffers, + * saving the results to the output mix buffers. + * + * @param params - Input parameters to use. + * @param state - State to use, must be initialized (see InitializeReverbEffect). + * @param enabled - If enabled, delay will be applied, otherwise input is copied to output. + * @param inputs - Input mix buffers to performan the reverb on. + * @param outputs - Output mix buffers to receive the reverbed samples. + * @param sample_count - Number of samples to process. + */ +static void ApplyReverbEffect(const ReverbInfo::ParameterVersion2& params, ReverbInfo::State& state, + const bool enabled, std::vector>& inputs, + std::vector>& outputs, const u32 sample_count) { + if (enabled) { + switch (params.channel_count) { + case 0: + return; + case 1: + ApplyReverbEffect<1>(params, state, inputs, outputs, sample_count); + break; + case 2: + ApplyReverbEffect<2>(params, state, inputs, outputs, sample_count); + break; + case 4: + ApplyReverbEffect<4>(params, state, inputs, outputs, sample_count); + break; + case 6: + ApplyReverbEffect<6>(params, state, inputs, outputs, sample_count); + break; + default: + ApplyReverbEffectBypass(inputs, outputs, params.channel_count, sample_count); + break; + } + } else { + ApplyReverbEffectBypass(inputs, outputs, params.channel_count, sample_count); + } +} + +void ReverbCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, + std::string& string) { + string += fmt::format( + "ReverbCommand\n\tenabled {} long_size_pre_delay_supported {}\n\tinputs: ", effect_enabled, + long_size_pre_delay_supported); + for (u32 i = 0; i < MaxChannels; i++) { + string += fmt::format("{:02X}, ", inputs[i]); + } + string += "\n\toutputs: "; + for (u32 i = 0; i < MaxChannels; i++) { + string += fmt::format("{:02X}, ", outputs[i]); + } + string += "\n"; +} + +void ReverbCommand::Process(const ADSP::CommandListProcessor& processor) { + std::vector> input_buffers(parameter.channel_count); + std::vector> output_buffers(parameter.channel_count); + + for (u32 i = 0; i < parameter.channel_count; i++) { + input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count, + processor.sample_count); + output_buffers[i] = processor.mix_buffers.subspan(outputs[i] * processor.sample_count, + processor.sample_count); + } + + auto state_{reinterpret_cast(state)}; + + if (effect_enabled) { + if (parameter.state == ReverbInfo::ParameterState::Updating) { + UpdateReverbEffectParameter(parameter, *state_); + } else if (parameter.state == ReverbInfo::ParameterState::Initialized) { + InitializeReverbEffect(parameter, *state_, workbuffer, long_size_pre_delay_supported); + } + } + ApplyReverbEffect(parameter, *state_, effect_enabled, input_buffers, output_buffers, + processor.sample_count); +} + +bool ReverbCommand::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/effect/reverb.h b/src/audio_core/renderer/command/effect/reverb.h new file mode 100644 index 000000000..328756150 --- /dev/null +++ b/src/audio_core/renderer/command/effect/reverb.h @@ -0,0 +1,62 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#include "audio_core/renderer/command/icommand.h" +#include "audio_core/renderer/effect/reverb.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +namespace ADSP { +class CommandListProcessor; +} + +/** + * AudioRenderer command for a Reverb effect. Apply a reverb to inputs mix buffer, outputs receives + * the results. + */ +struct ReverbCommand : ICommand { + /** + * Print this command's information to a string. + * + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; + + /** + * Process this command. + * + * @param processor - The CommandListProcessor processing this command. + */ + void Process(const ADSP::CommandListProcessor& processor) override; + + /** + * Verify this command's data is valid. + * + * @param processor - The CommandListProcessor processing this command. + * @return True if the command is valid, otherwise false. + */ + bool Verify(const ADSP::CommandListProcessor& processor) override; + + /// Input mix buffer offsets for each channel + std::array inputs; + /// Output mix buffer offsets for each channel + std::array outputs; + /// Input parameters + ReverbInfo::ParameterVersion2 parameter; + /// State, updated each call + CpuAddr state; + /// Game-supplied workbuffer (Unused) + CpuAddr workbuffer; + /// Is this effect enabled? + bool effect_enabled; + /// Is a longer pre-delay time supported? + bool long_size_pre_delay_supported; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/icommand.h b/src/audio_core/renderer/command/icommand.h new file mode 100644 index 000000000..f2dd41254 --- /dev/null +++ b/src/audio_core/renderer/command/icommand.h @@ -0,0 +1,93 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "audio_core/common/common.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +namespace ADSP { +class CommandListProcessor; +} + +enum class CommandId : u8 { + /* 0x00 */ Invalid, + /* 0x01 */ DataSourcePcmInt16Version1, + /* 0x02 */ DataSourcePcmInt16Version2, + /* 0x03 */ DataSourcePcmFloatVersion1, + /* 0x04 */ DataSourcePcmFloatVersion2, + /* 0x05 */ DataSourceAdpcmVersion1, + /* 0x06 */ DataSourceAdpcmVersion2, + /* 0x07 */ Volume, + /* 0x08 */ VolumeRamp, + /* 0x09 */ BiquadFilter, + /* 0x0A */ Mix, + /* 0x0B */ MixRamp, + /* 0x0C */ MixRampGrouped, + /* 0x0D */ DepopPrepare, + /* 0x0E */ DepopForMixBuffers, + /* 0x0F */ Delay, + /* 0x10 */ Upsample, + /* 0x11 */ DownMix6chTo2ch, + /* 0x12 */ Aux, + /* 0x13 */ DeviceSink, + /* 0x14 */ CircularBufferSink, + /* 0x15 */ Reverb, + /* 0x16 */ I3dl2Reverb, + /* 0x17 */ Performance, + /* 0x18 */ ClearMixBuffer, + /* 0x19 */ CopyMixBuffer, + /* 0x1A */ LightLimiterVersion1, + /* 0x1B */ LightLimiterVersion2, + /* 0x1C */ MultiTapBiquadFilter, + /* 0x1D */ Capture, + /* 0x1E */ Compressor, +}; + +constexpr u32 CommandMagic{0xCAFEBABE}; + +/** + * A command, generated by the host, and processed by the ADSP's AudioRenderer. + */ +struct ICommand { + virtual ~ICommand() = default; + + /** + * Print this command's information to a string. + * + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + virtual void Dump(const ADSP::CommandListProcessor& processor, std::string& string) = 0; + + /** + * Process this command. + * + * @param processor - The CommandListProcessor processing this command. + */ + virtual void Process(const ADSP::CommandListProcessor& processor) = 0; + + /** + * Verify this command's data is valid. + * + * @param processor - The CommandListProcessor processing this command. + * @return True if the command is valid, otherwise false. + */ + virtual bool Verify(const ADSP::CommandListProcessor& processor) = 0; + + /// Command magic 0xCAFEBABE + u32 magic{}; + /// Command enabled + bool enabled{}; + /// Type of this command (see CommandId) + CommandId type{}; + /// Size of this command + s16 size{}; + /// Estimated processing time for this command + u32 estimated_process_time{}; + /// Node id of the voice or mix this command was generated from + u32 node_id{}; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/mix/clear_mix.cpp b/src/audio_core/renderer/command/mix/clear_mix.cpp new file mode 100644 index 000000000..4f649d6a8 --- /dev/null +++ b/src/audio_core/renderer/command/mix/clear_mix.cpp @@ -0,0 +1,24 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include + +#include "audio_core/renderer/adsp/command_list_processor.h" +#include "audio_core/renderer/command/mix/clear_mix.h" + +namespace AudioCore::AudioRenderer { + +void ClearMixBufferCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, + std::string& string) { + string += fmt::format("ClearMixBufferCommand\n"); +} + +void ClearMixBufferCommand::Process(const ADSP::CommandListProcessor& processor) { + memset(processor.mix_buffers.data(), 0, processor.mix_buffers.size_bytes()); +} + +bool ClearMixBufferCommand::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/mix/clear_mix.h b/src/audio_core/renderer/command/mix/clear_mix.h new file mode 100644 index 000000000..956ec0b65 --- /dev/null +++ b/src/audio_core/renderer/command/mix/clear_mix.h @@ -0,0 +1,45 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "audio_core/renderer/command/icommand.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +namespace ADSP { +class CommandListProcessor; +} + +/** + * AudioRenderer command for a clearing the mix buffers. + * Used at the start of each command list. + */ +struct ClearMixBufferCommand : ICommand { + /** + * Print this command's information to a string. + * + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; + + /** + * Process this command. + * + * @param processor - The CommandListProcessor processing this command. + */ + void Process(const ADSP::CommandListProcessor& processor) override; + + /** + * Verify this command's data is valid. + * + * @param processor - The CommandListProcessor processing this command. + * @return True if the command is valid, otherwise false. + */ + bool Verify(const ADSP::CommandListProcessor& processor) override; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/mix/copy_mix.cpp b/src/audio_core/renderer/command/mix/copy_mix.cpp new file mode 100644 index 000000000..1d49f1644 --- /dev/null +++ b/src/audio_core/renderer/command/mix/copy_mix.cpp @@ -0,0 +1,27 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/adsp/command_list_processor.h" +#include "audio_core/renderer/command/mix/copy_mix.h" + +namespace AudioCore::AudioRenderer { + +void CopyMixBufferCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, + std::string& string) { + string += fmt::format("CopyMixBufferCommand\n\tinput {:02X} output {:02X}\n", input_index, + output_index); +} + +void CopyMixBufferCommand::Process(const ADSP::CommandListProcessor& processor) { + auto output{processor.mix_buffers.subspan(output_index * processor.sample_count, + processor.sample_count)}; + auto input{processor.mix_buffers.subspan(input_index * processor.sample_count, + processor.sample_count)}; + std::memcpy(output.data(), input.data(), processor.sample_count * sizeof(s32)); +} + +bool CopyMixBufferCommand::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/mix/copy_mix.h b/src/audio_core/renderer/command/mix/copy_mix.h new file mode 100644 index 000000000..a59007fb6 --- /dev/null +++ b/src/audio_core/renderer/command/mix/copy_mix.h @@ -0,0 +1,49 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "audio_core/renderer/command/icommand.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +namespace ADSP { +class CommandListProcessor; +} + +/** + * AudioRenderer command for a copying a mix buffer from input to output. + */ +struct CopyMixBufferCommand : ICommand { + /** + * Print this command's information to a string. + * + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; + + /** + * Process this command. + * + * @param processor - The CommandListProcessor processing this command. + */ + void Process(const ADSP::CommandListProcessor& processor) override; + + /** + * Verify this command's data is valid. + * + * @param processor - The CommandListProcessor processing this command. + * @return True if the command is valid, otherwise false. + */ + bool Verify(const ADSP::CommandListProcessor& processor) override; + + /// Input mix buffer index + s16 input_index; + /// Output mix buffer index + s16 output_index; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/mix/depop_for_mix_buffers.cpp b/src/audio_core/renderer/command/mix/depop_for_mix_buffers.cpp new file mode 100644 index 000000000..c2bc10061 --- /dev/null +++ b/src/audio_core/renderer/command/mix/depop_for_mix_buffers.cpp @@ -0,0 +1,64 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/common/common.h" +#include "audio_core/renderer/adsp/command_list_processor.h" +#include "audio_core/renderer/command/mix/depop_for_mix_buffers.h" + +namespace AudioCore::AudioRenderer { +/** + * Apply depopping. Add the depopped sample to each incoming new sample, decaying it each time + * according to decay. + * + * @param output - Output buffer to be depopped. + * @param depop_sample - Depopped sample to apply to output samples. + * @param decay_ - Amount to decay the depopped sample for every output sample. + * @param sample_count - Samples to process. + * @return Final decayed depop sample. + */ +static s32 ApplyDepopMix(std::span output, const s32 depop_sample, + Common::FixedPoint<49, 15>& decay_, const u32 sample_count) { + auto sample{std::abs(depop_sample)}; + auto decay{decay_.to_raw()}; + + if (depop_sample <= 0) { + for (u32 i = 0; i < sample_count; i++) { + sample = static_cast((static_cast(sample) * decay) >> 15); + output[i] -= sample; + } + return -sample; + } else { + for (u32 i = 0; i < sample_count; i++) { + sample = static_cast((static_cast(sample) * decay) >> 15); + output[i] += sample; + } + return sample; + } +} + +void DepopForMixBuffersCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, + std::string& string) { + string += fmt::format("DepopForMixBuffersCommand\n\tinput {:02X} count {} decay {}\n", input, + count, decay.to_float()); +} + +void DepopForMixBuffersCommand::Process(const ADSP::CommandListProcessor& processor) { + auto end_index{std::min(processor.buffer_count, input + count)}; + std::span depop_buff{reinterpret_cast(depop_buffer), end_index}; + + for (u32 index = input; index < end_index; index++) { + const auto depop_sample{depop_buff[index]}; + if (depop_sample != 0) { + auto input_buffer{processor.mix_buffers.subspan(index * processor.sample_count, + processor.sample_count)}; + depop_buff[index] = + ApplyDepopMix(input_buffer, depop_sample, decay, processor.sample_count); + } + } +} + +bool DepopForMixBuffersCommand::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/mix/depop_for_mix_buffers.h b/src/audio_core/renderer/command/mix/depop_for_mix_buffers.h new file mode 100644 index 000000000..e7268ff27 --- /dev/null +++ b/src/audio_core/renderer/command/mix/depop_for_mix_buffers.h @@ -0,0 +1,55 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "audio_core/renderer/command/icommand.h" +#include "common/common_types.h" +#include "common/fixed_point.h" + +namespace AudioCore::AudioRenderer { +namespace ADSP { +class CommandListProcessor; +} + +/** + * AudioRenderer command for depopping a mix buffer. + * Adds a cumulation of previous samples to the current mix buffer with a decay. + */ +struct DepopForMixBuffersCommand : ICommand { + /** + * Print this command's information to a string. + * + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; + + /** + * Process this command. + * + * @param processor - The CommandListProcessor processing this command. + */ + void Process(const ADSP::CommandListProcessor& processor) override; + + /** + * Verify this command's data is valid. + * + * @param processor - The CommandListProcessor processing this command. + * @return True if the command is valid, otherwise false. + */ + bool Verify(const ADSP::CommandListProcessor& processor) override; + + /// Starting input mix buffer index + u32 input; + /// Number of mix buffers to depop + u32 count; + /// Amount to decay the depop sample for each new sample + Common::FixedPoint<49, 15> decay; + /// Address of the depop buffer, holding the last sample for every mix buffer + CpuAddr depop_buffer; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/mix/depop_prepare.cpp b/src/audio_core/renderer/command/mix/depop_prepare.cpp new file mode 100644 index 000000000..2ee076ef6 --- /dev/null +++ b/src/audio_core/renderer/command/mix/depop_prepare.cpp @@ -0,0 +1,36 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/adsp/command_list_processor.h" +#include "audio_core/renderer/command/mix/depop_prepare.h" +#include "audio_core/renderer/voice/voice_state.h" +#include "common/fixed_point.h" + +namespace AudioCore::AudioRenderer { + +void DepopPrepareCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, + std::string& string) { + string += fmt::format("DepopPrepareCommand\n\tinputs: "); + for (u32 i = 0; i < buffer_count; i++) { + string += fmt::format("{:02X}, ", inputs[i]); + } + string += "\n"; +} + +void DepopPrepareCommand::Process(const ADSP::CommandListProcessor& processor) { + auto samples{reinterpret_cast(previous_samples)}; + auto buffer{std::span(reinterpret_cast(depop_buffer), buffer_count)}; + + for (u32 i = 0; i < buffer_count; i++) { + if (samples[i]) { + buffer[inputs[i]] += samples[i]; + samples[i] = 0; + } + } +} + +bool DepopPrepareCommand::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/mix/depop_prepare.h b/src/audio_core/renderer/command/mix/depop_prepare.h new file mode 100644 index 000000000..a5465da9a --- /dev/null +++ b/src/audio_core/renderer/command/mix/depop_prepare.h @@ -0,0 +1,54 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "audio_core/renderer/command/icommand.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +namespace ADSP { +class CommandListProcessor; +} + +/** + * AudioRenderer command for preparing depop. + * Adds the previusly output last samples to the depop buffer. + */ +struct DepopPrepareCommand : ICommand { + /** + * Print this command's information to a string. + * + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; + + /** + * Process this command. + * + * @param processor - The CommandListProcessor processing this command. + */ + void Process(const ADSP::CommandListProcessor& processor) override; + + /** + * Verify this command's data is valid. + * + * @param processor - The CommandListProcessor processing this command. + * @return True if the command is valid, otherwise false. + */ + bool Verify(const ADSP::CommandListProcessor& processor) override; + + /// Depop buffer offset for each mix buffer + std::array inputs; + /// Pointer to the previous mix buffer samples + CpuAddr previous_samples; + /// Number of mix buffers to use + u32 buffer_count; + /// Pointer to the current depop values + CpuAddr depop_buffer; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/mix/mix.cpp b/src/audio_core/renderer/command/mix/mix.cpp new file mode 100644 index 000000000..8ecf9b05a --- /dev/null +++ b/src/audio_core/renderer/command/mix/mix.cpp @@ -0,0 +1,70 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include + +#include "audio_core/renderer/adsp/command_list_processor.h" +#include "audio_core/renderer/command/mix/mix.h" +#include "common/fixed_point.h" + +namespace AudioCore::AudioRenderer { +/** + * Mix input mix buffer into output mix buffer, with volume applied to the input. + * + * @tparam Q - Number of bits for fixed point operations. + * @param output - Output mix buffer. + * @param input - Input mix buffer. + * @param volume - Volume applied to the input. + * @param sample_count - Number of samples to process. + */ +template +static void ApplyMix(std::span output, std::span input, const f32 volume_, + const u32 sample_count) { + const Common::FixedPoint<64 - Q, Q> volume{volume_}; + for (u32 i = 0; i < sample_count; i++) { + output[i] = (output[i] + input[i] * volume).to_int(); + } +} + +void MixCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, + std::string& string) { + string += fmt::format("MixCommand"); + string += fmt::format("\n\tinput {:02X}", input_index); + string += fmt::format("\n\toutput {:02X}", output_index); + string += fmt::format("\n\tvolume {:.8f}", volume); + string += "\n"; +} + +void MixCommand::Process(const ADSP::CommandListProcessor& processor) { + auto output{processor.mix_buffers.subspan(output_index * processor.sample_count, + processor.sample_count)}; + auto input{processor.mix_buffers.subspan(input_index * processor.sample_count, + processor.sample_count)}; + + // If volume is 0, nothing will be added to the output, so just skip. + if (volume == 0.0f) { + return; + } + + switch (precision) { + case 15: + ApplyMix<15>(output, input, volume, processor.sample_count); + break; + + case 23: + ApplyMix<23>(output, input, volume, processor.sample_count); + break; + + default: + LOG_ERROR(Service_Audio, "Invalid precision {}", precision); + break; + } +} + +bool MixCommand::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/mix/mix.h b/src/audio_core/renderer/command/mix/mix.h new file mode 100644 index 000000000..0201cf171 --- /dev/null +++ b/src/audio_core/renderer/command/mix/mix.h @@ -0,0 +1,54 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "audio_core/renderer/command/icommand.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +namespace ADSP { +class CommandListProcessor; +} + +/** + * AudioRenderer command for mixing an input mix buffer to an output mix buffer, with a volume + * applied to the input. + */ +struct MixCommand : ICommand { + /** + * Print this command's information to a string. + * + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; + + /** + * Process this command. + * + * @param processor - The CommandListProcessor processing this command. + */ + void Process(const ADSP::CommandListProcessor& processor) override; + + /** + * Verify this command's data is valid. + * + * @param processor - The CommandListProcessor processing this command. + * @return True if the command is valid, otherwise false. + */ + bool Verify(const ADSP::CommandListProcessor& processor) override; + + /// Fixed point precision + u8 precision; + /// Input mix buffer index + s16 input_index; + /// Output mix buffer index + s16 output_index; + /// Mix volume applied to the input + f32 volume; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/mix/mix_ramp.cpp b/src/audio_core/renderer/command/mix/mix_ramp.cpp new file mode 100644 index 000000000..ffdafa1c8 --- /dev/null +++ b/src/audio_core/renderer/command/mix/mix_ramp.cpp @@ -0,0 +1,94 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/adsp/command_list_processor.h" +#include "audio_core/renderer/command/mix/mix_ramp.h" +#include "common/fixed_point.h" +#include "common/logging/log.h" + +namespace AudioCore::AudioRenderer { +/** + * Mix input mix buffer into output mix buffer, with volume applied to the input. + * + * @tparam Q - Number of bits for fixed point operations. + * @param output - Output mix buffer. + * @param input - Input mix buffer. + * @param volume - Volume applied to the input. + * @param ramp - Ramp applied to volume every sample. + * @param sample_count - Number of samples to process. + * @return The final gained input sample, used for depopping. + */ +template +s32 ApplyMixRamp(std::span output, std::span input, const f32 volume_, + const f32 ramp_, const u32 sample_count) { + Common::FixedPoint<64 - Q, Q> volume{volume_}; + Common::FixedPoint<64 - Q, Q> sample{0}; + + if (ramp_ == 0.0f) { + for (u32 i = 0; i < sample_count; i++) { + sample = input[i] * volume; + output[i] = (output[i] + sample).to_int(); + } + } else { + Common::FixedPoint<64 - Q, Q> ramp{ramp_}; + for (u32 i = 0; i < sample_count; i++) { + sample = input[i] * volume; + output[i] = (output[i] + sample).to_int(); + volume += ramp; + } + } + return sample.to_int(); +} + +template s32 ApplyMixRamp<15>(std::span, std::span, const f32, const f32, + const u32); +template s32 ApplyMixRamp<23>(std::span, std::span, const f32, const f32, + const u32); + +void MixRampCommand::Dump(const ADSP::CommandListProcessor& processor, std::string& string) { + const auto ramp{(volume - prev_volume) / static_cast(processor.sample_count)}; + string += fmt::format("MixRampCommand"); + string += fmt::format("\n\tinput {:02X}", input_index); + string += fmt::format("\n\toutput {:02X}", output_index); + string += fmt::format("\n\tvolume {:.8f}", volume); + string += fmt::format("\n\tprev_volume {:.8f}", prev_volume); + string += fmt::format("\n\tramp {:.8f}", ramp); + string += "\n"; +} + +void MixRampCommand::Process(const ADSP::CommandListProcessor& processor) { + auto output{processor.mix_buffers.subspan(output_index * processor.sample_count, + processor.sample_count)}; + auto input{processor.mix_buffers.subspan(input_index * processor.sample_count, + processor.sample_count)}; + const auto ramp{(volume - prev_volume) / static_cast(processor.sample_count)}; + auto prev_sample_ptr{reinterpret_cast(previous_sample)}; + + // If previous volume and ramp are both 0, nothing will be added to the output, so just skip. + if (prev_volume == 0.0f && ramp == 0.0f) { + *prev_sample_ptr = 0; + return; + } + + switch (precision) { + case 15: + *prev_sample_ptr = + ApplyMixRamp<15>(output, input, prev_volume, ramp, processor.sample_count); + break; + + case 23: + *prev_sample_ptr = + ApplyMixRamp<23>(output, input, prev_volume, ramp, processor.sample_count); + break; + + default: + LOG_ERROR(Service_Audio, "Invalid precision {}", precision); + break; + } +} + +bool MixRampCommand::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/mix/mix_ramp.h b/src/audio_core/renderer/command/mix/mix_ramp.h new file mode 100644 index 000000000..770f57e80 --- /dev/null +++ b/src/audio_core/renderer/command/mix/mix_ramp.h @@ -0,0 +1,73 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#include "audio_core/renderer/command/icommand.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +namespace ADSP { +class CommandListProcessor; +} + +/** + * AudioRenderer command for mixing an input mix buffer to an output mix buffer, with a volume + * applied to the input, and volume ramping to smooth out the transition. + */ +struct MixRampCommand : ICommand { + /** + * Print this command's information to a string. + * + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; + + /** + * Process this command. + * + * @param processor - The CommandListProcessor processing this command. + */ + void Process(const ADSP::CommandListProcessor& processor) override; + + /** + * Verify this command's data is valid. + * + * @param processor - The CommandListProcessor processing this command. + * @return True if the command is valid, otherwise false. + */ + bool Verify(const ADSP::CommandListProcessor& processor) override; + + /// Fixed point precision + u8 precision; + /// Input mix buffer index + s16 input_index; + /// Output mix buffer index + s16 output_index; + /// Previous mix volume + f32 prev_volume; + /// Current mix volume + f32 volume; + /// Pointer to the previous sample buffer, used for depopping + CpuAddr previous_sample; +}; + +/** + * Mix input mix buffer into output mix buffer, with volume applied to the input. + * @tparam Q - Number of bits for fixed point operations. + * @param output - Output mix buffer. + * @param input - Input mix buffer. + * @param volume - Volume applied to the input. + * @param ramp - Ramp applied to volume every sample. + * @param sample_count - Number of samples to process. + * @return The final gained input sample, used for depopping. + */ +template +s32 ApplyMixRamp(std::span output, std::span input, const f32 volume_, + const f32 ramp_, const u32 sample_count); + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/mix/mix_ramp_grouped.cpp b/src/audio_core/renderer/command/mix/mix_ramp_grouped.cpp new file mode 100644 index 000000000..43dbef9fc --- /dev/null +++ b/src/audio_core/renderer/command/mix/mix_ramp_grouped.cpp @@ -0,0 +1,65 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/adsp/command_list_processor.h" +#include "audio_core/renderer/command/mix/mix_ramp.h" +#include "audio_core/renderer/command/mix/mix_ramp_grouped.h" + +namespace AudioCore::AudioRenderer { + +void MixRampGroupedCommand::Dump(const ADSP::CommandListProcessor& processor, std::string& string) { + string += "MixRampGroupedCommand"; + for (u32 i = 0; i < buffer_count; i++) { + string += fmt::format("\n\t{}", i); + const auto ramp{(volumes[i] - prev_volumes[i]) / static_cast(processor.sample_count)}; + string += fmt::format("\n\t\tinput {:02X}", inputs[i]); + string += fmt::format("\n\t\toutput {:02X}", outputs[i]); + string += fmt::format("\n\t\tvolume {:.8f}", volumes[i]); + string += fmt::format("\n\t\tprev_volume {:.8f}", prev_volumes[i]); + string += fmt::format("\n\t\tramp {:.8f}", ramp); + string += "\n"; + } +} + +void MixRampGroupedCommand::Process(const ADSP::CommandListProcessor& processor) { + std::span prev_samples = {reinterpret_cast(previous_samples), MaxMixBuffers}; + + for (u32 i = 0; i < buffer_count; i++) { + auto last_sample{0}; + if (prev_volumes[i] != 0.0f || volumes[i] != 0.0f) { + const auto output{processor.mix_buffers.subspan(outputs[i] * processor.sample_count, + processor.sample_count)}; + const auto input{processor.mix_buffers.subspan(inputs[i] * processor.sample_count, + processor.sample_count)}; + const auto ramp{(volumes[i] - prev_volumes[i]) / + static_cast(processor.sample_count)}; + + if (prev_volumes[i] == 0.0f && ramp == 0.0f) { + prev_samples[i] = 0; + continue; + } + + switch (precision) { + case 15: + last_sample = + ApplyMixRamp<15>(output, input, prev_volumes[i], ramp, processor.sample_count); + break; + case 23: + last_sample = + ApplyMixRamp<23>(output, input, prev_volumes[i], ramp, processor.sample_count); + break; + default: + LOG_ERROR(Service_Audio, "Invalid precision {}", precision); + break; + } + } + + prev_samples[i] = last_sample; + } +} + +bool MixRampGroupedCommand::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/mix/mix_ramp_grouped.h b/src/audio_core/renderer/command/mix/mix_ramp_grouped.h new file mode 100644 index 000000000..027276e5a --- /dev/null +++ b/src/audio_core/renderer/command/mix/mix_ramp_grouped.h @@ -0,0 +1,61 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#include "audio_core/renderer/command/icommand.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +namespace ADSP { +class CommandListProcessor; +} + +/** + * AudioRenderer command for mixing multiple input mix buffers to multiple output mix buffers, with + * a volume applied to the input, and volume ramping to smooth out the transition. + */ +struct MixRampGroupedCommand : ICommand { + /** + * Print this command's information to a string. + * + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; + + /** + * Process this command. + * + * @param processor - The CommandListProcessor processing this command. + */ + void Process(const ADSP::CommandListProcessor& processor) override; + + /** + * Verify this command's data is valid. + * + * @param processor - The CommandListProcessor processing this command. + * @return True if the command is valid, otherwise false. + */ + bool Verify(const ADSP::CommandListProcessor& processor) override; + + /// Fixed point precision + u8 precision; + /// Number of mix buffers to mix + u32 buffer_count; + /// Input mix buffer indexes for each mix buffer + std::array inputs; + /// Output mix buffer indexes for each mix buffer + std::array outputs; + /// Previous mix vloumes for each mix buffer + std::array prev_volumes; + /// Current mix vloumes for each mix buffer + std::array volumes; + /// Pointer to the previous sample buffer, used for depop + CpuAddr previous_samples; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/mix/volume.cpp b/src/audio_core/renderer/command/mix/volume.cpp new file mode 100644 index 000000000..b045fb062 --- /dev/null +++ b/src/audio_core/renderer/command/mix/volume.cpp @@ -0,0 +1,72 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/adsp/command_list_processor.h" +#include "audio_core/renderer/command/mix/volume.h" +#include "common/fixed_point.h" +#include "common/logging/log.h" + +namespace AudioCore::AudioRenderer { +/** + * Apply volume to the input mix buffer, saving to the output buffer. + * + * @tparam Q - Number of bits for fixed point operations. + * @param output - Output mix buffer. + * @param input - Input mix buffer. + * @param volume - Volume applied to the input. + * @param sample_count - Number of samples to process. + */ +template +static void ApplyUniformGain(std::span output, std::span input, const f32 volume, + const u32 sample_count) { + if (volume == 1.0f) { + std::memcpy(output.data(), input.data(), input.size_bytes()); + } else { + const Common::FixedPoint<64 - Q, Q> gain{volume}; + for (u32 i = 0; i < sample_count; i++) { + output[i] = (input[i] * gain).to_int(); + } + } +} + +void VolumeCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, + std::string& string) { + string += fmt::format("VolumeCommand"); + string += fmt::format("\n\tinput {:02X}", input_index); + string += fmt::format("\n\toutput {:02X}", output_index); + string += fmt::format("\n\tvolume {:.8f}", volume); + string += "\n"; +} + +void VolumeCommand::Process(const ADSP::CommandListProcessor& processor) { + // If input and output buffers are the same, and the volume is 1.0f, this won't do + // anything, so just skip. + if (input_index == output_index && volume == 1.0f) { + return; + } + + auto output{processor.mix_buffers.subspan(output_index * processor.sample_count, + processor.sample_count)}; + auto input{processor.mix_buffers.subspan(input_index * processor.sample_count, + processor.sample_count)}; + + switch (precision) { + case 15: + ApplyUniformGain<15>(output, input, volume, processor.sample_count); + break; + + case 23: + ApplyUniformGain<23>(output, input, volume, processor.sample_count); + break; + + default: + LOG_ERROR(Service_Audio, "Invalid precision {}", precision); + break; + } +} + +bool VolumeCommand::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/mix/volume.h b/src/audio_core/renderer/command/mix/volume.h new file mode 100644 index 000000000..6ae9fb794 --- /dev/null +++ b/src/audio_core/renderer/command/mix/volume.h @@ -0,0 +1,53 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "audio_core/renderer/command/icommand.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +namespace ADSP { +class CommandListProcessor; +} + +/** + * AudioRenderer command for applying volume to a mix buffer. + */ +struct VolumeCommand : ICommand { + /** + * Print this command's information to a string. + * + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; + + /** + * Process this command. + * + * @param processor - The CommandListProcessor processing this command. + */ + void Process(const ADSP::CommandListProcessor& processor) override; + + /** + * Verify this command's data is valid. + * + * @param processor - The CommandListProcessor processing this command. + * @return True if the command is valid, otherwise false. + */ + bool Verify(const ADSP::CommandListProcessor& processor) override; + + /// Fixed point precision + u8 precision; + /// Input mix buffer index + s16 input_index; + /// Output mix buffer index + s16 output_index; + /// Mix volume applied to the input + f32 volume; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/mix/volume_ramp.cpp b/src/audio_core/renderer/command/mix/volume_ramp.cpp new file mode 100644 index 000000000..424307148 --- /dev/null +++ b/src/audio_core/renderer/command/mix/volume_ramp.cpp @@ -0,0 +1,84 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/adsp/command_list_processor.h" +#include "audio_core/renderer/command/mix/volume_ramp.h" +#include "common/fixed_point.h" + +namespace AudioCore::AudioRenderer { +/** + * Apply volume with ramping to the input mix buffer, saving to the output buffer. + * + * @tparam Q - Number of bits for fixed point operations. + * @param output - Output mix buffers. + * @param input - Input mix buffers. + * @param volume - Volume applied to the input. + * @param ramp - Ramp applied to volume every sample. + * @param sample_count - Number of samples to process. + */ +template +static void ApplyLinearEnvelopeGain(std::span output, std::span input, + const f32 volume, const f32 ramp_, const u32 sample_count) { + if (volume == 0.0f && ramp_ == 0.0f) { + std::memset(output.data(), 0, output.size_bytes()); + } else if (volume == 1.0f && ramp_ == 0.0f) { + std::memcpy(output.data(), input.data(), output.size_bytes()); + } else if (ramp_ == 0.0f) { + const Common::FixedPoint<64 - Q, Q> gain{volume}; + for (u32 i = 0; i < sample_count; i++) { + output[i] = (input[i] * gain).to_int(); + } + } else { + Common::FixedPoint<64 - Q, Q> gain{volume}; + const Common::FixedPoint<64 - Q, Q> ramp{ramp_}; + for (u32 i = 0; i < sample_count; i++) { + output[i] = (input[i] * gain).to_int(); + gain += ramp; + } + } +} + +void VolumeRampCommand::Dump(const ADSP::CommandListProcessor& processor, std::string& string) { + const auto ramp{(volume - prev_volume) / static_cast(processor.sample_count)}; + string += fmt::format("VolumeRampCommand"); + string += fmt::format("\n\tinput {:02X}", input_index); + string += fmt::format("\n\toutput {:02X}", output_index); + string += fmt::format("\n\tvolume {:.8f}", volume); + string += fmt::format("\n\tprev_volume {:.8f}", prev_volume); + string += fmt::format("\n\tramp {:.8f}", ramp); + string += "\n"; +} + +void VolumeRampCommand::Process(const ADSP::CommandListProcessor& processor) { + auto output{processor.mix_buffers.subspan(output_index * processor.sample_count, + processor.sample_count)}; + auto input{processor.mix_buffers.subspan(input_index * processor.sample_count, + processor.sample_count)}; + const auto ramp{(volume - prev_volume) / static_cast(processor.sample_count)}; + + // If input and output buffers are the same, and the volume is 1.0f, and there's no ramping, + // this won't do anything, so just skip. + if (input_index == output_index && prev_volume == 1.0f && ramp == 0.0f) { + return; + } + + switch (precision) { + case 15: + ApplyLinearEnvelopeGain<15>(output, input, prev_volume, ramp, processor.sample_count); + break; + + case 23: + ApplyLinearEnvelopeGain<23>(output, input, prev_volume, ramp, processor.sample_count); + break; + + default: + LOG_ERROR(Service_Audio, "Invalid precision {}", precision); + break; + } +} + +bool VolumeRampCommand::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/mix/volume_ramp.h b/src/audio_core/renderer/command/mix/volume_ramp.h new file mode 100644 index 000000000..77b61547e --- /dev/null +++ b/src/audio_core/renderer/command/mix/volume_ramp.h @@ -0,0 +1,56 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "audio_core/renderer/command/icommand.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +namespace ADSP { +class CommandListProcessor; +} + +/** + * AudioRenderer command for applying volume to a mix buffer, with ramping for the volume to smooth + * out the transition. + */ +struct VolumeRampCommand : ICommand { + /** + * Print this command's information to a string. + * + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; + + /** + * Process this command. + * + * @param processor - The CommandListProcessor processing this command. + */ + void Process(const ADSP::CommandListProcessor& processor) override; + + /** + * Verify this command's data is valid. + * + * @param processor - The CommandListProcessor processing this command. + * @return True if the command is valid, otherwise false. + */ + bool Verify(const ADSP::CommandListProcessor& processor) override; + + /// Fixed point precision + u8 precision; + /// Input mix buffer index + s16 input_index; + /// Output mix buffer index + s16 output_index; + /// Previous mix volume applied to the input + f32 prev_volume; + /// Current mix volume applied to the input + f32 volume; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/performance/performance.cpp b/src/audio_core/renderer/command/performance/performance.cpp new file mode 100644 index 000000000..985958b03 --- /dev/null +++ b/src/audio_core/renderer/command/performance/performance.cpp @@ -0,0 +1,43 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/adsp/command_list_processor.h" +#include "audio_core/renderer/command/performance/performance.h" +#include "core/core.h" +#include "core/core_timing.h" +#include "core/core_timing_util.h" + +namespace AudioCore::AudioRenderer { + +void PerformanceCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, + std::string& string) { + string += fmt::format("PerformanceCommand\n\tstate {}\n", static_cast(state)); +} + +void PerformanceCommand::Process(const ADSP::CommandListProcessor& processor) { + auto base{entry_address.translated_address}; + if (state == PerformanceState::Start) { + auto start_time_ptr{reinterpret_cast(base + entry_address.entry_start_time_offset)}; + *start_time_ptr = static_cast( + Core::Timing::CyclesToUs(processor.system->CoreTiming().GetClockTicks() - + processor.start_time - processor.current_processing_time) + .count()); + } else if (state == PerformanceState::Stop) { + auto processed_time_ptr{ + reinterpret_cast(base + entry_address.entry_processed_time_offset)}; + auto entry_count_ptr{ + reinterpret_cast(base + entry_address.header_entry_count_offset)}; + + *processed_time_ptr = static_cast( + Core::Timing::CyclesToUs(processor.system->CoreTiming().GetClockTicks() - + processor.start_time - processor.current_processing_time) + .count()); + (*entry_count_ptr)++; + } +} + +bool PerformanceCommand::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/performance/performance.h b/src/audio_core/renderer/command/performance/performance.h new file mode 100644 index 000000000..11a7d6c08 --- /dev/null +++ b/src/audio_core/renderer/command/performance/performance.h @@ -0,0 +1,51 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "audio_core/renderer/command/icommand.h" +#include "audio_core/renderer/performance/performance_entry_addresses.h" +#include "audio_core/renderer/performance/performance_manager.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +namespace ADSP { +class CommandListProcessor; +} + +/** + * AudioRenderer command for writing AudioRenderer performance metrics back to the sysmodule. + */ +struct PerformanceCommand : ICommand { + /** + * Print this command's information to a string. + * + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; + + /** + * Process this command. + * + * @param processor - The CommandListProcessor processing this command. + */ + void Process(const ADSP::CommandListProcessor& processor) override; + + /** + * Verify this command's data is valid. + * + * @param processor - The CommandListProcessor processing this command. + * @return True if the command is valid, otherwise false. + */ + bool Verify(const ADSP::CommandListProcessor& processor) override; + + /// State of the performance + PerformanceState state; + /// Pointers to be written + PerformanceEntryAddresses entry_address; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/resample/downmix_6ch_to_2ch.cpp b/src/audio_core/renderer/command/resample/downmix_6ch_to_2ch.cpp new file mode 100644 index 000000000..1fd90308a --- /dev/null +++ b/src/audio_core/renderer/command/resample/downmix_6ch_to_2ch.cpp @@ -0,0 +1,74 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/adsp/command_list_processor.h" +#include "audio_core/renderer/command/resample/downmix_6ch_to_2ch.h" + +namespace AudioCore::AudioRenderer { + +void DownMix6chTo2chCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, + std::string& string) { + string += fmt::format("DownMix6chTo2chCommand\n\tinputs: "); + for (u32 i = 0; i < MaxChannels; i++) { + string += fmt::format("{:02X}, ", inputs[i]); + } + string += "\n\toutputs: "; + for (u32 i = 0; i < MaxChannels; i++) { + string += fmt::format("{:02X}, ", outputs[i]); + } + string += "\n"; +} + +void DownMix6chTo2chCommand::Process(const ADSP::CommandListProcessor& processor) { + auto in_front_left{ + processor.mix_buffers.subspan(inputs[0] * processor.sample_count, processor.sample_count)}; + auto in_front_right{ + processor.mix_buffers.subspan(inputs[1] * processor.sample_count, processor.sample_count)}; + auto in_center{ + processor.mix_buffers.subspan(inputs[2] * processor.sample_count, processor.sample_count)}; + auto in_lfe{ + processor.mix_buffers.subspan(inputs[3] * processor.sample_count, processor.sample_count)}; + auto in_back_left{ + processor.mix_buffers.subspan(inputs[4] * processor.sample_count, processor.sample_count)}; + auto in_back_right{ + processor.mix_buffers.subspan(inputs[5] * processor.sample_count, processor.sample_count)}; + + auto out_front_left{ + processor.mix_buffers.subspan(outputs[0] * processor.sample_count, processor.sample_count)}; + auto out_front_right{ + processor.mix_buffers.subspan(outputs[1] * processor.sample_count, processor.sample_count)}; + auto out_center{ + processor.mix_buffers.subspan(outputs[2] * processor.sample_count, processor.sample_count)}; + auto out_lfe{ + processor.mix_buffers.subspan(outputs[3] * processor.sample_count, processor.sample_count)}; + auto out_back_left{ + processor.mix_buffers.subspan(outputs[4] * processor.sample_count, processor.sample_count)}; + auto out_back_right{ + processor.mix_buffers.subspan(outputs[5] * processor.sample_count, processor.sample_count)}; + + for (u32 i = 0; i < processor.sample_count; i++) { + const auto left_sample{(in_front_left[i] * down_mix_coeff[0] + + in_center[i] * down_mix_coeff[1] + in_lfe[i] * down_mix_coeff[2] + + in_back_left[i] * down_mix_coeff[3]) + .to_int()}; + + const auto right_sample{(in_front_right[i] * down_mix_coeff[0] + + in_center[i] * down_mix_coeff[1] + in_lfe[i] * down_mix_coeff[2] + + in_back_right[i] * down_mix_coeff[3]) + .to_int()}; + + out_front_left[i] = left_sample; + out_front_right[i] = right_sample; + } + + std::memset(out_center.data(), 0, out_center.size_bytes()); + std::memset(out_lfe.data(), 0, out_lfe.size_bytes()); + std::memset(out_back_left.data(), 0, out_back_left.size_bytes()); + std::memset(out_back_right.data(), 0, out_back_right.size_bytes()); +} + +bool DownMix6chTo2chCommand::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/resample/downmix_6ch_to_2ch.h b/src/audio_core/renderer/command/resample/downmix_6ch_to_2ch.h new file mode 100644 index 000000000..dc133a73b --- /dev/null +++ b/src/audio_core/renderer/command/resample/downmix_6ch_to_2ch.h @@ -0,0 +1,59 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "audio_core/renderer/command/icommand.h" +#include "common/common_types.h" +#include "common/fixed_point.h" + +namespace AudioCore::AudioRenderer { +namespace ADSP { +class CommandListProcessor; +} + +/** + * AudioRenderer command for downmixing 6 channels to 2. + * Channel layout (SMPTE): + * 0 - front left + * 1 - front right + * 2 - center + * 3 - lfe + * 4 - back left + * 5 - back right + */ +struct DownMix6chTo2chCommand : ICommand { + /** + * Print this command's information to a string. + * + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; + + /** + * Process this command. + * + * @param processor - The CommandListProcessor processing this command. + */ + void Process(const ADSP::CommandListProcessor& processor) override; + + /** + * Verify this command's data is valid. + * + * @param processor - The CommandListProcessor processing this command. + * @return True if the command is valid, otherwise false. + */ + bool Verify(const ADSP::CommandListProcessor& processor) override; + + /// Input mix buffer offsets for each channel + std::array inputs; + /// Output mix buffer offsets for each channel + std::array outputs; + /// Coefficients used for downmixing + std::array, 4> down_mix_coeff; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/resample/resample.cpp b/src/audio_core/renderer/command/resample/resample.cpp new file mode 100644 index 000000000..070c9d2b8 --- /dev/null +++ b/src/audio_core/renderer/command/resample/resample.cpp @@ -0,0 +1,883 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/command/resample/resample.h" + +namespace AudioCore::AudioRenderer { + +static void ResampleLowQuality(std::span output, std::span input, + const Common::FixedPoint<49, 15>& sample_rate_ratio, + Common::FixedPoint<49, 15>& fraction, const u32 samples_to_write) { + if (sample_rate_ratio == 1.0f) { + for (u32 i = 0; i < samples_to_write; i++) { + output[i] = input[i]; + } + } else { + u32 read_index{0}; + for (u32 i = 0; i < samples_to_write; i++) { + output[i] = input[read_index + (fraction >= 0.5f)]; + fraction += sample_rate_ratio; + read_index += static_cast(fraction.to_int_floor()); + fraction.clear_int(); + } + } +} + +static void ResampleNormalQuality(std::span output, std::span input, + const Common::FixedPoint<49, 15>& sample_rate_ratio, + Common::FixedPoint<49, 15>& fraction, + const u32 samples_to_write) { + static constexpr std::array lut0 = { + 0.20141602f, 0.59283447f, 0.20513916f, 0.00009155f, 0.19772339f, 0.59277344f, 0.20889282f, + 0.00027466f, 0.19406128f, 0.59262085f, 0.21264648f, 0.00045776f, 0.19039917f, 0.59240723f, + 0.21646118f, 0.00067139f, 0.18679810f, 0.59213257f, 0.22030640f, 0.00085449f, 0.18322754f, + 0.59176636f, 0.22415161f, 0.00103760f, 0.17968750f, 0.59133911f, 0.22802734f, 0.00125122f, + 0.17617798f, 0.59085083f, 0.23193359f, 0.00146484f, 0.17269897f, 0.59027100f, 0.23583984f, + 0.00167847f, 0.16925049f, 0.58963013f, 0.23977661f, 0.00189209f, 0.16583252f, 0.58892822f, + 0.24374390f, 0.00210571f, 0.16244507f, 0.58816528f, 0.24774170f, 0.00234985f, 0.15908813f, + 0.58731079f, 0.25173950f, 0.00256348f, 0.15576172f, 0.58639526f, 0.25576782f, 0.00280762f, + 0.15249634f, 0.58541870f, 0.25979614f, 0.00308228f, 0.14923096f, 0.58435059f, 0.26385498f, + 0.00332642f, 0.14602661f, 0.58325195f, 0.26794434f, 0.00360107f, 0.14285278f, 0.58206177f, + 0.27203369f, 0.00387573f, 0.13973999f, 0.58078003f, 0.27612305f, 0.00418091f, 0.13662720f, + 0.57946777f, 0.28024292f, 0.00448608f, 0.13357544f, 0.57806396f, 0.28436279f, 0.00479126f, + 0.13052368f, 0.57662964f, 0.28851318f, 0.00512695f, 0.12753296f, 0.57510376f, 0.29266357f, + 0.00546265f, 0.12460327f, 0.57351685f, 0.29681396f, 0.00579834f, 0.12167358f, 0.57183838f, + 0.30099487f, 0.00616455f, 0.11880493f, 0.57012939f, 0.30517578f, 0.00656128f, 0.11596680f, + 0.56835938f, 0.30935669f, 0.00695801f, 0.11318970f, 0.56649780f, 0.31353760f, 0.00735474f, + 0.11041260f, 0.56457520f, 0.31771851f, 0.00778198f, 0.10769653f, 0.56262207f, 0.32192993f, + 0.00823975f, 0.10501099f, 0.56057739f, 0.32614136f, 0.00869751f, 0.10238647f, 0.55847168f, + 0.33032227f, 0.00915527f, 0.09976196f, 0.55633545f, 0.33453369f, 0.00967407f, 0.09722900f, + 0.55410767f, 0.33874512f, 0.01019287f, 0.09469604f, 0.55181885f, 0.34295654f, 0.01071167f, + 0.09222412f, 0.54949951f, 0.34713745f, 0.01126099f, 0.08978271f, 0.54708862f, 0.35134888f, + 0.01184082f, 0.08737183f, 0.54464722f, 0.35552979f, 0.01245117f, 0.08499146f, 0.54214478f, + 0.35974121f, 0.01306152f, 0.08267212f, 0.53958130f, 0.36392212f, 0.01370239f, 0.08041382f, + 0.53695679f, 0.36810303f, 0.01437378f, 0.07815552f, 0.53427124f, 0.37225342f, 0.01507568f, + 0.07595825f, 0.53155518f, 0.37640381f, 0.01577759f, 0.07379150f, 0.52877808f, 0.38055420f, + 0.01651001f, 0.07165527f, 0.52593994f, 0.38470459f, 0.01727295f, 0.06958008f, 0.52307129f, + 0.38882446f, 0.01806641f, 0.06753540f, 0.52014160f, 0.39294434f, 0.01889038f, 0.06552124f, + 0.51715088f, 0.39703369f, 0.01974487f, 0.06356812f, 0.51409912f, 0.40112305f, 0.02059937f, + 0.06164551f, 0.51101685f, 0.40518188f, 0.02148438f, 0.05975342f, 0.50790405f, 0.40921021f, + 0.02243042f, 0.05789185f, 0.50473022f, 0.41323853f, 0.02337646f, 0.05609131f, 0.50152588f, + 0.41726685f, 0.02435303f, 0.05432129f, 0.49826050f, 0.42123413f, 0.02539062f, 0.05258179f, + 0.49493408f, 0.42520142f, 0.02642822f, 0.05087280f, 0.49160767f, 0.42913818f, 0.02749634f, + 0.04922485f, 0.48822021f, 0.43307495f, 0.02859497f, 0.04760742f, 0.48477173f, 0.43695068f, + 0.02975464f, 0.04602051f, 0.48132324f, 0.44082642f, 0.03091431f, 0.04446411f, 0.47781372f, + 0.44467163f, 0.03210449f, 0.04293823f, 0.47424316f, 0.44845581f, 0.03335571f, 0.04147339f, + 0.47067261f, 0.45223999f, 0.03460693f, 0.04003906f, 0.46704102f, 0.45599365f, 0.03591919f, + 0.03863525f, 0.46340942f, 0.45971680f, 0.03726196f, 0.03726196f, 0.45971680f, 0.46340942f, + 0.03863525f, 0.03591919f, 0.45599365f, 0.46704102f, 0.04003906f, 0.03460693f, 0.45223999f, + 0.47067261f, 0.04147339f, 0.03335571f, 0.44845581f, 0.47424316f, 0.04293823f, 0.03210449f, + 0.44467163f, 0.47781372f, 0.04446411f, 0.03091431f, 0.44082642f, 0.48132324f, 0.04602051f, + 0.02975464f, 0.43695068f, 0.48477173f, 0.04760742f, 0.02859497f, 0.43307495f, 0.48822021f, + 0.04922485f, 0.02749634f, 0.42913818f, 0.49160767f, 0.05087280f, 0.02642822f, 0.42520142f, + 0.49493408f, 0.05258179f, 0.02539062f, 0.42123413f, 0.49826050f, 0.05432129f, 0.02435303f, + 0.41726685f, 0.50152588f, 0.05609131f, 0.02337646f, 0.41323853f, 0.50473022f, 0.05789185f, + 0.02243042f, 0.40921021f, 0.50790405f, 0.05975342f, 0.02148438f, 0.40518188f, 0.51101685f, + 0.06164551f, 0.02059937f, 0.40112305f, 0.51409912f, 0.06356812f, 0.01974487f, 0.39703369f, + 0.51715088f, 0.06552124f, 0.01889038f, 0.39294434f, 0.52014160f, 0.06753540f, 0.01806641f, + 0.38882446f, 0.52307129f, 0.06958008f, 0.01727295f, 0.38470459f, 0.52593994f, 0.07165527f, + 0.01651001f, 0.38055420f, 0.52877808f, 0.07379150f, 0.01577759f, 0.37640381f, 0.53155518f, + 0.07595825f, 0.01507568f, 0.37225342f, 0.53427124f, 0.07815552f, 0.01437378f, 0.36810303f, + 0.53695679f, 0.08041382f, 0.01370239f, 0.36392212f, 0.53958130f, 0.08267212f, 0.01306152f, + 0.35974121f, 0.54214478f, 0.08499146f, 0.01245117f, 0.35552979f, 0.54464722f, 0.08737183f, + 0.01184082f, 0.35134888f, 0.54708862f, 0.08978271f, 0.01126099f, 0.34713745f, 0.54949951f, + 0.09222412f, 0.01071167f, 0.34295654f, 0.55181885f, 0.09469604f, 0.01019287f, 0.33874512f, + 0.55410767f, 0.09722900f, 0.00967407f, 0.33453369f, 0.55633545f, 0.09976196f, 0.00915527f, + 0.33032227f, 0.55847168f, 0.10238647f, 0.00869751f, 0.32614136f, 0.56057739f, 0.10501099f, + 0.00823975f, 0.32192993f, 0.56262207f, 0.10769653f, 0.00778198f, 0.31771851f, 0.56457520f, + 0.11041260f, 0.00735474f, 0.31353760f, 0.56649780f, 0.11318970f, 0.00695801f, 0.30935669f, + 0.56835938f, 0.11596680f, 0.00656128f, 0.30517578f, 0.57012939f, 0.11880493f, 0.00616455f, + 0.30099487f, 0.57183838f, 0.12167358f, 0.00579834f, 0.29681396f, 0.57351685f, 0.12460327f, + 0.00546265f, 0.29266357f, 0.57510376f, 0.12753296f, 0.00512695f, 0.28851318f, 0.57662964f, + 0.13052368f, 0.00479126f, 0.28436279f, 0.57806396f, 0.13357544f, 0.00448608f, 0.28024292f, + 0.57946777f, 0.13662720f, 0.00418091f, 0.27612305f, 0.58078003f, 0.13973999f, 0.00387573f, + 0.27203369f, 0.58206177f, 0.14285278f, 0.00360107f, 0.26794434f, 0.58325195f, 0.14602661f, + 0.00332642f, 0.26385498f, 0.58435059f, 0.14923096f, 0.00308228f, 0.25979614f, 0.58541870f, + 0.15249634f, 0.00280762f, 0.25576782f, 0.58639526f, 0.15576172f, 0.00256348f, 0.25173950f, + 0.58731079f, 0.15908813f, 0.00234985f, 0.24774170f, 0.58816528f, 0.16244507f, 0.00210571f, + 0.24374390f, 0.58892822f, 0.16583252f, 0.00189209f, 0.23977661f, 0.58963013f, 0.16925049f, + 0.00167847f, 0.23583984f, 0.59027100f, 0.17269897f, 0.00146484f, 0.23193359f, 0.59085083f, + 0.17617798f, 0.00125122f, 0.22802734f, 0.59133911f, 0.17968750f, 0.00103760f, 0.22415161f, + 0.59176636f, 0.18322754f, 0.00085449f, 0.22030640f, 0.59213257f, 0.18679810f, 0.00067139f, + 0.21646118f, 0.59240723f, 0.19039917f, 0.00045776f, 0.21264648f, 0.59262085f, 0.19406128f, + 0.00027466f, 0.20889282f, 0.59277344f, 0.19772339f, 0.00009155f, 0.20513916f, 0.59283447f, + 0.20141602f, + }; + + static constexpr std::array lut1 = { + 0.00207520f, 0.99606323f, 0.00210571f, -0.00015259f, -0.00610352f, 0.99578857f, + 0.00646973f, -0.00045776f, -0.01000977f, 0.99526978f, 0.01095581f, -0.00079346f, + -0.01373291f, 0.99444580f, 0.01562500f, -0.00109863f, -0.01733398f, 0.99337769f, + 0.02041626f, -0.00143433f, -0.02075195f, 0.99203491f, 0.02539062f, -0.00177002f, + -0.02404785f, 0.99041748f, 0.03051758f, -0.00210571f, -0.02719116f, 0.98855591f, + 0.03582764f, -0.00244141f, -0.03021240f, 0.98641968f, 0.04125977f, -0.00280762f, + -0.03308105f, 0.98400879f, 0.04687500f, -0.00314331f, -0.03579712f, 0.98135376f, + 0.05261230f, -0.00350952f, -0.03839111f, 0.97842407f, 0.05856323f, -0.00390625f, + -0.04083252f, 0.97521973f, 0.06463623f, -0.00427246f, -0.04315186f, 0.97180176f, + 0.07086182f, -0.00466919f, -0.04534912f, 0.96810913f, 0.07727051f, -0.00509644f, + -0.04742432f, 0.96414185f, 0.08383179f, -0.00549316f, -0.04934692f, 0.95996094f, + 0.09054565f, -0.00592041f, -0.05114746f, 0.95550537f, 0.09741211f, -0.00637817f, + -0.05285645f, 0.95083618f, 0.10443115f, -0.00683594f, -0.05441284f, 0.94589233f, + 0.11160278f, -0.00732422f, -0.05584717f, 0.94073486f, 0.11892700f, -0.00781250f, + -0.05718994f, 0.93533325f, 0.12643433f, -0.00830078f, -0.05841064f, 0.92968750f, + 0.13406372f, -0.00881958f, -0.05953979f, 0.92382812f, 0.14184570f, -0.00936890f, + -0.06054688f, 0.91772461f, 0.14978027f, -0.00991821f, -0.06146240f, 0.91143799f, + 0.15783691f, -0.01046753f, -0.06225586f, 0.90490723f, 0.16607666f, -0.01104736f, + -0.06295776f, 0.89816284f, 0.17443848f, -0.01165771f, -0.06356812f, 0.89120483f, + 0.18292236f, -0.01229858f, -0.06408691f, 0.88403320f, 0.19155884f, -0.01293945f, + -0.06451416f, 0.87667847f, 0.20034790f, -0.01358032f, -0.06484985f, 0.86914062f, + 0.20925903f, -0.01428223f, -0.06509399f, 0.86138916f, 0.21829224f, -0.01495361f, + -0.06527710f, 0.85345459f, 0.22744751f, -0.01568604f, -0.06536865f, 0.84533691f, + 0.23675537f, -0.01641846f, -0.06536865f, 0.83703613f, 0.24615479f, -0.01718140f, + -0.06533813f, 0.82858276f, 0.25567627f, -0.01794434f, -0.06518555f, 0.81991577f, + 0.26531982f, -0.01873779f, -0.06500244f, 0.81112671f, 0.27505493f, -0.01956177f, + -0.06472778f, 0.80215454f, 0.28491211f, -0.02038574f, -0.06442261f, 0.79306030f, + 0.29489136f, -0.02124023f, -0.06402588f, 0.78378296f, 0.30496216f, -0.02209473f, + -0.06359863f, 0.77438354f, 0.31512451f, -0.02297974f, -0.06307983f, 0.76486206f, + 0.32537842f, -0.02389526f, -0.06253052f, 0.75518799f, 0.33569336f, -0.02481079f, + -0.06195068f, 0.74539185f, 0.34613037f, -0.02575684f, -0.06130981f, 0.73547363f, + 0.35662842f, -0.02670288f, -0.06060791f, 0.72543335f, 0.36721802f, -0.02767944f, + -0.05987549f, 0.71527100f, 0.37786865f, -0.02865601f, -0.05911255f, 0.70504761f, + 0.38858032f, -0.02966309f, -0.05831909f, 0.69470215f, 0.39935303f, -0.03067017f, + -0.05746460f, 0.68426514f, 0.41018677f, -0.03170776f, -0.05661011f, 0.67373657f, + 0.42108154f, -0.03271484f, -0.05569458f, 0.66311646f, 0.43200684f, -0.03378296f, + -0.05477905f, 0.65246582f, 0.44299316f, -0.03482056f, -0.05383301f, 0.64169312f, + 0.45401001f, -0.03588867f, -0.05285645f, 0.63088989f, 0.46505737f, -0.03695679f, + -0.05187988f, 0.62002563f, 0.47613525f, -0.03802490f, -0.05087280f, 0.60910034f, + 0.48721313f, -0.03912354f, -0.04983521f, 0.59814453f, 0.49832153f, -0.04019165f, + -0.04879761f, 0.58712769f, 0.50946045f, -0.04129028f, -0.04772949f, 0.57611084f, + 0.52056885f, -0.04235840f, -0.04669189f, 0.56503296f, 0.53170776f, -0.04345703f, + -0.04562378f, 0.55392456f, 0.54281616f, -0.04452515f, -0.04452515f, 0.54281616f, + 0.55392456f, -0.04562378f, -0.04345703f, 0.53170776f, 0.56503296f, -0.04669189f, + -0.04235840f, 0.52056885f, 0.57611084f, -0.04772949f, -0.04129028f, 0.50946045f, + 0.58712769f, -0.04879761f, -0.04019165f, 0.49832153f, 0.59814453f, -0.04983521f, + -0.03912354f, 0.48721313f, 0.60910034f, -0.05087280f, -0.03802490f, 0.47613525f, + 0.62002563f, -0.05187988f, -0.03695679f, 0.46505737f, 0.63088989f, -0.05285645f, + -0.03588867f, 0.45401001f, 0.64169312f, -0.05383301f, -0.03482056f, 0.44299316f, + 0.65246582f, -0.05477905f, -0.03378296f, 0.43200684f, 0.66311646f, -0.05569458f, + -0.03271484f, 0.42108154f, 0.67373657f, -0.05661011f, -0.03170776f, 0.41018677f, + 0.68426514f, -0.05746460f, -0.03067017f, 0.39935303f, 0.69470215f, -0.05831909f, + -0.02966309f, 0.38858032f, 0.70504761f, -0.05911255f, -0.02865601f, 0.37786865f, + 0.71527100f, -0.05987549f, -0.02767944f, 0.36721802f, 0.72543335f, -0.06060791f, + -0.02670288f, 0.35662842f, 0.73547363f, -0.06130981f, -0.02575684f, 0.34613037f, + 0.74539185f, -0.06195068f, -0.02481079f, 0.33569336f, 0.75518799f, -0.06253052f, + -0.02389526f, 0.32537842f, 0.76486206f, -0.06307983f, -0.02297974f, 0.31512451f, + 0.77438354f, -0.06359863f, -0.02209473f, 0.30496216f, 0.78378296f, -0.06402588f, + -0.02124023f, 0.29489136f, 0.79306030f, -0.06442261f, -0.02038574f, 0.28491211f, + 0.80215454f, -0.06472778f, -0.01956177f, 0.27505493f, 0.81112671f, -0.06500244f, + -0.01873779f, 0.26531982f, 0.81991577f, -0.06518555f, -0.01794434f, 0.25567627f, + 0.82858276f, -0.06533813f, -0.01718140f, 0.24615479f, 0.83703613f, -0.06536865f, + -0.01641846f, 0.23675537f, 0.84533691f, -0.06536865f, -0.01568604f, 0.22744751f, + 0.85345459f, -0.06527710f, -0.01495361f, 0.21829224f, 0.86138916f, -0.06509399f, + -0.01428223f, 0.20925903f, 0.86914062f, -0.06484985f, -0.01358032f, 0.20034790f, + 0.87667847f, -0.06451416f, -0.01293945f, 0.19155884f, 0.88403320f, -0.06408691f, + -0.01229858f, 0.18292236f, 0.89120483f, -0.06356812f, -0.01165771f, 0.17443848f, + 0.89816284f, -0.06295776f, -0.01104736f, 0.16607666f, 0.90490723f, -0.06225586f, + -0.01046753f, 0.15783691f, 0.91143799f, -0.06146240f, -0.00991821f, 0.14978027f, + 0.91772461f, -0.06054688f, -0.00936890f, 0.14184570f, 0.92382812f, -0.05953979f, + -0.00881958f, 0.13406372f, 0.92968750f, -0.05841064f, -0.00830078f, 0.12643433f, + 0.93533325f, -0.05718994f, -0.00781250f, 0.11892700f, 0.94073486f, -0.05584717f, + -0.00732422f, 0.11160278f, 0.94589233f, -0.05441284f, -0.00683594f, 0.10443115f, + 0.95083618f, -0.05285645f, -0.00637817f, 0.09741211f, 0.95550537f, -0.05114746f, + -0.00592041f, 0.09054565f, 0.95996094f, -0.04934692f, -0.00549316f, 0.08383179f, + 0.96414185f, -0.04742432f, -0.00509644f, 0.07727051f, 0.96810913f, -0.04534912f, + -0.00466919f, 0.07086182f, 0.97180176f, -0.04315186f, -0.00427246f, 0.06463623f, + 0.97521973f, -0.04083252f, -0.00390625f, 0.05856323f, 0.97842407f, -0.03839111f, + -0.00350952f, 0.05261230f, 0.98135376f, -0.03579712f, -0.00314331f, 0.04687500f, + 0.98400879f, -0.03308105f, -0.00280762f, 0.04125977f, 0.98641968f, -0.03021240f, + -0.00244141f, 0.03582764f, 0.98855591f, -0.02719116f, -0.00210571f, 0.03051758f, + 0.99041748f, -0.02404785f, -0.00177002f, 0.02539062f, 0.99203491f, -0.02075195f, + -0.00143433f, 0.02041626f, 0.99337769f, -0.01733398f, -0.00109863f, 0.01562500f, + 0.99444580f, -0.01373291f, -0.00079346f, 0.01095581f, 0.99526978f, -0.01000977f, + -0.00045776f, 0.00646973f, 0.99578857f, -0.00610352f, -0.00015259f, 0.00210571f, + 0.99606323f, -0.00207520f, + }; + + static constexpr std::array lut2 = { + 0.09750366f, 0.80221558f, 0.10159302f, -0.00097656f, 0.09350586f, 0.80203247f, + 0.10580444f, -0.00103760f, 0.08959961f, 0.80169678f, 0.11010742f, -0.00115967f, + 0.08578491f, 0.80117798f, 0.11447144f, -0.00128174f, 0.08203125f, 0.80047607f, + 0.11892700f, -0.00140381f, 0.07836914f, 0.79962158f, 0.12347412f, -0.00152588f, + 0.07479858f, 0.79861450f, 0.12814331f, -0.00164795f, 0.07135010f, 0.79742432f, + 0.13287354f, -0.00177002f, 0.06796265f, 0.79605103f, 0.13769531f, -0.00192261f, + 0.06469727f, 0.79452515f, 0.14260864f, -0.00204468f, 0.06149292f, 0.79284668f, + 0.14761353f, -0.00219727f, 0.05834961f, 0.79098511f, 0.15270996f, -0.00231934f, + 0.05532837f, 0.78894043f, 0.15789795f, -0.00247192f, 0.05236816f, 0.78674316f, + 0.16317749f, -0.00265503f, 0.04949951f, 0.78442383f, 0.16851807f, -0.00280762f, + 0.04672241f, 0.78189087f, 0.17398071f, -0.00299072f, 0.04400635f, 0.77920532f, + 0.17950439f, -0.00314331f, 0.04141235f, 0.77636719f, 0.18511963f, -0.00332642f, + 0.03887939f, 0.77337646f, 0.19082642f, -0.00350952f, 0.03640747f, 0.77023315f, + 0.19659424f, -0.00369263f, 0.03402710f, 0.76693726f, 0.20248413f, -0.00387573f, + 0.03173828f, 0.76348877f, 0.20843506f, -0.00405884f, 0.02951050f, 0.75985718f, + 0.21444702f, -0.00427246f, 0.02737427f, 0.75610352f, 0.22055054f, -0.00445557f, + 0.02529907f, 0.75219727f, 0.22674561f, -0.00466919f, 0.02331543f, 0.74816895f, + 0.23300171f, -0.00485229f, 0.02139282f, 0.74398804f, 0.23931885f, -0.00506592f, + 0.01956177f, 0.73965454f, 0.24572754f, -0.00531006f, 0.01779175f, 0.73519897f, + 0.25219727f, -0.00552368f, 0.01605225f, 0.73059082f, 0.25872803f, -0.00570679f, + 0.01440430f, 0.72586060f, 0.26535034f, -0.00592041f, 0.01281738f, 0.72100830f, + 0.27203369f, -0.00616455f, 0.01132202f, 0.71600342f, 0.27877808f, -0.00637817f, + 0.00988770f, 0.71090698f, 0.28558350f, -0.00656128f, 0.00851440f, 0.70565796f, + 0.29244995f, -0.00677490f, 0.00720215f, 0.70031738f, 0.29934692f, -0.00701904f, + 0.00592041f, 0.69485474f, 0.30633545f, -0.00723267f, 0.00469971f, 0.68927002f, + 0.31338501f, -0.00741577f, 0.00357056f, 0.68356323f, 0.32046509f, -0.00762939f, + 0.00247192f, 0.67773438f, 0.32760620f, -0.00787354f, 0.00143433f, 0.67184448f, + 0.33477783f, -0.00808716f, 0.00045776f, 0.66583252f, 0.34197998f, -0.00827026f, + -0.00048828f, 0.65972900f, 0.34924316f, -0.00845337f, -0.00134277f, 0.65353394f, + 0.35656738f, -0.00863647f, -0.00216675f, 0.64721680f, 0.36389160f, -0.00885010f, + -0.00296021f, 0.64083862f, 0.37127686f, -0.00903320f, -0.00369263f, 0.63433838f, + 0.37869263f, -0.00921631f, -0.00436401f, 0.62777710f, 0.38613892f, -0.00933838f, + -0.00497437f, 0.62115479f, 0.39361572f, -0.00949097f, -0.00558472f, 0.61444092f, + 0.40109253f, -0.00964355f, -0.00613403f, 0.60763550f, 0.40859985f, -0.00979614f, + -0.00665283f, 0.60076904f, 0.41610718f, -0.00991821f, -0.00714111f, 0.59384155f, + 0.42364502f, -0.01000977f, -0.00756836f, 0.58685303f, 0.43121338f, -0.01013184f, + -0.00796509f, 0.57977295f, 0.43875122f, -0.01022339f, -0.00833130f, 0.57266235f, + 0.44631958f, -0.01028442f, -0.00866699f, 0.56552124f, 0.45388794f, -0.01034546f, + -0.00897217f, 0.55831909f, 0.46145630f, -0.01040649f, -0.00921631f, 0.55105591f, + 0.46902466f, -0.01040649f, -0.00946045f, 0.54373169f, 0.47659302f, -0.01040649f, + -0.00967407f, 0.53640747f, 0.48413086f, -0.01037598f, -0.00985718f, 0.52902222f, + 0.49166870f, -0.01037598f, -0.01000977f, 0.52160645f, 0.49917603f, -0.01031494f, + -0.01013184f, 0.51416016f, 0.50668335f, -0.01025391f, -0.01025391f, 0.50668335f, + 0.51416016f, -0.01013184f, -0.01031494f, 0.49917603f, 0.52160645f, -0.01000977f, + -0.01037598f, 0.49166870f, 0.52902222f, -0.00985718f, -0.01037598f, 0.48413086f, + 0.53640747f, -0.00967407f, -0.01040649f, 0.47659302f, 0.54373169f, -0.00946045f, + -0.01040649f, 0.46902466f, 0.55105591f, -0.00921631f, -0.01040649f, 0.46145630f, + 0.55831909f, -0.00897217f, -0.01034546f, 0.45388794f, 0.56552124f, -0.00866699f, + -0.01028442f, 0.44631958f, 0.57266235f, -0.00833130f, -0.01022339f, 0.43875122f, + 0.57977295f, -0.00796509f, -0.01013184f, 0.43121338f, 0.58685303f, -0.00756836f, + -0.01000977f, 0.42364502f, 0.59384155f, -0.00714111f, -0.00991821f, 0.41610718f, + 0.60076904f, -0.00665283f, -0.00979614f, 0.40859985f, 0.60763550f, -0.00613403f, + -0.00964355f, 0.40109253f, 0.61444092f, -0.00558472f, -0.00949097f, 0.39361572f, + 0.62115479f, -0.00497437f, -0.00933838f, 0.38613892f, 0.62777710f, -0.00436401f, + -0.00921631f, 0.37869263f, 0.63433838f, -0.00369263f, -0.00903320f, 0.37127686f, + 0.64083862f, -0.00296021f, -0.00885010f, 0.36389160f, 0.64721680f, -0.00216675f, + -0.00863647f, 0.35656738f, 0.65353394f, -0.00134277f, -0.00845337f, 0.34924316f, + 0.65972900f, -0.00048828f, -0.00827026f, 0.34197998f, 0.66583252f, 0.00045776f, + -0.00808716f, 0.33477783f, 0.67184448f, 0.00143433f, -0.00787354f, 0.32760620f, + 0.67773438f, 0.00247192f, -0.00762939f, 0.32046509f, 0.68356323f, 0.00357056f, + -0.00741577f, 0.31338501f, 0.68927002f, 0.00469971f, -0.00723267f, 0.30633545f, + 0.69485474f, 0.00592041f, -0.00701904f, 0.29934692f, 0.70031738f, 0.00720215f, + -0.00677490f, 0.29244995f, 0.70565796f, 0.00851440f, -0.00656128f, 0.28558350f, + 0.71090698f, 0.00988770f, -0.00637817f, 0.27877808f, 0.71600342f, 0.01132202f, + -0.00616455f, 0.27203369f, 0.72100830f, 0.01281738f, -0.00592041f, 0.26535034f, + 0.72586060f, 0.01440430f, -0.00570679f, 0.25872803f, 0.73059082f, 0.01605225f, + -0.00552368f, 0.25219727f, 0.73519897f, 0.01779175f, -0.00531006f, 0.24572754f, + 0.73965454f, 0.01956177f, -0.00506592f, 0.23931885f, 0.74398804f, 0.02139282f, + -0.00485229f, 0.23300171f, 0.74816895f, 0.02331543f, -0.00466919f, 0.22674561f, + 0.75219727f, 0.02529907f, -0.00445557f, 0.22055054f, 0.75610352f, 0.02737427f, + -0.00427246f, 0.21444702f, 0.75985718f, 0.02951050f, -0.00405884f, 0.20843506f, + 0.76348877f, 0.03173828f, -0.00387573f, 0.20248413f, 0.76693726f, 0.03402710f, + -0.00369263f, 0.19659424f, 0.77023315f, 0.03640747f, -0.00350952f, 0.19082642f, + 0.77337646f, 0.03887939f, -0.00332642f, 0.18511963f, 0.77636719f, 0.04141235f, + -0.00314331f, 0.17950439f, 0.77920532f, 0.04400635f, -0.00299072f, 0.17398071f, + 0.78189087f, 0.04672241f, -0.00280762f, 0.16851807f, 0.78442383f, 0.04949951f, + -0.00265503f, 0.16317749f, 0.78674316f, 0.05236816f, -0.00247192f, 0.15789795f, + 0.78894043f, 0.05532837f, -0.00231934f, 0.15270996f, 0.79098511f, 0.05834961f, + -0.00219727f, 0.14761353f, 0.79284668f, 0.06149292f, -0.00204468f, 0.14260864f, + 0.79452515f, 0.06469727f, -0.00192261f, 0.13769531f, 0.79605103f, 0.06796265f, + -0.00177002f, 0.13287354f, 0.79742432f, 0.07135010f, -0.00164795f, 0.12814331f, + 0.79861450f, 0.07479858f, -0.00152588f, 0.12347412f, 0.79962158f, 0.07836914f, + -0.00140381f, 0.11892700f, 0.80047607f, 0.08203125f, -0.00128174f, 0.11447144f, + 0.80117798f, 0.08578491f, -0.00115967f, 0.11010742f, 0.80169678f, 0.08959961f, + -0.00103760f, 0.10580444f, 0.80203247f, 0.09350586f, -0.00097656f, 0.10159302f, + 0.80221558f, 0.09750366f, + }; + + const auto get_lut = [&]() -> std::span { + if (sample_rate_ratio <= 1.0f) { + return std::span(lut2.data(), lut2.size()); + } else if (sample_rate_ratio < 1.3f) { + return std::span(lut1.data(), lut1.size()); + } else { + return std::span(lut0.data(), lut0.size()); + } + }; + + auto lut{get_lut()}; + u32 read_index{0}; + for (u32 i = 0; i < samples_to_write; i++) { + const auto lut_index{(fraction.get_frac() >> 8) * 4}; + const Common::FixedPoint<56, 8> sample0{input[read_index + 0] * lut[lut_index + 0]}; + const Common::FixedPoint<56, 8> sample1{input[read_index + 1] * lut[lut_index + 1]}; + const Common::FixedPoint<56, 8> sample2{input[read_index + 2] * lut[lut_index + 2]}; + const Common::FixedPoint<56, 8> sample3{input[read_index + 3] * lut[lut_index + 3]}; + output[i] = (sample0 + sample1 + sample2 + sample3).to_int_floor(); + fraction += sample_rate_ratio; + read_index += static_cast(fraction.to_int_floor()); + fraction.clear_int(); + } +} + +static void ResampleHighQuality(std::span output, std::span input, + const Common::FixedPoint<49, 15>& sample_rate_ratio, + Common::FixedPoint<49, 15>& fraction, const u32 samples_to_write) { + static constexpr std::array lut0 = { + -0.01776123f, -0.00070190f, 0.26672363f, 0.50006104f, 0.26956177f, 0.00024414f, + -0.01800537f, 0.00000000f, -0.01748657f, -0.00164795f, 0.26388550f, 0.50003052f, + 0.27236938f, 0.00122070f, -0.01824951f, -0.00003052f, -0.01724243f, -0.00256348f, + 0.26107788f, 0.49996948f, 0.27520752f, 0.00219727f, -0.01849365f, -0.00003052f, + -0.01699829f, -0.00344849f, 0.25823975f, 0.49984741f, 0.27801514f, 0.00320435f, + -0.01873779f, -0.00006104f, -0.01675415f, -0.00433350f, 0.25543213f, 0.49972534f, + 0.28085327f, 0.00424194f, -0.01898193f, -0.00006104f, -0.01651001f, -0.00518799f, + 0.25259399f, 0.49954224f, 0.28366089f, 0.00527954f, -0.01922607f, -0.00009155f, + -0.01626587f, -0.00604248f, 0.24978638f, 0.49932861f, 0.28646851f, 0.00634766f, + -0.01947021f, -0.00012207f, -0.01602173f, -0.00686646f, 0.24697876f, 0.49908447f, + 0.28930664f, 0.00744629f, -0.01971436f, -0.00015259f, -0.01574707f, -0.00765991f, + 0.24414062f, 0.49877930f, 0.29211426f, 0.00854492f, -0.01995850f, -0.00015259f, + -0.01550293f, -0.00845337f, 0.24133301f, 0.49847412f, 0.29492188f, 0.00967407f, + -0.02020264f, -0.00018311f, -0.01525879f, -0.00921631f, 0.23852539f, 0.49810791f, + 0.29772949f, 0.01083374f, -0.02044678f, -0.00021362f, -0.01501465f, -0.00997925f, + 0.23571777f, 0.49774170f, 0.30050659f, 0.01199341f, -0.02069092f, -0.00024414f, + -0.01477051f, -0.01071167f, 0.23291016f, 0.49731445f, 0.30331421f, 0.01318359f, + -0.02093506f, -0.00027466f, -0.01452637f, -0.01141357f, 0.23010254f, 0.49685669f, + 0.30609131f, 0.01437378f, -0.02117920f, -0.00030518f, -0.01428223f, -0.01211548f, + 0.22732544f, 0.49636841f, 0.30886841f, 0.01559448f, -0.02142334f, -0.00033569f, + -0.01403809f, -0.01278687f, 0.22451782f, 0.49581909f, 0.31164551f, 0.01684570f, + -0.02163696f, -0.00039673f, -0.01379395f, -0.01345825f, 0.22174072f, 0.49526978f, + 0.31442261f, 0.01809692f, -0.02188110f, -0.00042725f, -0.01358032f, -0.01409912f, + 0.21896362f, 0.49465942f, 0.31719971f, 0.01937866f, -0.02209473f, -0.00045776f, + -0.01333618f, -0.01473999f, 0.21618652f, 0.49404907f, 0.31994629f, 0.02069092f, + -0.02233887f, -0.00048828f, -0.01309204f, -0.01535034f, 0.21343994f, 0.49337769f, + 0.32269287f, 0.02203369f, -0.02255249f, -0.00054932f, -0.01284790f, -0.01596069f, + 0.21066284f, 0.49267578f, 0.32543945f, 0.02337646f, -0.02279663f, -0.00057983f, + -0.01263428f, -0.01654053f, 0.20791626f, 0.49194336f, 0.32818604f, 0.02471924f, + -0.02301025f, -0.00064087f, -0.01239014f, -0.01708984f, 0.20516968f, 0.49118042f, + 0.33090210f, 0.02612305f, -0.02322388f, -0.00067139f, -0.01214600f, -0.01763916f, + 0.20242310f, 0.49035645f, 0.33361816f, 0.02752686f, -0.02343750f, -0.00073242f, + -0.01193237f, -0.01818848f, 0.19970703f, 0.48953247f, 0.33633423f, 0.02896118f, + -0.02365112f, -0.00079346f, -0.01168823f, -0.01867676f, 0.19696045f, 0.48864746f, + 0.33901978f, 0.03039551f, -0.02386475f, -0.00082397f, -0.01147461f, -0.01919556f, + 0.19427490f, 0.48776245f, 0.34170532f, 0.03186035f, -0.02407837f, -0.00088501f, + -0.01123047f, -0.01968384f, 0.19155884f, 0.48681641f, 0.34439087f, 0.03335571f, + -0.02429199f, -0.00094604f, -0.01101685f, -0.02014160f, 0.18887329f, 0.48583984f, + 0.34704590f, 0.03485107f, -0.02447510f, -0.00100708f, -0.01080322f, -0.02059937f, + 0.18615723f, 0.48483276f, 0.34970093f, 0.03637695f, -0.02468872f, -0.00106812f, + -0.01058960f, -0.02102661f, 0.18350220f, 0.48379517f, 0.35235596f, 0.03793335f, + -0.02487183f, -0.00112915f, -0.01034546f, -0.02145386f, 0.18081665f, 0.48272705f, + 0.35498047f, 0.03948975f, -0.02505493f, -0.00119019f, -0.01013184f, -0.02188110f, + 0.17816162f, 0.48162842f, 0.35760498f, 0.04107666f, -0.02523804f, -0.00125122f, + -0.00991821f, -0.02227783f, 0.17550659f, 0.48049927f, 0.36019897f, 0.04269409f, + -0.02542114f, -0.00131226f, -0.00970459f, -0.02264404f, 0.17288208f, 0.47933960f, + 0.36279297f, 0.04431152f, -0.02560425f, -0.00140381f, -0.00952148f, -0.02301025f, + 0.17025757f, 0.47814941f, 0.36538696f, 0.04595947f, -0.02578735f, -0.00146484f, + -0.00930786f, -0.02337646f, 0.16763306f, 0.47689819f, 0.36795044f, 0.04763794f, + -0.02593994f, -0.00152588f, -0.00909424f, -0.02371216f, 0.16503906f, 0.47564697f, + 0.37048340f, 0.04931641f, -0.02609253f, -0.00161743f, -0.00888062f, -0.02401733f, + 0.16244507f, 0.47436523f, 0.37304688f, 0.05102539f, -0.02627563f, -0.00170898f, + -0.00869751f, -0.02435303f, 0.15988159f, 0.47302246f, 0.37554932f, 0.05276489f, + -0.02642822f, -0.00177002f, -0.00848389f, -0.02462769f, 0.15731812f, 0.47167969f, + 0.37805176f, 0.05450439f, -0.02658081f, -0.00186157f, -0.00830078f, -0.02493286f, + 0.15475464f, 0.47027588f, 0.38055420f, 0.05627441f, -0.02670288f, -0.00195312f, + -0.00808716f, -0.02520752f, 0.15222168f, 0.46887207f, 0.38302612f, 0.05804443f, + -0.02685547f, -0.00204468f, -0.00790405f, -0.02545166f, 0.14968872f, 0.46743774f, + 0.38546753f, 0.05987549f, -0.02697754f, -0.00213623f, -0.00772095f, -0.02569580f, + 0.14718628f, 0.46594238f, 0.38790894f, 0.06170654f, -0.02709961f, -0.00222778f, + -0.00753784f, -0.02593994f, 0.14468384f, 0.46444702f, 0.39031982f, 0.06353760f, + -0.02722168f, -0.00231934f, -0.00735474f, -0.02615356f, 0.14218140f, 0.46289062f, + 0.39273071f, 0.06539917f, -0.02734375f, -0.00241089f, -0.00717163f, -0.02636719f, + 0.13970947f, 0.46133423f, 0.39511108f, 0.06729126f, -0.02743530f, -0.00250244f, + -0.00698853f, -0.02655029f, 0.13726807f, 0.45974731f, 0.39749146f, 0.06918335f, + -0.02755737f, -0.00259399f, -0.00680542f, -0.02673340f, 0.13479614f, 0.45812988f, + 0.39984131f, 0.07113647f, -0.02764893f, -0.00271606f, -0.00662231f, -0.02691650f, + 0.13238525f, 0.45648193f, 0.40216064f, 0.07305908f, -0.02774048f, -0.00280762f, + -0.00643921f, -0.02706909f, 0.12997437f, 0.45480347f, 0.40447998f, 0.07504272f, + -0.02780151f, -0.00292969f, -0.00628662f, -0.02722168f, 0.12756348f, 0.45309448f, + 0.40676880f, 0.07699585f, -0.02789307f, -0.00305176f, -0.00610352f, -0.02734375f, + 0.12518311f, 0.45135498f, 0.40902710f, 0.07901001f, -0.02795410f, -0.00314331f, + -0.00595093f, -0.02746582f, 0.12280273f, 0.44958496f, 0.41128540f, 0.08102417f, + -0.02801514f, -0.00326538f, -0.00579834f, -0.02758789f, 0.12045288f, 0.44778442f, + 0.41351318f, 0.08306885f, -0.02804565f, -0.00338745f, -0.00561523f, -0.02770996f, + 0.11813354f, 0.44598389f, 0.41571045f, 0.08511353f, -0.02810669f, -0.00350952f, + -0.00546265f, -0.02780151f, 0.11581421f, 0.44412231f, 0.41787720f, 0.08718872f, + -0.02813721f, -0.00363159f, -0.00531006f, -0.02786255f, 0.11349487f, 0.44226074f, + 0.42004395f, 0.08929443f, -0.02816772f, -0.00375366f, -0.00515747f, -0.02795410f, + 0.11120605f, 0.44036865f, 0.42218018f, 0.09140015f, -0.02816772f, -0.00387573f, + -0.00500488f, -0.02801514f, 0.10894775f, 0.43844604f, 0.42431641f, 0.09353638f, + -0.02819824f, -0.00402832f, -0.00485229f, -0.02807617f, 0.10668945f, 0.43649292f, + 0.42639160f, 0.09570312f, -0.02819824f, -0.00415039f, -0.00469971f, -0.02810669f, + 0.10446167f, 0.43453979f, 0.42846680f, 0.09786987f, -0.02819824f, -0.00427246f, + -0.00457764f, -0.02813721f, 0.10223389f, 0.43252563f, 0.43051147f, 0.10003662f, + -0.02816772f, -0.00442505f, -0.00442505f, -0.02816772f, 0.10003662f, 0.43051147f, + 0.43252563f, 0.10223389f, -0.02813721f, -0.00457764f, -0.00427246f, -0.02819824f, + 0.09786987f, 0.42846680f, 0.43453979f, 0.10446167f, -0.02810669f, -0.00469971f, + -0.00415039f, -0.02819824f, 0.09570312f, 0.42639160f, 0.43649292f, 0.10668945f, + -0.02807617f, -0.00485229f, -0.00402832f, -0.02819824f, 0.09353638f, 0.42431641f, + 0.43844604f, 0.10894775f, -0.02801514f, -0.00500488f, -0.00387573f, -0.02816772f, + 0.09140015f, 0.42218018f, 0.44036865f, 0.11120605f, -0.02795410f, -0.00515747f, + -0.00375366f, -0.02816772f, 0.08929443f, 0.42004395f, 0.44226074f, 0.11349487f, + -0.02786255f, -0.00531006f, -0.00363159f, -0.02813721f, 0.08718872f, 0.41787720f, + 0.44412231f, 0.11581421f, -0.02780151f, -0.00546265f, -0.00350952f, -0.02810669f, + 0.08511353f, 0.41571045f, 0.44598389f, 0.11813354f, -0.02770996f, -0.00561523f, + -0.00338745f, -0.02804565f, 0.08306885f, 0.41351318f, 0.44778442f, 0.12045288f, + -0.02758789f, -0.00579834f, -0.00326538f, -0.02801514f, 0.08102417f, 0.41128540f, + 0.44958496f, 0.12280273f, -0.02746582f, -0.00595093f, -0.00314331f, -0.02795410f, + 0.07901001f, 0.40902710f, 0.45135498f, 0.12518311f, -0.02734375f, -0.00610352f, + -0.00305176f, -0.02789307f, 0.07699585f, 0.40676880f, 0.45309448f, 0.12756348f, + -0.02722168f, -0.00628662f, -0.00292969f, -0.02780151f, 0.07504272f, 0.40447998f, + 0.45480347f, 0.12997437f, -0.02706909f, -0.00643921f, -0.00280762f, -0.02774048f, + 0.07305908f, 0.40216064f, 0.45648193f, 0.13238525f, -0.02691650f, -0.00662231f, + -0.00271606f, -0.02764893f, 0.07113647f, 0.39984131f, 0.45812988f, 0.13479614f, + -0.02673340f, -0.00680542f, -0.00259399f, -0.02755737f, 0.06918335f, 0.39749146f, + 0.45974731f, 0.13726807f, -0.02655029f, -0.00698853f, -0.00250244f, -0.02743530f, + 0.06729126f, 0.39511108f, 0.46133423f, 0.13970947f, -0.02636719f, -0.00717163f, + -0.00241089f, -0.02734375f, 0.06539917f, 0.39273071f, 0.46289062f, 0.14218140f, + -0.02615356f, -0.00735474f, -0.00231934f, -0.02722168f, 0.06353760f, 0.39031982f, + 0.46444702f, 0.14468384f, -0.02593994f, -0.00753784f, -0.00222778f, -0.02709961f, + 0.06170654f, 0.38790894f, 0.46594238f, 0.14718628f, -0.02569580f, -0.00772095f, + -0.00213623f, -0.02697754f, 0.05987549f, 0.38546753f, 0.46743774f, 0.14968872f, + -0.02545166f, -0.00790405f, -0.00204468f, -0.02685547f, 0.05804443f, 0.38302612f, + 0.46887207f, 0.15222168f, -0.02520752f, -0.00808716f, -0.00195312f, -0.02670288f, + 0.05627441f, 0.38055420f, 0.47027588f, 0.15475464f, -0.02493286f, -0.00830078f, + -0.00186157f, -0.02658081f, 0.05450439f, 0.37805176f, 0.47167969f, 0.15731812f, + -0.02462769f, -0.00848389f, -0.00177002f, -0.02642822f, 0.05276489f, 0.37554932f, + 0.47302246f, 0.15988159f, -0.02435303f, -0.00869751f, -0.00170898f, -0.02627563f, + 0.05102539f, 0.37304688f, 0.47436523f, 0.16244507f, -0.02401733f, -0.00888062f, + -0.00161743f, -0.02609253f, 0.04931641f, 0.37048340f, 0.47564697f, 0.16503906f, + -0.02371216f, -0.00909424f, -0.00152588f, -0.02593994f, 0.04763794f, 0.36795044f, + 0.47689819f, 0.16763306f, -0.02337646f, -0.00930786f, -0.00146484f, -0.02578735f, + 0.04595947f, 0.36538696f, 0.47814941f, 0.17025757f, -0.02301025f, -0.00952148f, + -0.00140381f, -0.02560425f, 0.04431152f, 0.36279297f, 0.47933960f, 0.17288208f, + -0.02264404f, -0.00970459f, -0.00131226f, -0.02542114f, 0.04269409f, 0.36019897f, + 0.48049927f, 0.17550659f, -0.02227783f, -0.00991821f, -0.00125122f, -0.02523804f, + 0.04107666f, 0.35760498f, 0.48162842f, 0.17816162f, -0.02188110f, -0.01013184f, + -0.00119019f, -0.02505493f, 0.03948975f, 0.35498047f, 0.48272705f, 0.18081665f, + -0.02145386f, -0.01034546f, -0.00112915f, -0.02487183f, 0.03793335f, 0.35235596f, + 0.48379517f, 0.18350220f, -0.02102661f, -0.01058960f, -0.00106812f, -0.02468872f, + 0.03637695f, 0.34970093f, 0.48483276f, 0.18615723f, -0.02059937f, -0.01080322f, + -0.00100708f, -0.02447510f, 0.03485107f, 0.34704590f, 0.48583984f, 0.18887329f, + -0.02014160f, -0.01101685f, -0.00094604f, -0.02429199f, 0.03335571f, 0.34439087f, + 0.48681641f, 0.19155884f, -0.01968384f, -0.01123047f, -0.00088501f, -0.02407837f, + 0.03186035f, 0.34170532f, 0.48776245f, 0.19427490f, -0.01919556f, -0.01147461f, + -0.00082397f, -0.02386475f, 0.03039551f, 0.33901978f, 0.48864746f, 0.19696045f, + -0.01867676f, -0.01168823f, -0.00079346f, -0.02365112f, 0.02896118f, 0.33633423f, + 0.48953247f, 0.19970703f, -0.01818848f, -0.01193237f, -0.00073242f, -0.02343750f, + 0.02752686f, 0.33361816f, 0.49035645f, 0.20242310f, -0.01763916f, -0.01214600f, + -0.00067139f, -0.02322388f, 0.02612305f, 0.33090210f, 0.49118042f, 0.20516968f, + -0.01708984f, -0.01239014f, -0.00064087f, -0.02301025f, 0.02471924f, 0.32818604f, + 0.49194336f, 0.20791626f, -0.01654053f, -0.01263428f, -0.00057983f, -0.02279663f, + 0.02337646f, 0.32543945f, 0.49267578f, 0.21066284f, -0.01596069f, -0.01284790f, + -0.00054932f, -0.02255249f, 0.02203369f, 0.32269287f, 0.49337769f, 0.21343994f, + -0.01535034f, -0.01309204f, -0.00048828f, -0.02233887f, 0.02069092f, 0.31994629f, + 0.49404907f, 0.21618652f, -0.01473999f, -0.01333618f, -0.00045776f, -0.02209473f, + 0.01937866f, 0.31719971f, 0.49465942f, 0.21896362f, -0.01409912f, -0.01358032f, + -0.00042725f, -0.02188110f, 0.01809692f, 0.31442261f, 0.49526978f, 0.22174072f, + -0.01345825f, -0.01379395f, -0.00039673f, -0.02163696f, 0.01684570f, 0.31164551f, + 0.49581909f, 0.22451782f, -0.01278687f, -0.01403809f, -0.00033569f, -0.02142334f, + 0.01559448f, 0.30886841f, 0.49636841f, 0.22732544f, -0.01211548f, -0.01428223f, + -0.00030518f, -0.02117920f, 0.01437378f, 0.30609131f, 0.49685669f, 0.23010254f, + -0.01141357f, -0.01452637f, -0.00027466f, -0.02093506f, 0.01318359f, 0.30331421f, + 0.49731445f, 0.23291016f, -0.01071167f, -0.01477051f, -0.00024414f, -0.02069092f, + 0.01199341f, 0.30050659f, 0.49774170f, 0.23571777f, -0.00997925f, -0.01501465f, + -0.00021362f, -0.02044678f, 0.01083374f, 0.29772949f, 0.49810791f, 0.23852539f, + -0.00921631f, -0.01525879f, -0.00018311f, -0.02020264f, 0.00967407f, 0.29492188f, + 0.49847412f, 0.24133301f, -0.00845337f, -0.01550293f, -0.00015259f, -0.01995850f, + 0.00854492f, 0.29211426f, 0.49877930f, 0.24414062f, -0.00765991f, -0.01574707f, + -0.00015259f, -0.01971436f, 0.00744629f, 0.28930664f, 0.49908447f, 0.24697876f, + -0.00686646f, -0.01602173f, -0.00012207f, -0.01947021f, 0.00634766f, 0.28646851f, + 0.49932861f, 0.24978638f, -0.00604248f, -0.01626587f, -0.00009155f, -0.01922607f, + 0.00527954f, 0.28366089f, 0.49954224f, 0.25259399f, -0.00518799f, -0.01651001f, + -0.00006104f, -0.01898193f, 0.00424194f, 0.28085327f, 0.49972534f, 0.25543213f, + -0.00433350f, -0.01675415f, -0.00006104f, -0.01873779f, 0.00320435f, 0.27801514f, + 0.49984741f, 0.25823975f, -0.00344849f, -0.01699829f, -0.00003052f, -0.01849365f, + 0.00219727f, 0.27520752f, 0.49996948f, 0.26107788f, -0.00256348f, -0.01724243f, + -0.00003052f, -0.01824951f, 0.00122070f, 0.27236938f, 0.50003052f, 0.26388550f, + -0.00164795f, -0.01748657f, 0.00000000f, -0.01800537f, 0.00024414f, 0.26956177f, + 0.50006104f, 0.26672363f, -0.00070190f, -0.01776123f, + }; + + static constexpr std::array lut1 = { + 0.01275635f, -0.07745361f, 0.18670654f, 0.75119019f, 0.19219971f, -0.07821655f, + 0.01272583f, 0.00000000f, 0.01281738f, -0.07666016f, 0.18124390f, 0.75106812f, + 0.19772339f, -0.07897949f, 0.01266479f, 0.00003052f, 0.01284790f, -0.07583618f, + 0.17581177f, 0.75088501f, 0.20330811f, -0.07971191f, 0.01257324f, 0.00006104f, + 0.01287842f, -0.07501221f, 0.17044067f, 0.75057983f, 0.20892334f, -0.08041382f, + 0.01248169f, 0.00009155f, 0.01290894f, -0.07415771f, 0.16510010f, 0.75018311f, + 0.21453857f, -0.08111572f, 0.01239014f, 0.00012207f, 0.01290894f, -0.07330322f, + 0.15979004f, 0.74966431f, 0.22021484f, -0.08178711f, 0.01229858f, 0.00015259f, + 0.01290894f, -0.07241821f, 0.15454102f, 0.74908447f, 0.22592163f, -0.08242798f, + 0.01217651f, 0.00018311f, 0.01290894f, -0.07150269f, 0.14932251f, 0.74838257f, + 0.23165894f, -0.08303833f, 0.01205444f, 0.00021362f, 0.01290894f, -0.07058716f, + 0.14416504f, 0.74755859f, 0.23742676f, -0.08364868f, 0.01193237f, 0.00024414f, + 0.01287842f, -0.06967163f, 0.13903809f, 0.74667358f, 0.24322510f, -0.08419800f, + 0.01177979f, 0.00027466f, 0.01284790f, -0.06872559f, 0.13397217f, 0.74566650f, + 0.24905396f, -0.08474731f, 0.01162720f, 0.00033569f, 0.01281738f, -0.06777954f, + 0.12893677f, 0.74456787f, 0.25491333f, -0.08526611f, 0.01147461f, 0.00036621f, + 0.01278687f, -0.06683350f, 0.12396240f, 0.74337769f, 0.26077271f, -0.08575439f, + 0.01129150f, 0.00042725f, 0.01275635f, -0.06585693f, 0.11901855f, 0.74206543f, + 0.26669312f, -0.08621216f, 0.01110840f, 0.00045776f, 0.01269531f, -0.06488037f, + 0.11413574f, 0.74069214f, 0.27261353f, -0.08663940f, 0.01092529f, 0.00051880f, + 0.01263428f, -0.06387329f, 0.10931396f, 0.73919678f, 0.27853394f, -0.08700562f, + 0.01071167f, 0.00057983f, 0.01257324f, -0.06286621f, 0.10452271f, 0.73760986f, + 0.28451538f, -0.08737183f, 0.01049805f, 0.00064087f, 0.01251221f, -0.06185913f, + 0.09979248f, 0.73593140f, 0.29049683f, -0.08770752f, 0.01025391f, 0.00067139f, + 0.01242065f, -0.06082153f, 0.09512329f, 0.73413086f, 0.29647827f, -0.08801270f, + 0.01000977f, 0.00073242f, 0.01232910f, -0.05981445f, 0.09051514f, 0.73226929f, + 0.30249023f, -0.08828735f, 0.00973511f, 0.00079346f, 0.01226807f, -0.05877686f, + 0.08593750f, 0.73028564f, 0.30853271f, -0.08850098f, 0.00949097f, 0.00088501f, + 0.01214600f, -0.05773926f, 0.08142090f, 0.72824097f, 0.31457520f, -0.08871460f, + 0.00918579f, 0.00094604f, 0.01205444f, -0.05670166f, 0.07696533f, 0.72607422f, + 0.32061768f, -0.08886719f, 0.00891113f, 0.00100708f, 0.01196289f, -0.05563354f, + 0.07257080f, 0.72381592f, 0.32669067f, -0.08898926f, 0.00860596f, 0.00106812f, + 0.01187134f, -0.05459595f, 0.06820679f, 0.72146606f, 0.33276367f, -0.08908081f, + 0.00827026f, 0.00115967f, 0.01174927f, -0.05352783f, 0.06393433f, 0.71902466f, + 0.33883667f, -0.08911133f, 0.00796509f, 0.00122070f, 0.01162720f, -0.05245972f, + 0.05969238f, 0.71649170f, 0.34494019f, -0.08914185f, 0.00759888f, 0.00131226f, + 0.01150513f, -0.05139160f, 0.05551147f, 0.71389771f, 0.35101318f, -0.08911133f, + 0.00726318f, 0.00137329f, 0.01138306f, -0.05032349f, 0.05139160f, 0.71118164f, + 0.35711670f, -0.08901978f, 0.00686646f, 0.00146484f, 0.01126099f, -0.04928589f, + 0.04733276f, 0.70837402f, 0.36322021f, -0.08892822f, 0.00650024f, 0.00155640f, + 0.01113892f, -0.04821777f, 0.04333496f, 0.70550537f, 0.36932373f, -0.08877563f, + 0.00610352f, 0.00164795f, 0.01101685f, -0.04714966f, 0.03939819f, 0.70251465f, + 0.37542725f, -0.08856201f, 0.00567627f, 0.00173950f, 0.01086426f, -0.04608154f, + 0.03549194f, 0.69946289f, 0.38153076f, -0.08834839f, 0.00527954f, 0.00183105f, + 0.01074219f, -0.04501343f, 0.03167725f, 0.69631958f, 0.38763428f, -0.08804321f, + 0.00482178f, 0.00192261f, 0.01058960f, -0.04394531f, 0.02792358f, 0.69308472f, + 0.39370728f, -0.08773804f, 0.00436401f, 0.00201416f, 0.01043701f, -0.04287720f, + 0.02420044f, 0.68975830f, 0.39981079f, -0.08737183f, 0.00390625f, 0.00210571f, + 0.01031494f, -0.04180908f, 0.02056885f, 0.68637085f, 0.40588379f, -0.08694458f, + 0.00344849f, 0.00222778f, 0.01016235f, -0.04074097f, 0.01699829f, 0.68289185f, + 0.41195679f, -0.08648682f, 0.00296021f, 0.00231934f, 0.01000977f, -0.03970337f, + 0.01345825f, 0.67932129f, 0.41802979f, -0.08596802f, 0.00244141f, 0.00244141f, + 0.00985718f, -0.03863525f, 0.01000977f, 0.67568970f, 0.42407227f, -0.08541870f, + 0.00192261f, 0.00253296f, 0.00970459f, -0.03759766f, 0.00662231f, 0.67196655f, + 0.43011475f, -0.08480835f, 0.00140381f, 0.00265503f, 0.00955200f, -0.03652954f, + 0.00326538f, 0.66815186f, 0.43612671f, -0.08416748f, 0.00085449f, 0.00277710f, + 0.00936890f, -0.03549194f, 0.00000000f, 0.66427612f, 0.44213867f, -0.08346558f, + 0.00027466f, 0.00289917f, 0.00921631f, -0.03445435f, -0.00320435f, 0.66030884f, + 0.44812012f, -0.08270264f, -0.00027466f, 0.00299072f, 0.00906372f, -0.03344727f, + -0.00634766f, 0.65631104f, 0.45407104f, -0.08190918f, -0.00088501f, 0.00311279f, + 0.00891113f, -0.03240967f, -0.00946045f, 0.65219116f, 0.46002197f, -0.08105469f, + -0.00146484f, 0.00323486f, 0.00872803f, -0.03140259f, -0.01248169f, 0.64801025f, + 0.46594238f, -0.08013916f, -0.00210571f, 0.00338745f, 0.00857544f, -0.03039551f, + -0.01544189f, 0.64376831f, 0.47183228f, -0.07919312f, -0.00271606f, 0.00350952f, + 0.00842285f, -0.02938843f, -0.01834106f, 0.63946533f, 0.47772217f, -0.07818604f, + -0.00335693f, 0.00363159f, 0.00823975f, -0.02838135f, -0.02117920f, 0.63507080f, + 0.48358154f, -0.07711792f, -0.00402832f, 0.00375366f, 0.00808716f, -0.02740479f, + -0.02395630f, 0.63061523f, 0.48937988f, -0.07598877f, -0.00469971f, 0.00390625f, + 0.00793457f, -0.02642822f, -0.02667236f, 0.62609863f, 0.49517822f, -0.07482910f, + -0.00537109f, 0.00402832f, 0.00775146f, -0.02545166f, -0.02932739f, 0.62152100f, + 0.50094604f, -0.07357788f, -0.00607300f, 0.00418091f, 0.00759888f, -0.02450562f, + -0.03192139f, 0.61685181f, 0.50665283f, -0.07229614f, -0.00677490f, 0.00430298f, + 0.00741577f, -0.02352905f, -0.03445435f, 0.61215210f, 0.51235962f, -0.07098389f, + -0.00750732f, 0.00445557f, 0.00726318f, -0.02258301f, -0.03689575f, 0.60736084f, + 0.51800537f, -0.06958008f, -0.00823975f, 0.00460815f, 0.00711060f, -0.02166748f, + -0.03930664f, 0.60253906f, 0.52362061f, -0.06811523f, -0.00897217f, 0.00476074f, + 0.00692749f, -0.02075195f, -0.04165649f, 0.59762573f, 0.52920532f, -0.06661987f, + -0.00973511f, 0.00488281f, 0.00677490f, -0.01983643f, -0.04394531f, 0.59268188f, + 0.53475952f, -0.06506348f, -0.01052856f, 0.00503540f, 0.00662231f, -0.01892090f, + -0.04617310f, 0.58767700f, 0.54025269f, -0.06344604f, -0.01129150f, 0.00518799f, + 0.00643921f, -0.01803589f, -0.04830933f, 0.58261108f, 0.54571533f, -0.06173706f, + -0.01208496f, 0.00534058f, 0.00628662f, -0.01715088f, -0.05041504f, 0.57748413f, + 0.55111694f, -0.05999756f, -0.01290894f, 0.00549316f, 0.00613403f, -0.01626587f, + -0.05245972f, 0.57232666f, 0.55648804f, -0.05819702f, -0.01373291f, 0.00564575f, + 0.00598145f, -0.01541138f, -0.05444336f, 0.56707764f, 0.56182861f, -0.05636597f, + -0.01455688f, 0.00582886f, 0.00582886f, -0.01455688f, -0.05636597f, 0.56182861f, + 0.56707764f, -0.05444336f, -0.01541138f, 0.00598145f, 0.00564575f, -0.01373291f, + -0.05819702f, 0.55648804f, 0.57232666f, -0.05245972f, -0.01626587f, 0.00613403f, + 0.00549316f, -0.01290894f, -0.05999756f, 0.55111694f, 0.57748413f, -0.05041504f, + -0.01715088f, 0.00628662f, 0.00534058f, -0.01208496f, -0.06173706f, 0.54571533f, + 0.58261108f, -0.04830933f, -0.01803589f, 0.00643921f, 0.00518799f, -0.01129150f, + -0.06344604f, 0.54025269f, 0.58767700f, -0.04617310f, -0.01892090f, 0.00662231f, + 0.00503540f, -0.01052856f, -0.06506348f, 0.53475952f, 0.59268188f, -0.04394531f, + -0.01983643f, 0.00677490f, 0.00488281f, -0.00973511f, -0.06661987f, 0.52920532f, + 0.59762573f, -0.04165649f, -0.02075195f, 0.00692749f, 0.00476074f, -0.00897217f, + -0.06811523f, 0.52362061f, 0.60253906f, -0.03930664f, -0.02166748f, 0.00711060f, + 0.00460815f, -0.00823975f, -0.06958008f, 0.51800537f, 0.60736084f, -0.03689575f, + -0.02258301f, 0.00726318f, 0.00445557f, -0.00750732f, -0.07098389f, 0.51235962f, + 0.61215210f, -0.03445435f, -0.02352905f, 0.00741577f, 0.00430298f, -0.00677490f, + -0.07229614f, 0.50665283f, 0.61685181f, -0.03192139f, -0.02450562f, 0.00759888f, + 0.00418091f, -0.00607300f, -0.07357788f, 0.50094604f, 0.62152100f, -0.02932739f, + -0.02545166f, 0.00775146f, 0.00402832f, -0.00537109f, -0.07482910f, 0.49517822f, + 0.62609863f, -0.02667236f, -0.02642822f, 0.00793457f, 0.00390625f, -0.00469971f, + -0.07598877f, 0.48937988f, 0.63061523f, -0.02395630f, -0.02740479f, 0.00808716f, + 0.00375366f, -0.00402832f, -0.07711792f, 0.48358154f, 0.63507080f, -0.02117920f, + -0.02838135f, 0.00823975f, 0.00363159f, -0.00335693f, -0.07818604f, 0.47772217f, + 0.63946533f, -0.01834106f, -0.02938843f, 0.00842285f, 0.00350952f, -0.00271606f, + -0.07919312f, 0.47183228f, 0.64376831f, -0.01544189f, -0.03039551f, 0.00857544f, + 0.00338745f, -0.00210571f, -0.08013916f, 0.46594238f, 0.64801025f, -0.01248169f, + -0.03140259f, 0.00872803f, 0.00323486f, -0.00146484f, -0.08105469f, 0.46002197f, + 0.65219116f, -0.00946045f, -0.03240967f, 0.00891113f, 0.00311279f, -0.00088501f, + -0.08190918f, 0.45407104f, 0.65631104f, -0.00634766f, -0.03344727f, 0.00906372f, + 0.00299072f, -0.00027466f, -0.08270264f, 0.44812012f, 0.66030884f, -0.00320435f, + -0.03445435f, 0.00921631f, 0.00289917f, 0.00027466f, -0.08346558f, 0.44213867f, + 0.66427612f, 0.00000000f, -0.03549194f, 0.00936890f, 0.00277710f, 0.00085449f, + -0.08416748f, 0.43612671f, 0.66815186f, 0.00326538f, -0.03652954f, 0.00955200f, + 0.00265503f, 0.00140381f, -0.08480835f, 0.43011475f, 0.67196655f, 0.00662231f, + -0.03759766f, 0.00970459f, 0.00253296f, 0.00192261f, -0.08541870f, 0.42407227f, + 0.67568970f, 0.01000977f, -0.03863525f, 0.00985718f, 0.00244141f, 0.00244141f, + -0.08596802f, 0.41802979f, 0.67932129f, 0.01345825f, -0.03970337f, 0.01000977f, + 0.00231934f, 0.00296021f, -0.08648682f, 0.41195679f, 0.68289185f, 0.01699829f, + -0.04074097f, 0.01016235f, 0.00222778f, 0.00344849f, -0.08694458f, 0.40588379f, + 0.68637085f, 0.02056885f, -0.04180908f, 0.01031494f, 0.00210571f, 0.00390625f, + -0.08737183f, 0.39981079f, 0.68975830f, 0.02420044f, -0.04287720f, 0.01043701f, + 0.00201416f, 0.00436401f, -0.08773804f, 0.39370728f, 0.69308472f, 0.02792358f, + -0.04394531f, 0.01058960f, 0.00192261f, 0.00482178f, -0.08804321f, 0.38763428f, + 0.69631958f, 0.03167725f, -0.04501343f, 0.01074219f, 0.00183105f, 0.00527954f, + -0.08834839f, 0.38153076f, 0.69946289f, 0.03549194f, -0.04608154f, 0.01086426f, + 0.00173950f, 0.00567627f, -0.08856201f, 0.37542725f, 0.70251465f, 0.03939819f, + -0.04714966f, 0.01101685f, 0.00164795f, 0.00610352f, -0.08877563f, 0.36932373f, + 0.70550537f, 0.04333496f, -0.04821777f, 0.01113892f, 0.00155640f, 0.00650024f, + -0.08892822f, 0.36322021f, 0.70837402f, 0.04733276f, -0.04928589f, 0.01126099f, + 0.00146484f, 0.00686646f, -0.08901978f, 0.35711670f, 0.71118164f, 0.05139160f, + -0.05032349f, 0.01138306f, 0.00137329f, 0.00726318f, -0.08911133f, 0.35101318f, + 0.71389771f, 0.05551147f, -0.05139160f, 0.01150513f, 0.00131226f, 0.00759888f, + -0.08914185f, 0.34494019f, 0.71649170f, 0.05969238f, -0.05245972f, 0.01162720f, + 0.00122070f, 0.00796509f, -0.08911133f, 0.33883667f, 0.71902466f, 0.06393433f, + -0.05352783f, 0.01174927f, 0.00115967f, 0.00827026f, -0.08908081f, 0.33276367f, + 0.72146606f, 0.06820679f, -0.05459595f, 0.01187134f, 0.00106812f, 0.00860596f, + -0.08898926f, 0.32669067f, 0.72381592f, 0.07257080f, -0.05563354f, 0.01196289f, + 0.00100708f, 0.00891113f, -0.08886719f, 0.32061768f, 0.72607422f, 0.07696533f, + -0.05670166f, 0.01205444f, 0.00094604f, 0.00918579f, -0.08871460f, 0.31457520f, + 0.72824097f, 0.08142090f, -0.05773926f, 0.01214600f, 0.00088501f, 0.00949097f, + -0.08850098f, 0.30853271f, 0.73028564f, 0.08593750f, -0.05877686f, 0.01226807f, + 0.00079346f, 0.00973511f, -0.08828735f, 0.30249023f, 0.73226929f, 0.09051514f, + -0.05981445f, 0.01232910f, 0.00073242f, 0.01000977f, -0.08801270f, 0.29647827f, + 0.73413086f, 0.09512329f, -0.06082153f, 0.01242065f, 0.00067139f, 0.01025391f, + -0.08770752f, 0.29049683f, 0.73593140f, 0.09979248f, -0.06185913f, 0.01251221f, + 0.00064087f, 0.01049805f, -0.08737183f, 0.28451538f, 0.73760986f, 0.10452271f, + -0.06286621f, 0.01257324f, 0.00057983f, 0.01071167f, -0.08700562f, 0.27853394f, + 0.73919678f, 0.10931396f, -0.06387329f, 0.01263428f, 0.00051880f, 0.01092529f, + -0.08663940f, 0.27261353f, 0.74069214f, 0.11413574f, -0.06488037f, 0.01269531f, + 0.00045776f, 0.01110840f, -0.08621216f, 0.26669312f, 0.74206543f, 0.11901855f, + -0.06585693f, 0.01275635f, 0.00042725f, 0.01129150f, -0.08575439f, 0.26077271f, + 0.74337769f, 0.12396240f, -0.06683350f, 0.01278687f, 0.00036621f, 0.01147461f, + -0.08526611f, 0.25491333f, 0.74456787f, 0.12893677f, -0.06777954f, 0.01281738f, + 0.00033569f, 0.01162720f, -0.08474731f, 0.24905396f, 0.74566650f, 0.13397217f, + -0.06872559f, 0.01284790f, 0.00027466f, 0.01177979f, -0.08419800f, 0.24322510f, + 0.74667358f, 0.13903809f, -0.06967163f, 0.01287842f, 0.00024414f, 0.01193237f, + -0.08364868f, 0.23742676f, 0.74755859f, 0.14416504f, -0.07058716f, 0.01290894f, + 0.00021362f, 0.01205444f, -0.08303833f, 0.23165894f, 0.74838257f, 0.14932251f, + -0.07150269f, 0.01290894f, 0.00018311f, 0.01217651f, -0.08242798f, 0.22592163f, + 0.74908447f, 0.15454102f, -0.07241821f, 0.01290894f, 0.00015259f, 0.01229858f, + -0.08178711f, 0.22021484f, 0.74966431f, 0.15979004f, -0.07330322f, 0.01290894f, + 0.00012207f, 0.01239014f, -0.08111572f, 0.21453857f, 0.75018311f, 0.16510010f, + -0.07415771f, 0.01290894f, 0.00009155f, 0.01248169f, -0.08041382f, 0.20892334f, + 0.75057983f, 0.17044067f, -0.07501221f, 0.01287842f, 0.00006104f, 0.01257324f, + -0.07971191f, 0.20330811f, 0.75088501f, 0.17581177f, -0.07583618f, 0.01284790f, + 0.00003052f, 0.01266479f, -0.07897949f, 0.19772339f, 0.75106812f, 0.18124390f, + -0.07666016f, 0.01281738f, 0.00000000f, 0.01272583f, -0.07821655f, 0.19219971f, + 0.75119019f, 0.18670654f, -0.07745361f, 0.01275635f, + }; + + static constexpr std::array lut2 = { + -0.00036621f, 0.00143433f, -0.00408936f, 0.99996948f, 0.00247192f, -0.00048828f, + 0.00006104f, 0.00000000f, -0.00079346f, 0.00329590f, -0.01052856f, 0.99975586f, + 0.00918579f, -0.00241089f, 0.00051880f, -0.00003052f, -0.00122070f, 0.00512695f, + -0.01684570f, 0.99929810f, 0.01605225f, -0.00439453f, 0.00097656f, -0.00006104f, + -0.00161743f, 0.00689697f, -0.02297974f, 0.99862671f, 0.02304077f, -0.00640869f, + 0.00143433f, -0.00009155f, -0.00201416f, 0.00866699f, -0.02899170f, 0.99774170f, + 0.03018188f, -0.00845337f, 0.00192261f, -0.00015259f, -0.00238037f, 0.01037598f, + -0.03488159f, 0.99664307f, 0.03741455f, -0.01055908f, 0.00241089f, -0.00018311f, + -0.00274658f, 0.01202393f, -0.04061890f, 0.99533081f, 0.04483032f, -0.01266479f, + 0.00292969f, -0.00024414f, -0.00308228f, 0.01364136f, -0.04620361f, 0.99377441f, + 0.05233765f, -0.01483154f, 0.00344849f, -0.00027466f, -0.00341797f, 0.01522827f, + -0.05163574f, 0.99200439f, 0.05999756f, -0.01699829f, 0.00396729f, -0.00033569f, + -0.00375366f, 0.01678467f, -0.05691528f, 0.99002075f, 0.06777954f, -0.01922607f, + 0.00451660f, -0.00039673f, -0.00405884f, 0.01828003f, -0.06207275f, 0.98782349f, + 0.07568359f, -0.02145386f, 0.00506592f, -0.00042725f, -0.00436401f, 0.01971436f, + -0.06707764f, 0.98541260f, 0.08370972f, -0.02374268f, 0.00564575f, -0.00048828f, + -0.00463867f, 0.02114868f, -0.07192993f, 0.98278809f, 0.09185791f, -0.02603149f, + 0.00622559f, -0.00054932f, -0.00494385f, 0.02252197f, -0.07666016f, 0.97991943f, + 0.10012817f, -0.02835083f, 0.00680542f, -0.00061035f, -0.00518799f, 0.02383423f, + -0.08123779f, 0.97686768f, 0.10848999f, -0.03073120f, 0.00738525f, -0.00070190f, + -0.00543213f, 0.02511597f, -0.08566284f, 0.97360229f, 0.11700439f, -0.03308105f, + 0.00799561f, -0.00076294f, -0.00567627f, 0.02636719f, -0.08993530f, 0.97012329f, + 0.12561035f, -0.03549194f, 0.00860596f, -0.00082397f, -0.00592041f, 0.02755737f, + -0.09405518f, 0.96643066f, 0.13436890f, -0.03790283f, 0.00924683f, -0.00091553f, + -0.00613403f, 0.02868652f, -0.09805298f, 0.96252441f, 0.14318848f, -0.04034424f, + 0.00985718f, -0.00097656f, -0.00631714f, 0.02981567f, -0.10189819f, 0.95843506f, + 0.15213013f, -0.04281616f, 0.01049805f, -0.00106812f, -0.00653076f, 0.03085327f, + -0.10559082f, 0.95413208f, 0.16119385f, -0.04528809f, 0.01113892f, -0.00112915f, + -0.00671387f, 0.03189087f, -0.10916138f, 0.94961548f, 0.17034912f, -0.04779053f, + 0.01181030f, -0.00122070f, -0.00686646f, 0.03286743f, -0.11254883f, 0.94491577f, + 0.17959595f, -0.05029297f, 0.01248169f, -0.00131226f, -0.00701904f, 0.03378296f, + -0.11584473f, 0.94000244f, 0.18893433f, -0.05279541f, 0.01315308f, -0.00140381f, + -0.00717163f, 0.03466797f, -0.11895752f, 0.93490601f, 0.19839478f, -0.05532837f, + 0.01382446f, -0.00149536f, -0.00732422f, 0.03552246f, -0.12194824f, 0.92962646f, + 0.20791626f, -0.05786133f, 0.01449585f, -0.00158691f, -0.00744629f, 0.03631592f, + -0.12478638f, 0.92413330f, 0.21752930f, -0.06042480f, 0.01519775f, -0.00167847f, + -0.00753784f, 0.03707886f, -0.12750244f, 0.91848755f, 0.22723389f, -0.06298828f, + 0.01586914f, -0.00177002f, -0.00765991f, 0.03781128f, -0.13006592f, 0.91262817f, + 0.23703003f, -0.06555176f, 0.01657104f, -0.00189209f, -0.00775146f, 0.03848267f, + -0.13250732f, 0.90658569f, 0.24691772f, -0.06808472f, 0.01727295f, -0.00198364f, + -0.00784302f, 0.03909302f, -0.13479614f, 0.90036011f, 0.25683594f, -0.07064819f, + 0.01797485f, -0.00210571f, -0.00790405f, 0.03970337f, -0.13696289f, 0.89395142f, + 0.26687622f, -0.07321167f, 0.01870728f, -0.00219727f, -0.00796509f, 0.04025269f, + -0.13900757f, 0.88739014f, 0.27694702f, -0.07577515f, 0.01940918f, -0.00231934f, + -0.00802612f, 0.04077148f, -0.14089966f, 0.88064575f, 0.28710938f, -0.07833862f, + 0.02011108f, -0.00244141f, -0.00808716f, 0.04122925f, -0.14263916f, 0.87374878f, + 0.29733276f, -0.08090210f, 0.02084351f, -0.00253296f, -0.00811768f, 0.04165649f, + -0.14428711f, 0.86666870f, 0.30761719f, -0.08343506f, 0.02154541f, -0.00265503f, + -0.00814819f, 0.04205322f, -0.14578247f, 0.85940552f, 0.31793213f, -0.08596802f, + 0.02227783f, -0.00277710f, -0.00814819f, 0.04238892f, -0.14715576f, 0.85202026f, + 0.32833862f, -0.08847046f, 0.02297974f, -0.00289917f, -0.00817871f, 0.04272461f, + -0.14840698f, 0.84445190f, 0.33874512f, -0.09097290f, 0.02371216f, -0.00302124f, + -0.00817871f, 0.04299927f, -0.14953613f, 0.83673096f, 0.34924316f, -0.09347534f, + 0.02441406f, -0.00314331f, -0.00817871f, 0.04321289f, -0.15054321f, 0.82888794f, + 0.35977173f, -0.09594727f, 0.02514648f, -0.00326538f, -0.00814819f, 0.04342651f, + -0.15142822f, 0.82086182f, 0.37033081f, -0.09838867f, 0.02584839f, -0.00341797f, + -0.00814819f, 0.04357910f, -0.15219116f, 0.81271362f, 0.38092041f, -0.10079956f, + 0.02655029f, -0.00354004f, -0.00811768f, 0.04373169f, -0.15283203f, 0.80441284f, + 0.39154053f, -0.10321045f, 0.02725220f, -0.00366211f, -0.00808716f, 0.04382324f, + -0.15338135f, 0.79598999f, 0.40219116f, -0.10559082f, 0.02795410f, -0.00381470f, + -0.00805664f, 0.04388428f, -0.15377808f, 0.78741455f, 0.41287231f, -0.10794067f, + 0.02865601f, -0.00393677f, -0.00799561f, 0.04388428f, -0.15408325f, 0.77871704f, + 0.42358398f, -0.11026001f, 0.02935791f, -0.00405884f, -0.00793457f, 0.04388428f, + -0.15426636f, 0.76989746f, 0.43429565f, -0.11251831f, 0.03002930f, -0.00421143f, + -0.00787354f, 0.04385376f, -0.15435791f, 0.76095581f, 0.44500732f, -0.11477661f, + 0.03070068f, -0.00433350f, -0.00781250f, 0.04379272f, -0.15435791f, 0.75192261f, + 0.45574951f, -0.11697388f, 0.03137207f, -0.00448608f, -0.00775146f, 0.04367065f, + -0.15420532f, 0.74273682f, 0.46649170f, -0.11914062f, 0.03201294f, -0.00460815f, + -0.00769043f, 0.04354858f, -0.15399170f, 0.73345947f, 0.47723389f, -0.12127686f, + 0.03268433f, -0.00473022f, -0.00759888f, 0.04339600f, -0.15365601f, 0.72406006f, + 0.48794556f, -0.12335205f, 0.03329468f, -0.00488281f, -0.00750732f, 0.04321289f, + -0.15322876f, 0.71456909f, 0.49868774f, -0.12539673f, 0.03393555f, -0.00500488f, + -0.00741577f, 0.04296875f, -0.15270996f, 0.70498657f, 0.50936890f, -0.12738037f, + 0.03454590f, -0.00515747f, -0.00732422f, 0.04272461f, -0.15209961f, 0.69528198f, + 0.52008057f, -0.12930298f, 0.03515625f, -0.00527954f, -0.00723267f, 0.04248047f, + -0.15136719f, 0.68551636f, 0.53076172f, -0.13119507f, 0.03573608f, -0.00543213f, + -0.00714111f, 0.04217529f, -0.15057373f, 0.67565918f, 0.54138184f, -0.13299561f, + 0.03631592f, -0.00555420f, -0.00701904f, 0.04183960f, -0.14968872f, 0.66571045f, + 0.55200195f, -0.13476562f, 0.03689575f, -0.00567627f, -0.00692749f, 0.04150391f, + -0.14871216f, 0.65567017f, 0.56259155f, -0.13647461f, 0.03741455f, -0.00582886f, + -0.00680542f, 0.04113770f, -0.14767456f, 0.64556885f, 0.57315063f, -0.13812256f, + 0.03796387f, -0.00595093f, -0.00668335f, 0.04074097f, -0.14651489f, 0.63540649f, + 0.58364868f, -0.13970947f, 0.03845215f, -0.00607300f, -0.00656128f, 0.04031372f, + -0.14529419f, 0.62518311f, 0.59411621f, -0.14120483f, 0.03897095f, -0.00619507f, + -0.00643921f, 0.03988647f, -0.14401245f, 0.61486816f, 0.60452271f, -0.14263916f, + 0.03942871f, -0.00631714f, -0.00631714f, 0.03942871f, -0.14263916f, 0.60452271f, + 0.61486816f, -0.14401245f, 0.03988647f, -0.00643921f, -0.00619507f, 0.03897095f, + -0.14120483f, 0.59411621f, 0.62518311f, -0.14529419f, 0.04031372f, -0.00656128f, + -0.00607300f, 0.03845215f, -0.13970947f, 0.58364868f, 0.63540649f, -0.14651489f, + 0.04074097f, -0.00668335f, -0.00595093f, 0.03796387f, -0.13812256f, 0.57315063f, + 0.64556885f, -0.14767456f, 0.04113770f, -0.00680542f, -0.00582886f, 0.03741455f, + -0.13647461f, 0.56259155f, 0.65567017f, -0.14871216f, 0.04150391f, -0.00692749f, + -0.00567627f, 0.03689575f, -0.13476562f, 0.55200195f, 0.66571045f, -0.14968872f, + 0.04183960f, -0.00701904f, -0.00555420f, 0.03631592f, -0.13299561f, 0.54138184f, + 0.67565918f, -0.15057373f, 0.04217529f, -0.00714111f, -0.00543213f, 0.03573608f, + -0.13119507f, 0.53076172f, 0.68551636f, -0.15136719f, 0.04248047f, -0.00723267f, + -0.00527954f, 0.03515625f, -0.12930298f, 0.52008057f, 0.69528198f, -0.15209961f, + 0.04272461f, -0.00732422f, -0.00515747f, 0.03454590f, -0.12738037f, 0.50936890f, + 0.70498657f, -0.15270996f, 0.04296875f, -0.00741577f, -0.00500488f, 0.03393555f, + -0.12539673f, 0.49868774f, 0.71456909f, -0.15322876f, 0.04321289f, -0.00750732f, + -0.00488281f, 0.03329468f, -0.12335205f, 0.48794556f, 0.72406006f, -0.15365601f, + 0.04339600f, -0.00759888f, -0.00473022f, 0.03268433f, -0.12127686f, 0.47723389f, + 0.73345947f, -0.15399170f, 0.04354858f, -0.00769043f, -0.00460815f, 0.03201294f, + -0.11914062f, 0.46649170f, 0.74273682f, -0.15420532f, 0.04367065f, -0.00775146f, + -0.00448608f, 0.03137207f, -0.11697388f, 0.45574951f, 0.75192261f, -0.15435791f, + 0.04379272f, -0.00781250f, -0.00433350f, 0.03070068f, -0.11477661f, 0.44500732f, + 0.76095581f, -0.15435791f, 0.04385376f, -0.00787354f, -0.00421143f, 0.03002930f, + -0.11251831f, 0.43429565f, 0.76989746f, -0.15426636f, 0.04388428f, -0.00793457f, + -0.00405884f, 0.02935791f, -0.11026001f, 0.42358398f, 0.77871704f, -0.15408325f, + 0.04388428f, -0.00799561f, -0.00393677f, 0.02865601f, -0.10794067f, 0.41287231f, + 0.78741455f, -0.15377808f, 0.04388428f, -0.00805664f, -0.00381470f, 0.02795410f, + -0.10559082f, 0.40219116f, 0.79598999f, -0.15338135f, 0.04382324f, -0.00808716f, + -0.00366211f, 0.02725220f, -0.10321045f, 0.39154053f, 0.80441284f, -0.15283203f, + 0.04373169f, -0.00811768f, -0.00354004f, 0.02655029f, -0.10079956f, 0.38092041f, + 0.81271362f, -0.15219116f, 0.04357910f, -0.00814819f, -0.00341797f, 0.02584839f, + -0.09838867f, 0.37033081f, 0.82086182f, -0.15142822f, 0.04342651f, -0.00814819f, + -0.00326538f, 0.02514648f, -0.09594727f, 0.35977173f, 0.82888794f, -0.15054321f, + 0.04321289f, -0.00817871f, -0.00314331f, 0.02441406f, -0.09347534f, 0.34924316f, + 0.83673096f, -0.14953613f, 0.04299927f, -0.00817871f, -0.00302124f, 0.02371216f, + -0.09097290f, 0.33874512f, 0.84445190f, -0.14840698f, 0.04272461f, -0.00817871f, + -0.00289917f, 0.02297974f, -0.08847046f, 0.32833862f, 0.85202026f, -0.14715576f, + 0.04238892f, -0.00814819f, -0.00277710f, 0.02227783f, -0.08596802f, 0.31793213f, + 0.85940552f, -0.14578247f, 0.04205322f, -0.00814819f, -0.00265503f, 0.02154541f, + -0.08343506f, 0.30761719f, 0.86666870f, -0.14428711f, 0.04165649f, -0.00811768f, + -0.00253296f, 0.02084351f, -0.08090210f, 0.29733276f, 0.87374878f, -0.14263916f, + 0.04122925f, -0.00808716f, -0.00244141f, 0.02011108f, -0.07833862f, 0.28710938f, + 0.88064575f, -0.14089966f, 0.04077148f, -0.00802612f, -0.00231934f, 0.01940918f, + -0.07577515f, 0.27694702f, 0.88739014f, -0.13900757f, 0.04025269f, -0.00796509f, + -0.00219727f, 0.01870728f, -0.07321167f, 0.26687622f, 0.89395142f, -0.13696289f, + 0.03970337f, -0.00790405f, -0.00210571f, 0.01797485f, -0.07064819f, 0.25683594f, + 0.90036011f, -0.13479614f, 0.03909302f, -0.00784302f, -0.00198364f, 0.01727295f, + -0.06808472f, 0.24691772f, 0.90658569f, -0.13250732f, 0.03848267f, -0.00775146f, + -0.00189209f, 0.01657104f, -0.06555176f, 0.23703003f, 0.91262817f, -0.13006592f, + 0.03781128f, -0.00765991f, -0.00177002f, 0.01586914f, -0.06298828f, 0.22723389f, + 0.91848755f, -0.12750244f, 0.03707886f, -0.00753784f, -0.00167847f, 0.01519775f, + -0.06042480f, 0.21752930f, 0.92413330f, -0.12478638f, 0.03631592f, -0.00744629f, + -0.00158691f, 0.01449585f, -0.05786133f, 0.20791626f, 0.92962646f, -0.12194824f, + 0.03552246f, -0.00732422f, -0.00149536f, 0.01382446f, -0.05532837f, 0.19839478f, + 0.93490601f, -0.11895752f, 0.03466797f, -0.00717163f, -0.00140381f, 0.01315308f, + -0.05279541f, 0.18893433f, 0.94000244f, -0.11584473f, 0.03378296f, -0.00701904f, + -0.00131226f, 0.01248169f, -0.05029297f, 0.17959595f, 0.94491577f, -0.11254883f, + 0.03286743f, -0.00686646f, -0.00122070f, 0.01181030f, -0.04779053f, 0.17034912f, + 0.94961548f, -0.10916138f, 0.03189087f, -0.00671387f, -0.00112915f, 0.01113892f, + -0.04528809f, 0.16119385f, 0.95413208f, -0.10559082f, 0.03085327f, -0.00653076f, + -0.00106812f, 0.01049805f, -0.04281616f, 0.15213013f, 0.95843506f, -0.10189819f, + 0.02981567f, -0.00631714f, -0.00097656f, 0.00985718f, -0.04034424f, 0.14318848f, + 0.96252441f, -0.09805298f, 0.02868652f, -0.00613403f, -0.00091553f, 0.00924683f, + -0.03790283f, 0.13436890f, 0.96643066f, -0.09405518f, 0.02755737f, -0.00592041f, + -0.00082397f, 0.00860596f, -0.03549194f, 0.12561035f, 0.97012329f, -0.08993530f, + 0.02636719f, -0.00567627f, -0.00076294f, 0.00799561f, -0.03308105f, 0.11700439f, + 0.97360229f, -0.08566284f, 0.02511597f, -0.00543213f, -0.00070190f, 0.00738525f, + -0.03073120f, 0.10848999f, 0.97686768f, -0.08123779f, 0.02383423f, -0.00518799f, + -0.00061035f, 0.00680542f, -0.02835083f, 0.10012817f, 0.97991943f, -0.07666016f, + 0.02252197f, -0.00494385f, -0.00054932f, 0.00622559f, -0.02603149f, 0.09185791f, + 0.98278809f, -0.07192993f, 0.02114868f, -0.00463867f, -0.00048828f, 0.00564575f, + -0.02374268f, 0.08370972f, 0.98541260f, -0.06707764f, 0.01971436f, -0.00436401f, + -0.00042725f, 0.00506592f, -0.02145386f, 0.07568359f, 0.98782349f, -0.06207275f, + 0.01828003f, -0.00405884f, -0.00039673f, 0.00451660f, -0.01922607f, 0.06777954f, + 0.99002075f, -0.05691528f, 0.01678467f, -0.00375366f, -0.00033569f, 0.00396729f, + -0.01699829f, 0.05999756f, 0.99200439f, -0.05163574f, 0.01522827f, -0.00341797f, + -0.00027466f, 0.00344849f, -0.01483154f, 0.05233765f, 0.99377441f, -0.04620361f, + 0.01364136f, -0.00308228f, -0.00024414f, 0.00292969f, -0.01266479f, 0.04483032f, + 0.99533081f, -0.04061890f, 0.01202393f, -0.00274658f, -0.00018311f, 0.00241089f, + -0.01055908f, 0.03741455f, 0.99664307f, -0.03488159f, 0.01037598f, -0.00238037f, + -0.00015259f, 0.00192261f, -0.00845337f, 0.03018188f, 0.99774170f, -0.02899170f, + 0.00866699f, -0.00201416f, -0.00009155f, 0.00143433f, -0.00640869f, 0.02304077f, + 0.99862671f, -0.02297974f, 0.00689697f, -0.00161743f, -0.00006104f, 0.00097656f, + -0.00439453f, 0.01605225f, 0.99929810f, -0.01684570f, 0.00512695f, -0.00122070f, + -0.00003052f, 0.00051880f, -0.00241089f, 0.00918579f, 0.99975586f, -0.01052856f, + 0.00329590f, -0.00079346f, 0.00000000f, 0.00006104f, -0.00048828f, 0.00247192f, + 0.99996948f, -0.00408936f, 0.00143433f, -0.00036621f, + }; + + const auto get_lut = [&]() -> std::span { + if (sample_rate_ratio <= 1.0f) { + return std::span(lut2.data(), lut2.size()); + } else if (sample_rate_ratio < 1.3f) { + return std::span(lut1.data(), lut1.size()); + } else { + return std::span(lut0.data(), lut0.size()); + } + }; + + auto lut{get_lut()}; + u32 read_index{0}; + for (u32 i = 0; i < samples_to_write; i++) { + const auto lut_index{(fraction.get_frac() >> 8) * 8}; + const Common::FixedPoint<56, 8> sample0{input[read_index + 0] * lut[lut_index + 0]}; + const Common::FixedPoint<56, 8> sample1{input[read_index + 1] * lut[lut_index + 1]}; + const Common::FixedPoint<56, 8> sample2{input[read_index + 2] * lut[lut_index + 2]}; + const Common::FixedPoint<56, 8> sample3{input[read_index + 3] * lut[lut_index + 3]}; + const Common::FixedPoint<56, 8> sample4{input[read_index + 4] * lut[lut_index + 4]}; + const Common::FixedPoint<56, 8> sample5{input[read_index + 5] * lut[lut_index + 5]}; + const Common::FixedPoint<56, 8> sample6{input[read_index + 6] * lut[lut_index + 6]}; + const Common::FixedPoint<56, 8> sample7{input[read_index + 7] * lut[lut_index + 7]}; + output[i] = (sample0 + sample1 + sample2 + sample3 + sample4 + sample5 + sample6 + sample7) + .to_int_floor(); + fraction += sample_rate_ratio; + read_index += static_cast(fraction.to_int_floor()); + fraction.clear_int(); + } +} + +void Resample(std::span output, std::span input, + const Common::FixedPoint<49, 15>& sample_rate_ratio, + Common::FixedPoint<49, 15>& fraction, const u32 samples_to_write, + const SrcQuality src_quality) { + + switch (src_quality) { + case SrcQuality::Low: + ResampleLowQuality(output, input, sample_rate_ratio, fraction, samples_to_write); + break; + case SrcQuality::Medium: + ResampleNormalQuality(output, input, sample_rate_ratio, fraction, samples_to_write); + break; + case SrcQuality::High: + ResampleHighQuality(output, input, sample_rate_ratio, fraction, samples_to_write); + break; + } +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/resample/resample.h b/src/audio_core/renderer/command/resample/resample.h new file mode 100644 index 000000000..ba9209b82 --- /dev/null +++ b/src/audio_core/renderer/command/resample/resample.h @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "audio_core/common/common.h" +#include "common/common_types.h" +#include "common/fixed_point.h" + +namespace AudioCore::AudioRenderer { +/** + * Resample an input buffer into an output buffer, according to the sample_rate_ratio. + * + * @param output - Output buffer. + * @param input - Input buffer. + * @param sample_rate_ratio - Ratio for resampling. + e.g 32000/48000 = 0.666 input samples read per output. + * @param fraction - Current read fraction, written to and should be passed back in for + * multiple calls. + * @param samples_to_write - Number of samples to write. + * @param src_quality - Resampling quality. + */ +void Resample(std::span output, std::span input, + const Common::FixedPoint<49, 15>& sample_rate_ratio, + Common::FixedPoint<49, 15>& fraction, u32 samples_to_write, SrcQuality src_quality); + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/resample/upsample.cpp b/src/audio_core/renderer/command/resample/upsample.cpp new file mode 100644 index 000000000..6c3ff31f7 --- /dev/null +++ b/src/audio_core/renderer/command/resample/upsample.cpp @@ -0,0 +1,262 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include + +#include "audio_core/renderer/adsp/command_list_processor.h" +#include "audio_core/renderer/command/resample/upsample.h" +#include "audio_core/renderer/upsampler/upsampler_info.h" + +namespace AudioCore::AudioRenderer { +/** + * Upsampling impl. Input must be 8K, 16K or 32K, output is 48K. + * + * @param output - Output buffer. + * @param input - Input buffer. + * @param target_sample_count - Number of samples for output. + * @param state - Upsampler state, updated each call. + */ +static void SrcProcessFrame(std::span output, std::span input, + const u32 target_sample_count, const u32 source_sample_count, + UpsamplerState* state) { + constexpr u32 WindowSize = 10; + constexpr std::array, WindowSize> SincWindow1{ + 51.93359375f, -18.80078125f, 9.73046875f, -5.33203125f, 2.84375f, + -1.41015625f, 0.62109375f, -0.2265625f, 0.0625f, -0.00390625f, + }; + constexpr std::array, WindowSize> SincWindow2{ + 105.35546875f, -24.52734375f, 11.9609375f, -6.515625f, 3.52734375f, + -1.796875f, 0.828125f, -0.32421875f, 0.1015625f, -0.015625f, + }; + constexpr std::array, WindowSize> SincWindow3{ + 122.08203125f, -16.47656250f, 7.68359375f, -4.15625000f, 2.26171875f, + -1.16796875f, 0.54687500f, -0.22265625f, 0.07421875f, -0.01171875f, + }; + constexpr std::array, WindowSize> SincWindow4{ + 23.73437500f, -9.62109375f, 5.07812500f, -2.78125000f, 1.46875000f, + -0.71484375f, 0.30859375f, -0.10546875f, 0.02734375f, 0.00000000f, + }; + constexpr std::array, WindowSize> SincWindow5{ + 80.62500000f, -24.67187500f, 12.44921875f, -6.80859375f, 3.66406250f, + -1.83984375f, 0.83203125f, -0.31640625f, 0.09375000f, -0.01171875f, + }; + + if (!state->initialized) { + switch (source_sample_count) { + case 40: + state->window_size = WindowSize; + state->ratio = 6.0f; + state->history.fill(0); + break; + + case 80: + state->window_size = WindowSize; + state->ratio = 3.0f; + state->history.fill(0); + break; + + case 160: + state->window_size = WindowSize; + state->ratio = 1.5f; + state->history.fill(0); + break; + + default: + LOG_ERROR(Service_Audio, "Invalid upsampling source count {}!", source_sample_count); + // This continues anyway, but let's assume 160 for sanity + state->window_size = WindowSize; + state->ratio = 1.5f; + state->history.fill(0); + break; + } + + state->history_input_index = 0; + state->history_output_index = 9; + state->history_start_index = 0; + state->history_end_index = UpsamplerState::HistorySize - 1; + state->initialized = true; + } + + if (target_sample_count == 0) { + return; + } + + u32 read_index{0}; + + auto increment = [&]() -> void { + state->history[state->history_input_index] = input[read_index++]; + state->history_input_index = + static_cast((state->history_input_index + 1) % UpsamplerState::HistorySize); + state->history_output_index = + static_cast((state->history_output_index + 1) % UpsamplerState::HistorySize); + }; + + auto calculate_sample = [&state](std::span> coeffs1, + std::span> coeffs2) -> s32 { + auto output_index{state->history_output_index}; + auto start_pos{output_index - state->history_start_index + 1U}; + auto end_pos{10U}; + + if (start_pos < 10) { + end_pos = start_pos; + } + + u64 prev_contrib{0}; + u32 coeff_index{0}; + for (; coeff_index < end_pos; coeff_index++, output_index--) { + prev_contrib += static_cast(state->history[output_index].to_raw()) * + coeffs1[coeff_index].to_raw(); + } + + auto end_index{state->history_end_index}; + for (; start_pos < 9; start_pos++, coeff_index++, end_index--) { + prev_contrib += static_cast(state->history[end_index].to_raw()) * + coeffs1[coeff_index].to_raw(); + } + + output_index = + static_cast((state->history_output_index + 1) % UpsamplerState::HistorySize); + start_pos = state->history_end_index - output_index + 1U; + end_pos = 10U; + + if (start_pos < 10) { + end_pos = start_pos; + } + + u64 next_contrib{0}; + coeff_index = 0; + for (; coeff_index < end_pos; coeff_index++, output_index++) { + next_contrib += static_cast(state->history[output_index].to_raw()) * + coeffs2[coeff_index].to_raw(); + } + + auto start_index{state->history_start_index}; + for (; start_pos < 9; start_pos++, start_index++, coeff_index++) { + next_contrib += static_cast(state->history[start_index].to_raw()) * + coeffs2[coeff_index].to_raw(); + } + + return static_cast(((prev_contrib >> 15) + (next_contrib >> 15)) >> 8); + }; + + switch (state->ratio.to_int_floor()) { + // 40 -> 240 + case 6: + for (u32 write_index = 0; write_index < target_sample_count; write_index++) { + switch (state->sample_index) { + case 0: + increment(); + output[write_index] = state->history[state->history_output_index].to_int_floor(); + break; + + case 1: + output[write_index] = calculate_sample(SincWindow3, SincWindow4); + break; + + case 2: + output[write_index] = calculate_sample(SincWindow2, SincWindow1); + break; + + case 3: + output[write_index] = calculate_sample(SincWindow5, SincWindow5); + break; + + case 4: + output[write_index] = calculate_sample(SincWindow1, SincWindow2); + break; + + case 5: + output[write_index] = calculate_sample(SincWindow4, SincWindow3); + break; + } + state->sample_index = static_cast((state->sample_index + 1) % 6); + } + break; + + // 80 -> 240 + case 3: + for (u32 write_index = 0; write_index < target_sample_count; write_index++) { + switch (state->sample_index) { + case 0: + increment(); + output[write_index] = state->history[state->history_output_index].to_int_floor(); + break; + + case 1: + output[write_index] = calculate_sample(SincWindow2, SincWindow1); + break; + + case 2: + output[write_index] = calculate_sample(SincWindow1, SincWindow2); + break; + } + state->sample_index = static_cast((state->sample_index + 1) % 3); + } + break; + + // 160 -> 240 + default: + for (u32 write_index = 0; write_index < target_sample_count; write_index++) { + switch (state->sample_index) { + case 0: + increment(); + output[write_index] = state->history[state->history_output_index].to_int_floor(); + break; + + case 1: + output[write_index] = calculate_sample(SincWindow1, SincWindow2); + break; + + case 2: + increment(); + output[write_index] = calculate_sample(SincWindow2, SincWindow1); + break; + } + state->sample_index = static_cast((state->sample_index + 1) % 3); + } + + break; + } +} + +auto UpsampleCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, + std::string& string) -> void { + string += fmt::format("UpsampleCommand\n\tsource_sample_count {} source_sample_rate {}", + source_sample_count, source_sample_rate); + const auto upsampler{reinterpret_cast(upsampler_info)}; + if (upsampler != nullptr) { + string += fmt::format("\n\tUpsampler\n\t\tenabled {} sample count {}\n\tinputs: ", + upsampler->enabled, upsampler->sample_count); + for (u32 i = 0; i < upsampler->input_count; i++) { + string += fmt::format("{:02X}, ", upsampler->inputs[i]); + } + } + string += "\n"; +} + +void UpsampleCommand::Process(const ADSP::CommandListProcessor& processor) { + const auto info{reinterpret_cast(upsampler_info)}; + const auto input_count{std::min(info->input_count, buffer_count)}; + const std::span inputs_{reinterpret_cast(inputs), input_count}; + + for (u32 i = 0; i < input_count; i++) { + const auto channel{inputs_[i]}; + + if (channel >= 0 && channel < static_cast(processor.buffer_count)) { + auto state{&info->states[i]}; + std::span output{ + reinterpret_cast(samples_buffer + info->sample_count * channel * sizeof(s32)), + info->sample_count}; + auto input{processor.mix_buffers.subspan(channel * processor.sample_count, + processor.sample_count)}; + + SrcProcessFrame(output, input, info->sample_count, source_sample_count, state); + } + } +} + +bool UpsampleCommand::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/resample/upsample.h b/src/audio_core/renderer/command/resample/upsample.h new file mode 100644 index 000000000..bfc94e8af --- /dev/null +++ b/src/audio_core/renderer/command/resample/upsample.h @@ -0,0 +1,60 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "audio_core/renderer/command/icommand.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +namespace ADSP { +class CommandListProcessor; +} + +/** + * AudioRenderer command for upsampling a mix buffer to 48Khz. + * Input must be 8Khz, 16Khz or 32Khz, and output will be 48Khz. + */ +struct UpsampleCommand : ICommand { + /** + * Print this command's information to a string. + * + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; + + /** + * Process this command. + * + * @param processor - The CommandListProcessor processing this command. + */ + void Process(const ADSP::CommandListProcessor& processor) override; + + /** + * Verify this command's data is valid. + * + * @param processor - The CommandListProcessor processing this command. + * @return True if the command is valid, otherwise false. + */ + bool Verify(const ADSP::CommandListProcessor& processor) override; + + /// Pointer to the output samples buffer. + CpuAddr samples_buffer; + /// Pointer to input mix buffer indexes. + CpuAddr inputs; + /// Number of input mix buffers. + u32 buffer_count; + /// Unknown, unused. + u32 unk_20; + /// Source data sample count. + u32 source_sample_count; + /// Source data sample rate. + u32 source_sample_rate; + /// Pointer to the upsampler info for this command. + CpuAddr upsampler_info; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/sink/circular_buffer.cpp b/src/audio_core/renderer/command/sink/circular_buffer.cpp new file mode 100644 index 000000000..ded5afc94 --- /dev/null +++ b/src/audio_core/renderer/command/sink/circular_buffer.cpp @@ -0,0 +1,48 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include + +#include "audio_core/renderer/adsp/command_list_processor.h" +#include "audio_core/renderer/command/sink/circular_buffer.h" +#include "core/memory.h" + +namespace AudioCore::AudioRenderer { + +void CircularBufferSinkCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, + std::string& string) { + string += fmt::format( + "CircularBufferSinkCommand\n\tinput_count {} ring size {:04X} ring pos {:04X}\n\tinputs: ", + input_count, size, pos); + for (u32 i = 0; i < input_count; i++) { + string += fmt::format("{:02X}, ", inputs[i]); + } + string += "\n"; +} + +void CircularBufferSinkCommand::Process(const ADSP::CommandListProcessor& processor) { + constexpr s32 min{std::numeric_limits::min()}; + constexpr s32 max{std::numeric_limits::max()}; + + std::vector output(processor.sample_count); + for (u32 channel = 0; channel < input_count; channel++) { + auto input{processor.mix_buffers.subspan(inputs[channel] * processor.sample_count, + processor.sample_count)}; + for (u32 sample_index = 0; sample_index < processor.sample_count; sample_index++) { + output[sample_index] = static_cast(std::clamp(input[sample_index], min, max)); + } + + processor.memory->WriteBlockUnsafe(address + pos, output.data(), + output.size() * sizeof(s16)); + pos += static_cast(processor.sample_count * sizeof(s16)); + if (pos >= size) { + pos = 0; + } + } +} + +bool CircularBufferSinkCommand::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/sink/circular_buffer.h b/src/audio_core/renderer/command/sink/circular_buffer.h new file mode 100644 index 000000000..e7d5be26e --- /dev/null +++ b/src/audio_core/renderer/command/sink/circular_buffer.h @@ -0,0 +1,55 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "audio_core/renderer/command/icommand.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +namespace ADSP { +class CommandListProcessor; +} + +/** + * AudioRenderer command for sinking samples to a circular buffer. + */ +struct CircularBufferSinkCommand : ICommand { + /** + * Print this command's information to a string. + * + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; + + /** + * Process this command. + * + * @param processor - The CommandListProcessor processing this command. + */ + void Process(const ADSP::CommandListProcessor& processor) override; + + /** + * Verify this command's data is valid. + * + * @param processor - The CommandListProcessor processing this command. + * @return True if the command is valid, otherwise false. + */ + bool Verify(const ADSP::CommandListProcessor& processor) override; + + /// Number of input mix buffers + u32 input_count; + /// Input mix buffer indexes + std::array inputs; + /// Circular buffer address + CpuAddr address; + /// Circular buffer size + u32 size; + /// Current buffer offset + u32 pos; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/sink/device.cpp b/src/audio_core/renderer/command/sink/device.cpp new file mode 100644 index 000000000..47e0c6722 --- /dev/null +++ b/src/audio_core/renderer/command/sink/device.cpp @@ -0,0 +1,55 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include + +#include "audio_core/renderer/adsp/command_list_processor.h" +#include "audio_core/renderer/command/sink/device.h" +#include "audio_core/sink/sink.h" + +namespace AudioCore::AudioRenderer { + +void DeviceSinkCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, + std::string& string) { + string += fmt::format("DeviceSinkCommand\n\t{} session {} input_count {}\n\tinputs: ", + std::string_view(name), session_id, input_count); + for (u32 i = 0; i < input_count; i++) { + string += fmt::format("{:02X}, ", inputs[i]); + } + string += "\n"; +} + +void DeviceSinkCommand::Process(const ADSP::CommandListProcessor& processor) { + constexpr s32 min = std::numeric_limits::min(); + constexpr s32 max = std::numeric_limits::max(); + + auto stream{processor.GetOutputSinkStream()}; + stream->SetSystemChannels(input_count); + + Sink::SinkBuffer out_buffer{ + .frames{TargetSampleCount}, + .frames_played{0}, + .tag{0}, + .consumed{false}, + }; + + std::vector samples(out_buffer.frames * input_count); + + for (u32 channel = 0; channel < input_count; channel++) { + const auto offset{inputs[channel] * out_buffer.frames}; + + for (u32 index = 0; index < out_buffer.frames; index++) { + samples[index * input_count + channel] = + static_cast(std::clamp(sample_buffer[offset + index], min, max)); + } + } + + out_buffer.tag = reinterpret_cast(samples.data()); + stream->AppendBuffer(out_buffer, samples); +} + +bool DeviceSinkCommand::Verify(const ADSP::CommandListProcessor& processor) { + return true; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/command/sink/device.h b/src/audio_core/renderer/command/sink/device.h new file mode 100644 index 000000000..1099bcf8c --- /dev/null +++ b/src/audio_core/renderer/command/sink/device.h @@ -0,0 +1,57 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include + +#include "audio_core/renderer/command/icommand.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +namespace ADSP { +class CommandListProcessor; +} + +/** + * AudioRenderer command for sinking samples to an output device. + */ +struct DeviceSinkCommand : ICommand { + /** + * Print this command's information to a string. + * + * @param processor - The CommandListProcessor processing this command. + * @param string - The string to print into. + */ + void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; + + /** + * Process this command. + * + * @param processor - The CommandListProcessor processing this command. + */ + void Process(const ADSP::CommandListProcessor& processor) override; + + /** + * Verify this command's data is valid. + * + * @param processor - The CommandListProcessor processing this command. + * @return True if the command is valid, otherwise false. + */ + bool Verify(const ADSP::CommandListProcessor& processor) override; + + /// Device name + char name[0x100]; + /// System session id (unused) + s32 session_id; + /// Sample buffer to sink + std::span sample_buffer; + /// Number of input channels + u32 input_count; + /// Mix buffer indexes for each channel + std::array inputs; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/effect/aux_.cpp b/src/audio_core/renderer/effect/aux_.cpp new file mode 100644 index 000000000..51e780ef1 --- /dev/null +++ b/src/audio_core/renderer/effect/aux_.cpp @@ -0,0 +1,93 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/effect/aux_.h" + +namespace AudioCore::AudioRenderer { + +void AuxInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params, + const PoolMapper& pool_mapper) { + auto in_specific{reinterpret_cast(in_params.specific.data())}; + auto params{reinterpret_cast(parameter.data())}; + + std::memcpy(params, in_specific, sizeof(ParameterVersion1)); + mix_id = in_params.mix_id; + process_order = in_params.process_order; + enabled = in_params.enabled; + if (buffer_unmapped || in_params.is_new) { + const bool send_unmapped{!pool_mapper.TryAttachBuffer( + error_info, workbuffers[0], in_specific->send_buffer_info_address, + sizeof(AuxBufferInfo) + in_specific->count_max * sizeof(s32))}; + const bool return_unmapped{!pool_mapper.TryAttachBuffer( + error_info, workbuffers[1], in_specific->return_buffer_info_address, + sizeof(AuxBufferInfo) + in_specific->count_max * sizeof(s32))}; + + buffer_unmapped = send_unmapped || return_unmapped; + + if (!buffer_unmapped) { + auto send{workbuffers[0].GetReference(false)}; + send_buffer_info = send + sizeof(AuxInfoDsp); + send_buffer = send + sizeof(AuxBufferInfo); + + auto ret{workbuffers[1].GetReference(false)}; + return_buffer_info = ret + sizeof(AuxInfoDsp); + return_buffer = ret + sizeof(AuxBufferInfo); + } + } else { + error_info.error_code = ResultSuccess; + error_info.address = CpuAddr(0); + } +} + +void AuxInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params, + const PoolMapper& pool_mapper) { + auto in_specific{reinterpret_cast(in_params.specific.data())}; + auto params{reinterpret_cast(parameter.data())}; + + std::memcpy(params, in_specific, sizeof(ParameterVersion2)); + mix_id = in_params.mix_id; + process_order = in_params.process_order; + enabled = in_params.enabled; + + if (buffer_unmapped || in_params.is_new) { + const bool send_unmapped{!pool_mapper.TryAttachBuffer( + error_info, workbuffers[0], params->send_buffer_info_address, + sizeof(AuxBufferInfo) + params->count_max * sizeof(s32))}; + const bool return_unmapped{!pool_mapper.TryAttachBuffer( + error_info, workbuffers[1], params->return_buffer_info_address, + sizeof(AuxBufferInfo) + params->count_max * sizeof(s32))}; + + buffer_unmapped = send_unmapped || return_unmapped; + + if (!buffer_unmapped) { + auto send{workbuffers[0].GetReference(false)}; + send_buffer_info = send + sizeof(AuxInfoDsp); + send_buffer = send + sizeof(AuxBufferInfo); + + auto ret{workbuffers[1].GetReference(false)}; + return_buffer_info = ret + sizeof(AuxInfoDsp); + return_buffer = ret + sizeof(AuxBufferInfo); + } + } else { + error_info.error_code = ResultSuccess; + error_info.address = CpuAddr(0); + } +} + +void AuxInfo::UpdateForCommandGeneration() { + if (enabled) { + usage_state = UsageState::Enabled; + } else { + usage_state = UsageState::Disabled; + } +} + +void AuxInfo::InitializeResultState(EffectResultState& result_state) {} + +void AuxInfo::UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) {} + +CpuAddr AuxInfo::GetWorkbuffer(s32 index) { + return workbuffers[index].GetReference(true); +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/effect/aux_.h b/src/audio_core/renderer/effect/aux_.h new file mode 100644 index 000000000..4d3d9e3d9 --- /dev/null +++ b/src/audio_core/renderer/effect/aux_.h @@ -0,0 +1,123 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "audio_core/common/common.h" +#include "audio_core/renderer/effect/effect_info_base.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +/** + * Auxiliary Buffer used for Aux commands. + * Send and return buffers are available (names from the game's perspective). + * Send is read by the host, containing a buffer of samples to be used for whatever purpose. + * Return is written by the host, writing a mix buffer back to the game. + * This allows the game to use pre-processed samples skipping the other render processing, + * and to examine or modify what the audio renderer has generated. + */ +class AuxInfo : public EffectInfoBase { +public: + struct ParameterVersion1 { + /* 0x00 */ std::array inputs; + /* 0x18 */ std::array outputs; + /* 0x30 */ u32 mix_buffer_count; + /* 0x34 */ u32 sample_rate; + /* 0x38 */ u32 count_max; + /* 0x3C */ u32 mix_buffer_count_max; + /* 0x40 */ CpuAddr send_buffer_info_address; + /* 0x48 */ CpuAddr send_buffer_address; + /* 0x50 */ CpuAddr return_buffer_info_address; + /* 0x58 */ CpuAddr return_buffer_address; + /* 0x60 */ u32 mix_buffer_sample_size; + /* 0x64 */ u32 sample_count; + /* 0x68 */ u32 mix_buffer_sample_count; + }; + static_assert(sizeof(ParameterVersion1) <= sizeof(EffectInfoBase::InParameterVersion1), + "AuxInfo::ParameterVersion1 has the wrong size!"); + + struct ParameterVersion2 { + /* 0x00 */ std::array inputs; + /* 0x18 */ std::array outputs; + /* 0x30 */ u32 mix_buffer_count; + /* 0x34 */ u32 sample_rate; + /* 0x38 */ u32 count_max; + /* 0x3C */ u32 mix_buffer_count_max; + /* 0x40 */ CpuAddr send_buffer_info_address; + /* 0x48 */ CpuAddr send_buffer_address; + /* 0x50 */ CpuAddr return_buffer_info_address; + /* 0x58 */ CpuAddr return_buffer_address; + /* 0x60 */ u32 mix_buffer_sample_size; + /* 0x64 */ u32 sample_count; + /* 0x68 */ u32 mix_buffer_sample_count; + }; + static_assert(sizeof(ParameterVersion2) <= sizeof(EffectInfoBase::InParameterVersion2), + "AuxInfo::ParameterVersion2 has the wrong size!"); + + struct AuxInfoDsp { + /* 0x00 */ u32 read_offset; + /* 0x04 */ u32 write_offset; + /* 0x08 */ u32 lost_sample_count; + /* 0x0C */ u32 total_sample_count; + /* 0x10 */ char unk10[0x30]; + }; + static_assert(sizeof(AuxInfoDsp) == 0x40, "AuxInfo::AuxInfoDsp has the wrong size!"); + + struct AuxBufferInfo { + /* 0x00 */ AuxInfoDsp cpu_info; + /* 0x40 */ AuxInfoDsp dsp_info; + }; + static_assert(sizeof(AuxBufferInfo) == 0x80, "AuxInfo::AuxBufferInfo has the wrong size!"); + + /** + * Update the info with new parameters, version 1. + * + * @param error_info - Used to write call result code. + * @param in_params - New parameters to update the info with. + * @param pool_mapper - Pool for mapping buffers. + */ + void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params, + const PoolMapper& pool_mapper) override; + + /** + * Update the info with new parameters, version 2. + * + * @param error_info - Used to write call result code. + * @param in_params - New parameters to update the info with. + * @param pool_mapper - Pool for mapping buffers. + */ + void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params, + const PoolMapper& pool_mapper) override; + + /** + * Update the info after command generation. Usually only changes its state. + */ + void UpdateForCommandGeneration() override; + + /** + * Initialize a new result state. Version 2 only, unused. + * + * @param result_state - Result state to initialize. + */ + void InitializeResultState(EffectResultState& result_state) override; + + /** + * Update the host-side state with the ADSP-side state. Version 2 only, unused. + * + * @param cpu_state - Host-side result state to update. + * @param dsp_state - AudioRenderer-side result state to update from. + */ + void UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) override; + + /** + * Get a workbuffer assigned to this effect with the given index. + * + * @param index - Workbuffer index. + * @return Address of the buffer. + */ + CpuAddr GetWorkbuffer(s32 index) override; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/effect/biquad_filter.cpp b/src/audio_core/renderer/effect/biquad_filter.cpp new file mode 100644 index 000000000..a1efb3231 --- /dev/null +++ b/src/audio_core/renderer/effect/biquad_filter.cpp @@ -0,0 +1,52 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/effect/biquad_filter.h" + +namespace AudioCore::AudioRenderer { + +void BiquadFilterInfo::Update(BehaviorInfo::ErrorInfo& error_info, + const InParameterVersion1& in_params, const PoolMapper& pool_mapper) { + auto in_specific{reinterpret_cast(in_params.specific.data())}; + auto params{reinterpret_cast(parameter.data())}; + + std::memcpy(params, in_specific, sizeof(ParameterVersion1)); + mix_id = in_params.mix_id; + process_order = in_params.process_order; + enabled = in_params.enabled; + + error_info.error_code = ResultSuccess; + error_info.address = CpuAddr(0); +} + +void BiquadFilterInfo::Update(BehaviorInfo::ErrorInfo& error_info, + const InParameterVersion2& in_params, const PoolMapper& pool_mapper) { + auto in_specific{reinterpret_cast(in_params.specific.data())}; + auto params{reinterpret_cast(parameter.data())}; + + std::memcpy(params, in_specific, sizeof(ParameterVersion2)); + mix_id = in_params.mix_id; + process_order = in_params.process_order; + enabled = in_params.enabled; + + error_info.error_code = ResultSuccess; + error_info.address = CpuAddr(0); +} + +void BiquadFilterInfo::UpdateForCommandGeneration() { + if (enabled) { + usage_state = UsageState::Enabled; + } else { + usage_state = UsageState::Disabled; + } + + auto params{reinterpret_cast(parameter.data())}; + params->state = ParameterState::Updated; +} + +void BiquadFilterInfo::InitializeResultState(EffectResultState& result_state) {} + +void BiquadFilterInfo::UpdateResultState(EffectResultState& cpu_state, + EffectResultState& dsp_state) {} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/effect/biquad_filter.h b/src/audio_core/renderer/effect/biquad_filter.h new file mode 100644 index 000000000..f53fd5bab --- /dev/null +++ b/src/audio_core/renderer/effect/biquad_filter.h @@ -0,0 +1,79 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "audio_core/common/common.h" +#include "audio_core/renderer/effect/effect_info_base.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { + +class BiquadFilterInfo : public EffectInfoBase { +public: + struct ParameterVersion1 { + /* 0x00 */ std::array inputs; + /* 0x06 */ std::array outputs; + /* 0x0C */ std::array b; + /* 0x12 */ std::array a; + /* 0x16 */ s8 channel_count; + /* 0x17 */ ParameterState state; + }; + static_assert(sizeof(ParameterVersion1) <= sizeof(EffectInfoBase::InParameterVersion1), + "BiquadFilterInfo::ParameterVersion1 has the wrong size!"); + + struct ParameterVersion2 { + /* 0x00 */ std::array inputs; + /* 0x06 */ std::array outputs; + /* 0x0C */ std::array b; + /* 0x12 */ std::array a; + /* 0x16 */ s8 channel_count; + /* 0x17 */ ParameterState state; + }; + static_assert(sizeof(ParameterVersion2) <= sizeof(EffectInfoBase::InParameterVersion2), + "BiquadFilterInfo::ParameterVersion2 has the wrong size!"); + + /** + * Update the info with new parameters, version 1. + * + * @param error_info - Used to write call result code. + * @param in_params - New parameters to update the info with. + * @param pool_mapper - Pool for mapping buffers. + */ + void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params, + const PoolMapper& pool_mapper) override; + + /** + * Update the info with new parameters, version 2. + * + * @param error_info - Used to write call result code. + * @param in_params - New parameters to update the info with. + * @param pool_mapper - Pool for mapping buffers. + */ + void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params, + const PoolMapper& pool_mapper) override; + + /** + * Update the info after command generation. Usually only changes its state. + */ + void UpdateForCommandGeneration() override; + + /** + * Initialize a new result state. Version 2 only, unused. + * + * @param result_state - Result state to initialize. + */ + void InitializeResultState(EffectResultState& result_state) override; + + /** + * Update the host-side state with the ADSP-side state. Version 2 only, unused. + * + * @param cpu_state - Host-side result state to update. + * @param dsp_state - AudioRenderer-side result state to update from. + */ + void UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) override; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/effect/buffer_mixer.cpp b/src/audio_core/renderer/effect/buffer_mixer.cpp new file mode 100644 index 000000000..9c8877f01 --- /dev/null +++ b/src/audio_core/renderer/effect/buffer_mixer.cpp @@ -0,0 +1,49 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/effect/buffer_mixer.h" + +namespace AudioCore::AudioRenderer { + +void BufferMixerInfo::Update(BehaviorInfo::ErrorInfo& error_info, + const InParameterVersion1& in_params, const PoolMapper& pool_mapper) { + auto in_specific{reinterpret_cast(in_params.specific.data())}; + auto params{reinterpret_cast(parameter.data())}; + + std::memcpy(params, in_specific, sizeof(ParameterVersion1)); + mix_id = in_params.mix_id; + process_order = in_params.process_order; + enabled = in_params.enabled; + + error_info.error_code = ResultSuccess; + error_info.address = CpuAddr(0); +} + +void BufferMixerInfo::Update(BehaviorInfo::ErrorInfo& error_info, + const InParameterVersion2& in_params, const PoolMapper& pool_mapper) { + auto in_specific{reinterpret_cast(in_params.specific.data())}; + auto params{reinterpret_cast(parameter.data())}; + + std::memcpy(params, in_specific, sizeof(ParameterVersion2)); + mix_id = in_params.mix_id; + process_order = in_params.process_order; + enabled = in_params.enabled; + + error_info.error_code = ResultSuccess; + error_info.address = CpuAddr(0); +} + +void BufferMixerInfo::UpdateForCommandGeneration() { + if (enabled) { + usage_state = UsageState::Enabled; + } else { + usage_state = UsageState::Disabled; + } +} + +void BufferMixerInfo::InitializeResultState(EffectResultState& result_state) {} + +void BufferMixerInfo::UpdateResultState(EffectResultState& cpu_state, + EffectResultState& dsp_state) {} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/effect/buffer_mixer.h b/src/audio_core/renderer/effect/buffer_mixer.h new file mode 100644 index 000000000..23eed4a8b --- /dev/null +++ b/src/audio_core/renderer/effect/buffer_mixer.h @@ -0,0 +1,75 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "audio_core/common/common.h" +#include "audio_core/renderer/effect/effect_info_base.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { + +class BufferMixerInfo : public EffectInfoBase { +public: + struct ParameterVersion1 { + /* 0x00 */ std::array inputs; + /* 0x18 */ std::array outputs; + /* 0x30 */ std::array volumes; + /* 0x90 */ u32 mix_count; + }; + static_assert(sizeof(ParameterVersion1) <= sizeof(EffectInfoBase::InParameterVersion1), + "BufferMixerInfo::ParameterVersion1 has the wrong size!"); + + struct ParameterVersion2 { + /* 0x00 */ std::array inputs; + /* 0x18 */ std::array outputs; + /* 0x30 */ std::array volumes; + /* 0x90 */ u32 mix_count; + }; + static_assert(sizeof(ParameterVersion2) <= sizeof(EffectInfoBase::InParameterVersion2), + "BufferMixerInfo::ParameterVersion2 has the wrong size!"); + + /** + * Update the info with new parameters, version 1. + * + * @param error_info - Used to write call result code. + * @param in_params - New parameters to update the info with. + * @param pool_mapper - Pool for mapping buffers. + */ + void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params, + const PoolMapper& pool_mapper) override; + + /** + * Update the info with new parameters, version 2. + * + * @param error_info - Used to write call result code. + * @param in_params - New parameters to update the info with. + * @param pool_mapper - Pool for mapping buffers. + */ + void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params, + const PoolMapper& pool_mapper) override; + + /** + * Update the info after command generation. Usually only changes its state. + */ + void UpdateForCommandGeneration() override; + + /** + * Initialize a new result state. Version 2 only, unused. + * + * @param result_state - Result state to initialize. + */ + void InitializeResultState(EffectResultState& result_state) override; + + /** + * Update the host-side state with the ADSP-side state. Version 2 only, unused. + * + * @param cpu_state - Host-side result state to update. + * @param dsp_state - AudioRenderer-side result state to update from. + */ + void UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) override; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/effect/capture.cpp b/src/audio_core/renderer/effect/capture.cpp new file mode 100644 index 000000000..3f038efdb --- /dev/null +++ b/src/audio_core/renderer/effect/capture.cpp @@ -0,0 +1,82 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/effect/aux_.h" +#include "audio_core/renderer/effect/capture.h" + +namespace AudioCore::AudioRenderer { + +void CaptureInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params, + const PoolMapper& pool_mapper) { + auto in_specific{ + reinterpret_cast(in_params.specific.data())}; + auto params{reinterpret_cast(parameter.data())}; + + std::memcpy(params, in_specific, sizeof(AuxInfo::ParameterVersion1)); + mix_id = in_params.mix_id; + process_order = in_params.process_order; + enabled = in_params.enabled; + if (buffer_unmapped || in_params.is_new) { + buffer_unmapped = !pool_mapper.TryAttachBuffer( + error_info, workbuffers[0], in_specific->send_buffer_info_address, + in_specific->count_max * sizeof(s32) + sizeof(AuxInfo::AuxBufferInfo)); + + if (!buffer_unmapped) { + const auto send_address{workbuffers[0].GetReference(false)}; + send_buffer_info = send_address + sizeof(AuxInfo::AuxInfoDsp); + send_buffer = send_address + sizeof(AuxInfo::AuxBufferInfo); + return_buffer_info = 0; + return_buffer = 0; + } + } else { + error_info.error_code = ResultSuccess; + error_info.address = CpuAddr(0); + } +} + +void CaptureInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params, + const PoolMapper& pool_mapper) { + auto in_specific{ + reinterpret_cast(in_params.specific.data())}; + auto params{reinterpret_cast(parameter.data())}; + + std::memcpy(params, in_specific, sizeof(AuxInfo::ParameterVersion2)); + mix_id = in_params.mix_id; + process_order = in_params.process_order; + enabled = in_params.enabled; + + if (buffer_unmapped || in_params.is_new) { + buffer_unmapped = !pool_mapper.TryAttachBuffer( + error_info, workbuffers[0], params->send_buffer_info_address, + params->count_max * sizeof(s32) + sizeof(AuxInfo::AuxBufferInfo)); + + if (!buffer_unmapped) { + const auto send_address{workbuffers[0].GetReference(false)}; + send_buffer_info = send_address + sizeof(AuxInfo::AuxInfoDsp); + send_buffer = send_address + sizeof(AuxInfo::AuxBufferInfo); + return_buffer_info = 0; + return_buffer = 0; + } + } else { + error_info.error_code = ResultSuccess; + error_info.address = CpuAddr(0); + } +} + +void CaptureInfo::UpdateForCommandGeneration() { + if (enabled) { + usage_state = UsageState::Enabled; + } else { + usage_state = UsageState::Disabled; + } +} + +void CaptureInfo::InitializeResultState(EffectResultState& result_state) {} + +void CaptureInfo::UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) {} + +CpuAddr CaptureInfo::GetWorkbuffer(s32 index) { + return workbuffers[index].GetReference(true); +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/effect/capture.h b/src/audio_core/renderer/effect/capture.h new file mode 100644 index 000000000..6fbed8e6b --- /dev/null +++ b/src/audio_core/renderer/effect/capture.h @@ -0,0 +1,65 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "audio_core/common/common.h" +#include "audio_core/renderer/effect/effect_info_base.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { + +class CaptureInfo : public EffectInfoBase { +public: + /** + * Update the info with new parameters, version 1. + * + * @param error_info - Used to write call result code. + * @param in_params - New parameters to update the info with. + * @param pool_mapper - Pool for mapping buffers. + */ + void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params, + const PoolMapper& pool_mapper) override; + + /** + * Update the info with new parameters, version 2. + * + * @param error_info - Used to write call result code. + * @param in_params - New parameters to update the info with. + * @param pool_mapper - Pool for mapping buffers. + */ + void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params, + const PoolMapper& pool_mapper) override; + + /** + * Update the info after command generation. Usually only changes its state. + */ + void UpdateForCommandGeneration() override; + + /** + * Initialize a new result state. Version 2 only, unused. + * + * @param result_state - Result state to initialize. + */ + void InitializeResultState(EffectResultState& result_state) override; + + /** + * Update the host-side state with the ADSP-side state. Version 2 only, unused. + * + * @param cpu_state - Host-side result state to update. + * @param dsp_state - AudioRenderer-side result state to update from. + */ + void UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) override; + + /** + * Get a workbuffer assigned to this effect with the given index. + * + * @param index - Workbuffer index. + * @return Address of the buffer. + */ + CpuAddr GetWorkbuffer(s32 index) override; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/effect/compressor.cpp b/src/audio_core/renderer/effect/compressor.cpp new file mode 100644 index 000000000..220ae02f9 --- /dev/null +++ b/src/audio_core/renderer/effect/compressor.cpp @@ -0,0 +1,40 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/effect/compressor.h" + +namespace AudioCore::AudioRenderer { + +void CompressorInfo::Update(BehaviorInfo::ErrorInfo& error_info, + const InParameterVersion1& in_params, const PoolMapper& pool_mapper) {} + +void CompressorInfo::Update(BehaviorInfo::ErrorInfo& error_info, + const InParameterVersion2& in_params, const PoolMapper& pool_mapper) { + auto in_specific{reinterpret_cast(in_params.specific.data())}; + auto params{reinterpret_cast(parameter.data())}; + + std::memcpy(params, in_specific, sizeof(ParameterVersion1)); + mix_id = in_params.mix_id; + process_order = in_params.process_order; + enabled = in_params.enabled; + + error_info.error_code = ResultSuccess; + error_info.address = CpuAddr(0); +} + +void CompressorInfo::UpdateForCommandGeneration() { + if (enabled) { + usage_state = UsageState::Enabled; + } else { + usage_state = UsageState::Disabled; + } + + auto params{reinterpret_cast(parameter.data())}; + params->state = ParameterState::Updated; +} + +CpuAddr CompressorInfo::GetWorkbuffer(s32 index) { + return GetSingleBuffer(index); +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/effect/compressor.h b/src/audio_core/renderer/effect/compressor.h new file mode 100644 index 000000000..019a5ae58 --- /dev/null +++ b/src/audio_core/renderer/effect/compressor.h @@ -0,0 +1,106 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "audio_core/common/common.h" +#include "audio_core/renderer/effect/effect_info_base.h" +#include "common/common_types.h" +#include "common/fixed_point.h" + +namespace AudioCore::AudioRenderer { + +class CompressorInfo : public EffectInfoBase { +public: + struct ParameterVersion1 { + /* 0x00 */ std::array inputs; + /* 0x06 */ std::array outputs; + /* 0x0C */ s16 channel_count_max; + /* 0x0E */ s16 channel_count; + /* 0x10 */ s32 sample_rate; + /* 0x14 */ f32 threshold; + /* 0x18 */ f32 compressor_ratio; + /* 0x1C */ s32 attack_time; + /* 0x20 */ s32 release_time; + /* 0x24 */ f32 unk_24; + /* 0x28 */ f32 unk_28; + /* 0x2C */ f32 unk_2C; + /* 0x30 */ f32 out_gain; + /* 0x34 */ ParameterState state; + /* 0x35 */ bool makeup_gain_enabled; + }; + static_assert(sizeof(ParameterVersion1) <= sizeof(EffectInfoBase::InParameterVersion1), + "CompressorInfo::ParameterVersion1 has the wrong size!"); + + struct ParameterVersion2 { + /* 0x00 */ std::array inputs; + /* 0x06 */ std::array outputs; + /* 0x0C */ s16 channel_count_max; + /* 0x0E */ s16 channel_count; + /* 0x10 */ s32 sample_rate; + /* 0x14 */ f32 threshold; + /* 0x18 */ f32 compressor_ratio; + /* 0x1C */ s32 attack_time; + /* 0x20 */ s32 release_time; + /* 0x24 */ f32 unk_24; + /* 0x28 */ f32 unk_28; + /* 0x2C */ f32 unk_2C; + /* 0x30 */ f32 out_gain; + /* 0x34 */ ParameterState state; + /* 0x35 */ bool makeup_gain_enabled; + }; + static_assert(sizeof(ParameterVersion2) <= sizeof(EffectInfoBase::InParameterVersion2), + "CompressorInfo::ParameterVersion2 has the wrong size!"); + + struct State { + f32 unk_00; + f32 unk_04; + f32 unk_08; + f32 unk_0C; + f32 unk_10; + f32 unk_14; + f32 unk_18; + f32 makeup_gain; + f32 unk_20; + char unk_24[0x1C]; + }; + static_assert(sizeof(State) <= sizeof(EffectInfoBase::State), + "CompressorInfo::State has the wrong size!"); + + /** + * Update the info with new parameters, version 1. + * + * @param error_info - Used to write call result code. + * @param in_params - New parameters to update the info with. + * @param pool_mapper - Pool for mapping buffers. + */ + void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params, + const PoolMapper& pool_mapper) override; + + /** + * Update the info with new parameters, version 2. + * + * @param error_info - Used to write call result code. + * @param in_params - New parameters to update the info with. + * @param pool_mapper - Pool for mapping buffers. + */ + void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params, + const PoolMapper& pool_mapper) override; + + /** + * Update the info after command generation. Usually only changes its state. + */ + void UpdateForCommandGeneration() override; + + /** + * Get a workbuffer assigned to this effect with the given index. + * + * @param index - Workbuffer index. + * @return Address of the buffer. + */ + CpuAddr GetWorkbuffer(s32 index) override; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/effect/delay.cpp b/src/audio_core/renderer/effect/delay.cpp new file mode 100644 index 000000000..d9853efd9 --- /dev/null +++ b/src/audio_core/renderer/effect/delay.cpp @@ -0,0 +1,93 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/effect/delay.h" + +namespace AudioCore::AudioRenderer { + +void DelayInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params, + const PoolMapper& pool_mapper) { + auto in_specific{reinterpret_cast(in_params.specific.data())}; + auto params{reinterpret_cast(parameter.data())}; + + if (IsChannelCountValid(in_specific->channel_count_max)) { + const auto old_state{params->state}; + std::memcpy(params, in_specific, sizeof(ParameterVersion1)); + mix_id = in_params.mix_id; + process_order = in_params.process_order; + enabled = in_params.enabled; + + if (!IsChannelCountValid(in_specific->channel_count)) { + params->channel_count = params->channel_count_max; + } + + if (!IsChannelCountValid(in_specific->channel_count) || + old_state != ParameterState::Updated) { + params->state = old_state; + } + + if (buffer_unmapped || in_params.is_new) { + usage_state = UsageState::New; + params->state = ParameterState::Initialized; + buffer_unmapped = !pool_mapper.TryAttachBuffer( + error_info, workbuffers[0], in_params.workbuffer, in_params.workbuffer_size); + return; + } + } + error_info.error_code = ResultSuccess; + error_info.address = CpuAddr(0); +} + +void DelayInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params, + const PoolMapper& pool_mapper) { + auto in_specific{reinterpret_cast(in_params.specific.data())}; + auto params{reinterpret_cast(parameter.data())}; + + if (IsChannelCountValid(in_specific->channel_count_max)) { + const auto old_state{params->state}; + std::memcpy(params, in_specific, sizeof(ParameterVersion1)); + mix_id = in_params.mix_id; + process_order = in_params.process_order; + enabled = in_params.enabled; + + if (!IsChannelCountValid(in_specific->channel_count)) { + params->channel_count = params->channel_count_max; + } + + if (!IsChannelCountValid(in_specific->channel_count) || + old_state != ParameterState::Updated) { + params->state = old_state; + } + + if (buffer_unmapped || in_params.is_new) { + usage_state = UsageState::New; + params->state = ParameterState::Initialized; + buffer_unmapped = !pool_mapper.TryAttachBuffer( + error_info, workbuffers[0], in_params.workbuffer, in_params.workbuffer_size); + return; + } + } + error_info.error_code = ResultSuccess; + error_info.address = CpuAddr(0); +} + +void DelayInfo::UpdateForCommandGeneration() { + if (enabled) { + usage_state = UsageState::Enabled; + } else { + usage_state = UsageState::Disabled; + } + + auto params{reinterpret_cast(parameter.data())}; + params->state = ParameterState::Updated; +} + +void DelayInfo::InitializeResultState(EffectResultState& result_state) {} + +void DelayInfo::UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) {} + +CpuAddr DelayInfo::GetWorkbuffer(s32 index) { + return GetSingleBuffer(index); +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/effect/delay.h b/src/audio_core/renderer/effect/delay.h new file mode 100644 index 000000000..accc42a06 --- /dev/null +++ b/src/audio_core/renderer/effect/delay.h @@ -0,0 +1,135 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#include "audio_core/common/common.h" +#include "audio_core/renderer/effect/effect_info_base.h" +#include "common/common_types.h" +#include "common/fixed_point.h" + +namespace AudioCore::AudioRenderer { + +class DelayInfo : public EffectInfoBase { +public: + struct ParameterVersion1 { + /* 0x00 */ std::array inputs; + /* 0x06 */ std::array outputs; + /* 0x0C */ u16 channel_count_max; + /* 0x0E */ u16 channel_count; + /* 0x10 */ u32 delay_time_max; + /* 0x14 */ u32 delay_time; + /* 0x18 */ Common::FixedPoint<18, 14> sample_rate; + /* 0x1C */ Common::FixedPoint<18, 14> in_gain; + /* 0x20 */ Common::FixedPoint<18, 14> feedback_gain; + /* 0x24 */ Common::FixedPoint<18, 14> wet_gain; + /* 0x28 */ Common::FixedPoint<18, 14> dry_gain; + /* 0x2C */ Common::FixedPoint<18, 14> channel_spread; + /* 0x30 */ Common::FixedPoint<18, 14> lowpass_amount; + /* 0x34 */ ParameterState state; + }; + static_assert(sizeof(ParameterVersion1) <= sizeof(EffectInfoBase::InParameterVersion1), + "DelayInfo::ParameterVersion1 has the wrong size!"); + + struct ParameterVersion2 { + /* 0x00 */ std::array inputs; + /* 0x06 */ std::array outputs; + /* 0x0C */ s16 channel_count_max; + /* 0x0E */ s16 channel_count; + /* 0x10 */ s32 delay_time_max; + /* 0x14 */ s32 delay_time; + /* 0x18 */ s32 sample_rate; + /* 0x1C */ s32 in_gain; + /* 0x20 */ s32 feedback_gain; + /* 0x24 */ s32 wet_gain; + /* 0x28 */ s32 dry_gain; + /* 0x2C */ s32 channel_spread; + /* 0x30 */ s32 lowpass_amount; + /* 0x34 */ ParameterState state; + }; + static_assert(sizeof(ParameterVersion2) <= sizeof(EffectInfoBase::InParameterVersion2), + "DelayInfo::ParameterVersion2 has the wrong size!"); + + struct DelayLine { + Common::FixedPoint<50, 14> Read() const { + return buffer[buffer_pos]; + } + + void Write(const Common::FixedPoint<50, 14> value) { + buffer[buffer_pos] = value; + buffer_pos = static_cast((buffer_pos + 1) % buffer.size()); + } + + s32 sample_count_max{}; + s32 sample_count{}; + std::vector> buffer{}; + u32 buffer_pos{}; + Common::FixedPoint<18, 14> decay_rate{}; + }; + + struct State { + /* 0x000 */ std::array unk_000; + /* 0x020 */ std::array delay_lines; + /* 0x0B0 */ Common::FixedPoint<18, 14> feedback_gain; + /* 0x0B4 */ Common::FixedPoint<18, 14> delay_feedback_gain; + /* 0x0B8 */ Common::FixedPoint<18, 14> delay_feedback_cross_gain; + /* 0x0BC */ Common::FixedPoint<18, 14> lowpass_gain; + /* 0x0C0 */ Common::FixedPoint<18, 14> lowpass_feedback_gain; + /* 0x0C4 */ std::array, MaxChannels> lowpass_z; + }; + static_assert(sizeof(State) <= sizeof(EffectInfoBase::State), + "DelayInfo::State has the wrong size!"); + + /** + * Update the info with new parameters, version 1. + * + * @param error_info - Used to write call result code. + * @param in_params - New parameters to update the info with. + * @param pool_mapper - Pool for mapping buffers. + */ + void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params, + const PoolMapper& pool_mapper) override; + + /** + * Update the info with new parameters, version 2. + * + * @param error_info - Used to write call result code. + * @param in_params - New parameters to update the info with. + * @param pool_mapper - Pool for mapping buffers. + */ + void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params, + const PoolMapper& pool_mapper) override; + + /** + * Update the info after command generation. Usually only changes its state. + */ + void UpdateForCommandGeneration() override; + + /** + * Initialize a new result state. Version 2 only, unused. + * + * @param result_state - Result state to initialize. + */ + void InitializeResultState(EffectResultState& result_state) override; + + /** + * Update the host-side state with the ADSP-side state. Version 2 only, unused. + * + * @param cpu_state - Host-side result state to update. + * @param dsp_state - AudioRenderer-side result state to update from. + */ + void UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) override; + + /** + * Get a workbuffer assigned to this effect with the given index. + * + * @param index - Workbuffer index. + * @return Address of the buffer. + */ + CpuAddr GetWorkbuffer(s32 index) override; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/effect/effect_context.cpp b/src/audio_core/renderer/effect/effect_context.cpp new file mode 100644 index 000000000..74c7801c9 --- /dev/null +++ b/src/audio_core/renderer/effect/effect_context.cpp @@ -0,0 +1,41 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/effect/effect_context.h" + +namespace AudioCore::AudioRenderer { + +void EffectContext::Initialize(std::span effect_infos_, const u32 effect_count_, + std::span result_states_cpu_, + std::span result_states_dsp_, + const size_t dsp_state_count_) { + effect_infos = effect_infos_; + effect_count = effect_count_; + result_states_cpu = result_states_cpu_; + result_states_dsp = result_states_dsp_; + dsp_state_count = dsp_state_count_; +} + +EffectInfoBase& EffectContext::GetInfo(const u32 index) { + return effect_infos[index]; +} + +EffectResultState& EffectContext::GetResultState(const u32 index) { + return result_states_cpu[index]; +} + +EffectResultState& EffectContext::GetDspSharedResultState(const u32 index) { + return result_states_dsp[index]; +} + +u32 EffectContext::GetCount() const { + return effect_count; +} + +void EffectContext::UpdateStateByDspShared() { + for (size_t i = 0; i < dsp_state_count; i++) { + effect_infos[i].UpdateResultState(result_states_cpu[i], result_states_dsp[i]); + } +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/effect/effect_context.h b/src/audio_core/renderer/effect/effect_context.h new file mode 100644 index 000000000..85955bd9c --- /dev/null +++ b/src/audio_core/renderer/effect/effect_context.h @@ -0,0 +1,75 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "audio_core/renderer/effect/effect_info_base.h" +#include "audio_core/renderer/effect/effect_result_state.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { + +class EffectContext { +public: + /** + * Initialize the effect context + * @param effect_infos List of effect infos for this context + * @param effect_count The number of effects in the list + * @param result_states_cpu The workbuffer of result states for the CPU for this context + * @param result_states_dsp The workbuffer of result states for the DSP for this context + * @param state_count The number of result states + */ + void Initialize(std::span effect_infos_, const u32 effect_count_, + std::span result_states_cpu_, + std::span result_states_dsp_, const size_t dsp_state_count); + + /** + * Get the EffectInfo for a given index + * @param index Which effect to return + * @return Pointer to the effect + */ + EffectInfoBase& GetInfo(const u32 index); + + /** + * Get the CPU result state for a given index + * @param index Which result to return + * @return Pointer to the effect result state + */ + EffectResultState& GetResultState(const u32 index); + + /** + * Get the DSP result state for a given index + * @param index Which result to return + * @return Pointer to the effect result state + */ + EffectResultState& GetDspSharedResultState(const u32 index); + + /** + * Get the number of effects in this context + * @return The number of effects + */ + u32 GetCount() const; + + /** + * Update the CPU and DSP result states for all effects + */ + void UpdateStateByDspShared(); + +private: + /// Workbuffer for all of the effects + std::span effect_infos{}; + /// Number of effects in the workbuffer + u32 effect_count{}; + /// Workbuffer of states for all effects, kept host-side and not directly modified, dsp states + /// are copied here on the next render frame + std::span result_states_cpu{}; + /// Workbuffer of states for all effects, used by the AudioRenderer to track effect state + /// between calls + std::span result_states_dsp{}; + /// Number of result states in the workbuffers + size_t dsp_state_count{}; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/effect/effect_info_base.h b/src/audio_core/renderer/effect/effect_info_base.h new file mode 100644 index 000000000..43d0589cc --- /dev/null +++ b/src/audio_core/renderer/effect/effect_info_base.h @@ -0,0 +1,435 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "audio_core/common/common.h" +#include "audio_core/renderer/behavior/behavior_info.h" +#include "audio_core/renderer/effect/effect_result_state.h" +#include "audio_core/renderer/memory/address_info.h" +#include "audio_core/renderer/memory/pool_mapper.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +/** + * Base of all effects. Holds various data and functions used for all derived effects. + * Should not be used directly. + */ +class EffectInfoBase { +public: + enum class Type : u8 { + Invalid, + Mix, + Aux, + Delay, + Reverb, + I3dl2Reverb, + BiquadFilter, + LightLimiter, + Capture, + Compressor, + }; + + enum class UsageState { + Invalid, + New, + Enabled, + Disabled, + }; + + enum class OutStatus : u8 { + Invalid, + New, + Initialized, + Used, + Removed, + }; + + enum class ParameterState : u8 { + Initialized, + Updating, + Updated, + }; + + struct InParameterVersion1 { + /* 0x00 */ Type type; + /* 0x01 */ bool is_new; + /* 0x02 */ bool enabled; + /* 0x04 */ u32 mix_id; + /* 0x08 */ CpuAddr workbuffer; + /* 0x10 */ CpuAddr workbuffer_size; + /* 0x18 */ u32 process_order; + /* 0x1C */ char unk1C[0x4]; + /* 0x20 */ std::array specific; + }; + static_assert(sizeof(InParameterVersion1) == 0xC0, + "EffectInfoBase::InParameterVersion1 has the wrong size!"); + + struct InParameterVersion2 { + /* 0x00 */ Type type; + /* 0x01 */ bool is_new; + /* 0x02 */ bool enabled; + /* 0x04 */ u32 mix_id; + /* 0x08 */ CpuAddr workbuffer; + /* 0x10 */ CpuAddr workbuffer_size; + /* 0x18 */ u32 process_order; + /* 0x1C */ char unk1C[0x4]; + /* 0x20 */ std::array specific; + }; + static_assert(sizeof(InParameterVersion2) == 0xC0, + "EffectInfoBase::InParameterVersion2 has the wrong size!"); + + struct OutStatusVersion1 { + /* 0x00 */ OutStatus state; + /* 0x01 */ char unk01[0xF]; + }; + static_assert(sizeof(OutStatusVersion1) == 0x10, + "EffectInfoBase::OutStatusVersion1 has the wrong size!"); + + struct OutStatusVersion2 { + /* 0x00 */ OutStatus state; + /* 0x01 */ char unk01[0xF]; + /* 0x10 */ EffectResultState result_state; + }; + static_assert(sizeof(OutStatusVersion2) == 0x90, + "EffectInfoBase::OutStatusVersion2 has the wrong size!"); + + struct State { + std::array buffer; + }; + static_assert(sizeof(State) == 0x500, "EffectInfoBase::State has the wrong size!"); + + EffectInfoBase() { + Cleanup(); + } + + virtual ~EffectInfoBase() = default; + + /** + * Cleanup this effect, resetting it to a starting state. + */ + void Cleanup() { + type = Type::Invalid; + enabled = false; + mix_id = UnusedMixId; + process_order = InvalidProcessOrder; + buffer_unmapped = false; + parameter = {}; + for (auto& workbuffer : workbuffers) { + workbuffer.Setup(CpuAddr(0), 0); + } + } + + /** + * Forcibly unmap all assigned workbuffers from the AudioRenderer. + * + * @param pool_mapper - Mapper to unmap the buffers. + */ + void ForceUnmapBuffers(const PoolMapper& pool_mapper) { + for (auto& workbuffer : workbuffers) { + if (workbuffer.GetReference(false) != 0) { + pool_mapper.ForceUnmapPointer(workbuffer); + } + } + } + + /** + * Check if this effect is enabled. + * + * @return True if effect is enabled, otherwise false. + */ + bool IsEnabled() const { + return enabled; + } + + /** + * Check if this effect should not be generated. + * + * @return True if effect should be skipped, otherwise false. + */ + bool ShouldSkip() const { + return buffer_unmapped; + } + + /** + * Get the type of this effect. + * + * @return The type of this effect. See EffectInfoBase::Type + */ + Type GetType() const { + return type; + } + + /** + * Set the type of this effect. + * + * @param type_ - The new type of this effect. + */ + void SetType(const Type type_) { + type = type_; + } + + /** + * Get the mix id of this effect. + * + * @return Mix id of this effect. + */ + s32 GetMixId() const { + return mix_id; + } + + /** + * Get the processing order of this effect. + * + * @return Process order of this effect. + */ + s32 GetProcessingOrder() const { + return process_order; + } + + /** + * Get this effect's parameter data. + * + * @return Pointer to the parametter, must be cast to the correct type. + */ + u8* GetParameter() { + return parameter.data(); + } + + /** + * Get this effect's parameter data. + * + * @return Pointer to the parametter, must be cast to the correct type. + */ + u8* GetStateBuffer() { + return state.data(); + } + + /** + * Set this effect's usage state. + * + * @param usage - new usage state of this effect. + */ + void SetUsage(const UsageState usage) { + usage_state = usage; + } + + /** + * Check if this effects need to have its workbuffer information updated. + * Version 1. + * + * @param params - Input parameters. + * @return True if workbuffers need updating, otherwise false. + */ + bool ShouldUpdateWorkBufferInfo(const InParameterVersion1& params) const { + return buffer_unmapped || params.is_new; + } + + /** + * Check if this effects need to have its workbuffer information updated. + * Version 2. + * + * @param params - Input parameters. + * @return True if workbuffers need updating, otherwise false. + */ + bool ShouldUpdateWorkBufferInfo(const InParameterVersion2& params) const { + return buffer_unmapped || params.is_new; + } + + /** + * Get the current usage state of this effect. + * + * @return The current usage state. + */ + UsageState GetUsage() const { + return usage_state; + } + + /** + * Write the current state. Version 1. + * + * @param out_status - Status to write. + * @param renderer_active - Is the AudioRenderer active? + */ + void StoreStatus(OutStatusVersion1& out_status, const bool renderer_active) const { + if (renderer_active) { + if (usage_state != UsageState::Disabled) { + out_status.state = OutStatus::Used; + } else { + out_status.state = OutStatus::Removed; + } + } else if (usage_state == UsageState::New) { + out_status.state = OutStatus::Used; + } else { + out_status.state = OutStatus::Removed; + } + } + + /** + * Write the current state. Version 2. + * + * @param out_status - Status to write. + * @param renderer_active - Is the AudioRenderer active? + */ + void StoreStatus(OutStatusVersion2& out_status, const bool renderer_active) const { + if (renderer_active) { + if (usage_state != UsageState::Disabled) { + out_status.state = OutStatus::Used; + } else { + out_status.state = OutStatus::Removed; + } + } else if (usage_state == UsageState::New) { + out_status.state = OutStatus::Used; + } else { + out_status.state = OutStatus::Removed; + } + } + + /** + * Update the info with new parameters, version 1. + * + * @param error_info - Used to write call result code. + * @param in_params - New parameters to update the info with. + * @param pool_mapper - Pool for mapping buffers. + */ + virtual void Update(BehaviorInfo::ErrorInfo& error_info, + [[maybe_unused]] const InParameterVersion1& params, + [[maybe_unused]] const PoolMapper& pool_mapper) { + error_info.error_code = ResultSuccess; + error_info.address = CpuAddr(0); + } + + /** + * Update the info with new parameters, version 2. + * + * @param error_info - Used to write call result code. + * @param in_params - New parameters to update the info with. + * @param pool_mapper - Pool for mapping buffers. + */ + virtual void Update(BehaviorInfo::ErrorInfo& error_info, + [[maybe_unused]] const InParameterVersion2& params, + [[maybe_unused]] const PoolMapper& pool_mapper) { + error_info.error_code = ResultSuccess; + error_info.address = CpuAddr(0); + } + + /** + * Update the info after command generation. Usually only changes its state. + */ + virtual void UpdateForCommandGeneration() {} + + /** + * Initialize a new result state. Version 2 only, unused. + * + * @param result_state - Result state to initialize. + */ + virtual void InitializeResultState([[maybe_unused]] EffectResultState& result_state) {} + + /** + * Update the host-side state with the ADSP-side state. Version 2 only, unused. + * + * @param cpu_state - Host-side result state to update. + * @param dsp_state - AudioRenderer-side result state to update from. + */ + virtual void UpdateResultState([[maybe_unused]] EffectResultState& cpu_state, + [[maybe_unused]] EffectResultState& dsp_state) {} + + /** + * Get a workbuffer assigned to this effect with the given index. + * + * @param index - Workbuffer index. + * @return Address of the buffer. + */ + virtual CpuAddr GetWorkbuffer([[maybe_unused]] s32 index) { + return 0; + } + + /** + * Get the first workbuffer assigned to this effect. + * + * @param index - Workbuffer index. Unused. + * @return Address of the buffer. + */ + CpuAddr GetSingleBuffer([[maybe_unused]] const s32 index) { + if (enabled) { + return workbuffers[0].GetReference(true); + } + + if (usage_state != UsageState::Disabled) { + const auto ref{workbuffers[0].GetReference(false)}; + const auto size{workbuffers[0].GetSize()}; + if (ref != 0 && size > 0) { + // Invalidate DSP cache + } + } + return 0; + } + + /** + * Get the send buffer info, used by Aux and Capture. + * + * @return Address of the buffer info. + */ + CpuAddr GetSendBufferInfo() const { + return send_buffer_info; + } + + /** + * Get the send buffer, used by Aux and Capture. + * + * @return Address of the buffer. + */ + CpuAddr GetSendBuffer() const { + return send_buffer; + } + + /** + * Get the return buffer info, used by Aux and Capture. + * + * @return Address of the buffer info. + */ + CpuAddr GetReturnBufferInfo() const { + return return_buffer_info; + } + + /** + * Get the return buffer, used by Aux and Capture. + * + * @return Address of the buffer. + */ + CpuAddr GetReturnBuffer() const { + return return_buffer; + } + +protected: + /// Type of this effect. May be changed + Type type{Type::Invalid}; + /// Is this effect enabled? + bool enabled{}; + /// Are this effect's buffers unmapped? + bool buffer_unmapped{}; + /// Current usage state + UsageState usage_state{UsageState::Invalid}; + /// Mix id of this effect + s32 mix_id{UnusedMixId}; + /// Process order of this effect + s32 process_order{InvalidProcessOrder}; + /// Workbuffers assigned to this effect + std::array workbuffers{AddressInfo(CpuAddr(0), 0), AddressInfo(CpuAddr(0), 0)}; + /// Aux/Capture buffer info for reading + CpuAddr send_buffer_info; + /// Aux/Capture buffer for reading + CpuAddr send_buffer; + /// Aux/Capture buffer info for writing + CpuAddr return_buffer_info; + /// Aux/Capture buffer for writing + CpuAddr return_buffer; + /// Parameters of this effect + std::array parameter{}; + /// State of this effect used by the AudioRenderer across calls + std::array state{}; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/effect/effect_reset.h b/src/audio_core/renderer/effect/effect_reset.h new file mode 100644 index 000000000..1ea67e334 --- /dev/null +++ b/src/audio_core/renderer/effect/effect_reset.h @@ -0,0 +1,71 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#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/compressor.h" +#include "audio_core/renderer/effect/delay.h" +#include "audio_core/renderer/effect/i3dl2.h" +#include "audio_core/renderer/effect/light_limiter.h" +#include "audio_core/renderer/effect/reverb.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +/** + * Reset an effect, and create a new one of the given type. + * + * @param effect - Effect to reset and re-construct. + * @param type - Type of the new effect to create. + */ +static void ResetEffect(EffectInfoBase* effect, const EffectInfoBase::Type type) { + *effect = {}; + + switch (type) { + case EffectInfoBase::Type::Invalid: + std::construct_at(effect); + effect->SetType(EffectInfoBase::Type::Invalid); + break; + case EffectInfoBase::Type::Mix: + std::construct_at(reinterpret_cast(effect)); + effect->SetType(EffectInfoBase::Type::Mix); + break; + case EffectInfoBase::Type::Aux: + std::construct_at(reinterpret_cast(effect)); + effect->SetType(EffectInfoBase::Type::Aux); + break; + case EffectInfoBase::Type::Delay: + std::construct_at(reinterpret_cast(effect)); + effect->SetType(EffectInfoBase::Type::Delay); + break; + case EffectInfoBase::Type::Reverb: + std::construct_at(reinterpret_cast(effect)); + effect->SetType(EffectInfoBase::Type::Reverb); + break; + case EffectInfoBase::Type::I3dl2Reverb: + std::construct_at(reinterpret_cast(effect)); + effect->SetType(EffectInfoBase::Type::I3dl2Reverb); + break; + case EffectInfoBase::Type::BiquadFilter: + std::construct_at(reinterpret_cast(effect)); + effect->SetType(EffectInfoBase::Type::BiquadFilter); + break; + case EffectInfoBase::Type::LightLimiter: + std::construct_at(reinterpret_cast(effect)); + effect->SetType(EffectInfoBase::Type::LightLimiter); + break; + case EffectInfoBase::Type::Capture: + std::construct_at(reinterpret_cast(effect)); + effect->SetType(EffectInfoBase::Type::Capture); + break; + case EffectInfoBase::Type::Compressor: + std::construct_at(reinterpret_cast(effect)); + effect->SetType(EffectInfoBase::Type::Compressor); + break; + } +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/effect/effect_result_state.h b/src/audio_core/renderer/effect/effect_result_state.h new file mode 100644 index 000000000..ae096ad69 --- /dev/null +++ b/src/audio_core/renderer/effect/effect_result_state.h @@ -0,0 +1,16 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { + +struct EffectResultState { + std::array state; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/effect/i3dl2.cpp b/src/audio_core/renderer/effect/i3dl2.cpp new file mode 100644 index 000000000..960b29cfc --- /dev/null +++ b/src/audio_core/renderer/effect/i3dl2.cpp @@ -0,0 +1,94 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/effect/i3dl2.h" + +namespace AudioCore::AudioRenderer { + +void I3dl2ReverbInfo::Update(BehaviorInfo::ErrorInfo& error_info, + const InParameterVersion1& in_params, const PoolMapper& pool_mapper) { + auto in_specific{reinterpret_cast(in_params.specific.data())}; + auto params{reinterpret_cast(parameter.data())}; + + if (IsChannelCountValid(in_specific->channel_count_max)) { + const auto old_state{params->state}; + std::memcpy(params, in_specific, sizeof(ParameterVersion1)); + mix_id = in_params.mix_id; + process_order = in_params.process_order; + enabled = in_params.enabled; + + if (!IsChannelCountValid(in_specific->channel_count)) { + params->channel_count = params->channel_count_max; + } + + if (!IsChannelCountValid(in_specific->channel_count) || + old_state != ParameterState::Updated) { + params->state = old_state; + } + + if (buffer_unmapped || in_params.is_new) { + usage_state = UsageState::New; + params->state = ParameterState::Initialized; + buffer_unmapped = !pool_mapper.TryAttachBuffer( + error_info, workbuffers[0], in_params.workbuffer, in_params.workbuffer_size); + return; + } + } + error_info.error_code = ResultSuccess; + error_info.address = CpuAddr(0); +} + +void I3dl2ReverbInfo::Update(BehaviorInfo::ErrorInfo& error_info, + const InParameterVersion2& in_params, const PoolMapper& pool_mapper) { + auto in_specific{reinterpret_cast(in_params.specific.data())}; + auto params{reinterpret_cast(parameter.data())}; + + if (IsChannelCountValid(in_specific->channel_count_max)) { + const auto old_state{params->state}; + std::memcpy(params, in_specific, sizeof(ParameterVersion1)); + mix_id = in_params.mix_id; + process_order = in_params.process_order; + enabled = in_params.enabled; + + if (!IsChannelCountValid(in_specific->channel_count)) { + params->channel_count = params->channel_count_max; + } + + if (!IsChannelCountValid(in_specific->channel_count) || + old_state != ParameterState::Updated) { + params->state = old_state; + } + + if (buffer_unmapped || in_params.is_new) { + usage_state = UsageState::New; + params->state = ParameterState::Initialized; + buffer_unmapped = !pool_mapper.TryAttachBuffer( + error_info, workbuffers[0], in_params.workbuffer, in_params.workbuffer_size); + return; + } + } + error_info.error_code = ResultSuccess; + error_info.address = CpuAddr(0); +} + +void I3dl2ReverbInfo::UpdateForCommandGeneration() { + if (enabled) { + usage_state = UsageState::Enabled; + } else { + usage_state = UsageState::Disabled; + } + + auto params{reinterpret_cast(parameter.data())}; + params->state = ParameterState::Updated; +} + +void I3dl2ReverbInfo::InitializeResultState(EffectResultState& result_state) {} + +void I3dl2ReverbInfo::UpdateResultState(EffectResultState& cpu_state, + EffectResultState& dsp_state) {} + +CpuAddr I3dl2ReverbInfo::GetWorkbuffer(s32 index) { + return GetSingleBuffer(index); +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/effect/i3dl2.h b/src/audio_core/renderer/effect/i3dl2.h new file mode 100644 index 000000000..7a088a627 --- /dev/null +++ b/src/audio_core/renderer/effect/i3dl2.h @@ -0,0 +1,200 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#include "audio_core/common/common.h" +#include "audio_core/renderer/effect/effect_info_base.h" +#include "common/common_types.h" +#include "common/fixed_point.h" + +namespace AudioCore::AudioRenderer { + +class I3dl2ReverbInfo : public EffectInfoBase { +public: + struct ParameterVersion1 { + /* 0x00 */ std::array inputs; + /* 0x06 */ std::array outputs; + /* 0x0C */ u16 channel_count_max; + /* 0x0E */ u16 channel_count; + /* 0x10 */ char unk10[0x4]; + /* 0x14 */ u32 sample_rate; + /* 0x18 */ f32 room_HF_gain; + /* 0x1C */ f32 reference_HF; + /* 0x20 */ f32 late_reverb_decay_time; + /* 0x24 */ f32 late_reverb_HF_decay_ratio; + /* 0x28 */ f32 room_gain; + /* 0x2C */ f32 reflection_gain; + /* 0x30 */ f32 reverb_gain; + /* 0x34 */ f32 late_reverb_diffusion; + /* 0x38 */ f32 reflection_delay; + /* 0x3C */ f32 late_reverb_delay_time; + /* 0x40 */ f32 late_reverb_density; + /* 0x44 */ f32 dry_gain; + /* 0x48 */ ParameterState state; + /* 0x49 */ char unk49[0x3]; + }; + static_assert(sizeof(ParameterVersion1) <= sizeof(EffectInfoBase::InParameterVersion1), + "I3dl2ReverbInfo::ParameterVersion1 has the wrong size!"); + + struct ParameterVersion2 { + /* 0x00 */ std::array inputs; + /* 0x06 */ std::array outputs; + /* 0x0C */ u16 channel_count_max; + /* 0x0E */ u16 channel_count; + /* 0x10 */ char unk10[0x4]; + /* 0x14 */ u32 sample_rate; + /* 0x18 */ f32 room_HF_gain; + /* 0x1C */ f32 reference_HF; + /* 0x20 */ f32 late_reverb_decay_time; + /* 0x24 */ f32 late_reverb_HF_decay_ratio; + /* 0x28 */ f32 room_gain; + /* 0x2C */ f32 reflection_gain; + /* 0x30 */ f32 reverb_gain; + /* 0x34 */ f32 late_reverb_diffusion; + /* 0x38 */ f32 reflection_delay; + /* 0x3C */ f32 late_reverb_delay_time; + /* 0x40 */ f32 late_reverb_density; + /* 0x44 */ f32 dry_gain; + /* 0x48 */ ParameterState state; + /* 0x49 */ char unk49[0x3]; + }; + static_assert(sizeof(ParameterVersion2) <= sizeof(EffectInfoBase::InParameterVersion2), + "I3dl2ReverbInfo::ParameterVersion2 has the wrong size!"); + + static constexpr u32 MaxDelayLines = 4; + static constexpr u32 MaxDelayTaps = 20; + + struct I3dl2DelayLine { + void Initialize(const s32 delay_time) { + max_delay = delay_time; + buffer.resize(delay_time + 1, 0); + buffer_end = &buffer[delay_time]; + output = &buffer[0]; + SetDelay(delay_time); + wet_gain = 0.0f; + } + + void SetDelay(const s32 delay_time) { + if (max_delay < delay_time) { + return; + } + delay = delay_time; + input = &buffer[(output - buffer.data() + delay) % (max_delay + 1)]; + } + + Common::FixedPoint<50, 14> Tick(const Common::FixedPoint<50, 14> sample) { + Write(sample); + + auto out_sample{Read()}; + + output++; + if (output >= buffer_end) { + output = buffer.data(); + } + + return out_sample; + } + + Common::FixedPoint<50, 14> Read() { + return *output; + } + + void Write(const Common::FixedPoint<50, 14> sample) { + *(input++) = sample; + if (input >= buffer_end) { + input = buffer.data(); + } + } + + Common::FixedPoint<50, 14> TapOut(const s32 index) { + auto out{input - (index + 1)}; + if (out < buffer.data()) { + out += max_delay + 1; + } + return *out; + } + + std::vector> buffer{}; + Common::FixedPoint<50, 14>* buffer_end{}; + s32 max_delay{}; + Common::FixedPoint<50, 14>* input{}; + Common::FixedPoint<50, 14>* output{}; + s32 delay{}; + f32 wet_gain{}; + }; + + struct State { + f32 lowpass_0; + f32 lowpass_1; + f32 lowpass_2; + I3dl2DelayLine early_delay_line; + std::array early_tap_steps; + f32 early_gain; + f32 late_gain; + s32 early_to_late_taps; + std::array fdn_delay_lines; + std::array decay_delay_lines0; + std::array decay_delay_lines1; + f32 last_reverb_echo; + I3dl2DelayLine center_delay_line; + std::array, MaxDelayLines> lowpass_coeff; + std::array shelf_filter; + f32 dry_gain; + }; + static_assert(sizeof(State) <= sizeof(EffectInfoBase::State), + "I3dl2ReverbInfo::State is too large!"); + + /** + * Update the info with new parameters, version 1. + * + * @param error_info - Used to write call result code. + * @param in_params - New parameters to update the info with. + * @param pool_mapper - Pool for mapping buffers. + */ + void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params, + const PoolMapper& pool_mapper) override; + + /** + * Update the info with new parameters, version 2. + * + * @param error_info - Used to write call result code. + * @param in_params - New parameters to update the info with. + * @param pool_mapper - Pool for mapping buffers. + */ + void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params, + const PoolMapper& pool_mapper) override; + + /** + * Update the info after command generation. Usually only changes its state. + */ + void UpdateForCommandGeneration() override; + + /** + * Initialize a new result state. Version 2 only, unused. + * + * @param result_state - Result state to initialize. + */ + void InitializeResultState(EffectResultState& result_state) override; + + /** + * Update the host-side state with the ADSP-side state. Version 2 only, unused. + * + * @param cpu_state - Host-side result state to update. + * @param dsp_state - AudioRenderer-side result state to update from. + */ + void UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) override; + + /** + * Get a workbuffer assigned to this effect with the given index. + * + * @param index - Workbuffer index. + * @return Address of the buffer. + */ + CpuAddr GetWorkbuffer(s32 index) override; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/effect/light_limiter.cpp b/src/audio_core/renderer/effect/light_limiter.cpp new file mode 100644 index 000000000..1635a952d --- /dev/null +++ b/src/audio_core/renderer/effect/light_limiter.cpp @@ -0,0 +1,81 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/effect/light_limiter.h" + +namespace AudioCore::AudioRenderer { + +void LightLimiterInfo::Update(BehaviorInfo::ErrorInfo& error_info, + const InParameterVersion1& in_params, const PoolMapper& pool_mapper) { + auto in_specific{reinterpret_cast(in_params.specific.data())}; + auto params{reinterpret_cast(parameter.data())}; + + std::memcpy(params, in_specific, sizeof(ParameterVersion1)); + mix_id = in_params.mix_id; + process_order = in_params.process_order; + enabled = in_params.enabled; + + if (buffer_unmapped || in_params.is_new) { + usage_state = UsageState::New; + params->state = ParameterState::Initialized; + buffer_unmapped = !pool_mapper.TryAttachBuffer( + error_info, workbuffers[0], in_params.workbuffer, in_params.workbuffer_size); + } else { + error_info.error_code = ResultSuccess; + error_info.address = CpuAddr(0); + } +} + +void LightLimiterInfo::Update(BehaviorInfo::ErrorInfo& error_info, + const InParameterVersion2& in_params, const PoolMapper& pool_mapper) { + auto in_specific{reinterpret_cast(in_params.specific.data())}; + auto params{reinterpret_cast(parameter.data())}; + + std::memcpy(params, in_specific, sizeof(ParameterVersion1)); + mix_id = in_params.mix_id; + process_order = in_params.process_order; + enabled = in_params.enabled; + + if (buffer_unmapped || in_params.is_new) { + usage_state = UsageState::New; + params->state = ParameterState::Initialized; + buffer_unmapped = !pool_mapper.TryAttachBuffer( + error_info, workbuffers[0], in_params.workbuffer, in_params.workbuffer_size); + } else { + error_info.error_code = ResultSuccess; + error_info.address = CpuAddr(0); + } +} + +void LightLimiterInfo::UpdateForCommandGeneration() { + if (enabled) { + usage_state = UsageState::Enabled; + } else { + usage_state = UsageState::Disabled; + } + + auto params{reinterpret_cast(parameter.data())}; + params->state = ParameterState::Updated; + params->statistics_reset_required = false; +} + +void LightLimiterInfo::InitializeResultState(EffectResultState& result_state) { + auto result_state_{reinterpret_cast(result_state.state.data())}; + + result_state_->channel_max_sample.fill(0); + result_state_->channel_compression_gain_min.fill(1.0f); +} + +void LightLimiterInfo::UpdateResultState(EffectResultState& cpu_state, + EffectResultState& dsp_state) { + auto cpu_statistics{reinterpret_cast(cpu_state.state.data())}; + auto dsp_statistics{reinterpret_cast(dsp_state.state.data())}; + + *cpu_statistics = *dsp_statistics; +} + +CpuAddr LightLimiterInfo::GetWorkbuffer(s32 index) { + return GetSingleBuffer(index); +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/effect/light_limiter.h b/src/audio_core/renderer/effect/light_limiter.h new file mode 100644 index 000000000..338d67bbc --- /dev/null +++ b/src/audio_core/renderer/effect/light_limiter.h @@ -0,0 +1,138 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#include "audio_core/common/common.h" +#include "audio_core/renderer/effect/effect_info_base.h" +#include "common/common_types.h" +#include "common/fixed_point.h" + +namespace AudioCore::AudioRenderer { + +class LightLimiterInfo : public EffectInfoBase { +public: + enum class ProcessingMode { + Mode0, + Mode1, + }; + + struct ParameterVersion1 { + /* 0x00 */ std::array inputs; + /* 0x06 */ std::array outputs; + /* 0x0C */ u16 channel_count_max; + /* 0x0E */ u16 channel_count; + /* 0x0C */ u32 sample_rate; + /* 0x14 */ s32 look_ahead_time_max; + /* 0x18 */ s32 attack_time; + /* 0x1C */ s32 release_time; + /* 0x20 */ s32 look_ahead_time; + /* 0x24 */ f32 attack_coeff; + /* 0x28 */ f32 release_coeff; + /* 0x2C */ f32 threshold; + /* 0x30 */ f32 input_gain; + /* 0x34 */ f32 output_gain; + /* 0x38 */ s32 look_ahead_samples_min; + /* 0x3C */ s32 look_ahead_samples_max; + /* 0x40 */ ParameterState state; + /* 0x41 */ bool statistics_enabled; + /* 0x42 */ bool statistics_reset_required; + /* 0x43 */ ProcessingMode processing_mode; + }; + static_assert(sizeof(ParameterVersion1) <= sizeof(EffectInfoBase::InParameterVersion1), + "LightLimiterInfo::ParameterVersion1 has the wrong size!"); + + struct ParameterVersion2 { + /* 0x00 */ std::array inputs; + /* 0x06 */ std::array outputs; + /* 0x0C */ u16 channel_count_max; + /* 0x0E */ u16 channel_count; + /* 0x0C */ u32 sample_rate; + /* 0x14 */ s32 look_ahead_time_max; + /* 0x18 */ s32 attack_time; + /* 0x1C */ s32 release_time; + /* 0x20 */ s32 look_ahead_time; + /* 0x24 */ f32 attack_coeff; + /* 0x28 */ f32 release_coeff; + /* 0x2C */ f32 threshold; + /* 0x30 */ f32 input_gain; + /* 0x34 */ f32 output_gain; + /* 0x38 */ s32 look_ahead_samples_min; + /* 0x3C */ s32 look_ahead_samples_max; + /* 0x40 */ ParameterState state; + /* 0x41 */ bool statistics_enabled; + /* 0x42 */ bool statistics_reset_required; + /* 0x43 */ ProcessingMode processing_mode; + }; + static_assert(sizeof(ParameterVersion2) <= sizeof(EffectInfoBase::InParameterVersion2), + "LightLimiterInfo::ParameterVersion2 has the wrong size!"); + + struct State { + std::array, MaxChannels> samples_average; + std::array, MaxChannels> compression_gain; + std::array look_ahead_sample_offsets; + std::array>, MaxChannels> look_ahead_sample_buffers; + }; + static_assert(sizeof(State) <= sizeof(EffectInfoBase::State), + "LightLimiterInfo::State has the wrong size!"); + + struct StatisticsInternal { + /* 0x00 */ std::array channel_max_sample; + /* 0x18 */ std::array channel_compression_gain_min; + }; + static_assert(sizeof(StatisticsInternal) == 0x30, + "LightLimiterInfo::StatisticsInternal has the wrong size!"); + + /** + * Update the info with new parameters, version 1. + * + * @param error_info - Used to write call result code. + * @param in_params - New parameters to update the info with. + * @param pool_mapper - Pool for mapping buffers. + */ + void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params, + const PoolMapper& pool_mapper) override; + + /** + * Update the info with new parameters, version 2. + * + * @param error_info - Used to write call result code. + * @param in_params - New parameters to update the info with. + * @param pool_mapper - Pool for mapping buffers. + */ + void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params, + const PoolMapper& pool_mapper) override; + + /** + * Update the info after command generation. Usually only changes its state. + */ + void UpdateForCommandGeneration() override; + + /** + * Initialize a new limiter statistics result state. Version 2 only. + * + * @param result_state - Result state to initialize. + */ + void InitializeResultState(EffectResultState& result_state) override; + + /** + * Update the host-side limiter statistics with the ADSP-side one. Version 2 only. + * + * @param cpu_state - Host-side result state to update. + * @param dsp_state - AudioRenderer-side result state to update from. + */ + void UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) override; + + /** + * Get a workbuffer assigned to this effect with the given index. + * + * @param index - Workbuffer index. + * @return Address of the buffer. + */ + CpuAddr GetWorkbuffer(s32 index) override; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/effect/reverb.cpp b/src/audio_core/renderer/effect/reverb.cpp new file mode 100644 index 000000000..2d32383d0 --- /dev/null +++ b/src/audio_core/renderer/effect/reverb.cpp @@ -0,0 +1,93 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/effect/reverb.h" + +namespace AudioCore::AudioRenderer { + +void ReverbInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params, + const PoolMapper& pool_mapper) { + auto in_specific{reinterpret_cast(in_params.specific.data())}; + auto params{reinterpret_cast(parameter.data())}; + + if (IsChannelCountValid(in_specific->channel_count_max)) { + const auto old_state{params->state}; + std::memcpy(params, in_specific, sizeof(ParameterVersion1)); + mix_id = in_params.mix_id; + process_order = in_params.process_order; + enabled = in_params.enabled; + + if (!IsChannelCountValid(in_specific->channel_count)) { + params->channel_count = params->channel_count_max; + } + + if (!IsChannelCountValid(in_specific->channel_count) || + old_state != ParameterState::Updated) { + params->state = old_state; + } + + if (buffer_unmapped || in_params.is_new) { + usage_state = UsageState::New; + params->state = ParameterState::Initialized; + buffer_unmapped = !pool_mapper.TryAttachBuffer( + error_info, workbuffers[0], in_params.workbuffer, in_params.workbuffer_size); + return; + } + } + error_info.error_code = ResultSuccess; + error_info.address = CpuAddr(0); +} + +void ReverbInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params, + const PoolMapper& pool_mapper) { + auto in_specific{reinterpret_cast(in_params.specific.data())}; + auto params{reinterpret_cast(parameter.data())}; + + if (IsChannelCountValid(in_specific->channel_count_max)) { + const auto old_state{params->state}; + std::memcpy(params, in_specific, sizeof(ParameterVersion2)); + mix_id = in_params.mix_id; + process_order = in_params.process_order; + enabled = in_params.enabled; + + if (!IsChannelCountValid(in_specific->channel_count)) { + params->channel_count = params->channel_count_max; + } + + if (!IsChannelCountValid(in_specific->channel_count) || + old_state != ParameterState::Updated) { + params->state = old_state; + } + + if (buffer_unmapped || in_params.is_new) { + usage_state = UsageState::New; + params->state = ParameterState::Initialized; + buffer_unmapped = !pool_mapper.TryAttachBuffer( + error_info, workbuffers[0], in_params.workbuffer, in_params.workbuffer_size); + return; + } + } + error_info.error_code = ResultSuccess; + error_info.address = CpuAddr(0); +} + +void ReverbInfo::UpdateForCommandGeneration() { + if (enabled) { + usage_state = UsageState::Enabled; + } else { + usage_state = UsageState::Disabled; + } + + auto params{reinterpret_cast(parameter.data())}; + params->state = ParameterState::Updated; +} + +void ReverbInfo::InitializeResultState(EffectResultState& result_state) {} + +void ReverbInfo::UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) {} + +CpuAddr ReverbInfo::GetWorkbuffer(s32 index) { + return GetSingleBuffer(index); +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/effect/reverb.h b/src/audio_core/renderer/effect/reverb.h new file mode 100644 index 000000000..b4df9f6ef --- /dev/null +++ b/src/audio_core/renderer/effect/reverb.h @@ -0,0 +1,190 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#include "audio_core/common/common.h" +#include "audio_core/renderer/effect/effect_info_base.h" +#include "common/common_types.h" +#include "common/fixed_point.h" + +namespace AudioCore::AudioRenderer { + +class ReverbInfo : public EffectInfoBase { +public: + struct ParameterVersion1 { + /* 0x00 */ std::array inputs; + /* 0x06 */ std::array outputs; + /* 0x0C */ u16 channel_count_max; + /* 0x0E */ u16 channel_count; + /* 0x10 */ u32 sample_rate; + /* 0x14 */ u32 early_mode; + /* 0x18 */ s32 early_gain; + /* 0x1C */ s32 pre_delay; + /* 0x20 */ s32 late_mode; + /* 0x24 */ s32 late_gain; + /* 0x28 */ s32 decay_time; + /* 0x2C */ s32 high_freq_Decay_ratio; + /* 0x30 */ s32 colouration; + /* 0x34 */ s32 base_gain; + /* 0x38 */ s32 wet_gain; + /* 0x3C */ s32 dry_gain; + /* 0x40 */ ParameterState state; + }; + static_assert(sizeof(ParameterVersion1) <= sizeof(EffectInfoBase::InParameterVersion1), + "ReverbInfo::ParameterVersion1 has the wrong size!"); + + struct ParameterVersion2 { + /* 0x00 */ std::array inputs; + /* 0x06 */ std::array outputs; + /* 0x0C */ u16 channel_count_max; + /* 0x0E */ u16 channel_count; + /* 0x10 */ u32 sample_rate; + /* 0x14 */ u32 early_mode; + /* 0x18 */ s32 early_gain; + /* 0x1C */ s32 pre_delay; + /* 0x20 */ s32 late_mode; + /* 0x24 */ s32 late_gain; + /* 0x28 */ s32 decay_time; + /* 0x2C */ s32 high_freq_decay_ratio; + /* 0x30 */ s32 colouration; + /* 0x34 */ s32 base_gain; + /* 0x38 */ s32 wet_gain; + /* 0x3C */ s32 dry_gain; + /* 0x40 */ ParameterState state; + }; + static_assert(sizeof(ParameterVersion2) <= sizeof(EffectInfoBase::InParameterVersion2), + "ReverbInfo::ParameterVersion2 has the wrong size!"); + + static constexpr u32 MaxDelayLines = 4; + static constexpr u32 MaxDelayTaps = 10; + static constexpr u32 NumEarlyModes = 5; + static constexpr u32 NumLateModes = 5; + + struct ReverbDelayLine { + void Initialize(const s32 delay_time, const f32 decay_rate) { + buffer.resize(delay_time + 1, 0); + buffer_end = &buffer[delay_time]; + output = &buffer[0]; + decay = decay_rate; + sample_count_max = delay_time; + SetDelay(delay_time); + } + + void SetDelay(const s32 delay_time) { + if (sample_count_max < delay_time) { + return; + } + sample_count = delay_time; + input = &buffer[(output - buffer.data() + sample_count) % (sample_count_max + 1)]; + } + + Common::FixedPoint<50, 14> Tick(const Common::FixedPoint<50, 14> sample) { + Write(sample); + + auto out_sample{Read()}; + + output++; + if (output >= buffer_end) { + output = buffer.data(); + } + + return out_sample; + } + + Common::FixedPoint<50, 14> Read() { + return *output; + } + + void Write(const Common::FixedPoint<50, 14> sample) { + *(input++) = sample; + if (input >= buffer_end) { + input = buffer.data(); + } + } + + Common::FixedPoint<50, 14> TapOut(const s32 index) { + auto out{input - (index + 1)}; + if (out < buffer.data()) { + out += sample_count; + } + return *out; + } + + s32 sample_count{}; + s32 sample_count_max{}; + std::vector> buffer{}; + Common::FixedPoint<50, 14>* buffer_end; + Common::FixedPoint<50, 14>* input{}; + Common::FixedPoint<50, 14>* output{}; + Common::FixedPoint<50, 14> decay{}; + }; + + struct State { + ReverbDelayLine pre_delay_line; + ReverbDelayLine center_delay_line; + std::array early_delay_times; + std::array, MaxDelayTaps> early_gains; + s32 pre_delay_time; + std::array decay_delay_lines; + std::array fdn_delay_lines; + std::array, MaxDelayLines> hf_decay_gain; + std::array, MaxDelayLines> hf_decay_prev_gain; + std::array, MaxDelayLines> prev_feedback_output; + }; + static_assert(sizeof(State) <= sizeof(EffectInfoBase::State), + "ReverbInfo::State is too large!"); + + /** + * Update the info with new parameters, version 1. + * + * @param error_info - Used to write call result code. + * @param in_params - New parameters to update the info with. + * @param pool_mapper - Pool for mapping buffers. + */ + void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params, + const PoolMapper& pool_mapper) override; + + /** + * Update the info with new parameters, version 2. + * + * @param error_info - Used to write call result code. + * @param in_params - New parameters to update the info with. + * @param pool_mapper - Pool for mapping buffers. + */ + void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params, + const PoolMapper& pool_mapper) override; + + /** + * Update the info after command generation. Usually only changes its state. + */ + void UpdateForCommandGeneration() override; + + /** + * Initialize a new result state. Version 2 only, unused. + * + * @param result_state - Result state to initialize. + */ + void InitializeResultState(EffectResultState& result_state) override; + + /** + * Update the host-side state with the ADSP-side state. Version 2 only, unused. + * + * @param cpu_state - Host-side result state to update. + * @param dsp_state - AudioRenderer-side result state to update from. + */ + void UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) override; + + /** + * Get a workbuffer assigned to this effect with the given index. + * + * @param index - Workbuffer index. + * @return Address of the buffer. + */ + CpuAddr GetWorkbuffer(s32 index) override; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/memory/address_info.h b/src/audio_core/renderer/memory/address_info.h new file mode 100644 index 000000000..4cfefea8e --- /dev/null +++ b/src/audio_core/renderer/memory/address_info.h @@ -0,0 +1,125 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "audio_core/renderer/memory/memory_pool_info.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { + +/** + * Represents a region of mapped or unmapped memory. + */ +class AddressInfo { +public: + AddressInfo() = default; + AddressInfo(CpuAddr cpu_address_, u64 size_) : cpu_address{cpu_address_}, size{size_} {} + + /** + * Setup a new AddressInfo. + * + * @param cpu_address - The CPU address of this region. + * @param size - The size of this region. + */ + void Setup(CpuAddr cpu_address_, u64 size_) { + cpu_address = cpu_address_; + size = size_; + memory_pool = nullptr; + dsp_address = 0; + } + + /** + * Get the CPU address. + * + * @return The CpuAddr address + */ + CpuAddr GetCpuAddr() const { + return cpu_address; + } + + /** + * Assign this region to a memory pool. + * + * @param memory_pool_ - Memory pool to assign. + * @return The CpuAddr address of this region. + */ + void SetPool(MemoryPoolInfo* memory_pool_) { + memory_pool = memory_pool_; + } + + /** + * Get the size of this region. + * + * @return The size of this region. + */ + u64 GetSize() const { + return size; + } + + /** + * Get the ADSP address for this region. + * + * @return The ADSP address for this region. + */ + CpuAddr GetForceMappedDspAddr() const { + return dsp_address; + } + + /** + * Set the ADSP address for this region. + * + * @param dsp_addr - The new ADSP address for this region. + */ + void SetForceMappedDspAddr(CpuAddr dsp_addr) { + dsp_address = dsp_addr; + } + + /** + * Check whether this region has an active memory pool. + * + * @return True if this region has a mapped memory pool, otherwise false. + */ + bool HasMappedMemoryPool() const { + return memory_pool != nullptr && memory_pool->GetDspAddress() != 0; + } + + /** + * Check whether this region is mapped to the ADSP. + * + * @return True if this region is mapped, otherwise false. + */ + bool IsMapped() const { + return HasMappedMemoryPool() || dsp_address != 0; + } + + /** + * Get a usable reference to this region of memory. + * + * @param mark_in_use - Whether this region should be marked as being in use. + * @return A valid memory address if valid, otherwise 0. + */ + CpuAddr GetReference(bool mark_in_use) { + if (!HasMappedMemoryPool()) { + return dsp_address; + } + + if (mark_in_use) { + memory_pool->SetUsed(true); + } + + return memory_pool->Translate(cpu_address, size); + } + +private: + /// CPU address of this region + CpuAddr cpu_address; + /// Size of this region + u64 size; + /// The memory this region is mapped to + MemoryPoolInfo* memory_pool; + /// ADSP address of this region + CpuAddr dsp_address; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/memory/memory_pool_info.cpp b/src/audio_core/renderer/memory/memory_pool_info.cpp new file mode 100644 index 000000000..9b7824af1 --- /dev/null +++ b/src/audio_core/renderer/memory/memory_pool_info.cpp @@ -0,0 +1,61 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/memory/memory_pool_info.h" + +namespace AudioCore::AudioRenderer { + +CpuAddr MemoryPoolInfo::GetCpuAddress() const { + return cpu_address; +} + +CpuAddr MemoryPoolInfo::GetDspAddress() const { + return dsp_address; +} + +u64 MemoryPoolInfo::GetSize() const { + return size; +} + +MemoryPoolInfo::Location MemoryPoolInfo::GetLocation() const { + return location; +} + +void MemoryPoolInfo::SetCpuAddress(const CpuAddr address, const u64 size_) { + cpu_address = address; + size = size_; +} + +void MemoryPoolInfo::SetDspAddress(const CpuAddr address) { + dsp_address = address; +} + +bool MemoryPoolInfo::Contains(const CpuAddr address_, const u64 size_) const { + return cpu_address <= address_ && (address_ + size_) <= (cpu_address + size); +} + +bool MemoryPoolInfo::IsMapped() const { + return dsp_address != 0; +} + +CpuAddr MemoryPoolInfo::Translate(const CpuAddr address, const u64 size_) const { + if (!Contains(address, size_)) { + return 0; + } + + if (!IsMapped()) { + return 0; + } + + return dsp_address + (address - cpu_address); +} + +void MemoryPoolInfo::SetUsed(const bool used) { + in_use = used; +} + +bool MemoryPoolInfo::IsUsed() const { + return in_use; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/memory/memory_pool_info.h b/src/audio_core/renderer/memory/memory_pool_info.h new file mode 100644 index 000000000..537a466ec --- /dev/null +++ b/src/audio_core/renderer/memory/memory_pool_info.h @@ -0,0 +1,170 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "audio_core/common/common.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +/** + * CPU pools are mapped in user memory with the supplied process_handle (see PoolMapper). + */ +class MemoryPoolInfo { +public: + /** + * The location of this pool. + * CPU pools are mapped in user memory with the supplied process_handle (see PoolMapper). + * DSP pools are mapped in the current process sysmodule. + */ + enum class Location { + CPU = 1, + DSP = 2, + }; + + /** + * Current state of the pool + */ + enum class State { + Invalid, + Aquired, + RequestDetach, + Detached, + RequestAttach, + Attached, + Released, + }; + + /** + * Result code for updating the pool (See InfoUpdater::Update) + */ + enum class ResultState { + Success, + BadParam, + MapFailed, + InUse, + }; + + /** + * Input parameters coming from the game which are used to update current pools + * (See InfoUpdater::Update) + */ + struct InParameter { + /* 0x00 */ u64 address; + /* 0x08 */ u64 size; + /* 0x10 */ State state; + /* 0x14 */ bool in_use; + /* 0x18 */ char unk18[0x8]; + }; + static_assert(sizeof(InParameter) == 0x20, "MemoryPoolInfo::InParameter has the wrong size!"); + + /** + * Output status sent back to the game on update (See InfoUpdater::Update) + */ + struct OutStatus { + /* 0x00 */ State state; + /* 0x04 */ char unk04[0xC]; + }; + static_assert(sizeof(OutStatus) == 0x10, "MemoryPoolInfo::OutStatus has the wrong size!"); + + MemoryPoolInfo() = default; + MemoryPoolInfo(Location location_) : location{location_} {} + + /** + * Get the CPU address for this pool. + * + * @return The CPU address of this pool. + */ + CpuAddr GetCpuAddress() const; + + /** + * Get the DSP address for this pool. + * + * @return The DSP address of this pool. + */ + CpuAddr GetDspAddress() const; + + /** + * Get the size of this pool. + * + * @return The size of this pool. + */ + u64 GetSize() const; + + /** + * Get the location of this pool. + * + * @return The location for the pool (see MemoryPoolInfo::Location). + */ + Location GetLocation() const; + + /** + * Set the CPU address for this pool. + * + * @param address - The new CPU address for this pool. + * @param size - The new size for this pool. + */ + void SetCpuAddress(CpuAddr address, u64 size); + + /** + * Set the DSP address for this pool. + * + * @param address - The new DSP address for this pool. + */ + void SetDspAddress(CpuAddr address); + + /** + * Check whether the pool contains a given range. + * + * @param address - The buffer address to look for. + * @param size - The size of the given buffer. + * @return True if the range is within this pool, otherwise false. + */ + bool Contains(CpuAddr address, u64 size) const; + + /** + * Check whether this pool is mapped, which is when the dsp address is set. + * + * @return True if the pool is mapped, otherwise false. + */ + bool IsMapped() const; + + /** + * Translates a given CPU range into a relative offset for the DSP. + * + * @param address - The buffer address to look for. + * @param size - The size of the given buffer. + * @return Pointer to the DSP-mapped memory. + */ + CpuAddr Translate(CpuAddr address, u64 size) const; + + /** + * Set or unset whether this memory pool is in use. + * + * @param used - Use state for this pool. + */ + void SetUsed(bool used); + + /** + * Get whether this pool is in use. + * + * @return True if in use, otherwise false. + */ + bool IsUsed() const; + +private: + /// Base address for the CPU-side memory + CpuAddr cpu_address{}; + /// Base address for the DSP-side memory + CpuAddr dsp_address{}; + /// Size of this pool + u64 size{}; + /// Location of this pool, either CPU or DSP + Location location{Location::DSP}; + /// If this pool is in use + bool in_use{}; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/memory/pool_mapper.cpp b/src/audio_core/renderer/memory/pool_mapper.cpp new file mode 100644 index 000000000..2baf2ce08 --- /dev/null +++ b/src/audio_core/renderer/memory/pool_mapper.cpp @@ -0,0 +1,243 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/memory/address_info.h" +#include "audio_core/renderer/memory/pool_mapper.h" +#include "core/hle/kernel/k_process.h" +#include "core/hle/kernel/svc.h" + +namespace AudioCore::AudioRenderer { + +PoolMapper::PoolMapper(u32 process_handle_, bool force_map_) + : process_handle{process_handle_}, force_map{force_map_} {} + +PoolMapper::PoolMapper(u32 process_handle_, std::span pool_infos_, u32 pool_count_, + bool force_map_) + : process_handle{process_handle_}, pool_infos{pool_infos_.data()}, + pool_count{pool_count_}, force_map{force_map_} {} + +void PoolMapper::ClearUseState(std::span pools, const u32 count) { + for (u32 i = 0; i < count; i++) { + pools[i].SetUsed(false); + } +} + +MemoryPoolInfo* PoolMapper::FindMemoryPool(MemoryPoolInfo* pools, const u64 count, + const CpuAddr address, const u64 size) const { + auto pool{pools}; + for (u64 i = 0; i < count; i++, pool++) { + if (pool->Contains(address, size)) { + return pool; + } + } + return nullptr; +} + +MemoryPoolInfo* PoolMapper::FindMemoryPool(const CpuAddr address, const u64 size) const { + auto pool{pool_infos}; + for (u64 i = 0; i < pool_count; i++, pool++) { + if (pool->Contains(address, size)) { + return pool; + } + } + return nullptr; +} + +bool PoolMapper::FillDspAddr(AddressInfo& address_info, MemoryPoolInfo* pools, + const u32 count) const { + if (address_info.GetCpuAddr() == 0) { + address_info.SetPool(nullptr); + return false; + } + + auto found_pool{ + FindMemoryPool(pools, count, address_info.GetCpuAddr(), address_info.GetSize())}; + if (found_pool != nullptr) { + address_info.SetPool(found_pool); + return true; + } + + if (force_map) { + address_info.SetForceMappedDspAddr(address_info.GetCpuAddr()); + } else { + address_info.SetPool(nullptr); + } + + return false; +} + +bool PoolMapper::FillDspAddr(AddressInfo& address_info) const { + if (address_info.GetCpuAddr() == 0) { + address_info.SetPool(nullptr); + return false; + } + + auto found_pool{FindMemoryPool(address_info.GetCpuAddr(), address_info.GetSize())}; + if (found_pool != nullptr) { + address_info.SetPool(found_pool); + return true; + } + + if (force_map) { + address_info.SetForceMappedDspAddr(address_info.GetCpuAddr()); + } else { + address_info.SetPool(nullptr); + } + + return false; +} + +bool PoolMapper::TryAttachBuffer(BehaviorInfo::ErrorInfo& error_info, AddressInfo& address_info, + const CpuAddr address, const u64 size) const { + address_info.Setup(address, size); + + if (!FillDspAddr(address_info)) { + error_info.error_code = Service::Audio::ERR_POOL_MAPPING_FAILED; + error_info.address = address; + return force_map; + } + + error_info.error_code = ResultSuccess; + error_info.address = CpuAddr(0); + return true; +} + +bool PoolMapper::IsForceMapEnabled() const { + return force_map; +} + +u32 PoolMapper::GetProcessHandle(const MemoryPoolInfo* pool) const { + switch (pool->GetLocation()) { + case MemoryPoolInfo::Location::CPU: + return process_handle; + case MemoryPoolInfo::Location::DSP: + return Kernel::Svc::CurrentProcess; + } + LOG_WARNING(Service_Audio, "Invalid MemoryPoolInfo location!"); + return Kernel::Svc::CurrentProcess; +} + +bool PoolMapper::Map([[maybe_unused]] const u32 handle, [[maybe_unused]] const CpuAddr cpu_addr, + [[maybe_unused]] const u64 size) const { + // nn::audio::dsp::MapUserPointer(handle, cpu_addr, size); + return true; +} + +bool PoolMapper::Map(MemoryPoolInfo& pool) const { + switch (pool.GetLocation()) { + case MemoryPoolInfo::Location::CPU: + // Map with process_handle + pool.SetDspAddress(pool.GetCpuAddress()); + return true; + case MemoryPoolInfo::Location::DSP: + // Map with Kernel::Svc::CurrentProcess + pool.SetDspAddress(pool.GetCpuAddress()); + return true; + default: + LOG_WARNING(Service_Audio, "Invalid MemoryPoolInfo location={}!", + static_cast(pool.GetLocation())); + return false; + } +} + +bool PoolMapper::Unmap([[maybe_unused]] const u32 handle, [[maybe_unused]] const CpuAddr cpu_addr, + [[maybe_unused]] const u64 size) const { + // nn::audio::dsp::UnmapUserPointer(handle, cpu_addr, size); + return true; +} + +bool PoolMapper::Unmap(MemoryPoolInfo& pool) const { + [[maybe_unused]] u32 handle{0}; + + switch (pool.GetLocation()) { + case MemoryPoolInfo::Location::CPU: + handle = process_handle; + break; + case MemoryPoolInfo::Location::DSP: + handle = Kernel::Svc::CurrentProcess; + break; + } + // nn::audio::dsp::UnmapUserPointer(handle, pool->cpu_address, pool->size); + pool.SetCpuAddress(0, 0); + pool.SetDspAddress(0); + return true; +} + +void PoolMapper::ForceUnmapPointer(const AddressInfo& address_info) const { + if (force_map) { + [[maybe_unused]] auto found_pool{ + FindMemoryPool(address_info.GetCpuAddr(), address_info.GetSize())}; + // nn::audio::dsp::UnmapUserPointer(this->processHandle, address_info.GetCpuAddr(), 0); + } +} + +MemoryPoolInfo::ResultState PoolMapper::Update(MemoryPoolInfo& pool, + const MemoryPoolInfo::InParameter& in_params, + MemoryPoolInfo::OutStatus& out_params) const { + if (in_params.state != MemoryPoolInfo::State::RequestAttach && + in_params.state != MemoryPoolInfo::State::RequestDetach) { + return MemoryPoolInfo::ResultState::Success; + } + + if (in_params.address == 0 || in_params.size == 0 || !Common::Is4KBAligned(in_params.address) || + !Common::Is4KBAligned(in_params.size)) { + return MemoryPoolInfo::ResultState::BadParam; + } + + switch (in_params.state) { + case MemoryPoolInfo::State::RequestAttach: + pool.SetCpuAddress(in_params.address, in_params.size); + + Map(pool); + + if (pool.IsMapped()) { + out_params.state = MemoryPoolInfo::State::Attached; + return MemoryPoolInfo::ResultState::Success; + } + pool.SetCpuAddress(0, 0); + return MemoryPoolInfo::ResultState::MapFailed; + + case MemoryPoolInfo::State::RequestDetach: + if (pool.GetCpuAddress() != in_params.address || pool.GetSize() != in_params.size) { + return MemoryPoolInfo::ResultState::BadParam; + } + + if (pool.IsUsed()) { + return MemoryPoolInfo::ResultState::InUse; + } + + Unmap(pool); + + pool.SetCpuAddress(0, 0); + pool.SetDspAddress(0); + out_params.state = MemoryPoolInfo::State::Detached; + return MemoryPoolInfo::ResultState::Success; + + default: + LOG_ERROR(Service_Audio, "Invalid MemoryPoolInfo::State!"); + break; + } + + return MemoryPoolInfo::ResultState::Success; +} + +bool PoolMapper::InitializeSystemPool(MemoryPoolInfo& pool, const u8* memory, + const u64 size_) const { + switch (pool.GetLocation()) { + case MemoryPoolInfo::Location::CPU: + return false; + case MemoryPoolInfo::Location::DSP: + pool.SetCpuAddress(reinterpret_cast(memory), size_); + if (Map(Kernel::Svc::CurrentProcess, reinterpret_cast(memory), size_)) { + pool.SetDspAddress(pool.GetCpuAddress()); + return true; + } + return false; + default: + LOG_WARNING(Service_Audio, "Invalid MemoryPoolInfo location={}!", + static_cast(pool.GetLocation())); + return false; + } +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/memory/pool_mapper.h b/src/audio_core/renderer/memory/pool_mapper.h new file mode 100644 index 000000000..9a691da7a --- /dev/null +++ b/src/audio_core/renderer/memory/pool_mapper.h @@ -0,0 +1,179 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "audio_core/renderer/behavior/behavior_info.h" +#include "audio_core/renderer/memory/memory_pool_info.h" +#include "common/common_types.h" +#include "core/hle/service/audio/errors.h" + +namespace AudioCore::AudioRenderer { +class AddressInfo; + +/** + * Utility functions for managing MemoryPoolInfos + */ +class PoolMapper { +public: + explicit PoolMapper(u32 process_handle, bool force_map); + explicit PoolMapper(u32 process_handle, std::span pool_infos, u32 pool_count, + bool force_map); + + /** + * Clear the usage state for all given pools. + * + * @param pools - The memory pools to clear. + * @param count - The number of pools. + */ + static void ClearUseState(std::span pools, u32 count); + + /** + * Find the memory pool containing the given address and size from a given list of pools. + * + * @param pools - The memory pools to search within. + * @param count - The number of pools. + * @param address - The address of the region to find. + * @param size - The size of the region to find. + * @return Pointer to the memory pool if found, otherwise nullptr. + */ + MemoryPoolInfo* FindMemoryPool(MemoryPoolInfo* pools, u64 count, CpuAddr address, + u64 size) const; + + /** + * Find the memory pool containing the given address and size from the PoolMapper's memory pool. + * + * @param address - The address of the region to find. + * @param size - The size of the region to find. + * @return Pointer to the memory pool if found, otherwise nullptr. + */ + MemoryPoolInfo* FindMemoryPool(CpuAddr address, u64 size) const; + + /** + * Set the PoolMapper's memory pool to one in the given list of pools, which contains + * address_info. + * + * @param address_info - The expected region to find within pools. + * @param pools - The list of pools to search within. + * @param count - The number of pools given. + * @return True if successfully mapped, otherwise false. + */ + bool FillDspAddr(AddressInfo& address_info, MemoryPoolInfo* pools, u32 count) const; + + /** + * Set the PoolMapper's memory pool to the one containing address_info. + * + * @param address_info - The address to find the memory pool for. + * @return True if successfully mapped, otherwise false. + */ + bool FillDspAddr(AddressInfo& address_info) const; + + /** + * Try to attach a {address, size} region to the given address_info, and map it. Fills in the + * given error_info and address_info. + * + * @param error_info - Output error info. + * @param address_info - Output address info, initialized with the given {address, size} and + * attempted to map. + * @param address - Address of the region to map. + * @param size - Size of the region to map. + * @return True if successfully attached, otherwise false. + */ + bool TryAttachBuffer(BehaviorInfo::ErrorInfo& error_info, AddressInfo& address_info, + CpuAddr address, u64 size) const; + + /** + * Return whether force mapping is enabled. + * + * @return True if force mapping is enabled, otherwise false. + */ + bool IsForceMapEnabled() const; + + /** + * Get the process handle, depending on location. + * + * @param pool - The pool to check the location of. + * @return CurrentProcessHandle if location == DSP, + * the PoolMapper's process_handle if location == CPU + */ + u32 GetProcessHandle(const MemoryPoolInfo* pool) const; + + /** + * Map the given region with the given handle. This is a no-op. + * + * @param handle - The process handle to map to. + * @param cpu_addr - Address to map. + * @param size - Size to map. + * @return True if successfully mapped, otherwise false. + */ + bool Map(u32 handle, CpuAddr cpu_addr, u64 size) const; + + /** + * Map the given memory pool. + * + * @param pool - The pool to map. + * @return True if successfully mapped, otherwise false. + */ + bool Map(MemoryPoolInfo& pool) const; + + /** + * Unmap the given region with the given handle. + * + * @param handle - The process handle to unmap to. + * @param cpu_addr - Address to unmap. + * @param size - Size to unmap. + * @return True if successfully unmapped, otherwise false. + */ + bool Unmap(u32 handle, CpuAddr cpu_addr, u64 size) const; + + /** + * Unmap the given memory pool. + * + * @param pool - The pool to unmap. + * @return True if successfully unmapped, otherwise false. + */ + bool Unmap(MemoryPoolInfo& pool) const; + + /** + * Forcibly unmap the given region. + * + * @param address_info - The region to unmap. + */ + void ForceUnmapPointer(const AddressInfo& address_info) const; + + /** + * Update the given memory pool. + * + * @param pool - Pool to update. + * @param in_params - Input parameters for the update. + * @param out_params - Output parameters for the update. + * @return The result of the update. See MemoryPoolInfo::ResultState + */ + MemoryPoolInfo::ResultState Update(MemoryPoolInfo& pool, + const MemoryPoolInfo::InParameter& in_params, + MemoryPoolInfo::OutStatus& out_params) const; + + /** + * Initialize the PoolMapper's memory pool. + * + * @param pool - Input pool to initialize. + * @param memory - Pointer to the memory region for the pool. + * @param size - Size of the memory region for the pool. + * @return True if initialized successfully, otherwise false. + */ + bool InitializeSystemPool(MemoryPoolInfo& pool, const u8* memory, u64 size) const; + +private: + /// Process handle for this mapper, used when location == CPU + u32 process_handle; + /// List of memory pools assigned to this mapper + MemoryPoolInfo* pool_infos{}; + /// The number of pools + u64 pool_count{}; + /// Is forced mapping enabled + bool force_map; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/mix/mix_context.cpp b/src/audio_core/renderer/mix/mix_context.cpp new file mode 100644 index 000000000..2427c83ed --- /dev/null +++ b/src/audio_core/renderer/mix/mix_context.cpp @@ -0,0 +1,141 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include + +#include "audio_core/renderer/mix/mix_context.h" +#include "audio_core/renderer/splitter/splitter_context.h" + +namespace AudioCore::AudioRenderer { + +void MixContext::Initialize(std::span sorted_mix_infos_, std::span mix_infos_, + const u32 count_, std::span effect_process_order_buffer_, + const u32 effect_count_, std::span node_states_workbuffer, + const u64 node_buffer_size, std::span edge_matrix_workbuffer, + const u64 edge_matrix_size) { + count = count_; + sorted_mix_infos = sorted_mix_infos_; + mix_infos = mix_infos_; + effect_process_order_buffer = effect_process_order_buffer_; + effect_count = effect_count_; + + if (node_states_workbuffer.size() > 0 && edge_matrix_workbuffer.size() > 0) { + node_states.Initialize(node_states_workbuffer, node_buffer_size, count); + edge_matrix.Initialize(edge_matrix_workbuffer, edge_matrix_size, count); + } + + for (s32 i = 0; i < count; i++) { + sorted_mix_infos[i] = &mix_infos[i]; + } +} + +MixInfo* MixContext::GetSortedInfo(const s32 index) { + return sorted_mix_infos[index]; +} + +void MixContext::SetSortedInfo(const s32 index, MixInfo& mix_info) { + sorted_mix_infos[index] = &mix_info; +} + +MixInfo* MixContext::GetInfo(const s32 index) { + return &mix_infos[index]; +} + +MixInfo* MixContext::GetFinalMixInfo() { + return &mix_infos[0]; +} + +s32 MixContext::GetCount() const { + return count; +} + +void MixContext::UpdateDistancesFromFinalMix() { + for (s32 i = 0; i < count; i++) { + mix_infos[i].distance_from_final_mix = InvalidDistanceFromFinalMix; + } + + for (s32 i = 0; i < count; i++) { + auto& mix_info{mix_infos[i]}; + sorted_mix_infos[i] = &mix_info; + + if (!mix_info.in_use) { + continue; + } + + auto mix_id{mix_info.mix_id}; + auto distance_to_final_mix{FinalMixId}; + + while (distance_to_final_mix < count) { + if (mix_id == FinalMixId) { + break; + } + + if (mix_id == UnusedMixId) { + distance_to_final_mix = InvalidDistanceFromFinalMix; + break; + } + + auto distance_from_final_mix{mix_infos[mix_id].distance_from_final_mix}; + if (distance_from_final_mix != InvalidDistanceFromFinalMix) { + distance_to_final_mix = distance_from_final_mix + 1; + break; + } + + distance_to_final_mix++; + mix_id = mix_infos[mix_id].dst_mix_id; + } + + if (distance_to_final_mix >= count) { + distance_to_final_mix = InvalidDistanceFromFinalMix; + } + mix_info.distance_from_final_mix = distance_to_final_mix; + } +} + +void MixContext::SortInfo() { + UpdateDistancesFromFinalMix(); + + std::ranges::sort(sorted_mix_infos, [](const MixInfo* lhs, const MixInfo* rhs) { + return lhs->distance_from_final_mix > rhs->distance_from_final_mix; + }); + + CalcMixBufferOffset(); +} + +void MixContext::CalcMixBufferOffset() { + s16 offset{0}; + for (s32 i = 0; i < count; i++) { + auto mix_info{sorted_mix_infos[i]}; + if (mix_info->in_use) { + const auto buffer_count{mix_info->buffer_count}; + mix_info->buffer_offset = offset; + offset += buffer_count; + } + } +} + +bool MixContext::TSortInfo(const SplitterContext& splitter_context) { + if (!splitter_context.UsingSplitter()) { + CalcMixBufferOffset(); + return true; + } + + if (!node_states.Tsort(edge_matrix)) { + return false; + } + + std::vector sorted_results{node_states.GetSortedResuls()}; + const auto result_size{std::min(count, static_cast(sorted_results.size()))}; + for (s32 i = 0; i < result_size; i++) { + sorted_mix_infos[i] = &mix_infos[sorted_results[i]]; + } + + CalcMixBufferOffset(); + return true; +} + +EdgeMatrix& MixContext::GetEdgeMatrix() { + return edge_matrix; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/mix/mix_context.h b/src/audio_core/renderer/mix/mix_context.h new file mode 100644 index 000000000..da3aa2829 --- /dev/null +++ b/src/audio_core/renderer/mix/mix_context.h @@ -0,0 +1,124 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "audio_core/renderer/mix/mix_info.h" +#include "audio_core/renderer/nodes/edge_matrix.h" +#include "audio_core/renderer/nodes/node_states.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +class SplitterContext; + +/* + * Manages mixing states, sorting and building a node graph to describe a mix order. + */ +class MixContext { +public: + /** + * Initialize the mix context. + * + * @param sorted_mix_infos - Buffer for the sorted mix infos. + * @param mix_infos - Buffer for the mix infos. + * @param effect_process_order_buffer - Buffer for the effect process orders. + * @param effect_count - Number of effects in the buffer. + * @param node_states_workbuffer - Buffer for node states. + * @param node_buffer_size - Size of the node states buffer. + * @param edge_matrix_workbuffer - Buffer for edge matrix. + * @param edge_matrix_size - Size of the edge matrix buffer. + */ + void Initialize(std::span sorted_mix_infos, std::span mix_infos, u32 count_, + std::span effect_process_order_buffer, u32 effect_count, + std::span node_states_workbuffer, u64 node_buffer_size, + std::span edge_matrix_workbuffer, u64 edge_matrix_size); + + /** + * Get a sorted mix at the given index. + * + * @param index - Index of sorted mix. + * @return The sorted mix. + */ + MixInfo* GetSortedInfo(s32 index); + + /** + * Set the sorted info at the given index. + * + * @param index - Index of sorted mix. + * @param mix_info - The new mix for this index. + */ + void SetSortedInfo(s32 index, MixInfo& mix_info); + + /** + * Get a mix at the given index. + * + * @param index - Index of mix. + * @return The mix. + */ + MixInfo* GetInfo(s32 index); + + /** + * Get the final mix. + * + * @return The final mix. + */ + MixInfo* GetFinalMixInfo(); + + /** + * Get the current number of mixes. + * + * @return The number of active mixes. + */ + s32 GetCount() const; + + /** + * Update all of the mixes' distance from the final mix. + * Needs to be called after altering the mix graph. + */ + void UpdateDistancesFromFinalMix(); + + /** + * Non-splitter sort, sorts the sorted mixes based on their distance from the final mix. + */ + void SortInfo(); + + /** + * Re-calculate the mix buffer offsets for each mix after altering the mix. + */ + void CalcMixBufferOffset(); + + /** + * Splitter sort, traverse the splitter node graph and sort the sorted mixes from results. + * + * @param splitter_context - Splitter context for the sort. + * @return True if the sort was successful, othewise false. + */ + bool TSortInfo(const SplitterContext& splitter_context); + + /** + * Get the edge matrix used for the mix graph. + * + * @return The edge matrix used. + */ + EdgeMatrix& GetEdgeMatrix(); + +private: + /// Array of sorted mixes + std::span sorted_mix_infos{}; + /// Array of mixes + std::span mix_infos{}; + /// Number of active mixes + s32 count{}; + /// Array of effect process orderings + std::span effect_process_order_buffer{}; + /// Number of effects in the process ordering buffer + u64 effect_count{}; + /// Node states used in splitter sort + NodeStates node_states{}; + /// Edge matrix for connected nodes used in splitter sort + EdgeMatrix edge_matrix{}; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/mix/mix_info.cpp b/src/audio_core/renderer/mix/mix_info.cpp new file mode 100644 index 000000000..cc18e57ee --- /dev/null +++ b/src/audio_core/renderer/mix/mix_info.cpp @@ -0,0 +1,120 @@ +// 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/effect/effect_context.h" +#include "audio_core/renderer/mix/mix_info.h" +#include "audio_core/renderer/nodes/edge_matrix.h" +#include "audio_core/renderer/splitter/splitter_context.h" + +namespace AudioCore::AudioRenderer { + +MixInfo::MixInfo(std::span effect_order_buffer_, s32 effect_count_, BehaviorInfo& behavior) + : effect_order_buffer{effect_order_buffer_}, effect_count{effect_count_}, + long_size_pre_delay_supported{behavior.IsLongSizePreDelaySupported()} { + ClearEffectProcessingOrder(); +} + +void MixInfo::Cleanup() { + mix_id = UnusedMixId; + dst_mix_id = UnusedMixId; + dst_splitter_id = UnusedSplitterId; +} + +void MixInfo::ClearEffectProcessingOrder() { + for (s32 i = 0; i < effect_count; i++) { + effect_order_buffer[i] = -1; + } +} + +bool MixInfo::Update(EdgeMatrix& edge_matrix, const InParameter& in_params, + EffectContext& effect_context, SplitterContext& splitter_context, + const BehaviorInfo& behavior) { + volume = in_params.volume; + sample_rate = in_params.sample_rate; + buffer_count = static_cast(in_params.buffer_count); + in_use = in_params.in_use; + mix_id = in_params.mix_id; + node_id = in_params.node_id; + mix_volumes = in_params.mix_volumes; + + bool sort_required{false}; + if (behavior.IsSplitterSupported()) { + sort_required = UpdateConnection(edge_matrix, in_params, splitter_context); + } else { + if (dst_mix_id != in_params.dest_mix_id) { + dst_mix_id = in_params.dest_mix_id; + sort_required = true; + } + dst_splitter_id = UnusedSplitterId; + } + + ClearEffectProcessingOrder(); + + // Check all effects, and set their order if they belong to this mix. + const auto count{effect_context.GetCount()}; + for (u32 i = 0; i < count; i++) { + const auto& info{effect_context.GetInfo(i)}; + if (mix_id == info.GetMixId()) { + const auto processing_order{info.GetProcessingOrder()}; + if (processing_order > effect_count) { + break; + } + effect_order_buffer[processing_order] = i; + } + } + + return sort_required; +} + +bool MixInfo::UpdateConnection(EdgeMatrix& edge_matrix, const InParameter& in_params, + SplitterContext& splitter_context) { + auto has_new_connection{false}; + if (dst_splitter_id != UnusedSplitterId) { + auto& splitter_info{splitter_context.GetInfo(dst_splitter_id)}; + has_new_connection = splitter_info.HasNewConnection(); + } + + // Check if this mix matches the input parameters. + // If everything is the same, don't bother updating. + if (dst_mix_id == in_params.dest_mix_id && dst_splitter_id == in_params.dest_splitter_id && + !has_new_connection) { + return false; + } + + // Reset the mix in the graph, as we're about to update it. + edge_matrix.RemoveEdges(mix_id); + + if (in_params.dest_mix_id == UnusedMixId) { + if (in_params.dest_splitter_id != UnusedSplitterId) { + // If the splitter is used, connect this mix to each active destination. + auto& splitter_info{splitter_context.GetInfo(in_params.dest_splitter_id)}; + auto const destination_count{splitter_info.GetDestinationCount()}; + + for (u32 i = 0; i < destination_count; i++) { + auto destination{ + splitter_context.GetDesintationData(in_params.dest_splitter_id, i)}; + + if (destination) { + const auto destination_id{destination->GetMixId()}; + if (destination_id != UnusedMixId) { + edge_matrix.Connect(mix_id, destination_id); + } + } + } + } + } else { + // If the splitter is not used, only connect this mix to its destination. + edge_matrix.Connect(mix_id, in_params.dest_mix_id); + } + + dst_mix_id = in_params.dest_mix_id; + dst_splitter_id = in_params.dest_splitter_id; + return true; +} + +bool MixInfo::HasAnyConnection() const { + return dst_mix_id != UnusedMixId || dst_splitter_id != UnusedSplitterId; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/mix/mix_info.h b/src/audio_core/renderer/mix/mix_info.h new file mode 100644 index 000000000..b5fa4c0c7 --- /dev/null +++ b/src/audio_core/renderer/mix/mix_info.h @@ -0,0 +1,124 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#include "audio_core/common/common.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +class EdgeMatrix; +class SplitterContext; +class EffectContext; +class BehaviorInfo; + +/** + * A single mix, which may feed through other mixes in a chain until reaching the final output mix. + */ +class MixInfo { +public: + struct InParameter { + /* 0x000 */ f32 volume; + /* 0x004 */ u32 sample_rate; + /* 0x008 */ u32 buffer_count; + /* 0x00C */ bool in_use; + /* 0x00D */ bool is_dirty; + /* 0x010 */ s32 mix_id; + /* 0x014 */ u32 effect_count; + /* 0x018 */ s32 node_id; + /* 0x01C */ char unk01C[0x8]; + /* 0x024 */ std::array, MaxMixBuffers> mix_volumes; + /* 0x924 */ s32 dest_mix_id; + /* 0x928 */ s32 dest_splitter_id; + /* 0x92C */ char unk92C[0x4]; + }; + static_assert(sizeof(InParameter) == 0x930, "MixInfo::InParameter has the wrong size!"); + + struct InDirtyParameter { + /* 0x00 */ u32 magic; + /* 0x04 */ s32 count; + /* 0x08 */ char unk08[0x18]; + }; + static_assert(sizeof(InDirtyParameter) == 0x20, + "MixInfo::InDirtyParameter has the wrong size!"); + + MixInfo(std::span effect_order_buffer, s32 effect_count, BehaviorInfo& behavior); + + /** + * Clean up the mix, resetting it to a default state. + */ + void Cleanup(); + + /** + * Clear the effect process order for all effects in this mix. + */ + void ClearEffectProcessingOrder(); + + /** + * Update the mix according to the given parameters. + * + * @param edge_matrix - Updated with new splitter node connections, if supported. + * @param in_params - Input parameters. + * @param effect_context - Used to update the effect orderings. + * @param splitter_context - Used to update the mix graph if supported. + * @param behavior - Used for checking which features are supported. + * @return True if the mix was updated and a sort is required, otherwise false. + */ + bool Update(EdgeMatrix& edge_matrix, const InParameter& in_params, + EffectContext& effect_context, SplitterContext& splitter_context, + const BehaviorInfo& behavior); + + /** + * Update the mix's connection in the node graph according to the given parameters. + * + * @param edge_matrix - Updated with new splitter node connections, if supported. + * @param in_params - Input parameters. + * @param splitter_context - Used to update the mix graph if supported. + * @return True if the mix was updated and a sort is required, otherwise false. + */ + bool UpdateConnection(EdgeMatrix& edge_matrix, const InParameter& in_params, + SplitterContext& splitter_context); + + /** + * Check if this mix is connected to any other. + * + * @return True if the mix has a connection, otherwise false. + */ + bool HasAnyConnection() const; + + /// Volume of this mix + f32 volume{}; + /// Sample rate of this mix + u32 sample_rate{}; + /// Number of buffers in this mix + s16 buffer_count{}; + /// Is this mix in use? + bool in_use{}; + /// Is this mix enabled? + bool enabled{}; + /// Id of this mix + s32 mix_id{UnusedMixId}; + /// Node id of this mix + s32 node_id{}; + /// Buffer offset for this mix + s16 buffer_offset{}; + /// Distance to the final mix + s32 distance_from_final_mix{InvalidDistanceFromFinalMix}; + /// Array of effect orderings of all effects in this mix + std::span effect_order_buffer; + /// Number of effects in this mix + const s32 effect_count; + /// Id for next mix in the chain + s32 dst_mix_id{UnusedMixId}; + /// Mixing volumes for this mix used when this mix is chained with another + std::array, MaxMixBuffers> mix_volumes{}; + /// Id for next mix in the graph when splitter is used + s32 dst_splitter_id{UnusedSplitterId}; + /// Is a longer pre-delay time supported for the reverb effect? + const bool long_size_pre_delay_supported; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/nodes/bit_array.h b/src/audio_core/renderer/nodes/bit_array.h new file mode 100644 index 000000000..b0d53cd51 --- /dev/null +++ b/src/audio_core/renderer/nodes/bit_array.h @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +/** + * Represents an array of bits used for nodes and edges for the mixing graph. + */ +struct BitArray { + void reset() { + buffer.assign(buffer.size(), false); + } + + /// Bits + std::vector buffer{}; + /// Size of the buffer + u32 size{}; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/nodes/edge_matrix.cpp b/src/audio_core/renderer/nodes/edge_matrix.cpp new file mode 100644 index 000000000..5573f33b9 --- /dev/null +++ b/src/audio_core/renderer/nodes/edge_matrix.cpp @@ -0,0 +1,38 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/nodes/edge_matrix.h" + +namespace AudioCore::AudioRenderer { + +void EdgeMatrix::Initialize([[maybe_unused]] std::span buffer, + [[maybe_unused]] const u64 node_buffer_size, const u32 count_) { + count = count_; + edges.buffer.resize(count_ * count_); + edges.size = count_ * count_; + edges.reset(); +} + +bool EdgeMatrix::Connected(const u32 id, const u32 destination_id) const { + return edges.buffer[count * id + destination_id]; +} + +void EdgeMatrix::Connect(const u32 id, const u32 destination_id) { + edges.buffer[count * id + destination_id] = true; +} + +void EdgeMatrix::Disconnect(const u32 id, const u32 destination_id) { + edges.buffer[count * id + destination_id] = false; +} + +void EdgeMatrix::RemoveEdges(const u32 id) { + for (u32 dest = 0; dest < count; dest++) { + Disconnect(id, dest); + } +} + +u32 EdgeMatrix::GetNodeCount() const { + return count; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/nodes/edge_matrix.h b/src/audio_core/renderer/nodes/edge_matrix.h new file mode 100644 index 000000000..27a20e43e --- /dev/null +++ b/src/audio_core/renderer/nodes/edge_matrix.h @@ -0,0 +1,82 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "audio_core/renderer/nodes/bit_array.h" +#include "common/alignment.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +/** + * An edge matrix, holding the connections for each node to every other node in the graph. + */ +class EdgeMatrix { +public: + /** + * Calculate the size required for its workbuffer. + * + * @param count - The number of nodes in the graph. + * @return The required workbuffer size. + */ + static u64 GetWorkBufferSize(u32 count) { + return Common::AlignUp(count * count, 0x40) / sizeof(u64); + } + + /** + * Initialize this edge matrix. + * + * @param buffer - The workbuffer to use. Unused. + * @param node_buffer_size - The size of the workbuffer. Unused. + * @param count - The number of nodes in the graph. + */ + void Initialize(std::span buffer, u64 node_buffer_size, u32 count); + + /** + * Check if a node is connected to another. + * + * @param id - The node id to check. + * @param destination_id - Node id to check connection with. + */ + bool Connected(u32 id, u32 destination_id) const; + + /** + * Connect a node to another. + * + * @param id - The node id to connect. + * @param destination_id - Destination to connect it to. + */ + void Connect(u32 id, u32 destination_id); + + /** + * Disconnect a node from another. + * + * @param id - The node id to disconnect. + * @param destination_id - Destination to disconnect it from. + */ + void Disconnect(u32 id, u32 destination_id); + + /** + * Remove all connections for a given node. + * + * @param id - The node id to disconnect. + */ + void RemoveEdges(u32 id); + + /** + * Get the number of nodes in the graph. + * + * @return Number of nodes. + */ + u32 GetNodeCount() const; + +private: + /// Edges for the current graph + BitArray edges; + /// Number of nodes (not edges) in the graph + u32 count; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/nodes/node_states.cpp b/src/audio_core/renderer/nodes/node_states.cpp new file mode 100644 index 000000000..1821a51e6 --- /dev/null +++ b/src/audio_core/renderer/nodes/node_states.cpp @@ -0,0 +1,141 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/nodes/node_states.h" +#include "common/logging/log.h" + +namespace AudioCore::AudioRenderer { + +void NodeStates::Initialize(std::span buffer_, [[maybe_unused]] const u64 node_buffer_size, + const u32 count) { + u64 num_blocks{Common::AlignUp(count, 0x40) / sizeof(u64)}; + u64 offset{0}; + + node_count = count; + + nodes_found.buffer.resize(count); + nodes_found.size = count; + nodes_found.reset(); + + offset += num_blocks; + + nodes_complete.buffer.resize(count); + nodes_complete.size = count; + nodes_complete.reset(); + + offset += num_blocks; + + results = {reinterpret_cast(&buffer_[offset]), count}; + + offset += count * sizeof(u32); + + stack.stack = {reinterpret_cast(&buffer_[offset]), count * count}; + stack.size = count * count; + stack.unk_10 = count * count; + + offset += count * count * sizeof(u32); +} + +bool NodeStates::Tsort(const EdgeMatrix& edge_matrix) { + return DepthFirstSearch(edge_matrix, stack); +} + +bool NodeStates::DepthFirstSearch(const EdgeMatrix& edge_matrix, Stack& stack_) { + ResetState(); + + for (u32 node_id = 0; node_id < node_count; node_id++) { + if (GetState(node_id) == SearchState::Unknown) { + stack_.push(node_id); + } + + while (stack_.Count() > 0) { + auto current_node{stack_.top()}; + switch (GetState(current_node)) { + case SearchState::Unknown: + SetState(current_node, SearchState::Found); + break; + case SearchState::Found: + SetState(current_node, SearchState::Complete); + PushTsortResult(current_node); + stack_.pop(); + continue; + case SearchState::Complete: + stack_.pop(); + continue; + } + + const auto edge_count{edge_matrix.GetNodeCount()}; + for (u32 edge_id = 0; edge_id < edge_count; edge_id++) { + if (!edge_matrix.Connected(current_node, edge_id)) { + continue; + } + + switch (GetState(edge_id)) { + case SearchState::Unknown: + stack_.push(edge_id); + break; + case SearchState::Found: + LOG_ERROR(Service_Audio, + "Cycle detected in the node graph, graph is not a DAG! " + "Bailing to avoid an infinite loop"); + ResetState(); + return false; + case SearchState::Complete: + break; + } + } + } + } + + return true; +} + +NodeStates::SearchState NodeStates::GetState(const u32 id) const { + if (nodes_found.buffer[id]) { + return SearchState::Found; + } else if (nodes_complete.buffer[id]) { + return SearchState::Complete; + } + return SearchState::Unknown; +} + +void NodeStates::PushTsortResult(const u32 id) { + results[result_pos++] = id; +} + +void NodeStates::SetState(const u32 id, const SearchState state) { + switch (state) { + case SearchState::Complete: + nodes_found.buffer[id] = false; + nodes_complete.buffer[id] = true; + break; + case SearchState::Found: + nodes_found.buffer[id] = true; + nodes_complete.buffer[id] = false; + break; + case SearchState::Unknown: + nodes_found.buffer[id] = false; + nodes_complete.buffer[id] = false; + break; + default: + LOG_ERROR(Service_Audio, "Unknown node SearchState {}", static_cast(state)); + break; + } +} + +void NodeStates::ResetState() { + nodes_found.reset(); + nodes_complete.reset(); + std::fill(results.begin(), results.end(), -1); + result_pos = 0; +} + +u32 NodeStates::GetNodeCount() const { + return node_count; +} + +std::vector NodeStates::GetSortedResuls() const { + return {results.rbegin(), results.rbegin() + result_pos}; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/nodes/node_states.h b/src/audio_core/renderer/nodes/node_states.h new file mode 100644 index 000000000..a1e0958a2 --- /dev/null +++ b/src/audio_core/renderer/nodes/node_states.h @@ -0,0 +1,195 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#include "audio_core/renderer/nodes/edge_matrix.h" +#include "common/alignment.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +/** + * Graph utility functions for sorting and getting results from the DAG. + */ +class NodeStates { + /** + * State of a node in the depth first search. + */ + enum class SearchState { + Unknown, + Found, + Complete, + }; + + /** + * Stack used for a depth first search. + */ + struct Stack { + /** + * Calculate the workbuffer size required for this stack. + * + * @param count - Maximum number of nodes for the stack. + * @return Required buffer size. + */ + static u32 CalcBufferSize(u32 count) { + return count * sizeof(u32); + } + + /** + * Reset the stack back to default. + * + * @param buffer_ - The new buffer to use. + * @param size_ - The size of the new buffer. + */ + void Reset(u32* buffer_, u32 size_) { + stack = {buffer_, size_}; + size = size_; + pos = 0; + unk_10 = size_; + } + + /** + * Get the current stack position. + * + * @return The current stack position. + */ + u32 Count() { + return pos; + } + + /** + * Push a new node to the stack. + * + * @param data - The node to push. + */ + void push(u32 data) { + stack[pos++] = data; + } + + /** + * Pop a node from the stack. + * + * @return The node on the top of the stack. + */ + u32 pop() { + return stack[--pos]; + } + + /** + * Get the top of the stack without popping. + * + * @return The node on the top of the stack. + */ + u32 top() { + return stack[pos - 1]; + } + + /// Buffer for the stack + std::span stack{}; + /// Size of the stack buffer + u32 size{}; + /// Current stack position + u32 pos{}; + /// Unknown + u32 unk_10{}; + }; + +public: + /** + * Calculate the workbuffer size required for the node states. + * + * @param count - The number of nodes. + * @return The required workbuffer size. + */ + static u64 GetWorkBufferSize(u32 count) { + return (Common::AlignUp(count, 0x40) / sizeof(u64)) * 2 + count * sizeof(BitArray) + + count * Stack::CalcBufferSize(count); + } + + /** + * Initialize the node states. + * + * @param buffer - The workbuffer to use. Unused. + * @param node_buffer_size - The size of the workbuffer. Unused. + * @param count - The number of nodes in the graph. + */ + void Initialize(std::span nodes, u64 node_buffer_size, u32 count); + + /** + * Sort the graph. Only calls DepthFirstSearch. + * + * @param edge_matrix - The edge matrix used to hold the connections between nodes. + * @return True if the sort was successful, otherwise false. + */ + bool Tsort(const EdgeMatrix& edge_matrix); + + /** + * Sort the graph via depth first search. + * + * @param edge_matrix - The edge matrix used to hold the connections between nodes. + * @param stack - The stack used for pushing and popping nodes. + * @return True if the sort was successful, otherwise false. + */ + bool DepthFirstSearch(const EdgeMatrix& edge_matrix, Stack& stack); + + /** + * Get the search state of a given node. + * + * @param id - The node id to check. + * @return The node's search state. See SearchState + */ + SearchState GetState(u32 id) const; + + /** + * Push a node id to the results buffer when found in the DFS. + * + * @param id - The node id to push. + */ + void PushTsortResult(u32 id); + + /** + * Set the state of a node. + * + * @param id - The node id to alter. + * @param state - The new search state. + */ + void SetState(u32 id, SearchState state); + + /** + * Reset the nodes found, complete and the results. + */ + void ResetState(); + + /** + * Get the number of nodes in the graph. + * + * @return The number of nodes. + */ + u32 GetNodeCount() const; + + /** + * Get the sorted results from the DFS. + * + * @return Vector of nodes in reverse order. + */ + std::vector GetSortedResuls() const; + +private: + /// Number of nodes in the graph + u32 node_count{}; + /// Position in results buffer + u32 result_pos{}; + /// List of nodes found + BitArray nodes_found{}; + /// List of nodes completed + BitArray nodes_complete{}; + /// List of results from the depth first search + std::span results{}; + /// Stack used during the depth first search + Stack stack{}; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/performance/detail_aspect.cpp b/src/audio_core/renderer/performance/detail_aspect.cpp new file mode 100644 index 000000000..f6405937f --- /dev/null +++ b/src/audio_core/renderer/performance/detail_aspect.cpp @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/command/command_buffer.h" +#include "audio_core/renderer/command/command_generator.h" +#include "audio_core/renderer/performance/detail_aspect.h" + +namespace AudioCore::AudioRenderer { + +DetailAspect::DetailAspect(CommandGenerator& command_generator_, + const PerformanceEntryType entry_type, const s32 node_id_, + const PerformanceDetailType detail_type) + : command_generator{command_generator_}, node_id{node_id_} { + auto perf_manager{command_generator.GetPerformanceManager()}; + if (perf_manager != nullptr && perf_manager->IsInitialized() && + perf_manager->IsDetailTarget(node_id) && + perf_manager->GetNextEntry(performance_entry_address, detail_type, entry_type, node_id)) { + command_generator.GeneratePerformanceCommand(node_id, PerformanceState::Start, + performance_entry_address); + + initialized = true; + } +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/performance/detail_aspect.h b/src/audio_core/renderer/performance/detail_aspect.h new file mode 100644 index 000000000..ee4ac2f76 --- /dev/null +++ b/src/audio_core/renderer/performance/detail_aspect.h @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "audio_core/renderer/performance/performance_entry_addresses.h" +#include "audio_core/renderer/performance/performance_manager.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +class CommandGenerator; + +/** + * Holds detailed information about performance metrics, filled in by the AudioRenderer during + * Performance commands. + */ +class DetailAspect { +public: + DetailAspect() = default; + DetailAspect(CommandGenerator& command_generator, PerformanceEntryType entry_type, s32 node_id, + PerformanceDetailType detail_type); + + /// Command generator the command will be generated into + CommandGenerator& command_generator; + /// Addresses to be filled by the AudioRenderer + PerformanceEntryAddresses performance_entry_address{}; + /// Is this detail aspect initialized? + bool initialized{}; + /// Node id of this aspect + s32 node_id; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/performance/entry_aspect.cpp b/src/audio_core/renderer/performance/entry_aspect.cpp new file mode 100644 index 000000000..dd4165803 --- /dev/null +++ b/src/audio_core/renderer/performance/entry_aspect.cpp @@ -0,0 +1,23 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/command/command_buffer.h" +#include "audio_core/renderer/command/command_generator.h" +#include "audio_core/renderer/performance/entry_aspect.h" + +namespace AudioCore::AudioRenderer { + +EntryAspect::EntryAspect(CommandGenerator& command_generator_, const PerformanceEntryType type, + const s32 node_id_) + : command_generator{command_generator_}, node_id{node_id_} { + auto perf_manager{command_generator.GetPerformanceManager()}; + if (perf_manager != nullptr && perf_manager->IsInitialized() && + perf_manager->GetNextEntry(performance_entry_address, type, node_id)) { + command_generator.GeneratePerformanceCommand(node_id, PerformanceState::Start, + performance_entry_address); + + initialized = true; + } +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/performance/entry_aspect.h b/src/audio_core/renderer/performance/entry_aspect.h new file mode 100644 index 000000000..01c1eb3f1 --- /dev/null +++ b/src/audio_core/renderer/performance/entry_aspect.h @@ -0,0 +1,32 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "audio_core/renderer/performance/performance_entry_addresses.h" +#include "audio_core/renderer/performance/performance_manager.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +class CommandGenerator; + +/** + * Holds entry information about performance metrics, filled in by the AudioRenderer during + * Performance commands. + */ +class EntryAspect { +public: + EntryAspect() = default; + EntryAspect(CommandGenerator& command_generator, PerformanceEntryType type, s32 node_id); + + /// Command generator the command will be generated into + CommandGenerator& command_generator; + /// Addresses to be filled by the AudioRenderer + PerformanceEntryAddresses performance_entry_address{}; + /// Is this detail aspect initialized? + bool initialized{}; + /// Node id of this aspect + s32 node_id; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/performance/performance_detail.h b/src/audio_core/renderer/performance/performance_detail.h new file mode 100644 index 000000000..3a4897e60 --- /dev/null +++ b/src/audio_core/renderer/performance/performance_detail.h @@ -0,0 +1,50 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "audio_core/renderer/performance/performance_entry.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { + +enum class PerformanceDetailType : u8 { + Invalid, + Unk1, + Unk2, + Unk3, + Unk4, + Unk5, + Unk6, + Unk7, + Unk8, + Unk9, + Unk10, + Unk11, + Unk12, + Unk13, +}; + +struct PerformanceDetailVersion1 { + /* 0x00 */ u32 node_id; + /* 0x04 */ u32 start_time; + /* 0x08 */ u32 processed_time; + /* 0x0C */ PerformanceDetailType detail_type; + /* 0x0D */ PerformanceEntryType entry_type; +}; +static_assert(sizeof(PerformanceDetailVersion1) == 0x10, + "PerformanceDetailVersion1 has the worng size!"); + +struct PerformanceDetailVersion2 { + /* 0x00 */ u32 node_id; + /* 0x04 */ u32 start_time; + /* 0x08 */ u32 processed_time; + /* 0x0C */ PerformanceDetailType detail_type; + /* 0x0D */ PerformanceEntryType entry_type; + /* 0x10 */ u32 unk_10; + /* 0x14 */ char unk14[0x4]; +}; +static_assert(sizeof(PerformanceDetailVersion2) == 0x18, + "PerformanceDetailVersion2 has the worng size!"); + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/performance/performance_entry.h b/src/audio_core/renderer/performance/performance_entry.h new file mode 100644 index 000000000..d1b21406b --- /dev/null +++ b/src/audio_core/renderer/performance/performance_entry.h @@ -0,0 +1,37 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { + +enum class PerformanceEntryType : u8 { + Invalid, + Voice, + SubMix, + FinalMix, + Sink, +}; + +struct PerformanceEntryVersion1 { + /* 0x00 */ u32 node_id; + /* 0x04 */ u32 start_time; + /* 0x08 */ u32 processed_time; + /* 0x0C */ PerformanceEntryType entry_type; +}; +static_assert(sizeof(PerformanceEntryVersion1) == 0x10, + "PerformanceEntryVersion1 has the worng size!"); + +struct PerformanceEntryVersion2 { + /* 0x00 */ u32 node_id; + /* 0x04 */ u32 start_time; + /* 0x08 */ u32 processed_time; + /* 0x0C */ PerformanceEntryType entry_type; + /* 0x0D */ char unk0D[0xB]; +}; +static_assert(sizeof(PerformanceEntryVersion2) == 0x18, + "PerformanceEntryVersion2 has the worng size!"); + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/performance/performance_entry_addresses.h b/src/audio_core/renderer/performance/performance_entry_addresses.h new file mode 100644 index 000000000..e381d765c --- /dev/null +++ b/src/audio_core/renderer/performance/performance_entry_addresses.h @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "audio_core/common/common.h" + +namespace AudioCore::AudioRenderer { + +struct PerformanceEntryAddresses { + CpuAddr translated_address; + CpuAddr entry_start_time_offset; + CpuAddr header_entry_count_offset; + CpuAddr entry_processed_time_offset; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/performance/performance_frame_header.h b/src/audio_core/renderer/performance/performance_frame_header.h new file mode 100644 index 000000000..707cc0afb --- /dev/null +++ b/src/audio_core/renderer/performance/performance_frame_header.h @@ -0,0 +1,36 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { + +struct PerformanceFrameHeaderVersion1 { + /* 0x00 */ u32 magic; // "PERF" + /* 0x04 */ u32 entry_count; + /* 0x08 */ u32 detail_count; + /* 0x0C */ u32 next_offset; + /* 0x10 */ u32 total_processing_time; + /* 0x14 */ u32 frame_index; +}; +static_assert(sizeof(PerformanceFrameHeaderVersion1) == 0x18, + "PerformanceFrameHeaderVersion1 has the worng size!"); + +struct PerformanceFrameHeaderVersion2 { + /* 0x00 */ u32 magic; // "PERF" + /* 0x04 */ u32 entry_count; + /* 0x08 */ u32 detail_count; + /* 0x0C */ u32 next_offset; + /* 0x10 */ u32 total_processing_time; + /* 0x14 */ u32 voices_dropped; + /* 0x18 */ u64 start_time; + /* 0x20 */ u32 frame_index; + /* 0x24 */ bool render_time_exceeded; + /* 0x25 */ char unk25[0xB]; +}; +static_assert(sizeof(PerformanceFrameHeaderVersion2) == 0x30, + "PerformanceFrameHeaderVersion2 has the worng size!"); + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/performance/performance_manager.cpp b/src/audio_core/renderer/performance/performance_manager.cpp new file mode 100644 index 000000000..fd5873e1e --- /dev/null +++ b/src/audio_core/renderer/performance/performance_manager.cpp @@ -0,0 +1,645 @@ +// 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/memory/memory_pool_info.h" +#include "audio_core/renderer/performance/performance_manager.h" +#include "common/common_funcs.h" + +namespace AudioCore::AudioRenderer { + +void PerformanceManager::CreateImpl(const size_t version) { + switch (version) { + case 1: + impl = std::make_unique< + PerformanceManagerImpl>(); + break; + case 2: + impl = std::make_unique< + PerformanceManagerImpl>(); + break; + default: + LOG_WARNING(Service_Audio, "Invalid PerformanceMetricsDataFormat {}, creating version 1", + static_cast(version)); + impl = std::make_unique< + PerformanceManagerImpl>(); + } +} + +void PerformanceManager::Initialize(std::span workbuffer, const u64 workbuffer_size, + const AudioRendererParameterInternal& params, + const BehaviorInfo& behavior, + const MemoryPoolInfo& memory_pool) { + CreateImpl(behavior.GetPerformanceMetricsDataFormat()); + impl->Initialize(workbuffer, workbuffer_size, params, behavior, memory_pool); +} + +bool PerformanceManager::IsInitialized() const { + if (impl) { + return impl->IsInitialized(); + } + return false; +} + +u32 PerformanceManager::CopyHistories(u8* out_buffer, u64 out_size) { + if (impl) { + return impl->CopyHistories(out_buffer, out_size); + } + return 0; +} + +bool PerformanceManager::GetNextEntry(PerformanceEntryAddresses& addresses, u32** unk, + const PerformanceSysDetailType sys_detail_type, + const s32 node_id) { + if (impl) { + return impl->GetNextEntry(addresses, unk, sys_detail_type, node_id); + } + return false; +} + +bool PerformanceManager::GetNextEntry(PerformanceEntryAddresses& addresses, + const PerformanceEntryType entry_type, const s32 node_id) { + if (impl) { + return impl->GetNextEntry(addresses, entry_type, node_id); + } + return false; +} + +bool PerformanceManager::GetNextEntry(PerformanceEntryAddresses& addresses, + const PerformanceDetailType detail_type, + const PerformanceEntryType entry_type, const s32 node_id) { + if (impl) { + return impl->GetNextEntry(addresses, detail_type, entry_type, node_id); + } + return false; +} + +void PerformanceManager::TapFrame(const bool dsp_behind, const u32 voices_dropped, + const u64 rendering_start_tick) { + if (impl) { + impl->TapFrame(dsp_behind, voices_dropped, rendering_start_tick); + } +} + +bool PerformanceManager::IsDetailTarget(const u32 target_node_id) const { + if (impl) { + return impl->IsDetailTarget(target_node_id); + } + return false; +} + +void PerformanceManager::SetDetailTarget(const u32 target_node_id) { + if (impl) { + impl->SetDetailTarget(target_node_id); + } +} + +template <> +void PerformanceManagerImpl< + PerformanceVersion::Version1, PerformanceFrameHeaderVersion1, PerformanceEntryVersion1, + PerformanceDetailVersion1>::Initialize(std::span workbuffer_, const u64 workbuffer_size, + const AudioRendererParameterInternal& params, + const BehaviorInfo& behavior, + const MemoryPoolInfo& memory_pool) { + workbuffer = workbuffer_; + entries_per_frame = params.voices + params.effects + params.sinks + params.sub_mixes + 1; + max_detail_count = MaxDetailEntries; + frame_size = GetRequiredBufferSizeForPerformanceMetricsPerFrame(behavior, params); + const auto frame_count{static_cast(workbuffer_size / frame_size)}; + max_frames = frame_count - 1; + translated_buffer = memory_pool.Translate(CpuAddr(workbuffer.data()), workbuffer_size); + + // The first frame is the "current" frame we're writing to. + auto buffer_offset{workbuffer.data()}; + frame_header = reinterpret_cast(buffer_offset); + buffer_offset += sizeof(PerformanceFrameHeaderVersion1); + entry_buffer = {reinterpret_cast(buffer_offset), entries_per_frame}; + buffer_offset += entries_per_frame * sizeof(PerformanceEntryVersion1); + detail_buffer = {reinterpret_cast(buffer_offset), max_detail_count}; + + // After the current, is a ringbuffer of history frames, the current frame will be copied here + // before a new frame is written. + frame_history = std::span(workbuffer.data() + frame_size, workbuffer_size - frame_size); + + // If there's room for any history frames. + if (frame_count >= 2) { + buffer_offset = frame_history.data(); + frame_history_header = reinterpret_cast(buffer_offset); + buffer_offset += sizeof(PerformanceFrameHeaderVersion1); + frame_history_entries = {reinterpret_cast(buffer_offset), + entries_per_frame}; + buffer_offset += entries_per_frame * sizeof(PerformanceEntryVersion1); + frame_history_details = {reinterpret_cast(buffer_offset), + max_detail_count}; + } else { + frame_history_header = {}; + frame_history_entries = {}; + frame_history_details = {}; + } + + target_node_id = 0; + version = PerformanceVersion(behavior.GetPerformanceMetricsDataFormat()); + entry_count = 0; + detail_count = 0; + frame_header->entry_count = 0; + frame_header->detail_count = 0; + output_frame_index = 0; + last_output_frame_index = 0; + is_initialized = true; +} + +template <> +bool PerformanceManagerImpl::IsInitialized() + const { + return is_initialized; +} + +template <> +u32 PerformanceManagerImpl::CopyHistories(u8* out_buffer, u64 out_size) { + if (out_buffer == nullptr || out_size == 0 || !is_initialized) { + return 0; + } + + // Are there any new frames waiting to be output? + if (last_output_frame_index == output_frame_index) { + return 0; + } + + PerformanceFrameHeaderVersion1* out_header{nullptr}; + u32 out_history_size{0}; + + while (last_output_frame_index != output_frame_index) { + PerformanceFrameHeaderVersion1* history_header{nullptr}; + std::span history_entries{}; + std::span history_details{}; + + if (max_frames > 0) { + auto frame_offset{&frame_history[last_output_frame_index * frame_size]}; + history_header = reinterpret_cast(frame_offset); + frame_offset += sizeof(PerformanceFrameHeaderVersion1); + history_entries = {reinterpret_cast(frame_offset), + history_header->entry_count}; + frame_offset += entries_per_frame * sizeof(PerformanceFrameHeaderVersion1); + history_details = {reinterpret_cast(frame_offset), + history_header->detail_count}; + } else { + // Original code does not break here, but will crash when trying to dereference the + // header in the next if, so let's just skip this frame and continue... + // Hopefully this will not happen. + LOG_WARNING(Service_Audio, + "max_frames should not be 0! Skipping frame to avoid a crash"); + last_output_frame_index++; + continue; + } + + if (out_size < history_header->entry_count * sizeof(PerformanceEntryVersion1) + + history_header->detail_count * sizeof(PerformanceDetailVersion1) + + 2 * sizeof(PerformanceFrameHeaderVersion1)) { + break; + } + + u32 out_offset{sizeof(PerformanceFrameHeaderVersion1)}; + auto out_entries{std::span( + reinterpret_cast(out_buffer + out_offset), + history_header->entry_count)}; + u32 out_entry_count{0}; + u32 total_processing_time{0}; + for (auto& history_entry : history_entries) { + if (history_entry.processed_time > 0 || history_entry.start_time > 0) { + out_entries[out_entry_count++] = history_entry; + total_processing_time += history_entry.processed_time; + } + } + + out_offset += static_cast(out_entry_count * sizeof(PerformanceEntryVersion1)); + auto out_details{std::span( + reinterpret_cast(out_buffer + out_offset), + history_header->detail_count)}; + u32 out_detail_count{0}; + for (auto& history_detail : history_details) { + if (history_detail.processed_time > 0 || history_detail.start_time > 0) { + out_details[out_detail_count++] = history_detail; + } + } + + out_offset += static_cast(out_detail_count * sizeof(PerformanceDetailVersion1)); + out_header = reinterpret_cast(out_buffer); + out_header->magic = Common::MakeMagic('P', 'E', 'R', 'F'); + out_header->entry_count = out_entry_count; + out_header->detail_count = out_detail_count; + out_header->next_offset = out_offset; + out_header->total_processing_time = total_processing_time; + out_header->frame_index = history_header->frame_index; + + out_history_size += out_offset; + + out_buffer += out_offset; + out_size -= out_offset; + last_output_frame_index = (last_output_frame_index + 1) % max_frames; + } + + // We're out of frames to output, so if there's enough left in the output buffer for another + // header, and we output at least 1 frame, set the next header to null. + if (out_size > sizeof(PerformanceFrameHeaderVersion1) && out_header != nullptr) { + std::memset(out_buffer, 0, sizeof(PerformanceFrameHeaderVersion1)); + } + + return out_history_size; +} + +template <> +bool PerformanceManagerImpl:: + GetNextEntry([[maybe_unused]] PerformanceEntryAddresses& addresses, [[maybe_unused]] u32** unk, + [[maybe_unused]] PerformanceSysDetailType sys_detail_type, + [[maybe_unused]] s32 node_id) { + return false; +} + +template <> +bool PerformanceManagerImpl< + PerformanceVersion::Version1, PerformanceFrameHeaderVersion1, PerformanceEntryVersion1, + PerformanceDetailVersion1>::GetNextEntry(PerformanceEntryAddresses& addresses, + const PerformanceEntryType entry_type, + const s32 node_id) { + if (!is_initialized) { + return false; + } + + addresses.translated_address = translated_buffer; + addresses.header_entry_count_offset = CpuAddr(frame_header) - CpuAddr(workbuffer.data()) + + offsetof(PerformanceFrameHeaderVersion1, entry_count); + + auto entry{&entry_buffer[entry_count++]}; + addresses.entry_start_time_offset = CpuAddr(entry) - CpuAddr(workbuffer.data()) + + offsetof(PerformanceEntryVersion1, start_time); + addresses.entry_processed_time_offset = CpuAddr(entry) - CpuAddr(workbuffer.data()) + + offsetof(PerformanceEntryVersion1, processed_time); + + std::memset(entry, 0, sizeof(PerformanceEntryVersion1)); + entry->node_id = node_id; + entry->entry_type = entry_type; + return true; +} + +template <> +bool PerformanceManagerImpl< + PerformanceVersion::Version1, PerformanceFrameHeaderVersion1, PerformanceEntryVersion1, + PerformanceDetailVersion1>::GetNextEntry(PerformanceEntryAddresses& addresses, + const PerformanceDetailType detail_type, + const PerformanceEntryType entry_type, + const s32 node_id) { + if (!is_initialized || detail_count > MaxDetailEntries) { + return false; + } + + auto detail{&detail_buffer[detail_count++]}; + + addresses.translated_address = translated_buffer; + addresses.header_entry_count_offset = CpuAddr(frame_header) - CpuAddr(workbuffer.data()) + + offsetof(PerformanceFrameHeaderVersion1, detail_count); + addresses.entry_start_time_offset = CpuAddr(detail) - CpuAddr(workbuffer.data()) + + offsetof(PerformanceDetailVersion1, start_time); + addresses.entry_processed_time_offset = CpuAddr(detail) - CpuAddr(workbuffer.data()) + + offsetof(PerformanceDetailVersion1, processed_time); + + std::memset(detail, 0, sizeof(PerformanceDetailVersion1)); + detail->node_id = node_id; + detail->entry_type = entry_type; + detail->detail_type = detail_type; + return true; +} + +template <> +void PerformanceManagerImpl< + PerformanceVersion::Version1, PerformanceFrameHeaderVersion1, PerformanceEntryVersion1, + PerformanceDetailVersion1>::TapFrame([[maybe_unused]] bool dsp_behind, + [[maybe_unused]] u32 voices_dropped, + [[maybe_unused]] u64 rendering_start_tick) { + if (!is_initialized) { + return; + } + + if (max_frames > 0) { + if (!frame_history.empty() && !workbuffer.empty()) { + auto history_frame = reinterpret_cast( + &frame_history[output_frame_index * frame_size]); + std::memcpy(history_frame, workbuffer.data(), frame_size); + history_frame->frame_index = history_frame_index++; + } + output_frame_index = (output_frame_index + 1) % max_frames; + } + + entry_count = 0; + detail_count = 0; + frame_header->entry_count = 0; + frame_header->detail_count = 0; +} + +template <> +bool PerformanceManagerImpl< + PerformanceVersion::Version1, PerformanceFrameHeaderVersion1, PerformanceEntryVersion1, + PerformanceDetailVersion1>::IsDetailTarget(const u32 target_node_id_) const { + return target_node_id == target_node_id_; +} + +template <> +void PerformanceManagerImpl::SetDetailTarget(const u32 target_node_id_) { + target_node_id = target_node_id_; +} + +template <> +void PerformanceManagerImpl< + PerformanceVersion::Version2, PerformanceFrameHeaderVersion2, PerformanceEntryVersion2, + PerformanceDetailVersion2>::Initialize(std::span workbuffer_, const u64 workbuffer_size, + const AudioRendererParameterInternal& params, + const BehaviorInfo& behavior, + const MemoryPoolInfo& memory_pool) { + workbuffer = workbuffer_; + entries_per_frame = params.voices + params.effects + params.sinks + params.sub_mixes + 1; + max_detail_count = MaxDetailEntries; + frame_size = GetRequiredBufferSizeForPerformanceMetricsPerFrame(behavior, params); + const auto frame_count{static_cast(workbuffer_size / frame_size)}; + max_frames = frame_count - 1; + translated_buffer = memory_pool.Translate(CpuAddr(workbuffer.data()), workbuffer_size); + + // The first frame is the "current" frame we're writing to. + auto buffer_offset{workbuffer.data()}; + frame_header = reinterpret_cast(buffer_offset); + buffer_offset += sizeof(PerformanceFrameHeaderVersion2); + entry_buffer = {reinterpret_cast(buffer_offset), entries_per_frame}; + buffer_offset += entries_per_frame * sizeof(PerformanceEntryVersion2); + detail_buffer = {reinterpret_cast(buffer_offset), max_detail_count}; + + // After the current, is a ringbuffer of history frames, the current frame will be copied here + // before a new frame is written. + frame_history = std::span(workbuffer.data() + frame_size, workbuffer_size - frame_size); + + // If there's room for any history frames. + if (frame_count >= 2) { + buffer_offset = frame_history.data(); + frame_history_header = reinterpret_cast(buffer_offset); + buffer_offset += sizeof(PerformanceFrameHeaderVersion2); + frame_history_entries = {reinterpret_cast(buffer_offset), + entries_per_frame}; + buffer_offset += entries_per_frame * sizeof(PerformanceEntryVersion2); + frame_history_details = {reinterpret_cast(buffer_offset), + max_detail_count}; + } else { + frame_history_header = {}; + frame_history_entries = {}; + frame_history_details = {}; + } + + target_node_id = 0; + version = PerformanceVersion(behavior.GetPerformanceMetricsDataFormat()); + entry_count = 0; + detail_count = 0; + frame_header->entry_count = 0; + frame_header->detail_count = 0; + output_frame_index = 0; + last_output_frame_index = 0; + is_initialized = true; +} + +template <> +bool PerformanceManagerImpl::IsInitialized() + const { + return is_initialized; +} + +template <> +u32 PerformanceManagerImpl::CopyHistories(u8* out_buffer, u64 out_size) { + if (out_buffer == nullptr || out_size == 0 || !is_initialized) { + return 0; + } + + // Are there any new frames waiting to be output? + if (last_output_frame_index == output_frame_index) { + return 0; + } + + PerformanceFrameHeaderVersion2* out_header{nullptr}; + u32 out_history_size{0}; + + while (last_output_frame_index != output_frame_index) { + PerformanceFrameHeaderVersion2* history_header{nullptr}; + std::span history_entries{}; + std::span history_details{}; + + if (max_frames > 0) { + auto frame_offset{&frame_history[last_output_frame_index * frame_size]}; + history_header = reinterpret_cast(frame_offset); + frame_offset += sizeof(PerformanceFrameHeaderVersion2); + history_entries = {reinterpret_cast(frame_offset), + history_header->entry_count}; + frame_offset += entries_per_frame * sizeof(PerformanceFrameHeaderVersion2); + history_details = {reinterpret_cast(frame_offset), + history_header->detail_count}; + } else { + // Original code does not break here, but will crash when trying to dereference the + // header in the next if, so let's just skip this frame and continue... + // Hopefully this will not happen. + LOG_WARNING(Service_Audio, + "max_frames should not be 0! Skipping frame to avoid a crash"); + last_output_frame_index++; + continue; + } + + if (out_size < history_header->entry_count * sizeof(PerformanceEntryVersion2) + + history_header->detail_count * sizeof(PerformanceDetailVersion2) + + 2 * sizeof(PerformanceFrameHeaderVersion2)) { + break; + } + + u32 out_offset{sizeof(PerformanceFrameHeaderVersion2)}; + auto out_entries{std::span( + reinterpret_cast(out_buffer + out_offset), + history_header->entry_count)}; + u32 out_entry_count{0}; + u32 total_processing_time{0}; + for (auto& history_entry : history_entries) { + if (history_entry.processed_time > 0 || history_entry.start_time > 0) { + out_entries[out_entry_count++] = history_entry; + total_processing_time += history_entry.processed_time; + } + } + + out_offset += static_cast(out_entry_count * sizeof(PerformanceEntryVersion2)); + auto out_details{std::span( + reinterpret_cast(out_buffer + out_offset), + history_header->detail_count)}; + u32 out_detail_count{0}; + for (auto& history_detail : history_details) { + if (history_detail.processed_time > 0 || history_detail.start_time > 0) { + out_details[out_detail_count++] = history_detail; + } + } + + out_offset += static_cast(out_detail_count * sizeof(PerformanceDetailVersion2)); + out_header = reinterpret_cast(out_buffer); + out_header->magic = Common::MakeMagic('P', 'E', 'R', 'F'); + out_header->entry_count = out_entry_count; + out_header->detail_count = out_detail_count; + out_header->next_offset = out_offset; + out_header->total_processing_time = total_processing_time; + out_header->voices_dropped = history_header->voices_dropped; + out_header->start_time = history_header->start_time; + out_header->frame_index = history_header->frame_index; + out_header->render_time_exceeded = history_header->render_time_exceeded; + + out_history_size += out_offset; + + out_buffer += out_offset; + out_size -= out_offset; + last_output_frame_index = (last_output_frame_index + 1) % max_frames; + } + + // We're out of frames to output, so if there's enough left in the output buffer for another + // header, and we output at least 1 frame, set the next header to null. + if (out_size > sizeof(PerformanceFrameHeaderVersion2) && out_header != nullptr) { + std::memset(out_buffer, 0, sizeof(PerformanceFrameHeaderVersion2)); + } + + return out_history_size; +} + +template <> +bool PerformanceManagerImpl< + PerformanceVersion::Version2, PerformanceFrameHeaderVersion2, PerformanceEntryVersion2, + PerformanceDetailVersion2>::GetNextEntry(PerformanceEntryAddresses& addresses, u32** unk, + const PerformanceSysDetailType sys_detail_type, + const s32 node_id) { + if (!is_initialized || detail_count > MaxDetailEntries) { + return false; + } + + auto detail{&detail_buffer[detail_count++]}; + + addresses.translated_address = translated_buffer; + addresses.header_entry_count_offset = CpuAddr(frame_header) - CpuAddr(workbuffer.data()) + + offsetof(PerformanceFrameHeaderVersion2, detail_count); + addresses.entry_start_time_offset = CpuAddr(detail) - CpuAddr(workbuffer.data()) + + offsetof(PerformanceDetailVersion2, start_time); + addresses.entry_processed_time_offset = CpuAddr(detail) - CpuAddr(workbuffer.data()) + + offsetof(PerformanceDetailVersion2, processed_time); + + std::memset(detail, 0, sizeof(PerformanceDetailVersion2)); + detail->node_id = node_id; + detail->detail_type = static_cast(sys_detail_type); + + if (unk) { + *unk = &detail->unk_10; + } + return true; +} + +template <> +bool PerformanceManagerImpl< + PerformanceVersion::Version2, PerformanceFrameHeaderVersion2, PerformanceEntryVersion2, + PerformanceDetailVersion2>::GetNextEntry(PerformanceEntryAddresses& addresses, + const PerformanceEntryType entry_type, + const s32 node_id) { + if (!is_initialized) { + return false; + } + + auto entry{&entry_buffer[entry_count++]}; + + addresses.translated_address = translated_buffer; + addresses.header_entry_count_offset = CpuAddr(frame_header) - CpuAddr(workbuffer.data()) + + offsetof(PerformanceFrameHeaderVersion2, entry_count); + addresses.entry_start_time_offset = CpuAddr(entry) - CpuAddr(workbuffer.data()) + + offsetof(PerformanceEntryVersion2, start_time); + addresses.entry_processed_time_offset = CpuAddr(entry) - CpuAddr(workbuffer.data()) + + offsetof(PerformanceEntryVersion2, processed_time); + + std::memset(entry, 0, sizeof(PerformanceEntryVersion2)); + entry->node_id = node_id; + entry->entry_type = entry_type; + return true; +} + +template <> +bool PerformanceManagerImpl< + PerformanceVersion::Version2, PerformanceFrameHeaderVersion2, PerformanceEntryVersion2, + PerformanceDetailVersion2>::GetNextEntry(PerformanceEntryAddresses& addresses, + const PerformanceDetailType detail_type, + const PerformanceEntryType entry_type, + const s32 node_id) { + if (!is_initialized || detail_count > MaxDetailEntries) { + return false; + } + + auto detail{&detail_buffer[detail_count++]}; + + addresses.translated_address = translated_buffer; + addresses.header_entry_count_offset = CpuAddr(frame_header) - CpuAddr(workbuffer.data()) + + offsetof(PerformanceFrameHeaderVersion2, detail_count); + addresses.entry_start_time_offset = CpuAddr(detail) - CpuAddr(workbuffer.data()) + + offsetof(PerformanceDetailVersion2, start_time); + addresses.entry_processed_time_offset = CpuAddr(detail) - CpuAddr(workbuffer.data()) + + offsetof(PerformanceDetailVersion2, processed_time); + + std::memset(detail, 0, sizeof(PerformanceDetailVersion2)); + detail->node_id = node_id; + detail->entry_type = entry_type; + detail->detail_type = detail_type; + return true; +} + +template <> +void PerformanceManagerImpl::TapFrame(const bool dsp_behind, + const u32 voices_dropped, + const u64 rendering_start_tick) { + if (!is_initialized) { + return; + } + + if (max_frames > 0) { + if (!frame_history.empty() && !workbuffer.empty()) { + auto history_frame{reinterpret_cast( + &frame_history[output_frame_index * frame_size])}; + std::memcpy(history_frame, workbuffer.data(), frame_size); + history_frame->render_time_exceeded = dsp_behind; + history_frame->voices_dropped = voices_dropped; + history_frame->start_time = rendering_start_tick; + history_frame->frame_index = history_frame_index++; + } + output_frame_index = (output_frame_index + 1) % max_frames; + } + + entry_count = 0; + detail_count = 0; + frame_header->entry_count = 0; + frame_header->detail_count = 0; +} + +template <> +bool PerformanceManagerImpl< + PerformanceVersion::Version2, PerformanceFrameHeaderVersion2, PerformanceEntryVersion2, + PerformanceDetailVersion2>::IsDetailTarget(const u32 target_node_id_) const { + return target_node_id == target_node_id_; +} + +template <> +void PerformanceManagerImpl::SetDetailTarget(const u32 target_node_id_) { + target_node_id = target_node_id_; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/performance/performance_manager.h b/src/audio_core/renderer/performance/performance_manager.h new file mode 100644 index 000000000..b82176bef --- /dev/null +++ b/src/audio_core/renderer/performance/performance_manager.h @@ -0,0 +1,273 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include + +#include "audio_core/common/audio_renderer_parameter.h" +#include "audio_core/renderer/performance/performance_detail.h" +#include "audio_core/renderer/performance/performance_entry.h" +#include "audio_core/renderer/performance/performance_entry_addresses.h" +#include "audio_core/renderer/performance/performance_frame_header.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +class BehaviorInfo; +class MemoryPoolInfo; + +enum class PerformanceVersion { + Version1, + Version2, +}; + +enum class PerformanceSysDetailType { + PcmInt16 = 15, + PcmFloat = 16, + Adpcm = 17, + LightLimiter = 37, +}; + +enum class PerformanceState { + Invalid, + Start, + Stop, +}; + +/** + * Manages performance information. + * + * The performance buffer is split into frames, each comprised of: + * Frame header - Information about the number of entries/details and some others + * Entries - Created when starting to generate types of commands, such as voice + * commands, mix commands, sink commands etc. Details - Created for specific commands + * within each group. Up to MaxDetailEntries per frame. + * + * A current frame is written to by the AudioRenderer, and before it processes the next command + * list, the current frame is copied to a ringbuffer of history frames. These frames are then + * output back to the game if it supplies a performance buffer to RequestUpdate. + * + * Two versions currently exist, version 2 adds a few extra fields to the header, and a new + * SysDetail type which is seemingly unused. + */ +class PerformanceManager { +public: + static constexpr size_t MaxDetailEntries = 100; + + struct InParameter { + /* 0x00 */ s32 target_node_id; + /* 0x04 */ char unk04[0xC]; + }; + static_assert(sizeof(InParameter) == 0x10, + "PerformanceManager::InParameter has the wrong size!"); + + struct OutStatus { + /* 0x00 */ s32 history_size; + /* 0x04 */ char unk04[0xC]; + }; + static_assert(sizeof(OutStatus) == 0x10, "PerformanceManager::OutStatus has the wrong size!"); + + /** + * Calculate the required size for the performance workbuffer. + * + * @param behavior - Check which version is supported. + * @param params - Input parameters. + * @return Required workbuffer size. + */ + static u64 GetRequiredBufferSizeForPerformanceMetricsPerFrame( + const BehaviorInfo& behavior, const AudioRendererParameterInternal& params) { + u64 entry_count{params.voices + params.effects + params.sub_mixes + params.sinks + 1}; + switch (behavior.GetPerformanceMetricsDataFormat()) { + case 1: + return sizeof(PerformanceFrameHeaderVersion1) + + PerformanceManager::MaxDetailEntries * sizeof(PerformanceDetailVersion1) + + entry_count * sizeof(PerformanceEntryVersion1); + case 2: + return sizeof(PerformanceFrameHeaderVersion2) + + PerformanceManager::MaxDetailEntries * sizeof(PerformanceDetailVersion2) + + entry_count * sizeof(PerformanceEntryVersion2); + } + + LOG_WARNING(Service_Audio, "Invalid PerformanceMetrics version, assuming version 1"); + return sizeof(PerformanceFrameHeaderVersion1) + + PerformanceManager::MaxDetailEntries * sizeof(PerformanceDetailVersion1) + + entry_count * sizeof(PerformanceEntryVersion1); + } + + virtual ~PerformanceManager() = default; + + /** + * Initialize the performance manager. + * + * @param workbuffer - Workbuffer to use for performance frames. + * @param workbuffer_size - Size of the workbuffer. + * @param params - Input parameters. + * @param behavior - Behaviour to check version and data format. + * @param memory_pool - Used to translate the workbuffer address for the DSP. + */ + virtual void Initialize(std::span workbuffer, u64 workbuffer_size, + const AudioRendererParameterInternal& params, + const BehaviorInfo& behavior, const MemoryPoolInfo& memory_pool); + + /** + * Check if the manager is initialized. + * + * @return True if initialized, otherwise false. + */ + virtual bool IsInitialized() const; + + /** + * Copy the waiting performance frames to the output buffer. + * + * @param out_buffer - Output buffer to store performance frames. + * @param out_size - Size of the output buffer. + * @return Size in bytes that were written to the buffer. + */ + virtual u32 CopyHistories(u8* out_buffer, u64 out_size); + + /** + * Setup a new sys detail in the current frame, filling in addresses with offsets to the + * current workbuffer, to be written by the AudioRenderer. Note: This version is + * unused/incomplete. + * + * @param addresses - Filled with pointers to the new entry, which should be passed to + * the AudioRenderer with Performance commands to be written. + * @param unk - Unknown. + * @param sys_detail_type - Sys detail type. + * @param node_id - Node id for this entry. + * @return True if a new entry was created and the offsets are valid, otherwise false. + */ + virtual bool GetNextEntry(PerformanceEntryAddresses& addresses, u32** unk, + PerformanceSysDetailType sys_detail_type, s32 node_id); + + /** + * Setup a new entry in the current frame, filling in addresses with offsets to the current + * workbuffer, to be written by the AudioRenderer. + * + * @param addresses - Filled with pointers to the new entry, which should be passed to + * the AudioRenderer with Performance commands to be written. + * @param entry_type - The type of this entry. See PerformanceEntryType + * @param node_id - Node id for this entry. + * @return True if a new entry was created and the offsets are valid, otherwise false. + */ + virtual bool GetNextEntry(PerformanceEntryAddresses& addresses, PerformanceEntryType entry_type, + s32 node_id); + + /** + * Setup a new detail in the current frame, filling in addresses with offsets to the current + * workbuffer, to be written by the AudioRenderer. + * + * @param addresses - Filled with pointers to the new detail, which should be passed + * to the AudioRenderer with Performance commands to be written. + * @param entry_type - The type of this detail. See PerformanceEntryType + * @param node_id - Node id for this detail. + * @return True if a new detail was created and the offsets are valid, otherwise false. + */ + virtual bool GetNextEntry(PerformanceEntryAddresses& addresses, + PerformanceDetailType detail_type, PerformanceEntryType entry_type, + s32 node_id); + + /** + * Save the current frame to the ring buffer. + * + * @param dsp_behind - Did the AudioRenderer fall behind and not + * finish processing the command list? + * @param voices_dropped - The number of voices that were dropped. + * @param rendering_start_tick - The tick rendering started. + */ + virtual void TapFrame(bool dsp_behind, u32 voices_dropped, u64 rendering_start_tick); + + /** + * Check if the node id is a detail type. + * + * @return True if the node is a detail type, otherwise false. + */ + virtual bool IsDetailTarget(u32 target_node_id) const; + + /** + * Set the given node to be a detail type. + * + * @param target_node_id - Node to set. + */ + virtual void SetDetailTarget(u32 target_node_id); + +private: + /** + * Create the performance manager. + * + * @param version - Performance version to create. + */ + void CreateImpl(size_t version); + + std::unique_ptr + /// Impl for the performance manager, may be version 1 or 2. + impl; +}; + +template +class PerformanceManagerImpl : public PerformanceManager { +public: + void Initialize(std::span workbuffer, u64 workbuffer_size, + const AudioRendererParameterInternal& params, const BehaviorInfo& behavior, + const MemoryPoolInfo& memory_pool) override; + bool IsInitialized() const override; + u32 CopyHistories(u8* out_buffer, u64 out_size) override; + bool GetNextEntry(PerformanceEntryAddresses& addresses, u32** unk, + PerformanceSysDetailType sys_detail_type, s32 node_id) override; + bool GetNextEntry(PerformanceEntryAddresses& addresses, PerformanceEntryType entry_type, + s32 node_id) override; + bool GetNextEntry(PerformanceEntryAddresses& addresses, PerformanceDetailType detail_type, + PerformanceEntryType entry_type, s32 node_id) override; + void TapFrame(bool dsp_behind, u32 voices_dropped, u64 rendering_start_tick) override; + bool IsDetailTarget(u32 target_node_id) const override; + void SetDetailTarget(u32 target_node_id) override; + +private: + /// Workbuffer used to store the current performance frame + std::span workbuffer{}; + /// DSP address of the workbuffer, used by the AudioRenderer + CpuAddr translated_buffer{}; + /// Current frame index + u32 history_frame_index{}; + /// Current frame header + FrameHeaderVersion* frame_header{}; + /// Current frame entry buffer + std::span entry_buffer{}; + /// Current frame detail buffer + std::span detail_buffer{}; + /// Current frame entry count + u32 entry_count{}; + /// Current frame detail count + u32 detail_count{}; + /// Ringbuffer of previous frames + std::span frame_history{}; + /// Current history frame header + FrameHeaderVersion* frame_history_header{}; + /// Current history entry buffer + std::span frame_history_entries{}; + /// Current history detail buffer + std::span frame_history_details{}; + /// Current history ringbuffer write index + u32 output_frame_index{}; + /// Last history frame index that was written back to the game + u32 last_output_frame_index{}; + /// Maximum number of history frames in the ringbuffer + u32 max_frames{}; + /// Number of entries per frame + u32 entries_per_frame{}; + /// Maximum number of details per frame + u32 max_detail_count{}; + /// Frame size in bytes + u64 frame_size{}; + /// Is the performance manager initialized? + bool is_initialized{}; + /// Target node id + u32 target_node_id{}; + /// Performance version in use + PerformanceVersion version{}; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/sink/circular_buffer_sink_info.cpp b/src/audio_core/renderer/sink/circular_buffer_sink_info.cpp new file mode 100644 index 000000000..d91f10402 --- /dev/null +++ b/src/audio_core/renderer/sink/circular_buffer_sink_info.cpp @@ -0,0 +1,76 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/memory/pool_mapper.h" +#include "audio_core/renderer/sink/circular_buffer_sink_info.h" +#include "audio_core/renderer/upsampler/upsampler_manager.h" + +namespace AudioCore::AudioRenderer { + +CircularBufferSinkInfo::CircularBufferSinkInfo() { + state.fill(0); + parameter.fill(0); + type = Type::CircularBufferSink; + + auto state_{reinterpret_cast(state.data())}; + state_->address_info.Setup(0, 0); +} + +void CircularBufferSinkInfo::CleanUp() { + auto state_{reinterpret_cast(state.data())}; + + if (state_->upsampler_info) { + state_->upsampler_info->manager->Free(state_->upsampler_info); + state_->upsampler_info = nullptr; + } + + parameter.fill(0); + type = Type::Invalid; +} + +void CircularBufferSinkInfo::Update(BehaviorInfo::ErrorInfo& error_info, OutStatus& out_status, + const InParameter& in_params, const PoolMapper& pool_mapper) { + const auto buffer_params{ + reinterpret_cast(&in_params.circular_buffer)}; + auto current_params{reinterpret_cast(parameter.data())}; + auto current_state{reinterpret_cast(state.data())}; + + if (in_use == buffer_params->in_use && !buffer_unmapped) { + error_info.error_code = ResultSuccess; + error_info.address = CpuAddr(0); + out_status.writeOffset = current_state->last_pos2; + return; + } + + node_id = in_params.node_id; + in_use = in_params.in_use; + + if (in_use) { + buffer_unmapped = + !pool_mapper.TryAttachBuffer(error_info, current_state->address_info, + buffer_params->cpu_address, buffer_params->size); + *current_params = *buffer_params; + } else { + *current_params = *buffer_params; + } + out_status.writeOffset = current_state->last_pos2; +} + +void CircularBufferSinkInfo::UpdateForCommandGeneration() { + if (in_use) { + auto params{reinterpret_cast(parameter.data())}; + auto state_{reinterpret_cast(state.data())}; + + const auto pos{state_->current_pos}; + state_->last_pos2 = state_->last_pos; + state_->last_pos = pos; + + state_->current_pos += static_cast(params->input_count * params->sample_count * + GetSampleFormatByteSize(SampleFormat::PcmInt16)); + if (params->size > 0) { + state_->current_pos %= params->size; + } + } +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/sink/circular_buffer_sink_info.h b/src/audio_core/renderer/sink/circular_buffer_sink_info.h new file mode 100644 index 000000000..3356213ea --- /dev/null +++ b/src/audio_core/renderer/sink/circular_buffer_sink_info.h @@ -0,0 +1,41 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "audio_core/renderer/sink/sink_info_base.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +/** + * Info for a circular buffer sink. + */ +class CircularBufferSinkInfo : public SinkInfoBase { +public: + CircularBufferSinkInfo(); + + /** + * Clean up for info, resetting it to a default state. + */ + void CleanUp() override; + + /** + * Update the info according to parameters, and write the current state to out_status. + * + * @param error_info - Output error code. + * @param out_status - Output status. + * @param in_params - Input parameters. + * @param pool_mapper - Used to map the circular buffer. + */ + void Update(BehaviorInfo::ErrorInfo& error_info, OutStatus& out_status, + const InParameter& in_params, const PoolMapper& pool_mapper) override; + + /** + * Update the circular buffer on command generation, incrementing its current offsets. + */ + void UpdateForCommandGeneration() override; +}; +static_assert(sizeof(CircularBufferSinkInfo) <= sizeof(SinkInfoBase), + "CircularBufferSinkInfo is too large!"); + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/sink/device_sink_info.cpp b/src/audio_core/renderer/sink/device_sink_info.cpp new file mode 100644 index 000000000..b7b3d6f1d --- /dev/null +++ b/src/audio_core/renderer/sink/device_sink_info.cpp @@ -0,0 +1,57 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/sink/device_sink_info.h" +#include "audio_core/renderer/upsampler/upsampler_manager.h" + +namespace AudioCore::AudioRenderer { + +DeviceSinkInfo::DeviceSinkInfo() { + state.fill(0); + parameter.fill(0); + type = Type::DeviceSink; +} + +void DeviceSinkInfo::CleanUp() { + auto state_{reinterpret_cast(state.data())}; + + if (state_->upsampler_info) { + state_->upsampler_info->manager->Free(state_->upsampler_info); + state_->upsampler_info = nullptr; + } + + parameter.fill(0); + type = Type::Invalid; +} + +void DeviceSinkInfo::Update(BehaviorInfo::ErrorInfo& error_info, OutStatus& out_status, + const InParameter& in_params, + [[maybe_unused]] const PoolMapper& pool_mapper) { + + const auto device_params{reinterpret_cast(&in_params.device)}; + auto current_params{reinterpret_cast(parameter.data())}; + + if (in_use == in_params.in_use) { + current_params->downmix_enabled = device_params->downmix_enabled; + current_params->downmix_coeff = device_params->downmix_coeff; + } else { + type = in_params.type; + in_use = in_params.in_use; + node_id = in_params.node_id; + *current_params = *device_params; + } + + auto current_state{reinterpret_cast(state.data())}; + + for (size_t i = 0; i < current_state->downmix_coeff.size(); i++) { + current_state->downmix_coeff[i] = current_params->downmix_coeff[i]; + } + + std::memset(&out_status, 0, sizeof(OutStatus)); + error_info.error_code = ResultSuccess; + error_info.address = CpuAddr(0); +} + +void DeviceSinkInfo::UpdateForCommandGeneration() {} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/sink/device_sink_info.h b/src/audio_core/renderer/sink/device_sink_info.h new file mode 100644 index 000000000..a1c441454 --- /dev/null +++ b/src/audio_core/renderer/sink/device_sink_info.h @@ -0,0 +1,40 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "audio_core/renderer/sink/sink_info_base.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +/** + * Info for a device sink. + */ +class DeviceSinkInfo : public SinkInfoBase { +public: + DeviceSinkInfo(); + + /** + * Clean up for info, resetting it to a default state. + */ + void CleanUp() override; + + /** + * Update the info according to parameters, and write the current state to out_status. + * + * @param error_info - Output error code. + * @param out_status - Output status. + * @param in_params - Input parameters. + * @param pool_mapper - Unused. + */ + void Update(BehaviorInfo::ErrorInfo& error_info, OutStatus& out_status, + const InParameter& in_params, const PoolMapper& pool_mapper) override; + + /** + * Update the device sink on command generation, unused. + */ + void UpdateForCommandGeneration() override; +}; +static_assert(sizeof(DeviceSinkInfo) <= sizeof(SinkInfoBase), "DeviceSinkInfo is too large!"); + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/sink/sink_context.cpp b/src/audio_core/renderer/sink/sink_context.cpp new file mode 100644 index 000000000..634bc1cf9 --- /dev/null +++ b/src/audio_core/renderer/sink/sink_context.cpp @@ -0,0 +1,21 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/sink/sink_context.h" + +namespace AudioCore::AudioRenderer { + +void SinkContext::Initialize(std::span sink_infos_, const u32 sink_count_) { + sink_infos = sink_infos_; + sink_count = sink_count_; +} + +SinkInfoBase* SinkContext::GetInfo(const u32 index) { + return &sink_infos[index]; +} + +u32 SinkContext::GetCount() const { + return sink_count; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/sink/sink_context.h b/src/audio_core/renderer/sink/sink_context.h new file mode 100644 index 000000000..185572e29 --- /dev/null +++ b/src/audio_core/renderer/sink/sink_context.h @@ -0,0 +1,47 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "audio_core/renderer/sink/sink_info_base.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +/** + * Manages output sinks. + */ +class SinkContext { +public: + /** + * Initialize the sink context. + * + * @param sink_infos - Workbuffer for the sinks. + * @param sink_count - Number of sinks in the buffer. + */ + void Initialize(std::span sink_infos, u32 sink_count); + + /** + * Get a given index's info. + * + * @param index - Sink index to get. + * @return The sink info base for the given index. + */ + SinkInfoBase* GetInfo(u32 index); + + /** + * Get the current number of sinks. + * + * @return The number of sinks. + */ + u32 GetCount() const; + +private: + /// Buffer of sink infos + std::span sink_infos{}; + /// Number of sinks in the buffer + u32 sink_count{}; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/sink/sink_info_base.cpp b/src/audio_core/renderer/sink/sink_info_base.cpp new file mode 100644 index 000000000..4279beaa0 --- /dev/null +++ b/src/audio_core/renderer/sink/sink_info_base.cpp @@ -0,0 +1,51 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/memory/pool_mapper.h" +#include "audio_core/renderer/sink/sink_info_base.h" + +namespace AudioCore::AudioRenderer { + +void SinkInfoBase::CleanUp() { + type = Type::Invalid; +} + +void SinkInfoBase::Update(BehaviorInfo::ErrorInfo& error_info, OutStatus& out_status, + [[maybe_unused]] const InParameter& in_params, + [[maybe_unused]] const PoolMapper& pool_mapper) { + std::memset(&out_status, 0, sizeof(OutStatus)); + error_info.error_code = ResultSuccess; + error_info.address = CpuAddr(0); +} + +void SinkInfoBase::UpdateForCommandGeneration() {} + +SinkInfoBase::DeviceState* SinkInfoBase::GetDeviceState() { + return reinterpret_cast(state.data()); +} + +SinkInfoBase::Type SinkInfoBase::GetType() const { + return type; +} + +bool SinkInfoBase::IsUsed() const { + return in_use; +} + +bool SinkInfoBase::ShouldSkip() const { + return buffer_unmapped; +} + +u32 SinkInfoBase::GetNodeId() const { + return node_id; +} + +u8* SinkInfoBase::GetState() { + return state.data(); +} + +u8* SinkInfoBase::GetParameter() { + return parameter.data(); +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/sink/sink_info_base.h b/src/audio_core/renderer/sink/sink_info_base.h new file mode 100644 index 000000000..a1b855f20 --- /dev/null +++ b/src/audio_core/renderer/sink/sink_info_base.h @@ -0,0 +1,177 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "audio_core/common/common.h" +#include "audio_core/renderer/behavior/behavior_info.h" +#include "audio_core/renderer/memory/address_info.h" +#include "common/common_types.h" +#include "common/fixed_point.h" + +namespace AudioCore::AudioRenderer { +struct UpsamplerInfo; +class PoolMapper; + +/** + * Base for the circular buffer and device sinks, holding their states for the AudioRenderer and + * their parametetrs for generating sink commands. + */ +class SinkInfoBase { +public: + enum class Type : u8 { + Invalid, + DeviceSink, + CircularBufferSink, + }; + + struct DeviceInParameter { + /* 0x000 */ char name[0x100]; + /* 0x100 */ u32 input_count; + /* 0x104 */ std::array inputs; + /* 0x10A */ char unk10A[0x1]; + /* 0x10B */ bool downmix_enabled; + /* 0x10C */ std::array downmix_coeff; + }; + static_assert(sizeof(DeviceInParameter) == 0x11C, "DeviceInParameter has the wrong size!"); + + struct DeviceState { + /* 0x00 */ UpsamplerInfo* upsampler_info; + /* 0x08 */ std::array, 4> downmix_coeff; + /* 0x18 */ char unk18[0x18]; + }; + static_assert(sizeof(DeviceState) == 0x30, "DeviceState has the wrong size!"); + + struct CircularBufferInParameter { + /* 0x00 */ u64 cpu_address; + /* 0x08 */ u32 size; + /* 0x0C */ u32 input_count; + /* 0x10 */ u32 sample_count; + /* 0x14 */ u32 previous_pos; + /* 0x18 */ SampleFormat format; + /* 0x1C */ std::array inputs; + /* 0x22 */ bool in_use; + /* 0x23 */ char unk23[0x5]; + }; + static_assert(sizeof(CircularBufferInParameter) == 0x28, + "CircularBufferInParameter has the wrong size!"); + + struct CircularBufferState { + /* 0x00 */ u32 last_pos2; + /* 0x04 */ s32 current_pos; + /* 0x08 */ u32 last_pos; + /* 0x0C */ char unk0C[0x4]; + /* 0x10 */ AddressInfo address_info; + }; + static_assert(sizeof(CircularBufferState) == 0x30, "CircularBufferState has the wrong size!"); + + struct InParameter { + /* 0x000 */ Type type; + /* 0x001 */ bool in_use; + /* 0x004 */ u32 node_id; + /* 0x008 */ char unk08[0x18]; + union { + /* 0x020 */ DeviceInParameter device; + /* 0x020 */ CircularBufferInParameter circular_buffer; + }; + }; + static_assert(sizeof(InParameter) == 0x140, "SinkInfoBase::InParameter has the wrong size!"); + + struct OutStatus { + /* 0x00 */ u32 writeOffset; + /* 0x04 */ char unk04[0x1C]; + }; // size == 0x20 + static_assert(sizeof(OutStatus) == 0x20, "SinkInfoBase::OutStatus has the wrong size!"); + + virtual ~SinkInfoBase() = default; + + /** + * Clean up for info, resetting it to a default state. + */ + virtual void CleanUp(); + + /** + * Update the info according to parameters, and write the current state to out_status. + * + * @param error_info - Output error code. + * @param out_status - Output status. + * @param in_params - Input parameters. + * @param pool_mapper - Used to map the circular buffer. + */ + virtual void Update(BehaviorInfo::ErrorInfo& error_info, OutStatus& out_status, + [[maybe_unused]] const InParameter& in_params, + [[maybe_unused]] const PoolMapper& pool_mapper); + + /** + * Update the circular buffer on command generation, incrementing its current offsets. + */ + virtual void UpdateForCommandGeneration(); + + /** + * Get the state as a device sink. + * + * @return Device state. + */ + DeviceState* GetDeviceState(); + + /** + * Get the type of this sink. + * + * @return Either Device, Circular, or Invalid. + */ + Type GetType() const; + + /** + * Check if this sink is in use. + * + * @return True if used, otherwise false. + */ + bool IsUsed() const; + + /** + * Check if this sink should be skipped for updates. + * + * @return True if it should be skipped, otherwise false. + */ + bool ShouldSkip() const; + + /** + * Get the node if of this sink. + * + * @return Node id for this sink. + */ + u32 GetNodeId() const; + + /** + * Get the state of this sink. + * + * @return Pointer to the state, must be cast to the correct type. + */ + u8* GetState(); + + /** + * Get the parameters of this sink. + * + * @return Pointer to the parameters, must be cast to the correct type. + */ + u8* GetParameter(); + +protected: + /// Type of this sink + Type type{Type::Invalid}; + /// Is this sink in use? + bool in_use{}; + /// Is this sink's buffer unmapped? Circular only + bool buffer_unmapped{}; + /// Node id for this sink + u32 node_id{}; + /// State buffer for this sink + std::array state{}; + /// Parameter buffer for this sink + std::array + parameter{}; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/splitter/splitter_context.cpp b/src/audio_core/renderer/splitter/splitter_context.cpp new file mode 100644 index 000000000..7a23ba43f --- /dev/null +++ b/src/audio_core/renderer/splitter/splitter_context.cpp @@ -0,0 +1,217 @@ +// 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/common/workbuffer_allocator.h" +#include "audio_core/renderer/behavior/behavior_info.h" +#include "audio_core/renderer/splitter/splitter_context.h" +#include "common/alignment.h" + +namespace AudioCore::AudioRenderer { + +SplitterDestinationData* SplitterContext::GetDesintationData(const s32 splitter_id, + const s32 destination_id) { + return splitter_infos[splitter_id].GetData(destination_id); +} + +SplitterInfo& SplitterContext::GetInfo(const s32 splitter_id) { + return splitter_infos[splitter_id]; +} + +u32 SplitterContext::GetDataCount() const { + return destinations_count; +} + +u32 SplitterContext::GetInfoCount() const { + return info_count; +} + +SplitterDestinationData& SplitterContext::GetData(const u32 index) { + return splitter_destinations[index]; +} + +void SplitterContext::Setup(std::span splitter_infos_, const u32 splitter_info_count_, + SplitterDestinationData* splitter_destinations_, + const u32 destination_count_, const bool splitter_bug_fixed_) { + splitter_infos = splitter_infos_; + info_count = splitter_info_count_; + splitter_destinations = splitter_destinations_; + destinations_count = destination_count_; + splitter_bug_fixed = splitter_bug_fixed_; +} + +bool SplitterContext::UsingSplitter() const { + return splitter_infos.size() > 0 && info_count > 0 && splitter_destinations != nullptr && + destinations_count > 0; +} + +void SplitterContext::ClearAllNewConnectionFlag() { + for (s32 i = 0; i < info_count; i++) { + splitter_infos[i].SetNewConnectionFlag(); + } +} + +bool SplitterContext::Initialize(const BehaviorInfo& behavior, + const AudioRendererParameterInternal& params, + WorkbufferAllocator& allocator) { + if (behavior.IsSplitterSupported() && params.splitter_infos > 0 && + params.splitter_destinations > 0) { + splitter_infos = allocator.Allocate(params.splitter_infos, 0x10); + + for (u32 i = 0; i < params.splitter_infos; i++) { + std::construct_at(&splitter_infos[i], static_cast(i)); + } + + if (splitter_infos.size() == 0) { + splitter_infos = {}; + return false; + } + + splitter_destinations = + allocator.Allocate(params.splitter_destinations, 0x10).data(); + + for (s32 i = 0; i < params.splitter_destinations; i++) { + std::construct_at(&splitter_destinations[i], i); + } + + if (params.splitter_destinations <= 0) { + splitter_infos = {}; + splitter_destinations = nullptr; + return false; + } + + Setup(splitter_infos, params.splitter_infos, splitter_destinations, + params.splitter_destinations, behavior.IsSplitterBugFixed()); + } + return true; +} + +bool SplitterContext::Update(const u8* input, u32& consumed_size) { + auto in_params{reinterpret_cast(input)}; + + if (destinations_count == 0 || info_count == 0) { + consumed_size = 0; + return true; + } + + if (in_params->magic != GetSplitterInParamHeaderMagic()) { + consumed_size = 0; + return false; + } + + for (auto& splitter_info : splitter_infos) { + splitter_info.ClearNewConnectionFlag(); + } + + u32 offset{sizeof(InParameterHeader)}; + offset = UpdateInfo(input, offset, in_params->info_count); + offset = UpdateData(input, offset, in_params->destination_count); + + consumed_size = Common::AlignUp(offset, 0x10); + return true; +} + +u32 SplitterContext::UpdateInfo(const u8* input, u32 offset, const u32 splitter_count) { + for (u32 i = 0; i < splitter_count; i++) { + auto info_header{reinterpret_cast(input + offset)}; + + if (info_header->magic != GetSplitterInfoMagic()) { + continue; + } + + if (info_header->id < 0 || info_header->id > info_count) { + break; + } + + auto& info{splitter_infos[info_header->id]}; + RecomposeDestination(info, info_header); + + offset += info.Update(info_header); + } + + return offset; +} + +u32 SplitterContext::UpdateData(const u8* input, u32 offset, const u32 count) { + for (u32 i = 0; i < count; i++) { + auto data_header{ + reinterpret_cast(input + offset)}; + + if (data_header->magic != GetSplitterSendDataMagic()) { + continue; + } + + if (data_header->id < 0 || data_header->id > destinations_count) { + continue; + } + + splitter_destinations[data_header->id].Update(*data_header); + offset += sizeof(SplitterDestinationData::InParameter); + } + + return offset; +} + +void SplitterContext::UpdateInternalState() { + for (s32 i = 0; i < info_count; i++) { + splitter_infos[i].UpdateInternalState(); + } +} + +void SplitterContext::RecomposeDestination(SplitterInfo& out_info, + const SplitterInfo::InParameter* info_header) { + auto destination{out_info.GetData(0)}; + while (destination != nullptr) { + auto dest{destination->GetNext()}; + destination->SetNext(nullptr); + destination = dest; + } + out_info.SetDestinations(nullptr); + + auto dest_count{info_header->destination_count}; + if (!splitter_bug_fixed) { + dest_count = std::min(dest_count, GetDestCountPerInfoForCompat()); + } + + if (dest_count == 0) { + return; + } + + std::span destination_ids{reinterpret_cast(&info_header[1]), dest_count}; + + auto head{&splitter_destinations[destination_ids[0]]}; + auto current_destination{head}; + for (u32 i = 1; i < dest_count; i++) { + auto next_destination{&splitter_destinations[destination_ids[i]]}; + current_destination->SetNext(next_destination); + current_destination = next_destination; + } + + out_info.SetDestinations(head); + out_info.SetDestinationCount(dest_count); +} + +u32 SplitterContext::GetDestCountPerInfoForCompat() const { + if (info_count <= 0) { + return 0; + } + return static_cast(destinations_count / info_count); +} + +u64 SplitterContext::CalcWorkBufferSize(const BehaviorInfo& behavior, + const AudioRendererParameterInternal& params) { + u64 size{0}; + if (!behavior.IsSplitterSupported()) { + return size; + } + + size += params.splitter_destinations * sizeof(SplitterDestinationData) + + params.splitter_infos * sizeof(SplitterInfo); + + if (behavior.IsSplitterBugFixed()) { + size += Common::AlignUp(params.splitter_destinations * sizeof(u32), 0x10); + } + return size; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/splitter/splitter_context.h b/src/audio_core/renderer/splitter/splitter_context.h new file mode 100644 index 000000000..cfd092b4f --- /dev/null +++ b/src/audio_core/renderer/splitter/splitter_context.h @@ -0,0 +1,189 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "audio_core/renderer/splitter/splitter_destinations_data.h" +#include "audio_core/renderer/splitter/splitter_info.h" +#include "common/common_types.h" + +namespace AudioCore { +struct AudioRendererParameterInternal; +class WorkbufferAllocator; + +namespace AudioRenderer { +class BehaviorInfo; + +/** + * The splitter allows much more control over how sound is mixed together. + * Previously, one mix can only connect to one other, and you may need + * more mixes (and duplicate processing) to achieve the same result. + * With the splitter, many-to-one and one-to-many mixing is possible. + * This was added in revision 2. + * Had a bug with incorrect numbers of destinations, fixed in revision 5. + */ +class SplitterContext { + struct InParameterHeader { + /* 0x00 */ u32 magic; // 'SNDH' + /* 0x04 */ s32 info_count; + /* 0x08 */ s32 destination_count; + /* 0x0C */ char unk0C[0x14]; + }; + static_assert(sizeof(InParameterHeader) == 0x20, + "SplitterContext::InParameterHeader has the wrong size!"); + +public: + /** + * Get a destination mix from the given splitter and destination index. + * + * @param splitter_id - Splitter index to get from. + * @param destination_id - Destination index within the splitter. + * @return Pointer to the found destination. May be nullptr. + */ + SplitterDestinationData* GetDesintationData(s32 splitter_id, s32 destination_id); + + /** + * Get a splitter from the given index. + * + * @param index - Index of the desired splitter. + * @return Splitter requested. + */ + SplitterInfo& GetInfo(s32 index); + + /** + * Get the total number of splitter destinations. + * + * @return Number of destiantions. + */ + u32 GetDataCount() const; + + /** + * Get the total number of splitters. + * + * @return Number of splitters. + */ + u32 GetInfoCount() const; + + /** + * Get a specific global destination. + * + * @param index - Index of the desired destination. + * @return The requested destination. + */ + SplitterDestinationData& GetData(u32 index); + + /** + * Check if the splitter is in use. + * + * @return True if any splitter or destination is in use, otherwise false. + */ + bool UsingSplitter() const; + + /** + * Mark all splitters as having new connections. + */ + void ClearAllNewConnectionFlag(); + + /** + * Initialize the context. + * + * @param behavior - Used to check for splitter support. + * @param params - Input parameters. + * @param allocator - Allocator used to allocate workbuffer memory. + */ + bool Initialize(const BehaviorInfo& behavior, const AudioRendererParameterInternal& params, + WorkbufferAllocator& allocator); + + /** + * Update the context. + * + * @param input - Input buffer with the new info, + * expected to point to a InParameterHeader. + * @param consumed_size - Output with the number of bytes consumed from input. + */ + bool Update(const u8* input, u32& consumed_size); + + /** + * Update the splitters. + * + * @param input - Input buffer with the new info. + * @param offset - Current offset within the input buffer, + * input + offset should point to a SplitterInfo::InParameter. + * @param splitter_count - Number of splitters in the input buffer. + * @return Number of bytes consumed in input. + */ + u32 UpdateInfo(const u8* input, u32 offset, u32 splitter_count); + + /** + * Update the splitters. + * + * @param input - Input buffer with the new info. + * @param offset - Current offset within the input buffer, + * input + offset should point to a + * SplitterDestinationData::InParameter. + * @param destination_count - Number of destinations in the input buffer. + * @return Number of bytes consumed in input. + */ + u32 UpdateData(const u8* input, u32 offset, u32 destination_count); + + /** + * Update the state of all destinations in all splitters. + */ + void UpdateInternalState(); + + /** + * Replace the given splitter's destinations with new ones. + * + * @param out_info - Splitter to recompose. + * @param info_header - Input parameters containing new destination ids. + */ + void RecomposeDestination(SplitterInfo& out_info, const SplitterInfo::InParameter* info_header); + + /** + * Old calculation for destinations, this is the thing the splitter bug fixes. + * Left for compatibility, and now min'd with the actual count to not bug. + * + * @return Number of splitter destinations. + */ + u32 GetDestCountPerInfoForCompat() const; + + /** + * Calculate the size of the required workbuffer for splitters and destinations. + * + * @param behavior - Used to check splitter features. + * @param params - Input parameters with splitter/destination counts. + * @return Required buffer size. + */ + static u64 CalcWorkBufferSize(const BehaviorInfo& behavior, + const AudioRendererParameterInternal& params); + +private: + /** + * Setup the context. + * + * @param splitter_infos - Workbuffer for splitters. + * @param splitter_info_count - Number of splitters in the workbuffer. + * @param splitter_destinations - Workbuffer for splitter destinations. + * @param destination_count - Number of destinations in the workbuffer. + * @param splitter_bug_fixed - Is the splitter bug fixed? + */ + void Setup(std::span splitter_infos, u32 splitter_info_count, + SplitterDestinationData* splitter_destinations, u32 destination_count, + bool splitter_bug_fixed); + + /// Workbuffer for splitters + std::span splitter_infos{}; + /// Number of splitters in buffer + s32 info_count{}; + /// Workbuffer for destinations + SplitterDestinationData* splitter_destinations{}; + /// Number of destinations in buffer + s32 destinations_count{}; + /// Is the splitter bug fixed? + bool splitter_bug_fixed{}; +}; + +} // namespace AudioRenderer +} // namespace AudioCore diff --git a/src/audio_core/renderer/splitter/splitter_destinations_data.cpp b/src/audio_core/renderer/splitter/splitter_destinations_data.cpp new file mode 100644 index 000000000..b27d44896 --- /dev/null +++ b/src/audio_core/renderer/splitter/splitter_destinations_data.cpp @@ -0,0 +1,87 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/splitter/splitter_destinations_data.h" + +namespace AudioCore::AudioRenderer { + +SplitterDestinationData::SplitterDestinationData(const s32 id_) : id{id_} {} + +void SplitterDestinationData::ClearMixVolume() { + mix_volumes.fill(0.0f); + prev_mix_volumes.fill(0.0f); +} + +s32 SplitterDestinationData::GetId() const { + return id; +} + +bool SplitterDestinationData::IsConfigured() const { + return in_use && destination_id != UnusedMixId; +} + +s32 SplitterDestinationData::GetMixId() const { + return destination_id; +} + +f32 SplitterDestinationData::GetMixVolume(const u32 index) const { + if (index >= mix_volumes.size()) { + LOG_ERROR(Service_Audio, "SplitterDestinationData::GetMixVolume Invalid index {}", index); + return 0.0f; + } + return mix_volumes[index]; +} + +std::span SplitterDestinationData::GetMixVolume() { + return mix_volumes; +} + +f32 SplitterDestinationData::GetMixVolumePrev(const u32 index) const { + if (index >= prev_mix_volumes.size()) { + LOG_ERROR(Service_Audio, "SplitterDestinationData::GetMixVolumePrev Invalid index {}", + index); + return 0.0f; + } + return prev_mix_volumes[index]; +} + +std::span SplitterDestinationData::GetMixVolumePrev() { + return prev_mix_volumes; +} + +void SplitterDestinationData::Update(const InParameter& params) { + if (params.id != id || params.magic != GetSplitterSendDataMagic()) { + return; + } + + destination_id = params.mix_id; + mix_volumes = params.mix_volumes; + + if (!in_use && params.in_use) { + prev_mix_volumes = mix_volumes; + need_update = false; + } + + in_use = params.in_use; +} + +void SplitterDestinationData::MarkAsNeedToUpdateInternalState() { + need_update = true; +} + +void SplitterDestinationData::UpdateInternalState() { + if (in_use && need_update) { + prev_mix_volumes = mix_volumes; + } + need_update = false; +} + +SplitterDestinationData* SplitterDestinationData::GetNext() const { + return next; +} + +void SplitterDestinationData::SetNext(SplitterDestinationData* next_) { + next = next_; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/splitter/splitter_destinations_data.h b/src/audio_core/renderer/splitter/splitter_destinations_data.h new file mode 100644 index 000000000..bd3d55748 --- /dev/null +++ b/src/audio_core/renderer/splitter/splitter_destinations_data.h @@ -0,0 +1,135 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#include "audio_core/common/common.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +/** + * Represents a mixing node, can be connected to a previous and next destination forming a chain + * that a certain mix buffer will pass through to output. + */ +class SplitterDestinationData { +public: + struct InParameter { + /* 0x00 */ u32 magic; // 'SNDD' + /* 0x04 */ s32 id; + /* 0x08 */ std::array mix_volumes; + /* 0x68 */ u32 mix_id; + /* 0x6C */ bool in_use; + }; + static_assert(sizeof(InParameter) == 0x70, + "SplitterDestinationData::InParameter has the wrong size!"); + + SplitterDestinationData(s32 id); + + /** + * Reset the mix volumes for this destination. + */ + void ClearMixVolume(); + + /** + * Get the id of this destination. + * + * @return Id for this destination. + */ + s32 GetId() const; + + /** + * Check if this destination is correctly configured. + * + * @return True if configured, otherwise false. + */ + bool IsConfigured() const; + + /** + * Get the mix id for this destination. + * + * @return Mix id for this destination. + */ + s32 GetMixId() const; + + /** + * Get the current mix volume of a given index in this destination. + * + * @param index - Mix buffer index to get the volume for. + * @return Current volume of the specified mix. + */ + f32 GetMixVolume(u32 index) const; + + /** + * Get the current mix volumes for all mix buffers in this destination. + * + * @return Span of current mix buffer volumes. + */ + std::span GetMixVolume(); + + /** + * Get the previous mix volume of a given index in this destination. + * + * @param index - Mix buffer index to get the volume for. + * @return Previous volume of the specified mix. + */ + f32 GetMixVolumePrev(u32 index) const; + + /** + * Get the previous mix volumes for all mix buffers in this destination. + * + * @return Span of previous mix buffer volumes. + */ + std::span GetMixVolumePrev(); + + /** + * Update this destination. + * + * @param params - Inpout parameters to update the destination. + */ + void Update(const InParameter& params); + + /** + * Mark this destination as needing its volumes updated. + */ + void MarkAsNeedToUpdateInternalState(); + + /** + * Copy current volumes to previous if an update is required. + */ + void UpdateInternalState(); + + /** + * Get the next destination in the mix chain. + * + * @return The next splitter destination, may be nullptr if this is the last in the chain. + */ + SplitterDestinationData* GetNext() const; + + /** + * Set the next destination in the mix chain. + * + * @param next - Destination this one is to be connected to. + */ + void SetNext(SplitterDestinationData* next); + +private: + /// Id of this destination + const s32 id; + /// Mix id this destination represents + s32 destination_id{UnusedMixId}; + /// Current mix volumes + std::array mix_volumes{0.0f}; + /// Previous mix volumes + std::array prev_mix_volumes{0.0f}; + /// Next destination in the mix chain + SplitterDestinationData* next{}; + /// Is this destiantion in use? + bool in_use{}; + /// Does this destiantion need its volumes updated? + bool need_update{}; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/splitter/splitter_info.cpp b/src/audio_core/renderer/splitter/splitter_info.cpp new file mode 100644 index 000000000..1aee6720b --- /dev/null +++ b/src/audio_core/renderer/splitter/splitter_info.cpp @@ -0,0 +1,79 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/splitter/splitter_info.h" + +namespace AudioCore::AudioRenderer { + +SplitterInfo::SplitterInfo(const s32 id_) : id{id_} {} + +void SplitterInfo::InitializeInfos(SplitterInfo* splitters, const u32 count) { + if (splitters == nullptr) { + return; + } + + for (u32 i = 0; i < count; i++) { + auto& splitter{splitters[i]}; + splitter.destinations = nullptr; + splitter.destination_count = 0; + splitter.has_new_connection = true; + } +} + +u32 SplitterInfo::Update(const InParameter* params) { + if (params->id != id) { + return 0; + } + sample_rate = params->sample_rate; + has_new_connection = true; + return static_cast((sizeof(InParameter) + 3 * sizeof(s32)) + + params->destination_count * sizeof(s32)); +} + +SplitterDestinationData* SplitterInfo::GetData(const u32 destination_id) { + auto out_destination{destinations}; + u32 i{0}; + while (i < destination_id) { + if (out_destination == nullptr) { + break; + } + out_destination = out_destination->GetNext(); + i++; + } + + return out_destination; +} + +u32 SplitterInfo::GetDestinationCount() const { + return destination_count; +} + +void SplitterInfo::SetDestinationCount(const u32 count) { + destination_count = count; +} + +bool SplitterInfo::HasNewConnection() const { + return has_new_connection; +} + +void SplitterInfo::ClearNewConnectionFlag() { + has_new_connection = false; +} + +void SplitterInfo::SetNewConnectionFlag() { + has_new_connection = true; +} + +void SplitterInfo::UpdateInternalState() { + auto destination{destinations}; + while (destination != nullptr) { + destination->UpdateInternalState(); + destination = destination->GetNext(); + } +} + +void SplitterInfo::SetDestinations(SplitterDestinationData* destinations_) { + destinations = destinations_; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/splitter/splitter_info.h b/src/audio_core/renderer/splitter/splitter_info.h new file mode 100644 index 000000000..d1d75064c --- /dev/null +++ b/src/audio_core/renderer/splitter/splitter_info.h @@ -0,0 +1,107 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "audio_core/renderer/splitter/splitter_destinations_data.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +/** + * Represents a splitter, wraps multiple output destinations to split an input mix into. + */ +class SplitterInfo { +public: + struct InParameter { + /* 0x00 */ u32 magic; // 'SNDI' + /* 0x04 */ s32 id; + /* 0x08 */ u32 sample_rate; + /* 0x0C */ u32 destination_count; + }; + static_assert(sizeof(InParameter) == 0x10, "SplitterInfo::InParameter has the wrong size!"); + + explicit SplitterInfo(s32 id); + + /** + * Initialize the given splitters. + * + * @param splitters - Splitters to initialize. + * @param count - Number of splitters given. + */ + static void InitializeInfos(SplitterInfo* splitters, u32 count); + + /** + * Update this splitter. + * + * @param params - Input parameters to update with. + * @return The size in bytes of this splitter. + */ + u32 Update(const InParameter* params); + + /** + * Get a destination in this splitter. + * + * @param id - Destination id to get. + * @return Pointer to the destination, may be nullptr. + */ + SplitterDestinationData* GetData(u32 id); + + /** + * Get the number of destinations in this splitter. + * + * @return The number of destiantions. + */ + u32 GetDestinationCount() const; + + /** + * Set the number of destinations in this splitter. + * + * @param count - The new number of destiantions. + */ + void SetDestinationCount(u32 count); + + /** + * Check if the splitter has a new connection. + * + * @return True if there is a new connection, otherwise false. + */ + bool HasNewConnection() const; + + /** + * Reset the new connection flag. + */ + void ClearNewConnectionFlag(); + + /** + * Mark as having a new connection. + */ + void SetNewConnectionFlag(); + + /** + * Update the state of all destinations. + */ + void UpdateInternalState(); + + /** + * Set this splitter's destinations. + * + * @param destinations - The new destination list for this splitter. + */ + void SetDestinations(SplitterDestinationData* destinations); + +private: + /// Id of this splitter + s32 id; + /// Sample rate of this splitter + u32 sample_rate{}; + /// Number of destinations in this splitter + u32 destination_count{}; + /// Does this splitter have a new connection? + bool has_new_connection{true}; + /// Pointer to the destinations of this splitter + SplitterDestinationData* destinations{}; + /// Number of channels this splitter manages + u32 channel_count{}; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/system.cpp b/src/audio_core/renderer/system.cpp new file mode 100644 index 000000000..7a217969e --- /dev/null +++ b/src/audio_core/renderer/system.cpp @@ -0,0 +1,802 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include + +#include "audio_core/audio_core.h" +#include "audio_core/common/audio_renderer_parameter.h" +#include "audio_core/common/common.h" +#include "audio_core/common/feature_support.h" +#include "audio_core/common/workbuffer_allocator.h" +#include "audio_core/renderer/adsp/adsp.h" +#include "audio_core/renderer/behavior/info_updater.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/effect_info_base.h" +#include "audio_core/renderer/effect/effect_result_state.h" +#include "audio_core/renderer/memory/memory_pool_info.h" +#include "audio_core/renderer/memory/pool_mapper.h" +#include "audio_core/renderer/mix/mix_info.h" +#include "audio_core/renderer/nodes/edge_matrix.h" +#include "audio_core/renderer/nodes/node_states.h" +#include "audio_core/renderer/sink/sink_info_base.h" +#include "audio_core/renderer/system.h" +#include "audio_core/renderer/upsampler/upsampler_info.h" +#include "audio_core/renderer/voice/voice_channel_resource.h" +#include "audio_core/renderer/voice/voice_info.h" +#include "audio_core/renderer/voice/voice_state.h" +#include "common/alignment.h" +#include "core/core.h" +#include "core/core_timing.h" +#include "core/hle/kernel/k_event.h" +#include "core/hle/kernel/k_transfer_memory.h" +#include "core/memory.h" + +namespace AudioCore::AudioRenderer { + +u64 System::GetWorkBufferSize(const AudioRendererParameterInternal& params) { + BehaviorInfo behavior; + behavior.SetUserLibRevision(params.revision); + + u64 size{0}; + + size += Common::AlignUp(params.mixes * sizeof(s32), 0x40); + size += params.sub_mixes * MaxEffects * sizeof(s32); + size += (params.sub_mixes + 1) * sizeof(MixInfo); + size += params.voices * (sizeof(VoiceInfo) + sizeof(VoiceChannelResource) + sizeof(VoiceState)); + size += Common::AlignUp((params.sub_mixes + 1) * sizeof(MixInfo*), 0x10); + size += Common::AlignUp(params.voices * sizeof(VoiceInfo*), 0x10); + size += Common::AlignUp(((params.sinks + params.sub_mixes) * TargetSampleCount * sizeof(s32) + + params.sample_count * sizeof(s32)) * + (params.mixes + MaxChannels), + 0x40); + + if (behavior.IsSplitterSupported()) { + const auto node_size{NodeStates::GetWorkBufferSize(params.sub_mixes + 1)}; + const auto edge_size{EdgeMatrix::GetWorkBufferSize(params.sub_mixes + 1)}; + size += Common::AlignUp(node_size + edge_size, 0x10); + } + + size += SplitterContext::CalcWorkBufferSize(behavior, params); + size += (params.effects + params.voices * MaxWaveBuffers) * sizeof(MemoryPoolInfo); + + if (behavior.IsEffectInfoVersion2Supported()) { + size += params.effects * sizeof(EffectResultState); + } + size += 0x50; + + size = Common::AlignUp(size, 0x40); + + size += (params.sinks + params.sub_mixes) * sizeof(UpsamplerInfo); + size += params.effects * sizeof(EffectInfoBase); + size += Common::AlignUp(params.voices * sizeof(VoiceState), 0x40); + size += params.sinks * sizeof(SinkInfoBase); + + if (behavior.IsEffectInfoVersion2Supported()) { + size += params.effects * sizeof(EffectResultState); + } + + if (params.perf_frames > 0) { + auto perf_size{PerformanceManager::GetRequiredBufferSizeForPerformanceMetricsPerFrame( + behavior, params)}; + size += Common::AlignUp(perf_size * (params.perf_frames + 1) + 0xC0, 0x100); + } + + if (behavior.IsVariadicCommandBufferSizeSupported()) { + size += CommandGenerator::CalculateCommandBufferSize(behavior, params) + (0x40 - 1) * 2; + } else { + size += 0x18000 + (0x40 - 1) * 2; + } + + size = Common::AlignUp(size, 0x1000); + return size; +} + +System::System(Core::System& core_, Kernel::KEvent* adsp_rendered_event_) + : core{core_}, adsp{core.AudioCore().GetADSP()}, adsp_rendered_event{adsp_rendered_event_} {} + +Result System::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 (!CheckValidRevision(params.revision)) { + return Service::Audio::ERR_INVALID_REVISION; + } + + if (GetWorkBufferSize(params) > transfer_memory_size) { + return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE; + } + + if (process_handle_ == 0) { + return Service::Audio::ERR_INVALID_PROCESS_HANDLE; + } + + behavior.SetUserLibRevision(params.revision); + + process_handle = process_handle_; + applet_resource_user_id = applet_resource_user_id_; + session_id = session_id_; + + sample_rate = params.sample_rate; + sample_count = params.sample_count; + mix_buffer_count = static_cast(params.mixes); + voice_channels = MaxChannels; + upsampler_count = params.sinks + params.sub_mixes; + memory_pool_count = params.effects + params.voices * MaxWaveBuffers; + render_device = params.rendering_device; + execution_mode = params.execution_mode; + + core.Memory().ZeroBlock(*core.Kernel().CurrentProcess(), transfer_memory->GetSourceAddress(), + transfer_memory_size); + + // Note: We're not actually using the transfer memory because it's a pain to code for. + // Allocate the memory normally instead and hope the game doesn't try to read anything back + workbuffer = std::make_unique(transfer_memory_size); + workbuffer_size = transfer_memory_size; + + PoolMapper pool_mapper(process_handle, false); + pool_mapper.InitializeSystemPool(memory_pool_info, workbuffer.get(), workbuffer_size); + + WorkbufferAllocator allocator({workbuffer.get(), workbuffer_size}, workbuffer_size); + + samples_workbuffer = + allocator.Allocate((voice_channels + mix_buffer_count) * sample_count, 0x10); + if (samples_workbuffer.empty()) { + return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE; + } + + auto upsampler_workbuffer{allocator.Allocate( + (voice_channels + mix_buffer_count) * TargetSampleCount * upsampler_count, 0x10)}; + if (upsampler_workbuffer.empty()) { + return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE; + } + + depop_buffer = + allocator.Allocate(Common::AlignUp(static_cast(mix_buffer_count), 0x40), 0x40); + if (depop_buffer.empty()) { + return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE; + } + + // invalidate samples_workbuffer DSP cache + + auto voice_infos{allocator.Allocate(params.voices, 0x10)}; + for (auto& voice_info : voice_infos) { + std::construct_at(&voice_info); + } + + if (voice_infos.empty()) { + return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE; + } + + auto sorted_voice_infos{allocator.Allocate(params.voices, 0x10)}; + if (sorted_voice_infos.empty()) { + return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE; + } + + std::memset(sorted_voice_infos.data(), 0, sorted_voice_infos.size_bytes()); + + auto voice_channel_resources{allocator.Allocate(params.voices, 0x10)}; + u32 i{0}; + for (auto& voice_channel_resource : voice_channel_resources) { + std::construct_at(&voice_channel_resource, i++); + } + + if (voice_channel_resources.empty()) { + return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE; + } + + auto voice_cpu_states{allocator.Allocate(params.voices, 0x10)}; + if (voice_cpu_states.empty()) { + return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE; + } + + for (auto& voice_state : voice_cpu_states) { + voice_state = {}; + } + + auto mix_infos{allocator.Allocate(params.sub_mixes + 1, 0x10)}; + + if (mix_infos.empty()) { + return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE; + } + + u32 effect_process_order_count{0}; + std::span effect_process_order_buffer{}; + + if (params.effects > 0) { + effect_process_order_count = params.effects * (params.sub_mixes + 1); + effect_process_order_buffer = allocator.Allocate(effect_process_order_count, 0x10); + if (effect_process_order_buffer.empty()) { + return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE; + } + } + + i = 0; + for (auto& mix_info : mix_infos) { + std::construct_at( + &mix_info, effect_process_order_buffer.subspan(i * params.effects, params.effects), + params.effects, this->behavior); + i++; + } + + auto sorted_mix_infos{allocator.Allocate(params.sub_mixes + 1, 0x10)}; + if (sorted_mix_infos.empty()) { + return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE; + } + + std::memset(sorted_mix_infos.data(), 0, sorted_mix_infos.size_bytes()); + + if (behavior.IsSplitterSupported()) { + u64 node_state_size{NodeStates::GetWorkBufferSize(params.sub_mixes + 1)}; + u64 edge_matrix_size{EdgeMatrix::GetWorkBufferSize(params.sub_mixes + 1)}; + + auto node_states_workbuffer{allocator.Allocate(node_state_size, 1)}; + auto edge_matrix_workbuffer{allocator.Allocate(edge_matrix_size, 1)}; + + if (node_states_workbuffer.empty() || edge_matrix_workbuffer.size() == 0) { + return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE; + } + + mix_context.Initialize(sorted_mix_infos, mix_infos, params.sub_mixes + 1, + effect_process_order_buffer, effect_process_order_count, + node_states_workbuffer, node_state_size, edge_matrix_workbuffer, + edge_matrix_size); + } else { + mix_context.Initialize(sorted_mix_infos, mix_infos, params.sub_mixes + 1, + effect_process_order_buffer, effect_process_order_count, {}, 0, {}, + 0); + } + + upsampler_manager = allocator.Allocate(1, 0x10).data(); + if (upsampler_manager == nullptr) { + return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE; + } + + memory_pool_workbuffer = allocator.Allocate(memory_pool_count, 0x10); + for (auto& memory_pool : memory_pool_workbuffer) { + std::construct_at(&memory_pool, MemoryPoolInfo::Location::DSP); + } + + if (memory_pool_workbuffer.empty() && memory_pool_count > 0) { + return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE; + } + + if (!splitter_context.Initialize(behavior, params, allocator)) { + return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE; + } + + std::span effect_result_states_cpu{}; + if (behavior.IsEffectInfoVersion2Supported() && params.effects > 0) { + effect_result_states_cpu = allocator.Allocate(params.effects, 0x10); + if (effect_result_states_cpu.empty()) { + return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE; + } + std::memset(effect_result_states_cpu.data(), 0, effect_result_states_cpu.size_bytes()); + } + + allocator.Align(0x40); + + unk_2B0 = allocator.GetSize() - allocator.GetCurrentOffset(); + unk_2A8 = {&workbuffer[allocator.GetCurrentOffset()], unk_2B0}; + + upsampler_infos = allocator.Allocate(upsampler_count, 0x40); + for (auto& upsampler_info : upsampler_infos) { + std::construct_at(&upsampler_info); + } + + std::construct_at(upsampler_manager, upsampler_count, upsampler_infos, + upsampler_workbuffer); + + if (upsampler_infos.empty()) { + return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE; + } + + auto effect_infos{allocator.Allocate(params.effects, 0x40)}; + for (auto& effect_info : effect_infos) { + std::construct_at(&effect_info); + } + + if (effect_infos.empty() && params.effects > 0) { + return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE; + } + + std::span effect_result_states_dsp{}; + if (behavior.IsEffectInfoVersion2Supported() && params.effects > 0) { + effect_result_states_dsp = allocator.Allocate(params.effects, 0x40); + if (effect_result_states_dsp.empty()) { + return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE; + } + std::memset(effect_result_states_dsp.data(), 0, effect_result_states_dsp.size_bytes()); + } + + effect_context.Initialize(effect_infos, params.effects, effect_result_states_cpu, + effect_result_states_dsp, effect_result_states_dsp.size()); + + auto sinks{allocator.Allocate(params.sinks, 0x10)}; + for (auto& sink : sinks) { + std::construct_at(&sink); + } + + if (sinks.empty()) { + return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE; + } + + sink_context.Initialize(sinks, params.sinks); + + auto voice_dsp_states{allocator.Allocate(params.voices, 0x40)}; + if (voice_dsp_states.empty()) { + return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE; + } + + for (auto& voice_state : voice_dsp_states) { + voice_state = {}; + } + + voice_context.Initialize(sorted_voice_infos, voice_infos, voice_channel_resources, + voice_cpu_states, voice_dsp_states, params.voices); + + if (params.perf_frames > 0) { + const auto perf_workbuffer_size{ + PerformanceManager::GetRequiredBufferSizeForPerformanceMetricsPerFrame(behavior, + params) * + (params.perf_frames + 1) + + 0xC}; + performance_workbuffer = allocator.Allocate(perf_workbuffer_size, 0x40); + if (performance_workbuffer.empty()) { + return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE; + } + std::memset(performance_workbuffer.data(), 0, performance_workbuffer.size_bytes()); + performance_manager.Initialize(performance_workbuffer, performance_workbuffer.size_bytes(), + params, behavior, memory_pool_info); + } + + render_time_limit_percent = 100; + drop_voice = params.voice_drop_enabled && params.execution_mode == ExecutionMode::Auto; + + allocator.Align(0x40); + command_workbuffer_size = allocator.GetRemainingSize(); + command_workbuffer = allocator.Allocate(command_workbuffer_size, 0x40); + if (command_workbuffer.empty()) { + return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE; + } + + command_buffer_size = 0; + reset_command_buffers = true; + + // nn::audio::dsp::FlushDataCache(transferMemory, transferMemorySize); + + if (behavior.IsCommandProcessingTimeEstimatorVersion5Supported()) { + command_processing_time_estimator = + std::make_unique(sample_count, + mix_buffer_count); + } else if (behavior.IsCommandProcessingTimeEstimatorVersion4Supported()) { + command_processing_time_estimator = + std::make_unique(sample_count, + mix_buffer_count); + } else if (behavior.IsCommandProcessingTimeEstimatorVersion3Supported()) { + command_processing_time_estimator = + std::make_unique(sample_count, + mix_buffer_count); + } else if (behavior.IsCommandProcessingTimeEstimatorVersion2Supported()) { + command_processing_time_estimator = + std::make_unique(sample_count, + mix_buffer_count); + } else { + command_processing_time_estimator = + std::make_unique(sample_count, + mix_buffer_count); + } + + initialized = true; + return ResultSuccess; +} + +void System::Finalize() { + if (!initialized) { + return; + } + + if (active) { + Stop(); + } + + applet_resource_user_id = 0; + + PoolMapper pool_mapper(process_handle, false); + pool_mapper.Unmap(memory_pool_info); + + if (process_handle) { + pool_mapper.ClearUseState(memory_pool_workbuffer, memory_pool_count); + for (auto& memory_pool : memory_pool_workbuffer) { + if (memory_pool.IsMapped()) { + pool_mapper.Unmap(memory_pool); + } + } + + // dsp::ProcessCleanup + // close handle + } + initialized = false; +} + +void System::Start() { + std::scoped_lock l{lock}; + frames_elapsed = 0; + state = State::Started; + active = true; +} + +void System::Stop() { + { + std::scoped_lock l{lock}; + state = State::Stopped; + active = false; + } + + if (execution_mode == ExecutionMode::Auto) { + // Should wait for the system to terminate here, but core timing (should have) already + // stopped, so this isn't needed. Find a way to make this definite. + + // terminate_event.Wait(); + } +} + +Result System::Update(std::span input, std::span performance, std::span output) { + std::scoped_lock l{lock}; + + const auto start_time{core.CoreTiming().GetClockTicks()}; + + InfoUpdater info_updater(input, output, process_handle, behavior); + + auto result{info_updater.UpdateBehaviorInfo(behavior)}; + if (result.IsError()) { + LOG_ERROR(Service_Audio, "Failed to update BehaviorInfo!"); + return result; + } + + result = info_updater.UpdateMemoryPools(memory_pool_workbuffer, memory_pool_count); + if (result.IsError()) { + LOG_ERROR(Service_Audio, "Failed to update MemoryPools!"); + return result; + } + + result = info_updater.UpdateVoiceChannelResources(voice_context); + if (result.IsError()) { + LOG_ERROR(Service_Audio, "Failed to update VoiceChannelResources!"); + return result; + } + + result = info_updater.UpdateVoices(voice_context, memory_pool_workbuffer, memory_pool_count); + if (result.IsError()) { + LOG_ERROR(Service_Audio, "Failed to update Voices!"); + return result; + } + + result = info_updater.UpdateEffects(effect_context, active, memory_pool_workbuffer, + memory_pool_count); + if (result.IsError()) { + LOG_ERROR(Service_Audio, "Failed to update Effects!"); + return result; + } + + if (behavior.IsSplitterSupported()) { + result = info_updater.UpdateSplitterInfo(splitter_context); + if (result.IsError()) { + LOG_ERROR(Service_Audio, "Failed to update SplitterInfo!"); + return result; + } + } + + result = + info_updater.UpdateMixes(mix_context, mix_buffer_count, effect_context, splitter_context); + if (result.IsError()) { + LOG_ERROR(Service_Audio, "Failed to update Mixes!"); + return result; + } + + result = info_updater.UpdateSinks(sink_context, memory_pool_workbuffer, memory_pool_count); + if (result.IsError()) { + LOG_ERROR(Service_Audio, "Failed to update Sinks!"); + return result; + } + + PerformanceManager* perf_manager{nullptr}; + if (performance_manager.IsInitialized()) { + perf_manager = &performance_manager; + } + + result = + info_updater.UpdatePerformanceBuffer(performance, performance.size_bytes(), perf_manager); + if (result.IsError()) { + LOG_ERROR(Service_Audio, "Failed to update PerformanceBuffer!"); + return result; + } + + result = info_updater.UpdateErrorInfo(behavior); + if (result.IsError()) { + LOG_ERROR(Service_Audio, "Failed to update ErrorInfo!"); + return result; + } + + if (behavior.IsElapsedFrameCountSupported()) { + result = info_updater.UpdateRendererInfo(frames_elapsed); + if (result.IsError()) { + LOG_ERROR(Service_Audio, "Failed to update RendererInfo!"); + return result; + } + } + + result = info_updater.CheckConsumedSize(); + if (result.IsError()) { + LOG_ERROR(Service_Audio, "Invalid consume size!"); + return result; + } + + adsp_rendered_event->GetWritableEvent().Clear(); + num_times_updated++; + + const auto end_time{core.CoreTiming().GetClockTicks()}; + ticks_spent_updating += end_time - start_time; + + return ResultSuccess; +} + +u32 System::GetRenderingTimeLimit() const { + return render_time_limit_percent; +} + +void System::SetRenderingTimeLimit(const u32 limit) { + render_time_limit_percent = limit; +} + +u32 System::GetSessionId() const { + return session_id; +} + +u32 System::GetSampleRate() const { + return sample_rate; +} + +u32 System::GetSampleCount() const { + return sample_count; +} + +u32 System::GetMixBufferCount() const { + return mix_buffer_count; +} + +ExecutionMode System::GetExecutionMode() const { + return execution_mode; +} + +u32 System::GetRenderingDevice() const { + return render_device; +} + +bool System::IsActive() const { + return active; +} + +void System::SendCommandToDsp() { + std::scoped_lock l{lock}; + + if (initialized) { + if (active) { + terminate_event.Reset(); + const auto remaining_command_count{adsp.GetRemainCommandCount(session_id)}; + u64 command_size{0}; + + if (remaining_command_count) { + adsp_behind = true; + command_size = command_buffer_size; + } else { + command_size = GenerateCommand(command_workbuffer, command_workbuffer_size); + } + + auto translated_addr{ + memory_pool_info.Translate(CpuAddr(command_workbuffer.data()), command_size)}; + + auto time_limit_percent{70.0f}; + if (behavior.IsAudioRendererProcessingTimeLimit80PercentSupported()) { + time_limit_percent = 80.0f; + } else if (behavior.IsAudioRendererProcessingTimeLimit75PercentSupported()) { + time_limit_percent = 75.0f; + } else { + // result ignored and 70 is used anyway + behavior.IsAudioRendererProcessingTimeLimit70PercentSupported(); + time_limit_percent = 70.0f; + } + + ADSP::CommandBuffer command_buffer{ + .buffer{translated_addr}, + .size{command_size}, + .time_limit{ + static_cast((time_limit_percent / 100) * 2'880'000.0 * + (static_cast(render_time_limit_percent) / 100.0f))}, + .remaining_command_count{remaining_command_count}, + .reset_buffers{reset_command_buffers}, + .applet_resource_user_id{applet_resource_user_id}, + .render_time_taken{adsp.GetRenderTimeTaken(session_id)}, + }; + + adsp.SendCommandBuffer(session_id, command_buffer); + reset_command_buffers = false; + command_buffer_size = command_size; + if (remaining_command_count == 0) { + adsp_rendered_event->GetWritableEvent().Signal(); + } + } else { + adsp.ClearRemainCount(session_id); + terminate_event.Set(); + } + } +} + +u64 System::GenerateCommand(std::span in_command_buffer, + [[maybe_unused]] const u64 command_buffer_size_) { + PoolMapper::ClearUseState(memory_pool_workbuffer, memory_pool_count); + const auto start_time{core.CoreTiming().GetClockTicks()}; + + auto command_list_header{reinterpret_cast(in_command_buffer.data())}; + + command_list_header->buffer_count = static_cast(voice_channels + mix_buffer_count); + command_list_header->sample_count = sample_count; + command_list_header->sample_rate = sample_rate; + command_list_header->samples_buffer = samples_workbuffer; + + const auto performance_initialized{performance_manager.IsInitialized()}; + if (performance_initialized) { + performance_manager.TapFrame(adsp_behind, num_voices_dropped, render_start_tick); + adsp_behind = false; + num_voices_dropped = 0; + render_start_tick = 0; + } + + s8 channel_count{2}; + if (execution_mode == ExecutionMode::Auto) { + const auto& sink{core.AudioCore().GetOutputSink()}; + channel_count = static_cast(sink.GetDeviceChannels()); + } + + AudioRendererSystemContext render_context{ + .session_id{session_id}, + .channels{channel_count}, + .mix_buffer_count{mix_buffer_count}, + .behavior{&behavior}, + .depop_buffer{depop_buffer}, + .upsampler_manager{upsampler_manager}, + .memory_pool_info{&memory_pool_info}, + }; + + CommandBuffer command_buffer{ + .command_list{in_command_buffer}, + .sample_count{sample_count}, + .sample_rate{sample_rate}, + .size{sizeof(CommandListHeader)}, + .count{0}, + .estimated_process_time{0}, + .memory_pool{&memory_pool_info}, + .time_estimator{command_processing_time_estimator.get()}, + .behavior{&behavior}, + }; + + PerformanceManager* perf_manager{nullptr}; + if (performance_initialized) { + perf_manager = &performance_manager; + } + + CommandGenerator command_generator{command_buffer, *command_list_header, render_context, + voice_context, mix_context, effect_context, + sink_context, splitter_context, perf_manager}; + + voice_context.SortInfo(); + + const auto start_estimated_time{command_buffer.estimated_process_time}; + + command_generator.GenerateVoiceCommands(); + command_generator.GenerateSubMixCommands(); + command_generator.GenerateFinalMixCommands(); + command_generator.GenerateSinkCommands(); + + if (drop_voice) { + f32 time_limit_percent{70.0f}; + if (render_context.behavior->IsAudioRendererProcessingTimeLimit80PercentSupported()) { + time_limit_percent = 80.0f; + } else if (render_context.behavior + ->IsAudioRendererProcessingTimeLimit75PercentSupported()) { + time_limit_percent = 75.0f; + } else { + // result is ignored + render_context.behavior->IsAudioRendererProcessingTimeLimit70PercentSupported(); + time_limit_percent = 70.0f; + } + const auto time_limit{static_cast( + static_cast(start_estimated_time - command_buffer.estimated_process_time) + + (((time_limit_percent / 100.0f) * 2'880'000.0) * + (static_cast(render_time_limit_percent) / 100.0f)))}; + num_voices_dropped = DropVoices(command_buffer, start_estimated_time, time_limit); + } + + command_list_header->buffer_size = command_buffer.size; + command_list_header->command_count = command_buffer.count; + + voice_context.UpdateStateByDspShared(); + + if (render_context.behavior->IsEffectInfoVersion2Supported()) { + effect_context.UpdateStateByDspShared(); + } + + const auto end_time{core.CoreTiming().GetClockTicks()}; + total_ticks_elapsed += end_time - start_time; + num_command_lists_generated++; + render_start_tick = adsp.GetRenderingStartTick(session_id); + frames_elapsed++; + + return command_buffer.size; +} + +u32 System::DropVoices(CommandBuffer& command_buffer, const u32 estimated_process_time, + const u32 time_limit) { + u32 i{0}; + auto command_list{command_buffer.command_list.data() + sizeof(CommandListHeader)}; + ICommand* cmd{}; + + for (; i < command_buffer.count; i++) { + cmd = reinterpret_cast(command_list); + if (cmd->type != CommandId::Performance && + cmd->type != CommandId::DataSourcePcmInt16Version1 && + cmd->type != CommandId::DataSourcePcmInt16Version2 && + cmd->type != CommandId::DataSourcePcmFloatVersion1 && + cmd->type != CommandId::DataSourcePcmFloatVersion2 && + cmd->type != CommandId::DataSourceAdpcmVersion1 && + cmd->type != CommandId::DataSourceAdpcmVersion2) { + break; + } + command_list += cmd->size; + } + + if (cmd == nullptr || command_buffer.count == 0 || i >= command_buffer.count) { + return 0; + } + + auto voices_dropped{0}; + while (i < command_buffer.count) { + const auto node_id{cmd->node_id}; + const auto node_id_type{cmd->node_id >> 28}; + const auto node_id_base{cmd->node_id & 0xFFF}; + + if (estimated_process_time <= time_limit) { + break; + } + + if (node_id_type != 1) { + break; + } + + auto& voice_info{voice_context.GetInfo(node_id_base)}; + if (voice_info.priority == HighestVoicePriority) { + break; + } + + voices_dropped++; + voice_info.voice_dropped = true; + + if (i < command_buffer.count) { + while (cmd->node_id == node_id) { + if (cmd->type == CommandId::DepopPrepare) { + cmd->enabled = true; + } else if (cmd->type == CommandId::Performance || !cmd->enabled) { + cmd->enabled = false; + } + i++; + command_list += cmd->size; + cmd = reinterpret_cast(command_list); + } + } + } + return voices_dropped; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/system.h b/src/audio_core/renderer/system.h new file mode 100644 index 000000000..bcbe65b07 --- /dev/null +++ b/src/audio_core/renderer/system.h @@ -0,0 +1,307 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include + +#include "audio_core/renderer/behavior/behavior_info.h" +#include "audio_core/renderer/command/command_processing_time_estimator.h" +#include "audio_core/renderer/effect/effect_context.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/sink_context.h" +#include "audio_core/renderer/splitter/splitter_context.h" +#include "audio_core/renderer/upsampler/upsampler_manager.h" +#include "audio_core/renderer/voice/voice_context.h" +#include "common/thread.h" +#include "core/hle/service/audio/errors.h" + +namespace Core { +namespace Memory { +class Memory; +} +class System; +} // namespace Core + +namespace Kernel { +class KEvent; +class KTransferMemory; +} // namespace Kernel + +namespace AudioCore { +struct AudioRendererParameterInternal; + +namespace AudioRenderer { +class CommandBuffer; +namespace ADSP { +class ADSP; +} + +/** + * Audio Renderer System, the main worker for audio rendering. + */ +class System { + enum class State { + Started = 0, + Stopped = 2, + }; + +public: + explicit System(Core::System& core, Kernel::KEvent* adsp_rendered_event); + + /** + * Calculate the total size required for all audio render workbuffers. + * + * @param params - Input parameters with the numbers of voices/mixes/sinks/etc. + * @return Size (in bytes) required for the audio renderer. + */ + static u64 GetWorkBufferSize(const AudioRendererParameterInternal& params); + + /** + * Initialize the renderer system. + * Allocates workbuffers and initializes everything to a default state, ready to receive a + * RequestUpdate. + * + * @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 system. + */ + void Finalize(); + + /** + * Start the system. + */ + void Start(); + + /** + * Stop the system. + */ + void Stop(); + + /** + * Update the system. + * + * @param input - Inout buffer containing the update data. + * @param performance - Optional buffer for writing back performance metrics. + * @param output - Output information from rendering. + * @return Result code. + */ + Result Update(std::span input, std::span performance, std::span output); + + /** + * Get the time limit (percent) for rendering + * + * @return Time limit as a percent. + */ + u32 GetRenderingTimeLimit() const; + + /** + * Set the time limit (percent) for rendering + * + * @param limit - New time limit. + */ + void SetRenderingTimeLimit(u32 limit); + + /** + * Get the session id for this system. + * + * @return Session id of this system. + */ + u32 GetSessionId() const; + + /** + * Get the sample rate of this system. + * + * @return Sample rate of this system. + */ + u32 GetSampleRate() const; + + /** + * Get the sample count of this system. + * + * @return Sample count of this system. + */ + u32 GetSampleCount() const; + + /** + * Get the number of mix buffers for this system. + * + * @return Number of mix buffers in the system. + */ + u32 GetMixBufferCount() const; + + /** + * Get the execution mode of this system. + * Note: Only Auto is implemented. + * + * @return Execution mode for this system. + */ + ExecutionMode GetExecutionMode() const; + + /** + * Get the rendering deivce for this system. + * This is unused. + * + * @return Rendering device for this system. + */ + u32 GetRenderingDevice() const; + + /** + * Check if this system is currently active. + * + * @return True if active, otherwise false. + */ + bool IsActive() const; + + /** + * Prepare and generate a list of commands for the AudioRenderer based on current state, + * signalling the buffer event when all processed. + */ + void SendCommandToDsp(); + + /** + * Generate a list of commands for the AudioRenderer based on current state. + * + * @param command_buffer - Buffer for commands to be written to. + * @param command_buffer_size - Size of the command_buffer. + * + * @return Number of bytes written. + */ + u64 GenerateCommand(std::span command_buffer, u64 command_buffer_size); + + /** + * Try to drop some voices if the AudioRenderer fell behind. + * + * @param command_buffer - Command buffer to drop voices from. + * @param estimated_process_time - Current estimated processing time of all commands. + * @param time_limit - Time limit for rendering, voices are dropped if estimated + * exceeds this. + * + * @return Number of voices dropped. + */ + u32 DropVoices(CommandBuffer& command_buffer, u32 estimated_process_time, u32 time_limit); + +private: + /// Core system + Core::System& core; + /// Reference to the ADSP for communication + ADSP::ADSP& adsp; + /// Is this system initialized? + bool initialized{}; + /// Is this system currently active? + std::atomic active{}; + /// State of the system + State state{State::Stopped}; + /// Sample rate for the system + u32 sample_rate{}; + /// Sample count of the system + u32 sample_count{}; + /// Number of mix buffers in use by the system + s16 mix_buffer_count{}; + /// Workbuffer for mix buffers, used by the AudioRenderer + std::span samples_workbuffer{}; + /// Depop samples for depopping commands + std::span depop_buffer{}; + /// Number of memory pools in the buffer + u32 memory_pool_count{}; + /// Workbuffer for memory pools + std::span memory_pool_workbuffer{}; + /// System memory pool info + MemoryPoolInfo memory_pool_info{}; + /// Workbuffer that commands will be generated into + std::span command_workbuffer{}; + /// Size of command workbuffer + u64 command_workbuffer_size{}; + /// Numebr of commands in the workbuffer + u64 command_buffer_size{}; + /// Manager for upsamplers + UpsamplerManager* upsampler_manager{}; + /// Upsampler workbuffer + std::span upsampler_infos{}; + /// Number of upsamplers in the workbuffer + u32 upsampler_count{}; + /// Holds and controls all voices + VoiceContext voice_context{}; + /// Holds and controls all mixes + MixContext mix_context{}; + /// Holds and controls all effects + EffectContext effect_context{}; + /// Holds and controls all sinks + SinkContext sink_context{}; + /// Holds and controls all splitters + SplitterContext splitter_context{}; + /// Estimates the time taken for each command + std::unique_ptr command_processing_time_estimator{}; + /// Session id of this system + s32 session_id{}; + /// Number of channels in use by voices + s32 voice_channels{}; + /// Event to be called when the AudioRenderer processes a command list + Kernel::KEvent* adsp_rendered_event{}; + /// Event signalled on system terminate + Common::Event terminate_event{}; + /// Does what locks do + std::mutex lock{}; + /// Handle for the process for this system, unused + u32 process_handle{}; + /// Applet resource id for this system, unused + u64 applet_resource_user_id{}; + /// Controls performance input and output + PerformanceManager performance_manager{}; + /// Workbuffer for performance metrics + std::span performance_workbuffer{}; + /// Main workbuffer, from which all other workbuffers here allocate into + std::unique_ptr workbuffer{}; + /// Size of the main workbuffer + u64 workbuffer_size{}; + /// Unknown buffer/marker + std::span unk_2A8{}; + /// Size of the above unknown buffer/marker + u64 unk_2B0{}; + /// Rendering time limit (percent) + u32 render_time_limit_percent{}; + /// Should any voices be dropped? + bool drop_voice{}; + /// Should the backend stream have its buffers flushed? + bool reset_command_buffers{}; + /// Execution mode of this system, only Auto is supported + ExecutionMode execution_mode{ExecutionMode::Auto}; + /// Render device, unused + u32 render_device{}; + /// Behaviour to check which features are supported by the user revision + BehaviorInfo behavior{}; + /// Total ticks the audio system has been running + u64 total_ticks_elapsed{}; + /// Ticks the system has spent in updates + u64 ticks_spent_updating{}; + /// Number of times a command list was generated + u64 num_command_lists_generated{}; + /// Number of times the system has updated + u64 num_times_updated{}; + /// Number of frames generated, written back to the game + std::atomic frames_elapsed{}; + /// Is the AudioRenderer running too slow? + bool adsp_behind{}; + /// Number of voices dropped + u32 num_voices_dropped{}; + /// Tick that rendering started + u64 render_start_tick{}; +}; + +} // namespace AudioRenderer +} // namespace AudioCore diff --git a/src/audio_core/renderer/system_manager.cpp b/src/audio_core/renderer/system_manager.cpp new file mode 100644 index 000000000..b326819ed --- /dev/null +++ b/src/audio_core/renderer/system_manager.cpp @@ -0,0 +1,162 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include + +#include "audio_core/audio_core.h" +#include "audio_core/renderer/adsp/adsp.h" +#include "audio_core/renderer/system_manager.h" +#include "common/microprofile.h" +#include "common/thread.h" +#include "core/core.h" +#include "core/core_timing.h" + +MICROPROFILE_DEFINE(Audio_RenderSystemManager, "Audio", "Render System Manager", + MP_RGB(60, 19, 97)); + +namespace AudioCore::AudioRenderer { +constexpr std::chrono::nanoseconds BaseRenderTime{5'000'000UL}; +constexpr std::chrono::nanoseconds RenderTimeOffset{400'000UL}; + +SystemManager::SystemManager(Core::System& core_) + : core{core_}, adsp{core.AudioCore().GetADSP()}, mailbox{adsp.GetRenderMailbox()}, + thread_event{Core::Timing::CreateEvent( + "AudioRendererSystemManager", [this](std::uintptr_t, s64 time, std::chrono::nanoseconds) { + return ThreadFunc2(time); + })} { + core.CoreTiming().RegisterPauseCallback([this](bool paused) { PauseCallback(paused); }); +} + +SystemManager::~SystemManager() { + Stop(); +} + +bool SystemManager::InitializeUnsafe() { + if (!active) { + if (adsp.Start()) { + active = true; + thread = std::jthread([this](std::stop_token stop_token) { ThreadFunc(); }); + core.CoreTiming().ScheduleLoopingEvent(std::chrono::nanoseconds(0), + BaseRenderTime - RenderTimeOffset, thread_event); + } + } + + return adsp.GetState() == ADSP::State::Started; +} + +void SystemManager::Stop() { + if (!active) { + return; + } + core.CoreTiming().UnscheduleEvent(thread_event, {}); + active = false; + update.store(true); + update.notify_all(); + thread.join(); + adsp.Stop(); +} + +bool SystemManager::Add(System& system_) { + std::scoped_lock l2{mutex2}; + + if (systems.size() + 1 > MaxRendererSessions) { + LOG_ERROR(Service_Audio, "Maximum AudioRenderer Systems active, cannot add more!"); + return false; + } + + { + std::scoped_lock l{mutex1}; + if (systems.empty()) { + if (!InitializeUnsafe()) { + LOG_ERROR(Service_Audio, "Failed to start the AudioRenderer SystemManager"); + return false; + } + } + } + + systems.push_back(&system_); + return true; +} + +bool SystemManager::Remove(System& system_) { + std::scoped_lock l2{mutex2}; + + { + std::scoped_lock l{mutex1}; + if (systems.remove(&system_) == 0) { + LOG_ERROR(Service_Audio, + "Failed to remove a render system, it was not found in the list!"); + return false; + } + } + + if (systems.empty()) { + Stop(); + } + return true; +} + +void SystemManager::ThreadFunc() { + constexpr char name[]{"yuzu:AudioRenderSystemManager"}; + MicroProfileOnThreadCreate(name); + Common::SetCurrentThreadName(name); + Common::SetCurrentThreadPriority(Common::ThreadPriority::High); + while (active) { + { + std::scoped_lock l{mutex1}; + + MICROPROFILE_SCOPE(Audio_RenderSystemManager); + + for (auto system : systems) { + system->SendCommandToDsp(); + } + } + + adsp.Signal(); + adsp.Wait(); + + update.wait(false); + update.store(false); + } +} + +std::optional SystemManager::ThreadFunc2(s64 time) { + std::optional new_schedule_time{std::nullopt}; + const auto queue_size{core.AudioCore().GetStreamQueue()}; + switch (state) { + case StreamState::Filling: + if (queue_size >= 5) { + new_schedule_time = BaseRenderTime; + state = StreamState::Steady; + } + break; + case StreamState::Steady: + if (queue_size <= 2) { + new_schedule_time = BaseRenderTime - RenderTimeOffset; + state = StreamState::Filling; + } else if (queue_size > 5) { + new_schedule_time = BaseRenderTime + RenderTimeOffset; + state = StreamState::Draining; + } + break; + case StreamState::Draining: + if (queue_size <= 5) { + new_schedule_time = BaseRenderTime; + state = StreamState::Steady; + } + break; + } + + update.store(true); + update.notify_all(); + return new_schedule_time; +} + +void SystemManager::PauseCallback(bool paused) { + if (paused && core.IsPoweredOn() && core.IsShuttingDown()) { + update.store(true); + update.notify_all(); + } +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/system_manager.h b/src/audio_core/renderer/system_manager.h new file mode 100644 index 000000000..1291e9e0e --- /dev/null +++ b/src/audio_core/renderer/system_manager.h @@ -0,0 +1,113 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include +#include + +#include "audio_core/renderer/system.h" + +namespace Core { +namespace Timing { +struct EventType; +} +class System; +} // namespace Core + +namespace AudioCore::AudioRenderer { +namespace ADSP { +class ADSP; +class AudioRenderer_Mailbox; +} // namespace ADSP + +/** + * Manages all audio renderers, responsible for triggering command list generation and signalling + * the ADSP. + */ +class SystemManager { +public: + explicit SystemManager(Core::System& core); + ~SystemManager(); + + /** + * Initialize the system manager, called when any system is registered. + * + * @return True if sucessfully initialized, otherwise false. + */ + bool InitializeUnsafe(); + + /** + * Stop the system manager. + */ + void Stop(); + + /** + * Add an audio render system to the manager. + * The manager does not own the system, so do not free it without calling Remove. + * + * @param system - The system to add. + * @return True if succesfully added, otherwise false. + */ + bool Add(System& system); + + /** + * Remove an audio render system from the manager. + * + * @param system - The system to remove. + * @return True if succesfully removed, otherwise false. + */ + bool Remove(System& system); + +private: + /** + * Main thread responsible for command generation. + */ + void ThreadFunc(); + + /** + * Signalling core timing thread to run ThreadFunc. + */ + std::optional ThreadFunc2(s64 time); + + /** + * Callback from core timing when pausing, used to detect shutdowns and stop ThreadFunc. + * + * @param paused - Are we pausing or resuming? + */ + void PauseCallback(bool paused); + + enum class StreamState { + Filling, + Steady, + Draining, + }; + + /// Core system + Core::System& core; + /// List of pointers to managed systems + std::list systems{}; + /// Main worker thread for generating command lists + std::jthread thread; + /// Mutex for the systems + std::mutex mutex1{}; + /// Mutex for adding/removing systems + std::mutex mutex2{}; + /// Is the system manager thread active? + std::atomic active{}; + /// Reference to the ADSP for communication + ADSP::ADSP& adsp; + /// AudioRenderer mailbox for communication + ADSP::AudioRenderer_Mailbox* mailbox{}; + /// Core timing event to signal main thread + std::shared_ptr thread_event; + /// Atomic for main thread to wait on + std::atomic update{}; + /// Current state of the streams + StreamState state{StreamState::Filling}; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/upsampler/upsampler_info.cpp b/src/audio_core/renderer/upsampler/upsampler_info.cpp new file mode 100644 index 000000000..e3d2f7db0 --- /dev/null +++ b/src/audio_core/renderer/upsampler/upsampler_info.cpp @@ -0,0 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/upsampler/upsampler_info.h" + +namespace AudioCore::AudioRenderer {} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/upsampler/upsampler_info.h b/src/audio_core/renderer/upsampler/upsampler_info.h new file mode 100644 index 000000000..a43c15af3 --- /dev/null +++ b/src/audio_core/renderer/upsampler/upsampler_info.h @@ -0,0 +1,35 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "audio_core/common/common.h" +#include "audio_core/renderer/upsampler/upsampler_state.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +class UpsamplerManager; + +/** + * Manages information needed to upsample a mix buffer. + */ +struct UpsamplerInfo { + /// States used by the AudioRenderer across calls. + std::array states{}; + /// Pointer to the manager + UpsamplerManager* manager{}; + /// Pointer to the samples to be upsampled + CpuAddr samples_pos{}; + /// Target number of samples to upsample to + u32 sample_count{}; + /// Number of channels to upsample + u32 input_count{}; + /// Is this upsampler enabled? + bool enabled{}; + /// Mix buffer indexes to be upsampled + std::array inputs{}; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/upsampler/upsampler_manager.cpp b/src/audio_core/renderer/upsampler/upsampler_manager.cpp new file mode 100644 index 000000000..4c76a5066 --- /dev/null +++ b/src/audio_core/renderer/upsampler/upsampler_manager.cpp @@ -0,0 +1,44 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/upsampler/upsampler_manager.h" + +namespace AudioCore::AudioRenderer { + +UpsamplerManager::UpsamplerManager(const u32 count_, std::span infos_, + std::span workbuffer_) + : count{count_}, upsampler_infos{infos_}, workbuffer{workbuffer_} {} + +UpsamplerInfo* UpsamplerManager::Allocate() { + std::scoped_lock l{lock}; + + if (count == 0) { + return nullptr; + } + + u32 free_index{0}; + for (auto& upsampler : upsampler_infos) { + if (!upsampler.enabled) { + break; + } + free_index++; + } + + if (free_index >= count) { + return nullptr; + } + + auto& upsampler{upsampler_infos[free_index]}; + upsampler.manager = this; + upsampler.sample_count = TargetSampleCount; + upsampler.samples_pos = CpuAddr(&workbuffer[upsampler.sample_count * MaxChannels]); + upsampler.enabled = true; + return &upsampler; +} + +void UpsamplerManager::Free(UpsamplerInfo* info) { + std::scoped_lock l{lock}; + info->enabled = false; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/upsampler/upsampler_manager.h b/src/audio_core/renderer/upsampler/upsampler_manager.h new file mode 100644 index 000000000..70cd42b08 --- /dev/null +++ b/src/audio_core/renderer/upsampler/upsampler_manager.h @@ -0,0 +1,45 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#include "audio_core/renderer/upsampler/upsampler_info.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +/** + * Manages and has utility functions for upsampler infos. + */ +class UpsamplerManager { +public: + UpsamplerManager(u32 count, std::span infos, std::span workbuffer); + + /** + * Allocate a new UpsamplerInfo. + * + * @return The allocated upsampler, may be nullptr if alloc failed. + */ + UpsamplerInfo* Allocate(); + + /** + * Free the given upsampler. + * + * @param The upsampler to be freed. + */ + void Free(UpsamplerInfo* info); + +private: + /// Maximum number of upsamplers in the buffer + const u32 count; + /// Upsamplers buffer + std::span upsampler_infos; + /// Workbuffer for upsampling samples + std::span workbuffer; + /// Lock for allocate/free + std::mutex lock{}; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/upsampler/upsampler_state.h b/src/audio_core/renderer/upsampler/upsampler_state.h new file mode 100644 index 000000000..28cebe200 --- /dev/null +++ b/src/audio_core/renderer/upsampler/upsampler_state.h @@ -0,0 +1,40 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "common/common_types.h" +#include "common/fixed_point.h" + +namespace AudioCore::AudioRenderer { +/** + * Upsampling state used by the AudioRenderer across calls. + */ +struct UpsamplerState { + static constexpr u16 HistorySize = 20; + + /// Source data to target data ratio. E.g 48'000/32'000 = 1.5 + Common::FixedPoint<16, 16> ratio; + /// Sample history + std::array, HistorySize> history; + /// Size of the sinc coefficient window + u16 window_size; + /// Read index for the history + u16 history_output_index; + /// Write index for the history + u16 history_input_index; + /// Start offset within the history, fixed to 0 + u16 history_start_index; + /// Ebd offset within the history, fixed to HistorySize + u16 history_end_index; + /// Is this state initialized? + bool initialized; + /// Index of the current sample. + /// E.g 16K -> 48K has a ratio of 3, so this will be 0-2. + /// See the Upsample command in the AudioRenderer for more information. + u8 sample_index; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/voice/voice_channel_resource.h b/src/audio_core/renderer/voice/voice_channel_resource.h new file mode 100644 index 000000000..26ab4ccce --- /dev/null +++ b/src/audio_core/renderer/voice/voice_channel_resource.h @@ -0,0 +1,38 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "audio_core/common/common.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +/** + * Represents one channel for mixing a voice. + */ +class VoiceChannelResource { +public: + struct InParameter { + /* 0x00 */ u32 id; + /* 0x04 */ std::array mix_volumes; + /* 0x64 */ bool in_use; + /* 0x65 */ char unk65[0xB]; + }; + static_assert(sizeof(InParameter) == 0x70, + "VoiceChannelResource::InParameter has the wrong size!"); + + explicit VoiceChannelResource(u32 id_) : id{id_} {} + + /// Current volume for each mix buffer + std::array mix_volumes{}; + /// Previous volume for each mix buffer + std::array prev_mix_volumes{}; + /// Id of this resource + const u32 id; + /// Is this resource in use? + bool in_use{}; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/voice/voice_context.cpp b/src/audio_core/renderer/voice/voice_context.cpp new file mode 100644 index 000000000..eafb51b01 --- /dev/null +++ b/src/audio_core/renderer/voice/voice_context.cpp @@ -0,0 +1,86 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include + +#include "audio_core/renderer/voice/voice_context.h" + +namespace AudioCore::AudioRenderer { + +VoiceState& VoiceContext::GetDspSharedState(const u32 index) { + if (index >= dsp_states.size()) { + LOG_ERROR(Service_Audio, "Invalid voice dsp state index {:04X}", index); + } + return dsp_states[index]; +} + +VoiceChannelResource& VoiceContext::GetChannelResource(const u32 index) { + if (index >= channel_resources.size()) { + LOG_ERROR(Service_Audio, "Invalid voice channel resource index {:04X}", index); + } + return channel_resources[index]; +} + +void VoiceContext::Initialize(std::span sorted_voice_infos_, + std::span voice_infos_, + std::span voice_channel_resources_, + std::span cpu_states_, std::span dsp_states_, + const u32 voice_count_) { + sorted_voice_info = sorted_voice_infos_; + voices = voice_infos_; + channel_resources = voice_channel_resources_; + cpu_states = cpu_states_; + dsp_states = dsp_states_; + voice_count = voice_count_; + active_count = 0; +} + +VoiceInfo* VoiceContext::GetSortedInfo(const u32 index) { + if (index >= sorted_voice_info.size()) { + LOG_ERROR(Service_Audio, "Invalid voice sorted info index {:04X}", index); + } + return sorted_voice_info[index]; +} + +VoiceInfo& VoiceContext::GetInfo(const u32 index) { + if (index >= voices.size()) { + LOG_ERROR(Service_Audio, "Invalid voice info index {:04X}", index); + } + return voices[index]; +} + +VoiceState& VoiceContext::GetState(const u32 index) { + if (index >= cpu_states.size()) { + LOG_ERROR(Service_Audio, "Invalid voice cpu state index {:04X}", index); + } + return cpu_states[index]; +} + +u32 VoiceContext::GetCount() const { + return voice_count; +} + +u32 VoiceContext::GetActiveCount() const { + return active_count; +} + +void VoiceContext::SetActiveCount(const u32 active_count_) { + active_count = active_count_; +} + +void VoiceContext::SortInfo() { + for (u32 i = 0; i < voice_count; i++) { + sorted_voice_info[i] = &voices[i]; + } + + std::ranges::sort(sorted_voice_info, [](const VoiceInfo* a, const VoiceInfo* b) { + return a->priority != b->priority ? a->priority < b->priority + : a->sort_order < b->sort_order; + }); +} + +void VoiceContext::UpdateStateByDspShared() { + std::memcpy(cpu_states.data(), dsp_states.data(), voice_count * sizeof(VoiceState)); +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/voice/voice_context.h b/src/audio_core/renderer/voice/voice_context.h new file mode 100644 index 000000000..43b677154 --- /dev/null +++ b/src/audio_core/renderer/voice/voice_context.h @@ -0,0 +1,126 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "audio_core/renderer/voice/voice_channel_resource.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 { +/** + * Contains all voices, with utility functions for managing them. + */ +class VoiceContext { +public: + /** + * Get the AudioRenderer state for a given index + * + * @param index - State index to get. + * @return The requested voice state. + */ + VoiceState& GetDspSharedState(u32 index); + + /** + * Get the channel resource for a given index + * + * @param index - Resource index to get. + * @return The requested voice resource. + */ + VoiceChannelResource& GetChannelResource(u32 index); + + /** + * Initialize the voice context. + * + * @param sorted_voice_infos - Workbuffer for the sorted voices. + * @param voice_infos - Workbuffer for the voices. + * @param voice_channel_resources - Workbuffer for the voice channel resources. + * @param cpu_states - Workbuffer for the host-side voice states. + * @param dsp_states - Workbuffer for the AudioRenderer-side voice states. + * @param voice_count - The number of voices in each workbuffer. + */ + void Initialize(std::span sorted_voice_infos, std::span voice_infos, + std::span voice_channel_resources, + std::span cpu_states, std::span dsp_states, + u32 voice_count); + + /** + * Get a sorted voice with the given index. + * + * @param index - The sorted voice index to get. + * @return The sorted voice. + */ + VoiceInfo* GetSortedInfo(u32 index); + + /** + * Get a voice with the given index. + * + * @param index - The voice index to get. + * @return The voice. + */ + VoiceInfo& GetInfo(u32 index); + + /** + * Get a host voice state with the given index. + * + * @param index - The host voice state index to get. + * @return The voice state. + */ + VoiceState& GetState(u32 index); + + /** + * Get the maximum number of voices. + * Not all voices in the buffers may be in use, see GetActiveCount. + * + * @return The maximum number of voices. + */ + u32 GetCount() const; + + /** + * Get the number of active voices. + * Can be less than or equal to the maximum number of voices. + * + * @return The number of active voices. + */ + u32 GetActiveCount() const; + + /** + * Set the number of active voices. + * Can be less than or equal to the maximum number of voices. + * + * @param active_count - The new number of active voices. + */ + void SetActiveCount(u32 active_count); + + /** + * Sort all voices. Results are available via GetSortedInfo. + * Voices are sorted descendingly, according to priority, and then sort order. + */ + void SortInfo(); + + /** + * Update all voice states, copying AudioRenderer-side states to host-side states. + */ + void UpdateStateByDspShared(); + +private: + /// Sorted voices + std::span sorted_voice_info{}; + /// Voices + std::span voices{}; + /// Channel resources + std::span channel_resources{}; + /// Host-side voice states + std::span cpu_states{}; + /// AudioRenderer-side voice states + std::span dsp_states{}; + /// Maximum number of voices + u32 voice_count{}; + /// Number of active voices + u32 active_count{}; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/voice/voice_info.cpp b/src/audio_core/renderer/voice/voice_info.cpp new file mode 100644 index 000000000..1849eeb57 --- /dev/null +++ b/src/audio_core/renderer/voice/voice_info.cpp @@ -0,0 +1,408 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/memory/pool_mapper.h" +#include "audio_core/renderer/voice/voice_context.h" +#include "audio_core/renderer/voice/voice_info.h" +#include "audio_core/renderer/voice/voice_state.h" + +namespace AudioCore::AudioRenderer { + +VoiceInfo::VoiceInfo() { + Initialize(); +} + +void VoiceInfo::Initialize() { + in_use = false; + is_new = false; + id = 0; + node_id = 0; + current_play_state = ServerPlayState::Stopped; + src_quality = SrcQuality::Medium; + priority = LowestVoicePriority; + sample_format = SampleFormat::Invalid; + sample_rate = 0; + channel_count = 0; + wave_buffer_count = 0; + wave_buffer_index = 0; + pitch = 0.0f; + volume = 0.0f; + prev_volume = 0.0f; + mix_id = UnusedMixId; + splitter_id = UnusedSplitterId; + biquads = {}; + biquad_initialized = {}; + voice_dropped = false; + data_unmapped = false; + buffer_unmapped = false; + flush_buffer_count = 0; + + data_address.Setup(0, 0); + for (auto& wavebuffer : wavebuffers) { + wavebuffer.Initialize(); + } +} + +bool VoiceInfo::ShouldUpdateParameters(const InParameter& params) const { + return data_address.GetCpuAddr() != params.src_data_address || + data_address.GetSize() != params.src_data_size || data_unmapped; +} + +void VoiceInfo::UpdateParameters(BehaviorInfo::ErrorInfo& error_info, const InParameter& params, + const PoolMapper& pool_mapper, const BehaviorInfo& behavior) { + in_use = params.in_use; + id = params.id; + node_id = params.node_id; + UpdatePlayState(params.play_state); + UpdateSrcQuality(params.src_quality); + priority = params.priority; + sort_order = params.sort_order; + sample_rate = params.sample_rate; + sample_format = params.sample_format; + channel_count = static_cast(params.channel_count); + pitch = params.pitch; + volume = params.volume; + biquads = params.biquads; + wave_buffer_count = params.wave_buffer_count; + wave_buffer_index = params.wave_buffer_index; + + if (behavior.IsFlushVoiceWaveBuffersSupported()) { + flush_buffer_count += params.flush_buffer_count; + } + + mix_id = params.mix_id; + + if (behavior.IsSplitterSupported()) { + splitter_id = params.splitter_id; + } else { + splitter_id = UnusedSplitterId; + } + + channel_resource_ids = params.channel_resource_ids; + + flags &= u16(~0b11); + if (behavior.IsVoicePlayedSampleCountResetAtLoopPointSupported()) { + flags |= u16(params.flags.IsVoicePlayedSampleCountResetAtLoopPointSupported); + } + + if (behavior.IsVoicePitchAndSrcSkippedSupported()) { + flags |= u16(params.flags.IsVoicePitchAndSrcSkippedSupported); + } + + if (params.clear_voice_drop) { + voice_dropped = false; + } + + if (ShouldUpdateParameters(params)) { + data_unmapped = !pool_mapper.TryAttachBuffer(error_info, data_address, + params.src_data_address, params.src_data_size); + } else { + error_info.error_code = ResultSuccess; + error_info.address = CpuAddr(0); + } +} + +void VoiceInfo::UpdatePlayState(const PlayState state) { + last_play_state = current_play_state; + + switch (state) { + case PlayState::Started: + current_play_state = ServerPlayState::Started; + break; + case PlayState::Stopped: + if (current_play_state != ServerPlayState::Stopped) { + current_play_state = ServerPlayState::RequestStop; + } + break; + case PlayState::Paused: + current_play_state = ServerPlayState::Paused; + break; + default: + LOG_ERROR(Service_Audio, "Invalid input play state {}", static_cast(state)); + break; + } +} + +void VoiceInfo::UpdateSrcQuality(const SrcQuality quality) { + switch (quality) { + case SrcQuality::Medium: + src_quality = quality; + break; + case SrcQuality::High: + src_quality = quality; + break; + case SrcQuality::Low: + src_quality = quality; + break; + default: + LOG_ERROR(Service_Audio, "Invalid input src quality {}", static_cast(quality)); + break; + } +} + +void VoiceInfo::UpdateWaveBuffers(std::span> error_infos, + [[maybe_unused]] u32 error_count, const InParameter& params, + std::span voice_states, + const PoolMapper& pool_mapper, const BehaviorInfo& behavior) { + if (params.is_new) { + for (size_t i = 0; i < wavebuffers.size(); i++) { + wavebuffers[i].Initialize(); + } + + for (s8 channel = 0; channel < static_cast(params.channel_count); channel++) { + voice_states[channel]->wave_buffer_valid.fill(false); + } + } + + for (u32 i = 0; i < MaxWaveBuffers; i++) { + UpdateWaveBuffer(error_infos[i], wavebuffers[i], params.wave_buffer_internal[i], + params.sample_format, voice_states[0]->wave_buffer_valid[i], pool_mapper, + behavior); + } +} + +void VoiceInfo::UpdateWaveBuffer(std::span error_info, + WaveBuffer& wave_buffer, + const WaveBufferInternal& wave_buffer_internal, + const SampleFormat sample_format_, const bool valid, + const PoolMapper& pool_mapper, const BehaviorInfo& behavior) { + if (!valid && wave_buffer.sent_to_DSP && wave_buffer.buffer_address.GetCpuAddr() != 0) { + pool_mapper.ForceUnmapPointer(wave_buffer.buffer_address); + wave_buffer.buffer_address.Setup(0, 0); + } + + if (!ShouldUpdateWaveBuffer(wave_buffer_internal)) { + return; + } + + switch (sample_format_) { + case SampleFormat::PcmInt16: { + constexpr auto byte_size{GetSampleFormatByteSize(SampleFormat::PcmInt16)}; + if (wave_buffer_internal.start_offset * byte_size > wave_buffer_internal.size || + wave_buffer_internal.end_offset * byte_size > wave_buffer_internal.size) { + LOG_ERROR(Service_Audio, "Invalid PCM16 start/end wavebuffer sizes!"); + error_info[0].error_code = Service::Audio::ERR_INVALID_UPDATE_DATA; + error_info[0].address = wave_buffer_internal.address; + return; + } + } break; + + case SampleFormat::PcmFloat: { + constexpr auto byte_size{GetSampleFormatByteSize(SampleFormat::PcmFloat)}; + if (wave_buffer_internal.start_offset * byte_size > wave_buffer_internal.size || + wave_buffer_internal.end_offset * byte_size > wave_buffer_internal.size) { + LOG_ERROR(Service_Audio, "Invalid PCMFloat start/end wavebuffer sizes!"); + error_info[0].error_code = Service::Audio::ERR_INVALID_UPDATE_DATA; + error_info[0].address = wave_buffer_internal.address; + return; + } + } break; + + case SampleFormat::Adpcm: { + const auto start_frame{wave_buffer_internal.start_offset / 14}; + auto start_extra{wave_buffer_internal.start_offset % 14 == 0 + ? 0 + : (wave_buffer_internal.start_offset % 14) / 2 + 1 + + ((wave_buffer_internal.start_offset % 14) % 2)}; + const auto start{start_frame * 8 + start_extra}; + + const auto end_frame{wave_buffer_internal.end_offset / 14}; + const auto end_extra{wave_buffer_internal.end_offset % 14 == 0 + ? 0 + : (wave_buffer_internal.end_offset % 14) / 2 + 1 + + ((wave_buffer_internal.end_offset % 14) % 2)}; + const auto end{end_frame * 8 + end_extra}; + + if (start > static_cast(wave_buffer_internal.size) || + end > static_cast(wave_buffer_internal.size)) { + LOG_ERROR(Service_Audio, "Invalid ADPCM start/end wavebuffer sizes!"); + error_info[0].error_code = Service::Audio::ERR_INVALID_UPDATE_DATA; + error_info[0].address = wave_buffer_internal.address; + return; + } + } break; + + default: + break; + } + + if (wave_buffer_internal.start_offset < 0 || wave_buffer_internal.end_offset < 0) { + LOG_ERROR(Service_Audio, "Invalid input start/end wavebuffer sizes!"); + error_info[0].error_code = Service::Audio::ERR_INVALID_UPDATE_DATA; + error_info[0].address = wave_buffer_internal.address; + return; + } + + wave_buffer.start_offset = wave_buffer_internal.start_offset; + wave_buffer.end_offset = wave_buffer_internal.end_offset; + wave_buffer.loop = wave_buffer_internal.loop; + wave_buffer.stream_ended = wave_buffer_internal.stream_ended; + wave_buffer.sent_to_DSP = false; + wave_buffer.loop_start_offset = wave_buffer_internal.loop_start; + wave_buffer.loop_end_offset = wave_buffer_internal.loop_end; + wave_buffer.loop_count = wave_buffer_internal.loop_count; + + buffer_unmapped = + !pool_mapper.TryAttachBuffer(error_info[0], wave_buffer.buffer_address, + wave_buffer_internal.address, wave_buffer_internal.size); + + if (sample_format_ == SampleFormat::Adpcm && behavior.IsAdpcmLoopContextBugFixed() && + wave_buffer_internal.context_address != 0) { + buffer_unmapped = !pool_mapper.TryAttachBuffer(error_info[1], wave_buffer.context_address, + wave_buffer_internal.context_address, + wave_buffer_internal.context_size) || + data_unmapped; + } else { + wave_buffer.context_address.Setup(0, 0); + } +} + +bool VoiceInfo::ShouldUpdateWaveBuffer(const WaveBufferInternal& wave_buffer_internal) const { + return !wave_buffer_internal.sent_to_DSP || buffer_unmapped; +} + +void VoiceInfo::WriteOutStatus(OutStatus& out_status, const InParameter& params, + std::span voice_states) { + if (params.is_new) { + is_new = true; + } + + if (params.is_new || is_new) { + out_status.played_sample_count = 0; + out_status.wave_buffers_consumed = 0; + out_status.voice_dropped = false; + } else { + out_status.played_sample_count = voice_states[0]->played_sample_count; + out_status.wave_buffers_consumed = voice_states[0]->wave_buffers_consumed; + out_status.voice_dropped = voice_dropped; + } +} + +bool VoiceInfo::ShouldSkip() const { + return !in_use || wave_buffer_count == 0 || data_unmapped || buffer_unmapped || voice_dropped; +} + +bool VoiceInfo::HasAnyConnection() const { + return mix_id != UnusedMixId || splitter_id != UnusedSplitterId; +} + +void VoiceInfo::FlushWaveBuffers(const u32 flush_count, std::span voice_states, + const s8 channel_count_) { + auto wave_index{wave_buffer_index}; + + for (size_t i = 0; i < flush_count; i++) { + wavebuffers[wave_index].sent_to_DSP = true; + + for (s8 j = 0; j < channel_count_; j++) { + auto voice_state{voice_states[j]}; + if (voice_state->wave_buffer_index == wave_index) { + voice_state->wave_buffer_index = + (voice_state->wave_buffer_index + 1) % MaxWaveBuffers; + voice_state->wave_buffers_consumed++; + } + voice_state->wave_buffer_valid[wave_index] = false; + } + + wave_index = (wave_index + 1) % MaxWaveBuffers; + } +} + +bool VoiceInfo::UpdateParametersForCommandGeneration(std::span voice_states) { + if (flush_buffer_count > 0) { + FlushWaveBuffers(flush_buffer_count, voice_states, channel_count); + flush_buffer_count = 0; + } + + switch (current_play_state) { + case ServerPlayState::Started: + for (u32 i = 0; i < MaxWaveBuffers; i++) { + if (!wavebuffers[i].sent_to_DSP) { + for (s8 channel = 0; channel < channel_count; channel++) { + voice_states[channel]->wave_buffer_valid[i] = true; + } + wavebuffers[i].sent_to_DSP = true; + } + } + + was_playing = false; + + for (u32 i = 0; i < MaxWaveBuffers; i++) { + if (voice_states[0]->wave_buffer_valid[i]) { + return true; + } + } + break; + + case ServerPlayState::Stopped: + case ServerPlayState::Paused: + for (auto& wavebuffer : wavebuffers) { + if (!wavebuffer.sent_to_DSP) { + wavebuffer.buffer_address.GetReference(true); + wavebuffer.context_address.GetReference(true); + } + } + + if (sample_format == SampleFormat::Adpcm && data_address.GetCpuAddr() != 0) { + data_address.GetReference(true); + } + + was_playing = last_play_state == ServerPlayState::Started; + break; + + case ServerPlayState::RequestStop: + for (u32 i = 0; i < MaxWaveBuffers; i++) { + wavebuffers[i].sent_to_DSP = true; + + for (s8 channel = 0; channel < channel_count; channel++) { + if (voice_states[channel]->wave_buffer_valid[i]) { + voice_states[channel]->wave_buffer_index = + (voice_states[channel]->wave_buffer_index + 1) % MaxWaveBuffers; + voice_states[channel]->wave_buffers_consumed++; + } + voice_states[channel]->wave_buffer_valid[i] = false; + } + } + + for (s8 channel = 0; channel < channel_count; channel++) { + voice_states[channel]->offset = 0; + voice_states[channel]->played_sample_count = 0; + voice_states[channel]->adpcm_context = {}; + voice_states[channel]->sample_history.fill(0); + voice_states[channel]->fraction = 0; + } + + current_play_state = ServerPlayState::Stopped; + was_playing = last_play_state == ServerPlayState::Started; + break; + } + + return was_playing; +} + +bool VoiceInfo::UpdateForCommandGeneration(VoiceContext& voice_context) { + std::array voice_states{}; + + if (is_new) { + ResetResources(voice_context); + prev_volume = volume; + is_new = false; + } + + for (s8 channel = 0; channel < channel_count; channel++) { + voice_states[channel] = &voice_context.GetDspSharedState(channel_resource_ids[channel]); + } + + return UpdateParametersForCommandGeneration(voice_states); +} + +void VoiceInfo::ResetResources(VoiceContext& voice_context) const { + for (s8 channel = 0; channel < channel_count; channel++) { + auto& state{voice_context.GetDspSharedState(channel_resource_ids[channel])}; + state = {}; + + auto& channel_resource{voice_context.GetChannelResource(channel_resource_ids[channel])}; + channel_resource.prev_mix_volumes = channel_resource.mix_volumes; + } +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/voice/voice_info.h b/src/audio_core/renderer/voice/voice_info.h new file mode 100644 index 000000000..896723e0c --- /dev/null +++ b/src/audio_core/renderer/voice/voice_info.h @@ -0,0 +1,378 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#include "audio_core/common/common.h" +#include "audio_core/common/wave_buffer.h" +#include "audio_core/renderer/behavior/behavior_info.h" +#include "audio_core/renderer/memory/address_info.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +class PoolMapper; +class VoiceContext; +struct VoiceState; + +/** + * Represents one voice. Voices are essentially noises, and they can be further mixed and have + * effects applied to them, but voices are the basis of all sounds. + */ +class VoiceInfo { +public: + enum class ServerPlayState { + Started, + Stopped, + RequestStop, + Paused, + }; + + struct Flags { + u8 IsVoicePlayedSampleCountResetAtLoopPointSupported : 1; + u8 IsVoicePitchAndSrcSkippedSupported : 1; + }; + + /** + * A wavebuffer contains information on the data source buffers. + */ + struct WaveBuffer { + void Copy(WaveBufferVersion1& other) { + other.buffer = buffer_address.GetReference(true); + other.buffer_size = buffer_address.GetSize(); + other.start_offset = start_offset; + other.end_offset = end_offset; + other.loop = loop; + other.stream_ended = stream_ended; + + if (context_address.GetCpuAddr()) { + other.context = context_address.GetReference(true); + other.context_size = context_address.GetSize(); + } else { + other.context = CpuAddr(0); + other.context_size = 0; + } + } + + void Copy(WaveBufferVersion2& other) { + other.buffer = buffer_address.GetReference(true); + other.buffer_size = buffer_address.GetSize(); + other.start_offset = start_offset; + other.end_offset = end_offset; + other.loop_start_offset = loop_start_offset; + other.loop_end_offset = loop_end_offset; + other.loop = loop; + other.loop_count = loop_count; + other.stream_ended = stream_ended; + + if (context_address.GetCpuAddr()) { + other.context = context_address.GetReference(true); + other.context_size = context_address.GetSize(); + } else { + other.context = CpuAddr(0); + other.context_size = 0; + } + } + + void Initialize() { + buffer_address.Setup(0, 0); + context_address.Setup(0, 0); + start_offset = 0; + end_offset = 0; + loop = false; + stream_ended = false; + sent_to_DSP = true; + loop_start_offset = 0; + loop_end_offset = 0; + loop_count = 0; + } + /// Game memory address of the wavebuffer data + AddressInfo buffer_address{0, 0}; + /// Context for decoding, used for ADPCM + AddressInfo context_address{0, 0}; + /// Starting offset for the wavebuffer + u32 start_offset{}; + /// Ending offset the wavebuffer + u32 end_offset{}; + /// Should this wavebuffer loop? + bool loop{}; + /// Has this wavebuffer ended? + bool stream_ended{}; + /// Has this wavebuffer been sent to the AudioRenderer? + bool sent_to_DSP{true}; + /// Starting offset when looping, can differ from start_offset + u32 loop_start_offset{}; + /// Ending offset when looping, can differ from end_offset + u32 loop_end_offset{}; + /// Number of times to loop this wavebuffer + s32 loop_count{}; + }; + + struct WaveBufferInternal { + /* 0x00 */ CpuAddr address; + /* 0x08 */ u64 size; + /* 0x10 */ s32 start_offset; + /* 0x14 */ s32 end_offset; + /* 0x18 */ bool loop; + /* 0x19 */ bool stream_ended; + /* 0x1A */ bool sent_to_DSP; + /* 0x1C */ s32 loop_count; + /* 0x20 */ CpuAddr context_address; + /* 0x28 */ u64 context_size; + /* 0x30 */ u32 loop_start; + /* 0x34 */ u32 loop_end; + }; + static_assert(sizeof(WaveBufferInternal) == 0x38, + "VoiceInfo::WaveBufferInternal has the wrong size!"); + + struct BiquadFilterParameter { + /* 0x00 */ bool enabled; + /* 0x02 */ std::array b; + /* 0x08 */ std::array a; + }; + static_assert(sizeof(BiquadFilterParameter) == 0xC, + "VoiceInfo::BiquadFilterParameter has the wrong size!"); + + struct InParameter { + /* 0x000 */ u32 id; + /* 0x004 */ u32 node_id; + /* 0x008 */ bool is_new; + /* 0x009 */ bool in_use; + /* 0x00A */ PlayState play_state; + /* 0x00B */ SampleFormat sample_format; + /* 0x00C */ u32 sample_rate; + /* 0x010 */ s32 priority; + /* 0x014 */ s32 sort_order; + /* 0x018 */ u32 channel_count; + /* 0x01C */ f32 pitch; + /* 0x020 */ f32 volume; + /* 0x024 */ std::array biquads; + /* 0x03C */ u32 wave_buffer_count; + /* 0x040 */ u16 wave_buffer_index; + /* 0x042 */ char unk042[0x6]; + /* 0x048 */ CpuAddr src_data_address; + /* 0x050 */ u64 src_data_size; + /* 0x058 */ u32 mix_id; + /* 0x05C */ u32 splitter_id; + /* 0x060 */ std::array wave_buffer_internal; + /* 0x140 */ std::array channel_resource_ids; + /* 0x158 */ bool clear_voice_drop; + /* 0x159 */ u8 flush_buffer_count; + /* 0x15A */ char unk15A[0x2]; + /* 0x15C */ Flags flags; + /* 0x15D */ char unk15D[0x1]; + /* 0x15E */ SrcQuality src_quality; + /* 0x15F */ char unk15F[0x11]; + }; + static_assert(sizeof(InParameter) == 0x170, "VoiceInfo::InParameter has the wrong size!"); + + struct OutStatus { + /* 0x00 */ u64 played_sample_count; + /* 0x08 */ u32 wave_buffers_consumed; + /* 0x0C */ bool voice_dropped; + }; + static_assert(sizeof(OutStatus) == 0x10, "OutStatus::InParameter has the wrong size!"); + + VoiceInfo(); + + /** + * Initialize this voice. + */ + void Initialize(); + + /** + * Does this voice ned an update? + * + * @param params - Input parametetrs to check matching. + * @return True if this voice needs an update, otherwise false. + */ + bool ShouldUpdateParameters(const InParameter& params) const; + + /** + * Update the parameters of this voice. + * + * @param error_info - Output error code. + * @param params - Input parametters to udpate from. + * @param pool_mapper - Used to map buffers. + * @param behavior - behavior to check supported features. + */ + void UpdateParameters(BehaviorInfo::ErrorInfo& error_info, const InParameter& params, + const PoolMapper& pool_mapper, const BehaviorInfo& behavior); + + /** + * Update the current play state. + * + * @param state - New play state for this voice. + */ + void UpdatePlayState(PlayState state); + + /** + * Update the current sample rate conversion quality. + * + * @param quality - New quality. + */ + void UpdateSrcQuality(SrcQuality quality); + + /** + * Update all wavebuffers. + * + * @param error_infos - Output 2D array of errors, 2 per wavebuffer. + * @param error_count - Number of errors provided. Unused. + * @param params - Input parametters to be used for the update. + * @param voice_states - The voice states for each channel in this voice to be updated. + * @param pool_mapper - Used to map the wavebuffers. + * @param behavior - Used to check for supported features. + */ + void UpdateWaveBuffers(std::span> error_infos, + u32 error_count, const InParameter& params, + std::span voice_states, const PoolMapper& pool_mapper, + const BehaviorInfo& behavior); + + /** + * Update a wavebuffer. + * + * @param error_infos - Output array of errors. + * @param wave_buffer - The wavebuffer to be updated. + * @param wave_buffer_internal - Input parametters to be used for the update. + * @param sample_format - Sample format of the wavebuffer. + * @param valid - Is this wavebuffer valid? + * @param pool_mapper - Used to map the wavebuffers. + * @param behavior - Used to check for supported features. + */ + void UpdateWaveBuffer(std::span error_info, WaveBuffer& wave_buffer, + const WaveBufferInternal& wave_buffer_internal, + SampleFormat sample_format, bool valid, const PoolMapper& pool_mapper, + const BehaviorInfo& behavior); + + /** + * Check if the input wavebuffer needs an update. + * + * @param wave_buffer_internal - Input wavebuffer parameters to check. + * @return True if the given wavebuffer needs an update, otherwise false. + */ + bool ShouldUpdateWaveBuffer(const WaveBufferInternal& wave_buffer_internal) const; + + /** + * Write the number of played samples, number of consumed wavebuffers and if this voice was + * dropped, to the given out_status. + * + * @param out_status - Output status to be written to. + * @param in_params - Input parameters to check if the wavebuffer is new. + * @param voice_states - Current host voice states for this voice, source of the output. + */ + void WriteOutStatus(OutStatus& out_status, const InParameter& in_params, + std::span voice_states); + + /** + * Check if this voice should be skipped for command generation. + * Checks various things such as usage state, whether data is mapped etc. + * + * @return True if this voice should not be generated, otherwise false. + */ + bool ShouldSkip() const; + + /** + * Check if this voice has any mixing connections. + * + * @return True if this voice participes in mixing, otherwise false. + */ + bool HasAnyConnection() const; + + /** + * Flush flush_count wavebuffers, marking them as consumed. + * + * @param flush_count - Number of wavebuffers to flush. + * @param voice_states - Voice states for these wavebuffers. + * @param channel_count - Number of active channels. + */ + void FlushWaveBuffers(u32 flush_count, std::span voice_states, s8 channel_count); + + /** + * Update this voice's parameters on command generation, + * updating voice states and flushing if needed. + * + * @param voice_states - Voice states for these wavebuffers. + * @return True if this voice should be generated, otherwise false. + */ + bool UpdateParametersForCommandGeneration(std::span voice_states); + + /** + * Update this voice on command generation. + * + * @param voice_states - Voice states for these wavebuffers. + * @return True if this voice should be generated, otherwise false. + */ + bool UpdateForCommandGeneration(VoiceContext& voice_context); + + /** + * Reset the AudioRenderer-side voice states, and the channel resources for this voice. + * + * @param voice_context - Context from which to get the resources. + */ + void ResetResources(VoiceContext& voice_context) const; + + /// Is this voice in use? + bool in_use{}; + /// Is this voice new? + bool is_new{}; + /// Was this voice last playing? Used for depopping + bool was_playing{}; + /// Sample format of the wavebuffers in this voice + SampleFormat sample_format{}; + /// Sample rate of the wavebuffers in this voice + u32 sample_rate{}; + /// Number of channels in this voice + s8 channel_count{}; + /// Id of this voice + u32 id{}; + /// Node id of this voice + u32 node_id{}; + /// Mix id this voice is mixed to + u32 mix_id{}; + /// Play state of this voice + ServerPlayState current_play_state{ServerPlayState::Stopped}; + /// Last play state of this voice + ServerPlayState last_play_state{ServerPlayState::Started}; + /// Priority of this voice, lower is higher + s32 priority{}; + /// Sort order of this voice, used when same priority + s32 sort_order{}; + /// Pitch of this voice (for sample rate conversion) + f32 pitch{}; + /// Current volume of this voice + f32 volume{}; + /// Previous volume of this voice + f32 prev_volume{}; + /// Biquad filters for generating filter commands on this voice + std::array biquads{}; + /// Number of active wavebuffers + u32 wave_buffer_count{}; + /// Current playing wavebuffer index + u16 wave_buffer_index{}; + /// Flags controlling decode behavior + u16 flags{}; + /// Game memory for ADPCM coefficients + AddressInfo data_address{0, 0}; + /// Wavebuffers + std::array wavebuffers{}; + /// Channel resources for this voice + std::array channel_resource_ids{}; + /// Splitter id this voice is connected with + s32 splitter_id{UnusedSplitterId}; + /// Sample rate conversion quality + SrcQuality src_quality{SrcQuality::Medium}; + /// Was this voice dropped due to limited time? + bool voice_dropped{}; + /// Is this voice's coefficient (data_address) unmapped? + bool data_unmapped{}; + /// Is this voice's buffers (wavebuffer data and ADPCM context) unmapped? + bool buffer_unmapped{}; + /// Initialisation state of the biquads + std::array biquad_initialized{}; + /// Number of wavebuffers to flush + u8 flush_buffer_count{}; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/voice/voice_state.h b/src/audio_core/renderer/voice/voice_state.h new file mode 100644 index 000000000..d5497e2fb --- /dev/null +++ b/src/audio_core/renderer/voice/voice_state.h @@ -0,0 +1,70 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "audio_core/common/common.h" +#include "common/common_types.h" +#include "common/fixed_point.h" + +namespace AudioCore::AudioRenderer { +/** + * Holds a state for a voice. One is kept host-side, and one is used by the AudioRenderer, + * host-side is updated on the next iteration. + */ +struct VoiceState { + /** + * State of the voice's biquad filter. + */ + struct BiquadFilterState { + Common::FixedPoint<50, 14> s0; + Common::FixedPoint<50, 14> s1; + Common::FixedPoint<50, 14> s2; + Common::FixedPoint<50, 14> s3; + }; + + /** + * Context for ADPCM decoding. + */ + struct AdpcmContext { + u16 header; + s16 yn0; + s16 yn1; + }; + + /// Number of samples played + u64 played_sample_count; + /// Current offset from the starting offset + u32 offset; + /// Currently active wavebuffer index + u32 wave_buffer_index; + /// Array of which wavebuffers are currently valid + + std::array wave_buffer_valid; + /// Number of wavebuffers consumed, given back to the game + u32 wave_buffers_consumed; + /// History of samples, used for rate conversion + + std::array sample_history; + /// Current read fraction, used for resampling + Common::FixedPoint<49, 15> fraction; + /// Current adpcm context + AdpcmContext adpcm_context; + /// Current biquad states, used when filtering + + std::array, MaxBiquadFilters> biquad_states; + /// Previous samples + std::array previous_samples; + /// Unused + u32 external_context_size; + /// Unused + bool external_context_enabled; + /// Was this voice dropped? + bool voice_dropped; + /// Number of times the wavebuffer has looped + s32 loop_count; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/sdl2_sink.cpp b/src/audio_core/sdl2_sink.cpp deleted file mode 100644 index a10ba4044..000000000 --- a/src/audio_core/sdl2_sink.cpp +++ /dev/null @@ -1,160 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include -#include -#include -#include "audio_core/sdl2_sink.h" -#include "audio_core/stream.h" -#include "common/assert.h" -#include "common/logging/log.h" -//#include "common/settings.h" - -// Ignore -Wimplicit-fallthrough due to https://github.com/libsdl-org/SDL/issues/4307 -#ifdef __clang__ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wimplicit-fallthrough" -#endif -#include -#ifdef __clang__ -#pragma clang diagnostic pop -#endif - -namespace AudioCore { - -class SDLSinkStream final : public SinkStream { -public: - SDLSinkStream(u32 sample_rate, u32 num_channels_, const std::string& output_device) - : num_channels{std::min(num_channels_, 6u)} { - - SDL_AudioSpec spec; - spec.freq = sample_rate; - spec.channels = static_cast(num_channels); - spec.format = AUDIO_S16SYS; - spec.samples = 4096; - spec.callback = nullptr; - - SDL_AudioSpec obtained; - if (output_device.empty()) { - dev = SDL_OpenAudioDevice(nullptr, 0, &spec, &obtained, 0); - } else { - dev = SDL_OpenAudioDevice(output_device.c_str(), 0, &spec, &obtained, 0); - } - - if (dev == 0) { - LOG_CRITICAL(Audio_Sink, "Error opening sdl audio device: {}", SDL_GetError()); - return; - } - - SDL_PauseAudioDevice(dev, 0); - } - - ~SDLSinkStream() override { - if (dev == 0) { - return; - } - - SDL_CloseAudioDevice(dev); - } - - void EnqueueSamples(u32 source_num_channels, const std::vector& 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 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(left + (clev * center / 1000) + - (slev * surround_left / 1000))); - buf.push_back(static_cast(right + (clev * center / 1000) + - (slev * surround_right / 1000))); - } - int ret = SDL_QueueAudio(dev, static_cast(buf.data()), - static_cast(buf.size() * sizeof(s16))); - if (ret < 0) - LOG_WARNING(Audio_Sink, "Could not queue audio buffer: {}", SDL_GetError()); - return; - } - - int ret = SDL_QueueAudio(dev, static_cast(samples.data()), - static_cast(samples.size() * sizeof(s16))); - if (ret < 0) - LOG_WARNING(Audio_Sink, "Could not queue audio buffer: {}", SDL_GetError()); - } - - std::size_t SamplesInQueue(u32 channel_count) const override { - if (dev == 0) - return 0; - - return SDL_GetQueuedAudioSize(dev) / (channel_count * sizeof(s16)); - } - - void Flush() override { - should_flush = true; - } - - u32 GetNumChannels() const { - return num_channels; - } - -private: - SDL_AudioDeviceID dev = 0; - u32 num_channels{}; - std::atomic should_flush{}; -}; - -SDLSink::SDLSink(std::string_view target_device_name) { - if (!SDL_WasInit(SDL_INIT_AUDIO)) { - if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) { - LOG_CRITICAL(Audio_Sink, "SDL_InitSubSystem audio failed: {}", SDL_GetError()); - return; - } - } - - if (target_device_name != auto_device_name && !target_device_name.empty()) { - output_device = target_device_name; - } else { - output_device.clear(); - } -} - -SDLSink::~SDLSink() = default; - -SinkStream& SDLSink::AcquireSinkStream(u32 sample_rate, u32 num_channels, const std::string&) { - sink_streams.push_back( - std::make_unique(sample_rate, num_channels, output_device)); - return *sink_streams.back(); -} - -std::vector ListSDLSinkDevices() { - std::vector device_list; - - if (!SDL_WasInit(SDL_INIT_AUDIO)) { - if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) { - LOG_CRITICAL(Audio_Sink, "SDL_InitSubSystem audio failed: {}", SDL_GetError()); - return {}; - } - } - - const int device_count = SDL_GetNumAudioDevices(0); - for (int i = 0; i < device_count; ++i) { - device_list.emplace_back(SDL_GetAudioDeviceName(i, 0)); - } - - return device_list; -} - -} // namespace AudioCore diff --git a/src/audio_core/sdl2_sink.h b/src/audio_core/sdl2_sink.h deleted file mode 100644 index f1dd1d677..000000000 --- a/src/audio_core/sdl2_sink.h +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include - -#include "audio_core/sink.h" - -namespace AudioCore { - -class SDLSink final : public Sink { -public: - explicit SDLSink(std::string_view device_id); - ~SDLSink() override; - - SinkStream& AcquireSinkStream(u32 sample_rate, u32 num_channels, - const std::string& name) override; - -private: - std::string output_device; - std::vector sink_streams; -}; - -std::vector ListSDLSinkDevices(); - -} // namespace AudioCore diff --git a/src/audio_core/sink.h b/src/audio_core/sink.h deleted file mode 100644 index 3c03554fa..000000000 --- a/src/audio_core/sink.h +++ /dev/null @@ -1,30 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include - -#include "audio_core/sink_stream.h" -#include "common/common_types.h" - -namespace AudioCore { - -constexpr char auto_device_name[] = "auto"; - -/** - * This class is an interface for an audio sink. An audio sink accepts samples in stereo signed - * PCM16 format to be output. Sinks *do not* handle resampling and expect the correct sample rate. - * They are dumb outputs. - */ -class Sink { -public: - virtual ~Sink() = default; - virtual SinkStream& AcquireSinkStream(u32 sample_rate, u32 num_channels, - const std::string& name) = 0; -}; - -using SinkPtr = std::unique_ptr; - -} // namespace AudioCore diff --git a/src/audio_core/sink/cubeb_sink.cpp b/src/audio_core/sink/cubeb_sink.cpp new file mode 100644 index 000000000..a4e28de6d --- /dev/null +++ b/src/audio_core/sink/cubeb_sink.cpp @@ -0,0 +1,651 @@ +// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include + +#include "audio_core/audio_core.h" +#include "audio_core/audio_event.h" +#include "audio_core/audio_manager.h" +#include "audio_core/sink/cubeb_sink.h" +#include "audio_core/sink/sink_stream.h" +#include "common/assert.h" +#include "common/fixed_point.h" +#include "common/logging/log.h" +#include "common/reader_writer_queue.h" +#include "common/ring_buffer.h" +#include "common/settings.h" +#include "core/core.h" + +#ifdef _WIN32 +#include +#undef CreateEvent +#endif + +namespace AudioCore::Sink { +/** + * Cubeb sink stream, responsible for sinking samples to hardware. + */ +class CubebSinkStream final : public SinkStream { +public: + /** + * Create a new sink stream. + * + * @param ctx_ - Cubeb context to create this stream with. + * @param device_channels_ - Number of channels supported by the hardware. + * @param system_channels_ - Number of channels the audio systems expect. + * @param output_device - Cubeb output device id. + * @param input_device - Cubeb input device id. + * @param name_ - Name of this stream. + * @param type_ - Type of this stream. + * @param system_ - Core system. + * @param event - Event used only for audio renderer, signalled on buffer consume. + */ + CubebSinkStream(cubeb* ctx_, const u32 device_channels_, const u32 system_channels_, + cubeb_devid output_device, cubeb_devid input_device, const std::string& name_, + const StreamType type_, Core::System& system_) + : ctx{ctx_}, type{type_}, system{system_} { +#ifdef _WIN32 + CoInitializeEx(nullptr, COINIT_MULTITHREADED); +#endif + name = name_; + device_channels = device_channels_; + system_channels = system_channels_; + + cubeb_stream_params params{}; + params.rate = TargetSampleRate; + params.channels = device_channels; + params.format = CUBEB_SAMPLE_S16LE; + params.prefs = CUBEB_STREAM_PREF_NONE; + switch (params.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{0}; + const auto latency_error = cubeb_get_min_latency(ctx, ¶ms, &minimum_latency); + if (latency_error != CUBEB_OK) { + LOG_CRITICAL(Audio_Sink, "Error getting minimum latency, error: {}", latency_error); + minimum_latency = 256U; + } + + minimum_latency = std::max(minimum_latency, 256u); + + playing_buffer.consumed = true; + + LOG_DEBUG(Service_Audio, + "Opening cubeb stream {} type {} with: rate {} channels {} (system channels {}) " + "latency {}", + name, type, params.rate, params.channels, system_channels, minimum_latency); + + auto init_error{0}; + if (type == StreamType::In) { + init_error = cubeb_stream_init(ctx, &stream_backend, name.c_str(), input_device, + ¶ms, output_device, nullptr, minimum_latency, + &CubebSinkStream::DataCallback, + &CubebSinkStream::StateCallback, this); + } else { + init_error = cubeb_stream_init(ctx, &stream_backend, name.c_str(), input_device, + nullptr, output_device, ¶ms, minimum_latency, + &CubebSinkStream::DataCallback, + &CubebSinkStream::StateCallback, this); + } + + if (init_error != CUBEB_OK) { + LOG_CRITICAL(Audio_Sink, "Error initializing cubeb stream, error: {}", init_error); + return; + } + } + + /** + * Destroy the sink stream. + */ + ~CubebSinkStream() override { + LOG_DEBUG(Service_Audio, "Destructing cubeb stream {}", name); + + if (!ctx) { + return; + } + + Finalize(); + +#ifdef _WIN32 + CoUninitialize(); +#endif + } + + /** + * Finalize the sink stream. + */ + void Finalize() override { + Stop(); + cubeb_stream_destroy(stream_backend); + } + + /** + * Start the sink stream. + * + * @param resume - Set to true if this is resuming the stream a previously-active stream. + * Default false. + */ + void Start(const bool resume = false) override { + if (!ctx) { + return; + } + + if (resume && was_playing) { + if (cubeb_stream_start(stream_backend) != CUBEB_OK) { + LOG_CRITICAL(Audio_Sink, "Error starting cubeb stream"); + } + paused = false; + } else if (!resume) { + if (cubeb_stream_start(stream_backend) != CUBEB_OK) { + LOG_CRITICAL(Audio_Sink, "Error starting cubeb stream"); + } + paused = false; + } + } + + /** + * Stop the sink stream. + */ + void Stop() override { + if (!ctx) { + return; + } + + if (cubeb_stream_stop(stream_backend) != CUBEB_OK) { + LOG_CRITICAL(Audio_Sink, "Error stopping cubeb stream"); + } + + was_playing.store(!paused); + paused = true; + } + + /** + * Append a new buffer and its samples to a waiting queue to play. + * + * @param buffer - Audio buffer information to be queued. + * @param samples - The s16 samples to be queue for playback. + */ + void AppendBuffer(::AudioCore::Sink::SinkBuffer& buffer, std::vector& samples) override { + if (type == StreamType::In) { + queue.enqueue(buffer); + queued_buffers++; + } else { + constexpr s32 min{std::numeric_limits::min()}; + constexpr s32 max{std::numeric_limits::max()}; + + auto yuzu_volume{Settings::Volume()}; + auto volume{system_volume * device_volume * yuzu_volume}; + + if (system_channels == 6 && device_channels == 2) { + // We're given 6 channels, but our device only outputs 2, so downmix. + constexpr std::array down_mix_coeff{1.0f, 0.707f, 0.251f, 0.707f}; + + for (u32 read_index = 0, write_index = 0; read_index < samples.size(); + read_index += system_channels, write_index += device_channels) { + const auto left_sample{ + ((Common::FixedPoint<49, 15>( + samples[read_index + static_cast(Channels::FrontLeft)]) * + down_mix_coeff[0] + + samples[read_index + static_cast(Channels::Center)] * + down_mix_coeff[1] + + samples[read_index + static_cast(Channels::LFE)] * + down_mix_coeff[2] + + samples[read_index + static_cast(Channels::BackLeft)] * + down_mix_coeff[3]) * + volume) + .to_int()}; + + const auto right_sample{ + ((Common::FixedPoint<49, 15>( + samples[read_index + static_cast(Channels::FrontRight)]) * + down_mix_coeff[0] + + samples[read_index + static_cast(Channels::Center)] * + down_mix_coeff[1] + + samples[read_index + static_cast(Channels::LFE)] * + down_mix_coeff[2] + + samples[read_index + static_cast(Channels::BackRight)] * + down_mix_coeff[3]) * + volume) + .to_int()}; + + samples[write_index + static_cast(Channels::FrontLeft)] = + static_cast(std::clamp(left_sample, min, max)); + samples[write_index + static_cast(Channels::FrontRight)] = + static_cast(std::clamp(right_sample, min, max)); + } + + samples.resize(samples.size() / system_channels * device_channels); + + } else if (system_channels == 2 && device_channels == 6) { + // We need moar samples! Not all games will provide 6 channel audio. + // TODO: Implement some upmixing here. Currently just passthrough, with other + // channels left as silence. + std::vector new_samples(samples.size() / system_channels * device_channels, 0); + + for (u32 read_index = 0, write_index = 0; read_index < samples.size(); + read_index += system_channels, write_index += device_channels) { + const auto left_sample{static_cast(std::clamp( + static_cast( + static_cast( + samples[read_index + static_cast(Channels::FrontLeft)]) * + volume), + min, max))}; + + new_samples[write_index + static_cast(Channels::FrontLeft)] = left_sample; + + const auto right_sample{static_cast(std::clamp( + static_cast( + static_cast( + samples[read_index + static_cast(Channels::FrontRight)]) * + volume), + min, max))}; + + new_samples[write_index + static_cast(Channels::FrontRight)] = + right_sample; + } + samples = std::move(new_samples); + + } else if (volume != 1.0f) { + for (u32 i = 0; i < samples.size(); i++) { + samples[i] = static_cast(std::clamp( + static_cast(static_cast(samples[i]) * volume), min, max)); + } + } + + samples_buffer.Push(samples); + queue.enqueue(buffer); + queued_buffers++; + } + } + + /** + * Release a buffer. Audio In only, will fill a buffer with recorded samples. + * + * @param num_samples - Maximum number of samples to receive. + * @return Vector of recorded samples. May have fewer than num_samples. + */ + std::vector ReleaseBuffer(const u64 num_samples) override { + static constexpr s32 min = std::numeric_limits::min(); + static constexpr s32 max = std::numeric_limits::max(); + + auto samples{samples_buffer.Pop(num_samples)}; + + // TODO: Up-mix to 6 channels if the game expects it. + // For audio input this is unlikely to ever be the case though. + + // Incoming mic volume seems to always be very quiet, so multiply by an additional 8 here. + // TODO: Play with this and find something that works better. + auto volume{system_volume * device_volume * 8}; + for (u32 i = 0; i < samples.size(); i++) { + samples[i] = static_cast( + std::clamp(static_cast(static_cast(samples[i]) * volume), min, max)); + } + + if (samples.size() < num_samples) { + samples.resize(num_samples, 0); + } + return samples; + } + + /** + * Check if a certain buffer has been consumed (fully played). + * + * @param tag - Unique tag of a buffer to check for. + * @return True if the buffer has been played, otherwise false. + */ + bool IsBufferConsumed(const u64 tag) override { + if (released_buffer.tag == 0) { + if (!released_buffers.try_dequeue(released_buffer)) { + return false; + } + } + + if (released_buffer.tag == tag) { + released_buffer.tag = 0; + return true; + } + return false; + } + + /** + * Empty out the buffer queue. + */ + void ClearQueue() override { + samples_buffer.Pop(); + while (queue.pop()) { + } + while (released_buffers.pop()) { + } + queued_buffers = 0; + released_buffer = {}; + playing_buffer = {}; + playing_buffer.consumed = true; + } + +private: + /** + * Signal events back to the audio system that a buffer was played/can be filled. + * + * @param buffer - Consumed audio buffer to be released. + */ + void SignalEvent(const ::AudioCore::Sink::SinkBuffer& buffer) { + auto& manager{system.AudioCore().GetAudioManager()}; + switch (type) { + case StreamType::Out: + released_buffers.enqueue(buffer); + manager.SetEvent(Event::Type::AudioOutManager, true); + break; + case StreamType::In: + released_buffers.enqueue(buffer); + manager.SetEvent(Event::Type::AudioInManager, true); + break; + case StreamType::Render: + break; + } + } + + /** + * Main callback from Cubeb. Either expects samples from us (audio render/audio out), or will + * provide samples to be copied (audio in). + * + * @param stream - Cubeb-specific data about the stream. + * @param user_data - Custom data pointer passed along, points to a CubebSinkStream. + * @param in_buff - Input buffer to be used if the stream is an input type. + * @param out_buff - Output buffer to be used if the stream is an output type. + * @param num_frames_ - Number of frames of audio in the buffers. Note: Not number of samples. + */ + static long DataCallback([[maybe_unused]] cubeb_stream* stream, void* user_data, + [[maybe_unused]] const void* in_buff, void* out_buff, + long num_frames_) { + auto* impl = static_cast(user_data); + if (!impl) { + return -1; + } + + const std::size_t num_channels = impl->GetDeviceChannels(); + const std::size_t frame_size = num_channels; + const std::size_t frame_size_bytes = frame_size * sizeof(s16); + const std::size_t num_frames{static_cast(num_frames_)}; + size_t frames_written{0}; + [[maybe_unused]] bool underrun{false}; + + if (impl->type == StreamType::In) { + // INPUT + std::span input_buffer{reinterpret_cast(in_buff), + num_frames * frame_size}; + + while (frames_written < num_frames) { + auto& playing_buffer{impl->playing_buffer}; + + // If the playing buffer has been consumed or has no frames, we need a new one + if (playing_buffer.consumed || playing_buffer.frames == 0) { + if (!impl->queue.try_dequeue(impl->playing_buffer)) { + // If no buffer was available we've underrun, just push the samples and + // continue. + underrun = true; + impl->samples_buffer.Push(&input_buffer[frames_written * frame_size], + (num_frames - frames_written) * frame_size); + frames_written = num_frames; + continue; + } else { + // Successfully got a new buffer, mark the old one as consumed and signal. + impl->queued_buffers--; + impl->SignalEvent(impl->playing_buffer); + } + } + + // Get the minimum frames available between the currently playing buffer, and the + // amount we have left to fill + size_t frames_available{ + std::min(playing_buffer.frames - playing_buffer.frames_played, + num_frames - frames_written)}; + + impl->samples_buffer.Push(&input_buffer[frames_written * frame_size], + frames_available * frame_size); + + frames_written += frames_available; + playing_buffer.frames_played += frames_available; + + // If that's all the frames in the current buffer, add its samples and mark it as + // consumed + if (playing_buffer.frames_played >= playing_buffer.frames) { + impl->AddPlayedSampleCount(playing_buffer.frames_played * num_channels); + impl->playing_buffer.consumed = true; + } + } + + std::memcpy(&impl->last_frame[0], &input_buffer[(frames_written - 1) * frame_size], + frame_size_bytes); + } else { + // OUTPUT + std::span output_buffer{reinterpret_cast(out_buff), num_frames * frame_size}; + + while (frames_written < num_frames) { + auto& playing_buffer{impl->playing_buffer}; + + // If the playing buffer has been consumed or has no frames, we need a new one + if (playing_buffer.consumed || playing_buffer.frames == 0) { + if (!impl->queue.try_dequeue(impl->playing_buffer)) { + // If no buffer was available we've underrun, fill the remaining buffer with + // the last written frame and continue. + underrun = true; + for (size_t i = frames_written; i < num_frames; i++) { + std::memcpy(&output_buffer[i * frame_size], &impl->last_frame[0], + frame_size_bytes); + } + frames_written = num_frames; + continue; + } else { + // Successfully got a new buffer, mark the old one as consumed and signal. + impl->queued_buffers--; + impl->SignalEvent(impl->playing_buffer); + } + } + + // Get the minimum frames available between the currently playing buffer, and the + // amount we have left to fill + size_t frames_available{ + std::min(playing_buffer.frames - playing_buffer.frames_played, + num_frames - frames_written)}; + + impl->samples_buffer.Pop(&output_buffer[frames_written * frame_size], + frames_available * frame_size); + + frames_written += frames_available; + playing_buffer.frames_played += frames_available; + + // If that's all the frames in the current buffer, add its samples and mark it as + // consumed + if (playing_buffer.frames_played >= playing_buffer.frames) { + impl->AddPlayedSampleCount(playing_buffer.frames_played * num_channels); + impl->playing_buffer.consumed = true; + } + } + + std::memcpy(&impl->last_frame[0], &output_buffer[(frames_written - 1) * frame_size], + frame_size_bytes); + } + + return num_frames_; + } + + /** + * Cubeb callback for if a device state changes. Unused currently. + * + * @param stream - Cubeb-specific data about the stream. + * @param user_data - Custom data pointer passed along, points to a CubebSinkStream. + * @param state - New state of the device. + */ + static void StateCallback([[maybe_unused]] cubeb_stream* stream, + [[maybe_unused]] void* user_data, + [[maybe_unused]] cubeb_state state) {} + + /// Main Cubeb context + cubeb* ctx{}; + /// Cubeb stream backend + cubeb_stream* stream_backend{}; + /// Name of this stream + std::string name{}; + /// Type of this stream + StreamType type; + /// Core system + Core::System& system; + /// Ring buffer of the samples waiting to be played or consumed + Common::RingBuffer samples_buffer; + /// Audio buffers queued and waiting to play + Common::ReaderWriterQueue<::AudioCore::Sink::SinkBuffer> queue; + /// The currently-playing audio buffer + ::AudioCore::Sink::SinkBuffer playing_buffer{}; + /// Audio buffers which have been played and are in queue to be released by the audio system + Common::ReaderWriterQueue<::AudioCore::Sink::SinkBuffer> released_buffers{}; + /// Currently released buffer waiting to be taken by the audio system + ::AudioCore::Sink::SinkBuffer released_buffer{}; + /// The last played (or received) frame of audio, used when the callback underruns + std::array last_frame{}; +}; + +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 == std::string(info.friendly_name); + })}; + if (device != collection_end) { + output_device = device->devid; + } + cubeb_device_collection_destroy(ctx, &collection); + } + } + + cubeb_get_max_channel_count(ctx, &device_channels); + device_channels = device_channels >= 6U ? 6U : 2U; +} + +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(Core::System& system, const u32 system_channels, + const std::string& name, const StreamType type) { + SinkStreamPtr& stream = sink_streams.emplace_back(std::make_unique( + ctx, device_channels, system_channels, output_device, input_device, name, type, system)); + + return stream.get(); +} + +void CubebSink::CloseStream(const SinkStream* stream) { + for (size_t i = 0; i < sink_streams.size(); i++) { + if (sink_streams[i].get() == stream) { + sink_streams[i].reset(); + sink_streams.erase(sink_streams.begin() + i); + break; + } + } +} + +void CubebSink::CloseStreams() { + sink_streams.clear(); +} + +void CubebSink::PauseStreams() { + for (auto& stream : sink_streams) { + stream->Stop(); + } +} + +void CubebSink::UnpauseStreams() { + for (auto& stream : sink_streams) { + stream->Start(true); + } +} + +f32 CubebSink::GetDeviceVolume() const { + if (sink_streams.empty()) { + return 1.0f; + } + + return sink_streams[0]->GetDeviceVolume(); +} + +void CubebSink::SetDeviceVolume(const f32 volume) { + for (auto& stream : sink_streams) { + stream->SetDeviceVolume(volume); + } +} + +void CubebSink::SetSystemVolume(const f32 volume) { + for (auto& stream : sink_streams) { + stream->SetSystemVolume(volume); + } +} + +std::vector ListCubebSinkDevices(const bool capture) { + std::vector device_list; + cubeb* ctx; + + if (cubeb_init(&ctx, "yuzu Device Enumerator", nullptr) != CUBEB_OK) { + LOG_CRITICAL(Audio_Sink, "cubeb_init failed"); + return {}; + } + + auto type{capture ? CUBEB_DEVICE_TYPE_INPUT : CUBEB_DEVICE_TYPE_OUTPUT}; + cubeb_device_collection collection; + if (cubeb_enumerate_devices(ctx, type, &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.friendly_name[0] != '\0' && + device.state == CUBEB_DEVICE_STATE_ENABLED) { + device_list.emplace_back(device.friendly_name); + } + } + cubeb_device_collection_destroy(ctx, &collection); + } + + cubeb_destroy(ctx); + return device_list; +} + +} // namespace AudioCore::Sink diff --git a/src/audio_core/sink/cubeb_sink.h b/src/audio_core/sink/cubeb_sink.h new file mode 100644 index 000000000..f0f43dfa1 --- /dev/null +++ b/src/audio_core/sink/cubeb_sink.h @@ -0,0 +1,110 @@ +// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#include + +#include "audio_core/sink/sink.h" + +namespace Core { +class System; +} + +namespace AudioCore::Sink { +class SinkStream; + +/** + * Cubeb backend sink, holds multiple output streams and is responsible for sinking samples to + * hardware. Used by Audio Render, Audio In and Audio Out. + */ +class CubebSink final : public Sink { +public: + explicit CubebSink(std::string_view device_id); + ~CubebSink() override; + + /** + * Create a new sink stream. + * + * @param system - Core system. + * @param system_channels - Number of channels the audio system expects. + * May differ from the device's channel count. + * @param name - Name of this stream. + * @param type - Type of this stream, render/in/out. + * @param event - Audio render only, a signal used to prevent the renderer running too + * fast. + * @return A pointer to the created SinkStream + */ + SinkStream* AcquireSinkStream(Core::System& system, u32 system_channels, + const std::string& name, StreamType type) override; + + /** + * Close a given stream. + * + * @param stream - The stream to close. + */ + void CloseStream(const SinkStream* stream) override; + + /** + * Close all streams. + */ + void CloseStreams() override; + + /** + * Pause all streams. + */ + void PauseStreams() override; + + /** + * Unpause all streams. + */ + void UnpauseStreams() override; + + /** + * Get the device volume. Set from calls to the IAudioDevice service. + * + * @return Volume of the device. + */ + f32 GetDeviceVolume() const override; + + /** + * Set the device volume. Set from calls to the IAudioDevice service. + * + * @param volume - New volume of the device. + */ + void SetDeviceVolume(f32 volume) override; + + /** + * Set the system volume. Comes from the audio system using this stream. + * + * @param volume - New volume of the system. + */ + void SetSystemVolume(f32 volume) override; + +private: + /// Backend Cubeb context + cubeb* ctx{}; + /// Cubeb id of the actual hardware output device + cubeb_devid output_device{}; + /// Cubeb id of the actual hardware input device + cubeb_devid input_device{}; + /// Vector of streams managed by this sink + std::vector sink_streams{}; + +#ifdef _WIN32 + /// Cubeb required COM to be initialized multi-threaded on Windows + u32 com_init_result = 0; +#endif +}; + +/** + * Get a list of conencted devices from Cubeb. + * + * @param capture - Return input (capture) devices if true, otherwise output devices. + */ +std::vector ListCubebSinkDevices(bool capture); + +} // namespace AudioCore::Sink diff --git a/src/audio_core/sink/null_sink.h b/src/audio_core/sink/null_sink.h new file mode 100644 index 000000000..47a342171 --- /dev/null +++ b/src/audio_core/sink/null_sink.h @@ -0,0 +1,52 @@ +// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "audio_core/sink/sink.h" +#include "audio_core/sink/sink_stream.h" + +namespace AudioCore::Sink { +/** + * A no-op sink for when no audio out is wanted. + */ +class NullSink final : public Sink { +public: + explicit NullSink(std::string_view) {} + ~NullSink() override = default; + + SinkStream* AcquireSinkStream([[maybe_unused]] Core::System& system, + [[maybe_unused]] u32 system_channels, + [[maybe_unused]] const std::string& name, + [[maybe_unused]] StreamType type) override { + return &null_sink_stream; + } + + void CloseStream([[maybe_unused]] const SinkStream* stream) override {} + void CloseStreams() override {} + void PauseStreams() override {} + void UnpauseStreams() override {} + f32 GetDeviceVolume() const override { + return 1.0f; + } + void SetDeviceVolume(f32 volume) override {} + void SetSystemVolume(f32 volume) override {} + +private: + struct NullSinkStreamImpl final : SinkStream { + void Finalize() override {} + void Start(bool resume = false) override {} + void Stop() override {} + void AppendBuffer([[maybe_unused]] ::AudioCore::Sink::SinkBuffer& buffer, + [[maybe_unused]] std::vector& samples) override {} + std::vector ReleaseBuffer([[maybe_unused]] u64 num_samples) override { + return {}; + } + bool IsBufferConsumed([[maybe_unused]] const u64 tag) { + return true; + } + void ClearQueue() override {} + } null_sink_stream; +}; + +} // namespace AudioCore::Sink diff --git a/src/audio_core/sink/sdl2_sink.cpp b/src/audio_core/sink/sdl2_sink.cpp new file mode 100644 index 000000000..d6c9ec90d --- /dev/null +++ b/src/audio_core/sink/sdl2_sink.cpp @@ -0,0 +1,556 @@ +// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include + +#include "audio_core/audio_core.h" +#include "audio_core/audio_event.h" +#include "audio_core/audio_manager.h" +#include "audio_core/sink/sdl2_sink.h" +#include "audio_core/sink/sink_stream.h" +#include "common/assert.h" +#include "common/fixed_point.h" +#include "common/logging/log.h" +#include "common/reader_writer_queue.h" +#include "common/ring_buffer.h" +#include "common/settings.h" +#include "core/core.h" + +// Ignore -Wimplicit-fallthrough due to https://github.com/libsdl-org/SDL/issues/4307 +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wimplicit-fallthrough" +#endif +#include +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +namespace AudioCore::Sink { +/** + * SDL sink stream, responsible for sinking samples to hardware. + */ +class SDLSinkStream final : public SinkStream { +public: + /** + * Create a new sink stream. + * + * @param device_channels_ - Number of channels supported by the hardware. + * @param system_channels_ - Number of channels the audio systems expect. + * @param output_device - Name of the output device to use for this stream. + * @param input_device - Name of the input device to use for this stream. + * @param type_ - Type of this stream. + * @param system_ - Core system. + * @param event - Event used only for audio renderer, signalled on buffer consume. + */ + SDLSinkStream(u32 device_channels_, const u32 system_channels_, + const std::string& output_device, const std::string& input_device, + const StreamType type_, Core::System& system_) + : type{type_}, system{system_} { + system_channels = system_channels_; + device_channels = device_channels_; + + SDL_AudioSpec spec; + spec.freq = TargetSampleRate; + spec.channels = static_cast(device_channels); + spec.format = AUDIO_S16SYS; + if (type == StreamType::Render) { + spec.samples = TargetSampleCount; + } else { + spec.samples = 1024; + } + spec.callback = &SDLSinkStream::DataCallback; + spec.userdata = this; + + playing_buffer.consumed = true; + + std::string device_name{output_device}; + bool capture{false}; + if (type == StreamType::In) { + device_name = input_device; + capture = true; + } + + SDL_AudioSpec obtained; + if (device_name.empty()) { + device = SDL_OpenAudioDevice(nullptr, capture, &spec, &obtained, false); + } else { + device = SDL_OpenAudioDevice(device_name.c_str(), capture, &spec, &obtained, false); + } + + if (device == 0) { + LOG_CRITICAL(Audio_Sink, "Error opening SDL audio device: {}", SDL_GetError()); + return; + } + + LOG_DEBUG(Service_Audio, + "Opening sdl stream {} with: rate {} channels {} (system channels {}) " + " samples {}", + device, obtained.freq, obtained.channels, system_channels, obtained.samples); + } + + /** + * Destroy the sink stream. + */ + ~SDLSinkStream() override { + if (device == 0) { + return; + } + + SDL_CloseAudioDevice(device); + } + + /** + * Finalize the sink stream. + */ + void Finalize() override { + if (device == 0) { + return; + } + + SDL_CloseAudioDevice(device); + } + + /** + * Start the sink stream. + * + * @param resume - Set to true if this is resuming the stream a previously-active stream. + * Default false. + */ + void Start(const bool resume = false) override { + if (device == 0) { + return; + } + + if (resume && was_playing) { + SDL_PauseAudioDevice(device, 0); + paused = false; + } else if (!resume) { + SDL_PauseAudioDevice(device, 0); + paused = false; + } + } + + /** + * Stop the sink stream. + */ + void Stop() { + if (device == 0) { + return; + } + SDL_PauseAudioDevice(device, 1); + paused = true; + } + + /** + * Append a new buffer and its samples to a waiting queue to play. + * + * @param buffer - Audio buffer information to be queued. + * @param samples - The s16 samples to be queue for playback. + */ + void AppendBuffer(::AudioCore::Sink::SinkBuffer& buffer, std::vector& samples) override { + if (type == StreamType::In) { + queue.enqueue(buffer); + queued_buffers++; + } else { + constexpr s32 min = std::numeric_limits::min(); + constexpr s32 max = std::numeric_limits::max(); + + auto yuzu_volume{Settings::Volume()}; + auto volume{system_volume * device_volume * yuzu_volume}; + + if (system_channels == 6 && device_channels == 2) { + // We're given 6 channels, but our device only outputs 2, so downmix. + constexpr std::array down_mix_coeff{1.0f, 0.707f, 0.251f, 0.707f}; + + for (u32 read_index = 0, write_index = 0; read_index < samples.size(); + read_index += system_channels, write_index += device_channels) { + const auto left_sample{ + ((Common::FixedPoint<49, 15>( + samples[read_index + static_cast(Channels::FrontLeft)]) * + down_mix_coeff[0] + + samples[read_index + static_cast(Channels::Center)] * + down_mix_coeff[1] + + samples[read_index + static_cast(Channels::LFE)] * + down_mix_coeff[2] + + samples[read_index + static_cast(Channels::BackLeft)] * + down_mix_coeff[3]) * + volume) + .to_int()}; + + const auto right_sample{ + ((Common::FixedPoint<49, 15>( + samples[read_index + static_cast(Channels::FrontRight)]) * + down_mix_coeff[0] + + samples[read_index + static_cast(Channels::Center)] * + down_mix_coeff[1] + + samples[read_index + static_cast(Channels::LFE)] * + down_mix_coeff[2] + + samples[read_index + static_cast(Channels::BackRight)] * + down_mix_coeff[3]) * + volume) + .to_int()}; + + samples[write_index + static_cast(Channels::FrontLeft)] = + static_cast(std::clamp(left_sample, min, max)); + samples[write_index + static_cast(Channels::FrontRight)] = + static_cast(std::clamp(right_sample, min, max)); + } + + samples.resize(samples.size() / system_channels * device_channels); + + } else if (system_channels == 2 && device_channels == 6) { + // We need moar samples! Not all games will provide 6 channel audio. + // TODO: Implement some upmixing here. Currently just passthrough, with other + // channels left as silence. + std::vector new_samples(samples.size() / system_channels * device_channels, 0); + + for (u32 read_index = 0, write_index = 0; read_index < samples.size(); + read_index += system_channels, write_index += device_channels) { + const auto left_sample{static_cast(std::clamp( + static_cast( + static_cast( + samples[read_index + static_cast(Channels::FrontLeft)]) * + volume), + min, max))}; + + new_samples[write_index + static_cast(Channels::FrontLeft)] = left_sample; + + const auto right_sample{static_cast(std::clamp( + static_cast( + static_cast( + samples[read_index + static_cast(Channels::FrontRight)]) * + volume), + min, max))}; + + new_samples[write_index + static_cast(Channels::FrontRight)] = + right_sample; + } + samples = std::move(new_samples); + + } else if (volume != 1.0f) { + for (u32 i = 0; i < samples.size(); i++) { + samples[i] = static_cast(std::clamp( + static_cast(static_cast(samples[i]) * volume), min, max)); + } + } + + samples_buffer.Push(samples); + queue.enqueue(buffer); + queued_buffers++; + } + } + + /** + * Release a buffer. Audio In only, will fill a buffer with recorded samples. + * + * @param num_samples - Maximum number of samples to receive. + * @return Vector of recorded samples. May have fewer than num_samples. + */ + std::vector ReleaseBuffer(const u64 num_samples) override { + static constexpr s32 min = std::numeric_limits::min(); + static constexpr s32 max = std::numeric_limits::max(); + + auto samples{samples_buffer.Pop(num_samples)}; + + // TODO: Up-mix to 6 channels if the game expects it. + // For audio input this is unlikely to ever be the case though. + + // Incoming mic volume seems to always be very quiet, so multiply by an additional 8 here. + // TODO: Play with this and find something that works better. + auto volume{system_volume * device_volume * 8}; + for (u32 i = 0; i < samples.size(); i++) { + samples[i] = static_cast( + std::clamp(static_cast(static_cast(samples[i]) * volume), min, max)); + } + + if (samples.size() < num_samples) { + samples.resize(num_samples, 0); + } + return samples; + } + + /** + * Check if a certain buffer has been consumed (fully played). + * + * @param tag - Unique tag of a buffer to check for. + * @return True if the buffer has been played, otherwise false. + */ + bool IsBufferConsumed(const u64 tag) override { + if (released_buffer.tag == 0) { + if (!released_buffers.try_dequeue(released_buffer)) { + return false; + } + } + + if (released_buffer.tag == tag) { + released_buffer.tag = 0; + return true; + } + return false; + } + + /** + * Empty out the buffer queue. + */ + void ClearQueue() override { + samples_buffer.Pop(); + while (queue.pop()) { + } + while (released_buffers.pop()) { + } + released_buffer = {}; + playing_buffer = {}; + playing_buffer.consumed = true; + queued_buffers = 0; + } + +private: + /** + * Signal events back to the audio system that a buffer was played/can be filled. + * + * @param buffer - Consumed audio buffer to be released. + */ + void SignalEvent(const ::AudioCore::Sink::SinkBuffer& buffer) { + auto& manager{system.AudioCore().GetAudioManager()}; + switch (type) { + case StreamType::Out: + released_buffers.enqueue(buffer); + manager.SetEvent(Event::Type::AudioOutManager, true); + break; + case StreamType::In: + released_buffers.enqueue(buffer); + manager.SetEvent(Event::Type::AudioInManager, true); + break; + case StreamType::Render: + break; + } + } + + /** + * Main callback from SDL. Either expects samples from us (audio render/audio out), or will + * provide samples to be copied (audio in). + * + * @param userdata - Custom data pointer passed along, points to a SDLSinkStream. + * @param stream - Buffer of samples to be filled or read. + * @param len - Length of the stream in bytes. + */ + static void DataCallback(void* userdata, Uint8* stream, int len) { + auto* impl = static_cast(userdata); + + if (!impl) { + return; + } + + const std::size_t num_channels = impl->GetDeviceChannels(); + const std::size_t frame_size = num_channels; + const std::size_t frame_size_bytes = frame_size * sizeof(s16); + const std::size_t num_frames{len / num_channels / sizeof(s16)}; + size_t frames_written{0}; + [[maybe_unused]] bool underrun{false}; + + if (impl->type == StreamType::In) { + std::span input_buffer{reinterpret_cast(stream), num_frames * frame_size}; + + while (frames_written < num_frames) { + auto& playing_buffer{impl->playing_buffer}; + + // If the playing buffer has been consumed or has no frames, we need a new one + if (playing_buffer.consumed || playing_buffer.frames == 0) { + if (!impl->queue.try_dequeue(impl->playing_buffer)) { + // If no buffer was available we've underrun, just push the samples and + // continue. + underrun = true; + impl->samples_buffer.Push(&input_buffer[frames_written * frame_size], + (num_frames - frames_written) * frame_size); + frames_written = num_frames; + continue; + } else { + impl->queued_buffers--; + impl->SignalEvent(impl->playing_buffer); + } + } + + // Get the minimum frames available between the currently playing buffer, and the + // amount we have left to fill + size_t frames_available{ + std::min(playing_buffer.frames - playing_buffer.frames_played, + num_frames - frames_written)}; + + impl->samples_buffer.Push(&input_buffer[frames_written * frame_size], + frames_available * frame_size); + + frames_written += frames_available; + playing_buffer.frames_played += frames_available; + + // If that's all the frames in the current buffer, add its samples and mark it as + // consumed + if (playing_buffer.frames_played >= playing_buffer.frames) { + impl->AddPlayedSampleCount(playing_buffer.frames_played * num_channels); + impl->playing_buffer.consumed = true; + } + } + + std::memcpy(&impl->last_frame[0], &input_buffer[(frames_written - 1) * frame_size], + frame_size_bytes); + } else { + std::span output_buffer{reinterpret_cast(stream), num_frames * frame_size}; + + while (frames_written < num_frames) { + auto& playing_buffer{impl->playing_buffer}; + + // If the playing buffer has been consumed or has no frames, we need a new one + if (playing_buffer.consumed || playing_buffer.frames == 0) { + if (!impl->queue.try_dequeue(impl->playing_buffer)) { + // If no buffer was available we've underrun, fill the remaining buffer with + // the last written frame and continue. + underrun = true; + for (size_t i = frames_written; i < num_frames; i++) { + std::memcpy(&output_buffer[i * frame_size], &impl->last_frame[0], + frame_size_bytes); + } + frames_written = num_frames; + continue; + } else { + impl->queued_buffers--; + impl->SignalEvent(impl->playing_buffer); + } + } + + // Get the minimum frames available between the currently playing buffer, and the + // amount we have left to fill + size_t frames_available{ + std::min(playing_buffer.frames - playing_buffer.frames_played, + num_frames - frames_written)}; + + impl->samples_buffer.Pop(&output_buffer[frames_written * frame_size], + frames_available * frame_size); + + frames_written += frames_available; + playing_buffer.frames_played += frames_available; + + // If that's all the frames in the current buffer, add its samples and mark it as + // consumed + if (playing_buffer.frames_played >= playing_buffer.frames) { + impl->AddPlayedSampleCount(playing_buffer.frames_played * num_channels); + impl->playing_buffer.consumed = true; + } + } + + std::memcpy(&impl->last_frame[0], &output_buffer[(frames_written - 1) * frame_size], + frame_size_bytes); + } + } + + /// SDL device id of the opened input/output device + SDL_AudioDeviceID device{}; + /// Type of this stream + StreamType type; + /// Core system + Core::System& system; + /// Ring buffer of the samples waiting to be played or consumed + Common::RingBuffer samples_buffer; + /// Audio buffers queued and waiting to play + Common::ReaderWriterQueue<::AudioCore::Sink::SinkBuffer> queue; + /// The currently-playing audio buffer + ::AudioCore::Sink::SinkBuffer playing_buffer{}; + /// Audio buffers which have been played and are in queue to be released by the audio system + Common::ReaderWriterQueue<::AudioCore::Sink::SinkBuffer> released_buffers{}; + /// Currently released buffer waiting to be taken by the audio system + ::AudioCore::Sink::SinkBuffer released_buffer{}; + /// The last played (or received) frame of audio, used when the callback underruns + std::array last_frame{}; +}; + +SDLSink::SDLSink(std::string_view target_device_name) { + if (!SDL_WasInit(SDL_INIT_AUDIO)) { + if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) { + LOG_CRITICAL(Audio_Sink, "SDL_InitSubSystem audio failed: {}", SDL_GetError()); + return; + } + } + + if (target_device_name != auto_device_name && !target_device_name.empty()) { + output_device = target_device_name; + } else { + output_device.clear(); + } + + device_channels = 2; +} + +SDLSink::~SDLSink() = default; + +SinkStream* SDLSink::AcquireSinkStream(Core::System& system, const u32 system_channels, + const std::string&, const StreamType type) { + SinkStreamPtr& stream = sink_streams.emplace_back(std::make_unique( + device_channels, system_channels, output_device, input_device, type, system)); + return stream.get(); +} + +void SDLSink::CloseStream(const SinkStream* stream) { + for (size_t i = 0; i < sink_streams.size(); i++) { + if (sink_streams[i].get() == stream) { + sink_streams[i].reset(); + sink_streams.erase(sink_streams.begin() + i); + break; + } + } +} + +void SDLSink::CloseStreams() { + sink_streams.clear(); +} + +void SDLSink::PauseStreams() { + for (auto& stream : sink_streams) { + stream->Stop(); + } +} + +void SDLSink::UnpauseStreams() { + for (auto& stream : sink_streams) { + stream->Start(); + } +} + +f32 SDLSink::GetDeviceVolume() const { + if (sink_streams.empty()) { + return 1.0f; + } + + return sink_streams[0]->GetDeviceVolume(); +} + +void SDLSink::SetDeviceVolume(const f32 volume) { + for (auto& stream : sink_streams) { + stream->SetDeviceVolume(volume); + } +} + +void SDLSink::SetSystemVolume(const f32 volume) { + for (auto& stream : sink_streams) { + stream->SetSystemVolume(volume); + } +} + +std::vector ListSDLSinkDevices(const bool capture) { + std::vector device_list; + + if (!SDL_WasInit(SDL_INIT_AUDIO)) { + if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) { + LOG_CRITICAL(Audio_Sink, "SDL_InitSubSystem audio failed: {}", SDL_GetError()); + return {}; + } + } + + const int device_count = SDL_GetNumAudioDevices(capture); + for (int i = 0; i < device_count; ++i) { + device_list.emplace_back(SDL_GetAudioDeviceName(i, 0)); + } + + return device_list; +} + +} // namespace AudioCore::Sink diff --git a/src/audio_core/sink/sdl2_sink.h b/src/audio_core/sink/sdl2_sink.h new file mode 100644 index 000000000..186bc2fa3 --- /dev/null +++ b/src/audio_core/sink/sdl2_sink.h @@ -0,0 +1,101 @@ +// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#include "audio_core/sink/sink.h" + +namespace Core { +class System; +} + +namespace AudioCore::Sink { +class SinkStream; + +/** + * SDL backend sink, holds multiple output streams and is responsible for sinking samples to + * hardware. Used by Audio Render, Audio In and Audio Out. + */ +class SDLSink final : public Sink { +public: + explicit SDLSink(std::string_view device_id); + ~SDLSink() override; + + /** + * Create a new sink stream. + * + * @param system - Core system. + * @param system_channels - Number of channels the audio system expects. + * May differ from the device's channel count. + * @param name - Name of this stream. + * @param type - Type of this stream, render/in/out. + * @param event - Audio render only, a signal used to prevent the renderer running too + * fast. + * @return A pointer to the created SinkStream + */ + SinkStream* AcquireSinkStream(Core::System& system, u32 system_channels, + const std::string& name, StreamType type) override; + + /** + * Close a given stream. + * + * @param stream - The stream to close. + */ + void CloseStream(const SinkStream* stream) override; + + /** + * Close all streams. + */ + void CloseStreams() override; + + /** + * Pause all streams. + */ + void PauseStreams() override; + + /** + * Unpause all streams. + */ + void UnpauseStreams() override; + + /** + * Get the device volume. Set from calls to the IAudioDevice service. + * + * @return Volume of the device. + */ + f32 GetDeviceVolume() const override; + + /** + * Set the device volume. Set from calls to the IAudioDevice service. + * + * @param volume - New volume of the device. + */ + void SetDeviceVolume(f32 volume) override; + + /** + * Set the system volume. Comes from the audio system using this stream. + * + * @param volume - New volume of the system. + */ + void SetSystemVolume(f32 volume) override; + +private: + /// Name of the output device used by streams + std::string output_device; + /// Name of the input device used by streams + std::string input_device; + /// Vector of streams managed by this sink + std::vector sink_streams; +}; + +/** + * Get a list of conencted devices from Cubeb. + * + * @param capture - Return input (capture) devices if true, otherwise output devices. + */ +std::vector ListSDLSinkDevices(bool capture); + +} // namespace AudioCore::Sink diff --git a/src/audio_core/sink/sink.h b/src/audio_core/sink/sink.h new file mode 100644 index 000000000..91fe455e4 --- /dev/null +++ b/src/audio_core/sink/sink.h @@ -0,0 +1,106 @@ +// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#include "audio_core/sink/sink_stream.h" +#include "common/common_types.h" + +namespace Common { +class Event; +} +namespace Core { +class System; +} + +namespace AudioCore::Sink { + +constexpr char auto_device_name[] = "auto"; + +/** + * This class is an interface for an audio sink, holds multiple output streams and is responsible + * for sinking samples to hardware. Used by Audio Render, Audio In and Audio Out. + */ +class Sink { +public: + virtual ~Sink() = default; + /** + * Close a given stream. + * + * @param stream - The stream to close. + */ + virtual void CloseStream(const SinkStream* stream) = 0; + + /** + * Close all streams. + */ + virtual void CloseStreams() = 0; + + /** + * Pause all streams. + */ + virtual void PauseStreams() = 0; + + /** + * Unpause all streams. + */ + virtual void UnpauseStreams() = 0; + + /** + * Create a new sink stream, kept within this sink, with a pointer returned for use. + * Do not free the returned pointer. When done with the stream, call CloseStream on the sink. + * + * @param system - Core system. + * @param system_channels - Number of channels the audio system expects. + * May differ from the device's channel count. + * @param name - Name of this stream. + * @param type - Type of this stream, render/in/out. + * @param event - Audio render only, a signal used to prevent the renderer running too + * fast. + * @return A pointer to the created SinkStream + */ + virtual SinkStream* AcquireSinkStream(Core::System& system, u32 system_channels, + const std::string& name, StreamType type) = 0; + + /** + * Get the number of channels the hardware device supports. + * Either 2 or 6. + * + * @return Number of device channels. + */ + u32 GetDeviceChannels() const { + return device_channels; + } + + /** + * Get the device volume. Set from calls to the IAudioDevice service. + * + * @return Volume of the device. + */ + virtual f32 GetDeviceVolume() const = 0; + + /** + * Set the device volume. Set from calls to the IAudioDevice service. + * + * @param volume - New volume of the device. + */ + virtual void SetDeviceVolume(f32 volume) = 0; + + /** + * Set the system volume. Comes from the audio system using this stream. + * + * @param volume - New volume of the system. + */ + virtual void SetSystemVolume(f32 volume) = 0; + +protected: + /// Number of device channels supported by the hardware + u32 device_channels{2}; +}; + +using SinkPtr = std::unique_ptr; + +} // namespace AudioCore::Sink diff --git a/src/audio_core/sink_details.cpp b/src/audio_core/sink/sink_details.cpp similarity index 76% rename from src/audio_core/sink_details.cpp rename to src/audio_core/sink/sink_details.cpp index c4cc66111..253c0fd1e 100644 --- a/src/audio_core/sink_details.cpp +++ b/src/audio_core/sink/sink_details.cpp @@ -5,21 +5,21 @@ #include #include #include -#include "audio_core/null_sink.h" -#include "audio_core/sink_details.h" +#include "audio_core/sink/null_sink.h" +#include "audio_core/sink/sink_details.h" #ifdef HAVE_CUBEB -#include "audio_core/cubeb_sink.h" +#include "audio_core/sink/cubeb_sink.h" #endif #ifdef HAVE_SDL2 -#include "audio_core/sdl2_sink.h" +#include "audio_core/sink/sdl2_sink.h" #endif #include "common/logging/log.h" -namespace AudioCore { +namespace AudioCore::Sink { namespace { struct SinkDetails { using FactoryFn = std::unique_ptr (*)(std::string_view); - using ListDevicesFn = std::vector (*)(); + using ListDevicesFn = std::vector (*)(bool); /// Name for this sink. const char* id; @@ -49,17 +49,18 @@ constexpr SinkDetails sink_details[] = { [](std::string_view device_id) -> std::unique_ptr { return std::make_unique(device_id); }, - [] { return std::vector{"null"}; }}, + [](bool capture) { return std::vector{"null"}; }}, }; -const SinkDetails& GetSinkDetails(std::string_view sink_id) { +const SinkDetails& GetOutputSinkDetails(std::string_view sink_id) { auto iter = std::find_if(std::begin(sink_details), std::end(sink_details), [sink_id](const auto& sink_detail) { return sink_detail.id == sink_id; }); if (sink_id == "auto" || iter == std::end(sink_details)) { if (sink_id != "auto") { - LOG_ERROR(Audio, "AudioCore::SelectSink given invalid sink_id {}", sink_id); + LOG_ERROR(Audio, "AudioCore::Sink::GetOutputSinkDetails given invalid sink_id {}", + sink_id); } // Auto-select. // sink_details is ordered in terms of desirability, with the best choice at the front. @@ -79,12 +80,12 @@ std::vector GetSinkIDs() { return sink_ids; } -std::vector GetDeviceListForSink(std::string_view sink_id) { - return GetSinkDetails(sink_id).list_devices(); +std::vector GetDeviceListForSink(std::string_view sink_id, bool capture) { + return GetOutputSinkDetails(sink_id).list_devices(capture); } std::unique_ptr CreateSinkFromID(std::string_view sink_id, std::string_view device_id) { - return GetSinkDetails(sink_id).factory(device_id); + return GetOutputSinkDetails(sink_id).factory(device_id); } -} // namespace AudioCore +} // namespace AudioCore::Sink diff --git a/src/audio_core/sink/sink_details.h b/src/audio_core/sink/sink_details.h new file mode 100644 index 000000000..3ebdb1e30 --- /dev/null +++ b/src/audio_core/sink/sink_details.h @@ -0,0 +1,43 @@ +// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include + +namespace AudioCore { +class AudioManager; + +namespace Sink { + +class Sink; + +/** + * Retrieves the IDs for all available audio sinks. + * + * @return Vector of available sink names. + */ +std::vector GetSinkIDs(); + +/** + * Gets the list of devices for a particular sink identified by the given ID. + * + * @param sink_id - Id of the sink to get devices from. + * @param capture - Get capture (input) devices, or output devices? + * @return Vector of device names. + */ +std::vector GetDeviceListForSink(std::string_view sink_id, bool capture); + +/** + * Creates an audio sink identified by the given device ID. + * + * @param sink_id - Id of the sink to create. + * @param device_id - Name of the device to create. + * @return Pointer to the created sink. + */ +std::unique_ptr CreateSinkFromID(std::string_view sink_id, std::string_view device_id); + +} // namespace Sink +} // namespace AudioCore diff --git a/src/audio_core/sink/sink_stream.h b/src/audio_core/sink/sink_stream.h new file mode 100644 index 000000000..17ed6593f --- /dev/null +++ b/src/audio_core/sink/sink_stream.h @@ -0,0 +1,224 @@ +// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include + +#include "audio_core/common/common.h" +#include "common/common_types.h" + +namespace AudioCore::Sink { + +enum class StreamType { + Render, + Out, + In, +}; + +struct SinkBuffer { + u64 frames; + u64 frames_played; + u64 tag; + bool consumed; +}; + +/** + * Contains a real backend stream for outputting samples to hardware, + * created only via a Sink (See Sink::AcquireSinkStream). + * + * Accepts a SinkBuffer and samples in PCM16 format to be output (see AppendBuffer). + * Appended buffers act as a FIFO queue, and will be held until played. + * You should regularly call IsBufferConsumed with the unique SinkBuffer tag to check if the buffer + * has been consumed. + * + * Since these are a FIFO queue, always check IsBufferConsumed in the same order you appended the + * buffers, skipping a buffer will result in all following buffers to never release. + * + * If the buffers appear to be stuck, you can stop and re-open an IAudioIn/IAudioOut service (this + * is what games do), or call ClearQueue to flush all of the buffers without a full restart. + */ +class SinkStream { +public: + virtual ~SinkStream() = default; + + /** + * Finalize the sink stream. + */ + virtual void Finalize() = 0; + + /** + * Start the sink stream. + * + * @param resume - Set to true if this is resuming the stream a previously-active stream. + * Default false. + */ + virtual void Start(bool resume = false) = 0; + + /** + * Stop the sink stream. + */ + virtual void Stop() = 0; + + /** + * Append a new buffer and its samples to a waiting queue to play. + * + * @param buffer - Audio buffer information to be queued. + * @param samples - The s16 samples to be queue for playback. + */ + virtual void AppendBuffer(SinkBuffer& buffer, std::vector& samples) = 0; + + /** + * Release a buffer. Audio In only, will fill a buffer with recorded samples. + * + * @param num_samples - Maximum number of samples to receive. + * @return Vector of recorded samples. May have fewer than num_samples. + */ + virtual std::vector ReleaseBuffer(u64 num_samples) = 0; + + /** + * Check if a certain buffer has been consumed (fully played). + * + * @param tag - Unique tag of a buffer to check for. + * @return True if the buffer has been played, otherwise false. + */ + virtual bool IsBufferConsumed(u64 tag) = 0; + + /** + * Empty out the buffer queue. + */ + virtual void ClearQueue() = 0; + + /** + * Check if the stream is paused. + * + * @return True if paused, otherwise false. + */ + bool IsPaused() { + return paused; + } + + /** + * Get the number of system channels in this stream. + * + * @return Number of system channels. + */ + u32 GetSystemChannels() const { + return system_channels; + } + + /** + * Set the number of channels the system expects. + * + * @param channels - New number of system channels. + */ + void SetSystemChannels(u32 channels) { + system_channels = channels; + } + + /** + * Get the number of channels the hardware supports. + * + * @return Number of channels supported. + */ + u32 GetDeviceChannels() const { + return device_channels; + } + + /** + * Get the total number of samples played by this stream. + * + * @return Number of samples played. + */ + u64 GetPlayedSampleCount() const { + return played_sample_count; + } + + /** + * Set the number of samples played. + * This is started and stopped on system start/stop. + * + * @param played_sample_count_ - Number of samples to set. + */ + void SetPlayedSampleCount(u64 played_sample_count_) { + played_sample_count = played_sample_count_; + } + + /** + * Add to the played sample count. + * + * @param num_samples - Number of samples to add. + */ + void AddPlayedSampleCount(u64 num_samples) { + played_sample_count += num_samples; + } + + /** + * Get the system volume. + * + * @return The current system volume. + */ + f32 GetSystemVolume() const { + return system_volume; + } + + /** + * Get the device volume. + * + * @return The current device volume. + */ + f32 GetDeviceVolume() const { + return device_volume; + } + + /** + * Set the system volume. + * + * @param volume_ - The new system volume. + */ + void SetSystemVolume(f32 volume_) { + system_volume = volume_; + } + + /** + * Set the device volume. + * + * @param volume_ - The new device volume. + */ + void SetDeviceVolume(f32 volume_) { + device_volume = volume_; + } + + /** + * Get the number of queued audio buffers. + * + * @return The number of queued buffers. + */ + u32 GetQueueSize() { + return queued_buffers.load(); + } + +protected: + /// Number of buffers waiting to be played + std::atomic queued_buffers{}; + /// Total samples played by this stream + std::atomic played_sample_count{}; + /// Set by the audio render/in/out system which uses this stream + f32 system_volume{1.0f}; + /// Set via IAudioDevice service calls + f32 device_volume{1.0f}; + /// Set by the audio render/in/out systen which uses this stream + u32 system_channels{2}; + /// Channels supported by hardware + u32 device_channels{2}; + /// Is this stream currently paused? + std::atomic paused{true}; + /// Was this stream previously playing? + std::atomic was_playing{false}; +}; + +using SinkStreamPtr = std::unique_ptr; + +} // namespace AudioCore::Sink diff --git a/src/audio_core/sink_context.cpp b/src/audio_core/sink_context.cpp deleted file mode 100644 index 835e12f67..000000000 --- a/src/audio_core/sink_context.cpp +++ /dev/null @@ -1,47 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "audio_core/sink_context.h" - -namespace AudioCore { -SinkContext::SinkContext(std::size_t sink_count_) : sink_count{sink_count_} {} -SinkContext::~SinkContext() = default; - -std::size_t SinkContext::GetCount() const { - return sink_count; -} - -void SinkContext::UpdateMainSink(const SinkInfo::InParams& in) { - ASSERT(in.type == SinkTypes::Device); - - if (in.device.down_matrix_enabled) { - downmix_coefficients = in.device.down_matrix_coef; - } else { - downmix_coefficients = { - 1.0f, // front - 0.707f, // center - 0.0f, // lfe - 0.707f, // back - }; - } - - in_use = in.in_use; - use_count = in.device.input_count; - buffers = in.device.input; -} - -bool SinkContext::InUse() const { - return in_use; -} - -std::vector SinkContext::OutputBuffers() const { - std::vector buffer_ret(use_count); - std::memcpy(buffer_ret.data(), buffers.data(), use_count); - return buffer_ret; -} - -const DownmixCoefficients& SinkContext::GetDownmixCoefficients() const { - return downmix_coefficients; -} - -} // namespace AudioCore diff --git a/src/audio_core/sink_context.h b/src/audio_core/sink_context.h deleted file mode 100644 index cc5a90d80..000000000 --- a/src/audio_core/sink_context.h +++ /dev/null @@ -1,95 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include -#include "audio_core/common.h" -#include "common/common_funcs.h" -#include "common/common_types.h" -#include "common/swap.h" - -namespace AudioCore { - -using DownmixCoefficients = std::array; - -enum class SinkTypes : u8 { - Invalid = 0, - Device = 1, - Circular = 2, -}; - -enum class SinkSampleFormat : u32_le { - None = 0, - Pcm8 = 1, - Pcm16 = 2, - Pcm24 = 3, - Pcm32 = 4, - PcmFloat = 5, - Adpcm = 6, -}; - -class SinkInfo { -public: - struct CircularBufferIn { - u64_le address; - u32_le size; - u32_le input_count; - u32_le sample_count; - u32_le previous_position; - SinkSampleFormat sample_format; - std::array input; - bool in_use; - INSERT_PADDING_BYTES_NOINIT(5); - }; - static_assert(sizeof(CircularBufferIn) == 0x28, - "SinkInfo::CircularBufferIn is in invalid size"); - - struct DeviceIn { - std::array device_name; - INSERT_PADDING_BYTES_NOINIT(1); - s32_le input_count; - std::array input; - INSERT_PADDING_BYTES_NOINIT(1); - bool down_matrix_enabled; - DownmixCoefficients down_matrix_coef; - }; - static_assert(sizeof(DeviceIn) == 0x11c, "SinkInfo::DeviceIn is an invalid size"); - - struct InParams { - SinkTypes type{}; - bool in_use{}; - INSERT_PADDING_BYTES(2); - u32_le node_id{}; - INSERT_PADDING_WORDS(6); - union { - // std::array raw{}; - DeviceIn device; - CircularBufferIn circular_buffer; - }; - }; - static_assert(sizeof(InParams) == 0x140, "SinkInfo::InParams are an invalid size!"); -}; - -class SinkContext { -public: - explicit SinkContext(std::size_t sink_count_); - ~SinkContext(); - - [[nodiscard]] std::size_t GetCount() const; - - void UpdateMainSink(const SinkInfo::InParams& in); - [[nodiscard]] bool InUse() const; - [[nodiscard]] std::vector OutputBuffers() const; - - [[nodiscard]] const DownmixCoefficients& GetDownmixCoefficients() const; - -private: - bool in_use{false}; - s32 use_count{}; - std::array buffers{}; - std::size_t sink_count{}; - DownmixCoefficients downmix_coefficients{}; -}; -} // namespace AudioCore diff --git a/src/audio_core/sink_details.h b/src/audio_core/sink_details.h deleted file mode 100644 index 042766358..000000000 --- a/src/audio_core/sink_details.h +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include -#include - -namespace AudioCore { - -class Sink; - -/// Retrieves the IDs for all available audio sinks. -std::vector GetSinkIDs(); - -/// Gets the list of devices for a particular sink identified by the given ID. -std::vector GetDeviceListForSink(std::string_view sink_id); - -/// Creates an audio sink identified by the given device ID. -std::unique_ptr CreateSinkFromID(std::string_view sink_id, std::string_view device_id); - -} // namespace AudioCore diff --git a/src/audio_core/sink_stream.h b/src/audio_core/sink_stream.h deleted file mode 100644 index 0449b90af..000000000 --- a/src/audio_core/sink_stream.h +++ /dev/null @@ -1,35 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include - -#include "common/common_types.h" - -namespace AudioCore { - -/** - * Accepts samples in stereo signed PCM16 format to be output. Sinks *do not* handle resampling and - * expect the correct sample rate. They are dumb outputs. - */ -class SinkStream { -public: - virtual ~SinkStream() = default; - - /** - * Feed stereo samples to sink. - * @param num_channels Number of channels used. - * @param samples Samples in interleaved stereo PCM16 format. - */ - virtual void EnqueueSamples(u32 num_channels, const std::vector& samples) = 0; - - virtual std::size_t SamplesInQueue(u32 num_channels) const = 0; - - virtual void Flush() = 0; -}; - -using SinkStreamPtr = std::unique_ptr; - -} // namespace AudioCore diff --git a/src/audio_core/splitter_context.cpp b/src/audio_core/splitter_context.cpp deleted file mode 100644 index 10646dc05..000000000 --- a/src/audio_core/splitter_context.cpp +++ /dev/null @@ -1,616 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "audio_core/behavior_info.h" -#include "audio_core/splitter_context.h" -#include "common/alignment.h" -#include "common/assert.h" -#include "common/logging/log.h" - -namespace AudioCore { - -ServerSplitterDestinationData::ServerSplitterDestinationData(s32 id_) : id{id_} {} -ServerSplitterDestinationData::~ServerSplitterDestinationData() = default; - -void ServerSplitterDestinationData::Update(SplitterInfo::InDestinationParams& header) { - // Log error as these are not actually failure states - if (header.magic != SplitterMagic::DataHeader) { - LOG_ERROR(Audio, "Splitter destination header is invalid!"); - return; - } - - // Incorrect splitter id - if (header.splitter_id != id) { - LOG_ERROR(Audio, "Splitter destination ids do not match!"); - return; - } - - mix_id = header.mix_id; - // Copy our mix volumes - std::copy(header.mix_volumes.begin(), header.mix_volumes.end(), current_mix_volumes.begin()); - if (!in_use && header.in_use) { - // Update mix volumes - std::copy(current_mix_volumes.begin(), current_mix_volumes.end(), last_mix_volumes.begin()); - needs_update = false; - } - in_use = header.in_use; -} - -ServerSplitterDestinationData* ServerSplitterDestinationData::GetNextDestination() { - return next; -} - -const ServerSplitterDestinationData* ServerSplitterDestinationData::GetNextDestination() const { - return next; -} - -void ServerSplitterDestinationData::SetNextDestination(ServerSplitterDestinationData* dest) { - next = dest; -} - -bool ServerSplitterDestinationData::ValidMixId() const { - return GetMixId() != AudioCommon::NO_MIX; -} - -s32 ServerSplitterDestinationData::GetMixId() const { - return mix_id; -} - -bool ServerSplitterDestinationData::IsConfigured() const { - return in_use && ValidMixId(); -} - -float ServerSplitterDestinationData::GetMixVolume(std::size_t i) const { - ASSERT(i < AudioCommon::MAX_MIX_BUFFERS); - return current_mix_volumes.at(i); -} - -const std::array& -ServerSplitterDestinationData::CurrentMixVolumes() const { - return current_mix_volumes; -} - -const std::array& -ServerSplitterDestinationData::LastMixVolumes() const { - return last_mix_volumes; -} - -void ServerSplitterDestinationData::MarkDirty() { - needs_update = true; -} - -void ServerSplitterDestinationData::UpdateInternalState() { - if (in_use && needs_update) { - std::copy(current_mix_volumes.begin(), current_mix_volumes.end(), last_mix_volumes.begin()); - } - needs_update = false; -} - -ServerSplitterInfo::ServerSplitterInfo(s32 id_) : id(id_) {} -ServerSplitterInfo::~ServerSplitterInfo() = default; - -void ServerSplitterInfo::InitializeInfos() { - send_length = 0; - head = nullptr; - new_connection = true; -} - -void ServerSplitterInfo::ClearNewConnectionFlag() { - new_connection = false; -} - -std::size_t ServerSplitterInfo::Update(SplitterInfo::InInfoPrams& header) { - if (header.send_id != id) { - return 0; - } - - sample_rate = header.sample_rate; - new_connection = true; - // We need to update the size here due to the splitter bug being present and providing an - // incorrect size. We're suppose to also update the header here but we just ignore and continue - return (sizeof(s32_le) * (header.length - 1)) + (sizeof(s32_le) * 3); -} - -ServerSplitterDestinationData* ServerSplitterInfo::GetHead() { - return head; -} - -const ServerSplitterDestinationData* ServerSplitterInfo::GetHead() const { - return head; -} - -ServerSplitterDestinationData* ServerSplitterInfo::GetData(std::size_t depth) { - auto* current_head = head; - for (std::size_t i = 0; i < depth; i++) { - if (current_head == nullptr) { - return nullptr; - } - current_head = current_head->GetNextDestination(); - } - return current_head; -} - -const ServerSplitterDestinationData* ServerSplitterInfo::GetData(std::size_t depth) const { - auto* current_head = head; - for (std::size_t i = 0; i < depth; i++) { - if (current_head == nullptr) { - return nullptr; - } - current_head = current_head->GetNextDestination(); - } - return current_head; -} - -bool ServerSplitterInfo::HasNewConnection() const { - return new_connection; -} - -s32 ServerSplitterInfo::GetLength() const { - return send_length; -} - -void ServerSplitterInfo::SetHead(ServerSplitterDestinationData* new_head) { - head = new_head; -} - -void ServerSplitterInfo::SetHeadDepth(s32 length) { - send_length = length; -} - -SplitterContext::SplitterContext() = default; -SplitterContext::~SplitterContext() = default; - -void SplitterContext::Initialize(BehaviorInfo& behavior_info, std::size_t _info_count, - std::size_t _data_count) { - if (!behavior_info.IsSplitterSupported() || _data_count == 0 || _info_count == 0) { - Setup(0, 0, false); - return; - } - // Only initialize if we're using splitters - Setup(_info_count, _data_count, behavior_info.IsSplitterBugFixed()); -} - -bool SplitterContext::Update(const std::vector& input, std::size_t& input_offset, - std::size_t& bytes_read) { - const auto UpdateOffsets = [&](std::size_t read) { - input_offset += read; - bytes_read += read; - }; - - if (info_count == 0 || data_count == 0) { - bytes_read = 0; - return true; - } - - if (!AudioCommon::CanConsumeBuffer(input.size(), input_offset, - sizeof(SplitterInfo::InHeader))) { - LOG_ERROR(Audio, "Buffer is an invalid size!"); - return false; - } - SplitterInfo::InHeader header{}; - std::memcpy(&header, input.data() + input_offset, sizeof(SplitterInfo::InHeader)); - UpdateOffsets(sizeof(SplitterInfo::InHeader)); - - if (header.magic != SplitterMagic::SplitterHeader) { - LOG_ERROR(Audio, "Invalid header magic! Expecting {:X} but got {:X}", - SplitterMagic::SplitterHeader, header.magic); - return false; - } - - // Clear all connections - for (auto& info : infos) { - info.ClearNewConnectionFlag(); - } - - UpdateInfo(input, input_offset, bytes_read, header.info_count); - UpdateData(input, input_offset, bytes_read, header.data_count); - const auto aligned_bytes_read = Common::AlignUp(bytes_read, 16); - input_offset += aligned_bytes_read - bytes_read; - bytes_read = aligned_bytes_read; - return true; -} - -bool SplitterContext::UsingSplitter() const { - return info_count > 0 && data_count > 0; -} - -ServerSplitterInfo& SplitterContext::GetInfo(std::size_t i) { - ASSERT(i < info_count); - return infos.at(i); -} - -const ServerSplitterInfo& SplitterContext::GetInfo(std::size_t i) const { - ASSERT(i < info_count); - return infos.at(i); -} - -ServerSplitterDestinationData& SplitterContext::GetData(std::size_t i) { - ASSERT(i < data_count); - return datas.at(i); -} - -const ServerSplitterDestinationData& SplitterContext::GetData(std::size_t i) const { - ASSERT(i < data_count); - return datas.at(i); -} - -ServerSplitterDestinationData* SplitterContext::GetDestinationData(std::size_t info, - std::size_t data) { - ASSERT(info < info_count); - auto& cur_info = GetInfo(info); - return cur_info.GetData(data); -} - -const ServerSplitterDestinationData* SplitterContext::GetDestinationData(std::size_t info, - std::size_t data) const { - ASSERT(info < info_count); - const auto& cur_info = GetInfo(info); - return cur_info.GetData(data); -} - -void SplitterContext::UpdateInternalState() { - if (data_count == 0) { - return; - } - - for (auto& data : datas) { - data.UpdateInternalState(); - } -} - -std::size_t SplitterContext::GetInfoCount() const { - return info_count; -} - -std::size_t SplitterContext::GetDataCount() const { - return data_count; -} - -void SplitterContext::Setup(std::size_t info_count_, std::size_t data_count_, - bool is_splitter_bug_fixed) { - - info_count = info_count_; - data_count = data_count_; - - for (std::size_t i = 0; i < info_count; i++) { - auto& splitter = infos.emplace_back(static_cast(i)); - splitter.InitializeInfos(); - } - for (std::size_t i = 0; i < data_count; i++) { - datas.emplace_back(static_cast(i)); - } - - bug_fixed = is_splitter_bug_fixed; -} - -bool SplitterContext::UpdateInfo(const std::vector& input, std::size_t& input_offset, - std::size_t& bytes_read, s32 in_splitter_count) { - const auto UpdateOffsets = [&](std::size_t read) { - input_offset += read; - bytes_read += read; - }; - - for (s32 i = 0; i < in_splitter_count; i++) { - if (!AudioCommon::CanConsumeBuffer(input.size(), input_offset, - sizeof(SplitterInfo::InInfoPrams))) { - LOG_ERROR(Audio, "Buffer is an invalid size!"); - return false; - } - SplitterInfo::InInfoPrams header{}; - std::memcpy(&header, input.data() + input_offset, sizeof(SplitterInfo::InInfoPrams)); - - // Logged as warning as these don't actually cause a bailout for some reason - if (header.magic != SplitterMagic::InfoHeader) { - LOG_ERROR(Audio, "Bad splitter data header"); - break; - } - - if (header.send_id < 0 || static_cast(header.send_id) > info_count) { - LOG_ERROR(Audio, "Bad splitter data id"); - break; - } - - UpdateOffsets(sizeof(SplitterInfo::InInfoPrams)); - auto& info = GetInfo(header.send_id); - if (!RecomposeDestination(info, header, input, input_offset)) { - LOG_ERROR(Audio, "Failed to recompose destination for splitter!"); - return false; - } - const std::size_t read = info.Update(header); - bytes_read += read; - input_offset += read; - } - return true; -} - -bool SplitterContext::UpdateData(const std::vector& input, std::size_t& input_offset, - std::size_t& bytes_read, s32 in_data_count) { - const auto UpdateOffsets = [&](std::size_t read) { - input_offset += read; - bytes_read += read; - }; - - for (s32 i = 0; i < in_data_count; i++) { - if (!AudioCommon::CanConsumeBuffer(input.size(), input_offset, - sizeof(SplitterInfo::InDestinationParams))) { - LOG_ERROR(Audio, "Buffer is an invalid size!"); - return false; - } - SplitterInfo::InDestinationParams header{}; - std::memcpy(&header, input.data() + input_offset, - sizeof(SplitterInfo::InDestinationParams)); - UpdateOffsets(sizeof(SplitterInfo::InDestinationParams)); - - // Logged as warning as these don't actually cause a bailout for some reason - if (header.magic != SplitterMagic::DataHeader) { - LOG_ERROR(Audio, "Bad splitter data header"); - break; - } - - if (header.splitter_id < 0 || static_cast(header.splitter_id) > data_count) { - LOG_ERROR(Audio, "Bad splitter data id"); - break; - } - GetData(header.splitter_id).Update(header); - } - return true; -} - -bool SplitterContext::RecomposeDestination(ServerSplitterInfo& info, - SplitterInfo::InInfoPrams& header, - const std::vector& input, - const std::size_t& input_offset) { - // Clear our current destinations - auto* current_head = info.GetHead(); - while (current_head != nullptr) { - auto* next_head = current_head->GetNextDestination(); - current_head->SetNextDestination(nullptr); - current_head = next_head; - } - info.SetHead(nullptr); - - s32 size = header.length; - // If the splitter bug is present, calculate fixed size - if (!bug_fixed) { - if (info_count > 0) { - const auto factor = data_count / info_count; - size = std::min(header.length, static_cast(factor)); - } else { - size = 0; - } - } - - if (size < 1) { - LOG_ERROR(Audio, "Invalid splitter info size! size={:X}", size); - return true; - } - - auto* start_head = &GetData(header.resource_id_base); - current_head = start_head; - std::vector resource_ids(size - 1); - if (!AudioCommon::CanConsumeBuffer(input.size(), input_offset, - resource_ids.size() * sizeof(s32_le))) { - LOG_ERROR(Audio, "Buffer is an invalid size!"); - return false; - } - std::memcpy(resource_ids.data(), input.data() + input_offset, - resource_ids.size() * sizeof(s32_le)); - - for (auto resource_id : resource_ids) { - auto* head = &GetData(resource_id); - current_head->SetNextDestination(head); - current_head = head; - } - - info.SetHead(start_head); - info.SetHeadDepth(size); - - return true; -} - -NodeStates::NodeStates() = default; -NodeStates::~NodeStates() = default; - -void NodeStates::Initialize(std::size_t node_count_) { - // Setup our work parameters - node_count = node_count_; - was_node_found.resize(node_count); - was_node_completed.resize(node_count); - index_list.resize(node_count); - index_stack.Reset(node_count * node_count); -} - -bool NodeStates::Tsort(EdgeMatrix& edge_matrix) { - return DepthFirstSearch(edge_matrix); -} - -std::size_t NodeStates::GetIndexPos() const { - return index_pos; -} - -const std::vector& NodeStates::GetIndexList() const { - return index_list; -} - -void NodeStates::PushTsortResult(s32 index) { - ASSERT(index < static_cast(node_count)); - index_list[index_pos++] = index; -} - -bool NodeStates::DepthFirstSearch(EdgeMatrix& edge_matrix) { - ResetState(); - for (std::size_t i = 0; i < node_count; i++) { - const auto node_id = static_cast(i); - - // If we don't have a state, send to our index stack for work - if (GetState(i) == NodeStates::State::NoState) { - index_stack.push(node_id); - } - - // While we have work to do in our stack - while (index_stack.Count() > 0) { - // Get the current node - const auto current_stack_index = index_stack.top(); - // Check if we've seen the node yet - const auto index_state = GetState(current_stack_index); - if (index_state == NodeStates::State::NoState) { - // Mark the node as seen - UpdateState(NodeStates::State::InFound, current_stack_index); - } else if (index_state == NodeStates::State::InFound) { - // We've seen this node before, mark it as completed - UpdateState(NodeStates::State::InCompleted, current_stack_index); - // Update our index list - PushTsortResult(current_stack_index); - // Pop the stack - index_stack.pop(); - continue; - } else if (index_state == NodeStates::State::InCompleted) { - // If our node is already sorted, clear it - index_stack.pop(); - continue; - } - - const auto edge_node_count = edge_matrix.GetNodeCount(); - for (s32 j = 0; j < static_cast(edge_node_count); j++) { - // Check if our node is connected to our edge matrix - if (!edge_matrix.Connected(current_stack_index, j)) { - continue; - } - - // Check if our node exists - const auto node_state = GetState(j); - if (node_state == NodeStates::State::NoState) { - // Add more work - index_stack.push(j); - } else if (node_state == NodeStates::State::InFound) { - ASSERT_MSG(false, "Node start marked as found"); - ResetState(); - return false; - } - } - } - } - return true; -} - -void NodeStates::ResetState() { - // Reset to the start of our index stack - index_pos = 0; - for (std::size_t i = 0; i < node_count; i++) { - // Mark all nodes as not found - was_node_found[i] = false; - // Mark all nodes as uncompleted - was_node_completed[i] = false; - // Mark all indexes as invalid - index_list[i] = -1; - } -} - -void NodeStates::UpdateState(NodeStates::State state, std::size_t i) { - switch (state) { - case NodeStates::State::NoState: - was_node_found[i] = false; - was_node_completed[i] = false; - break; - case NodeStates::State::InFound: - was_node_found[i] = true; - was_node_completed[i] = false; - break; - case NodeStates::State::InCompleted: - was_node_found[i] = false; - was_node_completed[i] = true; - break; - } -} - -NodeStates::State NodeStates::GetState(std::size_t i) { - ASSERT(i < node_count); - if (was_node_found[i]) { - // If our node exists in our found list - return NodeStates::State::InFound; - } else if (was_node_completed[i]) { - // If node is in the completed list - return NodeStates::State::InCompleted; - } else { - // If in neither - return NodeStates::State::NoState; - } -} - -NodeStates::Stack::Stack() = default; -NodeStates::Stack::~Stack() = default; - -void NodeStates::Stack::Reset(std::size_t size) { - // Mark our stack as empty - stack.resize(size); - stack_size = size; - stack_pos = 0; - std::fill(stack.begin(), stack.end(), 0); -} - -void NodeStates::Stack::push(s32 val) { - ASSERT(stack_pos < stack_size); - stack[stack_pos++] = val; -} - -std::size_t NodeStates::Stack::Count() const { - return stack_pos; -} - -s32 NodeStates::Stack::top() const { - ASSERT(stack_pos > 0); - return stack[stack_pos - 1]; -} - -s32 NodeStates::Stack::pop() { - ASSERT(stack_pos > 0); - stack_pos--; - return stack[stack_pos]; -} - -EdgeMatrix::EdgeMatrix() = default; -EdgeMatrix::~EdgeMatrix() = default; - -void EdgeMatrix::Initialize(std::size_t _node_count) { - node_count = _node_count; - edge_matrix.resize(node_count * node_count); -} - -bool EdgeMatrix::Connected(s32 a, s32 b) { - return GetState(a, b); -} - -void EdgeMatrix::Connect(s32 a, s32 b) { - SetState(a, b, true); -} - -void EdgeMatrix::Disconnect(s32 a, s32 b) { - SetState(a, b, false); -} - -void EdgeMatrix::RemoveEdges(s32 edge) { - for (std::size_t i = 0; i < node_count; i++) { - SetState(edge, static_cast(i), false); - } -} - -std::size_t EdgeMatrix::GetNodeCount() const { - return node_count; -} - -void EdgeMatrix::SetState(s32 a, s32 b, bool state) { - ASSERT(InRange(a, b)); - edge_matrix.at(a * node_count + b) = state; -} - -bool EdgeMatrix::GetState(s32 a, s32 b) { - ASSERT(InRange(a, b)); - return edge_matrix.at(a * node_count + b); -} - -bool EdgeMatrix::InRange(s32 a, s32 b) const { - const std::size_t pos = a * node_count + b; - return pos < (node_count * node_count); -} - -} // namespace AudioCore diff --git a/src/audio_core/splitter_context.h b/src/audio_core/splitter_context.h deleted file mode 100644 index 3a4b055eb..000000000 --- a/src/audio_core/splitter_context.h +++ /dev/null @@ -1,218 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include -#include "audio_core/common.h" -#include "common/common_funcs.h" -#include "common/common_types.h" -#include "common/swap.h" - -namespace AudioCore { -class BehaviorInfo; - -class EdgeMatrix { -public: - EdgeMatrix(); - ~EdgeMatrix(); - - void Initialize(std::size_t _node_count); - bool Connected(s32 a, s32 b); - void Connect(s32 a, s32 b); - void Disconnect(s32 a, s32 b); - void RemoveEdges(s32 edge); - std::size_t GetNodeCount() const; - -private: - void SetState(s32 a, s32 b, bool state); - bool GetState(s32 a, s32 b); - - bool InRange(s32 a, s32 b) const; - std::vector edge_matrix{}; - std::size_t node_count{}; -}; - -class NodeStates { -public: - enum class State { - NoState = 0, - InFound = 1, - InCompleted = 2, - }; - - // Looks to be a fixed size stack. Placed within the NodeStates class based on symbols - class Stack { - public: - Stack(); - ~Stack(); - - void Reset(std::size_t size); - void push(s32 val); - std::size_t Count() const; - s32 top() const; - s32 pop(); - - private: - std::vector stack{}; - std::size_t stack_size{}; - std::size_t stack_pos{}; - }; - NodeStates(); - ~NodeStates(); - - void Initialize(std::size_t node_count_); - bool Tsort(EdgeMatrix& edge_matrix); - std::size_t GetIndexPos() const; - const std::vector& GetIndexList() const; - -private: - void PushTsortResult(s32 index); - bool DepthFirstSearch(EdgeMatrix& edge_matrix); - void ResetState(); - void UpdateState(State state, std::size_t i); - State GetState(std::size_t i); - - std::size_t node_count{}; - std::vector was_node_found{}; - std::vector was_node_completed{}; - std::size_t index_pos{}; - std::vector index_list{}; - Stack index_stack{}; -}; - -enum class SplitterMagic : u32_le { - SplitterHeader = Common::MakeMagic('S', 'N', 'D', 'H'), - DataHeader = Common::MakeMagic('S', 'N', 'D', 'D'), - InfoHeader = Common::MakeMagic('S', 'N', 'D', 'I'), -}; - -class SplitterInfo { -public: - struct InHeader { - SplitterMagic magic{}; - s32_le info_count{}; - s32_le data_count{}; - INSERT_PADDING_WORDS(5); - }; - static_assert(sizeof(InHeader) == 0x20, "SplitterInfo::InHeader is an invalid size"); - - struct InInfoPrams { - SplitterMagic magic{}; - s32_le send_id{}; - s32_le sample_rate{}; - s32_le length{}; - s32_le resource_id_base{}; - }; - static_assert(sizeof(InInfoPrams) == 0x14, "SplitterInfo::InInfoPrams is an invalid size"); - - struct InDestinationParams { - SplitterMagic magic{}; - s32_le splitter_id{}; - std::array mix_volumes{}; - s32_le mix_id{}; - bool in_use{}; - INSERT_PADDING_BYTES(3); - }; - static_assert(sizeof(InDestinationParams) == 0x70, - "SplitterInfo::InDestinationParams is an invalid size"); -}; - -class ServerSplitterDestinationData { -public: - explicit ServerSplitterDestinationData(s32 id_); - ~ServerSplitterDestinationData(); - - void Update(SplitterInfo::InDestinationParams& header); - - ServerSplitterDestinationData* GetNextDestination(); - const ServerSplitterDestinationData* GetNextDestination() const; - void SetNextDestination(ServerSplitterDestinationData* dest); - bool ValidMixId() const; - s32 GetMixId() const; - bool IsConfigured() const; - float GetMixVolume(std::size_t i) const; - const std::array& CurrentMixVolumes() const; - const std::array& LastMixVolumes() const; - void MarkDirty(); - void UpdateInternalState(); - -private: - bool needs_update{}; - bool in_use{}; - s32 id{}; - s32 mix_id{}; - std::array current_mix_volumes{}; - std::array last_mix_volumes{}; - ServerSplitterDestinationData* next = nullptr; -}; - -class ServerSplitterInfo { -public: - explicit ServerSplitterInfo(s32 id_); - ~ServerSplitterInfo(); - - void InitializeInfos(); - void ClearNewConnectionFlag(); - std::size_t Update(SplitterInfo::InInfoPrams& header); - - ServerSplitterDestinationData* GetHead(); - const ServerSplitterDestinationData* GetHead() const; - ServerSplitterDestinationData* GetData(std::size_t depth); - const ServerSplitterDestinationData* GetData(std::size_t depth) const; - - bool HasNewConnection() const; - s32 GetLength() const; - - void SetHead(ServerSplitterDestinationData* new_head); - void SetHeadDepth(s32 length); - -private: - s32 sample_rate{}; - s32 id{}; - s32 send_length{}; - ServerSplitterDestinationData* head = nullptr; - bool new_connection{}; -}; - -class SplitterContext { -public: - SplitterContext(); - ~SplitterContext(); - - void Initialize(BehaviorInfo& behavior_info, std::size_t splitter_count, - std::size_t data_count); - - bool Update(const std::vector& input, std::size_t& input_offset, std::size_t& bytes_read); - bool UsingSplitter() const; - - ServerSplitterInfo& GetInfo(std::size_t i); - const ServerSplitterInfo& GetInfo(std::size_t i) const; - ServerSplitterDestinationData& GetData(std::size_t i); - const ServerSplitterDestinationData& GetData(std::size_t i) const; - ServerSplitterDestinationData* GetDestinationData(std::size_t info, std::size_t data); - const ServerSplitterDestinationData* GetDestinationData(std::size_t info, - std::size_t data) const; - void UpdateInternalState(); - - std::size_t GetInfoCount() const; - std::size_t GetDataCount() const; - -private: - void Setup(std::size_t info_count, std::size_t data_count, bool is_splitter_bug_fixed); - bool UpdateInfo(const std::vector& input, std::size_t& input_offset, - std::size_t& bytes_read, s32 in_splitter_count); - bool UpdateData(const std::vector& input, std::size_t& input_offset, - std::size_t& bytes_read, s32 in_data_count); - bool RecomposeDestination(ServerSplitterInfo& info, SplitterInfo::InInfoPrams& header, - const std::vector& input, const std::size_t& input_offset); - - std::vector infos{}; - std::vector datas{}; - - std::size_t info_count{}; - std::size_t data_count{}; - bool bug_fixed{}; -}; -} // namespace AudioCore diff --git a/src/audio_core/stream.cpp b/src/audio_core/stream.cpp deleted file mode 100644 index cf3d94c53..000000000 --- a/src/audio_core/stream.cpp +++ /dev/null @@ -1,175 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include -#include - -#include "audio_core/sink.h" -#include "audio_core/sink_details.h" -#include "audio_core/sink_stream.h" -#include "audio_core/stream.h" -#include "common/assert.h" -#include "common/logging/log.h" -#include "common/settings.h" -#include "core/core_timing.h" - -namespace AudioCore { - -constexpr std::size_t MaxAudioBufferCount{32}; - -u32 Stream::GetNumChannels() const { - switch (format) { - case Format::Mono16: - return 1; - case Format::Stereo16: - return 2; - case Format::Multi51Channel16: - return 6; - } - UNIMPLEMENTED_MSG("Unimplemented format={}", static_cast(format)); - return {}; -} - -Stream::Stream(Core::Timing::CoreTiming& core_timing_, u32 sample_rate_, Format format_, - ReleaseCallback&& release_callback_, SinkStream& sink_stream_, std::string&& name_) - : sample_rate{sample_rate_}, format{format_}, release_callback{std::move(release_callback_)}, - sink_stream{sink_stream_}, core_timing{core_timing_}, name{std::move(name_)} { - release_event = Core::Timing::CreateEvent( - name, [this](std::uintptr_t, s64 time, std::chrono::nanoseconds ns_late) { - ReleaseActiveBuffer(ns_late); - return std::nullopt; - }); -} - -void Stream::Play() { - state = State::Playing; - PlayNextBuffer(); -} - -void Stream::Stop() { - state = State::Stopped; - UNIMPLEMENTED(); -} - -bool Stream::Flush() { - const bool had_buffers = !queued_buffers.empty(); - while (!queued_buffers.empty()) { - queued_buffers.pop(); - } - return had_buffers; -} - -void Stream::SetVolume(float volume) { - game_volume = volume; -} - -Stream::State Stream::GetState() const { - return state; -} - -std::chrono::nanoseconds Stream::GetBufferReleaseNS(const Buffer& buffer) const { - const std::size_t num_samples{buffer.GetSamples().size() / GetNumChannels()}; - return std::chrono::nanoseconds((static_cast(num_samples) * 1000000000ULL) / sample_rate); -} - -static void VolumeAdjustSamples(std::vector& samples, float game_volume) { - const float volume{std::clamp(Settings::Volume() - (1.0f - game_volume), 0.0f, 1.0f)}; - - if (volume == 1.0f) { - return; - } - - // Perceived volume is not the same as the volume level - const float volume_scale_factor = (0.85f * ((volume * volume) - volume)) + volume; - for (auto& sample : samples) { - sample = static_cast(sample * volume_scale_factor); - } -} - -void Stream::PlayNextBuffer(std::chrono::nanoseconds ns_late) { - if (!IsPlaying()) { - // Ensure we are in playing state before playing the next buffer - sink_stream.Flush(); - return; - } - - if (active_buffer) { - // Do not queue a new buffer if we are already playing a buffer - return; - } - - if (queued_buffers.empty()) { - // No queued buffers - we are effectively paused - sink_stream.Flush(); - return; - } - - active_buffer = queued_buffers.front(); - queued_buffers.pop(); - - auto& samples = active_buffer->GetSamples(); - - VolumeAdjustSamples(samples, game_volume); - - sink_stream.EnqueueSamples(GetNumChannels(), samples); - played_samples += samples.size(); - - const auto buffer_release_ns = GetBufferReleaseNS(*active_buffer); - - // If ns_late is higher than the update rate ignore the delay - if (ns_late > buffer_release_ns) { - ns_late = {}; - } - - core_timing.ScheduleEvent(buffer_release_ns - ns_late, release_event, {}); -} - -void Stream::ReleaseActiveBuffer(std::chrono::nanoseconds ns_late) { - ASSERT(active_buffer); - released_buffers.push(std::move(active_buffer)); - release_callback(); - PlayNextBuffer(ns_late); -} - -bool Stream::QueueBuffer(BufferPtr&& buffer) { - if (queued_buffers.size() < MaxAudioBufferCount) { - queued_buffers.push(std::move(buffer)); - PlayNextBuffer(); - return true; - } - return false; -} - -bool Stream::ContainsBuffer([[maybe_unused]] Buffer::Tag tag) const { - UNIMPLEMENTED(); - return {}; -} - -std::vector Stream::GetTagsAndReleaseBuffers(std::size_t max_count) { - std::vector tags; - for (std::size_t count = 0; count < max_count && !released_buffers.empty(); ++count) { - if (released_buffers.front()) { - tags.push_back(released_buffers.front()->GetTag()); - } else { - ASSERT_MSG(false, "Invalid tag in released_buffers!"); - } - released_buffers.pop(); - } - return tags; -} - -std::vector Stream::GetTagsAndReleaseBuffers() { - std::vector tags; - tags.reserve(released_buffers.size()); - while (!released_buffers.empty()) { - if (released_buffers.front()) { - tags.push_back(released_buffers.front()->GetTag()); - } else { - ASSERT_MSG(false, "Invalid tag in released_buffers!"); - } - released_buffers.pop(); - } - return tags; -} - -} // namespace AudioCore diff --git a/src/audio_core/stream.h b/src/audio_core/stream.h deleted file mode 100644 index f5de70396..000000000 --- a/src/audio_core/stream.h +++ /dev/null @@ -1,130 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include -#include -#include -#include -#include - -#include "audio_core/buffer.h" -#include "common/common_types.h" - -namespace Core::Timing { -class CoreTiming; -struct EventType; -} // namespace Core::Timing - -namespace AudioCore { - -class SinkStream; - -/** - * Represents an audio stream, which is a sequence of queued buffers, to be outputed by AudioOut - */ -class Stream { -public: - /// Audio format of the stream - enum class Format { - Mono16, - Stereo16, - Multi51Channel16, - }; - - /// Current state of the stream - enum class State { - Stopped, - Playing, - }; - - /// Callback function type, used to change guest state on a buffer being released - using ReleaseCallback = std::function; - - Stream(Core::Timing::CoreTiming& core_timing_, u32 sample_rate_, Format format_, - ReleaseCallback&& release_callback_, SinkStream& sink_stream_, std::string&& name_); - - /// Plays the audio stream - void Play(); - - /// Stops the audio stream - void Stop(); - - /// Queues a buffer into the audio stream, returns true on success - bool QueueBuffer(BufferPtr&& buffer); - - /// Flush audio buffers - bool Flush(); - - /// Returns true if the audio stream contains a buffer with the specified tag - [[nodiscard]] bool ContainsBuffer(Buffer::Tag tag) const; - - /// Returns a vector of recently released buffers specified by tag - [[nodiscard]] std::vector GetTagsAndReleaseBuffers(std::size_t max_count); - - /// Returns a vector of all recently released buffers specified by tag - [[nodiscard]] std::vector GetTagsAndReleaseBuffers(); - - void SetVolume(float volume); - - [[nodiscard]] float GetVolume() const { - return game_volume; - } - - /// Returns true if the stream is currently playing - [[nodiscard]] bool IsPlaying() const { - return state == State::Playing; - } - - /// Returns the number of queued buffers - [[nodiscard]] std::size_t GetQueueSize() const { - return queued_buffers.size(); - } - - /// Gets the sample rate - [[nodiscard]] u32 GetSampleRate() const { - return sample_rate; - } - - /// Gets the number of samples played so far - [[nodiscard]] u64 GetPlayedSampleCount() const { - return played_samples; - } - - /// Gets the number of channels - [[nodiscard]] u32 GetNumChannels() const; - - /// Get the state - [[nodiscard]] State GetState() const; - -private: - /// Plays the next queued buffer in the audio stream, starting playback if necessary - void PlayNextBuffer(std::chrono::nanoseconds ns_late = {}); - - /// Releases the actively playing buffer, signalling that it has been completed - void ReleaseActiveBuffer(std::chrono::nanoseconds ns_late = {}); - - /// Gets the number of core cycles when the specified buffer will be released - [[nodiscard]] std::chrono::nanoseconds GetBufferReleaseNS(const Buffer& buffer) const; - - u32 sample_rate; ///< Sample rate of the stream - u64 played_samples{}; ///< The current played sample count - Format format; ///< Format of the stream - float game_volume = 1.0f; ///< The volume the game currently has set - ReleaseCallback release_callback; ///< Buffer release callback for the stream - State state{State::Stopped}; ///< Playback state of the stream - std::shared_ptr - release_event; ///< Core timing release event for the stream - BufferPtr active_buffer; ///< Actively playing buffer in the stream - std::queue queued_buffers; ///< Buffers queued to be played in the stream - std::queue released_buffers; ///< Buffers recently released from the stream - SinkStream& sink_stream; ///< Output sink for the stream - Core::Timing::CoreTiming& core_timing; ///< Core timing instance. - std::string name; ///< Name of the stream, must be unique -}; - -using StreamPtr = std::shared_ptr; - -} // namespace AudioCore diff --git a/src/audio_core/voice_context.cpp b/src/audio_core/voice_context.cpp deleted file mode 100644 index f58a5c754..000000000 --- a/src/audio_core/voice_context.cpp +++ /dev/null @@ -1,579 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include - -#include "audio_core/behavior_info.h" -#include "audio_core/voice_context.h" -#include "core/memory.h" - -namespace AudioCore { - -ServerVoiceChannelResource::ServerVoiceChannelResource(s32 id_) : id(id_) {} -ServerVoiceChannelResource::~ServerVoiceChannelResource() = default; - -bool ServerVoiceChannelResource::InUse() const { - return in_use; -} - -float ServerVoiceChannelResource::GetCurrentMixVolumeAt(std::size_t i) const { - ASSERT(i < AudioCommon::MAX_MIX_BUFFERS); - return mix_volume.at(i); -} - -float ServerVoiceChannelResource::GetLastMixVolumeAt(std::size_t i) const { - ASSERT(i < AudioCommon::MAX_MIX_BUFFERS); - return last_mix_volume.at(i); -} - -void ServerVoiceChannelResource::Update(VoiceChannelResource::InParams& in_params) { - in_use = in_params.in_use; - // Update our mix volumes only if it's in use - if (in_params.in_use) { - mix_volume = in_params.mix_volume; - } -} - -void ServerVoiceChannelResource::UpdateLastMixVolumes() { - last_mix_volume = mix_volume; -} - -const std::array& -ServerVoiceChannelResource::GetCurrentMixVolume() const { - return mix_volume; -} - -const std::array& -ServerVoiceChannelResource::GetLastMixVolume() const { - return last_mix_volume; -} - -ServerVoiceInfo::ServerVoiceInfo() { - Initialize(); -} -ServerVoiceInfo::~ServerVoiceInfo() = default; - -void ServerVoiceInfo::Initialize() { - in_params.in_use = false; - in_params.node_id = 0; - in_params.id = 0; - in_params.current_playstate = ServerPlayState::Stop; - in_params.priority = 255; - in_params.sample_rate = 0; - in_params.sample_format = SampleFormat::Invalid; - in_params.channel_count = 0; - in_params.pitch = 0.0f; - in_params.volume = 0.0f; - in_params.last_volume = 0.0f; - in_params.biquad_filter.fill({}); - in_params.wave_buffer_count = 0; - in_params.wave_buffer_head = 0; - in_params.mix_id = AudioCommon::NO_MIX; - in_params.splitter_info_id = AudioCommon::NO_SPLITTER; - in_params.additional_params_address = 0; - in_params.additional_params_size = 0; - in_params.is_new = false; - out_params.played_sample_count = 0; - out_params.wave_buffer_consumed = 0; - in_params.voice_drop_flag = false; - in_params.buffer_mapped = true; - in_params.wave_buffer_flush_request_count = 0; - in_params.was_biquad_filter_enabled.fill(false); - - for (auto& wave_buffer : in_params.wave_buffer) { - wave_buffer.start_sample_offset = 0; - wave_buffer.end_sample_offset = 0; - wave_buffer.is_looping = false; - wave_buffer.end_of_stream = false; - wave_buffer.buffer_address = 0; - wave_buffer.buffer_size = 0; - wave_buffer.context_address = 0; - wave_buffer.context_size = 0; - wave_buffer.sent_to_dsp = true; - } - - stored_samples.clear(); -} - -void ServerVoiceInfo::UpdateParameters(const VoiceInfo::InParams& voice_in, - BehaviorInfo& behavior_info) { - in_params.in_use = voice_in.is_in_use; - in_params.id = voice_in.id; - in_params.node_id = voice_in.node_id; - in_params.last_playstate = in_params.current_playstate; - switch (voice_in.play_state) { - case PlayState::Paused: - in_params.current_playstate = ServerPlayState::Paused; - break; - case PlayState::Stopped: - if (in_params.current_playstate != ServerPlayState::Stop) { - in_params.current_playstate = ServerPlayState::RequestStop; - } - break; - case PlayState::Started: - in_params.current_playstate = ServerPlayState::Play; - break; - default: - ASSERT_MSG(false, "Unknown playstate {}", voice_in.play_state); - break; - } - - in_params.priority = voice_in.priority; - in_params.sorting_order = voice_in.sorting_order; - in_params.sample_rate = voice_in.sample_rate; - in_params.sample_format = voice_in.sample_format; - in_params.channel_count = voice_in.channel_count; - in_params.pitch = voice_in.pitch; - in_params.volume = voice_in.volume; - in_params.biquad_filter = voice_in.biquad_filter; - in_params.wave_buffer_count = voice_in.wave_buffer_count; - in_params.wave_buffer_head = voice_in.wave_buffer_head; - if (behavior_info.IsFlushVoiceWaveBuffersSupported()) { - const auto in_request_count = in_params.wave_buffer_flush_request_count; - const auto voice_request_count = voice_in.wave_buffer_flush_request_count; - in_params.wave_buffer_flush_request_count = - static_cast(in_request_count + voice_request_count); - } - in_params.mix_id = voice_in.mix_id; - if (behavior_info.IsSplitterSupported()) { - in_params.splitter_info_id = voice_in.splitter_info_id; - } else { - in_params.splitter_info_id = AudioCommon::NO_SPLITTER; - } - - std::memcpy(in_params.voice_channel_resource_id.data(), - voice_in.voice_channel_resource_ids.data(), - sizeof(s32) * in_params.voice_channel_resource_id.size()); - - if (behavior_info.IsVoicePlayedSampleCountResetAtLoopPointSupported()) { - in_params.behavior_flags.is_played_samples_reset_at_loop_point = - voice_in.behavior_flags.is_played_samples_reset_at_loop_point; - } else { - in_params.behavior_flags.is_played_samples_reset_at_loop_point.Assign(0); - } - if (behavior_info.IsVoicePitchAndSrcSkippedSupported()) { - in_params.behavior_flags.is_pitch_and_src_skipped = - voice_in.behavior_flags.is_pitch_and_src_skipped; - } else { - in_params.behavior_flags.is_pitch_and_src_skipped.Assign(0); - } - - if (voice_in.is_voice_drop_flag_clear_requested) { - in_params.voice_drop_flag = false; - } - - if (in_params.additional_params_address != voice_in.additional_params_address || - in_params.additional_params_size != voice_in.additional_params_size) { - in_params.additional_params_address = voice_in.additional_params_address; - in_params.additional_params_size = voice_in.additional_params_size; - // TODO(ogniK): Reattach buffer, do we actually need to? Maybe just signal to the DSP that - // our context is new - } -} - -void ServerVoiceInfo::UpdateWaveBuffers( - const VoiceInfo::InParams& voice_in, - std::array& voice_states, - BehaviorInfo& behavior_info) { - if (voice_in.is_new) { - // Initialize our wave buffers - for (auto& wave_buffer : in_params.wave_buffer) { - wave_buffer.start_sample_offset = 0; - wave_buffer.end_sample_offset = 0; - wave_buffer.is_looping = false; - wave_buffer.end_of_stream = false; - wave_buffer.buffer_address = 0; - wave_buffer.buffer_size = 0; - wave_buffer.context_address = 0; - wave_buffer.context_size = 0; - wave_buffer.loop_start_sample = 0; - wave_buffer.loop_end_sample = 0; - wave_buffer.sent_to_dsp = true; - } - - // Mark all our wave buffers as invalid - for (std::size_t channel = 0; channel < static_cast(in_params.channel_count); - channel++) { - for (std::size_t i = 0; i < AudioCommon::MAX_WAVE_BUFFERS; ++i) { - voice_states[channel]->is_wave_buffer_valid[i] = false; - } - } - } - - // Update our wave buffers - for (std::size_t i = 0; i < AudioCommon::MAX_WAVE_BUFFERS; i++) { - // Assume that we have at least 1 channel voice state - const auto have_valid_wave_buffer = voice_states[0]->is_wave_buffer_valid[i]; - - UpdateWaveBuffer(in_params.wave_buffer[i], voice_in.wave_buffer[i], in_params.sample_format, - have_valid_wave_buffer, behavior_info); - } -} - -void ServerVoiceInfo::UpdateWaveBuffer(ServerWaveBuffer& out_wavebuffer, - const WaveBuffer& in_wave_buffer, SampleFormat sample_format, - bool is_buffer_valid, - [[maybe_unused]] BehaviorInfo& behavior_info) { - if (!is_buffer_valid && out_wavebuffer.sent_to_dsp && out_wavebuffer.buffer_address != 0) { - out_wavebuffer.buffer_address = 0; - out_wavebuffer.buffer_size = 0; - } - - if (!in_wave_buffer.sent_to_server || !in_params.buffer_mapped) { - // Validate sample offset sizings - if (sample_format == SampleFormat::Pcm16) { - const s64 buffer_size = static_cast(in_wave_buffer.buffer_size); - const s64 start = sizeof(s16) * in_wave_buffer.start_sample_offset; - const s64 end = sizeof(s16) * in_wave_buffer.end_sample_offset; - if (0 > start || start > buffer_size || 0 > end || end > buffer_size) { - // TODO(ogniK): Write error info - LOG_ERROR(Audio, - "PCM16 wavebuffer has an invalid size. Buffer has size 0x{:08X}, but " - "offsets were " - "{:08X} - 0x{:08X}", - buffer_size, sizeof(s16) * in_wave_buffer.start_sample_offset, - sizeof(s16) * in_wave_buffer.end_sample_offset); - return; - } - } else if (sample_format == SampleFormat::Adpcm) { - const s64 buffer_size = static_cast(in_wave_buffer.buffer_size); - const s64 start_frames = in_wave_buffer.start_sample_offset / 14; - const s64 start_extra = in_wave_buffer.start_sample_offset % 14 == 0 - ? 0 - : (in_wave_buffer.start_sample_offset % 14) / 2 + 1 + - (in_wave_buffer.start_sample_offset % 2); - const s64 start = start_frames * 8 + start_extra; - const s64 end_frames = in_wave_buffer.end_sample_offset / 14; - const s64 end_extra = in_wave_buffer.end_sample_offset % 14 == 0 - ? 0 - : (in_wave_buffer.end_sample_offset % 14) / 2 + 1 + - (in_wave_buffer.end_sample_offset % 2); - const s64 end = end_frames * 8 + end_extra; - if (in_wave_buffer.start_sample_offset < 0 || start > buffer_size || - in_wave_buffer.end_sample_offset < 0 || end > buffer_size) { - LOG_ERROR(Audio, - "ADPMC wavebuffer has an invalid size. Buffer has size 0x{:08X}, but " - "offsets were " - "{:08X} - 0x{:08X}", - in_wave_buffer.buffer_size, start, end); - return; - } - } - // TODO(ogniK): ADPCM Size error - - out_wavebuffer.sent_to_dsp = false; - out_wavebuffer.start_sample_offset = in_wave_buffer.start_sample_offset; - out_wavebuffer.end_sample_offset = in_wave_buffer.end_sample_offset; - out_wavebuffer.is_looping = in_wave_buffer.is_looping; - out_wavebuffer.end_of_stream = in_wave_buffer.end_of_stream; - - out_wavebuffer.buffer_address = in_wave_buffer.buffer_address; - out_wavebuffer.buffer_size = in_wave_buffer.buffer_size; - out_wavebuffer.context_address = in_wave_buffer.context_address; - out_wavebuffer.context_size = in_wave_buffer.context_size; - out_wavebuffer.loop_start_sample = in_wave_buffer.loop_start_sample; - out_wavebuffer.loop_end_sample = in_wave_buffer.loop_end_sample; - in_params.buffer_mapped = - in_wave_buffer.buffer_address != 0 && in_wave_buffer.buffer_size != 0; - // TODO(ogniK): Pool mapper attachment - // TODO(ogniK): IsAdpcmLoopContextBugFixed - if (sample_format == SampleFormat::Adpcm && in_wave_buffer.context_address != 0 && - in_wave_buffer.context_size != 0 && behavior_info.IsAdpcmLoopContextBugFixed()) { - } else { - out_wavebuffer.context_address = 0; - out_wavebuffer.context_size = 0; - } - } -} - -void ServerVoiceInfo::WriteOutStatus( - VoiceInfo::OutParams& voice_out, VoiceInfo::InParams& voice_in, - std::array& voice_states) { - if (voice_in.is_new || in_params.is_new) { - in_params.is_new = true; - voice_out.wave_buffer_consumed = 0; - voice_out.played_sample_count = 0; - voice_out.voice_dropped = false; - } else { - const auto& state = voice_states[0]; - voice_out.wave_buffer_consumed = state->wave_buffer_consumed; - voice_out.played_sample_count = state->played_sample_count; - voice_out.voice_dropped = state->voice_dropped; - } -} - -const ServerVoiceInfo::InParams& ServerVoiceInfo::GetInParams() const { - return in_params; -} - -ServerVoiceInfo::InParams& ServerVoiceInfo::GetInParams() { - return in_params; -} - -const ServerVoiceInfo::OutParams& ServerVoiceInfo::GetOutParams() const { - return out_params; -} - -ServerVoiceInfo::OutParams& ServerVoiceInfo::GetOutParams() { - return out_params; -} - -bool ServerVoiceInfo::ShouldSkip() const { - // TODO(ogniK): Handle unmapped wave buffers or parameters - return !in_params.in_use || in_params.wave_buffer_count == 0 || !in_params.buffer_mapped || - in_params.voice_drop_flag; -} - -bool ServerVoiceInfo::UpdateForCommandGeneration(VoiceContext& voice_context) { - std::array dsp_voice_states{}; - if (in_params.is_new) { - ResetResources(voice_context); - in_params.last_volume = in_params.volume; - in_params.is_new = false; - } - - const s32 channel_count = in_params.channel_count; - for (s32 i = 0; i < channel_count; i++) { - const auto channel_resource = in_params.voice_channel_resource_id[i]; - dsp_voice_states[i] = - &voice_context.GetDspSharedState(static_cast(channel_resource)); - } - return UpdateParametersForCommandGeneration(dsp_voice_states); -} - -void ServerVoiceInfo::ResetResources(VoiceContext& voice_context) { - const s32 channel_count = in_params.channel_count; - for (s32 i = 0; i < channel_count; i++) { - const auto channel_resource = in_params.voice_channel_resource_id[i]; - auto& dsp_state = - voice_context.GetDspSharedState(static_cast(channel_resource)); - dsp_state = {}; - voice_context.GetChannelResource(static_cast(channel_resource)) - .UpdateLastMixVolumes(); - } -} - -bool ServerVoiceInfo::UpdateParametersForCommandGeneration( - std::array& dsp_voice_states) { - const s32 channel_count = in_params.channel_count; - if (in_params.wave_buffer_flush_request_count > 0) { - FlushWaveBuffers(in_params.wave_buffer_flush_request_count, dsp_voice_states, - channel_count); - in_params.wave_buffer_flush_request_count = 0; - } - - switch (in_params.current_playstate) { - case ServerPlayState::Play: { - for (std::size_t i = 0; i < AudioCommon::MAX_WAVE_BUFFERS; i++) { - if (!in_params.wave_buffer[i].sent_to_dsp) { - for (s32 channel = 0; channel < channel_count; channel++) { - dsp_voice_states[channel]->is_wave_buffer_valid[i] = true; - } - in_params.wave_buffer[i].sent_to_dsp = true; - } - } - in_params.should_depop = false; - return HasValidWaveBuffer(dsp_voice_states[0]); - } - case ServerPlayState::Paused: - case ServerPlayState::Stop: { - in_params.should_depop = in_params.last_playstate == ServerPlayState::Play; - return in_params.should_depop; - } - case ServerPlayState::RequestStop: { - for (std::size_t i = 0; i < AudioCommon::MAX_WAVE_BUFFERS; i++) { - in_params.wave_buffer[i].sent_to_dsp = true; - for (s32 channel = 0; channel < channel_count; channel++) { - auto* dsp_state = dsp_voice_states[channel]; - - if (dsp_state->is_wave_buffer_valid[i]) { - dsp_state->wave_buffer_index = - (dsp_state->wave_buffer_index + 1) % AudioCommon::MAX_WAVE_BUFFERS; - dsp_state->wave_buffer_consumed++; - } - - dsp_state->is_wave_buffer_valid[i] = false; - } - } - - for (s32 channel = 0; channel < channel_count; channel++) { - auto* dsp_state = dsp_voice_states[channel]; - dsp_state->offset = 0; - dsp_state->played_sample_count = 0; - dsp_state->fraction = 0; - dsp_state->sample_history.fill(0); - dsp_state->context = {}; - } - - in_params.current_playstate = ServerPlayState::Stop; - in_params.should_depop = in_params.last_playstate == ServerPlayState::Play; - return in_params.should_depop; - } - default: - ASSERT_MSG(false, "Invalid playstate {}", in_params.current_playstate); - } - - return false; -} - -void ServerVoiceInfo::FlushWaveBuffers( - u8 flush_count, std::array& dsp_voice_states, - s32 channel_count) { - auto wave_head = in_params.wave_buffer_head; - - for (u8 i = 0; i < flush_count; i++) { - in_params.wave_buffer[wave_head].sent_to_dsp = true; - for (s32 channel = 0; channel < channel_count; channel++) { - auto* dsp_state = dsp_voice_states[channel]; - dsp_state->wave_buffer_consumed++; - dsp_state->is_wave_buffer_valid[wave_head] = false; - dsp_state->wave_buffer_index = - (dsp_state->wave_buffer_index + 1) % AudioCommon::MAX_WAVE_BUFFERS; - } - wave_head = (wave_head + 1) % AudioCommon::MAX_WAVE_BUFFERS; - } -} - -bool ServerVoiceInfo::HasValidWaveBuffer(const VoiceState* state) const { - const auto& valid_wb = state->is_wave_buffer_valid; - return std::find(valid_wb.begin(), valid_wb.end(), true) != valid_wb.end(); -} - -void ServerVoiceInfo::SetWaveBufferCompleted(VoiceState& dsp_state, - const ServerWaveBuffer& wave_buffer) { - dsp_state.is_wave_buffer_valid[dsp_state.wave_buffer_index] = false; - dsp_state.wave_buffer_consumed++; - dsp_state.wave_buffer_index = (dsp_state.wave_buffer_index + 1) % AudioCommon::MAX_WAVE_BUFFERS; - dsp_state.loop_count = 0; - if (wave_buffer.end_of_stream) { - dsp_state.played_sample_count = 0; - } -} - -VoiceContext::VoiceContext(std::size_t voice_count_) : voice_count{voice_count_} { - for (std::size_t i = 0; i < voice_count; i++) { - voice_channel_resources.emplace_back(static_cast(i)); - sorted_voice_info.push_back(&voice_info.emplace_back()); - voice_states.emplace_back(); - dsp_voice_states.emplace_back(); - } -} - -VoiceContext::~VoiceContext() { - sorted_voice_info.clear(); -} - -std::size_t VoiceContext::GetVoiceCount() const { - return voice_count; -} - -ServerVoiceChannelResource& VoiceContext::GetChannelResource(std::size_t i) { - ASSERT(i < voice_count); - return voice_channel_resources.at(i); -} - -const ServerVoiceChannelResource& VoiceContext::GetChannelResource(std::size_t i) const { - ASSERT(i < voice_count); - return voice_channel_resources.at(i); -} - -VoiceState& VoiceContext::GetState(std::size_t i) { - ASSERT(i < voice_count); - return voice_states.at(i); -} - -const VoiceState& VoiceContext::GetState(std::size_t i) const { - ASSERT(i < voice_count); - return voice_states.at(i); -} - -VoiceState& VoiceContext::GetDspSharedState(std::size_t i) { - ASSERT(i < voice_count); - return dsp_voice_states.at(i); -} - -const VoiceState& VoiceContext::GetDspSharedState(std::size_t i) const { - ASSERT(i < voice_count); - return dsp_voice_states.at(i); -} - -ServerVoiceInfo& VoiceContext::GetInfo(std::size_t i) { - ASSERT(i < voice_count); - return voice_info.at(i); -} - -const ServerVoiceInfo& VoiceContext::GetInfo(std::size_t i) const { - ASSERT(i < voice_count); - return voice_info.at(i); -} - -ServerVoiceInfo& VoiceContext::GetSortedInfo(std::size_t i) { - ASSERT(i < voice_count); - return *sorted_voice_info.at(i); -} - -const ServerVoiceInfo& VoiceContext::GetSortedInfo(std::size_t i) const { - ASSERT(i < voice_count); - return *sorted_voice_info.at(i); -} - -s32 VoiceContext::DecodePcm16(s32* output_buffer, ServerWaveBuffer* wave_buffer, s32 channel, - s32 channel_count, s32 buffer_offset, s32 sample_count, - Core::Memory::Memory& memory) { - if (wave_buffer->buffer_address == 0) { - return 0; - } - if (wave_buffer->buffer_size == 0) { - return 0; - } - if (wave_buffer->end_sample_offset < wave_buffer->start_sample_offset) { - return 0; - } - - const auto samples_remaining = - (wave_buffer->end_sample_offset - wave_buffer->start_sample_offset) - buffer_offset; - const auto start_offset = (wave_buffer->start_sample_offset + buffer_offset) * channel_count; - const auto buffer_pos = wave_buffer->buffer_address + start_offset; - - s16* buffer_data = reinterpret_cast(memory.GetPointer(buffer_pos)); - - const auto samples_processed = std::min(sample_count, samples_remaining); - - // Fast path - if (channel_count == 1) { - for (std::ptrdiff_t i = 0; i < samples_processed; i++) { - output_buffer[i] = buffer_data[i]; - } - } else { - for (std::ptrdiff_t i = 0; i < samples_processed; i++) { - output_buffer[i] = buffer_data[i * channel_count + channel]; - } - } - - return samples_processed; -} - -void VoiceContext::SortInfo() { - for (std::size_t i = 0; i < voice_count; i++) { - sorted_voice_info[i] = &voice_info[i]; - } - - std::sort(sorted_voice_info.begin(), sorted_voice_info.end(), - [](const ServerVoiceInfo* lhs, const ServerVoiceInfo* rhs) { - const auto& lhs_in = lhs->GetInParams(); - const auto& rhs_in = rhs->GetInParams(); - // Sort by priority - if (lhs_in.priority != rhs_in.priority) { - return lhs_in.priority > rhs_in.priority; - } else { - // If the priorities match, sort by sorting order - return lhs_in.sorting_order > rhs_in.sorting_order; - } - }); -} - -void VoiceContext::UpdateStateByDspShared() { - voice_states = dsp_voice_states; -} - -} // namespace AudioCore diff --git a/src/audio_core/voice_context.h b/src/audio_core/voice_context.h deleted file mode 100644 index 259220dc7..000000000 --- a/src/audio_core/voice_context.h +++ /dev/null @@ -1,302 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include "audio_core/algorithm/interpolate.h" -#include "audio_core/codec.h" -#include "audio_core/common.h" -#include "common/bit_field.h" -#include "common/common_funcs.h" -#include "common/common_types.h" - -namespace Core::Memory { -class Memory; -} - -namespace AudioCore { - -class BehaviorInfo; -class VoiceContext; - -enum class SampleFormat : u8 { - Invalid = 0, - Pcm8 = 1, - Pcm16 = 2, - Pcm24 = 3, - Pcm32 = 4, - PcmFloat = 5, - Adpcm = 6, -}; - -enum class PlayState : u8 { - Started = 0, - Stopped = 1, - Paused = 2, -}; - -enum class ServerPlayState { - Play = 0, - Stop = 1, - RequestStop = 2, - Paused = 3, -}; - -struct BiquadFilterParameter { - bool enabled{}; - INSERT_PADDING_BYTES(1); - std::array numerator{}; - std::array denominator{}; -}; -static_assert(sizeof(BiquadFilterParameter) == 0xc, "BiquadFilterParameter is an invalid size"); - -struct WaveBuffer { - u64_le buffer_address{}; - u64_le buffer_size{}; - s32_le start_sample_offset{}; - s32_le end_sample_offset{}; - u8 is_looping{}; - u8 end_of_stream{}; - u8 sent_to_server{}; - INSERT_PADDING_BYTES(1); - s32 loop_count{}; - u64 context_address{}; - u64 context_size{}; - u32 loop_start_sample{}; - u32 loop_end_sample{}; -}; -static_assert(sizeof(WaveBuffer) == 0x38, "WaveBuffer is an invalid size"); - -struct ServerWaveBuffer { - VAddr buffer_address{}; - std::size_t buffer_size{}; - s32 start_sample_offset{}; - s32 end_sample_offset{}; - bool is_looping{}; - bool end_of_stream{}; - VAddr context_address{}; - std::size_t context_size{}; - s32 loop_count{}; - u32 loop_start_sample{}; - u32 loop_end_sample{}; - bool sent_to_dsp{true}; -}; - -struct BehaviorFlags { - BitField<0, 1, u16> is_played_samples_reset_at_loop_point; - BitField<1, 1, u16> is_pitch_and_src_skipped; -}; -static_assert(sizeof(BehaviorFlags) == 0x4, "BehaviorFlags is an invalid size"); - -struct ADPCMContext { - u16 header; - s16 yn1; - s16 yn2; -}; -static_assert(sizeof(ADPCMContext) == 0x6, "ADPCMContext is an invalid size"); - -struct VoiceState { - s64 played_sample_count; - s32 offset; - s32 wave_buffer_index; - std::array is_wave_buffer_valid; - s32 wave_buffer_consumed; - std::array sample_history; - s32 fraction; - VAddr context_address; - Codec::ADPCM_Coeff coeff; - ADPCMContext context; - std::array biquad_filter_state; - std::array previous_samples; - u32 external_context_size; - bool is_external_context_used; - bool voice_dropped; - s32 loop_count; -}; - -class VoiceChannelResource { -public: - struct InParams { - s32_le id{}; - std::array mix_volume{}; - bool in_use{}; - INSERT_PADDING_BYTES(11); - }; - static_assert(sizeof(InParams) == 0x70, "InParams is an invalid size"); -}; - -class ServerVoiceChannelResource { -public: - explicit ServerVoiceChannelResource(s32 id_); - ~ServerVoiceChannelResource(); - - bool InUse() const; - float GetCurrentMixVolumeAt(std::size_t i) const; - float GetLastMixVolumeAt(std::size_t i) const; - void Update(VoiceChannelResource::InParams& in_params); - void UpdateLastMixVolumes(); - - const std::array& GetCurrentMixVolume() const; - const std::array& GetLastMixVolume() const; - -private: - s32 id{}; - std::array mix_volume{}; - std::array last_mix_volume{}; - bool in_use{}; -}; - -class VoiceInfo { -public: - struct InParams { - s32_le id{}; - u32_le node_id{}; - u8 is_new{}; - u8 is_in_use{}; - PlayState play_state{}; - SampleFormat sample_format{}; - s32_le sample_rate{}; - s32_le priority{}; - s32_le sorting_order{}; - s32_le channel_count{}; - float_le pitch{}; - float_le volume{}; - std::array biquad_filter{}; - s32_le wave_buffer_count{}; - s16_le wave_buffer_head{}; - INSERT_PADDING_BYTES(6); - u64_le additional_params_address{}; - u64_le additional_params_size{}; - s32_le mix_id{}; - s32_le splitter_info_id{}; - std::array wave_buffer{}; - std::array voice_channel_resource_ids{}; - // TODO(ogniK): Remaining flags - u8 is_voice_drop_flag_clear_requested{}; - u8 wave_buffer_flush_request_count{}; - INSERT_PADDING_BYTES(2); - BehaviorFlags behavior_flags{}; - INSERT_PADDING_BYTES(16); - }; - static_assert(sizeof(InParams) == 0x170, "InParams is an invalid size"); - - struct OutParams { - u64_le played_sample_count{}; - u32_le wave_buffer_consumed{}; - u8 voice_dropped{}; - INSERT_PADDING_BYTES(3); - }; - static_assert(sizeof(OutParams) == 0x10, "OutParams is an invalid size"); -}; - -class ServerVoiceInfo { -public: - struct InParams { - bool in_use{}; - bool is_new{}; - bool should_depop{}; - SampleFormat sample_format{}; - s32 sample_rate{}; - s32 channel_count{}; - s32 id{}; - s32 node_id{}; - s32 mix_id{}; - ServerPlayState current_playstate{}; - ServerPlayState last_playstate{}; - s32 priority{}; - s32 sorting_order{}; - float pitch{}; - float volume{}; - float last_volume{}; - std::array biquad_filter{}; - s32 wave_buffer_count{}; - s16 wave_buffer_head{}; - INSERT_PADDING_BYTES(2); - BehaviorFlags behavior_flags{}; - VAddr additional_params_address{}; - std::size_t additional_params_size{}; - std::array wave_buffer{}; - std::array voice_channel_resource_id{}; - s32 splitter_info_id{}; - u8 wave_buffer_flush_request_count{}; - bool voice_drop_flag{}; - bool buffer_mapped{}; - std::array was_biquad_filter_enabled{}; - }; - - struct OutParams { - s64 played_sample_count{}; - s32 wave_buffer_consumed{}; - }; - - ServerVoiceInfo(); - ~ServerVoiceInfo(); - void Initialize(); - void UpdateParameters(const VoiceInfo::InParams& voice_in, BehaviorInfo& behavior_info); - void UpdateWaveBuffers(const VoiceInfo::InParams& voice_in, - std::array& voice_states, - BehaviorInfo& behavior_info); - void UpdateWaveBuffer(ServerWaveBuffer& out_wavebuffer, const WaveBuffer& in_wave_buffer, - SampleFormat sample_format, bool is_buffer_valid, - BehaviorInfo& behavior_info); - void WriteOutStatus(VoiceInfo::OutParams& voice_out, VoiceInfo::InParams& voice_in, - std::array& voice_states); - - const InParams& GetInParams() const; - InParams& GetInParams(); - - const OutParams& GetOutParams() const; - OutParams& GetOutParams(); - - bool ShouldSkip() const; - bool UpdateForCommandGeneration(VoiceContext& voice_context); - void ResetResources(VoiceContext& voice_context); - bool UpdateParametersForCommandGeneration( - std::array& dsp_voice_states); - void FlushWaveBuffers(u8 flush_count, - std::array& dsp_voice_states, - s32 channel_count); - void SetWaveBufferCompleted(VoiceState& dsp_state, const ServerWaveBuffer& wave_buffer); - -private: - std::vector stored_samples; - InParams in_params{}; - OutParams out_params{}; - - bool HasValidWaveBuffer(const VoiceState* state) const; -}; - -class VoiceContext { -public: - explicit VoiceContext(std::size_t voice_count_); - ~VoiceContext(); - - std::size_t GetVoiceCount() const; - ServerVoiceChannelResource& GetChannelResource(std::size_t i); - const ServerVoiceChannelResource& GetChannelResource(std::size_t i) const; - VoiceState& GetState(std::size_t i); - const VoiceState& GetState(std::size_t i) const; - VoiceState& GetDspSharedState(std::size_t i); - const VoiceState& GetDspSharedState(std::size_t i) const; - ServerVoiceInfo& GetInfo(std::size_t i); - const ServerVoiceInfo& GetInfo(std::size_t i) const; - ServerVoiceInfo& GetSortedInfo(std::size_t i); - const ServerVoiceInfo& GetSortedInfo(std::size_t i) const; - - s32 DecodePcm16(s32* output_buffer, ServerWaveBuffer* wave_buffer, s32 channel, - s32 channel_count, s32 buffer_offset, s32 sample_count, - Core::Memory::Memory& memory); - void SortInfo(); - void UpdateStateByDspShared(); - -private: - std::size_t voice_count{}; - std::vector voice_channel_resources{}; - std::vector voice_states{}; - std::vector dsp_voice_states{}; - std::vector voice_info{}; - std::vector sorted_voice_info{}; -}; - -} // namespace AudioCore diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 73bf626d4..64bb753e6 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -43,6 +43,7 @@ add_library(common STATIC alignment.h assert.cpp assert.h + atomic_helpers.h atomic_ops.h detached_tasks.cpp detached_tasks.h @@ -64,6 +65,7 @@ add_library(common STATIC expected.h fiber.cpp fiber.h + fixed_point.h fs/file.cpp fs/file.h fs/fs.cpp @@ -109,6 +111,7 @@ add_library(common STATIC parent_of_member.h point.h quaternion.h + reader_writer_queue.h ring_buffer.h scm_rev.cpp scm_rev.h diff --git a/src/common/atomic_helpers.h b/src/common/atomic_helpers.h new file mode 100644 index 000000000..6d912b52e --- /dev/null +++ b/src/common/atomic_helpers.h @@ -0,0 +1,772 @@ +// ©2013-2016 Cameron Desrochers. +// Distributed under the simplified BSD license (see the license file that +// should have come with this header). +// Uses Jeff Preshing's semaphore implementation (under the terms of its +// separate zlib license, embedded below). + +#pragma once + +// Provides portable (VC++2010+, Intel ICC 13, GCC 4.7+, and anything C++11 compliant) +// implementation of low-level memory barriers, plus a few semi-portable utility macros (for +// inlining and alignment). Also has a basic atomic type (limited to hardware-supported atomics with +// no memory ordering guarantees). Uses the AE_* prefix for macros (historical reasons), and the +// "moodycamel" namespace for symbols. + +#include +#include +#include +#include +#include + +// Platform detection +#if defined(__INTEL_COMPILER) +#define AE_ICC +#elif defined(_MSC_VER) +#define AE_VCPP +#elif defined(__GNUC__) +#define AE_GCC +#endif + +#if defined(_M_IA64) || defined(__ia64__) +#define AE_ARCH_IA64 +#elif defined(_WIN64) || defined(__amd64__) || defined(_M_X64) || defined(__x86_64__) +#define AE_ARCH_X64 +#elif defined(_M_IX86) || defined(__i386__) +#define AE_ARCH_X86 +#elif defined(_M_PPC) || defined(__powerpc__) +#define AE_ARCH_PPC +#else +#define AE_ARCH_UNKNOWN +#endif + +// AE_UNUSED +#define AE_UNUSED(x) ((void)x) + +// AE_NO_TSAN/AE_TSAN_ANNOTATE_* +#if defined(__has_feature) +#if __has_feature(thread_sanitizer) +#if __cplusplus >= 201703L // inline variables require C++17 +namespace Common { +inline int ae_tsan_global; +} +#define AE_TSAN_ANNOTATE_RELEASE() \ + AnnotateHappensBefore(__FILE__, __LINE__, (void*)(&::moodycamel::ae_tsan_global)) +#define AE_TSAN_ANNOTATE_ACQUIRE() \ + AnnotateHappensAfter(__FILE__, __LINE__, (void*)(&::moodycamel::ae_tsan_global)) +extern "C" void AnnotateHappensBefore(const char*, int, void*); +extern "C" void AnnotateHappensAfter(const char*, int, void*); +#else // when we can't work with tsan, attempt to disable its warnings +#define AE_NO_TSAN __attribute__((no_sanitize("thread"))) +#endif +#endif +#endif +#ifndef AE_NO_TSAN +#define AE_NO_TSAN +#endif +#ifndef AE_TSAN_ANNOTATE_RELEASE +#define AE_TSAN_ANNOTATE_RELEASE() +#define AE_TSAN_ANNOTATE_ACQUIRE() +#endif + +// AE_FORCEINLINE +#if defined(AE_VCPP) || defined(AE_ICC) +#define AE_FORCEINLINE __forceinline +#elif defined(AE_GCC) +//#define AE_FORCEINLINE __attribute__((always_inline)) +#define AE_FORCEINLINE inline +#else +#define AE_FORCEINLINE inline +#endif + +// AE_ALIGN +#if defined(AE_VCPP) || defined(AE_ICC) +#define AE_ALIGN(x) __declspec(align(x)) +#elif defined(AE_GCC) +#define AE_ALIGN(x) __attribute__((aligned(x))) +#else +// Assume GCC compliant syntax... +#define AE_ALIGN(x) __attribute__((aligned(x))) +#endif + +// Portable atomic fences implemented below: + +namespace Common { + +enum memory_order { + memory_order_relaxed, + memory_order_acquire, + memory_order_release, + memory_order_acq_rel, + memory_order_seq_cst, + + // memory_order_sync: Forces a full sync: + // #LoadLoad, #LoadStore, #StoreStore, and most significantly, #StoreLoad + memory_order_sync = memory_order_seq_cst +}; + +} // namespace Common + +#if (defined(AE_VCPP) && (_MSC_VER < 1700 || defined(__cplusplus_cli))) || \ + (defined(AE_ICC) && __INTEL_COMPILER < 1600) +// VS2010 and ICC13 don't support std::atomic_*_fence, implement our own fences + +#include + +#if defined(AE_ARCH_X64) || defined(AE_ARCH_X86) +#define AeFullSync _mm_mfence +#define AeLiteSync _mm_mfence +#elif defined(AE_ARCH_IA64) +#define AeFullSync __mf +#define AeLiteSync __mf +#elif defined(AE_ARCH_PPC) +#include +#define AeFullSync __sync +#define AeLiteSync __lwsync +#endif + +#ifdef AE_VCPP +#pragma warning(push) +#pragma warning(disable : 4365) // Disable erroneous 'conversion from long to unsigned int, + // signed/unsigned mismatch' error when using `assert` +#ifdef __cplusplus_cli +#pragma managed(push, off) +#endif +#endif + +namespace Common { + +AE_FORCEINLINE void compiler_fence(memory_order order) AE_NO_TSAN { + switch (order) { + case memory_order_relaxed: + break; + case memory_order_acquire: + _ReadBarrier(); + break; + case memory_order_release: + _WriteBarrier(); + break; + case memory_order_acq_rel: + _ReadWriteBarrier(); + break; + case memory_order_seq_cst: + _ReadWriteBarrier(); + break; + default: + assert(false); + } +} + +// x86/x64 have a strong memory model -- all loads and stores have +// acquire and release semantics automatically (so only need compiler +// barriers for those). +#if defined(AE_ARCH_X86) || defined(AE_ARCH_X64) +AE_FORCEINLINE void fence(memory_order order) AE_NO_TSAN { + switch (order) { + case memory_order_relaxed: + break; + case memory_order_acquire: + _ReadBarrier(); + break; + case memory_order_release: + _WriteBarrier(); + break; + case memory_order_acq_rel: + _ReadWriteBarrier(); + break; + case memory_order_seq_cst: + _ReadWriteBarrier(); + AeFullSync(); + _ReadWriteBarrier(); + break; + default: + assert(false); + } +} +#else +AE_FORCEINLINE void fence(memory_order order) AE_NO_TSAN { + // Non-specialized arch, use heavier memory barriers everywhere just in case :-( + switch (order) { + case memory_order_relaxed: + break; + case memory_order_acquire: + _ReadBarrier(); + AeLiteSync(); + _ReadBarrier(); + break; + case memory_order_release: + _WriteBarrier(); + AeLiteSync(); + _WriteBarrier(); + break; + case memory_order_acq_rel: + _ReadWriteBarrier(); + AeLiteSync(); + _ReadWriteBarrier(); + break; + case memory_order_seq_cst: + _ReadWriteBarrier(); + AeFullSync(); + _ReadWriteBarrier(); + break; + default: + assert(false); + } +} +#endif +} // namespace Common +#else +// Use standard library of atomics +#include + +namespace Common { + +AE_FORCEINLINE void compiler_fence(memory_order order) AE_NO_TSAN { + switch (order) { + case memory_order_relaxed: + break; + case memory_order_acquire: + std::atomic_signal_fence(std::memory_order_acquire); + break; + case memory_order_release: + std::atomic_signal_fence(std::memory_order_release); + break; + case memory_order_acq_rel: + std::atomic_signal_fence(std::memory_order_acq_rel); + break; + case memory_order_seq_cst: + std::atomic_signal_fence(std::memory_order_seq_cst); + break; + default: + assert(false); + } +} + +AE_FORCEINLINE void fence(memory_order order) AE_NO_TSAN { + switch (order) { + case memory_order_relaxed: + break; + case memory_order_acquire: + AE_TSAN_ANNOTATE_ACQUIRE(); + std::atomic_thread_fence(std::memory_order_acquire); + break; + case memory_order_release: + AE_TSAN_ANNOTATE_RELEASE(); + std::atomic_thread_fence(std::memory_order_release); + break; + case memory_order_acq_rel: + AE_TSAN_ANNOTATE_ACQUIRE(); + AE_TSAN_ANNOTATE_RELEASE(); + std::atomic_thread_fence(std::memory_order_acq_rel); + break; + case memory_order_seq_cst: + AE_TSAN_ANNOTATE_ACQUIRE(); + AE_TSAN_ANNOTATE_RELEASE(); + std::atomic_thread_fence(std::memory_order_seq_cst); + break; + default: + assert(false); + } +} + +} // namespace Common + +#endif + +#if !defined(AE_VCPP) || (_MSC_VER >= 1700 && !defined(__cplusplus_cli)) +#define AE_USE_STD_ATOMIC_FOR_WEAK_ATOMIC +#endif + +#ifdef AE_USE_STD_ATOMIC_FOR_WEAK_ATOMIC +#include +#endif +#include + +// WARNING: *NOT* A REPLACEMENT FOR std::atomic. READ CAREFULLY: +// Provides basic support for atomic variables -- no memory ordering guarantees are provided. +// The guarantee of atomicity is only made for types that already have atomic load and store +// guarantees at the hardware level -- on most platforms this generally means aligned pointers and +// integers (only). +namespace Common { +template +class weak_atomic { +public: + AE_NO_TSAN weak_atomic() : value() {} +#ifdef AE_VCPP +#pragma warning(push) +#pragma warning(disable : 4100) // Get rid of (erroneous) 'unreferenced formal parameter' warning +#endif + template + AE_NO_TSAN weak_atomic(U&& x) : value(std::forward(x)) {} +#ifdef __cplusplus_cli + // Work around bug with universal reference/nullptr combination that only appears when /clr is + // on + AE_NO_TSAN weak_atomic(nullptr_t) : value(nullptr) {} +#endif + AE_NO_TSAN weak_atomic(weak_atomic const& other) : value(other.load()) {} + AE_NO_TSAN weak_atomic(weak_atomic&& other) : value(std::move(other.load())) {} +#ifdef AE_VCPP +#pragma warning(pop) +#endif + + AE_FORCEINLINE operator T() const AE_NO_TSAN { + return load(); + } + +#ifndef AE_USE_STD_ATOMIC_FOR_WEAK_ATOMIC + template + AE_FORCEINLINE weak_atomic const& operator=(U&& x) AE_NO_TSAN { + value = std::forward(x); + return *this; + } + AE_FORCEINLINE weak_atomic const& operator=(weak_atomic const& other) AE_NO_TSAN { + value = other.value; + return *this; + } + + AE_FORCEINLINE T load() const AE_NO_TSAN { + return value; + } + + AE_FORCEINLINE T fetch_add_acquire(T increment) AE_NO_TSAN { +#if defined(AE_ARCH_X64) || defined(AE_ARCH_X86) + if (sizeof(T) == 4) + return _InterlockedExchangeAdd((long volatile*)&value, (long)increment); +#if defined(_M_AMD64) + else if (sizeof(T) == 8) + return _InterlockedExchangeAdd64((long long volatile*)&value, (long long)increment); +#endif +#else +#error Unsupported platform +#endif + assert(false && "T must be either a 32 or 64 bit type"); + return value; + } + + AE_FORCEINLINE T fetch_add_release(T increment) AE_NO_TSAN { +#if defined(AE_ARCH_X64) || defined(AE_ARCH_X86) + if (sizeof(T) == 4) + return _InterlockedExchangeAdd((long volatile*)&value, (long)increment); +#if defined(_M_AMD64) + else if (sizeof(T) == 8) + return _InterlockedExchangeAdd64((long long volatile*)&value, (long long)increment); +#endif +#else +#error Unsupported platform +#endif + assert(false && "T must be either a 32 or 64 bit type"); + return value; + } +#else + template + AE_FORCEINLINE weak_atomic const& operator=(U&& x) AE_NO_TSAN { + value.store(std::forward(x), std::memory_order_relaxed); + return *this; + } + + AE_FORCEINLINE weak_atomic const& operator=(weak_atomic const& other) AE_NO_TSAN { + value.store(other.value.load(std::memory_order_relaxed), std::memory_order_relaxed); + return *this; + } + + AE_FORCEINLINE T load() const AE_NO_TSAN { + return value.load(std::memory_order_relaxed); + } + + AE_FORCEINLINE T fetch_add_acquire(T increment) AE_NO_TSAN { + return value.fetch_add(increment, std::memory_order_acquire); + } + + AE_FORCEINLINE T fetch_add_release(T increment) AE_NO_TSAN { + return value.fetch_add(increment, std::memory_order_release); + } +#endif + +private: +#ifndef AE_USE_STD_ATOMIC_FOR_WEAK_ATOMIC + // No std::atomic support, but still need to circumvent compiler optimizations. + // `volatile` will make memory access slow, but is guaranteed to be reliable. + volatile T value; +#else + std::atomic value; +#endif +}; + +} // namespace Common + +// Portable single-producer, single-consumer semaphore below: + +#if defined(_WIN32) +// Avoid including windows.h in a header; we only need a handful of +// items, so we'll redeclare them here (this is relatively safe since +// the API generally has to remain stable between Windows versions). +// I know this is an ugly hack but it still beats polluting the global +// namespace with thousands of generic names or adding a .cpp for nothing. +extern "C" { +struct _SECURITY_ATTRIBUTES; +__declspec(dllimport) void* __stdcall CreateSemaphoreW(_SECURITY_ATTRIBUTES* lpSemaphoreAttributes, + long lInitialCount, long lMaximumCount, + const wchar_t* lpName); +__declspec(dllimport) int __stdcall CloseHandle(void* hObject); +__declspec(dllimport) unsigned long __stdcall WaitForSingleObject(void* hHandle, + unsigned long dwMilliseconds); +__declspec(dllimport) int __stdcall ReleaseSemaphore(void* hSemaphore, long lReleaseCount, + long* lpPreviousCount); +} +#elif defined(__MACH__) +#include +#elif defined(__unix__) +#include +#elif defined(FREERTOS) +#include +#include +#include +#endif + +namespace Common { +// Code in the spsc_sema namespace below is an adaptation of Jeff Preshing's +// portable + lightweight semaphore implementations, originally from +// https://github.com/preshing/cpp11-on-multicore/blob/master/common/sema.h +// LICENSE: +// Copyright (c) 2015 Jeff Preshing +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgement in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. +namespace spsc_sema { +#if defined(_WIN32) +class Semaphore { +private: + void* m_hSema; + + Semaphore(const Semaphore& other); + Semaphore& operator=(const Semaphore& other); + +public: + AE_NO_TSAN Semaphore(int initialCount = 0) : m_hSema() { + assert(initialCount >= 0); + const long maxLong = 0x7fffffff; + m_hSema = CreateSemaphoreW(nullptr, initialCount, maxLong, nullptr); + assert(m_hSema); + } + + AE_NO_TSAN ~Semaphore() { + CloseHandle(m_hSema); + } + + bool wait() AE_NO_TSAN { + const unsigned long infinite = 0xffffffff; + return WaitForSingleObject(m_hSema, infinite) == 0; + } + + bool try_wait() AE_NO_TSAN { + return WaitForSingleObject(m_hSema, 0) == 0; + } + + bool timed_wait(std::uint64_t usecs) AE_NO_TSAN { + return WaitForSingleObject(m_hSema, (unsigned long)(usecs / 1000)) == 0; + } + + void signal(int count = 1) AE_NO_TSAN { + while (!ReleaseSemaphore(m_hSema, count, nullptr)) + ; + } +}; +#elif defined(__MACH__) +//--------------------------------------------------------- +// Semaphore (Apple iOS and OSX) +// Can't use POSIX semaphores due to +// http://lists.apple.com/archives/darwin-kernel/2009/Apr/msg00010.html +//--------------------------------------------------------- +class Semaphore { +private: + semaphore_t m_sema; + + Semaphore(const Semaphore& other); + Semaphore& operator=(const Semaphore& other); + +public: + AE_NO_TSAN Semaphore(int initialCount = 0) : m_sema() { + assert(initialCount >= 0); + kern_return_t rc = + semaphore_create(mach_task_self(), &m_sema, SYNC_POLICY_FIFO, initialCount); + assert(rc == KERN_SUCCESS); + AE_UNUSED(rc); + } + + AE_NO_TSAN ~Semaphore() { + semaphore_destroy(mach_task_self(), m_sema); + } + + bool wait() AE_NO_TSAN { + return semaphore_wait(m_sema) == KERN_SUCCESS; + } + + bool try_wait() AE_NO_TSAN { + return timed_wait(0); + } + + bool timed_wait(std::uint64_t timeout_usecs) AE_NO_TSAN { + mach_timespec_t ts; + ts.tv_sec = static_cast(timeout_usecs / 1000000); + ts.tv_nsec = static_cast((timeout_usecs % 1000000) * 1000); + + // added in OSX 10.10: + // https://developer.apple.com/library/prerelease/mac/documentation/General/Reference/APIDiffsMacOSX10_10SeedDiff/modules/Darwin.html + kern_return_t rc = semaphore_timedwait(m_sema, ts); + return rc == KERN_SUCCESS; + } + + void signal() AE_NO_TSAN { + while (semaphore_signal(m_sema) != KERN_SUCCESS) + ; + } + + void signal(int count) AE_NO_TSAN { + while (count-- > 0) { + while (semaphore_signal(m_sema) != KERN_SUCCESS) + ; + } + } +}; +#elif defined(__unix__) +//--------------------------------------------------------- +// Semaphore (POSIX, Linux) +//--------------------------------------------------------- +class Semaphore { +private: + sem_t m_sema; + + Semaphore(const Semaphore& other); + Semaphore& operator=(const Semaphore& other); + +public: + AE_NO_TSAN Semaphore(int initialCount = 0) : m_sema() { + assert(initialCount >= 0); + int rc = sem_init(&m_sema, 0, static_cast(initialCount)); + assert(rc == 0); + AE_UNUSED(rc); + } + + AE_NO_TSAN ~Semaphore() { + sem_destroy(&m_sema); + } + + bool wait() AE_NO_TSAN { + // http://stackoverflow.com/questions/2013181/gdb-causes-sem-wait-to-fail-with-eintr-error + int rc; + do { + rc = sem_wait(&m_sema); + } while (rc == -1 && errno == EINTR); + return rc == 0; + } + + bool try_wait() AE_NO_TSAN { + int rc; + do { + rc = sem_trywait(&m_sema); + } while (rc == -1 && errno == EINTR); + return rc == 0; + } + + bool timed_wait(std::uint64_t usecs) AE_NO_TSAN { + struct timespec ts; + const int usecs_in_1_sec = 1000000; + const int nsecs_in_1_sec = 1000000000; + clock_gettime(CLOCK_REALTIME, &ts); + ts.tv_sec += static_cast(usecs / usecs_in_1_sec); + ts.tv_nsec += static_cast(usecs % usecs_in_1_sec) * 1000; + // sem_timedwait bombs if you have more than 1e9 in tv_nsec + // so we have to clean things up before passing it in + if (ts.tv_nsec >= nsecs_in_1_sec) { + ts.tv_nsec -= nsecs_in_1_sec; + ++ts.tv_sec; + } + + int rc; + do { + rc = sem_timedwait(&m_sema, &ts); + } while (rc == -1 && errno == EINTR); + return rc == 0; + } + + void signal() AE_NO_TSAN { + while (sem_post(&m_sema) == -1) + ; + } + + void signal(int count) AE_NO_TSAN { + while (count-- > 0) { + while (sem_post(&m_sema) == -1) + ; + } + } +}; +#elif defined(FREERTOS) +//--------------------------------------------------------- +// Semaphore (FreeRTOS) +//--------------------------------------------------------- +class Semaphore { +private: + SemaphoreHandle_t m_sema; + + Semaphore(const Semaphore& other); + Semaphore& operator=(const Semaphore& other); + +public: + AE_NO_TSAN Semaphore(int initialCount = 0) : m_sema() { + assert(initialCount >= 0); + m_sema = xSemaphoreCreateCounting(static_cast(~0ull), + static_cast(initialCount)); + assert(m_sema); + } + + AE_NO_TSAN ~Semaphore() { + vSemaphoreDelete(m_sema); + } + + bool wait() AE_NO_TSAN { + return xSemaphoreTake(m_sema, portMAX_DELAY) == pdTRUE; + } + + bool try_wait() AE_NO_TSAN { + // Note: In an ISR context, if this causes a task to unblock, + // the caller won't know about it + if (xPortIsInsideInterrupt()) + return xSemaphoreTakeFromISR(m_sema, NULL) == pdTRUE; + return xSemaphoreTake(m_sema, 0) == pdTRUE; + } + + bool timed_wait(std::uint64_t usecs) AE_NO_TSAN { + std::uint64_t msecs = usecs / 1000; + TickType_t ticks = static_cast(msecs / portTICK_PERIOD_MS); + if (ticks == 0) + return try_wait(); + return xSemaphoreTake(m_sema, ticks) == pdTRUE; + } + + void signal() AE_NO_TSAN { + // Note: In an ISR context, if this causes a task to unblock, + // the caller won't know about it + BaseType_t rc; + if (xPortIsInsideInterrupt()) + rc = xSemaphoreGiveFromISR(m_sema, NULL); + else + rc = xSemaphoreGive(m_sema); + assert(rc == pdTRUE); + AE_UNUSED(rc); + } + + void signal(int count) AE_NO_TSAN { + while (count-- > 0) + signal(); + } +}; +#else +#error Unsupported platform! (No semaphore wrapper available) +#endif + +//--------------------------------------------------------- +// LightweightSemaphore +//--------------------------------------------------------- +class LightweightSemaphore { +public: + typedef std::make_signed::type ssize_t; + +private: + weak_atomic m_count; + Semaphore m_sema; + + bool waitWithPartialSpinning(std::int64_t timeout_usecs = -1) AE_NO_TSAN { + ssize_t oldCount; + // Is there a better way to set the initial spin count? + // If we lower it to 1000, testBenaphore becomes 15x slower on my Core i7-5930K Windows PC, + // as threads start hitting the kernel semaphore. + int spin = 1024; + while (--spin >= 0) { + if (m_count.load() > 0) { + m_count.fetch_add_acquire(-1); + return true; + } + compiler_fence(memory_order_acquire); // Prevent the compiler from collapsing the loop. + } + oldCount = m_count.fetch_add_acquire(-1); + if (oldCount > 0) + return true; + if (timeout_usecs < 0) { + if (m_sema.wait()) + return true; + } + if (timeout_usecs > 0 && m_sema.timed_wait(static_cast(timeout_usecs))) + return true; + // At this point, we've timed out waiting for the semaphore, but the + // count is still decremented indicating we may still be waiting on + // it. So we have to re-adjust the count, but only if the semaphore + // wasn't signaled enough times for us too since then. If it was, we + // need to release the semaphore too. + while (true) { + oldCount = m_count.fetch_add_release(1); + if (oldCount < 0) + return false; // successfully restored things to the way they were + // Oh, the producer thread just signaled the semaphore after all. Try again: + oldCount = m_count.fetch_add_acquire(-1); + if (oldCount > 0 && m_sema.try_wait()) + return true; + } + } + +public: + AE_NO_TSAN LightweightSemaphore(ssize_t initialCount = 0) : m_count(initialCount), m_sema() { + assert(initialCount >= 0); + } + + bool tryWait() AE_NO_TSAN { + if (m_count.load() > 0) { + m_count.fetch_add_acquire(-1); + return true; + } + return false; + } + + bool wait() AE_NO_TSAN { + return tryWait() || waitWithPartialSpinning(); + } + + bool wait(std::int64_t timeout_usecs) AE_NO_TSAN { + return tryWait() || waitWithPartialSpinning(timeout_usecs); + } + + void signal(ssize_t count = 1) AE_NO_TSAN { + assert(count >= 0); + ssize_t oldCount = m_count.fetch_add_release(count); + assert(oldCount >= -1); + if (oldCount < 0) { + m_sema.signal(1); + } + } + + std::size_t availableApprox() const AE_NO_TSAN { + ssize_t count = m_count.load(); + return count > 0 ? static_cast(count) : 0; + } +}; +} // namespace spsc_sema +} // namespace Common + +#if defined(AE_VCPP) && (_MSC_VER < 1700 || defined(__cplusplus_cli)) +#pragma warning(pop) +#ifdef __cplusplus_cli +#pragma managed(pop) +#endif +#endif diff --git a/src/common/fixed_point.h b/src/common/fixed_point.h new file mode 100644 index 000000000..1d45e51b3 --- /dev/null +++ b/src/common/fixed_point.h @@ -0,0 +1,726 @@ +// From: https://github.com/eteran/cpp-utilities/blob/master/fixed/include/cpp-utilities/fixed.h +// See also: http://stackoverflow.com/questions/79677/whats-the-best-way-to-do-fixed-point-math +/* + * The MIT License (MIT) + * + * Copyright (c) 2015 Evan Teran + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef FIXED_H_ +#define FIXED_H_ + +#if __cplusplus >= 201402L +#define CONSTEXPR14 constexpr +#else +#define CONSTEXPR14 +#endif + +#include // for size_t +#include +#include +#include +#include + +namespace Common { + +template +class FixedPoint; + +namespace detail { + +// helper templates to make magic with types :) +// these allow us to determine resonable types from +// a desired size, they also let us infer the next largest type +// from a type which is nice for the division op +template +struct type_from_size { + using value_type = void; + using unsigned_type = void; + using signed_type = void; + static constexpr bool is_specialized = false; +}; + +#if defined(__GNUC__) && defined(__x86_64__) && !defined(__STRICT_ANSI__) +template <> +struct type_from_size<128> { + static constexpr bool is_specialized = true; + static constexpr size_t size = 128; + + using value_type = __int128; + using unsigned_type = unsigned __int128; + using signed_type = __int128; + using next_size = type_from_size<256>; +}; +#endif + +template <> +struct type_from_size<64> { + static constexpr bool is_specialized = true; + static constexpr size_t size = 64; + + using value_type = int64_t; + using unsigned_type = std::make_unsigned::type; + using signed_type = std::make_signed::type; + using next_size = type_from_size<128>; +}; + +template <> +struct type_from_size<32> { + static constexpr bool is_specialized = true; + static constexpr size_t size = 32; + + using value_type = int32_t; + using unsigned_type = std::make_unsigned::type; + using signed_type = std::make_signed::type; + using next_size = type_from_size<64>; +}; + +template <> +struct type_from_size<16> { + static constexpr bool is_specialized = true; + static constexpr size_t size = 16; + + using value_type = int16_t; + using unsigned_type = std::make_unsigned::type; + using signed_type = std::make_signed::type; + using next_size = type_from_size<32>; +}; + +template <> +struct type_from_size<8> { + static constexpr bool is_specialized = true; + static constexpr size_t size = 8; + + using value_type = int8_t; + using unsigned_type = std::make_unsigned::type; + using signed_type = std::make_signed::type; + using next_size = type_from_size<16>; +}; + +// this is to assist in adding support for non-native base +// types (for adding big-int support), this should be fine +// unless your bit-int class doesn't nicely support casting +template +constexpr B next_to_base(N rhs) { + return static_cast(rhs); +} + +struct divide_by_zero : std::exception {}; + +template +CONSTEXPR14 FixedPoint divide( + FixedPoint numerator, FixedPoint denominator, FixedPoint& remainder, + typename std::enable_if::next_size::is_specialized>::type* = nullptr) { + + using next_type = typename FixedPoint::next_type; + using base_type = typename FixedPoint::base_type; + constexpr size_t fractional_bits = FixedPoint::fractional_bits; + + next_type t(numerator.to_raw()); + t <<= fractional_bits; + + FixedPoint quotient; + + quotient = FixedPoint::from_base(next_to_base(t / denominator.to_raw())); + remainder = FixedPoint::from_base(next_to_base(t % denominator.to_raw())); + + return quotient; +} + +template +CONSTEXPR14 FixedPoint divide( + FixedPoint numerator, FixedPoint denominator, FixedPoint& remainder, + typename std::enable_if::next_size::is_specialized>::type* = nullptr) { + + using unsigned_type = typename FixedPoint::unsigned_type; + + constexpr int bits = FixedPoint::total_bits; + + if (denominator == 0) { + throw divide_by_zero(); + } else { + + int sign = 0; + + FixedPoint quotient; + + if (numerator < 0) { + sign ^= 1; + numerator = -numerator; + } + + if (denominator < 0) { + sign ^= 1; + denominator = -denominator; + } + + unsigned_type n = numerator.to_raw(); + unsigned_type d = denominator.to_raw(); + unsigned_type x = 1; + unsigned_type answer = 0; + + // egyptian division algorithm + while ((n >= d) && (((d >> (bits - 1)) & 1) == 0)) { + x <<= 1; + d <<= 1; + } + + while (x != 0) { + if (n >= d) { + n -= d; + answer += x; + } + + x >>= 1; + d >>= 1; + } + + unsigned_type l1 = n; + unsigned_type l2 = denominator.to_raw(); + + // calculate the lower bits (needs to be unsigned) + while (l1 >> (bits - F) > 0) { + l1 >>= 1; + l2 >>= 1; + } + const unsigned_type lo = (l1 << F) / l2; + + quotient = FixedPoint::from_base((answer << F) | lo); + remainder = n; + + if (sign) { + quotient = -quotient; + } + + return quotient; + } +} + +// this is the usual implementation of multiplication +template +CONSTEXPR14 FixedPoint multiply( + FixedPoint lhs, FixedPoint rhs, + typename std::enable_if::next_size::is_specialized>::type* = nullptr) { + + using next_type = typename FixedPoint::next_type; + using base_type = typename FixedPoint::base_type; + + constexpr size_t fractional_bits = FixedPoint::fractional_bits; + + next_type t(static_cast(lhs.to_raw()) * static_cast(rhs.to_raw())); + t >>= fractional_bits; + + return FixedPoint::from_base(next_to_base(t)); +} + +// this is the fall back version we use when we don't have a next size +// it is slightly slower, but is more robust since it doesn't +// require and upgraded type +template +CONSTEXPR14 FixedPoint multiply( + FixedPoint lhs, FixedPoint rhs, + typename std::enable_if::next_size::is_specialized>::type* = nullptr) { + + using base_type = typename FixedPoint::base_type; + + constexpr size_t fractional_bits = FixedPoint::fractional_bits; + constexpr base_type integer_mask = FixedPoint::integer_mask; + constexpr base_type fractional_mask = FixedPoint::fractional_mask; + + // more costly but doesn't need a larger type + const base_type a_hi = (lhs.to_raw() & integer_mask) >> fractional_bits; + const base_type b_hi = (rhs.to_raw() & integer_mask) >> fractional_bits; + const base_type a_lo = (lhs.to_raw() & fractional_mask); + const base_type b_lo = (rhs.to_raw() & fractional_mask); + + const base_type x1 = a_hi * b_hi; + const base_type x2 = a_hi * b_lo; + const base_type x3 = a_lo * b_hi; + const base_type x4 = a_lo * b_lo; + + return FixedPoint::from_base((x1 << fractional_bits) + (x3 + x2) + + (x4 >> fractional_bits)); +} +} // namespace detail + +template +class FixedPoint { + static_assert(detail::type_from_size::is_specialized, "invalid combination of sizes"); + +public: + static constexpr size_t fractional_bits = F; + static constexpr size_t integer_bits = I; + static constexpr size_t total_bits = I + F; + + using base_type_info = detail::type_from_size; + + using base_type = typename base_type_info::value_type; + using next_type = typename base_type_info::next_size::value_type; + using unsigned_type = typename base_type_info::unsigned_type; + +public: +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Woverflow" +#endif + static constexpr base_type fractional_mask = + ~(static_cast(~base_type(0)) << fractional_bits); + static constexpr base_type integer_mask = ~fractional_mask; +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + +public: + static constexpr base_type one = base_type(1) << fractional_bits; + +public: // constructors + FixedPoint() = default; + FixedPoint(const FixedPoint&) = default; + FixedPoint(FixedPoint&&) = default; + FixedPoint& operator=(const FixedPoint&) = default; + + template + constexpr FixedPoint( + Number n, typename std::enable_if::value>::type* = nullptr) + : data_(static_cast(n * one)) {} + +public: // conversion + template + CONSTEXPR14 explicit FixedPoint(FixedPoint other) { + static_assert(I2 <= I && F2 <= F, "Scaling conversion can only upgrade types"); + using T = FixedPoint; + + const base_type fractional = (other.data_ & T::fractional_mask); + const base_type integer = (other.data_ & T::integer_mask) >> T::fractional_bits; + data_ = + (integer << fractional_bits) | (fractional << (fractional_bits - T::fractional_bits)); + } + +private: + // this makes it simpler to create a FixedPoint point object from + // a native type without scaling + // use "FixedPoint::from_base" in order to perform this. + struct NoScale {}; + + constexpr FixedPoint(base_type n, const NoScale&) : data_(n) {} + +public: + static constexpr FixedPoint from_base(base_type n) { + return FixedPoint(n, NoScale()); + } + +public: // comparison operators + constexpr bool operator==(FixedPoint rhs) const { + return data_ == rhs.data_; + } + + constexpr bool operator!=(FixedPoint rhs) const { + return data_ != rhs.data_; + } + + constexpr bool operator<(FixedPoint rhs) const { + return data_ < rhs.data_; + } + + constexpr bool operator>(FixedPoint rhs) const { + return data_ > rhs.data_; + } + + constexpr bool operator<=(FixedPoint rhs) const { + return data_ <= rhs.data_; + } + + constexpr bool operator>=(FixedPoint rhs) const { + return data_ >= rhs.data_; + } + +public: // unary operators + constexpr bool operator!() const { + return !data_; + } + + constexpr FixedPoint operator~() const { + // NOTE(eteran): this will often appear to "just negate" the value + // that is not an error, it is because -x == (~x+1) + // and that "+1" is adding an infinitesimally small fraction to the + // complimented value + return FixedPoint::from_base(~data_); + } + + constexpr FixedPoint operator-() const { + return FixedPoint::from_base(-data_); + } + + constexpr FixedPoint operator+() const { + return FixedPoint::from_base(+data_); + } + + CONSTEXPR14 FixedPoint& operator++() { + data_ += one; + return *this; + } + + CONSTEXPR14 FixedPoint& operator--() { + data_ -= one; + return *this; + } + + CONSTEXPR14 FixedPoint operator++(int) { + FixedPoint tmp(*this); + data_ += one; + return tmp; + } + + CONSTEXPR14 FixedPoint operator--(int) { + FixedPoint tmp(*this); + data_ -= one; + return tmp; + } + +public: // basic math operators + CONSTEXPR14 FixedPoint& operator+=(FixedPoint n) { + data_ += n.data_; + return *this; + } + + CONSTEXPR14 FixedPoint& operator-=(FixedPoint n) { + data_ -= n.data_; + return *this; + } + + CONSTEXPR14 FixedPoint& operator*=(FixedPoint n) { + return assign(detail::multiply(*this, n)); + } + + CONSTEXPR14 FixedPoint& operator/=(FixedPoint n) { + FixedPoint temp; + return assign(detail::divide(*this, n, temp)); + } + +private: + CONSTEXPR14 FixedPoint& assign(FixedPoint rhs) { + data_ = rhs.data_; + return *this; + } + +public: // binary math operators, effects underlying bit pattern since these + // don't really typically make sense for non-integer values + CONSTEXPR14 FixedPoint& operator&=(FixedPoint n) { + data_ &= n.data_; + return *this; + } + + CONSTEXPR14 FixedPoint& operator|=(FixedPoint n) { + data_ |= n.data_; + return *this; + } + + CONSTEXPR14 FixedPoint& operator^=(FixedPoint n) { + data_ ^= n.data_; + return *this; + } + + template ::value>::type> + CONSTEXPR14 FixedPoint& operator>>=(Integer n) { + data_ >>= n; + return *this; + } + + template ::value>::type> + CONSTEXPR14 FixedPoint& operator<<=(Integer n) { + data_ <<= n; + return *this; + } + +public: // conversion to basic types + constexpr void round_up() { + data_ += (data_ & fractional_mask) >> 1; + } + + constexpr int to_int() { + round_up(); + return static_cast((data_ & integer_mask) >> fractional_bits); + } + + constexpr unsigned int to_uint() const { + round_up(); + return static_cast((data_ & integer_mask) >> fractional_bits); + } + + constexpr int64_t to_long() { + round_up(); + return static_cast((data_ & integer_mask) >> fractional_bits); + } + + constexpr int to_int_floor() const { + return static_cast((data_ & integer_mask) >> fractional_bits); + } + + constexpr int64_t to_long_floor() { + return static_cast((data_ & integer_mask) >> fractional_bits); + } + + constexpr unsigned int to_uint_floor() const { + return static_cast((data_ & integer_mask) >> fractional_bits); + } + + constexpr float to_float() const { + return static_cast(data_) / FixedPoint::one; + } + + constexpr double to_double() const { + return static_cast(data_) / FixedPoint::one; + } + + constexpr base_type to_raw() const { + return data_; + } + + constexpr void clear_int() { + data_ &= fractional_mask; + } + + constexpr base_type get_frac() const { + return data_ & fractional_mask; + } + +public: + CONSTEXPR14 void swap(FixedPoint& rhs) { + using std::swap; + swap(data_, rhs.data_); + } + +public: + base_type data_; +}; + +// if we have the same fractional portion, but differing integer portions, we trivially upgrade the +// smaller type +template +CONSTEXPR14 typename std::conditional= I2, FixedPoint, FixedPoint>::type +operator+(FixedPoint lhs, FixedPoint rhs) { + + using T = typename std::conditional= I2, FixedPoint, FixedPoint>::type; + + const T l = T::from_base(lhs.to_raw()); + const T r = T::from_base(rhs.to_raw()); + return l + r; +} + +template +CONSTEXPR14 typename std::conditional= I2, FixedPoint, FixedPoint>::type +operator-(FixedPoint lhs, FixedPoint rhs) { + + using T = typename std::conditional= I2, FixedPoint, FixedPoint>::type; + + const T l = T::from_base(lhs.to_raw()); + const T r = T::from_base(rhs.to_raw()); + return l - r; +} + +template +CONSTEXPR14 typename std::conditional= I2, FixedPoint, FixedPoint>::type +operator*(FixedPoint lhs, FixedPoint rhs) { + + using T = typename std::conditional= I2, FixedPoint, FixedPoint>::type; + + const T l = T::from_base(lhs.to_raw()); + const T r = T::from_base(rhs.to_raw()); + return l * r; +} + +template +CONSTEXPR14 typename std::conditional= I2, FixedPoint, FixedPoint>::type +operator/(FixedPoint lhs, FixedPoint rhs) { + + using T = typename std::conditional= I2, FixedPoint, FixedPoint>::type; + + const T l = T::from_base(lhs.to_raw()); + const T r = T::from_base(rhs.to_raw()); + return l / r; +} + +template +std::ostream& operator<<(std::ostream& os, FixedPoint f) { + os << f.to_double(); + return os; +} + +// basic math operators +template +CONSTEXPR14 FixedPoint operator+(FixedPoint lhs, FixedPoint rhs) { + lhs += rhs; + return lhs; +} +template +CONSTEXPR14 FixedPoint operator-(FixedPoint lhs, FixedPoint rhs) { + lhs -= rhs; + return lhs; +} +template +CONSTEXPR14 FixedPoint operator*(FixedPoint lhs, FixedPoint rhs) { + lhs *= rhs; + return lhs; +} +template +CONSTEXPR14 FixedPoint operator/(FixedPoint lhs, FixedPoint rhs) { + lhs /= rhs; + return lhs; +} + +template ::value>::type> +CONSTEXPR14 FixedPoint operator+(FixedPoint lhs, Number rhs) { + lhs += FixedPoint(rhs); + return lhs; +} +template ::value>::type> +CONSTEXPR14 FixedPoint operator-(FixedPoint lhs, Number rhs) { + lhs -= FixedPoint(rhs); + return lhs; +} +template ::value>::type> +CONSTEXPR14 FixedPoint operator*(FixedPoint lhs, Number rhs) { + lhs *= FixedPoint(rhs); + return lhs; +} +template ::value>::type> +CONSTEXPR14 FixedPoint operator/(FixedPoint lhs, Number rhs) { + lhs /= FixedPoint(rhs); + return lhs; +} + +template ::value>::type> +CONSTEXPR14 FixedPoint operator+(Number lhs, FixedPoint rhs) { + FixedPoint tmp(lhs); + tmp += rhs; + return tmp; +} +template ::value>::type> +CONSTEXPR14 FixedPoint operator-(Number lhs, FixedPoint rhs) { + FixedPoint tmp(lhs); + tmp -= rhs; + return tmp; +} +template ::value>::type> +CONSTEXPR14 FixedPoint operator*(Number lhs, FixedPoint rhs) { + FixedPoint tmp(lhs); + tmp *= rhs; + return tmp; +} +template ::value>::type> +CONSTEXPR14 FixedPoint operator/(Number lhs, FixedPoint rhs) { + FixedPoint tmp(lhs); + tmp /= rhs; + return tmp; +} + +// shift operators +template ::value>::type> +CONSTEXPR14 FixedPoint operator<<(FixedPoint lhs, Integer rhs) { + lhs <<= rhs; + return lhs; +} +template ::value>::type> +CONSTEXPR14 FixedPoint operator>>(FixedPoint lhs, Integer rhs) { + lhs >>= rhs; + return lhs; +} + +// comparison operators +template ::value>::type> +constexpr bool operator>(FixedPoint lhs, Number rhs) { + return lhs > FixedPoint(rhs); +} +template ::value>::type> +constexpr bool operator<(FixedPoint lhs, Number rhs) { + return lhs < FixedPoint(rhs); +} +template ::value>::type> +constexpr bool operator>=(FixedPoint lhs, Number rhs) { + return lhs >= FixedPoint(rhs); +} +template ::value>::type> +constexpr bool operator<=(FixedPoint lhs, Number rhs) { + return lhs <= FixedPoint(rhs); +} +template ::value>::type> +constexpr bool operator==(FixedPoint lhs, Number rhs) { + return lhs == FixedPoint(rhs); +} +template ::value>::type> +constexpr bool operator!=(FixedPoint lhs, Number rhs) { + return lhs != FixedPoint(rhs); +} + +template ::value>::type> +constexpr bool operator>(Number lhs, FixedPoint rhs) { + return FixedPoint(lhs) > rhs; +} +template ::value>::type> +constexpr bool operator<(Number lhs, FixedPoint rhs) { + return FixedPoint(lhs) < rhs; +} +template ::value>::type> +constexpr bool operator>=(Number lhs, FixedPoint rhs) { + return FixedPoint(lhs) >= rhs; +} +template ::value>::type> +constexpr bool operator<=(Number lhs, FixedPoint rhs) { + return FixedPoint(lhs) <= rhs; +} +template ::value>::type> +constexpr bool operator==(Number lhs, FixedPoint rhs) { + return FixedPoint(lhs) == rhs; +} +template ::value>::type> +constexpr bool operator!=(Number lhs, FixedPoint rhs) { + return FixedPoint(lhs) != rhs; +} + +} // namespace Common + +#undef CONSTEXPR14 + +#endif diff --git a/src/common/reader_writer_queue.h b/src/common/reader_writer_queue.h new file mode 100644 index 000000000..8d2c9408c --- /dev/null +++ b/src/common/reader_writer_queue.h @@ -0,0 +1,941 @@ +// ©2013-2020 Cameron Desrochers. +// Distributed under the simplified BSD license (see the license file that +// should have come with this header). + +#pragma once + +#include +#include +#include // For malloc/free/abort & size_t +#include +#include +#include +#include +#include + +#include "common/atomic_helpers.h" + +#if __cplusplus > 199711L || _MSC_VER >= 1700 // C++11 or VS2012 +#include +#endif + +// A lock-free queue for a single-consumer, single-producer architecture. +// The queue is also wait-free in the common path (except if more memory +// needs to be allocated, in which case malloc is called). +// Allocates memory sparingly, and only once if the original maximum size +// estimate is never exceeded. +// Tested on x86/x64 processors, but semantics should be correct for all +// architectures (given the right implementations in atomicops.h), provided +// that aligned integer and pointer accesses are naturally atomic. +// Note that there should only be one consumer thread and producer thread; +// Switching roles of the threads, or using multiple consecutive threads for +// one role, is not safe unless properly synchronized. +// Using the queue exclusively from one thread is fine, though a bit silly. + +#ifndef MOODYCAMEL_CACHE_LINE_SIZE +#define MOODYCAMEL_CACHE_LINE_SIZE 64 +#endif + +#ifndef MOODYCAMEL_EXCEPTIONS_ENABLED +#if (defined(_MSC_VER) && defined(_CPPUNWIND)) || (defined(__GNUC__) && defined(__EXCEPTIONS)) || \ + (!defined(_MSC_VER) && !defined(__GNUC__)) +#define MOODYCAMEL_EXCEPTIONS_ENABLED +#endif +#endif + +#ifndef MOODYCAMEL_HAS_EMPLACE +#if !defined(_MSC_VER) || \ + _MSC_VER >= 1800 // variadic templates: either a non-MS compiler or VS >= 2013 +#define MOODYCAMEL_HAS_EMPLACE 1 +#endif +#endif + +#ifndef MOODYCAMEL_MAYBE_ALIGN_TO_CACHELINE +#if defined(__APPLE__) && defined(__MACH__) && __cplusplus >= 201703L +// This is required to find out what deployment target we are using +#include +#if !defined(MAC_OS_X_VERSION_MIN_REQUIRED) || \ + MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_14 +// C++17 new(size_t, align_val_t) is not backwards-compatible with older versions of macOS, so we +// can't support over-alignment in this case +#define MOODYCAMEL_MAYBE_ALIGN_TO_CACHELINE +#endif +#endif +#endif + +#ifndef MOODYCAMEL_MAYBE_ALIGN_TO_CACHELINE +#define MOODYCAMEL_MAYBE_ALIGN_TO_CACHELINE AE_ALIGN(MOODYCAMEL_CACHE_LINE_SIZE) +#endif + +#ifdef AE_VCPP +#pragma warning(push) +#pragma warning(disable : 4324) // structure was padded due to __declspec(align()) +#pragma warning(disable : 4820) // padding was added +#pragma warning(disable : 4127) // conditional expression is constant +#endif + +namespace Common { + +template +class MOODYCAMEL_MAYBE_ALIGN_TO_CACHELINE ReaderWriterQueue { + // Design: Based on a queue-of-queues. The low-level queues are just + // circular buffers with front and tail indices indicating where the + // next element to dequeue is and where the next element can be enqueued, + // respectively. Each low-level queue is called a "block". Each block + // wastes exactly one element's worth of space to keep the design simple + // (if front == tail then the queue is empty, and can't be full). + // The high-level queue is a circular linked list of blocks; again there + // is a front and tail, but this time they are pointers to the blocks. + // The front block is where the next element to be dequeued is, provided + // the block is not empty. The back block is where elements are to be + // enqueued, provided the block is not full. + // The producer thread owns all the tail indices/pointers. The consumer + // thread owns all the front indices/pointers. Both threads read each + // other's variables, but only the owning thread updates them. E.g. After + // the consumer reads the producer's tail, the tail may change before the + // consumer is done dequeuing an object, but the consumer knows the tail + // will never go backwards, only forwards. + // If there is no room to enqueue an object, an additional block (of + // equal size to the last block) is added. Blocks are never removed. + +public: + typedef T value_type; + + // Constructs a queue that can hold at least `size` elements without further + // allocations. If more than MAX_BLOCK_SIZE elements are requested, + // then several blocks of MAX_BLOCK_SIZE each are reserved (including + // at least one extra buffer block). + AE_NO_TSAN explicit ReaderWriterQueue(size_t size = 15) +#ifndef NDEBUG + : enqueuing(false), dequeuing(false) +#endif + { + assert(MAX_BLOCK_SIZE == ceilToPow2(MAX_BLOCK_SIZE) && + "MAX_BLOCK_SIZE must be a power of 2"); + assert(MAX_BLOCK_SIZE >= 2 && "MAX_BLOCK_SIZE must be at least 2"); + + Block* firstBlock = nullptr; + + largestBlockSize = + ceilToPow2(size + 1); // We need a spare slot to fit size elements in the block + if (largestBlockSize > MAX_BLOCK_SIZE * 2) { + // We need a spare block in case the producer is writing to a different block the + // consumer is reading from, and wants to enqueue the maximum number of elements. We + // also need a spare element in each block to avoid the ambiguity between front == tail + // meaning "empty" and "full". So the effective number of slots that are guaranteed to + // be usable at any time is the block size - 1 times the number of blocks - 1. Solving + // for size and applying a ceiling to the division gives us (after simplifying): + size_t initialBlockCount = (size + MAX_BLOCK_SIZE * 2 - 3) / (MAX_BLOCK_SIZE - 1); + largestBlockSize = MAX_BLOCK_SIZE; + Block* lastBlock = nullptr; + for (size_t i = 0; i != initialBlockCount; ++i) { + auto block = make_block(largestBlockSize); + if (block == nullptr) { +#ifdef MOODYCAMEL_EXCEPTIONS_ENABLED + throw std::bad_alloc(); +#else + abort(); +#endif + } + if (firstBlock == nullptr) { + firstBlock = block; + } else { + lastBlock->next = block; + } + lastBlock = block; + block->next = firstBlock; + } + } else { + firstBlock = make_block(largestBlockSize); + if (firstBlock == nullptr) { +#ifdef MOODYCAMEL_EXCEPTIONS_ENABLED + throw std::bad_alloc(); +#else + abort(); +#endif + } + firstBlock->next = firstBlock; + } + frontBlock = firstBlock; + tailBlock = firstBlock; + + // Make sure the reader/writer threads will have the initialized memory setup above: + fence(memory_order_sync); + } + + // Note: The queue should not be accessed concurrently while it's + // being moved. It's up to the user to synchronize this. + AE_NO_TSAN ReaderWriterQueue(ReaderWriterQueue&& other) + : frontBlock(other.frontBlock.load()), tailBlock(other.tailBlock.load()), + largestBlockSize(other.largestBlockSize) +#ifndef NDEBUG + , + enqueuing(false), dequeuing(false) +#endif + { + other.largestBlockSize = 32; + Block* b = other.make_block(other.largestBlockSize); + if (b == nullptr) { +#ifdef MOODYCAMEL_EXCEPTIONS_ENABLED + throw std::bad_alloc(); +#else + abort(); +#endif + } + b->next = b; + other.frontBlock = b; + other.tailBlock = b; + } + + // Note: The queue should not be accessed concurrently while it's + // being moved. It's up to the user to synchronize this. + ReaderWriterQueue& operator=(ReaderWriterQueue&& other) AE_NO_TSAN { + Block* b = frontBlock.load(); + frontBlock = other.frontBlock.load(); + other.frontBlock = b; + b = tailBlock.load(); + tailBlock = other.tailBlock.load(); + other.tailBlock = b; + std::swap(largestBlockSize, other.largestBlockSize); + return *this; + } + + // Note: The queue should not be accessed concurrently while it's + // being deleted. It's up to the user to synchronize this. + AE_NO_TSAN ~ReaderWriterQueue() { + // Make sure we get the latest version of all variables from other CPUs: + fence(memory_order_sync); + + // Destroy any remaining objects in queue and free memory + Block* frontBlock_ = frontBlock; + Block* block = frontBlock_; + do { + Block* nextBlock = block->next; + size_t blockFront = block->front; + size_t blockTail = block->tail; + + for (size_t i = blockFront; i != blockTail; i = (i + 1) & block->sizeMask) { + auto element = reinterpret_cast(block->data + i * sizeof(T)); + element->~T(); + (void)element; + } + + auto rawBlock = block->rawThis; + block->~Block(); + std::free(rawBlock); + block = nextBlock; + } while (block != frontBlock_); + } + + // Enqueues a copy of element if there is room in the queue. + // Returns true if the element was enqueued, false otherwise. + // Does not allocate memory. + AE_FORCEINLINE bool try_enqueue(T const& element) AE_NO_TSAN { + return inner_enqueue(element); + } + + // Enqueues a moved copy of element if there is room in the queue. + // Returns true if the element was enqueued, false otherwise. + // Does not allocate memory. + AE_FORCEINLINE bool try_enqueue(T&& element) AE_NO_TSAN { + return inner_enqueue(std::forward(element)); + } + +#if MOODYCAMEL_HAS_EMPLACE + // Like try_enqueue() but with emplace semantics (i.e. construct-in-place). + template + AE_FORCEINLINE bool try_emplace(Args&&... args) AE_NO_TSAN { + return inner_enqueue(std::forward(args)...); + } +#endif + + // Enqueues a copy of element on the queue. + // Allocates an additional block of memory if needed. + // Only fails (returns false) if memory allocation fails. + AE_FORCEINLINE bool enqueue(T const& element) AE_NO_TSAN { + return inner_enqueue(element); + } + + // Enqueues a moved copy of element on the queue. + // Allocates an additional block of memory if needed. + // Only fails (returns false) if memory allocation fails. + AE_FORCEINLINE bool enqueue(T&& element) AE_NO_TSAN { + return inner_enqueue(std::forward(element)); + } + +#if MOODYCAMEL_HAS_EMPLACE + // Like enqueue() but with emplace semantics (i.e. construct-in-place). + template + AE_FORCEINLINE bool emplace(Args&&... args) AE_NO_TSAN { + return inner_enqueue(std::forward(args)...); + } +#endif + + // Attempts to dequeue an element; if the queue is empty, + // returns false instead. If the queue has at least one element, + // moves front to result using operator=, then returns true. + template + bool try_dequeue(U& result) AE_NO_TSAN { +#ifndef NDEBUG + ReentrantGuard guard(this->dequeuing); +#endif + + // High-level pseudocode: + // Remember where the tail block is + // If the front block has an element in it, dequeue it + // Else + // If front block was the tail block when we entered the function, return false + // Else advance to next block and dequeue the item there + + // Note that we have to use the value of the tail block from before we check if the front + // block is full or not, in case the front block is empty and then, before we check if the + // tail block is at the front block or not, the producer fills up the front block *and + // moves on*, which would make us skip a filled block. Seems unlikely, but was consistently + // reproducible in practice. + // In order to avoid overhead in the common case, though, we do a double-checked pattern + // where we have the fast path if the front block is not empty, then read the tail block, + // then re-read the front block and check if it's not empty again, then check if the tail + // block has advanced. + + Block* frontBlock_ = frontBlock.load(); + size_t blockTail = frontBlock_->localTail; + size_t blockFront = frontBlock_->front.load(); + + if (blockFront != blockTail || + blockFront != (frontBlock_->localTail = frontBlock_->tail.load())) { + fence(memory_order_acquire); + + non_empty_front_block: + // Front block not empty, dequeue from here + auto element = reinterpret_cast(frontBlock_->data + blockFront * sizeof(T)); + result = std::move(*element); + element->~T(); + + blockFront = (blockFront + 1) & frontBlock_->sizeMask; + + fence(memory_order_release); + frontBlock_->front = blockFront; + } else if (frontBlock_ != tailBlock.load()) { + fence(memory_order_acquire); + + frontBlock_ = frontBlock.load(); + blockTail = frontBlock_->localTail = frontBlock_->tail.load(); + blockFront = frontBlock_->front.load(); + fence(memory_order_acquire); + + if (blockFront != blockTail) { + // Oh look, the front block isn't empty after all + goto non_empty_front_block; + } + + // Front block is empty but there's another block ahead, advance to it + Block* nextBlock = frontBlock_->next; + // Don't need an acquire fence here since next can only ever be set on the tailBlock, + // and we're not the tailBlock, and we did an acquire earlier after reading tailBlock + // which ensures next is up-to-date on this CPU in case we recently were at tailBlock. + + size_t nextBlockFront = nextBlock->front.load(); + size_t nextBlockTail = nextBlock->localTail = nextBlock->tail.load(); + fence(memory_order_acquire); + + // Since the tailBlock is only ever advanced after being written to, + // we know there's for sure an element to dequeue on it + assert(nextBlockFront != nextBlockTail); + AE_UNUSED(nextBlockTail); + + // We're done with this block, let the producer use it if it needs + fence(memory_order_release); // Expose possibly pending changes to frontBlock->front + // from last dequeue + frontBlock = frontBlock_ = nextBlock; + + compiler_fence(memory_order_release); // Not strictly needed + + auto element = reinterpret_cast(frontBlock_->data + nextBlockFront * sizeof(T)); + + result = std::move(*element); + element->~T(); + + nextBlockFront = (nextBlockFront + 1) & frontBlock_->sizeMask; + + fence(memory_order_release); + frontBlock_->front = nextBlockFront; + } else { + // No elements in current block and no other block to advance to + return false; + } + + return true; + } + + // Returns a pointer to the front element in the queue (the one that + // would be removed next by a call to `try_dequeue` or `pop`). If the + // queue appears empty at the time the method is called, nullptr is + // returned instead. + // Must be called only from the consumer thread. + T* peek() const AE_NO_TSAN { +#ifndef NDEBUG + ReentrantGuard guard(this->dequeuing); +#endif + // See try_dequeue() for reasoning + + Block* frontBlock_ = frontBlock.load(); + size_t blockTail = frontBlock_->localTail; + size_t blockFront = frontBlock_->front.load(); + + if (blockFront != blockTail || + blockFront != (frontBlock_->localTail = frontBlock_->tail.load())) { + fence(memory_order_acquire); + non_empty_front_block: + return reinterpret_cast(frontBlock_->data + blockFront * sizeof(T)); + } else if (frontBlock_ != tailBlock.load()) { + fence(memory_order_acquire); + frontBlock_ = frontBlock.load(); + blockTail = frontBlock_->localTail = frontBlock_->tail.load(); + blockFront = frontBlock_->front.load(); + fence(memory_order_acquire); + + if (blockFront != blockTail) { + goto non_empty_front_block; + } + + Block* nextBlock = frontBlock_->next; + + size_t nextBlockFront = nextBlock->front.load(); + fence(memory_order_acquire); + + assert(nextBlockFront != nextBlock->tail.load()); + return reinterpret_cast(nextBlock->data + nextBlockFront * sizeof(T)); + } + + return nullptr; + } + + // Removes the front element from the queue, if any, without returning it. + // Returns true on success, or false if the queue appeared empty at the time + // `pop` was called. + bool pop() AE_NO_TSAN { +#ifndef NDEBUG + ReentrantGuard guard(this->dequeuing); +#endif + // See try_dequeue() for reasoning + + Block* frontBlock_ = frontBlock.load(); + size_t blockTail = frontBlock_->localTail; + size_t blockFront = frontBlock_->front.load(); + + if (blockFront != blockTail || + blockFront != (frontBlock_->localTail = frontBlock_->tail.load())) { + fence(memory_order_acquire); + + non_empty_front_block: + auto element = reinterpret_cast(frontBlock_->data + blockFront * sizeof(T)); + element->~T(); + + blockFront = (blockFront + 1) & frontBlock_->sizeMask; + + fence(memory_order_release); + frontBlock_->front = blockFront; + } else if (frontBlock_ != tailBlock.load()) { + fence(memory_order_acquire); + frontBlock_ = frontBlock.load(); + blockTail = frontBlock_->localTail = frontBlock_->tail.load(); + blockFront = frontBlock_->front.load(); + fence(memory_order_acquire); + + if (blockFront != blockTail) { + goto non_empty_front_block; + } + + // Front block is empty but there's another block ahead, advance to it + Block* nextBlock = frontBlock_->next; + + size_t nextBlockFront = nextBlock->front.load(); + size_t nextBlockTail = nextBlock->localTail = nextBlock->tail.load(); + fence(memory_order_acquire); + + assert(nextBlockFront != nextBlockTail); + AE_UNUSED(nextBlockTail); + + fence(memory_order_release); + frontBlock = frontBlock_ = nextBlock; + + compiler_fence(memory_order_release); + + auto element = reinterpret_cast(frontBlock_->data + nextBlockFront * sizeof(T)); + element->~T(); + + nextBlockFront = (nextBlockFront + 1) & frontBlock_->sizeMask; + + fence(memory_order_release); + frontBlock_->front = nextBlockFront; + } else { + // No elements in current block and no other block to advance to + return false; + } + + return true; + } + + // Returns the approximate number of items currently in the queue. + // Safe to call from both the producer and consumer threads. + inline size_t size_approx() const AE_NO_TSAN { + size_t result = 0; + Block* frontBlock_ = frontBlock.load(); + Block* block = frontBlock_; + do { + fence(memory_order_acquire); + size_t blockFront = block->front.load(); + size_t blockTail = block->tail.load(); + result += (blockTail - blockFront) & block->sizeMask; + block = block->next.load(); + } while (block != frontBlock_); + return result; + } + + // Returns the total number of items that could be enqueued without incurring + // an allocation when this queue is empty. + // Safe to call from both the producer and consumer threads. + // + // NOTE: The actual capacity during usage may be different depending on the consumer. + // If the consumer is removing elements concurrently, the producer cannot add to + // the block the consumer is removing from until it's completely empty, except in + // the case where the producer was writing to the same block the consumer was + // reading from the whole time. + inline size_t max_capacity() const { + size_t result = 0; + Block* frontBlock_ = frontBlock.load(); + Block* block = frontBlock_; + do { + fence(memory_order_acquire); + result += block->sizeMask; + block = block->next.load(); + } while (block != frontBlock_); + return result; + } + +private: + enum AllocationMode { CanAlloc, CannotAlloc }; + +#if MOODYCAMEL_HAS_EMPLACE + template + bool inner_enqueue(Args&&... args) AE_NO_TSAN +#else + template + bool inner_enqueue(U&& element) AE_NO_TSAN +#endif + { +#ifndef NDEBUG + ReentrantGuard guard(this->enqueuing); +#endif + + // High-level pseudocode (assuming we're allowed to alloc a new block): + // If room in tail block, add to tail + // Else check next block + // If next block is not the head block, enqueue on next block + // Else create a new block and enqueue there + // Advance tail to the block we just enqueued to + + Block* tailBlock_ = tailBlock.load(); + size_t blockFront = tailBlock_->localFront; + size_t blockTail = tailBlock_->tail.load(); + + size_t nextBlockTail = (blockTail + 1) & tailBlock_->sizeMask; + if (nextBlockTail != blockFront || + nextBlockTail != (tailBlock_->localFront = tailBlock_->front.load())) { + fence(memory_order_acquire); + // This block has room for at least one more element + char* location = tailBlock_->data + blockTail * sizeof(T); +#if MOODYCAMEL_HAS_EMPLACE + new (location) T(std::forward(args)...); +#else + new (location) T(std::forward(element)); +#endif + + fence(memory_order_release); + tailBlock_->tail = nextBlockTail; + } else { + fence(memory_order_acquire); + if (tailBlock_->next.load() != frontBlock) { + // Note that the reason we can't advance to the frontBlock and start adding new + // entries there is because if we did, then dequeue would stay in that block, + // eventually reading the new values, instead of advancing to the next full block + // (whose values were enqueued first and so should be consumed first). + + fence(memory_order_acquire); // Ensure we get latest writes if we got the latest + // frontBlock + + // tailBlock is full, but there's a free block ahead, use it + Block* tailBlockNext = tailBlock_->next.load(); + size_t nextBlockFront = tailBlockNext->localFront = tailBlockNext->front.load(); + nextBlockTail = tailBlockNext->tail.load(); + fence(memory_order_acquire); + + // This block must be empty since it's not the head block and we + // go through the blocks in a circle + assert(nextBlockFront == nextBlockTail); + tailBlockNext->localFront = nextBlockFront; + + char* location = tailBlockNext->data + nextBlockTail * sizeof(T); +#if MOODYCAMEL_HAS_EMPLACE + new (location) T(std::forward(args)...); +#else + new (location) T(std::forward(element)); +#endif + + tailBlockNext->tail = (nextBlockTail + 1) & tailBlockNext->sizeMask; + + fence(memory_order_release); + tailBlock = tailBlockNext; + } else if (canAlloc == CanAlloc) { + // tailBlock is full and there's no free block ahead; create a new block + auto newBlockSize = + largestBlockSize >= MAX_BLOCK_SIZE ? largestBlockSize : largestBlockSize * 2; + auto newBlock = make_block(newBlockSize); + if (newBlock == nullptr) { + // Could not allocate a block! + return false; + } + largestBlockSize = newBlockSize; + +#if MOODYCAMEL_HAS_EMPLACE + new (newBlock->data) T(std::forward(args)...); +#else + new (newBlock->data) T(std::forward(element)); +#endif + assert(newBlock->front == 0); + newBlock->tail = newBlock->localTail = 1; + + newBlock->next = tailBlock_->next.load(); + tailBlock_->next = newBlock; + + // Might be possible for the dequeue thread to see the new tailBlock->next + // *without* seeing the new tailBlock value, but this is OK since it can't + // advance to the next block until tailBlock is set anyway (because the only + // case where it could try to read the next is if it's already at the tailBlock, + // and it won't advance past tailBlock in any circumstance). + + fence(memory_order_release); + tailBlock = newBlock; + } else if (canAlloc == CannotAlloc) { + // Would have had to allocate a new block to enqueue, but not allowed + return false; + } else { + assert(false && "Should be unreachable code"); + return false; + } + } + + return true; + } + + // Disable copying + ReaderWriterQueue(ReaderWriterQueue const&) {} + + // Disable assignment + ReaderWriterQueue& operator=(ReaderWriterQueue const&) {} + + AE_FORCEINLINE static size_t ceilToPow2(size_t x) { + // From http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2 + --x; + x |= x >> 1; + x |= x >> 2; + x |= x >> 4; + for (size_t i = 1; i < sizeof(size_t); i <<= 1) { + x |= x >> (i << 3); + } + ++x; + return x; + } + + template + static AE_FORCEINLINE char* align_for(char* ptr) AE_NO_TSAN { + const std::size_t alignment = std::alignment_of::value; + return ptr + (alignment - (reinterpret_cast(ptr) % alignment)) % alignment; + } + +private: +#ifndef NDEBUG + struct ReentrantGuard { + AE_NO_TSAN ReentrantGuard(weak_atomic& _inSection) : inSection(_inSection) { + assert(!inSection && + "Concurrent (or re-entrant) enqueue or dequeue operation detected (only one " + "thread at a time may hold the producer or consumer role)"); + inSection = true; + } + + AE_NO_TSAN ~ReentrantGuard() { + inSection = false; + } + + private: + ReentrantGuard& operator=(ReentrantGuard const&); + + private: + weak_atomic& inSection; + }; +#endif + + struct Block { + // Avoid false-sharing by putting highly contended variables on their own cache lines + weak_atomic front; // (Atomic) Elements are read from here + size_t localTail; // An uncontended shadow copy of tail, owned by the consumer + + char cachelineFiller0[MOODYCAMEL_CACHE_LINE_SIZE - sizeof(weak_atomic) - + sizeof(size_t)]; + weak_atomic tail; // (Atomic) Elements are enqueued here + size_t localFront; + + char cachelineFiller1[MOODYCAMEL_CACHE_LINE_SIZE - sizeof(weak_atomic) - + sizeof(size_t)]; // next isn't very contended, but we don't want it on + // the same cache line as tail (which is) + weak_atomic next; // (Atomic) + + char* data; // Contents (on heap) are aligned to T's alignment + + const size_t sizeMask; + + // size must be a power of two (and greater than 0) + AE_NO_TSAN Block(size_t const& _size, char* _rawThis, char* _data) + : front(0UL), localTail(0), tail(0UL), localFront(0), next(nullptr), data(_data), + sizeMask(_size - 1), rawThis(_rawThis) {} + + private: + // C4512 - Assignment operator could not be generated + Block& operator=(Block const&); + + public: + char* rawThis; + }; + + static Block* make_block(size_t capacity) AE_NO_TSAN { + // Allocate enough memory for the block itself, as well as all the elements it will contain + auto size = sizeof(Block) + std::alignment_of::value - 1; + size += sizeof(T) * capacity + std::alignment_of::value - 1; + auto newBlockRaw = static_cast(std::malloc(size)); + if (newBlockRaw == nullptr) { + return nullptr; + } + + auto newBlockAligned = align_for(newBlockRaw); + auto newBlockData = align_for(newBlockAligned + sizeof(Block)); + return new (newBlockAligned) Block(capacity, newBlockRaw, newBlockData); + } + +private: + weak_atomic frontBlock; // (Atomic) Elements are dequeued from this block + + char cachelineFiller[MOODYCAMEL_CACHE_LINE_SIZE - sizeof(weak_atomic)]; + weak_atomic tailBlock; // (Atomic) Elements are enqueued to this block + + size_t largestBlockSize; + +#ifndef NDEBUG + weak_atomic enqueuing; + mutable weak_atomic dequeuing; +#endif +}; + +// Like ReaderWriterQueue, but also providees blocking operations +template +class BlockingReaderWriterQueue { +private: + typedef ::Common::ReaderWriterQueue ReaderWriterQueue; + +public: + explicit BlockingReaderWriterQueue(size_t size = 15) AE_NO_TSAN + : inner(size), + sema(new spsc_sema::LightweightSemaphore()) {} + + BlockingReaderWriterQueue(BlockingReaderWriterQueue&& other) AE_NO_TSAN + : inner(std::move(other.inner)), + sema(std::move(other.sema)) {} + + BlockingReaderWriterQueue& operator=(BlockingReaderWriterQueue&& other) AE_NO_TSAN { + std::swap(sema, other.sema); + std::swap(inner, other.inner); + return *this; + } + + // Enqueues a copy of element if there is room in the queue. + // Returns true if the element was enqueued, false otherwise. + // Does not allocate memory. + AE_FORCEINLINE bool try_enqueue(T const& element) AE_NO_TSAN { + if (inner.try_enqueue(element)) { + sema->signal(); + return true; + } + return false; + } + + // Enqueues a moved copy of element if there is room in the queue. + // Returns true if the element was enqueued, false otherwise. + // Does not allocate memory. + AE_FORCEINLINE bool try_enqueue(T&& element) AE_NO_TSAN { + if (inner.try_enqueue(std::forward(element))) { + sema->signal(); + return true; + } + return false; + } + +#if MOODYCAMEL_HAS_EMPLACE + // Like try_enqueue() but with emplace semantics (i.e. construct-in-place). + template + AE_FORCEINLINE bool try_emplace(Args&&... args) AE_NO_TSAN { + if (inner.try_emplace(std::forward(args)...)) { + sema->signal(); + return true; + } + return false; + } +#endif + + // Enqueues a copy of element on the queue. + // Allocates an additional block of memory if needed. + // Only fails (returns false) if memory allocation fails. + AE_FORCEINLINE bool enqueue(T const& element) AE_NO_TSAN { + if (inner.enqueue(element)) { + sema->signal(); + return true; + } + return false; + } + + // Enqueues a moved copy of element on the queue. + // Allocates an additional block of memory if needed. + // Only fails (returns false) if memory allocation fails. + AE_FORCEINLINE bool enqueue(T&& element) AE_NO_TSAN { + if (inner.enqueue(std::forward(element))) { + sema->signal(); + return true; + } + return false; + } + +#if MOODYCAMEL_HAS_EMPLACE + // Like enqueue() but with emplace semantics (i.e. construct-in-place). + template + AE_FORCEINLINE bool emplace(Args&&... args) AE_NO_TSAN { + if (inner.emplace(std::forward(args)...)) { + sema->signal(); + return true; + } + return false; + } +#endif + + // Attempts to dequeue an element; if the queue is empty, + // returns false instead. If the queue has at least one element, + // moves front to result using operator=, then returns true. + template + bool try_dequeue(U& result) AE_NO_TSAN { + if (sema->tryWait()) { + bool success = inner.try_dequeue(result); + assert(success); + AE_UNUSED(success); + return true; + } + return false; + } + + // Attempts to dequeue an element; if the queue is empty, + // waits until an element is available, then dequeues it. + template + void wait_dequeue(U& result) AE_NO_TSAN { + while (!sema->wait()) + ; + bool success = inner.try_dequeue(result); + AE_UNUSED(result); + assert(success); + AE_UNUSED(success); + } + + // Attempts to dequeue an element; if the queue is empty, + // waits until an element is available up to the specified timeout, + // then dequeues it and returns true, or returns false if the timeout + // expires before an element can be dequeued. + // Using a negative timeout indicates an indefinite timeout, + // and is thus functionally equivalent to calling wait_dequeue. + template + bool wait_dequeue_timed(U& result, std::int64_t timeout_usecs) AE_NO_TSAN { + if (!sema->wait(timeout_usecs)) { + return false; + } + bool success = inner.try_dequeue(result); + AE_UNUSED(result); + assert(success); + AE_UNUSED(success); + return true; + } + +#if __cplusplus > 199711L || _MSC_VER >= 1700 + // Attempts to dequeue an element; if the queue is empty, + // waits until an element is available up to the specified timeout, + // then dequeues it and returns true, or returns false if the timeout + // expires before an element can be dequeued. + // Using a negative timeout indicates an indefinite timeout, + // and is thus functionally equivalent to calling wait_dequeue. + template + inline bool wait_dequeue_timed(U& result, + std::chrono::duration const& timeout) AE_NO_TSAN { + return wait_dequeue_timed( + result, std::chrono::duration_cast(timeout).count()); + } +#endif + + // Returns a pointer to the front element in the queue (the one that + // would be removed next by a call to `try_dequeue` or `pop`). If the + // queue appears empty at the time the method is called, nullptr is + // returned instead. + // Must be called only from the consumer thread. + AE_FORCEINLINE T* peek() const AE_NO_TSAN { + return inner.peek(); + } + + // Removes the front element from the queue, if any, without returning it. + // Returns true on success, or false if the queue appeared empty at the time + // `pop` was called. + AE_FORCEINLINE bool pop() AE_NO_TSAN { + if (sema->tryWait()) { + bool result = inner.pop(); + assert(result); + AE_UNUSED(result); + return true; + } + return false; + } + + // Returns the approximate number of items currently in the queue. + // Safe to call from both the producer and consumer threads. + AE_FORCEINLINE size_t size_approx() const AE_NO_TSAN { + return sema->availableApprox(); + } + + // Returns the total number of items that could be enqueued without incurring + // an allocation when this queue is empty. + // Safe to call from both the producer and consumer threads. + // + // NOTE: The actual capacity during usage may be different depending on the consumer. + // If the consumer is removing elements concurrently, the producer cannot add to + // the block the consumer is removing from until it's completely empty, except in + // the case where the producer was writing to the same block the consumer was + // reading from the whole time. + AE_FORCEINLINE size_t max_capacity() const { + return inner.max_capacity(); + } + +private: + // Disable copying & assignment + BlockingReaderWriterQueue(BlockingReaderWriterQueue const&) {} + BlockingReaderWriterQueue& operator=(BlockingReaderWriterQueue const&) {} + +private: + ReaderWriterQueue inner; + std::unique_ptr sema; +}; + +} // namespace Common + +#ifdef AE_VCPP +#pragma warning(pop) +#endif diff --git a/src/common/settings.cpp b/src/common/settings.cpp index d4c52989a..1c7b6dfae 100644 --- a/src/common/settings.cpp +++ b/src/common/settings.cpp @@ -62,7 +62,8 @@ void LogSettings() { log_setting("Renderer_UseAsynchronousShaders", values.use_asynchronous_shaders.GetValue()); log_setting("Renderer_AnisotropicFilteringLevel", values.max_anisotropy.GetValue()); log_setting("Audio_OutputEngine", values.sink_id.GetValue()); - log_setting("Audio_OutputDevice", values.audio_device_id.GetValue()); + log_setting("Audio_OutputDevice", values.audio_output_device_id.GetValue()); + log_setting("Audio_InputDevice", values.audio_input_device_id.GetValue()); log_setting("DataStorage_UseVirtualSd", values.use_virtual_sd.GetValue()); log_path("DataStorage_CacheDir", Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir)); log_path("DataStorage_ConfigDir", Common::FS::GetYuzuPath(Common::FS::YuzuPath::ConfigDir)); diff --git a/src/common/settings.h b/src/common/settings.h index 2bccb8642..06d72c8bf 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -370,10 +370,12 @@ struct TouchFromButtonMap { struct Values { // Audio - Setting audio_device_id{"auto", "output_device"}; Setting sink_id{"auto", "output_engine"}; + Setting audio_output_device_id{"auto", "output_device"}; + Setting audio_input_device_id{"auto", "input_device"}; Setting audio_muted{false, "audio_muted"}; SwitchableSetting volume{100, 0, 100, "volume"}; + Setting dump_audio_commands{false, "dump_audio_commands"}; // Core SwitchableSetting use_multi_core{true, "use_multi_core"}; diff --git a/src/core/core.cpp b/src/core/core.cpp index 7723d9782..0ede0d85c 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -8,6 +8,7 @@ #include #include +#include "audio_core/audio_core.h" #include "common/fs/fs.h" #include "common/logging/log.h" #include "common/microprofile.h" @@ -140,6 +141,8 @@ struct System::Impl { core_timing.SyncPause(false); is_paused = false; + audio_core->PauseSinks(false); + return status; } @@ -147,6 +150,8 @@ struct System::Impl { std::unique_lock lk(suspend_guard); status = SystemResultStatus::Success; + audio_core->PauseSinks(true); + core_timing.SyncPause(true); kernel.Suspend(true); is_paused = true; @@ -154,6 +159,11 @@ struct System::Impl { return status; } + bool IsPaused() const { + std::unique_lock lk(suspend_guard); + return is_paused; + } + std::unique_lock StallProcesses() { std::unique_lock lk(suspend_guard); kernel.Suspend(true); @@ -214,6 +224,8 @@ struct System::Impl { return SystemResultStatus::ErrorVideoCore; } + audio_core = std::make_unique(system); + service_manager = std::make_shared(kernel); services = std::make_unique(service_manager, system); interrupt_manager = std::make_unique(system); @@ -290,7 +302,7 @@ struct System::Impl { if (Settings::values.gamecard_current_game) { fs_controller.SetGameCard(GetGameFileFromPath(virtual_filesystem, filepath)); } else if (!Settings::values.gamecard_path.GetValue().empty()) { - const auto gamecard_path = Settings::values.gamecard_path.GetValue(); + const auto& gamecard_path = Settings::values.gamecard_path.GetValue(); fs_controller.SetGameCard(GetGameFileFromPath(virtual_filesystem, gamecard_path)); } } @@ -308,6 +320,8 @@ struct System::Impl { } void Shutdown() { + SetShuttingDown(true); + // Log last frame performance stats if game was loded if (perf_stats) { const auto perf_results = GetAndResetPerfStats(); @@ -333,14 +347,15 @@ struct System::Impl { kernel.ShutdownCores(); cpu_manager.Shutdown(); debugger.reset(); + kernel.CloseServices(); services.reset(); service_manager.reset(); cheat_engine.reset(); telemetry_session.reset(); - cpu_manager.Shutdown(); time_manager.Shutdown(); core_timing.Shutdown(); app_loader.reset(); + audio_core.reset(); gpu_core.reset(); perf_stats.reset(); kernel.Shutdown(); @@ -350,6 +365,14 @@ struct System::Impl { LOG_DEBUG(Core, "Shutdown OK"); } + bool IsShuttingDown() const { + return is_shutting_down; + } + + void SetShuttingDown(bool shutting_down) { + is_shutting_down = shutting_down; + } + Loader::ResultStatus GetGameName(std::string& out) const { if (app_loader == nullptr) return Loader::ResultStatus::ErrorNotInitialized; @@ -392,8 +415,9 @@ struct System::Impl { return perf_stats->GetAndResetStats(core_timing.GetGlobalTimeUs()); } - std::mutex suspend_guard; + mutable std::mutex suspend_guard; bool is_paused{}; + std::atomic is_shutting_down{}; Timing::CoreTiming core_timing; Kernel::KernelCore kernel; @@ -407,6 +431,7 @@ struct System::Impl { std::unique_ptr gpu_core; std::unique_ptr interrupt_manager; std::unique_ptr device_memory; + std::unique_ptr audio_core; Core::Memory::Memory memory; Core::HID::HIDCore hid_core; CpuManager cpu_manager; @@ -479,6 +504,10 @@ SystemResultStatus System::Pause() { return impl->Pause(); } +bool System::IsPaused() const { + return impl->IsPaused(); +} + void System::InvalidateCpuInstructionCaches() { impl->kernel.InvalidateAllInstructionCaches(); } @@ -491,6 +520,14 @@ void System::Shutdown() { impl->Shutdown(); } +bool System::IsShuttingDown() const { + return impl->IsShuttingDown(); +} + +void System::SetShuttingDown(bool shutting_down) { + impl->SetShuttingDown(shutting_down); +} + void System::DetachDebugger() { if (impl->debugger) { impl->debugger->NotifyShutdown(); @@ -640,6 +677,14 @@ const HID::HIDCore& System::HIDCore() const { return impl->hid_core; } +AudioCore::AudioCore& System::AudioCore() { + return *impl->audio_core; +} + +const AudioCore::AudioCore& System::AudioCore() const { + return *impl->audio_core; +} + Timing::CoreTiming& System::CoreTiming() { return impl->core_timing; } diff --git a/src/core/core.h b/src/core/core.h index 60efe4410..a49d1214b 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -81,6 +81,10 @@ namespace VideoCore { class RendererBase; } // namespace VideoCore +namespace AudioCore { +class AudioCore; +} // namespace AudioCore + namespace Core::Timing { class CoreTiming; } @@ -148,6 +152,9 @@ public: */ [[nodiscard]] SystemResultStatus Pause(); + /// Check if the core is currently paused. + [[nodiscard]] bool IsPaused() const; + /** * Invalidate the CPU instruction caches * This function should only be used by GDB Stub to support breakpoints, memory updates and @@ -160,6 +167,12 @@ public: /// Shutdown the emulated system. void Shutdown(); + /// Check if the core is shutting down. + [[nodiscard]] bool IsShuttingDown() const; + + /// Set the shutting down state. + void SetShuttingDown(bool shutting_down); + /// Forcibly detach the debugger if it is running. void DetachDebugger(); @@ -250,6 +263,12 @@ public: /// Gets an immutable reference to the renderer. [[nodiscard]] const VideoCore::RendererBase& Renderer() const; + /// Gets a mutable reference to the audio interface + [[nodiscard]] AudioCore::AudioCore& AudioCore(); + + /// Gets an immutable reference to the audio interface. + [[nodiscard]] const AudioCore::AudioCore& AudioCore() const; + /// Gets the global scheduler [[nodiscard]] Kernel::GlobalSchedulerContext& GlobalSchedulerContext(); diff --git a/src/core/hle/kernel/hle_ipc.cpp b/src/core/hle/kernel/hle_ipc.cpp index 45135a07f..5b3feec66 100644 --- a/src/core/hle/kernel/hle_ipc.cpp +++ b/src/core/hle/kernel/hle_ipc.cpp @@ -287,18 +287,52 @@ std::size_t HLERequestContext::WriteBuffer(const void* buffer, std::size_t size, BufferDescriptorB().size() > buffer_index && BufferDescriptorB()[buffer_index].Size() >= size, { return 0; }, "BufferDescriptorB is invalid, index={}, size={}", buffer_index, size); - memory.WriteBlock(BufferDescriptorB()[buffer_index].Address(), buffer, size); + WriteBufferB(buffer, size, buffer_index); } else { ASSERT_OR_EXECUTE_MSG( BufferDescriptorC().size() > buffer_index && BufferDescriptorC()[buffer_index].Size() >= size, { return 0; }, "BufferDescriptorC is invalid, index={}, size={}", buffer_index, size); - memory.WriteBlock(BufferDescriptorC()[buffer_index].Address(), buffer, size); + WriteBufferC(buffer, size, buffer_index); } return size; } +std::size_t HLERequestContext::WriteBufferB(const void* buffer, std::size_t size, + std::size_t buffer_index) const { + if (buffer_index >= BufferDescriptorB().size() || size == 0) { + return 0; + } + + const auto buffer_size{BufferDescriptorB()[buffer_index].Size()}; + if (size > buffer_size) { + LOG_CRITICAL(Core, "size ({:016X}) is greater than buffer_size ({:016X})", size, + buffer_size); + size = buffer_size; // TODO(bunnei): This needs to be HW tested + } + + memory.WriteBlock(BufferDescriptorB()[buffer_index].Address(), buffer, size); + return size; +} + +std::size_t HLERequestContext::WriteBufferC(const void* buffer, std::size_t size, + std::size_t buffer_index) const { + if (buffer_index >= BufferDescriptorC().size() || size == 0) { + return 0; + } + + const auto buffer_size{BufferDescriptorC()[buffer_index].Size()}; + if (size > buffer_size) { + LOG_CRITICAL(Core, "size ({:016X}) is greater than buffer_size ({:016X})", size, + buffer_size); + size = buffer_size; // TODO(bunnei): This needs to be HW tested + } + + memory.WriteBlock(BufferDescriptorC()[buffer_index].Address(), buffer, size); + return size; +} + std::size_t HLERequestContext::GetReadBufferSize(std::size_t buffer_index) const { const bool is_buffer_a{BufferDescriptorA().size() > buffer_index && BufferDescriptorA()[buffer_index].Size()}; diff --git a/src/core/hle/kernel/hle_ipc.h b/src/core/hle/kernel/hle_ipc.h index d3abeee85..99265ce90 100644 --- a/src/core/hle/kernel/hle_ipc.h +++ b/src/core/hle/kernel/hle_ipc.h @@ -277,6 +277,14 @@ public: std::size_t WriteBuffer(const void* buffer, std::size_t size, std::size_t buffer_index = 0) const; + /// Helper function to write buffer B + std::size_t WriteBufferB(const void* buffer, std::size_t size, + std::size_t buffer_index = 0) const; + + /// Helper function to write buffer C + std::size_t WriteBufferC(const void* buffer, std::size_t size, + std::size_t buffer_index = 0) const; + /* Helper function to write a buffer using the appropriate buffer descriptor * * @tparam T an arbitrary container that satisfies the diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp index 7307cf262..f23c629dc 100644 --- a/src/core/hle/kernel/kernel.cpp +++ b/src/core/hle/kernel/kernel.cpp @@ -95,19 +95,7 @@ struct KernelCore::Impl { process_list.clear(); - // Close all open server sessions and ports. - std::unordered_set server_objects_; - { - std::scoped_lock lk(server_objects_lock); - server_objects_ = server_objects; - server_objects.clear(); - } - for (auto* server_object : server_objects_) { - server_object->Close(); - } - - // Ensures all service threads gracefully shutdown. - ClearServiceThreads(); + CloseServices(); next_object_id = 0; next_kernel_process_id = KProcess::InitialKIPIDMin; @@ -191,6 +179,22 @@ struct KernelCore::Impl { global_object_list_container.reset(); } + void CloseServices() { + // Close all open server sessions and ports. + std::unordered_set server_objects_; + { + std::scoped_lock lk(server_objects_lock); + server_objects_ = server_objects; + server_objects.clear(); + } + for (auto* server_object : server_objects_) { + server_object->Close(); + } + + // Ensures all service threads gracefully shutdown. + ClearServiceThreads(); + } + void InitializePhysicalCores() { exclusive_monitor = Core::MakeExclusiveMonitor(system.Memory(), Core::Hardware::NUM_CPU_CORES); @@ -813,6 +817,10 @@ void KernelCore::Shutdown() { impl->Shutdown(); } +void KernelCore::CloseServices() { + impl->CloseServices(); +} + const KResourceLimit* KernelCore::GetSystemResourceLimit() const { return impl->system_resource_limit; } diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h index aa0ebaa02..6c7cf6af2 100644 --- a/src/core/hle/kernel/kernel.h +++ b/src/core/hle/kernel/kernel.h @@ -109,6 +109,9 @@ public: /// Clears all resources in use by the kernel instance. void Shutdown(); + /// Close all active services in use by the kernel instance. + void CloseServices(); + /// Retrieves a shared pointer to the system resource limit instance. const KResourceLimit* GetSystemResourceLimit() const; diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index 9116dd77c..118f226e4 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp @@ -5,7 +5,6 @@ #include #include #include -#include "audio_core/audio_renderer.h" #include "common/settings.h" #include "core/core.h" #include "core/file_sys/control_metadata.h" @@ -286,7 +285,7 @@ ISelfController::ISelfController(Core::System& system_, NVFlinger::NVFlinger& nv {62, &ISelfController::SetIdleTimeDetectionExtension, "SetIdleTimeDetectionExtension"}, {63, &ISelfController::GetIdleTimeDetectionExtension, "GetIdleTimeDetectionExtension"}, {64, nullptr, "SetInputDetectionSourceSet"}, - {65, nullptr, "ReportUserIsActive"}, + {65, &ISelfController::ReportUserIsActive, "ReportUserIsActive"}, {66, nullptr, "GetCurrentIlluminance"}, {67, nullptr, "IsIlluminanceAvailable"}, {68, &ISelfController::SetAutoSleepDisabled, "SetAutoSleepDisabled"}, @@ -518,6 +517,13 @@ void ISelfController::GetIdleTimeDetectionExtension(Kernel::HLERequestContext& c rb.Push(idle_time_detection_extension); } +void ISelfController::ReportUserIsActive(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_AM, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); +} + void ISelfController::SetAutoSleepDisabled(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; is_auto_sleep_disabled = rp.Pop(); diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h index 53144427b..bb75c6281 100644 --- a/src/core/hle/service/am/am.h +++ b/src/core/hle/service/am/am.h @@ -175,6 +175,7 @@ private: void SetHandlesRequestToDisplay(Kernel::HLERequestContext& ctx); void SetIdleTimeDetectionExtension(Kernel::HLERequestContext& ctx); void GetIdleTimeDetectionExtension(Kernel::HLERequestContext& ctx); + void ReportUserIsActive(Kernel::HLERequestContext& ctx); void SetAutoSleepDisabled(Kernel::HLERequestContext& ctx); void IsAutoSleepDisabled(Kernel::HLERequestContext& ctx); void GetAccumulatedSuspendedTickValue(Kernel::HLERequestContext& ctx); diff --git a/src/core/hle/service/audio/audin_u.cpp b/src/core/hle/service/audio/audin_u.cpp index 18d3ae682..48a9a73a0 100644 --- a/src/core/hle/service/audio/audin_u.cpp +++ b/src/core/hle/service/audio/audin_u.cpp @@ -1,68 +1,211 @@ // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include "audio_core/in/audio_in_system.h" +#include "audio_core/renderer/audio_device.h" +#include "common/common_funcs.h" #include "common/logging/log.h" +#include "common/string_util.h" #include "core/core.h" #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/k_event.h" #include "core/hle/service/audio/audin_u.h" namespace Service::Audio { +using namespace AudioCore::AudioIn; -IAudioIn::IAudioIn(Core::System& system_) - : ServiceFramework{system_, "IAudioIn"}, service_context{system_, "IAudioIn"} { - // clang-format off - static const FunctionInfo functions[] = { - {0, nullptr, "GetAudioInState"}, - {1, &IAudioIn::Start, "Start"}, - {2, nullptr, "Stop"}, - {3, nullptr, "AppendAudioInBuffer"}, - {4, &IAudioIn::RegisterBufferEvent, "RegisterBufferEvent"}, - {5, nullptr, "GetReleasedAudioInBuffer"}, - {6, nullptr, "ContainsAudioInBuffer"}, - {7, nullptr, "AppendUacInBuffer"}, - {8, &IAudioIn::AppendAudioInBufferAuto, "AppendAudioInBufferAuto"}, - {9, nullptr, "GetReleasedAudioInBuffersAuto"}, - {10, nullptr, "AppendUacInBufferAuto"}, - {11, nullptr, "GetAudioInBufferCount"}, - {12, nullptr, "SetDeviceGain"}, - {13, nullptr, "GetDeviceGain"}, - {14, nullptr, "FlushAudioInBuffers"}, - }; - // clang-format on +class IAudioIn final : public ServiceFramework { +public: + explicit IAudioIn(Core::System& system_, Manager& manager, size_t session_id, + std::string& device_name, const AudioInParameter& in_params, u32 handle, + u64 applet_resource_user_id) + : ServiceFramework{system_, "IAudioIn"}, + service_context{system_, "IAudioIn"}, event{service_context.CreateEvent("AudioInEvent")}, + impl{std::make_shared(system_, manager, event, session_id)} { + // clang-format off + static const FunctionInfo functions[] = { + {0, &IAudioIn::GetAudioInState, "GetAudioInState"}, + {1, &IAudioIn::Start, "Start"}, + {2, &IAudioIn::Stop, "Stop"}, + {3, &IAudioIn::AppendAudioInBuffer, "AppendAudioInBuffer"}, + {4, &IAudioIn::RegisterBufferEvent, "RegisterBufferEvent"}, + {5, &IAudioIn::GetReleasedAudioInBuffer, "GetReleasedAudioInBuffer"}, + {6, &IAudioIn::ContainsAudioInBuffer, "ContainsAudioInBuffer"}, + {7, &IAudioIn::AppendAudioInBuffer, "AppendUacInBuffer"}, + {8, &IAudioIn::AppendAudioInBuffer, "AppendAudioInBufferAuto"}, + {9, &IAudioIn::GetReleasedAudioInBuffer, "GetReleasedAudioInBuffersAuto"}, + {10, &IAudioIn::AppendAudioInBuffer, "AppendUacInBufferAuto"}, + {11, &IAudioIn::GetAudioInBufferCount, "GetAudioInBufferCount"}, + {12, &IAudioIn::SetDeviceGain, "SetDeviceGain"}, + {13, &IAudioIn::GetDeviceGain, "GetDeviceGain"}, + {14, &IAudioIn::FlushAudioInBuffers, "FlushAudioInBuffers"}, + }; + // clang-format on - RegisterHandlers(functions); + RegisterHandlers(functions); - buffer_event = service_context.CreateEvent("IAudioIn:BufferEvent"); -} + if (impl->GetSystem() + .Initialize(device_name, in_params, handle, applet_resource_user_id) + .IsError()) { + LOG_ERROR(Service_Audio, "Failed to initialize the AudioIn System!"); + } + } -IAudioIn::~IAudioIn() { - service_context.CloseEvent(buffer_event); -} + ~IAudioIn() override { + impl->Free(); + service_context.CloseEvent(event); + } -void IAudioIn::Start(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_Audio, "(STUBBED) called"); + [[nodiscard]] std::shared_ptr GetImpl() { + return impl; + } - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); -} +private: + void GetAudioInState(Kernel::HLERequestContext& ctx) { + const auto state = static_cast(impl->GetState()); -void IAudioIn::RegisterBufferEvent(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_Audio, "(STUBBED) called"); + LOG_DEBUG(Service_Audio, "called. State={}", state); - IPC::ResponseBuilder rb{ctx, 2, 1}; - rb.Push(ResultSuccess); - rb.PushCopyObjects(buffer_event->GetReadableEvent()); -} + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push(state); + } -void IAudioIn::AppendAudioInBufferAuto(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_Audio, "(STUBBED) called"); + void Start(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_Audio, "called"); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); -} + auto result = impl->StartSystem(); -AudInU::AudInU(Core::System& system_) : ServiceFramework{system_, "audin:u"} { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); + } + + void Stop(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_Audio, "called"); + + auto result = impl->StopSystem(); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); + } + + void AppendAudioInBuffer(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + u64 tag = rp.PopRaw(); + + const auto in_buffer_size{ctx.GetReadBufferSize()}; + if (in_buffer_size < sizeof(AudioInBuffer)) { + LOG_ERROR(Service_Audio, "Input buffer is too small for an AudioInBuffer!"); + } + + const auto& in_buffer = ctx.ReadBuffer(); + AudioInBuffer buffer{}; + std::memcpy(&buffer, in_buffer.data(), sizeof(AudioInBuffer)); + + [[maybe_unused]] auto sessionid{impl->GetSystem().GetSessionId()}; + LOG_TRACE(Service_Audio, "called. Session {} Appending buffer {:08X}", sessionid, tag); + + auto result = impl->AppendBuffer(buffer, tag); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); + } + + void RegisterBufferEvent(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_Audio, "called"); + + auto& buffer_event = impl->GetBufferEvent(); + + IPC::ResponseBuilder rb{ctx, 2, 1}; + rb.Push(ResultSuccess); + rb.PushCopyObjects(buffer_event); + } + + void GetReleasedAudioInBuffer(Kernel::HLERequestContext& ctx) { + auto write_buffer_size = ctx.GetWriteBufferSize() / sizeof(u64); + std::vector released_buffers(write_buffer_size, 0); + + auto count = impl->GetReleasedBuffers(released_buffers); + + [[maybe_unused]] std::string tags{}; + for (u32 i = 0; i < count; i++) { + tags += fmt::format("{:08X}, ", released_buffers[i]); + } + [[maybe_unused]] auto sessionid{impl->GetSystem().GetSessionId()}; + LOG_TRACE(Service_Audio, "called. Session {} released {} buffers: {}", sessionid, count, + tags); + + ctx.WriteBuffer(released_buffers); + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push(count); + } + + void ContainsAudioInBuffer(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + const u64 tag{rp.Pop()}; + const auto buffer_queued{impl->ContainsAudioBuffer(tag)}; + + LOG_DEBUG(Service_Audio, "called. Is buffer {:08X} registered? {}", tag, buffer_queued); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push(buffer_queued); + } + + void GetAudioInBufferCount(Kernel::HLERequestContext& ctx) { + const auto buffer_count = impl->GetBufferCount(); + + LOG_DEBUG(Service_Audio, "called. Buffer count={}", buffer_count); + + IPC::ResponseBuilder rb{ctx, 3}; + + rb.Push(ResultSuccess); + rb.Push(buffer_count); + } + + void SetDeviceGain(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + const auto volume{rp.Pop()}; + LOG_DEBUG(Service_Audio, "called. Gain {}", volume); + + impl->SetVolume(volume); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + } + + void GetDeviceGain(Kernel::HLERequestContext& ctx) { + auto volume{impl->GetVolume()}; + + LOG_DEBUG(Service_Audio, "called. Gain {}", volume); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push(volume); + } + + void FlushAudioInBuffers(Kernel::HLERequestContext& ctx) { + bool flushed{impl->FlushAudioInBuffers()}; + + LOG_DEBUG(Service_Audio, "called. Were any buffers flushed? {}", flushed); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push(flushed); + } + + KernelHelpers::ServiceContext service_context; + Kernel::KEvent* event; + std::shared_ptr impl; +}; + +AudInU::AudInU(Core::System& system_) + : ServiceFramework{system_, "audin:u", ServiceThreadType::CreateNew}, + service_context{system_, "AudInU"}, impl{std::make_unique( + system_)} { // clang-format off static const FunctionInfo functions[] = { {0, &AudInU::ListAudioIns, "ListAudioIns"}, @@ -80,59 +223,152 @@ AudInU::AudInU(Core::System& system_) : ServiceFramework{system_, "audin:u"} { AudInU::~AudInU() = default; void AudInU::ListAudioIns(Kernel::HLERequestContext& ctx) { + using namespace AudioCore::AudioRenderer; + LOG_DEBUG(Service_Audio, "called"); - const std::size_t count = ctx.GetWriteBufferSize() / sizeof(AudioInDeviceName); - const std::size_t device_count = std::min(count, audio_device_names.size()); - std::vector device_names; - device_names.reserve(device_count); + const auto write_count = + static_cast(ctx.GetWriteBufferSize() / sizeof(AudioDevice::AudioDeviceName)); + std::vector device_names{}; - for (std::size_t i = 0; i < device_count; i++) { - const auto& device_name = audio_device_names[i]; - auto& entry = device_names.emplace_back(); - device_name.copy(entry.data(), device_name.size()); + u32 out_count{0}; + if (write_count > 0) { + out_count = impl->GetDeviceNames(device_names, write_count, false); + ctx.WriteBuffer(device_names); } - ctx.WriteBuffer(device_names); - IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); - rb.Push(static_cast(device_names.size())); + rb.Push(out_count); } void AudInU::ListAudioInsAutoFiltered(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_Audio, "called"); - constexpr u32 device_count = 0; + using namespace AudioCore::AudioRenderer; - // Since we don't actually use any other audio input devices, we return 0 devices. Filtered - // device listing just omits the default input device + LOG_DEBUG(Service_Audio, "called"); + + const auto write_count = + static_cast(ctx.GetWriteBufferSize() / sizeof(AudioDevice::AudioDeviceName)); + std::vector device_names{}; + + u32 out_count{0}; + if (write_count > 0) { + out_count = impl->GetDeviceNames(device_names, write_count, true); + ctx.WriteBuffer(device_names); + } IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); - rb.Push(static_cast(device_count)); -} - -void AudInU::OpenInOutImpl(Kernel::HLERequestContext& ctx) { - AudInOutParams params{}; - params.channel_count = 2; - params.sample_format = SampleFormat::PCM16; - params.sample_rate = 48000; - params.state = State::Started; - - IPC::ResponseBuilder rb{ctx, 6, 0, 1}; - rb.Push(ResultSuccess); - rb.PushRaw(params); - rb.PushIpcInterface(system); + rb.Push(out_count); } void AudInU::OpenAudioIn(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_Audio, "(STUBBED) called"); - OpenInOutImpl(ctx); + IPC::RequestParser rp{ctx}; + auto in_params{rp.PopRaw()}; + auto applet_resource_user_id{rp.PopRaw()}; + const auto device_name_data{ctx.ReadBuffer()}; + auto device_name = Common::StringFromBuffer(device_name_data); + auto handle{ctx.GetCopyHandle(0)}; + + std::scoped_lock l{impl->mutex}; + auto link{impl->LinkToManager()}; + if (link.IsError()) { + LOG_ERROR(Service_Audio, "Failed to link Audio In to Audio Manager"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(link); + return; + } + + size_t new_session_id{}; + auto result{impl->AcquireSessionId(new_session_id)}; + if (result.IsError()) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); + return; + } + + LOG_DEBUG(Service_Audio, "Opening new AudioIn, sessionid={}, free sessions={}", new_session_id, + impl->num_free_sessions); + + auto audio_in = std::make_shared(system, *impl, new_session_id, device_name, + in_params, handle, applet_resource_user_id); + impl->sessions[new_session_id] = audio_in->GetImpl(); + impl->applet_resource_user_ids[new_session_id] = applet_resource_user_id; + + auto& out_system = impl->sessions[new_session_id]->GetSystem(); + AudioInParameterInternal out_params{.sample_rate = out_system.GetSampleRate(), + .channel_count = out_system.GetChannelCount(), + .sample_format = + static_cast(out_system.GetSampleFormat()), + .state = static_cast(out_system.GetState())}; + + IPC::ResponseBuilder rb{ctx, 6, 0, 1}; + + std::string out_name{out_system.GetName()}; + ctx.WriteBuffer(out_name); + + rb.Push(ResultSuccess); + rb.PushRaw(out_params); + rb.PushIpcInterface(audio_in); } void AudInU::OpenAudioInProtocolSpecified(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_Audio, "(STUBBED) called"); - OpenInOutImpl(ctx); + IPC::RequestParser rp{ctx}; + auto protocol_specified{rp.PopRaw()}; + auto in_params{rp.PopRaw()}; + auto applet_resource_user_id{rp.PopRaw()}; + const auto device_name_data{ctx.ReadBuffer()}; + auto device_name = Common::StringFromBuffer(device_name_data); + auto handle{ctx.GetCopyHandle(0)}; + + std::scoped_lock l{impl->mutex}; + auto link{impl->LinkToManager()}; + if (link.IsError()) { + LOG_ERROR(Service_Audio, "Failed to link Audio In to Audio Manager"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(link); + return; + } + + size_t new_session_id{}; + auto result{impl->AcquireSessionId(new_session_id)}; + if (result.IsError()) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); + return; + } + + LOG_DEBUG(Service_Audio, "Opening new AudioIn, sessionid={}, free sessions={}", new_session_id, + impl->num_free_sessions); + + auto audio_in = std::make_shared(system, *impl, new_session_id, device_name, + in_params, handle, applet_resource_user_id); + impl->sessions[new_session_id] = audio_in->GetImpl(); + impl->applet_resource_user_ids[new_session_id] = applet_resource_user_id; + + auto& out_system = impl->sessions[new_session_id]->GetSystem(); + AudioInParameterInternal out_params{.sample_rate = out_system.GetSampleRate(), + .channel_count = out_system.GetChannelCount(), + .sample_format = + static_cast(out_system.GetSampleFormat()), + .state = static_cast(out_system.GetState())}; + + IPC::ResponseBuilder rb{ctx, 6, 0, 1}; + + std::string out_name{out_system.GetName()}; + if (protocol_specified == 0) { + if (out_system.IsUac()) { + out_name = "UacIn"; + } else { + out_name = "DeviceIn"; + } + } + + ctx.WriteBuffer(out_name); + + rb.Push(ResultSuccess); + rb.PushRaw(out_params); + rb.PushIpcInterface(audio_in); } } // namespace Service::Audio diff --git a/src/core/hle/service/audio/audin_u.h b/src/core/hle/service/audio/audin_u.h index 2bfa38ecc..b45fda78a 100644 --- a/src/core/hle/service/audio/audin_u.h +++ b/src/core/hle/service/audio/audin_u.h @@ -3,6 +3,8 @@ #pragma once +#include "audio_core/audio_in_manager.h" +#include "audio_core/in/audio_in.h" #include "core/hle/service/kernel_helpers.h" #include "core/hle/service/service.h" @@ -14,56 +16,27 @@ namespace Kernel { class HLERequestContext; } +namespace AudioCore::AudioOut { +class Manager; +class In; +} // namespace AudioCore::AudioOut + namespace Service::Audio { -class IAudioIn final : public ServiceFramework { -public: - explicit IAudioIn(Core::System& system_); - ~IAudioIn() override; - -private: - void Start(Kernel::HLERequestContext& ctx); - void RegisterBufferEvent(Kernel::HLERequestContext& ctx); - void AppendAudioInBufferAuto(Kernel::HLERequestContext& ctx); - - KernelHelpers::ServiceContext service_context; - - Kernel::KEvent* buffer_event; -}; - class AudInU final : public ServiceFramework { public: explicit AudInU(Core::System& system_); ~AudInU() override; private: - enum class SampleFormat : u32_le { - PCM16 = 2, - }; - - enum class State : u32_le { - Started = 0, - Stopped = 1, - }; - - struct AudInOutParams { - u32_le sample_rate{}; - u32_le channel_count{}; - SampleFormat sample_format{}; - State state{}; - }; - static_assert(sizeof(AudInOutParams) == 0x10, "AudInOutParams is an invalid size"); - - using AudioInDeviceName = std::array; - static constexpr std::array audio_device_names{{ - "BuiltInHeadset", - }}; - void ListAudioIns(Kernel::HLERequestContext& ctx); void ListAudioInsAutoFiltered(Kernel::HLERequestContext& ctx); void OpenInOutImpl(Kernel::HLERequestContext& ctx); void OpenAudioIn(Kernel::HLERequestContext& ctx); void OpenAudioInProtocolSpecified(Kernel::HLERequestContext& ctx); + + KernelHelpers::ServiceContext service_context; + std::unique_ptr impl; }; } // namespace Service::Audio diff --git a/src/core/hle/service/audio/audout_u.cpp b/src/core/hle/service/audio/audout_u.cpp index b0dad6053..a44dd842a 100644 --- a/src/core/hle/service/audio/audout_u.cpp +++ b/src/core/hle/service/audio/audout_u.cpp @@ -5,56 +5,43 @@ #include #include -#include "audio_core/audio_out.h" -#include "audio_core/codec.h" +#include "audio_core/out/audio_out_system.h" +#include "audio_core/renderer/audio_device.h" #include "common/common_funcs.h" #include "common/logging/log.h" +#include "common/string_util.h" #include "common/swap.h" #include "core/core.h" #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/k_event.h" #include "core/hle/service/audio/audout_u.h" #include "core/hle/service/audio/errors.h" -#include "core/hle/service/kernel_helpers.h" #include "core/memory.h" namespace Service::Audio { - -constexpr std::array DefaultDevice{{"DeviceOut"}}; -constexpr int DefaultSampleRate{48000}; - -struct AudoutParams { - s32_le sample_rate; - u16_le channel_count; - INSERT_PADDING_BYTES_NOINIT(2); -}; -static_assert(sizeof(AudoutParams) == 0x8, "AudoutParams is an invalid size"); - -enum class AudioState : u32 { - Started, - Stopped, -}; +using namespace AudioCore::AudioOut; class IAudioOut final : public ServiceFramework { public: - explicit IAudioOut(Core::System& system_, AudoutParams audio_params_, - AudioCore::AudioOut& audio_core_, std::string&& device_name_, - std::string&& unique_name) + explicit IAudioOut(Core::System& system_, AudioCore::AudioOut::Manager& manager, + size_t session_id, std::string& device_name, + const AudioOutParameter& in_params, u32 handle, u64 applet_resource_user_id) : ServiceFramework{system_, "IAudioOut", ServiceThreadType::CreateNew}, - audio_core{audio_core_}, device_name{std::move(device_name_)}, - audio_params{audio_params_}, main_memory{system.Memory()}, service_context{system_, - "IAudioOut"} { + service_context{system_, "IAudioOut"}, event{service_context.CreateEvent( + "AudioOutEvent")}, + impl{std::make_shared(system_, manager, event, session_id)} { + // clang-format off static const FunctionInfo functions[] = { {0, &IAudioOut::GetAudioOutState, "GetAudioOutState"}, - {1, &IAudioOut::StartAudioOut, "Start"}, - {2, &IAudioOut::StopAudioOut, "Stop"}, - {3, &IAudioOut::AppendAudioOutBufferImpl, "AppendAudioOutBuffer"}, + {1, &IAudioOut::Start, "Start"}, + {2, &IAudioOut::Stop, "Stop"}, + {3, &IAudioOut::AppendAudioOutBuffer, "AppendAudioOutBuffer"}, {4, &IAudioOut::RegisterBufferEvent, "RegisterBufferEvent"}, - {5, &IAudioOut::GetReleasedAudioOutBufferImpl, "GetReleasedAudioOutBuffers"}, + {5, &IAudioOut::GetReleasedAudioOutBuffers, "GetReleasedAudioOutBuffers"}, {6, &IAudioOut::ContainsAudioOutBuffer, "ContainsAudioOutBuffer"}, - {7, &IAudioOut::AppendAudioOutBufferImpl, "AppendAudioOutBufferAuto"}, - {8, &IAudioOut::GetReleasedAudioOutBufferImpl, "GetReleasedAudioOutBufferAuto"}, + {7, &IAudioOut::AppendAudioOutBuffer, "AppendAudioOutBufferAuto"}, + {8, &IAudioOut::GetReleasedAudioOutBuffers, "GetReleasedAudioOutBuffersAuto"}, {9, &IAudioOut::GetAudioOutBufferCount, "GetAudioOutBufferCount"}, {10, &IAudioOut::GetAudioOutPlayedSampleCount, "GetAudioOutPlayedSampleCount"}, {11, &IAudioOut::FlushAudioOutBuffers, "FlushAudioOutBuffers"}, @@ -64,241 +51,263 @@ public: // clang-format on RegisterHandlers(functions); - // This is the event handle used to check if the audio buffer was released - buffer_event = service_context.CreateEvent("IAudioOutBufferReleased"); - - stream = audio_core.OpenStream(system.CoreTiming(), audio_params.sample_rate, - audio_params.channel_count, std::move(unique_name), [this] { - const auto guard = LockService(); - buffer_event->GetWritableEvent().Signal(); - }); + if (impl->GetSystem() + .Initialize(device_name, in_params, handle, applet_resource_user_id) + .IsError()) { + LOG_ERROR(Service_Audio, "Failed to initialize the AudioOut System!"); + } } ~IAudioOut() override { - service_context.CloseEvent(buffer_event); + impl->Free(); + service_context.CloseEvent(event); + } + + [[nodiscard]] std::shared_ptr GetImpl() { + return impl; } private: - struct AudioBuffer { - u64_le next; - u64_le buffer; - u64_le buffer_capacity; - u64_le buffer_size; - u64_le offset; - }; - static_assert(sizeof(AudioBuffer) == 0x28, "AudioBuffer is an invalid size"); - void GetAudioOutState(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_Audio, "called"); + const auto state = static_cast(impl->GetState()); + + LOG_DEBUG(Service_Audio, "called. State={}", state); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); - rb.Push(static_cast(stream->IsPlaying() ? AudioState::Started : AudioState::Stopped)); + rb.Push(state); } - void StartAudioOut(Kernel::HLERequestContext& ctx) { + void Start(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_Audio, "called"); - if (stream->IsPlaying()) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ERR_OPERATION_FAILED); - return; - } - - audio_core.StartStream(stream); + auto result = impl->StartSystem(); IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); + rb.Push(result); } - void StopAudioOut(Kernel::HLERequestContext& ctx) { + void Stop(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_Audio, "called"); - if (stream->IsPlaying()) { - audio_core.StopStream(stream); - } + auto result = impl->StopSystem(); IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); + rb.Push(result); + } + + void AppendAudioOutBuffer(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + u64 tag = rp.PopRaw(); + + const auto in_buffer_size{ctx.GetReadBufferSize()}; + if (in_buffer_size < sizeof(AudioOutBuffer)) { + LOG_ERROR(Service_Audio, "Input buffer is too small for an AudioOutBuffer!"); + } + + const auto& in_buffer = ctx.ReadBuffer(); + AudioOutBuffer buffer{}; + std::memcpy(&buffer, in_buffer.data(), sizeof(AudioOutBuffer)); + + [[maybe_unused]] auto sessionid{impl->GetSystem().GetSessionId()}; + LOG_TRACE(Service_Audio, "called. Session {} Appending buffer {:08X}", sessionid, tag); + + auto result = impl->AppendBuffer(buffer, tag); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); } void RegisterBufferEvent(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_Audio, "called"); + auto& buffer_event = impl->GetBufferEvent(); + IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(ResultSuccess); - rb.PushCopyObjects(buffer_event->GetReadableEvent()); + rb.PushCopyObjects(buffer_event); } - void AppendAudioOutBufferImpl(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_Audio, "(STUBBED) called {}", ctx.Description()); - IPC::RequestParser rp{ctx}; + void GetReleasedAudioOutBuffers(Kernel::HLERequestContext& ctx) { + auto write_buffer_size = ctx.GetWriteBufferSize() / sizeof(u64); + std::vector released_buffers(write_buffer_size, 0); - const auto& input_buffer{ctx.ReadBuffer()}; - ASSERT_MSG(input_buffer.size() == sizeof(AudioBuffer), - "AudioBuffer input is an invalid size!"); - AudioBuffer audio_buffer{}; - std::memcpy(&audio_buffer, input_buffer.data(), sizeof(AudioBuffer)); - const u64 tag{rp.Pop()}; + auto count = impl->GetReleasedBuffers(released_buffers); - std::vector samples(audio_buffer.buffer_size / sizeof(s16)); - main_memory.ReadBlock(audio_buffer.buffer, samples.data(), audio_buffer.buffer_size); - - if (!audio_core.QueueBuffer(stream, tag, std::move(samples))) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ERR_BUFFER_COUNT_EXCEEDED); - return; + [[maybe_unused]] std::string tags{}; + for (u32 i = 0; i < count; i++) { + tags += fmt::format("{:08X}, ", released_buffers[i]); } + [[maybe_unused]] auto sessionid{impl->GetSystem().GetSessionId()}; + LOG_TRACE(Service_Audio, "called. Session {} released {} buffers: {}", sessionid, count, + tags); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); - } - - void GetReleasedAudioOutBufferImpl(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_Audio, "called {}", ctx.Description()); - - const u64 max_count{ctx.GetWriteBufferSize() / sizeof(u64)}; - const auto released_buffers{audio_core.GetTagsAndReleaseBuffers(stream, max_count)}; - - std::vector tags{released_buffers}; - tags.resize(max_count); - ctx.WriteBuffer(tags); - + ctx.WriteBuffer(released_buffers); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); - rb.Push(static_cast(released_buffers.size())); + rb.Push(count); } void ContainsAudioOutBuffer(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_Audio, "called"); - IPC::RequestParser rp{ctx}; + const u64 tag{rp.Pop()}; + const auto buffer_queued{impl->ContainsAudioBuffer(tag)}; + + LOG_DEBUG(Service_Audio, "called. Is buffer {:08X} registered? {}", tag, buffer_queued); + IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); - rb.Push(stream->ContainsBuffer(tag)); + rb.Push(buffer_queued); } void GetAudioOutBufferCount(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_Audio, "called"); + const auto buffer_count = impl->GetBufferCount(); + + LOG_DEBUG(Service_Audio, "called. Buffer count={}", buffer_count); IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); - rb.Push(static_cast(stream->GetQueueSize())); + rb.Push(buffer_count); } void GetAudioOutPlayedSampleCount(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_Audio, "called"); + const auto samples_played = impl->GetPlayedSampleCount(); + + LOG_DEBUG(Service_Audio, "called. Played samples={}", samples_played); IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(ResultSuccess); - rb.Push(stream->GetPlayedSampleCount()); + rb.Push(samples_played); } void FlushAudioOutBuffers(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_Audio, "called"); + bool flushed{impl->FlushAudioOutBuffers()}; + + LOG_DEBUG(Service_Audio, "called. Were any buffers flushed? {}", flushed); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); - rb.Push(stream->Flush()); + rb.Push(flushed); } void SetAudioOutVolume(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const float volume = rp.Pop(); - LOG_DEBUG(Service_Audio, "called, volume={}", volume); + const auto volume = rp.Pop(); - stream->SetVolume(volume); + LOG_DEBUG(Service_Audio, "called. Volume={}", volume); + + impl->SetVolume(volume); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); } void GetAudioOutVolume(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_Audio, "called"); + const auto volume = impl->GetVolume(); + + LOG_DEBUG(Service_Audio, "called. Volume={}", volume); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); - rb.Push(stream->GetVolume()); + rb.Push(volume); } - AudioCore::AudioOut& audio_core; - AudioCore::StreamPtr stream; - std::string device_name; - - [[maybe_unused]] AudoutParams audio_params{}; - - Core::Memory::Memory& main_memory; - KernelHelpers::ServiceContext service_context; - - /// This is the event handle used to check if the audio buffer was released - Kernel::KEvent* buffer_event; + Kernel::KEvent* event; + std::shared_ptr impl; }; -AudOutU::AudOutU(Core::System& system_) : ServiceFramework{system_, "audout:u"} { +AudOutU::AudOutU(Core::System& system_) + : ServiceFramework{system_, "audout:u", ServiceThreadType::CreateNew}, + service_context{system_, "AudOutU"}, impl{std::make_unique( + system_)} { // clang-format off static const FunctionInfo functions[] = { - {0, &AudOutU::ListAudioOutsImpl, "ListAudioOuts"}, - {1, &AudOutU::OpenAudioOutImpl, "OpenAudioOut"}, - {2, &AudOutU::ListAudioOutsImpl, "ListAudioOutsAuto"}, - {3, &AudOutU::OpenAudioOutImpl, "OpenAudioOutAuto"}, + {0, &AudOutU::ListAudioOuts, "ListAudioOuts"}, + {1, &AudOutU::OpenAudioOut, "OpenAudioOut"}, + {2, &AudOutU::ListAudioOuts, "ListAudioOutsAuto"}, + {3, &AudOutU::OpenAudioOut, "OpenAudioOutAuto"}, }; // clang-format on RegisterHandlers(functions); - audio_core = std::make_unique(); } AudOutU::~AudOutU() = default; -void AudOutU::ListAudioOutsImpl(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_Audio, "called"); +void AudOutU::ListAudioOuts(Kernel::HLERequestContext& ctx) { + using namespace AudioCore::AudioRenderer; - ctx.WriteBuffer(DefaultDevice); + std::scoped_lock l{impl->mutex}; + + const auto write_count = + static_cast(ctx.GetWriteBufferSize() / sizeof(AudioDevice::AudioDeviceName)); + std::vector device_names{}; + std::string print_names{}; + if (write_count > 0) { + device_names.push_back(AudioDevice::AudioDeviceName("DeviceOut")); + LOG_DEBUG(Service_Audio, "called. \nName=DeviceOut"); + } else { + LOG_DEBUG(Service_Audio, "called. Empty buffer passed in."); + } + + ctx.WriteBuffer(device_names); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); - rb.Push(1); // Amount of audio devices + rb.Push(static_cast(device_names.size())); } -void AudOutU::OpenAudioOutImpl(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_Audio, "called"); - - const auto device_name_data{ctx.ReadBuffer()}; - std::string device_name; - if (device_name_data[0] != '\0') { - device_name.assign(device_name_data.begin(), device_name_data.end()); - } else { - device_name.assign(DefaultDevice.begin(), DefaultDevice.end()); - } - ctx.WriteBuffer(device_name); - +void AudOutU::OpenAudioOut(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - auto params{rp.PopRaw()}; - if (params.channel_count <= 2) { - // Mono does not exist for audout - params.channel_count = 2; - } else { - params.channel_count = 6; - } - if (!params.sample_rate) { - params.sample_rate = DefaultSampleRate; + auto in_params{rp.PopRaw()}; + auto applet_resource_user_id{rp.PopRaw()}; + const auto device_name_data{ctx.ReadBuffer()}; + auto device_name = Common::StringFromBuffer(device_name_data); + auto handle{ctx.GetCopyHandle(0)}; + + auto link{impl->LinkToManager()}; + if (link.IsError()) { + LOG_ERROR(Service_Audio, "Failed to link Audio Out to Audio Manager"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(link); + return; } - std::string unique_name{fmt::format("{}-{}", device_name, audio_out_interfaces.size())}; - auto audio_out_interface = std::make_shared( - system, params, *audio_core, std::move(device_name), std::move(unique_name)); + size_t new_session_id{}; + auto result{impl->AcquireSessionId(new_session_id)}; + if (result.IsError()) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); + return; + } + + LOG_DEBUG(Service_Audio, "Opening new AudioOut, sessionid={}, free sessions={}", new_session_id, + impl->num_free_sessions); + + auto audio_out = std::make_shared(system, *impl, new_session_id, device_name, + in_params, handle, applet_resource_user_id); + + impl->sessions[new_session_id] = audio_out->GetImpl(); + impl->applet_resource_user_ids[new_session_id] = applet_resource_user_id; + + auto& out_system = impl->sessions[new_session_id]->GetSystem(); + AudioOutParameterInternal out_params{.sample_rate = out_system.GetSampleRate(), + .channel_count = out_system.GetChannelCount(), + .sample_format = + static_cast(out_system.GetSampleFormat()), + .state = static_cast(out_system.GetState())}; IPC::ResponseBuilder rb{ctx, 6, 0, 1}; - rb.Push(ResultSuccess); - rb.Push(DefaultSampleRate); - rb.Push(params.channel_count); - rb.Push(static_cast(AudioCore::Codec::PcmFormat::Int16)); - rb.Push(static_cast(AudioState::Stopped)); - rb.PushIpcInterface(audio_out_interface); - audio_out_interfaces.push_back(std::move(audio_out_interface)); + ctx.WriteBuffer(out_system.GetName()); + + rb.Push(ResultSuccess); + rb.PushRaw(out_params); + rb.PushIpcInterface(audio_out); } } // namespace Service::Audio diff --git a/src/core/hle/service/audio/audout_u.h b/src/core/hle/service/audio/audout_u.h index d82004c2e..fdc0ee754 100644 --- a/src/core/hle/service/audio/audout_u.h +++ b/src/core/hle/service/audio/audout_u.h @@ -3,13 +3,11 @@ #pragma once -#include +#include "audio_core/audio_out_manager.h" +#include "audio_core/out/audio_out.h" +#include "core/hle/service/kernel_helpers.h" #include "core/hle/service/service.h" -namespace AudioCore { -class AudioOut; -} - namespace Core { class System; } @@ -18,6 +16,11 @@ namespace Kernel { class HLERequestContext; } +namespace AudioCore::AudioOut { +class Manager; +class Out; +} // namespace AudioCore::AudioOut + namespace Service::Audio { class IAudioOut; @@ -28,11 +31,11 @@ public: ~AudOutU() override; private: - void ListAudioOutsImpl(Kernel::HLERequestContext& ctx); - void OpenAudioOutImpl(Kernel::HLERequestContext& ctx); + void ListAudioOuts(Kernel::HLERequestContext& ctx); + void OpenAudioOut(Kernel::HLERequestContext& ctx); - std::vector> audio_out_interfaces; - std::unique_ptr audio_core; + KernelHelpers::ServiceContext service_context; + std::unique_ptr impl; }; } // namespace Service::Audio diff --git a/src/core/hle/service/audio/audren_u.cpp b/src/core/hle/service/audio/audren_u.cpp index 2ce63c004..381a66ba5 100644 --- a/src/core/hle/service/audio/audren_u.cpp +++ b/src/core/hle/service/audio/audren_u.cpp @@ -4,7 +4,12 @@ #include #include -#include "audio_core/audio_renderer.h" +#include "audio_core/audio_core.h" +#include "audio_core/common/audio_renderer_parameter.h" +#include "audio_core/common/feature_support.h" +#include "audio_core/renderer/audio_device.h" +#include "audio_core/renderer/audio_renderer.h" +#include "audio_core/renderer/voice/voice_info.h" #include "common/alignment.h" #include "common/bit_util.h" #include "common/common_funcs.h" @@ -13,91 +18,112 @@ #include "core/core.h" #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/k_event.h" +#include "core/hle/kernel/k_process.h" +#include "core/hle/kernel/k_transfer_memory.h" #include "core/hle/service/audio/audren_u.h" #include "core/hle/service/audio/errors.h" +#include "core/memory.h" + +using namespace AudioCore::AudioRenderer; namespace Service::Audio { class IAudioRenderer final : public ServiceFramework { public: - explicit IAudioRenderer(Core::System& system_, - const AudioCommon::AudioRendererParameter& audren_params, - const std::size_t instance_number) + explicit IAudioRenderer(Core::System& system_, Manager& manager_, + AudioCore::AudioRendererParameterInternal& params, + Kernel::KTransferMemory* transfer_memory, u64 transfer_memory_size, + u32 process_handle, u64 applet_resource_user_id, s32 session_id) : ServiceFramework{system_, "IAudioRenderer", ServiceThreadType::CreateNew}, - service_context{system_, "IAudioRenderer"} { + service_context{system_, "IAudioRenderer"}, rendered_event{service_context.CreateEvent( + "IAudioRendererEvent")}, + manager{manager_}, impl{std::make_unique(system_, manager, rendered_event)} { // clang-format off static const FunctionInfo functions[] = { {0, &IAudioRenderer::GetSampleRate, "GetSampleRate"}, {1, &IAudioRenderer::GetSampleCount, "GetSampleCount"}, {2, &IAudioRenderer::GetMixBufferCount, "GetMixBufferCount"}, {3, &IAudioRenderer::GetState, "GetState"}, - {4, &IAudioRenderer::RequestUpdateImpl, "RequestUpdate"}, + {4, &IAudioRenderer::RequestUpdate, "RequestUpdate"}, {5, &IAudioRenderer::Start, "Start"}, {6, &IAudioRenderer::Stop, "Stop"}, {7, &IAudioRenderer::QuerySystemEvent, "QuerySystemEvent"}, {8, &IAudioRenderer::SetRenderingTimeLimit, "SetRenderingTimeLimit"}, {9, &IAudioRenderer::GetRenderingTimeLimit, "GetRenderingTimeLimit"}, - {10, &IAudioRenderer::RequestUpdateImpl, "RequestUpdateAuto"}, - {11, &IAudioRenderer::ExecuteAudioRendererRendering, "ExecuteAudioRendererRendering"}, + {10, nullptr, "RequestUpdateAuto"}, + {11, nullptr, "ExecuteAudioRendererRendering"}, }; // clang-format on RegisterHandlers(functions); - system_event = service_context.CreateEvent("IAudioRenderer:SystemEvent"); - renderer = std::make_unique( - system.CoreTiming(), system.Memory(), audren_params, - [this]() { - const auto guard = LockService(); - system_event->GetWritableEvent().Signal(); - }, - instance_number); + impl->Initialize(params, transfer_memory, transfer_memory_size, process_handle, + applet_resource_user_id, session_id); } ~IAudioRenderer() override { - service_context.CloseEvent(system_event); + impl->Finalize(); + service_context.CloseEvent(rendered_event); } private: void GetSampleRate(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_Audio, "called"); + const auto sample_rate{impl->GetSystem().GetSampleRate()}; + + LOG_DEBUG(Service_Audio, "called. Sample rate {}", sample_rate); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); - rb.Push(renderer->GetSampleRate()); + rb.Push(sample_rate); } void GetSampleCount(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_Audio, "called"); + const auto sample_count{impl->GetSystem().GetSampleCount()}; + + LOG_DEBUG(Service_Audio, "called. Sample count {}", sample_count); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); - rb.Push(renderer->GetSampleCount()); + rb.Push(sample_count); } void GetState(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_Audio, "called"); + const u32 state{!impl->GetSystem().IsActive()}; + + LOG_DEBUG(Service_Audio, "called, state {}", state); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); - rb.Push(static_cast(renderer->GetStreamState())); + rb.Push(state); } void GetMixBufferCount(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_Audio, "called"); + const auto buffer_count{impl->GetSystem().GetMixBufferCount()}; + IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); - rb.Push(renderer->GetMixBufferCount()); + rb.Push(buffer_count); } - void RequestUpdateImpl(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_Audio, "(STUBBED) called"); + void RequestUpdate(Kernel::HLERequestContext& ctx) { + LOG_TRACE(Service_Audio, "called"); - std::vector output_params(ctx.GetWriteBufferSize(), 0); - auto result = renderer->UpdateAudioRenderer(ctx.ReadBuffer(), output_params); + std::vector input{ctx.ReadBuffer(0)}; + + // These buffers are written manually to avoid an issue with WriteBuffer throwing errors for + // checking size 0. Performance size is 0 for most games. + const auto buffers{ctx.BufferDescriptorB()}; + std::vector output(buffers[0].Size(), 0); + std::vector performance(buffers[1].Size(), 0); + + auto result = impl->RequestUpdate(input, performance, output); if (result.IsSuccess()) { - ctx.WriteBuffer(output_params); + ctx.WriteBufferB(output.data(), output.size(), 0); + ctx.WriteBufferB(performance.data(), performance.size(), 1); + } else { + LOG_ERROR(Service_Audio, "RequestUpdate failed error 0x{:02X}!", result.description); } IPC::ResponseBuilder rb{ctx, 2}; @@ -105,38 +131,45 @@ private: } void Start(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_Audio, "(STUBBED) called"); + LOG_DEBUG(Service_Audio, "called"); - const auto result = renderer->Start(); + impl->Start(); IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(result); + rb.Push(ResultSuccess); } void Stop(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_Audio, "(STUBBED) called"); + LOG_DEBUG(Service_Audio, "called"); - const auto result = renderer->Stop(); + impl->Stop(); IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(result); + rb.Push(ResultSuccess); } void QuerySystemEvent(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_Audio, "(STUBBED) called"); + LOG_DEBUG(Service_Audio, "called"); + + if (impl->GetSystem().GetExecutionMode() == AudioCore::ExecutionMode::Manual) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERR_NOT_SUPPORTED); + return; + } IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(ResultSuccess); - rb.PushCopyObjects(system_event->GetReadableEvent()); + rb.PushCopyObjects(rendered_event->GetReadableEvent()); } void SetRenderingTimeLimit(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - rendering_time_limit_percent = rp.Pop(); - LOG_DEBUG(Service_Audio, "called. rendering_time_limit_percent={}", - rendering_time_limit_percent); + LOG_DEBUG(Service_Audio, "called"); - ASSERT(rendering_time_limit_percent <= 100); + IPC::RequestParser rp{ctx}; + auto limit = rp.PopRaw(); + + auto& system_ = impl->GetSystem(); + system_.SetRenderingTimeLimit(limit); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); @@ -145,34 +178,34 @@ private: void GetRenderingTimeLimit(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_Audio, "called"); + auto& system_ = impl->GetSystem(); + auto time = system_.GetRenderingTimeLimit(); + IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); - rb.Push(rendering_time_limit_percent); + rb.Push(time); } void ExecuteAudioRendererRendering(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_Audio, "called"); - - // This service command currently only reports an unsupported operation - // error code, or aborts. Given that, we just always return an error - // code in this case. - - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ERR_NOT_SUPPORTED); } KernelHelpers::ServiceContext service_context; - - Kernel::KEvent* system_event; - std::unique_ptr renderer; - u32 rendering_time_limit_percent = 100; + Kernel::KEvent* rendered_event; + Manager& manager; + std::unique_ptr impl; }; class IAudioDevice final : public ServiceFramework { + public: - explicit IAudioDevice(Core::System& system_, Kernel::KEvent* buffer_event_, u32_le revision_) - : ServiceFramework{system_, "IAudioDevice"}, buffer_event{buffer_event_}, revision{ - revision_} { + explicit IAudioDevice(Core::System& system_, u64 applet_resource_user_id, u32 revision, + u32 device_num) + : ServiceFramework{system_, "IAudioDevice", ServiceThreadType::CreateNew}, + service_context{system_, "IAudioDevice"}, impl{std::make_unique( + system_, applet_resource_user_id, + revision)}, + event{service_context.CreateEvent(fmt::format("IAudioDeviceEvent-{}", device_num))} { static const FunctionInfo functions[] = { {0, &IAudioDevice::ListAudioDeviceName, "ListAudioDeviceName"}, {1, &IAudioDevice::SetAudioDeviceOutputVolume, "SetAudioDeviceOutputVolume"}, @@ -186,54 +219,45 @@ public: {10, &IAudioDevice::GetActiveAudioDeviceName, "GetActiveAudioDeviceNameAuto"}, {11, &IAudioDevice::QueryAudioDeviceInputEvent, "QueryAudioDeviceInputEvent"}, {12, &IAudioDevice::QueryAudioDeviceOutputEvent, "QueryAudioDeviceOutputEvent"}, - {13, nullptr, "GetActiveAudioOutputDeviceName"}, - {14, nullptr, "ListAudioOutputDeviceName"}, + {13, &IAudioDevice::GetActiveAudioDeviceName, "GetActiveAudioOutputDeviceName"}, + {14, &IAudioDevice::ListAudioOutputDeviceName, "ListAudioOutputDeviceName"}, }; RegisterHandlers(functions); + + event->GetWritableEvent().Signal(); + } + + ~IAudioDevice() override { + service_context.CloseEvent(event); } private: - using AudioDeviceName = std::array; - static constexpr std::array audio_device_names{{ - "AudioStereoJackOutput", - "AudioBuiltInSpeakerOutput", - "AudioTvOutput", - "AudioUsbDeviceOutput", - }}; - enum class DeviceType { - AHUBHeadphones, - AHUBSpeakers, - HDA, - USBOutput, - }; - void ListAudioDeviceName(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_Audio, "called"); + const size_t in_count = ctx.GetWriteBufferSize() / sizeof(AudioDevice::AudioDeviceName); - const bool usb_output_supported = - IsFeatureSupported(AudioFeatures::AudioUSBDeviceOutput, revision); - const std::size_t count = ctx.GetWriteBufferSize() / sizeof(AudioDeviceName); + std::vector out_names{}; - std::vector name_buffer; - name_buffer.reserve(audio_device_names.size()); + u32 out_count = impl->ListAudioDeviceName(out_names, in_count); - for (std::size_t i = 0; i < count && i < audio_device_names.size(); i++) { - const auto type = static_cast(i); - - if (!usb_output_supported && type == DeviceType::USBOutput) { - continue; + std::string out{}; + for (u32 i = 0; i < out_count; i++) { + std::string a{}; + u32 j = 0; + while (out_names[i].name[j] != '\0') { + a += out_names[i].name[j]; + j++; } - - const auto& device_name = audio_device_names[i]; - auto& entry = name_buffer.emplace_back(); - device_name.copy(entry.data(), device_name.size()); + out += "\n\t" + a; } - ctx.WriteBuffer(name_buffer); + LOG_DEBUG(Service_Audio, "called.\nNames={}", out); IPC::ResponseBuilder rb{ctx, 3}; + + ctx.WriteBuffer(out_names); + rb.Push(ResultSuccess); - rb.Push(static_cast(name_buffer.size())); + rb.Push(out_count); } void SetAudioDeviceOutputVolume(Kernel::HLERequestContext& ctx) { @@ -243,7 +267,11 @@ private: const auto device_name_buffer = ctx.ReadBuffer(); const std::string name = Common::StringFromBuffer(device_name_buffer); - LOG_WARNING(Service_Audio, "(STUBBED) called. name={}, volume={}", name, volume); + LOG_DEBUG(Service_Audio, "called. name={}, volume={}", name, volume); + + if (name == "AudioTvOutput") { + impl->SetDeviceVolumes(volume); + } IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); @@ -253,53 +281,60 @@ private: const auto device_name_buffer = ctx.ReadBuffer(); const std::string name = Common::StringFromBuffer(device_name_buffer); - LOG_WARNING(Service_Audio, "(STUBBED) called. name={}", name); + LOG_DEBUG(Service_Audio, "called. Name={}", name); + + f32 volume{1.0f}; + if (name == "AudioTvOutput") { + volume = impl->GetDeviceVolume(name); + } IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); - rb.Push(1.0f); + rb.Push(volume); } void GetActiveAudioDeviceName(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_Audio, "(STUBBED) called"); + const auto write_size = ctx.GetWriteBufferSize() / sizeof(char); + std::string out_name{"AudioTvOutput"}; - // Currently set to always be TV audio output. - const auto& device_name = audio_device_names[2]; + LOG_DEBUG(Service_Audio, "(STUBBED) called. Name={}", out_name); - AudioDeviceName out_device_name{}; - device_name.copy(out_device_name.data(), device_name.size()); + out_name.resize(write_size); - ctx.WriteBuffer(out_device_name); + ctx.WriteBuffer(out_name); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); } void QueryAudioDeviceSystemEvent(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_Audio, "(STUBBED) called"); + LOG_DEBUG(Service_Audio, "(STUBBED) called"); - buffer_event->GetWritableEvent().Signal(); + event->GetWritableEvent().Signal(); IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(ResultSuccess); - rb.PushCopyObjects(buffer_event->GetReadableEvent()); + rb.PushCopyObjects(event->GetReadableEvent()); } void GetActiveChannelCount(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_Audio, "(STUBBED) called"); + const auto& sink{system.AudioCore().GetOutputSink()}; + u32 channel_count{sink.GetDeviceChannels()}; + + LOG_DEBUG(Service_Audio, "(STUBBED) called. Channels={}", channel_count); IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); - rb.Push(2); + rb.Push(channel_count); } - // Should be similar to QueryAudioDeviceOutputEvent void QueryAudioDeviceInputEvent(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_Audio, "(STUBBED) called"); + LOG_DEBUG(Service_Audio, "(STUBBED) called"); IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(ResultSuccess); - rb.PushCopyObjects(buffer_event->GetReadableEvent()); + rb.PushCopyObjects(event->GetReadableEvent()); } void QueryAudioDeviceOutputEvent(Kernel::HLERequestContext& ctx) { @@ -307,402 +342,167 @@ private: IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(ResultSuccess); - rb.PushCopyObjects(buffer_event->GetReadableEvent()); + rb.PushCopyObjects(event->GetReadableEvent()); } - Kernel::KEvent* buffer_event; - u32_le revision = 0; + void ListAudioOutputDeviceName(Kernel::HLERequestContext& ctx) { + const size_t in_count = ctx.GetWriteBufferSize() / sizeof(AudioDevice::AudioDeviceName); + + std::vector out_names{}; + + u32 out_count = impl->ListAudioOutputDeviceName(out_names, in_count); + + std::string out{}; + for (u32 i = 0; i < out_count; i++) { + std::string a{}; + u32 j = 0; + while (out_names[i].name[j] != '\0') { + a += out_names[i].name[j]; + j++; + } + out += "\n\t" + a; + } + + LOG_DEBUG(Service_Audio, "called.\nNames={}", out); + + IPC::ResponseBuilder rb{ctx, 3}; + + ctx.WriteBuffer(out_names); + + rb.Push(ResultSuccess); + rb.Push(out_count); + } + + KernelHelpers::ServiceContext service_context; + std::unique_ptr impl; + Kernel::KEvent* event; }; AudRenU::AudRenU(Core::System& system_) - : ServiceFramework{system_, "audren:u"}, service_context{system_, "audren:u"} { + : ServiceFramework{system_, "audren:u", ServiceThreadType::CreateNew}, + service_context{system_, "audren:u"}, impl{std::make_unique(system_)} { // clang-format off static const FunctionInfo functions[] = { {0, &AudRenU::OpenAudioRenderer, "OpenAudioRenderer"}, - {1, &AudRenU::GetAudioRendererWorkBufferSize, "GetWorkBufferSize"}, + {1, &AudRenU::GetWorkBufferSize, "GetWorkBufferSize"}, {2, &AudRenU::GetAudioDeviceService, "GetAudioDeviceService"}, - {3, &AudRenU::OpenAudioRendererForManualExecution, "OpenAudioRendererForManualExecution"}, + {3, nullptr, "OpenAudioRendererForManualExecution"}, {4, &AudRenU::GetAudioDeviceServiceWithRevisionInfo, "GetAudioDeviceServiceWithRevisionInfo"}, }; // clang-format on RegisterHandlers(functions); - - buffer_event = service_context.CreateEvent("IAudioOutBufferReleasedEvent"); } -AudRenU::~AudRenU() { - service_context.CloseEvent(buffer_event); -} +AudRenU::~AudRenU() = default; void AudRenU::OpenAudioRenderer(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_Audio, "called"); + IPC::RequestParser rp{ctx}; - OpenAudioRendererImpl(ctx); + AudioCore::AudioRendererParameterInternal params; + rp.PopRaw(params); + auto transfer_memory_handle = ctx.GetCopyHandle(0); + auto process_handle = ctx.GetCopyHandle(1); + auto transfer_memory_size = rp.Pop(); + auto applet_resource_user_id = rp.Pop(); + + if (impl->GetSessionCount() + 1 > AudioCore::MaxRendererSessions) { + LOG_ERROR(Service_Audio, "Too many AudioRenderer sessions open!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERR_MAXIMUM_SESSIONS_REACHED); + return; + } + + const auto& handle_table{system.CurrentProcess()->GetHandleTable()}; + auto process{handle_table.GetObject(process_handle)}; + auto transfer_memory{ + process->GetHandleTable().GetObject(transfer_memory_handle)}; + + const auto session_id{impl->GetSessionId()}; + if (session_id == -1) { + LOG_ERROR(Service_Audio, "Tried to open a session that's already in use!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERR_MAXIMUM_SESSIONS_REACHED); + return; + } + + LOG_DEBUG(Service_Audio, "Opened new AudioRenderer session {} sessions open {}", session_id, + impl->GetSessionCount()); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(ResultSuccess); + rb.PushIpcInterface(system, *impl, params, transfer_memory.GetPointerUnsafe(), + transfer_memory_size, process_handle, + applet_resource_user_id, session_id); } -static u64 CalculateNumPerformanceEntries(const AudioCommon::AudioRendererParameter& params) { - // +1 represents the final mix. - return u64{params.effect_count} + params.submix_count + params.sink_count + params.voice_count + - 1; -} - -void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_Audio, "called"); - - // Several calculations below align the sizes being calculated - // onto a 64 byte boundary. - static constexpr u64 buffer_alignment_size = 64; - - // Some calculations that calculate portions of the buffer - // that will contain information, on the other hand, align - // the result of some of their calcularions on a 16 byte boundary. - static constexpr u64 info_field_alignment_size = 16; - - // Maximum detail entries that may exist at one time for performance - // frame statistics. - static constexpr u64 max_perf_detail_entries = 100; - - // Size of the data structure representing the bulk of the voice-related state. - static constexpr u64 voice_state_size_bytes = 0x100; - - // Size of the upsampler manager data structure - constexpr u64 upsampler_manager_size = 0x48; - - // Calculates the part of the size that relates to mix buffers. - const auto calculate_mix_buffer_sizes = [](const AudioCommon::AudioRendererParameter& params) { - // As of 8.0.0 this is the maximum on voice channels. - constexpr u64 max_voice_channels = 6; - - // The service expects the sample_count member of the parameters to either be - // a value of 160 or 240, so the maximum sample count is assumed in order - // to adequately handle all values at runtime. - constexpr u64 default_max_sample_count = 240; - - const u64 total_mix_buffers = params.mix_buffer_count + max_voice_channels; - - u64 size = 0; - size += total_mix_buffers * (sizeof(s32) * params.sample_count); - size += total_mix_buffers * (sizeof(s32) * default_max_sample_count); - size += u64{params.submix_count} + params.sink_count; - size = Common::AlignUp(size, buffer_alignment_size); - size += Common::AlignUp(params.unknown_30, buffer_alignment_size); - size += Common::AlignUp(sizeof(s32) * params.mix_buffer_count, buffer_alignment_size); - return size; - }; - - // Calculates the portion of the size related to the mix data (and the sorting thereof). - const auto calculate_mix_info_size = [](const AudioCommon::AudioRendererParameter& params) { - // The size of the mixing info data structure. - constexpr u64 mix_info_size = 0x940; - - // Consists of total submixes with the final mix included. - const u64 total_mix_count = u64{params.submix_count} + 1; - - // The total number of effects that may be available to the audio renderer at any time. - constexpr u64 max_effects = 256; - - // Calculates the part of the size related to the audio node state. - // This will only be used if the audio revision supports the splitter. - const auto calculate_node_state_size = [](std::size_t num_nodes) { - // Internally within a nodestate, it appears to use a data structure - // similar to a std::bitset<64> twice. - constexpr u64 bit_size = Common::BitSize(); - constexpr u64 num_bitsets = 2; - - // Node state instances have three states internally for performing - // depth-first searches of nodes. Initialized, Found, and Done Sorting. - constexpr u64 num_states = 3; - - u64 size = 0; - size += (num_nodes * num_nodes) * sizeof(s32); - size += num_states * (num_nodes * sizeof(s32)); - size += num_bitsets * (Common::AlignUp(num_nodes, bit_size) / Common::BitSize()); - return size; - }; - - // Calculates the part of the size related to the adjacency (aka edge) matrix. - const auto calculate_edge_matrix_size = [](std::size_t num_nodes) { - return (num_nodes * num_nodes) * sizeof(s32); - }; - - u64 size = 0; - size += Common::AlignUp(sizeof(void*) * total_mix_count, info_field_alignment_size); - size += Common::AlignUp(mix_info_size * total_mix_count, info_field_alignment_size); - size += Common::AlignUp(sizeof(s32) * max_effects * params.submix_count, - info_field_alignment_size); - - if (IsFeatureSupported(AudioFeatures::Splitter, params.revision)) { - size += Common::AlignUp(calculate_node_state_size(total_mix_count) + - calculate_edge_matrix_size(total_mix_count), - info_field_alignment_size); - } - - return size; - }; - - // Calculates the part of the size related to voice channel info. - const auto calculate_voice_info_size = [](const AudioCommon::AudioRendererParameter& params) { - constexpr u64 voice_info_size = 0x220; - constexpr u64 voice_resource_size = 0xD0; - - u64 size = 0; - size += Common::AlignUp(sizeof(void*) * params.voice_count, info_field_alignment_size); - size += Common::AlignUp(voice_info_size * params.voice_count, info_field_alignment_size); - size += - Common::AlignUp(voice_resource_size * params.voice_count, info_field_alignment_size); - size += - Common::AlignUp(voice_state_size_bytes * params.voice_count, info_field_alignment_size); - return size; - }; - - // Calculates the part of the size related to memory pools. - const auto calculate_memory_pools_size = [](const AudioCommon::AudioRendererParameter& params) { - const u64 num_memory_pools = sizeof(s32) * (u64{params.effect_count} + params.voice_count); - const u64 memory_pool_info_size = 0x20; - return Common::AlignUp(num_memory_pools * memory_pool_info_size, info_field_alignment_size); - }; - - // Calculates the part of the size related to the splitter context. - const auto calculate_splitter_context_size = - [](const AudioCommon::AudioRendererParameter& params) -> u64 { - if (!IsFeatureSupported(AudioFeatures::Splitter, params.revision)) { - return 0; - } - - constexpr u64 splitter_info_size = 0x20; - constexpr u64 splitter_destination_data_size = 0xE0; - - u64 size = 0; - size += params.num_splitter_send_channels; - size += - Common::AlignUp(splitter_info_size * params.splitter_count, info_field_alignment_size); - size += Common::AlignUp(splitter_destination_data_size * params.num_splitter_send_channels, - info_field_alignment_size); - - return size; - }; - - // Calculates the part of the size related to the upsampler info. - const auto calculate_upsampler_info_size = - [](const AudioCommon::AudioRendererParameter& params) { - constexpr u64 upsampler_info_size = 0x280; - // Yes, using the buffer size over info alignment size is intentional here. - return Common::AlignUp(upsampler_info_size * - (u64{params.submix_count} + params.sink_count), - buffer_alignment_size); - }; - - // Calculates the part of the size related to effect info. - const auto calculate_effect_info_size = [](const AudioCommon::AudioRendererParameter& params) { - constexpr u64 effect_info_size = 0x2B0; - return Common::AlignUp(effect_info_size * params.effect_count, info_field_alignment_size); - }; - - // Calculates the part of the size related to audio sink info. - const auto calculate_sink_info_size = [](const AudioCommon::AudioRendererParameter& params) { - const u64 sink_info_size = 0x170; - return Common::AlignUp(sink_info_size * params.sink_count, info_field_alignment_size); - }; - - // Calculates the part of the size related to voice state info. - const auto calculate_voice_state_size = [](const AudioCommon::AudioRendererParameter& params) { - const u64 voice_state_size = 0x100; - const u64 additional_size = buffer_alignment_size - 1; - return Common::AlignUp(voice_state_size * params.voice_count + additional_size, - info_field_alignment_size); - }; - - // Calculates the part of the size related to performance statistics. - const auto calculate_perf_size = [](const AudioCommon::AudioRendererParameter& params) { - // Extra size value appended to the end of the calculation. - constexpr u64 appended = 128; - - // Whether or not we assume the newer version of performance metrics data structures. - const bool is_v2 = - IsFeatureSupported(AudioFeatures::PerformanceMetricsVersion2, params.revision); - - // Data structure sizes - constexpr u64 perf_statistics_size = 0x0C; - const u64 header_size = is_v2 ? 0x30 : 0x18; - const u64 entry_size = is_v2 ? 0x18 : 0x10; - const u64 detail_size = is_v2 ? 0x18 : 0x10; - - const u64 entry_count = CalculateNumPerformanceEntries(params); - const u64 size_per_frame = - header_size + (entry_size * entry_count) + (detail_size * max_perf_detail_entries); - - u64 size = 0; - size += Common::AlignUp(size_per_frame * params.performance_frame_count + 1, - buffer_alignment_size); - size += Common::AlignUp(perf_statistics_size, buffer_alignment_size); - size += appended; - return size; - }; - - // Calculates the part of the size that relates to the audio command buffer. - const auto calculate_command_buffer_size = - [](const AudioCommon::AudioRendererParameter& params) { - constexpr u64 alignment = (buffer_alignment_size - 1) * 2; - - if (!IsFeatureSupported(AudioFeatures::VariadicCommandBuffer, params.revision)) { - constexpr u64 command_buffer_size = 0x18000; - - return command_buffer_size + alignment; - } - - // When the variadic command buffer is supported, this means - // the command generator for the audio renderer can issue commands - // that are (as one would expect), variable in size. So what we need to do - // is determine the maximum possible size for a few command data structures - // then multiply them by the amount of present commands indicated by the given - // respective audio parameters. - - constexpr u64 max_biquad_filters = 2; - constexpr u64 max_mix_buffers = 24; - - constexpr u64 biquad_filter_command_size = 0x2C; - - constexpr u64 depop_mix_command_size = 0x24; - constexpr u64 depop_setup_command_size = 0x50; - - constexpr u64 effect_command_max_size = 0x540; - - constexpr u64 mix_command_size = 0x1C; - constexpr u64 mix_ramp_command_size = 0x24; - constexpr u64 mix_ramp_grouped_command_size = 0x13C; - - constexpr u64 perf_command_size = 0x28; - - constexpr u64 sink_command_size = 0x130; - - constexpr u64 submix_command_max_size = - depop_mix_command_size + (mix_command_size * max_mix_buffers) * max_mix_buffers; - - constexpr u64 volume_command_size = 0x1C; - constexpr u64 volume_ramp_command_size = 0x20; - - constexpr u64 voice_biquad_filter_command_size = - biquad_filter_command_size * max_biquad_filters; - constexpr u64 voice_data_command_size = 0x9C; - const u64 voice_command_max_size = - (params.splitter_count * depop_setup_command_size) + - (voice_data_command_size + voice_biquad_filter_command_size + - volume_ramp_command_size + mix_ramp_grouped_command_size); - - // Now calculate the individual elements that comprise the size and add them together. - const u64 effect_commands_size = params.effect_count * effect_command_max_size; - - const u64 final_mix_commands_size = - depop_mix_command_size + volume_command_size * max_mix_buffers; - - const u64 perf_commands_size = - perf_command_size * - (CalculateNumPerformanceEntries(params) + max_perf_detail_entries); - - const u64 sink_commands_size = params.sink_count * sink_command_size; - - const u64 splitter_commands_size = - params.num_splitter_send_channels * max_mix_buffers * mix_ramp_command_size; - - const u64 submix_commands_size = params.submix_count * submix_command_max_size; - - const u64 voice_commands_size = params.voice_count * voice_command_max_size; - - return effect_commands_size + final_mix_commands_size + perf_commands_size + - sink_commands_size + splitter_commands_size + submix_commands_size + - voice_commands_size + alignment; - }; +void AudRenU::GetWorkBufferSize(Kernel::HLERequestContext& ctx) { + AudioCore::AudioRendererParameterInternal params; IPC::RequestParser rp{ctx}; - const auto params = rp.PopRaw(); + rp.PopRaw(params); - u64 size = 0; - size += calculate_mix_buffer_sizes(params); - size += calculate_mix_info_size(params); - size += calculate_voice_info_size(params); - size += upsampler_manager_size; - size += calculate_memory_pools_size(params); - size += calculate_splitter_context_size(params); + u64 size{0}; + auto result = impl->GetWorkBufferSize(params, size); - size = Common::AlignUp(size, buffer_alignment_size); + std::string output_info{}; + output_info += fmt::format("\tRevision {}", AudioCore::GetRevisionNum(params.revision)); + output_info += + fmt::format("\n\tSample Rate {}, Sample Count {}", params.sample_rate, params.sample_count); + output_info += fmt::format("\n\tExecution Mode {}, Voice Drop Enabled {}", + static_cast(params.execution_mode), params.voice_drop_enabled); + output_info += fmt::format( + "\n\tSizes: Effects {:04X}, Mixes {:04X}, Sinks {:04X}, Submixes {:04X}, Splitter Infos " + "{:04X}, Splitter Destinations {:04X}, Voices {:04X}, Performance Frames {:04X} External " + "Context {:04X}", + params.effects, params.mixes, params.sinks, params.sub_mixes, params.splitter_infos, + params.splitter_destinations, params.voices, params.perf_frames, + params.external_context_size); - size += calculate_upsampler_info_size(params); - size += calculate_effect_info_size(params); - size += calculate_sink_info_size(params); - size += calculate_voice_state_size(params); - size += calculate_perf_size(params); - size += calculate_command_buffer_size(params); - - // finally, 4KB page align the size, and we're done. - size = Common::AlignUp(size, 4096); + LOG_DEBUG(Service_Audio, "called.\nInput params:\n{}\nOutput params:\n\tWorkbuffer size {:08X}", + output_info, size); IPC::ResponseBuilder rb{ctx, 4}; - rb.Push(ResultSuccess); + rb.Push(result); rb.Push(size); - - LOG_DEBUG(Service_Audio, "buffer_size=0x{:X}", size); } void AudRenU::GetAudioDeviceService(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const u64 aruid = rp.Pop(); - LOG_DEBUG(Service_Audio, "called. aruid={:016X}", aruid); + const auto applet_resource_user_id = rp.Pop(); + + LOG_DEBUG(Service_Audio, "called. Applet resource id {}", applet_resource_user_id); - // Revisionless variant of GetAudioDeviceServiceWithRevisionInfo that - // always assumes the initial release revision (REV1). IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(ResultSuccess); - rb.PushIpcInterface(system, buffer_event, Common::MakeMagic('R', 'E', 'V', '1')); + rb.PushIpcInterface(system, applet_resource_user_id, + ::Common::MakeMagic('R', 'E', 'V', '1'), num_audio_devices++); } void AudRenU::OpenAudioRendererForManualExecution(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_Audio, "called"); - - OpenAudioRendererImpl(ctx); } void AudRenU::GetAudioDeviceServiceWithRevisionInfo(Kernel::HLERequestContext& ctx) { struct Parameters { u32 revision; - u64 aruid; + u64 applet_resource_user_id; }; IPC::RequestParser rp{ctx}; - const auto [revision, aruid] = rp.PopRaw(); - LOG_DEBUG(Service_Audio, "called. revision={:08X}, aruid={:016X}", revision, aruid); + const auto [revision, applet_resource_user_id] = rp.PopRaw(); - IPC::ResponseBuilder rb{ctx, 2, 0, 1}; - rb.Push(ResultSuccess); - rb.PushIpcInterface(system, buffer_event, revision); -} + LOG_DEBUG(Service_Audio, "called. Revision {} Applet resource id {}", + AudioCore::GetRevisionNum(revision), applet_resource_user_id); -void AudRenU::OpenAudioRendererImpl(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const auto params = rp.PopRaw(); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(ResultSuccess); - rb.PushIpcInterface(system, params, audren_instance_count++); -} - -bool IsFeatureSupported(AudioFeatures feature, u32_le revision) { - // Byte swap - const u32_be version_num = revision - Common::MakeMagic('R', 'E', 'V', '0'); - - switch (feature) { - case AudioFeatures::AudioUSBDeviceOutput: - return version_num >= 4U; - case AudioFeatures::Splitter: - return version_num >= 2U; - case AudioFeatures::PerformanceMetricsVersion2: - case AudioFeatures::VariadicCommandBuffer: - return version_num >= 5U; - default: - return false; - } + rb.PushIpcInterface(system, applet_resource_user_id, revision, + num_audio_devices++); } } // namespace Service::Audio diff --git a/src/core/hle/service/audio/audren_u.h b/src/core/hle/service/audio/audren_u.h index 869d39002..4384a9b3c 100644 --- a/src/core/hle/service/audio/audren_u.h +++ b/src/core/hle/service/audio/audren_u.h @@ -3,6 +3,7 @@ #pragma once +#include "audio_core/audio_render_manager.h" #include "core/hle/service/kernel_helpers.h" #include "core/hle/service/service.h" @@ -15,6 +16,7 @@ class HLERequestContext; } namespace Service::Audio { +class IAudioRenderer; class AudRenU final : public ServiceFramework { public: @@ -23,28 +25,14 @@ public: private: void OpenAudioRenderer(Kernel::HLERequestContext& ctx); - void GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx); + void GetWorkBufferSize(Kernel::HLERequestContext& ctx); void GetAudioDeviceService(Kernel::HLERequestContext& ctx); void OpenAudioRendererForManualExecution(Kernel::HLERequestContext& ctx); void GetAudioDeviceServiceWithRevisionInfo(Kernel::HLERequestContext& ctx); - void OpenAudioRendererImpl(Kernel::HLERequestContext& ctx); - KernelHelpers::ServiceContext service_context; - - std::size_t audren_instance_count = 0; - Kernel::KEvent* buffer_event; + std::unique_ptr impl; + u32 num_audio_devices{0}; }; -// Describes a particular audio feature that may be supported in a particular revision. -enum class AudioFeatures : u32 { - AudioUSBDeviceOutput, - Splitter, - PerformanceMetricsVersion2, - VariadicCommandBuffer, -}; - -// Tests if a particular audio feature is supported with a given audio revision. -bool IsFeatureSupported(AudioFeatures feature, u32_le revision); - } // namespace Service::Audio diff --git a/src/core/hle/service/audio/errors.h b/src/core/hle/service/audio/errors.h index ac6c514af..d706978cb 100644 --- a/src/core/hle/service/audio/errors.h +++ b/src/core/hle/service/audio/errors.h @@ -7,8 +7,17 @@ namespace Service::Audio { +constexpr Result ERR_INVALID_DEVICE_NAME{ErrorModule::Audio, 1}; constexpr Result ERR_OPERATION_FAILED{ErrorModule::Audio, 2}; +constexpr Result ERR_INVALID_SAMPLE_RATE{ErrorModule::Audio, 3}; +constexpr Result ERR_INSUFFICIENT_BUFFER_SIZE{ErrorModule::Audio, 4}; +constexpr Result ERR_MAXIMUM_SESSIONS_REACHED{ErrorModule::Audio, 5}; constexpr Result ERR_BUFFER_COUNT_EXCEEDED{ErrorModule::Audio, 8}; +constexpr Result ERR_INVALID_CHANNEL_COUNT{ErrorModule::Audio, 10}; +constexpr Result ERR_INVALID_UPDATE_DATA{ErrorModule::Audio, 41}; +constexpr Result ERR_POOL_MAPPING_FAILED{ErrorModule::Audio, 42}; constexpr Result ERR_NOT_SUPPORTED{ErrorModule::Audio, 513}; +constexpr Result ERR_INVALID_PROCESS_HANDLE{ErrorModule::Audio, 1536}; +constexpr Result ERR_INVALID_REVISION{ErrorModule::Audio, 1537}; } // namespace Service::Audio diff --git a/src/core/hle/service/audio/hwopus.cpp b/src/core/hle/service/audio/hwopus.cpp index 75da659e5..4f2ed2d52 100644 --- a/src/core/hle/service/audio/hwopus.cpp +++ b/src/core/hle/service/audio/hwopus.cpp @@ -298,7 +298,7 @@ void HwOpus::OpenHardwareOpusDecoderEx(Kernel::HLERequestContext& ctx) { const auto sample_rate = rp.Pop(); const auto channel_count = rp.Pop(); - LOG_CRITICAL(Audio, "called sample_rate={}, channel_count={}", sample_rate, channel_count); + LOG_DEBUG(Audio, "called sample_rate={}, channel_count={}", sample_rate, channel_count); ASSERT_MSG(sample_rate == 48000 || sample_rate == 24000 || sample_rate == 16000 || sample_rate == 12000 || sample_rate == 8000, diff --git a/src/core/memory.cpp b/src/core/memory.cpp index 584808d50..635449fce 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -511,7 +511,7 @@ struct Memory::Impl { [[nodiscard]] u8* GetPointerImpl(VAddr vaddr, auto on_unmapped, auto on_rasterizer) const { // AARCH64 masks the upper 16 bit of all memory accesses - vaddr &= 0xffffffffffffLL; + vaddr &= 0xffffffffffffULL; if (vaddr >= 1uLL << current_page_table->GetAddressSpaceBits()) { on_unmapped(); @@ -776,6 +776,10 @@ void Memory::CopyBlock(const Kernel::KProcess& process, VAddr dest_addr, VAddr s impl->CopyBlock(process, dest_addr, src_addr, size); } +void Memory::ZeroBlock(const Kernel::KProcess& process, VAddr dest_addr, const std::size_t size) { + impl->ZeroBlock(process, dest_addr, size); +} + void Memory::RasterizerMarkRegionCached(VAddr vaddr, u64 size, bool cached) { impl->RasterizerMarkRegionCached(vaddr, size, cached); } diff --git a/src/core/memory.h b/src/core/memory.h index f22c0a2d8..780c45385 100644 --- a/src/core/memory.h +++ b/src/core/memory.h @@ -436,6 +436,19 @@ public: void CopyBlock(const Kernel::KProcess& process, VAddr dest_addr, VAddr src_addr, std::size_t size); + /** + * Zeros a range of bytes within the current process' address space at the specified + * virtual address. + * + * @param process The process that will have data zeroed within its address space. + * @param dest_addr The destination virtual address to zero the data from. + * @param size The size of the range to zero out, in bytes. + * + * @post The range [dest_addr, size) within the process' address space contains the + * value 0. + */ + void ZeroBlock(const Kernel::KProcess& process, VAddr dest_addr, std::size_t size); + /** * Marks each page within the specified address range as cached or uncached. * diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index 0a61839da..2840bc5eb 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -372,8 +372,9 @@ void Config::ReadAudioValues() { qt_config->beginGroup(QStringLiteral("Audio")); if (global) { - ReadBasicSetting(Settings::values.audio_device_id); ReadBasicSetting(Settings::values.sink_id); + ReadBasicSetting(Settings::values.audio_output_device_id); + ReadBasicSetting(Settings::values.audio_input_device_id); } ReadGlobalSetting(Settings::values.volume); @@ -1027,7 +1028,8 @@ void Config::SaveAudioValues() { if (global) { WriteBasicSetting(Settings::values.sink_id); - WriteBasicSetting(Settings::values.audio_device_id); + WriteBasicSetting(Settings::values.audio_output_device_id); + WriteBasicSetting(Settings::values.audio_input_device_id); } WriteGlobalSetting(Settings::values.volume); diff --git a/src/yuzu/configuration/configure_audio.cpp b/src/yuzu/configuration/configure_audio.cpp index 512bdfc22..19b8b15ef 100644 --- a/src/yuzu/configuration/configure_audio.cpp +++ b/src/yuzu/configuration/configure_audio.cpp @@ -3,8 +3,8 @@ #include -#include "audio_core/sink.h" -#include "audio_core/sink_details.h" +#include "audio_core/sink/sink.h" +#include "audio_core/sink/sink_details.h" #include "common/settings.h" #include "core/core.h" #include "ui_configure_audio.h" @@ -15,11 +15,11 @@ ConfigureAudio::ConfigureAudio(const Core::System& system_, QWidget* parent) : QWidget(parent), ui(std::make_unique()), system{system_} { ui->setupUi(this); - InitializeAudioOutputSinkComboBox(); + InitializeAudioSinkComboBox(); connect(ui->volume_slider, &QSlider::valueChanged, this, &ConfigureAudio::SetVolumeIndicatorText); - connect(ui->output_sink_combo_box, qOverload(&QComboBox::currentIndexChanged), this, + connect(ui->sink_combo_box, qOverload(&QComboBox::currentIndexChanged), this, &ConfigureAudio::UpdateAudioDevices); ui->volume_label->setVisible(Settings::IsConfiguringGlobal()); @@ -30,8 +30,9 @@ ConfigureAudio::ConfigureAudio(const Core::System& system_, QWidget* parent) SetConfiguration(); const bool is_powered_on = system_.IsPoweredOn(); - ui->output_sink_combo_box->setEnabled(!is_powered_on); - ui->audio_device_combo_box->setEnabled(!is_powered_on); + ui->sink_combo_box->setEnabled(!is_powered_on); + ui->output_combo_box->setEnabled(!is_powered_on); + ui->input_combo_box->setEnabled(!is_powered_on); } ConfigureAudio::~ConfigureAudio() = default; @@ -40,9 +41,9 @@ void ConfigureAudio::SetConfiguration() { SetOutputSinkFromSinkID(); // The device list cannot be pre-populated (nor listed) until the output sink is known. - UpdateAudioDevices(ui->output_sink_combo_box->currentIndex()); + UpdateAudioDevices(ui->sink_combo_box->currentIndex()); - SetAudioDeviceFromDeviceID(); + SetAudioDevicesFromDeviceID(); const auto volume_value = static_cast(Settings::values.volume.GetValue()); ui->volume_slider->setValue(volume_value); @@ -62,32 +63,45 @@ void ConfigureAudio::SetConfiguration() { } void ConfigureAudio::SetOutputSinkFromSinkID() { - [[maybe_unused]] const QSignalBlocker blocker(ui->output_sink_combo_box); + [[maybe_unused]] const QSignalBlocker blocker(ui->sink_combo_box); int new_sink_index = 0; const QString sink_id = QString::fromStdString(Settings::values.sink_id.GetValue()); - for (int index = 0; index < ui->output_sink_combo_box->count(); index++) { - if (ui->output_sink_combo_box->itemText(index) == sink_id) { + for (int index = 0; index < ui->sink_combo_box->count(); index++) { + if (ui->sink_combo_box->itemText(index) == sink_id) { new_sink_index = index; break; } } - ui->output_sink_combo_box->setCurrentIndex(new_sink_index); + ui->sink_combo_box->setCurrentIndex(new_sink_index); } -void ConfigureAudio::SetAudioDeviceFromDeviceID() { +void ConfigureAudio::SetAudioDevicesFromDeviceID() { int new_device_index = -1; - const QString device_id = QString::fromStdString(Settings::values.audio_device_id.GetValue()); - for (int index = 0; index < ui->audio_device_combo_box->count(); index++) { - if (ui->audio_device_combo_box->itemText(index) == device_id) { + const QString output_device_id = + QString::fromStdString(Settings::values.audio_output_device_id.GetValue()); + for (int index = 0; index < ui->output_combo_box->count(); index++) { + if (ui->output_combo_box->itemText(index) == output_device_id) { new_device_index = index; break; } } - ui->audio_device_combo_box->setCurrentIndex(new_device_index); + ui->output_combo_box->setCurrentIndex(new_device_index); + + new_device_index = -1; + const QString input_device_id = + QString::fromStdString(Settings::values.audio_input_device_id.GetValue()); + for (int index = 0; index < ui->input_combo_box->count(); index++) { + if (ui->input_combo_box->itemText(index) == input_device_id) { + new_device_index = index; + break; + } + } + + ui->input_combo_box->setCurrentIndex(new_device_index); } void ConfigureAudio::SetVolumeIndicatorText(int percentage) { @@ -95,14 +109,13 @@ void ConfigureAudio::SetVolumeIndicatorText(int percentage) { } void ConfigureAudio::ApplyConfiguration() { - if (Settings::IsConfiguringGlobal()) { Settings::values.sink_id = - ui->output_sink_combo_box->itemText(ui->output_sink_combo_box->currentIndex()) - .toStdString(); - Settings::values.audio_device_id.SetValue( - ui->audio_device_combo_box->itemText(ui->audio_device_combo_box->currentIndex()) - .toStdString()); + ui->sink_combo_box->itemText(ui->sink_combo_box->currentIndex()).toStdString(); + Settings::values.audio_output_device_id.SetValue( + ui->output_combo_box->itemText(ui->output_combo_box->currentIndex()).toStdString()); + Settings::values.audio_input_device_id.SetValue( + ui->input_combo_box->itemText(ui->input_combo_box->currentIndex()).toStdString()); // Guard if during game and set to game-specific value if (Settings::values.volume.UsingGlobal()) { @@ -129,21 +142,27 @@ void ConfigureAudio::changeEvent(QEvent* event) { } void ConfigureAudio::UpdateAudioDevices(int sink_index) { - ui->audio_device_combo_box->clear(); - ui->audio_device_combo_box->addItem(QString::fromUtf8(AudioCore::auto_device_name)); + ui->output_combo_box->clear(); + ui->output_combo_box->addItem(QString::fromUtf8(AudioCore::Sink::auto_device_name)); - const std::string sink_id = ui->output_sink_combo_box->itemText(sink_index).toStdString(); - for (const auto& device : AudioCore::GetDeviceListForSink(sink_id)) { - ui->audio_device_combo_box->addItem(QString::fromStdString(device)); + const std::string sink_id = ui->sink_combo_box->itemText(sink_index).toStdString(); + for (const auto& device : AudioCore::Sink::GetDeviceListForSink(sink_id, false)) { + ui->output_combo_box->addItem(QString::fromStdString(device)); + } + + ui->input_combo_box->clear(); + ui->input_combo_box->addItem(QString::fromUtf8(AudioCore::Sink::auto_device_name)); + for (const auto& device : AudioCore::Sink::GetDeviceListForSink(sink_id, true)) { + ui->input_combo_box->addItem(QString::fromStdString(device)); } } -void ConfigureAudio::InitializeAudioOutputSinkComboBox() { - ui->output_sink_combo_box->clear(); - ui->output_sink_combo_box->addItem(QString::fromUtf8(AudioCore::auto_device_name)); +void ConfigureAudio::InitializeAudioSinkComboBox() { + ui->sink_combo_box->clear(); + ui->sink_combo_box->addItem(QString::fromUtf8(AudioCore::Sink::auto_device_name)); - for (const char* id : AudioCore::GetSinkIDs()) { - ui->output_sink_combo_box->addItem(QString::fromUtf8(id)); + for (const char* id : AudioCore::Sink::GetSinkIDs()) { + ui->sink_combo_box->addItem(QString::fromUtf8(id)); } } @@ -164,8 +183,10 @@ void ConfigureAudio::SetupPerGameUI() { ConfigurationShared::SetHighlight(ui->volume_layout, index == 1); }); - ui->output_sink_combo_box->setVisible(false); - ui->output_sink_label->setVisible(false); - ui->audio_device_combo_box->setVisible(false); - ui->audio_device_label->setVisible(false); + ui->sink_combo_box->setVisible(false); + ui->sink_label->setVisible(false); + ui->output_combo_box->setVisible(false); + ui->output_label->setVisible(false); + ui->input_combo_box->setVisible(false); + ui->input_label->setVisible(false); } diff --git a/src/yuzu/configuration/configure_audio.h b/src/yuzu/configuration/configure_audio.h index 08c278eeb..0d03aae1d 100644 --- a/src/yuzu/configuration/configure_audio.h +++ b/src/yuzu/configuration/configure_audio.h @@ -31,14 +31,14 @@ public: private: void changeEvent(QEvent* event) override; - void InitializeAudioOutputSinkComboBox(); + void InitializeAudioSinkComboBox(); void RetranslateUI(); void UpdateAudioDevices(int sink_index); void SetOutputSinkFromSinkID(); - void SetAudioDeviceFromDeviceID(); + void SetAudioDevicesFromDeviceID(); void SetVolumeIndicatorText(int percentage); void SetupPerGameUI(); diff --git a/src/yuzu/configuration/configure_audio.ui b/src/yuzu/configuration/configure_audio.ui index d1ac8ad02..a5bcee415 100644 --- a/src/yuzu/configuration/configure_audio.ui +++ b/src/yuzu/configuration/configure_audio.ui @@ -21,30 +21,44 @@ - + - + Output Engine: - + - + - + - Audio Device: + Output Device - + + + + + + + + + + Input Device + + + + + diff --git a/src/yuzu/configuration/configure_debug.cpp b/src/yuzu/configuration/configure_debug.cpp index 343d2aee1..84808f678 100644 --- a/src/yuzu/configuration/configure_debug.cpp +++ b/src/yuzu/configuration/configure_debug.cpp @@ -44,6 +44,7 @@ void ConfigureDebug::SetConfiguration() { ui->fs_access_log->setEnabled(runtime_lock); ui->fs_access_log->setChecked(Settings::values.enable_fs_access_log.GetValue()); ui->reporting_services->setChecked(Settings::values.reporting_services.GetValue()); + ui->dump_audio_commands->setChecked(Settings::values.dump_audio_commands.GetValue()); ui->quest_flag->setChecked(Settings::values.quest_flag.GetValue()); ui->use_debug_asserts->setChecked(Settings::values.use_debug_asserts.GetValue()); ui->use_auto_stub->setChecked(Settings::values.use_auto_stub.GetValue()); @@ -83,6 +84,7 @@ void ConfigureDebug::ApplyConfiguration() { Settings::values.program_args = ui->homebrew_args_edit->text().toStdString(); Settings::values.enable_fs_access_log = ui->fs_access_log->isChecked(); Settings::values.reporting_services = ui->reporting_services->isChecked(); + Settings::values.dump_audio_commands = ui->dump_audio_commands->isChecked(); Settings::values.quest_flag = ui->quest_flag->isChecked(); Settings::values.use_debug_asserts = ui->use_debug_asserts->isChecked(); Settings::values.use_auto_stub = ui->use_auto_stub->isChecked(); diff --git a/src/yuzu/configuration/configure_debug.ui b/src/yuzu/configuration/configure_debug.ui index 1152fa6c6..4c16274fc 100644 --- a/src/yuzu/configuration/configure_debug.ui +++ b/src/yuzu/configuration/configure_debug.ui @@ -235,6 +235,16 @@ + + + Dump Audio Commands To Console** + + + Enable this to output the latest generated audio command list to the console. Only affects games using the audio renderer. + + + + Enable Verbose Reporting Services** @@ -325,6 +335,7 @@ disable_loop_safety_checks fs_access_log reporting_services + dump_audio_commands quest_flag enable_cpu_debugging use_debug_asserts diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index e60d84054..a120f2662 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -1498,6 +1498,8 @@ void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t if (!LoadROM(filename, program_id, program_index)) return; + system->SetShuttingDown(false); + // Create and start the emulation thread emu_thread = std::make_unique(*system); emit EmulationStarting(emu_thread.get()); @@ -1588,6 +1590,7 @@ void GMainWindow::ShutdownGame() { AllowOSSleep(); + system->SetShuttingDown(true); system->DetachDebugger(); discord_rpc->Pause(); emu_thread->RequestStop(); diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp index 5576fb795..ad7f9d239 100644 --- a/src/yuzu_cmd/config.cpp +++ b/src/yuzu_cmd/config.cpp @@ -322,7 +322,7 @@ void Config::ReadValues() { // Audio ReadSetting("Audio", Settings::values.sink_id); - ReadSetting("Audio", Settings::values.audio_device_id); + ReadSetting("Audio", Settings::values.audio_output_device_id); ReadSetting("Audio", Settings::values.volume); // Miscellaneous