summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitmodules2
-rw-r--r--src/audio_core/CMakeLists.txt253
-rw-r--r--src/audio_core/algorithm/filter.cpp79
-rw-r--r--src/audio_core/algorithm/filter.h61
-rw-r--r--src/audio_core/algorithm/interpolate.cpp232
-rw-r--r--src/audio_core/algorithm/interpolate.h43
-rw-r--r--src/audio_core/audio_core.cpp68
-rw-r--r--src/audio_core/audio_core.h100
-rw-r--r--src/audio_core/audio_event.cpp61
-rw-r--r--src/audio_core/audio_event.h92
-rw-r--r--src/audio_core/audio_in_manager.cpp91
-rw-r--r--src/audio_core/audio_in_manager.h92
-rw-r--r--src/audio_core/audio_manager.cpp80
-rw-r--r--src/audio_core/audio_manager.h101
-rw-r--r--src/audio_core/audio_out.cpp62
-rw-r--r--src/audio_core/audio_out.h49
-rw-r--r--src/audio_core/audio_out_manager.cpp81
-rw-r--r--src/audio_core/audio_out_manager.h89
-rw-r--r--src/audio_core/audio_render_manager.cpp70
-rw-r--r--src/audio_core/audio_render_manager.h103
-rw-r--r--src/audio_core/audio_renderer.cpp343
-rw-r--r--src/audio_core/audio_renderer.h78
-rw-r--r--src/audio_core/behavior_info.cpp104
-rw-r--r--src/audio_core/behavior_info.h71
-rw-r--r--src/audio_core/buffer.h44
-rw-r--r--src/audio_core/codec.cpp77
-rw-r--r--src/audio_core/codec.h43
-rw-r--r--src/audio_core/command_generator.cpp1369
-rw-r--r--src/audio_core/command_generator.h110
-rw-r--r--src/audio_core/common.h132
-rw-r--r--src/audio_core/common/audio_renderer_parameter.h60
-rw-r--r--src/audio_core/common/common.h138
-rw-r--r--src/audio_core/common/feature_support.h105
-rw-r--r--src/audio_core/common/wave_buffer.h35
-rw-r--r--src/audio_core/common/workbuffer_allocator.h100
-rw-r--r--src/audio_core/cubeb_sink.cpp249
-rw-r--r--src/audio_core/cubeb_sink.h35
-rw-r--r--src/audio_core/delay_line.cpp107
-rw-r--r--src/audio_core/delay_line.h49
-rw-r--r--src/audio_core/device/audio_buffer.h21
-rw-r--r--src/audio_core/device/audio_buffers.h304
-rw-r--r--src/audio_core/device/device_session.cpp114
-rw-r--r--src/audio_core/device/device_session.h126
-rw-r--r--src/audio_core/effect_context.cpp320
-rw-r--r--src/audio_core/effect_context.h349
-rw-r--r--src/audio_core/in/audio_in.cpp100
-rw-r--r--src/audio_core/in/audio_in.h147
-rw-r--r--src/audio_core/in/audio_in_system.cpp213
-rw-r--r--src/audio_core/in/audio_in_system.h275
-rw-r--r--src/audio_core/info_updater.cpp511
-rw-r--r--src/audio_core/info_updater.h57
-rw-r--r--src/audio_core/memory_pool.cpp60
-rw-r--r--src/audio_core/memory_pool.h51
-rw-r--r--src/audio_core/mix_context.cpp297
-rw-r--r--src/audio_core/mix_context.h113
-rw-r--r--src/audio_core/null_sink.h32
-rw-r--r--src/audio_core/out/audio_out.cpp100
-rw-r--r--src/audio_core/out/audio_out.h147
-rw-r--r--src/audio_core/out/audio_out_system.cpp207
-rw-r--r--src/audio_core/out/audio_out_system.h257
-rw-r--r--src/audio_core/renderer/adsp/adsp.cpp118
-rw-r--r--src/audio_core/renderer/adsp/adsp.h173
-rw-r--r--src/audio_core/renderer/adsp/audio_renderer.cpp226
-rw-r--r--src/audio_core/renderer/adsp/audio_renderer.h203
-rw-r--r--src/audio_core/renderer/adsp/command_buffer.h21
-rw-r--r--src/audio_core/renderer/adsp/command_list_processor.cpp109
-rw-r--r--src/audio_core/renderer/adsp/command_list_processor.h118
-rw-r--r--src/audio_core/renderer/audio_device.cpp52
-rw-r--r--src/audio_core/renderer/audio_device.h88
-rw-r--r--src/audio_core/renderer/audio_renderer.cpp67
-rw-r--r--src/audio_core/renderer/audio_renderer.h97
-rw-r--r--src/audio_core/renderer/behavior/behavior_info.cpp191
-rw-r--r--src/audio_core/renderer/behavior/behavior_info.h376
-rw-r--r--src/audio_core/renderer/behavior/info_updater.cpp539
-rw-r--r--src/audio_core/renderer/behavior/info_updater.h205
-rw-r--r--src/audio_core/renderer/command/command_buffer.cpp714
-rw-r--r--src/audio_core/renderer/command/command_buffer.h466
-rw-r--r--src/audio_core/renderer/command/command_generator.cpp796
-rw-r--r--src/audio_core/renderer/command/command_generator.h349
-rw-r--r--src/audio_core/renderer/command/command_list_header.h22
-rw-r--r--src/audio_core/renderer/command/command_processing_time_estimator.cpp3620
-rw-r--r--src/audio_core/renderer/command/command_processing_time_estimator.h254
-rw-r--r--src/audio_core/renderer/command/commands.h32
-rw-r--r--src/audio_core/renderer/command/data_source/adpcm.cpp84
-rw-r--r--src/audio_core/renderer/command/data_source/adpcm.h119
-rw-r--r--src/audio_core/renderer/command/data_source/decode.cpp428
-rw-r--r--src/audio_core/renderer/command/data_source/decode.h59
-rw-r--r--src/audio_core/renderer/command/data_source/pcm_float.cpp86
-rw-r--r--src/audio_core/renderer/command/data_source/pcm_float.h113
-rw-r--r--src/audio_core/renderer/command/data_source/pcm_int16.cpp87
-rw-r--r--src/audio_core/renderer/command/data_source/pcm_int16.h110
-rw-r--r--src/audio_core/renderer/command/effect/aux_.cpp207
-rw-r--r--src/audio_core/renderer/command/effect/aux_.h66
-rw-r--r--src/audio_core/renderer/command/effect/biquad_filter.cpp118
-rw-r--r--src/audio_core/renderer/command/effect/biquad_filter.h74
-rw-r--r--src/audio_core/renderer/command/effect/capture.cpp142
-rw-r--r--src/audio_core/renderer/command/effect/capture.h62
-rw-r--r--src/audio_core/renderer/command/effect/compressor.cpp156
-rw-r--r--src/audio_core/renderer/command/effect/compressor.h60
-rw-r--r--src/audio_core/renderer/command/effect/delay.cpp238
-rw-r--r--src/audio_core/renderer/command/effect/delay.h60
-rw-r--r--src/audio_core/renderer/command/effect/i3dl2_reverb.cpp437
-rw-r--r--src/audio_core/renderer/command/effect/i3dl2_reverb.h60
-rw-r--r--src/audio_core/renderer/command/effect/light_limiter.cpp222
-rw-r--r--src/audio_core/renderer/command/effect/light_limiter.h103
-rw-r--r--src/audio_core/renderer/command/effect/multi_tap_biquad_filter.cpp45
-rw-r--r--src/audio_core/renderer/command/effect/multi_tap_biquad_filter.h59
-rw-r--r--src/audio_core/renderer/command/effect/reverb.cpp440
-rw-r--r--src/audio_core/renderer/command/effect/reverb.h62
-rw-r--r--src/audio_core/renderer/command/icommand.h93
-rw-r--r--src/audio_core/renderer/command/mix/clear_mix.cpp24
-rw-r--r--src/audio_core/renderer/command/mix/clear_mix.h45
-rw-r--r--src/audio_core/renderer/command/mix/copy_mix.cpp27
-rw-r--r--src/audio_core/renderer/command/mix/copy_mix.h49
-rw-r--r--src/audio_core/renderer/command/mix/depop_for_mix_buffers.cpp64
-rw-r--r--src/audio_core/renderer/command/mix/depop_for_mix_buffers.h55
-rw-r--r--src/audio_core/renderer/command/mix/depop_prepare.cpp36
-rw-r--r--src/audio_core/renderer/command/mix/depop_prepare.h54
-rw-r--r--src/audio_core/renderer/command/mix/mix.cpp70
-rw-r--r--src/audio_core/renderer/command/mix/mix.h54
-rw-r--r--src/audio_core/renderer/command/mix/mix_ramp.cpp94
-rw-r--r--src/audio_core/renderer/command/mix/mix_ramp.h73
-rw-r--r--src/audio_core/renderer/command/mix/mix_ramp_grouped.cpp65
-rw-r--r--src/audio_core/renderer/command/mix/mix_ramp_grouped.h61
-rw-r--r--src/audio_core/renderer/command/mix/volume.cpp72
-rw-r--r--src/audio_core/renderer/command/mix/volume.h53
-rw-r--r--src/audio_core/renderer/command/mix/volume_ramp.cpp84
-rw-r--r--src/audio_core/renderer/command/mix/volume_ramp.h56
-rw-r--r--src/audio_core/renderer/command/performance/performance.cpp43
-rw-r--r--src/audio_core/renderer/command/performance/performance.h51
-rw-r--r--src/audio_core/renderer/command/resample/downmix_6ch_to_2ch.cpp74
-rw-r--r--src/audio_core/renderer/command/resample/downmix_6ch_to_2ch.h59
-rw-r--r--src/audio_core/renderer/command/resample/resample.cpp883
-rw-r--r--src/audio_core/renderer/command/resample/resample.h29
-rw-r--r--src/audio_core/renderer/command/resample/upsample.cpp262
-rw-r--r--src/audio_core/renderer/command/resample/upsample.h60
-rw-r--r--src/audio_core/renderer/command/sink/circular_buffer.cpp48
-rw-r--r--src/audio_core/renderer/command/sink/circular_buffer.h55
-rw-r--r--src/audio_core/renderer/command/sink/device.cpp55
-rw-r--r--src/audio_core/renderer/command/sink/device.h57
-rw-r--r--src/audio_core/renderer/effect/aux_.cpp93
-rw-r--r--src/audio_core/renderer/effect/aux_.h123
-rw-r--r--src/audio_core/renderer/effect/biquad_filter.cpp52
-rw-r--r--src/audio_core/renderer/effect/biquad_filter.h79
-rw-r--r--src/audio_core/renderer/effect/buffer_mixer.cpp49
-rw-r--r--src/audio_core/renderer/effect/buffer_mixer.h75
-rw-r--r--src/audio_core/renderer/effect/capture.cpp82
-rw-r--r--src/audio_core/renderer/effect/capture.h65
-rw-r--r--src/audio_core/renderer/effect/compressor.cpp40
-rw-r--r--src/audio_core/renderer/effect/compressor.h106
-rw-r--r--src/audio_core/renderer/effect/delay.cpp93
-rw-r--r--src/audio_core/renderer/effect/delay.h135
-rw-r--r--src/audio_core/renderer/effect/effect_context.cpp41
-rw-r--r--src/audio_core/renderer/effect/effect_context.h75
-rw-r--r--src/audio_core/renderer/effect/effect_info_base.h435
-rw-r--r--src/audio_core/renderer/effect/effect_reset.h71
-rw-r--r--src/audio_core/renderer/effect/effect_result_state.h16
-rw-r--r--src/audio_core/renderer/effect/i3dl2.cpp94
-rw-r--r--src/audio_core/renderer/effect/i3dl2.h200
-rw-r--r--src/audio_core/renderer/effect/light_limiter.cpp81
-rw-r--r--src/audio_core/renderer/effect/light_limiter.h138
-rw-r--r--src/audio_core/renderer/effect/reverb.cpp93
-rw-r--r--src/audio_core/renderer/effect/reverb.h190
-rw-r--r--src/audio_core/renderer/memory/address_info.h125
-rw-r--r--src/audio_core/renderer/memory/memory_pool_info.cpp61
-rw-r--r--src/audio_core/renderer/memory/memory_pool_info.h170
-rw-r--r--src/audio_core/renderer/memory/pool_mapper.cpp243
-rw-r--r--src/audio_core/renderer/memory/pool_mapper.h179
-rw-r--r--src/audio_core/renderer/mix/mix_context.cpp141
-rw-r--r--src/audio_core/renderer/mix/mix_context.h124
-rw-r--r--src/audio_core/renderer/mix/mix_info.cpp120
-rw-r--r--src/audio_core/renderer/mix/mix_info.h124
-rw-r--r--src/audio_core/renderer/nodes/bit_array.h25
-rw-r--r--src/audio_core/renderer/nodes/edge_matrix.cpp38
-rw-r--r--src/audio_core/renderer/nodes/edge_matrix.h82
-rw-r--r--src/audio_core/renderer/nodes/node_states.cpp141
-rw-r--r--src/audio_core/renderer/nodes/node_states.h195
-rw-r--r--src/audio_core/renderer/performance/detail_aspect.cpp25
-rw-r--r--src/audio_core/renderer/performance/detail_aspect.h33
-rw-r--r--src/audio_core/renderer/performance/entry_aspect.cpp23
-rw-r--r--src/audio_core/renderer/performance/entry_aspect.h32
-rw-r--r--src/audio_core/renderer/performance/performance_detail.h50
-rw-r--r--src/audio_core/renderer/performance/performance_entry.h37
-rw-r--r--src/audio_core/renderer/performance/performance_entry_addresses.h17
-rw-r--r--src/audio_core/renderer/performance/performance_frame_header.h36
-rw-r--r--src/audio_core/renderer/performance/performance_manager.cpp645
-rw-r--r--src/audio_core/renderer/performance/performance_manager.h273
-rw-r--r--src/audio_core/renderer/sink/circular_buffer_sink_info.cpp76
-rw-r--r--src/audio_core/renderer/sink/circular_buffer_sink_info.h41
-rw-r--r--src/audio_core/renderer/sink/device_sink_info.cpp57
-rw-r--r--src/audio_core/renderer/sink/device_sink_info.h40
-rw-r--r--src/audio_core/renderer/sink/sink_context.cpp21
-rw-r--r--src/audio_core/renderer/sink/sink_context.h47
-rw-r--r--src/audio_core/renderer/sink/sink_info_base.cpp51
-rw-r--r--src/audio_core/renderer/sink/sink_info_base.h177
-rw-r--r--src/audio_core/renderer/splitter/splitter_context.cpp217
-rw-r--r--src/audio_core/renderer/splitter/splitter_context.h189
-rw-r--r--src/audio_core/renderer/splitter/splitter_destinations_data.cpp87
-rw-r--r--src/audio_core/renderer/splitter/splitter_destinations_data.h135
-rw-r--r--src/audio_core/renderer/splitter/splitter_info.cpp79
-rw-r--r--src/audio_core/renderer/splitter/splitter_info.h107
-rw-r--r--src/audio_core/renderer/system.cpp802
-rw-r--r--src/audio_core/renderer/system.h307
-rw-r--r--src/audio_core/renderer/system_manager.cpp162
-rw-r--r--src/audio_core/renderer/system_manager.h113
-rw-r--r--src/audio_core/renderer/upsampler/upsampler_info.cpp6
-rw-r--r--src/audio_core/renderer/upsampler/upsampler_info.h35
-rw-r--r--src/audio_core/renderer/upsampler/upsampler_manager.cpp44
-rw-r--r--src/audio_core/renderer/upsampler/upsampler_manager.h45
-rw-r--r--src/audio_core/renderer/upsampler/upsampler_state.h40
-rw-r--r--src/audio_core/renderer/voice/voice_channel_resource.h38
-rw-r--r--src/audio_core/renderer/voice/voice_context.cpp86
-rw-r--r--src/audio_core/renderer/voice/voice_context.h126
-rw-r--r--src/audio_core/renderer/voice/voice_info.cpp408
-rw-r--r--src/audio_core/renderer/voice/voice_info.h378
-rw-r--r--src/audio_core/renderer/voice/voice_state.h70
-rw-r--r--src/audio_core/sdl2_sink.cpp160
-rw-r--r--src/audio_core/sdl2_sink.h28
-rw-r--r--src/audio_core/sink.h30
-rw-r--r--src/audio_core/sink/cubeb_sink.cpp651
-rw-r--r--src/audio_core/sink/cubeb_sink.h110
-rw-r--r--src/audio_core/sink/null_sink.h52
-rw-r--r--src/audio_core/sink/sdl2_sink.cpp556
-rw-r--r--src/audio_core/sink/sdl2_sink.h101
-rw-r--r--src/audio_core/sink/sink.h106
-rw-r--r--src/audio_core/sink/sink_details.cpp (renamed from src/audio_core/sink_details.cpp)27
-rw-r--r--src/audio_core/sink/sink_details.h43
-rw-r--r--src/audio_core/sink/sink_stream.h224
-rw-r--r--src/audio_core/sink_context.cpp47
-rw-r--r--src/audio_core/sink_context.h95
-rw-r--r--src/audio_core/sink_details.h23
-rw-r--r--src/audio_core/sink_stream.h35
-rw-r--r--src/audio_core/splitter_context.cpp616
-rw-r--r--src/audio_core/splitter_context.h218
-rw-r--r--src/audio_core/stream.cpp175
-rw-r--r--src/audio_core/stream.h130
-rw-r--r--src/audio_core/voice_context.cpp579
-rw-r--r--src/audio_core/voice_context.h302
-rw-r--r--src/common/CMakeLists.txt3
-rw-r--r--src/common/atomic_helpers.h772
-rw-r--r--src/common/fixed_point.h726
-rw-r--r--src/common/reader_writer_queue.h941
-rw-r--r--src/common/settings.cpp3
-rw-r--r--src/common/settings.h4
-rw-r--r--src/core/core.cpp51
-rw-r--r--src/core/core.h19
-rw-r--r--src/core/hle/kernel/hle_ipc.cpp38
-rw-r--r--src/core/hle/kernel/hle_ipc.h8
-rw-r--r--src/core/hle/kernel/kernel.cpp34
-rw-r--r--src/core/hle/kernel/kernel.h3
-rw-r--r--src/core/hle/service/am/am.cpp10
-rw-r--r--src/core/hle/service/am/am.h1
-rw-r--r--src/core/hle/service/audio/audin_u.cpp384
-rw-r--r--src/core/hle/service/audio/audin_u.h47
-rw-r--r--src/core/hle/service/audio/audout_u.cpp327
-rw-r--r--src/core/hle/service/audio/audout_u.h21
-rw-r--r--src/core/hle/service/audio/audren_u.cpp692
-rw-r--r--src/core/hle/service/audio/audren_u.h22
-rw-r--r--src/core/hle/service/audio/errors.h9
-rw-r--r--src/core/hle/service/audio/hwopus.cpp2
-rw-r--r--src/core/memory.cpp6
-rw-r--r--src/core/memory.h13
-rw-r--r--src/yuzu/configuration/config.cpp6
-rw-r--r--src/yuzu/configuration/configure_audio.cpp95
-rw-r--r--src/yuzu/configuration/configure_audio.h4
-rw-r--r--src/yuzu/configuration/configure_audio.ui28
-rw-r--r--src/yuzu/configuration/configure_debug.cpp2
-rw-r--r--src/yuzu/configuration/configure_debug.ui11
-rw-r--r--src/yuzu/main.cpp3
-rw-r--r--src/yuzu_cmd/config.cpp2
270 files changed, 33704 insertions, 8437 deletions
diff --git a/.gitmodules b/.gitmodules
index dc92d0a4b..ed533f8d4 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -3,7 +3,7 @@
3 url = https://github.com/benhoyt/inih.git 3 url = https://github.com/benhoyt/inih.git
4[submodule "cubeb"] 4[submodule "cubeb"]
5 path = externals/cubeb 5 path = externals/cubeb
6 url = https://github.com/kinetiknz/cubeb.git 6 url = https://github.com/mozilla/cubeb.git
7[submodule "dynarmic"] 7[submodule "dynarmic"]
8 path = externals/dynarmic 8 path = externals/dynarmic
9 url = https://github.com/MerryMage/dynarmic.git 9 url = https://github.com/MerryMage/dynarmic.git
diff --git a/src/audio_core/CMakeLists.txt b/src/audio_core/CMakeLists.txt
index 89575a53e..2971c42a2 100644
--- a/src/audio_core/CMakeLists.txt
+++ b/src/audio_core/CMakeLists.txt
@@ -1,54 +1,218 @@
1add_library(audio_core STATIC 1add_library(audio_core STATIC
2 algorithm/filter.cpp 2 audio_core.cpp
3 algorithm/filter.h 3 audio_core.h
4 algorithm/interpolate.cpp 4 audio_event.h
5 algorithm/interpolate.h 5 audio_event.cpp
6 audio_out.cpp 6 audio_render_manager.cpp
7 audio_out.h 7 audio_render_manager.h
8 audio_renderer.cpp 8 audio_in_manager.cpp
9 audio_renderer.h 9 audio_in_manager.h
10 behavior_info.cpp 10 audio_out_manager.cpp
11 behavior_info.h 11 audio_out_manager.h
12 buffer.h 12 audio_manager.cpp
13 codec.cpp 13 audio_manager.h
14 codec.h 14 common/audio_renderer_parameter.h
15 command_generator.cpp 15 common/common.h
16 command_generator.h 16 common/feature_support.h
17 common.h 17 common/wave_buffer.h
18 delay_line.cpp 18 common/workbuffer_allocator.h
19 delay_line.h 19 device/audio_buffer.h
20 effect_context.cpp 20 device/audio_buffers.h
21 effect_context.h 21 device/device_session.cpp
22 info_updater.cpp 22 device/device_session.h
23 info_updater.h 23 in/audio_in.cpp
24 memory_pool.cpp 24 in/audio_in.h
25 memory_pool.h 25 in/audio_in_system.cpp
26 mix_context.cpp 26 in/audio_in_system.h
27 mix_context.h 27 out/audio_out.cpp
28 null_sink.h 28 out/audio_out.h
29 sink.h 29 out/audio_out_system.cpp
30 sink_context.cpp 30 out/audio_out_system.h
31 sink_context.h 31 renderer/adsp/adsp.cpp
32 sink_details.cpp 32 renderer/adsp/adsp.h
33 sink_details.h 33 renderer/adsp/audio_renderer.cpp
34 sink_stream.h 34 renderer/adsp/audio_renderer.h
35 splitter_context.cpp 35 renderer/adsp/command_buffer.h
36 splitter_context.h 36 renderer/adsp/command_list_processor.cpp
37 stream.cpp 37 renderer/adsp/command_list_processor.h
38 stream.h 38 renderer/audio_device.cpp
39 voice_context.cpp 39 renderer/audio_device.h
40 voice_context.h 40 renderer/audio_renderer.h
41 41 renderer/audio_renderer.cpp
42 $<$<BOOL:${ENABLE_CUBEB}>:cubeb_sink.cpp cubeb_sink.h> 42 renderer/behavior/behavior_info.cpp
43 $<$<BOOL:${ENABLE_SDL2}>:sdl2_sink.cpp sdl2_sink.h> 43 renderer/behavior/behavior_info.h
44 renderer/behavior/info_updater.cpp
45 renderer/behavior/info_updater.h
46 renderer/command/data_source/adpcm.cpp
47 renderer/command/data_source/adpcm.h
48 renderer/command/data_source/decode.cpp
49 renderer/command/data_source/decode.h
50 renderer/command/data_source/pcm_float.cpp
51 renderer/command/data_source/pcm_float.h
52 renderer/command/data_source/pcm_int16.cpp
53 renderer/command/data_source/pcm_int16.h
54 renderer/command/effect/aux_.cpp
55 renderer/command/effect/aux_.h
56 renderer/command/effect/biquad_filter.cpp
57 renderer/command/effect/biquad_filter.h
58 renderer/command/effect/capture.cpp
59 renderer/command/effect/capture.h
60 renderer/command/effect/compressor.cpp
61 renderer/command/effect/compressor.h
62 renderer/command/effect/delay.cpp
63 renderer/command/effect/delay.h
64 renderer/command/effect/i3dl2_reverb.cpp
65 renderer/command/effect/i3dl2_reverb.h
66 renderer/command/effect/light_limiter.cpp
67 renderer/command/effect/light_limiter.h
68 renderer/command/effect/multi_tap_biquad_filter.cpp
69 renderer/command/effect/multi_tap_biquad_filter.h
70 renderer/command/effect/reverb.cpp
71 renderer/command/effect/reverb.h
72 renderer/command/mix/clear_mix.cpp
73 renderer/command/mix/clear_mix.h
74 renderer/command/mix/copy_mix.cpp
75 renderer/command/mix/copy_mix.h
76 renderer/command/mix/depop_for_mix_buffers.cpp
77 renderer/command/mix/depop_for_mix_buffers.h
78 renderer/command/mix/depop_prepare.cpp
79 renderer/command/mix/depop_prepare.h
80 renderer/command/mix/mix.cpp
81 renderer/command/mix/mix.h
82 renderer/command/mix/mix_ramp.cpp
83 renderer/command/mix/mix_ramp.h
84 renderer/command/mix/mix_ramp_grouped.cpp
85 renderer/command/mix/mix_ramp_grouped.h
86 renderer/command/mix/volume.cpp
87 renderer/command/mix/volume.h
88 renderer/command/mix/volume_ramp.cpp
89 renderer/command/mix/volume_ramp.h
90 renderer/command/performance/performance.cpp
91 renderer/command/performance/performance.h
92 renderer/command/resample/downmix_6ch_to_2ch.cpp
93 renderer/command/resample/downmix_6ch_to_2ch.h
94 renderer/command/resample/resample.h
95 renderer/command/resample/resample.cpp
96 renderer/command/resample/upsample.cpp
97 renderer/command/resample/upsample.h
98 renderer/command/sink/device.cpp
99 renderer/command/sink/device.h
100 renderer/command/sink/circular_buffer.cpp
101 renderer/command/sink/circular_buffer.h
102 renderer/command/command_buffer.cpp
103 renderer/command/command_buffer.h
104 renderer/command/command_generator.cpp
105 renderer/command/command_generator.h
106 renderer/command/command_list_header.h
107 renderer/command/command_processing_time_estimator.cpp
108 renderer/command/command_processing_time_estimator.h
109 renderer/command/commands.h
110 renderer/command/icommand.h
111 renderer/effect/aux_.cpp
112 renderer/effect/aux_.h
113 renderer/effect/biquad_filter.cpp
114 renderer/effect/biquad_filter.h
115 renderer/effect/buffer_mixer.cpp
116 renderer/effect/buffer_mixer.h
117 renderer/effect/capture.cpp
118 renderer/effect/capture.h
119 renderer/effect/compressor.cpp
120 renderer/effect/compressor.h
121 renderer/effect/delay.cpp
122 renderer/effect/delay.h
123 renderer/effect/effect_context.cpp
124 renderer/effect/effect_context.h
125 renderer/effect/effect_info_base.h
126 renderer/effect/effect_reset.h
127 renderer/effect/effect_result_state.h
128 renderer/effect/i3dl2.cpp
129 renderer/effect/i3dl2.h
130 renderer/effect/light_limiter.cpp
131 renderer/effect/light_limiter.h
132 renderer/effect/reverb.h
133 renderer/effect/reverb.cpp
134 renderer/mix/mix_context.cpp
135 renderer/mix/mix_context.h
136 renderer/mix/mix_info.cpp
137 renderer/mix/mix_info.h
138 renderer/memory/address_info.h
139 renderer/memory/memory_pool_info.cpp
140 renderer/memory/memory_pool_info.h
141 renderer/memory/pool_mapper.cpp
142 renderer/memory/pool_mapper.h
143 renderer/nodes/bit_array.h
144 renderer/nodes/edge_matrix.cpp
145 renderer/nodes/edge_matrix.h
146 renderer/nodes/node_states.cpp
147 renderer/nodes/node_states.h
148 renderer/performance/detail_aspect.cpp
149 renderer/performance/detail_aspect.h
150 renderer/performance/entry_aspect.cpp
151 renderer/performance/entry_aspect.h
152 renderer/performance/performance_detail.h
153 renderer/performance/performance_entry.h
154 renderer/performance/performance_entry_addresses.h
155 renderer/performance/performance_frame_header.h
156 renderer/performance/performance_manager.cpp
157 renderer/performance/performance_manager.h
158 renderer/sink/circular_buffer_sink_info.cpp
159 renderer/sink/circular_buffer_sink_info.h
160 renderer/sink/device_sink_info.cpp
161 renderer/sink/device_sink_info.h
162 renderer/sink/sink_context.cpp
163 renderer/sink/sink_context.h
164 renderer/sink/sink_info_base.cpp
165 renderer/sink/sink_info_base.h
166 renderer/splitter/splitter_context.cpp
167 renderer/splitter/splitter_context.h
168 renderer/splitter/splitter_destinations_data.cpp
169 renderer/splitter/splitter_destinations_data.h
170 renderer/splitter/splitter_info.cpp
171 renderer/splitter/splitter_info.h
172 renderer/system.cpp
173 renderer/system.h
174 renderer/system_manager.cpp
175 renderer/system_manager.h
176 renderer/upsampler/upsampler_info.h
177 renderer/upsampler/upsampler_manager.cpp
178 renderer/upsampler/upsampler_manager.h
179 renderer/upsampler/upsampler_state.h
180 renderer/voice/voice_channel_resource.h
181 renderer/voice/voice_context.cpp
182 renderer/voice/voice_context.h
183 renderer/voice/voice_info.cpp
184 renderer/voice/voice_info.h
185 renderer/voice/voice_state.h
186 sink/cubeb_sink.cpp
187 sink/cubeb_sink.h
188 sink/null_sink.h
189 sink/sdl2_sink.cpp
190 sink/sdl2_sink.h
191 sink/sink.h
192 sink/sink_details.cpp
193 sink/sink_details.h
194 sink/sink_stream.h
44) 195)
45 196
46create_target_directory_groups(audio_core) 197create_target_directory_groups(audio_core)
47 198
48if (NOT MSVC) 199if (MSVC)
200 target_compile_options(audio_core PRIVATE
201 /we4242 # 'identifier': conversion from 'type1' to 'type2', possible loss of data
202 /we4244 # 'conversion': conversion from 'type1' to 'type2', possible loss of data
203 /we4245 # 'conversion': conversion from 'type1' to 'type2', signed/unsigned mismatch
204 /we4254 # 'operator': conversion from 'type1:field_bits' to 'type2:field_bits', possible loss of data
205 /we4456 # Declaration of 'identifier' hides previous local declaration
206 /we4457 # Declaration of 'identifier' hides function parameter
207 /we4458 # Declaration of 'identifier' hides class member
208 /we4459 # Declaration of 'identifier' hides global declaration
209 )
210else()
49 target_compile_options(audio_core PRIVATE 211 target_compile_options(audio_core PRIVATE
50 -Werror=conversion 212 -Werror=conversion
51 -Werror=ignored-qualifiers 213 -Werror=ignored-qualifiers
214 -Werror=shadow
215 -Werror=unused-variable
52 216
53 $<$<CXX_COMPILER_ID:GNU>:-Werror=unused-but-set-parameter> 217 $<$<CXX_COMPILER_ID:GNU>:-Werror=unused-but-set-parameter>
54 $<$<CXX_COMPILER_ID:GNU>:-Werror=unused-but-set-variable> 218 $<$<CXX_COMPILER_ID:GNU>:-Werror=unused-but-set-variable>
@@ -58,6 +222,9 @@ if (NOT MSVC)
58endif() 222endif()
59 223
60target_link_libraries(audio_core PUBLIC common core) 224target_link_libraries(audio_core PUBLIC common core)
225if (ARCHITECTURE_x86_64)
226 target_link_libraries(audio_core PRIVATE dynarmic)
227endif()
61 228
62if(ENABLE_CUBEB) 229if(ENABLE_CUBEB)
63 target_link_libraries(audio_core PRIVATE cubeb) 230 target_link_libraries(audio_core PRIVATE cubeb)
diff --git a/src/audio_core/algorithm/filter.cpp b/src/audio_core/algorithm/filter.cpp
deleted file mode 100644
index 96e37991f..000000000
--- a/src/audio_core/algorithm/filter.cpp
+++ /dev/null
@@ -1,79 +0,0 @@
1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#define _USE_MATH_DEFINES
5
6#include <algorithm>
7#include <array>
8#include <cmath>
9#include <vector>
10#include "audio_core/algorithm/filter.h"
11#include "common/common_types.h"
12
13namespace AudioCore {
14
15Filter Filter::LowPass(double cutoff, double Q) {
16 const double w0 = 2.0 * M_PI * cutoff;
17 const double sin_w0 = std::sin(w0);
18 const double cos_w0 = std::cos(w0);
19 const double alpha = sin_w0 / (2 * Q);
20
21 const double a0 = 1 + alpha;
22 const double a1 = -2.0 * cos_w0;
23 const double a2 = 1 - alpha;
24 const double b0 = 0.5 * (1 - cos_w0);
25 const double b1 = 1.0 * (1 - cos_w0);
26 const double b2 = 0.5 * (1 - cos_w0);
27
28 return {a0, a1, a2, b0, b1, b2};
29}
30
31Filter::Filter() : Filter(1.0, 0.0, 0.0, 1.0, 0.0, 0.0) {}
32
33Filter::Filter(double a0_, double a1_, double a2_, double b0_, double b1_, double b2_)
34 : a1(a1_ / a0_), a2(a2_ / a0_), b0(b0_ / a0_), b1(b1_ / a0_), b2(b2_ / a0_) {}
35
36void Filter::Process(std::vector<s16>& signal) {
37 const std::size_t num_frames = signal.size() / 2;
38 for (std::size_t i = 0; i < num_frames; i++) {
39 std::rotate(in.begin(), in.end() - 1, in.end());
40 std::rotate(out.begin(), out.end() - 1, out.end());
41
42 for (std::size_t ch = 0; ch < channel_count; ch++) {
43 in[0][ch] = signal[i * channel_count + ch];
44
45 out[0][ch] = b0 * in[0][ch] + b1 * in[1][ch] + b2 * in[2][ch] - a1 * out[1][ch] -
46 a2 * out[2][ch];
47
48 signal[i * 2 + ch] = static_cast<s16>(std::clamp(out[0][ch], -32768.0, 32767.0));
49 }
50 }
51}
52
53/// Calculates the appropriate Q for each biquad in a cascading filter.
54/// @param total_count The total number of biquads to be cascaded.
55/// @param index 0-index of the biquad to calculate the Q value for.
56static double CascadingBiquadQ(std::size_t total_count, std::size_t index) {
57 const auto pole =
58 M_PI * static_cast<double>(2 * index + 1) / (4.0 * static_cast<double>(total_count));
59 return 1.0 / (2.0 * std::cos(pole));
60}
61
62CascadingFilter CascadingFilter::LowPass(double cutoff, std::size_t cascade_size) {
63 std::vector<Filter> cascade(cascade_size);
64 for (std::size_t i = 0; i < cascade_size; i++) {
65 cascade[i] = Filter::LowPass(cutoff, CascadingBiquadQ(cascade_size, i));
66 }
67 return CascadingFilter{std::move(cascade)};
68}
69
70CascadingFilter::CascadingFilter() = default;
71CascadingFilter::CascadingFilter(std::vector<Filter> filters_) : filters(std::move(filters_)) {}
72
73void CascadingFilter::Process(std::vector<s16>& signal) {
74 for (auto& filter : filters) {
75 filter.Process(signal);
76 }
77}
78
79} // namespace AudioCore
diff --git a/src/audio_core/algorithm/filter.h b/src/audio_core/algorithm/filter.h
deleted file mode 100644
index 2586f0079..000000000
--- a/src/audio_core/algorithm/filter.h
+++ /dev/null
@@ -1,61 +0,0 @@
1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7#include <vector>
8#include "common/common_types.h"
9
10namespace AudioCore {
11
12/// Digital biquad filter:
13///
14/// b0 + b1 z^-1 + b2 z^-2
15/// H(z) = ------------------------
16/// a0 + a1 z^-1 + b2 z^-2
17class Filter {
18public:
19 /// Creates a low-pass filter.
20 /// @param cutoff Determines the cutoff frequency. A value from 0.0 to 1.0.
21 /// @param Q Determines the quality factor of this filter.
22 static Filter LowPass(double cutoff, double Q = 0.7071);
23
24 /// Passthrough filter.
25 Filter();
26
27 Filter(double a0_, double a1_, double a2_, double b0_, double b1_, double b2_);
28
29 void Process(std::vector<s16>& signal);
30
31private:
32 static constexpr std::size_t channel_count = 2;
33
34 /// Coefficients are in normalized form (a0 = 1.0).
35 double a1, a2, b0, b1, b2;
36 /// Input History
37 std::array<std::array<double, channel_count>, 3> in;
38 /// Output History
39 std::array<std::array<double, channel_count>, 3> out;
40};
41
42/// Cascade filters to build up higher-order filters from lower-order ones.
43class CascadingFilter {
44public:
45 /// Creates a cascading low-pass filter.
46 /// @param cutoff Determines the cutoff frequency. A value from 0.0 to 1.0.
47 /// @param cascade_size Number of biquads in cascade.
48 static CascadingFilter LowPass(double cutoff, std::size_t cascade_size);
49
50 /// Passthrough.
51 CascadingFilter();
52
53 explicit CascadingFilter(std::vector<Filter> filters_);
54
55 void Process(std::vector<s16>& signal);
56
57private:
58 std::vector<Filter> filters;
59};
60
61} // namespace AudioCore
diff --git a/src/audio_core/algorithm/interpolate.cpp b/src/audio_core/algorithm/interpolate.cpp
deleted file mode 100644
index d2a4cd53f..000000000
--- a/src/audio_core/algorithm/interpolate.cpp
+++ /dev/null
@@ -1,232 +0,0 @@
1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#define _USE_MATH_DEFINES
5
6#include <algorithm>
7#include <climits>
8#include <cmath>
9#include <vector>
10
11#include "audio_core/algorithm/interpolate.h"
12#include "common/common_types.h"
13#include "common/logging/log.h"
14
15namespace AudioCore {
16
17constexpr std::array<s16, 512> curve_lut0{
18 6600, 19426, 6722, 3, 6479, 19424, 6845, 9, 6359, 19419, 6968, 15, 6239,
19 19412, 7093, 22, 6121, 19403, 7219, 28, 6004, 19391, 7345, 34, 5888, 19377,
20 7472, 41, 5773, 19361, 7600, 48, 5659, 19342, 7728, 55, 5546, 19321, 7857,
21 62, 5434, 19298, 7987, 69, 5323, 19273, 8118, 77, 5213, 19245, 8249, 84,
22 5104, 19215, 8381, 92, 4997, 19183, 8513, 101, 4890, 19148, 8646, 109, 4785,
23 19112, 8780, 118, 4681, 19073, 8914, 127, 4579, 19031, 9048, 137, 4477, 18988,
24 9183, 147, 4377, 18942, 9318, 157, 4277, 18895, 9454, 168, 4179, 18845, 9590,
25 179, 4083, 18793, 9726, 190, 3987, 18738, 9863, 202, 3893, 18682, 10000, 215,
26 3800, 18624, 10137, 228, 3709, 18563, 10274, 241, 3618, 18500, 10411, 255, 3529,
27 18436, 10549, 270, 3441, 18369, 10687, 285, 3355, 18300, 10824, 300, 3269, 18230,
28 10962, 317, 3186, 18157, 11100, 334, 3103, 18082, 11238, 351, 3022, 18006, 11375,
29 369, 2942, 17927, 11513, 388, 2863, 17847, 11650, 408, 2785, 17765, 11788, 428,
30 2709, 17681, 11925, 449, 2635, 17595, 12062, 471, 2561, 17507, 12198, 494, 2489,
31 17418, 12334, 517, 2418, 17327, 12470, 541, 2348, 17234, 12606, 566, 2280, 17140,
32 12741, 592, 2213, 17044, 12876, 619, 2147, 16946, 13010, 647, 2083, 16846, 13144,
33 675, 2020, 16745, 13277, 704, 1958, 16643, 13409, 735, 1897, 16539, 13541, 766,
34 1838, 16434, 13673, 798, 1780, 16327, 13803, 832, 1723, 16218, 13933, 866, 1667,
35 16109, 14062, 901, 1613, 15998, 14191, 937, 1560, 15885, 14318, 975, 1508, 15772,
36 14445, 1013, 1457, 15657, 14571, 1052, 1407, 15540, 14695, 1093, 1359, 15423, 14819,
37 1134, 1312, 15304, 14942, 1177, 1266, 15185, 15064, 1221, 1221, 15064, 15185, 1266,
38 1177, 14942, 15304, 1312, 1134, 14819, 15423, 1359, 1093, 14695, 15540, 1407, 1052,
39 14571, 15657, 1457, 1013, 14445, 15772, 1508, 975, 14318, 15885, 1560, 937, 14191,
40 15998, 1613, 901, 14062, 16109, 1667, 866, 13933, 16218, 1723, 832, 13803, 16327,
41 1780, 798, 13673, 16434, 1838, 766, 13541, 16539, 1897, 735, 13409, 16643, 1958,
42 704, 13277, 16745, 2020, 675, 13144, 16846, 2083, 647, 13010, 16946, 2147, 619,
43 12876, 17044, 2213, 592, 12741, 17140, 2280, 566, 12606, 17234, 2348, 541, 12470,
44 17327, 2418, 517, 12334, 17418, 2489, 494, 12198, 17507, 2561, 471, 12062, 17595,
45 2635, 449, 11925, 17681, 2709, 428, 11788, 17765, 2785, 408, 11650, 17847, 2863,
46 388, 11513, 17927, 2942, 369, 11375, 18006, 3022, 351, 11238, 18082, 3103, 334,
47 11100, 18157, 3186, 317, 10962, 18230, 3269, 300, 10824, 18300, 3355, 285, 10687,
48 18369, 3441, 270, 10549, 18436, 3529, 255, 10411, 18500, 3618, 241, 10274, 18563,
49 3709, 228, 10137, 18624, 3800, 215, 10000, 18682, 3893, 202, 9863, 18738, 3987,
50 190, 9726, 18793, 4083, 179, 9590, 18845, 4179, 168, 9454, 18895, 4277, 157,
51 9318, 18942, 4377, 147, 9183, 18988, 4477, 137, 9048, 19031, 4579, 127, 8914,
52 19073, 4681, 118, 8780, 19112, 4785, 109, 8646, 19148, 4890, 101, 8513, 19183,
53 4997, 92, 8381, 19215, 5104, 84, 8249, 19245, 5213, 77, 8118, 19273, 5323,
54 69, 7987, 19298, 5434, 62, 7857, 19321, 5546, 55, 7728, 19342, 5659, 48,
55 7600, 19361, 5773, 41, 7472, 19377, 5888, 34, 7345, 19391, 6004, 28, 7219,
56 19403, 6121, 22, 7093, 19412, 6239, 15, 6968, 19419, 6359, 9, 6845, 19424,
57 6479, 3, 6722, 19426, 6600};
58
59constexpr std::array<s16, 512> curve_lut1{
60 -68, 32639, 69, -5, -200, 32630, 212, -15, -328, 32613, 359, -26, -450,
61 32586, 512, -36, -568, 32551, 669, -47, -680, 32507, 832, -58, -788, 32454,
62 1000, -69, -891, 32393, 1174, -80, -990, 32323, 1352, -92, -1084, 32244, 1536,
63 -103, -1173, 32157, 1724, -115, -1258, 32061, 1919, -128, -1338, 31956, 2118, -140,
64 -1414, 31844, 2322, -153, -1486, 31723, 2532, -167, -1554, 31593, 2747, -180, -1617,
65 31456, 2967, -194, -1676, 31310, 3192, -209, -1732, 31157, 3422, -224, -1783, 30995,
66 3657, -240, -1830, 30826, 3897, -256, -1874, 30649, 4143, -272, -1914, 30464, 4393,
67 -289, -1951, 30272, 4648, -307, -1984, 30072, 4908, -325, -2014, 29866, 5172, -343,
68 -2040, 29652, 5442, -362, -2063, 29431, 5716, -382, -2083, 29203, 5994, -403, -2100,
69 28968, 6277, -424, -2114, 28727, 6565, -445, -2125, 28480, 6857, -468, -2133, 28226,
70 7153, -490, -2139, 27966, 7453, -514, -2142, 27700, 7758, -538, -2142, 27428, 8066,
71 -563, -2141, 27151, 8378, -588, -2136, 26867, 8694, -614, -2130, 26579, 9013, -641,
72 -2121, 26285, 9336, -668, -2111, 25987, 9663, -696, -2098, 25683, 9993, -724, -2084,
73 25375, 10326, -753, -2067, 25063, 10662, -783, -2049, 24746, 11000, -813, -2030, 24425,
74 11342, -844, -2009, 24100, 11686, -875, -1986, 23771, 12033, -907, -1962, 23438, 12382,
75 -939, -1937, 23103, 12733, -972, -1911, 22764, 13086, -1005, -1883, 22422, 13441, -1039,
76 -1855, 22077, 13798, -1072, -1825, 21729, 14156, -1107, -1795, 21380, 14516, -1141, -1764,
77 21027, 14877, -1176, -1732, 20673, 15239, -1211, -1700, 20317, 15602, -1246, -1667, 19959,
78 15965, -1282, -1633, 19600, 16329, -1317, -1599, 19239, 16694, -1353, -1564, 18878, 17058,
79 -1388, -1530, 18515, 17423, -1424, -1495, 18151, 17787, -1459, -1459, 17787, 18151, -1495,
80 -1424, 17423, 18515, -1530, -1388, 17058, 18878, -1564, -1353, 16694, 19239, -1599, -1317,
81 16329, 19600, -1633, -1282, 15965, 19959, -1667, -1246, 15602, 20317, -1700, -1211, 15239,
82 20673, -1732, -1176, 14877, 21027, -1764, -1141, 14516, 21380, -1795, -1107, 14156, 21729,
83 -1825, -1072, 13798, 22077, -1855, -1039, 13441, 22422, -1883, -1005, 13086, 22764, -1911,
84 -972, 12733, 23103, -1937, -939, 12382, 23438, -1962, -907, 12033, 23771, -1986, -875,
85 11686, 24100, -2009, -844, 11342, 24425, -2030, -813, 11000, 24746, -2049, -783, 10662,
86 25063, -2067, -753, 10326, 25375, -2084, -724, 9993, 25683, -2098, -696, 9663, 25987,
87 -2111, -668, 9336, 26285, -2121, -641, 9013, 26579, -2130, -614, 8694, 26867, -2136,
88 -588, 8378, 27151, -2141, -563, 8066, 27428, -2142, -538, 7758, 27700, -2142, -514,
89 7453, 27966, -2139, -490, 7153, 28226, -2133, -468, 6857, 28480, -2125, -445, 6565,
90 28727, -2114, -424, 6277, 28968, -2100, -403, 5994, 29203, -2083, -382, 5716, 29431,
91 -2063, -362, 5442, 29652, -2040, -343, 5172, 29866, -2014, -325, 4908, 30072, -1984,
92 -307, 4648, 30272, -1951, -289, 4393, 30464, -1914, -272, 4143, 30649, -1874, -256,
93 3897, 30826, -1830, -240, 3657, 30995, -1783, -224, 3422, 31157, -1732, -209, 3192,
94 31310, -1676, -194, 2967, 31456, -1617, -180, 2747, 31593, -1554, -167, 2532, 31723,
95 -1486, -153, 2322, 31844, -1414, -140, 2118, 31956, -1338, -128, 1919, 32061, -1258,
96 -115, 1724, 32157, -1173, -103, 1536, 32244, -1084, -92, 1352, 32323, -990, -80,
97 1174, 32393, -891, -69, 1000, 32454, -788, -58, 832, 32507, -680, -47, 669,
98 32551, -568, -36, 512, 32586, -450, -26, 359, 32613, -328, -15, 212, 32630,
99 -200, -5, 69, 32639, -68};
100
101constexpr std::array<s16, 512> curve_lut2{
102 3195, 26287, 3329, -32, 3064, 26281, 3467, -34, 2936, 26270, 3608, -38, 2811,
103 26253, 3751, -42, 2688, 26230, 3897, -46, 2568, 26202, 4046, -50, 2451, 26169,
104 4199, -54, 2338, 26130, 4354, -58, 2227, 26085, 4512, -63, 2120, 26035, 4673,
105 -67, 2015, 25980, 4837, -72, 1912, 25919, 5004, -76, 1813, 25852, 5174, -81,
106 1716, 25780, 5347, -87, 1622, 25704, 5522, -92, 1531, 25621, 5701, -98, 1442,
107 25533, 5882, -103, 1357, 25440, 6066, -109, 1274, 25342, 6253, -115, 1193, 25239,
108 6442, -121, 1115, 25131, 6635, -127, 1040, 25018, 6830, -133, 967, 24899, 7027,
109 -140, 897, 24776, 7227, -146, 829, 24648, 7430, -153, 764, 24516, 7635, -159,
110 701, 24379, 7842, -166, 641, 24237, 8052, -174, 583, 24091, 8264, -181, 526,
111 23940, 8478, -187, 472, 23785, 8695, -194, 420, 23626, 8914, -202, 371, 23462,
112 9135, -209, 324, 23295, 9358, -215, 279, 23123, 9583, -222, 236, 22948, 9809,
113 -230, 194, 22769, 10038, -237, 154, 22586, 10269, -243, 117, 22399, 10501, -250,
114 81, 22208, 10735, -258, 47, 22015, 10970, -265, 15, 21818, 11206, -271, -16,
115 21618, 11444, -277, -44, 21415, 11684, -283, -71, 21208, 11924, -290, -97, 20999,
116 12166, -296, -121, 20786, 12409, -302, -143, 20571, 12653, -306, -163, 20354, 12898,
117 -311, -183, 20134, 13143, -316, -201, 19911, 13389, -321, -218, 19686, 13635, -325,
118 -234, 19459, 13882, -328, -248, 19230, 14130, -332, -261, 18998, 14377, -335, -273,
119 18765, 14625, -337, -284, 18531, 14873, -339, -294, 18295, 15121, -341, -302, 18057,
120 15369, -341, -310, 17817, 15617, -341, -317, 17577, 15864, -340, -323, 17335, 16111,
121 -340, -328, 17092, 16357, -338, -332, 16848, 16603, -336, -336, 16603, 16848, -332,
122 -338, 16357, 17092, -328, -340, 16111, 17335, -323, -340, 15864, 17577, -317, -341,
123 15617, 17817, -310, -341, 15369, 18057, -302, -341, 15121, 18295, -294, -339, 14873,
124 18531, -284, -337, 14625, 18765, -273, -335, 14377, 18998, -261, -332, 14130, 19230,
125 -248, -328, 13882, 19459, -234, -325, 13635, 19686, -218, -321, 13389, 19911, -201,
126 -316, 13143, 20134, -183, -311, 12898, 20354, -163, -306, 12653, 20571, -143, -302,
127 12409, 20786, -121, -296, 12166, 20999, -97, -290, 11924, 21208, -71, -283, 11684,
128 21415, -44, -277, 11444, 21618, -16, -271, 11206, 21818, 15, -265, 10970, 22015,
129 47, -258, 10735, 22208, 81, -250, 10501, 22399, 117, -243, 10269, 22586, 154,
130 -237, 10038, 22769, 194, -230, 9809, 22948, 236, -222, 9583, 23123, 279, -215,
131 9358, 23295, 324, -209, 9135, 23462, 371, -202, 8914, 23626, 420, -194, 8695,
132 23785, 472, -187, 8478, 23940, 526, -181, 8264, 24091, 583, -174, 8052, 24237,
133 641, -166, 7842, 24379, 701, -159, 7635, 24516, 764, -153, 7430, 24648, 829,
134 -146, 7227, 24776, 897, -140, 7027, 24899, 967, -133, 6830, 25018, 1040, -127,
135 6635, 25131, 1115, -121, 6442, 25239, 1193, -115, 6253, 25342, 1274, -109, 6066,
136 25440, 1357, -103, 5882, 25533, 1442, -98, 5701, 25621, 1531, -92, 5522, 25704,
137 1622, -87, 5347, 25780, 1716, -81, 5174, 25852, 1813, -76, 5004, 25919, 1912,
138 -72, 4837, 25980, 2015, -67, 4673, 26035, 2120, -63, 4512, 26085, 2227, -58,
139 4354, 26130, 2338, -54, 4199, 26169, 2451, -50, 4046, 26202, 2568, -46, 3897,
140 26230, 2688, -42, 3751, 26253, 2811, -38, 3608, 26270, 2936, -34, 3467, 26281,
141 3064, -32, 3329, 26287, 3195};
142
143std::vector<s16> Interpolate(InterpolationState& state, std::vector<s16> input, double ratio) {
144 if (input.size() < 2)
145 return {};
146
147 if (ratio <= 0) {
148 LOG_ERROR(Audio, "Nonsensical interpolation ratio {}", ratio);
149 return input;
150 }
151
152 const s32 step{static_cast<s32>(ratio * 0x8000)};
153 const std::array<s16, 512>& lut = [step] {
154 if (step > 0xaaaa) {
155 return curve_lut0;
156 }
157 if (step <= 0x8000) {
158 return curve_lut1;
159 }
160 return curve_lut2;
161 }();
162
163 const std::size_t num_frames{input.size() / 2};
164
165 std::vector<s16> output;
166 output.reserve(static_cast<std::size_t>(static_cast<double>(input.size()) / ratio +
167 InterpolationState::taps));
168
169 for (std::size_t frame{}; frame < num_frames; ++frame) {
170 const std::size_t lut_index{(state.fraction >> 8) * InterpolationState::taps};
171
172 std::rotate(state.history.begin(), state.history.end() - 1, state.history.end());
173 state.history[0][0] = input[frame * 2 + 0];
174 state.history[0][1] = input[frame * 2 + 1];
175
176 while (state.position <= 1.0) {
177 const s32 left{state.history[0][0] * lut[lut_index + 0] +
178 state.history[1][0] * lut[lut_index + 1] +
179 state.history[2][0] * lut[lut_index + 2] +
180 state.history[3][0] * lut[lut_index + 3]};
181 const s32 right{state.history[0][1] * lut[lut_index + 0] +
182 state.history[1][1] * lut[lut_index + 1] +
183 state.history[2][1] * lut[lut_index + 2] +
184 state.history[3][1] * lut[lut_index + 3]};
185 const s32 new_offset{state.fraction + step};
186
187 state.fraction = new_offset & 0x7fff;
188
189 output.emplace_back(static_cast<s16>(std::clamp(left >> 15, SHRT_MIN, SHRT_MAX)));
190 output.emplace_back(static_cast<s16>(std::clamp(right >> 15, SHRT_MIN, SHRT_MAX)));
191
192 state.position += ratio;
193 }
194 state.position -= 1.0;
195 }
196
197 return output;
198}
199
200void Resample(s32* output, const s32* input, s32 pitch, s32& fraction, std::size_t sample_count) {
201 const std::array<s16, 512>& lut = [pitch] {
202 if (pitch > 0xaaaa) {
203 return curve_lut0;
204 }
205 if (pitch <= 0x8000) {
206 return curve_lut1;
207 }
208 return curve_lut2;
209 }();
210
211 std::size_t index{};
212
213 for (std::size_t i = 0; i < sample_count; i++) {
214 const std::size_t lut_index{(static_cast<std::size_t>(fraction) >> 8) * 4};
215 const auto l0 = lut[lut_index + 0];
216 const auto l1 = lut[lut_index + 1];
217 const auto l2 = lut[lut_index + 2];
218 const auto l3 = lut[lut_index + 3];
219
220 const auto s0 = static_cast<s32>(input[index + 0]);
221 const auto s1 = static_cast<s32>(input[index + 1]);
222 const auto s2 = static_cast<s32>(input[index + 2]);
223 const auto s3 = static_cast<s32>(input[index + 3]);
224
225 output[i] = (l0 * s0 + l1 * s1 + l2 * s2 + l3 * s3) >> 15;
226 fraction += pitch;
227 index += (fraction >> 15);
228 fraction &= 0x7fff;
229 }
230}
231
232} // namespace AudioCore
diff --git a/src/audio_core/algorithm/interpolate.h b/src/audio_core/algorithm/interpolate.h
deleted file mode 100644
index 5e59f4d70..000000000
--- a/src/audio_core/algorithm/interpolate.h
+++ /dev/null
@@ -1,43 +0,0 @@
1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7#include <vector>
8
9#include "common/common_types.h"
10
11namespace AudioCore {
12
13struct InterpolationState {
14 static constexpr std::size_t taps{4};
15 static constexpr std::size_t history_size{taps * 2 - 1};
16 std::array<std::array<s16, 2>, history_size> history{};
17 double position{};
18 s32 fraction{};
19};
20
21/// Interpolates input signal to produce output signal.
22/// @param input The signal to interpolate.
23/// @param ratio Interpolation ratio.
24/// ratio > 1.0 results in fewer output samples.
25/// ratio < 1.0 results in more output samples.
26/// @returns Output signal.
27std::vector<s16> Interpolate(InterpolationState& state, std::vector<s16> input, double ratio);
28
29/// Interpolates input signal to produce output signal.
30/// @param input The signal to interpolate.
31/// @param input_rate The sample rate of input.
32/// @param output_rate The desired sample rate of the output.
33/// @returns Output signal.
34inline std::vector<s16> Interpolate(InterpolationState& state, std::vector<s16> input,
35 u32 input_rate, u32 output_rate) {
36 const double ratio = static_cast<double>(input_rate) / static_cast<double>(output_rate);
37 return Interpolate(state, std::move(input), ratio);
38}
39
40/// Nintendo Switchs DSP resampling algorithm. Based on a single channel
41void Resample(s32* output, const s32* input, s32 pitch, s32& fraction, std::size_t sample_count);
42
43} // namespace AudioCore
diff --git a/src/audio_core/audio_core.cpp b/src/audio_core/audio_core.cpp
new file mode 100644
index 000000000..78e615a10
--- /dev/null
+++ b/src/audio_core/audio_core.cpp
@@ -0,0 +1,68 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/audio_core.h"
5#include "audio_core/sink/sink_details.h"
6#include "common/settings.h"
7#include "core/core.h"
8
9namespace AudioCore {
10
11AudioCore::AudioCore(Core::System& system) : audio_manager{std::make_unique<AudioManager>(system)} {
12 CreateSinks();
13 // Must be created after the sinks
14 adsp = std::make_unique<AudioRenderer::ADSP::ADSP>(system, *output_sink);
15}
16
17AudioCore ::~AudioCore() {
18 Shutdown();
19}
20
21void AudioCore::CreateSinks() {
22 const auto& sink_id{Settings::values.sink_id};
23 const auto& audio_output_device_id{Settings::values.audio_output_device_id};
24 const auto& audio_input_device_id{Settings::values.audio_input_device_id};
25
26 output_sink = Sink::CreateSinkFromID(sink_id.GetValue(), audio_output_device_id.GetValue());
27 input_sink = Sink::CreateSinkFromID(sink_id.GetValue(), audio_input_device_id.GetValue());
28}
29
30void AudioCore::Shutdown() {
31 audio_manager->Shutdown();
32}
33
34AudioManager& AudioCore::GetAudioManager() {
35 return *audio_manager;
36}
37
38Sink::Sink& AudioCore::GetOutputSink() {
39 return *output_sink;
40}
41
42Sink::Sink& AudioCore::GetInputSink() {
43 return *input_sink;
44}
45
46AudioRenderer::ADSP::ADSP& AudioCore::GetADSP() {
47 return *adsp;
48}
49
50void AudioCore::PauseSinks(const bool pausing) const {
51 if (pausing) {
52 output_sink->PauseStreams();
53 input_sink->PauseStreams();
54 } else {
55 output_sink->UnpauseStreams();
56 input_sink->UnpauseStreams();
57 }
58}
59
60u32 AudioCore::GetStreamQueue() const {
61 return estimated_queue.load();
62}
63
64void AudioCore::SetStreamQueue(u32 size) {
65 estimated_queue.store(size);
66}
67
68} // namespace AudioCore
diff --git a/src/audio_core/audio_core.h b/src/audio_core/audio_core.h
new file mode 100644
index 000000000..0f7d61ee4
--- /dev/null
+++ b/src/audio_core/audio_core.h
@@ -0,0 +1,100 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <memory>
7
8#include "audio_core/audio_manager.h"
9#include "audio_core/renderer/adsp/adsp.h"
10#include "audio_core/sink/sink.h"
11
12namespace Core {
13class System;
14}
15
16namespace AudioCore {
17
18class AudioManager;
19/**
20 * Main audio class, sotred inside the core, and holding the audio manager, all sinks, and the ADSP.
21 */
22class AudioCore {
23public:
24 explicit AudioCore(Core::System& system);
25 ~AudioCore();
26
27 /**
28 * Shutdown the audio core.
29 */
30 void Shutdown();
31
32 /**
33 * Get a reference to the audio manager.
34 *
35 * @return Ref to the audio manager.
36 */
37 AudioManager& GetAudioManager();
38
39 /**
40 * Get the audio output sink currently in use.
41 *
42 * @return Ref to the sink.
43 */
44 Sink::Sink& GetOutputSink();
45
46 /**
47 * Get the audio input sink currently in use.
48 *
49 * @return Ref to the sink.
50 */
51 Sink::Sink& GetInputSink();
52
53 /**
54 * Get the ADSP.
55 *
56 * @return Ref to the ADSP.
57 */
58 AudioRenderer::ADSP::ADSP& GetADSP();
59
60 /**
61 * Pause the sink. Called from the core.
62 *
63 * @param pausing - Is this pause due to an actual pause, or shutdown?
64 * Unfortunately, shutdown also pauses streams, which can cause issues.
65 */
66 void PauseSinks(bool pausing) const;
67
68 /**
69 * Get the size of the current stream queue.
70 *
71 * @return Current stream queue size.
72 */
73 u32 GetStreamQueue() const;
74
75 /**
76 * Get the size of the current stream queue.
77 *
78 * @param size - New stream size.
79 */
80 void SetStreamQueue(u32 size);
81
82private:
83 /**
84 * Create the sinks on startup.
85 */
86 void CreateSinks();
87
88 /// Main audio manager for audio in/out
89 std::unique_ptr<AudioManager> audio_manager;
90 /// Sink used for audio renderer and audio out
91 std::unique_ptr<Sink::Sink> output_sink;
92 /// Sink used for audio input
93 std::unique_ptr<Sink::Sink> input_sink;
94 /// The ADSP in the sysmodule
95 std::unique_ptr<AudioRenderer::ADSP::ADSP> adsp;
96 /// Current size of the stream queue
97 std::atomic<u32> estimated_queue{0};
98};
99
100} // namespace AudioCore
diff --git a/src/audio_core/audio_event.cpp b/src/audio_core/audio_event.cpp
new file mode 100644
index 000000000..424049c7a
--- /dev/null
+++ b/src/audio_core/audio_event.cpp
@@ -0,0 +1,61 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/audio_event.h"
5#include "common/assert.h"
6
7namespace AudioCore {
8
9size_t Event::GetManagerIndex(const Type type) const {
10 switch (type) {
11 case Type::AudioInManager:
12 return 0;
13 case Type::AudioOutManager:
14 return 1;
15 case Type::FinalOutputRecorderManager:
16 return 2;
17 case Type::Max:
18 return 3;
19 default:
20 UNREACHABLE();
21 }
22 return 3;
23}
24
25void Event::SetAudioEvent(const Type type, const bool signalled) {
26 events_signalled[GetManagerIndex(type)] = signalled;
27 if (signalled) {
28 manager_event.notify_one();
29 }
30}
31
32bool Event::CheckAudioEventSet(const Type type) const {
33 return events_signalled[GetManagerIndex(type)];
34}
35
36std::mutex& Event::GetAudioEventLock() {
37 return event_lock;
38}
39
40std::condition_variable_any& Event::GetAudioEvent() {
41 return manager_event;
42}
43
44bool Event::Wait(std::unique_lock<std::mutex>& l, const std::chrono::seconds timeout) {
45 bool timed_out{false};
46 if (!manager_event.wait_for(l, timeout, [&]() {
47 return std::ranges::any_of(events_signalled, [](bool x) { return x; });
48 })) {
49 timed_out = true;
50 }
51 return timed_out;
52}
53
54void Event::ClearEvents() {
55 events_signalled[0] = false;
56 events_signalled[1] = false;
57 events_signalled[2] = false;
58 events_signalled[3] = false;
59}
60
61} // namespace AudioCore
diff --git a/src/audio_core/audio_event.h b/src/audio_core/audio_event.h
new file mode 100644
index 000000000..82dd32dca
--- /dev/null
+++ b/src/audio_core/audio_event.h
@@ -0,0 +1,92 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7#include <atomic>
8#include <chrono>
9#include <condition_variable>
10#include <mutex>
11
12namespace AudioCore {
13/**
14 * Responsible for the input/output events, set by the stream backend when buffers are consumed, and
15 * waited on by the audio manager. These callbacks signal the game's events to keep the audio buffer
16 * recycling going.
17 * In a real Switch this is not a seprate class, and exists entirely within the audio manager.
18 * On the Switch it's implemented more simply through a MultiWaitEventHolder, where it can
19 * wait on multiple events at once, and the events are not needed by the backend.
20 */
21class Event {
22public:
23 enum class Type {
24 AudioInManager,
25 AudioOutManager,
26 FinalOutputRecorderManager,
27 Max,
28 };
29
30 /**
31 * Convert a manager type to an index.
32 *
33 * @param type - The manager type to convert
34 * @return The index of the type.
35 */
36 size_t GetManagerIndex(Type type) const;
37
38 /**
39 * Set an audio event to true or false.
40 *
41 * @param type - The manager type to signal.
42 * @param signalled - Its signal state.
43 */
44 void SetAudioEvent(Type type, bool signalled);
45
46 /**
47 * Check if the given manager type is signalled.
48 *
49 * @param type - The manager type to check.
50 * @return True if the event is signalled, otherwise false.
51 */
52 bool CheckAudioEventSet(Type type) const;
53
54 /**
55 * Get the lock for audio events.
56 *
57 * @return Reference to the lock.
58 */
59 std::mutex& GetAudioEventLock();
60
61 /**
62 * Get the manager event, this signals the audio manager to release buffers and signal the game
63 * for more.
64 *
65 * @return Reference to the condition variable.
66 */
67 std::condition_variable_any& GetAudioEvent();
68
69 /**
70 * Wait on the manager_event.
71 *
72 * @param l - Lock held by the wait.
73 * @param timeout - Timeout for the wait. This is 2 seconds by default.
74 * @return True if the wait timed out, otherwise false if signalled.
75 */
76 bool Wait(std::unique_lock<std::mutex>& l, std::chrono::seconds timeout);
77
78 /**
79 * Reset all manager events.
80 */
81 void ClearEvents();
82
83private:
84 /// Lock, used bythe audio manager
85 std::mutex event_lock;
86 /// Array of events, one per system type (see Type), last event is used to terminate
87 std::array<std::atomic<bool>, 4> events_signalled;
88 /// Event to signal the audio manager
89 std::condition_variable_any manager_event;
90};
91
92} // namespace AudioCore
diff --git a/src/audio_core/audio_in_manager.cpp b/src/audio_core/audio_in_manager.cpp
new file mode 100644
index 000000000..4aadb7fd6
--- /dev/null
+++ b/src/audio_core/audio_in_manager.cpp
@@ -0,0 +1,91 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/audio_core.h"
5#include "audio_core/audio_in_manager.h"
6#include "audio_core/audio_manager.h"
7#include "audio_core/in/audio_in.h"
8#include "audio_core/sink/sink_details.h"
9#include "common/settings.h"
10#include "core/core.h"
11#include "core/hle/service/audio/errors.h"
12
13namespace AudioCore::AudioIn {
14
15Manager::Manager(Core::System& system_) : system{system_} {
16 std::iota(session_ids.begin(), session_ids.end(), 0);
17 num_free_sessions = MaxInSessions;
18}
19
20Result Manager::AcquireSessionId(size_t& session_id) {
21 if (num_free_sessions == 0) {
22 LOG_ERROR(Service_Audio, "All 4 AudioIn sessions are in use, cannot create any more");
23 return Service::Audio::ERR_MAXIMUM_SESSIONS_REACHED;
24 }
25 session_id = session_ids[next_session_id];
26 next_session_id = (next_session_id + 1) % MaxInSessions;
27 num_free_sessions--;
28 return ResultSuccess;
29}
30
31void Manager::ReleaseSessionId(const size_t session_id) {
32 std::scoped_lock l{mutex};
33 LOG_DEBUG(Service_Audio, "Freeing AudioIn session {}", session_id);
34 session_ids[free_session_id] = session_id;
35 num_free_sessions++;
36 free_session_id = (free_session_id + 1) % MaxInSessions;
37 sessions[session_id].reset();
38 applet_resource_user_ids[session_id] = 0;
39}
40
41Result Manager::LinkToManager() {
42 std::scoped_lock l{mutex};
43 if (!linked_to_manager) {
44 AudioManager& manager{system.AudioCore().GetAudioManager()};
45 manager.SetInManager(std::bind(&Manager::BufferReleaseAndRegister, this));
46 linked_to_manager = true;
47 }
48
49 return ResultSuccess;
50}
51
52void Manager::Start() {
53 if (sessions_started) {
54 return;
55 }
56
57 std::scoped_lock l{mutex};
58 for (auto& session : sessions) {
59 if (session) {
60 session->StartSession();
61 }
62 }
63
64 sessions_started = true;
65}
66
67void Manager::BufferReleaseAndRegister() {
68 std::scoped_lock l{mutex};
69 for (auto& session : sessions) {
70 if (session != nullptr) {
71 session->ReleaseAndRegisterBuffers();
72 }
73 }
74}
75
76u32 Manager::GetDeviceNames(std::vector<AudioRenderer::AudioDevice::AudioDeviceName>& names,
77 [[maybe_unused]] const u32 max_count,
78 [[maybe_unused]] const bool filter) {
79 std::scoped_lock l{mutex};
80
81 LinkToManager();
82
83 auto input_devices{Sink::GetDeviceListForSink(Settings::values.sink_id.GetValue(), true)};
84 if (input_devices.size() > 1) {
85 names.push_back(AudioRenderer::AudioDevice::AudioDeviceName("Uac"));
86 return 1;
87 }
88 return 0;
89}
90
91} // namespace AudioCore::AudioIn
diff --git a/src/audio_core/audio_in_manager.h b/src/audio_core/audio_in_manager.h
new file mode 100644
index 000000000..75b73a0b6
--- /dev/null
+++ b/src/audio_core/audio_in_manager.h
@@ -0,0 +1,92 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7#include <mutex>
8#include <vector>
9
10#include "audio_core/renderer/audio_device.h"
11
12namespace Core {
13class System;
14}
15
16namespace AudioCore::AudioIn {
17class In;
18
19constexpr size_t MaxInSessions = 4;
20/**
21 * Manages all audio in sessions.
22 */
23class Manager {
24public:
25 explicit Manager(Core::System& system);
26
27 /**
28 * Acquire a free session id for opening a new audio in.
29 *
30 * @param session_id - Output session_id.
31 * @return Result code.
32 */
33 Result AcquireSessionId(size_t& session_id);
34
35 /**
36 * Release a session id on close.
37 *
38 * @param session_id - Session id to free.
39 */
40 void ReleaseSessionId(size_t session_id);
41
42 /**
43 * Link the audio in manager to the main audio manager.
44 *
45 * @return Result code.
46 */
47 Result LinkToManager();
48
49 /**
50 * Start the audio in manager.
51 */
52 void Start();
53
54 /**
55 * Callback function, called by the audio manager when the audio in event is signalled.
56 */
57 void BufferReleaseAndRegister();
58
59 /**
60 * Get a list of audio in device names.
61 *
62 * @oaram names - Output container to write names to.
63 * @param max_count - Maximum numebr of deivce names to write. Unused
64 * @param filter - Should the list be filtered? Unused.
65 * @return Number of names written.
66 */
67 u32 GetDeviceNames(std::vector<AudioRenderer::AudioDevice::AudioDeviceName>& names,
68 u32 max_count, bool filter);
69
70 /// Core system
71 Core::System& system;
72 /// Array of session ids
73 std::array<size_t, MaxInSessions> session_ids{};
74 /// Array of resource user ids
75 std::array<size_t, MaxInSessions> applet_resource_user_ids{};
76 /// Pointer to each open session
77 std::array<std::shared_ptr<In>, MaxInSessions> sessions{};
78 /// The number of free sessions
79 size_t num_free_sessions{};
80 /// The next session id to be taken
81 size_t next_session_id{};
82 /// The next session id to be freed
83 size_t free_session_id{};
84 /// Whether this is linked to the audio manager
85 bool linked_to_manager{};
86 /// Whether the sessions have been started
87 bool sessions_started{};
88 /// Protect state due to audio manager callback
89 std::recursive_mutex mutex{};
90};
91
92} // namespace AudioCore::AudioIn
diff --git a/src/audio_core/audio_manager.cpp b/src/audio_core/audio_manager.cpp
new file mode 100644
index 000000000..2f1bba9c3
--- /dev/null
+++ b/src/audio_core/audio_manager.cpp
@@ -0,0 +1,80 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/audio_in_manager.h"
5#include "audio_core/audio_manager.h"
6#include "audio_core/audio_out_manager.h"
7#include "core/core.h"
8
9namespace AudioCore {
10
11AudioManager::AudioManager(Core::System& system_) : system{system_} {
12 thread = std::jthread([this]() { ThreadFunc(); });
13}
14
15void AudioManager::Shutdown() {
16 running = false;
17 events.SetAudioEvent(Event::Type::Max, true);
18 thread.join();
19}
20
21Result AudioManager::SetOutManager(BufferEventFunc buffer_func) {
22 if (!running) {
23 return Service::Audio::ERR_OPERATION_FAILED;
24 }
25
26 std::scoped_lock l{lock};
27
28 const auto index{events.GetManagerIndex(Event::Type::AudioOutManager)};
29 if (buffer_events[index] == nullptr) {
30 buffer_events[index] = buffer_func;
31 needs_update = true;
32 events.SetAudioEvent(Event::Type::AudioOutManager, true);
33 }
34 return ResultSuccess;
35}
36
37Result AudioManager::SetInManager(BufferEventFunc buffer_func) {
38 if (!running) {
39 return Service::Audio::ERR_OPERATION_FAILED;
40 }
41
42 std::scoped_lock l{lock};
43
44 const auto index{events.GetManagerIndex(Event::Type::AudioInManager)};
45 if (buffer_events[index] == nullptr) {
46 buffer_events[index] = buffer_func;
47 needs_update = true;
48 events.SetAudioEvent(Event::Type::AudioInManager, true);
49 }
50 return ResultSuccess;
51}
52
53void AudioManager::SetEvent(const Event::Type type, const bool signalled) {
54 events.SetAudioEvent(type, signalled);
55}
56
57void AudioManager::ThreadFunc() {
58 std::unique_lock l{events.GetAudioEventLock()};
59 events.ClearEvents();
60 running = true;
61
62 while (running) {
63 auto timed_out{events.Wait(l, std::chrono::seconds(2))};
64
65 if (events.CheckAudioEventSet(Event::Type::Max)) {
66 break;
67 }
68
69 for (size_t i = 0; i < buffer_events.size(); i++) {
70 if (events.CheckAudioEventSet(Event::Type(i)) || timed_out) {
71 if (buffer_events[i]) {
72 buffer_events[i]();
73 }
74 }
75 events.SetAudioEvent(Event::Type(i), false);
76 }
77 }
78}
79
80} // namespace AudioCore
diff --git a/src/audio_core/audio_manager.h b/src/audio_core/audio_manager.h
new file mode 100644
index 000000000..70316e9cb
--- /dev/null
+++ b/src/audio_core/audio_manager.h
@@ -0,0 +1,101 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7#include <atomic>
8#include <functional>
9#include <mutex>
10#include <thread>
11
12#include "audio_core/audio_event.h"
13#include "core/hle/service/audio/errors.h"
14
15namespace Core {
16class System;
17}
18
19namespace AudioCore {
20
21namespace AudioOut {
22class Manager;
23}
24
25namespace AudioIn {
26class Manager;
27}
28
29/**
30 * The AudioManager's main purpose is to wait for buffer events for the audio in and out managers,
31 * and call an associated callback to release buffers.
32 *
33 * Execution pattern is:
34 * Buffers appended ->
35 * Buffers queued and played by the backend stream ->
36 * When consumed, set the corresponding manager event and signal the audio manager ->
37 * Consumed buffers are released, game is signalled ->
38 * Game appends more buffers.
39 *
40 * This is only used by audio in and audio out.
41 */
42class AudioManager {
43 using BufferEventFunc = std::function<void()>;
44
45public:
46 explicit AudioManager(Core::System& system);
47
48 /**
49 * Shutdown the audio manager.
50 */
51 void Shutdown();
52
53 /**
54 * Register the out manager, keeping a function to be called when the out event is signalled.
55 *
56 * @param buffer_func - Function to be called on signal.
57 * @return Result code.
58 */
59 Result SetOutManager(BufferEventFunc buffer_func);
60
61 /**
62 * Register the in manager, keeping a function to be called when the in event is signalled.
63 *
64 * @param buffer_func - Function to be called on signal.
65 * @return Result code.
66 */
67 Result SetInManager(BufferEventFunc buffer_func);
68
69 /**
70 * Set an event to signalled, and signal the thread.
71 *
72 * @param type - Manager type to set.
73 * @param signalled - Set the event to true or false?
74 */
75 void SetEvent(Event::Type type, bool signalled);
76
77private:
78 /**
79 * Main thread, waiting on a manager signal and calling the registered fucntion.
80 */
81 void ThreadFunc();
82
83 /// Core system
84 Core::System& system;
85 /// Have sessions started palying?
86 bool sessions_started{};
87 /// Is the main thread running?
88 std::atomic<bool> running{};
89 /// Unused
90 bool needs_update{};
91 /// Events to be set and signalled
92 Event events{};
93 /// Callbacks for each manager
94 std::array<BufferEventFunc, 3> buffer_events{};
95 /// General lock
96 std::mutex lock{};
97 /// Main thread for waiting and callbacks
98 std::jthread thread;
99};
100
101} // namespace AudioCore
diff --git a/src/audio_core/audio_out.cpp b/src/audio_core/audio_out.cpp
deleted file mode 100644
index 83ec0221f..000000000
--- a/src/audio_core/audio_out.cpp
+++ /dev/null
@@ -1,62 +0,0 @@
1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/audio_out.h"
5#include "audio_core/sink.h"
6#include "audio_core/sink_details.h"
7#include "common/assert.h"
8#include "common/logging/log.h"
9#include "common/settings.h"
10
11namespace AudioCore {
12
13/// Returns the stream format from the specified number of channels
14static Stream::Format ChannelsToStreamFormat(u32 num_channels) {
15 switch (num_channels) {
16 case 1:
17 return Stream::Format::Mono16;
18 case 2:
19 return Stream::Format::Stereo16;
20 case 6:
21 return Stream::Format::Multi51Channel16;
22 }
23
24 UNIMPLEMENTED_MSG("Unimplemented num_channels={}", num_channels);
25 return {};
26}
27
28StreamPtr AudioOut::OpenStream(Core::Timing::CoreTiming& core_timing, u32 sample_rate,
29 u32 num_channels, std::string&& name,
30 Stream::ReleaseCallback&& release_callback) {
31 if (!sink) {
32 sink = CreateSinkFromID(Settings::values.sink_id.GetValue(),
33 Settings::values.audio_device_id.GetValue());
34 }
35
36 return std::make_shared<Stream>(
37 core_timing, sample_rate, ChannelsToStreamFormat(num_channels), std::move(release_callback),
38 sink->AcquireSinkStream(sample_rate, num_channels, name), std::move(name));
39}
40
41std::vector<Buffer::Tag> AudioOut::GetTagsAndReleaseBuffers(StreamPtr stream,
42 std::size_t max_count) {
43 return stream->GetTagsAndReleaseBuffers(max_count);
44}
45
46std::vector<Buffer::Tag> AudioOut::GetTagsAndReleaseBuffers(StreamPtr stream) {
47 return stream->GetTagsAndReleaseBuffers();
48}
49
50void AudioOut::StartStream(StreamPtr stream) {
51 stream->Play();
52}
53
54void AudioOut::StopStream(StreamPtr stream) {
55 stream->Stop();
56}
57
58bool AudioOut::QueueBuffer(StreamPtr stream, Buffer::Tag tag, std::vector<s16>&& data) {
59 return stream->QueueBuffer(std::make_shared<Buffer>(tag, std::move(data)));
60}
61
62} // namespace AudioCore
diff --git a/src/audio_core/audio_out.h b/src/audio_core/audio_out.h
deleted file mode 100644
index 6856373f1..000000000
--- a/src/audio_core/audio_out.h
+++ /dev/null
@@ -1,49 +0,0 @@
1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <memory>
7#include <string>
8#include <vector>
9
10#include "audio_core/buffer.h"
11#include "audio_core/sink.h"
12#include "audio_core/stream.h"
13#include "common/common_types.h"
14
15namespace Core::Timing {
16class CoreTiming;
17}
18
19namespace AudioCore {
20
21/**
22 * Represents an audio playback interface, used to open and play audio streams
23 */
24class AudioOut {
25public:
26 /// Opens a new audio stream
27 StreamPtr OpenStream(Core::Timing::CoreTiming& core_timing, u32 sample_rate, u32 num_channels,
28 std::string&& name, Stream::ReleaseCallback&& release_callback);
29
30 /// Returns a vector of recently released buffers specified by tag for the specified stream
31 std::vector<Buffer::Tag> GetTagsAndReleaseBuffers(StreamPtr stream, std::size_t max_count);
32
33 /// Returns a vector of all recently released buffers specified by tag for the specified stream
34 std::vector<Buffer::Tag> GetTagsAndReleaseBuffers(StreamPtr stream);
35
36 /// Starts an audio stream for playback
37 void StartStream(StreamPtr stream);
38
39 /// Stops an audio stream that is currently playing
40 void StopStream(StreamPtr stream);
41
42 /// Queues a buffer into the specified audio stream, returns true on success
43 bool QueueBuffer(StreamPtr stream, Buffer::Tag tag, std::vector<s16>&& data);
44
45private:
46 SinkPtr sink;
47};
48
49} // namespace AudioCore
diff --git a/src/audio_core/audio_out_manager.cpp b/src/audio_core/audio_out_manager.cpp
new file mode 100644
index 000000000..71d67de64
--- /dev/null
+++ b/src/audio_core/audio_out_manager.cpp
@@ -0,0 +1,81 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/audio_core.h"
5#include "audio_core/audio_manager.h"
6#include "audio_core/audio_out_manager.h"
7#include "audio_core/out/audio_out.h"
8#include "core/core.h"
9#include "core/hle/kernel/k_event.h"
10#include "core/hle/service/audio/errors.h"
11
12namespace AudioCore::AudioOut {
13
14Manager::Manager(Core::System& system_) : system{system_} {
15 std::iota(session_ids.begin(), session_ids.end(), 0);
16 num_free_sessions = MaxOutSessions;
17}
18
19Result Manager::AcquireSessionId(size_t& session_id) {
20 if (num_free_sessions == 0) {
21 LOG_ERROR(Service_Audio, "All 12 Audio Out sessions are in use, cannot create any more");
22 return Service::Audio::ERR_MAXIMUM_SESSIONS_REACHED;
23 }
24 session_id = session_ids[next_session_id];
25 next_session_id = (next_session_id + 1) % MaxOutSessions;
26 num_free_sessions--;
27 return ResultSuccess;
28}
29
30void Manager::ReleaseSessionId(const size_t session_id) {
31 std::scoped_lock l{mutex};
32 LOG_DEBUG(Service_Audio, "Freeing AudioOut session {}", session_id);
33 session_ids[free_session_id] = session_id;
34 num_free_sessions++;
35 free_session_id = (free_session_id + 1) % MaxOutSessions;
36 sessions[session_id].reset();
37 applet_resource_user_ids[session_id] = 0;
38}
39
40Result Manager::LinkToManager() {
41 std::scoped_lock l{mutex};
42 if (!linked_to_manager) {
43 AudioManager& manager{system.AudioCore().GetAudioManager()};
44 manager.SetOutManager(std::bind(&Manager::BufferReleaseAndRegister, this));
45 linked_to_manager = true;
46 }
47
48 return ResultSuccess;
49}
50
51void Manager::Start() {
52 if (sessions_started) {
53 return;
54 }
55
56 std::scoped_lock l{mutex};
57 for (auto& session : sessions) {
58 if (session) {
59 session->StartSession();
60 }
61 }
62
63 sessions_started = true;
64}
65
66void Manager::BufferReleaseAndRegister() {
67 std::scoped_lock l{mutex};
68 for (auto& session : sessions) {
69 if (session != nullptr) {
70 session->ReleaseAndRegisterBuffers();
71 }
72 }
73}
74
75u32 Manager::GetAudioOutDeviceNames(
76 std::vector<AudioRenderer::AudioDevice::AudioDeviceName>& names) const {
77 names.push_back({"DeviceOut"});
78 return 1;
79}
80
81} // namespace AudioCore::AudioOut
diff --git a/src/audio_core/audio_out_manager.h b/src/audio_core/audio_out_manager.h
new file mode 100644
index 000000000..24981e08f
--- /dev/null
+++ b/src/audio_core/audio_out_manager.h
@@ -0,0 +1,89 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7#include <mutex>
8
9#include "audio_core/renderer/audio_device.h"
10
11namespace Core {
12class System;
13}
14
15namespace AudioCore::AudioOut {
16class Out;
17
18constexpr size_t MaxOutSessions = 12;
19/**
20 * Manages all audio out sessions.
21 */
22class Manager {
23public:
24 explicit Manager(Core::System& system);
25
26 /**
27 * Acquire a free session id for opening a new audio out.
28 *
29 * @param session_id - Output session_id.
30 * @return Result code.
31 */
32 Result AcquireSessionId(size_t& session_id);
33
34 /**
35 * Release a session id on close.
36 *
37 * @param session_id - Session id to free.
38 */
39 void ReleaseSessionId(size_t session_id);
40
41 /**
42 * Link this manager to the main audio manager.
43 *
44 * @return Result code.
45 */
46 Result LinkToManager();
47
48 /**
49 * Start the audio out manager.
50 */
51 void Start();
52
53 /**
54 * Callback function, called by the audio manager when the audio out event is signalled.
55 */
56 void BufferReleaseAndRegister();
57
58 /**
59 * Get a list of audio out device names.
60 *
61 * @oaram names - Output container to write names to.
62 * @return Number of names written.
63 */
64 u32 GetAudioOutDeviceNames(
65 std::vector<AudioRenderer::AudioDevice::AudioDeviceName>& names) const;
66
67 /// Core system
68 Core::System& system;
69 /// Array of session ids
70 std::array<size_t, MaxOutSessions> session_ids{};
71 /// Array of resource user ids
72 std::array<size_t, MaxOutSessions> applet_resource_user_ids{};
73 /// Pointer to each open session
74 std::array<std::shared_ptr<Out>, MaxOutSessions> sessions{};
75 /// The number of free sessions
76 size_t num_free_sessions{};
77 /// The next session id to be taken
78 size_t next_session_id{};
79 /// The next session id to be freed
80 size_t free_session_id{};
81 /// Whether this is linked to the audio manager
82 bool linked_to_manager{};
83 /// Whether the sessions have been started
84 bool sessions_started{};
85 /// Protect state due to audio manager callback
86 std::recursive_mutex mutex{};
87};
88
89} // namespace AudioCore::AudioOut
diff --git a/src/audio_core/audio_render_manager.cpp b/src/audio_core/audio_render_manager.cpp
new file mode 100644
index 000000000..7a846835b
--- /dev/null
+++ b/src/audio_core/audio_render_manager.cpp
@@ -0,0 +1,70 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/audio_render_manager.h"
5#include "audio_core/common/audio_renderer_parameter.h"
6#include "audio_core/common/feature_support.h"
7#include "core/core.h"
8
9namespace AudioCore::AudioRenderer {
10
11Manager::Manager(Core::System& system_)
12 : system{system_}, system_manager{std::make_unique<SystemManager>(system)} {
13 std::iota(session_ids.begin(), session_ids.end(), 0);
14}
15
16Manager::~Manager() {
17 Stop();
18}
19
20void Manager::Stop() {
21 system_manager->Stop();
22}
23
24SystemManager& Manager::GetSystemManager() {
25 return *system_manager;
26}
27
28auto Manager::GetWorkBufferSize(const AudioRendererParameterInternal& params, u64& out_count)
29 -> Result {
30 if (!CheckValidRevision(params.revision)) {
31 return Service::Audio::ERR_INVALID_REVISION;
32 }
33
34 out_count = System::GetWorkBufferSize(params);
35
36 return ResultSuccess;
37}
38
39s32 Manager::GetSessionId() {
40 std::scoped_lock l{session_lock};
41 auto session_id{session_ids[session_count]};
42
43 if (session_id == -1) {
44 return -1;
45 }
46
47 session_ids[session_count] = -1;
48 session_count++;
49 return session_id;
50}
51
52void Manager::ReleaseSessionId(const s32 session_id) {
53 std::scoped_lock l{session_lock};
54 session_ids[--session_count] = session_id;
55}
56
57u32 Manager::GetSessionCount() {
58 std::scoped_lock l{session_lock};
59 return session_count;
60}
61
62bool Manager::AddSystem(System& system_) {
63 return system_manager->Add(system_);
64}
65
66bool Manager::RemoveSystem(System& system_) {
67 return system_manager->Remove(system_);
68}
69
70} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/audio_render_manager.h b/src/audio_core/audio_render_manager.h
new file mode 100644
index 000000000..6a508ec56
--- /dev/null
+++ b/src/audio_core/audio_render_manager.h
@@ -0,0 +1,103 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7#include <memory>
8#include <mutex>
9
10#include "audio_core/common/common.h"
11#include "audio_core/renderer/system_manager.h"
12#include "core/hle/service/audio/errors.h"
13
14namespace Core {
15class System;
16}
17
18namespace AudioCore {
19struct AudioRendererParameterInternal;
20
21namespace AudioRenderer {
22/**
23 * Wrapper for the audio system manager, handles service calls.
24 */
25class Manager {
26public:
27 explicit Manager(Core::System& system);
28 ~Manager();
29
30 /**
31 * Stop the manager.
32 */
33 void Stop();
34
35 /**
36 * Get the system manager.
37 *
38 * @return The system manager.
39 */
40 SystemManager& GetSystemManager();
41
42 /**
43 * Get required size for the audio renderer workbuffer.
44 *
45 * @param params - Input parameters with the numbers of voices/mixes/sinks etc.
46 * @param out_count - Output size of the required workbuffer.
47 * @return Result code.
48 */
49 Result GetWorkBufferSize(const AudioRendererParameterInternal& params, u64& out_count);
50
51 /**
52 * Get a new session id.
53 *
54 * @return The new session id. -1 if invalid, otherwise 0-MaxRendererSessions.
55 */
56 s32 GetSessionId();
57
58 /**
59 * Get the number of currently active sessions.
60 *
61 * @return The number of active sessions.
62 */
63 u32 GetSessionCount();
64
65 /**
66 * Add a renderer system to the manager.
67 * The system will be reguarly called to generate commands for the AudioRenderer.
68 *
69 * @param system - The system to add.
70 * @return True if the system was sucessfully added, otherwise false.
71 */
72 bool AddSystem(System& system);
73
74 /**
75 * Remove a renderer system from the manager.
76 *
77 * @param system - The system to remove.
78 * @return True if the system was sucessfully removed, otherwise false.
79 */
80 bool RemoveSystem(System& system);
81
82 /**
83 * Free a session id when the system wants to shut down.
84 *
85 * @param session_id - The session id to free.
86 */
87 void ReleaseSessionId(s32 session_id);
88
89private:
90 /// Core system
91 Core::System& system;
92 /// Session ids, -1 when in use
93 std::array<s32, MaxRendererSessions> session_ids{};
94 /// Number of active renderers
95 u32 session_count{};
96 /// Lock for interacting with the sessions
97 std::mutex session_lock{};
98 /// Regularly generates commands from the registered systems for the AudioRenderer
99 std::unique_ptr<SystemManager> system_manager{};
100};
101
102} // namespace AudioRenderer
103} // namespace AudioCore
diff --git a/src/audio_core/audio_renderer.cpp b/src/audio_core/audio_renderer.cpp
deleted file mode 100644
index 9191ca093..000000000
--- a/src/audio_core/audio_renderer.cpp
+++ /dev/null
@@ -1,343 +0,0 @@
1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <limits>
5#include <optional>
6#include <vector>
7
8#include "audio_core/audio_out.h"
9#include "audio_core/audio_renderer.h"
10#include "audio_core/common.h"
11#include "audio_core/info_updater.h"
12#include "audio_core/voice_context.h"
13#include "common/logging/log.h"
14#include "common/settings.h"
15#include "core/core_timing.h"
16#include "core/memory.h"
17
18namespace {
19[[nodiscard]] static constexpr s16 ClampToS16(s32 value) {
20 return static_cast<s16>(std::clamp(value, s32{std::numeric_limits<s16>::min()},
21 s32{std::numeric_limits<s16>::max()}));
22}
23
24[[nodiscard]] static constexpr s16 Mix2To1(s16 l_channel, s16 r_channel) {
25 // Mix 50% from left and 50% from right channel
26 constexpr float l_mix_amount = 50.0f / 100.0f;
27 constexpr float r_mix_amount = 50.0f / 100.0f;
28 return ClampToS16(static_cast<s32>((static_cast<float>(l_channel) * l_mix_amount) +
29 (static_cast<float>(r_channel) * r_mix_amount)));
30}
31
32[[maybe_unused, nodiscard]] static constexpr std::tuple<s16, s16> Mix6To2(
33 s16 fl_channel, s16 fr_channel, s16 fc_channel, [[maybe_unused]] s16 lf_channel, s16 bl_channel,
34 s16 br_channel) {
35 // Front channels are mixed 36.94%, Center channels are mixed to be 26.12% & the back channels
36 // are mixed to be 36.94%
37
38 constexpr float front_mix_amount = 36.94f / 100.0f;
39 constexpr float center_mix_amount = 26.12f / 100.0f;
40 constexpr float back_mix_amount = 36.94f / 100.0f;
41
42 // Mix 50% from left and 50% from right channel
43 const auto left = front_mix_amount * static_cast<float>(fl_channel) +
44 center_mix_amount * static_cast<float>(fc_channel) +
45 back_mix_amount * static_cast<float>(bl_channel);
46
47 const auto right = front_mix_amount * static_cast<float>(fr_channel) +
48 center_mix_amount * static_cast<float>(fc_channel) +
49 back_mix_amount * static_cast<float>(br_channel);
50
51 return {ClampToS16(static_cast<s32>(left)), ClampToS16(static_cast<s32>(right))};
52}
53
54[[nodiscard]] static constexpr std::tuple<s16, s16> Mix6To2WithCoefficients(
55 s16 fl_channel, s16 fr_channel, s16 fc_channel, s16 lf_channel, s16 bl_channel, s16 br_channel,
56 const std::array<float_le, 4>& coeff) {
57 const auto left =
58 static_cast<float>(fl_channel) * coeff[0] + static_cast<float>(fc_channel) * coeff[1] +
59 static_cast<float>(lf_channel) * coeff[2] + static_cast<float>(bl_channel) * coeff[3];
60
61 const auto right =
62 static_cast<float>(fr_channel) * coeff[0] + static_cast<float>(fc_channel) * coeff[1] +
63 static_cast<float>(lf_channel) * coeff[2] + static_cast<float>(br_channel) * coeff[3];
64
65 return {ClampToS16(static_cast<s32>(left)), ClampToS16(static_cast<s32>(right))};
66}
67
68} // namespace
69
70namespace AudioCore {
71constexpr s32 NUM_BUFFERS = 2;
72
73AudioRenderer::AudioRenderer(Core::Timing::CoreTiming& core_timing_, Core::Memory::Memory& memory_,
74 AudioCommon::AudioRendererParameter params,
75 Stream::ReleaseCallback&& release_callback,
76 std::size_t instance_number)
77 : worker_params{params}, memory_pool_info(params.effect_count + params.voice_count * 4),
78 voice_context(params.voice_count), effect_context(params.effect_count), mix_context(),
79 sink_context(params.sink_count), splitter_context(),
80 voices(params.voice_count), memory{memory_},
81 command_generator(worker_params, voice_context, mix_context, splitter_context, effect_context,
82 memory),
83 core_timing{core_timing_} {
84 behavior_info.SetUserRevision(params.revision);
85 splitter_context.Initialize(behavior_info, params.splitter_count,
86 params.num_splitter_send_channels);
87 mix_context.Initialize(behavior_info, params.submix_count + 1, params.effect_count);
88 audio_out = std::make_unique<AudioCore::AudioOut>();
89 stream = audio_out->OpenStream(
90 core_timing, params.sample_rate, AudioCommon::STREAM_NUM_CHANNELS,
91 fmt::format("AudioRenderer-Instance{}", instance_number), std::move(release_callback));
92 process_event =
93 Core::Timing::CreateEvent(fmt::format("AudioRenderer-Instance{}-Process", instance_number),
94 [this](std::uintptr_t, s64, std::chrono::nanoseconds) {
95 ReleaseAndQueueBuffers();
96 return std::nullopt;
97 });
98 for (s32 i = 0; i < NUM_BUFFERS; ++i) {
99 QueueMixedBuffer(i);
100 }
101}
102
103AudioRenderer::~AudioRenderer() = default;
104
105Result AudioRenderer::Start() {
106 audio_out->StartStream(stream);
107 ReleaseAndQueueBuffers();
108 return ResultSuccess;
109}
110
111Result AudioRenderer::Stop() {
112 audio_out->StopStream(stream);
113 return ResultSuccess;
114}
115
116u32 AudioRenderer::GetSampleRate() const {
117 return worker_params.sample_rate;
118}
119
120u32 AudioRenderer::GetSampleCount() const {
121 return worker_params.sample_count;
122}
123
124u32 AudioRenderer::GetMixBufferCount() const {
125 return worker_params.mix_buffer_count;
126}
127
128Stream::State AudioRenderer::GetStreamState() const {
129 return stream->GetState();
130}
131
132Result AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_params,
133 std::vector<u8>& output_params) {
134 std::scoped_lock lock{mutex};
135 InfoUpdater info_updater{input_params, output_params, behavior_info};
136
137 if (!info_updater.UpdateBehaviorInfo(behavior_info)) {
138 LOG_ERROR(Audio, "Failed to update behavior info input parameters");
139 return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
140 }
141
142 if (!info_updater.UpdateMemoryPools(memory_pool_info)) {
143 LOG_ERROR(Audio, "Failed to update memory pool parameters");
144 return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
145 }
146
147 if (!info_updater.UpdateVoiceChannelResources(voice_context)) {
148 LOG_ERROR(Audio, "Failed to update voice channel resource parameters");
149 return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
150 }
151
152 if (!info_updater.UpdateVoices(voice_context, memory_pool_info, 0)) {
153 LOG_ERROR(Audio, "Failed to update voice parameters");
154 return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
155 }
156
157 // TODO(ogniK): Deal with stopped audio renderer but updates still taking place
158 if (!info_updater.UpdateEffects(effect_context, true)) {
159 LOG_ERROR(Audio, "Failed to update effect parameters");
160 return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
161 }
162
163 if (behavior_info.IsSplitterSupported()) {
164 if (!info_updater.UpdateSplitterInfo(splitter_context)) {
165 LOG_ERROR(Audio, "Failed to update splitter parameters");
166 return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
167 }
168 }
169
170 const auto mix_result = info_updater.UpdateMixes(mix_context, worker_params.mix_buffer_count,
171 splitter_context, effect_context);
172
173 if (mix_result.IsError()) {
174 LOG_ERROR(Audio, "Failed to update mix parameters");
175 return mix_result;
176 }
177
178 // TODO(ogniK): Sinks
179 if (!info_updater.UpdateSinks(sink_context)) {
180 LOG_ERROR(Audio, "Failed to update sink parameters");
181 return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
182 }
183
184 // TODO(ogniK): Performance buffer
185 if (!info_updater.UpdatePerformanceBuffer()) {
186 LOG_ERROR(Audio, "Failed to update performance buffer parameters");
187 return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
188 }
189
190 if (!info_updater.UpdateErrorInfo(behavior_info)) {
191 LOG_ERROR(Audio, "Failed to update error info");
192 return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
193 }
194
195 if (behavior_info.IsElapsedFrameCountSupported()) {
196 if (!info_updater.UpdateRendererInfo(elapsed_frame_count)) {
197 LOG_ERROR(Audio, "Failed to update renderer info");
198 return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
199 }
200 }
201 // TODO(ogniK): Statistics
202
203 if (!info_updater.WriteOutputHeader()) {
204 LOG_ERROR(Audio, "Failed to write output header");
205 return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
206 }
207
208 // TODO(ogniK): Check when all sections are implemented
209
210 if (!info_updater.CheckConsumedSize()) {
211 LOG_ERROR(Audio, "Audio buffers were not consumed!");
212 return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
213 }
214 return ResultSuccess;
215}
216
217void AudioRenderer::QueueMixedBuffer(Buffer::Tag tag) {
218 command_generator.PreCommand();
219 // Clear mix buffers before our next operation
220 command_generator.ClearMixBuffers();
221
222 // If the splitter is not in use, sort our mixes
223 if (!splitter_context.UsingSplitter()) {
224 mix_context.SortInfo();
225 }
226 // Sort our voices
227 voice_context.SortInfo();
228
229 // Handle samples
230 command_generator.GenerateVoiceCommands();
231 command_generator.GenerateSubMixCommands();
232 command_generator.GenerateFinalMixCommands();
233
234 command_generator.PostCommand();
235 // Base sample size
236 std::size_t BUFFER_SIZE{worker_params.sample_count};
237 // Samples, making sure to clear
238 std::vector<s16> buffer(BUFFER_SIZE * stream->GetNumChannels(), 0);
239
240 if (sink_context.InUse()) {
241 const auto stream_channel_count = stream->GetNumChannels();
242 const auto buffer_offsets = sink_context.OutputBuffers();
243 const auto channel_count = buffer_offsets.size();
244 const auto& final_mix = mix_context.GetFinalMixInfo();
245 const auto& in_params = final_mix.GetInParams();
246 std::vector<std::span<s32>> mix_buffers(channel_count);
247 for (std::size_t i = 0; i < channel_count; i++) {
248 mix_buffers[i] =
249 command_generator.GetMixBuffer(in_params.buffer_offset + buffer_offsets[i]);
250 }
251
252 for (std::size_t i = 0; i < BUFFER_SIZE; i++) {
253 if (channel_count == 1) {
254 const auto sample = ClampToS16(mix_buffers[0][i]);
255
256 // Place sample in all channels
257 for (u32 channel = 0; channel < stream_channel_count; channel++) {
258 buffer[i * stream_channel_count + channel] = sample;
259 }
260
261 if (stream_channel_count == 6) {
262 // Output stream has a LF channel, mute it!
263 buffer[i * stream_channel_count + 3] = 0;
264 }
265
266 } else if (channel_count == 2) {
267 const auto l_sample = ClampToS16(mix_buffers[0][i]);
268 const auto r_sample = ClampToS16(mix_buffers[1][i]);
269 if (stream_channel_count == 1) {
270 buffer[i * stream_channel_count + 0] = Mix2To1(l_sample, r_sample);
271 } else if (stream_channel_count == 2) {
272 buffer[i * stream_channel_count + 0] = l_sample;
273 buffer[i * stream_channel_count + 1] = r_sample;
274 } else if (stream_channel_count == 6) {
275 buffer[i * stream_channel_count + 0] = l_sample;
276 buffer[i * stream_channel_count + 1] = r_sample;
277
278 // Combine both left and right channels to the center channel
279 buffer[i * stream_channel_count + 2] = Mix2To1(l_sample, r_sample);
280
281 buffer[i * stream_channel_count + 4] = l_sample;
282 buffer[i * stream_channel_count + 5] = r_sample;
283 }
284
285 } else if (channel_count == 6) {
286 const auto fl_sample = ClampToS16(mix_buffers[0][i]);
287 const auto fr_sample = ClampToS16(mix_buffers[1][i]);
288 const auto fc_sample = ClampToS16(mix_buffers[2][i]);
289 const auto lf_sample = ClampToS16(mix_buffers[3][i]);
290 const auto bl_sample = ClampToS16(mix_buffers[4][i]);
291 const auto br_sample = ClampToS16(mix_buffers[5][i]);
292
293 if (stream_channel_count == 1) {
294 // Games seem to ignore the center channel half the time, we use the front left
295 // and right channel for mixing as that's where majority of the audio goes
296 buffer[i * stream_channel_count + 0] = Mix2To1(fl_sample, fr_sample);
297 } else if (stream_channel_count == 2) {
298 // Mix all channels into 2 channels
299 const auto [left, right] = Mix6To2WithCoefficients(
300 fl_sample, fr_sample, fc_sample, lf_sample, bl_sample, br_sample,
301 sink_context.GetDownmixCoefficients());
302 buffer[i * stream_channel_count + 0] = left;
303 buffer[i * stream_channel_count + 1] = right;
304 } else if (stream_channel_count == 6) {
305 // Pass through
306 buffer[i * stream_channel_count + 0] = fl_sample;
307 buffer[i * stream_channel_count + 1] = fr_sample;
308 buffer[i * stream_channel_count + 2] = fc_sample;
309 buffer[i * stream_channel_count + 3] = lf_sample;
310 buffer[i * stream_channel_count + 4] = bl_sample;
311 buffer[i * stream_channel_count + 5] = br_sample;
312 }
313 }
314 }
315 }
316
317 audio_out->QueueBuffer(stream, tag, std::move(buffer));
318 elapsed_frame_count++;
319 voice_context.UpdateStateByDspShared();
320}
321
322void AudioRenderer::ReleaseAndQueueBuffers() {
323 if (!stream->IsPlaying()) {
324 return;
325 }
326
327 {
328 std::scoped_lock lock{mutex};
329 const auto released_buffers{audio_out->GetTagsAndReleaseBuffers(stream)};
330 for (const auto& tag : released_buffers) {
331 QueueMixedBuffer(tag);
332 }
333 }
334
335 const f32 sample_rate = static_cast<f32>(GetSampleRate());
336 const f32 sample_count = static_cast<f32>(GetSampleCount());
337 const f32 consume_rate = sample_rate / (sample_count * (sample_count / 240));
338 const s32 ms = (1000 / static_cast<s32>(consume_rate)) - 1;
339 const std::chrono::milliseconds next_event_time(std::max(ms / NUM_BUFFERS, 1));
340 core_timing.ScheduleEvent(next_event_time, process_event, {});
341}
342
343} // namespace AudioCore
diff --git a/src/audio_core/audio_renderer.h b/src/audio_core/audio_renderer.h
deleted file mode 100644
index a67ffd592..000000000
--- a/src/audio_core/audio_renderer.h
+++ /dev/null
@@ -1,78 +0,0 @@
1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7#include <memory>
8#include <mutex>
9#include <vector>
10
11#include "audio_core/behavior_info.h"
12#include "audio_core/command_generator.h"
13#include "audio_core/common.h"
14#include "audio_core/effect_context.h"
15#include "audio_core/memory_pool.h"
16#include "audio_core/mix_context.h"
17#include "audio_core/sink_context.h"
18#include "audio_core/splitter_context.h"
19#include "audio_core/stream.h"
20#include "audio_core/voice_context.h"
21#include "common/common_funcs.h"
22#include "common/common_types.h"
23#include "common/swap.h"
24#include "core/hle/result.h"
25
26namespace Core::Timing {
27class CoreTiming;
28}
29
30namespace Core::Memory {
31class Memory;
32}
33
34namespace AudioCore {
35using DSPStateHolder = std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>;
36
37class AudioOut;
38
39class AudioRenderer {
40public:
41 AudioRenderer(Core::Timing::CoreTiming& core_timing, Core::Memory::Memory& memory_,
42 AudioCommon::AudioRendererParameter params,
43 Stream::ReleaseCallback&& release_callback, std::size_t instance_number);
44 ~AudioRenderer();
45
46 [[nodiscard]] Result UpdateAudioRenderer(const std::vector<u8>& input_params,
47 std::vector<u8>& output_params);
48 [[nodiscard]] Result Start();
49 [[nodiscard]] Result Stop();
50 void QueueMixedBuffer(Buffer::Tag tag);
51 void ReleaseAndQueueBuffers();
52 [[nodiscard]] u32 GetSampleRate() const;
53 [[nodiscard]] u32 GetSampleCount() const;
54 [[nodiscard]] u32 GetMixBufferCount() const;
55 [[nodiscard]] Stream::State GetStreamState() const;
56
57private:
58 BehaviorInfo behavior_info{};
59
60 AudioCommon::AudioRendererParameter worker_params;
61 std::vector<ServerMemoryPoolInfo> memory_pool_info;
62 VoiceContext voice_context;
63 EffectContext effect_context;
64 MixContext mix_context;
65 SinkContext sink_context;
66 SplitterContext splitter_context;
67 std::vector<VoiceState> voices;
68 std::unique_ptr<AudioOut> audio_out;
69 StreamPtr stream;
70 Core::Memory::Memory& memory;
71 CommandGenerator command_generator;
72 std::size_t elapsed_frame_count{};
73 Core::Timing::CoreTiming& core_timing;
74 std::shared_ptr<Core::Timing::EventType> process_event;
75 std::mutex mutex;
76};
77
78} // namespace AudioCore
diff --git a/src/audio_core/behavior_info.cpp b/src/audio_core/behavior_info.cpp
deleted file mode 100644
index ea7e45617..000000000
--- a/src/audio_core/behavior_info.cpp
+++ /dev/null
@@ -1,104 +0,0 @@
1// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <cstring>
5#include "audio_core/behavior_info.h"
6#include "audio_core/common.h"
7#include "common/logging/log.h"
8
9namespace AudioCore {
10
11BehaviorInfo::BehaviorInfo() : process_revision(AudioCommon::CURRENT_PROCESS_REVISION) {}
12BehaviorInfo::~BehaviorInfo() = default;
13
14bool BehaviorInfo::UpdateOutput(std::vector<u8>& buffer, std::size_t offset) {
15 if (!AudioCommon::CanConsumeBuffer(buffer.size(), offset, sizeof(OutParams))) {
16 LOG_ERROR(Audio, "Buffer is an invalid size!");
17 return false;
18 }
19
20 OutParams params{};
21 std::memcpy(params.errors.data(), errors.data(), sizeof(ErrorInfo) * errors.size());
22 params.error_count = static_cast<u32_le>(error_count);
23 std::memcpy(buffer.data() + offset, &params, sizeof(OutParams));
24 return true;
25}
26
27void BehaviorInfo::ClearError() {
28 error_count = 0;
29}
30
31void BehaviorInfo::UpdateFlags(u64_le dest_flags) {
32 flags = dest_flags;
33}
34
35void BehaviorInfo::SetUserRevision(u32_le revision) {
36 user_revision = revision;
37}
38
39u32_le BehaviorInfo::GetUserRevision() const {
40 return user_revision;
41}
42
43u32_le BehaviorInfo::GetProcessRevision() const {
44 return process_revision;
45}
46
47bool BehaviorInfo::IsAdpcmLoopContextBugFixed() const {
48 return AudioCommon::IsRevisionSupported(2, user_revision);
49}
50
51bool BehaviorInfo::IsSplitterSupported() const {
52 return AudioCommon::IsRevisionSupported(2, user_revision);
53}
54
55bool BehaviorInfo::IsLongSizePreDelaySupported() const {
56 return AudioCommon::IsRevisionSupported(3, user_revision);
57}
58
59bool BehaviorInfo::IsAudioRendererProcessingTimeLimit80PercentSupported() const {
60 return AudioCommon::IsRevisionSupported(5, user_revision);
61}
62
63bool BehaviorInfo::IsAudioRendererProcessingTimeLimit75PercentSupported() const {
64 return AudioCommon::IsRevisionSupported(4, user_revision);
65}
66
67bool BehaviorInfo::IsAudioRendererProcessingTimeLimit70PercentSupported() const {
68 return AudioCommon::IsRevisionSupported(1, user_revision);
69}
70
71bool BehaviorInfo::IsElapsedFrameCountSupported() const {
72 return AudioCommon::IsRevisionSupported(5, user_revision);
73}
74
75bool BehaviorInfo::IsMemoryPoolForceMappingEnabled() const {
76 return (flags & 1) != 0;
77}
78
79bool BehaviorInfo::IsFlushVoiceWaveBuffersSupported() const {
80 return AudioCommon::IsRevisionSupported(5, user_revision);
81}
82
83bool BehaviorInfo::IsVoicePlayedSampleCountResetAtLoopPointSupported() const {
84 return AudioCommon::IsRevisionSupported(5, user_revision);
85}
86
87bool BehaviorInfo::IsVoicePitchAndSrcSkippedSupported() const {
88 return AudioCommon::IsRevisionSupported(5, user_revision);
89}
90
91bool BehaviorInfo::IsMixInParameterDirtyOnlyUpdateSupported() const {
92 return AudioCommon::IsRevisionSupported(7, user_revision);
93}
94
95bool BehaviorInfo::IsSplitterBugFixed() const {
96 return AudioCommon::IsRevisionSupported(5, user_revision);
97}
98
99void BehaviorInfo::CopyErrorInfo(BehaviorInfo::OutParams& dst) {
100 dst.error_count = static_cast<u32>(error_count);
101 std::copy(errors.begin(), errors.begin() + error_count, dst.errors.begin());
102}
103
104} // namespace AudioCore
diff --git a/src/audio_core/behavior_info.h b/src/audio_core/behavior_info.h
deleted file mode 100644
index b8c3159b9..000000000
--- a/src/audio_core/behavior_info.h
+++ /dev/null
@@ -1,71 +0,0 @@
1// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7
8#include <vector>
9#include "common/common_funcs.h"
10#include "common/common_types.h"
11#include "common/swap.h"
12
13namespace AudioCore {
14class BehaviorInfo {
15public:
16 struct ErrorInfo {
17 u32_le result{};
18 INSERT_PADDING_WORDS(1);
19 u64_le result_info{};
20 };
21 static_assert(sizeof(ErrorInfo) == 0x10, "ErrorInfo is an invalid size");
22
23 struct InParams {
24 u32_le revision{};
25 u32_le padding{};
26 u64_le flags{};
27 };
28 static_assert(sizeof(InParams) == 0x10, "InParams is an invalid size");
29
30 struct OutParams {
31 std::array<ErrorInfo, 10> errors{};
32 u32_le error_count{};
33 INSERT_PADDING_BYTES(12);
34 };
35 static_assert(sizeof(OutParams) == 0xb0, "OutParams is an invalid size");
36
37 explicit BehaviorInfo();
38 ~BehaviorInfo();
39
40 bool UpdateOutput(std::vector<u8>& buffer, std::size_t offset);
41
42 void ClearError();
43 void UpdateFlags(u64_le dest_flags);
44 void SetUserRevision(u32_le revision);
45 [[nodiscard]] u32_le GetUserRevision() const;
46 [[nodiscard]] u32_le GetProcessRevision() const;
47
48 [[nodiscard]] bool IsAdpcmLoopContextBugFixed() const;
49 [[nodiscard]] bool IsSplitterSupported() const;
50 [[nodiscard]] bool IsLongSizePreDelaySupported() const;
51 [[nodiscard]] bool IsAudioRendererProcessingTimeLimit80PercentSupported() const;
52 [[nodiscard]] bool IsAudioRendererProcessingTimeLimit75PercentSupported() const;
53 [[nodiscard]] bool IsAudioRendererProcessingTimeLimit70PercentSupported() const;
54 [[nodiscard]] bool IsElapsedFrameCountSupported() const;
55 [[nodiscard]] bool IsMemoryPoolForceMappingEnabled() const;
56 [[nodiscard]] bool IsFlushVoiceWaveBuffersSupported() const;
57 [[nodiscard]] bool IsVoicePlayedSampleCountResetAtLoopPointSupported() const;
58 [[nodiscard]] bool IsVoicePitchAndSrcSkippedSupported() const;
59 [[nodiscard]] bool IsMixInParameterDirtyOnlyUpdateSupported() const;
60 [[nodiscard]] bool IsSplitterBugFixed() const;
61 void CopyErrorInfo(OutParams& dst);
62
63private:
64 u32_le process_revision{};
65 u32_le user_revision{};
66 u64_le flags{};
67 std::array<ErrorInfo, 10> errors{};
68 std::size_t error_count{};
69};
70
71} // namespace AudioCore
diff --git a/src/audio_core/buffer.h b/src/audio_core/buffer.h
deleted file mode 100644
index ac001629f..000000000
--- a/src/audio_core/buffer.h
+++ /dev/null
@@ -1,44 +0,0 @@
1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <memory>
7#include <vector>
8
9#include "common/common_types.h"
10
11namespace AudioCore {
12
13/**
14 * Represents a buffer of audio samples to be played in an audio stream
15 */
16class Buffer {
17public:
18 using Tag = u64;
19
20 Buffer(Tag tag_, std::vector<s16>&& samples_) : tag{tag_}, samples{std::move(samples_)} {}
21
22 /// Returns the raw audio data for the buffer
23 std::vector<s16>& GetSamples() {
24 return samples;
25 }
26
27 /// Returns the raw audio data for the buffer
28 const std::vector<s16>& GetSamples() const {
29 return samples;
30 }
31
32 /// Returns the buffer tag, this is provided by the game to the audout service
33 Tag GetTag() const {
34 return tag;
35 }
36
37private:
38 Tag tag;
39 std::vector<s16> samples;
40};
41
42using BufferPtr = std::shared_ptr<Buffer>;
43
44} // namespace AudioCore
diff --git a/src/audio_core/codec.cpp b/src/audio_core/codec.cpp
deleted file mode 100644
index 868b7a173..000000000
--- a/src/audio_core/codec.cpp
+++ /dev/null
@@ -1,77 +0,0 @@
1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <algorithm>
5
6#include "audio_core/codec.h"
7
8namespace AudioCore::Codec {
9
10std::vector<s16> DecodeADPCM(const u8* const data, std::size_t size, const ADPCM_Coeff& coeff,
11 ADPCMState& state) {
12 // GC-ADPCM with scale factor and variable coefficients.
13 // Frames are 8 bytes long containing 14 samples each.
14 // Samples are 4 bits (one nibble) long.
15
16 constexpr std::size_t FRAME_LEN = 8;
17 constexpr std::size_t SAMPLES_PER_FRAME = 14;
18 static constexpr std::array<int, 16> SIGNED_NIBBLES{
19 0, 1, 2, 3, 4, 5, 6, 7, -8, -7, -6, -5, -4, -3, -2, -1,
20 };
21
22 const std::size_t sample_count = (size / FRAME_LEN) * SAMPLES_PER_FRAME;
23 const std::size_t ret_size =
24 sample_count % 2 == 0 ? sample_count : sample_count + 1; // Ensure multiple of two.
25 std::vector<s16> ret(ret_size);
26
27 int yn1 = state.yn1, yn2 = state.yn2;
28
29 const std::size_t NUM_FRAMES =
30 (sample_count + (SAMPLES_PER_FRAME - 1)) / SAMPLES_PER_FRAME; // Round up.
31 for (std::size_t framei = 0; framei < NUM_FRAMES; framei++) {
32 const int frame_header = data[framei * FRAME_LEN];
33 const int scale = 1 << (frame_header & 0xF);
34 const int idx = (frame_header >> 4) & 0x7;
35
36 // Coefficients are fixed point with 11 bits fractional part.
37 const int coef1 = coeff[idx * 2 + 0];
38 const int coef2 = coeff[idx * 2 + 1];
39
40 // Decodes an audio sample. One nibble produces one sample.
41 const auto decode_sample = [&](const int nibble) -> s16 {
42 const int xn = nibble * scale;
43 // We first transform everything into 11 bit fixed point, perform the second order
44 // digital filter, then transform back.
45 // 0x400 == 0.5 in 11 bit fixed point.
46 // Filter: y[n] = x[n] + 0.5 + c1 * y[n-1] + c2 * y[n-2]
47 int val = ((xn << 11) + 0x400 + coef1 * yn1 + coef2 * yn2) >> 11;
48 // Clamp to output range.
49 val = std::clamp<s32>(val, -32768, 32767);
50 // Advance output feedback.
51 yn2 = yn1;
52 yn1 = val;
53 return static_cast<s16>(val);
54 };
55
56 std::size_t outputi = framei * SAMPLES_PER_FRAME;
57 std::size_t datai = framei * FRAME_LEN + 1;
58 for (std::size_t i = 0; i < SAMPLES_PER_FRAME && outputi < sample_count; i += 2) {
59 const s16 sample1 = decode_sample(SIGNED_NIBBLES[data[datai] >> 4]);
60 ret[outputi] = sample1;
61 outputi++;
62
63 const s16 sample2 = decode_sample(SIGNED_NIBBLES[data[datai] & 0xF]);
64 ret[outputi] = sample2;
65 outputi++;
66
67 datai++;
68 }
69 }
70
71 state.yn1 = static_cast<s16>(yn1);
72 state.yn2 = static_cast<s16>(yn2);
73
74 return ret;
75}
76
77} // namespace AudioCore::Codec
diff --git a/src/audio_core/codec.h b/src/audio_core/codec.h
deleted file mode 100644
index 5a058b368..000000000
--- a/src/audio_core/codec.h
+++ /dev/null
@@ -1,43 +0,0 @@
1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7#include <vector>
8
9#include "common/common_types.h"
10
11namespace AudioCore::Codec {
12
13enum class PcmFormat : u32 {
14 Invalid = 0,
15 Int8 = 1,
16 Int16 = 2,
17 Int24 = 3,
18 Int32 = 4,
19 PcmFloat = 5,
20 Adpcm = 6,
21};
22
23/// See: Codec::DecodeADPCM
24struct ADPCMState {
25 // Two historical samples from previous processed buffer,
26 // required for ADPCM decoding
27 s16 yn1; ///< y[n-1]
28 s16 yn2; ///< y[n-2]
29};
30
31using ADPCM_Coeff = std::array<s16, 16>;
32
33/**
34 * @param data Pointer to buffer that contains ADPCM data to decode
35 * @param size Size of buffer in bytes
36 * @param coeff ADPCM coefficients
37 * @param state ADPCM state, this is updated with new state
38 * @return Decoded stereo signed PCM16 data, sample_count in length
39 */
40std::vector<s16> DecodeADPCM(const u8* data, std::size_t size, const ADPCM_Coeff& coeff,
41 ADPCMState& state);
42
43}; // namespace AudioCore::Codec
diff --git a/src/audio_core/command_generator.cpp b/src/audio_core/command_generator.cpp
deleted file mode 100644
index f97520820..000000000
--- a/src/audio_core/command_generator.cpp
+++ /dev/null
@@ -1,1369 +0,0 @@
1// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <algorithm>
5#include <cmath>
6#include <numbers>
7
8#include "audio_core/algorithm/interpolate.h"
9#include "audio_core/command_generator.h"
10#include "audio_core/effect_context.h"
11#include "audio_core/mix_context.h"
12#include "audio_core/voice_context.h"
13#include "common/common_types.h"
14#include "core/memory.h"
15
16namespace AudioCore {
17namespace {
18constexpr std::size_t MIX_BUFFER_SIZE = 0x3f00;
19constexpr std::size_t SCALED_MIX_BUFFER_SIZE = MIX_BUFFER_SIZE << 15ULL;
20using DelayLineTimes = std::array<f32, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT>;
21
22constexpr DelayLineTimes FDN_MIN_DELAY_LINE_TIMES{5.0f, 6.0f, 13.0f, 14.0f};
23constexpr DelayLineTimes FDN_MAX_DELAY_LINE_TIMES{45.704f, 82.782f, 149.94f, 271.58f};
24constexpr DelayLineTimes DECAY0_MAX_DELAY_LINE_TIMES{17.0f, 13.0f, 9.0f, 7.0f};
25constexpr DelayLineTimes DECAY1_MAX_DELAY_LINE_TIMES{19.0f, 11.0f, 10.0f, 6.0f};
26constexpr std::array<f32, AudioCommon::I3DL2REVERB_TAPS> EARLY_TAP_TIMES{
27 0.017136f, 0.059154f, 0.161733f, 0.390186f, 0.425262f, 0.455411f, 0.689737f,
28 0.745910f, 0.833844f, 0.859502f, 0.000000f, 0.075024f, 0.168788f, 0.299901f,
29 0.337443f, 0.371903f, 0.599011f, 0.716741f, 0.817859f, 0.851664f};
30constexpr std::array<f32, AudioCommon::I3DL2REVERB_TAPS> EARLY_GAIN{
31 0.67096f, 0.61027f, 1.0f, 0.35680f, 0.68361f, 0.65978f, 0.51939f,
32 0.24712f, 0.45945f, 0.45021f, 0.64196f, 0.54879f, 0.92925f, 0.38270f,
33 0.72867f, 0.69794f, 0.5464f, 0.24563f, 0.45214f, 0.44042f};
34
35template <std::size_t N>
36void ApplyMix(std::span<s32> output, std::span<const s32> input, s32 gain, s32 sample_count) {
37 for (std::size_t i = 0; i < static_cast<std::size_t>(sample_count); i += N) {
38 for (std::size_t j = 0; j < N; j++) {
39 output[i + j] +=
40 static_cast<s32>((static_cast<s64>(input[i + j]) * gain + 0x4000) >> 15);
41 }
42 }
43}
44
45s32 ApplyMixRamp(std::span<s32> output, std::span<const s32> input, float gain, float delta,
46 s32 sample_count) {
47 // XC2 passes in NaN mix volumes, causing further issues as we handle everything as s32 rather
48 // than float, so the NaN propogation is lost. As the samples get further modified for
49 // volume etc, they can get out of NaN range, so a later heuristic for catching this is
50 // more difficult. Handle it here by setting these samples to silence.
51 if (std::isnan(gain)) {
52 gain = 0.0f;
53 delta = 0.0f;
54 }
55
56 s32 x = 0;
57 for (s32 i = 0; i < sample_count; i++) {
58 x = static_cast<s32>(static_cast<float>(input[i]) * gain);
59 output[i] += x;
60 gain += delta;
61 }
62 return x;
63}
64
65void ApplyGain(std::span<s32> output, std::span<const s32> input, s32 gain, s32 delta,
66 s32 sample_count) {
67 for (s32 i = 0; i < sample_count; i++) {
68 output[i] = static_cast<s32>((static_cast<s64>(input[i]) * gain + 0x4000) >> 15);
69 gain += delta;
70 }
71}
72
73void ApplyGainWithoutDelta(std::span<s32> output, std::span<const s32> input, s32 gain,
74 s32 sample_count) {
75 for (s32 i = 0; i < sample_count; i++) {
76 output[i] = static_cast<s32>((static_cast<s64>(input[i]) * gain + 0x4000) >> 15);
77 }
78}
79
80s32 ApplyMixDepop(std::span<s32> output, s32 first_sample, s32 delta, s32 sample_count) {
81 const bool positive = first_sample > 0;
82 auto final_sample = std::abs(first_sample);
83 for (s32 i = 0; i < sample_count; i++) {
84 final_sample = static_cast<s32>((static_cast<s64>(final_sample) * delta) >> 15);
85 if (positive) {
86 output[i] += final_sample;
87 } else {
88 output[i] -= final_sample;
89 }
90 }
91 if (positive) {
92 return final_sample;
93 } else {
94 return -final_sample;
95 }
96}
97
98float Pow10(float x) {
99 if (x >= 0.0f) {
100 return 1.0f;
101 } else if (x <= -5.3f) {
102 return 0.0f;
103 }
104 return std::pow(10.0f, x);
105}
106
107float SinD(float degrees) {
108 return std::sin(degrees * std::numbers::pi_v<float> / 180.0f);
109}
110
111float CosD(float degrees) {
112 return std::cos(degrees * std::numbers::pi_v<float> / 180.0f);
113}
114
115float ToFloat(s32 sample) {
116 return static_cast<float>(sample) / 65536.f;
117}
118
119s32 ToS32(float sample) {
120 constexpr auto min = -8388608.0f;
121 constexpr auto max = 8388607.f;
122 float rescaled_sample = sample * 65536.0f;
123 if (rescaled_sample < min) {
124 rescaled_sample = min;
125 }
126 if (rescaled_sample > max) {
127 rescaled_sample = max;
128 }
129 return static_cast<s32>(rescaled_sample);
130}
131
132constexpr std::array<u8, 20> REVERB_TAP_INDEX_1CH{0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
133 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
134
135constexpr std::array<u8, 20> REVERB_TAP_INDEX_2CH{0, 0, 0, 1, 1, 1, 1, 0, 0, 0,
136 1, 1, 1, 0, 0, 0, 0, 1, 1, 1};
137
138constexpr std::array<u8, 20> REVERB_TAP_INDEX_4CH{0, 0, 0, 1, 1, 1, 1, 2, 2, 2,
139 1, 1, 1, 0, 0, 0, 0, 3, 3, 3};
140
141constexpr std::array<u8, 20> REVERB_TAP_INDEX_6CH{4, 0, 0, 1, 1, 1, 1, 2, 2, 2,
142 1, 1, 1, 0, 0, 0, 0, 3, 3, 3};
143
144template <std::size_t CHANNEL_COUNT>
145void ApplyReverbGeneric(
146 I3dl2ReverbState& state,
147 const std::array<std::span<const s32>, AudioCommon::MAX_CHANNEL_COUNT>& input,
148 const std::array<std::span<s32>, AudioCommon::MAX_CHANNEL_COUNT>& output, s32 sample_count) {
149
150 auto GetTapLookup = []() {
151 if constexpr (CHANNEL_COUNT == 1) {
152 return REVERB_TAP_INDEX_1CH;
153 } else if constexpr (CHANNEL_COUNT == 2) {
154 return REVERB_TAP_INDEX_2CH;
155 } else if constexpr (CHANNEL_COUNT == 4) {
156 return REVERB_TAP_INDEX_4CH;
157 } else if constexpr (CHANNEL_COUNT == 6) {
158 return REVERB_TAP_INDEX_6CH;
159 }
160 };
161
162 const auto& tap_index_lut = GetTapLookup();
163 for (s32 sample = 0; sample < sample_count; sample++) {
164 std::array<f32, CHANNEL_COUNT> out_samples{};
165 std::array<f32, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> fsamp{};
166 std::array<f32, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> mixed{};
167 std::array<f32, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> osamp{};
168
169 // Mix everything into a single sample
170 s32 temp_mixed_sample = 0;
171 for (std::size_t i = 0; i < CHANNEL_COUNT; i++) {
172 temp_mixed_sample += input[i][sample];
173 }
174 const auto current_sample = ToFloat(temp_mixed_sample);
175 const auto early_tap = state.early_delay_line.TapOut(state.early_to_late_taps);
176
177 for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_TAPS; i++) {
178 const auto tapped_samp =
179 state.early_delay_line.TapOut(state.early_tap_steps[i]) * EARLY_GAIN[i];
180 out_samples[tap_index_lut[i]] += tapped_samp;
181
182 if constexpr (CHANNEL_COUNT == 6) {
183 // handle lfe
184 out_samples[5] += tapped_samp;
185 }
186 }
187
188 state.lowpass_0 = current_sample * state.lowpass_2 + state.lowpass_0 * state.lowpass_1;
189 state.early_delay_line.Tick(state.lowpass_0);
190
191 for (std::size_t i = 0; i < CHANNEL_COUNT; i++) {
192 out_samples[i] *= state.early_gain;
193 }
194
195 // Two channel seems to apply a latet gain, we require to save this
196 f32 filter{};
197 for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT; i++) {
198 filter = state.fdn_delay_line[i].GetOutputSample();
199 const auto computed = filter * state.lpf_coefficients[0][i] + state.shelf_filter[i];
200 state.shelf_filter[i] =
201 filter * state.lpf_coefficients[1][i] + computed * state.lpf_coefficients[2][i];
202 fsamp[i] = computed;
203 }
204
205 // Mixing matrix
206 mixed[0] = fsamp[1] + fsamp[2];
207 mixed[1] = -fsamp[0] - fsamp[3];
208 mixed[2] = fsamp[0] - fsamp[3];
209 mixed[3] = fsamp[1] - fsamp[2];
210
211 if constexpr (CHANNEL_COUNT == 2) {
212 for (auto& mix : mixed) {
213 mix *= (filter * state.late_gain);
214 }
215 }
216
217 for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT; i++) {
218 const auto late = early_tap * state.late_gain;
219 osamp[i] = state.decay_delay_line0[i].Tick(late + mixed[i]);
220 osamp[i] = state.decay_delay_line1[i].Tick(osamp[i]);
221 state.fdn_delay_line[i].Tick(osamp[i]);
222 }
223
224 if constexpr (CHANNEL_COUNT == 1) {
225 output[0][sample] = ToS32(state.dry_gain * ToFloat(input[0][sample]) +
226 (out_samples[0] + osamp[0] + osamp[1]));
227 } else if constexpr (CHANNEL_COUNT == 2 || CHANNEL_COUNT == 4) {
228 for (std::size_t i = 0; i < CHANNEL_COUNT; i++) {
229 output[i][sample] =
230 ToS32(state.dry_gain * ToFloat(input[i][sample]) + (out_samples[i] + osamp[i]));
231 }
232 } else if constexpr (CHANNEL_COUNT == 6) {
233 const auto temp_center = state.center_delay_line.Tick(0.5f * (osamp[2] - osamp[3]));
234 for (std::size_t i = 0; i < 4; i++) {
235 output[i][sample] =
236 ToS32(state.dry_gain * ToFloat(input[i][sample]) + (out_samples[i] + osamp[i]));
237 }
238 output[4][sample] =
239 ToS32(state.dry_gain * ToFloat(input[4][sample]) + (out_samples[4] + temp_center));
240 output[5][sample] =
241 ToS32(state.dry_gain * ToFloat(input[5][sample]) + (out_samples[5] + osamp[3]));
242 }
243 }
244}
245
246} // namespace
247
248CommandGenerator::CommandGenerator(AudioCommon::AudioRendererParameter& worker_params_,
249 VoiceContext& voice_context_, MixContext& mix_context_,
250 SplitterContext& splitter_context_,
251 EffectContext& effect_context_, Core::Memory::Memory& memory_)
252 : worker_params(worker_params_), voice_context(voice_context_), mix_context(mix_context_),
253 splitter_context(splitter_context_), effect_context(effect_context_), memory(memory_),
254 mix_buffer((worker_params.mix_buffer_count + AudioCommon::MAX_CHANNEL_COUNT) *
255 worker_params.sample_count),
256 sample_buffer(MIX_BUFFER_SIZE),
257 depop_buffer((worker_params.mix_buffer_count + AudioCommon::MAX_CHANNEL_COUNT) *
258 worker_params.sample_count) {}
259CommandGenerator::~CommandGenerator() = default;
260
261void CommandGenerator::ClearMixBuffers() {
262 std::fill(mix_buffer.begin(), mix_buffer.end(), 0);
263 std::fill(sample_buffer.begin(), sample_buffer.end(), 0);
264 // std::fill(depop_buffer.begin(), depop_buffer.end(), 0);
265}
266
267void CommandGenerator::GenerateVoiceCommands() {
268 if (dumping_frame) {
269 LOG_DEBUG(Audio, "(DSP_TRACE) GenerateVoiceCommands");
270 }
271 // Grab all our voices
272 const auto voice_count = voice_context.GetVoiceCount();
273 for (std::size_t i = 0; i < voice_count; i++) {
274 auto& voice_info = voice_context.GetSortedInfo(i);
275 // Update voices and check if we should queue them
276 if (voice_info.ShouldSkip() || !voice_info.UpdateForCommandGeneration(voice_context)) {
277 continue;
278 }
279
280 // Queue our voice
281 GenerateVoiceCommand(voice_info);
282 }
283 // Update our splitters
284 splitter_context.UpdateInternalState();
285}
286
287void CommandGenerator::GenerateVoiceCommand(ServerVoiceInfo& voice_info) {
288 auto& in_params = voice_info.GetInParams();
289 const auto channel_count = in_params.channel_count;
290
291 for (s32 channel = 0; channel < channel_count; channel++) {
292 const auto resource_id = in_params.voice_channel_resource_id[channel];
293 auto& dsp_state = voice_context.GetDspSharedState(resource_id);
294 auto& channel_resource = voice_context.GetChannelResource(resource_id);
295
296 // Decode our samples for our channel
297 GenerateDataSourceCommand(voice_info, dsp_state, channel);
298
299 if (in_params.should_depop) {
300 in_params.last_volume = 0.0f;
301 } else if (in_params.splitter_info_id != AudioCommon::NO_SPLITTER ||
302 in_params.mix_id != AudioCommon::NO_MIX) {
303 // Apply a biquad filter if needed
304 GenerateBiquadFilterCommandForVoice(voice_info, dsp_state,
305 worker_params.mix_buffer_count, channel);
306 // Base voice volume ramping
307 GenerateVolumeRampCommand(in_params.last_volume, in_params.volume, channel,
308 in_params.node_id);
309 in_params.last_volume = in_params.volume;
310
311 if (in_params.mix_id != AudioCommon::NO_MIX) {
312 // If we're using a mix id
313 auto& mix_info = mix_context.GetInfo(in_params.mix_id);
314 const auto& dest_mix_params = mix_info.GetInParams();
315
316 // Voice Mixing
317 GenerateVoiceMixCommand(
318 channel_resource.GetCurrentMixVolume(), channel_resource.GetLastMixVolume(),
319 dsp_state, dest_mix_params.buffer_offset, dest_mix_params.buffer_count,
320 worker_params.mix_buffer_count + channel, in_params.node_id);
321
322 // Update last mix volumes
323 channel_resource.UpdateLastMixVolumes();
324 } else if (in_params.splitter_info_id != AudioCommon::NO_SPLITTER) {
325 s32 base = channel;
326 while (auto* destination_data =
327 GetDestinationData(in_params.splitter_info_id, base)) {
328 base += channel_count;
329
330 if (!destination_data->IsConfigured()) {
331 continue;
332 }
333 if (destination_data->GetMixId() >= static_cast<int>(mix_context.GetCount())) {
334 continue;
335 }
336
337 const auto& mix_info = mix_context.GetInfo(destination_data->GetMixId());
338 const auto& dest_mix_params = mix_info.GetInParams();
339 GenerateVoiceMixCommand(
340 destination_data->CurrentMixVolumes(), destination_data->LastMixVolumes(),
341 dsp_state, dest_mix_params.buffer_offset, dest_mix_params.buffer_count,
342 worker_params.mix_buffer_count + channel, in_params.node_id);
343 destination_data->MarkDirty();
344 }
345 }
346 // Update biquad filter enabled states
347 for (std::size_t i = 0; i < AudioCommon::MAX_BIQUAD_FILTERS; i++) {
348 in_params.was_biquad_filter_enabled[i] = in_params.biquad_filter[i].enabled;
349 }
350 }
351 }
352}
353
354void CommandGenerator::GenerateSubMixCommands() {
355 const auto mix_count = mix_context.GetCount();
356 for (std::size_t i = 0; i < mix_count; i++) {
357 auto& mix_info = mix_context.GetSortedInfo(i);
358 const auto& in_params = mix_info.GetInParams();
359 if (!in_params.in_use || in_params.mix_id == AudioCommon::FINAL_MIX) {
360 continue;
361 }
362 GenerateSubMixCommand(mix_info);
363 }
364}
365
366void CommandGenerator::GenerateFinalMixCommands() {
367 GenerateFinalMixCommand();
368}
369
370void CommandGenerator::PreCommand() {
371 if (!dumping_frame) {
372 return;
373 }
374 for (std::size_t i = 0; i < splitter_context.GetInfoCount(); i++) {
375 const auto& base = splitter_context.GetInfo(i);
376 std::string graph = fmt::format("b[{}]", i);
377 const auto* head = base.GetHead();
378 while (head != nullptr) {
379 graph += fmt::format("->{}", head->GetMixId());
380 head = head->GetNextDestination();
381 }
382 LOG_DEBUG(Audio, "(DSP_TRACE) SplitterGraph splitter_info={}, {}", i, graph);
383 }
384}
385
386void CommandGenerator::PostCommand() {
387 if (!dumping_frame) {
388 return;
389 }
390 dumping_frame = false;
391}
392
393void CommandGenerator::GenerateDataSourceCommand(ServerVoiceInfo& voice_info, VoiceState& dsp_state,
394 s32 channel) {
395 const auto& in_params = voice_info.GetInParams();
396 const auto depop = in_params.should_depop;
397
398 if (depop) {
399 if (in_params.mix_id != AudioCommon::NO_MIX) {
400 auto& mix_info = mix_context.GetInfo(in_params.mix_id);
401 const auto& mix_in = mix_info.GetInParams();
402 GenerateDepopPrepareCommand(dsp_state, mix_in.buffer_count, mix_in.buffer_offset);
403 } else if (in_params.splitter_info_id != AudioCommon::NO_SPLITTER) {
404 s32 index{};
405 while (const auto* destination =
406 GetDestinationData(in_params.splitter_info_id, index++)) {
407 if (!destination->IsConfigured()) {
408 continue;
409 }
410 auto& mix_info = mix_context.GetInfo(destination->GetMixId());
411 const auto& mix_in = mix_info.GetInParams();
412 GenerateDepopPrepareCommand(dsp_state, mix_in.buffer_count, mix_in.buffer_offset);
413 }
414 }
415 } else {
416 switch (in_params.sample_format) {
417 case SampleFormat::Pcm8:
418 case SampleFormat::Pcm16:
419 case SampleFormat::Pcm32:
420 case SampleFormat::PcmFloat:
421 DecodeFromWaveBuffers(voice_info, GetChannelMixBuffer(channel), dsp_state, channel,
422 worker_params.sample_rate, worker_params.sample_count,
423 in_params.node_id);
424 break;
425 case SampleFormat::Adpcm:
426 ASSERT(channel == 0 && in_params.channel_count == 1);
427 DecodeFromWaveBuffers(voice_info, GetChannelMixBuffer(0), dsp_state, 0,
428 worker_params.sample_rate, worker_params.sample_count,
429 in_params.node_id);
430 break;
431 default:
432 ASSERT_MSG(false, "Unimplemented sample format={}", in_params.sample_format);
433 }
434 }
435}
436
437void CommandGenerator::GenerateBiquadFilterCommandForVoice(ServerVoiceInfo& voice_info,
438 VoiceState& dsp_state,
439 [[maybe_unused]] s32 mix_buffer_count,
440 [[maybe_unused]] s32 channel) {
441 for (std::size_t i = 0; i < AudioCommon::MAX_BIQUAD_FILTERS; i++) {
442 const auto& in_params = voice_info.GetInParams();
443 auto& biquad_filter = in_params.biquad_filter[i];
444 // Check if biquad filter is actually used
445 if (!biquad_filter.enabled) {
446 continue;
447 }
448
449 // Reinitialize our biquad filter state if it was enabled previously
450 if (!in_params.was_biquad_filter_enabled[i]) {
451 dsp_state.biquad_filter_state.fill(0);
452 }
453
454 // Generate biquad filter
455 // GenerateBiquadFilterCommand(mix_buffer_count, biquad_filter,
456 // dsp_state.biquad_filter_state,
457 // mix_buffer_count + channel, mix_buffer_count + channel,
458 // worker_params.sample_count, voice_info.GetInParams().node_id);
459 }
460}
461
462void CommandGenerator::GenerateBiquadFilterCommand([[maybe_unused]] s32 mix_buffer_id,
463 const BiquadFilterParameter& params,
464 std::array<s64, 2>& state,
465 std::size_t input_offset,
466 std::size_t output_offset, s32 sample_count,
467 s32 node_id) {
468 if (dumping_frame) {
469 LOG_DEBUG(Audio,
470 "(DSP_TRACE) GenerateBiquadFilterCommand node_id={}, "
471 "input_mix_buffer={}, output_mix_buffer={}",
472 node_id, input_offset, output_offset);
473 }
474 std::span<const s32> input = GetMixBuffer(input_offset);
475 std::span<s32> output = GetMixBuffer(output_offset);
476
477 // Biquad filter parameters
478 const auto [n0, n1, n2] = params.numerator;
479 const auto [d0, d1] = params.denominator;
480
481 // Biquad filter states
482 auto [s0, s1] = state;
483
484 constexpr s64 int32_min = std::numeric_limits<s32>::min();
485 constexpr s64 int32_max = std::numeric_limits<s32>::max();
486
487 for (int i = 0; i < sample_count; ++i) {
488 const auto sample = static_cast<s64>(input[i]);
489 const auto f = (sample * n0 + s0 + 0x4000) >> 15;
490 const auto y = std::clamp(f, int32_min, int32_max);
491 s0 = sample * n1 + y * d0 + s1;
492 s1 = sample * n2 + y * d1;
493 output[i] = static_cast<s32>(y);
494 }
495
496 state = {s0, s1};
497}
498
499void CommandGenerator::GenerateDepopPrepareCommand(VoiceState& dsp_state,
500 std::size_t mix_buffer_count,
501 std::size_t mix_buffer_offset) {
502 for (std::size_t i = 0; i < mix_buffer_count; i++) {
503 auto& sample = dsp_state.previous_samples[i];
504 if (sample != 0) {
505 depop_buffer[mix_buffer_offset + i] += sample;
506 sample = 0;
507 }
508 }
509}
510
511void CommandGenerator::GenerateDepopForMixBuffersCommand(std::size_t mix_buffer_count,
512 std::size_t mix_buffer_offset,
513 s32 sample_rate) {
514 const std::size_t end_offset =
515 std::min(mix_buffer_offset + mix_buffer_count, GetTotalMixBufferCount());
516 const s32 delta = sample_rate == 48000 ? 0x7B29 : 0x78CB;
517 for (std::size_t i = mix_buffer_offset; i < end_offset; i++) {
518 if (depop_buffer[i] == 0) {
519 continue;
520 }
521
522 depop_buffer[i] =
523 ApplyMixDepop(GetMixBuffer(i), depop_buffer[i], delta, worker_params.sample_count);
524 }
525}
526
527void CommandGenerator::GenerateEffectCommand(ServerMixInfo& mix_info) {
528 const std::size_t effect_count = effect_context.GetCount();
529 const auto buffer_offset = mix_info.GetInParams().buffer_offset;
530 for (std::size_t i = 0; i < effect_count; i++) {
531 const auto index = mix_info.GetEffectOrder(i);
532 if (index == AudioCommon::NO_EFFECT_ORDER) {
533 break;
534 }
535 auto* info = effect_context.GetInfo(index);
536 const auto type = info->GetType();
537
538 // TODO(ogniK): Finish remaining effects
539 switch (type) {
540 case EffectType::Aux:
541 GenerateAuxCommand(buffer_offset, info, info->IsEnabled());
542 break;
543 case EffectType::I3dl2Reverb:
544 GenerateI3dl2ReverbEffectCommand(buffer_offset, info, info->IsEnabled());
545 break;
546 case EffectType::BiquadFilter:
547 GenerateBiquadFilterEffectCommand(buffer_offset, info, info->IsEnabled());
548 break;
549 default:
550 break;
551 }
552
553 info->UpdateForCommandGeneration();
554 }
555}
556
557void CommandGenerator::GenerateI3dl2ReverbEffectCommand(s32 mix_buffer_offset, EffectBase* info,
558 bool enabled) {
559 auto* reverb = dynamic_cast<EffectI3dl2Reverb*>(info);
560 const auto& params = reverb->GetParams();
561 auto& state = reverb->GetState();
562 const auto channel_count = params.channel_count;
563
564 if (channel_count != 1 && channel_count != 2 && channel_count != 4 && channel_count != 6) {
565 return;
566 }
567
568 std::array<std::span<const s32>, AudioCommon::MAX_CHANNEL_COUNT> input{};
569 std::array<std::span<s32>, AudioCommon::MAX_CHANNEL_COUNT> output{};
570
571 const auto status = params.status;
572 for (s32 i = 0; i < channel_count; i++) {
573 input[i] = GetMixBuffer(mix_buffer_offset + params.input[i]);
574 output[i] = GetMixBuffer(mix_buffer_offset + params.output[i]);
575 }
576
577 if (enabled) {
578 if (status == ParameterStatus::Initialized) {
579 InitializeI3dl2Reverb(reverb->GetParams(), state, info->GetWorkBuffer());
580 } else if (status == ParameterStatus::Updating) {
581 UpdateI3dl2Reverb(reverb->GetParams(), state, false);
582 }
583 }
584
585 if (enabled) {
586 switch (channel_count) {
587 case 1:
588 ApplyReverbGeneric<1>(state, input, output, worker_params.sample_count);
589 break;
590 case 2:
591 ApplyReverbGeneric<2>(state, input, output, worker_params.sample_count);
592 break;
593 case 4:
594 ApplyReverbGeneric<4>(state, input, output, worker_params.sample_count);
595 break;
596 case 6:
597 ApplyReverbGeneric<6>(state, input, output, worker_params.sample_count);
598 break;
599 }
600 } else {
601 for (s32 i = 0; i < channel_count; i++) {
602 // Only copy if the buffer input and output do not match!
603 if ((mix_buffer_offset + params.input[i]) != (mix_buffer_offset + params.output[i])) {
604 std::memcpy(output[i].data(), input[i].data(),
605 worker_params.sample_count * sizeof(s32));
606 }
607 }
608 }
609}
610
611void CommandGenerator::GenerateBiquadFilterEffectCommand(s32 mix_buffer_offset, EffectBase* info,
612 bool enabled) {
613 if (!enabled) {
614 return;
615 }
616 const auto& params = dynamic_cast<EffectBiquadFilter*>(info)->GetParams();
617 const auto channel_count = params.channel_count;
618 for (s32 i = 0; i < channel_count; i++) {
619 // TODO(ogniK): Actually implement biquad filter
620 if (params.input[i] != params.output[i]) {
621 std::span<const s32> input = GetMixBuffer(mix_buffer_offset + params.input[i]);
622 std::span<s32> output = GetMixBuffer(mix_buffer_offset + params.output[i]);
623 ApplyMix<1>(output, input, 32768, worker_params.sample_count);
624 }
625 }
626}
627
628void CommandGenerator::GenerateAuxCommand(s32 mix_buffer_offset, EffectBase* info, bool enabled) {
629 auto* aux = dynamic_cast<EffectAuxInfo*>(info);
630 const auto& params = aux->GetParams();
631 if (aux->GetSendBuffer() != 0 && aux->GetRecvBuffer() != 0) {
632 const auto max_channels = params.count;
633 u32 offset{};
634 for (u32 channel = 0; channel < max_channels; channel++) {
635 u32 write_count = 0;
636 if (channel == (max_channels - 1)) {
637 write_count = offset + worker_params.sample_count;
638 }
639
640 const auto input_index = params.input_mix_buffers[channel] + mix_buffer_offset;
641 const auto output_index = params.output_mix_buffers[channel] + mix_buffer_offset;
642
643 if (enabled) {
644 AuxInfoDSP send_info{};
645 AuxInfoDSP recv_info{};
646 memory.ReadBlock(aux->GetSendInfo(), &send_info, sizeof(AuxInfoDSP));
647 memory.ReadBlock(aux->GetRecvInfo(), &recv_info, sizeof(AuxInfoDSP));
648
649 WriteAuxBuffer(send_info, aux->GetSendBuffer(), params.sample_count,
650 GetMixBuffer(input_index), worker_params.sample_count, offset,
651 write_count);
652 memory.WriteBlock(aux->GetSendInfo(), &send_info, sizeof(AuxInfoDSP));
653
654 const auto samples_read = ReadAuxBuffer(
655 recv_info, aux->GetRecvBuffer(), params.sample_count,
656 GetMixBuffer(output_index), worker_params.sample_count, offset, write_count);
657 memory.WriteBlock(aux->GetRecvInfo(), &recv_info, sizeof(AuxInfoDSP));
658
659 if (samples_read != static_cast<int>(worker_params.sample_count) &&
660 samples_read <= params.sample_count) {
661 std::memset(GetMixBuffer(output_index).data(), 0,
662 params.sample_count - samples_read);
663 }
664 } else {
665 AuxInfoDSP empty{};
666 memory.WriteBlock(aux->GetSendInfo(), &empty, sizeof(AuxInfoDSP));
667 memory.WriteBlock(aux->GetRecvInfo(), &empty, sizeof(AuxInfoDSP));
668 if (output_index != input_index) {
669 std::memcpy(GetMixBuffer(output_index).data(), GetMixBuffer(input_index).data(),
670 worker_params.sample_count * sizeof(s32));
671 }
672 }
673
674 offset += worker_params.sample_count;
675 }
676 }
677}
678
679ServerSplitterDestinationData* CommandGenerator::GetDestinationData(s32 splitter_id, s32 index) {
680 if (splitter_id == AudioCommon::NO_SPLITTER) {
681 return nullptr;
682 }
683 return splitter_context.GetDestinationData(splitter_id, index);
684}
685
686s32 CommandGenerator::WriteAuxBuffer(AuxInfoDSP& dsp_info, VAddr send_buffer, u32 max_samples,
687 std::span<const s32> data, u32 sample_count, u32 write_offset,
688 u32 write_count) {
689 if (max_samples == 0) {
690 return 0;
691 }
692 u32 offset = dsp_info.write_offset + write_offset;
693 if (send_buffer == 0 || offset > max_samples) {
694 return 0;
695 }
696
697 s32 data_offset{};
698 u32 remaining = sample_count;
699 while (remaining > 0) {
700 // Get position in buffer
701 const auto base = send_buffer + (offset * sizeof(u32));
702 const auto samples_to_grab = std::min(max_samples - offset, remaining);
703 // Write to output
704 memory.WriteBlock(base, (data.data() + data_offset), samples_to_grab * sizeof(u32));
705 offset = (offset + samples_to_grab) % max_samples;
706 remaining -= samples_to_grab;
707 data_offset += samples_to_grab;
708 }
709
710 if (write_count != 0) {
711 dsp_info.write_offset = (dsp_info.write_offset + write_count) % max_samples;
712 }
713 return sample_count;
714}
715
716s32 CommandGenerator::ReadAuxBuffer(AuxInfoDSP& recv_info, VAddr recv_buffer, u32 max_samples,
717 std::span<s32> out_data, u32 sample_count, u32 read_offset,
718 u32 read_count) {
719 if (max_samples == 0) {
720 return 0;
721 }
722
723 u32 offset = recv_info.read_offset + read_offset;
724 if (recv_buffer == 0 || offset > max_samples) {
725 return 0;
726 }
727
728 u32 remaining = sample_count;
729 s32 data_offset{};
730 while (remaining > 0) {
731 const auto base = recv_buffer + (offset * sizeof(u32));
732 const auto samples_to_grab = std::min(max_samples - offset, remaining);
733 std::vector<s32> buffer(samples_to_grab);
734 memory.ReadBlock(base, buffer.data(), buffer.size() * sizeof(u32));
735 std::memcpy(out_data.data() + data_offset, buffer.data(), buffer.size() * sizeof(u32));
736 offset = (offset + samples_to_grab) % max_samples;
737 remaining -= samples_to_grab;
738 data_offset += samples_to_grab;
739 }
740
741 if (read_count != 0) {
742 recv_info.read_offset = (recv_info.read_offset + read_count) % max_samples;
743 }
744 return sample_count;
745}
746
747void CommandGenerator::InitializeI3dl2Reverb(I3dl2ReverbParams& info, I3dl2ReverbState& state,
748 std::vector<u8>& work_buffer) {
749 // Reset state
750 state.lowpass_0 = 0.0f;
751 state.lowpass_1 = 0.0f;
752 state.lowpass_2 = 0.0f;
753
754 state.early_delay_line.Reset();
755 state.early_tap_steps.fill(0);
756 state.early_gain = 0.0f;
757 state.late_gain = 0.0f;
758 state.early_to_late_taps = 0;
759 for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT; i++) {
760 state.fdn_delay_line[i].Reset();
761 state.decay_delay_line0[i].Reset();
762 state.decay_delay_line1[i].Reset();
763 }
764 state.last_reverb_echo = 0.0f;
765 state.center_delay_line.Reset();
766 for (auto& coef : state.lpf_coefficients) {
767 coef.fill(0.0f);
768 }
769 state.shelf_filter.fill(0.0f);
770 state.dry_gain = 0.0f;
771
772 const auto sample_rate = info.sample_rate / 1000;
773 f32* work_buffer_ptr = reinterpret_cast<f32*>(work_buffer.data());
774
775 s32 delay_samples{};
776 for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT; i++) {
777 delay_samples =
778 AudioCommon::CalculateDelaySamples(sample_rate, FDN_MAX_DELAY_LINE_TIMES[i]);
779 state.fdn_delay_line[i].Initialize(delay_samples, work_buffer_ptr);
780 work_buffer_ptr += delay_samples + 1;
781
782 delay_samples =
783 AudioCommon::CalculateDelaySamples(sample_rate, DECAY0_MAX_DELAY_LINE_TIMES[i]);
784 state.decay_delay_line0[i].Initialize(delay_samples, 0.0f, work_buffer_ptr);
785 work_buffer_ptr += delay_samples + 1;
786
787 delay_samples =
788 AudioCommon::CalculateDelaySamples(sample_rate, DECAY1_MAX_DELAY_LINE_TIMES[i]);
789 state.decay_delay_line1[i].Initialize(delay_samples, 0.0f, work_buffer_ptr);
790 work_buffer_ptr += delay_samples + 1;
791 }
792 delay_samples = AudioCommon::CalculateDelaySamples(sample_rate, 5.0f);
793 state.center_delay_line.Initialize(delay_samples, work_buffer_ptr);
794 work_buffer_ptr += delay_samples + 1;
795
796 delay_samples = AudioCommon::CalculateDelaySamples(sample_rate, 400.0f);
797 state.early_delay_line.Initialize(delay_samples, work_buffer_ptr);
798
799 UpdateI3dl2Reverb(info, state, true);
800}
801
802void CommandGenerator::UpdateI3dl2Reverb(I3dl2ReverbParams& info, I3dl2ReverbState& state,
803 bool should_clear) {
804
805 state.dry_gain = info.dry_gain;
806 state.shelf_filter.fill(0.0f);
807 state.lowpass_0 = 0.0f;
808 state.early_gain = Pow10(std::min(info.room + info.reflection, 5000.0f) / 2000.0f);
809 state.late_gain = Pow10(std::min(info.room + info.reverb, 5000.0f) / 2000.0f);
810
811 const auto sample_rate = info.sample_rate / 1000;
812 const f32 hf_gain = Pow10(info.room_hf / 2000.0f);
813 if (hf_gain >= 1.0f) {
814 state.lowpass_2 = 1.0f;
815 state.lowpass_1 = 0.0f;
816 } else {
817 const auto a = 1.0f - hf_gain;
818 const auto b = 2.0f * (2.0f - hf_gain * CosD(256.0f * info.hf_reference /
819 static_cast<f32>(info.sample_rate)));
820 const auto c = std::sqrt(b * b - 4.0f * a * a);
821
822 state.lowpass_1 = (b - c) / (2.0f * a);
823 state.lowpass_2 = 1.0f - state.lowpass_1;
824 }
825 state.early_to_late_taps = AudioCommon::CalculateDelaySamples(
826 sample_rate, 1000.0f * (info.reflection_delay + info.reverb_delay));
827
828 state.last_reverb_echo = 0.6f * info.diffusion * 0.01f;
829 for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT; i++) {
830 const auto length =
831 FDN_MIN_DELAY_LINE_TIMES[i] +
832 (info.density / 100.0f) * (FDN_MAX_DELAY_LINE_TIMES[i] - FDN_MIN_DELAY_LINE_TIMES[i]);
833 state.fdn_delay_line[i].SetDelay(AudioCommon::CalculateDelaySamples(sample_rate, length));
834
835 const auto delay_sample_counts = state.fdn_delay_line[i].GetDelay() +
836 state.decay_delay_line0[i].GetDelay() +
837 state.decay_delay_line1[i].GetDelay();
838
839 float a = (-60.0f * static_cast<f32>(delay_sample_counts)) /
840 (info.decay_time * static_cast<f32>(info.sample_rate));
841 float b = a / info.hf_decay_ratio;
842 float c = CosD(128.0f * 0.5f * info.hf_reference / static_cast<f32>(info.sample_rate)) /
843 SinD(128.0f * 0.5f * info.hf_reference / static_cast<f32>(info.sample_rate));
844 float d = Pow10((b - a) / 40.0f);
845 float e = Pow10((b + a) / 40.0f) * 0.7071f;
846
847 state.lpf_coefficients[0][i] = e * ((d * c) + 1.0f) / (c + d);
848 state.lpf_coefficients[1][i] = e * (1.0f - (d * c)) / (c + d);
849 state.lpf_coefficients[2][i] = (c - d) / (c + d);
850
851 state.decay_delay_line0[i].SetCoefficient(state.last_reverb_echo);
852 state.decay_delay_line1[i].SetCoefficient(-0.9f * state.last_reverb_echo);
853 }
854
855 if (should_clear) {
856 for (std::size_t i = 0; i < AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT; i++) {
857 state.fdn_delay_line[i].Clear();
858 state.decay_delay_line0[i].Clear();
859 state.decay_delay_line1[i].Clear();
860 }
861 state.early_delay_line.Clear();
862 state.center_delay_line.Clear();
863 }
864
865 const auto max_early_delay = state.early_delay_line.GetMaxDelay();
866 const auto reflection_time = 1000.0f * (0.9998f * info.reverb_delay + 0.02f);
867 for (std::size_t tap = 0; tap < AudioCommon::I3DL2REVERB_TAPS; tap++) {
868 const auto length = AudioCommon::CalculateDelaySamples(
869 sample_rate, 1000.0f * info.reflection_delay + reflection_time * EARLY_TAP_TIMES[tap]);
870 state.early_tap_steps[tap] = std::min(length, max_early_delay);
871 }
872}
873
874void CommandGenerator::GenerateVolumeRampCommand(float last_volume, float current_volume,
875 s32 channel, s32 node_id) {
876 const auto last = static_cast<s32>(last_volume * 32768.0f);
877 const auto current = static_cast<s32>(current_volume * 32768.0f);
878 const auto delta = static_cast<s32>((static_cast<float>(current) - static_cast<float>(last)) /
879 static_cast<float>(worker_params.sample_count));
880
881 if (dumping_frame) {
882 LOG_DEBUG(Audio,
883 "(DSP_TRACE) GenerateVolumeRampCommand node_id={}, input={}, output={}, "
884 "last_volume={}, current_volume={}",
885 node_id, GetMixChannelBufferOffset(channel), GetMixChannelBufferOffset(channel),
886 last_volume, current_volume);
887 }
888 // Apply generic gain on samples
889 ApplyGain(GetChannelMixBuffer(channel), GetChannelMixBuffer(channel), last, delta,
890 worker_params.sample_count);
891}
892
893void CommandGenerator::GenerateVoiceMixCommand(const MixVolumeBuffer& mix_volumes,
894 const MixVolumeBuffer& last_mix_volumes,
895 VoiceState& dsp_state, s32 mix_buffer_offset,
896 s32 mix_buffer_count, s32 voice_index, s32 node_id) {
897 // Loop all our mix buffers
898 for (s32 i = 0; i < mix_buffer_count; i++) {
899 if (last_mix_volumes[i] != 0.0f || mix_volumes[i] != 0.0f) {
900 const auto delta = static_cast<float>((mix_volumes[i] - last_mix_volumes[i])) /
901 static_cast<float>(worker_params.sample_count);
902
903 if (dumping_frame) {
904 LOG_DEBUG(Audio,
905 "(DSP_TRACE) GenerateVoiceMixCommand node_id={}, input={}, "
906 "output={}, last_volume={}, current_volume={}",
907 node_id, voice_index, mix_buffer_offset + i, last_mix_volumes[i],
908 mix_volumes[i]);
909 }
910
911 dsp_state.previous_samples[i] =
912 ApplyMixRamp(GetMixBuffer(mix_buffer_offset + i), GetMixBuffer(voice_index),
913 last_mix_volumes[i], delta, worker_params.sample_count);
914 } else {
915 dsp_state.previous_samples[i] = 0;
916 }
917 }
918}
919
920void CommandGenerator::GenerateSubMixCommand(ServerMixInfo& mix_info) {
921 if (dumping_frame) {
922 LOG_DEBUG(Audio, "(DSP_TRACE) GenerateSubMixCommand");
923 }
924 const auto& in_params = mix_info.GetInParams();
925 GenerateDepopForMixBuffersCommand(in_params.buffer_count, in_params.buffer_offset,
926 in_params.sample_rate);
927
928 GenerateEffectCommand(mix_info);
929
930 GenerateMixCommands(mix_info);
931}
932
933void CommandGenerator::GenerateMixCommands(ServerMixInfo& mix_info) {
934 if (!mix_info.HasAnyConnection()) {
935 return;
936 }
937 const auto& in_params = mix_info.GetInParams();
938 if (in_params.dest_mix_id != AudioCommon::NO_MIX) {
939 const auto& dest_mix = mix_context.GetInfo(in_params.dest_mix_id);
940 const auto& dest_in_params = dest_mix.GetInParams();
941
942 const auto buffer_count = in_params.buffer_count;
943
944 for (s32 i = 0; i < buffer_count; i++) {
945 for (s32 j = 0; j < dest_in_params.buffer_count; j++) {
946 const auto mixed_volume = in_params.volume * in_params.mix_volume[i][j];
947 if (mixed_volume != 0.0f) {
948 GenerateMixCommand(dest_in_params.buffer_offset + j,
949 in_params.buffer_offset + i, mixed_volume,
950 in_params.node_id);
951 }
952 }
953 }
954 } else if (in_params.splitter_id != AudioCommon::NO_SPLITTER) {
955 s32 base{};
956 while (const auto* destination_data = GetDestinationData(in_params.splitter_id, base++)) {
957 if (!destination_data->IsConfigured()) {
958 continue;
959 }
960
961 const auto& dest_mix = mix_context.GetInfo(destination_data->GetMixId());
962 const auto& dest_in_params = dest_mix.GetInParams();
963 const auto mix_index = (base - 1) % in_params.buffer_count + in_params.buffer_offset;
964 for (std::size_t i = 0; i < static_cast<std::size_t>(dest_in_params.buffer_count);
965 i++) {
966 const auto mixed_volume = in_params.volume * destination_data->GetMixVolume(i);
967 if (mixed_volume != 0.0f) {
968 GenerateMixCommand(dest_in_params.buffer_offset + i, mix_index, mixed_volume,
969 in_params.node_id);
970 }
971 }
972 }
973 }
974}
975
976void CommandGenerator::GenerateMixCommand(std::size_t output_offset, std::size_t input_offset,
977 float volume, s32 node_id) {
978
979 if (dumping_frame) {
980 LOG_DEBUG(Audio,
981 "(DSP_TRACE) GenerateMixCommand node_id={}, input={}, output={}, volume={}",
982 node_id, input_offset, output_offset, volume);
983 }
984
985 std::span<s32> output = GetMixBuffer(output_offset);
986 std::span<const s32> input = GetMixBuffer(input_offset);
987
988 const s32 gain = static_cast<s32>(volume * 32768.0f);
989 // Mix with loop unrolling
990 if (worker_params.sample_count % 4 == 0) {
991 ApplyMix<4>(output, input, gain, worker_params.sample_count);
992 } else if (worker_params.sample_count % 2 == 0) {
993 ApplyMix<2>(output, input, gain, worker_params.sample_count);
994 } else {
995 ApplyMix<1>(output, input, gain, worker_params.sample_count);
996 }
997}
998
999void CommandGenerator::GenerateFinalMixCommand() {
1000 if (dumping_frame) {
1001 LOG_DEBUG(Audio, "(DSP_TRACE) GenerateFinalMixCommand");
1002 }
1003 auto& mix_info = mix_context.GetFinalMixInfo();
1004 const auto& in_params = mix_info.GetInParams();
1005
1006 GenerateDepopForMixBuffersCommand(in_params.buffer_count, in_params.buffer_offset,
1007 in_params.sample_rate);
1008
1009 GenerateEffectCommand(mix_info);
1010
1011 for (s32 i = 0; i < in_params.buffer_count; i++) {
1012 const s32 gain = static_cast<s32>(in_params.volume * 32768.0f);
1013 if (dumping_frame) {
1014 LOG_DEBUG(
1015 Audio,
1016 "(DSP_TRACE) ApplyGainWithoutDelta node_id={}, input={}, output={}, volume={}",
1017 in_params.node_id, in_params.buffer_offset + i, in_params.buffer_offset + i,
1018 in_params.volume);
1019 }
1020 ApplyGainWithoutDelta(GetMixBuffer(in_params.buffer_offset + i),
1021 GetMixBuffer(in_params.buffer_offset + i), gain,
1022 worker_params.sample_count);
1023 }
1024}
1025
1026template <typename T>
1027s32 CommandGenerator::DecodePcm(ServerVoiceInfo& voice_info, VoiceState& dsp_state,
1028 s32 sample_start_offset, s32 sample_end_offset, s32 sample_count,
1029 s32 channel, std::size_t mix_offset) {
1030 const auto& in_params = voice_info.GetInParams();
1031 const auto& wave_buffer = in_params.wave_buffer[dsp_state.wave_buffer_index];
1032 if (wave_buffer.buffer_address == 0) {
1033 return 0;
1034 }
1035 if (wave_buffer.buffer_size == 0) {
1036 return 0;
1037 }
1038 if (sample_end_offset < sample_start_offset) {
1039 return 0;
1040 }
1041 const auto samples_remaining = (sample_end_offset - sample_start_offset) - dsp_state.offset;
1042 const auto start_offset =
1043 ((dsp_state.offset + sample_start_offset) * in_params.channel_count) * sizeof(T);
1044 const auto buffer_pos = wave_buffer.buffer_address + start_offset;
1045 const auto samples_processed = std::min(sample_count, samples_remaining);
1046
1047 const auto channel_count = in_params.channel_count;
1048 std::vector<T> buffer(samples_processed * channel_count);
1049 memory.ReadBlock(buffer_pos, buffer.data(), buffer.size() * sizeof(T));
1050
1051 if constexpr (std::is_floating_point_v<T>) {
1052 for (std::size_t i = 0; i < static_cast<std::size_t>(samples_processed); i++) {
1053 sample_buffer[mix_offset + i] = static_cast<s32>(buffer[i * channel_count + channel] *
1054 std::numeric_limits<s16>::max());
1055 }
1056 } else if constexpr (sizeof(T) == 1) {
1057 for (std::size_t i = 0; i < static_cast<std::size_t>(samples_processed); i++) {
1058 sample_buffer[mix_offset + i] =
1059 static_cast<s32>(static_cast<f32>(buffer[i * channel_count + channel] /
1060 std::numeric_limits<s8>::max()) *
1061 std::numeric_limits<s16>::max());
1062 }
1063 } else if constexpr (sizeof(T) == 2) {
1064 for (std::size_t i = 0; i < static_cast<std::size_t>(samples_processed); i++) {
1065 sample_buffer[mix_offset + i] = buffer[i * channel_count + channel];
1066 }
1067 } else {
1068 for (std::size_t i = 0; i < static_cast<std::size_t>(samples_processed); i++) {
1069 sample_buffer[mix_offset + i] =
1070 static_cast<s32>(static_cast<f32>(buffer[i * channel_count + channel] /
1071 std::numeric_limits<s32>::max()) *
1072 std::numeric_limits<s16>::max());
1073 }
1074 }
1075
1076 return samples_processed;
1077}
1078
1079s32 CommandGenerator::DecodeAdpcm(ServerVoiceInfo& voice_info, VoiceState& dsp_state,
1080 s32 sample_start_offset, s32 sample_end_offset, s32 sample_count,
1081 [[maybe_unused]] s32 channel, std::size_t mix_offset) {
1082 const auto& in_params = voice_info.GetInParams();
1083 const auto& wave_buffer = in_params.wave_buffer[dsp_state.wave_buffer_index];
1084 if (wave_buffer.buffer_address == 0) {
1085 return 0;
1086 }
1087 if (wave_buffer.buffer_size == 0) {
1088 return 0;
1089 }
1090 if (sample_end_offset < sample_start_offset) {
1091 return 0;
1092 }
1093
1094 static constexpr std::array<int, 16> SIGNED_NIBBLES{
1095 0, 1, 2, 3, 4, 5, 6, 7, -8, -7, -6, -5, -4, -3, -2, -1,
1096 };
1097
1098 constexpr std::size_t FRAME_LEN = 8;
1099 constexpr std::size_t NIBBLES_PER_SAMPLE = 16;
1100 constexpr std::size_t SAMPLES_PER_FRAME = 14;
1101
1102 auto frame_header = dsp_state.context.header;
1103 s32 idx = (frame_header >> 4) & 0xf;
1104 s32 scale = frame_header & 0xf;
1105 s16 yn1 = dsp_state.context.yn1;
1106 s16 yn2 = dsp_state.context.yn2;
1107
1108 Codec::ADPCM_Coeff coeffs;
1109 memory.ReadBlock(in_params.additional_params_address, coeffs.data(),
1110 sizeof(Codec::ADPCM_Coeff));
1111
1112 s32 coef1 = coeffs[idx * 2];
1113 s32 coef2 = coeffs[idx * 2 + 1];
1114
1115 const auto samples_remaining = (sample_end_offset - sample_start_offset) - dsp_state.offset;
1116 const auto samples_processed = std::min(sample_count, samples_remaining);
1117 const auto sample_pos = dsp_state.offset + sample_start_offset;
1118
1119 const auto samples_remaining_in_frame = sample_pos % SAMPLES_PER_FRAME;
1120 auto position_in_frame = ((sample_pos / SAMPLES_PER_FRAME) * NIBBLES_PER_SAMPLE) +
1121 samples_remaining_in_frame + (samples_remaining_in_frame != 0 ? 2 : 0);
1122
1123 const auto decode_sample = [&](const int nibble) -> s16 {
1124 const int xn = nibble * (1 << scale);
1125 // We first transform everything into 11 bit fixed point, perform the second order
1126 // digital filter, then transform back.
1127 // 0x400 == 0.5 in 11 bit fixed point.
1128 // Filter: y[n] = x[n] + 0.5 + c1 * y[n-1] + c2 * y[n-2]
1129 int val = ((xn << 11) + 0x400 + coef1 * yn1 + coef2 * yn2) >> 11;
1130 // Clamp to output range.
1131 val = std::clamp<s32>(val, -32768, 32767);
1132 // Advance output feedback.
1133 yn2 = yn1;
1134 yn1 = static_cast<s16>(val);
1135 return yn1;
1136 };
1137
1138 std::size_t buffer_offset{};
1139 std::vector<u8> buffer(
1140 std::max((samples_processed / FRAME_LEN) * SAMPLES_PER_FRAME, FRAME_LEN));
1141 memory.ReadBlock(wave_buffer.buffer_address + (position_in_frame / 2), buffer.data(),
1142 buffer.size());
1143 std::size_t cur_mix_offset = mix_offset;
1144
1145 auto remaining_samples = samples_processed;
1146 while (remaining_samples > 0) {
1147 if (position_in_frame % NIBBLES_PER_SAMPLE == 0) {
1148 // Read header
1149 frame_header = buffer[buffer_offset++];
1150 idx = (frame_header >> 4) & 0xf;
1151 scale = frame_header & 0xf;
1152 coef1 = coeffs[idx * 2];
1153 coef2 = coeffs[idx * 2 + 1];
1154 position_in_frame += 2;
1155
1156 // Decode entire frame
1157 if (remaining_samples >= static_cast<int>(SAMPLES_PER_FRAME)) {
1158 for (std::size_t i = 0; i < SAMPLES_PER_FRAME / 2; i++) {
1159 // Sample 1
1160 const s32 s0 = SIGNED_NIBBLES[buffer[buffer_offset] >> 4];
1161 const s32 s1 = SIGNED_NIBBLES[buffer[buffer_offset++] & 0xf];
1162 const s16 sample_1 = decode_sample(s0);
1163 const s16 sample_2 = decode_sample(s1);
1164 sample_buffer[cur_mix_offset++] = sample_1;
1165 sample_buffer[cur_mix_offset++] = sample_2;
1166 }
1167 remaining_samples -= static_cast<int>(SAMPLES_PER_FRAME);
1168 position_in_frame += SAMPLES_PER_FRAME;
1169 continue;
1170 }
1171 }
1172 // Decode mid frame
1173 s32 current_nibble = buffer[buffer_offset];
1174 if (position_in_frame++ & 0x1) {
1175 current_nibble &= 0xf;
1176 buffer_offset++;
1177 } else {
1178 current_nibble >>= 4;
1179 }
1180 const s16 sample = decode_sample(SIGNED_NIBBLES[current_nibble]);
1181 sample_buffer[cur_mix_offset++] = sample;
1182 remaining_samples--;
1183 }
1184
1185 dsp_state.context.header = frame_header;
1186 dsp_state.context.yn1 = yn1;
1187 dsp_state.context.yn2 = yn2;
1188
1189 return samples_processed;
1190}
1191
1192std::span<s32> CommandGenerator::GetMixBuffer(std::size_t index) {
1193 return std::span<s32>(mix_buffer.data() + (index * worker_params.sample_count),
1194 worker_params.sample_count);
1195}
1196
1197std::span<const s32> CommandGenerator::GetMixBuffer(std::size_t index) const {
1198 return std::span<const s32>(mix_buffer.data() + (index * worker_params.sample_count),
1199 worker_params.sample_count);
1200}
1201
1202std::size_t CommandGenerator::GetMixChannelBufferOffset(s32 channel) const {
1203 return worker_params.mix_buffer_count + channel;
1204}
1205
1206std::size_t CommandGenerator::GetTotalMixBufferCount() const {
1207 return worker_params.mix_buffer_count + AudioCommon::MAX_CHANNEL_COUNT;
1208}
1209
1210std::span<s32> CommandGenerator::GetChannelMixBuffer(s32 channel) {
1211 return GetMixBuffer(worker_params.mix_buffer_count + channel);
1212}
1213
1214std::span<const s32> CommandGenerator::GetChannelMixBuffer(s32 channel) const {
1215 return GetMixBuffer(worker_params.mix_buffer_count + channel);
1216}
1217
1218void CommandGenerator::DecodeFromWaveBuffers(ServerVoiceInfo& voice_info, std::span<s32> output,
1219 VoiceState& dsp_state, s32 channel,
1220 s32 target_sample_rate, s32 sample_count,
1221 s32 node_id) {
1222 const auto& in_params = voice_info.GetInParams();
1223 if (dumping_frame) {
1224 LOG_DEBUG(Audio,
1225 "(DSP_TRACE) DecodeFromWaveBuffers, node_id={}, channel={}, "
1226 "format={}, sample_count={}, sample_rate={}, mix_id={}, splitter_id={}",
1227 node_id, channel, in_params.sample_format, sample_count, in_params.sample_rate,
1228 in_params.mix_id, in_params.splitter_info_id);
1229 }
1230 ASSERT_OR_EXECUTE(output.data() != nullptr, { return; });
1231
1232 const auto resample_rate = static_cast<s32>(
1233 static_cast<float>(in_params.sample_rate) / static_cast<float>(target_sample_rate) *
1234 static_cast<float>(static_cast<s32>(in_params.pitch * 32768.0f)));
1235 if (dsp_state.fraction + sample_count * resample_rate >
1236 static_cast<s32>(SCALED_MIX_BUFFER_SIZE - 4ULL)) {
1237 return;
1238 }
1239
1240 auto min_required_samples =
1241 std::min(static_cast<s32>(SCALED_MIX_BUFFER_SIZE) - dsp_state.fraction, resample_rate);
1242 if (min_required_samples >= sample_count) {
1243 min_required_samples = sample_count;
1244 }
1245
1246 std::size_t temp_mix_offset{};
1247 s32 samples_output{};
1248 auto samples_remaining = sample_count;
1249 while (samples_remaining > 0) {
1250 const auto samples_to_output = std::min(samples_remaining, min_required_samples);
1251 const auto samples_to_read = (samples_to_output * resample_rate + dsp_state.fraction) >> 15;
1252
1253 if (!in_params.behavior_flags.is_pitch_and_src_skipped) {
1254 // Append sample histtory for resampler
1255 for (std::size_t i = 0; i < AudioCommon::MAX_SAMPLE_HISTORY; i++) {
1256 sample_buffer[temp_mix_offset + i] = dsp_state.sample_history[i];
1257 }
1258 temp_mix_offset += 4;
1259 }
1260
1261 s32 samples_read{};
1262 while (samples_read < samples_to_read) {
1263 const auto& wave_buffer = in_params.wave_buffer[dsp_state.wave_buffer_index];
1264 // No more data can be read
1265 if (!dsp_state.is_wave_buffer_valid[dsp_state.wave_buffer_index]) {
1266 break;
1267 }
1268
1269 if (in_params.sample_format == SampleFormat::Adpcm && dsp_state.offset == 0 &&
1270 wave_buffer.context_address != 0 && wave_buffer.context_size != 0) {
1271 memory.ReadBlock(wave_buffer.context_address, &dsp_state.context,
1272 sizeof(ADPCMContext));
1273 }
1274
1275 s32 samples_offset_start;
1276 s32 samples_offset_end;
1277 if (dsp_state.loop_count > 0 && wave_buffer.loop_start_sample != 0 &&
1278 wave_buffer.loop_end_sample != 0 &&
1279 wave_buffer.loop_start_sample <= wave_buffer.loop_end_sample) {
1280 samples_offset_start = wave_buffer.loop_start_sample;
1281 samples_offset_end = wave_buffer.loop_end_sample;
1282 } else {
1283 samples_offset_start = wave_buffer.start_sample_offset;
1284 samples_offset_end = wave_buffer.end_sample_offset;
1285 }
1286
1287 s32 samples_decoded{0};
1288 switch (in_params.sample_format) {
1289 case SampleFormat::Pcm8:
1290 samples_decoded =
1291 DecodePcm<s8>(voice_info, dsp_state, samples_offset_start, samples_offset_end,
1292 samples_to_read - samples_read, channel, temp_mix_offset);
1293 break;
1294 case SampleFormat::Pcm16:
1295 samples_decoded =
1296 DecodePcm<s16>(voice_info, dsp_state, samples_offset_start, samples_offset_end,
1297 samples_to_read - samples_read, channel, temp_mix_offset);
1298 break;
1299 case SampleFormat::Pcm32:
1300 samples_decoded =
1301 DecodePcm<s32>(voice_info, dsp_state, samples_offset_start, samples_offset_end,
1302 samples_to_read - samples_read, channel, temp_mix_offset);
1303 break;
1304 case SampleFormat::PcmFloat:
1305 samples_decoded =
1306 DecodePcm<f32>(voice_info, dsp_state, samples_offset_start, samples_offset_end,
1307 samples_to_read - samples_read, channel, temp_mix_offset);
1308 break;
1309 case SampleFormat::Adpcm:
1310 samples_decoded =
1311 DecodeAdpcm(voice_info, dsp_state, samples_offset_start, samples_offset_end,
1312 samples_to_read - samples_read, channel, temp_mix_offset);
1313 break;
1314 default:
1315 ASSERT_MSG(false, "Unimplemented sample format={}", in_params.sample_format);
1316 }
1317
1318 temp_mix_offset += samples_decoded;
1319 samples_read += samples_decoded;
1320 dsp_state.offset += samples_decoded;
1321 dsp_state.played_sample_count += samples_decoded;
1322
1323 if (dsp_state.offset >= (samples_offset_end - samples_offset_start) ||
1324 samples_decoded == 0) {
1325 // Reset our sample offset
1326 dsp_state.offset = 0;
1327 if (wave_buffer.is_looping) {
1328 dsp_state.loop_count++;
1329 if (wave_buffer.loop_count > 0 &&
1330 (dsp_state.loop_count > wave_buffer.loop_count || samples_decoded == 0)) {
1331 // End of our buffer
1332 voice_info.SetWaveBufferCompleted(dsp_state, wave_buffer);
1333 }
1334
1335 if (samples_decoded == 0) {
1336 break;
1337 }
1338
1339 if (in_params.behavior_flags.is_played_samples_reset_at_loop_point.Value()) {
1340 dsp_state.played_sample_count = 0;
1341 }
1342 } else {
1343 // Update our wave buffer states
1344 voice_info.SetWaveBufferCompleted(dsp_state, wave_buffer);
1345 }
1346 }
1347 }
1348
1349 if (in_params.behavior_flags.is_pitch_and_src_skipped.Value()) {
1350 // No need to resample
1351 std::memcpy(output.data() + samples_output, sample_buffer.data(),
1352 samples_read * sizeof(s32));
1353 } else {
1354 std::fill(sample_buffer.begin() + temp_mix_offset,
1355 sample_buffer.begin() + temp_mix_offset + (samples_to_read - samples_read),
1356 0);
1357 AudioCore::Resample(output.data() + samples_output, sample_buffer.data(), resample_rate,
1358 dsp_state.fraction, samples_to_output);
1359 // Resample
1360 for (std::size_t i = 0; i < AudioCommon::MAX_SAMPLE_HISTORY; i++) {
1361 dsp_state.sample_history[i] = sample_buffer[samples_to_read + i];
1362 }
1363 }
1364 samples_remaining -= samples_to_output;
1365 samples_output += samples_to_output;
1366 }
1367}
1368
1369} // namespace AudioCore
diff --git a/src/audio_core/command_generator.h b/src/audio_core/command_generator.h
deleted file mode 100644
index 8077e7768..000000000
--- a/src/audio_core/command_generator.h
+++ /dev/null
@@ -1,110 +0,0 @@
1// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7#include <span>
8#include "audio_core/common.h"
9#include "audio_core/voice_context.h"
10#include "common/common_types.h"
11
12namespace Core::Memory {
13class Memory;
14}
15
16namespace AudioCore {
17class MixContext;
18class SplitterContext;
19class ServerSplitterDestinationData;
20class ServerMixInfo;
21class EffectContext;
22class EffectBase;
23struct AuxInfoDSP;
24struct I3dl2ReverbParams;
25struct I3dl2ReverbState;
26using MixVolumeBuffer = std::array<float, AudioCommon::MAX_MIX_BUFFERS>;
27
28class CommandGenerator {
29public:
30 explicit CommandGenerator(AudioCommon::AudioRendererParameter& worker_params_,
31 VoiceContext& voice_context_, MixContext& mix_context_,
32 SplitterContext& splitter_context_, EffectContext& effect_context_,
33 Core::Memory::Memory& memory_);
34 ~CommandGenerator();
35
36 void ClearMixBuffers();
37 void GenerateVoiceCommands();
38 void GenerateVoiceCommand(ServerVoiceInfo& voice_info);
39 void GenerateSubMixCommands();
40 void GenerateFinalMixCommands();
41 void PreCommand();
42 void PostCommand();
43
44 [[nodiscard]] std::span<s32> GetChannelMixBuffer(s32 channel);
45 [[nodiscard]] std::span<const s32> GetChannelMixBuffer(s32 channel) const;
46 [[nodiscard]] std::span<s32> GetMixBuffer(std::size_t index);
47 [[nodiscard]] std::span<const s32> GetMixBuffer(std::size_t index) const;
48 [[nodiscard]] std::size_t GetMixChannelBufferOffset(s32 channel) const;
49
50 [[nodiscard]] std::size_t GetTotalMixBufferCount() const;
51
52private:
53 void GenerateDataSourceCommand(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 channel);
54 void GenerateBiquadFilterCommandForVoice(ServerVoiceInfo& voice_info, VoiceState& dsp_state,
55 s32 mix_buffer_count, s32 channel);
56 void GenerateVolumeRampCommand(float last_volume, float current_volume, s32 channel,
57 s32 node_id);
58 void GenerateVoiceMixCommand(const MixVolumeBuffer& mix_volumes,
59 const MixVolumeBuffer& last_mix_volumes, VoiceState& dsp_state,
60 s32 mix_buffer_offset, s32 mix_buffer_count, s32 voice_index,
61 s32 node_id);
62 void GenerateSubMixCommand(ServerMixInfo& mix_info);
63 void GenerateMixCommands(ServerMixInfo& mix_info);
64 void GenerateMixCommand(std::size_t output_offset, std::size_t input_offset, float volume,
65 s32 node_id);
66 void GenerateFinalMixCommand();
67 void GenerateBiquadFilterCommand(s32 mix_buffer, const BiquadFilterParameter& params,
68 std::array<s64, 2>& state, std::size_t input_offset,
69 std::size_t output_offset, s32 sample_count, s32 node_id);
70 void GenerateDepopPrepareCommand(VoiceState& dsp_state, std::size_t mix_buffer_count,
71 std::size_t mix_buffer_offset);
72 void GenerateDepopForMixBuffersCommand(std::size_t mix_buffer_count,
73 std::size_t mix_buffer_offset, s32 sample_rate);
74 void GenerateEffectCommand(ServerMixInfo& mix_info);
75 void GenerateI3dl2ReverbEffectCommand(s32 mix_buffer_offset, EffectBase* info, bool enabled);
76 void GenerateBiquadFilterEffectCommand(s32 mix_buffer_offset, EffectBase* info, bool enabled);
77 void GenerateAuxCommand(s32 mix_buffer_offset, EffectBase* info, bool enabled);
78 [[nodiscard]] ServerSplitterDestinationData* GetDestinationData(s32 splitter_id, s32 index);
79
80 s32 WriteAuxBuffer(AuxInfoDSP& dsp_info, VAddr send_buffer, u32 max_samples,
81 std::span<const s32> data, u32 sample_count, u32 write_offset,
82 u32 write_count);
83 s32 ReadAuxBuffer(AuxInfoDSP& recv_info, VAddr recv_buffer, u32 max_samples,
84 std::span<s32> out_data, u32 sample_count, u32 read_offset, u32 read_count);
85
86 void InitializeI3dl2Reverb(I3dl2ReverbParams& info, I3dl2ReverbState& state,
87 std::vector<u8>& work_buffer);
88 void UpdateI3dl2Reverb(I3dl2ReverbParams& info, I3dl2ReverbState& state, bool should_clear);
89 // DSP Code
90 template <typename T>
91 s32 DecodePcm(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 sample_start_offset,
92 s32 sample_end_offset, s32 sample_count, s32 channel, std::size_t mix_offset);
93 s32 DecodeAdpcm(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 sample_start_offset,
94 s32 sample_end_offset, s32 sample_count, s32 channel, std::size_t mix_offset);
95 void DecodeFromWaveBuffers(ServerVoiceInfo& voice_info, std::span<s32> output,
96 VoiceState& dsp_state, s32 channel, s32 target_sample_rate,
97 s32 sample_count, s32 node_id);
98
99 AudioCommon::AudioRendererParameter& worker_params;
100 VoiceContext& voice_context;
101 MixContext& mix_context;
102 SplitterContext& splitter_context;
103 EffectContext& effect_context;
104 Core::Memory::Memory& memory;
105 std::vector<s32> mix_buffer{};
106 std::vector<s32> sample_buffer{};
107 std::vector<s32> depop_buffer{};
108 bool dumping_frame{false};
109};
110} // namespace AudioCore
diff --git a/src/audio_core/common.h b/src/audio_core/common.h
deleted file mode 100644
index 056a0ac70..000000000
--- a/src/audio_core/common.h
+++ /dev/null
@@ -1,132 +0,0 @@
1// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "common/common_funcs.h"
7#include "common/common_types.h"
8#include "common/swap.h"
9#include "core/hle/result.h"
10
11namespace AudioCommon {
12namespace Audren {
13constexpr Result ERR_INVALID_PARAMETERS{ErrorModule::Audio, 41};
14constexpr Result ERR_SPLITTER_SORT_FAILED{ErrorModule::Audio, 43};
15} // namespace Audren
16
17constexpr u8 BASE_REVISION = '0';
18constexpr u32_le CURRENT_PROCESS_REVISION =
19 Common::MakeMagic('R', 'E', 'V', static_cast<u8>(BASE_REVISION + 0xA));
20constexpr std::size_t MAX_MIX_BUFFERS = 24;
21constexpr std::size_t MAX_BIQUAD_FILTERS = 2;
22constexpr std::size_t MAX_CHANNEL_COUNT = 6;
23constexpr std::size_t MAX_WAVE_BUFFERS = 4;
24constexpr std::size_t MAX_SAMPLE_HISTORY = 4;
25constexpr u32 STREAM_SAMPLE_RATE = 48000;
26constexpr u32 STREAM_NUM_CHANNELS = 2;
27constexpr s32 NO_SPLITTER = -1;
28constexpr s32 NO_MIX = 0x7fffffff;
29constexpr s32 NO_FINAL_MIX = std::numeric_limits<s32>::min();
30constexpr s32 FINAL_MIX = 0;
31constexpr s32 NO_EFFECT_ORDER = -1;
32constexpr std::size_t TEMP_MIX_BASE_SIZE = 0x3f00; // TODO(ogniK): Work out this constant
33// Any size checks seem to take the sample history into account
34// and our const ends up being 0x3f04, the 4 bytes are most
35// likely the sample history
36constexpr std::size_t TOTAL_TEMP_MIX_SIZE = TEMP_MIX_BASE_SIZE + AudioCommon::MAX_SAMPLE_HISTORY;
37constexpr f32 I3DL2REVERB_MAX_LEVEL = 5000.0f;
38constexpr f32 I3DL2REVERB_MIN_REFLECTION_DURATION = 0.02f;
39constexpr std::size_t I3DL2REVERB_TAPS = 20;
40constexpr std::size_t I3DL2REVERB_DELAY_LINE_COUNT = 4;
41using Fractional = s32;
42
43template <typename T>
44constexpr Fractional ToFractional(T x) {
45 return static_cast<Fractional>(x * static_cast<T>(0x4000));
46}
47
48constexpr Fractional MultiplyFractional(Fractional lhs, Fractional rhs) {
49 return static_cast<Fractional>(static_cast<s64>(lhs) * rhs >> 14);
50}
51
52constexpr s32 FractionalToFixed(Fractional x) {
53 const auto s = x & (1 << 13);
54 return static_cast<s32>(x >> 14) + s;
55}
56
57constexpr s32 CalculateDelaySamples(s32 sample_rate_khz, float time) {
58 return FractionalToFixed(MultiplyFractional(ToFractional(sample_rate_khz), ToFractional(time)));
59}
60
61static constexpr u32 VersionFromRevision(u32_le rev) {
62 // "REV7" -> 7
63 return ((rev >> 24) & 0xff) - 0x30;
64}
65
66static constexpr bool IsRevisionSupported(u32 required, u32_le user_revision) {
67 const auto base = VersionFromRevision(user_revision);
68 return required <= base;
69}
70
71static constexpr bool IsValidRevision(u32_le revision) {
72 const auto base = VersionFromRevision(revision);
73 constexpr auto max_rev = VersionFromRevision(CURRENT_PROCESS_REVISION);
74 return base <= max_rev;
75}
76
77static constexpr bool CanConsumeBuffer(std::size_t size, std::size_t offset, std::size_t required) {
78 if (offset > size) {
79 return false;
80 }
81 if (size < required) {
82 return false;
83 }
84 if ((size - offset) < required) {
85 return false;
86 }
87 return true;
88}
89
90struct UpdateDataSizes {
91 u32_le behavior{};
92 u32_le memory_pool{};
93 u32_le voice{};
94 u32_le voice_channel_resource{};
95 u32_le effect{};
96 u32_le mixer{};
97 u32_le sink{};
98 u32_le performance{};
99 u32_le splitter{};
100 u32_le render_info{};
101 INSERT_PADDING_WORDS(4);
102};
103static_assert(sizeof(UpdateDataSizes) == 0x38, "UpdateDataSizes is an invalid size");
104
105struct UpdateDataHeader {
106 u32_le revision{};
107 UpdateDataSizes size{};
108 u32_le total_size{};
109};
110static_assert(sizeof(UpdateDataHeader) == 0x40, "UpdateDataHeader is an invalid size");
111
112struct AudioRendererParameter {
113 u32_le sample_rate;
114 u32_le sample_count;
115 u32_le mix_buffer_count;
116 u32_le submix_count;
117 u32_le voice_count;
118 u32_le sink_count;
119 u32_le effect_count;
120 u32_le performance_frame_count;
121 u8 is_voice_drop_enabled;
122 u8 unknown_21;
123 u8 unknown_22;
124 u8 execution_mode;
125 u32_le splitter_count;
126 u32_le num_splitter_send_channels;
127 u32_le unknown_30;
128 u32_le revision;
129};
130static_assert(sizeof(AudioRendererParameter) == 52, "AudioRendererParameter is an invalid size");
131
132} // namespace AudioCommon
diff --git a/src/audio_core/common/audio_renderer_parameter.h b/src/audio_core/common/audio_renderer_parameter.h
new file mode 100644
index 000000000..2f62c383b
--- /dev/null
+++ b/src/audio_core/common/audio_renderer_parameter.h
@@ -0,0 +1,60 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <span>
7
8#include "audio_core/renderer/behavior/behavior_info.h"
9#include "audio_core/renderer/memory/memory_pool_info.h"
10#include "audio_core/renderer/upsampler/upsampler_manager.h"
11#include "common/common_types.h"
12
13namespace AudioCore {
14/**
15 * Execution mode of the audio renderer.
16 * Only Auto is currently supported.
17 */
18enum class ExecutionMode : u8 {
19 Auto,
20 Manual,
21};
22
23/**
24 * Parameters from the game, passed to the audio renderer for initialisation.
25 */
26struct AudioRendererParameterInternal {
27 /* 0x00 */ u32 sample_rate;
28 /* 0x04 */ u32 sample_count;
29 /* 0x08 */ u32 mixes;
30 /* 0x0C */ u32 sub_mixes;
31 /* 0x10 */ u32 voices;
32 /* 0x14 */ u32 sinks;
33 /* 0x18 */ u32 effects;
34 /* 0x1C */ u32 perf_frames;
35 /* 0x20 */ u16 voice_drop_enabled;
36 /* 0x22 */ u8 rendering_device;
37 /* 0x23 */ ExecutionMode execution_mode;
38 /* 0x24 */ u32 splitter_infos;
39 /* 0x28 */ s32 splitter_destinations;
40 /* 0x2C */ u32 external_context_size;
41 /* 0x30 */ u32 revision;
42 /* 0x34 */ char unk34[0x4];
43};
44static_assert(sizeof(AudioRendererParameterInternal) == 0x38,
45 "AudioRendererParameterInternal has the wrong size!");
46
47/**
48 * Context for rendering, contains a bunch of useful fields for the command generator.
49 */
50struct AudioRendererSystemContext {
51 s32 session_id;
52 s8 channels;
53 s16 mix_buffer_count;
54 AudioRenderer::BehaviorInfo* behavior;
55 std::span<s32> depop_buffer;
56 AudioRenderer::UpsamplerManager* upsampler_manager;
57 AudioRenderer::MemoryPoolInfo* memory_pool_info;
58};
59
60} // namespace AudioCore
diff --git a/src/audio_core/common/common.h b/src/audio_core/common/common.h
new file mode 100644
index 000000000..6abd9be45
--- /dev/null
+++ b/src/audio_core/common/common.h
@@ -0,0 +1,138 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <numeric>
7#include <span>
8
9#include "common/assert.h"
10#include "common/common_funcs.h"
11#include "common/common_types.h"
12
13namespace AudioCore {
14using CpuAddr = std::uintptr_t;
15
16enum class PlayState : u8 {
17 Started,
18 Stopped,
19 Paused,
20};
21
22enum class SrcQuality : u8 {
23 Medium,
24 High,
25 Low,
26};
27
28enum class SampleFormat : u8 {
29 Invalid,
30 PcmInt8,
31 PcmInt16,
32 PcmInt24,
33 PcmInt32,
34 PcmFloat,
35 Adpcm,
36};
37
38enum class SessionTypes {
39 AudioIn,
40 AudioOut,
41 FinalOutputRecorder,
42};
43
44enum class Channels : u32 {
45 FrontLeft,
46 FrontRight,
47 Center,
48 LFE,
49 BackLeft,
50 BackRight,
51};
52
53// These are used by Delay, Reverb and I3dl2Reverb prior to Revision 11.
54enum class OldChannels : u32 {
55 FrontLeft,
56 FrontRight,
57 BackLeft,
58 BackRight,
59 Center,
60 LFE,
61};
62
63constexpr u32 BufferCount = 32;
64
65constexpr u32 MaxRendererSessions = 2;
66constexpr u32 TargetSampleCount = 240;
67constexpr u32 TargetSampleRate = 48'000;
68constexpr u32 MaxChannels = 6;
69constexpr u32 MaxMixBuffers = 24;
70constexpr u32 MaxWaveBuffers = 4;
71constexpr s32 LowestVoicePriority = 0xFF;
72constexpr s32 HighestVoicePriority = 0;
73constexpr u32 BufferAlignment = 0x40;
74constexpr u32 WorkbufferAlignment = 0x1000;
75constexpr s32 FinalMixId = 0;
76constexpr s32 InvalidDistanceFromFinalMix = std::numeric_limits<s32>::min();
77constexpr s32 UnusedSplitterId = -1;
78constexpr s32 UnusedMixId = std::numeric_limits<s32>::max();
79constexpr u32 InvalidNodeId = 0xF0000000;
80constexpr s32 InvalidProcessOrder = -1;
81constexpr u32 MaxBiquadFilters = 2;
82constexpr u32 MaxEffects = 256;
83
84constexpr bool IsChannelCountValid(u16 channel_count) {
85 return channel_count <= 6 &&
86 (channel_count == 1 || channel_count == 2 || channel_count == 4 || channel_count == 6);
87}
88
89constexpr void UseOldChannelMapping(std::span<s16> inputs, std::span<s16> outputs) {
90 constexpr auto old_center{static_cast<u32>(OldChannels::Center)};
91 constexpr auto new_center{static_cast<u32>(Channels::Center)};
92 constexpr auto old_lfe{static_cast<u32>(OldChannels::LFE)};
93 constexpr auto new_lfe{static_cast<u32>(Channels::LFE)};
94
95 auto center{inputs[old_center]};
96 auto lfe{inputs[old_lfe]};
97 inputs[old_center] = inputs[new_center];
98 inputs[old_lfe] = inputs[new_lfe];
99 inputs[new_center] = center;
100 inputs[new_lfe] = lfe;
101
102 center = outputs[old_center];
103 lfe = outputs[old_lfe];
104 outputs[old_center] = outputs[new_center];
105 outputs[old_lfe] = outputs[new_lfe];
106 outputs[new_center] = center;
107 outputs[new_lfe] = lfe;
108}
109
110constexpr u32 GetSplitterInParamHeaderMagic() {
111 return Common::MakeMagic('S', 'N', 'D', 'H');
112}
113
114constexpr u32 GetSplitterInfoMagic() {
115 return Common::MakeMagic('S', 'N', 'D', 'I');
116}
117
118constexpr u32 GetSplitterSendDataMagic() {
119 return Common::MakeMagic('S', 'N', 'D', 'D');
120}
121
122constexpr size_t GetSampleFormatByteSize(SampleFormat format) {
123 switch (format) {
124 case SampleFormat::PcmInt8:
125 return 1;
126 case SampleFormat::PcmInt16:
127 return 2;
128 case SampleFormat::PcmInt24:
129 return 3;
130 case SampleFormat::PcmInt32:
131 case SampleFormat::PcmFloat:
132 return 4;
133 default:
134 return 2;
135 }
136}
137
138} // namespace AudioCore
diff --git a/src/audio_core/common/feature_support.h b/src/audio_core/common/feature_support.h
new file mode 100644
index 000000000..55c9e690d
--- /dev/null
+++ b/src/audio_core/common/feature_support.h
@@ -0,0 +1,105 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <map>
7#include <ranges>
8#include <tuple>
9
10#include "common/assert.h"
11#include "common/common_funcs.h"
12#include "common/common_types.h"
13
14namespace AudioCore {
15constexpr u32 CurrentRevision = 11;
16
17enum class SupportTags {
18 CommandProcessingTimeEstimatorVersion4,
19 CommandProcessingTimeEstimatorVersion3,
20 CommandProcessingTimeEstimatorVersion2,
21 MultiTapBiquadFilterProcessing,
22 EffectInfoVer2,
23 WaveBufferVer2,
24 BiquadFilterFloatProcessing,
25 VolumeMixParameterPrecisionQ23,
26 MixInParameterDirtyOnlyUpdate,
27 BiquadFilterEffectStateClearBugFix,
28 VoicePlayedSampleCountResetAtLoopPoint,
29 VoicePitchAndSrcSkipped,
30 SplitterBugFix,
31 FlushVoiceWaveBuffers,
32 ElapsedFrameCount,
33 AudioRendererVariadicCommandBufferSize,
34 PerformanceMetricsDataFormatVersion2,
35 AudioRendererProcessingTimeLimit80Percent,
36 AudioRendererProcessingTimeLimit75Percent,
37 AudioRendererProcessingTimeLimit70Percent,
38 AdpcmLoopContextBugFix,
39 Splitter,
40 LongSizePreDelay,
41 AudioUsbDeviceOutput,
42 DeviceApiVersion2,
43 DelayChannelMappingChange,
44 ReverbChannelMappingChange,
45 I3dl2ReverbChannelMappingChange,
46
47 // Not a real tag, just here to get the count.
48 Size
49};
50
51constexpr u32 GetRevisionNum(u32 user_revision) {
52 if (user_revision >= 0x100) {
53 user_revision -= Common::MakeMagic('R', 'E', 'V', '0');
54 user_revision >>= 24;
55 }
56 return user_revision;
57};
58
59constexpr bool CheckFeatureSupported(SupportTags tag, u32 user_revision) {
60 constexpr std::array<std::pair<SupportTags, u32>, static_cast<u32>(SupportTags::Size)> features{
61 {
62 {SupportTags::AudioRendererProcessingTimeLimit70Percent, 1},
63 {SupportTags::Splitter, 2},
64 {SupportTags::AdpcmLoopContextBugFix, 2},
65 {SupportTags::LongSizePreDelay, 3},
66 {SupportTags::AudioUsbDeviceOutput, 4},
67 {SupportTags::AudioRendererProcessingTimeLimit75Percent, 4},
68 {SupportTags::VoicePlayedSampleCountResetAtLoopPoint, 5},
69 {SupportTags::VoicePitchAndSrcSkipped, 5},
70 {SupportTags::SplitterBugFix, 5},
71 {SupportTags::FlushVoiceWaveBuffers, 5},
72 {SupportTags::ElapsedFrameCount, 5},
73 {SupportTags::AudioRendererProcessingTimeLimit80Percent, 5},
74 {SupportTags::AudioRendererVariadicCommandBufferSize, 5},
75 {SupportTags::PerformanceMetricsDataFormatVersion2, 5},
76 {SupportTags::CommandProcessingTimeEstimatorVersion2, 5},
77 {SupportTags::BiquadFilterEffectStateClearBugFix, 6},
78 {SupportTags::BiquadFilterFloatProcessing, 7},
79 {SupportTags::VolumeMixParameterPrecisionQ23, 7},
80 {SupportTags::MixInParameterDirtyOnlyUpdate, 7},
81 {SupportTags::WaveBufferVer2, 8},
82 {SupportTags::CommandProcessingTimeEstimatorVersion3, 8},
83 {SupportTags::EffectInfoVer2, 9},
84 {SupportTags::CommandProcessingTimeEstimatorVersion4, 10},
85 {SupportTags::MultiTapBiquadFilterProcessing, 10},
86 {SupportTags::DelayChannelMappingChange, 11},
87 {SupportTags::ReverbChannelMappingChange, 11},
88 {SupportTags::I3dl2ReverbChannelMappingChange, 11},
89 }};
90
91 const auto& feature =
92 std::ranges::find_if(features, [tag](const auto& entry) { return entry.first == tag; });
93 if (feature == features.cend()) {
94 LOG_ERROR(Service_Audio, "Invalid SupportTag {}!", static_cast<u32>(tag));
95 return false;
96 }
97 user_revision = GetRevisionNum(user_revision);
98 return (*feature).second <= user_revision;
99}
100
101constexpr bool CheckValidRevision(u32 user_revision) {
102 return GetRevisionNum(user_revision) <= CurrentRevision;
103};
104
105} // namespace AudioCore
diff --git a/src/audio_core/common/wave_buffer.h b/src/audio_core/common/wave_buffer.h
new file mode 100644
index 000000000..fc478ef79
--- /dev/null
+++ b/src/audio_core/common/wave_buffer.h
@@ -0,0 +1,35 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "common/common_types.h"
7
8namespace AudioCore {
9
10struct WaveBufferVersion1 {
11 CpuAddr buffer;
12 u64 buffer_size;
13 u32 start_offset;
14 u32 end_offset;
15 bool loop;
16 bool stream_ended;
17 CpuAddr context;
18 u64 context_size;
19};
20
21struct WaveBufferVersion2 {
22 CpuAddr buffer;
23 CpuAddr context;
24 u64 buffer_size;
25 u64 context_size;
26 u32 start_offset;
27 u32 end_offset;
28 u32 loop_start_offset;
29 u32 loop_end_offset;
30 s32 loop_count;
31 bool loop;
32 bool stream_ended;
33};
34
35} // namespace AudioCore
diff --git a/src/audio_core/common/workbuffer_allocator.h b/src/audio_core/common/workbuffer_allocator.h
new file mode 100644
index 000000000..fb89f97fe
--- /dev/null
+++ b/src/audio_core/common/workbuffer_allocator.h
@@ -0,0 +1,100 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <span>
7
8#include "common/alignment.h"
9#include "common/assert.h"
10#include "common/common_types.h"
11
12namespace AudioCore {
13/**
14 * Responsible for allocating up a workbuffer into multiple pieces.
15 * Takes in a buffer and size (it does not own them), and allocates up the buffer via Allocate.
16 */
17class WorkbufferAllocator {
18public:
19 explicit WorkbufferAllocator(std::span<u8> buffer_, u64 size_)
20 : buffer{reinterpret_cast<u64>(buffer_.data())}, size{size_} {}
21
22 /**
23 * Allocate the given count of T elements, aligned to alignment.
24 *
25 * @param count - The number of elements to allocate.
26 * @param alignment - The required starting alignment.
27 * @return Non-owning container of allocated elements.
28 */
29 template <typename T>
30 std::span<T> Allocate(u64 count, u64 alignment) {
31 u64 out{0};
32 u64 byte_size{count * sizeof(T)};
33
34 if (byte_size > 0) {
35 auto current{buffer + offset};
36 auto aligned_buffer{Common::AlignUp(current, alignment)};
37 if (aligned_buffer + byte_size <= buffer + size) {
38 out = aligned_buffer;
39 offset = byte_size - buffer + aligned_buffer;
40 } else {
41 LOG_ERROR(
42 Service_Audio,
43 "Allocated buffer was too small to hold new alloc.\nAllocator size={:08X}, "
44 "offset={:08X}.\nAttempting to allocate {:08X} with alignment={:02X}",
45 size, offset, byte_size, alignment);
46 count = 0;
47 }
48 }
49
50 return std::span<T>(reinterpret_cast<T*>(out), count);
51 }
52
53 /**
54 * Align the current offset to the given alignment.
55 *
56 * @param alignment - The required starting alignment.
57 */
58 void Align(u64 alignment) {
59 auto current{buffer + offset};
60 auto aligned_buffer{Common::AlignUp(current, alignment)};
61 offset = 0 - buffer + aligned_buffer;
62 }
63
64 /**
65 * Get the current buffer offset.
66 *
67 * @return The current allocating offset.
68 */
69 u64 GetCurrentOffset() const {
70 return offset;
71 }
72
73 /**
74 * Get the current buffer size.
75 *
76 * @return The size of the current buffer.
77 */
78 u64 GetSize() const {
79 return size;
80 }
81
82 /**
83 * Get the remaining size that can be allocated.
84 *
85 * @return The remaining size left in the buffer.
86 */
87 u64 GetRemainingSize() const {
88 return size - offset;
89 }
90
91private:
92 /// The buffer into which we are allocating.
93 u64 buffer;
94 /// Size of the buffer we're allocating to.
95 u64 size;
96 /// Current offset into the buffer, an error will be thrown if it exceeds size.
97 u64 offset{};
98};
99
100} // namespace AudioCore
diff --git a/src/audio_core/cubeb_sink.cpp b/src/audio_core/cubeb_sink.cpp
deleted file mode 100644
index e48c1ee8e..000000000
--- a/src/audio_core/cubeb_sink.cpp
+++ /dev/null
@@ -1,249 +0,0 @@
1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <algorithm>
5#include <atomic>
6#include <cstring>
7#include "audio_core/cubeb_sink.h"
8#include "audio_core/stream.h"
9#include "common/assert.h"
10#include "common/logging/log.h"
11#include "common/ring_buffer.h"
12#include "common/settings.h"
13
14#ifdef _WIN32
15#include <objbase.h>
16#endif
17
18namespace AudioCore {
19
20class CubebSinkStream final : public SinkStream {
21public:
22 CubebSinkStream(cubeb* ctx_, u32 sample_rate, u32 num_channels_, cubeb_devid output_device,
23 const std::string& name)
24 : ctx{ctx_}, num_channels{std::min(num_channels_, 6u)} {
25
26 cubeb_stream_params params{};
27 params.rate = sample_rate;
28 params.channels = num_channels;
29 params.format = CUBEB_SAMPLE_S16NE;
30 params.prefs = CUBEB_STREAM_PREF_PERSIST;
31 switch (num_channels) {
32 case 1:
33 params.layout = CUBEB_LAYOUT_MONO;
34 break;
35 case 2:
36 params.layout = CUBEB_LAYOUT_STEREO;
37 break;
38 case 6:
39 params.layout = CUBEB_LAYOUT_3F2_LFE;
40 break;
41 }
42
43 u32 minimum_latency{};
44 if (cubeb_get_min_latency(ctx, &params, &minimum_latency) != CUBEB_OK) {
45 LOG_CRITICAL(Audio_Sink, "Error getting minimum latency");
46 }
47
48 if (cubeb_stream_init(ctx, &stream_backend, name.c_str(), nullptr, nullptr, output_device,
49 &params, std::max(512u, minimum_latency),
50 &CubebSinkStream::DataCallback, &CubebSinkStream::StateCallback,
51 this) != CUBEB_OK) {
52 LOG_CRITICAL(Audio_Sink, "Error initializing cubeb stream");
53 return;
54 }
55
56 if (cubeb_stream_start(stream_backend) != CUBEB_OK) {
57 LOG_CRITICAL(Audio_Sink, "Error starting cubeb stream");
58 return;
59 }
60 }
61
62 ~CubebSinkStream() override {
63 if (!ctx) {
64 return;
65 }
66
67 if (cubeb_stream_stop(stream_backend) != CUBEB_OK) {
68 LOG_CRITICAL(Audio_Sink, "Error stopping cubeb stream");
69 }
70
71 cubeb_stream_destroy(stream_backend);
72 }
73
74 void EnqueueSamples(u32 source_num_channels, const std::vector<s16>& samples) override {
75 if (source_num_channels > num_channels) {
76 // Downsample 6 channels to 2
77 ASSERT_MSG(source_num_channels == 6, "Channel count must be 6");
78
79 std::vector<s16> buf;
80 buf.reserve(samples.size() * num_channels / source_num_channels);
81 for (std::size_t i = 0; i < samples.size(); i += source_num_channels) {
82 // Downmixing implementation taken from the ATSC standard
83 const s16 left{samples[i + 0]};
84 const s16 right{samples[i + 1]};
85 const s16 center{samples[i + 2]};
86 const s16 surround_left{samples[i + 4]};
87 const s16 surround_right{samples[i + 5]};
88 // Not used in the ATSC reference implementation
89 [[maybe_unused]] const s16 low_frequency_effects{samples[i + 3]};
90
91 constexpr s32 clev{707}; // center mixing level coefficient
92 constexpr s32 slev{707}; // surround mixing level coefficient
93
94 buf.push_back(static_cast<s16>(left + (clev * center / 1000) +
95 (slev * surround_left / 1000)));
96 buf.push_back(static_cast<s16>(right + (clev * center / 1000) +
97 (slev * surround_right / 1000)));
98 }
99 queue.Push(buf);
100 return;
101 }
102
103 queue.Push(samples);
104 }
105
106 std::size_t SamplesInQueue(u32 channel_count) const override {
107 if (!ctx)
108 return 0;
109
110 return queue.Size() / channel_count;
111 }
112
113 void Flush() override {
114 should_flush = true;
115 }
116
117 u32 GetNumChannels() const {
118 return num_channels;
119 }
120
121private:
122 std::vector<std::string> device_list;
123
124 cubeb* ctx{};
125 cubeb_stream* stream_backend{};
126 u32 num_channels{};
127
128 Common::RingBuffer<s16, 0x10000> queue;
129 std::array<s16, 2> last_frame{};
130 std::atomic<bool> should_flush{};
131
132 static long DataCallback(cubeb_stream* stream, void* user_data, const void* input_buffer,
133 void* output_buffer, long num_frames);
134 static void StateCallback(cubeb_stream* stream, void* user_data, cubeb_state state);
135};
136
137CubebSink::CubebSink(std::string_view target_device_name) {
138 // Cubeb requires COM to be initialized on the thread calling cubeb_init on Windows
139#ifdef _WIN32
140 com_init_result = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
141#endif
142
143 if (cubeb_init(&ctx, "yuzu", nullptr) != CUBEB_OK) {
144 LOG_CRITICAL(Audio_Sink, "cubeb_init failed");
145 return;
146 }
147
148 if (target_device_name != auto_device_name && !target_device_name.empty()) {
149 cubeb_device_collection collection;
150 if (cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_OUTPUT, &collection) != CUBEB_OK) {
151 LOG_WARNING(Audio_Sink, "Audio output device enumeration not supported");
152 } else {
153 const auto collection_end{collection.device + collection.count};
154 const auto device{
155 std::find_if(collection.device, collection_end, [&](const cubeb_device_info& info) {
156 return info.friendly_name != nullptr &&
157 target_device_name == info.friendly_name;
158 })};
159 if (device != collection_end) {
160 output_device = device->devid;
161 }
162 cubeb_device_collection_destroy(ctx, &collection);
163 }
164 }
165}
166
167CubebSink::~CubebSink() {
168 if (!ctx) {
169 return;
170 }
171
172 for (auto& sink_stream : sink_streams) {
173 sink_stream.reset();
174 }
175
176 cubeb_destroy(ctx);
177
178#ifdef _WIN32
179 if (SUCCEEDED(com_init_result)) {
180 CoUninitialize();
181 }
182#endif
183}
184
185SinkStream& CubebSink::AcquireSinkStream(u32 sample_rate, u32 num_channels,
186 const std::string& name) {
187 sink_streams.push_back(
188 std::make_unique<CubebSinkStream>(ctx, sample_rate, num_channels, output_device, name));
189 return *sink_streams.back();
190}
191
192long CubebSinkStream::DataCallback([[maybe_unused]] cubeb_stream* stream, void* user_data,
193 [[maybe_unused]] const void* input_buffer, void* output_buffer,
194 long num_frames) {
195 auto* impl = static_cast<CubebSinkStream*>(user_data);
196 auto* buffer = static_cast<u8*>(output_buffer);
197
198 if (!impl) {
199 return {};
200 }
201
202 const std::size_t num_channels = impl->GetNumChannels();
203 const std::size_t samples_to_write = num_channels * num_frames;
204 const std::size_t samples_written = impl->queue.Pop(buffer, samples_to_write);
205
206 if (samples_written >= num_channels) {
207 std::memcpy(&impl->last_frame[0], buffer + (samples_written - num_channels) * sizeof(s16),
208 num_channels * sizeof(s16));
209 }
210
211 // Fill the rest of the frames with last_frame
212 for (std::size_t i = samples_written; i < samples_to_write; i += num_channels) {
213 std::memcpy(buffer + i * sizeof(s16), &impl->last_frame[0], num_channels * sizeof(s16));
214 }
215
216 return num_frames;
217}
218
219void CubebSinkStream::StateCallback([[maybe_unused]] cubeb_stream* stream,
220 [[maybe_unused]] void* user_data,
221 [[maybe_unused]] cubeb_state state) {}
222
223std::vector<std::string> ListCubebSinkDevices() {
224 std::vector<std::string> device_list;
225 cubeb* ctx;
226
227 if (cubeb_init(&ctx, "yuzu Device Enumerator", nullptr) != CUBEB_OK) {
228 LOG_CRITICAL(Audio_Sink, "cubeb_init failed");
229 return {};
230 }
231
232 cubeb_device_collection collection;
233 if (cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_OUTPUT, &collection) != CUBEB_OK) {
234 LOG_WARNING(Audio_Sink, "Audio output device enumeration not supported");
235 } else {
236 for (std::size_t i = 0; i < collection.count; i++) {
237 const cubeb_device_info& device = collection.device[i];
238 if (device.friendly_name) {
239 device_list.emplace_back(device.friendly_name);
240 }
241 }
242 cubeb_device_collection_destroy(ctx, &collection);
243 }
244
245 cubeb_destroy(ctx);
246 return device_list;
247}
248
249} // namespace AudioCore
diff --git a/src/audio_core/cubeb_sink.h b/src/audio_core/cubeb_sink.h
deleted file mode 100644
index c124b7ee8..000000000
--- a/src/audio_core/cubeb_sink.h
+++ /dev/null
@@ -1,35 +0,0 @@
1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <string>
7#include <vector>
8
9#include <cubeb/cubeb.h>
10
11#include "audio_core/sink.h"
12
13namespace AudioCore {
14
15class CubebSink final : public Sink {
16public:
17 explicit CubebSink(std::string_view device_id);
18 ~CubebSink() override;
19
20 SinkStream& AcquireSinkStream(u32 sample_rate, u32 num_channels,
21 const std::string& name) override;
22
23private:
24 cubeb* ctx{};
25 cubeb_devid output_device{};
26 std::vector<SinkStreamPtr> sink_streams;
27
28#ifdef _WIN32
29 u32 com_init_result = 0;
30#endif
31};
32
33std::vector<std::string> ListCubebSinkDevices();
34
35} // namespace AudioCore
diff --git a/src/audio_core/delay_line.cpp b/src/audio_core/delay_line.cpp
deleted file mode 100644
index b1626a71b..000000000
--- a/src/audio_core/delay_line.cpp
+++ /dev/null
@@ -1,107 +0,0 @@
1// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <cstring>
5#include "audio_core/delay_line.h"
6
7namespace AudioCore {
8DelayLineBase::DelayLineBase() = default;
9DelayLineBase::~DelayLineBase() = default;
10
11void DelayLineBase::Initialize(s32 max_delay_, float* src_buffer) {
12 buffer = src_buffer;
13 buffer_end = buffer + max_delay_;
14 max_delay = max_delay_;
15 output = buffer;
16 SetDelay(max_delay_);
17 Clear();
18}
19
20void DelayLineBase::SetDelay(s32 new_delay) {
21 if (max_delay < new_delay) {
22 return;
23 }
24 delay = new_delay;
25 input = (buffer + ((output - buffer) + new_delay) % (max_delay + 1));
26}
27
28s32 DelayLineBase::GetDelay() const {
29 return delay;
30}
31
32s32 DelayLineBase::GetMaxDelay() const {
33 return max_delay;
34}
35
36f32 DelayLineBase::TapOut(s32 last_sample) {
37 const float* ptr = input - (last_sample + 1);
38 if (ptr < buffer) {
39 ptr += (max_delay + 1);
40 }
41
42 return *ptr;
43}
44
45f32 DelayLineBase::Tick(f32 sample) {
46 *(input++) = sample;
47 const auto out_sample = *(output++);
48
49 if (buffer_end < input) {
50 input = buffer;
51 }
52
53 if (buffer_end < output) {
54 output = buffer;
55 }
56
57 return out_sample;
58}
59
60float* DelayLineBase::GetInput() {
61 return input;
62}
63
64const float* DelayLineBase::GetInput() const {
65 return input;
66}
67
68f32 DelayLineBase::GetOutputSample() const {
69 return *output;
70}
71
72void DelayLineBase::Clear() {
73 std::memset(buffer, 0, sizeof(float) * max_delay);
74}
75
76void DelayLineBase::Reset() {
77 buffer = nullptr;
78 buffer_end = nullptr;
79 max_delay = 0;
80 input = nullptr;
81 output = nullptr;
82 delay = 0;
83}
84
85DelayLineAllPass::DelayLineAllPass() = default;
86DelayLineAllPass::~DelayLineAllPass() = default;
87
88void DelayLineAllPass::Initialize(u32 delay_, float coeffcient_, f32* src_buffer) {
89 DelayLineBase::Initialize(delay_, src_buffer);
90 SetCoefficient(coeffcient_);
91}
92
93void DelayLineAllPass::SetCoefficient(float coeffcient_) {
94 coefficient = coeffcient_;
95}
96
97f32 DelayLineAllPass::Tick(f32 sample) {
98 const auto temp = sample - coefficient * *output;
99 return coefficient * temp + DelayLineBase::Tick(temp);
100}
101
102void DelayLineAllPass::Reset() {
103 coefficient = 0.0f;
104 DelayLineBase::Reset();
105}
106
107} // namespace AudioCore
diff --git a/src/audio_core/delay_line.h b/src/audio_core/delay_line.h
deleted file mode 100644
index 05fda536f..000000000
--- a/src/audio_core/delay_line.h
+++ /dev/null
@@ -1,49 +0,0 @@
1// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "common/common_types.h"
7
8namespace AudioCore {
9
10class DelayLineBase {
11public:
12 DelayLineBase();
13 ~DelayLineBase();
14
15 void Initialize(s32 max_delay_, float* src_buffer);
16 void SetDelay(s32 new_delay);
17 s32 GetDelay() const;
18 s32 GetMaxDelay() const;
19 f32 TapOut(s32 last_sample);
20 f32 Tick(f32 sample);
21 float* GetInput();
22 const float* GetInput() const;
23 f32 GetOutputSample() const;
24 void Clear();
25 void Reset();
26
27protected:
28 float* buffer{nullptr};
29 float* buffer_end{nullptr};
30 s32 max_delay{};
31 float* input{nullptr};
32 float* output{nullptr};
33 s32 delay{};
34};
35
36class DelayLineAllPass final : public DelayLineBase {
37public:
38 DelayLineAllPass();
39 ~DelayLineAllPass();
40
41 void Initialize(u32 delay, float coeffcient_, f32* src_buffer);
42 void SetCoefficient(float coeffcient_);
43 f32 Tick(f32 sample);
44 void Reset();
45
46private:
47 float coefficient{};
48};
49} // namespace AudioCore
diff --git a/src/audio_core/device/audio_buffer.h b/src/audio_core/device/audio_buffer.h
new file mode 100644
index 000000000..cae7fa970
--- /dev/null
+++ b/src/audio_core/device/audio_buffer.h
@@ -0,0 +1,21 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "common/common_types.h"
7
8namespace AudioCore {
9
10struct AudioBuffer {
11 /// Timestamp this buffer completed playing.
12 s64 played_timestamp;
13 /// Game memory address for these samples.
14 VAddr samples;
15 /// Unqiue identifier for this buffer.
16 u64 tag;
17 /// Size of the samples buffer.
18 u64 size;
19};
20
21} // namespace AudioCore
diff --git a/src/audio_core/device/audio_buffers.h b/src/audio_core/device/audio_buffers.h
new file mode 100644
index 000000000..5d1979ea0
--- /dev/null
+++ b/src/audio_core/device/audio_buffers.h
@@ -0,0 +1,304 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7#include <mutex>
8#include <span>
9#include <vector>
10
11#include "audio_buffer.h"
12#include "audio_core/device/device_session.h"
13#include "core/core_timing.h"
14
15namespace AudioCore {
16
17constexpr s32 BufferAppendLimit = 4;
18
19/**
20 * A ringbuffer of N audio buffers.
21 * The buffer contains 3 sections:
22 * Appended - Buffers added to the ring, but have yet to be sent to the audio backend.
23 * Registered - Buffers sent to the backend and queued for playback.
24 * Released - Buffers which have been played, and can now be recycled.
25 * Any others are free/untracked.
26 *
27 * @tparam N - Maximum number of buffers in the ring.
28 */
29template <size_t N>
30class AudioBuffers {
31public:
32 explicit AudioBuffers(size_t limit) : append_limit{static_cast<u32>(limit)} {}
33
34 /**
35 * Append a new audio buffer to the ring.
36 *
37 * @param buffer - The new buffer.
38 */
39 void AppendBuffer(AudioBuffer& buffer) {
40 std::scoped_lock l{lock};
41 buffers[appended_index] = buffer;
42 appended_count++;
43 appended_index = (appended_index + 1) % append_limit;
44 }
45
46 /**
47 * Register waiting buffers, up to a maximum of BufferAppendLimit.
48 *
49 * @param out_buffers - The buffers which were registered.
50 */
51 void RegisterBuffers(std::vector<AudioBuffer>& out_buffers) {
52 std::scoped_lock l{lock};
53 const s32 to_register{std::min(std::min(appended_count, BufferAppendLimit),
54 BufferAppendLimit - registered_count)};
55
56 for (s32 i = 0; i < to_register; i++) {
57 s32 index{appended_index - appended_count};
58 if (index < 0) {
59 index += N;
60 }
61 out_buffers.push_back(buffers[index]);
62 registered_count++;
63 registered_index = (registered_index + 1) % append_limit;
64
65 appended_count--;
66 if (appended_count == 0) {
67 break;
68 }
69 }
70 }
71
72 /**
73 * Release a single buffer. Must be already registered.
74 *
75 * @param index - The buffer index to release.
76 * @param timestamp - The released timestamp for this buffer.
77 */
78 void ReleaseBuffer(s32 index, s64 timestamp) {
79 std::scoped_lock l{lock};
80 buffers[index].played_timestamp = timestamp;
81
82 registered_count--;
83 released_count++;
84 released_index = (released_index + 1) % append_limit;
85 }
86
87 /**
88 * Release all registered buffers.
89 *
90 * @param timestamp - The released timestamp for this buffer.
91 * @return Is the buffer was released.
92 */
93 bool ReleaseBuffers(Core::Timing::CoreTiming& core_timing, DeviceSession& session) {
94 std::scoped_lock l{lock};
95 bool buffer_released{false};
96 while (registered_count > 0) {
97 auto index{registered_index - registered_count};
98 if (index < 0) {
99 index += N;
100 }
101
102 // Check with the backend if this buffer can be released yet.
103 if (!session.IsBufferConsumed(buffers[index].tag)) {
104 break;
105 }
106
107 ReleaseBuffer(index, core_timing.GetGlobalTimeNs().count());
108 buffer_released = true;
109 }
110
111 return buffer_released || registered_count == 0;
112 }
113
114 /**
115 * Get all released buffers.
116 *
117 * @param tags - Container to be filled with the released buffers' tags.
118 * @return The number of buffers released.
119 */
120 u32 GetReleasedBuffers(std::span<u64> tags) {
121 std::scoped_lock l{lock};
122 u32 released{0};
123
124 while (released_count > 0) {
125 auto index{released_index - released_count};
126 if (index < 0) {
127 index += N;
128 }
129
130 auto& buffer{buffers[index]};
131 released_count--;
132
133 auto tag{buffer.tag};
134 buffer.played_timestamp = 0;
135 buffer.samples = 0;
136 buffer.tag = 0;
137 buffer.size = 0;
138
139 if (tag == 0) {
140 break;
141 }
142
143 tags[released++] = tag;
144
145 if (released >= tags.size()) {
146 break;
147 }
148 }
149
150 return released;
151 }
152
153 /**
154 * Get all appended and registered buffers.
155 *
156 * @param buffers_flushed - Output vector for the buffers which are released.
157 * @param max_buffers - Maximum number of buffers to released.
158 * @return The number of buffers released.
159 */
160 u32 GetRegisteredAppendedBuffers(std::vector<AudioBuffer>& buffers_flushed, u32 max_buffers) {
161 std::scoped_lock l{lock};
162 if (registered_count + appended_count == 0) {
163 return 0;
164 }
165
166 size_t buffers_to_flush{
167 std::min(static_cast<u32>(registered_count + appended_count), max_buffers)};
168 if (buffers_to_flush == 0) {
169 return 0;
170 }
171
172 while (registered_count > 0) {
173 auto index{registered_index - registered_count};
174 if (index < 0) {
175 index += N;
176 }
177
178 buffers_flushed.push_back(buffers[index]);
179
180 registered_count--;
181 released_count++;
182 released_index = (released_index + 1) % append_limit;
183
184 if (buffers_flushed.size() >= buffers_to_flush) {
185 break;
186 }
187 }
188
189 while (appended_count > 0) {
190 auto index{appended_index - appended_count};
191 if (index < 0) {
192 index += N;
193 }
194
195 buffers_flushed.push_back(buffers[index]);
196
197 appended_count--;
198 released_count++;
199 released_index = (released_index + 1) % append_limit;
200
201 if (buffers_flushed.size() >= buffers_to_flush) {
202 break;
203 }
204 }
205
206 return static_cast<u32>(buffers_flushed.size());
207 }
208
209 /**
210 * Check if the given tag is in the buffers.
211 *
212 * @param tag - Unique tag of the buffer to search for.
213 * @return True if the buffer is still in the ring, otherwise false.
214 */
215 bool ContainsBuffer(const u64 tag) const {
216 std::scoped_lock l{lock};
217 const auto registered_buffers{appended_count + registered_count + released_count};
218
219 if (registered_buffers == 0) {
220 return false;
221 }
222
223 auto index{released_index - released_count};
224 if (index < 0) {
225 index += append_limit;
226 }
227
228 for (s32 i = 0; i < registered_buffers; i++) {
229 if (buffers[index].tag == tag) {
230 return true;
231 }
232 index = (index + 1) % append_limit;
233 }
234
235 return false;
236 }
237
238 /**
239 * Get the number of active buffers in the ring.
240 * That is, appended, registered and released buffers.
241 *
242 * @return Number of active buffers.
243 */
244 u32 GetAppendedRegisteredCount() const {
245 std::scoped_lock l{lock};
246 return appended_count + registered_count;
247 }
248
249 /**
250 * Get the total number of active buffers in the ring.
251 * That is, appended, registered and released buffers.
252 *
253 * @return Number of active buffers.
254 */
255 u32 GetTotalBufferCount() const {
256 std::scoped_lock l{lock};
257 return static_cast<u32>(appended_count + registered_count + released_count);
258 }
259
260 /**
261 * Flush all of the currently appended and registered buffers
262 *
263 * @param buffers_released - Output count for the number of buffers released.
264 * @return True if buffers were successfully flushed, otherwise false.
265 */
266 bool FlushBuffers(u32& buffers_released) {
267 std::scoped_lock l{lock};
268 std::vector<AudioBuffer> buffers_flushed{};
269
270 buffers_released = GetRegisteredAppendedBuffers(buffers_flushed, append_limit);
271
272 if (registered_count > 0) {
273 return false;
274 }
275
276 if (static_cast<u32>(released_count + appended_count) > append_limit) {
277 return false;
278 }
279
280 return true;
281 }
282
283private:
284 /// Buffer lock
285 mutable std::recursive_mutex lock{};
286 /// The audio buffers
287 std::array<AudioBuffer, N> buffers{};
288 /// Current released index
289 s32 released_index{};
290 /// Number of released buffers
291 s32 released_count{};
292 /// Current registered index
293 s32 registered_index{};
294 /// Number of registered buffers
295 s32 registered_count{};
296 /// Current appended index
297 s32 appended_index{};
298 /// Number of appended buffers
299 s32 appended_count{};
300 /// Maximum number of buffers (default 32)
301 u32 append_limit{};
302};
303
304} // namespace AudioCore
diff --git a/src/audio_core/device/device_session.cpp b/src/audio_core/device/device_session.cpp
new file mode 100644
index 000000000..095fc96ce
--- /dev/null
+++ b/src/audio_core/device/device_session.cpp
@@ -0,0 +1,114 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/audio_core.h"
5#include "audio_core/audio_manager.h"
6#include "audio_core/device/audio_buffer.h"
7#include "audio_core/device/device_session.h"
8#include "audio_core/sink/sink_stream.h"
9#include "core/core.h"
10#include "core/memory.h"
11
12namespace AudioCore {
13
14DeviceSession::DeviceSession(Core::System& system_) : system{system_} {}
15
16DeviceSession::~DeviceSession() {
17 Finalize();
18}
19
20Result DeviceSession::Initialize(std::string_view name_, SampleFormat sample_format_,
21 u16 channel_count_, size_t session_id_, u32 handle_,
22 u64 applet_resource_user_id_, Sink::StreamType type_) {
23 if (stream) {
24 Finalize();
25 }
26 name = fmt::format("{}-{}", name_, session_id_);
27 type = type_;
28 sample_format = sample_format_;
29 channel_count = channel_count_;
30 session_id = session_id_;
31 handle = handle_;
32 applet_resource_user_id = applet_resource_user_id_;
33
34 if (type == Sink::StreamType::In) {
35 sink = &system.AudioCore().GetInputSink();
36 } else {
37 sink = &system.AudioCore().GetOutputSink();
38 }
39 stream = sink->AcquireSinkStream(system, channel_count, name, type);
40 initialized = true;
41 return ResultSuccess;
42}
43
44void DeviceSession::Finalize() {
45 if (initialized) {
46 Stop();
47 sink->CloseStream(stream);
48 stream = nullptr;
49 }
50}
51
52void DeviceSession::Start() {
53 stream->SetPlayedSampleCount(played_sample_count);
54 stream->Start();
55}
56
57void DeviceSession::Stop() {
58 if (stream) {
59 played_sample_count = stream->GetPlayedSampleCount();
60 stream->Stop();
61 }
62}
63
64void DeviceSession::AppendBuffers(std::span<AudioBuffer> buffers) const {
65 auto& memory{system.Memory()};
66
67 for (size_t i = 0; i < buffers.size(); i++) {
68 Sink::SinkBuffer new_buffer{
69 .frames = buffers[i].size / (channel_count * sizeof(s16)),
70 .frames_played = 0,
71 .tag = buffers[i].tag,
72 .consumed = false,
73 };
74
75 if (type == Sink::StreamType::In) {
76 std::vector<s16> samples{};
77 stream->AppendBuffer(new_buffer, samples);
78 } else {
79 std::vector<s16> samples(buffers[i].size / sizeof(s16));
80 memory.ReadBlockUnsafe(buffers[i].samples, samples.data(), buffers[i].size);
81 stream->AppendBuffer(new_buffer, samples);
82 }
83 }
84}
85
86void DeviceSession::ReleaseBuffer(AudioBuffer& buffer) const {
87 if (type == Sink::StreamType::In) {
88 auto& memory{system.Memory()};
89 auto samples{stream->ReleaseBuffer(buffer.size / sizeof(s16))};
90 memory.WriteBlockUnsafe(buffer.samples, samples.data(), buffer.size);
91 }
92}
93
94bool DeviceSession::IsBufferConsumed(u64 tag) const {
95 if (stream) {
96 return stream->IsBufferConsumed(tag);
97 }
98 return true;
99}
100
101void DeviceSession::SetVolume(f32 volume) const {
102 if (stream) {
103 stream->SetSystemVolume(volume);
104 }
105}
106
107u64 DeviceSession::GetPlayedSampleCount() const {
108 if (stream) {
109 return stream->GetPlayedSampleCount();
110 }
111 return 0;
112}
113
114} // namespace AudioCore
diff --git a/src/audio_core/device/device_session.h b/src/audio_core/device/device_session.h
new file mode 100644
index 000000000..4a031b765
--- /dev/null
+++ b/src/audio_core/device/device_session.h
@@ -0,0 +1,126 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <span>
7
8#include "audio_core/common/common.h"
9#include "audio_core/sink/sink.h"
10#include "core/hle/service/audio/errors.h"
11
12namespace Core {
13class System;
14}
15
16namespace AudioCore {
17namespace Sink {
18class SinkStream;
19struct SinkBuffer;
20} // namespace Sink
21
22struct AudioBuffer;
23
24/**
25 * Represents an input or output device stream for audio in and audio out (not used for render).
26 **/
27class DeviceSession {
28public:
29 explicit DeviceSession(Core::System& system);
30 ~DeviceSession();
31
32 /**
33 * Initialize this device session.
34 *
35 * @param name - Name of this device.
36 * @param sample_format - Sample format for this device's output.
37 * @param channel_count - Number of channels for this device (2 or 6).
38 * @param session_id - This session's id.
39 * @param handle - Handle for this device session (unused).
40 * @param applet_resource_user_id - Applet resource user id for this device session (unused).
41 * @param type - Type of this stream (Render, In, Out).
42 * @return Result code for this call.
43 */
44 Result Initialize(std::string_view name, SampleFormat sample_format, u16 channel_count,
45 size_t session_id, u32 handle, u64 applet_resource_user_id,
46 Sink::StreamType type);
47
48 /**
49 * Finalize this device session.
50 */
51 void Finalize();
52
53 /**
54 * Append audio buffers to this device session to be played back.
55 *
56 * @param buffers - The buffers to play.
57 */
58 void AppendBuffers(std::span<AudioBuffer> buffers) const;
59
60 /**
61 * (Audio In only) Pop samples from the backend, and write them back to this buffer's address.
62 *
63 * @param buffer - The buffer to write to.
64 */
65 void ReleaseBuffer(AudioBuffer& buffer) const;
66
67 /**
68 * Check if the buffer for the given tag has been consumed by the backend.
69 *
70 * @param tag - Unqiue tag of the buffer to check.
71 * @return true if the buffer has been consumed, otherwise false.
72 */
73 bool IsBufferConsumed(u64 tag) const;
74
75 /**
76 * Start this device session, starting the backend stream.
77 */
78 void Start();
79
80 /**
81 * Stop this device session, stopping the backend stream.
82 */
83 void Stop();
84
85 /**
86 * Set this device session's volume.
87 *
88 * @param volume - New volume for this session.
89 */
90 void SetVolume(f32 volume) const;
91
92 /**
93 * Get this device session's total played sample count.
94 *
95 * @return Samples played by this session.
96 */
97 u64 GetPlayedSampleCount() const;
98
99private:
100 /// System
101 Core::System& system;
102 /// Output sink this device will use
103 Sink::Sink* sink{};
104 /// The backend stream for this device session to send samples to
105 Sink::SinkStream* stream{};
106 /// Name of this device session
107 std::string name{};
108 /// Type of this device session (render/in/out)
109 Sink::StreamType type{};
110 /// Sample format for this device.
111 SampleFormat sample_format{SampleFormat::PcmInt16};
112 /// Channel count for this device session
113 u16 channel_count{};
114 /// Session id of this device session
115 size_t session_id{};
116 /// Handle of this device session
117 u32 handle{};
118 /// Applet resource user id of this device session
119 u64 applet_resource_user_id{};
120 /// Total number of samples played by this device session
121 u64 played_sample_count{};
122 /// Is this session initialised?
123 bool initialized{};
124};
125
126} // namespace AudioCore
diff --git a/src/audio_core/effect_context.cpp b/src/audio_core/effect_context.cpp
deleted file mode 100644
index 79bcd1192..000000000
--- a/src/audio_core/effect_context.cpp
+++ /dev/null
@@ -1,320 +0,0 @@
1// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <algorithm>
5#include "audio_core/effect_context.h"
6
7namespace AudioCore {
8namespace {
9bool ValidChannelCountForEffect(s32 channel_count) {
10 return channel_count == 1 || channel_count == 2 || channel_count == 4 || channel_count == 6;
11}
12} // namespace
13
14EffectContext::EffectContext(std::size_t effect_count_) : effect_count(effect_count_) {
15 effects.reserve(effect_count);
16 std::generate_n(std::back_inserter(effects), effect_count,
17 [] { return std::make_unique<EffectStubbed>(); });
18}
19EffectContext::~EffectContext() = default;
20
21std::size_t EffectContext::GetCount() const {
22 return effect_count;
23}
24
25EffectBase* EffectContext::GetInfo(std::size_t i) {
26 return effects.at(i).get();
27}
28
29EffectBase* EffectContext::RetargetEffect(std::size_t i, EffectType effect) {
30 switch (effect) {
31 case EffectType::Invalid:
32 effects[i] = std::make_unique<EffectStubbed>();
33 break;
34 case EffectType::BufferMixer:
35 effects[i] = std::make_unique<EffectBufferMixer>();
36 break;
37 case EffectType::Aux:
38 effects[i] = std::make_unique<EffectAuxInfo>();
39 break;
40 case EffectType::Delay:
41 effects[i] = std::make_unique<EffectDelay>();
42 break;
43 case EffectType::Reverb:
44 effects[i] = std::make_unique<EffectReverb>();
45 break;
46 case EffectType::I3dl2Reverb:
47 effects[i] = std::make_unique<EffectI3dl2Reverb>();
48 break;
49 case EffectType::BiquadFilter:
50 effects[i] = std::make_unique<EffectBiquadFilter>();
51 break;
52 default:
53 ASSERT_MSG(false, "Unimplemented effect {}", effect);
54 effects[i] = std::make_unique<EffectStubbed>();
55 }
56 return GetInfo(i);
57}
58
59const EffectBase* EffectContext::GetInfo(std::size_t i) const {
60 return effects.at(i).get();
61}
62
63EffectStubbed::EffectStubbed() : EffectBase(EffectType::Invalid) {}
64EffectStubbed::~EffectStubbed() = default;
65
66void EffectStubbed::Update([[maybe_unused]] EffectInfo::InParams& in_params) {}
67void EffectStubbed::UpdateForCommandGeneration() {}
68
69EffectBase::EffectBase(EffectType effect_type_) : effect_type(effect_type_) {}
70EffectBase::~EffectBase() = default;
71
72UsageState EffectBase::GetUsage() const {
73 return usage;
74}
75
76EffectType EffectBase::GetType() const {
77 return effect_type;
78}
79
80bool EffectBase::IsEnabled() const {
81 return enabled;
82}
83
84s32 EffectBase::GetMixID() const {
85 return mix_id;
86}
87
88s32 EffectBase::GetProcessingOrder() const {
89 return processing_order;
90}
91
92std::vector<u8>& EffectBase::GetWorkBuffer() {
93 return work_buffer;
94}
95
96const std::vector<u8>& EffectBase::GetWorkBuffer() const {
97 return work_buffer;
98}
99
100EffectI3dl2Reverb::EffectI3dl2Reverb() : EffectGeneric(EffectType::I3dl2Reverb) {}
101EffectI3dl2Reverb::~EffectI3dl2Reverb() = default;
102
103void EffectI3dl2Reverb::Update(EffectInfo::InParams& in_params) {
104 auto& params = GetParams();
105 const auto* reverb_params = reinterpret_cast<I3dl2ReverbParams*>(in_params.raw.data());
106 if (!ValidChannelCountForEffect(reverb_params->max_channels)) {
107 ASSERT_MSG(false, "Invalid reverb max channel count {}", reverb_params->max_channels);
108 return;
109 }
110
111 const auto last_status = params.status;
112 mix_id = in_params.mix_id;
113 processing_order = in_params.processing_order;
114 params = *reverb_params;
115 if (!ValidChannelCountForEffect(reverb_params->channel_count)) {
116 params.channel_count = params.max_channels;
117 }
118 enabled = in_params.is_enabled;
119 if (last_status != ParameterStatus::Updated) {
120 params.status = last_status;
121 }
122
123 if (in_params.is_new || skipped) {
124 usage = UsageState::Initialized;
125 params.status = ParameterStatus::Initialized;
126 skipped = in_params.buffer_address == 0 || in_params.buffer_size == 0;
127 if (!skipped) {
128 auto& cur_work_buffer = GetWorkBuffer();
129 // Has two buffers internally
130 cur_work_buffer.resize(in_params.buffer_size * 2);
131 std::fill(cur_work_buffer.begin(), cur_work_buffer.end(), 0);
132 }
133 }
134}
135
136void EffectI3dl2Reverb::UpdateForCommandGeneration() {
137 if (enabled) {
138 usage = UsageState::Running;
139 } else {
140 usage = UsageState::Stopped;
141 }
142 GetParams().status = ParameterStatus::Updated;
143}
144
145I3dl2ReverbState& EffectI3dl2Reverb::GetState() {
146 return state;
147}
148
149const I3dl2ReverbState& EffectI3dl2Reverb::GetState() const {
150 return state;
151}
152
153EffectBiquadFilter::EffectBiquadFilter() : EffectGeneric(EffectType::BiquadFilter) {}
154EffectBiquadFilter::~EffectBiquadFilter() = default;
155
156void EffectBiquadFilter::Update(EffectInfo::InParams& in_params) {
157 auto& params = GetParams();
158 const auto* biquad_params = reinterpret_cast<BiquadFilterParams*>(in_params.raw.data());
159 mix_id = in_params.mix_id;
160 processing_order = in_params.processing_order;
161 params = *biquad_params;
162 enabled = in_params.is_enabled;
163}
164
165void EffectBiquadFilter::UpdateForCommandGeneration() {
166 if (enabled) {
167 usage = UsageState::Running;
168 } else {
169 usage = UsageState::Stopped;
170 }
171 GetParams().status = ParameterStatus::Updated;
172}
173
174EffectAuxInfo::EffectAuxInfo() : EffectGeneric(EffectType::Aux) {}
175EffectAuxInfo::~EffectAuxInfo() = default;
176
177void EffectAuxInfo::Update(EffectInfo::InParams& in_params) {
178 const auto* aux_params = reinterpret_cast<AuxInfo*>(in_params.raw.data());
179 mix_id = in_params.mix_id;
180 processing_order = in_params.processing_order;
181 GetParams() = *aux_params;
182 enabled = in_params.is_enabled;
183
184 if (in_params.is_new || skipped) {
185 skipped = aux_params->send_buffer_info == 0 || aux_params->return_buffer_info == 0;
186 if (skipped) {
187 return;
188 }
189
190 // There's two AuxInfos which are an identical size, the first one is managed by the cpu,
191 // the second is managed by the dsp. All we care about is managing the DSP one
192 send_info = aux_params->send_buffer_info + sizeof(AuxInfoDSP);
193 send_buffer = aux_params->send_buffer_info + (sizeof(AuxInfoDSP) * 2);
194
195 recv_info = aux_params->return_buffer_info + sizeof(AuxInfoDSP);
196 recv_buffer = aux_params->return_buffer_info + (sizeof(AuxInfoDSP) * 2);
197 }
198}
199
200void EffectAuxInfo::UpdateForCommandGeneration() {
201 if (enabled) {
202 usage = UsageState::Running;
203 } else {
204 usage = UsageState::Stopped;
205 }
206}
207
208VAddr EffectAuxInfo::GetSendInfo() const {
209 return send_info;
210}
211
212VAddr EffectAuxInfo::GetSendBuffer() const {
213 return send_buffer;
214}
215
216VAddr EffectAuxInfo::GetRecvInfo() const {
217 return recv_info;
218}
219
220VAddr EffectAuxInfo::GetRecvBuffer() const {
221 return recv_buffer;
222}
223
224EffectDelay::EffectDelay() : EffectGeneric(EffectType::Delay) {}
225EffectDelay::~EffectDelay() = default;
226
227void EffectDelay::Update(EffectInfo::InParams& in_params) {
228 const auto* delay_params = reinterpret_cast<DelayParams*>(in_params.raw.data());
229 auto& params = GetParams();
230 if (!ValidChannelCountForEffect(delay_params->max_channels)) {
231 return;
232 }
233
234 const auto last_status = params.status;
235 mix_id = in_params.mix_id;
236 processing_order = in_params.processing_order;
237 params = *delay_params;
238 if (!ValidChannelCountForEffect(delay_params->channels)) {
239 params.channels = params.max_channels;
240 }
241 enabled = in_params.is_enabled;
242
243 if (last_status != ParameterStatus::Updated) {
244 params.status = last_status;
245 }
246
247 if (in_params.is_new || skipped) {
248 usage = UsageState::Initialized;
249 params.status = ParameterStatus::Initialized;
250 skipped = in_params.buffer_address == 0 || in_params.buffer_size == 0;
251 }
252}
253
254void EffectDelay::UpdateForCommandGeneration() {
255 if (enabled) {
256 usage = UsageState::Running;
257 } else {
258 usage = UsageState::Stopped;
259 }
260 GetParams().status = ParameterStatus::Updated;
261}
262
263EffectBufferMixer::EffectBufferMixer() : EffectGeneric(EffectType::BufferMixer) {}
264EffectBufferMixer::~EffectBufferMixer() = default;
265
266void EffectBufferMixer::Update(EffectInfo::InParams& in_params) {
267 mix_id = in_params.mix_id;
268 processing_order = in_params.processing_order;
269 GetParams() = *reinterpret_cast<BufferMixerParams*>(in_params.raw.data());
270 enabled = in_params.is_enabled;
271}
272
273void EffectBufferMixer::UpdateForCommandGeneration() {
274 if (enabled) {
275 usage = UsageState::Running;
276 } else {
277 usage = UsageState::Stopped;
278 }
279}
280
281EffectReverb::EffectReverb() : EffectGeneric(EffectType::Reverb) {}
282EffectReverb::~EffectReverb() = default;
283
284void EffectReverb::Update(EffectInfo::InParams& in_params) {
285 const auto* reverb_params = reinterpret_cast<ReverbParams*>(in_params.raw.data());
286 auto& params = GetParams();
287 if (!ValidChannelCountForEffect(reverb_params->max_channels)) {
288 return;
289 }
290
291 const auto last_status = params.status;
292 mix_id = in_params.mix_id;
293 processing_order = in_params.processing_order;
294 params = *reverb_params;
295 if (!ValidChannelCountForEffect(reverb_params->channels)) {
296 params.channels = params.max_channels;
297 }
298 enabled = in_params.is_enabled;
299
300 if (last_status != ParameterStatus::Updated) {
301 params.status = last_status;
302 }
303
304 if (in_params.is_new || skipped) {
305 usage = UsageState::Initialized;
306 params.status = ParameterStatus::Initialized;
307 skipped = in_params.buffer_address == 0 || in_params.buffer_size == 0;
308 }
309}
310
311void EffectReverb::UpdateForCommandGeneration() {
312 if (enabled) {
313 usage = UsageState::Running;
314 } else {
315 usage = UsageState::Stopped;
316 }
317 GetParams().status = ParameterStatus::Updated;
318}
319
320} // namespace AudioCore
diff --git a/src/audio_core/effect_context.h b/src/audio_core/effect_context.h
deleted file mode 100644
index cb47df472..000000000
--- a/src/audio_core/effect_context.h
+++ /dev/null
@@ -1,349 +0,0 @@
1// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7#include <memory>
8#include <vector>
9#include "audio_core/common.h"
10#include "audio_core/delay_line.h"
11#include "common/common_funcs.h"
12#include "common/common_types.h"
13#include "common/swap.h"
14
15namespace AudioCore {
16enum class EffectType : u8 {
17 Invalid = 0,
18 BufferMixer = 1,
19 Aux = 2,
20 Delay = 3,
21 Reverb = 4,
22 I3dl2Reverb = 5,
23 BiquadFilter = 6,
24};
25
26enum class UsageStatus : u8 {
27 Invalid = 0,
28 New = 1,
29 Initialized = 2,
30 Used = 3,
31 Removed = 4,
32};
33
34enum class UsageState {
35 Invalid = 0,
36 Initialized = 1,
37 Running = 2,
38 Stopped = 3,
39};
40
41enum class ParameterStatus : u8 {
42 Initialized = 0,
43 Updating = 1,
44 Updated = 2,
45};
46
47struct BufferMixerParams {
48 std::array<s8, AudioCommon::MAX_MIX_BUFFERS> input{};
49 std::array<s8, AudioCommon::MAX_MIX_BUFFERS> output{};
50 std::array<float_le, AudioCommon::MAX_MIX_BUFFERS> volume{};
51 s32_le count{};
52};
53static_assert(sizeof(BufferMixerParams) == 0x94, "BufferMixerParams is an invalid size");
54
55struct AuxInfoDSP {
56 u32_le read_offset{};
57 u32_le write_offset{};
58 u32_le remaining{};
59 INSERT_PADDING_WORDS(13);
60};
61static_assert(sizeof(AuxInfoDSP) == 0x40, "AuxInfoDSP is an invalid size");
62
63struct AuxInfo {
64 std::array<s8, AudioCommon::MAX_MIX_BUFFERS> input_mix_buffers{};
65 std::array<s8, AudioCommon::MAX_MIX_BUFFERS> output_mix_buffers{};
66 u32_le count{};
67 s32_le sample_rate{};
68 s32_le sample_count{};
69 s32_le mix_buffer_count{};
70 u64_le send_buffer_info{};
71 u64_le send_buffer_base{};
72
73 u64_le return_buffer_info{};
74 u64_le return_buffer_base{};
75};
76static_assert(sizeof(AuxInfo) == 0x60, "AuxInfo is an invalid size");
77
78struct I3dl2ReverbParams {
79 std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> input{};
80 std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> output{};
81 u16_le max_channels{};
82 u16_le channel_count{};
83 INSERT_PADDING_BYTES(1);
84 u32_le sample_rate{};
85 f32 room_hf{};
86 f32 hf_reference{};
87 f32 decay_time{};
88 f32 hf_decay_ratio{};
89 f32 room{};
90 f32 reflection{};
91 f32 reverb{};
92 f32 diffusion{};
93 f32 reflection_delay{};
94 f32 reverb_delay{};
95 f32 density{};
96 f32 dry_gain{};
97 ParameterStatus status{};
98 INSERT_PADDING_BYTES(3);
99};
100static_assert(sizeof(I3dl2ReverbParams) == 0x4c, "I3dl2ReverbParams is an invalid size");
101
102struct BiquadFilterParams {
103 std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> input{};
104 std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> output{};
105 std::array<s16_le, 3> numerator;
106 std::array<s16_le, 2> denominator;
107 s8 channel_count{};
108 ParameterStatus status{};
109};
110static_assert(sizeof(BiquadFilterParams) == 0x18, "BiquadFilterParams is an invalid size");
111
112struct DelayParams {
113 std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> input{};
114 std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> output{};
115 u16_le max_channels{};
116 u16_le channels{};
117 s32_le max_delay{};
118 s32_le delay{};
119 s32_le sample_rate{};
120 s32_le gain{};
121 s32_le feedback_gain{};
122 s32_le out_gain{};
123 s32_le dry_gain{};
124 s32_le channel_spread{};
125 s32_le low_pass{};
126 ParameterStatus status{};
127 INSERT_PADDING_BYTES(3);
128};
129static_assert(sizeof(DelayParams) == 0x38, "DelayParams is an invalid size");
130
131struct ReverbParams {
132 std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> input{};
133 std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> output{};
134 u16_le max_channels{};
135 u16_le channels{};
136 s32_le sample_rate{};
137 s32_le mode0{};
138 s32_le mode0_gain{};
139 s32_le pre_delay{};
140 s32_le mode1{};
141 s32_le mode1_gain{};
142 s32_le decay{};
143 s32_le hf_decay_ratio{};
144 s32_le coloration{};
145 s32_le reverb_gain{};
146 s32_le out_gain{};
147 s32_le dry_gain{};
148 ParameterStatus status{};
149 INSERT_PADDING_BYTES(3);
150};
151static_assert(sizeof(ReverbParams) == 0x44, "ReverbParams is an invalid size");
152
153class EffectInfo {
154public:
155 struct InParams {
156 EffectType type{};
157 u8 is_new{};
158 u8 is_enabled{};
159 INSERT_PADDING_BYTES(1);
160 s32_le mix_id{};
161 u64_le buffer_address{};
162 u64_le buffer_size{};
163 s32_le processing_order{};
164 INSERT_PADDING_BYTES(4);
165 union {
166 std::array<u8, 0xa0> raw;
167 };
168 };
169 static_assert(sizeof(InParams) == 0xc0, "InParams is an invalid size");
170
171 struct OutParams {
172 UsageStatus status{};
173 INSERT_PADDING_BYTES(15);
174 };
175 static_assert(sizeof(OutParams) == 0x10, "OutParams is an invalid size");
176};
177
178struct AuxAddress {
179 VAddr send_dsp_info{};
180 VAddr send_buffer_base{};
181 VAddr return_dsp_info{};
182 VAddr return_buffer_base{};
183};
184
185class EffectBase {
186public:
187 explicit EffectBase(EffectType effect_type_);
188 virtual ~EffectBase();
189
190 virtual void Update(EffectInfo::InParams& in_params) = 0;
191 virtual void UpdateForCommandGeneration() = 0;
192 [[nodiscard]] UsageState GetUsage() const;
193 [[nodiscard]] EffectType GetType() const;
194 [[nodiscard]] bool IsEnabled() const;
195 [[nodiscard]] s32 GetMixID() const;
196 [[nodiscard]] s32 GetProcessingOrder() const;
197 [[nodiscard]] std::vector<u8>& GetWorkBuffer();
198 [[nodiscard]] const std::vector<u8>& GetWorkBuffer() const;
199
200protected:
201 UsageState usage{UsageState::Invalid};
202 EffectType effect_type{};
203 s32 mix_id{};
204 s32 processing_order{};
205 bool enabled = false;
206 std::vector<u8> work_buffer{};
207};
208
209template <typename T>
210class EffectGeneric : public EffectBase {
211public:
212 explicit EffectGeneric(EffectType effect_type_) : EffectBase(effect_type_) {}
213
214 T& GetParams() {
215 return internal_params;
216 }
217
218 const T& GetParams() const {
219 return internal_params;
220 }
221
222private:
223 T internal_params{};
224};
225
226class EffectStubbed : public EffectBase {
227public:
228 explicit EffectStubbed();
229 ~EffectStubbed() override;
230
231 void Update(EffectInfo::InParams& in_params) override;
232 void UpdateForCommandGeneration() override;
233};
234
235struct I3dl2ReverbState {
236 f32 lowpass_0{};
237 f32 lowpass_1{};
238 f32 lowpass_2{};
239
240 DelayLineBase early_delay_line{};
241 std::array<u32, AudioCommon::I3DL2REVERB_TAPS> early_tap_steps{};
242 f32 early_gain{};
243 f32 late_gain{};
244
245 u32 early_to_late_taps{};
246 std::array<DelayLineBase, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> fdn_delay_line{};
247 std::array<DelayLineAllPass, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> decay_delay_line0{};
248 std::array<DelayLineAllPass, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> decay_delay_line1{};
249 f32 last_reverb_echo{};
250 DelayLineBase center_delay_line{};
251 std::array<std::array<f32, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT>, 3> lpf_coefficients{};
252 std::array<f32, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> shelf_filter{};
253 f32 dry_gain{};
254};
255
256class EffectI3dl2Reverb : public EffectGeneric<I3dl2ReverbParams> {
257public:
258 explicit EffectI3dl2Reverb();
259 ~EffectI3dl2Reverb() override;
260
261 void Update(EffectInfo::InParams& in_params) override;
262 void UpdateForCommandGeneration() override;
263
264 I3dl2ReverbState& GetState();
265 const I3dl2ReverbState& GetState() const;
266
267private:
268 bool skipped = false;
269 I3dl2ReverbState state{};
270};
271
272class EffectBiquadFilter : public EffectGeneric<BiquadFilterParams> {
273public:
274 explicit EffectBiquadFilter();
275 ~EffectBiquadFilter() override;
276
277 void Update(EffectInfo::InParams& in_params) override;
278 void UpdateForCommandGeneration() override;
279};
280
281class EffectAuxInfo : public EffectGeneric<AuxInfo> {
282public:
283 explicit EffectAuxInfo();
284 ~EffectAuxInfo() override;
285
286 void Update(EffectInfo::InParams& in_params) override;
287 void UpdateForCommandGeneration() override;
288 [[nodiscard]] VAddr GetSendInfo() const;
289 [[nodiscard]] VAddr GetSendBuffer() const;
290 [[nodiscard]] VAddr GetRecvInfo() const;
291 [[nodiscard]] VAddr GetRecvBuffer() const;
292
293private:
294 VAddr send_info{};
295 VAddr send_buffer{};
296 VAddr recv_info{};
297 VAddr recv_buffer{};
298 bool skipped = false;
299 AuxAddress addresses{};
300};
301
302class EffectDelay : public EffectGeneric<DelayParams> {
303public:
304 explicit EffectDelay();
305 ~EffectDelay() override;
306
307 void Update(EffectInfo::InParams& in_params) override;
308 void UpdateForCommandGeneration() override;
309
310private:
311 bool skipped = false;
312};
313
314class EffectBufferMixer : public EffectGeneric<BufferMixerParams> {
315public:
316 explicit EffectBufferMixer();
317 ~EffectBufferMixer() override;
318
319 void Update(EffectInfo::InParams& in_params) override;
320 void UpdateForCommandGeneration() override;
321};
322
323class EffectReverb : public EffectGeneric<ReverbParams> {
324public:
325 explicit EffectReverb();
326 ~EffectReverb() override;
327
328 void Update(EffectInfo::InParams& in_params) override;
329 void UpdateForCommandGeneration() override;
330
331private:
332 bool skipped = false;
333};
334
335class EffectContext {
336public:
337 explicit EffectContext(std::size_t effect_count_);
338 ~EffectContext();
339
340 [[nodiscard]] std::size_t GetCount() const;
341 [[nodiscard]] EffectBase* GetInfo(std::size_t i);
342 [[nodiscard]] EffectBase* RetargetEffect(std::size_t i, EffectType effect);
343 [[nodiscard]] const EffectBase* GetInfo(std::size_t i) const;
344
345private:
346 std::size_t effect_count{};
347 std::vector<std::unique_ptr<EffectBase>> effects;
348};
349} // namespace AudioCore
diff --git a/src/audio_core/in/audio_in.cpp b/src/audio_core/in/audio_in.cpp
new file mode 100644
index 000000000..c946895d6
--- /dev/null
+++ b/src/audio_core/in/audio_in.cpp
@@ -0,0 +1,100 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/audio_in_manager.h"
5#include "audio_core/in/audio_in.h"
6#include "core/hle/kernel/k_event.h"
7
8namespace AudioCore::AudioIn {
9
10In::In(Core::System& system_, Manager& manager_, Kernel::KEvent* event_, size_t session_id_)
11 : manager{manager_}, parent_mutex{manager.mutex}, event{event_}, system{system_, event,
12 session_id_} {}
13
14void In::Free() {
15 std::scoped_lock l{parent_mutex};
16 manager.ReleaseSessionId(system.GetSessionId());
17}
18
19System& In::GetSystem() {
20 return system;
21}
22
23AudioIn::State In::GetState() {
24 std::scoped_lock l{parent_mutex};
25 return system.GetState();
26}
27
28Result In::StartSystem() {
29 std::scoped_lock l{parent_mutex};
30 return system.Start();
31}
32
33void In::StartSession() {
34 std::scoped_lock l{parent_mutex};
35 system.StartSession();
36}
37
38Result In::StopSystem() {
39 std::scoped_lock l{parent_mutex};
40 return system.Stop();
41}
42
43Result In::AppendBuffer(const AudioInBuffer& buffer, u64 tag) {
44 std::scoped_lock l{parent_mutex};
45
46 if (system.AppendBuffer(buffer, tag)) {
47 return ResultSuccess;
48 }
49 return Service::Audio::ERR_BUFFER_COUNT_EXCEEDED;
50}
51
52void In::ReleaseAndRegisterBuffers() {
53 std::scoped_lock l{parent_mutex};
54 if (system.GetState() == State::Started) {
55 system.ReleaseBuffers();
56 system.RegisterBuffers();
57 }
58}
59
60bool In::FlushAudioInBuffers() {
61 std::scoped_lock l{parent_mutex};
62 return system.FlushAudioInBuffers();
63}
64
65u32 In::GetReleasedBuffers(std::span<u64> tags) {
66 std::scoped_lock l{parent_mutex};
67 return system.GetReleasedBuffers(tags);
68}
69
70Kernel::KReadableEvent& In::GetBufferEvent() {
71 std::scoped_lock l{parent_mutex};
72 return event->GetReadableEvent();
73}
74
75f32 In::GetVolume() {
76 std::scoped_lock l{parent_mutex};
77 return system.GetVolume();
78}
79
80void In::SetVolume(f32 volume) {
81 std::scoped_lock l{parent_mutex};
82 system.SetVolume(volume);
83}
84
85bool In::ContainsAudioBuffer(u64 tag) {
86 std::scoped_lock l{parent_mutex};
87 return system.ContainsAudioBuffer(tag);
88}
89
90u32 In::GetBufferCount() {
91 std::scoped_lock l{parent_mutex};
92 return system.GetBufferCount();
93}
94
95u64 In::GetPlayedSampleCount() {
96 std::scoped_lock l{parent_mutex};
97 return system.GetPlayedSampleCount();
98}
99
100} // namespace AudioCore::AudioIn
diff --git a/src/audio_core/in/audio_in.h b/src/audio_core/in/audio_in.h
new file mode 100644
index 000000000..6253891d5
--- /dev/null
+++ b/src/audio_core/in/audio_in.h
@@ -0,0 +1,147 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <mutex>
7
8#include "audio_core/in/audio_in_system.h"
9
10namespace Core {
11class System;
12}
13
14namespace Kernel {
15class KEvent;
16class KReadableEvent;
17} // namespace Kernel
18
19namespace AudioCore::AudioIn {
20class Manager;
21
22/**
23 * Interface between the service and audio in system. Mainly responsible for forwarding service
24 * calls to the system.
25 */
26class In {
27public:
28 explicit In(Core::System& system, Manager& manager, Kernel::KEvent* event, size_t session_id);
29
30 /**
31 * Free this audio in from the audio in manager.
32 */
33 void Free();
34
35 /**
36 * Get this audio in's system.
37 */
38 System& GetSystem();
39
40 /**
41 * Get the current state.
42 *
43 * @return Started or Stopped.
44 */
45 AudioIn::State GetState();
46
47 /**
48 * Start the system
49 *
50 * @return Result code
51 */
52 Result StartSystem();
53
54 /**
55 * Start the system's device session.
56 */
57 void StartSession();
58
59 /**
60 * Stop the system.
61 *
62 * @return Result code
63 */
64 Result StopSystem();
65
66 /**
67 * Append a new buffer to the system, the buffer event will be signalled when it is filled.
68 *
69 * @param buffer - The new buffer to append.
70 * @param tag - Unique tag for this buffer.
71 * @return Result code.
72 */
73 Result AppendBuffer(const AudioInBuffer& buffer, u64 tag);
74
75 /**
76 * Release all completed buffers, and register any appended.
77 */
78 void ReleaseAndRegisterBuffers();
79
80 /**
81 * Flush all buffers.
82 */
83 bool FlushAudioInBuffers();
84
85 /**
86 * Get all of the currently released buffers.
87 *
88 * @param tags - Output container for the buffer tags which were released.
89 * @return The number of buffers released.
90 */
91 u32 GetReleasedBuffers(std::span<u64> tags);
92
93 /**
94 * Get the buffer event for this audio in, this event will be signalled when a buffer is filled.
95 *
96 * @return The buffer event.
97 */
98 Kernel::KReadableEvent& GetBufferEvent();
99
100 /**
101 * Get the current system volume.
102 *
103 * @return The current volume.
104 */
105 f32 GetVolume();
106
107 /**
108 * Set the system volume.
109 *
110 * @param volume - The volume to set.
111 */
112 void SetVolume(f32 volume);
113
114 /**
115 * Check if a buffer is in the system.
116 *
117 * @param tag - The tag to search for.
118 * @return True if the buffer is in the system, otherwise false.
119 */
120 bool ContainsAudioBuffer(u64 tag);
121
122 /**
123 * Get the maximum number of buffers.
124 *
125 * @return The maximum number of buffers.
126 */
127 u32 GetBufferCount();
128
129 /**
130 * Get the total played sample count for this audio in.
131 *
132 * @return The played sample count.
133 */
134 u64 GetPlayedSampleCount();
135
136private:
137 /// The AudioIn::Manager this audio in is registered with
138 Manager& manager;
139 /// Manager's mutex
140 std::recursive_mutex& parent_mutex;
141 /// Buffer event, signalled when buffers are ready to be released
142 Kernel::KEvent* event;
143 /// Main audio in system
144 System system;
145};
146
147} // namespace AudioCore::AudioIn
diff --git a/src/audio_core/in/audio_in_system.cpp b/src/audio_core/in/audio_in_system.cpp
new file mode 100644
index 000000000..ec5d37ed4
--- /dev/null
+++ b/src/audio_core/in/audio_in_system.cpp
@@ -0,0 +1,213 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <mutex>
5#include "audio_core/audio_event.h"
6#include "audio_core/audio_manager.h"
7#include "audio_core/in/audio_in_system.h"
8#include "common/logging/log.h"
9#include "core/core.h"
10#include "core/core_timing.h"
11#include "core/hle/kernel/k_event.h"
12
13namespace AudioCore::AudioIn {
14
15System::System(Core::System& system_, Kernel::KEvent* event_, const size_t session_id_)
16 : system{system_}, buffer_event{event_},
17 session_id{session_id_}, session{std::make_unique<DeviceSession>(system_)} {}
18
19System::~System() {
20 Finalize();
21}
22
23void System::Finalize() {
24 Stop();
25 session->Finalize();
26 buffer_event->GetWritableEvent().Signal();
27}
28
29void System::StartSession() {
30 session->Start();
31}
32
33size_t System::GetSessionId() const {
34 return session_id;
35}
36
37std::string_view System::GetDefaultDeviceName() {
38 return "BuiltInHeadset";
39}
40
41std::string_view System::GetDefaultUacDeviceName() {
42 return "Uac";
43}
44
45Result System::IsConfigValid(const std::string_view device_name,
46 const AudioInParameter& in_params) {
47 if ((device_name.size() > 0) &&
48 (device_name != GetDefaultDeviceName() && device_name != GetDefaultUacDeviceName())) {
49 return Service::Audio::ERR_INVALID_DEVICE_NAME;
50 }
51
52 if (in_params.sample_rate != TargetSampleRate && in_params.sample_rate > 0) {
53 return Service::Audio::ERR_INVALID_SAMPLE_RATE;
54 }
55
56 return ResultSuccess;
57}
58
59Result System::Initialize(std::string& device_name, const AudioInParameter& in_params,
60 const u32 handle_, const u64 applet_resource_user_id_) {
61 auto result{IsConfigValid(device_name, in_params)};
62 if (result.IsError()) {
63 return result;
64 }
65
66 handle = handle_;
67 applet_resource_user_id = applet_resource_user_id_;
68 if (device_name.empty() || device_name[0] == '\0') {
69 name = std::string(GetDefaultDeviceName());
70 } else {
71 name = std::move(device_name);
72 }
73
74 sample_rate = TargetSampleRate;
75 sample_format = SampleFormat::PcmInt16;
76 channel_count = in_params.channel_count <= 2 ? 2 : 6;
77 volume = 1.0f;
78 is_uac = name == "Uac";
79 return ResultSuccess;
80}
81
82Result System::Start() {
83 if (state != State::Stopped) {
84 return Service::Audio::ERR_OPERATION_FAILED;
85 }
86
87 session->Initialize(name, sample_format, channel_count, session_id, handle,
88 applet_resource_user_id, Sink::StreamType::In);
89 session->SetVolume(volume);
90 session->Start();
91 state = State::Started;
92
93 std::vector<AudioBuffer> buffers_to_flush{};
94 buffers.RegisterBuffers(buffers_to_flush);
95 session->AppendBuffers(buffers_to_flush);
96
97 return ResultSuccess;
98}
99
100Result System::Stop() {
101 if (state == State::Started) {
102 session->Stop();
103 session->SetVolume(0.0f);
104 state = State::Stopped;
105 }
106
107 return ResultSuccess;
108}
109
110bool System::AppendBuffer(const AudioInBuffer& buffer, const u64 tag) {
111 if (buffers.GetTotalBufferCount() == BufferCount) {
112 return false;
113 }
114
115 AudioBuffer new_buffer{
116 .played_timestamp = 0, .samples = buffer.samples, .tag = tag, .size = buffer.size};
117
118 buffers.AppendBuffer(new_buffer);
119 RegisterBuffers();
120
121 return true;
122}
123
124void System::RegisterBuffers() {
125 if (state == State::Started) {
126 std::vector<AudioBuffer> registered_buffers{};
127 buffers.RegisterBuffers(registered_buffers);
128 session->AppendBuffers(registered_buffers);
129 }
130}
131
132void System::ReleaseBuffers() {
133 bool signal{buffers.ReleaseBuffers(system.CoreTiming(), *session)};
134
135 if (signal) {
136 // Signal if any buffer was released, or if none are registered, we need more.
137 buffer_event->GetWritableEvent().Signal();
138 }
139}
140
141u32 System::GetReleasedBuffers(std::span<u64> tags) {
142 return buffers.GetReleasedBuffers(tags);
143}
144
145bool System::FlushAudioInBuffers() {
146 if (state != State::Started) {
147 return false;
148 }
149
150 u32 buffers_released{};
151 buffers.FlushBuffers(buffers_released);
152
153 if (buffers_released > 0) {
154 buffer_event->GetWritableEvent().Signal();
155 }
156 return true;
157}
158
159u16 System::GetChannelCount() const {
160 return channel_count;
161}
162
163u32 System::GetSampleRate() const {
164 return sample_rate;
165}
166
167SampleFormat System::GetSampleFormat() const {
168 return sample_format;
169}
170
171State System::GetState() {
172 switch (state) {
173 case State::Started:
174 case State::Stopped:
175 return state;
176 default:
177 LOG_ERROR(Service_Audio, "AudioIn invalid state!");
178 state = State::Stopped;
179 break;
180 }
181 return state;
182}
183
184std::string System::GetName() const {
185 return name;
186}
187
188f32 System::GetVolume() const {
189 return volume;
190}
191
192void System::SetVolume(const f32 volume_) {
193 volume = volume_;
194 session->SetVolume(volume_);
195}
196
197bool System::ContainsAudioBuffer(const u64 tag) {
198 return buffers.ContainsBuffer(tag);
199}
200
201u32 System::GetBufferCount() {
202 return buffers.GetAppendedRegisteredCount();
203}
204
205u64 System::GetPlayedSampleCount() const {
206 return session->GetPlayedSampleCount();
207}
208
209bool System::IsUac() const {
210 return is_uac;
211}
212
213} // namespace AudioCore::AudioIn
diff --git a/src/audio_core/in/audio_in_system.h b/src/audio_core/in/audio_in_system.h
new file mode 100644
index 000000000..165e35d83
--- /dev/null
+++ b/src/audio_core/in/audio_in_system.h
@@ -0,0 +1,275 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <atomic>
7#include <memory>
8#include <span>
9#include <string>
10
11#include "audio_core/common/common.h"
12#include "audio_core/device/audio_buffers.h"
13#include "audio_core/device/device_session.h"
14#include "core/hle/service/audio/errors.h"
15
16namespace Core {
17class System;
18}
19
20namespace Kernel {
21class KEvent;
22}
23
24namespace AudioCore::AudioIn {
25
26constexpr SessionTypes SessionType = SessionTypes::AudioIn;
27
28struct AudioInParameter {
29 /* 0x0 */ s32_le sample_rate;
30 /* 0x4 */ u16_le channel_count;
31 /* 0x6 */ u16_le reserved;
32};
33static_assert(sizeof(AudioInParameter) == 0x8, "AudioInParameter is an invalid size");
34
35struct AudioInParameterInternal {
36 /* 0x0 */ u32_le sample_rate;
37 /* 0x4 */ u32_le channel_count;
38 /* 0x8 */ u32_le sample_format;
39 /* 0xC */ u32_le state;
40};
41static_assert(sizeof(AudioInParameterInternal) == 0x10,
42 "AudioInParameterInternal is an invalid size");
43
44struct AudioInBuffer {
45 /* 0x00 */ AudioInBuffer* next;
46 /* 0x08 */ VAddr samples;
47 /* 0x10 */ u64 capacity;
48 /* 0x18 */ u64 size;
49 /* 0x20 */ u64 offset;
50};
51static_assert(sizeof(AudioInBuffer) == 0x28, "AudioInBuffer is an invalid size");
52
53enum class State {
54 Started,
55 Stopped,
56};
57
58/**
59 * Controls and drives audio input.
60 */
61class System {
62public:
63 explicit System(Core::System& system, Kernel::KEvent* event, size_t session_id);
64 ~System();
65
66 /**
67 * Get the default audio input device name.
68 *
69 * @return The default audio input device name.
70 */
71 std::string_view GetDefaultDeviceName();
72
73 /**
74 * Get the default USB audio input device name.
75 * This is preferred over non-USB as some games refuse to work with the BuiltInHeadset
76 * (e.g Let's Sing).
77 *
78 * @return The default USB audio input device name.
79 */
80 std::string_view GetDefaultUacDeviceName();
81
82 /**
83 * Is the given initialize config valid?
84 *
85 * @param device_name - The name of the requested input device.
86 * @param in_params - Input parameters, see AudioInParameter.
87 * @return Result code.
88 */
89 Result IsConfigValid(std::string_view device_name, const AudioInParameter& in_params);
90
91 /**
92 * Initialize this system.
93 *
94 * @param device_name - The name of the requested input device.
95 * @param in_params - Input parameters, see AudioInParameter.
96 * @param handle - Unused.
97 * @param applet_resource_user_id - Unused.
98 * @return Result code.
99 */
100 Result Initialize(std::string& device_name, const AudioInParameter& in_params, u32 handle,
101 u64 applet_resource_user_id);
102
103 /**
104 * Start this system.
105 *
106 * @return Result code.
107 */
108 Result Start();
109
110 /**
111 * Stop this system.
112 *
113 * @return Result code.
114 */
115 Result Stop();
116
117 /**
118 * Finalize this system.
119 */
120 void Finalize();
121
122 /**
123 * Start this system's device session.
124 */
125 void StartSession();
126
127 /**
128 * Get this system's id.
129 */
130 size_t GetSessionId() const;
131
132 /**
133 * Append a new buffer to the device.
134 *
135 * @param buffer - New buffer to append.
136 * @param tag - Unique tag of the buffer.
137 * @return True if the buffer was appended, otherwise false.
138 */
139 bool AppendBuffer(const AudioInBuffer& buffer, u64 tag);
140
141 /**
142 * Register all appended buffers.
143 */
144 void RegisterBuffers();
145
146 /**
147 * Release all registered buffers.
148 */
149 void ReleaseBuffers();
150
151 /**
152 * Get all released buffers.
153 *
154 * @param tags - Container to be filled with the released buffers' tags.
155 * @return The number of buffers released.
156 */
157 u32 GetReleasedBuffers(std::span<u64> tags);
158
159 /**
160 * Flush all appended and registered buffers.
161 *
162 * @return True if buffers were successfully flushed, otherwise false.
163 */
164 bool FlushAudioInBuffers();
165
166 /**
167 * Get this system's current channel count.
168 *
169 * @return The channel count.
170 */
171 u16 GetChannelCount() const;
172
173 /**
174 * Get this system's current sample rate.
175 *
176 * @return The sample rate.
177 */
178 u32 GetSampleRate() const;
179
180 /**
181 * Get this system's current sample format.
182 *
183 * @return The sample format.
184 */
185 SampleFormat GetSampleFormat() const;
186
187 /**
188 * Get this system's current state.
189 *
190 * @return The current state.
191 */
192 State GetState();
193
194 /**
195 * Get this system's name.
196 *
197 * @return The system's name.
198 */
199 std::string GetName() const;
200
201 /**
202 * Get this system's current volume.
203 *
204 * @return The system's current volume.
205 */
206 f32 GetVolume() const;
207
208 /**
209 * Set this system's current volume.
210 *
211 * @param The new volume.
212 */
213 void SetVolume(f32 volume);
214
215 /**
216 * Does the system contain this buffer?
217 *
218 * @param tag - Unique tag to search for.
219 * @return True if the buffer is in the system, otherwise false.
220 */
221 bool ContainsAudioBuffer(u64 tag);
222
223 /**
224 * Get the maximum number of usable buffers (default 32).
225 *
226 * @return The number of buffers.
227 */
228 u32 GetBufferCount();
229
230 /**
231 * Get the total number of samples played by this system.
232 *
233 * @return The number of samples.
234 */
235 u64 GetPlayedSampleCount() const;
236
237 /**
238 * Is this system using a USB device?
239 *
240 * @return True if using a USB device, otherwise false.
241 */
242 bool IsUac() const;
243
244private:
245 /// Core system
246 Core::System& system;
247 /// (Unused)
248 u32 handle{};
249 /// (Unused)
250 u64 applet_resource_user_id{};
251 /// Buffer event, signalled when a buffer is ready
252 Kernel::KEvent* buffer_event;
253 /// Session id of this system
254 size_t session_id{};
255 /// Device session for this system
256 std::unique_ptr<DeviceSession> session;
257 /// Audio buffers in use by this system
258 AudioBuffers<BufferCount> buffers{BufferCount};
259 /// Sample rate of this system
260 u32 sample_rate{};
261 /// Sample format of this system
262 SampleFormat sample_format{SampleFormat::PcmInt16};
263 /// Channel count of this system
264 u16 channel_count{};
265 /// State of this system
266 std::atomic<State> state{State::Stopped};
267 /// Name of this system
268 std::string name{};
269 /// Volume of this system
270 f32 volume{1.0f};
271 /// Is this system's device USB?
272 bool is_uac{false};
273};
274
275} // namespace AudioCore::AudioIn
diff --git a/src/audio_core/info_updater.cpp b/src/audio_core/info_updater.cpp
deleted file mode 100644
index 0065e6e53..000000000
--- a/src/audio_core/info_updater.cpp
+++ /dev/null
@@ -1,511 +0,0 @@
1// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/behavior_info.h"
5#include "audio_core/effect_context.h"
6#include "audio_core/info_updater.h"
7#include "audio_core/memory_pool.h"
8#include "audio_core/mix_context.h"
9#include "audio_core/sink_context.h"
10#include "audio_core/splitter_context.h"
11#include "audio_core/voice_context.h"
12#include "common/logging/log.h"
13
14namespace AudioCore {
15
16InfoUpdater::InfoUpdater(const std::vector<u8>& in_params_, std::vector<u8>& out_params_,
17 BehaviorInfo& behavior_info_)
18 : in_params(in_params_), out_params(out_params_), behavior_info(behavior_info_) {
19 ASSERT(
20 AudioCommon::CanConsumeBuffer(in_params.size(), 0, sizeof(AudioCommon::UpdateDataHeader)));
21 std::memcpy(&input_header, in_params.data(), sizeof(AudioCommon::UpdateDataHeader));
22 output_header.total_size = sizeof(AudioCommon::UpdateDataHeader);
23}
24
25InfoUpdater::~InfoUpdater() = default;
26
27bool InfoUpdater::UpdateBehaviorInfo(BehaviorInfo& in_behavior_info) {
28 if (input_header.size.behavior != sizeof(BehaviorInfo::InParams)) {
29 LOG_ERROR(Audio, "Behavior info is an invalid size, expecting 0x{:X} but got 0x{:X}",
30 sizeof(BehaviorInfo::InParams), input_header.size.behavior);
31 return false;
32 }
33
34 if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset,
35 sizeof(BehaviorInfo::InParams))) {
36 LOG_ERROR(Audio, "Buffer is an invalid size!");
37 return false;
38 }
39
40 BehaviorInfo::InParams behavior_in{};
41 std::memcpy(&behavior_in, in_params.data() + input_offset, sizeof(BehaviorInfo::InParams));
42 input_offset += sizeof(BehaviorInfo::InParams);
43
44 // Make sure it's an audio revision we can actually support
45 if (!AudioCommon::IsValidRevision(behavior_in.revision)) {
46 LOG_ERROR(Audio, "Invalid input revision, revision=0x{:08X}", behavior_in.revision);
47 return false;
48 }
49
50 // Make sure that our behavior info revision matches the input
51 if (in_behavior_info.GetUserRevision() != behavior_in.revision) {
52 LOG_ERROR(Audio,
53 "User revision differs from input revision, expecting 0x{:08X} but got 0x{:08X}",
54 in_behavior_info.GetUserRevision(), behavior_in.revision);
55 return false;
56 }
57
58 // Update behavior info flags
59 in_behavior_info.ClearError();
60 in_behavior_info.UpdateFlags(behavior_in.flags);
61
62 return true;
63}
64
65bool InfoUpdater::UpdateMemoryPools(std::vector<ServerMemoryPoolInfo>& memory_pool_info) {
66 const auto memory_pool_count = memory_pool_info.size();
67 const auto total_memory_pool_in = sizeof(ServerMemoryPoolInfo::InParams) * memory_pool_count;
68 const auto total_memory_pool_out = sizeof(ServerMemoryPoolInfo::OutParams) * memory_pool_count;
69
70 if (input_header.size.memory_pool != total_memory_pool_in) {
71 LOG_ERROR(Audio, "Memory pools are an invalid size, expecting 0x{:X} but got 0x{:X}",
72 total_memory_pool_in, input_header.size.memory_pool);
73 return false;
74 }
75
76 if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, total_memory_pool_in)) {
77 LOG_ERROR(Audio, "Buffer is an invalid size!");
78 return false;
79 }
80
81 std::vector<ServerMemoryPoolInfo::InParams> mempool_in(memory_pool_count);
82 std::vector<ServerMemoryPoolInfo::OutParams> mempool_out(memory_pool_count);
83
84 std::memcpy(mempool_in.data(), in_params.data() + input_offset, total_memory_pool_in);
85 input_offset += total_memory_pool_in;
86
87 // Update our memory pools
88 for (std::size_t i = 0; i < memory_pool_count; i++) {
89 if (!memory_pool_info[i].Update(mempool_in[i], mempool_out[i])) {
90 LOG_ERROR(Audio, "Failed to update memory pool {}!", i);
91 return false;
92 }
93 }
94
95 if (!AudioCommon::CanConsumeBuffer(out_params.size(), output_offset,
96 sizeof(BehaviorInfo::InParams))) {
97 LOG_ERROR(Audio, "Buffer is an invalid size!");
98 return false;
99 }
100
101 std::memcpy(out_params.data() + output_offset, mempool_out.data(), total_memory_pool_out);
102 output_offset += total_memory_pool_out;
103 output_header.size.memory_pool = static_cast<u32>(total_memory_pool_out);
104 return true;
105}
106
107bool InfoUpdater::UpdateVoiceChannelResources(VoiceContext& voice_context) {
108 const auto voice_count = voice_context.GetVoiceCount();
109 const auto voice_size = voice_count * sizeof(VoiceChannelResource::InParams);
110 std::vector<VoiceChannelResource::InParams> resources_in(voice_count);
111
112 if (input_header.size.voice_channel_resource != voice_size) {
113 LOG_ERROR(Audio, "VoiceChannelResource is an invalid size, expecting 0x{:X} but got 0x{:X}",
114 voice_size, input_header.size.voice_channel_resource);
115 return false;
116 }
117
118 if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, voice_size)) {
119 LOG_ERROR(Audio, "Buffer is an invalid size!");
120 return false;
121 }
122
123 std::memcpy(resources_in.data(), in_params.data() + input_offset, voice_size);
124 input_offset += voice_size;
125
126 // Update our channel resources
127 for (std::size_t i = 0; i < voice_count; i++) {
128 // Grab our channel resource
129 auto& resource = voice_context.GetChannelResource(i);
130 resource.Update(resources_in[i]);
131 }
132
133 return true;
134}
135
136bool InfoUpdater::UpdateVoices(VoiceContext& voice_context,
137 [[maybe_unused]] std::vector<ServerMemoryPoolInfo>& memory_pool_info,
138 [[maybe_unused]] VAddr audio_codec_dsp_addr) {
139 const auto voice_count = voice_context.GetVoiceCount();
140 std::vector<VoiceInfo::InParams> voice_in(voice_count);
141 std::vector<VoiceInfo::OutParams> voice_out(voice_count);
142
143 const auto voice_in_size = voice_count * sizeof(VoiceInfo::InParams);
144 const auto voice_out_size = voice_count * sizeof(VoiceInfo::OutParams);
145
146 if (input_header.size.voice != voice_in_size) {
147 LOG_ERROR(Audio, "Voices are an invalid size, expecting 0x{:X} but got 0x{:X}",
148 voice_in_size, input_header.size.voice);
149 return false;
150 }
151
152 if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, voice_in_size)) {
153 LOG_ERROR(Audio, "Buffer is an invalid size!");
154 return false;
155 }
156
157 std::memcpy(voice_in.data(), in_params.data() + input_offset, voice_in_size);
158 input_offset += voice_in_size;
159
160 // Set all voices to not be in use
161 for (std::size_t i = 0; i < voice_count; i++) {
162 voice_context.GetInfo(i).GetInParams().in_use = false;
163 }
164
165 // Update our voices
166 for (std::size_t i = 0; i < voice_count; i++) {
167 auto& voice_in_params = voice_in[i];
168 const auto channel_count = static_cast<std::size_t>(voice_in_params.channel_count);
169 // Skip if it's not currently in use
170 if (!voice_in_params.is_in_use) {
171 continue;
172 }
173 // Voice states for each channel
174 std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT> voice_states{};
175 ASSERT(static_cast<std::size_t>(voice_in_params.id) < voice_count);
176
177 // Grab our current voice info
178 auto& voice_info = voice_context.GetInfo(static_cast<std::size_t>(voice_in_params.id));
179
180 ASSERT(channel_count <= AudioCommon::MAX_CHANNEL_COUNT);
181
182 // Get all our channel voice states
183 for (std::size_t channel = 0; channel < channel_count; channel++) {
184 voice_states[channel] =
185 &voice_context.GetState(voice_in_params.voice_channel_resource_ids[channel]);
186 }
187
188 if (voice_in_params.is_new) {
189 // Default our values for our voice
190 voice_info.Initialize();
191
192 // Zero out our voice states
193 for (std::size_t channel = 0; channel < channel_count; channel++) {
194 std::memset(voice_states[channel], 0, sizeof(VoiceState));
195 }
196 }
197
198 // Update our voice
199 voice_info.UpdateParameters(voice_in_params, behavior_info);
200 // TODO(ogniK): Handle mapping errors with behavior info based on in params response
201
202 // Update our wave buffers
203 voice_info.UpdateWaveBuffers(voice_in_params, voice_states, behavior_info);
204 voice_info.WriteOutStatus(voice_out[i], voice_in_params, voice_states);
205 }
206
207 if (!AudioCommon::CanConsumeBuffer(out_params.size(), output_offset, voice_out_size)) {
208 LOG_ERROR(Audio, "Buffer is an invalid size!");
209 return false;
210 }
211 std::memcpy(out_params.data() + output_offset, voice_out.data(), voice_out_size);
212 output_offset += voice_out_size;
213 output_header.size.voice = static_cast<u32>(voice_out_size);
214 return true;
215}
216
217bool InfoUpdater::UpdateEffects(EffectContext& effect_context, bool is_active) {
218 const auto effect_count = effect_context.GetCount();
219 std::vector<EffectInfo::InParams> effect_in(effect_count);
220 std::vector<EffectInfo::OutParams> effect_out(effect_count);
221
222 const auto total_effect_in = effect_count * sizeof(EffectInfo::InParams);
223 const auto total_effect_out = effect_count * sizeof(EffectInfo::OutParams);
224
225 if (input_header.size.effect != total_effect_in) {
226 LOG_ERROR(Audio, "Effects are an invalid size, expecting 0x{:X} but got 0x{:X}",
227 total_effect_in, input_header.size.effect);
228 return false;
229 }
230
231 if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, total_effect_in)) {
232 LOG_ERROR(Audio, "Buffer is an invalid size!");
233 return false;
234 }
235
236 std::memcpy(effect_in.data(), in_params.data() + input_offset, total_effect_in);
237 input_offset += total_effect_in;
238
239 // Update effects
240 for (std::size_t i = 0; i < effect_count; i++) {
241 auto* info = effect_context.GetInfo(i);
242 if (effect_in[i].type != info->GetType()) {
243 info = effect_context.RetargetEffect(i, effect_in[i].type);
244 }
245
246 info->Update(effect_in[i]);
247
248 if ((!is_active && info->GetUsage() != UsageState::Initialized) ||
249 info->GetUsage() == UsageState::Stopped) {
250 effect_out[i].status = UsageStatus::Removed;
251 } else {
252 effect_out[i].status = UsageStatus::Used;
253 }
254 }
255
256 if (!AudioCommon::CanConsumeBuffer(out_params.size(), output_offset, total_effect_out)) {
257 LOG_ERROR(Audio, "Buffer is an invalid size!");
258 return false;
259 }
260
261 std::memcpy(out_params.data() + output_offset, effect_out.data(), total_effect_out);
262 output_offset += total_effect_out;
263 output_header.size.effect = static_cast<u32>(total_effect_out);
264
265 return true;
266}
267
268bool InfoUpdater::UpdateSplitterInfo(SplitterContext& splitter_context) {
269 std::size_t start_offset = input_offset;
270 std::size_t bytes_read{};
271 // Update splitter context
272 if (!splitter_context.Update(in_params, input_offset, bytes_read)) {
273 LOG_ERROR(Audio, "Failed to update splitter context!");
274 return false;
275 }
276
277 const auto consumed = input_offset - start_offset;
278
279 if (input_header.size.splitter != consumed) {
280 LOG_ERROR(Audio, "Splitters is an invalid size, expecting 0x{:X} but got 0x{:X}",
281 bytes_read, input_header.size.splitter);
282 return false;
283 }
284
285 return true;
286}
287
288Result InfoUpdater::UpdateMixes(MixContext& mix_context, std::size_t mix_buffer_count,
289 SplitterContext& splitter_context, EffectContext& effect_context) {
290 std::vector<MixInfo::InParams> mix_in_params;
291
292 if (!behavior_info.IsMixInParameterDirtyOnlyUpdateSupported()) {
293 // If we're not dirty, get ALL mix in parameters
294 const auto context_mix_count = mix_context.GetCount();
295 const auto total_mix_in = context_mix_count * sizeof(MixInfo::InParams);
296 if (input_header.size.mixer != total_mix_in) {
297 LOG_ERROR(Audio, "Mixer is an invalid size, expecting 0x{:X} but got 0x{:X}",
298 total_mix_in, input_header.size.mixer);
299 return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
300 }
301
302 if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, total_mix_in)) {
303 LOG_ERROR(Audio, "Buffer is an invalid size!");
304 return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
305 }
306
307 mix_in_params.resize(context_mix_count);
308 std::memcpy(mix_in_params.data(), in_params.data() + input_offset, total_mix_in);
309
310 input_offset += total_mix_in;
311 } else {
312 // Only update the "dirty" mixes
313 MixInfo::DirtyHeader dirty_header{};
314 if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset,
315 sizeof(MixInfo::DirtyHeader))) {
316 LOG_ERROR(Audio, "Buffer is an invalid size!");
317 return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
318 }
319
320 std::memcpy(&dirty_header, in_params.data() + input_offset, sizeof(MixInfo::DirtyHeader));
321 input_offset += sizeof(MixInfo::DirtyHeader);
322
323 const auto total_mix_in =
324 dirty_header.mixer_count * sizeof(MixInfo::InParams) + sizeof(MixInfo::DirtyHeader);
325
326 if (input_header.size.mixer != total_mix_in) {
327 LOG_ERROR(Audio, "Mixer is an invalid size, expecting 0x{:X} but got 0x{:X}",
328 total_mix_in, input_header.size.mixer);
329 return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
330 }
331
332 if (dirty_header.mixer_count != 0) {
333 mix_in_params.resize(dirty_header.mixer_count);
334 std::memcpy(mix_in_params.data(), in_params.data() + input_offset,
335 mix_in_params.size() * sizeof(MixInfo::InParams));
336 input_offset += mix_in_params.size() * sizeof(MixInfo::InParams);
337 }
338 }
339
340 // Get our total input count
341 const auto mix_count = mix_in_params.size();
342
343 if (!behavior_info.IsMixInParameterDirtyOnlyUpdateSupported()) {
344 // Only verify our buffer count if we're not dirty
345 std::size_t total_buffer_count{};
346 for (std::size_t i = 0; i < mix_count; i++) {
347 const auto& in = mix_in_params[i];
348 total_buffer_count += in.buffer_count;
349 if (static_cast<std::size_t>(in.dest_mix_id) > mix_count &&
350 in.dest_mix_id != AudioCommon::NO_MIX && in.mix_id != AudioCommon::FINAL_MIX) {
351 LOG_ERROR(
352 Audio,
353 "Invalid mix destination, mix_id={:X}, dest_mix_id={:X}, mix_buffer_count={:X}",
354 in.mix_id, in.dest_mix_id, mix_buffer_count);
355 return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
356 }
357 }
358
359 if (total_buffer_count > mix_buffer_count) {
360 LOG_ERROR(Audio,
361 "Too many mix buffers used! mix_buffer_count={:X}, requesting_buffers={:X}",
362 mix_buffer_count, total_buffer_count);
363 return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
364 }
365 }
366
367 if (mix_buffer_count == 0) {
368 LOG_ERROR(Audio, "No mix buffers!");
369 return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
370 }
371
372 bool should_sort = false;
373 for (std::size_t i = 0; i < mix_count; i++) {
374 const auto& mix_in = mix_in_params[i];
375 std::size_t target_mix{};
376 if (behavior_info.IsMixInParameterDirtyOnlyUpdateSupported()) {
377 target_mix = mix_in.mix_id;
378 } else {
379 // Non dirty supported games just use i instead of the actual mix_id
380 target_mix = i;
381 }
382 auto& mix_info = mix_context.GetInfo(target_mix);
383 auto& mix_info_params = mix_info.GetInParams();
384 if (mix_info_params.in_use != mix_in.in_use) {
385 mix_info_params.in_use = mix_in.in_use;
386 mix_info.ResetEffectProcessingOrder();
387 should_sort = true;
388 }
389
390 if (mix_in.in_use) {
391 should_sort |= mix_info.Update(mix_context.GetEdgeMatrix(), mix_in, behavior_info,
392 splitter_context, effect_context);
393 }
394 }
395
396 if (should_sort && behavior_info.IsSplitterSupported()) {
397 // Sort our splitter data
398 if (!mix_context.TsortInfo(splitter_context)) {
399 return AudioCommon::Audren::ERR_SPLITTER_SORT_FAILED;
400 }
401 }
402
403 // TODO(ogniK): Sort when splitter is suppoorted
404
405 return ResultSuccess;
406}
407
408bool InfoUpdater::UpdateSinks(SinkContext& sink_context) {
409 const auto sink_count = sink_context.GetCount();
410 std::vector<SinkInfo::InParams> sink_in_params(sink_count);
411 const auto total_sink_in = sink_count * sizeof(SinkInfo::InParams);
412
413 if (input_header.size.sink != total_sink_in) {
414 LOG_ERROR(Audio, "Sinks are an invalid size, expecting 0x{:X} but got 0x{:X}",
415 total_sink_in, input_header.size.effect);
416 return false;
417 }
418
419 if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, total_sink_in)) {
420 LOG_ERROR(Audio, "Buffer is an invalid size!");
421 return false;
422 }
423
424 std::memcpy(sink_in_params.data(), in_params.data() + input_offset, total_sink_in);
425 input_offset += total_sink_in;
426
427 // TODO(ogniK): Properly update sinks
428 if (!sink_in_params.empty()) {
429 sink_context.UpdateMainSink(sink_in_params[0]);
430 }
431
432 output_header.size.sink = static_cast<u32>(0x20 * sink_count);
433 output_offset += 0x20 * sink_count;
434 return true;
435}
436
437bool InfoUpdater::UpdatePerformanceBuffer() {
438 output_header.size.performance = 0x10;
439 output_offset += 0x10;
440 return true;
441}
442
443bool InfoUpdater::UpdateErrorInfo([[maybe_unused]] BehaviorInfo& in_behavior_info) {
444 const auto total_beahvior_info_out = sizeof(BehaviorInfo::OutParams);
445
446 if (!AudioCommon::CanConsumeBuffer(out_params.size(), output_offset, total_beahvior_info_out)) {
447 LOG_ERROR(Audio, "Buffer is an invalid size!");
448 return false;
449 }
450
451 BehaviorInfo::OutParams behavior_info_out{};
452 behavior_info.CopyErrorInfo(behavior_info_out);
453
454 std::memcpy(out_params.data() + output_offset, &behavior_info_out, total_beahvior_info_out);
455 output_offset += total_beahvior_info_out;
456 output_header.size.behavior = total_beahvior_info_out;
457
458 return true;
459}
460
461struct RendererInfo {
462 u64_le elasped_frame_count{};
463 INSERT_PADDING_WORDS(2);
464};
465static_assert(sizeof(RendererInfo) == 0x10, "RendererInfo is an invalid size");
466
467bool InfoUpdater::UpdateRendererInfo(std::size_t elapsed_frame_count) {
468 const auto total_renderer_info_out = sizeof(RendererInfo);
469 if (!AudioCommon::CanConsumeBuffer(out_params.size(), output_offset, total_renderer_info_out)) {
470 LOG_ERROR(Audio, "Buffer is an invalid size!");
471 return false;
472 }
473 RendererInfo out{};
474 out.elasped_frame_count = elapsed_frame_count;
475 std::memcpy(out_params.data() + output_offset, &out, total_renderer_info_out);
476 output_offset += total_renderer_info_out;
477 output_header.size.render_info = total_renderer_info_out;
478
479 return true;
480}
481
482bool InfoUpdater::CheckConsumedSize() const {
483 if (output_offset != out_params.size()) {
484 LOG_ERROR(Audio, "Output is not consumed! Consumed {}, but requires {}. {} bytes remaining",
485 output_offset, out_params.size(), out_params.size() - output_offset);
486 return false;
487 }
488 /*if (input_offset != in_params.size()) {
489 LOG_ERROR(Audio, "Input is not consumed!");
490 return false;
491 }*/
492 return true;
493}
494
495bool InfoUpdater::WriteOutputHeader() {
496 if (!AudioCommon::CanConsumeBuffer(out_params.size(), 0,
497 sizeof(AudioCommon::UpdateDataHeader))) {
498 LOG_ERROR(Audio, "Buffer is an invalid size!");
499 return false;
500 }
501 output_header.revision = AudioCommon::CURRENT_PROCESS_REVISION;
502 const auto& sz = output_header.size;
503 output_header.total_size += sz.behavior + sz.memory_pool + sz.voice +
504 sz.voice_channel_resource + sz.effect + sz.mixer + sz.sink +
505 sz.performance + sz.splitter + sz.render_info;
506
507 std::memcpy(out_params.data(), &output_header, sizeof(AudioCommon::UpdateDataHeader));
508 return true;
509}
510
511} // namespace AudioCore
diff --git a/src/audio_core/info_updater.h b/src/audio_core/info_updater.h
deleted file mode 100644
index 17e66b036..000000000
--- a/src/audio_core/info_updater.h
+++ /dev/null
@@ -1,57 +0,0 @@
1// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <vector>
7#include "audio_core/common.h"
8#include "common/common_types.h"
9
10namespace AudioCore {
11
12class BehaviorInfo;
13class ServerMemoryPoolInfo;
14class VoiceContext;
15class EffectContext;
16class MixContext;
17class SinkContext;
18class SplitterContext;
19
20class InfoUpdater {
21public:
22 // TODO(ogniK): Pass process handle when we support it
23 InfoUpdater(const std::vector<u8>& in_params_, std::vector<u8>& out_params_,
24 BehaviorInfo& behavior_info_);
25 ~InfoUpdater();
26
27 bool UpdateBehaviorInfo(BehaviorInfo& in_behavior_info);
28 bool UpdateMemoryPools(std::vector<ServerMemoryPoolInfo>& memory_pool_info);
29 bool UpdateVoiceChannelResources(VoiceContext& voice_context);
30 bool UpdateVoices(VoiceContext& voice_context,
31 std::vector<ServerMemoryPoolInfo>& memory_pool_info,
32 VAddr audio_codec_dsp_addr);
33 bool UpdateEffects(EffectContext& effect_context, bool is_active);
34 bool UpdateSplitterInfo(SplitterContext& splitter_context);
35 Result UpdateMixes(MixContext& mix_context, std::size_t mix_buffer_count,
36 SplitterContext& splitter_context, EffectContext& effect_context);
37 bool UpdateSinks(SinkContext& sink_context);
38 bool UpdatePerformanceBuffer();
39 bool UpdateErrorInfo(BehaviorInfo& in_behavior_info);
40 bool UpdateRendererInfo(std::size_t elapsed_frame_count);
41 bool CheckConsumedSize() const;
42
43 bool WriteOutputHeader();
44
45private:
46 const std::vector<u8>& in_params;
47 std::vector<u8>& out_params;
48 BehaviorInfo& behavior_info;
49
50 AudioCommon::UpdateDataHeader input_header{};
51 AudioCommon::UpdateDataHeader output_header{};
52
53 std::size_t input_offset{sizeof(AudioCommon::UpdateDataHeader)};
54 std::size_t output_offset{sizeof(AudioCommon::UpdateDataHeader)};
55};
56
57} // namespace AudioCore
diff --git a/src/audio_core/memory_pool.cpp b/src/audio_core/memory_pool.cpp
deleted file mode 100644
index 627e5f15e..000000000
--- a/src/audio_core/memory_pool.cpp
+++ /dev/null
@@ -1,60 +0,0 @@
1
2// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
3// SPDX-License-Identifier: GPL-2.0-or-later
4
5#include "audio_core/memory_pool.h"
6#include "common/logging/log.h"
7
8namespace AudioCore {
9
10ServerMemoryPoolInfo::ServerMemoryPoolInfo() = default;
11ServerMemoryPoolInfo::~ServerMemoryPoolInfo() = default;
12
13bool ServerMemoryPoolInfo::Update(const InParams& in_params, OutParams& out_params) {
14 // Our state does not need to be changed
15 if (in_params.state != State::RequestAttach && in_params.state != State::RequestDetach) {
16 return true;
17 }
18
19 // Address or size is null
20 if (in_params.address == 0 || in_params.size == 0) {
21 LOG_ERROR(Audio, "Memory pool address or size is zero! address={:X}, size={:X}",
22 in_params.address, in_params.size);
23 return false;
24 }
25
26 // Address or size is not aligned
27 if ((in_params.address % 0x1000) != 0 || (in_params.size % 0x1000) != 0) {
28 LOG_ERROR(Audio, "Memory pool address or size is not aligned! address={:X}, size={:X}",
29 in_params.address, in_params.size);
30 return false;
31 }
32
33 if (in_params.state == State::RequestAttach) {
34 cpu_address = in_params.address;
35 size = in_params.size;
36 used = true;
37 out_params.state = State::Attached;
38 } else {
39 // Unexpected address
40 if (cpu_address != in_params.address) {
41 LOG_ERROR(Audio, "Memory pool address differs! Expecting {:X} but address is {:X}",
42 cpu_address, in_params.address);
43 return false;
44 }
45
46 if (size != in_params.size) {
47 LOG_ERROR(Audio, "Memory pool size differs! Expecting {:X} but size is {:X}", size,
48 in_params.size);
49 return false;
50 }
51
52 cpu_address = 0;
53 size = 0;
54 used = false;
55 out_params.state = State::Detached;
56 }
57 return true;
58}
59
60} // namespace AudioCore
diff --git a/src/audio_core/memory_pool.h b/src/audio_core/memory_pool.h
deleted file mode 100644
index e71bc025b..000000000
--- a/src/audio_core/memory_pool.h
+++ /dev/null
@@ -1,51 +0,0 @@
1// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "common/common_funcs.h"
7#include "common/common_types.h"
8#include "common/swap.h"
9
10namespace AudioCore {
11
12class ServerMemoryPoolInfo {
13public:
14 ServerMemoryPoolInfo();
15 ~ServerMemoryPoolInfo();
16
17 enum class State : u32_le {
18 Invalid = 0x0,
19 Aquired = 0x1,
20 RequestDetach = 0x2,
21 Detached = 0x3,
22 RequestAttach = 0x4,
23 Attached = 0x5,
24 Released = 0x6,
25 };
26
27 struct InParams {
28 u64_le address{};
29 u64_le size{};
30 State state{};
31 INSERT_PADDING_WORDS(3);
32 };
33 static_assert(sizeof(InParams) == 0x20, "InParams are an invalid size");
34
35 struct OutParams {
36 State state{};
37 INSERT_PADDING_WORDS(3);
38 };
39 static_assert(sizeof(OutParams) == 0x10, "OutParams are an invalid size");
40
41 bool Update(const InParams& in_params, OutParams& out_params);
42
43private:
44 // There's another entry here which is the DSP address, however since we're not talking to the
45 // DSP we can just use the same address provided by the guest without needing to remap
46 u64_le cpu_address{};
47 u64_le size{};
48 bool used{};
49};
50
51} // namespace AudioCore
diff --git a/src/audio_core/mix_context.cpp b/src/audio_core/mix_context.cpp
deleted file mode 100644
index bcaa7afab..000000000
--- a/src/audio_core/mix_context.cpp
+++ /dev/null
@@ -1,297 +0,0 @@
1// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <algorithm>
5
6#include "audio_core/behavior_info.h"
7#include "audio_core/common.h"
8#include "audio_core/effect_context.h"
9#include "audio_core/mix_context.h"
10#include "audio_core/splitter_context.h"
11
12namespace AudioCore {
13MixContext::MixContext() = default;
14MixContext::~MixContext() = default;
15
16void MixContext::Initialize(const BehaviorInfo& behavior_info, std::size_t mix_count,
17 std::size_t effect_count) {
18 info_count = mix_count;
19 infos.resize(info_count);
20 auto& final_mix = GetInfo(AudioCommon::FINAL_MIX);
21 final_mix.GetInParams().mix_id = AudioCommon::FINAL_MIX;
22 sorted_info.reserve(infos.size());
23 for (auto& info : infos) {
24 sorted_info.push_back(&info);
25 }
26
27 for (auto& info : infos) {
28 info.SetEffectCount(effect_count);
29 }
30
31 // Only initialize our edge matrix and node states if splitters are supported
32 if (behavior_info.IsSplitterSupported()) {
33 node_states.Initialize(mix_count);
34 edge_matrix.Initialize(mix_count);
35 }
36}
37
38void MixContext::UpdateDistancesFromFinalMix() {
39 // Set all distances to be invalid
40 for (std::size_t i = 0; i < info_count; i++) {
41 GetInfo(i).GetInParams().final_mix_distance = AudioCommon::NO_FINAL_MIX;
42 }
43
44 for (std::size_t i = 0; i < info_count; i++) {
45 auto& info = GetInfo(i);
46 auto& in_params = info.GetInParams();
47 // Populate our sorted info
48 sorted_info[i] = &info;
49
50 if (!in_params.in_use) {
51 continue;
52 }
53
54 auto mix_id = in_params.mix_id;
55 // Needs to be referenced out of scope
56 s32 distance_to_final_mix{AudioCommon::FINAL_MIX};
57 for (; distance_to_final_mix < static_cast<s32>(info_count); distance_to_final_mix++) {
58 if (mix_id == AudioCommon::FINAL_MIX) {
59 // If we're at the final mix, we're done
60 break;
61 } else if (mix_id == AudioCommon::NO_MIX) {
62 // If we have no more mix ids, we're done
63 distance_to_final_mix = AudioCommon::NO_FINAL_MIX;
64 break;
65 } else {
66 const auto& dest_mix = GetInfo(mix_id);
67 const auto dest_mix_distance = dest_mix.GetInParams().final_mix_distance;
68
69 if (dest_mix_distance == AudioCommon::NO_FINAL_MIX) {
70 // If our current mix isn't pointing to a final mix, follow through
71 mix_id = dest_mix.GetInParams().dest_mix_id;
72 } else {
73 // Our current mix + 1 = final distance
74 distance_to_final_mix = dest_mix_distance + 1;
75 break;
76 }
77 }
78 }
79
80 // If we're out of range for our distance, mark it as no final mix
81 if (distance_to_final_mix >= static_cast<s32>(info_count)) {
82 distance_to_final_mix = AudioCommon::NO_FINAL_MIX;
83 }
84
85 in_params.final_mix_distance = distance_to_final_mix;
86 }
87}
88
89void MixContext::CalcMixBufferOffset() {
90 s32 offset{};
91 for (std::size_t i = 0; i < info_count; i++) {
92 auto& info = GetSortedInfo(i);
93 auto& in_params = info.GetInParams();
94 if (in_params.in_use) {
95 // Only update if in use
96 in_params.buffer_offset = offset;
97 offset += in_params.buffer_count;
98 }
99 }
100}
101
102void MixContext::SortInfo() {
103 // Get the distance to the final mix
104 UpdateDistancesFromFinalMix();
105
106 // Sort based on the distance to the final mix
107 std::sort(sorted_info.begin(), sorted_info.end(),
108 [](const ServerMixInfo* lhs, const ServerMixInfo* rhs) {
109 return lhs->GetInParams().final_mix_distance >
110 rhs->GetInParams().final_mix_distance;
111 });
112
113 // Calculate the mix buffer offset
114 CalcMixBufferOffset();
115}
116
117bool MixContext::TsortInfo(SplitterContext& splitter_context) {
118 // If we're not using mixes, just calculate the mix buffer offset
119 if (!splitter_context.UsingSplitter()) {
120 CalcMixBufferOffset();
121 return true;
122 }
123 // Sort our node states
124 if (!node_states.Tsort(edge_matrix)) {
125 return false;
126 }
127
128 // Get our sorted list
129 const auto sorted_list = node_states.GetIndexList();
130 std::size_t info_id{};
131 for (auto itr = sorted_list.rbegin(); itr != sorted_list.rend(); ++itr) {
132 // Set our sorted info
133 sorted_info[info_id++] = &GetInfo(*itr);
134 }
135
136 // Calculate the mix buffer offset
137 CalcMixBufferOffset();
138 return true;
139}
140
141std::size_t MixContext::GetCount() const {
142 return info_count;
143}
144
145ServerMixInfo& MixContext::GetInfo(std::size_t i) {
146 ASSERT(i < info_count);
147 return infos.at(i);
148}
149
150const ServerMixInfo& MixContext::GetInfo(std::size_t i) const {
151 ASSERT(i < info_count);
152 return infos.at(i);
153}
154
155ServerMixInfo& MixContext::GetSortedInfo(std::size_t i) {
156 ASSERT(i < info_count);
157 return *sorted_info.at(i);
158}
159
160const ServerMixInfo& MixContext::GetSortedInfo(std::size_t i) const {
161 ASSERT(i < info_count);
162 return *sorted_info.at(i);
163}
164
165ServerMixInfo& MixContext::GetFinalMixInfo() {
166 return infos.at(AudioCommon::FINAL_MIX);
167}
168
169const ServerMixInfo& MixContext::GetFinalMixInfo() const {
170 return infos.at(AudioCommon::FINAL_MIX);
171}
172
173EdgeMatrix& MixContext::GetEdgeMatrix() {
174 return edge_matrix;
175}
176
177const EdgeMatrix& MixContext::GetEdgeMatrix() const {
178 return edge_matrix;
179}
180
181ServerMixInfo::ServerMixInfo() {
182 Cleanup();
183}
184ServerMixInfo::~ServerMixInfo() = default;
185
186const ServerMixInfo::InParams& ServerMixInfo::GetInParams() const {
187 return in_params;
188}
189
190ServerMixInfo::InParams& ServerMixInfo::GetInParams() {
191 return in_params;
192}
193
194bool ServerMixInfo::Update(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix_in,
195 BehaviorInfo& behavior_info, SplitterContext& splitter_context,
196 EffectContext& effect_context) {
197 in_params.volume = mix_in.volume;
198 in_params.sample_rate = mix_in.sample_rate;
199 in_params.buffer_count = mix_in.buffer_count;
200 in_params.in_use = mix_in.in_use;
201 in_params.mix_id = mix_in.mix_id;
202 in_params.node_id = mix_in.node_id;
203 for (std::size_t i = 0; i < mix_in.mix_volume.size(); i++) {
204 std::copy(mix_in.mix_volume[i].begin(), mix_in.mix_volume[i].end(),
205 in_params.mix_volume[i].begin());
206 }
207
208 bool require_sort = false;
209
210 if (behavior_info.IsSplitterSupported()) {
211 require_sort = UpdateConnection(edge_matrix, mix_in, splitter_context);
212 } else {
213 in_params.dest_mix_id = mix_in.dest_mix_id;
214 in_params.splitter_id = AudioCommon::NO_SPLITTER;
215 }
216
217 ResetEffectProcessingOrder();
218 const auto effect_count = effect_context.GetCount();
219 for (std::size_t i = 0; i < effect_count; i++) {
220 auto* effect_info = effect_context.GetInfo(i);
221 if (effect_info->GetMixID() == in_params.mix_id) {
222 effect_processing_order[effect_info->GetProcessingOrder()] = static_cast<s32>(i);
223 }
224 }
225
226 // TODO(ogniK): Update effect processing order
227 return require_sort;
228}
229
230bool ServerMixInfo::HasAnyConnection() const {
231 return in_params.splitter_id != AudioCommon::NO_SPLITTER ||
232 in_params.mix_id != AudioCommon::NO_MIX;
233}
234
235void ServerMixInfo::Cleanup() {
236 in_params.volume = 0.0f;
237 in_params.sample_rate = 0;
238 in_params.buffer_count = 0;
239 in_params.in_use = false;
240 in_params.mix_id = AudioCommon::NO_MIX;
241 in_params.node_id = 0;
242 in_params.buffer_offset = 0;
243 in_params.dest_mix_id = AudioCommon::NO_MIX;
244 in_params.splitter_id = AudioCommon::NO_SPLITTER;
245 std::memset(in_params.mix_volume.data(), 0, sizeof(float) * in_params.mix_volume.size());
246}
247
248void ServerMixInfo::SetEffectCount(std::size_t count) {
249 effect_processing_order.resize(count);
250 ResetEffectProcessingOrder();
251}
252
253void ServerMixInfo::ResetEffectProcessingOrder() {
254 for (auto& order : effect_processing_order) {
255 order = AudioCommon::NO_EFFECT_ORDER;
256 }
257}
258
259s32 ServerMixInfo::GetEffectOrder(std::size_t i) const {
260 return effect_processing_order.at(i);
261}
262
263bool ServerMixInfo::UpdateConnection(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix_in,
264 SplitterContext& splitter_context) {
265 // Mixes are identical
266 if (in_params.dest_mix_id == mix_in.dest_mix_id &&
267 in_params.splitter_id == mix_in.splitter_id &&
268 ((in_params.splitter_id == AudioCommon::NO_SPLITTER) ||
269 !splitter_context.GetInfo(in_params.splitter_id).HasNewConnection())) {
270 return false;
271 }
272 // Remove current edges for mix id
273 edge_matrix.RemoveEdges(in_params.mix_id);
274 if (mix_in.dest_mix_id != AudioCommon::NO_MIX) {
275 // If we have a valid destination mix id, set our edge matrix
276 edge_matrix.Connect(in_params.mix_id, mix_in.dest_mix_id);
277 } else if (mix_in.splitter_id != AudioCommon::NO_SPLITTER) {
278 // Recurse our splitter linked and set our edges
279 auto& splitter_info = splitter_context.GetInfo(mix_in.splitter_id);
280 const auto length = splitter_info.GetLength();
281 for (s32 i = 0; i < length; i++) {
282 const auto* splitter_destination =
283 splitter_context.GetDestinationData(mix_in.splitter_id, i);
284 if (splitter_destination == nullptr) {
285 continue;
286 }
287 if (splitter_destination->ValidMixId()) {
288 edge_matrix.Connect(in_params.mix_id, splitter_destination->GetMixId());
289 }
290 }
291 }
292 in_params.dest_mix_id = mix_in.dest_mix_id;
293 in_params.splitter_id = mix_in.splitter_id;
294 return true;
295}
296
297} // namespace AudioCore
diff --git a/src/audio_core/mix_context.h b/src/audio_core/mix_context.h
deleted file mode 100644
index 3939c77e9..000000000
--- a/src/audio_core/mix_context.h
+++ /dev/null
@@ -1,113 +0,0 @@
1// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7#include <vector>
8#include "audio_core/common.h"
9#include "audio_core/splitter_context.h"
10#include "common/common_funcs.h"
11#include "common/common_types.h"
12
13namespace AudioCore {
14class BehaviorInfo;
15class EffectContext;
16
17class MixInfo {
18public:
19 struct DirtyHeader {
20 u32_le magic{};
21 u32_le mixer_count{};
22 INSERT_PADDING_BYTES(0x18);
23 };
24 static_assert(sizeof(DirtyHeader) == 0x20, "MixInfo::DirtyHeader is an invalid size");
25
26 struct InParams {
27 float_le volume{};
28 s32_le sample_rate{};
29 s32_le buffer_count{};
30 bool in_use{};
31 INSERT_PADDING_BYTES(3);
32 s32_le mix_id{};
33 s32_le effect_count{};
34 u32_le node_id{};
35 INSERT_PADDING_WORDS(2);
36 std::array<std::array<float_le, AudioCommon::MAX_MIX_BUFFERS>, AudioCommon::MAX_MIX_BUFFERS>
37 mix_volume{};
38 s32_le dest_mix_id{};
39 s32_le splitter_id{};
40 INSERT_PADDING_WORDS(1);
41 };
42 static_assert(sizeof(MixInfo::InParams) == 0x930, "MixInfo::InParams is an invalid size");
43};
44
45class ServerMixInfo {
46public:
47 struct InParams {
48 float volume{};
49 s32 sample_rate{};
50 s32 buffer_count{};
51 bool in_use{};
52 s32 mix_id{};
53 u32 node_id{};
54 std::array<std::array<float_le, AudioCommon::MAX_MIX_BUFFERS>, AudioCommon::MAX_MIX_BUFFERS>
55 mix_volume{};
56 s32 dest_mix_id{};
57 s32 splitter_id{};
58 s32 buffer_offset{};
59 s32 final_mix_distance{};
60 };
61 ServerMixInfo();
62 ~ServerMixInfo();
63
64 [[nodiscard]] const ServerMixInfo::InParams& GetInParams() const;
65 [[nodiscard]] ServerMixInfo::InParams& GetInParams();
66
67 bool Update(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix_in,
68 BehaviorInfo& behavior_info, SplitterContext& splitter_context,
69 EffectContext& effect_context);
70 [[nodiscard]] bool HasAnyConnection() const;
71 void Cleanup();
72 void SetEffectCount(std::size_t count);
73 void ResetEffectProcessingOrder();
74 [[nodiscard]] s32 GetEffectOrder(std::size_t i) const;
75
76private:
77 std::vector<s32> effect_processing_order;
78 InParams in_params{};
79 bool UpdateConnection(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix_in,
80 SplitterContext& splitter_context);
81};
82
83class MixContext {
84public:
85 MixContext();
86 ~MixContext();
87
88 void Initialize(const BehaviorInfo& behavior_info, std::size_t mix_count,
89 std::size_t effect_count);
90 void SortInfo();
91 bool TsortInfo(SplitterContext& splitter_context);
92
93 [[nodiscard]] std::size_t GetCount() const;
94 [[nodiscard]] ServerMixInfo& GetInfo(std::size_t i);
95 [[nodiscard]] const ServerMixInfo& GetInfo(std::size_t i) const;
96 [[nodiscard]] ServerMixInfo& GetSortedInfo(std::size_t i);
97 [[nodiscard]] const ServerMixInfo& GetSortedInfo(std::size_t i) const;
98 [[nodiscard]] ServerMixInfo& GetFinalMixInfo();
99 [[nodiscard]] const ServerMixInfo& GetFinalMixInfo() const;
100 [[nodiscard]] EdgeMatrix& GetEdgeMatrix();
101 [[nodiscard]] const EdgeMatrix& GetEdgeMatrix() const;
102
103private:
104 void CalcMixBufferOffset();
105 void UpdateDistancesFromFinalMix();
106
107 NodeStates node_states{};
108 EdgeMatrix edge_matrix{};
109 std::size_t info_count{};
110 std::vector<ServerMixInfo> infos{};
111 std::vector<ServerMixInfo*> sorted_info{};
112};
113} // namespace AudioCore
diff --git a/src/audio_core/null_sink.h b/src/audio_core/null_sink.h
deleted file mode 100644
index 37b2f7eff..000000000
--- a/src/audio_core/null_sink.h
+++ /dev/null
@@ -1,32 +0,0 @@
1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "audio_core/sink.h"
7
8namespace AudioCore {
9
10class NullSink final : public Sink {
11public:
12 explicit NullSink(std::string_view) {}
13 ~NullSink() override = default;
14
15 SinkStream& AcquireSinkStream(u32 /*sample_rate*/, u32 /*num_channels*/,
16 const std::string& /*name*/) override {
17 return null_sink_stream;
18 }
19
20private:
21 struct NullSinkStreamImpl final : SinkStream {
22 void EnqueueSamples(u32 /*num_channels*/, const std::vector<s16>& /*samples*/) override {}
23
24 std::size_t SamplesInQueue(u32 /*num_channels*/) const override {
25 return 0;
26 }
27
28 void Flush() override {}
29 } null_sink_stream;
30};
31
32} // namespace AudioCore
diff --git a/src/audio_core/out/audio_out.cpp b/src/audio_core/out/audio_out.cpp
new file mode 100644
index 000000000..9a8d8a742
--- /dev/null
+++ b/src/audio_core/out/audio_out.cpp
@@ -0,0 +1,100 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/audio_out_manager.h"
5#include "audio_core/out/audio_out.h"
6#include "core/hle/kernel/k_event.h"
7
8namespace AudioCore::AudioOut {
9
10Out::Out(Core::System& system_, Manager& manager_, Kernel::KEvent* event_, size_t session_id_)
11 : manager{manager_}, parent_mutex{manager.mutex}, event{event_}, system{system_, event,
12 session_id_} {}
13
14void Out::Free() {
15 std::scoped_lock l{parent_mutex};
16 manager.ReleaseSessionId(system.GetSessionId());
17}
18
19System& Out::GetSystem() {
20 return system;
21}
22
23AudioOut::State Out::GetState() {
24 std::scoped_lock l{parent_mutex};
25 return system.GetState();
26}
27
28Result Out::StartSystem() {
29 std::scoped_lock l{parent_mutex};
30 return system.Start();
31}
32
33void Out::StartSession() {
34 std::scoped_lock l{parent_mutex};
35 system.StartSession();
36}
37
38Result Out::StopSystem() {
39 std::scoped_lock l{parent_mutex};
40 return system.Stop();
41}
42
43Result Out::AppendBuffer(const AudioOutBuffer& buffer, const u64 tag) {
44 std::scoped_lock l{parent_mutex};
45
46 if (system.AppendBuffer(buffer, tag)) {
47 return ResultSuccess;
48 }
49 return Service::Audio::ERR_BUFFER_COUNT_EXCEEDED;
50}
51
52void Out::ReleaseAndRegisterBuffers() {
53 std::scoped_lock l{parent_mutex};
54 if (system.GetState() == State::Started) {
55 system.ReleaseBuffers();
56 system.RegisterBuffers();
57 }
58}
59
60bool Out::FlushAudioOutBuffers() {
61 std::scoped_lock l{parent_mutex};
62 return system.FlushAudioOutBuffers();
63}
64
65u32 Out::GetReleasedBuffers(std::span<u64> tags) {
66 std::scoped_lock l{parent_mutex};
67 return system.GetReleasedBuffers(tags);
68}
69
70Kernel::KReadableEvent& Out::GetBufferEvent() {
71 std::scoped_lock l{parent_mutex};
72 return event->GetReadableEvent();
73}
74
75f32 Out::GetVolume() {
76 std::scoped_lock l{parent_mutex};
77 return system.GetVolume();
78}
79
80void Out::SetVolume(const f32 volume) {
81 std::scoped_lock l{parent_mutex};
82 system.SetVolume(volume);
83}
84
85bool Out::ContainsAudioBuffer(const u64 tag) {
86 std::scoped_lock l{parent_mutex};
87 return system.ContainsAudioBuffer(tag);
88}
89
90u32 Out::GetBufferCount() {
91 std::scoped_lock l{parent_mutex};
92 return system.GetBufferCount();
93}
94
95u64 Out::GetPlayedSampleCount() {
96 std::scoped_lock l{parent_mutex};
97 return system.GetPlayedSampleCount();
98}
99
100} // namespace AudioCore::AudioOut
diff --git a/src/audio_core/out/audio_out.h b/src/audio_core/out/audio_out.h
new file mode 100644
index 000000000..f6b921645
--- /dev/null
+++ b/src/audio_core/out/audio_out.h
@@ -0,0 +1,147 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <mutex>
7
8#include "audio_core/out/audio_out_system.h"
9
10namespace Core {
11class System;
12}
13
14namespace Kernel {
15class KEvent;
16class KReadableEvent;
17} // namespace Kernel
18
19namespace AudioCore::AudioOut {
20class Manager;
21
22/**
23 * Interface between the service and audio out system. Mainly responsible for forwarding service
24 * calls to the system.
25 */
26class Out {
27public:
28 explicit Out(Core::System& system, Manager& manager, Kernel::KEvent* event, size_t session_id);
29
30 /**
31 * Free this audio out from the audio out manager.
32 */
33 void Free();
34
35 /**
36 * Get this audio out's system.
37 */
38 System& GetSystem();
39
40 /**
41 * Get the current state.
42 *
43 * @return Started or Stopped.
44 */
45 AudioOut::State GetState();
46
47 /**
48 * Start the system
49 *
50 * @return Result code
51 */
52 Result StartSystem();
53
54 /**
55 * Start the system's device session.
56 */
57 void StartSession();
58
59 /**
60 * Stop the system.
61 *
62 * @return Result code
63 */
64 Result StopSystem();
65
66 /**
67 * Append a new buffer to the system, the buffer event will be signalled when it is filled.
68 *
69 * @param buffer - The new buffer to append.
70 * @param tag - Unique tag for this buffer.
71 * @return Result code.
72 */
73 Result AppendBuffer(const AudioOutBuffer& buffer, u64 tag);
74
75 /**
76 * Release all completed buffers, and register any appended.
77 */
78 void ReleaseAndRegisterBuffers();
79
80 /**
81 * Flush all buffers.
82 */
83 bool FlushAudioOutBuffers();
84
85 /**
86 * Get all of the currently released buffers.
87 *
88 * @param tags - Output container for the buffer tags which were released.
89 * @return The number of buffers released.
90 */
91 u32 GetReleasedBuffers(std::span<u64> tags);
92
93 /**
94 * Get the buffer event for this audio out, this event will be signalled when a buffer is
95 * filled.
96 * @return The buffer event.
97 */
98 Kernel::KReadableEvent& GetBufferEvent();
99
100 /**
101 * Get the current system volume.
102 *
103 * @return The current volume.
104 */
105 f32 GetVolume();
106
107 /**
108 * Set the system volume.
109 *
110 * @param volume - The volume to set.
111 */
112 void SetVolume(f32 volume);
113
114 /**
115 * Check if a buffer is in the system.
116 *
117 * @param tag - The tag to search for.
118 * @return True if the buffer is in the system, otherwise false.
119 */
120 bool ContainsAudioBuffer(u64 tag);
121
122 /**
123 * Get the maximum number of buffers.
124 *
125 * @return The maximum number of buffers.
126 */
127 u32 GetBufferCount();
128
129 /**
130 * Get the total played sample count for this audio out.
131 *
132 * @return The played sample count.
133 */
134 u64 GetPlayedSampleCount();
135
136private:
137 /// The AudioOut::Manager this audio out is registered with
138 Manager& manager;
139 /// Manager's mutex
140 std::recursive_mutex& parent_mutex;
141 /// Buffer event, signalled when buffers are ready to be released
142 Kernel::KEvent* event;
143 /// Main audio out system
144 System system;
145};
146
147} // namespace AudioCore::AudioOut
diff --git a/src/audio_core/out/audio_out_system.cpp b/src/audio_core/out/audio_out_system.cpp
new file mode 100644
index 000000000..35afddf06
--- /dev/null
+++ b/src/audio_core/out/audio_out_system.cpp
@@ -0,0 +1,207 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <mutex>
5
6#include "audio_core/audio_event.h"
7#include "audio_core/audio_manager.h"
8#include "audio_core/out/audio_out_system.h"
9#include "common/logging/log.h"
10#include "core/core.h"
11#include "core/core_timing.h"
12#include "core/hle/kernel/k_event.h"
13
14namespace AudioCore::AudioOut {
15
16System::System(Core::System& system_, Kernel::KEvent* event_, size_t session_id_)
17 : system{system_}, buffer_event{event_},
18 session_id{session_id_}, session{std::make_unique<DeviceSession>(system_)} {}
19
20System::~System() {
21 Finalize();
22}
23
24void System::Finalize() {
25 Stop();
26 session->Finalize();
27 buffer_event->GetWritableEvent().Signal();
28}
29
30std::string_view System::GetDefaultOutputDeviceName() {
31 return "DeviceOut";
32}
33
34Result System::IsConfigValid(std::string_view device_name, const AudioOutParameter& in_params) {
35 if ((device_name.size() > 0) && (device_name != GetDefaultOutputDeviceName())) {
36 return Service::Audio::ERR_INVALID_DEVICE_NAME;
37 }
38
39 if (in_params.sample_rate != TargetSampleRate && in_params.sample_rate > 0) {
40 return Service::Audio::ERR_INVALID_SAMPLE_RATE;
41 }
42
43 if (in_params.channel_count == 0 || in_params.channel_count == 2 ||
44 in_params.channel_count == 6) {
45 return ResultSuccess;
46 }
47
48 return Service::Audio::ERR_INVALID_CHANNEL_COUNT;
49}
50
51Result System::Initialize(std::string& device_name, const AudioOutParameter& in_params, u32 handle_,
52 u64& applet_resource_user_id_) {
53 auto result = IsConfigValid(device_name, in_params);
54 if (result.IsError()) {
55 return result;
56 }
57
58 handle = handle_;
59 applet_resource_user_id = applet_resource_user_id_;
60 if (device_name.empty() || device_name[0] == '\0') {
61 name = std::string(GetDefaultOutputDeviceName());
62 } else {
63 name = std::move(device_name);
64 }
65
66 sample_rate = TargetSampleRate;
67 sample_format = SampleFormat::PcmInt16;
68 channel_count = in_params.channel_count <= 2 ? 2 : 6;
69 volume = 1.0f;
70 return ResultSuccess;
71}
72
73void System::StartSession() {
74 session->Start();
75}
76
77size_t System::GetSessionId() const {
78 return session_id;
79}
80
81Result System::Start() {
82 if (state != State::Stopped) {
83 return Service::Audio::ERR_OPERATION_FAILED;
84 }
85
86 session->Initialize(name, sample_format, channel_count, session_id, handle,
87 applet_resource_user_id, Sink::StreamType::Out);
88 session->SetVolume(volume);
89 session->Start();
90 state = State::Started;
91
92 std::vector<AudioBuffer> buffers_to_flush{};
93 buffers.RegisterBuffers(buffers_to_flush);
94 session->AppendBuffers(buffers_to_flush);
95
96 return ResultSuccess;
97}
98
99Result System::Stop() {
100 if (state == State::Started) {
101 session->Stop();
102 session->SetVolume(0.0f);
103 state = State::Stopped;
104 }
105
106 return ResultSuccess;
107}
108
109bool System::AppendBuffer(const AudioOutBuffer& buffer, u64 tag) {
110 if (buffers.GetTotalBufferCount() == BufferCount) {
111 return false;
112 }
113
114 AudioBuffer new_buffer{
115 .played_timestamp = 0, .samples = buffer.samples, .tag = tag, .size = buffer.size};
116
117 buffers.AppendBuffer(new_buffer);
118 RegisterBuffers();
119
120 return true;
121}
122
123void System::RegisterBuffers() {
124 if (state == State::Started) {
125 std::vector<AudioBuffer> registered_buffers{};
126 buffers.RegisterBuffers(registered_buffers);
127 session->AppendBuffers(registered_buffers);
128 }
129}
130
131void System::ReleaseBuffers() {
132 bool signal{buffers.ReleaseBuffers(system.CoreTiming(), *session)};
133 if (signal) {
134 // Signal if any buffer was released, or if none are registered, we need more.
135 buffer_event->GetWritableEvent().Signal();
136 }
137}
138
139u32 System::GetReleasedBuffers(std::span<u64> tags) {
140 return buffers.GetReleasedBuffers(tags);
141}
142
143bool System::FlushAudioOutBuffers() {
144 if (state != State::Started) {
145 return false;
146 }
147
148 u32 buffers_released{};
149 buffers.FlushBuffers(buffers_released);
150
151 if (buffers_released > 0) {
152 buffer_event->GetWritableEvent().Signal();
153 }
154 return true;
155}
156
157u16 System::GetChannelCount() const {
158 return channel_count;
159}
160
161u32 System::GetSampleRate() const {
162 return sample_rate;
163}
164
165SampleFormat System::GetSampleFormat() const {
166 return sample_format;
167}
168
169State System::GetState() {
170 switch (state) {
171 case State::Started:
172 case State::Stopped:
173 return state;
174 default:
175 LOG_ERROR(Service_Audio, "AudioOut invalid state!");
176 state = State::Stopped;
177 break;
178 }
179 return state;
180}
181
182std::string System::GetName() const {
183 return name;
184}
185
186f32 System::GetVolume() const {
187 return volume;
188}
189
190void System::SetVolume(const f32 volume_) {
191 volume = volume_;
192 session->SetVolume(volume_);
193}
194
195bool System::ContainsAudioBuffer(const u64 tag) {
196 return buffers.ContainsBuffer(tag);
197}
198
199u32 System::GetBufferCount() {
200 return buffers.GetAppendedRegisteredCount();
201}
202
203u64 System::GetPlayedSampleCount() const {
204 return session->GetPlayedSampleCount();
205}
206
207} // namespace AudioCore::AudioOut
diff --git a/src/audio_core/out/audio_out_system.h b/src/audio_core/out/audio_out_system.h
new file mode 100644
index 000000000..4ca2f3417
--- /dev/null
+++ b/src/audio_core/out/audio_out_system.h
@@ -0,0 +1,257 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <atomic>
7#include <memory>
8#include <span>
9#include <string>
10
11#include "audio_core/common/common.h"
12#include "audio_core/device/audio_buffers.h"
13#include "audio_core/device/device_session.h"
14#include "core/hle/service/audio/errors.h"
15
16namespace Core {
17class System;
18}
19
20namespace Kernel {
21class KEvent;
22}
23
24namespace AudioCore::AudioOut {
25
26constexpr SessionTypes SessionType = SessionTypes::AudioOut;
27
28struct AudioOutParameter {
29 /* 0x0 */ s32_le sample_rate;
30 /* 0x4 */ u16_le channel_count;
31 /* 0x6 */ u16_le reserved;
32};
33static_assert(sizeof(AudioOutParameter) == 0x8, "AudioOutParameter is an invalid size");
34
35struct AudioOutParameterInternal {
36 /* 0x0 */ u32_le sample_rate;
37 /* 0x4 */ u32_le channel_count;
38 /* 0x8 */ u32_le sample_format;
39 /* 0xC */ u32_le state;
40};
41static_assert(sizeof(AudioOutParameterInternal) == 0x10,
42 "AudioOutParameterInternal is an invalid size");
43
44struct AudioOutBuffer {
45 /* 0x00 */ AudioOutBuffer* next;
46 /* 0x08 */ VAddr samples;
47 /* 0x10 */ u64 capacity;
48 /* 0x18 */ u64 size;
49 /* 0x20 */ u64 offset;
50};
51static_assert(sizeof(AudioOutBuffer) == 0x28, "AudioOutBuffer is an invalid size");
52
53enum class State {
54 Started,
55 Stopped,
56};
57
58/**
59 * Controls and drives audio output.
60 */
61class System {
62public:
63 explicit System(Core::System& system, Kernel::KEvent* event, size_t session_id);
64 ~System();
65
66 /**
67 * Get the default audio output device name.
68 *
69 * @return The default audio output device name.
70 */
71 std::string_view GetDefaultOutputDeviceName();
72
73 /**
74 * Is the given initialize config valid?
75 *
76 * @param device_name - The name of the requested output device.
77 * @param in_params - Input parameters, see AudioOutParameter.
78 * @return Result code.
79 */
80 Result IsConfigValid(std::string_view device_name, const AudioOutParameter& in_params);
81
82 /**
83 * Initialize this system.
84 *
85 * @param device_name - The name of the requested output device.
86 * @param in_params - Input parameters, see AudioOutParameter.
87 * @param handle - Unused.
88 * @param applet_resource_user_id - Unused.
89 * @return Result code.
90 */
91 Result Initialize(std::string& device_name, const AudioOutParameter& in_params, u32 handle,
92 u64& applet_resource_user_id);
93
94 /**
95 * Start this system.
96 *
97 * @return Result code.
98 */
99 Result Start();
100
101 /**
102 * Stop this system.
103 *
104 * @return Result code.
105 */
106 Result Stop();
107
108 /**
109 * Finalize this system.
110 */
111 void Finalize();
112
113 /**
114 * Start this system's device session.
115 */
116 void StartSession();
117
118 /**
119 * Get this system's id.
120 */
121 size_t GetSessionId() const;
122
123 /**
124 * Append a new buffer to the device.
125 *
126 * @param buffer - New buffer to append.
127 * @param tag - Unique tag of the buffer.
128 * @return True if the buffer was appended, otherwise false.
129 */
130 bool AppendBuffer(const AudioOutBuffer& buffer, u64 tag);
131
132 /**
133 * Register all appended buffers.
134 */
135 void RegisterBuffers();
136
137 /**
138 * Release all registered buffers.
139 */
140 void ReleaseBuffers();
141
142 /**
143 * Get all released buffers.
144 *
145 * @param tags - Container to be filled with the released buffers' tags.
146 * @return The number of buffers released.
147 */
148 u32 GetReleasedBuffers(std::span<u64> tags);
149
150 /**
151 * Flush all appended and registered buffers.
152 *
153 * @return True if buffers were successfully flushed, otherwise false.
154 */
155 bool FlushAudioOutBuffers();
156
157 /**
158 * Get this system's current channel count.
159 *
160 * @return The channel count.
161 */
162 u16 GetChannelCount() const;
163
164 /**
165 * Get this system's current sample rate.
166 *
167 * @return The sample rate.
168 */
169 u32 GetSampleRate() const;
170
171 /**
172 * Get this system's current sample format.
173 *
174 * @return The sample format.
175 */
176 SampleFormat GetSampleFormat() const;
177
178 /**
179 * Get this system's current state.
180 *
181 * @return The current state.
182 */
183 State GetState();
184
185 /**
186 * Get this system's name.
187 *
188 * @return The system's name.
189 */
190 std::string GetName() const;
191
192 /**
193 * Get this system's current volume.
194 *
195 * @return The system's current volume.
196 */
197 f32 GetVolume() const;
198
199 /**
200 * Set this system's current volume.
201 *
202 * @param The new volume.
203 */
204 void SetVolume(f32 volume);
205
206 /**
207 * Does the system contain this buffer?
208 *
209 * @param tag - Unique tag to search for.
210 * @return True if the buffer is in the system, otherwise false.
211 */
212 bool ContainsAudioBuffer(u64 tag);
213
214 /**
215 * Get the maximum number of usable buffers (default 32).
216 *
217 * @return The number of buffers.
218 */
219 u32 GetBufferCount();
220
221 /**
222 * Get the total number of samples played by this system.
223 *
224 * @return The number of samples.
225 */
226 u64 GetPlayedSampleCount() const;
227
228private:
229 /// Core system
230 Core::System& system;
231 /// (Unused)
232 u32 handle{};
233 /// (Unused)
234 u64 applet_resource_user_id{};
235 /// Buffer event, signalled when a buffer is ready
236 Kernel::KEvent* buffer_event;
237 /// Session id of this system
238 size_t session_id{};
239 /// Device session for this system
240 std::unique_ptr<DeviceSession> session;
241 /// Audio buffers in use by this system
242 AudioBuffers<BufferCount> buffers{BufferCount};
243 /// Sample rate of this system
244 u32 sample_rate{};
245 /// Sample format of this system
246 SampleFormat sample_format{SampleFormat::PcmInt16};
247 /// Channel count of this system
248 u16 channel_count{};
249 /// State of this system
250 std::atomic<State> state{State::Stopped};
251 /// Name of this system
252 std::string name{};
253 /// Volume of this system
254 f32 volume{1.0f};
255};
256
257} // namespace AudioCore::AudioOut
diff --git a/src/audio_core/renderer/adsp/adsp.cpp b/src/audio_core/renderer/adsp/adsp.cpp
new file mode 100644
index 000000000..e05a22d86
--- /dev/null
+++ b/src/audio_core/renderer/adsp/adsp.cpp
@@ -0,0 +1,118 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/adsp/adsp.h"
5#include "audio_core/renderer/adsp/command_buffer.h"
6#include "audio_core/sink/sink.h"
7#include "common/logging/log.h"
8#include "core/core.h"
9#include "core/core_timing.h"
10#include "core/core_timing_util.h"
11#include "core/memory.h"
12
13namespace AudioCore::AudioRenderer::ADSP {
14
15ADSP::ADSP(Core::System& system_, Sink::Sink& sink_)
16 : system{system_}, memory{system.Memory()}, sink{sink_} {}
17
18ADSP::~ADSP() {
19 ClearCommandBuffers();
20}
21
22State ADSP::GetState() const {
23 if (running) {
24 return State::Started;
25 }
26 return State::Stopped;
27}
28
29AudioRenderer_Mailbox* ADSP::GetRenderMailbox() {
30 return &render_mailbox;
31}
32
33void ADSP::ClearRemainCount(const u32 session_id) {
34 render_mailbox.ClearRemainCount(session_id);
35}
36
37u64 ADSP::GetSignalledTick() const {
38 return render_mailbox.GetSignalledTick();
39}
40
41u64 ADSP::GetTimeTaken() const {
42 return render_mailbox.GetRenderTimeTaken();
43}
44
45u64 ADSP::GetRenderTimeTaken(const u32 session_id) {
46 return render_mailbox.GetCommandBuffer(session_id).render_time_taken;
47}
48
49u32 ADSP::GetRemainCommandCount(const u32 session_id) const {
50 return render_mailbox.GetRemainCommandCount(session_id);
51}
52
53void ADSP::SendCommandBuffer(const u32 session_id, CommandBuffer& command_buffer) {
54 render_mailbox.SetCommandBuffer(session_id, command_buffer);
55}
56
57u64 ADSP::GetRenderingStartTick(const u32 session_id) {
58 return render_mailbox.GetSignalledTick() +
59 render_mailbox.GetCommandBuffer(session_id).render_time_taken;
60}
61
62bool ADSP::Start() {
63 if (running) {
64 return running;
65 }
66
67 running = true;
68 systems_active++;
69 audio_renderer = std::make_unique<AudioRenderer>(system);
70 audio_renderer->Start(&render_mailbox);
71 render_mailbox.HostSendMessage(RenderMessage::AudioRenderer_InitializeOK);
72 if (render_mailbox.HostWaitMessage() != RenderMessage::AudioRenderer_InitializeOK) {
73 LOG_ERROR(
74 Service_Audio,
75 "Host Audio Renderer -- Failed to receive initialize message response from ADSP!");
76 }
77 return running;
78}
79
80void ADSP::Stop() {
81 systems_active--;
82 if (running && systems_active == 0) {
83 {
84 std::scoped_lock l{mailbox_lock};
85 render_mailbox.HostSendMessage(RenderMessage::AudioRenderer_Shutdown);
86 if (render_mailbox.HostWaitMessage() != RenderMessage::AudioRenderer_Shutdown) {
87 LOG_ERROR(Service_Audio, "Host Audio Renderer -- Failed to receive shutdown "
88 "message response from ADSP!");
89 }
90 }
91 audio_renderer->Stop();
92 running = false;
93 }
94}
95
96void ADSP::Signal() {
97 const auto signalled_tick{system.CoreTiming().GetClockTicks()};
98 render_mailbox.SetSignalledTick(signalled_tick);
99 render_mailbox.HostSendMessage(RenderMessage::AudioRenderer_Render);
100}
101
102void ADSP::Wait() {
103 std::scoped_lock l{mailbox_lock};
104 auto response{render_mailbox.HostWaitMessage()};
105 if (response != RenderMessage::AudioRenderer_RenderResponse) {
106 LOG_ERROR(Service_Audio, "Invalid ADSP response message, expected 0x{:02X}, got 0x{:02X}",
107 static_cast<u32>(RenderMessage::AudioRenderer_RenderResponse),
108 static_cast<u32>(response));
109 }
110
111 ClearCommandBuffers();
112}
113
114void ADSP::ClearCommandBuffers() {
115 render_mailbox.ClearCommandBuffers();
116}
117
118} // namespace AudioCore::AudioRenderer::ADSP
diff --git a/src/audio_core/renderer/adsp/adsp.h b/src/audio_core/renderer/adsp/adsp.h
new file mode 100644
index 000000000..4dfcef4a5
--- /dev/null
+++ b/src/audio_core/renderer/adsp/adsp.h
@@ -0,0 +1,173 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <memory>
7#include <mutex>
8
9#include "audio_core/renderer/adsp/audio_renderer.h"
10#include "common/common_types.h"
11
12namespace Core {
13namespace Memory {
14class Memory;
15}
16class System;
17} // namespace Core
18
19namespace AudioCore {
20namespace Sink {
21class Sink;
22}
23
24namespace AudioRenderer::ADSP {
25struct CommandBuffer;
26
27enum class State {
28 Started,
29 Stopped,
30};
31
32/**
33 * Represents the ADSP embedded within the audio sysmodule.
34 * This is a 32-bit Linux4Tegra kernel from nVidia, which is launched with the sysmodule on boot.
35 *
36 * The kernel will run apps you program for it, Nintendo have the following:
37 *
38 * Gmix - Responsible for mixing final audio and sending it out to hardware. This is last place all
39 * audio samples end up, and we skip it entirely, since we have very different backends and
40 * mixing is implicitly handled by the OS (but also due to lack of research/simplicity).
41 *
42 * AudioRenderer - Receives command lists generated by the audio render
43 * system, processes them, and sends the samples to Gmix.
44 *
45 * OpusDecoder - Contains libopus, and controls processing Opus audio and sends it to Gmix.
46 * Not much research done here, TODO if needed.
47 *
48 * We only implement the AudioRenderer for now.
49 *
50 * Communication for the apps is done through mailboxes, and some shared memory.
51 */
52class ADSP {
53public:
54 explicit ADSP(Core::System& system, Sink::Sink& sink);
55 ~ADSP();
56
57 /**
58 * Start the ADSP.
59 *
60 * @return True if started or already running, otherwise false.
61 */
62 bool Start();
63
64 /**
65 * Stop the ADSP.
66 *
67 * @return True if started or already running, otherwise false.
68 */
69 void Stop();
70
71 /**
72 * Get the ADSP's state.
73 *
74 * @return Started or Stopped.
75 */
76 State GetState() const;
77
78 /**
79 * Get the AudioRenderer mailbox to communicate with it.
80 *
81 * @return The AudioRenderer mailbox.
82 */
83 AudioRenderer_Mailbox* GetRenderMailbox();
84
85 /**
86 * Get the tick the ADSP was signalled.
87 *
88 * @return The tick the ADSP was signalled.
89 */
90 u64 GetSignalledTick() const;
91
92 /**
93 * Get the total time it took for the ADSP to run the last command lists (both command lists).
94 *
95 * @return The tick the ADSP was signalled.
96 */
97 u64 GetTimeTaken() const;
98
99 /**
100 * Get the last time a given command list took to run.
101 *
102 * @param session_id - The session id to check (0 or 1).
103 * @return The time it took.
104 */
105 u64 GetRenderTimeTaken(u32 session_id);
106
107 /**
108 * Clear the remaining command count for a given session.
109 *
110 * @param session_id - The session id to check (0 or 1).
111 */
112 void ClearRemainCount(u32 session_id);
113
114 /**
115 * Get the remaining number of commands left to process for a command list.
116 *
117 * @param session_id - The session id to check (0 or 1).
118 * @return The number of commands remaining.
119 */
120 u32 GetRemainCommandCount(u32 session_id) const;
121
122 /**
123 * Get the last tick a command list started processing.
124 *
125 * @param session_id - The session id to check (0 or 1).
126 * @return The last tick the given command list started.
127 */
128 u64 GetRenderingStartTick(u32 session_id);
129
130 /**
131 * Set a command buffer to be processed.
132 *
133 * @param session_id - The session id to check (0 or 1).
134 * @param command_buffer - The command buffer to process.
135 */
136 void SendCommandBuffer(u32 session_id, CommandBuffer& command_buffer);
137
138 /**
139 * Clear the command buffers (does not clear the time taken or the remaining command count)
140 */
141 void ClearCommandBuffers();
142
143 /**
144 * Signal the AudioRenderer to begin processing.
145 */
146 void Signal();
147
148 /**
149 * Wait for the AudioRenderer to finish processing.
150 */
151 void Wait();
152
153private:
154 /// Core system
155 Core::System& system;
156 /// Core memory
157 Core::Memory::Memory& memory;
158 /// Number of systems active, used to prevent accidental shutdowns
159 u8 systems_active{0};
160 /// ADSP running state
161 std::atomic<bool> running{false};
162 /// Output sink used by the ADSP
163 Sink::Sink& sink;
164 /// AudioRenderer app
165 std::unique_ptr<AudioRenderer> audio_renderer{};
166 /// Communication for the AudioRenderer
167 AudioRenderer_Mailbox render_mailbox{};
168 /// Mailbox lock ffor the render mailbox
169 std::mutex mailbox_lock;
170};
171
172} // namespace AudioRenderer::ADSP
173} // namespace AudioCore
diff --git a/src/audio_core/renderer/adsp/audio_renderer.cpp b/src/audio_core/renderer/adsp/audio_renderer.cpp
new file mode 100644
index 000000000..3967ccfe6
--- /dev/null
+++ b/src/audio_core/renderer/adsp/audio_renderer.cpp
@@ -0,0 +1,226 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <array>
5#include <chrono>
6
7#include "audio_core/audio_core.h"
8#include "audio_core/common/common.h"
9#include "audio_core/renderer/adsp/audio_renderer.h"
10#include "audio_core/sink/sink.h"
11#include "common/logging/log.h"
12#include "common/microprofile.h"
13#include "common/thread.h"
14#include "core/core.h"
15#include "core/core_timing.h"
16#include "core/core_timing_util.h"
17
18MICROPROFILE_DEFINE(Audio_Renderer, "Audio", "DSP", MP_RGB(60, 19, 97));
19
20namespace AudioCore::AudioRenderer::ADSP {
21
22void AudioRenderer_Mailbox::HostSendMessage(RenderMessage message_) {
23 adsp_messages.enqueue(message_);
24 adsp_event.Set();
25}
26
27RenderMessage AudioRenderer_Mailbox::HostWaitMessage() {
28 host_event.Wait();
29 RenderMessage msg{RenderMessage::Invalid};
30 if (!host_messages.try_dequeue(msg)) {
31 LOG_ERROR(Service_Audio, "Failed to dequeue host message!");
32 }
33 return msg;
34}
35
36void AudioRenderer_Mailbox::ADSPSendMessage(const RenderMessage message_) {
37 host_messages.enqueue(message_);
38 host_event.Set();
39}
40
41RenderMessage AudioRenderer_Mailbox::ADSPWaitMessage() {
42 adsp_event.Wait();
43 RenderMessage msg{RenderMessage::Invalid};
44 if (!adsp_messages.try_dequeue(msg)) {
45 LOG_ERROR(Service_Audio, "Failed to dequeue ADSP message!");
46 }
47 return msg;
48}
49
50CommandBuffer& AudioRenderer_Mailbox::GetCommandBuffer(const s32 session_id) {
51 return command_buffers[session_id];
52}
53
54void AudioRenderer_Mailbox::SetCommandBuffer(const u32 session_id, CommandBuffer& buffer) {
55 command_buffers[session_id] = buffer;
56}
57
58u64 AudioRenderer_Mailbox::GetRenderTimeTaken() const {
59 return command_buffers[0].render_time_taken + command_buffers[1].render_time_taken;
60}
61
62u64 AudioRenderer_Mailbox::GetSignalledTick() const {
63 return signalled_tick;
64}
65
66void AudioRenderer_Mailbox::SetSignalledTick(const u64 tick) {
67 signalled_tick = tick;
68}
69
70void AudioRenderer_Mailbox::ClearRemainCount(const u32 session_id) {
71 command_buffers[session_id].remaining_command_count = 0;
72}
73
74u32 AudioRenderer_Mailbox::GetRemainCommandCount(const u32 session_id) const {
75 return command_buffers[session_id].remaining_command_count;
76}
77
78void AudioRenderer_Mailbox::ClearCommandBuffers() {
79 command_buffers[0].buffer = 0;
80 command_buffers[0].size = 0;
81 command_buffers[0].reset_buffers = false;
82 command_buffers[1].buffer = 0;
83 command_buffers[1].size = 0;
84 command_buffers[1].reset_buffers = false;
85}
86
87AudioRenderer::AudioRenderer(Core::System& system_)
88 : system{system_}, sink{system.AudioCore().GetOutputSink()} {
89 CreateSinkStreams();
90}
91
92AudioRenderer::~AudioRenderer() {
93 Stop();
94 for (auto& stream : streams) {
95 if (stream) {
96 sink.CloseStream(stream);
97 }
98 stream = nullptr;
99 }
100}
101
102void AudioRenderer::Start(AudioRenderer_Mailbox* mailbox_) {
103 if (running) {
104 return;
105 }
106
107 mailbox = mailbox_;
108 thread = std::thread(&AudioRenderer::ThreadFunc, this);
109 for (auto& stream : streams) {
110 stream->Start();
111 }
112 running = true;
113}
114
115void AudioRenderer::Stop() {
116 if (!running) {
117 return;
118 }
119
120 for (auto& stream : streams) {
121 stream->Stop();
122 }
123 thread.join();
124 running = false;
125}
126
127void AudioRenderer::CreateSinkStreams() {
128 u32 channels{sink.GetDeviceChannels()};
129 for (u32 i = 0; i < MaxRendererSessions; i++) {
130 std::string name{fmt::format("ADSP_RenderStream-{}", i)};
131 streams[i] =
132 sink.AcquireSinkStream(system, channels, name, ::AudioCore::Sink::StreamType::Render);
133 }
134}
135
136void AudioRenderer::ThreadFunc() {
137 constexpr char name[]{"yuzu:AudioRenderer"};
138 MicroProfileOnThreadCreate(name);
139 Common::SetCurrentThreadName(name);
140 Common::SetCurrentThreadPriority(Common::ThreadPriority::Critical);
141 if (mailbox->ADSPWaitMessage() != RenderMessage::AudioRenderer_InitializeOK) {
142 LOG_ERROR(Service_Audio,
143 "ADSP Audio Renderer -- Failed to receive initialize message from host!");
144 return;
145 }
146
147 mailbox->ADSPSendMessage(RenderMessage::AudioRenderer_InitializeOK);
148
149 constexpr u64 max_process_time{2'304'000ULL};
150
151 while (true) {
152 auto message{mailbox->ADSPWaitMessage()};
153 switch (message) {
154 case RenderMessage::AudioRenderer_Shutdown:
155 mailbox->ADSPSendMessage(RenderMessage::AudioRenderer_Shutdown);
156 return;
157
158 case RenderMessage::AudioRenderer_Render: {
159 std::array<bool, MaxRendererSessions> buffers_reset{};
160 std::array<u64, MaxRendererSessions> render_times_taken{};
161 const auto start_time{system.CoreTiming().GetClockTicks()};
162
163 for (u32 index = 0; index < 2; index++) {
164 auto& command_buffer{mailbox->GetCommandBuffer(index)};
165 auto& command_list_processor{command_list_processors[index]};
166
167 // Check this buffer is valid, as it may not be used.
168 if (command_buffer.buffer != 0) {
169 // If there are no remaining commands (from the previous list),
170 // this is a new command list, initalize it.
171 if (command_buffer.remaining_command_count == 0) {
172 command_list_processor.Initialize(system, command_buffer.buffer,
173 command_buffer.size, streams[index]);
174 }
175
176 if (command_buffer.reset_buffers && !buffers_reset[index]) {
177 streams[index]->ClearQueue();
178 buffers_reset[index] = true;
179 }
180
181 u64 max_time{max_process_time};
182 if (index == 1 && command_buffer.applet_resource_user_id ==
183 mailbox->GetCommandBuffer(0).applet_resource_user_id) {
184 max_time = max_process_time -
185 Core::Timing::CyclesToNs(render_times_taken[0]).count();
186 if (render_times_taken[0] > max_process_time) {
187 max_time = 0;
188 }
189 }
190
191 max_time = std::min(command_buffer.time_limit, max_time);
192 command_list_processor.SetProcessTimeMax(max_time);
193
194 // Process the command list
195 {
196 MICROPROFILE_SCOPE(Audio_Renderer);
197 render_times_taken[index] =
198 command_list_processor.Process(index) - start_time;
199 }
200
201 if (index == 0) {
202 auto stream{command_list_processor.GetOutputSinkStream()};
203 system.AudioCore().SetStreamQueue(stream->GetQueueSize());
204 }
205
206 const auto end_time{system.CoreTiming().GetClockTicks()};
207
208 command_buffer.remaining_command_count =
209 command_list_processor.GetRemainingCommandCount();
210 command_buffer.render_time_taken = end_time - start_time;
211 }
212 }
213
214 mailbox->ADSPSendMessage(RenderMessage::AudioRenderer_RenderResponse);
215 } break;
216
217 default:
218 LOG_WARNING(Service_Audio,
219 "ADSP AudioRenderer received an invalid message, msg={:02X}!",
220 static_cast<u32>(message));
221 break;
222 }
223 }
224}
225
226} // namespace AudioCore::AudioRenderer::ADSP
diff --git a/src/audio_core/renderer/adsp/audio_renderer.h b/src/audio_core/renderer/adsp/audio_renderer.h
new file mode 100644
index 000000000..b6ced9d2b
--- /dev/null
+++ b/src/audio_core/renderer/adsp/audio_renderer.h
@@ -0,0 +1,203 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7#include <memory>
8#include <thread>
9
10#include "audio_core/renderer/adsp/command_buffer.h"
11#include "audio_core/renderer/adsp/command_list_processor.h"
12#include "common/common_types.h"
13#include "common/reader_writer_queue.h"
14#include "common/thread.h"
15
16namespace Core {
17namespace Timing {
18struct EventType;
19}
20class System;
21} // namespace Core
22
23namespace AudioCore {
24namespace Sink {
25class Sink;
26}
27
28namespace AudioRenderer::ADSP {
29
30enum class RenderMessage {
31 /* 0x00 */ Invalid,
32 /* 0x01 */ AudioRenderer_MapUnmap_Map,
33 /* 0x02 */ AudioRenderer_MapUnmap_MapResponse,
34 /* 0x03 */ AudioRenderer_MapUnmap_Unmap,
35 /* 0x04 */ AudioRenderer_MapUnmap_UnmapResponse,
36 /* 0x05 */ AudioRenderer_MapUnmap_InvalidateCache,
37 /* 0x06 */ AudioRenderer_MapUnmap_InvalidateCacheResponse,
38 /* 0x07 */ AudioRenderer_MapUnmap_Shutdown,
39 /* 0x08 */ AudioRenderer_MapUnmap_ShutdownResponse,
40 /* 0x16 */ AudioRenderer_InitializeOK = 0x16,
41 /* 0x20 */ AudioRenderer_RenderResponse = 0x20,
42 /* 0x2A */ AudioRenderer_Render = 0x2A,
43 /* 0x34 */ AudioRenderer_Shutdown = 0x34,
44};
45
46/**
47 * A mailbox for the AudioRenderer, allowing communication between the host and the AudioRenderer
48 * running on the ADSP.
49 */
50class AudioRenderer_Mailbox {
51public:
52 /**
53 * Send a message from the host to the AudioRenderer.
54 *
55 * @param message_ - The message to send to the AudioRenderer.
56 */
57 void HostSendMessage(RenderMessage message);
58
59 /**
60 * Host wait for a message from the AudioRenderer.
61 *
62 * @return The message returned from the AudioRenderer.
63 */
64 RenderMessage HostWaitMessage();
65
66 /**
67 * Send a message from the AudioRenderer to the host.
68 *
69 * @param message_ - The message to send to the host.
70 */
71 void ADSPSendMessage(RenderMessage message);
72
73 /**
74 * AudioRenderer wait for a message from the host.
75 *
76 * @return The message returned from the AudioRenderer.
77 */
78 RenderMessage ADSPWaitMessage();
79
80 /**
81 * Get the command buffer with the given session id (0 or 1).
82 *
83 * @param session_id - The session id to get (0 or 1).
84 * @return The command buffer.
85 */
86 CommandBuffer& GetCommandBuffer(s32 session_id);
87
88 /**
89 * Set the command buffer with the given session id (0 or 1).
90 *
91 * @param session_id - The session id to get (0 or 1).
92 * @param buffer - The command buffer to set.
93 */
94 void SetCommandBuffer(u32 session_id, CommandBuffer& buffer);
95
96 /**
97 * Get the total render time taken for the last command lists sent.
98 *
99 * @return Total render time taken for the last command lists.
100 */
101 u64 GetRenderTimeTaken() const;
102
103 /**
104 * Get the tick the AudioRenderer was signalled.
105 *
106 * @return The tick the AudioRenderer was signalled.
107 */
108 u64 GetSignalledTick() const;
109
110 /**
111 * Set the tick the AudioRenderer was signalled.
112 *
113 * @param tick - The tick the AudioRenderer was signalled.
114 */
115 void SetSignalledTick(u64 tick);
116
117 /**
118 * Clear the remaining command count.
119 *
120 * @param session_id - Index for which command list to clear (0 or 1).
121 */
122 void ClearRemainCount(u32 session_id);
123
124 /**
125 * Get the remaining command count for a given command list.
126 *
127 * @param session_id - Index for which command list to clear (0 or 1).
128 * @return The remaining command count.
129 */
130 u32 GetRemainCommandCount(u32 session_id) const;
131
132 /**
133 * Clear the command buffers (does not clear the time taken or the remaining command count).
134 */
135 void ClearCommandBuffers();
136
137private:
138 /// Host signalling event
139 Common::Event host_event{};
140 /// AudioRenderer signalling event
141 Common::Event adsp_event{};
142 /// Host message queue
143
144 Common::ReaderWriterQueue<RenderMessage> host_messages{};
145 /// AudioRenderer message queue
146
147 Common::ReaderWriterQueue<RenderMessage> adsp_messages{};
148 /// Command buffers
149
150 std::array<CommandBuffer, MaxRendererSessions> command_buffers{};
151 /// Tick the AudioRnederer was signalled
152 u64 signalled_tick{};
153};
154
155/**
156 * The AudioRenderer application running on the ADSP.
157 */
158class AudioRenderer {
159public:
160 explicit AudioRenderer(Core::System& system);
161 ~AudioRenderer();
162
163 /**
164 * Start the AudioRenderer.
165 *
166 * @param The mailbox to use for this session.
167 */
168 void Start(AudioRenderer_Mailbox* mailbox);
169
170 /**
171 * Stop the AudioRenderer.
172 */
173 void Stop();
174
175private:
176 /**
177 * Main AudioRenderer thread, responsible for processing the command lists.
178 */
179 void ThreadFunc();
180
181 /**
182 * Creates the streams which will receive the processed samples.
183 */
184 void CreateSinkStreams();
185
186 /// Core system
187 Core::System& system;
188 /// Main thread
189 std::thread thread{};
190 /// The current state
191 std::atomic<bool> running{};
192 /// The active mailbox
193 AudioRenderer_Mailbox* mailbox{};
194 /// The command lists to process
195 std::array<CommandListProcessor, MaxRendererSessions> command_list_processors{};
196 /// The output sink the AudioRenderer will use
197 Sink::Sink& sink;
198 /// The streams which will receive the processed samples
199 std::array<Sink::SinkStream*, MaxRendererSessions> streams;
200};
201
202} // namespace AudioRenderer::ADSP
203} // namespace AudioCore
diff --git a/src/audio_core/renderer/adsp/command_buffer.h b/src/audio_core/renderer/adsp/command_buffer.h
new file mode 100644
index 000000000..880b279d8
--- /dev/null
+++ b/src/audio_core/renderer/adsp/command_buffer.h
@@ -0,0 +1,21 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "audio_core/common/common.h"
7#include "common/common_types.h"
8
9namespace AudioCore::AudioRenderer::ADSP {
10
11struct CommandBuffer {
12 CpuAddr buffer;
13 u64 size;
14 u64 time_limit;
15 u32 remaining_command_count;
16 bool reset_buffers;
17 u64 applet_resource_user_id;
18 u64 render_time_taken;
19};
20
21} // namespace AudioCore::AudioRenderer::ADSP
diff --git a/src/audio_core/renderer/adsp/command_list_processor.cpp b/src/audio_core/renderer/adsp/command_list_processor.cpp
new file mode 100644
index 000000000..e3bf2d7ec
--- /dev/null
+++ b/src/audio_core/renderer/adsp/command_list_processor.cpp
@@ -0,0 +1,109 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <string>
5
6#include "audio_core/renderer/adsp/command_list_processor.h"
7#include "audio_core/renderer/command/command_list_header.h"
8#include "audio_core/renderer/command/commands.h"
9#include "common/settings.h"
10#include "core/core.h"
11#include "core/core_timing.h"
12#include "core/core_timing_util.h"
13#include "core/memory.h"
14
15namespace AudioCore::AudioRenderer::ADSP {
16
17void CommandListProcessor::Initialize(Core::System& system_, CpuAddr buffer, u64 size,
18 Sink::SinkStream* stream_) {
19 system = &system_;
20 memory = &system->Memory();
21 stream = stream_;
22 header = reinterpret_cast<CommandListHeader*>(buffer);
23 commands = reinterpret_cast<u8*>(buffer + sizeof(CommandListHeader));
24 commands_buffer_size = size;
25 command_count = header->command_count;
26 sample_count = header->sample_count;
27 target_sample_rate = header->sample_rate;
28 mix_buffers = header->samples_buffer;
29 buffer_count = header->buffer_count;
30 processed_command_count = 0;
31}
32
33void CommandListProcessor::SetProcessTimeMax(const u64 time) {
34 max_process_time = time;
35}
36
37u32 CommandListProcessor::GetRemainingCommandCount() const {
38 return command_count - processed_command_count;
39}
40
41void CommandListProcessor::SetBuffer(const CpuAddr buffer, const u64 size) {
42 commands = reinterpret_cast<u8*>(buffer + sizeof(CommandListHeader));
43 commands_buffer_size = size;
44}
45
46Sink::SinkStream* CommandListProcessor::GetOutputSinkStream() const {
47 return stream;
48}
49
50u64 CommandListProcessor::Process(u32 session_id) {
51 const auto start_time_{system->CoreTiming().GetClockTicks()};
52 const auto command_base{CpuAddr(commands)};
53
54 if (processed_command_count > 0) {
55 current_processing_time += start_time_ - end_time;
56 } else {
57 start_time = start_time_;
58 current_processing_time = 0;
59 }
60
61 std::string dump{fmt::format("\nSession {}\n", session_id)};
62
63 for (u32 index = 0; index < command_count; index++) {
64 auto& command{*reinterpret_cast<ICommand*>(commands)};
65
66 if (command.magic != 0xCAFEBABE) {
67 LOG_ERROR(Service_Audio, "Command has invalid magic! Expected 0xCAFEBABE, got {:08X}",
68 command.magic);
69 return system->CoreTiming().GetClockTicks() - start_time_;
70 }
71
72 auto current_offset{CpuAddr(commands) - command_base};
73
74 if (current_offset + command.size > commands_buffer_size) {
75 LOG_ERROR(Service_Audio,
76 "Command exceeded command buffer, buffer size {:08X}, command ends at {:08X}",
77 commands_buffer_size,
78 CpuAddr(commands) + command.size - sizeof(CommandListHeader));
79 return system->CoreTiming().GetClockTicks() - start_time_;
80 }
81
82 if (Settings::values.dump_audio_commands) {
83 command.Dump(*this, dump);
84 }
85
86 if (!command.Verify(*this)) {
87 break;
88 }
89
90 if (command.enabled) {
91 command.Process(*this);
92 } else {
93 dump += fmt::format("\tDisabled!\n");
94 }
95
96 processed_command_count++;
97 commands += command.size;
98 }
99
100 if (Settings::values.dump_audio_commands && dump != last_dump) {
101 LOG_WARNING(Service_Audio, "{}", dump);
102 last_dump = dump;
103 }
104
105 end_time = system->CoreTiming().GetClockTicks();
106 return end_time - start_time_;
107}
108
109} // namespace AudioCore::AudioRenderer::ADSP
diff --git a/src/audio_core/renderer/adsp/command_list_processor.h b/src/audio_core/renderer/adsp/command_list_processor.h
new file mode 100644
index 000000000..3f99173e3
--- /dev/null
+++ b/src/audio_core/renderer/adsp/command_list_processor.h
@@ -0,0 +1,118 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <span>
7
8#include "audio_core/common/common.h"
9#include "common/common_types.h"
10
11namespace Core {
12namespace Memory {
13class Memory;
14}
15class System;
16} // namespace Core
17
18namespace AudioCore {
19namespace Sink {
20class SinkStream;
21}
22
23namespace AudioRenderer {
24struct CommandListHeader;
25
26namespace ADSP {
27
28/**
29 * A processor for command lists given to the AudioRenderer.
30 */
31class CommandListProcessor {
32public:
33 /**
34 * Initialize the processor.
35 *
36 * @param system_ - The core system.
37 * @param buffer - The command buffer to process.
38 * @param size - The size of the buffer.
39 * @param stream_ - The stream to be used for sending the samples.
40 */
41 void Initialize(Core::System& system, CpuAddr buffer, u64 size, Sink::SinkStream* stream);
42
43 /**
44 * Set the maximum processing time for this command list.
45 *
46 * @param time - The maximum process time.
47 */
48 void SetProcessTimeMax(u64 time);
49
50 /**
51 * Get the remaining command count for this list.
52 *
53 * @return The remaining command count.
54 */
55 u32 GetRemainingCommandCount() const;
56
57 /**
58 * Set the command buffer.
59 *
60 * @param buffer - The buffer to use.
61 * @param size - The size of the buffer.
62 */
63 void SetBuffer(CpuAddr buffer, u64 size);
64
65 /**
66 * Get the stream for this command list.
67 *
68 * @return The stream associated with this command list.
69 */
70 Sink::SinkStream* GetOutputSinkStream() const;
71
72 /**
73 * Process the command list.
74 *
75 * @param index - Index of the current command list.
76 * @return The time taken to process.
77 */
78 u64 Process(u32 session_id);
79
80 /// Core system
81 Core::System* system{};
82 /// Core memory
83 Core::Memory::Memory* memory{};
84 /// Stream for the processed samples
85 Sink::SinkStream* stream{};
86 /// Header info for this command list
87 CommandListHeader* header{};
88 /// The command buffer
89 u8* commands{};
90 /// The command buffer size
91 u64 commands_buffer_size{};
92 /// The maximum processing time alloted
93 u64 max_process_time{};
94 /// The number of commands in the buffer
95 u32 command_count{};
96 /// The target sample count for output
97 u32 sample_count{};
98 /// The target sample rate for output
99 u32 target_sample_rate{};
100 /// The mixing buffers used by the commands
101 std::span<s32> mix_buffers{};
102 /// The number of mix buffers
103 u32 buffer_count{};
104 /// The number of processed commands so far
105 u32 processed_command_count{};
106 /// The processing start time of this list
107 u64 start_time{};
108 /// The current processing time for this list
109 u64 current_processing_time{};
110 /// The end processing time for this list
111 u64 end_time{};
112 /// Last command list string generated, used for dumping audio commands to console
113 std::string last_dump{};
114};
115
116} // namespace ADSP
117} // namespace AudioRenderer
118} // namespace AudioCore
diff --git a/src/audio_core/renderer/audio_device.cpp b/src/audio_core/renderer/audio_device.cpp
new file mode 100644
index 000000000..d5886e55e
--- /dev/null
+++ b/src/audio_core/renderer/audio_device.cpp
@@ -0,0 +1,52 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/audio_core.h"
5#include "audio_core/common/feature_support.h"
6#include "audio_core/renderer/audio_device.h"
7#include "audio_core/sink/sink.h"
8#include "core/core.h"
9
10namespace AudioCore::AudioRenderer {
11
12AudioDevice::AudioDevice(Core::System& system, const u64 applet_resource_user_id_,
13 const u32 revision)
14 : output_sink{system.AudioCore().GetOutputSink()},
15 applet_resource_user_id{applet_resource_user_id_}, user_revision{revision} {}
16
17u32 AudioDevice::ListAudioDeviceName(std::vector<AudioDeviceName>& out_buffer,
18 const size_t max_count) {
19 std::span<AudioDeviceName> names{};
20
21 if (CheckFeatureSupported(SupportTags::AudioUsbDeviceOutput, user_revision)) {
22 names = usb_device_names;
23 } else {
24 names = device_names;
25 }
26
27 u32 out_count{static_cast<u32>(std::min(max_count, names.size()))};
28 for (u32 i = 0; i < out_count; i++) {
29 out_buffer.push_back(names[i]);
30 }
31 return out_count;
32}
33
34u32 AudioDevice::ListAudioOutputDeviceName(std::vector<AudioDeviceName>& out_buffer,
35 const size_t max_count) {
36 u32 out_count{static_cast<u32>(std::min(max_count, output_device_names.size()))};
37
38 for (u32 i = 0; i < out_count; i++) {
39 out_buffer.push_back(output_device_names[i]);
40 }
41 return out_count;
42}
43
44void AudioDevice::SetDeviceVolumes(const f32 volume) {
45 output_sink.SetDeviceVolume(volume);
46}
47
48f32 AudioDevice::GetDeviceVolume([[maybe_unused]] std::string_view name) {
49 return output_sink.GetDeviceVolume();
50}
51
52} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/audio_device.h b/src/audio_core/renderer/audio_device.h
new file mode 100644
index 000000000..1f449f261
--- /dev/null
+++ b/src/audio_core/renderer/audio_device.h
@@ -0,0 +1,88 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <span>
7
8#include "audio_core/audio_render_manager.h"
9
10namespace Core {
11class System;
12}
13
14namespace AudioCore {
15namespace Sink {
16class Sink;
17}
18
19namespace AudioRenderer {
20/**
21 * An interface to an output audio device available to the Switch.
22 */
23class AudioDevice {
24public:
25 struct AudioDeviceName {
26 std::array<char, 0x100> name;
27
28 AudioDeviceName(const char* name_) {
29 std::strncpy(name.data(), name_, name.size());
30 }
31 };
32
33 std::array<AudioDeviceName, 4> usb_device_names{"AudioStereoJackOutput",
34 "AudioBuiltInSpeakerOutput", "AudioTvOutput",
35 "AudioUsbDeviceOutput"};
36 std::array<AudioDeviceName, 3> device_names{"AudioStereoJackOutput",
37 "AudioBuiltInSpeakerOutput", "AudioTvOutput"};
38 std::array<AudioDeviceName, 3> output_device_names{"AudioBuiltInSpeakerOutput", "AudioTvOutput",
39 "AudioExternalOutput"};
40
41 explicit AudioDevice(Core::System& system, u64 applet_resource_user_id, u32 revision);
42
43 /**
44 * Get a list of the available output devices.
45 *
46 * @param out_buffer - Output buffer to write the available device names.
47 * @param max_count - Maximum number of devices to write (count of out_buffer).
48 * @return Number of device names written.
49 */
50 u32 ListAudioDeviceName(std::vector<AudioDeviceName>& out_buffer, size_t max_count);
51
52 /**
53 * Get a list of the available output devices.
54 * Different to above somehow...
55 *
56 * @param out_buffer - Output buffer to write the available device names.
57 * @param max_count - Maximum number of devices to write (count of out_buffer).
58 * @return Number of device names written.
59 */
60 u32 ListAudioOutputDeviceName(std::vector<AudioDeviceName>& out_buffer, size_t max_count);
61
62 /**
63 * Set the volume of all streams in the backend sink.
64 *
65 * @param volume - Volume to set.
66 */
67 void SetDeviceVolumes(f32 volume);
68
69 /**
70 * Get the volume for a given device name.
71 * Note: This is not fully implemented, we only assume 1 device for all streams.
72 *
73 * @param name - Name of the device to check. Unused.
74 * @return Volume of the device.
75 */
76 f32 GetDeviceVolume(std::string_view name);
77
78private:
79 /// Backend output sink for the device
80 Sink::Sink& output_sink;
81 /// Resource id this device is used for
82 const u64 applet_resource_user_id;
83 /// User audio renderer revision
84 const u32 user_revision;
85};
86
87} // namespace AudioRenderer
88} // namespace AudioCore
diff --git a/src/audio_core/renderer/audio_renderer.cpp b/src/audio_core/renderer/audio_renderer.cpp
new file mode 100644
index 000000000..51aa17599
--- /dev/null
+++ b/src/audio_core/renderer/audio_renderer.cpp
@@ -0,0 +1,67 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/audio_render_manager.h"
5#include "audio_core/common/audio_renderer_parameter.h"
6#include "audio_core/renderer/audio_renderer.h"
7#include "audio_core/renderer/system_manager.h"
8#include "core/core.h"
9#include "core/hle/kernel/k_transfer_memory.h"
10#include "core/hle/service/audio/errors.h"
11
12namespace AudioCore::AudioRenderer {
13
14Renderer::Renderer(Core::System& system_, Manager& manager_, Kernel::KEvent* rendered_event)
15 : core{system_}, manager{manager_}, system{system_, rendered_event} {}
16
17Result Renderer::Initialize(const AudioRendererParameterInternal& params,
18 Kernel::KTransferMemory* transfer_memory,
19 const u64 transfer_memory_size, const u32 process_handle,
20 const u64 applet_resource_user_id, const s32 session_id) {
21 if (params.execution_mode == ExecutionMode::Auto) {
22 if (!manager.AddSystem(system)) {
23 LOG_ERROR(Service_Audio,
24 "Both Audio Render sessions are in use, cannot create any more");
25 return Service::Audio::ERR_MAXIMUM_SESSIONS_REACHED;
26 }
27 system_registered = true;
28 }
29
30 initialized = true;
31 system.Initialize(params, transfer_memory, transfer_memory_size, process_handle,
32 applet_resource_user_id, session_id);
33
34 return ResultSuccess;
35}
36
37void Renderer::Finalize() {
38 auto session_id{system.GetSessionId()};
39
40 system.Finalize();
41
42 if (system_registered) {
43 manager.RemoveSystem(system);
44 system_registered = false;
45 }
46
47 manager.ReleaseSessionId(session_id);
48}
49
50System& Renderer::GetSystem() {
51 return system;
52}
53
54void Renderer::Start() {
55 system.Start();
56}
57
58void Renderer::Stop() {
59 system.Stop();
60}
61
62Result Renderer::RequestUpdate(std::span<const u8> input, std::span<u8> performance,
63 std::span<u8> output) {
64 return system.Update(input, performance, output);
65}
66
67} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/audio_renderer.h b/src/audio_core/renderer/audio_renderer.h
new file mode 100644
index 000000000..90c6f9727
--- /dev/null
+++ b/src/audio_core/renderer/audio_renderer.h
@@ -0,0 +1,97 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <span>
7
8#include "audio_core/renderer/system.h"
9#include "core/hle/service/audio/errors.h"
10
11namespace Core {
12class System;
13}
14
15namespace Kernel {
16class KTransferMemory;
17}
18
19namespace AudioCore {
20struct AudioRendererParameterInternal;
21
22namespace AudioRenderer {
23class Manager;
24
25/**
26 * Audio Renderer, wraps the main audio system and is mainly responsible for handling service calls.
27 */
28class Renderer {
29public:
30 explicit Renderer(Core::System& system, Manager& manager, Kernel::KEvent* rendered_event);
31
32 /**
33 * Initialize the renderer.
34 * Registers the system with the AudioRenderer::Manager, allocates workbuffers and initializes
35 * everything to a default state.
36 *
37 * @param params - Input parameters to initialize the system with.
38 * @param transfer_memory - Game-supplied memory for all workbuffers. Unused.
39 * @param transfer_memory_size - Size of the transfer memory. Unused.
40 * @param process_handle - Process handle, also used for memory. Unused.
41 * @param applet_resource_user_id - Applet id for this renderer. Unused.
42 * @param session_id - Session id of this renderer.
43 * @return Result code.
44 */
45 Result Initialize(const AudioRendererParameterInternal& params,
46 Kernel::KTransferMemory* transfer_memory, u64 transfer_memory_size,
47 u32 process_handle, u64 applet_resource_user_id, s32 session_id);
48
49 /**
50 * Finalize the renderer for shutdown.
51 */
52 void Finalize();
53
54 /**
55 * Get the renderer's system.
56 *
57 * @return Reference to the system.
58 */
59 System& GetSystem();
60
61 /**
62 * Start the renderer.
63 */
64 void Start();
65
66 /**
67 * Stop the renderer.
68 */
69 void Stop();
70
71 /**
72 * Update the audio renderer with new information.
73 * Called via RequestUpdate from the AudRen:U service.
74 *
75 * @param input - Input buffer containing the new data.
76 * @param performance - Optional performance buffer for outputting performance metrics.
77 * @param output - Output data from the renderer.
78 * @return Result code.
79 */
80 Result RequestUpdate(std::span<const u8> input, std::span<u8> performance,
81 std::span<u8> output);
82
83private:
84 /// System core
85 Core::System& core;
86 /// Manager this renderer is registered with
87 Manager& manager;
88 /// Is the audio renderer initialized?
89 bool initialized{};
90 /// Is the system registered with the manager?
91 bool system_registered{};
92 /// Audio render system, main driver of audio rendering
93 System system;
94};
95
96} // namespace AudioRenderer
97} // namespace AudioCore
diff --git a/src/audio_core/renderer/behavior/behavior_info.cpp b/src/audio_core/renderer/behavior/behavior_info.cpp
new file mode 100644
index 000000000..c5d4d66d8
--- /dev/null
+++ b/src/audio_core/renderer/behavior/behavior_info.cpp
@@ -0,0 +1,191 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/common/feature_support.h"
5#include "audio_core/renderer/behavior/behavior_info.h"
6
7namespace AudioCore::AudioRenderer {
8
9BehaviorInfo::BehaviorInfo() : process_revision{CurrentRevision} {}
10
11u32 BehaviorInfo::GetProcessRevisionNum() const {
12 return process_revision;
13}
14
15u32 BehaviorInfo::GetProcessRevision() const {
16 return Common::MakeMagic('R', 'E', 'V',
17 static_cast<char>(static_cast<u8>('0') + process_revision));
18}
19
20u32 BehaviorInfo::GetUserRevisionNum() const {
21 return user_revision;
22}
23
24u32 BehaviorInfo::GetUserRevision() const {
25 return Common::MakeMagic('R', 'E', 'V',
26 static_cast<char>(static_cast<u8>('0') + user_revision));
27}
28
29void BehaviorInfo::SetUserLibRevision(const u32 user_revision_) {
30 user_revision = GetRevisionNum(user_revision_);
31}
32
33void BehaviorInfo::ClearError() {
34 error_count = 0;
35}
36
37void BehaviorInfo::AppendError(ErrorInfo& error) {
38 LOG_ERROR(Service_Audio, "Error during RequestUpdate, reporting code {:04X} address {:08X}",
39 error.error_code.raw, error.address);
40 if (error_count < MaxErrors) {
41 errors[error_count++] = error;
42 }
43}
44
45void BehaviorInfo::CopyErrorInfo(std::span<ErrorInfo> out_errors, u32& out_count) {
46 auto error_count_{std::min(error_count, MaxErrors)};
47 std::memset(out_errors.data(), 0, MaxErrors * sizeof(ErrorInfo));
48
49 for (size_t i = 0; i < error_count_; i++) {
50 out_errors[i] = errors[i];
51 }
52 out_count = error_count_;
53}
54
55void BehaviorInfo::UpdateFlags(const Flags flags_) {
56 flags = flags_;
57}
58
59bool BehaviorInfo::IsMemoryForceMappingEnabled() const {
60 return flags.IsMemoryForceMappingEnabled;
61}
62
63bool BehaviorInfo::IsAdpcmLoopContextBugFixed() const {
64 return CheckFeatureSupported(SupportTags::AdpcmLoopContextBugFix, user_revision);
65}
66
67bool BehaviorInfo::IsSplitterSupported() const {
68 return CheckFeatureSupported(SupportTags::Splitter, user_revision);
69}
70
71bool BehaviorInfo::IsSplitterBugFixed() const {
72 return CheckFeatureSupported(SupportTags::SplitterBugFix, user_revision);
73}
74
75bool BehaviorInfo::IsEffectInfoVersion2Supported() const {
76 return CheckFeatureSupported(SupportTags::EffectInfoVer2, user_revision);
77}
78
79bool BehaviorInfo::IsVariadicCommandBufferSizeSupported() const {
80 return CheckFeatureSupported(SupportTags::AudioRendererVariadicCommandBufferSize,
81 user_revision);
82}
83
84bool BehaviorInfo::IsWaveBufferVer2Supported() const {
85 return CheckFeatureSupported(SupportTags::WaveBufferVer2, user_revision);
86}
87
88bool BehaviorInfo::IsLongSizePreDelaySupported() const {
89 return CheckFeatureSupported(SupportTags::LongSizePreDelay, user_revision);
90}
91
92bool BehaviorInfo::IsCommandProcessingTimeEstimatorVersion2Supported() const {
93 return CheckFeatureSupported(SupportTags::CommandProcessingTimeEstimatorVersion2,
94 user_revision);
95}
96
97bool BehaviorInfo::IsCommandProcessingTimeEstimatorVersion3Supported() const {
98 return CheckFeatureSupported(SupportTags::CommandProcessingTimeEstimatorVersion3,
99 user_revision);
100}
101
102bool BehaviorInfo::IsCommandProcessingTimeEstimatorVersion4Supported() const {
103 return CheckFeatureSupported(SupportTags::CommandProcessingTimeEstimatorVersion4,
104 user_revision);
105}
106
107bool BehaviorInfo::IsCommandProcessingTimeEstimatorVersion5Supported() const {
108 return CheckFeatureSupported(SupportTags::CommandProcessingTimeEstimatorVersion4,
109 user_revision);
110}
111
112bool BehaviorInfo::IsAudioRendererProcessingTimeLimit70PercentSupported() const {
113 return CheckFeatureSupported(SupportTags::AudioRendererProcessingTimeLimit70Percent,
114 user_revision);
115}
116
117bool BehaviorInfo::IsAudioRendererProcessingTimeLimit75PercentSupported() const {
118 return CheckFeatureSupported(SupportTags::AudioRendererProcessingTimeLimit75Percent,
119 user_revision);
120}
121
122bool BehaviorInfo::IsAudioRendererProcessingTimeLimit80PercentSupported() const {
123 return CheckFeatureSupported(SupportTags::AudioRendererProcessingTimeLimit80Percent,
124 user_revision);
125}
126
127bool BehaviorInfo::IsFlushVoiceWaveBuffersSupported() const {
128 return CheckFeatureSupported(SupportTags::FlushVoiceWaveBuffers, user_revision);
129}
130
131bool BehaviorInfo::IsElapsedFrameCountSupported() const {
132 return CheckFeatureSupported(SupportTags::ElapsedFrameCount, user_revision);
133}
134
135bool BehaviorInfo::IsPerformanceMetricsDataFormatVersion2Supported() const {
136 return CheckFeatureSupported(SupportTags::PerformanceMetricsDataFormatVersion2, user_revision);
137}
138
139size_t BehaviorInfo::GetPerformanceMetricsDataFormat() const {
140 if (CheckFeatureSupported(SupportTags::PerformanceMetricsDataFormatVersion2, user_revision)) {
141 return 2;
142 }
143 return 1;
144}
145
146bool BehaviorInfo::IsVoicePitchAndSrcSkippedSupported() const {
147 return CheckFeatureSupported(SupportTags::VoicePitchAndSrcSkipped, user_revision);
148}
149
150bool BehaviorInfo::IsVoicePlayedSampleCountResetAtLoopPointSupported() const {
151 return CheckFeatureSupported(SupportTags::VoicePlayedSampleCountResetAtLoopPoint,
152 user_revision);
153}
154
155bool BehaviorInfo::IsBiquadFilterEffectStateClearBugFixed() const {
156 return CheckFeatureSupported(SupportTags::BiquadFilterEffectStateClearBugFix, user_revision);
157}
158
159bool BehaviorInfo::IsVolumeMixParameterPrecisionQ23Supported() const {
160 return CheckFeatureSupported(SupportTags::VolumeMixParameterPrecisionQ23, user_revision);
161}
162
163bool BehaviorInfo::UseBiquadFilterFloatProcessing() const {
164 return CheckFeatureSupported(SupportTags::BiquadFilterFloatProcessing, user_revision);
165}
166
167bool BehaviorInfo::IsMixInParameterDirtyOnlyUpdateSupported() const {
168 return CheckFeatureSupported(SupportTags::MixInParameterDirtyOnlyUpdate, user_revision);
169}
170
171bool BehaviorInfo::UseMultiTapBiquadFilterProcessing() const {
172 return CheckFeatureSupported(SupportTags::MultiTapBiquadFilterProcessing, user_revision);
173}
174
175bool BehaviorInfo::IsDeviceApiVersion2Supported() const {
176 return CheckFeatureSupported(SupportTags::DeviceApiVersion2, user_revision);
177}
178
179bool BehaviorInfo::IsDelayChannelMappingChanged() const {
180 return CheckFeatureSupported(SupportTags::DelayChannelMappingChange, user_revision);
181}
182
183bool BehaviorInfo::IsReverbChannelMappingChanged() const {
184 return CheckFeatureSupported(SupportTags::ReverbChannelMappingChange, user_revision);
185}
186
187bool BehaviorInfo::IsI3dl2ReverbChannelMappingChanged() const {
188 return CheckFeatureSupported(SupportTags::I3dl2ReverbChannelMappingChange, user_revision);
189}
190
191} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/behavior/behavior_info.h b/src/audio_core/renderer/behavior/behavior_info.h
new file mode 100644
index 000000000..7333c297f
--- /dev/null
+++ b/src/audio_core/renderer/behavior/behavior_info.h
@@ -0,0 +1,376 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7#include <span>
8
9#include "audio_core/common/common.h"
10#include "common/common_types.h"
11#include "core/hle/service/audio/errors.h"
12
13namespace AudioCore::AudioRenderer {
14/**
15 * Holds host and user revisions, checks whether render features can be enabled, and reports errors.
16 */
17class BehaviorInfo {
18 static constexpr u32 MaxErrors = 10;
19
20public:
21 struct ErrorInfo {
22 /* 0x00 */ Result error_code{0};
23 /* 0x04 */ u32 unk_04;
24 /* 0x08 */ CpuAddr address;
25 };
26 static_assert(sizeof(ErrorInfo) == 0x10, "BehaviorInfo::ErrorInfo has the wrong size!");
27
28 struct Flags {
29 u64 IsMemoryForceMappingEnabled : 1;
30 };
31
32 struct InParameter {
33 /* 0x00 */ u32 revision;
34 /* 0x08 */ Flags flags;
35 };
36 static_assert(sizeof(InParameter) == 0x10, "BehaviorInfo::InParameter has the wrong size!");
37
38 struct OutStatus {
39 /* 0x00 */ std::array<ErrorInfo, MaxErrors> errors;
40 /* 0xA0 */ u32 error_count;
41 /* 0xA4 */ char unkA4[0xC];
42 };
43 static_assert(sizeof(OutStatus) == 0xB0, "BehaviorInfo::OutStatus has the wrong size!");
44
45 BehaviorInfo();
46
47 /**
48 * Get the host revision as a number.
49 *
50 * @return The host revision.
51 */
52 u32 GetProcessRevisionNum() const;
53
54 /**
55 * Get the host revision in chars, e.g REV8.
56 * Rev 10 and higher use the ascii characters above 9.
57 * E.g:
58 * Rev 10 = REV:
59 * Rev 11 = REV;
60 *
61 * @return The host revision.
62 */
63 u32 GetProcessRevision() const;
64
65 /**
66 * Get the user revision as a number.
67 *
68 * @return The user revision.
69 */
70 u32 GetUserRevisionNum() const;
71
72 /**
73 * Get the user revision in chars, e.g REV8.
74 * Rev 10 and higher use the ascii characters above 9. REV: REV; etc.
75 *
76 * @return The user revision.
77 */
78 u32 GetUserRevision() const;
79
80 /**
81 * Set the user revision.
82 *
83 * @param user_revision - The user's revision.
84 */
85 void SetUserLibRevision(u32 user_revision);
86
87 /**
88 * Clear the current error count.
89 */
90 void ClearError();
91
92 /**
93 * Append an error to the error list.
94 *
95 * @param error - The new error.
96 */
97 void AppendError(ErrorInfo& error);
98
99 /**
100 * Copy errors to the given output container.
101 *
102 * @param out_errors - Output container to receive the errors.
103 * @param out_count - The number of errors written.
104 */
105 void CopyErrorInfo(std::span<ErrorInfo> out_errors, u32& out_count);
106
107 /**
108 * Update the behaviour flags.
109 *
110 * @param flags - New flags to use.
111 */
112 void UpdateFlags(Flags flags);
113
114 /**
115 * Check if memory pools can be forcibly mapped.
116 *
117 * @return True if enabled, otherwise false.
118 */
119 bool IsMemoryForceMappingEnabled() const;
120
121 /**
122 * Check if the ADPCM context bug is fixed.
123 * The ADPCM context was not being sent to the AudioRenderer, leading to incorrect scaling being
124 * used.
125 *
126 * @return True if fixed, otherwise false.
127 */
128 bool IsAdpcmLoopContextBugFixed() const;
129
130 /**
131 * Check if the splitter is supported.
132 *
133 * @return True if supported, otherwise false.
134 */
135 bool IsSplitterSupported() const;
136
137 /**
138 * Check if the splitter bug is fixed.
139 * Update is given the wrong number of splitter destinations, leading to invalid data
140 * being processed.
141 *
142 * @return True if supported, otherwise false.
143 */
144 bool IsSplitterBugFixed() const;
145
146 /**
147 * Check if effects version 2 are supported.
148 * This gives support for returning effect states from the AudioRenderer, currently only used
149 * for Limiter statistics.
150 *
151 * @return True if supported, otherwise false.
152 */
153 bool IsEffectInfoVersion2Supported() const;
154
155 /**
156 * Check if a variadic command buffer is supported.
157 * As of Rev 5 with the added optional performance metric logging, the command
158 * buffer can be a variable size, so take that into account for calcualting its size.
159 *
160 * @return True if supported, otherwise false.
161 */
162 bool IsVariadicCommandBufferSizeSupported() const;
163
164 /**
165 * Check if wave buffers version 2 are supported.
166 * See WaveBufferVersion1 and WaveBufferVersion2.
167 *
168 * @return True if supported, otherwise false.
169 */
170 bool IsWaveBufferVer2Supported() const;
171
172 /**
173 * Check if long size pre delay is supported.
174 * This allows a longer initial delay time for the Reverb command.
175 *
176 * @return True if supported, otherwise false.
177 */
178 bool IsLongSizePreDelaySupported() const;
179
180 /**
181 * Check if the command time estimator version 2 is supported.
182 *
183 * @return True if supported, otherwise false.
184 */
185 bool IsCommandProcessingTimeEstimatorVersion2Supported() const;
186
187 /**
188 * Check if the command time estimator version 3 is supported.
189 *
190 * @return True if supported, otherwise false.
191 */
192 bool IsCommandProcessingTimeEstimatorVersion3Supported() const;
193
194 /**
195 * Check if the command time estimator version 4 is supported.
196 *
197 * @return True if supported, otherwise false.
198 */
199 bool IsCommandProcessingTimeEstimatorVersion4Supported() const;
200
201 /**
202 * Check if the command time estimator version 5 is supported.
203 *
204 * @return True if supported, otherwise false.
205 */
206 bool IsCommandProcessingTimeEstimatorVersion5Supported() const;
207
208 /**
209 * Check if the AudioRenderer can use up to 70% of the allocated processing timeslice.
210 *
211 * @return True if supported, otherwise false.
212 */
213 bool IsAudioRendererProcessingTimeLimit70PercentSupported() const;
214
215 /**
216 * Check if the AudioRenderer can use up to 75% of the allocated processing timeslice.
217 *
218 * @return True if supported, otherwise false.
219 */
220 bool IsAudioRendererProcessingTimeLimit75PercentSupported() const;
221
222 /**
223 * Check if the AudioRenderer can use up to 80% of the allocated processing timeslice.
224 *
225 * @return True if supported, otherwise false.
226 */
227 bool IsAudioRendererProcessingTimeLimit80PercentSupported() const;
228
229 /**
230 * Check if voice flushing is supported
231 * This allowws low-priority voices to be dropped if the AudioRenderer is running behind.
232 *
233 * @return True if supported, otherwise false.
234 */
235 bool IsFlushVoiceWaveBuffersSupported() const;
236
237 /**
238 * Check if counting the number of elapsed frames is supported.
239 * This adds extra output to RequestUpdate, returning the number of times the AudioRenderer
240 * processed a command list.
241 *
242 * @return True if supported, otherwise false.
243 */
244 bool IsElapsedFrameCountSupported() const;
245
246 /**
247 * Check if performance metrics version 2 are supported.
248 * This adds extra output to RequestUpdate, returning the number of times the AudioRenderer
249 * (Unused?).
250 *
251 * @return True if supported, otherwise false.
252 */
253 bool IsPerformanceMetricsDataFormatVersion2Supported() const;
254
255 /**
256 * Get the supported performance metrics version.
257 * Version 2 logs some extra fields in output, such as number of voices dropped,
258 * processing start time, if the AudioRenderer exceeded its time, etc.
259 *
260 * @return Version supported, either 1 or 2.
261 */
262 size_t GetPerformanceMetricsDataFormat() const;
263
264 /**
265 * Check if skipping voice pitch and sample rate conversion is supported.
266 * This speeds up the data source commands by skipping resampling if unwanted.
267 * See AudioCore::AudioRenderer::DecodeFromWaveBuffers
268 *
269 * @return True if supported, otherwise false.
270 */
271 bool IsVoicePitchAndSrcSkippedSupported() const;
272
273 /**
274 * Check if resetting played sample count at loop points is supported.
275 * This resets the number of samples played in a voice state when a loop point is reached.
276 * See AudioCore::AudioRenderer::DecodeFromWaveBuffers
277 *
278 * @return True if supported, otherwise false.
279 */
280 bool IsVoicePlayedSampleCountResetAtLoopPointSupported() const;
281
282 /**
283 * Check if the clear state bug for biquad filters is fixed.
284 * The biquad state was not marked as needing re-initialisation when the effect was updated, it
285 * was only initialized once with a new effect.
286 *
287 * @return True if fixed, otherwise false.
288 */
289 bool IsBiquadFilterEffectStateClearBugFixed() const;
290
291 /**
292 * Check if Q23 precision is supported for fixed point.
293 *
294 * @return True if supported, otherwise false.
295 */
296 bool IsVolumeMixParameterPrecisionQ23Supported() const;
297
298 /**
299 * Check if float processing for biuad filters is supported.
300 *
301 * @return True if supported, otherwise false.
302 */
303 bool UseBiquadFilterFloatProcessing() const;
304
305 /**
306 * Check if dirty-only mix updates are supported.
307 * This saves a lot of buffer size as mixes can be large and not change much.
308 *
309 * @return True if supported, otherwise false.
310 */
311 bool IsMixInParameterDirtyOnlyUpdateSupported() const;
312
313 /**
314 * Check if multi-tap biquad filters are supported.
315 *
316 * @return True if supported, otherwise false.
317 */
318 bool UseMultiTapBiquadFilterProcessing() const;
319
320 /**
321 * Check if device api version 2 is supported.
322 * In the SDK but not in any sysmodule? Not sure, left here for completeness anyway.
323 *
324 * @return True if supported, otherwise false.
325 */
326 bool IsDeviceApiVersion2Supported() const;
327
328 /**
329 * Check if new channel mappings are used for Delay commands.
330 * Older commands used:
331 * front left/front right/back left/back right/center/lfe
332 * Whereas everywhere else in the code uses:
333 * front left/front right/center/lfe/back left/back right
334 * This corrects that and makes everything standardised.
335 *
336 * @return True if supported, otherwise false.
337 */
338 bool IsDelayChannelMappingChanged() const;
339
340 /**
341 * Check if new channel mappings are used for Reverb commands.
342 * Older commands used:
343 * front left/front right/back left/back right/center/lfe
344 * Whereas everywhere else in the code uses:
345 * front left/front right/center/lfe/back left/back right
346 * This corrects that and makes everything standardised.
347 *
348 * @return True if supported, otherwise false.
349 */
350 bool IsReverbChannelMappingChanged() const;
351
352 /**
353 * Check if new channel mappings are used for I3dl2Reverb commands.
354 * Older commands used:
355 * front left/front right/back left/back right/center/lfe
356 * Whereas everywhere else in the code uses:
357 * front left/front right/center/lfe/back left/back right
358 * This corrects that and makes everything standardised.
359 *
360 * @return True if supported, otherwise false.
361 */
362 bool IsI3dl2ReverbChannelMappingChanged() const;
363
364 /// Host version
365 u32 process_revision;
366 /// User version
367 u32 user_revision{};
368 /// Behaviour flags
369 Flags flags{};
370 /// Errors generated and reported during Update
371 std::array<ErrorInfo, MaxErrors> errors{};
372 /// Error count
373 u32 error_count{};
374};
375
376} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/behavior/info_updater.cpp b/src/audio_core/renderer/behavior/info_updater.cpp
new file mode 100644
index 000000000..06a37e1a6
--- /dev/null
+++ b/src/audio_core/renderer/behavior/info_updater.cpp
@@ -0,0 +1,539 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/common/feature_support.h"
5#include "audio_core/renderer/behavior/behavior_info.h"
6#include "audio_core/renderer/behavior/info_updater.h"
7#include "audio_core/renderer/effect/effect_context.h"
8#include "audio_core/renderer/effect/effect_reset.h"
9#include "audio_core/renderer/memory/memory_pool_info.h"
10#include "audio_core/renderer/mix/mix_context.h"
11#include "audio_core/renderer/performance/performance_manager.h"
12#include "audio_core/renderer/sink/circular_buffer_sink_info.h"
13#include "audio_core/renderer/sink/device_sink_info.h"
14#include "audio_core/renderer/sink/sink_context.h"
15#include "audio_core/renderer/splitter/splitter_context.h"
16#include "audio_core/renderer/voice/voice_context.h"
17
18namespace AudioCore::AudioRenderer {
19
20InfoUpdater::InfoUpdater(std::span<const u8> input_, std::span<u8> output_,
21 const u32 process_handle_, BehaviorInfo& behaviour_)
22 : input{input_.data() + sizeof(UpdateDataHeader)},
23 input_origin{input_}, output{output_.data() + sizeof(UpdateDataHeader)},
24 output_origin{output_}, in_header{reinterpret_cast<const UpdateDataHeader*>(
25 input_origin.data())},
26 out_header{reinterpret_cast<UpdateDataHeader*>(output_origin.data())},
27 expected_input_size{input_.size()}, expected_output_size{output_.size()},
28 process_handle{process_handle_}, behaviour{behaviour_} {
29 std::construct_at<UpdateDataHeader>(out_header, behaviour.GetProcessRevision());
30}
31
32Result InfoUpdater::UpdateVoiceChannelResources(VoiceContext& voice_context) {
33 const auto voice_count{voice_context.GetCount()};
34 std::span<const VoiceChannelResource::InParameter> in_params{
35 reinterpret_cast<const VoiceChannelResource::InParameter*>(input), voice_count};
36
37 for (u32 i = 0; i < voice_count; i++) {
38 auto& resource{voice_context.GetChannelResource(i)};
39 resource.in_use = in_params[i].in_use;
40 if (in_params[i].in_use) {
41 resource.mix_volumes = in_params[i].mix_volumes;
42 }
43 }
44
45 const auto consumed_input_size{voice_count *
46 static_cast<u32>(sizeof(VoiceChannelResource::InParameter))};
47 if (consumed_input_size != in_header->voice_resources_size) {
48 LOG_ERROR(Service_Audio,
49 "Consumed an incorrect voice resource size, header size={}, consumed={}",
50 in_header->voice_resources_size, consumed_input_size);
51 return Service::Audio::ERR_INVALID_UPDATE_DATA;
52 }
53
54 input += consumed_input_size;
55 return ResultSuccess;
56}
57
58Result InfoUpdater::UpdateVoices(VoiceContext& voice_context,
59 std::span<MemoryPoolInfo> memory_pools,
60 const u32 memory_pool_count) {
61 const PoolMapper pool_mapper(process_handle, memory_pools, memory_pool_count,
62 behaviour.IsMemoryForceMappingEnabled());
63 const auto voice_count{voice_context.GetCount()};
64 std::span<const VoiceInfo::InParameter> in_params{
65 reinterpret_cast<const VoiceInfo::InParameter*>(input), voice_count};
66 std::span<VoiceInfo::OutStatus> out_params{reinterpret_cast<VoiceInfo::OutStatus*>(output),
67 voice_count};
68
69 for (u32 i = 0; i < voice_count; i++) {
70 auto& voice_info{voice_context.GetInfo(i)};
71 voice_info.in_use = false;
72 }
73
74 u32 new_voice_count{0};
75
76 for (u32 i = 0; i < voice_count; i++) {
77 const auto& in_param{in_params[i]};
78 std::array<VoiceState*, MaxChannels> voice_states{};
79
80 if (!in_param.in_use) {
81 continue;
82 }
83
84 auto& voice_info{voice_context.GetInfo(in_param.id)};
85
86 for (u32 channel = 0; channel < in_param.channel_count; channel++) {
87 voice_states[channel] = &voice_context.GetState(in_param.channel_resource_ids[channel]);
88 }
89
90 if (in_param.is_new) {
91 voice_info.Initialize();
92
93 for (u32 channel = 0; channel < in_param.channel_count; channel++) {
94 std::memset(voice_states[channel], 0, sizeof(VoiceState));
95 }
96 }
97
98 BehaviorInfo::ErrorInfo update_error{};
99 voice_info.UpdateParameters(update_error, in_param, pool_mapper, behaviour);
100
101 if (!update_error.error_code.IsSuccess()) {
102 behaviour.AppendError(update_error);
103 }
104
105 std::array<std::array<BehaviorInfo::ErrorInfo, 2>, MaxWaveBuffers> wavebuffer_errors{};
106 voice_info.UpdateWaveBuffers(wavebuffer_errors, MaxWaveBuffers * 2, in_param, voice_states,
107 pool_mapper, behaviour);
108
109 for (auto& wavebuffer_error : wavebuffer_errors) {
110 for (auto& error : wavebuffer_error) {
111 if (error.error_code.IsError()) {
112 behaviour.AppendError(error);
113 }
114 }
115 }
116
117 voice_info.WriteOutStatus(out_params[i], in_param, voice_states);
118 new_voice_count += in_param.channel_count;
119 }
120
121 auto consumed_input_size{voice_count * static_cast<u32>(sizeof(VoiceInfo::InParameter))};
122 auto consumed_output_size{voice_count * static_cast<u32>(sizeof(VoiceInfo::OutStatus))};
123 if (consumed_input_size != in_header->voices_size) {
124 LOG_ERROR(Service_Audio, "Consumed an incorrect voices size, header size={}, consumed={}",
125 in_header->voices_size, consumed_input_size);
126 return Service::Audio::ERR_INVALID_UPDATE_DATA;
127 }
128
129 out_header->voices_size = consumed_output_size;
130 out_header->size += consumed_output_size;
131 input += consumed_input_size;
132 output += consumed_output_size;
133
134 voice_context.SetActiveCount(new_voice_count);
135
136 return ResultSuccess;
137}
138
139Result InfoUpdater::UpdateEffects(EffectContext& effect_context, const bool renderer_active,
140 std::span<MemoryPoolInfo> memory_pools,
141 const u32 memory_pool_count) {
142 if (behaviour.IsEffectInfoVersion2Supported()) {
143 return UpdateEffectsVersion2(effect_context, renderer_active, memory_pools,
144 memory_pool_count);
145 } else {
146 return UpdateEffectsVersion1(effect_context, renderer_active, memory_pools,
147 memory_pool_count);
148 }
149}
150
151Result InfoUpdater::UpdateEffectsVersion1(EffectContext& effect_context, const bool renderer_active,
152 std::span<MemoryPoolInfo> memory_pools,
153 const u32 memory_pool_count) {
154 PoolMapper pool_mapper(process_handle, memory_pools, memory_pool_count,
155 behaviour.IsMemoryForceMappingEnabled());
156
157 const auto effect_count{effect_context.GetCount()};
158
159 std::span<const EffectInfoBase::InParameterVersion1> in_params{
160 reinterpret_cast<const EffectInfoBase::InParameterVersion1*>(input), effect_count};
161 std::span<EffectInfoBase::OutStatusVersion1> out_params{
162 reinterpret_cast<EffectInfoBase::OutStatusVersion1*>(output), effect_count};
163
164 for (u32 i = 0; i < effect_count; i++) {
165 auto effect_info{&effect_context.GetInfo(i)};
166 if (effect_info->GetType() != in_params[i].type) {
167 effect_info->ForceUnmapBuffers(pool_mapper);
168 ResetEffect(effect_info, in_params[i].type);
169 }
170
171 BehaviorInfo::ErrorInfo error_info{};
172 effect_info->Update(error_info, in_params[i], pool_mapper);
173 if (error_info.error_code.IsError()) {
174 behaviour.AppendError(error_info);
175 }
176
177 effect_info->StoreStatus(out_params[i], renderer_active);
178 }
179
180 auto consumed_input_size{effect_count *
181 static_cast<u32>(sizeof(EffectInfoBase::InParameterVersion1))};
182 auto consumed_output_size{effect_count *
183 static_cast<u32>(sizeof(EffectInfoBase::OutStatusVersion1))};
184 if (consumed_input_size != in_header->effects_size) {
185 LOG_ERROR(Service_Audio, "Consumed an incorrect effects size, header size={}, consumed={}",
186 in_header->effects_size, consumed_input_size);
187 return Service::Audio::ERR_INVALID_UPDATE_DATA;
188 }
189
190 out_header->effects_size = consumed_output_size;
191 out_header->size += consumed_output_size;
192 input += consumed_input_size;
193 output += consumed_output_size;
194
195 return ResultSuccess;
196}
197
198Result InfoUpdater::UpdateEffectsVersion2(EffectContext& effect_context, const bool renderer_active,
199 std::span<MemoryPoolInfo> memory_pools,
200 const u32 memory_pool_count) {
201 PoolMapper pool_mapper(process_handle, memory_pools, memory_pool_count,
202 behaviour.IsMemoryForceMappingEnabled());
203
204 const auto effect_count{effect_context.GetCount()};
205
206 std::span<const EffectInfoBase::InParameterVersion2> in_params{
207 reinterpret_cast<const EffectInfoBase::InParameterVersion2*>(input), effect_count};
208 std::span<EffectInfoBase::OutStatusVersion2> out_params{
209 reinterpret_cast<EffectInfoBase::OutStatusVersion2*>(output), effect_count};
210
211 for (u32 i = 0; i < effect_count; i++) {
212 auto effect_info{&effect_context.GetInfo(i)};
213 if (effect_info->GetType() != in_params[i].type) {
214 effect_info->ForceUnmapBuffers(pool_mapper);
215 ResetEffect(effect_info, in_params[i].type);
216 }
217
218 BehaviorInfo::ErrorInfo error_info{};
219 effect_info->Update(error_info, in_params[i], pool_mapper);
220
221 if (error_info.error_code.IsError()) {
222 behaviour.AppendError(error_info);
223 }
224
225 effect_info->StoreStatus(out_params[i], renderer_active);
226
227 if (in_params[i].is_new) {
228 effect_info->InitializeResultState(effect_context.GetDspSharedResultState(i));
229 effect_info->InitializeResultState(effect_context.GetResultState(i));
230 }
231 effect_info->UpdateResultState(out_params[i].result_state,
232 effect_context.GetResultState(i));
233 }
234
235 auto consumed_input_size{effect_count *
236 static_cast<u32>(sizeof(EffectInfoBase::InParameterVersion2))};
237 auto consumed_output_size{effect_count *
238 static_cast<u32>(sizeof(EffectInfoBase::OutStatusVersion2))};
239 if (consumed_input_size != in_header->effects_size) {
240 LOG_ERROR(Service_Audio, "Consumed an incorrect effects size, header size={}, consumed={}",
241 in_header->effects_size, consumed_input_size);
242 return Service::Audio::ERR_INVALID_UPDATE_DATA;
243 }
244
245 out_header->effects_size = consumed_output_size;
246 out_header->size += consumed_output_size;
247 input += consumed_input_size;
248 output += consumed_output_size;
249
250 return ResultSuccess;
251}
252
253Result InfoUpdater::UpdateMixes(MixContext& mix_context, const u32 mix_buffer_count,
254 EffectContext& effect_context, SplitterContext& splitter_context) {
255 s32 mix_count{0};
256 u32 consumed_input_size{0};
257
258 if (behaviour.IsMixInParameterDirtyOnlyUpdateSupported()) {
259 auto in_dirty_params{reinterpret_cast<const MixInfo::InDirtyParameter*>(input)};
260 mix_count = in_dirty_params->count;
261 input += sizeof(MixInfo::InDirtyParameter);
262 consumed_input_size = static_cast<u32>(sizeof(MixInfo::InDirtyParameter) +
263 mix_count * sizeof(MixInfo::InParameter));
264 } else {
265 mix_count = mix_context.GetCount();
266 consumed_input_size = static_cast<u32>(mix_count * sizeof(MixInfo::InParameter));
267 }
268
269 if (mix_buffer_count == 0) {
270 return Service::Audio::ERR_INVALID_UPDATE_DATA;
271 }
272
273 std::span<const MixInfo::InParameter> in_params{
274 reinterpret_cast<const MixInfo::InParameter*>(input), static_cast<size_t>(mix_count)};
275
276 u32 total_buffer_count{0};
277 for (s32 i = 0; i < mix_count; i++) {
278 const auto& params{in_params[i]};
279
280 if (params.in_use) {
281 total_buffer_count += params.buffer_count;
282 if (params.dest_mix_id > static_cast<s32>(mix_context.GetCount()) &&
283 params.dest_mix_id != UnusedMixId && params.mix_id != FinalMixId) {
284 return Service::Audio::ERR_INVALID_UPDATE_DATA;
285 }
286 }
287 }
288
289 if (total_buffer_count > mix_buffer_count) {
290 return Service::Audio::ERR_INVALID_UPDATE_DATA;
291 }
292
293 bool mix_dirty{false};
294 for (s32 i = 0; i < mix_count; i++) {
295 const auto& params{in_params[i]};
296
297 s32 mix_id{i};
298 if (behaviour.IsMixInParameterDirtyOnlyUpdateSupported()) {
299 mix_id = params.mix_id;
300 }
301
302 auto mix_info{mix_context.GetInfo(mix_id)};
303 if (mix_info->in_use != params.in_use) {
304 mix_info->in_use = params.in_use;
305 if (!params.in_use) {
306 mix_info->ClearEffectProcessingOrder();
307 }
308 mix_dirty = true;
309 }
310
311 if (params.in_use) {
312 mix_dirty |= mix_info->Update(mix_context.GetEdgeMatrix(), params, effect_context,
313 splitter_context, behaviour);
314 }
315 }
316
317 if (mix_dirty) {
318 if (behaviour.IsSplitterSupported() && splitter_context.UsingSplitter()) {
319 if (!mix_context.TSortInfo(splitter_context)) {
320 return Service::Audio::ERR_INVALID_UPDATE_DATA;
321 }
322 } else {
323 mix_context.SortInfo();
324 }
325 }
326
327 if (consumed_input_size != in_header->mix_size) {
328 LOG_ERROR(Service_Audio, "Consumed an incorrect mixes size, header size={}, consumed={}",
329 in_header->mix_size, consumed_input_size);
330 return Service::Audio::ERR_INVALID_UPDATE_DATA;
331 }
332
333 input += mix_count * sizeof(MixInfo::InParameter);
334
335 return ResultSuccess;
336}
337
338Result InfoUpdater::UpdateSinks(SinkContext& sink_context, std::span<MemoryPoolInfo> memory_pools,
339 const u32 memory_pool_count) {
340 PoolMapper pool_mapper(process_handle, memory_pools, memory_pool_count,
341 behaviour.IsMemoryForceMappingEnabled());
342
343 std::span<const SinkInfoBase::InParameter> in_params{
344 reinterpret_cast<const SinkInfoBase::InParameter*>(input), memory_pool_count};
345 std::span<SinkInfoBase::OutStatus> out_params{
346 reinterpret_cast<SinkInfoBase::OutStatus*>(output), memory_pool_count};
347
348 const auto sink_count{sink_context.GetCount()};
349
350 for (u32 i = 0; i < sink_count; i++) {
351 const auto& params{in_params[i]};
352 auto sink_info{sink_context.GetInfo(i)};
353
354 if (sink_info->GetType() != params.type) {
355 sink_info->CleanUp();
356 switch (params.type) {
357 case SinkInfoBase::Type::Invalid:
358 std::construct_at<SinkInfoBase>(reinterpret_cast<SinkInfoBase*>(sink_info));
359 break;
360 case SinkInfoBase::Type::DeviceSink:
361 std::construct_at<DeviceSinkInfo>(reinterpret_cast<DeviceSinkInfo*>(sink_info));
362 break;
363 case SinkInfoBase::Type::CircularBufferSink:
364 std::construct_at<CircularBufferSinkInfo>(
365 reinterpret_cast<CircularBufferSinkInfo*>(sink_info));
366 break;
367 default:
368 LOG_ERROR(Service_Audio, "Invalid sink type {}", static_cast<u32>(params.type));
369 break;
370 }
371 }
372
373 BehaviorInfo::ErrorInfo error_info{};
374 sink_info->Update(error_info, out_params[i], params, pool_mapper);
375
376 if (error_info.error_code.IsError()) {
377 behaviour.AppendError(error_info);
378 }
379 }
380
381 const auto consumed_input_size{sink_count *
382 static_cast<u32>(sizeof(SinkInfoBase::InParameter))};
383 const auto consumed_output_size{sink_count * static_cast<u32>(sizeof(SinkInfoBase::OutStatus))};
384 if (consumed_input_size != in_header->sinks_size) {
385 LOG_ERROR(Service_Audio, "Consumed an incorrect sinks size, header size={}, consumed={}",
386 in_header->sinks_size, consumed_input_size);
387 return Service::Audio::ERR_INVALID_UPDATE_DATA;
388 }
389
390 input += consumed_input_size;
391 output += consumed_output_size;
392 out_header->sinks_size = consumed_output_size;
393 out_header->size += consumed_output_size;
394
395 return ResultSuccess;
396}
397
398Result InfoUpdater::UpdateMemoryPools(std::span<MemoryPoolInfo> memory_pools,
399 const u32 memory_pool_count) {
400 PoolMapper pool_mapper(process_handle, memory_pools, memory_pool_count,
401 behaviour.IsMemoryForceMappingEnabled());
402 std::span<const MemoryPoolInfo::InParameter> in_params{
403 reinterpret_cast<const MemoryPoolInfo::InParameter*>(input), memory_pool_count};
404 std::span<MemoryPoolInfo::OutStatus> out_params{
405 reinterpret_cast<MemoryPoolInfo::OutStatus*>(output), memory_pool_count};
406
407 for (size_t i = 0; i < memory_pool_count; i++) {
408 auto state{pool_mapper.Update(memory_pools[i], in_params[i], out_params[i])};
409 if (state != MemoryPoolInfo::ResultState::Success &&
410 state != MemoryPoolInfo::ResultState::BadParam &&
411 state != MemoryPoolInfo::ResultState::MapFailed &&
412 state != MemoryPoolInfo::ResultState::InUse) {
413 LOG_WARNING(Service_Audio, "Invalid ResultState from updating memory pools");
414 return Service::Audio::ERR_INVALID_UPDATE_DATA;
415 }
416 }
417
418 const auto consumed_input_size{memory_pool_count *
419 static_cast<u32>(sizeof(MemoryPoolInfo::InParameter))};
420 const auto consumed_output_size{memory_pool_count *
421 static_cast<u32>(sizeof(MemoryPoolInfo::OutStatus))};
422 if (consumed_input_size != in_header->memory_pool_size) {
423 LOG_ERROR(Service_Audio,
424 "Consumed an incorrect memory pool size, header size={}, consumed={}",
425 in_header->memory_pool_size, consumed_input_size);
426 return Service::Audio::ERR_INVALID_UPDATE_DATA;
427 }
428
429 input += consumed_input_size;
430 output += consumed_output_size;
431 out_header->memory_pool_size = consumed_output_size;
432 out_header->size += consumed_output_size;
433 return ResultSuccess;
434}
435
436Result InfoUpdater::UpdatePerformanceBuffer(std::span<u8> performance_output,
437 const u64 performance_output_size,
438 PerformanceManager* performance_manager) {
439 auto in_params{reinterpret_cast<const PerformanceManager::InParameter*>(input)};
440 auto out_params{reinterpret_cast<PerformanceManager::OutStatus*>(output)};
441
442 if (performance_manager != nullptr) {
443 out_params->history_size =
444 performance_manager->CopyHistories(performance_output.data(), performance_output_size);
445 performance_manager->SetDetailTarget(in_params->target_node_id);
446 } else {
447 out_params->history_size = 0;
448 }
449
450 const auto consumed_input_size{static_cast<u32>(sizeof(PerformanceManager::InParameter))};
451 const auto consumed_output_size{static_cast<u32>(sizeof(PerformanceManager::OutStatus))};
452 if (consumed_input_size != in_header->performance_buffer_size) {
453 LOG_ERROR(Service_Audio,
454 "Consumed an incorrect performance size, header size={}, consumed={}",
455 in_header->performance_buffer_size, consumed_input_size);
456 return Service::Audio::ERR_INVALID_UPDATE_DATA;
457 }
458
459 input += consumed_input_size;
460 output += consumed_output_size;
461 out_header->performance_buffer_size = consumed_output_size;
462 out_header->size += consumed_output_size;
463 return ResultSuccess;
464}
465
466Result InfoUpdater::UpdateBehaviorInfo(BehaviorInfo& behaviour_) {
467 const auto in_params{reinterpret_cast<const BehaviorInfo::InParameter*>(input)};
468
469 if (!CheckValidRevision(in_params->revision)) {
470 return Service::Audio::ERR_INVALID_UPDATE_DATA;
471 }
472
473 if (in_params->revision != behaviour_.GetUserRevision()) {
474 return Service::Audio::ERR_INVALID_UPDATE_DATA;
475 }
476
477 behaviour_.ClearError();
478 behaviour_.UpdateFlags(in_params->flags);
479
480 if (in_header->behaviour_size != sizeof(BehaviorInfo::InParameter)) {
481 return Service::Audio::ERR_INVALID_UPDATE_DATA;
482 }
483
484 input += sizeof(BehaviorInfo::InParameter);
485 return ResultSuccess;
486}
487
488Result InfoUpdater::UpdateErrorInfo(BehaviorInfo& behaviour_) {
489 auto out_params{reinterpret_cast<BehaviorInfo::OutStatus*>(output)};
490 behaviour_.CopyErrorInfo(out_params->errors, out_params->error_count);
491
492 const auto consumed_output_size{static_cast<u32>(sizeof(BehaviorInfo::OutStatus))};
493
494 output += consumed_output_size;
495 out_header->behaviour_size = consumed_output_size;
496 out_header->size += consumed_output_size;
497 return ResultSuccess;
498}
499
500Result InfoUpdater::UpdateSplitterInfo(SplitterContext& splitter_context) {
501 u32 consumed_size{0};
502 if (!splitter_context.Update(input, consumed_size)) {
503 return Service::Audio::ERR_INVALID_UPDATE_DATA;
504 }
505
506 input += consumed_size;
507
508 return ResultSuccess;
509}
510
511Result InfoUpdater::UpdateRendererInfo(const u64 elapsed_frames) {
512 struct RenderInfo {
513 /* 0x00 */ u64 frames_elapsed;
514 /* 0x08 */ char unk08[0x8];
515 };
516 static_assert(sizeof(RenderInfo) == 0x10, "RenderInfo has the wrong size!");
517
518 auto out_params{reinterpret_cast<RenderInfo*>(output)};
519 out_params->frames_elapsed = elapsed_frames;
520
521 const auto consumed_output_size{static_cast<u32>(sizeof(RenderInfo))};
522
523 output += consumed_output_size;
524 out_header->render_info_size = consumed_output_size;
525 out_header->size += consumed_output_size;
526
527 return ResultSuccess;
528}
529
530Result InfoUpdater::CheckConsumedSize() {
531 if (CpuAddr(input) - CpuAddr(input_origin.data()) != expected_input_size) {
532 return Service::Audio::ERR_INVALID_UPDATE_DATA;
533 } else if (CpuAddr(output) - CpuAddr(output_origin.data()) != expected_output_size) {
534 return Service::Audio::ERR_INVALID_UPDATE_DATA;
535 }
536 return ResultSuccess;
537}
538
539} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/behavior/info_updater.h b/src/audio_core/renderer/behavior/info_updater.h
new file mode 100644
index 000000000..f0b445d9c
--- /dev/null
+++ b/src/audio_core/renderer/behavior/info_updater.h
@@ -0,0 +1,205 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <span>
7
8#include "common/common_types.h"
9#include "core/hle/service/audio/errors.h"
10
11namespace AudioCore::AudioRenderer {
12class BehaviorInfo;
13class VoiceContext;
14class MixContext;
15class SinkContext;
16class SplitterContext;
17class EffectContext;
18class MemoryPoolInfo;
19class PerformanceManager;
20
21class InfoUpdater {
22 struct UpdateDataHeader {
23 explicit UpdateDataHeader(u32 revision_) : revision{revision_} {}
24
25 /* 0x00 */ u32 revision;
26 /* 0x04 */ u32 behaviour_size{};
27 /* 0x08 */ u32 memory_pool_size{};
28 /* 0x0C */ u32 voices_size{};
29 /* 0x10 */ u32 voice_resources_size{};
30 /* 0x14 */ u32 effects_size{};
31 /* 0x18 */ u32 mix_size{};
32 /* 0x1C */ u32 sinks_size{};
33 /* 0x20 */ u32 performance_buffer_size{};
34 /* 0x24 */ char unk24[4];
35 /* 0x28 */ u32 render_info_size{};
36 /* 0x2C */ char unk2C[0x10];
37 /* 0x3C */ u32 size{sizeof(UpdateDataHeader)};
38 };
39 static_assert(sizeof(UpdateDataHeader) == 0x40, "UpdateDataHeader has the wrong size!");
40
41public:
42 explicit InfoUpdater(std::span<const u8> input, std::span<u8> output, u32 process_handle,
43 BehaviorInfo& behaviour);
44
45 /**
46 * Update the voice channel resources.
47 *
48 * @param voice_context - Voice context to update.
49 * @return Result code.
50 */
51 Result UpdateVoiceChannelResources(VoiceContext& voice_context);
52
53 /**
54 * Update voices.
55 *
56 * @param voice_context - Voice context to update.
57 * @param memory_pools - Memory pools to use for these voices.
58 * @param memory_pool_count - Number of memory pools.
59 * @return Result code.
60 */
61 Result UpdateVoices(VoiceContext& voice_context, std::span<MemoryPoolInfo> memory_pools,
62 u32 memory_pool_count);
63
64 /**
65 * Update effects.
66 *
67 * @param effect_context - Effect context to update.
68 * @param renderer_active - Whether the AudioRenderer is active.
69 * @param memory_pools - Memory pools to use for these voices.
70 * @param memory_pool_count - Number of memory pools.
71 * @return Result code.
72 */
73 Result UpdateEffects(EffectContext& effect_context, bool renderer_active,
74 std::span<MemoryPoolInfo> memory_pools, u32 memory_pool_count);
75
76 /**
77 * Update mixes.
78 *
79 * @param mix_context - Mix context to update.
80 * @param mix_buffer_count - Number of mix buffers.
81 * @param effect_context - Effect context to update effort order.
82 * @param splitter_context - Splitter context for the mixes.
83 * @return Result code.
84 */
85 Result UpdateMixes(MixContext& mix_context, u32 mix_buffer_count, EffectContext& effect_context,
86 SplitterContext& splitter_context);
87
88 /**
89 * Update sinks.
90 *
91 * @param sink_context - Sink context to update.
92 * @param memory_pools - Memory pools to use for these voices.
93 * @param memory_pool_count - Number of memory pools.
94 * @return Result code.
95 */
96 Result UpdateSinks(SinkContext& sink_context, std::span<MemoryPoolInfo> memory_pools,
97 u32 memory_pool_count);
98
99 /**
100 * Update memory pools.
101 *
102 * @param memory_pools - Memory pools to use for these voices.
103 * @param memory_pool_count - Number of memory pools.
104 * @return Result code.
105 */
106 Result UpdateMemoryPools(std::span<MemoryPoolInfo> memory_pools, u32 memory_pool_count);
107
108 /**
109 * Update the performance buffer.
110 *
111 * @param output - Output buffer for performance metrics.
112 * @param output_size - Output buffer size.
113 * @param performance_manager - Performance manager..
114 * @return Result code.
115 */
116 Result UpdatePerformanceBuffer(std::span<u8> output, u64 output_size,
117 PerformanceManager* performance_manager);
118
119 /**
120 * Update behaviour.
121 *
122 * @param behaviour - Behaviour to update.
123 * @return Result code.
124 */
125 Result UpdateBehaviorInfo(BehaviorInfo& behaviour);
126
127 /**
128 * Update errors.
129 *
130 * @param behaviour - Behaviour to update.
131 * @return Result code.
132 */
133 Result UpdateErrorInfo(BehaviorInfo& behaviour);
134
135 /**
136 * Update splitter.
137 *
138 * @param splitter_context - Splitter context to update.
139 * @return Result code.
140 */
141 Result UpdateSplitterInfo(SplitterContext& splitter_context);
142
143 /**
144 * Update renderer info.
145 *
146 * @param elapsed_frames - Number of elapsed frames.
147 * @return Result code.
148 */
149 Result UpdateRendererInfo(u64 elapsed_frames);
150
151 /**
152 * Check that the input.output sizes match their expected values.
153 *
154 * @return Result code.
155 */
156 Result CheckConsumedSize();
157
158private:
159 /**
160 * Update effects version 1.
161 *
162 * @param effect_context - Effect context to update.
163 * @param renderer_active - Is the AudioRenderer active?
164 * @param memory_pools - Memory pools to use for these voices.
165 * @param memory_pool_count - Number of memory pools.
166 * @return Result code.
167 */
168 Result UpdateEffectsVersion1(EffectContext& effect_context, bool renderer_active,
169 std::span<MemoryPoolInfo> memory_pools, u32 memory_pool_count);
170
171 /**
172 * Update effects version 2.
173 *
174 * @param effect_context - Effect context to update.
175 * @param renderer_active - Is the AudioRenderer active?
176 * @param memory_pools - Memory pools to use for these voices.
177 * @param memory_pool_count - Number of memory pools.
178 * @return Result code.
179 */
180 Result UpdateEffectsVersion2(EffectContext& effect_context, bool renderer_active,
181 std::span<MemoryPoolInfo> memory_pools, u32 memory_pool_count);
182
183 /// Input buffer
184 u8 const* input;
185 /// Input buffer start
186 std::span<const u8> input_origin;
187 /// Output buffer start
188 u8* output;
189 /// Output buffer start
190 std::span<u8> output_origin;
191 /// Input header
192 const UpdateDataHeader* in_header;
193 /// Output header
194 UpdateDataHeader* out_header;
195 /// Expected input size, see CheckConsumedSize
196 u64 expected_input_size;
197 /// Expected output size, see CheckConsumedSize
198 u64 expected_output_size;
199 /// Unused
200 u32 process_handle;
201 /// Behaviour
202 BehaviorInfo& behaviour;
203};
204
205} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/command_buffer.cpp b/src/audio_core/renderer/command/command_buffer.cpp
new file mode 100644
index 000000000..40074cf14
--- /dev/null
+++ b/src/audio_core/renderer/command/command_buffer.cpp
@@ -0,0 +1,714 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/behavior/behavior_info.h"
5#include "audio_core/renderer/command/command_buffer.h"
6#include "audio_core/renderer/command/command_list_header.h"
7#include "audio_core/renderer/command/command_processing_time_estimator.h"
8#include "audio_core/renderer/effect/biquad_filter.h"
9#include "audio_core/renderer/effect/delay.h"
10#include "audio_core/renderer/effect/reverb.h"
11#include "audio_core/renderer/memory/memory_pool_info.h"
12#include "audio_core/renderer/mix/mix_info.h"
13#include "audio_core/renderer/sink/circular_buffer_sink_info.h"
14#include "audio_core/renderer/sink/device_sink_info.h"
15#include "audio_core/renderer/sink/sink_info_base.h"
16#include "audio_core/renderer/voice/voice_info.h"
17#include "audio_core/renderer/voice/voice_state.h"
18
19namespace AudioCore::AudioRenderer {
20
21template <typename T, CommandId Id>
22T& CommandBuffer::GenerateStart(const s32 node_id) {
23 if (size + sizeof(T) >= command_list.size_bytes()) {
24 LOG_ERROR(
25 Service_Audio,
26 "Attempting to write commands beyond the end of allocated command buffer memory!");
27 UNREACHABLE();
28 }
29
30 auto& cmd{*std::construct_at<T>(reinterpret_cast<T*>(&command_list[size]))};
31
32 cmd.magic = CommandMagic;
33 cmd.enabled = true;
34 cmd.type = Id;
35 cmd.size = sizeof(T);
36 cmd.node_id = node_id;
37
38 return cmd;
39}
40
41template <typename T>
42void CommandBuffer::GenerateEnd(T& cmd) {
43 cmd.estimated_process_time = time_estimator->Estimate(cmd);
44 estimated_process_time += cmd.estimated_process_time;
45 size += sizeof(T);
46 count++;
47}
48
49void CommandBuffer::GeneratePcmInt16Version1Command(const s32 node_id,
50 const MemoryPoolInfo& memory_pool_,
51 VoiceInfo& voice_info,
52 const VoiceState& voice_state,
53 const s16 buffer_count, const s8 channel) {
54 auto& cmd{
55 GenerateStart<PcmInt16DataSourceVersion1Command, CommandId::DataSourcePcmInt16Version1>(
56 node_id)};
57
58 cmd.src_quality = voice_info.src_quality;
59 cmd.output_index = buffer_count + channel;
60 cmd.flags = voice_info.flags & 3;
61 cmd.sample_rate = voice_info.sample_rate;
62 cmd.pitch = voice_info.pitch;
63 cmd.channel_index = channel;
64 cmd.channel_count = voice_info.channel_count;
65
66 for (u32 i = 0; i < MaxWaveBuffers; i++) {
67 voice_info.wavebuffers[i].Copy(cmd.wave_buffers[i]);
68 }
69
70 cmd.voice_state = memory_pool_.Translate(CpuAddr(&voice_state), sizeof(VoiceState));
71
72 GenerateEnd<PcmInt16DataSourceVersion1Command>(cmd);
73}
74
75void CommandBuffer::GeneratePcmInt16Version2Command(const s32 node_id, VoiceInfo& voice_info,
76 const VoiceState& voice_state,
77 const s16 buffer_count, const s8 channel) {
78 auto& cmd{
79 GenerateStart<PcmInt16DataSourceVersion2Command, CommandId::DataSourcePcmInt16Version2>(
80 node_id)};
81
82 cmd.src_quality = voice_info.src_quality;
83 cmd.output_index = buffer_count + channel;
84 cmd.flags = voice_info.flags & 3;
85 cmd.sample_rate = voice_info.sample_rate;
86 cmd.pitch = voice_info.pitch;
87 cmd.channel_index = channel;
88 cmd.channel_count = voice_info.channel_count;
89
90 for (u32 i = 0; i < MaxWaveBuffers; i++) {
91 voice_info.wavebuffers[i].Copy(cmd.wave_buffers[i]);
92 }
93
94 cmd.voice_state = memory_pool->Translate(CpuAddr(&voice_state), sizeof(VoiceState));
95
96 GenerateEnd<PcmInt16DataSourceVersion2Command>(cmd);
97}
98
99void CommandBuffer::GeneratePcmFloatVersion1Command(const s32 node_id,
100 const MemoryPoolInfo& memory_pool_,
101 VoiceInfo& voice_info,
102 const VoiceState& voice_state,
103 const s16 buffer_count, const s8 channel) {
104 auto& cmd{
105 GenerateStart<PcmFloatDataSourceVersion1Command, CommandId::DataSourcePcmFloatVersion1>(
106 node_id)};
107
108 cmd.src_quality = voice_info.src_quality;
109 cmd.output_index = buffer_count + channel;
110 cmd.flags = voice_info.flags & 3;
111 cmd.sample_rate = voice_info.sample_rate;
112 cmd.pitch = voice_info.pitch;
113 cmd.channel_index = channel;
114 cmd.channel_count = voice_info.channel_count;
115
116 for (u32 i = 0; i < MaxWaveBuffers; i++) {
117 voice_info.wavebuffers[i].Copy(cmd.wave_buffers[i]);
118 }
119
120 cmd.voice_state = memory_pool_.Translate(CpuAddr(&voice_state), sizeof(VoiceState));
121
122 GenerateEnd<PcmFloatDataSourceVersion1Command>(cmd);
123}
124
125void CommandBuffer::GeneratePcmFloatVersion2Command(const s32 node_id, VoiceInfo& voice_info,
126 const VoiceState& voice_state,
127 const s16 buffer_count, const s8 channel) {
128 auto& cmd{
129 GenerateStart<PcmFloatDataSourceVersion2Command, CommandId::DataSourcePcmFloatVersion2>(
130 node_id)};
131
132 cmd.src_quality = voice_info.src_quality;
133 cmd.output_index = buffer_count + channel;
134 cmd.flags = voice_info.flags & 3;
135 cmd.sample_rate = voice_info.sample_rate;
136 cmd.pitch = voice_info.pitch;
137 cmd.channel_index = channel;
138 cmd.channel_count = voice_info.channel_count;
139
140 for (u32 i = 0; i < MaxWaveBuffers; i++) {
141 voice_info.wavebuffers[i].Copy(cmd.wave_buffers[i]);
142 }
143
144 cmd.voice_state = memory_pool->Translate(CpuAddr(&voice_state), sizeof(VoiceState));
145
146 GenerateEnd<PcmFloatDataSourceVersion2Command>(cmd);
147}
148
149void CommandBuffer::GenerateAdpcmVersion1Command(const s32 node_id,
150 const MemoryPoolInfo& memory_pool_,
151 VoiceInfo& voice_info,
152 const VoiceState& voice_state,
153 const s16 buffer_count, const s8 channel) {
154 auto& cmd{
155 GenerateStart<AdpcmDataSourceVersion1Command, CommandId::DataSourceAdpcmVersion1>(node_id)};
156
157 cmd.src_quality = voice_info.src_quality;
158 cmd.output_index = buffer_count + channel;
159 cmd.flags = voice_info.flags & 3;
160 cmd.sample_rate = voice_info.sample_rate;
161 cmd.pitch = voice_info.pitch;
162
163 for (u32 i = 0; i < MaxWaveBuffers; i++) {
164 voice_info.wavebuffers[i].Copy(cmd.wave_buffers[i]);
165 }
166
167 cmd.voice_state = memory_pool_.Translate(CpuAddr(&voice_state), sizeof(VoiceState));
168 cmd.data_address = voice_info.data_address.GetReference(true);
169 cmd.data_size = voice_info.data_address.GetSize();
170
171 GenerateEnd<AdpcmDataSourceVersion1Command>(cmd);
172}
173
174void CommandBuffer::GenerateAdpcmVersion2Command(const s32 node_id, VoiceInfo& voice_info,
175 const VoiceState& voice_state,
176 const s16 buffer_count, const s8 channel) {
177 auto& cmd{
178 GenerateStart<AdpcmDataSourceVersion2Command, CommandId::DataSourceAdpcmVersion2>(node_id)};
179
180 cmd.src_quality = voice_info.src_quality;
181 cmd.output_index = buffer_count + channel;
182 cmd.flags = voice_info.flags & 3;
183 cmd.sample_rate = voice_info.sample_rate;
184 cmd.pitch = voice_info.pitch;
185 cmd.channel_index = channel;
186 cmd.channel_count = voice_info.channel_count;
187
188 for (u32 i = 0; i < MaxWaveBuffers; i++) {
189 voice_info.wavebuffers[i].Copy(cmd.wave_buffers[i]);
190 }
191
192 cmd.voice_state = memory_pool->Translate(CpuAddr(&voice_state), sizeof(VoiceState));
193 cmd.data_address = voice_info.data_address.GetReference(true);
194 cmd.data_size = voice_info.data_address.GetSize();
195
196 GenerateEnd<AdpcmDataSourceVersion2Command>(cmd);
197}
198
199void CommandBuffer::GenerateVolumeCommand(const s32 node_id, const s16 buffer_offset,
200 const s16 input_index, const f32 volume,
201 const u8 precision) {
202 auto& cmd{GenerateStart<VolumeCommand, CommandId::Volume>(node_id)};
203
204 cmd.precision = precision;
205 cmd.input_index = buffer_offset + input_index;
206 cmd.output_index = buffer_offset + input_index;
207 cmd.volume = volume;
208
209 GenerateEnd<VolumeCommand>(cmd);
210}
211
212void CommandBuffer::GenerateVolumeRampCommand(const s32 node_id, VoiceInfo& voice_info,
213 const s16 buffer_count, const u8 precision) {
214 auto& cmd{GenerateStart<VolumeRampCommand, CommandId::VolumeRamp>(node_id)};
215
216 cmd.input_index = buffer_count;
217 cmd.output_index = buffer_count;
218 cmd.prev_volume = voice_info.prev_volume;
219 cmd.volume = voice_info.volume;
220 cmd.precision = precision;
221
222 GenerateEnd<VolumeRampCommand>(cmd);
223}
224
225void CommandBuffer::GenerateBiquadFilterCommand(const s32 node_id, VoiceInfo& voice_info,
226 const VoiceState& voice_state,
227 const s16 buffer_count, const s8 channel,
228 const u32 biquad_index,
229 const bool use_float_processing) {
230 auto& cmd{GenerateStart<BiquadFilterCommand, CommandId::BiquadFilter>(node_id)};
231
232 cmd.input = buffer_count + channel;
233 cmd.output = buffer_count + channel;
234
235 cmd.biquad = voice_info.biquads[biquad_index];
236
237 cmd.state = memory_pool->Translate(CpuAddr(voice_state.biquad_states[biquad_index].data()),
238 MaxBiquadFilters * sizeof(VoiceState::BiquadFilterState));
239
240 cmd.needs_init = !voice_info.biquad_initialized[biquad_index];
241 cmd.use_float_processing = use_float_processing;
242
243 GenerateEnd<BiquadFilterCommand>(cmd);
244}
245
246void CommandBuffer::GenerateBiquadFilterCommand(const s32 node_id, EffectInfoBase& effect_info,
247 const s16 buffer_offset, const s8 channel,
248 const bool needs_init,
249 const bool use_float_processing) {
250 auto& cmd{GenerateStart<BiquadFilterCommand, CommandId::BiquadFilter>(node_id)};
251
252 const auto& parameter{
253 *reinterpret_cast<BiquadFilterInfo::ParameterVersion1*>(effect_info.GetParameter())};
254 const auto state{
255 reinterpret_cast<VoiceState::BiquadFilterState*>(effect_info.GetStateBuffer())};
256
257 cmd.input = buffer_offset + parameter.inputs[channel];
258 cmd.output = buffer_offset + parameter.outputs[channel];
259
260 cmd.biquad.b = parameter.b;
261 cmd.biquad.a = parameter.a;
262
263 cmd.state = memory_pool->Translate(CpuAddr(state),
264 MaxBiquadFilters * sizeof(VoiceState::BiquadFilterState));
265
266 cmd.needs_init = needs_init;
267 cmd.use_float_processing = use_float_processing;
268
269 GenerateEnd<BiquadFilterCommand>(cmd);
270}
271
272void CommandBuffer::GenerateMixCommand(const s32 node_id, const s16 input_index,
273 const s16 output_index, const s16 buffer_offset,
274 const f32 volume, const u8 precision) {
275 auto& cmd{GenerateStart<MixCommand, CommandId::Mix>(node_id)};
276
277 cmd.input_index = input_index;
278 cmd.output_index = output_index;
279 cmd.volume = volume;
280 cmd.precision = precision;
281
282 GenerateEnd<MixCommand>(cmd);
283}
284
285void CommandBuffer::GenerateMixRampCommand(const s32 node_id,
286 [[maybe_unused]] const s16 buffer_count,
287 const s16 input_index, const s16 output_index,
288 const f32 volume, const f32 prev_volume,
289 const CpuAddr prev_samples, const u8 precision) {
290 if (volume == 0.0f && prev_volume == 0.0f) {
291 return;
292 }
293
294 auto& cmd{GenerateStart<MixRampCommand, CommandId::MixRamp>(node_id)};
295
296 cmd.input_index = input_index;
297 cmd.output_index = output_index;
298 cmd.prev_volume = prev_volume;
299 cmd.volume = volume;
300 cmd.previous_sample = prev_samples;
301 cmd.precision = precision;
302
303 GenerateEnd<MixRampCommand>(cmd);
304}
305
306void CommandBuffer::GenerateMixRampGroupedCommand(const s32 node_id, const s16 buffer_count,
307 const s16 input_index, s16 output_index,
308 std::span<const f32> volumes,
309 std::span<const f32> prev_volumes,
310 const CpuAddr prev_samples, const u8 precision) {
311 auto& cmd{GenerateStart<MixRampGroupedCommand, CommandId::MixRampGrouped>(node_id)};
312
313 cmd.buffer_count = buffer_count;
314
315 for (s32 i = 0; i < buffer_count; i++) {
316 cmd.inputs[i] = input_index;
317 cmd.outputs[i] = output_index++;
318 cmd.prev_volumes[i] = prev_volumes[i];
319 cmd.volumes[i] = volumes[i];
320 }
321
322 cmd.previous_samples = prev_samples;
323 cmd.precision = precision;
324
325 GenerateEnd<MixRampGroupedCommand>(cmd);
326}
327
328void CommandBuffer::GenerateDepopPrepareCommand(const s32 node_id, const VoiceState& voice_state,
329 std::span<const s32> buffer, const s16 buffer_count,
330 s16 buffer_offset, const bool was_playing) {
331 auto& cmd{GenerateStart<DepopPrepareCommand, CommandId::DepopPrepare>(node_id)};
332
333 cmd.enabled = was_playing;
334
335 for (u32 i = 0; i < MaxMixBuffers; i++) {
336 cmd.inputs[i] = buffer_offset++;
337 }
338
339 cmd.previous_samples = memory_pool->Translate(CpuAddr(voice_state.previous_samples.data()),
340 MaxMixBuffers * sizeof(s32));
341 cmd.buffer_count = buffer_count;
342 cmd.depop_buffer = memory_pool->Translate(CpuAddr(buffer.data()), buffer_count * sizeof(s32));
343
344 GenerateEnd<DepopPrepareCommand>(cmd);
345}
346
347void CommandBuffer::GenerateDepopForMixBuffersCommand(const s32 node_id, const MixInfo& mix_info,
348 std::span<const s32> depop_buffer) {
349 auto& cmd{GenerateStart<DepopForMixBuffersCommand, CommandId::DepopForMixBuffers>(node_id)};
350
351 cmd.input = mix_info.buffer_offset;
352 cmd.count = mix_info.buffer_count;
353 cmd.decay = mix_info.sample_rate == TargetSampleRate ? 0.96218872f : 0.94369507f;
354 cmd.depop_buffer =
355 memory_pool->Translate(CpuAddr(depop_buffer.data()), mix_info.buffer_count * sizeof(s32));
356
357 GenerateEnd<DepopForMixBuffersCommand>(cmd);
358}
359
360void CommandBuffer::GenerateDelayCommand(const s32 node_id, EffectInfoBase& effect_info,
361 const s16 buffer_offset) {
362 auto& cmd{GenerateStart<DelayCommand, CommandId::Delay>(node_id)};
363
364 const auto& parameter{
365 *reinterpret_cast<DelayInfo::ParameterVersion1*>(effect_info.GetParameter())};
366 const auto state{effect_info.GetStateBuffer()};
367
368 if (IsChannelCountValid(parameter.channel_count)) {
369 const auto state_buffer{memory_pool->Translate(CpuAddr(state), sizeof(DelayInfo::State))};
370 if (state_buffer) {
371 for (s16 channel = 0; channel < parameter.channel_count; channel++) {
372 cmd.inputs[channel] = buffer_offset + parameter.inputs[channel];
373 cmd.outputs[channel] = buffer_offset + parameter.outputs[channel];
374 }
375
376 if (!behavior->IsDelayChannelMappingChanged() && parameter.channel_count == 6) {
377 UseOldChannelMapping(cmd.inputs, cmd.outputs);
378 }
379
380 cmd.parameter = parameter;
381 cmd.effect_enabled = effect_info.IsEnabled();
382 cmd.state = state_buffer;
383 cmd.workbuffer = effect_info.GetWorkbuffer(-1);
384 }
385 }
386
387 GenerateEnd<DelayCommand>(cmd);
388}
389
390void CommandBuffer::GenerateUpsampleCommand(const s32 node_id, const s16 buffer_offset,
391 UpsamplerInfo& upsampler_info, const u32 input_count,
392 std::span<const s8> inputs, const s16 buffer_count,
393 const u32 sample_count_, const u32 sample_rate_) {
394 auto& cmd{GenerateStart<UpsampleCommand, CommandId::Upsample>(node_id)};
395
396 cmd.samples_buffer = memory_pool->Translate(upsampler_info.samples_pos,
397 upsampler_info.sample_count * sizeof(s32));
398 cmd.inputs = memory_pool->Translate(CpuAddr(upsampler_info.inputs.data()), MaxChannels);
399 cmd.buffer_count = buffer_count;
400 cmd.unk_20 = 0;
401 cmd.source_sample_count = sample_count_;
402 cmd.source_sample_rate = sample_rate_;
403
404 upsampler_info.input_count = input_count;
405 for (u32 i = 0; i < input_count; i++) {
406 upsampler_info.inputs[i] = buffer_offset + inputs[i];
407 }
408
409 cmd.upsampler_info = memory_pool->Translate(CpuAddr(&upsampler_info), sizeof(UpsamplerInfo));
410
411 GenerateEnd<UpsampleCommand>(cmd);
412}
413
414void CommandBuffer::GenerateDownMix6chTo2chCommand(const s32 node_id, std::span<const s8> inputs,
415 const s16 buffer_offset,
416 std::span<const f32> downmix_coeff) {
417 auto& cmd{GenerateStart<DownMix6chTo2chCommand, CommandId::DownMix6chTo2ch>(node_id)};
418
419 for (u32 i = 0; i < MaxChannels; i++) {
420 cmd.inputs[i] = buffer_offset + inputs[i];
421 cmd.outputs[i] = buffer_offset + inputs[i];
422 }
423
424 for (u32 i = 0; i < 4; i++) {
425 cmd.down_mix_coeff[i] = downmix_coeff[i];
426 }
427
428 GenerateEnd<DownMix6chTo2chCommand>(cmd);
429}
430
431void CommandBuffer::GenerateAuxCommand(const s32 node_id, EffectInfoBase& effect_info,
432 const s16 input_index, const s16 output_index,
433 const s16 buffer_offset, const u32 update_count,
434 const u32 count_max, const u32 write_offset) {
435 auto& cmd{GenerateStart<AuxCommand, CommandId::Aux>(node_id)};
436
437 if (effect_info.GetSendBuffer() != 0 && effect_info.GetReturnBuffer() != 0) {
438 cmd.input = buffer_offset + input_index;
439 cmd.output = buffer_offset + output_index;
440 cmd.send_buffer_info = effect_info.GetSendBufferInfo();
441 cmd.send_buffer = effect_info.GetSendBuffer();
442 cmd.return_buffer_info = effect_info.GetReturnBufferInfo();
443 cmd.return_buffer = effect_info.GetReturnBuffer();
444 cmd.count_max = count_max;
445 cmd.write_offset = write_offset;
446 cmd.update_count = update_count;
447 cmd.effect_enabled = effect_info.IsEnabled();
448 }
449
450 GenerateEnd<AuxCommand>(cmd);
451}
452
453void CommandBuffer::GenerateDeviceSinkCommand(const s32 node_id, const s16 buffer_offset,
454 SinkInfoBase& sink_info, const u32 session_id,
455 std::span<s32> samples_buffer) {
456 auto& cmd{GenerateStart<DeviceSinkCommand, CommandId::DeviceSink>(node_id)};
457 const auto& parameter{
458 *reinterpret_cast<DeviceSinkInfo::DeviceInParameter*>(sink_info.GetParameter())};
459 auto state{*reinterpret_cast<DeviceSinkInfo::DeviceState*>(sink_info.GetState())};
460
461 cmd.session_id = session_id;
462
463 if (state.upsampler_info != nullptr) {
464 const auto size_{state.upsampler_info->sample_count * parameter.input_count};
465 const auto size_bytes{size_ * sizeof(s32)};
466 const auto addr{memory_pool->Translate(state.upsampler_info->samples_pos, size_bytes)};
467 cmd.sample_buffer = {reinterpret_cast<s32*>(addr),
468 parameter.input_count * state.upsampler_info->sample_count};
469 } else {
470 cmd.sample_buffer = samples_buffer;
471 }
472
473 cmd.input_count = parameter.input_count;
474 for (u32 i = 0; i < parameter.input_count; i++) {
475 cmd.inputs[i] = buffer_offset + parameter.inputs[i];
476 }
477
478 GenerateEnd<DeviceSinkCommand>(cmd);
479}
480
481void CommandBuffer::GenerateCircularBufferSinkCommand(const s32 node_id, SinkInfoBase& sink_info,
482 const s16 buffer_offset) {
483 auto& cmd{GenerateStart<CircularBufferSinkCommand, CommandId::CircularBufferSink>(node_id)};
484 const auto& parameter{*reinterpret_cast<CircularBufferSinkInfo::CircularBufferInParameter*>(
485 sink_info.GetParameter())};
486 auto state{
487 *reinterpret_cast<CircularBufferSinkInfo::CircularBufferState*>(sink_info.GetState())};
488
489 cmd.input_count = parameter.input_count;
490 for (u32 i = 0; i < parameter.input_count; i++) {
491 cmd.inputs[i] = buffer_offset + parameter.inputs[i];
492 }
493
494 cmd.address = state.address_info.GetReference(true);
495 cmd.size = parameter.size;
496 cmd.pos = state.current_pos;
497
498 GenerateEnd<CircularBufferSinkCommand>(cmd);
499}
500
501void CommandBuffer::GenerateReverbCommand(const s32 node_id, EffectInfoBase& effect_info,
502 const s16 buffer_offset,
503 const bool long_size_pre_delay_supported) {
504 auto& cmd{GenerateStart<ReverbCommand, CommandId::Reverb>(node_id)};
505
506 const auto& parameter{
507 *reinterpret_cast<ReverbInfo::ParameterVersion2*>(effect_info.GetParameter())};
508 const auto state{effect_info.GetStateBuffer()};
509
510 if (IsChannelCountValid(parameter.channel_count)) {
511 const auto state_buffer{memory_pool->Translate(CpuAddr(state), sizeof(ReverbInfo::State))};
512 if (state_buffer) {
513 for (s16 channel = 0; channel < parameter.channel_count; channel++) {
514 cmd.inputs[channel] = buffer_offset + parameter.inputs[channel];
515 cmd.outputs[channel] = buffer_offset + parameter.outputs[channel];
516 }
517
518 if (!behavior->IsReverbChannelMappingChanged() && parameter.channel_count == 6) {
519 UseOldChannelMapping(cmd.inputs, cmd.outputs);
520 }
521
522 cmd.parameter = parameter;
523 cmd.effect_enabled = effect_info.IsEnabled();
524 cmd.state = state_buffer;
525 cmd.workbuffer = effect_info.GetWorkbuffer(-1);
526 cmd.long_size_pre_delay_supported = long_size_pre_delay_supported;
527 }
528 }
529
530 GenerateEnd<ReverbCommand>(cmd);
531}
532
533void CommandBuffer::GenerateI3dl2ReverbCommand(const s32 node_id, EffectInfoBase& effect_info,
534 const s16 buffer_offset) {
535 auto& cmd{GenerateStart<I3dl2ReverbCommand, CommandId::I3dl2Reverb>(node_id)};
536
537 const auto& parameter{
538 *reinterpret_cast<I3dl2ReverbInfo::ParameterVersion1*>(effect_info.GetParameter())};
539 const auto state{effect_info.GetStateBuffer()};
540
541 if (IsChannelCountValid(parameter.channel_count)) {
542 const auto state_buffer{
543 memory_pool->Translate(CpuAddr(state), sizeof(I3dl2ReverbInfo::State))};
544 if (state_buffer) {
545 for (s16 channel = 0; channel < parameter.channel_count; channel++) {
546 cmd.inputs[channel] = buffer_offset + parameter.inputs[channel];
547 cmd.outputs[channel] = buffer_offset + parameter.outputs[channel];
548 }
549
550 if (!behavior->IsI3dl2ReverbChannelMappingChanged() && parameter.channel_count == 6) {
551 UseOldChannelMapping(cmd.inputs, cmd.outputs);
552 }
553
554 cmd.parameter = parameter;
555 cmd.effect_enabled = effect_info.IsEnabled();
556 cmd.state = state_buffer;
557 cmd.workbuffer = effect_info.GetWorkbuffer(-1);
558 }
559 }
560
561 GenerateEnd<I3dl2ReverbCommand>(cmd);
562}
563
564void CommandBuffer::GeneratePerformanceCommand(const s32 node_id, const PerformanceState state,
565 const PerformanceEntryAddresses& entry_addresses) {
566 auto& cmd{GenerateStart<PerformanceCommand, CommandId::Performance>(node_id)};
567
568 cmd.state = state;
569 cmd.entry_address = entry_addresses;
570
571 GenerateEnd<PerformanceCommand>(cmd);
572}
573
574void CommandBuffer::GenerateClearMixCommand(const s32 node_id) {
575 auto& cmd{GenerateStart<ClearMixBufferCommand, CommandId::ClearMixBuffer>(node_id)};
576 GenerateEnd<ClearMixBufferCommand>(cmd);
577}
578
579void CommandBuffer::GenerateCopyMixBufferCommand(const s32 node_id, EffectInfoBase& effect_info,
580 const s16 buffer_offset, const s8 channel) {
581 auto& cmd{GenerateStart<CopyMixBufferCommand, CommandId::CopyMixBuffer>(node_id)};
582
583 const auto& parameter{
584 *reinterpret_cast<BiquadFilterInfo::ParameterVersion1*>(effect_info.GetParameter())};
585 cmd.input_index = buffer_offset + parameter.inputs[channel];
586 cmd.output_index = buffer_offset + parameter.outputs[channel];
587
588 GenerateEnd<CopyMixBufferCommand>(cmd);
589}
590
591void CommandBuffer::GenerateLightLimiterCommand(
592 const s32 node_id, const s16 buffer_offset,
593 const LightLimiterInfo::ParameterVersion1& parameter, const LightLimiterInfo::State& state,
594 const bool enabled, const CpuAddr workbuffer) {
595 auto& cmd{GenerateStart<LightLimiterVersion1Command, CommandId::LightLimiterVersion1>(node_id)};
596
597 if (IsChannelCountValid(parameter.channel_count)) {
598 const auto state_buffer{
599 memory_pool->Translate(CpuAddr(&state), sizeof(LightLimiterInfo::State))};
600 if (state_buffer) {
601 for (s8 channel = 0; channel < parameter.channel_count; channel++) {
602 cmd.inputs[channel] = buffer_offset + parameter.inputs[channel];
603 cmd.outputs[channel] = buffer_offset + parameter.outputs[channel];
604 }
605
606 std::memcpy(&cmd.parameter, &parameter, sizeof(LightLimiterInfo::ParameterVersion1));
607 cmd.effect_enabled = enabled;
608 cmd.state = state_buffer;
609 cmd.workbuffer = workbuffer;
610 }
611 }
612
613 GenerateEnd<LightLimiterVersion1Command>(cmd);
614}
615
616void CommandBuffer::GenerateLightLimiterCommand(
617 const s32 node_id, const s16 buffer_offset,
618 const LightLimiterInfo::ParameterVersion2& parameter,
619 const LightLimiterInfo::StatisticsInternal& statistics, const LightLimiterInfo::State& state,
620 const bool enabled, const CpuAddr workbuffer) {
621 auto& cmd{GenerateStart<LightLimiterVersion2Command, CommandId::LightLimiterVersion2>(node_id)};
622 if (IsChannelCountValid(parameter.channel_count)) {
623 const auto state_buffer{
624 memory_pool->Translate(CpuAddr(&state), sizeof(LightLimiterInfo::State))};
625 if (state_buffer) {
626 for (s8 channel = 0; channel < parameter.channel_count; channel++) {
627 cmd.inputs[channel] = buffer_offset + parameter.inputs[channel];
628 cmd.outputs[channel] = buffer_offset + parameter.outputs[channel];
629 }
630
631 cmd.parameter = parameter;
632 cmd.effect_enabled = enabled;
633 cmd.state = state_buffer;
634 if (cmd.parameter.statistics_enabled) {
635 cmd.result_state = memory_pool->Translate(
636 CpuAddr(&statistics), sizeof(LightLimiterInfo::StatisticsInternal));
637 } else {
638 cmd.result_state = 0;
639 }
640 cmd.workbuffer = workbuffer;
641 }
642 }
643
644 GenerateEnd<LightLimiterVersion2Command>(cmd);
645}
646
647void CommandBuffer::GenerateMultitapBiquadFilterCommand(const s32 node_id, VoiceInfo& voice_info,
648 const VoiceState& voice_state,
649 const s16 buffer_count, const s8 channel) {
650 auto& cmd{GenerateStart<MultiTapBiquadFilterCommand, CommandId::MultiTapBiquadFilter>(node_id)};
651
652 cmd.input = buffer_count + channel;
653 cmd.output = buffer_count + channel;
654 cmd.biquads = voice_info.biquads;
655
656 cmd.states[0] =
657 memory_pool->Translate(CpuAddr(voice_state.biquad_states[0].data()),
658 MaxBiquadFilters * sizeof(VoiceState::BiquadFilterState));
659 cmd.states[1] =
660 memory_pool->Translate(CpuAddr(voice_state.biquad_states[1].data()),
661 MaxBiquadFilters * sizeof(VoiceState::BiquadFilterState));
662
663 cmd.needs_init[0] = !voice_info.biquad_initialized[0];
664 cmd.needs_init[1] = !voice_info.biquad_initialized[1];
665 cmd.filter_tap_count = MaxBiquadFilters;
666
667 GenerateEnd<MultiTapBiquadFilterCommand>(cmd);
668}
669
670void CommandBuffer::GenerateCaptureCommand(const s32 node_id, EffectInfoBase& effect_info,
671 const s16 input_index, const s16 output_index,
672 const s16 buffer_offset, const u32 update_count,
673 const u32 count_max, const u32 write_offset) {
674 auto& cmd{GenerateStart<CaptureCommand, CommandId::Capture>(node_id)};
675
676 if (effect_info.GetSendBuffer()) {
677 cmd.input = buffer_offset + input_index;
678 cmd.output = buffer_offset + output_index;
679 cmd.send_buffer_info = effect_info.GetSendBufferInfo();
680 cmd.send_buffer = effect_info.GetSendBuffer();
681 cmd.count_max = count_max;
682 cmd.write_offset = write_offset;
683 cmd.update_count = update_count;
684 cmd.effect_enabled = effect_info.IsEnabled();
685 }
686
687 GenerateEnd<CaptureCommand>(cmd);
688}
689
690void CommandBuffer::GenerateCompressorCommand(s16 buffer_offset, EffectInfoBase& effect_info,
691 s32 node_id) {
692 auto& cmd{GenerateStart<CompressorCommand, CommandId::Compressor>(node_id)};
693
694 auto& parameter{
695 *reinterpret_cast<CompressorInfo::ParameterVersion2*>(effect_info.GetParameter())};
696 auto state{reinterpret_cast<CompressorInfo::State*>(effect_info.GetStateBuffer())};
697
698 if (IsChannelCountValid(parameter.channel_count)) {
699 auto state_buffer{memory_pool->Translate(CpuAddr(state), sizeof(CompressorInfo::State))};
700 if (state_buffer) {
701 for (u16 channel = 0; channel < parameter.channel_count; channel++) {
702 cmd.inputs[channel] = buffer_offset + parameter.inputs[channel];
703 cmd.outputs[channel] = buffer_offset + parameter.outputs[channel];
704 }
705 cmd.parameter = parameter;
706 cmd.workbuffer = state_buffer;
707 cmd.enabled = effect_info.IsEnabled();
708 }
709 }
710
711 GenerateEnd<CompressorCommand>(cmd);
712}
713
714} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/command_buffer.h b/src/audio_core/renderer/command/command_buffer.h
new file mode 100644
index 000000000..496b0e50a
--- /dev/null
+++ b/src/audio_core/renderer/command/command_buffer.h
@@ -0,0 +1,466 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <span>
7
8#include "audio_core/renderer/command/commands.h"
9#include "audio_core/renderer/effect/light_limiter.h"
10#include "audio_core/renderer/performance/performance_manager.h"
11#include "common/common_types.h"
12
13namespace AudioCore::AudioRenderer {
14struct UpsamplerInfo;
15struct VoiceState;
16class EffectInfoBase;
17class ICommandProcessingTimeEstimator;
18class MixInfo;
19class MemoryPoolInfo;
20class SinkInfoBase;
21class VoiceInfo;
22
23/**
24 * Utility functions to generate and add commands into the current command list.
25 */
26class CommandBuffer {
27public:
28 /**
29 * Generate a PCM s16 version 1 command, adding it to the command list.
30 *
31 * @param node_id - Node id of the voice this command is generated for.
32 * @param memory_pool - Memory pool for translating buffer addresses to the DSP.
33 * @param voice_info - The voice info this command is generated from.
34 * @param voice_state - The voice state the DSP will use for this command.
35 * @param buffer_count - Number of mix buffers in use,
36 * data will be read into this index + channel.
37 * @param channel - Channel index for this command.
38 */
39 void GeneratePcmInt16Version1Command(s32 node_id, const MemoryPoolInfo& memory_pool,
40 VoiceInfo& voice_info, const VoiceState& voice_state,
41 s16 buffer_count, s8 channel);
42
43 /**
44 * Generate a PCM s16 version 2 command, adding it to the command list.
45 *
46 * @param node_id - Node id of the voice this command is generated for.
47 * @param voice_info - The voice info this command is generated from.
48 * @param voice_state - The voice state the DSP will use for this command.
49 * @param buffer_count - Number of mix buffers in use,
50 * data will be read into this index + channel.
51 * @param channel - Channel index for this command.
52 */
53 void GeneratePcmInt16Version2Command(s32 node_id, VoiceInfo& voice_info,
54 const VoiceState& voice_state, s16 buffer_count,
55 s8 channel);
56
57 /**
58 * Generate a PCM f32 version 1 command, adding it to the command list.
59 *
60 * @param node_id - Node id of the voice this command is generated for.
61 * @param memory_pool - Memory pool for translating buffer addresses to the DSP.
62 * @param voice_info - The voice info this command is generated from.
63 * @param voice_state - The voice state the DSP will use for this command.
64 * @param buffer_count - Number of mix buffers in use,
65 * data will be read into this index + channel.
66 * @param channel - Channel index for this command.
67 */
68 void GeneratePcmFloatVersion1Command(s32 node_id, const MemoryPoolInfo& memory_pool,
69 VoiceInfo& voice_info, const VoiceState& voice_state,
70 s16 buffer_count, s8 channel);
71
72 /**
73 * Generate a PCM f32 version 2 command, adding it to the command list.
74 *
75 * @param node_id - Node id of the voice this command is generated for.
76 * @param voice_info - The voice info this command is generated from.
77 * @param voice_state - The voice state the DSP will use for this command.
78 * @param buffer_count - Number of mix buffers in use,
79 * data will be read into this index + channel.
80 * @param channel - Channel index for this command.
81 */
82 void GeneratePcmFloatVersion2Command(s32 node_id, VoiceInfo& voice_info,
83 const VoiceState& voice_state, s16 buffer_count,
84 s8 channel);
85
86 /**
87 * Generate an ADPCM version 1 command, adding it to the command list.
88 *
89 * @param node_id - Node id of the voice this command is generated for.
90 * @param memory_pool - Memory pool for translating buffer addresses to the DSP.
91 * @param voice_info - The voice info this command is generated from.
92 * @param voice_state - The voice state the DSP will use for this command.
93 * @param buffer_count - Number of mix buffers in use,
94 * data will be read into this index + channel.
95 * @param channel - Channel index for this command.
96 */
97 void GenerateAdpcmVersion1Command(s32 node_id, const MemoryPoolInfo& memory_pool,
98 VoiceInfo& voice_info, const VoiceState& voice_state,
99 s16 buffer_count, s8 channel);
100
101 /**
102 * Generate an ADPCM version 2 command, adding it to the command list.
103 *
104 * @param node_id - Node id of the voice this command is generated for.
105 * @param voice_info - The voice info this command is generated from.
106 * @param voice_state - The voice state the DSP will use for this command.
107 * @param buffer_count - Number of mix buffers in use,
108 * data will be read into this index + channel.
109 * @param channel - Channel index for this command.
110 */
111 void GenerateAdpcmVersion2Command(s32 node_id, VoiceInfo& voice_info,
112 const VoiceState& voice_state, s16 buffer_count, s8 channel);
113
114 /**
115 * Generate a volume command, adding it to the command list.
116 *
117 * @param node_id - Node id of the voice this command is generated for.
118 * @param buffer_offset - Base mix buffer index to generate this command at.
119 * @param input_index - Channel index and mix buffer offset for this command.
120 * @param volume - Mix volume added to the input samples.
121 * @param precision - Number of decimal bits for fixed point operations.
122 */
123 void GenerateVolumeCommand(s32 node_id, s16 buffer_offset, s16 input_index, f32 volume,
124 u8 precision);
125
126 /**
127 * Generate a volume ramp command, adding it to the command list.
128 *
129 * @param node_id - Node id of the voice this command is generated for.
130 * @param voice_info - The voice info this command takes its volumes from.
131 * @param buffer_count - Number of active mix buffers, command will generate at this index.
132 * @param precision - Number of decimal bits for fixed point operations.
133 */
134 void GenerateVolumeRampCommand(s32 node_id, VoiceInfo& voice_info, s16 buffer_count,
135 u8 precision);
136
137 /**
138 * Generate a biquad filter command from a voice, adding it to the command list.
139 *
140 * @param node_id - Node id of the voice this command is generated for.
141 * @param voice_info - The voice info this command takes biquad parameters from.
142 * @param voice_state - Used by the AudioRenderer to track previous samples.
143 * @param buffer_count - Number of active mix buffers,
144 * command will generate at this index + channel.
145 * @param channel - Channel index for this filter to work on.
146 * @param biquad_index - Which biquad filter to use for this command (0-1).
147 * @param use_float_processing - Should int or float processing be used?
148 */
149 void GenerateBiquadFilterCommand(s32 node_id, VoiceInfo& voice_info,
150 const VoiceState& voice_state, s16 buffer_count, s8 channel,
151 u32 biquad_index, bool use_float_processing);
152
153 /**
154 * Generate a biquad filter effect command, adding it to the command list.
155 *
156 * @param node_id - Node id of the voice this command is generated for.
157 * @param effect_info - The effect info this command takes biquad parameters from.
158 * @param buffer_offset - Mix buffer offset this command will use,
159 * command will generate at this index + channel.
160 * @param channel - Channel index for this filter to work on.
161 * @param needs_init - True if the biquad state needs initialisation.
162 * @param use_float_processing - Should int or float processing be used?
163 */
164 void GenerateBiquadFilterCommand(s32 node_id, EffectInfoBase& effect_info, s16 buffer_offset,
165 s8 channel, bool needs_init, bool use_float_processing);
166
167 /**
168 * Generate a mix command, adding it to the command list.
169 *
170 * @param node_id - Node id of the voice this command is generated for.
171 * @param input_index - Input mix buffer index for this command.
172 * Added to the buffer offset.
173 * @param output_index - Output mix buffer index for this command.
174 * Added to the buffer offset.
175 * @param buffer_offset - Mix buffer offset this command will use.
176 * @param volume - Volume to be applied to the input.
177 * @param precision - Number of decimal bits for fixed point operations.
178 */
179 void GenerateMixCommand(s32 node_id, s16 input_index, s16 output_index, s16 buffer_offset,
180 f32 volume, u8 precision);
181
182 /**
183 * Generate a mix ramp command, adding it to the command list.
184 *
185 * @param node_id - Node id of the voice this command is generated for.
186 * @param buffer_count - Number of active mix buffers.
187 * @param input_index - Input mix buffer index for this command.
188 * Added to buffer_count.
189 * @param output_index - Output mix buffer index for this command.
190 * Added to buffer_count.
191 * @param volume - Current mix volume used for calculating the ramp.
192 * @param prev_volume - Previous mix volume, used for calculating the ramp,
193 * also applied to the input.
194 * @param precision - Number of decimal bits for fixed point operations.
195 */
196 void GenerateMixRampCommand(s32 node_id, s16 buffer_count, s16 input_index, s16 output_index,
197 f32 volume, f32 prev_volume, CpuAddr prev_samples, u8 precision);
198
199 /**
200 * Generate a mix ramp grouped command, adding it to the command list.
201 *
202 * @param node_id - Node id of the voice this command is generated for.
203 * @param buffer_count - Number of active mix buffers.
204 * @param input_index - Input mix buffer index for this command.
205 * Added to buffer_count.
206 * @param output_index - Output mix buffer index for this command.
207 * Added to buffer_count.
208 * @param volumes - Current mix volumes used for calculating the ramp.
209 * @param prev_volumes - Previous mix volumes, used for calculating the ramp,
210 * also applied to the input.
211 * @param precision - Number of decimal bits for fixed point operations.
212 */
213 void GenerateMixRampGroupedCommand(s32 node_id, s16 buffer_count, s16 input_index,
214 s16 output_index, std::span<const f32> volumes,
215 std::span<const f32> prev_volumes, CpuAddr prev_samples,
216 u8 precision);
217
218 /**
219 * Generate a depop prepare command, adding it to the command list.
220 *
221 * @param node_id - Node id of the voice this command is generated for.
222 * @param voice_state - State to track the previous depop samples for each mix buffer.
223 * @param buffer - State to track the current depop samples for each mix buffer.
224 * @param buffer_count - Number of active mix buffers.
225 * @param buffer_offset - Base mix buffer index to generate the channel depops at.
226 * @param was_playing - Command only needs to work if the voice was previously playing.
227 */
228 void GenerateDepopPrepareCommand(s32 node_id, const VoiceState& voice_state,
229 std::span<const s32> buffer, s16 buffer_count,
230 s16 buffer_offset, bool was_playing);
231
232 /**
233 * Generate a depop command, adding it to the command list.
234 *
235 * @param node_id - Node id of the voice this command is generated for.
236 * @param mix_info - Mix info to get the buffer count and base offsets from.
237 * @param depop_buffer - Buffer of current depop sample values to be added to the input
238 * channels.
239 */
240 void GenerateDepopForMixBuffersCommand(s32 node_id, const MixInfo& mix_info,
241 std::span<const s32> depop_buffer);
242
243 /**
244 * Generate a delay command, adding it to the command list.
245 *
246 * @param node_id - Node id of the voice this command is generated for.
247 * @param effect_info - Delay effect info to generate this command from.
248 * @param buffer_offset - Base mix buffer offset to apply the apply the delay.
249 */
250 void GenerateDelayCommand(s32 node_id, EffectInfoBase& effect_info, s16 buffer_offset);
251
252 /**
253 * Generate an upsample command, adding it to the command list.
254 *
255 * @param node_id - Node id of the voice this command is generated for.
256 * @param buffer_offset - Base mix buffer offset to upsample.
257 * @param upsampler_info - Upsampler info to control the upsampling.
258 * @param input_count - Number of input channels to upsample.
259 * @param inputs - Input mix buffer indexes.
260 * @param buffer_count - Number of active mix buffers.
261 * @param sample_count - Source sample count of the input.
262 * @param sample_rate - Source sample rate of the input.
263 */
264 void GenerateUpsampleCommand(s32 node_id, s16 buffer_offset, UpsamplerInfo& upsampler_info,
265 u32 input_count, std::span<const s8> inputs, s16 buffer_count,
266 u32 sample_count, u32 sample_rate);
267
268 /**
269 * Generate a downmix 6 -> 2 command, adding it to the command list.
270 *
271 * @param node_id - Node id of the voice this command is generated for.
272 * @param inputs - Input mix buffer indexes.
273 * @param buffer_offset - Base mix buffer offset of the channels to downmix.
274 * @param downmix_coeff - Downmixing coefficients.
275 */
276 void GenerateDownMix6chTo2chCommand(s32 node_id, std::span<const s8> inputs, s16 buffer_offset,
277 std::span<const f32> downmix_coeff);
278
279 /**
280 * Generate an aux buffer command, adding it to the command list.
281 *
282 * @param node_id - Node id of the voice this command is generated for.
283 * @param effect_info - Aux effect info to generate this command from.
284 * @param input_index - Input mix buffer index for this command.
285 * Added to buffer_offset.
286 * @param output_index - Output mix buffer index for this command.
287 * Added to buffer_offset.
288 * @param buffer_offset - Base mix buffer offset to use.
289 * @param update_count - Number of samples to write back to the game as updated, can be 0.
290 * @param count_max - Maximum number of samples to read or write.
291 * @param write_offset - Current read or write offset within the buffer.
292 */
293 void GenerateAuxCommand(s32 node_id, EffectInfoBase& effect_info, s16 input_index,
294 s16 output_index, s16 buffer_offset, u32 update_count, u32 count_max,
295 u32 write_offset);
296
297 /**
298 * Generate a device sink command, adding it to the command list.
299 *
300 * @param node_id - Node id of the voice this command is generated for.
301 * @param buffer_offset - Base mix buffer offset to use.
302 * @param sink_info - The sink_info to generate this command from.
303 * @session_id - System session id this command is generated from.
304 * @samples_buffer - The buffer to be sent to the sink if upsampling is not used.
305 */
306 void GenerateDeviceSinkCommand(s32 node_id, s16 buffer_offset, SinkInfoBase& sink_info,
307 u32 session_id, std::span<s32> samples_buffer);
308
309 /**
310 * Generate a circular buffer sink command, adding it to the command list.
311 *
312 * @param node_id - Node id of the voice this command is generated for.
313 * @param sink_info - The sink_info to generate this command from.
314 * @param buffer_offset - Base mix buffer offset to use.
315 */
316 void GenerateCircularBufferSinkCommand(s32 node_id, SinkInfoBase& sink_info, s16 buffer_offset);
317
318 /**
319 * Generate a reverb command, adding it to the command list.
320 *
321 * @param node_id - Node id of the voice this command is generated for.
322 * @param effect_info - Reverb effect info to generate this command from.
323 * @param buffer_offset - Base mix buffer offset to use.
324 * @param long_size_pre_delay_supported - Should a longer pre-delay time be used before reverb
325 * begins?
326 */
327 void GenerateReverbCommand(s32 node_id, EffectInfoBase& effect_info, s16 buffer_offset,
328 bool long_size_pre_delay_supported);
329
330 /**
331 * Generate an I3DL2 reverb command, adding it to the command list.
332 *
333 * @param node_id - Node id of the voice this command is generated for.
334 * @param effect_info - I3DL2Reverb effect info to generate this command from.
335 * @param buffer_offset - Base mix buffer offset to use.
336 */
337 void GenerateI3dl2ReverbCommand(s32 node_id, EffectInfoBase& effect_info, s16 buffer_offset);
338
339 /**
340 * Generate a performance command, adding it to the command list.
341 *
342 * @param node_id - Node id of the voice this command is generated for.
343 * @param state - State of the performance.
344 * @param entry_addresses - The addresses to be filled in by the AudioRenderer.
345 */
346 void GeneratePerformanceCommand(s32 node_id, PerformanceState state,
347 const PerformanceEntryAddresses& entry_addresses);
348
349 /**
350 * Generate a clear mix command, adding it to the command list.
351 *
352 * @param node_id - Node id of the voice this command is generated for.
353 */
354 void GenerateClearMixCommand(s32 node_id);
355
356 /**
357 * Generate a copy mix command, adding it to the command list.
358 *
359 * @param node_id - Node id of the voice this command is generated for.
360 * @param effect_info - BiquadFilter effect info to generate this command from.
361 * @param buffer_offset - Base mix buffer offset to use.
362 * @param channel - Index to the effect's parameters input indexes for this command.
363 */
364 void GenerateCopyMixBufferCommand(s32 node_id, EffectInfoBase& effect_info, s16 buffer_offset,
365 s8 channel);
366
367 /**
368 * Generate a light limiter version 1 command, adding it to the command list.
369 *
370 * @param node_id - Node id of the voice this command is generated for.
371 * @param buffer_offset - Base mix buffer offset to use.
372 * @param parameter - Effect parameter to generate from.
373 * @param state - State used by the AudioRenderer between commands.
374 * @param enabled - Is this command enabled?
375 * @param workbuffer - Game-supplied memory for the state.
376 */
377 void GenerateLightLimiterCommand(s32 node_id, s16 buffer_offset,
378 const LightLimiterInfo::ParameterVersion1& parameter,
379 const LightLimiterInfo::State& state, bool enabled,
380 CpuAddr workbuffer);
381
382 /**
383 * Generate a light limiter version 2 command, adding it to the command list.
384 *
385 * @param node_id - Node id of the voice this command is generated for.
386 * @param buffer_offset - Base mix buffer offset to use.
387 * @param parameter - Effect parameter to generate from.
388 * @param statistics - Statistics reported by the AudioRenderer on the limiter's state.
389 * @param state - State used by the AudioRenderer between commands.
390 * @param enabled - Is this command enabled?
391 * @param workbuffer - Game-supplied memory for the state.
392 */
393 void GenerateLightLimiterCommand(s32 node_id, s16 buffer_offset,
394 const LightLimiterInfo::ParameterVersion2& parameter,
395 const LightLimiterInfo::StatisticsInternal& statistics,
396 const LightLimiterInfo::State& state, bool enabled,
397 CpuAddr workbuffer);
398
399 /**
400 * Generate a multitap biquad filter command, adding it to the command list.
401 *
402 * @param node_id - Node id of the voice this command is generated for.
403 * @param voice_info - The voice info this command takes biquad parameters from.
404 * @param voice_state - Used by the AudioRenderer to track previous samples.
405 * @param buffer_count - Number of active mix buffers,
406 * command will generate at this index + channel.
407 * @param channel - Channel index for this filter to work on.
408 */
409 void GenerateMultitapBiquadFilterCommand(s32 node_id, VoiceInfo& voice_info,
410 const VoiceState& voice_state, s16 buffer_count,
411 s8 channel);
412
413 /**
414 * Generate a capture command, adding it to the command list.
415 *
416 * @param node_id - Node id of the voice this command is generated for.
417 * @param effect_info - Capture effect info to generate this command from.
418 * @param input_index - Input mix buffer index for this command.
419 * Added to buffer_offset.
420 * @param output_index - Output mix buffer index for this command (unused).
421 * Added to buffer_offset.
422 * @param buffer_offset - Base mix buffer offset to use.
423 * @param update_count - Number of samples to write back to the game as updated, can be 0.
424 * @param count_max - Maximum number of samples to read or write.
425 * @param write_offset - Current read or write offset within the buffer.
426 */
427 void GenerateCaptureCommand(s32 node_id, EffectInfoBase& effect_info, s16 input_index,
428 s16 output_index, s16 buffer_offset, u32 update_count,
429 u32 count_max, u32 write_offset);
430
431 /**
432 * Generate a compressor command, adding it to the command list.
433 *
434 * @param buffer_offset - Base mix buffer offset to use.
435 * @param effect_info - Capture effect info to generate this command from.
436 * @param node_id - Node id of the voice this command is generated for.
437 */
438 void GenerateCompressorCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id);
439
440 /// Command list buffer generated commands will be added to
441 std::span<u8> command_list{};
442 /// Input sample count, unused
443 u32 sample_count{};
444 /// Input sample rate, unused
445 u32 sample_rate{};
446 /// Current size of the command buffer
447 u64 size{};
448 /// Current number of commands added
449 u32 count{};
450 /// Current estimated processing time for all commands
451 u32 estimated_process_time{};
452 /// Used for mapping buffers for the AudioRenderer
453 MemoryPoolInfo* memory_pool{};
454 /// Used for estimating command process times
455 ICommandProcessingTimeEstimator* time_estimator{};
456 /// Used to check which rendering features are currently enabled
457 BehaviorInfo* behavior{};
458
459private:
460 template <typename T, CommandId Id>
461 T& GenerateStart(const s32 node_id);
462 template <typename T>
463 void GenerateEnd(T& cmd);
464};
465
466} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/command_generator.cpp b/src/audio_core/renderer/command/command_generator.cpp
new file mode 100644
index 000000000..2ea50d128
--- /dev/null
+++ b/src/audio_core/renderer/command/command_generator.cpp
@@ -0,0 +1,796 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/common/audio_renderer_parameter.h"
5#include "audio_core/renderer/behavior/behavior_info.h"
6#include "audio_core/renderer/command/command_buffer.h"
7#include "audio_core/renderer/command/command_generator.h"
8#include "audio_core/renderer/command/command_list_header.h"
9#include "audio_core/renderer/effect/aux_.h"
10#include "audio_core/renderer/effect/biquad_filter.h"
11#include "audio_core/renderer/effect/buffer_mixer.h"
12#include "audio_core/renderer/effect/capture.h"
13#include "audio_core/renderer/effect/effect_context.h"
14#include "audio_core/renderer/effect/light_limiter.h"
15#include "audio_core/renderer/mix/mix_context.h"
16#include "audio_core/renderer/performance/detail_aspect.h"
17#include "audio_core/renderer/performance/entry_aspect.h"
18#include "audio_core/renderer/sink/device_sink_info.h"
19#include "audio_core/renderer/sink/sink_context.h"
20#include "audio_core/renderer/splitter/splitter_context.h"
21#include "audio_core/renderer/voice/voice_context.h"
22#include "common/alignment.h"
23
24namespace AudioCore::AudioRenderer {
25
26CommandGenerator::CommandGenerator(CommandBuffer& command_buffer_,
27 const CommandListHeader& command_list_header_,
28 const AudioRendererSystemContext& render_context_,
29 VoiceContext& voice_context_, MixContext& mix_context_,
30 EffectContext& effect_context_, SinkContext& sink_context_,
31 SplitterContext& splitter_context_,
32 PerformanceManager* performance_manager_)
33 : command_buffer{command_buffer_}, command_header{command_list_header_},
34 render_context{render_context_}, voice_context{voice_context_}, mix_context{mix_context_},
35 effect_context{effect_context_}, sink_context{sink_context_},
36 splitter_context{splitter_context_}, performance_manager{performance_manager_} {
37 command_buffer.GenerateClearMixCommand(InvalidNodeId);
38}
39
40void CommandGenerator::GenerateDataSourceCommand(VoiceInfo& voice_info,
41 const VoiceState& voice_state, const s8 channel) {
42 if (voice_info.mix_id == UnusedMixId) {
43 if (voice_info.splitter_id != UnusedSplitterId) {
44 auto destination{splitter_context.GetDesintationData(voice_info.splitter_id, 0)};
45 u32 dest_id{0};
46 while (destination != nullptr) {
47 if (destination->IsConfigured()) {
48 auto mix_id{destination->GetMixId()};
49 if (mix_id < mix_context.GetCount()) {
50 auto mix_info{mix_context.GetInfo(mix_id)};
51 command_buffer.GenerateDepopPrepareCommand(
52 voice_info.node_id, voice_state, render_context.depop_buffer,
53 mix_info->buffer_count, mix_info->buffer_offset,
54 voice_info.was_playing);
55 }
56 }
57 dest_id++;
58 destination = splitter_context.GetDesintationData(voice_info.splitter_id, dest_id);
59 }
60 }
61 } else {
62 auto mix_info{mix_context.GetInfo(voice_info.mix_id)};
63 command_buffer.GenerateDepopPrepareCommand(
64 voice_info.node_id, voice_state, render_context.depop_buffer, mix_info->buffer_count,
65 mix_info->buffer_offset, voice_info.was_playing);
66 }
67
68 if (voice_info.was_playing) {
69 return;
70 }
71
72 if (render_context.behavior->IsWaveBufferVer2Supported()) {
73 switch (voice_info.sample_format) {
74 case SampleFormat::PcmInt16:
75 command_buffer.GeneratePcmInt16Version2Command(
76 voice_info.node_id, voice_info, voice_state, render_context.mix_buffer_count,
77 channel);
78 break;
79 case SampleFormat::PcmFloat:
80 command_buffer.GeneratePcmFloatVersion2Command(
81 voice_info.node_id, voice_info, voice_state, render_context.mix_buffer_count,
82 channel);
83 break;
84 case SampleFormat::Adpcm:
85 command_buffer.GenerateAdpcmVersion2Command(voice_info.node_id, voice_info, voice_state,
86 render_context.mix_buffer_count, channel);
87 break;
88 default:
89 LOG_ERROR(Service_Audio, "Invalid SampleFormat {}",
90 static_cast<u32>(voice_info.sample_format));
91 break;
92 }
93 } else {
94 switch (voice_info.sample_format) {
95 case SampleFormat::PcmInt16:
96 command_buffer.GeneratePcmInt16Version1Command(
97 voice_info.node_id, *command_buffer.memory_pool, voice_info, voice_state,
98 render_context.mix_buffer_count, channel);
99 break;
100 case SampleFormat::PcmFloat:
101 command_buffer.GeneratePcmFloatVersion1Command(
102 voice_info.node_id, *command_buffer.memory_pool, voice_info, voice_state,
103 render_context.mix_buffer_count, channel);
104 break;
105 case SampleFormat::Adpcm:
106 command_buffer.GenerateAdpcmVersion1Command(
107 voice_info.node_id, *command_buffer.memory_pool, voice_info, voice_state,
108 render_context.mix_buffer_count, channel);
109 break;
110 default:
111 LOG_ERROR(Service_Audio, "Invalid SampleFormat {}",
112 static_cast<u32>(voice_info.sample_format));
113 break;
114 }
115 }
116}
117
118void CommandGenerator::GenerateVoiceMixCommand(std::span<const f32> mix_volumes,
119 std::span<const f32> prev_mix_volumes,
120 const VoiceState& voice_state, s16 output_index,
121 const s16 buffer_count, const s16 input_index,
122 const s32 node_id) {
123 u8 precision{15};
124 if (render_context.behavior->IsVolumeMixParameterPrecisionQ23Supported()) {
125 precision = 23;
126 }
127
128 if (buffer_count > 8) {
129 const auto prev_samples{render_context.memory_pool_info->Translate(
130 CpuAddr(voice_state.previous_samples.data()), buffer_count * sizeof(s32))};
131 command_buffer.GenerateMixRampGroupedCommand(node_id, buffer_count, input_index,
132 output_index, mix_volumes, prev_mix_volumes,
133 prev_samples, precision);
134 } else {
135 for (s16 i = 0; i < buffer_count; i++, output_index++) {
136 const auto prev_samples{render_context.memory_pool_info->Translate(
137 CpuAddr(&voice_state.previous_samples[i]), sizeof(s32))};
138
139 command_buffer.GenerateMixRampCommand(node_id, buffer_count, input_index, output_index,
140 mix_volumes[i], prev_mix_volumes[i], prev_samples,
141 precision);
142 }
143 }
144}
145
146void CommandGenerator::GenerateBiquadFilterCommandForVoice(VoiceInfo& voice_info,
147 const VoiceState& voice_state,
148 const s16 buffer_count, const s8 channel,
149 const s32 node_id) {
150 const bool both_biquads_enabled{voice_info.biquads[0].enabled && voice_info.biquads[1].enabled};
151 const auto use_float_processing{render_context.behavior->UseBiquadFilterFloatProcessing()};
152
153 if (both_biquads_enabled && render_context.behavior->UseMultiTapBiquadFilterProcessing() &&
154 use_float_processing) {
155 command_buffer.GenerateMultitapBiquadFilterCommand(node_id, voice_info, voice_state,
156 buffer_count, channel);
157 } else {
158 for (u32 i = 0; i < MaxBiquadFilters; i++) {
159 if (voice_info.biquads[i].enabled) {
160 command_buffer.GenerateBiquadFilterCommand(node_id, voice_info, voice_state,
161 buffer_count, channel, i,
162 use_float_processing);
163 }
164 }
165 }
166}
167
168void CommandGenerator::GenerateVoiceCommand(VoiceInfo& voice_info) {
169 u8 precision{15};
170 if (render_context.behavior->IsVolumeMixParameterPrecisionQ23Supported()) {
171 precision = 23;
172 }
173
174 for (s8 channel = 0; channel < voice_info.channel_count; channel++) {
175 const auto resource_id{voice_info.channel_resource_ids[channel]};
176 auto& voice_state{voice_context.GetDspSharedState(resource_id)};
177 auto& channel_resource{voice_context.GetChannelResource(resource_id)};
178
179 PerformanceDetailType detail_type{PerformanceDetailType::Invalid};
180 switch (voice_info.sample_format) {
181 case SampleFormat::PcmInt16:
182 detail_type = PerformanceDetailType::Unk1;
183 break;
184 case SampleFormat::PcmFloat:
185 detail_type = PerformanceDetailType::Unk10;
186 break;
187 default:
188 detail_type = PerformanceDetailType::Unk2;
189 break;
190 }
191
192 DetailAspect data_source_detail(*this, PerformanceEntryType::Voice, voice_info.node_id,
193 detail_type);
194 GenerateDataSourceCommand(voice_info, voice_state, channel);
195
196 if (data_source_detail.initialized) {
197 command_buffer.GeneratePerformanceCommand(data_source_detail.node_id,
198 PerformanceState::Stop,
199 data_source_detail.performance_entry_address);
200 }
201
202 if (voice_info.was_playing) {
203 voice_info.prev_volume = 0.0f;
204 continue;
205 }
206
207 if (!voice_info.HasAnyConnection()) {
208 continue;
209 }
210
211 DetailAspect biquad_detail_aspect(*this, PerformanceEntryType::Voice, voice_info.node_id,
212 PerformanceDetailType::Unk4);
213 GenerateBiquadFilterCommandForVoice(
214 voice_info, voice_state, render_context.mix_buffer_count, channel, voice_info.node_id);
215
216 if (biquad_detail_aspect.initialized) {
217 command_buffer.GeneratePerformanceCommand(
218 biquad_detail_aspect.node_id, PerformanceState::Stop,
219 biquad_detail_aspect.performance_entry_address);
220 }
221
222 DetailAspect volume_ramp_detail_aspect(*this, PerformanceEntryType::Voice,
223 voice_info.node_id, PerformanceDetailType::Unk3);
224 command_buffer.GenerateVolumeRampCommand(
225 voice_info.node_id, voice_info, render_context.mix_buffer_count + channel, precision);
226 if (volume_ramp_detail_aspect.initialized) {
227 command_buffer.GeneratePerformanceCommand(
228 volume_ramp_detail_aspect.node_id, PerformanceState::Stop,
229 volume_ramp_detail_aspect.performance_entry_address);
230 }
231
232 voice_info.prev_volume = voice_info.volume;
233
234 if (voice_info.mix_id == UnusedMixId) {
235 if (voice_info.splitter_id != UnusedSplitterId) {
236 auto i{channel};
237 auto destination{splitter_context.GetDesintationData(voice_info.splitter_id, i)};
238 while (destination != nullptr) {
239 if (destination->IsConfigured()) {
240 const auto mix_id{destination->GetMixId()};
241 if (mix_id < mix_context.GetCount() &&
242 static_cast<s32>(mix_id) != UnusedSplitterId) {
243 auto mix_info{mix_context.GetInfo(mix_id)};
244 GenerateVoiceMixCommand(
245 destination->GetMixVolume(), destination->GetMixVolumePrev(),
246 voice_state, mix_info->buffer_offset, mix_info->buffer_count,
247 render_context.mix_buffer_count + channel, voice_info.node_id);
248 destination->MarkAsNeedToUpdateInternalState();
249 }
250 }
251 i += voice_info.channel_count;
252 destination = splitter_context.GetDesintationData(voice_info.splitter_id, i);
253 }
254 }
255 } else {
256 DetailAspect volume_mix_detail_aspect(*this, PerformanceEntryType::Voice,
257 voice_info.node_id, PerformanceDetailType::Unk3);
258 auto mix_info{mix_context.GetInfo(voice_info.mix_id)};
259 GenerateVoiceMixCommand(channel_resource.mix_volumes, channel_resource.prev_mix_volumes,
260 voice_state, mix_info->buffer_offset, mix_info->buffer_count,
261 render_context.mix_buffer_count + channel, voice_info.node_id);
262 if (volume_mix_detail_aspect.initialized) {
263 command_buffer.GeneratePerformanceCommand(
264 volume_mix_detail_aspect.node_id, PerformanceState::Stop,
265 volume_mix_detail_aspect.performance_entry_address);
266 }
267
268 channel_resource.prev_mix_volumes = channel_resource.mix_volumes;
269 }
270 voice_info.biquad_initialized[0] = voice_info.biquads[0].enabled;
271 voice_info.biquad_initialized[1] = voice_info.biquads[1].enabled;
272 }
273}
274
275void CommandGenerator::GenerateVoiceCommands() {
276 const auto voice_count{voice_context.GetCount()};
277
278 for (u32 i = 0; i < voice_count; i++) {
279 auto sorted_info{voice_context.GetSortedInfo(i)};
280
281 if (sorted_info->ShouldSkip() || !sorted_info->UpdateForCommandGeneration(voice_context)) {
282 continue;
283 }
284
285 EntryAspect voice_entry_aspect(*this, PerformanceEntryType::Voice, sorted_info->node_id);
286
287 GenerateVoiceCommand(*sorted_info);
288
289 if (voice_entry_aspect.initialized) {
290 command_buffer.GeneratePerformanceCommand(voice_entry_aspect.node_id,
291 PerformanceState::Stop,
292 voice_entry_aspect.performance_entry_address);
293 }
294 }
295
296 splitter_context.UpdateInternalState();
297}
298
299void CommandGenerator::GenerateBufferMixerCommand(const s16 buffer_offset,
300 EffectInfoBase& effect_info, const s32 node_id) {
301 u8 precision{15};
302 if (render_context.behavior->IsVolumeMixParameterPrecisionQ23Supported()) {
303 precision = 23;
304 }
305
306 if (effect_info.IsEnabled()) {
307 const auto& parameter{
308 *reinterpret_cast<BufferMixerInfo::ParameterVersion1*>(effect_info.GetParameter())};
309 for (u32 i = 0; i < parameter.mix_count; i++) {
310 if (parameter.volumes[i] != 0.0f) {
311 command_buffer.GenerateMixCommand(node_id, buffer_offset + parameter.inputs[i],
312 buffer_offset + parameter.outputs[i],
313 buffer_offset, parameter.volumes[i], precision);
314 }
315 }
316 }
317}
318
319void CommandGenerator::GenerateDelayCommand(const s16 buffer_offset, EffectInfoBase& effect_info,
320 const s32 node_id) {
321 command_buffer.GenerateDelayCommand(node_id, effect_info, buffer_offset);
322}
323
324void CommandGenerator::GenerateReverbCommand(const s16 buffer_offset, EffectInfoBase& effect_info,
325 const s32 node_id,
326 const bool long_size_pre_delay_supported) {
327 command_buffer.GenerateReverbCommand(node_id, effect_info, buffer_offset,
328 long_size_pre_delay_supported);
329}
330
331void CommandGenerator::GenerateI3dl2ReverbEffectCommand(const s16 buffer_offset,
332 EffectInfoBase& effect_info,
333 const s32 node_id) {
334 command_buffer.GenerateI3dl2ReverbCommand(node_id, effect_info, buffer_offset);
335}
336
337void CommandGenerator::GenerateAuxCommand(const s16 buffer_offset, EffectInfoBase& effect_info,
338 const s32 node_id) {
339
340 if (effect_info.IsEnabled()) {
341 effect_info.GetWorkbuffer(0);
342 effect_info.GetWorkbuffer(1);
343 }
344
345 if (effect_info.GetSendBuffer() != 0 && effect_info.GetReturnBuffer() != 0) {
346 const auto& parameter{
347 *reinterpret_cast<AuxInfo::ParameterVersion1*>(effect_info.GetParameter())};
348 auto channel_index{parameter.mix_buffer_count - 1};
349 u32 write_offset{0};
350 for (u32 i = 0; i < parameter.mix_buffer_count; i++, channel_index--) {
351 auto new_update_count{command_header.sample_count + write_offset};
352 const auto update_count{channel_index > 0 ? 0 : new_update_count};
353 command_buffer.GenerateAuxCommand(node_id, effect_info, parameter.inputs[i],
354 parameter.outputs[i], buffer_offset, update_count,
355 parameter.count_max, write_offset);
356 write_offset = new_update_count;
357 }
358 }
359}
360
361void CommandGenerator::GenerateBiquadFilterEffectCommand(const s16 buffer_offset,
362 EffectInfoBase& effect_info,
363 const s32 node_id) {
364 const auto& parameter{
365 *reinterpret_cast<BiquadFilterInfo::ParameterVersion1*>(effect_info.GetParameter())};
366 if (effect_info.IsEnabled()) {
367 bool needs_init{false};
368
369 switch (parameter.state) {
370 case EffectInfoBase::ParameterState::Initialized:
371 needs_init = true;
372 break;
373 case EffectInfoBase::ParameterState::Updating:
374 case EffectInfoBase::ParameterState::Updated:
375 if (render_context.behavior->IsBiquadFilterEffectStateClearBugFixed()) {
376 needs_init = false;
377 } else {
378 needs_init = parameter.state == EffectInfoBase::ParameterState::Updating;
379 }
380 break;
381 default:
382 LOG_ERROR(Service_Audio, "Invalid biquad parameter state {}",
383 static_cast<u32>(parameter.state));
384 break;
385 }
386
387 for (s8 channel = 0; channel < parameter.channel_count; channel++) {
388 command_buffer.GenerateBiquadFilterCommand(
389 node_id, effect_info, buffer_offset, channel, needs_init,
390 render_context.behavior->UseBiquadFilterFloatProcessing());
391 }
392 } else {
393 for (s8 channel = 0; channel < parameter.channel_count; channel++) {
394 command_buffer.GenerateCopyMixBufferCommand(node_id, effect_info, buffer_offset,
395 channel);
396 }
397 }
398}
399
400void CommandGenerator::GenerateLightLimiterEffectCommand(const s16 buffer_offset,
401 EffectInfoBase& effect_info,
402 const s32 node_id,
403 const u32 effect_index) {
404
405 const auto& state{*reinterpret_cast<LightLimiterInfo::State*>(effect_info.GetStateBuffer())};
406
407 if (render_context.behavior->IsEffectInfoVersion2Supported()) {
408 const auto& parameter{
409 *reinterpret_cast<LightLimiterInfo::ParameterVersion2*>(effect_info.GetParameter())};
410 const auto& result_state{*reinterpret_cast<LightLimiterInfo::StatisticsInternal*>(
411 &effect_context.GetDspSharedResultState(effect_index))};
412 command_buffer.GenerateLightLimiterCommand(node_id, buffer_offset, parameter, result_state,
413 state, effect_info.IsEnabled(),
414 effect_info.GetWorkbuffer(-1));
415 } else {
416 const auto& parameter{
417 *reinterpret_cast<LightLimiterInfo::ParameterVersion1*>(effect_info.GetParameter())};
418 command_buffer.GenerateLightLimiterCommand(node_id, buffer_offset, parameter, state,
419 effect_info.IsEnabled(),
420 effect_info.GetWorkbuffer(-1));
421 }
422}
423
424void CommandGenerator::GenerateCaptureCommand(const s16 buffer_offset, EffectInfoBase& effect_info,
425 const s32 node_id) {
426 if (effect_info.IsEnabled()) {
427 effect_info.GetWorkbuffer(0);
428 }
429
430 if (effect_info.GetSendBuffer()) {
431 const auto& parameter{
432 *reinterpret_cast<AuxInfo::ParameterVersion1*>(effect_info.GetParameter())};
433 auto channel_index{parameter.mix_buffer_count - 1};
434 u32 write_offset{0};
435 for (u32 i = 0; i < parameter.mix_buffer_count; i++, channel_index--) {
436 auto new_update_count{command_header.sample_count + write_offset};
437 const auto update_count{channel_index > 0 ? 0 : new_update_count};
438 command_buffer.GenerateCaptureCommand(node_id, effect_info, parameter.inputs[i],
439 parameter.outputs[i], buffer_offset, update_count,
440 parameter.count_max, write_offset);
441 write_offset = new_update_count;
442 }
443 }
444}
445
446void CommandGenerator::GenerateCompressorCommand(const s16 buffer_offset,
447 EffectInfoBase& effect_info, const s32 node_id) {
448 command_buffer.GenerateCompressorCommand(buffer_offset, effect_info, node_id);
449}
450
451void CommandGenerator::GenerateEffectCommand(MixInfo& mix_info) {
452 const auto effect_count{effect_context.GetCount()};
453 for (u32 i = 0; i < effect_count; i++) {
454 const auto effect_index{mix_info.effect_order_buffer[i]};
455 if (effect_index == -1) {
456 break;
457 }
458
459 auto& effect_info = effect_context.GetInfo(effect_index);
460 if (effect_info.ShouldSkip()) {
461 continue;
462 }
463
464 const auto entry_type{mix_info.mix_id == FinalMixId ? PerformanceEntryType::FinalMix
465 : PerformanceEntryType::SubMix};
466
467 switch (effect_info.GetType()) {
468 case EffectInfoBase::Type::Mix: {
469 DetailAspect mix_detail_aspect(*this, entry_type, mix_info.node_id,
470 PerformanceDetailType::Unk5);
471 GenerateBufferMixerCommand(mix_info.buffer_offset, effect_info, mix_info.node_id);
472 if (mix_detail_aspect.initialized) {
473 command_buffer.GeneratePerformanceCommand(
474 mix_detail_aspect.node_id, PerformanceState::Stop,
475 mix_detail_aspect.performance_entry_address);
476 }
477 } break;
478
479 case EffectInfoBase::Type::Aux: {
480 DetailAspect aux_detail_aspect(*this, entry_type, mix_info.node_id,
481 PerformanceDetailType::Unk7);
482 GenerateAuxCommand(mix_info.buffer_offset, effect_info, mix_info.node_id);
483 if (aux_detail_aspect.initialized) {
484 command_buffer.GeneratePerformanceCommand(
485 aux_detail_aspect.node_id, PerformanceState::Stop,
486 aux_detail_aspect.performance_entry_address);
487 }
488 } break;
489
490 case EffectInfoBase::Type::Delay: {
491 DetailAspect delay_detail_aspect(*this, entry_type, mix_info.node_id,
492 PerformanceDetailType::Unk6);
493 GenerateDelayCommand(mix_info.buffer_offset, effect_info, mix_info.node_id);
494 if (delay_detail_aspect.initialized) {
495 command_buffer.GeneratePerformanceCommand(
496 delay_detail_aspect.node_id, PerformanceState::Stop,
497 delay_detail_aspect.performance_entry_address);
498 }
499 } break;
500
501 case EffectInfoBase::Type::Reverb: {
502 DetailAspect reverb_detail_aspect(*this, entry_type, mix_info.node_id,
503 PerformanceDetailType::Unk8);
504 GenerateReverbCommand(mix_info.buffer_offset, effect_info, mix_info.node_id,
505 render_context.behavior->IsLongSizePreDelaySupported());
506 if (reverb_detail_aspect.initialized) {
507 command_buffer.GeneratePerformanceCommand(
508 reverb_detail_aspect.node_id, PerformanceState::Stop,
509 reverb_detail_aspect.performance_entry_address);
510 }
511 } break;
512
513 case EffectInfoBase::Type::I3dl2Reverb: {
514 DetailAspect i3dl2_detail_aspect(*this, entry_type, mix_info.node_id,
515 PerformanceDetailType::Unk9);
516 GenerateI3dl2ReverbEffectCommand(mix_info.buffer_offset, effect_info, mix_info.node_id);
517 if (i3dl2_detail_aspect.initialized) {
518 command_buffer.GeneratePerformanceCommand(
519 i3dl2_detail_aspect.node_id, PerformanceState::Stop,
520 i3dl2_detail_aspect.performance_entry_address);
521 }
522 } break;
523
524 case EffectInfoBase::Type::BiquadFilter: {
525 DetailAspect biquad_detail_aspect(*this, entry_type, mix_info.node_id,
526 PerformanceDetailType::Unk4);
527 GenerateBiquadFilterEffectCommand(mix_info.buffer_offset, effect_info,
528 mix_info.node_id);
529 if (biquad_detail_aspect.initialized) {
530 command_buffer.GeneratePerformanceCommand(
531 biquad_detail_aspect.node_id, PerformanceState::Stop,
532 biquad_detail_aspect.performance_entry_address);
533 }
534 } break;
535
536 case EffectInfoBase::Type::LightLimiter: {
537 DetailAspect light_limiter_detail_aspect(*this, entry_type, mix_info.node_id,
538 PerformanceDetailType::Unk11);
539 GenerateLightLimiterEffectCommand(mix_info.buffer_offset, effect_info, mix_info.node_id,
540 effect_index);
541 if (light_limiter_detail_aspect.initialized) {
542 command_buffer.GeneratePerformanceCommand(
543 light_limiter_detail_aspect.node_id, PerformanceState::Stop,
544 light_limiter_detail_aspect.performance_entry_address);
545 }
546 } break;
547
548 case EffectInfoBase::Type::Capture: {
549 DetailAspect capture_detail_aspect(*this, entry_type, mix_info.node_id,
550 PerformanceDetailType::Unk12);
551 GenerateCaptureCommand(mix_info.buffer_offset, effect_info, mix_info.node_id);
552 if (capture_detail_aspect.initialized) {
553 command_buffer.GeneratePerformanceCommand(
554 capture_detail_aspect.node_id, PerformanceState::Stop,
555 capture_detail_aspect.performance_entry_address);
556 }
557 } break;
558
559 case EffectInfoBase::Type::Compressor: {
560 DetailAspect capture_detail_aspect(*this, entry_type, mix_info.node_id,
561 PerformanceDetailType::Unk13);
562 GenerateCompressorCommand(mix_info.buffer_offset, effect_info, mix_info.node_id);
563 if (capture_detail_aspect.initialized) {
564 command_buffer.GeneratePerformanceCommand(
565 capture_detail_aspect.node_id, PerformanceState::Stop,
566 capture_detail_aspect.performance_entry_address);
567 }
568 } break;
569
570 default:
571 LOG_ERROR(Service_Audio, "Invalid effect type {}",
572 static_cast<u32>(effect_info.GetType()));
573 break;
574 }
575
576 effect_info.UpdateForCommandGeneration();
577 }
578}
579
580void CommandGenerator::GenerateMixCommands(MixInfo& mix_info) {
581 u8 precision{15};
582 if (render_context.behavior->IsVolumeMixParameterPrecisionQ23Supported()) {
583 precision = 23;
584 }
585
586 if (!mix_info.HasAnyConnection()) {
587 return;
588 }
589
590 if (mix_info.dst_mix_id == UnusedMixId) {
591 if (mix_info.dst_splitter_id != UnusedSplitterId) {
592 s16 dest_id{0};
593 auto destination{
594 splitter_context.GetDesintationData(mix_info.dst_splitter_id, dest_id)};
595 while (destination != nullptr) {
596 if (destination->IsConfigured()) {
597 auto splitter_mix_id{destination->GetMixId()};
598 if (splitter_mix_id < mix_context.GetCount()) {
599 auto splitter_mix_info{mix_context.GetInfo(splitter_mix_id)};
600 const s16 input_index{static_cast<s16>(mix_info.buffer_offset +
601 (dest_id % mix_info.buffer_count))};
602 for (s16 i = 0; i < splitter_mix_info->buffer_count; i++) {
603 auto volume{mix_info.volume * destination->GetMixVolume(i)};
604 if (volume != 0.0f) {
605 command_buffer.GenerateMixCommand(
606 mix_info.node_id, input_index,
607 splitter_mix_info->buffer_offset + i, mix_info.buffer_offset,
608 volume, precision);
609 }
610 }
611 }
612 }
613 dest_id++;
614 destination =
615 splitter_context.GetDesintationData(mix_info.dst_splitter_id, dest_id);
616 }
617 }
618 } else {
619 auto dest_mix_info{mix_context.GetInfo(mix_info.dst_mix_id)};
620 for (s16 i = 0; i < mix_info.buffer_count; i++) {
621 for (s16 j = 0; j < dest_mix_info->buffer_count; j++) {
622 auto volume{mix_info.volume * mix_info.mix_volumes[i][j]};
623 if (volume != 0.0f) {
624 command_buffer.GenerateMixCommand(mix_info.node_id, mix_info.buffer_offset + i,
625 dest_mix_info->buffer_offset + j,
626 mix_info.buffer_offset, volume, precision);
627 }
628 }
629 }
630 }
631}
632
633void CommandGenerator::GenerateSubMixCommand(MixInfo& mix_info) {
634 command_buffer.GenerateDepopForMixBuffersCommand(mix_info.node_id, mix_info,
635 render_context.depop_buffer);
636 GenerateEffectCommand(mix_info);
637
638 DetailAspect mix_detail_aspect(*this, PerformanceEntryType::SubMix, mix_info.node_id,
639 PerformanceDetailType::Unk5);
640
641 GenerateMixCommands(mix_info);
642
643 if (mix_detail_aspect.initialized) {
644 command_buffer.GeneratePerformanceCommand(mix_detail_aspect.node_id, PerformanceState::Stop,
645 mix_detail_aspect.performance_entry_address);
646 }
647}
648
649void CommandGenerator::GenerateSubMixCommands() {
650 const auto submix_count{mix_context.GetCount()};
651 for (s32 i = 0; i < submix_count; i++) {
652 auto sorted_info{mix_context.GetSortedInfo(i)};
653 if (!sorted_info->in_use || sorted_info->mix_id == FinalMixId) {
654 continue;
655 }
656
657 EntryAspect submix_entry_aspect(*this, PerformanceEntryType::SubMix, sorted_info->node_id);
658
659 GenerateSubMixCommand(*sorted_info);
660
661 if (submix_entry_aspect.initialized) {
662 command_buffer.GeneratePerformanceCommand(
663 submix_entry_aspect.node_id, PerformanceState::Stop,
664 submix_entry_aspect.performance_entry_address);
665 }
666 }
667}
668
669void CommandGenerator::GenerateFinalMixCommand() {
670 auto& final_mix_info{*mix_context.GetFinalMixInfo()};
671
672 command_buffer.GenerateDepopForMixBuffersCommand(final_mix_info.node_id, final_mix_info,
673 render_context.depop_buffer);
674 GenerateEffectCommand(final_mix_info);
675
676 u8 precision{15};
677 if (render_context.behavior->IsVolumeMixParameterPrecisionQ23Supported()) {
678 precision = 23;
679 }
680
681 for (s16 i = 0; i < final_mix_info.buffer_count; i++) {
682 DetailAspect volume_aspect(*this, PerformanceEntryType::FinalMix, final_mix_info.node_id,
683 PerformanceDetailType::Unk3);
684 command_buffer.GenerateVolumeCommand(final_mix_info.node_id, final_mix_info.buffer_offset,
685 i, final_mix_info.volume, precision);
686 if (volume_aspect.initialized) {
687 command_buffer.GeneratePerformanceCommand(volume_aspect.node_id, PerformanceState::Stop,
688 volume_aspect.performance_entry_address);
689 }
690 }
691}
692
693void CommandGenerator::GenerateFinalMixCommands() {
694 auto final_mix_info{mix_context.GetFinalMixInfo()};
695 EntryAspect final_mix_entry(*this, PerformanceEntryType::FinalMix, final_mix_info->node_id);
696 GenerateFinalMixCommand();
697 if (final_mix_entry.initialized) {
698 command_buffer.GeneratePerformanceCommand(final_mix_entry.node_id, PerformanceState::Stop,
699 final_mix_entry.performance_entry_address);
700 }
701}
702
703void CommandGenerator::GenerateSinkCommands() {
704 const auto sink_count{sink_context.GetCount()};
705
706 for (u32 i = 0; i < sink_count; i++) {
707 auto sink_info{sink_context.GetInfo(i)};
708 if (sink_info->IsUsed() && sink_info->GetType() == SinkInfoBase::Type::DeviceSink) {
709 auto state{reinterpret_cast<DeviceSinkInfo::DeviceState*>(sink_info->GetState())};
710 if (command_header.sample_rate != TargetSampleRate &&
711 state->upsampler_info == nullptr) {
712 auto device_state{sink_info->GetDeviceState()};
713 device_state->upsampler_info = render_context.upsampler_manager->Allocate();
714 }
715
716 EntryAspect device_sink_entry(*this, PerformanceEntryType::Sink,
717 sink_info->GetNodeId());
718 auto final_mix{mix_context.GetFinalMixInfo()};
719 GenerateSinkCommand(final_mix->buffer_offset, *sink_info);
720
721 if (device_sink_entry.initialized) {
722 command_buffer.GeneratePerformanceCommand(
723 device_sink_entry.node_id, PerformanceState::Stop,
724 device_sink_entry.performance_entry_address);
725 }
726 }
727 }
728
729 for (u32 i = 0; i < sink_count; i++) {
730 auto sink_info{sink_context.GetInfo(i)};
731 if (sink_info->IsUsed() && sink_info->GetType() == SinkInfoBase::Type::CircularBufferSink) {
732 EntryAspect circular_buffer_entry(*this, PerformanceEntryType::Sink,
733 sink_info->GetNodeId());
734 auto final_mix{mix_context.GetFinalMixInfo()};
735 GenerateSinkCommand(final_mix->buffer_offset, *sink_info);
736
737 if (circular_buffer_entry.initialized) {
738 command_buffer.GeneratePerformanceCommand(
739 circular_buffer_entry.node_id, PerformanceState::Stop,
740 circular_buffer_entry.performance_entry_address);
741 }
742 }
743 }
744}
745
746void CommandGenerator::GenerateSinkCommand(const s16 buffer_offset, SinkInfoBase& sink_info) {
747 if (sink_info.ShouldSkip()) {
748 return;
749 }
750
751 switch (sink_info.GetType()) {
752 case SinkInfoBase::Type::DeviceSink:
753 GenerateDeviceSinkCommand(buffer_offset, sink_info);
754 break;
755
756 case SinkInfoBase::Type::CircularBufferSink:
757 command_buffer.GenerateCircularBufferSinkCommand(sink_info.GetNodeId(), sink_info,
758 buffer_offset);
759 break;
760
761 default:
762 LOG_ERROR(Service_Audio, "Invalid sink type {}", static_cast<u32>(sink_info.GetType()));
763 break;
764 }
765
766 sink_info.UpdateForCommandGeneration();
767}
768
769void CommandGenerator::GenerateDeviceSinkCommand(const s16 buffer_offset, SinkInfoBase& sink_info) {
770 auto& parameter{
771 *reinterpret_cast<DeviceSinkInfo::DeviceInParameter*>(sink_info.GetParameter())};
772 auto state{*reinterpret_cast<DeviceSinkInfo::DeviceState*>(sink_info.GetState())};
773
774 if (render_context.channels == 2 && parameter.downmix_enabled) {
775 command_buffer.GenerateDownMix6chTo2chCommand(InvalidNodeId, parameter.inputs,
776 buffer_offset, parameter.downmix_coeff);
777 }
778
779 if (state.upsampler_info != nullptr) {
780 command_buffer.GenerateUpsampleCommand(
781 InvalidNodeId, buffer_offset, *state.upsampler_info, parameter.input_count,
782 parameter.inputs, command_header.buffer_count, command_header.sample_count,
783 command_header.sample_rate);
784 }
785
786 command_buffer.GenerateDeviceSinkCommand(InvalidNodeId, buffer_offset, sink_info,
787 render_context.session_id,
788 command_header.samples_buffer);
789}
790
791void CommandGenerator::GeneratePerformanceCommand(
792 s32 node_id, PerformanceState state, const PerformanceEntryAddresses& entry_addresses) {
793 command_buffer.GeneratePerformanceCommand(node_id, state, entry_addresses);
794}
795
796} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/command_generator.h b/src/audio_core/renderer/command/command_generator.h
new file mode 100644
index 000000000..d80d9b0d8
--- /dev/null
+++ b/src/audio_core/renderer/command/command_generator.h
@@ -0,0 +1,349 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <span>
7
8#include "audio_core/renderer/command/commands.h"
9#include "audio_core/renderer/performance/performance_manager.h"
10#include "common/common_types.h"
11
12namespace AudioCore {
13struct AudioRendererSystemContext;
14
15namespace AudioRenderer {
16class CommandBuffer;
17struct CommandListHeader;
18class VoiceContext;
19class MixContext;
20class EffectContext;
21class SplitterContext;
22class SinkContext;
23class BehaviorInfo;
24class VoiceInfo;
25struct VoiceState;
26class MixInfo;
27class SinkInfoBase;
28
29/**
30 * Generates all commands to build up a command list, which are sent to the AudioRender for
31 * processing.
32 */
33class CommandGenerator {
34public:
35 explicit CommandGenerator(CommandBuffer& command_buffer,
36 const CommandListHeader& command_list_header,
37 const AudioRendererSystemContext& render_context,
38 VoiceContext& voice_context, MixContext& mix_context,
39 EffectContext& effect_context, SinkContext& sink_context,
40 SplitterContext& splitter_context,
41 PerformanceManager* performance_manager);
42
43 /**
44 * Calculate the buffer size needed for commands.
45 *
46 * @param behavior - Used to check what features are enabled.
47 * @param params - Input rendering parameters for numbers of voices/mixes/sinks etc.
48 */
49 static u64 CalculateCommandBufferSize(const BehaviorInfo& behavior,
50 const AudioRendererParameterInternal& params) {
51 u64 size{0};
52
53 // Effects
54 size += params.effects * sizeof(EffectInfoBase);
55
56 // Voices
57 u64 voice_size{0};
58 if (behavior.IsWaveBufferVer2Supported()) {
59 voice_size = std::max(std::max(sizeof(AdpcmDataSourceVersion2Command),
60 sizeof(PcmInt16DataSourceVersion2Command)),
61 sizeof(PcmFloatDataSourceVersion2Command));
62 } else {
63 voice_size = std::max(std::max(sizeof(AdpcmDataSourceVersion1Command),
64 sizeof(PcmInt16DataSourceVersion1Command)),
65 sizeof(PcmFloatDataSourceVersion1Command));
66 }
67 voice_size += sizeof(BiquadFilterCommand) * MaxBiquadFilters;
68 voice_size += sizeof(VolumeRampCommand);
69 voice_size += sizeof(MixRampGroupedCommand);
70
71 size += params.voices * (params.splitter_infos * sizeof(DepopPrepareCommand) + voice_size);
72
73 // Sub mixes
74 size += sizeof(DepopForMixBuffersCommand) +
75 (sizeof(MixCommand) * MaxMixBuffers) * MaxMixBuffers;
76
77 // Final mix
78 size += sizeof(DepopForMixBuffersCommand) + sizeof(VolumeCommand) * MaxMixBuffers;
79
80 // Splitters
81 size += params.splitter_destinations * sizeof(MixRampCommand) * MaxMixBuffers;
82
83 // Sinks
84 size +=
85 params.sinks * std::max(sizeof(DeviceSinkCommand), sizeof(CircularBufferSinkCommand));
86
87 // Performance
88 size += (params.effects + params.voices + params.sinks + params.sub_mixes + 1 +
89 PerformanceManager::MaxDetailEntries) *
90 sizeof(PerformanceCommand);
91 return size;
92 }
93
94 /**
95 * Get the current command buffer used to generate commands.
96 *
97 * @return The command buffer.
98 */
99 CommandBuffer& GetCommandBuffer() {
100 return command_buffer;
101 }
102
103 /**
104 * Get the current performance manager,
105 *
106 * @return The performance manager. May be nullptr.
107 */
108 PerformanceManager* GetPerformanceManager() {
109 return performance_manager;
110 }
111
112 /**
113 * Generate a data source command.
114 * These are the basis for all audio output.
115 *
116 * @param voice_info - Generate the command from this voice.
117 * @param voice_state - State used by the AudioRenderer across calls.
118 * @param channel - Channel index to generate the command into.
119 */
120 void GenerateDataSourceCommand(VoiceInfo& voice_info, const VoiceState& voice_state,
121 s8 channel);
122
123 /**
124 * Generate voice mixing commands.
125 * These are used to mix buffers together, to mix one input to many outputs,
126 * and also used as copy commands to move data around and prevent it being accidentally
127 * overwritten, e.g by another data source command into the same channel.
128 *
129 * @param mix_volumes - Current volumes of the mix.
130 * @param prev_mix_volumes - Previous volumes of the mix.
131 * @param voice_state - State used by the AudioRenderer across calls.
132 * @param output_index - Output mix buffer index.
133 * @param buffer_count - Number of active mix buffers.
134 * @param input_index - Input mix buffer index.
135 * @param node_id - Node id of the voice this command is generated for.
136 */
137 void GenerateVoiceMixCommand(std::span<const f32> mix_volumes,
138 std::span<const f32> prev_mix_volumes,
139 const VoiceState& voice_state, s16 output_index, s16 buffer_count,
140 s16 input_index, s32 node_id);
141
142 /**
143 * Generate a biquad filter command for a voice.
144 *
145 * @param voice_info - Voice info this command is generated from.
146 * @param voice_state - State used by the AudioRenderer across calls.
147 * @param buffer_count - Number of active mix buffers.
148 * @param channel - Channel index of this command.
149 * @param node_id - Node id of the voice this command is generated for.
150 */
151 void GenerateBiquadFilterCommandForVoice(VoiceInfo& voice_info, const VoiceState& voice_state,
152 s16 buffer_count, s8 channel, s32 node_id);
153
154 /**
155 * Generate commands for a voice.
156 * Includes a data source, biquad filter, volume and mixing.
157 *
158 * @param voice_info - Voice info these commands are generated from.
159 */
160 void GenerateVoiceCommand(VoiceInfo& voice_info);
161
162 /**
163 * Generate commands for all voices.
164 */
165 void GenerateVoiceCommands();
166
167 /**
168 * Generate a mixing command.
169 *
170 * @param buffer_offset - Base mix buffer offset to use.
171 * @param effect_info_base - BufferMixer effect info.
172 * @param node_id - Node id of the mix this command is generated for.
173 */
174 void GenerateBufferMixerCommand(s16 buffer_offset, EffectInfoBase& effect_info_base,
175 s32 node_id);
176
177 /**
178 * Generate a delay effect command.
179 *
180 * @param buffer_offset - Base mix buffer offset to use.
181 * @param effect_info_base - Delay effect info.
182 * @param node_id - Node id of the mix this command is generated for.
183 */
184 void GenerateDelayCommand(s16 buffer_offset, EffectInfoBase& effect_info_base, s32 node_id);
185
186 /**
187 * Generate a reverb effect command.
188 *
189 * @param buffer_offset - Base mix buffer offset to use.
190 * @param effect_info_base - Reverb effect info.
191 * @param node_id - Node id of the mix this command is generated for.
192 * @param long_size_pre_delay_supported - Use a longer pre-delay time before reverb starts.
193 */
194 void GenerateReverbCommand(s16 buffer_offset, EffectInfoBase& effect_info_base, s32 node_id,
195 bool long_size_pre_delay_supported);
196
197 /**
198 * Generate an I3DL2 reverb effect command.
199 *
200 * @param buffer_offset - Base mix buffer offset to use.
201 * @param effect_info_base - I3DL2Reverb effect info.
202 * @param node_id - Node id of the mix this command is generated for.
203 */
204 void GenerateI3dl2ReverbEffectCommand(s16 buffer_offset, EffectInfoBase& effect_info,
205 s32 node_id);
206
207 /**
208 * Generate an aux effect command.
209 *
210 * @param buffer_offset - Base mix buffer offset to use.
211 * @param effect_info_base - Aux effect info.
212 * @param node_id - Node id of the mix this command is generated for.
213 */
214 void GenerateAuxCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id);
215
216 /**
217 * Generate a biquad filter effect command.
218 *
219 * @param buffer_offset - Base mix buffer offset to use.
220 * @param effect_info_base - Aux effect info.
221 * @param node_id - Node id of the mix this command is generated for.
222 */
223 void GenerateBiquadFilterEffectCommand(s16 buffer_offset, EffectInfoBase& effect_info,
224 s32 node_id);
225
226 /**
227 * Generate a light limiter effect command.
228 *
229 * @param buffer_offset - Base mix buffer offset to use.
230 * @param effect_info_base - Limiter effect info.
231 * @param node_id - Node id of the mix this command is generated for.
232 * @param effect_index - Index for the statistics state.
233 */
234 void GenerateLightLimiterEffectCommand(s16 buffer_offset, EffectInfoBase& effect_info,
235 s32 node_id, u32 effect_index);
236
237 /**
238 * Generate a capture effect command.
239 * Writes a mix buffer back to game memory.
240 *
241 * @param buffer_offset - Base mix buffer offset to use.
242 * @param effect_info_base - Capture effect info.
243 * @param node_id - Node id of the mix this command is generated for.
244 */
245 void GenerateCaptureCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id);
246
247 /**
248 * Generate a compressor effect command.
249 *
250 * @param buffer_offset - Base mix buffer offset to use.
251 * @param effect_info_base - Compressor effect info.
252 * @param node_id - Node id of the mix this command is generated for.
253 */
254 void GenerateCompressorCommand(const s16 buffer_offset, EffectInfoBase& effect_info,
255 const s32 node_id);
256
257 /**
258 * Generate all effect commands for a mix.
259 *
260 * @param mix_info - Mix to generate effects from.
261 */
262 void GenerateEffectCommand(MixInfo& mix_info);
263
264 /**
265 * Generate all mix commands.
266 *
267 * @param mix_info - Mix to generate effects from.
268 */
269 void GenerateMixCommands(MixInfo& mix_info);
270
271 /**
272 * Generate a submix command.
273 * Generates all effects and all mixing commands.
274 *
275 * @param mix_info - Mix to generate effects from.
276 */
277 void GenerateSubMixCommand(MixInfo& mix_info);
278
279 /**
280 * Generate all submix command.
281 */
282 void GenerateSubMixCommands();
283
284 /**
285 * Generate the final mix.
286 */
287 void GenerateFinalMixCommand();
288
289 /**
290 * Generate the final mix commands.
291 */
292 void GenerateFinalMixCommands();
293
294 /**
295 * Generate all sink commands.
296 */
297 void GenerateSinkCommands();
298
299 /**
300 * Generate a sink command.
301 * Sends samples out to the backend, or a game-supplied circular buffer.
302 *
303 * @param buffer_offset - Base mix buffer offset to use.
304 * @param sink_info - Sink info to generate the commands from.
305 */
306 void GenerateSinkCommand(s16 buffer_offset, SinkInfoBase& sink_info);
307
308 /**
309 * Generate a device sink command.
310 * Sends samples out to the backend.
311 *
312 * @param buffer_offset - Base mix buffer offset to use.
313 * @param sink_info - Sink info to generate the commands from.
314 */
315 void GenerateDeviceSinkCommand(s16 buffer_offset, SinkInfoBase& sink_info);
316
317 /**
318 * Generate a performance command.
319 * Used to report performance metrics of the AudioRenderer back to the game.
320 *
321 * @param buffer_offset - Base mix buffer offset to use.
322 * @param sink_info - Sink info to generate the commands from.
323 */
324 void GeneratePerformanceCommand(s32 node_id, PerformanceState state,
325 const PerformanceEntryAddresses& entry_addresses);
326
327private:
328 /// Commands will be written by this buffer
329 CommandBuffer& command_buffer;
330 /// Header information for the commands generated
331 const CommandListHeader& command_header;
332 /// Various things to control generation
333 const AudioRendererSystemContext& render_context;
334 /// Used for generating voices
335 VoiceContext& voice_context;
336 /// Used for generating mixes
337 MixContext& mix_context;
338 /// Used for generating effects
339 EffectContext& effect_context;
340 /// Used for generating sinks
341 SinkContext& sink_context;
342 /// Used for generating submixes
343 SplitterContext& splitter_context;
344 /// Used for generating performance
345 PerformanceManager* performance_manager;
346};
347
348} // namespace AudioRenderer
349} // namespace AudioCore
diff --git a/src/audio_core/renderer/command/command_list_header.h b/src/audio_core/renderer/command/command_list_header.h
new file mode 100644
index 000000000..988530b1f
--- /dev/null
+++ b/src/audio_core/renderer/command/command_list_header.h
@@ -0,0 +1,22 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <span>
7
8#include "audio_core/common/common.h"
9#include "common/common_types.h"
10
11namespace AudioCore::AudioRenderer {
12
13struct CommandListHeader {
14 u64 buffer_size;
15 u32 command_count;
16 std::span<s32> samples_buffer;
17 s16 buffer_count;
18 u32 sample_count;
19 u32 sample_rate;
20};
21
22} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/command_processing_time_estimator.cpp b/src/audio_core/renderer/command/command_processing_time_estimator.cpp
new file mode 100644
index 000000000..3091f587a
--- /dev/null
+++ b/src/audio_core/renderer/command/command_processing_time_estimator.cpp
@@ -0,0 +1,3620 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/command/command_processing_time_estimator.h"
5
6namespace AudioCore::AudioRenderer {
7
8u32 CommandProcessingTimeEstimatorVersion1::Estimate(
9 const PcmInt16DataSourceVersion1Command& command) const {
10 return static_cast<u32>(command.pitch * 0.25f * 1.2f);
11}
12
13u32 CommandProcessingTimeEstimatorVersion1::Estimate(
14 const PcmInt16DataSourceVersion2Command& command) const {
15 return static_cast<u32>(command.pitch * 0.25f * 1.2f);
16}
17
18u32 CommandProcessingTimeEstimatorVersion1::Estimate(
19 [[maybe_unused]] const PcmFloatDataSourceVersion1Command& command) const {
20 return 0;
21}
22
23u32 CommandProcessingTimeEstimatorVersion1::Estimate(
24 [[maybe_unused]] const PcmFloatDataSourceVersion2Command& command) const {
25 return 0;
26}
27
28u32 CommandProcessingTimeEstimatorVersion1::Estimate(
29 const AdpcmDataSourceVersion1Command& command) const {
30 return static_cast<u32>(command.pitch * 0.25f * 1.2f);
31}
32
33u32 CommandProcessingTimeEstimatorVersion1::Estimate(
34 const AdpcmDataSourceVersion2Command& command) const {
35 return static_cast<u32>(command.pitch * 0.25f * 1.2f);
36}
37
38u32 CommandProcessingTimeEstimatorVersion1::Estimate(
39 [[maybe_unused]] const VolumeCommand& command) const {
40 return static_cast<u32>((static_cast<f32>(sample_count) * 8.8f) * 1.2f);
41}
42
43u32 CommandProcessingTimeEstimatorVersion1::Estimate(
44 [[maybe_unused]] const VolumeRampCommand& command) const {
45 return static_cast<u32>((static_cast<f32>(sample_count) * 9.8f) * 1.2f);
46}
47
48u32 CommandProcessingTimeEstimatorVersion1::Estimate(
49 [[maybe_unused]] const BiquadFilterCommand& command) const {
50 return static_cast<u32>((static_cast<f32>(sample_count) * 58.0f) * 1.2f);
51}
52
53u32 CommandProcessingTimeEstimatorVersion1::Estimate(
54 [[maybe_unused]] const MixCommand& command) const {
55 return static_cast<u32>((static_cast<f32>(sample_count) * 10.0f) * 1.2f);
56}
57
58u32 CommandProcessingTimeEstimatorVersion1::Estimate(
59 [[maybe_unused]] const MixRampCommand& command) const {
60 return static_cast<u32>((static_cast<f32>(sample_count) * 14.4f) * 1.2f);
61}
62
63u32 CommandProcessingTimeEstimatorVersion1::Estimate(const MixRampGroupedCommand& command) const {
64 u32 count{0};
65 for (u32 i = 0; i < command.buffer_count; i++) {
66 if (command.volumes[i] != 0.0f || command.prev_volumes[i] != 0.0f) {
67 count++;
68 }
69 }
70
71 return static_cast<u32>(((static_cast<f32>(sample_count) * 14.4f) * 1.2f) *
72 static_cast<f32>(count));
73}
74
75u32 CommandProcessingTimeEstimatorVersion1::Estimate(
76 [[maybe_unused]] const DepopPrepareCommand& command) const {
77 return 1080;
78}
79
80u32 CommandProcessingTimeEstimatorVersion1::Estimate(
81 const DepopForMixBuffersCommand& command) const {
82 return static_cast<u32>((static_cast<f32>(sample_count) * 8.9f) *
83 static_cast<f32>(command.count));
84}
85
86u32 CommandProcessingTimeEstimatorVersion1::Estimate(const DelayCommand& command) const {
87 return static_cast<u32>((static_cast<f32>(sample_count) * command.parameter.channel_count) *
88 202.5f);
89}
90
91u32 CommandProcessingTimeEstimatorVersion1::Estimate(
92 [[maybe_unused]] const UpsampleCommand& command) const {
93 return 357915;
94}
95
96u32 CommandProcessingTimeEstimatorVersion1::Estimate(
97 [[maybe_unused]] const DownMix6chTo2chCommand& command) const {
98 return 16108;
99}
100
101u32 CommandProcessingTimeEstimatorVersion1::Estimate(const AuxCommand& command) const {
102 if (command.enabled) {
103 return 15956;
104 }
105 return 3765;
106}
107
108u32 CommandProcessingTimeEstimatorVersion1::Estimate(
109 [[maybe_unused]] const DeviceSinkCommand& command) const {
110 return 10042;
111}
112
113u32 CommandProcessingTimeEstimatorVersion1::Estimate(
114 [[maybe_unused]] const CircularBufferSinkCommand& command) const {
115 return 55;
116}
117
118u32 CommandProcessingTimeEstimatorVersion1::Estimate(const ReverbCommand& command) const {
119 if (command.enabled) {
120 return static_cast<u32>(
121 (command.parameter.channel_count * static_cast<f32>(sample_count) * 750) * 1.2f);
122 }
123 return 0;
124}
125
126u32 CommandProcessingTimeEstimatorVersion1::Estimate(const I3dl2ReverbCommand& command) const {
127 if (command.enabled) {
128 return static_cast<u32>(
129 (command.parameter.channel_count * static_cast<f32>(sample_count) * 530) * 1.2f);
130 }
131 return 0;
132}
133
134u32 CommandProcessingTimeEstimatorVersion1::Estimate(
135 [[maybe_unused]] const PerformanceCommand& command) const {
136 return 1454;
137}
138
139u32 CommandProcessingTimeEstimatorVersion1::Estimate(
140 [[maybe_unused]] const ClearMixBufferCommand& command) const {
141 return static_cast<u32>(
142 ((static_cast<f32>(sample_count) * 0.83f) * static_cast<f32>(buffer_count)) * 1.2f);
143}
144
145u32 CommandProcessingTimeEstimatorVersion1::Estimate(
146 [[maybe_unused]] const CopyMixBufferCommand& command) const {
147 return 0;
148}
149
150u32 CommandProcessingTimeEstimatorVersion1::Estimate(
151 [[maybe_unused]] const LightLimiterVersion1Command& command) const {
152 return 0;
153}
154
155u32 CommandProcessingTimeEstimatorVersion1::Estimate(
156 [[maybe_unused]] const LightLimiterVersion2Command& command) const {
157 return 0;
158}
159
160u32 CommandProcessingTimeEstimatorVersion1::Estimate(
161 [[maybe_unused]] const MultiTapBiquadFilterCommand& command) const {
162 return 0;
163}
164
165u32 CommandProcessingTimeEstimatorVersion1::Estimate(
166 [[maybe_unused]] const CaptureCommand& command) const {
167 return 0;
168}
169
170u32 CommandProcessingTimeEstimatorVersion1::Estimate(
171 [[maybe_unused]] const CompressorCommand& command) const {
172 return 0;
173}
174
175u32 CommandProcessingTimeEstimatorVersion2::Estimate(
176 const PcmInt16DataSourceVersion1Command& command) const {
177 switch (sample_count) {
178 case 160:
179 return static_cast<u32>(
180 (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
181 (command.pitch * 2.0f) * 749.269f +
182 6138.94f);
183 case 240:
184 return static_cast<u32>(
185 (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
186 (command.pitch * 2.0f) * 1195.456f +
187 7797.047f);
188 default:
189 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
190 return 0;
191 }
192}
193
194u32 CommandProcessingTimeEstimatorVersion2::Estimate(
195 const PcmInt16DataSourceVersion2Command& command) const {
196 switch (sample_count) {
197 case 160:
198 return static_cast<u32>(
199 (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
200 (command.pitch * 2.0f) * 749.269f +
201 6138.94f);
202 case 240:
203 return static_cast<u32>(
204 (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
205 (command.pitch * 2.0f) * 1195.456f +
206 7797.047f);
207 default:
208 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
209 return 0;
210 }
211}
212
213u32 CommandProcessingTimeEstimatorVersion2::Estimate(
214 const PcmFloatDataSourceVersion1Command& command) const {
215 switch (sample_count) {
216 case 160:
217 return static_cast<u32>(
218 (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
219 (command.pitch * 2.0f) * 749.269f +
220 6138.94f);
221 case 240:
222 return static_cast<u32>(
223 (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
224 (command.pitch * 2.0f) * 1195.456f +
225 7797.047f);
226 default:
227 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
228 return 0;
229 }
230}
231
232u32 CommandProcessingTimeEstimatorVersion2::Estimate(
233 const PcmFloatDataSourceVersion2Command& command) const {
234 switch (sample_count) {
235 case 160:
236 return static_cast<u32>(
237 (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
238 (command.pitch * 2.0f) * 749.269f +
239 6138.94f);
240 case 240:
241 return static_cast<u32>(
242 (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
243 (command.pitch * 2.0f) * 1195.456f +
244 7797.047f);
245 default:
246 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
247 return 0;
248 }
249}
250
251u32 CommandProcessingTimeEstimatorVersion2::Estimate(
252 const AdpcmDataSourceVersion1Command& command) const {
253 switch (sample_count) {
254 case 160:
255 return static_cast<u32>(
256 (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
257 (command.pitch * 2.0f) * 2125.588f +
258 9039.47f);
259 case 240:
260 return static_cast<u32>(
261 (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
262 (command.pitch * 2.0f) * 3564.088 +
263 6225.471);
264 default:
265 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
266 return 0;
267 }
268}
269
270u32 CommandProcessingTimeEstimatorVersion2::Estimate(
271 const AdpcmDataSourceVersion2Command& command) const {
272 switch (sample_count) {
273 case 160:
274 return static_cast<u32>(
275 (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
276 (command.pitch * 2.0f) * 2125.588f +
277 9039.47f);
278 case 240:
279 return static_cast<u32>(
280 (static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
281 (command.pitch * 2.0f) * 3564.088 +
282 6225.471);
283 default:
284 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
285 return 0;
286 }
287}
288
289u32 CommandProcessingTimeEstimatorVersion2::Estimate(
290 [[maybe_unused]] const VolumeCommand& command) const {
291 switch (sample_count) {
292 case 160:
293 return static_cast<u32>(1280.3f);
294 case 240:
295 return static_cast<u32>(1737.8f);
296 default:
297 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
298 return 0;
299 }
300}
301
302u32 CommandProcessingTimeEstimatorVersion2::Estimate(
303 [[maybe_unused]] const VolumeRampCommand& command) const {
304 switch (sample_count) {
305 case 160:
306 return static_cast<u32>(1403.9f);
307 case 240:
308 return static_cast<u32>(1884.3f);
309 default:
310 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
311 return 0;
312 }
313}
314
315u32 CommandProcessingTimeEstimatorVersion2::Estimate(
316 [[maybe_unused]] const BiquadFilterCommand& command) const {
317 switch (sample_count) {
318 case 160:
319 return static_cast<u32>(4813.2f);
320 case 240:
321 return static_cast<u32>(6915.4f);
322 default:
323 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
324 return 0;
325 }
326}
327
328u32 CommandProcessingTimeEstimatorVersion2::Estimate(
329 [[maybe_unused]] const MixCommand& command) const {
330 switch (sample_count) {
331 case 160:
332 return static_cast<u32>(1342.2f);
333 case 240:
334 return static_cast<u32>(1833.2f);
335 default:
336 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
337 return 0;
338 }
339}
340
341u32 CommandProcessingTimeEstimatorVersion2::Estimate(
342 [[maybe_unused]] const MixRampCommand& command) const {
343 switch (sample_count) {
344 case 160:
345 return static_cast<u32>(1859.0f);
346 case 240:
347 return static_cast<u32>(2286.1f);
348 default:
349 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
350 return 0;
351 }
352}
353
354u32 CommandProcessingTimeEstimatorVersion2::Estimate(const MixRampGroupedCommand& command) const {
355 u32 count{0};
356 for (u32 i = 0; i < command.buffer_count; i++) {
357 if (command.volumes[i] != 0.0f || command.prev_volumes[i] != 0.0f) {
358 count++;
359 }
360 }
361
362 switch (sample_count) {
363 case 160:
364 return static_cast<u32>((static_cast<f32>(sample_count) * 7.245f) *
365 static_cast<f32>(count));
366 case 240:
367 return static_cast<u32>((static_cast<f32>(sample_count) * 7.245f) *
368 static_cast<f32>(count));
369 default:
370 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
371 return 0;
372 }
373}
374
375u32 CommandProcessingTimeEstimatorVersion2::Estimate(
376 [[maybe_unused]] const DepopPrepareCommand& command) const {
377 switch (sample_count) {
378 case 160:
379 return static_cast<u32>(306.62f);
380 case 240:
381 return static_cast<u32>(293.22f);
382 default:
383 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
384 return 0;
385 }
386}
387
388u32 CommandProcessingTimeEstimatorVersion2::Estimate(
389 [[maybe_unused]] const DepopForMixBuffersCommand& command) const {
390 switch (sample_count) {
391 case 160:
392 return static_cast<u32>(762.96f);
393 case 240:
394 return static_cast<u32>(726.96f);
395 default:
396 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
397 return 0;
398 }
399}
400
401u32 CommandProcessingTimeEstimatorVersion2::Estimate(const DelayCommand& command) const {
402 switch (sample_count) {
403 case 160:
404 if (command.enabled) {
405 switch (command.parameter.channel_count) {
406 case 1:
407 return static_cast<u32>(41635.555f);
408 case 2:
409 return static_cast<u32>(97861.211f);
410 case 4:
411 return static_cast<u32>(192515.516f);
412 case 6:
413 return static_cast<u32>(301755.969f);
414 default:
415 LOG_ERROR(Service_Audio, "Invalid channel count {}",
416 command.parameter.channel_count);
417 return 0;
418 }
419 }
420 switch (command.parameter.channel_count) {
421 case 1:
422 return static_cast<u32>(578.529f);
423 case 2:
424 return static_cast<u32>(663.064f);
425 case 4:
426 return static_cast<u32>(703.983f);
427 case 6:
428 return static_cast<u32>(760.032f);
429 default:
430 LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
431 return 0;
432 }
433 case 240:
434 if (command.enabled) {
435 switch (command.parameter.channel_count) {
436 case 1:
437 return static_cast<u32>(8770.345f);
438 case 2:
439 return static_cast<u32>(25741.18f);
440 case 4:
441 return static_cast<u32>(47551.168f);
442 case 6:
443 return static_cast<u32>(81629.219f);
444 default:
445 LOG_ERROR(Service_Audio, "Invalid channel count {}",
446 command.parameter.channel_count);
447 return 0;
448 }
449 }
450 switch (command.parameter.channel_count) {
451 case 1:
452 return static_cast<u32>(521.283f);
453 case 2:
454 return static_cast<u32>(585.396f);
455 case 4:
456 return static_cast<u32>(629.884f);
457 case 6:
458 return static_cast<u32>(713.57f);
459 default:
460 LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
461 return 0;
462 }
463 default:
464 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
465 return 0;
466 }
467}
468
469u32 CommandProcessingTimeEstimatorVersion2::Estimate(
470 [[maybe_unused]] const UpsampleCommand& command) const {
471 switch (sample_count) {
472 case 160:
473 return static_cast<u32>(292000.0f);
474 case 240:
475 return static_cast<u32>(0.0f);
476 default:
477 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
478 return 0;
479 }
480}
481
482u32 CommandProcessingTimeEstimatorVersion2::Estimate(
483 [[maybe_unused]] const DownMix6chTo2chCommand& command) const {
484 switch (sample_count) {
485 case 160:
486 return static_cast<u32>(10009.0f);
487 case 240:
488 return static_cast<u32>(14577.0f);
489 default:
490 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
491 return 0;
492 }
493}
494
495u32 CommandProcessingTimeEstimatorVersion2::Estimate(const AuxCommand& command) const {
496 // Is this function bugged, returning the wrong time?
497 // Surely the larger time should be returned when enabled...
498 // CMP W8, #0
499 // MOV W8, #0x60; // 489.163f
500 // MOV W10, #0x64; // 7177.936f
501 // CSEL X8, X10, X8, EQ
502
503 switch (sample_count) {
504 case 160:
505 if (command.enabled) {
506 return static_cast<u32>(489.163f);
507 }
508 return static_cast<u32>(7177.936f);
509 case 240:
510 if (command.enabled) {
511 return static_cast<u32>(485.562f);
512 }
513 return static_cast<u32>(9499.822f);
514 default:
515 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
516 return 0;
517 }
518}
519
520u32 CommandProcessingTimeEstimatorVersion2::Estimate(const DeviceSinkCommand& command) const {
521 switch (command.input_count) {
522 case 2:
523 switch (sample_count) {
524 case 160:
525 return static_cast<u32>(9261.545f);
526 case 240:
527 return static_cast<u32>(9336.054f);
528 default:
529 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
530 return 0;
531 }
532 case 6:
533 switch (sample_count) {
534 case 160:
535 return static_cast<u32>(9336.054f);
536 case 240:
537 return static_cast<u32>(9566.728f);
538 default:
539 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
540 return 0;
541 }
542 default:
543 LOG_ERROR(Service_Audio, "Invalid input count {}", command.input_count);
544 return 0;
545 }
546}
547
548u32 CommandProcessingTimeEstimatorVersion2::Estimate(
549 const CircularBufferSinkCommand& command) const {
550 switch (sample_count) {
551 case 160:
552 return static_cast<u32>(static_cast<f32>(command.input_count) * 853.629f + 1284.517f);
553 case 240:
554 return static_cast<u32>(static_cast<f32>(command.input_count) * 1726.021f + 1369.683f);
555 default:
556 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
557 return 0;
558 }
559}
560
561u32 CommandProcessingTimeEstimatorVersion2::Estimate(const ReverbCommand& command) const {
562 switch (sample_count) {
563 case 160:
564 if (command.enabled) {
565 switch (command.parameter.channel_count) {
566 case 1:
567 return static_cast<u32>(97192.227f);
568 case 2:
569 return static_cast<u32>(103278.555f);
570 case 4:
571 return static_cast<u32>(109579.039f);
572 case 6:
573 return static_cast<u32>(115065.438f);
574 default:
575 LOG_ERROR(Service_Audio, "Invalid channel count {}",
576 command.parameter.channel_count);
577 return 0;
578 }
579 }
580 switch (command.parameter.channel_count) {
581 case 1:
582 return static_cast<u32>(492.009f);
583 case 2:
584 return static_cast<u32>(554.463f);
585 case 4:
586 return static_cast<u32>(595.864f);
587 case 6:
588 return static_cast<u32>(656.617f);
589 default:
590 LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
591 return 0;
592 }
593 case 240:
594 if (command.enabled) {
595 switch (command.parameter.channel_count) {
596 case 1:
597 return static_cast<u32>(136463.641f);
598 case 2:
599 return static_cast<u32>(145749.047f);
600 case 4:
601 return static_cast<u32>(154796.938f);
602 case 6:
603 return static_cast<u32>(161968.406f);
604 default:
605 LOG_ERROR(Service_Audio, "Invalid channel count {}",
606 command.parameter.channel_count);
607 return 0;
608 }
609 }
610 switch (command.parameter.channel_count) {
611 case 1:
612 return static_cast<u32>(495.789f);
613 case 2:
614 return static_cast<u32>(527.163f);
615 case 4:
616 return static_cast<u32>(598.752f);
617 case 6:
618 return static_cast<u32>(666.025f);
619 default:
620 LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
621 return 0;
622 }
623 default:
624 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
625 return 0;
626 }
627}
628
629u32 CommandProcessingTimeEstimatorVersion2::Estimate(const I3dl2ReverbCommand& command) const {
630 switch (sample_count) {
631 case 160:
632 if (command.enabled) {
633 switch (command.parameter.channel_count) {
634 case 1:
635 return static_cast<u32>(138836.484f);
636 case 2:
637 return static_cast<u32>(135428.172f);
638 case 4:
639 return static_cast<u32>(199181.844f);
640 case 6:
641 return static_cast<u32>(247345.906f);
642 default:
643 LOG_ERROR(Service_Audio, "Invalid channel count {}",
644 command.parameter.channel_count);
645 return 0;
646 }
647 }
648 switch (command.parameter.channel_count) {
649 case 1:
650 return static_cast<u32>(718.704f);
651 case 2:
652 return static_cast<u32>(751.296f);
653 case 4:
654 return static_cast<u32>(797.464f);
655 case 6:
656 return static_cast<u32>(867.426f);
657 default:
658 LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
659 return 0;
660 }
661 case 240:
662 if (command.enabled) {
663 switch (command.parameter.channel_count) {
664 case 1:
665 return static_cast<u32>(199952.734f);
666 case 2:
667 return static_cast<u32>(195199.5f);
668 case 4:
669 return static_cast<u32>(290575.875f);
670 case 6:
671 return static_cast<u32>(363494.531f);
672 default:
673 LOG_ERROR(Service_Audio, "Invalid channel count {}",
674 command.parameter.channel_count);
675 return 0;
676 }
677 }
678 switch (command.parameter.channel_count) {
679 case 1:
680 return static_cast<u32>(534.24f);
681 case 2:
682 return static_cast<u32>(570.874f);
683 case 4:
684 return static_cast<u32>(660.933f);
685 case 6:
686 return static_cast<u32>(694.596f);
687 default:
688 LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
689 return 0;
690 }
691 default:
692 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
693 return 0;
694 }
695}
696
697u32 CommandProcessingTimeEstimatorVersion2::Estimate(
698 [[maybe_unused]] const PerformanceCommand& command) const {
699 switch (sample_count) {
700 case 160:
701 return static_cast<u32>(489.35f);
702 case 240:
703 return static_cast<u32>(491.18f);
704 default:
705 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
706 return 0;
707 }
708}
709
710u32 CommandProcessingTimeEstimatorVersion2::Estimate(
711 [[maybe_unused]] const ClearMixBufferCommand& command) const {
712 switch (sample_count) {
713 case 160:
714 return static_cast<u32>(static_cast<f32>(buffer_count) * 260.4f + 139.65f);
715 case 240:
716 return static_cast<u32>(static_cast<f32>(buffer_count) * 668.85f + 193.2f);
717 default:
718 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
719 return 0;
720 }
721}
722
723u32 CommandProcessingTimeEstimatorVersion2::Estimate(
724 [[maybe_unused]] const CopyMixBufferCommand& command) const {
725 switch (sample_count) {
726 case 160:
727 return static_cast<u32>(836.32f);
728 case 240:
729 return static_cast<u32>(1000.9f);
730 default:
731 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
732 return 0;
733 }
734}
735
736u32 CommandProcessingTimeEstimatorVersion2::Estimate(
737 [[maybe_unused]] const LightLimiterVersion1Command& command) const {
738 return 0;
739}
740
741u32 CommandProcessingTimeEstimatorVersion2::Estimate(
742 [[maybe_unused]] const LightLimiterVersion2Command& command) const {
743 return 0;
744}
745
746u32 CommandProcessingTimeEstimatorVersion2::Estimate(
747 [[maybe_unused]] const MultiTapBiquadFilterCommand& command) const {
748 return 0;
749}
750
751u32 CommandProcessingTimeEstimatorVersion2::Estimate(
752 [[maybe_unused]] const CaptureCommand& command) const {
753 return 0;
754}
755
756u32 CommandProcessingTimeEstimatorVersion2::Estimate(
757 [[maybe_unused]] const CompressorCommand& command) const {
758 return 0;
759}
760
761u32 CommandProcessingTimeEstimatorVersion3::Estimate(
762 const PcmInt16DataSourceVersion1Command& command) const {
763 switch (sample_count) {
764 case 160:
765 return static_cast<u32>(
766 ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
767 (command.pitch * 0.000030518f)) *
768 427.52f +
769 6329.442f);
770 case 240:
771 return static_cast<u32>(
772 ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
773 (command.pitch * 0.000030518f)) *
774 710.143f +
775 7853.286f);
776 default:
777 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
778 return 0;
779 }
780}
781
782u32 CommandProcessingTimeEstimatorVersion3::Estimate(
783 const PcmInt16DataSourceVersion2Command& command) const {
784 switch (sample_count) {
785 case 160:
786 switch (command.src_quality) {
787 case SrcQuality::Medium:
788 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
789 static_cast<f32>(sample_count)) *
790 (command.pitch * 0.000030518f)) -
791 1.0f) *
792 427.52f +
793 6329.442f);
794 case SrcQuality::High:
795 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
796 static_cast<f32>(sample_count)) *
797 (command.pitch * 0.000030518f)) -
798 1.0f) *
799 371.876f +
800 8049.415f);
801 case SrcQuality::Low:
802 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
803 static_cast<f32>(sample_count)) *
804 (command.pitch * 0.000030518f)) -
805 1.0f) *
806 423.43f +
807 5062.659f);
808 default:
809 LOG_ERROR(Service_Audio, "Invalid SRC quality {}",
810 static_cast<u32>(command.src_quality));
811 return 0;
812 }
813
814 case 240:
815 switch (command.src_quality) {
816 case SrcQuality::Medium:
817 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
818 static_cast<f32>(sample_count)) *
819 (command.pitch * 0.000030518f)) -
820 1.0f) *
821 710.143f +
822 7853.286f);
823 case SrcQuality::High:
824 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
825 static_cast<f32>(sample_count)) *
826 (command.pitch * 0.000030518f)) -
827 1.0f) *
828 610.487f +
829 10138.842f);
830 case SrcQuality::Low:
831 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
832 static_cast<f32>(sample_count)) *
833 (command.pitch * 0.000030518f)) -
834 1.0f) *
835 676.722f +
836 5810.962f);
837 default:
838 LOG_ERROR(Service_Audio, "Invalid SRC quality {}",
839 static_cast<u32>(command.src_quality));
840 return 0;
841 }
842
843 default:
844 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
845 return 0;
846 }
847}
848
849u32 CommandProcessingTimeEstimatorVersion3::Estimate(
850 const PcmFloatDataSourceVersion1Command& command) const {
851 switch (sample_count) {
852 case 160:
853 return static_cast<u32>(
854 ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
855 (command.pitch * 0.000030518f)) *
856 1672.026f +
857 7681.211f);
858 case 240:
859 return static_cast<u32>(
860 ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
861 (command.pitch * 0.000030518f)) *
862 2550.414f +
863 9663.969f);
864 default:
865 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
866 return 0;
867 }
868}
869
870u32 CommandProcessingTimeEstimatorVersion3::Estimate(
871 const PcmFloatDataSourceVersion2Command& command) const {
872 switch (sample_count) {
873 case 160:
874 switch (command.src_quality) {
875 case SrcQuality::Medium:
876 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
877 static_cast<f32>(sample_count)) *
878 (command.pitch * 0.000030518f)) -
879 1.0f) *
880 1672.026f +
881 7681.211f);
882 case SrcQuality::High:
883 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
884 static_cast<f32>(sample_count)) *
885 (command.pitch * 0.000030518f)) -
886 1.0f) *
887 1672.982f +
888 9038.011f);
889 case SrcQuality::Low:
890 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
891 static_cast<f32>(sample_count)) *
892 (command.pitch * 0.000030518f)) -
893 1.0f) *
894 1673.216f +
895 6027.577f);
896 default:
897 LOG_ERROR(Service_Audio, "Invalid SRC quality {}",
898 static_cast<u32>(command.src_quality));
899 return 0;
900 }
901
902 case 240:
903 switch (command.src_quality) {
904 case SrcQuality::Medium:
905 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
906 static_cast<f32>(sample_count)) *
907 (command.pitch * 0.000030518f)) -
908 1.0f) *
909 2550.414f +
910 9663.969f);
911 case SrcQuality::High:
912 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
913 static_cast<f32>(sample_count)) *
914 (command.pitch * 0.000030518f)) -
915 1.0f) *
916 2522.303f +
917 11758.571f);
918 case SrcQuality::Low:
919 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
920 static_cast<f32>(sample_count)) *
921 (command.pitch * 0.000030518f)) -
922 1.0f) *
923 2537.061f +
924 7369.309f);
925 default:
926 LOG_ERROR(Service_Audio, "Invalid SRC quality {}",
927 static_cast<u32>(command.src_quality));
928 return 0;
929 }
930
931 default:
932 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
933 return 0;
934 }
935}
936
937u32 CommandProcessingTimeEstimatorVersion3::Estimate(
938 const AdpcmDataSourceVersion1Command& command) const {
939 switch (sample_count) {
940 case 160:
941 return static_cast<u32>(
942 ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
943 (command.pitch * 0.000030518f)) *
944 1827.665f +
945 7913.808f);
946 case 240:
947 return static_cast<u32>(
948 ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
949 (command.pitch * 0.000030518f)) *
950 2756.372f +
951 9736.702f);
952 default:
953 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
954 return 0;
955 }
956}
957
958u32 CommandProcessingTimeEstimatorVersion3::Estimate(
959 const AdpcmDataSourceVersion2Command& command) const {
960 switch (sample_count) {
961 case 160:
962 switch (command.src_quality) {
963 case SrcQuality::Medium:
964 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
965 static_cast<f32>(sample_count)) *
966 (command.pitch * 0.000030518f)) -
967 1.0f) *
968 1827.665f +
969 7913.808f);
970 case SrcQuality::High:
971 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
972 static_cast<f32>(sample_count)) *
973 (command.pitch * 0.000030518f)) -
974 1.0f) *
975 1829.285f +
976 9607.814f);
977 case SrcQuality::Low:
978 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
979 static_cast<f32>(sample_count)) *
980 (command.pitch * 0.000030518f)) -
981 1.0f) *
982 1824.609f +
983 6517.476f);
984 default:
985 LOG_ERROR(Service_Audio, "Invalid SRC quality {}",
986 static_cast<u32>(command.src_quality));
987 return 0;
988 }
989
990 case 240:
991 switch (command.src_quality) {
992 case SrcQuality::Medium:
993 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
994 static_cast<f32>(sample_count)) *
995 (command.pitch * 0.000030518f)) -
996 1.0f) *
997 2756.372f +
998 9736.702f);
999 case SrcQuality::High:
1000 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
1001 static_cast<f32>(sample_count)) *
1002 (command.pitch * 0.000030518f)) -
1003 1.0f) *
1004 2731.308f +
1005 12154.379f);
1006 case SrcQuality::Low:
1007 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
1008 static_cast<f32>(sample_count)) *
1009 (command.pitch * 0.000030518f)) -
1010 1.0f) *
1011 2732.152f +
1012 7929.442f);
1013 default:
1014 LOG_ERROR(Service_Audio, "Invalid SRC quality {}",
1015 static_cast<u32>(command.src_quality));
1016 return 0;
1017 }
1018
1019 default:
1020 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
1021 return 0;
1022 }
1023}
1024
1025u32 CommandProcessingTimeEstimatorVersion3::Estimate(
1026 [[maybe_unused]] const VolumeCommand& command) const {
1027 switch (sample_count) {
1028 case 160:
1029 return static_cast<u32>(1311.1f);
1030 case 240:
1031 return static_cast<u32>(1713.6f);
1032 default:
1033 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
1034 return 0;
1035 }
1036}
1037
1038u32 CommandProcessingTimeEstimatorVersion3::Estimate(
1039 [[maybe_unused]] const VolumeRampCommand& command) const {
1040 switch (sample_count) {
1041 case 160:
1042 return static_cast<u32>(1425.3f);
1043 case 240:
1044 return static_cast<u32>(1700.0f);
1045 default:
1046 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
1047 return 0;
1048 }
1049}
1050
1051u32 CommandProcessingTimeEstimatorVersion3::Estimate(
1052 [[maybe_unused]] const BiquadFilterCommand& command) const {
1053 switch (sample_count) {
1054 case 160:
1055 return static_cast<u32>(4173.2f);
1056 case 240:
1057 return static_cast<u32>(5585.1f);
1058 default:
1059 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
1060 return 0;
1061 }
1062}
1063
1064u32 CommandProcessingTimeEstimatorVersion3::Estimate(
1065 [[maybe_unused]] const MixCommand& command) const {
1066 switch (sample_count) {
1067 case 160:
1068 return static_cast<u32>(1402.8f);
1069 case 240:
1070 return static_cast<u32>(1853.2f);
1071 default:
1072 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
1073 return 0;
1074 }
1075}
1076
1077u32 CommandProcessingTimeEstimatorVersion3::Estimate(
1078 [[maybe_unused]] const MixRampCommand& command) const {
1079 switch (sample_count) {
1080 case 160:
1081 return static_cast<u32>(1968.7f);
1082 case 240:
1083 return static_cast<u32>(2459.4f);
1084 default:
1085 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
1086 return 0;
1087 }
1088}
1089
1090u32 CommandProcessingTimeEstimatorVersion3::Estimate(const MixRampGroupedCommand& command) const {
1091 u32 count{0};
1092 for (u32 i = 0; i < command.buffer_count; i++) {
1093 if (command.volumes[i] != 0.0f || command.prev_volumes[i] != 0.0f) {
1094 count++;
1095 }
1096 }
1097
1098 switch (sample_count) {
1099 case 160:
1100 return static_cast<u32>((static_cast<f32>(sample_count) * 6.708f) *
1101 static_cast<f32>(count));
1102 case 240:
1103 return static_cast<u32>((static_cast<f32>(sample_count) * 6.443f) *
1104 static_cast<f32>(count));
1105 default:
1106 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
1107 return 0;
1108 }
1109}
1110
1111u32 CommandProcessingTimeEstimatorVersion3::Estimate(
1112 [[maybe_unused]] const DepopPrepareCommand& command) const {
1113 return 0;
1114}
1115
1116u32 CommandProcessingTimeEstimatorVersion3::Estimate(
1117 [[maybe_unused]] const DepopForMixBuffersCommand& command) const {
1118 switch (sample_count) {
1119 case 160:
1120 return static_cast<u32>(739.64f);
1121 case 240:
1122 return static_cast<u32>(910.97f);
1123 default:
1124 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
1125 return 0;
1126 }
1127}
1128
1129u32 CommandProcessingTimeEstimatorVersion3::Estimate(const DelayCommand& command) const {
1130 switch (sample_count) {
1131 case 160:
1132 if (command.enabled) {
1133 switch (command.parameter.channel_count) {
1134 case 1:
1135 return static_cast<u32>(8929.042f);
1136 case 2:
1137 return static_cast<u32>(25500.75f);
1138 case 4:
1139 return static_cast<u32>(47759.617f);
1140 case 6:
1141 return static_cast<u32>(82203.07f);
1142 default:
1143 LOG_ERROR(Service_Audio, "Invalid channel count {}",
1144 command.parameter.channel_count);
1145 return 0;
1146 }
1147 }
1148 switch (command.parameter.channel_count) {
1149 case 1:
1150 return static_cast<u32>(1295.206f);
1151 case 2:
1152 return static_cast<u32>(1213.6f);
1153 case 4:
1154 return static_cast<u32>(942.028f);
1155 case 6:
1156 return static_cast<u32>(1001.553f);
1157 default:
1158 LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
1159 return 0;
1160 }
1161 case 240:
1162 if (command.enabled) {
1163 switch (command.parameter.channel_count) {
1164 case 1:
1165 return static_cast<u32>(11941.051f);
1166 case 2:
1167 return static_cast<u32>(37197.371f);
1168 case 4:
1169 return static_cast<u32>(69749.836f);
1170 case 6:
1171 return static_cast<u32>(120042.398f);
1172 default:
1173 LOG_ERROR(Service_Audio, "Invalid channel count {}",
1174 command.parameter.channel_count);
1175 return 0;
1176 }
1177 }
1178 switch (command.parameter.channel_count) {
1179 case 1:
1180 return static_cast<u32>(997.668f);
1181 case 2:
1182 return static_cast<u32>(977.634f);
1183 case 4:
1184 return static_cast<u32>(792.309f);
1185 case 6:
1186 return static_cast<u32>(875.427f);
1187 default:
1188 LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
1189 return 0;
1190 }
1191 default:
1192 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
1193 return 0;
1194 }
1195}
1196
1197u32 CommandProcessingTimeEstimatorVersion3::Estimate(
1198 [[maybe_unused]] const UpsampleCommand& command) const {
1199 switch (sample_count) {
1200 case 160:
1201 return static_cast<u32>(312990.0f);
1202 case 240:
1203 return static_cast<u32>(0.0f);
1204 default:
1205 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
1206 return 0;
1207 }
1208}
1209
1210u32 CommandProcessingTimeEstimatorVersion3::Estimate(
1211 [[maybe_unused]] const DownMix6chTo2chCommand& command) const {
1212 switch (sample_count) {
1213 case 160:
1214 return static_cast<u32>(9949.7f);
1215 case 240:
1216 return static_cast<u32>(14679.0f);
1217 default:
1218 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
1219 return 0;
1220 }
1221}
1222
1223u32 CommandProcessingTimeEstimatorVersion3::Estimate(const AuxCommand& command) const {
1224 switch (sample_count) {
1225 case 160:
1226 if (command.enabled) {
1227 return static_cast<u32>(7182.136f);
1228 }
1229 return static_cast<u32>(472.111f);
1230 case 240:
1231 if (command.enabled) {
1232 return static_cast<u32>(9435.961f);
1233 }
1234 return static_cast<u32>(462.619f);
1235 default:
1236 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
1237 return 0;
1238 }
1239}
1240
1241u32 CommandProcessingTimeEstimatorVersion3::Estimate(const DeviceSinkCommand& command) const {
1242 switch (command.input_count) {
1243 case 2:
1244 switch (sample_count) {
1245 case 160:
1246 return static_cast<u32>(8979.956f);
1247 case 240:
1248 return static_cast<u32>(9221.907f);
1249 default:
1250 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
1251 return 0;
1252 }
1253 case 6:
1254 switch (sample_count) {
1255 case 160:
1256 return static_cast<u32>(9177.903f);
1257 case 240:
1258 return static_cast<u32>(9725.897f);
1259 default:
1260 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
1261 return 0;
1262 }
1263 default:
1264 LOG_ERROR(Service_Audio, "Invalid input count {}", command.input_count);
1265 return 0;
1266 }
1267}
1268
1269u32 CommandProcessingTimeEstimatorVersion3::Estimate(
1270 const CircularBufferSinkCommand& command) const {
1271 switch (sample_count) {
1272 case 160:
1273 return static_cast<u32>(static_cast<f32>(command.input_count) * 531.069f + 0.0f);
1274 case 240:
1275 return static_cast<u32>(static_cast<f32>(command.input_count) * 770.257f + 0.0f);
1276 default:
1277 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
1278 return 0;
1279 }
1280}
1281
1282u32 CommandProcessingTimeEstimatorVersion3::Estimate(const ReverbCommand& command) const {
1283 switch (sample_count) {
1284 case 160:
1285 if (command.enabled) {
1286 switch (command.parameter.channel_count) {
1287 case 1:
1288 return static_cast<u32>(81475.055f);
1289 case 2:
1290 return static_cast<u32>(84975.0f);
1291 case 4:
1292 return static_cast<u32>(91625.148f);
1293 case 6:
1294 return static_cast<u32>(95332.266f);
1295 default:
1296 LOG_ERROR(Service_Audio, "Invalid channel count {}",
1297 command.parameter.channel_count);
1298 return 0;
1299 }
1300 }
1301 switch (command.parameter.channel_count) {
1302 case 1:
1303 return static_cast<u32>(536.298f);
1304 case 2:
1305 return static_cast<u32>(588.798f);
1306 case 4:
1307 return static_cast<u32>(643.702f);
1308 case 6:
1309 return static_cast<u32>(705.999f);
1310 default:
1311 LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
1312 return 0;
1313 }
1314 case 240:
1315 if (command.enabled) {
1316 switch (command.parameter.channel_count) {
1317 case 1:
1318 return static_cast<u32>(120174.469f);
1319 case 2:
1320 return static_cast<u32>(125262.219f);
1321 case 4:
1322 return static_cast<u32>(135751.234f);
1323 case 6:
1324 return static_cast<u32>(141129.234f);
1325 default:
1326 LOG_ERROR(Service_Audio, "Invalid channel count {}",
1327 command.parameter.channel_count);
1328 return 0;
1329 }
1330 }
1331 switch (command.parameter.channel_count) {
1332 case 1:
1333 return static_cast<u32>(617.641f);
1334 case 2:
1335 return static_cast<u32>(659.536f);
1336 case 4:
1337 return static_cast<u32>(711.438f);
1338 case 6:
1339 return static_cast<u32>(778.071f);
1340 default:
1341 LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
1342 return 0;
1343 }
1344 default:
1345 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
1346 return 0;
1347 }
1348}
1349
1350u32 CommandProcessingTimeEstimatorVersion3::Estimate(const I3dl2ReverbCommand& command) const {
1351 switch (sample_count) {
1352 case 160:
1353 if (command.enabled) {
1354 switch (command.parameter.channel_count) {
1355 case 1:
1356 return static_cast<u32>(116754.984f);
1357 case 2:
1358 return static_cast<u32>(125912.055f);
1359 case 4:
1360 return static_cast<u32>(146336.031f);
1361 case 6:
1362 return static_cast<u32>(165812.656f);
1363 default:
1364 LOG_ERROR(Service_Audio, "Invalid channel count {}",
1365 command.parameter.channel_count);
1366 return 0;
1367 }
1368 }
1369 switch (command.parameter.channel_count) {
1370 case 1:
1371 return static_cast<u32>(735.0f);
1372 case 2:
1373 return static_cast<u32>(766.615f);
1374 case 4:
1375 return static_cast<u32>(834.067f);
1376 case 6:
1377 return static_cast<u32>(875.437f);
1378 default:
1379 LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
1380 return 0;
1381 }
1382 case 240:
1383 if (command.enabled) {
1384 switch (command.parameter.channel_count) {
1385 case 1:
1386 return static_cast<u32>(170292.344f);
1387 case 2:
1388 return static_cast<u32>(183875.625f);
1389 case 4:
1390 return static_cast<u32>(214696.188f);
1391 case 6:
1392 return static_cast<u32>(243846.766f);
1393 default:
1394 LOG_ERROR(Service_Audio, "Invalid channel count {}",
1395 command.parameter.channel_count);
1396 return 0;
1397 }
1398 }
1399 switch (command.parameter.channel_count) {
1400 case 1:
1401 return static_cast<u32>(508.473f);
1402 case 2:
1403 return static_cast<u32>(582.445f);
1404 case 4:
1405 return static_cast<u32>(626.419f);
1406 case 6:
1407 return static_cast<u32>(682.468f);
1408 default:
1409 LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
1410 return 0;
1411 }
1412 default:
1413 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
1414 return 0;
1415 }
1416}
1417
1418u32 CommandProcessingTimeEstimatorVersion3::Estimate(
1419 [[maybe_unused]] const PerformanceCommand& command) const {
1420 switch (sample_count) {
1421 case 160:
1422 return static_cast<u32>(498.17f);
1423 case 240:
1424 return static_cast<u32>(489.42f);
1425 default:
1426 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
1427 return 0;
1428 }
1429}
1430
1431u32 CommandProcessingTimeEstimatorVersion3::Estimate(
1432 [[maybe_unused]] const ClearMixBufferCommand& command) const {
1433 switch (sample_count) {
1434 case 160:
1435 return static_cast<u32>(static_cast<f32>(buffer_count - 1) * 266.645f + 0.0f);
1436 case 240:
1437 return static_cast<u32>(static_cast<f32>(buffer_count - 1) * 440.681f + 0.0f);
1438 default:
1439 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
1440 return 0;
1441 }
1442}
1443
1444u32 CommandProcessingTimeEstimatorVersion3::Estimate(
1445 [[maybe_unused]] const CopyMixBufferCommand& command) const {
1446 switch (sample_count) {
1447 case 160:
1448 return static_cast<u32>(842.59f);
1449 case 240:
1450 return static_cast<u32>(986.72f);
1451 default:
1452 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
1453 return 0;
1454 }
1455}
1456
1457u32 CommandProcessingTimeEstimatorVersion3::Estimate(
1458 const LightLimiterVersion1Command& command) const {
1459 switch (sample_count) {
1460 case 160:
1461 if (command.enabled) {
1462 switch (command.parameter.channel_count) {
1463 case 1:
1464 return static_cast<u32>(21392.383f);
1465 case 2:
1466 return static_cast<u32>(26829.389f);
1467 case 4:
1468 return static_cast<u32>(32405.152f);
1469 case 6:
1470 return static_cast<u32>(52218.586f);
1471 default:
1472 LOG_ERROR(Service_Audio, "Invalid channel count {}",
1473 command.parameter.channel_count);
1474 return 0;
1475 }
1476 }
1477 switch (command.parameter.channel_count) {
1478 case 1:
1479 return static_cast<u32>(897.004f);
1480 case 2:
1481 return static_cast<u32>(931.549f);
1482 case 4:
1483 return static_cast<u32>(975.387f);
1484 case 6:
1485 return static_cast<u32>(1016.778f);
1486 default:
1487 LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
1488 return 0;
1489 }
1490 case 240:
1491 if (command.enabled) {
1492 switch (command.parameter.channel_count) {
1493 case 1:
1494 return static_cast<u32>(30555.504f);
1495 case 2:
1496 return static_cast<u32>(39010.785f);
1497 case 4:
1498 return static_cast<u32>(48270.18f);
1499 case 6:
1500 return static_cast<u32>(76711.875f);
1501 default:
1502 LOG_ERROR(Service_Audio, "Invalid channel count {}",
1503 command.parameter.channel_count);
1504 return 0;
1505 }
1506 }
1507 switch (command.parameter.channel_count) {
1508 case 1:
1509 return static_cast<u32>(874.429f);
1510 case 2:
1511 return static_cast<u32>(921.553f);
1512 case 4:
1513 return static_cast<u32>(945.262f);
1514 case 6:
1515 return static_cast<u32>(992.26f);
1516 default:
1517 LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
1518 return 0;
1519 }
1520 default:
1521 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
1522 return 0;
1523 }
1524}
1525
1526u32 CommandProcessingTimeEstimatorVersion3::Estimate(
1527 const LightLimiterVersion2Command& command) const {
1528 switch (sample_count) {
1529 case 160:
1530 if (command.enabled) {
1531 if (command.parameter.statistics_enabled) {
1532 switch (command.parameter.channel_count) {
1533 case 1:
1534 return static_cast<u32>(23308.928f);
1535 case 2:
1536 return static_cast<u32>(29954.062f);
1537 case 4:
1538 return static_cast<u32>(35807.477f);
1539 case 6:
1540 return static_cast<u32>(58339.773f);
1541 default:
1542 LOG_ERROR(Service_Audio, "Invalid channel count {}",
1543 command.parameter.channel_count);
1544 return 0;
1545 }
1546 }
1547 switch (command.parameter.channel_count) {
1548 case 1:
1549 return static_cast<u32>(21392.383f);
1550 case 2:
1551 return static_cast<u32>(26829.389f);
1552 case 4:
1553 return static_cast<u32>(32405.152f);
1554 case 6:
1555 return static_cast<u32>(52218.586f);
1556 default:
1557 LOG_ERROR(Service_Audio, "Invalid channel count {}",
1558 command.parameter.channel_count);
1559 return 0;
1560 }
1561 }
1562 switch (command.parameter.channel_count) {
1563 case 1:
1564 return static_cast<u32>(897.004f);
1565 case 2:
1566 return static_cast<u32>(931.549f);
1567 case 4:
1568 return static_cast<u32>(975.387f);
1569 case 6:
1570 return static_cast<u32>(1016.778f);
1571 default:
1572 LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
1573 return 0;
1574 }
1575 case 240:
1576 if (command.enabled) {
1577 if (command.parameter.statistics_enabled) {
1578 switch (command.parameter.channel_count) {
1579 case 1:
1580 return static_cast<u32>(33526.121f);
1581 case 2:
1582 return static_cast<u32>(43549.355f);
1583 case 4:
1584 return static_cast<u32>(52190.281f);
1585 case 6:
1586 return static_cast<u32>(85526.516f);
1587 default:
1588 LOG_ERROR(Service_Audio, "Invalid channel count {}",
1589 command.parameter.channel_count);
1590 return 0;
1591 }
1592 }
1593 switch (command.parameter.channel_count) {
1594 case 1:
1595 return static_cast<u32>(30555.504f);
1596 case 2:
1597 return static_cast<u32>(39010.785f);
1598 case 4:
1599 return static_cast<u32>(48270.18f);
1600 case 6:
1601 return static_cast<u32>(76711.875f);
1602 default:
1603 LOG_ERROR(Service_Audio, "Invalid channel count {}",
1604 command.parameter.channel_count);
1605 return 0;
1606 }
1607 }
1608 switch (command.parameter.channel_count) {
1609 case 1:
1610 return static_cast<u32>(874.429f);
1611 case 2:
1612 return static_cast<u32>(921.553f);
1613 case 4:
1614 return static_cast<u32>(945.262f);
1615 case 6:
1616 return static_cast<u32>(992.26f);
1617 default:
1618 LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
1619 return 0;
1620 }
1621 default:
1622 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
1623 return 0;
1624 }
1625}
1626
1627u32 CommandProcessingTimeEstimatorVersion3::Estimate(
1628 [[maybe_unused]] const MultiTapBiquadFilterCommand& command) const {
1629 return 0;
1630}
1631
1632u32 CommandProcessingTimeEstimatorVersion3::Estimate(
1633 [[maybe_unused]] const CaptureCommand& command) const {
1634 return 0;
1635}
1636
1637u32 CommandProcessingTimeEstimatorVersion3::Estimate(
1638 [[maybe_unused]] const CompressorCommand& command) const {
1639 return 0;
1640}
1641
1642u32 CommandProcessingTimeEstimatorVersion4::Estimate(
1643 const PcmInt16DataSourceVersion1Command& command) const {
1644 switch (sample_count) {
1645 case 160:
1646 return static_cast<u32>(
1647 ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
1648 (command.pitch * 0.000030518f)) *
1649 427.52f +
1650 6329.442f);
1651 case 240:
1652 return static_cast<u32>(
1653 ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
1654 (command.pitch * 0.000030518f)) *
1655 710.143f +
1656 7853.286f);
1657 default:
1658 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
1659 return 0;
1660 }
1661}
1662
1663u32 CommandProcessingTimeEstimatorVersion4::Estimate(
1664 const PcmInt16DataSourceVersion2Command& command) const {
1665 switch (sample_count) {
1666 case 160:
1667 switch (command.src_quality) {
1668 case SrcQuality::Medium:
1669 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
1670 static_cast<f32>(sample_count)) *
1671 (command.pitch * 0.000030518f)) -
1672 1.0f) *
1673 427.52f +
1674 6329.442f);
1675 case SrcQuality::High:
1676 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
1677 static_cast<f32>(sample_count)) *
1678 (command.pitch * 0.000030518f)) -
1679 1.0f) *
1680 371.876f +
1681 8049.415f);
1682 case SrcQuality::Low:
1683 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
1684 static_cast<f32>(sample_count)) *
1685 (command.pitch * 0.000030518f)) -
1686 1.0f) *
1687 423.43f +
1688 5062.659f);
1689 default:
1690 LOG_ERROR(Service_Audio, "Invalid SRC quality {}",
1691 static_cast<u32>(command.src_quality));
1692 return 0;
1693 }
1694
1695 case 240:
1696 switch (command.src_quality) {
1697 case SrcQuality::Medium:
1698 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
1699 static_cast<f32>(sample_count)) *
1700 (command.pitch * 0.000030518f)) -
1701 1.0f) *
1702 710.143f +
1703 7853.286f);
1704 case SrcQuality::High:
1705 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
1706 static_cast<f32>(sample_count)) *
1707 (command.pitch * 0.000030518f)) -
1708 1.0f) *
1709 610.487f +
1710 10138.842f);
1711 case SrcQuality::Low:
1712 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
1713 static_cast<f32>(sample_count)) *
1714 (command.pitch * 0.000030518f)) -
1715 1.0f) *
1716 676.722f +
1717 5810.962f);
1718 default:
1719 LOG_ERROR(Service_Audio, "Invalid SRC quality {}",
1720 static_cast<u32>(command.src_quality));
1721 return 0;
1722 }
1723
1724 default:
1725 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
1726 return 0;
1727 }
1728}
1729
1730u32 CommandProcessingTimeEstimatorVersion4::Estimate(
1731 const PcmFloatDataSourceVersion1Command& command) const {
1732 switch (sample_count) {
1733 case 160:
1734 return static_cast<u32>(
1735 ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
1736 (command.pitch * 0.000030518f)) *
1737 1672.026f +
1738 7681.211f);
1739 case 240:
1740 return static_cast<u32>(
1741 ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
1742 (command.pitch * 0.000030518f)) *
1743 2550.414f +
1744 9663.969f);
1745 default:
1746 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
1747 return 0;
1748 }
1749}
1750
1751u32 CommandProcessingTimeEstimatorVersion4::Estimate(
1752 const PcmFloatDataSourceVersion2Command& command) const {
1753 switch (sample_count) {
1754 case 160:
1755 switch (command.src_quality) {
1756 case SrcQuality::Medium:
1757 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
1758 static_cast<f32>(sample_count)) *
1759 (command.pitch * 0.000030518f)) -
1760 1.0f) *
1761 1672.026f +
1762 7681.211f);
1763 case SrcQuality::High:
1764 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
1765 static_cast<f32>(sample_count)) *
1766 (command.pitch * 0.000030518f)) -
1767 1.0f) *
1768 1672.982f +
1769 9038.011f);
1770 case SrcQuality::Low:
1771 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
1772 static_cast<f32>(sample_count)) *
1773 (command.pitch * 0.000030518f)) -
1774 1.0f) *
1775 1673.216f +
1776 6027.577f);
1777 default:
1778 LOG_ERROR(Service_Audio, "Invalid SRC quality {}",
1779 static_cast<u32>(command.src_quality));
1780 return 0;
1781 }
1782
1783 case 240:
1784 switch (command.src_quality) {
1785 case SrcQuality::Medium:
1786 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
1787 static_cast<f32>(sample_count)) *
1788 (command.pitch * 0.000030518f)) -
1789 1.0f) *
1790 2550.414f +
1791 9663.969f);
1792 case SrcQuality::High:
1793 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
1794 static_cast<f32>(sample_count)) *
1795 (command.pitch * 0.000030518f)) -
1796 1.0f) *
1797 2522.303f +
1798 11758.571f);
1799 case SrcQuality::Low:
1800 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
1801 static_cast<f32>(sample_count)) *
1802 (command.pitch * 0.000030518f)) -
1803 1.0f) *
1804 2537.061f +
1805 7369.309f);
1806 default:
1807 LOG_ERROR(Service_Audio, "Invalid SRC quality {}",
1808 static_cast<u32>(command.src_quality));
1809 return 0;
1810 }
1811
1812 default:
1813 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
1814 return 0;
1815 }
1816}
1817
1818u32 CommandProcessingTimeEstimatorVersion4::Estimate(
1819 const AdpcmDataSourceVersion1Command& command) const {
1820 switch (sample_count) {
1821 case 160:
1822 return static_cast<u32>(
1823 ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
1824 (command.pitch * 0.000030518f)) *
1825 1827.665f +
1826 7913.808f);
1827 case 240:
1828 return static_cast<u32>(
1829 ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
1830 (command.pitch * 0.000030518f)) *
1831 2756.372f +
1832 9736.702f);
1833 default:
1834 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
1835 return 0;
1836 }
1837}
1838
1839u32 CommandProcessingTimeEstimatorVersion4::Estimate(
1840 const AdpcmDataSourceVersion2Command& command) const {
1841 switch (sample_count) {
1842 case 160:
1843 switch (command.src_quality) {
1844 case SrcQuality::Medium:
1845 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
1846 static_cast<f32>(sample_count)) *
1847 (command.pitch * 0.000030518f)) -
1848 1.0f) *
1849 1827.665f +
1850 7913.808f);
1851 case SrcQuality::High:
1852 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
1853 static_cast<f32>(sample_count)) *
1854 (command.pitch * 0.000030518f)) -
1855 1.0f) *
1856 1829.285f +
1857 9607.814f);
1858 case SrcQuality::Low:
1859 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
1860 static_cast<f32>(sample_count)) *
1861 (command.pitch * 0.000030518f)) -
1862 1.0f) *
1863 1824.609f +
1864 6517.476f);
1865 default:
1866 LOG_ERROR(Service_Audio, "Invalid SRC quality {}",
1867 static_cast<u32>(command.src_quality));
1868 return 0;
1869 }
1870
1871 case 240:
1872 switch (command.src_quality) {
1873 case SrcQuality::Medium:
1874 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
1875 static_cast<f32>(sample_count)) *
1876 (command.pitch * 0.000030518f)) -
1877 1.0f) *
1878 2756.372f +
1879 9736.702f);
1880 case SrcQuality::High:
1881 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
1882 static_cast<f32>(sample_count)) *
1883 (command.pitch * 0.000030518f)) -
1884 1.0f) *
1885 2731.308f +
1886 12154.379f);
1887 case SrcQuality::Low:
1888 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
1889 static_cast<f32>(sample_count)) *
1890 (command.pitch * 0.000030518f)) -
1891 1.0f) *
1892 2732.152f +
1893 7929.442f);
1894 default:
1895 LOG_ERROR(Service_Audio, "Invalid SRC quality {}",
1896 static_cast<u32>(command.src_quality));
1897 return 0;
1898 }
1899
1900 default:
1901 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
1902 return 0;
1903 }
1904}
1905
1906u32 CommandProcessingTimeEstimatorVersion4::Estimate(
1907 [[maybe_unused]] const VolumeCommand& command) const {
1908 switch (sample_count) {
1909 case 160:
1910 return static_cast<u32>(1311.1f);
1911 case 240:
1912 return static_cast<u32>(1713.6f);
1913 default:
1914 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
1915 return 0;
1916 }
1917}
1918
1919u32 CommandProcessingTimeEstimatorVersion4::Estimate(
1920 [[maybe_unused]] const VolumeRampCommand& command) const {
1921 switch (sample_count) {
1922 case 160:
1923 return static_cast<u32>(1425.3f);
1924 case 240:
1925 return static_cast<u32>(1700.0f);
1926 default:
1927 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
1928 return 0;
1929 }
1930}
1931
1932u32 CommandProcessingTimeEstimatorVersion4::Estimate(
1933 [[maybe_unused]] const BiquadFilterCommand& command) const {
1934 switch (sample_count) {
1935 case 160:
1936 return static_cast<u32>(4173.2f);
1937 case 240:
1938 return static_cast<u32>(5585.1f);
1939 default:
1940 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
1941 return 0;
1942 }
1943}
1944
1945u32 CommandProcessingTimeEstimatorVersion4::Estimate(
1946 [[maybe_unused]] const MixCommand& command) const {
1947 switch (sample_count) {
1948 case 160:
1949 return static_cast<u32>(1402.8f);
1950 case 240:
1951 return static_cast<u32>(1853.2f);
1952 default:
1953 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
1954 return 0;
1955 }
1956}
1957
1958u32 CommandProcessingTimeEstimatorVersion4::Estimate(
1959 [[maybe_unused]] const MixRampCommand& command) const {
1960 switch (sample_count) {
1961 case 160:
1962 return static_cast<u32>(1968.7f);
1963 case 240:
1964 return static_cast<u32>(2459.4f);
1965 default:
1966 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
1967 return 0;
1968 }
1969}
1970
1971u32 CommandProcessingTimeEstimatorVersion4::Estimate(const MixRampGroupedCommand& command) const {
1972 u32 count{0};
1973 for (u32 i = 0; i < command.buffer_count; i++) {
1974 if (command.volumes[i] != 0.0f || command.prev_volumes[i] != 0.0f) {
1975 count++;
1976 }
1977 }
1978
1979 switch (sample_count) {
1980 case 160:
1981 return static_cast<u32>((static_cast<f32>(sample_count) * 6.708f) *
1982 static_cast<f32>(count));
1983 case 240:
1984 return static_cast<u32>((static_cast<f32>(sample_count) * 6.443f) *
1985 static_cast<f32>(count));
1986 default:
1987 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
1988 return 0;
1989 }
1990}
1991
1992u32 CommandProcessingTimeEstimatorVersion4::Estimate(
1993 [[maybe_unused]] const DepopPrepareCommand& command) const {
1994 return 0;
1995}
1996
1997u32 CommandProcessingTimeEstimatorVersion4::Estimate(
1998 [[maybe_unused]] const DepopForMixBuffersCommand& command) const {
1999 switch (sample_count) {
2000 case 160:
2001 return static_cast<u32>(739.64f);
2002 case 240:
2003 return static_cast<u32>(910.97f);
2004 default:
2005 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
2006 return 0;
2007 }
2008}
2009
2010u32 CommandProcessingTimeEstimatorVersion4::Estimate(const DelayCommand& command) const {
2011 switch (sample_count) {
2012 case 160:
2013 if (command.enabled) {
2014 switch (command.parameter.channel_count) {
2015 case 1:
2016 return static_cast<u32>(8929.042f);
2017 case 2:
2018 return static_cast<u32>(25500.75f);
2019 case 4:
2020 return static_cast<u32>(47759.617f);
2021 case 6:
2022 return static_cast<u32>(82203.07f);
2023 default:
2024 LOG_ERROR(Service_Audio, "Invalid channel count {}",
2025 command.parameter.channel_count);
2026 return 0;
2027 }
2028 }
2029 switch (command.parameter.channel_count) {
2030 case 1:
2031 return static_cast<u32>(1295.206f);
2032 case 2:
2033 return static_cast<u32>(1213.6f);
2034 case 4:
2035 return static_cast<u32>(942.028f);
2036 case 6:
2037 return static_cast<u32>(1001.553f);
2038 default:
2039 LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
2040 return 0;
2041 }
2042 case 240:
2043 if (command.enabled) {
2044 switch (command.parameter.channel_count) {
2045 case 1:
2046 return static_cast<u32>(11941.051f);
2047 case 2:
2048 return static_cast<u32>(37197.371f);
2049 case 4:
2050 return static_cast<u32>(69749.836f);
2051 case 6:
2052 return static_cast<u32>(120042.398f);
2053 default:
2054 LOG_ERROR(Service_Audio, "Invalid channel count {}",
2055 command.parameter.channel_count);
2056 return 0;
2057 }
2058 }
2059 switch (command.parameter.channel_count) {
2060 case 1:
2061 return static_cast<u32>(997.668f);
2062 case 2:
2063 return static_cast<u32>(977.634f);
2064 case 4:
2065 return static_cast<u32>(792.309f);
2066 case 6:
2067 return static_cast<u32>(875.427f);
2068 default:
2069 LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
2070 return 0;
2071 }
2072 default:
2073 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
2074 return 0;
2075 }
2076}
2077
2078u32 CommandProcessingTimeEstimatorVersion4::Estimate(
2079 [[maybe_unused]] const UpsampleCommand& command) const {
2080 switch (sample_count) {
2081 case 160:
2082 return static_cast<u32>(312990.0f);
2083 case 240:
2084 return static_cast<u32>(0.0f);
2085 default:
2086 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
2087 return 0;
2088 }
2089}
2090
2091u32 CommandProcessingTimeEstimatorVersion4::Estimate(
2092 [[maybe_unused]] const DownMix6chTo2chCommand& command) const {
2093 switch (sample_count) {
2094 case 160:
2095 return static_cast<u32>(9949.7f);
2096 case 240:
2097 return static_cast<u32>(14679.0f);
2098 default:
2099 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
2100 return 0;
2101 }
2102}
2103
2104u32 CommandProcessingTimeEstimatorVersion4::Estimate(const AuxCommand& command) const {
2105 switch (sample_count) {
2106 case 160:
2107 if (command.enabled) {
2108 return static_cast<u32>(7182.136f);
2109 }
2110 return static_cast<u32>(472.111f);
2111 case 240:
2112 if (command.enabled) {
2113 return static_cast<u32>(9435.961f);
2114 }
2115 return static_cast<u32>(462.619f);
2116 default:
2117 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
2118 return 0;
2119 }
2120}
2121
2122u32 CommandProcessingTimeEstimatorVersion4::Estimate(const DeviceSinkCommand& command) const {
2123 switch (command.input_count) {
2124 case 2:
2125 switch (sample_count) {
2126 case 160:
2127 return static_cast<u32>(8979.956f);
2128 case 240:
2129 return static_cast<u32>(9221.907f);
2130 default:
2131 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
2132 return 0;
2133 }
2134 case 6:
2135 switch (sample_count) {
2136 case 160:
2137 return static_cast<u32>(9177.903f);
2138 case 240:
2139 return static_cast<u32>(9725.897f);
2140 default:
2141 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
2142 return 0;
2143 }
2144 default:
2145 LOG_ERROR(Service_Audio, "Invalid input count {}", command.input_count);
2146 return 0;
2147 }
2148}
2149
2150u32 CommandProcessingTimeEstimatorVersion4::Estimate(
2151 const CircularBufferSinkCommand& command) const {
2152 switch (sample_count) {
2153 case 160:
2154 return static_cast<u32>(static_cast<f32>(command.input_count) * 531.069f + 0.0f);
2155 case 240:
2156 return static_cast<u32>(static_cast<f32>(command.input_count) * 770.257f + 0.0f);
2157 default:
2158 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
2159 return 0;
2160 }
2161}
2162
2163u32 CommandProcessingTimeEstimatorVersion4::Estimate(const ReverbCommand& command) const {
2164 switch (sample_count) {
2165 case 160:
2166 if (command.enabled) {
2167 switch (command.parameter.channel_count) {
2168 case 1:
2169 return static_cast<u32>(81475.055f);
2170 case 2:
2171 return static_cast<u32>(84975.0f);
2172 case 4:
2173 return static_cast<u32>(91625.148f);
2174 case 6:
2175 return static_cast<u32>(95332.266f);
2176 default:
2177 LOG_ERROR(Service_Audio, "Invalid channel count {}",
2178 command.parameter.channel_count);
2179 return 0;
2180 }
2181 }
2182 switch (command.parameter.channel_count) {
2183 case 1:
2184 return static_cast<u32>(536.298f);
2185 case 2:
2186 return static_cast<u32>(588.798f);
2187 case 4:
2188 return static_cast<u32>(643.702f);
2189 case 6:
2190 return static_cast<u32>(705.999f);
2191 default:
2192 LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
2193 return 0;
2194 }
2195 case 240:
2196 if (command.enabled) {
2197 switch (command.parameter.channel_count) {
2198 case 1:
2199 return static_cast<u32>(120174.469f);
2200 case 2:
2201 return static_cast<u32>(125262.219f);
2202 case 4:
2203 return static_cast<u32>(135751.234f);
2204 case 6:
2205 return static_cast<u32>(141129.234f);
2206 default:
2207 LOG_ERROR(Service_Audio, "Invalid channel count {}",
2208 command.parameter.channel_count);
2209 return 0;
2210 }
2211 }
2212 switch (command.parameter.channel_count) {
2213 case 1:
2214 return static_cast<u32>(617.641f);
2215 case 2:
2216 return static_cast<u32>(659.536f);
2217 case 4:
2218 return static_cast<u32>(711.438f);
2219 case 6:
2220 return static_cast<u32>(778.071f);
2221 default:
2222 LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
2223 return 0;
2224 }
2225 default:
2226 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
2227 return 0;
2228 }
2229}
2230
2231u32 CommandProcessingTimeEstimatorVersion4::Estimate(const I3dl2ReverbCommand& command) const {
2232 switch (sample_count) {
2233 case 160:
2234 if (command.enabled) {
2235 switch (command.parameter.channel_count) {
2236 case 1:
2237 return static_cast<u32>(116754.984f);
2238 case 2:
2239 return static_cast<u32>(125912.055f);
2240 case 4:
2241 return static_cast<u32>(146336.031f);
2242 case 6:
2243 return static_cast<u32>(165812.656f);
2244 default:
2245 LOG_ERROR(Service_Audio, "Invalid channel count {}",
2246 command.parameter.channel_count);
2247 return 0;
2248 }
2249 }
2250 switch (command.parameter.channel_count) {
2251 case 1:
2252 return static_cast<u32>(735.0f);
2253 case 2:
2254 return static_cast<u32>(766.615f);
2255 case 4:
2256 return static_cast<u32>(834.067f);
2257 case 6:
2258 return static_cast<u32>(875.437f);
2259 default:
2260 LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
2261 return 0;
2262 }
2263 case 240:
2264 if (command.enabled) {
2265 switch (command.parameter.channel_count) {
2266 case 1:
2267 return static_cast<u32>(170292.344f);
2268 case 2:
2269 return static_cast<u32>(183875.625f);
2270 case 4:
2271 return static_cast<u32>(214696.188f);
2272 case 6:
2273 return static_cast<u32>(243846.766f);
2274 default:
2275 LOG_ERROR(Service_Audio, "Invalid channel count {}",
2276 command.parameter.channel_count);
2277 return 0;
2278 }
2279 }
2280 switch (command.parameter.channel_count) {
2281 case 1:
2282 return static_cast<u32>(508.473f);
2283 case 2:
2284 return static_cast<u32>(582.445f);
2285 case 4:
2286 return static_cast<u32>(626.419f);
2287 case 6:
2288 return static_cast<u32>(682.468f);
2289 default:
2290 LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
2291 return 0;
2292 }
2293 default:
2294 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
2295 return 0;
2296 }
2297}
2298
2299u32 CommandProcessingTimeEstimatorVersion4::Estimate(
2300 [[maybe_unused]] const PerformanceCommand& command) const {
2301 switch (sample_count) {
2302 case 160:
2303 return static_cast<u32>(498.17f);
2304 case 240:
2305 return static_cast<u32>(489.42f);
2306 default:
2307 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
2308 return 0;
2309 }
2310}
2311
2312u32 CommandProcessingTimeEstimatorVersion4::Estimate(
2313 [[maybe_unused]] const ClearMixBufferCommand& command) const {
2314 switch (sample_count) {
2315 case 160:
2316 return static_cast<u32>(static_cast<f32>(buffer_count - 1) * 266.645f + 0.0f);
2317 case 240:
2318 return static_cast<u32>(static_cast<f32>(buffer_count - 1) * 440.681f + 0.0f);
2319 default:
2320 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
2321 return 0;
2322 }
2323}
2324
2325u32 CommandProcessingTimeEstimatorVersion4::Estimate(
2326 [[maybe_unused]] const CopyMixBufferCommand& command) const {
2327 switch (sample_count) {
2328 case 160:
2329 return static_cast<u32>(842.59f);
2330 case 240:
2331 return static_cast<u32>(986.72f);
2332 default:
2333 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
2334 return 0;
2335 }
2336}
2337
2338u32 CommandProcessingTimeEstimatorVersion4::Estimate(
2339 const LightLimiterVersion1Command& command) const {
2340 switch (sample_count) {
2341 case 160:
2342 if (command.enabled) {
2343 switch (command.parameter.channel_count) {
2344 case 1:
2345 return static_cast<u32>(21392.383f);
2346 case 2:
2347 return static_cast<u32>(26829.389f);
2348 case 4:
2349 return static_cast<u32>(32405.152f);
2350 case 6:
2351 return static_cast<u32>(52218.586f);
2352 default:
2353 LOG_ERROR(Service_Audio, "Invalid channel count {}",
2354 command.parameter.channel_count);
2355 return 0;
2356 }
2357 }
2358 switch (command.parameter.channel_count) {
2359 case 1:
2360 return static_cast<u32>(897.004f);
2361 case 2:
2362 return static_cast<u32>(931.549f);
2363 case 4:
2364 return static_cast<u32>(975.387f);
2365 case 6:
2366 return static_cast<u32>(1016.778f);
2367 default:
2368 LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
2369 return 0;
2370 }
2371 case 240:
2372 if (command.enabled) {
2373 switch (command.parameter.channel_count) {
2374 case 1:
2375 return static_cast<u32>(30555.504f);
2376 case 2:
2377 return static_cast<u32>(39010.785f);
2378 case 4:
2379 return static_cast<u32>(48270.18f);
2380 case 6:
2381 return static_cast<u32>(76711.875f);
2382 default:
2383 LOG_ERROR(Service_Audio, "Invalid channel count {}",
2384 command.parameter.channel_count);
2385 return 0;
2386 }
2387 }
2388 switch (command.parameter.channel_count) {
2389 case 1:
2390 return static_cast<u32>(874.429f);
2391 case 2:
2392 return static_cast<u32>(921.553f);
2393 case 4:
2394 return static_cast<u32>(945.262f);
2395 case 6:
2396 return static_cast<u32>(992.26f);
2397 default:
2398 LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
2399 return 0;
2400 }
2401 default:
2402 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
2403 return 0;
2404 }
2405}
2406
2407u32 CommandProcessingTimeEstimatorVersion4::Estimate(
2408 const LightLimiterVersion2Command& command) const {
2409 switch (sample_count) {
2410 case 160:
2411 if (command.enabled) {
2412 if (command.parameter.statistics_enabled) {
2413 switch (command.parameter.channel_count) {
2414 case 1:
2415 return static_cast<u32>(23308.928f);
2416 case 2:
2417 return static_cast<u32>(29954.062f);
2418 case 4:
2419 return static_cast<u32>(35807.477f);
2420 case 6:
2421 return static_cast<u32>(58339.773f);
2422 default:
2423 LOG_ERROR(Service_Audio, "Invalid channel count {}",
2424 command.parameter.channel_count);
2425 return 0;
2426 }
2427 }
2428 switch (command.parameter.channel_count) {
2429 case 1:
2430 return static_cast<u32>(21392.383f);
2431 case 2:
2432 return static_cast<u32>(26829.389f);
2433 case 4:
2434 return static_cast<u32>(32405.152f);
2435 case 6:
2436 return static_cast<u32>(52218.586f);
2437 default:
2438 LOG_ERROR(Service_Audio, "Invalid channel count {}",
2439 command.parameter.channel_count);
2440 return 0;
2441 }
2442 }
2443 switch (command.parameter.channel_count) {
2444 case 1:
2445 return static_cast<u32>(897.004f);
2446 case 2:
2447 return static_cast<u32>(931.549f);
2448 case 4:
2449 return static_cast<u32>(975.387f);
2450 case 6:
2451 return static_cast<u32>(1016.778f);
2452 default:
2453 LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
2454 return 0;
2455 }
2456 case 240:
2457 if (command.enabled) {
2458 if (command.parameter.statistics_enabled) {
2459 switch (command.parameter.channel_count) {
2460 case 1:
2461 return static_cast<u32>(33526.121f);
2462 case 2:
2463 return static_cast<u32>(43549.355f);
2464 case 4:
2465 return static_cast<u32>(52190.281f);
2466 case 6:
2467 return static_cast<u32>(85526.516f);
2468 default:
2469 LOG_ERROR(Service_Audio, "Invalid channel count {}",
2470 command.parameter.channel_count);
2471 return 0;
2472 }
2473 }
2474 switch (command.parameter.channel_count) {
2475 case 1:
2476 return static_cast<u32>(30555.504f);
2477 case 2:
2478 return static_cast<u32>(39010.785f);
2479 case 4:
2480 return static_cast<u32>(48270.18f);
2481 case 6:
2482 return static_cast<u32>(76711.875f);
2483 default:
2484 LOG_ERROR(Service_Audio, "Invalid channel count {}",
2485 command.parameter.channel_count);
2486 return 0;
2487 }
2488 }
2489 switch (command.parameter.channel_count) {
2490 case 1:
2491 return static_cast<u32>(874.429f);
2492 case 2:
2493 return static_cast<u32>(921.553f);
2494 case 4:
2495 return static_cast<u32>(945.262f);
2496 case 6:
2497 return static_cast<u32>(992.26f);
2498 default:
2499 LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
2500 return 0;
2501 }
2502 default:
2503 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
2504 return 0;
2505 }
2506}
2507
2508u32 CommandProcessingTimeEstimatorVersion4::Estimate(
2509 [[maybe_unused]] const MultiTapBiquadFilterCommand& command) const {
2510 switch (sample_count) {
2511 case 160:
2512 return static_cast<u32>(7424.5f);
2513 case 240:
2514 return static_cast<u32>(9730.4f);
2515 default:
2516 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
2517 return 0;
2518 }
2519}
2520
2521u32 CommandProcessingTimeEstimatorVersion4::Estimate(const CaptureCommand& command) const {
2522 switch (sample_count) {
2523 case 160:
2524 if (command.enabled) {
2525 return static_cast<u32>(426.982f);
2526 }
2527 return static_cast<u32>(4261.005f);
2528 case 240:
2529 if (command.enabled) {
2530 return static_cast<u32>(435.204f);
2531 }
2532 return static_cast<u32>(5858.265f);
2533 default:
2534 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
2535 return 0;
2536 }
2537}
2538
2539u32 CommandProcessingTimeEstimatorVersion4::Estimate(
2540 [[maybe_unused]] const CompressorCommand& command) const {
2541 return 0;
2542}
2543
2544u32 CommandProcessingTimeEstimatorVersion5::Estimate(
2545 const PcmInt16DataSourceVersion1Command& command) const {
2546 switch (sample_count) {
2547 case 160:
2548 return static_cast<u32>(
2549 ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
2550 (command.pitch * 0.000030518f)) *
2551 427.52f +
2552 6329.442f);
2553 case 240:
2554 return static_cast<u32>(
2555 ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
2556 (command.pitch * 0.000030518f)) *
2557 710.143f +
2558 7853.286f);
2559 default:
2560 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
2561 return 0;
2562 }
2563}
2564
2565u32 CommandProcessingTimeEstimatorVersion5::Estimate(
2566 const PcmInt16DataSourceVersion2Command& command) const {
2567 switch (sample_count) {
2568 case 160:
2569 switch (command.src_quality) {
2570 case SrcQuality::Medium:
2571 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
2572 static_cast<f32>(sample_count)) *
2573 (command.pitch * 0.000030518f)) -
2574 1.0f) *
2575 427.52f +
2576 6329.442f);
2577 case SrcQuality::High:
2578 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
2579 static_cast<f32>(sample_count)) *
2580 (command.pitch * 0.000030518f)) -
2581 1.0f) *
2582 371.876f +
2583 8049.415f);
2584 case SrcQuality::Low:
2585 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
2586 static_cast<f32>(sample_count)) *
2587 (command.pitch * 0.000030518f)) -
2588 1.0f) *
2589 423.43f +
2590 5062.659f);
2591 default:
2592 LOG_ERROR(Service_Audio, "Invalid SRC quality {}",
2593 static_cast<u32>(command.src_quality));
2594 return 0;
2595 }
2596
2597 case 240:
2598 switch (command.src_quality) {
2599 case SrcQuality::Medium:
2600 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
2601 static_cast<f32>(sample_count)) *
2602 (command.pitch * 0.000030518f)) -
2603 1.0f) *
2604 710.143f +
2605 7853.286f);
2606 case SrcQuality::High:
2607 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
2608 static_cast<f32>(sample_count)) *
2609 (command.pitch * 0.000030518f)) -
2610 1.0f) *
2611 610.487f +
2612 10138.842f);
2613 case SrcQuality::Low:
2614 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
2615 static_cast<f32>(sample_count)) *
2616 (command.pitch * 0.000030518f)) -
2617 1.0f) *
2618 676.722f +
2619 5810.962f);
2620 default:
2621 LOG_ERROR(Service_Audio, "Invalid SRC quality {}",
2622 static_cast<u32>(command.src_quality));
2623 return 0;
2624 }
2625
2626 default:
2627 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
2628 return 0;
2629 }
2630}
2631
2632u32 CommandProcessingTimeEstimatorVersion5::Estimate(
2633 const PcmFloatDataSourceVersion1Command& command) const {
2634 switch (sample_count) {
2635 case 160:
2636 return static_cast<u32>(
2637 ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
2638 (command.pitch * 0.000030518f)) *
2639 1672.026f +
2640 7681.211f);
2641 case 240:
2642 return static_cast<u32>(
2643 ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
2644 (command.pitch * 0.000030518f)) *
2645 2550.414f +
2646 9663.969f);
2647 default:
2648 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
2649 return 0;
2650 }
2651}
2652
2653u32 CommandProcessingTimeEstimatorVersion5::Estimate(
2654 const PcmFloatDataSourceVersion2Command& command) const {
2655 switch (sample_count) {
2656 case 160:
2657 switch (command.src_quality) {
2658 case SrcQuality::Medium:
2659 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
2660 static_cast<f32>(sample_count)) *
2661 (command.pitch * 0.000030518f)) -
2662 1.0f) *
2663 1672.026f +
2664 7681.211f);
2665 case SrcQuality::High:
2666 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
2667 static_cast<f32>(sample_count)) *
2668 (command.pitch * 0.000030518f)) -
2669 1.0f) *
2670 1672.982f +
2671 9038.011f);
2672 case SrcQuality::Low:
2673 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
2674 static_cast<f32>(sample_count)) *
2675 (command.pitch * 0.000030518f)) -
2676 1.0f) *
2677 1673.216f +
2678 6027.577f);
2679 default:
2680 LOG_ERROR(Service_Audio, "Invalid SRC quality {}",
2681 static_cast<u32>(command.src_quality));
2682 return 0;
2683 }
2684
2685 case 240:
2686 switch (command.src_quality) {
2687 case SrcQuality::Medium:
2688 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
2689 static_cast<f32>(sample_count)) *
2690 (command.pitch * 0.000030518f)) -
2691 1.0f) *
2692 2550.414f +
2693 9663.969f);
2694 case SrcQuality::High:
2695 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
2696 static_cast<f32>(sample_count)) *
2697 (command.pitch * 0.000030518f)) -
2698 1.0f) *
2699 2522.303f +
2700 11758.571f);
2701 case SrcQuality::Low:
2702 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
2703 static_cast<f32>(sample_count)) *
2704 (command.pitch * 0.000030518f)) -
2705 1.0f) *
2706 2537.061f +
2707 7369.309f);
2708 default:
2709 LOG_ERROR(Service_Audio, "Invalid SRC quality {}",
2710 static_cast<u32>(command.src_quality));
2711 return 0;
2712 }
2713
2714 default:
2715 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
2716 return 0;
2717 }
2718}
2719
2720u32 CommandProcessingTimeEstimatorVersion5::Estimate(
2721 const AdpcmDataSourceVersion1Command& command) const {
2722 switch (sample_count) {
2723 case 160:
2724 return static_cast<u32>(
2725 ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
2726 (command.pitch * 0.000030518f)) *
2727 1827.665f +
2728 7913.808f);
2729 case 240:
2730 return static_cast<u32>(
2731 ((static_cast<f32>(command.sample_rate) / 200.0f / static_cast<f32>(sample_count)) *
2732 (command.pitch * 0.000030518f)) *
2733 2756.372f +
2734 9736.702f);
2735 default:
2736 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
2737 return 0;
2738 }
2739}
2740
2741u32 CommandProcessingTimeEstimatorVersion5::Estimate(
2742 const AdpcmDataSourceVersion2Command& command) const {
2743 switch (sample_count) {
2744 case 160:
2745 switch (command.src_quality) {
2746 case SrcQuality::Medium:
2747 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
2748 static_cast<f32>(sample_count)) *
2749 (command.pitch * 0.000030518f)) -
2750 1.0f) *
2751 1827.665f +
2752 7913.808f);
2753 case SrcQuality::High:
2754 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
2755 static_cast<f32>(sample_count)) *
2756 (command.pitch * 0.000030518f)) -
2757 1.0f) *
2758 1829.285f +
2759 9607.814f);
2760 case SrcQuality::Low:
2761 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
2762 static_cast<f32>(sample_count)) *
2763 (command.pitch * 0.000030518f)) -
2764 1.0f) *
2765 1824.609f +
2766 6517.476f);
2767 default:
2768 LOG_ERROR(Service_Audio, "Invalid SRC quality {}",
2769 static_cast<u32>(command.src_quality));
2770 return 0;
2771 }
2772
2773 case 240:
2774 switch (command.src_quality) {
2775 case SrcQuality::Medium:
2776 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
2777 static_cast<f32>(sample_count)) *
2778 (command.pitch * 0.000030518f)) -
2779 1.0f) *
2780 2756.372f +
2781 9736.702f);
2782 case SrcQuality::High:
2783 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
2784 static_cast<f32>(sample_count)) *
2785 (command.pitch * 0.000030518f)) -
2786 1.0f) *
2787 2731.308f +
2788 12154.379f);
2789 case SrcQuality::Low:
2790 return static_cast<u32>((((static_cast<f32>(command.sample_rate) / 200.0f /
2791 static_cast<f32>(sample_count)) *
2792 (command.pitch * 0.000030518f)) -
2793 1.0f) *
2794 2732.152f +
2795 7929.442f);
2796 default:
2797 LOG_ERROR(Service_Audio, "Invalid SRC quality {}",
2798 static_cast<u32>(command.src_quality));
2799 return 0;
2800 }
2801
2802 default:
2803 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
2804 return 0;
2805 }
2806}
2807
2808u32 CommandProcessingTimeEstimatorVersion5::Estimate(
2809 [[maybe_unused]] const VolumeCommand& command) const {
2810 switch (sample_count) {
2811 case 160:
2812 return static_cast<u32>(1311.1f);
2813 case 240:
2814 return static_cast<u32>(1713.6f);
2815 default:
2816 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
2817 return 0;
2818 }
2819}
2820
2821u32 CommandProcessingTimeEstimatorVersion5::Estimate(
2822 [[maybe_unused]] const VolumeRampCommand& command) const {
2823 switch (sample_count) {
2824 case 160:
2825 return static_cast<u32>(1425.3f);
2826 case 240:
2827 return static_cast<u32>(1700.0f);
2828 default:
2829 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
2830 return 0;
2831 }
2832}
2833
2834u32 CommandProcessingTimeEstimatorVersion5::Estimate(
2835 [[maybe_unused]] const BiquadFilterCommand& command) const {
2836 switch (sample_count) {
2837 case 160:
2838 return static_cast<u32>(4173.2f);
2839 case 240:
2840 return static_cast<u32>(5585.1f);
2841 default:
2842 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
2843 return 0;
2844 }
2845}
2846
2847u32 CommandProcessingTimeEstimatorVersion5::Estimate(
2848 [[maybe_unused]] const MixCommand& command) const {
2849 switch (sample_count) {
2850 case 160:
2851 return static_cast<u32>(1402.8f);
2852 case 240:
2853 return static_cast<u32>(1853.2f);
2854 default:
2855 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
2856 return 0;
2857 }
2858}
2859
2860u32 CommandProcessingTimeEstimatorVersion5::Estimate(
2861 [[maybe_unused]] const MixRampCommand& command) const {
2862 switch (sample_count) {
2863 case 160:
2864 return static_cast<u32>(1968.7f);
2865 case 240:
2866 return static_cast<u32>(2459.4f);
2867 default:
2868 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
2869 return 0;
2870 }
2871}
2872
2873u32 CommandProcessingTimeEstimatorVersion5::Estimate(const MixRampGroupedCommand& command) const {
2874 u32 count{0};
2875 for (u32 i = 0; i < command.buffer_count; i++) {
2876 if (command.volumes[i] != 0.0f || command.prev_volumes[i] != 0.0f) {
2877 count++;
2878 }
2879 }
2880
2881 switch (sample_count) {
2882 case 160:
2883 return static_cast<u32>((static_cast<f32>(sample_count) * 6.708f) *
2884 static_cast<f32>(count));
2885 case 240:
2886 return static_cast<u32>((static_cast<f32>(sample_count) * 6.443f) *
2887 static_cast<f32>(count));
2888 default:
2889 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
2890 return 0;
2891 }
2892}
2893
2894u32 CommandProcessingTimeEstimatorVersion5::Estimate(
2895 [[maybe_unused]] const DepopPrepareCommand& command) const {
2896 return 0;
2897}
2898
2899u32 CommandProcessingTimeEstimatorVersion5::Estimate(
2900 [[maybe_unused]] const DepopForMixBuffersCommand& command) const {
2901 switch (sample_count) {
2902 case 160:
2903 return static_cast<u32>(739.64f);
2904 case 240:
2905 return static_cast<u32>(910.97f);
2906 default:
2907 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
2908 return 0;
2909 }
2910}
2911
2912u32 CommandProcessingTimeEstimatorVersion5::Estimate(const DelayCommand& command) const {
2913 switch (sample_count) {
2914 case 160:
2915 if (command.enabled) {
2916 switch (command.parameter.channel_count) {
2917 case 1:
2918 return static_cast<u32>(8929.042f);
2919 case 2:
2920 return static_cast<u32>(25500.75f);
2921 case 4:
2922 return static_cast<u32>(47759.617f);
2923 case 6:
2924 return static_cast<u32>(82203.07f);
2925 default:
2926 LOG_ERROR(Service_Audio, "Invalid channel count {}",
2927 command.parameter.channel_count);
2928 return 0;
2929 }
2930 }
2931 switch (command.parameter.channel_count) {
2932 case 1:
2933 return static_cast<u32>(1295.206f);
2934 case 2:
2935 return static_cast<u32>(1213.6f);
2936 case 4:
2937 return static_cast<u32>(942.028f);
2938 case 6:
2939 return static_cast<u32>(1001.553f);
2940 default:
2941 LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
2942 return 0;
2943 }
2944 case 240:
2945 if (command.enabled) {
2946 switch (command.parameter.channel_count) {
2947 case 1:
2948 return static_cast<u32>(11941.051f);
2949 case 2:
2950 return static_cast<u32>(37197.371f);
2951 case 4:
2952 return static_cast<u32>(69749.836f);
2953 case 6:
2954 return static_cast<u32>(120042.398f);
2955 default:
2956 LOG_ERROR(Service_Audio, "Invalid channel count {}",
2957 command.parameter.channel_count);
2958 return 0;
2959 }
2960 }
2961 switch (command.parameter.channel_count) {
2962 case 1:
2963 return static_cast<u32>(997.668f);
2964 case 2:
2965 return static_cast<u32>(977.634f);
2966 case 4:
2967 return static_cast<u32>(792.309f);
2968 case 6:
2969 return static_cast<u32>(875.427f);
2970 default:
2971 LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
2972 return 0;
2973 }
2974 default:
2975 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
2976 return 0;
2977 }
2978}
2979
2980u32 CommandProcessingTimeEstimatorVersion5::Estimate(
2981 [[maybe_unused]] const UpsampleCommand& command) const {
2982 switch (sample_count) {
2983 case 160:
2984 return static_cast<u32>(312990.0f);
2985 case 240:
2986 return static_cast<u32>(0.0f);
2987 default:
2988 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
2989 return 0;
2990 }
2991}
2992
2993u32 CommandProcessingTimeEstimatorVersion5::Estimate(
2994 [[maybe_unused]] const DownMix6chTo2chCommand& command) const {
2995 switch (sample_count) {
2996 case 160:
2997 return static_cast<u32>(9949.7f);
2998 case 240:
2999 return static_cast<u32>(14679.0f);
3000 default:
3001 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
3002 return 0;
3003 }
3004}
3005
3006u32 CommandProcessingTimeEstimatorVersion5::Estimate(const AuxCommand& command) const {
3007 switch (sample_count) {
3008 case 160:
3009 if (command.enabled) {
3010 return static_cast<u32>(7182.136f);
3011 }
3012 return static_cast<u32>(472.111f);
3013 case 240:
3014 if (command.enabled) {
3015 return static_cast<u32>(9435.961f);
3016 }
3017 return static_cast<u32>(462.619f);
3018 default:
3019 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
3020 return 0;
3021 }
3022}
3023
3024u32 CommandProcessingTimeEstimatorVersion5::Estimate(const DeviceSinkCommand& command) const {
3025 switch (command.input_count) {
3026 case 2:
3027 switch (sample_count) {
3028 case 160:
3029 return static_cast<u32>(8979.956f);
3030 case 240:
3031 return static_cast<u32>(9221.907f);
3032 default:
3033 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
3034 return 0;
3035 }
3036 case 6:
3037 switch (sample_count) {
3038 case 160:
3039 return static_cast<u32>(9177.903f);
3040 case 240:
3041 return static_cast<u32>(9725.897f);
3042 default:
3043 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
3044 return 0;
3045 }
3046 default:
3047 LOG_ERROR(Service_Audio, "Invalid input count {}", command.input_count);
3048 return 0;
3049 }
3050}
3051
3052u32 CommandProcessingTimeEstimatorVersion5::Estimate(
3053 const CircularBufferSinkCommand& command) const {
3054 switch (sample_count) {
3055 case 160:
3056 return static_cast<u32>(static_cast<f32>(command.input_count) * 531.069f + 0.0f);
3057 case 240:
3058 return static_cast<u32>(static_cast<f32>(command.input_count) * 770.257f + 0.0f);
3059 default:
3060 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
3061 return 0;
3062 }
3063}
3064
3065u32 CommandProcessingTimeEstimatorVersion5::Estimate(const ReverbCommand& command) const {
3066 switch (sample_count) {
3067 case 160:
3068 if (command.enabled) {
3069 switch (command.parameter.channel_count) {
3070 case 1:
3071 return static_cast<u32>(81475.055f);
3072 case 2:
3073 return static_cast<u32>(84975.0f);
3074 case 4:
3075 return static_cast<u32>(91625.148f);
3076 case 6:
3077 return static_cast<u32>(95332.266f);
3078 default:
3079 LOG_ERROR(Service_Audio, "Invalid channel count {}",
3080 command.parameter.channel_count);
3081 return 0;
3082 }
3083 }
3084 switch (command.parameter.channel_count) {
3085 case 1:
3086 return static_cast<u32>(536.298f);
3087 case 2:
3088 return static_cast<u32>(588.798f);
3089 case 4:
3090 return static_cast<u32>(643.702f);
3091 case 6:
3092 return static_cast<u32>(705.999f);
3093 default:
3094 LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
3095 return 0;
3096 }
3097 case 240:
3098 if (command.enabled) {
3099 switch (command.parameter.channel_count) {
3100 case 1:
3101 return static_cast<u32>(120174.469f);
3102 case 2:
3103 return static_cast<u32>(125262.219f);
3104 case 4:
3105 return static_cast<u32>(135751.234f);
3106 case 6:
3107 return static_cast<u32>(141129.234f);
3108 default:
3109 LOG_ERROR(Service_Audio, "Invalid channel count {}",
3110 command.parameter.channel_count);
3111 return 0;
3112 }
3113 }
3114 switch (command.parameter.channel_count) {
3115 case 1:
3116 return static_cast<u32>(617.641f);
3117 case 2:
3118 return static_cast<u32>(659.536f);
3119 case 4:
3120 return static_cast<u32>(711.438f);
3121 case 6:
3122 return static_cast<u32>(778.071f);
3123 default:
3124 LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
3125 return 0;
3126 }
3127 default:
3128 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
3129 return 0;
3130 }
3131}
3132
3133u32 CommandProcessingTimeEstimatorVersion5::Estimate(const I3dl2ReverbCommand& command) const {
3134 switch (sample_count) {
3135 case 160:
3136 if (command.enabled) {
3137 switch (command.parameter.channel_count) {
3138 case 1:
3139 return static_cast<u32>(116754.984f);
3140 case 2:
3141 return static_cast<u32>(125912.055f);
3142 case 4:
3143 return static_cast<u32>(146336.031f);
3144 case 6:
3145 return static_cast<u32>(165812.656f);
3146 default:
3147 LOG_ERROR(Service_Audio, "Invalid channel count {}",
3148 command.parameter.channel_count);
3149 return 0;
3150 }
3151 }
3152 switch (command.parameter.channel_count) {
3153 case 1:
3154 return static_cast<u32>(735.0f);
3155 case 2:
3156 return static_cast<u32>(766.615f);
3157 case 4:
3158 return static_cast<u32>(834.067f);
3159 case 6:
3160 return static_cast<u32>(875.437f);
3161 default:
3162 LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
3163 return 0;
3164 }
3165 case 240:
3166 if (command.enabled) {
3167 switch (command.parameter.channel_count) {
3168 case 1:
3169 return static_cast<u32>(170292.344f);
3170 case 2:
3171 return static_cast<u32>(183875.625f);
3172 case 4:
3173 return static_cast<u32>(214696.188f);
3174 case 6:
3175 return static_cast<u32>(243846.766f);
3176 default:
3177 LOG_ERROR(Service_Audio, "Invalid channel count {}",
3178 command.parameter.channel_count);
3179 return 0;
3180 }
3181 }
3182 switch (command.parameter.channel_count) {
3183 case 1:
3184 return static_cast<u32>(508.473f);
3185 case 2:
3186 return static_cast<u32>(582.445f);
3187 case 4:
3188 return static_cast<u32>(626.419f);
3189 case 6:
3190 return static_cast<u32>(682.468f);
3191 default:
3192 LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
3193 return 0;
3194 }
3195 default:
3196 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
3197 return 0;
3198 }
3199}
3200
3201u32 CommandProcessingTimeEstimatorVersion5::Estimate(
3202 [[maybe_unused]] const PerformanceCommand& command) const {
3203 switch (sample_count) {
3204 case 160:
3205 return static_cast<u32>(498.17f);
3206 case 240:
3207 return static_cast<u32>(489.42f);
3208 default:
3209 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
3210 return 0;
3211 }
3212}
3213
3214u32 CommandProcessingTimeEstimatorVersion5::Estimate(
3215 [[maybe_unused]] const ClearMixBufferCommand& command) const {
3216 switch (sample_count) {
3217 case 160:
3218 return static_cast<u32>(static_cast<f32>(buffer_count - 1) * 266.645f + 0.0f);
3219 case 240:
3220 return static_cast<u32>(static_cast<f32>(buffer_count - 1) * 440.681f + 0.0f);
3221 default:
3222 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
3223 return 0;
3224 }
3225}
3226
3227u32 CommandProcessingTimeEstimatorVersion5::Estimate(
3228 [[maybe_unused]] const CopyMixBufferCommand& command) const {
3229 switch (sample_count) {
3230 case 160:
3231 return static_cast<u32>(842.59f);
3232 case 240:
3233 return static_cast<u32>(986.72f);
3234 default:
3235 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
3236 return 0;
3237 }
3238}
3239
3240u32 CommandProcessingTimeEstimatorVersion5::Estimate(
3241 const LightLimiterVersion1Command& command) const {
3242 switch (sample_count) {
3243 case 160:
3244 if (command.enabled) {
3245 switch (command.parameter.channel_count) {
3246 case 1:
3247 return static_cast<u32>(21508.01f);
3248 case 2:
3249 return static_cast<u32>(23120.453f);
3250 case 4:
3251 return static_cast<u32>(26270.053f);
3252 case 6:
3253 return static_cast<u32>(40471.902f);
3254 default:
3255 LOG_ERROR(Service_Audio, "Invalid channel count {}",
3256 command.parameter.channel_count);
3257 return 0;
3258 }
3259 }
3260 switch (command.parameter.channel_count) {
3261 case 1:
3262 return static_cast<u32>(897.004f);
3263 case 2:
3264 return static_cast<u32>(931.549f);
3265 case 4:
3266 return static_cast<u32>(975.387f);
3267 case 6:
3268 return static_cast<u32>(1016.778f);
3269 default:
3270 LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
3271 return 0;
3272 }
3273 case 240:
3274 if (command.enabled) {
3275 switch (command.parameter.channel_count) {
3276 case 1:
3277 return static_cast<u32>(30565.961f);
3278 case 2:
3279 return static_cast<u32>(32812.91f);
3280 case 4:
3281 return static_cast<u32>(37354.852f);
3282 case 6:
3283 return static_cast<u32>(58486.699f);
3284 default:
3285 LOG_ERROR(Service_Audio, "Invalid channel count {}",
3286 command.parameter.channel_count);
3287 return 0;
3288 }
3289 }
3290 switch (command.parameter.channel_count) {
3291 case 1:
3292 return static_cast<u32>(874.429f);
3293 case 2:
3294 return static_cast<u32>(921.553f);
3295 case 4:
3296 return static_cast<u32>(945.262f);
3297 case 6:
3298 return static_cast<u32>(992.26f);
3299 default:
3300 LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
3301 return 0;
3302 }
3303 default:
3304 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
3305 return 0;
3306 }
3307}
3308
3309u32 CommandProcessingTimeEstimatorVersion5::Estimate(
3310 const LightLimiterVersion2Command& command) const {
3311 switch (sample_count) {
3312 case 160:
3313 if (command.enabled) {
3314 if (command.parameter.processing_mode == LightLimiterInfo::ProcessingMode::Mode0) {
3315 if (command.parameter.statistics_enabled) {
3316 switch (command.parameter.channel_count) {
3317 case 1:
3318 return static_cast<u32>(23639.584f);
3319 case 2:
3320 return static_cast<u32>(24666.725f);
3321 case 4:
3322 return static_cast<u32>(28876.459f);
3323 case 6:
3324 return static_cast<u32>(47096.078f);
3325 default:
3326 LOG_ERROR(Service_Audio, "Invalid channel count {}",
3327 command.parameter.channel_count);
3328 return 0;
3329 }
3330 } else {
3331 if (command.parameter.statistics_enabled) {
3332 switch (command.parameter.channel_count) {
3333 case 1:
3334 return static_cast<u32>(21508.01f);
3335 case 2:
3336 return static_cast<u32>(23120.453f);
3337 case 4:
3338 return static_cast<u32>(26270.053f);
3339 case 6:
3340 return static_cast<u32>(40471.902f);
3341 default:
3342 LOG_ERROR(Service_Audio, "Invalid channel count {}",
3343 command.parameter.channel_count);
3344 return 0;
3345 }
3346 }
3347 }
3348 } else if (command.parameter.processing_mode ==
3349 LightLimiterInfo::ProcessingMode::Mode1) {
3350 if (command.parameter.statistics_enabled) {
3351 switch (command.parameter.channel_count) {
3352 case 1:
3353 return static_cast<u32>(23639.584f);
3354 case 2:
3355 return static_cast<u32>(29954.062f);
3356 case 4:
3357 return static_cast<u32>(35807.477f);
3358 case 6:
3359 return static_cast<u32>(58339.773f);
3360 default:
3361 LOG_ERROR(Service_Audio, "Invalid channel count {}",
3362 command.parameter.channel_count);
3363 return 0;
3364 }
3365 } else {
3366 if (command.parameter.statistics_enabled) {
3367 switch (command.parameter.channel_count) {
3368 case 1:
3369 return static_cast<u32>(23639.584f);
3370 case 2:
3371 return static_cast<u32>(29954.062f);
3372 case 4:
3373 return static_cast<u32>(35807.477f);
3374 case 6:
3375 return static_cast<u32>(58339.773f);
3376 default:
3377 LOG_ERROR(Service_Audio, "Invalid channel count {}",
3378 command.parameter.channel_count);
3379 return 0;
3380 }
3381 }
3382 }
3383 } else {
3384 LOG_ERROR(Service_Audio, "Invalid processing mode {}",
3385 command.parameter.processing_mode);
3386 return 0;
3387 }
3388 }
3389 switch (command.parameter.channel_count) {
3390 case 1:
3391 return static_cast<u32>(897.004f);
3392 case 2:
3393 return static_cast<u32>(931.549f);
3394 case 4:
3395 return static_cast<u32>(975.387f);
3396 case 6:
3397 return static_cast<u32>(1016.778f);
3398 default:
3399 LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
3400 return 0;
3401 }
3402 case 240:
3403 if (command.enabled) {
3404 if (command.parameter.processing_mode == LightLimiterInfo::ProcessingMode::Mode0) {
3405 if (command.parameter.statistics_enabled) {
3406 switch (command.parameter.channel_count) {
3407 case 1:
3408 return static_cast<u32>(33875.023f);
3409 case 2:
3410 return static_cast<u32>(35199.938f);
3411 case 4:
3412 return static_cast<u32>(41371.230f);
3413 case 6:
3414 return static_cast<u32>(68370.914f);
3415 default:
3416 LOG_ERROR(Service_Audio, "Invalid channel count {}",
3417 command.parameter.channel_count);
3418 return 0;
3419 }
3420 } else {
3421 switch (command.parameter.channel_count) {
3422 case 1:
3423 return static_cast<u32>(30565.961f);
3424 case 2:
3425 return static_cast<u32>(32812.91f);
3426 case 4:
3427 return static_cast<u32>(37354.852f);
3428 case 6:
3429 return static_cast<u32>(58486.699f);
3430 default:
3431 LOG_ERROR(Service_Audio, "Invalid channel count {}",
3432 command.parameter.channel_count);
3433 return 0;
3434 }
3435 }
3436 } else if (command.parameter.processing_mode ==
3437 LightLimiterInfo::ProcessingMode::Mode1) {
3438 if (command.parameter.statistics_enabled) {
3439 switch (command.parameter.channel_count) {
3440 case 1:
3441 return static_cast<u32>(33942.980f);
3442 case 2:
3443 return static_cast<u32>(28698.893f);
3444 case 4:
3445 return static_cast<u32>(34774.277f);
3446 case 6:
3447 return static_cast<u32>(61897.773f);
3448 default:
3449 LOG_ERROR(Service_Audio, "Invalid channel count {}",
3450 command.parameter.channel_count);
3451 return 0;
3452 }
3453 } else {
3454 switch (command.parameter.channel_count) {
3455 case 1:
3456 return static_cast<u32>(30610.248f);
3457 case 2:
3458 return static_cast<u32>(26322.408f);
3459 case 4:
3460 return static_cast<u32>(30369.000f);
3461 case 6:
3462 return static_cast<u32>(51892.090f);
3463 default:
3464 LOG_ERROR(Service_Audio, "Invalid channel count {}",
3465 command.parameter.channel_count);
3466 return 0;
3467 }
3468 }
3469 } else {
3470 LOG_ERROR(Service_Audio, "Invalid processing mode {}",
3471 command.parameter.processing_mode);
3472 return 0;
3473 }
3474 }
3475 switch (command.parameter.channel_count) {
3476 case 1:
3477 return static_cast<u32>(874.429f);
3478 case 2:
3479 return static_cast<u32>(921.553f);
3480 case 4:
3481 return static_cast<u32>(945.262f);
3482 case 6:
3483 return static_cast<u32>(992.26f);
3484 default:
3485 LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
3486 return 0;
3487 }
3488 default:
3489 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
3490 return 0;
3491 }
3492}
3493
3494u32 CommandProcessingTimeEstimatorVersion5::Estimate(
3495 [[maybe_unused]] const MultiTapBiquadFilterCommand& command) const {
3496 switch (sample_count) {
3497 case 160:
3498 return static_cast<u32>(7424.5f);
3499 case 240:
3500 return static_cast<u32>(9730.4f);
3501 default:
3502 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
3503 return 0;
3504 }
3505}
3506
3507u32 CommandProcessingTimeEstimatorVersion5::Estimate(const CaptureCommand& command) const {
3508 switch (sample_count) {
3509 case 160:
3510 if (command.enabled) {
3511 return static_cast<u32>(426.982f);
3512 }
3513 return static_cast<u32>(4261.005f);
3514 case 240:
3515 if (command.enabled) {
3516 return static_cast<u32>(435.204f);
3517 }
3518 return static_cast<u32>(5858.265f);
3519 default:
3520 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
3521 return 0;
3522 }
3523}
3524
3525u32 CommandProcessingTimeEstimatorVersion5::Estimate(const CompressorCommand& command) const {
3526 if (command.enabled) {
3527 switch (command.parameter.channel_count) {
3528 case 1:
3529 switch (sample_count) {
3530 case 160:
3531 return static_cast<u32>(34430.570f);
3532 case 240:
3533 return static_cast<u32>(51095.348f);
3534 default:
3535 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
3536 return 0;
3537 }
3538 case 2:
3539 switch (sample_count) {
3540 case 160:
3541 return static_cast<u32>(44253.320f);
3542 case 240:
3543 return static_cast<u32>(65693.094f);
3544 default:
3545 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
3546 return 0;
3547 }
3548 case 4:
3549 switch (sample_count) {
3550 case 160:
3551 return static_cast<u32>(63827.457f);
3552 case 240:
3553 return static_cast<u32>(95382.852f);
3554 default:
3555 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
3556 return 0;
3557 }
3558 case 6:
3559 switch (sample_count) {
3560 case 160:
3561 return static_cast<u32>(83361.484f);
3562 case 240:
3563 return static_cast<u32>(124509.906f);
3564 default:
3565 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
3566 return 0;
3567 }
3568 default:
3569 LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
3570 return 0;
3571 }
3572 }
3573 switch (command.parameter.channel_count) {
3574 case 1:
3575 switch (sample_count) {
3576 case 160:
3577 return static_cast<u32>(630.115f);
3578 case 240:
3579 return static_cast<u32>(840.136f);
3580 default:
3581 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
3582 return 0;
3583 }
3584 case 2:
3585 switch (sample_count) {
3586 case 160:
3587 return static_cast<u32>(638.274f);
3588 case 240:
3589 return static_cast<u32>(826.098f);
3590 default:
3591 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
3592 return 0;
3593 }
3594 case 4:
3595 switch (sample_count) {
3596 case 160:
3597 return static_cast<u32>(705.862f);
3598 case 240:
3599 return static_cast<u32>(901.876f);
3600 default:
3601 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
3602 return 0;
3603 }
3604 case 6:
3605 switch (sample_count) {
3606 case 160:
3607 return static_cast<u32>(782.019f);
3608 case 240:
3609 return static_cast<u32>(965.286f);
3610 default:
3611 LOG_ERROR(Service_Audio, "Invalid sample count {}", sample_count);
3612 return 0;
3613 }
3614 default:
3615 LOG_ERROR(Service_Audio, "Invalid channel count {}", command.parameter.channel_count);
3616 return 0;
3617 }
3618}
3619
3620} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/command_processing_time_estimator.h b/src/audio_core/renderer/command/command_processing_time_estimator.h
new file mode 100644
index 000000000..452217196
--- /dev/null
+++ b/src/audio_core/renderer/command/command_processing_time_estimator.h
@@ -0,0 +1,254 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "audio_core/renderer/command/commands.h"
7#include "common/common_types.h"
8
9namespace AudioCore::AudioRenderer {
10/**
11 * Estimate the processing time required for all commands.
12 */
13class ICommandProcessingTimeEstimator {
14public:
15 virtual ~ICommandProcessingTimeEstimator() = default;
16
17 virtual u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const = 0;
18 virtual u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const = 0;
19 virtual u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const = 0;
20 virtual u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const = 0;
21 virtual u32 Estimate(const AdpcmDataSourceVersion1Command& command) const = 0;
22 virtual u32 Estimate(const AdpcmDataSourceVersion2Command& command) const = 0;
23 virtual u32 Estimate(const VolumeCommand& command) const = 0;
24 virtual u32 Estimate(const VolumeRampCommand& command) const = 0;
25 virtual u32 Estimate(const BiquadFilterCommand& command) const = 0;
26 virtual u32 Estimate(const MixCommand& command) const = 0;
27 virtual u32 Estimate(const MixRampCommand& command) const = 0;
28 virtual u32 Estimate(const MixRampGroupedCommand& command) const = 0;
29 virtual u32 Estimate(const DepopPrepareCommand& command) const = 0;
30 virtual u32 Estimate(const DepopForMixBuffersCommand& command) const = 0;
31 virtual u32 Estimate(const DelayCommand& command) const = 0;
32 virtual u32 Estimate(const UpsampleCommand& command) const = 0;
33 virtual u32 Estimate(const DownMix6chTo2chCommand& command) const = 0;
34 virtual u32 Estimate(const AuxCommand& command) const = 0;
35 virtual u32 Estimate(const DeviceSinkCommand& command) const = 0;
36 virtual u32 Estimate(const CircularBufferSinkCommand& command) const = 0;
37 virtual u32 Estimate(const ReverbCommand& command) const = 0;
38 virtual u32 Estimate(const I3dl2ReverbCommand& command) const = 0;
39 virtual u32 Estimate(const PerformanceCommand& command) const = 0;
40 virtual u32 Estimate(const ClearMixBufferCommand& command) const = 0;
41 virtual u32 Estimate(const CopyMixBufferCommand& command) const = 0;
42 virtual u32 Estimate(const LightLimiterVersion1Command& command) const = 0;
43 virtual u32 Estimate(const LightLimiterVersion2Command& command) const = 0;
44 virtual u32 Estimate(const MultiTapBiquadFilterCommand& command) const = 0;
45 virtual u32 Estimate(const CaptureCommand& command) const = 0;
46 virtual u32 Estimate(const CompressorCommand& command) const = 0;
47};
48
49class CommandProcessingTimeEstimatorVersion1 final : public ICommandProcessingTimeEstimator {
50public:
51 CommandProcessingTimeEstimatorVersion1(u32 sample_count_, u32 buffer_count_)
52 : sample_count{sample_count_}, buffer_count{buffer_count_} {}
53
54 u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const override;
55 u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const override;
56 u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const override;
57 u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const override;
58 u32 Estimate(const AdpcmDataSourceVersion1Command& command) const override;
59 u32 Estimate(const AdpcmDataSourceVersion2Command& command) const override;
60 u32 Estimate(const VolumeCommand& command) const override;
61 u32 Estimate(const VolumeRampCommand& command) const override;
62 u32 Estimate(const BiquadFilterCommand& command) const override;
63 u32 Estimate(const MixCommand& command) const override;
64 u32 Estimate(const MixRampCommand& command) const override;
65 u32 Estimate(const MixRampGroupedCommand& command) const override;
66 u32 Estimate(const DepopPrepareCommand& command) const override;
67 u32 Estimate(const DepopForMixBuffersCommand& command) const override;
68 u32 Estimate(const DelayCommand& command) const override;
69 u32 Estimate(const UpsampleCommand& command) const override;
70 u32 Estimate(const DownMix6chTo2chCommand& command) const override;
71 u32 Estimate(const AuxCommand& command) const override;
72 u32 Estimate(const DeviceSinkCommand& command) const override;
73 u32 Estimate(const CircularBufferSinkCommand& command) const override;
74 u32 Estimate(const ReverbCommand& command) const override;
75 u32 Estimate(const I3dl2ReverbCommand& command) const override;
76 u32 Estimate(const PerformanceCommand& command) const override;
77 u32 Estimate(const ClearMixBufferCommand& command) const override;
78 u32 Estimate(const CopyMixBufferCommand& command) const override;
79 u32 Estimate(const LightLimiterVersion1Command& command) const override;
80 u32 Estimate(const LightLimiterVersion2Command& command) const override;
81 u32 Estimate(const MultiTapBiquadFilterCommand& command) const override;
82 u32 Estimate(const CaptureCommand& command) const override;
83 u32 Estimate(const CompressorCommand& command) const override;
84
85private:
86 u32 sample_count{};
87 u32 buffer_count{};
88};
89
90class CommandProcessingTimeEstimatorVersion2 final : public ICommandProcessingTimeEstimator {
91public:
92 CommandProcessingTimeEstimatorVersion2(u32 sample_count_, u32 buffer_count_)
93 : sample_count{sample_count_}, buffer_count{buffer_count_} {}
94
95 u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const override;
96 u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const override;
97 u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const override;
98 u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const override;
99 u32 Estimate(const AdpcmDataSourceVersion1Command& command) const override;
100 u32 Estimate(const AdpcmDataSourceVersion2Command& command) const override;
101 u32 Estimate(const VolumeCommand& command) const override;
102 u32 Estimate(const VolumeRampCommand& command) const override;
103 u32 Estimate(const BiquadFilterCommand& command) const override;
104 u32 Estimate(const MixCommand& command) const override;
105 u32 Estimate(const MixRampCommand& command) const override;
106 u32 Estimate(const MixRampGroupedCommand& command) const override;
107 u32 Estimate(const DepopPrepareCommand& command) const override;
108 u32 Estimate(const DepopForMixBuffersCommand& command) const override;
109 u32 Estimate(const DelayCommand& command) const override;
110 u32 Estimate(const UpsampleCommand& command) const override;
111 u32 Estimate(const DownMix6chTo2chCommand& command) const override;
112 u32 Estimate(const AuxCommand& command) const override;
113 u32 Estimate(const DeviceSinkCommand& command) const override;
114 u32 Estimate(const CircularBufferSinkCommand& command) const override;
115 u32 Estimate(const ReverbCommand& command) const override;
116 u32 Estimate(const I3dl2ReverbCommand& command) const override;
117 u32 Estimate(const PerformanceCommand& command) const override;
118 u32 Estimate(const ClearMixBufferCommand& command) const override;
119 u32 Estimate(const CopyMixBufferCommand& command) const override;
120 u32 Estimate(const LightLimiterVersion1Command& command) const override;
121 u32 Estimate(const LightLimiterVersion2Command& command) const override;
122 u32 Estimate(const MultiTapBiquadFilterCommand& command) const override;
123 u32 Estimate(const CaptureCommand& command) const override;
124 u32 Estimate(const CompressorCommand& command) const override;
125
126private:
127 u32 sample_count{};
128 u32 buffer_count{};
129};
130
131class CommandProcessingTimeEstimatorVersion3 final : public ICommandProcessingTimeEstimator {
132public:
133 CommandProcessingTimeEstimatorVersion3(u32 sample_count_, u32 buffer_count_)
134 : sample_count{sample_count_}, buffer_count{buffer_count_} {}
135
136 u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const override;
137 u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const override;
138 u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const override;
139 u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const override;
140 u32 Estimate(const AdpcmDataSourceVersion1Command& command) const override;
141 u32 Estimate(const AdpcmDataSourceVersion2Command& command) const override;
142 u32 Estimate(const VolumeCommand& command) const override;
143 u32 Estimate(const VolumeRampCommand& command) const override;
144 u32 Estimate(const BiquadFilterCommand& command) const override;
145 u32 Estimate(const MixCommand& command) const override;
146 u32 Estimate(const MixRampCommand& command) const override;
147 u32 Estimate(const MixRampGroupedCommand& command) const override;
148 u32 Estimate(const DepopPrepareCommand& command) const override;
149 u32 Estimate(const DepopForMixBuffersCommand& command) const override;
150 u32 Estimate(const DelayCommand& command) const override;
151 u32 Estimate(const UpsampleCommand& command) const override;
152 u32 Estimate(const DownMix6chTo2chCommand& command) const override;
153 u32 Estimate(const AuxCommand& command) const override;
154 u32 Estimate(const DeviceSinkCommand& command) const override;
155 u32 Estimate(const CircularBufferSinkCommand& command) const override;
156 u32 Estimate(const ReverbCommand& command) const override;
157 u32 Estimate(const I3dl2ReverbCommand& command) const override;
158 u32 Estimate(const PerformanceCommand& command) const override;
159 u32 Estimate(const ClearMixBufferCommand& command) const override;
160 u32 Estimate(const CopyMixBufferCommand& command) const override;
161 u32 Estimate(const LightLimiterVersion1Command& command) const override;
162 u32 Estimate(const LightLimiterVersion2Command& command) const override;
163 u32 Estimate(const MultiTapBiquadFilterCommand& command) const override;
164 u32 Estimate(const CaptureCommand& command) const override;
165 u32 Estimate(const CompressorCommand& command) const override;
166
167private:
168 u32 sample_count{};
169 u32 buffer_count{};
170};
171
172class CommandProcessingTimeEstimatorVersion4 final : public ICommandProcessingTimeEstimator {
173public:
174 CommandProcessingTimeEstimatorVersion4(u32 sample_count_, u32 buffer_count_)
175 : sample_count{sample_count_}, buffer_count{buffer_count_} {}
176
177 u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const override;
178 u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const override;
179 u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const override;
180 u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const override;
181 u32 Estimate(const AdpcmDataSourceVersion1Command& command) const override;
182 u32 Estimate(const AdpcmDataSourceVersion2Command& command) const override;
183 u32 Estimate(const VolumeCommand& command) const override;
184 u32 Estimate(const VolumeRampCommand& command) const override;
185 u32 Estimate(const BiquadFilterCommand& command) const override;
186 u32 Estimate(const MixCommand& command) const override;
187 u32 Estimate(const MixRampCommand& command) const override;
188 u32 Estimate(const MixRampGroupedCommand& command) const override;
189 u32 Estimate(const DepopPrepareCommand& command) const override;
190 u32 Estimate(const DepopForMixBuffersCommand& command) const override;
191 u32 Estimate(const DelayCommand& command) const override;
192 u32 Estimate(const UpsampleCommand& command) const override;
193 u32 Estimate(const DownMix6chTo2chCommand& command) const override;
194 u32 Estimate(const AuxCommand& command) const override;
195 u32 Estimate(const DeviceSinkCommand& command) const override;
196 u32 Estimate(const CircularBufferSinkCommand& command) const override;
197 u32 Estimate(const ReverbCommand& command) const override;
198 u32 Estimate(const I3dl2ReverbCommand& command) const override;
199 u32 Estimate(const PerformanceCommand& command) const override;
200 u32 Estimate(const ClearMixBufferCommand& command) const override;
201 u32 Estimate(const CopyMixBufferCommand& command) const override;
202 u32 Estimate(const LightLimiterVersion1Command& command) const override;
203 u32 Estimate(const LightLimiterVersion2Command& command) const override;
204 u32 Estimate(const MultiTapBiquadFilterCommand& command) const override;
205 u32 Estimate(const CaptureCommand& command) const override;
206 u32 Estimate(const CompressorCommand& command) const override;
207
208private:
209 u32 sample_count{};
210 u32 buffer_count{};
211};
212
213class CommandProcessingTimeEstimatorVersion5 final : public ICommandProcessingTimeEstimator {
214public:
215 CommandProcessingTimeEstimatorVersion5(u32 sample_count_, u32 buffer_count_)
216 : sample_count{sample_count_}, buffer_count{buffer_count_} {}
217
218 u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const override;
219 u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const override;
220 u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const override;
221 u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const override;
222 u32 Estimate(const AdpcmDataSourceVersion1Command& command) const override;
223 u32 Estimate(const AdpcmDataSourceVersion2Command& command) const override;
224 u32 Estimate(const VolumeCommand& command) const override;
225 u32 Estimate(const VolumeRampCommand& command) const override;
226 u32 Estimate(const BiquadFilterCommand& command) const override;
227 u32 Estimate(const MixCommand& command) const override;
228 u32 Estimate(const MixRampCommand& command) const override;
229 u32 Estimate(const MixRampGroupedCommand& command) const override;
230 u32 Estimate(const DepopPrepareCommand& command) const override;
231 u32 Estimate(const DepopForMixBuffersCommand& command) const override;
232 u32 Estimate(const DelayCommand& command) const override;
233 u32 Estimate(const UpsampleCommand& command) const override;
234 u32 Estimate(const DownMix6chTo2chCommand& command) const override;
235 u32 Estimate(const AuxCommand& command) const override;
236 u32 Estimate(const DeviceSinkCommand& command) const override;
237 u32 Estimate(const CircularBufferSinkCommand& command) const override;
238 u32 Estimate(const ReverbCommand& command) const override;
239 u32 Estimate(const I3dl2ReverbCommand& command) const override;
240 u32 Estimate(const PerformanceCommand& command) const override;
241 u32 Estimate(const ClearMixBufferCommand& command) const override;
242 u32 Estimate(const CopyMixBufferCommand& command) const override;
243 u32 Estimate(const LightLimiterVersion1Command& command) const override;
244 u32 Estimate(const LightLimiterVersion2Command& command) const override;
245 u32 Estimate(const MultiTapBiquadFilterCommand& command) const override;
246 u32 Estimate(const CaptureCommand& command) const override;
247 u32 Estimate(const CompressorCommand& command) const override;
248
249private:
250 u32 sample_count{};
251 u32 buffer_count{};
252};
253
254} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/commands.h b/src/audio_core/renderer/command/commands.h
new file mode 100644
index 000000000..6d8b8546d
--- /dev/null
+++ b/src/audio_core/renderer/command/commands.h
@@ -0,0 +1,32 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "audio_core/renderer/command/data_source/adpcm.h"
7#include "audio_core/renderer/command/data_source/pcm_float.h"
8#include "audio_core/renderer/command/data_source/pcm_int16.h"
9#include "audio_core/renderer/command/effect/aux_.h"
10#include "audio_core/renderer/command/effect/biquad_filter.h"
11#include "audio_core/renderer/command/effect/capture.h"
12#include "audio_core/renderer/command/effect/compressor.h"
13#include "audio_core/renderer/command/effect/delay.h"
14#include "audio_core/renderer/command/effect/i3dl2_reverb.h"
15#include "audio_core/renderer/command/effect/light_limiter.h"
16#include "audio_core/renderer/command/effect/multi_tap_biquad_filter.h"
17#include "audio_core/renderer/command/effect/reverb.h"
18#include "audio_core/renderer/command/icommand.h"
19#include "audio_core/renderer/command/mix/clear_mix.h"
20#include "audio_core/renderer/command/mix/copy_mix.h"
21#include "audio_core/renderer/command/mix/depop_for_mix_buffers.h"
22#include "audio_core/renderer/command/mix/depop_prepare.h"
23#include "audio_core/renderer/command/mix/mix.h"
24#include "audio_core/renderer/command/mix/mix_ramp.h"
25#include "audio_core/renderer/command/mix/mix_ramp_grouped.h"
26#include "audio_core/renderer/command/mix/volume.h"
27#include "audio_core/renderer/command/mix/volume_ramp.h"
28#include "audio_core/renderer/command/performance/performance.h"
29#include "audio_core/renderer/command/resample/downmix_6ch_to_2ch.h"
30#include "audio_core/renderer/command/resample/upsample.h"
31#include "audio_core/renderer/command/sink/circular_buffer.h"
32#include "audio_core/renderer/command/sink/device.h"
diff --git a/src/audio_core/renderer/command/data_source/adpcm.cpp b/src/audio_core/renderer/command/data_source/adpcm.cpp
new file mode 100644
index 000000000..e66ed2990
--- /dev/null
+++ b/src/audio_core/renderer/command/data_source/adpcm.cpp
@@ -0,0 +1,84 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <span>
5
6#include "audio_core/renderer/adsp/command_list_processor.h"
7#include "audio_core/renderer/command/data_source/adpcm.h"
8#include "audio_core/renderer/command/data_source/decode.h"
9
10namespace AudioCore::AudioRenderer {
11
12void AdpcmDataSourceVersion1Command::Dump(const ADSP::CommandListProcessor& processor,
13 std::string& string) {
14 string += fmt::format("AdpcmDataSourceVersion1Command\n\toutput_index {:02X} source sample "
15 "rate {} target sample rate {} src quality {}\n",
16 output_index, sample_rate, processor.target_sample_rate, src_quality);
17}
18
19void AdpcmDataSourceVersion1Command::Process(const ADSP::CommandListProcessor& processor) {
20 auto out_buffer{processor.mix_buffers.subspan(output_index * processor.sample_count,
21 processor.sample_count)};
22
23 DecodeFromWaveBuffersArgs args{
24 .sample_format{SampleFormat::Adpcm},
25 .output{out_buffer},
26 .voice_state{reinterpret_cast<VoiceState*>(voice_state)},
27 .wave_buffers{wave_buffers},
28 .channel{0},
29 .channel_count{1},
30 .src_quality{src_quality},
31 .pitch{pitch},
32 .source_sample_rate{sample_rate},
33 .target_sample_rate{processor.target_sample_rate},
34 .sample_count{processor.sample_count},
35 .data_address{data_address},
36 .data_size{data_size},
37 .IsVoicePlayedSampleCountResetAtLoopPointSupported{(flags & 1) != 0},
38 .IsVoicePitchAndSrcSkippedSupported{(flags & 2) != 0},
39 };
40
41 DecodeFromWaveBuffers(*processor.memory, args);
42}
43
44bool AdpcmDataSourceVersion1Command::Verify(const ADSP::CommandListProcessor& processor) {
45 return true;
46}
47
48void AdpcmDataSourceVersion2Command::Dump(const ADSP::CommandListProcessor& processor,
49 std::string& string) {
50 string += fmt::format("AdpcmDataSourceVersion2Command\n\toutput_index {:02X} source sample "
51 "rate {} target sample rate {} src quality {}\n",
52 output_index, sample_rate, processor.target_sample_rate, src_quality);
53}
54
55void AdpcmDataSourceVersion2Command::Process(const ADSP::CommandListProcessor& processor) {
56 auto out_buffer{processor.mix_buffers.subspan(output_index * processor.sample_count,
57 processor.sample_count)};
58
59 DecodeFromWaveBuffersArgs args{
60 .sample_format{SampleFormat::Adpcm},
61 .output{out_buffer},
62 .voice_state{reinterpret_cast<VoiceState*>(voice_state)},
63 .wave_buffers{wave_buffers},
64 .channel{0},
65 .channel_count{1},
66 .src_quality{src_quality},
67 .pitch{pitch},
68 .source_sample_rate{sample_rate},
69 .target_sample_rate{processor.target_sample_rate},
70 .sample_count{processor.sample_count},
71 .data_address{data_address},
72 .data_size{data_size},
73 .IsVoicePlayedSampleCountResetAtLoopPointSupported{(flags & 1) != 0},
74 .IsVoicePitchAndSrcSkippedSupported{(flags & 2) != 0},
75 };
76
77 DecodeFromWaveBuffers(*processor.memory, args);
78}
79
80bool AdpcmDataSourceVersion2Command::Verify(const ADSP::CommandListProcessor& processor) {
81 return true;
82}
83
84} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/data_source/adpcm.h b/src/audio_core/renderer/command/data_source/adpcm.h
new file mode 100644
index 000000000..a9cf9cee4
--- /dev/null
+++ b/src/audio_core/renderer/command/data_source/adpcm.h
@@ -0,0 +1,119 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7#include <string>
8
9#include "audio_core/common/common.h"
10#include "audio_core/common/wave_buffer.h"
11#include "audio_core/renderer/command/icommand.h"
12#include "common/common_types.h"
13
14namespace AudioCore::AudioRenderer {
15namespace ADSP {
16class CommandListProcessor;
17}
18
19/**
20 * AudioRenderer command to decode ADPCM-encoded version 1 wavebuffers
21 * into the output_index mix buffer.
22 */
23struct AdpcmDataSourceVersion1Command : ICommand {
24 /**
25 * Print this command's information to a string.
26 *
27 * @param processor - The CommandListProcessor processing this command.
28 * @param string - The string to print into.
29 */
30 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
31
32 /**
33 * Process this command.
34 *
35 * @param processor - The CommandListProcessor processing this command.
36 */
37 void Process(const ADSP::CommandListProcessor& processor) override;
38
39 /**
40 * Verify this command's data is valid.
41 *
42 * @param processor - The CommandListProcessor processing this command.
43 * @return True if the command is valid, otherwise false.
44 */
45 bool Verify(const ADSP::CommandListProcessor& processor) override;
46
47 /// Quality used for sample rate conversion
48 SrcQuality src_quality;
49 /// Mix buffer index for decoded samples
50 s16 output_index;
51 /// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags)
52 u16 flags;
53 /// Wavebuffer sample rate
54 u32 sample_rate;
55 /// Pitch used for sample rate conversion
56 f32 pitch;
57 /// Wavebuffers containing the wavebuffer address, context address, looping information etc
58 std::array<WaveBufferVersion2, MaxWaveBuffers> wave_buffers;
59 /// Voice state, updated each call and written back to game
60 CpuAddr voice_state;
61 /// Coefficients data address
62 CpuAddr data_address;
63 /// Coefficients data size
64 u64 data_size;
65};
66
67/**
68 * AudioRenderer command to decode ADPCM-encoded version 2 wavebuffers
69 * into the output_index mix buffer.
70 */
71struct AdpcmDataSourceVersion2Command : ICommand {
72 /**
73 * Print this command's information to a string.
74 *
75 * @param processor - The CommandListProcessor processing this command.
76 * @param string - The string to print into.
77 */
78 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
79
80 /**
81 * Process this command.
82 *
83 * @param processor - The CommandListProcessor processing this command.
84 */
85 void Process(const ADSP::CommandListProcessor& processor) override;
86
87 /**
88 * Verify this command's data is valid.
89 *
90 * @param processor - The CommandListProcessor processing this command.
91 * @return True if the command is valid, otherwise false.
92 */
93 bool Verify(const ADSP::CommandListProcessor& processor) override;
94
95 /// Quality used for sample rate conversion
96 SrcQuality src_quality;
97 /// Mix buffer index for decoded samples
98 s16 output_index;
99 /// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags)
100 u16 flags;
101 /// Wavebuffer sample rate
102 u32 sample_rate;
103 /// Pitch used for sample rate conversion
104 f32 pitch;
105 /// Target channel to read within the wavebuffer
106 s8 channel_index;
107 /// Number of channels within the wavebuffer
108 s8 channel_count;
109 /// Wavebuffers containing the wavebuffer address, context address, looping information etc
110 std::array<WaveBufferVersion2, MaxWaveBuffers> wave_buffers;
111 /// Voice state, updated each call and written back to game
112 CpuAddr voice_state;
113 /// Coefficients data address
114 CpuAddr data_address;
115 /// Coefficients data size
116 u64 data_size;
117};
118
119} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/data_source/decode.cpp b/src/audio_core/renderer/command/data_source/decode.cpp
new file mode 100644
index 000000000..ff5d31bd6
--- /dev/null
+++ b/src/audio_core/renderer/command/data_source/decode.cpp
@@ -0,0 +1,428 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <array>
5#include <vector>
6
7#include "audio_core/renderer/command/data_source/decode.h"
8#include "audio_core/renderer/command/resample/resample.h"
9#include "common/fixed_point.h"
10#include "common/logging/log.h"
11#include "core/memory.h"
12
13namespace AudioCore::AudioRenderer {
14
15constexpr u32 TempBufferSize = 0x3F00;
16constexpr std::array<u8, 3> PitchBySrcQuality = {4, 8, 4};
17
18/**
19 * Decode PCM data. Only s16 or f32 is supported.
20 *
21 * @tparam T - Type to decode. Only s16 and f32 are supported.
22 * @param memory - Core memory for reading samples.
23 * @param out_buffer - Output mix buffer to receive the samples.
24 * @param req - Information for how to decode.
25 * @return Number of samples decoded.
26 */
27template <typename T>
28static u32 DecodePcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
29 const DecodeArg& req) {
30 constexpr s32 min{std::numeric_limits<s16>::min()};
31 constexpr s32 max{std::numeric_limits<s16>::max()};
32
33 if (req.buffer == 0 || req.buffer_size == 0) {
34 return 0;
35 }
36
37 if (req.start_offset >= req.end_offset) {
38 return 0;
39 }
40
41 auto samples_to_decode{
42 std::min(req.samples_to_read, req.end_offset - req.start_offset - req.offset)};
43 u32 channel_count{static_cast<u32>(req.channel_count)};
44
45 switch (req.channel_count) {
46 default: {
47 const VAddr source{req.buffer +
48 (((req.start_offset + req.offset) * channel_count) * sizeof(T))};
49 const u64 size{channel_count * samples_to_decode};
50 const u64 size_bytes{size * sizeof(T)};
51
52 std::vector<T> samples(size);
53 memory.ReadBlockUnsafe(source, samples.data(), size_bytes);
54
55 if constexpr (std::is_floating_point_v<T>) {
56 for (u32 i = 0; i < samples_to_decode; i++) {
57 auto sample{static_cast<s32>(samples[i * channel_count + req.target_channel] *
58 std::numeric_limits<s16>::max())};
59 out_buffer[i] = static_cast<s16>(std::clamp(sample, min, max));
60 }
61 } else {
62 for (u32 i = 0; i < samples_to_decode; i++) {
63 out_buffer[i] = samples[i * channel_count + req.target_channel];
64 }
65 }
66 } break;
67
68 case 1:
69 if (req.target_channel != 0) {
70 LOG_ERROR(Service_Audio, "Invalid target channel, expected 0, got {}",
71 req.target_channel);
72 return 0;
73 }
74
75 const VAddr source{req.buffer + ((req.start_offset + req.offset) * sizeof(T))};
76 std::vector<T> samples(samples_to_decode);
77 memory.ReadBlockUnsafe(source, samples.data(), samples_to_decode * sizeof(T));
78
79 if constexpr (std::is_floating_point_v<T>) {
80 for (u32 i = 0; i < samples_to_decode; i++) {
81 auto sample{static_cast<s32>(samples[i * channel_count + req.target_channel] *
82 std::numeric_limits<s16>::max())};
83 out_buffer[i] = static_cast<s16>(std::clamp(sample, min, max));
84 }
85 } else {
86 std::memcpy(out_buffer.data(), samples.data(), samples_to_decode * sizeof(s16));
87 }
88 break;
89 }
90
91 return samples_to_decode;
92}
93
94/**
95 * Decode ADPCM data.
96 *
97 * @param memory - Core memory for reading samples.
98 * @param out_buffer - Output mix buffer to receive the samples.
99 * @param req - Information for how to decode.
100 * @return Number of samples decoded.
101 */
102static u32 DecodeAdpcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
103 const DecodeArg& req) {
104 constexpr u32 SamplesPerFrame{14};
105 constexpr u32 NibblesPerFrame{16};
106
107 if (req.buffer == 0 || req.buffer_size == 0) {
108 return 0;
109 }
110
111 if (req.end_offset < req.start_offset) {
112 return 0;
113 }
114
115 auto end{(req.end_offset % SamplesPerFrame) +
116 NibblesPerFrame * (req.end_offset / SamplesPerFrame)};
117 if (req.end_offset % SamplesPerFrame) {
118 end += 3;
119 } else {
120 end += 1;
121 }
122
123 if (req.buffer_size < end / 2) {
124 return 0;
125 }
126
127 auto samples_to_process{
128 std::min(req.end_offset - req.start_offset - req.offset, req.samples_to_read)};
129
130 auto samples_to_read{samples_to_process};
131 auto start_pos{req.start_offset + req.offset};
132 auto samples_remaining_in_frame{start_pos % SamplesPerFrame};
133 auto position_in_frame{(start_pos / SamplesPerFrame) * NibblesPerFrame +
134 samples_remaining_in_frame};
135
136 if (samples_remaining_in_frame) {
137 position_in_frame += 2;
138 }
139
140 const auto size{std::max((samples_to_process / 8U) * SamplesPerFrame, 8U)};
141 std::vector<u8> wavebuffer(size);
142 memory.ReadBlockUnsafe(req.buffer + position_in_frame / 2, wavebuffer.data(),
143 wavebuffer.size());
144
145 auto context{req.adpcm_context};
146 auto header{context->header};
147 u8 coeff_index{static_cast<u8>((header >> 4U) & 0xFU)};
148 u8 scale{static_cast<u8>(header & 0xFU)};
149 s32 coeff0{req.coefficients[coeff_index * 2 + 0]};
150 s32 coeff1{req.coefficients[coeff_index * 2 + 1]};
151
152 auto yn0{context->yn0};
153 auto yn1{context->yn1};
154
155 static constexpr std::array<s32, 16> Steps{
156 0, 1, 2, 3, 4, 5, 6, 7, -8, -7, -6, -5, -4, -3, -2, -1,
157 };
158
159 const auto decode_sample = [&](const s32 code) -> s16 {
160 const auto xn = code * (1 << scale);
161 const auto prediction = coeff0 * yn0 + coeff1 * yn1;
162 const auto sample = ((xn << 11) + 0x400 + prediction) >> 11;
163 const auto saturated = std::clamp<s32>(sample, -0x8000, 0x7FFF);
164 yn1 = yn0;
165 yn0 = static_cast<s16>(saturated);
166 return yn0;
167 };
168
169 u32 read_index{0};
170 u32 write_index{0};
171
172 while (samples_to_read > 0) {
173 // Are we at a new frame?
174 if ((position_in_frame % NibblesPerFrame) == 0) {
175 header = wavebuffer[read_index++];
176 coeff_index = (header >> 4) & 0xF;
177 scale = header & 0xF;
178 coeff0 = req.coefficients[coeff_index * 2 + 0];
179 coeff1 = req.coefficients[coeff_index * 2 + 1];
180 position_in_frame += 2;
181
182 // Can we consume all of this frame's samples?
183 if (samples_to_read >= SamplesPerFrame) {
184 // Can grab all samples until the next header
185 for (u32 i = 0; i < SamplesPerFrame / 2; i++) {
186 auto code0{Steps[(wavebuffer[read_index] >> 4) & 0xF]};
187 auto code1{Steps[wavebuffer[read_index] & 0xF]};
188 read_index++;
189
190 out_buffer[write_index++] = decode_sample(code0);
191 out_buffer[write_index++] = decode_sample(code1);
192 }
193
194 position_in_frame += SamplesPerFrame;
195 samples_to_read -= SamplesPerFrame;
196 continue;
197 }
198 }
199
200 // Decode a single sample
201 auto code{wavebuffer[read_index]};
202 if (position_in_frame & 1) {
203 code &= 0xF;
204 read_index++;
205 } else {
206 code >>= 4;
207 }
208
209 out_buffer[write_index++] = decode_sample(Steps[code]);
210
211 position_in_frame++;
212 samples_to_read--;
213 }
214
215 context->header = header;
216 context->yn0 = yn0;
217 context->yn1 = yn1;
218
219 return samples_to_process;
220}
221
222/**
223 * Decode implementation.
224 * Decode wavebuffers according to the given args.
225 *
226 * @param memory - Core memory to read data from.
227 * @param args - The wavebuffer data, and information for how to decode it.
228 */
229void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuffersArgs& args) {
230 auto& voice_state{*args.voice_state};
231 auto remaining_sample_count{args.sample_count};
232 auto fraction{voice_state.fraction};
233
234 const auto sample_rate_ratio{
235 (Common::FixedPoint<49, 15>(args.source_sample_rate) / args.target_sample_rate) *
236 args.pitch};
237 const auto size_required{fraction + remaining_sample_count * sample_rate_ratio};
238
239 if (size_required < 0) {
240 return;
241 }
242
243 auto pitch{PitchBySrcQuality[static_cast<u32>(args.src_quality)]};
244 if (static_cast<u32>(pitch + size_required.to_int_floor()) > TempBufferSize) {
245 return;
246 }
247
248 auto max_remaining_sample_count{
249 ((Common::FixedPoint<17, 15>(TempBufferSize) - fraction) / sample_rate_ratio)
250 .to_uint_floor()};
251 max_remaining_sample_count = std::min(max_remaining_sample_count, remaining_sample_count);
252
253 auto wavebuffers_consumed{voice_state.wave_buffers_consumed};
254 auto wavebuffer_index{voice_state.wave_buffer_index};
255 auto played_sample_count{voice_state.played_sample_count};
256
257 bool is_buffer_starved{false};
258 u32 offset{voice_state.offset};
259
260 auto output_buffer{args.output};
261 std::vector<s16> temp_buffer(TempBufferSize, 0);
262
263 while (remaining_sample_count > 0) {
264 const auto samples_to_write{std::min(remaining_sample_count, max_remaining_sample_count)};
265 const auto samples_to_read{
266 (fraction + samples_to_write * sample_rate_ratio).to_uint_floor()};
267
268 u32 temp_buffer_pos{0};
269
270 if (!args.IsVoicePitchAndSrcSkippedSupported) {
271 for (u32 i = 0; i < pitch; i++) {
272 temp_buffer[i] = voice_state.sample_history[i];
273 }
274 temp_buffer_pos = pitch;
275 }
276
277 u32 samples_read{0};
278 while (samples_read < samples_to_read) {
279 if (wavebuffer_index >= MaxWaveBuffers) {
280 LOG_ERROR(Service_Audio, "Invalid wavebuffer index! {}", wavebuffer_index);
281 wavebuffer_index = 0;
282 voice_state.wave_buffer_valid.fill(false);
283 wavebuffers_consumed = MaxWaveBuffers;
284 }
285
286 if (!voice_state.wave_buffer_valid[wavebuffer_index]) {
287 is_buffer_starved = true;
288 break;
289 }
290
291 auto& wavebuffer{args.wave_buffers[wavebuffer_index]};
292
293 if (offset == 0 && args.sample_format == SampleFormat::Adpcm &&
294 wavebuffer.context != 0) {
295 memory.ReadBlockUnsafe(wavebuffer.context, &voice_state.adpcm_context,
296 wavebuffer.context_size);
297 }
298
299 auto start_offset{wavebuffer.start_offset};
300 auto end_offset{wavebuffer.end_offset};
301
302 if (wavebuffer.loop && voice_state.loop_count > 0 &&
303 wavebuffer.loop_start_offset != 0 && wavebuffer.loop_end_offset != 0 &&
304 wavebuffer.loop_start_offset <= wavebuffer.loop_end_offset) {
305 start_offset = wavebuffer.loop_start_offset;
306 end_offset = wavebuffer.loop_end_offset;
307 }
308
309 DecodeArg decode_arg{.buffer{wavebuffer.buffer},
310 .buffer_size{wavebuffer.buffer_size},
311 .start_offset{start_offset},
312 .end_offset{end_offset},
313 .channel_count{args.channel_count},
314 .coefficients{},
315 .adpcm_context{nullptr},
316 .target_channel{args.channel},
317 .offset{offset},
318 .samples_to_read{samples_to_read - samples_read}};
319
320 s32 samples_decoded{0};
321
322 switch (args.sample_format) {
323 case SampleFormat::PcmInt16:
324 samples_decoded = DecodePcm<s16>(
325 memory, {&temp_buffer[temp_buffer_pos], TempBufferSize - temp_buffer_pos},
326 decode_arg);
327 break;
328
329 case SampleFormat::PcmFloat:
330 samples_decoded = DecodePcm<f32>(
331 memory, {&temp_buffer[temp_buffer_pos], TempBufferSize - temp_buffer_pos},
332 decode_arg);
333 break;
334
335 case SampleFormat::Adpcm: {
336 decode_arg.adpcm_context = &voice_state.adpcm_context;
337 memory.ReadBlockUnsafe(args.data_address, &decode_arg.coefficients, args.data_size);
338 samples_decoded = DecodeAdpcm(
339 memory, {&temp_buffer[temp_buffer_pos], TempBufferSize - temp_buffer_pos},
340 decode_arg);
341 } break;
342
343 default:
344 LOG_ERROR(Service_Audio, "Invalid sample format to decode {}",
345 static_cast<u32>(args.sample_format));
346 samples_decoded = 0;
347 break;
348 }
349
350 played_sample_count += samples_decoded;
351 samples_read += samples_decoded;
352 temp_buffer_pos += samples_decoded;
353 offset += samples_decoded;
354
355 if (samples_decoded == 0 || offset >= end_offset - start_offset) {
356 offset = 0;
357 if (!wavebuffer.loop) {
358 voice_state.wave_buffer_valid[wavebuffer_index] = false;
359 voice_state.loop_count = 0;
360
361 if (wavebuffer.stream_ended) {
362 played_sample_count = 0;
363 }
364
365 wavebuffer_index = (wavebuffer_index + 1) % MaxWaveBuffers;
366 wavebuffers_consumed++;
367 } else {
368 voice_state.loop_count++;
369 if (wavebuffer.loop_count > 0 &&
370 (voice_state.loop_count > wavebuffer.loop_count || samples_decoded == 0)) {
371 voice_state.wave_buffer_valid[wavebuffer_index] = false;
372 voice_state.loop_count = 0;
373
374 if (wavebuffer.stream_ended) {
375 played_sample_count = 0;
376 }
377
378 wavebuffer_index = (wavebuffer_index + 1) % MaxWaveBuffers;
379 wavebuffers_consumed++;
380 }
381
382 if (samples_decoded == 0) {
383 is_buffer_starved = true;
384 break;
385 }
386
387 if (args.IsVoicePlayedSampleCountResetAtLoopPointSupported) {
388 played_sample_count = 0;
389 }
390 }
391 }
392 }
393
394 if (args.IsVoicePitchAndSrcSkippedSupported) {
395 if (samples_read > output_buffer.size()) {
396 LOG_ERROR(Service_Audio, "Attempting to write past the end of output buffer!");
397 }
398 for (u32 i = 0; i < samples_read; i++) {
399 output_buffer[i] = temp_buffer[i];
400 }
401 } else {
402 std::memset(&temp_buffer[temp_buffer_pos], 0,
403 (samples_to_read - samples_read) * sizeof(s16));
404
405 Resample(output_buffer, temp_buffer, sample_rate_ratio, fraction, samples_to_write,
406 args.src_quality);
407
408 std::memcpy(voice_state.sample_history.data(), &temp_buffer[samples_to_read],
409 pitch * sizeof(s16));
410 }
411
412 remaining_sample_count -= samples_to_write;
413 if (remaining_sample_count != 0 && is_buffer_starved) {
414 LOG_ERROR(Service_Audio, "Samples remaining but buffer is starving??");
415 break;
416 }
417
418 output_buffer = output_buffer.subspan(samples_to_write);
419 }
420
421 voice_state.wave_buffers_consumed = wavebuffers_consumed;
422 voice_state.played_sample_count = played_sample_count;
423 voice_state.wave_buffer_index = wavebuffer_index;
424 voice_state.offset = offset;
425 voice_state.fraction = fraction;
426}
427
428} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/data_source/decode.h b/src/audio_core/renderer/command/data_source/decode.h
new file mode 100644
index 000000000..4d63d6fa8
--- /dev/null
+++ b/src/audio_core/renderer/command/data_source/decode.h
@@ -0,0 +1,59 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7#include <span>
8
9#include "audio_core/common/common.h"
10#include "audio_core/common/wave_buffer.h"
11#include "audio_core/renderer/voice/voice_state.h"
12#include "common/common_types.h"
13
14namespace Core::Memory {
15class Memory;
16}
17
18namespace AudioCore::AudioRenderer {
19
20struct DecodeFromWaveBuffersArgs {
21 SampleFormat sample_format;
22 std::span<s32> output;
23 VoiceState* voice_state;
24 std::span<WaveBufferVersion2> wave_buffers;
25 s8 channel;
26 s8 channel_count;
27 SrcQuality src_quality;
28 f32 pitch;
29 u32 source_sample_rate;
30 u32 target_sample_rate;
31 u32 sample_count;
32 CpuAddr data_address;
33 u64 data_size;
34 bool IsVoicePlayedSampleCountResetAtLoopPointSupported;
35 bool IsVoicePitchAndSrcSkippedSupported;
36};
37
38struct DecodeArg {
39 CpuAddr buffer;
40 u64 buffer_size;
41 u32 start_offset;
42 u32 end_offset;
43 s8 channel_count;
44 std::array<s16, 16> coefficients;
45 VoiceState::AdpcmContext* adpcm_context;
46 s8 target_channel;
47 u32 offset;
48 u32 samples_to_read;
49};
50
51/**
52 * Decode wavebuffers according to the given args.
53 *
54 * @param memory - Core memory to read data from.
55 * @param args - The wavebuffer data, and information for how to decode it.
56 */
57void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuffersArgs& args);
58
59} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/data_source/pcm_float.cpp b/src/audio_core/renderer/command/data_source/pcm_float.cpp
new file mode 100644
index 000000000..be77fab69
--- /dev/null
+++ b/src/audio_core/renderer/command/data_source/pcm_float.cpp
@@ -0,0 +1,86 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/adsp/command_list_processor.h"
5#include "audio_core/renderer/command/data_source/decode.h"
6#include "audio_core/renderer/command/data_source/pcm_float.h"
7
8namespace AudioCore::AudioRenderer {
9
10void PcmFloatDataSourceVersion1Command::Dump(const ADSP::CommandListProcessor& processor,
11 std::string& string) {
12 string +=
13 fmt::format("PcmFloatDataSourceVersion1Command\n\toutput_index {:02X} channel {} "
14 "channel count {} source sample rate {} target sample rate {} src quality {}\n",
15 output_index, channel_index, channel_count, sample_rate,
16 processor.target_sample_rate, src_quality);
17}
18
19void PcmFloatDataSourceVersion1Command::Process(const ADSP::CommandListProcessor& processor) {
20 auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count,
21 processor.sample_count);
22
23 DecodeFromWaveBuffersArgs args{
24 .sample_format{SampleFormat::PcmFloat},
25 .output{out_buffer},
26 .voice_state{reinterpret_cast<VoiceState*>(voice_state)},
27 .wave_buffers{wave_buffers},
28 .channel{channel_index},
29 .channel_count{channel_count},
30 .src_quality{src_quality},
31 .pitch{pitch},
32 .source_sample_rate{sample_rate},
33 .target_sample_rate{processor.target_sample_rate},
34 .sample_count{processor.sample_count},
35 .data_address{0},
36 .data_size{0},
37 .IsVoicePlayedSampleCountResetAtLoopPointSupported{(flags & 1) != 0},
38 .IsVoicePitchAndSrcSkippedSupported{(flags & 2) != 0},
39 };
40
41 DecodeFromWaveBuffers(*processor.memory, args);
42}
43
44bool PcmFloatDataSourceVersion1Command::Verify(const ADSP::CommandListProcessor& processor) {
45 return true;
46}
47
48void PcmFloatDataSourceVersion2Command::Dump(const ADSP::CommandListProcessor& processor,
49 std::string& string) {
50 string +=
51 fmt::format("PcmFloatDataSourceVersion2Command\n\toutput_index {:02X} channel {} "
52 "channel count {} source sample rate {} target sample rate {} src quality {}\n",
53 output_index, channel_index, channel_count, sample_rate,
54 processor.target_sample_rate, src_quality);
55}
56
57void PcmFloatDataSourceVersion2Command::Process(const ADSP::CommandListProcessor& processor) {
58 auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count,
59 processor.sample_count);
60
61 DecodeFromWaveBuffersArgs args{
62 .sample_format{SampleFormat::PcmFloat},
63 .output{out_buffer},
64 .voice_state{reinterpret_cast<VoiceState*>(voice_state)},
65 .wave_buffers{wave_buffers},
66 .channel{channel_index},
67 .channel_count{channel_count},
68 .src_quality{src_quality},
69 .pitch{pitch},
70 .source_sample_rate{sample_rate},
71 .target_sample_rate{processor.target_sample_rate},
72 .sample_count{processor.sample_count},
73 .data_address{0},
74 .data_size{0},
75 .IsVoicePlayedSampleCountResetAtLoopPointSupported{(flags & 1) != 0},
76 .IsVoicePitchAndSrcSkippedSupported{(flags & 2) != 0},
77 };
78
79 DecodeFromWaveBuffers(*processor.memory, args);
80}
81
82bool PcmFloatDataSourceVersion2Command::Verify(const ADSP::CommandListProcessor& processor) {
83 return true;
84}
85
86} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/data_source/pcm_float.h b/src/audio_core/renderer/command/data_source/pcm_float.h
new file mode 100644
index 000000000..e4af77c20
--- /dev/null
+++ b/src/audio_core/renderer/command/data_source/pcm_float.h
@@ -0,0 +1,113 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <string>
7
8#include "audio_core/common/wave_buffer.h"
9#include "audio_core/renderer/command/icommand.h"
10#include "common/common_types.h"
11
12namespace AudioCore::AudioRenderer {
13namespace ADSP {
14class CommandListProcessor;
15}
16
17/**
18 * AudioRenderer command to decode PCM float-encoded version 1 wavebuffers
19 * into the output_index mix buffer.
20 */
21struct PcmFloatDataSourceVersion1Command : ICommand {
22 /**
23 * Print this command's information to a string.
24 *
25 * @param processor - The CommandListProcessor processing this command.
26 * @param string - The string to print into.
27 */
28 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
29
30 /**
31 * Process this command.
32 *
33 * @param processor - The CommandListProcessor processing this command.
34 */
35 void Process(const ADSP::CommandListProcessor& processor) override;
36
37 /**
38 * Verify this command's data is valid.
39 *
40 * @param processor - The CommandListProcessor processing this command.
41 * @return True if the command is valid, otherwise false.
42 */
43 bool Verify(const ADSP::CommandListProcessor& processor) override;
44
45 /// Quality used for sample rate conversion
46 SrcQuality src_quality;
47 /// Mix buffer index for decoded samples
48 s16 output_index;
49 /// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags)
50 u16 flags;
51 /// Wavebuffer sample rate
52 u32 sample_rate;
53 /// Pitch used for sample rate conversion
54 f32 pitch;
55 /// Target channel to read within the wavebuffer
56 s8 channel_index;
57 /// Number of channels within the wavebuffer
58 s8 channel_count;
59 /// Wavebuffers containing the wavebuffer address, context address, looping information etc
60 std::array<WaveBufferVersion2, MaxWaveBuffers> wave_buffers;
61 /// Voice state, updated each call and written back to game
62 CpuAddr voice_state;
63};
64
65/**
66 * AudioRenderer command to decode PCM float-encoded version 2 wavebuffers
67 * into the output_index mix buffer.
68 */
69struct PcmFloatDataSourceVersion2Command : ICommand {
70 /**
71 * Print this command's information to a string.
72 *
73 * @param processor - The CommandListProcessor processing this command.
74 * @param string - The string to print into.
75 */
76 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
77
78 /**
79 * Process this command.
80 *
81 * @param processor - The CommandListProcessor processing this command.
82 */
83 void Process(const ADSP::CommandListProcessor& processor) override;
84
85 /**
86 * Verify this command's data is valid.
87 *
88 * @param processor - The CommandListProcessor processing this command.
89 * @return True if the command is valid, otherwise false.
90 */
91 bool Verify(const ADSP::CommandListProcessor& processor) override;
92
93 /// Quality used for sample rate conversion
94 SrcQuality src_quality;
95 /// Mix buffer index for decoded samples
96 s16 output_index;
97 /// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags)
98 u16 flags;
99 /// Wavebuffer sample rate
100 u32 sample_rate;
101 /// Pitch used for sample rate conversion
102 f32 pitch;
103 /// Target channel to read within the wavebuffer
104 s8 channel_index;
105 /// Number of channels within the wavebuffer
106 s8 channel_count;
107 /// Wavebuffers containing the wavebuffer address, context address, looping information etc
108 std::array<WaveBufferVersion2, MaxWaveBuffers> wave_buffers;
109 /// Voice state, updated each call and written back to game
110 CpuAddr voice_state;
111};
112
113} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/data_source/pcm_int16.cpp b/src/audio_core/renderer/command/data_source/pcm_int16.cpp
new file mode 100644
index 000000000..7a27463e4
--- /dev/null
+++ b/src/audio_core/renderer/command/data_source/pcm_int16.cpp
@@ -0,0 +1,87 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <span>
5
6#include "audio_core/renderer/adsp/command_list_processor.h"
7#include "audio_core/renderer/command/data_source/decode.h"
8#include "audio_core/renderer/command/data_source/pcm_int16.h"
9
10namespace AudioCore::AudioRenderer {
11
12void PcmInt16DataSourceVersion1Command::Dump(const ADSP::CommandListProcessor& processor,
13 std::string& string) {
14 string +=
15 fmt::format("PcmInt16DataSourceVersion1Command\n\toutput_index {:02X} channel {} "
16 "channel count {} source sample rate {} target sample rate {} src quality {}\n",
17 output_index, channel_index, channel_count, sample_rate,
18 processor.target_sample_rate, src_quality);
19}
20
21void PcmInt16DataSourceVersion1Command::Process(const ADSP::CommandListProcessor& processor) {
22 auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count,
23 processor.sample_count);
24
25 DecodeFromWaveBuffersArgs args{
26 .sample_format{SampleFormat::PcmInt16},
27 .output{out_buffer},
28 .voice_state{reinterpret_cast<VoiceState*>(voice_state)},
29 .wave_buffers{wave_buffers},
30 .channel{channel_index},
31 .channel_count{channel_count},
32 .src_quality{src_quality},
33 .pitch{pitch},
34 .source_sample_rate{sample_rate},
35 .target_sample_rate{processor.target_sample_rate},
36 .sample_count{processor.sample_count},
37 .data_address{0},
38 .data_size{0},
39 .IsVoicePlayedSampleCountResetAtLoopPointSupported{(flags & 1) != 0},
40 .IsVoicePitchAndSrcSkippedSupported{(flags & 2) != 0},
41 };
42
43 DecodeFromWaveBuffers(*processor.memory, args);
44}
45
46bool PcmInt16DataSourceVersion1Command::Verify(const ADSP::CommandListProcessor& processor) {
47 return true;
48}
49
50void PcmInt16DataSourceVersion2Command::Dump(const ADSP::CommandListProcessor& processor,
51 std::string& string) {
52 string +=
53 fmt::format("PcmInt16DataSourceVersion2Command\n\toutput_index {:02X} channel {} "
54 "channel count {} source sample rate {} target sample rate {} src quality {}\n",
55 output_index, channel_index, channel_count, sample_rate,
56 processor.target_sample_rate, src_quality);
57}
58
59void PcmInt16DataSourceVersion2Command::Process(const ADSP::CommandListProcessor& processor) {
60 auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count,
61 processor.sample_count);
62 DecodeFromWaveBuffersArgs args{
63 .sample_format{SampleFormat::PcmInt16},
64 .output{out_buffer},
65 .voice_state{reinterpret_cast<VoiceState*>(voice_state)},
66 .wave_buffers{wave_buffers},
67 .channel{channel_index},
68 .channel_count{channel_count},
69 .src_quality{src_quality},
70 .pitch{pitch},
71 .source_sample_rate{sample_rate},
72 .target_sample_rate{processor.target_sample_rate},
73 .sample_count{processor.sample_count},
74 .data_address{0},
75 .data_size{0},
76 .IsVoicePlayedSampleCountResetAtLoopPointSupported{(flags & 1) != 0},
77 .IsVoicePitchAndSrcSkippedSupported{(flags & 2) != 0},
78 };
79
80 DecodeFromWaveBuffers(*processor.memory, args);
81}
82
83bool PcmInt16DataSourceVersion2Command::Verify(const ADSP::CommandListProcessor& processor) {
84 return true;
85}
86
87} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/data_source/pcm_int16.h b/src/audio_core/renderer/command/data_source/pcm_int16.h
new file mode 100644
index 000000000..5de1ad60d
--- /dev/null
+++ b/src/audio_core/renderer/command/data_source/pcm_int16.h
@@ -0,0 +1,110 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <string>
7
8#include "audio_core/common/wave_buffer.h"
9#include "audio_core/renderer/command/icommand.h"
10#include "common/common_types.h"
11
12namespace AudioCore::AudioRenderer {
13namespace ADSP {
14class CommandListProcessor;
15}
16
17/**
18 * AudioRenderer command to decode PCM s16-encoded version 1 wavebuffers
19 * into the output_index mix buffer.
20 */
21struct PcmInt16DataSourceVersion1Command : ICommand {
22 /**
23 * Print this command's information to a string.
24 *
25 * @param processor - The CommandListProcessor processing this command.
26 * @param string - The string to print into.
27 */
28 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
29
30 /**
31 * Process this command.
32 *
33 * @param processor - The CommandListProcessor processing this command.
34 */
35 void Process(const ADSP::CommandListProcessor& processor) override;
36
37 /**
38 * Verify this command's data is valid.
39 *
40 * @param processor - The CommandListProcessor processing this command.
41 * @return True if the command is valid, otherwise false.
42 */
43 bool Verify(const ADSP::CommandListProcessor& processor) override;
44
45 /// Quality used for sample rate conversion
46 SrcQuality src_quality;
47 /// Mix buffer index for decoded samples
48 s16 output_index;
49 /// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags)
50 u16 flags;
51 /// Wavebuffer sample rate
52 u32 sample_rate;
53 /// Pitch used for sample rate conversion
54 f32 pitch;
55 /// Target channel to read within the wavebuffer
56 s8 channel_index;
57 /// Number of channels within the wavebuffer
58 s8 channel_count;
59 /// Wavebuffers containing the wavebuffer address, context address, looping information etc
60 std::array<WaveBufferVersion2, MaxWaveBuffers> wave_buffers;
61 /// Voice state, updated each call and written back to game
62 CpuAddr voice_state;
63};
64
65/**
66 * AudioRenderer command to decode PCM s16-encoded version 2 wavebuffers
67 * into the output_index mix buffer.
68 */
69struct PcmInt16DataSourceVersion2Command : ICommand {
70 /**
71 * Print this command's information to a string.
72 * @param processor - The CommandListProcessor processing this command.
73 * @param string - The string to print into.
74 */
75 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
76
77 /**
78 * Process this command.
79 * @param processor - The CommandListProcessor processing this command.
80 */
81 void Process(const ADSP::CommandListProcessor& processor) override;
82
83 /**
84 * Verify this command's data is valid.
85 * @param processor - The CommandListProcessor processing this command.
86 * @return True if the command is valid, otherwise false.
87 */
88 bool Verify(const ADSP::CommandListProcessor& processor) override;
89
90 /// Quality used for sample rate conversion
91 SrcQuality src_quality;
92 /// Mix buffer index for decoded samples
93 s16 output_index;
94 /// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags)
95 u16 flags;
96 /// Wavebuffer sample rate
97 u32 sample_rate;
98 /// Pitch used for sample rate conversion
99 f32 pitch;
100 /// Target channel to read within the wavebuffer
101 s8 channel_index;
102 /// Number of channels within the wavebuffer
103 s8 channel_count;
104 /// Wavebuffers containing the wavebuffer address, context address, looping information etc
105 std::array<WaveBufferVersion2, MaxWaveBuffers> wave_buffers;
106 /// Voice state, updated each call and written back to game
107 CpuAddr voice_state;
108};
109
110} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/effect/aux_.cpp b/src/audio_core/renderer/command/effect/aux_.cpp
new file mode 100644
index 000000000..e76db893f
--- /dev/null
+++ b/src/audio_core/renderer/command/effect/aux_.cpp
@@ -0,0 +1,207 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/adsp/command_list_processor.h"
5#include "audio_core/renderer/command/effect/aux_.h"
6#include "audio_core/renderer/effect/aux_.h"
7#include "core/memory.h"
8
9namespace AudioCore::AudioRenderer {
10/**
11 * Reset an AuxBuffer.
12 *
13 * @param memory - Core memory for writing.
14 * @param aux_info - Memory address pointing to the AuxInfo to reset.
15 */
16static void ResetAuxBufferDsp(Core::Memory::Memory& memory, const CpuAddr aux_info) {
17 if (aux_info == 0) {
18 LOG_ERROR(Service_Audio, "Aux info is 0!");
19 return;
20 }
21
22 auto info{reinterpret_cast<AuxInfo::AuxInfoDsp*>(memory.GetPointer(aux_info))};
23 info->read_offset = 0;
24 info->write_offset = 0;
25 info->total_sample_count = 0;
26}
27
28/**
29 * Write the given input mix buffer to the memory at send_buffer, and update send_info_ if
30 * update_count is set, to notify the game that an update happened.
31 *
32 * @param memory - Core memory for writing.
33 * @param send_info_ - Meta information for where to write the mix buffer.
34 * @param sample_count - Unused.
35 * @param send_buffer - Memory address to write the mix buffer to.
36 * @param count_max - Maximum number of samples in the receiving buffer.
37 * @param input - Input mix buffer to write.
38 * @param write_count_ - Number of samples to write.
39 * @param write_offset - Current offset to begin writing the receiving buffer at.
40 * @param update_count - If non-zero, send_info_ will be updated.
41 * @return Number of samples written.
42 */
43static u32 WriteAuxBufferDsp(Core::Memory::Memory& memory, const CpuAddr send_info_,
44 [[maybe_unused]] u32 sample_count, const CpuAddr send_buffer,
45 const u32 count_max, std::span<const s32> input,
46 const u32 write_count_, const u32 write_offset,
47 const u32 update_count) {
48 if (write_count_ > count_max) {
49 LOG_ERROR(Service_Audio,
50 "write_count must be smaller than count_max! write_count {}, count_max {}",
51 write_count_, count_max);
52 return 0;
53 }
54
55 if (input.empty()) {
56 LOG_ERROR(Service_Audio, "input buffer is empty!");
57 return 0;
58 }
59
60 if (send_buffer == 0) {
61 LOG_ERROR(Service_Audio, "send_buffer is 0!");
62 return 0;
63 }
64
65 if (count_max == 0) {
66 return 0;
67 }
68
69 AuxInfo::AuxInfoDsp send_info{};
70 memory.ReadBlockUnsafe(send_info_, &send_info, sizeof(AuxInfo::AuxInfoDsp));
71
72 u32 target_write_offset{send_info.write_offset + write_offset};
73 if (target_write_offset > count_max || write_count_ == 0) {
74 return 0;
75 }
76
77 u32 write_count{write_count_};
78 u32 write_pos{0};
79 while (write_count > 0) {
80 u32 to_write{std::min(count_max - target_write_offset, write_count)};
81
82 if (to_write > 0) {
83 memory.WriteBlockUnsafe(send_buffer + target_write_offset * sizeof(s32),
84 &input[write_pos], to_write * sizeof(s32));
85 }
86
87 target_write_offset = (target_write_offset + to_write) % count_max;
88 write_count -= to_write;
89 write_pos += to_write;
90 }
91
92 if (update_count) {
93 send_info.write_offset = (send_info.write_offset + update_count) % count_max;
94 }
95
96 memory.WriteBlockUnsafe(send_info_, &send_info, sizeof(AuxInfo::AuxInfoDsp));
97
98 return write_count_;
99}
100
101/**
102 * Read the given memory at return_buffer into the output mix buffer, and update return_info_ if
103 * update_count is set, to notify the game that an update happened.
104 *
105 * @param memory - Core memory for writing.
106 * @param return_info_ - Meta information for where to read the mix buffer.
107 * @param return_buffer - Memory address to read the samples from.
108 * @param count_max - Maximum number of samples in the receiving buffer.
109 * @param output - Output mix buffer which will receive the samples.
110 * @param count_ - Number of samples to read.
111 * @param read_offset - Current offset to begin reading the return_buffer at.
112 * @param update_count - If non-zero, send_info_ will be updated.
113 * @return Number of samples read.
114 */
115static u32 ReadAuxBufferDsp(Core::Memory::Memory& memory, const CpuAddr return_info_,
116 const CpuAddr return_buffer, const u32 count_max, std::span<s32> output,
117 const u32 count_, const u32 read_offset, const u32 update_count) {
118 if (count_max == 0) {
119 return 0;
120 }
121
122 if (count_ > count_max) {
123 LOG_ERROR(Service_Audio, "count must be smaller than count_max! count {}, count_max {}",
124 count_, count_max);
125 return 0;
126 }
127
128 if (output.empty()) {
129 LOG_ERROR(Service_Audio, "output buffer is empty!");
130 return 0;
131 }
132
133 if (return_buffer == 0) {
134 LOG_ERROR(Service_Audio, "return_buffer is 0!");
135 return 0;
136 }
137
138 AuxInfo::AuxInfoDsp return_info{};
139 memory.ReadBlockUnsafe(return_info_, &return_info, sizeof(AuxInfo::AuxInfoDsp));
140
141 u32 target_read_offset{return_info.read_offset + read_offset};
142 if (target_read_offset > count_max) {
143 return 0;
144 }
145
146 u32 read_count{count_};
147 u32 read_pos{0};
148 while (read_count > 0) {
149 u32 to_read{std::min(count_max - target_read_offset, read_count)};
150
151 if (to_read > 0) {
152 memory.ReadBlockUnsafe(return_buffer + target_read_offset * sizeof(s32),
153 &output[read_pos], to_read * sizeof(s32));
154 }
155
156 target_read_offset = (target_read_offset + to_read) % count_max;
157 read_count -= to_read;
158 read_pos += to_read;
159 }
160
161 if (update_count) {
162 return_info.read_offset = (return_info.read_offset + update_count) % count_max;
163 }
164
165 memory.WriteBlockUnsafe(return_info_, &return_info, sizeof(AuxInfo::AuxInfoDsp));
166
167 return count_;
168}
169
170void AuxCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
171 std::string& string) {
172 string += fmt::format("AuxCommand\n\tenabled {} input {:02X} output {:02X}\n", effect_enabled,
173 input, output);
174}
175
176void AuxCommand::Process(const ADSP::CommandListProcessor& processor) {
177 auto input_buffer{
178 processor.mix_buffers.subspan(input * processor.sample_count, processor.sample_count)};
179 auto output_buffer{
180 processor.mix_buffers.subspan(output * processor.sample_count, processor.sample_count)};
181
182 if (effect_enabled) {
183 WriteAuxBufferDsp(*processor.memory, send_buffer_info, processor.sample_count, send_buffer,
184 count_max, input_buffer, processor.sample_count, write_offset,
185 update_count);
186
187 auto read{ReadAuxBufferDsp(*processor.memory, return_buffer_info, return_buffer, count_max,
188 output_buffer, processor.sample_count, write_offset,
189 update_count)};
190
191 if (read != processor.sample_count) {
192 std::memset(&output_buffer[read], 0, processor.sample_count - read);
193 }
194 } else {
195 ResetAuxBufferDsp(*processor.memory, send_buffer_info);
196 ResetAuxBufferDsp(*processor.memory, return_buffer_info);
197 if (input != output) {
198 std::memcpy(output_buffer.data(), input_buffer.data(), output_buffer.size_bytes());
199 }
200 }
201}
202
203bool AuxCommand::Verify(const ADSP::CommandListProcessor& processor) {
204 return true;
205}
206
207} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/effect/aux_.h b/src/audio_core/renderer/command/effect/aux_.h
new file mode 100644
index 000000000..825c93732
--- /dev/null
+++ b/src/audio_core/renderer/command/effect/aux_.h
@@ -0,0 +1,66 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <string>
7
8#include "audio_core/renderer/command/icommand.h"
9#include "common/common_types.h"
10
11namespace AudioCore::AudioRenderer {
12namespace ADSP {
13class CommandListProcessor;
14}
15
16/**
17 * AudioRenderer command to read and write an auxiliary buffer, writing the input mix buffer to game
18 * memory, and reading into the output buffer from game memory.
19 */
20struct AuxCommand : ICommand {
21 /**
22 * Print this command's information to a string.
23 *
24 * @param processor - The CommandListProcessor processing this command.
25 * @param string - The string to print into.
26 */
27 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
28
29 /**
30 * Process this command.
31 *
32 * @param processor - The CommandListProcessor processing this command.
33 */
34 void Process(const ADSP::CommandListProcessor& processor) override;
35
36 /**
37 * Verify this command's data is valid.
38 *
39 * @param processor - The CommandListProcessor processing this command.
40 * @return True if the command is valid, otherwise false.
41 */
42 bool Verify(const ADSP::CommandListProcessor& processor) override;
43
44 /// Input mix buffer index
45 s16 input;
46 /// Output mix buffer index
47 s16 output;
48 /// Meta info for writing
49 CpuAddr send_buffer_info;
50 /// Meta info for reading
51 CpuAddr return_buffer_info;
52 /// Game memory write buffer
53 CpuAddr send_buffer;
54 /// Game memory read buffer
55 CpuAddr return_buffer;
56 /// Max samples to read/write
57 u32 count_max;
58 /// Current read/write offset
59 u32 write_offset;
60 /// Number of samples to update per call
61 u32 update_count;
62 /// is this effect enabled?
63 bool effect_enabled;
64};
65
66} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/effect/biquad_filter.cpp b/src/audio_core/renderer/command/effect/biquad_filter.cpp
new file mode 100644
index 000000000..1baae74fd
--- /dev/null
+++ b/src/audio_core/renderer/command/effect/biquad_filter.cpp
@@ -0,0 +1,118 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/adsp/command_list_processor.h"
5#include "audio_core/renderer/command/effect/biquad_filter.h"
6#include "audio_core/renderer/voice/voice_state.h"
7
8namespace AudioCore::AudioRenderer {
9/**
10 * Biquad filter float implementation.
11 *
12 * @param output - Output container for filtered samples.
13 * @param input - Input container for samples to be filtered.
14 * @param b - Feedforward coefficients.
15 * @param a - Feedback coefficients.
16 * @param state - State to track previous samples between calls.
17 * @param sample_count - Number of samples to process.
18 */
19void ApplyBiquadFilterFloat(std::span<s32> output, std::span<const s32> input,
20 std::array<s16, 3>& b_, std::array<s16, 2>& a_,
21 VoiceState::BiquadFilterState& state, const u32 sample_count) {
22 constexpr s64 min{std::numeric_limits<s32>::min()};
23 constexpr s64 max{std::numeric_limits<s32>::max()};
24 std::array<f64, 3> b{Common::FixedPoint<50, 14>::from_base(b_[0]).to_double(),
25 Common::FixedPoint<50, 14>::from_base(b_[1]).to_double(),
26 Common::FixedPoint<50, 14>::from_base(b_[2]).to_double()};
27 std::array<f64, 2> a{Common::FixedPoint<50, 14>::from_base(a_[0]).to_double(),
28 Common::FixedPoint<50, 14>::from_base(a_[1]).to_double()};
29 std::array<f64, 4> s{state.s0.to_double(), state.s1.to_double(), state.s2.to_double(),
30 state.s3.to_double()};
31
32 for (u32 i = 0; i < sample_count; i++) {
33 f64 in_sample{static_cast<f64>(input[i])};
34 auto sample{in_sample * b[0] + s[0] * b[1] + s[1] * b[2] + s[2] * a[0] + s[3] * a[1]};
35
36 output[i] = static_cast<s32>(std::clamp(static_cast<s64>(sample), min, max));
37
38 s[1] = s[0];
39 s[0] = in_sample;
40 s[3] = s[2];
41 s[2] = sample;
42 }
43
44 state.s0 = s[0];
45 state.s1 = s[1];
46 state.s2 = s[2];
47 state.s3 = s[3];
48}
49
50/**
51 * Biquad filter s32 implementation.
52 *
53 * @param output - Output container for filtered samples.
54 * @param input - Input container for samples to be filtered.
55 * @param b - Feedforward coefficients.
56 * @param a - Feedback coefficients.
57 * @param state - State to track previous samples between calls.
58 * @param sample_count - Number of samples to process.
59 */
60static void ApplyBiquadFilterInt(std::span<s32> output, std::span<const s32> input,
61 std::array<s16, 3>& b_, std::array<s16, 2>& a_,
62 VoiceState::BiquadFilterState& state, const u32 sample_count) {
63 constexpr s64 min{std::numeric_limits<s32>::min()};
64 constexpr s64 max{std::numeric_limits<s32>::max()};
65 std::array<Common::FixedPoint<50, 14>, 3> b{
66 Common::FixedPoint<50, 14>::from_base(b_[0]),
67 Common::FixedPoint<50, 14>::from_base(b_[1]),
68 Common::FixedPoint<50, 14>::from_base(b_[2]),
69 };
70 std::array<Common::FixedPoint<50, 14>, 3> a{
71 Common::FixedPoint<50, 14>::from_base(a_[0]),
72 Common::FixedPoint<50, 14>::from_base(a_[1]),
73 };
74
75 for (u32 i = 0; i < sample_count; i++) {
76 s64 in_sample{input[i]};
77 auto sample{in_sample * b[0] + state.s0};
78 const auto out_sample{std::clamp(sample.to_long(), min, max)};
79
80 output[i] = static_cast<s32>(out_sample);
81
82 state.s0 = state.s1 + b[1] * in_sample + a[0] * out_sample;
83 state.s1 = 0 + b[2] * in_sample + a[1] * out_sample;
84 }
85}
86
87void BiquadFilterCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
88 std::string& string) {
89 string += fmt::format(
90 "BiquadFilterCommand\n\tinput {:02X} output {:02X} needs_init {} use_float_processing {}\n",
91 input, output, needs_init, use_float_processing);
92}
93
94void BiquadFilterCommand::Process(const ADSP::CommandListProcessor& processor) {
95 auto state_{reinterpret_cast<VoiceState::BiquadFilterState*>(state)};
96 if (needs_init) {
97 std::memset(state_, 0, sizeof(VoiceState::BiquadFilterState));
98 }
99
100 auto input_buffer{
101 processor.mix_buffers.subspan(input * processor.sample_count, processor.sample_count)};
102 auto output_buffer{
103 processor.mix_buffers.subspan(output * processor.sample_count, processor.sample_count)};
104
105 if (use_float_processing) {
106 ApplyBiquadFilterFloat(output_buffer, input_buffer, biquad.b, biquad.a, *state_,
107 processor.sample_count);
108 } else {
109 ApplyBiquadFilterInt(output_buffer, input_buffer, biquad.b, biquad.a, *state_,
110 processor.sample_count);
111 }
112}
113
114bool BiquadFilterCommand::Verify(const ADSP::CommandListProcessor& processor) {
115 return true;
116}
117
118} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/effect/biquad_filter.h b/src/audio_core/renderer/command/effect/biquad_filter.h
new file mode 100644
index 000000000..4c9c42d29
--- /dev/null
+++ b/src/audio_core/renderer/command/effect/biquad_filter.h
@@ -0,0 +1,74 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <string>
7
8#include "audio_core/renderer/command/icommand.h"
9#include "audio_core/renderer/voice/voice_info.h"
10#include "audio_core/renderer/voice/voice_state.h"
11#include "common/common_types.h"
12
13namespace AudioCore::AudioRenderer {
14namespace ADSP {
15class CommandListProcessor;
16}
17
18/**
19 * AudioRenderer command for applying a biquad filter to the input mix buffer, saving the results to
20 * the output mix buffer.
21 */
22struct BiquadFilterCommand : ICommand {
23 /**
24 * Print this command's information to a string.
25 *
26 * @param processor - The CommandListProcessor processing this command.
27 * @param string - The string to print into.
28 */
29 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
30
31 /**
32 * Process this command.
33 *
34 * @param processor - The CommandListProcessor processing this command.
35 */
36 void Process(const ADSP::CommandListProcessor& processor) override;
37
38 /**
39 * Verify this command's data is valid.
40 *
41 * @param processor - The CommandListProcessor processing this command.
42 * @return True if the command is valid, otherwise false.
43 */
44 bool Verify(const ADSP::CommandListProcessor& processor) override;
45
46 /// Input mix buffer index
47 s16 input;
48 /// Output mix buffer index
49 s16 output;
50 /// Input parameters for biquad
51 VoiceInfo::BiquadFilterParameter biquad;
52 /// Biquad state, updated each call
53 CpuAddr state;
54 /// If true, reset the state
55 bool needs_init;
56 /// If true, use float processing rather than int
57 bool use_float_processing;
58};
59
60/**
61 * Biquad filter float implementation.
62 *
63 * @param output - Output container for filtered samples.
64 * @param input - Input container for samples to be filtered.
65 * @param b - Feedforward coefficients.
66 * @param a - Feedback coefficients.
67 * @param state - State to track previous samples.
68 * @param sample_count - Number of samples to process.
69 */
70void ApplyBiquadFilterFloat(std::span<s32> output, std::span<const s32> input,
71 std::array<s16, 3>& b, std::array<s16, 2>& a,
72 VoiceState::BiquadFilterState& state, const u32 sample_count);
73
74} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/effect/capture.cpp b/src/audio_core/renderer/command/effect/capture.cpp
new file mode 100644
index 000000000..042fd286e
--- /dev/null
+++ b/src/audio_core/renderer/command/effect/capture.cpp
@@ -0,0 +1,142 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/adsp/command_list_processor.h"
5#include "audio_core/renderer/command/effect/capture.h"
6#include "audio_core/renderer/effect/aux_.h"
7#include "core/memory.h"
8
9namespace AudioCore::AudioRenderer {
10/**
11 * Reset an AuxBuffer.
12 *
13 * @param memory - Core memory for writing.
14 * @param aux_info - Memory address pointing to the AuxInfo to reset.
15 */
16static void ResetAuxBufferDsp(Core::Memory::Memory& memory, const CpuAddr aux_info) {
17 if (aux_info == 0) {
18 LOG_ERROR(Service_Audio, "Aux info is 0!");
19 return;
20 }
21
22 memory.Write32(VAddr(aux_info + offsetof(AuxInfo::AuxInfoDsp, read_offset)), 0);
23 memory.Write32(VAddr(aux_info + offsetof(AuxInfo::AuxInfoDsp, write_offset)), 0);
24 memory.Write32(VAddr(aux_info + offsetof(AuxInfo::AuxInfoDsp, total_sample_count)), 0);
25}
26
27/**
28 * Write the given input mix buffer to the memory at send_buffer, and update send_info_ if
29 * update_count is set, to notify the game that an update happened.
30 *
31 * @param memory - Core memory for writing.
32 * @param send_info_ - Header information for where to write the mix buffer.
33 * @param send_buffer - Memory address to write the mix buffer to.
34 * @param count_max - Maximum number of samples in the receiving buffer.
35 * @param input - Input mix buffer to write.
36 * @param write_count_ - Number of samples to write.
37 * @param write_offset - Current offset to begin writing the receiving buffer at.
38 * @param update_count - If non-zero, send_info_ will be updated.
39 * @return Number of samples written.
40 */
41static u32 WriteAuxBufferDsp(Core::Memory::Memory& memory, const CpuAddr send_info_,
42 const CpuAddr send_buffer, u32 count_max, std::span<const s32> input,
43 const u32 write_count_, const u32 write_offset,
44 const u32 update_count) {
45 if (write_count_ > count_max) {
46 LOG_ERROR(Service_Audio,
47 "write_count must be smaller than count_max! write_count {}, count_max {}",
48 write_count_, count_max);
49 return 0;
50 }
51
52 if (send_info_ == 0) {
53 LOG_ERROR(Service_Audio, "send_info is 0!");
54 return 0;
55 }
56
57 if (input.empty()) {
58 LOG_ERROR(Service_Audio, "input buffer is empty!");
59 return 0;
60 }
61
62 if (send_buffer == 0) {
63 LOG_ERROR(Service_Audio, "send_buffer is 0!");
64 return 0;
65 }
66
67 if (count_max == 0) {
68 return 0;
69 }
70
71 AuxInfo::AuxBufferInfo send_info{};
72 memory.ReadBlockUnsafe(send_info_, &send_info, sizeof(AuxInfo::AuxBufferInfo));
73
74 u32 target_write_offset{send_info.dsp_info.write_offset + write_offset};
75 if (target_write_offset > count_max || write_count_ == 0) {
76 return 0;
77 }
78
79 u32 write_count{write_count_};
80 u32 write_pos{0};
81 while (write_count > 0) {
82 u32 to_write{std::min(count_max - target_write_offset, write_count)};
83
84 if (to_write > 0) {
85 memory.WriteBlockUnsafe(send_buffer + target_write_offset * sizeof(s32),
86 &input[write_pos], to_write * sizeof(s32));
87 }
88
89 target_write_offset = (target_write_offset + to_write) % count_max;
90 write_count -= to_write;
91 write_pos += to_write;
92 }
93
94 if (update_count) {
95 const auto count_diff{send_info.dsp_info.total_sample_count -
96 send_info.cpu_info.total_sample_count};
97 if (count_diff >= count_max) {
98 auto dsp_lost_count{send_info.dsp_info.lost_sample_count + update_count};
99 if (dsp_lost_count - send_info.cpu_info.lost_sample_count <
100 send_info.dsp_info.lost_sample_count - send_info.cpu_info.lost_sample_count) {
101 dsp_lost_count = send_info.cpu_info.lost_sample_count - 1;
102 }
103 send_info.dsp_info.lost_sample_count = dsp_lost_count;
104 }
105
106 send_info.dsp_info.write_offset =
107 (send_info.dsp_info.write_offset + update_count + count_max) % count_max;
108
109 auto new_sample_count{send_info.dsp_info.total_sample_count + update_count};
110 if (new_sample_count - send_info.cpu_info.total_sample_count < count_diff) {
111 new_sample_count = send_info.cpu_info.total_sample_count - 1;
112 }
113 send_info.dsp_info.total_sample_count = new_sample_count;
114 }
115
116 memory.WriteBlockUnsafe(send_info_, &send_info, sizeof(AuxInfo::AuxBufferInfo));
117
118 return write_count_;
119}
120
121void CaptureCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
122 std::string& string) {
123 string += fmt::format("CaptureCommand\n\tenabled {} input {:02X} output {:02X}", effect_enabled,
124 input, output);
125}
126
127void CaptureCommand::Process(const ADSP::CommandListProcessor& processor) {
128 if (effect_enabled) {
129 auto input_buffer{
130 processor.mix_buffers.subspan(input * processor.sample_count, processor.sample_count)};
131 WriteAuxBufferDsp(*processor.memory, send_buffer_info, send_buffer, count_max, input_buffer,
132 processor.sample_count, write_offset, update_count);
133 } else {
134 ResetAuxBufferDsp(*processor.memory, send_buffer_info);
135 }
136}
137
138bool CaptureCommand::Verify(const ADSP::CommandListProcessor& processor) {
139 return true;
140}
141
142} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/effect/capture.h b/src/audio_core/renderer/command/effect/capture.h
new file mode 100644
index 000000000..8670acb24
--- /dev/null
+++ b/src/audio_core/renderer/command/effect/capture.h
@@ -0,0 +1,62 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <string>
7
8#include "audio_core/renderer/command/icommand.h"
9#include "common/common_types.h"
10
11namespace AudioCore::AudioRenderer {
12namespace ADSP {
13class CommandListProcessor;
14}
15
16/**
17 * AudioRenderer command for capturing a mix buffer. That is, writing it back to a given game memory
18 * address.
19 */
20struct CaptureCommand : ICommand {
21 /**
22 * Print this command's information to a string.
23 *
24 * @param processor - The CommandListProcessor processing this command.
25 * @param string - The string to print into.
26 */
27 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
28
29 /**
30 * Process this command.
31 *
32 * @param processor - The CommandListProcessor processing this command.
33 */
34 void Process(const ADSP::CommandListProcessor& processor) override;
35
36 /**
37 * Verify this command's data is valid.
38 *
39 * @param processor - The CommandListProcessor processing this command.
40 * @return True if the command is valid, otherwise false.
41 */
42 bool Verify(const ADSP::CommandListProcessor& processor) override;
43
44 /// Input mix buffer index
45 s16 input;
46 /// Output mix buffer index
47 s16 output;
48 /// Meta info for writing
49 CpuAddr send_buffer_info;
50 /// Game memory write buffer
51 CpuAddr send_buffer;
52 /// Max samples to read/write
53 u32 count_max;
54 /// Current read/write offset
55 u32 write_offset;
56 /// Number of samples to update per call
57 u32 update_count;
58 /// is this effect enabled?
59 bool effect_enabled;
60};
61
62} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/effect/compressor.cpp b/src/audio_core/renderer/command/effect/compressor.cpp
new file mode 100644
index 000000000..2ebc140f1
--- /dev/null
+++ b/src/audio_core/renderer/command/effect/compressor.cpp
@@ -0,0 +1,156 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <cmath>
5#include <span>
6#include <vector>
7
8#include "audio_core/renderer/adsp/command_list_processor.h"
9#include "audio_core/renderer/command/effect/compressor.h"
10#include "audio_core/renderer/effect/compressor.h"
11
12namespace AudioCore::AudioRenderer {
13
14static void SetCompressorEffectParameter(CompressorInfo::ParameterVersion2& params,
15 CompressorInfo::State& state) {
16 const auto ratio{1.0f / params.compressor_ratio};
17 auto makeup_gain{0.0f};
18 if (params.makeup_gain_enabled) {
19 makeup_gain = (params.threshold * 0.5f) * (ratio - 1.0f) - 3.0f;
20 }
21 state.makeup_gain = makeup_gain;
22 state.unk_18 = params.unk_28;
23
24 const auto a{(params.out_gain + makeup_gain) / 20.0f * 3.3219f};
25 const auto b{(a - std::trunc(a)) * 0.69315f};
26 const auto c{std::pow(2.0f, b)};
27
28 state.unk_0C = (1.0f - ratio) / 6.0f;
29 state.unk_14 = params.threshold + 1.5f;
30 state.unk_10 = params.threshold - 1.5f;
31 state.unk_20 = c;
32}
33
34static void InitializeCompressorEffect(CompressorInfo::ParameterVersion2& params,
35 CompressorInfo::State& state) {
36 std::memset(&state, 0, sizeof(CompressorInfo::State));
37
38 state.unk_00 = 0;
39 state.unk_04 = 1.0f;
40 state.unk_08 = 1.0f;
41
42 SetCompressorEffectParameter(params, state);
43}
44
45static void ApplyCompressorEffect(CompressorInfo::ParameterVersion2& params,
46 CompressorInfo::State& state, bool enabled,
47 std::vector<std::span<const s32>> input_buffers,
48 std::vector<std::span<s32>> output_buffers, u32 sample_count) {
49 if (enabled) {
50 auto state_00{state.unk_00};
51 auto state_04{state.unk_04};
52 auto state_08{state.unk_08};
53 auto state_18{state.unk_18};
54
55 for (u32 i = 0; i < sample_count; i++) {
56 auto a{0.0f};
57 for (s16 channel = 0; channel < params.channel_count; channel++) {
58 const auto input_sample{Common::FixedPoint<49, 15>(input_buffers[channel][i])};
59 a += (input_sample * input_sample).to_float();
60 }
61
62 state_00 += params.unk_24 * ((a / params.channel_count) - state.unk_00);
63
64 auto b{-100.0f};
65 auto c{0.0f};
66 if (state_00 >= 1.0e-10) {
67 b = std::log10(state_00) * 10.0f;
68 c = 1.0f;
69 }
70
71 if (b >= state.unk_10) {
72 const auto d{b >= state.unk_14
73 ? ((1.0f / params.compressor_ratio) - 1.0f) *
74 (b - params.threshold)
75 : (b - state.unk_10) * (b - state.unk_10) * -state.unk_0C};
76 const auto e{d / 20.0f * 3.3219f};
77 const auto f{(e - std::trunc(e)) * 0.69315f};
78 c = std::pow(2.0f, f);
79 }
80
81 state_18 = params.unk_28;
82 auto tmp{c};
83 if ((state_04 - c) <= 0.08f) {
84 state_18 = params.unk_2C;
85 if (((state_04 - c) >= -0.08f) && (std::abs(state_08 - c) >= 0.001f)) {
86 tmp = state_04;
87 }
88 }
89
90 state_04 = tmp;
91 state_08 += (c - state_08) * state_18;
92
93 for (s16 channel = 0; channel < params.channel_count; channel++) {
94 output_buffers[channel][i] = static_cast<s32>(
95 static_cast<f32>(input_buffers[channel][i]) * state_08 * state.unk_20);
96 }
97 }
98
99 state.unk_00 = state_00;
100 state.unk_04 = state_04;
101 state.unk_08 = state_08;
102 state.unk_18 = state_18;
103 } else {
104 for (s16 channel = 0; channel < params.channel_count; channel++) {
105 if (params.inputs[channel] != params.outputs[channel]) {
106 std::memcpy((char*)output_buffers[channel].data(),
107 (char*)input_buffers[channel].data(),
108 output_buffers[channel].size_bytes());
109 }
110 }
111 }
112}
113
114void CompressorCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
115 std::string& string) {
116 string += fmt::format("CompressorCommand\n\tenabled {} \n\tinputs: ", effect_enabled);
117 for (s16 i = 0; i < parameter.channel_count; i++) {
118 string += fmt::format("{:02X}, ", inputs[i]);
119 }
120 string += "\n\toutputs: ";
121 for (s16 i = 0; i < parameter.channel_count; i++) {
122 string += fmt::format("{:02X}, ", outputs[i]);
123 }
124 string += "\n";
125}
126
127void CompressorCommand::Process(const ADSP::CommandListProcessor& processor) {
128 std::vector<std::span<const s32>> input_buffers(parameter.channel_count);
129 std::vector<std::span<s32>> output_buffers(parameter.channel_count);
130
131 for (s16 i = 0; i < parameter.channel_count; i++) {
132 input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count,
133 processor.sample_count);
134 output_buffers[i] = processor.mix_buffers.subspan(outputs[i] * processor.sample_count,
135 processor.sample_count);
136 }
137
138 auto state_{reinterpret_cast<CompressorInfo::State*>(state)};
139
140 if (effect_enabled) {
141 if (parameter.state == CompressorInfo::ParameterState::Updating) {
142 SetCompressorEffectParameter(parameter, *state_);
143 } else if (parameter.state == CompressorInfo::ParameterState::Initialized) {
144 InitializeCompressorEffect(parameter, *state_);
145 }
146 }
147
148 ApplyCompressorEffect(parameter, *state_, effect_enabled, input_buffers, output_buffers,
149 processor.sample_count);
150}
151
152bool CompressorCommand::Verify(const ADSP::CommandListProcessor& processor) {
153 return true;
154}
155
156} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/effect/compressor.h b/src/audio_core/renderer/command/effect/compressor.h
new file mode 100644
index 000000000..f8e96cb43
--- /dev/null
+++ b/src/audio_core/renderer/command/effect/compressor.h
@@ -0,0 +1,60 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7#include <string>
8
9#include "audio_core/renderer/command/icommand.h"
10#include "audio_core/renderer/effect/compressor.h"
11#include "common/common_types.h"
12
13namespace AudioCore::AudioRenderer {
14namespace ADSP {
15class CommandListProcessor;
16}
17
18/**
19 * AudioRenderer command for limiting volume between a high and low threshold.
20 * Version 1.
21 */
22struct CompressorCommand : ICommand {
23 /**
24 * Print this command's information to a string.
25 *
26 * @param processor - The CommandListProcessor processing this command.
27 * @param string - The string to print into.
28 */
29 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
30
31 /**
32 * Process this command.
33 *
34 * @param processor - The CommandListProcessor processing this command.
35 */
36 void Process(const ADSP::CommandListProcessor& processor) override;
37
38 /**
39 * Verify this command's data is valid.
40 *
41 * @param processor - The CommandListProcessor processing this command.
42 * @return True if the command is valid, otherwise false.
43 */
44 bool Verify(const ADSP::CommandListProcessor& processor) override;
45
46 /// Input mix buffer offsets for each channel
47 std::array<s16, MaxChannels> inputs;
48 /// Output mix buffer offsets for each channel
49 std::array<s16, MaxChannels> outputs;
50 /// Input parameters
51 CompressorInfo::ParameterVersion2 parameter;
52 /// State, updated each call
53 CpuAddr state;
54 /// Game-supplied workbuffer (Unused)
55 CpuAddr workbuffer;
56 /// Is this effect enabled?
57 bool effect_enabled;
58};
59
60} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/effect/delay.cpp b/src/audio_core/renderer/command/effect/delay.cpp
new file mode 100644
index 000000000..a4e408d40
--- /dev/null
+++ b/src/audio_core/renderer/command/effect/delay.cpp
@@ -0,0 +1,238 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/adsp/command_list_processor.h"
5#include "audio_core/renderer/command/effect/delay.h"
6
7namespace AudioCore::AudioRenderer {
8/**
9 * Update the DelayInfo state according to the given parameters.
10 *
11 * @param params - Input parameters to update the state.
12 * @param state - State to be updated.
13 */
14static void SetDelayEffectParameter(const DelayInfo::ParameterVersion1& params,
15 DelayInfo::State& state) {
16 auto channel_spread{params.channel_spread};
17 state.feedback_gain = params.feedback_gain * 0.97998046875f;
18 state.delay_feedback_gain = state.feedback_gain * (1.0f - channel_spread);
19 if (params.channel_count == 4 || params.channel_count == 6) {
20 channel_spread >>= 1;
21 }
22 state.delay_feedback_cross_gain = channel_spread * state.feedback_gain;
23 state.lowpass_feedback_gain = params.lowpass_amount * 0.949951171875f;
24 state.lowpass_gain = 1.0f - state.lowpass_feedback_gain;
25}
26
27/**
28 * Initialize a new DelayInfo state according to the given parameters.
29 *
30 * @param params - Input parameters to update the state.
31 * @param state - State to be updated.
32 * @param workbuffer - Game-supplied memory for the state. (Unused)
33 */
34static void InitializeDelayEffect(const DelayInfo::ParameterVersion1& params,
35 DelayInfo::State& state,
36 [[maybe_unused]] const CpuAddr workbuffer) {
37 state = {};
38
39 for (u32 channel = 0; channel < params.channel_count; channel++) {
40 Common::FixedPoint<32, 32> sample_count_max{0.064f};
41 sample_count_max *= params.sample_rate.to_int_floor() * params.delay_time_max;
42
43 Common::FixedPoint<18, 14> delay_time{params.delay_time};
44 delay_time *= params.sample_rate / 1000;
45 Common::FixedPoint<32, 32> sample_count{delay_time};
46
47 if (sample_count > sample_count_max) {
48 sample_count = sample_count_max;
49 }
50
51 state.delay_lines[channel].sample_count_max = sample_count_max.to_int_floor();
52 state.delay_lines[channel].sample_count = sample_count.to_int_floor();
53 state.delay_lines[channel].buffer.resize(state.delay_lines[channel].sample_count, 0);
54 if (state.delay_lines[channel].buffer.size() == 0) {
55 state.delay_lines[channel].buffer.push_back(0);
56 }
57 state.delay_lines[channel].buffer_pos = 0;
58 state.delay_lines[channel].decay_rate = 1.0f;
59 }
60
61 SetDelayEffectParameter(params, state);
62}
63
64/**
65 * Delay effect impl, according to the parameters and current state, on the input mix buffers,
66 * saving the results to the output mix buffers.
67 *
68 * @tparam NumChannels - Number of channels to process. 1-6.
69 * @param params - Input parameters to use.
70 * @param state - State to use, must be initialized (see InitializeDelayEffect).
71 * @param inputs - Input mix buffers to performan the delay on.
72 * @param outputs - Output mix buffers to receive the delayed samples.
73 * @param sample_count - Number of samples to process.
74 */
75template <size_t NumChannels>
76static void ApplyDelay(const DelayInfo::ParameterVersion1& params, DelayInfo::State& state,
77 std::vector<std::span<const s32>>& inputs,
78 std::vector<std::span<s32>>& outputs, const u32 sample_count) {
79 for (u32 sample_index = 0; sample_index < sample_count; sample_index++) {
80 std::array<Common::FixedPoint<50, 14>, NumChannels> input_samples{};
81 for (u32 channel = 0; channel < NumChannels; channel++) {
82 input_samples[channel] = inputs[channel][sample_index] * 64;
83 }
84
85 std::array<Common::FixedPoint<50, 14>, NumChannels> delay_samples{};
86 for (u32 channel = 0; channel < NumChannels; channel++) {
87 delay_samples[channel] = state.delay_lines[channel].Read();
88 }
89
90 // clang-format off
91 std::array<std::array<Common::FixedPoint<18, 14>, NumChannels>, NumChannels> matrix{};
92 if constexpr (NumChannels == 1) {
93 matrix = {{
94 {state.feedback_gain},
95 }};
96 } else if constexpr (NumChannels == 2) {
97 matrix = {{
98 {state.delay_feedback_gain, state.delay_feedback_cross_gain},
99 {state.delay_feedback_cross_gain, state.delay_feedback_gain},
100 }};
101 } else if constexpr (NumChannels == 4) {
102 matrix = {{
103 {state.delay_feedback_gain, state.delay_feedback_cross_gain, state.delay_feedback_cross_gain, 0.0f},
104 {state.delay_feedback_cross_gain, state.delay_feedback_gain, 0.0f, state.delay_feedback_cross_gain},
105 {state.delay_feedback_cross_gain, 0.0f, state.delay_feedback_gain, state.delay_feedback_cross_gain},
106 {0.0f, state.delay_feedback_cross_gain, state.delay_feedback_cross_gain, state.delay_feedback_gain},
107 }};
108 } else if constexpr (NumChannels == 6) {
109 matrix = {{
110 {state.delay_feedback_gain, 0.0f, state.delay_feedback_cross_gain, 0.0f, state.delay_feedback_cross_gain, 0.0f},
111 {0.0f, state.delay_feedback_gain, state.delay_feedback_cross_gain, 0.0f, 0.0f, state.delay_feedback_cross_gain},
112 {state.delay_feedback_cross_gain, state.delay_feedback_cross_gain, state.delay_feedback_gain, 0.0f, 0.0f, 0.0f},
113 {0.0f, 0.0f, 0.0f, params.feedback_gain, 0.0f, 0.0f},
114 {state.delay_feedback_cross_gain, 0.0f, 0.0f, 0.0f, state.delay_feedback_gain, state.delay_feedback_cross_gain},
115 {0.0f, state.delay_feedback_cross_gain, 0.0f, 0.0f, state.delay_feedback_cross_gain, state.delay_feedback_gain},
116 }};
117 }
118 // clang-format on
119
120 std::array<Common::FixedPoint<50, 14>, NumChannels> gained_samples{};
121 for (u32 channel = 0; channel < NumChannels; channel++) {
122 Common::FixedPoint<50, 14> delay{};
123 for (u32 j = 0; j < NumChannels; j++) {
124 delay += delay_samples[j] * matrix[j][channel];
125 }
126 gained_samples[channel] = input_samples[channel] * params.in_gain + delay;
127 }
128
129 for (u32 channel = 0; channel < NumChannels; channel++) {
130 state.lowpass_z[channel] = gained_samples[channel] * state.lowpass_gain +
131 state.lowpass_z[channel] * state.lowpass_feedback_gain;
132 state.delay_lines[channel].Write(state.lowpass_z[channel]);
133 }
134
135 for (u32 channel = 0; channel < NumChannels; channel++) {
136 outputs[channel][sample_index] = (input_samples[channel] * params.dry_gain +
137 delay_samples[channel] * params.wet_gain)
138 .to_int_floor() /
139 64;
140 }
141 }
142}
143
144/**
145 * Apply a delay effect if enabled, according to the parameters and current state, on the input mix
146 * buffers, saving the results to the output mix buffers.
147 *
148 * @param params - Input parameters to use.
149 * @param state - State to use, must be initialized (see InitializeDelayEffect).
150 * @param enabled - If enabled, delay will be applied, otherwise input is copied to output.
151 * @param inputs - Input mix buffers to performan the delay on.
152 * @param outputs - Output mix buffers to receive the delayed samples.
153 * @param sample_count - Number of samples to process.
154 */
155static void ApplyDelayEffect(const DelayInfo::ParameterVersion1& params, DelayInfo::State& state,
156 const bool enabled, std::vector<std::span<const s32>>& inputs,
157 std::vector<std::span<s32>>& outputs, const u32 sample_count) {
158
159 if (!IsChannelCountValid(params.channel_count)) {
160 LOG_ERROR(Service_Audio, "Invalid delay channels {}", params.channel_count);
161 return;
162 }
163
164 if (enabled) {
165 switch (params.channel_count) {
166 case 1:
167 ApplyDelay<1>(params, state, inputs, outputs, sample_count);
168 break;
169 case 2:
170 ApplyDelay<2>(params, state, inputs, outputs, sample_count);
171 break;
172 case 4:
173 ApplyDelay<4>(params, state, inputs, outputs, sample_count);
174 break;
175 case 6:
176 ApplyDelay<6>(params, state, inputs, outputs, sample_count);
177 break;
178 default:
179 for (u32 channel = 0; channel < params.channel_count; channel++) {
180 if (inputs[channel].data() != outputs[channel].data()) {
181 std::memcpy(outputs[channel].data(), inputs[channel].data(),
182 sample_count * sizeof(s32));
183 }
184 }
185 break;
186 }
187 } else {
188 for (u32 channel = 0; channel < params.channel_count; channel++) {
189 if (inputs[channel].data() != outputs[channel].data()) {
190 std::memcpy(outputs[channel].data(), inputs[channel].data(),
191 sample_count * sizeof(s32));
192 }
193 }
194 }
195}
196
197void DelayCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
198 std::string& string) {
199 string += fmt::format("DelayCommand\n\tenabled {} \n\tinputs: ", effect_enabled);
200 for (u32 i = 0; i < MaxChannels; i++) {
201 string += fmt::format("{:02X}, ", inputs[i]);
202 }
203 string += "\n\toutputs: ";
204 for (u32 i = 0; i < MaxChannels; i++) {
205 string += fmt::format("{:02X}, ", outputs[i]);
206 }
207 string += "\n";
208}
209
210void DelayCommand::Process(const ADSP::CommandListProcessor& processor) {
211 std::vector<std::span<const s32>> input_buffers(parameter.channel_count);
212 std::vector<std::span<s32>> output_buffers(parameter.channel_count);
213
214 for (s16 i = 0; i < parameter.channel_count; i++) {
215 input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count,
216 processor.sample_count);
217 output_buffers[i] = processor.mix_buffers.subspan(outputs[i] * processor.sample_count,
218 processor.sample_count);
219 }
220
221 auto state_{reinterpret_cast<DelayInfo::State*>(state)};
222
223 if (effect_enabled) {
224 if (parameter.state == DelayInfo::ParameterState::Updating) {
225 SetDelayEffectParameter(parameter, *state_);
226 } else if (parameter.state == DelayInfo::ParameterState::Initialized) {
227 InitializeDelayEffect(parameter, *state_, workbuffer);
228 }
229 }
230 ApplyDelayEffect(parameter, *state_, effect_enabled, input_buffers, output_buffers,
231 processor.sample_count);
232}
233
234bool DelayCommand::Verify(const ADSP::CommandListProcessor& processor) {
235 return true;
236}
237
238} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/effect/delay.h b/src/audio_core/renderer/command/effect/delay.h
new file mode 100644
index 000000000..b7a15ae6b
--- /dev/null
+++ b/src/audio_core/renderer/command/effect/delay.h
@@ -0,0 +1,60 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7#include <string>
8
9#include "audio_core/renderer/command/icommand.h"
10#include "audio_core/renderer/effect/delay.h"
11#include "common/common_types.h"
12
13namespace AudioCore::AudioRenderer {
14namespace ADSP {
15class CommandListProcessor;
16}
17
18/**
19 * AudioRenderer command for a delay effect. Delays inputs mix buffers according to the parameters
20 * and state, outputs receives the delayed samples.
21 */
22struct DelayCommand : ICommand {
23 /**
24 * Print this command's information to a string.
25 *
26 * @param processor - The CommandListProcessor processing this command.
27 * @param string - The string to print into.
28 */
29 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
30
31 /**
32 * Process this command.
33 *
34 * @param processor - The CommandListProcessor processing this command.
35 */
36 void Process(const ADSP::CommandListProcessor& processor) override;
37
38 /**
39 * Verify this command's data is valid.
40 *
41 * @param processor - The CommandListProcessor processing this command.
42 * @return True if the command is valid, otherwise false.
43 */
44 bool Verify(const ADSP::CommandListProcessor& processor) override;
45
46 /// Input mix buffer offsets for each channel
47 std::array<s16, MaxChannels> inputs;
48 /// Output mix buffer offsets for each channel
49 std::array<s16, MaxChannels> outputs;
50 /// Input parameters
51 DelayInfo::ParameterVersion1 parameter;
52 /// State, updated each call
53 CpuAddr state;
54 /// Game-supplied workbuffer (Unused)
55 CpuAddr workbuffer;
56 /// Is this effect enabled?
57 bool effect_enabled;
58};
59
60} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/effect/i3dl2_reverb.cpp b/src/audio_core/renderer/command/effect/i3dl2_reverb.cpp
new file mode 100644
index 000000000..c4bf3943a
--- /dev/null
+++ b/src/audio_core/renderer/command/effect/i3dl2_reverb.cpp
@@ -0,0 +1,437 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <numbers>
5
6#include "audio_core/renderer/adsp/command_list_processor.h"
7#include "audio_core/renderer/command/effect/i3dl2_reverb.h"
8
9namespace AudioCore::AudioRenderer {
10
11constexpr std::array<f32, I3dl2ReverbInfo::MaxDelayLines> MinDelayLineTimes{
12 5.0f,
13 6.0f,
14 13.0f,
15 14.0f,
16};
17constexpr std::array<f32, I3dl2ReverbInfo::MaxDelayLines> MaxDelayLineTimes{
18 45.7042007446f,
19 82.7817001343f,
20 149.938293457f,
21 271.575805664f,
22};
23constexpr std::array<f32, I3dl2ReverbInfo::MaxDelayLines> Decay0MaxDelayLineTimes{17.0f, 13.0f,
24 9.0f, 7.0f};
25constexpr std::array<f32, I3dl2ReverbInfo::MaxDelayLines> Decay1MaxDelayLineTimes{19.0f, 11.0f,
26 10.0f, 6.0f};
27constexpr std::array<f32, I3dl2ReverbInfo::MaxDelayTaps> EarlyTapTimes{
28 0.0171360000968f,
29 0.0591540001333f,
30 0.161733001471f,
31 0.390186011791f,
32 0.425262004137f,
33 0.455410987139f,
34 0.689737021923f,
35 0.74590998888f,
36 0.833844006062f,
37 0.859502017498f,
38 0.0f,
39 0.0750240013003f,
40 0.168788000941f,
41 0.299901008606f,
42 0.337442994118f,
43 0.371903002262f,
44 0.599011003971f,
45 0.716741025448f,
46 0.817858994007f,
47 0.85166400671f,
48};
49
50constexpr std::array<f32, I3dl2ReverbInfo::MaxDelayTaps> EarlyGains{
51 0.67096f, 0.61027f, 1.0f, 0.3568f, 0.68361f, 0.65978f, 0.51939f,
52 0.24712f, 0.45945f, 0.45021f, 0.64196f, 0.54879f, 0.92925f, 0.3827f,
53 0.72867f, 0.69794f, 0.5464f, 0.24563f, 0.45214f, 0.44042f};
54
55/**
56 * Update the I3dl2ReverbInfo state according to the given parameters.
57 *
58 * @param params - Input parameters to update the state.
59 * @param state - State to be updated.
60 * @param reset - If enabled, the state buffers will be reset. Only set this on initialize.
61 */
62static void UpdateI3dl2ReverbEffectParameter(const I3dl2ReverbInfo::ParameterVersion1& params,
63 I3dl2ReverbInfo::State& state, const bool reset) {
64 const auto pow_10 = [](f32 val) -> f32 {
65 return (val >= 0.0f) ? 1.0f : (val <= -5.3f) ? 0.0f : std::pow(10.0f, val);
66 };
67 const auto sin = [](f32 degrees) -> f32 {
68 return std::sin(degrees * std::numbers::pi_v<f32> / 180.0f);
69 };
70 const auto cos = [](f32 degrees) -> f32 {
71 return std::cos(degrees * std::numbers::pi_v<f32> / 180.0f);
72 };
73
74 Common::FixedPoint<50, 14> delay{static_cast<f32>(params.sample_rate) / 1000.0f};
75
76 state.dry_gain = params.dry_gain;
77 Common::FixedPoint<50, 14> early_gain{
78 std::min(params.room_gain + params.reflection_gain, 5000.0f) / 2000.0f};
79 state.early_gain = pow_10(early_gain.to_float());
80 Common::FixedPoint<50, 14> late_gain{std::min(params.room_gain + params.reverb_gain, 5000.0f) /
81 2000.0f};
82 state.late_gain = pow_10(late_gain.to_float());
83
84 Common::FixedPoint<50, 14> hf_gain{pow_10(params.room_HF_gain / 2000.0f)};
85 if (hf_gain >= 1.0f) {
86 state.lowpass_1 = 0.0f;
87 state.lowpass_2 = 1.0f;
88 } else {
89 const auto reference_hf{(params.reference_HF * 256.0f) /
90 static_cast<f32>(params.sample_rate)};
91 const Common::FixedPoint<50, 14> a{1.0f - hf_gain.to_float()};
92 const Common::FixedPoint<50, 14> b{2.0f + (-cos(reference_hf) * (hf_gain * 2.0f))};
93 const Common::FixedPoint<50, 14> c{
94 std::sqrt(std::pow(b.to_float(), 2.0f) + (std::pow(a.to_float(), 2.0f) * -4.0f))};
95
96 state.lowpass_1 = std::min(((b - c) / (a * 2.0f)).to_float(), 0.99723f);
97 state.lowpass_2 = 1.0f - state.lowpass_1;
98 }
99
100 state.early_to_late_taps =
101 (((params.reflection_delay + params.late_reverb_delay_time) * 1000.0f) * delay).to_int();
102 state.last_reverb_echo = params.late_reverb_diffusion * 0.6f * 0.01f;
103
104 for (u32 i = 0; i < I3dl2ReverbInfo::MaxDelayLines; i++) {
105 auto curr_delay{
106 ((MinDelayLineTimes[i] + (params.late_reverb_density / 100.0f) *
107 (MaxDelayLineTimes[i] - MinDelayLineTimes[i])) *
108 delay)
109 .to_int()};
110 state.fdn_delay_lines[i].SetDelay(curr_delay);
111
112 const auto a{
113 (static_cast<f32>(state.fdn_delay_lines[i].delay + state.decay_delay_lines0[i].delay +
114 state.decay_delay_lines1[i].delay) *
115 -60.0f) /
116 (params.late_reverb_decay_time * static_cast<f32>(params.sample_rate))};
117 const auto b{a / params.late_reverb_HF_decay_ratio};
118 const auto c{
119 cos(((params.reference_HF * 0.5f) * 128.0f) / static_cast<f32>(params.sample_rate)) /
120 sin(((params.reference_HF * 0.5f) * 128.0f) / static_cast<f32>(params.sample_rate))};
121 const auto d{pow_10((b - a) / 40.0f)};
122 const auto e{pow_10((b + a) / 40.0f) * 0.7071f};
123
124 state.lowpass_coeff[i][0] = ((c * d + 1.0f) * e) / (c + d);
125 state.lowpass_coeff[i][1] = ((1.0f - (c * d)) * e) / (c + d);
126 state.lowpass_coeff[i][2] = (c - d) / (c + d);
127
128 state.decay_delay_lines0[i].wet_gain = state.last_reverb_echo;
129 state.decay_delay_lines1[i].wet_gain = state.last_reverb_echo * -0.9f;
130 }
131
132 if (reset) {
133 state.shelf_filter.fill(0.0f);
134 state.lowpass_0 = 0.0f;
135 for (u32 i = 0; i < I3dl2ReverbInfo::MaxDelayLines; i++) {
136 std::ranges::fill(state.fdn_delay_lines[i].buffer, 0);
137 std::ranges::fill(state.decay_delay_lines0[i].buffer, 0);
138 std::ranges::fill(state.decay_delay_lines1[i].buffer, 0);
139 }
140 std::ranges::fill(state.center_delay_line.buffer, 0);
141 std::ranges::fill(state.early_delay_line.buffer, 0);
142 }
143
144 const auto reflection_time{(params.late_reverb_delay_time * 0.9998f + 0.02f) * 1000.0f};
145 const auto reflection_delay{params.reflection_delay * 1000.0f};
146 for (u32 i = 0; i < I3dl2ReverbInfo::MaxDelayTaps; i++) {
147 auto length{((reflection_delay + reflection_time * EarlyTapTimes[i]) * delay).to_int()};
148 if (length >= state.early_delay_line.max_delay) {
149 length = state.early_delay_line.max_delay;
150 }
151 state.early_tap_steps[i] = length;
152 }
153}
154
155/**
156 * Initialize a new I3dl2ReverbInfo state according to the given parameters.
157 *
158 * @param params - Input parameters to update the state.
159 * @param state - State to be updated.
160 * @param workbuffer - Game-supplied memory for the state. (Unused)
161 */
162static void InitializeI3dl2ReverbEffect(const I3dl2ReverbInfo::ParameterVersion1& params,
163 I3dl2ReverbInfo::State& state, const CpuAddr workbuffer) {
164 state = {};
165 Common::FixedPoint<50, 14> delay{static_cast<f32>(params.sample_rate) / 1000};
166
167 for (u32 i = 0; i < I3dl2ReverbInfo::MaxDelayLines; i++) {
168 auto fdn_delay_time{(MaxDelayLineTimes[i] * delay).to_uint_floor()};
169 state.fdn_delay_lines[i].Initialize(fdn_delay_time);
170
171 auto decay0_delay_time{(Decay0MaxDelayLineTimes[i] * delay).to_uint_floor()};
172 state.decay_delay_lines0[i].Initialize(decay0_delay_time);
173
174 auto decay1_delay_time{(Decay1MaxDelayLineTimes[i] * delay).to_uint_floor()};
175 state.decay_delay_lines1[i].Initialize(decay1_delay_time);
176 }
177
178 const auto center_delay_time{(5 * delay).to_uint_floor()};
179 state.center_delay_line.Initialize(center_delay_time);
180
181 const auto early_delay_time{(400 * delay).to_uint_floor()};
182 state.early_delay_line.Initialize(early_delay_time);
183
184 UpdateI3dl2ReverbEffectParameter(params, state, true);
185}
186
187/**
188 * Pass-through the effect, copying input to output directly, with no reverb applied.
189 *
190 * @param inputs - Array of input mix buffers to copy.
191 * @param outputs - Array of output mix buffers to receive copy.
192 * @param channel_count - Number of channels in inputs and outputs.
193 * @param sample_count - Number of samples within each channel (unused).
194 */
195static void ApplyI3dl2ReverbEffectBypass(std::span<std::span<const s32>> inputs,
196 std::span<std::span<s32>> outputs, const u32 channel_count,
197 [[maybe_unused]] const u32 sample_count) {
198 for (u32 i = 0; i < channel_count; i++) {
199 if (inputs[i].data() != outputs[i].data()) {
200 std::memcpy(outputs[i].data(), inputs[i].data(), outputs[i].size_bytes());
201 }
202 }
203}
204
205/**
206 * Tick the delay lines, reading and returning their current output, and writing a new decaying
207 * sample (mix).
208 *
209 * @param decay0 - The first decay line.
210 * @param decay1 - The second decay line.
211 * @param fdn - Feedback delay network.
212 * @param mix - The new calculated sample to be written and decayed.
213 * @return The next delayed and decayed sample.
214 */
215static Common::FixedPoint<50, 14> Axfx2AllPassTick(I3dl2ReverbInfo::I3dl2DelayLine& decay0,
216 I3dl2ReverbInfo::I3dl2DelayLine& decay1,
217 I3dl2ReverbInfo::I3dl2DelayLine& fdn,
218 const Common::FixedPoint<50, 14> mix) {
219 auto val{decay0.Read()};
220 auto mixed{mix - (val * decay0.wet_gain)};
221 auto out{decay0.Tick(mixed) + (mixed * decay0.wet_gain)};
222
223 val = decay1.Read();
224 mixed = out - (val * decay1.wet_gain);
225 out = decay1.Tick(mixed) + (mixed * decay1.wet_gain);
226
227 fdn.Tick(out);
228 return out;
229}
230
231/**
232 * Impl. Apply a I3DL2 reverb according to the current state, on the input mix buffers,
233 * saving the results to the output mix buffers.
234 *
235 * @tparam NumChannels - Number of channels to process. 1-6.
236 Inputs/outputs should have this many buffers.
237 * @param state - State to use, must be initialized (see InitializeI3dl2ReverbEffect).
238 * @param inputs - Input mix buffers to perform the reverb on.
239 * @param outputs - Output mix buffers to receive the reverbed samples.
240 * @param sample_count - Number of samples to process.
241 */
242template <size_t NumChannels>
243static void ApplyI3dl2ReverbEffect(I3dl2ReverbInfo::State& state,
244 std::span<std::span<const s32>> inputs,
245 std::span<std::span<s32>> outputs, const u32 sample_count) {
246 constexpr std::array<u8, I3dl2ReverbInfo::MaxDelayTaps> OutTapIndexes1Ch{
247 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
248 };
249 constexpr std::array<u8, I3dl2ReverbInfo::MaxDelayTaps> OutTapIndexes2Ch{
250 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1,
251 };
252 constexpr std::array<u8, I3dl2ReverbInfo::MaxDelayTaps> OutTapIndexes4Ch{
253 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 0, 0, 0, 0, 3, 3, 3,
254 };
255 constexpr std::array<u8, I3dl2ReverbInfo::MaxDelayTaps> OutTapIndexes6Ch{
256 2, 0, 0, 1, 1, 1, 1, 4, 4, 4, 1, 1, 1, 0, 0, 0, 0, 5, 5, 5,
257 };
258
259 std::span<const u8> tap_indexes{};
260 if constexpr (NumChannels == 1) {
261 tap_indexes = OutTapIndexes1Ch;
262 } else if constexpr (NumChannels == 2) {
263 tap_indexes = OutTapIndexes2Ch;
264 } else if constexpr (NumChannels == 4) {
265 tap_indexes = OutTapIndexes4Ch;
266 } else if constexpr (NumChannels == 6) {
267 tap_indexes = OutTapIndexes6Ch;
268 }
269
270 for (u32 sample_index = 0; sample_index < sample_count; sample_index++) {
271 Common::FixedPoint<50, 14> early_to_late_tap{
272 state.early_delay_line.TapOut(state.early_to_late_taps)};
273 std::array<Common::FixedPoint<50, 14>, NumChannels> output_samples{};
274
275 for (u32 early_tap = 0; early_tap < I3dl2ReverbInfo::MaxDelayTaps; early_tap++) {
276 output_samples[tap_indexes[early_tap]] +=
277 state.early_delay_line.TapOut(state.early_tap_steps[early_tap]) *
278 EarlyGains[early_tap];
279 if constexpr (NumChannels == 6) {
280 output_samples[static_cast<u32>(Channels::LFE)] +=
281 state.early_delay_line.TapOut(state.early_tap_steps[early_tap]) *
282 EarlyGains[early_tap];
283 }
284 }
285
286 Common::FixedPoint<50, 14> current_sample{};
287 for (u32 channel = 0; channel < NumChannels; channel++) {
288 current_sample += inputs[channel][sample_index];
289 }
290
291 state.lowpass_0 =
292 (current_sample * state.lowpass_2 + state.lowpass_0 * state.lowpass_1).to_float();
293 state.early_delay_line.Tick(state.lowpass_0);
294
295 for (u32 channel = 0; channel < NumChannels; channel++) {
296 output_samples[channel] *= state.early_gain;
297 }
298
299 std::array<Common::FixedPoint<50, 14>, I3dl2ReverbInfo::MaxDelayLines> filtered_samples{};
300 for (u32 delay_line = 0; delay_line < I3dl2ReverbInfo::MaxDelayLines; delay_line++) {
301 filtered_samples[delay_line] =
302 state.fdn_delay_lines[delay_line].Read() * state.lowpass_coeff[delay_line][0] +
303 state.shelf_filter[delay_line];
304 state.shelf_filter[delay_line] =
305 (filtered_samples[delay_line] * state.lowpass_coeff[delay_line][2] +
306 state.fdn_delay_lines[delay_line].Read() * state.lowpass_coeff[delay_line][1])
307 .to_float();
308 }
309
310 const std::array<Common::FixedPoint<50, 14>, I3dl2ReverbInfo::MaxDelayLines> mix_matrix{
311 filtered_samples[1] + filtered_samples[2] + early_to_late_tap * state.late_gain,
312 -filtered_samples[0] - filtered_samples[3] + early_to_late_tap * state.late_gain,
313 filtered_samples[0] - filtered_samples[3] + early_to_late_tap * state.late_gain,
314 filtered_samples[1] - filtered_samples[2] + early_to_late_tap * state.late_gain,
315 };
316
317 std::array<Common::FixedPoint<50, 14>, I3dl2ReverbInfo::MaxDelayLines> allpass_samples{};
318 for (u32 delay_line = 0; delay_line < I3dl2ReverbInfo::MaxDelayLines; delay_line++) {
319 allpass_samples[delay_line] = Axfx2AllPassTick(
320 state.decay_delay_lines0[delay_line], state.decay_delay_lines1[delay_line],
321 state.fdn_delay_lines[delay_line], mix_matrix[delay_line]);
322 }
323
324 if constexpr (NumChannels == 6) {
325 const std::array<Common::FixedPoint<50, 14>, MaxChannels> allpass_outputs{
326 allpass_samples[0], allpass_samples[1], allpass_samples[2] - allpass_samples[3],
327 allpass_samples[3], allpass_samples[2], allpass_samples[3],
328 };
329
330 for (u32 channel = 0; channel < NumChannels; channel++) {
331 Common::FixedPoint<50, 14> allpass{};
332
333 if (channel == static_cast<u32>(Channels::Center)) {
334 allpass = state.center_delay_line.Tick(allpass_outputs[channel] * 0.5f);
335 } else {
336 allpass = allpass_outputs[channel];
337 }
338
339 auto out_sample{output_samples[channel] + allpass +
340 state.dry_gain * static_cast<f32>(inputs[channel][sample_index])};
341
342 outputs[channel][sample_index] =
343 static_cast<s32>(std::clamp(out_sample.to_float(), -8388600.0f, 8388600.0f));
344 }
345 } else {
346 for (u32 channel = 0; channel < NumChannels; channel++) {
347 auto out_sample{output_samples[channel] + allpass_samples[channel] +
348 state.dry_gain * static_cast<f32>(inputs[channel][sample_index])};
349 outputs[channel][sample_index] =
350 static_cast<s32>(std::clamp(out_sample.to_float(), -8388600.0f, 8388600.0f));
351 }
352 }
353 }
354}
355
356/**
357 * Apply a I3DL2 reverb if enabled, according to the current state, on the input mix buffers,
358 * saving the results to the output mix buffers.
359 *
360 * @param params - Input parameters to use.
361 * @param state - State to use, must be initialized (see InitializeI3dl2ReverbEffect).
362 * @param enabled - If enabled, delay will be applied, otherwise input is copied to output.
363 * @param inputs - Input mix buffers to performan the delay on.
364 * @param outputs - Output mix buffers to receive the delayed samples.
365 * @param sample_count - Number of samples to process.
366 */
367static void ApplyI3dl2ReverbEffect(const I3dl2ReverbInfo::ParameterVersion1& params,
368 I3dl2ReverbInfo::State& state, const bool enabled,
369 std::span<std::span<const s32>> inputs,
370 std::span<std::span<s32>> outputs, const u32 sample_count) {
371 if (enabled) {
372 switch (params.channel_count) {
373 case 0:
374 return;
375 case 1:
376 ApplyI3dl2ReverbEffect<1>(state, inputs, outputs, sample_count);
377 break;
378 case 2:
379 ApplyI3dl2ReverbEffect<2>(state, inputs, outputs, sample_count);
380 break;
381 case 4:
382 ApplyI3dl2ReverbEffect<4>(state, inputs, outputs, sample_count);
383 break;
384 case 6:
385 ApplyI3dl2ReverbEffect<6>(state, inputs, outputs, sample_count);
386 break;
387 default:
388 ApplyI3dl2ReverbEffectBypass(inputs, outputs, params.channel_count, sample_count);
389 break;
390 }
391 } else {
392 ApplyI3dl2ReverbEffectBypass(inputs, outputs, params.channel_count, sample_count);
393 }
394}
395
396void I3dl2ReverbCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
397 std::string& string) {
398 string += fmt::format("I3dl2ReverbCommand\n\tenabled {} \n\tinputs: ", effect_enabled);
399 for (u32 i = 0; i < parameter.channel_count; i++) {
400 string += fmt::format("{:02X}, ", inputs[i]);
401 }
402 string += "\n\toutputs: ";
403 for (u32 i = 0; i < parameter.channel_count; i++) {
404 string += fmt::format("{:02X}, ", outputs[i]);
405 }
406 string += "\n";
407}
408
409void I3dl2ReverbCommand::Process(const ADSP::CommandListProcessor& processor) {
410 std::vector<std::span<const s32>> input_buffers(parameter.channel_count);
411 std::vector<std::span<s32>> output_buffers(parameter.channel_count);
412
413 for (u32 i = 0; i < parameter.channel_count; i++) {
414 input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count,
415 processor.sample_count);
416 output_buffers[i] = processor.mix_buffers.subspan(outputs[i] * processor.sample_count,
417 processor.sample_count);
418 }
419
420 auto state_{reinterpret_cast<I3dl2ReverbInfo::State*>(state)};
421
422 if (effect_enabled) {
423 if (parameter.state == I3dl2ReverbInfo::ParameterState::Updating) {
424 UpdateI3dl2ReverbEffectParameter(parameter, *state_, false);
425 } else if (parameter.state == I3dl2ReverbInfo::ParameterState::Initialized) {
426 InitializeI3dl2ReverbEffect(parameter, *state_, workbuffer);
427 }
428 }
429 ApplyI3dl2ReverbEffect(parameter, *state_, effect_enabled, input_buffers, output_buffers,
430 processor.sample_count);
431}
432
433bool I3dl2ReverbCommand::Verify(const ADSP::CommandListProcessor& processor) {
434 return true;
435}
436
437} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/effect/i3dl2_reverb.h b/src/audio_core/renderer/command/effect/i3dl2_reverb.h
new file mode 100644
index 000000000..243877056
--- /dev/null
+++ b/src/audio_core/renderer/command/effect/i3dl2_reverb.h
@@ -0,0 +1,60 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7#include <string>
8
9#include "audio_core/renderer/command/icommand.h"
10#include "audio_core/renderer/effect/i3dl2.h"
11#include "common/common_types.h"
12
13namespace AudioCore::AudioRenderer {
14namespace ADSP {
15class CommandListProcessor;
16}
17
18/**
19 * AudioRenderer command for a I3DL2Reverb effect. Apply a reverb to inputs mix buffer according to
20 * the I3DL2 spec, outputs receives the results.
21 */
22struct I3dl2ReverbCommand : ICommand {
23 /**
24 * Print this command's information to a string.
25 *
26 * @param processor - The CommandListProcessor processing this command.
27 * @param string - The string to print into.
28 */
29 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
30
31 /**
32 * Process this command.
33 *
34 * @param processor - The CommandListProcessor processing this command.
35 */
36 void Process(const ADSP::CommandListProcessor& processor) override;
37
38 /**
39 * Verify this command's data is valid.
40 *
41 * @param processor - The CommandListProcessor processing this command.
42 * @return True if the command is valid, otherwise false.
43 */
44 bool Verify(const ADSP::CommandListProcessor& processor) override;
45
46 /// Input mix buffer offsets for each channel
47 std::array<s16, MaxChannels> inputs;
48 /// Output mix buffer offsets for each channel
49 std::array<s16, MaxChannels> outputs;
50 /// Input parameters
51 I3dl2ReverbInfo::ParameterVersion1 parameter;
52 /// State, updated each call
53 CpuAddr state;
54 /// Game-supplied workbuffer (Unused)
55 CpuAddr workbuffer;
56 /// Is this effect enabled?
57 bool effect_enabled;
58};
59
60} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/effect/light_limiter.cpp b/src/audio_core/renderer/command/effect/light_limiter.cpp
new file mode 100644
index 000000000..e8fb0e2fc
--- /dev/null
+++ b/src/audio_core/renderer/command/effect/light_limiter.cpp
@@ -0,0 +1,222 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/adsp/command_list_processor.h"
5#include "audio_core/renderer/command/effect/light_limiter.h"
6
7namespace AudioCore::AudioRenderer {
8/**
9 * Update the LightLimiterInfo state according to the given parameters.
10 * A no-op.
11 *
12 * @param params - Input parameters to update the state.
13 * @param state - State to be updated.
14 */
15static void UpdateLightLimiterEffectParameter(const LightLimiterInfo::ParameterVersion2& params,
16 LightLimiterInfo::State& state) {}
17
18/**
19 * Initialize a new LightLimiterInfo state according to the given parameters.
20 *
21 * @param params - Input parameters to update the state.
22 * @param state - State to be updated.
23 * @param workbuffer - Game-supplied memory for the state. (Unused)
24 */
25static void InitializeLightLimiterEffect(const LightLimiterInfo::ParameterVersion2& params,
26 LightLimiterInfo::State& state, const CpuAddr workbuffer) {
27 state = {};
28 state.samples_average.fill(0.0f);
29 state.compression_gain.fill(1.0f);
30 state.look_ahead_sample_offsets.fill(0);
31 for (u32 i = 0; i < params.channel_count; i++) {
32 state.look_ahead_sample_buffers[i].resize(params.look_ahead_samples_max, 0.0f);
33 }
34}
35
36/**
37 * Apply a light limiter effect if enabled, according to the current state, on the input mix
38 * buffers, saving the results to the output mix buffers.
39 *
40 * @param params - Input parameters to use.
41 * @param state - State to use, must be initialized (see InitializeLightLimiterEffect).
42 * @param enabled - If enabled, limiter will be applied, otherwise input is copied to output.
43 * @param inputs - Input mix buffers to perform the limiter on.
44 * @param outputs - Output mix buffers to receive the limited samples.
45 * @param sample_count - Number of samples to process.
46 * @params statistics - Optional output statistics, only used with version 2.
47 */
48static void ApplyLightLimiterEffect(const LightLimiterInfo::ParameterVersion2& params,
49 LightLimiterInfo::State& state, const bool enabled,
50 std::vector<std::span<const s32>>& inputs,
51 std::vector<std::span<s32>>& outputs, const u32 sample_count,
52 LightLimiterInfo::StatisticsInternal* statistics) {
53 constexpr s64 min{std::numeric_limits<s32>::min()};
54 constexpr s64 max{std::numeric_limits<s32>::max()};
55
56 const auto recip_estimate = [](f64 a) -> f64 {
57 s32 q, s;
58 f64 r;
59 q = (s32)(a * 512.0); /* a in units of 1/512 rounded down */
60 r = 1.0 / (((f64)q + 0.5) / 512.0); /* reciprocal r */
61 s = (s32)(256.0 * r + 0.5); /* r in units of 1/256 rounded to nearest */
62 return ((f64)s / 256.0);
63 };
64
65 if (enabled) {
66 if (statistics && params.statistics_reset_required) {
67 for (u32 i = 0; i < params.channel_count; i++) {
68 statistics->channel_compression_gain_min[i] = 1.0f;
69 statistics->channel_max_sample[i] = 0;
70 }
71 }
72
73 for (u32 sample_index = 0; sample_index < sample_count; sample_index++) {
74 for (u32 channel = 0; channel < params.channel_count; channel++) {
75 auto sample{(Common::FixedPoint<49, 15>(inputs[channel][sample_index]) /
76 Common::FixedPoint<49, 15>::one) *
77 params.input_gain};
78 auto abs_sample{sample};
79 if (sample < 0.0f) {
80 abs_sample = -sample;
81 }
82 auto coeff{abs_sample > state.samples_average[channel] ? params.attack_coeff
83 : params.release_coeff};
84 state.samples_average[channel] +=
85 ((abs_sample - state.samples_average[channel]) * coeff).to_float();
86
87 // Reciprocal estimate
88 auto new_average_sample{Common::FixedPoint<49, 15>(
89 recip_estimate(state.samples_average[channel].to_double()))};
90 if (params.processing_mode != LightLimiterInfo::ProcessingMode::Mode1) {
91 // Two Newton-Raphson steps
92 auto temp{2.0 - (state.samples_average[channel] * new_average_sample)};
93 new_average_sample = 2.0 - (state.samples_average[channel] * temp);
94 }
95
96 auto above_threshold{state.samples_average[channel] > params.threshold};
97 auto attenuation{above_threshold ? params.threshold * new_average_sample : 1.0f};
98 coeff = attenuation < state.compression_gain[channel] ? params.attack_coeff
99 : params.release_coeff;
100 state.compression_gain[channel] +=
101 (attenuation - state.compression_gain[channel]) * coeff;
102
103 auto lookahead_sample{
104 state.look_ahead_sample_buffers[channel]
105 [state.look_ahead_sample_offsets[channel]]};
106
107 state.look_ahead_sample_buffers[channel][state.look_ahead_sample_offsets[channel]] =
108 sample;
109 state.look_ahead_sample_offsets[channel] =
110 (state.look_ahead_sample_offsets[channel] + 1) % params.look_ahead_samples_min;
111
112 outputs[channel][sample_index] = static_cast<s32>(
113 std::clamp((lookahead_sample * state.compression_gain[channel] *
114 params.output_gain * Common::FixedPoint<49, 15>::one)
115 .to_long(),
116 min, max));
117
118 if (statistics) {
119 statistics->channel_max_sample[channel] =
120 std::max(statistics->channel_max_sample[channel], abs_sample.to_float());
121 statistics->channel_compression_gain_min[channel] =
122 std::min(statistics->channel_compression_gain_min[channel],
123 state.compression_gain[channel].to_float());
124 }
125 }
126 }
127 } else {
128 for (u32 i = 0; i < params.channel_count; i++) {
129 if (params.inputs[i] != params.outputs[i]) {
130 std::memcpy(outputs[i].data(), inputs[i].data(), outputs[i].size_bytes());
131 }
132 }
133 }
134}
135
136void LightLimiterVersion1Command::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
137 std::string& string) {
138 string += fmt::format("LightLimiterVersion1Command\n\tinputs: ");
139 for (u32 i = 0; i < MaxChannels; i++) {
140 string += fmt::format("{:02X}, ", inputs[i]);
141 }
142 string += "\n\toutputs: ";
143 for (u32 i = 0; i < MaxChannels; i++) {
144 string += fmt::format("{:02X}, ", outputs[i]);
145 }
146 string += "\n";
147}
148
149void LightLimiterVersion1Command::Process(const ADSP::CommandListProcessor& processor) {
150 std::vector<std::span<const s32>> input_buffers(parameter.channel_count);
151 std::vector<std::span<s32>> output_buffers(parameter.channel_count);
152
153 for (u32 i = 0; i < parameter.channel_count; i++) {
154 input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count,
155 processor.sample_count);
156 output_buffers[i] = processor.mix_buffers.subspan(outputs[i] * processor.sample_count,
157 processor.sample_count);
158 }
159
160 auto state_{reinterpret_cast<LightLimiterInfo::State*>(state)};
161
162 if (effect_enabled) {
163 if (parameter.state == LightLimiterInfo::ParameterState::Updating) {
164 UpdateLightLimiterEffectParameter(parameter, *state_);
165 } else if (parameter.state == LightLimiterInfo::ParameterState::Initialized) {
166 InitializeLightLimiterEffect(parameter, *state_, workbuffer);
167 }
168 }
169
170 LightLimiterInfo::StatisticsInternal* statistics{nullptr};
171 ApplyLightLimiterEffect(parameter, *state_, effect_enabled, input_buffers, output_buffers,
172 processor.sample_count, statistics);
173}
174
175bool LightLimiterVersion1Command::Verify(const ADSP::CommandListProcessor& processor) {
176 return true;
177}
178
179void LightLimiterVersion2Command::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
180 std::string& string) {
181 string += fmt::format("LightLimiterVersion2Command\n\tinputs: \n");
182 for (u32 i = 0; i < MaxChannels; i++) {
183 string += fmt::format("{:02X}, ", inputs[i]);
184 }
185 string += "\n\toutputs: ";
186 for (u32 i = 0; i < MaxChannels; i++) {
187 string += fmt::format("{:02X}, ", outputs[i]);
188 }
189 string += "\n";
190}
191
192void LightLimiterVersion2Command::Process(const ADSP::CommandListProcessor& processor) {
193 std::vector<std::span<const s32>> input_buffers(parameter.channel_count);
194 std::vector<std::span<s32>> output_buffers(parameter.channel_count);
195
196 for (u32 i = 0; i < parameter.channel_count; i++) {
197 input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count,
198 processor.sample_count);
199 output_buffers[i] = processor.mix_buffers.subspan(outputs[i] * processor.sample_count,
200 processor.sample_count);
201 }
202
203 auto state_{reinterpret_cast<LightLimiterInfo::State*>(state)};
204
205 if (effect_enabled) {
206 if (parameter.state == LightLimiterInfo::ParameterState::Updating) {
207 UpdateLightLimiterEffectParameter(parameter, *state_);
208 } else if (parameter.state == LightLimiterInfo::ParameterState::Initialized) {
209 InitializeLightLimiterEffect(parameter, *state_, workbuffer);
210 }
211 }
212
213 auto statistics{reinterpret_cast<LightLimiterInfo::StatisticsInternal*>(result_state)};
214 ApplyLightLimiterEffect(parameter, *state_, effect_enabled, input_buffers, output_buffers,
215 processor.sample_count, statistics);
216}
217
218bool LightLimiterVersion2Command::Verify(const ADSP::CommandListProcessor& processor) {
219 return true;
220}
221
222} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/effect/light_limiter.h b/src/audio_core/renderer/command/effect/light_limiter.h
new file mode 100644
index 000000000..5d98272c7
--- /dev/null
+++ b/src/audio_core/renderer/command/effect/light_limiter.h
@@ -0,0 +1,103 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7#include <string>
8
9#include "audio_core/renderer/command/icommand.h"
10#include "audio_core/renderer/effect/light_limiter.h"
11#include "common/common_types.h"
12
13namespace AudioCore::AudioRenderer {
14namespace ADSP {
15class CommandListProcessor;
16}
17
18/**
19 * AudioRenderer command for limiting volume between a high and low threshold.
20 * Version 1.
21 */
22struct LightLimiterVersion1Command : ICommand {
23 /**
24 * Print this command's information to a string.
25 *
26 * @param processor - The CommandListProcessor processing this command.
27 * @param string - The string to print into.
28 */
29 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
30
31 /**
32 * Process this command.
33 *
34 * @param processor - The CommandListProcessor processing this command.
35 */
36 void Process(const ADSP::CommandListProcessor& processor) override;
37
38 /**
39 * Verify this command's data is valid.
40 *
41 * @param processor - The CommandListProcessor processing this command.
42 * @return True if the command is valid, otherwise false.
43 */
44 bool Verify(const ADSP::CommandListProcessor& processor) override;
45
46 /// Input mix buffer offsets for each channel
47 std::array<s16, MaxChannels> inputs;
48 /// Output mix buffer offsets for each channel
49 std::array<s16, MaxChannels> outputs;
50 /// Input parameters
51 LightLimiterInfo::ParameterVersion2 parameter;
52 /// State, updated each call
53 CpuAddr state;
54 /// Game-supplied workbuffer (Unused)
55 CpuAddr workbuffer;
56 /// Is this effect enabled?
57 bool effect_enabled;
58};
59
60/**
61 * AudioRenderer command for limiting volume between a high and low threshold.
62 * Version 2 with output statistics.
63 */
64struct LightLimiterVersion2Command : ICommand {
65 /**
66 * Print this command's information to a string.
67 *
68 * @param processor - The CommandListProcessor processing this command.
69 * @param string - The string to print into.
70 */
71 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
72
73 /**
74 * Process this command.
75 *
76 * @param processor - The CommandListProcessor processing this command.
77 */
78 void Process(const ADSP::CommandListProcessor& processor) override;
79
80 /**
81 * Verify this command's data is valid.
82 *
83 * @param processor - The CommandListProcessor processing this command.
84 */
85 bool Verify(const ADSP::CommandListProcessor& processor) override;
86
87 /// Input mix buffer offsets for each channel
88 std::array<s16, MaxChannels> inputs;
89 /// Output mix buffer offsets for each channel
90 std::array<s16, MaxChannels> outputs;
91 /// Input parameters
92 LightLimiterInfo::ParameterVersion2 parameter;
93 /// State, updated each call
94 CpuAddr state;
95 /// Game-supplied workbuffer (Unused)
96 CpuAddr workbuffer;
97 /// Optional statistics, sent back to the sysmodule
98 CpuAddr result_state;
99 /// Is this effect enabled?
100 bool effect_enabled;
101};
102
103} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/effect/multi_tap_biquad_filter.cpp b/src/audio_core/renderer/command/effect/multi_tap_biquad_filter.cpp
new file mode 100644
index 000000000..b3c3ba4ba
--- /dev/null
+++ b/src/audio_core/renderer/command/effect/multi_tap_biquad_filter.cpp
@@ -0,0 +1,45 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/adsp/command_list_processor.h"
5#include "audio_core/renderer/command/effect/biquad_filter.h"
6#include "audio_core/renderer/command/effect/multi_tap_biquad_filter.h"
7
8namespace AudioCore::AudioRenderer {
9
10void MultiTapBiquadFilterCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
11 std::string& string) {
12 string += fmt::format(
13 "MultiTapBiquadFilterCommand\n\tinput {:02X}\n\toutput {:02X}\n\tneeds_init ({}, {})\n",
14 input, output, needs_init[0], needs_init[1]);
15}
16
17void MultiTapBiquadFilterCommand::Process(const ADSP::CommandListProcessor& processor) {
18 if (filter_tap_count > MaxBiquadFilters) {
19 LOG_ERROR(Service_Audio, "Too many filter taps! {}", filter_tap_count);
20 filter_tap_count = MaxBiquadFilters;
21 }
22
23 auto input_buffer{
24 processor.mix_buffers.subspan(input * processor.sample_count, processor.sample_count)};
25 auto output_buffer{
26 processor.mix_buffers.subspan(output * processor.sample_count, processor.sample_count)};
27
28 // TODO: Fix this, currently just applies the filter to the input twice,
29 // and doesn't chain the biquads together at all.
30 for (u32 i = 0; i < filter_tap_count; i++) {
31 auto state{reinterpret_cast<VoiceState::BiquadFilterState*>(states[i])};
32 if (needs_init[i]) {
33 std::memset(state, 0, sizeof(VoiceState::BiquadFilterState));
34 }
35
36 ApplyBiquadFilterFloat(output_buffer, input_buffer, biquads[i].b, biquads[i].a, *state,
37 processor.sample_count);
38 }
39}
40
41bool MultiTapBiquadFilterCommand::Verify(const ADSP::CommandListProcessor& processor) {
42 return true;
43}
44
45} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/effect/multi_tap_biquad_filter.h b/src/audio_core/renderer/command/effect/multi_tap_biquad_filter.h
new file mode 100644
index 000000000..99c2c0830
--- /dev/null
+++ b/src/audio_core/renderer/command/effect/multi_tap_biquad_filter.h
@@ -0,0 +1,59 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7#include <string>
8
9#include "audio_core/renderer/command/icommand.h"
10#include "audio_core/renderer/voice/voice_info.h"
11#include "common/common_types.h"
12
13namespace AudioCore::AudioRenderer {
14namespace ADSP {
15class CommandListProcessor;
16}
17
18/**
19 * AudioRenderer command for applying multiple biquads at once.
20 */
21struct MultiTapBiquadFilterCommand : ICommand {
22 /**
23 * Print this command's information to a string.
24 *
25 * @param processor - The CommandListProcessor processing this command.
26 * @param string - The string to print into.
27 */
28 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
29
30 /**
31 * Process this command.
32 *
33 * @param processor - The CommandListProcessor processing this command.
34 */
35 void Process(const ADSP::CommandListProcessor& processor) override;
36
37 /**
38 * Verify this command's data is valid.
39 *
40 * @param processor - The CommandListProcessor processing this command.
41 * @return True if the command is valid, otherwise false.
42 */
43 bool Verify(const ADSP::CommandListProcessor& processor) override;
44
45 /// Input mix buffer index
46 s16 input;
47 /// Output mix buffer index
48 s16 output;
49 /// Biquad parameters
50 std::array<VoiceInfo::BiquadFilterParameter, MaxBiquadFilters> biquads;
51 /// Biquad states, updated each call
52 std::array<CpuAddr, MaxBiquadFilters> states;
53 /// If each biquad needs initialisation
54 std::array<bool, MaxBiquadFilters> needs_init;
55 /// Number of active biquads
56 u8 filter_tap_count;
57};
58
59} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/effect/reverb.cpp b/src/audio_core/renderer/command/effect/reverb.cpp
new file mode 100644
index 000000000..fe2b1eb43
--- /dev/null
+++ b/src/audio_core/renderer/command/effect/reverb.cpp
@@ -0,0 +1,440 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <numbers>
5#include <ranges>
6
7#include "audio_core/renderer/adsp/command_list_processor.h"
8#include "audio_core/renderer/command/effect/reverb.h"
9
10namespace AudioCore::AudioRenderer {
11
12constexpr std::array<f32, ReverbInfo::MaxDelayLines> FdnMaxDelayLineTimes = {
13 53.9532470703125f,
14 79.19256591796875f,
15 116.23876953125f,
16 170.61529541015625f,
17};
18
19constexpr std::array<f32, ReverbInfo::MaxDelayLines> DecayMaxDelayLineTimes = {
20 7.0f,
21 9.0f,
22 13.0f,
23 17.0f,
24};
25
26constexpr std::array<std::array<f32, ReverbInfo::MaxDelayTaps + 1>, ReverbInfo::NumEarlyModes>
27 EarlyDelayTimes = {
28 {{0.000000f, 3.500000f, 2.799988f, 3.899963f, 2.699951f, 13.399963f, 7.899963f, 8.399963f,
29 9.899963f, 12.000000f, 12.500000f},
30 {0.000000f, 11.799988f, 5.500000f, 11.199951f, 10.399963f, 38.099976f, 22.199951f,
31 29.599976f, 21.199951f, 24.799988f, 40.000000f},
32 {0.000000f, 41.500000f, 20.500000f, 41.299988f, 0.000000f, 29.500000f, 33.799988f,
33 45.199951f, 46.799988f, 0.000000f, 50.000000f},
34 {33.099976f, 43.299988f, 22.799988f, 37.899963f, 14.899963f, 35.299988f, 17.899963f,
35 34.199951f, 0.000000f, 43.299988f, 50.000000f},
36 {0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f,
37 0.000000f, 0.000000f, 0.000000f}},
38};
39
40constexpr std::array<std::array<f32, ReverbInfo::MaxDelayTaps>, ReverbInfo::NumEarlyModes>
41 EarlyDelayGains = {{
42 {0.699951f, 0.679993f, 0.699951f, 0.679993f, 0.699951f, 0.679993f, 0.699951f, 0.679993f,
43 0.679993f, 0.679993f},
44 {0.699951f, 0.679993f, 0.699951f, 0.679993f, 0.699951f, 0.679993f, 0.679993f, 0.679993f,
45 0.679993f, 0.679993f},
46 {0.500000f, 0.699951f, 0.699951f, 0.679993f, 0.500000f, 0.679993f, 0.679993f, 0.699951f,
47 0.679993f, 0.000000f},
48 {0.929993f, 0.919983f, 0.869995f, 0.859985f, 0.939941f, 0.809998f, 0.799988f, 0.769958f,
49 0.759949f, 0.649963f},
50 {0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f,
51 0.000000f, 0.000000f},
52 }};
53
54constexpr std::array<std::array<f32, ReverbInfo::MaxDelayLines>, ReverbInfo::NumLateModes>
55 FdnDelayTimes = {{
56 {53.953247f, 79.192566f, 116.238770f, 130.615295f},
57 {53.953247f, 79.192566f, 116.238770f, 170.615295f},
58 {5.000000f, 10.000000f, 5.000000f, 10.000000f},
59 {47.029968f, 71.000000f, 103.000000f, 170.000000f},
60 {53.953247f, 79.192566f, 116.238770f, 170.615295f},
61 }};
62
63constexpr std::array<std::array<f32, ReverbInfo::MaxDelayLines>, ReverbInfo::NumLateModes>
64 DecayDelayTimes = {{
65 {7.000000f, 9.000000f, 13.000000f, 17.000000f},
66 {7.000000f, 9.000000f, 13.000000f, 17.000000f},
67 {1.000000f, 1.000000f, 1.000000f, 1.000000f},
68 {7.000000f, 7.000000f, 13.000000f, 9.000000f},
69 {7.000000f, 9.000000f, 13.000000f, 17.000000f},
70 }};
71
72/**
73 * Update the ReverbInfo state according to the given parameters.
74 *
75 * @param params - Input parameters to update the state.
76 * @param state - State to be updated.
77 */
78static void UpdateReverbEffectParameter(const ReverbInfo::ParameterVersion2& params,
79 ReverbInfo::State& state) {
80 const auto pow_10 = [](f32 val) -> f32 {
81 return (val >= 0.0f) ? 1.0f : (val <= -5.3f) ? 0.0f : std::pow(10.0f, val);
82 };
83 const auto cos = [](f32 degrees) -> f32 {
84 return std::cos(degrees * std::numbers::pi_v<f32> / 180.0f);
85 };
86
87 static bool unk_initialized{false};
88 static Common::FixedPoint<50, 14> unk_value{};
89
90 const auto sample_rate{Common::FixedPoint<50, 14>::from_base(params.sample_rate)};
91 const auto pre_delay_time{Common::FixedPoint<50, 14>::from_base(params.pre_delay)};
92
93 for (u32 i = 0; i < ReverbInfo::MaxDelayTaps; i++) {
94 auto early_delay{
95 ((pre_delay_time + EarlyDelayTimes[params.early_mode][i]) * sample_rate).to_int()};
96 early_delay = std::min(early_delay, state.pre_delay_line.sample_count_max);
97 state.early_delay_times[i] = early_delay + 1;
98 state.early_gains[i] = Common::FixedPoint<50, 14>::from_base(params.early_gain) *
99 EarlyDelayGains[params.early_mode][i];
100 }
101
102 if (params.channel_count == 2) {
103 state.early_gains[4] * 0.5f;
104 state.early_gains[5] * 0.5f;
105 }
106
107 auto pre_time{
108 ((pre_delay_time + EarlyDelayTimes[params.early_mode][10]) * sample_rate).to_int()};
109 state.pre_delay_time = std::min(pre_time, state.pre_delay_line.sample_count_max);
110
111 if (!unk_initialized) {
112 unk_value = cos((1280.0f / sample_rate).to_float());
113 unk_initialized = true;
114 }
115
116 for (u32 i = 0; i < ReverbInfo::MaxDelayLines; i++) {
117 const auto fdn_delay{(FdnDelayTimes[params.late_mode][i] * sample_rate).to_int()};
118 state.fdn_delay_lines[i].sample_count =
119 std::min(fdn_delay, state.fdn_delay_lines[i].sample_count_max);
120 state.fdn_delay_lines[i].buffer_end =
121 &state.fdn_delay_lines[i].buffer[state.fdn_delay_lines[i].sample_count - 1];
122
123 const auto decay_delay{(DecayDelayTimes[params.late_mode][i] * sample_rate).to_int()};
124 state.decay_delay_lines[i].sample_count =
125 std::min(decay_delay, state.decay_delay_lines[i].sample_count_max);
126 state.decay_delay_lines[i].buffer_end =
127 &state.decay_delay_lines[i].buffer[state.decay_delay_lines[i].sample_count - 1];
128
129 state.decay_delay_lines[i].decay =
130 0.5999755859375f * (1.0f - Common::FixedPoint<50, 14>::from_base(params.colouration));
131
132 auto a{(Common::FixedPoint<50, 14>(state.fdn_delay_lines[i].sample_count_max) +
133 state.decay_delay_lines[i].sample_count_max) *
134 -3};
135 auto b{a / (Common::FixedPoint<50, 14>::from_base(params.decay_time) * sample_rate)};
136 Common::FixedPoint<50, 14> c{0.0f};
137 Common::FixedPoint<50, 14> d{0.0f};
138 auto hf_decay_ratio{Common::FixedPoint<50, 14>::from_base(params.high_freq_decay_ratio)};
139
140 if (hf_decay_ratio > 0.99493408203125f) {
141 c = 0.0f;
142 d = 1.0f;
143 } else {
144 const auto e{
145 pow_10(((((1.0f / hf_decay_ratio) - 1.0f) * 2) / 100 * (b / 10)).to_float())};
146 const auto f{1.0f - e};
147 const auto g{2.0f - (unk_value * e * 2)};
148 const auto h{std::sqrt(std::pow(g.to_float(), 2.0f) - (std::pow(f, 2.0f) * 4))};
149
150 c = (g - h) / (f * 2.0f);
151 d = 1.0f - c;
152 }
153
154 state.hf_decay_prev_gain[i] = c;
155 state.hf_decay_gain[i] = pow_10((b / 1000).to_float()) * d * 0.70709228515625f;
156 state.prev_feedback_output[i] = 0;
157 }
158}
159
160/**
161 * Initialize a new ReverbInfo state according to the given parameters.
162 *
163 * @param params - Input parameters to update the state.
164 * @param state - State to be updated.
165 * @param workbuffer - Game-supplied memory for the state. (Unused)
166 * @param long_size_pre_delay_supported - Use a longer pre-delay time before reverb begins.
167 */
168static void InitializeReverbEffect(const ReverbInfo::ParameterVersion2& params,
169 ReverbInfo::State& state, const CpuAddr workbuffer,
170 const bool long_size_pre_delay_supported) {
171 state = {};
172
173 auto delay{Common::FixedPoint<50, 14>::from_base(params.sample_rate)};
174
175 for (u32 i = 0; i < ReverbInfo::MaxDelayLines; i++) {
176 auto fdn_delay_time{(FdnMaxDelayLineTimes[i] * delay).to_uint_floor()};
177 state.fdn_delay_lines[i].Initialize(fdn_delay_time, 1.0f);
178
179 auto decay_delay_time{(DecayMaxDelayLineTimes[i] * delay).to_uint_floor()};
180 state.decay_delay_lines[i].Initialize(decay_delay_time, 0.0f);
181 }
182
183 const auto pre_delay{long_size_pre_delay_supported ? 350.0f : 150.0f};
184 const auto pre_delay_line{(pre_delay * delay).to_uint_floor()};
185 state.pre_delay_line.Initialize(pre_delay_line, 1.0f);
186
187 const auto center_delay_time{(5 * delay).to_uint_floor()};
188 state.center_delay_line.Initialize(center_delay_time, 1.0f);
189
190 UpdateReverbEffectParameter(params, state);
191
192 for (u32 i = 0; i < ReverbInfo::MaxDelayLines; i++) {
193 std::ranges::fill(state.fdn_delay_lines[i].buffer, 0);
194 std::ranges::fill(state.decay_delay_lines[i].buffer, 0);
195 }
196 std::ranges::fill(state.center_delay_line.buffer, 0);
197 std::ranges::fill(state.pre_delay_line.buffer, 0);
198}
199
200/**
201 * Pass-through the effect, copying input to output directly, with no reverb applied.
202 *
203 * @param inputs - Array of input mix buffers to copy.
204 * @param outputs - Array of output mix buffers to receive copy.
205 * @param channel_count - Number of channels in inputs and outputs.
206 * @param sample_count - Number of samples within each channel.
207 */
208static void ApplyReverbEffectBypass(std::span<std::span<const s32>> inputs,
209 std::span<std::span<s32>> outputs, const u32 channel_count,
210 const u32 sample_count) {
211 for (u32 i = 0; i < channel_count; i++) {
212 if (inputs[i].data() != outputs[i].data()) {
213 std::memcpy(outputs[i].data(), inputs[i].data(), outputs[i].size_bytes());
214 }
215 }
216}
217
218/**
219 * Tick the delay lines, reading and returning their current output, and writing a new decaying
220 * sample (mix).
221 *
222 * @param decay - The decay line.
223 * @param fdn - Feedback delay network.
224 * @param mix - The new calculated sample to be written and decayed.
225 * @return The next delayed and decayed sample.
226 */
227static Common::FixedPoint<50, 14> Axfx2AllPassTick(ReverbInfo::ReverbDelayLine& decay,
228 ReverbInfo::ReverbDelayLine& fdn,
229 const Common::FixedPoint<50, 14> mix) {
230 const auto val{decay.Read()};
231 const auto mixed{mix - (val * decay.decay)};
232 const auto out{decay.Tick(mixed) + (mixed * decay.decay)};
233
234 fdn.Tick(out);
235 return out;
236}
237
238/**
239 * Impl. Apply a Reverb according to the current state, on the input mix buffers,
240 * saving the results to the output mix buffers.
241 *
242 * @tparam NumChannels - Number of channels to process. 1-6.
243 Inputs/outputs should have this many buffers.
244 * @param params - Input parameters to update the state.
245 * @param state - State to use, must be initialized (see InitializeReverbEffect).
246 * @param inputs - Input mix buffers to perform the reverb on.
247 * @param outputs - Output mix buffers to receive the reverbed samples.
248 * @param sample_count - Number of samples to process.
249 */
250template <size_t NumChannels>
251static void ApplyReverbEffect(const ReverbInfo::ParameterVersion2& params, ReverbInfo::State& state,
252 std::vector<std::span<const s32>>& inputs,
253 std::vector<std::span<s32>>& outputs, const u32 sample_count) {
254 constexpr std::array<u8, ReverbInfo::MaxDelayTaps> OutTapIndexes1Ch{
255 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
256 };
257 constexpr std::array<u8, ReverbInfo::MaxDelayTaps> OutTapIndexes2Ch{
258 0, 0, 1, 1, 0, 1, 0, 0, 1, 1,
259 };
260 constexpr std::array<u8, ReverbInfo::MaxDelayTaps> OutTapIndexes4Ch{
261 0, 0, 1, 1, 0, 1, 2, 2, 3, 3,
262 };
263 constexpr std::array<u8, ReverbInfo::MaxDelayTaps> OutTapIndexes6Ch{
264 0, 0, 1, 1, 2, 2, 4, 4, 5, 5,
265 };
266
267 std::span<const u8> tap_indexes{};
268 if constexpr (NumChannels == 1) {
269 tap_indexes = OutTapIndexes1Ch;
270 } else if constexpr (NumChannels == 2) {
271 tap_indexes = OutTapIndexes2Ch;
272 } else if constexpr (NumChannels == 4) {
273 tap_indexes = OutTapIndexes4Ch;
274 } else if constexpr (NumChannels == 6) {
275 tap_indexes = OutTapIndexes6Ch;
276 }
277
278 for (u32 sample_index = 0; sample_index < sample_count; sample_index++) {
279 std::array<Common::FixedPoint<50, 14>, NumChannels> output_samples{};
280
281 for (u32 early_tap = 0; early_tap < ReverbInfo::MaxDelayTaps; early_tap++) {
282 const auto sample{state.pre_delay_line.TapOut(state.early_delay_times[early_tap]) *
283 state.early_gains[early_tap]};
284 output_samples[tap_indexes[early_tap]] += sample;
285 if constexpr (NumChannels == 6) {
286 output_samples[static_cast<u32>(Channels::LFE)] += sample;
287 }
288 }
289
290 if constexpr (NumChannels == 6) {
291 output_samples[static_cast<u32>(Channels::LFE)] *= 0.2f;
292 }
293
294 Common::FixedPoint<50, 14> input_sample{};
295 for (u32 channel = 0; channel < NumChannels; channel++) {
296 input_sample += inputs[channel][sample_index];
297 }
298
299 input_sample *= 64;
300 input_sample *= Common::FixedPoint<50, 14>::from_base(params.base_gain);
301 state.pre_delay_line.Write(input_sample);
302
303 for (u32 i = 0; i < ReverbInfo::MaxDelayLines; i++) {
304 state.prev_feedback_output[i] =
305 state.prev_feedback_output[i] * state.hf_decay_prev_gain[i] +
306 state.fdn_delay_lines[i].Read() * state.hf_decay_gain[i];
307 }
308
309 Common::FixedPoint<50, 14> pre_delay_sample{
310 state.pre_delay_line.Read() * Common::FixedPoint<50, 14>::from_base(params.late_gain)};
311
312 std::array<Common::FixedPoint<50, 14>, ReverbInfo::MaxDelayLines> mix_matrix{
313 state.prev_feedback_output[2] + state.prev_feedback_output[1] + pre_delay_sample,
314 -state.prev_feedback_output[0] - state.prev_feedback_output[3] + pre_delay_sample,
315 state.prev_feedback_output[0] - state.prev_feedback_output[3] + pre_delay_sample,
316 state.prev_feedback_output[1] - state.prev_feedback_output[2] + pre_delay_sample,
317 };
318
319 std::array<Common::FixedPoint<50, 14>, ReverbInfo::MaxDelayLines> allpass_samples{};
320 for (u32 i = 0; i < ReverbInfo::MaxDelayLines; i++) {
321 allpass_samples[i] = Axfx2AllPassTick(state.decay_delay_lines[i],
322 state.fdn_delay_lines[i], mix_matrix[i]);
323 }
324
325 const auto dry_gain{Common::FixedPoint<50, 14>::from_base(params.dry_gain)};
326 const auto wet_gain{Common::FixedPoint<50, 14>::from_base(params.wet_gain)};
327
328 if constexpr (NumChannels == 6) {
329 const std::array<Common::FixedPoint<50, 14>, MaxChannels> allpass_outputs{
330 allpass_samples[0], allpass_samples[1], allpass_samples[2] - allpass_samples[3],
331 allpass_samples[3], allpass_samples[2], allpass_samples[3],
332 };
333
334 for (u32 channel = 0; channel < NumChannels; channel++) {
335 auto in_sample{inputs[channel][sample_index] * dry_gain};
336
337 Common::FixedPoint<50, 14> allpass{};
338 if (channel == static_cast<u32>(Channels::Center)) {
339 allpass = state.center_delay_line.Tick(allpass_outputs[channel] * 0.5f);
340 } else {
341 allpass = allpass_outputs[channel];
342 }
343
344 auto out_sample{((output_samples[channel] + allpass) * wet_gain) / 64};
345 outputs[channel][sample_index] = (in_sample + out_sample).to_int();
346 }
347 } else {
348 for (u32 channel = 0; channel < NumChannels; channel++) {
349 auto in_sample{inputs[channel][sample_index] * dry_gain};
350 auto out_sample{((output_samples[channel] + allpass_samples[channel]) * wet_gain) /
351 64};
352 outputs[channel][sample_index] = (in_sample + out_sample).to_int();
353 }
354 }
355 }
356}
357
358/**
359 * Apply a Reverb if enabled, according to the current state, on the input mix buffers,
360 * saving the results to the output mix buffers.
361 *
362 * @param params - Input parameters to use.
363 * @param state - State to use, must be initialized (see InitializeReverbEffect).
364 * @param enabled - If enabled, delay will be applied, otherwise input is copied to output.
365 * @param inputs - Input mix buffers to performan the reverb on.
366 * @param outputs - Output mix buffers to receive the reverbed samples.
367 * @param sample_count - Number of samples to process.
368 */
369static void ApplyReverbEffect(const ReverbInfo::ParameterVersion2& params, ReverbInfo::State& state,
370 const bool enabled, std::vector<std::span<const s32>>& inputs,
371 std::vector<std::span<s32>>& outputs, const u32 sample_count) {
372 if (enabled) {
373 switch (params.channel_count) {
374 case 0:
375 return;
376 case 1:
377 ApplyReverbEffect<1>(params, state, inputs, outputs, sample_count);
378 break;
379 case 2:
380 ApplyReverbEffect<2>(params, state, inputs, outputs, sample_count);
381 break;
382 case 4:
383 ApplyReverbEffect<4>(params, state, inputs, outputs, sample_count);
384 break;
385 case 6:
386 ApplyReverbEffect<6>(params, state, inputs, outputs, sample_count);
387 break;
388 default:
389 ApplyReverbEffectBypass(inputs, outputs, params.channel_count, sample_count);
390 break;
391 }
392 } else {
393 ApplyReverbEffectBypass(inputs, outputs, params.channel_count, sample_count);
394 }
395}
396
397void ReverbCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
398 std::string& string) {
399 string += fmt::format(
400 "ReverbCommand\n\tenabled {} long_size_pre_delay_supported {}\n\tinputs: ", effect_enabled,
401 long_size_pre_delay_supported);
402 for (u32 i = 0; i < MaxChannels; i++) {
403 string += fmt::format("{:02X}, ", inputs[i]);
404 }
405 string += "\n\toutputs: ";
406 for (u32 i = 0; i < MaxChannels; i++) {
407 string += fmt::format("{:02X}, ", outputs[i]);
408 }
409 string += "\n";
410}
411
412void ReverbCommand::Process(const ADSP::CommandListProcessor& processor) {
413 std::vector<std::span<const s32>> input_buffers(parameter.channel_count);
414 std::vector<std::span<s32>> output_buffers(parameter.channel_count);
415
416 for (u32 i = 0; i < parameter.channel_count; i++) {
417 input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count,
418 processor.sample_count);
419 output_buffers[i] = processor.mix_buffers.subspan(outputs[i] * processor.sample_count,
420 processor.sample_count);
421 }
422
423 auto state_{reinterpret_cast<ReverbInfo::State*>(state)};
424
425 if (effect_enabled) {
426 if (parameter.state == ReverbInfo::ParameterState::Updating) {
427 UpdateReverbEffectParameter(parameter, *state_);
428 } else if (parameter.state == ReverbInfo::ParameterState::Initialized) {
429 InitializeReverbEffect(parameter, *state_, workbuffer, long_size_pre_delay_supported);
430 }
431 }
432 ApplyReverbEffect(parameter, *state_, effect_enabled, input_buffers, output_buffers,
433 processor.sample_count);
434}
435
436bool ReverbCommand::Verify(const ADSP::CommandListProcessor& processor) {
437 return true;
438}
439
440} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/effect/reverb.h b/src/audio_core/renderer/command/effect/reverb.h
new file mode 100644
index 000000000..328756150
--- /dev/null
+++ b/src/audio_core/renderer/command/effect/reverb.h
@@ -0,0 +1,62 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7#include <string>
8
9#include "audio_core/renderer/command/icommand.h"
10#include "audio_core/renderer/effect/reverb.h"
11#include "common/common_types.h"
12
13namespace AudioCore::AudioRenderer {
14namespace ADSP {
15class CommandListProcessor;
16}
17
18/**
19 * AudioRenderer command for a Reverb effect. Apply a reverb to inputs mix buffer, outputs receives
20 * the results.
21 */
22struct ReverbCommand : ICommand {
23 /**
24 * Print this command's information to a string.
25 *
26 * @param processor - The CommandListProcessor processing this command.
27 * @param string - The string to print into.
28 */
29 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
30
31 /**
32 * Process this command.
33 *
34 * @param processor - The CommandListProcessor processing this command.
35 */
36 void Process(const ADSP::CommandListProcessor& processor) override;
37
38 /**
39 * Verify this command's data is valid.
40 *
41 * @param processor - The CommandListProcessor processing this command.
42 * @return True if the command is valid, otherwise false.
43 */
44 bool Verify(const ADSP::CommandListProcessor& processor) override;
45
46 /// Input mix buffer offsets for each channel
47 std::array<s16, MaxChannels> inputs;
48 /// Output mix buffer offsets for each channel
49 std::array<s16, MaxChannels> outputs;
50 /// Input parameters
51 ReverbInfo::ParameterVersion2 parameter;
52 /// State, updated each call
53 CpuAddr state;
54 /// Game-supplied workbuffer (Unused)
55 CpuAddr workbuffer;
56 /// Is this effect enabled?
57 bool effect_enabled;
58 /// Is a longer pre-delay time supported?
59 bool long_size_pre_delay_supported;
60};
61
62} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/icommand.h b/src/audio_core/renderer/command/icommand.h
new file mode 100644
index 000000000..f2dd41254
--- /dev/null
+++ b/src/audio_core/renderer/command/icommand.h
@@ -0,0 +1,93 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "audio_core/common/common.h"
7#include "common/common_types.h"
8
9namespace AudioCore::AudioRenderer {
10namespace ADSP {
11class CommandListProcessor;
12}
13
14enum class CommandId : u8 {
15 /* 0x00 */ Invalid,
16 /* 0x01 */ DataSourcePcmInt16Version1,
17 /* 0x02 */ DataSourcePcmInt16Version2,
18 /* 0x03 */ DataSourcePcmFloatVersion1,
19 /* 0x04 */ DataSourcePcmFloatVersion2,
20 /* 0x05 */ DataSourceAdpcmVersion1,
21 /* 0x06 */ DataSourceAdpcmVersion2,
22 /* 0x07 */ Volume,
23 /* 0x08 */ VolumeRamp,
24 /* 0x09 */ BiquadFilter,
25 /* 0x0A */ Mix,
26 /* 0x0B */ MixRamp,
27 /* 0x0C */ MixRampGrouped,
28 /* 0x0D */ DepopPrepare,
29 /* 0x0E */ DepopForMixBuffers,
30 /* 0x0F */ Delay,
31 /* 0x10 */ Upsample,
32 /* 0x11 */ DownMix6chTo2ch,
33 /* 0x12 */ Aux,
34 /* 0x13 */ DeviceSink,
35 /* 0x14 */ CircularBufferSink,
36 /* 0x15 */ Reverb,
37 /* 0x16 */ I3dl2Reverb,
38 /* 0x17 */ Performance,
39 /* 0x18 */ ClearMixBuffer,
40 /* 0x19 */ CopyMixBuffer,
41 /* 0x1A */ LightLimiterVersion1,
42 /* 0x1B */ LightLimiterVersion2,
43 /* 0x1C */ MultiTapBiquadFilter,
44 /* 0x1D */ Capture,
45 /* 0x1E */ Compressor,
46};
47
48constexpr u32 CommandMagic{0xCAFEBABE};
49
50/**
51 * A command, generated by the host, and processed by the ADSP's AudioRenderer.
52 */
53struct ICommand {
54 virtual ~ICommand() = default;
55
56 /**
57 * Print this command's information to a string.
58 *
59 * @param processor - The CommandListProcessor processing this command.
60 * @param string - The string to print into.
61 */
62 virtual void Dump(const ADSP::CommandListProcessor& processor, std::string& string) = 0;
63
64 /**
65 * Process this command.
66 *
67 * @param processor - The CommandListProcessor processing this command.
68 */
69 virtual void Process(const ADSP::CommandListProcessor& processor) = 0;
70
71 /**
72 * Verify this command's data is valid.
73 *
74 * @param processor - The CommandListProcessor processing this command.
75 * @return True if the command is valid, otherwise false.
76 */
77 virtual bool Verify(const ADSP::CommandListProcessor& processor) = 0;
78
79 /// Command magic 0xCAFEBABE
80 u32 magic{};
81 /// Command enabled
82 bool enabled{};
83 /// Type of this command (see CommandId)
84 CommandId type{};
85 /// Size of this command
86 s16 size{};
87 /// Estimated processing time for this command
88 u32 estimated_process_time{};
89 /// Node id of the voice or mix this command was generated from
90 u32 node_id{};
91};
92
93} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/mix/clear_mix.cpp b/src/audio_core/renderer/command/mix/clear_mix.cpp
new file mode 100644
index 000000000..4f649d6a8
--- /dev/null
+++ b/src/audio_core/renderer/command/mix/clear_mix.cpp
@@ -0,0 +1,24 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <string>
5
6#include "audio_core/renderer/adsp/command_list_processor.h"
7#include "audio_core/renderer/command/mix/clear_mix.h"
8
9namespace AudioCore::AudioRenderer {
10
11void ClearMixBufferCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
12 std::string& string) {
13 string += fmt::format("ClearMixBufferCommand\n");
14}
15
16void ClearMixBufferCommand::Process(const ADSP::CommandListProcessor& processor) {
17 memset(processor.mix_buffers.data(), 0, processor.mix_buffers.size_bytes());
18}
19
20bool ClearMixBufferCommand::Verify(const ADSP::CommandListProcessor& processor) {
21 return true;
22}
23
24} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/mix/clear_mix.h b/src/audio_core/renderer/command/mix/clear_mix.h
new file mode 100644
index 000000000..956ec0b65
--- /dev/null
+++ b/src/audio_core/renderer/command/mix/clear_mix.h
@@ -0,0 +1,45 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <string>
7
8#include "audio_core/renderer/command/icommand.h"
9#include "common/common_types.h"
10
11namespace AudioCore::AudioRenderer {
12namespace ADSP {
13class CommandListProcessor;
14}
15
16/**
17 * AudioRenderer command for a clearing the mix buffers.
18 * Used at the start of each command list.
19 */
20struct ClearMixBufferCommand : ICommand {
21 /**
22 * Print this command's information to a string.
23 *
24 * @param processor - The CommandListProcessor processing this command.
25 * @param string - The string to print into.
26 */
27 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
28
29 /**
30 * Process this command.
31 *
32 * @param processor - The CommandListProcessor processing this command.
33 */
34 void Process(const ADSP::CommandListProcessor& processor) override;
35
36 /**
37 * Verify this command's data is valid.
38 *
39 * @param processor - The CommandListProcessor processing this command.
40 * @return True if the command is valid, otherwise false.
41 */
42 bool Verify(const ADSP::CommandListProcessor& processor) override;
43};
44
45} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/mix/copy_mix.cpp b/src/audio_core/renderer/command/mix/copy_mix.cpp
new file mode 100644
index 000000000..1d49f1644
--- /dev/null
+++ b/src/audio_core/renderer/command/mix/copy_mix.cpp
@@ -0,0 +1,27 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/adsp/command_list_processor.h"
5#include "audio_core/renderer/command/mix/copy_mix.h"
6
7namespace AudioCore::AudioRenderer {
8
9void CopyMixBufferCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
10 std::string& string) {
11 string += fmt::format("CopyMixBufferCommand\n\tinput {:02X} output {:02X}\n", input_index,
12 output_index);
13}
14
15void CopyMixBufferCommand::Process(const ADSP::CommandListProcessor& processor) {
16 auto output{processor.mix_buffers.subspan(output_index * processor.sample_count,
17 processor.sample_count)};
18 auto input{processor.mix_buffers.subspan(input_index * processor.sample_count,
19 processor.sample_count)};
20 std::memcpy(output.data(), input.data(), processor.sample_count * sizeof(s32));
21}
22
23bool CopyMixBufferCommand::Verify(const ADSP::CommandListProcessor& processor) {
24 return true;
25}
26
27} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/mix/copy_mix.h b/src/audio_core/renderer/command/mix/copy_mix.h
new file mode 100644
index 000000000..a59007fb6
--- /dev/null
+++ b/src/audio_core/renderer/command/mix/copy_mix.h
@@ -0,0 +1,49 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <string>
7
8#include "audio_core/renderer/command/icommand.h"
9#include "common/common_types.h"
10
11namespace AudioCore::AudioRenderer {
12namespace ADSP {
13class CommandListProcessor;
14}
15
16/**
17 * AudioRenderer command for a copying a mix buffer from input to output.
18 */
19struct CopyMixBufferCommand : ICommand {
20 /**
21 * Print this command's information to a string.
22 *
23 * @param processor - The CommandListProcessor processing this command.
24 * @param string - The string to print into.
25 */
26 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
27
28 /**
29 * Process this command.
30 *
31 * @param processor - The CommandListProcessor processing this command.
32 */
33 void Process(const ADSP::CommandListProcessor& processor) override;
34
35 /**
36 * Verify this command's data is valid.
37 *
38 * @param processor - The CommandListProcessor processing this command.
39 * @return True if the command is valid, otherwise false.
40 */
41 bool Verify(const ADSP::CommandListProcessor& processor) override;
42
43 /// Input mix buffer index
44 s16 input_index;
45 /// Output mix buffer index
46 s16 output_index;
47};
48
49} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/mix/depop_for_mix_buffers.cpp b/src/audio_core/renderer/command/mix/depop_for_mix_buffers.cpp
new file mode 100644
index 000000000..c2bc10061
--- /dev/null
+++ b/src/audio_core/renderer/command/mix/depop_for_mix_buffers.cpp
@@ -0,0 +1,64 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/common/common.h"
5#include "audio_core/renderer/adsp/command_list_processor.h"
6#include "audio_core/renderer/command/mix/depop_for_mix_buffers.h"
7
8namespace AudioCore::AudioRenderer {
9/**
10 * Apply depopping. Add the depopped sample to each incoming new sample, decaying it each time
11 * according to decay.
12 *
13 * @param output - Output buffer to be depopped.
14 * @param depop_sample - Depopped sample to apply to output samples.
15 * @param decay_ - Amount to decay the depopped sample for every output sample.
16 * @param sample_count - Samples to process.
17 * @return Final decayed depop sample.
18 */
19static s32 ApplyDepopMix(std::span<s32> output, const s32 depop_sample,
20 Common::FixedPoint<49, 15>& decay_, const u32 sample_count) {
21 auto sample{std::abs(depop_sample)};
22 auto decay{decay_.to_raw()};
23
24 if (depop_sample <= 0) {
25 for (u32 i = 0; i < sample_count; i++) {
26 sample = static_cast<s32>((static_cast<s64>(sample) * decay) >> 15);
27 output[i] -= sample;
28 }
29 return -sample;
30 } else {
31 for (u32 i = 0; i < sample_count; i++) {
32 sample = static_cast<s32>((static_cast<s64>(sample) * decay) >> 15);
33 output[i] += sample;
34 }
35 return sample;
36 }
37}
38
39void DepopForMixBuffersCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
40 std::string& string) {
41 string += fmt::format("DepopForMixBuffersCommand\n\tinput {:02X} count {} decay {}\n", input,
42 count, decay.to_float());
43}
44
45void DepopForMixBuffersCommand::Process(const ADSP::CommandListProcessor& processor) {
46 auto end_index{std::min(processor.buffer_count, input + count)};
47 std::span<s32> depop_buff{reinterpret_cast<s32*>(depop_buffer), end_index};
48
49 for (u32 index = input; index < end_index; index++) {
50 const auto depop_sample{depop_buff[index]};
51 if (depop_sample != 0) {
52 auto input_buffer{processor.mix_buffers.subspan(index * processor.sample_count,
53 processor.sample_count)};
54 depop_buff[index] =
55 ApplyDepopMix(input_buffer, depop_sample, decay, processor.sample_count);
56 }
57 }
58}
59
60bool DepopForMixBuffersCommand::Verify(const ADSP::CommandListProcessor& processor) {
61 return true;
62}
63
64} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/mix/depop_for_mix_buffers.h b/src/audio_core/renderer/command/mix/depop_for_mix_buffers.h
new file mode 100644
index 000000000..e7268ff27
--- /dev/null
+++ b/src/audio_core/renderer/command/mix/depop_for_mix_buffers.h
@@ -0,0 +1,55 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <string>
7
8#include "audio_core/renderer/command/icommand.h"
9#include "common/common_types.h"
10#include "common/fixed_point.h"
11
12namespace AudioCore::AudioRenderer {
13namespace ADSP {
14class CommandListProcessor;
15}
16
17/**
18 * AudioRenderer command for depopping a mix buffer.
19 * Adds a cumulation of previous samples to the current mix buffer with a decay.
20 */
21struct DepopForMixBuffersCommand : ICommand {
22 /**
23 * Print this command's information to a string.
24 *
25 * @param processor - The CommandListProcessor processing this command.
26 * @param string - The string to print into.
27 */
28 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
29
30 /**
31 * Process this command.
32 *
33 * @param processor - The CommandListProcessor processing this command.
34 */
35 void Process(const ADSP::CommandListProcessor& processor) override;
36
37 /**
38 * Verify this command's data is valid.
39 *
40 * @param processor - The CommandListProcessor processing this command.
41 * @return True if the command is valid, otherwise false.
42 */
43 bool Verify(const ADSP::CommandListProcessor& processor) override;
44
45 /// Starting input mix buffer index
46 u32 input;
47 /// Number of mix buffers to depop
48 u32 count;
49 /// Amount to decay the depop sample for each new sample
50 Common::FixedPoint<49, 15> decay;
51 /// Address of the depop buffer, holding the last sample for every mix buffer
52 CpuAddr depop_buffer;
53};
54
55} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/mix/depop_prepare.cpp b/src/audio_core/renderer/command/mix/depop_prepare.cpp
new file mode 100644
index 000000000..2ee076ef6
--- /dev/null
+++ b/src/audio_core/renderer/command/mix/depop_prepare.cpp
@@ -0,0 +1,36 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/adsp/command_list_processor.h"
5#include "audio_core/renderer/command/mix/depop_prepare.h"
6#include "audio_core/renderer/voice/voice_state.h"
7#include "common/fixed_point.h"
8
9namespace AudioCore::AudioRenderer {
10
11void DepopPrepareCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
12 std::string& string) {
13 string += fmt::format("DepopPrepareCommand\n\tinputs: ");
14 for (u32 i = 0; i < buffer_count; i++) {
15 string += fmt::format("{:02X}, ", inputs[i]);
16 }
17 string += "\n";
18}
19
20void DepopPrepareCommand::Process(const ADSP::CommandListProcessor& processor) {
21 auto samples{reinterpret_cast<s32*>(previous_samples)};
22 auto buffer{std::span(reinterpret_cast<s32*>(depop_buffer), buffer_count)};
23
24 for (u32 i = 0; i < buffer_count; i++) {
25 if (samples[i]) {
26 buffer[inputs[i]] += samples[i];
27 samples[i] = 0;
28 }
29 }
30}
31
32bool DepopPrepareCommand::Verify(const ADSP::CommandListProcessor& processor) {
33 return true;
34}
35
36} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/mix/depop_prepare.h b/src/audio_core/renderer/command/mix/depop_prepare.h
new file mode 100644
index 000000000..a5465da9a
--- /dev/null
+++ b/src/audio_core/renderer/command/mix/depop_prepare.h
@@ -0,0 +1,54 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <string>
7
8#include "audio_core/renderer/command/icommand.h"
9#include "common/common_types.h"
10
11namespace AudioCore::AudioRenderer {
12namespace ADSP {
13class CommandListProcessor;
14}
15
16/**
17 * AudioRenderer command for preparing depop.
18 * Adds the previusly output last samples to the depop buffer.
19 */
20struct DepopPrepareCommand : ICommand {
21 /**
22 * Print this command's information to a string.
23 *
24 * @param processor - The CommandListProcessor processing this command.
25 * @param string - The string to print into.
26 */
27 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
28
29 /**
30 * Process this command.
31 *
32 * @param processor - The CommandListProcessor processing this command.
33 */
34 void Process(const ADSP::CommandListProcessor& processor) override;
35
36 /**
37 * Verify this command's data is valid.
38 *
39 * @param processor - The CommandListProcessor processing this command.
40 * @return True if the command is valid, otherwise false.
41 */
42 bool Verify(const ADSP::CommandListProcessor& processor) override;
43
44 /// Depop buffer offset for each mix buffer
45 std::array<s16, MaxMixBuffers> inputs;
46 /// Pointer to the previous mix buffer samples
47 CpuAddr previous_samples;
48 /// Number of mix buffers to use
49 u32 buffer_count;
50 /// Pointer to the current depop values
51 CpuAddr depop_buffer;
52};
53
54} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/mix/mix.cpp b/src/audio_core/renderer/command/mix/mix.cpp
new file mode 100644
index 000000000..8ecf9b05a
--- /dev/null
+++ b/src/audio_core/renderer/command/mix/mix.cpp
@@ -0,0 +1,70 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <algorithm>
5#include <limits>
6#include <span>
7
8#include "audio_core/renderer/adsp/command_list_processor.h"
9#include "audio_core/renderer/command/mix/mix.h"
10#include "common/fixed_point.h"
11
12namespace AudioCore::AudioRenderer {
13/**
14 * Mix input mix buffer into output mix buffer, with volume applied to the input.
15 *
16 * @tparam Q - Number of bits for fixed point operations.
17 * @param output - Output mix buffer.
18 * @param input - Input mix buffer.
19 * @param volume - Volume applied to the input.
20 * @param sample_count - Number of samples to process.
21 */
22template <size_t Q>
23static void ApplyMix(std::span<s32> output, std::span<const s32> input, const f32 volume_,
24 const u32 sample_count) {
25 const Common::FixedPoint<64 - Q, Q> volume{volume_};
26 for (u32 i = 0; i < sample_count; i++) {
27 output[i] = (output[i] + input[i] * volume).to_int();
28 }
29}
30
31void MixCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
32 std::string& string) {
33 string += fmt::format("MixCommand");
34 string += fmt::format("\n\tinput {:02X}", input_index);
35 string += fmt::format("\n\toutput {:02X}", output_index);
36 string += fmt::format("\n\tvolume {:.8f}", volume);
37 string += "\n";
38}
39
40void MixCommand::Process(const ADSP::CommandListProcessor& processor) {
41 auto output{processor.mix_buffers.subspan(output_index * processor.sample_count,
42 processor.sample_count)};
43 auto input{processor.mix_buffers.subspan(input_index * processor.sample_count,
44 processor.sample_count)};
45
46 // If volume is 0, nothing will be added to the output, so just skip.
47 if (volume == 0.0f) {
48 return;
49 }
50
51 switch (precision) {
52 case 15:
53 ApplyMix<15>(output, input, volume, processor.sample_count);
54 break;
55
56 case 23:
57 ApplyMix<23>(output, input, volume, processor.sample_count);
58 break;
59
60 default:
61 LOG_ERROR(Service_Audio, "Invalid precision {}", precision);
62 break;
63 }
64}
65
66bool MixCommand::Verify(const ADSP::CommandListProcessor& processor) {
67 return true;
68}
69
70} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/mix/mix.h b/src/audio_core/renderer/command/mix/mix.h
new file mode 100644
index 000000000..0201cf171
--- /dev/null
+++ b/src/audio_core/renderer/command/mix/mix.h
@@ -0,0 +1,54 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <string>
7
8#include "audio_core/renderer/command/icommand.h"
9#include "common/common_types.h"
10
11namespace AudioCore::AudioRenderer {
12namespace ADSP {
13class CommandListProcessor;
14}
15
16/**
17 * AudioRenderer command for mixing an input mix buffer to an output mix buffer, with a volume
18 * applied to the input.
19 */
20struct MixCommand : ICommand {
21 /**
22 * Print this command's information to a string.
23 *
24 * @param processor - The CommandListProcessor processing this command.
25 * @param string - The string to print into.
26 */
27 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
28
29 /**
30 * Process this command.
31 *
32 * @param processor - The CommandListProcessor processing this command.
33 */
34 void Process(const ADSP::CommandListProcessor& processor) override;
35
36 /**
37 * Verify this command's data is valid.
38 *
39 * @param processor - The CommandListProcessor processing this command.
40 * @return True if the command is valid, otherwise false.
41 */
42 bool Verify(const ADSP::CommandListProcessor& processor) override;
43
44 /// Fixed point precision
45 u8 precision;
46 /// Input mix buffer index
47 s16 input_index;
48 /// Output mix buffer index
49 s16 output_index;
50 /// Mix volume applied to the input
51 f32 volume;
52};
53
54} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/mix/mix_ramp.cpp b/src/audio_core/renderer/command/mix/mix_ramp.cpp
new file mode 100644
index 000000000..ffdafa1c8
--- /dev/null
+++ b/src/audio_core/renderer/command/mix/mix_ramp.cpp
@@ -0,0 +1,94 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/adsp/command_list_processor.h"
5#include "audio_core/renderer/command/mix/mix_ramp.h"
6#include "common/fixed_point.h"
7#include "common/logging/log.h"
8
9namespace AudioCore::AudioRenderer {
10/**
11 * Mix input mix buffer into output mix buffer, with volume applied to the input.
12 *
13 * @tparam Q - Number of bits for fixed point operations.
14 * @param output - Output mix buffer.
15 * @param input - Input mix buffer.
16 * @param volume - Volume applied to the input.
17 * @param ramp - Ramp applied to volume every sample.
18 * @param sample_count - Number of samples to process.
19 * @return The final gained input sample, used for depopping.
20 */
21template <size_t Q>
22s32 ApplyMixRamp(std::span<s32> output, std::span<const s32> input, const f32 volume_,
23 const f32 ramp_, const u32 sample_count) {
24 Common::FixedPoint<64 - Q, Q> volume{volume_};
25 Common::FixedPoint<64 - Q, Q> sample{0};
26
27 if (ramp_ == 0.0f) {
28 for (u32 i = 0; i < sample_count; i++) {
29 sample = input[i] * volume;
30 output[i] = (output[i] + sample).to_int();
31 }
32 } else {
33 Common::FixedPoint<64 - Q, Q> ramp{ramp_};
34 for (u32 i = 0; i < sample_count; i++) {
35 sample = input[i] * volume;
36 output[i] = (output[i] + sample).to_int();
37 volume += ramp;
38 }
39 }
40 return sample.to_int();
41}
42
43template s32 ApplyMixRamp<15>(std::span<s32>, std::span<const s32>, const f32, const f32,
44 const u32);
45template s32 ApplyMixRamp<23>(std::span<s32>, std::span<const s32>, const f32, const f32,
46 const u32);
47
48void MixRampCommand::Dump(const ADSP::CommandListProcessor& processor, std::string& string) {
49 const auto ramp{(volume - prev_volume) / static_cast<f32>(processor.sample_count)};
50 string += fmt::format("MixRampCommand");
51 string += fmt::format("\n\tinput {:02X}", input_index);
52 string += fmt::format("\n\toutput {:02X}", output_index);
53 string += fmt::format("\n\tvolume {:.8f}", volume);
54 string += fmt::format("\n\tprev_volume {:.8f}", prev_volume);
55 string += fmt::format("\n\tramp {:.8f}", ramp);
56 string += "\n";
57}
58
59void MixRampCommand::Process(const ADSP::CommandListProcessor& processor) {
60 auto output{processor.mix_buffers.subspan(output_index * processor.sample_count,
61 processor.sample_count)};
62 auto input{processor.mix_buffers.subspan(input_index * processor.sample_count,
63 processor.sample_count)};
64 const auto ramp{(volume - prev_volume) / static_cast<f32>(processor.sample_count)};
65 auto prev_sample_ptr{reinterpret_cast<s32*>(previous_sample)};
66
67 // If previous volume and ramp are both 0, nothing will be added to the output, so just skip.
68 if (prev_volume == 0.0f && ramp == 0.0f) {
69 *prev_sample_ptr = 0;
70 return;
71 }
72
73 switch (precision) {
74 case 15:
75 *prev_sample_ptr =
76 ApplyMixRamp<15>(output, input, prev_volume, ramp, processor.sample_count);
77 break;
78
79 case 23:
80 *prev_sample_ptr =
81 ApplyMixRamp<23>(output, input, prev_volume, ramp, processor.sample_count);
82 break;
83
84 default:
85 LOG_ERROR(Service_Audio, "Invalid precision {}", precision);
86 break;
87 }
88}
89
90bool MixRampCommand::Verify(const ADSP::CommandListProcessor& processor) {
91 return true;
92}
93
94} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/mix/mix_ramp.h b/src/audio_core/renderer/command/mix/mix_ramp.h
new file mode 100644
index 000000000..770f57e80
--- /dev/null
+++ b/src/audio_core/renderer/command/mix/mix_ramp.h
@@ -0,0 +1,73 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <span>
7#include <string>
8
9#include "audio_core/renderer/command/icommand.h"
10#include "common/common_types.h"
11
12namespace AudioCore::AudioRenderer {
13namespace ADSP {
14class CommandListProcessor;
15}
16
17/**
18 * AudioRenderer command for mixing an input mix buffer to an output mix buffer, with a volume
19 * applied to the input, and volume ramping to smooth out the transition.
20 */
21struct MixRampCommand : ICommand {
22 /**
23 * Print this command's information to a string.
24 *
25 * @param processor - The CommandListProcessor processing this command.
26 * @param string - The string to print into.
27 */
28 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
29
30 /**
31 * Process this command.
32 *
33 * @param processor - The CommandListProcessor processing this command.
34 */
35 void Process(const ADSP::CommandListProcessor& processor) override;
36
37 /**
38 * Verify this command's data is valid.
39 *
40 * @param processor - The CommandListProcessor processing this command.
41 * @return True if the command is valid, otherwise false.
42 */
43 bool Verify(const ADSP::CommandListProcessor& processor) override;
44
45 /// Fixed point precision
46 u8 precision;
47 /// Input mix buffer index
48 s16 input_index;
49 /// Output mix buffer index
50 s16 output_index;
51 /// Previous mix volume
52 f32 prev_volume;
53 /// Current mix volume
54 f32 volume;
55 /// Pointer to the previous sample buffer, used for depopping
56 CpuAddr previous_sample;
57};
58
59/**
60 * Mix input mix buffer into output mix buffer, with volume applied to the input.
61 * @tparam Q - Number of bits for fixed point operations.
62 * @param output - Output mix buffer.
63 * @param input - Input mix buffer.
64 * @param volume - Volume applied to the input.
65 * @param ramp - Ramp applied to volume every sample.
66 * @param sample_count - Number of samples to process.
67 * @return The final gained input sample, used for depopping.
68 */
69template <size_t Q>
70s32 ApplyMixRamp(std::span<s32> output, std::span<const s32> input, const f32 volume_,
71 const f32 ramp_, const u32 sample_count);
72
73} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/mix/mix_ramp_grouped.cpp b/src/audio_core/renderer/command/mix/mix_ramp_grouped.cpp
new file mode 100644
index 000000000..43dbef9fc
--- /dev/null
+++ b/src/audio_core/renderer/command/mix/mix_ramp_grouped.cpp
@@ -0,0 +1,65 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/adsp/command_list_processor.h"
5#include "audio_core/renderer/command/mix/mix_ramp.h"
6#include "audio_core/renderer/command/mix/mix_ramp_grouped.h"
7
8namespace AudioCore::AudioRenderer {
9
10void MixRampGroupedCommand::Dump(const ADSP::CommandListProcessor& processor, std::string& string) {
11 string += "MixRampGroupedCommand";
12 for (u32 i = 0; i < buffer_count; i++) {
13 string += fmt::format("\n\t{}", i);
14 const auto ramp{(volumes[i] - prev_volumes[i]) / static_cast<f32>(processor.sample_count)};
15 string += fmt::format("\n\t\tinput {:02X}", inputs[i]);
16 string += fmt::format("\n\t\toutput {:02X}", outputs[i]);
17 string += fmt::format("\n\t\tvolume {:.8f}", volumes[i]);
18 string += fmt::format("\n\t\tprev_volume {:.8f}", prev_volumes[i]);
19 string += fmt::format("\n\t\tramp {:.8f}", ramp);
20 string += "\n";
21 }
22}
23
24void MixRampGroupedCommand::Process(const ADSP::CommandListProcessor& processor) {
25 std::span<s32> prev_samples = {reinterpret_cast<s32*>(previous_samples), MaxMixBuffers};
26
27 for (u32 i = 0; i < buffer_count; i++) {
28 auto last_sample{0};
29 if (prev_volumes[i] != 0.0f || volumes[i] != 0.0f) {
30 const auto output{processor.mix_buffers.subspan(outputs[i] * processor.sample_count,
31 processor.sample_count)};
32 const auto input{processor.mix_buffers.subspan(inputs[i] * processor.sample_count,
33 processor.sample_count)};
34 const auto ramp{(volumes[i] - prev_volumes[i]) /
35 static_cast<f32>(processor.sample_count)};
36
37 if (prev_volumes[i] == 0.0f && ramp == 0.0f) {
38 prev_samples[i] = 0;
39 continue;
40 }
41
42 switch (precision) {
43 case 15:
44 last_sample =
45 ApplyMixRamp<15>(output, input, prev_volumes[i], ramp, processor.sample_count);
46 break;
47 case 23:
48 last_sample =
49 ApplyMixRamp<23>(output, input, prev_volumes[i], ramp, processor.sample_count);
50 break;
51 default:
52 LOG_ERROR(Service_Audio, "Invalid precision {}", precision);
53 break;
54 }
55 }
56
57 prev_samples[i] = last_sample;
58 }
59}
60
61bool MixRampGroupedCommand::Verify(const ADSP::CommandListProcessor& processor) {
62 return true;
63}
64
65} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/mix/mix_ramp_grouped.h b/src/audio_core/renderer/command/mix/mix_ramp_grouped.h
new file mode 100644
index 000000000..027276e5a
--- /dev/null
+++ b/src/audio_core/renderer/command/mix/mix_ramp_grouped.h
@@ -0,0 +1,61 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7#include <string>
8
9#include "audio_core/renderer/command/icommand.h"
10#include "common/common_types.h"
11
12namespace AudioCore::AudioRenderer {
13namespace ADSP {
14class CommandListProcessor;
15}
16
17/**
18 * AudioRenderer command for mixing multiple input mix buffers to multiple output mix buffers, with
19 * a volume applied to the input, and volume ramping to smooth out the transition.
20 */
21struct MixRampGroupedCommand : ICommand {
22 /**
23 * Print this command's information to a string.
24 *
25 * @param processor - The CommandListProcessor processing this command.
26 * @param string - The string to print into.
27 */
28 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
29
30 /**
31 * Process this command.
32 *
33 * @param processor - The CommandListProcessor processing this command.
34 */
35 void Process(const ADSP::CommandListProcessor& processor) override;
36
37 /**
38 * Verify this command's data is valid.
39 *
40 * @param processor - The CommandListProcessor processing this command.
41 * @return True if the command is valid, otherwise false.
42 */
43 bool Verify(const ADSP::CommandListProcessor& processor) override;
44
45 /// Fixed point precision
46 u8 precision;
47 /// Number of mix buffers to mix
48 u32 buffer_count;
49 /// Input mix buffer indexes for each mix buffer
50 std::array<s16, MaxMixBuffers> inputs;
51 /// Output mix buffer indexes for each mix buffer
52 std::array<s16, MaxMixBuffers> outputs;
53 /// Previous mix vloumes for each mix buffer
54 std::array<f32, MaxMixBuffers> prev_volumes;
55 /// Current mix vloumes for each mix buffer
56 std::array<f32, MaxMixBuffers> volumes;
57 /// Pointer to the previous sample buffer, used for depop
58 CpuAddr previous_samples;
59};
60
61} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/mix/volume.cpp b/src/audio_core/renderer/command/mix/volume.cpp
new file mode 100644
index 000000000..b045fb062
--- /dev/null
+++ b/src/audio_core/renderer/command/mix/volume.cpp
@@ -0,0 +1,72 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/adsp/command_list_processor.h"
5#include "audio_core/renderer/command/mix/volume.h"
6#include "common/fixed_point.h"
7#include "common/logging/log.h"
8
9namespace AudioCore::AudioRenderer {
10/**
11 * Apply volume to the input mix buffer, saving to the output buffer.
12 *
13 * @tparam Q - Number of bits for fixed point operations.
14 * @param output - Output mix buffer.
15 * @param input - Input mix buffer.
16 * @param volume - Volume applied to the input.
17 * @param sample_count - Number of samples to process.
18 */
19template <size_t Q>
20static void ApplyUniformGain(std::span<s32> output, std::span<const s32> input, const f32 volume,
21 const u32 sample_count) {
22 if (volume == 1.0f) {
23 std::memcpy(output.data(), input.data(), input.size_bytes());
24 } else {
25 const Common::FixedPoint<64 - Q, Q> gain{volume};
26 for (u32 i = 0; i < sample_count; i++) {
27 output[i] = (input[i] * gain).to_int();
28 }
29 }
30}
31
32void VolumeCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
33 std::string& string) {
34 string += fmt::format("VolumeCommand");
35 string += fmt::format("\n\tinput {:02X}", input_index);
36 string += fmt::format("\n\toutput {:02X}", output_index);
37 string += fmt::format("\n\tvolume {:.8f}", volume);
38 string += "\n";
39}
40
41void VolumeCommand::Process(const ADSP::CommandListProcessor& processor) {
42 // If input and output buffers are the same, and the volume is 1.0f, this won't do
43 // anything, so just skip.
44 if (input_index == output_index && volume == 1.0f) {
45 return;
46 }
47
48 auto output{processor.mix_buffers.subspan(output_index * processor.sample_count,
49 processor.sample_count)};
50 auto input{processor.mix_buffers.subspan(input_index * processor.sample_count,
51 processor.sample_count)};
52
53 switch (precision) {
54 case 15:
55 ApplyUniformGain<15>(output, input, volume, processor.sample_count);
56 break;
57
58 case 23:
59 ApplyUniformGain<23>(output, input, volume, processor.sample_count);
60 break;
61
62 default:
63 LOG_ERROR(Service_Audio, "Invalid precision {}", precision);
64 break;
65 }
66}
67
68bool VolumeCommand::Verify(const ADSP::CommandListProcessor& processor) {
69 return true;
70}
71
72} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/mix/volume.h b/src/audio_core/renderer/command/mix/volume.h
new file mode 100644
index 000000000..6ae9fb794
--- /dev/null
+++ b/src/audio_core/renderer/command/mix/volume.h
@@ -0,0 +1,53 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <string>
7
8#include "audio_core/renderer/command/icommand.h"
9#include "common/common_types.h"
10
11namespace AudioCore::AudioRenderer {
12namespace ADSP {
13class CommandListProcessor;
14}
15
16/**
17 * AudioRenderer command for applying volume to a mix buffer.
18 */
19struct VolumeCommand : ICommand {
20 /**
21 * Print this command's information to a string.
22 *
23 * @param processor - The CommandListProcessor processing this command.
24 * @param string - The string to print into.
25 */
26 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
27
28 /**
29 * Process this command.
30 *
31 * @param processor - The CommandListProcessor processing this command.
32 */
33 void Process(const ADSP::CommandListProcessor& processor) override;
34
35 /**
36 * Verify this command's data is valid.
37 *
38 * @param processor - The CommandListProcessor processing this command.
39 * @return True if the command is valid, otherwise false.
40 */
41 bool Verify(const ADSP::CommandListProcessor& processor) override;
42
43 /// Fixed point precision
44 u8 precision;
45 /// Input mix buffer index
46 s16 input_index;
47 /// Output mix buffer index
48 s16 output_index;
49 /// Mix volume applied to the input
50 f32 volume;
51};
52
53} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/mix/volume_ramp.cpp b/src/audio_core/renderer/command/mix/volume_ramp.cpp
new file mode 100644
index 000000000..424307148
--- /dev/null
+++ b/src/audio_core/renderer/command/mix/volume_ramp.cpp
@@ -0,0 +1,84 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/adsp/command_list_processor.h"
5#include "audio_core/renderer/command/mix/volume_ramp.h"
6#include "common/fixed_point.h"
7
8namespace AudioCore::AudioRenderer {
9/**
10 * Apply volume with ramping to the input mix buffer, saving to the output buffer.
11 *
12 * @tparam Q - Number of bits for fixed point operations.
13 * @param output - Output mix buffers.
14 * @param input - Input mix buffers.
15 * @param volume - Volume applied to the input.
16 * @param ramp - Ramp applied to volume every sample.
17 * @param sample_count - Number of samples to process.
18 */
19template <size_t Q>
20static void ApplyLinearEnvelopeGain(std::span<s32> output, std::span<const s32> input,
21 const f32 volume, const f32 ramp_, const u32 sample_count) {
22 if (volume == 0.0f && ramp_ == 0.0f) {
23 std::memset(output.data(), 0, output.size_bytes());
24 } else if (volume == 1.0f && ramp_ == 0.0f) {
25 std::memcpy(output.data(), input.data(), output.size_bytes());
26 } else if (ramp_ == 0.0f) {
27 const Common::FixedPoint<64 - Q, Q> gain{volume};
28 for (u32 i = 0; i < sample_count; i++) {
29 output[i] = (input[i] * gain).to_int();
30 }
31 } else {
32 Common::FixedPoint<64 - Q, Q> gain{volume};
33 const Common::FixedPoint<64 - Q, Q> ramp{ramp_};
34 for (u32 i = 0; i < sample_count; i++) {
35 output[i] = (input[i] * gain).to_int();
36 gain += ramp;
37 }
38 }
39}
40
41void VolumeRampCommand::Dump(const ADSP::CommandListProcessor& processor, std::string& string) {
42 const auto ramp{(volume - prev_volume) / static_cast<f32>(processor.sample_count)};
43 string += fmt::format("VolumeRampCommand");
44 string += fmt::format("\n\tinput {:02X}", input_index);
45 string += fmt::format("\n\toutput {:02X}", output_index);
46 string += fmt::format("\n\tvolume {:.8f}", volume);
47 string += fmt::format("\n\tprev_volume {:.8f}", prev_volume);
48 string += fmt::format("\n\tramp {:.8f}", ramp);
49 string += "\n";
50}
51
52void VolumeRampCommand::Process(const ADSP::CommandListProcessor& processor) {
53 auto output{processor.mix_buffers.subspan(output_index * processor.sample_count,
54 processor.sample_count)};
55 auto input{processor.mix_buffers.subspan(input_index * processor.sample_count,
56 processor.sample_count)};
57 const auto ramp{(volume - prev_volume) / static_cast<f32>(processor.sample_count)};
58
59 // If input and output buffers are the same, and the volume is 1.0f, and there's no ramping,
60 // this won't do anything, so just skip.
61 if (input_index == output_index && prev_volume == 1.0f && ramp == 0.0f) {
62 return;
63 }
64
65 switch (precision) {
66 case 15:
67 ApplyLinearEnvelopeGain<15>(output, input, prev_volume, ramp, processor.sample_count);
68 break;
69
70 case 23:
71 ApplyLinearEnvelopeGain<23>(output, input, prev_volume, ramp, processor.sample_count);
72 break;
73
74 default:
75 LOG_ERROR(Service_Audio, "Invalid precision {}", precision);
76 break;
77 }
78}
79
80bool VolumeRampCommand::Verify(const ADSP::CommandListProcessor& processor) {
81 return true;
82}
83
84} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/mix/volume_ramp.h b/src/audio_core/renderer/command/mix/volume_ramp.h
new file mode 100644
index 000000000..77b61547e
--- /dev/null
+++ b/src/audio_core/renderer/command/mix/volume_ramp.h
@@ -0,0 +1,56 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <string>
7
8#include "audio_core/renderer/command/icommand.h"
9#include "common/common_types.h"
10
11namespace AudioCore::AudioRenderer {
12namespace ADSP {
13class CommandListProcessor;
14}
15
16/**
17 * AudioRenderer command for applying volume to a mix buffer, with ramping for the volume to smooth
18 * out the transition.
19 */
20struct VolumeRampCommand : ICommand {
21 /**
22 * Print this command's information to a string.
23 *
24 * @param processor - The CommandListProcessor processing this command.
25 * @param string - The string to print into.
26 */
27 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
28
29 /**
30 * Process this command.
31 *
32 * @param processor - The CommandListProcessor processing this command.
33 */
34 void Process(const ADSP::CommandListProcessor& processor) override;
35
36 /**
37 * Verify this command's data is valid.
38 *
39 * @param processor - The CommandListProcessor processing this command.
40 * @return True if the command is valid, otherwise false.
41 */
42 bool Verify(const ADSP::CommandListProcessor& processor) override;
43
44 /// Fixed point precision
45 u8 precision;
46 /// Input mix buffer index
47 s16 input_index;
48 /// Output mix buffer index
49 s16 output_index;
50 /// Previous mix volume applied to the input
51 f32 prev_volume;
52 /// Current mix volume applied to the input
53 f32 volume;
54};
55
56} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/performance/performance.cpp b/src/audio_core/renderer/command/performance/performance.cpp
new file mode 100644
index 000000000..985958b03
--- /dev/null
+++ b/src/audio_core/renderer/command/performance/performance.cpp
@@ -0,0 +1,43 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/adsp/command_list_processor.h"
5#include "audio_core/renderer/command/performance/performance.h"
6#include "core/core.h"
7#include "core/core_timing.h"
8#include "core/core_timing_util.h"
9
10namespace AudioCore::AudioRenderer {
11
12void PerformanceCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
13 std::string& string) {
14 string += fmt::format("PerformanceCommand\n\tstate {}\n", static_cast<u32>(state));
15}
16
17void PerformanceCommand::Process(const ADSP::CommandListProcessor& processor) {
18 auto base{entry_address.translated_address};
19 if (state == PerformanceState::Start) {
20 auto start_time_ptr{reinterpret_cast<u32*>(base + entry_address.entry_start_time_offset)};
21 *start_time_ptr = static_cast<u32>(
22 Core::Timing::CyclesToUs(processor.system->CoreTiming().GetClockTicks() -
23 processor.start_time - processor.current_processing_time)
24 .count());
25 } else if (state == PerformanceState::Stop) {
26 auto processed_time_ptr{
27 reinterpret_cast<u32*>(base + entry_address.entry_processed_time_offset)};
28 auto entry_count_ptr{
29 reinterpret_cast<u32*>(base + entry_address.header_entry_count_offset)};
30
31 *processed_time_ptr = static_cast<u32>(
32 Core::Timing::CyclesToUs(processor.system->CoreTiming().GetClockTicks() -
33 processor.start_time - processor.current_processing_time)
34 .count());
35 (*entry_count_ptr)++;
36 }
37}
38
39bool PerformanceCommand::Verify(const ADSP::CommandListProcessor& processor) {
40 return true;
41}
42
43} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/performance/performance.h b/src/audio_core/renderer/command/performance/performance.h
new file mode 100644
index 000000000..11a7d6c08
--- /dev/null
+++ b/src/audio_core/renderer/command/performance/performance.h
@@ -0,0 +1,51 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <string>
7
8#include "audio_core/renderer/command/icommand.h"
9#include "audio_core/renderer/performance/performance_entry_addresses.h"
10#include "audio_core/renderer/performance/performance_manager.h"
11#include "common/common_types.h"
12
13namespace AudioCore::AudioRenderer {
14namespace ADSP {
15class CommandListProcessor;
16}
17
18/**
19 * AudioRenderer command for writing AudioRenderer performance metrics back to the sysmodule.
20 */
21struct PerformanceCommand : ICommand {
22 /**
23 * Print this command's information to a string.
24 *
25 * @param processor - The CommandListProcessor processing this command.
26 * @param string - The string to print into.
27 */
28 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
29
30 /**
31 * Process this command.
32 *
33 * @param processor - The CommandListProcessor processing this command.
34 */
35 void Process(const ADSP::CommandListProcessor& processor) override;
36
37 /**
38 * Verify this command's data is valid.
39 *
40 * @param processor - The CommandListProcessor processing this command.
41 * @return True if the command is valid, otherwise false.
42 */
43 bool Verify(const ADSP::CommandListProcessor& processor) override;
44
45 /// State of the performance
46 PerformanceState state;
47 /// Pointers to be written
48 PerformanceEntryAddresses entry_address;
49};
50
51} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/resample/downmix_6ch_to_2ch.cpp b/src/audio_core/renderer/command/resample/downmix_6ch_to_2ch.cpp
new file mode 100644
index 000000000..1fd90308a
--- /dev/null
+++ b/src/audio_core/renderer/command/resample/downmix_6ch_to_2ch.cpp
@@ -0,0 +1,74 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/adsp/command_list_processor.h"
5#include "audio_core/renderer/command/resample/downmix_6ch_to_2ch.h"
6
7namespace AudioCore::AudioRenderer {
8
9void DownMix6chTo2chCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
10 std::string& string) {
11 string += fmt::format("DownMix6chTo2chCommand\n\tinputs: ");
12 for (u32 i = 0; i < MaxChannels; i++) {
13 string += fmt::format("{:02X}, ", inputs[i]);
14 }
15 string += "\n\toutputs: ";
16 for (u32 i = 0; i < MaxChannels; i++) {
17 string += fmt::format("{:02X}, ", outputs[i]);
18 }
19 string += "\n";
20}
21
22void DownMix6chTo2chCommand::Process(const ADSP::CommandListProcessor& processor) {
23 auto in_front_left{
24 processor.mix_buffers.subspan(inputs[0] * processor.sample_count, processor.sample_count)};
25 auto in_front_right{
26 processor.mix_buffers.subspan(inputs[1] * processor.sample_count, processor.sample_count)};
27 auto in_center{
28 processor.mix_buffers.subspan(inputs[2] * processor.sample_count, processor.sample_count)};
29 auto in_lfe{
30 processor.mix_buffers.subspan(inputs[3] * processor.sample_count, processor.sample_count)};
31 auto in_back_left{
32 processor.mix_buffers.subspan(inputs[4] * processor.sample_count, processor.sample_count)};
33 auto in_back_right{
34 processor.mix_buffers.subspan(inputs[5] * processor.sample_count, processor.sample_count)};
35
36 auto out_front_left{
37 processor.mix_buffers.subspan(outputs[0] * processor.sample_count, processor.sample_count)};
38 auto out_front_right{
39 processor.mix_buffers.subspan(outputs[1] * processor.sample_count, processor.sample_count)};
40 auto out_center{
41 processor.mix_buffers.subspan(outputs[2] * processor.sample_count, processor.sample_count)};
42 auto out_lfe{
43 processor.mix_buffers.subspan(outputs[3] * processor.sample_count, processor.sample_count)};
44 auto out_back_left{
45 processor.mix_buffers.subspan(outputs[4] * processor.sample_count, processor.sample_count)};
46 auto out_back_right{
47 processor.mix_buffers.subspan(outputs[5] * processor.sample_count, processor.sample_count)};
48
49 for (u32 i = 0; i < processor.sample_count; i++) {
50 const auto left_sample{(in_front_left[i] * down_mix_coeff[0] +
51 in_center[i] * down_mix_coeff[1] + in_lfe[i] * down_mix_coeff[2] +
52 in_back_left[i] * down_mix_coeff[3])
53 .to_int()};
54
55 const auto right_sample{(in_front_right[i] * down_mix_coeff[0] +
56 in_center[i] * down_mix_coeff[1] + in_lfe[i] * down_mix_coeff[2] +
57 in_back_right[i] * down_mix_coeff[3])
58 .to_int()};
59
60 out_front_left[i] = left_sample;
61 out_front_right[i] = right_sample;
62 }
63
64 std::memset(out_center.data(), 0, out_center.size_bytes());
65 std::memset(out_lfe.data(), 0, out_lfe.size_bytes());
66 std::memset(out_back_left.data(), 0, out_back_left.size_bytes());
67 std::memset(out_back_right.data(), 0, out_back_right.size_bytes());
68}
69
70bool DownMix6chTo2chCommand::Verify(const ADSP::CommandListProcessor& processor) {
71 return true;
72}
73
74} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/resample/downmix_6ch_to_2ch.h b/src/audio_core/renderer/command/resample/downmix_6ch_to_2ch.h
new file mode 100644
index 000000000..dc133a73b
--- /dev/null
+++ b/src/audio_core/renderer/command/resample/downmix_6ch_to_2ch.h
@@ -0,0 +1,59 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <string>
7
8#include "audio_core/renderer/command/icommand.h"
9#include "common/common_types.h"
10#include "common/fixed_point.h"
11
12namespace AudioCore::AudioRenderer {
13namespace ADSP {
14class CommandListProcessor;
15}
16
17/**
18 * AudioRenderer command for downmixing 6 channels to 2.
19 * Channel layout (SMPTE):
20 * 0 - front left
21 * 1 - front right
22 * 2 - center
23 * 3 - lfe
24 * 4 - back left
25 * 5 - back right
26 */
27struct DownMix6chTo2chCommand : ICommand {
28 /**
29 * Print this command's information to a string.
30 *
31 * @param processor - The CommandListProcessor processing this command.
32 * @param string - The string to print into.
33 */
34 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
35
36 /**
37 * Process this command.
38 *
39 * @param processor - The CommandListProcessor processing this command.
40 */
41 void Process(const ADSP::CommandListProcessor& processor) override;
42
43 /**
44 * Verify this command's data is valid.
45 *
46 * @param processor - The CommandListProcessor processing this command.
47 * @return True if the command is valid, otherwise false.
48 */
49 bool Verify(const ADSP::CommandListProcessor& processor) override;
50
51 /// Input mix buffer offsets for each channel
52 std::array<s16, MaxChannels> inputs;
53 /// Output mix buffer offsets for each channel
54 std::array<s16, MaxChannels> outputs;
55 /// Coefficients used for downmixing
56 std::array<Common::FixedPoint<48, 16>, 4> down_mix_coeff;
57};
58
59} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/resample/resample.cpp b/src/audio_core/renderer/command/resample/resample.cpp
new file mode 100644
index 000000000..070c9d2b8
--- /dev/null
+++ b/src/audio_core/renderer/command/resample/resample.cpp
@@ -0,0 +1,883 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/command/resample/resample.h"
5
6namespace AudioCore::AudioRenderer {
7
8static void ResampleLowQuality(std::span<s32> output, std::span<const s16> input,
9 const Common::FixedPoint<49, 15>& sample_rate_ratio,
10 Common::FixedPoint<49, 15>& fraction, const u32 samples_to_write) {
11 if (sample_rate_ratio == 1.0f) {
12 for (u32 i = 0; i < samples_to_write; i++) {
13 output[i] = input[i];
14 }
15 } else {
16 u32 read_index{0};
17 for (u32 i = 0; i < samples_to_write; i++) {
18 output[i] = input[read_index + (fraction >= 0.5f)];
19 fraction += sample_rate_ratio;
20 read_index += static_cast<u32>(fraction.to_int_floor());
21 fraction.clear_int();
22 }
23 }
24}
25
26static void ResampleNormalQuality(std::span<s32> output, std::span<const s16> input,
27 const Common::FixedPoint<49, 15>& sample_rate_ratio,
28 Common::FixedPoint<49, 15>& fraction,
29 const u32 samples_to_write) {
30 static constexpr std::array<f32, 512> lut0 = {
31 0.20141602f, 0.59283447f, 0.20513916f, 0.00009155f, 0.19772339f, 0.59277344f, 0.20889282f,
32 0.00027466f, 0.19406128f, 0.59262085f, 0.21264648f, 0.00045776f, 0.19039917f, 0.59240723f,
33 0.21646118f, 0.00067139f, 0.18679810f, 0.59213257f, 0.22030640f, 0.00085449f, 0.18322754f,
34 0.59176636f, 0.22415161f, 0.00103760f, 0.17968750f, 0.59133911f, 0.22802734f, 0.00125122f,
35 0.17617798f, 0.59085083f, 0.23193359f, 0.00146484f, 0.17269897f, 0.59027100f, 0.23583984f,
36 0.00167847f, 0.16925049f, 0.58963013f, 0.23977661f, 0.00189209f, 0.16583252f, 0.58892822f,
37 0.24374390f, 0.00210571f, 0.16244507f, 0.58816528f, 0.24774170f, 0.00234985f, 0.15908813f,
38 0.58731079f, 0.25173950f, 0.00256348f, 0.15576172f, 0.58639526f, 0.25576782f, 0.00280762f,
39 0.15249634f, 0.58541870f, 0.25979614f, 0.00308228f, 0.14923096f, 0.58435059f, 0.26385498f,
40 0.00332642f, 0.14602661f, 0.58325195f, 0.26794434f, 0.00360107f, 0.14285278f, 0.58206177f,
41 0.27203369f, 0.00387573f, 0.13973999f, 0.58078003f, 0.27612305f, 0.00418091f, 0.13662720f,
42 0.57946777f, 0.28024292f, 0.00448608f, 0.13357544f, 0.57806396f, 0.28436279f, 0.00479126f,
43 0.13052368f, 0.57662964f, 0.28851318f, 0.00512695f, 0.12753296f, 0.57510376f, 0.29266357f,
44 0.00546265f, 0.12460327f, 0.57351685f, 0.29681396f, 0.00579834f, 0.12167358f, 0.57183838f,
45 0.30099487f, 0.00616455f, 0.11880493f, 0.57012939f, 0.30517578f, 0.00656128f, 0.11596680f,
46 0.56835938f, 0.30935669f, 0.00695801f, 0.11318970f, 0.56649780f, 0.31353760f, 0.00735474f,
47 0.11041260f, 0.56457520f, 0.31771851f, 0.00778198f, 0.10769653f, 0.56262207f, 0.32192993f,
48 0.00823975f, 0.10501099f, 0.56057739f, 0.32614136f, 0.00869751f, 0.10238647f, 0.55847168f,
49 0.33032227f, 0.00915527f, 0.09976196f, 0.55633545f, 0.33453369f, 0.00967407f, 0.09722900f,
50 0.55410767f, 0.33874512f, 0.01019287f, 0.09469604f, 0.55181885f, 0.34295654f, 0.01071167f,
51 0.09222412f, 0.54949951f, 0.34713745f, 0.01126099f, 0.08978271f, 0.54708862f, 0.35134888f,
52 0.01184082f, 0.08737183f, 0.54464722f, 0.35552979f, 0.01245117f, 0.08499146f, 0.54214478f,
53 0.35974121f, 0.01306152f, 0.08267212f, 0.53958130f, 0.36392212f, 0.01370239f, 0.08041382f,
54 0.53695679f, 0.36810303f, 0.01437378f, 0.07815552f, 0.53427124f, 0.37225342f, 0.01507568f,
55 0.07595825f, 0.53155518f, 0.37640381f, 0.01577759f, 0.07379150f, 0.52877808f, 0.38055420f,
56 0.01651001f, 0.07165527f, 0.52593994f, 0.38470459f, 0.01727295f, 0.06958008f, 0.52307129f,
57 0.38882446f, 0.01806641f, 0.06753540f, 0.52014160f, 0.39294434f, 0.01889038f, 0.06552124f,
58 0.51715088f, 0.39703369f, 0.01974487f, 0.06356812f, 0.51409912f, 0.40112305f, 0.02059937f,
59 0.06164551f, 0.51101685f, 0.40518188f, 0.02148438f, 0.05975342f, 0.50790405f, 0.40921021f,
60 0.02243042f, 0.05789185f, 0.50473022f, 0.41323853f, 0.02337646f, 0.05609131f, 0.50152588f,
61 0.41726685f, 0.02435303f, 0.05432129f, 0.49826050f, 0.42123413f, 0.02539062f, 0.05258179f,
62 0.49493408f, 0.42520142f, 0.02642822f, 0.05087280f, 0.49160767f, 0.42913818f, 0.02749634f,
63 0.04922485f, 0.48822021f, 0.43307495f, 0.02859497f, 0.04760742f, 0.48477173f, 0.43695068f,
64 0.02975464f, 0.04602051f, 0.48132324f, 0.44082642f, 0.03091431f, 0.04446411f, 0.47781372f,
65 0.44467163f, 0.03210449f, 0.04293823f, 0.47424316f, 0.44845581f, 0.03335571f, 0.04147339f,
66 0.47067261f, 0.45223999f, 0.03460693f, 0.04003906f, 0.46704102f, 0.45599365f, 0.03591919f,
67 0.03863525f, 0.46340942f, 0.45971680f, 0.03726196f, 0.03726196f, 0.45971680f, 0.46340942f,
68 0.03863525f, 0.03591919f, 0.45599365f, 0.46704102f, 0.04003906f, 0.03460693f, 0.45223999f,
69 0.47067261f, 0.04147339f, 0.03335571f, 0.44845581f, 0.47424316f, 0.04293823f, 0.03210449f,
70 0.44467163f, 0.47781372f, 0.04446411f, 0.03091431f, 0.44082642f, 0.48132324f, 0.04602051f,
71 0.02975464f, 0.43695068f, 0.48477173f, 0.04760742f, 0.02859497f, 0.43307495f, 0.48822021f,
72 0.04922485f, 0.02749634f, 0.42913818f, 0.49160767f, 0.05087280f, 0.02642822f, 0.42520142f,
73 0.49493408f, 0.05258179f, 0.02539062f, 0.42123413f, 0.49826050f, 0.05432129f, 0.02435303f,
74 0.41726685f, 0.50152588f, 0.05609131f, 0.02337646f, 0.41323853f, 0.50473022f, 0.05789185f,
75 0.02243042f, 0.40921021f, 0.50790405f, 0.05975342f, 0.02148438f, 0.40518188f, 0.51101685f,
76 0.06164551f, 0.02059937f, 0.40112305f, 0.51409912f, 0.06356812f, 0.01974487f, 0.39703369f,
77 0.51715088f, 0.06552124f, 0.01889038f, 0.39294434f, 0.52014160f, 0.06753540f, 0.01806641f,
78 0.38882446f, 0.52307129f, 0.06958008f, 0.01727295f, 0.38470459f, 0.52593994f, 0.07165527f,
79 0.01651001f, 0.38055420f, 0.52877808f, 0.07379150f, 0.01577759f, 0.37640381f, 0.53155518f,
80 0.07595825f, 0.01507568f, 0.37225342f, 0.53427124f, 0.07815552f, 0.01437378f, 0.36810303f,
81 0.53695679f, 0.08041382f, 0.01370239f, 0.36392212f, 0.53958130f, 0.08267212f, 0.01306152f,
82 0.35974121f, 0.54214478f, 0.08499146f, 0.01245117f, 0.35552979f, 0.54464722f, 0.08737183f,
83 0.01184082f, 0.35134888f, 0.54708862f, 0.08978271f, 0.01126099f, 0.34713745f, 0.54949951f,
84 0.09222412f, 0.01071167f, 0.34295654f, 0.55181885f, 0.09469604f, 0.01019287f, 0.33874512f,
85 0.55410767f, 0.09722900f, 0.00967407f, 0.33453369f, 0.55633545f, 0.09976196f, 0.00915527f,
86 0.33032227f, 0.55847168f, 0.10238647f, 0.00869751f, 0.32614136f, 0.56057739f, 0.10501099f,
87 0.00823975f, 0.32192993f, 0.56262207f, 0.10769653f, 0.00778198f, 0.31771851f, 0.56457520f,
88 0.11041260f, 0.00735474f, 0.31353760f, 0.56649780f, 0.11318970f, 0.00695801f, 0.30935669f,
89 0.56835938f, 0.11596680f, 0.00656128f, 0.30517578f, 0.57012939f, 0.11880493f, 0.00616455f,
90 0.30099487f, 0.57183838f, 0.12167358f, 0.00579834f, 0.29681396f, 0.57351685f, 0.12460327f,
91 0.00546265f, 0.29266357f, 0.57510376f, 0.12753296f, 0.00512695f, 0.28851318f, 0.57662964f,
92 0.13052368f, 0.00479126f, 0.28436279f, 0.57806396f, 0.13357544f, 0.00448608f, 0.28024292f,
93 0.57946777f, 0.13662720f, 0.00418091f, 0.27612305f, 0.58078003f, 0.13973999f, 0.00387573f,
94 0.27203369f, 0.58206177f, 0.14285278f, 0.00360107f, 0.26794434f, 0.58325195f, 0.14602661f,
95 0.00332642f, 0.26385498f, 0.58435059f, 0.14923096f, 0.00308228f, 0.25979614f, 0.58541870f,
96 0.15249634f, 0.00280762f, 0.25576782f, 0.58639526f, 0.15576172f, 0.00256348f, 0.25173950f,
97 0.58731079f, 0.15908813f, 0.00234985f, 0.24774170f, 0.58816528f, 0.16244507f, 0.00210571f,
98 0.24374390f, 0.58892822f, 0.16583252f, 0.00189209f, 0.23977661f, 0.58963013f, 0.16925049f,
99 0.00167847f, 0.23583984f, 0.59027100f, 0.17269897f, 0.00146484f, 0.23193359f, 0.59085083f,
100 0.17617798f, 0.00125122f, 0.22802734f, 0.59133911f, 0.17968750f, 0.00103760f, 0.22415161f,
101 0.59176636f, 0.18322754f, 0.00085449f, 0.22030640f, 0.59213257f, 0.18679810f, 0.00067139f,
102 0.21646118f, 0.59240723f, 0.19039917f, 0.00045776f, 0.21264648f, 0.59262085f, 0.19406128f,
103 0.00027466f, 0.20889282f, 0.59277344f, 0.19772339f, 0.00009155f, 0.20513916f, 0.59283447f,
104 0.20141602f,
105 };
106
107 static constexpr std::array<f32, 512> lut1 = {
108 0.00207520f, 0.99606323f, 0.00210571f, -0.00015259f, -0.00610352f, 0.99578857f,
109 0.00646973f, -0.00045776f, -0.01000977f, 0.99526978f, 0.01095581f, -0.00079346f,
110 -0.01373291f, 0.99444580f, 0.01562500f, -0.00109863f, -0.01733398f, 0.99337769f,
111 0.02041626f, -0.00143433f, -0.02075195f, 0.99203491f, 0.02539062f, -0.00177002f,
112 -0.02404785f, 0.99041748f, 0.03051758f, -0.00210571f, -0.02719116f, 0.98855591f,
113 0.03582764f, -0.00244141f, -0.03021240f, 0.98641968f, 0.04125977f, -0.00280762f,
114 -0.03308105f, 0.98400879f, 0.04687500f, -0.00314331f, -0.03579712f, 0.98135376f,
115 0.05261230f, -0.00350952f, -0.03839111f, 0.97842407f, 0.05856323f, -0.00390625f,
116 -0.04083252f, 0.97521973f, 0.06463623f, -0.00427246f, -0.04315186f, 0.97180176f,
117 0.07086182f, -0.00466919f, -0.04534912f, 0.96810913f, 0.07727051f, -0.00509644f,
118 -0.04742432f, 0.96414185f, 0.08383179f, -0.00549316f, -0.04934692f, 0.95996094f,
119 0.09054565f, -0.00592041f, -0.05114746f, 0.95550537f, 0.09741211f, -0.00637817f,
120 -0.05285645f, 0.95083618f, 0.10443115f, -0.00683594f, -0.05441284f, 0.94589233f,
121 0.11160278f, -0.00732422f, -0.05584717f, 0.94073486f, 0.11892700f, -0.00781250f,
122 -0.05718994f, 0.93533325f, 0.12643433f, -0.00830078f, -0.05841064f, 0.92968750f,
123 0.13406372f, -0.00881958f, -0.05953979f, 0.92382812f, 0.14184570f, -0.00936890f,
124 -0.06054688f, 0.91772461f, 0.14978027f, -0.00991821f, -0.06146240f, 0.91143799f,
125 0.15783691f, -0.01046753f, -0.06225586f, 0.90490723f, 0.16607666f, -0.01104736f,
126 -0.06295776f, 0.89816284f, 0.17443848f, -0.01165771f, -0.06356812f, 0.89120483f,
127 0.18292236f, -0.01229858f, -0.06408691f, 0.88403320f, 0.19155884f, -0.01293945f,
128 -0.06451416f, 0.87667847f, 0.20034790f, -0.01358032f, -0.06484985f, 0.86914062f,
129 0.20925903f, -0.01428223f, -0.06509399f, 0.86138916f, 0.21829224f, -0.01495361f,
130 -0.06527710f, 0.85345459f, 0.22744751f, -0.01568604f, -0.06536865f, 0.84533691f,
131 0.23675537f, -0.01641846f, -0.06536865f, 0.83703613f, 0.24615479f, -0.01718140f,
132 -0.06533813f, 0.82858276f, 0.25567627f, -0.01794434f, -0.06518555f, 0.81991577f,
133 0.26531982f, -0.01873779f, -0.06500244f, 0.81112671f, 0.27505493f, -0.01956177f,
134 -0.06472778f, 0.80215454f, 0.28491211f, -0.02038574f, -0.06442261f, 0.79306030f,
135 0.29489136f, -0.02124023f, -0.06402588f, 0.78378296f, 0.30496216f, -0.02209473f,
136 -0.06359863f, 0.77438354f, 0.31512451f, -0.02297974f, -0.06307983f, 0.76486206f,
137 0.32537842f, -0.02389526f, -0.06253052f, 0.75518799f, 0.33569336f, -0.02481079f,
138 -0.06195068f, 0.74539185f, 0.34613037f, -0.02575684f, -0.06130981f, 0.73547363f,
139 0.35662842f, -0.02670288f, -0.06060791f, 0.72543335f, 0.36721802f, -0.02767944f,
140 -0.05987549f, 0.71527100f, 0.37786865f, -0.02865601f, -0.05911255f, 0.70504761f,
141 0.38858032f, -0.02966309f, -0.05831909f, 0.69470215f, 0.39935303f, -0.03067017f,
142 -0.05746460f, 0.68426514f, 0.41018677f, -0.03170776f, -0.05661011f, 0.67373657f,
143 0.42108154f, -0.03271484f, -0.05569458f, 0.66311646f, 0.43200684f, -0.03378296f,
144 -0.05477905f, 0.65246582f, 0.44299316f, -0.03482056f, -0.05383301f, 0.64169312f,
145 0.45401001f, -0.03588867f, -0.05285645f, 0.63088989f, 0.46505737f, -0.03695679f,
146 -0.05187988f, 0.62002563f, 0.47613525f, -0.03802490f, -0.05087280f, 0.60910034f,
147 0.48721313f, -0.03912354f, -0.04983521f, 0.59814453f, 0.49832153f, -0.04019165f,
148 -0.04879761f, 0.58712769f, 0.50946045f, -0.04129028f, -0.04772949f, 0.57611084f,
149 0.52056885f, -0.04235840f, -0.04669189f, 0.56503296f, 0.53170776f, -0.04345703f,
150 -0.04562378f, 0.55392456f, 0.54281616f, -0.04452515f, -0.04452515f, 0.54281616f,
151 0.55392456f, -0.04562378f, -0.04345703f, 0.53170776f, 0.56503296f, -0.04669189f,
152 -0.04235840f, 0.52056885f, 0.57611084f, -0.04772949f, -0.04129028f, 0.50946045f,
153 0.58712769f, -0.04879761f, -0.04019165f, 0.49832153f, 0.59814453f, -0.04983521f,
154 -0.03912354f, 0.48721313f, 0.60910034f, -0.05087280f, -0.03802490f, 0.47613525f,
155 0.62002563f, -0.05187988f, -0.03695679f, 0.46505737f, 0.63088989f, -0.05285645f,
156 -0.03588867f, 0.45401001f, 0.64169312f, -0.05383301f, -0.03482056f, 0.44299316f,
157 0.65246582f, -0.05477905f, -0.03378296f, 0.43200684f, 0.66311646f, -0.05569458f,
158 -0.03271484f, 0.42108154f, 0.67373657f, -0.05661011f, -0.03170776f, 0.41018677f,
159 0.68426514f, -0.05746460f, -0.03067017f, 0.39935303f, 0.69470215f, -0.05831909f,
160 -0.02966309f, 0.38858032f, 0.70504761f, -0.05911255f, -0.02865601f, 0.37786865f,
161 0.71527100f, -0.05987549f, -0.02767944f, 0.36721802f, 0.72543335f, -0.06060791f,
162 -0.02670288f, 0.35662842f, 0.73547363f, -0.06130981f, -0.02575684f, 0.34613037f,
163 0.74539185f, -0.06195068f, -0.02481079f, 0.33569336f, 0.75518799f, -0.06253052f,
164 -0.02389526f, 0.32537842f, 0.76486206f, -0.06307983f, -0.02297974f, 0.31512451f,
165 0.77438354f, -0.06359863f, -0.02209473f, 0.30496216f, 0.78378296f, -0.06402588f,
166 -0.02124023f, 0.29489136f, 0.79306030f, -0.06442261f, -0.02038574f, 0.28491211f,
167 0.80215454f, -0.06472778f, -0.01956177f, 0.27505493f, 0.81112671f, -0.06500244f,
168 -0.01873779f, 0.26531982f, 0.81991577f, -0.06518555f, -0.01794434f, 0.25567627f,
169 0.82858276f, -0.06533813f, -0.01718140f, 0.24615479f, 0.83703613f, -0.06536865f,
170 -0.01641846f, 0.23675537f, 0.84533691f, -0.06536865f, -0.01568604f, 0.22744751f,
171 0.85345459f, -0.06527710f, -0.01495361f, 0.21829224f, 0.86138916f, -0.06509399f,
172 -0.01428223f, 0.20925903f, 0.86914062f, -0.06484985f, -0.01358032f, 0.20034790f,
173 0.87667847f, -0.06451416f, -0.01293945f, 0.19155884f, 0.88403320f, -0.06408691f,
174 -0.01229858f, 0.18292236f, 0.89120483f, -0.06356812f, -0.01165771f, 0.17443848f,
175 0.89816284f, -0.06295776f, -0.01104736f, 0.16607666f, 0.90490723f, -0.06225586f,
176 -0.01046753f, 0.15783691f, 0.91143799f, -0.06146240f, -0.00991821f, 0.14978027f,
177 0.91772461f, -0.06054688f, -0.00936890f, 0.14184570f, 0.92382812f, -0.05953979f,
178 -0.00881958f, 0.13406372f, 0.92968750f, -0.05841064f, -0.00830078f, 0.12643433f,
179 0.93533325f, -0.05718994f, -0.00781250f, 0.11892700f, 0.94073486f, -0.05584717f,
180 -0.00732422f, 0.11160278f, 0.94589233f, -0.05441284f, -0.00683594f, 0.10443115f,
181 0.95083618f, -0.05285645f, -0.00637817f, 0.09741211f, 0.95550537f, -0.05114746f,
182 -0.00592041f, 0.09054565f, 0.95996094f, -0.04934692f, -0.00549316f, 0.08383179f,
183 0.96414185f, -0.04742432f, -0.00509644f, 0.07727051f, 0.96810913f, -0.04534912f,
184 -0.00466919f, 0.07086182f, 0.97180176f, -0.04315186f, -0.00427246f, 0.06463623f,
185 0.97521973f, -0.04083252f, -0.00390625f, 0.05856323f, 0.97842407f, -0.03839111f,
186 -0.00350952f, 0.05261230f, 0.98135376f, -0.03579712f, -0.00314331f, 0.04687500f,
187 0.98400879f, -0.03308105f, -0.00280762f, 0.04125977f, 0.98641968f, -0.03021240f,
188 -0.00244141f, 0.03582764f, 0.98855591f, -0.02719116f, -0.00210571f, 0.03051758f,
189 0.99041748f, -0.02404785f, -0.00177002f, 0.02539062f, 0.99203491f, -0.02075195f,
190 -0.00143433f, 0.02041626f, 0.99337769f, -0.01733398f, -0.00109863f, 0.01562500f,
191 0.99444580f, -0.01373291f, -0.00079346f, 0.01095581f, 0.99526978f, -0.01000977f,
192 -0.00045776f, 0.00646973f, 0.99578857f, -0.00610352f, -0.00015259f, 0.00210571f,
193 0.99606323f, -0.00207520f,
194 };
195
196 static constexpr std::array<f32, 512> lut2 = {
197 0.09750366f, 0.80221558f, 0.10159302f, -0.00097656f, 0.09350586f, 0.80203247f,
198 0.10580444f, -0.00103760f, 0.08959961f, 0.80169678f, 0.11010742f, -0.00115967f,
199 0.08578491f, 0.80117798f, 0.11447144f, -0.00128174f, 0.08203125f, 0.80047607f,
200 0.11892700f, -0.00140381f, 0.07836914f, 0.79962158f, 0.12347412f, -0.00152588f,
201 0.07479858f, 0.79861450f, 0.12814331f, -0.00164795f, 0.07135010f, 0.79742432f,
202 0.13287354f, -0.00177002f, 0.06796265f, 0.79605103f, 0.13769531f, -0.00192261f,
203 0.06469727f, 0.79452515f, 0.14260864f, -0.00204468f, 0.06149292f, 0.79284668f,
204 0.14761353f, -0.00219727f, 0.05834961f, 0.79098511f, 0.15270996f, -0.00231934f,
205 0.05532837f, 0.78894043f, 0.15789795f, -0.00247192f, 0.05236816f, 0.78674316f,
206 0.16317749f, -0.00265503f, 0.04949951f, 0.78442383f, 0.16851807f, -0.00280762f,
207 0.04672241f, 0.78189087f, 0.17398071f, -0.00299072f, 0.04400635f, 0.77920532f,
208 0.17950439f, -0.00314331f, 0.04141235f, 0.77636719f, 0.18511963f, -0.00332642f,
209 0.03887939f, 0.77337646f, 0.19082642f, -0.00350952f, 0.03640747f, 0.77023315f,
210 0.19659424f, -0.00369263f, 0.03402710f, 0.76693726f, 0.20248413f, -0.00387573f,
211 0.03173828f, 0.76348877f, 0.20843506f, -0.00405884f, 0.02951050f, 0.75985718f,
212 0.21444702f, -0.00427246f, 0.02737427f, 0.75610352f, 0.22055054f, -0.00445557f,
213 0.02529907f, 0.75219727f, 0.22674561f, -0.00466919f, 0.02331543f, 0.74816895f,
214 0.23300171f, -0.00485229f, 0.02139282f, 0.74398804f, 0.23931885f, -0.00506592f,
215 0.01956177f, 0.73965454f, 0.24572754f, -0.00531006f, 0.01779175f, 0.73519897f,
216 0.25219727f, -0.00552368f, 0.01605225f, 0.73059082f, 0.25872803f, -0.00570679f,
217 0.01440430f, 0.72586060f, 0.26535034f, -0.00592041f, 0.01281738f, 0.72100830f,
218 0.27203369f, -0.00616455f, 0.01132202f, 0.71600342f, 0.27877808f, -0.00637817f,
219 0.00988770f, 0.71090698f, 0.28558350f, -0.00656128f, 0.00851440f, 0.70565796f,
220 0.29244995f, -0.00677490f, 0.00720215f, 0.70031738f, 0.29934692f, -0.00701904f,
221 0.00592041f, 0.69485474f, 0.30633545f, -0.00723267f, 0.00469971f, 0.68927002f,
222 0.31338501f, -0.00741577f, 0.00357056f, 0.68356323f, 0.32046509f, -0.00762939f,
223 0.00247192f, 0.67773438f, 0.32760620f, -0.00787354f, 0.00143433f, 0.67184448f,
224 0.33477783f, -0.00808716f, 0.00045776f, 0.66583252f, 0.34197998f, -0.00827026f,
225 -0.00048828f, 0.65972900f, 0.34924316f, -0.00845337f, -0.00134277f, 0.65353394f,
226 0.35656738f, -0.00863647f, -0.00216675f, 0.64721680f, 0.36389160f, -0.00885010f,
227 -0.00296021f, 0.64083862f, 0.37127686f, -0.00903320f, -0.00369263f, 0.63433838f,
228 0.37869263f, -0.00921631f, -0.00436401f, 0.62777710f, 0.38613892f, -0.00933838f,
229 -0.00497437f, 0.62115479f, 0.39361572f, -0.00949097f, -0.00558472f, 0.61444092f,
230 0.40109253f, -0.00964355f, -0.00613403f, 0.60763550f, 0.40859985f, -0.00979614f,
231 -0.00665283f, 0.60076904f, 0.41610718f, -0.00991821f, -0.00714111f, 0.59384155f,
232 0.42364502f, -0.01000977f, -0.00756836f, 0.58685303f, 0.43121338f, -0.01013184f,
233 -0.00796509f, 0.57977295f, 0.43875122f, -0.01022339f, -0.00833130f, 0.57266235f,
234 0.44631958f, -0.01028442f, -0.00866699f, 0.56552124f, 0.45388794f, -0.01034546f,
235 -0.00897217f, 0.55831909f, 0.46145630f, -0.01040649f, -0.00921631f, 0.55105591f,
236 0.46902466f, -0.01040649f, -0.00946045f, 0.54373169f, 0.47659302f, -0.01040649f,
237 -0.00967407f, 0.53640747f, 0.48413086f, -0.01037598f, -0.00985718f, 0.52902222f,
238 0.49166870f, -0.01037598f, -0.01000977f, 0.52160645f, 0.49917603f, -0.01031494f,
239 -0.01013184f, 0.51416016f, 0.50668335f, -0.01025391f, -0.01025391f, 0.50668335f,
240 0.51416016f, -0.01013184f, -0.01031494f, 0.49917603f, 0.52160645f, -0.01000977f,
241 -0.01037598f, 0.49166870f, 0.52902222f, -0.00985718f, -0.01037598f, 0.48413086f,
242 0.53640747f, -0.00967407f, -0.01040649f, 0.47659302f, 0.54373169f, -0.00946045f,
243 -0.01040649f, 0.46902466f, 0.55105591f, -0.00921631f, -0.01040649f, 0.46145630f,
244 0.55831909f, -0.00897217f, -0.01034546f, 0.45388794f, 0.56552124f, -0.00866699f,
245 -0.01028442f, 0.44631958f, 0.57266235f, -0.00833130f, -0.01022339f, 0.43875122f,
246 0.57977295f, -0.00796509f, -0.01013184f, 0.43121338f, 0.58685303f, -0.00756836f,
247 -0.01000977f, 0.42364502f, 0.59384155f, -0.00714111f, -0.00991821f, 0.41610718f,
248 0.60076904f, -0.00665283f, -0.00979614f, 0.40859985f, 0.60763550f, -0.00613403f,
249 -0.00964355f, 0.40109253f, 0.61444092f, -0.00558472f, -0.00949097f, 0.39361572f,
250 0.62115479f, -0.00497437f, -0.00933838f, 0.38613892f, 0.62777710f, -0.00436401f,
251 -0.00921631f, 0.37869263f, 0.63433838f, -0.00369263f, -0.00903320f, 0.37127686f,
252 0.64083862f, -0.00296021f, -0.00885010f, 0.36389160f, 0.64721680f, -0.00216675f,
253 -0.00863647f, 0.35656738f, 0.65353394f, -0.00134277f, -0.00845337f, 0.34924316f,
254 0.65972900f, -0.00048828f, -0.00827026f, 0.34197998f, 0.66583252f, 0.00045776f,
255 -0.00808716f, 0.33477783f, 0.67184448f, 0.00143433f, -0.00787354f, 0.32760620f,
256 0.67773438f, 0.00247192f, -0.00762939f, 0.32046509f, 0.68356323f, 0.00357056f,
257 -0.00741577f, 0.31338501f, 0.68927002f, 0.00469971f, -0.00723267f, 0.30633545f,
258 0.69485474f, 0.00592041f, -0.00701904f, 0.29934692f, 0.70031738f, 0.00720215f,
259 -0.00677490f, 0.29244995f, 0.70565796f, 0.00851440f, -0.00656128f, 0.28558350f,
260 0.71090698f, 0.00988770f, -0.00637817f, 0.27877808f, 0.71600342f, 0.01132202f,
261 -0.00616455f, 0.27203369f, 0.72100830f, 0.01281738f, -0.00592041f, 0.26535034f,
262 0.72586060f, 0.01440430f, -0.00570679f, 0.25872803f, 0.73059082f, 0.01605225f,
263 -0.00552368f, 0.25219727f, 0.73519897f, 0.01779175f, -0.00531006f, 0.24572754f,
264 0.73965454f, 0.01956177f, -0.00506592f, 0.23931885f, 0.74398804f, 0.02139282f,
265 -0.00485229f, 0.23300171f, 0.74816895f, 0.02331543f, -0.00466919f, 0.22674561f,
266 0.75219727f, 0.02529907f, -0.00445557f, 0.22055054f, 0.75610352f, 0.02737427f,
267 -0.00427246f, 0.21444702f, 0.75985718f, 0.02951050f, -0.00405884f, 0.20843506f,
268 0.76348877f, 0.03173828f, -0.00387573f, 0.20248413f, 0.76693726f, 0.03402710f,
269 -0.00369263f, 0.19659424f, 0.77023315f, 0.03640747f, -0.00350952f, 0.19082642f,
270 0.77337646f, 0.03887939f, -0.00332642f, 0.18511963f, 0.77636719f, 0.04141235f,
271 -0.00314331f, 0.17950439f, 0.77920532f, 0.04400635f, -0.00299072f, 0.17398071f,
272 0.78189087f, 0.04672241f, -0.00280762f, 0.16851807f, 0.78442383f, 0.04949951f,
273 -0.00265503f, 0.16317749f, 0.78674316f, 0.05236816f, -0.00247192f, 0.15789795f,
274 0.78894043f, 0.05532837f, -0.00231934f, 0.15270996f, 0.79098511f, 0.05834961f,
275 -0.00219727f, 0.14761353f, 0.79284668f, 0.06149292f, -0.00204468f, 0.14260864f,
276 0.79452515f, 0.06469727f, -0.00192261f, 0.13769531f, 0.79605103f, 0.06796265f,
277 -0.00177002f, 0.13287354f, 0.79742432f, 0.07135010f, -0.00164795f, 0.12814331f,
278 0.79861450f, 0.07479858f, -0.00152588f, 0.12347412f, 0.79962158f, 0.07836914f,
279 -0.00140381f, 0.11892700f, 0.80047607f, 0.08203125f, -0.00128174f, 0.11447144f,
280 0.80117798f, 0.08578491f, -0.00115967f, 0.11010742f, 0.80169678f, 0.08959961f,
281 -0.00103760f, 0.10580444f, 0.80203247f, 0.09350586f, -0.00097656f, 0.10159302f,
282 0.80221558f, 0.09750366f,
283 };
284
285 const auto get_lut = [&]() -> std::span<const f32> {
286 if (sample_rate_ratio <= 1.0f) {
287 return std::span<const f32>(lut2.data(), lut2.size());
288 } else if (sample_rate_ratio < 1.3f) {
289 return std::span<const f32>(lut1.data(), lut1.size());
290 } else {
291 return std::span<const f32>(lut0.data(), lut0.size());
292 }
293 };
294
295 auto lut{get_lut()};
296 u32 read_index{0};
297 for (u32 i = 0; i < samples_to_write; i++) {
298 const auto lut_index{(fraction.get_frac() >> 8) * 4};
299 const Common::FixedPoint<56, 8> sample0{input[read_index + 0] * lut[lut_index + 0]};
300 const Common::FixedPoint<56, 8> sample1{input[read_index + 1] * lut[lut_index + 1]};
301 const Common::FixedPoint<56, 8> sample2{input[read_index + 2] * lut[lut_index + 2]};
302 const Common::FixedPoint<56, 8> sample3{input[read_index + 3] * lut[lut_index + 3]};
303 output[i] = (sample0 + sample1 + sample2 + sample3).to_int_floor();
304 fraction += sample_rate_ratio;
305 read_index += static_cast<u32>(fraction.to_int_floor());
306 fraction.clear_int();
307 }
308}
309
310static void ResampleHighQuality(std::span<s32> output, std::span<const s16> input,
311 const Common::FixedPoint<49, 15>& sample_rate_ratio,
312 Common::FixedPoint<49, 15>& fraction, const u32 samples_to_write) {
313 static constexpr std::array<f32, 1024> lut0 = {
314 -0.01776123f, -0.00070190f, 0.26672363f, 0.50006104f, 0.26956177f, 0.00024414f,
315 -0.01800537f, 0.00000000f, -0.01748657f, -0.00164795f, 0.26388550f, 0.50003052f,
316 0.27236938f, 0.00122070f, -0.01824951f, -0.00003052f, -0.01724243f, -0.00256348f,
317 0.26107788f, 0.49996948f, 0.27520752f, 0.00219727f, -0.01849365f, -0.00003052f,
318 -0.01699829f, -0.00344849f, 0.25823975f, 0.49984741f, 0.27801514f, 0.00320435f,
319 -0.01873779f, -0.00006104f, -0.01675415f, -0.00433350f, 0.25543213f, 0.49972534f,
320 0.28085327f, 0.00424194f, -0.01898193f, -0.00006104f, -0.01651001f, -0.00518799f,
321 0.25259399f, 0.49954224f, 0.28366089f, 0.00527954f, -0.01922607f, -0.00009155f,
322 -0.01626587f, -0.00604248f, 0.24978638f, 0.49932861f, 0.28646851f, 0.00634766f,
323 -0.01947021f, -0.00012207f, -0.01602173f, -0.00686646f, 0.24697876f, 0.49908447f,
324 0.28930664f, 0.00744629f, -0.01971436f, -0.00015259f, -0.01574707f, -0.00765991f,
325 0.24414062f, 0.49877930f, 0.29211426f, 0.00854492f, -0.01995850f, -0.00015259f,
326 -0.01550293f, -0.00845337f, 0.24133301f, 0.49847412f, 0.29492188f, 0.00967407f,
327 -0.02020264f, -0.00018311f, -0.01525879f, -0.00921631f, 0.23852539f, 0.49810791f,
328 0.29772949f, 0.01083374f, -0.02044678f, -0.00021362f, -0.01501465f, -0.00997925f,
329 0.23571777f, 0.49774170f, 0.30050659f, 0.01199341f, -0.02069092f, -0.00024414f,
330 -0.01477051f, -0.01071167f, 0.23291016f, 0.49731445f, 0.30331421f, 0.01318359f,
331 -0.02093506f, -0.00027466f, -0.01452637f, -0.01141357f, 0.23010254f, 0.49685669f,
332 0.30609131f, 0.01437378f, -0.02117920f, -0.00030518f, -0.01428223f, -0.01211548f,
333 0.22732544f, 0.49636841f, 0.30886841f, 0.01559448f, -0.02142334f, -0.00033569f,
334 -0.01403809f, -0.01278687f, 0.22451782f, 0.49581909f, 0.31164551f, 0.01684570f,
335 -0.02163696f, -0.00039673f, -0.01379395f, -0.01345825f, 0.22174072f, 0.49526978f,
336 0.31442261f, 0.01809692f, -0.02188110f, -0.00042725f, -0.01358032f, -0.01409912f,
337 0.21896362f, 0.49465942f, 0.31719971f, 0.01937866f, -0.02209473f, -0.00045776f,
338 -0.01333618f, -0.01473999f, 0.21618652f, 0.49404907f, 0.31994629f, 0.02069092f,
339 -0.02233887f, -0.00048828f, -0.01309204f, -0.01535034f, 0.21343994f, 0.49337769f,
340 0.32269287f, 0.02203369f, -0.02255249f, -0.00054932f, -0.01284790f, -0.01596069f,
341 0.21066284f, 0.49267578f, 0.32543945f, 0.02337646f, -0.02279663f, -0.00057983f,
342 -0.01263428f, -0.01654053f, 0.20791626f, 0.49194336f, 0.32818604f, 0.02471924f,
343 -0.02301025f, -0.00064087f, -0.01239014f, -0.01708984f, 0.20516968f, 0.49118042f,
344 0.33090210f, 0.02612305f, -0.02322388f, -0.00067139f, -0.01214600f, -0.01763916f,
345 0.20242310f, 0.49035645f, 0.33361816f, 0.02752686f, -0.02343750f, -0.00073242f,
346 -0.01193237f, -0.01818848f, 0.19970703f, 0.48953247f, 0.33633423f, 0.02896118f,
347 -0.02365112f, -0.00079346f, -0.01168823f, -0.01867676f, 0.19696045f, 0.48864746f,
348 0.33901978f, 0.03039551f, -0.02386475f, -0.00082397f, -0.01147461f, -0.01919556f,
349 0.19427490f, 0.48776245f, 0.34170532f, 0.03186035f, -0.02407837f, -0.00088501f,
350 -0.01123047f, -0.01968384f, 0.19155884f, 0.48681641f, 0.34439087f, 0.03335571f,
351 -0.02429199f, -0.00094604f, -0.01101685f, -0.02014160f, 0.18887329f, 0.48583984f,
352 0.34704590f, 0.03485107f, -0.02447510f, -0.00100708f, -0.01080322f, -0.02059937f,
353 0.18615723f, 0.48483276f, 0.34970093f, 0.03637695f, -0.02468872f, -0.00106812f,
354 -0.01058960f, -0.02102661f, 0.18350220f, 0.48379517f, 0.35235596f, 0.03793335f,
355 -0.02487183f, -0.00112915f, -0.01034546f, -0.02145386f, 0.18081665f, 0.48272705f,
356 0.35498047f, 0.03948975f, -0.02505493f, -0.00119019f, -0.01013184f, -0.02188110f,
357 0.17816162f, 0.48162842f, 0.35760498f, 0.04107666f, -0.02523804f, -0.00125122f,
358 -0.00991821f, -0.02227783f, 0.17550659f, 0.48049927f, 0.36019897f, 0.04269409f,
359 -0.02542114f, -0.00131226f, -0.00970459f, -0.02264404f, 0.17288208f, 0.47933960f,
360 0.36279297f, 0.04431152f, -0.02560425f, -0.00140381f, -0.00952148f, -0.02301025f,
361 0.17025757f, 0.47814941f, 0.36538696f, 0.04595947f, -0.02578735f, -0.00146484f,
362 -0.00930786f, -0.02337646f, 0.16763306f, 0.47689819f, 0.36795044f, 0.04763794f,
363 -0.02593994f, -0.00152588f, -0.00909424f, -0.02371216f, 0.16503906f, 0.47564697f,
364 0.37048340f, 0.04931641f, -0.02609253f, -0.00161743f, -0.00888062f, -0.02401733f,
365 0.16244507f, 0.47436523f, 0.37304688f, 0.05102539f, -0.02627563f, -0.00170898f,
366 -0.00869751f, -0.02435303f, 0.15988159f, 0.47302246f, 0.37554932f, 0.05276489f,
367 -0.02642822f, -0.00177002f, -0.00848389f, -0.02462769f, 0.15731812f, 0.47167969f,
368 0.37805176f, 0.05450439f, -0.02658081f, -0.00186157f, -0.00830078f, -0.02493286f,
369 0.15475464f, 0.47027588f, 0.38055420f, 0.05627441f, -0.02670288f, -0.00195312f,
370 -0.00808716f, -0.02520752f, 0.15222168f, 0.46887207f, 0.38302612f, 0.05804443f,
371 -0.02685547f, -0.00204468f, -0.00790405f, -0.02545166f, 0.14968872f, 0.46743774f,
372 0.38546753f, 0.05987549f, -0.02697754f, -0.00213623f, -0.00772095f, -0.02569580f,
373 0.14718628f, 0.46594238f, 0.38790894f, 0.06170654f, -0.02709961f, -0.00222778f,
374 -0.00753784f, -0.02593994f, 0.14468384f, 0.46444702f, 0.39031982f, 0.06353760f,
375 -0.02722168f, -0.00231934f, -0.00735474f, -0.02615356f, 0.14218140f, 0.46289062f,
376 0.39273071f, 0.06539917f, -0.02734375f, -0.00241089f, -0.00717163f, -0.02636719f,
377 0.13970947f, 0.46133423f, 0.39511108f, 0.06729126f, -0.02743530f, -0.00250244f,
378 -0.00698853f, -0.02655029f, 0.13726807f, 0.45974731f, 0.39749146f, 0.06918335f,
379 -0.02755737f, -0.00259399f, -0.00680542f, -0.02673340f, 0.13479614f, 0.45812988f,
380 0.39984131f, 0.07113647f, -0.02764893f, -0.00271606f, -0.00662231f, -0.02691650f,
381 0.13238525f, 0.45648193f, 0.40216064f, 0.07305908f, -0.02774048f, -0.00280762f,
382 -0.00643921f, -0.02706909f, 0.12997437f, 0.45480347f, 0.40447998f, 0.07504272f,
383 -0.02780151f, -0.00292969f, -0.00628662f, -0.02722168f, 0.12756348f, 0.45309448f,
384 0.40676880f, 0.07699585f, -0.02789307f, -0.00305176f, -0.00610352f, -0.02734375f,
385 0.12518311f, 0.45135498f, 0.40902710f, 0.07901001f, -0.02795410f, -0.00314331f,
386 -0.00595093f, -0.02746582f, 0.12280273f, 0.44958496f, 0.41128540f, 0.08102417f,
387 -0.02801514f, -0.00326538f, -0.00579834f, -0.02758789f, 0.12045288f, 0.44778442f,
388 0.41351318f, 0.08306885f, -0.02804565f, -0.00338745f, -0.00561523f, -0.02770996f,
389 0.11813354f, 0.44598389f, 0.41571045f, 0.08511353f, -0.02810669f, -0.00350952f,
390 -0.00546265f, -0.02780151f, 0.11581421f, 0.44412231f, 0.41787720f, 0.08718872f,
391 -0.02813721f, -0.00363159f, -0.00531006f, -0.02786255f, 0.11349487f, 0.44226074f,
392 0.42004395f, 0.08929443f, -0.02816772f, -0.00375366f, -0.00515747f, -0.02795410f,
393 0.11120605f, 0.44036865f, 0.42218018f, 0.09140015f, -0.02816772f, -0.00387573f,
394 -0.00500488f, -0.02801514f, 0.10894775f, 0.43844604f, 0.42431641f, 0.09353638f,
395 -0.02819824f, -0.00402832f, -0.00485229f, -0.02807617f, 0.10668945f, 0.43649292f,
396 0.42639160f, 0.09570312f, -0.02819824f, -0.00415039f, -0.00469971f, -0.02810669f,
397 0.10446167f, 0.43453979f, 0.42846680f, 0.09786987f, -0.02819824f, -0.00427246f,
398 -0.00457764f, -0.02813721f, 0.10223389f, 0.43252563f, 0.43051147f, 0.10003662f,
399 -0.02816772f, -0.00442505f, -0.00442505f, -0.02816772f, 0.10003662f, 0.43051147f,
400 0.43252563f, 0.10223389f, -0.02813721f, -0.00457764f, -0.00427246f, -0.02819824f,
401 0.09786987f, 0.42846680f, 0.43453979f, 0.10446167f, -0.02810669f, -0.00469971f,
402 -0.00415039f, -0.02819824f, 0.09570312f, 0.42639160f, 0.43649292f, 0.10668945f,
403 -0.02807617f, -0.00485229f, -0.00402832f, -0.02819824f, 0.09353638f, 0.42431641f,
404 0.43844604f, 0.10894775f, -0.02801514f, -0.00500488f, -0.00387573f, -0.02816772f,
405 0.09140015f, 0.42218018f, 0.44036865f, 0.11120605f, -0.02795410f, -0.00515747f,
406 -0.00375366f, -0.02816772f, 0.08929443f, 0.42004395f, 0.44226074f, 0.11349487f,
407 -0.02786255f, -0.00531006f, -0.00363159f, -0.02813721f, 0.08718872f, 0.41787720f,
408 0.44412231f, 0.11581421f, -0.02780151f, -0.00546265f, -0.00350952f, -0.02810669f,
409 0.08511353f, 0.41571045f, 0.44598389f, 0.11813354f, -0.02770996f, -0.00561523f,
410 -0.00338745f, -0.02804565f, 0.08306885f, 0.41351318f, 0.44778442f, 0.12045288f,
411 -0.02758789f, -0.00579834f, -0.00326538f, -0.02801514f, 0.08102417f, 0.41128540f,
412 0.44958496f, 0.12280273f, -0.02746582f, -0.00595093f, -0.00314331f, -0.02795410f,
413 0.07901001f, 0.40902710f, 0.45135498f, 0.12518311f, -0.02734375f, -0.00610352f,
414 -0.00305176f, -0.02789307f, 0.07699585f, 0.40676880f, 0.45309448f, 0.12756348f,
415 -0.02722168f, -0.00628662f, -0.00292969f, -0.02780151f, 0.07504272f, 0.40447998f,
416 0.45480347f, 0.12997437f, -0.02706909f, -0.00643921f, -0.00280762f, -0.02774048f,
417 0.07305908f, 0.40216064f, 0.45648193f, 0.13238525f, -0.02691650f, -0.00662231f,
418 -0.00271606f, -0.02764893f, 0.07113647f, 0.39984131f, 0.45812988f, 0.13479614f,
419 -0.02673340f, -0.00680542f, -0.00259399f, -0.02755737f, 0.06918335f, 0.39749146f,
420 0.45974731f, 0.13726807f, -0.02655029f, -0.00698853f, -0.00250244f, -0.02743530f,
421 0.06729126f, 0.39511108f, 0.46133423f, 0.13970947f, -0.02636719f, -0.00717163f,
422 -0.00241089f, -0.02734375f, 0.06539917f, 0.39273071f, 0.46289062f, 0.14218140f,
423 -0.02615356f, -0.00735474f, -0.00231934f, -0.02722168f, 0.06353760f, 0.39031982f,
424 0.46444702f, 0.14468384f, -0.02593994f, -0.00753784f, -0.00222778f, -0.02709961f,
425 0.06170654f, 0.38790894f, 0.46594238f, 0.14718628f, -0.02569580f, -0.00772095f,
426 -0.00213623f, -0.02697754f, 0.05987549f, 0.38546753f, 0.46743774f, 0.14968872f,
427 -0.02545166f, -0.00790405f, -0.00204468f, -0.02685547f, 0.05804443f, 0.38302612f,
428 0.46887207f, 0.15222168f, -0.02520752f, -0.00808716f, -0.00195312f, -0.02670288f,
429 0.05627441f, 0.38055420f, 0.47027588f, 0.15475464f, -0.02493286f, -0.00830078f,
430 -0.00186157f, -0.02658081f, 0.05450439f, 0.37805176f, 0.47167969f, 0.15731812f,
431 -0.02462769f, -0.00848389f, -0.00177002f, -0.02642822f, 0.05276489f, 0.37554932f,
432 0.47302246f, 0.15988159f, -0.02435303f, -0.00869751f, -0.00170898f, -0.02627563f,
433 0.05102539f, 0.37304688f, 0.47436523f, 0.16244507f, -0.02401733f, -0.00888062f,
434 -0.00161743f, -0.02609253f, 0.04931641f, 0.37048340f, 0.47564697f, 0.16503906f,
435 -0.02371216f, -0.00909424f, -0.00152588f, -0.02593994f, 0.04763794f, 0.36795044f,
436 0.47689819f, 0.16763306f, -0.02337646f, -0.00930786f, -0.00146484f, -0.02578735f,
437 0.04595947f, 0.36538696f, 0.47814941f, 0.17025757f, -0.02301025f, -0.00952148f,
438 -0.00140381f, -0.02560425f, 0.04431152f, 0.36279297f, 0.47933960f, 0.17288208f,
439 -0.02264404f, -0.00970459f, -0.00131226f, -0.02542114f, 0.04269409f, 0.36019897f,
440 0.48049927f, 0.17550659f, -0.02227783f, -0.00991821f, -0.00125122f, -0.02523804f,
441 0.04107666f, 0.35760498f, 0.48162842f, 0.17816162f, -0.02188110f, -0.01013184f,
442 -0.00119019f, -0.02505493f, 0.03948975f, 0.35498047f, 0.48272705f, 0.18081665f,
443 -0.02145386f, -0.01034546f, -0.00112915f, -0.02487183f, 0.03793335f, 0.35235596f,
444 0.48379517f, 0.18350220f, -0.02102661f, -0.01058960f, -0.00106812f, -0.02468872f,
445 0.03637695f, 0.34970093f, 0.48483276f, 0.18615723f, -0.02059937f, -0.01080322f,
446 -0.00100708f, -0.02447510f, 0.03485107f, 0.34704590f, 0.48583984f, 0.18887329f,
447 -0.02014160f, -0.01101685f, -0.00094604f, -0.02429199f, 0.03335571f, 0.34439087f,
448 0.48681641f, 0.19155884f, -0.01968384f, -0.01123047f, -0.00088501f, -0.02407837f,
449 0.03186035f, 0.34170532f, 0.48776245f, 0.19427490f, -0.01919556f, -0.01147461f,
450 -0.00082397f, -0.02386475f, 0.03039551f, 0.33901978f, 0.48864746f, 0.19696045f,
451 -0.01867676f, -0.01168823f, -0.00079346f, -0.02365112f, 0.02896118f, 0.33633423f,
452 0.48953247f, 0.19970703f, -0.01818848f, -0.01193237f, -0.00073242f, -0.02343750f,
453 0.02752686f, 0.33361816f, 0.49035645f, 0.20242310f, -0.01763916f, -0.01214600f,
454 -0.00067139f, -0.02322388f, 0.02612305f, 0.33090210f, 0.49118042f, 0.20516968f,
455 -0.01708984f, -0.01239014f, -0.00064087f, -0.02301025f, 0.02471924f, 0.32818604f,
456 0.49194336f, 0.20791626f, -0.01654053f, -0.01263428f, -0.00057983f, -0.02279663f,
457 0.02337646f, 0.32543945f, 0.49267578f, 0.21066284f, -0.01596069f, -0.01284790f,
458 -0.00054932f, -0.02255249f, 0.02203369f, 0.32269287f, 0.49337769f, 0.21343994f,
459 -0.01535034f, -0.01309204f, -0.00048828f, -0.02233887f, 0.02069092f, 0.31994629f,
460 0.49404907f, 0.21618652f, -0.01473999f, -0.01333618f, -0.00045776f, -0.02209473f,
461 0.01937866f, 0.31719971f, 0.49465942f, 0.21896362f, -0.01409912f, -0.01358032f,
462 -0.00042725f, -0.02188110f, 0.01809692f, 0.31442261f, 0.49526978f, 0.22174072f,
463 -0.01345825f, -0.01379395f, -0.00039673f, -0.02163696f, 0.01684570f, 0.31164551f,
464 0.49581909f, 0.22451782f, -0.01278687f, -0.01403809f, -0.00033569f, -0.02142334f,
465 0.01559448f, 0.30886841f, 0.49636841f, 0.22732544f, -0.01211548f, -0.01428223f,
466 -0.00030518f, -0.02117920f, 0.01437378f, 0.30609131f, 0.49685669f, 0.23010254f,
467 -0.01141357f, -0.01452637f, -0.00027466f, -0.02093506f, 0.01318359f, 0.30331421f,
468 0.49731445f, 0.23291016f, -0.01071167f, -0.01477051f, -0.00024414f, -0.02069092f,
469 0.01199341f, 0.30050659f, 0.49774170f, 0.23571777f, -0.00997925f, -0.01501465f,
470 -0.00021362f, -0.02044678f, 0.01083374f, 0.29772949f, 0.49810791f, 0.23852539f,
471 -0.00921631f, -0.01525879f, -0.00018311f, -0.02020264f, 0.00967407f, 0.29492188f,
472 0.49847412f, 0.24133301f, -0.00845337f, -0.01550293f, -0.00015259f, -0.01995850f,
473 0.00854492f, 0.29211426f, 0.49877930f, 0.24414062f, -0.00765991f, -0.01574707f,
474 -0.00015259f, -0.01971436f, 0.00744629f, 0.28930664f, 0.49908447f, 0.24697876f,
475 -0.00686646f, -0.01602173f, -0.00012207f, -0.01947021f, 0.00634766f, 0.28646851f,
476 0.49932861f, 0.24978638f, -0.00604248f, -0.01626587f, -0.00009155f, -0.01922607f,
477 0.00527954f, 0.28366089f, 0.49954224f, 0.25259399f, -0.00518799f, -0.01651001f,
478 -0.00006104f, -0.01898193f, 0.00424194f, 0.28085327f, 0.49972534f, 0.25543213f,
479 -0.00433350f, -0.01675415f, -0.00006104f, -0.01873779f, 0.00320435f, 0.27801514f,
480 0.49984741f, 0.25823975f, -0.00344849f, -0.01699829f, -0.00003052f, -0.01849365f,
481 0.00219727f, 0.27520752f, 0.49996948f, 0.26107788f, -0.00256348f, -0.01724243f,
482 -0.00003052f, -0.01824951f, 0.00122070f, 0.27236938f, 0.50003052f, 0.26388550f,
483 -0.00164795f, -0.01748657f, 0.00000000f, -0.01800537f, 0.00024414f, 0.26956177f,
484 0.50006104f, 0.26672363f, -0.00070190f, -0.01776123f,
485 };
486
487 static constexpr std::array<f32, 1024> lut1 = {
488 0.01275635f, -0.07745361f, 0.18670654f, 0.75119019f, 0.19219971f, -0.07821655f,
489 0.01272583f, 0.00000000f, 0.01281738f, -0.07666016f, 0.18124390f, 0.75106812f,
490 0.19772339f, -0.07897949f, 0.01266479f, 0.00003052f, 0.01284790f, -0.07583618f,
491 0.17581177f, 0.75088501f, 0.20330811f, -0.07971191f, 0.01257324f, 0.00006104f,
492 0.01287842f, -0.07501221f, 0.17044067f, 0.75057983f, 0.20892334f, -0.08041382f,
493 0.01248169f, 0.00009155f, 0.01290894f, -0.07415771f, 0.16510010f, 0.75018311f,
494 0.21453857f, -0.08111572f, 0.01239014f, 0.00012207f, 0.01290894f, -0.07330322f,
495 0.15979004f, 0.74966431f, 0.22021484f, -0.08178711f, 0.01229858f, 0.00015259f,
496 0.01290894f, -0.07241821f, 0.15454102f, 0.74908447f, 0.22592163f, -0.08242798f,
497 0.01217651f, 0.00018311f, 0.01290894f, -0.07150269f, 0.14932251f, 0.74838257f,
498 0.23165894f, -0.08303833f, 0.01205444f, 0.00021362f, 0.01290894f, -0.07058716f,
499 0.14416504f, 0.74755859f, 0.23742676f, -0.08364868f, 0.01193237f, 0.00024414f,
500 0.01287842f, -0.06967163f, 0.13903809f, 0.74667358f, 0.24322510f, -0.08419800f,
501 0.01177979f, 0.00027466f, 0.01284790f, -0.06872559f, 0.13397217f, 0.74566650f,
502 0.24905396f, -0.08474731f, 0.01162720f, 0.00033569f, 0.01281738f, -0.06777954f,
503 0.12893677f, 0.74456787f, 0.25491333f, -0.08526611f, 0.01147461f, 0.00036621f,
504 0.01278687f, -0.06683350f, 0.12396240f, 0.74337769f, 0.26077271f, -0.08575439f,
505 0.01129150f, 0.00042725f, 0.01275635f, -0.06585693f, 0.11901855f, 0.74206543f,
506 0.26669312f, -0.08621216f, 0.01110840f, 0.00045776f, 0.01269531f, -0.06488037f,
507 0.11413574f, 0.74069214f, 0.27261353f, -0.08663940f, 0.01092529f, 0.00051880f,
508 0.01263428f, -0.06387329f, 0.10931396f, 0.73919678f, 0.27853394f, -0.08700562f,
509 0.01071167f, 0.00057983f, 0.01257324f, -0.06286621f, 0.10452271f, 0.73760986f,
510 0.28451538f, -0.08737183f, 0.01049805f, 0.00064087f, 0.01251221f, -0.06185913f,
511 0.09979248f, 0.73593140f, 0.29049683f, -0.08770752f, 0.01025391f, 0.00067139f,
512 0.01242065f, -0.06082153f, 0.09512329f, 0.73413086f, 0.29647827f, -0.08801270f,
513 0.01000977f, 0.00073242f, 0.01232910f, -0.05981445f, 0.09051514f, 0.73226929f,
514 0.30249023f, -0.08828735f, 0.00973511f, 0.00079346f, 0.01226807f, -0.05877686f,
515 0.08593750f, 0.73028564f, 0.30853271f, -0.08850098f, 0.00949097f, 0.00088501f,
516 0.01214600f, -0.05773926f, 0.08142090f, 0.72824097f, 0.31457520f, -0.08871460f,
517 0.00918579f, 0.00094604f, 0.01205444f, -0.05670166f, 0.07696533f, 0.72607422f,
518 0.32061768f, -0.08886719f, 0.00891113f, 0.00100708f, 0.01196289f, -0.05563354f,
519 0.07257080f, 0.72381592f, 0.32669067f, -0.08898926f, 0.00860596f, 0.00106812f,
520 0.01187134f, -0.05459595f, 0.06820679f, 0.72146606f, 0.33276367f, -0.08908081f,
521 0.00827026f, 0.00115967f, 0.01174927f, -0.05352783f, 0.06393433f, 0.71902466f,
522 0.33883667f, -0.08911133f, 0.00796509f, 0.00122070f, 0.01162720f, -0.05245972f,
523 0.05969238f, 0.71649170f, 0.34494019f, -0.08914185f, 0.00759888f, 0.00131226f,
524 0.01150513f, -0.05139160f, 0.05551147f, 0.71389771f, 0.35101318f, -0.08911133f,
525 0.00726318f, 0.00137329f, 0.01138306f, -0.05032349f, 0.05139160f, 0.71118164f,
526 0.35711670f, -0.08901978f, 0.00686646f, 0.00146484f, 0.01126099f, -0.04928589f,
527 0.04733276f, 0.70837402f, 0.36322021f, -0.08892822f, 0.00650024f, 0.00155640f,
528 0.01113892f, -0.04821777f, 0.04333496f, 0.70550537f, 0.36932373f, -0.08877563f,
529 0.00610352f, 0.00164795f, 0.01101685f, -0.04714966f, 0.03939819f, 0.70251465f,
530 0.37542725f, -0.08856201f, 0.00567627f, 0.00173950f, 0.01086426f, -0.04608154f,
531 0.03549194f, 0.69946289f, 0.38153076f, -0.08834839f, 0.00527954f, 0.00183105f,
532 0.01074219f, -0.04501343f, 0.03167725f, 0.69631958f, 0.38763428f, -0.08804321f,
533 0.00482178f, 0.00192261f, 0.01058960f, -0.04394531f, 0.02792358f, 0.69308472f,
534 0.39370728f, -0.08773804f, 0.00436401f, 0.00201416f, 0.01043701f, -0.04287720f,
535 0.02420044f, 0.68975830f, 0.39981079f, -0.08737183f, 0.00390625f, 0.00210571f,
536 0.01031494f, -0.04180908f, 0.02056885f, 0.68637085f, 0.40588379f, -0.08694458f,
537 0.00344849f, 0.00222778f, 0.01016235f, -0.04074097f, 0.01699829f, 0.68289185f,
538 0.41195679f, -0.08648682f, 0.00296021f, 0.00231934f, 0.01000977f, -0.03970337f,
539 0.01345825f, 0.67932129f, 0.41802979f, -0.08596802f, 0.00244141f, 0.00244141f,
540 0.00985718f, -0.03863525f, 0.01000977f, 0.67568970f, 0.42407227f, -0.08541870f,
541 0.00192261f, 0.00253296f, 0.00970459f, -0.03759766f, 0.00662231f, 0.67196655f,
542 0.43011475f, -0.08480835f, 0.00140381f, 0.00265503f, 0.00955200f, -0.03652954f,
543 0.00326538f, 0.66815186f, 0.43612671f, -0.08416748f, 0.00085449f, 0.00277710f,
544 0.00936890f, -0.03549194f, 0.00000000f, 0.66427612f, 0.44213867f, -0.08346558f,
545 0.00027466f, 0.00289917f, 0.00921631f, -0.03445435f, -0.00320435f, 0.66030884f,
546 0.44812012f, -0.08270264f, -0.00027466f, 0.00299072f, 0.00906372f, -0.03344727f,
547 -0.00634766f, 0.65631104f, 0.45407104f, -0.08190918f, -0.00088501f, 0.00311279f,
548 0.00891113f, -0.03240967f, -0.00946045f, 0.65219116f, 0.46002197f, -0.08105469f,
549 -0.00146484f, 0.00323486f, 0.00872803f, -0.03140259f, -0.01248169f, 0.64801025f,
550 0.46594238f, -0.08013916f, -0.00210571f, 0.00338745f, 0.00857544f, -0.03039551f,
551 -0.01544189f, 0.64376831f, 0.47183228f, -0.07919312f, -0.00271606f, 0.00350952f,
552 0.00842285f, -0.02938843f, -0.01834106f, 0.63946533f, 0.47772217f, -0.07818604f,
553 -0.00335693f, 0.00363159f, 0.00823975f, -0.02838135f, -0.02117920f, 0.63507080f,
554 0.48358154f, -0.07711792f, -0.00402832f, 0.00375366f, 0.00808716f, -0.02740479f,
555 -0.02395630f, 0.63061523f, 0.48937988f, -0.07598877f, -0.00469971f, 0.00390625f,
556 0.00793457f, -0.02642822f, -0.02667236f, 0.62609863f, 0.49517822f, -0.07482910f,
557 -0.00537109f, 0.00402832f, 0.00775146f, -0.02545166f, -0.02932739f, 0.62152100f,
558 0.50094604f, -0.07357788f, -0.00607300f, 0.00418091f, 0.00759888f, -0.02450562f,
559 -0.03192139f, 0.61685181f, 0.50665283f, -0.07229614f, -0.00677490f, 0.00430298f,
560 0.00741577f, -0.02352905f, -0.03445435f, 0.61215210f, 0.51235962f, -0.07098389f,
561 -0.00750732f, 0.00445557f, 0.00726318f, -0.02258301f, -0.03689575f, 0.60736084f,
562 0.51800537f, -0.06958008f, -0.00823975f, 0.00460815f, 0.00711060f, -0.02166748f,
563 -0.03930664f, 0.60253906f, 0.52362061f, -0.06811523f, -0.00897217f, 0.00476074f,
564 0.00692749f, -0.02075195f, -0.04165649f, 0.59762573f, 0.52920532f, -0.06661987f,
565 -0.00973511f, 0.00488281f, 0.00677490f, -0.01983643f, -0.04394531f, 0.59268188f,
566 0.53475952f, -0.06506348f, -0.01052856f, 0.00503540f, 0.00662231f, -0.01892090f,
567 -0.04617310f, 0.58767700f, 0.54025269f, -0.06344604f, -0.01129150f, 0.00518799f,
568 0.00643921f, -0.01803589f, -0.04830933f, 0.58261108f, 0.54571533f, -0.06173706f,
569 -0.01208496f, 0.00534058f, 0.00628662f, -0.01715088f, -0.05041504f, 0.57748413f,
570 0.55111694f, -0.05999756f, -0.01290894f, 0.00549316f, 0.00613403f, -0.01626587f,
571 -0.05245972f, 0.57232666f, 0.55648804f, -0.05819702f, -0.01373291f, 0.00564575f,
572 0.00598145f, -0.01541138f, -0.05444336f, 0.56707764f, 0.56182861f, -0.05636597f,
573 -0.01455688f, 0.00582886f, 0.00582886f, -0.01455688f, -0.05636597f, 0.56182861f,
574 0.56707764f, -0.05444336f, -0.01541138f, 0.00598145f, 0.00564575f, -0.01373291f,
575 -0.05819702f, 0.55648804f, 0.57232666f, -0.05245972f, -0.01626587f, 0.00613403f,
576 0.00549316f, -0.01290894f, -0.05999756f, 0.55111694f, 0.57748413f, -0.05041504f,
577 -0.01715088f, 0.00628662f, 0.00534058f, -0.01208496f, -0.06173706f, 0.54571533f,
578 0.58261108f, -0.04830933f, -0.01803589f, 0.00643921f, 0.00518799f, -0.01129150f,
579 -0.06344604f, 0.54025269f, 0.58767700f, -0.04617310f, -0.01892090f, 0.00662231f,
580 0.00503540f, -0.01052856f, -0.06506348f, 0.53475952f, 0.59268188f, -0.04394531f,
581 -0.01983643f, 0.00677490f, 0.00488281f, -0.00973511f, -0.06661987f, 0.52920532f,
582 0.59762573f, -0.04165649f, -0.02075195f, 0.00692749f, 0.00476074f, -0.00897217f,
583 -0.06811523f, 0.52362061f, 0.60253906f, -0.03930664f, -0.02166748f, 0.00711060f,
584 0.00460815f, -0.00823975f, -0.06958008f, 0.51800537f, 0.60736084f, -0.03689575f,
585 -0.02258301f, 0.00726318f, 0.00445557f, -0.00750732f, -0.07098389f, 0.51235962f,
586 0.61215210f, -0.03445435f, -0.02352905f, 0.00741577f, 0.00430298f, -0.00677490f,
587 -0.07229614f, 0.50665283f, 0.61685181f, -0.03192139f, -0.02450562f, 0.00759888f,
588 0.00418091f, -0.00607300f, -0.07357788f, 0.50094604f, 0.62152100f, -0.02932739f,
589 -0.02545166f, 0.00775146f, 0.00402832f, -0.00537109f, -0.07482910f, 0.49517822f,
590 0.62609863f, -0.02667236f, -0.02642822f, 0.00793457f, 0.00390625f, -0.00469971f,
591 -0.07598877f, 0.48937988f, 0.63061523f, -0.02395630f, -0.02740479f, 0.00808716f,
592 0.00375366f, -0.00402832f, -0.07711792f, 0.48358154f, 0.63507080f, -0.02117920f,
593 -0.02838135f, 0.00823975f, 0.00363159f, -0.00335693f, -0.07818604f, 0.47772217f,
594 0.63946533f, -0.01834106f, -0.02938843f, 0.00842285f, 0.00350952f, -0.00271606f,
595 -0.07919312f, 0.47183228f, 0.64376831f, -0.01544189f, -0.03039551f, 0.00857544f,
596 0.00338745f, -0.00210571f, -0.08013916f, 0.46594238f, 0.64801025f, -0.01248169f,
597 -0.03140259f, 0.00872803f, 0.00323486f, -0.00146484f, -0.08105469f, 0.46002197f,
598 0.65219116f, -0.00946045f, -0.03240967f, 0.00891113f, 0.00311279f, -0.00088501f,
599 -0.08190918f, 0.45407104f, 0.65631104f, -0.00634766f, -0.03344727f, 0.00906372f,
600 0.00299072f, -0.00027466f, -0.08270264f, 0.44812012f, 0.66030884f, -0.00320435f,
601 -0.03445435f, 0.00921631f, 0.00289917f, 0.00027466f, -0.08346558f, 0.44213867f,
602 0.66427612f, 0.00000000f, -0.03549194f, 0.00936890f, 0.00277710f, 0.00085449f,
603 -0.08416748f, 0.43612671f, 0.66815186f, 0.00326538f, -0.03652954f, 0.00955200f,
604 0.00265503f, 0.00140381f, -0.08480835f, 0.43011475f, 0.67196655f, 0.00662231f,
605 -0.03759766f, 0.00970459f, 0.00253296f, 0.00192261f, -0.08541870f, 0.42407227f,
606 0.67568970f, 0.01000977f, -0.03863525f, 0.00985718f, 0.00244141f, 0.00244141f,
607 -0.08596802f, 0.41802979f, 0.67932129f, 0.01345825f, -0.03970337f, 0.01000977f,
608 0.00231934f, 0.00296021f, -0.08648682f, 0.41195679f, 0.68289185f, 0.01699829f,
609 -0.04074097f, 0.01016235f, 0.00222778f, 0.00344849f, -0.08694458f, 0.40588379f,
610 0.68637085f, 0.02056885f, -0.04180908f, 0.01031494f, 0.00210571f, 0.00390625f,
611 -0.08737183f, 0.39981079f, 0.68975830f, 0.02420044f, -0.04287720f, 0.01043701f,
612 0.00201416f, 0.00436401f, -0.08773804f, 0.39370728f, 0.69308472f, 0.02792358f,
613 -0.04394531f, 0.01058960f, 0.00192261f, 0.00482178f, -0.08804321f, 0.38763428f,
614 0.69631958f, 0.03167725f, -0.04501343f, 0.01074219f, 0.00183105f, 0.00527954f,
615 -0.08834839f, 0.38153076f, 0.69946289f, 0.03549194f, -0.04608154f, 0.01086426f,
616 0.00173950f, 0.00567627f, -0.08856201f, 0.37542725f, 0.70251465f, 0.03939819f,
617 -0.04714966f, 0.01101685f, 0.00164795f, 0.00610352f, -0.08877563f, 0.36932373f,
618 0.70550537f, 0.04333496f, -0.04821777f, 0.01113892f, 0.00155640f, 0.00650024f,
619 -0.08892822f, 0.36322021f, 0.70837402f, 0.04733276f, -0.04928589f, 0.01126099f,
620 0.00146484f, 0.00686646f, -0.08901978f, 0.35711670f, 0.71118164f, 0.05139160f,
621 -0.05032349f, 0.01138306f, 0.00137329f, 0.00726318f, -0.08911133f, 0.35101318f,
622 0.71389771f, 0.05551147f, -0.05139160f, 0.01150513f, 0.00131226f, 0.00759888f,
623 -0.08914185f, 0.34494019f, 0.71649170f, 0.05969238f, -0.05245972f, 0.01162720f,
624 0.00122070f, 0.00796509f, -0.08911133f, 0.33883667f, 0.71902466f, 0.06393433f,
625 -0.05352783f, 0.01174927f, 0.00115967f, 0.00827026f, -0.08908081f, 0.33276367f,
626 0.72146606f, 0.06820679f, -0.05459595f, 0.01187134f, 0.00106812f, 0.00860596f,
627 -0.08898926f, 0.32669067f, 0.72381592f, 0.07257080f, -0.05563354f, 0.01196289f,
628 0.00100708f, 0.00891113f, -0.08886719f, 0.32061768f, 0.72607422f, 0.07696533f,
629 -0.05670166f, 0.01205444f, 0.00094604f, 0.00918579f, -0.08871460f, 0.31457520f,
630 0.72824097f, 0.08142090f, -0.05773926f, 0.01214600f, 0.00088501f, 0.00949097f,
631 -0.08850098f, 0.30853271f, 0.73028564f, 0.08593750f, -0.05877686f, 0.01226807f,
632 0.00079346f, 0.00973511f, -0.08828735f, 0.30249023f, 0.73226929f, 0.09051514f,
633 -0.05981445f, 0.01232910f, 0.00073242f, 0.01000977f, -0.08801270f, 0.29647827f,
634 0.73413086f, 0.09512329f, -0.06082153f, 0.01242065f, 0.00067139f, 0.01025391f,
635 -0.08770752f, 0.29049683f, 0.73593140f, 0.09979248f, -0.06185913f, 0.01251221f,
636 0.00064087f, 0.01049805f, -0.08737183f, 0.28451538f, 0.73760986f, 0.10452271f,
637 -0.06286621f, 0.01257324f, 0.00057983f, 0.01071167f, -0.08700562f, 0.27853394f,
638 0.73919678f, 0.10931396f, -0.06387329f, 0.01263428f, 0.00051880f, 0.01092529f,
639 -0.08663940f, 0.27261353f, 0.74069214f, 0.11413574f, -0.06488037f, 0.01269531f,
640 0.00045776f, 0.01110840f, -0.08621216f, 0.26669312f, 0.74206543f, 0.11901855f,
641 -0.06585693f, 0.01275635f, 0.00042725f, 0.01129150f, -0.08575439f, 0.26077271f,
642 0.74337769f, 0.12396240f, -0.06683350f, 0.01278687f, 0.00036621f, 0.01147461f,
643 -0.08526611f, 0.25491333f, 0.74456787f, 0.12893677f, -0.06777954f, 0.01281738f,
644 0.00033569f, 0.01162720f, -0.08474731f, 0.24905396f, 0.74566650f, 0.13397217f,
645 -0.06872559f, 0.01284790f, 0.00027466f, 0.01177979f, -0.08419800f, 0.24322510f,
646 0.74667358f, 0.13903809f, -0.06967163f, 0.01287842f, 0.00024414f, 0.01193237f,
647 -0.08364868f, 0.23742676f, 0.74755859f, 0.14416504f, -0.07058716f, 0.01290894f,
648 0.00021362f, 0.01205444f, -0.08303833f, 0.23165894f, 0.74838257f, 0.14932251f,
649 -0.07150269f, 0.01290894f, 0.00018311f, 0.01217651f, -0.08242798f, 0.22592163f,
650 0.74908447f, 0.15454102f, -0.07241821f, 0.01290894f, 0.00015259f, 0.01229858f,
651 -0.08178711f, 0.22021484f, 0.74966431f, 0.15979004f, -0.07330322f, 0.01290894f,
652 0.00012207f, 0.01239014f, -0.08111572f, 0.21453857f, 0.75018311f, 0.16510010f,
653 -0.07415771f, 0.01290894f, 0.00009155f, 0.01248169f, -0.08041382f, 0.20892334f,
654 0.75057983f, 0.17044067f, -0.07501221f, 0.01287842f, 0.00006104f, 0.01257324f,
655 -0.07971191f, 0.20330811f, 0.75088501f, 0.17581177f, -0.07583618f, 0.01284790f,
656 0.00003052f, 0.01266479f, -0.07897949f, 0.19772339f, 0.75106812f, 0.18124390f,
657 -0.07666016f, 0.01281738f, 0.00000000f, 0.01272583f, -0.07821655f, 0.19219971f,
658 0.75119019f, 0.18670654f, -0.07745361f, 0.01275635f,
659 };
660
661 static constexpr std::array<f32, 1024> lut2 = {
662 -0.00036621f, 0.00143433f, -0.00408936f, 0.99996948f, 0.00247192f, -0.00048828f,
663 0.00006104f, 0.00000000f, -0.00079346f, 0.00329590f, -0.01052856f, 0.99975586f,
664 0.00918579f, -0.00241089f, 0.00051880f, -0.00003052f, -0.00122070f, 0.00512695f,
665 -0.01684570f, 0.99929810f, 0.01605225f, -0.00439453f, 0.00097656f, -0.00006104f,
666 -0.00161743f, 0.00689697f, -0.02297974f, 0.99862671f, 0.02304077f, -0.00640869f,
667 0.00143433f, -0.00009155f, -0.00201416f, 0.00866699f, -0.02899170f, 0.99774170f,
668 0.03018188f, -0.00845337f, 0.00192261f, -0.00015259f, -0.00238037f, 0.01037598f,
669 -0.03488159f, 0.99664307f, 0.03741455f, -0.01055908f, 0.00241089f, -0.00018311f,
670 -0.00274658f, 0.01202393f, -0.04061890f, 0.99533081f, 0.04483032f, -0.01266479f,
671 0.00292969f, -0.00024414f, -0.00308228f, 0.01364136f, -0.04620361f, 0.99377441f,
672 0.05233765f, -0.01483154f, 0.00344849f, -0.00027466f, -0.00341797f, 0.01522827f,
673 -0.05163574f, 0.99200439f, 0.05999756f, -0.01699829f, 0.00396729f, -0.00033569f,
674 -0.00375366f, 0.01678467f, -0.05691528f, 0.99002075f, 0.06777954f, -0.01922607f,
675 0.00451660f, -0.00039673f, -0.00405884f, 0.01828003f, -0.06207275f, 0.98782349f,
676 0.07568359f, -0.02145386f, 0.00506592f, -0.00042725f, -0.00436401f, 0.01971436f,
677 -0.06707764f, 0.98541260f, 0.08370972f, -0.02374268f, 0.00564575f, -0.00048828f,
678 -0.00463867f, 0.02114868f, -0.07192993f, 0.98278809f, 0.09185791f, -0.02603149f,
679 0.00622559f, -0.00054932f, -0.00494385f, 0.02252197f, -0.07666016f, 0.97991943f,
680 0.10012817f, -0.02835083f, 0.00680542f, -0.00061035f, -0.00518799f, 0.02383423f,
681 -0.08123779f, 0.97686768f, 0.10848999f, -0.03073120f, 0.00738525f, -0.00070190f,
682 -0.00543213f, 0.02511597f, -0.08566284f, 0.97360229f, 0.11700439f, -0.03308105f,
683 0.00799561f, -0.00076294f, -0.00567627f, 0.02636719f, -0.08993530f, 0.97012329f,
684 0.12561035f, -0.03549194f, 0.00860596f, -0.00082397f, -0.00592041f, 0.02755737f,
685 -0.09405518f, 0.96643066f, 0.13436890f, -0.03790283f, 0.00924683f, -0.00091553f,
686 -0.00613403f, 0.02868652f, -0.09805298f, 0.96252441f, 0.14318848f, -0.04034424f,
687 0.00985718f, -0.00097656f, -0.00631714f, 0.02981567f, -0.10189819f, 0.95843506f,
688 0.15213013f, -0.04281616f, 0.01049805f, -0.00106812f, -0.00653076f, 0.03085327f,
689 -0.10559082f, 0.95413208f, 0.16119385f, -0.04528809f, 0.01113892f, -0.00112915f,
690 -0.00671387f, 0.03189087f, -0.10916138f, 0.94961548f, 0.17034912f, -0.04779053f,
691 0.01181030f, -0.00122070f, -0.00686646f, 0.03286743f, -0.11254883f, 0.94491577f,
692 0.17959595f, -0.05029297f, 0.01248169f, -0.00131226f, -0.00701904f, 0.03378296f,
693 -0.11584473f, 0.94000244f, 0.18893433f, -0.05279541f, 0.01315308f, -0.00140381f,
694 -0.00717163f, 0.03466797f, -0.11895752f, 0.93490601f, 0.19839478f, -0.05532837f,
695 0.01382446f, -0.00149536f, -0.00732422f, 0.03552246f, -0.12194824f, 0.92962646f,
696 0.20791626f, -0.05786133f, 0.01449585f, -0.00158691f, -0.00744629f, 0.03631592f,
697 -0.12478638f, 0.92413330f, 0.21752930f, -0.06042480f, 0.01519775f, -0.00167847f,
698 -0.00753784f, 0.03707886f, -0.12750244f, 0.91848755f, 0.22723389f, -0.06298828f,
699 0.01586914f, -0.00177002f, -0.00765991f, 0.03781128f, -0.13006592f, 0.91262817f,
700 0.23703003f, -0.06555176f, 0.01657104f, -0.00189209f, -0.00775146f, 0.03848267f,
701 -0.13250732f, 0.90658569f, 0.24691772f, -0.06808472f, 0.01727295f, -0.00198364f,
702 -0.00784302f, 0.03909302f, -0.13479614f, 0.90036011f, 0.25683594f, -0.07064819f,
703 0.01797485f, -0.00210571f, -0.00790405f, 0.03970337f, -0.13696289f, 0.89395142f,
704 0.26687622f, -0.07321167f, 0.01870728f, -0.00219727f, -0.00796509f, 0.04025269f,
705 -0.13900757f, 0.88739014f, 0.27694702f, -0.07577515f, 0.01940918f, -0.00231934f,
706 -0.00802612f, 0.04077148f, -0.14089966f, 0.88064575f, 0.28710938f, -0.07833862f,
707 0.02011108f, -0.00244141f, -0.00808716f, 0.04122925f, -0.14263916f, 0.87374878f,
708 0.29733276f, -0.08090210f, 0.02084351f, -0.00253296f, -0.00811768f, 0.04165649f,
709 -0.14428711f, 0.86666870f, 0.30761719f, -0.08343506f, 0.02154541f, -0.00265503f,
710 -0.00814819f, 0.04205322f, -0.14578247f, 0.85940552f, 0.31793213f, -0.08596802f,
711 0.02227783f, -0.00277710f, -0.00814819f, 0.04238892f, -0.14715576f, 0.85202026f,
712 0.32833862f, -0.08847046f, 0.02297974f, -0.00289917f, -0.00817871f, 0.04272461f,
713 -0.14840698f, 0.84445190f, 0.33874512f, -0.09097290f, 0.02371216f, -0.00302124f,
714 -0.00817871f, 0.04299927f, -0.14953613f, 0.83673096f, 0.34924316f, -0.09347534f,
715 0.02441406f, -0.00314331f, -0.00817871f, 0.04321289f, -0.15054321f, 0.82888794f,
716 0.35977173f, -0.09594727f, 0.02514648f, -0.00326538f, -0.00814819f, 0.04342651f,
717 -0.15142822f, 0.82086182f, 0.37033081f, -0.09838867f, 0.02584839f, -0.00341797f,
718 -0.00814819f, 0.04357910f, -0.15219116f, 0.81271362f, 0.38092041f, -0.10079956f,
719 0.02655029f, -0.00354004f, -0.00811768f, 0.04373169f, -0.15283203f, 0.80441284f,
720 0.39154053f, -0.10321045f, 0.02725220f, -0.00366211f, -0.00808716f, 0.04382324f,
721 -0.15338135f, 0.79598999f, 0.40219116f, -0.10559082f, 0.02795410f, -0.00381470f,
722 -0.00805664f, 0.04388428f, -0.15377808f, 0.78741455f, 0.41287231f, -0.10794067f,
723 0.02865601f, -0.00393677f, -0.00799561f, 0.04388428f, -0.15408325f, 0.77871704f,
724 0.42358398f, -0.11026001f, 0.02935791f, -0.00405884f, -0.00793457f, 0.04388428f,
725 -0.15426636f, 0.76989746f, 0.43429565f, -0.11251831f, 0.03002930f, -0.00421143f,
726 -0.00787354f, 0.04385376f, -0.15435791f, 0.76095581f, 0.44500732f, -0.11477661f,
727 0.03070068f, -0.00433350f, -0.00781250f, 0.04379272f, -0.15435791f, 0.75192261f,
728 0.45574951f, -0.11697388f, 0.03137207f, -0.00448608f, -0.00775146f, 0.04367065f,
729 -0.15420532f, 0.74273682f, 0.46649170f, -0.11914062f, 0.03201294f, -0.00460815f,
730 -0.00769043f, 0.04354858f, -0.15399170f, 0.73345947f, 0.47723389f, -0.12127686f,
731 0.03268433f, -0.00473022f, -0.00759888f, 0.04339600f, -0.15365601f, 0.72406006f,
732 0.48794556f, -0.12335205f, 0.03329468f, -0.00488281f, -0.00750732f, 0.04321289f,
733 -0.15322876f, 0.71456909f, 0.49868774f, -0.12539673f, 0.03393555f, -0.00500488f,
734 -0.00741577f, 0.04296875f, -0.15270996f, 0.70498657f, 0.50936890f, -0.12738037f,
735 0.03454590f, -0.00515747f, -0.00732422f, 0.04272461f, -0.15209961f, 0.69528198f,
736 0.52008057f, -0.12930298f, 0.03515625f, -0.00527954f, -0.00723267f, 0.04248047f,
737 -0.15136719f, 0.68551636f, 0.53076172f, -0.13119507f, 0.03573608f, -0.00543213f,
738 -0.00714111f, 0.04217529f, -0.15057373f, 0.67565918f, 0.54138184f, -0.13299561f,
739 0.03631592f, -0.00555420f, -0.00701904f, 0.04183960f, -0.14968872f, 0.66571045f,
740 0.55200195f, -0.13476562f, 0.03689575f, -0.00567627f, -0.00692749f, 0.04150391f,
741 -0.14871216f, 0.65567017f, 0.56259155f, -0.13647461f, 0.03741455f, -0.00582886f,
742 -0.00680542f, 0.04113770f, -0.14767456f, 0.64556885f, 0.57315063f, -0.13812256f,
743 0.03796387f, -0.00595093f, -0.00668335f, 0.04074097f, -0.14651489f, 0.63540649f,
744 0.58364868f, -0.13970947f, 0.03845215f, -0.00607300f, -0.00656128f, 0.04031372f,
745 -0.14529419f, 0.62518311f, 0.59411621f, -0.14120483f, 0.03897095f, -0.00619507f,
746 -0.00643921f, 0.03988647f, -0.14401245f, 0.61486816f, 0.60452271f, -0.14263916f,
747 0.03942871f, -0.00631714f, -0.00631714f, 0.03942871f, -0.14263916f, 0.60452271f,
748 0.61486816f, -0.14401245f, 0.03988647f, -0.00643921f, -0.00619507f, 0.03897095f,
749 -0.14120483f, 0.59411621f, 0.62518311f, -0.14529419f, 0.04031372f, -0.00656128f,
750 -0.00607300f, 0.03845215f, -0.13970947f, 0.58364868f, 0.63540649f, -0.14651489f,
751 0.04074097f, -0.00668335f, -0.00595093f, 0.03796387f, -0.13812256f, 0.57315063f,
752 0.64556885f, -0.14767456f, 0.04113770f, -0.00680542f, -0.00582886f, 0.03741455f,
753 -0.13647461f, 0.56259155f, 0.65567017f, -0.14871216f, 0.04150391f, -0.00692749f,
754 -0.00567627f, 0.03689575f, -0.13476562f, 0.55200195f, 0.66571045f, -0.14968872f,
755 0.04183960f, -0.00701904f, -0.00555420f, 0.03631592f, -0.13299561f, 0.54138184f,
756 0.67565918f, -0.15057373f, 0.04217529f, -0.00714111f, -0.00543213f, 0.03573608f,
757 -0.13119507f, 0.53076172f, 0.68551636f, -0.15136719f, 0.04248047f, -0.00723267f,
758 -0.00527954f, 0.03515625f, -0.12930298f, 0.52008057f, 0.69528198f, -0.15209961f,
759 0.04272461f, -0.00732422f, -0.00515747f, 0.03454590f, -0.12738037f, 0.50936890f,
760 0.70498657f, -0.15270996f, 0.04296875f, -0.00741577f, -0.00500488f, 0.03393555f,
761 -0.12539673f, 0.49868774f, 0.71456909f, -0.15322876f, 0.04321289f, -0.00750732f,
762 -0.00488281f, 0.03329468f, -0.12335205f, 0.48794556f, 0.72406006f, -0.15365601f,
763 0.04339600f, -0.00759888f, -0.00473022f, 0.03268433f, -0.12127686f, 0.47723389f,
764 0.73345947f, -0.15399170f, 0.04354858f, -0.00769043f, -0.00460815f, 0.03201294f,
765 -0.11914062f, 0.46649170f, 0.74273682f, -0.15420532f, 0.04367065f, -0.00775146f,
766 -0.00448608f, 0.03137207f, -0.11697388f, 0.45574951f, 0.75192261f, -0.15435791f,
767 0.04379272f, -0.00781250f, -0.00433350f, 0.03070068f, -0.11477661f, 0.44500732f,
768 0.76095581f, -0.15435791f, 0.04385376f, -0.00787354f, -0.00421143f, 0.03002930f,
769 -0.11251831f, 0.43429565f, 0.76989746f, -0.15426636f, 0.04388428f, -0.00793457f,
770 -0.00405884f, 0.02935791f, -0.11026001f, 0.42358398f, 0.77871704f, -0.15408325f,
771 0.04388428f, -0.00799561f, -0.00393677f, 0.02865601f, -0.10794067f, 0.41287231f,
772 0.78741455f, -0.15377808f, 0.04388428f, -0.00805664f, -0.00381470f, 0.02795410f,
773 -0.10559082f, 0.40219116f, 0.79598999f, -0.15338135f, 0.04382324f, -0.00808716f,
774 -0.00366211f, 0.02725220f, -0.10321045f, 0.39154053f, 0.80441284f, -0.15283203f,
775 0.04373169f, -0.00811768f, -0.00354004f, 0.02655029f, -0.10079956f, 0.38092041f,
776 0.81271362f, -0.15219116f, 0.04357910f, -0.00814819f, -0.00341797f, 0.02584839f,
777 -0.09838867f, 0.37033081f, 0.82086182f, -0.15142822f, 0.04342651f, -0.00814819f,
778 -0.00326538f, 0.02514648f, -0.09594727f, 0.35977173f, 0.82888794f, -0.15054321f,
779 0.04321289f, -0.00817871f, -0.00314331f, 0.02441406f, -0.09347534f, 0.34924316f,
780 0.83673096f, -0.14953613f, 0.04299927f, -0.00817871f, -0.00302124f, 0.02371216f,
781 -0.09097290f, 0.33874512f, 0.84445190f, -0.14840698f, 0.04272461f, -0.00817871f,
782 -0.00289917f, 0.02297974f, -0.08847046f, 0.32833862f, 0.85202026f, -0.14715576f,
783 0.04238892f, -0.00814819f, -0.00277710f, 0.02227783f, -0.08596802f, 0.31793213f,
784 0.85940552f, -0.14578247f, 0.04205322f, -0.00814819f, -0.00265503f, 0.02154541f,
785 -0.08343506f, 0.30761719f, 0.86666870f, -0.14428711f, 0.04165649f, -0.00811768f,
786 -0.00253296f, 0.02084351f, -0.08090210f, 0.29733276f, 0.87374878f, -0.14263916f,
787 0.04122925f, -0.00808716f, -0.00244141f, 0.02011108f, -0.07833862f, 0.28710938f,
788 0.88064575f, -0.14089966f, 0.04077148f, -0.00802612f, -0.00231934f, 0.01940918f,
789 -0.07577515f, 0.27694702f, 0.88739014f, -0.13900757f, 0.04025269f, -0.00796509f,
790 -0.00219727f, 0.01870728f, -0.07321167f, 0.26687622f, 0.89395142f, -0.13696289f,
791 0.03970337f, -0.00790405f, -0.00210571f, 0.01797485f, -0.07064819f, 0.25683594f,
792 0.90036011f, -0.13479614f, 0.03909302f, -0.00784302f, -0.00198364f, 0.01727295f,
793 -0.06808472f, 0.24691772f, 0.90658569f, -0.13250732f, 0.03848267f, -0.00775146f,
794 -0.00189209f, 0.01657104f, -0.06555176f, 0.23703003f, 0.91262817f, -0.13006592f,
795 0.03781128f, -0.00765991f, -0.00177002f, 0.01586914f, -0.06298828f, 0.22723389f,
796 0.91848755f, -0.12750244f, 0.03707886f, -0.00753784f, -0.00167847f, 0.01519775f,
797 -0.06042480f, 0.21752930f, 0.92413330f, -0.12478638f, 0.03631592f, -0.00744629f,
798 -0.00158691f, 0.01449585f, -0.05786133f, 0.20791626f, 0.92962646f, -0.12194824f,
799 0.03552246f, -0.00732422f, -0.00149536f, 0.01382446f, -0.05532837f, 0.19839478f,
800 0.93490601f, -0.11895752f, 0.03466797f, -0.00717163f, -0.00140381f, 0.01315308f,
801 -0.05279541f, 0.18893433f, 0.94000244f, -0.11584473f, 0.03378296f, -0.00701904f,
802 -0.00131226f, 0.01248169f, -0.05029297f, 0.17959595f, 0.94491577f, -0.11254883f,
803 0.03286743f, -0.00686646f, -0.00122070f, 0.01181030f, -0.04779053f, 0.17034912f,
804 0.94961548f, -0.10916138f, 0.03189087f, -0.00671387f, -0.00112915f, 0.01113892f,
805 -0.04528809f, 0.16119385f, 0.95413208f, -0.10559082f, 0.03085327f, -0.00653076f,
806 -0.00106812f, 0.01049805f, -0.04281616f, 0.15213013f, 0.95843506f, -0.10189819f,
807 0.02981567f, -0.00631714f, -0.00097656f, 0.00985718f, -0.04034424f, 0.14318848f,
808 0.96252441f, -0.09805298f, 0.02868652f, -0.00613403f, -0.00091553f, 0.00924683f,
809 -0.03790283f, 0.13436890f, 0.96643066f, -0.09405518f, 0.02755737f, -0.00592041f,
810 -0.00082397f, 0.00860596f, -0.03549194f, 0.12561035f, 0.97012329f, -0.08993530f,
811 0.02636719f, -0.00567627f, -0.00076294f, 0.00799561f, -0.03308105f, 0.11700439f,
812 0.97360229f, -0.08566284f, 0.02511597f, -0.00543213f, -0.00070190f, 0.00738525f,
813 -0.03073120f, 0.10848999f, 0.97686768f, -0.08123779f, 0.02383423f, -0.00518799f,
814 -0.00061035f, 0.00680542f, -0.02835083f, 0.10012817f, 0.97991943f, -0.07666016f,
815 0.02252197f, -0.00494385f, -0.00054932f, 0.00622559f, -0.02603149f, 0.09185791f,
816 0.98278809f, -0.07192993f, 0.02114868f, -0.00463867f, -0.00048828f, 0.00564575f,
817 -0.02374268f, 0.08370972f, 0.98541260f, -0.06707764f, 0.01971436f, -0.00436401f,
818 -0.00042725f, 0.00506592f, -0.02145386f, 0.07568359f, 0.98782349f, -0.06207275f,
819 0.01828003f, -0.00405884f, -0.00039673f, 0.00451660f, -0.01922607f, 0.06777954f,
820 0.99002075f, -0.05691528f, 0.01678467f, -0.00375366f, -0.00033569f, 0.00396729f,
821 -0.01699829f, 0.05999756f, 0.99200439f, -0.05163574f, 0.01522827f, -0.00341797f,
822 -0.00027466f, 0.00344849f, -0.01483154f, 0.05233765f, 0.99377441f, -0.04620361f,
823 0.01364136f, -0.00308228f, -0.00024414f, 0.00292969f, -0.01266479f, 0.04483032f,
824 0.99533081f, -0.04061890f, 0.01202393f, -0.00274658f, -0.00018311f, 0.00241089f,
825 -0.01055908f, 0.03741455f, 0.99664307f, -0.03488159f, 0.01037598f, -0.00238037f,
826 -0.00015259f, 0.00192261f, -0.00845337f, 0.03018188f, 0.99774170f, -0.02899170f,
827 0.00866699f, -0.00201416f, -0.00009155f, 0.00143433f, -0.00640869f, 0.02304077f,
828 0.99862671f, -0.02297974f, 0.00689697f, -0.00161743f, -0.00006104f, 0.00097656f,
829 -0.00439453f, 0.01605225f, 0.99929810f, -0.01684570f, 0.00512695f, -0.00122070f,
830 -0.00003052f, 0.00051880f, -0.00241089f, 0.00918579f, 0.99975586f, -0.01052856f,
831 0.00329590f, -0.00079346f, 0.00000000f, 0.00006104f, -0.00048828f, 0.00247192f,
832 0.99996948f, -0.00408936f, 0.00143433f, -0.00036621f,
833 };
834
835 const auto get_lut = [&]() -> std::span<const f32> {
836 if (sample_rate_ratio <= 1.0f) {
837 return std::span<const f32>(lut2.data(), lut2.size());
838 } else if (sample_rate_ratio < 1.3f) {
839 return std::span<const f32>(lut1.data(), lut1.size());
840 } else {
841 return std::span<const f32>(lut0.data(), lut0.size());
842 }
843 };
844
845 auto lut{get_lut()};
846 u32 read_index{0};
847 for (u32 i = 0; i < samples_to_write; i++) {
848 const auto lut_index{(fraction.get_frac() >> 8) * 8};
849 const Common::FixedPoint<56, 8> sample0{input[read_index + 0] * lut[lut_index + 0]};
850 const Common::FixedPoint<56, 8> sample1{input[read_index + 1] * lut[lut_index + 1]};
851 const Common::FixedPoint<56, 8> sample2{input[read_index + 2] * lut[lut_index + 2]};
852 const Common::FixedPoint<56, 8> sample3{input[read_index + 3] * lut[lut_index + 3]};
853 const Common::FixedPoint<56, 8> sample4{input[read_index + 4] * lut[lut_index + 4]};
854 const Common::FixedPoint<56, 8> sample5{input[read_index + 5] * lut[lut_index + 5]};
855 const Common::FixedPoint<56, 8> sample6{input[read_index + 6] * lut[lut_index + 6]};
856 const Common::FixedPoint<56, 8> sample7{input[read_index + 7] * lut[lut_index + 7]};
857 output[i] = (sample0 + sample1 + sample2 + sample3 + sample4 + sample5 + sample6 + sample7)
858 .to_int_floor();
859 fraction += sample_rate_ratio;
860 read_index += static_cast<u32>(fraction.to_int_floor());
861 fraction.clear_int();
862 }
863}
864
865void Resample(std::span<s32> output, std::span<const s16> input,
866 const Common::FixedPoint<49, 15>& sample_rate_ratio,
867 Common::FixedPoint<49, 15>& fraction, const u32 samples_to_write,
868 const SrcQuality src_quality) {
869
870 switch (src_quality) {
871 case SrcQuality::Low:
872 ResampleLowQuality(output, input, sample_rate_ratio, fraction, samples_to_write);
873 break;
874 case SrcQuality::Medium:
875 ResampleNormalQuality(output, input, sample_rate_ratio, fraction, samples_to_write);
876 break;
877 case SrcQuality::High:
878 ResampleHighQuality(output, input, sample_rate_ratio, fraction, samples_to_write);
879 break;
880 }
881}
882
883} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/resample/resample.h b/src/audio_core/renderer/command/resample/resample.h
new file mode 100644
index 000000000..ba9209b82
--- /dev/null
+++ b/src/audio_core/renderer/command/resample/resample.h
@@ -0,0 +1,29 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <span>
7
8#include "audio_core/common/common.h"
9#include "common/common_types.h"
10#include "common/fixed_point.h"
11
12namespace AudioCore::AudioRenderer {
13/**
14 * Resample an input buffer into an output buffer, according to the sample_rate_ratio.
15 *
16 * @param output - Output buffer.
17 * @param input - Input buffer.
18 * @param sample_rate_ratio - Ratio for resampling.
19 e.g 32000/48000 = 0.666 input samples read per output.
20 * @param fraction - Current read fraction, written to and should be passed back in for
21 * multiple calls.
22 * @param samples_to_write - Number of samples to write.
23 * @param src_quality - Resampling quality.
24 */
25void Resample(std::span<s32> output, std::span<const s16> input,
26 const Common::FixedPoint<49, 15>& sample_rate_ratio,
27 Common::FixedPoint<49, 15>& fraction, u32 samples_to_write, SrcQuality src_quality);
28
29} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/resample/upsample.cpp b/src/audio_core/renderer/command/resample/upsample.cpp
new file mode 100644
index 000000000..6c3ff31f7
--- /dev/null
+++ b/src/audio_core/renderer/command/resample/upsample.cpp
@@ -0,0 +1,262 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <array>
5
6#include "audio_core/renderer/adsp/command_list_processor.h"
7#include "audio_core/renderer/command/resample/upsample.h"
8#include "audio_core/renderer/upsampler/upsampler_info.h"
9
10namespace AudioCore::AudioRenderer {
11/**
12 * Upsampling impl. Input must be 8K, 16K or 32K, output is 48K.
13 *
14 * @param output - Output buffer.
15 * @param input - Input buffer.
16 * @param target_sample_count - Number of samples for output.
17 * @param state - Upsampler state, updated each call.
18 */
19static void SrcProcessFrame(std::span<s32> output, std::span<const s32> input,
20 const u32 target_sample_count, const u32 source_sample_count,
21 UpsamplerState* state) {
22 constexpr u32 WindowSize = 10;
23 constexpr std::array<Common::FixedPoint<24, 8>, WindowSize> SincWindow1{
24 51.93359375f, -18.80078125f, 9.73046875f, -5.33203125f, 2.84375f,
25 -1.41015625f, 0.62109375f, -0.2265625f, 0.0625f, -0.00390625f,
26 };
27 constexpr std::array<Common::FixedPoint<24, 8>, WindowSize> SincWindow2{
28 105.35546875f, -24.52734375f, 11.9609375f, -6.515625f, 3.52734375f,
29 -1.796875f, 0.828125f, -0.32421875f, 0.1015625f, -0.015625f,
30 };
31 constexpr std::array<Common::FixedPoint<24, 8>, WindowSize> SincWindow3{
32 122.08203125f, -16.47656250f, 7.68359375f, -4.15625000f, 2.26171875f,
33 -1.16796875f, 0.54687500f, -0.22265625f, 0.07421875f, -0.01171875f,
34 };
35 constexpr std::array<Common::FixedPoint<24, 8>, WindowSize> SincWindow4{
36 23.73437500f, -9.62109375f, 5.07812500f, -2.78125000f, 1.46875000f,
37 -0.71484375f, 0.30859375f, -0.10546875f, 0.02734375f, 0.00000000f,
38 };
39 constexpr std::array<Common::FixedPoint<24, 8>, WindowSize> SincWindow5{
40 80.62500000f, -24.67187500f, 12.44921875f, -6.80859375f, 3.66406250f,
41 -1.83984375f, 0.83203125f, -0.31640625f, 0.09375000f, -0.01171875f,
42 };
43
44 if (!state->initialized) {
45 switch (source_sample_count) {
46 case 40:
47 state->window_size = WindowSize;
48 state->ratio = 6.0f;
49 state->history.fill(0);
50 break;
51
52 case 80:
53 state->window_size = WindowSize;
54 state->ratio = 3.0f;
55 state->history.fill(0);
56 break;
57
58 case 160:
59 state->window_size = WindowSize;
60 state->ratio = 1.5f;
61 state->history.fill(0);
62 break;
63
64 default:
65 LOG_ERROR(Service_Audio, "Invalid upsampling source count {}!", source_sample_count);
66 // This continues anyway, but let's assume 160 for sanity
67 state->window_size = WindowSize;
68 state->ratio = 1.5f;
69 state->history.fill(0);
70 break;
71 }
72
73 state->history_input_index = 0;
74 state->history_output_index = 9;
75 state->history_start_index = 0;
76 state->history_end_index = UpsamplerState::HistorySize - 1;
77 state->initialized = true;
78 }
79
80 if (target_sample_count == 0) {
81 return;
82 }
83
84 u32 read_index{0};
85
86 auto increment = [&]() -> void {
87 state->history[state->history_input_index] = input[read_index++];
88 state->history_input_index =
89 static_cast<u16>((state->history_input_index + 1) % UpsamplerState::HistorySize);
90 state->history_output_index =
91 static_cast<u16>((state->history_output_index + 1) % UpsamplerState::HistorySize);
92 };
93
94 auto calculate_sample = [&state](std::span<const Common::FixedPoint<24, 8>> coeffs1,
95 std::span<const Common::FixedPoint<24, 8>> coeffs2) -> s32 {
96 auto output_index{state->history_output_index};
97 auto start_pos{output_index - state->history_start_index + 1U};
98 auto end_pos{10U};
99
100 if (start_pos < 10) {
101 end_pos = start_pos;
102 }
103
104 u64 prev_contrib{0};
105 u32 coeff_index{0};
106 for (; coeff_index < end_pos; coeff_index++, output_index--) {
107 prev_contrib += static_cast<u64>(state->history[output_index].to_raw()) *
108 coeffs1[coeff_index].to_raw();
109 }
110
111 auto end_index{state->history_end_index};
112 for (; start_pos < 9; start_pos++, coeff_index++, end_index--) {
113 prev_contrib += static_cast<u64>(state->history[end_index].to_raw()) *
114 coeffs1[coeff_index].to_raw();
115 }
116
117 output_index =
118 static_cast<u16>((state->history_output_index + 1) % UpsamplerState::HistorySize);
119 start_pos = state->history_end_index - output_index + 1U;
120 end_pos = 10U;
121
122 if (start_pos < 10) {
123 end_pos = start_pos;
124 }
125
126 u64 next_contrib{0};
127 coeff_index = 0;
128 for (; coeff_index < end_pos; coeff_index++, output_index++) {
129 next_contrib += static_cast<u64>(state->history[output_index].to_raw()) *
130 coeffs2[coeff_index].to_raw();
131 }
132
133 auto start_index{state->history_start_index};
134 for (; start_pos < 9; start_pos++, start_index++, coeff_index++) {
135 next_contrib += static_cast<u64>(state->history[start_index].to_raw()) *
136 coeffs2[coeff_index].to_raw();
137 }
138
139 return static_cast<s32>(((prev_contrib >> 15) + (next_contrib >> 15)) >> 8);
140 };
141
142 switch (state->ratio.to_int_floor()) {
143 // 40 -> 240
144 case 6:
145 for (u32 write_index = 0; write_index < target_sample_count; write_index++) {
146 switch (state->sample_index) {
147 case 0:
148 increment();
149 output[write_index] = state->history[state->history_output_index].to_int_floor();
150 break;
151
152 case 1:
153 output[write_index] = calculate_sample(SincWindow3, SincWindow4);
154 break;
155
156 case 2:
157 output[write_index] = calculate_sample(SincWindow2, SincWindow1);
158 break;
159
160 case 3:
161 output[write_index] = calculate_sample(SincWindow5, SincWindow5);
162 break;
163
164 case 4:
165 output[write_index] = calculate_sample(SincWindow1, SincWindow2);
166 break;
167
168 case 5:
169 output[write_index] = calculate_sample(SincWindow4, SincWindow3);
170 break;
171 }
172 state->sample_index = static_cast<u8>((state->sample_index + 1) % 6);
173 }
174 break;
175
176 // 80 -> 240
177 case 3:
178 for (u32 write_index = 0; write_index < target_sample_count; write_index++) {
179 switch (state->sample_index) {
180 case 0:
181 increment();
182 output[write_index] = state->history[state->history_output_index].to_int_floor();
183 break;
184
185 case 1:
186 output[write_index] = calculate_sample(SincWindow2, SincWindow1);
187 break;
188
189 case 2:
190 output[write_index] = calculate_sample(SincWindow1, SincWindow2);
191 break;
192 }
193 state->sample_index = static_cast<u8>((state->sample_index + 1) % 3);
194 }
195 break;
196
197 // 160 -> 240
198 default:
199 for (u32 write_index = 0; write_index < target_sample_count; write_index++) {
200 switch (state->sample_index) {
201 case 0:
202 increment();
203 output[write_index] = state->history[state->history_output_index].to_int_floor();
204 break;
205
206 case 1:
207 output[write_index] = calculate_sample(SincWindow1, SincWindow2);
208 break;
209
210 case 2:
211 increment();
212 output[write_index] = calculate_sample(SincWindow2, SincWindow1);
213 break;
214 }
215 state->sample_index = static_cast<u8>((state->sample_index + 1) % 3);
216 }
217
218 break;
219 }
220}
221
222auto UpsampleCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
223 std::string& string) -> void {
224 string += fmt::format("UpsampleCommand\n\tsource_sample_count {} source_sample_rate {}",
225 source_sample_count, source_sample_rate);
226 const auto upsampler{reinterpret_cast<UpsamplerInfo*>(upsampler_info)};
227 if (upsampler != nullptr) {
228 string += fmt::format("\n\tUpsampler\n\t\tenabled {} sample count {}\n\tinputs: ",
229 upsampler->enabled, upsampler->sample_count);
230 for (u32 i = 0; i < upsampler->input_count; i++) {
231 string += fmt::format("{:02X}, ", upsampler->inputs[i]);
232 }
233 }
234 string += "\n";
235}
236
237void UpsampleCommand::Process(const ADSP::CommandListProcessor& processor) {
238 const auto info{reinterpret_cast<UpsamplerInfo*>(upsampler_info)};
239 const auto input_count{std::min(info->input_count, buffer_count)};
240 const std::span<const s16> inputs_{reinterpret_cast<const s16*>(inputs), input_count};
241
242 for (u32 i = 0; i < input_count; i++) {
243 const auto channel{inputs_[i]};
244
245 if (channel >= 0 && channel < static_cast<s16>(processor.buffer_count)) {
246 auto state{&info->states[i]};
247 std::span<s32> output{
248 reinterpret_cast<s32*>(samples_buffer + info->sample_count * channel * sizeof(s32)),
249 info->sample_count};
250 auto input{processor.mix_buffers.subspan(channel * processor.sample_count,
251 processor.sample_count)};
252
253 SrcProcessFrame(output, input, info->sample_count, source_sample_count, state);
254 }
255 }
256}
257
258bool UpsampleCommand::Verify(const ADSP::CommandListProcessor& processor) {
259 return true;
260}
261
262} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/resample/upsample.h b/src/audio_core/renderer/command/resample/upsample.h
new file mode 100644
index 000000000..bfc94e8af
--- /dev/null
+++ b/src/audio_core/renderer/command/resample/upsample.h
@@ -0,0 +1,60 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <string>
7
8#include "audio_core/renderer/command/icommand.h"
9#include "common/common_types.h"
10
11namespace AudioCore::AudioRenderer {
12namespace ADSP {
13class CommandListProcessor;
14}
15
16/**
17 * AudioRenderer command for upsampling a mix buffer to 48Khz.
18 * Input must be 8Khz, 16Khz or 32Khz, and output will be 48Khz.
19 */
20struct UpsampleCommand : ICommand {
21 /**
22 * Print this command's information to a string.
23 *
24 * @param processor - The CommandListProcessor processing this command.
25 * @param string - The string to print into.
26 */
27 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
28
29 /**
30 * Process this command.
31 *
32 * @param processor - The CommandListProcessor processing this command.
33 */
34 void Process(const ADSP::CommandListProcessor& processor) override;
35
36 /**
37 * Verify this command's data is valid.
38 *
39 * @param processor - The CommandListProcessor processing this command.
40 * @return True if the command is valid, otherwise false.
41 */
42 bool Verify(const ADSP::CommandListProcessor& processor) override;
43
44 /// Pointer to the output samples buffer.
45 CpuAddr samples_buffer;
46 /// Pointer to input mix buffer indexes.
47 CpuAddr inputs;
48 /// Number of input mix buffers.
49 u32 buffer_count;
50 /// Unknown, unused.
51 u32 unk_20;
52 /// Source data sample count.
53 u32 source_sample_count;
54 /// Source data sample rate.
55 u32 source_sample_rate;
56 /// Pointer to the upsampler info for this command.
57 CpuAddr upsampler_info;
58};
59
60} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/sink/circular_buffer.cpp b/src/audio_core/renderer/command/sink/circular_buffer.cpp
new file mode 100644
index 000000000..ded5afc94
--- /dev/null
+++ b/src/audio_core/renderer/command/sink/circular_buffer.cpp
@@ -0,0 +1,48 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <vector>
5
6#include "audio_core/renderer/adsp/command_list_processor.h"
7#include "audio_core/renderer/command/sink/circular_buffer.h"
8#include "core/memory.h"
9
10namespace AudioCore::AudioRenderer {
11
12void CircularBufferSinkCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
13 std::string& string) {
14 string += fmt::format(
15 "CircularBufferSinkCommand\n\tinput_count {} ring size {:04X} ring pos {:04X}\n\tinputs: ",
16 input_count, size, pos);
17 for (u32 i = 0; i < input_count; i++) {
18 string += fmt::format("{:02X}, ", inputs[i]);
19 }
20 string += "\n";
21}
22
23void CircularBufferSinkCommand::Process(const ADSP::CommandListProcessor& processor) {
24 constexpr s32 min{std::numeric_limits<s16>::min()};
25 constexpr s32 max{std::numeric_limits<s16>::max()};
26
27 std::vector<s16> output(processor.sample_count);
28 for (u32 channel = 0; channel < input_count; channel++) {
29 auto input{processor.mix_buffers.subspan(inputs[channel] * processor.sample_count,
30 processor.sample_count)};
31 for (u32 sample_index = 0; sample_index < processor.sample_count; sample_index++) {
32 output[sample_index] = static_cast<s16>(std::clamp(input[sample_index], min, max));
33 }
34
35 processor.memory->WriteBlockUnsafe(address + pos, output.data(),
36 output.size() * sizeof(s16));
37 pos += static_cast<u32>(processor.sample_count * sizeof(s16));
38 if (pos >= size) {
39 pos = 0;
40 }
41 }
42}
43
44bool CircularBufferSinkCommand::Verify(const ADSP::CommandListProcessor& processor) {
45 return true;
46}
47
48} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/sink/circular_buffer.h b/src/audio_core/renderer/command/sink/circular_buffer.h
new file mode 100644
index 000000000..e7d5be26e
--- /dev/null
+++ b/src/audio_core/renderer/command/sink/circular_buffer.h
@@ -0,0 +1,55 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <string>
7
8#include "audio_core/renderer/command/icommand.h"
9#include "common/common_types.h"
10
11namespace AudioCore::AudioRenderer {
12namespace ADSP {
13class CommandListProcessor;
14}
15
16/**
17 * AudioRenderer command for sinking samples to a circular buffer.
18 */
19struct CircularBufferSinkCommand : ICommand {
20 /**
21 * Print this command's information to a string.
22 *
23 * @param processor - The CommandListProcessor processing this command.
24 * @param string - The string to print into.
25 */
26 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
27
28 /**
29 * Process this command.
30 *
31 * @param processor - The CommandListProcessor processing this command.
32 */
33 void Process(const ADSP::CommandListProcessor& processor) override;
34
35 /**
36 * Verify this command's data is valid.
37 *
38 * @param processor - The CommandListProcessor processing this command.
39 * @return True if the command is valid, otherwise false.
40 */
41 bool Verify(const ADSP::CommandListProcessor& processor) override;
42
43 /// Number of input mix buffers
44 u32 input_count;
45 /// Input mix buffer indexes
46 std::array<s16, MaxChannels> inputs;
47 /// Circular buffer address
48 CpuAddr address;
49 /// Circular buffer size
50 u32 size;
51 /// Current buffer offset
52 u32 pos;
53};
54
55} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/sink/device.cpp b/src/audio_core/renderer/command/sink/device.cpp
new file mode 100644
index 000000000..47e0c6722
--- /dev/null
+++ b/src/audio_core/renderer/command/sink/device.cpp
@@ -0,0 +1,55 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <algorithm>
5
6#include "audio_core/renderer/adsp/command_list_processor.h"
7#include "audio_core/renderer/command/sink/device.h"
8#include "audio_core/sink/sink.h"
9
10namespace AudioCore::AudioRenderer {
11
12void DeviceSinkCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
13 std::string& string) {
14 string += fmt::format("DeviceSinkCommand\n\t{} session {} input_count {}\n\tinputs: ",
15 std::string_view(name), session_id, input_count);
16 for (u32 i = 0; i < input_count; i++) {
17 string += fmt::format("{:02X}, ", inputs[i]);
18 }
19 string += "\n";
20}
21
22void DeviceSinkCommand::Process(const ADSP::CommandListProcessor& processor) {
23 constexpr s32 min = std::numeric_limits<s16>::min();
24 constexpr s32 max = std::numeric_limits<s16>::max();
25
26 auto stream{processor.GetOutputSinkStream()};
27 stream->SetSystemChannels(input_count);
28
29 Sink::SinkBuffer out_buffer{
30 .frames{TargetSampleCount},
31 .frames_played{0},
32 .tag{0},
33 .consumed{false},
34 };
35
36 std::vector<s16> samples(out_buffer.frames * input_count);
37
38 for (u32 channel = 0; channel < input_count; channel++) {
39 const auto offset{inputs[channel] * out_buffer.frames};
40
41 for (u32 index = 0; index < out_buffer.frames; index++) {
42 samples[index * input_count + channel] =
43 static_cast<s16>(std::clamp(sample_buffer[offset + index], min, max));
44 }
45 }
46
47 out_buffer.tag = reinterpret_cast<u64>(samples.data());
48 stream->AppendBuffer(out_buffer, samples);
49}
50
51bool DeviceSinkCommand::Verify(const ADSP::CommandListProcessor& processor) {
52 return true;
53}
54
55} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/sink/device.h b/src/audio_core/renderer/command/sink/device.h
new file mode 100644
index 000000000..1099bcf8c
--- /dev/null
+++ b/src/audio_core/renderer/command/sink/device.h
@@ -0,0 +1,57 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7#include <span>
8#include <string>
9
10#include "audio_core/renderer/command/icommand.h"
11#include "common/common_types.h"
12
13namespace AudioCore::AudioRenderer {
14namespace ADSP {
15class CommandListProcessor;
16}
17
18/**
19 * AudioRenderer command for sinking samples to an output device.
20 */
21struct DeviceSinkCommand : ICommand {
22 /**
23 * Print this command's information to a string.
24 *
25 * @param processor - The CommandListProcessor processing this command.
26 * @param string - The string to print into.
27 */
28 void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
29
30 /**
31 * Process this command.
32 *
33 * @param processor - The CommandListProcessor processing this command.
34 */
35 void Process(const ADSP::CommandListProcessor& processor) override;
36
37 /**
38 * Verify this command's data is valid.
39 *
40 * @param processor - The CommandListProcessor processing this command.
41 * @return True if the command is valid, otherwise false.
42 */
43 bool Verify(const ADSP::CommandListProcessor& processor) override;
44
45 /// Device name
46 char name[0x100];
47 /// System session id (unused)
48 s32 session_id;
49 /// Sample buffer to sink
50 std::span<s32> sample_buffer;
51 /// Number of input channels
52 u32 input_count;
53 /// Mix buffer indexes for each channel
54 std::array<s16, MaxChannels> inputs;
55};
56
57} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/effect/aux_.cpp b/src/audio_core/renderer/effect/aux_.cpp
new file mode 100644
index 000000000..51e780ef1
--- /dev/null
+++ b/src/audio_core/renderer/effect/aux_.cpp
@@ -0,0 +1,93 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/effect/aux_.h"
5
6namespace AudioCore::AudioRenderer {
7
8void AuxInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params,
9 const PoolMapper& pool_mapper) {
10 auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())};
11 auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
12
13 std::memcpy(params, in_specific, sizeof(ParameterVersion1));
14 mix_id = in_params.mix_id;
15 process_order = in_params.process_order;
16 enabled = in_params.enabled;
17 if (buffer_unmapped || in_params.is_new) {
18 const bool send_unmapped{!pool_mapper.TryAttachBuffer(
19 error_info, workbuffers[0], in_specific->send_buffer_info_address,
20 sizeof(AuxBufferInfo) + in_specific->count_max * sizeof(s32))};
21 const bool return_unmapped{!pool_mapper.TryAttachBuffer(
22 error_info, workbuffers[1], in_specific->return_buffer_info_address,
23 sizeof(AuxBufferInfo) + in_specific->count_max * sizeof(s32))};
24
25 buffer_unmapped = send_unmapped || return_unmapped;
26
27 if (!buffer_unmapped) {
28 auto send{workbuffers[0].GetReference(false)};
29 send_buffer_info = send + sizeof(AuxInfoDsp);
30 send_buffer = send + sizeof(AuxBufferInfo);
31
32 auto ret{workbuffers[1].GetReference(false)};
33 return_buffer_info = ret + sizeof(AuxInfoDsp);
34 return_buffer = ret + sizeof(AuxBufferInfo);
35 }
36 } else {
37 error_info.error_code = ResultSuccess;
38 error_info.address = CpuAddr(0);
39 }
40}
41
42void AuxInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params,
43 const PoolMapper& pool_mapper) {
44 auto in_specific{reinterpret_cast<const ParameterVersion2*>(in_params.specific.data())};
45 auto params{reinterpret_cast<ParameterVersion2*>(parameter.data())};
46
47 std::memcpy(params, in_specific, sizeof(ParameterVersion2));
48 mix_id = in_params.mix_id;
49 process_order = in_params.process_order;
50 enabled = in_params.enabled;
51
52 if (buffer_unmapped || in_params.is_new) {
53 const bool send_unmapped{!pool_mapper.TryAttachBuffer(
54 error_info, workbuffers[0], params->send_buffer_info_address,
55 sizeof(AuxBufferInfo) + params->count_max * sizeof(s32))};
56 const bool return_unmapped{!pool_mapper.TryAttachBuffer(
57 error_info, workbuffers[1], params->return_buffer_info_address,
58 sizeof(AuxBufferInfo) + params->count_max * sizeof(s32))};
59
60 buffer_unmapped = send_unmapped || return_unmapped;
61
62 if (!buffer_unmapped) {
63 auto send{workbuffers[0].GetReference(false)};
64 send_buffer_info = send + sizeof(AuxInfoDsp);
65 send_buffer = send + sizeof(AuxBufferInfo);
66
67 auto ret{workbuffers[1].GetReference(false)};
68 return_buffer_info = ret + sizeof(AuxInfoDsp);
69 return_buffer = ret + sizeof(AuxBufferInfo);
70 }
71 } else {
72 error_info.error_code = ResultSuccess;
73 error_info.address = CpuAddr(0);
74 }
75}
76
77void AuxInfo::UpdateForCommandGeneration() {
78 if (enabled) {
79 usage_state = UsageState::Enabled;
80 } else {
81 usage_state = UsageState::Disabled;
82 }
83}
84
85void AuxInfo::InitializeResultState(EffectResultState& result_state) {}
86
87void AuxInfo::UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) {}
88
89CpuAddr AuxInfo::GetWorkbuffer(s32 index) {
90 return workbuffers[index].GetReference(true);
91}
92
93} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/effect/aux_.h b/src/audio_core/renderer/effect/aux_.h
new file mode 100644
index 000000000..4d3d9e3d9
--- /dev/null
+++ b/src/audio_core/renderer/effect/aux_.h
@@ -0,0 +1,123 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7
8#include "audio_core/common/common.h"
9#include "audio_core/renderer/effect/effect_info_base.h"
10#include "common/common_types.h"
11
12namespace AudioCore::AudioRenderer {
13/**
14 * Auxiliary Buffer used for Aux commands.
15 * Send and return buffers are available (names from the game's perspective).
16 * Send is read by the host, containing a buffer of samples to be used for whatever purpose.
17 * Return is written by the host, writing a mix buffer back to the game.
18 * This allows the game to use pre-processed samples skipping the other render processing,
19 * and to examine or modify what the audio renderer has generated.
20 */
21class AuxInfo : public EffectInfoBase {
22public:
23 struct ParameterVersion1 {
24 /* 0x00 */ std::array<s8, MaxMixBuffers> inputs;
25 /* 0x18 */ std::array<s8, MaxMixBuffers> outputs;
26 /* 0x30 */ u32 mix_buffer_count;
27 /* 0x34 */ u32 sample_rate;
28 /* 0x38 */ u32 count_max;
29 /* 0x3C */ u32 mix_buffer_count_max;
30 /* 0x40 */ CpuAddr send_buffer_info_address;
31 /* 0x48 */ CpuAddr send_buffer_address;
32 /* 0x50 */ CpuAddr return_buffer_info_address;
33 /* 0x58 */ CpuAddr return_buffer_address;
34 /* 0x60 */ u32 mix_buffer_sample_size;
35 /* 0x64 */ u32 sample_count;
36 /* 0x68 */ u32 mix_buffer_sample_count;
37 };
38 static_assert(sizeof(ParameterVersion1) <= sizeof(EffectInfoBase::InParameterVersion1),
39 "AuxInfo::ParameterVersion1 has the wrong size!");
40
41 struct ParameterVersion2 {
42 /* 0x00 */ std::array<s8, MaxMixBuffers> inputs;
43 /* 0x18 */ std::array<s8, MaxMixBuffers> outputs;
44 /* 0x30 */ u32 mix_buffer_count;
45 /* 0x34 */ u32 sample_rate;
46 /* 0x38 */ u32 count_max;
47 /* 0x3C */ u32 mix_buffer_count_max;
48 /* 0x40 */ CpuAddr send_buffer_info_address;
49 /* 0x48 */ CpuAddr send_buffer_address;
50 /* 0x50 */ CpuAddr return_buffer_info_address;
51 /* 0x58 */ CpuAddr return_buffer_address;
52 /* 0x60 */ u32 mix_buffer_sample_size;
53 /* 0x64 */ u32 sample_count;
54 /* 0x68 */ u32 mix_buffer_sample_count;
55 };
56 static_assert(sizeof(ParameterVersion2) <= sizeof(EffectInfoBase::InParameterVersion2),
57 "AuxInfo::ParameterVersion2 has the wrong size!");
58
59 struct AuxInfoDsp {
60 /* 0x00 */ u32 read_offset;
61 /* 0x04 */ u32 write_offset;
62 /* 0x08 */ u32 lost_sample_count;
63 /* 0x0C */ u32 total_sample_count;
64 /* 0x10 */ char unk10[0x30];
65 };
66 static_assert(sizeof(AuxInfoDsp) == 0x40, "AuxInfo::AuxInfoDsp has the wrong size!");
67
68 struct AuxBufferInfo {
69 /* 0x00 */ AuxInfoDsp cpu_info;
70 /* 0x40 */ AuxInfoDsp dsp_info;
71 };
72 static_assert(sizeof(AuxBufferInfo) == 0x80, "AuxInfo::AuxBufferInfo has the wrong size!");
73
74 /**
75 * Update the info with new parameters, version 1.
76 *
77 * @param error_info - Used to write call result code.
78 * @param in_params - New parameters to update the info with.
79 * @param pool_mapper - Pool for mapping buffers.
80 */
81 void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params,
82 const PoolMapper& pool_mapper) override;
83
84 /**
85 * Update the info with new parameters, version 2.
86 *
87 * @param error_info - Used to write call result code.
88 * @param in_params - New parameters to update the info with.
89 * @param pool_mapper - Pool for mapping buffers.
90 */
91 void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params,
92 const PoolMapper& pool_mapper) override;
93
94 /**
95 * Update the info after command generation. Usually only changes its state.
96 */
97 void UpdateForCommandGeneration() override;
98
99 /**
100 * Initialize a new result state. Version 2 only, unused.
101 *
102 * @param result_state - Result state to initialize.
103 */
104 void InitializeResultState(EffectResultState& result_state) override;
105
106 /**
107 * Update the host-side state with the ADSP-side state. Version 2 only, unused.
108 *
109 * @param cpu_state - Host-side result state to update.
110 * @param dsp_state - AudioRenderer-side result state to update from.
111 */
112 void UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) override;
113
114 /**
115 * Get a workbuffer assigned to this effect with the given index.
116 *
117 * @param index - Workbuffer index.
118 * @return Address of the buffer.
119 */
120 CpuAddr GetWorkbuffer(s32 index) override;
121};
122
123} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/effect/biquad_filter.cpp b/src/audio_core/renderer/effect/biquad_filter.cpp
new file mode 100644
index 000000000..a1efb3231
--- /dev/null
+++ b/src/audio_core/renderer/effect/biquad_filter.cpp
@@ -0,0 +1,52 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/effect/biquad_filter.h"
5
6namespace AudioCore::AudioRenderer {
7
8void BiquadFilterInfo::Update(BehaviorInfo::ErrorInfo& error_info,
9 const InParameterVersion1& in_params, const PoolMapper& pool_mapper) {
10 auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())};
11 auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
12
13 std::memcpy(params, in_specific, sizeof(ParameterVersion1));
14 mix_id = in_params.mix_id;
15 process_order = in_params.process_order;
16 enabled = in_params.enabled;
17
18 error_info.error_code = ResultSuccess;
19 error_info.address = CpuAddr(0);
20}
21
22void BiquadFilterInfo::Update(BehaviorInfo::ErrorInfo& error_info,
23 const InParameterVersion2& in_params, const PoolMapper& pool_mapper) {
24 auto in_specific{reinterpret_cast<const ParameterVersion2*>(in_params.specific.data())};
25 auto params{reinterpret_cast<ParameterVersion2*>(parameter.data())};
26
27 std::memcpy(params, in_specific, sizeof(ParameterVersion2));
28 mix_id = in_params.mix_id;
29 process_order = in_params.process_order;
30 enabled = in_params.enabled;
31
32 error_info.error_code = ResultSuccess;
33 error_info.address = CpuAddr(0);
34}
35
36void BiquadFilterInfo::UpdateForCommandGeneration() {
37 if (enabled) {
38 usage_state = UsageState::Enabled;
39 } else {
40 usage_state = UsageState::Disabled;
41 }
42
43 auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
44 params->state = ParameterState::Updated;
45}
46
47void BiquadFilterInfo::InitializeResultState(EffectResultState& result_state) {}
48
49void BiquadFilterInfo::UpdateResultState(EffectResultState& cpu_state,
50 EffectResultState& dsp_state) {}
51
52} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/effect/biquad_filter.h b/src/audio_core/renderer/effect/biquad_filter.h
new file mode 100644
index 000000000..f53fd5bab
--- /dev/null
+++ b/src/audio_core/renderer/effect/biquad_filter.h
@@ -0,0 +1,79 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7
8#include "audio_core/common/common.h"
9#include "audio_core/renderer/effect/effect_info_base.h"
10#include "common/common_types.h"
11
12namespace AudioCore::AudioRenderer {
13
14class BiquadFilterInfo : public EffectInfoBase {
15public:
16 struct ParameterVersion1 {
17 /* 0x00 */ std::array<s8, MaxChannels> inputs;
18 /* 0x06 */ std::array<s8, MaxChannels> outputs;
19 /* 0x0C */ std::array<s16, 3> b;
20 /* 0x12 */ std::array<s16, 2> a;
21 /* 0x16 */ s8 channel_count;
22 /* 0x17 */ ParameterState state;
23 };
24 static_assert(sizeof(ParameterVersion1) <= sizeof(EffectInfoBase::InParameterVersion1),
25 "BiquadFilterInfo::ParameterVersion1 has the wrong size!");
26
27 struct ParameterVersion2 {
28 /* 0x00 */ std::array<s8, MaxChannels> inputs;
29 /* 0x06 */ std::array<s8, MaxChannels> outputs;
30 /* 0x0C */ std::array<s16, 3> b;
31 /* 0x12 */ std::array<s16, 2> a;
32 /* 0x16 */ s8 channel_count;
33 /* 0x17 */ ParameterState state;
34 };
35 static_assert(sizeof(ParameterVersion2) <= sizeof(EffectInfoBase::InParameterVersion2),
36 "BiquadFilterInfo::ParameterVersion2 has the wrong size!");
37
38 /**
39 * Update the info with new parameters, version 1.
40 *
41 * @param error_info - Used to write call result code.
42 * @param in_params - New parameters to update the info with.
43 * @param pool_mapper - Pool for mapping buffers.
44 */
45 void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params,
46 const PoolMapper& pool_mapper) override;
47
48 /**
49 * Update the info with new parameters, version 2.
50 *
51 * @param error_info - Used to write call result code.
52 * @param in_params - New parameters to update the info with.
53 * @param pool_mapper - Pool for mapping buffers.
54 */
55 void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params,
56 const PoolMapper& pool_mapper) override;
57
58 /**
59 * Update the info after command generation. Usually only changes its state.
60 */
61 void UpdateForCommandGeneration() override;
62
63 /**
64 * Initialize a new result state. Version 2 only, unused.
65 *
66 * @param result_state - Result state to initialize.
67 */
68 void InitializeResultState(EffectResultState& result_state) override;
69
70 /**
71 * Update the host-side state with the ADSP-side state. Version 2 only, unused.
72 *
73 * @param cpu_state - Host-side result state to update.
74 * @param dsp_state - AudioRenderer-side result state to update from.
75 */
76 void UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) override;
77};
78
79} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/effect/buffer_mixer.cpp b/src/audio_core/renderer/effect/buffer_mixer.cpp
new file mode 100644
index 000000000..9c8877f01
--- /dev/null
+++ b/src/audio_core/renderer/effect/buffer_mixer.cpp
@@ -0,0 +1,49 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/effect/buffer_mixer.h"
5
6namespace AudioCore::AudioRenderer {
7
8void BufferMixerInfo::Update(BehaviorInfo::ErrorInfo& error_info,
9 const InParameterVersion1& in_params, const PoolMapper& pool_mapper) {
10 auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())};
11 auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
12
13 std::memcpy(params, in_specific, sizeof(ParameterVersion1));
14 mix_id = in_params.mix_id;
15 process_order = in_params.process_order;
16 enabled = in_params.enabled;
17
18 error_info.error_code = ResultSuccess;
19 error_info.address = CpuAddr(0);
20}
21
22void BufferMixerInfo::Update(BehaviorInfo::ErrorInfo& error_info,
23 const InParameterVersion2& in_params, const PoolMapper& pool_mapper) {
24 auto in_specific{reinterpret_cast<const ParameterVersion2*>(in_params.specific.data())};
25 auto params{reinterpret_cast<ParameterVersion2*>(parameter.data())};
26
27 std::memcpy(params, in_specific, sizeof(ParameterVersion2));
28 mix_id = in_params.mix_id;
29 process_order = in_params.process_order;
30 enabled = in_params.enabled;
31
32 error_info.error_code = ResultSuccess;
33 error_info.address = CpuAddr(0);
34}
35
36void BufferMixerInfo::UpdateForCommandGeneration() {
37 if (enabled) {
38 usage_state = UsageState::Enabled;
39 } else {
40 usage_state = UsageState::Disabled;
41 }
42}
43
44void BufferMixerInfo::InitializeResultState(EffectResultState& result_state) {}
45
46void BufferMixerInfo::UpdateResultState(EffectResultState& cpu_state,
47 EffectResultState& dsp_state) {}
48
49} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/effect/buffer_mixer.h b/src/audio_core/renderer/effect/buffer_mixer.h
new file mode 100644
index 000000000..23eed4a8b
--- /dev/null
+++ b/src/audio_core/renderer/effect/buffer_mixer.h
@@ -0,0 +1,75 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7
8#include "audio_core/common/common.h"
9#include "audio_core/renderer/effect/effect_info_base.h"
10#include "common/common_types.h"
11
12namespace AudioCore::AudioRenderer {
13
14class BufferMixerInfo : public EffectInfoBase {
15public:
16 struct ParameterVersion1 {
17 /* 0x00 */ std::array<s8, MaxMixBuffers> inputs;
18 /* 0x18 */ std::array<s8, MaxMixBuffers> outputs;
19 /* 0x30 */ std::array<f32, MaxMixBuffers> volumes;
20 /* 0x90 */ u32 mix_count;
21 };
22 static_assert(sizeof(ParameterVersion1) <= sizeof(EffectInfoBase::InParameterVersion1),
23 "BufferMixerInfo::ParameterVersion1 has the wrong size!");
24
25 struct ParameterVersion2 {
26 /* 0x00 */ std::array<s8, MaxMixBuffers> inputs;
27 /* 0x18 */ std::array<s8, MaxMixBuffers> outputs;
28 /* 0x30 */ std::array<f32, MaxMixBuffers> volumes;
29 /* 0x90 */ u32 mix_count;
30 };
31 static_assert(sizeof(ParameterVersion2) <= sizeof(EffectInfoBase::InParameterVersion2),
32 "BufferMixerInfo::ParameterVersion2 has the wrong size!");
33
34 /**
35 * Update the info with new parameters, version 1.
36 *
37 * @param error_info - Used to write call result code.
38 * @param in_params - New parameters to update the info with.
39 * @param pool_mapper - Pool for mapping buffers.
40 */
41 void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params,
42 const PoolMapper& pool_mapper) override;
43
44 /**
45 * Update the info with new parameters, version 2.
46 *
47 * @param error_info - Used to write call result code.
48 * @param in_params - New parameters to update the info with.
49 * @param pool_mapper - Pool for mapping buffers.
50 */
51 void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params,
52 const PoolMapper& pool_mapper) override;
53
54 /**
55 * Update the info after command generation. Usually only changes its state.
56 */
57 void UpdateForCommandGeneration() override;
58
59 /**
60 * Initialize a new result state. Version 2 only, unused.
61 *
62 * @param result_state - Result state to initialize.
63 */
64 void InitializeResultState(EffectResultState& result_state) override;
65
66 /**
67 * Update the host-side state with the ADSP-side state. Version 2 only, unused.
68 *
69 * @param cpu_state - Host-side result state to update.
70 * @param dsp_state - AudioRenderer-side result state to update from.
71 */
72 void UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) override;
73};
74
75} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/effect/capture.cpp b/src/audio_core/renderer/effect/capture.cpp
new file mode 100644
index 000000000..3f038efdb
--- /dev/null
+++ b/src/audio_core/renderer/effect/capture.cpp
@@ -0,0 +1,82 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/effect/aux_.h"
5#include "audio_core/renderer/effect/capture.h"
6
7namespace AudioCore::AudioRenderer {
8
9void CaptureInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params,
10 const PoolMapper& pool_mapper) {
11 auto in_specific{
12 reinterpret_cast<const AuxInfo::ParameterVersion1*>(in_params.specific.data())};
13 auto params{reinterpret_cast<AuxInfo::ParameterVersion1*>(parameter.data())};
14
15 std::memcpy(params, in_specific, sizeof(AuxInfo::ParameterVersion1));
16 mix_id = in_params.mix_id;
17 process_order = in_params.process_order;
18 enabled = in_params.enabled;
19 if (buffer_unmapped || in_params.is_new) {
20 buffer_unmapped = !pool_mapper.TryAttachBuffer(
21 error_info, workbuffers[0], in_specific->send_buffer_info_address,
22 in_specific->count_max * sizeof(s32) + sizeof(AuxInfo::AuxBufferInfo));
23
24 if (!buffer_unmapped) {
25 const auto send_address{workbuffers[0].GetReference(false)};
26 send_buffer_info = send_address + sizeof(AuxInfo::AuxInfoDsp);
27 send_buffer = send_address + sizeof(AuxInfo::AuxBufferInfo);
28 return_buffer_info = 0;
29 return_buffer = 0;
30 }
31 } else {
32 error_info.error_code = ResultSuccess;
33 error_info.address = CpuAddr(0);
34 }
35}
36
37void CaptureInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params,
38 const PoolMapper& pool_mapper) {
39 auto in_specific{
40 reinterpret_cast<const AuxInfo::ParameterVersion2*>(in_params.specific.data())};
41 auto params{reinterpret_cast<AuxInfo::ParameterVersion2*>(parameter.data())};
42
43 std::memcpy(params, in_specific, sizeof(AuxInfo::ParameterVersion2));
44 mix_id = in_params.mix_id;
45 process_order = in_params.process_order;
46 enabled = in_params.enabled;
47
48 if (buffer_unmapped || in_params.is_new) {
49 buffer_unmapped = !pool_mapper.TryAttachBuffer(
50 error_info, workbuffers[0], params->send_buffer_info_address,
51 params->count_max * sizeof(s32) + sizeof(AuxInfo::AuxBufferInfo));
52
53 if (!buffer_unmapped) {
54 const auto send_address{workbuffers[0].GetReference(false)};
55 send_buffer_info = send_address + sizeof(AuxInfo::AuxInfoDsp);
56 send_buffer = send_address + sizeof(AuxInfo::AuxBufferInfo);
57 return_buffer_info = 0;
58 return_buffer = 0;
59 }
60 } else {
61 error_info.error_code = ResultSuccess;
62 error_info.address = CpuAddr(0);
63 }
64}
65
66void CaptureInfo::UpdateForCommandGeneration() {
67 if (enabled) {
68 usage_state = UsageState::Enabled;
69 } else {
70 usage_state = UsageState::Disabled;
71 }
72}
73
74void CaptureInfo::InitializeResultState(EffectResultState& result_state) {}
75
76void CaptureInfo::UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) {}
77
78CpuAddr CaptureInfo::GetWorkbuffer(s32 index) {
79 return workbuffers[index].GetReference(true);
80}
81
82} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/effect/capture.h b/src/audio_core/renderer/effect/capture.h
new file mode 100644
index 000000000..6fbed8e6b
--- /dev/null
+++ b/src/audio_core/renderer/effect/capture.h
@@ -0,0 +1,65 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7
8#include "audio_core/common/common.h"
9#include "audio_core/renderer/effect/effect_info_base.h"
10#include "common/common_types.h"
11
12namespace AudioCore::AudioRenderer {
13
14class CaptureInfo : public EffectInfoBase {
15public:
16 /**
17 * Update the info with new parameters, version 1.
18 *
19 * @param error_info - Used to write call result code.
20 * @param in_params - New parameters to update the info with.
21 * @param pool_mapper - Pool for mapping buffers.
22 */
23 void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params,
24 const PoolMapper& pool_mapper) override;
25
26 /**
27 * Update the info with new parameters, version 2.
28 *
29 * @param error_info - Used to write call result code.
30 * @param in_params - New parameters to update the info with.
31 * @param pool_mapper - Pool for mapping buffers.
32 */
33 void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params,
34 const PoolMapper& pool_mapper) override;
35
36 /**
37 * Update the info after command generation. Usually only changes its state.
38 */
39 void UpdateForCommandGeneration() override;
40
41 /**
42 * Initialize a new result state. Version 2 only, unused.
43 *
44 * @param result_state - Result state to initialize.
45 */
46 void InitializeResultState(EffectResultState& result_state) override;
47
48 /**
49 * Update the host-side state with the ADSP-side state. Version 2 only, unused.
50 *
51 * @param cpu_state - Host-side result state to update.
52 * @param dsp_state - AudioRenderer-side result state to update from.
53 */
54 void UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) override;
55
56 /**
57 * Get a workbuffer assigned to this effect with the given index.
58 *
59 * @param index - Workbuffer index.
60 * @return Address of the buffer.
61 */
62 CpuAddr GetWorkbuffer(s32 index) override;
63};
64
65} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/effect/compressor.cpp b/src/audio_core/renderer/effect/compressor.cpp
new file mode 100644
index 000000000..220ae02f9
--- /dev/null
+++ b/src/audio_core/renderer/effect/compressor.cpp
@@ -0,0 +1,40 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/effect/compressor.h"
5
6namespace AudioCore::AudioRenderer {
7
8void CompressorInfo::Update(BehaviorInfo::ErrorInfo& error_info,
9 const InParameterVersion1& in_params, const PoolMapper& pool_mapper) {}
10
11void CompressorInfo::Update(BehaviorInfo::ErrorInfo& error_info,
12 const InParameterVersion2& in_params, const PoolMapper& pool_mapper) {
13 auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())};
14 auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
15
16 std::memcpy(params, in_specific, sizeof(ParameterVersion1));
17 mix_id = in_params.mix_id;
18 process_order = in_params.process_order;
19 enabled = in_params.enabled;
20
21 error_info.error_code = ResultSuccess;
22 error_info.address = CpuAddr(0);
23}
24
25void CompressorInfo::UpdateForCommandGeneration() {
26 if (enabled) {
27 usage_state = UsageState::Enabled;
28 } else {
29 usage_state = UsageState::Disabled;
30 }
31
32 auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
33 params->state = ParameterState::Updated;
34}
35
36CpuAddr CompressorInfo::GetWorkbuffer(s32 index) {
37 return GetSingleBuffer(index);
38}
39
40} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/effect/compressor.h b/src/audio_core/renderer/effect/compressor.h
new file mode 100644
index 000000000..019a5ae58
--- /dev/null
+++ b/src/audio_core/renderer/effect/compressor.h
@@ -0,0 +1,106 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7
8#include "audio_core/common/common.h"
9#include "audio_core/renderer/effect/effect_info_base.h"
10#include "common/common_types.h"
11#include "common/fixed_point.h"
12
13namespace AudioCore::AudioRenderer {
14
15class CompressorInfo : public EffectInfoBase {
16public:
17 struct ParameterVersion1 {
18 /* 0x00 */ std::array<s8, MaxChannels> inputs;
19 /* 0x06 */ std::array<s8, MaxChannels> outputs;
20 /* 0x0C */ s16 channel_count_max;
21 /* 0x0E */ s16 channel_count;
22 /* 0x10 */ s32 sample_rate;
23 /* 0x14 */ f32 threshold;
24 /* 0x18 */ f32 compressor_ratio;
25 /* 0x1C */ s32 attack_time;
26 /* 0x20 */ s32 release_time;
27 /* 0x24 */ f32 unk_24;
28 /* 0x28 */ f32 unk_28;
29 /* 0x2C */ f32 unk_2C;
30 /* 0x30 */ f32 out_gain;
31 /* 0x34 */ ParameterState state;
32 /* 0x35 */ bool makeup_gain_enabled;
33 };
34 static_assert(sizeof(ParameterVersion1) <= sizeof(EffectInfoBase::InParameterVersion1),
35 "CompressorInfo::ParameterVersion1 has the wrong size!");
36
37 struct ParameterVersion2 {
38 /* 0x00 */ std::array<s8, MaxChannels> inputs;
39 /* 0x06 */ std::array<s8, MaxChannels> outputs;
40 /* 0x0C */ s16 channel_count_max;
41 /* 0x0E */ s16 channel_count;
42 /* 0x10 */ s32 sample_rate;
43 /* 0x14 */ f32 threshold;
44 /* 0x18 */ f32 compressor_ratio;
45 /* 0x1C */ s32 attack_time;
46 /* 0x20 */ s32 release_time;
47 /* 0x24 */ f32 unk_24;
48 /* 0x28 */ f32 unk_28;
49 /* 0x2C */ f32 unk_2C;
50 /* 0x30 */ f32 out_gain;
51 /* 0x34 */ ParameterState state;
52 /* 0x35 */ bool makeup_gain_enabled;
53 };
54 static_assert(sizeof(ParameterVersion2) <= sizeof(EffectInfoBase::InParameterVersion2),
55 "CompressorInfo::ParameterVersion2 has the wrong size!");
56
57 struct State {
58 f32 unk_00;
59 f32 unk_04;
60 f32 unk_08;
61 f32 unk_0C;
62 f32 unk_10;
63 f32 unk_14;
64 f32 unk_18;
65 f32 makeup_gain;
66 f32 unk_20;
67 char unk_24[0x1C];
68 };
69 static_assert(sizeof(State) <= sizeof(EffectInfoBase::State),
70 "CompressorInfo::State has the wrong size!");
71
72 /**
73 * Update the info with new parameters, version 1.
74 *
75 * @param error_info - Used to write call result code.
76 * @param in_params - New parameters to update the info with.
77 * @param pool_mapper - Pool for mapping buffers.
78 */
79 void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params,
80 const PoolMapper& pool_mapper) override;
81
82 /**
83 * Update the info with new parameters, version 2.
84 *
85 * @param error_info - Used to write call result code.
86 * @param in_params - New parameters to update the info with.
87 * @param pool_mapper - Pool for mapping buffers.
88 */
89 void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params,
90 const PoolMapper& pool_mapper) override;
91
92 /**
93 * Update the info after command generation. Usually only changes its state.
94 */
95 void UpdateForCommandGeneration() override;
96
97 /**
98 * Get a workbuffer assigned to this effect with the given index.
99 *
100 * @param index - Workbuffer index.
101 * @return Address of the buffer.
102 */
103 CpuAddr GetWorkbuffer(s32 index) override;
104};
105
106} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/effect/delay.cpp b/src/audio_core/renderer/effect/delay.cpp
new file mode 100644
index 000000000..d9853efd9
--- /dev/null
+++ b/src/audio_core/renderer/effect/delay.cpp
@@ -0,0 +1,93 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/effect/delay.h"
5
6namespace AudioCore::AudioRenderer {
7
8void DelayInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params,
9 const PoolMapper& pool_mapper) {
10 auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())};
11 auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
12
13 if (IsChannelCountValid(in_specific->channel_count_max)) {
14 const auto old_state{params->state};
15 std::memcpy(params, in_specific, sizeof(ParameterVersion1));
16 mix_id = in_params.mix_id;
17 process_order = in_params.process_order;
18 enabled = in_params.enabled;
19
20 if (!IsChannelCountValid(in_specific->channel_count)) {
21 params->channel_count = params->channel_count_max;
22 }
23
24 if (!IsChannelCountValid(in_specific->channel_count) ||
25 old_state != ParameterState::Updated) {
26 params->state = old_state;
27 }
28
29 if (buffer_unmapped || in_params.is_new) {
30 usage_state = UsageState::New;
31 params->state = ParameterState::Initialized;
32 buffer_unmapped = !pool_mapper.TryAttachBuffer(
33 error_info, workbuffers[0], in_params.workbuffer, in_params.workbuffer_size);
34 return;
35 }
36 }
37 error_info.error_code = ResultSuccess;
38 error_info.address = CpuAddr(0);
39}
40
41void DelayInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params,
42 const PoolMapper& pool_mapper) {
43 auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())};
44 auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
45
46 if (IsChannelCountValid(in_specific->channel_count_max)) {
47 const auto old_state{params->state};
48 std::memcpy(params, in_specific, sizeof(ParameterVersion1));
49 mix_id = in_params.mix_id;
50 process_order = in_params.process_order;
51 enabled = in_params.enabled;
52
53 if (!IsChannelCountValid(in_specific->channel_count)) {
54 params->channel_count = params->channel_count_max;
55 }
56
57 if (!IsChannelCountValid(in_specific->channel_count) ||
58 old_state != ParameterState::Updated) {
59 params->state = old_state;
60 }
61
62 if (buffer_unmapped || in_params.is_new) {
63 usage_state = UsageState::New;
64 params->state = ParameterState::Initialized;
65 buffer_unmapped = !pool_mapper.TryAttachBuffer(
66 error_info, workbuffers[0], in_params.workbuffer, in_params.workbuffer_size);
67 return;
68 }
69 }
70 error_info.error_code = ResultSuccess;
71 error_info.address = CpuAddr(0);
72}
73
74void DelayInfo::UpdateForCommandGeneration() {
75 if (enabled) {
76 usage_state = UsageState::Enabled;
77 } else {
78 usage_state = UsageState::Disabled;
79 }
80
81 auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
82 params->state = ParameterState::Updated;
83}
84
85void DelayInfo::InitializeResultState(EffectResultState& result_state) {}
86
87void DelayInfo::UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) {}
88
89CpuAddr DelayInfo::GetWorkbuffer(s32 index) {
90 return GetSingleBuffer(index);
91}
92
93} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/effect/delay.h b/src/audio_core/renderer/effect/delay.h
new file mode 100644
index 000000000..accc42a06
--- /dev/null
+++ b/src/audio_core/renderer/effect/delay.h
@@ -0,0 +1,135 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7#include <vector>
8
9#include "audio_core/common/common.h"
10#include "audio_core/renderer/effect/effect_info_base.h"
11#include "common/common_types.h"
12#include "common/fixed_point.h"
13
14namespace AudioCore::AudioRenderer {
15
16class DelayInfo : public EffectInfoBase {
17public:
18 struct ParameterVersion1 {
19 /* 0x00 */ std::array<s8, MaxChannels> inputs;
20 /* 0x06 */ std::array<s8, MaxChannels> outputs;
21 /* 0x0C */ u16 channel_count_max;
22 /* 0x0E */ u16 channel_count;
23 /* 0x10 */ u32 delay_time_max;
24 /* 0x14 */ u32 delay_time;
25 /* 0x18 */ Common::FixedPoint<18, 14> sample_rate;
26 /* 0x1C */ Common::FixedPoint<18, 14> in_gain;
27 /* 0x20 */ Common::FixedPoint<18, 14> feedback_gain;
28 /* 0x24 */ Common::FixedPoint<18, 14> wet_gain;
29 /* 0x28 */ Common::FixedPoint<18, 14> dry_gain;
30 /* 0x2C */ Common::FixedPoint<18, 14> channel_spread;
31 /* 0x30 */ Common::FixedPoint<18, 14> lowpass_amount;
32 /* 0x34 */ ParameterState state;
33 };
34 static_assert(sizeof(ParameterVersion1) <= sizeof(EffectInfoBase::InParameterVersion1),
35 "DelayInfo::ParameterVersion1 has the wrong size!");
36
37 struct ParameterVersion2 {
38 /* 0x00 */ std::array<s8, MaxChannels> inputs;
39 /* 0x06 */ std::array<s8, MaxChannels> outputs;
40 /* 0x0C */ s16 channel_count_max;
41 /* 0x0E */ s16 channel_count;
42 /* 0x10 */ s32 delay_time_max;
43 /* 0x14 */ s32 delay_time;
44 /* 0x18 */ s32 sample_rate;
45 /* 0x1C */ s32 in_gain;
46 /* 0x20 */ s32 feedback_gain;
47 /* 0x24 */ s32 wet_gain;
48 /* 0x28 */ s32 dry_gain;
49 /* 0x2C */ s32 channel_spread;
50 /* 0x30 */ s32 lowpass_amount;
51 /* 0x34 */ ParameterState state;
52 };
53 static_assert(sizeof(ParameterVersion2) <= sizeof(EffectInfoBase::InParameterVersion2),
54 "DelayInfo::ParameterVersion2 has the wrong size!");
55
56 struct DelayLine {
57 Common::FixedPoint<50, 14> Read() const {
58 return buffer[buffer_pos];
59 }
60
61 void Write(const Common::FixedPoint<50, 14> value) {
62 buffer[buffer_pos] = value;
63 buffer_pos = static_cast<u32>((buffer_pos + 1) % buffer.size());
64 }
65
66 s32 sample_count_max{};
67 s32 sample_count{};
68 std::vector<Common::FixedPoint<50, 14>> buffer{};
69 u32 buffer_pos{};
70 Common::FixedPoint<18, 14> decay_rate{};
71 };
72
73 struct State {
74 /* 0x000 */ std::array<s32, 8> unk_000;
75 /* 0x020 */ std::array<DelayLine, MaxChannels> delay_lines;
76 /* 0x0B0 */ Common::FixedPoint<18, 14> feedback_gain;
77 /* 0x0B4 */ Common::FixedPoint<18, 14> delay_feedback_gain;
78 /* 0x0B8 */ Common::FixedPoint<18, 14> delay_feedback_cross_gain;
79 /* 0x0BC */ Common::FixedPoint<18, 14> lowpass_gain;
80 /* 0x0C0 */ Common::FixedPoint<18, 14> lowpass_feedback_gain;
81 /* 0x0C4 */ std::array<Common::FixedPoint<50, 14>, MaxChannels> lowpass_z;
82 };
83 static_assert(sizeof(State) <= sizeof(EffectInfoBase::State),
84 "DelayInfo::State has the wrong size!");
85
86 /**
87 * Update the info with new parameters, version 1.
88 *
89 * @param error_info - Used to write call result code.
90 * @param in_params - New parameters to update the info with.
91 * @param pool_mapper - Pool for mapping buffers.
92 */
93 void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params,
94 const PoolMapper& pool_mapper) override;
95
96 /**
97 * Update the info with new parameters, version 2.
98 *
99 * @param error_info - Used to write call result code.
100 * @param in_params - New parameters to update the info with.
101 * @param pool_mapper - Pool for mapping buffers.
102 */
103 void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params,
104 const PoolMapper& pool_mapper) override;
105
106 /**
107 * Update the info after command generation. Usually only changes its state.
108 */
109 void UpdateForCommandGeneration() override;
110
111 /**
112 * Initialize a new result state. Version 2 only, unused.
113 *
114 * @param result_state - Result state to initialize.
115 */
116 void InitializeResultState(EffectResultState& result_state) override;
117
118 /**
119 * Update the host-side state with the ADSP-side state. Version 2 only, unused.
120 *
121 * @param cpu_state - Host-side result state to update.
122 * @param dsp_state - AudioRenderer-side result state to update from.
123 */
124 void UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) override;
125
126 /**
127 * Get a workbuffer assigned to this effect with the given index.
128 *
129 * @param index - Workbuffer index.
130 * @return Address of the buffer.
131 */
132 CpuAddr GetWorkbuffer(s32 index) override;
133};
134
135} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/effect/effect_context.cpp b/src/audio_core/renderer/effect/effect_context.cpp
new file mode 100644
index 000000000..74c7801c9
--- /dev/null
+++ b/src/audio_core/renderer/effect/effect_context.cpp
@@ -0,0 +1,41 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/effect/effect_context.h"
5
6namespace AudioCore::AudioRenderer {
7
8void EffectContext::Initialize(std::span<EffectInfoBase> effect_infos_, const u32 effect_count_,
9 std::span<EffectResultState> result_states_cpu_,
10 std::span<EffectResultState> result_states_dsp_,
11 const size_t dsp_state_count_) {
12 effect_infos = effect_infos_;
13 effect_count = effect_count_;
14 result_states_cpu = result_states_cpu_;
15 result_states_dsp = result_states_dsp_;
16 dsp_state_count = dsp_state_count_;
17}
18
19EffectInfoBase& EffectContext::GetInfo(const u32 index) {
20 return effect_infos[index];
21}
22
23EffectResultState& EffectContext::GetResultState(const u32 index) {
24 return result_states_cpu[index];
25}
26
27EffectResultState& EffectContext::GetDspSharedResultState(const u32 index) {
28 return result_states_dsp[index];
29}
30
31u32 EffectContext::GetCount() const {
32 return effect_count;
33}
34
35void EffectContext::UpdateStateByDspShared() {
36 for (size_t i = 0; i < dsp_state_count; i++) {
37 effect_infos[i].UpdateResultState(result_states_cpu[i], result_states_dsp[i]);
38 }
39}
40
41} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/effect/effect_context.h b/src/audio_core/renderer/effect/effect_context.h
new file mode 100644
index 000000000..85955bd9c
--- /dev/null
+++ b/src/audio_core/renderer/effect/effect_context.h
@@ -0,0 +1,75 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <span>
7
8#include "audio_core/renderer/effect/effect_info_base.h"
9#include "audio_core/renderer/effect/effect_result_state.h"
10#include "common/common_types.h"
11
12namespace AudioCore::AudioRenderer {
13
14class EffectContext {
15public:
16 /**
17 * Initialize the effect context
18 * @param effect_infos List of effect infos for this context
19 * @param effect_count The number of effects in the list
20 * @param result_states_cpu The workbuffer of result states for the CPU for this context
21 * @param result_states_dsp The workbuffer of result states for the DSP for this context
22 * @param state_count The number of result states
23 */
24 void Initialize(std::span<EffectInfoBase> effect_infos_, const u32 effect_count_,
25 std::span<EffectResultState> result_states_cpu_,
26 std::span<EffectResultState> result_states_dsp_, const size_t dsp_state_count);
27
28 /**
29 * Get the EffectInfo for a given index
30 * @param index Which effect to return
31 * @return Pointer to the effect
32 */
33 EffectInfoBase& GetInfo(const u32 index);
34
35 /**
36 * Get the CPU result state for a given index
37 * @param index Which result to return
38 * @return Pointer to the effect result state
39 */
40 EffectResultState& GetResultState(const u32 index);
41
42 /**
43 * Get the DSP result state for a given index
44 * @param index Which result to return
45 * @return Pointer to the effect result state
46 */
47 EffectResultState& GetDspSharedResultState(const u32 index);
48
49 /**
50 * Get the number of effects in this context
51 * @return The number of effects
52 */
53 u32 GetCount() const;
54
55 /**
56 * Update the CPU and DSP result states for all effects
57 */
58 void UpdateStateByDspShared();
59
60private:
61 /// Workbuffer for all of the effects
62 std::span<EffectInfoBase> effect_infos{};
63 /// Number of effects in the workbuffer
64 u32 effect_count{};
65 /// Workbuffer of states for all effects, kept host-side and not directly modified, dsp states
66 /// are copied here on the next render frame
67 std::span<EffectResultState> result_states_cpu{};
68 /// Workbuffer of states for all effects, used by the AudioRenderer to track effect state
69 /// between calls
70 std::span<EffectResultState> result_states_dsp{};
71 /// Number of result states in the workbuffers
72 size_t dsp_state_count{};
73};
74
75} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/effect/effect_info_base.h b/src/audio_core/renderer/effect/effect_info_base.h
new file mode 100644
index 000000000..43d0589cc
--- /dev/null
+++ b/src/audio_core/renderer/effect/effect_info_base.h
@@ -0,0 +1,435 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7
8#include "audio_core/common/common.h"
9#include "audio_core/renderer/behavior/behavior_info.h"
10#include "audio_core/renderer/effect/effect_result_state.h"
11#include "audio_core/renderer/memory/address_info.h"
12#include "audio_core/renderer/memory/pool_mapper.h"
13#include "common/common_types.h"
14
15namespace AudioCore::AudioRenderer {
16/**
17 * Base of all effects. Holds various data and functions used for all derived effects.
18 * Should not be used directly.
19 */
20class EffectInfoBase {
21public:
22 enum class Type : u8 {
23 Invalid,
24 Mix,
25 Aux,
26 Delay,
27 Reverb,
28 I3dl2Reverb,
29 BiquadFilter,
30 LightLimiter,
31 Capture,
32 Compressor,
33 };
34
35 enum class UsageState {
36 Invalid,
37 New,
38 Enabled,
39 Disabled,
40 };
41
42 enum class OutStatus : u8 {
43 Invalid,
44 New,
45 Initialized,
46 Used,
47 Removed,
48 };
49
50 enum class ParameterState : u8 {
51 Initialized,
52 Updating,
53 Updated,
54 };
55
56 struct InParameterVersion1 {
57 /* 0x00 */ Type type;
58 /* 0x01 */ bool is_new;
59 /* 0x02 */ bool enabled;
60 /* 0x04 */ u32 mix_id;
61 /* 0x08 */ CpuAddr workbuffer;
62 /* 0x10 */ CpuAddr workbuffer_size;
63 /* 0x18 */ u32 process_order;
64 /* 0x1C */ char unk1C[0x4];
65 /* 0x20 */ std::array<u8, 0xA0> specific;
66 };
67 static_assert(sizeof(InParameterVersion1) == 0xC0,
68 "EffectInfoBase::InParameterVersion1 has the wrong size!");
69
70 struct InParameterVersion2 {
71 /* 0x00 */ Type type;
72 /* 0x01 */ bool is_new;
73 /* 0x02 */ bool enabled;
74 /* 0x04 */ u32 mix_id;
75 /* 0x08 */ CpuAddr workbuffer;
76 /* 0x10 */ CpuAddr workbuffer_size;
77 /* 0x18 */ u32 process_order;
78 /* 0x1C */ char unk1C[0x4];
79 /* 0x20 */ std::array<u8, 0xA0> specific;
80 };
81 static_assert(sizeof(InParameterVersion2) == 0xC0,
82 "EffectInfoBase::InParameterVersion2 has the wrong size!");
83
84 struct OutStatusVersion1 {
85 /* 0x00 */ OutStatus state;
86 /* 0x01 */ char unk01[0xF];
87 };
88 static_assert(sizeof(OutStatusVersion1) == 0x10,
89 "EffectInfoBase::OutStatusVersion1 has the wrong size!");
90
91 struct OutStatusVersion2 {
92 /* 0x00 */ OutStatus state;
93 /* 0x01 */ char unk01[0xF];
94 /* 0x10 */ EffectResultState result_state;
95 };
96 static_assert(sizeof(OutStatusVersion2) == 0x90,
97 "EffectInfoBase::OutStatusVersion2 has the wrong size!");
98
99 struct State {
100 std::array<u8, 0x500> buffer;
101 };
102 static_assert(sizeof(State) == 0x500, "EffectInfoBase::State has the wrong size!");
103
104 EffectInfoBase() {
105 Cleanup();
106 }
107
108 virtual ~EffectInfoBase() = default;
109
110 /**
111 * Cleanup this effect, resetting it to a starting state.
112 */
113 void Cleanup() {
114 type = Type::Invalid;
115 enabled = false;
116 mix_id = UnusedMixId;
117 process_order = InvalidProcessOrder;
118 buffer_unmapped = false;
119 parameter = {};
120 for (auto& workbuffer : workbuffers) {
121 workbuffer.Setup(CpuAddr(0), 0);
122 }
123 }
124
125 /**
126 * Forcibly unmap all assigned workbuffers from the AudioRenderer.
127 *
128 * @param pool_mapper - Mapper to unmap the buffers.
129 */
130 void ForceUnmapBuffers(const PoolMapper& pool_mapper) {
131 for (auto& workbuffer : workbuffers) {
132 if (workbuffer.GetReference(false) != 0) {
133 pool_mapper.ForceUnmapPointer(workbuffer);
134 }
135 }
136 }
137
138 /**
139 * Check if this effect is enabled.
140 *
141 * @return True if effect is enabled, otherwise false.
142 */
143 bool IsEnabled() const {
144 return enabled;
145 }
146
147 /**
148 * Check if this effect should not be generated.
149 *
150 * @return True if effect should be skipped, otherwise false.
151 */
152 bool ShouldSkip() const {
153 return buffer_unmapped;
154 }
155
156 /**
157 * Get the type of this effect.
158 *
159 * @return The type of this effect. See EffectInfoBase::Type
160 */
161 Type GetType() const {
162 return type;
163 }
164
165 /**
166 * Set the type of this effect.
167 *
168 * @param type_ - The new type of this effect.
169 */
170 void SetType(const Type type_) {
171 type = type_;
172 }
173
174 /**
175 * Get the mix id of this effect.
176 *
177 * @return Mix id of this effect.
178 */
179 s32 GetMixId() const {
180 return mix_id;
181 }
182
183 /**
184 * Get the processing order of this effect.
185 *
186 * @return Process order of this effect.
187 */
188 s32 GetProcessingOrder() const {
189 return process_order;
190 }
191
192 /**
193 * Get this effect's parameter data.
194 *
195 * @return Pointer to the parametter, must be cast to the correct type.
196 */
197 u8* GetParameter() {
198 return parameter.data();
199 }
200
201 /**
202 * Get this effect's parameter data.
203 *
204 * @return Pointer to the parametter, must be cast to the correct type.
205 */
206 u8* GetStateBuffer() {
207 return state.data();
208 }
209
210 /**
211 * Set this effect's usage state.
212 *
213 * @param usage - new usage state of this effect.
214 */
215 void SetUsage(const UsageState usage) {
216 usage_state = usage;
217 }
218
219 /**
220 * Check if this effects need to have its workbuffer information updated.
221 * Version 1.
222 *
223 * @param params - Input parameters.
224 * @return True if workbuffers need updating, otherwise false.
225 */
226 bool ShouldUpdateWorkBufferInfo(const InParameterVersion1& params) const {
227 return buffer_unmapped || params.is_new;
228 }
229
230 /**
231 * Check if this effects need to have its workbuffer information updated.
232 * Version 2.
233 *
234 * @param params - Input parameters.
235 * @return True if workbuffers need updating, otherwise false.
236 */
237 bool ShouldUpdateWorkBufferInfo(const InParameterVersion2& params) const {
238 return buffer_unmapped || params.is_new;
239 }
240
241 /**
242 * Get the current usage state of this effect.
243 *
244 * @return The current usage state.
245 */
246 UsageState GetUsage() const {
247 return usage_state;
248 }
249
250 /**
251 * Write the current state. Version 1.
252 *
253 * @param out_status - Status to write.
254 * @param renderer_active - Is the AudioRenderer active?
255 */
256 void StoreStatus(OutStatusVersion1& out_status, const bool renderer_active) const {
257 if (renderer_active) {
258 if (usage_state != UsageState::Disabled) {
259 out_status.state = OutStatus::Used;
260 } else {
261 out_status.state = OutStatus::Removed;
262 }
263 } else if (usage_state == UsageState::New) {
264 out_status.state = OutStatus::Used;
265 } else {
266 out_status.state = OutStatus::Removed;
267 }
268 }
269
270 /**
271 * Write the current state. Version 2.
272 *
273 * @param out_status - Status to write.
274 * @param renderer_active - Is the AudioRenderer active?
275 */
276 void StoreStatus(OutStatusVersion2& out_status, const bool renderer_active) const {
277 if (renderer_active) {
278 if (usage_state != UsageState::Disabled) {
279 out_status.state = OutStatus::Used;
280 } else {
281 out_status.state = OutStatus::Removed;
282 }
283 } else if (usage_state == UsageState::New) {
284 out_status.state = OutStatus::Used;
285 } else {
286 out_status.state = OutStatus::Removed;
287 }
288 }
289
290 /**
291 * Update the info with new parameters, version 1.
292 *
293 * @param error_info - Used to write call result code.
294 * @param in_params - New parameters to update the info with.
295 * @param pool_mapper - Pool for mapping buffers.
296 */
297 virtual void Update(BehaviorInfo::ErrorInfo& error_info,
298 [[maybe_unused]] const InParameterVersion1& params,
299 [[maybe_unused]] const PoolMapper& pool_mapper) {
300 error_info.error_code = ResultSuccess;
301 error_info.address = CpuAddr(0);
302 }
303
304 /**
305 * Update the info with new parameters, version 2.
306 *
307 * @param error_info - Used to write call result code.
308 * @param in_params - New parameters to update the info with.
309 * @param pool_mapper - Pool for mapping buffers.
310 */
311 virtual void Update(BehaviorInfo::ErrorInfo& error_info,
312 [[maybe_unused]] const InParameterVersion2& params,
313 [[maybe_unused]] const PoolMapper& pool_mapper) {
314 error_info.error_code = ResultSuccess;
315 error_info.address = CpuAddr(0);
316 }
317
318 /**
319 * Update the info after command generation. Usually only changes its state.
320 */
321 virtual void UpdateForCommandGeneration() {}
322
323 /**
324 * Initialize a new result state. Version 2 only, unused.
325 *
326 * @param result_state - Result state to initialize.
327 */
328 virtual void InitializeResultState([[maybe_unused]] EffectResultState& result_state) {}
329
330 /**
331 * Update the host-side state with the ADSP-side state. Version 2 only, unused.
332 *
333 * @param cpu_state - Host-side result state to update.
334 * @param dsp_state - AudioRenderer-side result state to update from.
335 */
336 virtual void UpdateResultState([[maybe_unused]] EffectResultState& cpu_state,
337 [[maybe_unused]] EffectResultState& dsp_state) {}
338
339 /**
340 * Get a workbuffer assigned to this effect with the given index.
341 *
342 * @param index - Workbuffer index.
343 * @return Address of the buffer.
344 */
345 virtual CpuAddr GetWorkbuffer([[maybe_unused]] s32 index) {
346 return 0;
347 }
348
349 /**
350 * Get the first workbuffer assigned to this effect.
351 *
352 * @param index - Workbuffer index. Unused.
353 * @return Address of the buffer.
354 */
355 CpuAddr GetSingleBuffer([[maybe_unused]] const s32 index) {
356 if (enabled) {
357 return workbuffers[0].GetReference(true);
358 }
359
360 if (usage_state != UsageState::Disabled) {
361 const auto ref{workbuffers[0].GetReference(false)};
362 const auto size{workbuffers[0].GetSize()};
363 if (ref != 0 && size > 0) {
364 // Invalidate DSP cache
365 }
366 }
367 return 0;
368 }
369
370 /**
371 * Get the send buffer info, used by Aux and Capture.
372 *
373 * @return Address of the buffer info.
374 */
375 CpuAddr GetSendBufferInfo() const {
376 return send_buffer_info;
377 }
378
379 /**
380 * Get the send buffer, used by Aux and Capture.
381 *
382 * @return Address of the buffer.
383 */
384 CpuAddr GetSendBuffer() const {
385 return send_buffer;
386 }
387
388 /**
389 * Get the return buffer info, used by Aux and Capture.
390 *
391 * @return Address of the buffer info.
392 */
393 CpuAddr GetReturnBufferInfo() const {
394 return return_buffer_info;
395 }
396
397 /**
398 * Get the return buffer, used by Aux and Capture.
399 *
400 * @return Address of the buffer.
401 */
402 CpuAddr GetReturnBuffer() const {
403 return return_buffer;
404 }
405
406protected:
407 /// Type of this effect. May be changed
408 Type type{Type::Invalid};
409 /// Is this effect enabled?
410 bool enabled{};
411 /// Are this effect's buffers unmapped?
412 bool buffer_unmapped{};
413 /// Current usage state
414 UsageState usage_state{UsageState::Invalid};
415 /// Mix id of this effect
416 s32 mix_id{UnusedMixId};
417 /// Process order of this effect
418 s32 process_order{InvalidProcessOrder};
419 /// Workbuffers assigned to this effect
420 std::array<AddressInfo, 2> workbuffers{AddressInfo(CpuAddr(0), 0), AddressInfo(CpuAddr(0), 0)};
421 /// Aux/Capture buffer info for reading
422 CpuAddr send_buffer_info;
423 /// Aux/Capture buffer for reading
424 CpuAddr send_buffer;
425 /// Aux/Capture buffer info for writing
426 CpuAddr return_buffer_info;
427 /// Aux/Capture buffer for writing
428 CpuAddr return_buffer;
429 /// Parameters of this effect
430 std::array<u8, sizeof(InParameterVersion2)> parameter{};
431 /// State of this effect used by the AudioRenderer across calls
432 std::array<u8, sizeof(State)> state{};
433};
434
435} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/effect/effect_reset.h b/src/audio_core/renderer/effect/effect_reset.h
new file mode 100644
index 000000000..1ea67e334
--- /dev/null
+++ b/src/audio_core/renderer/effect/effect_reset.h
@@ -0,0 +1,71 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "audio_core/renderer/effect/aux_.h"
7#include "audio_core/renderer/effect/biquad_filter.h"
8#include "audio_core/renderer/effect/buffer_mixer.h"
9#include "audio_core/renderer/effect/capture.h"
10#include "audio_core/renderer/effect/compressor.h"
11#include "audio_core/renderer/effect/delay.h"
12#include "audio_core/renderer/effect/i3dl2.h"
13#include "audio_core/renderer/effect/light_limiter.h"
14#include "audio_core/renderer/effect/reverb.h"
15#include "common/common_types.h"
16
17namespace AudioCore::AudioRenderer {
18/**
19 * Reset an effect, and create a new one of the given type.
20 *
21 * @param effect - Effect to reset and re-construct.
22 * @param type - Type of the new effect to create.
23 */
24static void ResetEffect(EffectInfoBase* effect, const EffectInfoBase::Type type) {
25 *effect = {};
26
27 switch (type) {
28 case EffectInfoBase::Type::Invalid:
29 std::construct_at<EffectInfoBase>(effect);
30 effect->SetType(EffectInfoBase::Type::Invalid);
31 break;
32 case EffectInfoBase::Type::Mix:
33 std::construct_at<BufferMixerInfo>(reinterpret_cast<BufferMixerInfo*>(effect));
34 effect->SetType(EffectInfoBase::Type::Mix);
35 break;
36 case EffectInfoBase::Type::Aux:
37 std::construct_at<AuxInfo>(reinterpret_cast<AuxInfo*>(effect));
38 effect->SetType(EffectInfoBase::Type::Aux);
39 break;
40 case EffectInfoBase::Type::Delay:
41 std::construct_at<DelayInfo>(reinterpret_cast<DelayInfo*>(effect));
42 effect->SetType(EffectInfoBase::Type::Delay);
43 break;
44 case EffectInfoBase::Type::Reverb:
45 std::construct_at<ReverbInfo>(reinterpret_cast<ReverbInfo*>(effect));
46 effect->SetType(EffectInfoBase::Type::Reverb);
47 break;
48 case EffectInfoBase::Type::I3dl2Reverb:
49 std::construct_at<I3dl2ReverbInfo>(reinterpret_cast<I3dl2ReverbInfo*>(effect));
50 effect->SetType(EffectInfoBase::Type::I3dl2Reverb);
51 break;
52 case EffectInfoBase::Type::BiquadFilter:
53 std::construct_at<BiquadFilterInfo>(reinterpret_cast<BiquadFilterInfo*>(effect));
54 effect->SetType(EffectInfoBase::Type::BiquadFilter);
55 break;
56 case EffectInfoBase::Type::LightLimiter:
57 std::construct_at<LightLimiterInfo>(reinterpret_cast<LightLimiterInfo*>(effect));
58 effect->SetType(EffectInfoBase::Type::LightLimiter);
59 break;
60 case EffectInfoBase::Type::Capture:
61 std::construct_at<CaptureInfo>(reinterpret_cast<CaptureInfo*>(effect));
62 effect->SetType(EffectInfoBase::Type::Capture);
63 break;
64 case EffectInfoBase::Type::Compressor:
65 std::construct_at<CompressorInfo>(reinterpret_cast<CompressorInfo*>(effect));
66 effect->SetType(EffectInfoBase::Type::Compressor);
67 break;
68 }
69}
70
71} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/effect/effect_result_state.h b/src/audio_core/renderer/effect/effect_result_state.h
new file mode 100644
index 000000000..ae096ad69
--- /dev/null
+++ b/src/audio_core/renderer/effect/effect_result_state.h
@@ -0,0 +1,16 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7
8#include "common/common_types.h"
9
10namespace AudioCore::AudioRenderer {
11
12struct EffectResultState {
13 std::array<u8, 0x80> state;
14};
15
16} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/effect/i3dl2.cpp b/src/audio_core/renderer/effect/i3dl2.cpp
new file mode 100644
index 000000000..960b29cfc
--- /dev/null
+++ b/src/audio_core/renderer/effect/i3dl2.cpp
@@ -0,0 +1,94 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/effect/i3dl2.h"
5
6namespace AudioCore::AudioRenderer {
7
8void I3dl2ReverbInfo::Update(BehaviorInfo::ErrorInfo& error_info,
9 const InParameterVersion1& in_params, const PoolMapper& pool_mapper) {
10 auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())};
11 auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
12
13 if (IsChannelCountValid(in_specific->channel_count_max)) {
14 const auto old_state{params->state};
15 std::memcpy(params, in_specific, sizeof(ParameterVersion1));
16 mix_id = in_params.mix_id;
17 process_order = in_params.process_order;
18 enabled = in_params.enabled;
19
20 if (!IsChannelCountValid(in_specific->channel_count)) {
21 params->channel_count = params->channel_count_max;
22 }
23
24 if (!IsChannelCountValid(in_specific->channel_count) ||
25 old_state != ParameterState::Updated) {
26 params->state = old_state;
27 }
28
29 if (buffer_unmapped || in_params.is_new) {
30 usage_state = UsageState::New;
31 params->state = ParameterState::Initialized;
32 buffer_unmapped = !pool_mapper.TryAttachBuffer(
33 error_info, workbuffers[0], in_params.workbuffer, in_params.workbuffer_size);
34 return;
35 }
36 }
37 error_info.error_code = ResultSuccess;
38 error_info.address = CpuAddr(0);
39}
40
41void I3dl2ReverbInfo::Update(BehaviorInfo::ErrorInfo& error_info,
42 const InParameterVersion2& in_params, const PoolMapper& pool_mapper) {
43 auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())};
44 auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
45
46 if (IsChannelCountValid(in_specific->channel_count_max)) {
47 const auto old_state{params->state};
48 std::memcpy(params, in_specific, sizeof(ParameterVersion1));
49 mix_id = in_params.mix_id;
50 process_order = in_params.process_order;
51 enabled = in_params.enabled;
52
53 if (!IsChannelCountValid(in_specific->channel_count)) {
54 params->channel_count = params->channel_count_max;
55 }
56
57 if (!IsChannelCountValid(in_specific->channel_count) ||
58 old_state != ParameterState::Updated) {
59 params->state = old_state;
60 }
61
62 if (buffer_unmapped || in_params.is_new) {
63 usage_state = UsageState::New;
64 params->state = ParameterState::Initialized;
65 buffer_unmapped = !pool_mapper.TryAttachBuffer(
66 error_info, workbuffers[0], in_params.workbuffer, in_params.workbuffer_size);
67 return;
68 }
69 }
70 error_info.error_code = ResultSuccess;
71 error_info.address = CpuAddr(0);
72}
73
74void I3dl2ReverbInfo::UpdateForCommandGeneration() {
75 if (enabled) {
76 usage_state = UsageState::Enabled;
77 } else {
78 usage_state = UsageState::Disabled;
79 }
80
81 auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
82 params->state = ParameterState::Updated;
83}
84
85void I3dl2ReverbInfo::InitializeResultState(EffectResultState& result_state) {}
86
87void I3dl2ReverbInfo::UpdateResultState(EffectResultState& cpu_state,
88 EffectResultState& dsp_state) {}
89
90CpuAddr I3dl2ReverbInfo::GetWorkbuffer(s32 index) {
91 return GetSingleBuffer(index);
92}
93
94} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/effect/i3dl2.h b/src/audio_core/renderer/effect/i3dl2.h
new file mode 100644
index 000000000..7a088a627
--- /dev/null
+++ b/src/audio_core/renderer/effect/i3dl2.h
@@ -0,0 +1,200 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7#include <vector>
8
9#include "audio_core/common/common.h"
10#include "audio_core/renderer/effect/effect_info_base.h"
11#include "common/common_types.h"
12#include "common/fixed_point.h"
13
14namespace AudioCore::AudioRenderer {
15
16class I3dl2ReverbInfo : public EffectInfoBase {
17public:
18 struct ParameterVersion1 {
19 /* 0x00 */ std::array<s8, MaxChannels> inputs;
20 /* 0x06 */ std::array<s8, MaxChannels> outputs;
21 /* 0x0C */ u16 channel_count_max;
22 /* 0x0E */ u16 channel_count;
23 /* 0x10 */ char unk10[0x4];
24 /* 0x14 */ u32 sample_rate;
25 /* 0x18 */ f32 room_HF_gain;
26 /* 0x1C */ f32 reference_HF;
27 /* 0x20 */ f32 late_reverb_decay_time;
28 /* 0x24 */ f32 late_reverb_HF_decay_ratio;
29 /* 0x28 */ f32 room_gain;
30 /* 0x2C */ f32 reflection_gain;
31 /* 0x30 */ f32 reverb_gain;
32 /* 0x34 */ f32 late_reverb_diffusion;
33 /* 0x38 */ f32 reflection_delay;
34 /* 0x3C */ f32 late_reverb_delay_time;
35 /* 0x40 */ f32 late_reverb_density;
36 /* 0x44 */ f32 dry_gain;
37 /* 0x48 */ ParameterState state;
38 /* 0x49 */ char unk49[0x3];
39 };
40 static_assert(sizeof(ParameterVersion1) <= sizeof(EffectInfoBase::InParameterVersion1),
41 "I3dl2ReverbInfo::ParameterVersion1 has the wrong size!");
42
43 struct ParameterVersion2 {
44 /* 0x00 */ std::array<s8, MaxChannels> inputs;
45 /* 0x06 */ std::array<s8, MaxChannels> outputs;
46 /* 0x0C */ u16 channel_count_max;
47 /* 0x0E */ u16 channel_count;
48 /* 0x10 */ char unk10[0x4];
49 /* 0x14 */ u32 sample_rate;
50 /* 0x18 */ f32 room_HF_gain;
51 /* 0x1C */ f32 reference_HF;
52 /* 0x20 */ f32 late_reverb_decay_time;
53 /* 0x24 */ f32 late_reverb_HF_decay_ratio;
54 /* 0x28 */ f32 room_gain;
55 /* 0x2C */ f32 reflection_gain;
56 /* 0x30 */ f32 reverb_gain;
57 /* 0x34 */ f32 late_reverb_diffusion;
58 /* 0x38 */ f32 reflection_delay;
59 /* 0x3C */ f32 late_reverb_delay_time;
60 /* 0x40 */ f32 late_reverb_density;
61 /* 0x44 */ f32 dry_gain;
62 /* 0x48 */ ParameterState state;
63 /* 0x49 */ char unk49[0x3];
64 };
65 static_assert(sizeof(ParameterVersion2) <= sizeof(EffectInfoBase::InParameterVersion2),
66 "I3dl2ReverbInfo::ParameterVersion2 has the wrong size!");
67
68 static constexpr u32 MaxDelayLines = 4;
69 static constexpr u32 MaxDelayTaps = 20;
70
71 struct I3dl2DelayLine {
72 void Initialize(const s32 delay_time) {
73 max_delay = delay_time;
74 buffer.resize(delay_time + 1, 0);
75 buffer_end = &buffer[delay_time];
76 output = &buffer[0];
77 SetDelay(delay_time);
78 wet_gain = 0.0f;
79 }
80
81 void SetDelay(const s32 delay_time) {
82 if (max_delay < delay_time) {
83 return;
84 }
85 delay = delay_time;
86 input = &buffer[(output - buffer.data() + delay) % (max_delay + 1)];
87 }
88
89 Common::FixedPoint<50, 14> Tick(const Common::FixedPoint<50, 14> sample) {
90 Write(sample);
91
92 auto out_sample{Read()};
93
94 output++;
95 if (output >= buffer_end) {
96 output = buffer.data();
97 }
98
99 return out_sample;
100 }
101
102 Common::FixedPoint<50, 14> Read() {
103 return *output;
104 }
105
106 void Write(const Common::FixedPoint<50, 14> sample) {
107 *(input++) = sample;
108 if (input >= buffer_end) {
109 input = buffer.data();
110 }
111 }
112
113 Common::FixedPoint<50, 14> TapOut(const s32 index) {
114 auto out{input - (index + 1)};
115 if (out < buffer.data()) {
116 out += max_delay + 1;
117 }
118 return *out;
119 }
120
121 std::vector<Common::FixedPoint<50, 14>> buffer{};
122 Common::FixedPoint<50, 14>* buffer_end{};
123 s32 max_delay{};
124 Common::FixedPoint<50, 14>* input{};
125 Common::FixedPoint<50, 14>* output{};
126 s32 delay{};
127 f32 wet_gain{};
128 };
129
130 struct State {
131 f32 lowpass_0;
132 f32 lowpass_1;
133 f32 lowpass_2;
134 I3dl2DelayLine early_delay_line;
135 std::array<s32, MaxDelayTaps> early_tap_steps;
136 f32 early_gain;
137 f32 late_gain;
138 s32 early_to_late_taps;
139 std::array<I3dl2DelayLine, MaxDelayLines> fdn_delay_lines;
140 std::array<I3dl2DelayLine, MaxDelayLines> decay_delay_lines0;
141 std::array<I3dl2DelayLine, MaxDelayLines> decay_delay_lines1;
142 f32 last_reverb_echo;
143 I3dl2DelayLine center_delay_line;
144 std::array<std::array<f32, 3>, MaxDelayLines> lowpass_coeff;
145 std::array<f32, MaxDelayLines> shelf_filter;
146 f32 dry_gain;
147 };
148 static_assert(sizeof(State) <= sizeof(EffectInfoBase::State),
149 "I3dl2ReverbInfo::State is too large!");
150
151 /**
152 * Update the info with new parameters, version 1.
153 *
154 * @param error_info - Used to write call result code.
155 * @param in_params - New parameters to update the info with.
156 * @param pool_mapper - Pool for mapping buffers.
157 */
158 void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params,
159 const PoolMapper& pool_mapper) override;
160
161 /**
162 * Update the info with new parameters, version 2.
163 *
164 * @param error_info - Used to write call result code.
165 * @param in_params - New parameters to update the info with.
166 * @param pool_mapper - Pool for mapping buffers.
167 */
168 void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params,
169 const PoolMapper& pool_mapper) override;
170
171 /**
172 * Update the info after command generation. Usually only changes its state.
173 */
174 void UpdateForCommandGeneration() override;
175
176 /**
177 * Initialize a new result state. Version 2 only, unused.
178 *
179 * @param result_state - Result state to initialize.
180 */
181 void InitializeResultState(EffectResultState& result_state) override;
182
183 /**
184 * Update the host-side state with the ADSP-side state. Version 2 only, unused.
185 *
186 * @param cpu_state - Host-side result state to update.
187 * @param dsp_state - AudioRenderer-side result state to update from.
188 */
189 void UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) override;
190
191 /**
192 * Get a workbuffer assigned to this effect with the given index.
193 *
194 * @param index - Workbuffer index.
195 * @return Address of the buffer.
196 */
197 CpuAddr GetWorkbuffer(s32 index) override;
198};
199
200} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/effect/light_limiter.cpp b/src/audio_core/renderer/effect/light_limiter.cpp
new file mode 100644
index 000000000..1635a952d
--- /dev/null
+++ b/src/audio_core/renderer/effect/light_limiter.cpp
@@ -0,0 +1,81 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/effect/light_limiter.h"
5
6namespace AudioCore::AudioRenderer {
7
8void LightLimiterInfo::Update(BehaviorInfo::ErrorInfo& error_info,
9 const InParameterVersion1& in_params, const PoolMapper& pool_mapper) {
10 auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())};
11 auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
12
13 std::memcpy(params, in_specific, sizeof(ParameterVersion1));
14 mix_id = in_params.mix_id;
15 process_order = in_params.process_order;
16 enabled = in_params.enabled;
17
18 if (buffer_unmapped || in_params.is_new) {
19 usage_state = UsageState::New;
20 params->state = ParameterState::Initialized;
21 buffer_unmapped = !pool_mapper.TryAttachBuffer(
22 error_info, workbuffers[0], in_params.workbuffer, in_params.workbuffer_size);
23 } else {
24 error_info.error_code = ResultSuccess;
25 error_info.address = CpuAddr(0);
26 }
27}
28
29void LightLimiterInfo::Update(BehaviorInfo::ErrorInfo& error_info,
30 const InParameterVersion2& in_params, const PoolMapper& pool_mapper) {
31 auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())};
32 auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
33
34 std::memcpy(params, in_specific, sizeof(ParameterVersion1));
35 mix_id = in_params.mix_id;
36 process_order = in_params.process_order;
37 enabled = in_params.enabled;
38
39 if (buffer_unmapped || in_params.is_new) {
40 usage_state = UsageState::New;
41 params->state = ParameterState::Initialized;
42 buffer_unmapped = !pool_mapper.TryAttachBuffer(
43 error_info, workbuffers[0], in_params.workbuffer, in_params.workbuffer_size);
44 } else {
45 error_info.error_code = ResultSuccess;
46 error_info.address = CpuAddr(0);
47 }
48}
49
50void LightLimiterInfo::UpdateForCommandGeneration() {
51 if (enabled) {
52 usage_state = UsageState::Enabled;
53 } else {
54 usage_state = UsageState::Disabled;
55 }
56
57 auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
58 params->state = ParameterState::Updated;
59 params->statistics_reset_required = false;
60}
61
62void LightLimiterInfo::InitializeResultState(EffectResultState& result_state) {
63 auto result_state_{reinterpret_cast<StatisticsInternal*>(result_state.state.data())};
64
65 result_state_->channel_max_sample.fill(0);
66 result_state_->channel_compression_gain_min.fill(1.0f);
67}
68
69void LightLimiterInfo::UpdateResultState(EffectResultState& cpu_state,
70 EffectResultState& dsp_state) {
71 auto cpu_statistics{reinterpret_cast<StatisticsInternal*>(cpu_state.state.data())};
72 auto dsp_statistics{reinterpret_cast<StatisticsInternal*>(dsp_state.state.data())};
73
74 *cpu_statistics = *dsp_statistics;
75}
76
77CpuAddr LightLimiterInfo::GetWorkbuffer(s32 index) {
78 return GetSingleBuffer(index);
79}
80
81} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/effect/light_limiter.h b/src/audio_core/renderer/effect/light_limiter.h
new file mode 100644
index 000000000..338d67bbc
--- /dev/null
+++ b/src/audio_core/renderer/effect/light_limiter.h
@@ -0,0 +1,138 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7#include <vector>
8
9#include "audio_core/common/common.h"
10#include "audio_core/renderer/effect/effect_info_base.h"
11#include "common/common_types.h"
12#include "common/fixed_point.h"
13
14namespace AudioCore::AudioRenderer {
15
16class LightLimiterInfo : public EffectInfoBase {
17public:
18 enum class ProcessingMode {
19 Mode0,
20 Mode1,
21 };
22
23 struct ParameterVersion1 {
24 /* 0x00 */ std::array<s8, MaxChannels> inputs;
25 /* 0x06 */ std::array<s8, MaxChannels> outputs;
26 /* 0x0C */ u16 channel_count_max;
27 /* 0x0E */ u16 channel_count;
28 /* 0x0C */ u32 sample_rate;
29 /* 0x14 */ s32 look_ahead_time_max;
30 /* 0x18 */ s32 attack_time;
31 /* 0x1C */ s32 release_time;
32 /* 0x20 */ s32 look_ahead_time;
33 /* 0x24 */ f32 attack_coeff;
34 /* 0x28 */ f32 release_coeff;
35 /* 0x2C */ f32 threshold;
36 /* 0x30 */ f32 input_gain;
37 /* 0x34 */ f32 output_gain;
38 /* 0x38 */ s32 look_ahead_samples_min;
39 /* 0x3C */ s32 look_ahead_samples_max;
40 /* 0x40 */ ParameterState state;
41 /* 0x41 */ bool statistics_enabled;
42 /* 0x42 */ bool statistics_reset_required;
43 /* 0x43 */ ProcessingMode processing_mode;
44 };
45 static_assert(sizeof(ParameterVersion1) <= sizeof(EffectInfoBase::InParameterVersion1),
46 "LightLimiterInfo::ParameterVersion1 has the wrong size!");
47
48 struct ParameterVersion2 {
49 /* 0x00 */ std::array<s8, MaxChannels> inputs;
50 /* 0x06 */ std::array<s8, MaxChannels> outputs;
51 /* 0x0C */ u16 channel_count_max;
52 /* 0x0E */ u16 channel_count;
53 /* 0x0C */ u32 sample_rate;
54 /* 0x14 */ s32 look_ahead_time_max;
55 /* 0x18 */ s32 attack_time;
56 /* 0x1C */ s32 release_time;
57 /* 0x20 */ s32 look_ahead_time;
58 /* 0x24 */ f32 attack_coeff;
59 /* 0x28 */ f32 release_coeff;
60 /* 0x2C */ f32 threshold;
61 /* 0x30 */ f32 input_gain;
62 /* 0x34 */ f32 output_gain;
63 /* 0x38 */ s32 look_ahead_samples_min;
64 /* 0x3C */ s32 look_ahead_samples_max;
65 /* 0x40 */ ParameterState state;
66 /* 0x41 */ bool statistics_enabled;
67 /* 0x42 */ bool statistics_reset_required;
68 /* 0x43 */ ProcessingMode processing_mode;
69 };
70 static_assert(sizeof(ParameterVersion2) <= sizeof(EffectInfoBase::InParameterVersion2),
71 "LightLimiterInfo::ParameterVersion2 has the wrong size!");
72
73 struct State {
74 std::array<Common::FixedPoint<49, 15>, MaxChannels> samples_average;
75 std::array<Common::FixedPoint<49, 15>, MaxChannels> compression_gain;
76 std::array<s32, MaxChannels> look_ahead_sample_offsets;
77 std::array<std::vector<Common::FixedPoint<49, 15>>, MaxChannels> look_ahead_sample_buffers;
78 };
79 static_assert(sizeof(State) <= sizeof(EffectInfoBase::State),
80 "LightLimiterInfo::State has the wrong size!");
81
82 struct StatisticsInternal {
83 /* 0x00 */ std::array<f32, MaxChannels> channel_max_sample;
84 /* 0x18 */ std::array<f32, MaxChannels> channel_compression_gain_min;
85 };
86 static_assert(sizeof(StatisticsInternal) == 0x30,
87 "LightLimiterInfo::StatisticsInternal has the wrong size!");
88
89 /**
90 * Update the info with new parameters, version 1.
91 *
92 * @param error_info - Used to write call result code.
93 * @param in_params - New parameters to update the info with.
94 * @param pool_mapper - Pool for mapping buffers.
95 */
96 void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params,
97 const PoolMapper& pool_mapper) override;
98
99 /**
100 * Update the info with new parameters, version 2.
101 *
102 * @param error_info - Used to write call result code.
103 * @param in_params - New parameters to update the info with.
104 * @param pool_mapper - Pool for mapping buffers.
105 */
106 void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params,
107 const PoolMapper& pool_mapper) override;
108
109 /**
110 * Update the info after command generation. Usually only changes its state.
111 */
112 void UpdateForCommandGeneration() override;
113
114 /**
115 * Initialize a new limiter statistics result state. Version 2 only.
116 *
117 * @param result_state - Result state to initialize.
118 */
119 void InitializeResultState(EffectResultState& result_state) override;
120
121 /**
122 * Update the host-side limiter statistics with the ADSP-side one. Version 2 only.
123 *
124 * @param cpu_state - Host-side result state to update.
125 * @param dsp_state - AudioRenderer-side result state to update from.
126 */
127 void UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) override;
128
129 /**
130 * Get a workbuffer assigned to this effect with the given index.
131 *
132 * @param index - Workbuffer index.
133 * @return Address of the buffer.
134 */
135 CpuAddr GetWorkbuffer(s32 index) override;
136};
137
138} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/effect/reverb.cpp b/src/audio_core/renderer/effect/reverb.cpp
new file mode 100644
index 000000000..2d32383d0
--- /dev/null
+++ b/src/audio_core/renderer/effect/reverb.cpp
@@ -0,0 +1,93 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/effect/reverb.h"
5
6namespace AudioCore::AudioRenderer {
7
8void ReverbInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params,
9 const PoolMapper& pool_mapper) {
10 auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())};
11 auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
12
13 if (IsChannelCountValid(in_specific->channel_count_max)) {
14 const auto old_state{params->state};
15 std::memcpy(params, in_specific, sizeof(ParameterVersion1));
16 mix_id = in_params.mix_id;
17 process_order = in_params.process_order;
18 enabled = in_params.enabled;
19
20 if (!IsChannelCountValid(in_specific->channel_count)) {
21 params->channel_count = params->channel_count_max;
22 }
23
24 if (!IsChannelCountValid(in_specific->channel_count) ||
25 old_state != ParameterState::Updated) {
26 params->state = old_state;
27 }
28
29 if (buffer_unmapped || in_params.is_new) {
30 usage_state = UsageState::New;
31 params->state = ParameterState::Initialized;
32 buffer_unmapped = !pool_mapper.TryAttachBuffer(
33 error_info, workbuffers[0], in_params.workbuffer, in_params.workbuffer_size);
34 return;
35 }
36 }
37 error_info.error_code = ResultSuccess;
38 error_info.address = CpuAddr(0);
39}
40
41void ReverbInfo::Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params,
42 const PoolMapper& pool_mapper) {
43 auto in_specific{reinterpret_cast<const ParameterVersion2*>(in_params.specific.data())};
44 auto params{reinterpret_cast<ParameterVersion2*>(parameter.data())};
45
46 if (IsChannelCountValid(in_specific->channel_count_max)) {
47 const auto old_state{params->state};
48 std::memcpy(params, in_specific, sizeof(ParameterVersion2));
49 mix_id = in_params.mix_id;
50 process_order = in_params.process_order;
51 enabled = in_params.enabled;
52
53 if (!IsChannelCountValid(in_specific->channel_count)) {
54 params->channel_count = params->channel_count_max;
55 }
56
57 if (!IsChannelCountValid(in_specific->channel_count) ||
58 old_state != ParameterState::Updated) {
59 params->state = old_state;
60 }
61
62 if (buffer_unmapped || in_params.is_new) {
63 usage_state = UsageState::New;
64 params->state = ParameterState::Initialized;
65 buffer_unmapped = !pool_mapper.TryAttachBuffer(
66 error_info, workbuffers[0], in_params.workbuffer, in_params.workbuffer_size);
67 return;
68 }
69 }
70 error_info.error_code = ResultSuccess;
71 error_info.address = CpuAddr(0);
72}
73
74void ReverbInfo::UpdateForCommandGeneration() {
75 if (enabled) {
76 usage_state = UsageState::Enabled;
77 } else {
78 usage_state = UsageState::Disabled;
79 }
80
81 auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
82 params->state = ParameterState::Updated;
83}
84
85void ReverbInfo::InitializeResultState(EffectResultState& result_state) {}
86
87void ReverbInfo::UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) {}
88
89CpuAddr ReverbInfo::GetWorkbuffer(s32 index) {
90 return GetSingleBuffer(index);
91}
92
93} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/effect/reverb.h b/src/audio_core/renderer/effect/reverb.h
new file mode 100644
index 000000000..b4df9f6ef
--- /dev/null
+++ b/src/audio_core/renderer/effect/reverb.h
@@ -0,0 +1,190 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7#include <vector>
8
9#include "audio_core/common/common.h"
10#include "audio_core/renderer/effect/effect_info_base.h"
11#include "common/common_types.h"
12#include "common/fixed_point.h"
13
14namespace AudioCore::AudioRenderer {
15
16class ReverbInfo : public EffectInfoBase {
17public:
18 struct ParameterVersion1 {
19 /* 0x00 */ std::array<s8, MaxChannels> inputs;
20 /* 0x06 */ std::array<s8, MaxChannels> outputs;
21 /* 0x0C */ u16 channel_count_max;
22 /* 0x0E */ u16 channel_count;
23 /* 0x10 */ u32 sample_rate;
24 /* 0x14 */ u32 early_mode;
25 /* 0x18 */ s32 early_gain;
26 /* 0x1C */ s32 pre_delay;
27 /* 0x20 */ s32 late_mode;
28 /* 0x24 */ s32 late_gain;
29 /* 0x28 */ s32 decay_time;
30 /* 0x2C */ s32 high_freq_Decay_ratio;
31 /* 0x30 */ s32 colouration;
32 /* 0x34 */ s32 base_gain;
33 /* 0x38 */ s32 wet_gain;
34 /* 0x3C */ s32 dry_gain;
35 /* 0x40 */ ParameterState state;
36 };
37 static_assert(sizeof(ParameterVersion1) <= sizeof(EffectInfoBase::InParameterVersion1),
38 "ReverbInfo::ParameterVersion1 has the wrong size!");
39
40 struct ParameterVersion2 {
41 /* 0x00 */ std::array<s8, MaxChannels> inputs;
42 /* 0x06 */ std::array<s8, MaxChannels> outputs;
43 /* 0x0C */ u16 channel_count_max;
44 /* 0x0E */ u16 channel_count;
45 /* 0x10 */ u32 sample_rate;
46 /* 0x14 */ u32 early_mode;
47 /* 0x18 */ s32 early_gain;
48 /* 0x1C */ s32 pre_delay;
49 /* 0x20 */ s32 late_mode;
50 /* 0x24 */ s32 late_gain;
51 /* 0x28 */ s32 decay_time;
52 /* 0x2C */ s32 high_freq_decay_ratio;
53 /* 0x30 */ s32 colouration;
54 /* 0x34 */ s32 base_gain;
55 /* 0x38 */ s32 wet_gain;
56 /* 0x3C */ s32 dry_gain;
57 /* 0x40 */ ParameterState state;
58 };
59 static_assert(sizeof(ParameterVersion2) <= sizeof(EffectInfoBase::InParameterVersion2),
60 "ReverbInfo::ParameterVersion2 has the wrong size!");
61
62 static constexpr u32 MaxDelayLines = 4;
63 static constexpr u32 MaxDelayTaps = 10;
64 static constexpr u32 NumEarlyModes = 5;
65 static constexpr u32 NumLateModes = 5;
66
67 struct ReverbDelayLine {
68 void Initialize(const s32 delay_time, const f32 decay_rate) {
69 buffer.resize(delay_time + 1, 0);
70 buffer_end = &buffer[delay_time];
71 output = &buffer[0];
72 decay = decay_rate;
73 sample_count_max = delay_time;
74 SetDelay(delay_time);
75 }
76
77 void SetDelay(const s32 delay_time) {
78 if (sample_count_max < delay_time) {
79 return;
80 }
81 sample_count = delay_time;
82 input = &buffer[(output - buffer.data() + sample_count) % (sample_count_max + 1)];
83 }
84
85 Common::FixedPoint<50, 14> Tick(const Common::FixedPoint<50, 14> sample) {
86 Write(sample);
87
88 auto out_sample{Read()};
89
90 output++;
91 if (output >= buffer_end) {
92 output = buffer.data();
93 }
94
95 return out_sample;
96 }
97
98 Common::FixedPoint<50, 14> Read() {
99 return *output;
100 }
101
102 void Write(const Common::FixedPoint<50, 14> sample) {
103 *(input++) = sample;
104 if (input >= buffer_end) {
105 input = buffer.data();
106 }
107 }
108
109 Common::FixedPoint<50, 14> TapOut(const s32 index) {
110 auto out{input - (index + 1)};
111 if (out < buffer.data()) {
112 out += sample_count;
113 }
114 return *out;
115 }
116
117 s32 sample_count{};
118 s32 sample_count_max{};
119 std::vector<Common::FixedPoint<50, 14>> buffer{};
120 Common::FixedPoint<50, 14>* buffer_end;
121 Common::FixedPoint<50, 14>* input{};
122 Common::FixedPoint<50, 14>* output{};
123 Common::FixedPoint<50, 14> decay{};
124 };
125
126 struct State {
127 ReverbDelayLine pre_delay_line;
128 ReverbDelayLine center_delay_line;
129 std::array<s32, MaxDelayTaps> early_delay_times;
130 std::array<Common::FixedPoint<50, 14>, MaxDelayTaps> early_gains;
131 s32 pre_delay_time;
132 std::array<ReverbDelayLine, MaxDelayLines> decay_delay_lines;
133 std::array<ReverbDelayLine, MaxDelayLines> fdn_delay_lines;
134 std::array<Common::FixedPoint<50, 14>, MaxDelayLines> hf_decay_gain;
135 std::array<Common::FixedPoint<50, 14>, MaxDelayLines> hf_decay_prev_gain;
136 std::array<Common::FixedPoint<50, 14>, MaxDelayLines> prev_feedback_output;
137 };
138 static_assert(sizeof(State) <= sizeof(EffectInfoBase::State),
139 "ReverbInfo::State is too large!");
140
141 /**
142 * Update the info with new parameters, version 1.
143 *
144 * @param error_info - Used to write call result code.
145 * @param in_params - New parameters to update the info with.
146 * @param pool_mapper - Pool for mapping buffers.
147 */
148 void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion1& in_params,
149 const PoolMapper& pool_mapper) override;
150
151 /**
152 * Update the info with new parameters, version 2.
153 *
154 * @param error_info - Used to write call result code.
155 * @param in_params - New parameters to update the info with.
156 * @param pool_mapper - Pool for mapping buffers.
157 */
158 void Update(BehaviorInfo::ErrorInfo& error_info, const InParameterVersion2& in_params,
159 const PoolMapper& pool_mapper) override;
160
161 /**
162 * Update the info after command generation. Usually only changes its state.
163 */
164 void UpdateForCommandGeneration() override;
165
166 /**
167 * Initialize a new result state. Version 2 only, unused.
168 *
169 * @param result_state - Result state to initialize.
170 */
171 void InitializeResultState(EffectResultState& result_state) override;
172
173 /**
174 * Update the host-side state with the ADSP-side state. Version 2 only, unused.
175 *
176 * @param cpu_state - Host-side result state to update.
177 * @param dsp_state - AudioRenderer-side result state to update from.
178 */
179 void UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) override;
180
181 /**
182 * Get a workbuffer assigned to this effect with the given index.
183 *
184 * @param index - Workbuffer index.
185 * @return Address of the buffer.
186 */
187 CpuAddr GetWorkbuffer(s32 index) override;
188};
189
190} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/memory/address_info.h b/src/audio_core/renderer/memory/address_info.h
new file mode 100644
index 000000000..4cfefea8e
--- /dev/null
+++ b/src/audio_core/renderer/memory/address_info.h
@@ -0,0 +1,125 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "audio_core/renderer/memory/memory_pool_info.h"
7#include "common/common_types.h"
8
9namespace AudioCore::AudioRenderer {
10
11/**
12 * Represents a region of mapped or unmapped memory.
13 */
14class AddressInfo {
15public:
16 AddressInfo() = default;
17 AddressInfo(CpuAddr cpu_address_, u64 size_) : cpu_address{cpu_address_}, size{size_} {}
18
19 /**
20 * Setup a new AddressInfo.
21 *
22 * @param cpu_address - The CPU address of this region.
23 * @param size - The size of this region.
24 */
25 void Setup(CpuAddr cpu_address_, u64 size_) {
26 cpu_address = cpu_address_;
27 size = size_;
28 memory_pool = nullptr;
29 dsp_address = 0;
30 }
31
32 /**
33 * Get the CPU address.
34 *
35 * @return The CpuAddr address
36 */
37 CpuAddr GetCpuAddr() const {
38 return cpu_address;
39 }
40
41 /**
42 * Assign this region to a memory pool.
43 *
44 * @param memory_pool_ - Memory pool to assign.
45 * @return The CpuAddr address of this region.
46 */
47 void SetPool(MemoryPoolInfo* memory_pool_) {
48 memory_pool = memory_pool_;
49 }
50
51 /**
52 * Get the size of this region.
53 *
54 * @return The size of this region.
55 */
56 u64 GetSize() const {
57 return size;
58 }
59
60 /**
61 * Get the ADSP address for this region.
62 *
63 * @return The ADSP address for this region.
64 */
65 CpuAddr GetForceMappedDspAddr() const {
66 return dsp_address;
67 }
68
69 /**
70 * Set the ADSP address for this region.
71 *
72 * @param dsp_addr - The new ADSP address for this region.
73 */
74 void SetForceMappedDspAddr(CpuAddr dsp_addr) {
75 dsp_address = dsp_addr;
76 }
77
78 /**
79 * Check whether this region has an active memory pool.
80 *
81 * @return True if this region has a mapped memory pool, otherwise false.
82 */
83 bool HasMappedMemoryPool() const {
84 return memory_pool != nullptr && memory_pool->GetDspAddress() != 0;
85 }
86
87 /**
88 * Check whether this region is mapped to the ADSP.
89 *
90 * @return True if this region is mapped, otherwise false.
91 */
92 bool IsMapped() const {
93 return HasMappedMemoryPool() || dsp_address != 0;
94 }
95
96 /**
97 * Get a usable reference to this region of memory.
98 *
99 * @param mark_in_use - Whether this region should be marked as being in use.
100 * @return A valid memory address if valid, otherwise 0.
101 */
102 CpuAddr GetReference(bool mark_in_use) {
103 if (!HasMappedMemoryPool()) {
104 return dsp_address;
105 }
106
107 if (mark_in_use) {
108 memory_pool->SetUsed(true);
109 }
110
111 return memory_pool->Translate(cpu_address, size);
112 }
113
114private:
115 /// CPU address of this region
116 CpuAddr cpu_address;
117 /// Size of this region
118 u64 size;
119 /// The memory this region is mapped to
120 MemoryPoolInfo* memory_pool;
121 /// ADSP address of this region
122 CpuAddr dsp_address;
123};
124
125} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/memory/memory_pool_info.cpp b/src/audio_core/renderer/memory/memory_pool_info.cpp
new file mode 100644
index 000000000..9b7824af1
--- /dev/null
+++ b/src/audio_core/renderer/memory/memory_pool_info.cpp
@@ -0,0 +1,61 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/memory/memory_pool_info.h"
5
6namespace AudioCore::AudioRenderer {
7
8CpuAddr MemoryPoolInfo::GetCpuAddress() const {
9 return cpu_address;
10}
11
12CpuAddr MemoryPoolInfo::GetDspAddress() const {
13 return dsp_address;
14}
15
16u64 MemoryPoolInfo::GetSize() const {
17 return size;
18}
19
20MemoryPoolInfo::Location MemoryPoolInfo::GetLocation() const {
21 return location;
22}
23
24void MemoryPoolInfo::SetCpuAddress(const CpuAddr address, const u64 size_) {
25 cpu_address = address;
26 size = size_;
27}
28
29void MemoryPoolInfo::SetDspAddress(const CpuAddr address) {
30 dsp_address = address;
31}
32
33bool MemoryPoolInfo::Contains(const CpuAddr address_, const u64 size_) const {
34 return cpu_address <= address_ && (address_ + size_) <= (cpu_address + size);
35}
36
37bool MemoryPoolInfo::IsMapped() const {
38 return dsp_address != 0;
39}
40
41CpuAddr MemoryPoolInfo::Translate(const CpuAddr address, const u64 size_) const {
42 if (!Contains(address, size_)) {
43 return 0;
44 }
45
46 if (!IsMapped()) {
47 return 0;
48 }
49
50 return dsp_address + (address - cpu_address);
51}
52
53void MemoryPoolInfo::SetUsed(const bool used) {
54 in_use = used;
55}
56
57bool MemoryPoolInfo::IsUsed() const {
58 return in_use;
59}
60
61} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/memory/memory_pool_info.h b/src/audio_core/renderer/memory/memory_pool_info.h
new file mode 100644
index 000000000..537a466ec
--- /dev/null
+++ b/src/audio_core/renderer/memory/memory_pool_info.h
@@ -0,0 +1,170 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <memory>
7
8#include "audio_core/common/common.h"
9#include "common/common_types.h"
10
11namespace AudioCore::AudioRenderer {
12/**
13 * CPU pools are mapped in user memory with the supplied process_handle (see PoolMapper).
14 */
15class MemoryPoolInfo {
16public:
17 /**
18 * The location of this pool.
19 * CPU pools are mapped in user memory with the supplied process_handle (see PoolMapper).
20 * DSP pools are mapped in the current process sysmodule.
21 */
22 enum class Location {
23 CPU = 1,
24 DSP = 2,
25 };
26
27 /**
28 * Current state of the pool
29 */
30 enum class State {
31 Invalid,
32 Aquired,
33 RequestDetach,
34 Detached,
35 RequestAttach,
36 Attached,
37 Released,
38 };
39
40 /**
41 * Result code for updating the pool (See InfoUpdater::Update)
42 */
43 enum class ResultState {
44 Success,
45 BadParam,
46 MapFailed,
47 InUse,
48 };
49
50 /**
51 * Input parameters coming from the game which are used to update current pools
52 * (See InfoUpdater::Update)
53 */
54 struct InParameter {
55 /* 0x00 */ u64 address;
56 /* 0x08 */ u64 size;
57 /* 0x10 */ State state;
58 /* 0x14 */ bool in_use;
59 /* 0x18 */ char unk18[0x8];
60 };
61 static_assert(sizeof(InParameter) == 0x20, "MemoryPoolInfo::InParameter has the wrong size!");
62
63 /**
64 * Output status sent back to the game on update (See InfoUpdater::Update)
65 */
66 struct OutStatus {
67 /* 0x00 */ State state;
68 /* 0x04 */ char unk04[0xC];
69 };
70 static_assert(sizeof(OutStatus) == 0x10, "MemoryPoolInfo::OutStatus has the wrong size!");
71
72 MemoryPoolInfo() = default;
73 MemoryPoolInfo(Location location_) : location{location_} {}
74
75 /**
76 * Get the CPU address for this pool.
77 *
78 * @return The CPU address of this pool.
79 */
80 CpuAddr GetCpuAddress() const;
81
82 /**
83 * Get the DSP address for this pool.
84 *
85 * @return The DSP address of this pool.
86 */
87 CpuAddr GetDspAddress() const;
88
89 /**
90 * Get the size of this pool.
91 *
92 * @return The size of this pool.
93 */
94 u64 GetSize() const;
95
96 /**
97 * Get the location of this pool.
98 *
99 * @return The location for the pool (see MemoryPoolInfo::Location).
100 */
101 Location GetLocation() const;
102
103 /**
104 * Set the CPU address for this pool.
105 *
106 * @param address - The new CPU address for this pool.
107 * @param size - The new size for this pool.
108 */
109 void SetCpuAddress(CpuAddr address, u64 size);
110
111 /**
112 * Set the DSP address for this pool.
113 *
114 * @param address - The new DSP address for this pool.
115 */
116 void SetDspAddress(CpuAddr address);
117
118 /**
119 * Check whether the pool contains a given range.
120 *
121 * @param address - The buffer address to look for.
122 * @param size - The size of the given buffer.
123 * @return True if the range is within this pool, otherwise false.
124 */
125 bool Contains(CpuAddr address, u64 size) const;
126
127 /**
128 * Check whether this pool is mapped, which is when the dsp address is set.
129 *
130 * @return True if the pool is mapped, otherwise false.
131 */
132 bool IsMapped() const;
133
134 /**
135 * Translates a given CPU range into a relative offset for the DSP.
136 *
137 * @param address - The buffer address to look for.
138 * @param size - The size of the given buffer.
139 * @return Pointer to the DSP-mapped memory.
140 */
141 CpuAddr Translate(CpuAddr address, u64 size) const;
142
143 /**
144 * Set or unset whether this memory pool is in use.
145 *
146 * @param used - Use state for this pool.
147 */
148 void SetUsed(bool used);
149
150 /**
151 * Get whether this pool is in use.
152 *
153 * @return True if in use, otherwise false.
154 */
155 bool IsUsed() const;
156
157private:
158 /// Base address for the CPU-side memory
159 CpuAddr cpu_address{};
160 /// Base address for the DSP-side memory
161 CpuAddr dsp_address{};
162 /// Size of this pool
163 u64 size{};
164 /// Location of this pool, either CPU or DSP
165 Location location{Location::DSP};
166 /// If this pool is in use
167 bool in_use{};
168};
169
170} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/memory/pool_mapper.cpp b/src/audio_core/renderer/memory/pool_mapper.cpp
new file mode 100644
index 000000000..2baf2ce08
--- /dev/null
+++ b/src/audio_core/renderer/memory/pool_mapper.cpp
@@ -0,0 +1,243 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/memory/address_info.h"
5#include "audio_core/renderer/memory/pool_mapper.h"
6#include "core/hle/kernel/k_process.h"
7#include "core/hle/kernel/svc.h"
8
9namespace AudioCore::AudioRenderer {
10
11PoolMapper::PoolMapper(u32 process_handle_, bool force_map_)
12 : process_handle{process_handle_}, force_map{force_map_} {}
13
14PoolMapper::PoolMapper(u32 process_handle_, std::span<MemoryPoolInfo> pool_infos_, u32 pool_count_,
15 bool force_map_)
16 : process_handle{process_handle_}, pool_infos{pool_infos_.data()},
17 pool_count{pool_count_}, force_map{force_map_} {}
18
19void PoolMapper::ClearUseState(std::span<MemoryPoolInfo> pools, const u32 count) {
20 for (u32 i = 0; i < count; i++) {
21 pools[i].SetUsed(false);
22 }
23}
24
25MemoryPoolInfo* PoolMapper::FindMemoryPool(MemoryPoolInfo* pools, const u64 count,
26 const CpuAddr address, const u64 size) const {
27 auto pool{pools};
28 for (u64 i = 0; i < count; i++, pool++) {
29 if (pool->Contains(address, size)) {
30 return pool;
31 }
32 }
33 return nullptr;
34}
35
36MemoryPoolInfo* PoolMapper::FindMemoryPool(const CpuAddr address, const u64 size) const {
37 auto pool{pool_infos};
38 for (u64 i = 0; i < pool_count; i++, pool++) {
39 if (pool->Contains(address, size)) {
40 return pool;
41 }
42 }
43 return nullptr;
44}
45
46bool PoolMapper::FillDspAddr(AddressInfo& address_info, MemoryPoolInfo* pools,
47 const u32 count) const {
48 if (address_info.GetCpuAddr() == 0) {
49 address_info.SetPool(nullptr);
50 return false;
51 }
52
53 auto found_pool{
54 FindMemoryPool(pools, count, address_info.GetCpuAddr(), address_info.GetSize())};
55 if (found_pool != nullptr) {
56 address_info.SetPool(found_pool);
57 return true;
58 }
59
60 if (force_map) {
61 address_info.SetForceMappedDspAddr(address_info.GetCpuAddr());
62 } else {
63 address_info.SetPool(nullptr);
64 }
65
66 return false;
67}
68
69bool PoolMapper::FillDspAddr(AddressInfo& address_info) const {
70 if (address_info.GetCpuAddr() == 0) {
71 address_info.SetPool(nullptr);
72 return false;
73 }
74
75 auto found_pool{FindMemoryPool(address_info.GetCpuAddr(), address_info.GetSize())};
76 if (found_pool != nullptr) {
77 address_info.SetPool(found_pool);
78 return true;
79 }
80
81 if (force_map) {
82 address_info.SetForceMappedDspAddr(address_info.GetCpuAddr());
83 } else {
84 address_info.SetPool(nullptr);
85 }
86
87 return false;
88}
89
90bool PoolMapper::TryAttachBuffer(BehaviorInfo::ErrorInfo& error_info, AddressInfo& address_info,
91 const CpuAddr address, const u64 size) const {
92 address_info.Setup(address, size);
93
94 if (!FillDspAddr(address_info)) {
95 error_info.error_code = Service::Audio::ERR_POOL_MAPPING_FAILED;
96 error_info.address = address;
97 return force_map;
98 }
99
100 error_info.error_code = ResultSuccess;
101 error_info.address = CpuAddr(0);
102 return true;
103}
104
105bool PoolMapper::IsForceMapEnabled() const {
106 return force_map;
107}
108
109u32 PoolMapper::GetProcessHandle(const MemoryPoolInfo* pool) const {
110 switch (pool->GetLocation()) {
111 case MemoryPoolInfo::Location::CPU:
112 return process_handle;
113 case MemoryPoolInfo::Location::DSP:
114 return Kernel::Svc::CurrentProcess;
115 }
116 LOG_WARNING(Service_Audio, "Invalid MemoryPoolInfo location!");
117 return Kernel::Svc::CurrentProcess;
118}
119
120bool PoolMapper::Map([[maybe_unused]] const u32 handle, [[maybe_unused]] const CpuAddr cpu_addr,
121 [[maybe_unused]] const u64 size) const {
122 // nn::audio::dsp::MapUserPointer(handle, cpu_addr, size);
123 return true;
124}
125
126bool PoolMapper::Map(MemoryPoolInfo& pool) const {
127 switch (pool.GetLocation()) {
128 case MemoryPoolInfo::Location::CPU:
129 // Map with process_handle
130 pool.SetDspAddress(pool.GetCpuAddress());
131 return true;
132 case MemoryPoolInfo::Location::DSP:
133 // Map with Kernel::Svc::CurrentProcess
134 pool.SetDspAddress(pool.GetCpuAddress());
135 return true;
136 default:
137 LOG_WARNING(Service_Audio, "Invalid MemoryPoolInfo location={}!",
138 static_cast<u32>(pool.GetLocation()));
139 return false;
140 }
141}
142
143bool PoolMapper::Unmap([[maybe_unused]] const u32 handle, [[maybe_unused]] const CpuAddr cpu_addr,
144 [[maybe_unused]] const u64 size) const {
145 // nn::audio::dsp::UnmapUserPointer(handle, cpu_addr, size);
146 return true;
147}
148
149bool PoolMapper::Unmap(MemoryPoolInfo& pool) const {
150 [[maybe_unused]] u32 handle{0};
151
152 switch (pool.GetLocation()) {
153 case MemoryPoolInfo::Location::CPU:
154 handle = process_handle;
155 break;
156 case MemoryPoolInfo::Location::DSP:
157 handle = Kernel::Svc::CurrentProcess;
158 break;
159 }
160 // nn::audio::dsp::UnmapUserPointer(handle, pool->cpu_address, pool->size);
161 pool.SetCpuAddress(0, 0);
162 pool.SetDspAddress(0);
163 return true;
164}
165
166void PoolMapper::ForceUnmapPointer(const AddressInfo& address_info) const {
167 if (force_map) {
168 [[maybe_unused]] auto found_pool{
169 FindMemoryPool(address_info.GetCpuAddr(), address_info.GetSize())};
170 // nn::audio::dsp::UnmapUserPointer(this->processHandle, address_info.GetCpuAddr(), 0);
171 }
172}
173
174MemoryPoolInfo::ResultState PoolMapper::Update(MemoryPoolInfo& pool,
175 const MemoryPoolInfo::InParameter& in_params,
176 MemoryPoolInfo::OutStatus& out_params) const {
177 if (in_params.state != MemoryPoolInfo::State::RequestAttach &&
178 in_params.state != MemoryPoolInfo::State::RequestDetach) {
179 return MemoryPoolInfo::ResultState::Success;
180 }
181
182 if (in_params.address == 0 || in_params.size == 0 || !Common::Is4KBAligned(in_params.address) ||
183 !Common::Is4KBAligned(in_params.size)) {
184 return MemoryPoolInfo::ResultState::BadParam;
185 }
186
187 switch (in_params.state) {
188 case MemoryPoolInfo::State::RequestAttach:
189 pool.SetCpuAddress(in_params.address, in_params.size);
190
191 Map(pool);
192
193 if (pool.IsMapped()) {
194 out_params.state = MemoryPoolInfo::State::Attached;
195 return MemoryPoolInfo::ResultState::Success;
196 }
197 pool.SetCpuAddress(0, 0);
198 return MemoryPoolInfo::ResultState::MapFailed;
199
200 case MemoryPoolInfo::State::RequestDetach:
201 if (pool.GetCpuAddress() != in_params.address || pool.GetSize() != in_params.size) {
202 return MemoryPoolInfo::ResultState::BadParam;
203 }
204
205 if (pool.IsUsed()) {
206 return MemoryPoolInfo::ResultState::InUse;
207 }
208
209 Unmap(pool);
210
211 pool.SetCpuAddress(0, 0);
212 pool.SetDspAddress(0);
213 out_params.state = MemoryPoolInfo::State::Detached;
214 return MemoryPoolInfo::ResultState::Success;
215
216 default:
217 LOG_ERROR(Service_Audio, "Invalid MemoryPoolInfo::State!");
218 break;
219 }
220
221 return MemoryPoolInfo::ResultState::Success;
222}
223
224bool PoolMapper::InitializeSystemPool(MemoryPoolInfo& pool, const u8* memory,
225 const u64 size_) const {
226 switch (pool.GetLocation()) {
227 case MemoryPoolInfo::Location::CPU:
228 return false;
229 case MemoryPoolInfo::Location::DSP:
230 pool.SetCpuAddress(reinterpret_cast<u64>(memory), size_);
231 if (Map(Kernel::Svc::CurrentProcess, reinterpret_cast<u64>(memory), size_)) {
232 pool.SetDspAddress(pool.GetCpuAddress());
233 return true;
234 }
235 return false;
236 default:
237 LOG_WARNING(Service_Audio, "Invalid MemoryPoolInfo location={}!",
238 static_cast<u32>(pool.GetLocation()));
239 return false;
240 }
241}
242
243} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/memory/pool_mapper.h b/src/audio_core/renderer/memory/pool_mapper.h
new file mode 100644
index 000000000..9a691da7a
--- /dev/null
+++ b/src/audio_core/renderer/memory/pool_mapper.h
@@ -0,0 +1,179 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <span>
7
8#include "audio_core/renderer/behavior/behavior_info.h"
9#include "audio_core/renderer/memory/memory_pool_info.h"
10#include "common/common_types.h"
11#include "core/hle/service/audio/errors.h"
12
13namespace AudioCore::AudioRenderer {
14class AddressInfo;
15
16/**
17 * Utility functions for managing MemoryPoolInfos
18 */
19class PoolMapper {
20public:
21 explicit PoolMapper(u32 process_handle, bool force_map);
22 explicit PoolMapper(u32 process_handle, std::span<MemoryPoolInfo> pool_infos, u32 pool_count,
23 bool force_map);
24
25 /**
26 * Clear the usage state for all given pools.
27 *
28 * @param pools - The memory pools to clear.
29 * @param count - The number of pools.
30 */
31 static void ClearUseState(std::span<MemoryPoolInfo> pools, u32 count);
32
33 /**
34 * Find the memory pool containing the given address and size from a given list of pools.
35 *
36 * @param pools - The memory pools to search within.
37 * @param count - The number of pools.
38 * @param address - The address of the region to find.
39 * @param size - The size of the region to find.
40 * @return Pointer to the memory pool if found, otherwise nullptr.
41 */
42 MemoryPoolInfo* FindMemoryPool(MemoryPoolInfo* pools, u64 count, CpuAddr address,
43 u64 size) const;
44
45 /**
46 * Find the memory pool containing the given address and size from the PoolMapper's memory pool.
47 *
48 * @param address - The address of the region to find.
49 * @param size - The size of the region to find.
50 * @return Pointer to the memory pool if found, otherwise nullptr.
51 */
52 MemoryPoolInfo* FindMemoryPool(CpuAddr address, u64 size) const;
53
54 /**
55 * Set the PoolMapper's memory pool to one in the given list of pools, which contains
56 * address_info.
57 *
58 * @param address_info - The expected region to find within pools.
59 * @param pools - The list of pools to search within.
60 * @param count - The number of pools given.
61 * @return True if successfully mapped, otherwise false.
62 */
63 bool FillDspAddr(AddressInfo& address_info, MemoryPoolInfo* pools, u32 count) const;
64
65 /**
66 * Set the PoolMapper's memory pool to the one containing address_info.
67 *
68 * @param address_info - The address to find the memory pool for.
69 * @return True if successfully mapped, otherwise false.
70 */
71 bool FillDspAddr(AddressInfo& address_info) const;
72
73 /**
74 * Try to attach a {address, size} region to the given address_info, and map it. Fills in the
75 * given error_info and address_info.
76 *
77 * @param error_info - Output error info.
78 * @param address_info - Output address info, initialized with the given {address, size} and
79 * attempted to map.
80 * @param address - Address of the region to map.
81 * @param size - Size of the region to map.
82 * @return True if successfully attached, otherwise false.
83 */
84 bool TryAttachBuffer(BehaviorInfo::ErrorInfo& error_info, AddressInfo& address_info,
85 CpuAddr address, u64 size) const;
86
87 /**
88 * Return whether force mapping is enabled.
89 *
90 * @return True if force mapping is enabled, otherwise false.
91 */
92 bool IsForceMapEnabled() const;
93
94 /**
95 * Get the process handle, depending on location.
96 *
97 * @param pool - The pool to check the location of.
98 * @return CurrentProcessHandle if location == DSP,
99 * the PoolMapper's process_handle if location == CPU
100 */
101 u32 GetProcessHandle(const MemoryPoolInfo* pool) const;
102
103 /**
104 * Map the given region with the given handle. This is a no-op.
105 *
106 * @param handle - The process handle to map to.
107 * @param cpu_addr - Address to map.
108 * @param size - Size to map.
109 * @return True if successfully mapped, otherwise false.
110 */
111 bool Map(u32 handle, CpuAddr cpu_addr, u64 size) const;
112
113 /**
114 * Map the given memory pool.
115 *
116 * @param pool - The pool to map.
117 * @return True if successfully mapped, otherwise false.
118 */
119 bool Map(MemoryPoolInfo& pool) const;
120
121 /**
122 * Unmap the given region with the given handle.
123 *
124 * @param handle - The process handle to unmap to.
125 * @param cpu_addr - Address to unmap.
126 * @param size - Size to unmap.
127 * @return True if successfully unmapped, otherwise false.
128 */
129 bool Unmap(u32 handle, CpuAddr cpu_addr, u64 size) const;
130
131 /**
132 * Unmap the given memory pool.
133 *
134 * @param pool - The pool to unmap.
135 * @return True if successfully unmapped, otherwise false.
136 */
137 bool Unmap(MemoryPoolInfo& pool) const;
138
139 /**
140 * Forcibly unmap the given region.
141 *
142 * @param address_info - The region to unmap.
143 */
144 void ForceUnmapPointer(const AddressInfo& address_info) const;
145
146 /**
147 * Update the given memory pool.
148 *
149 * @param pool - Pool to update.
150 * @param in_params - Input parameters for the update.
151 * @param out_params - Output parameters for the update.
152 * @return The result of the update. See MemoryPoolInfo::ResultState
153 */
154 MemoryPoolInfo::ResultState Update(MemoryPoolInfo& pool,
155 const MemoryPoolInfo::InParameter& in_params,
156 MemoryPoolInfo::OutStatus& out_params) const;
157
158 /**
159 * Initialize the PoolMapper's memory pool.
160 *
161 * @param pool - Input pool to initialize.
162 * @param memory - Pointer to the memory region for the pool.
163 * @param size - Size of the memory region for the pool.
164 * @return True if initialized successfully, otherwise false.
165 */
166 bool InitializeSystemPool(MemoryPoolInfo& pool, const u8* memory, u64 size) const;
167
168private:
169 /// Process handle for this mapper, used when location == CPU
170 u32 process_handle;
171 /// List of memory pools assigned to this mapper
172 MemoryPoolInfo* pool_infos{};
173 /// The number of pools
174 u64 pool_count{};
175 /// Is forced mapping enabled
176 bool force_map;
177};
178
179} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/mix/mix_context.cpp b/src/audio_core/renderer/mix/mix_context.cpp
new file mode 100644
index 000000000..2427c83ed
--- /dev/null
+++ b/src/audio_core/renderer/mix/mix_context.cpp
@@ -0,0 +1,141 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <ranges>
5
6#include "audio_core/renderer/mix/mix_context.h"
7#include "audio_core/renderer/splitter/splitter_context.h"
8
9namespace AudioCore::AudioRenderer {
10
11void MixContext::Initialize(std::span<MixInfo*> sorted_mix_infos_, std::span<MixInfo> mix_infos_,
12 const u32 count_, std::span<s32> effect_process_order_buffer_,
13 const u32 effect_count_, std::span<u8> node_states_workbuffer,
14 const u64 node_buffer_size, std::span<u8> edge_matrix_workbuffer,
15 const u64 edge_matrix_size) {
16 count = count_;
17 sorted_mix_infos = sorted_mix_infos_;
18 mix_infos = mix_infos_;
19 effect_process_order_buffer = effect_process_order_buffer_;
20 effect_count = effect_count_;
21
22 if (node_states_workbuffer.size() > 0 && edge_matrix_workbuffer.size() > 0) {
23 node_states.Initialize(node_states_workbuffer, node_buffer_size, count);
24 edge_matrix.Initialize(edge_matrix_workbuffer, edge_matrix_size, count);
25 }
26
27 for (s32 i = 0; i < count; i++) {
28 sorted_mix_infos[i] = &mix_infos[i];
29 }
30}
31
32MixInfo* MixContext::GetSortedInfo(const s32 index) {
33 return sorted_mix_infos[index];
34}
35
36void MixContext::SetSortedInfo(const s32 index, MixInfo& mix_info) {
37 sorted_mix_infos[index] = &mix_info;
38}
39
40MixInfo* MixContext::GetInfo(const s32 index) {
41 return &mix_infos[index];
42}
43
44MixInfo* MixContext::GetFinalMixInfo() {
45 return &mix_infos[0];
46}
47
48s32 MixContext::GetCount() const {
49 return count;
50}
51
52void MixContext::UpdateDistancesFromFinalMix() {
53 for (s32 i = 0; i < count; i++) {
54 mix_infos[i].distance_from_final_mix = InvalidDistanceFromFinalMix;
55 }
56
57 for (s32 i = 0; i < count; i++) {
58 auto& mix_info{mix_infos[i]};
59 sorted_mix_infos[i] = &mix_info;
60
61 if (!mix_info.in_use) {
62 continue;
63 }
64
65 auto mix_id{mix_info.mix_id};
66 auto distance_to_final_mix{FinalMixId};
67
68 while (distance_to_final_mix < count) {
69 if (mix_id == FinalMixId) {
70 break;
71 }
72
73 if (mix_id == UnusedMixId) {
74 distance_to_final_mix = InvalidDistanceFromFinalMix;
75 break;
76 }
77
78 auto distance_from_final_mix{mix_infos[mix_id].distance_from_final_mix};
79 if (distance_from_final_mix != InvalidDistanceFromFinalMix) {
80 distance_to_final_mix = distance_from_final_mix + 1;
81 break;
82 }
83
84 distance_to_final_mix++;
85 mix_id = mix_infos[mix_id].dst_mix_id;
86 }
87
88 if (distance_to_final_mix >= count) {
89 distance_to_final_mix = InvalidDistanceFromFinalMix;
90 }
91 mix_info.distance_from_final_mix = distance_to_final_mix;
92 }
93}
94
95void MixContext::SortInfo() {
96 UpdateDistancesFromFinalMix();
97
98 std::ranges::sort(sorted_mix_infos, [](const MixInfo* lhs, const MixInfo* rhs) {
99 return lhs->distance_from_final_mix > rhs->distance_from_final_mix;
100 });
101
102 CalcMixBufferOffset();
103}
104
105void MixContext::CalcMixBufferOffset() {
106 s16 offset{0};
107 for (s32 i = 0; i < count; i++) {
108 auto mix_info{sorted_mix_infos[i]};
109 if (mix_info->in_use) {
110 const auto buffer_count{mix_info->buffer_count};
111 mix_info->buffer_offset = offset;
112 offset += buffer_count;
113 }
114 }
115}
116
117bool MixContext::TSortInfo(const SplitterContext& splitter_context) {
118 if (!splitter_context.UsingSplitter()) {
119 CalcMixBufferOffset();
120 return true;
121 }
122
123 if (!node_states.Tsort(edge_matrix)) {
124 return false;
125 }
126
127 std::vector<s32> sorted_results{node_states.GetSortedResuls()};
128 const auto result_size{std::min(count, static_cast<s32>(sorted_results.size()))};
129 for (s32 i = 0; i < result_size; i++) {
130 sorted_mix_infos[i] = &mix_infos[sorted_results[i]];
131 }
132
133 CalcMixBufferOffset();
134 return true;
135}
136
137EdgeMatrix& MixContext::GetEdgeMatrix() {
138 return edge_matrix;
139}
140
141} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/mix/mix_context.h b/src/audio_core/renderer/mix/mix_context.h
new file mode 100644
index 000000000..da3aa2829
--- /dev/null
+++ b/src/audio_core/renderer/mix/mix_context.h
@@ -0,0 +1,124 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <span>
7
8#include "audio_core/renderer/mix/mix_info.h"
9#include "audio_core/renderer/nodes/edge_matrix.h"
10#include "audio_core/renderer/nodes/node_states.h"
11#include "common/common_types.h"
12
13namespace AudioCore::AudioRenderer {
14class SplitterContext;
15
16/*
17 * Manages mixing states, sorting and building a node graph to describe a mix order.
18 */
19class MixContext {
20public:
21 /**
22 * Initialize the mix context.
23 *
24 * @param sorted_mix_infos - Buffer for the sorted mix infos.
25 * @param mix_infos - Buffer for the mix infos.
26 * @param effect_process_order_buffer - Buffer for the effect process orders.
27 * @param effect_count - Number of effects in the buffer.
28 * @param node_states_workbuffer - Buffer for node states.
29 * @param node_buffer_size - Size of the node states buffer.
30 * @param edge_matrix_workbuffer - Buffer for edge matrix.
31 * @param edge_matrix_size - Size of the edge matrix buffer.
32 */
33 void Initialize(std::span<MixInfo*> sorted_mix_infos, std::span<MixInfo> mix_infos, u32 count_,
34 std::span<s32> effect_process_order_buffer, u32 effect_count,
35 std::span<u8> node_states_workbuffer, u64 node_buffer_size,
36 std::span<u8> edge_matrix_workbuffer, u64 edge_matrix_size);
37
38 /**
39 * Get a sorted mix at the given index.
40 *
41 * @param index - Index of sorted mix.
42 * @return The sorted mix.
43 */
44 MixInfo* GetSortedInfo(s32 index);
45
46 /**
47 * Set the sorted info at the given index.
48 *
49 * @param index - Index of sorted mix.
50 * @param mix_info - The new mix for this index.
51 */
52 void SetSortedInfo(s32 index, MixInfo& mix_info);
53
54 /**
55 * Get a mix at the given index.
56 *
57 * @param index - Index of mix.
58 * @return The mix.
59 */
60 MixInfo* GetInfo(s32 index);
61
62 /**
63 * Get the final mix.
64 *
65 * @return The final mix.
66 */
67 MixInfo* GetFinalMixInfo();
68
69 /**
70 * Get the current number of mixes.
71 *
72 * @return The number of active mixes.
73 */
74 s32 GetCount() const;
75
76 /**
77 * Update all of the mixes' distance from the final mix.
78 * Needs to be called after altering the mix graph.
79 */
80 void UpdateDistancesFromFinalMix();
81
82 /**
83 * Non-splitter sort, sorts the sorted mixes based on their distance from the final mix.
84 */
85 void SortInfo();
86
87 /**
88 * Re-calculate the mix buffer offsets for each mix after altering the mix.
89 */
90 void CalcMixBufferOffset();
91
92 /**
93 * Splitter sort, traverse the splitter node graph and sort the sorted mixes from results.
94 *
95 * @param splitter_context - Splitter context for the sort.
96 * @return True if the sort was successful, othewise false.
97 */
98 bool TSortInfo(const SplitterContext& splitter_context);
99
100 /**
101 * Get the edge matrix used for the mix graph.
102 *
103 * @return The edge matrix used.
104 */
105 EdgeMatrix& GetEdgeMatrix();
106
107private:
108 /// Array of sorted mixes
109 std::span<MixInfo*> sorted_mix_infos{};
110 /// Array of mixes
111 std::span<MixInfo> mix_infos{};
112 /// Number of active mixes
113 s32 count{};
114 /// Array of effect process orderings
115 std::span<s32> effect_process_order_buffer{};
116 /// Number of effects in the process ordering buffer
117 u64 effect_count{};
118 /// Node states used in splitter sort
119 NodeStates node_states{};
120 /// Edge matrix for connected nodes used in splitter sort
121 EdgeMatrix edge_matrix{};
122};
123
124} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/mix/mix_info.cpp b/src/audio_core/renderer/mix/mix_info.cpp
new file mode 100644
index 000000000..cc18e57ee
--- /dev/null
+++ b/src/audio_core/renderer/mix/mix_info.cpp
@@ -0,0 +1,120 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/behavior/behavior_info.h"
5#include "audio_core/renderer/effect/effect_context.h"
6#include "audio_core/renderer/mix/mix_info.h"
7#include "audio_core/renderer/nodes/edge_matrix.h"
8#include "audio_core/renderer/splitter/splitter_context.h"
9
10namespace AudioCore::AudioRenderer {
11
12MixInfo::MixInfo(std::span<s32> effect_order_buffer_, s32 effect_count_, BehaviorInfo& behavior)
13 : effect_order_buffer{effect_order_buffer_}, effect_count{effect_count_},
14 long_size_pre_delay_supported{behavior.IsLongSizePreDelaySupported()} {
15 ClearEffectProcessingOrder();
16}
17
18void MixInfo::Cleanup() {
19 mix_id = UnusedMixId;
20 dst_mix_id = UnusedMixId;
21 dst_splitter_id = UnusedSplitterId;
22}
23
24void MixInfo::ClearEffectProcessingOrder() {
25 for (s32 i = 0; i < effect_count; i++) {
26 effect_order_buffer[i] = -1;
27 }
28}
29
30bool MixInfo::Update(EdgeMatrix& edge_matrix, const InParameter& in_params,
31 EffectContext& effect_context, SplitterContext& splitter_context,
32 const BehaviorInfo& behavior) {
33 volume = in_params.volume;
34 sample_rate = in_params.sample_rate;
35 buffer_count = static_cast<s16>(in_params.buffer_count);
36 in_use = in_params.in_use;
37 mix_id = in_params.mix_id;
38 node_id = in_params.node_id;
39 mix_volumes = in_params.mix_volumes;
40
41 bool sort_required{false};
42 if (behavior.IsSplitterSupported()) {
43 sort_required = UpdateConnection(edge_matrix, in_params, splitter_context);
44 } else {
45 if (dst_mix_id != in_params.dest_mix_id) {
46 dst_mix_id = in_params.dest_mix_id;
47 sort_required = true;
48 }
49 dst_splitter_id = UnusedSplitterId;
50 }
51
52 ClearEffectProcessingOrder();
53
54 // Check all effects, and set their order if they belong to this mix.
55 const auto count{effect_context.GetCount()};
56 for (u32 i = 0; i < count; i++) {
57 const auto& info{effect_context.GetInfo(i)};
58 if (mix_id == info.GetMixId()) {
59 const auto processing_order{info.GetProcessingOrder()};
60 if (processing_order > effect_count) {
61 break;
62 }
63 effect_order_buffer[processing_order] = i;
64 }
65 }
66
67 return sort_required;
68}
69
70bool MixInfo::UpdateConnection(EdgeMatrix& edge_matrix, const InParameter& in_params,
71 SplitterContext& splitter_context) {
72 auto has_new_connection{false};
73 if (dst_splitter_id != UnusedSplitterId) {
74 auto& splitter_info{splitter_context.GetInfo(dst_splitter_id)};
75 has_new_connection = splitter_info.HasNewConnection();
76 }
77
78 // Check if this mix matches the input parameters.
79 // If everything is the same, don't bother updating.
80 if (dst_mix_id == in_params.dest_mix_id && dst_splitter_id == in_params.dest_splitter_id &&
81 !has_new_connection) {
82 return false;
83 }
84
85 // Reset the mix in the graph, as we're about to update it.
86 edge_matrix.RemoveEdges(mix_id);
87
88 if (in_params.dest_mix_id == UnusedMixId) {
89 if (in_params.dest_splitter_id != UnusedSplitterId) {
90 // If the splitter is used, connect this mix to each active destination.
91 auto& splitter_info{splitter_context.GetInfo(in_params.dest_splitter_id)};
92 auto const destination_count{splitter_info.GetDestinationCount()};
93
94 for (u32 i = 0; i < destination_count; i++) {
95 auto destination{
96 splitter_context.GetDesintationData(in_params.dest_splitter_id, i)};
97
98 if (destination) {
99 const auto destination_id{destination->GetMixId()};
100 if (destination_id != UnusedMixId) {
101 edge_matrix.Connect(mix_id, destination_id);
102 }
103 }
104 }
105 }
106 } else {
107 // If the splitter is not used, only connect this mix to its destination.
108 edge_matrix.Connect(mix_id, in_params.dest_mix_id);
109 }
110
111 dst_mix_id = in_params.dest_mix_id;
112 dst_splitter_id = in_params.dest_splitter_id;
113 return true;
114}
115
116bool MixInfo::HasAnyConnection() const {
117 return dst_mix_id != UnusedMixId || dst_splitter_id != UnusedSplitterId;
118}
119
120} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/mix/mix_info.h b/src/audio_core/renderer/mix/mix_info.h
new file mode 100644
index 000000000..b5fa4c0c7
--- /dev/null
+++ b/src/audio_core/renderer/mix/mix_info.h
@@ -0,0 +1,124 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7#include <span>
8
9#include "audio_core/common/common.h"
10#include "common/common_types.h"
11
12namespace AudioCore::AudioRenderer {
13class EdgeMatrix;
14class SplitterContext;
15class EffectContext;
16class BehaviorInfo;
17
18/**
19 * A single mix, which may feed through other mixes in a chain until reaching the final output mix.
20 */
21class MixInfo {
22public:
23 struct InParameter {
24 /* 0x000 */ f32 volume;
25 /* 0x004 */ u32 sample_rate;
26 /* 0x008 */ u32 buffer_count;
27 /* 0x00C */ bool in_use;
28 /* 0x00D */ bool is_dirty;
29 /* 0x010 */ s32 mix_id;
30 /* 0x014 */ u32 effect_count;
31 /* 0x018 */ s32 node_id;
32 /* 0x01C */ char unk01C[0x8];
33 /* 0x024 */ std::array<std::array<f32, MaxMixBuffers>, MaxMixBuffers> mix_volumes;
34 /* 0x924 */ s32 dest_mix_id;
35 /* 0x928 */ s32 dest_splitter_id;
36 /* 0x92C */ char unk92C[0x4];
37 };
38 static_assert(sizeof(InParameter) == 0x930, "MixInfo::InParameter has the wrong size!");
39
40 struct InDirtyParameter {
41 /* 0x00 */ u32 magic;
42 /* 0x04 */ s32 count;
43 /* 0x08 */ char unk08[0x18];
44 };
45 static_assert(sizeof(InDirtyParameter) == 0x20,
46 "MixInfo::InDirtyParameter has the wrong size!");
47
48 MixInfo(std::span<s32> effect_order_buffer, s32 effect_count, BehaviorInfo& behavior);
49
50 /**
51 * Clean up the mix, resetting it to a default state.
52 */
53 void Cleanup();
54
55 /**
56 * Clear the effect process order for all effects in this mix.
57 */
58 void ClearEffectProcessingOrder();
59
60 /**
61 * Update the mix according to the given parameters.
62 *
63 * @param edge_matrix - Updated with new splitter node connections, if supported.
64 * @param in_params - Input parameters.
65 * @param effect_context - Used to update the effect orderings.
66 * @param splitter_context - Used to update the mix graph if supported.
67 * @param behavior - Used for checking which features are supported.
68 * @return True if the mix was updated and a sort is required, otherwise false.
69 */
70 bool Update(EdgeMatrix& edge_matrix, const InParameter& in_params,
71 EffectContext& effect_context, SplitterContext& splitter_context,
72 const BehaviorInfo& behavior);
73
74 /**
75 * Update the mix's connection in the node graph according to the given parameters.
76 *
77 * @param edge_matrix - Updated with new splitter node connections, if supported.
78 * @param in_params - Input parameters.
79 * @param splitter_context - Used to update the mix graph if supported.
80 * @return True if the mix was updated and a sort is required, otherwise false.
81 */
82 bool UpdateConnection(EdgeMatrix& edge_matrix, const InParameter& in_params,
83 SplitterContext& splitter_context);
84
85 /**
86 * Check if this mix is connected to any other.
87 *
88 * @return True if the mix has a connection, otherwise false.
89 */
90 bool HasAnyConnection() const;
91
92 /// Volume of this mix
93 f32 volume{};
94 /// Sample rate of this mix
95 u32 sample_rate{};
96 /// Number of buffers in this mix
97 s16 buffer_count{};
98 /// Is this mix in use?
99 bool in_use{};
100 /// Is this mix enabled?
101 bool enabled{};
102 /// Id of this mix
103 s32 mix_id{UnusedMixId};
104 /// Node id of this mix
105 s32 node_id{};
106 /// Buffer offset for this mix
107 s16 buffer_offset{};
108 /// Distance to the final mix
109 s32 distance_from_final_mix{InvalidDistanceFromFinalMix};
110 /// Array of effect orderings of all effects in this mix
111 std::span<s32> effect_order_buffer;
112 /// Number of effects in this mix
113 const s32 effect_count;
114 /// Id for next mix in the chain
115 s32 dst_mix_id{UnusedMixId};
116 /// Mixing volumes for this mix used when this mix is chained with another
117 std::array<std::array<f32, MaxMixBuffers>, MaxMixBuffers> mix_volumes{};
118 /// Id for next mix in the graph when splitter is used
119 s32 dst_splitter_id{UnusedSplitterId};
120 /// Is a longer pre-delay time supported for the reverb effect?
121 const bool long_size_pre_delay_supported;
122};
123
124} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/nodes/bit_array.h b/src/audio_core/renderer/nodes/bit_array.h
new file mode 100644
index 000000000..b0d53cd51
--- /dev/null
+++ b/src/audio_core/renderer/nodes/bit_array.h
@@ -0,0 +1,25 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <vector>
7
8#include "common/common_types.h"
9
10namespace AudioCore::AudioRenderer {
11/**
12 * Represents an array of bits used for nodes and edges for the mixing graph.
13 */
14struct BitArray {
15 void reset() {
16 buffer.assign(buffer.size(), false);
17 }
18
19 /// Bits
20 std::vector<bool> buffer{};
21 /// Size of the buffer
22 u32 size{};
23};
24
25} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/nodes/edge_matrix.cpp b/src/audio_core/renderer/nodes/edge_matrix.cpp
new file mode 100644
index 000000000..5573f33b9
--- /dev/null
+++ b/src/audio_core/renderer/nodes/edge_matrix.cpp
@@ -0,0 +1,38 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/nodes/edge_matrix.h"
5
6namespace AudioCore::AudioRenderer {
7
8void EdgeMatrix::Initialize([[maybe_unused]] std::span<u8> buffer,
9 [[maybe_unused]] const u64 node_buffer_size, const u32 count_) {
10 count = count_;
11 edges.buffer.resize(count_ * count_);
12 edges.size = count_ * count_;
13 edges.reset();
14}
15
16bool EdgeMatrix::Connected(const u32 id, const u32 destination_id) const {
17 return edges.buffer[count * id + destination_id];
18}
19
20void EdgeMatrix::Connect(const u32 id, const u32 destination_id) {
21 edges.buffer[count * id + destination_id] = true;
22}
23
24void EdgeMatrix::Disconnect(const u32 id, const u32 destination_id) {
25 edges.buffer[count * id + destination_id] = false;
26}
27
28void EdgeMatrix::RemoveEdges(const u32 id) {
29 for (u32 dest = 0; dest < count; dest++) {
30 Disconnect(id, dest);
31 }
32}
33
34u32 EdgeMatrix::GetNodeCount() const {
35 return count;
36}
37
38} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/nodes/edge_matrix.h b/src/audio_core/renderer/nodes/edge_matrix.h
new file mode 100644
index 000000000..27a20e43e
--- /dev/null
+++ b/src/audio_core/renderer/nodes/edge_matrix.h
@@ -0,0 +1,82 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <span>
7
8#include "audio_core/renderer/nodes/bit_array.h"
9#include "common/alignment.h"
10#include "common/common_types.h"
11
12namespace AudioCore::AudioRenderer {
13/**
14 * An edge matrix, holding the connections for each node to every other node in the graph.
15 */
16class EdgeMatrix {
17public:
18 /**
19 * Calculate the size required for its workbuffer.
20 *
21 * @param count - The number of nodes in the graph.
22 * @return The required workbuffer size.
23 */
24 static u64 GetWorkBufferSize(u32 count) {
25 return Common::AlignUp(count * count, 0x40) / sizeof(u64);
26 }
27
28 /**
29 * Initialize this edge matrix.
30 *
31 * @param buffer - The workbuffer to use. Unused.
32 * @param node_buffer_size - The size of the workbuffer. Unused.
33 * @param count - The number of nodes in the graph.
34 */
35 void Initialize(std::span<u8> buffer, u64 node_buffer_size, u32 count);
36
37 /**
38 * Check if a node is connected to another.
39 *
40 * @param id - The node id to check.
41 * @param destination_id - Node id to check connection with.
42 */
43 bool Connected(u32 id, u32 destination_id) const;
44
45 /**
46 * Connect a node to another.
47 *
48 * @param id - The node id to connect.
49 * @param destination_id - Destination to connect it to.
50 */
51 void Connect(u32 id, u32 destination_id);
52
53 /**
54 * Disconnect a node from another.
55 *
56 * @param id - The node id to disconnect.
57 * @param destination_id - Destination to disconnect it from.
58 */
59 void Disconnect(u32 id, u32 destination_id);
60
61 /**
62 * Remove all connections for a given node.
63 *
64 * @param id - The node id to disconnect.
65 */
66 void RemoveEdges(u32 id);
67
68 /**
69 * Get the number of nodes in the graph.
70 *
71 * @return Number of nodes.
72 */
73 u32 GetNodeCount() const;
74
75private:
76 /// Edges for the current graph
77 BitArray edges;
78 /// Number of nodes (not edges) in the graph
79 u32 count;
80};
81
82} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/nodes/node_states.cpp b/src/audio_core/renderer/nodes/node_states.cpp
new file mode 100644
index 000000000..1821a51e6
--- /dev/null
+++ b/src/audio_core/renderer/nodes/node_states.cpp
@@ -0,0 +1,141 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/nodes/node_states.h"
5#include "common/logging/log.h"
6
7namespace AudioCore::AudioRenderer {
8
9void NodeStates::Initialize(std::span<u8> buffer_, [[maybe_unused]] const u64 node_buffer_size,
10 const u32 count) {
11 u64 num_blocks{Common::AlignUp(count, 0x40) / sizeof(u64)};
12 u64 offset{0};
13
14 node_count = count;
15
16 nodes_found.buffer.resize(count);
17 nodes_found.size = count;
18 nodes_found.reset();
19
20 offset += num_blocks;
21
22 nodes_complete.buffer.resize(count);
23 nodes_complete.size = count;
24 nodes_complete.reset();
25
26 offset += num_blocks;
27
28 results = {reinterpret_cast<u32*>(&buffer_[offset]), count};
29
30 offset += count * sizeof(u32);
31
32 stack.stack = {reinterpret_cast<u32*>(&buffer_[offset]), count * count};
33 stack.size = count * count;
34 stack.unk_10 = count * count;
35
36 offset += count * count * sizeof(u32);
37}
38
39bool NodeStates::Tsort(const EdgeMatrix& edge_matrix) {
40 return DepthFirstSearch(edge_matrix, stack);
41}
42
43bool NodeStates::DepthFirstSearch(const EdgeMatrix& edge_matrix, Stack& stack_) {
44 ResetState();
45
46 for (u32 node_id = 0; node_id < node_count; node_id++) {
47 if (GetState(node_id) == SearchState::Unknown) {
48 stack_.push(node_id);
49 }
50
51 while (stack_.Count() > 0) {
52 auto current_node{stack_.top()};
53 switch (GetState(current_node)) {
54 case SearchState::Unknown:
55 SetState(current_node, SearchState::Found);
56 break;
57 case SearchState::Found:
58 SetState(current_node, SearchState::Complete);
59 PushTsortResult(current_node);
60 stack_.pop();
61 continue;
62 case SearchState::Complete:
63 stack_.pop();
64 continue;
65 }
66
67 const auto edge_count{edge_matrix.GetNodeCount()};
68 for (u32 edge_id = 0; edge_id < edge_count; edge_id++) {
69 if (!edge_matrix.Connected(current_node, edge_id)) {
70 continue;
71 }
72
73 switch (GetState(edge_id)) {
74 case SearchState::Unknown:
75 stack_.push(edge_id);
76 break;
77 case SearchState::Found:
78 LOG_ERROR(Service_Audio,
79 "Cycle detected in the node graph, graph is not a DAG! "
80 "Bailing to avoid an infinite loop");
81 ResetState();
82 return false;
83 case SearchState::Complete:
84 break;
85 }
86 }
87 }
88 }
89
90 return true;
91}
92
93NodeStates::SearchState NodeStates::GetState(const u32 id) const {
94 if (nodes_found.buffer[id]) {
95 return SearchState::Found;
96 } else if (nodes_complete.buffer[id]) {
97 return SearchState::Complete;
98 }
99 return SearchState::Unknown;
100}
101
102void NodeStates::PushTsortResult(const u32 id) {
103 results[result_pos++] = id;
104}
105
106void NodeStates::SetState(const u32 id, const SearchState state) {
107 switch (state) {
108 case SearchState::Complete:
109 nodes_found.buffer[id] = false;
110 nodes_complete.buffer[id] = true;
111 break;
112 case SearchState::Found:
113 nodes_found.buffer[id] = true;
114 nodes_complete.buffer[id] = false;
115 break;
116 case SearchState::Unknown:
117 nodes_found.buffer[id] = false;
118 nodes_complete.buffer[id] = false;
119 break;
120 default:
121 LOG_ERROR(Service_Audio, "Unknown node SearchState {}", static_cast<u32>(state));
122 break;
123 }
124}
125
126void NodeStates::ResetState() {
127 nodes_found.reset();
128 nodes_complete.reset();
129 std::fill(results.begin(), results.end(), -1);
130 result_pos = 0;
131}
132
133u32 NodeStates::GetNodeCount() const {
134 return node_count;
135}
136
137std::vector<s32> NodeStates::GetSortedResuls() const {
138 return {results.rbegin(), results.rbegin() + result_pos};
139}
140
141} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/nodes/node_states.h b/src/audio_core/renderer/nodes/node_states.h
new file mode 100644
index 000000000..a1e0958a2
--- /dev/null
+++ b/src/audio_core/renderer/nodes/node_states.h
@@ -0,0 +1,195 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <span>
7#include <vector>
8
9#include "audio_core/renderer/nodes/edge_matrix.h"
10#include "common/alignment.h"
11#include "common/common_types.h"
12
13namespace AudioCore::AudioRenderer {
14/**
15 * Graph utility functions for sorting and getting results from the DAG.
16 */
17class NodeStates {
18 /**
19 * State of a node in the depth first search.
20 */
21 enum class SearchState {
22 Unknown,
23 Found,
24 Complete,
25 };
26
27 /**
28 * Stack used for a depth first search.
29 */
30 struct Stack {
31 /**
32 * Calculate the workbuffer size required for this stack.
33 *
34 * @param count - Maximum number of nodes for the stack.
35 * @return Required buffer size.
36 */
37 static u32 CalcBufferSize(u32 count) {
38 return count * sizeof(u32);
39 }
40
41 /**
42 * Reset the stack back to default.
43 *
44 * @param buffer_ - The new buffer to use.
45 * @param size_ - The size of the new buffer.
46 */
47 void Reset(u32* buffer_, u32 size_) {
48 stack = {buffer_, size_};
49 size = size_;
50 pos = 0;
51 unk_10 = size_;
52 }
53
54 /**
55 * Get the current stack position.
56 *
57 * @return The current stack position.
58 */
59 u32 Count() {
60 return pos;
61 }
62
63 /**
64 * Push a new node to the stack.
65 *
66 * @param data - The node to push.
67 */
68 void push(u32 data) {
69 stack[pos++] = data;
70 }
71
72 /**
73 * Pop a node from the stack.
74 *
75 * @return The node on the top of the stack.
76 */
77 u32 pop() {
78 return stack[--pos];
79 }
80
81 /**
82 * Get the top of the stack without popping.
83 *
84 * @return The node on the top of the stack.
85 */
86 u32 top() {
87 return stack[pos - 1];
88 }
89
90 /// Buffer for the stack
91 std::span<u32> stack{};
92 /// Size of the stack buffer
93 u32 size{};
94 /// Current stack position
95 u32 pos{};
96 /// Unknown
97 u32 unk_10{};
98 };
99
100public:
101 /**
102 * Calculate the workbuffer size required for the node states.
103 *
104 * @param count - The number of nodes.
105 * @return The required workbuffer size.
106 */
107 static u64 GetWorkBufferSize(u32 count) {
108 return (Common::AlignUp(count, 0x40) / sizeof(u64)) * 2 + count * sizeof(BitArray) +
109 count * Stack::CalcBufferSize(count);
110 }
111
112 /**
113 * Initialize the node states.
114 *
115 * @param buffer - The workbuffer to use. Unused.
116 * @param node_buffer_size - The size of the workbuffer. Unused.
117 * @param count - The number of nodes in the graph.
118 */
119 void Initialize(std::span<u8> nodes, u64 node_buffer_size, u32 count);
120
121 /**
122 * Sort the graph. Only calls DepthFirstSearch.
123 *
124 * @param edge_matrix - The edge matrix used to hold the connections between nodes.
125 * @return True if the sort was successful, otherwise false.
126 */
127 bool Tsort(const EdgeMatrix& edge_matrix);
128
129 /**
130 * Sort the graph via depth first search.
131 *
132 * @param edge_matrix - The edge matrix used to hold the connections between nodes.
133 * @param stack - The stack used for pushing and popping nodes.
134 * @return True if the sort was successful, otherwise false.
135 */
136 bool DepthFirstSearch(const EdgeMatrix& edge_matrix, Stack& stack);
137
138 /**
139 * Get the search state of a given node.
140 *
141 * @param id - The node id to check.
142 * @return The node's search state. See SearchState
143 */
144 SearchState GetState(u32 id) const;
145
146 /**
147 * Push a node id to the results buffer when found in the DFS.
148 *
149 * @param id - The node id to push.
150 */
151 void PushTsortResult(u32 id);
152
153 /**
154 * Set the state of a node.
155 *
156 * @param id - The node id to alter.
157 * @param state - The new search state.
158 */
159 void SetState(u32 id, SearchState state);
160
161 /**
162 * Reset the nodes found, complete and the results.
163 */
164 void ResetState();
165
166 /**
167 * Get the number of nodes in the graph.
168 *
169 * @return The number of nodes.
170 */
171 u32 GetNodeCount() const;
172
173 /**
174 * Get the sorted results from the DFS.
175 *
176 * @return Vector of nodes in reverse order.
177 */
178 std::vector<s32> GetSortedResuls() const;
179
180private:
181 /// Number of nodes in the graph
182 u32 node_count{};
183 /// Position in results buffer
184 u32 result_pos{};
185 /// List of nodes found
186 BitArray nodes_found{};
187 /// List of nodes completed
188 BitArray nodes_complete{};
189 /// List of results from the depth first search
190 std::span<u32> results{};
191 /// Stack used during the depth first search
192 Stack stack{};
193};
194
195} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/performance/detail_aspect.cpp b/src/audio_core/renderer/performance/detail_aspect.cpp
new file mode 100644
index 000000000..f6405937f
--- /dev/null
+++ b/src/audio_core/renderer/performance/detail_aspect.cpp
@@ -0,0 +1,25 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/command/command_buffer.h"
5#include "audio_core/renderer/command/command_generator.h"
6#include "audio_core/renderer/performance/detail_aspect.h"
7
8namespace AudioCore::AudioRenderer {
9
10DetailAspect::DetailAspect(CommandGenerator& command_generator_,
11 const PerformanceEntryType entry_type, const s32 node_id_,
12 const PerformanceDetailType detail_type)
13 : command_generator{command_generator_}, node_id{node_id_} {
14 auto perf_manager{command_generator.GetPerformanceManager()};
15 if (perf_manager != nullptr && perf_manager->IsInitialized() &&
16 perf_manager->IsDetailTarget(node_id) &&
17 perf_manager->GetNextEntry(performance_entry_address, detail_type, entry_type, node_id)) {
18 command_generator.GeneratePerformanceCommand(node_id, PerformanceState::Start,
19 performance_entry_address);
20
21 initialized = true;
22 }
23}
24
25} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/performance/detail_aspect.h b/src/audio_core/renderer/performance/detail_aspect.h
new file mode 100644
index 000000000..ee4ac2f76
--- /dev/null
+++ b/src/audio_core/renderer/performance/detail_aspect.h
@@ -0,0 +1,33 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "audio_core/renderer/performance/performance_entry_addresses.h"
7#include "audio_core/renderer/performance/performance_manager.h"
8#include "common/common_types.h"
9
10namespace AudioCore::AudioRenderer {
11class CommandGenerator;
12
13/**
14 * Holds detailed information about performance metrics, filled in by the AudioRenderer during
15 * Performance commands.
16 */
17class DetailAspect {
18public:
19 DetailAspect() = default;
20 DetailAspect(CommandGenerator& command_generator, PerformanceEntryType entry_type, s32 node_id,
21 PerformanceDetailType detail_type);
22
23 /// Command generator the command will be generated into
24 CommandGenerator& command_generator;
25 /// Addresses to be filled by the AudioRenderer
26 PerformanceEntryAddresses performance_entry_address{};
27 /// Is this detail aspect initialized?
28 bool initialized{};
29 /// Node id of this aspect
30 s32 node_id;
31};
32
33} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/performance/entry_aspect.cpp b/src/audio_core/renderer/performance/entry_aspect.cpp
new file mode 100644
index 000000000..dd4165803
--- /dev/null
+++ b/src/audio_core/renderer/performance/entry_aspect.cpp
@@ -0,0 +1,23 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/command/command_buffer.h"
5#include "audio_core/renderer/command/command_generator.h"
6#include "audio_core/renderer/performance/entry_aspect.h"
7
8namespace AudioCore::AudioRenderer {
9
10EntryAspect::EntryAspect(CommandGenerator& command_generator_, const PerformanceEntryType type,
11 const s32 node_id_)
12 : command_generator{command_generator_}, node_id{node_id_} {
13 auto perf_manager{command_generator.GetPerformanceManager()};
14 if (perf_manager != nullptr && perf_manager->IsInitialized() &&
15 perf_manager->GetNextEntry(performance_entry_address, type, node_id)) {
16 command_generator.GeneratePerformanceCommand(node_id, PerformanceState::Start,
17 performance_entry_address);
18
19 initialized = true;
20 }
21}
22
23} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/performance/entry_aspect.h b/src/audio_core/renderer/performance/entry_aspect.h
new file mode 100644
index 000000000..01c1eb3f1
--- /dev/null
+++ b/src/audio_core/renderer/performance/entry_aspect.h
@@ -0,0 +1,32 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "audio_core/renderer/performance/performance_entry_addresses.h"
7#include "audio_core/renderer/performance/performance_manager.h"
8#include "common/common_types.h"
9
10namespace AudioCore::AudioRenderer {
11class CommandGenerator;
12
13/**
14 * Holds entry information about performance metrics, filled in by the AudioRenderer during
15 * Performance commands.
16 */
17class EntryAspect {
18public:
19 EntryAspect() = default;
20 EntryAspect(CommandGenerator& command_generator, PerformanceEntryType type, s32 node_id);
21
22 /// Command generator the command will be generated into
23 CommandGenerator& command_generator;
24 /// Addresses to be filled by the AudioRenderer
25 PerformanceEntryAddresses performance_entry_address{};
26 /// Is this detail aspect initialized?
27 bool initialized{};
28 /// Node id of this aspect
29 s32 node_id;
30};
31
32} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/performance/performance_detail.h b/src/audio_core/renderer/performance/performance_detail.h
new file mode 100644
index 000000000..3a4897e60
--- /dev/null
+++ b/src/audio_core/renderer/performance/performance_detail.h
@@ -0,0 +1,50 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "audio_core/renderer/performance/performance_entry.h"
7#include "common/common_types.h"
8
9namespace AudioCore::AudioRenderer {
10
11enum class PerformanceDetailType : u8 {
12 Invalid,
13 Unk1,
14 Unk2,
15 Unk3,
16 Unk4,
17 Unk5,
18 Unk6,
19 Unk7,
20 Unk8,
21 Unk9,
22 Unk10,
23 Unk11,
24 Unk12,
25 Unk13,
26};
27
28struct PerformanceDetailVersion1 {
29 /* 0x00 */ u32 node_id;
30 /* 0x04 */ u32 start_time;
31 /* 0x08 */ u32 processed_time;
32 /* 0x0C */ PerformanceDetailType detail_type;
33 /* 0x0D */ PerformanceEntryType entry_type;
34};
35static_assert(sizeof(PerformanceDetailVersion1) == 0x10,
36 "PerformanceDetailVersion1 has the worng size!");
37
38struct PerformanceDetailVersion2 {
39 /* 0x00 */ u32 node_id;
40 /* 0x04 */ u32 start_time;
41 /* 0x08 */ u32 processed_time;
42 /* 0x0C */ PerformanceDetailType detail_type;
43 /* 0x0D */ PerformanceEntryType entry_type;
44 /* 0x10 */ u32 unk_10;
45 /* 0x14 */ char unk14[0x4];
46};
47static_assert(sizeof(PerformanceDetailVersion2) == 0x18,
48 "PerformanceDetailVersion2 has the worng size!");
49
50} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/performance/performance_entry.h b/src/audio_core/renderer/performance/performance_entry.h
new file mode 100644
index 000000000..d1b21406b
--- /dev/null
+++ b/src/audio_core/renderer/performance/performance_entry.h
@@ -0,0 +1,37 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "common/common_types.h"
7
8namespace AudioCore::AudioRenderer {
9
10enum class PerformanceEntryType : u8 {
11 Invalid,
12 Voice,
13 SubMix,
14 FinalMix,
15 Sink,
16};
17
18struct PerformanceEntryVersion1 {
19 /* 0x00 */ u32 node_id;
20 /* 0x04 */ u32 start_time;
21 /* 0x08 */ u32 processed_time;
22 /* 0x0C */ PerformanceEntryType entry_type;
23};
24static_assert(sizeof(PerformanceEntryVersion1) == 0x10,
25 "PerformanceEntryVersion1 has the worng size!");
26
27struct PerformanceEntryVersion2 {
28 /* 0x00 */ u32 node_id;
29 /* 0x04 */ u32 start_time;
30 /* 0x08 */ u32 processed_time;
31 /* 0x0C */ PerformanceEntryType entry_type;
32 /* 0x0D */ char unk0D[0xB];
33};
34static_assert(sizeof(PerformanceEntryVersion2) == 0x18,
35 "PerformanceEntryVersion2 has the worng size!");
36
37} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/performance/performance_entry_addresses.h b/src/audio_core/renderer/performance/performance_entry_addresses.h
new file mode 100644
index 000000000..e381d765c
--- /dev/null
+++ b/src/audio_core/renderer/performance/performance_entry_addresses.h
@@ -0,0 +1,17 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "audio_core/common/common.h"
7
8namespace AudioCore::AudioRenderer {
9
10struct PerformanceEntryAddresses {
11 CpuAddr translated_address;
12 CpuAddr entry_start_time_offset;
13 CpuAddr header_entry_count_offset;
14 CpuAddr entry_processed_time_offset;
15};
16
17} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/performance/performance_frame_header.h b/src/audio_core/renderer/performance/performance_frame_header.h
new file mode 100644
index 000000000..707cc0afb
--- /dev/null
+++ b/src/audio_core/renderer/performance/performance_frame_header.h
@@ -0,0 +1,36 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "common/common_types.h"
7
8namespace AudioCore::AudioRenderer {
9
10struct PerformanceFrameHeaderVersion1 {
11 /* 0x00 */ u32 magic; // "PERF"
12 /* 0x04 */ u32 entry_count;
13 /* 0x08 */ u32 detail_count;
14 /* 0x0C */ u32 next_offset;
15 /* 0x10 */ u32 total_processing_time;
16 /* 0x14 */ u32 frame_index;
17};
18static_assert(sizeof(PerformanceFrameHeaderVersion1) == 0x18,
19 "PerformanceFrameHeaderVersion1 has the worng size!");
20
21struct PerformanceFrameHeaderVersion2 {
22 /* 0x00 */ u32 magic; // "PERF"
23 /* 0x04 */ u32 entry_count;
24 /* 0x08 */ u32 detail_count;
25 /* 0x0C */ u32 next_offset;
26 /* 0x10 */ u32 total_processing_time;
27 /* 0x14 */ u32 voices_dropped;
28 /* 0x18 */ u64 start_time;
29 /* 0x20 */ u32 frame_index;
30 /* 0x24 */ bool render_time_exceeded;
31 /* 0x25 */ char unk25[0xB];
32};
33static_assert(sizeof(PerformanceFrameHeaderVersion2) == 0x30,
34 "PerformanceFrameHeaderVersion2 has the worng size!");
35
36} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/performance/performance_manager.cpp b/src/audio_core/renderer/performance/performance_manager.cpp
new file mode 100644
index 000000000..fd5873e1e
--- /dev/null
+++ b/src/audio_core/renderer/performance/performance_manager.cpp
@@ -0,0 +1,645 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/behavior/behavior_info.h"
5#include "audio_core/renderer/memory/memory_pool_info.h"
6#include "audio_core/renderer/performance/performance_manager.h"
7#include "common/common_funcs.h"
8
9namespace AudioCore::AudioRenderer {
10
11void PerformanceManager::CreateImpl(const size_t version) {
12 switch (version) {
13 case 1:
14 impl = std::make_unique<
15 PerformanceManagerImpl<PerformanceVersion::Version1, PerformanceFrameHeaderVersion1,
16 PerformanceEntryVersion1, PerformanceDetailVersion1>>();
17 break;
18 case 2:
19 impl = std::make_unique<
20 PerformanceManagerImpl<PerformanceVersion::Version2, PerformanceFrameHeaderVersion2,
21 PerformanceEntryVersion2, PerformanceDetailVersion2>>();
22 break;
23 default:
24 LOG_WARNING(Service_Audio, "Invalid PerformanceMetricsDataFormat {}, creating version 1",
25 static_cast<u32>(version));
26 impl = std::make_unique<
27 PerformanceManagerImpl<PerformanceVersion::Version1, PerformanceFrameHeaderVersion1,
28 PerformanceEntryVersion1, PerformanceDetailVersion1>>();
29 }
30}
31
32void PerformanceManager::Initialize(std::span<u8> workbuffer, const u64 workbuffer_size,
33 const AudioRendererParameterInternal& params,
34 const BehaviorInfo& behavior,
35 const MemoryPoolInfo& memory_pool) {
36 CreateImpl(behavior.GetPerformanceMetricsDataFormat());
37 impl->Initialize(workbuffer, workbuffer_size, params, behavior, memory_pool);
38}
39
40bool PerformanceManager::IsInitialized() const {
41 if (impl) {
42 return impl->IsInitialized();
43 }
44 return false;
45}
46
47u32 PerformanceManager::CopyHistories(u8* out_buffer, u64 out_size) {
48 if (impl) {
49 return impl->CopyHistories(out_buffer, out_size);
50 }
51 return 0;
52}
53
54bool PerformanceManager::GetNextEntry(PerformanceEntryAddresses& addresses, u32** unk,
55 const PerformanceSysDetailType sys_detail_type,
56 const s32 node_id) {
57 if (impl) {
58 return impl->GetNextEntry(addresses, unk, sys_detail_type, node_id);
59 }
60 return false;
61}
62
63bool PerformanceManager::GetNextEntry(PerformanceEntryAddresses& addresses,
64 const PerformanceEntryType entry_type, const s32 node_id) {
65 if (impl) {
66 return impl->GetNextEntry(addresses, entry_type, node_id);
67 }
68 return false;
69}
70
71bool PerformanceManager::GetNextEntry(PerformanceEntryAddresses& addresses,
72 const PerformanceDetailType detail_type,
73 const PerformanceEntryType entry_type, const s32 node_id) {
74 if (impl) {
75 return impl->GetNextEntry(addresses, detail_type, entry_type, node_id);
76 }
77 return false;
78}
79
80void PerformanceManager::TapFrame(const bool dsp_behind, const u32 voices_dropped,
81 const u64 rendering_start_tick) {
82 if (impl) {
83 impl->TapFrame(dsp_behind, voices_dropped, rendering_start_tick);
84 }
85}
86
87bool PerformanceManager::IsDetailTarget(const u32 target_node_id) const {
88 if (impl) {
89 return impl->IsDetailTarget(target_node_id);
90 }
91 return false;
92}
93
94void PerformanceManager::SetDetailTarget(const u32 target_node_id) {
95 if (impl) {
96 impl->SetDetailTarget(target_node_id);
97 }
98}
99
100template <>
101void PerformanceManagerImpl<
102 PerformanceVersion::Version1, PerformanceFrameHeaderVersion1, PerformanceEntryVersion1,
103 PerformanceDetailVersion1>::Initialize(std::span<u8> workbuffer_, const u64 workbuffer_size,
104 const AudioRendererParameterInternal& params,
105 const BehaviorInfo& behavior,
106 const MemoryPoolInfo& memory_pool) {
107 workbuffer = workbuffer_;
108 entries_per_frame = params.voices + params.effects + params.sinks + params.sub_mixes + 1;
109 max_detail_count = MaxDetailEntries;
110 frame_size = GetRequiredBufferSizeForPerformanceMetricsPerFrame(behavior, params);
111 const auto frame_count{static_cast<u32>(workbuffer_size / frame_size)};
112 max_frames = frame_count - 1;
113 translated_buffer = memory_pool.Translate(CpuAddr(workbuffer.data()), workbuffer_size);
114
115 // The first frame is the "current" frame we're writing to.
116 auto buffer_offset{workbuffer.data()};
117 frame_header = reinterpret_cast<PerformanceFrameHeaderVersion1*>(buffer_offset);
118 buffer_offset += sizeof(PerformanceFrameHeaderVersion1);
119 entry_buffer = {reinterpret_cast<PerformanceEntryVersion1*>(buffer_offset), entries_per_frame};
120 buffer_offset += entries_per_frame * sizeof(PerformanceEntryVersion1);
121 detail_buffer = {reinterpret_cast<PerformanceDetailVersion1*>(buffer_offset), max_detail_count};
122
123 // After the current, is a ringbuffer of history frames, the current frame will be copied here
124 // before a new frame is written.
125 frame_history = std::span<u8>(workbuffer.data() + frame_size, workbuffer_size - frame_size);
126
127 // If there's room for any history frames.
128 if (frame_count >= 2) {
129 buffer_offset = frame_history.data();
130 frame_history_header = reinterpret_cast<PerformanceFrameHeaderVersion1*>(buffer_offset);
131 buffer_offset += sizeof(PerformanceFrameHeaderVersion1);
132 frame_history_entries = {reinterpret_cast<PerformanceEntryVersion1*>(buffer_offset),
133 entries_per_frame};
134 buffer_offset += entries_per_frame * sizeof(PerformanceEntryVersion1);
135 frame_history_details = {reinterpret_cast<PerformanceDetailVersion1*>(buffer_offset),
136 max_detail_count};
137 } else {
138 frame_history_header = {};
139 frame_history_entries = {};
140 frame_history_details = {};
141 }
142
143 target_node_id = 0;
144 version = PerformanceVersion(behavior.GetPerformanceMetricsDataFormat());
145 entry_count = 0;
146 detail_count = 0;
147 frame_header->entry_count = 0;
148 frame_header->detail_count = 0;
149 output_frame_index = 0;
150 last_output_frame_index = 0;
151 is_initialized = true;
152}
153
154template <>
155bool PerformanceManagerImpl<PerformanceVersion::Version1, PerformanceFrameHeaderVersion1,
156 PerformanceEntryVersion1, PerformanceDetailVersion1>::IsInitialized()
157 const {
158 return is_initialized;
159}
160
161template <>
162u32 PerformanceManagerImpl<PerformanceVersion::Version1, PerformanceFrameHeaderVersion1,
163 PerformanceEntryVersion1,
164 PerformanceDetailVersion1>::CopyHistories(u8* out_buffer, u64 out_size) {
165 if (out_buffer == nullptr || out_size == 0 || !is_initialized) {
166 return 0;
167 }
168
169 // Are there any new frames waiting to be output?
170 if (last_output_frame_index == output_frame_index) {
171 return 0;
172 }
173
174 PerformanceFrameHeaderVersion1* out_header{nullptr};
175 u32 out_history_size{0};
176
177 while (last_output_frame_index != output_frame_index) {
178 PerformanceFrameHeaderVersion1* history_header{nullptr};
179 std::span<PerformanceEntryVersion1> history_entries{};
180 std::span<PerformanceDetailVersion1> history_details{};
181
182 if (max_frames > 0) {
183 auto frame_offset{&frame_history[last_output_frame_index * frame_size]};
184 history_header = reinterpret_cast<PerformanceFrameHeaderVersion1*>(frame_offset);
185 frame_offset += sizeof(PerformanceFrameHeaderVersion1);
186 history_entries = {reinterpret_cast<PerformanceEntryVersion1*>(frame_offset),
187 history_header->entry_count};
188 frame_offset += entries_per_frame * sizeof(PerformanceFrameHeaderVersion1);
189 history_details = {reinterpret_cast<PerformanceDetailVersion1*>(frame_offset),
190 history_header->detail_count};
191 } else {
192 // Original code does not break here, but will crash when trying to dereference the
193 // header in the next if, so let's just skip this frame and continue...
194 // Hopefully this will not happen.
195 LOG_WARNING(Service_Audio,
196 "max_frames should not be 0! Skipping frame to avoid a crash");
197 last_output_frame_index++;
198 continue;
199 }
200
201 if (out_size < history_header->entry_count * sizeof(PerformanceEntryVersion1) +
202 history_header->detail_count * sizeof(PerformanceDetailVersion1) +
203 2 * sizeof(PerformanceFrameHeaderVersion1)) {
204 break;
205 }
206
207 u32 out_offset{sizeof(PerformanceFrameHeaderVersion1)};
208 auto out_entries{std::span<PerformanceEntryVersion1>(
209 reinterpret_cast<PerformanceEntryVersion1*>(out_buffer + out_offset),
210 history_header->entry_count)};
211 u32 out_entry_count{0};
212 u32 total_processing_time{0};
213 for (auto& history_entry : history_entries) {
214 if (history_entry.processed_time > 0 || history_entry.start_time > 0) {
215 out_entries[out_entry_count++] = history_entry;
216 total_processing_time += history_entry.processed_time;
217 }
218 }
219
220 out_offset += static_cast<u32>(out_entry_count * sizeof(PerformanceEntryVersion1));
221 auto out_details{std::span<PerformanceDetailVersion1>(
222 reinterpret_cast<PerformanceDetailVersion1*>(out_buffer + out_offset),
223 history_header->detail_count)};
224 u32 out_detail_count{0};
225 for (auto& history_detail : history_details) {
226 if (history_detail.processed_time > 0 || history_detail.start_time > 0) {
227 out_details[out_detail_count++] = history_detail;
228 }
229 }
230
231 out_offset += static_cast<u32>(out_detail_count * sizeof(PerformanceDetailVersion1));
232 out_header = reinterpret_cast<PerformanceFrameHeaderVersion1*>(out_buffer);
233 out_header->magic = Common::MakeMagic('P', 'E', 'R', 'F');
234 out_header->entry_count = out_entry_count;
235 out_header->detail_count = out_detail_count;
236 out_header->next_offset = out_offset;
237 out_header->total_processing_time = total_processing_time;
238 out_header->frame_index = history_header->frame_index;
239
240 out_history_size += out_offset;
241
242 out_buffer += out_offset;
243 out_size -= out_offset;
244 last_output_frame_index = (last_output_frame_index + 1) % max_frames;
245 }
246
247 // We're out of frames to output, so if there's enough left in the output buffer for another
248 // header, and we output at least 1 frame, set the next header to null.
249 if (out_size > sizeof(PerformanceFrameHeaderVersion1) && out_header != nullptr) {
250 std::memset(out_buffer, 0, sizeof(PerformanceFrameHeaderVersion1));
251 }
252
253 return out_history_size;
254}
255
256template <>
257bool PerformanceManagerImpl<PerformanceVersion::Version1, PerformanceFrameHeaderVersion1,
258 PerformanceEntryVersion1, PerformanceDetailVersion1>::
259 GetNextEntry([[maybe_unused]] PerformanceEntryAddresses& addresses, [[maybe_unused]] u32** unk,
260 [[maybe_unused]] PerformanceSysDetailType sys_detail_type,
261 [[maybe_unused]] s32 node_id) {
262 return false;
263}
264
265template <>
266bool PerformanceManagerImpl<
267 PerformanceVersion::Version1, PerformanceFrameHeaderVersion1, PerformanceEntryVersion1,
268 PerformanceDetailVersion1>::GetNextEntry(PerformanceEntryAddresses& addresses,
269 const PerformanceEntryType entry_type,
270 const s32 node_id) {
271 if (!is_initialized) {
272 return false;
273 }
274
275 addresses.translated_address = translated_buffer;
276 addresses.header_entry_count_offset = CpuAddr(frame_header) - CpuAddr(workbuffer.data()) +
277 offsetof(PerformanceFrameHeaderVersion1, entry_count);
278
279 auto entry{&entry_buffer[entry_count++]};
280 addresses.entry_start_time_offset = CpuAddr(entry) - CpuAddr(workbuffer.data()) +
281 offsetof(PerformanceEntryVersion1, start_time);
282 addresses.entry_processed_time_offset = CpuAddr(entry) - CpuAddr(workbuffer.data()) +
283 offsetof(PerformanceEntryVersion1, processed_time);
284
285 std::memset(entry, 0, sizeof(PerformanceEntryVersion1));
286 entry->node_id = node_id;
287 entry->entry_type = entry_type;
288 return true;
289}
290
291template <>
292bool PerformanceManagerImpl<
293 PerformanceVersion::Version1, PerformanceFrameHeaderVersion1, PerformanceEntryVersion1,
294 PerformanceDetailVersion1>::GetNextEntry(PerformanceEntryAddresses& addresses,
295 const PerformanceDetailType detail_type,
296 const PerformanceEntryType entry_type,
297 const s32 node_id) {
298 if (!is_initialized || detail_count > MaxDetailEntries) {
299 return false;
300 }
301
302 auto detail{&detail_buffer[detail_count++]};
303
304 addresses.translated_address = translated_buffer;
305 addresses.header_entry_count_offset = CpuAddr(frame_header) - CpuAddr(workbuffer.data()) +
306 offsetof(PerformanceFrameHeaderVersion1, detail_count);
307 addresses.entry_start_time_offset = CpuAddr(detail) - CpuAddr(workbuffer.data()) +
308 offsetof(PerformanceDetailVersion1, start_time);
309 addresses.entry_processed_time_offset = CpuAddr(detail) - CpuAddr(workbuffer.data()) +
310 offsetof(PerformanceDetailVersion1, processed_time);
311
312 std::memset(detail, 0, sizeof(PerformanceDetailVersion1));
313 detail->node_id = node_id;
314 detail->entry_type = entry_type;
315 detail->detail_type = detail_type;
316 return true;
317}
318
319template <>
320void PerformanceManagerImpl<
321 PerformanceVersion::Version1, PerformanceFrameHeaderVersion1, PerformanceEntryVersion1,
322 PerformanceDetailVersion1>::TapFrame([[maybe_unused]] bool dsp_behind,
323 [[maybe_unused]] u32 voices_dropped,
324 [[maybe_unused]] u64 rendering_start_tick) {
325 if (!is_initialized) {
326 return;
327 }
328
329 if (max_frames > 0) {
330 if (!frame_history.empty() && !workbuffer.empty()) {
331 auto history_frame = reinterpret_cast<PerformanceFrameHeaderVersion1*>(
332 &frame_history[output_frame_index * frame_size]);
333 std::memcpy(history_frame, workbuffer.data(), frame_size);
334 history_frame->frame_index = history_frame_index++;
335 }
336 output_frame_index = (output_frame_index + 1) % max_frames;
337 }
338
339 entry_count = 0;
340 detail_count = 0;
341 frame_header->entry_count = 0;
342 frame_header->detail_count = 0;
343}
344
345template <>
346bool PerformanceManagerImpl<
347 PerformanceVersion::Version1, PerformanceFrameHeaderVersion1, PerformanceEntryVersion1,
348 PerformanceDetailVersion1>::IsDetailTarget(const u32 target_node_id_) const {
349 return target_node_id == target_node_id_;
350}
351
352template <>
353void PerformanceManagerImpl<PerformanceVersion::Version1, PerformanceFrameHeaderVersion1,
354 PerformanceEntryVersion1,
355 PerformanceDetailVersion1>::SetDetailTarget(const u32 target_node_id_) {
356 target_node_id = target_node_id_;
357}
358
359template <>
360void PerformanceManagerImpl<
361 PerformanceVersion::Version2, PerformanceFrameHeaderVersion2, PerformanceEntryVersion2,
362 PerformanceDetailVersion2>::Initialize(std::span<u8> workbuffer_, const u64 workbuffer_size,
363 const AudioRendererParameterInternal& params,
364 const BehaviorInfo& behavior,
365 const MemoryPoolInfo& memory_pool) {
366 workbuffer = workbuffer_;
367 entries_per_frame = params.voices + params.effects + params.sinks + params.sub_mixes + 1;
368 max_detail_count = MaxDetailEntries;
369 frame_size = GetRequiredBufferSizeForPerformanceMetricsPerFrame(behavior, params);
370 const auto frame_count{static_cast<u32>(workbuffer_size / frame_size)};
371 max_frames = frame_count - 1;
372 translated_buffer = memory_pool.Translate(CpuAddr(workbuffer.data()), workbuffer_size);
373
374 // The first frame is the "current" frame we're writing to.
375 auto buffer_offset{workbuffer.data()};
376 frame_header = reinterpret_cast<PerformanceFrameHeaderVersion2*>(buffer_offset);
377 buffer_offset += sizeof(PerformanceFrameHeaderVersion2);
378 entry_buffer = {reinterpret_cast<PerformanceEntryVersion2*>(buffer_offset), entries_per_frame};
379 buffer_offset += entries_per_frame * sizeof(PerformanceEntryVersion2);
380 detail_buffer = {reinterpret_cast<PerformanceDetailVersion2*>(buffer_offset), max_detail_count};
381
382 // After the current, is a ringbuffer of history frames, the current frame will be copied here
383 // before a new frame is written.
384 frame_history = std::span<u8>(workbuffer.data() + frame_size, workbuffer_size - frame_size);
385
386 // If there's room for any history frames.
387 if (frame_count >= 2) {
388 buffer_offset = frame_history.data();
389 frame_history_header = reinterpret_cast<PerformanceFrameHeaderVersion2*>(buffer_offset);
390 buffer_offset += sizeof(PerformanceFrameHeaderVersion2);
391 frame_history_entries = {reinterpret_cast<PerformanceEntryVersion2*>(buffer_offset),
392 entries_per_frame};
393 buffer_offset += entries_per_frame * sizeof(PerformanceEntryVersion2);
394 frame_history_details = {reinterpret_cast<PerformanceDetailVersion2*>(buffer_offset),
395 max_detail_count};
396 } else {
397 frame_history_header = {};
398 frame_history_entries = {};
399 frame_history_details = {};
400 }
401
402 target_node_id = 0;
403 version = PerformanceVersion(behavior.GetPerformanceMetricsDataFormat());
404 entry_count = 0;
405 detail_count = 0;
406 frame_header->entry_count = 0;
407 frame_header->detail_count = 0;
408 output_frame_index = 0;
409 last_output_frame_index = 0;
410 is_initialized = true;
411}
412
413template <>
414bool PerformanceManagerImpl<PerformanceVersion::Version2, PerformanceFrameHeaderVersion2,
415 PerformanceEntryVersion2, PerformanceDetailVersion2>::IsInitialized()
416 const {
417 return is_initialized;
418}
419
420template <>
421u32 PerformanceManagerImpl<PerformanceVersion::Version2, PerformanceFrameHeaderVersion2,
422 PerformanceEntryVersion2,
423 PerformanceDetailVersion2>::CopyHistories(u8* out_buffer, u64 out_size) {
424 if (out_buffer == nullptr || out_size == 0 || !is_initialized) {
425 return 0;
426 }
427
428 // Are there any new frames waiting to be output?
429 if (last_output_frame_index == output_frame_index) {
430 return 0;
431 }
432
433 PerformanceFrameHeaderVersion2* out_header{nullptr};
434 u32 out_history_size{0};
435
436 while (last_output_frame_index != output_frame_index) {
437 PerformanceFrameHeaderVersion2* history_header{nullptr};
438 std::span<PerformanceEntryVersion2> history_entries{};
439 std::span<PerformanceDetailVersion2> history_details{};
440
441 if (max_frames > 0) {
442 auto frame_offset{&frame_history[last_output_frame_index * frame_size]};
443 history_header = reinterpret_cast<PerformanceFrameHeaderVersion2*>(frame_offset);
444 frame_offset += sizeof(PerformanceFrameHeaderVersion2);
445 history_entries = {reinterpret_cast<PerformanceEntryVersion2*>(frame_offset),
446 history_header->entry_count};
447 frame_offset += entries_per_frame * sizeof(PerformanceFrameHeaderVersion2);
448 history_details = {reinterpret_cast<PerformanceDetailVersion2*>(frame_offset),
449 history_header->detail_count};
450 } else {
451 // Original code does not break here, but will crash when trying to dereference the
452 // header in the next if, so let's just skip this frame and continue...
453 // Hopefully this will not happen.
454 LOG_WARNING(Service_Audio,
455 "max_frames should not be 0! Skipping frame to avoid a crash");
456 last_output_frame_index++;
457 continue;
458 }
459
460 if (out_size < history_header->entry_count * sizeof(PerformanceEntryVersion2) +
461 history_header->detail_count * sizeof(PerformanceDetailVersion2) +
462 2 * sizeof(PerformanceFrameHeaderVersion2)) {
463 break;
464 }
465
466 u32 out_offset{sizeof(PerformanceFrameHeaderVersion2)};
467 auto out_entries{std::span<PerformanceEntryVersion2>(
468 reinterpret_cast<PerformanceEntryVersion2*>(out_buffer + out_offset),
469 history_header->entry_count)};
470 u32 out_entry_count{0};
471 u32 total_processing_time{0};
472 for (auto& history_entry : history_entries) {
473 if (history_entry.processed_time > 0 || history_entry.start_time > 0) {
474 out_entries[out_entry_count++] = history_entry;
475 total_processing_time += history_entry.processed_time;
476 }
477 }
478
479 out_offset += static_cast<u32>(out_entry_count * sizeof(PerformanceEntryVersion2));
480 auto out_details{std::span<PerformanceDetailVersion2>(
481 reinterpret_cast<PerformanceDetailVersion2*>(out_buffer + out_offset),
482 history_header->detail_count)};
483 u32 out_detail_count{0};
484 for (auto& history_detail : history_details) {
485 if (history_detail.processed_time > 0 || history_detail.start_time > 0) {
486 out_details[out_detail_count++] = history_detail;
487 }
488 }
489
490 out_offset += static_cast<u32>(out_detail_count * sizeof(PerformanceDetailVersion2));
491 out_header = reinterpret_cast<PerformanceFrameHeaderVersion2*>(out_buffer);
492 out_header->magic = Common::MakeMagic('P', 'E', 'R', 'F');
493 out_header->entry_count = out_entry_count;
494 out_header->detail_count = out_detail_count;
495 out_header->next_offset = out_offset;
496 out_header->total_processing_time = total_processing_time;
497 out_header->voices_dropped = history_header->voices_dropped;
498 out_header->start_time = history_header->start_time;
499 out_header->frame_index = history_header->frame_index;
500 out_header->render_time_exceeded = history_header->render_time_exceeded;
501
502 out_history_size += out_offset;
503
504 out_buffer += out_offset;
505 out_size -= out_offset;
506 last_output_frame_index = (last_output_frame_index + 1) % max_frames;
507 }
508
509 // We're out of frames to output, so if there's enough left in the output buffer for another
510 // header, and we output at least 1 frame, set the next header to null.
511 if (out_size > sizeof(PerformanceFrameHeaderVersion2) && out_header != nullptr) {
512 std::memset(out_buffer, 0, sizeof(PerformanceFrameHeaderVersion2));
513 }
514
515 return out_history_size;
516}
517
518template <>
519bool PerformanceManagerImpl<
520 PerformanceVersion::Version2, PerformanceFrameHeaderVersion2, PerformanceEntryVersion2,
521 PerformanceDetailVersion2>::GetNextEntry(PerformanceEntryAddresses& addresses, u32** unk,
522 const PerformanceSysDetailType sys_detail_type,
523 const s32 node_id) {
524 if (!is_initialized || detail_count > MaxDetailEntries) {
525 return false;
526 }
527
528 auto detail{&detail_buffer[detail_count++]};
529
530 addresses.translated_address = translated_buffer;
531 addresses.header_entry_count_offset = CpuAddr(frame_header) - CpuAddr(workbuffer.data()) +
532 offsetof(PerformanceFrameHeaderVersion2, detail_count);
533 addresses.entry_start_time_offset = CpuAddr(detail) - CpuAddr(workbuffer.data()) +
534 offsetof(PerformanceDetailVersion2, start_time);
535 addresses.entry_processed_time_offset = CpuAddr(detail) - CpuAddr(workbuffer.data()) +
536 offsetof(PerformanceDetailVersion2, processed_time);
537
538 std::memset(detail, 0, sizeof(PerformanceDetailVersion2));
539 detail->node_id = node_id;
540 detail->detail_type = static_cast<PerformanceDetailType>(sys_detail_type);
541
542 if (unk) {
543 *unk = &detail->unk_10;
544 }
545 return true;
546}
547
548template <>
549bool PerformanceManagerImpl<
550 PerformanceVersion::Version2, PerformanceFrameHeaderVersion2, PerformanceEntryVersion2,
551 PerformanceDetailVersion2>::GetNextEntry(PerformanceEntryAddresses& addresses,
552 const PerformanceEntryType entry_type,
553 const s32 node_id) {
554 if (!is_initialized) {
555 return false;
556 }
557
558 auto entry{&entry_buffer[entry_count++]};
559
560 addresses.translated_address = translated_buffer;
561 addresses.header_entry_count_offset = CpuAddr(frame_header) - CpuAddr(workbuffer.data()) +
562 offsetof(PerformanceFrameHeaderVersion2, entry_count);
563 addresses.entry_start_time_offset = CpuAddr(entry) - CpuAddr(workbuffer.data()) +
564 offsetof(PerformanceEntryVersion2, start_time);
565 addresses.entry_processed_time_offset = CpuAddr(entry) - CpuAddr(workbuffer.data()) +
566 offsetof(PerformanceEntryVersion2, processed_time);
567
568 std::memset(entry, 0, sizeof(PerformanceEntryVersion2));
569 entry->node_id = node_id;
570 entry->entry_type = entry_type;
571 return true;
572}
573
574template <>
575bool PerformanceManagerImpl<
576 PerformanceVersion::Version2, PerformanceFrameHeaderVersion2, PerformanceEntryVersion2,
577 PerformanceDetailVersion2>::GetNextEntry(PerformanceEntryAddresses& addresses,
578 const PerformanceDetailType detail_type,
579 const PerformanceEntryType entry_type,
580 const s32 node_id) {
581 if (!is_initialized || detail_count > MaxDetailEntries) {
582 return false;
583 }
584
585 auto detail{&detail_buffer[detail_count++]};
586
587 addresses.translated_address = translated_buffer;
588 addresses.header_entry_count_offset = CpuAddr(frame_header) - CpuAddr(workbuffer.data()) +
589 offsetof(PerformanceFrameHeaderVersion2, detail_count);
590 addresses.entry_start_time_offset = CpuAddr(detail) - CpuAddr(workbuffer.data()) +
591 offsetof(PerformanceDetailVersion2, start_time);
592 addresses.entry_processed_time_offset = CpuAddr(detail) - CpuAddr(workbuffer.data()) +
593 offsetof(PerformanceDetailVersion2, processed_time);
594
595 std::memset(detail, 0, sizeof(PerformanceDetailVersion2));
596 detail->node_id = node_id;
597 detail->entry_type = entry_type;
598 detail->detail_type = detail_type;
599 return true;
600}
601
602template <>
603void PerformanceManagerImpl<PerformanceVersion::Version2, PerformanceFrameHeaderVersion2,
604 PerformanceEntryVersion2,
605 PerformanceDetailVersion2>::TapFrame(const bool dsp_behind,
606 const u32 voices_dropped,
607 const u64 rendering_start_tick) {
608 if (!is_initialized) {
609 return;
610 }
611
612 if (max_frames > 0) {
613 if (!frame_history.empty() && !workbuffer.empty()) {
614 auto history_frame{reinterpret_cast<PerformanceFrameHeaderVersion2*>(
615 &frame_history[output_frame_index * frame_size])};
616 std::memcpy(history_frame, workbuffer.data(), frame_size);
617 history_frame->render_time_exceeded = dsp_behind;
618 history_frame->voices_dropped = voices_dropped;
619 history_frame->start_time = rendering_start_tick;
620 history_frame->frame_index = history_frame_index++;
621 }
622 output_frame_index = (output_frame_index + 1) % max_frames;
623 }
624
625 entry_count = 0;
626 detail_count = 0;
627 frame_header->entry_count = 0;
628 frame_header->detail_count = 0;
629}
630
631template <>
632bool PerformanceManagerImpl<
633 PerformanceVersion::Version2, PerformanceFrameHeaderVersion2, PerformanceEntryVersion2,
634 PerformanceDetailVersion2>::IsDetailTarget(const u32 target_node_id_) const {
635 return target_node_id == target_node_id_;
636}
637
638template <>
639void PerformanceManagerImpl<PerformanceVersion::Version2, PerformanceFrameHeaderVersion2,
640 PerformanceEntryVersion2,
641 PerformanceDetailVersion2>::SetDetailTarget(const u32 target_node_id_) {
642 target_node_id = target_node_id_;
643}
644
645} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/performance/performance_manager.h b/src/audio_core/renderer/performance/performance_manager.h
new file mode 100644
index 000000000..b82176bef
--- /dev/null
+++ b/src/audio_core/renderer/performance/performance_manager.h
@@ -0,0 +1,273 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <chrono>
7#include <memory>
8#include <span>
9
10#include "audio_core/common/audio_renderer_parameter.h"
11#include "audio_core/renderer/performance/performance_detail.h"
12#include "audio_core/renderer/performance/performance_entry.h"
13#include "audio_core/renderer/performance/performance_entry_addresses.h"
14#include "audio_core/renderer/performance/performance_frame_header.h"
15#include "common/common_types.h"
16
17namespace AudioCore::AudioRenderer {
18class BehaviorInfo;
19class MemoryPoolInfo;
20
21enum class PerformanceVersion {
22 Version1,
23 Version2,
24};
25
26enum class PerformanceSysDetailType {
27 PcmInt16 = 15,
28 PcmFloat = 16,
29 Adpcm = 17,
30 LightLimiter = 37,
31};
32
33enum class PerformanceState {
34 Invalid,
35 Start,
36 Stop,
37};
38
39/**
40 * Manages performance information.
41 *
42 * The performance buffer is split into frames, each comprised of:
43 * Frame header - Information about the number of entries/details and some others
44 * Entries - Created when starting to generate types of commands, such as voice
45 * commands, mix commands, sink commands etc. Details - Created for specific commands
46 * within each group. Up to MaxDetailEntries per frame.
47 *
48 * A current frame is written to by the AudioRenderer, and before it processes the next command
49 * list, the current frame is copied to a ringbuffer of history frames. These frames are then
50 * output back to the game if it supplies a performance buffer to RequestUpdate.
51 *
52 * Two versions currently exist, version 2 adds a few extra fields to the header, and a new
53 * SysDetail type which is seemingly unused.
54 */
55class PerformanceManager {
56public:
57 static constexpr size_t MaxDetailEntries = 100;
58
59 struct InParameter {
60 /* 0x00 */ s32 target_node_id;
61 /* 0x04 */ char unk04[0xC];
62 };
63 static_assert(sizeof(InParameter) == 0x10,
64 "PerformanceManager::InParameter has the wrong size!");
65
66 struct OutStatus {
67 /* 0x00 */ s32 history_size;
68 /* 0x04 */ char unk04[0xC];
69 };
70 static_assert(sizeof(OutStatus) == 0x10, "PerformanceManager::OutStatus has the wrong size!");
71
72 /**
73 * Calculate the required size for the performance workbuffer.
74 *
75 * @param behavior - Check which version is supported.
76 * @param params - Input parameters.
77 * @return Required workbuffer size.
78 */
79 static u64 GetRequiredBufferSizeForPerformanceMetricsPerFrame(
80 const BehaviorInfo& behavior, const AudioRendererParameterInternal& params) {
81 u64 entry_count{params.voices + params.effects + params.sub_mixes + params.sinks + 1};
82 switch (behavior.GetPerformanceMetricsDataFormat()) {
83 case 1:
84 return sizeof(PerformanceFrameHeaderVersion1) +
85 PerformanceManager::MaxDetailEntries * sizeof(PerformanceDetailVersion1) +
86 entry_count * sizeof(PerformanceEntryVersion1);
87 case 2:
88 return sizeof(PerformanceFrameHeaderVersion2) +
89 PerformanceManager::MaxDetailEntries * sizeof(PerformanceDetailVersion2) +
90 entry_count * sizeof(PerformanceEntryVersion2);
91 }
92
93 LOG_WARNING(Service_Audio, "Invalid PerformanceMetrics version, assuming version 1");
94 return sizeof(PerformanceFrameHeaderVersion1) +
95 PerformanceManager::MaxDetailEntries * sizeof(PerformanceDetailVersion1) +
96 entry_count * sizeof(PerformanceEntryVersion1);
97 }
98
99 virtual ~PerformanceManager() = default;
100
101 /**
102 * Initialize the performance manager.
103 *
104 * @param workbuffer - Workbuffer to use for performance frames.
105 * @param workbuffer_size - Size of the workbuffer.
106 * @param params - Input parameters.
107 * @param behavior - Behaviour to check version and data format.
108 * @param memory_pool - Used to translate the workbuffer address for the DSP.
109 */
110 virtual void Initialize(std::span<u8> workbuffer, u64 workbuffer_size,
111 const AudioRendererParameterInternal& params,
112 const BehaviorInfo& behavior, const MemoryPoolInfo& memory_pool);
113
114 /**
115 * Check if the manager is initialized.
116 *
117 * @return True if initialized, otherwise false.
118 */
119 virtual bool IsInitialized() const;
120
121 /**
122 * Copy the waiting performance frames to the output buffer.
123 *
124 * @param out_buffer - Output buffer to store performance frames.
125 * @param out_size - Size of the output buffer.
126 * @return Size in bytes that were written to the buffer.
127 */
128 virtual u32 CopyHistories(u8* out_buffer, u64 out_size);
129
130 /**
131 * Setup a new sys detail in the current frame, filling in addresses with offsets to the
132 * current workbuffer, to be written by the AudioRenderer. Note: This version is
133 * unused/incomplete.
134 *
135 * @param addresses - Filled with pointers to the new entry, which should be passed to
136 * the AudioRenderer with Performance commands to be written.
137 * @param unk - Unknown.
138 * @param sys_detail_type - Sys detail type.
139 * @param node_id - Node id for this entry.
140 * @return True if a new entry was created and the offsets are valid, otherwise false.
141 */
142 virtual bool GetNextEntry(PerformanceEntryAddresses& addresses, u32** unk,
143 PerformanceSysDetailType sys_detail_type, s32 node_id);
144
145 /**
146 * Setup a new entry in the current frame, filling in addresses with offsets to the current
147 * workbuffer, to be written by the AudioRenderer.
148 *
149 * @param addresses - Filled with pointers to the new entry, which should be passed to
150 * the AudioRenderer with Performance commands to be written.
151 * @param entry_type - The type of this entry. See PerformanceEntryType
152 * @param node_id - Node id for this entry.
153 * @return True if a new entry was created and the offsets are valid, otherwise false.
154 */
155 virtual bool GetNextEntry(PerformanceEntryAddresses& addresses, PerformanceEntryType entry_type,
156 s32 node_id);
157
158 /**
159 * Setup a new detail in the current frame, filling in addresses with offsets to the current
160 * workbuffer, to be written by the AudioRenderer.
161 *
162 * @param addresses - Filled with pointers to the new detail, which should be passed
163 * to the AudioRenderer with Performance commands to be written.
164 * @param entry_type - The type of this detail. See PerformanceEntryType
165 * @param node_id - Node id for this detail.
166 * @return True if a new detail was created and the offsets are valid, otherwise false.
167 */
168 virtual bool GetNextEntry(PerformanceEntryAddresses& addresses,
169 PerformanceDetailType detail_type, PerformanceEntryType entry_type,
170 s32 node_id);
171
172 /**
173 * Save the current frame to the ring buffer.
174 *
175 * @param dsp_behind - Did the AudioRenderer fall behind and not
176 * finish processing the command list?
177 * @param voices_dropped - The number of voices that were dropped.
178 * @param rendering_start_tick - The tick rendering started.
179 */
180 virtual void TapFrame(bool dsp_behind, u32 voices_dropped, u64 rendering_start_tick);
181
182 /**
183 * Check if the node id is a detail type.
184 *
185 * @return True if the node is a detail type, otherwise false.
186 */
187 virtual bool IsDetailTarget(u32 target_node_id) const;
188
189 /**
190 * Set the given node to be a detail type.
191 *
192 * @param target_node_id - Node to set.
193 */
194 virtual void SetDetailTarget(u32 target_node_id);
195
196private:
197 /**
198 * Create the performance manager.
199 *
200 * @param version - Performance version to create.
201 */
202 void CreateImpl(size_t version);
203
204 std::unique_ptr<PerformanceManager>
205 /// Impl for the performance manager, may be version 1 or 2.
206 impl;
207};
208
209template <PerformanceVersion Version, typename FrameHeaderVersion, typename EntryVersion,
210 typename DetailVersion>
211class PerformanceManagerImpl : public PerformanceManager {
212public:
213 void Initialize(std::span<u8> workbuffer, u64 workbuffer_size,
214 const AudioRendererParameterInternal& params, const BehaviorInfo& behavior,
215 const MemoryPoolInfo& memory_pool) override;
216 bool IsInitialized() const override;
217 u32 CopyHistories(u8* out_buffer, u64 out_size) override;
218 bool GetNextEntry(PerformanceEntryAddresses& addresses, u32** unk,
219 PerformanceSysDetailType sys_detail_type, s32 node_id) override;
220 bool GetNextEntry(PerformanceEntryAddresses& addresses, PerformanceEntryType entry_type,
221 s32 node_id) override;
222 bool GetNextEntry(PerformanceEntryAddresses& addresses, PerformanceDetailType detail_type,
223 PerformanceEntryType entry_type, s32 node_id) override;
224 void TapFrame(bool dsp_behind, u32 voices_dropped, u64 rendering_start_tick) override;
225 bool IsDetailTarget(u32 target_node_id) const override;
226 void SetDetailTarget(u32 target_node_id) override;
227
228private:
229 /// Workbuffer used to store the current performance frame
230 std::span<u8> workbuffer{};
231 /// DSP address of the workbuffer, used by the AudioRenderer
232 CpuAddr translated_buffer{};
233 /// Current frame index
234 u32 history_frame_index{};
235 /// Current frame header
236 FrameHeaderVersion* frame_header{};
237 /// Current frame entry buffer
238 std::span<EntryVersion> entry_buffer{};
239 /// Current frame detail buffer
240 std::span<DetailVersion> detail_buffer{};
241 /// Current frame entry count
242 u32 entry_count{};
243 /// Current frame detail count
244 u32 detail_count{};
245 /// Ringbuffer of previous frames
246 std::span<u8> frame_history{};
247 /// Current history frame header
248 FrameHeaderVersion* frame_history_header{};
249 /// Current history entry buffer
250 std::span<EntryVersion> frame_history_entries{};
251 /// Current history detail buffer
252 std::span<DetailVersion> frame_history_details{};
253 /// Current history ringbuffer write index
254 u32 output_frame_index{};
255 /// Last history frame index that was written back to the game
256 u32 last_output_frame_index{};
257 /// Maximum number of history frames in the ringbuffer
258 u32 max_frames{};
259 /// Number of entries per frame
260 u32 entries_per_frame{};
261 /// Maximum number of details per frame
262 u32 max_detail_count{};
263 /// Frame size in bytes
264 u64 frame_size{};
265 /// Is the performance manager initialized?
266 bool is_initialized{};
267 /// Target node id
268 u32 target_node_id{};
269 /// Performance version in use
270 PerformanceVersion version{};
271};
272
273} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/sink/circular_buffer_sink_info.cpp b/src/audio_core/renderer/sink/circular_buffer_sink_info.cpp
new file mode 100644
index 000000000..d91f10402
--- /dev/null
+++ b/src/audio_core/renderer/sink/circular_buffer_sink_info.cpp
@@ -0,0 +1,76 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/memory/pool_mapper.h"
5#include "audio_core/renderer/sink/circular_buffer_sink_info.h"
6#include "audio_core/renderer/upsampler/upsampler_manager.h"
7
8namespace AudioCore::AudioRenderer {
9
10CircularBufferSinkInfo::CircularBufferSinkInfo() {
11 state.fill(0);
12 parameter.fill(0);
13 type = Type::CircularBufferSink;
14
15 auto state_{reinterpret_cast<CircularBufferState*>(state.data())};
16 state_->address_info.Setup(0, 0);
17}
18
19void CircularBufferSinkInfo::CleanUp() {
20 auto state_{reinterpret_cast<DeviceState*>(state.data())};
21
22 if (state_->upsampler_info) {
23 state_->upsampler_info->manager->Free(state_->upsampler_info);
24 state_->upsampler_info = nullptr;
25 }
26
27 parameter.fill(0);
28 type = Type::Invalid;
29}
30
31void CircularBufferSinkInfo::Update(BehaviorInfo::ErrorInfo& error_info, OutStatus& out_status,
32 const InParameter& in_params, const PoolMapper& pool_mapper) {
33 const auto buffer_params{
34 reinterpret_cast<const CircularBufferInParameter*>(&in_params.circular_buffer)};
35 auto current_params{reinterpret_cast<CircularBufferInParameter*>(parameter.data())};
36 auto current_state{reinterpret_cast<CircularBufferState*>(state.data())};
37
38 if (in_use == buffer_params->in_use && !buffer_unmapped) {
39 error_info.error_code = ResultSuccess;
40 error_info.address = CpuAddr(0);
41 out_status.writeOffset = current_state->last_pos2;
42 return;
43 }
44
45 node_id = in_params.node_id;
46 in_use = in_params.in_use;
47
48 if (in_use) {
49 buffer_unmapped =
50 !pool_mapper.TryAttachBuffer(error_info, current_state->address_info,
51 buffer_params->cpu_address, buffer_params->size);
52 *current_params = *buffer_params;
53 } else {
54 *current_params = *buffer_params;
55 }
56 out_status.writeOffset = current_state->last_pos2;
57}
58
59void CircularBufferSinkInfo::UpdateForCommandGeneration() {
60 if (in_use) {
61 auto params{reinterpret_cast<CircularBufferInParameter*>(parameter.data())};
62 auto state_{reinterpret_cast<CircularBufferState*>(state.data())};
63
64 const auto pos{state_->current_pos};
65 state_->last_pos2 = state_->last_pos;
66 state_->last_pos = pos;
67
68 state_->current_pos += static_cast<s32>(params->input_count * params->sample_count *
69 GetSampleFormatByteSize(SampleFormat::PcmInt16));
70 if (params->size > 0) {
71 state_->current_pos %= params->size;
72 }
73 }
74}
75
76} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/sink/circular_buffer_sink_info.h b/src/audio_core/renderer/sink/circular_buffer_sink_info.h
new file mode 100644
index 000000000..3356213ea
--- /dev/null
+++ b/src/audio_core/renderer/sink/circular_buffer_sink_info.h
@@ -0,0 +1,41 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "audio_core/renderer/sink/sink_info_base.h"
7#include "common/common_types.h"
8
9namespace AudioCore::AudioRenderer {
10/**
11 * Info for a circular buffer sink.
12 */
13class CircularBufferSinkInfo : public SinkInfoBase {
14public:
15 CircularBufferSinkInfo();
16
17 /**
18 * Clean up for info, resetting it to a default state.
19 */
20 void CleanUp() override;
21
22 /**
23 * Update the info according to parameters, and write the current state to out_status.
24 *
25 * @param error_info - Output error code.
26 * @param out_status - Output status.
27 * @param in_params - Input parameters.
28 * @param pool_mapper - Used to map the circular buffer.
29 */
30 void Update(BehaviorInfo::ErrorInfo& error_info, OutStatus& out_status,
31 const InParameter& in_params, const PoolMapper& pool_mapper) override;
32
33 /**
34 * Update the circular buffer on command generation, incrementing its current offsets.
35 */
36 void UpdateForCommandGeneration() override;
37};
38static_assert(sizeof(CircularBufferSinkInfo) <= sizeof(SinkInfoBase),
39 "CircularBufferSinkInfo is too large!");
40
41} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/sink/device_sink_info.cpp b/src/audio_core/renderer/sink/device_sink_info.cpp
new file mode 100644
index 000000000..b7b3d6f1d
--- /dev/null
+++ b/src/audio_core/renderer/sink/device_sink_info.cpp
@@ -0,0 +1,57 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/sink/device_sink_info.h"
5#include "audio_core/renderer/upsampler/upsampler_manager.h"
6
7namespace AudioCore::AudioRenderer {
8
9DeviceSinkInfo::DeviceSinkInfo() {
10 state.fill(0);
11 parameter.fill(0);
12 type = Type::DeviceSink;
13}
14
15void DeviceSinkInfo::CleanUp() {
16 auto state_{reinterpret_cast<DeviceState*>(state.data())};
17
18 if (state_->upsampler_info) {
19 state_->upsampler_info->manager->Free(state_->upsampler_info);
20 state_->upsampler_info = nullptr;
21 }
22
23 parameter.fill(0);
24 type = Type::Invalid;
25}
26
27void DeviceSinkInfo::Update(BehaviorInfo::ErrorInfo& error_info, OutStatus& out_status,
28 const InParameter& in_params,
29 [[maybe_unused]] const PoolMapper& pool_mapper) {
30
31 const auto device_params{reinterpret_cast<const DeviceInParameter*>(&in_params.device)};
32 auto current_params{reinterpret_cast<DeviceInParameter*>(parameter.data())};
33
34 if (in_use == in_params.in_use) {
35 current_params->downmix_enabled = device_params->downmix_enabled;
36 current_params->downmix_coeff = device_params->downmix_coeff;
37 } else {
38 type = in_params.type;
39 in_use = in_params.in_use;
40 node_id = in_params.node_id;
41 *current_params = *device_params;
42 }
43
44 auto current_state{reinterpret_cast<DeviceState*>(state.data())};
45
46 for (size_t i = 0; i < current_state->downmix_coeff.size(); i++) {
47 current_state->downmix_coeff[i] = current_params->downmix_coeff[i];
48 }
49
50 std::memset(&out_status, 0, sizeof(OutStatus));
51 error_info.error_code = ResultSuccess;
52 error_info.address = CpuAddr(0);
53}
54
55void DeviceSinkInfo::UpdateForCommandGeneration() {}
56
57} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/sink/device_sink_info.h b/src/audio_core/renderer/sink/device_sink_info.h
new file mode 100644
index 000000000..a1c441454
--- /dev/null
+++ b/src/audio_core/renderer/sink/device_sink_info.h
@@ -0,0 +1,40 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "audio_core/renderer/sink/sink_info_base.h"
7#include "common/common_types.h"
8
9namespace AudioCore::AudioRenderer {
10/**
11 * Info for a device sink.
12 */
13class DeviceSinkInfo : public SinkInfoBase {
14public:
15 DeviceSinkInfo();
16
17 /**
18 * Clean up for info, resetting it to a default state.
19 */
20 void CleanUp() override;
21
22 /**
23 * Update the info according to parameters, and write the current state to out_status.
24 *
25 * @param error_info - Output error code.
26 * @param out_status - Output status.
27 * @param in_params - Input parameters.
28 * @param pool_mapper - Unused.
29 */
30 void Update(BehaviorInfo::ErrorInfo& error_info, OutStatus& out_status,
31 const InParameter& in_params, const PoolMapper& pool_mapper) override;
32
33 /**
34 * Update the device sink on command generation, unused.
35 */
36 void UpdateForCommandGeneration() override;
37};
38static_assert(sizeof(DeviceSinkInfo) <= sizeof(SinkInfoBase), "DeviceSinkInfo is too large!");
39
40} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/sink/sink_context.cpp b/src/audio_core/renderer/sink/sink_context.cpp
new file mode 100644
index 000000000..634bc1cf9
--- /dev/null
+++ b/src/audio_core/renderer/sink/sink_context.cpp
@@ -0,0 +1,21 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/sink/sink_context.h"
5
6namespace AudioCore::AudioRenderer {
7
8void SinkContext::Initialize(std::span<SinkInfoBase> sink_infos_, const u32 sink_count_) {
9 sink_infos = sink_infos_;
10 sink_count = sink_count_;
11}
12
13SinkInfoBase* SinkContext::GetInfo(const u32 index) {
14 return &sink_infos[index];
15}
16
17u32 SinkContext::GetCount() const {
18 return sink_count;
19}
20
21} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/sink/sink_context.h b/src/audio_core/renderer/sink/sink_context.h
new file mode 100644
index 000000000..185572e29
--- /dev/null
+++ b/src/audio_core/renderer/sink/sink_context.h
@@ -0,0 +1,47 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <span>
7
8#include "audio_core/renderer/sink/sink_info_base.h"
9#include "common/common_types.h"
10
11namespace AudioCore::AudioRenderer {
12/**
13 * Manages output sinks.
14 */
15class SinkContext {
16public:
17 /**
18 * Initialize the sink context.
19 *
20 * @param sink_infos - Workbuffer for the sinks.
21 * @param sink_count - Number of sinks in the buffer.
22 */
23 void Initialize(std::span<SinkInfoBase> sink_infos, u32 sink_count);
24
25 /**
26 * Get a given index's info.
27 *
28 * @param index - Sink index to get.
29 * @return The sink info base for the given index.
30 */
31 SinkInfoBase* GetInfo(u32 index);
32
33 /**
34 * Get the current number of sinks.
35 *
36 * @return The number of sinks.
37 */
38 u32 GetCount() const;
39
40private:
41 /// Buffer of sink infos
42 std::span<SinkInfoBase> sink_infos{};
43 /// Number of sinks in the buffer
44 u32 sink_count{};
45};
46
47} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/sink/sink_info_base.cpp b/src/audio_core/renderer/sink/sink_info_base.cpp
new file mode 100644
index 000000000..4279beaa0
--- /dev/null
+++ b/src/audio_core/renderer/sink/sink_info_base.cpp
@@ -0,0 +1,51 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/memory/pool_mapper.h"
5#include "audio_core/renderer/sink/sink_info_base.h"
6
7namespace AudioCore::AudioRenderer {
8
9void SinkInfoBase::CleanUp() {
10 type = Type::Invalid;
11}
12
13void SinkInfoBase::Update(BehaviorInfo::ErrorInfo& error_info, OutStatus& out_status,
14 [[maybe_unused]] const InParameter& in_params,
15 [[maybe_unused]] const PoolMapper& pool_mapper) {
16 std::memset(&out_status, 0, sizeof(OutStatus));
17 error_info.error_code = ResultSuccess;
18 error_info.address = CpuAddr(0);
19}
20
21void SinkInfoBase::UpdateForCommandGeneration() {}
22
23SinkInfoBase::DeviceState* SinkInfoBase::GetDeviceState() {
24 return reinterpret_cast<DeviceState*>(state.data());
25}
26
27SinkInfoBase::Type SinkInfoBase::GetType() const {
28 return type;
29}
30
31bool SinkInfoBase::IsUsed() const {
32 return in_use;
33}
34
35bool SinkInfoBase::ShouldSkip() const {
36 return buffer_unmapped;
37}
38
39u32 SinkInfoBase::GetNodeId() const {
40 return node_id;
41}
42
43u8* SinkInfoBase::GetState() {
44 return state.data();
45}
46
47u8* SinkInfoBase::GetParameter() {
48 return parameter.data();
49}
50
51} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/sink/sink_info_base.h b/src/audio_core/renderer/sink/sink_info_base.h
new file mode 100644
index 000000000..a1b855f20
--- /dev/null
+++ b/src/audio_core/renderer/sink/sink_info_base.h
@@ -0,0 +1,177 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7
8#include "audio_core/common/common.h"
9#include "audio_core/renderer/behavior/behavior_info.h"
10#include "audio_core/renderer/memory/address_info.h"
11#include "common/common_types.h"
12#include "common/fixed_point.h"
13
14namespace AudioCore::AudioRenderer {
15struct UpsamplerInfo;
16class PoolMapper;
17
18/**
19 * Base for the circular buffer and device sinks, holding their states for the AudioRenderer and
20 * their parametetrs for generating sink commands.
21 */
22class SinkInfoBase {
23public:
24 enum class Type : u8 {
25 Invalid,
26 DeviceSink,
27 CircularBufferSink,
28 };
29
30 struct DeviceInParameter {
31 /* 0x000 */ char name[0x100];
32 /* 0x100 */ u32 input_count;
33 /* 0x104 */ std::array<s8, MaxChannels> inputs;
34 /* 0x10A */ char unk10A[0x1];
35 /* 0x10B */ bool downmix_enabled;
36 /* 0x10C */ std::array<f32, 4> downmix_coeff;
37 };
38 static_assert(sizeof(DeviceInParameter) == 0x11C, "DeviceInParameter has the wrong size!");
39
40 struct DeviceState {
41 /* 0x00 */ UpsamplerInfo* upsampler_info;
42 /* 0x08 */ std::array<Common::FixedPoint<16, 16>, 4> downmix_coeff;
43 /* 0x18 */ char unk18[0x18];
44 };
45 static_assert(sizeof(DeviceState) == 0x30, "DeviceState has the wrong size!");
46
47 struct CircularBufferInParameter {
48 /* 0x00 */ u64 cpu_address;
49 /* 0x08 */ u32 size;
50 /* 0x0C */ u32 input_count;
51 /* 0x10 */ u32 sample_count;
52 /* 0x14 */ u32 previous_pos;
53 /* 0x18 */ SampleFormat format;
54 /* 0x1C */ std::array<s8, MaxChannels> inputs;
55 /* 0x22 */ bool in_use;
56 /* 0x23 */ char unk23[0x5];
57 };
58 static_assert(sizeof(CircularBufferInParameter) == 0x28,
59 "CircularBufferInParameter has the wrong size!");
60
61 struct CircularBufferState {
62 /* 0x00 */ u32 last_pos2;
63 /* 0x04 */ s32 current_pos;
64 /* 0x08 */ u32 last_pos;
65 /* 0x0C */ char unk0C[0x4];
66 /* 0x10 */ AddressInfo address_info;
67 };
68 static_assert(sizeof(CircularBufferState) == 0x30, "CircularBufferState has the wrong size!");
69
70 struct InParameter {
71 /* 0x000 */ Type type;
72 /* 0x001 */ bool in_use;
73 /* 0x004 */ u32 node_id;
74 /* 0x008 */ char unk08[0x18];
75 union {
76 /* 0x020 */ DeviceInParameter device;
77 /* 0x020 */ CircularBufferInParameter circular_buffer;
78 };
79 };
80 static_assert(sizeof(InParameter) == 0x140, "SinkInfoBase::InParameter has the wrong size!");
81
82 struct OutStatus {
83 /* 0x00 */ u32 writeOffset;
84 /* 0x04 */ char unk04[0x1C];
85 }; // size == 0x20
86 static_assert(sizeof(OutStatus) == 0x20, "SinkInfoBase::OutStatus has the wrong size!");
87
88 virtual ~SinkInfoBase() = default;
89
90 /**
91 * Clean up for info, resetting it to a default state.
92 */
93 virtual void CleanUp();
94
95 /**
96 * Update the info according to parameters, and write the current state to out_status.
97 *
98 * @param error_info - Output error code.
99 * @param out_status - Output status.
100 * @param in_params - Input parameters.
101 * @param pool_mapper - Used to map the circular buffer.
102 */
103 virtual void Update(BehaviorInfo::ErrorInfo& error_info, OutStatus& out_status,
104 [[maybe_unused]] const InParameter& in_params,
105 [[maybe_unused]] const PoolMapper& pool_mapper);
106
107 /**
108 * Update the circular buffer on command generation, incrementing its current offsets.
109 */
110 virtual void UpdateForCommandGeneration();
111
112 /**
113 * Get the state as a device sink.
114 *
115 * @return Device state.
116 */
117 DeviceState* GetDeviceState();
118
119 /**
120 * Get the type of this sink.
121 *
122 * @return Either Device, Circular, or Invalid.
123 */
124 Type GetType() const;
125
126 /**
127 * Check if this sink is in use.
128 *
129 * @return True if used, otherwise false.
130 */
131 bool IsUsed() const;
132
133 /**
134 * Check if this sink should be skipped for updates.
135 *
136 * @return True if it should be skipped, otherwise false.
137 */
138 bool ShouldSkip() const;
139
140 /**
141 * Get the node if of this sink.
142 *
143 * @return Node id for this sink.
144 */
145 u32 GetNodeId() const;
146
147 /**
148 * Get the state of this sink.
149 *
150 * @return Pointer to the state, must be cast to the correct type.
151 */
152 u8* GetState();
153
154 /**
155 * Get the parameters of this sink.
156 *
157 * @return Pointer to the parameters, must be cast to the correct type.
158 */
159 u8* GetParameter();
160
161protected:
162 /// Type of this sink
163 Type type{Type::Invalid};
164 /// Is this sink in use?
165 bool in_use{};
166 /// Is this sink's buffer unmapped? Circular only
167 bool buffer_unmapped{};
168 /// Node id for this sink
169 u32 node_id{};
170 /// State buffer for this sink
171 std::array<u8, std::max(sizeof(DeviceState), sizeof(CircularBufferState))> state{};
172 /// Parameter buffer for this sink
173 std::array<u8, std::max(sizeof(DeviceInParameter), sizeof(CircularBufferInParameter))>
174 parameter{};
175};
176
177} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/splitter/splitter_context.cpp b/src/audio_core/renderer/splitter/splitter_context.cpp
new file mode 100644
index 000000000..7a23ba43f
--- /dev/null
+++ b/src/audio_core/renderer/splitter/splitter_context.cpp
@@ -0,0 +1,217 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/common/audio_renderer_parameter.h"
5#include "audio_core/common/workbuffer_allocator.h"
6#include "audio_core/renderer/behavior/behavior_info.h"
7#include "audio_core/renderer/splitter/splitter_context.h"
8#include "common/alignment.h"
9
10namespace AudioCore::AudioRenderer {
11
12SplitterDestinationData* SplitterContext::GetDesintationData(const s32 splitter_id,
13 const s32 destination_id) {
14 return splitter_infos[splitter_id].GetData(destination_id);
15}
16
17SplitterInfo& SplitterContext::GetInfo(const s32 splitter_id) {
18 return splitter_infos[splitter_id];
19}
20
21u32 SplitterContext::GetDataCount() const {
22 return destinations_count;
23}
24
25u32 SplitterContext::GetInfoCount() const {
26 return info_count;
27}
28
29SplitterDestinationData& SplitterContext::GetData(const u32 index) {
30 return splitter_destinations[index];
31}
32
33void SplitterContext::Setup(std::span<SplitterInfo> splitter_infos_, const u32 splitter_info_count_,
34 SplitterDestinationData* splitter_destinations_,
35 const u32 destination_count_, const bool splitter_bug_fixed_) {
36 splitter_infos = splitter_infos_;
37 info_count = splitter_info_count_;
38 splitter_destinations = splitter_destinations_;
39 destinations_count = destination_count_;
40 splitter_bug_fixed = splitter_bug_fixed_;
41}
42
43bool SplitterContext::UsingSplitter() const {
44 return splitter_infos.size() > 0 && info_count > 0 && splitter_destinations != nullptr &&
45 destinations_count > 0;
46}
47
48void SplitterContext::ClearAllNewConnectionFlag() {
49 for (s32 i = 0; i < info_count; i++) {
50 splitter_infos[i].SetNewConnectionFlag();
51 }
52}
53
54bool SplitterContext::Initialize(const BehaviorInfo& behavior,
55 const AudioRendererParameterInternal& params,
56 WorkbufferAllocator& allocator) {
57 if (behavior.IsSplitterSupported() && params.splitter_infos > 0 &&
58 params.splitter_destinations > 0) {
59 splitter_infos = allocator.Allocate<SplitterInfo>(params.splitter_infos, 0x10);
60
61 for (u32 i = 0; i < params.splitter_infos; i++) {
62 std::construct_at<SplitterInfo>(&splitter_infos[i], static_cast<s32>(i));
63 }
64
65 if (splitter_infos.size() == 0) {
66 splitter_infos = {};
67 return false;
68 }
69
70 splitter_destinations =
71 allocator.Allocate<SplitterDestinationData>(params.splitter_destinations, 0x10).data();
72
73 for (s32 i = 0; i < params.splitter_destinations; i++) {
74 std::construct_at<SplitterDestinationData>(&splitter_destinations[i], i);
75 }
76
77 if (params.splitter_destinations <= 0) {
78 splitter_infos = {};
79 splitter_destinations = nullptr;
80 return false;
81 }
82
83 Setup(splitter_infos, params.splitter_infos, splitter_destinations,
84 params.splitter_destinations, behavior.IsSplitterBugFixed());
85 }
86 return true;
87}
88
89bool SplitterContext::Update(const u8* input, u32& consumed_size) {
90 auto in_params{reinterpret_cast<const InParameterHeader*>(input)};
91
92 if (destinations_count == 0 || info_count == 0) {
93 consumed_size = 0;
94 return true;
95 }
96
97 if (in_params->magic != GetSplitterInParamHeaderMagic()) {
98 consumed_size = 0;
99 return false;
100 }
101
102 for (auto& splitter_info : splitter_infos) {
103 splitter_info.ClearNewConnectionFlag();
104 }
105
106 u32 offset{sizeof(InParameterHeader)};
107 offset = UpdateInfo(input, offset, in_params->info_count);
108 offset = UpdateData(input, offset, in_params->destination_count);
109
110 consumed_size = Common::AlignUp(offset, 0x10);
111 return true;
112}
113
114u32 SplitterContext::UpdateInfo(const u8* input, u32 offset, const u32 splitter_count) {
115 for (u32 i = 0; i < splitter_count; i++) {
116 auto info_header{reinterpret_cast<const SplitterInfo::InParameter*>(input + offset)};
117
118 if (info_header->magic != GetSplitterInfoMagic()) {
119 continue;
120 }
121
122 if (info_header->id < 0 || info_header->id > info_count) {
123 break;
124 }
125
126 auto& info{splitter_infos[info_header->id]};
127 RecomposeDestination(info, info_header);
128
129 offset += info.Update(info_header);
130 }
131
132 return offset;
133}
134
135u32 SplitterContext::UpdateData(const u8* input, u32 offset, const u32 count) {
136 for (u32 i = 0; i < count; i++) {
137 auto data_header{
138 reinterpret_cast<const SplitterDestinationData::InParameter*>(input + offset)};
139
140 if (data_header->magic != GetSplitterSendDataMagic()) {
141 continue;
142 }
143
144 if (data_header->id < 0 || data_header->id > destinations_count) {
145 continue;
146 }
147
148 splitter_destinations[data_header->id].Update(*data_header);
149 offset += sizeof(SplitterDestinationData::InParameter);
150 }
151
152 return offset;
153}
154
155void SplitterContext::UpdateInternalState() {
156 for (s32 i = 0; i < info_count; i++) {
157 splitter_infos[i].UpdateInternalState();
158 }
159}
160
161void SplitterContext::RecomposeDestination(SplitterInfo& out_info,
162 const SplitterInfo::InParameter* info_header) {
163 auto destination{out_info.GetData(0)};
164 while (destination != nullptr) {
165 auto dest{destination->GetNext()};
166 destination->SetNext(nullptr);
167 destination = dest;
168 }
169 out_info.SetDestinations(nullptr);
170
171 auto dest_count{info_header->destination_count};
172 if (!splitter_bug_fixed) {
173 dest_count = std::min(dest_count, GetDestCountPerInfoForCompat());
174 }
175
176 if (dest_count == 0) {
177 return;
178 }
179
180 std::span<const u32> destination_ids{reinterpret_cast<const u32*>(&info_header[1]), dest_count};
181
182 auto head{&splitter_destinations[destination_ids[0]]};
183 auto current_destination{head};
184 for (u32 i = 1; i < dest_count; i++) {
185 auto next_destination{&splitter_destinations[destination_ids[i]]};
186 current_destination->SetNext(next_destination);
187 current_destination = next_destination;
188 }
189
190 out_info.SetDestinations(head);
191 out_info.SetDestinationCount(dest_count);
192}
193
194u32 SplitterContext::GetDestCountPerInfoForCompat() const {
195 if (info_count <= 0) {
196 return 0;
197 }
198 return static_cast<u32>(destinations_count / info_count);
199}
200
201u64 SplitterContext::CalcWorkBufferSize(const BehaviorInfo& behavior,
202 const AudioRendererParameterInternal& params) {
203 u64 size{0};
204 if (!behavior.IsSplitterSupported()) {
205 return size;
206 }
207
208 size += params.splitter_destinations * sizeof(SplitterDestinationData) +
209 params.splitter_infos * sizeof(SplitterInfo);
210
211 if (behavior.IsSplitterBugFixed()) {
212 size += Common::AlignUp(params.splitter_destinations * sizeof(u32), 0x10);
213 }
214 return size;
215}
216
217} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/splitter/splitter_context.h b/src/audio_core/renderer/splitter/splitter_context.h
new file mode 100644
index 000000000..cfd092b4f
--- /dev/null
+++ b/src/audio_core/renderer/splitter/splitter_context.h
@@ -0,0 +1,189 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <span>
7
8#include "audio_core/renderer/splitter/splitter_destinations_data.h"
9#include "audio_core/renderer/splitter/splitter_info.h"
10#include "common/common_types.h"
11
12namespace AudioCore {
13struct AudioRendererParameterInternal;
14class WorkbufferAllocator;
15
16namespace AudioRenderer {
17class BehaviorInfo;
18
19/**
20 * The splitter allows much more control over how sound is mixed together.
21 * Previously, one mix can only connect to one other, and you may need
22 * more mixes (and duplicate processing) to achieve the same result.
23 * With the splitter, many-to-one and one-to-many mixing is possible.
24 * This was added in revision 2.
25 * Had a bug with incorrect numbers of destinations, fixed in revision 5.
26 */
27class SplitterContext {
28 struct InParameterHeader {
29 /* 0x00 */ u32 magic; // 'SNDH'
30 /* 0x04 */ s32 info_count;
31 /* 0x08 */ s32 destination_count;
32 /* 0x0C */ char unk0C[0x14];
33 };
34 static_assert(sizeof(InParameterHeader) == 0x20,
35 "SplitterContext::InParameterHeader has the wrong size!");
36
37public:
38 /**
39 * Get a destination mix from the given splitter and destination index.
40 *
41 * @param splitter_id - Splitter index to get from.
42 * @param destination_id - Destination index within the splitter.
43 * @return Pointer to the found destination. May be nullptr.
44 */
45 SplitterDestinationData* GetDesintationData(s32 splitter_id, s32 destination_id);
46
47 /**
48 * Get a splitter from the given index.
49 *
50 * @param index - Index of the desired splitter.
51 * @return Splitter requested.
52 */
53 SplitterInfo& GetInfo(s32 index);
54
55 /**
56 * Get the total number of splitter destinations.
57 *
58 * @return Number of destiantions.
59 */
60 u32 GetDataCount() const;
61
62 /**
63 * Get the total number of splitters.
64 *
65 * @return Number of splitters.
66 */
67 u32 GetInfoCount() const;
68
69 /**
70 * Get a specific global destination.
71 *
72 * @param index - Index of the desired destination.
73 * @return The requested destination.
74 */
75 SplitterDestinationData& GetData(u32 index);
76
77 /**
78 * Check if the splitter is in use.
79 *
80 * @return True if any splitter or destination is in use, otherwise false.
81 */
82 bool UsingSplitter() const;
83
84 /**
85 * Mark all splitters as having new connections.
86 */
87 void ClearAllNewConnectionFlag();
88
89 /**
90 * Initialize the context.
91 *
92 * @param behavior - Used to check for splitter support.
93 * @param params - Input parameters.
94 * @param allocator - Allocator used to allocate workbuffer memory.
95 */
96 bool Initialize(const BehaviorInfo& behavior, const AudioRendererParameterInternal& params,
97 WorkbufferAllocator& allocator);
98
99 /**
100 * Update the context.
101 *
102 * @param input - Input buffer with the new info,
103 * expected to point to a InParameterHeader.
104 * @param consumed_size - Output with the number of bytes consumed from input.
105 */
106 bool Update(const u8* input, u32& consumed_size);
107
108 /**
109 * Update the splitters.
110 *
111 * @param input - Input buffer with the new info.
112 * @param offset - Current offset within the input buffer,
113 * input + offset should point to a SplitterInfo::InParameter.
114 * @param splitter_count - Number of splitters in the input buffer.
115 * @return Number of bytes consumed in input.
116 */
117 u32 UpdateInfo(const u8* input, u32 offset, u32 splitter_count);
118
119 /**
120 * Update the splitters.
121 *
122 * @param input - Input buffer with the new info.
123 * @param offset - Current offset within the input buffer,
124 * input + offset should point to a
125 * SplitterDestinationData::InParameter.
126 * @param destination_count - Number of destinations in the input buffer.
127 * @return Number of bytes consumed in input.
128 */
129 u32 UpdateData(const u8* input, u32 offset, u32 destination_count);
130
131 /**
132 * Update the state of all destinations in all splitters.
133 */
134 void UpdateInternalState();
135
136 /**
137 * Replace the given splitter's destinations with new ones.
138 *
139 * @param out_info - Splitter to recompose.
140 * @param info_header - Input parameters containing new destination ids.
141 */
142 void RecomposeDestination(SplitterInfo& out_info, const SplitterInfo::InParameter* info_header);
143
144 /**
145 * Old calculation for destinations, this is the thing the splitter bug fixes.
146 * Left for compatibility, and now min'd with the actual count to not bug.
147 *
148 * @return Number of splitter destinations.
149 */
150 u32 GetDestCountPerInfoForCompat() const;
151
152 /**
153 * Calculate the size of the required workbuffer for splitters and destinations.
154 *
155 * @param behavior - Used to check splitter features.
156 * @param params - Input parameters with splitter/destination counts.
157 * @return Required buffer size.
158 */
159 static u64 CalcWorkBufferSize(const BehaviorInfo& behavior,
160 const AudioRendererParameterInternal& params);
161
162private:
163 /**
164 * Setup the context.
165 *
166 * @param splitter_infos - Workbuffer for splitters.
167 * @param splitter_info_count - Number of splitters in the workbuffer.
168 * @param splitter_destinations - Workbuffer for splitter destinations.
169 * @param destination_count - Number of destinations in the workbuffer.
170 * @param splitter_bug_fixed - Is the splitter bug fixed?
171 */
172 void Setup(std::span<SplitterInfo> splitter_infos, u32 splitter_info_count,
173 SplitterDestinationData* splitter_destinations, u32 destination_count,
174 bool splitter_bug_fixed);
175
176 /// Workbuffer for splitters
177 std::span<SplitterInfo> splitter_infos{};
178 /// Number of splitters in buffer
179 s32 info_count{};
180 /// Workbuffer for destinations
181 SplitterDestinationData* splitter_destinations{};
182 /// Number of destinations in buffer
183 s32 destinations_count{};
184 /// Is the splitter bug fixed?
185 bool splitter_bug_fixed{};
186};
187
188} // namespace AudioRenderer
189} // namespace AudioCore
diff --git a/src/audio_core/renderer/splitter/splitter_destinations_data.cpp b/src/audio_core/renderer/splitter/splitter_destinations_data.cpp
new file mode 100644
index 000000000..b27d44896
--- /dev/null
+++ b/src/audio_core/renderer/splitter/splitter_destinations_data.cpp
@@ -0,0 +1,87 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/splitter/splitter_destinations_data.h"
5
6namespace AudioCore::AudioRenderer {
7
8SplitterDestinationData::SplitterDestinationData(const s32 id_) : id{id_} {}
9
10void SplitterDestinationData::ClearMixVolume() {
11 mix_volumes.fill(0.0f);
12 prev_mix_volumes.fill(0.0f);
13}
14
15s32 SplitterDestinationData::GetId() const {
16 return id;
17}
18
19bool SplitterDestinationData::IsConfigured() const {
20 return in_use && destination_id != UnusedMixId;
21}
22
23s32 SplitterDestinationData::GetMixId() const {
24 return destination_id;
25}
26
27f32 SplitterDestinationData::GetMixVolume(const u32 index) const {
28 if (index >= mix_volumes.size()) {
29 LOG_ERROR(Service_Audio, "SplitterDestinationData::GetMixVolume Invalid index {}", index);
30 return 0.0f;
31 }
32 return mix_volumes[index];
33}
34
35std::span<f32> SplitterDestinationData::GetMixVolume() {
36 return mix_volumes;
37}
38
39f32 SplitterDestinationData::GetMixVolumePrev(const u32 index) const {
40 if (index >= prev_mix_volumes.size()) {
41 LOG_ERROR(Service_Audio, "SplitterDestinationData::GetMixVolumePrev Invalid index {}",
42 index);
43 return 0.0f;
44 }
45 return prev_mix_volumes[index];
46}
47
48std::span<f32> SplitterDestinationData::GetMixVolumePrev() {
49 return prev_mix_volumes;
50}
51
52void SplitterDestinationData::Update(const InParameter& params) {
53 if (params.id != id || params.magic != GetSplitterSendDataMagic()) {
54 return;
55 }
56
57 destination_id = params.mix_id;
58 mix_volumes = params.mix_volumes;
59
60 if (!in_use && params.in_use) {
61 prev_mix_volumes = mix_volumes;
62 need_update = false;
63 }
64
65 in_use = params.in_use;
66}
67
68void SplitterDestinationData::MarkAsNeedToUpdateInternalState() {
69 need_update = true;
70}
71
72void SplitterDestinationData::UpdateInternalState() {
73 if (in_use && need_update) {
74 prev_mix_volumes = mix_volumes;
75 }
76 need_update = false;
77}
78
79SplitterDestinationData* SplitterDestinationData::GetNext() const {
80 return next;
81}
82
83void SplitterDestinationData::SetNext(SplitterDestinationData* next_) {
84 next = next_;
85}
86
87} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/splitter/splitter_destinations_data.h b/src/audio_core/renderer/splitter/splitter_destinations_data.h
new file mode 100644
index 000000000..bd3d55748
--- /dev/null
+++ b/src/audio_core/renderer/splitter/splitter_destinations_data.h
@@ -0,0 +1,135 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7#include <span>
8
9#include "audio_core/common/common.h"
10#include "common/common_types.h"
11
12namespace AudioCore::AudioRenderer {
13/**
14 * Represents a mixing node, can be connected to a previous and next destination forming a chain
15 * that a certain mix buffer will pass through to output.
16 */
17class SplitterDestinationData {
18public:
19 struct InParameter {
20 /* 0x00 */ u32 magic; // 'SNDD'
21 /* 0x04 */ s32 id;
22 /* 0x08 */ std::array<f32, MaxMixBuffers> mix_volumes;
23 /* 0x68 */ u32 mix_id;
24 /* 0x6C */ bool in_use;
25 };
26 static_assert(sizeof(InParameter) == 0x70,
27 "SplitterDestinationData::InParameter has the wrong size!");
28
29 SplitterDestinationData(s32 id);
30
31 /**
32 * Reset the mix volumes for this destination.
33 */
34 void ClearMixVolume();
35
36 /**
37 * Get the id of this destination.
38 *
39 * @return Id for this destination.
40 */
41 s32 GetId() const;
42
43 /**
44 * Check if this destination is correctly configured.
45 *
46 * @return True if configured, otherwise false.
47 */
48 bool IsConfigured() const;
49
50 /**
51 * Get the mix id for this destination.
52 *
53 * @return Mix id for this destination.
54 */
55 s32 GetMixId() const;
56
57 /**
58 * Get the current mix volume of a given index in this destination.
59 *
60 * @param index - Mix buffer index to get the volume for.
61 * @return Current volume of the specified mix.
62 */
63 f32 GetMixVolume(u32 index) const;
64
65 /**
66 * Get the current mix volumes for all mix buffers in this destination.
67 *
68 * @return Span of current mix buffer volumes.
69 */
70 std::span<f32> GetMixVolume();
71
72 /**
73 * Get the previous mix volume of a given index in this destination.
74 *
75 * @param index - Mix buffer index to get the volume for.
76 * @return Previous volume of the specified mix.
77 */
78 f32 GetMixVolumePrev(u32 index) const;
79
80 /**
81 * Get the previous mix volumes for all mix buffers in this destination.
82 *
83 * @return Span of previous mix buffer volumes.
84 */
85 std::span<f32> GetMixVolumePrev();
86
87 /**
88 * Update this destination.
89 *
90 * @param params - Inpout parameters to update the destination.
91 */
92 void Update(const InParameter& params);
93
94 /**
95 * Mark this destination as needing its volumes updated.
96 */
97 void MarkAsNeedToUpdateInternalState();
98
99 /**
100 * Copy current volumes to previous if an update is required.
101 */
102 void UpdateInternalState();
103
104 /**
105 * Get the next destination in the mix chain.
106 *
107 * @return The next splitter destination, may be nullptr if this is the last in the chain.
108 */
109 SplitterDestinationData* GetNext() const;
110
111 /**
112 * Set the next destination in the mix chain.
113 *
114 * @param next - Destination this one is to be connected to.
115 */
116 void SetNext(SplitterDestinationData* next);
117
118private:
119 /// Id of this destination
120 const s32 id;
121 /// Mix id this destination represents
122 s32 destination_id{UnusedMixId};
123 /// Current mix volumes
124 std::array<f32, MaxMixBuffers> mix_volumes{0.0f};
125 /// Previous mix volumes
126 std::array<f32, MaxMixBuffers> prev_mix_volumes{0.0f};
127 /// Next destination in the mix chain
128 SplitterDestinationData* next{};
129 /// Is this destiantion in use?
130 bool in_use{};
131 /// Does this destiantion need its volumes updated?
132 bool need_update{};
133};
134
135} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/splitter/splitter_info.cpp b/src/audio_core/renderer/splitter/splitter_info.cpp
new file mode 100644
index 000000000..1aee6720b
--- /dev/null
+++ b/src/audio_core/renderer/splitter/splitter_info.cpp
@@ -0,0 +1,79 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/splitter/splitter_info.h"
5
6namespace AudioCore::AudioRenderer {
7
8SplitterInfo::SplitterInfo(const s32 id_) : id{id_} {}
9
10void SplitterInfo::InitializeInfos(SplitterInfo* splitters, const u32 count) {
11 if (splitters == nullptr) {
12 return;
13 }
14
15 for (u32 i = 0; i < count; i++) {
16 auto& splitter{splitters[i]};
17 splitter.destinations = nullptr;
18 splitter.destination_count = 0;
19 splitter.has_new_connection = true;
20 }
21}
22
23u32 SplitterInfo::Update(const InParameter* params) {
24 if (params->id != id) {
25 return 0;
26 }
27 sample_rate = params->sample_rate;
28 has_new_connection = true;
29 return static_cast<u32>((sizeof(InParameter) + 3 * sizeof(s32)) +
30 params->destination_count * sizeof(s32));
31}
32
33SplitterDestinationData* SplitterInfo::GetData(const u32 destination_id) {
34 auto out_destination{destinations};
35 u32 i{0};
36 while (i < destination_id) {
37 if (out_destination == nullptr) {
38 break;
39 }
40 out_destination = out_destination->GetNext();
41 i++;
42 }
43
44 return out_destination;
45}
46
47u32 SplitterInfo::GetDestinationCount() const {
48 return destination_count;
49}
50
51void SplitterInfo::SetDestinationCount(const u32 count) {
52 destination_count = count;
53}
54
55bool SplitterInfo::HasNewConnection() const {
56 return has_new_connection;
57}
58
59void SplitterInfo::ClearNewConnectionFlag() {
60 has_new_connection = false;
61}
62
63void SplitterInfo::SetNewConnectionFlag() {
64 has_new_connection = true;
65}
66
67void SplitterInfo::UpdateInternalState() {
68 auto destination{destinations};
69 while (destination != nullptr) {
70 destination->UpdateInternalState();
71 destination = destination->GetNext();
72 }
73}
74
75void SplitterInfo::SetDestinations(SplitterDestinationData* destinations_) {
76 destinations = destinations_;
77}
78
79} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/splitter/splitter_info.h b/src/audio_core/renderer/splitter/splitter_info.h
new file mode 100644
index 000000000..d1d75064c
--- /dev/null
+++ b/src/audio_core/renderer/splitter/splitter_info.h
@@ -0,0 +1,107 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "audio_core/renderer/splitter/splitter_destinations_data.h"
7#include "common/common_types.h"
8
9namespace AudioCore::AudioRenderer {
10/**
11 * Represents a splitter, wraps multiple output destinations to split an input mix into.
12 */
13class SplitterInfo {
14public:
15 struct InParameter {
16 /* 0x00 */ u32 magic; // 'SNDI'
17 /* 0x04 */ s32 id;
18 /* 0x08 */ u32 sample_rate;
19 /* 0x0C */ u32 destination_count;
20 };
21 static_assert(sizeof(InParameter) == 0x10, "SplitterInfo::InParameter has the wrong size!");
22
23 explicit SplitterInfo(s32 id);
24
25 /**
26 * Initialize the given splitters.
27 *
28 * @param splitters - Splitters to initialize.
29 * @param count - Number of splitters given.
30 */
31 static void InitializeInfos(SplitterInfo* splitters, u32 count);
32
33 /**
34 * Update this splitter.
35 *
36 * @param params - Input parameters to update with.
37 * @return The size in bytes of this splitter.
38 */
39 u32 Update(const InParameter* params);
40
41 /**
42 * Get a destination in this splitter.
43 *
44 * @param id - Destination id to get.
45 * @return Pointer to the destination, may be nullptr.
46 */
47 SplitterDestinationData* GetData(u32 id);
48
49 /**
50 * Get the number of destinations in this splitter.
51 *
52 * @return The number of destiantions.
53 */
54 u32 GetDestinationCount() const;
55
56 /**
57 * Set the number of destinations in this splitter.
58 *
59 * @param count - The new number of destiantions.
60 */
61 void SetDestinationCount(u32 count);
62
63 /**
64 * Check if the splitter has a new connection.
65 *
66 * @return True if there is a new connection, otherwise false.
67 */
68 bool HasNewConnection() const;
69
70 /**
71 * Reset the new connection flag.
72 */
73 void ClearNewConnectionFlag();
74
75 /**
76 * Mark as having a new connection.
77 */
78 void SetNewConnectionFlag();
79
80 /**
81 * Update the state of all destinations.
82 */
83 void UpdateInternalState();
84
85 /**
86 * Set this splitter's destinations.
87 *
88 * @param destinations - The new destination list for this splitter.
89 */
90 void SetDestinations(SplitterDestinationData* destinations);
91
92private:
93 /// Id of this splitter
94 s32 id;
95 /// Sample rate of this splitter
96 u32 sample_rate{};
97 /// Number of destinations in this splitter
98 u32 destination_count{};
99 /// Does this splitter have a new connection?
100 bool has_new_connection{true};
101 /// Pointer to the destinations of this splitter
102 SplitterDestinationData* destinations{};
103 /// Number of channels this splitter manages
104 u32 channel_count{};
105};
106
107} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/system.cpp b/src/audio_core/renderer/system.cpp
new file mode 100644
index 000000000..7a217969e
--- /dev/null
+++ b/src/audio_core/renderer/system.cpp
@@ -0,0 +1,802 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <chrono>
5#include <span>
6
7#include "audio_core/audio_core.h"
8#include "audio_core/common/audio_renderer_parameter.h"
9#include "audio_core/common/common.h"
10#include "audio_core/common/feature_support.h"
11#include "audio_core/common/workbuffer_allocator.h"
12#include "audio_core/renderer/adsp/adsp.h"
13#include "audio_core/renderer/behavior/info_updater.h"
14#include "audio_core/renderer/command/command_buffer.h"
15#include "audio_core/renderer/command/command_generator.h"
16#include "audio_core/renderer/command/command_list_header.h"
17#include "audio_core/renderer/effect/effect_info_base.h"
18#include "audio_core/renderer/effect/effect_result_state.h"
19#include "audio_core/renderer/memory/memory_pool_info.h"
20#include "audio_core/renderer/memory/pool_mapper.h"
21#include "audio_core/renderer/mix/mix_info.h"
22#include "audio_core/renderer/nodes/edge_matrix.h"
23#include "audio_core/renderer/nodes/node_states.h"
24#include "audio_core/renderer/sink/sink_info_base.h"
25#include "audio_core/renderer/system.h"
26#include "audio_core/renderer/upsampler/upsampler_info.h"
27#include "audio_core/renderer/voice/voice_channel_resource.h"
28#include "audio_core/renderer/voice/voice_info.h"
29#include "audio_core/renderer/voice/voice_state.h"
30#include "common/alignment.h"
31#include "core/core.h"
32#include "core/core_timing.h"
33#include "core/hle/kernel/k_event.h"
34#include "core/hle/kernel/k_transfer_memory.h"
35#include "core/memory.h"
36
37namespace AudioCore::AudioRenderer {
38
39u64 System::GetWorkBufferSize(const AudioRendererParameterInternal& params) {
40 BehaviorInfo behavior;
41 behavior.SetUserLibRevision(params.revision);
42
43 u64 size{0};
44
45 size += Common::AlignUp(params.mixes * sizeof(s32), 0x40);
46 size += params.sub_mixes * MaxEffects * sizeof(s32);
47 size += (params.sub_mixes + 1) * sizeof(MixInfo);
48 size += params.voices * (sizeof(VoiceInfo) + sizeof(VoiceChannelResource) + sizeof(VoiceState));
49 size += Common::AlignUp((params.sub_mixes + 1) * sizeof(MixInfo*), 0x10);
50 size += Common::AlignUp(params.voices * sizeof(VoiceInfo*), 0x10);
51 size += Common::AlignUp(((params.sinks + params.sub_mixes) * TargetSampleCount * sizeof(s32) +
52 params.sample_count * sizeof(s32)) *
53 (params.mixes + MaxChannels),
54 0x40);
55
56 if (behavior.IsSplitterSupported()) {
57 const auto node_size{NodeStates::GetWorkBufferSize(params.sub_mixes + 1)};
58 const auto edge_size{EdgeMatrix::GetWorkBufferSize(params.sub_mixes + 1)};
59 size += Common::AlignUp(node_size + edge_size, 0x10);
60 }
61
62 size += SplitterContext::CalcWorkBufferSize(behavior, params);
63 size += (params.effects + params.voices * MaxWaveBuffers) * sizeof(MemoryPoolInfo);
64
65 if (behavior.IsEffectInfoVersion2Supported()) {
66 size += params.effects * sizeof(EffectResultState);
67 }
68 size += 0x50;
69
70 size = Common::AlignUp(size, 0x40);
71
72 size += (params.sinks + params.sub_mixes) * sizeof(UpsamplerInfo);
73 size += params.effects * sizeof(EffectInfoBase);
74 size += Common::AlignUp(params.voices * sizeof(VoiceState), 0x40);
75 size += params.sinks * sizeof(SinkInfoBase);
76
77 if (behavior.IsEffectInfoVersion2Supported()) {
78 size += params.effects * sizeof(EffectResultState);
79 }
80
81 if (params.perf_frames > 0) {
82 auto perf_size{PerformanceManager::GetRequiredBufferSizeForPerformanceMetricsPerFrame(
83 behavior, params)};
84 size += Common::AlignUp(perf_size * (params.perf_frames + 1) + 0xC0, 0x100);
85 }
86
87 if (behavior.IsVariadicCommandBufferSizeSupported()) {
88 size += CommandGenerator::CalculateCommandBufferSize(behavior, params) + (0x40 - 1) * 2;
89 } else {
90 size += 0x18000 + (0x40 - 1) * 2;
91 }
92
93 size = Common::AlignUp(size, 0x1000);
94 return size;
95}
96
97System::System(Core::System& core_, Kernel::KEvent* adsp_rendered_event_)
98 : core{core_}, adsp{core.AudioCore().GetADSP()}, adsp_rendered_event{adsp_rendered_event_} {}
99
100Result System::Initialize(const AudioRendererParameterInternal& params,
101 Kernel::KTransferMemory* transfer_memory, const u64 transfer_memory_size,
102 const u32 process_handle_, const u64 applet_resource_user_id_,
103 const s32 session_id_) {
104 if (!CheckValidRevision(params.revision)) {
105 return Service::Audio::ERR_INVALID_REVISION;
106 }
107
108 if (GetWorkBufferSize(params) > transfer_memory_size) {
109 return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
110 }
111
112 if (process_handle_ == 0) {
113 return Service::Audio::ERR_INVALID_PROCESS_HANDLE;
114 }
115
116 behavior.SetUserLibRevision(params.revision);
117
118 process_handle = process_handle_;
119 applet_resource_user_id = applet_resource_user_id_;
120 session_id = session_id_;
121
122 sample_rate = params.sample_rate;
123 sample_count = params.sample_count;
124 mix_buffer_count = static_cast<s16>(params.mixes);
125 voice_channels = MaxChannels;
126 upsampler_count = params.sinks + params.sub_mixes;
127 memory_pool_count = params.effects + params.voices * MaxWaveBuffers;
128 render_device = params.rendering_device;
129 execution_mode = params.execution_mode;
130
131 core.Memory().ZeroBlock(*core.Kernel().CurrentProcess(), transfer_memory->GetSourceAddress(),
132 transfer_memory_size);
133
134 // Note: We're not actually using the transfer memory because it's a pain to code for.
135 // Allocate the memory normally instead and hope the game doesn't try to read anything back
136 workbuffer = std::make_unique<u8[]>(transfer_memory_size);
137 workbuffer_size = transfer_memory_size;
138
139 PoolMapper pool_mapper(process_handle, false);
140 pool_mapper.InitializeSystemPool(memory_pool_info, workbuffer.get(), workbuffer_size);
141
142 WorkbufferAllocator allocator({workbuffer.get(), workbuffer_size}, workbuffer_size);
143
144 samples_workbuffer =
145 allocator.Allocate<s32>((voice_channels + mix_buffer_count) * sample_count, 0x10);
146 if (samples_workbuffer.empty()) {
147 return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
148 }
149
150 auto upsampler_workbuffer{allocator.Allocate<s32>(
151 (voice_channels + mix_buffer_count) * TargetSampleCount * upsampler_count, 0x10)};
152 if (upsampler_workbuffer.empty()) {
153 return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
154 }
155
156 depop_buffer =
157 allocator.Allocate<s32>(Common::AlignUp(static_cast<u32>(mix_buffer_count), 0x40), 0x40);
158 if (depop_buffer.empty()) {
159 return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
160 }
161
162 // invalidate samples_workbuffer DSP cache
163
164 auto voice_infos{allocator.Allocate<VoiceInfo>(params.voices, 0x10)};
165 for (auto& voice_info : voice_infos) {
166 std::construct_at<VoiceInfo>(&voice_info);
167 }
168
169 if (voice_infos.empty()) {
170 return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
171 }
172
173 auto sorted_voice_infos{allocator.Allocate<VoiceInfo*>(params.voices, 0x10)};
174 if (sorted_voice_infos.empty()) {
175 return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
176 }
177
178 std::memset(sorted_voice_infos.data(), 0, sorted_voice_infos.size_bytes());
179
180 auto voice_channel_resources{allocator.Allocate<VoiceChannelResource>(params.voices, 0x10)};
181 u32 i{0};
182 for (auto& voice_channel_resource : voice_channel_resources) {
183 std::construct_at<VoiceChannelResource>(&voice_channel_resource, i++);
184 }
185
186 if (voice_channel_resources.empty()) {
187 return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
188 }
189
190 auto voice_cpu_states{allocator.Allocate<VoiceState>(params.voices, 0x10)};
191 if (voice_cpu_states.empty()) {
192 return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
193 }
194
195 for (auto& voice_state : voice_cpu_states) {
196 voice_state = {};
197 }
198
199 auto mix_infos{allocator.Allocate<MixInfo>(params.sub_mixes + 1, 0x10)};
200
201 if (mix_infos.empty()) {
202 return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
203 }
204
205 u32 effect_process_order_count{0};
206 std::span<s32> effect_process_order_buffer{};
207
208 if (params.effects > 0) {
209 effect_process_order_count = params.effects * (params.sub_mixes + 1);
210 effect_process_order_buffer = allocator.Allocate<s32>(effect_process_order_count, 0x10);
211 if (effect_process_order_buffer.empty()) {
212 return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
213 }
214 }
215
216 i = 0;
217 for (auto& mix_info : mix_infos) {
218 std::construct_at<MixInfo>(
219 &mix_info, effect_process_order_buffer.subspan(i * params.effects, params.effects),
220 params.effects, this->behavior);
221 i++;
222 }
223
224 auto sorted_mix_infos{allocator.Allocate<MixInfo*>(params.sub_mixes + 1, 0x10)};
225 if (sorted_mix_infos.empty()) {
226 return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
227 }
228
229 std::memset(sorted_mix_infos.data(), 0, sorted_mix_infos.size_bytes());
230
231 if (behavior.IsSplitterSupported()) {
232 u64 node_state_size{NodeStates::GetWorkBufferSize(params.sub_mixes + 1)};
233 u64 edge_matrix_size{EdgeMatrix::GetWorkBufferSize(params.sub_mixes + 1)};
234
235 auto node_states_workbuffer{allocator.Allocate<u8>(node_state_size, 1)};
236 auto edge_matrix_workbuffer{allocator.Allocate<u8>(edge_matrix_size, 1)};
237
238 if (node_states_workbuffer.empty() || edge_matrix_workbuffer.size() == 0) {
239 return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
240 }
241
242 mix_context.Initialize(sorted_mix_infos, mix_infos, params.sub_mixes + 1,
243 effect_process_order_buffer, effect_process_order_count,
244 node_states_workbuffer, node_state_size, edge_matrix_workbuffer,
245 edge_matrix_size);
246 } else {
247 mix_context.Initialize(sorted_mix_infos, mix_infos, params.sub_mixes + 1,
248 effect_process_order_buffer, effect_process_order_count, {}, 0, {},
249 0);
250 }
251
252 upsampler_manager = allocator.Allocate<UpsamplerManager>(1, 0x10).data();
253 if (upsampler_manager == nullptr) {
254 return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
255 }
256
257 memory_pool_workbuffer = allocator.Allocate<MemoryPoolInfo>(memory_pool_count, 0x10);
258 for (auto& memory_pool : memory_pool_workbuffer) {
259 std::construct_at<MemoryPoolInfo>(&memory_pool, MemoryPoolInfo::Location::DSP);
260 }
261
262 if (memory_pool_workbuffer.empty() && memory_pool_count > 0) {
263 return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
264 }
265
266 if (!splitter_context.Initialize(behavior, params, allocator)) {
267 return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
268 }
269
270 std::span<EffectResultState> effect_result_states_cpu{};
271 if (behavior.IsEffectInfoVersion2Supported() && params.effects > 0) {
272 effect_result_states_cpu = allocator.Allocate<EffectResultState>(params.effects, 0x10);
273 if (effect_result_states_cpu.empty()) {
274 return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
275 }
276 std::memset(effect_result_states_cpu.data(), 0, effect_result_states_cpu.size_bytes());
277 }
278
279 allocator.Align(0x40);
280
281 unk_2B0 = allocator.GetSize() - allocator.GetCurrentOffset();
282 unk_2A8 = {&workbuffer[allocator.GetCurrentOffset()], unk_2B0};
283
284 upsampler_infos = allocator.Allocate<UpsamplerInfo>(upsampler_count, 0x40);
285 for (auto& upsampler_info : upsampler_infos) {
286 std::construct_at<UpsamplerInfo>(&upsampler_info);
287 }
288
289 std::construct_at<UpsamplerManager>(upsampler_manager, upsampler_count, upsampler_infos,
290 upsampler_workbuffer);
291
292 if (upsampler_infos.empty()) {
293 return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
294 }
295
296 auto effect_infos{allocator.Allocate<EffectInfoBase>(params.effects, 0x40)};
297 for (auto& effect_info : effect_infos) {
298 std::construct_at<EffectInfoBase>(&effect_info);
299 }
300
301 if (effect_infos.empty() && params.effects > 0) {
302 return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
303 }
304
305 std::span<EffectResultState> effect_result_states_dsp{};
306 if (behavior.IsEffectInfoVersion2Supported() && params.effects > 0) {
307 effect_result_states_dsp = allocator.Allocate<EffectResultState>(params.effects, 0x40);
308 if (effect_result_states_dsp.empty()) {
309 return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
310 }
311 std::memset(effect_result_states_dsp.data(), 0, effect_result_states_dsp.size_bytes());
312 }
313
314 effect_context.Initialize(effect_infos, params.effects, effect_result_states_cpu,
315 effect_result_states_dsp, effect_result_states_dsp.size());
316
317 auto sinks{allocator.Allocate<SinkInfoBase>(params.sinks, 0x10)};
318 for (auto& sink : sinks) {
319 std::construct_at<SinkInfoBase>(&sink);
320 }
321
322 if (sinks.empty()) {
323 return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
324 }
325
326 sink_context.Initialize(sinks, params.sinks);
327
328 auto voice_dsp_states{allocator.Allocate<VoiceState>(params.voices, 0x40)};
329 if (voice_dsp_states.empty()) {
330 return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
331 }
332
333 for (auto& voice_state : voice_dsp_states) {
334 voice_state = {};
335 }
336
337 voice_context.Initialize(sorted_voice_infos, voice_infos, voice_channel_resources,
338 voice_cpu_states, voice_dsp_states, params.voices);
339
340 if (params.perf_frames > 0) {
341 const auto perf_workbuffer_size{
342 PerformanceManager::GetRequiredBufferSizeForPerformanceMetricsPerFrame(behavior,
343 params) *
344 (params.perf_frames + 1) +
345 0xC};
346 performance_workbuffer = allocator.Allocate<u8>(perf_workbuffer_size, 0x40);
347 if (performance_workbuffer.empty()) {
348 return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
349 }
350 std::memset(performance_workbuffer.data(), 0, performance_workbuffer.size_bytes());
351 performance_manager.Initialize(performance_workbuffer, performance_workbuffer.size_bytes(),
352 params, behavior, memory_pool_info);
353 }
354
355 render_time_limit_percent = 100;
356 drop_voice = params.voice_drop_enabled && params.execution_mode == ExecutionMode::Auto;
357
358 allocator.Align(0x40);
359 command_workbuffer_size = allocator.GetRemainingSize();
360 command_workbuffer = allocator.Allocate<u8>(command_workbuffer_size, 0x40);
361 if (command_workbuffer.empty()) {
362 return Service::Audio::ERR_INSUFFICIENT_BUFFER_SIZE;
363 }
364
365 command_buffer_size = 0;
366 reset_command_buffers = true;
367
368 // nn::audio::dsp::FlushDataCache(transferMemory, transferMemorySize);
369
370 if (behavior.IsCommandProcessingTimeEstimatorVersion5Supported()) {
371 command_processing_time_estimator =
372 std::make_unique<CommandProcessingTimeEstimatorVersion5>(sample_count,
373 mix_buffer_count);
374 } else if (behavior.IsCommandProcessingTimeEstimatorVersion4Supported()) {
375 command_processing_time_estimator =
376 std::make_unique<CommandProcessingTimeEstimatorVersion4>(sample_count,
377 mix_buffer_count);
378 } else if (behavior.IsCommandProcessingTimeEstimatorVersion3Supported()) {
379 command_processing_time_estimator =
380 std::make_unique<CommandProcessingTimeEstimatorVersion3>(sample_count,
381 mix_buffer_count);
382 } else if (behavior.IsCommandProcessingTimeEstimatorVersion2Supported()) {
383 command_processing_time_estimator =
384 std::make_unique<CommandProcessingTimeEstimatorVersion2>(sample_count,
385 mix_buffer_count);
386 } else {
387 command_processing_time_estimator =
388 std::make_unique<CommandProcessingTimeEstimatorVersion1>(sample_count,
389 mix_buffer_count);
390 }
391
392 initialized = true;
393 return ResultSuccess;
394}
395
396void System::Finalize() {
397 if (!initialized) {
398 return;
399 }
400
401 if (active) {
402 Stop();
403 }
404
405 applet_resource_user_id = 0;
406
407 PoolMapper pool_mapper(process_handle, false);
408 pool_mapper.Unmap(memory_pool_info);
409
410 if (process_handle) {
411 pool_mapper.ClearUseState(memory_pool_workbuffer, memory_pool_count);
412 for (auto& memory_pool : memory_pool_workbuffer) {
413 if (memory_pool.IsMapped()) {
414 pool_mapper.Unmap(memory_pool);
415 }
416 }
417
418 // dsp::ProcessCleanup
419 // close handle
420 }
421 initialized = false;
422}
423
424void System::Start() {
425 std::scoped_lock l{lock};
426 frames_elapsed = 0;
427 state = State::Started;
428 active = true;
429}
430
431void System::Stop() {
432 {
433 std::scoped_lock l{lock};
434 state = State::Stopped;
435 active = false;
436 }
437
438 if (execution_mode == ExecutionMode::Auto) {
439 // Should wait for the system to terminate here, but core timing (should have) already
440 // stopped, so this isn't needed. Find a way to make this definite.
441
442 // terminate_event.Wait();
443 }
444}
445
446Result System::Update(std::span<const u8> input, std::span<u8> performance, std::span<u8> output) {
447 std::scoped_lock l{lock};
448
449 const auto start_time{core.CoreTiming().GetClockTicks()};
450
451 InfoUpdater info_updater(input, output, process_handle, behavior);
452
453 auto result{info_updater.UpdateBehaviorInfo(behavior)};
454 if (result.IsError()) {
455 LOG_ERROR(Service_Audio, "Failed to update BehaviorInfo!");
456 return result;
457 }
458
459 result = info_updater.UpdateMemoryPools(memory_pool_workbuffer, memory_pool_count);
460 if (result.IsError()) {
461 LOG_ERROR(Service_Audio, "Failed to update MemoryPools!");
462 return result;
463 }
464
465 result = info_updater.UpdateVoiceChannelResources(voice_context);
466 if (result.IsError()) {
467 LOG_ERROR(Service_Audio, "Failed to update VoiceChannelResources!");
468 return result;
469 }
470
471 result = info_updater.UpdateVoices(voice_context, memory_pool_workbuffer, memory_pool_count);
472 if (result.IsError()) {
473 LOG_ERROR(Service_Audio, "Failed to update Voices!");
474 return result;
475 }
476
477 result = info_updater.UpdateEffects(effect_context, active, memory_pool_workbuffer,
478 memory_pool_count);
479 if (result.IsError()) {
480 LOG_ERROR(Service_Audio, "Failed to update Effects!");
481 return result;
482 }
483
484 if (behavior.IsSplitterSupported()) {
485 result = info_updater.UpdateSplitterInfo(splitter_context);
486 if (result.IsError()) {
487 LOG_ERROR(Service_Audio, "Failed to update SplitterInfo!");
488 return result;
489 }
490 }
491
492 result =
493 info_updater.UpdateMixes(mix_context, mix_buffer_count, effect_context, splitter_context);
494 if (result.IsError()) {
495 LOG_ERROR(Service_Audio, "Failed to update Mixes!");
496 return result;
497 }
498
499 result = info_updater.UpdateSinks(sink_context, memory_pool_workbuffer, memory_pool_count);
500 if (result.IsError()) {
501 LOG_ERROR(Service_Audio, "Failed to update Sinks!");
502 return result;
503 }
504
505 PerformanceManager* perf_manager{nullptr};
506 if (performance_manager.IsInitialized()) {
507 perf_manager = &performance_manager;
508 }
509
510 result =
511 info_updater.UpdatePerformanceBuffer(performance, performance.size_bytes(), perf_manager);
512 if (result.IsError()) {
513 LOG_ERROR(Service_Audio, "Failed to update PerformanceBuffer!");
514 return result;
515 }
516
517 result = info_updater.UpdateErrorInfo(behavior);
518 if (result.IsError()) {
519 LOG_ERROR(Service_Audio, "Failed to update ErrorInfo!");
520 return result;
521 }
522
523 if (behavior.IsElapsedFrameCountSupported()) {
524 result = info_updater.UpdateRendererInfo(frames_elapsed);
525 if (result.IsError()) {
526 LOG_ERROR(Service_Audio, "Failed to update RendererInfo!");
527 return result;
528 }
529 }
530
531 result = info_updater.CheckConsumedSize();
532 if (result.IsError()) {
533 LOG_ERROR(Service_Audio, "Invalid consume size!");
534 return result;
535 }
536
537 adsp_rendered_event->GetWritableEvent().Clear();
538 num_times_updated++;
539
540 const auto end_time{core.CoreTiming().GetClockTicks()};
541 ticks_spent_updating += end_time - start_time;
542
543 return ResultSuccess;
544}
545
546u32 System::GetRenderingTimeLimit() const {
547 return render_time_limit_percent;
548}
549
550void System::SetRenderingTimeLimit(const u32 limit) {
551 render_time_limit_percent = limit;
552}
553
554u32 System::GetSessionId() const {
555 return session_id;
556}
557
558u32 System::GetSampleRate() const {
559 return sample_rate;
560}
561
562u32 System::GetSampleCount() const {
563 return sample_count;
564}
565
566u32 System::GetMixBufferCount() const {
567 return mix_buffer_count;
568}
569
570ExecutionMode System::GetExecutionMode() const {
571 return execution_mode;
572}
573
574u32 System::GetRenderingDevice() const {
575 return render_device;
576}
577
578bool System::IsActive() const {
579 return active;
580}
581
582void System::SendCommandToDsp() {
583 std::scoped_lock l{lock};
584
585 if (initialized) {
586 if (active) {
587 terminate_event.Reset();
588 const auto remaining_command_count{adsp.GetRemainCommandCount(session_id)};
589 u64 command_size{0};
590
591 if (remaining_command_count) {
592 adsp_behind = true;
593 command_size = command_buffer_size;
594 } else {
595 command_size = GenerateCommand(command_workbuffer, command_workbuffer_size);
596 }
597
598 auto translated_addr{
599 memory_pool_info.Translate(CpuAddr(command_workbuffer.data()), command_size)};
600
601 auto time_limit_percent{70.0f};
602 if (behavior.IsAudioRendererProcessingTimeLimit80PercentSupported()) {
603 time_limit_percent = 80.0f;
604 } else if (behavior.IsAudioRendererProcessingTimeLimit75PercentSupported()) {
605 time_limit_percent = 75.0f;
606 } else {
607 // result ignored and 70 is used anyway
608 behavior.IsAudioRendererProcessingTimeLimit70PercentSupported();
609 time_limit_percent = 70.0f;
610 }
611
612 ADSP::CommandBuffer command_buffer{
613 .buffer{translated_addr},
614 .size{command_size},
615 .time_limit{
616 static_cast<u64>((time_limit_percent / 100) * 2'880'000.0 *
617 (static_cast<f32>(render_time_limit_percent) / 100.0f))},
618 .remaining_command_count{remaining_command_count},
619 .reset_buffers{reset_command_buffers},
620 .applet_resource_user_id{applet_resource_user_id},
621 .render_time_taken{adsp.GetRenderTimeTaken(session_id)},
622 };
623
624 adsp.SendCommandBuffer(session_id, command_buffer);
625 reset_command_buffers = false;
626 command_buffer_size = command_size;
627 if (remaining_command_count == 0) {
628 adsp_rendered_event->GetWritableEvent().Signal();
629 }
630 } else {
631 adsp.ClearRemainCount(session_id);
632 terminate_event.Set();
633 }
634 }
635}
636
637u64 System::GenerateCommand(std::span<u8> in_command_buffer,
638 [[maybe_unused]] const u64 command_buffer_size_) {
639 PoolMapper::ClearUseState(memory_pool_workbuffer, memory_pool_count);
640 const auto start_time{core.CoreTiming().GetClockTicks()};
641
642 auto command_list_header{reinterpret_cast<CommandListHeader*>(in_command_buffer.data())};
643
644 command_list_header->buffer_count = static_cast<s16>(voice_channels + mix_buffer_count);
645 command_list_header->sample_count = sample_count;
646 command_list_header->sample_rate = sample_rate;
647 command_list_header->samples_buffer = samples_workbuffer;
648
649 const auto performance_initialized{performance_manager.IsInitialized()};
650 if (performance_initialized) {
651 performance_manager.TapFrame(adsp_behind, num_voices_dropped, render_start_tick);
652 adsp_behind = false;
653 num_voices_dropped = 0;
654 render_start_tick = 0;
655 }
656
657 s8 channel_count{2};
658 if (execution_mode == ExecutionMode::Auto) {
659 const auto& sink{core.AudioCore().GetOutputSink()};
660 channel_count = static_cast<s8>(sink.GetDeviceChannels());
661 }
662
663 AudioRendererSystemContext render_context{
664 .session_id{session_id},
665 .channels{channel_count},
666 .mix_buffer_count{mix_buffer_count},
667 .behavior{&behavior},
668 .depop_buffer{depop_buffer},
669 .upsampler_manager{upsampler_manager},
670 .memory_pool_info{&memory_pool_info},
671 };
672
673 CommandBuffer command_buffer{
674 .command_list{in_command_buffer},
675 .sample_count{sample_count},
676 .sample_rate{sample_rate},
677 .size{sizeof(CommandListHeader)},
678 .count{0},
679 .estimated_process_time{0},
680 .memory_pool{&memory_pool_info},
681 .time_estimator{command_processing_time_estimator.get()},
682 .behavior{&behavior},
683 };
684
685 PerformanceManager* perf_manager{nullptr};
686 if (performance_initialized) {
687 perf_manager = &performance_manager;
688 }
689
690 CommandGenerator command_generator{command_buffer, *command_list_header, render_context,
691 voice_context, mix_context, effect_context,
692 sink_context, splitter_context, perf_manager};
693
694 voice_context.SortInfo();
695
696 const auto start_estimated_time{command_buffer.estimated_process_time};
697
698 command_generator.GenerateVoiceCommands();
699 command_generator.GenerateSubMixCommands();
700 command_generator.GenerateFinalMixCommands();
701 command_generator.GenerateSinkCommands();
702
703 if (drop_voice) {
704 f32 time_limit_percent{70.0f};
705 if (render_context.behavior->IsAudioRendererProcessingTimeLimit80PercentSupported()) {
706 time_limit_percent = 80.0f;
707 } else if (render_context.behavior
708 ->IsAudioRendererProcessingTimeLimit75PercentSupported()) {
709 time_limit_percent = 75.0f;
710 } else {
711 // result is ignored
712 render_context.behavior->IsAudioRendererProcessingTimeLimit70PercentSupported();
713 time_limit_percent = 70.0f;
714 }
715 const auto time_limit{static_cast<u32>(
716 static_cast<f32>(start_estimated_time - command_buffer.estimated_process_time) +
717 (((time_limit_percent / 100.0f) * 2'880'000.0) *
718 (static_cast<f32>(render_time_limit_percent) / 100.0f)))};
719 num_voices_dropped = DropVoices(command_buffer, start_estimated_time, time_limit);
720 }
721
722 command_list_header->buffer_size = command_buffer.size;
723 command_list_header->command_count = command_buffer.count;
724
725 voice_context.UpdateStateByDspShared();
726
727 if (render_context.behavior->IsEffectInfoVersion2Supported()) {
728 effect_context.UpdateStateByDspShared();
729 }
730
731 const auto end_time{core.CoreTiming().GetClockTicks()};
732 total_ticks_elapsed += end_time - start_time;
733 num_command_lists_generated++;
734 render_start_tick = adsp.GetRenderingStartTick(session_id);
735 frames_elapsed++;
736
737 return command_buffer.size;
738}
739
740u32 System::DropVoices(CommandBuffer& command_buffer, const u32 estimated_process_time,
741 const u32 time_limit) {
742 u32 i{0};
743 auto command_list{command_buffer.command_list.data() + sizeof(CommandListHeader)};
744 ICommand* cmd{};
745
746 for (; i < command_buffer.count; i++) {
747 cmd = reinterpret_cast<ICommand*>(command_list);
748 if (cmd->type != CommandId::Performance &&
749 cmd->type != CommandId::DataSourcePcmInt16Version1 &&
750 cmd->type != CommandId::DataSourcePcmInt16Version2 &&
751 cmd->type != CommandId::DataSourcePcmFloatVersion1 &&
752 cmd->type != CommandId::DataSourcePcmFloatVersion2 &&
753 cmd->type != CommandId::DataSourceAdpcmVersion1 &&
754 cmd->type != CommandId::DataSourceAdpcmVersion2) {
755 break;
756 }
757 command_list += cmd->size;
758 }
759
760 if (cmd == nullptr || command_buffer.count == 0 || i >= command_buffer.count) {
761 return 0;
762 }
763
764 auto voices_dropped{0};
765 while (i < command_buffer.count) {
766 const auto node_id{cmd->node_id};
767 const auto node_id_type{cmd->node_id >> 28};
768 const auto node_id_base{cmd->node_id & 0xFFF};
769
770 if (estimated_process_time <= time_limit) {
771 break;
772 }
773
774 if (node_id_type != 1) {
775 break;
776 }
777
778 auto& voice_info{voice_context.GetInfo(node_id_base)};
779 if (voice_info.priority == HighestVoicePriority) {
780 break;
781 }
782
783 voices_dropped++;
784 voice_info.voice_dropped = true;
785
786 if (i < command_buffer.count) {
787 while (cmd->node_id == node_id) {
788 if (cmd->type == CommandId::DepopPrepare) {
789 cmd->enabled = true;
790 } else if (cmd->type == CommandId::Performance || !cmd->enabled) {
791 cmd->enabled = false;
792 }
793 i++;
794 command_list += cmd->size;
795 cmd = reinterpret_cast<ICommand*>(command_list);
796 }
797 }
798 }
799 return voices_dropped;
800}
801
802} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/system.h b/src/audio_core/renderer/system.h
new file mode 100644
index 000000000..bcbe65b07
--- /dev/null
+++ b/src/audio_core/renderer/system.h
@@ -0,0 +1,307 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <memory>
7#include <mutex>
8#include <span>
9
10#include "audio_core/renderer/behavior/behavior_info.h"
11#include "audio_core/renderer/command/command_processing_time_estimator.h"
12#include "audio_core/renderer/effect/effect_context.h"
13#include "audio_core/renderer/memory/memory_pool_info.h"
14#include "audio_core/renderer/mix/mix_context.h"
15#include "audio_core/renderer/performance/performance_manager.h"
16#include "audio_core/renderer/sink/sink_context.h"
17#include "audio_core/renderer/splitter/splitter_context.h"
18#include "audio_core/renderer/upsampler/upsampler_manager.h"
19#include "audio_core/renderer/voice/voice_context.h"
20#include "common/thread.h"
21#include "core/hle/service/audio/errors.h"
22
23namespace Core {
24namespace Memory {
25class Memory;
26}
27class System;
28} // namespace Core
29
30namespace Kernel {
31class KEvent;
32class KTransferMemory;
33} // namespace Kernel
34
35namespace AudioCore {
36struct AudioRendererParameterInternal;
37
38namespace AudioRenderer {
39class CommandBuffer;
40namespace ADSP {
41class ADSP;
42}
43
44/**
45 * Audio Renderer System, the main worker for audio rendering.
46 */
47class System {
48 enum class State {
49 Started = 0,
50 Stopped = 2,
51 };
52
53public:
54 explicit System(Core::System& core, Kernel::KEvent* adsp_rendered_event);
55
56 /**
57 * Calculate the total size required for all audio render workbuffers.
58 *
59 * @param params - Input parameters with the numbers of voices/mixes/sinks/etc.
60 * @return Size (in bytes) required for the audio renderer.
61 */
62 static u64 GetWorkBufferSize(const AudioRendererParameterInternal& params);
63
64 /**
65 * Initialize the renderer system.
66 * Allocates workbuffers and initializes everything to a default state, ready to receive a
67 * RequestUpdate.
68 *
69 * @param params - Input parameters to initialize the system with.
70 * @param transfer_memory - Game-supplied memory for all workbuffers. Unused.
71 * @param transfer_memory_size - Size of the transfer memory. Unused.
72 * @param process_handle - Process handle, also used for memory. Unused.
73 * @param applet_resource_user_id - Applet id for this renderer. Unused.
74 * @param session_id - Session id of this renderer.
75 * @return Result code.
76 */
77 Result Initialize(const AudioRendererParameterInternal& params,
78 Kernel::KTransferMemory* transfer_memory, u64 transfer_memory_size,
79 u32 process_handle, u64 applet_resource_user_id, s32 session_id);
80
81 /**
82 * Finalize the system.
83 */
84 void Finalize();
85
86 /**
87 * Start the system.
88 */
89 void Start();
90
91 /**
92 * Stop the system.
93 */
94 void Stop();
95
96 /**
97 * Update the system.
98 *
99 * @param input - Inout buffer containing the update data.
100 * @param performance - Optional buffer for writing back performance metrics.
101 * @param output - Output information from rendering.
102 * @return Result code.
103 */
104 Result Update(std::span<const u8> input, std::span<u8> performance, std::span<u8> output);
105
106 /**
107 * Get the time limit (percent) for rendering
108 *
109 * @return Time limit as a percent.
110 */
111 u32 GetRenderingTimeLimit() const;
112
113 /**
114 * Set the time limit (percent) for rendering
115 *
116 * @param limit - New time limit.
117 */
118 void SetRenderingTimeLimit(u32 limit);
119
120 /**
121 * Get the session id for this system.
122 *
123 * @return Session id of this system.
124 */
125 u32 GetSessionId() const;
126
127 /**
128 * Get the sample rate of this system.
129 *
130 * @return Sample rate of this system.
131 */
132 u32 GetSampleRate() const;
133
134 /**
135 * Get the sample count of this system.
136 *
137 * @return Sample count of this system.
138 */
139 u32 GetSampleCount() const;
140
141 /**
142 * Get the number of mix buffers for this system.
143 *
144 * @return Number of mix buffers in the system.
145 */
146 u32 GetMixBufferCount() const;
147
148 /**
149 * Get the execution mode of this system.
150 * Note: Only Auto is implemented.
151 *
152 * @return Execution mode for this system.
153 */
154 ExecutionMode GetExecutionMode() const;
155
156 /**
157 * Get the rendering deivce for this system.
158 * This is unused.
159 *
160 * @return Rendering device for this system.
161 */
162 u32 GetRenderingDevice() const;
163
164 /**
165 * Check if this system is currently active.
166 *
167 * @return True if active, otherwise false.
168 */
169 bool IsActive() const;
170
171 /**
172 * Prepare and generate a list of commands for the AudioRenderer based on current state,
173 * signalling the buffer event when all processed.
174 */
175 void SendCommandToDsp();
176
177 /**
178 * Generate a list of commands for the AudioRenderer based on current state.
179 *
180 * @param command_buffer - Buffer for commands to be written to.
181 * @param command_buffer_size - Size of the command_buffer.
182 *
183 * @return Number of bytes written.
184 */
185 u64 GenerateCommand(std::span<u8> command_buffer, u64 command_buffer_size);
186
187 /**
188 * Try to drop some voices if the AudioRenderer fell behind.
189 *
190 * @param command_buffer - Command buffer to drop voices from.
191 * @param estimated_process_time - Current estimated processing time of all commands.
192 * @param time_limit - Time limit for rendering, voices are dropped if estimated
193 * exceeds this.
194 *
195 * @return Number of voices dropped.
196 */
197 u32 DropVoices(CommandBuffer& command_buffer, u32 estimated_process_time, u32 time_limit);
198
199private:
200 /// Core system
201 Core::System& core;
202 /// Reference to the ADSP for communication
203 ADSP::ADSP& adsp;
204 /// Is this system initialized?
205 bool initialized{};
206 /// Is this system currently active?
207 std::atomic<bool> active{};
208 /// State of the system
209 State state{State::Stopped};
210 /// Sample rate for the system
211 u32 sample_rate{};
212 /// Sample count of the system
213 u32 sample_count{};
214 /// Number of mix buffers in use by the system
215 s16 mix_buffer_count{};
216 /// Workbuffer for mix buffers, used by the AudioRenderer
217 std::span<s32> samples_workbuffer{};
218 /// Depop samples for depopping commands
219 std::span<s32> depop_buffer{};
220 /// Number of memory pools in the buffer
221 u32 memory_pool_count{};
222 /// Workbuffer for memory pools
223 std::span<MemoryPoolInfo> memory_pool_workbuffer{};
224 /// System memory pool info
225 MemoryPoolInfo memory_pool_info{};
226 /// Workbuffer that commands will be generated into
227 std::span<u8> command_workbuffer{};
228 /// Size of command workbuffer
229 u64 command_workbuffer_size{};
230 /// Numebr of commands in the workbuffer
231 u64 command_buffer_size{};
232 /// Manager for upsamplers
233 UpsamplerManager* upsampler_manager{};
234 /// Upsampler workbuffer
235 std::span<UpsamplerInfo> upsampler_infos{};
236 /// Number of upsamplers in the workbuffer
237 u32 upsampler_count{};
238 /// Holds and controls all voices
239 VoiceContext voice_context{};
240 /// Holds and controls all mixes
241 MixContext mix_context{};
242 /// Holds and controls all effects
243 EffectContext effect_context{};
244 /// Holds and controls all sinks
245 SinkContext sink_context{};
246 /// Holds and controls all splitters
247 SplitterContext splitter_context{};
248 /// Estimates the time taken for each command
249 std::unique_ptr<ICommandProcessingTimeEstimator> command_processing_time_estimator{};
250 /// Session id of this system
251 s32 session_id{};
252 /// Number of channels in use by voices
253 s32 voice_channels{};
254 /// Event to be called when the AudioRenderer processes a command list
255 Kernel::KEvent* adsp_rendered_event{};
256 /// Event signalled on system terminate
257 Common::Event terminate_event{};
258 /// Does what locks do
259 std::mutex lock{};
260 /// Handle for the process for this system, unused
261 u32 process_handle{};
262 /// Applet resource id for this system, unused
263 u64 applet_resource_user_id{};
264 /// Controls performance input and output
265 PerformanceManager performance_manager{};
266 /// Workbuffer for performance metrics
267 std::span<u8> performance_workbuffer{};
268 /// Main workbuffer, from which all other workbuffers here allocate into
269 std::unique_ptr<u8[]> workbuffer{};
270 /// Size of the main workbuffer
271 u64 workbuffer_size{};
272 /// Unknown buffer/marker
273 std::span<u8> unk_2A8{};
274 /// Size of the above unknown buffer/marker
275 u64 unk_2B0{};
276 /// Rendering time limit (percent)
277 u32 render_time_limit_percent{};
278 /// Should any voices be dropped?
279 bool drop_voice{};
280 /// Should the backend stream have its buffers flushed?
281 bool reset_command_buffers{};
282 /// Execution mode of this system, only Auto is supported
283 ExecutionMode execution_mode{ExecutionMode::Auto};
284 /// Render device, unused
285 u32 render_device{};
286 /// Behaviour to check which features are supported by the user revision
287 BehaviorInfo behavior{};
288 /// Total ticks the audio system has been running
289 u64 total_ticks_elapsed{};
290 /// Ticks the system has spent in updates
291 u64 ticks_spent_updating{};
292 /// Number of times a command list was generated
293 u64 num_command_lists_generated{};
294 /// Number of times the system has updated
295 u64 num_times_updated{};
296 /// Number of frames generated, written back to the game
297 std::atomic<u64> frames_elapsed{};
298 /// Is the AudioRenderer running too slow?
299 bool adsp_behind{};
300 /// Number of voices dropped
301 u32 num_voices_dropped{};
302 /// Tick that rendering started
303 u64 render_start_tick{};
304};
305
306} // namespace AudioRenderer
307} // namespace AudioCore
diff --git a/src/audio_core/renderer/system_manager.cpp b/src/audio_core/renderer/system_manager.cpp
new file mode 100644
index 000000000..b326819ed
--- /dev/null
+++ b/src/audio_core/renderer/system_manager.cpp
@@ -0,0 +1,162 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <chrono>
5
6#include "audio_core/audio_core.h"
7#include "audio_core/renderer/adsp/adsp.h"
8#include "audio_core/renderer/system_manager.h"
9#include "common/microprofile.h"
10#include "common/thread.h"
11#include "core/core.h"
12#include "core/core_timing.h"
13
14MICROPROFILE_DEFINE(Audio_RenderSystemManager, "Audio", "Render System Manager",
15 MP_RGB(60, 19, 97));
16
17namespace AudioCore::AudioRenderer {
18constexpr std::chrono::nanoseconds BaseRenderTime{5'000'000UL};
19constexpr std::chrono::nanoseconds RenderTimeOffset{400'000UL};
20
21SystemManager::SystemManager(Core::System& core_)
22 : core{core_}, adsp{core.AudioCore().GetADSP()}, mailbox{adsp.GetRenderMailbox()},
23 thread_event{Core::Timing::CreateEvent(
24 "AudioRendererSystemManager", [this](std::uintptr_t, s64 time, std::chrono::nanoseconds) {
25 return ThreadFunc2(time);
26 })} {
27 core.CoreTiming().RegisterPauseCallback([this](bool paused) { PauseCallback(paused); });
28}
29
30SystemManager::~SystemManager() {
31 Stop();
32}
33
34bool SystemManager::InitializeUnsafe() {
35 if (!active) {
36 if (adsp.Start()) {
37 active = true;
38 thread = std::jthread([this](std::stop_token stop_token) { ThreadFunc(); });
39 core.CoreTiming().ScheduleLoopingEvent(std::chrono::nanoseconds(0),
40 BaseRenderTime - RenderTimeOffset, thread_event);
41 }
42 }
43
44 return adsp.GetState() == ADSP::State::Started;
45}
46
47void SystemManager::Stop() {
48 if (!active) {
49 return;
50 }
51 core.CoreTiming().UnscheduleEvent(thread_event, {});
52 active = false;
53 update.store(true);
54 update.notify_all();
55 thread.join();
56 adsp.Stop();
57}
58
59bool SystemManager::Add(System& system_) {
60 std::scoped_lock l2{mutex2};
61
62 if (systems.size() + 1 > MaxRendererSessions) {
63 LOG_ERROR(Service_Audio, "Maximum AudioRenderer Systems active, cannot add more!");
64 return false;
65 }
66
67 {
68 std::scoped_lock l{mutex1};
69 if (systems.empty()) {
70 if (!InitializeUnsafe()) {
71 LOG_ERROR(Service_Audio, "Failed to start the AudioRenderer SystemManager");
72 return false;
73 }
74 }
75 }
76
77 systems.push_back(&system_);
78 return true;
79}
80
81bool SystemManager::Remove(System& system_) {
82 std::scoped_lock l2{mutex2};
83
84 {
85 std::scoped_lock l{mutex1};
86 if (systems.remove(&system_) == 0) {
87 LOG_ERROR(Service_Audio,
88 "Failed to remove a render system, it was not found in the list!");
89 return false;
90 }
91 }
92
93 if (systems.empty()) {
94 Stop();
95 }
96 return true;
97}
98
99void SystemManager::ThreadFunc() {
100 constexpr char name[]{"yuzu:AudioRenderSystemManager"};
101 MicroProfileOnThreadCreate(name);
102 Common::SetCurrentThreadName(name);
103 Common::SetCurrentThreadPriority(Common::ThreadPriority::High);
104 while (active) {
105 {
106 std::scoped_lock l{mutex1};
107
108 MICROPROFILE_SCOPE(Audio_RenderSystemManager);
109
110 for (auto system : systems) {
111 system->SendCommandToDsp();
112 }
113 }
114
115 adsp.Signal();
116 adsp.Wait();
117
118 update.wait(false);
119 update.store(false);
120 }
121}
122
123std::optional<std::chrono::nanoseconds> SystemManager::ThreadFunc2(s64 time) {
124 std::optional<std::chrono::nanoseconds> new_schedule_time{std::nullopt};
125 const auto queue_size{core.AudioCore().GetStreamQueue()};
126 switch (state) {
127 case StreamState::Filling:
128 if (queue_size >= 5) {
129 new_schedule_time = BaseRenderTime;
130 state = StreamState::Steady;
131 }
132 break;
133 case StreamState::Steady:
134 if (queue_size <= 2) {
135 new_schedule_time = BaseRenderTime - RenderTimeOffset;
136 state = StreamState::Filling;
137 } else if (queue_size > 5) {
138 new_schedule_time = BaseRenderTime + RenderTimeOffset;
139 state = StreamState::Draining;
140 }
141 break;
142 case StreamState::Draining:
143 if (queue_size <= 5) {
144 new_schedule_time = BaseRenderTime;
145 state = StreamState::Steady;
146 }
147 break;
148 }
149
150 update.store(true);
151 update.notify_all();
152 return new_schedule_time;
153}
154
155void SystemManager::PauseCallback(bool paused) {
156 if (paused && core.IsPoweredOn() && core.IsShuttingDown()) {
157 update.store(true);
158 update.notify_all();
159 }
160}
161
162} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/system_manager.h b/src/audio_core/renderer/system_manager.h
new file mode 100644
index 000000000..1291e9e0e
--- /dev/null
+++ b/src/audio_core/renderer/system_manager.h
@@ -0,0 +1,113 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <list>
7#include <memory>
8#include <mutex>
9#include <optional>
10#include <thread>
11
12#include "audio_core/renderer/system.h"
13
14namespace Core {
15namespace Timing {
16struct EventType;
17}
18class System;
19} // namespace Core
20
21namespace AudioCore::AudioRenderer {
22namespace ADSP {
23class ADSP;
24class AudioRenderer_Mailbox;
25} // namespace ADSP
26
27/**
28 * Manages all audio renderers, responsible for triggering command list generation and signalling
29 * the ADSP.
30 */
31class SystemManager {
32public:
33 explicit SystemManager(Core::System& core);
34 ~SystemManager();
35
36 /**
37 * Initialize the system manager, called when any system is registered.
38 *
39 * @return True if sucessfully initialized, otherwise false.
40 */
41 bool InitializeUnsafe();
42
43 /**
44 * Stop the system manager.
45 */
46 void Stop();
47
48 /**
49 * Add an audio render system to the manager.
50 * The manager does not own the system, so do not free it without calling Remove.
51 *
52 * @param system - The system to add.
53 * @return True if succesfully added, otherwise false.
54 */
55 bool Add(System& system);
56
57 /**
58 * Remove an audio render system from the manager.
59 *
60 * @param system - The system to remove.
61 * @return True if succesfully removed, otherwise false.
62 */
63 bool Remove(System& system);
64
65private:
66 /**
67 * Main thread responsible for command generation.
68 */
69 void ThreadFunc();
70
71 /**
72 * Signalling core timing thread to run ThreadFunc.
73 */
74 std::optional<std::chrono::nanoseconds> ThreadFunc2(s64 time);
75
76 /**
77 * Callback from core timing when pausing, used to detect shutdowns and stop ThreadFunc.
78 *
79 * @param paused - Are we pausing or resuming?
80 */
81 void PauseCallback(bool paused);
82
83 enum class StreamState {
84 Filling,
85 Steady,
86 Draining,
87 };
88
89 /// Core system
90 Core::System& core;
91 /// List of pointers to managed systems
92 std::list<System*> systems{};
93 /// Main worker thread for generating command lists
94 std::jthread thread;
95 /// Mutex for the systems
96 std::mutex mutex1{};
97 /// Mutex for adding/removing systems
98 std::mutex mutex2{};
99 /// Is the system manager thread active?
100 std::atomic<bool> active{};
101 /// Reference to the ADSP for communication
102 ADSP::ADSP& adsp;
103 /// AudioRenderer mailbox for communication
104 ADSP::AudioRenderer_Mailbox* mailbox{};
105 /// Core timing event to signal main thread
106 std::shared_ptr<Core::Timing::EventType> thread_event;
107 /// Atomic for main thread to wait on
108 std::atomic<bool> update{};
109 /// Current state of the streams
110 StreamState state{StreamState::Filling};
111};
112
113} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/upsampler/upsampler_info.cpp b/src/audio_core/renderer/upsampler/upsampler_info.cpp
new file mode 100644
index 000000000..e3d2f7db0
--- /dev/null
+++ b/src/audio_core/renderer/upsampler/upsampler_info.cpp
@@ -0,0 +1,6 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/upsampler/upsampler_info.h"
5
6namespace AudioCore::AudioRenderer {} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/upsampler/upsampler_info.h b/src/audio_core/renderer/upsampler/upsampler_info.h
new file mode 100644
index 000000000..a43c15af3
--- /dev/null
+++ b/src/audio_core/renderer/upsampler/upsampler_info.h
@@ -0,0 +1,35 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7
8#include "audio_core/common/common.h"
9#include "audio_core/renderer/upsampler/upsampler_state.h"
10#include "common/common_types.h"
11
12namespace AudioCore::AudioRenderer {
13class UpsamplerManager;
14
15/**
16 * Manages information needed to upsample a mix buffer.
17 */
18struct UpsamplerInfo {
19 /// States used by the AudioRenderer across calls.
20 std::array<UpsamplerState, MaxChannels> states{};
21 /// Pointer to the manager
22 UpsamplerManager* manager{};
23 /// Pointer to the samples to be upsampled
24 CpuAddr samples_pos{};
25 /// Target number of samples to upsample to
26 u32 sample_count{};
27 /// Number of channels to upsample
28 u32 input_count{};
29 /// Is this upsampler enabled?
30 bool enabled{};
31 /// Mix buffer indexes to be upsampled
32 std::array<s16, MaxChannels> inputs{};
33};
34
35} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/upsampler/upsampler_manager.cpp b/src/audio_core/renderer/upsampler/upsampler_manager.cpp
new file mode 100644
index 000000000..4c76a5066
--- /dev/null
+++ b/src/audio_core/renderer/upsampler/upsampler_manager.cpp
@@ -0,0 +1,44 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/upsampler/upsampler_manager.h"
5
6namespace AudioCore::AudioRenderer {
7
8UpsamplerManager::UpsamplerManager(const u32 count_, std::span<UpsamplerInfo> infos_,
9 std::span<s32> workbuffer_)
10 : count{count_}, upsampler_infos{infos_}, workbuffer{workbuffer_} {}
11
12UpsamplerInfo* UpsamplerManager::Allocate() {
13 std::scoped_lock l{lock};
14
15 if (count == 0) {
16 return nullptr;
17 }
18
19 u32 free_index{0};
20 for (auto& upsampler : upsampler_infos) {
21 if (!upsampler.enabled) {
22 break;
23 }
24 free_index++;
25 }
26
27 if (free_index >= count) {
28 return nullptr;
29 }
30
31 auto& upsampler{upsampler_infos[free_index]};
32 upsampler.manager = this;
33 upsampler.sample_count = TargetSampleCount;
34 upsampler.samples_pos = CpuAddr(&workbuffer[upsampler.sample_count * MaxChannels]);
35 upsampler.enabled = true;
36 return &upsampler;
37}
38
39void UpsamplerManager::Free(UpsamplerInfo* info) {
40 std::scoped_lock l{lock};
41 info->enabled = false;
42}
43
44} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/upsampler/upsampler_manager.h b/src/audio_core/renderer/upsampler/upsampler_manager.h
new file mode 100644
index 000000000..70cd42b08
--- /dev/null
+++ b/src/audio_core/renderer/upsampler/upsampler_manager.h
@@ -0,0 +1,45 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <mutex>
7#include <span>
8
9#include "audio_core/renderer/upsampler/upsampler_info.h"
10#include "common/common_types.h"
11
12namespace AudioCore::AudioRenderer {
13/**
14 * Manages and has utility functions for upsampler infos.
15 */
16class UpsamplerManager {
17public:
18 UpsamplerManager(u32 count, std::span<UpsamplerInfo> infos, std::span<s32> workbuffer);
19
20 /**
21 * Allocate a new UpsamplerInfo.
22 *
23 * @return The allocated upsampler, may be nullptr if alloc failed.
24 */
25 UpsamplerInfo* Allocate();
26
27 /**
28 * Free the given upsampler.
29 *
30 * @param The upsampler to be freed.
31 */
32 void Free(UpsamplerInfo* info);
33
34private:
35 /// Maximum number of upsamplers in the buffer
36 const u32 count;
37 /// Upsamplers buffer
38 std::span<UpsamplerInfo> upsampler_infos;
39 /// Workbuffer for upsampling samples
40 std::span<s32> workbuffer;
41 /// Lock for allocate/free
42 std::mutex lock{};
43};
44
45} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/upsampler/upsampler_state.h b/src/audio_core/renderer/upsampler/upsampler_state.h
new file mode 100644
index 000000000..28cebe200
--- /dev/null
+++ b/src/audio_core/renderer/upsampler/upsampler_state.h
@@ -0,0 +1,40 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7
8#include "common/common_types.h"
9#include "common/fixed_point.h"
10
11namespace AudioCore::AudioRenderer {
12/**
13 * Upsampling state used by the AudioRenderer across calls.
14 */
15struct UpsamplerState {
16 static constexpr u16 HistorySize = 20;
17
18 /// Source data to target data ratio. E.g 48'000/32'000 = 1.5
19 Common::FixedPoint<16, 16> ratio;
20 /// Sample history
21 std::array<Common::FixedPoint<24, 8>, HistorySize> history;
22 /// Size of the sinc coefficient window
23 u16 window_size;
24 /// Read index for the history
25 u16 history_output_index;
26 /// Write index for the history
27 u16 history_input_index;
28 /// Start offset within the history, fixed to 0
29 u16 history_start_index;
30 /// Ebd offset within the history, fixed to HistorySize
31 u16 history_end_index;
32 /// Is this state initialized?
33 bool initialized;
34 /// Index of the current sample.
35 /// E.g 16K -> 48K has a ratio of 3, so this will be 0-2.
36 /// See the Upsample command in the AudioRenderer for more information.
37 u8 sample_index;
38};
39
40} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/voice/voice_channel_resource.h b/src/audio_core/renderer/voice/voice_channel_resource.h
new file mode 100644
index 000000000..26ab4ccce
--- /dev/null
+++ b/src/audio_core/renderer/voice/voice_channel_resource.h
@@ -0,0 +1,38 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7
8#include "audio_core/common/common.h"
9#include "common/common_types.h"
10
11namespace AudioCore::AudioRenderer {
12/**
13 * Represents one channel for mixing a voice.
14 */
15class VoiceChannelResource {
16public:
17 struct InParameter {
18 /* 0x00 */ u32 id;
19 /* 0x04 */ std::array<f32, MaxMixBuffers> mix_volumes;
20 /* 0x64 */ bool in_use;
21 /* 0x65 */ char unk65[0xB];
22 };
23 static_assert(sizeof(InParameter) == 0x70,
24 "VoiceChannelResource::InParameter has the wrong size!");
25
26 explicit VoiceChannelResource(u32 id_) : id{id_} {}
27
28 /// Current volume for each mix buffer
29 std::array<f32, MaxMixBuffers> mix_volumes{};
30 /// Previous volume for each mix buffer
31 std::array<f32, MaxMixBuffers> prev_mix_volumes{};
32 /// Id of this resource
33 const u32 id;
34 /// Is this resource in use?
35 bool in_use{};
36};
37
38} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/voice/voice_context.cpp b/src/audio_core/renderer/voice/voice_context.cpp
new file mode 100644
index 000000000..eafb51b01
--- /dev/null
+++ b/src/audio_core/renderer/voice/voice_context.cpp
@@ -0,0 +1,86 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <ranges>
5
6#include "audio_core/renderer/voice/voice_context.h"
7
8namespace AudioCore::AudioRenderer {
9
10VoiceState& VoiceContext::GetDspSharedState(const u32 index) {
11 if (index >= dsp_states.size()) {
12 LOG_ERROR(Service_Audio, "Invalid voice dsp state index {:04X}", index);
13 }
14 return dsp_states[index];
15}
16
17VoiceChannelResource& VoiceContext::GetChannelResource(const u32 index) {
18 if (index >= channel_resources.size()) {
19 LOG_ERROR(Service_Audio, "Invalid voice channel resource index {:04X}", index);
20 }
21 return channel_resources[index];
22}
23
24void VoiceContext::Initialize(std::span<VoiceInfo*> sorted_voice_infos_,
25 std::span<VoiceInfo> voice_infos_,
26 std::span<VoiceChannelResource> voice_channel_resources_,
27 std::span<VoiceState> cpu_states_, std::span<VoiceState> dsp_states_,
28 const u32 voice_count_) {
29 sorted_voice_info = sorted_voice_infos_;
30 voices = voice_infos_;
31 channel_resources = voice_channel_resources_;
32 cpu_states = cpu_states_;
33 dsp_states = dsp_states_;
34 voice_count = voice_count_;
35 active_count = 0;
36}
37
38VoiceInfo* VoiceContext::GetSortedInfo(const u32 index) {
39 if (index >= sorted_voice_info.size()) {
40 LOG_ERROR(Service_Audio, "Invalid voice sorted info index {:04X}", index);
41 }
42 return sorted_voice_info[index];
43}
44
45VoiceInfo& VoiceContext::GetInfo(const u32 index) {
46 if (index >= voices.size()) {
47 LOG_ERROR(Service_Audio, "Invalid voice info index {:04X}", index);
48 }
49 return voices[index];
50}
51
52VoiceState& VoiceContext::GetState(const u32 index) {
53 if (index >= cpu_states.size()) {
54 LOG_ERROR(Service_Audio, "Invalid voice cpu state index {:04X}", index);
55 }
56 return cpu_states[index];
57}
58
59u32 VoiceContext::GetCount() const {
60 return voice_count;
61}
62
63u32 VoiceContext::GetActiveCount() const {
64 return active_count;
65}
66
67void VoiceContext::SetActiveCount(const u32 active_count_) {
68 active_count = active_count_;
69}
70
71void VoiceContext::SortInfo() {
72 for (u32 i = 0; i < voice_count; i++) {
73 sorted_voice_info[i] = &voices[i];
74 }
75
76 std::ranges::sort(sorted_voice_info, [](const VoiceInfo* a, const VoiceInfo* b) {
77 return a->priority != b->priority ? a->priority < b->priority
78 : a->sort_order < b->sort_order;
79 });
80}
81
82void VoiceContext::UpdateStateByDspShared() {
83 std::memcpy(cpu_states.data(), dsp_states.data(), voice_count * sizeof(VoiceState));
84}
85
86} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/voice/voice_context.h b/src/audio_core/renderer/voice/voice_context.h
new file mode 100644
index 000000000..43b677154
--- /dev/null
+++ b/src/audio_core/renderer/voice/voice_context.h
@@ -0,0 +1,126 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <span>
7
8#include "audio_core/renderer/voice/voice_channel_resource.h"
9#include "audio_core/renderer/voice/voice_info.h"
10#include "audio_core/renderer/voice/voice_state.h"
11#include "common/common_types.h"
12
13namespace AudioCore::AudioRenderer {
14/**
15 * Contains all voices, with utility functions for managing them.
16 */
17class VoiceContext {
18public:
19 /**
20 * Get the AudioRenderer state for a given index
21 *
22 * @param index - State index to get.
23 * @return The requested voice state.
24 */
25 VoiceState& GetDspSharedState(u32 index);
26
27 /**
28 * Get the channel resource for a given index
29 *
30 * @param index - Resource index to get.
31 * @return The requested voice resource.
32 */
33 VoiceChannelResource& GetChannelResource(u32 index);
34
35 /**
36 * Initialize the voice context.
37 *
38 * @param sorted_voice_infos - Workbuffer for the sorted voices.
39 * @param voice_infos - Workbuffer for the voices.
40 * @param voice_channel_resources - Workbuffer for the voice channel resources.
41 * @param cpu_states - Workbuffer for the host-side voice states.
42 * @param dsp_states - Workbuffer for the AudioRenderer-side voice states.
43 * @param voice_count - The number of voices in each workbuffer.
44 */
45 void Initialize(std::span<VoiceInfo*> sorted_voice_infos, std::span<VoiceInfo> voice_infos,
46 std::span<VoiceChannelResource> voice_channel_resources,
47 std::span<VoiceState> cpu_states, std::span<VoiceState> dsp_states,
48 u32 voice_count);
49
50 /**
51 * Get a sorted voice with the given index.
52 *
53 * @param index - The sorted voice index to get.
54 * @return The sorted voice.
55 */
56 VoiceInfo* GetSortedInfo(u32 index);
57
58 /**
59 * Get a voice with the given index.
60 *
61 * @param index - The voice index to get.
62 * @return The voice.
63 */
64 VoiceInfo& GetInfo(u32 index);
65
66 /**
67 * Get a host voice state with the given index.
68 *
69 * @param index - The host voice state index to get.
70 * @return The voice state.
71 */
72 VoiceState& GetState(u32 index);
73
74 /**
75 * Get the maximum number of voices.
76 * Not all voices in the buffers may be in use, see GetActiveCount.
77 *
78 * @return The maximum number of voices.
79 */
80 u32 GetCount() const;
81
82 /**
83 * Get the number of active voices.
84 * Can be less than or equal to the maximum number of voices.
85 *
86 * @return The number of active voices.
87 */
88 u32 GetActiveCount() const;
89
90 /**
91 * Set the number of active voices.
92 * Can be less than or equal to the maximum number of voices.
93 *
94 * @param active_count - The new number of active voices.
95 */
96 void SetActiveCount(u32 active_count);
97
98 /**
99 * Sort all voices. Results are available via GetSortedInfo.
100 * Voices are sorted descendingly, according to priority, and then sort order.
101 */
102 void SortInfo();
103
104 /**
105 * Update all voice states, copying AudioRenderer-side states to host-side states.
106 */
107 void UpdateStateByDspShared();
108
109private:
110 /// Sorted voices
111 std::span<VoiceInfo*> sorted_voice_info{};
112 /// Voices
113 std::span<VoiceInfo> voices{};
114 /// Channel resources
115 std::span<VoiceChannelResource> channel_resources{};
116 /// Host-side voice states
117 std::span<VoiceState> cpu_states{};
118 /// AudioRenderer-side voice states
119 std::span<VoiceState> dsp_states{};
120 /// Maximum number of voices
121 u32 voice_count{};
122 /// Number of active voices
123 u32 active_count{};
124};
125
126} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/voice/voice_info.cpp b/src/audio_core/renderer/voice/voice_info.cpp
new file mode 100644
index 000000000..1849eeb57
--- /dev/null
+++ b/src/audio_core/renderer/voice/voice_info.cpp
@@ -0,0 +1,408 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/renderer/memory/pool_mapper.h"
5#include "audio_core/renderer/voice/voice_context.h"
6#include "audio_core/renderer/voice/voice_info.h"
7#include "audio_core/renderer/voice/voice_state.h"
8
9namespace AudioCore::AudioRenderer {
10
11VoiceInfo::VoiceInfo() {
12 Initialize();
13}
14
15void VoiceInfo::Initialize() {
16 in_use = false;
17 is_new = false;
18 id = 0;
19 node_id = 0;
20 current_play_state = ServerPlayState::Stopped;
21 src_quality = SrcQuality::Medium;
22 priority = LowestVoicePriority;
23 sample_format = SampleFormat::Invalid;
24 sample_rate = 0;
25 channel_count = 0;
26 wave_buffer_count = 0;
27 wave_buffer_index = 0;
28 pitch = 0.0f;
29 volume = 0.0f;
30 prev_volume = 0.0f;
31 mix_id = UnusedMixId;
32 splitter_id = UnusedSplitterId;
33 biquads = {};
34 biquad_initialized = {};
35 voice_dropped = false;
36 data_unmapped = false;
37 buffer_unmapped = false;
38 flush_buffer_count = 0;
39
40 data_address.Setup(0, 0);
41 for (auto& wavebuffer : wavebuffers) {
42 wavebuffer.Initialize();
43 }
44}
45
46bool VoiceInfo::ShouldUpdateParameters(const InParameter& params) const {
47 return data_address.GetCpuAddr() != params.src_data_address ||
48 data_address.GetSize() != params.src_data_size || data_unmapped;
49}
50
51void VoiceInfo::UpdateParameters(BehaviorInfo::ErrorInfo& error_info, const InParameter& params,
52 const PoolMapper& pool_mapper, const BehaviorInfo& behavior) {
53 in_use = params.in_use;
54 id = params.id;
55 node_id = params.node_id;
56 UpdatePlayState(params.play_state);
57 UpdateSrcQuality(params.src_quality);
58 priority = params.priority;
59 sort_order = params.sort_order;
60 sample_rate = params.sample_rate;
61 sample_format = params.sample_format;
62 channel_count = static_cast<s8>(params.channel_count);
63 pitch = params.pitch;
64 volume = params.volume;
65 biquads = params.biquads;
66 wave_buffer_count = params.wave_buffer_count;
67 wave_buffer_index = params.wave_buffer_index;
68
69 if (behavior.IsFlushVoiceWaveBuffersSupported()) {
70 flush_buffer_count += params.flush_buffer_count;
71 }
72
73 mix_id = params.mix_id;
74
75 if (behavior.IsSplitterSupported()) {
76 splitter_id = params.splitter_id;
77 } else {
78 splitter_id = UnusedSplitterId;
79 }
80
81 channel_resource_ids = params.channel_resource_ids;
82
83 flags &= u16(~0b11);
84 if (behavior.IsVoicePlayedSampleCountResetAtLoopPointSupported()) {
85 flags |= u16(params.flags.IsVoicePlayedSampleCountResetAtLoopPointSupported);
86 }
87
88 if (behavior.IsVoicePitchAndSrcSkippedSupported()) {
89 flags |= u16(params.flags.IsVoicePitchAndSrcSkippedSupported);
90 }
91
92 if (params.clear_voice_drop) {
93 voice_dropped = false;
94 }
95
96 if (ShouldUpdateParameters(params)) {
97 data_unmapped = !pool_mapper.TryAttachBuffer(error_info, data_address,
98 params.src_data_address, params.src_data_size);
99 } else {
100 error_info.error_code = ResultSuccess;
101 error_info.address = CpuAddr(0);
102 }
103}
104
105void VoiceInfo::UpdatePlayState(const PlayState state) {
106 last_play_state = current_play_state;
107
108 switch (state) {
109 case PlayState::Started:
110 current_play_state = ServerPlayState::Started;
111 break;
112 case PlayState::Stopped:
113 if (current_play_state != ServerPlayState::Stopped) {
114 current_play_state = ServerPlayState::RequestStop;
115 }
116 break;
117 case PlayState::Paused:
118 current_play_state = ServerPlayState::Paused;
119 break;
120 default:
121 LOG_ERROR(Service_Audio, "Invalid input play state {}", static_cast<u32>(state));
122 break;
123 }
124}
125
126void VoiceInfo::UpdateSrcQuality(const SrcQuality quality) {
127 switch (quality) {
128 case SrcQuality::Medium:
129 src_quality = quality;
130 break;
131 case SrcQuality::High:
132 src_quality = quality;
133 break;
134 case SrcQuality::Low:
135 src_quality = quality;
136 break;
137 default:
138 LOG_ERROR(Service_Audio, "Invalid input src quality {}", static_cast<u32>(quality));
139 break;
140 }
141}
142
143void VoiceInfo::UpdateWaveBuffers(std::span<std::array<BehaviorInfo::ErrorInfo, 2>> error_infos,
144 [[maybe_unused]] u32 error_count, const InParameter& params,
145 std::span<VoiceState*> voice_states,
146 const PoolMapper& pool_mapper, const BehaviorInfo& behavior) {
147 if (params.is_new) {
148 for (size_t i = 0; i < wavebuffers.size(); i++) {
149 wavebuffers[i].Initialize();
150 }
151
152 for (s8 channel = 0; channel < static_cast<s8>(params.channel_count); channel++) {
153 voice_states[channel]->wave_buffer_valid.fill(false);
154 }
155 }
156
157 for (u32 i = 0; i < MaxWaveBuffers; i++) {
158 UpdateWaveBuffer(error_infos[i], wavebuffers[i], params.wave_buffer_internal[i],
159 params.sample_format, voice_states[0]->wave_buffer_valid[i], pool_mapper,
160 behavior);
161 }
162}
163
164void VoiceInfo::UpdateWaveBuffer(std::span<BehaviorInfo::ErrorInfo> error_info,
165 WaveBuffer& wave_buffer,
166 const WaveBufferInternal& wave_buffer_internal,
167 const SampleFormat sample_format_, const bool valid,
168 const PoolMapper& pool_mapper, const BehaviorInfo& behavior) {
169 if (!valid && wave_buffer.sent_to_DSP && wave_buffer.buffer_address.GetCpuAddr() != 0) {
170 pool_mapper.ForceUnmapPointer(wave_buffer.buffer_address);
171 wave_buffer.buffer_address.Setup(0, 0);
172 }
173
174 if (!ShouldUpdateWaveBuffer(wave_buffer_internal)) {
175 return;
176 }
177
178 switch (sample_format_) {
179 case SampleFormat::PcmInt16: {
180 constexpr auto byte_size{GetSampleFormatByteSize(SampleFormat::PcmInt16)};
181 if (wave_buffer_internal.start_offset * byte_size > wave_buffer_internal.size ||
182 wave_buffer_internal.end_offset * byte_size > wave_buffer_internal.size) {
183 LOG_ERROR(Service_Audio, "Invalid PCM16 start/end wavebuffer sizes!");
184 error_info[0].error_code = Service::Audio::ERR_INVALID_UPDATE_DATA;
185 error_info[0].address = wave_buffer_internal.address;
186 return;
187 }
188 } break;
189
190 case SampleFormat::PcmFloat: {
191 constexpr auto byte_size{GetSampleFormatByteSize(SampleFormat::PcmFloat)};
192 if (wave_buffer_internal.start_offset * byte_size > wave_buffer_internal.size ||
193 wave_buffer_internal.end_offset * byte_size > wave_buffer_internal.size) {
194 LOG_ERROR(Service_Audio, "Invalid PCMFloat start/end wavebuffer sizes!");
195 error_info[0].error_code = Service::Audio::ERR_INVALID_UPDATE_DATA;
196 error_info[0].address = wave_buffer_internal.address;
197 return;
198 }
199 } break;
200
201 case SampleFormat::Adpcm: {
202 const auto start_frame{wave_buffer_internal.start_offset / 14};
203 auto start_extra{wave_buffer_internal.start_offset % 14 == 0
204 ? 0
205 : (wave_buffer_internal.start_offset % 14) / 2 + 1 +
206 ((wave_buffer_internal.start_offset % 14) % 2)};
207 const auto start{start_frame * 8 + start_extra};
208
209 const auto end_frame{wave_buffer_internal.end_offset / 14};
210 const auto end_extra{wave_buffer_internal.end_offset % 14 == 0
211 ? 0
212 : (wave_buffer_internal.end_offset % 14) / 2 + 1 +
213 ((wave_buffer_internal.end_offset % 14) % 2)};
214 const auto end{end_frame * 8 + end_extra};
215
216 if (start > static_cast<s64>(wave_buffer_internal.size) ||
217 end > static_cast<s64>(wave_buffer_internal.size)) {
218 LOG_ERROR(Service_Audio, "Invalid ADPCM start/end wavebuffer sizes!");
219 error_info[0].error_code = Service::Audio::ERR_INVALID_UPDATE_DATA;
220 error_info[0].address = wave_buffer_internal.address;
221 return;
222 }
223 } break;
224
225 default:
226 break;
227 }
228
229 if (wave_buffer_internal.start_offset < 0 || wave_buffer_internal.end_offset < 0) {
230 LOG_ERROR(Service_Audio, "Invalid input start/end wavebuffer sizes!");
231 error_info[0].error_code = Service::Audio::ERR_INVALID_UPDATE_DATA;
232 error_info[0].address = wave_buffer_internal.address;
233 return;
234 }
235
236 wave_buffer.start_offset = wave_buffer_internal.start_offset;
237 wave_buffer.end_offset = wave_buffer_internal.end_offset;
238 wave_buffer.loop = wave_buffer_internal.loop;
239 wave_buffer.stream_ended = wave_buffer_internal.stream_ended;
240 wave_buffer.sent_to_DSP = false;
241 wave_buffer.loop_start_offset = wave_buffer_internal.loop_start;
242 wave_buffer.loop_end_offset = wave_buffer_internal.loop_end;
243 wave_buffer.loop_count = wave_buffer_internal.loop_count;
244
245 buffer_unmapped =
246 !pool_mapper.TryAttachBuffer(error_info[0], wave_buffer.buffer_address,
247 wave_buffer_internal.address, wave_buffer_internal.size);
248
249 if (sample_format_ == SampleFormat::Adpcm && behavior.IsAdpcmLoopContextBugFixed() &&
250 wave_buffer_internal.context_address != 0) {
251 buffer_unmapped = !pool_mapper.TryAttachBuffer(error_info[1], wave_buffer.context_address,
252 wave_buffer_internal.context_address,
253 wave_buffer_internal.context_size) ||
254 data_unmapped;
255 } else {
256 wave_buffer.context_address.Setup(0, 0);
257 }
258}
259
260bool VoiceInfo::ShouldUpdateWaveBuffer(const WaveBufferInternal& wave_buffer_internal) const {
261 return !wave_buffer_internal.sent_to_DSP || buffer_unmapped;
262}
263
264void VoiceInfo::WriteOutStatus(OutStatus& out_status, const InParameter& params,
265 std::span<VoiceState*> voice_states) {
266 if (params.is_new) {
267 is_new = true;
268 }
269
270 if (params.is_new || is_new) {
271 out_status.played_sample_count = 0;
272 out_status.wave_buffers_consumed = 0;
273 out_status.voice_dropped = false;
274 } else {
275 out_status.played_sample_count = voice_states[0]->played_sample_count;
276 out_status.wave_buffers_consumed = voice_states[0]->wave_buffers_consumed;
277 out_status.voice_dropped = voice_dropped;
278 }
279}
280
281bool VoiceInfo::ShouldSkip() const {
282 return !in_use || wave_buffer_count == 0 || data_unmapped || buffer_unmapped || voice_dropped;
283}
284
285bool VoiceInfo::HasAnyConnection() const {
286 return mix_id != UnusedMixId || splitter_id != UnusedSplitterId;
287}
288
289void VoiceInfo::FlushWaveBuffers(const u32 flush_count, std::span<VoiceState*> voice_states,
290 const s8 channel_count_) {
291 auto wave_index{wave_buffer_index};
292
293 for (size_t i = 0; i < flush_count; i++) {
294 wavebuffers[wave_index].sent_to_DSP = true;
295
296 for (s8 j = 0; j < channel_count_; j++) {
297 auto voice_state{voice_states[j]};
298 if (voice_state->wave_buffer_index == wave_index) {
299 voice_state->wave_buffer_index =
300 (voice_state->wave_buffer_index + 1) % MaxWaveBuffers;
301 voice_state->wave_buffers_consumed++;
302 }
303 voice_state->wave_buffer_valid[wave_index] = false;
304 }
305
306 wave_index = (wave_index + 1) % MaxWaveBuffers;
307 }
308}
309
310bool VoiceInfo::UpdateParametersForCommandGeneration(std::span<VoiceState*> voice_states) {
311 if (flush_buffer_count > 0) {
312 FlushWaveBuffers(flush_buffer_count, voice_states, channel_count);
313 flush_buffer_count = 0;
314 }
315
316 switch (current_play_state) {
317 case ServerPlayState::Started:
318 for (u32 i = 0; i < MaxWaveBuffers; i++) {
319 if (!wavebuffers[i].sent_to_DSP) {
320 for (s8 channel = 0; channel < channel_count; channel++) {
321 voice_states[channel]->wave_buffer_valid[i] = true;
322 }
323 wavebuffers[i].sent_to_DSP = true;
324 }
325 }
326
327 was_playing = false;
328
329 for (u32 i = 0; i < MaxWaveBuffers; i++) {
330 if (voice_states[0]->wave_buffer_valid[i]) {
331 return true;
332 }
333 }
334 break;
335
336 case ServerPlayState::Stopped:
337 case ServerPlayState::Paused:
338 for (auto& wavebuffer : wavebuffers) {
339 if (!wavebuffer.sent_to_DSP) {
340 wavebuffer.buffer_address.GetReference(true);
341 wavebuffer.context_address.GetReference(true);
342 }
343 }
344
345 if (sample_format == SampleFormat::Adpcm && data_address.GetCpuAddr() != 0) {
346 data_address.GetReference(true);
347 }
348
349 was_playing = last_play_state == ServerPlayState::Started;
350 break;
351
352 case ServerPlayState::RequestStop:
353 for (u32 i = 0; i < MaxWaveBuffers; i++) {
354 wavebuffers[i].sent_to_DSP = true;
355
356 for (s8 channel = 0; channel < channel_count; channel++) {
357 if (voice_states[channel]->wave_buffer_valid[i]) {
358 voice_states[channel]->wave_buffer_index =
359 (voice_states[channel]->wave_buffer_index + 1) % MaxWaveBuffers;
360 voice_states[channel]->wave_buffers_consumed++;
361 }
362 voice_states[channel]->wave_buffer_valid[i] = false;
363 }
364 }
365
366 for (s8 channel = 0; channel < channel_count; channel++) {
367 voice_states[channel]->offset = 0;
368 voice_states[channel]->played_sample_count = 0;
369 voice_states[channel]->adpcm_context = {};
370 voice_states[channel]->sample_history.fill(0);
371 voice_states[channel]->fraction = 0;
372 }
373
374 current_play_state = ServerPlayState::Stopped;
375 was_playing = last_play_state == ServerPlayState::Started;
376 break;
377 }
378
379 return was_playing;
380}
381
382bool VoiceInfo::UpdateForCommandGeneration(VoiceContext& voice_context) {
383 std::array<VoiceState*, MaxChannels> voice_states{};
384
385 if (is_new) {
386 ResetResources(voice_context);
387 prev_volume = volume;
388 is_new = false;
389 }
390
391 for (s8 channel = 0; channel < channel_count; channel++) {
392 voice_states[channel] = &voice_context.GetDspSharedState(channel_resource_ids[channel]);
393 }
394
395 return UpdateParametersForCommandGeneration(voice_states);
396}
397
398void VoiceInfo::ResetResources(VoiceContext& voice_context) const {
399 for (s8 channel = 0; channel < channel_count; channel++) {
400 auto& state{voice_context.GetDspSharedState(channel_resource_ids[channel])};
401 state = {};
402
403 auto& channel_resource{voice_context.GetChannelResource(channel_resource_ids[channel])};
404 channel_resource.prev_mix_volumes = channel_resource.mix_volumes;
405 }
406}
407
408} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/voice/voice_info.h b/src/audio_core/renderer/voice/voice_info.h
new file mode 100644
index 000000000..896723e0c
--- /dev/null
+++ b/src/audio_core/renderer/voice/voice_info.h
@@ -0,0 +1,378 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7#include <bitset>
8
9#include "audio_core/common/common.h"
10#include "audio_core/common/wave_buffer.h"
11#include "audio_core/renderer/behavior/behavior_info.h"
12#include "audio_core/renderer/memory/address_info.h"
13#include "common/common_types.h"
14
15namespace AudioCore::AudioRenderer {
16class PoolMapper;
17class VoiceContext;
18struct VoiceState;
19
20/**
21 * Represents one voice. Voices are essentially noises, and they can be further mixed and have
22 * effects applied to them, but voices are the basis of all sounds.
23 */
24class VoiceInfo {
25public:
26 enum class ServerPlayState {
27 Started,
28 Stopped,
29 RequestStop,
30 Paused,
31 };
32
33 struct Flags {
34 u8 IsVoicePlayedSampleCountResetAtLoopPointSupported : 1;
35 u8 IsVoicePitchAndSrcSkippedSupported : 1;
36 };
37
38 /**
39 * A wavebuffer contains information on the data source buffers.
40 */
41 struct WaveBuffer {
42 void Copy(WaveBufferVersion1& other) {
43 other.buffer = buffer_address.GetReference(true);
44 other.buffer_size = buffer_address.GetSize();
45 other.start_offset = start_offset;
46 other.end_offset = end_offset;
47 other.loop = loop;
48 other.stream_ended = stream_ended;
49
50 if (context_address.GetCpuAddr()) {
51 other.context = context_address.GetReference(true);
52 other.context_size = context_address.GetSize();
53 } else {
54 other.context = CpuAddr(0);
55 other.context_size = 0;
56 }
57 }
58
59 void Copy(WaveBufferVersion2& other) {
60 other.buffer = buffer_address.GetReference(true);
61 other.buffer_size = buffer_address.GetSize();
62 other.start_offset = start_offset;
63 other.end_offset = end_offset;
64 other.loop_start_offset = loop_start_offset;
65 other.loop_end_offset = loop_end_offset;
66 other.loop = loop;
67 other.loop_count = loop_count;
68 other.stream_ended = stream_ended;
69
70 if (context_address.GetCpuAddr()) {
71 other.context = context_address.GetReference(true);
72 other.context_size = context_address.GetSize();
73 } else {
74 other.context = CpuAddr(0);
75 other.context_size = 0;
76 }
77 }
78
79 void Initialize() {
80 buffer_address.Setup(0, 0);
81 context_address.Setup(0, 0);
82 start_offset = 0;
83 end_offset = 0;
84 loop = false;
85 stream_ended = false;
86 sent_to_DSP = true;
87 loop_start_offset = 0;
88 loop_end_offset = 0;
89 loop_count = 0;
90 }
91 /// Game memory address of the wavebuffer data
92 AddressInfo buffer_address{0, 0};
93 /// Context for decoding, used for ADPCM
94 AddressInfo context_address{0, 0};
95 /// Starting offset for the wavebuffer
96 u32 start_offset{};
97 /// Ending offset the wavebuffer
98 u32 end_offset{};
99 /// Should this wavebuffer loop?
100 bool loop{};
101 /// Has this wavebuffer ended?
102 bool stream_ended{};
103 /// Has this wavebuffer been sent to the AudioRenderer?
104 bool sent_to_DSP{true};
105 /// Starting offset when looping, can differ from start_offset
106 u32 loop_start_offset{};
107 /// Ending offset when looping, can differ from end_offset
108 u32 loop_end_offset{};
109 /// Number of times to loop this wavebuffer
110 s32 loop_count{};
111 };
112
113 struct WaveBufferInternal {
114 /* 0x00 */ CpuAddr address;
115 /* 0x08 */ u64 size;
116 /* 0x10 */ s32 start_offset;
117 /* 0x14 */ s32 end_offset;
118 /* 0x18 */ bool loop;
119 /* 0x19 */ bool stream_ended;
120 /* 0x1A */ bool sent_to_DSP;
121 /* 0x1C */ s32 loop_count;
122 /* 0x20 */ CpuAddr context_address;
123 /* 0x28 */ u64 context_size;
124 /* 0x30 */ u32 loop_start;
125 /* 0x34 */ u32 loop_end;
126 };
127 static_assert(sizeof(WaveBufferInternal) == 0x38,
128 "VoiceInfo::WaveBufferInternal has the wrong size!");
129
130 struct BiquadFilterParameter {
131 /* 0x00 */ bool enabled;
132 /* 0x02 */ std::array<s16, 3> b;
133 /* 0x08 */ std::array<s16, 2> a;
134 };
135 static_assert(sizeof(BiquadFilterParameter) == 0xC,
136 "VoiceInfo::BiquadFilterParameter has the wrong size!");
137
138 struct InParameter {
139 /* 0x000 */ u32 id;
140 /* 0x004 */ u32 node_id;
141 /* 0x008 */ bool is_new;
142 /* 0x009 */ bool in_use;
143 /* 0x00A */ PlayState play_state;
144 /* 0x00B */ SampleFormat sample_format;
145 /* 0x00C */ u32 sample_rate;
146 /* 0x010 */ s32 priority;
147 /* 0x014 */ s32 sort_order;
148 /* 0x018 */ u32 channel_count;
149 /* 0x01C */ f32 pitch;
150 /* 0x020 */ f32 volume;
151 /* 0x024 */ std::array<BiquadFilterParameter, MaxBiquadFilters> biquads;
152 /* 0x03C */ u32 wave_buffer_count;
153 /* 0x040 */ u16 wave_buffer_index;
154 /* 0x042 */ char unk042[0x6];
155 /* 0x048 */ CpuAddr src_data_address;
156 /* 0x050 */ u64 src_data_size;
157 /* 0x058 */ u32 mix_id;
158 /* 0x05C */ u32 splitter_id;
159 /* 0x060 */ std::array<WaveBufferInternal, MaxWaveBuffers> wave_buffer_internal;
160 /* 0x140 */ std::array<u32, MaxChannels> channel_resource_ids;
161 /* 0x158 */ bool clear_voice_drop;
162 /* 0x159 */ u8 flush_buffer_count;
163 /* 0x15A */ char unk15A[0x2];
164 /* 0x15C */ Flags flags;
165 /* 0x15D */ char unk15D[0x1];
166 /* 0x15E */ SrcQuality src_quality;
167 /* 0x15F */ char unk15F[0x11];
168 };
169 static_assert(sizeof(InParameter) == 0x170, "VoiceInfo::InParameter has the wrong size!");
170
171 struct OutStatus {
172 /* 0x00 */ u64 played_sample_count;
173 /* 0x08 */ u32 wave_buffers_consumed;
174 /* 0x0C */ bool voice_dropped;
175 };
176 static_assert(sizeof(OutStatus) == 0x10, "OutStatus::InParameter has the wrong size!");
177
178 VoiceInfo();
179
180 /**
181 * Initialize this voice.
182 */
183 void Initialize();
184
185 /**
186 * Does this voice ned an update?
187 *
188 * @param params - Input parametetrs to check matching.
189 * @return True if this voice needs an update, otherwise false.
190 */
191 bool ShouldUpdateParameters(const InParameter& params) const;
192
193 /**
194 * Update the parameters of this voice.
195 *
196 * @param error_info - Output error code.
197 * @param params - Input parametters to udpate from.
198 * @param pool_mapper - Used to map buffers.
199 * @param behavior - behavior to check supported features.
200 */
201 void UpdateParameters(BehaviorInfo::ErrorInfo& error_info, const InParameter& params,
202 const PoolMapper& pool_mapper, const BehaviorInfo& behavior);
203
204 /**
205 * Update the current play state.
206 *
207 * @param state - New play state for this voice.
208 */
209 void UpdatePlayState(PlayState state);
210
211 /**
212 * Update the current sample rate conversion quality.
213 *
214 * @param quality - New quality.
215 */
216 void UpdateSrcQuality(SrcQuality quality);
217
218 /**
219 * Update all wavebuffers.
220 *
221 * @param error_infos - Output 2D array of errors, 2 per wavebuffer.
222 * @param error_count - Number of errors provided. Unused.
223 * @param params - Input parametters to be used for the update.
224 * @param voice_states - The voice states for each channel in this voice to be updated.
225 * @param pool_mapper - Used to map the wavebuffers.
226 * @param behavior - Used to check for supported features.
227 */
228 void UpdateWaveBuffers(std::span<std::array<BehaviorInfo::ErrorInfo, 2>> error_infos,
229 u32 error_count, const InParameter& params,
230 std::span<VoiceState*> voice_states, const PoolMapper& pool_mapper,
231 const BehaviorInfo& behavior);
232
233 /**
234 * Update a wavebuffer.
235 *
236 * @param error_infos - Output array of errors.
237 * @param wave_buffer - The wavebuffer to be updated.
238 * @param wave_buffer_internal - Input parametters to be used for the update.
239 * @param sample_format - Sample format of the wavebuffer.
240 * @param valid - Is this wavebuffer valid?
241 * @param pool_mapper - Used to map the wavebuffers.
242 * @param behavior - Used to check for supported features.
243 */
244 void UpdateWaveBuffer(std::span<BehaviorInfo::ErrorInfo> error_info, WaveBuffer& wave_buffer,
245 const WaveBufferInternal& wave_buffer_internal,
246 SampleFormat sample_format, bool valid, const PoolMapper& pool_mapper,
247 const BehaviorInfo& behavior);
248
249 /**
250 * Check if the input wavebuffer needs an update.
251 *
252 * @param wave_buffer_internal - Input wavebuffer parameters to check.
253 * @return True if the given wavebuffer needs an update, otherwise false.
254 */
255 bool ShouldUpdateWaveBuffer(const WaveBufferInternal& wave_buffer_internal) const;
256
257 /**
258 * Write the number of played samples, number of consumed wavebuffers and if this voice was
259 * dropped, to the given out_status.
260 *
261 * @param out_status - Output status to be written to.
262 * @param in_params - Input parameters to check if the wavebuffer is new.
263 * @param voice_states - Current host voice states for this voice, source of the output.
264 */
265 void WriteOutStatus(OutStatus& out_status, const InParameter& in_params,
266 std::span<VoiceState*> voice_states);
267
268 /**
269 * Check if this voice should be skipped for command generation.
270 * Checks various things such as usage state, whether data is mapped etc.
271 *
272 * @return True if this voice should not be generated, otherwise false.
273 */
274 bool ShouldSkip() const;
275
276 /**
277 * Check if this voice has any mixing connections.
278 *
279 * @return True if this voice participes in mixing, otherwise false.
280 */
281 bool HasAnyConnection() const;
282
283 /**
284 * Flush flush_count wavebuffers, marking them as consumed.
285 *
286 * @param flush_count - Number of wavebuffers to flush.
287 * @param voice_states - Voice states for these wavebuffers.
288 * @param channel_count - Number of active channels.
289 */
290 void FlushWaveBuffers(u32 flush_count, std::span<VoiceState*> voice_states, s8 channel_count);
291
292 /**
293 * Update this voice's parameters on command generation,
294 * updating voice states and flushing if needed.
295 *
296 * @param voice_states - Voice states for these wavebuffers.
297 * @return True if this voice should be generated, otherwise false.
298 */
299 bool UpdateParametersForCommandGeneration(std::span<VoiceState*> voice_states);
300
301 /**
302 * Update this voice on command generation.
303 *
304 * @param voice_states - Voice states for these wavebuffers.
305 * @return True if this voice should be generated, otherwise false.
306 */
307 bool UpdateForCommandGeneration(VoiceContext& voice_context);
308
309 /**
310 * Reset the AudioRenderer-side voice states, and the channel resources for this voice.
311 *
312 * @param voice_context - Context from which to get the resources.
313 */
314 void ResetResources(VoiceContext& voice_context) const;
315
316 /// Is this voice in use?
317 bool in_use{};
318 /// Is this voice new?
319 bool is_new{};
320 /// Was this voice last playing? Used for depopping
321 bool was_playing{};
322 /// Sample format of the wavebuffers in this voice
323 SampleFormat sample_format{};
324 /// Sample rate of the wavebuffers in this voice
325 u32 sample_rate{};
326 /// Number of channels in this voice
327 s8 channel_count{};
328 /// Id of this voice
329 u32 id{};
330 /// Node id of this voice
331 u32 node_id{};
332 /// Mix id this voice is mixed to
333 u32 mix_id{};
334 /// Play state of this voice
335 ServerPlayState current_play_state{ServerPlayState::Stopped};
336 /// Last play state of this voice
337 ServerPlayState last_play_state{ServerPlayState::Started};
338 /// Priority of this voice, lower is higher
339 s32 priority{};
340 /// Sort order of this voice, used when same priority
341 s32 sort_order{};
342 /// Pitch of this voice (for sample rate conversion)
343 f32 pitch{};
344 /// Current volume of this voice
345 f32 volume{};
346 /// Previous volume of this voice
347 f32 prev_volume{};
348 /// Biquad filters for generating filter commands on this voice
349 std::array<BiquadFilterParameter, MaxBiquadFilters> biquads{};
350 /// Number of active wavebuffers
351 u32 wave_buffer_count{};
352 /// Current playing wavebuffer index
353 u16 wave_buffer_index{};
354 /// Flags controlling decode behavior
355 u16 flags{};
356 /// Game memory for ADPCM coefficients
357 AddressInfo data_address{0, 0};
358 /// Wavebuffers
359 std::array<WaveBuffer, MaxWaveBuffers> wavebuffers{};
360 /// Channel resources for this voice
361 std::array<u32, MaxChannels> channel_resource_ids{};
362 /// Splitter id this voice is connected with
363 s32 splitter_id{UnusedSplitterId};
364 /// Sample rate conversion quality
365 SrcQuality src_quality{SrcQuality::Medium};
366 /// Was this voice dropped due to limited time?
367 bool voice_dropped{};
368 /// Is this voice's coefficient (data_address) unmapped?
369 bool data_unmapped{};
370 /// Is this voice's buffers (wavebuffer data and ADPCM context) unmapped?
371 bool buffer_unmapped{};
372 /// Initialisation state of the biquads
373 std::array<bool, MaxBiquadFilters> biquad_initialized{};
374 /// Number of wavebuffers to flush
375 u8 flush_buffer_count{};
376};
377
378} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/voice/voice_state.h b/src/audio_core/renderer/voice/voice_state.h
new file mode 100644
index 000000000..d5497e2fb
--- /dev/null
+++ b/src/audio_core/renderer/voice/voice_state.h
@@ -0,0 +1,70 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7
8#include "audio_core/common/common.h"
9#include "common/common_types.h"
10#include "common/fixed_point.h"
11
12namespace AudioCore::AudioRenderer {
13/**
14 * Holds a state for a voice. One is kept host-side, and one is used by the AudioRenderer,
15 * host-side is updated on the next iteration.
16 */
17struct VoiceState {
18 /**
19 * State of the voice's biquad filter.
20 */
21 struct BiquadFilterState {
22 Common::FixedPoint<50, 14> s0;
23 Common::FixedPoint<50, 14> s1;
24 Common::FixedPoint<50, 14> s2;
25 Common::FixedPoint<50, 14> s3;
26 };
27
28 /**
29 * Context for ADPCM decoding.
30 */
31 struct AdpcmContext {
32 u16 header;
33 s16 yn0;
34 s16 yn1;
35 };
36
37 /// Number of samples played
38 u64 played_sample_count;
39 /// Current offset from the starting offset
40 u32 offset;
41 /// Currently active wavebuffer index
42 u32 wave_buffer_index;
43 /// Array of which wavebuffers are currently valid
44
45 std::array<bool, MaxWaveBuffers> wave_buffer_valid;
46 /// Number of wavebuffers consumed, given back to the game
47 u32 wave_buffers_consumed;
48 /// History of samples, used for rate conversion
49
50 std::array<s16, MaxWaveBuffers * 2> sample_history;
51 /// Current read fraction, used for resampling
52 Common::FixedPoint<49, 15> fraction;
53 /// Current adpcm context
54 AdpcmContext adpcm_context;
55 /// Current biquad states, used when filtering
56
57 std::array<std::array<BiquadFilterState, MaxBiquadFilters>, MaxBiquadFilters> biquad_states;
58 /// Previous samples
59 std::array<s32, MaxMixBuffers> previous_samples;
60 /// Unused
61 u32 external_context_size;
62 /// Unused
63 bool external_context_enabled;
64 /// Was this voice dropped?
65 bool voice_dropped;
66 /// Number of times the wavebuffer has looped
67 s32 loop_count;
68};
69
70} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/sdl2_sink.cpp b/src/audio_core/sdl2_sink.cpp
deleted file mode 100644
index a10ba4044..000000000
--- a/src/audio_core/sdl2_sink.cpp
+++ /dev/null
@@ -1,160 +0,0 @@
1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <algorithm>
5#include <atomic>
6#include <cstring>
7#include "audio_core/sdl2_sink.h"
8#include "audio_core/stream.h"
9#include "common/assert.h"
10#include "common/logging/log.h"
11//#include "common/settings.h"
12
13// Ignore -Wimplicit-fallthrough due to https://github.com/libsdl-org/SDL/issues/4307
14#ifdef __clang__
15#pragma clang diagnostic push
16#pragma clang diagnostic ignored "-Wimplicit-fallthrough"
17#endif
18#include <SDL.h>
19#ifdef __clang__
20#pragma clang diagnostic pop
21#endif
22
23namespace AudioCore {
24
25class SDLSinkStream final : public SinkStream {
26public:
27 SDLSinkStream(u32 sample_rate, u32 num_channels_, const std::string& output_device)
28 : num_channels{std::min(num_channels_, 6u)} {
29
30 SDL_AudioSpec spec;
31 spec.freq = sample_rate;
32 spec.channels = static_cast<u8>(num_channels);
33 spec.format = AUDIO_S16SYS;
34 spec.samples = 4096;
35 spec.callback = nullptr;
36
37 SDL_AudioSpec obtained;
38 if (output_device.empty()) {
39 dev = SDL_OpenAudioDevice(nullptr, 0, &spec, &obtained, 0);
40 } else {
41 dev = SDL_OpenAudioDevice(output_device.c_str(), 0, &spec, &obtained, 0);
42 }
43
44 if (dev == 0) {
45 LOG_CRITICAL(Audio_Sink, "Error opening sdl audio device: {}", SDL_GetError());
46 return;
47 }
48
49 SDL_PauseAudioDevice(dev, 0);
50 }
51
52 ~SDLSinkStream() override {
53 if (dev == 0) {
54 return;
55 }
56
57 SDL_CloseAudioDevice(dev);
58 }
59
60 void EnqueueSamples(u32 source_num_channels, const std::vector<s16>& samples) override {
61 if (source_num_channels > num_channels) {
62 // Downsample 6 channels to 2
63 ASSERT_MSG(source_num_channels == 6, "Channel count must be 6");
64
65 std::vector<s16> buf;
66 buf.reserve(samples.size() * num_channels / source_num_channels);
67 for (std::size_t i = 0; i < samples.size(); i += source_num_channels) {
68 // Downmixing implementation taken from the ATSC standard
69 const s16 left{samples[i + 0]};
70 const s16 right{samples[i + 1]};
71 const s16 center{samples[i + 2]};
72 const s16 surround_left{samples[i + 4]};
73 const s16 surround_right{samples[i + 5]};
74 // Not used in the ATSC reference implementation
75 [[maybe_unused]] const s16 low_frequency_effects{samples[i + 3]};
76
77 constexpr s32 clev{707}; // center mixing level coefficient
78 constexpr s32 slev{707}; // surround mixing level coefficient
79
80 buf.push_back(static_cast<s16>(left + (clev * center / 1000) +
81 (slev * surround_left / 1000)));
82 buf.push_back(static_cast<s16>(right + (clev * center / 1000) +
83 (slev * surround_right / 1000)));
84 }
85 int ret = SDL_QueueAudio(dev, static_cast<const void*>(buf.data()),
86 static_cast<u32>(buf.size() * sizeof(s16)));
87 if (ret < 0)
88 LOG_WARNING(Audio_Sink, "Could not queue audio buffer: {}", SDL_GetError());
89 return;
90 }
91
92 int ret = SDL_QueueAudio(dev, static_cast<const void*>(samples.data()),
93 static_cast<u32>(samples.size() * sizeof(s16)));
94 if (ret < 0)
95 LOG_WARNING(Audio_Sink, "Could not queue audio buffer: {}", SDL_GetError());
96 }
97
98 std::size_t SamplesInQueue(u32 channel_count) const override {
99 if (dev == 0)
100 return 0;
101
102 return SDL_GetQueuedAudioSize(dev) / (channel_count * sizeof(s16));
103 }
104
105 void Flush() override {
106 should_flush = true;
107 }
108
109 u32 GetNumChannels() const {
110 return num_channels;
111 }
112
113private:
114 SDL_AudioDeviceID dev = 0;
115 u32 num_channels{};
116 std::atomic<bool> should_flush{};
117};
118
119SDLSink::SDLSink(std::string_view target_device_name) {
120 if (!SDL_WasInit(SDL_INIT_AUDIO)) {
121 if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) {
122 LOG_CRITICAL(Audio_Sink, "SDL_InitSubSystem audio failed: {}", SDL_GetError());
123 return;
124 }
125 }
126
127 if (target_device_name != auto_device_name && !target_device_name.empty()) {
128 output_device = target_device_name;
129 } else {
130 output_device.clear();
131 }
132}
133
134SDLSink::~SDLSink() = default;
135
136SinkStream& SDLSink::AcquireSinkStream(u32 sample_rate, u32 num_channels, const std::string&) {
137 sink_streams.push_back(
138 std::make_unique<SDLSinkStream>(sample_rate, num_channels, output_device));
139 return *sink_streams.back();
140}
141
142std::vector<std::string> ListSDLSinkDevices() {
143 std::vector<std::string> device_list;
144
145 if (!SDL_WasInit(SDL_INIT_AUDIO)) {
146 if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) {
147 LOG_CRITICAL(Audio_Sink, "SDL_InitSubSystem audio failed: {}", SDL_GetError());
148 return {};
149 }
150 }
151
152 const int device_count = SDL_GetNumAudioDevices(0);
153 for (int i = 0; i < device_count; ++i) {
154 device_list.emplace_back(SDL_GetAudioDeviceName(i, 0));
155 }
156
157 return device_list;
158}
159
160} // namespace AudioCore
diff --git a/src/audio_core/sdl2_sink.h b/src/audio_core/sdl2_sink.h
deleted file mode 100644
index f1dd1d677..000000000
--- a/src/audio_core/sdl2_sink.h
+++ /dev/null
@@ -1,28 +0,0 @@
1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <string>
7#include <vector>
8
9#include "audio_core/sink.h"
10
11namespace AudioCore {
12
13class SDLSink final : public Sink {
14public:
15 explicit SDLSink(std::string_view device_id);
16 ~SDLSink() override;
17
18 SinkStream& AcquireSinkStream(u32 sample_rate, u32 num_channels,
19 const std::string& name) override;
20
21private:
22 std::string output_device;
23 std::vector<SinkStreamPtr> sink_streams;
24};
25
26std::vector<std::string> ListSDLSinkDevices();
27
28} // namespace AudioCore
diff --git a/src/audio_core/sink.h b/src/audio_core/sink.h
deleted file mode 100644
index 3c03554fa..000000000
--- a/src/audio_core/sink.h
+++ /dev/null
@@ -1,30 +0,0 @@
1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <memory>
7#include <string>
8
9#include "audio_core/sink_stream.h"
10#include "common/common_types.h"
11
12namespace AudioCore {
13
14constexpr char auto_device_name[] = "auto";
15
16/**
17 * This class is an interface for an audio sink. An audio sink accepts samples in stereo signed
18 * PCM16 format to be output. Sinks *do not* handle resampling and expect the correct sample rate.
19 * They are dumb outputs.
20 */
21class Sink {
22public:
23 virtual ~Sink() = default;
24 virtual SinkStream& AcquireSinkStream(u32 sample_rate, u32 num_channels,
25 const std::string& name) = 0;
26};
27
28using SinkPtr = std::unique_ptr<Sink>;
29
30} // namespace AudioCore
diff --git a/src/audio_core/sink/cubeb_sink.cpp b/src/audio_core/sink/cubeb_sink.cpp
new file mode 100644
index 000000000..a4e28de6d
--- /dev/null
+++ b/src/audio_core/sink/cubeb_sink.cpp
@@ -0,0 +1,651 @@
1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <algorithm>
5#include <atomic>
6#include <span>
7
8#include "audio_core/audio_core.h"
9#include "audio_core/audio_event.h"
10#include "audio_core/audio_manager.h"
11#include "audio_core/sink/cubeb_sink.h"
12#include "audio_core/sink/sink_stream.h"
13#include "common/assert.h"
14#include "common/fixed_point.h"
15#include "common/logging/log.h"
16#include "common/reader_writer_queue.h"
17#include "common/ring_buffer.h"
18#include "common/settings.h"
19#include "core/core.h"
20
21#ifdef _WIN32
22#include <objbase.h>
23#undef CreateEvent
24#endif
25
26namespace AudioCore::Sink {
27/**
28 * Cubeb sink stream, responsible for sinking samples to hardware.
29 */
30class CubebSinkStream final : public SinkStream {
31public:
32 /**
33 * Create a new sink stream.
34 *
35 * @param ctx_ - Cubeb context to create this stream with.
36 * @param device_channels_ - Number of channels supported by the hardware.
37 * @param system_channels_ - Number of channels the audio systems expect.
38 * @param output_device - Cubeb output device id.
39 * @param input_device - Cubeb input device id.
40 * @param name_ - Name of this stream.
41 * @param type_ - Type of this stream.
42 * @param system_ - Core system.
43 * @param event - Event used only for audio renderer, signalled on buffer consume.
44 */
45 CubebSinkStream(cubeb* ctx_, const u32 device_channels_, const u32 system_channels_,
46 cubeb_devid output_device, cubeb_devid input_device, const std::string& name_,
47 const StreamType type_, Core::System& system_)
48 : ctx{ctx_}, type{type_}, system{system_} {
49#ifdef _WIN32
50 CoInitializeEx(nullptr, COINIT_MULTITHREADED);
51#endif
52 name = name_;
53 device_channels = device_channels_;
54 system_channels = system_channels_;
55
56 cubeb_stream_params params{};
57 params.rate = TargetSampleRate;
58 params.channels = device_channels;
59 params.format = CUBEB_SAMPLE_S16LE;
60 params.prefs = CUBEB_STREAM_PREF_NONE;
61 switch (params.channels) {
62 case 1:
63 params.layout = CUBEB_LAYOUT_MONO;
64 break;
65 case 2:
66 params.layout = CUBEB_LAYOUT_STEREO;
67 break;
68 case 6:
69 params.layout = CUBEB_LAYOUT_3F2_LFE;
70 break;
71 }
72
73 u32 minimum_latency{0};
74 const auto latency_error = cubeb_get_min_latency(ctx, &params, &minimum_latency);
75 if (latency_error != CUBEB_OK) {
76 LOG_CRITICAL(Audio_Sink, "Error getting minimum latency, error: {}", latency_error);
77 minimum_latency = 256U;
78 }
79
80 minimum_latency = std::max(minimum_latency, 256u);
81
82 playing_buffer.consumed = true;
83
84 LOG_DEBUG(Service_Audio,
85 "Opening cubeb stream {} type {} with: rate {} channels {} (system channels {}) "
86 "latency {}",
87 name, type, params.rate, params.channels, system_channels, minimum_latency);
88
89 auto init_error{0};
90 if (type == StreamType::In) {
91 init_error = cubeb_stream_init(ctx, &stream_backend, name.c_str(), input_device,
92 &params, output_device, nullptr, minimum_latency,
93 &CubebSinkStream::DataCallback,
94 &CubebSinkStream::StateCallback, this);
95 } else {
96 init_error = cubeb_stream_init(ctx, &stream_backend, name.c_str(), input_device,
97 nullptr, output_device, &params, minimum_latency,
98 &CubebSinkStream::DataCallback,
99 &CubebSinkStream::StateCallback, this);
100 }
101
102 if (init_error != CUBEB_OK) {
103 LOG_CRITICAL(Audio_Sink, "Error initializing cubeb stream, error: {}", init_error);
104 return;
105 }
106 }
107
108 /**
109 * Destroy the sink stream.
110 */
111 ~CubebSinkStream() override {
112 LOG_DEBUG(Service_Audio, "Destructing cubeb stream {}", name);
113
114 if (!ctx) {
115 return;
116 }
117
118 Finalize();
119
120#ifdef _WIN32
121 CoUninitialize();
122#endif
123 }
124
125 /**
126 * Finalize the sink stream.
127 */
128 void Finalize() override {
129 Stop();
130 cubeb_stream_destroy(stream_backend);
131 }
132
133 /**
134 * Start the sink stream.
135 *
136 * @param resume - Set to true if this is resuming the stream a previously-active stream.
137 * Default false.
138 */
139 void Start(const bool resume = false) override {
140 if (!ctx) {
141 return;
142 }
143
144 if (resume && was_playing) {
145 if (cubeb_stream_start(stream_backend) != CUBEB_OK) {
146 LOG_CRITICAL(Audio_Sink, "Error starting cubeb stream");
147 }
148 paused = false;
149 } else if (!resume) {
150 if (cubeb_stream_start(stream_backend) != CUBEB_OK) {
151 LOG_CRITICAL(Audio_Sink, "Error starting cubeb stream");
152 }
153 paused = false;
154 }
155 }
156
157 /**
158 * Stop the sink stream.
159 */
160 void Stop() override {
161 if (!ctx) {
162 return;
163 }
164
165 if (cubeb_stream_stop(stream_backend) != CUBEB_OK) {
166 LOG_CRITICAL(Audio_Sink, "Error stopping cubeb stream");
167 }
168
169 was_playing.store(!paused);
170 paused = true;
171 }
172
173 /**
174 * Append a new buffer and its samples to a waiting queue to play.
175 *
176 * @param buffer - Audio buffer information to be queued.
177 * @param samples - The s16 samples to be queue for playback.
178 */
179 void AppendBuffer(::AudioCore::Sink::SinkBuffer& buffer, std::vector<s16>& samples) override {
180 if (type == StreamType::In) {
181 queue.enqueue(buffer);
182 queued_buffers++;
183 } else {
184 constexpr s32 min{std::numeric_limits<s16>::min()};
185 constexpr s32 max{std::numeric_limits<s16>::max()};
186
187 auto yuzu_volume{Settings::Volume()};
188 auto volume{system_volume * device_volume * yuzu_volume};
189
190 if (system_channels == 6 && device_channels == 2) {
191 // We're given 6 channels, but our device only outputs 2, so downmix.
192 constexpr std::array<f32, 4> down_mix_coeff{1.0f, 0.707f, 0.251f, 0.707f};
193
194 for (u32 read_index = 0, write_index = 0; read_index < samples.size();
195 read_index += system_channels, write_index += device_channels) {
196 const auto left_sample{
197 ((Common::FixedPoint<49, 15>(
198 samples[read_index + static_cast<u32>(Channels::FrontLeft)]) *
199 down_mix_coeff[0] +
200 samples[read_index + static_cast<u32>(Channels::Center)] *
201 down_mix_coeff[1] +
202 samples[read_index + static_cast<u32>(Channels::LFE)] *
203 down_mix_coeff[2] +
204 samples[read_index + static_cast<u32>(Channels::BackLeft)] *
205 down_mix_coeff[3]) *
206 volume)
207 .to_int()};
208
209 const auto right_sample{
210 ((Common::FixedPoint<49, 15>(
211 samples[read_index + static_cast<u32>(Channels::FrontRight)]) *
212 down_mix_coeff[0] +
213 samples[read_index + static_cast<u32>(Channels::Center)] *
214 down_mix_coeff[1] +
215 samples[read_index + static_cast<u32>(Channels::LFE)] *
216 down_mix_coeff[2] +
217 samples[read_index + static_cast<u32>(Channels::BackRight)] *
218 down_mix_coeff[3]) *
219 volume)
220 .to_int()};
221
222 samples[write_index + static_cast<u32>(Channels::FrontLeft)] =
223 static_cast<s16>(std::clamp(left_sample, min, max));
224 samples[write_index + static_cast<u32>(Channels::FrontRight)] =
225 static_cast<s16>(std::clamp(right_sample, min, max));
226 }
227
228 samples.resize(samples.size() / system_channels * device_channels);
229
230 } else if (system_channels == 2 && device_channels == 6) {
231 // We need moar samples! Not all games will provide 6 channel audio.
232 // TODO: Implement some upmixing here. Currently just passthrough, with other
233 // channels left as silence.
234 std::vector<s16> new_samples(samples.size() / system_channels * device_channels, 0);
235
236 for (u32 read_index = 0, write_index = 0; read_index < samples.size();
237 read_index += system_channels, write_index += device_channels) {
238 const auto left_sample{static_cast<s16>(std::clamp(
239 static_cast<s32>(
240 static_cast<f32>(
241 samples[read_index + static_cast<u32>(Channels::FrontLeft)]) *
242 volume),
243 min, max))};
244
245 new_samples[write_index + static_cast<u32>(Channels::FrontLeft)] = left_sample;
246
247 const auto right_sample{static_cast<s16>(std::clamp(
248 static_cast<s32>(
249 static_cast<f32>(
250 samples[read_index + static_cast<u32>(Channels::FrontRight)]) *
251 volume),
252 min, max))};
253
254 new_samples[write_index + static_cast<u32>(Channels::FrontRight)] =
255 right_sample;
256 }
257 samples = std::move(new_samples);
258
259 } else if (volume != 1.0f) {
260 for (u32 i = 0; i < samples.size(); i++) {
261 samples[i] = static_cast<s16>(std::clamp(
262 static_cast<s32>(static_cast<f32>(samples[i]) * volume), min, max));
263 }
264 }
265
266 samples_buffer.Push(samples);
267 queue.enqueue(buffer);
268 queued_buffers++;
269 }
270 }
271
272 /**
273 * Release a buffer. Audio In only, will fill a buffer with recorded samples.
274 *
275 * @param num_samples - Maximum number of samples to receive.
276 * @return Vector of recorded samples. May have fewer than num_samples.
277 */
278 std::vector<s16> ReleaseBuffer(const u64 num_samples) override {
279 static constexpr s32 min = std::numeric_limits<s16>::min();
280 static constexpr s32 max = std::numeric_limits<s16>::max();
281
282 auto samples{samples_buffer.Pop(num_samples)};
283
284 // TODO: Up-mix to 6 channels if the game expects it.
285 // For audio input this is unlikely to ever be the case though.
286
287 // Incoming mic volume seems to always be very quiet, so multiply by an additional 8 here.
288 // TODO: Play with this and find something that works better.
289 auto volume{system_volume * device_volume * 8};
290 for (u32 i = 0; i < samples.size(); i++) {
291 samples[i] = static_cast<s16>(
292 std::clamp(static_cast<s32>(static_cast<f32>(samples[i]) * volume), min, max));
293 }
294
295 if (samples.size() < num_samples) {
296 samples.resize(num_samples, 0);
297 }
298 return samples;
299 }
300
301 /**
302 * Check if a certain buffer has been consumed (fully played).
303 *
304 * @param tag - Unique tag of a buffer to check for.
305 * @return True if the buffer has been played, otherwise false.
306 */
307 bool IsBufferConsumed(const u64 tag) override {
308 if (released_buffer.tag == 0) {
309 if (!released_buffers.try_dequeue(released_buffer)) {
310 return false;
311 }
312 }
313
314 if (released_buffer.tag == tag) {
315 released_buffer.tag = 0;
316 return true;
317 }
318 return false;
319 }
320
321 /**
322 * Empty out the buffer queue.
323 */
324 void ClearQueue() override {
325 samples_buffer.Pop();
326 while (queue.pop()) {
327 }
328 while (released_buffers.pop()) {
329 }
330 queued_buffers = 0;
331 released_buffer = {};
332 playing_buffer = {};
333 playing_buffer.consumed = true;
334 }
335
336private:
337 /**
338 * Signal events back to the audio system that a buffer was played/can be filled.
339 *
340 * @param buffer - Consumed audio buffer to be released.
341 */
342 void SignalEvent(const ::AudioCore::Sink::SinkBuffer& buffer) {
343 auto& manager{system.AudioCore().GetAudioManager()};
344 switch (type) {
345 case StreamType::Out:
346 released_buffers.enqueue(buffer);
347 manager.SetEvent(Event::Type::AudioOutManager, true);
348 break;
349 case StreamType::In:
350 released_buffers.enqueue(buffer);
351 manager.SetEvent(Event::Type::AudioInManager, true);
352 break;
353 case StreamType::Render:
354 break;
355 }
356 }
357
358 /**
359 * Main callback from Cubeb. Either expects samples from us (audio render/audio out), or will
360 * provide samples to be copied (audio in).
361 *
362 * @param stream - Cubeb-specific data about the stream.
363 * @param user_data - Custom data pointer passed along, points to a CubebSinkStream.
364 * @param in_buff - Input buffer to be used if the stream is an input type.
365 * @param out_buff - Output buffer to be used if the stream is an output type.
366 * @param num_frames_ - Number of frames of audio in the buffers. Note: Not number of samples.
367 */
368 static long DataCallback([[maybe_unused]] cubeb_stream* stream, void* user_data,
369 [[maybe_unused]] const void* in_buff, void* out_buff,
370 long num_frames_) {
371 auto* impl = static_cast<CubebSinkStream*>(user_data);
372 if (!impl) {
373 return -1;
374 }
375
376 const std::size_t num_channels = impl->GetDeviceChannels();
377 const std::size_t frame_size = num_channels;
378 const std::size_t frame_size_bytes = frame_size * sizeof(s16);
379 const std::size_t num_frames{static_cast<size_t>(num_frames_)};
380 size_t frames_written{0};
381 [[maybe_unused]] bool underrun{false};
382
383 if (impl->type == StreamType::In) {
384 // INPUT
385 std::span<const s16> input_buffer{reinterpret_cast<const s16*>(in_buff),
386 num_frames * frame_size};
387
388 while (frames_written < num_frames) {
389 auto& playing_buffer{impl->playing_buffer};
390
391 // If the playing buffer has been consumed or has no frames, we need a new one
392 if (playing_buffer.consumed || playing_buffer.frames == 0) {
393 if (!impl->queue.try_dequeue(impl->playing_buffer)) {
394 // If no buffer was available we've underrun, just push the samples and
395 // continue.
396 underrun = true;
397 impl->samples_buffer.Push(&input_buffer[frames_written * frame_size],
398 (num_frames - frames_written) * frame_size);
399 frames_written = num_frames;
400 continue;
401 } else {
402 // Successfully got a new buffer, mark the old one as consumed and signal.
403 impl->queued_buffers--;
404 impl->SignalEvent(impl->playing_buffer);
405 }
406 }
407
408 // Get the minimum frames available between the currently playing buffer, and the
409 // amount we have left to fill
410 size_t frames_available{
411 std::min(playing_buffer.frames - playing_buffer.frames_played,
412 num_frames - frames_written)};
413
414 impl->samples_buffer.Push(&input_buffer[frames_written * frame_size],
415 frames_available * frame_size);
416
417 frames_written += frames_available;
418 playing_buffer.frames_played += frames_available;
419
420 // If that's all the frames in the current buffer, add its samples and mark it as
421 // consumed
422 if (playing_buffer.frames_played >= playing_buffer.frames) {
423 impl->AddPlayedSampleCount(playing_buffer.frames_played * num_channels);
424 impl->playing_buffer.consumed = true;
425 }
426 }
427
428 std::memcpy(&impl->last_frame[0], &input_buffer[(frames_written - 1) * frame_size],
429 frame_size_bytes);
430 } else {
431 // OUTPUT
432 std::span<s16> output_buffer{reinterpret_cast<s16*>(out_buff), num_frames * frame_size};
433
434 while (frames_written < num_frames) {
435 auto& playing_buffer{impl->playing_buffer};
436
437 // If the playing buffer has been consumed or has no frames, we need a new one
438 if (playing_buffer.consumed || playing_buffer.frames == 0) {
439 if (!impl->queue.try_dequeue(impl->playing_buffer)) {
440 // If no buffer was available we've underrun, fill the remaining buffer with
441 // the last written frame and continue.
442 underrun = true;
443 for (size_t i = frames_written; i < num_frames; i++) {
444 std::memcpy(&output_buffer[i * frame_size], &impl->last_frame[0],
445 frame_size_bytes);
446 }
447 frames_written = num_frames;
448 continue;
449 } else {
450 // Successfully got a new buffer, mark the old one as consumed and signal.
451 impl->queued_buffers--;
452 impl->SignalEvent(impl->playing_buffer);
453 }
454 }
455
456 // Get the minimum frames available between the currently playing buffer, and the
457 // amount we have left to fill
458 size_t frames_available{
459 std::min(playing_buffer.frames - playing_buffer.frames_played,
460 num_frames - frames_written)};
461
462 impl->samples_buffer.Pop(&output_buffer[frames_written * frame_size],
463 frames_available * frame_size);
464
465 frames_written += frames_available;
466 playing_buffer.frames_played += frames_available;
467
468 // If that's all the frames in the current buffer, add its samples and mark it as
469 // consumed
470 if (playing_buffer.frames_played >= playing_buffer.frames) {
471 impl->AddPlayedSampleCount(playing_buffer.frames_played * num_channels);
472 impl->playing_buffer.consumed = true;
473 }
474 }
475
476 std::memcpy(&impl->last_frame[0], &output_buffer[(frames_written - 1) * frame_size],
477 frame_size_bytes);
478 }
479
480 return num_frames_;
481 }
482
483 /**
484 * Cubeb callback for if a device state changes. Unused currently.
485 *
486 * @param stream - Cubeb-specific data about the stream.
487 * @param user_data - Custom data pointer passed along, points to a CubebSinkStream.
488 * @param state - New state of the device.
489 */
490 static void StateCallback([[maybe_unused]] cubeb_stream* stream,
491 [[maybe_unused]] void* user_data,
492 [[maybe_unused]] cubeb_state state) {}
493
494 /// Main Cubeb context
495 cubeb* ctx{};
496 /// Cubeb stream backend
497 cubeb_stream* stream_backend{};
498 /// Name of this stream
499 std::string name{};
500 /// Type of this stream
501 StreamType type;
502 /// Core system
503 Core::System& system;
504 /// Ring buffer of the samples waiting to be played or consumed
505 Common::RingBuffer<s16, 0x10000> samples_buffer;
506 /// Audio buffers queued and waiting to play
507 Common::ReaderWriterQueue<::AudioCore::Sink::SinkBuffer> queue;
508 /// The currently-playing audio buffer
509 ::AudioCore::Sink::SinkBuffer playing_buffer{};
510 /// Audio buffers which have been played and are in queue to be released by the audio system
511 Common::ReaderWriterQueue<::AudioCore::Sink::SinkBuffer> released_buffers{};
512 /// Currently released buffer waiting to be taken by the audio system
513 ::AudioCore::Sink::SinkBuffer released_buffer{};
514 /// The last played (or received) frame of audio, used when the callback underruns
515 std::array<s16, MaxChannels> last_frame{};
516};
517
518CubebSink::CubebSink(std::string_view target_device_name) {
519 // Cubeb requires COM to be initialized on the thread calling cubeb_init on Windows
520#ifdef _WIN32
521 com_init_result = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
522#endif
523
524 if (cubeb_init(&ctx, "yuzu", nullptr) != CUBEB_OK) {
525 LOG_CRITICAL(Audio_Sink, "cubeb_init failed");
526 return;
527 }
528
529 if (target_device_name != auto_device_name && !target_device_name.empty()) {
530 cubeb_device_collection collection;
531 if (cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_OUTPUT, &collection) != CUBEB_OK) {
532 LOG_WARNING(Audio_Sink, "Audio output device enumeration not supported");
533 } else {
534 const auto collection_end{collection.device + collection.count};
535 const auto device{
536 std::find_if(collection.device, collection_end, [&](const cubeb_device_info& info) {
537 return info.friendly_name != nullptr &&
538 target_device_name == std::string(info.friendly_name);
539 })};
540 if (device != collection_end) {
541 output_device = device->devid;
542 }
543 cubeb_device_collection_destroy(ctx, &collection);
544 }
545 }
546
547 cubeb_get_max_channel_count(ctx, &device_channels);
548 device_channels = device_channels >= 6U ? 6U : 2U;
549}
550
551CubebSink::~CubebSink() {
552 if (!ctx) {
553 return;
554 }
555
556 for (auto& sink_stream : sink_streams) {
557 sink_stream.reset();
558 }
559
560 cubeb_destroy(ctx);
561
562#ifdef _WIN32
563 if (SUCCEEDED(com_init_result)) {
564 CoUninitialize();
565 }
566#endif
567}
568
569SinkStream* CubebSink::AcquireSinkStream(Core::System& system, const u32 system_channels,
570 const std::string& name, const StreamType type) {
571 SinkStreamPtr& stream = sink_streams.emplace_back(std::make_unique<CubebSinkStream>(
572 ctx, device_channels, system_channels, output_device, input_device, name, type, system));
573
574 return stream.get();
575}
576
577void CubebSink::CloseStream(const SinkStream* stream) {
578 for (size_t i = 0; i < sink_streams.size(); i++) {
579 if (sink_streams[i].get() == stream) {
580 sink_streams[i].reset();
581 sink_streams.erase(sink_streams.begin() + i);
582 break;
583 }
584 }
585}
586
587void CubebSink::CloseStreams() {
588 sink_streams.clear();
589}
590
591void CubebSink::PauseStreams() {
592 for (auto& stream : sink_streams) {
593 stream->Stop();
594 }
595}
596
597void CubebSink::UnpauseStreams() {
598 for (auto& stream : sink_streams) {
599 stream->Start(true);
600 }
601}
602
603f32 CubebSink::GetDeviceVolume() const {
604 if (sink_streams.empty()) {
605 return 1.0f;
606 }
607
608 return sink_streams[0]->GetDeviceVolume();
609}
610
611void CubebSink::SetDeviceVolume(const f32 volume) {
612 for (auto& stream : sink_streams) {
613 stream->SetDeviceVolume(volume);
614 }
615}
616
617void CubebSink::SetSystemVolume(const f32 volume) {
618 for (auto& stream : sink_streams) {
619 stream->SetSystemVolume(volume);
620 }
621}
622
623std::vector<std::string> ListCubebSinkDevices(const bool capture) {
624 std::vector<std::string> device_list;
625 cubeb* ctx;
626
627 if (cubeb_init(&ctx, "yuzu Device Enumerator", nullptr) != CUBEB_OK) {
628 LOG_CRITICAL(Audio_Sink, "cubeb_init failed");
629 return {};
630 }
631
632 auto type{capture ? CUBEB_DEVICE_TYPE_INPUT : CUBEB_DEVICE_TYPE_OUTPUT};
633 cubeb_device_collection collection;
634 if (cubeb_enumerate_devices(ctx, type, &collection) != CUBEB_OK) {
635 LOG_WARNING(Audio_Sink, "Audio output device enumeration not supported");
636 } else {
637 for (std::size_t i = 0; i < collection.count; i++) {
638 const cubeb_device_info& device = collection.device[i];
639 if (device.friendly_name && device.friendly_name[0] != '\0' &&
640 device.state == CUBEB_DEVICE_STATE_ENABLED) {
641 device_list.emplace_back(device.friendly_name);
642 }
643 }
644 cubeb_device_collection_destroy(ctx, &collection);
645 }
646
647 cubeb_destroy(ctx);
648 return device_list;
649}
650
651} // namespace AudioCore::Sink
diff --git a/src/audio_core/sink/cubeb_sink.h b/src/audio_core/sink/cubeb_sink.h
new file mode 100644
index 000000000..f0f43dfa1
--- /dev/null
+++ b/src/audio_core/sink/cubeb_sink.h
@@ -0,0 +1,110 @@
1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <string>
7#include <vector>
8
9#include <cubeb/cubeb.h>
10
11#include "audio_core/sink/sink.h"
12
13namespace Core {
14class System;
15}
16
17namespace AudioCore::Sink {
18class SinkStream;
19
20/**
21 * Cubeb backend sink, holds multiple output streams and is responsible for sinking samples to
22 * hardware. Used by Audio Render, Audio In and Audio Out.
23 */
24class CubebSink final : public Sink {
25public:
26 explicit CubebSink(std::string_view device_id);
27 ~CubebSink() override;
28
29 /**
30 * Create a new sink stream.
31 *
32 * @param system - Core system.
33 * @param system_channels - Number of channels the audio system expects.
34 * May differ from the device's channel count.
35 * @param name - Name of this stream.
36 * @param type - Type of this stream, render/in/out.
37 * @param event - Audio render only, a signal used to prevent the renderer running too
38 * fast.
39 * @return A pointer to the created SinkStream
40 */
41 SinkStream* AcquireSinkStream(Core::System& system, u32 system_channels,
42 const std::string& name, StreamType type) override;
43
44 /**
45 * Close a given stream.
46 *
47 * @param stream - The stream to close.
48 */
49 void CloseStream(const SinkStream* stream) override;
50
51 /**
52 * Close all streams.
53 */
54 void CloseStreams() override;
55
56 /**
57 * Pause all streams.
58 */
59 void PauseStreams() override;
60
61 /**
62 * Unpause all streams.
63 */
64 void UnpauseStreams() override;
65
66 /**
67 * Get the device volume. Set from calls to the IAudioDevice service.
68 *
69 * @return Volume of the device.
70 */
71 f32 GetDeviceVolume() const override;
72
73 /**
74 * Set the device volume. Set from calls to the IAudioDevice service.
75 *
76 * @param volume - New volume of the device.
77 */
78 void SetDeviceVolume(f32 volume) override;
79
80 /**
81 * Set the system volume. Comes from the audio system using this stream.
82 *
83 * @param volume - New volume of the system.
84 */
85 void SetSystemVolume(f32 volume) override;
86
87private:
88 /// Backend Cubeb context
89 cubeb* ctx{};
90 /// Cubeb id of the actual hardware output device
91 cubeb_devid output_device{};
92 /// Cubeb id of the actual hardware input device
93 cubeb_devid input_device{};
94 /// Vector of streams managed by this sink
95 std::vector<SinkStreamPtr> sink_streams{};
96
97#ifdef _WIN32
98 /// Cubeb required COM to be initialized multi-threaded on Windows
99 u32 com_init_result = 0;
100#endif
101};
102
103/**
104 * Get a list of conencted devices from Cubeb.
105 *
106 * @param capture - Return input (capture) devices if true, otherwise output devices.
107 */
108std::vector<std::string> ListCubebSinkDevices(bool capture);
109
110} // namespace AudioCore::Sink
diff --git a/src/audio_core/sink/null_sink.h b/src/audio_core/sink/null_sink.h
new file mode 100644
index 000000000..47a342171
--- /dev/null
+++ b/src/audio_core/sink/null_sink.h
@@ -0,0 +1,52 @@
1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "audio_core/sink/sink.h"
7#include "audio_core/sink/sink_stream.h"
8
9namespace AudioCore::Sink {
10/**
11 * A no-op sink for when no audio out is wanted.
12 */
13class NullSink final : public Sink {
14public:
15 explicit NullSink(std::string_view) {}
16 ~NullSink() override = default;
17
18 SinkStream* AcquireSinkStream([[maybe_unused]] Core::System& system,
19 [[maybe_unused]] u32 system_channels,
20 [[maybe_unused]] const std::string& name,
21 [[maybe_unused]] StreamType type) override {
22 return &null_sink_stream;
23 }
24
25 void CloseStream([[maybe_unused]] const SinkStream* stream) override {}
26 void CloseStreams() override {}
27 void PauseStreams() override {}
28 void UnpauseStreams() override {}
29 f32 GetDeviceVolume() const override {
30 return 1.0f;
31 }
32 void SetDeviceVolume(f32 volume) override {}
33 void SetSystemVolume(f32 volume) override {}
34
35private:
36 struct NullSinkStreamImpl final : SinkStream {
37 void Finalize() override {}
38 void Start(bool resume = false) override {}
39 void Stop() override {}
40 void AppendBuffer([[maybe_unused]] ::AudioCore::Sink::SinkBuffer& buffer,
41 [[maybe_unused]] std::vector<s16>& samples) override {}
42 std::vector<s16> ReleaseBuffer([[maybe_unused]] u64 num_samples) override {
43 return {};
44 }
45 bool IsBufferConsumed([[maybe_unused]] const u64 tag) {
46 return true;
47 }
48 void ClearQueue() override {}
49 } null_sink_stream;
50};
51
52} // namespace AudioCore::Sink
diff --git a/src/audio_core/sink/sdl2_sink.cpp b/src/audio_core/sink/sdl2_sink.cpp
new file mode 100644
index 000000000..d6c9ec90d
--- /dev/null
+++ b/src/audio_core/sink/sdl2_sink.cpp
@@ -0,0 +1,556 @@
1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <algorithm>
5#include <atomic>
6
7#include "audio_core/audio_core.h"
8#include "audio_core/audio_event.h"
9#include "audio_core/audio_manager.h"
10#include "audio_core/sink/sdl2_sink.h"
11#include "audio_core/sink/sink_stream.h"
12#include "common/assert.h"
13#include "common/fixed_point.h"
14#include "common/logging/log.h"
15#include "common/reader_writer_queue.h"
16#include "common/ring_buffer.h"
17#include "common/settings.h"
18#include "core/core.h"
19
20// Ignore -Wimplicit-fallthrough due to https://github.com/libsdl-org/SDL/issues/4307
21#ifdef __clang__
22#pragma clang diagnostic push
23#pragma clang diagnostic ignored "-Wimplicit-fallthrough"
24#endif
25#include <SDL.h>
26#ifdef __clang__
27#pragma clang diagnostic pop
28#endif
29
30namespace AudioCore::Sink {
31/**
32 * SDL sink stream, responsible for sinking samples to hardware.
33 */
34class SDLSinkStream final : public SinkStream {
35public:
36 /**
37 * Create a new sink stream.
38 *
39 * @param device_channels_ - Number of channels supported by the hardware.
40 * @param system_channels_ - Number of channels the audio systems expect.
41 * @param output_device - Name of the output device to use for this stream.
42 * @param input_device - Name of the input device to use for this stream.
43 * @param type_ - Type of this stream.
44 * @param system_ - Core system.
45 * @param event - Event used only for audio renderer, signalled on buffer consume.
46 */
47 SDLSinkStream(u32 device_channels_, const u32 system_channels_,
48 const std::string& output_device, const std::string& input_device,
49 const StreamType type_, Core::System& system_)
50 : type{type_}, system{system_} {
51 system_channels = system_channels_;
52 device_channels = device_channels_;
53
54 SDL_AudioSpec spec;
55 spec.freq = TargetSampleRate;
56 spec.channels = static_cast<u8>(device_channels);
57 spec.format = AUDIO_S16SYS;
58 if (type == StreamType::Render) {
59 spec.samples = TargetSampleCount;
60 } else {
61 spec.samples = 1024;
62 }
63 spec.callback = &SDLSinkStream::DataCallback;
64 spec.userdata = this;
65
66 playing_buffer.consumed = true;
67
68 std::string device_name{output_device};
69 bool capture{false};
70 if (type == StreamType::In) {
71 device_name = input_device;
72 capture = true;
73 }
74
75 SDL_AudioSpec obtained;
76 if (device_name.empty()) {
77 device = SDL_OpenAudioDevice(nullptr, capture, &spec, &obtained, false);
78 } else {
79 device = SDL_OpenAudioDevice(device_name.c_str(), capture, &spec, &obtained, false);
80 }
81
82 if (device == 0) {
83 LOG_CRITICAL(Audio_Sink, "Error opening SDL audio device: {}", SDL_GetError());
84 return;
85 }
86
87 LOG_DEBUG(Service_Audio,
88 "Opening sdl stream {} with: rate {} channels {} (system channels {}) "
89 " samples {}",
90 device, obtained.freq, obtained.channels, system_channels, obtained.samples);
91 }
92
93 /**
94 * Destroy the sink stream.
95 */
96 ~SDLSinkStream() override {
97 if (device == 0) {
98 return;
99 }
100
101 SDL_CloseAudioDevice(device);
102 }
103
104 /**
105 * Finalize the sink stream.
106 */
107 void Finalize() override {
108 if (device == 0) {
109 return;
110 }
111
112 SDL_CloseAudioDevice(device);
113 }
114
115 /**
116 * Start the sink stream.
117 *
118 * @param resume - Set to true if this is resuming the stream a previously-active stream.
119 * Default false.
120 */
121 void Start(const bool resume = false) override {
122 if (device == 0) {
123 return;
124 }
125
126 if (resume && was_playing) {
127 SDL_PauseAudioDevice(device, 0);
128 paused = false;
129 } else if (!resume) {
130 SDL_PauseAudioDevice(device, 0);
131 paused = false;
132 }
133 }
134
135 /**
136 * Stop the sink stream.
137 */
138 void Stop() {
139 if (device == 0) {
140 return;
141 }
142 SDL_PauseAudioDevice(device, 1);
143 paused = true;
144 }
145
146 /**
147 * Append a new buffer and its samples to a waiting queue to play.
148 *
149 * @param buffer - Audio buffer information to be queued.
150 * @param samples - The s16 samples to be queue for playback.
151 */
152 void AppendBuffer(::AudioCore::Sink::SinkBuffer& buffer, std::vector<s16>& samples) override {
153 if (type == StreamType::In) {
154 queue.enqueue(buffer);
155 queued_buffers++;
156 } else {
157 constexpr s32 min = std::numeric_limits<s16>::min();
158 constexpr s32 max = std::numeric_limits<s16>::max();
159
160 auto yuzu_volume{Settings::Volume()};
161 auto volume{system_volume * device_volume * yuzu_volume};
162
163 if (system_channels == 6 && device_channels == 2) {
164 // We're given 6 channels, but our device only outputs 2, so downmix.
165 constexpr std::array<f32, 4> down_mix_coeff{1.0f, 0.707f, 0.251f, 0.707f};
166
167 for (u32 read_index = 0, write_index = 0; read_index < samples.size();
168 read_index += system_channels, write_index += device_channels) {
169 const auto left_sample{
170 ((Common::FixedPoint<49, 15>(
171 samples[read_index + static_cast<u32>(Channels::FrontLeft)]) *
172 down_mix_coeff[0] +
173 samples[read_index + static_cast<u32>(Channels::Center)] *
174 down_mix_coeff[1] +
175 samples[read_index + static_cast<u32>(Channels::LFE)] *
176 down_mix_coeff[2] +
177 samples[read_index + static_cast<u32>(Channels::BackLeft)] *
178 down_mix_coeff[3]) *
179 volume)
180 .to_int()};
181
182 const auto right_sample{
183 ((Common::FixedPoint<49, 15>(
184 samples[read_index + static_cast<u32>(Channels::FrontRight)]) *
185 down_mix_coeff[0] +
186 samples[read_index + static_cast<u32>(Channels::Center)] *
187 down_mix_coeff[1] +
188 samples[read_index + static_cast<u32>(Channels::LFE)] *
189 down_mix_coeff[2] +
190 samples[read_index + static_cast<u32>(Channels::BackRight)] *
191 down_mix_coeff[3]) *
192 volume)
193 .to_int()};
194
195 samples[write_index + static_cast<u32>(Channels::FrontLeft)] =
196 static_cast<s16>(std::clamp(left_sample, min, max));
197 samples[write_index + static_cast<u32>(Channels::FrontRight)] =
198 static_cast<s16>(std::clamp(right_sample, min, max));
199 }
200
201 samples.resize(samples.size() / system_channels * device_channels);
202
203 } else if (system_channels == 2 && device_channels == 6) {
204 // We need moar samples! Not all games will provide 6 channel audio.
205 // TODO: Implement some upmixing here. Currently just passthrough, with other
206 // channels left as silence.
207 std::vector<s16> new_samples(samples.size() / system_channels * device_channels, 0);
208
209 for (u32 read_index = 0, write_index = 0; read_index < samples.size();
210 read_index += system_channels, write_index += device_channels) {
211 const auto left_sample{static_cast<s16>(std::clamp(
212 static_cast<s32>(
213 static_cast<f32>(
214 samples[read_index + static_cast<u32>(Channels::FrontLeft)]) *
215 volume),
216 min, max))};
217
218 new_samples[write_index + static_cast<u32>(Channels::FrontLeft)] = left_sample;
219
220 const auto right_sample{static_cast<s16>(std::clamp(
221 static_cast<s32>(
222 static_cast<f32>(
223 samples[read_index + static_cast<u32>(Channels::FrontRight)]) *
224 volume),
225 min, max))};
226
227 new_samples[write_index + static_cast<u32>(Channels::FrontRight)] =
228 right_sample;
229 }
230 samples = std::move(new_samples);
231
232 } else if (volume != 1.0f) {
233 for (u32 i = 0; i < samples.size(); i++) {
234 samples[i] = static_cast<s16>(std::clamp(
235 static_cast<s32>(static_cast<f32>(samples[i]) * volume), min, max));
236 }
237 }
238
239 samples_buffer.Push(samples);
240 queue.enqueue(buffer);
241 queued_buffers++;
242 }
243 }
244
245 /**
246 * Release a buffer. Audio In only, will fill a buffer with recorded samples.
247 *
248 * @param num_samples - Maximum number of samples to receive.
249 * @return Vector of recorded samples. May have fewer than num_samples.
250 */
251 std::vector<s16> ReleaseBuffer(const u64 num_samples) override {
252 static constexpr s32 min = std::numeric_limits<s16>::min();
253 static constexpr s32 max = std::numeric_limits<s16>::max();
254
255 auto samples{samples_buffer.Pop(num_samples)};
256
257 // TODO: Up-mix to 6 channels if the game expects it.
258 // For audio input this is unlikely to ever be the case though.
259
260 // Incoming mic volume seems to always be very quiet, so multiply by an additional 8 here.
261 // TODO: Play with this and find something that works better.
262 auto volume{system_volume * device_volume * 8};
263 for (u32 i = 0; i < samples.size(); i++) {
264 samples[i] = static_cast<s16>(
265 std::clamp(static_cast<s32>(static_cast<f32>(samples[i]) * volume), min, max));
266 }
267
268 if (samples.size() < num_samples) {
269 samples.resize(num_samples, 0);
270 }
271 return samples;
272 }
273
274 /**
275 * Check if a certain buffer has been consumed (fully played).
276 *
277 * @param tag - Unique tag of a buffer to check for.
278 * @return True if the buffer has been played, otherwise false.
279 */
280 bool IsBufferConsumed(const u64 tag) override {
281 if (released_buffer.tag == 0) {
282 if (!released_buffers.try_dequeue(released_buffer)) {
283 return false;
284 }
285 }
286
287 if (released_buffer.tag == tag) {
288 released_buffer.tag = 0;
289 return true;
290 }
291 return false;
292 }
293
294 /**
295 * Empty out the buffer queue.
296 */
297 void ClearQueue() override {
298 samples_buffer.Pop();
299 while (queue.pop()) {
300 }
301 while (released_buffers.pop()) {
302 }
303 released_buffer = {};
304 playing_buffer = {};
305 playing_buffer.consumed = true;
306 queued_buffers = 0;
307 }
308
309private:
310 /**
311 * Signal events back to the audio system that a buffer was played/can be filled.
312 *
313 * @param buffer - Consumed audio buffer to be released.
314 */
315 void SignalEvent(const ::AudioCore::Sink::SinkBuffer& buffer) {
316 auto& manager{system.AudioCore().GetAudioManager()};
317 switch (type) {
318 case StreamType::Out:
319 released_buffers.enqueue(buffer);
320 manager.SetEvent(Event::Type::AudioOutManager, true);
321 break;
322 case StreamType::In:
323 released_buffers.enqueue(buffer);
324 manager.SetEvent(Event::Type::AudioInManager, true);
325 break;
326 case StreamType::Render:
327 break;
328 }
329 }
330
331 /**
332 * Main callback from SDL. Either expects samples from us (audio render/audio out), or will
333 * provide samples to be copied (audio in).
334 *
335 * @param userdata - Custom data pointer passed along, points to a SDLSinkStream.
336 * @param stream - Buffer of samples to be filled or read.
337 * @param len - Length of the stream in bytes.
338 */
339 static void DataCallback(void* userdata, Uint8* stream, int len) {
340 auto* impl = static_cast<SDLSinkStream*>(userdata);
341
342 if (!impl) {
343 return;
344 }
345
346 const std::size_t num_channels = impl->GetDeviceChannels();
347 const std::size_t frame_size = num_channels;
348 const std::size_t frame_size_bytes = frame_size * sizeof(s16);
349 const std::size_t num_frames{len / num_channels / sizeof(s16)};
350 size_t frames_written{0};
351 [[maybe_unused]] bool underrun{false};
352
353 if (impl->type == StreamType::In) {
354 std::span<s16> input_buffer{reinterpret_cast<s16*>(stream), num_frames * frame_size};
355
356 while (frames_written < num_frames) {
357 auto& playing_buffer{impl->playing_buffer};
358
359 // If the playing buffer has been consumed or has no frames, we need a new one
360 if (playing_buffer.consumed || playing_buffer.frames == 0) {
361 if (!impl->queue.try_dequeue(impl->playing_buffer)) {
362 // If no buffer was available we've underrun, just push the samples and
363 // continue.
364 underrun = true;
365 impl->samples_buffer.Push(&input_buffer[frames_written * frame_size],
366 (num_frames - frames_written) * frame_size);
367 frames_written = num_frames;
368 continue;
369 } else {
370 impl->queued_buffers--;
371 impl->SignalEvent(impl->playing_buffer);
372 }
373 }
374
375 // Get the minimum frames available between the currently playing buffer, and the
376 // amount we have left to fill
377 size_t frames_available{
378 std::min(playing_buffer.frames - playing_buffer.frames_played,
379 num_frames - frames_written)};
380
381 impl->samples_buffer.Push(&input_buffer[frames_written * frame_size],
382 frames_available * frame_size);
383
384 frames_written += frames_available;
385 playing_buffer.frames_played += frames_available;
386
387 // If that's all the frames in the current buffer, add its samples and mark it as
388 // consumed
389 if (playing_buffer.frames_played >= playing_buffer.frames) {
390 impl->AddPlayedSampleCount(playing_buffer.frames_played * num_channels);
391 impl->playing_buffer.consumed = true;
392 }
393 }
394
395 std::memcpy(&impl->last_frame[0], &input_buffer[(frames_written - 1) * frame_size],
396 frame_size_bytes);
397 } else {
398 std::span<s16> output_buffer{reinterpret_cast<s16*>(stream), num_frames * frame_size};
399
400 while (frames_written < num_frames) {
401 auto& playing_buffer{impl->playing_buffer};
402
403 // If the playing buffer has been consumed or has no frames, we need a new one
404 if (playing_buffer.consumed || playing_buffer.frames == 0) {
405 if (!impl->queue.try_dequeue(impl->playing_buffer)) {
406 // If no buffer was available we've underrun, fill the remaining buffer with
407 // the last written frame and continue.
408 underrun = true;
409 for (size_t i = frames_written; i < num_frames; i++) {
410 std::memcpy(&output_buffer[i * frame_size], &impl->last_frame[0],
411 frame_size_bytes);
412 }
413 frames_written = num_frames;
414 continue;
415 } else {
416 impl->queued_buffers--;
417 impl->SignalEvent(impl->playing_buffer);
418 }
419 }
420
421 // Get the minimum frames available between the currently playing buffer, and the
422 // amount we have left to fill
423 size_t frames_available{
424 std::min(playing_buffer.frames - playing_buffer.frames_played,
425 num_frames - frames_written)};
426
427 impl->samples_buffer.Pop(&output_buffer[frames_written * frame_size],
428 frames_available * frame_size);
429
430 frames_written += frames_available;
431 playing_buffer.frames_played += frames_available;
432
433 // If that's all the frames in the current buffer, add its samples and mark it as
434 // consumed
435 if (playing_buffer.frames_played >= playing_buffer.frames) {
436 impl->AddPlayedSampleCount(playing_buffer.frames_played * num_channels);
437 impl->playing_buffer.consumed = true;
438 }
439 }
440
441 std::memcpy(&impl->last_frame[0], &output_buffer[(frames_written - 1) * frame_size],
442 frame_size_bytes);
443 }
444 }
445
446 /// SDL device id of the opened input/output device
447 SDL_AudioDeviceID device{};
448 /// Type of this stream
449 StreamType type;
450 /// Core system
451 Core::System& system;
452 /// Ring buffer of the samples waiting to be played or consumed
453 Common::RingBuffer<s16, 0x10000> samples_buffer;
454 /// Audio buffers queued and waiting to play
455 Common::ReaderWriterQueue<::AudioCore::Sink::SinkBuffer> queue;
456 /// The currently-playing audio buffer
457 ::AudioCore::Sink::SinkBuffer playing_buffer{};
458 /// Audio buffers which have been played and are in queue to be released by the audio system
459 Common::ReaderWriterQueue<::AudioCore::Sink::SinkBuffer> released_buffers{};
460 /// Currently released buffer waiting to be taken by the audio system
461 ::AudioCore::Sink::SinkBuffer released_buffer{};
462 /// The last played (or received) frame of audio, used when the callback underruns
463 std::array<s16, MaxChannels> last_frame{};
464};
465
466SDLSink::SDLSink(std::string_view target_device_name) {
467 if (!SDL_WasInit(SDL_INIT_AUDIO)) {
468 if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) {
469 LOG_CRITICAL(Audio_Sink, "SDL_InitSubSystem audio failed: {}", SDL_GetError());
470 return;
471 }
472 }
473
474 if (target_device_name != auto_device_name && !target_device_name.empty()) {
475 output_device = target_device_name;
476 } else {
477 output_device.clear();
478 }
479
480 device_channels = 2;
481}
482
483SDLSink::~SDLSink() = default;
484
485SinkStream* SDLSink::AcquireSinkStream(Core::System& system, const u32 system_channels,
486 const std::string&, const StreamType type) {
487 SinkStreamPtr& stream = sink_streams.emplace_back(std::make_unique<SDLSinkStream>(
488 device_channels, system_channels, output_device, input_device, type, system));
489 return stream.get();
490}
491
492void SDLSink::CloseStream(const SinkStream* stream) {
493 for (size_t i = 0; i < sink_streams.size(); i++) {
494 if (sink_streams[i].get() == stream) {
495 sink_streams[i].reset();
496 sink_streams.erase(sink_streams.begin() + i);
497 break;
498 }
499 }
500}
501
502void SDLSink::CloseStreams() {
503 sink_streams.clear();
504}
505
506void SDLSink::PauseStreams() {
507 for (auto& stream : sink_streams) {
508 stream->Stop();
509 }
510}
511
512void SDLSink::UnpauseStreams() {
513 for (auto& stream : sink_streams) {
514 stream->Start();
515 }
516}
517
518f32 SDLSink::GetDeviceVolume() const {
519 if (sink_streams.empty()) {
520 return 1.0f;
521 }
522
523 return sink_streams[0]->GetDeviceVolume();
524}
525
526void SDLSink::SetDeviceVolume(const f32 volume) {
527 for (auto& stream : sink_streams) {
528 stream->SetDeviceVolume(volume);
529 }
530}
531
532void SDLSink::SetSystemVolume(const f32 volume) {
533 for (auto& stream : sink_streams) {
534 stream->SetSystemVolume(volume);
535 }
536}
537
538std::vector<std::string> ListSDLSinkDevices(const bool capture) {
539 std::vector<std::string> device_list;
540
541 if (!SDL_WasInit(SDL_INIT_AUDIO)) {
542 if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) {
543 LOG_CRITICAL(Audio_Sink, "SDL_InitSubSystem audio failed: {}", SDL_GetError());
544 return {};
545 }
546 }
547
548 const int device_count = SDL_GetNumAudioDevices(capture);
549 for (int i = 0; i < device_count; ++i) {
550 device_list.emplace_back(SDL_GetAudioDeviceName(i, 0));
551 }
552
553 return device_list;
554}
555
556} // namespace AudioCore::Sink
diff --git a/src/audio_core/sink/sdl2_sink.h b/src/audio_core/sink/sdl2_sink.h
new file mode 100644
index 000000000..186bc2fa3
--- /dev/null
+++ b/src/audio_core/sink/sdl2_sink.h
@@ -0,0 +1,101 @@
1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <string>
7#include <vector>
8
9#include "audio_core/sink/sink.h"
10
11namespace Core {
12class System;
13}
14
15namespace AudioCore::Sink {
16class SinkStream;
17
18/**
19 * SDL backend sink, holds multiple output streams and is responsible for sinking samples to
20 * hardware. Used by Audio Render, Audio In and Audio Out.
21 */
22class SDLSink final : public Sink {
23public:
24 explicit SDLSink(std::string_view device_id);
25 ~SDLSink() override;
26
27 /**
28 * Create a new sink stream.
29 *
30 * @param system - Core system.
31 * @param system_channels - Number of channels the audio system expects.
32 * May differ from the device's channel count.
33 * @param name - Name of this stream.
34 * @param type - Type of this stream, render/in/out.
35 * @param event - Audio render only, a signal used to prevent the renderer running too
36 * fast.
37 * @return A pointer to the created SinkStream
38 */
39 SinkStream* AcquireSinkStream(Core::System& system, u32 system_channels,
40 const std::string& name, StreamType type) override;
41
42 /**
43 * Close a given stream.
44 *
45 * @param stream - The stream to close.
46 */
47 void CloseStream(const SinkStream* stream) override;
48
49 /**
50 * Close all streams.
51 */
52 void CloseStreams() override;
53
54 /**
55 * Pause all streams.
56 */
57 void PauseStreams() override;
58
59 /**
60 * Unpause all streams.
61 */
62 void UnpauseStreams() override;
63
64 /**
65 * Get the device volume. Set from calls to the IAudioDevice service.
66 *
67 * @return Volume of the device.
68 */
69 f32 GetDeviceVolume() const override;
70
71 /**
72 * Set the device volume. Set from calls to the IAudioDevice service.
73 *
74 * @param volume - New volume of the device.
75 */
76 void SetDeviceVolume(f32 volume) override;
77
78 /**
79 * Set the system volume. Comes from the audio system using this stream.
80 *
81 * @param volume - New volume of the system.
82 */
83 void SetSystemVolume(f32 volume) override;
84
85private:
86 /// Name of the output device used by streams
87 std::string output_device;
88 /// Name of the input device used by streams
89 std::string input_device;
90 /// Vector of streams managed by this sink
91 std::vector<SinkStreamPtr> sink_streams;
92};
93
94/**
95 * Get a list of conencted devices from Cubeb.
96 *
97 * @param capture - Return input (capture) devices if true, otherwise output devices.
98 */
99std::vector<std::string> ListSDLSinkDevices(bool capture);
100
101} // namespace AudioCore::Sink
diff --git a/src/audio_core/sink/sink.h b/src/audio_core/sink/sink.h
new file mode 100644
index 000000000..91fe455e4
--- /dev/null
+++ b/src/audio_core/sink/sink.h
@@ -0,0 +1,106 @@
1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <memory>
7#include <string>
8
9#include "audio_core/sink/sink_stream.h"
10#include "common/common_types.h"
11
12namespace Common {
13class Event;
14}
15namespace Core {
16class System;
17}
18
19namespace AudioCore::Sink {
20
21constexpr char auto_device_name[] = "auto";
22
23/**
24 * This class is an interface for an audio sink, holds multiple output streams and is responsible
25 * for sinking samples to hardware. Used by Audio Render, Audio In and Audio Out.
26 */
27class Sink {
28public:
29 virtual ~Sink() = default;
30 /**
31 * Close a given stream.
32 *
33 * @param stream - The stream to close.
34 */
35 virtual void CloseStream(const SinkStream* stream) = 0;
36
37 /**
38 * Close all streams.
39 */
40 virtual void CloseStreams() = 0;
41
42 /**
43 * Pause all streams.
44 */
45 virtual void PauseStreams() = 0;
46
47 /**
48 * Unpause all streams.
49 */
50 virtual void UnpauseStreams() = 0;
51
52 /**
53 * Create a new sink stream, kept within this sink, with a pointer returned for use.
54 * Do not free the returned pointer. When done with the stream, call CloseStream on the sink.
55 *
56 * @param system - Core system.
57 * @param system_channels - Number of channels the audio system expects.
58 * May differ from the device's channel count.
59 * @param name - Name of this stream.
60 * @param type - Type of this stream, render/in/out.
61 * @param event - Audio render only, a signal used to prevent the renderer running too
62 * fast.
63 * @return A pointer to the created SinkStream
64 */
65 virtual SinkStream* AcquireSinkStream(Core::System& system, u32 system_channels,
66 const std::string& name, StreamType type) = 0;
67
68 /**
69 * Get the number of channels the hardware device supports.
70 * Either 2 or 6.
71 *
72 * @return Number of device channels.
73 */
74 u32 GetDeviceChannels() const {
75 return device_channels;
76 }
77
78 /**
79 * Get the device volume. Set from calls to the IAudioDevice service.
80 *
81 * @return Volume of the device.
82 */
83 virtual f32 GetDeviceVolume() const = 0;
84
85 /**
86 * Set the device volume. Set from calls to the IAudioDevice service.
87 *
88 * @param volume - New volume of the device.
89 */
90 virtual void SetDeviceVolume(f32 volume) = 0;
91
92 /**
93 * Set the system volume. Comes from the audio system using this stream.
94 *
95 * @param volume - New volume of the system.
96 */
97 virtual void SetSystemVolume(f32 volume) = 0;
98
99protected:
100 /// Number of device channels supported by the hardware
101 u32 device_channels{2};
102};
103
104using SinkPtr = std::unique_ptr<Sink>;
105
106} // namespace AudioCore::Sink
diff --git a/src/audio_core/sink_details.cpp b/src/audio_core/sink/sink_details.cpp
index c4cc66111..253c0fd1e 100644
--- a/src/audio_core/sink_details.cpp
+++ b/src/audio_core/sink/sink_details.cpp
@@ -5,21 +5,21 @@
5#include <memory> 5#include <memory>
6#include <string> 6#include <string>
7#include <vector> 7#include <vector>
8#include "audio_core/null_sink.h" 8#include "audio_core/sink/null_sink.h"
9#include "audio_core/sink_details.h" 9#include "audio_core/sink/sink_details.h"
10#ifdef HAVE_CUBEB 10#ifdef HAVE_CUBEB
11#include "audio_core/cubeb_sink.h" 11#include "audio_core/sink/cubeb_sink.h"
12#endif 12#endif
13#ifdef HAVE_SDL2 13#ifdef HAVE_SDL2
14#include "audio_core/sdl2_sink.h" 14#include "audio_core/sink/sdl2_sink.h"
15#endif 15#endif
16#include "common/logging/log.h" 16#include "common/logging/log.h"
17 17
18namespace AudioCore { 18namespace AudioCore::Sink {
19namespace { 19namespace {
20struct SinkDetails { 20struct SinkDetails {
21 using FactoryFn = std::unique_ptr<Sink> (*)(std::string_view); 21 using FactoryFn = std::unique_ptr<Sink> (*)(std::string_view);
22 using ListDevicesFn = std::vector<std::string> (*)(); 22 using ListDevicesFn = std::vector<std::string> (*)(bool);
23 23
24 /// Name for this sink. 24 /// Name for this sink.
25 const char* id; 25 const char* id;
@@ -49,17 +49,18 @@ constexpr SinkDetails sink_details[] = {
49 [](std::string_view device_id) -> std::unique_ptr<Sink> { 49 [](std::string_view device_id) -> std::unique_ptr<Sink> {
50 return std::make_unique<NullSink>(device_id); 50 return std::make_unique<NullSink>(device_id);
51 }, 51 },
52 [] { return std::vector<std::string>{"null"}; }}, 52 [](bool capture) { return std::vector<std::string>{"null"}; }},
53}; 53};
54 54
55const SinkDetails& GetSinkDetails(std::string_view sink_id) { 55const SinkDetails& GetOutputSinkDetails(std::string_view sink_id) {
56 auto iter = 56 auto iter =
57 std::find_if(std::begin(sink_details), std::end(sink_details), 57 std::find_if(std::begin(sink_details), std::end(sink_details),
58 [sink_id](const auto& sink_detail) { return sink_detail.id == sink_id; }); 58 [sink_id](const auto& sink_detail) { return sink_detail.id == sink_id; });
59 59
60 if (sink_id == "auto" || iter == std::end(sink_details)) { 60 if (sink_id == "auto" || iter == std::end(sink_details)) {
61 if (sink_id != "auto") { 61 if (sink_id != "auto") {
62 LOG_ERROR(Audio, "AudioCore::SelectSink given invalid sink_id {}", sink_id); 62 LOG_ERROR(Audio, "AudioCore::Sink::GetOutputSinkDetails given invalid sink_id {}",
63 sink_id);
63 } 64 }
64 // Auto-select. 65 // Auto-select.
65 // sink_details is ordered in terms of desirability, with the best choice at the front. 66 // sink_details is ordered in terms of desirability, with the best choice at the front.
@@ -79,12 +80,12 @@ std::vector<const char*> GetSinkIDs() {
79 return sink_ids; 80 return sink_ids;
80} 81}
81 82
82std::vector<std::string> GetDeviceListForSink(std::string_view sink_id) { 83std::vector<std::string> GetDeviceListForSink(std::string_view sink_id, bool capture) {
83 return GetSinkDetails(sink_id).list_devices(); 84 return GetOutputSinkDetails(sink_id).list_devices(capture);
84} 85}
85 86
86std::unique_ptr<Sink> CreateSinkFromID(std::string_view sink_id, std::string_view device_id) { 87std::unique_ptr<Sink> CreateSinkFromID(std::string_view sink_id, std::string_view device_id) {
87 return GetSinkDetails(sink_id).factory(device_id); 88 return GetOutputSinkDetails(sink_id).factory(device_id);
88} 89}
89 90
90} // namespace AudioCore 91} // namespace AudioCore::Sink
diff --git a/src/audio_core/sink/sink_details.h b/src/audio_core/sink/sink_details.h
new file mode 100644
index 000000000..3ebdb1e30
--- /dev/null
+++ b/src/audio_core/sink/sink_details.h
@@ -0,0 +1,43 @@
1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <string>
7#include <string_view>
8#include <vector>
9
10namespace AudioCore {
11class AudioManager;
12
13namespace Sink {
14
15class Sink;
16
17/**
18 * Retrieves the IDs for all available audio sinks.
19 *
20 * @return Vector of available sink names.
21 */
22std::vector<const char*> GetSinkIDs();
23
24/**
25 * Gets the list of devices for a particular sink identified by the given ID.
26 *
27 * @param sink_id - Id of the sink to get devices from.
28 * @param capture - Get capture (input) devices, or output devices?
29 * @return Vector of device names.
30 */
31std::vector<std::string> GetDeviceListForSink(std::string_view sink_id, bool capture);
32
33/**
34 * Creates an audio sink identified by the given device ID.
35 *
36 * @param sink_id - Id of the sink to create.
37 * @param device_id - Name of the device to create.
38 * @return Pointer to the created sink.
39 */
40std::unique_ptr<Sink> CreateSinkFromID(std::string_view sink_id, std::string_view device_id);
41
42} // namespace Sink
43} // namespace AudioCore
diff --git a/src/audio_core/sink/sink_stream.h b/src/audio_core/sink/sink_stream.h
new file mode 100644
index 000000000..17ed6593f
--- /dev/null
+++ b/src/audio_core/sink/sink_stream.h
@@ -0,0 +1,224 @@
1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <atomic>
7#include <memory>
8#include <vector>
9
10#include "audio_core/common/common.h"
11#include "common/common_types.h"
12
13namespace AudioCore::Sink {
14
15enum class StreamType {
16 Render,
17 Out,
18 In,
19};
20
21struct SinkBuffer {
22 u64 frames;
23 u64 frames_played;
24 u64 tag;
25 bool consumed;
26};
27
28/**
29 * Contains a real backend stream for outputting samples to hardware,
30 * created only via a Sink (See Sink::AcquireSinkStream).
31 *
32 * Accepts a SinkBuffer and samples in PCM16 format to be output (see AppendBuffer).
33 * Appended buffers act as a FIFO queue, and will be held until played.
34 * You should regularly call IsBufferConsumed with the unique SinkBuffer tag to check if the buffer
35 * has been consumed.
36 *
37 * Since these are a FIFO queue, always check IsBufferConsumed in the same order you appended the
38 * buffers, skipping a buffer will result in all following buffers to never release.
39 *
40 * If the buffers appear to be stuck, you can stop and re-open an IAudioIn/IAudioOut service (this
41 * is what games do), or call ClearQueue to flush all of the buffers without a full restart.
42 */
43class SinkStream {
44public:
45 virtual ~SinkStream() = default;
46
47 /**
48 * Finalize the sink stream.
49 */
50 virtual void Finalize() = 0;
51
52 /**
53 * Start the sink stream.
54 *
55 * @param resume - Set to true if this is resuming the stream a previously-active stream.
56 * Default false.
57 */
58 virtual void Start(bool resume = false) = 0;
59
60 /**
61 * Stop the sink stream.
62 */
63 virtual void Stop() = 0;
64
65 /**
66 * Append a new buffer and its samples to a waiting queue to play.
67 *
68 * @param buffer - Audio buffer information to be queued.
69 * @param samples - The s16 samples to be queue for playback.
70 */
71 virtual void AppendBuffer(SinkBuffer& buffer, std::vector<s16>& samples) = 0;
72
73 /**
74 * Release a buffer. Audio In only, will fill a buffer with recorded samples.
75 *
76 * @param num_samples - Maximum number of samples to receive.
77 * @return Vector of recorded samples. May have fewer than num_samples.
78 */
79 virtual std::vector<s16> ReleaseBuffer(u64 num_samples) = 0;
80
81 /**
82 * Check if a certain buffer has been consumed (fully played).
83 *
84 * @param tag - Unique tag of a buffer to check for.
85 * @return True if the buffer has been played, otherwise false.
86 */
87 virtual bool IsBufferConsumed(u64 tag) = 0;
88
89 /**
90 * Empty out the buffer queue.
91 */
92 virtual void ClearQueue() = 0;
93
94 /**
95 * Check if the stream is paused.
96 *
97 * @return True if paused, otherwise false.
98 */
99 bool IsPaused() {
100 return paused;
101 }
102
103 /**
104 * Get the number of system channels in this stream.
105 *
106 * @return Number of system channels.
107 */
108 u32 GetSystemChannels() const {
109 return system_channels;
110 }
111
112 /**
113 * Set the number of channels the system expects.
114 *
115 * @param channels - New number of system channels.
116 */
117 void SetSystemChannels(u32 channels) {
118 system_channels = channels;
119 }
120
121 /**
122 * Get the number of channels the hardware supports.
123 *
124 * @return Number of channels supported.
125 */
126 u32 GetDeviceChannels() const {
127 return device_channels;
128 }
129
130 /**
131 * Get the total number of samples played by this stream.
132 *
133 * @return Number of samples played.
134 */
135 u64 GetPlayedSampleCount() const {
136 return played_sample_count;
137 }
138
139 /**
140 * Set the number of samples played.
141 * This is started and stopped on system start/stop.
142 *
143 * @param played_sample_count_ - Number of samples to set.
144 */
145 void SetPlayedSampleCount(u64 played_sample_count_) {
146 played_sample_count = played_sample_count_;
147 }
148
149 /**
150 * Add to the played sample count.
151 *
152 * @param num_samples - Number of samples to add.
153 */
154 void AddPlayedSampleCount(u64 num_samples) {
155 played_sample_count += num_samples;
156 }
157
158 /**
159 * Get the system volume.
160 *
161 * @return The current system volume.
162 */
163 f32 GetSystemVolume() const {
164 return system_volume;
165 }
166
167 /**
168 * Get the device volume.
169 *
170 * @return The current device volume.
171 */
172 f32 GetDeviceVolume() const {
173 return device_volume;
174 }
175
176 /**
177 * Set the system volume.
178 *
179 * @param volume_ - The new system volume.
180 */
181 void SetSystemVolume(f32 volume_) {
182 system_volume = volume_;
183 }
184
185 /**
186 * Set the device volume.
187 *
188 * @param volume_ - The new device volume.
189 */
190 void SetDeviceVolume(f32 volume_) {
191 device_volume = volume_;
192 }
193
194 /**
195 * Get the number of queued audio buffers.
196 *
197 * @return The number of queued buffers.
198 */
199 u32 GetQueueSize() {
200 return queued_buffers.load();
201 }
202
203protected:
204 /// Number of buffers waiting to be played
205 std::atomic<u32> queued_buffers{};
206 /// Total samples played by this stream
207 std::atomic<u64> played_sample_count{};
208 /// Set by the audio render/in/out system which uses this stream
209 f32 system_volume{1.0f};
210 /// Set via IAudioDevice service calls
211 f32 device_volume{1.0f};
212 /// Set by the audio render/in/out systen which uses this stream
213 u32 system_channels{2};
214 /// Channels supported by hardware
215 u32 device_channels{2};
216 /// Is this stream currently paused?
217 std::atomic<bool> paused{true};
218 /// Was this stream previously playing?
219 std::atomic<bool> was_playing{false};
220};
221
222using SinkStreamPtr = std::unique_ptr<SinkStream>;
223
224} // namespace AudioCore::Sink
diff --git a/src/audio_core/sink_context.cpp b/src/audio_core/sink_context.cpp
deleted file mode 100644
index 835e12f67..000000000
--- a/src/audio_core/sink_context.cpp
+++ /dev/null
@@ -1,47 +0,0 @@
1// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/sink_context.h"
5
6namespace AudioCore {
7SinkContext::SinkContext(std::size_t sink_count_) : sink_count{sink_count_} {}
8SinkContext::~SinkContext() = default;
9
10std::size_t SinkContext::GetCount() const {
11 return sink_count;
12}
13
14void SinkContext::UpdateMainSink(const SinkInfo::InParams& in) {
15 ASSERT(in.type == SinkTypes::Device);
16
17 if (in.device.down_matrix_enabled) {
18 downmix_coefficients = in.device.down_matrix_coef;
19 } else {
20 downmix_coefficients = {
21 1.0f, // front
22 0.707f, // center
23 0.0f, // lfe
24 0.707f, // back
25 };
26 }
27
28 in_use = in.in_use;
29 use_count = in.device.input_count;
30 buffers = in.device.input;
31}
32
33bool SinkContext::InUse() const {
34 return in_use;
35}
36
37std::vector<u8> SinkContext::OutputBuffers() const {
38 std::vector<u8> buffer_ret(use_count);
39 std::memcpy(buffer_ret.data(), buffers.data(), use_count);
40 return buffer_ret;
41}
42
43const DownmixCoefficients& SinkContext::GetDownmixCoefficients() const {
44 return downmix_coefficients;
45}
46
47} // namespace AudioCore
diff --git a/src/audio_core/sink_context.h b/src/audio_core/sink_context.h
deleted file mode 100644
index cc5a90d80..000000000
--- a/src/audio_core/sink_context.h
+++ /dev/null
@@ -1,95 +0,0 @@
1// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7#include <vector>
8#include "audio_core/common.h"
9#include "common/common_funcs.h"
10#include "common/common_types.h"
11#include "common/swap.h"
12
13namespace AudioCore {
14
15using DownmixCoefficients = std::array<float_le, 4>;
16
17enum class SinkTypes : u8 {
18 Invalid = 0,
19 Device = 1,
20 Circular = 2,
21};
22
23enum class SinkSampleFormat : u32_le {
24 None = 0,
25 Pcm8 = 1,
26 Pcm16 = 2,
27 Pcm24 = 3,
28 Pcm32 = 4,
29 PcmFloat = 5,
30 Adpcm = 6,
31};
32
33class SinkInfo {
34public:
35 struct CircularBufferIn {
36 u64_le address;
37 u32_le size;
38 u32_le input_count;
39 u32_le sample_count;
40 u32_le previous_position;
41 SinkSampleFormat sample_format;
42 std::array<u8, AudioCommon::MAX_CHANNEL_COUNT> input;
43 bool in_use;
44 INSERT_PADDING_BYTES_NOINIT(5);
45 };
46 static_assert(sizeof(CircularBufferIn) == 0x28,
47 "SinkInfo::CircularBufferIn is in invalid size");
48
49 struct DeviceIn {
50 std::array<u8, 255> device_name;
51 INSERT_PADDING_BYTES_NOINIT(1);
52 s32_le input_count;
53 std::array<u8, AudioCommon::MAX_CHANNEL_COUNT> input;
54 INSERT_PADDING_BYTES_NOINIT(1);
55 bool down_matrix_enabled;
56 DownmixCoefficients down_matrix_coef;
57 };
58 static_assert(sizeof(DeviceIn) == 0x11c, "SinkInfo::DeviceIn is an invalid size");
59
60 struct InParams {
61 SinkTypes type{};
62 bool in_use{};
63 INSERT_PADDING_BYTES(2);
64 u32_le node_id{};
65 INSERT_PADDING_WORDS(6);
66 union {
67 // std::array<u8, 0x120> raw{};
68 DeviceIn device;
69 CircularBufferIn circular_buffer;
70 };
71 };
72 static_assert(sizeof(InParams) == 0x140, "SinkInfo::InParams are an invalid size!");
73};
74
75class SinkContext {
76public:
77 explicit SinkContext(std::size_t sink_count_);
78 ~SinkContext();
79
80 [[nodiscard]] std::size_t GetCount() const;
81
82 void UpdateMainSink(const SinkInfo::InParams& in);
83 [[nodiscard]] bool InUse() const;
84 [[nodiscard]] std::vector<u8> OutputBuffers() const;
85
86 [[nodiscard]] const DownmixCoefficients& GetDownmixCoefficients() const;
87
88private:
89 bool in_use{false};
90 s32 use_count{};
91 std::array<u8, AudioCommon::MAX_CHANNEL_COUNT> buffers{};
92 std::size_t sink_count{};
93 DownmixCoefficients downmix_coefficients{};
94};
95} // namespace AudioCore
diff --git a/src/audio_core/sink_details.h b/src/audio_core/sink_details.h
deleted file mode 100644
index 042766358..000000000
--- a/src/audio_core/sink_details.h
+++ /dev/null
@@ -1,23 +0,0 @@
1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <string>
7#include <string_view>
8#include <vector>
9
10namespace AudioCore {
11
12class Sink;
13
14/// Retrieves the IDs for all available audio sinks.
15std::vector<const char*> GetSinkIDs();
16
17/// Gets the list of devices for a particular sink identified by the given ID.
18std::vector<std::string> GetDeviceListForSink(std::string_view sink_id);
19
20/// Creates an audio sink identified by the given device ID.
21std::unique_ptr<Sink> CreateSinkFromID(std::string_view sink_id, std::string_view device_id);
22
23} // namespace AudioCore
diff --git a/src/audio_core/sink_stream.h b/src/audio_core/sink_stream.h
deleted file mode 100644
index 0449b90af..000000000
--- a/src/audio_core/sink_stream.h
+++ /dev/null
@@ -1,35 +0,0 @@
1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <memory>
7#include <vector>
8
9#include "common/common_types.h"
10
11namespace AudioCore {
12
13/**
14 * Accepts samples in stereo signed PCM16 format to be output. Sinks *do not* handle resampling and
15 * expect the correct sample rate. They are dumb outputs.
16 */
17class SinkStream {
18public:
19 virtual ~SinkStream() = default;
20
21 /**
22 * Feed stereo samples to sink.
23 * @param num_channels Number of channels used.
24 * @param samples Samples in interleaved stereo PCM16 format.
25 */
26 virtual void EnqueueSamples(u32 num_channels, const std::vector<s16>& samples) = 0;
27
28 virtual std::size_t SamplesInQueue(u32 num_channels) const = 0;
29
30 virtual void Flush() = 0;
31};
32
33using SinkStreamPtr = std::unique_ptr<SinkStream>;
34
35} // namespace AudioCore
diff --git a/src/audio_core/splitter_context.cpp b/src/audio_core/splitter_context.cpp
deleted file mode 100644
index 10646dc05..000000000
--- a/src/audio_core/splitter_context.cpp
+++ /dev/null
@@ -1,616 +0,0 @@
1// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "audio_core/behavior_info.h"
5#include "audio_core/splitter_context.h"
6#include "common/alignment.h"
7#include "common/assert.h"
8#include "common/logging/log.h"
9
10namespace AudioCore {
11
12ServerSplitterDestinationData::ServerSplitterDestinationData(s32 id_) : id{id_} {}
13ServerSplitterDestinationData::~ServerSplitterDestinationData() = default;
14
15void ServerSplitterDestinationData::Update(SplitterInfo::InDestinationParams& header) {
16 // Log error as these are not actually failure states
17 if (header.magic != SplitterMagic::DataHeader) {
18 LOG_ERROR(Audio, "Splitter destination header is invalid!");
19 return;
20 }
21
22 // Incorrect splitter id
23 if (header.splitter_id != id) {
24 LOG_ERROR(Audio, "Splitter destination ids do not match!");
25 return;
26 }
27
28 mix_id = header.mix_id;
29 // Copy our mix volumes
30 std::copy(header.mix_volumes.begin(), header.mix_volumes.end(), current_mix_volumes.begin());
31 if (!in_use && header.in_use) {
32 // Update mix volumes
33 std::copy(current_mix_volumes.begin(), current_mix_volumes.end(), last_mix_volumes.begin());
34 needs_update = false;
35 }
36 in_use = header.in_use;
37}
38
39ServerSplitterDestinationData* ServerSplitterDestinationData::GetNextDestination() {
40 return next;
41}
42
43const ServerSplitterDestinationData* ServerSplitterDestinationData::GetNextDestination() const {
44 return next;
45}
46
47void ServerSplitterDestinationData::SetNextDestination(ServerSplitterDestinationData* dest) {
48 next = dest;
49}
50
51bool ServerSplitterDestinationData::ValidMixId() const {
52 return GetMixId() != AudioCommon::NO_MIX;
53}
54
55s32 ServerSplitterDestinationData::GetMixId() const {
56 return mix_id;
57}
58
59bool ServerSplitterDestinationData::IsConfigured() const {
60 return in_use && ValidMixId();
61}
62
63float ServerSplitterDestinationData::GetMixVolume(std::size_t i) const {
64 ASSERT(i < AudioCommon::MAX_MIX_BUFFERS);
65 return current_mix_volumes.at(i);
66}
67
68const std::array<float, AudioCommon::MAX_MIX_BUFFERS>&
69ServerSplitterDestinationData::CurrentMixVolumes() const {
70 return current_mix_volumes;
71}
72
73const std::array<float, AudioCommon::MAX_MIX_BUFFERS>&
74ServerSplitterDestinationData::LastMixVolumes() const {
75 return last_mix_volumes;
76}
77
78void ServerSplitterDestinationData::MarkDirty() {
79 needs_update = true;
80}
81
82void ServerSplitterDestinationData::UpdateInternalState() {
83 if (in_use && needs_update) {
84 std::copy(current_mix_volumes.begin(), current_mix_volumes.end(), last_mix_volumes.begin());
85 }
86 needs_update = false;
87}
88
89ServerSplitterInfo::ServerSplitterInfo(s32 id_) : id(id_) {}
90ServerSplitterInfo::~ServerSplitterInfo() = default;
91
92void ServerSplitterInfo::InitializeInfos() {
93 send_length = 0;
94 head = nullptr;
95 new_connection = true;
96}
97
98void ServerSplitterInfo::ClearNewConnectionFlag() {
99 new_connection = false;
100}
101
102std::size_t ServerSplitterInfo::Update(SplitterInfo::InInfoPrams& header) {
103 if (header.send_id != id) {
104 return 0;
105 }
106
107 sample_rate = header.sample_rate;
108 new_connection = true;
109 // We need to update the size here due to the splitter bug being present and providing an
110 // incorrect size. We're suppose to also update the header here but we just ignore and continue
111 return (sizeof(s32_le) * (header.length - 1)) + (sizeof(s32_le) * 3);
112}
113
114ServerSplitterDestinationData* ServerSplitterInfo::GetHead() {
115 return head;
116}
117
118const ServerSplitterDestinationData* ServerSplitterInfo::GetHead() const {
119 return head;
120}
121
122ServerSplitterDestinationData* ServerSplitterInfo::GetData(std::size_t depth) {
123 auto* current_head = head;
124 for (std::size_t i = 0; i < depth; i++) {
125 if (current_head == nullptr) {
126 return nullptr;
127 }
128 current_head = current_head->GetNextDestination();
129 }
130 return current_head;
131}
132
133const ServerSplitterDestinationData* ServerSplitterInfo::GetData(std::size_t depth) const {
134 auto* current_head = head;
135 for (std::size_t i = 0; i < depth; i++) {
136 if (current_head == nullptr) {
137 return nullptr;
138 }
139 current_head = current_head->GetNextDestination();
140 }
141 return current_head;
142}
143
144bool ServerSplitterInfo::HasNewConnection() const {
145 return new_connection;
146}
147
148s32 ServerSplitterInfo::GetLength() const {
149 return send_length;
150}
151
152void ServerSplitterInfo::SetHead(ServerSplitterDestinationData* new_head) {
153 head = new_head;
154}
155
156void ServerSplitterInfo::SetHeadDepth(s32 length) {
157 send_length = length;
158}
159
160SplitterContext::SplitterContext() = default;
161SplitterContext::~SplitterContext() = default;
162
163void SplitterContext::Initialize(BehaviorInfo& behavior_info, std::size_t _info_count,
164 std::size_t _data_count) {
165 if (!behavior_info.IsSplitterSupported() || _data_count == 0 || _info_count == 0) {
166 Setup(0, 0, false);
167 return;
168 }
169 // Only initialize if we're using splitters
170 Setup(_info_count, _data_count, behavior_info.IsSplitterBugFixed());
171}
172
173bool SplitterContext::Update(const std::vector<u8>& input, std::size_t& input_offset,
174 std::size_t& bytes_read) {
175 const auto UpdateOffsets = [&](std::size_t read) {
176 input_offset += read;
177 bytes_read += read;
178 };
179
180 if (info_count == 0 || data_count == 0) {
181 bytes_read = 0;
182 return true;
183 }
184
185 if (!AudioCommon::CanConsumeBuffer(input.size(), input_offset,
186 sizeof(SplitterInfo::InHeader))) {
187 LOG_ERROR(Audio, "Buffer is an invalid size!");
188 return false;
189 }
190 SplitterInfo::InHeader header{};
191 std::memcpy(&header, input.data() + input_offset, sizeof(SplitterInfo::InHeader));
192 UpdateOffsets(sizeof(SplitterInfo::InHeader));
193
194 if (header.magic != SplitterMagic::SplitterHeader) {
195 LOG_ERROR(Audio, "Invalid header magic! Expecting {:X} but got {:X}",
196 SplitterMagic::SplitterHeader, header.magic);
197 return false;
198 }
199
200 // Clear all connections
201 for (auto& info : infos) {
202 info.ClearNewConnectionFlag();
203 }
204
205 UpdateInfo(input, input_offset, bytes_read, header.info_count);
206 UpdateData(input, input_offset, bytes_read, header.data_count);
207 const auto aligned_bytes_read = Common::AlignUp(bytes_read, 16);
208 input_offset += aligned_bytes_read - bytes_read;
209 bytes_read = aligned_bytes_read;
210 return true;
211}
212
213bool SplitterContext::UsingSplitter() const {
214 return info_count > 0 && data_count > 0;
215}
216
217ServerSplitterInfo& SplitterContext::GetInfo(std::size_t i) {
218 ASSERT(i < info_count);
219 return infos.at(i);
220}
221
222const ServerSplitterInfo& SplitterContext::GetInfo(std::size_t i) const {
223 ASSERT(i < info_count);
224 return infos.at(i);
225}
226
227ServerSplitterDestinationData& SplitterContext::GetData(std::size_t i) {
228 ASSERT(i < data_count);
229 return datas.at(i);
230}
231
232const ServerSplitterDestinationData& SplitterContext::GetData(std::size_t i) const {
233 ASSERT(i < data_count);
234 return datas.at(i);
235}
236
237ServerSplitterDestinationData* SplitterContext::GetDestinationData(std::size_t info,
238 std::size_t data) {
239 ASSERT(info < info_count);
240 auto& cur_info = GetInfo(info);
241 return cur_info.GetData(data);
242}
243
244const ServerSplitterDestinationData* SplitterContext::GetDestinationData(std::size_t info,
245 std::size_t data) const {
246 ASSERT(info < info_count);
247 const auto& cur_info = GetInfo(info);
248 return cur_info.GetData(data);
249}
250
251void SplitterContext::UpdateInternalState() {
252 if (data_count == 0) {
253 return;
254 }
255
256 for (auto& data : datas) {
257 data.UpdateInternalState();
258 }
259}
260
261std::size_t SplitterContext::GetInfoCount() const {
262 return info_count;
263}
264
265std::size_t SplitterContext::GetDataCount() const {
266 return data_count;
267}
268
269void SplitterContext::Setup(std::size_t info_count_, std::size_t data_count_,
270 bool is_splitter_bug_fixed) {
271
272 info_count = info_count_;
273 data_count = data_count_;
274
275 for (std::size_t i = 0; i < info_count; i++) {
276 auto& splitter = infos.emplace_back(static_cast<s32>(i));
277 splitter.InitializeInfos();
278 }
279 for (std::size_t i = 0; i < data_count; i++) {
280 datas.emplace_back(static_cast<s32>(i));
281 }
282
283 bug_fixed = is_splitter_bug_fixed;
284}
285
286bool SplitterContext::UpdateInfo(const std::vector<u8>& input, std::size_t& input_offset,
287 std::size_t& bytes_read, s32 in_splitter_count) {
288 const auto UpdateOffsets = [&](std::size_t read) {
289 input_offset += read;
290 bytes_read += read;
291 };
292
293 for (s32 i = 0; i < in_splitter_count; i++) {
294 if (!AudioCommon::CanConsumeBuffer(input.size(), input_offset,
295 sizeof(SplitterInfo::InInfoPrams))) {
296 LOG_ERROR(Audio, "Buffer is an invalid size!");
297 return false;
298 }
299 SplitterInfo::InInfoPrams header{};
300 std::memcpy(&header, input.data() + input_offset, sizeof(SplitterInfo::InInfoPrams));
301
302 // Logged as warning as these don't actually cause a bailout for some reason
303 if (header.magic != SplitterMagic::InfoHeader) {
304 LOG_ERROR(Audio, "Bad splitter data header");
305 break;
306 }
307
308 if (header.send_id < 0 || static_cast<std::size_t>(header.send_id) > info_count) {
309 LOG_ERROR(Audio, "Bad splitter data id");
310 break;
311 }
312
313 UpdateOffsets(sizeof(SplitterInfo::InInfoPrams));
314 auto& info = GetInfo(header.send_id);
315 if (!RecomposeDestination(info, header, input, input_offset)) {
316 LOG_ERROR(Audio, "Failed to recompose destination for splitter!");
317 return false;
318 }
319 const std::size_t read = info.Update(header);
320 bytes_read += read;
321 input_offset += read;
322 }
323 return true;
324}
325
326bool SplitterContext::UpdateData(const std::vector<u8>& input, std::size_t& input_offset,
327 std::size_t& bytes_read, s32 in_data_count) {
328 const auto UpdateOffsets = [&](std::size_t read) {
329 input_offset += read;
330 bytes_read += read;
331 };
332
333 for (s32 i = 0; i < in_data_count; i++) {
334 if (!AudioCommon::CanConsumeBuffer(input.size(), input_offset,
335 sizeof(SplitterInfo::InDestinationParams))) {
336 LOG_ERROR(Audio, "Buffer is an invalid size!");
337 return false;
338 }
339 SplitterInfo::InDestinationParams header{};
340 std::memcpy(&header, input.data() + input_offset,
341 sizeof(SplitterInfo::InDestinationParams));
342 UpdateOffsets(sizeof(SplitterInfo::InDestinationParams));
343
344 // Logged as warning as these don't actually cause a bailout for some reason
345 if (header.magic != SplitterMagic::DataHeader) {
346 LOG_ERROR(Audio, "Bad splitter data header");
347 break;
348 }
349
350 if (header.splitter_id < 0 || static_cast<std::size_t>(header.splitter_id) > data_count) {
351 LOG_ERROR(Audio, "Bad splitter data id");
352 break;
353 }
354 GetData(header.splitter_id).Update(header);
355 }
356 return true;
357}
358
359bool SplitterContext::RecomposeDestination(ServerSplitterInfo& info,
360 SplitterInfo::InInfoPrams& header,
361 const std::vector<u8>& input,
362 const std::size_t& input_offset) {
363 // Clear our current destinations
364 auto* current_head = info.GetHead();
365 while (current_head != nullptr) {
366 auto* next_head = current_head->GetNextDestination();
367 current_head->SetNextDestination(nullptr);
368 current_head = next_head;
369 }
370 info.SetHead(nullptr);
371
372 s32 size = header.length;
373 // If the splitter bug is present, calculate fixed size
374 if (!bug_fixed) {
375 if (info_count > 0) {
376 const auto factor = data_count / info_count;
377 size = std::min(header.length, static_cast<s32>(factor));
378 } else {
379 size = 0;
380 }
381 }
382
383 if (size < 1) {
384 LOG_ERROR(Audio, "Invalid splitter info size! size={:X}", size);
385 return true;
386 }
387
388 auto* start_head = &GetData(header.resource_id_base);
389 current_head = start_head;
390 std::vector<s32_le> resource_ids(size - 1);
391 if (!AudioCommon::CanConsumeBuffer(input.size(), input_offset,
392 resource_ids.size() * sizeof(s32_le))) {
393 LOG_ERROR(Audio, "Buffer is an invalid size!");
394 return false;
395 }
396 std::memcpy(resource_ids.data(), input.data() + input_offset,
397 resource_ids.size() * sizeof(s32_le));
398
399 for (auto resource_id : resource_ids) {
400 auto* head = &GetData(resource_id);
401 current_head->SetNextDestination(head);
402 current_head = head;
403 }
404
405 info.SetHead(start_head);
406 info.SetHeadDepth(size);
407
408 return true;
409}
410
411NodeStates::NodeStates() = default;
412NodeStates::~NodeStates() = default;
413
414void NodeStates::Initialize(std::size_t node_count_) {
415 // Setup our work parameters
416 node_count = node_count_;
417 was_node_found.resize(node_count);
418 was_node_completed.resize(node_count);
419 index_list.resize(node_count);
420 index_stack.Reset(node_count * node_count);
421}
422
423bool NodeStates::Tsort(EdgeMatrix& edge_matrix) {
424 return DepthFirstSearch(edge_matrix);
425}
426
427std::size_t NodeStates::GetIndexPos() const {
428 return index_pos;
429}
430
431const std::vector<s32>& NodeStates::GetIndexList() const {
432 return index_list;
433}
434
435void NodeStates::PushTsortResult(s32 index) {
436 ASSERT(index < static_cast<s32>(node_count));
437 index_list[index_pos++] = index;
438}
439
440bool NodeStates::DepthFirstSearch(EdgeMatrix& edge_matrix) {
441 ResetState();
442 for (std::size_t i = 0; i < node_count; i++) {
443 const auto node_id = static_cast<s32>(i);
444
445 // If we don't have a state, send to our index stack for work
446 if (GetState(i) == NodeStates::State::NoState) {
447 index_stack.push(node_id);
448 }
449
450 // While we have work to do in our stack
451 while (index_stack.Count() > 0) {
452 // Get the current node
453 const auto current_stack_index = index_stack.top();
454 // Check if we've seen the node yet
455 const auto index_state = GetState(current_stack_index);
456 if (index_state == NodeStates::State::NoState) {
457 // Mark the node as seen
458 UpdateState(NodeStates::State::InFound, current_stack_index);
459 } else if (index_state == NodeStates::State::InFound) {
460 // We've seen this node before, mark it as completed
461 UpdateState(NodeStates::State::InCompleted, current_stack_index);
462 // Update our index list
463 PushTsortResult(current_stack_index);
464 // Pop the stack
465 index_stack.pop();
466 continue;
467 } else if (index_state == NodeStates::State::InCompleted) {
468 // If our node is already sorted, clear it
469 index_stack.pop();
470 continue;
471 }
472
473 const auto edge_node_count = edge_matrix.GetNodeCount();
474 for (s32 j = 0; j < static_cast<s32>(edge_node_count); j++) {
475 // Check if our node is connected to our edge matrix
476 if (!edge_matrix.Connected(current_stack_index, j)) {
477 continue;
478 }
479
480 // Check if our node exists
481 const auto node_state = GetState(j);
482 if (node_state == NodeStates::State::NoState) {
483 // Add more work
484 index_stack.push(j);
485 } else if (node_state == NodeStates::State::InFound) {
486 ASSERT_MSG(false, "Node start marked as found");
487 ResetState();
488 return false;
489 }
490 }
491 }
492 }
493 return true;
494}
495
496void NodeStates::ResetState() {
497 // Reset to the start of our index stack
498 index_pos = 0;
499 for (std::size_t i = 0; i < node_count; i++) {
500 // Mark all nodes as not found
501 was_node_found[i] = false;
502 // Mark all nodes as uncompleted
503 was_node_completed[i] = false;
504 // Mark all indexes as invalid
505 index_list[i] = -1;
506 }
507}
508
509void NodeStates::UpdateState(NodeStates::State state, std::size_t i) {
510 switch (state) {
511 case NodeStates::State::NoState:
512 was_node_found[i] = false;
513 was_node_completed[i] = false;
514 break;
515 case NodeStates::State::InFound:
516 was_node_found[i] = true;
517 was_node_completed[i] = false;
518 break;
519 case NodeStates::State::InCompleted:
520 was_node_found[i] = false;
521 was_node_completed[i] = true;
522 break;
523 }
524}
525
526NodeStates::State NodeStates::GetState(std::size_t i) {
527 ASSERT(i < node_count);
528 if (was_node_found[i]) {
529 // If our node exists in our found list
530 return NodeStates::State::InFound;
531 } else if (was_node_completed[i]) {
532 // If node is in the completed list
533 return NodeStates::State::InCompleted;
534 } else {
535 // If in neither
536 return NodeStates::State::NoState;
537 }
538}
539
540NodeStates::Stack::Stack() = default;
541NodeStates::Stack::~Stack() = default;
542
543void NodeStates::Stack::Reset(std::size_t size) {
544 // Mark our stack as empty
545 stack.resize(size);
546 stack_size = size;
547 stack_pos = 0;
548 std::fill(stack.begin(), stack.end(), 0);
549}
550
551void NodeStates::Stack::push(s32 val) {
552 ASSERT(stack_pos < stack_size);
553 stack[stack_pos++] = val;
554}
555
556std::size_t NodeStates::Stack::Count() const {
557 return stack_pos;
558}
559
560s32 NodeStates::Stack::top() const {
561 ASSERT(stack_pos > 0);
562 return stack[stack_pos - 1];
563}
564
565s32 NodeStates::Stack::pop() {
566 ASSERT(stack_pos > 0);
567 stack_pos--;
568 return stack[stack_pos];
569}
570
571EdgeMatrix::EdgeMatrix() = default;
572EdgeMatrix::~EdgeMatrix() = default;
573
574void EdgeMatrix::Initialize(std::size_t _node_count) {
575 node_count = _node_count;
576 edge_matrix.resize(node_count * node_count);
577}
578
579bool EdgeMatrix::Connected(s32 a, s32 b) {
580 return GetState(a, b);
581}
582
583void EdgeMatrix::Connect(s32 a, s32 b) {
584 SetState(a, b, true);
585}
586
587void EdgeMatrix::Disconnect(s32 a, s32 b) {
588 SetState(a, b, false);
589}
590
591void EdgeMatrix::RemoveEdges(s32 edge) {
592 for (std::size_t i = 0; i < node_count; i++) {
593 SetState(edge, static_cast<s32>(i), false);
594 }
595}
596
597std::size_t EdgeMatrix::GetNodeCount() const {
598 return node_count;
599}
600
601void EdgeMatrix::SetState(s32 a, s32 b, bool state) {
602 ASSERT(InRange(a, b));
603 edge_matrix.at(a * node_count + b) = state;
604}
605
606bool EdgeMatrix::GetState(s32 a, s32 b) {
607 ASSERT(InRange(a, b));
608 return edge_matrix.at(a * node_count + b);
609}
610
611bool EdgeMatrix::InRange(s32 a, s32 b) const {
612 const std::size_t pos = a * node_count + b;
613 return pos < (node_count * node_count);
614}
615
616} // namespace AudioCore
diff --git a/src/audio_core/splitter_context.h b/src/audio_core/splitter_context.h
deleted file mode 100644
index 3a4b055eb..000000000
--- a/src/audio_core/splitter_context.h
+++ /dev/null
@@ -1,218 +0,0 @@
1// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <stack>
7#include <vector>
8#include "audio_core/common.h"
9#include "common/common_funcs.h"
10#include "common/common_types.h"
11#include "common/swap.h"
12
13namespace AudioCore {
14class BehaviorInfo;
15
16class EdgeMatrix {
17public:
18 EdgeMatrix();
19 ~EdgeMatrix();
20
21 void Initialize(std::size_t _node_count);
22 bool Connected(s32 a, s32 b);
23 void Connect(s32 a, s32 b);
24 void Disconnect(s32 a, s32 b);
25 void RemoveEdges(s32 edge);
26 std::size_t GetNodeCount() const;
27
28private:
29 void SetState(s32 a, s32 b, bool state);
30 bool GetState(s32 a, s32 b);
31
32 bool InRange(s32 a, s32 b) const;
33 std::vector<bool> edge_matrix{};
34 std::size_t node_count{};
35};
36
37class NodeStates {
38public:
39 enum class State {
40 NoState = 0,
41 InFound = 1,
42 InCompleted = 2,
43 };
44
45 // Looks to be a fixed size stack. Placed within the NodeStates class based on symbols
46 class Stack {
47 public:
48 Stack();
49 ~Stack();
50
51 void Reset(std::size_t size);
52 void push(s32 val);
53 std::size_t Count() const;
54 s32 top() const;
55 s32 pop();
56
57 private:
58 std::vector<s32> stack{};
59 std::size_t stack_size{};
60 std::size_t stack_pos{};
61 };
62 NodeStates();
63 ~NodeStates();
64
65 void Initialize(std::size_t node_count_);
66 bool Tsort(EdgeMatrix& edge_matrix);
67 std::size_t GetIndexPos() const;
68 const std::vector<s32>& GetIndexList() const;
69
70private:
71 void PushTsortResult(s32 index);
72 bool DepthFirstSearch(EdgeMatrix& edge_matrix);
73 void ResetState();
74 void UpdateState(State state, std::size_t i);
75 State GetState(std::size_t i);
76
77 std::size_t node_count{};
78 std::vector<bool> was_node_found{};
79 std::vector<bool> was_node_completed{};
80 std::size_t index_pos{};
81 std::vector<s32> index_list{};
82 Stack index_stack{};
83};
84
85enum class SplitterMagic : u32_le {
86 SplitterHeader = Common::MakeMagic('S', 'N', 'D', 'H'),
87 DataHeader = Common::MakeMagic('S', 'N', 'D', 'D'),
88 InfoHeader = Common::MakeMagic('S', 'N', 'D', 'I'),
89};
90
91class SplitterInfo {
92public:
93 struct InHeader {
94 SplitterMagic magic{};
95 s32_le info_count{};
96 s32_le data_count{};
97 INSERT_PADDING_WORDS(5);
98 };
99 static_assert(sizeof(InHeader) == 0x20, "SplitterInfo::InHeader is an invalid size");
100
101 struct InInfoPrams {
102 SplitterMagic magic{};
103 s32_le send_id{};
104 s32_le sample_rate{};
105 s32_le length{};
106 s32_le resource_id_base{};
107 };
108 static_assert(sizeof(InInfoPrams) == 0x14, "SplitterInfo::InInfoPrams is an invalid size");
109
110 struct InDestinationParams {
111 SplitterMagic magic{};
112 s32_le splitter_id{};
113 std::array<float_le, AudioCommon::MAX_MIX_BUFFERS> mix_volumes{};
114 s32_le mix_id{};
115 bool in_use{};
116 INSERT_PADDING_BYTES(3);
117 };
118 static_assert(sizeof(InDestinationParams) == 0x70,
119 "SplitterInfo::InDestinationParams is an invalid size");
120};
121
122class ServerSplitterDestinationData {
123public:
124 explicit ServerSplitterDestinationData(s32 id_);
125 ~ServerSplitterDestinationData();
126
127 void Update(SplitterInfo::InDestinationParams& header);
128
129 ServerSplitterDestinationData* GetNextDestination();
130 const ServerSplitterDestinationData* GetNextDestination() const;
131 void SetNextDestination(ServerSplitterDestinationData* dest);
132 bool ValidMixId() const;
133 s32 GetMixId() const;
134 bool IsConfigured() const;
135 float GetMixVolume(std::size_t i) const;
136 const std::array<float, AudioCommon::MAX_MIX_BUFFERS>& CurrentMixVolumes() const;
137 const std::array<float, AudioCommon::MAX_MIX_BUFFERS>& LastMixVolumes() const;
138 void MarkDirty();
139 void UpdateInternalState();
140
141private:
142 bool needs_update{};
143 bool in_use{};
144 s32 id{};
145 s32 mix_id{};
146 std::array<float, AudioCommon::MAX_MIX_BUFFERS> current_mix_volumes{};
147 std::array<float, AudioCommon::MAX_MIX_BUFFERS> last_mix_volumes{};
148 ServerSplitterDestinationData* next = nullptr;
149};
150
151class ServerSplitterInfo {
152public:
153 explicit ServerSplitterInfo(s32 id_);
154 ~ServerSplitterInfo();
155
156 void InitializeInfos();
157 void ClearNewConnectionFlag();
158 std::size_t Update(SplitterInfo::InInfoPrams& header);
159
160 ServerSplitterDestinationData* GetHead();
161 const ServerSplitterDestinationData* GetHead() const;
162 ServerSplitterDestinationData* GetData(std::size_t depth);
163 const ServerSplitterDestinationData* GetData(std::size_t depth) const;
164
165 bool HasNewConnection() const;
166 s32 GetLength() const;
167
168 void SetHead(ServerSplitterDestinationData* new_head);
169 void SetHeadDepth(s32 length);
170
171private:
172 s32 sample_rate{};
173 s32 id{};
174 s32 send_length{};
175 ServerSplitterDestinationData* head = nullptr;
176 bool new_connection{};
177};
178
179class SplitterContext {
180public:
181 SplitterContext();
182 ~SplitterContext();
183
184 void Initialize(BehaviorInfo& behavior_info, std::size_t splitter_count,
185 std::size_t data_count);
186
187 bool Update(const std::vector<u8>& input, std::size_t& input_offset, std::size_t& bytes_read);
188 bool UsingSplitter() const;
189
190 ServerSplitterInfo& GetInfo(std::size_t i);
191 const ServerSplitterInfo& GetInfo(std::size_t i) const;
192 ServerSplitterDestinationData& GetData(std::size_t i);
193 const ServerSplitterDestinationData& GetData(std::size_t i) const;
194 ServerSplitterDestinationData* GetDestinationData(std::size_t info, std::size_t data);
195 const ServerSplitterDestinationData* GetDestinationData(std::size_t info,
196 std::size_t data) const;
197 void UpdateInternalState();
198
199 std::size_t GetInfoCount() const;
200 std::size_t GetDataCount() const;
201
202private:
203 void Setup(std::size_t info_count, std::size_t data_count, bool is_splitter_bug_fixed);
204 bool UpdateInfo(const std::vector<u8>& input, std::size_t& input_offset,
205 std::size_t& bytes_read, s32 in_splitter_count);
206 bool UpdateData(const std::vector<u8>& input, std::size_t& input_offset,
207 std::size_t& bytes_read, s32 in_data_count);
208 bool RecomposeDestination(ServerSplitterInfo& info, SplitterInfo::InInfoPrams& header,
209 const std::vector<u8>& input, const std::size_t& input_offset);
210
211 std::vector<ServerSplitterInfo> infos{};
212 std::vector<ServerSplitterDestinationData> datas{};
213
214 std::size_t info_count{};
215 std::size_t data_count{};
216 bool bug_fixed{};
217};
218} // namespace AudioCore
diff --git a/src/audio_core/stream.cpp b/src/audio_core/stream.cpp
deleted file mode 100644
index cf3d94c53..000000000
--- a/src/audio_core/stream.cpp
+++ /dev/null
@@ -1,175 +0,0 @@
1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <algorithm>
5#include <cmath>
6
7#include "audio_core/sink.h"
8#include "audio_core/sink_details.h"
9#include "audio_core/sink_stream.h"
10#include "audio_core/stream.h"
11#include "common/assert.h"
12#include "common/logging/log.h"
13#include "common/settings.h"
14#include "core/core_timing.h"
15
16namespace AudioCore {
17
18constexpr std::size_t MaxAudioBufferCount{32};
19
20u32 Stream::GetNumChannels() const {
21 switch (format) {
22 case Format::Mono16:
23 return 1;
24 case Format::Stereo16:
25 return 2;
26 case Format::Multi51Channel16:
27 return 6;
28 }
29 UNIMPLEMENTED_MSG("Unimplemented format={}", static_cast<u32>(format));
30 return {};
31}
32
33Stream::Stream(Core::Timing::CoreTiming& core_timing_, u32 sample_rate_, Format format_,
34 ReleaseCallback&& release_callback_, SinkStream& sink_stream_, std::string&& name_)
35 : sample_rate{sample_rate_}, format{format_}, release_callback{std::move(release_callback_)},
36 sink_stream{sink_stream_}, core_timing{core_timing_}, name{std::move(name_)} {
37 release_event = Core::Timing::CreateEvent(
38 name, [this](std::uintptr_t, s64 time, std::chrono::nanoseconds ns_late) {
39 ReleaseActiveBuffer(ns_late);
40 return std::nullopt;
41 });
42}
43
44void Stream::Play() {
45 state = State::Playing;
46 PlayNextBuffer();
47}
48
49void Stream::Stop() {
50 state = State::Stopped;
51 UNIMPLEMENTED();
52}
53
54bool Stream::Flush() {
55 const bool had_buffers = !queued_buffers.empty();
56 while (!queued_buffers.empty()) {
57 queued_buffers.pop();
58 }
59 return had_buffers;
60}
61
62void Stream::SetVolume(float volume) {
63 game_volume = volume;
64}
65
66Stream::State Stream::GetState() const {
67 return state;
68}
69
70std::chrono::nanoseconds Stream::GetBufferReleaseNS(const Buffer& buffer) const {
71 const std::size_t num_samples{buffer.GetSamples().size() / GetNumChannels()};
72 return std::chrono::nanoseconds((static_cast<u64>(num_samples) * 1000000000ULL) / sample_rate);
73}
74
75static void VolumeAdjustSamples(std::vector<s16>& samples, float game_volume) {
76 const float volume{std::clamp(Settings::Volume() - (1.0f - game_volume), 0.0f, 1.0f)};
77
78 if (volume == 1.0f) {
79 return;
80 }
81
82 // Perceived volume is not the same as the volume level
83 const float volume_scale_factor = (0.85f * ((volume * volume) - volume)) + volume;
84 for (auto& sample : samples) {
85 sample = static_cast<s16>(sample * volume_scale_factor);
86 }
87}
88
89void Stream::PlayNextBuffer(std::chrono::nanoseconds ns_late) {
90 if (!IsPlaying()) {
91 // Ensure we are in playing state before playing the next buffer
92 sink_stream.Flush();
93 return;
94 }
95
96 if (active_buffer) {
97 // Do not queue a new buffer if we are already playing a buffer
98 return;
99 }
100
101 if (queued_buffers.empty()) {
102 // No queued buffers - we are effectively paused
103 sink_stream.Flush();
104 return;
105 }
106
107 active_buffer = queued_buffers.front();
108 queued_buffers.pop();
109
110 auto& samples = active_buffer->GetSamples();
111
112 VolumeAdjustSamples(samples, game_volume);
113
114 sink_stream.EnqueueSamples(GetNumChannels(), samples);
115 played_samples += samples.size();
116
117 const auto buffer_release_ns = GetBufferReleaseNS(*active_buffer);
118
119 // If ns_late is higher than the update rate ignore the delay
120 if (ns_late > buffer_release_ns) {
121 ns_late = {};
122 }
123
124 core_timing.ScheduleEvent(buffer_release_ns - ns_late, release_event, {});
125}
126
127void Stream::ReleaseActiveBuffer(std::chrono::nanoseconds ns_late) {
128 ASSERT(active_buffer);
129 released_buffers.push(std::move(active_buffer));
130 release_callback();
131 PlayNextBuffer(ns_late);
132}
133
134bool Stream::QueueBuffer(BufferPtr&& buffer) {
135 if (queued_buffers.size() < MaxAudioBufferCount) {
136 queued_buffers.push(std::move(buffer));
137 PlayNextBuffer();
138 return true;
139 }
140 return false;
141}
142
143bool Stream::ContainsBuffer([[maybe_unused]] Buffer::Tag tag) const {
144 UNIMPLEMENTED();
145 return {};
146}
147
148std::vector<Buffer::Tag> Stream::GetTagsAndReleaseBuffers(std::size_t max_count) {
149 std::vector<Buffer::Tag> tags;
150 for (std::size_t count = 0; count < max_count && !released_buffers.empty(); ++count) {
151 if (released_buffers.front()) {
152 tags.push_back(released_buffers.front()->GetTag());
153 } else {
154 ASSERT_MSG(false, "Invalid tag in released_buffers!");
155 }
156 released_buffers.pop();
157 }
158 return tags;
159}
160
161std::vector<Buffer::Tag> Stream::GetTagsAndReleaseBuffers() {
162 std::vector<Buffer::Tag> tags;
163 tags.reserve(released_buffers.size());
164 while (!released_buffers.empty()) {
165 if (released_buffers.front()) {
166 tags.push_back(released_buffers.front()->GetTag());
167 } else {
168 ASSERT_MSG(false, "Invalid tag in released_buffers!");
169 }
170 released_buffers.pop();
171 }
172 return tags;
173}
174
175} // namespace AudioCore
diff --git a/src/audio_core/stream.h b/src/audio_core/stream.h
deleted file mode 100644
index f5de70396..000000000
--- a/src/audio_core/stream.h
+++ /dev/null
@@ -1,130 +0,0 @@
1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <chrono>
7#include <functional>
8#include <memory>
9#include <string>
10#include <vector>
11#include <queue>
12
13#include "audio_core/buffer.h"
14#include "common/common_types.h"
15
16namespace Core::Timing {
17class CoreTiming;
18struct EventType;
19} // namespace Core::Timing
20
21namespace AudioCore {
22
23class SinkStream;
24
25/**
26 * Represents an audio stream, which is a sequence of queued buffers, to be outputed by AudioOut
27 */
28class Stream {
29public:
30 /// Audio format of the stream
31 enum class Format {
32 Mono16,
33 Stereo16,
34 Multi51Channel16,
35 };
36
37 /// Current state of the stream
38 enum class State {
39 Stopped,
40 Playing,
41 };
42
43 /// Callback function type, used to change guest state on a buffer being released
44 using ReleaseCallback = std::function<void()>;
45
46 Stream(Core::Timing::CoreTiming& core_timing_, u32 sample_rate_, Format format_,
47 ReleaseCallback&& release_callback_, SinkStream& sink_stream_, std::string&& name_);
48
49 /// Plays the audio stream
50 void Play();
51
52 /// Stops the audio stream
53 void Stop();
54
55 /// Queues a buffer into the audio stream, returns true on success
56 bool QueueBuffer(BufferPtr&& buffer);
57
58 /// Flush audio buffers
59 bool Flush();
60
61 /// Returns true if the audio stream contains a buffer with the specified tag
62 [[nodiscard]] bool ContainsBuffer(Buffer::Tag tag) const;
63
64 /// Returns a vector of recently released buffers specified by tag
65 [[nodiscard]] std::vector<Buffer::Tag> GetTagsAndReleaseBuffers(std::size_t max_count);
66
67 /// Returns a vector of all recently released buffers specified by tag
68 [[nodiscard]] std::vector<Buffer::Tag> GetTagsAndReleaseBuffers();
69
70 void SetVolume(float volume);
71
72 [[nodiscard]] float GetVolume() const {
73 return game_volume;
74 }
75
76 /// Returns true if the stream is currently playing
77 [[nodiscard]] bool IsPlaying() const {
78 return state == State::Playing;
79 }
80
81 /// Returns the number of queued buffers
82 [[nodiscard]] std::size_t GetQueueSize() const {
83 return queued_buffers.size();
84 }
85
86 /// Gets the sample rate
87 [[nodiscard]] u32 GetSampleRate() const {
88 return sample_rate;
89 }
90
91 /// Gets the number of samples played so far
92 [[nodiscard]] u64 GetPlayedSampleCount() const {
93 return played_samples;
94 }
95
96 /// Gets the number of channels
97 [[nodiscard]] u32 GetNumChannels() const;
98
99 /// Get the state
100 [[nodiscard]] State GetState() const;
101
102private:
103 /// Plays the next queued buffer in the audio stream, starting playback if necessary
104 void PlayNextBuffer(std::chrono::nanoseconds ns_late = {});
105
106 /// Releases the actively playing buffer, signalling that it has been completed
107 void ReleaseActiveBuffer(std::chrono::nanoseconds ns_late = {});
108
109 /// Gets the number of core cycles when the specified buffer will be released
110 [[nodiscard]] std::chrono::nanoseconds GetBufferReleaseNS(const Buffer& buffer) const;
111
112 u32 sample_rate; ///< Sample rate of the stream
113 u64 played_samples{}; ///< The current played sample count
114 Format format; ///< Format of the stream
115 float game_volume = 1.0f; ///< The volume the game currently has set
116 ReleaseCallback release_callback; ///< Buffer release callback for the stream
117 State state{State::Stopped}; ///< Playback state of the stream
118 std::shared_ptr<Core::Timing::EventType>
119 release_event; ///< Core timing release event for the stream
120 BufferPtr active_buffer; ///< Actively playing buffer in the stream
121 std::queue<BufferPtr> queued_buffers; ///< Buffers queued to be played in the stream
122 std::queue<BufferPtr> released_buffers; ///< Buffers recently released from the stream
123 SinkStream& sink_stream; ///< Output sink for the stream
124 Core::Timing::CoreTiming& core_timing; ///< Core timing instance.
125 std::string name; ///< Name of the stream, must be unique
126};
127
128using StreamPtr = std::shared_ptr<Stream>;
129
130} // namespace AudioCore
diff --git a/src/audio_core/voice_context.cpp b/src/audio_core/voice_context.cpp
deleted file mode 100644
index f58a5c754..000000000
--- a/src/audio_core/voice_context.cpp
+++ /dev/null
@@ -1,579 +0,0 @@
1// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <algorithm>
5
6#include "audio_core/behavior_info.h"
7#include "audio_core/voice_context.h"
8#include "core/memory.h"
9
10namespace AudioCore {
11
12ServerVoiceChannelResource::ServerVoiceChannelResource(s32 id_) : id(id_) {}
13ServerVoiceChannelResource::~ServerVoiceChannelResource() = default;
14
15bool ServerVoiceChannelResource::InUse() const {
16 return in_use;
17}
18
19float ServerVoiceChannelResource::GetCurrentMixVolumeAt(std::size_t i) const {
20 ASSERT(i < AudioCommon::MAX_MIX_BUFFERS);
21 return mix_volume.at(i);
22}
23
24float ServerVoiceChannelResource::GetLastMixVolumeAt(std::size_t i) const {
25 ASSERT(i < AudioCommon::MAX_MIX_BUFFERS);
26 return last_mix_volume.at(i);
27}
28
29void ServerVoiceChannelResource::Update(VoiceChannelResource::InParams& in_params) {
30 in_use = in_params.in_use;
31 // Update our mix volumes only if it's in use
32 if (in_params.in_use) {
33 mix_volume = in_params.mix_volume;
34 }
35}
36
37void ServerVoiceChannelResource::UpdateLastMixVolumes() {
38 last_mix_volume = mix_volume;
39}
40
41const std::array<float, AudioCommon::MAX_MIX_BUFFERS>&
42ServerVoiceChannelResource::GetCurrentMixVolume() const {
43 return mix_volume;
44}
45
46const std::array<float, AudioCommon::MAX_MIX_BUFFERS>&
47ServerVoiceChannelResource::GetLastMixVolume() const {
48 return last_mix_volume;
49}
50
51ServerVoiceInfo::ServerVoiceInfo() {
52 Initialize();
53}
54ServerVoiceInfo::~ServerVoiceInfo() = default;
55
56void ServerVoiceInfo::Initialize() {
57 in_params.in_use = false;
58 in_params.node_id = 0;
59 in_params.id = 0;
60 in_params.current_playstate = ServerPlayState::Stop;
61 in_params.priority = 255;
62 in_params.sample_rate = 0;
63 in_params.sample_format = SampleFormat::Invalid;
64 in_params.channel_count = 0;
65 in_params.pitch = 0.0f;
66 in_params.volume = 0.0f;
67 in_params.last_volume = 0.0f;
68 in_params.biquad_filter.fill({});
69 in_params.wave_buffer_count = 0;
70 in_params.wave_buffer_head = 0;
71 in_params.mix_id = AudioCommon::NO_MIX;
72 in_params.splitter_info_id = AudioCommon::NO_SPLITTER;
73 in_params.additional_params_address = 0;
74 in_params.additional_params_size = 0;
75 in_params.is_new = false;
76 out_params.played_sample_count = 0;
77 out_params.wave_buffer_consumed = 0;
78 in_params.voice_drop_flag = false;
79 in_params.buffer_mapped = true;
80 in_params.wave_buffer_flush_request_count = 0;
81 in_params.was_biquad_filter_enabled.fill(false);
82
83 for (auto& wave_buffer : in_params.wave_buffer) {
84 wave_buffer.start_sample_offset = 0;
85 wave_buffer.end_sample_offset = 0;
86 wave_buffer.is_looping = false;
87 wave_buffer.end_of_stream = false;
88 wave_buffer.buffer_address = 0;
89 wave_buffer.buffer_size = 0;
90 wave_buffer.context_address = 0;
91 wave_buffer.context_size = 0;
92 wave_buffer.sent_to_dsp = true;
93 }
94
95 stored_samples.clear();
96}
97
98void ServerVoiceInfo::UpdateParameters(const VoiceInfo::InParams& voice_in,
99 BehaviorInfo& behavior_info) {
100 in_params.in_use = voice_in.is_in_use;
101 in_params.id = voice_in.id;
102 in_params.node_id = voice_in.node_id;
103 in_params.last_playstate = in_params.current_playstate;
104 switch (voice_in.play_state) {
105 case PlayState::Paused:
106 in_params.current_playstate = ServerPlayState::Paused;
107 break;
108 case PlayState::Stopped:
109 if (in_params.current_playstate != ServerPlayState::Stop) {
110 in_params.current_playstate = ServerPlayState::RequestStop;
111 }
112 break;
113 case PlayState::Started:
114 in_params.current_playstate = ServerPlayState::Play;
115 break;
116 default:
117 ASSERT_MSG(false, "Unknown playstate {}", voice_in.play_state);
118 break;
119 }
120
121 in_params.priority = voice_in.priority;
122 in_params.sorting_order = voice_in.sorting_order;
123 in_params.sample_rate = voice_in.sample_rate;
124 in_params.sample_format = voice_in.sample_format;
125 in_params.channel_count = voice_in.channel_count;
126 in_params.pitch = voice_in.pitch;
127 in_params.volume = voice_in.volume;
128 in_params.biquad_filter = voice_in.biquad_filter;
129 in_params.wave_buffer_count = voice_in.wave_buffer_count;
130 in_params.wave_buffer_head = voice_in.wave_buffer_head;
131 if (behavior_info.IsFlushVoiceWaveBuffersSupported()) {
132 const auto in_request_count = in_params.wave_buffer_flush_request_count;
133 const auto voice_request_count = voice_in.wave_buffer_flush_request_count;
134 in_params.wave_buffer_flush_request_count =
135 static_cast<u8>(in_request_count + voice_request_count);
136 }
137 in_params.mix_id = voice_in.mix_id;
138 if (behavior_info.IsSplitterSupported()) {
139 in_params.splitter_info_id = voice_in.splitter_info_id;
140 } else {
141 in_params.splitter_info_id = AudioCommon::NO_SPLITTER;
142 }
143
144 std::memcpy(in_params.voice_channel_resource_id.data(),
145 voice_in.voice_channel_resource_ids.data(),
146 sizeof(s32) * in_params.voice_channel_resource_id.size());
147
148 if (behavior_info.IsVoicePlayedSampleCountResetAtLoopPointSupported()) {
149 in_params.behavior_flags.is_played_samples_reset_at_loop_point =
150 voice_in.behavior_flags.is_played_samples_reset_at_loop_point;
151 } else {
152 in_params.behavior_flags.is_played_samples_reset_at_loop_point.Assign(0);
153 }
154 if (behavior_info.IsVoicePitchAndSrcSkippedSupported()) {
155 in_params.behavior_flags.is_pitch_and_src_skipped =
156 voice_in.behavior_flags.is_pitch_and_src_skipped;
157 } else {
158 in_params.behavior_flags.is_pitch_and_src_skipped.Assign(0);
159 }
160
161 if (voice_in.is_voice_drop_flag_clear_requested) {
162 in_params.voice_drop_flag = false;
163 }
164
165 if (in_params.additional_params_address != voice_in.additional_params_address ||
166 in_params.additional_params_size != voice_in.additional_params_size) {
167 in_params.additional_params_address = voice_in.additional_params_address;
168 in_params.additional_params_size = voice_in.additional_params_size;
169 // TODO(ogniK): Reattach buffer, do we actually need to? Maybe just signal to the DSP that
170 // our context is new
171 }
172}
173
174void ServerVoiceInfo::UpdateWaveBuffers(
175 const VoiceInfo::InParams& voice_in,
176 std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& voice_states,
177 BehaviorInfo& behavior_info) {
178 if (voice_in.is_new) {
179 // Initialize our wave buffers
180 for (auto& wave_buffer : in_params.wave_buffer) {
181 wave_buffer.start_sample_offset = 0;
182 wave_buffer.end_sample_offset = 0;
183 wave_buffer.is_looping = false;
184 wave_buffer.end_of_stream = false;
185 wave_buffer.buffer_address = 0;
186 wave_buffer.buffer_size = 0;
187 wave_buffer.context_address = 0;
188 wave_buffer.context_size = 0;
189 wave_buffer.loop_start_sample = 0;
190 wave_buffer.loop_end_sample = 0;
191 wave_buffer.sent_to_dsp = true;
192 }
193
194 // Mark all our wave buffers as invalid
195 for (std::size_t channel = 0; channel < static_cast<std::size_t>(in_params.channel_count);
196 channel++) {
197 for (std::size_t i = 0; i < AudioCommon::MAX_WAVE_BUFFERS; ++i) {
198 voice_states[channel]->is_wave_buffer_valid[i] = false;
199 }
200 }
201 }
202
203 // Update our wave buffers
204 for (std::size_t i = 0; i < AudioCommon::MAX_WAVE_BUFFERS; i++) {
205 // Assume that we have at least 1 channel voice state
206 const auto have_valid_wave_buffer = voice_states[0]->is_wave_buffer_valid[i];
207
208 UpdateWaveBuffer(in_params.wave_buffer[i], voice_in.wave_buffer[i], in_params.sample_format,
209 have_valid_wave_buffer, behavior_info);
210 }
211}
212
213void ServerVoiceInfo::UpdateWaveBuffer(ServerWaveBuffer& out_wavebuffer,
214 const WaveBuffer& in_wave_buffer, SampleFormat sample_format,
215 bool is_buffer_valid,
216 [[maybe_unused]] BehaviorInfo& behavior_info) {
217 if (!is_buffer_valid && out_wavebuffer.sent_to_dsp && out_wavebuffer.buffer_address != 0) {
218 out_wavebuffer.buffer_address = 0;
219 out_wavebuffer.buffer_size = 0;
220 }
221
222 if (!in_wave_buffer.sent_to_server || !in_params.buffer_mapped) {
223 // Validate sample offset sizings
224 if (sample_format == SampleFormat::Pcm16) {
225 const s64 buffer_size = static_cast<s64>(in_wave_buffer.buffer_size);
226 const s64 start = sizeof(s16) * in_wave_buffer.start_sample_offset;
227 const s64 end = sizeof(s16) * in_wave_buffer.end_sample_offset;
228 if (0 > start || start > buffer_size || 0 > end || end > buffer_size) {
229 // TODO(ogniK): Write error info
230 LOG_ERROR(Audio,
231 "PCM16 wavebuffer has an invalid size. Buffer has size 0x{:08X}, but "
232 "offsets were "
233 "{:08X} - 0x{:08X}",
234 buffer_size, sizeof(s16) * in_wave_buffer.start_sample_offset,
235 sizeof(s16) * in_wave_buffer.end_sample_offset);
236 return;
237 }
238 } else if (sample_format == SampleFormat::Adpcm) {
239 const s64 buffer_size = static_cast<s64>(in_wave_buffer.buffer_size);
240 const s64 start_frames = in_wave_buffer.start_sample_offset / 14;
241 const s64 start_extra = in_wave_buffer.start_sample_offset % 14 == 0
242 ? 0
243 : (in_wave_buffer.start_sample_offset % 14) / 2 + 1 +
244 (in_wave_buffer.start_sample_offset % 2);
245 const s64 start = start_frames * 8 + start_extra;
246 const s64 end_frames = in_wave_buffer.end_sample_offset / 14;
247 const s64 end_extra = in_wave_buffer.end_sample_offset % 14 == 0
248 ? 0
249 : (in_wave_buffer.end_sample_offset % 14) / 2 + 1 +
250 (in_wave_buffer.end_sample_offset % 2);
251 const s64 end = end_frames * 8 + end_extra;
252 if (in_wave_buffer.start_sample_offset < 0 || start > buffer_size ||
253 in_wave_buffer.end_sample_offset < 0 || end > buffer_size) {
254 LOG_ERROR(Audio,
255 "ADPMC wavebuffer has an invalid size. Buffer has size 0x{:08X}, but "
256 "offsets were "
257 "{:08X} - 0x{:08X}",
258 in_wave_buffer.buffer_size, start, end);
259 return;
260 }
261 }
262 // TODO(ogniK): ADPCM Size error
263
264 out_wavebuffer.sent_to_dsp = false;
265 out_wavebuffer.start_sample_offset = in_wave_buffer.start_sample_offset;
266 out_wavebuffer.end_sample_offset = in_wave_buffer.end_sample_offset;
267 out_wavebuffer.is_looping = in_wave_buffer.is_looping;
268 out_wavebuffer.end_of_stream = in_wave_buffer.end_of_stream;
269
270 out_wavebuffer.buffer_address = in_wave_buffer.buffer_address;
271 out_wavebuffer.buffer_size = in_wave_buffer.buffer_size;
272 out_wavebuffer.context_address = in_wave_buffer.context_address;
273 out_wavebuffer.context_size = in_wave_buffer.context_size;
274 out_wavebuffer.loop_start_sample = in_wave_buffer.loop_start_sample;
275 out_wavebuffer.loop_end_sample = in_wave_buffer.loop_end_sample;
276 in_params.buffer_mapped =
277 in_wave_buffer.buffer_address != 0 && in_wave_buffer.buffer_size != 0;
278 // TODO(ogniK): Pool mapper attachment
279 // TODO(ogniK): IsAdpcmLoopContextBugFixed
280 if (sample_format == SampleFormat::Adpcm && in_wave_buffer.context_address != 0 &&
281 in_wave_buffer.context_size != 0 && behavior_info.IsAdpcmLoopContextBugFixed()) {
282 } else {
283 out_wavebuffer.context_address = 0;
284 out_wavebuffer.context_size = 0;
285 }
286 }
287}
288
289void ServerVoiceInfo::WriteOutStatus(
290 VoiceInfo::OutParams& voice_out, VoiceInfo::InParams& voice_in,
291 std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& voice_states) {
292 if (voice_in.is_new || in_params.is_new) {
293 in_params.is_new = true;
294 voice_out.wave_buffer_consumed = 0;
295 voice_out.played_sample_count = 0;
296 voice_out.voice_dropped = false;
297 } else {
298 const auto& state = voice_states[0];
299 voice_out.wave_buffer_consumed = state->wave_buffer_consumed;
300 voice_out.played_sample_count = state->played_sample_count;
301 voice_out.voice_dropped = state->voice_dropped;
302 }
303}
304
305const ServerVoiceInfo::InParams& ServerVoiceInfo::GetInParams() const {
306 return in_params;
307}
308
309ServerVoiceInfo::InParams& ServerVoiceInfo::GetInParams() {
310 return in_params;
311}
312
313const ServerVoiceInfo::OutParams& ServerVoiceInfo::GetOutParams() const {
314 return out_params;
315}
316
317ServerVoiceInfo::OutParams& ServerVoiceInfo::GetOutParams() {
318 return out_params;
319}
320
321bool ServerVoiceInfo::ShouldSkip() const {
322 // TODO(ogniK): Handle unmapped wave buffers or parameters
323 return !in_params.in_use || in_params.wave_buffer_count == 0 || !in_params.buffer_mapped ||
324 in_params.voice_drop_flag;
325}
326
327bool ServerVoiceInfo::UpdateForCommandGeneration(VoiceContext& voice_context) {
328 std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT> dsp_voice_states{};
329 if (in_params.is_new) {
330 ResetResources(voice_context);
331 in_params.last_volume = in_params.volume;
332 in_params.is_new = false;
333 }
334
335 const s32 channel_count = in_params.channel_count;
336 for (s32 i = 0; i < channel_count; i++) {
337 const auto channel_resource = in_params.voice_channel_resource_id[i];
338 dsp_voice_states[i] =
339 &voice_context.GetDspSharedState(static_cast<std::size_t>(channel_resource));
340 }
341 return UpdateParametersForCommandGeneration(dsp_voice_states);
342}
343
344void ServerVoiceInfo::ResetResources(VoiceContext& voice_context) {
345 const s32 channel_count = in_params.channel_count;
346 for (s32 i = 0; i < channel_count; i++) {
347 const auto channel_resource = in_params.voice_channel_resource_id[i];
348 auto& dsp_state =
349 voice_context.GetDspSharedState(static_cast<std::size_t>(channel_resource));
350 dsp_state = {};
351 voice_context.GetChannelResource(static_cast<std::size_t>(channel_resource))
352 .UpdateLastMixVolumes();
353 }
354}
355
356bool ServerVoiceInfo::UpdateParametersForCommandGeneration(
357 std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& dsp_voice_states) {
358 const s32 channel_count = in_params.channel_count;
359 if (in_params.wave_buffer_flush_request_count > 0) {
360 FlushWaveBuffers(in_params.wave_buffer_flush_request_count, dsp_voice_states,
361 channel_count);
362 in_params.wave_buffer_flush_request_count = 0;
363 }
364
365 switch (in_params.current_playstate) {
366 case ServerPlayState::Play: {
367 for (std::size_t i = 0; i < AudioCommon::MAX_WAVE_BUFFERS; i++) {
368 if (!in_params.wave_buffer[i].sent_to_dsp) {
369 for (s32 channel = 0; channel < channel_count; channel++) {
370 dsp_voice_states[channel]->is_wave_buffer_valid[i] = true;
371 }
372 in_params.wave_buffer[i].sent_to_dsp = true;
373 }
374 }
375 in_params.should_depop = false;
376 return HasValidWaveBuffer(dsp_voice_states[0]);
377 }
378 case ServerPlayState::Paused:
379 case ServerPlayState::Stop: {
380 in_params.should_depop = in_params.last_playstate == ServerPlayState::Play;
381 return in_params.should_depop;
382 }
383 case ServerPlayState::RequestStop: {
384 for (std::size_t i = 0; i < AudioCommon::MAX_WAVE_BUFFERS; i++) {
385 in_params.wave_buffer[i].sent_to_dsp = true;
386 for (s32 channel = 0; channel < channel_count; channel++) {
387 auto* dsp_state = dsp_voice_states[channel];
388
389 if (dsp_state->is_wave_buffer_valid[i]) {
390 dsp_state->wave_buffer_index =
391 (dsp_state->wave_buffer_index + 1) % AudioCommon::MAX_WAVE_BUFFERS;
392 dsp_state->wave_buffer_consumed++;
393 }
394
395 dsp_state->is_wave_buffer_valid[i] = false;
396 }
397 }
398
399 for (s32 channel = 0; channel < channel_count; channel++) {
400 auto* dsp_state = dsp_voice_states[channel];
401 dsp_state->offset = 0;
402 dsp_state->played_sample_count = 0;
403 dsp_state->fraction = 0;
404 dsp_state->sample_history.fill(0);
405 dsp_state->context = {};
406 }
407
408 in_params.current_playstate = ServerPlayState::Stop;
409 in_params.should_depop = in_params.last_playstate == ServerPlayState::Play;
410 return in_params.should_depop;
411 }
412 default:
413 ASSERT_MSG(false, "Invalid playstate {}", in_params.current_playstate);
414 }
415
416 return false;
417}
418
419void ServerVoiceInfo::FlushWaveBuffers(
420 u8 flush_count, std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& dsp_voice_states,
421 s32 channel_count) {
422 auto wave_head = in_params.wave_buffer_head;
423
424 for (u8 i = 0; i < flush_count; i++) {
425 in_params.wave_buffer[wave_head].sent_to_dsp = true;
426 for (s32 channel = 0; channel < channel_count; channel++) {
427 auto* dsp_state = dsp_voice_states[channel];
428 dsp_state->wave_buffer_consumed++;
429 dsp_state->is_wave_buffer_valid[wave_head] = false;
430 dsp_state->wave_buffer_index =
431 (dsp_state->wave_buffer_index + 1) % AudioCommon::MAX_WAVE_BUFFERS;
432 }
433 wave_head = (wave_head + 1) % AudioCommon::MAX_WAVE_BUFFERS;
434 }
435}
436
437bool ServerVoiceInfo::HasValidWaveBuffer(const VoiceState* state) const {
438 const auto& valid_wb = state->is_wave_buffer_valid;
439 return std::find(valid_wb.begin(), valid_wb.end(), true) != valid_wb.end();
440}
441
442void ServerVoiceInfo::SetWaveBufferCompleted(VoiceState& dsp_state,
443 const ServerWaveBuffer& wave_buffer) {
444 dsp_state.is_wave_buffer_valid[dsp_state.wave_buffer_index] = false;
445 dsp_state.wave_buffer_consumed++;
446 dsp_state.wave_buffer_index = (dsp_state.wave_buffer_index + 1) % AudioCommon::MAX_WAVE_BUFFERS;
447 dsp_state.loop_count = 0;
448 if (wave_buffer.end_of_stream) {
449 dsp_state.played_sample_count = 0;
450 }
451}
452
453VoiceContext::VoiceContext(std::size_t voice_count_) : voice_count{voice_count_} {
454 for (std::size_t i = 0; i < voice_count; i++) {
455 voice_channel_resources.emplace_back(static_cast<s32>(i));
456 sorted_voice_info.push_back(&voice_info.emplace_back());
457 voice_states.emplace_back();
458 dsp_voice_states.emplace_back();
459 }
460}
461
462VoiceContext::~VoiceContext() {
463 sorted_voice_info.clear();
464}
465
466std::size_t VoiceContext::GetVoiceCount() const {
467 return voice_count;
468}
469
470ServerVoiceChannelResource& VoiceContext::GetChannelResource(std::size_t i) {
471 ASSERT(i < voice_count);
472 return voice_channel_resources.at(i);
473}
474
475const ServerVoiceChannelResource& VoiceContext::GetChannelResource(std::size_t i) const {
476 ASSERT(i < voice_count);
477 return voice_channel_resources.at(i);
478}
479
480VoiceState& VoiceContext::GetState(std::size_t i) {
481 ASSERT(i < voice_count);
482 return voice_states.at(i);
483}
484
485const VoiceState& VoiceContext::GetState(std::size_t i) const {
486 ASSERT(i < voice_count);
487 return voice_states.at(i);
488}
489
490VoiceState& VoiceContext::GetDspSharedState(std::size_t i) {
491 ASSERT(i < voice_count);
492 return dsp_voice_states.at(i);
493}
494
495const VoiceState& VoiceContext::GetDspSharedState(std::size_t i) const {
496 ASSERT(i < voice_count);
497 return dsp_voice_states.at(i);
498}
499
500ServerVoiceInfo& VoiceContext::GetInfo(std::size_t i) {
501 ASSERT(i < voice_count);
502 return voice_info.at(i);
503}
504
505const ServerVoiceInfo& VoiceContext::GetInfo(std::size_t i) const {
506 ASSERT(i < voice_count);
507 return voice_info.at(i);
508}
509
510ServerVoiceInfo& VoiceContext::GetSortedInfo(std::size_t i) {
511 ASSERT(i < voice_count);
512 return *sorted_voice_info.at(i);
513}
514
515const ServerVoiceInfo& VoiceContext::GetSortedInfo(std::size_t i) const {
516 ASSERT(i < voice_count);
517 return *sorted_voice_info.at(i);
518}
519
520s32 VoiceContext::DecodePcm16(s32* output_buffer, ServerWaveBuffer* wave_buffer, s32 channel,
521 s32 channel_count, s32 buffer_offset, s32 sample_count,
522 Core::Memory::Memory& memory) {
523 if (wave_buffer->buffer_address == 0) {
524 return 0;
525 }
526 if (wave_buffer->buffer_size == 0) {
527 return 0;
528 }
529 if (wave_buffer->end_sample_offset < wave_buffer->start_sample_offset) {
530 return 0;
531 }
532
533 const auto samples_remaining =
534 (wave_buffer->end_sample_offset - wave_buffer->start_sample_offset) - buffer_offset;
535 const auto start_offset = (wave_buffer->start_sample_offset + buffer_offset) * channel_count;
536 const auto buffer_pos = wave_buffer->buffer_address + start_offset;
537
538 s16* buffer_data = reinterpret_cast<s16*>(memory.GetPointer(buffer_pos));
539
540 const auto samples_processed = std::min(sample_count, samples_remaining);
541
542 // Fast path
543 if (channel_count == 1) {
544 for (std::ptrdiff_t i = 0; i < samples_processed; i++) {
545 output_buffer[i] = buffer_data[i];
546 }
547 } else {
548 for (std::ptrdiff_t i = 0; i < samples_processed; i++) {
549 output_buffer[i] = buffer_data[i * channel_count + channel];
550 }
551 }
552
553 return samples_processed;
554}
555
556void VoiceContext::SortInfo() {
557 for (std::size_t i = 0; i < voice_count; i++) {
558 sorted_voice_info[i] = &voice_info[i];
559 }
560
561 std::sort(sorted_voice_info.begin(), sorted_voice_info.end(),
562 [](const ServerVoiceInfo* lhs, const ServerVoiceInfo* rhs) {
563 const auto& lhs_in = lhs->GetInParams();
564 const auto& rhs_in = rhs->GetInParams();
565 // Sort by priority
566 if (lhs_in.priority != rhs_in.priority) {
567 return lhs_in.priority > rhs_in.priority;
568 } else {
569 // If the priorities match, sort by sorting order
570 return lhs_in.sorting_order > rhs_in.sorting_order;
571 }
572 });
573}
574
575void VoiceContext::UpdateStateByDspShared() {
576 voice_states = dsp_voice_states;
577}
578
579} // namespace AudioCore
diff --git a/src/audio_core/voice_context.h b/src/audio_core/voice_context.h
deleted file mode 100644
index 259220dc7..000000000
--- a/src/audio_core/voice_context.h
+++ /dev/null
@@ -1,302 +0,0 @@
1// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7#include "audio_core/algorithm/interpolate.h"
8#include "audio_core/codec.h"
9#include "audio_core/common.h"
10#include "common/bit_field.h"
11#include "common/common_funcs.h"
12#include "common/common_types.h"
13
14namespace Core::Memory {
15class Memory;
16}
17
18namespace AudioCore {
19
20class BehaviorInfo;
21class VoiceContext;
22
23enum class SampleFormat : u8 {
24 Invalid = 0,
25 Pcm8 = 1,
26 Pcm16 = 2,
27 Pcm24 = 3,
28 Pcm32 = 4,
29 PcmFloat = 5,
30 Adpcm = 6,
31};
32
33enum class PlayState : u8 {
34 Started = 0,
35 Stopped = 1,
36 Paused = 2,
37};
38
39enum class ServerPlayState {
40 Play = 0,
41 Stop = 1,
42 RequestStop = 2,
43 Paused = 3,
44};
45
46struct BiquadFilterParameter {
47 bool enabled{};
48 INSERT_PADDING_BYTES(1);
49 std::array<s16, 3> numerator{};
50 std::array<s16, 2> denominator{};
51};
52static_assert(sizeof(BiquadFilterParameter) == 0xc, "BiquadFilterParameter is an invalid size");
53
54struct WaveBuffer {
55 u64_le buffer_address{};
56 u64_le buffer_size{};
57 s32_le start_sample_offset{};
58 s32_le end_sample_offset{};
59 u8 is_looping{};
60 u8 end_of_stream{};
61 u8 sent_to_server{};
62 INSERT_PADDING_BYTES(1);
63 s32 loop_count{};
64 u64 context_address{};
65 u64 context_size{};
66 u32 loop_start_sample{};
67 u32 loop_end_sample{};
68};
69static_assert(sizeof(WaveBuffer) == 0x38, "WaveBuffer is an invalid size");
70
71struct ServerWaveBuffer {
72 VAddr buffer_address{};
73 std::size_t buffer_size{};
74 s32 start_sample_offset{};
75 s32 end_sample_offset{};
76 bool is_looping{};
77 bool end_of_stream{};
78 VAddr context_address{};
79 std::size_t context_size{};
80 s32 loop_count{};
81 u32 loop_start_sample{};
82 u32 loop_end_sample{};
83 bool sent_to_dsp{true};
84};
85
86struct BehaviorFlags {
87 BitField<0, 1, u16> is_played_samples_reset_at_loop_point;
88 BitField<1, 1, u16> is_pitch_and_src_skipped;
89};
90static_assert(sizeof(BehaviorFlags) == 0x4, "BehaviorFlags is an invalid size");
91
92struct ADPCMContext {
93 u16 header;
94 s16 yn1;
95 s16 yn2;
96};
97static_assert(sizeof(ADPCMContext) == 0x6, "ADPCMContext is an invalid size");
98
99struct VoiceState {
100 s64 played_sample_count;
101 s32 offset;
102 s32 wave_buffer_index;
103 std::array<bool, AudioCommon::MAX_WAVE_BUFFERS> is_wave_buffer_valid;
104 s32 wave_buffer_consumed;
105 std::array<s32, AudioCommon::MAX_SAMPLE_HISTORY> sample_history;
106 s32 fraction;
107 VAddr context_address;
108 Codec::ADPCM_Coeff coeff;
109 ADPCMContext context;
110 std::array<s64, 2> biquad_filter_state;
111 std::array<s32, AudioCommon::MAX_MIX_BUFFERS> previous_samples;
112 u32 external_context_size;
113 bool is_external_context_used;
114 bool voice_dropped;
115 s32 loop_count;
116};
117
118class VoiceChannelResource {
119public:
120 struct InParams {
121 s32_le id{};
122 std::array<float_le, AudioCommon::MAX_MIX_BUFFERS> mix_volume{};
123 bool in_use{};
124 INSERT_PADDING_BYTES(11);
125 };
126 static_assert(sizeof(InParams) == 0x70, "InParams is an invalid size");
127};
128
129class ServerVoiceChannelResource {
130public:
131 explicit ServerVoiceChannelResource(s32 id_);
132 ~ServerVoiceChannelResource();
133
134 bool InUse() const;
135 float GetCurrentMixVolumeAt(std::size_t i) const;
136 float GetLastMixVolumeAt(std::size_t i) const;
137 void Update(VoiceChannelResource::InParams& in_params);
138 void UpdateLastMixVolumes();
139
140 const std::array<float, AudioCommon::MAX_MIX_BUFFERS>& GetCurrentMixVolume() const;
141 const std::array<float, AudioCommon::MAX_MIX_BUFFERS>& GetLastMixVolume() const;
142
143private:
144 s32 id{};
145 std::array<float, AudioCommon::MAX_MIX_BUFFERS> mix_volume{};
146 std::array<float, AudioCommon::MAX_MIX_BUFFERS> last_mix_volume{};
147 bool in_use{};
148};
149
150class VoiceInfo {
151public:
152 struct InParams {
153 s32_le id{};
154 u32_le node_id{};
155 u8 is_new{};
156 u8 is_in_use{};
157 PlayState play_state{};
158 SampleFormat sample_format{};
159 s32_le sample_rate{};
160 s32_le priority{};
161 s32_le sorting_order{};
162 s32_le channel_count{};
163 float_le pitch{};
164 float_le volume{};
165 std::array<BiquadFilterParameter, 2> biquad_filter{};
166 s32_le wave_buffer_count{};
167 s16_le wave_buffer_head{};
168 INSERT_PADDING_BYTES(6);
169 u64_le additional_params_address{};
170 u64_le additional_params_size{};
171 s32_le mix_id{};
172 s32_le splitter_info_id{};
173 std::array<WaveBuffer, 4> wave_buffer{};
174 std::array<u32_le, 6> voice_channel_resource_ids{};
175 // TODO(ogniK): Remaining flags
176 u8 is_voice_drop_flag_clear_requested{};
177 u8 wave_buffer_flush_request_count{};
178 INSERT_PADDING_BYTES(2);
179 BehaviorFlags behavior_flags{};
180 INSERT_PADDING_BYTES(16);
181 };
182 static_assert(sizeof(InParams) == 0x170, "InParams is an invalid size");
183
184 struct OutParams {
185 u64_le played_sample_count{};
186 u32_le wave_buffer_consumed{};
187 u8 voice_dropped{};
188 INSERT_PADDING_BYTES(3);
189 };
190 static_assert(sizeof(OutParams) == 0x10, "OutParams is an invalid size");
191};
192
193class ServerVoiceInfo {
194public:
195 struct InParams {
196 bool in_use{};
197 bool is_new{};
198 bool should_depop{};
199 SampleFormat sample_format{};
200 s32 sample_rate{};
201 s32 channel_count{};
202 s32 id{};
203 s32 node_id{};
204 s32 mix_id{};
205 ServerPlayState current_playstate{};
206 ServerPlayState last_playstate{};
207 s32 priority{};
208 s32 sorting_order{};
209 float pitch{};
210 float volume{};
211 float last_volume{};
212 std::array<BiquadFilterParameter, AudioCommon::MAX_BIQUAD_FILTERS> biquad_filter{};
213 s32 wave_buffer_count{};
214 s16 wave_buffer_head{};
215 INSERT_PADDING_BYTES(2);
216 BehaviorFlags behavior_flags{};
217 VAddr additional_params_address{};
218 std::size_t additional_params_size{};
219 std::array<ServerWaveBuffer, AudioCommon::MAX_WAVE_BUFFERS> wave_buffer{};
220 std::array<s32, AudioCommon::MAX_CHANNEL_COUNT> voice_channel_resource_id{};
221 s32 splitter_info_id{};
222 u8 wave_buffer_flush_request_count{};
223 bool voice_drop_flag{};
224 bool buffer_mapped{};
225 std::array<bool, AudioCommon::MAX_BIQUAD_FILTERS> was_biquad_filter_enabled{};
226 };
227
228 struct OutParams {
229 s64 played_sample_count{};
230 s32 wave_buffer_consumed{};
231 };
232
233 ServerVoiceInfo();
234 ~ServerVoiceInfo();
235 void Initialize();
236 void UpdateParameters(const VoiceInfo::InParams& voice_in, BehaviorInfo& behavior_info);
237 void UpdateWaveBuffers(const VoiceInfo::InParams& voice_in,
238 std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& voice_states,
239 BehaviorInfo& behavior_info);
240 void UpdateWaveBuffer(ServerWaveBuffer& out_wavebuffer, const WaveBuffer& in_wave_buffer,
241 SampleFormat sample_format, bool is_buffer_valid,
242 BehaviorInfo& behavior_info);
243 void WriteOutStatus(VoiceInfo::OutParams& voice_out, VoiceInfo::InParams& voice_in,
244 std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& voice_states);
245
246 const InParams& GetInParams() const;
247 InParams& GetInParams();
248
249 const OutParams& GetOutParams() const;
250 OutParams& GetOutParams();
251
252 bool ShouldSkip() const;
253 bool UpdateForCommandGeneration(VoiceContext& voice_context);
254 void ResetResources(VoiceContext& voice_context);
255 bool UpdateParametersForCommandGeneration(
256 std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& dsp_voice_states);
257 void FlushWaveBuffers(u8 flush_count,
258 std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& dsp_voice_states,
259 s32 channel_count);
260 void SetWaveBufferCompleted(VoiceState& dsp_state, const ServerWaveBuffer& wave_buffer);
261
262private:
263 std::vector<s16> stored_samples;
264 InParams in_params{};
265 OutParams out_params{};
266
267 bool HasValidWaveBuffer(const VoiceState* state) const;
268};
269
270class VoiceContext {
271public:
272 explicit VoiceContext(std::size_t voice_count_);
273 ~VoiceContext();
274
275 std::size_t GetVoiceCount() const;
276 ServerVoiceChannelResource& GetChannelResource(std::size_t i);
277 const ServerVoiceChannelResource& GetChannelResource(std::size_t i) const;
278 VoiceState& GetState(std::size_t i);
279 const VoiceState& GetState(std::size_t i) const;
280 VoiceState& GetDspSharedState(std::size_t i);
281 const VoiceState& GetDspSharedState(std::size_t i) const;
282 ServerVoiceInfo& GetInfo(std::size_t i);
283 const ServerVoiceInfo& GetInfo(std::size_t i) const;
284 ServerVoiceInfo& GetSortedInfo(std::size_t i);
285 const ServerVoiceInfo& GetSortedInfo(std::size_t i) const;
286
287 s32 DecodePcm16(s32* output_buffer, ServerWaveBuffer* wave_buffer, s32 channel,
288 s32 channel_count, s32 buffer_offset, s32 sample_count,
289 Core::Memory::Memory& memory);
290 void SortInfo();
291 void UpdateStateByDspShared();
292
293private:
294 std::size_t voice_count{};
295 std::vector<ServerVoiceChannelResource> voice_channel_resources{};
296 std::vector<VoiceState> voice_states{};
297 std::vector<VoiceState> dsp_voice_states{};
298 std::vector<ServerVoiceInfo> voice_info{};
299 std::vector<ServerVoiceInfo*> sorted_voice_info{};
300};
301
302} // namespace AudioCore
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt
index 73bf626d4..64bb753e6 100644
--- a/src/common/CMakeLists.txt
+++ b/src/common/CMakeLists.txt
@@ -43,6 +43,7 @@ add_library(common STATIC
43 alignment.h 43 alignment.h
44 assert.cpp 44 assert.cpp
45 assert.h 45 assert.h
46 atomic_helpers.h
46 atomic_ops.h 47 atomic_ops.h
47 detached_tasks.cpp 48 detached_tasks.cpp
48 detached_tasks.h 49 detached_tasks.h
@@ -64,6 +65,7 @@ add_library(common STATIC
64 expected.h 65 expected.h
65 fiber.cpp 66 fiber.cpp
66 fiber.h 67 fiber.h
68 fixed_point.h
67 fs/file.cpp 69 fs/file.cpp
68 fs/file.h 70 fs/file.h
69 fs/fs.cpp 71 fs/fs.cpp
@@ -109,6 +111,7 @@ add_library(common STATIC
109 parent_of_member.h 111 parent_of_member.h
110 point.h 112 point.h
111 quaternion.h 113 quaternion.h
114 reader_writer_queue.h
112 ring_buffer.h 115 ring_buffer.h
113 scm_rev.cpp 116 scm_rev.cpp
114 scm_rev.h 117 scm_rev.h
diff --git a/src/common/atomic_helpers.h b/src/common/atomic_helpers.h
new file mode 100644
index 000000000..6d912b52e
--- /dev/null
+++ b/src/common/atomic_helpers.h
@@ -0,0 +1,772 @@
1// ©2013-2016 Cameron Desrochers.
2// Distributed under the simplified BSD license (see the license file that
3// should have come with this header).
4// Uses Jeff Preshing's semaphore implementation (under the terms of its
5// separate zlib license, embedded below).
6
7#pragma once
8
9// Provides portable (VC++2010+, Intel ICC 13, GCC 4.7+, and anything C++11 compliant)
10// implementation of low-level memory barriers, plus a few semi-portable utility macros (for
11// inlining and alignment). Also has a basic atomic type (limited to hardware-supported atomics with
12// no memory ordering guarantees). Uses the AE_* prefix for macros (historical reasons), and the
13// "moodycamel" namespace for symbols.
14
15#include <cassert>
16#include <cerrno>
17#include <cstdint>
18#include <ctime>
19#include <type_traits>
20
21// Platform detection
22#if defined(__INTEL_COMPILER)
23#define AE_ICC
24#elif defined(_MSC_VER)
25#define AE_VCPP
26#elif defined(__GNUC__)
27#define AE_GCC
28#endif
29
30#if defined(_M_IA64) || defined(__ia64__)
31#define AE_ARCH_IA64
32#elif defined(_WIN64) || defined(__amd64__) || defined(_M_X64) || defined(__x86_64__)
33#define AE_ARCH_X64
34#elif defined(_M_IX86) || defined(__i386__)
35#define AE_ARCH_X86
36#elif defined(_M_PPC) || defined(__powerpc__)
37#define AE_ARCH_PPC
38#else
39#define AE_ARCH_UNKNOWN
40#endif
41
42// AE_UNUSED
43#define AE_UNUSED(x) ((void)x)
44
45// AE_NO_TSAN/AE_TSAN_ANNOTATE_*
46#if defined(__has_feature)
47#if __has_feature(thread_sanitizer)
48#if __cplusplus >= 201703L // inline variables require C++17
49namespace Common {
50inline int ae_tsan_global;
51}
52#define AE_TSAN_ANNOTATE_RELEASE() \
53 AnnotateHappensBefore(__FILE__, __LINE__, (void*)(&::moodycamel::ae_tsan_global))
54#define AE_TSAN_ANNOTATE_ACQUIRE() \
55 AnnotateHappensAfter(__FILE__, __LINE__, (void*)(&::moodycamel::ae_tsan_global))
56extern "C" void AnnotateHappensBefore(const char*, int, void*);
57extern "C" void AnnotateHappensAfter(const char*, int, void*);
58#else // when we can't work with tsan, attempt to disable its warnings
59#define AE_NO_TSAN __attribute__((no_sanitize("thread")))
60#endif
61#endif
62#endif
63#ifndef AE_NO_TSAN
64#define AE_NO_TSAN
65#endif
66#ifndef AE_TSAN_ANNOTATE_RELEASE
67#define AE_TSAN_ANNOTATE_RELEASE()
68#define AE_TSAN_ANNOTATE_ACQUIRE()
69#endif
70
71// AE_FORCEINLINE
72#if defined(AE_VCPP) || defined(AE_ICC)
73#define AE_FORCEINLINE __forceinline
74#elif defined(AE_GCC)
75//#define AE_FORCEINLINE __attribute__((always_inline))
76#define AE_FORCEINLINE inline
77#else
78#define AE_FORCEINLINE inline
79#endif
80
81// AE_ALIGN
82#if defined(AE_VCPP) || defined(AE_ICC)
83#define AE_ALIGN(x) __declspec(align(x))
84#elif defined(AE_GCC)
85#define AE_ALIGN(x) __attribute__((aligned(x)))
86#else
87// Assume GCC compliant syntax...
88#define AE_ALIGN(x) __attribute__((aligned(x)))
89#endif
90
91// Portable atomic fences implemented below:
92
93namespace Common {
94
95enum memory_order {
96 memory_order_relaxed,
97 memory_order_acquire,
98 memory_order_release,
99 memory_order_acq_rel,
100 memory_order_seq_cst,
101
102 // memory_order_sync: Forces a full sync:
103 // #LoadLoad, #LoadStore, #StoreStore, and most significantly, #StoreLoad
104 memory_order_sync = memory_order_seq_cst
105};
106
107} // namespace Common
108
109#if (defined(AE_VCPP) && (_MSC_VER < 1700 || defined(__cplusplus_cli))) || \
110 (defined(AE_ICC) && __INTEL_COMPILER < 1600)
111// VS2010 and ICC13 don't support std::atomic_*_fence, implement our own fences
112
113#include <intrin.h>
114
115#if defined(AE_ARCH_X64) || defined(AE_ARCH_X86)
116#define AeFullSync _mm_mfence
117#define AeLiteSync _mm_mfence
118#elif defined(AE_ARCH_IA64)
119#define AeFullSync __mf
120#define AeLiteSync __mf
121#elif defined(AE_ARCH_PPC)
122#include <ppcintrinsics.h>
123#define AeFullSync __sync
124#define AeLiteSync __lwsync
125#endif
126
127#ifdef AE_VCPP
128#pragma warning(push)
129#pragma warning(disable : 4365) // Disable erroneous 'conversion from long to unsigned int,
130 // signed/unsigned mismatch' error when using `assert`
131#ifdef __cplusplus_cli
132#pragma managed(push, off)
133#endif
134#endif
135
136namespace Common {
137
138AE_FORCEINLINE void compiler_fence(memory_order order) AE_NO_TSAN {
139 switch (order) {
140 case memory_order_relaxed:
141 break;
142 case memory_order_acquire:
143 _ReadBarrier();
144 break;
145 case memory_order_release:
146 _WriteBarrier();
147 break;
148 case memory_order_acq_rel:
149 _ReadWriteBarrier();
150 break;
151 case memory_order_seq_cst:
152 _ReadWriteBarrier();
153 break;
154 default:
155 assert(false);
156 }
157}
158
159// x86/x64 have a strong memory model -- all loads and stores have
160// acquire and release semantics automatically (so only need compiler
161// barriers for those).
162#if defined(AE_ARCH_X86) || defined(AE_ARCH_X64)
163AE_FORCEINLINE void fence(memory_order order) AE_NO_TSAN {
164 switch (order) {
165 case memory_order_relaxed:
166 break;
167 case memory_order_acquire:
168 _ReadBarrier();
169 break;
170 case memory_order_release:
171 _WriteBarrier();
172 break;
173 case memory_order_acq_rel:
174 _ReadWriteBarrier();
175 break;
176 case memory_order_seq_cst:
177 _ReadWriteBarrier();
178 AeFullSync();
179 _ReadWriteBarrier();
180 break;
181 default:
182 assert(false);
183 }
184}
185#else
186AE_FORCEINLINE void fence(memory_order order) AE_NO_TSAN {
187 // Non-specialized arch, use heavier memory barriers everywhere just in case :-(
188 switch (order) {
189 case memory_order_relaxed:
190 break;
191 case memory_order_acquire:
192 _ReadBarrier();
193 AeLiteSync();
194 _ReadBarrier();
195 break;
196 case memory_order_release:
197 _WriteBarrier();
198 AeLiteSync();
199 _WriteBarrier();
200 break;
201 case memory_order_acq_rel:
202 _ReadWriteBarrier();
203 AeLiteSync();
204 _ReadWriteBarrier();
205 break;
206 case memory_order_seq_cst:
207 _ReadWriteBarrier();
208 AeFullSync();
209 _ReadWriteBarrier();
210 break;
211 default:
212 assert(false);
213 }
214}
215#endif
216} // namespace Common
217#else
218// Use standard library of atomics
219#include <atomic>
220
221namespace Common {
222
223AE_FORCEINLINE void compiler_fence(memory_order order) AE_NO_TSAN {
224 switch (order) {
225 case memory_order_relaxed:
226 break;
227 case memory_order_acquire:
228 std::atomic_signal_fence(std::memory_order_acquire);
229 break;
230 case memory_order_release:
231 std::atomic_signal_fence(std::memory_order_release);
232 break;
233 case memory_order_acq_rel:
234 std::atomic_signal_fence(std::memory_order_acq_rel);
235 break;
236 case memory_order_seq_cst:
237 std::atomic_signal_fence(std::memory_order_seq_cst);
238 break;
239 default:
240 assert(false);
241 }
242}
243
244AE_FORCEINLINE void fence(memory_order order) AE_NO_TSAN {
245 switch (order) {
246 case memory_order_relaxed:
247 break;
248 case memory_order_acquire:
249 AE_TSAN_ANNOTATE_ACQUIRE();
250 std::atomic_thread_fence(std::memory_order_acquire);
251 break;
252 case memory_order_release:
253 AE_TSAN_ANNOTATE_RELEASE();
254 std::atomic_thread_fence(std::memory_order_release);
255 break;
256 case memory_order_acq_rel:
257 AE_TSAN_ANNOTATE_ACQUIRE();
258 AE_TSAN_ANNOTATE_RELEASE();
259 std::atomic_thread_fence(std::memory_order_acq_rel);
260 break;
261 case memory_order_seq_cst:
262 AE_TSAN_ANNOTATE_ACQUIRE();
263 AE_TSAN_ANNOTATE_RELEASE();
264 std::atomic_thread_fence(std::memory_order_seq_cst);
265 break;
266 default:
267 assert(false);
268 }
269}
270
271} // namespace Common
272
273#endif
274
275#if !defined(AE_VCPP) || (_MSC_VER >= 1700 && !defined(__cplusplus_cli))
276#define AE_USE_STD_ATOMIC_FOR_WEAK_ATOMIC
277#endif
278
279#ifdef AE_USE_STD_ATOMIC_FOR_WEAK_ATOMIC
280#include <atomic>
281#endif
282#include <utility>
283
284// WARNING: *NOT* A REPLACEMENT FOR std::atomic. READ CAREFULLY:
285// Provides basic support for atomic variables -- no memory ordering guarantees are provided.
286// The guarantee of atomicity is only made for types that already have atomic load and store
287// guarantees at the hardware level -- on most platforms this generally means aligned pointers and
288// integers (only).
289namespace Common {
290template <typename T>
291class weak_atomic {
292public:
293 AE_NO_TSAN weak_atomic() : value() {}
294#ifdef AE_VCPP
295#pragma warning(push)
296#pragma warning(disable : 4100) // Get rid of (erroneous) 'unreferenced formal parameter' warning
297#endif
298 template <typename U>
299 AE_NO_TSAN weak_atomic(U&& x) : value(std::forward<U>(x)) {}
300#ifdef __cplusplus_cli
301 // Work around bug with universal reference/nullptr combination that only appears when /clr is
302 // on
303 AE_NO_TSAN weak_atomic(nullptr_t) : value(nullptr) {}
304#endif
305 AE_NO_TSAN weak_atomic(weak_atomic const& other) : value(other.load()) {}
306 AE_NO_TSAN weak_atomic(weak_atomic&& other) : value(std::move(other.load())) {}
307#ifdef AE_VCPP
308#pragma warning(pop)
309#endif
310
311 AE_FORCEINLINE operator T() const AE_NO_TSAN {
312 return load();
313 }
314
315#ifndef AE_USE_STD_ATOMIC_FOR_WEAK_ATOMIC
316 template <typename U>
317 AE_FORCEINLINE weak_atomic const& operator=(U&& x) AE_NO_TSAN {
318 value = std::forward<U>(x);
319 return *this;
320 }
321 AE_FORCEINLINE weak_atomic const& operator=(weak_atomic const& other) AE_NO_TSAN {
322 value = other.value;
323 return *this;
324 }
325
326 AE_FORCEINLINE T load() const AE_NO_TSAN {
327 return value;
328 }
329
330 AE_FORCEINLINE T fetch_add_acquire(T increment) AE_NO_TSAN {
331#if defined(AE_ARCH_X64) || defined(AE_ARCH_X86)
332 if (sizeof(T) == 4)
333 return _InterlockedExchangeAdd((long volatile*)&value, (long)increment);
334#if defined(_M_AMD64)
335 else if (sizeof(T) == 8)
336 return _InterlockedExchangeAdd64((long long volatile*)&value, (long long)increment);
337#endif
338#else
339#error Unsupported platform
340#endif
341 assert(false && "T must be either a 32 or 64 bit type");
342 return value;
343 }
344
345 AE_FORCEINLINE T fetch_add_release(T increment) AE_NO_TSAN {
346#if defined(AE_ARCH_X64) || defined(AE_ARCH_X86)
347 if (sizeof(T) == 4)
348 return _InterlockedExchangeAdd((long volatile*)&value, (long)increment);
349#if defined(_M_AMD64)
350 else if (sizeof(T) == 8)
351 return _InterlockedExchangeAdd64((long long volatile*)&value, (long long)increment);
352#endif
353#else
354#error Unsupported platform
355#endif
356 assert(false && "T must be either a 32 or 64 bit type");
357 return value;
358 }
359#else
360 template <typename U>
361 AE_FORCEINLINE weak_atomic const& operator=(U&& x) AE_NO_TSAN {
362 value.store(std::forward<U>(x), std::memory_order_relaxed);
363 return *this;
364 }
365
366 AE_FORCEINLINE weak_atomic const& operator=(weak_atomic const& other) AE_NO_TSAN {
367 value.store(other.value.load(std::memory_order_relaxed), std::memory_order_relaxed);
368 return *this;
369 }
370
371 AE_FORCEINLINE T load() const AE_NO_TSAN {
372 return value.load(std::memory_order_relaxed);
373 }
374
375 AE_FORCEINLINE T fetch_add_acquire(T increment) AE_NO_TSAN {
376 return value.fetch_add(increment, std::memory_order_acquire);
377 }
378
379 AE_FORCEINLINE T fetch_add_release(T increment) AE_NO_TSAN {
380 return value.fetch_add(increment, std::memory_order_release);
381 }
382#endif
383
384private:
385#ifndef AE_USE_STD_ATOMIC_FOR_WEAK_ATOMIC
386 // No std::atomic support, but still need to circumvent compiler optimizations.
387 // `volatile` will make memory access slow, but is guaranteed to be reliable.
388 volatile T value;
389#else
390 std::atomic<T> value;
391#endif
392};
393
394} // namespace Common
395
396// Portable single-producer, single-consumer semaphore below:
397
398#if defined(_WIN32)
399// Avoid including windows.h in a header; we only need a handful of
400// items, so we'll redeclare them here (this is relatively safe since
401// the API generally has to remain stable between Windows versions).
402// I know this is an ugly hack but it still beats polluting the global
403// namespace with thousands of generic names or adding a .cpp for nothing.
404extern "C" {
405struct _SECURITY_ATTRIBUTES;
406__declspec(dllimport) void* __stdcall CreateSemaphoreW(_SECURITY_ATTRIBUTES* lpSemaphoreAttributes,
407 long lInitialCount, long lMaximumCount,
408 const wchar_t* lpName);
409__declspec(dllimport) int __stdcall CloseHandle(void* hObject);
410__declspec(dllimport) unsigned long __stdcall WaitForSingleObject(void* hHandle,
411 unsigned long dwMilliseconds);
412__declspec(dllimport) int __stdcall ReleaseSemaphore(void* hSemaphore, long lReleaseCount,
413 long* lpPreviousCount);
414}
415#elif defined(__MACH__)
416#include <mach/mach.h>
417#elif defined(__unix__)
418#include <semaphore.h>
419#elif defined(FREERTOS)
420#include <FreeRTOS.h>
421#include <semphr.h>
422#include <task.h>
423#endif
424
425namespace Common {
426// Code in the spsc_sema namespace below is an adaptation of Jeff Preshing's
427// portable + lightweight semaphore implementations, originally from
428// https://github.com/preshing/cpp11-on-multicore/blob/master/common/sema.h
429// LICENSE:
430// Copyright (c) 2015 Jeff Preshing
431//
432// This software is provided 'as-is', without any express or implied
433// warranty. In no event will the authors be held liable for any damages
434// arising from the use of this software.
435//
436// Permission is granted to anyone to use this software for any purpose,
437// including commercial applications, and to alter it and redistribute it
438// freely, subject to the following restrictions:
439//
440// 1. The origin of this software must not be misrepresented; you must not
441// claim that you wrote the original software. If you use this software
442// in a product, an acknowledgement in the product documentation would be
443// appreciated but is not required.
444// 2. Altered source versions must be plainly marked as such, and must not be
445// misrepresented as being the original software.
446// 3. This notice may not be removed or altered from any source distribution.
447namespace spsc_sema {
448#if defined(_WIN32)
449class Semaphore {
450private:
451 void* m_hSema;
452
453 Semaphore(const Semaphore& other);
454 Semaphore& operator=(const Semaphore& other);
455
456public:
457 AE_NO_TSAN Semaphore(int initialCount = 0) : m_hSema() {
458 assert(initialCount >= 0);
459 const long maxLong = 0x7fffffff;
460 m_hSema = CreateSemaphoreW(nullptr, initialCount, maxLong, nullptr);
461 assert(m_hSema);
462 }
463
464 AE_NO_TSAN ~Semaphore() {
465 CloseHandle(m_hSema);
466 }
467
468 bool wait() AE_NO_TSAN {
469 const unsigned long infinite = 0xffffffff;
470 return WaitForSingleObject(m_hSema, infinite) == 0;
471 }
472
473 bool try_wait() AE_NO_TSAN {
474 return WaitForSingleObject(m_hSema, 0) == 0;
475 }
476
477 bool timed_wait(std::uint64_t usecs) AE_NO_TSAN {
478 return WaitForSingleObject(m_hSema, (unsigned long)(usecs / 1000)) == 0;
479 }
480
481 void signal(int count = 1) AE_NO_TSAN {
482 while (!ReleaseSemaphore(m_hSema, count, nullptr))
483 ;
484 }
485};
486#elif defined(__MACH__)
487//---------------------------------------------------------
488// Semaphore (Apple iOS and OSX)
489// Can't use POSIX semaphores due to
490// http://lists.apple.com/archives/darwin-kernel/2009/Apr/msg00010.html
491//---------------------------------------------------------
492class Semaphore {
493private:
494 semaphore_t m_sema;
495
496 Semaphore(const Semaphore& other);
497 Semaphore& operator=(const Semaphore& other);
498
499public:
500 AE_NO_TSAN Semaphore(int initialCount = 0) : m_sema() {
501 assert(initialCount >= 0);
502 kern_return_t rc =
503 semaphore_create(mach_task_self(), &m_sema, SYNC_POLICY_FIFO, initialCount);
504 assert(rc == KERN_SUCCESS);
505 AE_UNUSED(rc);
506 }
507
508 AE_NO_TSAN ~Semaphore() {
509 semaphore_destroy(mach_task_self(), m_sema);
510 }
511
512 bool wait() AE_NO_TSAN {
513 return semaphore_wait(m_sema) == KERN_SUCCESS;
514 }
515
516 bool try_wait() AE_NO_TSAN {
517 return timed_wait(0);
518 }
519
520 bool timed_wait(std::uint64_t timeout_usecs) AE_NO_TSAN {
521 mach_timespec_t ts;
522 ts.tv_sec = static_cast<unsigned int>(timeout_usecs / 1000000);
523 ts.tv_nsec = static_cast<int>((timeout_usecs % 1000000) * 1000);
524
525 // added in OSX 10.10:
526 // https://developer.apple.com/library/prerelease/mac/documentation/General/Reference/APIDiffsMacOSX10_10SeedDiff/modules/Darwin.html
527 kern_return_t rc = semaphore_timedwait(m_sema, ts);
528 return rc == KERN_SUCCESS;
529 }
530
531 void signal() AE_NO_TSAN {
532 while (semaphore_signal(m_sema) != KERN_SUCCESS)
533 ;
534 }
535
536 void signal(int count) AE_NO_TSAN {
537 while (count-- > 0) {
538 while (semaphore_signal(m_sema) != KERN_SUCCESS)
539 ;
540 }
541 }
542};
543#elif defined(__unix__)
544//---------------------------------------------------------
545// Semaphore (POSIX, Linux)
546//---------------------------------------------------------
547class Semaphore {
548private:
549 sem_t m_sema;
550
551 Semaphore(const Semaphore& other);
552 Semaphore& operator=(const Semaphore& other);
553
554public:
555 AE_NO_TSAN Semaphore(int initialCount = 0) : m_sema() {
556 assert(initialCount >= 0);
557 int rc = sem_init(&m_sema, 0, static_cast<unsigned int>(initialCount));
558 assert(rc == 0);
559 AE_UNUSED(rc);
560 }
561
562 AE_NO_TSAN ~Semaphore() {
563 sem_destroy(&m_sema);
564 }
565
566 bool wait() AE_NO_TSAN {
567 // http://stackoverflow.com/questions/2013181/gdb-causes-sem-wait-to-fail-with-eintr-error
568 int rc;
569 do {
570 rc = sem_wait(&m_sema);
571 } while (rc == -1 && errno == EINTR);
572 return rc == 0;
573 }
574
575 bool try_wait() AE_NO_TSAN {
576 int rc;
577 do {
578 rc = sem_trywait(&m_sema);
579 } while (rc == -1 && errno == EINTR);
580 return rc == 0;
581 }
582
583 bool timed_wait(std::uint64_t usecs) AE_NO_TSAN {
584 struct timespec ts;
585 const int usecs_in_1_sec = 1000000;
586 const int nsecs_in_1_sec = 1000000000;
587 clock_gettime(CLOCK_REALTIME, &ts);
588 ts.tv_sec += static_cast<time_t>(usecs / usecs_in_1_sec);
589 ts.tv_nsec += static_cast<long>(usecs % usecs_in_1_sec) * 1000;
590 // sem_timedwait bombs if you have more than 1e9 in tv_nsec
591 // so we have to clean things up before passing it in
592 if (ts.tv_nsec >= nsecs_in_1_sec) {
593 ts.tv_nsec -= nsecs_in_1_sec;
594 ++ts.tv_sec;
595 }
596
597 int rc;
598 do {
599 rc = sem_timedwait(&m_sema, &ts);
600 } while (rc == -1 && errno == EINTR);
601 return rc == 0;
602 }
603
604 void signal() AE_NO_TSAN {
605 while (sem_post(&m_sema) == -1)
606 ;
607 }
608
609 void signal(int count) AE_NO_TSAN {
610 while (count-- > 0) {
611 while (sem_post(&m_sema) == -1)
612 ;
613 }
614 }
615};
616#elif defined(FREERTOS)
617//---------------------------------------------------------
618// Semaphore (FreeRTOS)
619//---------------------------------------------------------
620class Semaphore {
621private:
622 SemaphoreHandle_t m_sema;
623
624 Semaphore(const Semaphore& other);
625 Semaphore& operator=(const Semaphore& other);
626
627public:
628 AE_NO_TSAN Semaphore(int initialCount = 0) : m_sema() {
629 assert(initialCount >= 0);
630 m_sema = xSemaphoreCreateCounting(static_cast<UBaseType_t>(~0ull),
631 static_cast<UBaseType_t>(initialCount));
632 assert(m_sema);
633 }
634
635 AE_NO_TSAN ~Semaphore() {
636 vSemaphoreDelete(m_sema);
637 }
638
639 bool wait() AE_NO_TSAN {
640 return xSemaphoreTake(m_sema, portMAX_DELAY) == pdTRUE;
641 }
642
643 bool try_wait() AE_NO_TSAN {
644 // Note: In an ISR context, if this causes a task to unblock,
645 // the caller won't know about it
646 if (xPortIsInsideInterrupt())
647 return xSemaphoreTakeFromISR(m_sema, NULL) == pdTRUE;
648 return xSemaphoreTake(m_sema, 0) == pdTRUE;
649 }
650
651 bool timed_wait(std::uint64_t usecs) AE_NO_TSAN {
652 std::uint64_t msecs = usecs / 1000;
653 TickType_t ticks = static_cast<TickType_t>(msecs / portTICK_PERIOD_MS);
654 if (ticks == 0)
655 return try_wait();
656 return xSemaphoreTake(m_sema, ticks) == pdTRUE;
657 }
658
659 void signal() AE_NO_TSAN {
660 // Note: In an ISR context, if this causes a task to unblock,
661 // the caller won't know about it
662 BaseType_t rc;
663 if (xPortIsInsideInterrupt())
664 rc = xSemaphoreGiveFromISR(m_sema, NULL);
665 else
666 rc = xSemaphoreGive(m_sema);
667 assert(rc == pdTRUE);
668 AE_UNUSED(rc);
669 }
670
671 void signal(int count) AE_NO_TSAN {
672 while (count-- > 0)
673 signal();
674 }
675};
676#else
677#error Unsupported platform! (No semaphore wrapper available)
678#endif
679
680//---------------------------------------------------------
681// LightweightSemaphore
682//---------------------------------------------------------
683class LightweightSemaphore {
684public:
685 typedef std::make_signed<std::size_t>::type ssize_t;
686
687private:
688 weak_atomic<ssize_t> m_count;
689 Semaphore m_sema;
690
691 bool waitWithPartialSpinning(std::int64_t timeout_usecs = -1) AE_NO_TSAN {
692 ssize_t oldCount;
693 // Is there a better way to set the initial spin count?
694 // If we lower it to 1000, testBenaphore becomes 15x slower on my Core i7-5930K Windows PC,
695 // as threads start hitting the kernel semaphore.
696 int spin = 1024;
697 while (--spin >= 0) {
698 if (m_count.load() > 0) {
699 m_count.fetch_add_acquire(-1);
700 return true;
701 }
702 compiler_fence(memory_order_acquire); // Prevent the compiler from collapsing the loop.
703 }
704 oldCount = m_count.fetch_add_acquire(-1);
705 if (oldCount > 0)
706 return true;
707 if (timeout_usecs < 0) {
708 if (m_sema.wait())
709 return true;
710 }
711 if (timeout_usecs > 0 && m_sema.timed_wait(static_cast<uint64_t>(timeout_usecs)))
712 return true;
713 // At this point, we've timed out waiting for the semaphore, but the
714 // count is still decremented indicating we may still be waiting on
715 // it. So we have to re-adjust the count, but only if the semaphore
716 // wasn't signaled enough times for us too since then. If it was, we
717 // need to release the semaphore too.
718 while (true) {
719 oldCount = m_count.fetch_add_release(1);
720 if (oldCount < 0)
721 return false; // successfully restored things to the way they were
722 // Oh, the producer thread just signaled the semaphore after all. Try again:
723 oldCount = m_count.fetch_add_acquire(-1);
724 if (oldCount > 0 && m_sema.try_wait())
725 return true;
726 }
727 }
728
729public:
730 AE_NO_TSAN LightweightSemaphore(ssize_t initialCount = 0) : m_count(initialCount), m_sema() {
731 assert(initialCount >= 0);
732 }
733
734 bool tryWait() AE_NO_TSAN {
735 if (m_count.load() > 0) {
736 m_count.fetch_add_acquire(-1);
737 return true;
738 }
739 return false;
740 }
741
742 bool wait() AE_NO_TSAN {
743 return tryWait() || waitWithPartialSpinning();
744 }
745
746 bool wait(std::int64_t timeout_usecs) AE_NO_TSAN {
747 return tryWait() || waitWithPartialSpinning(timeout_usecs);
748 }
749
750 void signal(ssize_t count = 1) AE_NO_TSAN {
751 assert(count >= 0);
752 ssize_t oldCount = m_count.fetch_add_release(count);
753 assert(oldCount >= -1);
754 if (oldCount < 0) {
755 m_sema.signal(1);
756 }
757 }
758
759 std::size_t availableApprox() const AE_NO_TSAN {
760 ssize_t count = m_count.load();
761 return count > 0 ? static_cast<std::size_t>(count) : 0;
762 }
763};
764} // namespace spsc_sema
765} // namespace Common
766
767#if defined(AE_VCPP) && (_MSC_VER < 1700 || defined(__cplusplus_cli))
768#pragma warning(pop)
769#ifdef __cplusplus_cli
770#pragma managed(pop)
771#endif
772#endif
diff --git a/src/common/fixed_point.h b/src/common/fixed_point.h
new file mode 100644
index 000000000..1d45e51b3
--- /dev/null
+++ b/src/common/fixed_point.h
@@ -0,0 +1,726 @@
1// From: https://github.com/eteran/cpp-utilities/blob/master/fixed/include/cpp-utilities/fixed.h
2// See also: http://stackoverflow.com/questions/79677/whats-the-best-way-to-do-fixed-point-math
3/*
4 * The MIT License (MIT)
5 *
6 * Copyright (c) 2015 Evan Teran
7 *
8 * Permission is hereby granted, free of charge, to any person obtaining a copy
9 * of this software and associated documentation files (the "Software"), to deal
10 * in the Software without restriction, including without limitation the rights
11 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 * copies of the Software, and to permit persons to whom the Software is
13 * furnished to do so, subject to the following conditions:
14 *
15 * The above copyright notice and this permission notice shall be included in all
16 * copies or substantial portions of the Software.
17 *
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 * SOFTWARE.
25 */
26
27#ifndef FIXED_H_
28#define FIXED_H_
29
30#if __cplusplus >= 201402L
31#define CONSTEXPR14 constexpr
32#else
33#define CONSTEXPR14
34#endif
35
36#include <cstddef> // for size_t
37#include <cstdint>
38#include <exception>
39#include <ostream>
40#include <type_traits>
41
42namespace Common {
43
44template <size_t I, size_t F>
45class FixedPoint;
46
47namespace detail {
48
49// helper templates to make magic with types :)
50// these allow us to determine resonable types from
51// a desired size, they also let us infer the next largest type
52// from a type which is nice for the division op
53template <size_t T>
54struct type_from_size {
55 using value_type = void;
56 using unsigned_type = void;
57 using signed_type = void;
58 static constexpr bool is_specialized = false;
59};
60
61#if defined(__GNUC__) && defined(__x86_64__) && !defined(__STRICT_ANSI__)
62template <>
63struct type_from_size<128> {
64 static constexpr bool is_specialized = true;
65 static constexpr size_t size = 128;
66
67 using value_type = __int128;
68 using unsigned_type = unsigned __int128;
69 using signed_type = __int128;
70 using next_size = type_from_size<256>;
71};
72#endif
73
74template <>
75struct type_from_size<64> {
76 static constexpr bool is_specialized = true;
77 static constexpr size_t size = 64;
78
79 using value_type = int64_t;
80 using unsigned_type = std::make_unsigned<value_type>::type;
81 using signed_type = std::make_signed<value_type>::type;
82 using next_size = type_from_size<128>;
83};
84
85template <>
86struct type_from_size<32> {
87 static constexpr bool is_specialized = true;
88 static constexpr size_t size = 32;
89
90 using value_type = int32_t;
91 using unsigned_type = std::make_unsigned<value_type>::type;
92 using signed_type = std::make_signed<value_type>::type;
93 using next_size = type_from_size<64>;
94};
95
96template <>
97struct type_from_size<16> {
98 static constexpr bool is_specialized = true;
99 static constexpr size_t size = 16;
100
101 using value_type = int16_t;
102 using unsigned_type = std::make_unsigned<value_type>::type;
103 using signed_type = std::make_signed<value_type>::type;
104 using next_size = type_from_size<32>;
105};
106
107template <>
108struct type_from_size<8> {
109 static constexpr bool is_specialized = true;
110 static constexpr size_t size = 8;
111
112 using value_type = int8_t;
113 using unsigned_type = std::make_unsigned<value_type>::type;
114 using signed_type = std::make_signed<value_type>::type;
115 using next_size = type_from_size<16>;
116};
117
118// this is to assist in adding support for non-native base
119// types (for adding big-int support), this should be fine
120// unless your bit-int class doesn't nicely support casting
121template <class B, class N>
122constexpr B next_to_base(N rhs) {
123 return static_cast<B>(rhs);
124}
125
126struct divide_by_zero : std::exception {};
127
128template <size_t I, size_t F>
129CONSTEXPR14 FixedPoint<I, F> divide(
130 FixedPoint<I, F> numerator, FixedPoint<I, F> denominator, FixedPoint<I, F>& remainder,
131 typename std::enable_if<type_from_size<I + F>::next_size::is_specialized>::type* = nullptr) {
132
133 using next_type = typename FixedPoint<I, F>::next_type;
134 using base_type = typename FixedPoint<I, F>::base_type;
135 constexpr size_t fractional_bits = FixedPoint<I, F>::fractional_bits;
136
137 next_type t(numerator.to_raw());
138 t <<= fractional_bits;
139
140 FixedPoint<I, F> quotient;
141
142 quotient = FixedPoint<I, F>::from_base(next_to_base<base_type>(t / denominator.to_raw()));
143 remainder = FixedPoint<I, F>::from_base(next_to_base<base_type>(t % denominator.to_raw()));
144
145 return quotient;
146}
147
148template <size_t I, size_t F>
149CONSTEXPR14 FixedPoint<I, F> divide(
150 FixedPoint<I, F> numerator, FixedPoint<I, F> denominator, FixedPoint<I, F>& remainder,
151 typename std::enable_if<!type_from_size<I + F>::next_size::is_specialized>::type* = nullptr) {
152
153 using unsigned_type = typename FixedPoint<I, F>::unsigned_type;
154
155 constexpr int bits = FixedPoint<I, F>::total_bits;
156
157 if (denominator == 0) {
158 throw divide_by_zero();
159 } else {
160
161 int sign = 0;
162
163 FixedPoint<I, F> quotient;
164
165 if (numerator < 0) {
166 sign ^= 1;
167 numerator = -numerator;
168 }
169
170 if (denominator < 0) {
171 sign ^= 1;
172 denominator = -denominator;
173 }
174
175 unsigned_type n = numerator.to_raw();
176 unsigned_type d = denominator.to_raw();
177 unsigned_type x = 1;
178 unsigned_type answer = 0;
179
180 // egyptian division algorithm
181 while ((n >= d) && (((d >> (bits - 1)) & 1) == 0)) {
182 x <<= 1;
183 d <<= 1;
184 }
185
186 while (x != 0) {
187 if (n >= d) {
188 n -= d;
189 answer += x;
190 }
191
192 x >>= 1;
193 d >>= 1;
194 }
195
196 unsigned_type l1 = n;
197 unsigned_type l2 = denominator.to_raw();
198
199 // calculate the lower bits (needs to be unsigned)
200 while (l1 >> (bits - F) > 0) {
201 l1 >>= 1;
202 l2 >>= 1;
203 }
204 const unsigned_type lo = (l1 << F) / l2;
205
206 quotient = FixedPoint<I, F>::from_base((answer << F) | lo);
207 remainder = n;
208
209 if (sign) {
210 quotient = -quotient;
211 }
212
213 return quotient;
214 }
215}
216
217// this is the usual implementation of multiplication
218template <size_t I, size_t F>
219CONSTEXPR14 FixedPoint<I, F> multiply(
220 FixedPoint<I, F> lhs, FixedPoint<I, F> rhs,
221 typename std::enable_if<type_from_size<I + F>::next_size::is_specialized>::type* = nullptr) {
222
223 using next_type = typename FixedPoint<I, F>::next_type;
224 using base_type = typename FixedPoint<I, F>::base_type;
225
226 constexpr size_t fractional_bits = FixedPoint<I, F>::fractional_bits;
227
228 next_type t(static_cast<next_type>(lhs.to_raw()) * static_cast<next_type>(rhs.to_raw()));
229 t >>= fractional_bits;
230
231 return FixedPoint<I, F>::from_base(next_to_base<base_type>(t));
232}
233
234// this is the fall back version we use when we don't have a next size
235// it is slightly slower, but is more robust since it doesn't
236// require and upgraded type
237template <size_t I, size_t F>
238CONSTEXPR14 FixedPoint<I, F> multiply(
239 FixedPoint<I, F> lhs, FixedPoint<I, F> rhs,
240 typename std::enable_if<!type_from_size<I + F>::next_size::is_specialized>::type* = nullptr) {
241
242 using base_type = typename FixedPoint<I, F>::base_type;
243
244 constexpr size_t fractional_bits = FixedPoint<I, F>::fractional_bits;
245 constexpr base_type integer_mask = FixedPoint<I, F>::integer_mask;
246 constexpr base_type fractional_mask = FixedPoint<I, F>::fractional_mask;
247
248 // more costly but doesn't need a larger type
249 const base_type a_hi = (lhs.to_raw() & integer_mask) >> fractional_bits;
250 const base_type b_hi = (rhs.to_raw() & integer_mask) >> fractional_bits;
251 const base_type a_lo = (lhs.to_raw() & fractional_mask);
252 const base_type b_lo = (rhs.to_raw() & fractional_mask);
253
254 const base_type x1 = a_hi * b_hi;
255 const base_type x2 = a_hi * b_lo;
256 const base_type x3 = a_lo * b_hi;
257 const base_type x4 = a_lo * b_lo;
258
259 return FixedPoint<I, F>::from_base((x1 << fractional_bits) + (x3 + x2) +
260 (x4 >> fractional_bits));
261}
262} // namespace detail
263
264template <size_t I, size_t F>
265class FixedPoint {
266 static_assert(detail::type_from_size<I + F>::is_specialized, "invalid combination of sizes");
267
268public:
269 static constexpr size_t fractional_bits = F;
270 static constexpr size_t integer_bits = I;
271 static constexpr size_t total_bits = I + F;
272
273 using base_type_info = detail::type_from_size<total_bits>;
274
275 using base_type = typename base_type_info::value_type;
276 using next_type = typename base_type_info::next_size::value_type;
277 using unsigned_type = typename base_type_info::unsigned_type;
278
279public:
280#ifdef __GNUC__
281#pragma GCC diagnostic push
282#pragma GCC diagnostic ignored "-Woverflow"
283#endif
284 static constexpr base_type fractional_mask =
285 ~(static_cast<unsigned_type>(~base_type(0)) << fractional_bits);
286 static constexpr base_type integer_mask = ~fractional_mask;
287#ifdef __GNUC__
288#pragma GCC diagnostic pop
289#endif
290
291public:
292 static constexpr base_type one = base_type(1) << fractional_bits;
293
294public: // constructors
295 FixedPoint() = default;
296 FixedPoint(const FixedPoint&) = default;
297 FixedPoint(FixedPoint&&) = default;
298 FixedPoint& operator=(const FixedPoint&) = default;
299
300 template <class Number>
301 constexpr FixedPoint(
302 Number n, typename std::enable_if<std::is_arithmetic<Number>::value>::type* = nullptr)
303 : data_(static_cast<base_type>(n * one)) {}
304
305public: // conversion
306 template <size_t I2, size_t F2>
307 CONSTEXPR14 explicit FixedPoint(FixedPoint<I2, F2> other) {
308 static_assert(I2 <= I && F2 <= F, "Scaling conversion can only upgrade types");
309 using T = FixedPoint<I2, F2>;
310
311 const base_type fractional = (other.data_ & T::fractional_mask);
312 const base_type integer = (other.data_ & T::integer_mask) >> T::fractional_bits;
313 data_ =
314 (integer << fractional_bits) | (fractional << (fractional_bits - T::fractional_bits));
315 }
316
317private:
318 // this makes it simpler to create a FixedPoint point object from
319 // a native type without scaling
320 // use "FixedPoint::from_base" in order to perform this.
321 struct NoScale {};
322
323 constexpr FixedPoint(base_type n, const NoScale&) : data_(n) {}
324
325public:
326 static constexpr FixedPoint from_base(base_type n) {
327 return FixedPoint(n, NoScale());
328 }
329
330public: // comparison operators
331 constexpr bool operator==(FixedPoint rhs) const {
332 return data_ == rhs.data_;
333 }
334
335 constexpr bool operator!=(FixedPoint rhs) const {
336 return data_ != rhs.data_;
337 }
338
339 constexpr bool operator<(FixedPoint rhs) const {
340 return data_ < rhs.data_;
341 }
342
343 constexpr bool operator>(FixedPoint rhs) const {
344 return data_ > rhs.data_;
345 }
346
347 constexpr bool operator<=(FixedPoint rhs) const {
348 return data_ <= rhs.data_;
349 }
350
351 constexpr bool operator>=(FixedPoint rhs) const {
352 return data_ >= rhs.data_;
353 }
354
355public: // unary operators
356 constexpr bool operator!() const {
357 return !data_;
358 }
359
360 constexpr FixedPoint operator~() const {
361 // NOTE(eteran): this will often appear to "just negate" the value
362 // that is not an error, it is because -x == (~x+1)
363 // and that "+1" is adding an infinitesimally small fraction to the
364 // complimented value
365 return FixedPoint::from_base(~data_);
366 }
367
368 constexpr FixedPoint operator-() const {
369 return FixedPoint::from_base(-data_);
370 }
371
372 constexpr FixedPoint operator+() const {
373 return FixedPoint::from_base(+data_);
374 }
375
376 CONSTEXPR14 FixedPoint& operator++() {
377 data_ += one;
378 return *this;
379 }
380
381 CONSTEXPR14 FixedPoint& operator--() {
382 data_ -= one;
383 return *this;
384 }
385
386 CONSTEXPR14 FixedPoint operator++(int) {
387 FixedPoint tmp(*this);
388 data_ += one;
389 return tmp;
390 }
391
392 CONSTEXPR14 FixedPoint operator--(int) {
393 FixedPoint tmp(*this);
394 data_ -= one;
395 return tmp;
396 }
397
398public: // basic math operators
399 CONSTEXPR14 FixedPoint& operator+=(FixedPoint n) {
400 data_ += n.data_;
401 return *this;
402 }
403
404 CONSTEXPR14 FixedPoint& operator-=(FixedPoint n) {
405 data_ -= n.data_;
406 return *this;
407 }
408
409 CONSTEXPR14 FixedPoint& operator*=(FixedPoint n) {
410 return assign(detail::multiply(*this, n));
411 }
412
413 CONSTEXPR14 FixedPoint& operator/=(FixedPoint n) {
414 FixedPoint temp;
415 return assign(detail::divide(*this, n, temp));
416 }
417
418private:
419 CONSTEXPR14 FixedPoint& assign(FixedPoint rhs) {
420 data_ = rhs.data_;
421 return *this;
422 }
423
424public: // binary math operators, effects underlying bit pattern since these
425 // don't really typically make sense for non-integer values
426 CONSTEXPR14 FixedPoint& operator&=(FixedPoint n) {
427 data_ &= n.data_;
428 return *this;
429 }
430
431 CONSTEXPR14 FixedPoint& operator|=(FixedPoint n) {
432 data_ |= n.data_;
433 return *this;
434 }
435
436 CONSTEXPR14 FixedPoint& operator^=(FixedPoint n) {
437 data_ ^= n.data_;
438 return *this;
439 }
440
441 template <class Integer,
442 class = typename std::enable_if<std::is_integral<Integer>::value>::type>
443 CONSTEXPR14 FixedPoint& operator>>=(Integer n) {
444 data_ >>= n;
445 return *this;
446 }
447
448 template <class Integer,
449 class = typename std::enable_if<std::is_integral<Integer>::value>::type>
450 CONSTEXPR14 FixedPoint& operator<<=(Integer n) {
451 data_ <<= n;
452 return *this;
453 }
454
455public: // conversion to basic types
456 constexpr void round_up() {
457 data_ += (data_ & fractional_mask) >> 1;
458 }
459
460 constexpr int to_int() {
461 round_up();
462 return static_cast<int>((data_ & integer_mask) >> fractional_bits);
463 }
464
465 constexpr unsigned int to_uint() const {
466 round_up();
467 return static_cast<unsigned int>((data_ & integer_mask) >> fractional_bits);
468 }
469
470 constexpr int64_t to_long() {
471 round_up();
472 return static_cast<int64_t>((data_ & integer_mask) >> fractional_bits);
473 }
474
475 constexpr int to_int_floor() const {
476 return static_cast<int>((data_ & integer_mask) >> fractional_bits);
477 }
478
479 constexpr int64_t to_long_floor() {
480 return static_cast<int64_t>((data_ & integer_mask) >> fractional_bits);
481 }
482
483 constexpr unsigned int to_uint_floor() const {
484 return static_cast<unsigned int>((data_ & integer_mask) >> fractional_bits);
485 }
486
487 constexpr float to_float() const {
488 return static_cast<float>(data_) / FixedPoint::one;
489 }
490
491 constexpr double to_double() const {
492 return static_cast<double>(data_) / FixedPoint::one;
493 }
494
495 constexpr base_type to_raw() const {
496 return data_;
497 }
498
499 constexpr void clear_int() {
500 data_ &= fractional_mask;
501 }
502
503 constexpr base_type get_frac() const {
504 return data_ & fractional_mask;
505 }
506
507public:
508 CONSTEXPR14 void swap(FixedPoint& rhs) {
509 using std::swap;
510 swap(data_, rhs.data_);
511 }
512
513public:
514 base_type data_;
515};
516
517// if we have the same fractional portion, but differing integer portions, we trivially upgrade the
518// smaller type
519template <size_t I1, size_t I2, size_t F>
520CONSTEXPR14 typename std::conditional<I1 >= I2, FixedPoint<I1, F>, FixedPoint<I2, F>>::type
521operator+(FixedPoint<I1, F> lhs, FixedPoint<I2, F> rhs) {
522
523 using T = typename std::conditional<I1 >= I2, FixedPoint<I1, F>, FixedPoint<I2, F>>::type;
524
525 const T l = T::from_base(lhs.to_raw());
526 const T r = T::from_base(rhs.to_raw());
527 return l + r;
528}
529
530template <size_t I1, size_t I2, size_t F>
531CONSTEXPR14 typename std::conditional<I1 >= I2, FixedPoint<I1, F>, FixedPoint<I2, F>>::type
532operator-(FixedPoint<I1, F> lhs, FixedPoint<I2, F> rhs) {
533
534 using T = typename std::conditional<I1 >= I2, FixedPoint<I1, F>, FixedPoint<I2, F>>::type;
535
536 const T l = T::from_base(lhs.to_raw());
537 const T r = T::from_base(rhs.to_raw());
538 return l - r;
539}
540
541template <size_t I1, size_t I2, size_t F>
542CONSTEXPR14 typename std::conditional<I1 >= I2, FixedPoint<I1, F>, FixedPoint<I2, F>>::type
543operator*(FixedPoint<I1, F> lhs, FixedPoint<I2, F> rhs) {
544
545 using T = typename std::conditional<I1 >= I2, FixedPoint<I1, F>, FixedPoint<I2, F>>::type;
546
547 const T l = T::from_base(lhs.to_raw());
548 const T r = T::from_base(rhs.to_raw());
549 return l * r;
550}
551
552template <size_t I1, size_t I2, size_t F>
553CONSTEXPR14 typename std::conditional<I1 >= I2, FixedPoint<I1, F>, FixedPoint<I2, F>>::type
554operator/(FixedPoint<I1, F> lhs, FixedPoint<I2, F> rhs) {
555
556 using T = typename std::conditional<I1 >= I2, FixedPoint<I1, F>, FixedPoint<I2, F>>::type;
557
558 const T l = T::from_base(lhs.to_raw());
559 const T r = T::from_base(rhs.to_raw());
560 return l / r;
561}
562
563template <size_t I, size_t F>
564std::ostream& operator<<(std::ostream& os, FixedPoint<I, F> f) {
565 os << f.to_double();
566 return os;
567}
568
569// basic math operators
570template <size_t I, size_t F>
571CONSTEXPR14 FixedPoint<I, F> operator+(FixedPoint<I, F> lhs, FixedPoint<I, F> rhs) {
572 lhs += rhs;
573 return lhs;
574}
575template <size_t I, size_t F>
576CONSTEXPR14 FixedPoint<I, F> operator-(FixedPoint<I, F> lhs, FixedPoint<I, F> rhs) {
577 lhs -= rhs;
578 return lhs;
579}
580template <size_t I, size_t F>
581CONSTEXPR14 FixedPoint<I, F> operator*(FixedPoint<I, F> lhs, FixedPoint<I, F> rhs) {
582 lhs *= rhs;
583 return lhs;
584}
585template <size_t I, size_t F>
586CONSTEXPR14 FixedPoint<I, F> operator/(FixedPoint<I, F> lhs, FixedPoint<I, F> rhs) {
587 lhs /= rhs;
588 return lhs;
589}
590
591template <size_t I, size_t F, class Number,
592 class = typename std::enable_if<std::is_arithmetic<Number>::value>::type>
593CONSTEXPR14 FixedPoint<I, F> operator+(FixedPoint<I, F> lhs, Number rhs) {
594 lhs += FixedPoint<I, F>(rhs);
595 return lhs;
596}
597template <size_t I, size_t F, class Number,
598 class = typename std::enable_if<std::is_arithmetic<Number>::value>::type>
599CONSTEXPR14 FixedPoint<I, F> operator-(FixedPoint<I, F> lhs, Number rhs) {
600 lhs -= FixedPoint<I, F>(rhs);
601 return lhs;
602}
603template <size_t I, size_t F, class Number,
604 class = typename std::enable_if<std::is_arithmetic<Number>::value>::type>
605CONSTEXPR14 FixedPoint<I, F> operator*(FixedPoint<I, F> lhs, Number rhs) {
606 lhs *= FixedPoint<I, F>(rhs);
607 return lhs;
608}
609template <size_t I, size_t F, class Number,
610 class = typename std::enable_if<std::is_arithmetic<Number>::value>::type>
611CONSTEXPR14 FixedPoint<I, F> operator/(FixedPoint<I, F> lhs, Number rhs) {
612 lhs /= FixedPoint<I, F>(rhs);
613 return lhs;
614}
615
616template <size_t I, size_t F, class Number,
617 class = typename std::enable_if<std::is_arithmetic<Number>::value>::type>
618CONSTEXPR14 FixedPoint<I, F> operator+(Number lhs, FixedPoint<I, F> rhs) {
619 FixedPoint<I, F> tmp(lhs);
620 tmp += rhs;
621 return tmp;
622}
623template <size_t I, size_t F, class Number,
624 class = typename std::enable_if<std::is_arithmetic<Number>::value>::type>
625CONSTEXPR14 FixedPoint<I, F> operator-(Number lhs, FixedPoint<I, F> rhs) {
626 FixedPoint<I, F> tmp(lhs);
627 tmp -= rhs;
628 return tmp;
629}
630template <size_t I, size_t F, class Number,
631 class = typename std::enable_if<std::is_arithmetic<Number>::value>::type>
632CONSTEXPR14 FixedPoint<I, F> operator*(Number lhs, FixedPoint<I, F> rhs) {
633 FixedPoint<I, F> tmp(lhs);
634 tmp *= rhs;
635 return tmp;
636}
637template <size_t I, size_t F, class Number,
638 class = typename std::enable_if<std::is_arithmetic<Number>::value>::type>
639CONSTEXPR14 FixedPoint<I, F> operator/(Number lhs, FixedPoint<I, F> rhs) {
640 FixedPoint<I, F> tmp(lhs);
641 tmp /= rhs;
642 return tmp;
643}
644
645// shift operators
646template <size_t I, size_t F, class Integer,
647 class = typename std::enable_if<std::is_integral<Integer>::value>::type>
648CONSTEXPR14 FixedPoint<I, F> operator<<(FixedPoint<I, F> lhs, Integer rhs) {
649 lhs <<= rhs;
650 return lhs;
651}
652template <size_t I, size_t F, class Integer,
653 class = typename std::enable_if<std::is_integral<Integer>::value>::type>
654CONSTEXPR14 FixedPoint<I, F> operator>>(FixedPoint<I, F> lhs, Integer rhs) {
655 lhs >>= rhs;
656 return lhs;
657}
658
659// comparison operators
660template <size_t I, size_t F, class Number,
661 class = typename std::enable_if<std::is_arithmetic<Number>::value>::type>
662constexpr bool operator>(FixedPoint<I, F> lhs, Number rhs) {
663 return lhs > FixedPoint<I, F>(rhs);
664}
665template <size_t I, size_t F, class Number,
666 class = typename std::enable_if<std::is_arithmetic<Number>::value>::type>
667constexpr bool operator<(FixedPoint<I, F> lhs, Number rhs) {
668 return lhs < FixedPoint<I, F>(rhs);
669}
670template <size_t I, size_t F, class Number,
671 class = typename std::enable_if<std::is_arithmetic<Number>::value>::type>
672constexpr bool operator>=(FixedPoint<I, F> lhs, Number rhs) {
673 return lhs >= FixedPoint<I, F>(rhs);
674}
675template <size_t I, size_t F, class Number,
676 class = typename std::enable_if<std::is_arithmetic<Number>::value>::type>
677constexpr bool operator<=(FixedPoint<I, F> lhs, Number rhs) {
678 return lhs <= FixedPoint<I, F>(rhs);
679}
680template <size_t I, size_t F, class Number,
681 class = typename std::enable_if<std::is_arithmetic<Number>::value>::type>
682constexpr bool operator==(FixedPoint<I, F> lhs, Number rhs) {
683 return lhs == FixedPoint<I, F>(rhs);
684}
685template <size_t I, size_t F, class Number,
686 class = typename std::enable_if<std::is_arithmetic<Number>::value>::type>
687constexpr bool operator!=(FixedPoint<I, F> lhs, Number rhs) {
688 return lhs != FixedPoint<I, F>(rhs);
689}
690
691template <size_t I, size_t F, class Number,
692 class = typename std::enable_if<std::is_arithmetic<Number>::value>::type>
693constexpr bool operator>(Number lhs, FixedPoint<I, F> rhs) {
694 return FixedPoint<I, F>(lhs) > rhs;
695}
696template <size_t I, size_t F, class Number,
697 class = typename std::enable_if<std::is_arithmetic<Number>::value>::type>
698constexpr bool operator<(Number lhs, FixedPoint<I, F> rhs) {
699 return FixedPoint<I, F>(lhs) < rhs;
700}
701template <size_t I, size_t F, class Number,
702 class = typename std::enable_if<std::is_arithmetic<Number>::value>::type>
703constexpr bool operator>=(Number lhs, FixedPoint<I, F> rhs) {
704 return FixedPoint<I, F>(lhs) >= rhs;
705}
706template <size_t I, size_t F, class Number,
707 class = typename std::enable_if<std::is_arithmetic<Number>::value>::type>
708constexpr bool operator<=(Number lhs, FixedPoint<I, F> rhs) {
709 return FixedPoint<I, F>(lhs) <= rhs;
710}
711template <size_t I, size_t F, class Number,
712 class = typename std::enable_if<std::is_arithmetic<Number>::value>::type>
713constexpr bool operator==(Number lhs, FixedPoint<I, F> rhs) {
714 return FixedPoint<I, F>(lhs) == rhs;
715}
716template <size_t I, size_t F, class Number,
717 class = typename std::enable_if<std::is_arithmetic<Number>::value>::type>
718constexpr bool operator!=(Number lhs, FixedPoint<I, F> rhs) {
719 return FixedPoint<I, F>(lhs) != rhs;
720}
721
722} // namespace Common
723
724#undef CONSTEXPR14
725
726#endif
diff --git a/src/common/reader_writer_queue.h b/src/common/reader_writer_queue.h
new file mode 100644
index 000000000..8d2c9408c
--- /dev/null
+++ b/src/common/reader_writer_queue.h
@@ -0,0 +1,941 @@
1// ©2013-2020 Cameron Desrochers.
2// Distributed under the simplified BSD license (see the license file that
3// should have come with this header).
4
5#pragma once
6
7#include <cassert>
8#include <cstdint>
9#include <cstdlib> // For malloc/free/abort & size_t
10#include <memory>
11#include <new>
12#include <stdexcept>
13#include <type_traits>
14#include <utility>
15
16#include "common/atomic_helpers.h"
17
18#if __cplusplus > 199711L || _MSC_VER >= 1700 // C++11 or VS2012
19#include <chrono>
20#endif
21
22// A lock-free queue for a single-consumer, single-producer architecture.
23// The queue is also wait-free in the common path (except if more memory
24// needs to be allocated, in which case malloc is called).
25// Allocates memory sparingly, and only once if the original maximum size
26// estimate is never exceeded.
27// Tested on x86/x64 processors, but semantics should be correct for all
28// architectures (given the right implementations in atomicops.h), provided
29// that aligned integer and pointer accesses are naturally atomic.
30// Note that there should only be one consumer thread and producer thread;
31// Switching roles of the threads, or using multiple consecutive threads for
32// one role, is not safe unless properly synchronized.
33// Using the queue exclusively from one thread is fine, though a bit silly.
34
35#ifndef MOODYCAMEL_CACHE_LINE_SIZE
36#define MOODYCAMEL_CACHE_LINE_SIZE 64
37#endif
38
39#ifndef MOODYCAMEL_EXCEPTIONS_ENABLED
40#if (defined(_MSC_VER) && defined(_CPPUNWIND)) || (defined(__GNUC__) && defined(__EXCEPTIONS)) || \
41 (!defined(_MSC_VER) && !defined(__GNUC__))
42#define MOODYCAMEL_EXCEPTIONS_ENABLED
43#endif
44#endif
45
46#ifndef MOODYCAMEL_HAS_EMPLACE
47#if !defined(_MSC_VER) || \
48 _MSC_VER >= 1800 // variadic templates: either a non-MS compiler or VS >= 2013
49#define MOODYCAMEL_HAS_EMPLACE 1
50#endif
51#endif
52
53#ifndef MOODYCAMEL_MAYBE_ALIGN_TO_CACHELINE
54#if defined(__APPLE__) && defined(__MACH__) && __cplusplus >= 201703L
55// This is required to find out what deployment target we are using
56#include <CoreFoundation/CoreFoundation.h>
57#if !defined(MAC_OS_X_VERSION_MIN_REQUIRED) || \
58 MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_14
59// C++17 new(size_t, align_val_t) is not backwards-compatible with older versions of macOS, so we
60// can't support over-alignment in this case
61#define MOODYCAMEL_MAYBE_ALIGN_TO_CACHELINE
62#endif
63#endif
64#endif
65
66#ifndef MOODYCAMEL_MAYBE_ALIGN_TO_CACHELINE
67#define MOODYCAMEL_MAYBE_ALIGN_TO_CACHELINE AE_ALIGN(MOODYCAMEL_CACHE_LINE_SIZE)
68#endif
69
70#ifdef AE_VCPP
71#pragma warning(push)
72#pragma warning(disable : 4324) // structure was padded due to __declspec(align())
73#pragma warning(disable : 4820) // padding was added
74#pragma warning(disable : 4127) // conditional expression is constant
75#endif
76
77namespace Common {
78
79template <typename T, size_t MAX_BLOCK_SIZE = 512>
80class MOODYCAMEL_MAYBE_ALIGN_TO_CACHELINE ReaderWriterQueue {
81 // Design: Based on a queue-of-queues. The low-level queues are just
82 // circular buffers with front and tail indices indicating where the
83 // next element to dequeue is and where the next element can be enqueued,
84 // respectively. Each low-level queue is called a "block". Each block
85 // wastes exactly one element's worth of space to keep the design simple
86 // (if front == tail then the queue is empty, and can't be full).
87 // The high-level queue is a circular linked list of blocks; again there
88 // is a front and tail, but this time they are pointers to the blocks.
89 // The front block is where the next element to be dequeued is, provided
90 // the block is not empty. The back block is where elements are to be
91 // enqueued, provided the block is not full.
92 // The producer thread owns all the tail indices/pointers. The consumer
93 // thread owns all the front indices/pointers. Both threads read each
94 // other's variables, but only the owning thread updates them. E.g. After
95 // the consumer reads the producer's tail, the tail may change before the
96 // consumer is done dequeuing an object, but the consumer knows the tail
97 // will never go backwards, only forwards.
98 // If there is no room to enqueue an object, an additional block (of
99 // equal size to the last block) is added. Blocks are never removed.
100
101public:
102 typedef T value_type;
103
104 // Constructs a queue that can hold at least `size` elements without further
105 // allocations. If more than MAX_BLOCK_SIZE elements are requested,
106 // then several blocks of MAX_BLOCK_SIZE each are reserved (including
107 // at least one extra buffer block).
108 AE_NO_TSAN explicit ReaderWriterQueue(size_t size = 15)
109#ifndef NDEBUG
110 : enqueuing(false), dequeuing(false)
111#endif
112 {
113 assert(MAX_BLOCK_SIZE == ceilToPow2(MAX_BLOCK_SIZE) &&
114 "MAX_BLOCK_SIZE must be a power of 2");
115 assert(MAX_BLOCK_SIZE >= 2 && "MAX_BLOCK_SIZE must be at least 2");
116
117 Block* firstBlock = nullptr;
118
119 largestBlockSize =
120 ceilToPow2(size + 1); // We need a spare slot to fit size elements in the block
121 if (largestBlockSize > MAX_BLOCK_SIZE * 2) {
122 // We need a spare block in case the producer is writing to a different block the
123 // consumer is reading from, and wants to enqueue the maximum number of elements. We
124 // also need a spare element in each block to avoid the ambiguity between front == tail
125 // meaning "empty" and "full". So the effective number of slots that are guaranteed to
126 // be usable at any time is the block size - 1 times the number of blocks - 1. Solving
127 // for size and applying a ceiling to the division gives us (after simplifying):
128 size_t initialBlockCount = (size + MAX_BLOCK_SIZE * 2 - 3) / (MAX_BLOCK_SIZE - 1);
129 largestBlockSize = MAX_BLOCK_SIZE;
130 Block* lastBlock = nullptr;
131 for (size_t i = 0; i != initialBlockCount; ++i) {
132 auto block = make_block(largestBlockSize);
133 if (block == nullptr) {
134#ifdef MOODYCAMEL_EXCEPTIONS_ENABLED
135 throw std::bad_alloc();
136#else
137 abort();
138#endif
139 }
140 if (firstBlock == nullptr) {
141 firstBlock = block;
142 } else {
143 lastBlock->next = block;
144 }
145 lastBlock = block;
146 block->next = firstBlock;
147 }
148 } else {
149 firstBlock = make_block(largestBlockSize);
150 if (firstBlock == nullptr) {
151#ifdef MOODYCAMEL_EXCEPTIONS_ENABLED
152 throw std::bad_alloc();
153#else
154 abort();
155#endif
156 }
157 firstBlock->next = firstBlock;
158 }
159 frontBlock = firstBlock;
160 tailBlock = firstBlock;
161
162 // Make sure the reader/writer threads will have the initialized memory setup above:
163 fence(memory_order_sync);
164 }
165
166 // Note: The queue should not be accessed concurrently while it's
167 // being moved. It's up to the user to synchronize this.
168 AE_NO_TSAN ReaderWriterQueue(ReaderWriterQueue&& other)
169 : frontBlock(other.frontBlock.load()), tailBlock(other.tailBlock.load()),
170 largestBlockSize(other.largestBlockSize)
171#ifndef NDEBUG
172 ,
173 enqueuing(false), dequeuing(false)
174#endif
175 {
176 other.largestBlockSize = 32;
177 Block* b = other.make_block(other.largestBlockSize);
178 if (b == nullptr) {
179#ifdef MOODYCAMEL_EXCEPTIONS_ENABLED
180 throw std::bad_alloc();
181#else
182 abort();
183#endif
184 }
185 b->next = b;
186 other.frontBlock = b;
187 other.tailBlock = b;
188 }
189
190 // Note: The queue should not be accessed concurrently while it's
191 // being moved. It's up to the user to synchronize this.
192 ReaderWriterQueue& operator=(ReaderWriterQueue&& other) AE_NO_TSAN {
193 Block* b = frontBlock.load();
194 frontBlock = other.frontBlock.load();
195 other.frontBlock = b;
196 b = tailBlock.load();
197 tailBlock = other.tailBlock.load();
198 other.tailBlock = b;
199 std::swap(largestBlockSize, other.largestBlockSize);
200 return *this;
201 }
202
203 // Note: The queue should not be accessed concurrently while it's
204 // being deleted. It's up to the user to synchronize this.
205 AE_NO_TSAN ~ReaderWriterQueue() {
206 // Make sure we get the latest version of all variables from other CPUs:
207 fence(memory_order_sync);
208
209 // Destroy any remaining objects in queue and free memory
210 Block* frontBlock_ = frontBlock;
211 Block* block = frontBlock_;
212 do {
213 Block* nextBlock = block->next;
214 size_t blockFront = block->front;
215 size_t blockTail = block->tail;
216
217 for (size_t i = blockFront; i != blockTail; i = (i + 1) & block->sizeMask) {
218 auto element = reinterpret_cast<T*>(block->data + i * sizeof(T));
219 element->~T();
220 (void)element;
221 }
222
223 auto rawBlock = block->rawThis;
224 block->~Block();
225 std::free(rawBlock);
226 block = nextBlock;
227 } while (block != frontBlock_);
228 }
229
230 // Enqueues a copy of element if there is room in the queue.
231 // Returns true if the element was enqueued, false otherwise.
232 // Does not allocate memory.
233 AE_FORCEINLINE bool try_enqueue(T const& element) AE_NO_TSAN {
234 return inner_enqueue<CannotAlloc>(element);
235 }
236
237 // Enqueues a moved copy of element if there is room in the queue.
238 // Returns true if the element was enqueued, false otherwise.
239 // Does not allocate memory.
240 AE_FORCEINLINE bool try_enqueue(T&& element) AE_NO_TSAN {
241 return inner_enqueue<CannotAlloc>(std::forward<T>(element));
242 }
243
244#if MOODYCAMEL_HAS_EMPLACE
245 // Like try_enqueue() but with emplace semantics (i.e. construct-in-place).
246 template <typename... Args>
247 AE_FORCEINLINE bool try_emplace(Args&&... args) AE_NO_TSAN {
248 return inner_enqueue<CannotAlloc>(std::forward<Args>(args)...);
249 }
250#endif
251
252 // Enqueues a copy of element on the queue.
253 // Allocates an additional block of memory if needed.
254 // Only fails (returns false) if memory allocation fails.
255 AE_FORCEINLINE bool enqueue(T const& element) AE_NO_TSAN {
256 return inner_enqueue<CanAlloc>(element);
257 }
258
259 // Enqueues a moved copy of element on the queue.
260 // Allocates an additional block of memory if needed.
261 // Only fails (returns false) if memory allocation fails.
262 AE_FORCEINLINE bool enqueue(T&& element) AE_NO_TSAN {
263 return inner_enqueue<CanAlloc>(std::forward<T>(element));
264 }
265
266#if MOODYCAMEL_HAS_EMPLACE
267 // Like enqueue() but with emplace semantics (i.e. construct-in-place).
268 template <typename... Args>
269 AE_FORCEINLINE bool emplace(Args&&... args) AE_NO_TSAN {
270 return inner_enqueue<CanAlloc>(std::forward<Args>(args)...);
271 }
272#endif
273
274 // Attempts to dequeue an element; if the queue is empty,
275 // returns false instead. If the queue has at least one element,
276 // moves front to result using operator=, then returns true.
277 template <typename U>
278 bool try_dequeue(U& result) AE_NO_TSAN {
279#ifndef NDEBUG
280 ReentrantGuard guard(this->dequeuing);
281#endif
282
283 // High-level pseudocode:
284 // Remember where the tail block is
285 // If the front block has an element in it, dequeue it
286 // Else
287 // If front block was the tail block when we entered the function, return false
288 // Else advance to next block and dequeue the item there
289
290 // Note that we have to use the value of the tail block from before we check if the front
291 // block is full or not, in case the front block is empty and then, before we check if the
292 // tail block is at the front block or not, the producer fills up the front block *and
293 // moves on*, which would make us skip a filled block. Seems unlikely, but was consistently
294 // reproducible in practice.
295 // In order to avoid overhead in the common case, though, we do a double-checked pattern
296 // where we have the fast path if the front block is not empty, then read the tail block,
297 // then re-read the front block and check if it's not empty again, then check if the tail
298 // block has advanced.
299
300 Block* frontBlock_ = frontBlock.load();
301 size_t blockTail = frontBlock_->localTail;
302 size_t blockFront = frontBlock_->front.load();
303
304 if (blockFront != blockTail ||
305 blockFront != (frontBlock_->localTail = frontBlock_->tail.load())) {
306 fence(memory_order_acquire);
307
308 non_empty_front_block:
309 // Front block not empty, dequeue from here
310 auto element = reinterpret_cast<T*>(frontBlock_->data + blockFront * sizeof(T));
311 result = std::move(*element);
312 element->~T();
313
314 blockFront = (blockFront + 1) & frontBlock_->sizeMask;
315
316 fence(memory_order_release);
317 frontBlock_->front = blockFront;
318 } else if (frontBlock_ != tailBlock.load()) {
319 fence(memory_order_acquire);
320
321 frontBlock_ = frontBlock.load();
322 blockTail = frontBlock_->localTail = frontBlock_->tail.load();
323 blockFront = frontBlock_->front.load();
324 fence(memory_order_acquire);
325
326 if (blockFront != blockTail) {
327 // Oh look, the front block isn't empty after all
328 goto non_empty_front_block;
329 }
330
331 // Front block is empty but there's another block ahead, advance to it
332 Block* nextBlock = frontBlock_->next;
333 // Don't need an acquire fence here since next can only ever be set on the tailBlock,
334 // and we're not the tailBlock, and we did an acquire earlier after reading tailBlock
335 // which ensures next is up-to-date on this CPU in case we recently were at tailBlock.
336
337 size_t nextBlockFront = nextBlock->front.load();
338 size_t nextBlockTail = nextBlock->localTail = nextBlock->tail.load();
339 fence(memory_order_acquire);
340
341 // Since the tailBlock is only ever advanced after being written to,
342 // we know there's for sure an element to dequeue on it
343 assert(nextBlockFront != nextBlockTail);
344 AE_UNUSED(nextBlockTail);
345
346 // We're done with this block, let the producer use it if it needs
347 fence(memory_order_release); // Expose possibly pending changes to frontBlock->front
348 // from last dequeue
349 frontBlock = frontBlock_ = nextBlock;
350
351 compiler_fence(memory_order_release); // Not strictly needed
352
353 auto element = reinterpret_cast<T*>(frontBlock_->data + nextBlockFront * sizeof(T));
354
355 result = std::move(*element);
356 element->~T();
357
358 nextBlockFront = (nextBlockFront + 1) & frontBlock_->sizeMask;
359
360 fence(memory_order_release);
361 frontBlock_->front = nextBlockFront;
362 } else {
363 // No elements in current block and no other block to advance to
364 return false;
365 }
366
367 return true;
368 }
369
370 // Returns a pointer to the front element in the queue (the one that
371 // would be removed next by a call to `try_dequeue` or `pop`). If the
372 // queue appears empty at the time the method is called, nullptr is
373 // returned instead.
374 // Must be called only from the consumer thread.
375 T* peek() const AE_NO_TSAN {
376#ifndef NDEBUG
377 ReentrantGuard guard(this->dequeuing);
378#endif
379 // See try_dequeue() for reasoning
380
381 Block* frontBlock_ = frontBlock.load();
382 size_t blockTail = frontBlock_->localTail;
383 size_t blockFront = frontBlock_->front.load();
384
385 if (blockFront != blockTail ||
386 blockFront != (frontBlock_->localTail = frontBlock_->tail.load())) {
387 fence(memory_order_acquire);
388 non_empty_front_block:
389 return reinterpret_cast<T*>(frontBlock_->data + blockFront * sizeof(T));
390 } else if (frontBlock_ != tailBlock.load()) {
391 fence(memory_order_acquire);
392 frontBlock_ = frontBlock.load();
393 blockTail = frontBlock_->localTail = frontBlock_->tail.load();
394 blockFront = frontBlock_->front.load();
395 fence(memory_order_acquire);
396
397 if (blockFront != blockTail) {
398 goto non_empty_front_block;
399 }
400
401 Block* nextBlock = frontBlock_->next;
402
403 size_t nextBlockFront = nextBlock->front.load();
404 fence(memory_order_acquire);
405
406 assert(nextBlockFront != nextBlock->tail.load());
407 return reinterpret_cast<T*>(nextBlock->data + nextBlockFront * sizeof(T));
408 }
409
410 return nullptr;
411 }
412
413 // Removes the front element from the queue, if any, without returning it.
414 // Returns true on success, or false if the queue appeared empty at the time
415 // `pop` was called.
416 bool pop() AE_NO_TSAN {
417#ifndef NDEBUG
418 ReentrantGuard guard(this->dequeuing);
419#endif
420 // See try_dequeue() for reasoning
421
422 Block* frontBlock_ = frontBlock.load();
423 size_t blockTail = frontBlock_->localTail;
424 size_t blockFront = frontBlock_->front.load();
425
426 if (blockFront != blockTail ||
427 blockFront != (frontBlock_->localTail = frontBlock_->tail.load())) {
428 fence(memory_order_acquire);
429
430 non_empty_front_block:
431 auto element = reinterpret_cast<T*>(frontBlock_->data + blockFront * sizeof(T));
432 element->~T();
433
434 blockFront = (blockFront + 1) & frontBlock_->sizeMask;
435
436 fence(memory_order_release);
437 frontBlock_->front = blockFront;
438 } else if (frontBlock_ != tailBlock.load()) {
439 fence(memory_order_acquire);
440 frontBlock_ = frontBlock.load();
441 blockTail = frontBlock_->localTail = frontBlock_->tail.load();
442 blockFront = frontBlock_->front.load();
443 fence(memory_order_acquire);
444
445 if (blockFront != blockTail) {
446 goto non_empty_front_block;
447 }
448
449 // Front block is empty but there's another block ahead, advance to it
450 Block* nextBlock = frontBlock_->next;
451
452 size_t nextBlockFront = nextBlock->front.load();
453 size_t nextBlockTail = nextBlock->localTail = nextBlock->tail.load();
454 fence(memory_order_acquire);
455
456 assert(nextBlockFront != nextBlockTail);
457 AE_UNUSED(nextBlockTail);
458
459 fence(memory_order_release);
460 frontBlock = frontBlock_ = nextBlock;
461
462 compiler_fence(memory_order_release);
463
464 auto element = reinterpret_cast<T*>(frontBlock_->data + nextBlockFront * sizeof(T));
465 element->~T();
466
467 nextBlockFront = (nextBlockFront + 1) & frontBlock_->sizeMask;
468
469 fence(memory_order_release);
470 frontBlock_->front = nextBlockFront;
471 } else {
472 // No elements in current block and no other block to advance to
473 return false;
474 }
475
476 return true;
477 }
478
479 // Returns the approximate number of items currently in the queue.
480 // Safe to call from both the producer and consumer threads.
481 inline size_t size_approx() const AE_NO_TSAN {
482 size_t result = 0;
483 Block* frontBlock_ = frontBlock.load();
484 Block* block = frontBlock_;
485 do {
486 fence(memory_order_acquire);
487 size_t blockFront = block->front.load();
488 size_t blockTail = block->tail.load();
489 result += (blockTail - blockFront) & block->sizeMask;
490 block = block->next.load();
491 } while (block != frontBlock_);
492 return result;
493 }
494
495 // Returns the total number of items that could be enqueued without incurring
496 // an allocation when this queue is empty.
497 // Safe to call from both the producer and consumer threads.
498 //
499 // NOTE: The actual capacity during usage may be different depending on the consumer.
500 // If the consumer is removing elements concurrently, the producer cannot add to
501 // the block the consumer is removing from until it's completely empty, except in
502 // the case where the producer was writing to the same block the consumer was
503 // reading from the whole time.
504 inline size_t max_capacity() const {
505 size_t result = 0;
506 Block* frontBlock_ = frontBlock.load();
507 Block* block = frontBlock_;
508 do {
509 fence(memory_order_acquire);
510 result += block->sizeMask;
511 block = block->next.load();
512 } while (block != frontBlock_);
513 return result;
514 }
515
516private:
517 enum AllocationMode { CanAlloc, CannotAlloc };
518
519#if MOODYCAMEL_HAS_EMPLACE
520 template <AllocationMode canAlloc, typename... Args>
521 bool inner_enqueue(Args&&... args) AE_NO_TSAN
522#else
523 template <AllocationMode canAlloc, typename U>
524 bool inner_enqueue(U&& element) AE_NO_TSAN
525#endif
526 {
527#ifndef NDEBUG
528 ReentrantGuard guard(this->enqueuing);
529#endif
530
531 // High-level pseudocode (assuming we're allowed to alloc a new block):
532 // If room in tail block, add to tail
533 // Else check next block
534 // If next block is not the head block, enqueue on next block
535 // Else create a new block and enqueue there
536 // Advance tail to the block we just enqueued to
537
538 Block* tailBlock_ = tailBlock.load();
539 size_t blockFront = tailBlock_->localFront;
540 size_t blockTail = tailBlock_->tail.load();
541
542 size_t nextBlockTail = (blockTail + 1) & tailBlock_->sizeMask;
543 if (nextBlockTail != blockFront ||
544 nextBlockTail != (tailBlock_->localFront = tailBlock_->front.load())) {
545 fence(memory_order_acquire);
546 // This block has room for at least one more element
547 char* location = tailBlock_->data + blockTail * sizeof(T);
548#if MOODYCAMEL_HAS_EMPLACE
549 new (location) T(std::forward<Args>(args)...);
550#else
551 new (location) T(std::forward<U>(element));
552#endif
553
554 fence(memory_order_release);
555 tailBlock_->tail = nextBlockTail;
556 } else {
557 fence(memory_order_acquire);
558 if (tailBlock_->next.load() != frontBlock) {
559 // Note that the reason we can't advance to the frontBlock and start adding new
560 // entries there is because if we did, then dequeue would stay in that block,
561 // eventually reading the new values, instead of advancing to the next full block
562 // (whose values were enqueued first and so should be consumed first).
563
564 fence(memory_order_acquire); // Ensure we get latest writes if we got the latest
565 // frontBlock
566
567 // tailBlock is full, but there's a free block ahead, use it
568 Block* tailBlockNext = tailBlock_->next.load();
569 size_t nextBlockFront = tailBlockNext->localFront = tailBlockNext->front.load();
570 nextBlockTail = tailBlockNext->tail.load();
571 fence(memory_order_acquire);
572
573 // This block must be empty since it's not the head block and we
574 // go through the blocks in a circle
575 assert(nextBlockFront == nextBlockTail);
576 tailBlockNext->localFront = nextBlockFront;
577
578 char* location = tailBlockNext->data + nextBlockTail * sizeof(T);
579#if MOODYCAMEL_HAS_EMPLACE
580 new (location) T(std::forward<Args>(args)...);
581#else
582 new (location) T(std::forward<U>(element));
583#endif
584
585 tailBlockNext->tail = (nextBlockTail + 1) & tailBlockNext->sizeMask;
586
587 fence(memory_order_release);
588 tailBlock = tailBlockNext;
589 } else if (canAlloc == CanAlloc) {
590 // tailBlock is full and there's no free block ahead; create a new block
591 auto newBlockSize =
592 largestBlockSize >= MAX_BLOCK_SIZE ? largestBlockSize : largestBlockSize * 2;
593 auto newBlock = make_block(newBlockSize);
594 if (newBlock == nullptr) {
595 // Could not allocate a block!
596 return false;
597 }
598 largestBlockSize = newBlockSize;
599
600#if MOODYCAMEL_HAS_EMPLACE
601 new (newBlock->data) T(std::forward<Args>(args)...);
602#else
603 new (newBlock->data) T(std::forward<U>(element));
604#endif
605 assert(newBlock->front == 0);
606 newBlock->tail = newBlock->localTail = 1;
607
608 newBlock->next = tailBlock_->next.load();
609 tailBlock_->next = newBlock;
610
611 // Might be possible for the dequeue thread to see the new tailBlock->next
612 // *without* seeing the new tailBlock value, but this is OK since it can't
613 // advance to the next block until tailBlock is set anyway (because the only
614 // case where it could try to read the next is if it's already at the tailBlock,
615 // and it won't advance past tailBlock in any circumstance).
616
617 fence(memory_order_release);
618 tailBlock = newBlock;
619 } else if (canAlloc == CannotAlloc) {
620 // Would have had to allocate a new block to enqueue, but not allowed
621 return false;
622 } else {
623 assert(false && "Should be unreachable code");
624 return false;
625 }
626 }
627
628 return true;
629 }
630
631 // Disable copying
632 ReaderWriterQueue(ReaderWriterQueue const&) {}
633
634 // Disable assignment
635 ReaderWriterQueue& operator=(ReaderWriterQueue const&) {}
636
637 AE_FORCEINLINE static size_t ceilToPow2(size_t x) {
638 // From http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2
639 --x;
640 x |= x >> 1;
641 x |= x >> 2;
642 x |= x >> 4;
643 for (size_t i = 1; i < sizeof(size_t); i <<= 1) {
644 x |= x >> (i << 3);
645 }
646 ++x;
647 return x;
648 }
649
650 template <typename U>
651 static AE_FORCEINLINE char* align_for(char* ptr) AE_NO_TSAN {
652 const std::size_t alignment = std::alignment_of<U>::value;
653 return ptr + (alignment - (reinterpret_cast<std::uintptr_t>(ptr) % alignment)) % alignment;
654 }
655
656private:
657#ifndef NDEBUG
658 struct ReentrantGuard {
659 AE_NO_TSAN ReentrantGuard(weak_atomic<bool>& _inSection) : inSection(_inSection) {
660 assert(!inSection &&
661 "Concurrent (or re-entrant) enqueue or dequeue operation detected (only one "
662 "thread at a time may hold the producer or consumer role)");
663 inSection = true;
664 }
665
666 AE_NO_TSAN ~ReentrantGuard() {
667 inSection = false;
668 }
669
670 private:
671 ReentrantGuard& operator=(ReentrantGuard const&);
672
673 private:
674 weak_atomic<bool>& inSection;
675 };
676#endif
677
678 struct Block {
679 // Avoid false-sharing by putting highly contended variables on their own cache lines
680 weak_atomic<size_t> front; // (Atomic) Elements are read from here
681 size_t localTail; // An uncontended shadow copy of tail, owned by the consumer
682
683 char cachelineFiller0[MOODYCAMEL_CACHE_LINE_SIZE - sizeof(weak_atomic<size_t>) -
684 sizeof(size_t)];
685 weak_atomic<size_t> tail; // (Atomic) Elements are enqueued here
686 size_t localFront;
687
688 char cachelineFiller1[MOODYCAMEL_CACHE_LINE_SIZE - sizeof(weak_atomic<size_t>) -
689 sizeof(size_t)]; // next isn't very contended, but we don't want it on
690 // the same cache line as tail (which is)
691 weak_atomic<Block*> next; // (Atomic)
692
693 char* data; // Contents (on heap) are aligned to T's alignment
694
695 const size_t sizeMask;
696
697 // size must be a power of two (and greater than 0)
698 AE_NO_TSAN Block(size_t const& _size, char* _rawThis, char* _data)
699 : front(0UL), localTail(0), tail(0UL), localFront(0), next(nullptr), data(_data),
700 sizeMask(_size - 1), rawThis(_rawThis) {}
701
702 private:
703 // C4512 - Assignment operator could not be generated
704 Block& operator=(Block const&);
705
706 public:
707 char* rawThis;
708 };
709
710 static Block* make_block(size_t capacity) AE_NO_TSAN {
711 // Allocate enough memory for the block itself, as well as all the elements it will contain
712 auto size = sizeof(Block) + std::alignment_of<Block>::value - 1;
713 size += sizeof(T) * capacity + std::alignment_of<T>::value - 1;
714 auto newBlockRaw = static_cast<char*>(std::malloc(size));
715 if (newBlockRaw == nullptr) {
716 return nullptr;
717 }
718
719 auto newBlockAligned = align_for<Block>(newBlockRaw);
720 auto newBlockData = align_for<T>(newBlockAligned + sizeof(Block));
721 return new (newBlockAligned) Block(capacity, newBlockRaw, newBlockData);
722 }
723
724private:
725 weak_atomic<Block*> frontBlock; // (Atomic) Elements are dequeued from this block
726
727 char cachelineFiller[MOODYCAMEL_CACHE_LINE_SIZE - sizeof(weak_atomic<Block*>)];
728 weak_atomic<Block*> tailBlock; // (Atomic) Elements are enqueued to this block
729
730 size_t largestBlockSize;
731
732#ifndef NDEBUG
733 weak_atomic<bool> enqueuing;
734 mutable weak_atomic<bool> dequeuing;
735#endif
736};
737
738// Like ReaderWriterQueue, but also providees blocking operations
739template <typename T, size_t MAX_BLOCK_SIZE = 512>
740class BlockingReaderWriterQueue {
741private:
742 typedef ::Common::ReaderWriterQueue<T, MAX_BLOCK_SIZE> ReaderWriterQueue;
743
744public:
745 explicit BlockingReaderWriterQueue(size_t size = 15) AE_NO_TSAN
746 : inner(size),
747 sema(new spsc_sema::LightweightSemaphore()) {}
748
749 BlockingReaderWriterQueue(BlockingReaderWriterQueue&& other) AE_NO_TSAN
750 : inner(std::move(other.inner)),
751 sema(std::move(other.sema)) {}
752
753 BlockingReaderWriterQueue& operator=(BlockingReaderWriterQueue&& other) AE_NO_TSAN {
754 std::swap(sema, other.sema);
755 std::swap(inner, other.inner);
756 return *this;
757 }
758
759 // Enqueues a copy of element if there is room in the queue.
760 // Returns true if the element was enqueued, false otherwise.
761 // Does not allocate memory.
762 AE_FORCEINLINE bool try_enqueue(T const& element) AE_NO_TSAN {
763 if (inner.try_enqueue(element)) {
764 sema->signal();
765 return true;
766 }
767 return false;
768 }
769
770 // Enqueues a moved copy of element if there is room in the queue.
771 // Returns true if the element was enqueued, false otherwise.
772 // Does not allocate memory.
773 AE_FORCEINLINE bool try_enqueue(T&& element) AE_NO_TSAN {
774 if (inner.try_enqueue(std::forward<T>(element))) {
775 sema->signal();
776 return true;
777 }
778 return false;
779 }
780
781#if MOODYCAMEL_HAS_EMPLACE
782 // Like try_enqueue() but with emplace semantics (i.e. construct-in-place).
783 template <typename... Args>
784 AE_FORCEINLINE bool try_emplace(Args&&... args) AE_NO_TSAN {
785 if (inner.try_emplace(std::forward<Args>(args)...)) {
786 sema->signal();
787 return true;
788 }
789 return false;
790 }
791#endif
792
793 // Enqueues a copy of element on the queue.
794 // Allocates an additional block of memory if needed.
795 // Only fails (returns false) if memory allocation fails.
796 AE_FORCEINLINE bool enqueue(T const& element) AE_NO_TSAN {
797 if (inner.enqueue(element)) {
798 sema->signal();
799 return true;
800 }
801 return false;
802 }
803
804 // Enqueues a moved copy of element on the queue.
805 // Allocates an additional block of memory if needed.
806 // Only fails (returns false) if memory allocation fails.
807 AE_FORCEINLINE bool enqueue(T&& element) AE_NO_TSAN {
808 if (inner.enqueue(std::forward<T>(element))) {
809 sema->signal();
810 return true;
811 }
812 return false;
813 }
814
815#if MOODYCAMEL_HAS_EMPLACE
816 // Like enqueue() but with emplace semantics (i.e. construct-in-place).
817 template <typename... Args>
818 AE_FORCEINLINE bool emplace(Args&&... args) AE_NO_TSAN {
819 if (inner.emplace(std::forward<Args>(args)...)) {
820 sema->signal();
821 return true;
822 }
823 return false;
824 }
825#endif
826
827 // Attempts to dequeue an element; if the queue is empty,
828 // returns false instead. If the queue has at least one element,
829 // moves front to result using operator=, then returns true.
830 template <typename U>
831 bool try_dequeue(U& result) AE_NO_TSAN {
832 if (sema->tryWait()) {
833 bool success = inner.try_dequeue(result);
834 assert(success);
835 AE_UNUSED(success);
836 return true;
837 }
838 return false;
839 }
840
841 // Attempts to dequeue an element; if the queue is empty,
842 // waits until an element is available, then dequeues it.
843 template <typename U>
844 void wait_dequeue(U& result) AE_NO_TSAN {
845 while (!sema->wait())
846 ;
847 bool success = inner.try_dequeue(result);
848 AE_UNUSED(result);
849 assert(success);
850 AE_UNUSED(success);
851 }
852
853 // Attempts to dequeue an element; if the queue is empty,
854 // waits until an element is available up to the specified timeout,
855 // then dequeues it and returns true, or returns false if the timeout
856 // expires before an element can be dequeued.
857 // Using a negative timeout indicates an indefinite timeout,
858 // and is thus functionally equivalent to calling wait_dequeue.
859 template <typename U>
860 bool wait_dequeue_timed(U& result, std::int64_t timeout_usecs) AE_NO_TSAN {
861 if (!sema->wait(timeout_usecs)) {
862 return false;
863 }
864 bool success = inner.try_dequeue(result);
865 AE_UNUSED(result);
866 assert(success);
867 AE_UNUSED(success);
868 return true;
869 }
870
871#if __cplusplus > 199711L || _MSC_VER >= 1700
872 // Attempts to dequeue an element; if the queue is empty,
873 // waits until an element is available up to the specified timeout,
874 // then dequeues it and returns true, or returns false if the timeout
875 // expires before an element can be dequeued.
876 // Using a negative timeout indicates an indefinite timeout,
877 // and is thus functionally equivalent to calling wait_dequeue.
878 template <typename U, typename Rep, typename Period>
879 inline bool wait_dequeue_timed(U& result,
880 std::chrono::duration<Rep, Period> const& timeout) AE_NO_TSAN {
881 return wait_dequeue_timed(
882 result, std::chrono::duration_cast<std::chrono::microseconds>(timeout).count());
883 }
884#endif
885
886 // Returns a pointer to the front element in the queue (the one that
887 // would be removed next by a call to `try_dequeue` or `pop`). If the
888 // queue appears empty at the time the method is called, nullptr is
889 // returned instead.
890 // Must be called only from the consumer thread.
891 AE_FORCEINLINE T* peek() const AE_NO_TSAN {
892 return inner.peek();
893 }
894
895 // Removes the front element from the queue, if any, without returning it.
896 // Returns true on success, or false if the queue appeared empty at the time
897 // `pop` was called.
898 AE_FORCEINLINE bool pop() AE_NO_TSAN {
899 if (sema->tryWait()) {
900 bool result = inner.pop();
901 assert(result);
902 AE_UNUSED(result);
903 return true;
904 }
905 return false;
906 }
907
908 // Returns the approximate number of items currently in the queue.
909 // Safe to call from both the producer and consumer threads.
910 AE_FORCEINLINE size_t size_approx() const AE_NO_TSAN {
911 return sema->availableApprox();
912 }
913
914 // Returns the total number of items that could be enqueued without incurring
915 // an allocation when this queue is empty.
916 // Safe to call from both the producer and consumer threads.
917 //
918 // NOTE: The actual capacity during usage may be different depending on the consumer.
919 // If the consumer is removing elements concurrently, the producer cannot add to
920 // the block the consumer is removing from until it's completely empty, except in
921 // the case where the producer was writing to the same block the consumer was
922 // reading from the whole time.
923 AE_FORCEINLINE size_t max_capacity() const {
924 return inner.max_capacity();
925 }
926
927private:
928 // Disable copying & assignment
929 BlockingReaderWriterQueue(BlockingReaderWriterQueue const&) {}
930 BlockingReaderWriterQueue& operator=(BlockingReaderWriterQueue const&) {}
931
932private:
933 ReaderWriterQueue inner;
934 std::unique_ptr<spsc_sema::LightweightSemaphore> sema;
935};
936
937} // namespace Common
938
939#ifdef AE_VCPP
940#pragma warning(pop)
941#endif
diff --git a/src/common/settings.cpp b/src/common/settings.cpp
index d4c52989a..1c7b6dfae 100644
--- a/src/common/settings.cpp
+++ b/src/common/settings.cpp
@@ -62,7 +62,8 @@ void LogSettings() {
62 log_setting("Renderer_UseAsynchronousShaders", values.use_asynchronous_shaders.GetValue()); 62 log_setting("Renderer_UseAsynchronousShaders", values.use_asynchronous_shaders.GetValue());
63 log_setting("Renderer_AnisotropicFilteringLevel", values.max_anisotropy.GetValue()); 63 log_setting("Renderer_AnisotropicFilteringLevel", values.max_anisotropy.GetValue());
64 log_setting("Audio_OutputEngine", values.sink_id.GetValue()); 64 log_setting("Audio_OutputEngine", values.sink_id.GetValue());
65 log_setting("Audio_OutputDevice", values.audio_device_id.GetValue()); 65 log_setting("Audio_OutputDevice", values.audio_output_device_id.GetValue());
66 log_setting("Audio_InputDevice", values.audio_input_device_id.GetValue());
66 log_setting("DataStorage_UseVirtualSd", values.use_virtual_sd.GetValue()); 67 log_setting("DataStorage_UseVirtualSd", values.use_virtual_sd.GetValue());
67 log_path("DataStorage_CacheDir", Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir)); 68 log_path("DataStorage_CacheDir", Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir));
68 log_path("DataStorage_ConfigDir", Common::FS::GetYuzuPath(Common::FS::YuzuPath::ConfigDir)); 69 log_path("DataStorage_ConfigDir", Common::FS::GetYuzuPath(Common::FS::YuzuPath::ConfigDir));
diff --git a/src/common/settings.h b/src/common/settings.h
index 2bccb8642..06d72c8bf 100644
--- a/src/common/settings.h
+++ b/src/common/settings.h
@@ -370,10 +370,12 @@ struct TouchFromButtonMap {
370 370
371struct Values { 371struct Values {
372 // Audio 372 // Audio
373 Setting<std::string> audio_device_id{"auto", "output_device"};
374 Setting<std::string> sink_id{"auto", "output_engine"}; 373 Setting<std::string> sink_id{"auto", "output_engine"};
374 Setting<std::string> audio_output_device_id{"auto", "output_device"};
375 Setting<std::string> audio_input_device_id{"auto", "input_device"};
375 Setting<bool> audio_muted{false, "audio_muted"}; 376 Setting<bool> audio_muted{false, "audio_muted"};
376 SwitchableSetting<u8, true> volume{100, 0, 100, "volume"}; 377 SwitchableSetting<u8, true> volume{100, 0, 100, "volume"};
378 Setting<bool> dump_audio_commands{false, "dump_audio_commands"};
377 379
378 // Core 380 // Core
379 SwitchableSetting<bool> use_multi_core{true, "use_multi_core"}; 381 SwitchableSetting<bool> use_multi_core{true, "use_multi_core"};
diff --git a/src/core/core.cpp b/src/core/core.cpp
index 7723d9782..0ede0d85c 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -8,6 +8,7 @@
8#include <memory> 8#include <memory>
9#include <utility> 9#include <utility>
10 10
11#include "audio_core/audio_core.h"
11#include "common/fs/fs.h" 12#include "common/fs/fs.h"
12#include "common/logging/log.h" 13#include "common/logging/log.h"
13#include "common/microprofile.h" 14#include "common/microprofile.h"
@@ -140,6 +141,8 @@ struct System::Impl {
140 core_timing.SyncPause(false); 141 core_timing.SyncPause(false);
141 is_paused = false; 142 is_paused = false;
142 143
144 audio_core->PauseSinks(false);
145
143 return status; 146 return status;
144 } 147 }
145 148
@@ -147,6 +150,8 @@ struct System::Impl {
147 std::unique_lock<std::mutex> lk(suspend_guard); 150 std::unique_lock<std::mutex> lk(suspend_guard);
148 status = SystemResultStatus::Success; 151 status = SystemResultStatus::Success;
149 152
153 audio_core->PauseSinks(true);
154
150 core_timing.SyncPause(true); 155 core_timing.SyncPause(true);
151 kernel.Suspend(true); 156 kernel.Suspend(true);
152 is_paused = true; 157 is_paused = true;
@@ -154,6 +159,11 @@ struct System::Impl {
154 return status; 159 return status;
155 } 160 }
156 161
162 bool IsPaused() const {
163 std::unique_lock lk(suspend_guard);
164 return is_paused;
165 }
166
157 std::unique_lock<std::mutex> StallProcesses() { 167 std::unique_lock<std::mutex> StallProcesses() {
158 std::unique_lock<std::mutex> lk(suspend_guard); 168 std::unique_lock<std::mutex> lk(suspend_guard);
159 kernel.Suspend(true); 169 kernel.Suspend(true);
@@ -214,6 +224,8 @@ struct System::Impl {
214 return SystemResultStatus::ErrorVideoCore; 224 return SystemResultStatus::ErrorVideoCore;
215 } 225 }
216 226
227 audio_core = std::make_unique<AudioCore::AudioCore>(system);
228
217 service_manager = std::make_shared<Service::SM::ServiceManager>(kernel); 229 service_manager = std::make_shared<Service::SM::ServiceManager>(kernel);
218 services = std::make_unique<Service::Services>(service_manager, system); 230 services = std::make_unique<Service::Services>(service_manager, system);
219 interrupt_manager = std::make_unique<Hardware::InterruptManager>(system); 231 interrupt_manager = std::make_unique<Hardware::InterruptManager>(system);
@@ -290,7 +302,7 @@ struct System::Impl {
290 if (Settings::values.gamecard_current_game) { 302 if (Settings::values.gamecard_current_game) {
291 fs_controller.SetGameCard(GetGameFileFromPath(virtual_filesystem, filepath)); 303 fs_controller.SetGameCard(GetGameFileFromPath(virtual_filesystem, filepath));
292 } else if (!Settings::values.gamecard_path.GetValue().empty()) { 304 } else if (!Settings::values.gamecard_path.GetValue().empty()) {
293 const auto gamecard_path = Settings::values.gamecard_path.GetValue(); 305 const auto& gamecard_path = Settings::values.gamecard_path.GetValue();
294 fs_controller.SetGameCard(GetGameFileFromPath(virtual_filesystem, gamecard_path)); 306 fs_controller.SetGameCard(GetGameFileFromPath(virtual_filesystem, gamecard_path));
295 } 307 }
296 } 308 }
@@ -308,6 +320,8 @@ struct System::Impl {
308 } 320 }
309 321
310 void Shutdown() { 322 void Shutdown() {
323 SetShuttingDown(true);
324
311 // Log last frame performance stats if game was loded 325 // Log last frame performance stats if game was loded
312 if (perf_stats) { 326 if (perf_stats) {
313 const auto perf_results = GetAndResetPerfStats(); 327 const auto perf_results = GetAndResetPerfStats();
@@ -333,14 +347,15 @@ struct System::Impl {
333 kernel.ShutdownCores(); 347 kernel.ShutdownCores();
334 cpu_manager.Shutdown(); 348 cpu_manager.Shutdown();
335 debugger.reset(); 349 debugger.reset();
350 kernel.CloseServices();
336 services.reset(); 351 services.reset();
337 service_manager.reset(); 352 service_manager.reset();
338 cheat_engine.reset(); 353 cheat_engine.reset();
339 telemetry_session.reset(); 354 telemetry_session.reset();
340 cpu_manager.Shutdown();
341 time_manager.Shutdown(); 355 time_manager.Shutdown();
342 core_timing.Shutdown(); 356 core_timing.Shutdown();
343 app_loader.reset(); 357 app_loader.reset();
358 audio_core.reset();
344 gpu_core.reset(); 359 gpu_core.reset();
345 perf_stats.reset(); 360 perf_stats.reset();
346 kernel.Shutdown(); 361 kernel.Shutdown();
@@ -350,6 +365,14 @@ struct System::Impl {
350 LOG_DEBUG(Core, "Shutdown OK"); 365 LOG_DEBUG(Core, "Shutdown OK");
351 } 366 }
352 367
368 bool IsShuttingDown() const {
369 return is_shutting_down;
370 }
371
372 void SetShuttingDown(bool shutting_down) {
373 is_shutting_down = shutting_down;
374 }
375
353 Loader::ResultStatus GetGameName(std::string& out) const { 376 Loader::ResultStatus GetGameName(std::string& out) const {
354 if (app_loader == nullptr) 377 if (app_loader == nullptr)
355 return Loader::ResultStatus::ErrorNotInitialized; 378 return Loader::ResultStatus::ErrorNotInitialized;
@@ -392,8 +415,9 @@ struct System::Impl {
392 return perf_stats->GetAndResetStats(core_timing.GetGlobalTimeUs()); 415 return perf_stats->GetAndResetStats(core_timing.GetGlobalTimeUs());
393 } 416 }
394 417
395 std::mutex suspend_guard; 418 mutable std::mutex suspend_guard;
396 bool is_paused{}; 419 bool is_paused{};
420 std::atomic<bool> is_shutting_down{};
397 421
398 Timing::CoreTiming core_timing; 422 Timing::CoreTiming core_timing;
399 Kernel::KernelCore kernel; 423 Kernel::KernelCore kernel;
@@ -407,6 +431,7 @@ struct System::Impl {
407 std::unique_ptr<Tegra::GPU> gpu_core; 431 std::unique_ptr<Tegra::GPU> gpu_core;
408 std::unique_ptr<Hardware::InterruptManager> interrupt_manager; 432 std::unique_ptr<Hardware::InterruptManager> interrupt_manager;
409 std::unique_ptr<Core::DeviceMemory> device_memory; 433 std::unique_ptr<Core::DeviceMemory> device_memory;
434 std::unique_ptr<AudioCore::AudioCore> audio_core;
410 Core::Memory::Memory memory; 435 Core::Memory::Memory memory;
411 Core::HID::HIDCore hid_core; 436 Core::HID::HIDCore hid_core;
412 CpuManager cpu_manager; 437 CpuManager cpu_manager;
@@ -479,6 +504,10 @@ SystemResultStatus System::Pause() {
479 return impl->Pause(); 504 return impl->Pause();
480} 505}
481 506
507bool System::IsPaused() const {
508 return impl->IsPaused();
509}
510
482void System::InvalidateCpuInstructionCaches() { 511void System::InvalidateCpuInstructionCaches() {
483 impl->kernel.InvalidateAllInstructionCaches(); 512 impl->kernel.InvalidateAllInstructionCaches();
484} 513}
@@ -491,6 +520,14 @@ void System::Shutdown() {
491 impl->Shutdown(); 520 impl->Shutdown();
492} 521}
493 522
523bool System::IsShuttingDown() const {
524 return impl->IsShuttingDown();
525}
526
527void System::SetShuttingDown(bool shutting_down) {
528 impl->SetShuttingDown(shutting_down);
529}
530
494void System::DetachDebugger() { 531void System::DetachDebugger() {
495 if (impl->debugger) { 532 if (impl->debugger) {
496 impl->debugger->NotifyShutdown(); 533 impl->debugger->NotifyShutdown();
@@ -640,6 +677,14 @@ const HID::HIDCore& System::HIDCore() const {
640 return impl->hid_core; 677 return impl->hid_core;
641} 678}
642 679
680AudioCore::AudioCore& System::AudioCore() {
681 return *impl->audio_core;
682}
683
684const AudioCore::AudioCore& System::AudioCore() const {
685 return *impl->audio_core;
686}
687
643Timing::CoreTiming& System::CoreTiming() { 688Timing::CoreTiming& System::CoreTiming() {
644 return impl->core_timing; 689 return impl->core_timing;
645} 690}
diff --git a/src/core/core.h b/src/core/core.h
index 60efe4410..a49d1214b 100644
--- a/src/core/core.h
+++ b/src/core/core.h
@@ -81,6 +81,10 @@ namespace VideoCore {
81class RendererBase; 81class RendererBase;
82} // namespace VideoCore 82} // namespace VideoCore
83 83
84namespace AudioCore {
85class AudioCore;
86} // namespace AudioCore
87
84namespace Core::Timing { 88namespace Core::Timing {
85class CoreTiming; 89class CoreTiming;
86} 90}
@@ -148,6 +152,9 @@ public:
148 */ 152 */
149 [[nodiscard]] SystemResultStatus Pause(); 153 [[nodiscard]] SystemResultStatus Pause();
150 154
155 /// Check if the core is currently paused.
156 [[nodiscard]] bool IsPaused() const;
157
151 /** 158 /**
152 * Invalidate the CPU instruction caches 159 * Invalidate the CPU instruction caches
153 * This function should only be used by GDB Stub to support breakpoints, memory updates and 160 * This function should only be used by GDB Stub to support breakpoints, memory updates and
@@ -160,6 +167,12 @@ public:
160 /// Shutdown the emulated system. 167 /// Shutdown the emulated system.
161 void Shutdown(); 168 void Shutdown();
162 169
170 /// Check if the core is shutting down.
171 [[nodiscard]] bool IsShuttingDown() const;
172
173 /// Set the shutting down state.
174 void SetShuttingDown(bool shutting_down);
175
163 /// Forcibly detach the debugger if it is running. 176 /// Forcibly detach the debugger if it is running.
164 void DetachDebugger(); 177 void DetachDebugger();
165 178
@@ -250,6 +263,12 @@ public:
250 /// Gets an immutable reference to the renderer. 263 /// Gets an immutable reference to the renderer.
251 [[nodiscard]] const VideoCore::RendererBase& Renderer() const; 264 [[nodiscard]] const VideoCore::RendererBase& Renderer() const;
252 265
266 /// Gets a mutable reference to the audio interface
267 [[nodiscard]] AudioCore::AudioCore& AudioCore();
268
269 /// Gets an immutable reference to the audio interface.
270 [[nodiscard]] const AudioCore::AudioCore& AudioCore() const;
271
253 /// Gets the global scheduler 272 /// Gets the global scheduler
254 [[nodiscard]] Kernel::GlobalSchedulerContext& GlobalSchedulerContext(); 273 [[nodiscard]] Kernel::GlobalSchedulerContext& GlobalSchedulerContext();
255 274
diff --git a/src/core/hle/kernel/hle_ipc.cpp b/src/core/hle/kernel/hle_ipc.cpp
index 45135a07f..5b3feec66 100644
--- a/src/core/hle/kernel/hle_ipc.cpp
+++ b/src/core/hle/kernel/hle_ipc.cpp
@@ -287,18 +287,52 @@ std::size_t HLERequestContext::WriteBuffer(const void* buffer, std::size_t size,
287 BufferDescriptorB().size() > buffer_index && 287 BufferDescriptorB().size() > buffer_index &&
288 BufferDescriptorB()[buffer_index].Size() >= size, 288 BufferDescriptorB()[buffer_index].Size() >= size,
289 { return 0; }, "BufferDescriptorB is invalid, index={}, size={}", buffer_index, size); 289 { return 0; }, "BufferDescriptorB is invalid, index={}, size={}", buffer_index, size);
290 memory.WriteBlock(BufferDescriptorB()[buffer_index].Address(), buffer, size); 290 WriteBufferB(buffer, size, buffer_index);
291 } else { 291 } else {
292 ASSERT_OR_EXECUTE_MSG( 292 ASSERT_OR_EXECUTE_MSG(
293 BufferDescriptorC().size() > buffer_index && 293 BufferDescriptorC().size() > buffer_index &&
294 BufferDescriptorC()[buffer_index].Size() >= size, 294 BufferDescriptorC()[buffer_index].Size() >= size,
295 { return 0; }, "BufferDescriptorC is invalid, index={}, size={}", buffer_index, size); 295 { return 0; }, "BufferDescriptorC is invalid, index={}, size={}", buffer_index, size);
296 memory.WriteBlock(BufferDescriptorC()[buffer_index].Address(), buffer, size); 296 WriteBufferC(buffer, size, buffer_index);
297 } 297 }
298 298
299 return size; 299 return size;
300} 300}
301 301
302std::size_t HLERequestContext::WriteBufferB(const void* buffer, std::size_t size,
303 std::size_t buffer_index) const {
304 if (buffer_index >= BufferDescriptorB().size() || size == 0) {
305 return 0;
306 }
307
308 const auto buffer_size{BufferDescriptorB()[buffer_index].Size()};
309 if (size > buffer_size) {
310 LOG_CRITICAL(Core, "size ({:016X}) is greater than buffer_size ({:016X})", size,
311 buffer_size);
312 size = buffer_size; // TODO(bunnei): This needs to be HW tested
313 }
314
315 memory.WriteBlock(BufferDescriptorB()[buffer_index].Address(), buffer, size);
316 return size;
317}
318
319std::size_t HLERequestContext::WriteBufferC(const void* buffer, std::size_t size,
320 std::size_t buffer_index) const {
321 if (buffer_index >= BufferDescriptorC().size() || size == 0) {
322 return 0;
323 }
324
325 const auto buffer_size{BufferDescriptorC()[buffer_index].Size()};
326 if (size > buffer_size) {
327 LOG_CRITICAL(Core, "size ({:016X}) is greater than buffer_size ({:016X})", size,
328 buffer_size);
329 size = buffer_size; // TODO(bunnei): This needs to be HW tested
330 }
331
332 memory.WriteBlock(BufferDescriptorC()[buffer_index].Address(), buffer, size);
333 return size;
334}
335
302std::size_t HLERequestContext::GetReadBufferSize(std::size_t buffer_index) const { 336std::size_t HLERequestContext::GetReadBufferSize(std::size_t buffer_index) const {
303 const bool is_buffer_a{BufferDescriptorA().size() > buffer_index && 337 const bool is_buffer_a{BufferDescriptorA().size() > buffer_index &&
304 BufferDescriptorA()[buffer_index].Size()}; 338 BufferDescriptorA()[buffer_index].Size()};
diff --git a/src/core/hle/kernel/hle_ipc.h b/src/core/hle/kernel/hle_ipc.h
index d3abeee85..99265ce90 100644
--- a/src/core/hle/kernel/hle_ipc.h
+++ b/src/core/hle/kernel/hle_ipc.h
@@ -277,6 +277,14 @@ public:
277 std::size_t WriteBuffer(const void* buffer, std::size_t size, 277 std::size_t WriteBuffer(const void* buffer, std::size_t size,
278 std::size_t buffer_index = 0) const; 278 std::size_t buffer_index = 0) const;
279 279
280 /// Helper function to write buffer B
281 std::size_t WriteBufferB(const void* buffer, std::size_t size,
282 std::size_t buffer_index = 0) const;
283
284 /// Helper function to write buffer C
285 std::size_t WriteBufferC(const void* buffer, std::size_t size,
286 std::size_t buffer_index = 0) const;
287
280 /* Helper function to write a buffer using the appropriate buffer descriptor 288 /* Helper function to write a buffer using the appropriate buffer descriptor
281 * 289 *
282 * @tparam T an arbitrary container that satisfies the 290 * @tparam T an arbitrary container that satisfies the
diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp
index 7307cf262..f23c629dc 100644
--- a/src/core/hle/kernel/kernel.cpp
+++ b/src/core/hle/kernel/kernel.cpp
@@ -95,19 +95,7 @@ struct KernelCore::Impl {
95 95
96 process_list.clear(); 96 process_list.clear();
97 97
98 // Close all open server sessions and ports. 98 CloseServices();
99 std::unordered_set<KAutoObject*> server_objects_;
100 {
101 std::scoped_lock lk(server_objects_lock);
102 server_objects_ = server_objects;
103 server_objects.clear();
104 }
105 for (auto* server_object : server_objects_) {
106 server_object->Close();
107 }
108
109 // Ensures all service threads gracefully shutdown.
110 ClearServiceThreads();
111 99
112 next_object_id = 0; 100 next_object_id = 0;
113 next_kernel_process_id = KProcess::InitialKIPIDMin; 101 next_kernel_process_id = KProcess::InitialKIPIDMin;
@@ -191,6 +179,22 @@ struct KernelCore::Impl {
191 global_object_list_container.reset(); 179 global_object_list_container.reset();
192 } 180 }
193 181
182 void CloseServices() {
183 // Close all open server sessions and ports.
184 std::unordered_set<KAutoObject*> server_objects_;
185 {
186 std::scoped_lock lk(server_objects_lock);
187 server_objects_ = server_objects;
188 server_objects.clear();
189 }
190 for (auto* server_object : server_objects_) {
191 server_object->Close();
192 }
193
194 // Ensures all service threads gracefully shutdown.
195 ClearServiceThreads();
196 }
197
194 void InitializePhysicalCores() { 198 void InitializePhysicalCores() {
195 exclusive_monitor = 199 exclusive_monitor =
196 Core::MakeExclusiveMonitor(system.Memory(), Core::Hardware::NUM_CPU_CORES); 200 Core::MakeExclusiveMonitor(system.Memory(), Core::Hardware::NUM_CPU_CORES);
@@ -813,6 +817,10 @@ void KernelCore::Shutdown() {
813 impl->Shutdown(); 817 impl->Shutdown();
814} 818}
815 819
820void KernelCore::CloseServices() {
821 impl->CloseServices();
822}
823
816const KResourceLimit* KernelCore::GetSystemResourceLimit() const { 824const KResourceLimit* KernelCore::GetSystemResourceLimit() const {
817 return impl->system_resource_limit; 825 return impl->system_resource_limit;
818} 826}
diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h
index aa0ebaa02..6c7cf6af2 100644
--- a/src/core/hle/kernel/kernel.h
+++ b/src/core/hle/kernel/kernel.h
@@ -109,6 +109,9 @@ public:
109 /// Clears all resources in use by the kernel instance. 109 /// Clears all resources in use by the kernel instance.
110 void Shutdown(); 110 void Shutdown();
111 111
112 /// Close all active services in use by the kernel instance.
113 void CloseServices();
114
112 /// Retrieves a shared pointer to the system resource limit instance. 115 /// Retrieves a shared pointer to the system resource limit instance.
113 const KResourceLimit* GetSystemResourceLimit() const; 116 const KResourceLimit* GetSystemResourceLimit() const;
114 117
diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp
index 9116dd77c..118f226e4 100644
--- a/src/core/hle/service/am/am.cpp
+++ b/src/core/hle/service/am/am.cpp
@@ -5,7 +5,6 @@
5#include <array> 5#include <array>
6#include <cinttypes> 6#include <cinttypes>
7#include <cstring> 7#include <cstring>
8#include "audio_core/audio_renderer.h"
9#include "common/settings.h" 8#include "common/settings.h"
10#include "core/core.h" 9#include "core/core.h"
11#include "core/file_sys/control_metadata.h" 10#include "core/file_sys/control_metadata.h"
@@ -286,7 +285,7 @@ ISelfController::ISelfController(Core::System& system_, NVFlinger::NVFlinger& nv
286 {62, &ISelfController::SetIdleTimeDetectionExtension, "SetIdleTimeDetectionExtension"}, 285 {62, &ISelfController::SetIdleTimeDetectionExtension, "SetIdleTimeDetectionExtension"},
287 {63, &ISelfController::GetIdleTimeDetectionExtension, "GetIdleTimeDetectionExtension"}, 286 {63, &ISelfController::GetIdleTimeDetectionExtension, "GetIdleTimeDetectionExtension"},
288 {64, nullptr, "SetInputDetectionSourceSet"}, 287 {64, nullptr, "SetInputDetectionSourceSet"},
289 {65, nullptr, "ReportUserIsActive"}, 288 {65, &ISelfController::ReportUserIsActive, "ReportUserIsActive"},
290 {66, nullptr, "GetCurrentIlluminance"}, 289 {66, nullptr, "GetCurrentIlluminance"},
291 {67, nullptr, "IsIlluminanceAvailable"}, 290 {67, nullptr, "IsIlluminanceAvailable"},
292 {68, &ISelfController::SetAutoSleepDisabled, "SetAutoSleepDisabled"}, 291 {68, &ISelfController::SetAutoSleepDisabled, "SetAutoSleepDisabled"},
@@ -518,6 +517,13 @@ void ISelfController::GetIdleTimeDetectionExtension(Kernel::HLERequestContext& c
518 rb.Push<u32>(idle_time_detection_extension); 517 rb.Push<u32>(idle_time_detection_extension);
519} 518}
520 519
520void ISelfController::ReportUserIsActive(Kernel::HLERequestContext& ctx) {
521 LOG_WARNING(Service_AM, "(STUBBED) called");
522
523 IPC::ResponseBuilder rb{ctx, 2};
524 rb.Push(ResultSuccess);
525}
526
521void ISelfController::SetAutoSleepDisabled(Kernel::HLERequestContext& ctx) { 527void ISelfController::SetAutoSleepDisabled(Kernel::HLERequestContext& ctx) {
522 IPC::RequestParser rp{ctx}; 528 IPC::RequestParser rp{ctx};
523 is_auto_sleep_disabled = rp.Pop<bool>(); 529 is_auto_sleep_disabled = rp.Pop<bool>();
diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h
index 53144427b..bb75c6281 100644
--- a/src/core/hle/service/am/am.h
+++ b/src/core/hle/service/am/am.h
@@ -175,6 +175,7 @@ private:
175 void SetHandlesRequestToDisplay(Kernel::HLERequestContext& ctx); 175 void SetHandlesRequestToDisplay(Kernel::HLERequestContext& ctx);
176 void SetIdleTimeDetectionExtension(Kernel::HLERequestContext& ctx); 176 void SetIdleTimeDetectionExtension(Kernel::HLERequestContext& ctx);
177 void GetIdleTimeDetectionExtension(Kernel::HLERequestContext& ctx); 177 void GetIdleTimeDetectionExtension(Kernel::HLERequestContext& ctx);
178 void ReportUserIsActive(Kernel::HLERequestContext& ctx);
178 void SetAutoSleepDisabled(Kernel::HLERequestContext& ctx); 179 void SetAutoSleepDisabled(Kernel::HLERequestContext& ctx);
179 void IsAutoSleepDisabled(Kernel::HLERequestContext& ctx); 180 void IsAutoSleepDisabled(Kernel::HLERequestContext& ctx);
180 void GetAccumulatedSuspendedTickValue(Kernel::HLERequestContext& ctx); 181 void GetAccumulatedSuspendedTickValue(Kernel::HLERequestContext& ctx);
diff --git a/src/core/hle/service/audio/audin_u.cpp b/src/core/hle/service/audio/audin_u.cpp
index 18d3ae682..48a9a73a0 100644
--- a/src/core/hle/service/audio/audin_u.cpp
+++ b/src/core/hle/service/audio/audin_u.cpp
@@ -1,68 +1,211 @@
1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project 1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-2.0-or-later
3 3
4#include "audio_core/in/audio_in_system.h"
5#include "audio_core/renderer/audio_device.h"
6#include "common/common_funcs.h"
4#include "common/logging/log.h" 7#include "common/logging/log.h"
8#include "common/string_util.h"
5#include "core/core.h" 9#include "core/core.h"
6#include "core/hle/ipc_helpers.h" 10#include "core/hle/ipc_helpers.h"
7#include "core/hle/kernel/k_event.h" 11#include "core/hle/kernel/k_event.h"
8#include "core/hle/service/audio/audin_u.h" 12#include "core/hle/service/audio/audin_u.h"
9 13
10namespace Service::Audio { 14namespace Service::Audio {
15using namespace AudioCore::AudioIn;
11 16
12IAudioIn::IAudioIn(Core::System& system_) 17class IAudioIn final : public ServiceFramework<IAudioIn> {
13 : ServiceFramework{system_, "IAudioIn"}, service_context{system_, "IAudioIn"} { 18public:
14 // clang-format off 19 explicit IAudioIn(Core::System& system_, Manager& manager, size_t session_id,
15 static const FunctionInfo functions[] = { 20 std::string& device_name, const AudioInParameter& in_params, u32 handle,
16 {0, nullptr, "GetAudioInState"}, 21 u64 applet_resource_user_id)
17 {1, &IAudioIn::Start, "Start"}, 22 : ServiceFramework{system_, "IAudioIn"},
18 {2, nullptr, "Stop"}, 23 service_context{system_, "IAudioIn"}, event{service_context.CreateEvent("AudioInEvent")},
19 {3, nullptr, "AppendAudioInBuffer"}, 24 impl{std::make_shared<In>(system_, manager, event, session_id)} {
20 {4, &IAudioIn::RegisterBufferEvent, "RegisterBufferEvent"}, 25 // clang-format off
21 {5, nullptr, "GetReleasedAudioInBuffer"}, 26 static const FunctionInfo functions[] = {
22 {6, nullptr, "ContainsAudioInBuffer"}, 27 {0, &IAudioIn::GetAudioInState, "GetAudioInState"},
23 {7, nullptr, "AppendUacInBuffer"}, 28 {1, &IAudioIn::Start, "Start"},
24 {8, &IAudioIn::AppendAudioInBufferAuto, "AppendAudioInBufferAuto"}, 29 {2, &IAudioIn::Stop, "Stop"},
25 {9, nullptr, "GetReleasedAudioInBuffersAuto"}, 30 {3, &IAudioIn::AppendAudioInBuffer, "AppendAudioInBuffer"},
26 {10, nullptr, "AppendUacInBufferAuto"}, 31 {4, &IAudioIn::RegisterBufferEvent, "RegisterBufferEvent"},
27 {11, nullptr, "GetAudioInBufferCount"}, 32 {5, &IAudioIn::GetReleasedAudioInBuffer, "GetReleasedAudioInBuffer"},
28 {12, nullptr, "SetDeviceGain"}, 33 {6, &IAudioIn::ContainsAudioInBuffer, "ContainsAudioInBuffer"},
29 {13, nullptr, "GetDeviceGain"}, 34 {7, &IAudioIn::AppendAudioInBuffer, "AppendUacInBuffer"},
30 {14, nullptr, "FlushAudioInBuffers"}, 35 {8, &IAudioIn::AppendAudioInBuffer, "AppendAudioInBufferAuto"},
31 }; 36 {9, &IAudioIn::GetReleasedAudioInBuffer, "GetReleasedAudioInBuffersAuto"},
32 // clang-format on 37 {10, &IAudioIn::AppendAudioInBuffer, "AppendUacInBufferAuto"},
38 {11, &IAudioIn::GetAudioInBufferCount, "GetAudioInBufferCount"},
39 {12, &IAudioIn::SetDeviceGain, "SetDeviceGain"},
40 {13, &IAudioIn::GetDeviceGain, "GetDeviceGain"},
41 {14, &IAudioIn::FlushAudioInBuffers, "FlushAudioInBuffers"},
42 };
43 // clang-format on
33 44
34 RegisterHandlers(functions); 45 RegisterHandlers(functions);
35 46
36 buffer_event = service_context.CreateEvent("IAudioIn:BufferEvent"); 47 if (impl->GetSystem()
37} 48 .Initialize(device_name, in_params, handle, applet_resource_user_id)
49 .IsError()) {
50 LOG_ERROR(Service_Audio, "Failed to initialize the AudioIn System!");
51 }
52 }
38 53
39IAudioIn::~IAudioIn() { 54 ~IAudioIn() override {
40 service_context.CloseEvent(buffer_event); 55 impl->Free();
41} 56 service_context.CloseEvent(event);
57 }
42 58
43void IAudioIn::Start(Kernel::HLERequestContext& ctx) { 59 [[nodiscard]] std::shared_ptr<In> GetImpl() {
44 LOG_WARNING(Service_Audio, "(STUBBED) called"); 60 return impl;
61 }
45 62
46 IPC::ResponseBuilder rb{ctx, 2}; 63private:
47 rb.Push(ResultSuccess); 64 void GetAudioInState(Kernel::HLERequestContext& ctx) {
48} 65 const auto state = static_cast<u32>(impl->GetState());
49 66
50void IAudioIn::RegisterBufferEvent(Kernel::HLERequestContext& ctx) { 67 LOG_DEBUG(Service_Audio, "called. State={}", state);
51 LOG_WARNING(Service_Audio, "(STUBBED) called");
52 68
53 IPC::ResponseBuilder rb{ctx, 2, 1}; 69 IPC::ResponseBuilder rb{ctx, 3};
54 rb.Push(ResultSuccess); 70 rb.Push(ResultSuccess);
55 rb.PushCopyObjects(buffer_event->GetReadableEvent()); 71 rb.Push(state);
56} 72 }
57 73
58void IAudioIn::AppendAudioInBufferAuto(Kernel::HLERequestContext& ctx) { 74 void Start(Kernel::HLERequestContext& ctx) {
59 LOG_WARNING(Service_Audio, "(STUBBED) called"); 75 LOG_DEBUG(Service_Audio, "called");
60 76
61 IPC::ResponseBuilder rb{ctx, 2}; 77 auto result = impl->StartSystem();
62 rb.Push(ResultSuccess); 78
63} 79 IPC::ResponseBuilder rb{ctx, 2};
80 rb.Push(result);
81 }
82
83 void Stop(Kernel::HLERequestContext& ctx) {
84 LOG_DEBUG(Service_Audio, "called");
85
86 auto result = impl->StopSystem();
87
88 IPC::ResponseBuilder rb{ctx, 2};
89 rb.Push(result);
90 }
91
92 void AppendAudioInBuffer(Kernel::HLERequestContext& ctx) {
93 IPC::RequestParser rp{ctx};
94 u64 tag = rp.PopRaw<u64>();
64 95
65AudInU::AudInU(Core::System& system_) : ServiceFramework{system_, "audin:u"} { 96 const auto in_buffer_size{ctx.GetReadBufferSize()};
97 if (in_buffer_size < sizeof(AudioInBuffer)) {
98 LOG_ERROR(Service_Audio, "Input buffer is too small for an AudioInBuffer!");
99 }
100
101 const auto& in_buffer = ctx.ReadBuffer();
102 AudioInBuffer buffer{};
103 std::memcpy(&buffer, in_buffer.data(), sizeof(AudioInBuffer));
104
105 [[maybe_unused]] auto sessionid{impl->GetSystem().GetSessionId()};
106 LOG_TRACE(Service_Audio, "called. Session {} Appending buffer {:08X}", sessionid, tag);
107
108 auto result = impl->AppendBuffer(buffer, tag);
109
110 IPC::ResponseBuilder rb{ctx, 2};
111 rb.Push(result);
112 }
113
114 void RegisterBufferEvent(Kernel::HLERequestContext& ctx) {
115 LOG_DEBUG(Service_Audio, "called");
116
117 auto& buffer_event = impl->GetBufferEvent();
118
119 IPC::ResponseBuilder rb{ctx, 2, 1};
120 rb.Push(ResultSuccess);
121 rb.PushCopyObjects(buffer_event);
122 }
123
124 void GetReleasedAudioInBuffer(Kernel::HLERequestContext& ctx) {
125 auto write_buffer_size = ctx.GetWriteBufferSize() / sizeof(u64);
126 std::vector<u64> released_buffers(write_buffer_size, 0);
127
128 auto count = impl->GetReleasedBuffers(released_buffers);
129
130 [[maybe_unused]] std::string tags{};
131 for (u32 i = 0; i < count; i++) {
132 tags += fmt::format("{:08X}, ", released_buffers[i]);
133 }
134 [[maybe_unused]] auto sessionid{impl->GetSystem().GetSessionId()};
135 LOG_TRACE(Service_Audio, "called. Session {} released {} buffers: {}", sessionid, count,
136 tags);
137
138 ctx.WriteBuffer(released_buffers);
139 IPC::ResponseBuilder rb{ctx, 3};
140 rb.Push(ResultSuccess);
141 rb.Push(count);
142 }
143
144 void ContainsAudioInBuffer(Kernel::HLERequestContext& ctx) {
145 IPC::RequestParser rp{ctx};
146
147 const u64 tag{rp.Pop<u64>()};
148 const auto buffer_queued{impl->ContainsAudioBuffer(tag)};
149
150 LOG_DEBUG(Service_Audio, "called. Is buffer {:08X} registered? {}", tag, buffer_queued);
151
152 IPC::ResponseBuilder rb{ctx, 3};
153 rb.Push(ResultSuccess);
154 rb.Push(buffer_queued);
155 }
156
157 void GetAudioInBufferCount(Kernel::HLERequestContext& ctx) {
158 const auto buffer_count = impl->GetBufferCount();
159
160 LOG_DEBUG(Service_Audio, "called. Buffer count={}", buffer_count);
161
162 IPC::ResponseBuilder rb{ctx, 3};
163
164 rb.Push(ResultSuccess);
165 rb.Push(buffer_count);
166 }
167
168 void SetDeviceGain(Kernel::HLERequestContext& ctx) {
169 IPC::RequestParser rp{ctx};
170
171 const auto volume{rp.Pop<f32>()};
172 LOG_DEBUG(Service_Audio, "called. Gain {}", volume);
173
174 impl->SetVolume(volume);
175
176 IPC::ResponseBuilder rb{ctx, 2};
177 rb.Push(ResultSuccess);
178 }
179
180 void GetDeviceGain(Kernel::HLERequestContext& ctx) {
181 auto volume{impl->GetVolume()};
182
183 LOG_DEBUG(Service_Audio, "called. Gain {}", volume);
184
185 IPC::ResponseBuilder rb{ctx, 3};
186 rb.Push(ResultSuccess);
187 rb.Push(volume);
188 }
189
190 void FlushAudioInBuffers(Kernel::HLERequestContext& ctx) {
191 bool flushed{impl->FlushAudioInBuffers()};
192
193 LOG_DEBUG(Service_Audio, "called. Were any buffers flushed? {}", flushed);
194
195 IPC::ResponseBuilder rb{ctx, 3};
196 rb.Push(ResultSuccess);
197 rb.Push(flushed);
198 }
199
200 KernelHelpers::ServiceContext service_context;
201 Kernel::KEvent* event;
202 std::shared_ptr<AudioCore::AudioIn::In> impl;
203};
204
205AudInU::AudInU(Core::System& system_)
206 : ServiceFramework{system_, "audin:u", ServiceThreadType::CreateNew},
207 service_context{system_, "AudInU"}, impl{std::make_unique<AudioCore::AudioIn::Manager>(
208 system_)} {
66 // clang-format off 209 // clang-format off
67 static const FunctionInfo functions[] = { 210 static const FunctionInfo functions[] = {
68 {0, &AudInU::ListAudioIns, "ListAudioIns"}, 211 {0, &AudInU::ListAudioIns, "ListAudioIns"},
@@ -80,59 +223,152 @@ AudInU::AudInU(Core::System& system_) : ServiceFramework{system_, "audin:u"} {
80AudInU::~AudInU() = default; 223AudInU::~AudInU() = default;
81 224
82void AudInU::ListAudioIns(Kernel::HLERequestContext& ctx) { 225void AudInU::ListAudioIns(Kernel::HLERequestContext& ctx) {
226 using namespace AudioCore::AudioRenderer;
227
83 LOG_DEBUG(Service_Audio, "called"); 228 LOG_DEBUG(Service_Audio, "called");
84 const std::size_t count = ctx.GetWriteBufferSize() / sizeof(AudioInDeviceName);
85 229
86 const std::size_t device_count = std::min(count, audio_device_names.size()); 230 const auto write_count =
87 std::vector<AudioInDeviceName> device_names; 231 static_cast<u32>(ctx.GetWriteBufferSize() / sizeof(AudioDevice::AudioDeviceName));
88 device_names.reserve(device_count); 232 std::vector<AudioDevice::AudioDeviceName> device_names{};
89 233
90 for (std::size_t i = 0; i < device_count; i++) { 234 u32 out_count{0};
91 const auto& device_name = audio_device_names[i]; 235 if (write_count > 0) {
92 auto& entry = device_names.emplace_back(); 236 out_count = impl->GetDeviceNames(device_names, write_count, false);
93 device_name.copy(entry.data(), device_name.size()); 237 ctx.WriteBuffer(device_names);
94 } 238 }
95 239
96 ctx.WriteBuffer(device_names);
97
98 IPC::ResponseBuilder rb{ctx, 3}; 240 IPC::ResponseBuilder rb{ctx, 3};
99 rb.Push(ResultSuccess); 241 rb.Push(ResultSuccess);
100 rb.Push(static_cast<u32>(device_names.size())); 242 rb.Push(out_count);
101} 243}
102 244
103void AudInU::ListAudioInsAutoFiltered(Kernel::HLERequestContext& ctx) { 245void AudInU::ListAudioInsAutoFiltered(Kernel::HLERequestContext& ctx) {
246 using namespace AudioCore::AudioRenderer;
247
104 LOG_DEBUG(Service_Audio, "called"); 248 LOG_DEBUG(Service_Audio, "called");
105 constexpr u32 device_count = 0;
106 249
107 // Since we don't actually use any other audio input devices, we return 0 devices. Filtered 250 const auto write_count =
108 // device listing just omits the default input device 251 static_cast<u32>(ctx.GetWriteBufferSize() / sizeof(AudioDevice::AudioDeviceName));
252 std::vector<AudioDevice::AudioDeviceName> device_names{};
253
254 u32 out_count{0};
255 if (write_count > 0) {
256 out_count = impl->GetDeviceNames(device_names, write_count, true);
257 ctx.WriteBuffer(device_names);
258 }
109 259
110 IPC::ResponseBuilder rb{ctx, 3}; 260 IPC::ResponseBuilder rb{ctx, 3};
111 rb.Push(ResultSuccess); 261 rb.Push(ResultSuccess);
112 rb.Push(static_cast<u32>(device_count)); 262 rb.Push(out_count);
113} 263}
114 264
115void AudInU::OpenInOutImpl(Kernel::HLERequestContext& ctx) { 265void AudInU::OpenAudioIn(Kernel::HLERequestContext& ctx) {
116 AudInOutParams params{}; 266 IPC::RequestParser rp{ctx};
117 params.channel_count = 2; 267 auto in_params{rp.PopRaw<AudioInParameter>()};
118 params.sample_format = SampleFormat::PCM16; 268 auto applet_resource_user_id{rp.PopRaw<u64>()};
119 params.sample_rate = 48000; 269 const auto device_name_data{ctx.ReadBuffer()};
120 params.state = State::Started; 270 auto device_name = Common::StringFromBuffer(device_name_data);
271 auto handle{ctx.GetCopyHandle(0)};
272
273 std::scoped_lock l{impl->mutex};
274 auto link{impl->LinkToManager()};
275 if (link.IsError()) {
276 LOG_ERROR(Service_Audio, "Failed to link Audio In to Audio Manager");
277 IPC::ResponseBuilder rb{ctx, 2};
278 rb.Push(link);
279 return;
280 }
281
282 size_t new_session_id{};
283 auto result{impl->AcquireSessionId(new_session_id)};
284 if (result.IsError()) {
285 IPC::ResponseBuilder rb{ctx, 2};
286 rb.Push(result);
287 return;
288 }
289
290 LOG_DEBUG(Service_Audio, "Opening new AudioIn, sessionid={}, free sessions={}", new_session_id,
291 impl->num_free_sessions);
292
293 auto audio_in = std::make_shared<IAudioIn>(system, *impl, new_session_id, device_name,
294 in_params, handle, applet_resource_user_id);
295 impl->sessions[new_session_id] = audio_in->GetImpl();
296 impl->applet_resource_user_ids[new_session_id] = applet_resource_user_id;
297
298 auto& out_system = impl->sessions[new_session_id]->GetSystem();
299 AudioInParameterInternal out_params{.sample_rate = out_system.GetSampleRate(),
300 .channel_count = out_system.GetChannelCount(),
301 .sample_format =
302 static_cast<u32>(out_system.GetSampleFormat()),
303 .state = static_cast<u32>(out_system.GetState())};
121 304
122 IPC::ResponseBuilder rb{ctx, 6, 0, 1}; 305 IPC::ResponseBuilder rb{ctx, 6, 0, 1};
123 rb.Push(ResultSuccess);
124 rb.PushRaw<AudInOutParams>(params);
125 rb.PushIpcInterface<IAudioIn>(system);
126}
127 306
128void AudInU::OpenAudioIn(Kernel::HLERequestContext& ctx) { 307 std::string out_name{out_system.GetName()};
129 LOG_WARNING(Service_Audio, "(STUBBED) called"); 308 ctx.WriteBuffer(out_name);
130 OpenInOutImpl(ctx); 309
310 rb.Push(ResultSuccess);
311 rb.PushRaw<AudioInParameterInternal>(out_params);
312 rb.PushIpcInterface<IAudioIn>(audio_in);
131} 313}
132 314
133void AudInU::OpenAudioInProtocolSpecified(Kernel::HLERequestContext& ctx) { 315void AudInU::OpenAudioInProtocolSpecified(Kernel::HLERequestContext& ctx) {
134 LOG_WARNING(Service_Audio, "(STUBBED) called"); 316 IPC::RequestParser rp{ctx};
135 OpenInOutImpl(ctx); 317 auto protocol_specified{rp.PopRaw<u64>()};
318 auto in_params{rp.PopRaw<AudioInParameter>()};
319 auto applet_resource_user_id{rp.PopRaw<u64>()};
320 const auto device_name_data{ctx.ReadBuffer()};
321 auto device_name = Common::StringFromBuffer(device_name_data);
322 auto handle{ctx.GetCopyHandle(0)};
323
324 std::scoped_lock l{impl->mutex};
325 auto link{impl->LinkToManager()};
326 if (link.IsError()) {
327 LOG_ERROR(Service_Audio, "Failed to link Audio In to Audio Manager");
328 IPC::ResponseBuilder rb{ctx, 2};
329 rb.Push(link);
330 return;
331 }
332
333 size_t new_session_id{};
334 auto result{impl->AcquireSessionId(new_session_id)};
335 if (result.IsError()) {
336 IPC::ResponseBuilder rb{ctx, 2};
337 rb.Push(result);
338 return;
339 }
340
341 LOG_DEBUG(Service_Audio, "Opening new AudioIn, sessionid={}, free sessions={}", new_session_id,
342 impl->num_free_sessions);
343
344 auto audio_in = std::make_shared<IAudioIn>(system, *impl, new_session_id, device_name,
345 in_params, handle, applet_resource_user_id);
346 impl->sessions[new_session_id] = audio_in->GetImpl();
347 impl->applet_resource_user_ids[new_session_id] = applet_resource_user_id;
348
349 auto& out_system = impl->sessions[new_session_id]->GetSystem();
350 AudioInParameterInternal out_params{.sample_rate = out_system.GetSampleRate(),
351 .channel_count = out_system.GetChannelCount(),
352 .sample_format =
353 static_cast<u32>(out_system.GetSampleFormat()),
354 .state = static_cast<u32>(out_system.GetState())};
355
356 IPC::ResponseBuilder rb{ctx, 6, 0, 1};
357
358 std::string out_name{out_system.GetName()};
359 if (protocol_specified == 0) {
360 if (out_system.IsUac()) {
361 out_name = "UacIn";
362 } else {
363 out_name = "DeviceIn";
364 }
365 }
366
367 ctx.WriteBuffer(out_name);
368
369 rb.Push(ResultSuccess);
370 rb.PushRaw<AudioInParameterInternal>(out_params);
371 rb.PushIpcInterface<IAudioIn>(audio_in);
136} 372}
137 373
138} // namespace Service::Audio 374} // namespace Service::Audio
diff --git a/src/core/hle/service/audio/audin_u.h b/src/core/hle/service/audio/audin_u.h
index 2bfa38ecc..b45fda78a 100644
--- a/src/core/hle/service/audio/audin_u.h
+++ b/src/core/hle/service/audio/audin_u.h
@@ -3,6 +3,8 @@
3 3
4#pragma once 4#pragma once
5 5
6#include "audio_core/audio_in_manager.h"
7#include "audio_core/in/audio_in.h"
6#include "core/hle/service/kernel_helpers.h" 8#include "core/hle/service/kernel_helpers.h"
7#include "core/hle/service/service.h" 9#include "core/hle/service/service.h"
8 10
@@ -14,22 +16,12 @@ namespace Kernel {
14class HLERequestContext; 16class HLERequestContext;
15} 17}
16 18
17namespace Service::Audio { 19namespace AudioCore::AudioOut {
18 20class Manager;
19class IAudioIn final : public ServiceFramework<IAudioIn> { 21class In;
20public: 22} // namespace AudioCore::AudioOut
21 explicit IAudioIn(Core::System& system_);
22 ~IAudioIn() override;
23
24private:
25 void Start(Kernel::HLERequestContext& ctx);
26 void RegisterBufferEvent(Kernel::HLERequestContext& ctx);
27 void AppendAudioInBufferAuto(Kernel::HLERequestContext& ctx);
28
29 KernelHelpers::ServiceContext service_context;
30 23
31 Kernel::KEvent* buffer_event; 24namespace Service::Audio {
32};
33 25
34class AudInU final : public ServiceFramework<AudInU> { 26class AudInU final : public ServiceFramework<AudInU> {
35public: 27public:
@@ -37,33 +29,14 @@ public:
37 ~AudInU() override; 29 ~AudInU() override;
38 30
39private: 31private:
40 enum class SampleFormat : u32_le {
41 PCM16 = 2,
42 };
43
44 enum class State : u32_le {
45 Started = 0,
46 Stopped = 1,
47 };
48
49 struct AudInOutParams {
50 u32_le sample_rate{};
51 u32_le channel_count{};
52 SampleFormat sample_format{};
53 State state{};
54 };
55 static_assert(sizeof(AudInOutParams) == 0x10, "AudInOutParams is an invalid size");
56
57 using AudioInDeviceName = std::array<char, 256>;
58 static constexpr std::array<std::string_view, 1> audio_device_names{{
59 "BuiltInHeadset",
60 }};
61
62 void ListAudioIns(Kernel::HLERequestContext& ctx); 32 void ListAudioIns(Kernel::HLERequestContext& ctx);
63 void ListAudioInsAutoFiltered(Kernel::HLERequestContext& ctx); 33 void ListAudioInsAutoFiltered(Kernel::HLERequestContext& ctx);
64 void OpenInOutImpl(Kernel::HLERequestContext& ctx); 34 void OpenInOutImpl(Kernel::HLERequestContext& ctx);
65 void OpenAudioIn(Kernel::HLERequestContext& ctx); 35 void OpenAudioIn(Kernel::HLERequestContext& ctx);
66 void OpenAudioInProtocolSpecified(Kernel::HLERequestContext& ctx); 36 void OpenAudioInProtocolSpecified(Kernel::HLERequestContext& ctx);
37
38 KernelHelpers::ServiceContext service_context;
39 std::unique_ptr<AudioCore::AudioIn::Manager> impl;
67}; 40};
68 41
69} // namespace Service::Audio 42} // namespace Service::Audio
diff --git a/src/core/hle/service/audio/audout_u.cpp b/src/core/hle/service/audio/audout_u.cpp
index b0dad6053..a44dd842a 100644
--- a/src/core/hle/service/audio/audout_u.cpp
+++ b/src/core/hle/service/audio/audout_u.cpp
@@ -5,56 +5,43 @@
5#include <cstring> 5#include <cstring>
6#include <vector> 6#include <vector>
7 7
8#include "audio_core/audio_out.h" 8#include "audio_core/out/audio_out_system.h"
9#include "audio_core/codec.h" 9#include "audio_core/renderer/audio_device.h"
10#include "common/common_funcs.h" 10#include "common/common_funcs.h"
11#include "common/logging/log.h" 11#include "common/logging/log.h"
12#include "common/string_util.h"
12#include "common/swap.h" 13#include "common/swap.h"
13#include "core/core.h" 14#include "core/core.h"
14#include "core/hle/ipc_helpers.h" 15#include "core/hle/ipc_helpers.h"
15#include "core/hle/kernel/k_event.h" 16#include "core/hle/kernel/k_event.h"
16#include "core/hle/service/audio/audout_u.h" 17#include "core/hle/service/audio/audout_u.h"
17#include "core/hle/service/audio/errors.h" 18#include "core/hle/service/audio/errors.h"
18#include "core/hle/service/kernel_helpers.h"
19#include "core/memory.h" 19#include "core/memory.h"
20 20
21namespace Service::Audio { 21namespace Service::Audio {
22 22using namespace AudioCore::AudioOut;
23constexpr std::array<char, 10> DefaultDevice{{"DeviceOut"}};
24constexpr int DefaultSampleRate{48000};
25
26struct AudoutParams {
27 s32_le sample_rate;
28 u16_le channel_count;
29 INSERT_PADDING_BYTES_NOINIT(2);
30};
31static_assert(sizeof(AudoutParams) == 0x8, "AudoutParams is an invalid size");
32
33enum class AudioState : u32 {
34 Started,
35 Stopped,
36};
37 23
38class IAudioOut final : public ServiceFramework<IAudioOut> { 24class IAudioOut final : public ServiceFramework<IAudioOut> {
39public: 25public:
40 explicit IAudioOut(Core::System& system_, AudoutParams audio_params_, 26 explicit IAudioOut(Core::System& system_, AudioCore::AudioOut::Manager& manager,
41 AudioCore::AudioOut& audio_core_, std::string&& device_name_, 27 size_t session_id, std::string& device_name,
42 std::string&& unique_name) 28 const AudioOutParameter& in_params, u32 handle, u64 applet_resource_user_id)
43 : ServiceFramework{system_, "IAudioOut", ServiceThreadType::CreateNew}, 29 : ServiceFramework{system_, "IAudioOut", ServiceThreadType::CreateNew},
44 audio_core{audio_core_}, device_name{std::move(device_name_)}, 30 service_context{system_, "IAudioOut"}, event{service_context.CreateEvent(
45 audio_params{audio_params_}, main_memory{system.Memory()}, service_context{system_, 31 "AudioOutEvent")},
46 "IAudioOut"} { 32 impl{std::make_shared<AudioCore::AudioOut::Out>(system_, manager, event, session_id)} {
33
47 // clang-format off 34 // clang-format off
48 static const FunctionInfo functions[] = { 35 static const FunctionInfo functions[] = {
49 {0, &IAudioOut::GetAudioOutState, "GetAudioOutState"}, 36 {0, &IAudioOut::GetAudioOutState, "GetAudioOutState"},
50 {1, &IAudioOut::StartAudioOut, "Start"}, 37 {1, &IAudioOut::Start, "Start"},
51 {2, &IAudioOut::StopAudioOut, "Stop"}, 38 {2, &IAudioOut::Stop, "Stop"},
52 {3, &IAudioOut::AppendAudioOutBufferImpl, "AppendAudioOutBuffer"}, 39 {3, &IAudioOut::AppendAudioOutBuffer, "AppendAudioOutBuffer"},
53 {4, &IAudioOut::RegisterBufferEvent, "RegisterBufferEvent"}, 40 {4, &IAudioOut::RegisterBufferEvent, "RegisterBufferEvent"},
54 {5, &IAudioOut::GetReleasedAudioOutBufferImpl, "GetReleasedAudioOutBuffers"}, 41 {5, &IAudioOut::GetReleasedAudioOutBuffers, "GetReleasedAudioOutBuffers"},
55 {6, &IAudioOut::ContainsAudioOutBuffer, "ContainsAudioOutBuffer"}, 42 {6, &IAudioOut::ContainsAudioOutBuffer, "ContainsAudioOutBuffer"},
56 {7, &IAudioOut::AppendAudioOutBufferImpl, "AppendAudioOutBufferAuto"}, 43 {7, &IAudioOut::AppendAudioOutBuffer, "AppendAudioOutBufferAuto"},
57 {8, &IAudioOut::GetReleasedAudioOutBufferImpl, "GetReleasedAudioOutBufferAuto"}, 44 {8, &IAudioOut::GetReleasedAudioOutBuffers, "GetReleasedAudioOutBuffersAuto"},
58 {9, &IAudioOut::GetAudioOutBufferCount, "GetAudioOutBufferCount"}, 45 {9, &IAudioOut::GetAudioOutBufferCount, "GetAudioOutBufferCount"},
59 {10, &IAudioOut::GetAudioOutPlayedSampleCount, "GetAudioOutPlayedSampleCount"}, 46 {10, &IAudioOut::GetAudioOutPlayedSampleCount, "GetAudioOutPlayedSampleCount"},
60 {11, &IAudioOut::FlushAudioOutBuffers, "FlushAudioOutBuffers"}, 47 {11, &IAudioOut::FlushAudioOutBuffers, "FlushAudioOutBuffers"},
@@ -64,241 +51,263 @@ public:
64 // clang-format on 51 // clang-format on
65 RegisterHandlers(functions); 52 RegisterHandlers(functions);
66 53
67 // This is the event handle used to check if the audio buffer was released 54 if (impl->GetSystem()
68 buffer_event = service_context.CreateEvent("IAudioOutBufferReleased"); 55 .Initialize(device_name, in_params, handle, applet_resource_user_id)
69 56 .IsError()) {
70 stream = audio_core.OpenStream(system.CoreTiming(), audio_params.sample_rate, 57 LOG_ERROR(Service_Audio, "Failed to initialize the AudioOut System!");
71 audio_params.channel_count, std::move(unique_name), [this] { 58 }
72 const auto guard = LockService();
73 buffer_event->GetWritableEvent().Signal();
74 });
75 } 59 }
76 60
77 ~IAudioOut() override { 61 ~IAudioOut() override {
78 service_context.CloseEvent(buffer_event); 62 impl->Free();
63 service_context.CloseEvent(event);
79 } 64 }
80 65
81private: 66 [[nodiscard]] std::shared_ptr<AudioCore::AudioOut::Out> GetImpl() {
82 struct AudioBuffer { 67 return impl;
83 u64_le next; 68 }
84 u64_le buffer;
85 u64_le buffer_capacity;
86 u64_le buffer_size;
87 u64_le offset;
88 };
89 static_assert(sizeof(AudioBuffer) == 0x28, "AudioBuffer is an invalid size");
90 69
70private:
91 void GetAudioOutState(Kernel::HLERequestContext& ctx) { 71 void GetAudioOutState(Kernel::HLERequestContext& ctx) {
92 LOG_DEBUG(Service_Audio, "called"); 72 const auto state = static_cast<u32>(impl->GetState());
73
74 LOG_DEBUG(Service_Audio, "called. State={}", state);
93 75
94 IPC::ResponseBuilder rb{ctx, 3}; 76 IPC::ResponseBuilder rb{ctx, 3};
95 rb.Push(ResultSuccess); 77 rb.Push(ResultSuccess);
96 rb.Push(static_cast<u32>(stream->IsPlaying() ? AudioState::Started : AudioState::Stopped)); 78 rb.Push(state);
97 } 79 }
98 80
99 void StartAudioOut(Kernel::HLERequestContext& ctx) { 81 void Start(Kernel::HLERequestContext& ctx) {
100 LOG_DEBUG(Service_Audio, "called"); 82 LOG_DEBUG(Service_Audio, "called");
101 83
102 if (stream->IsPlaying()) { 84 auto result = impl->StartSystem();
103 IPC::ResponseBuilder rb{ctx, 2};
104 rb.Push(ERR_OPERATION_FAILED);
105 return;
106 }
107
108 audio_core.StartStream(stream);
109 85
110 IPC::ResponseBuilder rb{ctx, 2}; 86 IPC::ResponseBuilder rb{ctx, 2};
111 rb.Push(ResultSuccess); 87 rb.Push(result);
112 } 88 }
113 89
114 void StopAudioOut(Kernel::HLERequestContext& ctx) { 90 void Stop(Kernel::HLERequestContext& ctx) {
115 LOG_DEBUG(Service_Audio, "called"); 91 LOG_DEBUG(Service_Audio, "called");
116 92
117 if (stream->IsPlaying()) { 93 auto result = impl->StopSystem();
118 audio_core.StopStream(stream);
119 }
120 94
121 IPC::ResponseBuilder rb{ctx, 2}; 95 IPC::ResponseBuilder rb{ctx, 2};
122 rb.Push(ResultSuccess); 96 rb.Push(result);
123 } 97 }
124 98
125 void RegisterBufferEvent(Kernel::HLERequestContext& ctx) { 99 void AppendAudioOutBuffer(Kernel::HLERequestContext& ctx) {
126 LOG_DEBUG(Service_Audio, "called");
127
128 IPC::ResponseBuilder rb{ctx, 2, 1};
129 rb.Push(ResultSuccess);
130 rb.PushCopyObjects(buffer_event->GetReadableEvent());
131 }
132
133 void AppendAudioOutBufferImpl(Kernel::HLERequestContext& ctx) {
134 LOG_DEBUG(Service_Audio, "(STUBBED) called {}", ctx.Description());
135 IPC::RequestParser rp{ctx}; 100 IPC::RequestParser rp{ctx};
101 u64 tag = rp.PopRaw<u64>();
136 102
137 const auto& input_buffer{ctx.ReadBuffer()}; 103 const auto in_buffer_size{ctx.GetReadBufferSize()};
138 ASSERT_MSG(input_buffer.size() == sizeof(AudioBuffer), 104 if (in_buffer_size < sizeof(AudioOutBuffer)) {
139 "AudioBuffer input is an invalid size!"); 105 LOG_ERROR(Service_Audio, "Input buffer is too small for an AudioOutBuffer!");
140 AudioBuffer audio_buffer{}; 106 }
141 std::memcpy(&audio_buffer, input_buffer.data(), sizeof(AudioBuffer));
142 const u64 tag{rp.Pop<u64>()};
143 107
144 std::vector<s16> samples(audio_buffer.buffer_size / sizeof(s16)); 108 const auto& in_buffer = ctx.ReadBuffer();
145 main_memory.ReadBlock(audio_buffer.buffer, samples.data(), audio_buffer.buffer_size); 109 AudioOutBuffer buffer{};
110 std::memcpy(&buffer, in_buffer.data(), sizeof(AudioOutBuffer));
146 111
147 if (!audio_core.QueueBuffer(stream, tag, std::move(samples))) { 112 [[maybe_unused]] auto sessionid{impl->GetSystem().GetSessionId()};
148 IPC::ResponseBuilder rb{ctx, 2}; 113 LOG_TRACE(Service_Audio, "called. Session {} Appending buffer {:08X}", sessionid, tag);
149 rb.Push(ERR_BUFFER_COUNT_EXCEEDED); 114
150 return; 115 auto result = impl->AppendBuffer(buffer, tag);
151 }
152 116
153 IPC::ResponseBuilder rb{ctx, 2}; 117 IPC::ResponseBuilder rb{ctx, 2};
118 rb.Push(result);
119 }
120
121 void RegisterBufferEvent(Kernel::HLERequestContext& ctx) {
122 LOG_DEBUG(Service_Audio, "called");
123
124 auto& buffer_event = impl->GetBufferEvent();
125
126 IPC::ResponseBuilder rb{ctx, 2, 1};
154 rb.Push(ResultSuccess); 127 rb.Push(ResultSuccess);
128 rb.PushCopyObjects(buffer_event);
155 } 129 }
156 130
157 void GetReleasedAudioOutBufferImpl(Kernel::HLERequestContext& ctx) { 131 void GetReleasedAudioOutBuffers(Kernel::HLERequestContext& ctx) {
158 LOG_DEBUG(Service_Audio, "called {}", ctx.Description()); 132 auto write_buffer_size = ctx.GetWriteBufferSize() / sizeof(u64);
133 std::vector<u64> released_buffers(write_buffer_size, 0);
159 134
160 const u64 max_count{ctx.GetWriteBufferSize() / sizeof(u64)}; 135 auto count = impl->GetReleasedBuffers(released_buffers);
161 const auto released_buffers{audio_core.GetTagsAndReleaseBuffers(stream, max_count)};
162 136
163 std::vector<u64> tags{released_buffers}; 137 [[maybe_unused]] std::string tags{};
164 tags.resize(max_count); 138 for (u32 i = 0; i < count; i++) {
165 ctx.WriteBuffer(tags); 139 tags += fmt::format("{:08X}, ", released_buffers[i]);
140 }
141 [[maybe_unused]] auto sessionid{impl->GetSystem().GetSessionId()};
142 LOG_TRACE(Service_Audio, "called. Session {} released {} buffers: {}", sessionid, count,
143 tags);
166 144
145 ctx.WriteBuffer(released_buffers);
167 IPC::ResponseBuilder rb{ctx, 3}; 146 IPC::ResponseBuilder rb{ctx, 3};
168 rb.Push(ResultSuccess); 147 rb.Push(ResultSuccess);
169 rb.Push<u32>(static_cast<u32>(released_buffers.size())); 148 rb.Push(count);
170 } 149 }
171 150
172 void ContainsAudioOutBuffer(Kernel::HLERequestContext& ctx) { 151 void ContainsAudioOutBuffer(Kernel::HLERequestContext& ctx) {
173 LOG_DEBUG(Service_Audio, "called");
174
175 IPC::RequestParser rp{ctx}; 152 IPC::RequestParser rp{ctx};
153
176 const u64 tag{rp.Pop<u64>()}; 154 const u64 tag{rp.Pop<u64>()};
155 const auto buffer_queued{impl->ContainsAudioBuffer(tag)};
156
157 LOG_DEBUG(Service_Audio, "called. Is buffer {:08X} registered? {}", tag, buffer_queued);
158
177 IPC::ResponseBuilder rb{ctx, 3}; 159 IPC::ResponseBuilder rb{ctx, 3};
178 rb.Push(ResultSuccess); 160 rb.Push(ResultSuccess);
179 rb.Push(stream->ContainsBuffer(tag)); 161 rb.Push(buffer_queued);
180 } 162 }
181 163
182 void GetAudioOutBufferCount(Kernel::HLERequestContext& ctx) { 164 void GetAudioOutBufferCount(Kernel::HLERequestContext& ctx) {
183 LOG_DEBUG(Service_Audio, "called"); 165 const auto buffer_count = impl->GetBufferCount();
166
167 LOG_DEBUG(Service_Audio, "called. Buffer count={}", buffer_count);
184 168
185 IPC::ResponseBuilder rb{ctx, 3}; 169 IPC::ResponseBuilder rb{ctx, 3};
170
186 rb.Push(ResultSuccess); 171 rb.Push(ResultSuccess);
187 rb.Push(static_cast<u32>(stream->GetQueueSize())); 172 rb.Push(buffer_count);
188 } 173 }
189 174
190 void GetAudioOutPlayedSampleCount(Kernel::HLERequestContext& ctx) { 175 void GetAudioOutPlayedSampleCount(Kernel::HLERequestContext& ctx) {
191 LOG_DEBUG(Service_Audio, "called"); 176 const auto samples_played = impl->GetPlayedSampleCount();
177
178 LOG_DEBUG(Service_Audio, "called. Played samples={}", samples_played);
192 179
193 IPC::ResponseBuilder rb{ctx, 4}; 180 IPC::ResponseBuilder rb{ctx, 4};
181
194 rb.Push(ResultSuccess); 182 rb.Push(ResultSuccess);
195 rb.Push(stream->GetPlayedSampleCount()); 183 rb.Push(samples_played);
196 } 184 }
197 185
198 void FlushAudioOutBuffers(Kernel::HLERequestContext& ctx) { 186 void FlushAudioOutBuffers(Kernel::HLERequestContext& ctx) {
199 LOG_DEBUG(Service_Audio, "called"); 187 bool flushed{impl->FlushAudioOutBuffers()};
188
189 LOG_DEBUG(Service_Audio, "called. Were any buffers flushed? {}", flushed);
200 190
201 IPC::ResponseBuilder rb{ctx, 3}; 191 IPC::ResponseBuilder rb{ctx, 3};
202 rb.Push(ResultSuccess); 192 rb.Push(ResultSuccess);
203 rb.Push(stream->Flush()); 193 rb.Push(flushed);
204 } 194 }
205 195
206 void SetAudioOutVolume(Kernel::HLERequestContext& ctx) { 196 void SetAudioOutVolume(Kernel::HLERequestContext& ctx) {
207 IPC::RequestParser rp{ctx}; 197 IPC::RequestParser rp{ctx};
208 const float volume = rp.Pop<float>(); 198 const auto volume = rp.Pop<f32>();
209 LOG_DEBUG(Service_Audio, "called, volume={}", volume); 199
200 LOG_DEBUG(Service_Audio, "called. Volume={}", volume);
210 201
211 stream->SetVolume(volume); 202 impl->SetVolume(volume);
212 203
213 IPC::ResponseBuilder rb{ctx, 2}; 204 IPC::ResponseBuilder rb{ctx, 2};
214 rb.Push(ResultSuccess); 205 rb.Push(ResultSuccess);
215 } 206 }
216 207
217 void GetAudioOutVolume(Kernel::HLERequestContext& ctx) { 208 void GetAudioOutVolume(Kernel::HLERequestContext& ctx) {
218 LOG_DEBUG(Service_Audio, "called"); 209 const auto volume = impl->GetVolume();
210
211 LOG_DEBUG(Service_Audio, "called. Volume={}", volume);
219 212
220 IPC::ResponseBuilder rb{ctx, 3}; 213 IPC::ResponseBuilder rb{ctx, 3};
221 rb.Push(ResultSuccess); 214 rb.Push(ResultSuccess);
222 rb.Push(stream->GetVolume()); 215 rb.Push(volume);
223 } 216 }
224 217
225 AudioCore::AudioOut& audio_core;
226 AudioCore::StreamPtr stream;
227 std::string device_name;
228
229 [[maybe_unused]] AudoutParams audio_params{};
230
231 Core::Memory::Memory& main_memory;
232
233 KernelHelpers::ServiceContext service_context; 218 KernelHelpers::ServiceContext service_context;
234 219 Kernel::KEvent* event;
235 /// This is the event handle used to check if the audio buffer was released 220 std::shared_ptr<AudioCore::AudioOut::Out> impl;
236 Kernel::KEvent* buffer_event;
237}; 221};
238 222
239AudOutU::AudOutU(Core::System& system_) : ServiceFramework{system_, "audout:u"} { 223AudOutU::AudOutU(Core::System& system_)
224 : ServiceFramework{system_, "audout:u", ServiceThreadType::CreateNew},
225 service_context{system_, "AudOutU"}, impl{std::make_unique<AudioCore::AudioOut::Manager>(
226 system_)} {
240 // clang-format off 227 // clang-format off
241 static const FunctionInfo functions[] = { 228 static const FunctionInfo functions[] = {
242 {0, &AudOutU::ListAudioOutsImpl, "ListAudioOuts"}, 229 {0, &AudOutU::ListAudioOuts, "ListAudioOuts"},
243 {1, &AudOutU::OpenAudioOutImpl, "OpenAudioOut"}, 230 {1, &AudOutU::OpenAudioOut, "OpenAudioOut"},
244 {2, &AudOutU::ListAudioOutsImpl, "ListAudioOutsAuto"}, 231 {2, &AudOutU::ListAudioOuts, "ListAudioOutsAuto"},
245 {3, &AudOutU::OpenAudioOutImpl, "OpenAudioOutAuto"}, 232 {3, &AudOutU::OpenAudioOut, "OpenAudioOutAuto"},
246 }; 233 };
247 // clang-format on 234 // clang-format on
248 235
249 RegisterHandlers(functions); 236 RegisterHandlers(functions);
250 audio_core = std::make_unique<AudioCore::AudioOut>();
251} 237}
252 238
253AudOutU::~AudOutU() = default; 239AudOutU::~AudOutU() = default;
254 240
255void AudOutU::ListAudioOutsImpl(Kernel::HLERequestContext& ctx) { 241void AudOutU::ListAudioOuts(Kernel::HLERequestContext& ctx) {
256 LOG_DEBUG(Service_Audio, "called"); 242 using namespace AudioCore::AudioRenderer;
257 243
258 ctx.WriteBuffer(DefaultDevice); 244 std::scoped_lock l{impl->mutex};
245
246 const auto write_count =
247 static_cast<u32>(ctx.GetWriteBufferSize() / sizeof(AudioDevice::AudioDeviceName));
248 std::vector<AudioDevice::AudioDeviceName> device_names{};
249 std::string print_names{};
250 if (write_count > 0) {
251 device_names.push_back(AudioDevice::AudioDeviceName("DeviceOut"));
252 LOG_DEBUG(Service_Audio, "called. \nName=DeviceOut");
253 } else {
254 LOG_DEBUG(Service_Audio, "called. Empty buffer passed in.");
255 }
256
257 ctx.WriteBuffer(device_names);
259 258
260 IPC::ResponseBuilder rb{ctx, 3}; 259 IPC::ResponseBuilder rb{ctx, 3};
261 rb.Push(ResultSuccess); 260 rb.Push(ResultSuccess);
262 rb.Push<u32>(1); // Amount of audio devices 261 rb.Push<u32>(static_cast<u32>(device_names.size()));
263} 262}
264 263
265void AudOutU::OpenAudioOutImpl(Kernel::HLERequestContext& ctx) { 264void AudOutU::OpenAudioOut(Kernel::HLERequestContext& ctx) {
266 LOG_DEBUG(Service_Audio, "called"); 265 IPC::RequestParser rp{ctx};
267 266 auto in_params{rp.PopRaw<AudioOutParameter>()};
267 auto applet_resource_user_id{rp.PopRaw<u64>()};
268 const auto device_name_data{ctx.ReadBuffer()}; 268 const auto device_name_data{ctx.ReadBuffer()};
269 std::string device_name; 269 auto device_name = Common::StringFromBuffer(device_name_data);
270 if (device_name_data[0] != '\0') { 270 auto handle{ctx.GetCopyHandle(0)};
271 device_name.assign(device_name_data.begin(), device_name_data.end());
272 } else {
273 device_name.assign(DefaultDevice.begin(), DefaultDevice.end());
274 }
275 ctx.WriteBuffer(device_name);
276 271
277 IPC::RequestParser rp{ctx}; 272 auto link{impl->LinkToManager()};
278 auto params{rp.PopRaw<AudoutParams>()}; 273 if (link.IsError()) {
279 if (params.channel_count <= 2) { 274 LOG_ERROR(Service_Audio, "Failed to link Audio Out to Audio Manager");
280 // Mono does not exist for audout 275 IPC::ResponseBuilder rb{ctx, 2};
281 params.channel_count = 2; 276 rb.Push(link);
282 } else { 277 return;
283 params.channel_count = 6;
284 } 278 }
285 if (!params.sample_rate) { 279
286 params.sample_rate = DefaultSampleRate; 280 size_t new_session_id{};
281 auto result{impl->AcquireSessionId(new_session_id)};
282 if (result.IsError()) {
283 IPC::ResponseBuilder rb{ctx, 2};
284 rb.Push(result);
285 return;
287 } 286 }
288 287
289 std::string unique_name{fmt::format("{}-{}", device_name, audio_out_interfaces.size())}; 288 LOG_DEBUG(Service_Audio, "Opening new AudioOut, sessionid={}, free sessions={}", new_session_id,
290 auto audio_out_interface = std::make_shared<IAudioOut>( 289 impl->num_free_sessions);
291 system, params, *audio_core, std::move(device_name), std::move(unique_name)); 290
291 auto audio_out = std::make_shared<IAudioOut>(system, *impl, new_session_id, device_name,
292 in_params, handle, applet_resource_user_id);
293
294 impl->sessions[new_session_id] = audio_out->GetImpl();
295 impl->applet_resource_user_ids[new_session_id] = applet_resource_user_id;
296
297 auto& out_system = impl->sessions[new_session_id]->GetSystem();
298 AudioOutParameterInternal out_params{.sample_rate = out_system.GetSampleRate(),
299 .channel_count = out_system.GetChannelCount(),
300 .sample_format =
301 static_cast<u32>(out_system.GetSampleFormat()),
302 .state = static_cast<u32>(out_system.GetState())};
292 303
293 IPC::ResponseBuilder rb{ctx, 6, 0, 1}; 304 IPC::ResponseBuilder rb{ctx, 6, 0, 1};
294 rb.Push(ResultSuccess);
295 rb.Push<u32>(DefaultSampleRate);
296 rb.Push<u32>(params.channel_count);
297 rb.Push<u32>(static_cast<u32>(AudioCore::Codec::PcmFormat::Int16));
298 rb.Push<u32>(static_cast<u32>(AudioState::Stopped));
299 rb.PushIpcInterface<IAudioOut>(audio_out_interface);
300 305
301 audio_out_interfaces.push_back(std::move(audio_out_interface)); 306 ctx.WriteBuffer(out_system.GetName());
307
308 rb.Push(ResultSuccess);
309 rb.PushRaw<AudioOutParameterInternal>(out_params);
310 rb.PushIpcInterface<IAudioOut>(audio_out);
302} 311}
303 312
304} // namespace Service::Audio 313} // namespace Service::Audio
diff --git a/src/core/hle/service/audio/audout_u.h b/src/core/hle/service/audio/audout_u.h
index d82004c2e..fdc0ee754 100644
--- a/src/core/hle/service/audio/audout_u.h
+++ b/src/core/hle/service/audio/audout_u.h
@@ -3,13 +3,11 @@
3 3
4#pragma once 4#pragma once
5 5
6#include <vector> 6#include "audio_core/audio_out_manager.h"
7#include "audio_core/out/audio_out.h"
8#include "core/hle/service/kernel_helpers.h"
7#include "core/hle/service/service.h" 9#include "core/hle/service/service.h"
8 10
9namespace AudioCore {
10class AudioOut;
11}
12
13namespace Core { 11namespace Core {
14class System; 12class System;
15} 13}
@@ -18,6 +16,11 @@ namespace Kernel {
18class HLERequestContext; 16class HLERequestContext;
19} 17}
20 18
19namespace AudioCore::AudioOut {
20class Manager;
21class Out;
22} // namespace AudioCore::AudioOut
23
21namespace Service::Audio { 24namespace Service::Audio {
22 25
23class IAudioOut; 26class IAudioOut;
@@ -28,11 +31,11 @@ public:
28 ~AudOutU() override; 31 ~AudOutU() override;
29 32
30private: 33private:
31 void ListAudioOutsImpl(Kernel::HLERequestContext& ctx); 34 void ListAudioOuts(Kernel::HLERequestContext& ctx);
32 void OpenAudioOutImpl(Kernel::HLERequestContext& ctx); 35 void OpenAudioOut(Kernel::HLERequestContext& ctx);
33 36
34 std::vector<std::shared_ptr<IAudioOut>> audio_out_interfaces; 37 KernelHelpers::ServiceContext service_context;
35 std::unique_ptr<AudioCore::AudioOut> audio_core; 38 std::unique_ptr<AudioCore::AudioOut::Manager> impl;
36}; 39};
37 40
38} // namespace Service::Audio 41} // namespace Service::Audio
diff --git a/src/core/hle/service/audio/audren_u.cpp b/src/core/hle/service/audio/audren_u.cpp
index 2ce63c004..381a66ba5 100644
--- a/src/core/hle/service/audio/audren_u.cpp
+++ b/src/core/hle/service/audio/audren_u.cpp
@@ -4,7 +4,12 @@
4#include <array> 4#include <array>
5#include <memory> 5#include <memory>
6 6
7#include "audio_core/audio_renderer.h" 7#include "audio_core/audio_core.h"
8#include "audio_core/common/audio_renderer_parameter.h"
9#include "audio_core/common/feature_support.h"
10#include "audio_core/renderer/audio_device.h"
11#include "audio_core/renderer/audio_renderer.h"
12#include "audio_core/renderer/voice/voice_info.h"
8#include "common/alignment.h" 13#include "common/alignment.h"
9#include "common/bit_util.h" 14#include "common/bit_util.h"
10#include "common/common_funcs.h" 15#include "common/common_funcs.h"
@@ -13,91 +18,112 @@
13#include "core/core.h" 18#include "core/core.h"
14#include "core/hle/ipc_helpers.h" 19#include "core/hle/ipc_helpers.h"
15#include "core/hle/kernel/k_event.h" 20#include "core/hle/kernel/k_event.h"
21#include "core/hle/kernel/k_process.h"
22#include "core/hle/kernel/k_transfer_memory.h"
16#include "core/hle/service/audio/audren_u.h" 23#include "core/hle/service/audio/audren_u.h"
17#include "core/hle/service/audio/errors.h" 24#include "core/hle/service/audio/errors.h"
25#include "core/memory.h"
26
27using namespace AudioCore::AudioRenderer;
18 28
19namespace Service::Audio { 29namespace Service::Audio {
20 30
21class IAudioRenderer final : public ServiceFramework<IAudioRenderer> { 31class IAudioRenderer final : public ServiceFramework<IAudioRenderer> {
22public: 32public:
23 explicit IAudioRenderer(Core::System& system_, 33 explicit IAudioRenderer(Core::System& system_, Manager& manager_,
24 const AudioCommon::AudioRendererParameter& audren_params, 34 AudioCore::AudioRendererParameterInternal& params,
25 const std::size_t instance_number) 35 Kernel::KTransferMemory* transfer_memory, u64 transfer_memory_size,
36 u32 process_handle, u64 applet_resource_user_id, s32 session_id)
26 : ServiceFramework{system_, "IAudioRenderer", ServiceThreadType::CreateNew}, 37 : ServiceFramework{system_, "IAudioRenderer", ServiceThreadType::CreateNew},
27 service_context{system_, "IAudioRenderer"} { 38 service_context{system_, "IAudioRenderer"}, rendered_event{service_context.CreateEvent(
39 "IAudioRendererEvent")},
40 manager{manager_}, impl{std::make_unique<Renderer>(system_, manager, rendered_event)} {
28 // clang-format off 41 // clang-format off
29 static const FunctionInfo functions[] = { 42 static const FunctionInfo functions[] = {
30 {0, &IAudioRenderer::GetSampleRate, "GetSampleRate"}, 43 {0, &IAudioRenderer::GetSampleRate, "GetSampleRate"},
31 {1, &IAudioRenderer::GetSampleCount, "GetSampleCount"}, 44 {1, &IAudioRenderer::GetSampleCount, "GetSampleCount"},
32 {2, &IAudioRenderer::GetMixBufferCount, "GetMixBufferCount"}, 45 {2, &IAudioRenderer::GetMixBufferCount, "GetMixBufferCount"},
33 {3, &IAudioRenderer::GetState, "GetState"}, 46 {3, &IAudioRenderer::GetState, "GetState"},
34 {4, &IAudioRenderer::RequestUpdateImpl, "RequestUpdate"}, 47 {4, &IAudioRenderer::RequestUpdate, "RequestUpdate"},
35 {5, &IAudioRenderer::Start, "Start"}, 48 {5, &IAudioRenderer::Start, "Start"},
36 {6, &IAudioRenderer::Stop, "Stop"}, 49 {6, &IAudioRenderer::Stop, "Stop"},
37 {7, &IAudioRenderer::QuerySystemEvent, "QuerySystemEvent"}, 50 {7, &IAudioRenderer::QuerySystemEvent, "QuerySystemEvent"},
38 {8, &IAudioRenderer::SetRenderingTimeLimit, "SetRenderingTimeLimit"}, 51 {8, &IAudioRenderer::SetRenderingTimeLimit, "SetRenderingTimeLimit"},
39 {9, &IAudioRenderer::GetRenderingTimeLimit, "GetRenderingTimeLimit"}, 52 {9, &IAudioRenderer::GetRenderingTimeLimit, "GetRenderingTimeLimit"},
40 {10, &IAudioRenderer::RequestUpdateImpl, "RequestUpdateAuto"}, 53 {10, nullptr, "RequestUpdateAuto"},
41 {11, &IAudioRenderer::ExecuteAudioRendererRendering, "ExecuteAudioRendererRendering"}, 54 {11, nullptr, "ExecuteAudioRendererRendering"},
42 }; 55 };
43 // clang-format on 56 // clang-format on
44 RegisterHandlers(functions); 57 RegisterHandlers(functions);
45 58
46 system_event = service_context.CreateEvent("IAudioRenderer:SystemEvent"); 59 impl->Initialize(params, transfer_memory, transfer_memory_size, process_handle,
47 renderer = std::make_unique<AudioCore::AudioRenderer>( 60 applet_resource_user_id, session_id);
48 system.CoreTiming(), system.Memory(), audren_params,
49 [this]() {
50 const auto guard = LockService();
51 system_event->GetWritableEvent().Signal();
52 },
53 instance_number);
54 } 61 }
55 62
56 ~IAudioRenderer() override { 63 ~IAudioRenderer() override {
57 service_context.CloseEvent(system_event); 64 impl->Finalize();
65 service_context.CloseEvent(rendered_event);
58 } 66 }
59 67
60private: 68private:
61 void GetSampleRate(Kernel::HLERequestContext& ctx) { 69 void GetSampleRate(Kernel::HLERequestContext& ctx) {
62 LOG_DEBUG(Service_Audio, "called"); 70 const auto sample_rate{impl->GetSystem().GetSampleRate()};
71
72 LOG_DEBUG(Service_Audio, "called. Sample rate {}", sample_rate);
63 73
64 IPC::ResponseBuilder rb{ctx, 3}; 74 IPC::ResponseBuilder rb{ctx, 3};
65 rb.Push(ResultSuccess); 75 rb.Push(ResultSuccess);
66 rb.Push<u32>(renderer->GetSampleRate()); 76 rb.Push(sample_rate);
67 } 77 }
68 78
69 void GetSampleCount(Kernel::HLERequestContext& ctx) { 79 void GetSampleCount(Kernel::HLERequestContext& ctx) {
70 LOG_DEBUG(Service_Audio, "called"); 80 const auto sample_count{impl->GetSystem().GetSampleCount()};
81
82 LOG_DEBUG(Service_Audio, "called. Sample count {}", sample_count);
71 83
72 IPC::ResponseBuilder rb{ctx, 3}; 84 IPC::ResponseBuilder rb{ctx, 3};
73 rb.Push(ResultSuccess); 85 rb.Push(ResultSuccess);
74 rb.Push<u32>(renderer->GetSampleCount()); 86 rb.Push(sample_count);
75 } 87 }
76 88
77 void GetState(Kernel::HLERequestContext& ctx) { 89 void GetState(Kernel::HLERequestContext& ctx) {
78 LOG_DEBUG(Service_Audio, "called"); 90 const u32 state{!impl->GetSystem().IsActive()};
91
92 LOG_DEBUG(Service_Audio, "called, state {}", state);
79 93
80 IPC::ResponseBuilder rb{ctx, 3}; 94 IPC::ResponseBuilder rb{ctx, 3};
81 rb.Push(ResultSuccess); 95 rb.Push(ResultSuccess);
82 rb.Push<u32>(static_cast<u32>(renderer->GetStreamState())); 96 rb.Push(state);
83 } 97 }
84 98
85 void GetMixBufferCount(Kernel::HLERequestContext& ctx) { 99 void GetMixBufferCount(Kernel::HLERequestContext& ctx) {
86 LOG_DEBUG(Service_Audio, "called"); 100 LOG_DEBUG(Service_Audio, "called");
87 101
102 const auto buffer_count{impl->GetSystem().GetMixBufferCount()};
103
88 IPC::ResponseBuilder rb{ctx, 3}; 104 IPC::ResponseBuilder rb{ctx, 3};
89 rb.Push(ResultSuccess); 105 rb.Push(ResultSuccess);
90 rb.Push<u32>(renderer->GetMixBufferCount()); 106 rb.Push(buffer_count);
91 } 107 }
92 108
93 void RequestUpdateImpl(Kernel::HLERequestContext& ctx) { 109 void RequestUpdate(Kernel::HLERequestContext& ctx) {
94 LOG_DEBUG(Service_Audio, "(STUBBED) called"); 110 LOG_TRACE(Service_Audio, "called");
95 111
96 std::vector<u8> output_params(ctx.GetWriteBufferSize(), 0); 112 std::vector<u8> input{ctx.ReadBuffer(0)};
97 auto result = renderer->UpdateAudioRenderer(ctx.ReadBuffer(), output_params); 113
114 // These buffers are written manually to avoid an issue with WriteBuffer throwing errors for
115 // checking size 0. Performance size is 0 for most games.
116 const auto buffers{ctx.BufferDescriptorB()};
117 std::vector<u8> output(buffers[0].Size(), 0);
118 std::vector<u8> performance(buffers[1].Size(), 0);
119
120 auto result = impl->RequestUpdate(input, performance, output);
98 121
99 if (result.IsSuccess()) { 122 if (result.IsSuccess()) {
100 ctx.WriteBuffer(output_params); 123 ctx.WriteBufferB(output.data(), output.size(), 0);
124 ctx.WriteBufferB(performance.data(), performance.size(), 1);
125 } else {
126 LOG_ERROR(Service_Audio, "RequestUpdate failed error 0x{:02X}!", result.description);
101 } 127 }
102 128
103 IPC::ResponseBuilder rb{ctx, 2}; 129 IPC::ResponseBuilder rb{ctx, 2};
@@ -105,38 +131,45 @@ private:
105 } 131 }
106 132
107 void Start(Kernel::HLERequestContext& ctx) { 133 void Start(Kernel::HLERequestContext& ctx) {
108 LOG_WARNING(Service_Audio, "(STUBBED) called"); 134 LOG_DEBUG(Service_Audio, "called");
109 135
110 const auto result = renderer->Start(); 136 impl->Start();
111 137
112 IPC::ResponseBuilder rb{ctx, 2}; 138 IPC::ResponseBuilder rb{ctx, 2};
113 rb.Push(result); 139 rb.Push(ResultSuccess);
114 } 140 }
115 141
116 void Stop(Kernel::HLERequestContext& ctx) { 142 void Stop(Kernel::HLERequestContext& ctx) {
117 LOG_WARNING(Service_Audio, "(STUBBED) called"); 143 LOG_DEBUG(Service_Audio, "called");
118 144
119 const auto result = renderer->Stop(); 145 impl->Stop();
120 146
121 IPC::ResponseBuilder rb{ctx, 2}; 147 IPC::ResponseBuilder rb{ctx, 2};
122 rb.Push(result); 148 rb.Push(ResultSuccess);
123 } 149 }
124 150
125 void QuerySystemEvent(Kernel::HLERequestContext& ctx) { 151 void QuerySystemEvent(Kernel::HLERequestContext& ctx) {
126 LOG_WARNING(Service_Audio, "(STUBBED) called"); 152 LOG_DEBUG(Service_Audio, "called");
153
154 if (impl->GetSystem().GetExecutionMode() == AudioCore::ExecutionMode::Manual) {
155 IPC::ResponseBuilder rb{ctx, 2};
156 rb.Push(ERR_NOT_SUPPORTED);
157 return;
158 }
127 159
128 IPC::ResponseBuilder rb{ctx, 2, 1}; 160 IPC::ResponseBuilder rb{ctx, 2, 1};
129 rb.Push(ResultSuccess); 161 rb.Push(ResultSuccess);
130 rb.PushCopyObjects(system_event->GetReadableEvent()); 162 rb.PushCopyObjects(rendered_event->GetReadableEvent());
131 } 163 }
132 164
133 void SetRenderingTimeLimit(Kernel::HLERequestContext& ctx) { 165 void SetRenderingTimeLimit(Kernel::HLERequestContext& ctx) {
166 LOG_DEBUG(Service_Audio, "called");
167
134 IPC::RequestParser rp{ctx}; 168 IPC::RequestParser rp{ctx};
135 rendering_time_limit_percent = rp.Pop<u32>(); 169 auto limit = rp.PopRaw<u32>();
136 LOG_DEBUG(Service_Audio, "called. rendering_time_limit_percent={}",
137 rendering_time_limit_percent);
138 170
139 ASSERT(rendering_time_limit_percent <= 100); 171 auto& system_ = impl->GetSystem();
172 system_.SetRenderingTimeLimit(limit);
140 173
141 IPC::ResponseBuilder rb{ctx, 2}; 174 IPC::ResponseBuilder rb{ctx, 2};
142 rb.Push(ResultSuccess); 175 rb.Push(ResultSuccess);
@@ -145,34 +178,34 @@ private:
145 void GetRenderingTimeLimit(Kernel::HLERequestContext& ctx) { 178 void GetRenderingTimeLimit(Kernel::HLERequestContext& ctx) {
146 LOG_DEBUG(Service_Audio, "called"); 179 LOG_DEBUG(Service_Audio, "called");
147 180
181 auto& system_ = impl->GetSystem();
182 auto time = system_.GetRenderingTimeLimit();
183
148 IPC::ResponseBuilder rb{ctx, 3}; 184 IPC::ResponseBuilder rb{ctx, 3};
149 rb.Push(ResultSuccess); 185 rb.Push(ResultSuccess);
150 rb.Push(rendering_time_limit_percent); 186 rb.Push(time);
151 } 187 }
152 188
153 void ExecuteAudioRendererRendering(Kernel::HLERequestContext& ctx) { 189 void ExecuteAudioRendererRendering(Kernel::HLERequestContext& ctx) {
154 LOG_DEBUG(Service_Audio, "called"); 190 LOG_DEBUG(Service_Audio, "called");
155
156 // This service command currently only reports an unsupported operation
157 // error code, or aborts. Given that, we just always return an error
158 // code in this case.
159
160 IPC::ResponseBuilder rb{ctx, 2};
161 rb.Push(ERR_NOT_SUPPORTED);
162 } 191 }
163 192
164 KernelHelpers::ServiceContext service_context; 193 KernelHelpers::ServiceContext service_context;
165 194 Kernel::KEvent* rendered_event;
166 Kernel::KEvent* system_event; 195 Manager& manager;
167 std::unique_ptr<AudioCore::AudioRenderer> renderer; 196 std::unique_ptr<Renderer> impl;
168 u32 rendering_time_limit_percent = 100;
169}; 197};
170 198
171class IAudioDevice final : public ServiceFramework<IAudioDevice> { 199class IAudioDevice final : public ServiceFramework<IAudioDevice> {
200
172public: 201public:
173 explicit IAudioDevice(Core::System& system_, Kernel::KEvent* buffer_event_, u32_le revision_) 202 explicit IAudioDevice(Core::System& system_, u64 applet_resource_user_id, u32 revision,
174 : ServiceFramework{system_, "IAudioDevice"}, buffer_event{buffer_event_}, revision{ 203 u32 device_num)
175 revision_} { 204 : ServiceFramework{system_, "IAudioDevice", ServiceThreadType::CreateNew},
205 service_context{system_, "IAudioDevice"}, impl{std::make_unique<AudioDevice>(
206 system_, applet_resource_user_id,
207 revision)},
208 event{service_context.CreateEvent(fmt::format("IAudioDeviceEvent-{}", device_num))} {
176 static const FunctionInfo functions[] = { 209 static const FunctionInfo functions[] = {
177 {0, &IAudioDevice::ListAudioDeviceName, "ListAudioDeviceName"}, 210 {0, &IAudioDevice::ListAudioDeviceName, "ListAudioDeviceName"},
178 {1, &IAudioDevice::SetAudioDeviceOutputVolume, "SetAudioDeviceOutputVolume"}, 211 {1, &IAudioDevice::SetAudioDeviceOutputVolume, "SetAudioDeviceOutputVolume"},
@@ -186,54 +219,45 @@ public:
186 {10, &IAudioDevice::GetActiveAudioDeviceName, "GetActiveAudioDeviceNameAuto"}, 219 {10, &IAudioDevice::GetActiveAudioDeviceName, "GetActiveAudioDeviceNameAuto"},
187 {11, &IAudioDevice::QueryAudioDeviceInputEvent, "QueryAudioDeviceInputEvent"}, 220 {11, &IAudioDevice::QueryAudioDeviceInputEvent, "QueryAudioDeviceInputEvent"},
188 {12, &IAudioDevice::QueryAudioDeviceOutputEvent, "QueryAudioDeviceOutputEvent"}, 221 {12, &IAudioDevice::QueryAudioDeviceOutputEvent, "QueryAudioDeviceOutputEvent"},
189 {13, nullptr, "GetActiveAudioOutputDeviceName"}, 222 {13, &IAudioDevice::GetActiveAudioDeviceName, "GetActiveAudioOutputDeviceName"},
190 {14, nullptr, "ListAudioOutputDeviceName"}, 223 {14, &IAudioDevice::ListAudioOutputDeviceName, "ListAudioOutputDeviceName"},
191 }; 224 };
192 RegisterHandlers(functions); 225 RegisterHandlers(functions);
226
227 event->GetWritableEvent().Signal();
193 } 228 }
194 229
195private: 230 ~IAudioDevice() override {
196 using AudioDeviceName = std::array<char, 256>; 231 service_context.CloseEvent(event);
197 static constexpr std::array<std::string_view, 4> audio_device_names{{ 232 }
198 "AudioStereoJackOutput",
199 "AudioBuiltInSpeakerOutput",
200 "AudioTvOutput",
201 "AudioUsbDeviceOutput",
202 }};
203 enum class DeviceType {
204 AHUBHeadphones,
205 AHUBSpeakers,
206 HDA,
207 USBOutput,
208 };
209 233
234private:
210 void ListAudioDeviceName(Kernel::HLERequestContext& ctx) { 235 void ListAudioDeviceName(Kernel::HLERequestContext& ctx) {
211 LOG_DEBUG(Service_Audio, "called"); 236 const size_t in_count = ctx.GetWriteBufferSize() / sizeof(AudioDevice::AudioDeviceName);
212 237
213 const bool usb_output_supported = 238 std::vector<AudioDevice::AudioDeviceName> out_names{};
214 IsFeatureSupported(AudioFeatures::AudioUSBDeviceOutput, revision);
215 const std::size_t count = ctx.GetWriteBufferSize() / sizeof(AudioDeviceName);
216 239
217 std::vector<AudioDeviceName> name_buffer; 240 u32 out_count = impl->ListAudioDeviceName(out_names, in_count);
218 name_buffer.reserve(audio_device_names.size());
219 241
220 for (std::size_t i = 0; i < count && i < audio_device_names.size(); i++) { 242 std::string out{};
221 const auto type = static_cast<DeviceType>(i); 243 for (u32 i = 0; i < out_count; i++) {
222 244 std::string a{};
223 if (!usb_output_supported && type == DeviceType::USBOutput) { 245 u32 j = 0;
224 continue; 246 while (out_names[i].name[j] != '\0') {
247 a += out_names[i].name[j];
248 j++;
225 } 249 }
226 250 out += "\n\t" + a;
227 const auto& device_name = audio_device_names[i];
228 auto& entry = name_buffer.emplace_back();
229 device_name.copy(entry.data(), device_name.size());
230 } 251 }
231 252
232 ctx.WriteBuffer(name_buffer); 253 LOG_DEBUG(Service_Audio, "called.\nNames={}", out);
233 254
234 IPC::ResponseBuilder rb{ctx, 3}; 255 IPC::ResponseBuilder rb{ctx, 3};
256
257 ctx.WriteBuffer(out_names);
258
235 rb.Push(ResultSuccess); 259 rb.Push(ResultSuccess);
236 rb.Push(static_cast<u32>(name_buffer.size())); 260 rb.Push(out_count);
237 } 261 }
238 262
239 void SetAudioDeviceOutputVolume(Kernel::HLERequestContext& ctx) { 263 void SetAudioDeviceOutputVolume(Kernel::HLERequestContext& ctx) {
@@ -243,7 +267,11 @@ private:
243 const auto device_name_buffer = ctx.ReadBuffer(); 267 const auto device_name_buffer = ctx.ReadBuffer();
244 const std::string name = Common::StringFromBuffer(device_name_buffer); 268 const std::string name = Common::StringFromBuffer(device_name_buffer);
245 269
246 LOG_WARNING(Service_Audio, "(STUBBED) called. name={}, volume={}", name, volume); 270 LOG_DEBUG(Service_Audio, "called. name={}, volume={}", name, volume);
271
272 if (name == "AudioTvOutput") {
273 impl->SetDeviceVolumes(volume);
274 }
247 275
248 IPC::ResponseBuilder rb{ctx, 2}; 276 IPC::ResponseBuilder rb{ctx, 2};
249 rb.Push(ResultSuccess); 277 rb.Push(ResultSuccess);
@@ -253,53 +281,60 @@ private:
253 const auto device_name_buffer = ctx.ReadBuffer(); 281 const auto device_name_buffer = ctx.ReadBuffer();
254 const std::string name = Common::StringFromBuffer(device_name_buffer); 282 const std::string name = Common::StringFromBuffer(device_name_buffer);
255 283
256 LOG_WARNING(Service_Audio, "(STUBBED) called. name={}", name); 284 LOG_DEBUG(Service_Audio, "called. Name={}", name);
285
286 f32 volume{1.0f};
287 if (name == "AudioTvOutput") {
288 volume = impl->GetDeviceVolume(name);
289 }
257 290
258 IPC::ResponseBuilder rb{ctx, 3}; 291 IPC::ResponseBuilder rb{ctx, 3};
259 rb.Push(ResultSuccess); 292 rb.Push(ResultSuccess);
260 rb.Push(1.0f); 293 rb.Push(volume);
261 } 294 }
262 295
263 void GetActiveAudioDeviceName(Kernel::HLERequestContext& ctx) { 296 void GetActiveAudioDeviceName(Kernel::HLERequestContext& ctx) {
264 LOG_WARNING(Service_Audio, "(STUBBED) called"); 297 const auto write_size = ctx.GetWriteBufferSize() / sizeof(char);
298 std::string out_name{"AudioTvOutput"};
265 299
266 // Currently set to always be TV audio output. 300 LOG_DEBUG(Service_Audio, "(STUBBED) called. Name={}", out_name);
267 const auto& device_name = audio_device_names[2];
268 301
269 AudioDeviceName out_device_name{}; 302 out_name.resize(write_size);
270 device_name.copy(out_device_name.data(), device_name.size());
271 303
272 ctx.WriteBuffer(out_device_name); 304 ctx.WriteBuffer(out_name);
273 305
274 IPC::ResponseBuilder rb{ctx, 2}; 306 IPC::ResponseBuilder rb{ctx, 2};
275 rb.Push(ResultSuccess); 307 rb.Push(ResultSuccess);
276 } 308 }
277 309
278 void QueryAudioDeviceSystemEvent(Kernel::HLERequestContext& ctx) { 310 void QueryAudioDeviceSystemEvent(Kernel::HLERequestContext& ctx) {
279 LOG_WARNING(Service_Audio, "(STUBBED) called"); 311 LOG_DEBUG(Service_Audio, "(STUBBED) called");
280 312
281 buffer_event->GetWritableEvent().Signal(); 313 event->GetWritableEvent().Signal();
282 314
283 IPC::ResponseBuilder rb{ctx, 2, 1}; 315 IPC::ResponseBuilder rb{ctx, 2, 1};
284 rb.Push(ResultSuccess); 316 rb.Push(ResultSuccess);
285 rb.PushCopyObjects(buffer_event->GetReadableEvent()); 317 rb.PushCopyObjects(event->GetReadableEvent());
286 } 318 }
287 319
288 void GetActiveChannelCount(Kernel::HLERequestContext& ctx) { 320 void GetActiveChannelCount(Kernel::HLERequestContext& ctx) {
289 LOG_WARNING(Service_Audio, "(STUBBED) called"); 321 const auto& sink{system.AudioCore().GetOutputSink()};
322 u32 channel_count{sink.GetDeviceChannels()};
323
324 LOG_DEBUG(Service_Audio, "(STUBBED) called. Channels={}", channel_count);
290 325
291 IPC::ResponseBuilder rb{ctx, 3}; 326 IPC::ResponseBuilder rb{ctx, 3};
327
292 rb.Push(ResultSuccess); 328 rb.Push(ResultSuccess);
293 rb.Push<u32>(2); 329 rb.Push<u32>(channel_count);
294 } 330 }
295 331
296 // Should be similar to QueryAudioDeviceOutputEvent
297 void QueryAudioDeviceInputEvent(Kernel::HLERequestContext& ctx) { 332 void QueryAudioDeviceInputEvent(Kernel::HLERequestContext& ctx) {
298 LOG_WARNING(Service_Audio, "(STUBBED) called"); 333 LOG_DEBUG(Service_Audio, "(STUBBED) called");
299 334
300 IPC::ResponseBuilder rb{ctx, 2, 1}; 335 IPC::ResponseBuilder rb{ctx, 2, 1};
301 rb.Push(ResultSuccess); 336 rb.Push(ResultSuccess);
302 rb.PushCopyObjects(buffer_event->GetReadableEvent()); 337 rb.PushCopyObjects(event->GetReadableEvent());
303 } 338 }
304 339
305 void QueryAudioDeviceOutputEvent(Kernel::HLERequestContext& ctx) { 340 void QueryAudioDeviceOutputEvent(Kernel::HLERequestContext& ctx) {
@@ -307,402 +342,167 @@ private:
307 342
308 IPC::ResponseBuilder rb{ctx, 2, 1}; 343 IPC::ResponseBuilder rb{ctx, 2, 1};
309 rb.Push(ResultSuccess); 344 rb.Push(ResultSuccess);
310 rb.PushCopyObjects(buffer_event->GetReadableEvent()); 345 rb.PushCopyObjects(event->GetReadableEvent());
311 } 346 }
312 347
313 Kernel::KEvent* buffer_event; 348 void ListAudioOutputDeviceName(Kernel::HLERequestContext& ctx) {
314 u32_le revision = 0; 349 const size_t in_count = ctx.GetWriteBufferSize() / sizeof(AudioDevice::AudioDeviceName);
350
351 std::vector<AudioDevice::AudioDeviceName> out_names{};
352
353 u32 out_count = impl->ListAudioOutputDeviceName(out_names, in_count);
354
355 std::string out{};
356 for (u32 i = 0; i < out_count; i++) {
357 std::string a{};
358 u32 j = 0;
359 while (out_names[i].name[j] != '\0') {
360 a += out_names[i].name[j];
361 j++;
362 }
363 out += "\n\t" + a;
364 }
365
366 LOG_DEBUG(Service_Audio, "called.\nNames={}", out);
367
368 IPC::ResponseBuilder rb{ctx, 3};
369
370 ctx.WriteBuffer(out_names);
371
372 rb.Push(ResultSuccess);
373 rb.Push(out_count);
374 }
375
376 KernelHelpers::ServiceContext service_context;
377 std::unique_ptr<AudioDevice> impl;
378 Kernel::KEvent* event;
315}; 379};
316 380
317AudRenU::AudRenU(Core::System& system_) 381AudRenU::AudRenU(Core::System& system_)
318 : ServiceFramework{system_, "audren:u"}, service_context{system_, "audren:u"} { 382 : ServiceFramework{system_, "audren:u", ServiceThreadType::CreateNew},
383 service_context{system_, "audren:u"}, impl{std::make_unique<Manager>(system_)} {
319 // clang-format off 384 // clang-format off
320 static const FunctionInfo functions[] = { 385 static const FunctionInfo functions[] = {
321 {0, &AudRenU::OpenAudioRenderer, "OpenAudioRenderer"}, 386 {0, &AudRenU::OpenAudioRenderer, "OpenAudioRenderer"},
322 {1, &AudRenU::GetAudioRendererWorkBufferSize, "GetWorkBufferSize"}, 387 {1, &AudRenU::GetWorkBufferSize, "GetWorkBufferSize"},
323 {2, &AudRenU::GetAudioDeviceService, "GetAudioDeviceService"}, 388 {2, &AudRenU::GetAudioDeviceService, "GetAudioDeviceService"},
324 {3, &AudRenU::OpenAudioRendererForManualExecution, "OpenAudioRendererForManualExecution"}, 389 {3, nullptr, "OpenAudioRendererForManualExecution"},
325 {4, &AudRenU::GetAudioDeviceServiceWithRevisionInfo, "GetAudioDeviceServiceWithRevisionInfo"}, 390 {4, &AudRenU::GetAudioDeviceServiceWithRevisionInfo, "GetAudioDeviceServiceWithRevisionInfo"},
326 }; 391 };
327 // clang-format on 392 // clang-format on
328 393
329 RegisterHandlers(functions); 394 RegisterHandlers(functions);
330
331 buffer_event = service_context.CreateEvent("IAudioOutBufferReleasedEvent");
332} 395}
333 396
334AudRenU::~AudRenU() { 397AudRenU::~AudRenU() = default;
335 service_context.CloseEvent(buffer_event);
336}
337 398
338void AudRenU::OpenAudioRenderer(Kernel::HLERequestContext& ctx) { 399void AudRenU::OpenAudioRenderer(Kernel::HLERequestContext& ctx) {
339 LOG_DEBUG(Service_Audio, "called"); 400 IPC::RequestParser rp{ctx};
340
341 OpenAudioRendererImpl(ctx);
342}
343
344static u64 CalculateNumPerformanceEntries(const AudioCommon::AudioRendererParameter& params) {
345 // +1 represents the final mix.
346 return u64{params.effect_count} + params.submix_count + params.sink_count + params.voice_count +
347 1;
348}
349
350void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) {
351 LOG_DEBUG(Service_Audio, "called");
352
353 // Several calculations below align the sizes being calculated
354 // onto a 64 byte boundary.
355 static constexpr u64 buffer_alignment_size = 64;
356
357 // Some calculations that calculate portions of the buffer
358 // that will contain information, on the other hand, align
359 // the result of some of their calcularions on a 16 byte boundary.
360 static constexpr u64 info_field_alignment_size = 16;
361
362 // Maximum detail entries that may exist at one time for performance
363 // frame statistics.
364 static constexpr u64 max_perf_detail_entries = 100;
365
366 // Size of the data structure representing the bulk of the voice-related state.
367 static constexpr u64 voice_state_size_bytes = 0x100;
368
369 // Size of the upsampler manager data structure
370 constexpr u64 upsampler_manager_size = 0x48;
371
372 // Calculates the part of the size that relates to mix buffers.
373 const auto calculate_mix_buffer_sizes = [](const AudioCommon::AudioRendererParameter& params) {
374 // As of 8.0.0 this is the maximum on voice channels.
375 constexpr u64 max_voice_channels = 6;
376
377 // The service expects the sample_count member of the parameters to either be
378 // a value of 160 or 240, so the maximum sample count is assumed in order
379 // to adequately handle all values at runtime.
380 constexpr u64 default_max_sample_count = 240;
381
382 const u64 total_mix_buffers = params.mix_buffer_count + max_voice_channels;
383
384 u64 size = 0;
385 size += total_mix_buffers * (sizeof(s32) * params.sample_count);
386 size += total_mix_buffers * (sizeof(s32) * default_max_sample_count);
387 size += u64{params.submix_count} + params.sink_count;
388 size = Common::AlignUp(size, buffer_alignment_size);
389 size += Common::AlignUp(params.unknown_30, buffer_alignment_size);
390 size += Common::AlignUp(sizeof(s32) * params.mix_buffer_count, buffer_alignment_size);
391 return size;
392 };
393
394 // Calculates the portion of the size related to the mix data (and the sorting thereof).
395 const auto calculate_mix_info_size = [](const AudioCommon::AudioRendererParameter& params) {
396 // The size of the mixing info data structure.
397 constexpr u64 mix_info_size = 0x940;
398
399 // Consists of total submixes with the final mix included.
400 const u64 total_mix_count = u64{params.submix_count} + 1;
401
402 // The total number of effects that may be available to the audio renderer at any time.
403 constexpr u64 max_effects = 256;
404
405 // Calculates the part of the size related to the audio node state.
406 // This will only be used if the audio revision supports the splitter.
407 const auto calculate_node_state_size = [](std::size_t num_nodes) {
408 // Internally within a nodestate, it appears to use a data structure
409 // similar to a std::bitset<64> twice.
410 constexpr u64 bit_size = Common::BitSize<u64>();
411 constexpr u64 num_bitsets = 2;
412
413 // Node state instances have three states internally for performing
414 // depth-first searches of nodes. Initialized, Found, and Done Sorting.
415 constexpr u64 num_states = 3;
416
417 u64 size = 0;
418 size += (num_nodes * num_nodes) * sizeof(s32);
419 size += num_states * (num_nodes * sizeof(s32));
420 size += num_bitsets * (Common::AlignUp(num_nodes, bit_size) / Common::BitSize<u8>());
421 return size;
422 };
423
424 // Calculates the part of the size related to the adjacency (aka edge) matrix.
425 const auto calculate_edge_matrix_size = [](std::size_t num_nodes) {
426 return (num_nodes * num_nodes) * sizeof(s32);
427 };
428
429 u64 size = 0;
430 size += Common::AlignUp(sizeof(void*) * total_mix_count, info_field_alignment_size);
431 size += Common::AlignUp(mix_info_size * total_mix_count, info_field_alignment_size);
432 size += Common::AlignUp(sizeof(s32) * max_effects * params.submix_count,
433 info_field_alignment_size);
434
435 if (IsFeatureSupported(AudioFeatures::Splitter, params.revision)) {
436 size += Common::AlignUp(calculate_node_state_size(total_mix_count) +
437 calculate_edge_matrix_size(total_mix_count),
438 info_field_alignment_size);
439 }
440
441 return size;
442 };
443
444 // Calculates the part of the size related to voice channel info.
445 const auto calculate_voice_info_size = [](const AudioCommon::AudioRendererParameter& params) {
446 constexpr u64 voice_info_size = 0x220;
447 constexpr u64 voice_resource_size = 0xD0;
448
449 u64 size = 0;
450 size += Common::AlignUp(sizeof(void*) * params.voice_count, info_field_alignment_size);
451 size += Common::AlignUp(voice_info_size * params.voice_count, info_field_alignment_size);
452 size +=
453 Common::AlignUp(voice_resource_size * params.voice_count, info_field_alignment_size);
454 size +=
455 Common::AlignUp(voice_state_size_bytes * params.voice_count, info_field_alignment_size);
456 return size;
457 };
458
459 // Calculates the part of the size related to memory pools.
460 const auto calculate_memory_pools_size = [](const AudioCommon::AudioRendererParameter& params) {
461 const u64 num_memory_pools = sizeof(s32) * (u64{params.effect_count} + params.voice_count);
462 const u64 memory_pool_info_size = 0x20;
463 return Common::AlignUp(num_memory_pools * memory_pool_info_size, info_field_alignment_size);
464 };
465
466 // Calculates the part of the size related to the splitter context.
467 const auto calculate_splitter_context_size =
468 [](const AudioCommon::AudioRendererParameter& params) -> u64 {
469 if (!IsFeatureSupported(AudioFeatures::Splitter, params.revision)) {
470 return 0;
471 }
472
473 constexpr u64 splitter_info_size = 0x20;
474 constexpr u64 splitter_destination_data_size = 0xE0;
475
476 u64 size = 0;
477 size += params.num_splitter_send_channels;
478 size +=
479 Common::AlignUp(splitter_info_size * params.splitter_count, info_field_alignment_size);
480 size += Common::AlignUp(splitter_destination_data_size * params.num_splitter_send_channels,
481 info_field_alignment_size);
482
483 return size;
484 };
485
486 // Calculates the part of the size related to the upsampler info.
487 const auto calculate_upsampler_info_size =
488 [](const AudioCommon::AudioRendererParameter& params) {
489 constexpr u64 upsampler_info_size = 0x280;
490 // Yes, using the buffer size over info alignment size is intentional here.
491 return Common::AlignUp(upsampler_info_size *
492 (u64{params.submix_count} + params.sink_count),
493 buffer_alignment_size);
494 };
495
496 // Calculates the part of the size related to effect info.
497 const auto calculate_effect_info_size = [](const AudioCommon::AudioRendererParameter& params) {
498 constexpr u64 effect_info_size = 0x2B0;
499 return Common::AlignUp(effect_info_size * params.effect_count, info_field_alignment_size);
500 };
501
502 // Calculates the part of the size related to audio sink info.
503 const auto calculate_sink_info_size = [](const AudioCommon::AudioRendererParameter& params) {
504 const u64 sink_info_size = 0x170;
505 return Common::AlignUp(sink_info_size * params.sink_count, info_field_alignment_size);
506 };
507
508 // Calculates the part of the size related to voice state info.
509 const auto calculate_voice_state_size = [](const AudioCommon::AudioRendererParameter& params) {
510 const u64 voice_state_size = 0x100;
511 const u64 additional_size = buffer_alignment_size - 1;
512 return Common::AlignUp(voice_state_size * params.voice_count + additional_size,
513 info_field_alignment_size);
514 };
515
516 // Calculates the part of the size related to performance statistics.
517 const auto calculate_perf_size = [](const AudioCommon::AudioRendererParameter& params) {
518 // Extra size value appended to the end of the calculation.
519 constexpr u64 appended = 128;
520
521 // Whether or not we assume the newer version of performance metrics data structures.
522 const bool is_v2 =
523 IsFeatureSupported(AudioFeatures::PerformanceMetricsVersion2, params.revision);
524
525 // Data structure sizes
526 constexpr u64 perf_statistics_size = 0x0C;
527 const u64 header_size = is_v2 ? 0x30 : 0x18;
528 const u64 entry_size = is_v2 ? 0x18 : 0x10;
529 const u64 detail_size = is_v2 ? 0x18 : 0x10;
530
531 const u64 entry_count = CalculateNumPerformanceEntries(params);
532 const u64 size_per_frame =
533 header_size + (entry_size * entry_count) + (detail_size * max_perf_detail_entries);
534
535 u64 size = 0;
536 size += Common::AlignUp(size_per_frame * params.performance_frame_count + 1,
537 buffer_alignment_size);
538 size += Common::AlignUp(perf_statistics_size, buffer_alignment_size);
539 size += appended;
540 return size;
541 };
542
543 // Calculates the part of the size that relates to the audio command buffer.
544 const auto calculate_command_buffer_size =
545 [](const AudioCommon::AudioRendererParameter& params) {
546 constexpr u64 alignment = (buffer_alignment_size - 1) * 2;
547
548 if (!IsFeatureSupported(AudioFeatures::VariadicCommandBuffer, params.revision)) {
549 constexpr u64 command_buffer_size = 0x18000;
550
551 return command_buffer_size + alignment;
552 }
553
554 // When the variadic command buffer is supported, this means
555 // the command generator for the audio renderer can issue commands
556 // that are (as one would expect), variable in size. So what we need to do
557 // is determine the maximum possible size for a few command data structures
558 // then multiply them by the amount of present commands indicated by the given
559 // respective audio parameters.
560
561 constexpr u64 max_biquad_filters = 2;
562 constexpr u64 max_mix_buffers = 24;
563
564 constexpr u64 biquad_filter_command_size = 0x2C;
565
566 constexpr u64 depop_mix_command_size = 0x24;
567 constexpr u64 depop_setup_command_size = 0x50;
568
569 constexpr u64 effect_command_max_size = 0x540;
570
571 constexpr u64 mix_command_size = 0x1C;
572 constexpr u64 mix_ramp_command_size = 0x24;
573 constexpr u64 mix_ramp_grouped_command_size = 0x13C;
574
575 constexpr u64 perf_command_size = 0x28;
576
577 constexpr u64 sink_command_size = 0x130;
578
579 constexpr u64 submix_command_max_size =
580 depop_mix_command_size + (mix_command_size * max_mix_buffers) * max_mix_buffers;
581
582 constexpr u64 volume_command_size = 0x1C;
583 constexpr u64 volume_ramp_command_size = 0x20;
584
585 constexpr u64 voice_biquad_filter_command_size =
586 biquad_filter_command_size * max_biquad_filters;
587 constexpr u64 voice_data_command_size = 0x9C;
588 const u64 voice_command_max_size =
589 (params.splitter_count * depop_setup_command_size) +
590 (voice_data_command_size + voice_biquad_filter_command_size +
591 volume_ramp_command_size + mix_ramp_grouped_command_size);
592 401
593 // Now calculate the individual elements that comprise the size and add them together. 402 AudioCore::AudioRendererParameterInternal params;
594 const u64 effect_commands_size = params.effect_count * effect_command_max_size; 403 rp.PopRaw<AudioCore::AudioRendererParameterInternal>(params);
404 auto transfer_memory_handle = ctx.GetCopyHandle(0);
405 auto process_handle = ctx.GetCopyHandle(1);
406 auto transfer_memory_size = rp.Pop<u64>();
407 auto applet_resource_user_id = rp.Pop<u64>();
595 408
596 const u64 final_mix_commands_size = 409 if (impl->GetSessionCount() + 1 > AudioCore::MaxRendererSessions) {
597 depop_mix_command_size + volume_command_size * max_mix_buffers; 410 LOG_ERROR(Service_Audio, "Too many AudioRenderer sessions open!");
411 IPC::ResponseBuilder rb{ctx, 2};
412 rb.Push(ERR_MAXIMUM_SESSIONS_REACHED);
413 return;
414 }
598 415
599 const u64 perf_commands_size = 416 const auto& handle_table{system.CurrentProcess()->GetHandleTable()};
600 perf_command_size * 417 auto process{handle_table.GetObject<Kernel::KProcess>(process_handle)};
601 (CalculateNumPerformanceEntries(params) + max_perf_detail_entries); 418 auto transfer_memory{
419 process->GetHandleTable().GetObject<Kernel::KTransferMemory>(transfer_memory_handle)};
602 420
603 const u64 sink_commands_size = params.sink_count * sink_command_size; 421 const auto session_id{impl->GetSessionId()};
422 if (session_id == -1) {
423 LOG_ERROR(Service_Audio, "Tried to open a session that's already in use!");
424 IPC::ResponseBuilder rb{ctx, 2};
425 rb.Push(ERR_MAXIMUM_SESSIONS_REACHED);
426 return;
427 }
604 428
605 const u64 splitter_commands_size = 429 LOG_DEBUG(Service_Audio, "Opened new AudioRenderer session {} sessions open {}", session_id,
606 params.num_splitter_send_channels * max_mix_buffers * mix_ramp_command_size; 430 impl->GetSessionCount());
607 431
608 const u64 submix_commands_size = params.submix_count * submix_command_max_size; 432 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
433 rb.Push(ResultSuccess);
434 rb.PushIpcInterface<IAudioRenderer>(system, *impl, params, transfer_memory.GetPointerUnsafe(),
435 transfer_memory_size, process_handle,
436 applet_resource_user_id, session_id);
437}
609 438
610 const u64 voice_commands_size = params.voice_count * voice_command_max_size; 439void AudRenU::GetWorkBufferSize(Kernel::HLERequestContext& ctx) {
611 440 AudioCore::AudioRendererParameterInternal params;
612 return effect_commands_size + final_mix_commands_size + perf_commands_size +
613 sink_commands_size + splitter_commands_size + submix_commands_size +
614 voice_commands_size + alignment;
615 };
616 441
617 IPC::RequestParser rp{ctx}; 442 IPC::RequestParser rp{ctx};
618 const auto params = rp.PopRaw<AudioCommon::AudioRendererParameter>(); 443 rp.PopRaw<AudioCore::AudioRendererParameterInternal>(params);
619 444
620 u64 size = 0; 445 u64 size{0};
621 size += calculate_mix_buffer_sizes(params); 446 auto result = impl->GetWorkBufferSize(params, size);
622 size += calculate_mix_info_size(params); 447
623 size += calculate_voice_info_size(params); 448 std::string output_info{};
624 size += upsampler_manager_size; 449 output_info += fmt::format("\tRevision {}", AudioCore::GetRevisionNum(params.revision));
625 size += calculate_memory_pools_size(params); 450 output_info +=
626 size += calculate_splitter_context_size(params); 451 fmt::format("\n\tSample Rate {}, Sample Count {}", params.sample_rate, params.sample_count);
627 452 output_info += fmt::format("\n\tExecution Mode {}, Voice Drop Enabled {}",
628 size = Common::AlignUp(size, buffer_alignment_size); 453 static_cast<u32>(params.execution_mode), params.voice_drop_enabled);
629 454 output_info += fmt::format(
630 size += calculate_upsampler_info_size(params); 455 "\n\tSizes: Effects {:04X}, Mixes {:04X}, Sinks {:04X}, Submixes {:04X}, Splitter Infos "
631 size += calculate_effect_info_size(params); 456 "{:04X}, Splitter Destinations {:04X}, Voices {:04X}, Performance Frames {:04X} External "
632 size += calculate_sink_info_size(params); 457 "Context {:04X}",
633 size += calculate_voice_state_size(params); 458 params.effects, params.mixes, params.sinks, params.sub_mixes, params.splitter_infos,
634 size += calculate_perf_size(params); 459 params.splitter_destinations, params.voices, params.perf_frames,
635 size += calculate_command_buffer_size(params); 460 params.external_context_size);
636 461
637 // finally, 4KB page align the size, and we're done. 462 LOG_DEBUG(Service_Audio, "called.\nInput params:\n{}\nOutput params:\n\tWorkbuffer size {:08X}",
638 size = Common::AlignUp(size, 4096); 463 output_info, size);
639 464
640 IPC::ResponseBuilder rb{ctx, 4}; 465 IPC::ResponseBuilder rb{ctx, 4};
641 rb.Push(ResultSuccess); 466 rb.Push(result);
642 rb.Push<u64>(size); 467 rb.Push<u64>(size);
643
644 LOG_DEBUG(Service_Audio, "buffer_size=0x{:X}", size);
645} 468}
646 469
647void AudRenU::GetAudioDeviceService(Kernel::HLERequestContext& ctx) { 470void AudRenU::GetAudioDeviceService(Kernel::HLERequestContext& ctx) {
648 IPC::RequestParser rp{ctx}; 471 IPC::RequestParser rp{ctx};
649 const u64 aruid = rp.Pop<u64>();
650 472
651 LOG_DEBUG(Service_Audio, "called. aruid={:016X}", aruid); 473 const auto applet_resource_user_id = rp.Pop<u64>();
474
475 LOG_DEBUG(Service_Audio, "called. Applet resource id {}", applet_resource_user_id);
652 476
653 // Revisionless variant of GetAudioDeviceServiceWithRevisionInfo that
654 // always assumes the initial release revision (REV1).
655 IPC::ResponseBuilder rb{ctx, 2, 0, 1}; 477 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
478
656 rb.Push(ResultSuccess); 479 rb.Push(ResultSuccess);
657 rb.PushIpcInterface<IAudioDevice>(system, buffer_event, Common::MakeMagic('R', 'E', 'V', '1')); 480 rb.PushIpcInterface<IAudioDevice>(system, applet_resource_user_id,
481 ::Common::MakeMagic('R', 'E', 'V', '1'), num_audio_devices++);
658} 482}
659 483
660void AudRenU::OpenAudioRendererForManualExecution(Kernel::HLERequestContext& ctx) { 484void AudRenU::OpenAudioRendererForManualExecution(Kernel::HLERequestContext& ctx) {
661 LOG_DEBUG(Service_Audio, "called"); 485 LOG_DEBUG(Service_Audio, "called");
662
663 OpenAudioRendererImpl(ctx);
664} 486}
665 487
666void AudRenU::GetAudioDeviceServiceWithRevisionInfo(Kernel::HLERequestContext& ctx) { 488void AudRenU::GetAudioDeviceServiceWithRevisionInfo(Kernel::HLERequestContext& ctx) {
667 struct Parameters { 489 struct Parameters {
668 u32 revision; 490 u32 revision;
669 u64 aruid; 491 u64 applet_resource_user_id;
670 }; 492 };
671 493
672 IPC::RequestParser rp{ctx}; 494 IPC::RequestParser rp{ctx};
673 const auto [revision, aruid] = rp.PopRaw<Parameters>();
674 495
675 LOG_DEBUG(Service_Audio, "called. revision={:08X}, aruid={:016X}", revision, aruid); 496 const auto [revision, applet_resource_user_id] = rp.PopRaw<Parameters>();
676 497
677 IPC::ResponseBuilder rb{ctx, 2, 0, 1}; 498 LOG_DEBUG(Service_Audio, "called. Revision {} Applet resource id {}",
678 rb.Push(ResultSuccess); 499 AudioCore::GetRevisionNum(revision), applet_resource_user_id);
679 rb.PushIpcInterface<IAudioDevice>(system, buffer_event, revision);
680}
681 500
682void AudRenU::OpenAudioRendererImpl(Kernel::HLERequestContext& ctx) {
683 IPC::RequestParser rp{ctx};
684 const auto params = rp.PopRaw<AudioCommon::AudioRendererParameter>();
685 IPC::ResponseBuilder rb{ctx, 2, 0, 1}; 501 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
686 502
687 rb.Push(ResultSuccess); 503 rb.Push(ResultSuccess);
688 rb.PushIpcInterface<IAudioRenderer>(system, params, audren_instance_count++); 504 rb.PushIpcInterface<IAudioDevice>(system, applet_resource_user_id, revision,
689} 505 num_audio_devices++);
690
691bool IsFeatureSupported(AudioFeatures feature, u32_le revision) {
692 // Byte swap
693 const u32_be version_num = revision - Common::MakeMagic('R', 'E', 'V', '0');
694
695 switch (feature) {
696 case AudioFeatures::AudioUSBDeviceOutput:
697 return version_num >= 4U;
698 case AudioFeatures::Splitter:
699 return version_num >= 2U;
700 case AudioFeatures::PerformanceMetricsVersion2:
701 case AudioFeatures::VariadicCommandBuffer:
702 return version_num >= 5U;
703 default:
704 return false;
705 }
706} 506}
707 507
708} // namespace Service::Audio 508} // namespace Service::Audio
diff --git a/src/core/hle/service/audio/audren_u.h b/src/core/hle/service/audio/audren_u.h
index 869d39002..4384a9b3c 100644
--- a/src/core/hle/service/audio/audren_u.h
+++ b/src/core/hle/service/audio/audren_u.h
@@ -3,6 +3,7 @@
3 3
4#pragma once 4#pragma once
5 5
6#include "audio_core/audio_render_manager.h"
6#include "core/hle/service/kernel_helpers.h" 7#include "core/hle/service/kernel_helpers.h"
7#include "core/hle/service/service.h" 8#include "core/hle/service/service.h"
8 9
@@ -15,6 +16,7 @@ class HLERequestContext;
15} 16}
16 17
17namespace Service::Audio { 18namespace Service::Audio {
19class IAudioRenderer;
18 20
19class AudRenU final : public ServiceFramework<AudRenU> { 21class AudRenU final : public ServiceFramework<AudRenU> {
20public: 22public:
@@ -23,28 +25,14 @@ public:
23 25
24private: 26private:
25 void OpenAudioRenderer(Kernel::HLERequestContext& ctx); 27 void OpenAudioRenderer(Kernel::HLERequestContext& ctx);
26 void GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx); 28 void GetWorkBufferSize(Kernel::HLERequestContext& ctx);
27 void GetAudioDeviceService(Kernel::HLERequestContext& ctx); 29 void GetAudioDeviceService(Kernel::HLERequestContext& ctx);
28 void OpenAudioRendererForManualExecution(Kernel::HLERequestContext& ctx); 30 void OpenAudioRendererForManualExecution(Kernel::HLERequestContext& ctx);
29 void GetAudioDeviceServiceWithRevisionInfo(Kernel::HLERequestContext& ctx); 31 void GetAudioDeviceServiceWithRevisionInfo(Kernel::HLERequestContext& ctx);
30 32
31 void OpenAudioRendererImpl(Kernel::HLERequestContext& ctx);
32
33 KernelHelpers::ServiceContext service_context; 33 KernelHelpers::ServiceContext service_context;
34 34 std::unique_ptr<AudioCore::AudioRenderer::Manager> impl;
35 std::size_t audren_instance_count = 0; 35 u32 num_audio_devices{0};
36 Kernel::KEvent* buffer_event;
37}; 36};
38 37
39// Describes a particular audio feature that may be supported in a particular revision.
40enum class AudioFeatures : u32 {
41 AudioUSBDeviceOutput,
42 Splitter,
43 PerformanceMetricsVersion2,
44 VariadicCommandBuffer,
45};
46
47// Tests if a particular audio feature is supported with a given audio revision.
48bool IsFeatureSupported(AudioFeatures feature, u32_le revision);
49
50} // namespace Service::Audio 38} // namespace Service::Audio
diff --git a/src/core/hle/service/audio/errors.h b/src/core/hle/service/audio/errors.h
index ac6c514af..d706978cb 100644
--- a/src/core/hle/service/audio/errors.h
+++ b/src/core/hle/service/audio/errors.h
@@ -7,8 +7,17 @@
7 7
8namespace Service::Audio { 8namespace Service::Audio {
9 9
10constexpr Result ERR_INVALID_DEVICE_NAME{ErrorModule::Audio, 1};
10constexpr Result ERR_OPERATION_FAILED{ErrorModule::Audio, 2}; 11constexpr Result ERR_OPERATION_FAILED{ErrorModule::Audio, 2};
12constexpr Result ERR_INVALID_SAMPLE_RATE{ErrorModule::Audio, 3};
13constexpr Result ERR_INSUFFICIENT_BUFFER_SIZE{ErrorModule::Audio, 4};
14constexpr Result ERR_MAXIMUM_SESSIONS_REACHED{ErrorModule::Audio, 5};
11constexpr Result ERR_BUFFER_COUNT_EXCEEDED{ErrorModule::Audio, 8}; 15constexpr Result ERR_BUFFER_COUNT_EXCEEDED{ErrorModule::Audio, 8};
16constexpr Result ERR_INVALID_CHANNEL_COUNT{ErrorModule::Audio, 10};
17constexpr Result ERR_INVALID_UPDATE_DATA{ErrorModule::Audio, 41};
18constexpr Result ERR_POOL_MAPPING_FAILED{ErrorModule::Audio, 42};
12constexpr Result ERR_NOT_SUPPORTED{ErrorModule::Audio, 513}; 19constexpr Result ERR_NOT_SUPPORTED{ErrorModule::Audio, 513};
20constexpr Result ERR_INVALID_PROCESS_HANDLE{ErrorModule::Audio, 1536};
21constexpr Result ERR_INVALID_REVISION{ErrorModule::Audio, 1537};
13 22
14} // namespace Service::Audio 23} // namespace Service::Audio
diff --git a/src/core/hle/service/audio/hwopus.cpp b/src/core/hle/service/audio/hwopus.cpp
index 75da659e5..4f2ed2d52 100644
--- a/src/core/hle/service/audio/hwopus.cpp
+++ b/src/core/hle/service/audio/hwopus.cpp
@@ -298,7 +298,7 @@ void HwOpus::OpenHardwareOpusDecoderEx(Kernel::HLERequestContext& ctx) {
298 const auto sample_rate = rp.Pop<u32>(); 298 const auto sample_rate = rp.Pop<u32>();
299 const auto channel_count = rp.Pop<u32>(); 299 const auto channel_count = rp.Pop<u32>();
300 300
301 LOG_CRITICAL(Audio, "called sample_rate={}, channel_count={}", sample_rate, channel_count); 301 LOG_DEBUG(Audio, "called sample_rate={}, channel_count={}", sample_rate, channel_count);
302 302
303 ASSERT_MSG(sample_rate == 48000 || sample_rate == 24000 || sample_rate == 16000 || 303 ASSERT_MSG(sample_rate == 48000 || sample_rate == 24000 || sample_rate == 16000 ||
304 sample_rate == 12000 || sample_rate == 8000, 304 sample_rate == 12000 || sample_rate == 8000,
diff --git a/src/core/memory.cpp b/src/core/memory.cpp
index 584808d50..635449fce 100644
--- a/src/core/memory.cpp
+++ b/src/core/memory.cpp
@@ -511,7 +511,7 @@ struct Memory::Impl {
511 511
512 [[nodiscard]] u8* GetPointerImpl(VAddr vaddr, auto on_unmapped, auto on_rasterizer) const { 512 [[nodiscard]] u8* GetPointerImpl(VAddr vaddr, auto on_unmapped, auto on_rasterizer) const {
513 // AARCH64 masks the upper 16 bit of all memory accesses 513 // AARCH64 masks the upper 16 bit of all memory accesses
514 vaddr &= 0xffffffffffffLL; 514 vaddr &= 0xffffffffffffULL;
515 515
516 if (vaddr >= 1uLL << current_page_table->GetAddressSpaceBits()) { 516 if (vaddr >= 1uLL << current_page_table->GetAddressSpaceBits()) {
517 on_unmapped(); 517 on_unmapped();
@@ -776,6 +776,10 @@ void Memory::CopyBlock(const Kernel::KProcess& process, VAddr dest_addr, VAddr s
776 impl->CopyBlock(process, dest_addr, src_addr, size); 776 impl->CopyBlock(process, dest_addr, src_addr, size);
777} 777}
778 778
779void Memory::ZeroBlock(const Kernel::KProcess& process, VAddr dest_addr, const std::size_t size) {
780 impl->ZeroBlock(process, dest_addr, size);
781}
782
779void Memory::RasterizerMarkRegionCached(VAddr vaddr, u64 size, bool cached) { 783void Memory::RasterizerMarkRegionCached(VAddr vaddr, u64 size, bool cached) {
780 impl->RasterizerMarkRegionCached(vaddr, size, cached); 784 impl->RasterizerMarkRegionCached(vaddr, size, cached);
781} 785}
diff --git a/src/core/memory.h b/src/core/memory.h
index f22c0a2d8..780c45385 100644
--- a/src/core/memory.h
+++ b/src/core/memory.h
@@ -437,6 +437,19 @@ public:
437 std::size_t size); 437 std::size_t size);
438 438
439 /** 439 /**
440 * Zeros a range of bytes within the current process' address space at the specified
441 * virtual address.
442 *
443 * @param process The process that will have data zeroed within its address space.
444 * @param dest_addr The destination virtual address to zero the data from.
445 * @param size The size of the range to zero out, in bytes.
446 *
447 * @post The range [dest_addr, size) within the process' address space contains the
448 * value 0.
449 */
450 void ZeroBlock(const Kernel::KProcess& process, VAddr dest_addr, std::size_t size);
451
452 /**
440 * Marks each page within the specified address range as cached or uncached. 453 * Marks each page within the specified address range as cached or uncached.
441 * 454 *
442 * @param vaddr The virtual address indicating the start of the address range. 455 * @param vaddr The virtual address indicating the start of the address range.
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index 0a61839da..2840bc5eb 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -372,8 +372,9 @@ void Config::ReadAudioValues() {
372 qt_config->beginGroup(QStringLiteral("Audio")); 372 qt_config->beginGroup(QStringLiteral("Audio"));
373 373
374 if (global) { 374 if (global) {
375 ReadBasicSetting(Settings::values.audio_device_id);
376 ReadBasicSetting(Settings::values.sink_id); 375 ReadBasicSetting(Settings::values.sink_id);
376 ReadBasicSetting(Settings::values.audio_output_device_id);
377 ReadBasicSetting(Settings::values.audio_input_device_id);
377 } 378 }
378 ReadGlobalSetting(Settings::values.volume); 379 ReadGlobalSetting(Settings::values.volume);
379 380
@@ -1027,7 +1028,8 @@ void Config::SaveAudioValues() {
1027 1028
1028 if (global) { 1029 if (global) {
1029 WriteBasicSetting(Settings::values.sink_id); 1030 WriteBasicSetting(Settings::values.sink_id);
1030 WriteBasicSetting(Settings::values.audio_device_id); 1031 WriteBasicSetting(Settings::values.audio_output_device_id);
1032 WriteBasicSetting(Settings::values.audio_input_device_id);
1031 } 1033 }
1032 WriteGlobalSetting(Settings::values.volume); 1034 WriteGlobalSetting(Settings::values.volume);
1033 1035
diff --git a/src/yuzu/configuration/configure_audio.cpp b/src/yuzu/configuration/configure_audio.cpp
index 512bdfc22..19b8b15ef 100644
--- a/src/yuzu/configuration/configure_audio.cpp
+++ b/src/yuzu/configuration/configure_audio.cpp
@@ -3,8 +3,8 @@
3 3
4#include <memory> 4#include <memory>
5 5
6#include "audio_core/sink.h" 6#include "audio_core/sink/sink.h"
7#include "audio_core/sink_details.h" 7#include "audio_core/sink/sink_details.h"
8#include "common/settings.h" 8#include "common/settings.h"
9#include "core/core.h" 9#include "core/core.h"
10#include "ui_configure_audio.h" 10#include "ui_configure_audio.h"
@@ -15,11 +15,11 @@ ConfigureAudio::ConfigureAudio(const Core::System& system_, QWidget* parent)
15 : QWidget(parent), ui(std::make_unique<Ui::ConfigureAudio>()), system{system_} { 15 : QWidget(parent), ui(std::make_unique<Ui::ConfigureAudio>()), system{system_} {
16 ui->setupUi(this); 16 ui->setupUi(this);
17 17
18 InitializeAudioOutputSinkComboBox(); 18 InitializeAudioSinkComboBox();
19 19
20 connect(ui->volume_slider, &QSlider::valueChanged, this, 20 connect(ui->volume_slider, &QSlider::valueChanged, this,
21 &ConfigureAudio::SetVolumeIndicatorText); 21 &ConfigureAudio::SetVolumeIndicatorText);
22 connect(ui->output_sink_combo_box, qOverload<int>(&QComboBox::currentIndexChanged), this, 22 connect(ui->sink_combo_box, qOverload<int>(&QComboBox::currentIndexChanged), this,
23 &ConfigureAudio::UpdateAudioDevices); 23 &ConfigureAudio::UpdateAudioDevices);
24 24
25 ui->volume_label->setVisible(Settings::IsConfiguringGlobal()); 25 ui->volume_label->setVisible(Settings::IsConfiguringGlobal());
@@ -30,8 +30,9 @@ ConfigureAudio::ConfigureAudio(const Core::System& system_, QWidget* parent)
30 SetConfiguration(); 30 SetConfiguration();
31 31
32 const bool is_powered_on = system_.IsPoweredOn(); 32 const bool is_powered_on = system_.IsPoweredOn();
33 ui->output_sink_combo_box->setEnabled(!is_powered_on); 33 ui->sink_combo_box->setEnabled(!is_powered_on);
34 ui->audio_device_combo_box->setEnabled(!is_powered_on); 34 ui->output_combo_box->setEnabled(!is_powered_on);
35 ui->input_combo_box->setEnabled(!is_powered_on);
35} 36}
36 37
37ConfigureAudio::~ConfigureAudio() = default; 38ConfigureAudio::~ConfigureAudio() = default;
@@ -40,9 +41,9 @@ void ConfigureAudio::SetConfiguration() {
40 SetOutputSinkFromSinkID(); 41 SetOutputSinkFromSinkID();
41 42
42 // The device list cannot be pre-populated (nor listed) until the output sink is known. 43 // The device list cannot be pre-populated (nor listed) until the output sink is known.
43 UpdateAudioDevices(ui->output_sink_combo_box->currentIndex()); 44 UpdateAudioDevices(ui->sink_combo_box->currentIndex());
44 45
45 SetAudioDeviceFromDeviceID(); 46 SetAudioDevicesFromDeviceID();
46 47
47 const auto volume_value = static_cast<int>(Settings::values.volume.GetValue()); 48 const auto volume_value = static_cast<int>(Settings::values.volume.GetValue());
48 ui->volume_slider->setValue(volume_value); 49 ui->volume_slider->setValue(volume_value);
@@ -62,32 +63,45 @@ void ConfigureAudio::SetConfiguration() {
62} 63}
63 64
64void ConfigureAudio::SetOutputSinkFromSinkID() { 65void ConfigureAudio::SetOutputSinkFromSinkID() {
65 [[maybe_unused]] const QSignalBlocker blocker(ui->output_sink_combo_box); 66 [[maybe_unused]] const QSignalBlocker blocker(ui->sink_combo_box);
66 67
67 int new_sink_index = 0; 68 int new_sink_index = 0;
68 const QString sink_id = QString::fromStdString(Settings::values.sink_id.GetValue()); 69 const QString sink_id = QString::fromStdString(Settings::values.sink_id.GetValue());
69 for (int index = 0; index < ui->output_sink_combo_box->count(); index++) { 70 for (int index = 0; index < ui->sink_combo_box->count(); index++) {
70 if (ui->output_sink_combo_box->itemText(index) == sink_id) { 71 if (ui->sink_combo_box->itemText(index) == sink_id) {
71 new_sink_index = index; 72 new_sink_index = index;
72 break; 73 break;
73 } 74 }
74 } 75 }
75 76
76 ui->output_sink_combo_box->setCurrentIndex(new_sink_index); 77 ui->sink_combo_box->setCurrentIndex(new_sink_index);
77} 78}
78 79
79void ConfigureAudio::SetAudioDeviceFromDeviceID() { 80void ConfigureAudio::SetAudioDevicesFromDeviceID() {
80 int new_device_index = -1; 81 int new_device_index = -1;
81 82
82 const QString device_id = QString::fromStdString(Settings::values.audio_device_id.GetValue()); 83 const QString output_device_id =
83 for (int index = 0; index < ui->audio_device_combo_box->count(); index++) { 84 QString::fromStdString(Settings::values.audio_output_device_id.GetValue());
84 if (ui->audio_device_combo_box->itemText(index) == device_id) { 85 for (int index = 0; index < ui->output_combo_box->count(); index++) {
86 if (ui->output_combo_box->itemText(index) == output_device_id) {
85 new_device_index = index; 87 new_device_index = index;
86 break; 88 break;
87 } 89 }
88 } 90 }
89 91
90 ui->audio_device_combo_box->setCurrentIndex(new_device_index); 92 ui->output_combo_box->setCurrentIndex(new_device_index);
93
94 new_device_index = -1;
95 const QString input_device_id =
96 QString::fromStdString(Settings::values.audio_input_device_id.GetValue());
97 for (int index = 0; index < ui->input_combo_box->count(); index++) {
98 if (ui->input_combo_box->itemText(index) == input_device_id) {
99 new_device_index = index;
100 break;
101 }
102 }
103
104 ui->input_combo_box->setCurrentIndex(new_device_index);
91} 105}
92 106
93void ConfigureAudio::SetVolumeIndicatorText(int percentage) { 107void ConfigureAudio::SetVolumeIndicatorText(int percentage) {
@@ -95,14 +109,13 @@ void ConfigureAudio::SetVolumeIndicatorText(int percentage) {
95} 109}
96 110
97void ConfigureAudio::ApplyConfiguration() { 111void ConfigureAudio::ApplyConfiguration() {
98
99 if (Settings::IsConfiguringGlobal()) { 112 if (Settings::IsConfiguringGlobal()) {
100 Settings::values.sink_id = 113 Settings::values.sink_id =
101 ui->output_sink_combo_box->itemText(ui->output_sink_combo_box->currentIndex()) 114 ui->sink_combo_box->itemText(ui->sink_combo_box->currentIndex()).toStdString();
102 .toStdString(); 115 Settings::values.audio_output_device_id.SetValue(
103 Settings::values.audio_device_id.SetValue( 116 ui->output_combo_box->itemText(ui->output_combo_box->currentIndex()).toStdString());
104 ui->audio_device_combo_box->itemText(ui->audio_device_combo_box->currentIndex()) 117 Settings::values.audio_input_device_id.SetValue(
105 .toStdString()); 118 ui->input_combo_box->itemText(ui->input_combo_box->currentIndex()).toStdString());
106 119
107 // Guard if during game and set to game-specific value 120 // Guard if during game and set to game-specific value
108 if (Settings::values.volume.UsingGlobal()) { 121 if (Settings::values.volume.UsingGlobal()) {
@@ -129,21 +142,27 @@ void ConfigureAudio::changeEvent(QEvent* event) {
129} 142}
130 143
131void ConfigureAudio::UpdateAudioDevices(int sink_index) { 144void ConfigureAudio::UpdateAudioDevices(int sink_index) {
132 ui->audio_device_combo_box->clear(); 145 ui->output_combo_box->clear();
133 ui->audio_device_combo_box->addItem(QString::fromUtf8(AudioCore::auto_device_name)); 146 ui->output_combo_box->addItem(QString::fromUtf8(AudioCore::Sink::auto_device_name));
147
148 const std::string sink_id = ui->sink_combo_box->itemText(sink_index).toStdString();
149 for (const auto& device : AudioCore::Sink::GetDeviceListForSink(sink_id, false)) {
150 ui->output_combo_box->addItem(QString::fromStdString(device));
151 }
134 152
135 const std::string sink_id = ui->output_sink_combo_box->itemText(sink_index).toStdString(); 153 ui->input_combo_box->clear();
136 for (const auto& device : AudioCore::GetDeviceListForSink(sink_id)) { 154 ui->input_combo_box->addItem(QString::fromUtf8(AudioCore::Sink::auto_device_name));
137 ui->audio_device_combo_box->addItem(QString::fromStdString(device)); 155 for (const auto& device : AudioCore::Sink::GetDeviceListForSink(sink_id, true)) {
156 ui->input_combo_box->addItem(QString::fromStdString(device));
138 } 157 }
139} 158}
140 159
141void ConfigureAudio::InitializeAudioOutputSinkComboBox() { 160void ConfigureAudio::InitializeAudioSinkComboBox() {
142 ui->output_sink_combo_box->clear(); 161 ui->sink_combo_box->clear();
143 ui->output_sink_combo_box->addItem(QString::fromUtf8(AudioCore::auto_device_name)); 162 ui->sink_combo_box->addItem(QString::fromUtf8(AudioCore::Sink::auto_device_name));
144 163
145 for (const char* id : AudioCore::GetSinkIDs()) { 164 for (const char* id : AudioCore::Sink::GetSinkIDs()) {
146 ui->output_sink_combo_box->addItem(QString::fromUtf8(id)); 165 ui->sink_combo_box->addItem(QString::fromUtf8(id));
147 } 166 }
148} 167}
149 168
@@ -164,8 +183,10 @@ void ConfigureAudio::SetupPerGameUI() {
164 ConfigurationShared::SetHighlight(ui->volume_layout, index == 1); 183 ConfigurationShared::SetHighlight(ui->volume_layout, index == 1);
165 }); 184 });
166 185
167 ui->output_sink_combo_box->setVisible(false); 186 ui->sink_combo_box->setVisible(false);
168 ui->output_sink_label->setVisible(false); 187 ui->sink_label->setVisible(false);
169 ui->audio_device_combo_box->setVisible(false); 188 ui->output_combo_box->setVisible(false);
170 ui->audio_device_label->setVisible(false); 189 ui->output_label->setVisible(false);
190 ui->input_combo_box->setVisible(false);
191 ui->input_label->setVisible(false);
171} 192}
diff --git a/src/yuzu/configuration/configure_audio.h b/src/yuzu/configuration/configure_audio.h
index 08c278eeb..0d03aae1d 100644
--- a/src/yuzu/configuration/configure_audio.h
+++ b/src/yuzu/configuration/configure_audio.h
@@ -31,14 +31,14 @@ public:
31private: 31private:
32 void changeEvent(QEvent* event) override; 32 void changeEvent(QEvent* event) override;
33 33
34 void InitializeAudioOutputSinkComboBox(); 34 void InitializeAudioSinkComboBox();
35 35
36 void RetranslateUI(); 36 void RetranslateUI();
37 37
38 void UpdateAudioDevices(int sink_index); 38 void UpdateAudioDevices(int sink_index);
39 39
40 void SetOutputSinkFromSinkID(); 40 void SetOutputSinkFromSinkID();
41 void SetAudioDeviceFromDeviceID(); 41 void SetAudioDevicesFromDeviceID();
42 void SetVolumeIndicatorText(int percentage); 42 void SetVolumeIndicatorText(int percentage);
43 43
44 void SetupPerGameUI(); 44 void SetupPerGameUI();
diff --git a/src/yuzu/configuration/configure_audio.ui b/src/yuzu/configuration/configure_audio.ui
index d1ac8ad02..a5bcee415 100644
--- a/src/yuzu/configuration/configure_audio.ui
+++ b/src/yuzu/configuration/configure_audio.ui
@@ -21,30 +21,44 @@
21 </property> 21 </property>
22 <layout class="QVBoxLayout"> 22 <layout class="QVBoxLayout">
23 <item> 23 <item>
24 <layout class="QHBoxLayout" name="_3"> 24 <layout class="QHBoxLayout" name="engine_layout">
25 <item> 25 <item>
26 <widget class="QLabel" name="output_sink_label"> 26 <widget class="QLabel" name="sink_label">
27 <property name="text"> 27 <property name="text">
28 <string>Output Engine:</string> 28 <string>Output Engine:</string>
29 </property> 29 </property>
30 </widget> 30 </widget>
31 </item> 31 </item>
32 <item> 32 <item>
33 <widget class="QComboBox" name="output_sink_combo_box"/> 33 <widget class="QComboBox" name="sink_combo_box"/>
34 </item> 34 </item>
35 </layout> 35 </layout>
36 </item> 36 </item>
37 <item> 37 <item>
38 <layout class="QHBoxLayout" name="_2"> 38 <layout class="QHBoxLayout" name="output_layout">
39 <item> 39 <item>
40 <widget class="QLabel" name="audio_device_label"> 40 <widget class="QLabel" name="output_label">
41 <property name="text"> 41 <property name="text">
42 <string>Audio Device:</string> 42 <string>Output Device</string>
43 </property> 43 </property>
44 </widget> 44 </widget>
45 </item> 45 </item>
46 <item> 46 <item>
47 <widget class="QComboBox" name="audio_device_combo_box"/> 47 <widget class="QComboBox" name="output_combo_box"/>
48 </item>
49 </layout>
50 </item>
51 <item>
52 <layout class="QHBoxLayout" name="input_layout">
53 <item>
54 <widget class="QLabel" name="input_label">
55 <property name="text">
56 <string>Input Device</string>
57 </property>
58 </widget>
59 </item>
60 <item>
61 <widget class="QComboBox" name="input_combo_box"/>
48 </item> 62 </item>
49 </layout> 63 </layout>
50 </item> 64 </item>
diff --git a/src/yuzu/configuration/configure_debug.cpp b/src/yuzu/configuration/configure_debug.cpp
index 343d2aee1..84808f678 100644
--- a/src/yuzu/configuration/configure_debug.cpp
+++ b/src/yuzu/configuration/configure_debug.cpp
@@ -44,6 +44,7 @@ void ConfigureDebug::SetConfiguration() {
44 ui->fs_access_log->setEnabled(runtime_lock); 44 ui->fs_access_log->setEnabled(runtime_lock);
45 ui->fs_access_log->setChecked(Settings::values.enable_fs_access_log.GetValue()); 45 ui->fs_access_log->setChecked(Settings::values.enable_fs_access_log.GetValue());
46 ui->reporting_services->setChecked(Settings::values.reporting_services.GetValue()); 46 ui->reporting_services->setChecked(Settings::values.reporting_services.GetValue());
47 ui->dump_audio_commands->setChecked(Settings::values.dump_audio_commands.GetValue());
47 ui->quest_flag->setChecked(Settings::values.quest_flag.GetValue()); 48 ui->quest_flag->setChecked(Settings::values.quest_flag.GetValue());
48 ui->use_debug_asserts->setChecked(Settings::values.use_debug_asserts.GetValue()); 49 ui->use_debug_asserts->setChecked(Settings::values.use_debug_asserts.GetValue());
49 ui->use_auto_stub->setChecked(Settings::values.use_auto_stub.GetValue()); 50 ui->use_auto_stub->setChecked(Settings::values.use_auto_stub.GetValue());
@@ -83,6 +84,7 @@ void ConfigureDebug::ApplyConfiguration() {
83 Settings::values.program_args = ui->homebrew_args_edit->text().toStdString(); 84 Settings::values.program_args = ui->homebrew_args_edit->text().toStdString();
84 Settings::values.enable_fs_access_log = ui->fs_access_log->isChecked(); 85 Settings::values.enable_fs_access_log = ui->fs_access_log->isChecked();
85 Settings::values.reporting_services = ui->reporting_services->isChecked(); 86 Settings::values.reporting_services = ui->reporting_services->isChecked();
87 Settings::values.dump_audio_commands = ui->dump_audio_commands->isChecked();
86 Settings::values.quest_flag = ui->quest_flag->isChecked(); 88 Settings::values.quest_flag = ui->quest_flag->isChecked();
87 Settings::values.use_debug_asserts = ui->use_debug_asserts->isChecked(); 89 Settings::values.use_debug_asserts = ui->use_debug_asserts->isChecked();
88 Settings::values.use_auto_stub = ui->use_auto_stub->isChecked(); 90 Settings::values.use_auto_stub = ui->use_auto_stub->isChecked();
diff --git a/src/yuzu/configuration/configure_debug.ui b/src/yuzu/configuration/configure_debug.ui
index 1152fa6c6..4c16274fc 100644
--- a/src/yuzu/configuration/configure_debug.ui
+++ b/src/yuzu/configuration/configure_debug.ui
@@ -235,6 +235,16 @@
235 </widget> 235 </widget>
236 </item> 236 </item>
237 <item row="1" column="0"> 237 <item row="1" column="0">
238 <widget class="QCheckBox" name="dump_audio_commands">
239 <property name="text">
240 <string>Dump Audio Commands To Console**</string>
241 </property>
242 <property name="toolTip">
243 <string>Enable this to output the latest generated audio command list to the console. Only affects games using the audio renderer.</string>
244 </property>
245 </widget>
246 </item>
247 <item row="2" column="0">
238 <widget class="QCheckBox" name="reporting_services"> 248 <widget class="QCheckBox" name="reporting_services">
239 <property name="text"> 249 <property name="text">
240 <string>Enable Verbose Reporting Services**</string> 250 <string>Enable Verbose Reporting Services**</string>
@@ -325,6 +335,7 @@
325 <tabstop>disable_loop_safety_checks</tabstop> 335 <tabstop>disable_loop_safety_checks</tabstop>
326 <tabstop>fs_access_log</tabstop> 336 <tabstop>fs_access_log</tabstop>
327 <tabstop>reporting_services</tabstop> 337 <tabstop>reporting_services</tabstop>
338 <tabstop>dump_audio_commands</tabstop>
328 <tabstop>quest_flag</tabstop> 339 <tabstop>quest_flag</tabstop>
329 <tabstop>enable_cpu_debugging</tabstop> 340 <tabstop>enable_cpu_debugging</tabstop>
330 <tabstop>use_debug_asserts</tabstop> 341 <tabstop>use_debug_asserts</tabstop>
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index e60d84054..a120f2662 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -1498,6 +1498,8 @@ void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t
1498 if (!LoadROM(filename, program_id, program_index)) 1498 if (!LoadROM(filename, program_id, program_index))
1499 return; 1499 return;
1500 1500
1501 system->SetShuttingDown(false);
1502
1501 // Create and start the emulation thread 1503 // Create and start the emulation thread
1502 emu_thread = std::make_unique<EmuThread>(*system); 1504 emu_thread = std::make_unique<EmuThread>(*system);
1503 emit EmulationStarting(emu_thread.get()); 1505 emit EmulationStarting(emu_thread.get());
@@ -1588,6 +1590,7 @@ void GMainWindow::ShutdownGame() {
1588 1590
1589 AllowOSSleep(); 1591 AllowOSSleep();
1590 1592
1593 system->SetShuttingDown(true);
1591 system->DetachDebugger(); 1594 system->DetachDebugger();
1592 discord_rpc->Pause(); 1595 discord_rpc->Pause();
1593 emu_thread->RequestStop(); 1596 emu_thread->RequestStop();
diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp
index 5576fb795..ad7f9d239 100644
--- a/src/yuzu_cmd/config.cpp
+++ b/src/yuzu_cmd/config.cpp
@@ -322,7 +322,7 @@ void Config::ReadValues() {
322 322
323 // Audio 323 // Audio
324 ReadSetting("Audio", Settings::values.sink_id); 324 ReadSetting("Audio", Settings::values.sink_id);
325 ReadSetting("Audio", Settings::values.audio_device_id); 325 ReadSetting("Audio", Settings::values.audio_output_device_id);
326 ReadSetting("Audio", Settings::values.volume); 326 ReadSetting("Audio", Settings::values.volume);
327 327
328 // Miscellaneous 328 // Miscellaneous