270 changed files with 33704 additions and 8437 deletions
-
2.gitmodules
-
253src/audio_core/CMakeLists.txt
-
79src/audio_core/algorithm/filter.cpp
-
61src/audio_core/algorithm/filter.h
-
232src/audio_core/algorithm/interpolate.cpp
-
43src/audio_core/algorithm/interpolate.h
-
68src/audio_core/audio_core.cpp
-
100src/audio_core/audio_core.h
-
61src/audio_core/audio_event.cpp
-
92src/audio_core/audio_event.h
-
91src/audio_core/audio_in_manager.cpp
-
92src/audio_core/audio_in_manager.h
-
80src/audio_core/audio_manager.cpp
-
101src/audio_core/audio_manager.h
-
62src/audio_core/audio_out.cpp
-
49src/audio_core/audio_out.h
-
81src/audio_core/audio_out_manager.cpp
-
89src/audio_core/audio_out_manager.h
-
70src/audio_core/audio_render_manager.cpp
-
103src/audio_core/audio_render_manager.h
-
343src/audio_core/audio_renderer.cpp
-
78src/audio_core/audio_renderer.h
-
104src/audio_core/behavior_info.cpp
-
71src/audio_core/behavior_info.h
-
44src/audio_core/buffer.h
-
77src/audio_core/codec.cpp
-
43src/audio_core/codec.h
-
1369src/audio_core/command_generator.cpp
-
110src/audio_core/command_generator.h
-
132src/audio_core/common.h
-
60src/audio_core/common/audio_renderer_parameter.h
-
138src/audio_core/common/common.h
-
105src/audio_core/common/feature_support.h
-
35src/audio_core/common/wave_buffer.h
-
100src/audio_core/common/workbuffer_allocator.h
-
249src/audio_core/cubeb_sink.cpp
-
35src/audio_core/cubeb_sink.h
-
107src/audio_core/delay_line.cpp
-
49src/audio_core/delay_line.h
-
21src/audio_core/device/audio_buffer.h
-
304src/audio_core/device/audio_buffers.h
-
114src/audio_core/device/device_session.cpp
-
126src/audio_core/device/device_session.h
-
320src/audio_core/effect_context.cpp
-
349src/audio_core/effect_context.h
-
100src/audio_core/in/audio_in.cpp
-
147src/audio_core/in/audio_in.h
-
213src/audio_core/in/audio_in_system.cpp
-
275src/audio_core/in/audio_in_system.h
-
511src/audio_core/info_updater.cpp
-
57src/audio_core/info_updater.h
-
60src/audio_core/memory_pool.cpp
-
51src/audio_core/memory_pool.h
-
297src/audio_core/mix_context.cpp
-
113src/audio_core/mix_context.h
-
32src/audio_core/null_sink.h
-
100src/audio_core/out/audio_out.cpp
-
147src/audio_core/out/audio_out.h
-
207src/audio_core/out/audio_out_system.cpp
-
257src/audio_core/out/audio_out_system.h
-
118src/audio_core/renderer/adsp/adsp.cpp
-
173src/audio_core/renderer/adsp/adsp.h
-
226src/audio_core/renderer/adsp/audio_renderer.cpp
-
203src/audio_core/renderer/adsp/audio_renderer.h
-
21src/audio_core/renderer/adsp/command_buffer.h
-
109src/audio_core/renderer/adsp/command_list_processor.cpp
-
118src/audio_core/renderer/adsp/command_list_processor.h
-
52src/audio_core/renderer/audio_device.cpp
-
88src/audio_core/renderer/audio_device.h
-
67src/audio_core/renderer/audio_renderer.cpp
-
97src/audio_core/renderer/audio_renderer.h
-
191src/audio_core/renderer/behavior/behavior_info.cpp
-
376src/audio_core/renderer/behavior/behavior_info.h
-
539src/audio_core/renderer/behavior/info_updater.cpp
-
205src/audio_core/renderer/behavior/info_updater.h
-
714src/audio_core/renderer/command/command_buffer.cpp
-
466src/audio_core/renderer/command/command_buffer.h
-
796src/audio_core/renderer/command/command_generator.cpp
-
349src/audio_core/renderer/command/command_generator.h
-
22src/audio_core/renderer/command/command_list_header.h
-
3620src/audio_core/renderer/command/command_processing_time_estimator.cpp
-
254src/audio_core/renderer/command/command_processing_time_estimator.h
-
32src/audio_core/renderer/command/commands.h
-
84src/audio_core/renderer/command/data_source/adpcm.cpp
-
119src/audio_core/renderer/command/data_source/adpcm.h
-
428src/audio_core/renderer/command/data_source/decode.cpp
-
59src/audio_core/renderer/command/data_source/decode.h
-
86src/audio_core/renderer/command/data_source/pcm_float.cpp
-
113src/audio_core/renderer/command/data_source/pcm_float.h
-
87src/audio_core/renderer/command/data_source/pcm_int16.cpp
-
110src/audio_core/renderer/command/data_source/pcm_int16.h
-
207src/audio_core/renderer/command/effect/aux_.cpp
-
66src/audio_core/renderer/command/effect/aux_.h
-
118src/audio_core/renderer/command/effect/biquad_filter.cpp
-
74src/audio_core/renderer/command/effect/biquad_filter.h
-
142src/audio_core/renderer/command/effect/capture.cpp
-
62src/audio_core/renderer/command/effect/capture.h
-
156src/audio_core/renderer/command/effect/compressor.cpp
-
60src/audio_core/renderer/command/effect/compressor.h
-
238src/audio_core/renderer/command/effect/delay.cpp
@ -1,79 +0,0 @@ |
|||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||
|
|
||||
#define _USE_MATH_DEFINES
|
|
||||
|
|
||||
#include <algorithm>
|
|
||||
#include <array>
|
|
||||
#include <cmath>
|
|
||||
#include <vector>
|
|
||||
#include "audio_core/algorithm/filter.h"
|
|
||||
#include "common/common_types.h"
|
|
||||
|
|
||||
namespace AudioCore { |
|
||||
|
|
||||
Filter Filter::LowPass(double cutoff, double Q) { |
|
||||
const double w0 = 2.0 * M_PI * cutoff; |
|
||||
const double sin_w0 = std::sin(w0); |
|
||||
const double cos_w0 = std::cos(w0); |
|
||||
const double alpha = sin_w0 / (2 * Q); |
|
||||
|
|
||||
const double a0 = 1 + alpha; |
|
||||
const double a1 = -2.0 * cos_w0; |
|
||||
const double a2 = 1 - alpha; |
|
||||
const double b0 = 0.5 * (1 - cos_w0); |
|
||||
const double b1 = 1.0 * (1 - cos_w0); |
|
||||
const double b2 = 0.5 * (1 - cos_w0); |
|
||||
|
|
||||
return {a0, a1, a2, b0, b1, b2}; |
|
||||
} |
|
||||
|
|
||||
Filter::Filter() : Filter(1.0, 0.0, 0.0, 1.0, 0.0, 0.0) {} |
|
||||
|
|
||||
Filter::Filter(double a0_, double a1_, double a2_, double b0_, double b1_, double b2_) |
|
||||
: a1(a1_ / a0_), a2(a2_ / a0_), b0(b0_ / a0_), b1(b1_ / a0_), b2(b2_ / a0_) {} |
|
||||
|
|
||||
void Filter::Process(std::vector<s16>& signal) { |
|
||||
const std::size_t num_frames = signal.size() / 2; |
|
||||
for (std::size_t i = 0; i < num_frames; i++) { |
|
||||
std::rotate(in.begin(), in.end() - 1, in.end()); |
|
||||
std::rotate(out.begin(), out.end() - 1, out.end()); |
|
||||
|
|
||||
for (std::size_t ch = 0; ch < channel_count; ch++) { |
|
||||
in[0][ch] = signal[i * channel_count + ch]; |
|
||||
|
|
||||
out[0][ch] = b0 * in[0][ch] + b1 * in[1][ch] + b2 * in[2][ch] - a1 * out[1][ch] - |
|
||||
a2 * out[2][ch]; |
|
||||
|
|
||||
signal[i * 2 + ch] = static_cast<s16>(std::clamp(out[0][ch], -32768.0, 32767.0)); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// Calculates the appropriate Q for each biquad in a cascading filter.
|
|
||||
/// @param total_count The total number of biquads to be cascaded.
|
|
||||
/// @param index 0-index of the biquad to calculate the Q value for.
|
|
||||
static double CascadingBiquadQ(std::size_t total_count, std::size_t index) { |
|
||||
const auto pole = |
|
||||
M_PI * static_cast<double>(2 * index + 1) / (4.0 * static_cast<double>(total_count)); |
|
||||
return 1.0 / (2.0 * std::cos(pole)); |
|
||||
} |
|
||||
|
|
||||
CascadingFilter CascadingFilter::LowPass(double cutoff, std::size_t cascade_size) { |
|
||||
std::vector<Filter> cascade(cascade_size); |
|
||||
for (std::size_t i = 0; i < cascade_size; i++) { |
|
||||
cascade[i] = Filter::LowPass(cutoff, CascadingBiquadQ(cascade_size, i)); |
|
||||
} |
|
||||
return CascadingFilter{std::move(cascade)}; |
|
||||
} |
|
||||
|
|
||||
CascadingFilter::CascadingFilter() = default; |
|
||||
CascadingFilter::CascadingFilter(std::vector<Filter> filters_) : filters(std::move(filters_)) {} |
|
||||
|
|
||||
void CascadingFilter::Process(std::vector<s16>& signal) { |
|
||||
for (auto& filter : filters) { |
|
||||
filter.Process(signal); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
} // namespace AudioCore
|
|
||||
@ -1,61 +0,0 @@ |
|||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project |
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later |
|
||||
|
|
||||
#pragma once |
|
||||
|
|
||||
#include <array> |
|
||||
#include <vector> |
|
||||
#include "common/common_types.h" |
|
||||
|
|
||||
namespace AudioCore { |
|
||||
|
|
||||
/// Digital biquad filter: |
|
||||
/// |
|
||||
/// b0 + b1 z^-1 + b2 z^-2 |
|
||||
/// H(z) = ------------------------ |
|
||||
/// a0 + a1 z^-1 + b2 z^-2 |
|
||||
class Filter { |
|
||||
public: |
|
||||
/// Creates a low-pass filter. |
|
||||
/// @param cutoff Determines the cutoff frequency. A value from 0.0 to 1.0. |
|
||||
/// @param Q Determines the quality factor of this filter. |
|
||||
static Filter LowPass(double cutoff, double Q = 0.7071); |
|
||||
|
|
||||
/// Passthrough filter. |
|
||||
Filter(); |
|
||||
|
|
||||
Filter(double a0_, double a1_, double a2_, double b0_, double b1_, double b2_); |
|
||||
|
|
||||
void Process(std::vector<s16>& signal); |
|
||||
|
|
||||
private: |
|
||||
static constexpr std::size_t channel_count = 2; |
|
||||
|
|
||||
/// Coefficients are in normalized form (a0 = 1.0). |
|
||||
double a1, a2, b0, b1, b2; |
|
||||
/// Input History |
|
||||
std::array<std::array<double, channel_count>, 3> in; |
|
||||
/// Output History |
|
||||
std::array<std::array<double, channel_count>, 3> out; |
|
||||
}; |
|
||||
|
|
||||
/// Cascade filters to build up higher-order filters from lower-order ones. |
|
||||
class CascadingFilter { |
|
||||
public: |
|
||||
/// Creates a cascading low-pass filter. |
|
||||
/// @param cutoff Determines the cutoff frequency. A value from 0.0 to 1.0. |
|
||||
/// @param cascade_size Number of biquads in cascade. |
|
||||
static CascadingFilter LowPass(double cutoff, std::size_t cascade_size); |
|
||||
|
|
||||
/// Passthrough. |
|
||||
CascadingFilter(); |
|
||||
|
|
||||
explicit CascadingFilter(std::vector<Filter> filters_); |
|
||||
|
|
||||
void Process(std::vector<s16>& signal); |
|
||||
|
|
||||
private: |
|
||||
std::vector<Filter> filters; |
|
||||
}; |
|
||||
|
|
||||
} // namespace AudioCore |
|
||||
@ -1,232 +0,0 @@ |
|||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||
|
|
||||
#define _USE_MATH_DEFINES
|
|
||||
|
|
||||
#include <algorithm>
|
|
||||
#include <climits>
|
|
||||
#include <cmath>
|
|
||||
#include <vector>
|
|
||||
|
|
||||
#include "audio_core/algorithm/interpolate.h"
|
|
||||
#include "common/common_types.h"
|
|
||||
#include "common/logging/log.h"
|
|
||||
|
|
||||
namespace AudioCore { |
|
||||
|
|
||||
constexpr std::array<s16, 512> curve_lut0{ |
|
||||
6600, 19426, 6722, 3, 6479, 19424, 6845, 9, 6359, 19419, 6968, 15, 6239, |
|
||||
19412, 7093, 22, 6121, 19403, 7219, 28, 6004, 19391, 7345, 34, 5888, 19377, |
|
||||
7472, 41, 5773, 19361, 7600, 48, 5659, 19342, 7728, 55, 5546, 19321, 7857, |
|
||||
62, 5434, 19298, 7987, 69, 5323, 19273, 8118, 77, 5213, 19245, 8249, 84, |
|
||||
5104, 19215, 8381, 92, 4997, 19183, 8513, 101, 4890, 19148, 8646, 109, 4785, |
|
||||
19112, 8780, 118, 4681, 19073, 8914, 127, 4579, 19031, 9048, 137, 4477, 18988, |
|
||||
9183, 147, 4377, 18942, 9318, 157, 4277, 18895, 9454, 168, 4179, 18845, 9590, |
|
||||
179, 4083, 18793, 9726, 190, 3987, 18738, 9863, 202, 3893, 18682, 10000, 215, |
|
||||
3800, 18624, 10137, 228, 3709, 18563, 10274, 241, 3618, 18500, 10411, 255, 3529, |
|
||||
18436, 10549, 270, 3441, 18369, 10687, 285, 3355, 18300, 10824, 300, 3269, 18230, |
|
||||
10962, 317, 3186, 18157, 11100, 334, 3103, 18082, 11238, 351, 3022, 18006, 11375, |
|
||||
369, 2942, 17927, 11513, 388, 2863, 17847, 11650, 408, 2785, 17765, 11788, 428, |
|
||||
2709, 17681, 11925, 449, 2635, 17595, 12062, 471, 2561, 17507, 12198, 494, 2489, |
|
||||
17418, 12334, 517, 2418, 17327, 12470, 541, 2348, 17234, 12606, 566, 2280, 17140, |
|
||||
12741, 592, 2213, 17044, 12876, 619, 2147, 16946, 13010, 647, 2083, 16846, 13144, |
|
||||
675, 2020, 16745, 13277, 704, 1958, 16643, 13409, 735, 1897, 16539, 13541, 766, |
|
||||
1838, 16434, 13673, 798, 1780, 16327, 13803, 832, 1723, 16218, 13933, 866, 1667, |
|
||||
16109, 14062, 901, 1613, 15998, 14191, 937, 1560, 15885, 14318, 975, 1508, 15772, |
|
||||
14445, 1013, 1457, 15657, 14571, 1052, 1407, 15540, 14695, 1093, 1359, 15423, 14819, |
|
||||
1134, 1312, 15304, 14942, 1177, 1266, 15185, 15064, 1221, 1221, 15064, 15185, 1266, |
|
||||
1177, 14942, 15304, 1312, 1134, 14819, 15423, 1359, 1093, 14695, 15540, 1407, 1052, |
|
||||
14571, 15657, 1457, 1013, 14445, 15772, 1508, 975, 14318, 15885, 1560, 937, 14191, |
|
||||
15998, 1613, 901, 14062, 16109, 1667, 866, 13933, 16218, 1723, 832, 13803, 16327, |
|
||||
1780, 798, 13673, 16434, 1838, 766, 13541, 16539, 1897, 735, 13409, 16643, 1958, |
|
||||
704, 13277, 16745, 2020, 675, 13144, 16846, 2083, 647, 13010, 16946, 2147, 619, |
|
||||
12876, 17044, 2213, 592, 12741, 17140, 2280, 566, 12606, 17234, 2348, 541, 12470, |
|
||||
17327, 2418, 517, 12334, 17418, 2489, 494, 12198, 17507, 2561, 471, 12062, 17595, |
|
||||
2635, 449, 11925, 17681, 2709, 428, 11788, 17765, 2785, 408, 11650, 17847, 2863, |
|
||||
388, 11513, 17927, 2942, 369, 11375, 18006, 3022, 351, 11238, 18082, 3103, 334, |
|
||||
11100, 18157, 3186, 317, 10962, 18230, 3269, 300, 10824, 18300, 3355, 285, 10687, |
|
||||
18369, 3441, 270, 10549, 18436, 3529, 255, 10411, 18500, 3618, 241, 10274, 18563, |
|
||||
3709, 228, 10137, 18624, 3800, 215, 10000, 18682, 3893, 202, 9863, 18738, 3987, |
|
||||
190, 9726, 18793, 4083, 179, 9590, 18845, 4179, 168, 9454, 18895, 4277, 157, |
|
||||
9318, 18942, 4377, 147, 9183, 18988, 4477, 137, 9048, 19031, 4579, 127, 8914, |
|
||||
19073, 4681, 118, 8780, 19112, 4785, 109, 8646, 19148, 4890, 101, 8513, 19183, |
|
||||
4997, 92, 8381, 19215, 5104, 84, 8249, 19245, 5213, 77, 8118, 19273, 5323, |
|
||||
69, 7987, 19298, 5434, 62, 7857, 19321, 5546, 55, 7728, 19342, 5659, 48, |
|
||||
7600, 19361, 5773, 41, 7472, 19377, 5888, 34, 7345, 19391, 6004, 28, 7219, |
|
||||
19403, 6121, 22, 7093, 19412, 6239, 15, 6968, 19419, 6359, 9, 6845, 19424, |
|
||||
6479, 3, 6722, 19426, 6600}; |
|
||||
|
|
||||
constexpr std::array<s16, 512> curve_lut1{ |
|
||||
-68, 32639, 69, -5, -200, 32630, 212, -15, -328, 32613, 359, -26, -450, |
|
||||
32586, 512, -36, -568, 32551, 669, -47, -680, 32507, 832, -58, -788, 32454, |
|
||||
1000, -69, -891, 32393, 1174, -80, -990, 32323, 1352, -92, -1084, 32244, 1536, |
|
||||
-103, -1173, 32157, 1724, -115, -1258, 32061, 1919, -128, -1338, 31956, 2118, -140, |
|
||||
-1414, 31844, 2322, -153, -1486, 31723, 2532, -167, -1554, 31593, 2747, -180, -1617, |
|
||||
31456, 2967, -194, -1676, 31310, 3192, -209, -1732, 31157, 3422, -224, -1783, 30995, |
|
||||
3657, -240, -1830, 30826, 3897, -256, -1874, 30649, 4143, -272, -1914, 30464, 4393, |
|
||||
-289, -1951, 30272, 4648, -307, -1984, 30072, 4908, -325, -2014, 29866, 5172, -343, |
|
||||
-2040, 29652, 5442, -362, -2063, 29431, 5716, -382, -2083, 29203, 5994, -403, -2100, |
|
||||
28968, 6277, -424, -2114, 28727, 6565, -445, -2125, 28480, 6857, -468, -2133, 28226, |
|
||||
7153, -490, -2139, 27966, 7453, -514, -2142, 27700, 7758, -538, -2142, 27428, 8066, |
|
||||
-563, -2141, 27151, 8378, -588, -2136, 26867, 8694, -614, -2130, 26579, 9013, -641, |
|
||||
-2121, 26285, 9336, -668, -2111, 25987, 9663, -696, -2098, 25683, 9993, -724, -2084, |
|
||||
25375, 10326, -753, -2067, 25063, 10662, -783, -2049, 24746, 11000, -813, -2030, 24425, |
|
||||
11342, -844, -2009, 24100, 11686, -875, -1986, 23771, 12033, -907, -1962, 23438, 12382, |
|
||||
-939, -1937, 23103, 12733, -972, -1911, 22764, 13086, -1005, -1883, 22422, 13441, -1039, |
|
||||
-1855, 22077, 13798, -1072, -1825, 21729, 14156, -1107, -1795, 21380, 14516, -1141, -1764, |
|
||||
21027, 14877, -1176, -1732, 20673, 15239, -1211, -1700, 20317, 15602, -1246, -1667, 19959, |
|
||||
15965, -1282, -1633, 19600, 16329, -1317, -1599, 19239, 16694, -1353, -1564, 18878, 17058, |
|
||||
-1388, -1530, 18515, 17423, -1424, -1495, 18151, 17787, -1459, -1459, 17787, 18151, -1495, |
|
||||
-1424, 17423, 18515, -1530, -1388, 17058, 18878, -1564, -1353, 16694, 19239, -1599, -1317, |
|
||||
16329, 19600, -1633, -1282, 15965, 19959, -1667, -1246, 15602, 20317, -1700, -1211, 15239, |
|
||||
20673, -1732, -1176, 14877, 21027, -1764, -1141, 14516, 21380, -1795, -1107, 14156, 21729, |
|
||||
-1825, -1072, 13798, 22077, -1855, -1039, 13441, 22422, -1883, -1005, 13086, 22764, -1911, |
|
||||
-972, 12733, 23103, -1937, -939, 12382, 23438, -1962, -907, 12033, 23771, -1986, -875, |
|
||||
11686, 24100, -2009, -844, 11342, 24425, -2030, -813, 11000, 24746, -2049, -783, 10662, |
|
||||
25063, -2067, -753, 10326, 25375, -2084, -724, 9993, 25683, -2098, -696, 9663, 25987, |
|
||||
-2111, -668, 9336, 26285, -2121, -641, 9013, 26579, -2130, -614, 8694, 26867, -2136, |
|
||||
-588, 8378, 27151, -2141, -563, 8066, 27428, -2142, -538, 7758, 27700, -2142, -514, |
|
||||
7453, 27966, -2139, -490, 7153, 28226, -2133, -468, 6857, 28480, -2125, -445, 6565, |
|
||||
28727, -2114, -424, 6277, 28968, -2100, -403, 5994, 29203, -2083, -382, 5716, 29431, |
|
||||
-2063, -362, 5442, 29652, -2040, -343, 5172, 29866, -2014, -325, 4908, 30072, -1984, |
|
||||
-307, 4648, 30272, -1951, -289, 4393, 30464, -1914, -272, 4143, 30649, -1874, -256, |
|
||||
3897, 30826, -1830, -240, 3657, 30995, -1783, -224, 3422, 31157, -1732, -209, 3192, |
|
||||
31310, -1676, -194, 2967, 31456, -1617, -180, 2747, 31593, -1554, -167, 2532, 31723, |
|
||||
-1486, -153, 2322, 31844, -1414, -140, 2118, 31956, -1338, -128, 1919, 32061, -1258, |
|
||||
-115, 1724, 32157, -1173, -103, 1536, 32244, -1084, -92, 1352, 32323, -990, -80, |
|
||||
1174, 32393, -891, -69, 1000, 32454, -788, -58, 832, 32507, -680, -47, 669, |
|
||||
32551, -568, -36, 512, 32586, -450, -26, 359, 32613, -328, -15, 212, 32630, |
|
||||
-200, -5, 69, 32639, -68}; |
|
||||
|
|
||||
constexpr std::array<s16, 512> curve_lut2{ |
|
||||
3195, 26287, 3329, -32, 3064, 26281, 3467, -34, 2936, 26270, 3608, -38, 2811, |
|
||||
26253, 3751, -42, 2688, 26230, 3897, -46, 2568, 26202, 4046, -50, 2451, 26169, |
|
||||
4199, -54, 2338, 26130, 4354, -58, 2227, 26085, 4512, -63, 2120, 26035, 4673, |
|
||||
-67, 2015, 25980, 4837, -72, 1912, 25919, 5004, -76, 1813, 25852, 5174, -81, |
|
||||
1716, 25780, 5347, -87, 1622, 25704, 5522, -92, 1531, 25621, 5701, -98, 1442, |
|
||||
25533, 5882, -103, 1357, 25440, 6066, -109, 1274, 25342, 6253, -115, 1193, 25239, |
|
||||
6442, -121, 1115, 25131, 6635, -127, 1040, 25018, 6830, -133, 967, 24899, 7027, |
|
||||
-140, 897, 24776, 7227, -146, 829, 24648, 7430, -153, 764, 24516, 7635, -159, |
|
||||
701, 24379, 7842, -166, 641, 24237, 8052, -174, 583, 24091, 8264, -181, 526, |
|
||||
23940, 8478, -187, 472, 23785, 8695, -194, 420, 23626, 8914, -202, 371, 23462, |
|
||||
9135, -209, 324, 23295, 9358, -215, 279, 23123, 9583, -222, 236, 22948, 9809, |
|
||||
-230, 194, 22769, 10038, -237, 154, 22586, 10269, -243, 117, 22399, 10501, -250, |
|
||||
81, 22208, 10735, -258, 47, 22015, 10970, -265, 15, 21818, 11206, -271, -16, |
|
||||
21618, 11444, -277, -44, 21415, 11684, -283, -71, 21208, 11924, -290, -97, 20999, |
|
||||
12166, -296, -121, 20786, 12409, -302, -143, 20571, 12653, -306, -163, 20354, 12898, |
|
||||
-311, -183, 20134, 13143, -316, -201, 19911, 13389, -321, -218, 19686, 13635, -325, |
|
||||
-234, 19459, 13882, -328, -248, 19230, 14130, -332, -261, 18998, 14377, -335, -273, |
|
||||
18765, 14625, -337, -284, 18531, 14873, -339, -294, 18295, 15121, -341, -302, 18057, |
|
||||
15369, -341, -310, 17817, 15617, -341, -317, 17577, 15864, -340, -323, 17335, 16111, |
|
||||
-340, -328, 17092, 16357, -338, -332, 16848, 16603, -336, -336, 16603, 16848, -332, |
|
||||
-338, 16357, 17092, -328, -340, 16111, 17335, -323, -340, 15864, 17577, -317, -341, |
|
||||
15617, 17817, -310, -341, 15369, 18057, -302, -341, 15121, 18295, -294, -339, 14873, |
|
||||
18531, -284, -337, 14625, 18765, -273, -335, 14377, 18998, -261, -332, 14130, 19230, |
|
||||
-248, -328, 13882, 19459, -234, -325, 13635, 19686, -218, -321, 13389, 19911, -201, |
|
||||
-316, 13143, 20134, -183, -311, 12898, 20354, -163, -306, 12653, 20571, -143, -302, |
|
||||
12409, 20786, -121, -296, 12166, 20999, -97, -290, 11924, 21208, -71, -283, 11684, |
|
||||
21415, -44, -277, 11444, 21618, -16, -271, 11206, 21818, 15, -265, 10970, 22015, |
|
||||
47, -258, 10735, 22208, 81, -250, 10501, 22399, 117, -243, 10269, 22586, 154, |
|
||||
-237, 10038, 22769, 194, -230, 9809, 22948, 236, -222, 9583, 23123, 279, -215, |
|
||||
9358, 23295, 324, -209, 9135, 23462, 371, -202, 8914, 23626, 420, -194, 8695, |
|
||||
23785, 472, -187, 8478, 23940, 526, -181, 8264, 24091, 583, -174, 8052, 24237, |
|
||||
641, -166, 7842, 24379, 701, -159, 7635, 24516, 764, -153, 7430, 24648, 829, |
|
||||
-146, 7227, 24776, 897, -140, 7027, 24899, 967, -133, 6830, 25018, 1040, -127, |
|
||||
6635, 25131, 1115, -121, 6442, 25239, 1193, -115, 6253, 25342, 1274, -109, 6066, |
|
||||
25440, 1357, -103, 5882, 25533, 1442, -98, 5701, 25621, 1531, -92, 5522, 25704, |
|
||||
1622, -87, 5347, 25780, 1716, -81, 5174, 25852, 1813, -76, 5004, 25919, 1912, |
|
||||
-72, 4837, 25980, 2015, -67, 4673, 26035, 2120, -63, 4512, 26085, 2227, -58, |
|
||||
4354, 26130, 2338, -54, 4199, 26169, 2451, -50, 4046, 26202, 2568, -46, 3897, |
|
||||
26230, 2688, -42, 3751, 26253, 2811, -38, 3608, 26270, 2936, -34, 3467, 26281, |
|
||||
3064, -32, 3329, 26287, 3195}; |
|
||||
|
|
||||
std::vector<s16> Interpolate(InterpolationState& state, std::vector<s16> input, double ratio) { |
|
||||
if (input.size() < 2) |
|
||||
return {}; |
|
||||
|
|
||||
if (ratio <= 0) { |
|
||||
LOG_ERROR(Audio, "Nonsensical interpolation ratio {}", ratio); |
|
||||
return input; |
|
||||
} |
|
||||
|
|
||||
const s32 step{static_cast<s32>(ratio * 0x8000)}; |
|
||||
const std::array<s16, 512>& lut = [step] { |
|
||||
if (step > 0xaaaa) { |
|
||||
return curve_lut0; |
|
||||
} |
|
||||
if (step <= 0x8000) { |
|
||||
return curve_lut1; |
|
||||
} |
|
||||
return curve_lut2; |
|
||||
}(); |
|
||||
|
|
||||
const std::size_t num_frames{input.size() / 2}; |
|
||||
|
|
||||
std::vector<s16> output; |
|
||||
output.reserve(static_cast<std::size_t>(static_cast<double>(input.size()) / ratio + |
|
||||
InterpolationState::taps)); |
|
||||
|
|
||||
for (std::size_t frame{}; frame < num_frames; ++frame) { |
|
||||
const std::size_t lut_index{(state.fraction >> 8) * InterpolationState::taps}; |
|
||||
|
|
||||
std::rotate(state.history.begin(), state.history.end() - 1, state.history.end()); |
|
||||
state.history[0][0] = input[frame * 2 + 0]; |
|
||||
state.history[0][1] = input[frame * 2 + 1]; |
|
||||
|
|
||||
while (state.position <= 1.0) { |
|
||||
const s32 left{state.history[0][0] * lut[lut_index + 0] + |
|
||||
state.history[1][0] * lut[lut_index + 1] + |
|
||||
state.history[2][0] * lut[lut_index + 2] + |
|
||||
state.history[3][0] * lut[lut_index + 3]}; |
|
||||
const s32 right{state.history[0][1] * lut[lut_index + 0] + |
|
||||
state.history[1][1] * lut[lut_index + 1] + |
|
||||
state.history[2][1] * lut[lut_index + 2] + |
|
||||
state.history[3][1] * lut[lut_index + 3]}; |
|
||||
const s32 new_offset{state.fraction + step}; |
|
||||
|
|
||||
state.fraction = new_offset & 0x7fff; |
|
||||
|
|
||||
output.emplace_back(static_cast<s16>(std::clamp(left >> 15, SHRT_MIN, SHRT_MAX))); |
|
||||
output.emplace_back(static_cast<s16>(std::clamp(right >> 15, SHRT_MIN, SHRT_MAX))); |
|
||||
|
|
||||
state.position += ratio; |
|
||||
} |
|
||||
state.position -= 1.0; |
|
||||
} |
|
||||
|
|
||||
return output; |
|
||||
} |
|
||||
|
|
||||
void Resample(s32* output, const s32* input, s32 pitch, s32& fraction, std::size_t sample_count) { |
|
||||
const std::array<s16, 512>& lut = [pitch] { |
|
||||
if (pitch > 0xaaaa) { |
|
||||
return curve_lut0; |
|
||||
} |
|
||||
if (pitch <= 0x8000) { |
|
||||
return curve_lut1; |
|
||||
} |
|
||||
return curve_lut2; |
|
||||
}(); |
|
||||
|
|
||||
std::size_t index{}; |
|
||||
|
|
||||
for (std::size_t i = 0; i < sample_count; i++) { |
|
||||
const std::size_t lut_index{(static_cast<std::size_t>(fraction) >> 8) * 4}; |
|
||||
const auto l0 = lut[lut_index + 0]; |
|
||||
const auto l1 = lut[lut_index + 1]; |
|
||||
const auto l2 = lut[lut_index + 2]; |
|
||||
const auto l3 = lut[lut_index + 3]; |
|
||||
|
|
||||
const auto s0 = static_cast<s32>(input[index + 0]); |
|
||||
const auto s1 = static_cast<s32>(input[index + 1]); |
|
||||
const auto s2 = static_cast<s32>(input[index + 2]); |
|
||||
const auto s3 = static_cast<s32>(input[index + 3]); |
|
||||
|
|
||||
output[i] = (l0 * s0 + l1 * s1 + l2 * s2 + l3 * s3) >> 15; |
|
||||
fraction += pitch; |
|
||||
index += (fraction >> 15); |
|
||||
fraction &= 0x7fff; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
} // namespace AudioCore
|
|
||||
@ -1,43 +0,0 @@ |
|||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project |
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later |
|
||||
|
|
||||
#pragma once |
|
||||
|
|
||||
#include <array> |
|
||||
#include <vector> |
|
||||
|
|
||||
#include "common/common_types.h" |
|
||||
|
|
||||
namespace AudioCore { |
|
||||
|
|
||||
struct InterpolationState { |
|
||||
static constexpr std::size_t taps{4}; |
|
||||
static constexpr std::size_t history_size{taps * 2 - 1}; |
|
||||
std::array<std::array<s16, 2>, history_size> history{}; |
|
||||
double position{}; |
|
||||
s32 fraction{}; |
|
||||
}; |
|
||||
|
|
||||
/// Interpolates input signal to produce output signal. |
|
||||
/// @param input The signal to interpolate. |
|
||||
/// @param ratio Interpolation ratio. |
|
||||
/// ratio > 1.0 results in fewer output samples. |
|
||||
/// ratio < 1.0 results in more output samples. |
|
||||
/// @returns Output signal. |
|
||||
std::vector<s16> Interpolate(InterpolationState& state, std::vector<s16> input, double ratio); |
|
||||
|
|
||||
/// Interpolates input signal to produce output signal. |
|
||||
/// @param input The signal to interpolate. |
|
||||
/// @param input_rate The sample rate of input. |
|
||||
/// @param output_rate The desired sample rate of the output. |
|
||||
/// @returns Output signal. |
|
||||
inline std::vector<s16> Interpolate(InterpolationState& state, std::vector<s16> input, |
|
||||
u32 input_rate, u32 output_rate) { |
|
||||
const double ratio = static_cast<double>(input_rate) / static_cast<double>(output_rate); |
|
||||
return Interpolate(state, std::move(input), ratio); |
|
||||
} |
|
||||
|
|
||||
/// Nintendo Switchs DSP resampling algorithm. Based on a single channel |
|
||||
void Resample(s32* output, const s32* input, s32 pitch, s32& fraction, std::size_t sample_count); |
|
||||
|
|
||||
} // namespace AudioCore |
|
||||
@ -0,0 +1,68 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
|
||||
|
#include "audio_core/audio_core.h"
|
||||
|
#include "audio_core/sink/sink_details.h"
|
||||
|
#include "common/settings.h"
|
||||
|
#include "core/core.h"
|
||||
|
|
||||
|
namespace AudioCore { |
||||
|
|
||||
|
AudioCore::AudioCore(Core::System& system) : audio_manager{std::make_unique<AudioManager>(system)} { |
||||
|
CreateSinks(); |
||||
|
// Must be created after the sinks
|
||||
|
adsp = std::make_unique<AudioRenderer::ADSP::ADSP>(system, *output_sink); |
||||
|
} |
||||
|
|
||||
|
AudioCore ::~AudioCore() { |
||||
|
Shutdown(); |
||||
|
} |
||||
|
|
||||
|
void AudioCore::CreateSinks() { |
||||
|
const auto& sink_id{Settings::values.sink_id}; |
||||
|
const auto& audio_output_device_id{Settings::values.audio_output_device_id}; |
||||
|
const auto& audio_input_device_id{Settings::values.audio_input_device_id}; |
||||
|
|
||||
|
output_sink = Sink::CreateSinkFromID(sink_id.GetValue(), audio_output_device_id.GetValue()); |
||||
|
input_sink = Sink::CreateSinkFromID(sink_id.GetValue(), audio_input_device_id.GetValue()); |
||||
|
} |
||||
|
|
||||
|
void AudioCore::Shutdown() { |
||||
|
audio_manager->Shutdown(); |
||||
|
} |
||||
|
|
||||
|
AudioManager& AudioCore::GetAudioManager() { |
||||
|
return *audio_manager; |
||||
|
} |
||||
|
|
||||
|
Sink::Sink& AudioCore::GetOutputSink() { |
||||
|
return *output_sink; |
||||
|
} |
||||
|
|
||||
|
Sink::Sink& AudioCore::GetInputSink() { |
||||
|
return *input_sink; |
||||
|
} |
||||
|
|
||||
|
AudioRenderer::ADSP::ADSP& AudioCore::GetADSP() { |
||||
|
return *adsp; |
||||
|
} |
||||
|
|
||||
|
void AudioCore::PauseSinks(const bool pausing) const { |
||||
|
if (pausing) { |
||||
|
output_sink->PauseStreams(); |
||||
|
input_sink->PauseStreams(); |
||||
|
} else { |
||||
|
output_sink->UnpauseStreams(); |
||||
|
input_sink->UnpauseStreams(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
u32 AudioCore::GetStreamQueue() const { |
||||
|
return estimated_queue.load(); |
||||
|
} |
||||
|
|
||||
|
void AudioCore::SetStreamQueue(u32 size) { |
||||
|
estimated_queue.store(size); |
||||
|
} |
||||
|
|
||||
|
} // namespace AudioCore
|
||||
@ -0,0 +1,100 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project |
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <memory> |
||||
|
|
||||
|
#include "audio_core/audio_manager.h" |
||||
|
#include "audio_core/renderer/adsp/adsp.h" |
||||
|
#include "audio_core/sink/sink.h" |
||||
|
|
||||
|
namespace Core { |
||||
|
class System; |
||||
|
} |
||||
|
|
||||
|
namespace AudioCore { |
||||
|
|
||||
|
class AudioManager; |
||||
|
/** |
||||
|
* Main audio class, sotred inside the core, and holding the audio manager, all sinks, and the ADSP. |
||||
|
*/ |
||||
|
class AudioCore { |
||||
|
public: |
||||
|
explicit AudioCore(Core::System& system); |
||||
|
~AudioCore(); |
||||
|
|
||||
|
/** |
||||
|
* Shutdown the audio core. |
||||
|
*/ |
||||
|
void Shutdown(); |
||||
|
|
||||
|
/** |
||||
|
* Get a reference to the audio manager. |
||||
|
* |
||||
|
* @return Ref to the audio manager. |
||||
|
*/ |
||||
|
AudioManager& GetAudioManager(); |
||||
|
|
||||
|
/** |
||||
|
* Get the audio output sink currently in use. |
||||
|
* |
||||
|
* @return Ref to the sink. |
||||
|
*/ |
||||
|
Sink::Sink& GetOutputSink(); |
||||
|
|
||||
|
/** |
||||
|
* Get the audio input sink currently in use. |
||||
|
* |
||||
|
* @return Ref to the sink. |
||||
|
*/ |
||||
|
Sink::Sink& GetInputSink(); |
||||
|
|
||||
|
/** |
||||
|
* Get the ADSP. |
||||
|
* |
||||
|
* @return Ref to the ADSP. |
||||
|
*/ |
||||
|
AudioRenderer::ADSP::ADSP& GetADSP(); |
||||
|
|
||||
|
/** |
||||
|
* Pause the sink. Called from the core. |
||||
|
* |
||||
|
* @param pausing - Is this pause due to an actual pause, or shutdown? |
||||
|
* Unfortunately, shutdown also pauses streams, which can cause issues. |
||||
|
*/ |
||||
|
void PauseSinks(bool pausing) const; |
||||
|
|
||||
|
/** |
||||
|
* Get the size of the current stream queue. |
||||
|
* |
||||
|
* @return Current stream queue size. |
||||
|
*/ |
||||
|
u32 GetStreamQueue() const; |
||||
|
|
||||
|
/** |
||||
|
* Get the size of the current stream queue. |
||||
|
* |
||||
|
* @param size - New stream size. |
||||
|
*/ |
||||
|
void SetStreamQueue(u32 size); |
||||
|
|
||||
|
private: |
||||
|
/** |
||||
|
* Create the sinks on startup. |
||||
|
*/ |
||||
|
void CreateSinks(); |
||||
|
|
||||
|
/// Main audio manager for audio in/out |
||||
|
std::unique_ptr<AudioManager> audio_manager; |
||||
|
/// Sink used for audio renderer and audio out |
||||
|
std::unique_ptr<Sink::Sink> output_sink; |
||||
|
/// Sink used for audio input |
||||
|
std::unique_ptr<Sink::Sink> input_sink; |
||||
|
/// The ADSP in the sysmodule |
||||
|
std::unique_ptr<AudioRenderer::ADSP::ADSP> adsp; |
||||
|
/// Current size of the stream queue |
||||
|
std::atomic<u32> estimated_queue{0}; |
||||
|
}; |
||||
|
|
||||
|
} // namespace AudioCore |
||||
@ -0,0 +1,61 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
|
||||
|
#include "audio_core/audio_event.h"
|
||||
|
#include "common/assert.h"
|
||||
|
|
||||
|
namespace AudioCore { |
||||
|
|
||||
|
size_t Event::GetManagerIndex(const Type type) const { |
||||
|
switch (type) { |
||||
|
case Type::AudioInManager: |
||||
|
return 0; |
||||
|
case Type::AudioOutManager: |
||||
|
return 1; |
||||
|
case Type::FinalOutputRecorderManager: |
||||
|
return 2; |
||||
|
case Type::Max: |
||||
|
return 3; |
||||
|
default: |
||||
|
UNREACHABLE(); |
||||
|
} |
||||
|
return 3; |
||||
|
} |
||||
|
|
||||
|
void Event::SetAudioEvent(const Type type, const bool signalled) { |
||||
|
events_signalled[GetManagerIndex(type)] = signalled; |
||||
|
if (signalled) { |
||||
|
manager_event.notify_one(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
bool Event::CheckAudioEventSet(const Type type) const { |
||||
|
return events_signalled[GetManagerIndex(type)]; |
||||
|
} |
||||
|
|
||||
|
std::mutex& Event::GetAudioEventLock() { |
||||
|
return event_lock; |
||||
|
} |
||||
|
|
||||
|
std::condition_variable_any& Event::GetAudioEvent() { |
||||
|
return manager_event; |
||||
|
} |
||||
|
|
||||
|
bool Event::Wait(std::unique_lock<std::mutex>& l, const std::chrono::seconds timeout) { |
||||
|
bool timed_out{false}; |
||||
|
if (!manager_event.wait_for(l, timeout, [&]() { |
||||
|
return std::ranges::any_of(events_signalled, [](bool x) { return x; }); |
||||
|
})) { |
||||
|
timed_out = true; |
||||
|
} |
||||
|
return timed_out; |
||||
|
} |
||||
|
|
||||
|
void Event::ClearEvents() { |
||||
|
events_signalled[0] = false; |
||||
|
events_signalled[1] = false; |
||||
|
events_signalled[2] = false; |
||||
|
events_signalled[3] = false; |
||||
|
} |
||||
|
|
||||
|
} // namespace AudioCore
|
||||
@ -0,0 +1,92 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project |
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <array> |
||||
|
#include <atomic> |
||||
|
#include <chrono> |
||||
|
#include <condition_variable> |
||||
|
#include <mutex> |
||||
|
|
||||
|
namespace AudioCore { |
||||
|
/** |
||||
|
* Responsible for the input/output events, set by the stream backend when buffers are consumed, and |
||||
|
* waited on by the audio manager. These callbacks signal the game's events to keep the audio buffer |
||||
|
* recycling going. |
||||
|
* In a real Switch this is not a seprate class, and exists entirely within the audio manager. |
||||
|
* On the Switch it's implemented more simply through a MultiWaitEventHolder, where it can |
||||
|
* wait on multiple events at once, and the events are not needed by the backend. |
||||
|
*/ |
||||
|
class Event { |
||||
|
public: |
||||
|
enum class Type { |
||||
|
AudioInManager, |
||||
|
AudioOutManager, |
||||
|
FinalOutputRecorderManager, |
||||
|
Max, |
||||
|
}; |
||||
|
|
||||
|
/** |
||||
|
* Convert a manager type to an index. |
||||
|
* |
||||
|
* @param type - The manager type to convert |
||||
|
* @return The index of the type. |
||||
|
*/ |
||||
|
size_t GetManagerIndex(Type type) const; |
||||
|
|
||||
|
/** |
||||
|
* Set an audio event to true or false. |
||||
|
* |
||||
|
* @param type - The manager type to signal. |
||||
|
* @param signalled - Its signal state. |
||||
|
*/ |
||||
|
void SetAudioEvent(Type type, bool signalled); |
||||
|
|
||||
|
/** |
||||
|
* Check if the given manager type is signalled. |
||||
|
* |
||||
|
* @param type - The manager type to check. |
||||
|
* @return True if the event is signalled, otherwise false. |
||||
|
*/ |
||||
|
bool CheckAudioEventSet(Type type) const; |
||||
|
|
||||
|
/** |
||||
|
* Get the lock for audio events. |
||||
|
* |
||||
|
* @return Reference to the lock. |
||||
|
*/ |
||||
|
std::mutex& GetAudioEventLock(); |
||||
|
|
||||
|
/** |
||||
|
* Get the manager event, this signals the audio manager to release buffers and signal the game |
||||
|
* for more. |
||||
|
* |
||||
|
* @return Reference to the condition variable. |
||||
|
*/ |
||||
|
std::condition_variable_any& GetAudioEvent(); |
||||
|
|
||||
|
/** |
||||
|
* Wait on the manager_event. |
||||
|
* |
||||
|
* @param l - Lock held by the wait. |
||||
|
* @param timeout - Timeout for the wait. This is 2 seconds by default. |
||||
|
* @return True if the wait timed out, otherwise false if signalled. |
||||
|
*/ |
||||
|
bool Wait(std::unique_lock<std::mutex>& l, std::chrono::seconds timeout); |
||||
|
|
||||
|
/** |
||||
|
* Reset all manager events. |
||||
|
*/ |
||||
|
void ClearEvents(); |
||||
|
|
||||
|
private: |
||||
|
/// Lock, used bythe audio manager |
||||
|
std::mutex event_lock; |
||||
|
/// Array of events, one per system type (see Type), last event is used to terminate |
||||
|
std::array<std::atomic<bool>, 4> events_signalled; |
||||
|
/// Event to signal the audio manager |
||||
|
std::condition_variable_any manager_event; |
||||
|
}; |
||||
|
|
||||
|
} // namespace AudioCore |
||||
@ -0,0 +1,91 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
|
||||
|
#include "audio_core/audio_core.h"
|
||||
|
#include "audio_core/audio_in_manager.h"
|
||||
|
#include "audio_core/audio_manager.h"
|
||||
|
#include "audio_core/in/audio_in.h"
|
||||
|
#include "audio_core/sink/sink_details.h"
|
||||
|
#include "common/settings.h"
|
||||
|
#include "core/core.h"
|
||||
|
#include "core/hle/service/audio/errors.h"
|
||||
|
|
||||
|
namespace AudioCore::AudioIn { |
||||
|
|
||||
|
Manager::Manager(Core::System& system_) : system{system_} { |
||||
|
std::iota(session_ids.begin(), session_ids.end(), 0); |
||||
|
num_free_sessions = MaxInSessions; |
||||
|
} |
||||
|
|
||||
|
Result Manager::AcquireSessionId(size_t& session_id) { |
||||
|
if (num_free_sessions == 0) { |
||||
|
LOG_ERROR(Service_Audio, "All 4 AudioIn sessions are in use, cannot create any more"); |
||||
|
return Service::Audio::ERR_MAXIMUM_SESSIONS_REACHED; |
||||
|
} |
||||
|
session_id = session_ids[next_session_id]; |
||||
|
next_session_id = (next_session_id + 1) % MaxInSessions; |
||||
|
num_free_sessions--; |
||||
|
return ResultSuccess; |
||||
|
} |
||||
|
|
||||
|
void Manager::ReleaseSessionId(const size_t session_id) { |
||||
|
std::scoped_lock l{mutex}; |
||||
|
LOG_DEBUG(Service_Audio, "Freeing AudioIn session {}", session_id); |
||||
|
session_ids[free_session_id] = session_id; |
||||
|
num_free_sessions++; |
||||
|
free_session_id = (free_session_id + 1) % MaxInSessions; |
||||
|
sessions[session_id].reset(); |
||||
|
applet_resource_user_ids[session_id] = 0; |
||||
|
} |
||||
|
|
||||
|
Result Manager::LinkToManager() { |
||||
|
std::scoped_lock l{mutex}; |
||||
|
if (!linked_to_manager) { |
||||
|
AudioManager& manager{system.AudioCore().GetAudioManager()}; |
||||
|
manager.SetInManager(std::bind(&Manager::BufferReleaseAndRegister, this)); |
||||
|
linked_to_manager = true; |
||||
|
} |
||||
|
|
||||
|
return ResultSuccess; |
||||
|
} |
||||
|
|
||||
|
void Manager::Start() { |
||||
|
if (sessions_started) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
std::scoped_lock l{mutex}; |
||||
|
for (auto& session : sessions) { |
||||
|
if (session) { |
||||
|
session->StartSession(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
sessions_started = true; |
||||
|
} |
||||
|
|
||||
|
void Manager::BufferReleaseAndRegister() { |
||||
|
std::scoped_lock l{mutex}; |
||||
|
for (auto& session : sessions) { |
||||
|
if (session != nullptr) { |
||||
|
session->ReleaseAndRegisterBuffers(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
u32 Manager::GetDeviceNames(std::vector<AudioRenderer::AudioDevice::AudioDeviceName>& names, |
||||
|
[[maybe_unused]] const u32 max_count, |
||||
|
[[maybe_unused]] const bool filter) { |
||||
|
std::scoped_lock l{mutex}; |
||||
|
|
||||
|
LinkToManager(); |
||||
|
|
||||
|
auto input_devices{Sink::GetDeviceListForSink(Settings::values.sink_id.GetValue(), true)}; |
||||
|
if (input_devices.size() > 1) { |
||||
|
names.push_back(AudioRenderer::AudioDevice::AudioDeviceName("Uac")); |
||||
|
return 1; |
||||
|
} |
||||
|
return 0; |
||||
|
} |
||||
|
|
||||
|
} // namespace AudioCore::AudioIn
|
||||
@ -0,0 +1,92 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project |
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <array> |
||||
|
#include <mutex> |
||||
|
#include <vector> |
||||
|
|
||||
|
#include "audio_core/renderer/audio_device.h" |
||||
|
|
||||
|
namespace Core { |
||||
|
class System; |
||||
|
} |
||||
|
|
||||
|
namespace AudioCore::AudioIn { |
||||
|
class In; |
||||
|
|
||||
|
constexpr size_t MaxInSessions = 4; |
||||
|
/** |
||||
|
* Manages all audio in sessions. |
||||
|
*/ |
||||
|
class Manager { |
||||
|
public: |
||||
|
explicit Manager(Core::System& system); |
||||
|
|
||||
|
/** |
||||
|
* Acquire a free session id for opening a new audio in. |
||||
|
* |
||||
|
* @param session_id - Output session_id. |
||||
|
* @return Result code. |
||||
|
*/ |
||||
|
Result AcquireSessionId(size_t& session_id); |
||||
|
|
||||
|
/** |
||||
|
* Release a session id on close. |
||||
|
* |
||||
|
* @param session_id - Session id to free. |
||||
|
*/ |
||||
|
void ReleaseSessionId(size_t session_id); |
||||
|
|
||||
|
/** |
||||
|
* Link the audio in manager to the main audio manager. |
||||
|
* |
||||
|
* @return Result code. |
||||
|
*/ |
||||
|
Result LinkToManager(); |
||||
|
|
||||
|
/** |
||||
|
* Start the audio in manager. |
||||
|
*/ |
||||
|
void Start(); |
||||
|
|
||||
|
/** |
||||
|
* Callback function, called by the audio manager when the audio in event is signalled. |
||||
|
*/ |
||||
|
void BufferReleaseAndRegister(); |
||||
|
|
||||
|
/** |
||||
|
* Get a list of audio in device names. |
||||
|
* |
||||
|
* @oaram names - Output container to write names to. |
||||
|
* @param max_count - Maximum numebr of deivce names to write. Unused |
||||
|
* @param filter - Should the list be filtered? Unused. |
||||
|
* @return Number of names written. |
||||
|
*/ |
||||
|
u32 GetDeviceNames(std::vector<AudioRenderer::AudioDevice::AudioDeviceName>& names, |
||||
|
u32 max_count, bool filter); |
||||
|
|
||||
|
/// Core system |
||||
|
Core::System& system; |
||||
|
/// Array of session ids |
||||
|
std::array<size_t, MaxInSessions> session_ids{}; |
||||
|
/// Array of resource user ids |
||||
|
std::array<size_t, MaxInSessions> applet_resource_user_ids{}; |
||||
|
/// Pointer to each open session |
||||
|
std::array<std::shared_ptr<In>, MaxInSessions> sessions{}; |
||||
|
/// The number of free sessions |
||||
|
size_t num_free_sessions{}; |
||||
|
/// The next session id to be taken |
||||
|
size_t next_session_id{}; |
||||
|
/// The next session id to be freed |
||||
|
size_t free_session_id{}; |
||||
|
/// Whether this is linked to the audio manager |
||||
|
bool linked_to_manager{}; |
||||
|
/// Whether the sessions have been started |
||||
|
bool sessions_started{}; |
||||
|
/// Protect state due to audio manager callback |
||||
|
std::recursive_mutex mutex{}; |
||||
|
}; |
||||
|
|
||||
|
} // namespace AudioCore::AudioIn |
||||
@ -0,0 +1,80 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
|
||||
|
#include "audio_core/audio_in_manager.h"
|
||||
|
#include "audio_core/audio_manager.h"
|
||||
|
#include "audio_core/audio_out_manager.h"
|
||||
|
#include "core/core.h"
|
||||
|
|
||||
|
namespace AudioCore { |
||||
|
|
||||
|
AudioManager::AudioManager(Core::System& system_) : system{system_} { |
||||
|
thread = std::jthread([this]() { ThreadFunc(); }); |
||||
|
} |
||||
|
|
||||
|
void AudioManager::Shutdown() { |
||||
|
running = false; |
||||
|
events.SetAudioEvent(Event::Type::Max, true); |
||||
|
thread.join(); |
||||
|
} |
||||
|
|
||||
|
Result AudioManager::SetOutManager(BufferEventFunc buffer_func) { |
||||
|
if (!running) { |
||||
|
return Service::Audio::ERR_OPERATION_FAILED; |
||||
|
} |
||||
|
|
||||
|
std::scoped_lock l{lock}; |
||||
|
|
||||
|
const auto index{events.GetManagerIndex(Event::Type::AudioOutManager)}; |
||||
|
if (buffer_events[index] == nullptr) { |
||||
|
buffer_events[index] = buffer_func; |
||||
|
needs_update = true; |
||||
|
events.SetAudioEvent(Event::Type::AudioOutManager, true); |
||||
|
} |
||||
|
return ResultSuccess; |
||||
|
} |
||||
|
|
||||
|
Result AudioManager::SetInManager(BufferEventFunc buffer_func) { |
||||
|
if (!running) { |
||||
|
return Service::Audio::ERR_OPERATION_FAILED; |
||||
|
} |
||||
|
|
||||
|
std::scoped_lock l{lock}; |
||||
|
|
||||
|
const auto index{events.GetManagerIndex(Event::Type::AudioInManager)}; |
||||
|
if (buffer_events[index] == nullptr) { |
||||
|
buffer_events[index] = buffer_func; |
||||
|
needs_update = true; |
||||
|
events.SetAudioEvent(Event::Type::AudioInManager, true); |
||||
|
} |
||||
|
return ResultSuccess; |
||||
|
} |
||||
|
|
||||
|
void AudioManager::SetEvent(const Event::Type type, const bool signalled) { |
||||
|
events.SetAudioEvent(type, signalled); |
||||
|
} |
||||
|
|
||||
|
void AudioManager::ThreadFunc() { |
||||
|
std::unique_lock l{events.GetAudioEventLock()}; |
||||
|
events.ClearEvents(); |
||||
|
running = true; |
||||
|
|
||||
|
while (running) { |
||||
|
auto timed_out{events.Wait(l, std::chrono::seconds(2))}; |
||||
|
|
||||
|
if (events.CheckAudioEventSet(Event::Type::Max)) { |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
for (size_t i = 0; i < buffer_events.size(); i++) { |
||||
|
if (events.CheckAudioEventSet(Event::Type(i)) || timed_out) { |
||||
|
if (buffer_events[i]) { |
||||
|
buffer_events[i](); |
||||
|
} |
||||
|
} |
||||
|
events.SetAudioEvent(Event::Type(i), false); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} // namespace AudioCore
|
||||
@ -0,0 +1,101 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project |
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <array> |
||||
|
#include <atomic> |
||||
|
#include <functional> |
||||
|
#include <mutex> |
||||
|
#include <thread> |
||||
|
|
||||
|
#include "audio_core/audio_event.h" |
||||
|
#include "core/hle/service/audio/errors.h" |
||||
|
|
||||
|
namespace Core { |
||||
|
class System; |
||||
|
} |
||||
|
|
||||
|
namespace AudioCore { |
||||
|
|
||||
|
namespace AudioOut { |
||||
|
class Manager; |
||||
|
} |
||||
|
|
||||
|
namespace AudioIn { |
||||
|
class Manager; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* The AudioManager's main purpose is to wait for buffer events for the audio in and out managers, |
||||
|
* and call an associated callback to release buffers. |
||||
|
* |
||||
|
* Execution pattern is: |
||||
|
* Buffers appended -> |
||||
|
* Buffers queued and played by the backend stream -> |
||||
|
* When consumed, set the corresponding manager event and signal the audio manager -> |
||||
|
* Consumed buffers are released, game is signalled -> |
||||
|
* Game appends more buffers. |
||||
|
* |
||||
|
* This is only used by audio in and audio out. |
||||
|
*/ |
||||
|
class AudioManager { |
||||
|
using BufferEventFunc = std::function<void()>; |
||||
|
|
||||
|
public: |
||||
|
explicit AudioManager(Core::System& system); |
||||
|
|
||||
|
/** |
||||
|
* Shutdown the audio manager. |
||||
|
*/ |
||||
|
void Shutdown(); |
||||
|
|
||||
|
/** |
||||
|
* Register the out manager, keeping a function to be called when the out event is signalled. |
||||
|
* |
||||
|
* @param buffer_func - Function to be called on signal. |
||||
|
* @return Result code. |
||||
|
*/ |
||||
|
Result SetOutManager(BufferEventFunc buffer_func); |
||||
|
|
||||
|
/** |
||||
|
* Register the in manager, keeping a function to be called when the in event is signalled. |
||||
|
* |
||||
|
* @param buffer_func - Function to be called on signal. |
||||
|
* @return Result code. |
||||
|
*/ |
||||
|
Result SetInManager(BufferEventFunc buffer_func); |
||||
|
|
||||
|
/** |
||||
|
* Set an event to signalled, and signal the thread. |
||||
|
* |
||||
|
* @param type - Manager type to set. |
||||
|
* @param signalled - Set the event to true or false? |
||||
|
*/ |
||||
|
void SetEvent(Event::Type type, bool signalled); |
||||
|
|
||||
|
private: |
||||
|
/** |
||||
|
* Main thread, waiting on a manager signal and calling the registered fucntion. |
||||
|
*/ |
||||
|
void ThreadFunc(); |
||||
|
|
||||
|
/// Core system |
||||
|
Core::System& system; |
||||
|
/// Have sessions started palying? |
||||
|
bool sessions_started{}; |
||||
|
/// Is the main thread running? |
||||
|
std::atomic<bool> running{}; |
||||
|
/// Unused |
||||
|
bool needs_update{}; |
||||
|
/// Events to be set and signalled |
||||
|
Event events{}; |
||||
|
/// Callbacks for each manager |
||||
|
std::array<BufferEventFunc, 3> buffer_events{}; |
||||
|
/// General lock |
||||
|
std::mutex lock{}; |
||||
|
/// Main thread for waiting and callbacks |
||||
|
std::jthread thread; |
||||
|
}; |
||||
|
|
||||
|
} // namespace AudioCore |
||||
@ -1,62 +0,0 @@ |
|||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||
|
|
||||
#include "audio_core/audio_out.h"
|
|
||||
#include "audio_core/sink.h"
|
|
||||
#include "audio_core/sink_details.h"
|
|
||||
#include "common/assert.h"
|
|
||||
#include "common/logging/log.h"
|
|
||||
#include "common/settings.h"
|
|
||||
|
|
||||
namespace AudioCore { |
|
||||
|
|
||||
/// Returns the stream format from the specified number of channels
|
|
||||
static Stream::Format ChannelsToStreamFormat(u32 num_channels) { |
|
||||
switch (num_channels) { |
|
||||
case 1: |
|
||||
return Stream::Format::Mono16; |
|
||||
case 2: |
|
||||
return Stream::Format::Stereo16; |
|
||||
case 6: |
|
||||
return Stream::Format::Multi51Channel16; |
|
||||
} |
|
||||
|
|
||||
UNIMPLEMENTED_MSG("Unimplemented num_channels={}", num_channels); |
|
||||
return {}; |
|
||||
} |
|
||||
|
|
||||
StreamPtr AudioOut::OpenStream(Core::Timing::CoreTiming& core_timing, u32 sample_rate, |
|
||||
u32 num_channels, std::string&& name, |
|
||||
Stream::ReleaseCallback&& release_callback) { |
|
||||
if (!sink) { |
|
||||
sink = CreateSinkFromID(Settings::values.sink_id.GetValue(), |
|
||||
Settings::values.audio_device_id.GetValue()); |
|
||||
} |
|
||||
|
|
||||
return std::make_shared<Stream>( |
|
||||
core_timing, sample_rate, ChannelsToStreamFormat(num_channels), std::move(release_callback), |
|
||||
sink->AcquireSinkStream(sample_rate, num_channels, name), std::move(name)); |
|
||||
} |
|
||||
|
|
||||
std::vector<Buffer::Tag> AudioOut::GetTagsAndReleaseBuffers(StreamPtr stream, |
|
||||
std::size_t max_count) { |
|
||||
return stream->GetTagsAndReleaseBuffers(max_count); |
|
||||
} |
|
||||
|
|
||||
std::vector<Buffer::Tag> AudioOut::GetTagsAndReleaseBuffers(StreamPtr stream) { |
|
||||
return stream->GetTagsAndReleaseBuffers(); |
|
||||
} |
|
||||
|
|
||||
void AudioOut::StartStream(StreamPtr stream) { |
|
||||
stream->Play(); |
|
||||
} |
|
||||
|
|
||||
void AudioOut::StopStream(StreamPtr stream) { |
|
||||
stream->Stop(); |
|
||||
} |
|
||||
|
|
||||
bool AudioOut::QueueBuffer(StreamPtr stream, Buffer::Tag tag, std::vector<s16>&& data) { |
|
||||
return stream->QueueBuffer(std::make_shared<Buffer>(tag, std::move(data))); |
|
||||
} |
|
||||
|
|
||||
} // namespace AudioCore
|
|
||||
@ -1,49 +0,0 @@ |
|||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project |
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later |
|
||||
|
|
||||
#pragma once |
|
||||
|
|
||||
#include <memory> |
|
||||
#include <string> |
|
||||
#include <vector> |
|
||||
|
|
||||
#include "audio_core/buffer.h" |
|
||||
#include "audio_core/sink.h" |
|
||||
#include "audio_core/stream.h" |
|
||||
#include "common/common_types.h" |
|
||||
|
|
||||
namespace Core::Timing { |
|
||||
class CoreTiming; |
|
||||
} |
|
||||
|
|
||||
namespace AudioCore { |
|
||||
|
|
||||
/** |
|
||||
* Represents an audio playback interface, used to open and play audio streams |
|
||||
*/ |
|
||||
class AudioOut { |
|
||||
public: |
|
||||
/// Opens a new audio stream |
|
||||
StreamPtr OpenStream(Core::Timing::CoreTiming& core_timing, u32 sample_rate, u32 num_channels, |
|
||||
std::string&& name, Stream::ReleaseCallback&& release_callback); |
|
||||
|
|
||||
/// Returns a vector of recently released buffers specified by tag for the specified stream |
|
||||
std::vector<Buffer::Tag> GetTagsAndReleaseBuffers(StreamPtr stream, std::size_t max_count); |
|
||||
|
|
||||
/// Returns a vector of all recently released buffers specified by tag for the specified stream |
|
||||
std::vector<Buffer::Tag> GetTagsAndReleaseBuffers(StreamPtr stream); |
|
||||
|
|
||||
/// Starts an audio stream for playback |
|
||||
void StartStream(StreamPtr stream); |
|
||||
|
|
||||
/// Stops an audio stream that is currently playing |
|
||||
void StopStream(StreamPtr stream); |
|
||||
|
|
||||
/// Queues a buffer into the specified audio stream, returns true on success |
|
||||
bool QueueBuffer(StreamPtr stream, Buffer::Tag tag, std::vector<s16>&& data); |
|
||||
|
|
||||
private: |
|
||||
SinkPtr sink; |
|
||||
}; |
|
||||
|
|
||||
} // namespace AudioCore |
|
||||
@ -0,0 +1,81 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
|
||||
|
#include "audio_core/audio_core.h"
|
||||
|
#include "audio_core/audio_manager.h"
|
||||
|
#include "audio_core/audio_out_manager.h"
|
||||
|
#include "audio_core/out/audio_out.h"
|
||||
|
#include "core/core.h"
|
||||
|
#include "core/hle/kernel/k_event.h"
|
||||
|
#include "core/hle/service/audio/errors.h"
|
||||
|
|
||||
|
namespace AudioCore::AudioOut { |
||||
|
|
||||
|
Manager::Manager(Core::System& system_) : system{system_} { |
||||
|
std::iota(session_ids.begin(), session_ids.end(), 0); |
||||
|
num_free_sessions = MaxOutSessions; |
||||
|
} |
||||
|
|
||||
|
Result Manager::AcquireSessionId(size_t& session_id) { |
||||
|
if (num_free_sessions == 0) { |
||||
|
LOG_ERROR(Service_Audio, "All 12 Audio Out sessions are in use, cannot create any more"); |
||||
|
return Service::Audio::ERR_MAXIMUM_SESSIONS_REACHED; |
||||
|
} |
||||
|
session_id = session_ids[next_session_id]; |
||||
|
next_session_id = (next_session_id + 1) % MaxOutSessions; |
||||
|
num_free_sessions--; |
||||
|
return ResultSuccess; |
||||
|
} |
||||
|
|
||||
|
void Manager::ReleaseSessionId(const size_t session_id) { |
||||
|
std::scoped_lock l{mutex}; |
||||
|
LOG_DEBUG(Service_Audio, "Freeing AudioOut session {}", session_id); |
||||
|
session_ids[free_session_id] = session_id; |
||||
|
num_free_sessions++; |
||||
|
free_session_id = (free_session_id + 1) % MaxOutSessions; |
||||
|
sessions[session_id].reset(); |
||||
|
applet_resource_user_ids[session_id] = 0; |
||||
|
} |
||||
|
|
||||
|
Result Manager::LinkToManager() { |
||||
|
std::scoped_lock l{mutex}; |
||||
|
if (!linked_to_manager) { |
||||
|
AudioManager& manager{system.AudioCore().GetAudioManager()}; |
||||
|
manager.SetOutManager(std::bind(&Manager::BufferReleaseAndRegister, this)); |
||||
|
linked_to_manager = true; |
||||
|
} |
||||
|
|
||||
|
return ResultSuccess; |
||||
|
} |
||||
|
|
||||
|
void Manager::Start() { |
||||
|
if (sessions_started) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
std::scoped_lock l{mutex}; |
||||
|
for (auto& session : sessions) { |
||||
|
if (session) { |
||||
|
session->StartSession(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
sessions_started = true; |
||||
|
} |
||||
|
|
||||
|
void Manager::BufferReleaseAndRegister() { |
||||
|
std::scoped_lock l{mutex}; |
||||
|
for (auto& session : sessions) { |
||||
|
if (session != nullptr) { |
||||
|
session->ReleaseAndRegisterBuffers(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
u32 Manager::GetAudioOutDeviceNames( |
||||
|
std::vector<AudioRenderer::AudioDevice::AudioDeviceName>& names) const { |
||||
|
names.push_back({"DeviceOut"}); |
||||
|
return 1; |
||||
|
} |
||||
|
|
||||
|
} // namespace AudioCore::AudioOut
|
||||
@ -0,0 +1,89 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project |
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <array> |
||||
|
#include <mutex> |
||||
|
|
||||
|
#include "audio_core/renderer/audio_device.h" |
||||
|
|
||||
|
namespace Core { |
||||
|
class System; |
||||
|
} |
||||
|
|
||||
|
namespace AudioCore::AudioOut { |
||||
|
class Out; |
||||
|
|
||||
|
constexpr size_t MaxOutSessions = 12; |
||||
|
/** |
||||
|
* Manages all audio out sessions. |
||||
|
*/ |
||||
|
class Manager { |
||||
|
public: |
||||
|
explicit Manager(Core::System& system); |
||||
|
|
||||
|
/** |
||||
|
* Acquire a free session id for opening a new audio out. |
||||
|
* |
||||
|
* @param session_id - Output session_id. |
||||
|
* @return Result code. |
||||
|
*/ |
||||
|
Result AcquireSessionId(size_t& session_id); |
||||
|
|
||||
|
/** |
||||
|
* Release a session id on close. |
||||
|
* |
||||
|
* @param session_id - Session id to free. |
||||
|
*/ |
||||
|
void ReleaseSessionId(size_t session_id); |
||||
|
|
||||
|
/** |
||||
|
* Link this manager to the main audio manager. |
||||
|
* |
||||
|
* @return Result code. |
||||
|
*/ |
||||
|
Result LinkToManager(); |
||||
|
|
||||
|
/** |
||||
|
* Start the audio out manager. |
||||
|
*/ |
||||
|
void Start(); |
||||
|
|
||||
|
/** |
||||
|
* Callback function, called by the audio manager when the audio out event is signalled. |
||||
|
*/ |
||||
|
void BufferReleaseAndRegister(); |
||||
|
|
||||
|
/** |
||||
|
* Get a list of audio out device names. |
||||
|
* |
||||
|
* @oaram names - Output container to write names to. |
||||
|
* @return Number of names written. |
||||
|
*/ |
||||
|
u32 GetAudioOutDeviceNames( |
||||
|
std::vector<AudioRenderer::AudioDevice::AudioDeviceName>& names) const; |
||||
|
|
||||
|
/// Core system |
||||
|
Core::System& system; |
||||
|
/// Array of session ids |
||||
|
std::array<size_t, MaxOutSessions> session_ids{}; |
||||
|
/// Array of resource user ids |
||||
|
std::array<size_t, MaxOutSessions> applet_resource_user_ids{}; |
||||
|
/// Pointer to each open session |
||||
|
std::array<std::shared_ptr<Out>, MaxOutSessions> sessions{}; |
||||
|
/// The number of free sessions |
||||
|
size_t num_free_sessions{}; |
||||
|
/// The next session id to be taken |
||||
|
size_t next_session_id{}; |
||||
|
/// The next session id to be freed |
||||
|
size_t free_session_id{}; |
||||
|
/// Whether this is linked to the audio manager |
||||
|
bool linked_to_manager{}; |
||||
|
/// Whether the sessions have been started |
||||
|
bool sessions_started{}; |
||||
|
/// Protect state due to audio manager callback |
||||
|
std::recursive_mutex mutex{}; |
||||
|
}; |
||||
|
|
||||
|
} // namespace AudioCore::AudioOut |
||||
@ -0,0 +1,70 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
|
||||
|
#include "audio_core/audio_render_manager.h"
|
||||
|
#include "audio_core/common/audio_renderer_parameter.h"
|
||||
|
#include "audio_core/common/feature_support.h"
|
||||
|
#include "core/core.h"
|
||||
|
|
||||
|
namespace AudioCore::AudioRenderer { |
||||
|
|
||||
|
Manager::Manager(Core::System& system_) |
||||
|
: system{system_}, system_manager{std::make_unique<SystemManager>(system)} { |
||||
|
std::iota(session_ids.begin(), session_ids.end(), 0); |
||||
|
} |
||||
|
|
||||
|
Manager::~Manager() { |
||||
|
Stop(); |
||||
|
} |
||||
|
|
||||
|
void Manager::Stop() { |
||||
|
system_manager->Stop(); |
||||
|
} |
||||
|
|
||||
|
SystemManager& Manager::GetSystemManager() { |
||||
|
return *system_manager; |
||||
|
} |
||||
|
|
||||
|
auto Manager::GetWorkBufferSize(const AudioRendererParameterInternal& params, u64& out_count) |
||||
|
-> Result { |
||||
|
if (!CheckValidRevision(params.revision)) { |
||||
|
return Service::Audio::ERR_INVALID_REVISION; |
||||
|
} |
||||
|
|
||||
|
out_count = System::GetWorkBufferSize(params); |
||||
|
|
||||
|
return ResultSuccess; |
||||
|
} |
||||
|
|
||||
|
s32 Manager::GetSessionId() { |
||||
|
std::scoped_lock l{session_lock}; |
||||
|
auto session_id{session_ids[session_count]}; |
||||
|
|
||||
|
if (session_id == -1) { |
||||
|
return -1; |
||||
|
} |
||||
|
|
||||
|
session_ids[session_count] = -1; |
||||
|
session_count++; |
||||
|
return session_id; |
||||
|
} |
||||
|
|
||||
|
void Manager::ReleaseSessionId(const s32 session_id) { |
||||
|
std::scoped_lock l{session_lock}; |
||||
|
session_ids[--session_count] = session_id; |
||||
|
} |
||||
|
|
||||
|
u32 Manager::GetSessionCount() { |
||||
|
std::scoped_lock l{session_lock}; |
||||
|
return session_count; |
||||
|
} |
||||
|
|
||||
|
bool Manager::AddSystem(System& system_) { |
||||
|
return system_manager->Add(system_); |
||||
|
} |
||||
|
|
||||
|
bool Manager::RemoveSystem(System& system_) { |
||||
|
return system_manager->Remove(system_); |
||||
|
} |
||||
|
|
||||
|
} // namespace AudioCore::AudioRenderer
|
||||
@ -0,0 +1,103 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project |
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <array> |
||||
|
#include <memory> |
||||
|
#include <mutex> |
||||
|
|
||||
|
#include "audio_core/common/common.h" |
||||
|
#include "audio_core/renderer/system_manager.h" |
||||
|
#include "core/hle/service/audio/errors.h" |
||||
|
|
||||
|
namespace Core { |
||||
|
class System; |
||||
|
} |
||||
|
|
||||
|
namespace AudioCore { |
||||
|
struct AudioRendererParameterInternal; |
||||
|
|
||||
|
namespace AudioRenderer { |
||||
|
/** |
||||
|
* Wrapper for the audio system manager, handles service calls. |
||||
|
*/ |
||||
|
class Manager { |
||||
|
public: |
||||
|
explicit Manager(Core::System& system); |
||||
|
~Manager(); |
||||
|
|
||||
|
/** |
||||
|
* Stop the manager. |
||||
|
*/ |
||||
|
void Stop(); |
||||
|
|
||||
|
/** |
||||
|
* Get the system manager. |
||||
|
* |
||||
|
* @return The system manager. |
||||
|
*/ |
||||
|
SystemManager& GetSystemManager(); |
||||
|
|
||||
|
/** |
||||
|
* Get required size for the audio renderer workbuffer. |
||||
|
* |
||||
|
* @param params - Input parameters with the numbers of voices/mixes/sinks etc. |
||||
|
* @param out_count - Output size of the required workbuffer. |
||||
|
* @return Result code. |
||||
|
*/ |
||||
|
Result GetWorkBufferSize(const AudioRendererParameterInternal& params, u64& out_count); |
||||
|
|
||||
|
/** |
||||
|
* Get a new session id. |
||||
|
* |
||||
|
* @return The new session id. -1 if invalid, otherwise 0-MaxRendererSessions. |
||||
|
*/ |
||||
|
s32 GetSessionId(); |
||||
|
|
||||
|
/** |
||||
|
* Get the number of currently active sessions. |
||||
|
* |
||||
|
* @return The number of active sessions. |
||||
|
*/ |
||||
|
u32 GetSessionCount(); |
||||
|
|
||||
|
/** |
||||
|
* Add a renderer system to the manager. |
||||
|
* The system will be reguarly called to generate commands for the AudioRenderer. |
||||
|
* |
||||
|
* @param system - The system to add. |
||||
|
* @return True if the system was sucessfully added, otherwise false. |
||||
|
*/ |
||||
|
bool AddSystem(System& system); |
||||
|
|
||||
|
/** |
||||
|
* Remove a renderer system from the manager. |
||||
|
* |
||||
|
* @param system - The system to remove. |
||||
|
* @return True if the system was sucessfully removed, otherwise false. |
||||
|
*/ |
||||
|
bool RemoveSystem(System& system); |
||||
|
|
||||
|
/** |
||||
|
* Free a session id when the system wants to shut down. |
||||
|
* |
||||
|
* @param session_id - The session id to free. |
||||
|
*/ |
||||
|
void ReleaseSessionId(s32 session_id); |
||||
|
|
||||
|
private: |
||||
|
/// Core system |
||||
|
Core::System& system; |
||||
|
/// Session ids, -1 when in use |
||||
|
std::array<s32, MaxRendererSessions> session_ids{}; |
||||
|
/// Number of active renderers |
||||
|
u32 session_count{}; |
||||
|
/// Lock for interacting with the sessions |
||||
|
std::mutex session_lock{}; |
||||
|
/// Regularly generates commands from the registered systems for the AudioRenderer |
||||
|
std::unique_ptr<SystemManager> system_manager{}; |
||||
|
}; |
||||
|
|
||||
|
} // namespace AudioRenderer |
||||
|
} // namespace AudioCore |
||||
@ -1,343 +0,0 @@ |
|||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||
|
|
||||
#include <limits>
|
|
||||
#include <optional>
|
|
||||
#include <vector>
|
|
||||
|
|
||||
#include "audio_core/audio_out.h"
|
|
||||
#include "audio_core/audio_renderer.h"
|
|
||||
#include "audio_core/common.h"
|
|
||||
#include "audio_core/info_updater.h"
|
|
||||
#include "audio_core/voice_context.h"
|
|
||||
#include "common/logging/log.h"
|
|
||||
#include "common/settings.h"
|
|
||||
#include "core/core_timing.h"
|
|
||||
#include "core/memory.h"
|
|
||||
|
|
||||
namespace { |
|
||||
[[nodiscard]] static constexpr s16 ClampToS16(s32 value) { |
|
||||
return static_cast<s16>(std::clamp(value, s32{std::numeric_limits<s16>::min()}, |
|
||||
s32{std::numeric_limits<s16>::max()})); |
|
||||
} |
|
||||
|
|
||||
[[nodiscard]] static constexpr s16 Mix2To1(s16 l_channel, s16 r_channel) { |
|
||||
// Mix 50% from left and 50% from right channel
|
|
||||
constexpr float l_mix_amount = 50.0f / 100.0f; |
|
||||
constexpr float r_mix_amount = 50.0f / 100.0f; |
|
||||
return ClampToS16(static_cast<s32>((static_cast<float>(l_channel) * l_mix_amount) + |
|
||||
(static_cast<float>(r_channel) * r_mix_amount))); |
|
||||
} |
|
||||
|
|
||||
[[maybe_unused, nodiscard]] static constexpr std::tuple<s16, s16> Mix6To2( |
|
||||
s16 fl_channel, s16 fr_channel, s16 fc_channel, [[maybe_unused]] s16 lf_channel, s16 bl_channel, |
|
||||
s16 br_channel) { |
|
||||
// Front channels are mixed 36.94%, Center channels are mixed to be 26.12% & the back channels
|
|
||||
// are mixed to be 36.94%
|
|
||||
|
|
||||
constexpr float front_mix_amount = 36.94f / 100.0f; |
|
||||
constexpr float center_mix_amount = 26.12f / 100.0f; |
|
||||
constexpr float back_mix_amount = 36.94f / 100.0f; |
|
||||
|
|
||||
// Mix 50% from left and 50% from right channel
|
|
||||
const auto left = front_mix_amount * static_cast<float>(fl_channel) + |
|
||||
center_mix_amount * static_cast<float>(fc_channel) + |
|
||||
back_mix_amount * static_cast<float>(bl_channel); |
|
||||
|
|
||||
const auto right = front_mix_amount * static_cast<float>(fr_channel) + |
|
||||
center_mix_amount * static_cast<float>(fc_channel) + |
|
||||
back_mix_amount * static_cast<float>(br_channel); |
|
||||
|
|
||||
return {ClampToS16(static_cast<s32>(left)), ClampToS16(static_cast<s32>(right))}; |
|
||||
} |
|
||||
|
|
||||
[[nodiscard]] static constexpr std::tuple<s16, s16> Mix6To2WithCoefficients( |
|
||||
s16 fl_channel, s16 fr_channel, s16 fc_channel, s16 lf_channel, s16 bl_channel, s16 br_channel, |
|
||||
const std::array<float_le, 4>& coeff) { |
|
||||
const auto left = |
|
||||
static_cast<float>(fl_channel) * coeff[0] + static_cast<float>(fc_channel) * coeff[1] + |
|
||||
static_cast<float>(lf_channel) * coeff[2] + static_cast<float>(bl_channel) * coeff[3]; |
|
||||
|
|
||||
const auto right = |
|
||||
static_cast<float>(fr_channel) * coeff[0] + static_cast<float>(fc_channel) * coeff[1] + |
|
||||
static_cast<float>(lf_channel) * coeff[2] + static_cast<float>(br_channel) * coeff[3]; |
|
||||
|
|
||||
return {ClampToS16(static_cast<s32>(left)), ClampToS16(static_cast<s32>(right))}; |
|
||||
} |
|
||||
|
|
||||
} // namespace
|
|
||||
|
|
||||
namespace AudioCore { |
|
||||
constexpr s32 NUM_BUFFERS = 2; |
|
||||
|
|
||||
AudioRenderer::AudioRenderer(Core::Timing::CoreTiming& core_timing_, Core::Memory::Memory& memory_, |
|
||||
AudioCommon::AudioRendererParameter params, |
|
||||
Stream::ReleaseCallback&& release_callback, |
|
||||
std::size_t instance_number) |
|
||||
: worker_params{params}, memory_pool_info(params.effect_count + params.voice_count * 4), |
|
||||
voice_context(params.voice_count), effect_context(params.effect_count), mix_context(), |
|
||||
sink_context(params.sink_count), splitter_context(), |
|
||||
voices(params.voice_count), memory{memory_}, |
|
||||
command_generator(worker_params, voice_context, mix_context, splitter_context, effect_context, |
|
||||
memory), |
|
||||
core_timing{core_timing_} { |
|
||||
behavior_info.SetUserRevision(params.revision); |
|
||||
splitter_context.Initialize(behavior_info, params.splitter_count, |
|
||||
params.num_splitter_send_channels); |
|
||||
mix_context.Initialize(behavior_info, params.submix_count + 1, params.effect_count); |
|
||||
audio_out = std::make_unique<AudioCore::AudioOut>(); |
|
||||
stream = audio_out->OpenStream( |
|
||||
core_timing, params.sample_rate, AudioCommon::STREAM_NUM_CHANNELS, |
|
||||
fmt::format("AudioRenderer-Instance{}", instance_number), std::move(release_callback)); |
|
||||
process_event = |
|
||||
Core::Timing::CreateEvent(fmt::format("AudioRenderer-Instance{}-Process", instance_number), |
|
||||
[this](std::uintptr_t, s64, std::chrono::nanoseconds) { |
|
||||
ReleaseAndQueueBuffers(); |
|
||||
return std::nullopt; |
|
||||
}); |
|
||||
for (s32 i = 0; i < NUM_BUFFERS; ++i) { |
|
||||
QueueMixedBuffer(i); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
AudioRenderer::~AudioRenderer() = default; |
|
||||
|
|
||||
Result AudioRenderer::Start() { |
|
||||
audio_out->StartStream(stream); |
|
||||
ReleaseAndQueueBuffers(); |
|
||||
return ResultSuccess; |
|
||||
} |
|
||||
|
|
||||
Result AudioRenderer::Stop() { |
|
||||
audio_out->StopStream(stream); |
|
||||
return ResultSuccess; |
|
||||
} |
|
||||
|
|
||||
u32 AudioRenderer::GetSampleRate() const { |
|
||||
return worker_params.sample_rate; |
|
||||
} |
|
||||
|
|
||||
u32 AudioRenderer::GetSampleCount() const { |
|
||||
return worker_params.sample_count; |
|
||||
} |
|
||||
|
|
||||
u32 AudioRenderer::GetMixBufferCount() const { |
|
||||
return worker_params.mix_buffer_count; |
|
||||
} |
|
||||
|
|
||||
Stream::State AudioRenderer::GetStreamState() const { |
|
||||
return stream->GetState(); |
|
||||
} |
|
||||
|
|
||||
Result AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_params, |
|
||||
std::vector<u8>& output_params) { |
|
||||
std::scoped_lock lock{mutex}; |
|
||||
InfoUpdater info_updater{input_params, output_params, behavior_info}; |
|
||||
|
|
||||
if (!info_updater.UpdateBehaviorInfo(behavior_info)) { |
|
||||
LOG_ERROR(Audio, "Failed to update behavior info input parameters"); |
|
||||
return AudioCommon::Audren::ERR_INVALID_PARAMETERS; |
|
||||
} |
|
||||
|
|
||||
if (!info_updater.UpdateMemoryPools(memory_pool_info)) { |
|
||||
LOG_ERROR(Audio, "Failed to update memory pool parameters"); |
|
||||
return AudioCommon::Audren::ERR_INVALID_PARAMETERS; |
|
||||
} |
|
||||
|
|
||||
if (!info_updater.UpdateVoiceChannelResources(voice_context)) { |
|
||||
LOG_ERROR(Audio, "Failed to update voice channel resource parameters"); |
|
||||
return AudioCommon::Audren::ERR_INVALID_PARAMETERS; |
|
||||
} |
|
||||
|
|
||||
if (!info_updater.UpdateVoices(voice_context, memory_pool_info, 0)) { |
|
||||
LOG_ERROR(Audio, "Failed to update voice parameters"); |
|
||||
return AudioCommon::Audren::ERR_INVALID_PARAMETERS; |
|
||||
} |
|
||||
|
|
||||
// TODO(ogniK): Deal with stopped audio renderer but updates still taking place
|
|
||||
if (!info_updater.UpdateEffects(effect_context, true)) { |
|
||||
LOG_ERROR(Audio, "Failed to update effect parameters"); |
|
||||
return AudioCommon::Audren::ERR_INVALID_PARAMETERS; |
|
||||
} |
|
||||
|
|
||||
if (behavior_info.IsSplitterSupported()) { |
|
||||
if (!info_updater.UpdateSplitterInfo(splitter_context)) { |
|
||||
LOG_ERROR(Audio, "Failed to update splitter parameters"); |
|
||||
return AudioCommon::Audren::ERR_INVALID_PARAMETERS; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
const auto mix_result = info_updater.UpdateMixes(mix_context, worker_params.mix_buffer_count, |
|
||||
splitter_context, effect_context); |
|
||||
|
|
||||
if (mix_result.IsError()) { |
|
||||
LOG_ERROR(Audio, "Failed to update mix parameters"); |
|
||||
return mix_result; |
|
||||
} |
|
||||
|
|
||||
// TODO(ogniK): Sinks
|
|
||||
if (!info_updater.UpdateSinks(sink_context)) { |
|
||||
LOG_ERROR(Audio, "Failed to update sink parameters"); |
|
||||
return AudioCommon::Audren::ERR_INVALID_PARAMETERS; |
|
||||
} |
|
||||
|
|
||||
// TODO(ogniK): Performance buffer
|
|
||||
if (!info_updater.UpdatePerformanceBuffer()) { |
|
||||
LOG_ERROR(Audio, "Failed to update performance buffer parameters"); |
|
||||
return AudioCommon::Audren::ERR_INVALID_PARAMETERS; |
|
||||
} |
|
||||
|
|
||||
if (!info_updater.UpdateErrorInfo(behavior_info)) { |
|
||||
LOG_ERROR(Audio, "Failed to update error info"); |
|
||||
return AudioCommon::Audren::ERR_INVALID_PARAMETERS; |
|
||||
} |
|
||||
|
|
||||
if (behavior_info.IsElapsedFrameCountSupported()) { |
|
||||
if (!info_updater.UpdateRendererInfo(elapsed_frame_count)) { |
|
||||
LOG_ERROR(Audio, "Failed to update renderer info"); |
|
||||
return AudioCommon::Audren::ERR_INVALID_PARAMETERS; |
|
||||
} |
|
||||
} |
|
||||
// TODO(ogniK): Statistics
|
|
||||
|
|
||||
if (!info_updater.WriteOutputHeader()) { |
|
||||
LOG_ERROR(Audio, "Failed to write output header"); |
|
||||
return AudioCommon::Audren::ERR_INVALID_PARAMETERS; |
|
||||
} |
|
||||
|
|
||||
// TODO(ogniK): Check when all sections are implemented
|
|
||||
|
|
||||
if (!info_updater.CheckConsumedSize()) { |
|
||||
LOG_ERROR(Audio, "Audio buffers were not consumed!"); |
|
||||
return AudioCommon::Audren::ERR_INVALID_PARAMETERS; |
|
||||
} |
|
||||
return ResultSuccess; |
|
||||
} |
|
||||
|
|
||||
void AudioRenderer::QueueMixedBuffer(Buffer::Tag tag) { |
|
||||
command_generator.PreCommand(); |
|
||||
// Clear mix buffers before our next operation
|
|
||||
command_generator.ClearMixBuffers(); |
|
||||
|
|
||||
// If the splitter is not in use, sort our mixes
|
|
||||
if (!splitter_context.UsingSplitter()) { |
|
||||
mix_context.SortInfo(); |
|
||||
} |
|
||||
// Sort our voices
|
|
||||
voice_context.SortInfo(); |
|
||||
|
|
||||
// Handle samples
|
|
||||
command_generator.GenerateVoiceCommands(); |
|
||||
command_generator.GenerateSubMixCommands(); |
|
||||
command_generator.GenerateFinalMixCommands(); |
|
||||
|
|
||||
command_generator.PostCommand(); |
|
||||
// Base sample size
|
|
||||
std::size_t BUFFER_SIZE{worker_params.sample_count}; |
|
||||
// Samples, making sure to clear
|
|
||||
std::vector<s16> buffer(BUFFER_SIZE * stream->GetNumChannels(), 0); |
|
||||
|
|
||||
if (sink_context.InUse()) { |
|
||||
const auto stream_channel_count = stream->GetNumChannels(); |
|
||||
const auto buffer_offsets = sink_context.OutputBuffers(); |
|
||||
const auto channel_count = buffer_offsets.size(); |
|
||||
const auto& final_mix = mix_context.GetFinalMixInfo(); |
|
||||
const auto& in_params = final_mix.GetInParams(); |
|
||||
std::vector<std::span<s32>> mix_buffers(channel_count); |
|
||||
for (std::size_t i = 0; i < channel_count; i++) { |
|
||||
mix_buffers[i] = |
|
||||
command_generator.GetMixBuffer(in_params.buffer_offset + buffer_offsets[i]); |
|
||||
} |
|
||||
|
|
||||
for (std::size_t i = 0; i < BUFFER_SIZE; i++) { |
|
||||
if (channel_count == 1) { |
|
||||
const auto sample = ClampToS16(mix_buffers[0][i]); |
|
||||
|
|
||||
// Place sample in all channels
|
|
||||
for (u32 channel = 0; channel < stream_channel_count; channel++) { |
|
||||
buffer[i * stream_channel_count + channel] = sample; |
|
||||
} |
|
||||
|
|
||||
if (stream_channel_count == 6) { |
|
||||
// Output stream has a LF channel, mute it!
|
|
||||
buffer[i * stream_channel_count + 3] = 0; |
|
||||
} |
|
||||
|
|
||||
} else if (channel_count == 2) { |
|
||||
const auto l_sample = ClampToS16(mix_buffers[0][i]); |
|
||||
const auto r_sample = ClampToS16(mix_buffers[1][i]); |
|
||||
if (stream_channel_count == 1) { |
|
||||
buffer[i * stream_channel_count + 0] = Mix2To1(l_sample, r_sample); |
|
||||
} else if (stream_channel_count == 2) { |
|
||||
buffer[i * stream_channel_count + 0] = l_sample; |
|
||||
buffer[i * stream_channel_count + 1] = r_sample; |
|
||||
} else if (stream_channel_count == 6) { |
|
||||
buffer[i * stream_channel_count + 0] = l_sample; |
|
||||
buffer[i * stream_channel_count + 1] = r_sample; |
|
||||
|
|
||||
// Combine both left and right channels to the center channel
|
|
||||
buffer[i * stream_channel_count + 2] = Mix2To1(l_sample, r_sample); |
|
||||
|
|
||||
buffer[i * stream_channel_count + 4] = l_sample; |
|
||||
buffer[i * stream_channel_count + 5] = r_sample; |
|
||||
} |
|
||||
|
|
||||
} else if (channel_count == 6) { |
|
||||
const auto fl_sample = ClampToS16(mix_buffers[0][i]); |
|
||||
const auto fr_sample = ClampToS16(mix_buffers[1][i]); |
|
||||
const auto fc_sample = ClampToS16(mix_buffers[2][i]); |
|
||||
const auto lf_sample = ClampToS16(mix_buffers[3][i]); |
|
||||
const auto bl_sample = ClampToS16(mix_buffers[4][i]); |
|
||||
const auto br_sample = ClampToS16(mix_buffers[5][i]); |
|
||||
|
|
||||
if (stream_channel_count == 1) { |
|
||||
// Games seem to ignore the center channel half the time, we use the front left
|
|
||||
// and right channel for mixing as that's where majority of the audio goes
|
|
||||
buffer[i * stream_channel_count + 0] = Mix2To1(fl_sample, fr_sample); |
|
||||
} else if (stream_channel_count == 2) { |
|
||||
// Mix all channels into 2 channels
|
|
||||
const auto [left, right] = Mix6To2WithCoefficients( |
|
||||
fl_sample, fr_sample, fc_sample, lf_sample, bl_sample, br_sample, |
|
||||
sink_context.GetDownmixCoefficients()); |
|
||||
buffer[i * stream_channel_count + 0] = left; |
|
||||
buffer[i * stream_channel_count + 1] = right; |
|
||||
} else if (stream_channel_count == 6) { |
|
||||
// Pass through
|
|
||||
buffer[i * stream_channel_count + 0] = fl_sample; |
|
||||
buffer[i * stream_channel_count + 1] = fr_sample; |
|
||||
buffer[i * stream_channel_count + 2] = fc_sample; |
|
||||
buffer[i * stream_channel_count + 3] = lf_sample; |
|
||||
buffer[i * stream_channel_count + 4] = bl_sample; |
|
||||
buffer[i * stream_channel_count + 5] = br_sample; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
audio_out->QueueBuffer(stream, tag, std::move(buffer)); |
|
||||
elapsed_frame_count++; |
|
||||
voice_context.UpdateStateByDspShared(); |
|
||||
} |
|
||||
|
|
||||
void AudioRenderer::ReleaseAndQueueBuffers() { |
|
||||
if (!stream->IsPlaying()) { |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
{ |
|
||||
std::scoped_lock lock{mutex}; |
|
||||
const auto released_buffers{audio_out->GetTagsAndReleaseBuffers(stream)}; |
|
||||
for (const auto& tag : released_buffers) { |
|
||||
QueueMixedBuffer(tag); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
const f32 sample_rate = static_cast<f32>(GetSampleRate()); |
|
||||
const f32 sample_count = static_cast<f32>(GetSampleCount()); |
|
||||
const f32 consume_rate = sample_rate / (sample_count * (sample_count / 240)); |
|
||||
const s32 ms = (1000 / static_cast<s32>(consume_rate)) - 1; |
|
||||
const std::chrono::milliseconds next_event_time(std::max(ms / NUM_BUFFERS, 1)); |
|
||||
core_timing.ScheduleEvent(next_event_time, process_event, {}); |
|
||||
} |
|
||||
|
|
||||
} // namespace AudioCore
|
|
||||
@ -1,78 +0,0 @@ |
|||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project |
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later |
|
||||
|
|
||||
#pragma once |
|
||||
|
|
||||
#include <array> |
|
||||
#include <memory> |
|
||||
#include <mutex> |
|
||||
#include <vector> |
|
||||
|
|
||||
#include "audio_core/behavior_info.h" |
|
||||
#include "audio_core/command_generator.h" |
|
||||
#include "audio_core/common.h" |
|
||||
#include "audio_core/effect_context.h" |
|
||||
#include "audio_core/memory_pool.h" |
|
||||
#include "audio_core/mix_context.h" |
|
||||
#include "audio_core/sink_context.h" |
|
||||
#include "audio_core/splitter_context.h" |
|
||||
#include "audio_core/stream.h" |
|
||||
#include "audio_core/voice_context.h" |
|
||||
#include "common/common_funcs.h" |
|
||||
#include "common/common_types.h" |
|
||||
#include "common/swap.h" |
|
||||
#include "core/hle/result.h" |
|
||||
|
|
||||
namespace Core::Timing { |
|
||||
class CoreTiming; |
|
||||
} |
|
||||
|
|
||||
namespace Core::Memory { |
|
||||
class Memory; |
|
||||
} |
|
||||
|
|
||||
namespace AudioCore { |
|
||||
using DSPStateHolder = std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>; |
|
||||
|
|
||||
class AudioOut; |
|
||||
|
|
||||
class AudioRenderer { |
|
||||
public: |
|
||||
AudioRenderer(Core::Timing::CoreTiming& core_timing, Core::Memory::Memory& memory_, |
|
||||
AudioCommon::AudioRendererParameter params, |
|
||||
Stream::ReleaseCallback&& release_callback, std::size_t instance_number); |
|
||||
~AudioRenderer(); |
|
||||
|
|
||||
[[nodiscard]] Result UpdateAudioRenderer(const std::vector<u8>& input_params, |
|
||||
std::vector<u8>& output_params); |
|
||||
[[nodiscard]] Result Start(); |
|
||||
[[nodiscard]] Result Stop(); |
|
||||
void QueueMixedBuffer(Buffer::Tag tag); |
|
||||
void ReleaseAndQueueBuffers(); |
|
||||
[[nodiscard]] u32 GetSampleRate() const; |
|
||||
[[nodiscard]] u32 GetSampleCount() const; |
|
||||
[[nodiscard]] u32 GetMixBufferCount() const; |
|
||||
[[nodiscard]] Stream::State GetStreamState() const; |
|
||||
|
|
||||
private: |
|
||||
BehaviorInfo behavior_info{}; |
|
||||
|
|
||||
AudioCommon::AudioRendererParameter worker_params; |
|
||||
std::vector<ServerMemoryPoolInfo> memory_pool_info; |
|
||||
VoiceContext voice_context; |
|
||||
EffectContext effect_context; |
|
||||
MixContext mix_context; |
|
||||
SinkContext sink_context; |
|
||||
SplitterContext splitter_context; |
|
||||
std::vector<VoiceState> voices; |
|
||||
std::unique_ptr<AudioOut> audio_out; |
|
||||
StreamPtr stream; |
|
||||
Core::Memory::Memory& memory; |
|
||||
CommandGenerator command_generator; |
|
||||
std::size_t elapsed_frame_count{}; |
|
||||
Core::Timing::CoreTiming& core_timing; |
|
||||
std::shared_ptr<Core::Timing::EventType> process_event; |
|
||||
std::mutex mutex; |
|
||||
}; |
|
||||
|
|
||||
} // namespace AudioCore |
|
||||
@ -1,104 +0,0 @@ |
|||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||
|
|
||||
#include <cstring>
|
|
||||
#include "audio_core/behavior_info.h"
|
|
||||
#include "audio_core/common.h"
|
|
||||
#include "common/logging/log.h"
|
|
||||
|
|
||||
namespace AudioCore { |
|
||||
|
|
||||
BehaviorInfo::BehaviorInfo() : process_revision(AudioCommon::CURRENT_PROCESS_REVISION) {} |
|
||||
BehaviorInfo::~BehaviorInfo() = default; |
|
||||
|
|
||||
bool BehaviorInfo::UpdateOutput(std::vector<u8>& buffer, std::size_t offset) { |
|
||||
if (!AudioCommon::CanConsumeBuffer(buffer.size(), offset, sizeof(OutParams))) { |
|
||||
LOG_ERROR(Audio, "Buffer is an invalid size!"); |
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
OutParams params{}; |
|
||||
std::memcpy(params.errors.data(), errors.data(), sizeof(ErrorInfo) * errors.size()); |
|
||||
params.error_count = static_cast<u32_le>(error_count); |
|
||||
std::memcpy(buffer.data() + offset, ¶ms, sizeof(OutParams)); |
|
||||
return true; |
|
||||
} |
|
||||
|
|
||||
void BehaviorInfo::ClearError() { |
|
||||
error_count = 0; |
|
||||
} |
|
||||
|
|
||||
void BehaviorInfo::UpdateFlags(u64_le dest_flags) { |
|
||||
flags = dest_flags; |
|
||||
} |
|
||||
|
|
||||
void BehaviorInfo::SetUserRevision(u32_le revision) { |
|
||||
user_revision = revision; |
|
||||
} |
|
||||
|
|
||||
u32_le BehaviorInfo::GetUserRevision() const { |
|
||||
return user_revision; |
|
||||
} |
|
||||
|
|
||||
u32_le BehaviorInfo::GetProcessRevision() const { |
|
||||
return process_revision; |
|
||||
} |
|
||||
|
|
||||
bool BehaviorInfo::IsAdpcmLoopContextBugFixed() const { |
|
||||
return AudioCommon::IsRevisionSupported(2, user_revision); |
|
||||
} |
|
||||
|
|
||||
bool BehaviorInfo::IsSplitterSupported() const { |
|
||||
return AudioCommon::IsRevisionSupported(2, user_revision); |
|
||||
} |
|
||||
|
|
||||
bool BehaviorInfo::IsLongSizePreDelaySupported() const { |
|
||||
return AudioCommon::IsRevisionSupported(3, user_revision); |
|
||||
} |
|
||||
|
|
||||
bool BehaviorInfo::IsAudioRendererProcessingTimeLimit80PercentSupported() const { |
|
||||
return AudioCommon::IsRevisionSupported(5, user_revision); |
|
||||
} |
|
||||
|
|
||||
bool BehaviorInfo::IsAudioRendererProcessingTimeLimit75PercentSupported() const { |
|
||||
return AudioCommon::IsRevisionSupported(4, user_revision); |
|
||||
} |
|
||||
|
|
||||
bool BehaviorInfo::IsAudioRendererProcessingTimeLimit70PercentSupported() const { |
|
||||
return AudioCommon::IsRevisionSupported(1, user_revision); |
|
||||
} |
|
||||
|
|
||||
bool BehaviorInfo::IsElapsedFrameCountSupported() const { |
|
||||
return AudioCommon::IsRevisionSupported(5, user_revision); |
|
||||
} |
|
||||
|
|
||||
bool BehaviorInfo::IsMemoryPoolForceMappingEnabled() const { |
|
||||
return (flags & 1) != 0; |
|
||||
} |
|
||||
|
|
||||
bool BehaviorInfo::IsFlushVoiceWaveBuffersSupported() const { |
|
||||
return AudioCommon::IsRevisionSupported(5, user_revision); |
|
||||
} |
|
||||
|
|
||||
bool BehaviorInfo::IsVoicePlayedSampleCountResetAtLoopPointSupported() const { |
|
||||
return AudioCommon::IsRevisionSupported(5, user_revision); |
|
||||
} |
|
||||
|
|
||||
bool BehaviorInfo::IsVoicePitchAndSrcSkippedSupported() const { |
|
||||
return AudioCommon::IsRevisionSupported(5, user_revision); |
|
||||
} |
|
||||
|
|
||||
bool BehaviorInfo::IsMixInParameterDirtyOnlyUpdateSupported() const { |
|
||||
return AudioCommon::IsRevisionSupported(7, user_revision); |
|
||||
} |
|
||||
|
|
||||
bool BehaviorInfo::IsSplitterBugFixed() const { |
|
||||
return AudioCommon::IsRevisionSupported(5, user_revision); |
|
||||
} |
|
||||
|
|
||||
void BehaviorInfo::CopyErrorInfo(BehaviorInfo::OutParams& dst) { |
|
||||
dst.error_count = static_cast<u32>(error_count); |
|
||||
std::copy(errors.begin(), errors.begin() + error_count, dst.errors.begin()); |
|
||||
} |
|
||||
|
|
||||
} // namespace AudioCore
|
|
||||
@ -1,71 +0,0 @@ |
|||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project |
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later |
|
||||
|
|
||||
#pragma once |
|
||||
|
|
||||
#include <array> |
|
||||
|
|
||||
#include <vector> |
|
||||
#include "common/common_funcs.h" |
|
||||
#include "common/common_types.h" |
|
||||
#include "common/swap.h" |
|
||||
|
|
||||
namespace AudioCore { |
|
||||
class BehaviorInfo { |
|
||||
public: |
|
||||
struct ErrorInfo { |
|
||||
u32_le result{}; |
|
||||
INSERT_PADDING_WORDS(1); |
|
||||
u64_le result_info{}; |
|
||||
}; |
|
||||
static_assert(sizeof(ErrorInfo) == 0x10, "ErrorInfo is an invalid size"); |
|
||||
|
|
||||
struct InParams { |
|
||||
u32_le revision{}; |
|
||||
u32_le padding{}; |
|
||||
u64_le flags{}; |
|
||||
}; |
|
||||
static_assert(sizeof(InParams) == 0x10, "InParams is an invalid size"); |
|
||||
|
|
||||
struct OutParams { |
|
||||
std::array<ErrorInfo, 10> errors{}; |
|
||||
u32_le error_count{}; |
|
||||
INSERT_PADDING_BYTES(12); |
|
||||
}; |
|
||||
static_assert(sizeof(OutParams) == 0xb0, "OutParams is an invalid size"); |
|
||||
|
|
||||
explicit BehaviorInfo(); |
|
||||
~BehaviorInfo(); |
|
||||
|
|
||||
bool UpdateOutput(std::vector<u8>& buffer, std::size_t offset); |
|
||||
|
|
||||
void ClearError(); |
|
||||
void UpdateFlags(u64_le dest_flags); |
|
||||
void SetUserRevision(u32_le revision); |
|
||||
[[nodiscard]] u32_le GetUserRevision() const; |
|
||||
[[nodiscard]] u32_le GetProcessRevision() const; |
|
||||
|
|
||||
[[nodiscard]] bool IsAdpcmLoopContextBugFixed() const; |
|
||||
[[nodiscard]] bool IsSplitterSupported() const; |
|
||||
[[nodiscard]] bool IsLongSizePreDelaySupported() const; |
|
||||
[[nodiscard]] bool IsAudioRendererProcessingTimeLimit80PercentSupported() const; |
|
||||
[[nodiscard]] bool IsAudioRendererProcessingTimeLimit75PercentSupported() const; |
|
||||
[[nodiscard]] bool IsAudioRendererProcessingTimeLimit70PercentSupported() const; |
|
||||
[[nodiscard]] bool IsElapsedFrameCountSupported() const; |
|
||||
[[nodiscard]] bool IsMemoryPoolForceMappingEnabled() const; |
|
||||
[[nodiscard]] bool IsFlushVoiceWaveBuffersSupported() const; |
|
||||
[[nodiscard]] bool IsVoicePlayedSampleCountResetAtLoopPointSupported() const; |
|
||||
[[nodiscard]] bool IsVoicePitchAndSrcSkippedSupported() const; |
|
||||
[[nodiscard]] bool IsMixInParameterDirtyOnlyUpdateSupported() const; |
|
||||
[[nodiscard]] bool IsSplitterBugFixed() const; |
|
||||
void CopyErrorInfo(OutParams& dst); |
|
||||
|
|
||||
private: |
|
||||
u32_le process_revision{}; |
|
||||
u32_le user_revision{}; |
|
||||
u64_le flags{}; |
|
||||
std::array<ErrorInfo, 10> errors{}; |
|
||||
std::size_t error_count{}; |
|
||||
}; |
|
||||
|
|
||||
} // namespace AudioCore |
|
||||
@ -1,44 +0,0 @@ |
|||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project |
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later |
|
||||
|
|
||||
#pragma once |
|
||||
|
|
||||
#include <memory> |
|
||||
#include <vector> |
|
||||
|
|
||||
#include "common/common_types.h" |
|
||||
|
|
||||
namespace AudioCore { |
|
||||
|
|
||||
/** |
|
||||
* Represents a buffer of audio samples to be played in an audio stream |
|
||||
*/ |
|
||||
class Buffer { |
|
||||
public: |
|
||||
using Tag = u64; |
|
||||
|
|
||||
Buffer(Tag tag_, std::vector<s16>&& samples_) : tag{tag_}, samples{std::move(samples_)} {} |
|
||||
|
|
||||
/// Returns the raw audio data for the buffer |
|
||||
std::vector<s16>& GetSamples() { |
|
||||
return samples; |
|
||||
} |
|
||||
|
|
||||
/// Returns the raw audio data for the buffer |
|
||||
const std::vector<s16>& GetSamples() const { |
|
||||
return samples; |
|
||||
} |
|
||||
|
|
||||
/// Returns the buffer tag, this is provided by the game to the audout service |
|
||||
Tag GetTag() const { |
|
||||
return tag; |
|
||||
} |
|
||||
|
|
||||
private: |
|
||||
Tag tag; |
|
||||
std::vector<s16> samples; |
|
||||
}; |
|
||||
|
|
||||
using BufferPtr = std::shared_ptr<Buffer>; |
|
||||
|
|
||||
} // namespace AudioCore |
|
||||
@ -1,77 +0,0 @@ |
|||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||
|
|
||||
#include <algorithm>
|
|
||||
|
|
||||
#include "audio_core/codec.h"
|
|
||||
|
|
||||
namespace AudioCore::Codec { |
|
||||
|
|
||||
std::vector<s16> DecodeADPCM(const u8* const data, std::size_t size, const ADPCM_Coeff& coeff, |
|
||||
ADPCMState& state) { |
|
||||
// GC-ADPCM with scale factor and variable coefficients.
|
|
||||
// Frames are 8 bytes long containing 14 samples each.
|
|
||||
// Samples are 4 bits (one nibble) long.
|
|
||||
|
|
||||
constexpr std::size_t FRAME_LEN = 8; |
|
||||
constexpr std::size_t SAMPLES_PER_FRAME = 14; |
|
||||
static constexpr std::array<int, 16> SIGNED_NIBBLES{ |
|
||||
0, 1, 2, 3, 4, 5, 6, 7, -8, -7, -6, -5, -4, -3, -2, -1, |
|
||||
}; |
|
||||
|
|
||||
const std::size_t sample_count = (size / FRAME_LEN) * SAMPLES_PER_FRAME; |
|
||||
const std::size_t ret_size = |
|
||||
sample_count % 2 == 0 ? sample_count : sample_count + 1; // Ensure multiple of two.
|
|
||||
std::vector<s16> ret(ret_size); |
|
||||
|
|
||||
int yn1 = state.yn1, yn2 = state.yn2; |
|
||||
|
|
||||
const std::size_t NUM_FRAMES = |
|
||||
(sample_count + (SAMPLES_PER_FRAME - 1)) / SAMPLES_PER_FRAME; // Round up.
|
|
||||
for (std::size_t framei = 0; framei < NUM_FRAMES; framei++) { |
|
||||
const int frame_header = data[framei * FRAME_LEN]; |
|
||||
const int scale = 1 << (frame_header & 0xF); |
|
||||
const int idx = (frame_header >> 4) & 0x7; |
|
||||
|
|
||||
// Coefficients are fixed point with 11 bits fractional part.
|
|
||||
const int coef1 = coeff[idx * 2 + 0]; |
|
||||
const int coef2 = coeff[idx * 2 + 1]; |
|
||||
|
|
||||
// Decodes an audio sample. One nibble produces one sample.
|
|
||||
const auto decode_sample = [&](const int nibble) -> s16 { |
|
||||
const int xn = nibble * scale; |
|
||||
// We first transform everything into 11 bit fixed point, perform the second order
|
|
||||
// digital filter, then transform back.
|
|
||||
// 0x400 == 0.5 in 11 bit fixed point.
|
|
||||
// Filter: y[n] = x[n] + 0.5 + c1 * y[n-1] + c2 * y[n-2]
|
|
||||
int val = ((xn << 11) + 0x400 + coef1 * yn1 + coef2 * yn2) >> 11; |
|
||||
// Clamp to output range.
|
|
||||
val = std::clamp<s32>(val, -32768, 32767); |
|
||||
// Advance output feedback.
|
|
||||
yn2 = yn1; |
|
||||
yn1 = val; |
|
||||
return static_cast<s16>(val); |
|
||||
}; |
|
||||
|
|
||||
std::size_t outputi = framei * SAMPLES_PER_FRAME; |
|
||||
std::size_t datai = framei * FRAME_LEN + 1; |
|
||||
for (std::size_t i = 0; i < SAMPLES_PER_FRAME && outputi < sample_count; i += 2) { |
|
||||
const s16 sample1 = decode_sample(SIGNED_NIBBLES[data[datai] >> 4]); |
|
||||
ret[outputi] = sample1; |
|
||||
outputi++; |
|
||||
|
|
||||
const s16 sample2 = decode_sample(SIGNED_NIBBLES[data[datai] & 0xF]); |
|
||||
ret[outputi] = sample2; |
|
||||
outputi++; |
|
||||
|
|
||||
datai++; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
state.yn1 = static_cast<s16>(yn1); |
|
||||
state.yn2 = static_cast<s16>(yn2); |
|
||||
|
|
||||
return ret; |
|
||||
} |
|
||||
|
|
||||
} // namespace AudioCore::Codec
|
|
||||
@ -1,43 +0,0 @@ |
|||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project |
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later |
|
||||
|
|
||||
#pragma once |
|
||||
|
|
||||
#include <array> |
|
||||
#include <vector> |
|
||||
|
|
||||
#include "common/common_types.h" |
|
||||
|
|
||||
namespace AudioCore::Codec { |
|
||||
|
|
||||
enum class PcmFormat : u32 { |
|
||||
Invalid = 0, |
|
||||
Int8 = 1, |
|
||||
Int16 = 2, |
|
||||
Int24 = 3, |
|
||||
Int32 = 4, |
|
||||
PcmFloat = 5, |
|
||||
Adpcm = 6, |
|
||||
}; |
|
||||
|
|
||||
/// See: Codec::DecodeADPCM |
|
||||
struct ADPCMState { |
|
||||
// Two historical samples from previous processed buffer, |
|
||||
// required for ADPCM decoding |
|
||||
s16 yn1; ///< y[n-1] |
|
||||
s16 yn2; ///< y[n-2] |
|
||||
}; |
|
||||
|
|
||||
using ADPCM_Coeff = std::array<s16, 16>; |
|
||||
|
|
||||
/** |
|
||||
* @param data Pointer to buffer that contains ADPCM data to decode |
|
||||
* @param size Size of buffer in bytes |
|
||||
* @param coeff ADPCM coefficients |
|
||||
* @param state ADPCM state, this is updated with new state |
|
||||
* @return Decoded stereo signed PCM16 data, sample_count in length |
|
||||
*/ |
|
||||
std::vector<s16> DecodeADPCM(const u8* data, std::size_t size, const ADPCM_Coeff& coeff, |
|
||||
ADPCMState& state); |
|
||||
|
|
||||
}; // namespace AudioCore::Codec |
|
||||
1369
src/audio_core/command_generator.cpp
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -1,110 +0,0 @@ |
|||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project |
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later |
|
||||
|
|
||||
#pragma once |
|
||||
|
|
||||
#include <array> |
|
||||
#include <span> |
|
||||
#include "audio_core/common.h" |
|
||||
#include "audio_core/voice_context.h" |
|
||||
#include "common/common_types.h" |
|
||||
|
|
||||
namespace Core::Memory { |
|
||||
class Memory; |
|
||||
} |
|
||||
|
|
||||
namespace AudioCore { |
|
||||
class MixContext; |
|
||||
class SplitterContext; |
|
||||
class ServerSplitterDestinationData; |
|
||||
class ServerMixInfo; |
|
||||
class EffectContext; |
|
||||
class EffectBase; |
|
||||
struct AuxInfoDSP; |
|
||||
struct I3dl2ReverbParams; |
|
||||
struct I3dl2ReverbState; |
|
||||
using MixVolumeBuffer = std::array<float, AudioCommon::MAX_MIX_BUFFERS>; |
|
||||
|
|
||||
class CommandGenerator { |
|
||||
public: |
|
||||
explicit CommandGenerator(AudioCommon::AudioRendererParameter& worker_params_, |
|
||||
VoiceContext& voice_context_, MixContext& mix_context_, |
|
||||
SplitterContext& splitter_context_, EffectContext& effect_context_, |
|
||||
Core::Memory::Memory& memory_); |
|
||||
~CommandGenerator(); |
|
||||
|
|
||||
void ClearMixBuffers(); |
|
||||
void GenerateVoiceCommands(); |
|
||||
void GenerateVoiceCommand(ServerVoiceInfo& voice_info); |
|
||||
void GenerateSubMixCommands(); |
|
||||
void GenerateFinalMixCommands(); |
|
||||
void PreCommand(); |
|
||||
void PostCommand(); |
|
||||
|
|
||||
[[nodiscard]] std::span<s32> GetChannelMixBuffer(s32 channel); |
|
||||
[[nodiscard]] std::span<const s32> GetChannelMixBuffer(s32 channel) const; |
|
||||
[[nodiscard]] std::span<s32> GetMixBuffer(std::size_t index); |
|
||||
[[nodiscard]] std::span<const s32> GetMixBuffer(std::size_t index) const; |
|
||||
[[nodiscard]] std::size_t GetMixChannelBufferOffset(s32 channel) const; |
|
||||
|
|
||||
[[nodiscard]] std::size_t GetTotalMixBufferCount() const; |
|
||||
|
|
||||
private: |
|
||||
void GenerateDataSourceCommand(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 channel); |
|
||||
void GenerateBiquadFilterCommandForVoice(ServerVoiceInfo& voice_info, VoiceState& dsp_state, |
|
||||
s32 mix_buffer_count, s32 channel); |
|
||||
void GenerateVolumeRampCommand(float last_volume, float current_volume, s32 channel, |
|
||||
s32 node_id); |
|
||||
void GenerateVoiceMixCommand(const MixVolumeBuffer& mix_volumes, |
|
||||
const MixVolumeBuffer& last_mix_volumes, VoiceState& dsp_state, |
|
||||
s32 mix_buffer_offset, s32 mix_buffer_count, s32 voice_index, |
|
||||
s32 node_id); |
|
||||
void GenerateSubMixCommand(ServerMixInfo& mix_info); |
|
||||
void GenerateMixCommands(ServerMixInfo& mix_info); |
|
||||
void GenerateMixCommand(std::size_t output_offset, std::size_t input_offset, float volume, |
|
||||
s32 node_id); |
|
||||
void GenerateFinalMixCommand(); |
|
||||
void GenerateBiquadFilterCommand(s32 mix_buffer, const BiquadFilterParameter& params, |
|
||||
std::array<s64, 2>& state, std::size_t input_offset, |
|
||||
std::size_t output_offset, s32 sample_count, s32 node_id); |
|
||||
void GenerateDepopPrepareCommand(VoiceState& dsp_state, std::size_t mix_buffer_count, |
|
||||
std::size_t mix_buffer_offset); |
|
||||
void GenerateDepopForMixBuffersCommand(std::size_t mix_buffer_count, |
|
||||
std::size_t mix_buffer_offset, s32 sample_rate); |
|
||||
void GenerateEffectCommand(ServerMixInfo& mix_info); |
|
||||
void GenerateI3dl2ReverbEffectCommand(s32 mix_buffer_offset, EffectBase* info, bool enabled); |
|
||||
void GenerateBiquadFilterEffectCommand(s32 mix_buffer_offset, EffectBase* info, bool enabled); |
|
||||
void GenerateAuxCommand(s32 mix_buffer_offset, EffectBase* info, bool enabled); |
|
||||
[[nodiscard]] ServerSplitterDestinationData* GetDestinationData(s32 splitter_id, s32 index); |
|
||||
|
|
||||
s32 WriteAuxBuffer(AuxInfoDSP& dsp_info, VAddr send_buffer, u32 max_samples, |
|
||||
std::span<const s32> data, u32 sample_count, u32 write_offset, |
|
||||
u32 write_count); |
|
||||
s32 ReadAuxBuffer(AuxInfoDSP& recv_info, VAddr recv_buffer, u32 max_samples, |
|
||||
std::span<s32> out_data, u32 sample_count, u32 read_offset, u32 read_count); |
|
||||
|
|
||||
void InitializeI3dl2Reverb(I3dl2ReverbParams& info, I3dl2ReverbState& state, |
|
||||
std::vector<u8>& work_buffer); |
|
||||
void UpdateI3dl2Reverb(I3dl2ReverbParams& info, I3dl2ReverbState& state, bool should_clear); |
|
||||
// DSP Code |
|
||||
template <typename T> |
|
||||
s32 DecodePcm(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 sample_start_offset, |
|
||||
s32 sample_end_offset, s32 sample_count, s32 channel, std::size_t mix_offset); |
|
||||
s32 DecodeAdpcm(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 sample_start_offset, |
|
||||
s32 sample_end_offset, s32 sample_count, s32 channel, std::size_t mix_offset); |
|
||||
void DecodeFromWaveBuffers(ServerVoiceInfo& voice_info, std::span<s32> output, |
|
||||
VoiceState& dsp_state, s32 channel, s32 target_sample_rate, |
|
||||
s32 sample_count, s32 node_id); |
|
||||
|
|
||||
AudioCommon::AudioRendererParameter& worker_params; |
|
||||
VoiceContext& voice_context; |
|
||||
MixContext& mix_context; |
|
||||
SplitterContext& splitter_context; |
|
||||
EffectContext& effect_context; |
|
||||
Core::Memory::Memory& memory; |
|
||||
std::vector<s32> mix_buffer{}; |
|
||||
std::vector<s32> sample_buffer{}; |
|
||||
std::vector<s32> depop_buffer{}; |
|
||||
bool dumping_frame{false}; |
|
||||
}; |
|
||||
} // namespace AudioCore |
|
||||
@ -1,132 +0,0 @@ |
|||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project |
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later |
|
||||
|
|
||||
#pragma once |
|
||||
|
|
||||
#include "common/common_funcs.h" |
|
||||
#include "common/common_types.h" |
|
||||
#include "common/swap.h" |
|
||||
#include "core/hle/result.h" |
|
||||
|
|
||||
namespace AudioCommon { |
|
||||
namespace Audren { |
|
||||
constexpr Result ERR_INVALID_PARAMETERS{ErrorModule::Audio, 41}; |
|
||||
constexpr Result ERR_SPLITTER_SORT_FAILED{ErrorModule::Audio, 43}; |
|
||||
} // namespace Audren |
|
||||
|
|
||||
constexpr u8 BASE_REVISION = '0'; |
|
||||
constexpr u32_le CURRENT_PROCESS_REVISION = |
|
||||
Common::MakeMagic('R', 'E', 'V', static_cast<u8>(BASE_REVISION + 0xA)); |
|
||||
constexpr std::size_t MAX_MIX_BUFFERS = 24; |
|
||||
constexpr std::size_t MAX_BIQUAD_FILTERS = 2; |
|
||||
constexpr std::size_t MAX_CHANNEL_COUNT = 6; |
|
||||
constexpr std::size_t MAX_WAVE_BUFFERS = 4; |
|
||||
constexpr std::size_t MAX_SAMPLE_HISTORY = 4; |
|
||||
constexpr u32 STREAM_SAMPLE_RATE = 48000; |
|
||||
constexpr u32 STREAM_NUM_CHANNELS = 2; |
|
||||
constexpr s32 NO_SPLITTER = -1; |
|
||||
constexpr s32 NO_MIX = 0x7fffffff; |
|
||||
constexpr s32 NO_FINAL_MIX = std::numeric_limits<s32>::min(); |
|
||||
constexpr s32 FINAL_MIX = 0; |
|
||||
constexpr s32 NO_EFFECT_ORDER = -1; |
|
||||
constexpr std::size_t TEMP_MIX_BASE_SIZE = 0x3f00; // TODO(ogniK): Work out this constant |
|
||||
// Any size checks seem to take the sample history into account |
|
||||
// and our const ends up being 0x3f04, the 4 bytes are most |
|
||||
// likely the sample history |
|
||||
constexpr std::size_t TOTAL_TEMP_MIX_SIZE = TEMP_MIX_BASE_SIZE + AudioCommon::MAX_SAMPLE_HISTORY; |
|
||||
constexpr f32 I3DL2REVERB_MAX_LEVEL = 5000.0f; |
|
||||
constexpr f32 I3DL2REVERB_MIN_REFLECTION_DURATION = 0.02f; |
|
||||
constexpr std::size_t I3DL2REVERB_TAPS = 20; |
|
||||
constexpr std::size_t I3DL2REVERB_DELAY_LINE_COUNT = 4; |
|
||||
using Fractional = s32; |
|
||||
|
|
||||
template <typename T> |
|
||||
constexpr Fractional ToFractional(T x) { |
|
||||
return static_cast<Fractional>(x * static_cast<T>(0x4000)); |
|
||||
} |
|
||||
|
|
||||
constexpr Fractional MultiplyFractional(Fractional lhs, Fractional rhs) { |
|
||||
return static_cast<Fractional>(static_cast<s64>(lhs) * rhs >> 14); |
|
||||
} |
|
||||
|
|
||||
constexpr s32 FractionalToFixed(Fractional x) { |
|
||||
const auto s = x & (1 << 13); |
|
||||
return static_cast<s32>(x >> 14) + s; |
|
||||
} |
|
||||
|
|
||||
constexpr s32 CalculateDelaySamples(s32 sample_rate_khz, float time) { |
|
||||
return FractionalToFixed(MultiplyFractional(ToFractional(sample_rate_khz), ToFractional(time))); |
|
||||
} |
|
||||
|
|
||||
static constexpr u32 VersionFromRevision(u32_le rev) { |
|
||||
// "REV7" -> 7 |
|
||||
return ((rev >> 24) & 0xff) - 0x30; |
|
||||
} |
|
||||
|
|
||||
static constexpr bool IsRevisionSupported(u32 required, u32_le user_revision) { |
|
||||
const auto base = VersionFromRevision(user_revision); |
|
||||
return required <= base; |
|
||||
} |
|
||||
|
|
||||
static constexpr bool IsValidRevision(u32_le revision) { |
|
||||
const auto base = VersionFromRevision(revision); |
|
||||
constexpr auto max_rev = VersionFromRevision(CURRENT_PROCESS_REVISION); |
|
||||
return base <= max_rev; |
|
||||
} |
|
||||
|
|
||||
static constexpr bool CanConsumeBuffer(std::size_t size, std::size_t offset, std::size_t required) { |
|
||||
if (offset > size) { |
|
||||
return false; |
|
||||
} |
|
||||
if (size < required) { |
|
||||
return false; |
|
||||
} |
|
||||
if ((size - offset) < required) { |
|
||||
return false; |
|
||||
} |
|
||||
return true; |
|
||||
} |
|
||||
|
|
||||
struct UpdateDataSizes { |
|
||||
u32_le behavior{}; |
|
||||
u32_le memory_pool{}; |
|
||||
u32_le voice{}; |
|
||||
u32_le voice_channel_resource{}; |
|
||||
u32_le effect{}; |
|
||||
u32_le mixer{}; |
|
||||
u32_le sink{}; |
|
||||
u32_le performance{}; |
|
||||
u32_le splitter{}; |
|
||||
u32_le render_info{}; |
|
||||
INSERT_PADDING_WORDS(4); |
|
||||
}; |
|
||||
static_assert(sizeof(UpdateDataSizes) == 0x38, "UpdateDataSizes is an invalid size"); |
|
||||
|
|
||||
struct UpdateDataHeader { |
|
||||
u32_le revision{}; |
|
||||
UpdateDataSizes size{}; |
|
||||
u32_le total_size{}; |
|
||||
}; |
|
||||
static_assert(sizeof(UpdateDataHeader) == 0x40, "UpdateDataHeader is an invalid size"); |
|
||||
|
|
||||
struct AudioRendererParameter { |
|
||||
u32_le sample_rate; |
|
||||
u32_le sample_count; |
|
||||
u32_le mix_buffer_count; |
|
||||
u32_le submix_count; |
|
||||
u32_le voice_count; |
|
||||
u32_le sink_count; |
|
||||
u32_le effect_count; |
|
||||
u32_le performance_frame_count; |
|
||||
u8 is_voice_drop_enabled; |
|
||||
u8 unknown_21; |
|
||||
u8 unknown_22; |
|
||||
u8 execution_mode; |
|
||||
u32_le splitter_count; |
|
||||
u32_le num_splitter_send_channels; |
|
||||
u32_le unknown_30; |
|
||||
u32_le revision; |
|
||||
}; |
|
||||
static_assert(sizeof(AudioRendererParameter) == 52, "AudioRendererParameter is an invalid size"); |
|
||||
|
|
||||
} // namespace AudioCommon |
|
||||
@ -0,0 +1,60 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project |
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <span> |
||||
|
|
||||
|
#include "audio_core/renderer/behavior/behavior_info.h" |
||||
|
#include "audio_core/renderer/memory/memory_pool_info.h" |
||||
|
#include "audio_core/renderer/upsampler/upsampler_manager.h" |
||||
|
#include "common/common_types.h" |
||||
|
|
||||
|
namespace AudioCore { |
||||
|
/** |
||||
|
* Execution mode of the audio renderer. |
||||
|
* Only Auto is currently supported. |
||||
|
*/ |
||||
|
enum class ExecutionMode : u8 { |
||||
|
Auto, |
||||
|
Manual, |
||||
|
}; |
||||
|
|
||||
|
/** |
||||
|
* Parameters from the game, passed to the audio renderer for initialisation. |
||||
|
*/ |
||||
|
struct AudioRendererParameterInternal { |
||||
|
/* 0x00 */ u32 sample_rate; |
||||
|
/* 0x04 */ u32 sample_count; |
||||
|
/* 0x08 */ u32 mixes; |
||||
|
/* 0x0C */ u32 sub_mixes; |
||||
|
/* 0x10 */ u32 voices; |
||||
|
/* 0x14 */ u32 sinks; |
||||
|
/* 0x18 */ u32 effects; |
||||
|
/* 0x1C */ u32 perf_frames; |
||||
|
/* 0x20 */ u16 voice_drop_enabled; |
||||
|
/* 0x22 */ u8 rendering_device; |
||||
|
/* 0x23 */ ExecutionMode execution_mode; |
||||
|
/* 0x24 */ u32 splitter_infos; |
||||
|
/* 0x28 */ s32 splitter_destinations; |
||||
|
/* 0x2C */ u32 external_context_size; |
||||
|
/* 0x30 */ u32 revision; |
||||
|
/* 0x34 */ char unk34[0x4]; |
||||
|
}; |
||||
|
static_assert(sizeof(AudioRendererParameterInternal) == 0x38, |
||||
|
"AudioRendererParameterInternal has the wrong size!"); |
||||
|
|
||||
|
/** |
||||
|
* Context for rendering, contains a bunch of useful fields for the command generator. |
||||
|
*/ |
||||
|
struct AudioRendererSystemContext { |
||||
|
s32 session_id; |
||||
|
s8 channels; |
||||
|
s16 mix_buffer_count; |
||||
|
AudioRenderer::BehaviorInfo* behavior; |
||||
|
std::span<s32> depop_buffer; |
||||
|
AudioRenderer::UpsamplerManager* upsampler_manager; |
||||
|
AudioRenderer::MemoryPoolInfo* memory_pool_info; |
||||
|
}; |
||||
|
|
||||
|
} // namespace AudioCore |
||||
@ -0,0 +1,138 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project |
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <numeric> |
||||
|
#include <span> |
||||
|
|
||||
|
#include "common/assert.h" |
||||
|
#include "common/common_funcs.h" |
||||
|
#include "common/common_types.h" |
||||
|
|
||||
|
namespace AudioCore { |
||||
|
using CpuAddr = std::uintptr_t; |
||||
|
|
||||
|
enum class PlayState : u8 { |
||||
|
Started, |
||||
|
Stopped, |
||||
|
Paused, |
||||
|
}; |
||||
|
|
||||
|
enum class SrcQuality : u8 { |
||||
|
Medium, |
||||
|
High, |
||||
|
Low, |
||||
|
}; |
||||
|
|
||||
|
enum class SampleFormat : u8 { |
||||
|
Invalid, |
||||
|
PcmInt8, |
||||
|
PcmInt16, |
||||
|
PcmInt24, |
||||
|
PcmInt32, |
||||
|
PcmFloat, |
||||
|
Adpcm, |
||||
|
}; |
||||
|
|
||||
|
enum class SessionTypes { |
||||
|
AudioIn, |
||||
|
AudioOut, |
||||
|
FinalOutputRecorder, |
||||
|
}; |
||||
|
|
||||
|
enum class Channels : u32 { |
||||
|
FrontLeft, |
||||
|
FrontRight, |
||||
|
Center, |
||||
|
LFE, |
||||
|
BackLeft, |
||||
|
BackRight, |
||||
|
}; |
||||
|
|
||||
|
// These are used by Delay, Reverb and I3dl2Reverb prior to Revision 11. |
||||
|
enum class OldChannels : u32 { |
||||
|
FrontLeft, |
||||
|
FrontRight, |
||||
|
BackLeft, |
||||
|
BackRight, |
||||
|
Center, |
||||
|
LFE, |
||||
|
}; |
||||
|
|
||||
|
constexpr u32 BufferCount = 32; |
||||
|
|
||||
|
constexpr u32 MaxRendererSessions = 2; |
||||
|
constexpr u32 TargetSampleCount = 240; |
||||
|
constexpr u32 TargetSampleRate = 48'000; |
||||
|
constexpr u32 MaxChannels = 6; |
||||
|
constexpr u32 MaxMixBuffers = 24; |
||||
|
constexpr u32 MaxWaveBuffers = 4; |
||||
|
constexpr s32 LowestVoicePriority = 0xFF; |
||||
|
constexpr s32 HighestVoicePriority = 0; |
||||
|
constexpr u32 BufferAlignment = 0x40; |
||||
|
constexpr u32 WorkbufferAlignment = 0x1000; |
||||
|
constexpr s32 FinalMixId = 0; |
||||
|
constexpr s32 InvalidDistanceFromFinalMix = std::numeric_limits<s32>::min(); |
||||
|
constexpr s32 UnusedSplitterId = -1; |
||||
|
constexpr s32 UnusedMixId = std::numeric_limits<s32>::max(); |
||||
|
constexpr u32 InvalidNodeId = 0xF0000000; |
||||
|
constexpr s32 InvalidProcessOrder = -1; |
||||
|
constexpr u32 MaxBiquadFilters = 2; |
||||
|
constexpr u32 MaxEffects = 256; |
||||
|
|
||||
|
constexpr bool IsChannelCountValid(u16 channel_count) { |
||||
|
return channel_count <= 6 && |
||||
|
(channel_count == 1 || channel_count == 2 || channel_count == 4 || channel_count == 6); |
||||
|
} |
||||
|
|
||||
|
constexpr void UseOldChannelMapping(std::span<s16> inputs, std::span<s16> outputs) { |
||||
|
constexpr auto old_center{static_cast<u32>(OldChannels::Center)}; |
||||
|
constexpr auto new_center{static_cast<u32>(Channels::Center)}; |
||||
|
constexpr auto old_lfe{static_cast<u32>(OldChannels::LFE)}; |
||||
|
constexpr auto new_lfe{static_cast<u32>(Channels::LFE)}; |
||||
|
|
||||
|
auto center{inputs[old_center]}; |
||||
|
auto lfe{inputs[old_lfe]}; |
||||
|
inputs[old_center] = inputs[new_center]; |
||||
|
inputs[old_lfe] = inputs[new_lfe]; |
||||
|
inputs[new_center] = center; |
||||
|
inputs[new_lfe] = lfe; |
||||
|
|
||||
|
center = outputs[old_center]; |
||||
|
lfe = outputs[old_lfe]; |
||||
|
outputs[old_center] = outputs[new_center]; |
||||
|
outputs[old_lfe] = outputs[new_lfe]; |
||||
|
outputs[new_center] = center; |
||||
|
outputs[new_lfe] = lfe; |
||||
|
} |
||||
|
|
||||
|
constexpr u32 GetSplitterInParamHeaderMagic() { |
||||
|
return Common::MakeMagic('S', 'N', 'D', 'H'); |
||||
|
} |
||||
|
|
||||
|
constexpr u32 GetSplitterInfoMagic() { |
||||
|
return Common::MakeMagic('S', 'N', 'D', 'I'); |
||||
|
} |
||||
|
|
||||
|
constexpr u32 GetSplitterSendDataMagic() { |
||||
|
return Common::MakeMagic('S', 'N', 'D', 'D'); |
||||
|
} |
||||
|
|
||||
|
constexpr size_t GetSampleFormatByteSize(SampleFormat format) { |
||||
|
switch (format) { |
||||
|
case SampleFormat::PcmInt8: |
||||
|
return 1; |
||||
|
case SampleFormat::PcmInt16: |
||||
|
return 2; |
||||
|
case SampleFormat::PcmInt24: |
||||
|
return 3; |
||||
|
case SampleFormat::PcmInt32: |
||||
|
case SampleFormat::PcmFloat: |
||||
|
return 4; |
||||
|
default: |
||||
|
return 2; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} // namespace AudioCore |
||||
@ -0,0 +1,105 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project |
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <map> |
||||
|
#include <ranges> |
||||
|
#include <tuple> |
||||
|
|
||||
|
#include "common/assert.h" |
||||
|
#include "common/common_funcs.h" |
||||
|
#include "common/common_types.h" |
||||
|
|
||||
|
namespace AudioCore { |
||||
|
constexpr u32 CurrentRevision = 11; |
||||
|
|
||||
|
enum class SupportTags { |
||||
|
CommandProcessingTimeEstimatorVersion4, |
||||
|
CommandProcessingTimeEstimatorVersion3, |
||||
|
CommandProcessingTimeEstimatorVersion2, |
||||
|
MultiTapBiquadFilterProcessing, |
||||
|
EffectInfoVer2, |
||||
|
WaveBufferVer2, |
||||
|
BiquadFilterFloatProcessing, |
||||
|
VolumeMixParameterPrecisionQ23, |
||||
|
MixInParameterDirtyOnlyUpdate, |
||||
|
BiquadFilterEffectStateClearBugFix, |
||||
|
VoicePlayedSampleCountResetAtLoopPoint, |
||||
|
VoicePitchAndSrcSkipped, |
||||
|
SplitterBugFix, |
||||
|
FlushVoiceWaveBuffers, |
||||
|
ElapsedFrameCount, |
||||
|
AudioRendererVariadicCommandBufferSize, |
||||
|
PerformanceMetricsDataFormatVersion2, |
||||
|
AudioRendererProcessingTimeLimit80Percent, |
||||
|
AudioRendererProcessingTimeLimit75Percent, |
||||
|
AudioRendererProcessingTimeLimit70Percent, |
||||
|
AdpcmLoopContextBugFix, |
||||
|
Splitter, |
||||
|
LongSizePreDelay, |
||||
|
AudioUsbDeviceOutput, |
||||
|
DeviceApiVersion2, |
||||
|
DelayChannelMappingChange, |
||||
|
ReverbChannelMappingChange, |
||||
|
I3dl2ReverbChannelMappingChange, |
||||
|
|
||||
|
// Not a real tag, just here to get the count. |
||||
|
Size |
||||
|
}; |
||||
|
|
||||
|
constexpr u32 GetRevisionNum(u32 user_revision) { |
||||
|
if (user_revision >= 0x100) { |
||||
|
user_revision -= Common::MakeMagic('R', 'E', 'V', '0'); |
||||
|
user_revision >>= 24; |
||||
|
} |
||||
|
return user_revision; |
||||
|
}; |
||||
|
|
||||
|
constexpr bool CheckFeatureSupported(SupportTags tag, u32 user_revision) { |
||||
|
constexpr std::array<std::pair<SupportTags, u32>, static_cast<u32>(SupportTags::Size)> features{ |
||||
|
{ |
||||
|
{SupportTags::AudioRendererProcessingTimeLimit70Percent, 1}, |
||||
|
{SupportTags::Splitter, 2}, |
||||
|
{SupportTags::AdpcmLoopContextBugFix, 2}, |
||||
|
{SupportTags::LongSizePreDelay, 3}, |
||||
|
{SupportTags::AudioUsbDeviceOutput, 4}, |
||||
|
{SupportTags::AudioRendererProcessingTimeLimit75Percent, 4}, |
||||
|
{SupportTags::VoicePlayedSampleCountResetAtLoopPoint, 5}, |
||||
|
{SupportTags::VoicePitchAndSrcSkipped, 5}, |
||||
|
{SupportTags::SplitterBugFix, 5}, |
||||
|
{SupportTags::FlushVoiceWaveBuffers, 5}, |
||||
|
{SupportTags::ElapsedFrameCount, 5}, |
||||
|
{SupportTags::AudioRendererProcessingTimeLimit80Percent, 5}, |
||||
|
{SupportTags::AudioRendererVariadicCommandBufferSize, 5}, |
||||
|
{SupportTags::PerformanceMetricsDataFormatVersion2, 5}, |
||||
|
{SupportTags::CommandProcessingTimeEstimatorVersion2, 5}, |
||||
|
{SupportTags::BiquadFilterEffectStateClearBugFix, 6}, |
||||
|
{SupportTags::BiquadFilterFloatProcessing, 7}, |
||||
|
{SupportTags::VolumeMixParameterPrecisionQ23, 7}, |
||||
|
{SupportTags::MixInParameterDirtyOnlyUpdate, 7}, |
||||
|
{SupportTags::WaveBufferVer2, 8}, |
||||
|
{SupportTags::CommandProcessingTimeEstimatorVersion3, 8}, |
||||
|
{SupportTags::EffectInfoVer2, 9}, |
||||
|
{SupportTags::CommandProcessingTimeEstimatorVersion4, 10}, |
||||
|
{SupportTags::MultiTapBiquadFilterProcessing, 10}, |
||||
|
{SupportTags::DelayChannelMappingChange, 11}, |
||||
|
{SupportTags::ReverbChannelMappingChange, 11}, |
||||
|
{SupportTags::I3dl2ReverbChannelMappingChange, 11}, |
||||
|
}}; |
||||
|
|
||||
|
const auto& feature = |
||||
|
std::ranges::find_if(features, [tag](const auto& entry) { return entry.first == tag; }); |
||||
|
if (feature == features.cend()) { |
||||
|
LOG_ERROR(Service_Audio, "Invalid SupportTag {}!", static_cast<u32>(tag)); |
||||
|
return false; |
||||
|
} |
||||
|
user_revision = GetRevisionNum(user_revision); |
||||
|
return (*feature).second <= user_revision; |
||||
|
} |
||||
|
|
||||
|
constexpr bool CheckValidRevision(u32 user_revision) { |
||||
|
return GetRevisionNum(user_revision) <= CurrentRevision; |
||||
|
}; |
||||
|
|
||||
|
} // namespace AudioCore |
||||
@ -0,0 +1,35 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project |
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include "common/common_types.h" |
||||
|
|
||||
|
namespace AudioCore { |
||||
|
|
||||
|
struct WaveBufferVersion1 { |
||||
|
CpuAddr buffer; |
||||
|
u64 buffer_size; |
||||
|
u32 start_offset; |
||||
|
u32 end_offset; |
||||
|
bool loop; |
||||
|
bool stream_ended; |
||||
|
CpuAddr context; |
||||
|
u64 context_size; |
||||
|
}; |
||||
|
|
||||
|
struct WaveBufferVersion2 { |
||||
|
CpuAddr buffer; |
||||
|
CpuAddr context; |
||||
|
u64 buffer_size; |
||||
|
u64 context_size; |
||||
|
u32 start_offset; |
||||
|
u32 end_offset; |
||||
|
u32 loop_start_offset; |
||||
|
u32 loop_end_offset; |
||||
|
s32 loop_count; |
||||
|
bool loop; |
||||
|
bool stream_ended; |
||||
|
}; |
||||
|
|
||||
|
} // namespace AudioCore |
||||
@ -0,0 +1,100 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project |
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <span> |
||||
|
|
||||
|
#include "common/alignment.h" |
||||
|
#include "common/assert.h" |
||||
|
#include "common/common_types.h" |
||||
|
|
||||
|
namespace AudioCore { |
||||
|
/** |
||||
|
* Responsible for allocating up a workbuffer into multiple pieces. |
||||
|
* Takes in a buffer and size (it does not own them), and allocates up the buffer via Allocate. |
||||
|
*/ |
||||
|
class WorkbufferAllocator { |
||||
|
public: |
||||
|
explicit WorkbufferAllocator(std::span<u8> buffer_, u64 size_) |
||||
|
: buffer{reinterpret_cast<u64>(buffer_.data())}, size{size_} {} |
||||
|
|
||||
|
/** |
||||
|
* Allocate the given count of T elements, aligned to alignment. |
||||
|
* |
||||
|
* @param count - The number of elements to allocate. |
||||
|
* @param alignment - The required starting alignment. |
||||
|
* @return Non-owning container of allocated elements. |
||||
|
*/ |
||||
|
template <typename T> |
||||
|
std::span<T> Allocate(u64 count, u64 alignment) { |
||||
|
u64 out{0}; |
||||
|
u64 byte_size{count * sizeof(T)}; |
||||
|
|
||||
|
if (byte_size > 0) { |
||||
|
auto current{buffer + offset}; |
||||
|
auto aligned_buffer{Common::AlignUp(current, alignment)}; |
||||
|
if (aligned_buffer + byte_size <= buffer + size) { |
||||
|
out = aligned_buffer; |
||||
|
offset = byte_size - buffer + aligned_buffer; |
||||
|
} else { |
||||
|
LOG_ERROR( |
||||
|
Service_Audio, |
||||
|
"Allocated buffer was too small to hold new alloc.\nAllocator size={:08X}, " |
||||
|
"offset={:08X}.\nAttempting to allocate {:08X} with alignment={:02X}", |
||||
|
size, offset, byte_size, alignment); |
||||
|
count = 0; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return std::span<T>(reinterpret_cast<T*>(out), count); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Align the current offset to the given alignment. |
||||
|
* |
||||
|
* @param alignment - The required starting alignment. |
||||
|
*/ |
||||
|
void Align(u64 alignment) { |
||||
|
auto current{buffer + offset}; |
||||
|
auto aligned_buffer{Common::AlignUp(current, alignment)}; |
||||
|
offset = 0 - buffer + aligned_buffer; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Get the current buffer offset. |
||||
|
* |
||||
|
* @return The current allocating offset. |
||||
|
*/ |
||||
|
u64 GetCurrentOffset() const { |
||||
|
return offset; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Get the current buffer size. |
||||
|
* |
||||
|
* @return The size of the current buffer. |
||||
|
*/ |
||||
|
u64 GetSize() const { |
||||
|
return size; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Get the remaining size that can be allocated. |
||||
|
* |
||||
|
* @return The remaining size left in the buffer. |
||||
|
*/ |
||||
|
u64 GetRemainingSize() const { |
||||
|
return size - offset; |
||||
|
} |
||||
|
|
||||
|
private: |
||||
|
/// The buffer into which we are allocating. |
||||
|
u64 buffer; |
||||
|
/// Size of the buffer we're allocating to. |
||||
|
u64 size; |
||||
|
/// Current offset into the buffer, an error will be thrown if it exceeds size. |
||||
|
u64 offset{}; |
||||
|
}; |
||||
|
|
||||
|
} // namespace AudioCore |
||||
@ -1,249 +0,0 @@ |
|||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||
|
|
||||
#include <algorithm>
|
|
||||
#include <atomic>
|
|
||||
#include <cstring>
|
|
||||
#include "audio_core/cubeb_sink.h"
|
|
||||
#include "audio_core/stream.h"
|
|
||||
#include "common/assert.h"
|
|
||||
#include "common/logging/log.h"
|
|
||||
#include "common/ring_buffer.h"
|
|
||||
#include "common/settings.h"
|
|
||||
|
|
||||
#ifdef _WIN32
|
|
||||
#include <objbase.h>
|
|
||||
#endif
|
|
||||
|
|
||||
namespace AudioCore { |
|
||||
|
|
||||
class CubebSinkStream final : public SinkStream { |
|
||||
public: |
|
||||
CubebSinkStream(cubeb* ctx_, u32 sample_rate, u32 num_channels_, cubeb_devid output_device, |
|
||||
const std::string& name) |
|
||||
: ctx{ctx_}, num_channels{std::min(num_channels_, 6u)} { |
|
||||
|
|
||||
cubeb_stream_params params{}; |
|
||||
params.rate = sample_rate; |
|
||||
params.channels = num_channels; |
|
||||
params.format = CUBEB_SAMPLE_S16NE; |
|
||||
params.prefs = CUBEB_STREAM_PREF_PERSIST; |
|
||||
switch (num_channels) { |
|
||||
case 1: |
|
||||
params.layout = CUBEB_LAYOUT_MONO; |
|
||||
break; |
|
||||
case 2: |
|
||||
params.layout = CUBEB_LAYOUT_STEREO; |
|
||||
break; |
|
||||
case 6: |
|
||||
params.layout = CUBEB_LAYOUT_3F2_LFE; |
|
||||
break; |
|
||||
} |
|
||||
|
|
||||
u32 minimum_latency{}; |
|
||||
if (cubeb_get_min_latency(ctx, ¶ms, &minimum_latency) != CUBEB_OK) { |
|
||||
LOG_CRITICAL(Audio_Sink, "Error getting minimum latency"); |
|
||||
} |
|
||||
|
|
||||
if (cubeb_stream_init(ctx, &stream_backend, name.c_str(), nullptr, nullptr, output_device, |
|
||||
¶ms, std::max(512u, minimum_latency), |
|
||||
&CubebSinkStream::DataCallback, &CubebSinkStream::StateCallback, |
|
||||
this) != CUBEB_OK) { |
|
||||
LOG_CRITICAL(Audio_Sink, "Error initializing cubeb stream"); |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
if (cubeb_stream_start(stream_backend) != CUBEB_OK) { |
|
||||
LOG_CRITICAL(Audio_Sink, "Error starting cubeb stream"); |
|
||||
return; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
~CubebSinkStream() override { |
|
||||
if (!ctx) { |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
if (cubeb_stream_stop(stream_backend) != CUBEB_OK) { |
|
||||
LOG_CRITICAL(Audio_Sink, "Error stopping cubeb stream"); |
|
||||
} |
|
||||
|
|
||||
cubeb_stream_destroy(stream_backend); |
|
||||
} |
|
||||
|
|
||||
void EnqueueSamples(u32 source_num_channels, const std::vector<s16>& samples) override { |
|
||||
if (source_num_channels > num_channels) { |
|
||||
// Downsample 6 channels to 2
|
|
||||
ASSERT_MSG(source_num_channels == 6, "Channel count must be 6"); |
|
||||
|
|
||||
std::vector<s16> buf; |
|
||||
buf.reserve(samples.size() * num_channels / source_num_channels); |
|
||||
for (std::size_t i = 0; i < samples.size(); i += source_num_channels) { |
|
||||
// Downmixing implementation taken from the ATSC standard
|
|
||||
const s16 left{samples[i + 0]}; |
|
||||
const s16 right{samples[i + 1]}; |
|
||||
const s16 center{samples[i + 2]}; |
|
||||
const s16 surround_left{samples[i + 4]}; |
|
||||
const s16 surround_right{samples[i + 5]}; |
|
||||
// Not used in the ATSC reference implementation
|
|
||||
[[maybe_unused]] const s16 low_frequency_effects{samples[i + 3]}; |
|
||||
|
|
||||
constexpr s32 clev{707}; // center mixing level coefficient
|
|
||||
constexpr s32 slev{707}; // surround mixing level coefficient
|
|
||||
|
|
||||
buf.push_back(static_cast<s16>(left + (clev * center / 1000) + |
|
||||
(slev * surround_left / 1000))); |
|
||||
buf.push_back(static_cast<s16>(right + (clev * center / 1000) + |
|
||||
(slev * surround_right / 1000))); |
|
||||
} |
|
||||
queue.Push(buf); |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
queue.Push(samples); |
|
||||
} |
|
||||
|
|
||||
std::size_t SamplesInQueue(u32 channel_count) const override { |
|
||||
if (!ctx) |
|
||||
return 0; |
|
||||
|
|
||||
return queue.Size() / channel_count; |
|
||||
} |
|
||||
|
|
||||
void Flush() override { |
|
||||
should_flush = true; |
|
||||
} |
|
||||
|
|
||||
u32 GetNumChannels() const { |
|
||||
return num_channels; |
|
||||
} |
|
||||
|
|
||||
private: |
|
||||
std::vector<std::string> device_list; |
|
||||
|
|
||||
cubeb* ctx{}; |
|
||||
cubeb_stream* stream_backend{}; |
|
||||
u32 num_channels{}; |
|
||||
|
|
||||
Common::RingBuffer<s16, 0x10000> queue; |
|
||||
std::array<s16, 2> last_frame{}; |
|
||||
std::atomic<bool> should_flush{}; |
|
||||
|
|
||||
static long DataCallback(cubeb_stream* stream, void* user_data, const void* input_buffer, |
|
||||
void* output_buffer, long num_frames); |
|
||||
static void StateCallback(cubeb_stream* stream, void* user_data, cubeb_state state); |
|
||||
}; |
|
||||
|
|
||||
CubebSink::CubebSink(std::string_view target_device_name) { |
|
||||
// Cubeb requires COM to be initialized on the thread calling cubeb_init on Windows
|
|
||||
#ifdef _WIN32
|
|
||||
com_init_result = CoInitializeEx(nullptr, COINIT_MULTITHREADED); |
|
||||
#endif
|
|
||||
|
|
||||
if (cubeb_init(&ctx, "yuzu", nullptr) != CUBEB_OK) { |
|
||||
LOG_CRITICAL(Audio_Sink, "cubeb_init failed"); |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
if (target_device_name != auto_device_name && !target_device_name.empty()) { |
|
||||
cubeb_device_collection collection; |
|
||||
if (cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_OUTPUT, &collection) != CUBEB_OK) { |
|
||||
LOG_WARNING(Audio_Sink, "Audio output device enumeration not supported"); |
|
||||
} else { |
|
||||
const auto collection_end{collection.device + collection.count}; |
|
||||
const auto device{ |
|
||||
std::find_if(collection.device, collection_end, [&](const cubeb_device_info& info) { |
|
||||
return info.friendly_name != nullptr && |
|
||||
target_device_name == info.friendly_name; |
|
||||
})}; |
|
||||
if (device != collection_end) { |
|
||||
output_device = device->devid; |
|
||||
} |
|
||||
cubeb_device_collection_destroy(ctx, &collection); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
CubebSink::~CubebSink() { |
|
||||
if (!ctx) { |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
for (auto& sink_stream : sink_streams) { |
|
||||
sink_stream.reset(); |
|
||||
} |
|
||||
|
|
||||
cubeb_destroy(ctx); |
|
||||
|
|
||||
#ifdef _WIN32
|
|
||||
if (SUCCEEDED(com_init_result)) { |
|
||||
CoUninitialize(); |
|
||||
} |
|
||||
#endif
|
|
||||
} |
|
||||
|
|
||||
SinkStream& CubebSink::AcquireSinkStream(u32 sample_rate, u32 num_channels, |
|
||||
const std::string& name) { |
|
||||
sink_streams.push_back( |
|
||||
std::make_unique<CubebSinkStream>(ctx, sample_rate, num_channels, output_device, name)); |
|
||||
return *sink_streams.back(); |
|
||||
} |
|
||||
|
|
||||
long CubebSinkStream::DataCallback([[maybe_unused]] cubeb_stream* stream, void* user_data, |
|
||||
[[maybe_unused]] const void* input_buffer, void* output_buffer, |
|
||||
long num_frames) { |
|
||||
auto* impl = static_cast<CubebSinkStream*>(user_data); |
|
||||
auto* buffer = static_cast<u8*>(output_buffer); |
|
||||
|
|
||||
if (!impl) { |
|
||||
return {}; |
|
||||
} |
|
||||
|
|
||||
const std::size_t num_channels = impl->GetNumChannels(); |
|
||||
const std::size_t samples_to_write = num_channels * num_frames; |
|
||||
const std::size_t samples_written = impl->queue.Pop(buffer, samples_to_write); |
|
||||
|
|
||||
if (samples_written >= num_channels) { |
|
||||
std::memcpy(&impl->last_frame[0], buffer + (samples_written - num_channels) * sizeof(s16), |
|
||||
num_channels * sizeof(s16)); |
|
||||
} |
|
||||
|
|
||||
// Fill the rest of the frames with last_frame
|
|
||||
for (std::size_t i = samples_written; i < samples_to_write; i += num_channels) { |
|
||||
std::memcpy(buffer + i * sizeof(s16), &impl->last_frame[0], num_channels * sizeof(s16)); |
|
||||
} |
|
||||
|
|
||||
return num_frames; |
|
||||
} |
|
||||
|
|
||||
void CubebSinkStream::StateCallback([[maybe_unused]] cubeb_stream* stream, |
|
||||
[[maybe_unused]] void* user_data, |
|
||||
[[maybe_unused]] cubeb_state state) {} |
|
||||
|
|
||||
std::vector<std::string> ListCubebSinkDevices() { |
|
||||
std::vector<std::string> device_list; |
|
||||
cubeb* ctx; |
|
||||
|
|
||||
if (cubeb_init(&ctx, "yuzu Device Enumerator", nullptr) != CUBEB_OK) { |
|
||||
LOG_CRITICAL(Audio_Sink, "cubeb_init failed"); |
|
||||
return {}; |
|
||||
} |
|
||||
|
|
||||
cubeb_device_collection collection; |
|
||||
if (cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_OUTPUT, &collection) != CUBEB_OK) { |
|
||||
LOG_WARNING(Audio_Sink, "Audio output device enumeration not supported"); |
|
||||
} else { |
|
||||
for (std::size_t i = 0; i < collection.count; i++) { |
|
||||
const cubeb_device_info& device = collection.device[i]; |
|
||||
if (device.friendly_name) { |
|
||||
device_list.emplace_back(device.friendly_name); |
|
||||
} |
|
||||
} |
|
||||
cubeb_device_collection_destroy(ctx, &collection); |
|
||||
} |
|
||||
|
|
||||
cubeb_destroy(ctx); |
|
||||
return device_list; |
|
||||
} |
|
||||
|
|
||||
} // namespace AudioCore
|
|
||||
@ -1,35 +0,0 @@ |
|||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project |
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later |
|
||||
|
|
||||
#pragma once |
|
||||
|
|
||||
#include <string> |
|
||||
#include <vector> |
|
||||
|
|
||||
#include <cubeb/cubeb.h> |
|
||||
|
|
||||
#include "audio_core/sink.h" |
|
||||
|
|
||||
namespace AudioCore { |
|
||||
|
|
||||
class CubebSink final : public Sink { |
|
||||
public: |
|
||||
explicit CubebSink(std::string_view device_id); |
|
||||
~CubebSink() override; |
|
||||
|
|
||||
SinkStream& AcquireSinkStream(u32 sample_rate, u32 num_channels, |
|
||||
const std::string& name) override; |
|
||||
|
|
||||
private: |
|
||||
cubeb* ctx{}; |
|
||||
cubeb_devid output_device{}; |
|
||||
std::vector<SinkStreamPtr> sink_streams; |
|
||||
|
|
||||
#ifdef _WIN32 |
|
||||
u32 com_init_result = 0; |
|
||||
#endif |
|
||||
}; |
|
||||
|
|
||||
std::vector<std::string> ListCubebSinkDevices(); |
|
||||
|
|
||||
} // namespace AudioCore |
|
||||
@ -1,107 +0,0 @@ |
|||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||
|
|
||||
#include <cstring>
|
|
||||
#include "audio_core/delay_line.h"
|
|
||||
|
|
||||
namespace AudioCore { |
|
||||
DelayLineBase::DelayLineBase() = default; |
|
||||
DelayLineBase::~DelayLineBase() = default; |
|
||||
|
|
||||
void DelayLineBase::Initialize(s32 max_delay_, float* src_buffer) { |
|
||||
buffer = src_buffer; |
|
||||
buffer_end = buffer + max_delay_; |
|
||||
max_delay = max_delay_; |
|
||||
output = buffer; |
|
||||
SetDelay(max_delay_); |
|
||||
Clear(); |
|
||||
} |
|
||||
|
|
||||
void DelayLineBase::SetDelay(s32 new_delay) { |
|
||||
if (max_delay < new_delay) { |
|
||||
return; |
|
||||
} |
|
||||
delay = new_delay; |
|
||||
input = (buffer + ((output - buffer) + new_delay) % (max_delay + 1)); |
|
||||
} |
|
||||
|
|
||||
s32 DelayLineBase::GetDelay() const { |
|
||||
return delay; |
|
||||
} |
|
||||
|
|
||||
s32 DelayLineBase::GetMaxDelay() const { |
|
||||
return max_delay; |
|
||||
} |
|
||||
|
|
||||
f32 DelayLineBase::TapOut(s32 last_sample) { |
|
||||
const float* ptr = input - (last_sample + 1); |
|
||||
if (ptr < buffer) { |
|
||||
ptr += (max_delay + 1); |
|
||||
} |
|
||||
|
|
||||
return *ptr; |
|
||||
} |
|
||||
|
|
||||
f32 DelayLineBase::Tick(f32 sample) { |
|
||||
*(input++) = sample; |
|
||||
const auto out_sample = *(output++); |
|
||||
|
|
||||
if (buffer_end < input) { |
|
||||
input = buffer; |
|
||||
} |
|
||||
|
|
||||
if (buffer_end < output) { |
|
||||
output = buffer; |
|
||||
} |
|
||||
|
|
||||
return out_sample; |
|
||||
} |
|
||||
|
|
||||
float* DelayLineBase::GetInput() { |
|
||||
return input; |
|
||||
} |
|
||||
|
|
||||
const float* DelayLineBase::GetInput() const { |
|
||||
return input; |
|
||||
} |
|
||||
|
|
||||
f32 DelayLineBase::GetOutputSample() const { |
|
||||
return *output; |
|
||||
} |
|
||||
|
|
||||
void DelayLineBase::Clear() { |
|
||||
std::memset(buffer, 0, sizeof(float) * max_delay); |
|
||||
} |
|
||||
|
|
||||
void DelayLineBase::Reset() { |
|
||||
buffer = nullptr; |
|
||||
buffer_end = nullptr; |
|
||||
max_delay = 0; |
|
||||
input = nullptr; |
|
||||
output = nullptr; |
|
||||
delay = 0; |
|
||||
} |
|
||||
|
|
||||
DelayLineAllPass::DelayLineAllPass() = default; |
|
||||
DelayLineAllPass::~DelayLineAllPass() = default; |
|
||||
|
|
||||
void DelayLineAllPass::Initialize(u32 delay_, float coeffcient_, f32* src_buffer) { |
|
||||
DelayLineBase::Initialize(delay_, src_buffer); |
|
||||
SetCoefficient(coeffcient_); |
|
||||
} |
|
||||
|
|
||||
void DelayLineAllPass::SetCoefficient(float coeffcient_) { |
|
||||
coefficient = coeffcient_; |
|
||||
} |
|
||||
|
|
||||
f32 DelayLineAllPass::Tick(f32 sample) { |
|
||||
const auto temp = sample - coefficient * *output; |
|
||||
return coefficient * temp + DelayLineBase::Tick(temp); |
|
||||
} |
|
||||
|
|
||||
void DelayLineAllPass::Reset() { |
|
||||
coefficient = 0.0f; |
|
||||
DelayLineBase::Reset(); |
|
||||
} |
|
||||
|
|
||||
} // namespace AudioCore
|
|
||||
@ -1,49 +0,0 @@ |
|||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project |
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later |
|
||||
|
|
||||
#pragma once |
|
||||
|
|
||||
#include "common/common_types.h" |
|
||||
|
|
||||
namespace AudioCore { |
|
||||
|
|
||||
class DelayLineBase { |
|
||||
public: |
|
||||
DelayLineBase(); |
|
||||
~DelayLineBase(); |
|
||||
|
|
||||
void Initialize(s32 max_delay_, float* src_buffer); |
|
||||
void SetDelay(s32 new_delay); |
|
||||
s32 GetDelay() const; |
|
||||
s32 GetMaxDelay() const; |
|
||||
f32 TapOut(s32 last_sample); |
|
||||
f32 Tick(f32 sample); |
|
||||
float* GetInput(); |
|
||||
const float* GetInput() const; |
|
||||
f32 GetOutputSample() const; |
|
||||
void Clear(); |
|
||||
void Reset(); |
|
||||
|
|
||||
protected: |
|
||||
float* buffer{nullptr}; |
|
||||
float* buffer_end{nullptr}; |
|
||||
s32 max_delay{}; |
|
||||
float* input{nullptr}; |
|
||||
float* output{nullptr}; |
|
||||
s32 delay{}; |
|
||||
}; |
|
||||
|
|
||||
class DelayLineAllPass final : public DelayLineBase { |
|
||||
public: |
|
||||
DelayLineAllPass(); |
|
||||
~DelayLineAllPass(); |
|
||||
|
|
||||
void Initialize(u32 delay, float coeffcient_, f32* src_buffer); |
|
||||
void SetCoefficient(float coeffcient_); |
|
||||
f32 Tick(f32 sample); |
|
||||
void Reset(); |
|
||||
|
|
||||
private: |
|
||||
float coefficient{}; |
|
||||
}; |
|
||||
} // namespace AudioCore |
|
||||
@ -0,0 +1,21 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project |
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include "common/common_types.h" |
||||
|
|
||||
|
namespace AudioCore { |
||||
|
|
||||
|
struct AudioBuffer { |
||||
|
/// Timestamp this buffer completed playing. |
||||
|
s64 played_timestamp; |
||||
|
/// Game memory address for these samples. |
||||
|
VAddr samples; |
||||
|
/// Unqiue identifier for this buffer. |
||||
|
u64 tag; |
||||
|
/// Size of the samples buffer. |
||||
|
u64 size; |
||||
|
}; |
||||
|
|
||||
|
} // namespace AudioCore |
||||
@ -0,0 +1,304 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project |
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <array> |
||||
|
#include <mutex> |
||||
|
#include <span> |
||||
|
#include <vector> |
||||
|
|
||||
|
#include "audio_buffer.h" |
||||
|
#include "audio_core/device/device_session.h" |
||||
|
#include "core/core_timing.h" |
||||
|
|
||||
|
namespace AudioCore { |
||||
|
|
||||
|
constexpr s32 BufferAppendLimit = 4; |
||||
|
|
||||
|
/** |
||||
|
* A ringbuffer of N audio buffers. |
||||
|
* The buffer contains 3 sections: |
||||
|
* Appended - Buffers added to the ring, but have yet to be sent to the audio backend. |
||||
|
* Registered - Buffers sent to the backend and queued for playback. |
||||
|
* Released - Buffers which have been played, and can now be recycled. |
||||
|
* Any others are free/untracked. |
||||
|
* |
||||
|
* @tparam N - Maximum number of buffers in the ring. |
||||
|
*/ |
||||
|
template <size_t N> |
||||
|
class AudioBuffers { |
||||
|
public: |
||||
|
explicit AudioBuffers(size_t limit) : append_limit{static_cast<u32>(limit)} {} |
||||
|
|
||||
|
/** |
||||
|
* Append a new audio buffer to the ring. |
||||
|
* |
||||
|
* @param buffer - The new buffer. |
||||
|
*/ |
||||
|
void AppendBuffer(AudioBuffer& buffer) { |
||||
|
std::scoped_lock l{lock}; |
||||
|
buffers[appended_index] = buffer; |
||||
|
appended_count++; |
||||
|
appended_index = (appended_index + 1) % append_limit; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Register waiting buffers, up to a maximum of BufferAppendLimit. |
||||
|
* |
||||
|
* @param out_buffers - The buffers which were registered. |
||||
|
*/ |
||||
|
void RegisterBuffers(std::vector<AudioBuffer>& out_buffers) { |
||||
|
std::scoped_lock l{lock}; |
||||
|
const s32 to_register{std::min(std::min(appended_count, BufferAppendLimit), |
||||
|
BufferAppendLimit - registered_count)}; |
||||
|
|
||||
|
for (s32 i = 0; i < to_register; i++) { |
||||
|
s32 index{appended_index - appended_count}; |
||||
|
if (index < 0) { |
||||
|
index += N; |
||||
|
} |
||||
|
out_buffers.push_back(buffers[index]); |
||||
|
registered_count++; |
||||
|
registered_index = (registered_index + 1) % append_limit; |
||||
|
|
||||
|
appended_count--; |
||||
|
if (appended_count == 0) { |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Release a single buffer. Must be already registered. |
||||
|
* |
||||
|
* @param index - The buffer index to release. |
||||
|
* @param timestamp - The released timestamp for this buffer. |
||||
|
*/ |
||||
|
void ReleaseBuffer(s32 index, s64 timestamp) { |
||||
|
std::scoped_lock l{lock}; |
||||
|
buffers[index].played_timestamp = timestamp; |
||||
|
|
||||
|
registered_count--; |
||||
|
released_count++; |
||||
|
released_index = (released_index + 1) % append_limit; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Release all registered buffers. |
||||
|
* |
||||
|
* @param timestamp - The released timestamp for this buffer. |
||||
|
* @return Is the buffer was released. |
||||
|
*/ |
||||
|
bool ReleaseBuffers(Core::Timing::CoreTiming& core_timing, DeviceSession& session) { |
||||
|
std::scoped_lock l{lock}; |
||||
|
bool buffer_released{false}; |
||||
|
while (registered_count > 0) { |
||||
|
auto index{registered_index - registered_count}; |
||||
|
if (index < 0) { |
||||
|
index += N; |
||||
|
} |
||||
|
|
||||
|
// Check with the backend if this buffer can be released yet. |
||||
|
if (!session.IsBufferConsumed(buffers[index].tag)) { |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
ReleaseBuffer(index, core_timing.GetGlobalTimeNs().count()); |
||||
|
buffer_released = true; |
||||
|
} |
||||
|
|
||||
|
return buffer_released || registered_count == 0; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Get all released buffers. |
||||
|
* |
||||
|
* @param tags - Container to be filled with the released buffers' tags. |
||||
|
* @return The number of buffers released. |
||||
|
*/ |
||||
|
u32 GetReleasedBuffers(std::span<u64> tags) { |
||||
|
std::scoped_lock l{lock}; |
||||
|
u32 released{0}; |
||||
|
|
||||
|
while (released_count > 0) { |
||||
|
auto index{released_index - released_count}; |
||||
|
if (index < 0) { |
||||
|
index += N; |
||||
|
} |
||||
|
|
||||
|
auto& buffer{buffers[index]}; |
||||
|
released_count--; |
||||
|
|
||||
|
auto tag{buffer.tag}; |
||||
|
buffer.played_timestamp = 0; |
||||
|
buffer.samples = 0; |
||||
|
buffer.tag = 0; |
||||
|
buffer.size = 0; |
||||
|
|
||||
|
if (tag == 0) { |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
tags[released++] = tag; |
||||
|
|
||||
|
if (released >= tags.size()) { |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return released; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Get all appended and registered buffers. |
||||
|
* |
||||
|
* @param buffers_flushed - Output vector for the buffers which are released. |
||||
|
* @param max_buffers - Maximum number of buffers to released. |
||||
|
* @return The number of buffers released. |
||||
|
*/ |
||||
|
u32 GetRegisteredAppendedBuffers(std::vector<AudioBuffer>& buffers_flushed, u32 max_buffers) { |
||||
|
std::scoped_lock l{lock}; |
||||
|
if (registered_count + appended_count == 0) { |
||||
|
return 0; |
||||
|
} |
||||
|
|
||||
|
size_t buffers_to_flush{ |
||||
|
std::min(static_cast<u32>(registered_count + appended_count), max_buffers)}; |
||||
|
if (buffers_to_flush == 0) { |
||||
|
return 0; |
||||
|
} |
||||
|
|
||||
|
while (registered_count > 0) { |
||||
|
auto index{registered_index - registered_count}; |
||||
|
if (index < 0) { |
||||
|
index += N; |
||||
|
} |
||||
|
|
||||
|
buffers_flushed.push_back(buffers[index]); |
||||
|
|
||||
|
registered_count--; |
||||
|
released_count++; |
||||
|
released_index = (released_index + 1) % append_limit; |
||||
|
|
||||
|
if (buffers_flushed.size() >= buffers_to_flush) { |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
while (appended_count > 0) { |
||||
|
auto index{appended_index - appended_count}; |
||||
|
if (index < 0) { |
||||
|
index += N; |
||||
|
} |
||||
|
|
||||
|
buffers_flushed.push_back(buffers[index]); |
||||
|
|
||||
|
appended_count--; |
||||
|
released_count++; |
||||
|
released_index = (released_index + 1) % append_limit; |
||||
|
|
||||
|
if (buffers_flushed.size() >= buffers_to_flush) { |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return static_cast<u32>(buffers_flushed.size()); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Check if the given tag is in the buffers. |
||||
|
* |
||||
|
* @param tag - Unique tag of the buffer to search for. |
||||
|
* @return True if the buffer is still in the ring, otherwise false. |
||||
|
*/ |
||||
|
bool ContainsBuffer(const u64 tag) const { |
||||
|
std::scoped_lock l{lock}; |
||||
|
const auto registered_buffers{appended_count + registered_count + released_count}; |
||||
|
|
||||
|
if (registered_buffers == 0) { |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
auto index{released_index - released_count}; |
||||
|
if (index < 0) { |
||||
|
index += append_limit; |
||||
|
} |
||||
|
|
||||
|
for (s32 i = 0; i < registered_buffers; i++) { |
||||
|
if (buffers[index].tag == tag) { |
||||
|
return true; |
||||
|
} |
||||
|
index = (index + 1) % append_limit; |
||||
|
} |
||||
|
|
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Get the number of active buffers in the ring. |
||||
|
* That is, appended, registered and released buffers. |
||||
|
* |
||||
|
* @return Number of active buffers. |
||||
|
*/ |
||||
|
u32 GetAppendedRegisteredCount() const { |
||||
|
std::scoped_lock l{lock}; |
||||
|
return appended_count + registered_count; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Get the total number of active buffers in the ring. |
||||
|
* That is, appended, registered and released buffers. |
||||
|
* |
||||
|
* @return Number of active buffers. |
||||
|
*/ |
||||
|
u32 GetTotalBufferCount() const { |
||||
|
std::scoped_lock l{lock}; |
||||
|
return static_cast<u32>(appended_count + registered_count + released_count); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Flush all of the currently appended and registered buffers |
||||
|
* |
||||
|
* @param buffers_released - Output count for the number of buffers released. |
||||
|
* @return True if buffers were successfully flushed, otherwise false. |
||||
|
*/ |
||||
|
bool FlushBuffers(u32& buffers_released) { |
||||
|
std::scoped_lock l{lock}; |
||||
|
std::vector<AudioBuffer> buffers_flushed{}; |
||||
|
|
||||
|
buffers_released = GetRegisteredAppendedBuffers(buffers_flushed, append_limit); |
||||
|
|
||||
|
if (registered_count > 0) { |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
if (static_cast<u32>(released_count + appended_count) > append_limit) { |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
private: |
||||
|
/// Buffer lock |
||||
|
mutable std::recursive_mutex lock{}; |
||||
|
/// The audio buffers |
||||
|
std::array<AudioBuffer, N> buffers{}; |
||||
|
/// Current released index |
||||
|
s32 released_index{}; |
||||
|
/// Number of released buffers |
||||
|
s32 released_count{}; |
||||
|
/// Current registered index |
||||
|
s32 registered_index{}; |
||||
|
/// Number of registered buffers |
||||
|
s32 registered_count{}; |
||||
|
/// Current appended index |
||||
|
s32 appended_index{}; |
||||
|
/// Number of appended buffers |
||||
|
s32 appended_count{}; |
||||
|
/// Maximum number of buffers (default 32) |
||||
|
u32 append_limit{}; |
||||
|
}; |
||||
|
|
||||
|
} // namespace AudioCore |
||||
@ -0,0 +1,114 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
|
||||
|
#include "audio_core/audio_core.h"
|
||||
|
#include "audio_core/audio_manager.h"
|
||||
|
#include "audio_core/device/audio_buffer.h"
|
||||
|
#include "audio_core/device/device_session.h"
|
||||
|
#include "audio_core/sink/sink_stream.h"
|
||||
|
#include "core/core.h"
|
||||
|
#include "core/memory.h"
|
||||
|
|
||||
|
namespace AudioCore { |
||||
|
|
||||
|
DeviceSession::DeviceSession(Core::System& system_) : system{system_} {} |
||||
|
|
||||
|
DeviceSession::~DeviceSession() { |
||||
|
Finalize(); |
||||
|
} |
||||
|
|
||||
|
Result DeviceSession::Initialize(std::string_view name_, SampleFormat sample_format_, |
||||
|
u16 channel_count_, size_t session_id_, u32 handle_, |
||||
|
u64 applet_resource_user_id_, Sink::StreamType type_) { |
||||
|
if (stream) { |
||||
|
Finalize(); |
||||
|
} |
||||
|
name = fmt::format("{}-{}", name_, session_id_); |
||||
|
type = type_; |
||||
|
sample_format = sample_format_; |
||||
|
channel_count = channel_count_; |
||||
|
session_id = session_id_; |
||||
|
handle = handle_; |
||||
|
applet_resource_user_id = applet_resource_user_id_; |
||||
|
|
||||
|
if (type == Sink::StreamType::In) { |
||||
|
sink = &system.AudioCore().GetInputSink(); |
||||
|
} else { |
||||
|
sink = &system.AudioCore().GetOutputSink(); |
||||
|
} |
||||
|
stream = sink->AcquireSinkStream(system, channel_count, name, type); |
||||
|
initialized = true; |
||||
|
return ResultSuccess; |
||||
|
} |
||||
|
|
||||
|
void DeviceSession::Finalize() { |
||||
|
if (initialized) { |
||||
|
Stop(); |
||||
|
sink->CloseStream(stream); |
||||
|
stream = nullptr; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void DeviceSession::Start() { |
||||
|
stream->SetPlayedSampleCount(played_sample_count); |
||||
|
stream->Start(); |
||||
|
} |
||||
|
|
||||
|
void DeviceSession::Stop() { |
||||
|
if (stream) { |
||||
|
played_sample_count = stream->GetPlayedSampleCount(); |
||||
|
stream->Stop(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void DeviceSession::AppendBuffers(std::span<AudioBuffer> buffers) const { |
||||
|
auto& memory{system.Memory()}; |
||||
|
|
||||
|
for (size_t i = 0; i < buffers.size(); i++) { |
||||
|
Sink::SinkBuffer new_buffer{ |
||||
|
.frames = buffers[i].size / (channel_count * sizeof(s16)), |
||||
|
.frames_played = 0, |
||||
|
.tag = buffers[i].tag, |
||||
|
.consumed = false, |
||||
|
}; |
||||
|
|
||||
|
if (type == Sink::StreamType::In) { |
||||
|
std::vector<s16> samples{}; |
||||
|
stream->AppendBuffer(new_buffer, samples); |
||||
|
} else { |
||||
|
std::vector<s16> samples(buffers[i].size / sizeof(s16)); |
||||
|
memory.ReadBlockUnsafe(buffers[i].samples, samples.data(), buffers[i].size); |
||||
|
stream->AppendBuffer(new_buffer, samples); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void DeviceSession::ReleaseBuffer(AudioBuffer& buffer) const { |
||||
|
if (type == Sink::StreamType::In) { |
||||
|
auto& memory{system.Memory()}; |
||||
|
auto samples{stream->ReleaseBuffer(buffer.size / sizeof(s16))}; |
||||
|
memory.WriteBlockUnsafe(buffer.samples, samples.data(), buffer.size); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
bool DeviceSession::IsBufferConsumed(u64 tag) const { |
||||
|
if (stream) { |
||||
|
return stream->IsBufferConsumed(tag); |
||||
|
} |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
void DeviceSession::SetVolume(f32 volume) const { |
||||
|
if (stream) { |
||||
|
stream->SetSystemVolume(volume); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
u64 DeviceSession::GetPlayedSampleCount() const { |
||||
|
if (stream) { |
||||
|
return stream->GetPlayedSampleCount(); |
||||
|
} |
||||
|
return 0; |
||||
|
} |
||||
|
|
||||
|
} // namespace AudioCore
|
||||
@ -0,0 +1,126 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project |
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <span> |
||||
|
|
||||
|
#include "audio_core/common/common.h" |
||||
|
#include "audio_core/sink/sink.h" |
||||
|
#include "core/hle/service/audio/errors.h" |
||||
|
|
||||
|
namespace Core { |
||||
|
class System; |
||||
|
} |
||||
|
|
||||
|
namespace AudioCore { |
||||
|
namespace Sink { |
||||
|
class SinkStream; |
||||
|
struct SinkBuffer; |
||||
|
} // namespace Sink |
||||
|
|
||||
|
struct AudioBuffer; |
||||
|
|
||||
|
/** |
||||
|
* Represents an input or output device stream for audio in and audio out (not used for render). |
||||
|
**/ |
||||
|
class DeviceSession { |
||||
|
public: |
||||
|
explicit DeviceSession(Core::System& system); |
||||
|
~DeviceSession(); |
||||
|
|
||||
|
/** |
||||
|
* Initialize this device session. |
||||
|
* |
||||
|
* @param name - Name of this device. |
||||
|
* @param sample_format - Sample format for this device's output. |
||||
|
* @param channel_count - Number of channels for this device (2 or 6). |
||||
|
* @param session_id - This session's id. |
||||
|
* @param handle - Handle for this device session (unused). |
||||
|
* @param applet_resource_user_id - Applet resource user id for this device session (unused). |
||||
|
* @param type - Type of this stream (Render, In, Out). |
||||
|
* @return Result code for this call. |
||||
|
*/ |
||||
|
Result Initialize(std::string_view name, SampleFormat sample_format, u16 channel_count, |
||||
|
size_t session_id, u32 handle, u64 applet_resource_user_id, |
||||
|
Sink::StreamType type); |
||||
|
|
||||
|
/** |
||||
|
* Finalize this device session. |
||||
|
*/ |
||||
|
void Finalize(); |
||||
|
|
||||
|
/** |
||||
|
* Append audio buffers to this device session to be played back. |
||||
|
* |
||||
|
* @param buffers - The buffers to play. |
||||
|
*/ |
||||
|
void AppendBuffers(std::span<AudioBuffer> buffers) const; |
||||
|
|
||||
|
/** |
||||
|
* (Audio In only) Pop samples from the backend, and write them back to this buffer's address. |
||||
|
* |
||||
|
* @param buffer - The buffer to write to. |
||||
|
*/ |
||||
|
void ReleaseBuffer(AudioBuffer& buffer) const; |
||||
|
|
||||
|
/** |
||||
|
* Check if the buffer for the given tag has been consumed by the backend. |
||||
|
* |
||||
|
* @param tag - Unqiue tag of the buffer to check. |
||||
|
* @return true if the buffer has been consumed, otherwise false. |
||||
|
*/ |
||||
|
bool IsBufferConsumed(u64 tag) const; |
||||
|
|
||||
|
/** |
||||
|
* Start this device session, starting the backend stream. |
||||
|
*/ |
||||
|
void Start(); |
||||
|
|
||||
|
/** |
||||
|
* Stop this device session, stopping the backend stream. |
||||
|
*/ |
||||
|
void Stop(); |
||||
|
|
||||
|
/** |
||||
|
* Set this device session's volume. |
||||
|
* |
||||
|
* @param volume - New volume for this session. |
||||
|
*/ |
||||
|
void SetVolume(f32 volume) const; |
||||
|
|
||||
|
/** |
||||
|
* Get this device session's total played sample count. |
||||
|
* |
||||
|
* @return Samples played by this session. |
||||
|
*/ |
||||
|
u64 GetPlayedSampleCount() const; |
||||
|
|
||||
|
private: |
||||
|
/// System |
||||
|
Core::System& system; |
||||
|
/// Output sink this device will use |
||||
|
Sink::Sink* sink{}; |
||||
|
/// The backend stream for this device session to send samples to |
||||
|
Sink::SinkStream* stream{}; |
||||
|
/// Name of this device session |
||||
|
std::string name{}; |
||||
|
/// Type of this device session (render/in/out) |
||||
|
Sink::StreamType type{}; |
||||
|
/// Sample format for this device. |
||||
|
SampleFormat sample_format{SampleFormat::PcmInt16}; |
||||
|
/// Channel count for this device session |
||||
|
u16 channel_count{}; |
||||
|
/// Session id of this device session |
||||
|
size_t session_id{}; |
||||
|
/// Handle of this device session |
||||
|
u32 handle{}; |
||||
|
/// Applet resource user id of this device session |
||||
|
u64 applet_resource_user_id{}; |
||||
|
/// Total number of samples played by this device session |
||||
|
u64 played_sample_count{}; |
||||
|
/// Is this session initialised? |
||||
|
bool initialized{}; |
||||
|
}; |
||||
|
|
||||
|
} // namespace AudioCore |
||||
@ -1,320 +0,0 @@ |
|||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||
|
|
||||
#include <algorithm>
|
|
||||
#include "audio_core/effect_context.h"
|
|
||||
|
|
||||
namespace AudioCore { |
|
||||
namespace { |
|
||||
bool ValidChannelCountForEffect(s32 channel_count) { |
|
||||
return channel_count == 1 || channel_count == 2 || channel_count == 4 || channel_count == 6; |
|
||||
} |
|
||||
} // namespace
|
|
||||
|
|
||||
EffectContext::EffectContext(std::size_t effect_count_) : effect_count(effect_count_) { |
|
||||
effects.reserve(effect_count); |
|
||||
std::generate_n(std::back_inserter(effects), effect_count, |
|
||||
[] { return std::make_unique<EffectStubbed>(); }); |
|
||||
} |
|
||||
EffectContext::~EffectContext() = default; |
|
||||
|
|
||||
std::size_t EffectContext::GetCount() const { |
|
||||
return effect_count; |
|
||||
} |
|
||||
|
|
||||
EffectBase* EffectContext::GetInfo(std::size_t i) { |
|
||||
return effects.at(i).get(); |
|
||||
} |
|
||||
|
|
||||
EffectBase* EffectContext::RetargetEffect(std::size_t i, EffectType effect) { |
|
||||
switch (effect) { |
|
||||
case EffectType::Invalid: |
|
||||
effects[i] = std::make_unique<EffectStubbed>(); |
|
||||
break; |
|
||||
case EffectType::BufferMixer: |
|
||||
effects[i] = std::make_unique<EffectBufferMixer>(); |
|
||||
break; |
|
||||
case EffectType::Aux: |
|
||||
effects[i] = std::make_unique<EffectAuxInfo>(); |
|
||||
break; |
|
||||
case EffectType::Delay: |
|
||||
effects[i] = std::make_unique<EffectDelay>(); |
|
||||
break; |
|
||||
case EffectType::Reverb: |
|
||||
effects[i] = std::make_unique<EffectReverb>(); |
|
||||
break; |
|
||||
case EffectType::I3dl2Reverb: |
|
||||
effects[i] = std::make_unique<EffectI3dl2Reverb>(); |
|
||||
break; |
|
||||
case EffectType::BiquadFilter: |
|
||||
effects[i] = std::make_unique<EffectBiquadFilter>(); |
|
||||
break; |
|
||||
default: |
|
||||
ASSERT_MSG(false, "Unimplemented effect {}", effect); |
|
||||
effects[i] = std::make_unique<EffectStubbed>(); |
|
||||
} |
|
||||
return GetInfo(i); |
|
||||
} |
|
||||
|
|
||||
const EffectBase* EffectContext::GetInfo(std::size_t i) const { |
|
||||
return effects.at(i).get(); |
|
||||
} |
|
||||
|
|
||||
EffectStubbed::EffectStubbed() : EffectBase(EffectType::Invalid) {} |
|
||||
EffectStubbed::~EffectStubbed() = default; |
|
||||
|
|
||||
void EffectStubbed::Update([[maybe_unused]] EffectInfo::InParams& in_params) {} |
|
||||
void EffectStubbed::UpdateForCommandGeneration() {} |
|
||||
|
|
||||
EffectBase::EffectBase(EffectType effect_type_) : effect_type(effect_type_) {} |
|
||||
EffectBase::~EffectBase() = default; |
|
||||
|
|
||||
UsageState EffectBase::GetUsage() const { |
|
||||
return usage; |
|
||||
} |
|
||||
|
|
||||
EffectType EffectBase::GetType() const { |
|
||||
return effect_type; |
|
||||
} |
|
||||
|
|
||||
bool EffectBase::IsEnabled() const { |
|
||||
return enabled; |
|
||||
} |
|
||||
|
|
||||
s32 EffectBase::GetMixID() const { |
|
||||
return mix_id; |
|
||||
} |
|
||||
|
|
||||
s32 EffectBase::GetProcessingOrder() const { |
|
||||
return processing_order; |
|
||||
} |
|
||||
|
|
||||
std::vector<u8>& EffectBase::GetWorkBuffer() { |
|
||||
return work_buffer; |
|
||||
} |
|
||||
|
|
||||
const std::vector<u8>& EffectBase::GetWorkBuffer() const { |
|
||||
return work_buffer; |
|
||||
} |
|
||||
|
|
||||
EffectI3dl2Reverb::EffectI3dl2Reverb() : EffectGeneric(EffectType::I3dl2Reverb) {} |
|
||||
EffectI3dl2Reverb::~EffectI3dl2Reverb() = default; |
|
||||
|
|
||||
void EffectI3dl2Reverb::Update(EffectInfo::InParams& in_params) { |
|
||||
auto& params = GetParams(); |
|
||||
const auto* reverb_params = reinterpret_cast<I3dl2ReverbParams*>(in_params.raw.data()); |
|
||||
if (!ValidChannelCountForEffect(reverb_params->max_channels)) { |
|
||||
ASSERT_MSG(false, "Invalid reverb max channel count {}", reverb_params->max_channels); |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
const auto last_status = params.status; |
|
||||
mix_id = in_params.mix_id; |
|
||||
processing_order = in_params.processing_order; |
|
||||
params = *reverb_params; |
|
||||
if (!ValidChannelCountForEffect(reverb_params->channel_count)) { |
|
||||
params.channel_count = params.max_channels; |
|
||||
} |
|
||||
enabled = in_params.is_enabled; |
|
||||
if (last_status != ParameterStatus::Updated) { |
|
||||
params.status = last_status; |
|
||||
} |
|
||||
|
|
||||
if (in_params.is_new || skipped) { |
|
||||
usage = UsageState::Initialized; |
|
||||
params.status = ParameterStatus::Initialized; |
|
||||
skipped = in_params.buffer_address == 0 || in_params.buffer_size == 0; |
|
||||
if (!skipped) { |
|
||||
auto& cur_work_buffer = GetWorkBuffer(); |
|
||||
// Has two buffers internally
|
|
||||
cur_work_buffer.resize(in_params.buffer_size * 2); |
|
||||
std::fill(cur_work_buffer.begin(), cur_work_buffer.end(), 0); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
void EffectI3dl2Reverb::UpdateForCommandGeneration() { |
|
||||
if (enabled) { |
|
||||
usage = UsageState::Running; |
|
||||
} else { |
|
||||
usage = UsageState::Stopped; |
|
||||
} |
|
||||
GetParams().status = ParameterStatus::Updated; |
|
||||
} |
|
||||
|
|
||||
I3dl2ReverbState& EffectI3dl2Reverb::GetState() { |
|
||||
return state; |
|
||||
} |
|
||||
|
|
||||
const I3dl2ReverbState& EffectI3dl2Reverb::GetState() const { |
|
||||
return state; |
|
||||
} |
|
||||
|
|
||||
EffectBiquadFilter::EffectBiquadFilter() : EffectGeneric(EffectType::BiquadFilter) {} |
|
||||
EffectBiquadFilter::~EffectBiquadFilter() = default; |
|
||||
|
|
||||
void EffectBiquadFilter::Update(EffectInfo::InParams& in_params) { |
|
||||
auto& params = GetParams(); |
|
||||
const auto* biquad_params = reinterpret_cast<BiquadFilterParams*>(in_params.raw.data()); |
|
||||
mix_id = in_params.mix_id; |
|
||||
processing_order = in_params.processing_order; |
|
||||
params = *biquad_params; |
|
||||
enabled = in_params.is_enabled; |
|
||||
} |
|
||||
|
|
||||
void EffectBiquadFilter::UpdateForCommandGeneration() { |
|
||||
if (enabled) { |
|
||||
usage = UsageState::Running; |
|
||||
} else { |
|
||||
usage = UsageState::Stopped; |
|
||||
} |
|
||||
GetParams().status = ParameterStatus::Updated; |
|
||||
} |
|
||||
|
|
||||
EffectAuxInfo::EffectAuxInfo() : EffectGeneric(EffectType::Aux) {} |
|
||||
EffectAuxInfo::~EffectAuxInfo() = default; |
|
||||
|
|
||||
void EffectAuxInfo::Update(EffectInfo::InParams& in_params) { |
|
||||
const auto* aux_params = reinterpret_cast<AuxInfo*>(in_params.raw.data()); |
|
||||
mix_id = in_params.mix_id; |
|
||||
processing_order = in_params.processing_order; |
|
||||
GetParams() = *aux_params; |
|
||||
enabled = in_params.is_enabled; |
|
||||
|
|
||||
if (in_params.is_new || skipped) { |
|
||||
skipped = aux_params->send_buffer_info == 0 || aux_params->return_buffer_info == 0; |
|
||||
if (skipped) { |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
// There's two AuxInfos which are an identical size, the first one is managed by the cpu,
|
|
||||
// the second is managed by the dsp. All we care about is managing the DSP one
|
|
||||
send_info = aux_params->send_buffer_info + sizeof(AuxInfoDSP); |
|
||||
send_buffer = aux_params->send_buffer_info + (sizeof(AuxInfoDSP) * 2); |
|
||||
|
|
||||
recv_info = aux_params->return_buffer_info + sizeof(AuxInfoDSP); |
|
||||
recv_buffer = aux_params->return_buffer_info + (sizeof(AuxInfoDSP) * 2); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
void EffectAuxInfo::UpdateForCommandGeneration() { |
|
||||
if (enabled) { |
|
||||
usage = UsageState::Running; |
|
||||
} else { |
|
||||
usage = UsageState::Stopped; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
VAddr EffectAuxInfo::GetSendInfo() const { |
|
||||
return send_info; |
|
||||
} |
|
||||
|
|
||||
VAddr EffectAuxInfo::GetSendBuffer() const { |
|
||||
return send_buffer; |
|
||||
} |
|
||||
|
|
||||
VAddr EffectAuxInfo::GetRecvInfo() const { |
|
||||
return recv_info; |
|
||||
} |
|
||||
|
|
||||
VAddr EffectAuxInfo::GetRecvBuffer() const { |
|
||||
return recv_buffer; |
|
||||
} |
|
||||
|
|
||||
EffectDelay::EffectDelay() : EffectGeneric(EffectType::Delay) {} |
|
||||
EffectDelay::~EffectDelay() = default; |
|
||||
|
|
||||
void EffectDelay::Update(EffectInfo::InParams& in_params) { |
|
||||
const auto* delay_params = reinterpret_cast<DelayParams*>(in_params.raw.data()); |
|
||||
auto& params = GetParams(); |
|
||||
if (!ValidChannelCountForEffect(delay_params->max_channels)) { |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
const auto last_status = params.status; |
|
||||
mix_id = in_params.mix_id; |
|
||||
processing_order = in_params.processing_order; |
|
||||
params = *delay_params; |
|
||||
if (!ValidChannelCountForEffect(delay_params->channels)) { |
|
||||
params.channels = params.max_channels; |
|
||||
} |
|
||||
enabled = in_params.is_enabled; |
|
||||
|
|
||||
if (last_status != ParameterStatus::Updated) { |
|
||||
params.status = last_status; |
|
||||
} |
|
||||
|
|
||||
if (in_params.is_new || skipped) { |
|
||||
usage = UsageState::Initialized; |
|
||||
params.status = ParameterStatus::Initialized; |
|
||||
skipped = in_params.buffer_address == 0 || in_params.buffer_size == 0; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
void EffectDelay::UpdateForCommandGeneration() { |
|
||||
if (enabled) { |
|
||||
usage = UsageState::Running; |
|
||||
} else { |
|
||||
usage = UsageState::Stopped; |
|
||||
} |
|
||||
GetParams().status = ParameterStatus::Updated; |
|
||||
} |
|
||||
|
|
||||
EffectBufferMixer::EffectBufferMixer() : EffectGeneric(EffectType::BufferMixer) {} |
|
||||
EffectBufferMixer::~EffectBufferMixer() = default; |
|
||||
|
|
||||
void EffectBufferMixer::Update(EffectInfo::InParams& in_params) { |
|
||||
mix_id = in_params.mix_id; |
|
||||
processing_order = in_params.processing_order; |
|
||||
GetParams() = *reinterpret_cast<BufferMixerParams*>(in_params.raw.data()); |
|
||||
enabled = in_params.is_enabled; |
|
||||
} |
|
||||
|
|
||||
void EffectBufferMixer::UpdateForCommandGeneration() { |
|
||||
if (enabled) { |
|
||||
usage = UsageState::Running; |
|
||||
} else { |
|
||||
usage = UsageState::Stopped; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
EffectReverb::EffectReverb() : EffectGeneric(EffectType::Reverb) {} |
|
||||
EffectReverb::~EffectReverb() = default; |
|
||||
|
|
||||
void EffectReverb::Update(EffectInfo::InParams& in_params) { |
|
||||
const auto* reverb_params = reinterpret_cast<ReverbParams*>(in_params.raw.data()); |
|
||||
auto& params = GetParams(); |
|
||||
if (!ValidChannelCountForEffect(reverb_params->max_channels)) { |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
const auto last_status = params.status; |
|
||||
mix_id = in_params.mix_id; |
|
||||
processing_order = in_params.processing_order; |
|
||||
params = *reverb_params; |
|
||||
if (!ValidChannelCountForEffect(reverb_params->channels)) { |
|
||||
params.channels = params.max_channels; |
|
||||
} |
|
||||
enabled = in_params.is_enabled; |
|
||||
|
|
||||
if (last_status != ParameterStatus::Updated) { |
|
||||
params.status = last_status; |
|
||||
} |
|
||||
|
|
||||
if (in_params.is_new || skipped) { |
|
||||
usage = UsageState::Initialized; |
|
||||
params.status = ParameterStatus::Initialized; |
|
||||
skipped = in_params.buffer_address == 0 || in_params.buffer_size == 0; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
void EffectReverb::UpdateForCommandGeneration() { |
|
||||
if (enabled) { |
|
||||
usage = UsageState::Running; |
|
||||
} else { |
|
||||
usage = UsageState::Stopped; |
|
||||
} |
|
||||
GetParams().status = ParameterStatus::Updated; |
|
||||
} |
|
||||
|
|
||||
} // namespace AudioCore
|
|
||||
@ -1,349 +0,0 @@ |
|||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project |
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later |
|
||||
|
|
||||
#pragma once |
|
||||
|
|
||||
#include <array> |
|
||||
#include <memory> |
|
||||
#include <vector> |
|
||||
#include "audio_core/common.h" |
|
||||
#include "audio_core/delay_line.h" |
|
||||
#include "common/common_funcs.h" |
|
||||
#include "common/common_types.h" |
|
||||
#include "common/swap.h" |
|
||||
|
|
||||
namespace AudioCore { |
|
||||
enum class EffectType : u8 { |
|
||||
Invalid = 0, |
|
||||
BufferMixer = 1, |
|
||||
Aux = 2, |
|
||||
Delay = 3, |
|
||||
Reverb = 4, |
|
||||
I3dl2Reverb = 5, |
|
||||
BiquadFilter = 6, |
|
||||
}; |
|
||||
|
|
||||
enum class UsageStatus : u8 { |
|
||||
Invalid = 0, |
|
||||
New = 1, |
|
||||
Initialized = 2, |
|
||||
Used = 3, |
|
||||
Removed = 4, |
|
||||
}; |
|
||||
|
|
||||
enum class UsageState { |
|
||||
Invalid = 0, |
|
||||
Initialized = 1, |
|
||||
Running = 2, |
|
||||
Stopped = 3, |
|
||||
}; |
|
||||
|
|
||||
enum class ParameterStatus : u8 { |
|
||||
Initialized = 0, |
|
||||
Updating = 1, |
|
||||
Updated = 2, |
|
||||
}; |
|
||||
|
|
||||
struct BufferMixerParams { |
|
||||
std::array<s8, AudioCommon::MAX_MIX_BUFFERS> input{}; |
|
||||
std::array<s8, AudioCommon::MAX_MIX_BUFFERS> output{}; |
|
||||
std::array<float_le, AudioCommon::MAX_MIX_BUFFERS> volume{}; |
|
||||
s32_le count{}; |
|
||||
}; |
|
||||
static_assert(sizeof(BufferMixerParams) == 0x94, "BufferMixerParams is an invalid size"); |
|
||||
|
|
||||
struct AuxInfoDSP { |
|
||||
u32_le read_offset{}; |
|
||||
u32_le write_offset{}; |
|
||||
u32_le remaining{}; |
|
||||
INSERT_PADDING_WORDS(13); |
|
||||
}; |
|
||||
static_assert(sizeof(AuxInfoDSP) == 0x40, "AuxInfoDSP is an invalid size"); |
|
||||
|
|
||||
struct AuxInfo { |
|
||||
std::array<s8, AudioCommon::MAX_MIX_BUFFERS> input_mix_buffers{}; |
|
||||
std::array<s8, AudioCommon::MAX_MIX_BUFFERS> output_mix_buffers{}; |
|
||||
u32_le count{}; |
|
||||
s32_le sample_rate{}; |
|
||||
s32_le sample_count{}; |
|
||||
s32_le mix_buffer_count{}; |
|
||||
u64_le send_buffer_info{}; |
|
||||
u64_le send_buffer_base{}; |
|
||||
|
|
||||
u64_le return_buffer_info{}; |
|
||||
u64_le return_buffer_base{}; |
|
||||
}; |
|
||||
static_assert(sizeof(AuxInfo) == 0x60, "AuxInfo is an invalid size"); |
|
||||
|
|
||||
struct I3dl2ReverbParams { |
|
||||
std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> input{}; |
|
||||
std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> output{}; |
|
||||
u16_le max_channels{}; |
|
||||
u16_le channel_count{}; |
|
||||
INSERT_PADDING_BYTES(1); |
|
||||
u32_le sample_rate{}; |
|
||||
f32 room_hf{}; |
|
||||
f32 hf_reference{}; |
|
||||
f32 decay_time{}; |
|
||||
f32 hf_decay_ratio{}; |
|
||||
f32 room{}; |
|
||||
f32 reflection{}; |
|
||||
f32 reverb{}; |
|
||||
f32 diffusion{}; |
|
||||
f32 reflection_delay{}; |
|
||||
f32 reverb_delay{}; |
|
||||
f32 density{}; |
|
||||
f32 dry_gain{}; |
|
||||
ParameterStatus status{}; |
|
||||
INSERT_PADDING_BYTES(3); |
|
||||
}; |
|
||||
static_assert(sizeof(I3dl2ReverbParams) == 0x4c, "I3dl2ReverbParams is an invalid size"); |
|
||||
|
|
||||
struct BiquadFilterParams { |
|
||||
std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> input{}; |
|
||||
std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> output{}; |
|
||||
std::array<s16_le, 3> numerator; |
|
||||
std::array<s16_le, 2> denominator; |
|
||||
s8 channel_count{}; |
|
||||
ParameterStatus status{}; |
|
||||
}; |
|
||||
static_assert(sizeof(BiquadFilterParams) == 0x18, "BiquadFilterParams is an invalid size"); |
|
||||
|
|
||||
struct DelayParams { |
|
||||
std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> input{}; |
|
||||
std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> output{}; |
|
||||
u16_le max_channels{}; |
|
||||
u16_le channels{}; |
|
||||
s32_le max_delay{}; |
|
||||
s32_le delay{}; |
|
||||
s32_le sample_rate{}; |
|
||||
s32_le gain{}; |
|
||||
s32_le feedback_gain{}; |
|
||||
s32_le out_gain{}; |
|
||||
s32_le dry_gain{}; |
|
||||
s32_le channel_spread{}; |
|
||||
s32_le low_pass{}; |
|
||||
ParameterStatus status{}; |
|
||||
INSERT_PADDING_BYTES(3); |
|
||||
}; |
|
||||
static_assert(sizeof(DelayParams) == 0x38, "DelayParams is an invalid size"); |
|
||||
|
|
||||
struct ReverbParams { |
|
||||
std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> input{}; |
|
||||
std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> output{}; |
|
||||
u16_le max_channels{}; |
|
||||
u16_le channels{}; |
|
||||
s32_le sample_rate{}; |
|
||||
s32_le mode0{}; |
|
||||
s32_le mode0_gain{}; |
|
||||
s32_le pre_delay{}; |
|
||||
s32_le mode1{}; |
|
||||
s32_le mode1_gain{}; |
|
||||
s32_le decay{}; |
|
||||
s32_le hf_decay_ratio{}; |
|
||||
s32_le coloration{}; |
|
||||
s32_le reverb_gain{}; |
|
||||
s32_le out_gain{}; |
|
||||
s32_le dry_gain{}; |
|
||||
ParameterStatus status{}; |
|
||||
INSERT_PADDING_BYTES(3); |
|
||||
}; |
|
||||
static_assert(sizeof(ReverbParams) == 0x44, "ReverbParams is an invalid size"); |
|
||||
|
|
||||
class EffectInfo { |
|
||||
public: |
|
||||
struct InParams { |
|
||||
EffectType type{}; |
|
||||
u8 is_new{}; |
|
||||
u8 is_enabled{}; |
|
||||
INSERT_PADDING_BYTES(1); |
|
||||
s32_le mix_id{}; |
|
||||
u64_le buffer_address{}; |
|
||||
u64_le buffer_size{}; |
|
||||
s32_le processing_order{}; |
|
||||
INSERT_PADDING_BYTES(4); |
|
||||
union { |
|
||||
std::array<u8, 0xa0> raw; |
|
||||
}; |
|
||||
}; |
|
||||
static_assert(sizeof(InParams) == 0xc0, "InParams is an invalid size"); |
|
||||
|
|
||||
struct OutParams { |
|
||||
UsageStatus status{}; |
|
||||
INSERT_PADDING_BYTES(15); |
|
||||
}; |
|
||||
static_assert(sizeof(OutParams) == 0x10, "OutParams is an invalid size"); |
|
||||
}; |
|
||||
|
|
||||
struct AuxAddress { |
|
||||
VAddr send_dsp_info{}; |
|
||||
VAddr send_buffer_base{}; |
|
||||
VAddr return_dsp_info{}; |
|
||||
VAddr return_buffer_base{}; |
|
||||
}; |
|
||||
|
|
||||
class EffectBase { |
|
||||
public: |
|
||||
explicit EffectBase(EffectType effect_type_); |
|
||||
virtual ~EffectBase(); |
|
||||
|
|
||||
virtual void Update(EffectInfo::InParams& in_params) = 0; |
|
||||
virtual void UpdateForCommandGeneration() = 0; |
|
||||
[[nodiscard]] UsageState GetUsage() const; |
|
||||
[[nodiscard]] EffectType GetType() const; |
|
||||
[[nodiscard]] bool IsEnabled() const; |
|
||||
[[nodiscard]] s32 GetMixID() const; |
|
||||
[[nodiscard]] s32 GetProcessingOrder() const; |
|
||||
[[nodiscard]] std::vector<u8>& GetWorkBuffer(); |
|
||||
[[nodiscard]] const std::vector<u8>& GetWorkBuffer() const; |
|
||||
|
|
||||
protected: |
|
||||
UsageState usage{UsageState::Invalid}; |
|
||||
EffectType effect_type{}; |
|
||||
s32 mix_id{}; |
|
||||
s32 processing_order{}; |
|
||||
bool enabled = false; |
|
||||
std::vector<u8> work_buffer{}; |
|
||||
}; |
|
||||
|
|
||||
template <typename T> |
|
||||
class EffectGeneric : public EffectBase { |
|
||||
public: |
|
||||
explicit EffectGeneric(EffectType effect_type_) : EffectBase(effect_type_) {} |
|
||||
|
|
||||
T& GetParams() { |
|
||||
return internal_params; |
|
||||
} |
|
||||
|
|
||||
const T& GetParams() const { |
|
||||
return internal_params; |
|
||||
} |
|
||||
|
|
||||
private: |
|
||||
T internal_params{}; |
|
||||
}; |
|
||||
|
|
||||
class EffectStubbed : public EffectBase { |
|
||||
public: |
|
||||
explicit EffectStubbed(); |
|
||||
~EffectStubbed() override; |
|
||||
|
|
||||
void Update(EffectInfo::InParams& in_params) override; |
|
||||
void UpdateForCommandGeneration() override; |
|
||||
}; |
|
||||
|
|
||||
struct I3dl2ReverbState { |
|
||||
f32 lowpass_0{}; |
|
||||
f32 lowpass_1{}; |
|
||||
f32 lowpass_2{}; |
|
||||
|
|
||||
DelayLineBase early_delay_line{}; |
|
||||
std::array<u32, AudioCommon::I3DL2REVERB_TAPS> early_tap_steps{}; |
|
||||
f32 early_gain{}; |
|
||||
f32 late_gain{}; |
|
||||
|
|
||||
u32 early_to_late_taps{}; |
|
||||
std::array<DelayLineBase, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> fdn_delay_line{}; |
|
||||
std::array<DelayLineAllPass, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> decay_delay_line0{}; |
|
||||
std::array<DelayLineAllPass, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> decay_delay_line1{}; |
|
||||
f32 last_reverb_echo{}; |
|
||||
DelayLineBase center_delay_line{}; |
|
||||
std::array<std::array<f32, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT>, 3> lpf_coefficients{}; |
|
||||
std::array<f32, AudioCommon::I3DL2REVERB_DELAY_LINE_COUNT> shelf_filter{}; |
|
||||
f32 dry_gain{}; |
|
||||
}; |
|
||||
|
|
||||
class EffectI3dl2Reverb : public EffectGeneric<I3dl2ReverbParams> { |
|
||||
public: |
|
||||
explicit EffectI3dl2Reverb(); |
|
||||
~EffectI3dl2Reverb() override; |
|
||||
|
|
||||
void Update(EffectInfo::InParams& in_params) override; |
|
||||
void UpdateForCommandGeneration() override; |
|
||||
|
|
||||
I3dl2ReverbState& GetState(); |
|
||||
const I3dl2ReverbState& GetState() const; |
|
||||
|
|
||||
private: |
|
||||
bool skipped = false; |
|
||||
I3dl2ReverbState state{}; |
|
||||
}; |
|
||||
|
|
||||
class EffectBiquadFilter : public EffectGeneric<BiquadFilterParams> { |
|
||||
public: |
|
||||
explicit EffectBiquadFilter(); |
|
||||
~EffectBiquadFilter() override; |
|
||||
|
|
||||
void Update(EffectInfo::InParams& in_params) override; |
|
||||
void UpdateForCommandGeneration() override; |
|
||||
}; |
|
||||
|
|
||||
class EffectAuxInfo : public EffectGeneric<AuxInfo> { |
|
||||
public: |
|
||||
explicit EffectAuxInfo(); |
|
||||
~EffectAuxInfo() override; |
|
||||
|
|
||||
void Update(EffectInfo::InParams& in_params) override; |
|
||||
void UpdateForCommandGeneration() override; |
|
||||
[[nodiscard]] VAddr GetSendInfo() const; |
|
||||
[[nodiscard]] VAddr GetSendBuffer() const; |
|
||||
[[nodiscard]] VAddr GetRecvInfo() const; |
|
||||
[[nodiscard]] VAddr GetRecvBuffer() const; |
|
||||
|
|
||||
private: |
|
||||
VAddr send_info{}; |
|
||||
VAddr send_buffer{}; |
|
||||
VAddr recv_info{}; |
|
||||
VAddr recv_buffer{}; |
|
||||
bool skipped = false; |
|
||||
AuxAddress addresses{}; |
|
||||
}; |
|
||||
|
|
||||
class EffectDelay : public EffectGeneric<DelayParams> { |
|
||||
public: |
|
||||
explicit EffectDelay(); |
|
||||
~EffectDelay() override; |
|
||||
|
|
||||
void Update(EffectInfo::InParams& in_params) override; |
|
||||
void UpdateForCommandGeneration() override; |
|
||||
|
|
||||
private: |
|
||||
bool skipped = false; |
|
||||
}; |
|
||||
|
|
||||
class EffectBufferMixer : public EffectGeneric<BufferMixerParams> { |
|
||||
public: |
|
||||
explicit EffectBufferMixer(); |
|
||||
~EffectBufferMixer() override; |
|
||||
|
|
||||
void Update(EffectInfo::InParams& in_params) override; |
|
||||
void UpdateForCommandGeneration() override; |
|
||||
}; |
|
||||
|
|
||||
class EffectReverb : public EffectGeneric<ReverbParams> { |
|
||||
public: |
|
||||
explicit EffectReverb(); |
|
||||
~EffectReverb() override; |
|
||||
|
|
||||
void Update(EffectInfo::InParams& in_params) override; |
|
||||
void UpdateForCommandGeneration() override; |
|
||||
|
|
||||
private: |
|
||||
bool skipped = false; |
|
||||
}; |
|
||||
|
|
||||
class EffectContext { |
|
||||
public: |
|
||||
explicit EffectContext(std::size_t effect_count_); |
|
||||
~EffectContext(); |
|
||||
|
|
||||
[[nodiscard]] std::size_t GetCount() const; |
|
||||
[[nodiscard]] EffectBase* GetInfo(std::size_t i); |
|
||||
[[nodiscard]] EffectBase* RetargetEffect(std::size_t i, EffectType effect); |
|
||||
[[nodiscard]] const EffectBase* GetInfo(std::size_t i) const; |
|
||||
|
|
||||
private: |
|
||||
std::size_t effect_count{}; |
|
||||
std::vector<std::unique_ptr<EffectBase>> effects; |
|
||||
}; |
|
||||
} // namespace AudioCore |
|
||||
@ -0,0 +1,100 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
|
||||
|
#include "audio_core/audio_in_manager.h"
|
||||
|
#include "audio_core/in/audio_in.h"
|
||||
|
#include "core/hle/kernel/k_event.h"
|
||||
|
|
||||
|
namespace AudioCore::AudioIn { |
||||
|
|
||||
|
In::In(Core::System& system_, Manager& manager_, Kernel::KEvent* event_, size_t session_id_) |
||||
|
: manager{manager_}, parent_mutex{manager.mutex}, event{event_}, system{system_, event, |
||||
|
session_id_} {} |
||||
|
|
||||
|
void In::Free() { |
||||
|
std::scoped_lock l{parent_mutex}; |
||||
|
manager.ReleaseSessionId(system.GetSessionId()); |
||||
|
} |
||||
|
|
||||
|
System& In::GetSystem() { |
||||
|
return system; |
||||
|
} |
||||
|
|
||||
|
AudioIn::State In::GetState() { |
||||
|
std::scoped_lock l{parent_mutex}; |
||||
|
return system.GetState(); |
||||
|
} |
||||
|
|
||||
|
Result In::StartSystem() { |
||||
|
std::scoped_lock l{parent_mutex}; |
||||
|
return system.Start(); |
||||
|
} |
||||
|
|
||||
|
void In::StartSession() { |
||||
|
std::scoped_lock l{parent_mutex}; |
||||
|
system.StartSession(); |
||||
|
} |
||||
|
|
||||
|
Result In::StopSystem() { |
||||
|
std::scoped_lock l{parent_mutex}; |
||||
|
return system.Stop(); |
||||
|
} |
||||
|
|
||||
|
Result In::AppendBuffer(const AudioInBuffer& buffer, u64 tag) { |
||||
|
std::scoped_lock l{parent_mutex}; |
||||
|
|
||||
|
if (system.AppendBuffer(buffer, tag)) { |
||||
|
return ResultSuccess; |
||||
|
} |
||||
|
return Service::Audio::ERR_BUFFER_COUNT_EXCEEDED; |
||||
|
} |
||||
|
|
||||
|
void In::ReleaseAndRegisterBuffers() { |
||||
|
std::scoped_lock l{parent_mutex}; |
||||
|
if (system.GetState() == State::Started) { |
||||
|
system.ReleaseBuffers(); |
||||
|
system.RegisterBuffers(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
bool In::FlushAudioInBuffers() { |
||||
|
std::scoped_lock l{parent_mutex}; |
||||
|
return system.FlushAudioInBuffers(); |
||||
|
} |
||||
|
|
||||
|
u32 In::GetReleasedBuffers(std::span<u64> tags) { |
||||
|
std::scoped_lock l{parent_mutex}; |
||||
|
return system.GetReleasedBuffers(tags); |
||||
|
} |
||||
|
|
||||
|
Kernel::KReadableEvent& In::GetBufferEvent() { |
||||
|
std::scoped_lock l{parent_mutex}; |
||||
|
return event->GetReadableEvent(); |
||||
|
} |
||||
|
|
||||
|
f32 In::GetVolume() { |
||||
|
std::scoped_lock l{parent_mutex}; |
||||
|
return system.GetVolume(); |
||||
|
} |
||||
|
|
||||
|
void In::SetVolume(f32 volume) { |
||||
|
std::scoped_lock l{parent_mutex}; |
||||
|
system.SetVolume(volume); |
||||
|
} |
||||
|
|
||||
|
bool In::ContainsAudioBuffer(u64 tag) { |
||||
|
std::scoped_lock l{parent_mutex}; |
||||
|
return system.ContainsAudioBuffer(tag); |
||||
|
} |
||||
|
|
||||
|
u32 In::GetBufferCount() { |
||||
|
std::scoped_lock l{parent_mutex}; |
||||
|
return system.GetBufferCount(); |
||||
|
} |
||||
|
|
||||
|
u64 In::GetPlayedSampleCount() { |
||||
|
std::scoped_lock l{parent_mutex}; |
||||
|
return system.GetPlayedSampleCount(); |
||||
|
} |
||||
|
|
||||
|
} // namespace AudioCore::AudioIn
|
||||
@ -0,0 +1,147 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project |
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <mutex> |
||||
|
|
||||
|
#include "audio_core/in/audio_in_system.h" |
||||
|
|
||||
|
namespace Core { |
||||
|
class System; |
||||
|
} |
||||
|
|
||||
|
namespace Kernel { |
||||
|
class KEvent; |
||||
|
class KReadableEvent; |
||||
|
} // namespace Kernel |
||||
|
|
||||
|
namespace AudioCore::AudioIn { |
||||
|
class Manager; |
||||
|
|
||||
|
/** |
||||
|
* Interface between the service and audio in system. Mainly responsible for forwarding service |
||||
|
* calls to the system. |
||||
|
*/ |
||||
|
class In { |
||||
|
public: |
||||
|
explicit In(Core::System& system, Manager& manager, Kernel::KEvent* event, size_t session_id); |
||||
|
|
||||
|
/** |
||||
|
* Free this audio in from the audio in manager. |
||||
|
*/ |
||||
|
void Free(); |
||||
|
|
||||
|
/** |
||||
|
* Get this audio in's system. |
||||
|
*/ |
||||
|
System& GetSystem(); |
||||
|
|
||||
|
/** |
||||
|
* Get the current state. |
||||
|
* |
||||
|
* @return Started or Stopped. |
||||
|
*/ |
||||
|
AudioIn::State GetState(); |
||||
|
|
||||
|
/** |
||||
|
* Start the system |
||||
|
* |
||||
|
* @return Result code |
||||
|
*/ |
||||
|
Result StartSystem(); |
||||
|
|
||||
|
/** |
||||
|
* Start the system's device session. |
||||
|
*/ |
||||
|
void StartSession(); |
||||
|
|
||||
|
/** |
||||
|
* Stop the system. |
||||
|
* |
||||
|
* @return Result code |
||||
|
*/ |
||||
|
Result StopSystem(); |
||||
|
|
||||
|
/** |
||||
|
* Append a new buffer to the system, the buffer event will be signalled when it is filled. |
||||
|
* |
||||
|
* @param buffer - The new buffer to append. |
||||
|
* @param tag - Unique tag for this buffer. |
||||
|
* @return Result code. |
||||
|
*/ |
||||
|
Result AppendBuffer(const AudioInBuffer& buffer, u64 tag); |
||||
|
|
||||
|
/** |
||||
|
* Release all completed buffers, and register any appended. |
||||
|
*/ |
||||
|
void ReleaseAndRegisterBuffers(); |
||||
|
|
||||
|
/** |
||||
|
* Flush all buffers. |
||||
|
*/ |
||||
|
bool FlushAudioInBuffers(); |
||||
|
|
||||
|
/** |
||||
|
* Get all of the currently released buffers. |
||||
|
* |
||||
|
* @param tags - Output container for the buffer tags which were released. |
||||
|
* @return The number of buffers released. |
||||
|
*/ |
||||
|
u32 GetReleasedBuffers(std::span<u64> tags); |
||||
|
|
||||
|
/** |
||||
|
* Get the buffer event for this audio in, this event will be signalled when a buffer is filled. |
||||
|
* |
||||
|
* @return The buffer event. |
||||
|
*/ |
||||
|
Kernel::KReadableEvent& GetBufferEvent(); |
||||
|
|
||||
|
/** |
||||
|
* Get the current system volume. |
||||
|
* |
||||
|
* @return The current volume. |
||||
|
*/ |
||||
|
f32 GetVolume(); |
||||
|
|
||||
|
/** |
||||
|
* Set the system volume. |
||||
|
* |
||||
|
* @param volume - The volume to set. |
||||
|
*/ |
||||
|
void SetVolume(f32 volume); |
||||
|
|
||||
|
/** |
||||
|
* Check if a buffer is in the system. |
||||
|
* |
||||
|
* @param tag - The tag to search for. |
||||
|
* @return True if the buffer is in the system, otherwise false. |
||||
|
*/ |
||||
|
bool ContainsAudioBuffer(u64 tag); |
||||
|
|
||||
|
/** |
||||
|
* Get the maximum number of buffers. |
||||
|
* |
||||
|
* @return The maximum number of buffers. |
||||
|
*/ |
||||
|
u32 GetBufferCount(); |
||||
|
|
||||
|
/** |
||||
|
* Get the total played sample count for this audio in. |
||||
|
* |
||||
|
* @return The played sample count. |
||||
|
*/ |
||||
|
u64 GetPlayedSampleCount(); |
||||
|
|
||||
|
private: |
||||
|
/// The AudioIn::Manager this audio in is registered with |
||||
|
Manager& manager; |
||||
|
/// Manager's mutex |
||||
|
std::recursive_mutex& parent_mutex; |
||||
|
/// Buffer event, signalled when buffers are ready to be released |
||||
|
Kernel::KEvent* event; |
||||
|
/// Main audio in system |
||||
|
System system; |
||||
|
}; |
||||
|
|
||||
|
} // namespace AudioCore::AudioIn |
||||
@ -0,0 +1,213 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
|
||||
|
#include <mutex>
|
||||
|
#include "audio_core/audio_event.h"
|
||||
|
#include "audio_core/audio_manager.h"
|
||||
|
#include "audio_core/in/audio_in_system.h"
|
||||
|
#include "common/logging/log.h"
|
||||
|
#include "core/core.h"
|
||||
|
#include "core/core_timing.h"
|
||||
|
#include "core/hle/kernel/k_event.h"
|
||||
|
|
||||
|
namespace AudioCore::AudioIn { |
||||
|
|
||||
|
System::System(Core::System& system_, Kernel::KEvent* event_, const size_t session_id_) |
||||
|
: system{system_}, buffer_event{event_}, |
||||
|
session_id{session_id_}, session{std::make_unique<DeviceSession>(system_)} {} |
||||
|
|
||||
|
System::~System() { |
||||
|
Finalize(); |
||||
|
} |
||||
|
|
||||
|
void System::Finalize() { |
||||
|
Stop(); |
||||
|
session->Finalize(); |
||||
|
buffer_event->GetWritableEvent().Signal(); |
||||
|
} |
||||
|
|
||||
|
void System::StartSession() { |
||||
|
session->Start(); |
||||
|
} |
||||
|
|
||||
|
size_t System::GetSessionId() const { |
||||
|
return session_id; |
||||
|
} |
||||
|
|
||||
|
std::string_view System::GetDefaultDeviceName() { |
||||
|
return "BuiltInHeadset"; |
||||
|
} |
||||
|
|
||||
|
std::string_view System::GetDefaultUacDeviceName() { |
||||
|
return "Uac"; |
||||
|
} |
||||
|
|
||||
|
Result System::IsConfigValid(const std::string_view device_name, |
||||
|
const AudioInParameter& in_params) { |
||||
|
if ((device_name.size() > 0) && |
||||
|
(device_name != GetDefaultDeviceName() && device_name != GetDefaultUacDeviceName())) { |
||||
|
return Service::Audio::ERR_INVALID_DEVICE_NAME; |
||||
|
} |
||||
|
|
||||
|
if (in_params.sample_rate != TargetSampleRate && in_params.sample_rate > 0) { |
||||
|
return Service::Audio::ERR_INVALID_SAMPLE_RATE; |
||||
|
} |
||||
|
|
||||
|
return ResultSuccess; |
||||
|
} |
||||
|
|
||||
|
Result System::Initialize(std::string& device_name, const AudioInParameter& in_params, |
||||
|
const u32 handle_, const u64 applet_resource_user_id_) { |
||||
|
auto result{IsConfigValid(device_name, in_params)}; |
||||
|
if (result.IsError()) { |
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
handle = handle_; |
||||
|
applet_resource_user_id = applet_resource_user_id_; |
||||
|
if (device_name.empty() || device_name[0] == '\0') { |
||||
|
name = std::string(GetDefaultDeviceName()); |
||||
|
} else { |
||||
|
name = std::move(device_name); |
||||
|
} |
||||
|
|
||||
|
sample_rate = TargetSampleRate; |
||||
|
sample_format = SampleFormat::PcmInt16; |
||||
|
channel_count = in_params.channel_count <= 2 ? 2 : 6; |
||||
|
volume = 1.0f; |
||||
|
is_uac = name == "Uac"; |
||||
|
return ResultSuccess; |
||||
|
} |
||||
|
|
||||
|
Result System::Start() { |
||||
|
if (state != State::Stopped) { |
||||
|
return Service::Audio::ERR_OPERATION_FAILED; |
||||
|
} |
||||
|
|
||||
|
session->Initialize(name, sample_format, channel_count, session_id, handle, |
||||
|
applet_resource_user_id, Sink::StreamType::In); |
||||
|
session->SetVolume(volume); |
||||
|
session->Start(); |
||||
|
state = State::Started; |
||||
|
|
||||
|
std::vector<AudioBuffer> buffers_to_flush{}; |
||||
|
buffers.RegisterBuffers(buffers_to_flush); |
||||
|
session->AppendBuffers(buffers_to_flush); |
||||
|
|
||||
|
return ResultSuccess; |
||||
|
} |
||||
|
|
||||
|
Result System::Stop() { |
||||
|
if (state == State::Started) { |
||||
|
session->Stop(); |
||||
|
session->SetVolume(0.0f); |
||||
|
state = State::Stopped; |
||||
|
} |
||||
|
|
||||
|
return ResultSuccess; |
||||
|
} |
||||
|
|
||||
|
bool System::AppendBuffer(const AudioInBuffer& buffer, const u64 tag) { |
||||
|
if (buffers.GetTotalBufferCount() == BufferCount) { |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
AudioBuffer new_buffer{ |
||||
|
.played_timestamp = 0, .samples = buffer.samples, .tag = tag, .size = buffer.size}; |
||||
|
|
||||
|
buffers.AppendBuffer(new_buffer); |
||||
|
RegisterBuffers(); |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
void System::RegisterBuffers() { |
||||
|
if (state == State::Started) { |
||||
|
std::vector<AudioBuffer> registered_buffers{}; |
||||
|
buffers.RegisterBuffers(registered_buffers); |
||||
|
session->AppendBuffers(registered_buffers); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void System::ReleaseBuffers() { |
||||
|
bool signal{buffers.ReleaseBuffers(system.CoreTiming(), *session)}; |
||||
|
|
||||
|
if (signal) { |
||||
|
// Signal if any buffer was released, or if none are registered, we need more.
|
||||
|
buffer_event->GetWritableEvent().Signal(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
u32 System::GetReleasedBuffers(std::span<u64> tags) { |
||||
|
return buffers.GetReleasedBuffers(tags); |
||||
|
} |
||||
|
|
||||
|
bool System::FlushAudioInBuffers() { |
||||
|
if (state != State::Started) { |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
u32 buffers_released{}; |
||||
|
buffers.FlushBuffers(buffers_released); |
||||
|
|
||||
|
if (buffers_released > 0) { |
||||
|
buffer_event->GetWritableEvent().Signal(); |
||||
|
} |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
u16 System::GetChannelCount() const { |
||||
|
return channel_count; |
||||
|
} |
||||
|
|
||||
|
u32 System::GetSampleRate() const { |
||||
|
return sample_rate; |
||||
|
} |
||||
|
|
||||
|
SampleFormat System::GetSampleFormat() const { |
||||
|
return sample_format; |
||||
|
} |
||||
|
|
||||
|
State System::GetState() { |
||||
|
switch (state) { |
||||
|
case State::Started: |
||||
|
case State::Stopped: |
||||
|
return state; |
||||
|
default: |
||||
|
LOG_ERROR(Service_Audio, "AudioIn invalid state!"); |
||||
|
state = State::Stopped; |
||||
|
break; |
||||
|
} |
||||
|
return state; |
||||
|
} |
||||
|
|
||||
|
std::string System::GetName() const { |
||||
|
return name; |
||||
|
} |
||||
|
|
||||
|
f32 System::GetVolume() const { |
||||
|
return volume; |
||||
|
} |
||||
|
|
||||
|
void System::SetVolume(const f32 volume_) { |
||||
|
volume = volume_; |
||||
|
session->SetVolume(volume_); |
||||
|
} |
||||
|
|
||||
|
bool System::ContainsAudioBuffer(const u64 tag) { |
||||
|
return buffers.ContainsBuffer(tag); |
||||
|
} |
||||
|
|
||||
|
u32 System::GetBufferCount() { |
||||
|
return buffers.GetAppendedRegisteredCount(); |
||||
|
} |
||||
|
|
||||
|
u64 System::GetPlayedSampleCount() const { |
||||
|
return session->GetPlayedSampleCount(); |
||||
|
} |
||||
|
|
||||
|
bool System::IsUac() const { |
||||
|
return is_uac; |
||||
|
} |
||||
|
|
||||
|
} // namespace AudioCore::AudioIn
|
||||
@ -0,0 +1,275 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project |
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <atomic> |
||||
|
#include <memory> |
||||
|
#include <span> |
||||
|
#include <string> |
||||
|
|
||||
|
#include "audio_core/common/common.h" |
||||
|
#include "audio_core/device/audio_buffers.h" |
||||
|
#include "audio_core/device/device_session.h" |
||||
|
#include "core/hle/service/audio/errors.h" |
||||
|
|
||||
|
namespace Core { |
||||
|
class System; |
||||
|
} |
||||
|
|
||||
|
namespace Kernel { |
||||
|
class KEvent; |
||||
|
} |
||||
|
|
||||
|
namespace AudioCore::AudioIn { |
||||
|
|
||||
|
constexpr SessionTypes SessionType = SessionTypes::AudioIn; |
||||
|
|
||||
|
struct AudioInParameter { |
||||
|
/* 0x0 */ s32_le sample_rate; |
||||
|
/* 0x4 */ u16_le channel_count; |
||||
|
/* 0x6 */ u16_le reserved; |
||||
|
}; |
||||
|
static_assert(sizeof(AudioInParameter) == 0x8, "AudioInParameter is an invalid size"); |
||||
|
|
||||
|
struct AudioInParameterInternal { |
||||
|
/* 0x0 */ u32_le sample_rate; |
||||
|
/* 0x4 */ u32_le channel_count; |
||||
|
/* 0x8 */ u32_le sample_format; |
||||
|
/* 0xC */ u32_le state; |
||||
|
}; |
||||
|
static_assert(sizeof(AudioInParameterInternal) == 0x10, |
||||
|
"AudioInParameterInternal is an invalid size"); |
||||
|
|
||||
|
struct AudioInBuffer { |
||||
|
/* 0x00 */ AudioInBuffer* next; |
||||
|
/* 0x08 */ VAddr samples; |
||||
|
/* 0x10 */ u64 capacity; |
||||
|
/* 0x18 */ u64 size; |
||||
|
/* 0x20 */ u64 offset; |
||||
|
}; |
||||
|
static_assert(sizeof(AudioInBuffer) == 0x28, "AudioInBuffer is an invalid size"); |
||||
|
|
||||
|
enum class State { |
||||
|
Started, |
||||
|
Stopped, |
||||
|
}; |
||||
|
|
||||
|
/** |
||||
|
* Controls and drives audio input. |
||||
|
*/ |
||||
|
class System { |
||||
|
public: |
||||
|
explicit System(Core::System& system, Kernel::KEvent* event, size_t session_id); |
||||
|
~System(); |
||||
|
|
||||
|
/** |
||||
|
* Get the default audio input device name. |
||||
|
* |
||||
|
* @return The default audio input device name. |
||||
|
*/ |
||||
|
std::string_view GetDefaultDeviceName(); |
||||
|
|
||||
|
/** |
||||
|
* Get the default USB audio input device name. |
||||
|
* This is preferred over non-USB as some games refuse to work with the BuiltInHeadset |
||||
|
* (e.g Let's Sing). |
||||
|
* |
||||
|
* @return The default USB audio input device name. |
||||
|
*/ |
||||
|
std::string_view GetDefaultUacDeviceName(); |
||||
|
|
||||
|
/** |
||||
|
* Is the given initialize config valid? |
||||
|
* |
||||
|
* @param device_name - The name of the requested input device. |
||||
|
* @param in_params - Input parameters, see AudioInParameter. |
||||
|
* @return Result code. |
||||
|
*/ |
||||
|
Result IsConfigValid(std::string_view device_name, const AudioInParameter& in_params); |
||||
|
|
||||
|
/** |
||||
|
* Initialize this system. |
||||
|
* |
||||
|
* @param device_name - The name of the requested input device. |
||||
|
* @param in_params - Input parameters, see AudioInParameter. |
||||
|
* @param handle - Unused. |
||||
|
* @param applet_resource_user_id - Unused. |
||||
|
* @return Result code. |
||||
|
*/ |
||||
|
Result Initialize(std::string& device_name, const AudioInParameter& in_params, u32 handle, |
||||
|
u64 applet_resource_user_id); |
||||
|
|
||||
|
/** |
||||
|
* Start this system. |
||||
|
* |
||||
|
* @return Result code. |
||||
|
*/ |
||||
|
Result Start(); |
||||
|
|
||||
|
/** |
||||
|
* Stop this system. |
||||
|
* |
||||
|
* @return Result code. |
||||
|
*/ |
||||
|
Result Stop(); |
||||
|
|
||||
|
/** |
||||
|
* Finalize this system. |
||||
|
*/ |
||||
|
void Finalize(); |
||||
|
|
||||
|
/** |
||||
|
* Start this system's device session. |
||||
|
*/ |
||||
|
void StartSession(); |
||||
|
|
||||
|
/** |
||||
|
* Get this system's id. |
||||
|
*/ |
||||
|
size_t GetSessionId() const; |
||||
|
|
||||
|
/** |
||||
|
* Append a new buffer to the device. |
||||
|
* |
||||
|
* @param buffer - New buffer to append. |
||||
|
* @param tag - Unique tag of the buffer. |
||||
|
* @return True if the buffer was appended, otherwise false. |
||||
|
*/ |
||||
|
bool AppendBuffer(const AudioInBuffer& buffer, u64 tag); |
||||
|
|
||||
|
/** |
||||
|
* Register all appended buffers. |
||||
|
*/ |
||||
|
void RegisterBuffers(); |
||||
|
|
||||
|
/** |
||||
|
* Release all registered buffers. |
||||
|
*/ |
||||
|
void ReleaseBuffers(); |
||||
|
|
||||
|
/** |
||||
|
* Get all released buffers. |
||||
|
* |
||||
|
* @param tags - Container to be filled with the released buffers' tags. |
||||
|
* @return The number of buffers released. |
||||
|
*/ |
||||
|
u32 GetReleasedBuffers(std::span<u64> tags); |
||||
|
|
||||
|
/** |
||||
|
* Flush all appended and registered buffers. |
||||
|
* |
||||
|
* @return True if buffers were successfully flushed, otherwise false. |
||||
|
*/ |
||||
|
bool FlushAudioInBuffers(); |
||||
|
|
||||
|
/** |
||||
|
* Get this system's current channel count. |
||||
|
* |
||||
|
* @return The channel count. |
||||
|
*/ |
||||
|
u16 GetChannelCount() const; |
||||
|
|
||||
|
/** |
||||
|
* Get this system's current sample rate. |
||||
|
* |
||||
|
* @return The sample rate. |
||||
|
*/ |
||||
|
u32 GetSampleRate() const; |
||||
|
|
||||
|
/** |
||||
|
* Get this system's current sample format. |
||||
|
* |
||||
|
* @return The sample format. |
||||
|
*/ |
||||
|
SampleFormat GetSampleFormat() const; |
||||
|
|
||||
|
/** |
||||
|
* Get this system's current state. |
||||
|
* |
||||
|
* @return The current state. |
||||
|
*/ |
||||
|
State GetState(); |
||||
|
|
||||
|
/** |
||||
|
* Get this system's name. |
||||
|
* |
||||
|
* @return The system's name. |
||||
|
*/ |
||||
|
std::string GetName() const; |
||||
|
|
||||
|
/** |
||||
|
* Get this system's current volume. |
||||
|
* |
||||
|
* @return The system's current volume. |
||||
|
*/ |
||||
|
f32 GetVolume() const; |
||||
|
|
||||
|
/** |
||||
|
* Set this system's current volume. |
||||
|
* |
||||
|
* @param The new volume. |
||||
|
*/ |
||||
|
void SetVolume(f32 volume); |
||||
|
|
||||
|
/** |
||||
|
* Does the system contain this buffer? |
||||
|
* |
||||
|
* @param tag - Unique tag to search for. |
||||
|
* @return True if the buffer is in the system, otherwise false. |
||||
|
*/ |
||||
|
bool ContainsAudioBuffer(u64 tag); |
||||
|
|
||||
|
/** |
||||
|
* Get the maximum number of usable buffers (default 32). |
||||
|
* |
||||
|
* @return The number of buffers. |
||||
|
*/ |
||||
|
u32 GetBufferCount(); |
||||
|
|
||||
|
/** |
||||
|
* Get the total number of samples played by this system. |
||||
|
* |
||||
|
* @return The number of samples. |
||||
|
*/ |
||||
|
u64 GetPlayedSampleCount() const; |
||||
|
|
||||
|
/** |
||||
|
* Is this system using a USB device? |
||||
|
* |
||||
|
* @return True if using a USB device, otherwise false. |
||||
|
*/ |
||||
|
bool IsUac() const; |
||||
|
|
||||
|
private: |
||||
|
/// Core system |
||||
|
Core::System& system; |
||||
|
/// (Unused) |
||||
|
u32 handle{}; |
||||
|
/// (Unused) |
||||
|
u64 applet_resource_user_id{}; |
||||
|
/// Buffer event, signalled when a buffer is ready |
||||
|
Kernel::KEvent* buffer_event; |
||||
|
/// Session id of this system |
||||
|
size_t session_id{}; |
||||
|
/// Device session for this system |
||||
|
std::unique_ptr<DeviceSession> session; |
||||
|
/// Audio buffers in use by this system |
||||
|
AudioBuffers<BufferCount> buffers{BufferCount}; |
||||
|
/// Sample rate of this system |
||||
|
u32 sample_rate{}; |
||||
|
/// Sample format of this system |
||||
|
SampleFormat sample_format{SampleFormat::PcmInt16}; |
||||
|
/// Channel count of this system |
||||
|
u16 channel_count{}; |
||||
|
/// State of this system |
||||
|
std::atomic<State> state{State::Stopped}; |
||||
|
/// Name of this system |
||||
|
std::string name{}; |
||||
|
/// Volume of this system |
||||
|
f32 volume{1.0f}; |
||||
|
/// Is this system's device USB? |
||||
|
bool is_uac{false}; |
||||
|
}; |
||||
|
|
||||
|
} // namespace AudioCore::AudioIn |
||||
@ -1,511 +0,0 @@ |
|||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||
|
|
||||
#include "audio_core/behavior_info.h"
|
|
||||
#include "audio_core/effect_context.h"
|
|
||||
#include "audio_core/info_updater.h"
|
|
||||
#include "audio_core/memory_pool.h"
|
|
||||
#include "audio_core/mix_context.h"
|
|
||||
#include "audio_core/sink_context.h"
|
|
||||
#include "audio_core/splitter_context.h"
|
|
||||
#include "audio_core/voice_context.h"
|
|
||||
#include "common/logging/log.h"
|
|
||||
|
|
||||
namespace AudioCore { |
|
||||
|
|
||||
InfoUpdater::InfoUpdater(const std::vector<u8>& in_params_, std::vector<u8>& out_params_, |
|
||||
BehaviorInfo& behavior_info_) |
|
||||
: in_params(in_params_), out_params(out_params_), behavior_info(behavior_info_) { |
|
||||
ASSERT( |
|
||||
AudioCommon::CanConsumeBuffer(in_params.size(), 0, sizeof(AudioCommon::UpdateDataHeader))); |
|
||||
std::memcpy(&input_header, in_params.data(), sizeof(AudioCommon::UpdateDataHeader)); |
|
||||
output_header.total_size = sizeof(AudioCommon::UpdateDataHeader); |
|
||||
} |
|
||||
|
|
||||
InfoUpdater::~InfoUpdater() = default; |
|
||||
|
|
||||
bool InfoUpdater::UpdateBehaviorInfo(BehaviorInfo& in_behavior_info) { |
|
||||
if (input_header.size.behavior != sizeof(BehaviorInfo::InParams)) { |
|
||||
LOG_ERROR(Audio, "Behavior info is an invalid size, expecting 0x{:X} but got 0x{:X}", |
|
||||
sizeof(BehaviorInfo::InParams), input_header.size.behavior); |
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, |
|
||||
sizeof(BehaviorInfo::InParams))) { |
|
||||
LOG_ERROR(Audio, "Buffer is an invalid size!"); |
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
BehaviorInfo::InParams behavior_in{}; |
|
||||
std::memcpy(&behavior_in, in_params.data() + input_offset, sizeof(BehaviorInfo::InParams)); |
|
||||
input_offset += sizeof(BehaviorInfo::InParams); |
|
||||
|
|
||||
// Make sure it's an audio revision we can actually support
|
|
||||
if (!AudioCommon::IsValidRevision(behavior_in.revision)) { |
|
||||
LOG_ERROR(Audio, "Invalid input revision, revision=0x{:08X}", behavior_in.revision); |
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
// Make sure that our behavior info revision matches the input
|
|
||||
if (in_behavior_info.GetUserRevision() != behavior_in.revision) { |
|
||||
LOG_ERROR(Audio, |
|
||||
"User revision differs from input revision, expecting 0x{:08X} but got 0x{:08X}", |
|
||||
in_behavior_info.GetUserRevision(), behavior_in.revision); |
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
// Update behavior info flags
|
|
||||
in_behavior_info.ClearError(); |
|
||||
in_behavior_info.UpdateFlags(behavior_in.flags); |
|
||||
|
|
||||
return true; |
|
||||
} |
|
||||
|
|
||||
bool InfoUpdater::UpdateMemoryPools(std::vector<ServerMemoryPoolInfo>& memory_pool_info) { |
|
||||
const auto memory_pool_count = memory_pool_info.size(); |
|
||||
const auto total_memory_pool_in = sizeof(ServerMemoryPoolInfo::InParams) * memory_pool_count; |
|
||||
const auto total_memory_pool_out = sizeof(ServerMemoryPoolInfo::OutParams) * memory_pool_count; |
|
||||
|
|
||||
if (input_header.size.memory_pool != total_memory_pool_in) { |
|
||||
LOG_ERROR(Audio, "Memory pools are an invalid size, expecting 0x{:X} but got 0x{:X}", |
|
||||
total_memory_pool_in, input_header.size.memory_pool); |
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, total_memory_pool_in)) { |
|
||||
LOG_ERROR(Audio, "Buffer is an invalid size!"); |
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
std::vector<ServerMemoryPoolInfo::InParams> mempool_in(memory_pool_count); |
|
||||
std::vector<ServerMemoryPoolInfo::OutParams> mempool_out(memory_pool_count); |
|
||||
|
|
||||
std::memcpy(mempool_in.data(), in_params.data() + input_offset, total_memory_pool_in); |
|
||||
input_offset += total_memory_pool_in; |
|
||||
|
|
||||
// Update our memory pools
|
|
||||
for (std::size_t i = 0; i < memory_pool_count; i++) { |
|
||||
if (!memory_pool_info[i].Update(mempool_in[i], mempool_out[i])) { |
|
||||
LOG_ERROR(Audio, "Failed to update memory pool {}!", i); |
|
||||
return false; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
if (!AudioCommon::CanConsumeBuffer(out_params.size(), output_offset, |
|
||||
sizeof(BehaviorInfo::InParams))) { |
|
||||
LOG_ERROR(Audio, "Buffer is an invalid size!"); |
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
std::memcpy(out_params.data() + output_offset, mempool_out.data(), total_memory_pool_out); |
|
||||
output_offset += total_memory_pool_out; |
|
||||
output_header.size.memory_pool = static_cast<u32>(total_memory_pool_out); |
|
||||
return true; |
|
||||
} |
|
||||
|
|
||||
bool InfoUpdater::UpdateVoiceChannelResources(VoiceContext& voice_context) { |
|
||||
const auto voice_count = voice_context.GetVoiceCount(); |
|
||||
const auto voice_size = voice_count * sizeof(VoiceChannelResource::InParams); |
|
||||
std::vector<VoiceChannelResource::InParams> resources_in(voice_count); |
|
||||
|
|
||||
if (input_header.size.voice_channel_resource != voice_size) { |
|
||||
LOG_ERROR(Audio, "VoiceChannelResource is an invalid size, expecting 0x{:X} but got 0x{:X}", |
|
||||
voice_size, input_header.size.voice_channel_resource); |
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, voice_size)) { |
|
||||
LOG_ERROR(Audio, "Buffer is an invalid size!"); |
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
std::memcpy(resources_in.data(), in_params.data() + input_offset, voice_size); |
|
||||
input_offset += voice_size; |
|
||||
|
|
||||
// Update our channel resources
|
|
||||
for (std::size_t i = 0; i < voice_count; i++) { |
|
||||
// Grab our channel resource
|
|
||||
auto& resource = voice_context.GetChannelResource(i); |
|
||||
resource.Update(resources_in[i]); |
|
||||
} |
|
||||
|
|
||||
return true; |
|
||||
} |
|
||||
|
|
||||
bool InfoUpdater::UpdateVoices(VoiceContext& voice_context, |
|
||||
[[maybe_unused]] std::vector<ServerMemoryPoolInfo>& memory_pool_info, |
|
||||
[[maybe_unused]] VAddr audio_codec_dsp_addr) { |
|
||||
const auto voice_count = voice_context.GetVoiceCount(); |
|
||||
std::vector<VoiceInfo::InParams> voice_in(voice_count); |
|
||||
std::vector<VoiceInfo::OutParams> voice_out(voice_count); |
|
||||
|
|
||||
const auto voice_in_size = voice_count * sizeof(VoiceInfo::InParams); |
|
||||
const auto voice_out_size = voice_count * sizeof(VoiceInfo::OutParams); |
|
||||
|
|
||||
if (input_header.size.voice != voice_in_size) { |
|
||||
LOG_ERROR(Audio, "Voices are an invalid size, expecting 0x{:X} but got 0x{:X}", |
|
||||
voice_in_size, input_header.size.voice); |
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, voice_in_size)) { |
|
||||
LOG_ERROR(Audio, "Buffer is an invalid size!"); |
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
std::memcpy(voice_in.data(), in_params.data() + input_offset, voice_in_size); |
|
||||
input_offset += voice_in_size; |
|
||||
|
|
||||
// Set all voices to not be in use
|
|
||||
for (std::size_t i = 0; i < voice_count; i++) { |
|
||||
voice_context.GetInfo(i).GetInParams().in_use = false; |
|
||||
} |
|
||||
|
|
||||
// Update our voices
|
|
||||
for (std::size_t i = 0; i < voice_count; i++) { |
|
||||
auto& voice_in_params = voice_in[i]; |
|
||||
const auto channel_count = static_cast<std::size_t>(voice_in_params.channel_count); |
|
||||
// Skip if it's not currently in use
|
|
||||
if (!voice_in_params.is_in_use) { |
|
||||
continue; |
|
||||
} |
|
||||
// Voice states for each channel
|
|
||||
std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT> voice_states{}; |
|
||||
ASSERT(static_cast<std::size_t>(voice_in_params.id) < voice_count); |
|
||||
|
|
||||
// Grab our current voice info
|
|
||||
auto& voice_info = voice_context.GetInfo(static_cast<std::size_t>(voice_in_params.id)); |
|
||||
|
|
||||
ASSERT(channel_count <= AudioCommon::MAX_CHANNEL_COUNT); |
|
||||
|
|
||||
// Get all our channel voice states
|
|
||||
for (std::size_t channel = 0; channel < channel_count; channel++) { |
|
||||
voice_states[channel] = |
|
||||
&voice_context.GetState(voice_in_params.voice_channel_resource_ids[channel]); |
|
||||
} |
|
||||
|
|
||||
if (voice_in_params.is_new) { |
|
||||
// Default our values for our voice
|
|
||||
voice_info.Initialize(); |
|
||||
|
|
||||
// Zero out our voice states
|
|
||||
for (std::size_t channel = 0; channel < channel_count; channel++) { |
|
||||
std::memset(voice_states[channel], 0, sizeof(VoiceState)); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// Update our voice
|
|
||||
voice_info.UpdateParameters(voice_in_params, behavior_info); |
|
||||
// TODO(ogniK): Handle mapping errors with behavior info based on in params response
|
|
||||
|
|
||||
// Update our wave buffers
|
|
||||
voice_info.UpdateWaveBuffers(voice_in_params, voice_states, behavior_info); |
|
||||
voice_info.WriteOutStatus(voice_out[i], voice_in_params, voice_states); |
|
||||
} |
|
||||
|
|
||||
if (!AudioCommon::CanConsumeBuffer(out_params.size(), output_offset, voice_out_size)) { |
|
||||
LOG_ERROR(Audio, "Buffer is an invalid size!"); |
|
||||
return false; |
|
||||
} |
|
||||
std::memcpy(out_params.data() + output_offset, voice_out.data(), voice_out_size); |
|
||||
output_offset += voice_out_size; |
|
||||
output_header.size.voice = static_cast<u32>(voice_out_size); |
|
||||
return true; |
|
||||
} |
|
||||
|
|
||||
bool InfoUpdater::UpdateEffects(EffectContext& effect_context, bool is_active) { |
|
||||
const auto effect_count = effect_context.GetCount(); |
|
||||
std::vector<EffectInfo::InParams> effect_in(effect_count); |
|
||||
std::vector<EffectInfo::OutParams> effect_out(effect_count); |
|
||||
|
|
||||
const auto total_effect_in = effect_count * sizeof(EffectInfo::InParams); |
|
||||
const auto total_effect_out = effect_count * sizeof(EffectInfo::OutParams); |
|
||||
|
|
||||
if (input_header.size.effect != total_effect_in) { |
|
||||
LOG_ERROR(Audio, "Effects are an invalid size, expecting 0x{:X} but got 0x{:X}", |
|
||||
total_effect_in, input_header.size.effect); |
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, total_effect_in)) { |
|
||||
LOG_ERROR(Audio, "Buffer is an invalid size!"); |
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
std::memcpy(effect_in.data(), in_params.data() + input_offset, total_effect_in); |
|
||||
input_offset += total_effect_in; |
|
||||
|
|
||||
// Update effects
|
|
||||
for (std::size_t i = 0; i < effect_count; i++) { |
|
||||
auto* info = effect_context.GetInfo(i); |
|
||||
if (effect_in[i].type != info->GetType()) { |
|
||||
info = effect_context.RetargetEffect(i, effect_in[i].type); |
|
||||
} |
|
||||
|
|
||||
info->Update(effect_in[i]); |
|
||||
|
|
||||
if ((!is_active && info->GetUsage() != UsageState::Initialized) || |
|
||||
info->GetUsage() == UsageState::Stopped) { |
|
||||
effect_out[i].status = UsageStatus::Removed; |
|
||||
} else { |
|
||||
effect_out[i].status = UsageStatus::Used; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
if (!AudioCommon::CanConsumeBuffer(out_params.size(), output_offset, total_effect_out)) { |
|
||||
LOG_ERROR(Audio, "Buffer is an invalid size!"); |
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
std::memcpy(out_params.data() + output_offset, effect_out.data(), total_effect_out); |
|
||||
output_offset += total_effect_out; |
|
||||
output_header.size.effect = static_cast<u32>(total_effect_out); |
|
||||
|
|
||||
return true; |
|
||||
} |
|
||||
|
|
||||
bool InfoUpdater::UpdateSplitterInfo(SplitterContext& splitter_context) { |
|
||||
std::size_t start_offset = input_offset; |
|
||||
std::size_t bytes_read{}; |
|
||||
// Update splitter context
|
|
||||
if (!splitter_context.Update(in_params, input_offset, bytes_read)) { |
|
||||
LOG_ERROR(Audio, "Failed to update splitter context!"); |
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
const auto consumed = input_offset - start_offset; |
|
||||
|
|
||||
if (input_header.size.splitter != consumed) { |
|
||||
LOG_ERROR(Audio, "Splitters is an invalid size, expecting 0x{:X} but got 0x{:X}", |
|
||||
bytes_read, input_header.size.splitter); |
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
return true; |
|
||||
} |
|
||||
|
|
||||
Result InfoUpdater::UpdateMixes(MixContext& mix_context, std::size_t mix_buffer_count, |
|
||||
SplitterContext& splitter_context, EffectContext& effect_context) { |
|
||||
std::vector<MixInfo::InParams> mix_in_params; |
|
||||
|
|
||||
if (!behavior_info.IsMixInParameterDirtyOnlyUpdateSupported()) { |
|
||||
// If we're not dirty, get ALL mix in parameters
|
|
||||
const auto context_mix_count = mix_context.GetCount(); |
|
||||
const auto total_mix_in = context_mix_count * sizeof(MixInfo::InParams); |
|
||||
if (input_header.size.mixer != total_mix_in) { |
|
||||
LOG_ERROR(Audio, "Mixer is an invalid size, expecting 0x{:X} but got 0x{:X}", |
|
||||
total_mix_in, input_header.size.mixer); |
|
||||
return AudioCommon::Audren::ERR_INVALID_PARAMETERS; |
|
||||
} |
|
||||
|
|
||||
if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, total_mix_in)) { |
|
||||
LOG_ERROR(Audio, "Buffer is an invalid size!"); |
|
||||
return AudioCommon::Audren::ERR_INVALID_PARAMETERS; |
|
||||
} |
|
||||
|
|
||||
mix_in_params.resize(context_mix_count); |
|
||||
std::memcpy(mix_in_params.data(), in_params.data() + input_offset, total_mix_in); |
|
||||
|
|
||||
input_offset += total_mix_in; |
|
||||
} else { |
|
||||
// Only update the "dirty" mixes
|
|
||||
MixInfo::DirtyHeader dirty_header{}; |
|
||||
if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, |
|
||||
sizeof(MixInfo::DirtyHeader))) { |
|
||||
LOG_ERROR(Audio, "Buffer is an invalid size!"); |
|
||||
return AudioCommon::Audren::ERR_INVALID_PARAMETERS; |
|
||||
} |
|
||||
|
|
||||
std::memcpy(&dirty_header, in_params.data() + input_offset, sizeof(MixInfo::DirtyHeader)); |
|
||||
input_offset += sizeof(MixInfo::DirtyHeader); |
|
||||
|
|
||||
const auto total_mix_in = |
|
||||
dirty_header.mixer_count * sizeof(MixInfo::InParams) + sizeof(MixInfo::DirtyHeader); |
|
||||
|
|
||||
if (input_header.size.mixer != total_mix_in) { |
|
||||
LOG_ERROR(Audio, "Mixer is an invalid size, expecting 0x{:X} but got 0x{:X}", |
|
||||
total_mix_in, input_header.size.mixer); |
|
||||
return AudioCommon::Audren::ERR_INVALID_PARAMETERS; |
|
||||
} |
|
||||
|
|
||||
if (dirty_header.mixer_count != 0) { |
|
||||
mix_in_params.resize(dirty_header.mixer_count); |
|
||||
std::memcpy(mix_in_params.data(), in_params.data() + input_offset, |
|
||||
mix_in_params.size() * sizeof(MixInfo::InParams)); |
|
||||
input_offset += mix_in_params.size() * sizeof(MixInfo::InParams); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// Get our total input count
|
|
||||
const auto mix_count = mix_in_params.size(); |
|
||||
|
|
||||
if (!behavior_info.IsMixInParameterDirtyOnlyUpdateSupported()) { |
|
||||
// Only verify our buffer count if we're not dirty
|
|
||||
std::size_t total_buffer_count{}; |
|
||||
for (std::size_t i = 0; i < mix_count; i++) { |
|
||||
const auto& in = mix_in_params[i]; |
|
||||
total_buffer_count += in.buffer_count; |
|
||||
if (static_cast<std::size_t>(in.dest_mix_id) > mix_count && |
|
||||
in.dest_mix_id != AudioCommon::NO_MIX && in.mix_id != AudioCommon::FINAL_MIX) { |
|
||||
LOG_ERROR( |
|
||||
Audio, |
|
||||
"Invalid mix destination, mix_id={:X}, dest_mix_id={:X}, mix_buffer_count={:X}", |
|
||||
in.mix_id, in.dest_mix_id, mix_buffer_count); |
|
||||
return AudioCommon::Audren::ERR_INVALID_PARAMETERS; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
if (total_buffer_count > mix_buffer_count) { |
|
||||
LOG_ERROR(Audio, |
|
||||
"Too many mix buffers used! mix_buffer_count={:X}, requesting_buffers={:X}", |
|
||||
mix_buffer_count, total_buffer_count); |
|
||||
return AudioCommon::Audren::ERR_INVALID_PARAMETERS; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
if (mix_buffer_count == 0) { |
|
||||
LOG_ERROR(Audio, "No mix buffers!"); |
|
||||
return AudioCommon::Audren::ERR_INVALID_PARAMETERS; |
|
||||
} |
|
||||
|
|
||||
bool should_sort = false; |
|
||||
for (std::size_t i = 0; i < mix_count; i++) { |
|
||||
const auto& mix_in = mix_in_params[i]; |
|
||||
std::size_t target_mix{}; |
|
||||
if (behavior_info.IsMixInParameterDirtyOnlyUpdateSupported()) { |
|
||||
target_mix = mix_in.mix_id; |
|
||||
} else { |
|
||||
// Non dirty supported games just use i instead of the actual mix_id
|
|
||||
target_mix = i; |
|
||||
} |
|
||||
auto& mix_info = mix_context.GetInfo(target_mix); |
|
||||
auto& mix_info_params = mix_info.GetInParams(); |
|
||||
if (mix_info_params.in_use != mix_in.in_use) { |
|
||||
mix_info_params.in_use = mix_in.in_use; |
|
||||
mix_info.ResetEffectProcessingOrder(); |
|
||||
should_sort = true; |
|
||||
} |
|
||||
|
|
||||
if (mix_in.in_use) { |
|
||||
should_sort |= mix_info.Update(mix_context.GetEdgeMatrix(), mix_in, behavior_info, |
|
||||
splitter_context, effect_context); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
if (should_sort && behavior_info.IsSplitterSupported()) { |
|
||||
// Sort our splitter data
|
|
||||
if (!mix_context.TsortInfo(splitter_context)) { |
|
||||
return AudioCommon::Audren::ERR_SPLITTER_SORT_FAILED; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// TODO(ogniK): Sort when splitter is suppoorted
|
|
||||
|
|
||||
return ResultSuccess; |
|
||||
} |
|
||||
|
|
||||
bool InfoUpdater::UpdateSinks(SinkContext& sink_context) { |
|
||||
const auto sink_count = sink_context.GetCount(); |
|
||||
std::vector<SinkInfo::InParams> sink_in_params(sink_count); |
|
||||
const auto total_sink_in = sink_count * sizeof(SinkInfo::InParams); |
|
||||
|
|
||||
if (input_header.size.sink != total_sink_in) { |
|
||||
LOG_ERROR(Audio, "Sinks are an invalid size, expecting 0x{:X} but got 0x{:X}", |
|
||||
total_sink_in, input_header.size.effect); |
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, total_sink_in)) { |
|
||||
LOG_ERROR(Audio, "Buffer is an invalid size!"); |
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
std::memcpy(sink_in_params.data(), in_params.data() + input_offset, total_sink_in); |
|
||||
input_offset += total_sink_in; |
|
||||
|
|
||||
// TODO(ogniK): Properly update sinks
|
|
||||
if (!sink_in_params.empty()) { |
|
||||
sink_context.UpdateMainSink(sink_in_params[0]); |
|
||||
} |
|
||||
|
|
||||
output_header.size.sink = static_cast<u32>(0x20 * sink_count); |
|
||||
output_offset += 0x20 * sink_count; |
|
||||
return true; |
|
||||
} |
|
||||
|
|
||||
bool InfoUpdater::UpdatePerformanceBuffer() { |
|
||||
output_header.size.performance = 0x10; |
|
||||
output_offset += 0x10; |
|
||||
return true; |
|
||||
} |
|
||||
|
|
||||
bool InfoUpdater::UpdateErrorInfo([[maybe_unused]] BehaviorInfo& in_behavior_info) { |
|
||||
const auto total_beahvior_info_out = sizeof(BehaviorInfo::OutParams); |
|
||||
|
|
||||
if (!AudioCommon::CanConsumeBuffer(out_params.size(), output_offset, total_beahvior_info_out)) { |
|
||||
LOG_ERROR(Audio, "Buffer is an invalid size!"); |
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
BehaviorInfo::OutParams behavior_info_out{}; |
|
||||
behavior_info.CopyErrorInfo(behavior_info_out); |
|
||||
|
|
||||
std::memcpy(out_params.data() + output_offset, &behavior_info_out, total_beahvior_info_out); |
|
||||
output_offset += total_beahvior_info_out; |
|
||||
output_header.size.behavior = total_beahvior_info_out; |
|
||||
|
|
||||
return true; |
|
||||
} |
|
||||
|
|
||||
struct RendererInfo { |
|
||||
u64_le elasped_frame_count{}; |
|
||||
INSERT_PADDING_WORDS(2); |
|
||||
}; |
|
||||
static_assert(sizeof(RendererInfo) == 0x10, "RendererInfo is an invalid size"); |
|
||||
|
|
||||
bool InfoUpdater::UpdateRendererInfo(std::size_t elapsed_frame_count) { |
|
||||
const auto total_renderer_info_out = sizeof(RendererInfo); |
|
||||
if (!AudioCommon::CanConsumeBuffer(out_params.size(), output_offset, total_renderer_info_out)) { |
|
||||
LOG_ERROR(Audio, "Buffer is an invalid size!"); |
|
||||
return false; |
|
||||
} |
|
||||
RendererInfo out{}; |
|
||||
out.elasped_frame_count = elapsed_frame_count; |
|
||||
std::memcpy(out_params.data() + output_offset, &out, total_renderer_info_out); |
|
||||
output_offset += total_renderer_info_out; |
|
||||
output_header.size.render_info = total_renderer_info_out; |
|
||||
|
|
||||
return true; |
|
||||
} |
|
||||
|
|
||||
bool InfoUpdater::CheckConsumedSize() const { |
|
||||
if (output_offset != out_params.size()) { |
|
||||
LOG_ERROR(Audio, "Output is not consumed! Consumed {}, but requires {}. {} bytes remaining", |
|
||||
output_offset, out_params.size(), out_params.size() - output_offset); |
|
||||
return false; |
|
||||
} |
|
||||
/*if (input_offset != in_params.size()) {
|
|
||||
LOG_ERROR(Audio, "Input is not consumed!"); |
|
||||
return false; |
|
||||
}*/ |
|
||||
return true; |
|
||||
} |
|
||||
|
|
||||
bool InfoUpdater::WriteOutputHeader() { |
|
||||
if (!AudioCommon::CanConsumeBuffer(out_params.size(), 0, |
|
||||
sizeof(AudioCommon::UpdateDataHeader))) { |
|
||||
LOG_ERROR(Audio, "Buffer is an invalid size!"); |
|
||||
return false; |
|
||||
} |
|
||||
output_header.revision = AudioCommon::CURRENT_PROCESS_REVISION; |
|
||||
const auto& sz = output_header.size; |
|
||||
output_header.total_size += sz.behavior + sz.memory_pool + sz.voice + |
|
||||
sz.voice_channel_resource + sz.effect + sz.mixer + sz.sink + |
|
||||
sz.performance + sz.splitter + sz.render_info; |
|
||||
|
|
||||
std::memcpy(out_params.data(), &output_header, sizeof(AudioCommon::UpdateDataHeader)); |
|
||||
return true; |
|
||||
} |
|
||||
|
|
||||
} // namespace AudioCore
|
|
||||
@ -1,57 +0,0 @@ |
|||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project |
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later |
|
||||
|
|
||||
#pragma once |
|
||||
|
|
||||
#include <vector> |
|
||||
#include "audio_core/common.h" |
|
||||
#include "common/common_types.h" |
|
||||
|
|
||||
namespace AudioCore { |
|
||||
|
|
||||
class BehaviorInfo; |
|
||||
class ServerMemoryPoolInfo; |
|
||||
class VoiceContext; |
|
||||
class EffectContext; |
|
||||
class MixContext; |
|
||||
class SinkContext; |
|
||||
class SplitterContext; |
|
||||
|
|
||||
class InfoUpdater { |
|
||||
public: |
|
||||
// TODO(ogniK): Pass process handle when we support it |
|
||||
InfoUpdater(const std::vector<u8>& in_params_, std::vector<u8>& out_params_, |
|
||||
BehaviorInfo& behavior_info_); |
|
||||
~InfoUpdater(); |
|
||||
|
|
||||
bool UpdateBehaviorInfo(BehaviorInfo& in_behavior_info); |
|
||||
bool UpdateMemoryPools(std::vector<ServerMemoryPoolInfo>& memory_pool_info); |
|
||||
bool UpdateVoiceChannelResources(VoiceContext& voice_context); |
|
||||
bool UpdateVoices(VoiceContext& voice_context, |
|
||||
std::vector<ServerMemoryPoolInfo>& memory_pool_info, |
|
||||
VAddr audio_codec_dsp_addr); |
|
||||
bool UpdateEffects(EffectContext& effect_context, bool is_active); |
|
||||
bool UpdateSplitterInfo(SplitterContext& splitter_context); |
|
||||
Result UpdateMixes(MixContext& mix_context, std::size_t mix_buffer_count, |
|
||||
SplitterContext& splitter_context, EffectContext& effect_context); |
|
||||
bool UpdateSinks(SinkContext& sink_context); |
|
||||
bool UpdatePerformanceBuffer(); |
|
||||
bool UpdateErrorInfo(BehaviorInfo& in_behavior_info); |
|
||||
bool UpdateRendererInfo(std::size_t elapsed_frame_count); |
|
||||
bool CheckConsumedSize() const; |
|
||||
|
|
||||
bool WriteOutputHeader(); |
|
||||
|
|
||||
private: |
|
||||
const std::vector<u8>& in_params; |
|
||||
std::vector<u8>& out_params; |
|
||||
BehaviorInfo& behavior_info; |
|
||||
|
|
||||
AudioCommon::UpdateDataHeader input_header{}; |
|
||||
AudioCommon::UpdateDataHeader output_header{}; |
|
||||
|
|
||||
std::size_t input_offset{sizeof(AudioCommon::UpdateDataHeader)}; |
|
||||
std::size_t output_offset{sizeof(AudioCommon::UpdateDataHeader)}; |
|
||||
}; |
|
||||
|
|
||||
} // namespace AudioCore |
|
||||
@ -1,60 +0,0 @@ |
|||||
|
|
||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||
|
|
||||
#include "audio_core/memory_pool.h"
|
|
||||
#include "common/logging/log.h"
|
|
||||
|
|
||||
namespace AudioCore { |
|
||||
|
|
||||
ServerMemoryPoolInfo::ServerMemoryPoolInfo() = default; |
|
||||
ServerMemoryPoolInfo::~ServerMemoryPoolInfo() = default; |
|
||||
|
|
||||
bool ServerMemoryPoolInfo::Update(const InParams& in_params, OutParams& out_params) { |
|
||||
// Our state does not need to be changed
|
|
||||
if (in_params.state != State::RequestAttach && in_params.state != State::RequestDetach) { |
|
||||
return true; |
|
||||
} |
|
||||
|
|
||||
// Address or size is null
|
|
||||
if (in_params.address == 0 || in_params.size == 0) { |
|
||||
LOG_ERROR(Audio, "Memory pool address or size is zero! address={:X}, size={:X}", |
|
||||
in_params.address, in_params.size); |
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
// Address or size is not aligned
|
|
||||
if ((in_params.address % 0x1000) != 0 || (in_params.size % 0x1000) != 0) { |
|
||||
LOG_ERROR(Audio, "Memory pool address or size is not aligned! address={:X}, size={:X}", |
|
||||
in_params.address, in_params.size); |
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
if (in_params.state == State::RequestAttach) { |
|
||||
cpu_address = in_params.address; |
|
||||
size = in_params.size; |
|
||||
used = true; |
|
||||
out_params.state = State::Attached; |
|
||||
} else { |
|
||||
// Unexpected address
|
|
||||
if (cpu_address != in_params.address) { |
|
||||
LOG_ERROR(Audio, "Memory pool address differs! Expecting {:X} but address is {:X}", |
|
||||
cpu_address, in_params.address); |
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
if (size != in_params.size) { |
|
||||
LOG_ERROR(Audio, "Memory pool size differs! Expecting {:X} but size is {:X}", size, |
|
||||
in_params.size); |
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
cpu_address = 0; |
|
||||
size = 0; |
|
||||
used = false; |
|
||||
out_params.state = State::Detached; |
|
||||
} |
|
||||
return true; |
|
||||
} |
|
||||
|
|
||||
} // namespace AudioCore
|
|
||||
@ -1,51 +0,0 @@ |
|||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project |
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later |
|
||||
|
|
||||
#pragma once |
|
||||
|
|
||||
#include "common/common_funcs.h" |
|
||||
#include "common/common_types.h" |
|
||||
#include "common/swap.h" |
|
||||
|
|
||||
namespace AudioCore { |
|
||||
|
|
||||
class ServerMemoryPoolInfo { |
|
||||
public: |
|
||||
ServerMemoryPoolInfo(); |
|
||||
~ServerMemoryPoolInfo(); |
|
||||
|
|
||||
enum class State : u32_le { |
|
||||
Invalid = 0x0, |
|
||||
Aquired = 0x1, |
|
||||
RequestDetach = 0x2, |
|
||||
Detached = 0x3, |
|
||||
RequestAttach = 0x4, |
|
||||
Attached = 0x5, |
|
||||
Released = 0x6, |
|
||||
}; |
|
||||
|
|
||||
struct InParams { |
|
||||
u64_le address{}; |
|
||||
u64_le size{}; |
|
||||
State state{}; |
|
||||
INSERT_PADDING_WORDS(3); |
|
||||
}; |
|
||||
static_assert(sizeof(InParams) == 0x20, "InParams are an invalid size"); |
|
||||
|
|
||||
struct OutParams { |
|
||||
State state{}; |
|
||||
INSERT_PADDING_WORDS(3); |
|
||||
}; |
|
||||
static_assert(sizeof(OutParams) == 0x10, "OutParams are an invalid size"); |
|
||||
|
|
||||
bool Update(const InParams& in_params, OutParams& out_params); |
|
||||
|
|
||||
private: |
|
||||
// There's another entry here which is the DSP address, however since we're not talking to the |
|
||||
// DSP we can just use the same address provided by the guest without needing to remap |
|
||||
u64_le cpu_address{}; |
|
||||
u64_le size{}; |
|
||||
bool used{}; |
|
||||
}; |
|
||||
|
|
||||
} // namespace AudioCore |
|
||||
@ -1,297 +0,0 @@ |
|||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||
|
|
||||
#include <algorithm>
|
|
||||
|
|
||||
#include "audio_core/behavior_info.h"
|
|
||||
#include "audio_core/common.h"
|
|
||||
#include "audio_core/effect_context.h"
|
|
||||
#include "audio_core/mix_context.h"
|
|
||||
#include "audio_core/splitter_context.h"
|
|
||||
|
|
||||
namespace AudioCore { |
|
||||
MixContext::MixContext() = default; |
|
||||
MixContext::~MixContext() = default; |
|
||||
|
|
||||
void MixContext::Initialize(const BehaviorInfo& behavior_info, std::size_t mix_count, |
|
||||
std::size_t effect_count) { |
|
||||
info_count = mix_count; |
|
||||
infos.resize(info_count); |
|
||||
auto& final_mix = GetInfo(AudioCommon::FINAL_MIX); |
|
||||
final_mix.GetInParams().mix_id = AudioCommon::FINAL_MIX; |
|
||||
sorted_info.reserve(infos.size()); |
|
||||
for (auto& info : infos) { |
|
||||
sorted_info.push_back(&info); |
|
||||
} |
|
||||
|
|
||||
for (auto& info : infos) { |
|
||||
info.SetEffectCount(effect_count); |
|
||||
} |
|
||||
|
|
||||
// Only initialize our edge matrix and node states if splitters are supported
|
|
||||
if (behavior_info.IsSplitterSupported()) { |
|
||||
node_states.Initialize(mix_count); |
|
||||
edge_matrix.Initialize(mix_count); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
void MixContext::UpdateDistancesFromFinalMix() { |
|
||||
// Set all distances to be invalid
|
|
||||
for (std::size_t i = 0; i < info_count; i++) { |
|
||||
GetInfo(i).GetInParams().final_mix_distance = AudioCommon::NO_FINAL_MIX; |
|
||||
} |
|
||||
|
|
||||
for (std::size_t i = 0; i < info_count; i++) { |
|
||||
auto& info = GetInfo(i); |
|
||||
auto& in_params = info.GetInParams(); |
|
||||
// Populate our sorted info
|
|
||||
sorted_info[i] = &info; |
|
||||
|
|
||||
if (!in_params.in_use) { |
|
||||
continue; |
|
||||
} |
|
||||
|
|
||||
auto mix_id = in_params.mix_id; |
|
||||
// Needs to be referenced out of scope
|
|
||||
s32 distance_to_final_mix{AudioCommon::FINAL_MIX}; |
|
||||
for (; distance_to_final_mix < static_cast<s32>(info_count); distance_to_final_mix++) { |
|
||||
if (mix_id == AudioCommon::FINAL_MIX) { |
|
||||
// If we're at the final mix, we're done
|
|
||||
break; |
|
||||
} else if (mix_id == AudioCommon::NO_MIX) { |
|
||||
// If we have no more mix ids, we're done
|
|
||||
distance_to_final_mix = AudioCommon::NO_FINAL_MIX; |
|
||||
break; |
|
||||
} else { |
|
||||
const auto& dest_mix = GetInfo(mix_id); |
|
||||
const auto dest_mix_distance = dest_mix.GetInParams().final_mix_distance; |
|
||||
|
|
||||
if (dest_mix_distance == AudioCommon::NO_FINAL_MIX) { |
|
||||
// If our current mix isn't pointing to a final mix, follow through
|
|
||||
mix_id = dest_mix.GetInParams().dest_mix_id; |
|
||||
} else { |
|
||||
// Our current mix + 1 = final distance
|
|
||||
distance_to_final_mix = dest_mix_distance + 1; |
|
||||
break; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// If we're out of range for our distance, mark it as no final mix
|
|
||||
if (distance_to_final_mix >= static_cast<s32>(info_count)) { |
|
||||
distance_to_final_mix = AudioCommon::NO_FINAL_MIX; |
|
||||
} |
|
||||
|
|
||||
in_params.final_mix_distance = distance_to_final_mix; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
void MixContext::CalcMixBufferOffset() { |
|
||||
s32 offset{}; |
|
||||
for (std::size_t i = 0; i < info_count; i++) { |
|
||||
auto& info = GetSortedInfo(i); |
|
||||
auto& in_params = info.GetInParams(); |
|
||||
if (in_params.in_use) { |
|
||||
// Only update if in use
|
|
||||
in_params.buffer_offset = offset; |
|
||||
offset += in_params.buffer_count; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
void MixContext::SortInfo() { |
|
||||
// Get the distance to the final mix
|
|
||||
UpdateDistancesFromFinalMix(); |
|
||||
|
|
||||
// Sort based on the distance to the final mix
|
|
||||
std::sort(sorted_info.begin(), sorted_info.end(), |
|
||||
[](const ServerMixInfo* lhs, const ServerMixInfo* rhs) { |
|
||||
return lhs->GetInParams().final_mix_distance > |
|
||||
rhs->GetInParams().final_mix_distance; |
|
||||
}); |
|
||||
|
|
||||
// Calculate the mix buffer offset
|
|
||||
CalcMixBufferOffset(); |
|
||||
} |
|
||||
|
|
||||
bool MixContext::TsortInfo(SplitterContext& splitter_context) { |
|
||||
// If we're not using mixes, just calculate the mix buffer offset
|
|
||||
if (!splitter_context.UsingSplitter()) { |
|
||||
CalcMixBufferOffset(); |
|
||||
return true; |
|
||||
} |
|
||||
// Sort our node states
|
|
||||
if (!node_states.Tsort(edge_matrix)) { |
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
// Get our sorted list
|
|
||||
const auto sorted_list = node_states.GetIndexList(); |
|
||||
std::size_t info_id{}; |
|
||||
for (auto itr = sorted_list.rbegin(); itr != sorted_list.rend(); ++itr) { |
|
||||
// Set our sorted info
|
|
||||
sorted_info[info_id++] = &GetInfo(*itr); |
|
||||
} |
|
||||
|
|
||||
// Calculate the mix buffer offset
|
|
||||
CalcMixBufferOffset(); |
|
||||
return true; |
|
||||
} |
|
||||
|
|
||||
std::size_t MixContext::GetCount() const { |
|
||||
return info_count; |
|
||||
} |
|
||||
|
|
||||
ServerMixInfo& MixContext::GetInfo(std::size_t i) { |
|
||||
ASSERT(i < info_count); |
|
||||
return infos.at(i); |
|
||||
} |
|
||||
|
|
||||
const ServerMixInfo& MixContext::GetInfo(std::size_t i) const { |
|
||||
ASSERT(i < info_count); |
|
||||
return infos.at(i); |
|
||||
} |
|
||||
|
|
||||
ServerMixInfo& MixContext::GetSortedInfo(std::size_t i) { |
|
||||
ASSERT(i < info_count); |
|
||||
return *sorted_info.at(i); |
|
||||
} |
|
||||
|
|
||||
const ServerMixInfo& MixContext::GetSortedInfo(std::size_t i) const { |
|
||||
ASSERT(i < info_count); |
|
||||
return *sorted_info.at(i); |
|
||||
} |
|
||||
|
|
||||
ServerMixInfo& MixContext::GetFinalMixInfo() { |
|
||||
return infos.at(AudioCommon::FINAL_MIX); |
|
||||
} |
|
||||
|
|
||||
const ServerMixInfo& MixContext::GetFinalMixInfo() const { |
|
||||
return infos.at(AudioCommon::FINAL_MIX); |
|
||||
} |
|
||||
|
|
||||
EdgeMatrix& MixContext::GetEdgeMatrix() { |
|
||||
return edge_matrix; |
|
||||
} |
|
||||
|
|
||||
const EdgeMatrix& MixContext::GetEdgeMatrix() const { |
|
||||
return edge_matrix; |
|
||||
} |
|
||||
|
|
||||
ServerMixInfo::ServerMixInfo() { |
|
||||
Cleanup(); |
|
||||
} |
|
||||
ServerMixInfo::~ServerMixInfo() = default; |
|
||||
|
|
||||
const ServerMixInfo::InParams& ServerMixInfo::GetInParams() const { |
|
||||
return in_params; |
|
||||
} |
|
||||
|
|
||||
ServerMixInfo::InParams& ServerMixInfo::GetInParams() { |
|
||||
return in_params; |
|
||||
} |
|
||||
|
|
||||
bool ServerMixInfo::Update(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix_in, |
|
||||
BehaviorInfo& behavior_info, SplitterContext& splitter_context, |
|
||||
EffectContext& effect_context) { |
|
||||
in_params.volume = mix_in.volume; |
|
||||
in_params.sample_rate = mix_in.sample_rate; |
|
||||
in_params.buffer_count = mix_in.buffer_count; |
|
||||
in_params.in_use = mix_in.in_use; |
|
||||
in_params.mix_id = mix_in.mix_id; |
|
||||
in_params.node_id = mix_in.node_id; |
|
||||
for (std::size_t i = 0; i < mix_in.mix_volume.size(); i++) { |
|
||||
std::copy(mix_in.mix_volume[i].begin(), mix_in.mix_volume[i].end(), |
|
||||
in_params.mix_volume[i].begin()); |
|
||||
} |
|
||||
|
|
||||
bool require_sort = false; |
|
||||
|
|
||||
if (behavior_info.IsSplitterSupported()) { |
|
||||
require_sort = UpdateConnection(edge_matrix, mix_in, splitter_context); |
|
||||
} else { |
|
||||
in_params.dest_mix_id = mix_in.dest_mix_id; |
|
||||
in_params.splitter_id = AudioCommon::NO_SPLITTER; |
|
||||
} |
|
||||
|
|
||||
ResetEffectProcessingOrder(); |
|
||||
const auto effect_count = effect_context.GetCount(); |
|
||||
for (std::size_t i = 0; i < effect_count; i++) { |
|
||||
auto* effect_info = effect_context.GetInfo(i); |
|
||||
if (effect_info->GetMixID() == in_params.mix_id) { |
|
||||
effect_processing_order[effect_info->GetProcessingOrder()] = static_cast<s32>(i); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// TODO(ogniK): Update effect processing order
|
|
||||
return require_sort; |
|
||||
} |
|
||||
|
|
||||
bool ServerMixInfo::HasAnyConnection() const { |
|
||||
return in_params.splitter_id != AudioCommon::NO_SPLITTER || |
|
||||
in_params.mix_id != AudioCommon::NO_MIX; |
|
||||
} |
|
||||
|
|
||||
void ServerMixInfo::Cleanup() { |
|
||||
in_params.volume = 0.0f; |
|
||||
in_params.sample_rate = 0; |
|
||||
in_params.buffer_count = 0; |
|
||||
in_params.in_use = false; |
|
||||
in_params.mix_id = AudioCommon::NO_MIX; |
|
||||
in_params.node_id = 0; |
|
||||
in_params.buffer_offset = 0; |
|
||||
in_params.dest_mix_id = AudioCommon::NO_MIX; |
|
||||
in_params.splitter_id = AudioCommon::NO_SPLITTER; |
|
||||
std::memset(in_params.mix_volume.data(), 0, sizeof(float) * in_params.mix_volume.size()); |
|
||||
} |
|
||||
|
|
||||
void ServerMixInfo::SetEffectCount(std::size_t count) { |
|
||||
effect_processing_order.resize(count); |
|
||||
ResetEffectProcessingOrder(); |
|
||||
} |
|
||||
|
|
||||
void ServerMixInfo::ResetEffectProcessingOrder() { |
|
||||
for (auto& order : effect_processing_order) { |
|
||||
order = AudioCommon::NO_EFFECT_ORDER; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
s32 ServerMixInfo::GetEffectOrder(std::size_t i) const { |
|
||||
return effect_processing_order.at(i); |
|
||||
} |
|
||||
|
|
||||
bool ServerMixInfo::UpdateConnection(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix_in, |
|
||||
SplitterContext& splitter_context) { |
|
||||
// Mixes are identical
|
|
||||
if (in_params.dest_mix_id == mix_in.dest_mix_id && |
|
||||
in_params.splitter_id == mix_in.splitter_id && |
|
||||
((in_params.splitter_id == AudioCommon::NO_SPLITTER) || |
|
||||
!splitter_context.GetInfo(in_params.splitter_id).HasNewConnection())) { |
|
||||
return false; |
|
||||
} |
|
||||
// Remove current edges for mix id
|
|
||||
edge_matrix.RemoveEdges(in_params.mix_id); |
|
||||
if (mix_in.dest_mix_id != AudioCommon::NO_MIX) { |
|
||||
// If we have a valid destination mix id, set our edge matrix
|
|
||||
edge_matrix.Connect(in_params.mix_id, mix_in.dest_mix_id); |
|
||||
} else if (mix_in.splitter_id != AudioCommon::NO_SPLITTER) { |
|
||||
// Recurse our splitter linked and set our edges
|
|
||||
auto& splitter_info = splitter_context.GetInfo(mix_in.splitter_id); |
|
||||
const auto length = splitter_info.GetLength(); |
|
||||
for (s32 i = 0; i < length; i++) { |
|
||||
const auto* splitter_destination = |
|
||||
splitter_context.GetDestinationData(mix_in.splitter_id, i); |
|
||||
if (splitter_destination == nullptr) { |
|
||||
continue; |
|
||||
} |
|
||||
if (splitter_destination->ValidMixId()) { |
|
||||
edge_matrix.Connect(in_params.mix_id, splitter_destination->GetMixId()); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
in_params.dest_mix_id = mix_in.dest_mix_id; |
|
||||
in_params.splitter_id = mix_in.splitter_id; |
|
||||
return true; |
|
||||
} |
|
||||
|
|
||||
} // namespace AudioCore
|
|
||||
@ -1,113 +0,0 @@ |
|||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project |
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later |
|
||||
|
|
||||
#pragma once |
|
||||
|
|
||||
#include <array> |
|
||||
#include <vector> |
|
||||
#include "audio_core/common.h" |
|
||||
#include "audio_core/splitter_context.h" |
|
||||
#include "common/common_funcs.h" |
|
||||
#include "common/common_types.h" |
|
||||
|
|
||||
namespace AudioCore { |
|
||||
class BehaviorInfo; |
|
||||
class EffectContext; |
|
||||
|
|
||||
class MixInfo { |
|
||||
public: |
|
||||
struct DirtyHeader { |
|
||||
u32_le magic{}; |
|
||||
u32_le mixer_count{}; |
|
||||
INSERT_PADDING_BYTES(0x18); |
|
||||
}; |
|
||||
static_assert(sizeof(DirtyHeader) == 0x20, "MixInfo::DirtyHeader is an invalid size"); |
|
||||
|
|
||||
struct InParams { |
|
||||
float_le volume{}; |
|
||||
s32_le sample_rate{}; |
|
||||
s32_le buffer_count{}; |
|
||||
bool in_use{}; |
|
||||
INSERT_PADDING_BYTES(3); |
|
||||
s32_le mix_id{}; |
|
||||
s32_le effect_count{}; |
|
||||
u32_le node_id{}; |
|
||||
INSERT_PADDING_WORDS(2); |
|
||||
std::array<std::array<float_le, AudioCommon::MAX_MIX_BUFFERS>, AudioCommon::MAX_MIX_BUFFERS> |
|
||||
mix_volume{}; |
|
||||
s32_le dest_mix_id{}; |
|
||||
s32_le splitter_id{}; |
|
||||
INSERT_PADDING_WORDS(1); |
|
||||
}; |
|
||||
static_assert(sizeof(MixInfo::InParams) == 0x930, "MixInfo::InParams is an invalid size"); |
|
||||
}; |
|
||||
|
|
||||
class ServerMixInfo { |
|
||||
public: |
|
||||
struct InParams { |
|
||||
float volume{}; |
|
||||
s32 sample_rate{}; |
|
||||
s32 buffer_count{}; |
|
||||
bool in_use{}; |
|
||||
s32 mix_id{}; |
|
||||
u32 node_id{}; |
|
||||
std::array<std::array<float_le, AudioCommon::MAX_MIX_BUFFERS>, AudioCommon::MAX_MIX_BUFFERS> |
|
||||
mix_volume{}; |
|
||||
s32 dest_mix_id{}; |
|
||||
s32 splitter_id{}; |
|
||||
s32 buffer_offset{}; |
|
||||
s32 final_mix_distance{}; |
|
||||
}; |
|
||||
ServerMixInfo(); |
|
||||
~ServerMixInfo(); |
|
||||
|
|
||||
[[nodiscard]] const ServerMixInfo::InParams& GetInParams() const; |
|
||||
[[nodiscard]] ServerMixInfo::InParams& GetInParams(); |
|
||||
|
|
||||
bool Update(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix_in, |
|
||||
BehaviorInfo& behavior_info, SplitterContext& splitter_context, |
|
||||
EffectContext& effect_context); |
|
||||
[[nodiscard]] bool HasAnyConnection() const; |
|
||||
void Cleanup(); |
|
||||
void SetEffectCount(std::size_t count); |
|
||||
void ResetEffectProcessingOrder(); |
|
||||
[[nodiscard]] s32 GetEffectOrder(std::size_t i) const; |
|
||||
|
|
||||
private: |
|
||||
std::vector<s32> effect_processing_order; |
|
||||
InParams in_params{}; |
|
||||
bool UpdateConnection(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix_in, |
|
||||
SplitterContext& splitter_context); |
|
||||
}; |
|
||||
|
|
||||
class MixContext { |
|
||||
public: |
|
||||
MixContext(); |
|
||||
~MixContext(); |
|
||||
|
|
||||
void Initialize(const BehaviorInfo& behavior_info, std::size_t mix_count, |
|
||||
std::size_t effect_count); |
|
||||
void SortInfo(); |
|
||||
bool TsortInfo(SplitterContext& splitter_context); |
|
||||
|
|
||||
[[nodiscard]] std::size_t GetCount() const; |
|
||||
[[nodiscard]] ServerMixInfo& GetInfo(std::size_t i); |
|
||||
[[nodiscard]] const ServerMixInfo& GetInfo(std::size_t i) const; |
|
||||
[[nodiscard]] ServerMixInfo& GetSortedInfo(std::size_t i); |
|
||||
[[nodiscard]] const ServerMixInfo& GetSortedInfo(std::size_t i) const; |
|
||||
[[nodiscard]] ServerMixInfo& GetFinalMixInfo(); |
|
||||
[[nodiscard]] const ServerMixInfo& GetFinalMixInfo() const; |
|
||||
[[nodiscard]] EdgeMatrix& GetEdgeMatrix(); |
|
||||
[[nodiscard]] const EdgeMatrix& GetEdgeMatrix() const; |
|
||||
|
|
||||
private: |
|
||||
void CalcMixBufferOffset(); |
|
||||
void UpdateDistancesFromFinalMix(); |
|
||||
|
|
||||
NodeStates node_states{}; |
|
||||
EdgeMatrix edge_matrix{}; |
|
||||
std::size_t info_count{}; |
|
||||
std::vector<ServerMixInfo> infos{}; |
|
||||
std::vector<ServerMixInfo*> sorted_info{}; |
|
||||
}; |
|
||||
} // namespace AudioCore |
|
||||
@ -1,32 +0,0 @@ |
|||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project |
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later |
|
||||
|
|
||||
#pragma once |
|
||||
|
|
||||
#include "audio_core/sink.h" |
|
||||
|
|
||||
namespace AudioCore { |
|
||||
|
|
||||
class NullSink final : public Sink { |
|
||||
public: |
|
||||
explicit NullSink(std::string_view) {} |
|
||||
~NullSink() override = default; |
|
||||
|
|
||||
SinkStream& AcquireSinkStream(u32 /*sample_rate*/, u32 /*num_channels*/, |
|
||||
const std::string& /*name*/) override { |
|
||||
return null_sink_stream; |
|
||||
} |
|
||||
|
|
||||
private: |
|
||||
struct NullSinkStreamImpl final : SinkStream { |
|
||||
void EnqueueSamples(u32 /*num_channels*/, const std::vector<s16>& /*samples*/) override {} |
|
||||
|
|
||||
std::size_t SamplesInQueue(u32 /*num_channels*/) const override { |
|
||||
return 0; |
|
||||
} |
|
||||
|
|
||||
void Flush() override {} |
|
||||
} null_sink_stream; |
|
||||
}; |
|
||||
|
|
||||
} // namespace AudioCore |
|
||||
@ -0,0 +1,100 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
|
||||
|
#include "audio_core/audio_out_manager.h"
|
||||
|
#include "audio_core/out/audio_out.h"
|
||||
|
#include "core/hle/kernel/k_event.h"
|
||||
|
|
||||
|
namespace AudioCore::AudioOut { |
||||
|
|
||||
|
Out::Out(Core::System& system_, Manager& manager_, Kernel::KEvent* event_, size_t session_id_) |
||||
|
: manager{manager_}, parent_mutex{manager.mutex}, event{event_}, system{system_, event, |
||||
|
session_id_} {} |
||||
|
|
||||
|
void Out::Free() { |
||||
|
std::scoped_lock l{parent_mutex}; |
||||
|
manager.ReleaseSessionId(system.GetSessionId()); |
||||
|
} |
||||
|
|
||||
|
System& Out::GetSystem() { |
||||
|
return system; |
||||
|
} |
||||
|
|
||||
|
AudioOut::State Out::GetState() { |
||||
|
std::scoped_lock l{parent_mutex}; |
||||
|
return system.GetState(); |
||||
|
} |
||||
|
|
||||
|
Result Out::StartSystem() { |
||||
|
std::scoped_lock l{parent_mutex}; |
||||
|
return system.Start(); |
||||
|
} |
||||
|
|
||||
|
void Out::StartSession() { |
||||
|
std::scoped_lock l{parent_mutex}; |
||||
|
system.StartSession(); |
||||
|
} |
||||
|
|
||||
|
Result Out::StopSystem() { |
||||
|
std::scoped_lock l{parent_mutex}; |
||||
|
return system.Stop(); |
||||
|
} |
||||
|
|
||||
|
Result Out::AppendBuffer(const AudioOutBuffer& buffer, const u64 tag) { |
||||
|
std::scoped_lock l{parent_mutex}; |
||||
|
|
||||
|
if (system.AppendBuffer(buffer, tag)) { |
||||
|
return ResultSuccess; |
||||
|
} |
||||
|
return Service::Audio::ERR_BUFFER_COUNT_EXCEEDED; |
||||
|
} |
||||
|
|
||||
|
void Out::ReleaseAndRegisterBuffers() { |
||||
|
std::scoped_lock l{parent_mutex}; |
||||
|
if (system.GetState() == State::Started) { |
||||
|
system.ReleaseBuffers(); |
||||
|
system.RegisterBuffers(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
bool Out::FlushAudioOutBuffers() { |
||||
|
std::scoped_lock l{parent_mutex}; |
||||
|
return system.FlushAudioOutBuffers(); |
||||
|
} |
||||
|
|
||||
|
u32 Out::GetReleasedBuffers(std::span<u64> tags) { |
||||
|
std::scoped_lock l{parent_mutex}; |
||||
|
return system.GetReleasedBuffers(tags); |
||||
|
} |
||||
|
|
||||
|
Kernel::KReadableEvent& Out::GetBufferEvent() { |
||||
|
std::scoped_lock l{parent_mutex}; |
||||
|
return event->GetReadableEvent(); |
||||
|
} |
||||
|
|
||||
|
f32 Out::GetVolume() { |
||||
|
std::scoped_lock l{parent_mutex}; |
||||
|
return system.GetVolume(); |
||||
|
} |
||||
|
|
||||
|
void Out::SetVolume(const f32 volume) { |
||||
|
std::scoped_lock l{parent_mutex}; |
||||
|
system.SetVolume(volume); |
||||
|
} |
||||
|
|
||||
|
bool Out::ContainsAudioBuffer(const u64 tag) { |
||||
|
std::scoped_lock l{parent_mutex}; |
||||
|
return system.ContainsAudioBuffer(tag); |
||||
|
} |
||||
|
|
||||
|
u32 Out::GetBufferCount() { |
||||
|
std::scoped_lock l{parent_mutex}; |
||||
|
return system.GetBufferCount(); |
||||
|
} |
||||
|
|
||||
|
u64 Out::GetPlayedSampleCount() { |
||||
|
std::scoped_lock l{parent_mutex}; |
||||
|
return system.GetPlayedSampleCount(); |
||||
|
} |
||||
|
|
||||
|
} // namespace AudioCore::AudioOut
|
||||
@ -0,0 +1,147 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project |
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <mutex> |
||||
|
|
||||
|
#include "audio_core/out/audio_out_system.h" |
||||
|
|
||||
|
namespace Core { |
||||
|
class System; |
||||
|
} |
||||
|
|
||||
|
namespace Kernel { |
||||
|
class KEvent; |
||||
|
class KReadableEvent; |
||||
|
} // namespace Kernel |
||||
|
|
||||
|
namespace AudioCore::AudioOut { |
||||
|
class Manager; |
||||
|
|
||||
|
/** |
||||
|
* Interface between the service and audio out system. Mainly responsible for forwarding service |
||||
|
* calls to the system. |
||||
|
*/ |
||||
|
class Out { |
||||
|
public: |
||||
|
explicit Out(Core::System& system, Manager& manager, Kernel::KEvent* event, size_t session_id); |
||||
|
|
||||
|
/** |
||||
|
* Free this audio out from the audio out manager. |
||||
|
*/ |
||||
|
void Free(); |
||||
|
|
||||
|
/** |
||||
|
* Get this audio out's system. |
||||
|
*/ |
||||
|
System& GetSystem(); |
||||
|
|
||||
|
/** |
||||
|
* Get the current state. |
||||
|
* |
||||
|
* @return Started or Stopped. |
||||
|
*/ |
||||
|
AudioOut::State GetState(); |
||||
|
|
||||
|
/** |
||||
|
* Start the system |
||||
|
* |
||||
|
* @return Result code |
||||
|
*/ |
||||
|
Result StartSystem(); |
||||
|
|
||||
|
/** |
||||
|
* Start the system's device session. |
||||
|
*/ |
||||
|
void StartSession(); |
||||
|
|
||||
|
/** |
||||
|
* Stop the system. |
||||
|
* |
||||
|
* @return Result code |
||||
|
*/ |
||||
|
Result StopSystem(); |
||||
|
|
||||
|
/** |
||||
|
* Append a new buffer to the system, the buffer event will be signalled when it is filled. |
||||
|
* |
||||
|
* @param buffer - The new buffer to append. |
||||
|
* @param tag - Unique tag for this buffer. |
||||
|
* @return Result code. |
||||
|
*/ |
||||
|
Result AppendBuffer(const AudioOutBuffer& buffer, u64 tag); |
||||
|
|
||||
|
/** |
||||
|
* Release all completed buffers, and register any appended. |
||||
|
*/ |
||||
|
void ReleaseAndRegisterBuffers(); |
||||
|
|
||||
|
/** |
||||
|
* Flush all buffers. |
||||
|
*/ |
||||
|
bool FlushAudioOutBuffers(); |
||||
|
|
||||
|
/** |
||||
|
* Get all of the currently released buffers. |
||||
|
* |
||||
|
* @param tags - Output container for the buffer tags which were released. |
||||
|
* @return The number of buffers released. |
||||
|
*/ |
||||
|
u32 GetReleasedBuffers(std::span<u64> tags); |
||||
|
|
||||
|
/** |
||||
|
* Get the buffer event for this audio out, this event will be signalled when a buffer is |
||||
|
* filled. |
||||
|
* @return The buffer event. |
||||
|
*/ |
||||
|
Kernel::KReadableEvent& GetBufferEvent(); |
||||
|
|
||||
|
/** |
||||
|
* Get the current system volume. |
||||
|
* |
||||
|
* @return The current volume. |
||||
|
*/ |
||||
|
f32 GetVolume(); |
||||
|
|
||||
|
/** |
||||
|
* Set the system volume. |
||||
|
* |
||||
|
* @param volume - The volume to set. |
||||
|
*/ |
||||
|
void SetVolume(f32 volume); |
||||
|
|
||||
|
/** |
||||
|
* Check if a buffer is in the system. |
||||
|
* |
||||
|
* @param tag - The tag to search for. |
||||
|
* @return True if the buffer is in the system, otherwise false. |
||||
|
*/ |
||||
|
bool ContainsAudioBuffer(u64 tag); |
||||
|
|
||||
|
/** |
||||
|
* Get the maximum number of buffers. |
||||
|
* |
||||
|
* @return The maximum number of buffers. |
||||
|
*/ |
||||
|
u32 GetBufferCount(); |
||||
|
|
||||
|
/** |
||||
|
* Get the total played sample count for this audio out. |
||||
|
* |
||||
|
* @return The played sample count. |
||||
|
*/ |
||||
|
u64 GetPlayedSampleCount(); |
||||
|
|
||||
|
private: |
||||
|
/// The AudioOut::Manager this audio out is registered with |
||||
|
Manager& manager; |
||||
|
/// Manager's mutex |
||||
|
std::recursive_mutex& parent_mutex; |
||||
|
/// Buffer event, signalled when buffers are ready to be released |
||||
|
Kernel::KEvent* event; |
||||
|
/// Main audio out system |
||||
|
System system; |
||||
|
}; |
||||
|
|
||||
|
} // namespace AudioCore::AudioOut |
||||
@ -0,0 +1,207 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
|
||||
|
#include <mutex>
|
||||
|
|
||||
|
#include "audio_core/audio_event.h"
|
||||
|
#include "audio_core/audio_manager.h"
|
||||
|
#include "audio_core/out/audio_out_system.h"
|
||||
|
#include "common/logging/log.h"
|
||||
|
#include "core/core.h"
|
||||
|
#include "core/core_timing.h"
|
||||
|
#include "core/hle/kernel/k_event.h"
|
||||
|
|
||||
|
namespace AudioCore::AudioOut { |
||||
|
|
||||
|
System::System(Core::System& system_, Kernel::KEvent* event_, size_t session_id_) |
||||
|
: system{system_}, buffer_event{event_}, |
||||
|
session_id{session_id_}, session{std::make_unique<DeviceSession>(system_)} {} |
||||
|
|
||||
|
System::~System() { |
||||
|
Finalize(); |
||||
|
} |
||||
|
|
||||
|
void System::Finalize() { |
||||
|
Stop(); |
||||
|
session->Finalize(); |
||||
|
buffer_event->GetWritableEvent().Signal(); |
||||
|
} |
||||
|
|
||||
|
std::string_view System::GetDefaultOutputDeviceName() { |
||||
|
return "DeviceOut"; |
||||
|
} |
||||
|
|
||||
|
Result System::IsConfigValid(std::string_view device_name, const AudioOutParameter& in_params) { |
||||
|
if ((device_name.size() > 0) && (device_name != GetDefaultOutputDeviceName())) { |
||||
|
return Service::Audio::ERR_INVALID_DEVICE_NAME; |
||||
|
} |
||||
|
|
||||
|
if (in_params.sample_rate != TargetSampleRate && in_params.sample_rate > 0) { |
||||
|
return Service::Audio::ERR_INVALID_SAMPLE_RATE; |
||||
|
} |
||||
|
|
||||
|
if (in_params.channel_count == 0 || in_params.channel_count == 2 || |
||||
|
in_params.channel_count == 6) { |
||||
|
return ResultSuccess; |
||||
|
} |
||||
|
|
||||
|
return Service::Audio::ERR_INVALID_CHANNEL_COUNT; |
||||
|
} |
||||
|
|
||||
|
Result System::Initialize(std::string& device_name, const AudioOutParameter& in_params, u32 handle_, |
||||
|
u64& applet_resource_user_id_) { |
||||
|
auto result = IsConfigValid(device_name, in_params); |
||||
|
if (result.IsError()) { |
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
handle = handle_; |
||||
|
applet_resource_user_id = applet_resource_user_id_; |
||||
|
if (device_name.empty() || device_name[0] == '\0') { |
||||
|
name = std::string(GetDefaultOutputDeviceName()); |
||||
|
} else { |
||||
|
name = std::move(device_name); |
||||
|
} |
||||
|
|
||||
|
sample_rate = TargetSampleRate; |
||||
|
sample_format = SampleFormat::PcmInt16; |
||||
|
channel_count = in_params.channel_count <= 2 ? 2 : 6; |
||||
|
volume = 1.0f; |
||||
|
return ResultSuccess; |
||||
|
} |
||||
|
|
||||
|
void System::StartSession() { |
||||
|
session->Start(); |
||||
|
} |
||||
|
|
||||
|
size_t System::GetSessionId() const { |
||||
|
return session_id; |
||||
|
} |
||||
|
|
||||
|
Result System::Start() { |
||||
|
if (state != State::Stopped) { |
||||
|
return Service::Audio::ERR_OPERATION_FAILED; |
||||
|
} |
||||
|
|
||||
|
session->Initialize(name, sample_format, channel_count, session_id, handle, |
||||
|
applet_resource_user_id, Sink::StreamType::Out); |
||||
|
session->SetVolume(volume); |
||||
|
session->Start(); |
||||
|
state = State::Started; |
||||
|
|
||||
|
std::vector<AudioBuffer> buffers_to_flush{}; |
||||
|
buffers.RegisterBuffers(buffers_to_flush); |
||||
|
session->AppendBuffers(buffers_to_flush); |
||||
|
|
||||
|
return ResultSuccess; |
||||
|
} |
||||
|
|
||||
|
Result System::Stop() { |
||||
|
if (state == State::Started) { |
||||
|
session->Stop(); |
||||
|
session->SetVolume(0.0f); |
||||
|
state = State::Stopped; |
||||
|
} |
||||
|
|
||||
|
return ResultSuccess; |
||||
|
} |
||||
|
|
||||
|
bool System::AppendBuffer(const AudioOutBuffer& buffer, u64 tag) { |
||||
|
if (buffers.GetTotalBufferCount() == BufferCount) { |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
AudioBuffer new_buffer{ |
||||
|
.played_timestamp = 0, .samples = buffer.samples, .tag = tag, .size = buffer.size}; |
||||
|
|
||||
|
buffers.AppendBuffer(new_buffer); |
||||
|
RegisterBuffers(); |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
void System::RegisterBuffers() { |
||||
|
if (state == State::Started) { |
||||
|
std::vector<AudioBuffer> registered_buffers{}; |
||||
|
buffers.RegisterBuffers(registered_buffers); |
||||
|
session->AppendBuffers(registered_buffers); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void System::ReleaseBuffers() { |
||||
|
bool signal{buffers.ReleaseBuffers(system.CoreTiming(), *session)}; |
||||
|
if (signal) { |
||||
|
// Signal if any buffer was released, or if none are registered, we need more.
|
||||
|
buffer_event->GetWritableEvent().Signal(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
u32 System::GetReleasedBuffers(std::span<u64> tags) { |
||||
|
return buffers.GetReleasedBuffers(tags); |
||||
|
} |
||||
|
|
||||
|
bool System::FlushAudioOutBuffers() { |
||||
|
if (state != State::Started) { |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
u32 buffers_released{}; |
||||
|
buffers.FlushBuffers(buffers_released); |
||||
|
|
||||
|
if (buffers_released > 0) { |
||||
|
buffer_event->GetWritableEvent().Signal(); |
||||
|
} |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
u16 System::GetChannelCount() const { |
||||
|
return channel_count; |
||||
|
} |
||||
|
|
||||
|
u32 System::GetSampleRate() const { |
||||
|
return sample_rate; |
||||
|
} |
||||
|
|
||||
|
SampleFormat System::GetSampleFormat() const { |
||||
|
return sample_format; |
||||
|
} |
||||
|
|
||||
|
State System::GetState() { |
||||
|
switch (state) { |
||||
|
case State::Started: |
||||
|
case State::Stopped: |
||||
|
return state; |
||||
|
default: |
||||
|
LOG_ERROR(Service_Audio, "AudioOut invalid state!"); |
||||
|
state = State::Stopped; |
||||
|
break; |
||||
|
} |
||||
|
return state; |
||||
|
} |
||||
|
|
||||
|
std::string System::GetName() const { |
||||
|
return name; |
||||
|
} |
||||
|
|
||||
|
f32 System::GetVolume() const { |
||||
|
return volume; |
||||
|
} |
||||
|
|
||||
|
void System::SetVolume(const f32 volume_) { |
||||
|
volume = volume_; |
||||
|
session->SetVolume(volume_); |
||||
|
} |
||||
|
|
||||
|
bool System::ContainsAudioBuffer(const u64 tag) { |
||||
|
return buffers.ContainsBuffer(tag); |
||||
|
} |
||||
|
|
||||
|
u32 System::GetBufferCount() { |
||||
|
return buffers.GetAppendedRegisteredCount(); |
||||
|
} |
||||
|
|
||||
|
u64 System::GetPlayedSampleCount() const { |
||||
|
return session->GetPlayedSampleCount(); |
||||
|
} |
||||
|
|
||||
|
} // namespace AudioCore::AudioOut
|
||||
@ -0,0 +1,257 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project |
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <atomic> |
||||
|
#include <memory> |
||||
|
#include <span> |
||||
|
#include <string> |
||||
|
|
||||
|
#include "audio_core/common/common.h" |
||||
|
#include "audio_core/device/audio_buffers.h" |
||||
|
#include "audio_core/device/device_session.h" |
||||
|
#include "core/hle/service/audio/errors.h" |
||||
|
|
||||
|
namespace Core { |
||||
|
class System; |
||||
|
} |
||||
|
|
||||
|
namespace Kernel { |
||||
|
class KEvent; |
||||
|
} |
||||
|
|
||||
|
namespace AudioCore::AudioOut { |
||||
|
|
||||
|
constexpr SessionTypes SessionType = SessionTypes::AudioOut; |
||||
|
|
||||
|
struct AudioOutParameter { |
||||
|
/* 0x0 */ s32_le sample_rate; |
||||
|
/* 0x4 */ u16_le channel_count; |
||||
|
/* 0x6 */ u16_le reserved; |
||||
|
}; |
||||
|
static_assert(sizeof(AudioOutParameter) == 0x8, "AudioOutParameter is an invalid size"); |
||||
|
|
||||
|
struct AudioOutParameterInternal { |
||||
|
/* 0x0 */ u32_le sample_rate; |
||||
|
/* 0x4 */ u32_le channel_count; |
||||
|
/* 0x8 */ u32_le sample_format; |
||||
|
/* 0xC */ u32_le state; |
||||
|
}; |
||||
|
static_assert(sizeof(AudioOutParameterInternal) == 0x10, |
||||
|
"AudioOutParameterInternal is an invalid size"); |
||||
|
|
||||
|
struct AudioOutBuffer { |
||||
|
/* 0x00 */ AudioOutBuffer* next; |
||||
|
/* 0x08 */ VAddr samples; |
||||
|
/* 0x10 */ u64 capacity; |
||||
|
/* 0x18 */ u64 size; |
||||
|
/* 0x20 */ u64 offset; |
||||
|
}; |
||||
|
static_assert(sizeof(AudioOutBuffer) == 0x28, "AudioOutBuffer is an invalid size"); |
||||
|
|
||||
|
enum class State { |
||||
|
Started, |
||||
|
Stopped, |
||||
|
}; |
||||
|
|
||||
|
/** |
||||
|
* Controls and drives audio output. |
||||
|
*/ |
||||
|
class System { |
||||
|
public: |
||||
|
explicit System(Core::System& system, Kernel::KEvent* event, size_t session_id); |
||||
|
~System(); |
||||
|
|
||||
|
/** |
||||
|
* Get the default audio output device name. |
||||
|
* |
||||
|
* @return The default audio output device name. |
||||
|
*/ |
||||
|
std::string_view GetDefaultOutputDeviceName(); |
||||
|
|
||||
|
/** |
||||
|
* Is the given initialize config valid? |
||||
|
* |
||||
|
* @param device_name - The name of the requested output device. |
||||
|
* @param in_params - Input parameters, see AudioOutParameter. |
||||
|
* @return Result code. |
||||
|
*/ |
||||
|
Result IsConfigValid(std::string_view device_name, const AudioOutParameter& in_params); |
||||
|
|
||||
|
/** |
||||
|
* Initialize this system. |
||||
|
* |
||||
|
* @param device_name - The name of the requested output device. |
||||
|
* @param in_params - Input parameters, see AudioOutParameter. |
||||
|
* @param handle - Unused. |
||||
|
* @param applet_resource_user_id - Unused. |
||||
|
* @return Result code. |
||||
|
*/ |
||||
|
Result Initialize(std::string& device_name, const AudioOutParameter& in_params, u32 handle, |
||||
|
u64& applet_resource_user_id); |
||||
|
|
||||
|
/** |
||||
|
* Start this system. |
||||
|
* |
||||
|
* @return Result code. |
||||
|
*/ |
||||
|
Result Start(); |
||||
|
|
||||
|
/** |
||||
|
* Stop this system. |
||||
|
* |
||||
|
* @return Result code. |
||||
|
*/ |
||||
|
Result Stop(); |
||||
|
|
||||
|
/** |
||||
|
* Finalize this system. |
||||
|
*/ |
||||
|
void Finalize(); |
||||
|
|
||||
|
/** |
||||
|
* Start this system's device session. |
||||
|
*/ |
||||
|
void StartSession(); |
||||
|
|
||||
|
/** |
||||
|
* Get this system's id. |
||||
|
*/ |
||||
|
size_t GetSessionId() const; |
||||
|
|
||||
|
/** |
||||
|
* Append a new buffer to the device. |
||||
|
* |
||||
|
* @param buffer - New buffer to append. |
||||
|
* @param tag - Unique tag of the buffer. |
||||
|
* @return True if the buffer was appended, otherwise false. |
||||
|
*/ |
||||
|
bool AppendBuffer(const AudioOutBuffer& buffer, u64 tag); |
||||
|
|
||||
|
/** |
||||
|
* Register all appended buffers. |
||||
|
*/ |
||||
|
void RegisterBuffers(); |
||||
|
|
||||
|
/** |
||||
|
* Release all registered buffers. |
||||
|
*/ |
||||
|
void ReleaseBuffers(); |
||||
|
|
||||
|
/** |
||||
|
* Get all released buffers. |
||||
|
* |
||||
|
* @param tags - Container to be filled with the released buffers' tags. |
||||
|
* @return The number of buffers released. |
||||
|
*/ |
||||
|
u32 GetReleasedBuffers(std::span<u64> tags); |
||||
|
|
||||
|
/** |
||||
|
* Flush all appended and registered buffers. |
||||
|
* |
||||
|
* @return True if buffers were successfully flushed, otherwise false. |
||||
|
*/ |
||||
|
bool FlushAudioOutBuffers(); |
||||
|
|
||||
|
/** |
||||
|
* Get this system's current channel count. |
||||
|
* |
||||
|
* @return The channel count. |
||||
|
*/ |
||||
|
u16 GetChannelCount() const; |
||||
|
|
||||
|
/** |
||||
|
* Get this system's current sample rate. |
||||
|
* |
||||
|
* @return The sample rate. |
||||
|
*/ |
||||
|
u32 GetSampleRate() const; |
||||
|
|
||||
|
/** |
||||
|
* Get this system's current sample format. |
||||
|
* |
||||
|
* @return The sample format. |
||||
|
*/ |
||||
|
SampleFormat GetSampleFormat() const; |
||||
|
|
||||
|
/** |
||||
|
* Get this system's current state. |
||||
|
* |
||||
|
* @return The current state. |
||||
|
*/ |
||||
|
State GetState(); |
||||
|
|
||||
|
/** |
||||
|
* Get this system's name. |
||||
|
* |
||||
|
* @return The system's name. |
||||
|
*/ |
||||
|
std::string GetName() const; |
||||
|
|
||||
|
/** |
||||
|
* Get this system's current volume. |
||||
|
* |
||||
|
* @return The system's current volume. |
||||
|
*/ |
||||
|
f32 GetVolume() const; |
||||
|
|
||||
|
/** |
||||
|
* Set this system's current volume. |
||||
|
* |
||||
|
* @param The new volume. |
||||
|
*/ |
||||
|
void SetVolume(f32 volume); |
||||
|
|
||||
|
/** |
||||
|
* Does the system contain this buffer? |
||||
|
* |
||||
|
* @param tag - Unique tag to search for. |
||||
|
* @return True if the buffer is in the system, otherwise false. |
||||
|
*/ |
||||
|
bool ContainsAudioBuffer(u64 tag); |
||||
|
|
||||
|
/** |
||||
|
* Get the maximum number of usable buffers (default 32). |
||||
|
* |
||||
|
* @return The number of buffers. |
||||
|
*/ |
||||
|
u32 GetBufferCount(); |
||||
|
|
||||
|
/** |
||||
|
* Get the total number of samples played by this system. |
||||
|
* |
||||
|
* @return The number of samples. |
||||
|
*/ |
||||
|
u64 GetPlayedSampleCount() const; |
||||
|
|
||||
|
private: |
||||
|
/// Core system |
||||
|
Core::System& system; |
||||
|
/// (Unused) |
||||
|
u32 handle{}; |
||||
|
/// (Unused) |
||||
|
u64 applet_resource_user_id{}; |
||||
|
/// Buffer event, signalled when a buffer is ready |
||||
|
Kernel::KEvent* buffer_event; |
||||
|
/// Session id of this system |
||||
|
size_t session_id{}; |
||||
|
/// Device session for this system |
||||
|
std::unique_ptr<DeviceSession> session; |
||||
|
/// Audio buffers in use by this system |
||||
|
AudioBuffers<BufferCount> buffers{BufferCount}; |
||||
|
/// Sample rate of this system |
||||
|
u32 sample_rate{}; |
||||
|
/// Sample format of this system |
||||
|
SampleFormat sample_format{SampleFormat::PcmInt16}; |
||||
|
/// Channel count of this system |
||||
|
u16 channel_count{}; |
||||
|
/// State of this system |
||||
|
std::atomic<State> state{State::Stopped}; |
||||
|
/// Name of this system |
||||
|
std::string name{}; |
||||
|
/// Volume of this system |
||||
|
f32 volume{1.0f}; |
||||
|
}; |
||||
|
|
||||
|
} // namespace AudioCore::AudioOut |
||||
@ -0,0 +1,118 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
|
||||
|
#include "audio_core/renderer/adsp/adsp.h"
|
||||
|
#include "audio_core/renderer/adsp/command_buffer.h"
|
||||
|
#include "audio_core/sink/sink.h"
|
||||
|
#include "common/logging/log.h"
|
||||
|
#include "core/core.h"
|
||||
|
#include "core/core_timing.h"
|
||||
|
#include "core/core_timing_util.h"
|
||||
|
#include "core/memory.h"
|
||||
|
|
||||
|
namespace AudioCore::AudioRenderer::ADSP { |
||||
|
|
||||
|
ADSP::ADSP(Core::System& system_, Sink::Sink& sink_) |
||||
|
: system{system_}, memory{system.Memory()}, sink{sink_} {} |
||||
|
|
||||
|
ADSP::~ADSP() { |
||||
|
ClearCommandBuffers(); |
||||
|
} |
||||
|
|
||||
|
State ADSP::GetState() const { |
||||
|
if (running) { |
||||
|
return State::Started; |
||||
|
} |
||||
|
return State::Stopped; |
||||
|
} |
||||
|
|
||||
|
AudioRenderer_Mailbox* ADSP::GetRenderMailbox() { |
||||
|
return &render_mailbox; |
||||
|
} |
||||
|
|
||||
|
void ADSP::ClearRemainCount(const u32 session_id) { |
||||
|
render_mailbox.ClearRemainCount(session_id); |
||||
|
} |
||||
|
|
||||
|
u64 ADSP::GetSignalledTick() const { |
||||
|
return render_mailbox.GetSignalledTick(); |
||||
|
} |
||||
|
|
||||
|
u64 ADSP::GetTimeTaken() const { |
||||
|
return render_mailbox.GetRenderTimeTaken(); |
||||
|
} |
||||
|
|
||||
|
u64 ADSP::GetRenderTimeTaken(const u32 session_id) { |
||||
|
return render_mailbox.GetCommandBuffer(session_id).render_time_taken; |
||||
|
} |
||||
|
|
||||
|
u32 ADSP::GetRemainCommandCount(const u32 session_id) const { |
||||
|
return render_mailbox.GetRemainCommandCount(session_id); |
||||
|
} |
||||
|
|
||||
|
void ADSP::SendCommandBuffer(const u32 session_id, CommandBuffer& command_buffer) { |
||||
|
render_mailbox.SetCommandBuffer(session_id, command_buffer); |
||||
|
} |
||||
|
|
||||
|
u64 ADSP::GetRenderingStartTick(const u32 session_id) { |
||||
|
return render_mailbox.GetSignalledTick() + |
||||
|
render_mailbox.GetCommandBuffer(session_id).render_time_taken; |
||||
|
} |
||||
|
|
||||
|
bool ADSP::Start() { |
||||
|
if (running) { |
||||
|
return running; |
||||
|
} |
||||
|
|
||||
|
running = true; |
||||
|
systems_active++; |
||||
|
audio_renderer = std::make_unique<AudioRenderer>(system); |
||||
|
audio_renderer->Start(&render_mailbox); |
||||
|
render_mailbox.HostSendMessage(RenderMessage::AudioRenderer_InitializeOK); |
||||
|
if (render_mailbox.HostWaitMessage() != RenderMessage::AudioRenderer_InitializeOK) { |
||||
|
LOG_ERROR( |
||||
|
Service_Audio, |
||||
|
"Host Audio Renderer -- Failed to receive initialize message response from ADSP!"); |
||||
|
} |
||||
|
return running; |
||||
|
} |
||||
|
|
||||
|
void ADSP::Stop() { |
||||
|
systems_active--; |
||||
|
if (running && systems_active == 0) { |
||||
|
{ |
||||
|
std::scoped_lock l{mailbox_lock}; |
||||
|
render_mailbox.HostSendMessage(RenderMessage::AudioRenderer_Shutdown); |
||||
|
if (render_mailbox.HostWaitMessage() != RenderMessage::AudioRenderer_Shutdown) { |
||||
|
LOG_ERROR(Service_Audio, "Host Audio Renderer -- Failed to receive shutdown " |
||||
|
"message response from ADSP!"); |
||||
|
} |
||||
|
} |
||||
|
audio_renderer->Stop(); |
||||
|
running = false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void ADSP::Signal() { |
||||
|
const auto signalled_tick{system.CoreTiming().GetClockTicks()}; |
||||
|
render_mailbox.SetSignalledTick(signalled_tick); |
||||
|
render_mailbox.HostSendMessage(RenderMessage::AudioRenderer_Render); |
||||
|
} |
||||
|
|
||||
|
void ADSP::Wait() { |
||||
|
std::scoped_lock l{mailbox_lock}; |
||||
|
auto response{render_mailbox.HostWaitMessage()}; |
||||
|
if (response != RenderMessage::AudioRenderer_RenderResponse) { |
||||
|
LOG_ERROR(Service_Audio, "Invalid ADSP response message, expected 0x{:02X}, got 0x{:02X}", |
||||
|
static_cast<u32>(RenderMessage::AudioRenderer_RenderResponse), |
||||
|
static_cast<u32>(response)); |
||||
|
} |
||||
|
|
||||
|
ClearCommandBuffers(); |
||||
|
} |
||||
|
|
||||
|
void ADSP::ClearCommandBuffers() { |
||||
|
render_mailbox.ClearCommandBuffers(); |
||||
|
} |
||||
|
|
||||
|
} // namespace AudioCore::AudioRenderer::ADSP
|
||||
@ -0,0 +1,173 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project |
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <memory> |
||||
|
#include <mutex> |
||||
|
|
||||
|
#include "audio_core/renderer/adsp/audio_renderer.h" |
||||
|
#include "common/common_types.h" |
||||
|
|
||||
|
namespace Core { |
||||
|
namespace Memory { |
||||
|
class Memory; |
||||
|
} |
||||
|
class System; |
||||
|
} // namespace Core |
||||
|
|
||||
|
namespace AudioCore { |
||||
|
namespace Sink { |
||||
|
class Sink; |
||||
|
} |
||||
|
|
||||
|
namespace AudioRenderer::ADSP { |
||||
|
struct CommandBuffer; |
||||
|
|
||||
|
enum class State { |
||||
|
Started, |
||||
|
Stopped, |
||||
|
}; |
||||
|
|
||||
|
/** |
||||
|
* Represents the ADSP embedded within the audio sysmodule. |
||||
|
* This is a 32-bit Linux4Tegra kernel from nVidia, which is launched with the sysmodule on boot. |
||||
|
* |
||||
|
* The kernel will run apps you program for it, Nintendo have the following: |
||||
|
* |
||||
|
* Gmix - Responsible for mixing final audio and sending it out to hardware. This is last place all |
||||
|
* audio samples end up, and we skip it entirely, since we have very different backends and |
||||
|
* mixing is implicitly handled by the OS (but also due to lack of research/simplicity). |
||||
|
* |
||||
|
* AudioRenderer - Receives command lists generated by the audio render |
||||
|
* system, processes them, and sends the samples to Gmix. |
||||
|
* |
||||
|
* OpusDecoder - Contains libopus, and controls processing Opus audio and sends it to Gmix. |
||||
|
* Not much research done here, TODO if needed. |
||||
|
* |
||||
|
* We only implement the AudioRenderer for now. |
||||
|
* |
||||
|
* Communication for the apps is done through mailboxes, and some shared memory. |
||||
|
*/ |
||||
|
class ADSP { |
||||
|
public: |
||||
|
explicit ADSP(Core::System& system, Sink::Sink& sink); |
||||
|
~ADSP(); |
||||
|
|
||||
|
/** |
||||
|
* Start the ADSP. |
||||
|
* |
||||
|
* @return True if started or already running, otherwise false. |
||||
|
*/ |
||||
|
bool Start(); |
||||
|
|
||||
|
/** |
||||
|
* Stop the ADSP. |
||||
|
* |
||||
|
* @return True if started or already running, otherwise false. |
||||
|
*/ |
||||
|
void Stop(); |
||||
|
|
||||
|
/** |
||||
|
* Get the ADSP's state. |
||||
|
* |
||||
|
* @return Started or Stopped. |
||||
|
*/ |
||||
|
State GetState() const; |
||||
|
|
||||
|
/** |
||||
|
* Get the AudioRenderer mailbox to communicate with it. |
||||
|
* |
||||
|
* @return The AudioRenderer mailbox. |
||||
|
*/ |
||||
|
AudioRenderer_Mailbox* GetRenderMailbox(); |
||||
|
|
||||
|
/** |
||||
|
* Get the tick the ADSP was signalled. |
||||
|
* |
||||
|
* @return The tick the ADSP was signalled. |
||||
|
*/ |
||||
|
u64 GetSignalledTick() const; |
||||
|
|
||||
|
/** |
||||
|
* Get the total time it took for the ADSP to run the last command lists (both command lists). |
||||
|
* |
||||
|
* @return The tick the ADSP was signalled. |
||||
|
*/ |
||||
|
u64 GetTimeTaken() const; |
||||
|
|
||||
|
/** |
||||
|
* Get the last time a given command list took to run. |
||||
|
* |
||||
|
* @param session_id - The session id to check (0 or 1). |
||||
|
* @return The time it took. |
||||
|
*/ |
||||
|
u64 GetRenderTimeTaken(u32 session_id); |
||||
|
|
||||
|
/** |
||||
|
* Clear the remaining command count for a given session. |
||||
|
* |
||||
|
* @param session_id - The session id to check (0 or 1). |
||||
|
*/ |
||||
|
void ClearRemainCount(u32 session_id); |
||||
|
|
||||
|
/** |
||||
|
* Get the remaining number of commands left to process for a command list. |
||||
|
* |
||||
|
* @param session_id - The session id to check (0 or 1). |
||||
|
* @return The number of commands remaining. |
||||
|
*/ |
||||
|
u32 GetRemainCommandCount(u32 session_id) const; |
||||
|
|
||||
|
/** |
||||
|
* Get the last tick a command list started processing. |
||||
|
* |
||||
|
* @param session_id - The session id to check (0 or 1). |
||||
|
* @return The last tick the given command list started. |
||||
|
*/ |
||||
|
u64 GetRenderingStartTick(u32 session_id); |
||||
|
|
||||
|
/** |
||||
|
* Set a command buffer to be processed. |
||||
|
* |
||||
|
* @param session_id - The session id to check (0 or 1). |
||||
|
* @param command_buffer - The command buffer to process. |
||||
|
*/ |
||||
|
void SendCommandBuffer(u32 session_id, CommandBuffer& command_buffer); |
||||
|
|
||||
|
/** |
||||
|
* Clear the command buffers (does not clear the time taken or the remaining command count) |
||||
|
*/ |
||||
|
void ClearCommandBuffers(); |
||||
|
|
||||
|
/** |
||||
|
* Signal the AudioRenderer to begin processing. |
||||
|
*/ |
||||
|
void Signal(); |
||||
|
|
||||
|
/** |
||||
|
* Wait for the AudioRenderer to finish processing. |
||||
|
*/ |
||||
|
void Wait(); |
||||
|
|
||||
|
private: |
||||
|
/// Core system |
||||
|
Core::System& system; |
||||
|
/// Core memory |
||||
|
Core::Memory::Memory& memory; |
||||
|
/// Number of systems active, used to prevent accidental shutdowns |
||||
|
u8 systems_active{0}; |
||||
|
/// ADSP running state |
||||
|
std::atomic<bool> running{false}; |
||||
|
/// Output sink used by the ADSP |
||||
|
Sink::Sink& sink; |
||||
|
/// AudioRenderer app |
||||
|
std::unique_ptr<AudioRenderer> audio_renderer{}; |
||||
|
/// Communication for the AudioRenderer |
||||
|
AudioRenderer_Mailbox render_mailbox{}; |
||||
|
/// Mailbox lock ffor the render mailbox |
||||
|
std::mutex mailbox_lock; |
||||
|
}; |
||||
|
|
||||
|
} // namespace AudioRenderer::ADSP |
||||
|
} // namespace AudioCore |
||||
@ -0,0 +1,226 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
|
||||
|
#include <array>
|
||||
|
#include <chrono>
|
||||
|
|
||||
|
#include "audio_core/audio_core.h"
|
||||
|
#include "audio_core/common/common.h"
|
||||
|
#include "audio_core/renderer/adsp/audio_renderer.h"
|
||||
|
#include "audio_core/sink/sink.h"
|
||||
|
#include "common/logging/log.h"
|
||||
|
#include "common/microprofile.h"
|
||||
|
#include "common/thread.h"
|
||||
|
#include "core/core.h"
|
||||
|
#include "core/core_timing.h"
|
||||
|
#include "core/core_timing_util.h"
|
||||
|
|
||||
|
MICROPROFILE_DEFINE(Audio_Renderer, "Audio", "DSP", MP_RGB(60, 19, 97)); |
||||
|
|
||||
|
namespace AudioCore::AudioRenderer::ADSP { |
||||
|
|
||||
|
void AudioRenderer_Mailbox::HostSendMessage(RenderMessage message_) { |
||||
|
adsp_messages.enqueue(message_); |
||||
|
adsp_event.Set(); |
||||
|
} |
||||
|
|
||||
|
RenderMessage AudioRenderer_Mailbox::HostWaitMessage() { |
||||
|
host_event.Wait(); |
||||
|
RenderMessage msg{RenderMessage::Invalid}; |
||||
|
if (!host_messages.try_dequeue(msg)) { |
||||
|
LOG_ERROR(Service_Audio, "Failed to dequeue host message!"); |
||||
|
} |
||||
|
return msg; |
||||
|
} |
||||
|
|
||||
|
void AudioRenderer_Mailbox::ADSPSendMessage(const RenderMessage message_) { |
||||
|
host_messages.enqueue(message_); |
||||
|
host_event.Set(); |
||||
|
} |
||||
|
|
||||
|
RenderMessage AudioRenderer_Mailbox::ADSPWaitMessage() { |
||||
|
adsp_event.Wait(); |
||||
|
RenderMessage msg{RenderMessage::Invalid}; |
||||
|
if (!adsp_messages.try_dequeue(msg)) { |
||||
|
LOG_ERROR(Service_Audio, "Failed to dequeue ADSP message!"); |
||||
|
} |
||||
|
return msg; |
||||
|
} |
||||
|
|
||||
|
CommandBuffer& AudioRenderer_Mailbox::GetCommandBuffer(const s32 session_id) { |
||||
|
return command_buffers[session_id]; |
||||
|
} |
||||
|
|
||||
|
void AudioRenderer_Mailbox::SetCommandBuffer(const u32 session_id, CommandBuffer& buffer) { |
||||
|
command_buffers[session_id] = buffer; |
||||
|
} |
||||
|
|
||||
|
u64 AudioRenderer_Mailbox::GetRenderTimeTaken() const { |
||||
|
return command_buffers[0].render_time_taken + command_buffers[1].render_time_taken; |
||||
|
} |
||||
|
|
||||
|
u64 AudioRenderer_Mailbox::GetSignalledTick() const { |
||||
|
return signalled_tick; |
||||
|
} |
||||
|
|
||||
|
void AudioRenderer_Mailbox::SetSignalledTick(const u64 tick) { |
||||
|
signalled_tick = tick; |
||||
|
} |
||||
|
|
||||
|
void AudioRenderer_Mailbox::ClearRemainCount(const u32 session_id) { |
||||
|
command_buffers[session_id].remaining_command_count = 0; |
||||
|
} |
||||
|
|
||||
|
u32 AudioRenderer_Mailbox::GetRemainCommandCount(const u32 session_id) const { |
||||
|
return command_buffers[session_id].remaining_command_count; |
||||
|
} |
||||
|
|
||||
|
void AudioRenderer_Mailbox::ClearCommandBuffers() { |
||||
|
command_buffers[0].buffer = 0; |
||||
|
command_buffers[0].size = 0; |
||||
|
command_buffers[0].reset_buffers = false; |
||||
|
command_buffers[1].buffer = 0; |
||||
|
command_buffers[1].size = 0; |
||||
|
command_buffers[1].reset_buffers = false; |
||||
|
} |
||||
|
|
||||
|
AudioRenderer::AudioRenderer(Core::System& system_) |
||||
|
: system{system_}, sink{system.AudioCore().GetOutputSink()} { |
||||
|
CreateSinkStreams(); |
||||
|
} |
||||
|
|
||||
|
AudioRenderer::~AudioRenderer() { |
||||
|
Stop(); |
||||
|
for (auto& stream : streams) { |
||||
|
if (stream) { |
||||
|
sink.CloseStream(stream); |
||||
|
} |
||||
|
stream = nullptr; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void AudioRenderer::Start(AudioRenderer_Mailbox* mailbox_) { |
||||
|
if (running) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
mailbox = mailbox_; |
||||
|
thread = std::thread(&AudioRenderer::ThreadFunc, this); |
||||
|
for (auto& stream : streams) { |
||||
|
stream->Start(); |
||||
|
} |
||||
|
running = true; |
||||
|
} |
||||
|
|
||||
|
void AudioRenderer::Stop() { |
||||
|
if (!running) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
for (auto& stream : streams) { |
||||
|
stream->Stop(); |
||||
|
} |
||||
|
thread.join(); |
||||
|
running = false; |
||||
|
} |
||||
|
|
||||
|
void AudioRenderer::CreateSinkStreams() { |
||||
|
u32 channels{sink.GetDeviceChannels()}; |
||||
|
for (u32 i = 0; i < MaxRendererSessions; i++) { |
||||
|
std::string name{fmt::format("ADSP_RenderStream-{}", i)}; |
||||
|
streams[i] = |
||||
|
sink.AcquireSinkStream(system, channels, name, ::AudioCore::Sink::StreamType::Render); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void AudioRenderer::ThreadFunc() { |
||||
|
constexpr char name[]{"yuzu:AudioRenderer"}; |
||||
|
MicroProfileOnThreadCreate(name); |
||||
|
Common::SetCurrentThreadName(name); |
||||
|
Common::SetCurrentThreadPriority(Common::ThreadPriority::Critical); |
||||
|
if (mailbox->ADSPWaitMessage() != RenderMessage::AudioRenderer_InitializeOK) { |
||||
|
LOG_ERROR(Service_Audio, |
||||
|
"ADSP Audio Renderer -- Failed to receive initialize message from host!"); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
mailbox->ADSPSendMessage(RenderMessage::AudioRenderer_InitializeOK); |
||||
|
|
||||
|
constexpr u64 max_process_time{2'304'000ULL}; |
||||
|
|
||||
|
while (true) { |
||||
|
auto message{mailbox->ADSPWaitMessage()}; |
||||
|
switch (message) { |
||||
|
case RenderMessage::AudioRenderer_Shutdown: |
||||
|
mailbox->ADSPSendMessage(RenderMessage::AudioRenderer_Shutdown); |
||||
|
return; |
||||
|
|
||||
|
case RenderMessage::AudioRenderer_Render: { |
||||
|
std::array<bool, MaxRendererSessions> buffers_reset{}; |
||||
|
std::array<u64, MaxRendererSessions> render_times_taken{}; |
||||
|
const auto start_time{system.CoreTiming().GetClockTicks()}; |
||||
|
|
||||
|
for (u32 index = 0; index < 2; index++) { |
||||
|
auto& command_buffer{mailbox->GetCommandBuffer(index)}; |
||||
|
auto& command_list_processor{command_list_processors[index]}; |
||||
|
|
||||
|
// Check this buffer is valid, as it may not be used.
|
||||
|
if (command_buffer.buffer != 0) { |
||||
|
// If there are no remaining commands (from the previous list),
|
||||
|
// this is a new command list, initalize it.
|
||||
|
if (command_buffer.remaining_command_count == 0) { |
||||
|
command_list_processor.Initialize(system, command_buffer.buffer, |
||||
|
command_buffer.size, streams[index]); |
||||
|
} |
||||
|
|
||||
|
if (command_buffer.reset_buffers && !buffers_reset[index]) { |
||||
|
streams[index]->ClearQueue(); |
||||
|
buffers_reset[index] = true; |
||||
|
} |
||||
|
|
||||
|
u64 max_time{max_process_time}; |
||||
|
if (index == 1 && command_buffer.applet_resource_user_id == |
||||
|
mailbox->GetCommandBuffer(0).applet_resource_user_id) { |
||||
|
max_time = max_process_time - |
||||
|
Core::Timing::CyclesToNs(render_times_taken[0]).count(); |
||||
|
if (render_times_taken[0] > max_process_time) { |
||||
|
max_time = 0; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
max_time = std::min(command_buffer.time_limit, max_time); |
||||
|
command_list_processor.SetProcessTimeMax(max_time); |
||||
|
|
||||
|
// Process the command list
|
||||
|
{ |
||||
|
MICROPROFILE_SCOPE(Audio_Renderer); |
||||
|
render_times_taken[index] = |
||||
|
command_list_processor.Process(index) - start_time; |
||||
|
} |
||||
|
|
||||
|
if (index == 0) { |
||||
|
auto stream{command_list_processor.GetOutputSinkStream()}; |
||||
|
system.AudioCore().SetStreamQueue(stream->GetQueueSize()); |
||||
|
} |
||||
|
|
||||
|
const auto end_time{system.CoreTiming().GetClockTicks()}; |
||||
|
|
||||
|
command_buffer.remaining_command_count = |
||||
|
command_list_processor.GetRemainingCommandCount(); |
||||
|
command_buffer.render_time_taken = end_time - start_time; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
mailbox->ADSPSendMessage(RenderMessage::AudioRenderer_RenderResponse); |
||||
|
} break; |
||||
|
|
||||
|
default: |
||||
|
LOG_WARNING(Service_Audio, |
||||
|
"ADSP AudioRenderer received an invalid message, msg={:02X}!", |
||||
|
static_cast<u32>(message)); |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} // namespace AudioCore::AudioRenderer::ADSP
|
||||
@ -0,0 +1,203 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project |
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <array> |
||||
|
#include <memory> |
||||
|
#include <thread> |
||||
|
|
||||
|
#include "audio_core/renderer/adsp/command_buffer.h" |
||||
|
#include "audio_core/renderer/adsp/command_list_processor.h" |
||||
|
#include "common/common_types.h" |
||||
|
#include "common/reader_writer_queue.h" |
||||
|
#include "common/thread.h" |
||||
|
|
||||
|
namespace Core { |
||||
|
namespace Timing { |
||||
|
struct EventType; |
||||
|
} |
||||
|
class System; |
||||
|
} // namespace Core |
||||
|
|
||||
|
namespace AudioCore { |
||||
|
namespace Sink { |
||||
|
class Sink; |
||||
|
} |
||||
|
|
||||
|
namespace AudioRenderer::ADSP { |
||||
|
|
||||
|
enum class RenderMessage { |
||||
|
/* 0x00 */ Invalid, |
||||
|
/* 0x01 */ AudioRenderer_MapUnmap_Map, |
||||
|
/* 0x02 */ AudioRenderer_MapUnmap_MapResponse, |
||||
|
/* 0x03 */ AudioRenderer_MapUnmap_Unmap, |
||||
|
/* 0x04 */ AudioRenderer_MapUnmap_UnmapResponse, |
||||
|
/* 0x05 */ AudioRenderer_MapUnmap_InvalidateCache, |
||||
|
/* 0x06 */ AudioRenderer_MapUnmap_InvalidateCacheResponse, |
||||
|
/* 0x07 */ AudioRenderer_MapUnmap_Shutdown, |
||||
|
/* 0x08 */ AudioRenderer_MapUnmap_ShutdownResponse, |
||||
|
/* 0x16 */ AudioRenderer_InitializeOK = 0x16, |
||||
|
/* 0x20 */ AudioRenderer_RenderResponse = 0x20, |
||||
|
/* 0x2A */ AudioRenderer_Render = 0x2A, |
||||
|
/* 0x34 */ AudioRenderer_Shutdown = 0x34, |
||||
|
}; |
||||
|
|
||||
|
/** |
||||
|
* A mailbox for the AudioRenderer, allowing communication between the host and the AudioRenderer |
||||
|
* running on the ADSP. |
||||
|
*/ |
||||
|
class AudioRenderer_Mailbox { |
||||
|
public: |
||||
|
/** |
||||
|
* Send a message from the host to the AudioRenderer. |
||||
|
* |
||||
|
* @param message_ - The message to send to the AudioRenderer. |
||||
|
*/ |
||||
|
void HostSendMessage(RenderMessage message); |
||||
|
|
||||
|
/** |
||||
|
* Host wait for a message from the AudioRenderer. |
||||
|
* |
||||
|
* @return The message returned from the AudioRenderer. |
||||
|
*/ |
||||
|
RenderMessage HostWaitMessage(); |
||||
|
|
||||
|
/** |
||||
|
* Send a message from the AudioRenderer to the host. |
||||
|
* |
||||
|
* @param message_ - The message to send to the host. |
||||
|
*/ |
||||
|
void ADSPSendMessage(RenderMessage message); |
||||
|
|
||||
|
/** |
||||
|
* AudioRenderer wait for a message from the host. |
||||
|
* |
||||
|
* @return The message returned from the AudioRenderer. |
||||
|
*/ |
||||
|
RenderMessage ADSPWaitMessage(); |
||||
|
|
||||
|
/** |
||||
|
* Get the command buffer with the given session id (0 or 1). |
||||
|
* |
||||
|
* @param session_id - The session id to get (0 or 1). |
||||
|
* @return The command buffer. |
||||
|
*/ |
||||
|
CommandBuffer& GetCommandBuffer(s32 session_id); |
||||
|
|
||||
|
/** |
||||
|
* Set the command buffer with the given session id (0 or 1). |
||||
|
* |
||||
|
* @param session_id - The session id to get (0 or 1). |
||||
|
* @param buffer - The command buffer to set. |
||||
|
*/ |
||||
|
void SetCommandBuffer(u32 session_id, CommandBuffer& buffer); |
||||
|
|
||||
|
/** |
||||
|
* Get the total render time taken for the last command lists sent. |
||||
|
* |
||||
|
* @return Total render time taken for the last command lists. |
||||
|
*/ |
||||
|
u64 GetRenderTimeTaken() const; |
||||
|
|
||||
|
/** |
||||
|
* Get the tick the AudioRenderer was signalled. |
||||
|
* |
||||
|
* @return The tick the AudioRenderer was signalled. |
||||
|
*/ |
||||
|
u64 GetSignalledTick() const; |
||||
|
|
||||
|
/** |
||||
|
* Set the tick the AudioRenderer was signalled. |
||||
|
* |
||||
|
* @param tick - The tick the AudioRenderer was signalled. |
||||
|
*/ |
||||
|
void SetSignalledTick(u64 tick); |
||||
|
|
||||
|
/** |
||||
|
* Clear the remaining command count. |
||||
|
* |
||||
|
* @param session_id - Index for which command list to clear (0 or 1). |
||||
|
*/ |
||||
|
void ClearRemainCount(u32 session_id); |
||||
|
|
||||
|
/** |
||||
|
* Get the remaining command count for a given command list. |
||||
|
* |
||||
|
* @param session_id - Index for which command list to clear (0 or 1). |
||||
|
* @return The remaining command count. |
||||
|
*/ |
||||
|
u32 GetRemainCommandCount(u32 session_id) const; |
||||
|
|
||||
|
/** |
||||
|
* Clear the command buffers (does not clear the time taken or the remaining command count). |
||||
|
*/ |
||||
|
void ClearCommandBuffers(); |
||||
|
|
||||
|
private: |
||||
|
/// Host signalling event |
||||
|
Common::Event host_event{}; |
||||
|
/// AudioRenderer signalling event |
||||
|
Common::Event adsp_event{}; |
||||
|
/// Host message queue |
||||
|
|
||||
|
Common::ReaderWriterQueue<RenderMessage> host_messages{}; |
||||
|
/// AudioRenderer message queue |
||||
|
|
||||
|
Common::ReaderWriterQueue<RenderMessage> adsp_messages{}; |
||||
|
/// Command buffers |
||||
|
|
||||
|
std::array<CommandBuffer, MaxRendererSessions> command_buffers{}; |
||||
|
/// Tick the AudioRnederer was signalled |
||||
|
u64 signalled_tick{}; |
||||
|
}; |
||||
|
|
||||
|
/** |
||||
|
* The AudioRenderer application running on the ADSP. |
||||
|
*/ |
||||
|
class AudioRenderer { |
||||
|
public: |
||||
|
explicit AudioRenderer(Core::System& system); |
||||
|
~AudioRenderer(); |
||||
|
|
||||
|
/** |
||||
|
* Start the AudioRenderer. |
||||
|
* |
||||
|
* @param The mailbox to use for this session. |
||||
|
*/ |
||||
|
void Start(AudioRenderer_Mailbox* mailbox); |
||||
|
|
||||
|
/** |
||||
|
* Stop the AudioRenderer. |
||||
|
*/ |
||||
|
void Stop(); |
||||
|
|
||||
|
private: |
||||
|
/** |
||||
|
* Main AudioRenderer thread, responsible for processing the command lists. |
||||
|
*/ |
||||
|
void ThreadFunc(); |
||||
|
|
||||
|
/** |
||||
|
* Creates the streams which will receive the processed samples. |
||||
|
*/ |
||||
|
void CreateSinkStreams(); |
||||
|
|
||||
|
/// Core system |
||||
|
Core::System& system; |
||||
|
/// Main thread |
||||
|
std::thread thread{}; |
||||
|
/// The current state |
||||
|
std::atomic<bool> running{}; |
||||
|
/// The active mailbox |
||||
|
AudioRenderer_Mailbox* mailbox{}; |
||||
|
/// The command lists to process |
||||
|
std::array<CommandListProcessor, MaxRendererSessions> command_list_processors{}; |
||||
|
/// The output sink the AudioRenderer will use |
||||
|
Sink::Sink& sink; |
||||
|
/// The streams which will receive the processed samples |
||||
|
std::array<Sink::SinkStream*, MaxRendererSessions> streams; |
||||
|
}; |
||||
|
|
||||
|
} // namespace AudioRenderer::ADSP |
||||
|
} // namespace AudioCore |
||||
@ -0,0 +1,21 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project |
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include "audio_core/common/common.h" |
||||
|
#include "common/common_types.h" |
||||
|
|
||||
|
namespace AudioCore::AudioRenderer::ADSP { |
||||
|
|
||||
|
struct CommandBuffer { |
||||
|
CpuAddr buffer; |
||||
|
u64 size; |
||||
|
u64 time_limit; |
||||
|
u32 remaining_command_count; |
||||
|
bool reset_buffers; |
||||
|
u64 applet_resource_user_id; |
||||
|
u64 render_time_taken; |
||||
|
}; |
||||
|
|
||||
|
} // namespace AudioCore::AudioRenderer::ADSP |
||||
@ -0,0 +1,109 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
|
||||
|
#include <string>
|
||||
|
|
||||
|
#include "audio_core/renderer/adsp/command_list_processor.h"
|
||||
|
#include "audio_core/renderer/command/command_list_header.h"
|
||||
|
#include "audio_core/renderer/command/commands.h"
|
||||
|
#include "common/settings.h"
|
||||
|
#include "core/core.h"
|
||||
|
#include "core/core_timing.h"
|
||||
|
#include "core/core_timing_util.h"
|
||||
|
#include "core/memory.h"
|
||||
|
|
||||
|
namespace AudioCore::AudioRenderer::ADSP { |
||||
|
|
||||
|
void CommandListProcessor::Initialize(Core::System& system_, CpuAddr buffer, u64 size, |
||||
|
Sink::SinkStream* stream_) { |
||||
|
system = &system_; |
||||
|
memory = &system->Memory(); |
||||
|
stream = stream_; |
||||
|
header = reinterpret_cast<CommandListHeader*>(buffer); |
||||
|
commands = reinterpret_cast<u8*>(buffer + sizeof(CommandListHeader)); |
||||
|
commands_buffer_size = size; |
||||
|
command_count = header->command_count; |
||||
|
sample_count = header->sample_count; |
||||
|
target_sample_rate = header->sample_rate; |
||||
|
mix_buffers = header->samples_buffer; |
||||
|
buffer_count = header->buffer_count; |
||||
|
processed_command_count = 0; |
||||
|
} |
||||
|
|
||||
|
void CommandListProcessor::SetProcessTimeMax(const u64 time) { |
||||
|
max_process_time = time; |
||||
|
} |
||||
|
|
||||
|
u32 CommandListProcessor::GetRemainingCommandCount() const { |
||||
|
return command_count - processed_command_count; |
||||
|
} |
||||
|
|
||||
|
void CommandListProcessor::SetBuffer(const CpuAddr buffer, const u64 size) { |
||||
|
commands = reinterpret_cast<u8*>(buffer + sizeof(CommandListHeader)); |
||||
|
commands_buffer_size = size; |
||||
|
} |
||||
|
|
||||
|
Sink::SinkStream* CommandListProcessor::GetOutputSinkStream() const { |
||||
|
return stream; |
||||
|
} |
||||
|
|
||||
|
u64 CommandListProcessor::Process(u32 session_id) { |
||||
|
const auto start_time_{system->CoreTiming().GetClockTicks()}; |
||||
|
const auto command_base{CpuAddr(commands)}; |
||||
|
|
||||
|
if (processed_command_count > 0) { |
||||
|
current_processing_time += start_time_ - end_time; |
||||
|
} else { |
||||
|
start_time = start_time_; |
||||
|
current_processing_time = 0; |
||||
|
} |
||||
|
|
||||
|
std::string dump{fmt::format("\nSession {}\n", session_id)}; |
||||
|
|
||||
|
for (u32 index = 0; index < command_count; index++) { |
||||
|
auto& command{*reinterpret_cast<ICommand*>(commands)}; |
||||
|
|
||||
|
if (command.magic != 0xCAFEBABE) { |
||||
|
LOG_ERROR(Service_Audio, "Command has invalid magic! Expected 0xCAFEBABE, got {:08X}", |
||||
|
command.magic); |
||||
|
return system->CoreTiming().GetClockTicks() - start_time_; |
||||
|
} |
||||
|
|
||||
|
auto current_offset{CpuAddr(commands) - command_base}; |
||||
|
|
||||
|
if (current_offset + command.size > commands_buffer_size) { |
||||
|
LOG_ERROR(Service_Audio, |
||||
|
"Command exceeded command buffer, buffer size {:08X}, command ends at {:08X}", |
||||
|
commands_buffer_size, |
||||
|
CpuAddr(commands) + command.size - sizeof(CommandListHeader)); |
||||
|
return system->CoreTiming().GetClockTicks() - start_time_; |
||||
|
} |
||||
|
|
||||
|
if (Settings::values.dump_audio_commands) { |
||||
|
command.Dump(*this, dump); |
||||
|
} |
||||
|
|
||||
|
if (!command.Verify(*this)) { |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
if (command.enabled) { |
||||
|
command.Process(*this); |
||||
|
} else { |
||||
|
dump += fmt::format("\tDisabled!\n"); |
||||
|
} |
||||
|
|
||||
|
processed_command_count++; |
||||
|
commands += command.size; |
||||
|
} |
||||
|
|
||||
|
if (Settings::values.dump_audio_commands && dump != last_dump) { |
||||
|
LOG_WARNING(Service_Audio, "{}", dump); |
||||
|
last_dump = dump; |
||||
|
} |
||||
|
|
||||
|
end_time = system->CoreTiming().GetClockTicks(); |
||||
|
return end_time - start_time_; |
||||
|
} |
||||
|
|
||||
|
} // namespace AudioCore::AudioRenderer::ADSP
|
||||
@ -0,0 +1,118 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project |
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <span> |
||||
|
|
||||
|
#include "audio_core/common/common.h" |
||||
|
#include "common/common_types.h" |
||||
|
|
||||
|
namespace Core { |
||||
|
namespace Memory { |
||||
|
class Memory; |
||||
|
} |
||||
|
class System; |
||||
|
} // namespace Core |
||||
|
|
||||
|
namespace AudioCore { |
||||
|
namespace Sink { |
||||
|
class SinkStream; |
||||
|
} |
||||
|
|
||||
|
namespace AudioRenderer { |
||||
|
struct CommandListHeader; |
||||
|
|
||||
|
namespace ADSP { |
||||
|
|
||||
|
/** |
||||
|
* A processor for command lists given to the AudioRenderer. |
||||
|
*/ |
||||
|
class CommandListProcessor { |
||||
|
public: |
||||
|
/** |
||||
|
* Initialize the processor. |
||||
|
* |
||||
|
* @param system_ - The core system. |
||||
|
* @param buffer - The command buffer to process. |
||||
|
* @param size - The size of the buffer. |
||||
|
* @param stream_ - The stream to be used for sending the samples. |
||||
|
*/ |
||||
|
void Initialize(Core::System& system, CpuAddr buffer, u64 size, Sink::SinkStream* stream); |
||||
|
|
||||
|
/** |
||||
|
* Set the maximum processing time for this command list. |
||||
|
* |
||||
|
* @param time - The maximum process time. |
||||
|
*/ |
||||
|
void SetProcessTimeMax(u64 time); |
||||
|
|
||||
|
/** |
||||
|
* Get the remaining command count for this list. |
||||
|
* |
||||
|
* @return The remaining command count. |
||||
|
*/ |
||||
|
u32 GetRemainingCommandCount() const; |
||||
|
|
||||
|
/** |
||||
|
* Set the command buffer. |
||||
|
* |
||||
|
* @param buffer - The buffer to use. |
||||
|
* @param size - The size of the buffer. |
||||
|
*/ |
||||
|
void SetBuffer(CpuAddr buffer, u64 size); |
||||
|
|
||||
|
/** |
||||
|
* Get the stream for this command list. |
||||
|
* |
||||
|
* @return The stream associated with this command list. |
||||
|
*/ |
||||
|
Sink::SinkStream* GetOutputSinkStream() const; |
||||
|
|
||||
|
/** |
||||
|
* Process the command list. |
||||
|
* |
||||
|
* @param index - Index of the current command list. |
||||
|
* @return The time taken to process. |
||||
|
*/ |
||||
|
u64 Process(u32 session_id); |
||||
|
|
||||
|
/// Core system |
||||
|
Core::System* system{}; |
||||
|
/// Core memory |
||||
|
Core::Memory::Memory* memory{}; |
||||
|
/// Stream for the processed samples |
||||
|
Sink::SinkStream* stream{}; |
||||
|
/// Header info for this command list |
||||
|
CommandListHeader* header{}; |
||||
|
/// The command buffer |
||||
|
u8* commands{}; |
||||
|
/// The command buffer size |
||||
|
u64 commands_buffer_size{}; |
||||
|
/// The maximum processing time alloted |
||||
|
u64 max_process_time{}; |
||||
|
/// The number of commands in the buffer |
||||
|
u32 command_count{}; |
||||
|
/// The target sample count for output |
||||
|
u32 sample_count{}; |
||||
|
/// The target sample rate for output |
||||
|
u32 target_sample_rate{}; |
||||
|
/// The mixing buffers used by the commands |
||||
|
std::span<s32> mix_buffers{}; |
||||
|
/// The number of mix buffers |
||||
|
u32 buffer_count{}; |
||||
|
/// The number of processed commands so far |
||||
|
u32 processed_command_count{}; |
||||
|
/// The processing start time of this list |
||||
|
u64 start_time{}; |
||||
|
/// The current processing time for this list |
||||
|
u64 current_processing_time{}; |
||||
|
/// The end processing time for this list |
||||
|
u64 end_time{}; |
||||
|
/// Last command list string generated, used for dumping audio commands to console |
||||
|
std::string last_dump{}; |
||||
|
}; |
||||
|
|
||||
|
} // namespace ADSP |
||||
|
} // namespace AudioRenderer |
||||
|
} // namespace AudioCore |
||||
@ -0,0 +1,52 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
|
||||
|
#include "audio_core/audio_core.h"
|
||||
|
#include "audio_core/common/feature_support.h"
|
||||
|
#include "audio_core/renderer/audio_device.h"
|
||||
|
#include "audio_core/sink/sink.h"
|
||||
|
#include "core/core.h"
|
||||
|
|
||||
|
namespace AudioCore::AudioRenderer { |
||||
|
|
||||
|
AudioDevice::AudioDevice(Core::System& system, const u64 applet_resource_user_id_, |
||||
|
const u32 revision) |
||||
|
: output_sink{system.AudioCore().GetOutputSink()}, |
||||
|
applet_resource_user_id{applet_resource_user_id_}, user_revision{revision} {} |
||||
|
|
||||
|
u32 AudioDevice::ListAudioDeviceName(std::vector<AudioDeviceName>& out_buffer, |
||||
|
const size_t max_count) { |
||||
|
std::span<AudioDeviceName> names{}; |
||||
|
|
||||
|
if (CheckFeatureSupported(SupportTags::AudioUsbDeviceOutput, user_revision)) { |
||||
|
names = usb_device_names; |
||||
|
} else { |
||||
|
names = device_names; |
||||
|
} |
||||
|
|
||||
|
u32 out_count{static_cast<u32>(std::min(max_count, names.size()))}; |
||||
|
for (u32 i = 0; i < out_count; i++) { |
||||
|
out_buffer.push_back(names[i]); |
||||
|
} |
||||
|
return out_count; |
||||
|
} |
||||
|
|
||||
|
u32 AudioDevice::ListAudioOutputDeviceName(std::vector<AudioDeviceName>& out_buffer, |
||||
|
const size_t max_count) { |
||||
|
u32 out_count{static_cast<u32>(std::min(max_count, output_device_names.size()))}; |
||||
|
|
||||
|
for (u32 i = 0; i < out_count; i++) { |
||||
|
out_buffer.push_back(output_device_names[i]); |
||||
|
} |
||||
|
return out_count; |
||||
|
} |
||||
|
|
||||
|
void AudioDevice::SetDeviceVolumes(const f32 volume) { |
||||
|
output_sink.SetDeviceVolume(volume); |
||||
|
} |
||||
|
|
||||
|
f32 AudioDevice::GetDeviceVolume([[maybe_unused]] std::string_view name) { |
||||
|
return output_sink.GetDeviceVolume(); |
||||
|
} |
||||
|
|
||||
|
} // namespace AudioCore::AudioRenderer
|
||||
@ -0,0 +1,88 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project |
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <span> |
||||
|
|
||||
|
#include "audio_core/audio_render_manager.h" |
||||
|
|
||||
|
namespace Core { |
||||
|
class System; |
||||
|
} |
||||
|
|
||||
|
namespace AudioCore { |
||||
|
namespace Sink { |
||||
|
class Sink; |
||||
|
} |
||||
|
|
||||
|
namespace AudioRenderer { |
||||
|
/** |
||||
|
* An interface to an output audio device available to the Switch. |
||||
|
*/ |
||||
|
class AudioDevice { |
||||
|
public: |
||||
|
struct AudioDeviceName { |
||||
|
std::array<char, 0x100> name; |
||||
|
|
||||
|
AudioDeviceName(const char* name_) { |
||||
|
std::strncpy(name.data(), name_, name.size()); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
std::array<AudioDeviceName, 4> usb_device_names{"AudioStereoJackOutput", |
||||
|
"AudioBuiltInSpeakerOutput", "AudioTvOutput", |
||||
|
"AudioUsbDeviceOutput"}; |
||||
|
std::array<AudioDeviceName, 3> device_names{"AudioStereoJackOutput", |
||||
|
"AudioBuiltInSpeakerOutput", "AudioTvOutput"}; |
||||
|
std::array<AudioDeviceName, 3> output_device_names{"AudioBuiltInSpeakerOutput", "AudioTvOutput", |
||||
|
"AudioExternalOutput"}; |
||||
|
|
||||
|
explicit AudioDevice(Core::System& system, u64 applet_resource_user_id, u32 revision); |
||||
|
|
||||
|
/** |
||||
|
* Get a list of the available output devices. |
||||
|
* |
||||
|
* @param out_buffer - Output buffer to write the available device names. |
||||
|
* @param max_count - Maximum number of devices to write (count of out_buffer). |
||||
|
* @return Number of device names written. |
||||
|
*/ |
||||
|
u32 ListAudioDeviceName(std::vector<AudioDeviceName>& out_buffer, size_t max_count); |
||||
|
|
||||
|
/** |
||||
|
* Get a list of the available output devices. |
||||
|
* Different to above somehow... |
||||
|
* |
||||
|
* @param out_buffer - Output buffer to write the available device names. |
||||
|
* @param max_count - Maximum number of devices to write (count of out_buffer). |
||||
|
* @return Number of device names written. |
||||
|
*/ |
||||
|
u32 ListAudioOutputDeviceName(std::vector<AudioDeviceName>& out_buffer, size_t max_count); |
||||
|
|
||||
|
/** |
||||
|
* Set the volume of all streams in the backend sink. |
||||
|
* |
||||
|
* @param volume - Volume to set. |
||||
|
*/ |
||||
|
void SetDeviceVolumes(f32 volume); |
||||
|
|
||||
|
/** |
||||
|
* Get the volume for a given device name. |
||||
|
* Note: This is not fully implemented, we only assume 1 device for all streams. |
||||
|
* |
||||
|
* @param name - Name of the device to check. Unused. |
||||
|
* @return Volume of the device. |
||||
|
*/ |
||||
|
f32 GetDeviceVolume(std::string_view name); |
||||
|
|
||||
|
private: |
||||
|
/// Backend output sink for the device |
||||
|
Sink::Sink& output_sink; |
||||
|
/// Resource id this device is used for |
||||
|
const u64 applet_resource_user_id; |
||||
|
/// User audio renderer revision |
||||
|
const u32 user_revision; |
||||
|
}; |
||||
|
|
||||
|
} // namespace AudioRenderer |
||||
|
} // namespace AudioCore |
||||
@ -0,0 +1,67 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
|
||||
|
#include "audio_core/audio_render_manager.h"
|
||||
|
#include "audio_core/common/audio_renderer_parameter.h"
|
||||
|
#include "audio_core/renderer/audio_renderer.h"
|
||||
|
#include "audio_core/renderer/system_manager.h"
|
||||
|
#include "core/core.h"
|
||||
|
#include "core/hle/kernel/k_transfer_memory.h"
|
||||
|
#include "core/hle/service/audio/errors.h"
|
||||
|
|
||||
|
namespace AudioCore::AudioRenderer { |
||||
|
|
||||
|
Renderer::Renderer(Core::System& system_, Manager& manager_, Kernel::KEvent* rendered_event) |
||||
|
: core{system_}, manager{manager_}, system{system_, rendered_event} {} |
||||
|
|
||||
|
Result Renderer::Initialize(const AudioRendererParameterInternal& params, |
||||
|
Kernel::KTransferMemory* transfer_memory, |
||||
|
const u64 transfer_memory_size, const u32 process_handle, |
||||
|
const u64 applet_resource_user_id, const s32 session_id) { |
||||
|
if (params.execution_mode == ExecutionMode::Auto) { |
||||
|
if (!manager.AddSystem(system)) { |
||||
|
LOG_ERROR(Service_Audio, |
||||
|
"Both Audio Render sessions are in use, cannot create any more"); |
||||
|
return Service::Audio::ERR_MAXIMUM_SESSIONS_REACHED; |
||||
|
} |
||||
|
system_registered = true; |
||||
|
} |
||||
|
|
||||
|
initialized = true; |
||||
|
system.Initialize(params, transfer_memory, transfer_memory_size, process_handle, |
||||
|
applet_resource_user_id, session_id); |
||||
|
|
||||
|
return ResultSuccess; |
||||
|
} |
||||
|
|
||||
|
void Renderer::Finalize() { |
||||
|
auto session_id{system.GetSessionId()}; |
||||
|
|
||||
|
system.Finalize(); |
||||
|
|
||||
|
if (system_registered) { |
||||
|
manager.RemoveSystem(system); |
||||
|
system_registered = false; |
||||
|
} |
||||
|
|
||||
|
manager.ReleaseSessionId(session_id); |
||||
|
} |
||||
|
|
||||
|
System& Renderer::GetSystem() { |
||||
|
return system; |
||||
|
} |
||||
|
|
||||
|
void Renderer::Start() { |
||||
|
system.Start(); |
||||
|
} |
||||
|
|
||||
|
void Renderer::Stop() { |
||||
|
system.Stop(); |
||||
|
} |
||||
|
|
||||
|
Result Renderer::RequestUpdate(std::span<const u8> input, std::span<u8> performance, |
||||
|
std::span<u8> output) { |
||||
|
return system.Update(input, performance, output); |
||||
|
} |
||||
|
|
||||
|
} // namespace AudioCore::AudioRenderer
|
||||
@ -0,0 +1,97 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project |
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <span> |
||||
|
|
||||
|
#include "audio_core/renderer/system.h" |
||||
|
#include "core/hle/service/audio/errors.h" |
||||
|
|
||||
|
namespace Core { |
||||
|
class System; |
||||
|
} |
||||
|
|
||||
|
namespace Kernel { |
||||
|
class KTransferMemory; |
||||
|
} |
||||
|
|
||||
|
namespace AudioCore { |
||||
|
struct AudioRendererParameterInternal; |
||||
|
|
||||
|
namespace AudioRenderer { |
||||
|
class Manager; |
||||
|
|
||||
|
/** |
||||
|
* Audio Renderer, wraps the main audio system and is mainly responsible for handling service calls. |
||||
|
*/ |
||||
|
class Renderer { |
||||
|
public: |
||||
|
explicit Renderer(Core::System& system, Manager& manager, Kernel::KEvent* rendered_event); |
||||
|
|
||||
|
/** |
||||
|
* Initialize the renderer. |
||||
|
* Registers the system with the AudioRenderer::Manager, allocates workbuffers and initializes |
||||
|
* everything to a default state. |
||||
|
* |
||||
|
* @param params - Input parameters to initialize the system with. |
||||
|
* @param transfer_memory - Game-supplied memory for all workbuffers. Unused. |
||||
|
* @param transfer_memory_size - Size of the transfer memory. Unused. |
||||
|
* @param process_handle - Process handle, also used for memory. Unused. |
||||
|
* @param applet_resource_user_id - Applet id for this renderer. Unused. |
||||
|
* @param session_id - Session id of this renderer. |
||||
|
* @return Result code. |
||||
|
*/ |
||||
|
Result Initialize(const AudioRendererParameterInternal& params, |
||||
|
Kernel::KTransferMemory* transfer_memory, u64 transfer_memory_size, |
||||
|
u32 process_handle, u64 applet_resource_user_id, s32 session_id); |
||||
|
|
||||
|
/** |
||||
|
* Finalize the renderer for shutdown. |
||||
|
*/ |
||||
|
void Finalize(); |
||||
|
|
||||
|
/** |
||||
|
* Get the renderer's system. |
||||
|
* |
||||
|
* @return Reference to the system. |
||||
|
*/ |
||||
|
System& GetSystem(); |
||||
|
|
||||
|
/** |
||||
|
* Start the renderer. |
||||
|
*/ |
||||
|
void Start(); |
||||
|
|
||||
|
/** |
||||
|
* Stop the renderer. |
||||
|
*/ |
||||
|
void Stop(); |
||||
|
|
||||
|
/** |
||||
|
* Update the audio renderer with new information. |
||||
|
* Called via RequestUpdate from the AudRen:U service. |
||||
|
* |
||||
|
* @param input - Input buffer containing the new data. |
||||
|
* @param performance - Optional performance buffer for outputting performance metrics. |
||||
|
* @param output - Output data from the renderer. |
||||
|
* @return Result code. |
||||
|
*/ |
||||
|
Result RequestUpdate(std::span<const u8> input, std::span<u8> performance, |
||||
|
std::span<u8> output); |
||||
|
|
||||
|
private: |
||||
|
/// System core |
||||
|
Core::System& core; |
||||
|
/// Manager this renderer is registered with |
||||
|
Manager& manager; |
||||
|
/// Is the audio renderer initialized? |
||||
|
bool initialized{}; |
||||
|
/// Is the system registered with the manager? |
||||
|
bool system_registered{}; |
||||
|
/// Audio render system, main driver of audio rendering |
||||
|
System system; |
||||
|
}; |
||||
|
|
||||
|
} // namespace AudioRenderer |
||||
|
} // namespace AudioCore |
||||
@ -0,0 +1,191 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
|
||||
|
#include "audio_core/common/feature_support.h"
|
||||
|
#include "audio_core/renderer/behavior/behavior_info.h"
|
||||
|
|
||||
|
namespace AudioCore::AudioRenderer { |
||||
|
|
||||
|
BehaviorInfo::BehaviorInfo() : process_revision{CurrentRevision} {} |
||||
|
|
||||
|
u32 BehaviorInfo::GetProcessRevisionNum() const { |
||||
|
return process_revision; |
||||
|
} |
||||
|
|
||||
|
u32 BehaviorInfo::GetProcessRevision() const { |
||||
|
return Common::MakeMagic('R', 'E', 'V', |
||||
|
static_cast<char>(static_cast<u8>('0') + process_revision)); |
||||
|
} |
||||
|
|
||||
|
u32 BehaviorInfo::GetUserRevisionNum() const { |
||||
|
return user_revision; |
||||
|
} |
||||
|
|
||||
|
u32 BehaviorInfo::GetUserRevision() const { |
||||
|
return Common::MakeMagic('R', 'E', 'V', |
||||
|
static_cast<char>(static_cast<u8>('0') + user_revision)); |
||||
|
} |
||||
|
|
||||
|
void BehaviorInfo::SetUserLibRevision(const u32 user_revision_) { |
||||
|
user_revision = GetRevisionNum(user_revision_); |
||||
|
} |
||||
|
|
||||
|
void BehaviorInfo::ClearError() { |
||||
|
error_count = 0; |
||||
|
} |
||||
|
|
||||
|
void BehaviorInfo::AppendError(ErrorInfo& error) { |
||||
|
LOG_ERROR(Service_Audio, "Error during RequestUpdate, reporting code {:04X} address {:08X}", |
||||
|
error.error_code.raw, error.address); |
||||
|
if (error_count < MaxErrors) { |
||||
|
errors[error_count++] = error; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void BehaviorInfo::CopyErrorInfo(std::span<ErrorInfo> out_errors, u32& out_count) { |
||||
|
auto error_count_{std::min(error_count, MaxErrors)}; |
||||
|
std::memset(out_errors.data(), 0, MaxErrors * sizeof(ErrorInfo)); |
||||
|
|
||||
|
for (size_t i = 0; i < error_count_; i++) { |
||||
|
out_errors[i] = errors[i]; |
||||
|
} |
||||
|
out_count = error_count_; |
||||
|
} |
||||
|
|
||||
|
void BehaviorInfo::UpdateFlags(const Flags flags_) { |
||||
|
flags = flags_; |
||||
|
} |
||||
|
|
||||
|
bool BehaviorInfo::IsMemoryForceMappingEnabled() const { |
||||
|
return flags.IsMemoryForceMappingEnabled; |
||||
|
} |
||||
|
|
||||
|
bool BehaviorInfo::IsAdpcmLoopContextBugFixed() const { |
||||
|
return CheckFeatureSupported(SupportTags::AdpcmLoopContextBugFix, user_revision); |
||||
|
} |
||||
|
|
||||
|
bool BehaviorInfo::IsSplitterSupported() const { |
||||
|
return CheckFeatureSupported(SupportTags::Splitter, user_revision); |
||||
|
} |
||||
|
|
||||
|
bool BehaviorInfo::IsSplitterBugFixed() const { |
||||
|
return CheckFeatureSupported(SupportTags::SplitterBugFix, user_revision); |
||||
|
} |
||||
|
|
||||
|
bool BehaviorInfo::IsEffectInfoVersion2Supported() const { |
||||
|
return CheckFeatureSupported(SupportTags::EffectInfoVer2, user_revision); |
||||
|
} |
||||
|
|
||||
|
bool BehaviorInfo::IsVariadicCommandBufferSizeSupported() const { |
||||
|
return CheckFeatureSupported(SupportTags::AudioRendererVariadicCommandBufferSize, |
||||
|
user_revision); |
||||
|
} |
||||
|
|
||||
|
bool BehaviorInfo::IsWaveBufferVer2Supported() const { |
||||
|
return CheckFeatureSupported(SupportTags::WaveBufferVer2, user_revision); |
||||
|
} |
||||
|
|
||||
|
bool BehaviorInfo::IsLongSizePreDelaySupported() const { |
||||
|
return CheckFeatureSupported(SupportTags::LongSizePreDelay, user_revision); |
||||
|
} |
||||
|
|
||||
|
bool BehaviorInfo::IsCommandProcessingTimeEstimatorVersion2Supported() const { |
||||
|
return CheckFeatureSupported(SupportTags::CommandProcessingTimeEstimatorVersion2, |
||||
|
user_revision); |
||||
|
} |
||||
|
|
||||
|
bool BehaviorInfo::IsCommandProcessingTimeEstimatorVersion3Supported() const { |
||||
|
return CheckFeatureSupported(SupportTags::CommandProcessingTimeEstimatorVersion3, |
||||
|
user_revision); |
||||
|
} |
||||
|
|
||||
|
bool BehaviorInfo::IsCommandProcessingTimeEstimatorVersion4Supported() const { |
||||
|
return CheckFeatureSupported(SupportTags::CommandProcessingTimeEstimatorVersion4, |
||||
|
user_revision); |
||||
|
} |
||||
|
|
||||
|
bool BehaviorInfo::IsCommandProcessingTimeEstimatorVersion5Supported() const { |
||||
|
return CheckFeatureSupported(SupportTags::CommandProcessingTimeEstimatorVersion4, |
||||
|
user_revision); |
||||
|
} |
||||
|
|
||||
|
bool BehaviorInfo::IsAudioRendererProcessingTimeLimit70PercentSupported() const { |
||||
|
return CheckFeatureSupported(SupportTags::AudioRendererProcessingTimeLimit70Percent, |
||||
|
user_revision); |
||||
|
} |
||||
|
|
||||
|
bool BehaviorInfo::IsAudioRendererProcessingTimeLimit75PercentSupported() const { |
||||
|
return CheckFeatureSupported(SupportTags::AudioRendererProcessingTimeLimit75Percent, |
||||
|
user_revision); |
||||
|
} |
||||
|
|
||||
|
bool BehaviorInfo::IsAudioRendererProcessingTimeLimit80PercentSupported() const { |
||||
|
return CheckFeatureSupported(SupportTags::AudioRendererProcessingTimeLimit80Percent, |
||||
|
user_revision); |
||||
|
} |
||||
|
|
||||
|
bool BehaviorInfo::IsFlushVoiceWaveBuffersSupported() const { |
||||
|
return CheckFeatureSupported(SupportTags::FlushVoiceWaveBuffers, user_revision); |
||||
|
} |
||||
|
|
||||
|
bool BehaviorInfo::IsElapsedFrameCountSupported() const { |
||||
|
return CheckFeatureSupported(SupportTags::ElapsedFrameCount, user_revision); |
||||
|
} |
||||
|
|
||||
|
bool BehaviorInfo::IsPerformanceMetricsDataFormatVersion2Supported() const { |
||||
|
return CheckFeatureSupported(SupportTags::PerformanceMetricsDataFormatVersion2, user_revision); |
||||
|
} |
||||
|
|
||||
|
size_t BehaviorInfo::GetPerformanceMetricsDataFormat() const { |
||||
|
if (CheckFeatureSupported(SupportTags::PerformanceMetricsDataFormatVersion2, user_revision)) { |
||||
|
return 2; |
||||
|
} |
||||
|
return 1; |
||||
|
} |
||||
|
|
||||
|
bool BehaviorInfo::IsVoicePitchAndSrcSkippedSupported() const { |
||||
|
return CheckFeatureSupported(SupportTags::VoicePitchAndSrcSkipped, user_revision); |
||||
|
} |
||||
|
|
||||
|
bool BehaviorInfo::IsVoicePlayedSampleCountResetAtLoopPointSupported() const { |
||||
|
return CheckFeatureSupported(SupportTags::VoicePlayedSampleCountResetAtLoopPoint, |
||||
|
user_revision); |
||||
|
} |
||||
|
|
||||
|
bool BehaviorInfo::IsBiquadFilterEffectStateClearBugFixed() const { |
||||
|
return CheckFeatureSupported(SupportTags::BiquadFilterEffectStateClearBugFix, user_revision); |
||||
|
} |
||||
|
|
||||
|
bool BehaviorInfo::IsVolumeMixParameterPrecisionQ23Supported() const { |
||||
|
return CheckFeatureSupported(SupportTags::VolumeMixParameterPrecisionQ23, user_revision); |
||||
|
} |
||||
|
|
||||
|
bool BehaviorInfo::UseBiquadFilterFloatProcessing() const { |
||||
|
return CheckFeatureSupported(SupportTags::BiquadFilterFloatProcessing, user_revision); |
||||
|
} |
||||
|
|
||||
|
bool BehaviorInfo::IsMixInParameterDirtyOnlyUpdateSupported() const { |
||||
|
return CheckFeatureSupported(SupportTags::MixInParameterDirtyOnlyUpdate, user_revision); |
||||
|
} |
||||
|
|
||||
|
bool BehaviorInfo::UseMultiTapBiquadFilterProcessing() const { |
||||
|
return CheckFeatureSupported(SupportTags::MultiTapBiquadFilterProcessing, user_revision); |
||||
|
} |
||||
|
|
||||
|
bool BehaviorInfo::IsDeviceApiVersion2Supported() const { |
||||
|
return CheckFeatureSupported(SupportTags::DeviceApiVersion2, user_revision); |
||||
|
} |
||||
|
|
||||
|
bool BehaviorInfo::IsDelayChannelMappingChanged() const { |
||||
|
return CheckFeatureSupported(SupportTags::DelayChannelMappingChange, user_revision); |
||||
|
} |
||||
|
|
||||
|
bool BehaviorInfo::IsReverbChannelMappingChanged() const { |
||||
|
return CheckFeatureSupported(SupportTags::ReverbChannelMappingChange, user_revision); |
||||
|
} |
||||
|
|
||||
|
bool BehaviorInfo::IsI3dl2ReverbChannelMappingChanged() const { |
||||
|
return CheckFeatureSupported(SupportTags::I3dl2ReverbChannelMappingChange, user_revision); |
||||
|
} |
||||
|
|
||||
|
} // namespace AudioCore::AudioRenderer
|
||||
@ -0,0 +1,376 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project |
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <array> |
||||
|
#include <span> |
||||
|
|
||||
|
#include "audio_core/common/common.h" |
||||
|
#include "common/common_types.h" |
||||
|
#include "core/hle/service/audio/errors.h" |
||||
|
|
||||
|
namespace AudioCore::AudioRenderer { |
||||
|
/** |
||||
|
* Holds host and user revisions, checks whether render features can be enabled, and reports errors. |
||||
|
*/ |
||||
|
class BehaviorInfo { |
||||
|
static constexpr u32 MaxErrors = 10; |
||||
|
|
||||
|
public: |
||||
|
struct ErrorInfo { |
||||
|
/* 0x00 */ Result error_code{0}; |
||||
|
/* 0x04 */ u32 unk_04; |
||||
|
/* 0x08 */ CpuAddr address; |
||||
|
}; |
||||
|
static_assert(sizeof(ErrorInfo) == 0x10, "BehaviorInfo::ErrorInfo has the wrong size!"); |
||||
|
|
||||
|
struct Flags { |
||||
|
u64 IsMemoryForceMappingEnabled : 1; |
||||
|
}; |
||||
|
|
||||
|
struct InParameter { |
||||
|
/* 0x00 */ u32 revision; |
||||
|
/* 0x08 */ Flags flags; |
||||
|
}; |
||||
|
static_assert(sizeof(InParameter) == 0x10, "BehaviorInfo::InParameter has the wrong size!"); |
||||
|
|
||||
|
struct OutStatus { |
||||
|
/* 0x00 */ std::array<ErrorInfo, MaxErrors> errors; |
||||
|
/* 0xA0 */ u32 error_count; |
||||
|
/* 0xA4 */ char unkA4[0xC]; |
||||
|
}; |
||||
|
static_assert(sizeof(OutStatus) == 0xB0, "BehaviorInfo::OutStatus has the wrong size!"); |
||||
|
|
||||
|
BehaviorInfo(); |
||||
|
|
||||
|
/** |
||||
|
* Get the host revision as a number. |
||||
|
* |
||||
|
* @return The host revision. |
||||
|
*/ |
||||
|
u32 GetProcessRevisionNum() const; |
||||
|
|
||||
|
/** |
||||
|
* Get the host revision in chars, e.g REV8. |
||||
|
* Rev 10 and higher use the ascii characters above 9. |
||||
|
* E.g: |
||||
|
* Rev 10 = REV: |
||||
|
* Rev 11 = REV; |
||||
|
* |
||||
|
* @return The host revision. |
||||
|
*/ |
||||
|
u32 GetProcessRevision() const; |
||||
|
|
||||
|
/** |
||||
|
* Get the user revision as a number. |
||||
|
* |
||||
|
* @return The user revision. |
||||
|
*/ |
||||
|
u32 GetUserRevisionNum() const; |
||||
|
|
||||
|
/** |
||||
|
* Get the user revision in chars, e.g REV8. |
||||
|
* Rev 10 and higher use the ascii characters above 9. REV: REV; etc. |
||||
|
* |
||||
|
* @return The user revision. |
||||
|
*/ |
||||
|
u32 GetUserRevision() const; |
||||
|
|
||||
|
/** |
||||
|
* Set the user revision. |
||||
|
* |
||||
|
* @param user_revision - The user's revision. |
||||
|
*/ |
||||
|
void SetUserLibRevision(u32 user_revision); |
||||
|
|
||||
|
/** |
||||
|
* Clear the current error count. |
||||
|
*/ |
||||
|
void ClearError(); |
||||
|
|
||||
|
/** |
||||
|
* Append an error to the error list. |
||||
|
* |
||||
|
* @param error - The new error. |
||||
|
*/ |
||||
|
void AppendError(ErrorInfo& error); |
||||
|
|
||||
|
/** |
||||
|
* Copy errors to the given output container. |
||||
|
* |
||||
|
* @param out_errors - Output container to receive the errors. |
||||
|
* @param out_count - The number of errors written. |
||||
|
*/ |
||||
|
void CopyErrorInfo(std::span<ErrorInfo> out_errors, u32& out_count); |
||||
|
|
||||
|
/** |
||||
|
* Update the behaviour flags. |
||||
|
* |
||||
|
* @param flags - New flags to use. |
||||
|
*/ |
||||
|
void UpdateFlags(Flags flags); |
||||
|
|
||||
|
/** |
||||
|
* Check if memory pools can be forcibly mapped. |
||||
|
* |
||||
|
* @return True if enabled, otherwise false. |
||||
|
*/ |
||||
|
bool IsMemoryForceMappingEnabled() const; |
||||
|
|
||||
|
/** |
||||
|
* Check if the ADPCM context bug is fixed. |
||||
|
* The ADPCM context was not being sent to the AudioRenderer, leading to incorrect scaling being |
||||
|
* used. |
||||
|
* |
||||
|
* @return True if fixed, otherwise false. |
||||
|
*/ |
||||
|
bool IsAdpcmLoopContextBugFixed() const; |
||||
|
|
||||
|
/** |
||||
|
* Check if the splitter is supported. |
||||
|
* |
||||
|
* @return True if supported, otherwise false. |
||||
|
*/ |
||||
|
bool IsSplitterSupported() const; |
||||
|
|
||||
|
/** |
||||
|
* Check if the splitter bug is fixed. |
||||
|
* Update is given the wrong number of splitter destinations, leading to invalid data |
||||
|
* being processed. |
||||
|
* |
||||
|
* @return True if supported, otherwise false. |
||||
|
*/ |
||||
|
bool IsSplitterBugFixed() const; |
||||
|
|
||||
|
/** |
||||
|
* Check if effects version 2 are supported. |
||||
|
* This gives support for returning effect states from the AudioRenderer, currently only used |
||||
|
* for Limiter statistics. |
||||
|
* |
||||
|
* @return True if supported, otherwise false. |
||||
|
*/ |
||||
|
bool IsEffectInfoVersion2Supported() const; |
||||
|
|
||||
|
/** |
||||
|
* Check if a variadic command buffer is supported. |
||||
|
* As of Rev 5 with the added optional performance metric logging, the command |
||||
|
* buffer can be a variable size, so take that into account for calcualting its size. |
||||
|
* |
||||
|
* @return True if supported, otherwise false. |
||||
|
*/ |
||||
|
bool IsVariadicCommandBufferSizeSupported() const; |
||||
|
|
||||
|
/** |
||||
|
* Check if wave buffers version 2 are supported. |
||||
|
* See WaveBufferVersion1 and WaveBufferVersion2. |
||||
|
* |
||||
|
* @return True if supported, otherwise false. |
||||
|
*/ |
||||
|
bool IsWaveBufferVer2Supported() const; |
||||
|
|
||||
|
/** |
||||
|
* Check if long size pre delay is supported. |
||||
|
* This allows a longer initial delay time for the Reverb command. |
||||
|
* |
||||
|
* @return True if supported, otherwise false. |
||||
|
*/ |
||||
|
bool IsLongSizePreDelaySupported() const; |
||||
|
|
||||
|
/** |
||||
|
* Check if the command time estimator version 2 is supported. |
||||
|
* |
||||
|
* @return True if supported, otherwise false. |
||||
|
*/ |
||||
|
bool IsCommandProcessingTimeEstimatorVersion2Supported() const; |
||||
|
|
||||
|
/** |
||||
|
* Check if the command time estimator version 3 is supported. |
||||
|
* |
||||
|
* @return True if supported, otherwise false. |
||||
|
*/ |
||||
|
bool IsCommandProcessingTimeEstimatorVersion3Supported() const; |
||||
|
|
||||
|
/** |
||||
|
* Check if the command time estimator version 4 is supported. |
||||
|
* |
||||
|
* @return True if supported, otherwise false. |
||||
|
*/ |
||||
|
bool IsCommandProcessingTimeEstimatorVersion4Supported() const; |
||||
|
|
||||
|
/** |
||||
|
* Check if the command time estimator version 5 is supported. |
||||
|
* |
||||
|
* @return True if supported, otherwise false. |
||||
|
*/ |
||||
|
bool IsCommandProcessingTimeEstimatorVersion5Supported() const; |
||||
|
|
||||
|
/** |
||||
|
* Check if the AudioRenderer can use up to 70% of the allocated processing timeslice. |
||||
|
* |
||||
|
* @return True if supported, otherwise false. |
||||
|
*/ |
||||
|
bool IsAudioRendererProcessingTimeLimit70PercentSupported() const; |
||||
|
|
||||
|
/** |
||||
|
* Check if the AudioRenderer can use up to 75% of the allocated processing timeslice. |
||||
|
* |
||||
|
* @return True if supported, otherwise false. |
||||
|
*/ |
||||
|
bool IsAudioRendererProcessingTimeLimit75PercentSupported() const; |
||||
|
|
||||
|
/** |
||||
|
* Check if the AudioRenderer can use up to 80% of the allocated processing timeslice. |
||||
|
* |
||||
|
* @return True if supported, otherwise false. |
||||
|
*/ |
||||
|
bool IsAudioRendererProcessingTimeLimit80PercentSupported() const; |
||||
|
|
||||
|
/** |
||||
|
* Check if voice flushing is supported |
||||
|
* This allowws low-priority voices to be dropped if the AudioRenderer is running behind. |
||||
|
* |
||||
|
* @return True if supported, otherwise false. |
||||
|
*/ |
||||
|
bool IsFlushVoiceWaveBuffersSupported() const; |
||||
|
|
||||
|
/** |
||||
|
* Check if counting the number of elapsed frames is supported. |
||||
|
* This adds extra output to RequestUpdate, returning the number of times the AudioRenderer |
||||
|
* processed a command list. |
||||
|
* |
||||
|
* @return True if supported, otherwise false. |
||||
|
*/ |
||||
|
bool IsElapsedFrameCountSupported() const; |
||||
|
|
||||
|
/** |
||||
|
* Check if performance metrics version 2 are supported. |
||||
|
* This adds extra output to RequestUpdate, returning the number of times the AudioRenderer |
||||
|
* (Unused?). |
||||
|
* |
||||
|
* @return True if supported, otherwise false. |
||||
|
*/ |
||||
|
bool IsPerformanceMetricsDataFormatVersion2Supported() const; |
||||
|
|
||||
|
/** |
||||
|
* Get the supported performance metrics version. |
||||
|
* Version 2 logs some extra fields in output, such as number of voices dropped, |
||||
|
* processing start time, if the AudioRenderer exceeded its time, etc. |
||||
|
* |
||||
|
* @return Version supported, either 1 or 2. |
||||
|
*/ |
||||
|
size_t GetPerformanceMetricsDataFormat() const; |
||||
|
|
||||
|
/** |
||||
|
* Check if skipping voice pitch and sample rate conversion is supported. |
||||
|
* This speeds up the data source commands by skipping resampling if unwanted. |
||||
|
* See AudioCore::AudioRenderer::DecodeFromWaveBuffers |
||||
|
* |
||||
|
* @return True if supported, otherwise false. |
||||
|
*/ |
||||
|
bool IsVoicePitchAndSrcSkippedSupported() const; |
||||
|
|
||||
|
/** |
||||
|
* Check if resetting played sample count at loop points is supported. |
||||
|
* This resets the number of samples played in a voice state when a loop point is reached. |
||||
|
* See AudioCore::AudioRenderer::DecodeFromWaveBuffers |
||||
|
* |
||||
|
* @return True if supported, otherwise false. |
||||
|
*/ |
||||
|
bool IsVoicePlayedSampleCountResetAtLoopPointSupported() const; |
||||
|
|
||||
|
/** |
||||
|
* Check if the clear state bug for biquad filters is fixed. |
||||
|
* The biquad state was not marked as needing re-initialisation when the effect was updated, it |
||||
|
* was only initialized once with a new effect. |
||||
|
* |
||||
|
* @return True if fixed, otherwise false. |
||||
|
*/ |
||||
|
bool IsBiquadFilterEffectStateClearBugFixed() const; |
||||
|
|
||||
|
/** |
||||
|
* Check if Q23 precision is supported for fixed point. |
||||
|
* |
||||
|
* @return True if supported, otherwise false. |
||||
|
*/ |
||||
|
bool IsVolumeMixParameterPrecisionQ23Supported() const; |
||||
|
|
||||
|
/** |
||||
|
* Check if float processing for biuad filters is supported. |
||||
|
* |
||||
|
* @return True if supported, otherwise false. |
||||
|
*/ |
||||
|
bool UseBiquadFilterFloatProcessing() const; |
||||
|
|
||||
|
/** |
||||
|
* Check if dirty-only mix updates are supported. |
||||
|
* This saves a lot of buffer size as mixes can be large and not change much. |
||||
|
* |
||||
|
* @return True if supported, otherwise false. |
||||
|
*/ |
||||
|
bool IsMixInParameterDirtyOnlyUpdateSupported() const; |
||||
|
|
||||
|
/** |
||||
|
* Check if multi-tap biquad filters are supported. |
||||
|
* |
||||
|
* @return True if supported, otherwise false. |
||||
|
*/ |
||||
|
bool UseMultiTapBiquadFilterProcessing() const; |
||||
|
|
||||
|
/** |
||||
|
* Check if device api version 2 is supported. |
||||
|
* In the SDK but not in any sysmodule? Not sure, left here for completeness anyway. |
||||
|
* |
||||
|
* @return True if supported, otherwise false. |
||||
|
*/ |
||||
|
bool IsDeviceApiVersion2Supported() const; |
||||
|
|
||||
|
/** |
||||
|
* Check if new channel mappings are used for Delay commands. |
||||
|
* Older commands used: |
||||
|
* front left/front right/back left/back right/center/lfe |
||||
|
* Whereas everywhere else in the code uses: |
||||
|
* front left/front right/center/lfe/back left/back right |
||||
|
* This corrects that and makes everything standardised. |
||||
|
* |
||||
|
* @return True if supported, otherwise false. |
||||
|
*/ |
||||
|
bool IsDelayChannelMappingChanged() const; |
||||
|
|
||||
|
/** |
||||
|
* Check if new channel mappings are used for Reverb commands. |
||||
|
* Older commands used: |
||||
|
* front left/front right/back left/back right/center/lfe |
||||
|
* Whereas everywhere else in the code uses: |
||||
|
* front left/front right/center/lfe/back left/back right |
||||
|
* This corrects that and makes everything standardised. |
||||
|
* |
||||
|
* @return True if supported, otherwise false. |
||||
|
*/ |
||||
|
bool IsReverbChannelMappingChanged() const; |
||||
|
|
||||
|
/** |
||||
|
* Check if new channel mappings are used for I3dl2Reverb commands. |
||||
|
* Older commands used: |
||||
|
* front left/front right/back left/back right/center/lfe |
||||
|
* Whereas everywhere else in the code uses: |
||||
|
* front left/front right/center/lfe/back left/back right |
||||
|
* This corrects that and makes everything standardised. |
||||
|
* |
||||
|
* @return True if supported, otherwise false. |
||||
|
*/ |
||||
|
bool IsI3dl2ReverbChannelMappingChanged() const; |
||||
|
|
||||
|
/// Host version |
||||
|
u32 process_revision; |
||||
|
/// User version |
||||
|
u32 user_revision{}; |
||||
|
/// Behaviour flags |
||||
|
Flags flags{}; |
||||
|
/// Errors generated and reported during Update |
||||
|
std::array<ErrorInfo, MaxErrors> errors{}; |
||||
|
/// Error count |
||||
|
u32 error_count{}; |
||||
|
}; |
||||
|
|
||||
|
} // namespace AudioCore::AudioRenderer |
||||
@ -0,0 +1,539 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
|
||||
|
#include "audio_core/common/feature_support.h"
|
||||
|
#include "audio_core/renderer/behavior/behavior_info.h"
|
||||
|
#include "audio_core/renderer/behavior/info_updater.h"
|
||||
|
#include "audio_core/renderer/effect/effect_context.h"
|
||||
|
#include "audio_core/renderer/effect/effect_reset.h"
|
||||
|
#include "audio_core/renderer/memory/memory_pool_info.h"
|
||||
|
#include "audio_core/renderer/mix/mix_context.h"
|
||||
|
#include "audio_core/renderer/performance/performance_manager.h"
|
||||
|
#include "audio_core/renderer/sink/circular_buffer_sink_info.h"
|
||||
|
#include "audio_core/renderer/sink/device_sink_info.h"
|
||||
|
#include "audio_core/renderer/sink/sink_context.h"
|
||||
|
#include "audio_core/renderer/splitter/splitter_context.h"
|
||||
|
#include "audio_core/renderer/voice/voice_context.h"
|
||||
|
|
||||
|
namespace AudioCore::AudioRenderer { |
||||
|
|
||||
|
InfoUpdater::InfoUpdater(std::span<const u8> input_, std::span<u8> output_, |
||||
|
const u32 process_handle_, BehaviorInfo& behaviour_) |
||||
|
: input{input_.data() + sizeof(UpdateDataHeader)}, |
||||
|
input_origin{input_}, output{output_.data() + sizeof(UpdateDataHeader)}, |
||||
|
output_origin{output_}, in_header{reinterpret_cast<const UpdateDataHeader*>( |
||||
|
input_origin.data())}, |
||||
|
out_header{reinterpret_cast<UpdateDataHeader*>(output_origin.data())}, |
||||
|
expected_input_size{input_.size()}, expected_output_size{output_.size()}, |
||||
|
process_handle{process_handle_}, behaviour{behaviour_} { |
||||
|
std::construct_at<UpdateDataHeader>(out_header, behaviour.GetProcessRevision()); |
||||
|
} |
||||
|
|
||||
|
Result InfoUpdater::UpdateVoiceChannelResources(VoiceContext& voice_context) { |
||||
|
const auto voice_count{voice_context.GetCount()}; |
||||
|
std::span<const VoiceChannelResource::InParameter> in_params{ |
||||
|
reinterpret_cast<const VoiceChannelResource::InParameter*>(input), voice_count}; |
||||
|
|
||||
|
for (u32 i = 0; i < voice_count; i++) { |
||||
|
auto& resource{voice_context.GetChannelResource(i)}; |
||||
|
resource.in_use = in_params[i].in_use; |
||||
|
if (in_params[i].in_use) { |
||||
|
resource.mix_volumes = in_params[i].mix_volumes; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
const auto consumed_input_size{voice_count * |
||||
|
static_cast<u32>(sizeof(VoiceChannelResource::InParameter))}; |
||||
|
if (consumed_input_size != in_header->voice_resources_size) { |
||||
|
LOG_ERROR(Service_Audio, |
||||
|
"Consumed an incorrect voice resource size, header size={}, consumed={}", |
||||
|
in_header->voice_resources_size, consumed_input_size); |
||||
|
return Service::Audio::ERR_INVALID_UPDATE_DATA; |
||||
|
} |
||||
|
|
||||
|
input += consumed_input_size; |
||||
|
return ResultSuccess; |
||||
|
} |
||||
|
|
||||
|
Result InfoUpdater::UpdateVoices(VoiceContext& voice_context, |
||||
|
std::span<MemoryPoolInfo> memory_pools, |
||||
|
const u32 memory_pool_count) { |
||||
|
const PoolMapper pool_mapper(process_handle, memory_pools, memory_pool_count, |
||||
|
behaviour.IsMemoryForceMappingEnabled()); |
||||
|
const auto voice_count{voice_context.GetCount()}; |
||||
|
std::span<const VoiceInfo::InParameter> in_params{ |
||||
|
reinterpret_cast<const VoiceInfo::InParameter*>(input), voice_count}; |
||||
|
std::span<VoiceInfo::OutStatus> out_params{reinterpret_cast<VoiceInfo::OutStatus*>(output), |
||||
|
voice_count}; |
||||
|
|
||||
|
for (u32 i = 0; i < voice_count; i++) { |
||||
|
auto& voice_info{voice_context.GetInfo(i)}; |
||||
|
voice_info.in_use = false; |
||||
|
} |
||||
|
|
||||
|
u32 new_voice_count{0}; |
||||
|
|
||||
|
for (u32 i = 0; i < voice_count; i++) { |
||||
|
const auto& in_param{in_params[i]}; |
||||
|
std::array<VoiceState*, MaxChannels> voice_states{}; |
||||
|
|
||||
|
if (!in_param.in_use) { |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
auto& voice_info{voice_context.GetInfo(in_param.id)}; |
||||
|
|
||||
|
for (u32 channel = 0; channel < in_param.channel_count; channel++) { |
||||
|
voice_states[channel] = &voice_context.GetState(in_param.channel_resource_ids[channel]); |
||||
|
} |
||||
|
|
||||
|
if (in_param.is_new) { |
||||
|
voice_info.Initialize(); |
||||
|
|
||||
|
for (u32 channel = 0; channel < in_param.channel_count; channel++) { |
||||
|
std::memset(voice_states[channel], 0, sizeof(VoiceState)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
BehaviorInfo::ErrorInfo update_error{}; |
||||
|
voice_info.UpdateParameters(update_error, in_param, pool_mapper, behaviour); |
||||
|
|
||||
|
if (!update_error.error_code.IsSuccess()) { |
||||
|
behaviour.AppendError(update_error); |
||||
|
} |
||||
|
|
||||
|
std::array<std::array<BehaviorInfo::ErrorInfo, 2>, MaxWaveBuffers> wavebuffer_errors{}; |
||||
|
voice_info.UpdateWaveBuffers(wavebuffer_errors, MaxWaveBuffers * 2, in_param, voice_states, |
||||
|
pool_mapper, behaviour); |
||||
|
|
||||
|
for (auto& wavebuffer_error : wavebuffer_errors) { |
||||
|
for (auto& error : wavebuffer_error) { |
||||
|
if (error.error_code.IsError()) { |
||||
|
behaviour.AppendError(error); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
voice_info.WriteOutStatus(out_params[i], in_param, voice_states); |
||||
|
new_voice_count += in_param.channel_count; |
||||
|
} |
||||
|
|
||||
|
auto consumed_input_size{voice_count * static_cast<u32>(sizeof(VoiceInfo::InParameter))}; |
||||
|
auto consumed_output_size{voice_count * static_cast<u32>(sizeof(VoiceInfo::OutStatus))}; |
||||
|
if (consumed_input_size != in_header->voices_size) { |
||||
|
LOG_ERROR(Service_Audio, "Consumed an incorrect voices size, header size={}, consumed={}", |
||||
|
in_header->voices_size, consumed_input_size); |
||||
|
return Service::Audio::ERR_INVALID_UPDATE_DATA; |
||||
|
} |
||||
|
|
||||
|
out_header->voices_size = consumed_output_size; |
||||
|
out_header->size += consumed_output_size; |
||||
|
input += consumed_input_size; |
||||
|
output += consumed_output_size; |
||||
|
|
||||
|
voice_context.SetActiveCount(new_voice_count); |
||||
|
|
||||
|
return ResultSuccess; |
||||
|
} |
||||
|
|
||||
|
Result InfoUpdater::UpdateEffects(EffectContext& effect_context, const bool renderer_active, |
||||
|
std::span<MemoryPoolInfo> memory_pools, |
||||
|
const u32 memory_pool_count) { |
||||
|
if (behaviour.IsEffectInfoVersion2Supported()) { |
||||
|
return UpdateEffectsVersion2(effect_context, renderer_active, memory_pools, |
||||
|
memory_pool_count); |
||||
|
} else { |
||||
|
return UpdateEffectsVersion1(effect_context, renderer_active, memory_pools, |
||||
|
memory_pool_count); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
Result InfoUpdater::UpdateEffectsVersion1(EffectContext& effect_context, const bool renderer_active, |
||||
|
std::span<MemoryPoolInfo> memory_pools, |
||||
|
const u32 memory_pool_count) { |
||||
|
PoolMapper pool_mapper(process_handle, memory_pools, memory_pool_count, |
||||
|
behaviour.IsMemoryForceMappingEnabled()); |
||||
|
|
||||
|
const auto effect_count{effect_context.GetCount()}; |
||||
|
|
||||
|
std::span<const EffectInfoBase::InParameterVersion1> in_params{ |
||||
|
reinterpret_cast<const EffectInfoBase::InParameterVersion1*>(input), effect_count}; |
||||
|
std::span<EffectInfoBase::OutStatusVersion1> out_params{ |
||||
|
reinterpret_cast<EffectInfoBase::OutStatusVersion1*>(output), effect_count}; |
||||
|
|
||||
|
for (u32 i = 0; i < effect_count; i++) { |
||||
|
auto effect_info{&effect_context.GetInfo(i)}; |
||||
|
if (effect_info->GetType() != in_params[i].type) { |
||||
|
effect_info->ForceUnmapBuffers(pool_mapper); |
||||
|
ResetEffect(effect_info, in_params[i].type); |
||||
|
} |
||||
|
|
||||
|
BehaviorInfo::ErrorInfo error_info{}; |
||||
|
effect_info->Update(error_info, in_params[i], pool_mapper); |
||||
|
if (error_info.error_code.IsError()) { |
||||
|
behaviour.AppendError(error_info); |
||||
|
} |
||||
|
|
||||
|
effect_info->StoreStatus(out_params[i], renderer_active); |
||||
|
} |
||||
|
|
||||
|
auto consumed_input_size{effect_count * |
||||
|
static_cast<u32>(sizeof(EffectInfoBase::InParameterVersion1))}; |
||||
|
auto consumed_output_size{effect_count * |
||||
|
static_cast<u32>(sizeof(EffectInfoBase::OutStatusVersion1))}; |
||||
|
if (consumed_input_size != in_header->effects_size) { |
||||
|
LOG_ERROR(Service_Audio, "Consumed an incorrect effects size, header size={}, consumed={}", |
||||
|
in_header->effects_size, consumed_input_size); |
||||
|
return Service::Audio::ERR_INVALID_UPDATE_DATA; |
||||
|
} |
||||
|
|
||||
|
out_header->effects_size = consumed_output_size; |
||||
|
out_header->size += consumed_output_size; |
||||
|
input += consumed_input_size; |
||||
|
output += consumed_output_size; |
||||
|
|
||||
|
return ResultSuccess; |
||||
|
} |
||||
|
|
||||
|
Result InfoUpdater::UpdateEffectsVersion2(EffectContext& effect_context, const bool renderer_active, |
||||
|
std::span<MemoryPoolInfo> memory_pools, |
||||
|
const u32 memory_pool_count) { |
||||
|
PoolMapper pool_mapper(process_handle, memory_pools, memory_pool_count, |
||||
|
behaviour.IsMemoryForceMappingEnabled()); |
||||
|
|
||||
|
const auto effect_count{effect_context.GetCount()}; |
||||
|
|
||||
|
std::span<const EffectInfoBase::InParameterVersion2> in_params{ |
||||
|
reinterpret_cast<const EffectInfoBase::InParameterVersion2*>(input), effect_count}; |
||||
|
std::span<EffectInfoBase::OutStatusVersion2> out_params{ |
||||
|
reinterpret_cast<EffectInfoBase::OutStatusVersion2*>(output), effect_count}; |
||||
|
|
||||
|
for (u32 i = 0; i < effect_count; i++) { |
||||
|
auto effect_info{&effect_context.GetInfo(i)}; |
||||
|
if (effect_info->GetType() != in_params[i].type) { |
||||
|
effect_info->ForceUnmapBuffers(pool_mapper); |
||||
|
ResetEffect(effect_info, in_params[i].type); |
||||
|
} |
||||
|
|
||||
|
BehaviorInfo::ErrorInfo error_info{}; |
||||
|
effect_info->Update(error_info, in_params[i], pool_mapper); |
||||
|
|
||||
|
if (error_info.error_code.IsError()) { |
||||
|
behaviour.AppendError(error_info); |
||||
|
} |
||||
|
|
||||
|
effect_info->StoreStatus(out_params[i], renderer_active); |
||||
|
|
||||
|
if (in_params[i].is_new) { |
||||
|
effect_info->InitializeResultState(effect_context.GetDspSharedResultState(i)); |
||||
|
effect_info->InitializeResultState(effect_context.GetResultState(i)); |
||||
|
} |
||||
|
effect_info->UpdateResultState(out_params[i].result_state, |
||||
|
effect_context.GetResultState(i)); |
||||
|
} |
||||
|
|
||||
|
auto consumed_input_size{effect_count * |
||||
|
static_cast<u32>(sizeof(EffectInfoBase::InParameterVersion2))}; |
||||
|
auto consumed_output_size{effect_count * |
||||
|
static_cast<u32>(sizeof(EffectInfoBase::OutStatusVersion2))}; |
||||
|
if (consumed_input_size != in_header->effects_size) { |
||||
|
LOG_ERROR(Service_Audio, "Consumed an incorrect effects size, header size={}, consumed={}", |
||||
|
in_header->effects_size, consumed_input_size); |
||||
|
return Service::Audio::ERR_INVALID_UPDATE_DATA; |
||||
|
} |
||||
|
|
||||
|
out_header->effects_size = consumed_output_size; |
||||
|
out_header->size += consumed_output_size; |
||||
|
input += consumed_input_size; |
||||
|
output += consumed_output_size; |
||||
|
|
||||
|
return ResultSuccess; |
||||
|
} |
||||
|
|
||||
|
Result InfoUpdater::UpdateMixes(MixContext& mix_context, const u32 mix_buffer_count, |
||||
|
EffectContext& effect_context, SplitterContext& splitter_context) { |
||||
|
s32 mix_count{0}; |
||||
|
u32 consumed_input_size{0}; |
||||
|
|
||||
|
if (behaviour.IsMixInParameterDirtyOnlyUpdateSupported()) { |
||||
|
auto in_dirty_params{reinterpret_cast<const MixInfo::InDirtyParameter*>(input)}; |
||||
|
mix_count = in_dirty_params->count; |
||||
|
input += sizeof(MixInfo::InDirtyParameter); |
||||
|
consumed_input_size = static_cast<u32>(sizeof(MixInfo::InDirtyParameter) + |
||||
|
mix_count * sizeof(MixInfo::InParameter)); |
||||
|
} else { |
||||
|
mix_count = mix_context.GetCount(); |
||||
|
consumed_input_size = static_cast<u32>(mix_count * sizeof(MixInfo::InParameter)); |
||||
|
} |
||||
|
|
||||
|
if (mix_buffer_count == 0) { |
||||
|
return Service::Audio::ERR_INVALID_UPDATE_DATA; |
||||
|
} |
||||
|
|
||||
|
std::span<const MixInfo::InParameter> in_params{ |
||||
|
reinterpret_cast<const MixInfo::InParameter*>(input), static_cast<size_t>(mix_count)}; |
||||
|
|
||||
|
u32 total_buffer_count{0}; |
||||
|
for (s32 i = 0; i < mix_count; i++) { |
||||
|
const auto& params{in_params[i]}; |
||||
|
|
||||
|
if (params.in_use) { |
||||
|
total_buffer_count += params.buffer_count; |
||||
|
if (params.dest_mix_id > static_cast<s32>(mix_context.GetCount()) && |
||||
|
params.dest_mix_id != UnusedMixId && params.mix_id != FinalMixId) { |
||||
|
return Service::Audio::ERR_INVALID_UPDATE_DATA; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (total_buffer_count > mix_buffer_count) { |
||||
|
return Service::Audio::ERR_INVALID_UPDATE_DATA; |
||||
|
} |
||||
|
|
||||
|
bool mix_dirty{false}; |
||||
|
for (s32 i = 0; i < mix_count; i++) { |
||||
|
const auto& params{in_params[i]}; |
||||
|
|
||||
|
s32 mix_id{i}; |
||||
|
if (behaviour.IsMixInParameterDirtyOnlyUpdateSupported()) { |
||||
|
mix_id = params.mix_id; |
||||
|
} |
||||
|
|
||||
|
auto mix_info{mix_context.GetInfo(mix_id)}; |
||||
|
if (mix_info->in_use != params.in_use) { |
||||
|
mix_info->in_use = params.in_use; |
||||
|
if (!params.in_use) { |
||||
|
mix_info->ClearEffectProcessingOrder(); |
||||
|
} |
||||
|
mix_dirty = true; |
||||
|
} |
||||
|
|
||||
|
if (params.in_use) { |
||||
|
mix_dirty |= mix_info->Update(mix_context.GetEdgeMatrix(), params, effect_context, |
||||
|
splitter_context, behaviour); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (mix_dirty) { |
||||
|
if (behaviour.IsSplitterSupported() && splitter_context.UsingSplitter()) { |
||||
|
if (!mix_context.TSortInfo(splitter_context)) { |
||||
|
return Service::Audio::ERR_INVALID_UPDATE_DATA; |
||||
|
} |
||||
|
} else { |
||||
|
mix_context.SortInfo(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (consumed_input_size != in_header->mix_size) { |
||||
|
LOG_ERROR(Service_Audio, "Consumed an incorrect mixes size, header size={}, consumed={}", |
||||
|
in_header->mix_size, consumed_input_size); |
||||
|
return Service::Audio::ERR_INVALID_UPDATE_DATA; |
||||
|
} |
||||
|
|
||||
|
input += mix_count * sizeof(MixInfo::InParameter); |
||||
|
|
||||
|
return ResultSuccess; |
||||
|
} |
||||
|
|
||||
|
Result InfoUpdater::UpdateSinks(SinkContext& sink_context, std::span<MemoryPoolInfo> memory_pools, |
||||
|
const u32 memory_pool_count) { |
||||
|
PoolMapper pool_mapper(process_handle, memory_pools, memory_pool_count, |
||||
|
behaviour.IsMemoryForceMappingEnabled()); |
||||
|
|
||||
|
std::span<const SinkInfoBase::InParameter> in_params{ |
||||
|
reinterpret_cast<const SinkInfoBase::InParameter*>(input), memory_pool_count}; |
||||
|
std::span<SinkInfoBase::OutStatus> out_params{ |
||||
|
reinterpret_cast<SinkInfoBase::OutStatus*>(output), memory_pool_count}; |
||||
|
|
||||
|
const auto sink_count{sink_context.GetCount()}; |
||||
|
|
||||
|
for (u32 i = 0; i < sink_count; i++) { |
||||
|
const auto& params{in_params[i]}; |
||||
|
auto sink_info{sink_context.GetInfo(i)}; |
||||
|
|
||||
|
if (sink_info->GetType() != params.type) { |
||||
|
sink_info->CleanUp(); |
||||
|
switch (params.type) { |
||||
|
case SinkInfoBase::Type::Invalid: |
||||
|
std::construct_at<SinkInfoBase>(reinterpret_cast<SinkInfoBase*>(sink_info)); |
||||
|
break; |
||||
|
case SinkInfoBase::Type::DeviceSink: |
||||
|
std::construct_at<DeviceSinkInfo>(reinterpret_cast<DeviceSinkInfo*>(sink_info)); |
||||
|
break; |
||||
|
case SinkInfoBase::Type::CircularBufferSink: |
||||
|
std::construct_at<CircularBufferSinkInfo>( |
||||
|
reinterpret_cast<CircularBufferSinkInfo*>(sink_info)); |
||||
|
break; |
||||
|
default: |
||||
|
LOG_ERROR(Service_Audio, "Invalid sink type {}", static_cast<u32>(params.type)); |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
BehaviorInfo::ErrorInfo error_info{}; |
||||
|
sink_info->Update(error_info, out_params[i], params, pool_mapper); |
||||
|
|
||||
|
if (error_info.error_code.IsError()) { |
||||
|
behaviour.AppendError(error_info); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
const auto consumed_input_size{sink_count * |
||||
|
static_cast<u32>(sizeof(SinkInfoBase::InParameter))}; |
||||
|
const auto consumed_output_size{sink_count * static_cast<u32>(sizeof(SinkInfoBase::OutStatus))}; |
||||
|
if (consumed_input_size != in_header->sinks_size) { |
||||
|
LOG_ERROR(Service_Audio, "Consumed an incorrect sinks size, header size={}, consumed={}", |
||||
|
in_header->sinks_size, consumed_input_size); |
||||
|
return Service::Audio::ERR_INVALID_UPDATE_DATA; |
||||
|
} |
||||
|
|
||||
|
input += consumed_input_size; |
||||
|
output += consumed_output_size; |
||||
|
out_header->sinks_size = consumed_output_size; |
||||
|
out_header->size += consumed_output_size; |
||||
|
|
||||
|
return ResultSuccess; |
||||
|
} |
||||
|
|
||||
|
Result InfoUpdater::UpdateMemoryPools(std::span<MemoryPoolInfo> memory_pools, |
||||
|
const u32 memory_pool_count) { |
||||
|
PoolMapper pool_mapper(process_handle, memory_pools, memory_pool_count, |
||||
|
behaviour.IsMemoryForceMappingEnabled()); |
||||
|
std::span<const MemoryPoolInfo::InParameter> in_params{ |
||||
|
reinterpret_cast<const MemoryPoolInfo::InParameter*>(input), memory_pool_count}; |
||||
|
std::span<MemoryPoolInfo::OutStatus> out_params{ |
||||
|
reinterpret_cast<MemoryPoolInfo::OutStatus*>(output), memory_pool_count}; |
||||
|
|
||||
|
for (size_t i = 0; i < memory_pool_count; i++) { |
||||
|
auto state{pool_mapper.Update(memory_pools[i], in_params[i], out_params[i])}; |
||||
|
if (state != MemoryPoolInfo::ResultState::Success && |
||||
|
state != MemoryPoolInfo::ResultState::BadParam && |
||||
|
state != MemoryPoolInfo::ResultState::MapFailed && |
||||
|
state != MemoryPoolInfo::ResultState::InUse) { |
||||
|
LOG_WARNING(Service_Audio, "Invalid ResultState from updating memory pools"); |
||||
|
return Service::Audio::ERR_INVALID_UPDATE_DATA; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
const auto consumed_input_size{memory_pool_count * |
||||
|
static_cast<u32>(sizeof(MemoryPoolInfo::InParameter))}; |
||||
|
const auto consumed_output_size{memory_pool_count * |
||||
|
static_cast<u32>(sizeof(MemoryPoolInfo::OutStatus))}; |
||||
|
if (consumed_input_size != in_header->memory_pool_size) { |
||||
|
LOG_ERROR(Service_Audio, |
||||
|
"Consumed an incorrect memory pool size, header size={}, consumed={}", |
||||
|
in_header->memory_pool_size, consumed_input_size); |
||||
|
return Service::Audio::ERR_INVALID_UPDATE_DATA; |
||||
|
} |
||||
|
|
||||
|
input += consumed_input_size; |
||||
|
output += consumed_output_size; |
||||
|
out_header->memory_pool_size = consumed_output_size; |
||||
|
out_header->size += consumed_output_size; |
||||
|
return ResultSuccess; |
||||
|
} |
||||
|
|
||||
|
Result InfoUpdater::UpdatePerformanceBuffer(std::span<u8> performance_output, |
||||
|
const u64 performance_output_size, |
||||
|
PerformanceManager* performance_manager) { |
||||
|
auto in_params{reinterpret_cast<const PerformanceManager::InParameter*>(input)}; |
||||
|
auto out_params{reinterpret_cast<PerformanceManager::OutStatus*>(output)}; |
||||
|
|
||||
|
if (performance_manager != nullptr) { |
||||
|
out_params->history_size = |
||||
|
performance_manager->CopyHistories(performance_output.data(), performance_output_size); |
||||
|
performance_manager->SetDetailTarget(in_params->target_node_id); |
||||
|
} else { |
||||
|
out_params->history_size = 0; |
||||
|
} |
||||
|
|
||||
|
const auto consumed_input_size{static_cast<u32>(sizeof(PerformanceManager::InParameter))}; |
||||
|
const auto consumed_output_size{static_cast<u32>(sizeof(PerformanceManager::OutStatus))}; |
||||
|
if (consumed_input_size != in_header->performance_buffer_size) { |
||||
|
LOG_ERROR(Service_Audio, |
||||
|
"Consumed an incorrect performance size, header size={}, consumed={}", |
||||
|
in_header->performance_buffer_size, consumed_input_size); |
||||
|
return Service::Audio::ERR_INVALID_UPDATE_DATA; |
||||
|
} |
||||
|
|
||||
|
input += consumed_input_size; |
||||
|
output += consumed_output_size; |
||||
|
out_header->performance_buffer_size = consumed_output_size; |
||||
|
out_header->size += consumed_output_size; |
||||
|
return ResultSuccess; |
||||
|
} |
||||
|
|
||||
|
Result InfoUpdater::UpdateBehaviorInfo(BehaviorInfo& behaviour_) { |
||||
|
const auto in_params{reinterpret_cast<const BehaviorInfo::InParameter*>(input)}; |
||||
|
|
||||
|
if (!CheckValidRevision(in_params->revision)) { |
||||
|
return Service::Audio::ERR_INVALID_UPDATE_DATA; |
||||
|
} |
||||
|
|
||||
|
if (in_params->revision != behaviour_.GetUserRevision()) { |
||||
|
return Service::Audio::ERR_INVALID_UPDATE_DATA; |
||||
|
} |
||||
|
|
||||
|
behaviour_.ClearError(); |
||||
|
behaviour_.UpdateFlags(in_params->flags); |
||||
|
|
||||
|
if (in_header->behaviour_size != sizeof(BehaviorInfo::InParameter)) { |
||||
|
return Service::Audio::ERR_INVALID_UPDATE_DATA; |
||||
|
} |
||||
|
|
||||
|
input += sizeof(BehaviorInfo::InParameter); |
||||
|
return ResultSuccess; |
||||
|
} |
||||
|
|
||||
|
Result InfoUpdater::UpdateErrorInfo(BehaviorInfo& behaviour_) { |
||||
|
auto out_params{reinterpret_cast<BehaviorInfo::OutStatus*>(output)}; |
||||
|
behaviour_.CopyErrorInfo(out_params->errors, out_params->error_count); |
||||
|
|
||||
|
const auto consumed_output_size{static_cast<u32>(sizeof(BehaviorInfo::OutStatus))}; |
||||
|
|
||||
|
output += consumed_output_size; |
||||
|
out_header->behaviour_size = consumed_output_size; |
||||
|
out_header->size += consumed_output_size; |
||||
|
return ResultSuccess; |
||||
|
} |
||||
|
|
||||
|
Result InfoUpdater::UpdateSplitterInfo(SplitterContext& splitter_context) { |
||||
|
u32 consumed_size{0}; |
||||
|
if (!splitter_context.Update(input, consumed_size)) { |
||||
|
return Service::Audio::ERR_INVALID_UPDATE_DATA; |
||||
|
} |
||||
|
|
||||
|
input += consumed_size; |
||||
|
|
||||
|
return ResultSuccess; |
||||
|
} |
||||
|
|
||||
|
Result InfoUpdater::UpdateRendererInfo(const u64 elapsed_frames) { |
||||
|
struct RenderInfo { |
||||
|
/* 0x00 */ u64 frames_elapsed; |
||||
|
/* 0x08 */ char unk08[0x8]; |
||||
|
}; |
||||
|
static_assert(sizeof(RenderInfo) == 0x10, "RenderInfo has the wrong size!"); |
||||
|
|
||||
|
auto out_params{reinterpret_cast<RenderInfo*>(output)}; |
||||
|
out_params->frames_elapsed = elapsed_frames; |
||||
|
|
||||
|
const auto consumed_output_size{static_cast<u32>(sizeof(RenderInfo))}; |
||||
|
|
||||
|
output += consumed_output_size; |
||||
|
out_header->render_info_size = consumed_output_size; |
||||
|
out_header->size += consumed_output_size; |
||||
|
|
||||
|
return ResultSuccess; |
||||
|
} |
||||
|
|
||||
|
Result InfoUpdater::CheckConsumedSize() { |
||||
|
if (CpuAddr(input) - CpuAddr(input_origin.data()) != expected_input_size) { |
||||
|
return Service::Audio::ERR_INVALID_UPDATE_DATA; |
||||
|
} else if (CpuAddr(output) - CpuAddr(output_origin.data()) != expected_output_size) { |
||||
|
return Service::Audio::ERR_INVALID_UPDATE_DATA; |
||||
|
} |
||||
|
return ResultSuccess; |
||||
|
} |
||||
|
|
||||
|
} // namespace AudioCore::AudioRenderer
|
||||
@ -0,0 +1,205 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project |
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <span> |
||||
|
|
||||
|
#include "common/common_types.h" |
||||
|
#include "core/hle/service/audio/errors.h" |
||||
|
|
||||
|
namespace AudioCore::AudioRenderer { |
||||
|
class BehaviorInfo; |
||||
|
class VoiceContext; |
||||
|
class MixContext; |
||||
|
class SinkContext; |
||||
|
class SplitterContext; |
||||
|
class EffectContext; |
||||
|
class MemoryPoolInfo; |
||||
|
class PerformanceManager; |
||||
|
|
||||
|
class InfoUpdater { |
||||
|
struct UpdateDataHeader { |
||||
|
explicit UpdateDataHeader(u32 revision_) : revision{revision_} {} |
||||
|
|
||||
|
/* 0x00 */ u32 revision; |
||||
|
/* 0x04 */ u32 behaviour_size{}; |
||||
|
/* 0x08 */ u32 memory_pool_size{}; |
||||
|
/* 0x0C */ u32 voices_size{}; |
||||
|
/* 0x10 */ u32 voice_resources_size{}; |
||||
|
/* 0x14 */ u32 effects_size{}; |
||||
|
/* 0x18 */ u32 mix_size{}; |
||||
|
/* 0x1C */ u32 sinks_size{}; |
||||
|
/* 0x20 */ u32 performance_buffer_size{}; |
||||
|
/* 0x24 */ char unk24[4]; |
||||
|
/* 0x28 */ u32 render_info_size{}; |
||||
|
/* 0x2C */ char unk2C[0x10]; |
||||
|
/* 0x3C */ u32 size{sizeof(UpdateDataHeader)}; |
||||
|
}; |
||||
|
static_assert(sizeof(UpdateDataHeader) == 0x40, "UpdateDataHeader has the wrong size!"); |
||||
|
|
||||
|
public: |
||||
|
explicit InfoUpdater(std::span<const u8> input, std::span<u8> output, u32 process_handle, |
||||
|
BehaviorInfo& behaviour); |
||||
|
|
||||
|
/** |
||||
|
* Update the voice channel resources. |
||||
|
* |
||||
|
* @param voice_context - Voice context to update. |
||||
|
* @return Result code. |
||||
|
*/ |
||||
|
Result UpdateVoiceChannelResources(VoiceContext& voice_context); |
||||
|
|
||||
|
/** |
||||
|
* Update voices. |
||||
|
* |
||||
|
* @param voice_context - Voice context to update. |
||||
|
* @param memory_pools - Memory pools to use for these voices. |
||||
|
* @param memory_pool_count - Number of memory pools. |
||||
|
* @return Result code. |
||||
|
*/ |
||||
|
Result UpdateVoices(VoiceContext& voice_context, std::span<MemoryPoolInfo> memory_pools, |
||||
|
u32 memory_pool_count); |
||||
|
|
||||
|
/** |
||||
|
* Update effects. |
||||
|
* |
||||
|
* @param effect_context - Effect context to update. |
||||
|
* @param renderer_active - Whether the AudioRenderer is active. |
||||
|
* @param memory_pools - Memory pools to use for these voices. |
||||
|
* @param memory_pool_count - Number of memory pools. |
||||
|
* @return Result code. |
||||
|
*/ |
||||
|
Result UpdateEffects(EffectContext& effect_context, bool renderer_active, |
||||
|
std::span<MemoryPoolInfo> memory_pools, u32 memory_pool_count); |
||||
|
|
||||
|
/** |
||||
|
* Update mixes. |
||||
|
* |
||||
|
* @param mix_context - Mix context to update. |
||||
|
* @param mix_buffer_count - Number of mix buffers. |
||||
|
* @param effect_context - Effect context to update effort order. |
||||
|
* @param splitter_context - Splitter context for the mixes. |
||||
|
* @return Result code. |
||||
|
*/ |
||||
|
Result UpdateMixes(MixContext& mix_context, u32 mix_buffer_count, EffectContext& effect_context, |
||||
|
SplitterContext& splitter_context); |
||||
|
|
||||
|
/** |
||||
|
* Update sinks. |
||||
|
* |
||||
|
* @param sink_context - Sink context to update. |
||||
|
* @param memory_pools - Memory pools to use for these voices. |
||||
|
* @param memory_pool_count - Number of memory pools. |
||||
|
* @return Result code. |
||||
|
*/ |
||||
|
Result UpdateSinks(SinkContext& sink_context, std::span<MemoryPoolInfo> memory_pools, |
||||
|
u32 memory_pool_count); |
||||
|
|
||||
|
/** |
||||
|
* Update memory pools. |
||||
|
* |
||||
|
* @param memory_pools - Memory pools to use for these voices. |
||||
|
* @param memory_pool_count - Number of memory pools. |
||||
|
* @return Result code. |
||||
|
*/ |
||||
|
Result UpdateMemoryPools(std::span<MemoryPoolInfo> memory_pools, u32 memory_pool_count); |
||||
|
|
||||
|
/** |
||||
|
* Update the performance buffer. |
||||
|
* |
||||
|
* @param output - Output buffer for performance metrics. |
||||
|
* @param output_size - Output buffer size. |
||||
|
* @param performance_manager - Performance manager.. |
||||
|
* @return Result code. |
||||
|
*/ |
||||
|
Result UpdatePerformanceBuffer(std::span<u8> output, u64 output_size, |
||||
|
PerformanceManager* performance_manager); |
||||
|
|
||||
|
/** |
||||
|
* Update behaviour. |
||||
|
* |
||||
|
* @param behaviour - Behaviour to update. |
||||
|
* @return Result code. |
||||
|
*/ |
||||
|
Result UpdateBehaviorInfo(BehaviorInfo& behaviour); |
||||
|
|
||||
|
/** |
||||
|
* Update errors. |
||||
|
* |
||||
|
* @param behaviour - Behaviour to update. |
||||
|
* @return Result code. |
||||
|
*/ |
||||
|
Result UpdateErrorInfo(BehaviorInfo& behaviour); |
||||
|
|
||||
|
/** |
||||
|
* Update splitter. |
||||
|
* |
||||
|
* @param splitter_context - Splitter context to update. |
||||
|
* @return Result code. |
||||
|
*/ |
||||
|
Result UpdateSplitterInfo(SplitterContext& splitter_context); |
||||
|
|
||||
|
/** |
||||
|
* Update renderer info. |
||||
|
* |
||||
|
* @param elapsed_frames - Number of elapsed frames. |
||||
|
* @return Result code. |
||||
|
*/ |
||||
|
Result UpdateRendererInfo(u64 elapsed_frames); |
||||
|
|
||||
|
/** |
||||
|
* Check that the input.output sizes match their expected values. |
||||
|
* |
||||
|
* @return Result code. |
||||
|
*/ |
||||
|
Result CheckConsumedSize(); |
||||
|
|
||||
|
private: |
||||
|
/** |
||||
|
* Update effects version 1. |
||||
|
* |
||||
|
* @param effect_context - Effect context to update. |
||||
|
* @param renderer_active - Is the AudioRenderer active? |
||||
|
* @param memory_pools - Memory pools to use for these voices. |
||||
|
* @param memory_pool_count - Number of memory pools. |
||||
|
* @return Result code. |
||||
|
*/ |
||||
|
Result UpdateEffectsVersion1(EffectContext& effect_context, bool renderer_active, |
||||
|
std::span<MemoryPoolInfo> memory_pools, u32 memory_pool_count); |
||||
|
|
||||
|
/** |
||||
|
* Update effects version 2. |
||||
|
* |
||||
|
* @param effect_context - Effect context to update. |
||||
|
* @param renderer_active - Is the AudioRenderer active? |
||||
|
* @param memory_pools - Memory pools to use for these voices. |
||||
|
* @param memory_pool_count - Number of memory pools. |
||||
|
* @return Result code. |
||||
|
*/ |
||||
|
Result UpdateEffectsVersion2(EffectContext& effect_context, bool renderer_active, |
||||
|
std::span<MemoryPoolInfo> memory_pools, u32 memory_pool_count); |
||||
|
|
||||
|
/// Input buffer |
||||
|
u8 const* input; |
||||
|
/// Input buffer start |
||||
|
std::span<const u8> input_origin; |
||||
|
/// Output buffer start |
||||
|
u8* output; |
||||
|
/// Output buffer start |
||||
|
std::span<u8> output_origin; |
||||
|
/// Input header |
||||
|
const UpdateDataHeader* in_header; |
||||
|
/// Output header |
||||
|
UpdateDataHeader* out_header; |
||||
|
/// Expected input size, see CheckConsumedSize |
||||
|
u64 expected_input_size; |
||||
|
/// Expected output size, see CheckConsumedSize |
||||
|
u64 expected_output_size; |
||||
|
/// Unused |
||||
|
u32 process_handle; |
||||
|
/// Behaviour |
||||
|
BehaviorInfo& behaviour; |
||||
|
}; |
||||
|
|
||||
|
} // namespace AudioCore::AudioRenderer |
||||
@ -0,0 +1,714 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
|
||||
|
#include "audio_core/renderer/behavior/behavior_info.h"
|
||||
|
#include "audio_core/renderer/command/command_buffer.h"
|
||||
|
#include "audio_core/renderer/command/command_list_header.h"
|
||||
|
#include "audio_core/renderer/command/command_processing_time_estimator.h"
|
||||
|
#include "audio_core/renderer/effect/biquad_filter.h"
|
||||
|
#include "audio_core/renderer/effect/delay.h"
|
||||
|
#include "audio_core/renderer/effect/reverb.h"
|
||||
|
#include "audio_core/renderer/memory/memory_pool_info.h"
|
||||
|
#include "audio_core/renderer/mix/mix_info.h"
|
||||
|
#include "audio_core/renderer/sink/circular_buffer_sink_info.h"
|
||||
|
#include "audio_core/renderer/sink/device_sink_info.h"
|
||||
|
#include "audio_core/renderer/sink/sink_info_base.h"
|
||||
|
#include "audio_core/renderer/voice/voice_info.h"
|
||||
|
#include "audio_core/renderer/voice/voice_state.h"
|
||||
|
|
||||
|
namespace AudioCore::AudioRenderer { |
||||
|
|
||||
|
template <typename T, CommandId Id> |
||||
|
T& CommandBuffer::GenerateStart(const s32 node_id) { |
||||
|
if (size + sizeof(T) >= command_list.size_bytes()) { |
||||
|
LOG_ERROR( |
||||
|
Service_Audio, |
||||
|
"Attempting to write commands beyond the end of allocated command buffer memory!"); |
||||
|
UNREACHABLE(); |
||||
|
} |
||||
|
|
||||
|
auto& cmd{*std::construct_at<T>(reinterpret_cast<T*>(&command_list[size]))}; |
||||
|
|
||||
|
cmd.magic = CommandMagic; |
||||
|
cmd.enabled = true; |
||||
|
cmd.type = Id; |
||||
|
cmd.size = sizeof(T); |
||||
|
cmd.node_id = node_id; |
||||
|
|
||||
|
return cmd; |
||||
|
} |
||||
|
|
||||
|
template <typename T> |
||||
|
void CommandBuffer::GenerateEnd(T& cmd) { |
||||
|
cmd.estimated_process_time = time_estimator->Estimate(cmd); |
||||
|
estimated_process_time += cmd.estimated_process_time; |
||||
|
size += sizeof(T); |
||||
|
count++; |
||||
|
} |
||||
|
|
||||
|
void CommandBuffer::GeneratePcmInt16Version1Command(const s32 node_id, |
||||
|
const MemoryPoolInfo& memory_pool_, |
||||
|
VoiceInfo& voice_info, |
||||
|
const VoiceState& voice_state, |
||||
|
const s16 buffer_count, const s8 channel) { |
||||
|
auto& cmd{ |
||||
|
GenerateStart<PcmInt16DataSourceVersion1Command, CommandId::DataSourcePcmInt16Version1>( |
||||
|
node_id)}; |
||||
|
|
||||
|
cmd.src_quality = voice_info.src_quality; |
||||
|
cmd.output_index = buffer_count + channel; |
||||
|
cmd.flags = voice_info.flags & 3; |
||||
|
cmd.sample_rate = voice_info.sample_rate; |
||||
|
cmd.pitch = voice_info.pitch; |
||||
|
cmd.channel_index = channel; |
||||
|
cmd.channel_count = voice_info.channel_count; |
||||
|
|
||||
|
for (u32 i = 0; i < MaxWaveBuffers; i++) { |
||||
|
voice_info.wavebuffers[i].Copy(cmd.wave_buffers[i]); |
||||
|
} |
||||
|
|
||||
|
cmd.voice_state = memory_pool_.Translate(CpuAddr(&voice_state), sizeof(VoiceState)); |
||||
|
|
||||
|
GenerateEnd<PcmInt16DataSourceVersion1Command>(cmd); |
||||
|
} |
||||
|
|
||||
|
void CommandBuffer::GeneratePcmInt16Version2Command(const s32 node_id, VoiceInfo& voice_info, |
||||
|
const VoiceState& voice_state, |
||||
|
const s16 buffer_count, const s8 channel) { |
||||
|
auto& cmd{ |
||||
|
GenerateStart<PcmInt16DataSourceVersion2Command, CommandId::DataSourcePcmInt16Version2>( |
||||
|
node_id)}; |
||||
|
|
||||
|
cmd.src_quality = voice_info.src_quality; |
||||
|
cmd.output_index = buffer_count + channel; |
||||
|
cmd.flags = voice_info.flags & 3; |
||||
|
cmd.sample_rate = voice_info.sample_rate; |
||||
|
cmd.pitch = voice_info.pitch; |
||||
|
cmd.channel_index = channel; |
||||
|
cmd.channel_count = voice_info.channel_count; |
||||
|
|
||||
|
for (u32 i = 0; i < MaxWaveBuffers; i++) { |
||||
|
voice_info.wavebuffers[i].Copy(cmd.wave_buffers[i]); |
||||
|
} |
||||
|
|
||||
|
cmd.voice_state = memory_pool->Translate(CpuAddr(&voice_state), sizeof(VoiceState)); |
||||
|
|
||||
|
GenerateEnd<PcmInt16DataSourceVersion2Command>(cmd); |
||||
|
} |
||||
|
|
||||
|
void CommandBuffer::GeneratePcmFloatVersion1Command(const s32 node_id, |
||||
|
const MemoryPoolInfo& memory_pool_, |
||||
|
VoiceInfo& voice_info, |
||||
|
const VoiceState& voice_state, |
||||
|
const s16 buffer_count, const s8 channel) { |
||||
|
auto& cmd{ |
||||
|
GenerateStart<PcmFloatDataSourceVersion1Command, CommandId::DataSourcePcmFloatVersion1>( |
||||
|
node_id)}; |
||||
|
|
||||
|
cmd.src_quality = voice_info.src_quality; |
||||
|
cmd.output_index = buffer_count + channel; |
||||
|
cmd.flags = voice_info.flags & 3; |
||||
|
cmd.sample_rate = voice_info.sample_rate; |
||||
|
cmd.pitch = voice_info.pitch; |
||||
|
cmd.channel_index = channel; |
||||
|
cmd.channel_count = voice_info.channel_count; |
||||
|
|
||||
|
for (u32 i = 0; i < MaxWaveBuffers; i++) { |
||||
|
voice_info.wavebuffers[i].Copy(cmd.wave_buffers[i]); |
||||
|
} |
||||
|
|
||||
|
cmd.voice_state = memory_pool_.Translate(CpuAddr(&voice_state), sizeof(VoiceState)); |
||||
|
|
||||
|
GenerateEnd<PcmFloatDataSourceVersion1Command>(cmd); |
||||
|
} |
||||
|
|
||||
|
void CommandBuffer::GeneratePcmFloatVersion2Command(const s32 node_id, VoiceInfo& voice_info, |
||||
|
const VoiceState& voice_state, |
||||
|
const s16 buffer_count, const s8 channel) { |
||||
|
auto& cmd{ |
||||
|
GenerateStart<PcmFloatDataSourceVersion2Command, CommandId::DataSourcePcmFloatVersion2>( |
||||
|
node_id)}; |
||||
|
|
||||
|
cmd.src_quality = voice_info.src_quality; |
||||
|
cmd.output_index = buffer_count + channel; |
||||
|
cmd.flags = voice_info.flags & 3; |
||||
|
cmd.sample_rate = voice_info.sample_rate; |
||||
|
cmd.pitch = voice_info.pitch; |
||||
|
cmd.channel_index = channel; |
||||
|
cmd.channel_count = voice_info.channel_count; |
||||
|
|
||||
|
for (u32 i = 0; i < MaxWaveBuffers; i++) { |
||||
|
voice_info.wavebuffers[i].Copy(cmd.wave_buffers[i]); |
||||
|
} |
||||
|
|
||||
|
cmd.voice_state = memory_pool->Translate(CpuAddr(&voice_state), sizeof(VoiceState)); |
||||
|
|
||||
|
GenerateEnd<PcmFloatDataSourceVersion2Command>(cmd); |
||||
|
} |
||||
|
|
||||
|
void CommandBuffer::GenerateAdpcmVersion1Command(const s32 node_id, |
||||
|
const MemoryPoolInfo& memory_pool_, |
||||
|
VoiceInfo& voice_info, |
||||
|
const VoiceState& voice_state, |
||||
|
const s16 buffer_count, const s8 channel) { |
||||
|
auto& cmd{ |
||||
|
GenerateStart<AdpcmDataSourceVersion1Command, CommandId::DataSourceAdpcmVersion1>(node_id)}; |
||||
|
|
||||
|
cmd.src_quality = voice_info.src_quality; |
||||
|
cmd.output_index = buffer_count + channel; |
||||
|
cmd.flags = voice_info.flags & 3; |
||||
|
cmd.sample_rate = voice_info.sample_rate; |
||||
|
cmd.pitch = voice_info.pitch; |
||||
|
|
||||
|
for (u32 i = 0; i < MaxWaveBuffers; i++) { |
||||
|
voice_info.wavebuffers[i].Copy(cmd.wave_buffers[i]); |
||||
|
} |
||||
|
|
||||
|
cmd.voice_state = memory_pool_.Translate(CpuAddr(&voice_state), sizeof(VoiceState)); |
||||
|
cmd.data_address = voice_info.data_address.GetReference(true); |
||||
|
cmd.data_size = voice_info.data_address.GetSize(); |
||||
|
|
||||
|
GenerateEnd<AdpcmDataSourceVersion1Command>(cmd); |
||||
|
} |
||||
|
|
||||
|
void CommandBuffer::GenerateAdpcmVersion2Command(const s32 node_id, VoiceInfo& voice_info, |
||||
|
const VoiceState& voice_state, |
||||
|
const s16 buffer_count, const s8 channel) { |
||||
|
auto& cmd{ |
||||
|
GenerateStart<AdpcmDataSourceVersion2Command, CommandId::DataSourceAdpcmVersion2>(node_id)}; |
||||
|
|
||||
|
cmd.src_quality = voice_info.src_quality; |
||||
|
cmd.output_index = buffer_count + channel; |
||||
|
cmd.flags = voice_info.flags & 3; |
||||
|
cmd.sample_rate = voice_info.sample_rate; |
||||
|
cmd.pitch = voice_info.pitch; |
||||
|
cmd.channel_index = channel; |
||||
|
cmd.channel_count = voice_info.channel_count; |
||||
|
|
||||
|
for (u32 i = 0; i < MaxWaveBuffers; i++) { |
||||
|
voice_info.wavebuffers[i].Copy(cmd.wave_buffers[i]); |
||||
|
} |
||||
|
|
||||
|
cmd.voice_state = memory_pool->Translate(CpuAddr(&voice_state), sizeof(VoiceState)); |
||||
|
cmd.data_address = voice_info.data_address.GetReference(true); |
||||
|
cmd.data_size = voice_info.data_address.GetSize(); |
||||
|
|
||||
|
GenerateEnd<AdpcmDataSourceVersion2Command>(cmd); |
||||
|
} |
||||
|
|
||||
|
void CommandBuffer::GenerateVolumeCommand(const s32 node_id, const s16 buffer_offset, |
||||
|
const s16 input_index, const f32 volume, |
||||
|
const u8 precision) { |
||||
|
auto& cmd{GenerateStart<VolumeCommand, CommandId::Volume>(node_id)}; |
||||
|
|
||||
|
cmd.precision = precision; |
||||
|
cmd.input_index = buffer_offset + input_index; |
||||
|
cmd.output_index = buffer_offset + input_index; |
||||
|
cmd.volume = volume; |
||||
|
|
||||
|
GenerateEnd<VolumeCommand>(cmd); |
||||
|
} |
||||
|
|
||||
|
void CommandBuffer::GenerateVolumeRampCommand(const s32 node_id, VoiceInfo& voice_info, |
||||
|
const s16 buffer_count, const u8 precision) { |
||||
|
auto& cmd{GenerateStart<VolumeRampCommand, CommandId::VolumeRamp>(node_id)}; |
||||
|
|
||||
|
cmd.input_index = buffer_count; |
||||
|
cmd.output_index = buffer_count; |
||||
|
cmd.prev_volume = voice_info.prev_volume; |
||||
|
cmd.volume = voice_info.volume; |
||||
|
cmd.precision = precision; |
||||
|
|
||||
|
GenerateEnd<VolumeRampCommand>(cmd); |
||||
|
} |
||||
|
|
||||
|
void CommandBuffer::GenerateBiquadFilterCommand(const s32 node_id, VoiceInfo& voice_info, |
||||
|
const VoiceState& voice_state, |
||||
|
const s16 buffer_count, const s8 channel, |
||||
|
const u32 biquad_index, |
||||
|
const bool use_float_processing) { |
||||
|
auto& cmd{GenerateStart<BiquadFilterCommand, CommandId::BiquadFilter>(node_id)}; |
||||
|
|
||||
|
cmd.input = buffer_count + channel; |
||||
|
cmd.output = buffer_count + channel; |
||||
|
|
||||
|
cmd.biquad = voice_info.biquads[biquad_index]; |
||||
|
|
||||
|
cmd.state = memory_pool->Translate(CpuAddr(voice_state.biquad_states[biquad_index].data()), |
||||
|
MaxBiquadFilters * sizeof(VoiceState::BiquadFilterState)); |
||||
|
|
||||
|
cmd.needs_init = !voice_info.biquad_initialized[biquad_index]; |
||||
|
cmd.use_float_processing = use_float_processing; |
||||
|
|
||||
|
GenerateEnd<BiquadFilterCommand>(cmd); |
||||
|
} |
||||
|
|
||||
|
void CommandBuffer::GenerateBiquadFilterCommand(const s32 node_id, EffectInfoBase& effect_info, |
||||
|
const s16 buffer_offset, const s8 channel, |
||||
|
const bool needs_init, |
||||
|
const bool use_float_processing) { |
||||
|
auto& cmd{GenerateStart<BiquadFilterCommand, CommandId::BiquadFilter>(node_id)}; |
||||
|
|
||||
|
const auto& parameter{ |
||||
|
*reinterpret_cast<BiquadFilterInfo::ParameterVersion1*>(effect_info.GetParameter())}; |
||||
|
const auto state{ |
||||
|
reinterpret_cast<VoiceState::BiquadFilterState*>(effect_info.GetStateBuffer())}; |
||||
|
|
||||
|
cmd.input = buffer_offset + parameter.inputs[channel]; |
||||
|
cmd.output = buffer_offset + parameter.outputs[channel]; |
||||
|
|
||||
|
cmd.biquad.b = parameter.b; |
||||
|
cmd.biquad.a = parameter.a; |
||||
|
|
||||
|
cmd.state = memory_pool->Translate(CpuAddr(state), |
||||
|
MaxBiquadFilters * sizeof(VoiceState::BiquadFilterState)); |
||||
|
|
||||
|
cmd.needs_init = needs_init; |
||||
|
cmd.use_float_processing = use_float_processing; |
||||
|
|
||||
|
GenerateEnd<BiquadFilterCommand>(cmd); |
||||
|
} |
||||
|
|
||||
|
void CommandBuffer::GenerateMixCommand(const s32 node_id, const s16 input_index, |
||||
|
const s16 output_index, const s16 buffer_offset, |
||||
|
const f32 volume, const u8 precision) { |
||||
|
auto& cmd{GenerateStart<MixCommand, CommandId::Mix>(node_id)}; |
||||
|
|
||||
|
cmd.input_index = input_index; |
||||
|
cmd.output_index = output_index; |
||||
|
cmd.volume = volume; |
||||
|
cmd.precision = precision; |
||||
|
|
||||
|
GenerateEnd<MixCommand>(cmd); |
||||
|
} |
||||
|
|
||||
|
void CommandBuffer::GenerateMixRampCommand(const s32 node_id, |
||||
|
[[maybe_unused]] const s16 buffer_count, |
||||
|
const s16 input_index, const s16 output_index, |
||||
|
const f32 volume, const f32 prev_volume, |
||||
|
const CpuAddr prev_samples, const u8 precision) { |
||||
|
if (volume == 0.0f && prev_volume == 0.0f) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
auto& cmd{GenerateStart<MixRampCommand, CommandId::MixRamp>(node_id)}; |
||||
|
|
||||
|
cmd.input_index = input_index; |
||||
|
cmd.output_index = output_index; |
||||
|
cmd.prev_volume = prev_volume; |
||||
|
cmd.volume = volume; |
||||
|
cmd.previous_sample = prev_samples; |
||||
|
cmd.precision = precision; |
||||
|
|
||||
|
GenerateEnd<MixRampCommand>(cmd); |
||||
|
} |
||||
|
|
||||
|
void CommandBuffer::GenerateMixRampGroupedCommand(const s32 node_id, const s16 buffer_count, |
||||
|
const s16 input_index, s16 output_index, |
||||
|
std::span<const f32> volumes, |
||||
|
std::span<const f32> prev_volumes, |
||||
|
const CpuAddr prev_samples, const u8 precision) { |
||||
|
auto& cmd{GenerateStart<MixRampGroupedCommand, CommandId::MixRampGrouped>(node_id)}; |
||||
|
|
||||
|
cmd.buffer_count = buffer_count; |
||||
|
|
||||
|
for (s32 i = 0; i < buffer_count; i++) { |
||||
|
cmd.inputs[i] = input_index; |
||||
|
cmd.outputs[i] = output_index++; |
||||
|
cmd.prev_volumes[i] = prev_volumes[i]; |
||||
|
cmd.volumes[i] = volumes[i]; |
||||
|
} |
||||
|
|
||||
|
cmd.previous_samples = prev_samples; |
||||
|
cmd.precision = precision; |
||||
|
|
||||
|
GenerateEnd<MixRampGroupedCommand>(cmd); |
||||
|
} |
||||
|
|
||||
|
void CommandBuffer::GenerateDepopPrepareCommand(const s32 node_id, const VoiceState& voice_state, |
||||
|
std::span<const s32> buffer, const s16 buffer_count, |
||||
|
s16 buffer_offset, const bool was_playing) { |
||||
|
auto& cmd{GenerateStart<DepopPrepareCommand, CommandId::DepopPrepare>(node_id)}; |
||||
|
|
||||
|
cmd.enabled = was_playing; |
||||
|
|
||||
|
for (u32 i = 0; i < MaxMixBuffers; i++) { |
||||
|
cmd.inputs[i] = buffer_offset++; |
||||
|
} |
||||
|
|
||||
|
cmd.previous_samples = memory_pool->Translate(CpuAddr(voice_state.previous_samples.data()), |
||||
|
MaxMixBuffers * sizeof(s32)); |
||||
|
cmd.buffer_count = buffer_count; |
||||
|
cmd.depop_buffer = memory_pool->Translate(CpuAddr(buffer.data()), buffer_count * sizeof(s32)); |
||||
|
|
||||
|
GenerateEnd<DepopPrepareCommand>(cmd); |
||||
|
} |
||||
|
|
||||
|
void CommandBuffer::GenerateDepopForMixBuffersCommand(const s32 node_id, const MixInfo& mix_info, |
||||
|
std::span<const s32> depop_buffer) { |
||||
|
auto& cmd{GenerateStart<DepopForMixBuffersCommand, CommandId::DepopForMixBuffers>(node_id)}; |
||||
|
|
||||
|
cmd.input = mix_info.buffer_offset; |
||||
|
cmd.count = mix_info.buffer_count; |
||||
|
cmd.decay = mix_info.sample_rate == TargetSampleRate ? 0.96218872f : 0.94369507f; |
||||
|
cmd.depop_buffer = |
||||
|
memory_pool->Translate(CpuAddr(depop_buffer.data()), mix_info.buffer_count * sizeof(s32)); |
||||
|
|
||||
|
GenerateEnd<DepopForMixBuffersCommand>(cmd); |
||||
|
} |
||||
|
|
||||
|
void CommandBuffer::GenerateDelayCommand(const s32 node_id, EffectInfoBase& effect_info, |
||||
|
const s16 buffer_offset) { |
||||
|
auto& cmd{GenerateStart<DelayCommand, CommandId::Delay>(node_id)}; |
||||
|
|
||||
|
const auto& parameter{ |
||||
|
*reinterpret_cast<DelayInfo::ParameterVersion1*>(effect_info.GetParameter())}; |
||||
|
const auto state{effect_info.GetStateBuffer()}; |
||||
|
|
||||
|
if (IsChannelCountValid(parameter.channel_count)) { |
||||
|
const auto state_buffer{memory_pool->Translate(CpuAddr(state), sizeof(DelayInfo::State))}; |
||||
|
if (state_buffer) { |
||||
|
for (s16 channel = 0; channel < parameter.channel_count; channel++) { |
||||
|
cmd.inputs[channel] = buffer_offset + parameter.inputs[channel]; |
||||
|
cmd.outputs[channel] = buffer_offset + parameter.outputs[channel]; |
||||
|
} |
||||
|
|
||||
|
if (!behavior->IsDelayChannelMappingChanged() && parameter.channel_count == 6) { |
||||
|
UseOldChannelMapping(cmd.inputs, cmd.outputs); |
||||
|
} |
||||
|
|
||||
|
cmd.parameter = parameter; |
||||
|
cmd.effect_enabled = effect_info.IsEnabled(); |
||||
|
cmd.state = state_buffer; |
||||
|
cmd.workbuffer = effect_info.GetWorkbuffer(-1); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
GenerateEnd<DelayCommand>(cmd); |
||||
|
} |
||||
|
|
||||
|
void CommandBuffer::GenerateUpsampleCommand(const s32 node_id, const s16 buffer_offset, |
||||
|
UpsamplerInfo& upsampler_info, const u32 input_count, |
||||
|
std::span<const s8> inputs, const s16 buffer_count, |
||||
|
const u32 sample_count_, const u32 sample_rate_) { |
||||
|
auto& cmd{GenerateStart<UpsampleCommand, CommandId::Upsample>(node_id)}; |
||||
|
|
||||
|
cmd.samples_buffer = memory_pool->Translate(upsampler_info.samples_pos, |
||||
|
upsampler_info.sample_count * sizeof(s32)); |
||||
|
cmd.inputs = memory_pool->Translate(CpuAddr(upsampler_info.inputs.data()), MaxChannels); |
||||
|
cmd.buffer_count = buffer_count; |
||||
|
cmd.unk_20 = 0; |
||||
|
cmd.source_sample_count = sample_count_; |
||||
|
cmd.source_sample_rate = sample_rate_; |
||||
|
|
||||
|
upsampler_info.input_count = input_count; |
||||
|
for (u32 i = 0; i < input_count; i++) { |
||||
|
upsampler_info.inputs[i] = buffer_offset + inputs[i]; |
||||
|
} |
||||
|
|
||||
|
cmd.upsampler_info = memory_pool->Translate(CpuAddr(&upsampler_info), sizeof(UpsamplerInfo)); |
||||
|
|
||||
|
GenerateEnd<UpsampleCommand>(cmd); |
||||
|
} |
||||
|
|
||||
|
void CommandBuffer::GenerateDownMix6chTo2chCommand(const s32 node_id, std::span<const s8> inputs, |
||||
|
const s16 buffer_offset, |
||||
|
std::span<const f32> downmix_coeff) { |
||||
|
auto& cmd{GenerateStart<DownMix6chTo2chCommand, CommandId::DownMix6chTo2ch>(node_id)}; |
||||
|
|
||||
|
for (u32 i = 0; i < MaxChannels; i++) { |
||||
|
cmd.inputs[i] = buffer_offset + inputs[i]; |
||||
|
cmd.outputs[i] = buffer_offset + inputs[i]; |
||||
|
} |
||||
|
|
||||
|
for (u32 i = 0; i < 4; i++) { |
||||
|
cmd.down_mix_coeff[i] = downmix_coeff[i]; |
||||
|
} |
||||
|
|
||||
|
GenerateEnd<DownMix6chTo2chCommand>(cmd); |
||||
|
} |
||||
|
|
||||
|
void CommandBuffer::GenerateAuxCommand(const s32 node_id, EffectInfoBase& effect_info, |
||||
|
const s16 input_index, const s16 output_index, |
||||
|
const s16 buffer_offset, const u32 update_count, |
||||
|
const u32 count_max, const u32 write_offset) { |
||||
|
auto& cmd{GenerateStart<AuxCommand, CommandId::Aux>(node_id)}; |
||||
|
|
||||
|
if (effect_info.GetSendBuffer() != 0 && effect_info.GetReturnBuffer() != 0) { |
||||
|
cmd.input = buffer_offset + input_index; |
||||
|
cmd.output = buffer_offset + output_index; |
||||
|
cmd.send_buffer_info = effect_info.GetSendBufferInfo(); |
||||
|
cmd.send_buffer = effect_info.GetSendBuffer(); |
||||
|
cmd.return_buffer_info = effect_info.GetReturnBufferInfo(); |
||||
|
cmd.return_buffer = effect_info.GetReturnBuffer(); |
||||
|
cmd.count_max = count_max; |
||||
|
cmd.write_offset = write_offset; |
||||
|
cmd.update_count = update_count; |
||||
|
cmd.effect_enabled = effect_info.IsEnabled(); |
||||
|
} |
||||
|
|
||||
|
GenerateEnd<AuxCommand>(cmd); |
||||
|
} |
||||
|
|
||||
|
void CommandBuffer::GenerateDeviceSinkCommand(const s32 node_id, const s16 buffer_offset, |
||||
|
SinkInfoBase& sink_info, const u32 session_id, |
||||
|
std::span<s32> samples_buffer) { |
||||
|
auto& cmd{GenerateStart<DeviceSinkCommand, CommandId::DeviceSink>(node_id)}; |
||||
|
const auto& parameter{ |
||||
|
*reinterpret_cast<DeviceSinkInfo::DeviceInParameter*>(sink_info.GetParameter())}; |
||||
|
auto state{*reinterpret_cast<DeviceSinkInfo::DeviceState*>(sink_info.GetState())}; |
||||
|
|
||||
|
cmd.session_id = session_id; |
||||
|
|
||||
|
if (state.upsampler_info != nullptr) { |
||||
|
const auto size_{state.upsampler_info->sample_count * parameter.input_count}; |
||||
|
const auto size_bytes{size_ * sizeof(s32)}; |
||||
|
const auto addr{memory_pool->Translate(state.upsampler_info->samples_pos, size_bytes)}; |
||||
|
cmd.sample_buffer = {reinterpret_cast<s32*>(addr), |
||||
|
parameter.input_count * state.upsampler_info->sample_count}; |
||||
|
} else { |
||||
|
cmd.sample_buffer = samples_buffer; |
||||
|
} |
||||
|
|
||||
|
cmd.input_count = parameter.input_count; |
||||
|
for (u32 i = 0; i < parameter.input_count; i++) { |
||||
|
cmd.inputs[i] = buffer_offset + parameter.inputs[i]; |
||||
|
} |
||||
|
|
||||
|
GenerateEnd<DeviceSinkCommand>(cmd); |
||||
|
} |
||||
|
|
||||
|
void CommandBuffer::GenerateCircularBufferSinkCommand(const s32 node_id, SinkInfoBase& sink_info, |
||||
|
const s16 buffer_offset) { |
||||
|
auto& cmd{GenerateStart<CircularBufferSinkCommand, CommandId::CircularBufferSink>(node_id)}; |
||||
|
const auto& parameter{*reinterpret_cast<CircularBufferSinkInfo::CircularBufferInParameter*>( |
||||
|
sink_info.GetParameter())}; |
||||
|
auto state{ |
||||
|
*reinterpret_cast<CircularBufferSinkInfo::CircularBufferState*>(sink_info.GetState())}; |
||||
|
|
||||
|
cmd.input_count = parameter.input_count; |
||||
|
for (u32 i = 0; i < parameter.input_count; i++) { |
||||
|
cmd.inputs[i] = buffer_offset + parameter.inputs[i]; |
||||
|
} |
||||
|
|
||||
|
cmd.address = state.address_info.GetReference(true); |
||||
|
cmd.size = parameter.size; |
||||
|
cmd.pos = state.current_pos; |
||||
|
|
||||
|
GenerateEnd<CircularBufferSinkCommand>(cmd); |
||||
|
} |
||||
|
|
||||
|
void CommandBuffer::GenerateReverbCommand(const s32 node_id, EffectInfoBase& effect_info, |
||||
|
const s16 buffer_offset, |
||||
|
const bool long_size_pre_delay_supported) { |
||||
|
auto& cmd{GenerateStart<ReverbCommand, CommandId::Reverb>(node_id)}; |
||||
|
|
||||
|
const auto& parameter{ |
||||
|
*reinterpret_cast<ReverbInfo::ParameterVersion2*>(effect_info.GetParameter())}; |
||||
|
const auto state{effect_info.GetStateBuffer()}; |
||||
|
|
||||
|
if (IsChannelCountValid(parameter.channel_count)) { |
||||
|
const auto state_buffer{memory_pool->Translate(CpuAddr(state), sizeof(ReverbInfo::State))}; |
||||
|
if (state_buffer) { |
||||
|
for (s16 channel = 0; channel < parameter.channel_count; channel++) { |
||||
|
cmd.inputs[channel] = buffer_offset + parameter.inputs[channel]; |
||||
|
cmd.outputs[channel] = buffer_offset + parameter.outputs[channel]; |
||||
|
} |
||||
|
|
||||
|
if (!behavior->IsReverbChannelMappingChanged() && parameter.channel_count == 6) { |
||||
|
UseOldChannelMapping(cmd.inputs, cmd.outputs); |
||||
|
} |
||||
|
|
||||
|
cmd.parameter = parameter; |
||||
|
cmd.effect_enabled = effect_info.IsEnabled(); |
||||
|
cmd.state = state_buffer; |
||||
|
cmd.workbuffer = effect_info.GetWorkbuffer(-1); |
||||
|
cmd.long_size_pre_delay_supported = long_size_pre_delay_supported; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
GenerateEnd<ReverbCommand>(cmd); |
||||
|
} |
||||
|
|
||||
|
void CommandBuffer::GenerateI3dl2ReverbCommand(const s32 node_id, EffectInfoBase& effect_info, |
||||
|
const s16 buffer_offset) { |
||||
|
auto& cmd{GenerateStart<I3dl2ReverbCommand, CommandId::I3dl2Reverb>(node_id)}; |
||||
|
|
||||
|
const auto& parameter{ |
||||
|
*reinterpret_cast<I3dl2ReverbInfo::ParameterVersion1*>(effect_info.GetParameter())}; |
||||
|
const auto state{effect_info.GetStateBuffer()}; |
||||
|
|
||||
|
if (IsChannelCountValid(parameter.channel_count)) { |
||||
|
const auto state_buffer{ |
||||
|
memory_pool->Translate(CpuAddr(state), sizeof(I3dl2ReverbInfo::State))}; |
||||
|
if (state_buffer) { |
||||
|
for (s16 channel = 0; channel < parameter.channel_count; channel++) { |
||||
|
cmd.inputs[channel] = buffer_offset + parameter.inputs[channel]; |
||||
|
cmd.outputs[channel] = buffer_offset + parameter.outputs[channel]; |
||||
|
} |
||||
|
|
||||
|
if (!behavior->IsI3dl2ReverbChannelMappingChanged() && parameter.channel_count == 6) { |
||||
|
UseOldChannelMapping(cmd.inputs, cmd.outputs); |
||||
|
} |
||||
|
|
||||
|
cmd.parameter = parameter; |
||||
|
cmd.effect_enabled = effect_info.IsEnabled(); |
||||
|
cmd.state = state_buffer; |
||||
|
cmd.workbuffer = effect_info.GetWorkbuffer(-1); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
GenerateEnd<I3dl2ReverbCommand>(cmd); |
||||
|
} |
||||
|
|
||||
|
void CommandBuffer::GeneratePerformanceCommand(const s32 node_id, const PerformanceState state, |
||||
|
const PerformanceEntryAddresses& entry_addresses) { |
||||
|
auto& cmd{GenerateStart<PerformanceCommand, CommandId::Performance>(node_id)}; |
||||
|
|
||||
|
cmd.state = state; |
||||
|
cmd.entry_address = entry_addresses; |
||||
|
|
||||
|
GenerateEnd<PerformanceCommand>(cmd); |
||||
|
} |
||||
|
|
||||
|
void CommandBuffer::GenerateClearMixCommand(const s32 node_id) { |
||||
|
auto& cmd{GenerateStart<ClearMixBufferCommand, CommandId::ClearMixBuffer>(node_id)}; |
||||
|
GenerateEnd<ClearMixBufferCommand>(cmd); |
||||
|
} |
||||
|
|
||||
|
void CommandBuffer::GenerateCopyMixBufferCommand(const s32 node_id, EffectInfoBase& effect_info, |
||||
|
const s16 buffer_offset, const s8 channel) { |
||||
|
auto& cmd{GenerateStart<CopyMixBufferCommand, CommandId::CopyMixBuffer>(node_id)}; |
||||
|
|
||||
|
const auto& parameter{ |
||||
|
*reinterpret_cast<BiquadFilterInfo::ParameterVersion1*>(effect_info.GetParameter())}; |
||||
|
cmd.input_index = buffer_offset + parameter.inputs[channel]; |
||||
|
cmd.output_index = buffer_offset + parameter.outputs[channel]; |
||||
|
|
||||
|
GenerateEnd<CopyMixBufferCommand>(cmd); |
||||
|
} |
||||
|
|
||||
|
void CommandBuffer::GenerateLightLimiterCommand( |
||||
|
const s32 node_id, const s16 buffer_offset, |
||||
|
const LightLimiterInfo::ParameterVersion1& parameter, const LightLimiterInfo::State& state, |
||||
|
const bool enabled, const CpuAddr workbuffer) { |
||||
|
auto& cmd{GenerateStart<LightLimiterVersion1Command, CommandId::LightLimiterVersion1>(node_id)}; |
||||
|
|
||||
|
if (IsChannelCountValid(parameter.channel_count)) { |
||||
|
const auto state_buffer{ |
||||
|
memory_pool->Translate(CpuAddr(&state), sizeof(LightLimiterInfo::State))}; |
||||
|
if (state_buffer) { |
||||
|
for (s8 channel = 0; channel < parameter.channel_count; channel++) { |
||||
|
cmd.inputs[channel] = buffer_offset + parameter.inputs[channel]; |
||||
|
cmd.outputs[channel] = buffer_offset + parameter.outputs[channel]; |
||||
|
} |
||||
|
|
||||
|
std::memcpy(&cmd.parameter, ¶meter, sizeof(LightLimiterInfo::ParameterVersion1)); |
||||
|
cmd.effect_enabled = enabled; |
||||
|
cmd.state = state_buffer; |
||||
|
cmd.workbuffer = workbuffer; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
GenerateEnd<LightLimiterVersion1Command>(cmd); |
||||
|
} |
||||
|
|
||||
|
void CommandBuffer::GenerateLightLimiterCommand( |
||||
|
const s32 node_id, const s16 buffer_offset, |
||||
|
const LightLimiterInfo::ParameterVersion2& parameter, |
||||
|
const LightLimiterInfo::StatisticsInternal& statistics, const LightLimiterInfo::State& state, |
||||
|
const bool enabled, const CpuAddr workbuffer) { |
||||
|
auto& cmd{GenerateStart<LightLimiterVersion2Command, CommandId::LightLimiterVersion2>(node_id)}; |
||||
|
if (IsChannelCountValid(parameter.channel_count)) { |
||||
|
const auto state_buffer{ |
||||
|
memory_pool->Translate(CpuAddr(&state), sizeof(LightLimiterInfo::State))}; |
||||
|
if (state_buffer) { |
||||
|
for (s8 channel = 0; channel < parameter.channel_count; channel++) { |
||||
|
cmd.inputs[channel] = buffer_offset + parameter.inputs[channel]; |
||||
|
cmd.outputs[channel] = buffer_offset + parameter.outputs[channel]; |
||||
|
} |
||||
|
|
||||
|
cmd.parameter = parameter; |
||||
|
cmd.effect_enabled = enabled; |
||||
|
cmd.state = state_buffer; |
||||
|
if (cmd.parameter.statistics_enabled) { |
||||
|
cmd.result_state = memory_pool->Translate( |
||||
|
CpuAddr(&statistics), sizeof(LightLimiterInfo::StatisticsInternal)); |
||||
|
} else { |
||||
|
cmd.result_state = 0; |
||||
|
} |
||||
|
cmd.workbuffer = workbuffer; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
GenerateEnd<LightLimiterVersion2Command>(cmd); |
||||
|
} |
||||
|
|
||||
|
void CommandBuffer::GenerateMultitapBiquadFilterCommand(const s32 node_id, VoiceInfo& voice_info, |
||||
|
const VoiceState& voice_state, |
||||
|
const s16 buffer_count, const s8 channel) { |
||||
|
auto& cmd{GenerateStart<MultiTapBiquadFilterCommand, CommandId::MultiTapBiquadFilter>(node_id)}; |
||||
|
|
||||
|
cmd.input = buffer_count + channel; |
||||
|
cmd.output = buffer_count + channel; |
||||
|
cmd.biquads = voice_info.biquads; |
||||
|
|
||||
|
cmd.states[0] = |
||||
|
memory_pool->Translate(CpuAddr(voice_state.biquad_states[0].data()), |
||||
|
MaxBiquadFilters * sizeof(VoiceState::BiquadFilterState)); |
||||
|
cmd.states[1] = |
||||
|
memory_pool->Translate(CpuAddr(voice_state.biquad_states[1].data()), |
||||
|
MaxBiquadFilters * sizeof(VoiceState::BiquadFilterState)); |
||||
|
|
||||
|
cmd.needs_init[0] = !voice_info.biquad_initialized[0]; |
||||
|
cmd.needs_init[1] = !voice_info.biquad_initialized[1]; |
||||
|
cmd.filter_tap_count = MaxBiquadFilters; |
||||
|
|
||||
|
GenerateEnd<MultiTapBiquadFilterCommand>(cmd); |
||||
|
} |
||||
|
|
||||
|
void CommandBuffer::GenerateCaptureCommand(const s32 node_id, EffectInfoBase& effect_info, |
||||
|
const s16 input_index, const s16 output_index, |
||||
|
const s16 buffer_offset, const u32 update_count, |
||||
|
const u32 count_max, const u32 write_offset) { |
||||
|
auto& cmd{GenerateStart<CaptureCommand, CommandId::Capture>(node_id)}; |
||||
|
|
||||
|
if (effect_info.GetSendBuffer()) { |
||||
|
cmd.input = buffer_offset + input_index; |
||||
|
cmd.output = buffer_offset + output_index; |
||||
|
cmd.send_buffer_info = effect_info.GetSendBufferInfo(); |
||||
|
cmd.send_buffer = effect_info.GetSendBuffer(); |
||||
|
cmd.count_max = count_max; |
||||
|
cmd.write_offset = write_offset; |
||||
|
cmd.update_count = update_count; |
||||
|
cmd.effect_enabled = effect_info.IsEnabled(); |
||||
|
} |
||||
|
|
||||
|
GenerateEnd<CaptureCommand>(cmd); |
||||
|
} |
||||
|
|
||||
|
void CommandBuffer::GenerateCompressorCommand(s16 buffer_offset, EffectInfoBase& effect_info, |
||||
|
s32 node_id) { |
||||
|
auto& cmd{GenerateStart<CompressorCommand, CommandId::Compressor>(node_id)}; |
||||
|
|
||||
|
auto& parameter{ |
||||
|
*reinterpret_cast<CompressorInfo::ParameterVersion2*>(effect_info.GetParameter())}; |
||||
|
auto state{reinterpret_cast<CompressorInfo::State*>(effect_info.GetStateBuffer())}; |
||||
|
|
||||
|
if (IsChannelCountValid(parameter.channel_count)) { |
||||
|
auto state_buffer{memory_pool->Translate(CpuAddr(state), sizeof(CompressorInfo::State))}; |
||||
|
if (state_buffer) { |
||||
|
for (u16 channel = 0; channel < parameter.channel_count; channel++) { |
||||
|
cmd.inputs[channel] = buffer_offset + parameter.inputs[channel]; |
||||
|
cmd.outputs[channel] = buffer_offset + parameter.outputs[channel]; |
||||
|
} |
||||
|
cmd.parameter = parameter; |
||||
|
cmd.workbuffer = state_buffer; |
||||
|
cmd.enabled = effect_info.IsEnabled(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
GenerateEnd<CompressorCommand>(cmd); |
||||
|
} |
||||
|
|
||||
|
} // namespace AudioCore::AudioRenderer
|
||||
@ -0,0 +1,466 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project |
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <span> |
||||
|
|
||||
|
#include "audio_core/renderer/command/commands.h" |
||||
|
#include "audio_core/renderer/effect/light_limiter.h" |
||||
|
#include "audio_core/renderer/performance/performance_manager.h" |
||||
|
#include "common/common_types.h" |
||||
|
|
||||
|
namespace AudioCore::AudioRenderer { |
||||
|
struct UpsamplerInfo; |
||||
|
struct VoiceState; |
||||
|
class EffectInfoBase; |
||||
|
class ICommandProcessingTimeEstimator; |
||||
|
class MixInfo; |
||||
|
class MemoryPoolInfo; |
||||
|
class SinkInfoBase; |
||||
|
class VoiceInfo; |
||||
|
|
||||
|
/** |
||||
|
* Utility functions to generate and add commands into the current command list. |
||||
|
*/ |
||||
|
class CommandBuffer { |
||||
|
public: |
||||
|
/** |
||||
|
* Generate a PCM s16 version 1 command, adding it to the command list. |
||||
|
* |
||||
|
* @param node_id - Node id of the voice this command is generated for. |
||||
|
* @param memory_pool - Memory pool for translating buffer addresses to the DSP. |
||||
|
* @param voice_info - The voice info this command is generated from. |
||||
|
* @param voice_state - The voice state the DSP will use for this command. |
||||
|
* @param buffer_count - Number of mix buffers in use, |
||||
|
* data will be read into this index + channel. |
||||
|
* @param channel - Channel index for this command. |
||||
|
*/ |
||||
|
void GeneratePcmInt16Version1Command(s32 node_id, const MemoryPoolInfo& memory_pool, |
||||
|
VoiceInfo& voice_info, const VoiceState& voice_state, |
||||
|
s16 buffer_count, s8 channel); |
||||
|
|
||||
|
/** |
||||
|
* Generate a PCM s16 version 2 command, adding it to the command list. |
||||
|
* |
||||
|
* @param node_id - Node id of the voice this command is generated for. |
||||
|
* @param voice_info - The voice info this command is generated from. |
||||
|
* @param voice_state - The voice state the DSP will use for this command. |
||||
|
* @param buffer_count - Number of mix buffers in use, |
||||
|
* data will be read into this index + channel. |
||||
|
* @param channel - Channel index for this command. |
||||
|
*/ |
||||
|
void GeneratePcmInt16Version2Command(s32 node_id, VoiceInfo& voice_info, |
||||
|
const VoiceState& voice_state, s16 buffer_count, |
||||
|
s8 channel); |
||||
|
|
||||
|
/** |
||||
|
* Generate a PCM f32 version 1 command, adding it to the command list. |
||||
|
* |
||||
|
* @param node_id - Node id of the voice this command is generated for. |
||||
|
* @param memory_pool - Memory pool for translating buffer addresses to the DSP. |
||||
|
* @param voice_info - The voice info this command is generated from. |
||||
|
* @param voice_state - The voice state the DSP will use for this command. |
||||
|
* @param buffer_count - Number of mix buffers in use, |
||||
|
* data will be read into this index + channel. |
||||
|
* @param channel - Channel index for this command. |
||||
|
*/ |
||||
|
void GeneratePcmFloatVersion1Command(s32 node_id, const MemoryPoolInfo& memory_pool, |
||||
|
VoiceInfo& voice_info, const VoiceState& voice_state, |
||||
|
s16 buffer_count, s8 channel); |
||||
|
|
||||
|
/** |
||||
|
* Generate a PCM f32 version 2 command, adding it to the command list. |
||||
|
* |
||||
|
* @param node_id - Node id of the voice this command is generated for. |
||||
|
* @param voice_info - The voice info this command is generated from. |
||||
|
* @param voice_state - The voice state the DSP will use for this command. |
||||
|
* @param buffer_count - Number of mix buffers in use, |
||||
|
* data will be read into this index + channel. |
||||
|
* @param channel - Channel index for this command. |
||||
|
*/ |
||||
|
void GeneratePcmFloatVersion2Command(s32 node_id, VoiceInfo& voice_info, |
||||
|
const VoiceState& voice_state, s16 buffer_count, |
||||
|
s8 channel); |
||||
|
|
||||
|
/** |
||||
|
* Generate an ADPCM version 1 command, adding it to the command list. |
||||
|
* |
||||
|
* @param node_id - Node id of the voice this command is generated for. |
||||
|
* @param memory_pool - Memory pool for translating buffer addresses to the DSP. |
||||
|
* @param voice_info - The voice info this command is generated from. |
||||
|
* @param voice_state - The voice state the DSP will use for this command. |
||||
|
* @param buffer_count - Number of mix buffers in use, |
||||
|
* data will be read into this index + channel. |
||||
|
* @param channel - Channel index for this command. |
||||
|
*/ |
||||
|
void GenerateAdpcmVersion1Command(s32 node_id, const MemoryPoolInfo& memory_pool, |
||||
|
VoiceInfo& voice_info, const VoiceState& voice_state, |
||||
|
s16 buffer_count, s8 channel); |
||||
|
|
||||
|
/** |
||||
|
* Generate an ADPCM version 2 command, adding it to the command list. |
||||
|
* |
||||
|
* @param node_id - Node id of the voice this command is generated for. |
||||
|
* @param voice_info - The voice info this command is generated from. |
||||
|
* @param voice_state - The voice state the DSP will use for this command. |
||||
|
* @param buffer_count - Number of mix buffers in use, |
||||
|
* data will be read into this index + channel. |
||||
|
* @param channel - Channel index for this command. |
||||
|
*/ |
||||
|
void GenerateAdpcmVersion2Command(s32 node_id, VoiceInfo& voice_info, |
||||
|
const VoiceState& voice_state, s16 buffer_count, s8 channel); |
||||
|
|
||||
|
/** |
||||
|
* Generate a volume command, adding it to the command list. |
||||
|
* |
||||
|
* @param node_id - Node id of the voice this command is generated for. |
||||
|
* @param buffer_offset - Base mix buffer index to generate this command at. |
||||
|
* @param input_index - Channel index and mix buffer offset for this command. |
||||
|
* @param volume - Mix volume added to the input samples. |
||||
|
* @param precision - Number of decimal bits for fixed point operations. |
||||
|
*/ |
||||
|
void GenerateVolumeCommand(s32 node_id, s16 buffer_offset, s16 input_index, f32 volume, |
||||
|
u8 precision); |
||||
|
|
||||
|
/** |
||||
|
* Generate a volume ramp command, adding it to the command list. |
||||
|
* |
||||
|
* @param node_id - Node id of the voice this command is generated for. |
||||
|
* @param voice_info - The voice info this command takes its volumes from. |
||||
|
* @param buffer_count - Number of active mix buffers, command will generate at this index. |
||||
|
* @param precision - Number of decimal bits for fixed point operations. |
||||
|
*/ |
||||
|
void GenerateVolumeRampCommand(s32 node_id, VoiceInfo& voice_info, s16 buffer_count, |
||||
|
u8 precision); |
||||
|
|
||||
|
/** |
||||
|
* Generate a biquad filter command from a voice, adding it to the command list. |
||||
|
* |
||||
|
* @param node_id - Node id of the voice this command is generated for. |
||||
|
* @param voice_info - The voice info this command takes biquad parameters from. |
||||
|
* @param voice_state - Used by the AudioRenderer to track previous samples. |
||||
|
* @param buffer_count - Number of active mix buffers, |
||||
|
* command will generate at this index + channel. |
||||
|
* @param channel - Channel index for this filter to work on. |
||||
|
* @param biquad_index - Which biquad filter to use for this command (0-1). |
||||
|
* @param use_float_processing - Should int or float processing be used? |
||||
|
*/ |
||||
|
void GenerateBiquadFilterCommand(s32 node_id, VoiceInfo& voice_info, |
||||
|
const VoiceState& voice_state, s16 buffer_count, s8 channel, |
||||
|
u32 biquad_index, bool use_float_processing); |
||||
|
|
||||
|
/** |
||||
|
* Generate a biquad filter effect command, adding it to the command list. |
||||
|
* |
||||
|
* @param node_id - Node id of the voice this command is generated for. |
||||
|
* @param effect_info - The effect info this command takes biquad parameters from. |
||||
|
* @param buffer_offset - Mix buffer offset this command will use, |
||||
|
* command will generate at this index + channel. |
||||
|
* @param channel - Channel index for this filter to work on. |
||||
|
* @param needs_init - True if the biquad state needs initialisation. |
||||
|
* @param use_float_processing - Should int or float processing be used? |
||||
|
*/ |
||||
|
void GenerateBiquadFilterCommand(s32 node_id, EffectInfoBase& effect_info, s16 buffer_offset, |
||||
|
s8 channel, bool needs_init, bool use_float_processing); |
||||
|
|
||||
|
/** |
||||
|
* Generate a mix command, adding it to the command list. |
||||
|
* |
||||
|
* @param node_id - Node id of the voice this command is generated for. |
||||
|
* @param input_index - Input mix buffer index for this command. |
||||
|
* Added to the buffer offset. |
||||
|
* @param output_index - Output mix buffer index for this command. |
||||
|
* Added to the buffer offset. |
||||
|
* @param buffer_offset - Mix buffer offset this command will use. |
||||
|
* @param volume - Volume to be applied to the input. |
||||
|
* @param precision - Number of decimal bits for fixed point operations. |
||||
|
*/ |
||||
|
void GenerateMixCommand(s32 node_id, s16 input_index, s16 output_index, s16 buffer_offset, |
||||
|
f32 volume, u8 precision); |
||||
|
|
||||
|
/** |
||||
|
* Generate a mix ramp command, adding it to the command list. |
||||
|
* |
||||
|
* @param node_id - Node id of the voice this command is generated for. |
||||
|
* @param buffer_count - Number of active mix buffers. |
||||
|
* @param input_index - Input mix buffer index for this command. |
||||
|
* Added to buffer_count. |
||||
|
* @param output_index - Output mix buffer index for this command. |
||||
|
* Added to buffer_count. |
||||
|
* @param volume - Current mix volume used for calculating the ramp. |
||||
|
* @param prev_volume - Previous mix volume, used for calculating the ramp, |
||||
|
* also applied to the input. |
||||
|
* @param precision - Number of decimal bits for fixed point operations. |
||||
|
*/ |
||||
|
void GenerateMixRampCommand(s32 node_id, s16 buffer_count, s16 input_index, s16 output_index, |
||||
|
f32 volume, f32 prev_volume, CpuAddr prev_samples, u8 precision); |
||||
|
|
||||
|
/** |
||||
|
* Generate a mix ramp grouped command, adding it to the command list. |
||||
|
* |
||||
|
* @param node_id - Node id of the voice this command is generated for. |
||||
|
* @param buffer_count - Number of active mix buffers. |
||||
|
* @param input_index - Input mix buffer index for this command. |
||||
|
* Added to buffer_count. |
||||
|
* @param output_index - Output mix buffer index for this command. |
||||
|
* Added to buffer_count. |
||||
|
* @param volumes - Current mix volumes used for calculating the ramp. |
||||
|
* @param prev_volumes - Previous mix volumes, used for calculating the ramp, |
||||
|
* also applied to the input. |
||||
|
* @param precision - Number of decimal bits for fixed point operations. |
||||
|
*/ |
||||
|
void GenerateMixRampGroupedCommand(s32 node_id, s16 buffer_count, s16 input_index, |
||||
|
s16 output_index, std::span<const f32> volumes, |
||||
|
std::span<const f32> prev_volumes, CpuAddr prev_samples, |
||||
|
u8 precision); |
||||
|
|
||||
|
/** |
||||
|
* Generate a depop prepare command, adding it to the command list. |
||||
|
* |
||||
|
* @param node_id - Node id of the voice this command is generated for. |
||||
|
* @param voice_state - State to track the previous depop samples for each mix buffer. |
||||
|
* @param buffer - State to track the current depop samples for each mix buffer. |
||||
|
* @param buffer_count - Number of active mix buffers. |
||||
|
* @param buffer_offset - Base mix buffer index to generate the channel depops at. |
||||
|
* @param was_playing - Command only needs to work if the voice was previously playing. |
||||
|
*/ |
||||
|
void GenerateDepopPrepareCommand(s32 node_id, const VoiceState& voice_state, |
||||
|
std::span<const s32> buffer, s16 buffer_count, |
||||
|
s16 buffer_offset, bool was_playing); |
||||
|
|
||||
|
/** |
||||
|
* Generate a depop command, adding it to the command list. |
||||
|
* |
||||
|
* @param node_id - Node id of the voice this command is generated for. |
||||
|
* @param mix_info - Mix info to get the buffer count and base offsets from. |
||||
|
* @param depop_buffer - Buffer of current depop sample values to be added to the input |
||||
|
* channels. |
||||
|
*/ |
||||
|
void GenerateDepopForMixBuffersCommand(s32 node_id, const MixInfo& mix_info, |
||||
|
std::span<const s32> depop_buffer); |
||||
|
|
||||
|
/** |
||||
|
* Generate a delay command, adding it to the command list. |
||||
|
* |
||||
|
* @param node_id - Node id of the voice this command is generated for. |
||||
|
* @param effect_info - Delay effect info to generate this command from. |
||||
|
* @param buffer_offset - Base mix buffer offset to apply the apply the delay. |
||||
|
*/ |
||||
|
void GenerateDelayCommand(s32 node_id, EffectInfoBase& effect_info, s16 buffer_offset); |
||||
|
|
||||
|
/** |
||||
|
* Generate an upsample command, adding it to the command list. |
||||
|
* |
||||
|
* @param node_id - Node id of the voice this command is generated for. |
||||
|
* @param buffer_offset - Base mix buffer offset to upsample. |
||||
|
* @param upsampler_info - Upsampler info to control the upsampling. |
||||
|
* @param input_count - Number of input channels to upsample. |
||||
|
* @param inputs - Input mix buffer indexes. |
||||
|
* @param buffer_count - Number of active mix buffers. |
||||
|
* @param sample_count - Source sample count of the input. |
||||
|
* @param sample_rate - Source sample rate of the input. |
||||
|
*/ |
||||
|
void GenerateUpsampleCommand(s32 node_id, s16 buffer_offset, UpsamplerInfo& upsampler_info, |
||||
|
u32 input_count, std::span<const s8> inputs, s16 buffer_count, |
||||
|
u32 sample_count, u32 sample_rate); |
||||
|
|
||||
|
/** |
||||
|
* Generate a downmix 6 -> 2 command, adding it to the command list. |
||||
|
* |
||||
|
* @param node_id - Node id of the voice this command is generated for. |
||||
|
* @param inputs - Input mix buffer indexes. |
||||
|
* @param buffer_offset - Base mix buffer offset of the channels to downmix. |
||||
|
* @param downmix_coeff - Downmixing coefficients. |
||||
|
*/ |
||||
|
void GenerateDownMix6chTo2chCommand(s32 node_id, std::span<const s8> inputs, s16 buffer_offset, |
||||
|
std::span<const f32> downmix_coeff); |
||||
|
|
||||
|
/** |
||||
|
* Generate an aux buffer command, adding it to the command list. |
||||
|
* |
||||
|
* @param node_id - Node id of the voice this command is generated for. |
||||
|
* @param effect_info - Aux effect info to generate this command from. |
||||
|
* @param input_index - Input mix buffer index for this command. |
||||
|
* Added to buffer_offset. |
||||
|
* @param output_index - Output mix buffer index for this command. |
||||
|
* Added to buffer_offset. |
||||
|
* @param buffer_offset - Base mix buffer offset to use. |
||||
|
* @param update_count - Number of samples to write back to the game as updated, can be 0. |
||||
|
* @param count_max - Maximum number of samples to read or write. |
||||
|
* @param write_offset - Current read or write offset within the buffer. |
||||
|
*/ |
||||
|
void GenerateAuxCommand(s32 node_id, EffectInfoBase& effect_info, s16 input_index, |
||||
|
s16 output_index, s16 buffer_offset, u32 update_count, u32 count_max, |
||||
|
u32 write_offset); |
||||
|
|
||||
|
/** |
||||
|
* Generate a device sink command, adding it to the command list. |
||||
|
* |
||||
|
* @param node_id - Node id of the voice this command is generated for. |
||||
|
* @param buffer_offset - Base mix buffer offset to use. |
||||
|
* @param sink_info - The sink_info to generate this command from. |
||||
|
* @session_id - System session id this command is generated from. |
||||
|
* @samples_buffer - The buffer to be sent to the sink if upsampling is not used. |
||||
|
*/ |
||||
|
void GenerateDeviceSinkCommand(s32 node_id, s16 buffer_offset, SinkInfoBase& sink_info, |
||||
|
u32 session_id, std::span<s32> samples_buffer); |
||||
|
|
||||
|
/** |
||||
|
* Generate a circular buffer sink command, adding it to the command list. |
||||
|
* |
||||
|
* @param node_id - Node id of the voice this command is generated for. |
||||
|
* @param sink_info - The sink_info to generate this command from. |
||||
|
* @param buffer_offset - Base mix buffer offset to use. |
||||
|
*/ |
||||
|
void GenerateCircularBufferSinkCommand(s32 node_id, SinkInfoBase& sink_info, s16 buffer_offset); |
||||
|
|
||||
|
/** |
||||
|
* Generate a reverb command, adding it to the command list. |
||||
|
* |
||||
|
* @param node_id - Node id of the voice this command is generated for. |
||||
|
* @param effect_info - Reverb effect info to generate this command from. |
||||
|
* @param buffer_offset - Base mix buffer offset to use. |
||||
|
* @param long_size_pre_delay_supported - Should a longer pre-delay time be used before reverb |
||||
|
* begins? |
||||
|
*/ |
||||
|
void GenerateReverbCommand(s32 node_id, EffectInfoBase& effect_info, s16 buffer_offset, |
||||
|
bool long_size_pre_delay_supported); |
||||
|
|
||||
|
/** |
||||
|
* Generate an I3DL2 reverb command, adding it to the command list. |
||||
|
* |
||||
|
* @param node_id - Node id of the voice this command is generated for. |
||||
|
* @param effect_info - I3DL2Reverb effect info to generate this command from. |
||||
|
* @param buffer_offset - Base mix buffer offset to use. |
||||
|
*/ |
||||
|
void GenerateI3dl2ReverbCommand(s32 node_id, EffectInfoBase& effect_info, s16 buffer_offset); |
||||
|
|
||||
|
/** |
||||
|
* Generate a performance command, adding it to the command list. |
||||
|
* |
||||
|
* @param node_id - Node id of the voice this command is generated for. |
||||
|
* @param state - State of the performance. |
||||
|
* @param entry_addresses - The addresses to be filled in by the AudioRenderer. |
||||
|
*/ |
||||
|
void GeneratePerformanceCommand(s32 node_id, PerformanceState state, |
||||
|
const PerformanceEntryAddresses& entry_addresses); |
||||
|
|
||||
|
/** |
||||
|
* Generate a clear mix command, adding it to the command list. |
||||
|
* |
||||
|
* @param node_id - Node id of the voice this command is generated for. |
||||
|
*/ |
||||
|
void GenerateClearMixCommand(s32 node_id); |
||||
|
|
||||
|
/** |
||||
|
* Generate a copy mix command, adding it to the command list. |
||||
|
* |
||||
|
* @param node_id - Node id of the voice this command is generated for. |
||||
|
* @param effect_info - BiquadFilter effect info to generate this command from. |
||||
|
* @param buffer_offset - Base mix buffer offset to use. |
||||
|
* @param channel - Index to the effect's parameters input indexes for this command. |
||||
|
*/ |
||||
|
void GenerateCopyMixBufferCommand(s32 node_id, EffectInfoBase& effect_info, s16 buffer_offset, |
||||
|
s8 channel); |
||||
|
|
||||
|
/** |
||||
|
* Generate a light limiter version 1 command, adding it to the command list. |
||||
|
* |
||||
|
* @param node_id - Node id of the voice this command is generated for. |
||||
|
* @param buffer_offset - Base mix buffer offset to use. |
||||
|
* @param parameter - Effect parameter to generate from. |
||||
|
* @param state - State used by the AudioRenderer between commands. |
||||
|
* @param enabled - Is this command enabled? |
||||
|
* @param workbuffer - Game-supplied memory for the state. |
||||
|
*/ |
||||
|
void GenerateLightLimiterCommand(s32 node_id, s16 buffer_offset, |
||||
|
const LightLimiterInfo::ParameterVersion1& parameter, |
||||
|
const LightLimiterInfo::State& state, bool enabled, |
||||
|
CpuAddr workbuffer); |
||||
|
|
||||
|
/** |
||||
|
* Generate a light limiter version 2 command, adding it to the command list. |
||||
|
* |
||||
|
* @param node_id - Node id of the voice this command is generated for. |
||||
|
* @param buffer_offset - Base mix buffer offset to use. |
||||
|
* @param parameter - Effect parameter to generate from. |
||||
|
* @param statistics - Statistics reported by the AudioRenderer on the limiter's state. |
||||
|
* @param state - State used by the AudioRenderer between commands. |
||||
|
* @param enabled - Is this command enabled? |
||||
|
* @param workbuffer - Game-supplied memory for the state. |
||||
|
*/ |
||||
|
void GenerateLightLimiterCommand(s32 node_id, s16 buffer_offset, |
||||
|
const LightLimiterInfo::ParameterVersion2& parameter, |
||||
|
const LightLimiterInfo::StatisticsInternal& statistics, |
||||
|
const LightLimiterInfo::State& state, bool enabled, |
||||
|
CpuAddr workbuffer); |
||||
|
|
||||
|
/** |
||||
|
* Generate a multitap biquad filter command, adding it to the command list. |
||||
|
* |
||||
|
* @param node_id - Node id of the voice this command is generated for. |
||||
|
* @param voice_info - The voice info this command takes biquad parameters from. |
||||
|
* @param voice_state - Used by the AudioRenderer to track previous samples. |
||||
|
* @param buffer_count - Number of active mix buffers, |
||||
|
* command will generate at this index + channel. |
||||
|
* @param channel - Channel index for this filter to work on. |
||||
|
*/ |
||||
|
void GenerateMultitapBiquadFilterCommand(s32 node_id, VoiceInfo& voice_info, |
||||
|
const VoiceState& voice_state, s16 buffer_count, |
||||
|
s8 channel); |
||||
|
|
||||
|
/** |
||||
|
* Generate a capture command, adding it to the command list. |
||||
|
* |
||||
|
* @param node_id - Node id of the voice this command is generated for. |
||||
|
* @param effect_info - Capture effect info to generate this command from. |
||||
|
* @param input_index - Input mix buffer index for this command. |
||||
|
* Added to buffer_offset. |
||||
|
* @param output_index - Output mix buffer index for this command (unused). |
||||
|
* Added to buffer_offset. |
||||
|
* @param buffer_offset - Base mix buffer offset to use. |
||||
|
* @param update_count - Number of samples to write back to the game as updated, can be 0. |
||||
|
* @param count_max - Maximum number of samples to read or write. |
||||
|
* @param write_offset - Current read or write offset within the buffer. |
||||
|
*/ |
||||
|
void GenerateCaptureCommand(s32 node_id, EffectInfoBase& effect_info, s16 input_index, |
||||
|
s16 output_index, s16 buffer_offset, u32 update_count, |
||||
|
u32 count_max, u32 write_offset); |
||||
|
|
||||
|
/** |
||||
|
* Generate a compressor command, adding it to the command list. |
||||
|
* |
||||
|
* @param buffer_offset - Base mix buffer offset to use. |
||||
|
* @param effect_info - Capture effect info to generate this command from. |
||||
|
* @param node_id - Node id of the voice this command is generated for. |
||||
|
*/ |
||||
|
void GenerateCompressorCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id); |
||||
|
|
||||
|
/// Command list buffer generated commands will be added to |
||||
|
std::span<u8> command_list{}; |
||||
|
/// Input sample count, unused |
||||
|
u32 sample_count{}; |
||||
|
/// Input sample rate, unused |
||||
|
u32 sample_rate{}; |
||||
|
/// Current size of the command buffer |
||||
|
u64 size{}; |
||||
|
/// Current number of commands added |
||||
|
u32 count{}; |
||||
|
/// Current estimated processing time for all commands |
||||
|
u32 estimated_process_time{}; |
||||
|
/// Used for mapping buffers for the AudioRenderer |
||||
|
MemoryPoolInfo* memory_pool{}; |
||||
|
/// Used for estimating command process times |
||||
|
ICommandProcessingTimeEstimator* time_estimator{}; |
||||
|
/// Used to check which rendering features are currently enabled |
||||
|
BehaviorInfo* behavior{}; |
||||
|
|
||||
|
private: |
||||
|
template <typename T, CommandId Id> |
||||
|
T& GenerateStart(const s32 node_id); |
||||
|
template <typename T> |
||||
|
void GenerateEnd(T& cmd); |
||||
|
}; |
||||
|
|
||||
|
} // namespace AudioCore::AudioRenderer |
||||
@ -0,0 +1,796 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
|
||||
|
#include "audio_core/common/audio_renderer_parameter.h"
|
||||
|
#include "audio_core/renderer/behavior/behavior_info.h"
|
||||
|
#include "audio_core/renderer/command/command_buffer.h"
|
||||
|
#include "audio_core/renderer/command/command_generator.h"
|
||||
|
#include "audio_core/renderer/command/command_list_header.h"
|
||||
|
#include "audio_core/renderer/effect/aux_.h"
|
||||
|
#include "audio_core/renderer/effect/biquad_filter.h"
|
||||
|
#include "audio_core/renderer/effect/buffer_mixer.h"
|
||||
|
#include "audio_core/renderer/effect/capture.h"
|
||||
|
#include "audio_core/renderer/effect/effect_context.h"
|
||||
|
#include "audio_core/renderer/effect/light_limiter.h"
|
||||
|
#include "audio_core/renderer/mix/mix_context.h"
|
||||
|
#include "audio_core/renderer/performance/detail_aspect.h"
|
||||
|
#include "audio_core/renderer/performance/entry_aspect.h"
|
||||
|
#include "audio_core/renderer/sink/device_sink_info.h"
|
||||
|
#include "audio_core/renderer/sink/sink_context.h"
|
||||
|
#include "audio_core/renderer/splitter/splitter_context.h"
|
||||
|
#include "audio_core/renderer/voice/voice_context.h"
|
||||
|
#include "common/alignment.h"
|
||||
|
|
||||
|
namespace AudioCore::AudioRenderer { |
||||
|
|
||||
|
CommandGenerator::CommandGenerator(CommandBuffer& command_buffer_, |
||||
|
const CommandListHeader& command_list_header_, |
||||
|
const AudioRendererSystemContext& render_context_, |
||||
|
VoiceContext& voice_context_, MixContext& mix_context_, |
||||
|
EffectContext& effect_context_, SinkContext& sink_context_, |
||||
|
SplitterContext& splitter_context_, |
||||
|
PerformanceManager* performance_manager_) |
||||
|
: command_buffer{command_buffer_}, command_header{command_list_header_}, |
||||
|
render_context{render_context_}, voice_context{voice_context_}, mix_context{mix_context_}, |
||||
|
effect_context{effect_context_}, sink_context{sink_context_}, |
||||
|
splitter_context{splitter_context_}, performance_manager{performance_manager_} { |
||||
|
command_buffer.GenerateClearMixCommand(InvalidNodeId); |
||||
|
} |
||||
|
|
||||
|
void CommandGenerator::GenerateDataSourceCommand(VoiceInfo& voice_info, |
||||
|
const VoiceState& voice_state, const s8 channel) { |
||||
|
if (voice_info.mix_id == UnusedMixId) { |
||||
|
if (voice_info.splitter_id != UnusedSplitterId) { |
||||
|
auto destination{splitter_context.GetDesintationData(voice_info.splitter_id, 0)}; |
||||
|
u32 dest_id{0}; |
||||
|
while (destination != nullptr) { |
||||
|
if (destination->IsConfigured()) { |
||||
|
auto mix_id{destination->GetMixId()}; |
||||
|
if (mix_id < mix_context.GetCount()) { |
||||
|
auto mix_info{mix_context.GetInfo(mix_id)}; |
||||
|
command_buffer.GenerateDepopPrepareCommand( |
||||
|
voice_info.node_id, voice_state, render_context.depop_buffer, |
||||
|
mix_info->buffer_count, mix_info->buffer_offset, |
||||
|
voice_info.was_playing); |
||||
|
} |
||||
|
} |
||||
|
dest_id++; |
||||
|
destination = splitter_context.GetDesintationData(voice_info.splitter_id, dest_id); |
||||
|
} |
||||
|
} |
||||
|
} else { |
||||
|
auto mix_info{mix_context.GetInfo(voice_info.mix_id)}; |
||||
|
command_buffer.GenerateDepopPrepareCommand( |
||||
|
voice_info.node_id, voice_state, render_context.depop_buffer, mix_info->buffer_count, |
||||
|
mix_info->buffer_offset, voice_info.was_playing); |
||||
|
} |
||||
|
|
||||
|
if (voice_info.was_playing) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
if (render_context.behavior->IsWaveBufferVer2Supported()) { |
||||
|
switch (voice_info.sample_format) { |
||||
|
case SampleFormat::PcmInt16: |
||||
|
command_buffer.GeneratePcmInt16Version2Command( |
||||
|
voice_info.node_id, voice_info, voice_state, render_context.mix_buffer_count, |
||||
|
channel); |
||||
|
break; |
||||
|
case SampleFormat::PcmFloat: |
||||
|
command_buffer.GeneratePcmFloatVersion2Command( |
||||
|
voice_info.node_id, voice_info, voice_state, render_context.mix_buffer_count, |
||||
|
channel); |
||||
|
break; |
||||
|
case SampleFormat::Adpcm: |
||||
|
command_buffer.GenerateAdpcmVersion2Command(voice_info.node_id, voice_info, voice_state, |
||||
|
render_context.mix_buffer_count, channel); |
||||
|
break; |
||||
|
default: |
||||
|
LOG_ERROR(Service_Audio, "Invalid SampleFormat {}", |
||||
|
static_cast<u32>(voice_info.sample_format)); |
||||
|
break; |
||||
|
} |
||||
|
} else { |
||||
|
switch (voice_info.sample_format) { |
||||
|
case SampleFormat::PcmInt16: |
||||
|
command_buffer.GeneratePcmInt16Version1Command( |
||||
|
voice_info.node_id, *command_buffer.memory_pool, voice_info, voice_state, |
||||
|
render_context.mix_buffer_count, channel); |
||||
|
break; |
||||
|
case SampleFormat::PcmFloat: |
||||
|
command_buffer.GeneratePcmFloatVersion1Command( |
||||
|
voice_info.node_id, *command_buffer.memory_pool, voice_info, voice_state, |
||||
|
render_context.mix_buffer_count, channel); |
||||
|
break; |
||||
|
case SampleFormat::Adpcm: |
||||
|
command_buffer.GenerateAdpcmVersion1Command( |
||||
|
voice_info.node_id, *command_buffer.memory_pool, voice_info, voice_state, |
||||
|
render_context.mix_buffer_count, channel); |
||||
|
break; |
||||
|
default: |
||||
|
LOG_ERROR(Service_Audio, "Invalid SampleFormat {}", |
||||
|
static_cast<u32>(voice_info.sample_format)); |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void CommandGenerator::GenerateVoiceMixCommand(std::span<const f32> mix_volumes, |
||||
|
std::span<const f32> prev_mix_volumes, |
||||
|
const VoiceState& voice_state, s16 output_index, |
||||
|
const s16 buffer_count, const s16 input_index, |
||||
|
const s32 node_id) { |
||||
|
u8 precision{15}; |
||||
|
if (render_context.behavior->IsVolumeMixParameterPrecisionQ23Supported()) { |
||||
|
precision = 23; |
||||
|
} |
||||
|
|
||||
|
if (buffer_count > 8) { |
||||
|
const auto prev_samples{render_context.memory_pool_info->Translate( |
||||
|
CpuAddr(voice_state.previous_samples.data()), buffer_count * sizeof(s32))}; |
||||
|
command_buffer.GenerateMixRampGroupedCommand(node_id, buffer_count, input_index, |
||||
|
output_index, mix_volumes, prev_mix_volumes, |
||||
|
prev_samples, precision); |
||||
|
} else { |
||||
|
for (s16 i = 0; i < buffer_count; i++, output_index++) { |
||||
|
const auto prev_samples{render_context.memory_pool_info->Translate( |
||||
|
CpuAddr(&voice_state.previous_samples[i]), sizeof(s32))}; |
||||
|
|
||||
|
command_buffer.GenerateMixRampCommand(node_id, buffer_count, input_index, output_index, |
||||
|
mix_volumes[i], prev_mix_volumes[i], prev_samples, |
||||
|
precision); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void CommandGenerator::GenerateBiquadFilterCommandForVoice(VoiceInfo& voice_info, |
||||
|
const VoiceState& voice_state, |
||||
|
const s16 buffer_count, const s8 channel, |
||||
|
const s32 node_id) { |
||||
|
const bool both_biquads_enabled{voice_info.biquads[0].enabled && voice_info.biquads[1].enabled}; |
||||
|
const auto use_float_processing{render_context.behavior->UseBiquadFilterFloatProcessing()}; |
||||
|
|
||||
|
if (both_biquads_enabled && render_context.behavior->UseMultiTapBiquadFilterProcessing() && |
||||
|
use_float_processing) { |
||||
|
command_buffer.GenerateMultitapBiquadFilterCommand(node_id, voice_info, voice_state, |
||||
|
buffer_count, channel); |
||||
|
} else { |
||||
|
for (u32 i = 0; i < MaxBiquadFilters; i++) { |
||||
|
if (voice_info.biquads[i].enabled) { |
||||
|
command_buffer.GenerateBiquadFilterCommand(node_id, voice_info, voice_state, |
||||
|
buffer_count, channel, i, |
||||
|
use_float_processing); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void CommandGenerator::GenerateVoiceCommand(VoiceInfo& voice_info) { |
||||
|
u8 precision{15}; |
||||
|
if (render_context.behavior->IsVolumeMixParameterPrecisionQ23Supported()) { |
||||
|
precision = 23; |
||||
|
} |
||||
|
|
||||
|
for (s8 channel = 0; channel < voice_info.channel_count; channel++) { |
||||
|
const auto resource_id{voice_info.channel_resource_ids[channel]}; |
||||
|
auto& voice_state{voice_context.GetDspSharedState(resource_id)}; |
||||
|
auto& channel_resource{voice_context.GetChannelResource(resource_id)}; |
||||
|
|
||||
|
PerformanceDetailType detail_type{PerformanceDetailType::Invalid}; |
||||
|
switch (voice_info.sample_format) { |
||||
|
case SampleFormat::PcmInt16: |
||||
|
detail_type = PerformanceDetailType::Unk1; |
||||
|
break; |
||||
|
case SampleFormat::PcmFloat: |
||||
|
detail_type = PerformanceDetailType::Unk10; |
||||
|
break; |
||||
|
default: |
||||
|
detail_type = PerformanceDetailType::Unk2; |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
DetailAspect data_source_detail(*this, PerformanceEntryType::Voice, voice_info.node_id, |
||||
|
detail_type); |
||||
|
GenerateDataSourceCommand(voice_info, voice_state, channel); |
||||
|
|
||||
|
if (data_source_detail.initialized) { |
||||
|
command_buffer.GeneratePerformanceCommand(data_source_detail.node_id, |
||||
|
PerformanceState::Stop, |
||||
|
data_source_detail.performance_entry_address); |
||||
|
} |
||||
|
|
||||
|
if (voice_info.was_playing) { |
||||
|
voice_info.prev_volume = 0.0f; |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
if (!voice_info.HasAnyConnection()) { |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
DetailAspect biquad_detail_aspect(*this, PerformanceEntryType::Voice, voice_info.node_id, |
||||
|
PerformanceDetailType::Unk4); |
||||
|
GenerateBiquadFilterCommandForVoice( |
||||
|
voice_info, voice_state, render_context.mix_buffer_count, channel, voice_info.node_id); |
||||
|
|
||||
|
if (biquad_detail_aspect.initialized) { |
||||
|
command_buffer.GeneratePerformanceCommand( |
||||
|
biquad_detail_aspect.node_id, PerformanceState::Stop, |
||||
|
biquad_detail_aspect.performance_entry_address); |
||||
|
} |
||||
|
|
||||
|
DetailAspect volume_ramp_detail_aspect(*this, PerformanceEntryType::Voice, |
||||
|
voice_info.node_id, PerformanceDetailType::Unk3); |
||||
|
command_buffer.GenerateVolumeRampCommand( |
||||
|
voice_info.node_id, voice_info, render_context.mix_buffer_count + channel, precision); |
||||
|
if (volume_ramp_detail_aspect.initialized) { |
||||
|
command_buffer.GeneratePerformanceCommand( |
||||
|
volume_ramp_detail_aspect.node_id, PerformanceState::Stop, |
||||
|
volume_ramp_detail_aspect.performance_entry_address); |
||||
|
} |
||||
|
|
||||
|
voice_info.prev_volume = voice_info.volume; |
||||
|
|
||||
|
if (voice_info.mix_id == UnusedMixId) { |
||||
|
if (voice_info.splitter_id != UnusedSplitterId) { |
||||
|
auto i{channel}; |
||||
|
auto destination{splitter_context.GetDesintationData(voice_info.splitter_id, i)}; |
||||
|
while (destination != nullptr) { |
||||
|
if (destination->IsConfigured()) { |
||||
|
const auto mix_id{destination->GetMixId()}; |
||||
|
if (mix_id < mix_context.GetCount() && |
||||
|
static_cast<s32>(mix_id) != UnusedSplitterId) { |
||||
|
auto mix_info{mix_context.GetInfo(mix_id)}; |
||||
|
GenerateVoiceMixCommand( |
||||
|
destination->GetMixVolume(), destination->GetMixVolumePrev(), |
||||
|
voice_state, mix_info->buffer_offset, mix_info->buffer_count, |
||||
|
render_context.mix_buffer_count + channel, voice_info.node_id); |
||||
|
destination->MarkAsNeedToUpdateInternalState(); |
||||
|
} |
||||
|
} |
||||
|
i += voice_info.channel_count; |
||||
|
destination = splitter_context.GetDesintationData(voice_info.splitter_id, i); |
||||
|
} |
||||
|
} |
||||
|
} else { |
||||
|
DetailAspect volume_mix_detail_aspect(*this, PerformanceEntryType::Voice, |
||||
|
voice_info.node_id, PerformanceDetailType::Unk3); |
||||
|
auto mix_info{mix_context.GetInfo(voice_info.mix_id)}; |
||||
|
GenerateVoiceMixCommand(channel_resource.mix_volumes, channel_resource.prev_mix_volumes, |
||||
|
voice_state, mix_info->buffer_offset, mix_info->buffer_count, |
||||
|
render_context.mix_buffer_count + channel, voice_info.node_id); |
||||
|
if (volume_mix_detail_aspect.initialized) { |
||||
|
command_buffer.GeneratePerformanceCommand( |
||||
|
volume_mix_detail_aspect.node_id, PerformanceState::Stop, |
||||
|
volume_mix_detail_aspect.performance_entry_address); |
||||
|
} |
||||
|
|
||||
|
channel_resource.prev_mix_volumes = channel_resource.mix_volumes; |
||||
|
} |
||||
|
voice_info.biquad_initialized[0] = voice_info.biquads[0].enabled; |
||||
|
voice_info.biquad_initialized[1] = voice_info.biquads[1].enabled; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void CommandGenerator::GenerateVoiceCommands() { |
||||
|
const auto voice_count{voice_context.GetCount()}; |
||||
|
|
||||
|
for (u32 i = 0; i < voice_count; i++) { |
||||
|
auto sorted_info{voice_context.GetSortedInfo(i)}; |
||||
|
|
||||
|
if (sorted_info->ShouldSkip() || !sorted_info->UpdateForCommandGeneration(voice_context)) { |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
EntryAspect voice_entry_aspect(*this, PerformanceEntryType::Voice, sorted_info->node_id); |
||||
|
|
||||
|
GenerateVoiceCommand(*sorted_info); |
||||
|
|
||||
|
if (voice_entry_aspect.initialized) { |
||||
|
command_buffer.GeneratePerformanceCommand(voice_entry_aspect.node_id, |
||||
|
PerformanceState::Stop, |
||||
|
voice_entry_aspect.performance_entry_address); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
splitter_context.UpdateInternalState(); |
||||
|
} |
||||
|
|
||||
|
void CommandGenerator::GenerateBufferMixerCommand(const s16 buffer_offset, |
||||
|
EffectInfoBase& effect_info, const s32 node_id) { |
||||
|
u8 precision{15}; |
||||
|
if (render_context.behavior->IsVolumeMixParameterPrecisionQ23Supported()) { |
||||
|
precision = 23; |
||||
|
} |
||||
|
|
||||
|
if (effect_info.IsEnabled()) { |
||||
|
const auto& parameter{ |
||||
|
*reinterpret_cast<BufferMixerInfo::ParameterVersion1*>(effect_info.GetParameter())}; |
||||
|
for (u32 i = 0; i < parameter.mix_count; i++) { |
||||
|
if (parameter.volumes[i] != 0.0f) { |
||||
|
command_buffer.GenerateMixCommand(node_id, buffer_offset + parameter.inputs[i], |
||||
|
buffer_offset + parameter.outputs[i], |
||||
|
buffer_offset, parameter.volumes[i], precision); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void CommandGenerator::GenerateDelayCommand(const s16 buffer_offset, EffectInfoBase& effect_info, |
||||
|
const s32 node_id) { |
||||
|
command_buffer.GenerateDelayCommand(node_id, effect_info, buffer_offset); |
||||
|
} |
||||
|
|
||||
|
void CommandGenerator::GenerateReverbCommand(const s16 buffer_offset, EffectInfoBase& effect_info, |
||||
|
const s32 node_id, |
||||
|
const bool long_size_pre_delay_supported) { |
||||
|
command_buffer.GenerateReverbCommand(node_id, effect_info, buffer_offset, |
||||
|
long_size_pre_delay_supported); |
||||
|
} |
||||
|
|
||||
|
void CommandGenerator::GenerateI3dl2ReverbEffectCommand(const s16 buffer_offset, |
||||
|
EffectInfoBase& effect_info, |
||||
|
const s32 node_id) { |
||||
|
command_buffer.GenerateI3dl2ReverbCommand(node_id, effect_info, buffer_offset); |
||||
|
} |
||||
|
|
||||
|
void CommandGenerator::GenerateAuxCommand(const s16 buffer_offset, EffectInfoBase& effect_info, |
||||
|
const s32 node_id) { |
||||
|
|
||||
|
if (effect_info.IsEnabled()) { |
||||
|
effect_info.GetWorkbuffer(0); |
||||
|
effect_info.GetWorkbuffer(1); |
||||
|
} |
||||
|
|
||||
|
if (effect_info.GetSendBuffer() != 0 && effect_info.GetReturnBuffer() != 0) { |
||||
|
const auto& parameter{ |
||||
|
*reinterpret_cast<AuxInfo::ParameterVersion1*>(effect_info.GetParameter())}; |
||||
|
auto channel_index{parameter.mix_buffer_count - 1}; |
||||
|
u32 write_offset{0}; |
||||
|
for (u32 i = 0; i < parameter.mix_buffer_count; i++, channel_index--) { |
||||
|
auto new_update_count{command_header.sample_count + write_offset}; |
||||
|
const auto update_count{channel_index > 0 ? 0 : new_update_count}; |
||||
|
command_buffer.GenerateAuxCommand(node_id, effect_info, parameter.inputs[i], |
||||
|
parameter.outputs[i], buffer_offset, update_count, |
||||
|
parameter.count_max, write_offset); |
||||
|
write_offset = new_update_count; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void CommandGenerator::GenerateBiquadFilterEffectCommand(const s16 buffer_offset, |
||||
|
EffectInfoBase& effect_info, |
||||
|
const s32 node_id) { |
||||
|
const auto& parameter{ |
||||
|
*reinterpret_cast<BiquadFilterInfo::ParameterVersion1*>(effect_info.GetParameter())}; |
||||
|
if (effect_info.IsEnabled()) { |
||||
|
bool needs_init{false}; |
||||
|
|
||||
|
switch (parameter.state) { |
||||
|
case EffectInfoBase::ParameterState::Initialized: |
||||
|
needs_init = true; |
||||
|
break; |
||||
|
case EffectInfoBase::ParameterState::Updating: |
||||
|
case EffectInfoBase::ParameterState::Updated: |
||||
|
if (render_context.behavior->IsBiquadFilterEffectStateClearBugFixed()) { |
||||
|
needs_init = false; |
||||
|
} else { |
||||
|
needs_init = parameter.state == EffectInfoBase::ParameterState::Updating; |
||||
|
} |
||||
|
break; |
||||
|
default: |
||||
|
LOG_ERROR(Service_Audio, "Invalid biquad parameter state {}", |
||||
|
static_cast<u32>(parameter.state)); |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
for (s8 channel = 0; channel < parameter.channel_count; channel++) { |
||||
|
command_buffer.GenerateBiquadFilterCommand( |
||||
|
node_id, effect_info, buffer_offset, channel, needs_init, |
||||
|
render_context.behavior->UseBiquadFilterFloatProcessing()); |
||||
|
} |
||||
|
} else { |
||||
|
for (s8 channel = 0; channel < parameter.channel_count; channel++) { |
||||
|
command_buffer.GenerateCopyMixBufferCommand(node_id, effect_info, buffer_offset, |
||||
|
channel); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void CommandGenerator::GenerateLightLimiterEffectCommand(const s16 buffer_offset, |
||||
|
EffectInfoBase& effect_info, |
||||
|
const s32 node_id, |
||||
|
const u32 effect_index) { |
||||
|
|
||||
|
const auto& state{*reinterpret_cast<LightLimiterInfo::State*>(effect_info.GetStateBuffer())}; |
||||
|
|
||||
|
if (render_context.behavior->IsEffectInfoVersion2Supported()) { |
||||
|
const auto& parameter{ |
||||
|
*reinterpret_cast<LightLimiterInfo::ParameterVersion2*>(effect_info.GetParameter())}; |
||||
|
const auto& result_state{*reinterpret_cast<LightLimiterInfo::StatisticsInternal*>( |
||||
|
&effect_context.GetDspSharedResultState(effect_index))}; |
||||
|
command_buffer.GenerateLightLimiterCommand(node_id, buffer_offset, parameter, result_state, |
||||
|
state, effect_info.IsEnabled(), |
||||
|
effect_info.GetWorkbuffer(-1)); |
||||
|
} else { |
||||
|
const auto& parameter{ |
||||
|
*reinterpret_cast<LightLimiterInfo::ParameterVersion1*>(effect_info.GetParameter())}; |
||||
|
command_buffer.GenerateLightLimiterCommand(node_id, buffer_offset, parameter, state, |
||||
|
effect_info.IsEnabled(), |
||||
|
effect_info.GetWorkbuffer(-1)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void CommandGenerator::GenerateCaptureCommand(const s16 buffer_offset, EffectInfoBase& effect_info, |
||||
|
const s32 node_id) { |
||||
|
if (effect_info.IsEnabled()) { |
||||
|
effect_info.GetWorkbuffer(0); |
||||
|
} |
||||
|
|
||||
|
if (effect_info.GetSendBuffer()) { |
||||
|
const auto& parameter{ |
||||
|
*reinterpret_cast<AuxInfo::ParameterVersion1*>(effect_info.GetParameter())}; |
||||
|
auto channel_index{parameter.mix_buffer_count - 1}; |
||||
|
u32 write_offset{0}; |
||||
|
for (u32 i = 0; i < parameter.mix_buffer_count; i++, channel_index--) { |
||||
|
auto new_update_count{command_header.sample_count + write_offset}; |
||||
|
const auto update_count{channel_index > 0 ? 0 : new_update_count}; |
||||
|
command_buffer.GenerateCaptureCommand(node_id, effect_info, parameter.inputs[i], |
||||
|
parameter.outputs[i], buffer_offset, update_count, |
||||
|
parameter.count_max, write_offset); |
||||
|
write_offset = new_update_count; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void CommandGenerator::GenerateCompressorCommand(const s16 buffer_offset, |
||||
|
EffectInfoBase& effect_info, const s32 node_id) { |
||||
|
command_buffer.GenerateCompressorCommand(buffer_offset, effect_info, node_id); |
||||
|
} |
||||
|
|
||||
|
void CommandGenerator::GenerateEffectCommand(MixInfo& mix_info) { |
||||
|
const auto effect_count{effect_context.GetCount()}; |
||||
|
for (u32 i = 0; i < effect_count; i++) { |
||||
|
const auto effect_index{mix_info.effect_order_buffer[i]}; |
||||
|
if (effect_index == -1) { |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
auto& effect_info = effect_context.GetInfo(effect_index); |
||||
|
if (effect_info.ShouldSkip()) { |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
const auto entry_type{mix_info.mix_id == FinalMixId ? PerformanceEntryType::FinalMix |
||||
|
: PerformanceEntryType::SubMix}; |
||||
|
|
||||
|
switch (effect_info.GetType()) { |
||||
|
case EffectInfoBase::Type::Mix: { |
||||
|
DetailAspect mix_detail_aspect(*this, entry_type, mix_info.node_id, |
||||
|
PerformanceDetailType::Unk5); |
||||
|
GenerateBufferMixerCommand(mix_info.buffer_offset, effect_info, mix_info.node_id); |
||||
|
if (mix_detail_aspect.initialized) { |
||||
|
command_buffer.GeneratePerformanceCommand( |
||||
|
mix_detail_aspect.node_id, PerformanceState::Stop, |
||||
|
mix_detail_aspect.performance_entry_address); |
||||
|
} |
||||
|
} break; |
||||
|
|
||||
|
case EffectInfoBase::Type::Aux: { |
||||
|
DetailAspect aux_detail_aspect(*this, entry_type, mix_info.node_id, |
||||
|
PerformanceDetailType::Unk7); |
||||
|
GenerateAuxCommand(mix_info.buffer_offset, effect_info, mix_info.node_id); |
||||
|
if (aux_detail_aspect.initialized) { |
||||
|
command_buffer.GeneratePerformanceCommand( |
||||
|
aux_detail_aspect.node_id, PerformanceState::Stop, |
||||
|
aux_detail_aspect.performance_entry_address); |
||||
|
} |
||||
|
} break; |
||||
|
|
||||
|
case EffectInfoBase::Type::Delay: { |
||||
|
DetailAspect delay_detail_aspect(*this, entry_type, mix_info.node_id, |
||||
|
PerformanceDetailType::Unk6); |
||||
|
GenerateDelayCommand(mix_info.buffer_offset, effect_info, mix_info.node_id); |
||||
|
if (delay_detail_aspect.initialized) { |
||||
|
command_buffer.GeneratePerformanceCommand( |
||||
|
delay_detail_aspect.node_id, PerformanceState::Stop, |
||||
|
delay_detail_aspect.performance_entry_address); |
||||
|
} |
||||
|
} break; |
||||
|
|
||||
|
case EffectInfoBase::Type::Reverb: { |
||||
|
DetailAspect reverb_detail_aspect(*this, entry_type, mix_info.node_id, |
||||
|
PerformanceDetailType::Unk8); |
||||
|
GenerateReverbCommand(mix_info.buffer_offset, effect_info, mix_info.node_id, |
||||
|
render_context.behavior->IsLongSizePreDelaySupported()); |
||||
|
if (reverb_detail_aspect.initialized) { |
||||
|
command_buffer.GeneratePerformanceCommand( |
||||
|
reverb_detail_aspect.node_id, PerformanceState::Stop, |
||||
|
reverb_detail_aspect.performance_entry_address); |
||||
|
} |
||||
|
} break; |
||||
|
|
||||
|
case EffectInfoBase::Type::I3dl2Reverb: { |
||||
|
DetailAspect i3dl2_detail_aspect(*this, entry_type, mix_info.node_id, |
||||
|
PerformanceDetailType::Unk9); |
||||
|
GenerateI3dl2ReverbEffectCommand(mix_info.buffer_offset, effect_info, mix_info.node_id); |
||||
|
if (i3dl2_detail_aspect.initialized) { |
||||
|
command_buffer.GeneratePerformanceCommand( |
||||
|
i3dl2_detail_aspect.node_id, PerformanceState::Stop, |
||||
|
i3dl2_detail_aspect.performance_entry_address); |
||||
|
} |
||||
|
} break; |
||||
|
|
||||
|
case EffectInfoBase::Type::BiquadFilter: { |
||||
|
DetailAspect biquad_detail_aspect(*this, entry_type, mix_info.node_id, |
||||
|
PerformanceDetailType::Unk4); |
||||
|
GenerateBiquadFilterEffectCommand(mix_info.buffer_offset, effect_info, |
||||
|
mix_info.node_id); |
||||
|
if (biquad_detail_aspect.initialized) { |
||||
|
command_buffer.GeneratePerformanceCommand( |
||||
|
biquad_detail_aspect.node_id, PerformanceState::Stop, |
||||
|
biquad_detail_aspect.performance_entry_address); |
||||
|
} |
||||
|
} break; |
||||
|
|
||||
|
case EffectInfoBase::Type::LightLimiter: { |
||||
|
DetailAspect light_limiter_detail_aspect(*this, entry_type, mix_info.node_id, |
||||
|
PerformanceDetailType::Unk11); |
||||
|
GenerateLightLimiterEffectCommand(mix_info.buffer_offset, effect_info, mix_info.node_id, |
||||
|
effect_index); |
||||
|
if (light_limiter_detail_aspect.initialized) { |
||||
|
command_buffer.GeneratePerformanceCommand( |
||||
|
light_limiter_detail_aspect.node_id, PerformanceState::Stop, |
||||
|
light_limiter_detail_aspect.performance_entry_address); |
||||
|
} |
||||
|
} break; |
||||
|
|
||||
|
case EffectInfoBase::Type::Capture: { |
||||
|
DetailAspect capture_detail_aspect(*this, entry_type, mix_info.node_id, |
||||
|
PerformanceDetailType::Unk12); |
||||
|
GenerateCaptureCommand(mix_info.buffer_offset, effect_info, mix_info.node_id); |
||||
|
if (capture_detail_aspect.initialized) { |
||||
|
command_buffer.GeneratePerformanceCommand( |
||||
|
capture_detail_aspect.node_id, PerformanceState::Stop, |
||||
|
capture_detail_aspect.performance_entry_address); |
||||
|
} |
||||
|
} break; |
||||
|
|
||||
|
case EffectInfoBase::Type::Compressor: { |
||||
|
DetailAspect capture_detail_aspect(*this, entry_type, mix_info.node_id, |
||||
|
PerformanceDetailType::Unk13); |
||||
|
GenerateCompressorCommand(mix_info.buffer_offset, effect_info, mix_info.node_id); |
||||
|
if (capture_detail_aspect.initialized) { |
||||
|
command_buffer.GeneratePerformanceCommand( |
||||
|
capture_detail_aspect.node_id, PerformanceState::Stop, |
||||
|
capture_detail_aspect.performance_entry_address); |
||||
|
} |
||||
|
} break; |
||||
|
|
||||
|
default: |
||||
|
LOG_ERROR(Service_Audio, "Invalid effect type {}", |
||||
|
static_cast<u32>(effect_info.GetType())); |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
effect_info.UpdateForCommandGeneration(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void CommandGenerator::GenerateMixCommands(MixInfo& mix_info) { |
||||
|
u8 precision{15}; |
||||
|
if (render_context.behavior->IsVolumeMixParameterPrecisionQ23Supported()) { |
||||
|
precision = 23; |
||||
|
} |
||||
|
|
||||
|
if (!mix_info.HasAnyConnection()) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
if (mix_info.dst_mix_id == UnusedMixId) { |
||||
|
if (mix_info.dst_splitter_id != UnusedSplitterId) { |
||||
|
s16 dest_id{0}; |
||||
|
auto destination{ |
||||
|
splitter_context.GetDesintationData(mix_info.dst_splitter_id, dest_id)}; |
||||
|
while (destination != nullptr) { |
||||
|
if (destination->IsConfigured()) { |
||||
|
auto splitter_mix_id{destination->GetMixId()}; |
||||
|
if (splitter_mix_id < mix_context.GetCount()) { |
||||
|
auto splitter_mix_info{mix_context.GetInfo(splitter_mix_id)}; |
||||
|
const s16 input_index{static_cast<s16>(mix_info.buffer_offset + |
||||
|
(dest_id % mix_info.buffer_count))}; |
||||
|
for (s16 i = 0; i < splitter_mix_info->buffer_count; i++) { |
||||
|
auto volume{mix_info.volume * destination->GetMixVolume(i)}; |
||||
|
if (volume != 0.0f) { |
||||
|
command_buffer.GenerateMixCommand( |
||||
|
mix_info.node_id, input_index, |
||||
|
splitter_mix_info->buffer_offset + i, mix_info.buffer_offset, |
||||
|
volume, precision); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
dest_id++; |
||||
|
destination = |
||||
|
splitter_context.GetDesintationData(mix_info.dst_splitter_id, dest_id); |
||||
|
} |
||||
|
} |
||||
|
} else { |
||||
|
auto dest_mix_info{mix_context.GetInfo(mix_info.dst_mix_id)}; |
||||
|
for (s16 i = 0; i < mix_info.buffer_count; i++) { |
||||
|
for (s16 j = 0; j < dest_mix_info->buffer_count; j++) { |
||||
|
auto volume{mix_info.volume * mix_info.mix_volumes[i][j]}; |
||||
|
if (volume != 0.0f) { |
||||
|
command_buffer.GenerateMixCommand(mix_info.node_id, mix_info.buffer_offset + i, |
||||
|
dest_mix_info->buffer_offset + j, |
||||
|
mix_info.buffer_offset, volume, precision); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void CommandGenerator::GenerateSubMixCommand(MixInfo& mix_info) { |
||||
|
command_buffer.GenerateDepopForMixBuffersCommand(mix_info.node_id, mix_info, |
||||
|
render_context.depop_buffer); |
||||
|
GenerateEffectCommand(mix_info); |
||||
|
|
||||
|
DetailAspect mix_detail_aspect(*this, PerformanceEntryType::SubMix, mix_info.node_id, |
||||
|
PerformanceDetailType::Unk5); |
||||
|
|
||||
|
GenerateMixCommands(mix_info); |
||||
|
|
||||
|
if (mix_detail_aspect.initialized) { |
||||
|
command_buffer.GeneratePerformanceCommand(mix_detail_aspect.node_id, PerformanceState::Stop, |
||||
|
mix_detail_aspect.performance_entry_address); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void CommandGenerator::GenerateSubMixCommands() { |
||||
|
const auto submix_count{mix_context.GetCount()}; |
||||
|
for (s32 i = 0; i < submix_count; i++) { |
||||
|
auto sorted_info{mix_context.GetSortedInfo(i)}; |
||||
|
if (!sorted_info->in_use || sorted_info->mix_id == FinalMixId) { |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
EntryAspect submix_entry_aspect(*this, PerformanceEntryType::SubMix, sorted_info->node_id); |
||||
|
|
||||
|
GenerateSubMixCommand(*sorted_info); |
||||
|
|
||||
|
if (submix_entry_aspect.initialized) { |
||||
|
command_buffer.GeneratePerformanceCommand( |
||||
|
submix_entry_aspect.node_id, PerformanceState::Stop, |
||||
|
submix_entry_aspect.performance_entry_address); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void CommandGenerator::GenerateFinalMixCommand() { |
||||
|
auto& final_mix_info{*mix_context.GetFinalMixInfo()}; |
||||
|
|
||||
|
command_buffer.GenerateDepopForMixBuffersCommand(final_mix_info.node_id, final_mix_info, |
||||
|
render_context.depop_buffer); |
||||
|
GenerateEffectCommand(final_mix_info); |
||||
|
|
||||
|
u8 precision{15}; |
||||
|
if (render_context.behavior->IsVolumeMixParameterPrecisionQ23Supported()) { |
||||
|
precision = 23; |
||||
|
} |
||||
|
|
||||
|
for (s16 i = 0; i < final_mix_info.buffer_count; i++) { |
||||
|
DetailAspect volume_aspect(*this, PerformanceEntryType::FinalMix, final_mix_info.node_id, |
||||
|
PerformanceDetailType::Unk3); |
||||
|
command_buffer.GenerateVolumeCommand(final_mix_info.node_id, final_mix_info.buffer_offset, |
||||
|
i, final_mix_info.volume, precision); |
||||
|
if (volume_aspect.initialized) { |
||||
|
command_buffer.GeneratePerformanceCommand(volume_aspect.node_id, PerformanceState::Stop, |
||||
|
volume_aspect.performance_entry_address); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void CommandGenerator::GenerateFinalMixCommands() { |
||||
|
auto final_mix_info{mix_context.GetFinalMixInfo()}; |
||||
|
EntryAspect final_mix_entry(*this, PerformanceEntryType::FinalMix, final_mix_info->node_id); |
||||
|
GenerateFinalMixCommand(); |
||||
|
if (final_mix_entry.initialized) { |
||||
|
command_buffer.GeneratePerformanceCommand(final_mix_entry.node_id, PerformanceState::Stop, |
||||
|
final_mix_entry.performance_entry_address); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void CommandGenerator::GenerateSinkCommands() { |
||||
|
const auto sink_count{sink_context.GetCount()}; |
||||
|
|
||||
|
for (u32 i = 0; i < sink_count; i++) { |
||||
|
auto sink_info{sink_context.GetInfo(i)}; |
||||
|
if (sink_info->IsUsed() && sink_info->GetType() == SinkInfoBase::Type::DeviceSink) { |
||||
|
auto state{reinterpret_cast<DeviceSinkInfo::DeviceState*>(sink_info->GetState())}; |
||||
|
if (command_header.sample_rate != TargetSampleRate && |
||||
|
state->upsampler_info == nullptr) { |
||||
|
auto device_state{sink_info->GetDeviceState()}; |
||||
|
device_state->upsampler_info = render_context.upsampler_manager->Allocate(); |
||||
|
} |
||||
|
|
||||
|
EntryAspect device_sink_entry(*this, PerformanceEntryType::Sink, |
||||
|
sink_info->GetNodeId()); |
||||
|
auto final_mix{mix_context.GetFinalMixInfo()}; |
||||
|
GenerateSinkCommand(final_mix->buffer_offset, *sink_info); |
||||
|
|
||||
|
if (device_sink_entry.initialized) { |
||||
|
command_buffer.GeneratePerformanceCommand( |
||||
|
device_sink_entry.node_id, PerformanceState::Stop, |
||||
|
device_sink_entry.performance_entry_address); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
for (u32 i = 0; i < sink_count; i++) { |
||||
|
auto sink_info{sink_context.GetInfo(i)}; |
||||
|
if (sink_info->IsUsed() && sink_info->GetType() == SinkInfoBase::Type::CircularBufferSink) { |
||||
|
EntryAspect circular_buffer_entry(*this, PerformanceEntryType::Sink, |
||||
|
sink_info->GetNodeId()); |
||||
|
auto final_mix{mix_context.GetFinalMixInfo()}; |
||||
|
GenerateSinkCommand(final_mix->buffer_offset, *sink_info); |
||||
|
|
||||
|
if (circular_buffer_entry.initialized) { |
||||
|
command_buffer.GeneratePerformanceCommand( |
||||
|
circular_buffer_entry.node_id, PerformanceState::Stop, |
||||
|
circular_buffer_entry.performance_entry_address); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void CommandGenerator::GenerateSinkCommand(const s16 buffer_offset, SinkInfoBase& sink_info) { |
||||
|
if (sink_info.ShouldSkip()) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
switch (sink_info.GetType()) { |
||||
|
case SinkInfoBase::Type::DeviceSink: |
||||
|
GenerateDeviceSinkCommand(buffer_offset, sink_info); |
||||
|
break; |
||||
|
|
||||
|
case SinkInfoBase::Type::CircularBufferSink: |
||||
|
command_buffer.GenerateCircularBufferSinkCommand(sink_info.GetNodeId(), sink_info, |
||||
|
buffer_offset); |
||||
|
break; |
||||
|
|
||||
|
default: |
||||
|
LOG_ERROR(Service_Audio, "Invalid sink type {}", static_cast<u32>(sink_info.GetType())); |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
sink_info.UpdateForCommandGeneration(); |
||||
|
} |
||||
|
|
||||
|
void CommandGenerator::GenerateDeviceSinkCommand(const s16 buffer_offset, SinkInfoBase& sink_info) { |
||||
|
auto& parameter{ |
||||
|
*reinterpret_cast<DeviceSinkInfo::DeviceInParameter*>(sink_info.GetParameter())}; |
||||
|
auto state{*reinterpret_cast<DeviceSinkInfo::DeviceState*>(sink_info.GetState())}; |
||||
|
|
||||
|
if (render_context.channels == 2 && parameter.downmix_enabled) { |
||||
|
command_buffer.GenerateDownMix6chTo2chCommand(InvalidNodeId, parameter.inputs, |
||||
|
buffer_offset, parameter.downmix_coeff); |
||||
|
} |
||||
|
|
||||
|
if (state.upsampler_info != nullptr) { |
||||
|
command_buffer.GenerateUpsampleCommand( |
||||
|
InvalidNodeId, buffer_offset, *state.upsampler_info, parameter.input_count, |
||||
|
parameter.inputs, command_header.buffer_count, command_header.sample_count, |
||||
|
command_header.sample_rate); |
||||
|
} |
||||
|
|
||||
|
command_buffer.GenerateDeviceSinkCommand(InvalidNodeId, buffer_offset, sink_info, |
||||
|
render_context.session_id, |
||||
|
command_header.samples_buffer); |
||||
|
} |
||||
|
|
||||
|
void CommandGenerator::GeneratePerformanceCommand( |
||||
|
s32 node_id, PerformanceState state, const PerformanceEntryAddresses& entry_addresses) { |
||||
|
command_buffer.GeneratePerformanceCommand(node_id, state, entry_addresses); |
||||
|
} |
||||
|
|
||||
|
} // namespace AudioCore::AudioRenderer
|
||||
@ -0,0 +1,349 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project |
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <span> |
||||
|
|
||||
|
#include "audio_core/renderer/command/commands.h" |
||||
|
#include "audio_core/renderer/performance/performance_manager.h" |
||||
|
#include "common/common_types.h" |
||||
|
|
||||
|
namespace AudioCore { |
||||
|
struct AudioRendererSystemContext; |
||||
|
|
||||
|
namespace AudioRenderer { |
||||
|
class CommandBuffer; |
||||
|
struct CommandListHeader; |
||||
|
class VoiceContext; |
||||
|
class MixContext; |
||||
|
class EffectContext; |
||||
|
class SplitterContext; |
||||
|
class SinkContext; |
||||
|
class BehaviorInfo; |
||||
|
class VoiceInfo; |
||||
|
struct VoiceState; |
||||
|
class MixInfo; |
||||
|
class SinkInfoBase; |
||||
|
|
||||
|
/** |
||||
|
* Generates all commands to build up a command list, which are sent to the AudioRender for |
||||
|
* processing. |
||||
|
*/ |
||||
|
class CommandGenerator { |
||||
|
public: |
||||
|
explicit CommandGenerator(CommandBuffer& command_buffer, |
||||
|
const CommandListHeader& command_list_header, |
||||
|
const AudioRendererSystemContext& render_context, |
||||
|
VoiceContext& voice_context, MixContext& mix_context, |
||||
|
EffectContext& effect_context, SinkContext& sink_context, |
||||
|
SplitterContext& splitter_context, |
||||
|
PerformanceManager* performance_manager); |
||||
|
|
||||
|
/** |
||||
|
* Calculate the buffer size needed for commands. |
||||
|
* |
||||
|
* @param behavior - Used to check what features are enabled. |
||||
|
* @param params - Input rendering parameters for numbers of voices/mixes/sinks etc. |
||||
|
*/ |
||||
|
static u64 CalculateCommandBufferSize(const BehaviorInfo& behavior, |
||||
|
const AudioRendererParameterInternal& params) { |
||||
|
u64 size{0}; |
||||
|
|
||||
|
// Effects |
||||
|
size += params.effects * sizeof(EffectInfoBase); |
||||
|
|
||||
|
// Voices |
||||
|
u64 voice_size{0}; |
||||
|
if (behavior.IsWaveBufferVer2Supported()) { |
||||
|
voice_size = std::max(std::max(sizeof(AdpcmDataSourceVersion2Command), |
||||
|
sizeof(PcmInt16DataSourceVersion2Command)), |
||||
|
sizeof(PcmFloatDataSourceVersion2Command)); |
||||
|
} else { |
||||
|
voice_size = std::max(std::max(sizeof(AdpcmDataSourceVersion1Command), |
||||
|
sizeof(PcmInt16DataSourceVersion1Command)), |
||||
|
sizeof(PcmFloatDataSourceVersion1Command)); |
||||
|
} |
||||
|
voice_size += sizeof(BiquadFilterCommand) * MaxBiquadFilters; |
||||
|
voice_size += sizeof(VolumeRampCommand); |
||||
|
voice_size += sizeof(MixRampGroupedCommand); |
||||
|
|
||||
|
size += params.voices * (params.splitter_infos * sizeof(DepopPrepareCommand) + voice_size); |
||||
|
|
||||
|
// Sub mixes |
||||
|
size += sizeof(DepopForMixBuffersCommand) + |
||||
|
(sizeof(MixCommand) * MaxMixBuffers) * MaxMixBuffers; |
||||
|
|
||||
|
// Final mix |
||||
|
size += sizeof(DepopForMixBuffersCommand) + sizeof(VolumeCommand) * MaxMixBuffers; |
||||
|
|
||||
|
// Splitters |
||||
|
size += params.splitter_destinations * sizeof(MixRampCommand) * MaxMixBuffers; |
||||
|
|
||||
|
// Sinks |
||||
|
size += |
||||
|
params.sinks * std::max(sizeof(DeviceSinkCommand), sizeof(CircularBufferSinkCommand)); |
||||
|
|
||||
|
// Performance |
||||
|
size += (params.effects + params.voices + params.sinks + params.sub_mixes + 1 + |
||||
|
PerformanceManager::MaxDetailEntries) * |
||||
|
sizeof(PerformanceCommand); |
||||
|
return size; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Get the current command buffer used to generate commands. |
||||
|
* |
||||
|
* @return The command buffer. |
||||
|
*/ |
||||
|
CommandBuffer& GetCommandBuffer() { |
||||
|
return command_buffer; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Get the current performance manager, |
||||
|
* |
||||
|
* @return The performance manager. May be nullptr. |
||||
|
*/ |
||||
|
PerformanceManager* GetPerformanceManager() { |
||||
|
return performance_manager; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Generate a data source command. |
||||
|
* These are the basis for all audio output. |
||||
|
* |
||||
|
* @param voice_info - Generate the command from this voice. |
||||
|
* @param voice_state - State used by the AudioRenderer across calls. |
||||
|
* @param channel - Channel index to generate the command into. |
||||
|
*/ |
||||
|
void GenerateDataSourceCommand(VoiceInfo& voice_info, const VoiceState& voice_state, |
||||
|
s8 channel); |
||||
|
|
||||
|
/** |
||||
|
* Generate voice mixing commands. |
||||
|
* These are used to mix buffers together, to mix one input to many outputs, |
||||
|
* and also used as copy commands to move data around and prevent it being accidentally |
||||
|
* overwritten, e.g by another data source command into the same channel. |
||||
|
* |
||||
|
* @param mix_volumes - Current volumes of the mix. |
||||
|
* @param prev_mix_volumes - Previous volumes of the mix. |
||||
|
* @param voice_state - State used by the AudioRenderer across calls. |
||||
|
* @param output_index - Output mix buffer index. |
||||
|
* @param buffer_count - Number of active mix buffers. |
||||
|
* @param input_index - Input mix buffer index. |
||||
|
* @param node_id - Node id of the voice this command is generated for. |
||||
|
*/ |
||||
|
void GenerateVoiceMixCommand(std::span<const f32> mix_volumes, |
||||
|
std::span<const f32> prev_mix_volumes, |
||||
|
const VoiceState& voice_state, s16 output_index, s16 buffer_count, |
||||
|
s16 input_index, s32 node_id); |
||||
|
|
||||
|
/** |
||||
|
* Generate a biquad filter command for a voice. |
||||
|
* |
||||
|
* @param voice_info - Voice info this command is generated from. |
||||
|
* @param voice_state - State used by the AudioRenderer across calls. |
||||
|
* @param buffer_count - Number of active mix buffers. |
||||
|
* @param channel - Channel index of this command. |
||||
|
* @param node_id - Node id of the voice this command is generated for. |
||||
|
*/ |
||||
|
void GenerateBiquadFilterCommandForVoice(VoiceInfo& voice_info, const VoiceState& voice_state, |
||||
|
s16 buffer_count, s8 channel, s32 node_id); |
||||
|
|
||||
|
/** |
||||
|
* Generate commands for a voice. |
||||
|
* Includes a data source, biquad filter, volume and mixing. |
||||
|
* |
||||
|
* @param voice_info - Voice info these commands are generated from. |
||||
|
*/ |
||||
|
void GenerateVoiceCommand(VoiceInfo& voice_info); |
||||
|
|
||||
|
/** |
||||
|
* Generate commands for all voices. |
||||
|
*/ |
||||
|
void GenerateVoiceCommands(); |
||||
|
|
||||
|
/** |
||||
|
* Generate a mixing command. |
||||
|
* |
||||
|
* @param buffer_offset - Base mix buffer offset to use. |
||||
|
* @param effect_info_base - BufferMixer effect info. |
||||
|
* @param node_id - Node id of the mix this command is generated for. |
||||
|
*/ |
||||
|
void GenerateBufferMixerCommand(s16 buffer_offset, EffectInfoBase& effect_info_base, |
||||
|
s32 node_id); |
||||
|
|
||||
|
/** |
||||
|
* Generate a delay effect command. |
||||
|
* |
||||
|
* @param buffer_offset - Base mix buffer offset to use. |
||||
|
* @param effect_info_base - Delay effect info. |
||||
|
* @param node_id - Node id of the mix this command is generated for. |
||||
|
*/ |
||||
|
void GenerateDelayCommand(s16 buffer_offset, EffectInfoBase& effect_info_base, s32 node_id); |
||||
|
|
||||
|
/** |
||||
|
* Generate a reverb effect command. |
||||
|
* |
||||
|
* @param buffer_offset - Base mix buffer offset to use. |
||||
|
* @param effect_info_base - Reverb effect info. |
||||
|
* @param node_id - Node id of the mix this command is generated for. |
||||
|
* @param long_size_pre_delay_supported - Use a longer pre-delay time before reverb starts. |
||||
|
*/ |
||||
|
void GenerateReverbCommand(s16 buffer_offset, EffectInfoBase& effect_info_base, s32 node_id, |
||||
|
bool long_size_pre_delay_supported); |
||||
|
|
||||
|
/** |
||||
|
* Generate an I3DL2 reverb effect command. |
||||
|
* |
||||
|
* @param buffer_offset - Base mix buffer offset to use. |
||||
|
* @param effect_info_base - I3DL2Reverb effect info. |
||||
|
* @param node_id - Node id of the mix this command is generated for. |
||||
|
*/ |
||||
|
void GenerateI3dl2ReverbEffectCommand(s16 buffer_offset, EffectInfoBase& effect_info, |
||||
|
s32 node_id); |
||||
|
|
||||
|
/** |
||||
|
* Generate an aux effect command. |
||||
|
* |
||||
|
* @param buffer_offset - Base mix buffer offset to use. |
||||
|
* @param effect_info_base - Aux effect info. |
||||
|
* @param node_id - Node id of the mix this command is generated for. |
||||
|
*/ |
||||
|
void GenerateAuxCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id); |
||||
|
|
||||
|
/** |
||||
|
* Generate a biquad filter effect command. |
||||
|
* |
||||
|
* @param buffer_offset - Base mix buffer offset to use. |
||||
|
* @param effect_info_base - Aux effect info. |
||||
|
* @param node_id - Node id of the mix this command is generated for. |
||||
|
*/ |
||||
|
void GenerateBiquadFilterEffectCommand(s16 buffer_offset, EffectInfoBase& effect_info, |
||||
|
s32 node_id); |
||||
|
|
||||
|
/** |
||||
|
* Generate a light limiter effect command. |
||||
|
* |
||||
|
* @param buffer_offset - Base mix buffer offset to use. |
||||
|
* @param effect_info_base - Limiter effect info. |
||||
|
* @param node_id - Node id of the mix this command is generated for. |
||||
|
* @param effect_index - Index for the statistics state. |
||||
|
*/ |
||||
|
void GenerateLightLimiterEffectCommand(s16 buffer_offset, EffectInfoBase& effect_info, |
||||
|
s32 node_id, u32 effect_index); |
||||
|
|
||||
|
/** |
||||
|
* Generate a capture effect command. |
||||
|
* Writes a mix buffer back to game memory. |
||||
|
* |
||||
|
* @param buffer_offset - Base mix buffer offset to use. |
||||
|
* @param effect_info_base - Capture effect info. |
||||
|
* @param node_id - Node id of the mix this command is generated for. |
||||
|
*/ |
||||
|
void GenerateCaptureCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id); |
||||
|
|
||||
|
/** |
||||
|
* Generate a compressor effect command. |
||||
|
* |
||||
|
* @param buffer_offset - Base mix buffer offset to use. |
||||
|
* @param effect_info_base - Compressor effect info. |
||||
|
* @param node_id - Node id of the mix this command is generated for. |
||||
|
*/ |
||||
|
void GenerateCompressorCommand(const s16 buffer_offset, EffectInfoBase& effect_info, |
||||
|
const s32 node_id); |
||||
|
|
||||
|
/** |
||||
|
* Generate all effect commands for a mix. |
||||
|
* |
||||
|
* @param mix_info - Mix to generate effects from. |
||||
|
*/ |
||||
|
void GenerateEffectCommand(MixInfo& mix_info); |
||||
|
|
||||
|
/** |
||||
|
* Generate all mix commands. |
||||
|
* |
||||
|
* @param mix_info - Mix to generate effects from. |
||||
|
*/ |
||||
|
void GenerateMixCommands(MixInfo& mix_info); |
||||
|
|
||||
|
/** |
||||
|
* Generate a submix command. |
||||
|
* Generates all effects and all mixing commands. |
||||
|
* |
||||
|
* @param mix_info - Mix to generate effects from. |
||||
|
*/ |
||||
|
void GenerateSubMixCommand(MixInfo& mix_info); |
||||
|
|
||||
|
/** |
||||
|
* Generate all submix command. |
||||
|
*/ |
||||
|
void GenerateSubMixCommands(); |
||||
|
|
||||
|
/** |
||||
|
* Generate the final mix. |
||||
|
*/ |
||||
|
void GenerateFinalMixCommand(); |
||||
|
|
||||
|
/** |
||||
|
* Generate the final mix commands. |
||||
|
*/ |
||||
|
void GenerateFinalMixCommands(); |
||||
|
|
||||
|
/** |
||||
|
* Generate all sink commands. |
||||
|
*/ |
||||
|
void GenerateSinkCommands(); |
||||
|
|
||||
|
/** |
||||
|
* Generate a sink command. |
||||
|
* Sends samples out to the backend, or a game-supplied circular buffer. |
||||
|
* |
||||
|
* @param buffer_offset - Base mix buffer offset to use. |
||||
|
* @param sink_info - Sink info to generate the commands from. |
||||
|
*/ |
||||
|
void GenerateSinkCommand(s16 buffer_offset, SinkInfoBase& sink_info); |
||||
|
|
||||
|
/** |
||||
|
* Generate a device sink command. |
||||
|
* Sends samples out to the backend. |
||||
|
* |
||||
|
* @param buffer_offset - Base mix buffer offset to use. |
||||
|
* @param sink_info - Sink info to generate the commands from. |
||||
|
*/ |
||||
|
void GenerateDeviceSinkCommand(s16 buffer_offset, SinkInfoBase& sink_info); |
||||
|
|
||||
|
/** |
||||
|
* Generate a performance command. |
||||
|
* Used to report performance metrics of the AudioRenderer back to the game. |
||||
|
* |
||||
|
* @param buffer_offset - Base mix buffer offset to use. |
||||
|
* @param sink_info - Sink info to generate the commands from. |
||||
|
*/ |
||||
|
void GeneratePerformanceCommand(s32 node_id, PerformanceState state, |
||||
|
const PerformanceEntryAddresses& entry_addresses); |
||||
|
|
||||
|
private: |
||||
|
/// Commands will be written by this buffer |
||||
|
CommandBuffer& command_buffer; |
||||
|
/// Header information for the commands generated |
||||
|
const CommandListHeader& command_header; |
||||
|
/// Various things to control generation |
||||
|
const AudioRendererSystemContext& render_context; |
||||
|
/// Used for generating voices |
||||
|
VoiceContext& voice_context; |
||||
|
/// Used for generating mixes |
||||
|
MixContext& mix_context; |
||||
|
/// Used for generating effects |
||||
|
EffectContext& effect_context; |
||||
|
/// Used for generating sinks |
||||
|
SinkContext& sink_context; |
||||
|
/// Used for generating submixes |
||||
|
SplitterContext& splitter_context; |
||||
|
/// Used for generating performance |
||||
|
PerformanceManager* performance_manager; |
||||
|
}; |
||||
|
|
||||
|
} // namespace AudioRenderer |
||||
|
} // namespace AudioCore |
||||
@ -0,0 +1,22 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project |
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <span> |
||||
|
|
||||
|
#include "audio_core/common/common.h" |
||||
|
#include "common/common_types.h" |
||||
|
|
||||
|
namespace AudioCore::AudioRenderer { |
||||
|
|
||||
|
struct CommandListHeader { |
||||
|
u64 buffer_size; |
||||
|
u32 command_count; |
||||
|
std::span<s32> samples_buffer; |
||||
|
s16 buffer_count; |
||||
|
u32 sample_count; |
||||
|
u32 sample_rate; |
||||
|
}; |
||||
|
|
||||
|
} // namespace AudioCore::AudioRenderer |
||||
3620
src/audio_core/renderer/command/command_processing_time_estimator.cpp
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -0,0 +1,254 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project |
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include "audio_core/renderer/command/commands.h" |
||||
|
#include "common/common_types.h" |
||||
|
|
||||
|
namespace AudioCore::AudioRenderer { |
||||
|
/** |
||||
|
* Estimate the processing time required for all commands. |
||||
|
*/ |
||||
|
class ICommandProcessingTimeEstimator { |
||||
|
public: |
||||
|
virtual ~ICommandProcessingTimeEstimator() = default; |
||||
|
|
||||
|
virtual u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const = 0; |
||||
|
virtual u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const = 0; |
||||
|
virtual u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const = 0; |
||||
|
virtual u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const = 0; |
||||
|
virtual u32 Estimate(const AdpcmDataSourceVersion1Command& command) const = 0; |
||||
|
virtual u32 Estimate(const AdpcmDataSourceVersion2Command& command) const = 0; |
||||
|
virtual u32 Estimate(const VolumeCommand& command) const = 0; |
||||
|
virtual u32 Estimate(const VolumeRampCommand& command) const = 0; |
||||
|
virtual u32 Estimate(const BiquadFilterCommand& command) const = 0; |
||||
|
virtual u32 Estimate(const MixCommand& command) const = 0; |
||||
|
virtual u32 Estimate(const MixRampCommand& command) const = 0; |
||||
|
virtual u32 Estimate(const MixRampGroupedCommand& command) const = 0; |
||||
|
virtual u32 Estimate(const DepopPrepareCommand& command) const = 0; |
||||
|
virtual u32 Estimate(const DepopForMixBuffersCommand& command) const = 0; |
||||
|
virtual u32 Estimate(const DelayCommand& command) const = 0; |
||||
|
virtual u32 Estimate(const UpsampleCommand& command) const = 0; |
||||
|
virtual u32 Estimate(const DownMix6chTo2chCommand& command) const = 0; |
||||
|
virtual u32 Estimate(const AuxCommand& command) const = 0; |
||||
|
virtual u32 Estimate(const DeviceSinkCommand& command) const = 0; |
||||
|
virtual u32 Estimate(const CircularBufferSinkCommand& command) const = 0; |
||||
|
virtual u32 Estimate(const ReverbCommand& command) const = 0; |
||||
|
virtual u32 Estimate(const I3dl2ReverbCommand& command) const = 0; |
||||
|
virtual u32 Estimate(const PerformanceCommand& command) const = 0; |
||||
|
virtual u32 Estimate(const ClearMixBufferCommand& command) const = 0; |
||||
|
virtual u32 Estimate(const CopyMixBufferCommand& command) const = 0; |
||||
|
virtual u32 Estimate(const LightLimiterVersion1Command& command) const = 0; |
||||
|
virtual u32 Estimate(const LightLimiterVersion2Command& command) const = 0; |
||||
|
virtual u32 Estimate(const MultiTapBiquadFilterCommand& command) const = 0; |
||||
|
virtual u32 Estimate(const CaptureCommand& command) const = 0; |
||||
|
virtual u32 Estimate(const CompressorCommand& command) const = 0; |
||||
|
}; |
||||
|
|
||||
|
class CommandProcessingTimeEstimatorVersion1 final : public ICommandProcessingTimeEstimator { |
||||
|
public: |
||||
|
CommandProcessingTimeEstimatorVersion1(u32 sample_count_, u32 buffer_count_) |
||||
|
: sample_count{sample_count_}, buffer_count{buffer_count_} {} |
||||
|
|
||||
|
u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const override; |
||||
|
u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const override; |
||||
|
u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const override; |
||||
|
u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const override; |
||||
|
u32 Estimate(const AdpcmDataSourceVersion1Command& command) const override; |
||||
|
u32 Estimate(const AdpcmDataSourceVersion2Command& command) const override; |
||||
|
u32 Estimate(const VolumeCommand& command) const override; |
||||
|
u32 Estimate(const VolumeRampCommand& command) const override; |
||||
|
u32 Estimate(const BiquadFilterCommand& command) const override; |
||||
|
u32 Estimate(const MixCommand& command) const override; |
||||
|
u32 Estimate(const MixRampCommand& command) const override; |
||||
|
u32 Estimate(const MixRampGroupedCommand& command) const override; |
||||
|
u32 Estimate(const DepopPrepareCommand& command) const override; |
||||
|
u32 Estimate(const DepopForMixBuffersCommand& command) const override; |
||||
|
u32 Estimate(const DelayCommand& command) const override; |
||||
|
u32 Estimate(const UpsampleCommand& command) const override; |
||||
|
u32 Estimate(const DownMix6chTo2chCommand& command) const override; |
||||
|
u32 Estimate(const AuxCommand& command) const override; |
||||
|
u32 Estimate(const DeviceSinkCommand& command) const override; |
||||
|
u32 Estimate(const CircularBufferSinkCommand& command) const override; |
||||
|
u32 Estimate(const ReverbCommand& command) const override; |
||||
|
u32 Estimate(const I3dl2ReverbCommand& command) const override; |
||||
|
u32 Estimate(const PerformanceCommand& command) const override; |
||||
|
u32 Estimate(const ClearMixBufferCommand& command) const override; |
||||
|
u32 Estimate(const CopyMixBufferCommand& command) const override; |
||||
|
u32 Estimate(const LightLimiterVersion1Command& command) const override; |
||||
|
u32 Estimate(const LightLimiterVersion2Command& command) const override; |
||||
|
u32 Estimate(const MultiTapBiquadFilterCommand& command) const override; |
||||
|
u32 Estimate(const CaptureCommand& command) const override; |
||||
|
u32 Estimate(const CompressorCommand& command) const override; |
||||
|
|
||||
|
private: |
||||
|
u32 sample_count{}; |
||||
|
u32 buffer_count{}; |
||||
|
}; |
||||
|
|
||||
|
class CommandProcessingTimeEstimatorVersion2 final : public ICommandProcessingTimeEstimator { |
||||
|
public: |
||||
|
CommandProcessingTimeEstimatorVersion2(u32 sample_count_, u32 buffer_count_) |
||||
|
: sample_count{sample_count_}, buffer_count{buffer_count_} {} |
||||
|
|
||||
|
u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const override; |
||||
|
u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const override; |
||||
|
u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const override; |
||||
|
u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const override; |
||||
|
u32 Estimate(const AdpcmDataSourceVersion1Command& command) const override; |
||||
|
u32 Estimate(const AdpcmDataSourceVersion2Command& command) const override; |
||||
|
u32 Estimate(const VolumeCommand& command) const override; |
||||
|
u32 Estimate(const VolumeRampCommand& command) const override; |
||||
|
u32 Estimate(const BiquadFilterCommand& command) const override; |
||||
|
u32 Estimate(const MixCommand& command) const override; |
||||
|
u32 Estimate(const MixRampCommand& command) const override; |
||||
|
u32 Estimate(const MixRampGroupedCommand& command) const override; |
||||
|
u32 Estimate(const DepopPrepareCommand& command) const override; |
||||
|
u32 Estimate(const DepopForMixBuffersCommand& command) const override; |
||||
|
u32 Estimate(const DelayCommand& command) const override; |
||||
|
u32 Estimate(const UpsampleCommand& command) const override; |
||||
|
u32 Estimate(const DownMix6chTo2chCommand& command) const override; |
||||
|
u32 Estimate(const AuxCommand& command) const override; |
||||
|
u32 Estimate(const DeviceSinkCommand& command) const override; |
||||
|
u32 Estimate(const CircularBufferSinkCommand& command) const override; |
||||
|
u32 Estimate(const ReverbCommand& command) const override; |
||||
|
u32 Estimate(const I3dl2ReverbCommand& command) const override; |
||||
|
u32 Estimate(const PerformanceCommand& command) const override; |
||||
|
u32 Estimate(const ClearMixBufferCommand& command) const override; |
||||
|
u32 Estimate(const CopyMixBufferCommand& command) const override; |
||||
|
u32 Estimate(const LightLimiterVersion1Command& command) const override; |
||||
|
u32 Estimate(const LightLimiterVersion2Command& command) const override; |
||||
|
u32 Estimate(const MultiTapBiquadFilterCommand& command) const override; |
||||
|
u32 Estimate(const CaptureCommand& command) const override; |
||||
|
u32 Estimate(const CompressorCommand& command) const override; |
||||
|
|
||||
|
private: |
||||
|
u32 sample_count{}; |
||||
|
u32 buffer_count{}; |
||||
|
}; |
||||
|
|
||||
|
class CommandProcessingTimeEstimatorVersion3 final : public ICommandProcessingTimeEstimator { |
||||
|
public: |
||||
|
CommandProcessingTimeEstimatorVersion3(u32 sample_count_, u32 buffer_count_) |
||||
|
: sample_count{sample_count_}, buffer_count{buffer_count_} {} |
||||
|
|
||||
|
u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const override; |
||||
|
u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const override; |
||||
|
u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const override; |
||||
|
u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const override; |
||||
|
u32 Estimate(const AdpcmDataSourceVersion1Command& command) const override; |
||||
|
u32 Estimate(const AdpcmDataSourceVersion2Command& command) const override; |
||||
|
u32 Estimate(const VolumeCommand& command) const override; |
||||
|
u32 Estimate(const VolumeRampCommand& command) const override; |
||||
|
u32 Estimate(const BiquadFilterCommand& command) const override; |
||||
|
u32 Estimate(const MixCommand& command) const override; |
||||
|
u32 Estimate(const MixRampCommand& command) const override; |
||||
|
u32 Estimate(const MixRampGroupedCommand& command) const override; |
||||
|
u32 Estimate(const DepopPrepareCommand& command) const override; |
||||
|
u32 Estimate(const DepopForMixBuffersCommand& command) const override; |
||||
|
u32 Estimate(const DelayCommand& command) const override; |
||||
|
u32 Estimate(const UpsampleCommand& command) const override; |
||||
|
u32 Estimate(const DownMix6chTo2chCommand& command) const override; |
||||
|
u32 Estimate(const AuxCommand& command) const override; |
||||
|
u32 Estimate(const DeviceSinkCommand& command) const override; |
||||
|
u32 Estimate(const CircularBufferSinkCommand& command) const override; |
||||
|
u32 Estimate(const ReverbCommand& command) const override; |
||||
|
u32 Estimate(const I3dl2ReverbCommand& command) const override; |
||||
|
u32 Estimate(const PerformanceCommand& command) const override; |
||||
|
u32 Estimate(const ClearMixBufferCommand& command) const override; |
||||
|
u32 Estimate(const CopyMixBufferCommand& command) const override; |
||||
|
u32 Estimate(const LightLimiterVersion1Command& command) const override; |
||||
|
u32 Estimate(const LightLimiterVersion2Command& command) const override; |
||||
|
u32 Estimate(const MultiTapBiquadFilterCommand& command) const override; |
||||
|
u32 Estimate(const CaptureCommand& command) const override; |
||||
|
u32 Estimate(const CompressorCommand& command) const override; |
||||
|
|
||||
|
private: |
||||
|
u32 sample_count{}; |
||||
|
u32 buffer_count{}; |
||||
|
}; |
||||
|
|
||||
|
class CommandProcessingTimeEstimatorVersion4 final : public ICommandProcessingTimeEstimator { |
||||
|
public: |
||||
|
CommandProcessingTimeEstimatorVersion4(u32 sample_count_, u32 buffer_count_) |
||||
|
: sample_count{sample_count_}, buffer_count{buffer_count_} {} |
||||
|
|
||||
|
u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const override; |
||||
|
u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const override; |
||||
|
u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const override; |
||||
|
u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const override; |
||||
|
u32 Estimate(const AdpcmDataSourceVersion1Command& command) const override; |
||||
|
u32 Estimate(const AdpcmDataSourceVersion2Command& command) const override; |
||||
|
u32 Estimate(const VolumeCommand& command) const override; |
||||
|
u32 Estimate(const VolumeRampCommand& command) const override; |
||||
|
u32 Estimate(const BiquadFilterCommand& command) const override; |
||||
|
u32 Estimate(const MixCommand& command) const override; |
||||
|
u32 Estimate(const MixRampCommand& command) const override; |
||||
|
u32 Estimate(const MixRampGroupedCommand& command) const override; |
||||
|
u32 Estimate(const DepopPrepareCommand& command) const override; |
||||
|
u32 Estimate(const DepopForMixBuffersCommand& command) const override; |
||||
|
u32 Estimate(const DelayCommand& command) const override; |
||||
|
u32 Estimate(const UpsampleCommand& command) const override; |
||||
|
u32 Estimate(const DownMix6chTo2chCommand& command) const override; |
||||
|
u32 Estimate(const AuxCommand& command) const override; |
||||
|
u32 Estimate(const DeviceSinkCommand& command) const override; |
||||
|
u32 Estimate(const CircularBufferSinkCommand& command) const override; |
||||
|
u32 Estimate(const ReverbCommand& command) const override; |
||||
|
u32 Estimate(const I3dl2ReverbCommand& command) const override; |
||||
|
u32 Estimate(const PerformanceCommand& command) const override; |
||||
|
u32 Estimate(const ClearMixBufferCommand& command) const override; |
||||
|
u32 Estimate(const CopyMixBufferCommand& command) const override; |
||||
|
u32 Estimate(const LightLimiterVersion1Command& command) const override; |
||||
|
u32 Estimate(const LightLimiterVersion2Command& command) const override; |
||||
|
u32 Estimate(const MultiTapBiquadFilterCommand& command) const override; |
||||
|
u32 Estimate(const CaptureCommand& command) const override; |
||||
|
u32 Estimate(const CompressorCommand& command) const override; |
||||
|
|
||||
|
private: |
||||
|
u32 sample_count{}; |
||||
|
u32 buffer_count{}; |
||||
|
}; |
||||
|
|
||||
|
class CommandProcessingTimeEstimatorVersion5 final : public ICommandProcessingTimeEstimator { |
||||
|
public: |
||||
|
CommandProcessingTimeEstimatorVersion5(u32 sample_count_, u32 buffer_count_) |
||||
|
: sample_count{sample_count_}, buffer_count{buffer_count_} {} |
||||
|
|
||||
|
u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const override; |
||||
|
u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const override; |
||||
|
u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const override; |
||||
|
u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const override; |
||||
|
u32 Estimate(const AdpcmDataSourceVersion1Command& command) const override; |
||||
|
u32 Estimate(const AdpcmDataSourceVersion2Command& command) const override; |
||||
|
u32 Estimate(const VolumeCommand& command) const override; |
||||
|
u32 Estimate(const VolumeRampCommand& command) const override; |
||||
|
u32 Estimate(const BiquadFilterCommand& command) const override; |
||||
|
u32 Estimate(const MixCommand& command) const override; |
||||
|
u32 Estimate(const MixRampCommand& command) const override; |
||||
|
u32 Estimate(const MixRampGroupedCommand& command) const override; |
||||
|
u32 Estimate(const DepopPrepareCommand& command) const override; |
||||
|
u32 Estimate(const DepopForMixBuffersCommand& command) const override; |
||||
|
u32 Estimate(const DelayCommand& command) const override; |
||||
|
u32 Estimate(const UpsampleCommand& command) const override; |
||||
|
u32 Estimate(const DownMix6chTo2chCommand& command) const override; |
||||
|
u32 Estimate(const AuxCommand& command) const override; |
||||
|
u32 Estimate(const DeviceSinkCommand& command) const override; |
||||
|
u32 Estimate(const CircularBufferSinkCommand& command) const override; |
||||
|
u32 Estimate(const ReverbCommand& command) const override; |
||||
|
u32 Estimate(const I3dl2ReverbCommand& command) const override; |
||||
|
u32 Estimate(const PerformanceCommand& command) const override; |
||||
|
u32 Estimate(const ClearMixBufferCommand& command) const override; |
||||
|
u32 Estimate(const CopyMixBufferCommand& command) const override; |
||||
|
u32 Estimate(const LightLimiterVersion1Command& command) const override; |
||||
|
u32 Estimate(const LightLimiterVersion2Command& command) const override; |
||||
|
u32 Estimate(const MultiTapBiquadFilterCommand& command) const override; |
||||
|
u32 Estimate(const CaptureCommand& command) const override; |
||||
|
u32 Estimate(const CompressorCommand& command) const override; |
||||
|
|
||||
|
private: |
||||
|
u32 sample_count{}; |
||||
|
u32 buffer_count{}; |
||||
|
}; |
||||
|
|
||||
|
} // namespace AudioCore::AudioRenderer |
||||
@ -0,0 +1,32 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project |
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include "audio_core/renderer/command/data_source/adpcm.h" |
||||
|
#include "audio_core/renderer/command/data_source/pcm_float.h" |
||||
|
#include "audio_core/renderer/command/data_source/pcm_int16.h" |
||||
|
#include "audio_core/renderer/command/effect/aux_.h" |
||||
|
#include "audio_core/renderer/command/effect/biquad_filter.h" |
||||
|
#include "audio_core/renderer/command/effect/capture.h" |
||||
|
#include "audio_core/renderer/command/effect/compressor.h" |
||||
|
#include "audio_core/renderer/command/effect/delay.h" |
||||
|
#include "audio_core/renderer/command/effect/i3dl2_reverb.h" |
||||
|
#include "audio_core/renderer/command/effect/light_limiter.h" |
||||
|
#include "audio_core/renderer/command/effect/multi_tap_biquad_filter.h" |
||||
|
#include "audio_core/renderer/command/effect/reverb.h" |
||||
|
#include "audio_core/renderer/command/icommand.h" |
||||
|
#include "audio_core/renderer/command/mix/clear_mix.h" |
||||
|
#include "audio_core/renderer/command/mix/copy_mix.h" |
||||
|
#include "audio_core/renderer/command/mix/depop_for_mix_buffers.h" |
||||
|
#include "audio_core/renderer/command/mix/depop_prepare.h" |
||||
|
#include "audio_core/renderer/command/mix/mix.h" |
||||
|
#include "audio_core/renderer/command/mix/mix_ramp.h" |
||||
|
#include "audio_core/renderer/command/mix/mix_ramp_grouped.h" |
||||
|
#include "audio_core/renderer/command/mix/volume.h" |
||||
|
#include "audio_core/renderer/command/mix/volume_ramp.h" |
||||
|
#include "audio_core/renderer/command/performance/performance.h" |
||||
|
#include "audio_core/renderer/command/resample/downmix_6ch_to_2ch.h" |
||||
|
#include "audio_core/renderer/command/resample/upsample.h" |
||||
|
#include "audio_core/renderer/command/sink/circular_buffer.h" |
||||
|
#include "audio_core/renderer/command/sink/device.h" |
||||
@ -0,0 +1,84 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
|
||||
|
#include <span>
|
||||
|
|
||||
|
#include "audio_core/renderer/adsp/command_list_processor.h"
|
||||
|
#include "audio_core/renderer/command/data_source/adpcm.h"
|
||||
|
#include "audio_core/renderer/command/data_source/decode.h"
|
||||
|
|
||||
|
namespace AudioCore::AudioRenderer { |
||||
|
|
||||
|
void AdpcmDataSourceVersion1Command::Dump(const ADSP::CommandListProcessor& processor, |
||||
|
std::string& string) { |
||||
|
string += fmt::format("AdpcmDataSourceVersion1Command\n\toutput_index {:02X} source sample " |
||||
|
"rate {} target sample rate {} src quality {}\n", |
||||
|
output_index, sample_rate, processor.target_sample_rate, src_quality); |
||||
|
} |
||||
|
|
||||
|
void AdpcmDataSourceVersion1Command::Process(const ADSP::CommandListProcessor& processor) { |
||||
|
auto out_buffer{processor.mix_buffers.subspan(output_index * processor.sample_count, |
||||
|
processor.sample_count)}; |
||||
|
|
||||
|
DecodeFromWaveBuffersArgs args{ |
||||
|
.sample_format{SampleFormat::Adpcm}, |
||||
|
.output{out_buffer}, |
||||
|
.voice_state{reinterpret_cast<VoiceState*>(voice_state)}, |
||||
|
.wave_buffers{wave_buffers}, |
||||
|
.channel{0}, |
||||
|
.channel_count{1}, |
||||
|
.src_quality{src_quality}, |
||||
|
.pitch{pitch}, |
||||
|
.source_sample_rate{sample_rate}, |
||||
|
.target_sample_rate{processor.target_sample_rate}, |
||||
|
.sample_count{processor.sample_count}, |
||||
|
.data_address{data_address}, |
||||
|
.data_size{data_size}, |
||||
|
.IsVoicePlayedSampleCountResetAtLoopPointSupported{(flags & 1) != 0}, |
||||
|
.IsVoicePitchAndSrcSkippedSupported{(flags & 2) != 0}, |
||||
|
}; |
||||
|
|
||||
|
DecodeFromWaveBuffers(*processor.memory, args); |
||||
|
} |
||||
|
|
||||
|
bool AdpcmDataSourceVersion1Command::Verify(const ADSP::CommandListProcessor& processor) { |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
void AdpcmDataSourceVersion2Command::Dump(const ADSP::CommandListProcessor& processor, |
||||
|
std::string& string) { |
||||
|
string += fmt::format("AdpcmDataSourceVersion2Command\n\toutput_index {:02X} source sample " |
||||
|
"rate {} target sample rate {} src quality {}\n", |
||||
|
output_index, sample_rate, processor.target_sample_rate, src_quality); |
||||
|
} |
||||
|
|
||||
|
void AdpcmDataSourceVersion2Command::Process(const ADSP::CommandListProcessor& processor) { |
||||
|
auto out_buffer{processor.mix_buffers.subspan(output_index * processor.sample_count, |
||||
|
processor.sample_count)}; |
||||
|
|
||||
|
DecodeFromWaveBuffersArgs args{ |
||||
|
.sample_format{SampleFormat::Adpcm}, |
||||
|
.output{out_buffer}, |
||||
|
.voice_state{reinterpret_cast<VoiceState*>(voice_state)}, |
||||
|
.wave_buffers{wave_buffers}, |
||||
|
.channel{0}, |
||||
|
.channel_count{1}, |
||||
|
.src_quality{src_quality}, |
||||
|
.pitch{pitch}, |
||||
|
.source_sample_rate{sample_rate}, |
||||
|
.target_sample_rate{processor.target_sample_rate}, |
||||
|
.sample_count{processor.sample_count}, |
||||
|
.data_address{data_address}, |
||||
|
.data_size{data_size}, |
||||
|
.IsVoicePlayedSampleCountResetAtLoopPointSupported{(flags & 1) != 0}, |
||||
|
.IsVoicePitchAndSrcSkippedSupported{(flags & 2) != 0}, |
||||
|
}; |
||||
|
|
||||
|
DecodeFromWaveBuffers(*processor.memory, args); |
||||
|
} |
||||
|
|
||||
|
bool AdpcmDataSourceVersion2Command::Verify(const ADSP::CommandListProcessor& processor) { |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
} // namespace AudioCore::AudioRenderer
|
||||
@ -0,0 +1,119 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project |
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <array> |
||||
|
#include <string> |
||||
|
|
||||
|
#include "audio_core/common/common.h" |
||||
|
#include "audio_core/common/wave_buffer.h" |
||||
|
#include "audio_core/renderer/command/icommand.h" |
||||
|
#include "common/common_types.h" |
||||
|
|
||||
|
namespace AudioCore::AudioRenderer { |
||||
|
namespace ADSP { |
||||
|
class CommandListProcessor; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* AudioRenderer command to decode ADPCM-encoded version 1 wavebuffers |
||||
|
* into the output_index mix buffer. |
||||
|
*/ |
||||
|
struct AdpcmDataSourceVersion1Command : ICommand { |
||||
|
/** |
||||
|
* Print this command's information to a string. |
||||
|
* |
||||
|
* @param processor - The CommandListProcessor processing this command. |
||||
|
* @param string - The string to print into. |
||||
|
*/ |
||||
|
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; |
||||
|
|
||||
|
/** |
||||
|
* Process this command. |
||||
|
* |
||||
|
* @param processor - The CommandListProcessor processing this command. |
||||
|
*/ |
||||
|
void Process(const ADSP::CommandListProcessor& processor) override; |
||||
|
|
||||
|
/** |
||||
|
* Verify this command's data is valid. |
||||
|
* |
||||
|
* @param processor - The CommandListProcessor processing this command. |
||||
|
* @return True if the command is valid, otherwise false. |
||||
|
*/ |
||||
|
bool Verify(const ADSP::CommandListProcessor& processor) override; |
||||
|
|
||||
|
/// Quality used for sample rate conversion |
||||
|
SrcQuality src_quality; |
||||
|
/// Mix buffer index for decoded samples |
||||
|
s16 output_index; |
||||
|
/// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags) |
||||
|
u16 flags; |
||||
|
/// Wavebuffer sample rate |
||||
|
u32 sample_rate; |
||||
|
/// Pitch used for sample rate conversion |
||||
|
f32 pitch; |
||||
|
/// Wavebuffers containing the wavebuffer address, context address, looping information etc |
||||
|
std::array<WaveBufferVersion2, MaxWaveBuffers> wave_buffers; |
||||
|
/// Voice state, updated each call and written back to game |
||||
|
CpuAddr voice_state; |
||||
|
/// Coefficients data address |
||||
|
CpuAddr data_address; |
||||
|
/// Coefficients data size |
||||
|
u64 data_size; |
||||
|
}; |
||||
|
|
||||
|
/** |
||||
|
* AudioRenderer command to decode ADPCM-encoded version 2 wavebuffers |
||||
|
* into the output_index mix buffer. |
||||
|
*/ |
||||
|
struct AdpcmDataSourceVersion2Command : ICommand { |
||||
|
/** |
||||
|
* Print this command's information to a string. |
||||
|
* |
||||
|
* @param processor - The CommandListProcessor processing this command. |
||||
|
* @param string - The string to print into. |
||||
|
*/ |
||||
|
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; |
||||
|
|
||||
|
/** |
||||
|
* Process this command. |
||||
|
* |
||||
|
* @param processor - The CommandListProcessor processing this command. |
||||
|
*/ |
||||
|
void Process(const ADSP::CommandListProcessor& processor) override; |
||||
|
|
||||
|
/** |
||||
|
* Verify this command's data is valid. |
||||
|
* |
||||
|
* @param processor - The CommandListProcessor processing this command. |
||||
|
* @return True if the command is valid, otherwise false. |
||||
|
*/ |
||||
|
bool Verify(const ADSP::CommandListProcessor& processor) override; |
||||
|
|
||||
|
/// Quality used for sample rate conversion |
||||
|
SrcQuality src_quality; |
||||
|
/// Mix buffer index for decoded samples |
||||
|
s16 output_index; |
||||
|
/// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags) |
||||
|
u16 flags; |
||||
|
/// Wavebuffer sample rate |
||||
|
u32 sample_rate; |
||||
|
/// Pitch used for sample rate conversion |
||||
|
f32 pitch; |
||||
|
/// Target channel to read within the wavebuffer |
||||
|
s8 channel_index; |
||||
|
/// Number of channels within the wavebuffer |
||||
|
s8 channel_count; |
||||
|
/// Wavebuffers containing the wavebuffer address, context address, looping information etc |
||||
|
std::array<WaveBufferVersion2, MaxWaveBuffers> wave_buffers; |
||||
|
/// Voice state, updated each call and written back to game |
||||
|
CpuAddr voice_state; |
||||
|
/// Coefficients data address |
||||
|
CpuAddr data_address; |
||||
|
/// Coefficients data size |
||||
|
u64 data_size; |
||||
|
}; |
||||
|
|
||||
|
} // namespace AudioCore::AudioRenderer |
||||
@ -0,0 +1,428 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
|
||||
|
#include <array>
|
||||
|
#include <vector>
|
||||
|
|
||||
|
#include "audio_core/renderer/command/data_source/decode.h"
|
||||
|
#include "audio_core/renderer/command/resample/resample.h"
|
||||
|
#include "common/fixed_point.h"
|
||||
|
#include "common/logging/log.h"
|
||||
|
#include "core/memory.h"
|
||||
|
|
||||
|
namespace AudioCore::AudioRenderer { |
||||
|
|
||||
|
constexpr u32 TempBufferSize = 0x3F00; |
||||
|
constexpr std::array<u8, 3> PitchBySrcQuality = {4, 8, 4}; |
||||
|
|
||||
|
/**
|
||||
|
* Decode PCM data. Only s16 or f32 is supported. |
||||
|
* |
||||
|
* @tparam T - Type to decode. Only s16 and f32 are supported. |
||||
|
* @param memory - Core memory for reading samples. |
||||
|
* @param out_buffer - Output mix buffer to receive the samples. |
||||
|
* @param req - Information for how to decode. |
||||
|
* @return Number of samples decoded. |
||||
|
*/ |
||||
|
template <typename T> |
||||
|
static u32 DecodePcm(Core::Memory::Memory& memory, std::span<s16> out_buffer, |
||||
|
const DecodeArg& req) { |
||||
|
constexpr s32 min{std::numeric_limits<s16>::min()}; |
||||
|
constexpr s32 max{std::numeric_limits<s16>::max()}; |
||||
|
|
||||
|
if (req.buffer == 0 || req.buffer_size == 0) { |
||||
|
return 0; |
||||
|
} |
||||
|
|
||||
|
if (req.start_offset >= req.end_offset) { |
||||
|
return 0; |
||||
|
} |
||||
|
|
||||
|
auto samples_to_decode{ |
||||
|
std::min(req.samples_to_read, req.end_offset - req.start_offset - req.offset)}; |
||||
|
u32 channel_count{static_cast<u32>(req.channel_count)}; |
||||
|
|
||||
|
switch (req.channel_count) { |
||||
|
default: { |
||||
|
const VAddr source{req.buffer + |
||||
|
(((req.start_offset + req.offset) * channel_count) * sizeof(T))}; |
||||
|
const u64 size{channel_count * samples_to_decode}; |
||||
|
const u64 size_bytes{size * sizeof(T)}; |
||||
|
|
||||
|
std::vector<T> samples(size); |
||||
|
memory.ReadBlockUnsafe(source, samples.data(), size_bytes); |
||||
|
|
||||
|
if constexpr (std::is_floating_point_v<T>) { |
||||
|
for (u32 i = 0; i < samples_to_decode; i++) { |
||||
|
auto sample{static_cast<s32>(samples[i * channel_count + req.target_channel] * |
||||
|
std::numeric_limits<s16>::max())}; |
||||
|
out_buffer[i] = static_cast<s16>(std::clamp(sample, min, max)); |
||||
|
} |
||||
|
} else { |
||||
|
for (u32 i = 0; i < samples_to_decode; i++) { |
||||
|
out_buffer[i] = samples[i * channel_count + req.target_channel]; |
||||
|
} |
||||
|
} |
||||
|
} break; |
||||
|
|
||||
|
case 1: |
||||
|
if (req.target_channel != 0) { |
||||
|
LOG_ERROR(Service_Audio, "Invalid target channel, expected 0, got {}", |
||||
|
req.target_channel); |
||||
|
return 0; |
||||
|
} |
||||
|
|
||||
|
const VAddr source{req.buffer + ((req.start_offset + req.offset) * sizeof(T))}; |
||||
|
std::vector<T> samples(samples_to_decode); |
||||
|
memory.ReadBlockUnsafe(source, samples.data(), samples_to_decode * sizeof(T)); |
||||
|
|
||||
|
if constexpr (std::is_floating_point_v<T>) { |
||||
|
for (u32 i = 0; i < samples_to_decode; i++) { |
||||
|
auto sample{static_cast<s32>(samples[i * channel_count + req.target_channel] * |
||||
|
std::numeric_limits<s16>::max())}; |
||||
|
out_buffer[i] = static_cast<s16>(std::clamp(sample, min, max)); |
||||
|
} |
||||
|
} else { |
||||
|
std::memcpy(out_buffer.data(), samples.data(), samples_to_decode * sizeof(s16)); |
||||
|
} |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
return samples_to_decode; |
||||
|
} |
||||
|
|
||||
|
/**
|
||||
|
* Decode ADPCM data. |
||||
|
* |
||||
|
* @param memory - Core memory for reading samples. |
||||
|
* @param out_buffer - Output mix buffer to receive the samples. |
||||
|
* @param req - Information for how to decode. |
||||
|
* @return Number of samples decoded. |
||||
|
*/ |
||||
|
static u32 DecodeAdpcm(Core::Memory::Memory& memory, std::span<s16> out_buffer, |
||||
|
const DecodeArg& req) { |
||||
|
constexpr u32 SamplesPerFrame{14}; |
||||
|
constexpr u32 NibblesPerFrame{16}; |
||||
|
|
||||
|
if (req.buffer == 0 || req.buffer_size == 0) { |
||||
|
return 0; |
||||
|
} |
||||
|
|
||||
|
if (req.end_offset < req.start_offset) { |
||||
|
return 0; |
||||
|
} |
||||
|
|
||||
|
auto end{(req.end_offset % SamplesPerFrame) + |
||||
|
NibblesPerFrame * (req.end_offset / SamplesPerFrame)}; |
||||
|
if (req.end_offset % SamplesPerFrame) { |
||||
|
end += 3; |
||||
|
} else { |
||||
|
end += 1; |
||||
|
} |
||||
|
|
||||
|
if (req.buffer_size < end / 2) { |
||||
|
return 0; |
||||
|
} |
||||
|
|
||||
|
auto samples_to_process{ |
||||
|
std::min(req.end_offset - req.start_offset - req.offset, req.samples_to_read)}; |
||||
|
|
||||
|
auto samples_to_read{samples_to_process}; |
||||
|
auto start_pos{req.start_offset + req.offset}; |
||||
|
auto samples_remaining_in_frame{start_pos % SamplesPerFrame}; |
||||
|
auto position_in_frame{(start_pos / SamplesPerFrame) * NibblesPerFrame + |
||||
|
samples_remaining_in_frame}; |
||||
|
|
||||
|
if (samples_remaining_in_frame) { |
||||
|
position_in_frame += 2; |
||||
|
} |
||||
|
|
||||
|
const auto size{std::max((samples_to_process / 8U) * SamplesPerFrame, 8U)}; |
||||
|
std::vector<u8> wavebuffer(size); |
||||
|
memory.ReadBlockUnsafe(req.buffer + position_in_frame / 2, wavebuffer.data(), |
||||
|
wavebuffer.size()); |
||||
|
|
||||
|
auto context{req.adpcm_context}; |
||||
|
auto header{context->header}; |
||||
|
u8 coeff_index{static_cast<u8>((header >> 4U) & 0xFU)}; |
||||
|
u8 scale{static_cast<u8>(header & 0xFU)}; |
||||
|
s32 coeff0{req.coefficients[coeff_index * 2 + 0]}; |
||||
|
s32 coeff1{req.coefficients[coeff_index * 2 + 1]}; |
||||
|
|
||||
|
auto yn0{context->yn0}; |
||||
|
auto yn1{context->yn1}; |
||||
|
|
||||
|
static constexpr std::array<s32, 16> Steps{ |
||||
|
0, 1, 2, 3, 4, 5, 6, 7, -8, -7, -6, -5, -4, -3, -2, -1, |
||||
|
}; |
||||
|
|
||||
|
const auto decode_sample = [&](const s32 code) -> s16 { |
||||
|
const auto xn = code * (1 << scale); |
||||
|
const auto prediction = coeff0 * yn0 + coeff1 * yn1; |
||||
|
const auto sample = ((xn << 11) + 0x400 + prediction) >> 11; |
||||
|
const auto saturated = std::clamp<s32>(sample, -0x8000, 0x7FFF); |
||||
|
yn1 = yn0; |
||||
|
yn0 = static_cast<s16>(saturated); |
||||
|
return yn0; |
||||
|
}; |
||||
|
|
||||
|
u32 read_index{0}; |
||||
|
u32 write_index{0}; |
||||
|
|
||||
|
while (samples_to_read > 0) { |
||||
|
// Are we at a new frame?
|
||||
|
if ((position_in_frame % NibblesPerFrame) == 0) { |
||||
|
header = wavebuffer[read_index++]; |
||||
|
coeff_index = (header >> 4) & 0xF; |
||||
|
scale = header & 0xF; |
||||
|
coeff0 = req.coefficients[coeff_index * 2 + 0]; |
||||
|
coeff1 = req.coefficients[coeff_index * 2 + 1]; |
||||
|
position_in_frame += 2; |
||||
|
|
||||
|
// Can we consume all of this frame's samples?
|
||||
|
if (samples_to_read >= SamplesPerFrame) { |
||||
|
// Can grab all samples until the next header
|
||||
|
for (u32 i = 0; i < SamplesPerFrame / 2; i++) { |
||||
|
auto code0{Steps[(wavebuffer[read_index] >> 4) & 0xF]}; |
||||
|
auto code1{Steps[wavebuffer[read_index] & 0xF]}; |
||||
|
read_index++; |
||||
|
|
||||
|
out_buffer[write_index++] = decode_sample(code0); |
||||
|
out_buffer[write_index++] = decode_sample(code1); |
||||
|
} |
||||
|
|
||||
|
position_in_frame += SamplesPerFrame; |
||||
|
samples_to_read -= SamplesPerFrame; |
||||
|
continue; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Decode a single sample
|
||||
|
auto code{wavebuffer[read_index]}; |
||||
|
if (position_in_frame & 1) { |
||||
|
code &= 0xF; |
||||
|
read_index++; |
||||
|
} else { |
||||
|
code >>= 4; |
||||
|
} |
||||
|
|
||||
|
out_buffer[write_index++] = decode_sample(Steps[code]); |
||||
|
|
||||
|
position_in_frame++; |
||||
|
samples_to_read--; |
||||
|
} |
||||
|
|
||||
|
context->header = header; |
||||
|
context->yn0 = yn0; |
||||
|
context->yn1 = yn1; |
||||
|
|
||||
|
return samples_to_process; |
||||
|
} |
||||
|
|
||||
|
/**
|
||||
|
* Decode implementation. |
||||
|
* Decode wavebuffers according to the given args. |
||||
|
* |
||||
|
* @param memory - Core memory to read data from. |
||||
|
* @param args - The wavebuffer data, and information for how to decode it. |
||||
|
*/ |
||||
|
void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuffersArgs& args) { |
||||
|
auto& voice_state{*args.voice_state}; |
||||
|
auto remaining_sample_count{args.sample_count}; |
||||
|
auto fraction{voice_state.fraction}; |
||||
|
|
||||
|
const auto sample_rate_ratio{ |
||||
|
(Common::FixedPoint<49, 15>(args.source_sample_rate) / args.target_sample_rate) * |
||||
|
args.pitch}; |
||||
|
const auto size_required{fraction + remaining_sample_count * sample_rate_ratio}; |
||||
|
|
||||
|
if (size_required < 0) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
auto pitch{PitchBySrcQuality[static_cast<u32>(args.src_quality)]}; |
||||
|
if (static_cast<u32>(pitch + size_required.to_int_floor()) > TempBufferSize) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
auto max_remaining_sample_count{ |
||||
|
((Common::FixedPoint<17, 15>(TempBufferSize) - fraction) / sample_rate_ratio) |
||||
|
.to_uint_floor()}; |
||||
|
max_remaining_sample_count = std::min(max_remaining_sample_count, remaining_sample_count); |
||||
|
|
||||
|
auto wavebuffers_consumed{voice_state.wave_buffers_consumed}; |
||||
|
auto wavebuffer_index{voice_state.wave_buffer_index}; |
||||
|
auto played_sample_count{voice_state.played_sample_count}; |
||||
|
|
||||
|
bool is_buffer_starved{false}; |
||||
|
u32 offset{voice_state.offset}; |
||||
|
|
||||
|
auto output_buffer{args.output}; |
||||
|
std::vector<s16> temp_buffer(TempBufferSize, 0); |
||||
|
|
||||
|
while (remaining_sample_count > 0) { |
||||
|
const auto samples_to_write{std::min(remaining_sample_count, max_remaining_sample_count)}; |
||||
|
const auto samples_to_read{ |
||||
|
(fraction + samples_to_write * sample_rate_ratio).to_uint_floor()}; |
||||
|
|
||||
|
u32 temp_buffer_pos{0}; |
||||
|
|
||||
|
if (!args.IsVoicePitchAndSrcSkippedSupported) { |
||||
|
for (u32 i = 0; i < pitch; i++) { |
||||
|
temp_buffer[i] = voice_state.sample_history[i]; |
||||
|
} |
||||
|
temp_buffer_pos = pitch; |
||||
|
} |
||||
|
|
||||
|
u32 samples_read{0}; |
||||
|
while (samples_read < samples_to_read) { |
||||
|
if (wavebuffer_index >= MaxWaveBuffers) { |
||||
|
LOG_ERROR(Service_Audio, "Invalid wavebuffer index! {}", wavebuffer_index); |
||||
|
wavebuffer_index = 0; |
||||
|
voice_state.wave_buffer_valid.fill(false); |
||||
|
wavebuffers_consumed = MaxWaveBuffers; |
||||
|
} |
||||
|
|
||||
|
if (!voice_state.wave_buffer_valid[wavebuffer_index]) { |
||||
|
is_buffer_starved = true; |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
auto& wavebuffer{args.wave_buffers[wavebuffer_index]}; |
||||
|
|
||||
|
if (offset == 0 && args.sample_format == SampleFormat::Adpcm && |
||||
|
wavebuffer.context != 0) { |
||||
|
memory.ReadBlockUnsafe(wavebuffer.context, &voice_state.adpcm_context, |
||||
|
wavebuffer.context_size); |
||||
|
} |
||||
|
|
||||
|
auto start_offset{wavebuffer.start_offset}; |
||||
|
auto end_offset{wavebuffer.end_offset}; |
||||
|
|
||||
|
if (wavebuffer.loop && voice_state.loop_count > 0 && |
||||
|
wavebuffer.loop_start_offset != 0 && wavebuffer.loop_end_offset != 0 && |
||||
|
wavebuffer.loop_start_offset <= wavebuffer.loop_end_offset) { |
||||
|
start_offset = wavebuffer.loop_start_offset; |
||||
|
end_offset = wavebuffer.loop_end_offset; |
||||
|
} |
||||
|
|
||||
|
DecodeArg decode_arg{.buffer{wavebuffer.buffer}, |
||||
|
.buffer_size{wavebuffer.buffer_size}, |
||||
|
.start_offset{start_offset}, |
||||
|
.end_offset{end_offset}, |
||||
|
.channel_count{args.channel_count}, |
||||
|
.coefficients{}, |
||||
|
.adpcm_context{nullptr}, |
||||
|
.target_channel{args.channel}, |
||||
|
.offset{offset}, |
||||
|
.samples_to_read{samples_to_read - samples_read}}; |
||||
|
|
||||
|
s32 samples_decoded{0}; |
||||
|
|
||||
|
switch (args.sample_format) { |
||||
|
case SampleFormat::PcmInt16: |
||||
|
samples_decoded = DecodePcm<s16>( |
||||
|
memory, {&temp_buffer[temp_buffer_pos], TempBufferSize - temp_buffer_pos}, |
||||
|
decode_arg); |
||||
|
break; |
||||
|
|
||||
|
case SampleFormat::PcmFloat: |
||||
|
samples_decoded = DecodePcm<f32>( |
||||
|
memory, {&temp_buffer[temp_buffer_pos], TempBufferSize - temp_buffer_pos}, |
||||
|
decode_arg); |
||||
|
break; |
||||
|
|
||||
|
case SampleFormat::Adpcm: { |
||||
|
decode_arg.adpcm_context = &voice_state.adpcm_context; |
||||
|
memory.ReadBlockUnsafe(args.data_address, &decode_arg.coefficients, args.data_size); |
||||
|
samples_decoded = DecodeAdpcm( |
||||
|
memory, {&temp_buffer[temp_buffer_pos], TempBufferSize - temp_buffer_pos}, |
||||
|
decode_arg); |
||||
|
} break; |
||||
|
|
||||
|
default: |
||||
|
LOG_ERROR(Service_Audio, "Invalid sample format to decode {}", |
||||
|
static_cast<u32>(args.sample_format)); |
||||
|
samples_decoded = 0; |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
played_sample_count += samples_decoded; |
||||
|
samples_read += samples_decoded; |
||||
|
temp_buffer_pos += samples_decoded; |
||||
|
offset += samples_decoded; |
||||
|
|
||||
|
if (samples_decoded == 0 || offset >= end_offset - start_offset) { |
||||
|
offset = 0; |
||||
|
if (!wavebuffer.loop) { |
||||
|
voice_state.wave_buffer_valid[wavebuffer_index] = false; |
||||
|
voice_state.loop_count = 0; |
||||
|
|
||||
|
if (wavebuffer.stream_ended) { |
||||
|
played_sample_count = 0; |
||||
|
} |
||||
|
|
||||
|
wavebuffer_index = (wavebuffer_index + 1) % MaxWaveBuffers; |
||||
|
wavebuffers_consumed++; |
||||
|
} else { |
||||
|
voice_state.loop_count++; |
||||
|
if (wavebuffer.loop_count > 0 && |
||||
|
(voice_state.loop_count > wavebuffer.loop_count || samples_decoded == 0)) { |
||||
|
voice_state.wave_buffer_valid[wavebuffer_index] = false; |
||||
|
voice_state.loop_count = 0; |
||||
|
|
||||
|
if (wavebuffer.stream_ended) { |
||||
|
played_sample_count = 0; |
||||
|
} |
||||
|
|
||||
|
wavebuffer_index = (wavebuffer_index + 1) % MaxWaveBuffers; |
||||
|
wavebuffers_consumed++; |
||||
|
} |
||||
|
|
||||
|
if (samples_decoded == 0) { |
||||
|
is_buffer_starved = true; |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
if (args.IsVoicePlayedSampleCountResetAtLoopPointSupported) { |
||||
|
played_sample_count = 0; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (args.IsVoicePitchAndSrcSkippedSupported) { |
||||
|
if (samples_read > output_buffer.size()) { |
||||
|
LOG_ERROR(Service_Audio, "Attempting to write past the end of output buffer!"); |
||||
|
} |
||||
|
for (u32 i = 0; i < samples_read; i++) { |
||||
|
output_buffer[i] = temp_buffer[i]; |
||||
|
} |
||||
|
} else { |
||||
|
std::memset(&temp_buffer[temp_buffer_pos], 0, |
||||
|
(samples_to_read - samples_read) * sizeof(s16)); |
||||
|
|
||||
|
Resample(output_buffer, temp_buffer, sample_rate_ratio, fraction, samples_to_write, |
||||
|
args.src_quality); |
||||
|
|
||||
|
std::memcpy(voice_state.sample_history.data(), &temp_buffer[samples_to_read], |
||||
|
pitch * sizeof(s16)); |
||||
|
} |
||||
|
|
||||
|
remaining_sample_count -= samples_to_write; |
||||
|
if (remaining_sample_count != 0 && is_buffer_starved) { |
||||
|
LOG_ERROR(Service_Audio, "Samples remaining but buffer is starving??"); |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
output_buffer = output_buffer.subspan(samples_to_write); |
||||
|
} |
||||
|
|
||||
|
voice_state.wave_buffers_consumed = wavebuffers_consumed; |
||||
|
voice_state.played_sample_count = played_sample_count; |
||||
|
voice_state.wave_buffer_index = wavebuffer_index; |
||||
|
voice_state.offset = offset; |
||||
|
voice_state.fraction = fraction; |
||||
|
} |
||||
|
|
||||
|
} // namespace AudioCore::AudioRenderer
|
||||
@ -0,0 +1,59 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project |
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <array> |
||||
|
#include <span> |
||||
|
|
||||
|
#include "audio_core/common/common.h" |
||||
|
#include "audio_core/common/wave_buffer.h" |
||||
|
#include "audio_core/renderer/voice/voice_state.h" |
||||
|
#include "common/common_types.h" |
||||
|
|
||||
|
namespace Core::Memory { |
||||
|
class Memory; |
||||
|
} |
||||
|
|
||||
|
namespace AudioCore::AudioRenderer { |
||||
|
|
||||
|
struct DecodeFromWaveBuffersArgs { |
||||
|
SampleFormat sample_format; |
||||
|
std::span<s32> output; |
||||
|
VoiceState* voice_state; |
||||
|
std::span<WaveBufferVersion2> wave_buffers; |
||||
|
s8 channel; |
||||
|
s8 channel_count; |
||||
|
SrcQuality src_quality; |
||||
|
f32 pitch; |
||||
|
u32 source_sample_rate; |
||||
|
u32 target_sample_rate; |
||||
|
u32 sample_count; |
||||
|
CpuAddr data_address; |
||||
|
u64 data_size; |
||||
|
bool IsVoicePlayedSampleCountResetAtLoopPointSupported; |
||||
|
bool IsVoicePitchAndSrcSkippedSupported; |
||||
|
}; |
||||
|
|
||||
|
struct DecodeArg { |
||||
|
CpuAddr buffer; |
||||
|
u64 buffer_size; |
||||
|
u32 start_offset; |
||||
|
u32 end_offset; |
||||
|
s8 channel_count; |
||||
|
std::array<s16, 16> coefficients; |
||||
|
VoiceState::AdpcmContext* adpcm_context; |
||||
|
s8 target_channel; |
||||
|
u32 offset; |
||||
|
u32 samples_to_read; |
||||
|
}; |
||||
|
|
||||
|
/** |
||||
|
* Decode wavebuffers according to the given args. |
||||
|
* |
||||
|
* @param memory - Core memory to read data from. |
||||
|
* @param args - The wavebuffer data, and information for how to decode it. |
||||
|
*/ |
||||
|
void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuffersArgs& args); |
||||
|
|
||||
|
} // namespace AudioCore::AudioRenderer |
||||
@ -0,0 +1,86 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
|
||||
|
#include "audio_core/renderer/adsp/command_list_processor.h"
|
||||
|
#include "audio_core/renderer/command/data_source/decode.h"
|
||||
|
#include "audio_core/renderer/command/data_source/pcm_float.h"
|
||||
|
|
||||
|
namespace AudioCore::AudioRenderer { |
||||
|
|
||||
|
void PcmFloatDataSourceVersion1Command::Dump(const ADSP::CommandListProcessor& processor, |
||||
|
std::string& string) { |
||||
|
string += |
||||
|
fmt::format("PcmFloatDataSourceVersion1Command\n\toutput_index {:02X} channel {} " |
||||
|
"channel count {} source sample rate {} target sample rate {} src quality {}\n", |
||||
|
output_index, channel_index, channel_count, sample_rate, |
||||
|
processor.target_sample_rate, src_quality); |
||||
|
} |
||||
|
|
||||
|
void PcmFloatDataSourceVersion1Command::Process(const ADSP::CommandListProcessor& processor) { |
||||
|
auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count, |
||||
|
processor.sample_count); |
||||
|
|
||||
|
DecodeFromWaveBuffersArgs args{ |
||||
|
.sample_format{SampleFormat::PcmFloat}, |
||||
|
.output{out_buffer}, |
||||
|
.voice_state{reinterpret_cast<VoiceState*>(voice_state)}, |
||||
|
.wave_buffers{wave_buffers}, |
||||
|
.channel{channel_index}, |
||||
|
.channel_count{channel_count}, |
||||
|
.src_quality{src_quality}, |
||||
|
.pitch{pitch}, |
||||
|
.source_sample_rate{sample_rate}, |
||||
|
.target_sample_rate{processor.target_sample_rate}, |
||||
|
.sample_count{processor.sample_count}, |
||||
|
.data_address{0}, |
||||
|
.data_size{0}, |
||||
|
.IsVoicePlayedSampleCountResetAtLoopPointSupported{(flags & 1) != 0}, |
||||
|
.IsVoicePitchAndSrcSkippedSupported{(flags & 2) != 0}, |
||||
|
}; |
||||
|
|
||||
|
DecodeFromWaveBuffers(*processor.memory, args); |
||||
|
} |
||||
|
|
||||
|
bool PcmFloatDataSourceVersion1Command::Verify(const ADSP::CommandListProcessor& processor) { |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
void PcmFloatDataSourceVersion2Command::Dump(const ADSP::CommandListProcessor& processor, |
||||
|
std::string& string) { |
||||
|
string += |
||||
|
fmt::format("PcmFloatDataSourceVersion2Command\n\toutput_index {:02X} channel {} " |
||||
|
"channel count {} source sample rate {} target sample rate {} src quality {}\n", |
||||
|
output_index, channel_index, channel_count, sample_rate, |
||||
|
processor.target_sample_rate, src_quality); |
||||
|
} |
||||
|
|
||||
|
void PcmFloatDataSourceVersion2Command::Process(const ADSP::CommandListProcessor& processor) { |
||||
|
auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count, |
||||
|
processor.sample_count); |
||||
|
|
||||
|
DecodeFromWaveBuffersArgs args{ |
||||
|
.sample_format{SampleFormat::PcmFloat}, |
||||
|
.output{out_buffer}, |
||||
|
.voice_state{reinterpret_cast<VoiceState*>(voice_state)}, |
||||
|
.wave_buffers{wave_buffers}, |
||||
|
.channel{channel_index}, |
||||
|
.channel_count{channel_count}, |
||||
|
.src_quality{src_quality}, |
||||
|
.pitch{pitch}, |
||||
|
.source_sample_rate{sample_rate}, |
||||
|
.target_sample_rate{processor.target_sample_rate}, |
||||
|
.sample_count{processor.sample_count}, |
||||
|
.data_address{0}, |
||||
|
.data_size{0}, |
||||
|
.IsVoicePlayedSampleCountResetAtLoopPointSupported{(flags & 1) != 0}, |
||||
|
.IsVoicePitchAndSrcSkippedSupported{(flags & 2) != 0}, |
||||
|
}; |
||||
|
|
||||
|
DecodeFromWaveBuffers(*processor.memory, args); |
||||
|
} |
||||
|
|
||||
|
bool PcmFloatDataSourceVersion2Command::Verify(const ADSP::CommandListProcessor& processor) { |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
} // namespace AudioCore::AudioRenderer
|
||||
@ -0,0 +1,113 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project |
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <string> |
||||
|
|
||||
|
#include "audio_core/common/wave_buffer.h" |
||||
|
#include "audio_core/renderer/command/icommand.h" |
||||
|
#include "common/common_types.h" |
||||
|
|
||||
|
namespace AudioCore::AudioRenderer { |
||||
|
namespace ADSP { |
||||
|
class CommandListProcessor; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* AudioRenderer command to decode PCM float-encoded version 1 wavebuffers |
||||
|
* into the output_index mix buffer. |
||||
|
*/ |
||||
|
struct PcmFloatDataSourceVersion1Command : ICommand { |
||||
|
/** |
||||
|
* Print this command's information to a string. |
||||
|
* |
||||
|
* @param processor - The CommandListProcessor processing this command. |
||||
|
* @param string - The string to print into. |
||||
|
*/ |
||||
|
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; |
||||
|
|
||||
|
/** |
||||
|
* Process this command. |
||||
|
* |
||||
|
* @param processor - The CommandListProcessor processing this command. |
||||
|
*/ |
||||
|
void Process(const ADSP::CommandListProcessor& processor) override; |
||||
|
|
||||
|
/** |
||||
|
* Verify this command's data is valid. |
||||
|
* |
||||
|
* @param processor - The CommandListProcessor processing this command. |
||||
|
* @return True if the command is valid, otherwise false. |
||||
|
*/ |
||||
|
bool Verify(const ADSP::CommandListProcessor& processor) override; |
||||
|
|
||||
|
/// Quality used for sample rate conversion |
||||
|
SrcQuality src_quality; |
||||
|
/// Mix buffer index for decoded samples |
||||
|
s16 output_index; |
||||
|
/// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags) |
||||
|
u16 flags; |
||||
|
/// Wavebuffer sample rate |
||||
|
u32 sample_rate; |
||||
|
/// Pitch used for sample rate conversion |
||||
|
f32 pitch; |
||||
|
/// Target channel to read within the wavebuffer |
||||
|
s8 channel_index; |
||||
|
/// Number of channels within the wavebuffer |
||||
|
s8 channel_count; |
||||
|
/// Wavebuffers containing the wavebuffer address, context address, looping information etc |
||||
|
std::array<WaveBufferVersion2, MaxWaveBuffers> wave_buffers; |
||||
|
/// Voice state, updated each call and written back to game |
||||
|
CpuAddr voice_state; |
||||
|
}; |
||||
|
|
||||
|
/** |
||||
|
* AudioRenderer command to decode PCM float-encoded version 2 wavebuffers |
||||
|
* into the output_index mix buffer. |
||||
|
*/ |
||||
|
struct PcmFloatDataSourceVersion2Command : ICommand { |
||||
|
/** |
||||
|
* Print this command's information to a string. |
||||
|
* |
||||
|
* @param processor - The CommandListProcessor processing this command. |
||||
|
* @param string - The string to print into. |
||||
|
*/ |
||||
|
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; |
||||
|
|
||||
|
/** |
||||
|
* Process this command. |
||||
|
* |
||||
|
* @param processor - The CommandListProcessor processing this command. |
||||
|
*/ |
||||
|
void Process(const ADSP::CommandListProcessor& processor) override; |
||||
|
|
||||
|
/** |
||||
|
* Verify this command's data is valid. |
||||
|
* |
||||
|
* @param processor - The CommandListProcessor processing this command. |
||||
|
* @return True if the command is valid, otherwise false. |
||||
|
*/ |
||||
|
bool Verify(const ADSP::CommandListProcessor& processor) override; |
||||
|
|
||||
|
/// Quality used for sample rate conversion |
||||
|
SrcQuality src_quality; |
||||
|
/// Mix buffer index for decoded samples |
||||
|
s16 output_index; |
||||
|
/// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags) |
||||
|
u16 flags; |
||||
|
/// Wavebuffer sample rate |
||||
|
u32 sample_rate; |
||||
|
/// Pitch used for sample rate conversion |
||||
|
f32 pitch; |
||||
|
/// Target channel to read within the wavebuffer |
||||
|
s8 channel_index; |
||||
|
/// Number of channels within the wavebuffer |
||||
|
s8 channel_count; |
||||
|
/// Wavebuffers containing the wavebuffer address, context address, looping information etc |
||||
|
std::array<WaveBufferVersion2, MaxWaveBuffers> wave_buffers; |
||||
|
/// Voice state, updated each call and written back to game |
||||
|
CpuAddr voice_state; |
||||
|
}; |
||||
|
|
||||
|
} // namespace AudioCore::AudioRenderer |
||||
@ -0,0 +1,87 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
|
||||
|
#include <span>
|
||||
|
|
||||
|
#include "audio_core/renderer/adsp/command_list_processor.h"
|
||||
|
#include "audio_core/renderer/command/data_source/decode.h"
|
||||
|
#include "audio_core/renderer/command/data_source/pcm_int16.h"
|
||||
|
|
||||
|
namespace AudioCore::AudioRenderer { |
||||
|
|
||||
|
void PcmInt16DataSourceVersion1Command::Dump(const ADSP::CommandListProcessor& processor, |
||||
|
std::string& string) { |
||||
|
string += |
||||
|
fmt::format("PcmInt16DataSourceVersion1Command\n\toutput_index {:02X} channel {} " |
||||
|
"channel count {} source sample rate {} target sample rate {} src quality {}\n", |
||||
|
output_index, channel_index, channel_count, sample_rate, |
||||
|
processor.target_sample_rate, src_quality); |
||||
|
} |
||||
|
|
||||
|
void PcmInt16DataSourceVersion1Command::Process(const ADSP::CommandListProcessor& processor) { |
||||
|
auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count, |
||||
|
processor.sample_count); |
||||
|
|
||||
|
DecodeFromWaveBuffersArgs args{ |
||||
|
.sample_format{SampleFormat::PcmInt16}, |
||||
|
.output{out_buffer}, |
||||
|
.voice_state{reinterpret_cast<VoiceState*>(voice_state)}, |
||||
|
.wave_buffers{wave_buffers}, |
||||
|
.channel{channel_index}, |
||||
|
.channel_count{channel_count}, |
||||
|
.src_quality{src_quality}, |
||||
|
.pitch{pitch}, |
||||
|
.source_sample_rate{sample_rate}, |
||||
|
.target_sample_rate{processor.target_sample_rate}, |
||||
|
.sample_count{processor.sample_count}, |
||||
|
.data_address{0}, |
||||
|
.data_size{0}, |
||||
|
.IsVoicePlayedSampleCountResetAtLoopPointSupported{(flags & 1) != 0}, |
||||
|
.IsVoicePitchAndSrcSkippedSupported{(flags & 2) != 0}, |
||||
|
}; |
||||
|
|
||||
|
DecodeFromWaveBuffers(*processor.memory, args); |
||||
|
} |
||||
|
|
||||
|
bool PcmInt16DataSourceVersion1Command::Verify(const ADSP::CommandListProcessor& processor) { |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
void PcmInt16DataSourceVersion2Command::Dump(const ADSP::CommandListProcessor& processor, |
||||
|
std::string& string) { |
||||
|
string += |
||||
|
fmt::format("PcmInt16DataSourceVersion2Command\n\toutput_index {:02X} channel {} " |
||||
|
"channel count {} source sample rate {} target sample rate {} src quality {}\n", |
||||
|
output_index, channel_index, channel_count, sample_rate, |
||||
|
processor.target_sample_rate, src_quality); |
||||
|
} |
||||
|
|
||||
|
void PcmInt16DataSourceVersion2Command::Process(const ADSP::CommandListProcessor& processor) { |
||||
|
auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count, |
||||
|
processor.sample_count); |
||||
|
DecodeFromWaveBuffersArgs args{ |
||||
|
.sample_format{SampleFormat::PcmInt16}, |
||||
|
.output{out_buffer}, |
||||
|
.voice_state{reinterpret_cast<VoiceState*>(voice_state)}, |
||||
|
.wave_buffers{wave_buffers}, |
||||
|
.channel{channel_index}, |
||||
|
.channel_count{channel_count}, |
||||
|
.src_quality{src_quality}, |
||||
|
.pitch{pitch}, |
||||
|
.source_sample_rate{sample_rate}, |
||||
|
.target_sample_rate{processor.target_sample_rate}, |
||||
|
.sample_count{processor.sample_count}, |
||||
|
.data_address{0}, |
||||
|
.data_size{0}, |
||||
|
.IsVoicePlayedSampleCountResetAtLoopPointSupported{(flags & 1) != 0}, |
||||
|
.IsVoicePitchAndSrcSkippedSupported{(flags & 2) != 0}, |
||||
|
}; |
||||
|
|
||||
|
DecodeFromWaveBuffers(*processor.memory, args); |
||||
|
} |
||||
|
|
||||
|
bool PcmInt16DataSourceVersion2Command::Verify(const ADSP::CommandListProcessor& processor) { |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
} // namespace AudioCore::AudioRenderer
|
||||
@ -0,0 +1,110 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project |
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <string> |
||||
|
|
||||
|
#include "audio_core/common/wave_buffer.h" |
||||
|
#include "audio_core/renderer/command/icommand.h" |
||||
|
#include "common/common_types.h" |
||||
|
|
||||
|
namespace AudioCore::AudioRenderer { |
||||
|
namespace ADSP { |
||||
|
class CommandListProcessor; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* AudioRenderer command to decode PCM s16-encoded version 1 wavebuffers |
||||
|
* into the output_index mix buffer. |
||||
|
*/ |
||||
|
struct PcmInt16DataSourceVersion1Command : ICommand { |
||||
|
/** |
||||
|
* Print this command's information to a string. |
||||
|
* |
||||
|
* @param processor - The CommandListProcessor processing this command. |
||||
|
* @param string - The string to print into. |
||||
|
*/ |
||||
|
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; |
||||
|
|
||||
|
/** |
||||
|
* Process this command. |
||||
|
* |
||||
|
* @param processor - The CommandListProcessor processing this command. |
||||
|
*/ |
||||
|
void Process(const ADSP::CommandListProcessor& processor) override; |
||||
|
|
||||
|
/** |
||||
|
* Verify this command's data is valid. |
||||
|
* |
||||
|
* @param processor - The CommandListProcessor processing this command. |
||||
|
* @return True if the command is valid, otherwise false. |
||||
|
*/ |
||||
|
bool Verify(const ADSP::CommandListProcessor& processor) override; |
||||
|
|
||||
|
/// Quality used for sample rate conversion |
||||
|
SrcQuality src_quality; |
||||
|
/// Mix buffer index for decoded samples |
||||
|
s16 output_index; |
||||
|
/// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags) |
||||
|
u16 flags; |
||||
|
/// Wavebuffer sample rate |
||||
|
u32 sample_rate; |
||||
|
/// Pitch used for sample rate conversion |
||||
|
f32 pitch; |
||||
|
/// Target channel to read within the wavebuffer |
||||
|
s8 channel_index; |
||||
|
/// Number of channels within the wavebuffer |
||||
|
s8 channel_count; |
||||
|
/// Wavebuffers containing the wavebuffer address, context address, looping information etc |
||||
|
std::array<WaveBufferVersion2, MaxWaveBuffers> wave_buffers; |
||||
|
/// Voice state, updated each call and written back to game |
||||
|
CpuAddr voice_state; |
||||
|
}; |
||||
|
|
||||
|
/** |
||||
|
* AudioRenderer command to decode PCM s16-encoded version 2 wavebuffers |
||||
|
* into the output_index mix buffer. |
||||
|
*/ |
||||
|
struct PcmInt16DataSourceVersion2Command : ICommand { |
||||
|
/** |
||||
|
* Print this command's information to a string. |
||||
|
* @param processor - The CommandListProcessor processing this command. |
||||
|
* @param string - The string to print into. |
||||
|
*/ |
||||
|
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; |
||||
|
|
||||
|
/** |
||||
|
* Process this command. |
||||
|
* @param processor - The CommandListProcessor processing this command. |
||||
|
*/ |
||||
|
void Process(const ADSP::CommandListProcessor& processor) override; |
||||
|
|
||||
|
/** |
||||
|
* Verify this command's data is valid. |
||||
|
* @param processor - The CommandListProcessor processing this command. |
||||
|
* @return True if the command is valid, otherwise false. |
||||
|
*/ |
||||
|
bool Verify(const ADSP::CommandListProcessor& processor) override; |
||||
|
|
||||
|
/// Quality used for sample rate conversion |
||||
|
SrcQuality src_quality; |
||||
|
/// Mix buffer index for decoded samples |
||||
|
s16 output_index; |
||||
|
/// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags) |
||||
|
u16 flags; |
||||
|
/// Wavebuffer sample rate |
||||
|
u32 sample_rate; |
||||
|
/// Pitch used for sample rate conversion |
||||
|
f32 pitch; |
||||
|
/// Target channel to read within the wavebuffer |
||||
|
s8 channel_index; |
||||
|
/// Number of channels within the wavebuffer |
||||
|
s8 channel_count; |
||||
|
/// Wavebuffers containing the wavebuffer address, context address, looping information etc |
||||
|
std::array<WaveBufferVersion2, MaxWaveBuffers> wave_buffers; |
||||
|
/// Voice state, updated each call and written back to game |
||||
|
CpuAddr voice_state; |
||||
|
}; |
||||
|
|
||||
|
} // namespace AudioCore::AudioRenderer |
||||
@ -0,0 +1,207 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
|
||||
|
#include "audio_core/renderer/adsp/command_list_processor.h"
|
||||
|
#include "audio_core/renderer/command/effect/aux_.h"
|
||||
|
#include "audio_core/renderer/effect/aux_.h"
|
||||
|
#include "core/memory.h"
|
||||
|
|
||||
|
namespace AudioCore::AudioRenderer { |
||||
|
/**
|
||||
|
* Reset an AuxBuffer. |
||||
|
* |
||||
|
* @param memory - Core memory for writing. |
||||
|
* @param aux_info - Memory address pointing to the AuxInfo to reset. |
||||
|
*/ |
||||
|
static void ResetAuxBufferDsp(Core::Memory::Memory& memory, const CpuAddr aux_info) { |
||||
|
if (aux_info == 0) { |
||||
|
LOG_ERROR(Service_Audio, "Aux info is 0!"); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
auto info{reinterpret_cast<AuxInfo::AuxInfoDsp*>(memory.GetPointer(aux_info))}; |
||||
|
info->read_offset = 0; |
||||
|
info->write_offset = 0; |
||||
|
info->total_sample_count = 0; |
||||
|
} |
||||
|
|
||||
|
/**
|
||||
|
* Write the given input mix buffer to the memory at send_buffer, and update send_info_ if |
||||
|
* update_count is set, to notify the game that an update happened. |
||||
|
* |
||||
|
* @param memory - Core memory for writing. |
||||
|
* @param send_info_ - Meta information for where to write the mix buffer. |
||||
|
* @param sample_count - Unused. |
||||
|
* @param send_buffer - Memory address to write the mix buffer to. |
||||
|
* @param count_max - Maximum number of samples in the receiving buffer. |
||||
|
* @param input - Input mix buffer to write. |
||||
|
* @param write_count_ - Number of samples to write. |
||||
|
* @param write_offset - Current offset to begin writing the receiving buffer at. |
||||
|
* @param update_count - If non-zero, send_info_ will be updated. |
||||
|
* @return Number of samples written. |
||||
|
*/ |
||||
|
static u32 WriteAuxBufferDsp(Core::Memory::Memory& memory, const CpuAddr send_info_, |
||||
|
[[maybe_unused]] u32 sample_count, const CpuAddr send_buffer, |
||||
|
const u32 count_max, std::span<const s32> input, |
||||
|
const u32 write_count_, const u32 write_offset, |
||||
|
const u32 update_count) { |
||||
|
if (write_count_ > count_max) { |
||||
|
LOG_ERROR(Service_Audio, |
||||
|
"write_count must be smaller than count_max! write_count {}, count_max {}", |
||||
|
write_count_, count_max); |
||||
|
return 0; |
||||
|
} |
||||
|
|
||||
|
if (input.empty()) { |
||||
|
LOG_ERROR(Service_Audio, "input buffer is empty!"); |
||||
|
return 0; |
||||
|
} |
||||
|
|
||||
|
if (send_buffer == 0) { |
||||
|
LOG_ERROR(Service_Audio, "send_buffer is 0!"); |
||||
|
return 0; |
||||
|
} |
||||
|
|
||||
|
if (count_max == 0) { |
||||
|
return 0; |
||||
|
} |
||||
|
|
||||
|
AuxInfo::AuxInfoDsp send_info{}; |
||||
|
memory.ReadBlockUnsafe(send_info_, &send_info, sizeof(AuxInfo::AuxInfoDsp)); |
||||
|
|
||||
|
u32 target_write_offset{send_info.write_offset + write_offset}; |
||||
|
if (target_write_offset > count_max || write_count_ == 0) { |
||||
|
return 0; |
||||
|
} |
||||
|
|
||||
|
u32 write_count{write_count_}; |
||||
|
u32 write_pos{0}; |
||||
|
while (write_count > 0) { |
||||
|
u32 to_write{std::min(count_max - target_write_offset, write_count)}; |
||||
|
|
||||
|
if (to_write > 0) { |
||||
|
memory.WriteBlockUnsafe(send_buffer + target_write_offset * sizeof(s32), |
||||
|
&input[write_pos], to_write * sizeof(s32)); |
||||
|
} |
||||
|
|
||||
|
target_write_offset = (target_write_offset + to_write) % count_max; |
||||
|
write_count -= to_write; |
||||
|
write_pos += to_write; |
||||
|
} |
||||
|
|
||||
|
if (update_count) { |
||||
|
send_info.write_offset = (send_info.write_offset + update_count) % count_max; |
||||
|
} |
||||
|
|
||||
|
memory.WriteBlockUnsafe(send_info_, &send_info, sizeof(AuxInfo::AuxInfoDsp)); |
||||
|
|
||||
|
return write_count_; |
||||
|
} |
||||
|
|
||||
|
/**
|
||||
|
* Read the given memory at return_buffer into the output mix buffer, and update return_info_ if |
||||
|
* update_count is set, to notify the game that an update happened. |
||||
|
* |
||||
|
* @param memory - Core memory for writing. |
||||
|
* @param return_info_ - Meta information for where to read the mix buffer. |
||||
|
* @param return_buffer - Memory address to read the samples from. |
||||
|
* @param count_max - Maximum number of samples in the receiving buffer. |
||||
|
* @param output - Output mix buffer which will receive the samples. |
||||
|
* @param count_ - Number of samples to read. |
||||
|
* @param read_offset - Current offset to begin reading the return_buffer at. |
||||
|
* @param update_count - If non-zero, send_info_ will be updated. |
||||
|
* @return Number of samples read. |
||||
|
*/ |
||||
|
static u32 ReadAuxBufferDsp(Core::Memory::Memory& memory, const CpuAddr return_info_, |
||||
|
const CpuAddr return_buffer, const u32 count_max, std::span<s32> output, |
||||
|
const u32 count_, const u32 read_offset, const u32 update_count) { |
||||
|
if (count_max == 0) { |
||||
|
return 0; |
||||
|
} |
||||
|
|
||||
|
if (count_ > count_max) { |
||||
|
LOG_ERROR(Service_Audio, "count must be smaller than count_max! count {}, count_max {}", |
||||
|
count_, count_max); |
||||
|
return 0; |
||||
|
} |
||||
|
|
||||
|
if (output.empty()) { |
||||
|
LOG_ERROR(Service_Audio, "output buffer is empty!"); |
||||
|
return 0; |
||||
|
} |
||||
|
|
||||
|
if (return_buffer == 0) { |
||||
|
LOG_ERROR(Service_Audio, "return_buffer is 0!"); |
||||
|
return 0; |
||||
|
} |
||||
|
|
||||
|
AuxInfo::AuxInfoDsp return_info{}; |
||||
|
memory.ReadBlockUnsafe(return_info_, &return_info, sizeof(AuxInfo::AuxInfoDsp)); |
||||
|
|
||||
|
u32 target_read_offset{return_info.read_offset + read_offset}; |
||||
|
if (target_read_offset > count_max) { |
||||
|
return 0; |
||||
|
} |
||||
|
|
||||
|
u32 read_count{count_}; |
||||
|
u32 read_pos{0}; |
||||
|
while (read_count > 0) { |
||||
|
u32 to_read{std::min(count_max - target_read_offset, read_count)}; |
||||
|
|
||||
|
if (to_read > 0) { |
||||
|
memory.ReadBlockUnsafe(return_buffer + target_read_offset * sizeof(s32), |
||||
|
&output[read_pos], to_read * sizeof(s32)); |
||||
|
} |
||||
|
|
||||
|
target_read_offset = (target_read_offset + to_read) % count_max; |
||||
|
read_count -= to_read; |
||||
|
read_pos += to_read; |
||||
|
} |
||||
|
|
||||
|
if (update_count) { |
||||
|
return_info.read_offset = (return_info.read_offset + update_count) % count_max; |
||||
|
} |
||||
|
|
||||
|
memory.WriteBlockUnsafe(return_info_, &return_info, sizeof(AuxInfo::AuxInfoDsp)); |
||||
|
|
||||
|
return count_; |
||||
|
} |
||||
|
|
||||
|
void AuxCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, |
||||
|
std::string& string) { |
||||
|
string += fmt::format("AuxCommand\n\tenabled {} input {:02X} output {:02X}\n", effect_enabled, |
||||
|
input, output); |
||||
|
} |
||||
|
|
||||
|
void AuxCommand::Process(const ADSP::CommandListProcessor& processor) { |
||||
|
auto input_buffer{ |
||||
|
processor.mix_buffers.subspan(input * processor.sample_count, processor.sample_count)}; |
||||
|
auto output_buffer{ |
||||
|
processor.mix_buffers.subspan(output * processor.sample_count, processor.sample_count)}; |
||||
|
|
||||
|
if (effect_enabled) { |
||||
|
WriteAuxBufferDsp(*processor.memory, send_buffer_info, processor.sample_count, send_buffer, |
||||
|
count_max, input_buffer, processor.sample_count, write_offset, |
||||
|
update_count); |
||||
|
|
||||
|
auto read{ReadAuxBufferDsp(*processor.memory, return_buffer_info, return_buffer, count_max, |
||||
|
output_buffer, processor.sample_count, write_offset, |
||||
|
update_count)}; |
||||
|
|
||||
|
if (read != processor.sample_count) { |
||||
|
std::memset(&output_buffer[read], 0, processor.sample_count - read); |
||||
|
} |
||||
|
} else { |
||||
|
ResetAuxBufferDsp(*processor.memory, send_buffer_info); |
||||
|
ResetAuxBufferDsp(*processor.memory, return_buffer_info); |
||||
|
if (input != output) { |
||||
|
std::memcpy(output_buffer.data(), input_buffer.data(), output_buffer.size_bytes()); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
bool AuxCommand::Verify(const ADSP::CommandListProcessor& processor) { |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
} // namespace AudioCore::AudioRenderer
|
||||
@ -0,0 +1,66 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project |
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <string> |
||||
|
|
||||
|
#include "audio_core/renderer/command/icommand.h" |
||||
|
#include "common/common_types.h" |
||||
|
|
||||
|
namespace AudioCore::AudioRenderer { |
||||
|
namespace ADSP { |
||||
|
class CommandListProcessor; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* AudioRenderer command to read and write an auxiliary buffer, writing the input mix buffer to game |
||||
|
* memory, and reading into the output buffer from game memory. |
||||
|
*/ |
||||
|
struct AuxCommand : ICommand { |
||||
|
/** |
||||
|
* Print this command's information to a string. |
||||
|
* |
||||
|
* @param processor - The CommandListProcessor processing this command. |
||||
|
* @param string - The string to print into. |
||||
|
*/ |
||||
|
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; |
||||
|
|
||||
|
/** |
||||
|
* Process this command. |
||||
|
* |
||||
|
* @param processor - The CommandListProcessor processing this command. |
||||
|
*/ |
||||
|
void Process(const ADSP::CommandListProcessor& processor) override; |
||||
|
|
||||
|
/** |
||||
|
* Verify this command's data is valid. |
||||
|
* |
||||
|
* @param processor - The CommandListProcessor processing this command. |
||||
|
* @return True if the command is valid, otherwise false. |
||||
|
*/ |
||||
|
bool Verify(const ADSP::CommandListProcessor& processor) override; |
||||
|
|
||||
|
/// Input mix buffer index |
||||
|
s16 input; |
||||
|
/// Output mix buffer index |
||||
|
s16 output; |
||||
|
/// Meta info for writing |
||||
|
CpuAddr send_buffer_info; |
||||
|
/// Meta info for reading |
||||
|
CpuAddr return_buffer_info; |
||||
|
/// Game memory write buffer |
||||
|
CpuAddr send_buffer; |
||||
|
/// Game memory read buffer |
||||
|
CpuAddr return_buffer; |
||||
|
/// Max samples to read/write |
||||
|
u32 count_max; |
||||
|
/// Current read/write offset |
||||
|
u32 write_offset; |
||||
|
/// Number of samples to update per call |
||||
|
u32 update_count; |
||||
|
/// is this effect enabled? |
||||
|
bool effect_enabled; |
||||
|
}; |
||||
|
|
||||
|
} // namespace AudioCore::AudioRenderer |
||||
@ -0,0 +1,118 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
|
||||
|
#include "audio_core/renderer/adsp/command_list_processor.h"
|
||||
|
#include "audio_core/renderer/command/effect/biquad_filter.h"
|
||||
|
#include "audio_core/renderer/voice/voice_state.h"
|
||||
|
|
||||
|
namespace AudioCore::AudioRenderer { |
||||
|
/**
|
||||
|
* Biquad filter float implementation. |
||||
|
* |
||||
|
* @param output - Output container for filtered samples. |
||||
|
* @param input - Input container for samples to be filtered. |
||||
|
* @param b - Feedforward coefficients. |
||||
|
* @param a - Feedback coefficients. |
||||
|
* @param state - State to track previous samples between calls. |
||||
|
* @param sample_count - Number of samples to process. |
||||
|
*/ |
||||
|
void ApplyBiquadFilterFloat(std::span<s32> output, std::span<const s32> input, |
||||
|
std::array<s16, 3>& b_, std::array<s16, 2>& a_, |
||||
|
VoiceState::BiquadFilterState& state, const u32 sample_count) { |
||||
|
constexpr s64 min{std::numeric_limits<s32>::min()}; |
||||
|
constexpr s64 max{std::numeric_limits<s32>::max()}; |
||||
|
std::array<f64, 3> b{Common::FixedPoint<50, 14>::from_base(b_[0]).to_double(), |
||||
|
Common::FixedPoint<50, 14>::from_base(b_[1]).to_double(), |
||||
|
Common::FixedPoint<50, 14>::from_base(b_[2]).to_double()}; |
||||
|
std::array<f64, 2> a{Common::FixedPoint<50, 14>::from_base(a_[0]).to_double(), |
||||
|
Common::FixedPoint<50, 14>::from_base(a_[1]).to_double()}; |
||||
|
std::array<f64, 4> s{state.s0.to_double(), state.s1.to_double(), state.s2.to_double(), |
||||
|
state.s3.to_double()}; |
||||
|
|
||||
|
for (u32 i = 0; i < sample_count; i++) { |
||||
|
f64 in_sample{static_cast<f64>(input[i])}; |
||||
|
auto sample{in_sample * b[0] + s[0] * b[1] + s[1] * b[2] + s[2] * a[0] + s[3] * a[1]}; |
||||
|
|
||||
|
output[i] = static_cast<s32>(std::clamp(static_cast<s64>(sample), min, max)); |
||||
|
|
||||
|
s[1] = s[0]; |
||||
|
s[0] = in_sample; |
||||
|
s[3] = s[2]; |
||||
|
s[2] = sample; |
||||
|
} |
||||
|
|
||||
|
state.s0 = s[0]; |
||||
|
state.s1 = s[1]; |
||||
|
state.s2 = s[2]; |
||||
|
state.s3 = s[3]; |
||||
|
} |
||||
|
|
||||
|
/**
|
||||
|
* Biquad filter s32 implementation. |
||||
|
* |
||||
|
* @param output - Output container for filtered samples. |
||||
|
* @param input - Input container for samples to be filtered. |
||||
|
* @param b - Feedforward coefficients. |
||||
|
* @param a - Feedback coefficients. |
||||
|
* @param state - State to track previous samples between calls. |
||||
|
* @param sample_count - Number of samples to process. |
||||
|
*/ |
||||
|
static void ApplyBiquadFilterInt(std::span<s32> output, std::span<const s32> input, |
||||
|
std::array<s16, 3>& b_, std::array<s16, 2>& a_, |
||||
|
VoiceState::BiquadFilterState& state, const u32 sample_count) { |
||||
|
constexpr s64 min{std::numeric_limits<s32>::min()}; |
||||
|
constexpr s64 max{std::numeric_limits<s32>::max()}; |
||||
|
std::array<Common::FixedPoint<50, 14>, 3> b{ |
||||
|
Common::FixedPoint<50, 14>::from_base(b_[0]), |
||||
|
Common::FixedPoint<50, 14>::from_base(b_[1]), |
||||
|
Common::FixedPoint<50, 14>::from_base(b_[2]), |
||||
|
}; |
||||
|
std::array<Common::FixedPoint<50, 14>, 3> a{ |
||||
|
Common::FixedPoint<50, 14>::from_base(a_[0]), |
||||
|
Common::FixedPoint<50, 14>::from_base(a_[1]), |
||||
|
}; |
||||
|
|
||||
|
for (u32 i = 0; i < sample_count; i++) { |
||||
|
s64 in_sample{input[i]}; |
||||
|
auto sample{in_sample * b[0] + state.s0}; |
||||
|
const auto out_sample{std::clamp(sample.to_long(), min, max)}; |
||||
|
|
||||
|
output[i] = static_cast<s32>(out_sample); |
||||
|
|
||||
|
state.s0 = state.s1 + b[1] * in_sample + a[0] * out_sample; |
||||
|
state.s1 = 0 + b[2] * in_sample + a[1] * out_sample; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void BiquadFilterCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, |
||||
|
std::string& string) { |
||||
|
string += fmt::format( |
||||
|
"BiquadFilterCommand\n\tinput {:02X} output {:02X} needs_init {} use_float_processing {}\n", |
||||
|
input, output, needs_init, use_float_processing); |
||||
|
} |
||||
|
|
||||
|
void BiquadFilterCommand::Process(const ADSP::CommandListProcessor& processor) { |
||||
|
auto state_{reinterpret_cast<VoiceState::BiquadFilterState*>(state)}; |
||||
|
if (needs_init) { |
||||
|
std::memset(state_, 0, sizeof(VoiceState::BiquadFilterState)); |
||||
|
} |
||||
|
|
||||
|
auto input_buffer{ |
||||
|
processor.mix_buffers.subspan(input * processor.sample_count, processor.sample_count)}; |
||||
|
auto output_buffer{ |
||||
|
processor.mix_buffers.subspan(output * processor.sample_count, processor.sample_count)}; |
||||
|
|
||||
|
if (use_float_processing) { |
||||
|
ApplyBiquadFilterFloat(output_buffer, input_buffer, biquad.b, biquad.a, *state_, |
||||
|
processor.sample_count); |
||||
|
} else { |
||||
|
ApplyBiquadFilterInt(output_buffer, input_buffer, biquad.b, biquad.a, *state_, |
||||
|
processor.sample_count); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
bool BiquadFilterCommand::Verify(const ADSP::CommandListProcessor& processor) { |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
} // namespace AudioCore::AudioRenderer
|
||||
@ -0,0 +1,74 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project |
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <string> |
||||
|
|
||||
|
#include "audio_core/renderer/command/icommand.h" |
||||
|
#include "audio_core/renderer/voice/voice_info.h" |
||||
|
#include "audio_core/renderer/voice/voice_state.h" |
||||
|
#include "common/common_types.h" |
||||
|
|
||||
|
namespace AudioCore::AudioRenderer { |
||||
|
namespace ADSP { |
||||
|
class CommandListProcessor; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* AudioRenderer command for applying a biquad filter to the input mix buffer, saving the results to |
||||
|
* the output mix buffer. |
||||
|
*/ |
||||
|
struct BiquadFilterCommand : ICommand { |
||||
|
/** |
||||
|
* Print this command's information to a string. |
||||
|
* |
||||
|
* @param processor - The CommandListProcessor processing this command. |
||||
|
* @param string - The string to print into. |
||||
|
*/ |
||||
|
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; |
||||
|
|
||||
|
/** |
||||
|
* Process this command. |
||||
|
* |
||||
|
* @param processor - The CommandListProcessor processing this command. |
||||
|
*/ |
||||
|
void Process(const ADSP::CommandListProcessor& processor) override; |
||||
|
|
||||
|
/** |
||||
|
* Verify this command's data is valid. |
||||
|
* |
||||
|
* @param processor - The CommandListProcessor processing this command. |
||||
|
* @return True if the command is valid, otherwise false. |
||||
|
*/ |
||||
|
bool Verify(const ADSP::CommandListProcessor& processor) override; |
||||
|
|
||||
|
/// Input mix buffer index |
||||
|
s16 input; |
||||
|
/// Output mix buffer index |
||||
|
s16 output; |
||||
|
/// Input parameters for biquad |
||||
|
VoiceInfo::BiquadFilterParameter biquad; |
||||
|
/// Biquad state, updated each call |
||||
|
CpuAddr state; |
||||
|
/// If true, reset the state |
||||
|
bool needs_init; |
||||
|
/// If true, use float processing rather than int |
||||
|
bool use_float_processing; |
||||
|
}; |
||||
|
|
||||
|
/** |
||||
|
* Biquad filter float implementation. |
||||
|
* |
||||
|
* @param output - Output container for filtered samples. |
||||
|
* @param input - Input container for samples to be filtered. |
||||
|
* @param b - Feedforward coefficients. |
||||
|
* @param a - Feedback coefficients. |
||||
|
* @param state - State to track previous samples. |
||||
|
* @param sample_count - Number of samples to process. |
||||
|
*/ |
||||
|
void ApplyBiquadFilterFloat(std::span<s32> output, std::span<const s32> input, |
||||
|
std::array<s16, 3>& b, std::array<s16, 2>& a, |
||||
|
VoiceState::BiquadFilterState& state, const u32 sample_count); |
||||
|
|
||||
|
} // namespace AudioCore::AudioRenderer |
||||
@ -0,0 +1,142 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
|
||||
|
#include "audio_core/renderer/adsp/command_list_processor.h"
|
||||
|
#include "audio_core/renderer/command/effect/capture.h"
|
||||
|
#include "audio_core/renderer/effect/aux_.h"
|
||||
|
#include "core/memory.h"
|
||||
|
|
||||
|
namespace AudioCore::AudioRenderer { |
||||
|
/**
|
||||
|
* Reset an AuxBuffer. |
||||
|
* |
||||
|
* @param memory - Core memory for writing. |
||||
|
* @param aux_info - Memory address pointing to the AuxInfo to reset. |
||||
|
*/ |
||||
|
static void ResetAuxBufferDsp(Core::Memory::Memory& memory, const CpuAddr aux_info) { |
||||
|
if (aux_info == 0) { |
||||
|
LOG_ERROR(Service_Audio, "Aux info is 0!"); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
memory.Write32(VAddr(aux_info + offsetof(AuxInfo::AuxInfoDsp, read_offset)), 0); |
||||
|
memory.Write32(VAddr(aux_info + offsetof(AuxInfo::AuxInfoDsp, write_offset)), 0); |
||||
|
memory.Write32(VAddr(aux_info + offsetof(AuxInfo::AuxInfoDsp, total_sample_count)), 0); |
||||
|
} |
||||
|
|
||||
|
/**
|
||||
|
* Write the given input mix buffer to the memory at send_buffer, and update send_info_ if |
||||
|
* update_count is set, to notify the game that an update happened. |
||||
|
* |
||||
|
* @param memory - Core memory for writing. |
||||
|
* @param send_info_ - Header information for where to write the mix buffer. |
||||
|
* @param send_buffer - Memory address to write the mix buffer to. |
||||
|
* @param count_max - Maximum number of samples in the receiving buffer. |
||||
|
* @param input - Input mix buffer to write. |
||||
|
* @param write_count_ - Number of samples to write. |
||||
|
* @param write_offset - Current offset to begin writing the receiving buffer at. |
||||
|
* @param update_count - If non-zero, send_info_ will be updated. |
||||
|
* @return Number of samples written. |
||||
|
*/ |
||||
|
static u32 WriteAuxBufferDsp(Core::Memory::Memory& memory, const CpuAddr send_info_, |
||||
|
const CpuAddr send_buffer, u32 count_max, std::span<const s32> input, |
||||
|
const u32 write_count_, const u32 write_offset, |
||||
|
const u32 update_count) { |
||||
|
if (write_count_ > count_max) { |
||||
|
LOG_ERROR(Service_Audio, |
||||
|
"write_count must be smaller than count_max! write_count {}, count_max {}", |
||||
|
write_count_, count_max); |
||||
|
return 0; |
||||
|
} |
||||
|
|
||||
|
if (send_info_ == 0) { |
||||
|
LOG_ERROR(Service_Audio, "send_info is 0!"); |
||||
|
return 0; |
||||
|
} |
||||
|
|
||||
|
if (input.empty()) { |
||||
|
LOG_ERROR(Service_Audio, "input buffer is empty!"); |
||||
|
return 0; |
||||
|
} |
||||
|
|
||||
|
if (send_buffer == 0) { |
||||
|
LOG_ERROR(Service_Audio, "send_buffer is 0!"); |
||||
|
return 0; |
||||
|
} |
||||
|
|
||||
|
if (count_max == 0) { |
||||
|
return 0; |
||||
|
} |
||||
|
|
||||
|
AuxInfo::AuxBufferInfo send_info{}; |
||||
|
memory.ReadBlockUnsafe(send_info_, &send_info, sizeof(AuxInfo::AuxBufferInfo)); |
||||
|
|
||||
|
u32 target_write_offset{send_info.dsp_info.write_offset + write_offset}; |
||||
|
if (target_write_offset > count_max || write_count_ == 0) { |
||||
|
return 0; |
||||
|
} |
||||
|
|
||||
|
u32 write_count{write_count_}; |
||||
|
u32 write_pos{0}; |
||||
|
while (write_count > 0) { |
||||
|
u32 to_write{std::min(count_max - target_write_offset, write_count)}; |
||||
|
|
||||
|
if (to_write > 0) { |
||||
|
memory.WriteBlockUnsafe(send_buffer + target_write_offset * sizeof(s32), |
||||
|
&input[write_pos], to_write * sizeof(s32)); |
||||
|
} |
||||
|
|
||||
|
target_write_offset = (target_write_offset + to_write) % count_max; |
||||
|
write_count -= to_write; |
||||
|
write_pos += to_write; |
||||
|
} |
||||
|
|
||||
|
if (update_count) { |
||||
|
const auto count_diff{send_info.dsp_info.total_sample_count - |
||||
|
send_info.cpu_info.total_sample_count}; |
||||
|
if (count_diff >= count_max) { |
||||
|
auto dsp_lost_count{send_info.dsp_info.lost_sample_count + update_count}; |
||||
|
if (dsp_lost_count - send_info.cpu_info.lost_sample_count < |
||||
|
send_info.dsp_info.lost_sample_count - send_info.cpu_info.lost_sample_count) { |
||||
|
dsp_lost_count = send_info.cpu_info.lost_sample_count - 1; |
||||
|
} |
||||
|
send_info.dsp_info.lost_sample_count = dsp_lost_count; |
||||
|
} |
||||
|
|
||||
|
send_info.dsp_info.write_offset = |
||||
|
(send_info.dsp_info.write_offset + update_count + count_max) % count_max; |
||||
|
|
||||
|
auto new_sample_count{send_info.dsp_info.total_sample_count + update_count}; |
||||
|
if (new_sample_count - send_info.cpu_info.total_sample_count < count_diff) { |
||||
|
new_sample_count = send_info.cpu_info.total_sample_count - 1; |
||||
|
} |
||||
|
send_info.dsp_info.total_sample_count = new_sample_count; |
||||
|
} |
||||
|
|
||||
|
memory.WriteBlockUnsafe(send_info_, &send_info, sizeof(AuxInfo::AuxBufferInfo)); |
||||
|
|
||||
|
return write_count_; |
||||
|
} |
||||
|
|
||||
|
void CaptureCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, |
||||
|
std::string& string) { |
||||
|
string += fmt::format("CaptureCommand\n\tenabled {} input {:02X} output {:02X}", effect_enabled, |
||||
|
input, output); |
||||
|
} |
||||
|
|
||||
|
void CaptureCommand::Process(const ADSP::CommandListProcessor& processor) { |
||||
|
if (effect_enabled) { |
||||
|
auto input_buffer{ |
||||
|
processor.mix_buffers.subspan(input * processor.sample_count, processor.sample_count)}; |
||||
|
WriteAuxBufferDsp(*processor.memory, send_buffer_info, send_buffer, count_max, input_buffer, |
||||
|
processor.sample_count, write_offset, update_count); |
||||
|
} else { |
||||
|
ResetAuxBufferDsp(*processor.memory, send_buffer_info); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
bool CaptureCommand::Verify(const ADSP::CommandListProcessor& processor) { |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
} // namespace AudioCore::AudioRenderer
|
||||
@ -0,0 +1,62 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project |
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <string> |
||||
|
|
||||
|
#include "audio_core/renderer/command/icommand.h" |
||||
|
#include "common/common_types.h" |
||||
|
|
||||
|
namespace AudioCore::AudioRenderer { |
||||
|
namespace ADSP { |
||||
|
class CommandListProcessor; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* AudioRenderer command for capturing a mix buffer. That is, writing it back to a given game memory |
||||
|
* address. |
||||
|
*/ |
||||
|
struct CaptureCommand : ICommand { |
||||
|
/** |
||||
|
* Print this command's information to a string. |
||||
|
* |
||||
|
* @param processor - The CommandListProcessor processing this command. |
||||
|
* @param string - The string to print into. |
||||
|
*/ |
||||
|
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; |
||||
|
|
||||
|
/** |
||||
|
* Process this command. |
||||
|
* |
||||
|
* @param processor - The CommandListProcessor processing this command. |
||||
|
*/ |
||||
|
void Process(const ADSP::CommandListProcessor& processor) override; |
||||
|
|
||||
|
/** |
||||
|
* Verify this command's data is valid. |
||||
|
* |
||||
|
* @param processor - The CommandListProcessor processing this command. |
||||
|
* @return True if the command is valid, otherwise false. |
||||
|
*/ |
||||
|
bool Verify(const ADSP::CommandListProcessor& processor) override; |
||||
|
|
||||
|
/// Input mix buffer index |
||||
|
s16 input; |
||||
|
/// Output mix buffer index |
||||
|
s16 output; |
||||
|
/// Meta info for writing |
||||
|
CpuAddr send_buffer_info; |
||||
|
/// Game memory write buffer |
||||
|
CpuAddr send_buffer; |
||||
|
/// Max samples to read/write |
||||
|
u32 count_max; |
||||
|
/// Current read/write offset |
||||
|
u32 write_offset; |
||||
|
/// Number of samples to update per call |
||||
|
u32 update_count; |
||||
|
/// is this effect enabled? |
||||
|
bool effect_enabled; |
||||
|
}; |
||||
|
|
||||
|
} // namespace AudioCore::AudioRenderer |
||||
@ -0,0 +1,156 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
|
||||
|
#include <cmath>
|
||||
|
#include <span>
|
||||
|
#include <vector>
|
||||
|
|
||||
|
#include "audio_core/renderer/adsp/command_list_processor.h"
|
||||
|
#include "audio_core/renderer/command/effect/compressor.h"
|
||||
|
#include "audio_core/renderer/effect/compressor.h"
|
||||
|
|
||||
|
namespace AudioCore::AudioRenderer { |
||||
|
|
||||
|
static void SetCompressorEffectParameter(CompressorInfo::ParameterVersion2& params, |
||||
|
CompressorInfo::State& state) { |
||||
|
const auto ratio{1.0f / params.compressor_ratio}; |
||||
|
auto makeup_gain{0.0f}; |
||||
|
if (params.makeup_gain_enabled) { |
||||
|
makeup_gain = (params.threshold * 0.5f) * (ratio - 1.0f) - 3.0f; |
||||
|
} |
||||
|
state.makeup_gain = makeup_gain; |
||||
|
state.unk_18 = params.unk_28; |
||||
|
|
||||
|
const auto a{(params.out_gain + makeup_gain) / 20.0f * 3.3219f}; |
||||
|
const auto b{(a - std::trunc(a)) * 0.69315f}; |
||||
|
const auto c{std::pow(2.0f, b)}; |
||||
|
|
||||
|
state.unk_0C = (1.0f - ratio) / 6.0f; |
||||
|
state.unk_14 = params.threshold + 1.5f; |
||||
|
state.unk_10 = params.threshold - 1.5f; |
||||
|
state.unk_20 = c; |
||||
|
} |
||||
|
|
||||
|
static void InitializeCompressorEffect(CompressorInfo::ParameterVersion2& params, |
||||
|
CompressorInfo::State& state) { |
||||
|
std::memset(&state, 0, sizeof(CompressorInfo::State)); |
||||
|
|
||||
|
state.unk_00 = 0; |
||||
|
state.unk_04 = 1.0f; |
||||
|
state.unk_08 = 1.0f; |
||||
|
|
||||
|
SetCompressorEffectParameter(params, state); |
||||
|
} |
||||
|
|
||||
|
static void ApplyCompressorEffect(CompressorInfo::ParameterVersion2& params, |
||||
|
CompressorInfo::State& state, bool enabled, |
||||
|
std::vector<std::span<const s32>> input_buffers, |
||||
|
std::vector<std::span<s32>> output_buffers, u32 sample_count) { |
||||
|
if (enabled) { |
||||
|
auto state_00{state.unk_00}; |
||||
|
auto state_04{state.unk_04}; |
||||
|
auto state_08{state.unk_08}; |
||||
|
auto state_18{state.unk_18}; |
||||
|
|
||||
|
for (u32 i = 0; i < sample_count; i++) { |
||||
|
auto a{0.0f}; |
||||
|
for (s16 channel = 0; channel < params.channel_count; channel++) { |
||||
|
const auto input_sample{Common::FixedPoint<49, 15>(input_buffers[channel][i])}; |
||||
|
a += (input_sample * input_sample).to_float(); |
||||
|
} |
||||
|
|
||||
|
state_00 += params.unk_24 * ((a / params.channel_count) - state.unk_00); |
||||
|
|
||||
|
auto b{-100.0f}; |
||||
|
auto c{0.0f}; |
||||
|
if (state_00 >= 1.0e-10) { |
||||
|
b = std::log10(state_00) * 10.0f; |
||||
|
c = 1.0f; |
||||
|
} |
||||
|
|
||||
|
if (b >= state.unk_10) { |
||||
|
const auto d{b >= state.unk_14 |
||||
|
? ((1.0f / params.compressor_ratio) - 1.0f) * |
||||
|
(b - params.threshold) |
||||
|
: (b - state.unk_10) * (b - state.unk_10) * -state.unk_0C}; |
||||
|
const auto e{d / 20.0f * 3.3219f}; |
||||
|
const auto f{(e - std::trunc(e)) * 0.69315f}; |
||||
|
c = std::pow(2.0f, f); |
||||
|
} |
||||
|
|
||||
|
state_18 = params.unk_28; |
||||
|
auto tmp{c}; |
||||
|
if ((state_04 - c) <= 0.08f) { |
||||
|
state_18 = params.unk_2C; |
||||
|
if (((state_04 - c) >= -0.08f) && (std::abs(state_08 - c) >= 0.001f)) { |
||||
|
tmp = state_04; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
state_04 = tmp; |
||||
|
state_08 += (c - state_08) * state_18; |
||||
|
|
||||
|
for (s16 channel = 0; channel < params.channel_count; channel++) { |
||||
|
output_buffers[channel][i] = static_cast<s32>( |
||||
|
static_cast<f32>(input_buffers[channel][i]) * state_08 * state.unk_20); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
state.unk_00 = state_00; |
||||
|
state.unk_04 = state_04; |
||||
|
state.unk_08 = state_08; |
||||
|
state.unk_18 = state_18; |
||||
|
} else { |
||||
|
for (s16 channel = 0; channel < params.channel_count; channel++) { |
||||
|
if (params.inputs[channel] != params.outputs[channel]) { |
||||
|
std::memcpy((char*)output_buffers[channel].data(), |
||||
|
(char*)input_buffers[channel].data(), |
||||
|
output_buffers[channel].size_bytes()); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void CompressorCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, |
||||
|
std::string& string) { |
||||
|
string += fmt::format("CompressorCommand\n\tenabled {} \n\tinputs: ", effect_enabled); |
||||
|
for (s16 i = 0; i < parameter.channel_count; i++) { |
||||
|
string += fmt::format("{:02X}, ", inputs[i]); |
||||
|
} |
||||
|
string += "\n\toutputs: "; |
||||
|
for (s16 i = 0; i < parameter.channel_count; i++) { |
||||
|
string += fmt::format("{:02X}, ", outputs[i]); |
||||
|
} |
||||
|
string += "\n"; |
||||
|
} |
||||
|
|
||||
|
void CompressorCommand::Process(const ADSP::CommandListProcessor& processor) { |
||||
|
std::vector<std::span<const s32>> input_buffers(parameter.channel_count); |
||||
|
std::vector<std::span<s32>> output_buffers(parameter.channel_count); |
||||
|
|
||||
|
for (s16 i = 0; i < parameter.channel_count; i++) { |
||||
|
input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count, |
||||
|
processor.sample_count); |
||||
|
output_buffers[i] = processor.mix_buffers.subspan(outputs[i] * processor.sample_count, |
||||
|
processor.sample_count); |
||||
|
} |
||||
|
|
||||
|
auto state_{reinterpret_cast<CompressorInfo::State*>(state)}; |
||||
|
|
||||
|
if (effect_enabled) { |
||||
|
if (parameter.state == CompressorInfo::ParameterState::Updating) { |
||||
|
SetCompressorEffectParameter(parameter, *state_); |
||||
|
} else if (parameter.state == CompressorInfo::ParameterState::Initialized) { |
||||
|
InitializeCompressorEffect(parameter, *state_); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
ApplyCompressorEffect(parameter, *state_, effect_enabled, input_buffers, output_buffers, |
||||
|
processor.sample_count); |
||||
|
} |
||||
|
|
||||
|
bool CompressorCommand::Verify(const ADSP::CommandListProcessor& processor) { |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
} // namespace AudioCore::AudioRenderer
|
||||
@ -0,0 +1,60 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project |
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <array> |
||||
|
#include <string> |
||||
|
|
||||
|
#include "audio_core/renderer/command/icommand.h" |
||||
|
#include "audio_core/renderer/effect/compressor.h" |
||||
|
#include "common/common_types.h" |
||||
|
|
||||
|
namespace AudioCore::AudioRenderer { |
||||
|
namespace ADSP { |
||||
|
class CommandListProcessor; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* AudioRenderer command for limiting volume between a high and low threshold. |
||||
|
* Version 1. |
||||
|
*/ |
||||
|
struct CompressorCommand : ICommand { |
||||
|
/** |
||||
|
* Print this command's information to a string. |
||||
|
* |
||||
|
* @param processor - The CommandListProcessor processing this command. |
||||
|
* @param string - The string to print into. |
||||
|
*/ |
||||
|
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override; |
||||
|
|
||||
|
/** |
||||
|
* Process this command. |
||||
|
* |
||||
|
* @param processor - The CommandListProcessor processing this command. |
||||
|
*/ |
||||
|
void Process(const ADSP::CommandListProcessor& processor) override; |
||||
|
|
||||
|
/** |
||||
|
* Verify this command's data is valid. |
||||
|
* |
||||
|
* @param processor - The CommandListProcessor processing this command. |
||||
|
* @return True if the command is valid, otherwise false. |
||||
|
*/ |
||||
|
bool Verify(const ADSP::CommandListProcessor& processor) override; |
||||
|
|
||||
|
/// Input mix buffer offsets for each channel |
||||
|
std::array<s16, MaxChannels> inputs; |
||||
|
/// Output mix buffer offsets for each channel |
||||
|
std::array<s16, MaxChannels> outputs; |
||||
|
/// Input parameters |
||||
|
CompressorInfo::ParameterVersion2 parameter; |
||||
|
/// State, updated each call |
||||
|
CpuAddr state; |
||||
|
/// Game-supplied workbuffer (Unused) |
||||
|
CpuAddr workbuffer; |
||||
|
/// Is this effect enabled? |
||||
|
bool effect_enabled; |
||||
|
}; |
||||
|
|
||||
|
} // namespace AudioCore::AudioRenderer |
||||
@ -0,0 +1,238 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
|
||||
|
#include "audio_core/renderer/adsp/command_list_processor.h"
|
||||
|
#include "audio_core/renderer/command/effect/delay.h"
|
||||
|
|
||||
|
namespace AudioCore::AudioRenderer { |
||||
|
/**
|
||||
|
* Update the DelayInfo state according to the given parameters. |
||||
|
* |
||||
|
* @param params - Input parameters to update the state. |
||||
|
* @param state - State to be updated. |
||||
|
*/ |
||||
|
static void SetDelayEffectParameter(const DelayInfo::ParameterVersion1& params, |
||||
|
DelayInfo::State& state) { |
||||
|
auto channel_spread{params.channel_spread}; |
||||
|
state.feedback_gain = params.feedback_gain * 0.97998046875f; |
||||
|
state.delay_feedback_gain = state.feedback_gain * (1.0f - channel_spread); |
||||
|
if (params.channel_count == 4 || params.channel_count == 6) { |
||||
|
channel_spread >>= 1; |
||||
|
} |
||||
|
state.delay_feedback_cross_gain = channel_spread * state.feedback_gain; |
||||
|
state.lowpass_feedback_gain = params.lowpass_amount * 0.949951171875f; |
||||
|
state.lowpass_gain = 1.0f - state.lowpass_feedback_gain; |
||||
|
} |
||||
|
|
||||
|
/**
|
||||
|
* Initialize a new DelayInfo state according to the given parameters. |
||||
|
* |
||||
|
* @param params - Input parameters to update the state. |
||||
|
* @param state - State to be updated. |
||||
|
* @param workbuffer - Game-supplied memory for the state. (Unused) |
||||
|
*/ |
||||
|
static void InitializeDelayEffect(const DelayInfo::ParameterVersion1& params, |
||||
|
DelayInfo::State& state, |
||||
|
[[maybe_unused]] const CpuAddr workbuffer) { |
||||
|
state = {}; |
||||
|
|
||||
|
for (u32 channel = 0; channel < params.channel_count; channel++) { |
||||
|
Common::FixedPoint<32, 32> sample_count_max{0.064f}; |
||||
|
sample_count_max *= params.sample_rate.to_int_floor() * params.delay_time_max; |
||||
|
|
||||
|
Common::FixedPoint<18, 14> delay_time{params.delay_time}; |
||||
|
delay_time *= params.sample_rate / 1000; |
||||
|
Common::FixedPoint<32, 32> sample_count{delay_time}; |
||||
|
|
||||
|
if (sample_count > sample_count_max) { |
||||
|
sample_count = sample_count_max; |
||||
|
} |
||||
|
|
||||
|
state.delay_lines[channel].sample_count_max = sample_count_max.to_int_floor(); |
||||
|
state.delay_lines[channel].sample_count = sample_count.to_int_floor(); |
||||
|
state.delay_lines[channel].buffer.resize(state.delay_lines[channel].sample_count, 0); |
||||
|
if (state.delay_lines[channel].buffer.size() == 0) { |
||||
|
state.delay_lines[channel].buffer.push_back(0); |
||||
|
} |
||||
|
state.delay_lines[channel].buffer_pos = 0; |
||||
|
state.delay_lines[channel].decay_rate = 1.0f; |
||||
|
} |
||||
|
|
||||
|
SetDelayEffectParameter(params, state); |
||||
|
} |
||||
|
|
||||
|
/**
|
||||
|
* Delay effect impl, according to the parameters and current state, on the input mix buffers, |
||||
|
* saving the results to the output mix buffers. |
||||
|
* |
||||
|
* @tparam NumChannels - Number of channels to process. 1-6. |
||||
|
* @param params - Input parameters to use. |
||||
|
* @param state - State to use, must be initialized (see InitializeDelayEffect). |
||||
|
* @param inputs - Input mix buffers to performan the delay on. |
||||
|
* @param outputs - Output mix buffers to receive the delayed samples. |
||||
|
* @param sample_count - Number of samples to process. |
||||
|
*/ |
||||
|
template <size_t NumChannels> |
||||
|
static void ApplyDelay(const DelayInfo::ParameterVersion1& params, DelayInfo::State& state, |
||||
|
std::vector<std::span<const s32>>& inputs, |
||||
|
std::vector<std::span<s32>>& outputs, const u32 sample_count) { |
||||
|
for (u32 sample_index = 0; sample_index < sample_count; sample_index++) { |
||||
|
std::array<Common::FixedPoint<50, 14>, NumChannels> input_samples{}; |
||||
|
for (u32 channel = 0; channel < NumChannels; channel++) { |
||||
|
input_samples[channel] = inputs[channel][sample_index] * 64; |
||||
|
} |
||||
|
|
||||
|
std::array<Common::FixedPoint<50, 14>, NumChannels> delay_samples{}; |
||||
|
for (u32 channel = 0; channel < NumChannels; channel++) { |
||||
|
delay_samples[channel] = state.delay_lines[channel].Read(); |
||||
|
} |
||||
|
|
||||
|
// clang-format off
|
||||
|
std::array<std::array<Common::FixedPoint<18, 14>, NumChannels>, NumChannels> matrix{}; |
||||
|
if constexpr (NumChannels == 1) { |
||||
|
matrix = {{ |
||||
|
{state.feedback_gain}, |
||||
|
}}; |
||||
|
} else if constexpr (NumChannels == 2) { |
||||
|
matrix = {{ |
||||
|
{state.delay_feedback_gain, state.delay_feedback_cross_gain}, |
||||
|
{state.delay_feedback_cross_gain, state.delay_feedback_gain}, |
||||
|
}}; |
||||
|
} else if constexpr (NumChannels == 4) { |
||||
|
matrix = {{ |
||||
|
{state.delay_feedback_gain, state.delay_feedback_cross_gain, state.delay_feedback_cross_gain, 0.0f}, |
||||
|
{state.delay_feedback_cross_gain, state.delay_feedback_gain, 0.0f, state.delay_feedback_cross_gain}, |
||||
|
{state.delay_feedback_cross_gain, 0.0f, state.delay_feedback_gain, state.delay_feedback_cross_gain}, |
||||
|
{0.0f, state.delay_feedback_cross_gain, state.delay_feedback_cross_gain, state.delay_feedback_gain}, |
||||
|
}}; |
||||
|
} else if constexpr (NumChannels == 6) { |
||||
|
matrix = {{ |
||||
|
{state.delay_feedback_gain, 0.0f, state.delay_feedback_cross_gain, 0.0f, state.delay_feedback_cross_gain, 0.0f}, |
||||
|
{0.0f, state.delay_feedback_gain, state.delay_feedback_cross_gain, 0.0f, 0.0f, state.delay_feedback_cross_gain}, |
||||
|
{state.delay_feedback_cross_gain, state.delay_feedback_cross_gain, state.delay_feedback_gain, 0.0f, 0.0f, 0.0f}, |
||||
|
{0.0f, 0.0f, 0.0f, params.feedback_gain, 0.0f, 0.0f}, |
||||
|
{state.delay_feedback_cross_gain, 0.0f, 0.0f, 0.0f, state.delay_feedback_gain, state.delay_feedback_cross_gain}, |
||||
|
{0.0f, state.delay_feedback_cross_gain, 0.0f, 0.0f, state.delay_feedback_cross_gain, state.delay_feedback_gain}, |
||||
|
}}; |
||||
|
} |
||||
|
// clang-format on
|
||||
|
|
||||
|
std::array<Common::FixedPoint<50, 14>, NumChannels> gained_samples{}; |
||||
|
for (u32 channel = 0; channel < NumChannels; channel++) { |
||||
|
Common::FixedPoint<50, 14> delay{}; |
||||
|
for (u32 j = 0; j < NumChannels; j++) { |
||||
|
delay += delay_samples[j] * matrix[j][channel]; |
||||
|
} |
||||
|
gained_samples[channel] = input_samples[channel] * params.in_gain + delay; |
||||
|
} |
||||
|
|
||||
|
for (u32 channel = 0; channel < NumChannels; channel++) { |
||||
|
state.lowpass_z[channel] = gained_samples[channel] * state.lowpass_gain + |
||||
|
state.lowpass_z[channel] * state.lowpass_feedback_gain; |
||||
|
state.delay_lines[channel].Write(state.lowpass_z[channel]); |
||||
|
} |
||||
|
|
||||
|
for (u32 channel = 0; channel < NumChannels; channel++) { |
||||
|
outputs[channel][sample_index] = (input_samples[channel] * params.dry_gain + |
||||
|
delay_samples[channel] * params.wet_gain) |
||||
|
.to_int_floor() / |
||||
|
64; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/**
|
||||
|
* Apply a delay effect if enabled, according to the parameters and current state, on the input mix |
||||
|
* buffers, saving the results to the output mix buffers. |
||||
|
* |
||||
|
* @param params - Input parameters to use. |
||||
|
* @param state - State to use, must be initialized (see InitializeDelayEffect). |
||||
|
* @param enabled - If enabled, delay will be applied, otherwise input is copied to output. |
||||
|
* @param inputs - Input mix buffers to performan the delay on. |
||||
|
* @param outputs - Output mix buffers to receive the delayed samples. |
||||
|
* @param sample_count - Number of samples to process. |
||||
|
*/ |
||||
|
static void ApplyDelayEffect(const DelayInfo::ParameterVersion1& params, DelayInfo::State& state, |
||||
|
const bool enabled, std::vector<std::span<const s32>>& inputs, |
||||
|
std::vector<std::span<s32>>& outputs, const u32 sample_count) { |
||||
|
|
||||
|
if (!IsChannelCountValid(params.channel_count)) { |
||||
|
LOG_ERROR(Service_Audio, "Invalid delay channels {}", params.channel_count); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
if (enabled) { |
||||
|
switch (params.channel_count) { |
||||
|
case 1: |
||||
|
ApplyDelay<1>(params, state, inputs, outputs, sample_count); |
||||
|
break; |
||||
|
case 2: |
||||
|
ApplyDelay<2>(params, state, inputs, outputs, sample_count); |
||||
|
break; |
||||
|
case 4: |
||||
|
ApplyDelay<4>(params, state, inputs, outputs, sample_count); |
||||
|
break; |
||||
|
case 6: |
||||
|
ApplyDelay<6>(params, state, inputs, outputs, sample_count); |
||||
|
break; |
||||
|
default: |
||||
|
for (u32 channel = 0; channel < params.channel_count; channel++) { |
||||
|
if (inputs[channel].data() != outputs[channel].data()) { |
||||
|
std::memcpy(outputs[channel].data(), inputs[channel].data(), |
||||
|
sample_count * sizeof(s32)); |
||||
|
} |
||||
|
} |
||||
|
break; |
||||
|
} |
||||
|
} else { |
||||
|
for (u32 channel = 0; channel < params.channel_count; channel++) { |
||||
|
if (inputs[channel].data() != outputs[channel].data()) { |
||||
|
std::memcpy(outputs[channel].data(), inputs[channel].data(), |
||||
|
sample_count * sizeof(s32)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void DelayCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor, |
||||
|
std::string& string) { |
||||
|
string += fmt::format("DelayCommand\n\tenabled {} \n\tinputs: ", effect_enabled); |
||||
|
for (u32 i = 0; i < MaxChannels; i++) { |
||||
|
string += fmt::format("{:02X}, ", inputs[i]); |
||||
|
} |
||||
|
string += "\n\toutputs: "; |
||||
|
for (u32 i = 0; i < MaxChannels; i++) { |
||||
|
string += fmt::format("{:02X}, ", outputs[i]); |
||||
|
} |
||||
|
string += "\n"; |
||||
|
} |
||||
|
|
||||
|
void DelayCommand::Process(const ADSP::CommandListProcessor& processor) { |
||||
|
std::vector<std::span<const s32>> input_buffers(parameter.channel_count); |
||||
|
std::vector<std::span<s32>> output_buffers(parameter.channel_count); |
||||
|
|
||||
|
for (s16 i = 0; i < parameter.channel_count; i++) { |
||||
|
input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count, |
||||
|
processor.sample_count); |
||||
|
output_buffers[i] = processor.mix_buffers.subspan(outputs[i] * processor.sample_count, |
||||
|
processor.sample_count); |
||||
|
} |
||||
|
|
||||
|
auto state_{reinterpret_cast<DelayInfo::State*>(state)}; |
||||
|
|
||||
|
if (effect_enabled) { |
||||
|
if (parameter.state == DelayInfo::ParameterState::Updating) { |
||||
|
SetDelayEffectParameter(parameter, *state_); |
||||
|
} else if (parameter.state == DelayInfo::ParameterState::Initialized) { |
||||
|
InitializeDelayEffect(parameter, *state_, workbuffer); |
||||
|
} |
||||
|
} |
||||
|
ApplyDelayEffect(parameter, *state_, effect_enabled, input_buffers, output_buffers, |
||||
|
processor.sample_count); |
||||
|
} |
||||
|
|
||||
|
bool DelayCommand::Verify(const ADSP::CommandListProcessor& processor) { |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
} // namespace AudioCore::AudioRenderer
|
||||
Some files were not shown because too many files changed in this diff
Write
Preview
Loading…
Cancel
Save
Reference in new issue