Browse Source
Merge pull request #8545 from Kelebek1/Audio
Merge pull request #8545 from Kelebek1/Audio
Project Andioremotes/1778439668340191200/nce_cpp
committed by
GitHub
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