summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/audio_core/CMakeLists.txt16
-rw-r--r--src/audio_core/algorithm/interpolate.cpp32
-rw-r--r--src/audio_core/algorithm/interpolate.h3
-rw-r--r--src/audio_core/audio_renderer.cpp541
-rw-r--r--src/audio_core/audio_renderer.h224
-rw-r--r--src/audio_core/behavior_info.cpp79
-rw-r--r--src/audio_core/behavior_info.h52
-rw-r--r--src/audio_core/command_generator.cpp668
-rw-r--r--src/audio_core/command_generator.h83
-rw-r--r--src/audio_core/common.h64
-rw-r--r--src/audio_core/cubeb_sink.cpp14
-rw-r--r--src/audio_core/effect_context.cpp39
-rw-r--r--src/audio_core/effect_context.h114
-rw-r--r--src/audio_core/info_updater.cpp515
-rw-r--r--src/audio_core/info_updater.h58
-rw-r--r--src/audio_core/memory_pool.cpp62
-rw-r--r--src/audio_core/memory_pool.h53
-rw-r--r--src/audio_core/mix_context.cpp264
-rw-r--r--src/audio_core/mix_context.h107
-rw-r--r--src/audio_core/sink_context.cpp31
-rw-r--r--src/audio_core/sink_context.h89
-rw-r--r--src/audio_core/splitter_context.cpp617
-rw-r--r--src/audio_core/splitter_context.h221
-rw-r--r--src/audio_core/voice_context.cpp531
-rw-r--r--src/audio_core/voice_context.h291
-rw-r--r--src/core/hle/service/audio/audren_u.cpp149
26 files changed, 4204 insertions, 713 deletions
diff --git a/src/audio_core/CMakeLists.txt b/src/audio_core/CMakeLists.txt
index 5ef38a337..cb00ef60e 100644
--- a/src/audio_core/CMakeLists.txt
+++ b/src/audio_core/CMakeLists.txt
@@ -12,16 +12,32 @@ add_library(audio_core STATIC
12 buffer.h 12 buffer.h
13 codec.cpp 13 codec.cpp
14 codec.h 14 codec.h
15 command_generator.cpp
16 command_generator.h
15 common.h 17 common.h
18 effect_context.cpp
19 effect_context.h
20 info_updater.cpp
21 info_updater.h
22 memory_pool.cpp
23 memory_pool.h
24 mix_context.cpp
25 mix_context.h
16 null_sink.h 26 null_sink.h
17 sink.h 27 sink.h
28 sink_context.cpp
29 sink_context.h
18 sink_details.cpp 30 sink_details.cpp
19 sink_details.h 31 sink_details.h
20 sink_stream.h 32 sink_stream.h
33 splitter_context.cpp
34 splitter_context.h
21 stream.cpp 35 stream.cpp
22 stream.h 36 stream.h
23 time_stretch.cpp 37 time_stretch.cpp
24 time_stretch.h 38 time_stretch.h
39 voice_context.cpp
40 voice_context.h
25 41
26 $<$<BOOL:${ENABLE_CUBEB}>:cubeb_sink.cpp cubeb_sink.h> 42 $<$<BOOL:${ENABLE_CUBEB}>:cubeb_sink.cpp cubeb_sink.h>
27) 43)
diff --git a/src/audio_core/algorithm/interpolate.cpp b/src/audio_core/algorithm/interpolate.cpp
index 49ab9d3e1..689a54508 100644
--- a/src/audio_core/algorithm/interpolate.cpp
+++ b/src/audio_core/algorithm/interpolate.cpp
@@ -197,4 +197,36 @@ std::vector<s16> Interpolate(InterpolationState& state, std::vector<s16> input,
197 return output; 197 return output;
198} 198}
199 199
200void Resample(s32* output, const s32* input, s32 pitch, s32& fraction, std::size_t sample_count) {
201 const std::array<s16, 512>& lut = [pitch] {
202 if (pitch > 0xaaaa) {
203 return curve_lut0;
204 }
205 if (pitch <= 0x8000) {
206 return curve_lut1;
207 }
208 return curve_lut2;
209 }();
210
211 std::size_t index{};
212
213 for (std::size_t i = 0; i < sample_count; i++) {
214 const std::size_t lut_index{(static_cast<std::size_t>(fraction) >> 8) * 4};
215 const auto l0 = lut[lut_index + 0];
216 const auto l1 = lut[lut_index + 1];
217 const auto l2 = lut[lut_index + 2];
218 const auto l3 = lut[lut_index + 3];
219
220 const auto s0 = static_cast<s32>(input[index]);
221 const auto s1 = static_cast<s32>(input[index + 1]);
222 const auto s2 = static_cast<s32>(input[index + 2]);
223 const auto s3 = static_cast<s32>(input[index + 3]);
224
225 output[i] = (l0 * s0 + l1 * s1 + l2 * s2 + l3 * s3) >> 15;
226 fraction += pitch;
227 index += (fraction >> 15);
228 fraction &= 0x7fff;
229 }
230}
231
200} // namespace AudioCore 232} // namespace AudioCore
diff --git a/src/audio_core/algorithm/interpolate.h b/src/audio_core/algorithm/interpolate.h
index ab1a31754..d534077af 100644
--- a/src/audio_core/algorithm/interpolate.h
+++ b/src/audio_core/algorithm/interpolate.h
@@ -38,4 +38,7 @@ inline std::vector<s16> Interpolate(InterpolationState& state, std::vector<s16>
38 return Interpolate(state, std::move(input), ratio); 38 return Interpolate(state, std::move(input), ratio);
39} 39}
40 40
41/// Nintendo Switchs DSP resampling algorithm. Based on a single channel
42void Resample(s32* output, const s32* input, s32 pitch, s32& fraction, std::size_t sample_count);
43
41} // namespace AudioCore 44} // namespace AudioCore
diff --git a/src/audio_core/audio_renderer.cpp b/src/audio_core/audio_renderer.cpp
index d64452617..fbd87d5bf 100644
--- a/src/audio_core/audio_renderer.cpp
+++ b/src/audio_core/audio_renderer.cpp
@@ -2,90 +2,42 @@
2// Licensed under GPLv2 or any later version 2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include <vector>
5#include "audio_core/algorithm/interpolate.h" 6#include "audio_core/algorithm/interpolate.h"
6#include "audio_core/audio_out.h" 7#include "audio_core/audio_out.h"
7#include "audio_core/audio_renderer.h" 8#include "audio_core/audio_renderer.h"
8#include "audio_core/codec.h" 9#include "audio_core/codec.h"
9#include "audio_core/common.h" 10#include "audio_core/common.h"
11#include "audio_core/info_updater.h"
12#include "audio_core/voice_context.h"
10#include "common/assert.h" 13#include "common/assert.h"
11#include "common/logging/log.h" 14#include "common/logging/log.h"
12#include "core/core.h" 15#include "core/core.h"
13#include "core/hle/kernel/writable_event.h" 16#include "core/hle/kernel/writable_event.h"
14#include "core/memory.h" 17#include "core/memory.h"
18#include "core/settings.h"
15 19
16namespace AudioCore { 20namespace AudioCore {
17
18constexpr u32 STREAM_SAMPLE_RATE{48000};
19constexpr u32 STREAM_NUM_CHANNELS{2};
20using VoiceChannelHolder = std::array<VoiceResourceInformation*, 6>;
21class AudioRenderer::VoiceState {
22public:
23 bool IsPlaying() const {
24 return is_in_use && info.play_state == PlayState::Started;
25 }
26
27 const VoiceOutStatus& GetOutStatus() const {
28 return out_status;
29 }
30
31 const VoiceInfo& GetInfo() const {
32 return info;
33 }
34
35 VoiceInfo& GetInfo() {
36 return info;
37 }
38
39 void SetWaveIndex(std::size_t index);
40 std::vector<s16> DequeueSamples(std::size_t sample_count, Core::Memory::Memory& memory,
41 const VoiceChannelHolder& voice_resources);
42 void UpdateState();
43 void RefreshBuffer(Core::Memory::Memory& memory, const VoiceChannelHolder& voice_resources);
44
45private:
46 bool is_in_use{};
47 bool is_refresh_pending{};
48 std::size_t wave_index{};
49 std::size_t offset{};
50 Codec::ADPCMState adpcm_state{};
51 InterpolationState interp_state{};
52 std::vector<s16> samples;
53 VoiceOutStatus out_status{};
54 VoiceInfo info{};
55};
56
57class AudioRenderer::EffectState {
58public:
59 const EffectOutStatus& GetOutStatus() const {
60 return out_status;
61 }
62
63 const EffectInStatus& GetInfo() const {
64 return info;
65 }
66
67 EffectInStatus& GetInfo() {
68 return info;
69 }
70
71 void UpdateState(Core::Memory::Memory& memory);
72
73private:
74 EffectOutStatus out_status{};
75 EffectInStatus info{};
76};
77
78AudioRenderer::AudioRenderer(Core::Timing::CoreTiming& core_timing, Core::Memory::Memory& memory_, 21AudioRenderer::AudioRenderer(Core::Timing::CoreTiming& core_timing, Core::Memory::Memory& memory_,
79 AudioRendererParameter params, 22 AudioCommon::AudioRendererParameter params,
80 std::shared_ptr<Kernel::WritableEvent> buffer_event, 23 std::shared_ptr<Kernel::WritableEvent> buffer_event,
81 std::size_t instance_number) 24 std::size_t instance_number)
82 : worker_params{params}, buffer_event{buffer_event}, voices(params.voice_count), 25 : worker_params{params}, buffer_event{buffer_event},
83 voice_resources(params.voice_count), effects(params.effect_count), memory{memory_} { 26 memory_pool_info(params.effect_count + params.voice_count * 4),
27 voice_context(params.voice_count), effect_context(params.effect_count), mix_context(),
28 sink_context(params.sink_count), splitter_context(),
29 voices(params.voice_count), memory{memory_},
30 command_generator(worker_params, voice_context, mix_context, splitter_context, memory),
31 temp_mix_buffer(AudioCommon::TOTAL_TEMP_MIX_SIZE) {
84 behavior_info.SetUserRevision(params.revision); 32 behavior_info.SetUserRevision(params.revision);
33 splitter_context.Initialize(behavior_info, params.splitter_count,
34 params.num_splitter_send_channels);
35 mix_context.Initialize(behavior_info, params.submix_count + 1);
85 audio_out = std::make_unique<AudioCore::AudioOut>(); 36 audio_out = std::make_unique<AudioCore::AudioOut>();
86 stream = audio_out->OpenStream(core_timing, STREAM_SAMPLE_RATE, STREAM_NUM_CHANNELS, 37 stream =
87 fmt::format("AudioRenderer-Instance{}", instance_number), 38 audio_out->OpenStream(core_timing, params.sample_rate, AudioCommon::STREAM_NUM_CHANNELS,
88 [=]() { buffer_event->Signal(); }); 39 fmt::format("AudioRenderer-Instance{}", instance_number),
40 [=]() { buffer_event->Signal(); });
89 audio_out->StartStream(stream); 41 audio_out->StartStream(stream);
90 42
91 QueueMixedBuffer(0); 43 QueueMixedBuffer(0);
@@ -111,355 +63,200 @@ Stream::State AudioRenderer::GetStreamState() const {
111 return stream->GetState(); 63 return stream->GetState();
112} 64}
113 65
114ResultVal<std::vector<u8>> AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_params) { 66static constexpr s16 ClampToS16(s32 value) {
115 // Copy UpdateDataHeader struct 67 return static_cast<s16>(std::clamp(value, -32768, 32767));
116 UpdateDataHeader config{}; 68}
117 std::memcpy(&config, input_params.data(), sizeof(UpdateDataHeader));
118 u32 memory_pool_count = worker_params.effect_count + (worker_params.voice_count * 4);
119
120 if (!behavior_info.UpdateInput(input_params, sizeof(UpdateDataHeader))) {
121 LOG_ERROR(Audio, "Failed to update behavior info input parameters");
122 return Audren::ERR_INVALID_PARAMETERS;
123 }
124
125 // Copy MemoryPoolInfo structs
126 std::vector<MemoryPoolInfo> mem_pool_info(memory_pool_count);
127 std::memcpy(mem_pool_info.data(),
128 input_params.data() + sizeof(UpdateDataHeader) + config.behavior_size,
129 memory_pool_count * sizeof(MemoryPoolInfo));
130
131 // Copy voice resources
132 const std::size_t voice_resource_offset{sizeof(UpdateDataHeader) + config.behavior_size +
133 config.memory_pools_size};
134 std::memcpy(voice_resources.data(), input_params.data() + voice_resource_offset,
135 sizeof(VoiceResourceInformation) * voice_resources.size());
136
137 // Copy VoiceInfo structs
138 std::size_t voice_offset{sizeof(UpdateDataHeader) + config.behavior_size +
139 config.memory_pools_size + config.voice_resource_size};
140 for (auto& voice : voices) {
141 std::memcpy(&voice.GetInfo(), input_params.data() + voice_offset, sizeof(VoiceInfo));
142 voice_offset += sizeof(VoiceInfo);
143 }
144
145 std::size_t effect_offset{sizeof(UpdateDataHeader) + config.behavior_size +
146 config.memory_pools_size + config.voice_resource_size +
147 config.voices_size};
148 for (auto& effect : effects) {
149 std::memcpy(&effect.GetInfo(), input_params.data() + effect_offset, sizeof(EffectInStatus));
150 effect_offset += sizeof(EffectInStatus);
151 }
152
153 // Update memory pool state
154 std::vector<MemoryPoolEntry> memory_pool(memory_pool_count);
155 for (std::size_t index = 0; index < memory_pool.size(); ++index) {
156 if (mem_pool_info[index].pool_state == MemoryPoolStates::RequestAttach) {
157 memory_pool[index].state = MemoryPoolStates::Attached;
158 } else if (mem_pool_info[index].pool_state == MemoryPoolStates::RequestDetach) {
159 memory_pool[index].state = MemoryPoolStates::Detached;
160 }
161 }
162
163 // Update voices
164 for (auto& voice : voices) {
165 voice.UpdateState();
166 if (!voice.GetInfo().is_in_use) {
167 continue;
168 }
169 if (voice.GetInfo().is_new) {
170 voice.SetWaveIndex(voice.GetInfo().wave_buffer_head);
171 }
172 }
173
174 for (auto& effect : effects) {
175 effect.UpdateState(memory);
176 }
177
178 // Release previous buffers and queue next ones for playback
179 ReleaseAndQueueBuffers();
180
181 // Copy output header
182 UpdateDataHeader response_data{worker_params};
183 if (behavior_info.IsElapsedFrameCountSupported()) {
184 response_data.render_info = sizeof(RendererInfo);
185 response_data.total_size += sizeof(RendererInfo);
186 }
187
188 std::vector<u8> output_params(response_data.total_size);
189 std::memcpy(output_params.data(), &response_data, sizeof(UpdateDataHeader));
190
191 // Copy output memory pool entries
192 std::memcpy(output_params.data() + sizeof(UpdateDataHeader), memory_pool.data(),
193 response_data.memory_pools_size);
194
195 // Copy output voice status
196 std::size_t voice_out_status_offset{sizeof(UpdateDataHeader) + response_data.memory_pools_size};
197 for (const auto& voice : voices) {
198 std::memcpy(output_params.data() + voice_out_status_offset, &voice.GetOutStatus(),
199 sizeof(VoiceOutStatus));
200 voice_out_status_offset += sizeof(VoiceOutStatus);
201 }
202 69
203 std::size_t effect_out_status_offset{ 70ResultCode AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_params,
204 sizeof(UpdateDataHeader) + response_data.memory_pools_size + response_data.voices_size + 71 std::vector<u8>& output_params) {
205 response_data.voice_resource_size};
206 for (const auto& effect : effects) {
207 std::memcpy(output_params.data() + effect_out_status_offset, &effect.GetOutStatus(),
208 sizeof(EffectOutStatus));
209 effect_out_status_offset += sizeof(EffectOutStatus);
210 }
211 72
212 // Update behavior info output 73 InfoUpdater info_updater{input_params, output_params, behavior_info};
213 const std::size_t behavior_out_status_offset{
214 sizeof(UpdateDataHeader) + response_data.memory_pools_size + response_data.voices_size +
215 response_data.effects_size + response_data.sinks_size +
216 response_data.performance_manager_size};
217 74
218 if (!behavior_info.UpdateOutput(output_params, behavior_out_status_offset)) { 75 if (!info_updater.UpdateBehaviorInfo(behavior_info)) {
219 LOG_ERROR(Audio, "Failed to update behavior info output parameters"); 76 LOG_ERROR(Audio, "Failed to update behavior info input parameters");
220 return Audren::ERR_INVALID_PARAMETERS; 77 return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
221 } 78 }
222 79
223 if (behavior_info.IsElapsedFrameCountSupported()) { 80 if (!info_updater.UpdateMemoryPools(memory_pool_info)) {
224 const std::size_t renderer_info_offset{ 81 LOG_ERROR(Audio, "Failed to update memory pool parameters");
225 sizeof(UpdateDataHeader) + response_data.memory_pools_size + response_data.voices_size + 82 return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
226 response_data.effects_size + response_data.sinks_size +
227 response_data.performance_manager_size + response_data.behavior_size};
228 RendererInfo renderer_info{};
229 renderer_info.elasped_frame_count = elapsed_frame_count;
230 std::memcpy(output_params.data() + renderer_info_offset, &renderer_info,
231 sizeof(RendererInfo));
232 } 83 }
233 84
234 return MakeResult(output_params); 85 if (!info_updater.UpdateVoiceChannelResources(voice_context)) {
235} 86 LOG_ERROR(Audio, "Failed to update voice channel resource parameters");
236 87 return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
237void AudioRenderer::VoiceState::SetWaveIndex(std::size_t index) {
238 wave_index = index & 3;
239 is_refresh_pending = true;
240}
241
242std::vector<s16> AudioRenderer::VoiceState::DequeueSamples(
243 std::size_t sample_count, Core::Memory::Memory& memory,
244 const VoiceChannelHolder& voice_resources) {
245 if (!IsPlaying()) {
246 return {};
247 } 88 }
248 89
249 if (is_refresh_pending) { 90 if (!info_updater.UpdateVoices(voice_context, memory_pool_info, 0)) {
250 RefreshBuffer(memory, voice_resources); 91 LOG_ERROR(Audio, "Failed to update voice parameters");
92 return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
251 } 93 }
252 94
253 const std::size_t max_size{samples.size() - offset}; 95 // TODO(ogniK): Deal with stopped audio renderer but updates still taking place
254 const std::size_t dequeue_offset{offset}; 96 if (!info_updater.UpdateEffects(effect_context, true)) {
255 std::size_t size{sample_count * STREAM_NUM_CHANNELS}; 97 LOG_ERROR(Audio, "Failed to update effect parameters");
256 if (size > max_size) { 98 return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
257 size = max_size;
258 } 99 }
259 100
260 out_status.played_sample_count += size / STREAM_NUM_CHANNELS; 101 if (behavior_info.IsSplitterSupported()) {
261 offset += size; 102 if (!info_updater.UpdateSplitterInfo(splitter_context)) {
262 103 LOG_ERROR(Audio, "Failed to update splitter parameters");
263 const auto& wave_buffer{info.wave_buffer[wave_index]}; 104 return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
264 if (offset == samples.size()) {
265 offset = 0;
266
267 if (!wave_buffer.is_looping && wave_buffer.buffer_sz) {
268 SetWaveIndex(wave_index + 1);
269 }
270
271 if (wave_buffer.buffer_sz) {
272 out_status.wave_buffer_consumed++;
273 }
274
275 if (wave_buffer.end_of_stream || wave_buffer.buffer_sz == 0) {
276 info.play_state = PlayState::Paused;
277 } 105 }
278 } 106 }
279 107
280 return {samples.begin() + dequeue_offset, samples.begin() + dequeue_offset + size}; 108 auto mix_result =
281} 109 info_updater.UpdateMixes(mix_context, worker_params.mix_buffer_count, splitter_context);
282 110
283void AudioRenderer::VoiceState::UpdateState() { 111 if (mix_result.IsError()) {
284 if (is_in_use && !info.is_in_use) { 112 LOG_ERROR(Audio, "Failed to update mix parameters");
285 // No longer in use, reset state 113 return mix_result;
286 is_refresh_pending = true;
287 wave_index = 0;
288 offset = 0;
289 out_status = {};
290 } 114 }
291 is_in_use = info.is_in_use;
292}
293 115
294void AudioRenderer::VoiceState::RefreshBuffer(Core::Memory::Memory& memory, 116 // TODO(ogniK): Sinks
295 const VoiceChannelHolder& voice_resources) { 117 if (!info_updater.UpdateSinks(sink_context)) {
296 const auto wave_buffer_address = info.wave_buffer[wave_index].buffer_addr; 118 LOG_ERROR(Audio, "Failed to update sink parameters");
297 const auto wave_buffer_size = info.wave_buffer[wave_index].buffer_sz; 119 return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
298 std::vector<s16> new_samples(wave_buffer_size / sizeof(s16));
299 memory.ReadBlock(wave_buffer_address, new_samples.data(), wave_buffer_size);
300
301 switch (static_cast<Codec::PcmFormat>(info.sample_format)) {
302 case Codec::PcmFormat::Int16: {
303 // PCM16 is played as-is
304 break;
305 }
306 case Codec::PcmFormat::Adpcm: {
307 // Decode ADPCM to PCM16
308 Codec::ADPCM_Coeff coeffs;
309 memory.ReadBlock(info.additional_params_addr, coeffs.data(), sizeof(Codec::ADPCM_Coeff));
310 new_samples = Codec::DecodeADPCM(reinterpret_cast<u8*>(new_samples.data()),
311 new_samples.size() * sizeof(s16), coeffs, adpcm_state);
312 break;
313 } 120 }
314 default:
315 UNIMPLEMENTED_MSG("Unimplemented sample_format={}", info.sample_format);
316 break;
317 }
318
319 switch (info.channel_count) {
320 case 1: {
321 // 1 channel is upsampled to 2 channel
322 samples.resize(new_samples.size() * 2);
323 121
324 for (std::size_t index = 0; index < new_samples.size(); ++index) { 122 // TODO(ogniK): Performance buffer
325 auto sample = static_cast<float>(new_samples[index]); 123 if (!info_updater.UpdatePerformanceBuffer()) {
326 if (voice_resources[0]->in_use) { 124 LOG_ERROR(Audio, "Failed to update performance buffer parameters");
327 sample *= voice_resources[0]->mix_volumes[0]; 125 return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
328 }
329
330 samples[index * 2] = static_cast<s16>(sample * info.volume);
331 samples[index * 2 + 1] = static_cast<s16>(sample * info.volume);
332 }
333 break;
334 } 126 }
335 case 2: {
336 // 2 channel is played as is
337 samples = std::move(new_samples);
338 const std::size_t sample_count = (samples.size() / 2);
339 for (std::size_t index = 0; index < sample_count; ++index) {
340 const std::size_t index_l = index * 2;
341 const std::size_t index_r = index * 2 + 1;
342
343 auto sample_l = static_cast<float>(samples[index_l]);
344 auto sample_r = static_cast<float>(samples[index_r]);
345
346 if (voice_resources[0]->in_use) {
347 sample_l *= voice_resources[0]->mix_volumes[0];
348 }
349
350 if (voice_resources[1]->in_use) {
351 sample_r *= voice_resources[1]->mix_volumes[1];
352 }
353 127
354 samples[index_l] = static_cast<s16>(sample_l * info.volume); 128 if (!info_updater.UpdateErrorInfo(behavior_info)) {
355 samples[index_r] = static_cast<s16>(sample_r * info.volume); 129 LOG_ERROR(Audio, "Failed to update error info");
356 } 130 return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
357 break;
358 } 131 }
359 case 6: {
360 samples.resize((new_samples.size() / 6) * 2);
361 const std::size_t sample_count = samples.size() / 2;
362
363 for (std::size_t index = 0; index < sample_count; ++index) {
364 auto FL = static_cast<float>(new_samples[index * 6]);
365 auto FR = static_cast<float>(new_samples[index * 6 + 1]);
366 auto FC = static_cast<float>(new_samples[index * 6 + 2]);
367 auto BL = static_cast<float>(new_samples[index * 6 + 4]);
368 auto BR = static_cast<float>(new_samples[index * 6 + 5]);
369
370 if (voice_resources[0]->in_use) {
371 FL *= voice_resources[0]->mix_volumes[0];
372 }
373 if (voice_resources[1]->in_use) {
374 FR *= voice_resources[1]->mix_volumes[1];
375 }
376 if (voice_resources[2]->in_use) {
377 FC *= voice_resources[2]->mix_volumes[2];
378 }
379 if (voice_resources[4]->in_use) {
380 BL *= voice_resources[4]->mix_volumes[4];
381 }
382 if (voice_resources[5]->in_use) {
383 BR *= voice_resources[5]->mix_volumes[5];
384 }
385 132
386 samples[index * 2] = 133 if (behavior_info.IsElapsedFrameCountSupported()) {
387 static_cast<s16>((0.3694f * FL + 0.2612f * FC + 0.3694f * BL) * info.volume); 134 if (!info_updater.UpdateRendererInfo(elapsed_frame_count)) {
388 samples[index * 2 + 1] = 135 LOG_ERROR(Audio, "Failed to update renderer info");
389 static_cast<s16>((0.3694f * FR + 0.2612f * FC + 0.3694f * BR) * info.volume); 136 return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
390 } 137 }
391 break;
392 }
393 default:
394 UNIMPLEMENTED_MSG("Unimplemented channel_count={}", info.channel_count);
395 break;
396 } 138 }
139 // TODO(ogniK): Statistics
397 140
398 // Only interpolate when necessary, expensive. 141 if (!info_updater.WriteOutputHeader()) {
399 if (GetInfo().sample_rate != STREAM_SAMPLE_RATE) { 142 LOG_ERROR(Audio, "Failed to write output header");
400 samples = Interpolate(interp_state, std::move(samples), GetInfo().sample_rate, 143 return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
401 STREAM_SAMPLE_RATE);
402 } 144 }
403 145
404 is_refresh_pending = false; 146 // TODO(ogniK): Check when all sections are implemented
405}
406 147
407void AudioRenderer::EffectState::UpdateState(Core::Memory::Memory& memory) { 148 if (!info_updater.CheckConsumedSize()) {
408 if (info.is_new) { 149 LOG_ERROR(Audio, "Audio buffers were not consumed!");
409 out_status.state = EffectStatus::New; 150 return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
410 } else {
411 if (info.type == Effect::Aux) {
412 ASSERT_MSG(memory.Read32(info.aux_info.return_buffer_info) == 0,
413 "Aux buffers tried to update");
414 ASSERT_MSG(memory.Read32(info.aux_info.send_buffer_info) == 0,
415 "Aux buffers tried to update");
416 ASSERT_MSG(memory.Read32(info.aux_info.return_buffer_base) == 0,
417 "Aux buffers tried to update");
418 ASSERT_MSG(memory.Read32(info.aux_info.send_buffer_base) == 0,
419 "Aux buffers tried to update");
420 }
421 } 151 }
422}
423 152
424static constexpr s16 ClampToS16(s32 value) { 153 ReleaseAndQueueBuffers();
425 return static_cast<s16>(std::clamp(value, -32768, 32767)); 154
155 return RESULT_SUCCESS;
426} 156}
427 157
428void AudioRenderer::QueueMixedBuffer(Buffer::Tag tag) { 158void AudioRenderer::QueueMixedBuffer(Buffer::Tag tag) {
429 constexpr std::size_t BUFFER_SIZE{512}; 159 command_generator.PreCommand();
160 // Clear mix buffers before our next operation
161 command_generator.ClearMixBuffers();
162
163 // If the splitter is not in use, sort our mixes
164 if (!splitter_context.UsingSplitter()) {
165 mix_context.SortInfo();
166 }
167 // Sort our voices
168 voice_context.SortInfo();
169
170 // Handle samples
171 command_generator.GenerateVoiceCommands();
172 command_generator.GenerateSubMixCommands();
173 command_generator.GenerateFinalMixCommands();
174
175 command_generator.PostCommand();
176 // Base sample size
177 std::size_t BUFFER_SIZE{worker_params.sample_count};
178 // Samples
430 std::vector<s16> buffer(BUFFER_SIZE * stream->GetNumChannels()); 179 std::vector<s16> buffer(BUFFER_SIZE * stream->GetNumChannels());
431 180 // Make sure to clear our samples
432 for (auto& voice : voices) { 181 std::memset(buffer.data(), 0, buffer.size() * sizeof(s16));
433 if (!voice.IsPlaying()) { 182
434 continue; 183 if (sink_context.InUse()) {
435 } 184 const auto stream_channel_count = stream->GetNumChannels();
436 VoiceChannelHolder resources{}; 185 const auto buffer_offsets = sink_context.OutputBuffers();
437 for (u32 channel = 0; channel < voice.GetInfo().channel_count; channel++) { 186 const auto channel_count = buffer_offsets.size();
438 const auto channel_resource_id = voice.GetInfo().voice_channel_resource_ids[channel]; 187 const auto& final_mix = mix_context.GetFinalMixInfo();
439 resources[channel] = &voice_resources[channel_resource_id]; 188 const auto& in_params = final_mix.GetInParams();
189 std::vector<s32*> mix_buffers(channel_count);
190 for (std::size_t i = 0; i < channel_count; i++) {
191 mix_buffers[i] =
192 command_generator.GetMixBuffer(in_params.buffer_offset + buffer_offsets[i]);
440 } 193 }
441 194
442 std::size_t offset{}; 195 for (std::size_t i = 0; i < BUFFER_SIZE; i++) {
443 s64 samples_remaining{BUFFER_SIZE}; 196 if (channel_count == 1) {
444 while (samples_remaining > 0) { 197 const auto sample = ClampToS16(mix_buffers[0][i]);
445 const std::vector<s16> samples{ 198 buffer[i * stream_channel_count + 0] = sample;
446 voice.DequeueSamples(samples_remaining, memory, resources)}; 199 if (stream_channel_count > 1) {
447 200 buffer[i * stream_channel_count + 1] = sample;
448 if (samples.empty()) { 201 }
449 break; 202 if (stream_channel_count == 6) {
450 } 203 buffer[i * stream_channel_count + 2] = sample;
451 204 buffer[i * stream_channel_count + 4] = sample;
452 samples_remaining -= samples.size() / stream->GetNumChannels(); 205 buffer[i * stream_channel_count + 5] = sample;
453 206 }
454 for (const auto& sample : samples) { 207 } else if (channel_count == 2) {
455 const s32 buffer_sample{buffer[offset]}; 208 const auto l_sample = ClampToS16(mix_buffers[0][i]);
456 buffer[offset++] = 209 const auto r_sample = ClampToS16(mix_buffers[1][i]);
457 ClampToS16(buffer_sample + static_cast<s32>(sample * voice.GetInfo().volume)); 210 if (stream_channel_count == 0) {
211 buffer[i * stream_channel_count + 0] = l_sample;
212 } else if (stream_channel_count == 2) {
213 buffer[i * stream_channel_count + 0] = l_sample;
214 buffer[i * stream_channel_count + 1] = r_sample;
215 } else if (stream_channel_count == 6) {
216 buffer[i * stream_channel_count + 0] = l_sample;
217 buffer[i * stream_channel_count + 1] = r_sample;
218
219 buffer[i * stream_channel_count + 2] =
220 ClampToS16((static_cast<s32>(l_sample) + static_cast<s32>(r_sample)) / 2);
221
222 buffer[i * stream_channel_count + 4] = l_sample;
223 buffer[i * stream_channel_count + 5] = r_sample;
224 }
225
226 } else if (channel_count == 6) {
227 const auto fl_sample = ClampToS16(mix_buffers[0][i]);
228 const auto fr_sample = ClampToS16(mix_buffers[1][i]);
229 const auto fc_sample = ClampToS16(mix_buffers[2][i]);
230 const auto lf_sample = ClampToS16(mix_buffers[3][i]);
231 const auto bl_sample = ClampToS16(mix_buffers[4][i]);
232 const auto br_sample = ClampToS16(mix_buffers[5][i]);
233
234 if (stream_channel_count == 1) {
235 buffer[i * stream_channel_count + 0] = fc_sample;
236 } else if (stream_channel_count == 2) {
237 buffer[i * stream_channel_count + 0] =
238 static_cast<s16>(0.3694f * static_cast<float>(fl_sample) +
239 0.2612f * static_cast<float>(fc_sample) +
240 0.3694f * static_cast<float>(bl_sample));
241 buffer[i * stream_channel_count + 1] =
242 static_cast<s16>(0.3694f * static_cast<float>(fr_sample) +
243 0.2612f * static_cast<float>(fc_sample) +
244 0.3694f * static_cast<float>(br_sample));
245 } else if (stream_channel_count == 6) {
246 buffer[i * stream_channel_count + 0] = fl_sample;
247 buffer[i * stream_channel_count + 1] = fr_sample;
248 buffer[i * stream_channel_count + 2] = fc_sample;
249 buffer[i * stream_channel_count + 3] = lf_sample;
250 buffer[i * stream_channel_count + 4] = bl_sample;
251 buffer[i * stream_channel_count + 5] = br_sample;
252 }
458 } 253 }
459 } 254 }
460 } 255 }
256
461 audio_out->QueueBuffer(stream, tag, std::move(buffer)); 257 audio_out->QueueBuffer(stream, tag, std::move(buffer));
462 elapsed_frame_count++; 258 elapsed_frame_count++;
259 voice_context.UpdateStateByDspShared();
463} 260}
464 261
465void AudioRenderer::ReleaseAndQueueBuffers() { 262void AudioRenderer::ReleaseAndQueueBuffers() {
diff --git a/src/audio_core/audio_renderer.h b/src/audio_core/audio_renderer.h
index f0b691a86..2bca795ba 100644
--- a/src/audio_core/audio_renderer.h
+++ b/src/audio_core/audio_renderer.h
@@ -9,8 +9,15 @@
9#include <vector> 9#include <vector>
10 10
11#include "audio_core/behavior_info.h" 11#include "audio_core/behavior_info.h"
12#include "audio_core/command_generator.h"
12#include "audio_core/common.h" 13#include "audio_core/common.h"
14#include "audio_core/effect_context.h"
15#include "audio_core/memory_pool.h"
16#include "audio_core/mix_context.h"
17#include "audio_core/sink_context.h"
18#include "audio_core/splitter_context.h"
13#include "audio_core/stream.h" 19#include "audio_core/stream.h"
20#include "audio_core/voice_context.h"
14#include "common/common_funcs.h" 21#include "common/common_funcs.h"
15#include "common/common_types.h" 22#include "common/common_types.h"
16#include "common/swap.h" 23#include "common/swap.h"
@@ -30,220 +37,25 @@ class Memory;
30} 37}
31 38
32namespace AudioCore { 39namespace AudioCore {
40using DSPStateHolder = std::array<VoiceState*, 6>;
33 41
34class AudioOut; 42class AudioOut;
35 43
36enum class PlayState : u8 {
37 Started = 0,
38 Stopped = 1,
39 Paused = 2,
40};
41
42enum class Effect : u8 {
43 None = 0,
44 Aux = 2,
45};
46
47enum class EffectStatus : u8 {
48 None = 0,
49 New = 1,
50};
51
52struct AudioRendererParameter {
53 u32_le sample_rate;
54 u32_le sample_count;
55 u32_le mix_buffer_count;
56 u32_le submix_count;
57 u32_le voice_count;
58 u32_le sink_count;
59 u32_le effect_count;
60 u32_le performance_frame_count;
61 u8 is_voice_drop_enabled;
62 u8 unknown_21;
63 u8 unknown_22;
64 u8 execution_mode;
65 u32_le splitter_count;
66 u32_le num_splitter_send_channels;
67 u32_le unknown_30;
68 u32_le revision;
69};
70static_assert(sizeof(AudioRendererParameter) == 52, "AudioRendererParameter is an invalid size");
71
72enum class MemoryPoolStates : u32 { // Should be LE
73 Invalid = 0x0,
74 Unknown = 0x1,
75 RequestDetach = 0x2,
76 Detached = 0x3,
77 RequestAttach = 0x4,
78 Attached = 0x5,
79 Released = 0x6,
80};
81
82struct MemoryPoolEntry {
83 MemoryPoolStates state;
84 u32_le unknown_4;
85 u32_le unknown_8;
86 u32_le unknown_c;
87};
88static_assert(sizeof(MemoryPoolEntry) == 0x10, "MemoryPoolEntry has wrong size");
89
90struct MemoryPoolInfo {
91 u64_le pool_address;
92 u64_le pool_size;
93 MemoryPoolStates pool_state;
94 INSERT_PADDING_WORDS(3); // Unknown
95};
96static_assert(sizeof(MemoryPoolInfo) == 0x20, "MemoryPoolInfo has wrong size");
97struct BiquadFilter {
98 u8 enable;
99 INSERT_PADDING_BYTES(1);
100 std::array<s16_le, 3> numerator;
101 std::array<s16_le, 2> denominator;
102};
103static_assert(sizeof(BiquadFilter) == 0xc, "BiquadFilter has wrong size");
104
105struct WaveBuffer {
106 u64_le buffer_addr;
107 u64_le buffer_sz;
108 s32_le start_sample_offset;
109 s32_le end_sample_offset;
110 u8 is_looping;
111 u8 end_of_stream;
112 u8 sent_to_server;
113 INSERT_PADDING_BYTES(5);
114 u64 context_addr;
115 u64 context_sz;
116 INSERT_PADDING_BYTES(8);
117};
118static_assert(sizeof(WaveBuffer) == 0x38, "WaveBuffer has wrong size");
119
120struct VoiceResourceInformation {
121 s32_le id{};
122 std::array<float_le, MAX_MIX_BUFFERS> mix_volumes{};
123 bool in_use{};
124 INSERT_PADDING_BYTES(11);
125};
126static_assert(sizeof(VoiceResourceInformation) == 0x70, "VoiceResourceInformation has wrong size");
127
128struct VoiceInfo {
129 u32_le id;
130 u32_le node_id;
131 u8 is_new;
132 u8 is_in_use;
133 PlayState play_state;
134 u8 sample_format;
135 u32_le sample_rate;
136 u32_le priority;
137 u32_le sorting_order;
138 u32_le channel_count;
139 float_le pitch;
140 float_le volume;
141 std::array<BiquadFilter, 2> biquad_filter;
142 u32_le wave_buffer_count;
143 u32_le wave_buffer_head;
144 INSERT_PADDING_WORDS(1);
145 u64_le additional_params_addr;
146 u64_le additional_params_sz;
147 u32_le mix_id;
148 u32_le splitter_info_id;
149 std::array<WaveBuffer, 4> wave_buffer;
150 std::array<u32_le, 6> voice_channel_resource_ids;
151 INSERT_PADDING_BYTES(24);
152};
153static_assert(sizeof(VoiceInfo) == 0x170, "VoiceInfo is wrong size");
154
155struct VoiceOutStatus {
156 u64_le played_sample_count;
157 u32_le wave_buffer_consumed;
158 u32_le voice_drops_count;
159};
160static_assert(sizeof(VoiceOutStatus) == 0x10, "VoiceOutStatus has wrong size");
161
162struct AuxInfo {
163 std::array<u8, 24> input_mix_buffers;
164 std::array<u8, 24> output_mix_buffers;
165 u32_le mix_buffer_count;
166 u32_le sample_rate; // Stored in the aux buffer currently
167 u32_le sample_count;
168 u64_le send_buffer_info;
169 u64_le send_buffer_base;
170
171 u64_le return_buffer_info;
172 u64_le return_buffer_base;
173};
174static_assert(sizeof(AuxInfo) == 0x60, "AuxInfo is an invalid size");
175
176struct EffectInStatus {
177 Effect type;
178 u8 is_new;
179 u8 is_enabled;
180 INSERT_PADDING_BYTES(1);
181 u32_le mix_id;
182 u64_le buffer_base;
183 u64_le buffer_sz;
184 s32_le priority;
185 INSERT_PADDING_BYTES(4);
186 union {
187 std::array<u8, 0xa0> raw;
188 AuxInfo aux_info;
189 };
190};
191static_assert(sizeof(EffectInStatus) == 0xc0, "EffectInStatus is an invalid size");
192
193struct EffectOutStatus {
194 EffectStatus state;
195 INSERT_PADDING_BYTES(0xf);
196};
197static_assert(sizeof(EffectOutStatus) == 0x10, "EffectOutStatus is an invalid size");
198
199struct RendererInfo { 44struct RendererInfo {
200 u64_le elasped_frame_count{}; 45 u64_le elasped_frame_count{};
201 INSERT_PADDING_WORDS(2); 46 INSERT_PADDING_WORDS(2);
202}; 47};
203static_assert(sizeof(RendererInfo) == 0x10, "RendererInfo is an invalid size"); 48static_assert(sizeof(RendererInfo) == 0x10, "RendererInfo is an invalid size");
204 49
205struct UpdateDataHeader {
206 UpdateDataHeader() {}
207
208 explicit UpdateDataHeader(const AudioRendererParameter& config) {
209 revision = Common::MakeMagic('R', 'E', 'V', '8'); // 9.2.0 Revision
210 behavior_size = 0xb0;
211 memory_pools_size = (config.effect_count + (config.voice_count * 4)) * 0x10;
212 voices_size = config.voice_count * 0x10;
213 voice_resource_size = 0x0;
214 effects_size = config.effect_count * 0x10;
215 mixes_size = 0x0;
216 sinks_size = config.sink_count * 0x20;
217 performance_manager_size = 0x10;
218 render_info = 0;
219 total_size = sizeof(UpdateDataHeader) + behavior_size + memory_pools_size + voices_size +
220 effects_size + sinks_size + performance_manager_size;
221 }
222
223 u32_le revision{};
224 u32_le behavior_size{};
225 u32_le memory_pools_size{};
226 u32_le voices_size{};
227 u32_le voice_resource_size{};
228 u32_le effects_size{};
229 u32_le mixes_size{};
230 u32_le sinks_size{};
231 u32_le performance_manager_size{};
232 u32_le splitter_size{};
233 u32_le render_info{};
234 INSERT_PADDING_WORDS(4);
235 u32_le total_size{};
236};
237static_assert(sizeof(UpdateDataHeader) == 0x40, "UpdateDataHeader has wrong size");
238
239class AudioRenderer { 50class AudioRenderer {
240public: 51public:
241 AudioRenderer(Core::Timing::CoreTiming& core_timing, Core::Memory::Memory& memory_, 52 AudioRenderer(Core::Timing::CoreTiming& core_timing, Core::Memory::Memory& memory_,
242 AudioRendererParameter params, 53 AudioCommon::AudioRendererParameter params,
243 std::shared_ptr<Kernel::WritableEvent> buffer_event, std::size_t instance_number); 54 std::shared_ptr<Kernel::WritableEvent> buffer_event, std::size_t instance_number);
244 ~AudioRenderer(); 55 ~AudioRenderer();
245 56
246 ResultVal<std::vector<u8>> UpdateAudioRenderer(const std::vector<u8>& input_params); 57 ResultCode UpdateAudioRenderer(const std::vector<u8>& input_params,
58 std::vector<u8>& output_params);
247 void QueueMixedBuffer(Buffer::Tag tag); 59 void QueueMixedBuffer(Buffer::Tag tag);
248 void ReleaseAndQueueBuffers(); 60 void ReleaseAndQueueBuffers();
249 u32 GetSampleRate() const; 61 u32 GetSampleRate() const;
@@ -252,19 +64,23 @@ public:
252 Stream::State GetStreamState() const; 64 Stream::State GetStreamState() const;
253 65
254private: 66private:
255 class EffectState;
256 class VoiceState;
257 BehaviorInfo behavior_info{}; 67 BehaviorInfo behavior_info{};
258 68
259 AudioRendererParameter worker_params; 69 AudioCommon::AudioRendererParameter worker_params;
260 std::shared_ptr<Kernel::WritableEvent> buffer_event; 70 std::shared_ptr<Kernel::WritableEvent> buffer_event;
71 std::vector<ServerMemoryPoolInfo> memory_pool_info;
72 VoiceContext voice_context;
73 EffectContext effect_context;
74 MixContext mix_context;
75 SinkContext sink_context;
76 SplitterContext splitter_context;
261 std::vector<VoiceState> voices; 77 std::vector<VoiceState> voices;
262 std::vector<VoiceResourceInformation> voice_resources;
263 std::vector<EffectState> effects;
264 std::unique_ptr<AudioOut> audio_out; 78 std::unique_ptr<AudioOut> audio_out;
265 StreamPtr stream; 79 StreamPtr stream;
266 Core::Memory::Memory& memory; 80 Core::Memory::Memory& memory;
81 CommandGenerator command_generator;
267 std::size_t elapsed_frame_count{}; 82 std::size_t elapsed_frame_count{};
83 std::vector<s32> temp_mix_buffer{};
268}; 84};
269 85
270} // namespace AudioCore 86} // namespace AudioCore
diff --git a/src/audio_core/behavior_info.cpp b/src/audio_core/behavior_info.cpp
index 94b7a3bf1..b7cd8f17d 100644
--- a/src/audio_core/behavior_info.cpp
+++ b/src/audio_core/behavior_info.cpp
@@ -9,39 +9,11 @@
9 9
10namespace AudioCore { 10namespace AudioCore {
11 11
12BehaviorInfo::BehaviorInfo() : process_revision(CURRENT_PROCESS_REVISION) {} 12BehaviorInfo::BehaviorInfo() : process_revision(AudioCommon::CURRENT_PROCESS_REVISION) {}
13BehaviorInfo::~BehaviorInfo() = default; 13BehaviorInfo::~BehaviorInfo() = default;
14 14
15bool BehaviorInfo::UpdateInput(const std::vector<u8>& buffer, std::size_t offset) {
16 if (!CanConsumeBuffer(buffer.size(), offset, sizeof(InParams))) {
17 LOG_ERROR(Audio, "Buffer is an invalid size!");
18 return false;
19 }
20 InParams params{};
21 std::memcpy(&params, buffer.data() + offset, sizeof(InParams));
22
23 if (!IsValidRevision(params.revision)) {
24 LOG_ERROR(Audio, "Invalid input revision, revision=0x{:08X}", params.revision);
25 return false;
26 }
27
28 if (user_revision != params.revision) {
29 LOG_ERROR(Audio,
30 "User revision differs from input revision, expecting 0x{:08X} but got 0x{:08X}",
31 user_revision, params.revision);
32 return false;
33 }
34
35 ClearError();
36 UpdateFlags(params.flags);
37
38 // TODO(ogniK): Check input params size when InfoUpdater is used
39
40 return true;
41}
42
43bool BehaviorInfo::UpdateOutput(std::vector<u8>& buffer, std::size_t offset) { 15bool BehaviorInfo::UpdateOutput(std::vector<u8>& buffer, std::size_t offset) {
44 if (!CanConsumeBuffer(buffer.size(), offset, sizeof(OutParams))) { 16 if (!AudioCommon::CanConsumeBuffer(buffer.size(), offset, sizeof(OutParams))) {
45 LOG_ERROR(Audio, "Buffer is an invalid size!"); 17 LOG_ERROR(Audio, "Buffer is an invalid size!");
46 return false; 18 return false;
47 } 19 }
@@ -65,36 +37,69 @@ void BehaviorInfo::SetUserRevision(u32_le revision) {
65 user_revision = revision; 37 user_revision = revision;
66} 38}
67 39
40u32_le BehaviorInfo::GetUserRevision() const {
41 return user_revision;
42}
43
44u32_le BehaviorInfo::GetProcessRevision() const {
45 return process_revision;
46}
47
68bool BehaviorInfo::IsAdpcmLoopContextBugFixed() const { 48bool BehaviorInfo::IsAdpcmLoopContextBugFixed() const {
69 return IsRevisionSupported(2, user_revision); 49 return AudioCommon::IsRevisionSupported(2, user_revision);
70} 50}
71 51
72bool BehaviorInfo::IsSplitterSupported() const { 52bool BehaviorInfo::IsSplitterSupported() const {
73 return IsRevisionSupported(2, user_revision); 53 return AudioCommon::IsRevisionSupported(2, user_revision);
74} 54}
75 55
76bool BehaviorInfo::IsLongSizePreDelaySupported() const { 56bool BehaviorInfo::IsLongSizePreDelaySupported() const {
77 return IsRevisionSupported(3, user_revision); 57 return AudioCommon::IsRevisionSupported(3, user_revision);
78} 58}
79 59
80bool BehaviorInfo::IsAudioRenererProcessingTimeLimit80PercentSupported() const { 60bool BehaviorInfo::IsAudioRenererProcessingTimeLimit80PercentSupported() const {
81 return IsRevisionSupported(5, user_revision); 61 return AudioCommon::IsRevisionSupported(5, user_revision);
82} 62}
83 63
84bool BehaviorInfo::IsAudioRenererProcessingTimeLimit75PercentSupported() const { 64bool BehaviorInfo::IsAudioRenererProcessingTimeLimit75PercentSupported() const {
85 return IsRevisionSupported(4, user_revision); 65 return AudioCommon::IsRevisionSupported(4, user_revision);
86} 66}
87 67
88bool BehaviorInfo::IsAudioRenererProcessingTimeLimit70PercentSupported() const { 68bool BehaviorInfo::IsAudioRenererProcessingTimeLimit70PercentSupported() const {
89 return IsRevisionSupported(1, user_revision); 69 return AudioCommon::IsRevisionSupported(1, user_revision);
90} 70}
91 71
92bool BehaviorInfo::IsElapsedFrameCountSupported() const { 72bool BehaviorInfo::IsElapsedFrameCountSupported() const {
93 return IsRevisionSupported(5, user_revision); 73 return AudioCommon::IsRevisionSupported(5, user_revision);
94} 74}
95 75
96bool BehaviorInfo::IsMemoryPoolForceMappingEnabled() const { 76bool BehaviorInfo::IsMemoryPoolForceMappingEnabled() const {
97 return (flags & 1) != 0; 77 return (flags & 1) != 0;
98} 78}
99 79
80bool BehaviorInfo::IsFlushVoiceWaveBuffersSupported() const {
81 return AudioCommon::IsRevisionSupported(5, user_revision);
82}
83
84bool BehaviorInfo::IsVoicePlayedSampleCountResetAtLoopPointSupported() const {
85 return AudioCommon::IsRevisionSupported(5, user_revision);
86}
87
88bool BehaviorInfo::IsVoicePitchAndSrcSkippedSupported() const {
89 return AudioCommon::IsRevisionSupported(5, user_revision);
90}
91
92bool BehaviorInfo::IsMixInParameterDirtyOnlyUpdateSupported() const {
93 return AudioCommon::IsRevisionSupported(7, user_revision);
94}
95
96bool BehaviorInfo::IsSplitterBugFixed() const {
97 return AudioCommon::IsRevisionSupported(5, user_revision);
98}
99
100void BehaviorInfo::CopyErrorInfo(BehaviorInfo::OutParams& dst) {
101 dst.error_count = static_cast<u32>(error_count);
102 std::memcpy(dst.errors.data(), errors.data(), sizeof(ErrorInfo) * dst.errors.size());
103}
104
100} // namespace AudioCore 105} // namespace AudioCore
diff --git a/src/audio_core/behavior_info.h b/src/audio_core/behavior_info.h
index c5e91ab39..50948e8df 100644
--- a/src/audio_core/behavior_info.h
+++ b/src/audio_core/behavior_info.h
@@ -14,15 +14,37 @@
14namespace AudioCore { 14namespace AudioCore {
15class BehaviorInfo { 15class BehaviorInfo {
16public: 16public:
17 struct ErrorInfo {
18 u32_le result{};
19 INSERT_PADDING_WORDS(1);
20 u64_le result_info{};
21 };
22 static_assert(sizeof(ErrorInfo) == 0x10, "ErrorInfo is an invalid size");
23
24 struct InParams {
25 u32_le revision{};
26 u32_le padding{};
27 u64_le flags{};
28 };
29 static_assert(sizeof(InParams) == 0x10, "InParams is an invalid size");
30
31 struct OutParams {
32 std::array<ErrorInfo, 10> errors{};
33 u32_le error_count{};
34 INSERT_PADDING_BYTES(12);
35 };
36 static_assert(sizeof(OutParams) == 0xb0, "OutParams is an invalid size");
37
17 explicit BehaviorInfo(); 38 explicit BehaviorInfo();
18 ~BehaviorInfo(); 39 ~BehaviorInfo();
19 40
20 bool UpdateInput(const std::vector<u8>& buffer, std::size_t offset);
21 bool UpdateOutput(std::vector<u8>& buffer, std::size_t offset); 41 bool UpdateOutput(std::vector<u8>& buffer, std::size_t offset);
22 42
23 void ClearError(); 43 void ClearError();
24 void UpdateFlags(u64_le dest_flags); 44 void UpdateFlags(u64_le dest_flags);
25 void SetUserRevision(u32_le revision); 45 void SetUserRevision(u32_le revision);
46 u32_le GetUserRevision() const;
47 u32_le GetProcessRevision() const;
26 48
27 bool IsAdpcmLoopContextBugFixed() const; 49 bool IsAdpcmLoopContextBugFixed() const;
28 bool IsSplitterSupported() const; 50 bool IsSplitterSupported() const;
@@ -32,35 +54,19 @@ public:
32 bool IsAudioRenererProcessingTimeLimit70PercentSupported() const; 54 bool IsAudioRenererProcessingTimeLimit70PercentSupported() const;
33 bool IsElapsedFrameCountSupported() const; 55 bool IsElapsedFrameCountSupported() const;
34 bool IsMemoryPoolForceMappingEnabled() const; 56 bool IsMemoryPoolForceMappingEnabled() const;
57 bool IsFlushVoiceWaveBuffersSupported() const;
58 bool IsVoicePlayedSampleCountResetAtLoopPointSupported() const;
59 bool IsVoicePitchAndSrcSkippedSupported() const;
60 bool IsMixInParameterDirtyOnlyUpdateSupported() const;
61 bool IsSplitterBugFixed() const;
62 void CopyErrorInfo(OutParams& dst);
35 63
36private: 64private:
37 u32_le process_revision{}; 65 u32_le process_revision{};
38 u32_le user_revision{}; 66 u32_le user_revision{};
39 u64_le flags{}; 67 u64_le flags{};
40
41 struct ErrorInfo {
42 u32_le result{};
43 INSERT_PADDING_WORDS(1);
44 u64_le result_info{};
45 };
46 static_assert(sizeof(ErrorInfo) == 0x10, "ErrorInfo is an invalid size");
47
48 std::array<ErrorInfo, 10> errors{}; 68 std::array<ErrorInfo, 10> errors{};
49 std::size_t error_count{}; 69 std::size_t error_count{};
50
51 struct InParams {
52 u32_le revision{};
53 u32_le padding{};
54 u64_le flags{};
55 };
56 static_assert(sizeof(InParams) == 0x10, "InParams is an invalid size");
57
58 struct OutParams {
59 std::array<ErrorInfo, 10> errors{};
60 u32_le error_count{};
61 INSERT_PADDING_BYTES(12);
62 };
63 static_assert(sizeof(OutParams) == 0xb0, "OutParams is an invalid size");
64}; 70};
65 71
66} // namespace AudioCore 72} // namespace AudioCore
diff --git a/src/audio_core/command_generator.cpp b/src/audio_core/command_generator.cpp
new file mode 100644
index 000000000..722f9b6c5
--- /dev/null
+++ b/src/audio_core/command_generator.cpp
@@ -0,0 +1,668 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include "audio_core/algorithm/interpolate.h"
6#include "audio_core/command_generator.h"
7#include "audio_core/mix_context.h"
8#include "audio_core/voice_context.h"
9#include "core/memory.h"
10
11namespace AudioCore {
12namespace {
13static constexpr std::size_t MIX_BUFFER_SIZE = 0x3f00;
14static constexpr std::size_t SCALED_MIX_BUFFER_SIZE = MIX_BUFFER_SIZE << 15ULL;
15
16template <std::size_t N>
17void ApplyMix(s32* output, const s32* input, s32 gain, s32 sample_count) {
18 for (s32 i = 0; i < sample_count; i += N) {
19 for (std::size_t j = 0; j < N; j++) {
20 output[i + j] +=
21 static_cast<s32>((static_cast<s64>(input[i + j]) * gain + 0x4000) >> 15);
22 }
23 }
24}
25
26s32 ApplyMixRamp(s32* output, const s32* input, float gain, float delta, s32 sample_count) {
27 s32 x = 0;
28 for (s32 i = 0; i < sample_count; i++) {
29 x = static_cast<s32>(static_cast<float>(input[i]) * gain);
30 output[i] += x;
31 gain += delta;
32 }
33 return x;
34}
35
36void ApplyGain(s32* output, const s32* input, s32 gain, s32 delta, s32 sample_count) {
37 for (s32 i = 0; i < sample_count; i++) {
38 output[i] = static_cast<s32>((static_cast<s64>(input[i]) * gain + 0x4000) >> 15);
39 gain += delta;
40 }
41}
42
43void ApplyGainWithoutDelta(s32* output, const s32* input, s32 gain, s32 sample_count) {
44 for (s32 i = 0; i < sample_count; i++) {
45 output[i] = static_cast<s32>((static_cast<s64>(input[i]) * gain + 0x4000) >> 15);
46 }
47}
48
49} // namespace
50
51CommandGenerator::CommandGenerator(AudioCommon::AudioRendererParameter& worker_params,
52 VoiceContext& voice_context, MixContext& mix_context,
53 SplitterContext& splitter_context, Core::Memory::Memory& memory)
54 : worker_params(worker_params), voice_context(voice_context), mix_context(mix_context),
55 splitter_context(splitter_context), memory(memory),
56 mix_buffer((worker_params.mix_buffer_count + AudioCommon::MAX_CHANNEL_COUNT) *
57 worker_params.sample_count),
58 sample_buffer(MIX_BUFFER_SIZE) {}
59CommandGenerator::~CommandGenerator() = default;
60
61void CommandGenerator::ClearMixBuffers() {
62 std::memset(mix_buffer.data(), 0, mix_buffer.size() * sizeof(s32));
63 std::memset(sample_buffer.data(), 0, sample_buffer.size() * sizeof(s32));
64}
65
66void CommandGenerator::GenerateVoiceCommands() {
67 if (dumping_frame) {
68 LOG_CRITICAL(Audio, "(DSP_TRACE) GenerateVoiceCommands");
69 }
70 // Grab all our voices
71 const auto voice_count = voice_context.GetVoiceCount();
72 for (std::size_t i = 0; i < voice_count; i++) {
73 auto& voice_info = voice_context.GetSortedInfo(i);
74 // Update voices and check if we should queue them
75 if (voice_info.ShouldSkip() || !voice_info.UpdateForCommandGeneration(voice_context)) {
76 continue;
77 }
78
79 // Queue our voice
80 GenerateVoiceCommand(voice_info);
81 }
82 // Update our splitters
83 splitter_context.UpdateInternalState();
84}
85
86void CommandGenerator::GenerateVoiceCommand(ServerVoiceInfo& voice_info) {
87 auto& in_params = voice_info.GetInParams();
88 const auto channel_count = in_params.channel_count;
89
90 for (s32 channel = 0; channel < channel_count; channel++) {
91 const auto resource_id = in_params.voice_channel_resource_id[channel];
92 auto& dsp_state = voice_context.GetDspSharedState(resource_id);
93 auto& channel_resource = voice_context.GetChannelResource(resource_id);
94
95 // Decode our samples for our channel
96 GenerateDataSourceCommand(voice_info, dsp_state, channel);
97
98 if (in_params.should_depop) {
99 in_params.last_volume = 0.0f;
100 } else if (in_params.splitter_info_id != AudioCommon::NO_SPLITTER ||
101 in_params.mix_id != AudioCommon::NO_MIX) {
102 // Apply a biquad filter if needed
103 GenerateBiquadFilterCommandForVoice(voice_info, dsp_state,
104 worker_params.mix_buffer_count, channel);
105 // Base voice volume ramping
106 GenerateVolumeRampCommand(in_params.last_volume, in_params.volume, channel,
107 in_params.node_id);
108 in_params.last_volume = in_params.volume;
109
110 if (in_params.mix_id != AudioCommon::NO_MIX) {
111 // If we're using a mix id
112 auto& mix_info = mix_context.GetInfo(in_params.mix_id);
113 const auto& dest_mix_params = mix_info.GetInParams();
114
115 // Voice Mixing
116 GenerateVoiceMixCommand(
117 channel_resource.GetCurrentMixVolume(), channel_resource.GetLastMixVolume(),
118 dsp_state, dest_mix_params.buffer_offset, dest_mix_params.buffer_count,
119 worker_params.mix_buffer_count + channel, in_params.node_id);
120
121 // Update last mix volumes
122 channel_resource.UpdateLastMixVolumes();
123 } else if (in_params.splitter_info_id != AudioCommon::NO_SPLITTER) {
124 s32 base = channel;
125 while (auto* destination_data =
126 GetDestinationData(in_params.splitter_info_id, base)) {
127 base += channel_count;
128
129 if (!destination_data->IsConfigured()) {
130 continue;
131 }
132 if (destination_data->GetMixId() >= mix_context.GetCount()) {
133 continue;
134 }
135
136 const auto& mix_info = mix_context.GetInfo(destination_data->GetMixId());
137 const auto& dest_mix_params = mix_info.GetInParams();
138 GenerateVoiceMixCommand(
139 destination_data->CurrentMixVolumes(), destination_data->LastMixVolumes(),
140 dsp_state, dest_mix_params.buffer_offset, dest_mix_params.buffer_count,
141 worker_params.mix_buffer_count + channel, in_params.node_id);
142 destination_data->MarkDirty();
143 }
144 }
145 }
146
147 // Update biquad filter enabled states
148 for (std::size_t i = 0; i < AudioCommon::MAX_BIQUAD_FILTERS; i++) {
149 in_params.was_biquad_filter_enabled[i] = in_params.biquad_filter[i].enabled;
150 }
151 }
152}
153
154void CommandGenerator::GenerateSubMixCommands() {
155 const auto mix_count = mix_context.GetCount();
156 for (std::size_t i = 0; i < mix_count; i++) {
157 auto& mix_info = mix_context.GetSortedInfo(i);
158 const auto& in_params = mix_info.GetInParams();
159 if (!in_params.in_use || in_params.mix_id == AudioCommon::FINAL_MIX) {
160 continue;
161 }
162 GenerateSubMixCommand(mix_info);
163 }
164}
165
166void CommandGenerator::GenerateFinalMixCommands() {
167 GenerateFinalMixCommand();
168}
169
170void CommandGenerator::PreCommand() {
171 if (dumping_frame) {
172 for (std::size_t i = 0; i < splitter_context.GetInfoCount(); i++) {
173 const auto& base = splitter_context.GetInfo(i);
174 std::string graph = fmt::format("b[{}]", i);
175 auto* head = base.GetHead();
176 while (head != nullptr) {
177 graph += fmt::format("->{}", head->GetMixId());
178 head = head->GetNextDestination();
179 }
180 LOG_CRITICAL(Audio, "(DSP_TRACE) SplitterGraph splitter_info={}, {}", i, graph);
181 }
182 }
183}
184
185void CommandGenerator::PostCommand() {
186 if (dumping_frame) {
187 dumping_frame = false;
188 }
189}
190
191void CommandGenerator::GenerateDataSourceCommand(ServerVoiceInfo& voice_info, VoiceState& dsp_state,
192 s32 channel) {
193 auto& in_params = voice_info.GetInParams();
194 const auto depop = in_params.should_depop;
195
196 if (in_params.mix_id != AudioCommon::NO_MIX) {
197 auto& mix_info = mix_context.GetInfo(in_params.mix_id);
198 // mix_info.
199 // TODO(ogniK): Depop to destination mix
200 } else if (in_params.splitter_info_id != AudioCommon::NO_SPLITTER) {
201 // TODO(ogniK): Depop to splitter
202 }
203
204 if (!depop) {
205 switch (in_params.sample_format) {
206 case SampleFormat::Pcm16:
207 DecodeFromWaveBuffers(voice_info, GetChannelMixBuffer(channel), dsp_state, channel,
208 worker_params.sample_rate, worker_params.sample_count,
209 in_params.node_id);
210 break;
211 case SampleFormat::Adpcm:
212 ASSERT(channel == 0 && in_params.channel_count == 1);
213 DecodeFromWaveBuffers(voice_info, GetChannelMixBuffer(0), dsp_state, 0,
214 worker_params.sample_rate, worker_params.sample_count,
215 in_params.node_id);
216 break;
217 default:
218 UNREACHABLE_MSG("Unimplemented sample format={}", in_params.sample_format);
219 }
220 }
221}
222
223void CommandGenerator::GenerateBiquadFilterCommandForVoice(ServerVoiceInfo& voice_info,
224 VoiceState& dsp_state,
225 s32 mix_buffer_count, s32 channel) {
226 for (std::size_t i = 0; i < AudioCommon::MAX_BIQUAD_FILTERS; i++) {
227 const auto& in_params = voice_info.GetInParams();
228 auto& biquad_filter = in_params.biquad_filter[i];
229 // Check if biquad filter is actually used
230 if (!biquad_filter.enabled) {
231 continue;
232 }
233
234 // Reinitialize our biquad filter state if it was enabled previously
235 if (!in_params.was_biquad_filter_enabled[i]) {
236 std::memset(dsp_state.biquad_filter_state.data(), 0,
237 dsp_state.biquad_filter_state.size() * sizeof(s64));
238 }
239
240 // Generate biquad filter
241 GenerateBiquadFilterCommand(mix_buffer_count, biquad_filter, dsp_state.biquad_filter_state,
242 mix_buffer_count + channel, mix_buffer_count + channel,
243 worker_params.sample_count, voice_info.GetInParams().node_id);
244 }
245}
246
247void AudioCore::CommandGenerator::GenerateBiquadFilterCommand(
248 s32 mix_buffer, const BiquadFilterParameter& params, std::array<s64, 2>& state,
249 std::size_t input_offset, std::size_t output_offset, s32 sample_count, s32 node_id) {
250 if (dumping_frame) {
251 LOG_CRITICAL(Audio,
252 "(DSP_TRACE) GenerateBiquadFilterCommand node_id={}, "
253 "input_mix_buffer={}, output_mix_buffer={}",
254 node_id, input_offset, output_offset);
255 }
256 const auto* input = GetMixBuffer(input_offset);
257 auto* output = GetMixBuffer(output_offset);
258
259 // Biquad filter parameters
260 const auto n0 = params.numerator[0];
261 const auto n1 = params.numerator[1];
262 const auto n2 = params.numerator[2];
263 const auto d0 = params.denominator[0];
264 const auto d1 = params.denominator[1];
265
266 // Biquad filter states
267 auto s0 = state[0];
268 auto s1 = state[1];
269
270 constexpr s64 MIN = std::numeric_limits<int32_t>::min();
271 constexpr s64 MAX = std::numeric_limits<int32_t>::max();
272
273 for (int i = 0; i < sample_count; ++i) {
274 const auto sample = static_cast<int64_t>(input[i]);
275 const auto f = (sample * n0 + s0 + 0x4000) >> 15;
276 const auto y = std::clamp(f, MIN, MAX);
277 s0 = sample * n1 + y * d0 + s1;
278 s1 = sample * n2 + y * d1;
279 output[i] = static_cast<s32>(y);
280 }
281
282 state[0] = s0;
283 state[1] = s1;
284}
285
286ServerSplitterDestinationData* CommandGenerator::GetDestinationData(s32 splitter_id, s32 index) {
287 if (splitter_id == AudioCommon::NO_SPLITTER) {
288 return nullptr;
289 }
290 return splitter_context.GetDestinationData(splitter_id, index);
291}
292
293void CommandGenerator::GenerateVolumeRampCommand(float last_volume, float current_volume,
294 s32 channel, s32 node_id) {
295 const auto last = static_cast<s32>(last_volume * 32768.0f);
296 const auto current = static_cast<s32>(current_volume * 32768.0f);
297 const auto delta = static_cast<s32>((static_cast<float>(current) - static_cast<float>(last)) /
298 static_cast<float>(worker_params.sample_count));
299
300 if (dumping_frame) {
301 LOG_CRITICAL(Audio,
302 "(DSP_TRACE) GenerateVolumeRampCommand node_id={}, input={}, output={}, "
303 "last_volume={}, current_volume={}",
304 node_id, GetMixChannelBufferOffset(channel),
305 GetMixChannelBufferOffset(channel), last_volume, current_volume);
306 }
307 // Apply generic gain on samples
308 ApplyGain(GetChannelMixBuffer(channel), GetChannelMixBuffer(channel), last, delta,
309 worker_params.sample_count);
310}
311
312void CommandGenerator::GenerateVoiceMixCommand(const MixVolumeBuffer& mix_volumes,
313 const MixVolumeBuffer& last_mix_volumes,
314 VoiceState& dsp_state, s32 mix_buffer_offset,
315 s32 mix_buffer_count, s32 voice_index, s32 node_id) {
316 // Loop all our mix buffers
317 for (s32 i = 0; i < mix_buffer_count; i++) {
318 if (last_mix_volumes[i] != 0.0f || mix_volumes[i] != 0.0f) {
319 const auto delta = static_cast<float>((mix_volumes[i] - last_mix_volumes[i])) /
320 static_cast<float>(worker_params.sample_count);
321
322 if (dumping_frame) {
323 LOG_CRITICAL(Audio,
324 "(DSP_TRACE) GenerateVoiceMixCommand node_id={}, input={}, "
325 "output={}, last_volume={}, current_volume={}",
326 node_id, voice_index, mix_buffer_offset + i, last_mix_volumes[i],
327 mix_volumes[i]);
328 }
329
330 dsp_state.previous_samples[i] =
331 ApplyMixRamp(GetMixBuffer(mix_buffer_offset + i), GetMixBuffer(voice_index),
332 last_mix_volumes[i], delta, worker_params.sample_count);
333 } else {
334 dsp_state.previous_samples[i] = 0;
335 }
336 }
337}
338
339void CommandGenerator::GenerateSubMixCommand(ServerMixInfo& mix_info) {
340 if (dumping_frame) {
341 LOG_CRITICAL(Audio, "(DSP_TRACE) GenerateSubMixCommand");
342 }
343 // TODO(ogniK): Depop
344 // TODO(ogniK): Effects
345 GenerateMixCommands(mix_info);
346}
347
348void CommandGenerator::GenerateMixCommands(ServerMixInfo& mix_info) {
349 if (!mix_info.HasAnyConnection()) {
350 return;
351 }
352 const auto& in_params = mix_info.GetInParams();
353 if (in_params.dest_mix_id != AudioCommon::NO_MIX) {
354 const auto& dest_mix = mix_context.GetInfo(in_params.dest_mix_id);
355 const auto& dest_in_params = dest_mix.GetInParams();
356
357 const auto buffer_count = in_params.buffer_count;
358
359 for (s32 i = 0; i < buffer_count; i++) {
360 for (s32 j = 0; j < dest_in_params.buffer_count; j++) {
361 const auto mixed_volume = in_params.volume * in_params.mix_volume[i][j];
362 if (mixed_volume != 0.0f) {
363 GenerateMixCommand(dest_in_params.buffer_offset + j,
364 in_params.buffer_offset + i, mixed_volume,
365 in_params.node_id);
366 }
367 }
368 }
369 } else if (in_params.splitter_id != AudioCommon::NO_SPLITTER) {
370 s32 base{};
371 while (const auto* destination_data = GetDestinationData(in_params.splitter_id, base++)) {
372 if (!destination_data->IsConfigured()) {
373 continue;
374 }
375
376 const auto& dest_mix = mix_context.GetInfo(destination_data->GetMixId());
377 const auto& dest_in_params = dest_mix.GetInParams();
378 const auto mix_index = (base - 1) % in_params.buffer_count + in_params.buffer_offset;
379 for (std::size_t i = 0; i < dest_in_params.buffer_count; i++) {
380 const auto mixed_volume = in_params.volume * destination_data->GetMixVolume(i);
381 if (mixed_volume != 0.0f) {
382 GenerateMixCommand(dest_in_params.buffer_offset + i, mix_index, mixed_volume,
383 in_params.node_id);
384 }
385 }
386 }
387 }
388}
389
390void CommandGenerator::GenerateMixCommand(std::size_t output_offset, std::size_t input_offset,
391 float volume, s32 node_id) {
392
393 if (dumping_frame) {
394 LOG_CRITICAL(Audio,
395 "(DSP_TRACE) GenerateMixCommand node_id={}, input={}, output={}, volume={}",
396 node_id, input_offset, output_offset, volume);
397 }
398
399 auto* output = GetMixBuffer(output_offset);
400 const auto* input = GetMixBuffer(input_offset);
401
402 const s32 gain = static_cast<s32>(volume * 32768.0f);
403 // Mix with loop unrolling
404 if (worker_params.sample_count % 4 == 0) {
405 ApplyMix<4>(output, input, gain, worker_params.sample_count);
406 } else if (worker_params.sample_count % 2 == 0) {
407 ApplyMix<2>(output, input, gain, worker_params.sample_count);
408 } else {
409 ApplyMix<1>(output, input, gain, worker_params.sample_count);
410 }
411}
412
413void CommandGenerator::GenerateFinalMixCommand() {
414 if (dumping_frame) {
415 LOG_CRITICAL(Audio, "(DSP_TRACE) GenerateFinalMixCommand");
416 }
417 // TODO(ogniK): Depop
418 // TODO(ogniK): Effects
419 auto& mix_info = mix_context.GetFinalMixInfo();
420 const auto in_params = mix_info.GetInParams();
421 for (s32 i = 0; i < in_params.buffer_count; i++) {
422 const s32 gain = static_cast<s32>(in_params.volume * 32768.0f);
423 if (dumping_frame) {
424 LOG_CRITICAL(
425 Audio,
426 "(DSP_TRACE) ApplyGainWithoutDelta node_id={}, input={}, output={}, volume={}",
427 in_params.node_id, in_params.buffer_offset + i, in_params.buffer_offset + i,
428 in_params.volume);
429 }
430 ApplyGainWithoutDelta(GetMixBuffer(in_params.buffer_offset + i),
431 GetMixBuffer(in_params.buffer_offset + i), gain,
432 worker_params.sample_count);
433 }
434}
435
436s32 CommandGenerator::DecodePcm16(ServerVoiceInfo& voice_info, VoiceState& dsp_state,
437 s32 sample_count, s32 channel, std::size_t mix_offset) {
438 auto& in_params = voice_info.GetInParams();
439 const auto& wave_buffer = in_params.wave_buffer[dsp_state.wave_buffer_index];
440 if (wave_buffer.buffer_address == 0) {
441 return 0;
442 }
443 if (wave_buffer.buffer_size == 0) {
444 return 0;
445 }
446 if (wave_buffer.end_sample_offset < wave_buffer.start_sample_offset) {
447 return 0;
448 }
449 const auto samples_remaining =
450 (wave_buffer.end_sample_offset - wave_buffer.start_sample_offset) - dsp_state.offset;
451 const auto start_offset =
452 ((wave_buffer.start_sample_offset + dsp_state.offset) * in_params.channel_count) *
453 sizeof(s16);
454 const auto buffer_pos = wave_buffer.buffer_address + start_offset;
455 const auto samples_processed = std::min(sample_count, samples_remaining);
456
457 if (in_params.channel_count == 1) {
458 std::vector<s16> buffer(samples_processed);
459 memory.ReadBlock(buffer_pos, buffer.data(), buffer.size() * sizeof(s16));
460 for (std::size_t i = 0; i < buffer.size(); i++) {
461 sample_buffer[mix_offset + i] = buffer[i];
462 }
463 } else {
464 const auto channel_count = in_params.channel_count;
465 std::vector<s16> buffer(samples_processed * channel_count);
466 memory.ReadBlock(buffer_pos, buffer.data(), buffer.size() * sizeof(s16));
467
468 for (std::size_t i = 0; i < samples_processed; i++) {
469 sample_buffer[mix_offset + i] = buffer[i * channel_count + channel];
470 }
471 }
472
473 return samples_processed;
474}
475s32 CommandGenerator::DecodeAdpcm(ServerVoiceInfo& voice_info, VoiceState& dsp_state,
476 s32 sample_count, s32 channel, std::size_t mix_offset) {
477 auto& in_params = voice_info.GetInParams();
478 const auto& wave_buffer = in_params.wave_buffer[dsp_state.wave_buffer_index];
479 if (wave_buffer.buffer_address == 0) {
480 return 0;
481 }
482 if (wave_buffer.buffer_size == 0) {
483 return 0;
484 }
485 if (wave_buffer.end_sample_offset < wave_buffer.start_sample_offset) {
486 return 0;
487 }
488
489 const auto samples_remaining =
490 (wave_buffer.end_sample_offset - wave_buffer.start_sample_offset) - dsp_state.offset;
491 const auto start_offset =
492 ((wave_buffer.start_sample_offset + dsp_state.offset) * in_params.channel_count);
493 const auto buffer_pos = wave_buffer.buffer_address + start_offset;
494
495 const auto samples_processed = std::min(sample_count, samples_remaining);
496
497 if (start_offset > dsp_state.adpcm_samples.size()) {
498 dsp_state.adpcm_samples.clear();
499 }
500
501 // TODO(ogniK): Proper ADPCM streaming
502 if (dsp_state.adpcm_samples.empty()) {
503 Codec::ADPCM_Coeff coeffs;
504 memory.ReadBlock(in_params.additional_params_address, coeffs.data(),
505 sizeof(Codec::ADPCM_Coeff));
506 std::vector<u8> buffer(wave_buffer.buffer_size);
507 memory.ReadBlock(wave_buffer.buffer_address, buffer.data(), buffer.size());
508 dsp_state.adpcm_samples =
509 std::move(Codec::DecodeADPCM(buffer.data(), buffer.size(), coeffs, dsp_state.context));
510 }
511
512 for (std::size_t i = 0; i < samples_processed; i++) {
513 const auto sample_offset = i + start_offset;
514 sample_buffer[mix_offset + i] =
515 dsp_state.adpcm_samples[sample_offset * in_params.channel_count + channel];
516 }
517
518 return samples_processed;
519}
520
521s32* CommandGenerator::GetMixBuffer(std::size_t index) {
522 return mix_buffer.data() + (index * worker_params.sample_count);
523}
524
525const s32* CommandGenerator::GetMixBuffer(std::size_t index) const {
526 return mix_buffer.data() + (index * worker_params.sample_count);
527}
528
529std::size_t CommandGenerator::GetMixChannelBufferOffset(s32 channel) const {
530 return worker_params.mix_buffer_count + channel;
531}
532
533s32* CommandGenerator::GetChannelMixBuffer(s32 channel) {
534 return GetMixBuffer(worker_params.mix_buffer_count + channel);
535}
536
537const s32* CommandGenerator::GetChannelMixBuffer(s32 channel) const {
538 return GetMixBuffer(worker_params.mix_buffer_count + channel);
539}
540
541void CommandGenerator::DecodeFromWaveBuffers(ServerVoiceInfo& voice_info, s32* output,
542 VoiceState& dsp_state, s32 channel,
543 s32 target_sample_rate, s32 sample_count,
544 s32 node_id) {
545 auto& in_params = voice_info.GetInParams();
546 if (dumping_frame) {
547 LOG_CRITICAL(Audio,
548 "(DSP_TRACE) DecodeFromWaveBuffers, node_id={}, channel={}, "
549 "format={}, sample_count={}, sample_rate={}, mix_id={}, splitter_id={}",
550 node_id, channel, in_params.sample_format, sample_count, in_params.sample_rate,
551 in_params.mix_id, in_params.splitter_info_id);
552 }
553 ASSERT_OR_EXECUTE(output != nullptr, { return; });
554
555 const auto resample_rate = static_cast<s32>(
556 static_cast<float>(in_params.sample_rate) / static_cast<float>(target_sample_rate) *
557 static_cast<float>(static_cast<s32>(in_params.pitch * 32768.0f)));
558 auto* output_base = output;
559 if ((dsp_state.fraction + sample_count * resample_rate) > (SCALED_MIX_BUFFER_SIZE - 4ULL)) {
560 return;
561 }
562
563 auto min_required_samples =
564 std::min(static_cast<s32>(SCALED_MIX_BUFFER_SIZE) - dsp_state.fraction, resample_rate);
565 if (min_required_samples >= sample_count) {
566 min_required_samples = sample_count;
567 }
568
569 std::size_t temp_mix_offset{};
570 bool is_buffer_completed{false};
571 auto samples_remaining = sample_count;
572 while (samples_remaining > 0 && !is_buffer_completed) {
573 const auto samples_to_output = std::min(samples_remaining, min_required_samples);
574 const auto samples_to_read = (samples_to_output * resample_rate + dsp_state.fraction) >> 15;
575
576 if (!in_params.behavior_flags.is_pitch_and_src_skipped) {
577 // Append sample histtory for resampler
578 for (std::size_t i = 0; i < AudioCommon::MAX_SAMPLE_HISTORY; i++) {
579 sample_buffer[temp_mix_offset + i] = dsp_state.sample_history[i];
580 }
581 temp_mix_offset += 4;
582 }
583
584 s32 samples_read{};
585 while (samples_read < samples_to_read) {
586 const auto& wave_buffer = in_params.wave_buffer[dsp_state.wave_buffer_index];
587 // No more data can be read
588 if (!dsp_state.is_wave_buffer_valid[dsp_state.wave_buffer_index]) {
589 is_buffer_completed = true;
590 break;
591 }
592
593 if (in_params.sample_format == SampleFormat::Adpcm && dsp_state.offset == 0 &&
594 wave_buffer.context_address != 0 && wave_buffer.context_size != 0) {
595 // TODO(ogniK): ADPCM loop context
596 }
597
598 s32 samples_decoded{0};
599 switch (in_params.sample_format) {
600 case SampleFormat::Pcm16:
601 samples_decoded = DecodePcm16(voice_info, dsp_state, samples_to_read - samples_read,
602 channel, temp_mix_offset);
603 break;
604 case SampleFormat::Adpcm:
605 samples_decoded = DecodeAdpcm(voice_info, dsp_state, samples_to_read - samples_read,
606 channel, temp_mix_offset);
607 break;
608 default:
609 UNREACHABLE_MSG("Unimplemented sample format={}", in_params.sample_format);
610 }
611
612 temp_mix_offset += samples_decoded;
613 samples_read += samples_decoded;
614 dsp_state.offset += samples_decoded;
615 dsp_state.played_sample_count += samples_decoded;
616
617 if (dsp_state.offset >=
618 (wave_buffer.end_sample_offset - wave_buffer.start_sample_offset) ||
619 samples_decoded == 0) {
620 // Reset our sample offset
621 dsp_state.offset = 0;
622 if (wave_buffer.is_looping) {
623 if (samples_decoded == 0) {
624 // End of our buffer
625 is_buffer_completed = true;
626 break;
627 }
628
629 if (in_params.behavior_flags.is_played_samples_reset_at_loop_point.Value()) {
630 dsp_state.played_sample_count = 0;
631 }
632 } else {
633 if (in_params.sample_format == SampleFormat::Adpcm) {
634 // TODO(ogniK): Remove this when ADPCM streaming implemented
635 dsp_state.adpcm_samples.clear();
636 }
637
638 // Update our wave buffer states
639 dsp_state.is_wave_buffer_valid[dsp_state.wave_buffer_index] = false;
640 dsp_state.wave_buffer_consumed++;
641 dsp_state.wave_buffer_index =
642 (dsp_state.wave_buffer_index + 1) % AudioCommon::MAX_WAVE_BUFFERS;
643 if (wave_buffer.end_of_stream) {
644 dsp_state.played_sample_count = 0;
645 }
646 }
647 }
648 }
649
650 if (in_params.behavior_flags.is_pitch_and_src_skipped.Value()) {
651 // No need to resample
652 memcpy(output, sample_buffer.data(), samples_read * sizeof(s32));
653 } else {
654 std::memset(sample_buffer.data() + temp_mix_offset, 0,
655 sizeof(s32) * (samples_to_read - samples_read));
656 AudioCore::Resample(output, sample_buffer.data(), resample_rate, dsp_state.fraction,
657 samples_to_output);
658 // Resample
659 for (std::size_t i = 0; i < AudioCommon::MAX_SAMPLE_HISTORY; i++) {
660 dsp_state.sample_history[i] = sample_buffer[samples_to_read + i];
661 }
662 }
663 output += samples_to_output;
664 samples_remaining -= samples_to_output;
665 }
666}
667
668} // namespace AudioCore
diff --git a/src/audio_core/command_generator.h b/src/audio_core/command_generator.h
new file mode 100644
index 000000000..e0d7510fc
--- /dev/null
+++ b/src/audio_core/command_generator.h
@@ -0,0 +1,83 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <array>
8#include "audio_core/common.h"
9#include "audio_core/voice_context.h"
10#include "common/common_funcs.h"
11#include "common/common_types.h"
12
13namespace Core::Memory {
14class Memory;
15}
16
17namespace AudioCore {
18class MixContext;
19class SplitterContext;
20class ServerSplitterDestinationData;
21class ServerMixInfo;
22using MixVolumeBuffer = std::array<float, AudioCommon::MAX_MIX_BUFFERS>;
23class CommandGenerator {
24public:
25 explicit CommandGenerator(AudioCommon::AudioRendererParameter& worker_params,
26 VoiceContext& voice_context, MixContext& mix_context,
27 SplitterContext& splitter_context, Core::Memory::Memory& memory);
28 ~CommandGenerator();
29
30 void ClearMixBuffers();
31 void GenerateVoiceCommands();
32 void GenerateVoiceCommand(ServerVoiceInfo& voice_info);
33 void GenerateSubMixCommands();
34 void GenerateFinalMixCommands();
35 void PreCommand();
36 void PostCommand();
37
38 s32* GetChannelMixBuffer(s32 channel);
39 const s32* GetChannelMixBuffer(s32 channel) const;
40 s32* GetMixBuffer(std::size_t index);
41 const s32* GetMixBuffer(std::size_t index) const;
42 std::size_t GetMixChannelBufferOffset(s32 channel) const;
43
44private:
45 void GenerateDataSourceCommand(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 channel);
46 void GenerateBiquadFilterCommandForVoice(ServerVoiceInfo& voice_info, VoiceState& dsp_state,
47 s32 mix_buffer_count, s32 channel);
48 void GenerateVolumeRampCommand(float last_volume, float current_volume, s32 channel,
49 s32 node_id);
50 void GenerateVoiceMixCommand(const MixVolumeBuffer& mix_volumes,
51 const MixVolumeBuffer& last_mix_volumes, VoiceState& dsp_state,
52 s32 mix_buffer_offset, s32 mix_buffer_count, s32 voice_index,
53 s32 node_id);
54 void GenerateSubMixCommand(ServerMixInfo& mix_info);
55 void GenerateMixCommands(ServerMixInfo& mix_info);
56 void GenerateMixCommand(std::size_t output_offset, std::size_t input_offset, float volume,
57 s32 node_id);
58 void GenerateFinalMixCommand();
59 void GenerateBiquadFilterCommand(s32 mix_buffer, const BiquadFilterParameter& params,
60 std::array<s64, 2>& state, std::size_t input_offset,
61 std::size_t output_offset, s32 sample_count, s32 node_id);
62 void GenerateDepopPrepareCommand(VoiceState& dsp_state);
63 ServerSplitterDestinationData* GetDestinationData(s32 splitter_id, s32 index);
64
65 // DSP Code
66 s32 DecodePcm16(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 sample_count,
67 s32 channel, std::size_t mix_offset);
68 s32 DecodeAdpcm(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 sample_count,
69 s32 channel, std::size_t mix_offset);
70 void DecodeFromWaveBuffers(ServerVoiceInfo& voice_info, s32* output, VoiceState& dsp_state,
71 s32 channel, s32 target_sample_rate, s32 sample_count, s32 node_id);
72 void Resample(s32* output, s32* input, s32 pitch, s32& fraction, s32 sample_count);
73
74 AudioCommon::AudioRendererParameter& worker_params;
75 VoiceContext& voice_context;
76 MixContext& mix_context;
77 SplitterContext& splitter_context;
78 Core::Memory::Memory& memory;
79 std::vector<s32> mix_buffer{};
80 std::vector<s32> sample_buffer{};
81 bool dumping_frame{false};
82};
83} // namespace AudioCore
diff --git a/src/audio_core/common.h b/src/audio_core/common.h
index 7bb145c53..0731d3eb3 100644
--- a/src/audio_core/common.h
+++ b/src/audio_core/common.h
@@ -8,13 +8,29 @@
8#include "common/swap.h" 8#include "common/swap.h"
9#include "core/hle/result.h" 9#include "core/hle/result.h"
10 10
11namespace AudioCore { 11namespace AudioCommon {
12namespace Audren { 12namespace Audren {
13constexpr ResultCode ERR_INVALID_PARAMETERS{ErrorModule::Audio, 41}; 13constexpr ResultCode ERR_INVALID_PARAMETERS{ErrorModule::Audio, 41};
14} 14constexpr ResultCode ERR_SPLITTER_SORT_FAILED{ErrorModule::Audio, 43};
15} // namespace Audren
15 16
16constexpr u32_le CURRENT_PROCESS_REVISION = Common::MakeMagic('R', 'E', 'V', '8'); 17constexpr u32_le CURRENT_PROCESS_REVISION = Common::MakeMagic('R', 'E', 'V', '8');
17constexpr std::size_t MAX_MIX_BUFFERS = 24; 18constexpr std::size_t MAX_MIX_BUFFERS = 24;
19constexpr std::size_t MAX_BIQUAD_FILTERS = 2;
20constexpr std::size_t MAX_CHANNEL_COUNT = 6;
21constexpr std::size_t MAX_WAVE_BUFFERS = 4;
22constexpr std::size_t MAX_SAMPLE_HISTORY = 4;
23constexpr u32 STREAM_SAMPLE_RATE = 48000;
24constexpr u32 STREAM_NUM_CHANNELS = 6;
25constexpr s32 NO_SPLITTER = -1;
26constexpr s32 NO_MIX = 0x7fffffff;
27constexpr s32 NO_FINAL_MIX = std::numeric_limits<s32>::min();
28constexpr s32 FINAL_MIX = 0;
29constexpr std::size_t TEMP_MIX_BASE_SIZE = 0x3f00; // TODO(ogniK): Work out this constant
30// Any size checks seem to take the sample history into account
31// and our const ends up being 0x3f04, the 4 bytes are most
32// likely the sample history
33constexpr std::size_t TOTAL_TEMP_MIX_SIZE = TEMP_MIX_BASE_SIZE + AudioCommon::MAX_SAMPLE_HISTORY;
18 34
19static constexpr u32 VersionFromRevision(u32_le rev) { 35static constexpr u32 VersionFromRevision(u32_le rev) {
20 // "REV7" -> 7 36 // "REV7" -> 7
@@ -45,4 +61,46 @@ static constexpr bool CanConsumeBuffer(std::size_t size, std::size_t offset, std
45 return true; 61 return true;
46} 62}
47 63
48} // namespace AudioCore 64struct UpdateDataSizes {
65 u32_le behavior{};
66 u32_le memory_pool{};
67 u32_le voice{};
68 u32_le voice_channel_resource{};
69 u32_le effect{};
70 u32_le mixer{};
71 u32_le sink{};
72 u32_le performance{};
73 u32_le splitter{};
74 u32_le render_info{};
75 INSERT_PADDING_WORDS(4);
76};
77static_assert(sizeof(UpdateDataSizes) == 0x38, "UpdateDataSizes is an invalid size");
78
79struct UpdateDataHeader {
80 u32_le revision{};
81 UpdateDataSizes size{};
82 u32_le total_size{};
83};
84static_assert(sizeof(UpdateDataHeader) == 0x40, "UpdateDataHeader is an invalid size");
85
86struct AudioRendererParameter {
87 u32_le sample_rate;
88 u32_le sample_count;
89 u32_le mix_buffer_count;
90 u32_le submix_count;
91 u32_le voice_count;
92 u32_le sink_count;
93 u32_le effect_count;
94 u32_le performance_frame_count;
95 u8 is_voice_drop_enabled;
96 u8 unknown_21;
97 u8 unknown_22;
98 u8 execution_mode;
99 u32_le splitter_count;
100 u32_le num_splitter_send_channels;
101 u32_le unknown_30;
102 u32_le revision;
103};
104static_assert(sizeof(AudioRendererParameter) == 52, "AudioRendererParameter is an invalid size");
105
106} // namespace AudioCommon
diff --git a/src/audio_core/cubeb_sink.cpp b/src/audio_core/cubeb_sink.cpp
index 41bf5cd4d..cbd6c56da 100644
--- a/src/audio_core/cubeb_sink.cpp
+++ b/src/audio_core/cubeb_sink.cpp
@@ -23,14 +23,24 @@ class CubebSinkStream final : public SinkStream {
23public: 23public:
24 CubebSinkStream(cubeb* ctx, u32 sample_rate, u32 num_channels_, cubeb_devid output_device, 24 CubebSinkStream(cubeb* ctx, u32 sample_rate, u32 num_channels_, cubeb_devid output_device,
25 const std::string& name) 25 const std::string& name)
26 : ctx{ctx}, num_channels{std::min(num_channels_, 2u)}, time_stretch{sample_rate, 26 : ctx{ctx}, num_channels{std::min(num_channels_, 6u)}, time_stretch{sample_rate,
27 num_channels} { 27 num_channels} {
28 28
29 cubeb_stream_params params{}; 29 cubeb_stream_params params{};
30 params.rate = sample_rate; 30 params.rate = sample_rate;
31 params.channels = num_channels; 31 params.channels = num_channels;
32 params.format = CUBEB_SAMPLE_S16NE; 32 params.format = CUBEB_SAMPLE_S16NE;
33 params.layout = num_channels == 1 ? CUBEB_LAYOUT_MONO : CUBEB_LAYOUT_STEREO; 33 switch (num_channels) {
34 case 1:
35 params.layout = CUBEB_LAYOUT_MONO;
36 break;
37 case 2:
38 params.layout = CUBEB_LAYOUT_STEREO;
39 break;
40 case 6:
41 params.layout = CUBEB_LAYOUT_3F2_LFE;
42 break;
43 }
34 44
35 u32 minimum_latency{}; 45 u32 minimum_latency{};
36 if (cubeb_get_min_latency(ctx, &params, &minimum_latency) != CUBEB_OK) { 46 if (cubeb_get_min_latency(ctx, &params, &minimum_latency) != CUBEB_OK) {
diff --git a/src/audio_core/effect_context.cpp b/src/audio_core/effect_context.cpp
new file mode 100644
index 000000000..c42e71c1c
--- /dev/null
+++ b/src/audio_core/effect_context.cpp
@@ -0,0 +1,39 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include "audio_core/effect_context.h"
6
7namespace AudioCore {
8EffectContext::EffectContext(std::size_t effect_count) : effect_count(effect_count) {
9 for (std::size_t i = 0; i < effect_count; i++) {
10 effects.push_back(std::make_unique<EffectStubbed>());
11 }
12}
13EffectContext::~EffectContext() = default;
14
15std::size_t EffectContext::GetCount() const {
16 return effect_count;
17}
18
19EffectBase* EffectContext::GetInfo(std::size_t i) {
20 return effects.at(i).get();
21}
22
23EffectStubbed::EffectStubbed() : EffectBase::EffectBase() {}
24EffectStubbed::~EffectStubbed() = default;
25
26void EffectStubbed::Update(EffectInfo::InParams& in_params) {
27 if (in_params.is_new) {
28 usage = UsageStatus::New;
29 }
30}
31
32EffectBase::EffectBase() = default;
33EffectBase::~EffectBase() = default;
34
35UsageStatus EffectBase::GetUsage() const {
36 return usage;
37}
38
39} // namespace AudioCore
diff --git a/src/audio_core/effect_context.h b/src/audio_core/effect_context.h
new file mode 100644
index 000000000..09aedf385
--- /dev/null
+++ b/src/audio_core/effect_context.h
@@ -0,0 +1,114 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <array>
8#include <memory>
9#include <vector>
10#include "audio_core/common.h"
11#include "common/common_funcs.h"
12#include "common/common_types.h"
13#include "common/swap.h"
14
15namespace AudioCore {
16enum class EffectType : u8 {
17 Invalid = 0,
18 BufferMixer = 1,
19 Aux = 2,
20 Delay = 3,
21 Reverb = 4,
22 I3dl2Reverb = 5,
23 BiquadFilter = 6,
24};
25
26enum class UsageStatus : u8 {
27 Invalid = 0,
28 New = 1,
29 Initialized = 2,
30 Used = 3,
31 Removed = 4,
32};
33
34struct BufferMixerParams {
35 std::array<s8, AudioCommon::MAX_MIX_BUFFERS> input{};
36 std::array<s8, AudioCommon::MAX_MIX_BUFFERS> output{};
37 std::array<float_le, AudioCommon::MAX_MIX_BUFFERS> volume{};
38 s32_le count{};
39};
40static_assert(sizeof(BufferMixerParams) == 0x94, "BufferMixerParams is an invalid size");
41
42struct AuxInfo {
43 std::array<s8, AudioCommon::MAX_MIX_BUFFERS> input_mix_buffers{};
44 std::array<s8, AudioCommon::MAX_MIX_BUFFERS> output_mix_buffers{};
45 u32_le count{};
46 s32_le sample_rate{};
47 s32_le sample_count{};
48 s32_le mix_buffer_count{};
49 u64_le send_buffer_info{};
50 u64_le send_buffer_base{};
51
52 u64_le return_buffer_info{};
53 u64_le return_buffer_base{};
54};
55static_assert(sizeof(AuxInfo) == 0x60, "AuxInfo is an invalid size");
56
57class EffectInfo {
58public:
59 struct InParams {
60 EffectType type{};
61 u8 is_new{};
62 u8 is_enabled{};
63 INSERT_PADDING_BYTES(1);
64 s32_le mix_id{};
65 u64_le buffer_address{};
66 u64_le buffer_size{};
67 s32_le priority{};
68 INSERT_PADDING_BYTES(4);
69 union {
70 std::array<u8, 0xa0> raw;
71 };
72 };
73 static_assert(sizeof(EffectInfo::InParams) == 0xc0, "InParams is an invalid size");
74
75 struct OutParams {
76 UsageStatus status{};
77 INSERT_PADDING_BYTES(15);
78 };
79 static_assert(sizeof(EffectInfo::OutParams) == 0x10, "OutParams is an invalid size");
80};
81
82class EffectBase {
83public:
84 EffectBase();
85 ~EffectBase();
86
87 virtual void Update(EffectInfo::InParams& in_params) = 0;
88 UsageStatus GetUsage() const;
89
90protected:
91 UsageStatus usage{UsageStatus::Invalid};
92};
93
94class EffectStubbed : public EffectBase {
95public:
96 explicit EffectStubbed();
97 ~EffectStubbed();
98
99 void Update(EffectInfo::InParams& in_params) override;
100};
101
102class EffectContext {
103public:
104 explicit EffectContext(std::size_t effect_count);
105 ~EffectContext();
106
107 std::size_t GetCount() const;
108 EffectBase* GetInfo(std::size_t i);
109
110private:
111 std::size_t effect_count{};
112 std::vector<std::unique_ptr<EffectBase>> effects;
113};
114} // namespace AudioCore
diff --git a/src/audio_core/info_updater.cpp b/src/audio_core/info_updater.cpp
new file mode 100644
index 000000000..286aa0321
--- /dev/null
+++ b/src/audio_core/info_updater.cpp
@@ -0,0 +1,515 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include "audio_core/behavior_info.h"
6#include "audio_core/effect_context.h"
7#include "audio_core/info_updater.h"
8#include "audio_core/memory_pool.h"
9#include "audio_core/mix_context.h"
10#include "audio_core/sink_context.h"
11#include "audio_core/splitter_context.h"
12#include "audio_core/voice_context.h"
13#include "common/logging/log.h"
14
15namespace AudioCore {
16
17InfoUpdater::InfoUpdater(const std::vector<u8>& in_params, std::vector<u8>& out_params,
18 BehaviorInfo& behavior_info)
19 : in_params(in_params), out_params(out_params), behavior_info(behavior_info) {
20 ASSERT(
21 AudioCommon::CanConsumeBuffer(in_params.size(), 0, sizeof(AudioCommon::UpdateDataHeader)));
22 std::memcpy(&input_header, in_params.data(), sizeof(AudioCommon::UpdateDataHeader));
23 output_header.total_size = sizeof(AudioCommon::UpdateDataHeader);
24}
25
26InfoUpdater::~InfoUpdater() = default;
27
28bool InfoUpdater::UpdateBehaviorInfo(BehaviorInfo& in_behavior_info) {
29 if (input_header.size.behavior != sizeof(BehaviorInfo::InParams)) {
30 LOG_ERROR(Audio, "Behavior info is an invalid size, expecting 0x{:X} but got 0x{:X}",
31 sizeof(BehaviorInfo::InParams), input_header.size.behavior);
32 return false;
33 }
34
35 if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset,
36 sizeof(BehaviorInfo::InParams))) {
37 LOG_ERROR(Audio, "Buffer is an invalid size!");
38 return false;
39 }
40
41 BehaviorInfo::InParams behavior_in{};
42 std::memcpy(&behavior_in, in_params.data() + input_offset, sizeof(BehaviorInfo::InParams));
43 input_offset += sizeof(BehaviorInfo::InParams);
44
45 // Make sure it's an audio revision we can actually support
46 if (!AudioCommon::IsValidRevision(behavior_in.revision)) {
47 LOG_ERROR(Audio, "Invalid input revision, revision=0x{:08X}", behavior_in.revision);
48 return false;
49 }
50
51 // Make sure that our behavior info revision matches the input
52 if (in_behavior_info.GetUserRevision() != behavior_in.revision) {
53 LOG_ERROR(Audio,
54 "User revision differs from input revision, expecting 0x{:08X} but got 0x{:08X}",
55 in_behavior_info.GetUserRevision(), behavior_in.revision);
56 return false;
57 }
58
59 // Update behavior info flags
60 in_behavior_info.ClearError();
61 in_behavior_info.UpdateFlags(behavior_in.flags);
62
63 return true;
64}
65
66bool InfoUpdater::UpdateMemoryPools(std::vector<ServerMemoryPoolInfo>& memory_pool_info) {
67 const auto force_mapping = behavior_info.IsMemoryPoolForceMappingEnabled();
68 const auto memory_pool_count = memory_pool_info.size();
69 const auto total_memory_pool_in = sizeof(ServerMemoryPoolInfo::InParams) * memory_pool_count;
70 const auto total_memory_pool_out = sizeof(ServerMemoryPoolInfo::OutParams) * memory_pool_count;
71
72 if (input_header.size.memory_pool != total_memory_pool_in) {
73 LOG_ERROR(Audio, "Memory pools are an invalid size, expecting 0x{:X} but got 0x{:X}",
74 total_memory_pool_in, input_header.size.memory_pool);
75 return false;
76 }
77
78 if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, total_memory_pool_in)) {
79 LOG_ERROR(Audio, "Buffer is an invalid size!");
80 return false;
81 }
82
83 std::vector<ServerMemoryPoolInfo::InParams> mempool_in(memory_pool_count);
84 std::vector<ServerMemoryPoolInfo::OutParams> mempool_out(memory_pool_count);
85
86 std::memcpy(mempool_in.data(), in_params.data() + input_offset, total_memory_pool_in);
87 input_offset += total_memory_pool_in;
88
89 // Update our memory pools
90 for (std::size_t i = 0; i < memory_pool_count; i++) {
91 if (!memory_pool_info[i].Update(mempool_in[i], mempool_out[i])) {
92 LOG_ERROR(Audio, "Failed to update memory pool {}!", i);
93 return false;
94 }
95 }
96
97 if (!AudioCommon::CanConsumeBuffer(out_params.size(), output_offset,
98 sizeof(BehaviorInfo::InParams))) {
99 LOG_ERROR(Audio, "Buffer is an invalid size!");
100 return false;
101 }
102
103 std::memcpy(out_params.data() + output_offset, mempool_out.data(), total_memory_pool_out);
104 output_offset += total_memory_pool_out;
105 output_header.size.memory_pool = static_cast<u32>(total_memory_pool_out);
106 return true;
107}
108
109bool InfoUpdater::UpdateVoiceChannelResources(VoiceContext& voice_context) {
110 const auto voice_count = voice_context.GetVoiceCount();
111 const auto voice_size = voice_count * sizeof(VoiceChannelResource::InParams);
112 std::vector<VoiceChannelResource::InParams> resources_in(voice_count);
113
114 if (input_header.size.voice_channel_resource != voice_size) {
115 LOG_ERROR(Audio, "VoiceChannelResource is an invalid size, expecting 0x{:X} but got 0x{:X}",
116 voice_size, input_header.size.voice_channel_resource);
117 return false;
118 }
119
120 if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, voice_size)) {
121 LOG_ERROR(Audio, "Buffer is an invalid size!");
122 return false;
123 }
124
125 std::memcpy(resources_in.data(), in_params.data() + input_offset, voice_size);
126 input_offset += voice_size;
127
128 // Update our channel resources
129 for (std::size_t i = 0; i < voice_count; i++) {
130 // Grab our channel resource
131 auto& resource = voice_context.GetChannelResource(i);
132 resource.Update(resources_in[i]);
133 }
134
135 return true;
136}
137
138bool InfoUpdater::UpdateVoices(VoiceContext& voice_context,
139 std::vector<ServerMemoryPoolInfo>& memory_pool_info,
140 VAddr audio_codec_dsp_addr) {
141 const auto voice_count = voice_context.GetVoiceCount();
142 std::vector<VoiceInfo::InParams> voice_in(voice_count);
143 std::vector<VoiceInfo::OutParams> voice_out(voice_count);
144
145 const auto voice_in_size = voice_count * sizeof(VoiceInfo::InParams);
146 const auto voice_out_size = voice_count * sizeof(VoiceInfo::OutParams);
147
148 if (input_header.size.voice != voice_in_size) {
149 LOG_ERROR(Audio, "Voices are an invalid size, expecting 0x{:X} but got 0x{:X}",
150 voice_in_size, input_header.size.voice);
151 return false;
152 }
153
154 if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, voice_in_size)) {
155 LOG_ERROR(Audio, "Buffer is an invalid size!");
156 return false;
157 }
158
159 std::memcpy(voice_in.data(), in_params.data() + input_offset, voice_in_size);
160 input_offset += voice_in_size;
161
162 // Set all voices to not be in use
163 for (std::size_t i = 0; i < voice_count; i++) {
164 voice_context.GetInfo(i).GetInParams().in_use = false;
165 }
166
167 // Update our voices
168 for (std::size_t i = 0; i < voice_count; i++) {
169 auto& in_params = voice_in[i];
170 const auto channel_count = static_cast<std::size_t>(in_params.channel_count);
171 // Skip if it's not currently in use
172 if (!in_params.is_in_use) {
173 continue;
174 }
175 // Voice states for each channel
176 std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT> voice_states{};
177 ASSERT(in_params.id < voice_count);
178
179 // Grab our current voice info
180 auto& voice_info = voice_context.GetInfo(static_cast<std::size_t>(in_params.id));
181
182 ASSERT(channel_count <= AudioCommon::MAX_CHANNEL_COUNT);
183
184 // Get all our channel voice states
185 for (std::size_t channel = 0; channel < channel_count; channel++) {
186 voice_states[channel] =
187 &voice_context.GetState(in_params.voice_channel_resource_ids[channel]);
188 }
189
190 if (in_params.is_new) {
191 // Default our values for our voice
192 voice_info.Initialize();
193 if (channel_count == 0 || channel_count > AudioCommon::MAX_CHANNEL_COUNT) {
194 continue;
195 }
196
197 // Zero out our voice states
198 for (std::size_t channel = 0; channel < channel_count; channel++) {
199 std::memset(voice_states[channel], 0, sizeof(VoiceState));
200 }
201 }
202
203 // Update our voice
204 voice_info.UpdateParameters(in_params, behavior_info);
205 // TODO(ogniK): Handle mapping errors with behavior info based on in params response
206
207 // Update our wave buffers
208 voice_info.UpdateWaveBuffers(in_params, voice_states, behavior_info);
209 voice_info.WriteOutStatus(voice_out[i], in_params, voice_states);
210 }
211
212 if (!AudioCommon::CanConsumeBuffer(out_params.size(), output_offset, voice_out_size)) {
213 LOG_ERROR(Audio, "Buffer is an invalid size!");
214 return false;
215 }
216 std::memcpy(out_params.data() + output_offset, voice_out.data(), voice_out_size);
217 output_offset += voice_out_size;
218 output_header.size.voice = static_cast<u32>(voice_out_size);
219 return true;
220}
221
222bool InfoUpdater::UpdateEffects(EffectContext& effect_context, bool is_active) {
223 const auto effect_count = effect_context.GetCount();
224 std::vector<EffectInfo::InParams> effect_in(effect_count);
225 std::vector<EffectInfo::OutParams> effect_out(effect_count);
226
227 const auto total_effect_in = effect_count * sizeof(EffectInfo::InParams);
228 const auto total_effect_out = effect_count * sizeof(EffectInfo::OutParams);
229
230 if (input_header.size.effect != total_effect_in) {
231 LOG_ERROR(Audio, "Effects are an invalid size, expecting 0x{:X} but got 0x{:X}",
232 total_effect_in, input_header.size.effect);
233 return false;
234 }
235
236 if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, total_effect_in)) {
237 LOG_ERROR(Audio, "Buffer is an invalid size!");
238 return false;
239 }
240
241 std::memcpy(effect_in.data(), in_params.data() + input_offset, total_effect_in);
242 input_offset += total_effect_in;
243
244 // Update effects
245 for (std::size_t i = 0; i < effect_count; i++) {
246 auto* info = effect_context.GetInfo(i);
247 info->Update(effect_in[i]);
248
249 // TODO(ogniK): Update individual effects
250 if ((!is_active && info->GetUsage() != UsageStatus::New) ||
251 info->GetUsage() == UsageStatus::Removed) {
252 effect_out[i].status = UsageStatus::Removed;
253 } else if (info->GetUsage() == UsageStatus::New) {
254 effect_out[i].status = UsageStatus::New;
255 } else {
256 effect_out[i].status = UsageStatus::Used;
257 }
258 }
259
260 if (!AudioCommon::CanConsumeBuffer(out_params.size(), output_offset, total_effect_out)) {
261 LOG_ERROR(Audio, "Buffer is an invalid size!");
262 return false;
263 }
264
265 std::memcpy(out_params.data() + output_offset, effect_out.data(), total_effect_out);
266 output_offset += total_effect_out;
267 output_header.size.effect = static_cast<u32>(total_effect_out);
268
269 return true;
270}
271
272bool InfoUpdater::UpdateSplitterInfo(SplitterContext& splitter_context) {
273 std::size_t start_offset = input_offset;
274 std::size_t bytes_read{};
275 // Update splitter context
276 if (!splitter_context.Update(in_params, input_offset, bytes_read)) {
277 LOG_ERROR(Audio, "Failed to update splitter context!");
278 return false;
279 }
280
281 const auto consumed = input_offset - start_offset;
282
283 if (input_header.size.splitter != consumed) {
284 LOG_ERROR(Audio, "Splitters is an invalid size, expecting 0x{:X} but got 0x{:X}",
285 bytes_read, input_header.size.splitter);
286 return false;
287 }
288
289 return true;
290}
291
292ResultCode InfoUpdater::UpdateMixes(MixContext& mix_context, std::size_t mix_buffer_count,
293 SplitterContext& splitter_context) {
294 std::vector<MixInfo::InParams> mix_in_params;
295
296 if (!behavior_info.IsMixInParameterDirtyOnlyUpdateSupported()) {
297 // If we're not dirty, get ALL mix in parameters
298 const auto context_mix_count = mix_context.GetCount();
299 const auto total_mix_in = context_mix_count * sizeof(MixInfo::InParams);
300 if (input_header.size.mixer != total_mix_in) {
301 LOG_ERROR(Audio, "Mixer is an invalid size, expecting 0x{:X} but got 0x{:X}",
302 total_mix_in, input_header.size.mixer);
303 return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
304 }
305
306 if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, total_mix_in)) {
307 LOG_ERROR(Audio, "Buffer is an invalid size!");
308 return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
309 }
310
311 mix_in_params.resize(context_mix_count);
312 std::memcpy(mix_in_params.data(), in_params.data() + input_offset, total_mix_in);
313
314 input_offset += total_mix_in;
315 } else {
316 // Only update the "dirty" mixes
317 MixInfo::DirtyHeader dirty_header{};
318 if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset,
319 sizeof(MixInfo::DirtyHeader))) {
320 LOG_ERROR(Audio, "Buffer is an invalid size!");
321 return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
322 }
323
324 std::memcpy(&dirty_header, in_params.data() + input_offset, sizeof(MixInfo::DirtyHeader));
325 input_offset += sizeof(MixInfo::DirtyHeader);
326
327 const auto total_mix_in =
328 dirty_header.mixer_count * sizeof(MixInfo::InParams) + sizeof(MixInfo::DirtyHeader);
329
330 if (input_header.size.mixer != total_mix_in) {
331 LOG_ERROR(Audio, "Mixer is an invalid size, expecting 0x{:X} but got 0x{:X}",
332 total_mix_in, input_header.size.mixer);
333 return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
334 }
335
336 if (dirty_header.mixer_count != 0) {
337 mix_in_params.resize(dirty_header.mixer_count);
338 std::memcpy(mix_in_params.data(), in_params.data() + input_offset,
339 mix_in_params.size() * sizeof(MixInfo::InParams));
340 input_offset += mix_in_params.size() * sizeof(MixInfo::InParams);
341 }
342 }
343
344 // Get our total input count
345 const auto mix_count = mix_in_params.size();
346
347 if (!behavior_info.IsMixInParameterDirtyOnlyUpdateSupported()) {
348 // Only verify our buffer count if we're not dirty
349 std::size_t total_buffer_count{};
350 for (std::size_t i = 0; i < mix_count; i++) {
351 const auto& in = mix_in_params[i];
352 total_buffer_count += in.buffer_count;
353 if (in.dest_mix_id > mix_count && in.dest_mix_id != AudioCommon::NO_MIX &&
354 in.mix_id != AudioCommon::FINAL_MIX) {
355 LOG_ERROR(
356 Audio,
357 "Invalid mix destination, mix_id={:X}, dest_mix_id={:X}, mix_buffer_count={:X}",
358 in.mix_id, in.dest_mix_id, mix_buffer_count);
359 return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
360 }
361 }
362
363 if (total_buffer_count > mix_buffer_count) {
364 LOG_ERROR(Audio,
365 "Too many mix buffers used! mix_buffer_count={:X}, requesting_buffers={:X}",
366 mix_buffer_count, total_buffer_count);
367 return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
368 }
369 }
370
371 if (mix_buffer_count == 0) {
372 LOG_ERROR(Audio, "No mix buffers!");
373 return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
374 }
375
376 bool should_sort = false;
377 for (std::size_t i = 0; i < mix_count; i++) {
378 const auto& mix_in = mix_in_params[i];
379 std::size_t target_mix{};
380 if (behavior_info.IsMixInParameterDirtyOnlyUpdateSupported()) {
381 target_mix = mix_in.mix_id;
382 } else {
383 // Non dirty supported games just use i instead of the actual mix_id
384 target_mix = i;
385 }
386 auto& mix_info = mix_context.GetInfo(target_mix);
387 auto& mix_info_params = mix_info.GetInParams();
388 if (mix_info_params.in_use != mix_in.in_use) {
389 mix_info_params.in_use = mix_in.in_use;
390 // TODO(ogniK): Update effect processing order
391 should_sort = true;
392 }
393
394 if (mix_in.in_use) {
395 should_sort |= mix_info.Update(mix_context.GetEdgeMatrix(), mix_in, behavior_info,
396 splitter_context);
397 }
398 }
399
400 if (should_sort && behavior_info.IsSplitterSupported()) {
401 // Sort our splitter data
402 if (!mix_context.TsortInfo(splitter_context)) {
403 return AudioCommon::Audren::ERR_SPLITTER_SORT_FAILED;
404 }
405 }
406
407 // TODO(ogniK): Sort when splitter is suppoorted
408
409 return RESULT_SUCCESS;
410}
411
412bool InfoUpdater::UpdateSinks(SinkContext& sink_context) {
413 const auto sink_count = sink_context.GetCount();
414 std::vector<SinkInfo::InParams> sink_in_params(sink_count);
415 const auto total_sink_in = sink_count * sizeof(SinkInfo::InParams);
416
417 if (input_header.size.sink != total_sink_in) {
418 LOG_ERROR(Audio, "Sinks are an invalid size, expecting 0x{:X} but got 0x{:X}",
419 total_sink_in, input_header.size.effect);
420 return false;
421 }
422
423 if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, total_sink_in)) {
424 LOG_ERROR(Audio, "Buffer is an invalid size!");
425 return false;
426 }
427
428 std::memcpy(sink_in_params.data(), in_params.data() + input_offset, total_sink_in);
429 input_offset += total_sink_in;
430
431 // TODO(ogniK): Properly update sinks
432 if (!sink_in_params.empty()) {
433 sink_context.UpdateMainSink(sink_in_params[0]);
434 }
435
436 output_header.size.sink = static_cast<u32>(0x20 * sink_count);
437 output_offset += 0x20 * sink_count;
438 return true;
439}
440
441bool InfoUpdater::UpdatePerformanceBuffer() {
442 output_header.size.performance = 0x10;
443 output_offset += 0x10;
444 return true;
445}
446
447bool InfoUpdater::UpdateErrorInfo(BehaviorInfo& in_behavior_info) {
448 const auto total_beahvior_info_out = sizeof(BehaviorInfo::OutParams);
449
450 if (!AudioCommon::CanConsumeBuffer(out_params.size(), output_offset, total_beahvior_info_out)) {
451 LOG_ERROR(Audio, "Buffer is an invalid size!");
452 return false;
453 }
454
455 BehaviorInfo::OutParams behavior_info_out{};
456 behavior_info.CopyErrorInfo(behavior_info_out);
457
458 std::memcpy(out_params.data() + output_offset, &behavior_info_out, total_beahvior_info_out);
459 output_offset += total_beahvior_info_out;
460 output_header.size.behavior = total_beahvior_info_out;
461
462 return true;
463}
464
465struct RendererInfo {
466 u64_le elasped_frame_count{};
467 INSERT_PADDING_WORDS(2);
468};
469static_assert(sizeof(RendererInfo) == 0x10, "RendererInfo is an invalid size");
470
471bool InfoUpdater::UpdateRendererInfo(std::size_t elapsed_frame_count) {
472 const auto total_renderer_info_out = sizeof(RendererInfo);
473 if (!AudioCommon::CanConsumeBuffer(out_params.size(), output_offset, total_renderer_info_out)) {
474 LOG_ERROR(Audio, "Buffer is an invalid size!");
475 return false;
476 }
477 RendererInfo out{};
478 out.elasped_frame_count = elapsed_frame_count;
479 std::memcpy(out_params.data() + output_offset, &out, total_renderer_info_out);
480 output_offset += total_renderer_info_out;
481 output_header.size.render_info = total_renderer_info_out;
482
483 return true;
484}
485
486bool InfoUpdater::CheckConsumedSize() const {
487 if (output_offset != out_params.size()) {
488 LOG_ERROR(Audio, "Output is not consumed! Consumed {}, but requires {}. {} bytes remaining",
489 output_offset, out_params.size(), out_params.size() - output_offset);
490 return false;
491 }
492 /*if (input_offset != in_params.size()) {
493 LOG_ERROR(Audio, "Input is not consumed!");
494 return false;
495 }*/
496 return true;
497}
498
499bool InfoUpdater::WriteOutputHeader() {
500 if (!AudioCommon::CanConsumeBuffer(out_params.size(), 0,
501 sizeof(AudioCommon::UpdateDataHeader))) {
502 LOG_ERROR(Audio, "Buffer is an invalid size!");
503 return false;
504 }
505 output_header.revision = AudioCommon::CURRENT_PROCESS_REVISION;
506 const auto& sz = output_header.size;
507 output_header.total_size += sz.behavior + sz.memory_pool + sz.voice +
508 sz.voice_channel_resource + sz.effect + sz.mixer + sz.sink +
509 sz.performance + sz.splitter + sz.render_info;
510
511 std::memcpy(out_params.data(), &output_header, sizeof(AudioCommon::UpdateDataHeader));
512 return true;
513}
514
515} // namespace AudioCore
diff --git a/src/audio_core/info_updater.h b/src/audio_core/info_updater.h
new file mode 100644
index 000000000..6969de67d
--- /dev/null
+++ b/src/audio_core/info_updater.h
@@ -0,0 +1,58 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <vector>
8#include "audio_core/common.h"
9#include "common/common_types.h"
10
11namespace AudioCore {
12
13class BehaviorInfo;
14class ServerMemoryPoolInfo;
15class VoiceContext;
16class EffectContext;
17class MixContext;
18class SinkContext;
19class SplitterContext;
20
21class InfoUpdater {
22public:
23 // TODO(ogniK): Pass process handle when we support it
24 InfoUpdater(const std::vector<u8>& in_params, std::vector<u8>& out_params,
25 BehaviorInfo& behavior_info);
26 ~InfoUpdater();
27
28 bool UpdateBehaviorInfo(BehaviorInfo& in_behavior_info);
29 bool UpdateMemoryPools(std::vector<ServerMemoryPoolInfo>& memory_pool_info);
30 bool UpdateVoiceChannelResources(VoiceContext& voice_context);
31 bool UpdateVoices(VoiceContext& voice_context,
32 std::vector<ServerMemoryPoolInfo>& memory_pool_info,
33 VAddr audio_codec_dsp_addr);
34 bool UpdateEffects(EffectContext& effect_context, bool is_active);
35 bool UpdateSplitterInfo(SplitterContext& splitter_context);
36 ResultCode UpdateMixes(MixContext& mix_context, std::size_t mix_buffer_count,
37 SplitterContext& splitter_context);
38 bool UpdateSinks(SinkContext& sink_context);
39 bool UpdatePerformanceBuffer();
40 bool UpdateErrorInfo(BehaviorInfo& in_behavior_info);
41 bool UpdateRendererInfo(std::size_t elapsed_frame_count);
42 bool CheckConsumedSize() const;
43
44 bool WriteOutputHeader();
45
46private:
47 const std::vector<u8>& in_params;
48 std::vector<u8>& out_params;
49 BehaviorInfo& behavior_info;
50
51 AudioCommon::UpdateDataHeader input_header{};
52 AudioCommon::UpdateDataHeader output_header{};
53
54 std::size_t input_offset{sizeof(AudioCommon::UpdateDataHeader)};
55 std::size_t output_offset{sizeof(AudioCommon::UpdateDataHeader)};
56};
57
58} // namespace AudioCore
diff --git a/src/audio_core/memory_pool.cpp b/src/audio_core/memory_pool.cpp
new file mode 100644
index 000000000..5a3453063
--- /dev/null
+++ b/src/audio_core/memory_pool.cpp
@@ -0,0 +1,62 @@
1
2// Copyright 2020 yuzu Emulator Project
3// Licensed under GPLv2 or any later version
4// Refer to the license.txt file included.
5
6#include "audio_core/memory_pool.h"
7#include "common/logging/log.h"
8
9namespace AudioCore {
10
11ServerMemoryPoolInfo::ServerMemoryPoolInfo() = default;
12ServerMemoryPoolInfo::~ServerMemoryPoolInfo() = default;
13bool ServerMemoryPoolInfo::Update(const ServerMemoryPoolInfo::InParams& in_params,
14 ServerMemoryPoolInfo::OutParams& out_params) {
15 // Our state does not need to be changed
16 if (in_params.state != ServerMemoryPoolInfo::State::RequestAttach &&
17 in_params.state != ServerMemoryPoolInfo::State::RequestDetach) {
18 return true;
19 }
20
21 // Address or size is null
22 if (in_params.address == 0 || in_params.size == 0) {
23 LOG_ERROR(Audio, "Memory pool address or size is zero! address={:X}, size={:X}",
24 in_params.address, in_params.size);
25 return false;
26 }
27
28 // Address or size is not aligned
29 if ((in_params.address % 0x1000) != 0 || (in_params.size % 0x1000) != 0) {
30 LOG_ERROR(Audio, "Memory pool address or size is not aligned! address={:X}, size={:X}",
31 in_params.address, in_params.size);
32 return false;
33 }
34
35 if (in_params.state == ServerMemoryPoolInfo::State::RequestAttach) {
36 cpu_address = in_params.address;
37 size = in_params.size;
38 used = true;
39 out_params.state = ServerMemoryPoolInfo::State::Attached;
40 } else {
41 // Unexpected address
42 if (cpu_address != in_params.address) {
43 LOG_ERROR(Audio, "Memory pool address differs! Expecting {:X} but address is {:X}",
44 cpu_address, in_params.address);
45 return false;
46 }
47
48 if (size != in_params.size) {
49 LOG_ERROR(Audio, "Memory pool size differs! Expecting {:X} but size is {:X}", size,
50 in_params.size);
51 return false;
52 }
53
54 cpu_address = 0;
55 size = 0;
56 used = false;
57 out_params.state = ServerMemoryPoolInfo::State::Detached;
58 }
59 return true;
60}
61
62} // namespace AudioCore
diff --git a/src/audio_core/memory_pool.h b/src/audio_core/memory_pool.h
new file mode 100644
index 000000000..8ac503f1c
--- /dev/null
+++ b/src/audio_core/memory_pool.h
@@ -0,0 +1,53 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include "common/common_funcs.h"
8#include "common/common_types.h"
9#include "common/swap.h"
10
11namespace AudioCore {
12
13class ServerMemoryPoolInfo {
14public:
15 ServerMemoryPoolInfo();
16 ~ServerMemoryPoolInfo();
17
18 enum class State : u32_le {
19 Invalid = 0x0,
20 Aquired = 0x1,
21 RequestDetach = 0x2,
22 Detached = 0x3,
23 RequestAttach = 0x4,
24 Attached = 0x5,
25 Released = 0x6,
26 };
27
28 struct InParams {
29 u64_le address{};
30 u64_le size{};
31 ServerMemoryPoolInfo::State state{};
32 INSERT_PADDING_WORDS(3);
33 };
34 static_assert(sizeof(ServerMemoryPoolInfo::InParams) == 0x20, "InParams are an invalid size");
35
36 struct OutParams {
37 ServerMemoryPoolInfo::State state{};
38 INSERT_PADDING_WORDS(3);
39 };
40 static_assert(sizeof(ServerMemoryPoolInfo::OutParams) == 0x10, "OutParams are an invalid size");
41
42 bool Update(const ServerMemoryPoolInfo::InParams& in_params,
43 ServerMemoryPoolInfo::OutParams& out_params);
44
45private:
46 // There's another entry here which is the DSP address, however since we're not talking to the
47 // DSP we can just use the same address provided by the guest without needing to remap
48 u64_le cpu_address{};
49 u64_le size{};
50 bool used{};
51};
52
53} // namespace AudioCore
diff --git a/src/audio_core/mix_context.cpp b/src/audio_core/mix_context.cpp
new file mode 100644
index 000000000..8e150db03
--- /dev/null
+++ b/src/audio_core/mix_context.cpp
@@ -0,0 +1,264 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include "audio_core/behavior_info.h"
6#include "audio_core/common.h"
7#include "audio_core/mix_context.h"
8#include "audio_core/splitter_context.h"
9
10namespace AudioCore {
11MixContext::MixContext() = default;
12MixContext::~MixContext() = default;
13
14void MixContext::Initialize(const BehaviorInfo& behavior_info, std::size_t mix_count) {
15 info_count = mix_count;
16 infos.resize(info_count);
17 auto& final_mix = GetInfo(AudioCommon::FINAL_MIX);
18 final_mix.GetInParams().mix_id = AudioCommon::FINAL_MIX;
19 for (auto& info : infos) {
20 sorted_info.push_back(&info);
21 }
22
23 // Only initialize our edge matrix and node states if splitters are supported
24 if (behavior_info.IsSplitterSupported()) {
25 node_states.Initialize(mix_count);
26 edge_matrix.Initialize(mix_count);
27 }
28}
29
30void MixContext::UpdateDistancesFromFinalMix() {
31 // Set all distances to be invalid
32 for (std::size_t i = 0; i < info_count; i++) {
33 GetInfo(i).GetInParams().final_mix_distance = AudioCommon::NO_FINAL_MIX;
34 }
35
36 for (std::size_t i = 0; i < info_count; i++) {
37 auto& info = GetInfo(i);
38 auto& in_params = info.GetInParams();
39 // Populate our sorted info
40 sorted_info[i] = &info;
41
42 if (!in_params.in_use) {
43 continue;
44 }
45
46 auto mix_id = in_params.mix_id;
47 // Needs to be referenced out of scope
48 s32 distance_to_final_mix{AudioCommon::FINAL_MIX};
49 for (; distance_to_final_mix < info_count; distance_to_final_mix++) {
50 if (mix_id == AudioCommon::FINAL_MIX) {
51 // If we're at the final mix, we're done
52 break;
53 } else if (mix_id == AudioCommon::NO_MIX) {
54 // If we have no more mix ids, we're done
55 distance_to_final_mix = AudioCommon::NO_FINAL_MIX;
56 break;
57 } else {
58 const auto& dest_mix = GetInfo(mix_id);
59 const auto dest_mix_distance = dest_mix.GetInParams().final_mix_distance;
60
61 if (dest_mix_distance == AudioCommon::NO_FINAL_MIX) {
62 // If our current mix isn't pointing to a final mix, follow through
63 mix_id = dest_mix.GetInParams().dest_mix_id;
64 } else {
65 // Our current mix + 1 = final distance
66 distance_to_final_mix = dest_mix_distance + 1;
67 break;
68 }
69 }
70 }
71
72 // If we're out of range for our distance, mark it as no final mix
73 if (distance_to_final_mix >= info_count) {
74 distance_to_final_mix = AudioCommon::NO_FINAL_MIX;
75 }
76
77 in_params.final_mix_distance = distance_to_final_mix;
78 }
79}
80
81void MixContext::CalcMixBufferOffset() {
82 s32 offset{};
83 for (std::size_t i = 0; i < info_count; i++) {
84 auto& info = GetSortedInfo(i);
85 auto& in_params = info.GetInParams();
86 if (in_params.in_use) {
87 // Only update if in use
88 in_params.buffer_offset = offset;
89 offset += in_params.buffer_count;
90 }
91 }
92}
93
94void MixContext::SortInfo() {
95 // Get the distance to the final mix
96 UpdateDistancesFromFinalMix();
97
98 // Sort based on the distance to the final mix
99 std::sort(sorted_info.begin(), sorted_info.end(),
100 [](const ServerMixInfo* lhs, const ServerMixInfo* rhs) {
101 return lhs->GetInParams().final_mix_distance >
102 rhs->GetInParams().final_mix_distance;
103 });
104
105 // Calculate the mix buffer offset
106 CalcMixBufferOffset();
107}
108
109bool MixContext::TsortInfo(SplitterContext& splitter_context) {
110 // If we're not using mixes, just calculate the mix buffer offset
111 if (!splitter_context.UsingSplitter()) {
112 CalcMixBufferOffset();
113 return true;
114 }
115 // Sort our node states
116 if (!node_states.Tsort(edge_matrix)) {
117 return false;
118 }
119
120 // Get our sorted list
121 const auto sorted_list = node_states.GetIndexList();
122 std::size_t info_id{};
123 for (auto itr = sorted_list.rbegin(); itr != sorted_list.rend(); ++itr) {
124 // Set our sorted info
125 sorted_info[info_id++] = &GetInfo(*itr);
126 }
127
128 // Calculate the mix buffer offset
129 CalcMixBufferOffset();
130 return true;
131}
132
133std::size_t MixContext::GetCount() const {
134 return info_count;
135}
136
137ServerMixInfo& MixContext::GetInfo(std::size_t i) {
138 ASSERT(i < info_count);
139 return infos.at(i);
140}
141
142const ServerMixInfo& MixContext::GetInfo(std::size_t i) const {
143 ASSERT(i < info_count);
144 return infos.at(i);
145}
146
147ServerMixInfo& MixContext::GetSortedInfo(std::size_t i) {
148 ASSERT(i < info_count);
149 return *sorted_info.at(i);
150}
151
152const ServerMixInfo& MixContext::GetSortedInfo(std::size_t i) const {
153 ASSERT(i < info_count);
154 return *sorted_info.at(i);
155}
156
157ServerMixInfo& MixContext::GetFinalMixInfo() {
158 return infos.at(AudioCommon::FINAL_MIX);
159}
160
161const ServerMixInfo& MixContext::GetFinalMixInfo() const {
162 return infos.at(AudioCommon::FINAL_MIX);
163}
164
165EdgeMatrix& MixContext::GetEdgeMatrix() {
166 return edge_matrix;
167}
168
169const EdgeMatrix& MixContext::GetEdgeMatrix() const {
170 return edge_matrix;
171}
172
173ServerMixInfo::ServerMixInfo() {
174 Cleanup();
175}
176ServerMixInfo::~ServerMixInfo() = default;
177
178const ServerMixInfo::InParams& ServerMixInfo::GetInParams() const {
179 return in_params;
180}
181
182ServerMixInfo::InParams& ServerMixInfo::GetInParams() {
183 return in_params;
184}
185
186bool ServerMixInfo::Update(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix_in,
187 BehaviorInfo& behavior_info, SplitterContext& splitter_context) {
188 in_params.volume = mix_in.volume;
189 in_params.sample_rate = mix_in.sample_rate;
190 in_params.buffer_count = mix_in.buffer_count;
191 in_params.in_use = mix_in.in_use;
192 in_params.mix_id = mix_in.mix_id;
193 in_params.node_id = mix_in.node_id;
194 for (std::size_t i = 0; i < mix_in.mix_volume.size(); i++) {
195 std::copy(mix_in.mix_volume[i].begin(), mix_in.mix_volume[i].end(),
196 in_params.mix_volume[i].begin());
197 }
198
199 bool require_sort = false;
200
201 if (behavior_info.IsSplitterSupported()) {
202 require_sort = UpdateConnection(edge_matrix, mix_in, splitter_context);
203 } else {
204 in_params.dest_mix_id = mix_in.dest_mix_id;
205 in_params.splitter_id = AudioCommon::NO_SPLITTER;
206 }
207
208 // TODO(ogniK): Update effect processing order
209 return require_sort;
210}
211
212bool ServerMixInfo::HasAnyConnection() const {
213 return in_params.splitter_id != AudioCommon::NO_SPLITTER ||
214 in_params.mix_id != AudioCommon::NO_MIX;
215}
216
217void ServerMixInfo::Cleanup() {
218 in_params.volume = 0.0f;
219 in_params.sample_rate = 0;
220 in_params.buffer_count = 0;
221 in_params.in_use = false;
222 in_params.mix_id = AudioCommon::NO_MIX;
223 in_params.node_id = 0;
224 in_params.buffer_offset = 0;
225 in_params.dest_mix_id = AudioCommon::NO_MIX;
226 in_params.splitter_id = AudioCommon::NO_SPLITTER;
227 std::memset(in_params.mix_volume.data(), 0, sizeof(float) * in_params.mix_volume.size());
228}
229
230bool ServerMixInfo::UpdateConnection(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix_in,
231 SplitterContext& splitter_context) {
232 // Mixes are identical
233 if (in_params.dest_mix_id == mix_in.dest_mix_id &&
234 in_params.splitter_id == mix_in.splitter_id &&
235 ((in_params.splitter_id == AudioCommon::NO_SPLITTER) ||
236 !splitter_context.GetInfo(in_params.splitter_id).HasNewConnection())) {
237 return false;
238 }
239 // Remove current edges for mix id
240 edge_matrix.RemoveEdges(in_params.mix_id);
241 if (mix_in.dest_mix_id != AudioCommon::NO_MIX) {
242 // If we have a valid destination mix id, set our edge matrix
243 edge_matrix.Connect(in_params.mix_id, mix_in.dest_mix_id);
244 } else if (mix_in.splitter_id != AudioCommon::NO_SPLITTER) {
245 // Recurse our splitter linked and set our edges
246 auto& splitter_info = splitter_context.GetInfo(mix_in.splitter_id);
247 const auto length = splitter_info.GetLength();
248 for (s32 i = 0; i < length; i++) {
249 const auto* splitter_destination =
250 splitter_context.GetDestinationData(mix_in.splitter_id, i);
251 if (splitter_destination == nullptr) {
252 continue;
253 }
254 if (splitter_destination->ValidMixId()) {
255 edge_matrix.Connect(in_params.mix_id, splitter_destination->GetMixId());
256 }
257 }
258 }
259 in_params.dest_mix_id = mix_in.dest_mix_id;
260 in_params.splitter_id = mix_in.splitter_id;
261 return true;
262}
263
264} // namespace AudioCore
diff --git a/src/audio_core/mix_context.h b/src/audio_core/mix_context.h
new file mode 100644
index 000000000..381566699
--- /dev/null
+++ b/src/audio_core/mix_context.h
@@ -0,0 +1,107 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <array>
8#include <vector>
9#include "audio_core/common.h"
10#include "audio_core/splitter_context.h"
11#include "common/common_funcs.h"
12#include "common/common_types.h"
13
14namespace AudioCore {
15class BehaviorInfo;
16
17class MixInfo {
18public:
19 struct DirtyHeader {
20 u32_le magic{};
21 u32_le mixer_count{};
22 INSERT_PADDING_BYTES(0x18);
23 };
24 static_assert(sizeof(DirtyHeader) == 0x20, "MixInfo::DirtyHeader is an invalid size");
25
26 struct InParams {
27 float_le volume{};
28 s32_le sample_rate{};
29 s32_le buffer_count{};
30 bool in_use{};
31 INSERT_PADDING_BYTES(3);
32 s32_le mix_id{};
33 s32_le effect_count{};
34 u32_le node_id{};
35 INSERT_PADDING_WORDS(2);
36 std::array<std::array<float_le, AudioCommon::MAX_MIX_BUFFERS>, AudioCommon::MAX_MIX_BUFFERS>
37 mix_volume{};
38 s32_le dest_mix_id{};
39 s32_le splitter_id{};
40 INSERT_PADDING_WORDS(1);
41 };
42 static_assert(sizeof(MixInfo::InParams) == 0x930, "MixInfo::InParams is an invalid size");
43};
44
45class ServerMixInfo {
46public:
47 struct InParams {
48 float volume{};
49 s32 sample_rate{};
50 s32 buffer_count{};
51 bool in_use{};
52 s32 mix_id{};
53 u32 node_id{};
54 std::array<std::array<float_le, AudioCommon::MAX_MIX_BUFFERS>, AudioCommon::MAX_MIX_BUFFERS>
55 mix_volume{};
56 s32 dest_mix_id{};
57 s32 splitter_id{};
58 s32 buffer_offset{};
59 s32 final_mix_distance{};
60 };
61 ServerMixInfo();
62 ~ServerMixInfo();
63
64 const ServerMixInfo::InParams& GetInParams() const;
65 ServerMixInfo::InParams& GetInParams();
66
67 bool Update(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix_in,
68 BehaviorInfo& behavior_info, SplitterContext& splitter_context);
69 bool HasAnyConnection() const;
70 void Cleanup();
71
72private:
73 InParams in_params{};
74 bool UpdateConnection(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix_in,
75 SplitterContext& splitter_context);
76};
77
78class MixContext {
79public:
80 MixContext();
81 ~MixContext();
82
83 void Initialize(const BehaviorInfo& behavior_info, std::size_t mix_count);
84 void SortInfo();
85 bool TsortInfo(SplitterContext& splitter_context);
86
87 std::size_t GetCount() const;
88 ServerMixInfo& GetInfo(std::size_t i);
89 const ServerMixInfo& GetInfo(std::size_t i) const;
90 ServerMixInfo& GetSortedInfo(std::size_t i);
91 const ServerMixInfo& GetSortedInfo(std::size_t i) const;
92 ServerMixInfo& GetFinalMixInfo();
93 const ServerMixInfo& GetFinalMixInfo() const;
94 EdgeMatrix& GetEdgeMatrix();
95 const EdgeMatrix& GetEdgeMatrix() const;
96
97private:
98 void CalcMixBufferOffset();
99 void UpdateDistancesFromFinalMix();
100
101 NodeStates node_states{};
102 EdgeMatrix edge_matrix{};
103 std::size_t info_count{};
104 std::vector<ServerMixInfo> infos{};
105 std::vector<ServerMixInfo*> sorted_info{};
106};
107} // namespace AudioCore
diff --git a/src/audio_core/sink_context.cpp b/src/audio_core/sink_context.cpp
new file mode 100644
index 000000000..0882b411a
--- /dev/null
+++ b/src/audio_core/sink_context.cpp
@@ -0,0 +1,31 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include "audio_core/sink_context.h"
6
7namespace AudioCore {
8SinkContext::SinkContext(std::size_t sink_count) : sink_count(sink_count) {}
9SinkContext::~SinkContext() = default;
10
11std::size_t SinkContext::GetCount() const {
12 return sink_count;
13}
14
15void SinkContext::UpdateMainSink(SinkInfo::InParams& in) {
16 in_use = in.in_use;
17 use_count = in.device.input_count;
18 std::memcpy(buffers.data(), in.device.input.data(), AudioCommon::MAX_CHANNEL_COUNT);
19}
20
21bool SinkContext::InUse() const {
22 return in_use;
23}
24
25std::vector<u8> SinkContext::OutputBuffers() const {
26 std::vector<u8> buffer_ret(use_count);
27 std::memcpy(buffer_ret.data(), buffers.data(), use_count);
28 return buffer_ret;
29}
30
31} // namespace AudioCore
diff --git a/src/audio_core/sink_context.h b/src/audio_core/sink_context.h
new file mode 100644
index 000000000..d7aa72ba7
--- /dev/null
+++ b/src/audio_core/sink_context.h
@@ -0,0 +1,89 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include "audio_core/common.h"
8#include "common/common_funcs.h"
9#include "common/common_types.h"
10#include "common/swap.h"
11
12namespace AudioCore {
13
14enum class SinkTypes : u8 {
15 Invalid = 0,
16 Device = 1,
17 Circular = 2,
18};
19
20enum class SinkSampleFormat : u32_le {
21 None = 0,
22 Pcm8 = 1,
23 Pcm16 = 2,
24 Pcm24 = 3,
25 Pcm32 = 4,
26 PcmFloat = 5,
27 Adpcm = 6,
28};
29
30class SinkInfo {
31public:
32 struct CircularBufferIn {
33 u64_le address;
34 u32_le size;
35 u32_le input_count;
36 u32_le sample_count;
37 u32_le previous_position;
38 SinkSampleFormat sample_format;
39 std::array<u8, AudioCommon::MAX_CHANNEL_COUNT> input;
40 bool in_use;
41 INSERT_UNION_PADDING_BYTES(5);
42 };
43 static_assert(sizeof(SinkInfo::CircularBufferIn) == 0x28,
44 "SinkInfo::CircularBufferIn is in invalid size");
45
46 struct DeviceIn {
47 std::array<u8, 255> device_name;
48 INSERT_UNION_PADDING_BYTES(1);
49 s32_le input_count;
50 std::array<u8, AudioCommon::MAX_CHANNEL_COUNT> input;
51 INSERT_UNION_PADDING_BYTES(1);
52 bool down_matrix_enabled;
53 std::array<float_le, 4> down_matrix_coef;
54 };
55 static_assert(sizeof(SinkInfo::DeviceIn) == 0x11c, "SinkInfo::DeviceIn is an invalid size");
56
57 struct InParams {
58 SinkTypes type{};
59 bool in_use{};
60 INSERT_PADDING_BYTES(2);
61 u32_le node_id{};
62 INSERT_PADDING_WORDS(6);
63 union {
64 // std::array<u8, 0x120> raw{};
65 SinkInfo::DeviceIn device;
66 SinkInfo::CircularBufferIn circular_buffer;
67 };
68 };
69 static_assert(sizeof(SinkInfo::InParams) == 0x140, "SinkInfo::InParams are an invalid size!");
70};
71
72class SinkContext {
73public:
74 explicit SinkContext(std::size_t sink_count);
75 ~SinkContext();
76
77 std::size_t GetCount() const;
78
79 void UpdateMainSink(SinkInfo::InParams& in);
80 bool InUse() const;
81 std::vector<u8> OutputBuffers() const;
82
83private:
84 bool in_use{false};
85 s32 use_count{};
86 std::array<u8, AudioCommon::MAX_CHANNEL_COUNT> buffers{};
87 std::size_t sink_count{};
88};
89} // namespace AudioCore
diff --git a/src/audio_core/splitter_context.cpp b/src/audio_core/splitter_context.cpp
new file mode 100644
index 000000000..c0be26be1
--- /dev/null
+++ b/src/audio_core/splitter_context.cpp
@@ -0,0 +1,617 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include "audio_core/behavior_info.h"
6#include "audio_core/splitter_context.h"
7#include "common/alignment.h"
8#include "common/assert.h"
9#include "common/logging/log.h"
10
11namespace AudioCore {
12
13ServerSplitterDestinationData::ServerSplitterDestinationData(s32 id) : id(id) {}
14ServerSplitterDestinationData::~ServerSplitterDestinationData() = default;
15
16void ServerSplitterDestinationData::Update(SplitterInfo::InDestinationParams& header) {
17 // Log error as these are not actually failure states
18 if (header.magic != SplitterMagic::DataHeader) {
19 LOG_ERROR(Audio, "Splitter destination header is invalid!");
20 return;
21 }
22
23 // Incorrect splitter id
24 if (header.splitter_id != id) {
25 LOG_ERROR(Audio, "Splitter destination ids do not match!");
26 return;
27 }
28
29 mix_id = header.mix_id;
30 // Copy our mix volumes
31 std::copy(header.mix_volumes.begin(), header.mix_volumes.end(), current_mix_volumes.begin());
32 if (!in_use && header.in_use) {
33 // Update mix volumes
34 std::copy(current_mix_volumes.begin(), current_mix_volumes.end(), last_mix_volumes.begin());
35 needs_update = false;
36 }
37 in_use = header.in_use;
38}
39
40ServerSplitterDestinationData* ServerSplitterDestinationData::GetNextDestination() {
41 return next;
42}
43
44const ServerSplitterDestinationData* ServerSplitterDestinationData::GetNextDestination() const {
45 return next;
46}
47
48void ServerSplitterDestinationData::SetNextDestination(ServerSplitterDestinationData* dest) {
49 next = dest;
50}
51
52bool ServerSplitterDestinationData::ValidMixId() const {
53 return GetMixId() != AudioCommon::NO_MIX;
54}
55
56s32 ServerSplitterDestinationData::GetMixId() const {
57 return mix_id;
58}
59
60bool ServerSplitterDestinationData::IsConfigured() const {
61 return in_use && ValidMixId();
62}
63
64float ServerSplitterDestinationData::GetMixVolume(std::size_t i) const {
65 ASSERT(i < AudioCommon::MAX_MIX_BUFFERS);
66 return current_mix_volumes.at(i);
67}
68
69const std::array<float, AudioCommon::MAX_MIX_BUFFERS>&
70ServerSplitterDestinationData::CurrentMixVolumes() const {
71 return current_mix_volumes;
72}
73
74const std::array<float, AudioCommon::MAX_MIX_BUFFERS>&
75ServerSplitterDestinationData::LastMixVolumes() const {
76 return last_mix_volumes;
77}
78
79void ServerSplitterDestinationData::MarkDirty() {
80 needs_update = true;
81}
82
83void ServerSplitterDestinationData::UpdateInternalState() {
84 if (in_use && needs_update) {
85 std::copy(current_mix_volumes.begin(), current_mix_volumes.end(), last_mix_volumes.begin());
86 }
87 needs_update = false;
88}
89
90ServerSplitterInfo::ServerSplitterInfo(s32 id) : id(id) {}
91ServerSplitterInfo::~ServerSplitterInfo() = default;
92
93void ServerSplitterInfo::InitializeInfos() {
94 send_length = 0;
95 head = nullptr;
96 new_connection = true;
97}
98
99void ServerSplitterInfo::ClearNewConnectionFlag() {
100 new_connection = false;
101}
102
103std::size_t ServerSplitterInfo::Update(SplitterInfo::InInfoPrams& header) {
104 if (header.send_id != id) {
105 return 0;
106 }
107
108 sample_rate = header.sample_rate;
109 new_connection = true;
110 // We need to update the size here due to the splitter bug being present and providing an
111 // incorrect size. We're suppose to also update the header here but we just ignore and continue
112 return (sizeof(s32_le) * (header.length - 1)) + (sizeof(s32_le) * 3);
113}
114
115ServerSplitterDestinationData* ServerSplitterInfo::GetHead() {
116 return head;
117}
118
119const ServerSplitterDestinationData* ServerSplitterInfo::GetHead() const {
120 return head;
121}
122
123ServerSplitterDestinationData* ServerSplitterInfo::GetData(std::size_t depth) {
124 auto current_head = head;
125 for (std::size_t i = 0; i < depth; i++) {
126 if (current_head == nullptr) {
127 return nullptr;
128 }
129 current_head = current_head->GetNextDestination();
130 }
131 return current_head;
132}
133
134const ServerSplitterDestinationData* ServerSplitterInfo::GetData(std::size_t depth) const {
135 auto current_head = head;
136 for (std::size_t i = 0; i < depth; i++) {
137 if (current_head == nullptr) {
138 return nullptr;
139 }
140 current_head = current_head->GetNextDestination();
141 }
142 return current_head;
143}
144
145bool ServerSplitterInfo::HasNewConnection() const {
146 return new_connection;
147}
148
149s32 ServerSplitterInfo::GetLength() const {
150 return send_length;
151}
152
153void ServerSplitterInfo::SetHead(ServerSplitterDestinationData* new_head) {
154 head = new_head;
155}
156
157void ServerSplitterInfo::SetHeadDepth(s32 length) {
158 send_length = length;
159}
160
161SplitterContext::SplitterContext() = default;
162SplitterContext::~SplitterContext() = default;
163
164void SplitterContext::Initialize(BehaviorInfo& behavior_info, std::size_t _info_count,
165 std::size_t _data_count) {
166 if (!behavior_info.IsSplitterSupported() || _data_count == 0 || _info_count == 0) {
167 Setup(0, 0, false);
168 return;
169 }
170 // Only initialize if we're using splitters
171 Setup(_info_count, _data_count, behavior_info.IsSplitterBugFixed());
172}
173
174bool SplitterContext::Update(const std::vector<u8>& input, std::size_t& input_offset,
175 std::size_t& bytes_read) {
176 auto UpdateOffsets = [&](std::size_t read) {
177 input_offset += read;
178 bytes_read += read;
179 };
180
181 if (info_count == 0 || data_count == 0) {
182 bytes_read = 0;
183 return true;
184 }
185
186 if (!AudioCommon::CanConsumeBuffer(input.size(), input_offset,
187 sizeof(SplitterInfo::InHeader))) {
188 LOG_ERROR(Audio, "Buffer is an invalid size!");
189 return false;
190 }
191 SplitterInfo::InHeader header{};
192 std::memcpy(&header, input.data() + input_offset, sizeof(SplitterInfo::InHeader));
193 UpdateOffsets(sizeof(SplitterInfo::InHeader));
194
195 if (header.magic != SplitterMagic::SplitterHeader) {
196 LOG_ERROR(Audio, "Invalid header magic! Expecting {:X} but got {:X}",
197 SplitterMagic::SplitterHeader, header.magic);
198 return false;
199 }
200
201 // Clear all connections
202 for (auto& info : infos) {
203 info.ClearNewConnectionFlag();
204 }
205
206 UpdateInfo(input, input_offset, bytes_read, header.info_count);
207 UpdateData(input, input_offset, bytes_read, header.data_count);
208 const auto aligned_bytes_read = Common::AlignUp(bytes_read, 16);
209 input_offset += aligned_bytes_read - bytes_read;
210 bytes_read = aligned_bytes_read;
211 return true;
212}
213
214bool SplitterContext::UsingSplitter() const {
215 return info_count > 0 && data_count > 0;
216}
217
218ServerSplitterInfo& SplitterContext::GetInfo(std::size_t i) {
219 ASSERT(i < info_count);
220 return infos.at(i);
221}
222
223const ServerSplitterInfo& SplitterContext::GetInfo(std::size_t i) const {
224 ASSERT(i < info_count);
225 return infos.at(i);
226}
227
228ServerSplitterDestinationData& SplitterContext::GetData(std::size_t i) {
229 ASSERT(i < data_count);
230 return datas.at(i);
231}
232
233const ServerSplitterDestinationData& SplitterContext::GetData(std::size_t i) const {
234 ASSERT(i < data_count);
235 return datas.at(i);
236}
237
238ServerSplitterDestinationData* SplitterContext::GetDestinationData(std::size_t info,
239 std::size_t data) {
240 ASSERT(info < info_count);
241 auto& cur_info = GetInfo(info);
242 return cur_info.GetData(data);
243}
244
245const ServerSplitterDestinationData* SplitterContext::GetDestinationData(std::size_t info,
246 std::size_t data) const {
247 ASSERT(info < info_count);
248 auto& cur_info = GetInfo(info);
249 return cur_info.GetData(data);
250}
251
252void SplitterContext::UpdateInternalState() {
253 if (data_count == 0) {
254 return;
255 }
256
257 for (auto& data : datas) {
258 data.UpdateInternalState();
259 }
260}
261
262std::size_t SplitterContext::GetInfoCount() const {
263 return info_count;
264}
265
266std::size_t SplitterContext::GetDataCount() const {
267 return data_count;
268}
269
270void SplitterContext::Setup(std::size_t _info_count, std::size_t _data_count,
271 bool is_splitter_bug_fixed) {
272
273 info_count = _info_count;
274 data_count = _data_count;
275
276 for (std::size_t i = 0; i < info_count; i++) {
277 auto& splitter = infos.emplace_back(static_cast<s32>(i));
278 splitter.InitializeInfos();
279 }
280 for (std::size_t i = 0; i < data_count; i++) {
281 datas.emplace_back(static_cast<s32>(i));
282 }
283
284 bug_fixed = is_splitter_bug_fixed;
285}
286
287bool SplitterContext::UpdateInfo(const std::vector<u8>& input, std::size_t& input_offset,
288 std::size_t& bytes_read, s32 in_splitter_count) {
289 auto UpdateOffsets = [&](std::size_t read) {
290 input_offset += read;
291 bytes_read += read;
292 };
293
294 for (s32 i = 0; i < in_splitter_count; i++) {
295 if (!AudioCommon::CanConsumeBuffer(input.size(), input_offset,
296 sizeof(SplitterInfo::InInfoPrams))) {
297 LOG_ERROR(Audio, "Buffer is an invalid size!");
298 return false;
299 }
300 SplitterInfo::InInfoPrams header{};
301 std::memcpy(&header, input.data() + input_offset, sizeof(SplitterInfo::InInfoPrams));
302
303 // Logged as warning as these don't actually cause a bailout for some reason
304 if (header.magic != SplitterMagic::InfoHeader) {
305 LOG_ERROR(Audio, "Bad splitter data header");
306 break;
307 }
308
309 if (header.send_id < 0 || header.send_id > info_count) {
310 LOG_ERROR(Audio, "Bad splitter data id");
311 break;
312 }
313
314 UpdateOffsets(sizeof(SplitterInfo::InInfoPrams));
315 auto& info = GetInfo(header.send_id);
316 if (!RecomposeDestination(info, header, input, input_offset)) {
317 LOG_ERROR(Audio, "Failed to recompose destination for splitter!");
318 return false;
319 }
320 const std::size_t read = info.Update(header);
321 bytes_read += read;
322 input_offset += read;
323 }
324 return true;
325}
326
327bool SplitterContext::UpdateData(const std::vector<u8>& input, std::size_t& input_offset,
328 std::size_t& bytes_read, s32 in_data_count) {
329 auto UpdateOffsets = [&](std::size_t read) {
330 input_offset += read;
331 bytes_read += read;
332 };
333
334 for (s32 i = 0; i < in_data_count; i++) {
335 if (!AudioCommon::CanConsumeBuffer(input.size(), input_offset,
336 sizeof(SplitterInfo::InDestinationParams))) {
337 LOG_ERROR(Audio, "Buffer is an invalid size!");
338 return false;
339 }
340 SplitterInfo::InDestinationParams header{};
341 std::memcpy(&header, input.data() + input_offset,
342 sizeof(SplitterInfo::InDestinationParams));
343 UpdateOffsets(sizeof(SplitterInfo::InDestinationParams));
344
345 // Logged as warning as these don't actually cause a bailout for some reason
346 if (header.magic != SplitterMagic::DataHeader) {
347 LOG_ERROR(Audio, "Bad splitter data header");
348 break;
349 }
350
351 if (header.splitter_id < 0 || header.splitter_id > data_count) {
352 LOG_ERROR(Audio, "Bad splitter data id");
353 break;
354 }
355 GetData(header.splitter_id).Update(header);
356 }
357 return true;
358}
359
360bool SplitterContext::RecomposeDestination(ServerSplitterInfo& info,
361 SplitterInfo::InInfoPrams& header,
362 const std::vector<u8>& input,
363 const std::size_t& input_offset) {
364 // Clear our current destinations
365 auto* current_head = info.GetHead();
366 while (current_head != nullptr) {
367 auto next_head = current_head->GetNextDestination();
368 current_head->SetNextDestination(nullptr);
369 current_head = next_head;
370 }
371 info.SetHead(nullptr);
372
373 s32 size = header.length;
374 // If the splitter bug is present, calculate fixed size
375 if (!bug_fixed) {
376 if (info_count > 0) {
377 const auto factor = data_count / info_count;
378 size = std::min(header.length, static_cast<s32>(factor));
379 } else {
380 size = 0;
381 }
382 }
383
384 if (size < 1) {
385 LOG_ERROR(Audio, "Invalid splitter info size! size={:X}", size);
386 return true;
387 }
388
389 auto* start_head = &GetData(header.resource_id_base);
390 current_head = start_head;
391 std::vector<s32_le> resource_ids(size - 1);
392 if (!AudioCommon::CanConsumeBuffer(input.size(), input_offset,
393 resource_ids.size() * sizeof(s32_le))) {
394 LOG_ERROR(Audio, "Buffer is an invalid size!");
395 return false;
396 }
397 std::memcpy(resource_ids.data(), input.data() + input_offset,
398 resource_ids.size() * sizeof(s32_le));
399
400 for (auto resource_id : resource_ids) {
401 auto* head = &GetData(resource_id);
402 current_head->SetNextDestination(head);
403 current_head = head;
404 }
405
406 info.SetHead(start_head);
407 info.SetHeadDepth(size);
408
409 return true;
410}
411
412NodeStates::NodeStates() = default;
413NodeStates::~NodeStates() = default;
414
415void NodeStates::Initialize(std::size_t _node_count) {
416 // Setup our work parameters
417 node_count = _node_count;
418 was_node_found.resize(node_count);
419 was_node_completed.resize(node_count);
420 index_list.resize(node_count);
421 index_stack.Reset(node_count * node_count);
422}
423
424bool NodeStates::Tsort(EdgeMatrix& edge_matrix) {
425 return DepthFirstSearch(edge_matrix);
426}
427
428std::size_t NodeStates::GetIndexPos() const {
429 return index_pos;
430}
431
432const std::vector<s32>& NodeStates::GetIndexList() const {
433 return index_list;
434}
435
436void NodeStates::PushTsortResult(s32 index) {
437 ASSERT(index < node_count);
438 index_list[index_pos++] = index;
439}
440
441bool NodeStates::DepthFirstSearch(EdgeMatrix& edge_matrix) {
442 ResetState();
443 for (std::size_t i = 0; i < node_count; i++) {
444 const auto node_id = static_cast<s32>(i);
445
446 // If we don't have a state, send to our index stack for work
447 if (GetState(i) == NodeStates::State::NoState) {
448 index_stack.push(node_id);
449 }
450
451 // While we have work to do in our stack
452 while (index_stack.Count() > 0) {
453 // Get the current node
454 const auto current_stack_index = index_stack.top();
455 // Check if we've seen the node yet
456 const auto index_state = GetState(current_stack_index);
457 if (index_state == NodeStates::State::NoState) {
458 // Mark the node as seen
459 UpdateState(NodeStates::State::InFound, current_stack_index);
460 } else if (index_state == NodeStates::State::InFound) {
461 // We've seen this node before, mark it as completed
462 UpdateState(NodeStates::State::InCompleted, current_stack_index);
463 // Update our index list
464 PushTsortResult(current_stack_index);
465 // Pop the stack
466 index_stack.pop();
467 continue;
468 } else if (index_state == NodeStates::State::InCompleted) {
469 // If our node is already sorted, clear it
470 index_stack.pop();
471 continue;
472 }
473
474 const auto node_count = edge_matrix.GetNodeCount();
475 for (s32 j = 0; j < static_cast<s32>(node_count); j++) {
476 // Check if our node is connected to our edge matrix
477 if (!edge_matrix.Connected(current_stack_index, j)) {
478 continue;
479 }
480
481 // Check if our node exists
482 const auto node_state = GetState(j);
483 if (node_state == NodeStates::State::NoState) {
484 // Add more work
485 index_stack.push(j);
486 } else if (node_state == NodeStates::State::InFound) {
487 UNREACHABLE_MSG("Node start marked as found");
488 ResetState();
489 return false;
490 }
491 }
492 }
493 }
494 return true;
495}
496
497void NodeStates::ResetState() {
498 // Reset to the start of our index stack
499 index_pos = 0;
500 for (std::size_t i = 0; i < node_count; i++) {
501 // Mark all nodes as not found
502 was_node_found[i] = false;
503 // Mark all nodes as uncompleted
504 was_node_completed[i] = false;
505 // Mark all indexes as invalid
506 index_list[i] = -1;
507 }
508}
509
510void NodeStates::UpdateState(NodeStates::State state, std::size_t i) {
511 switch (state) {
512 case NodeStates::State::NoState:
513 was_node_found[i] = false;
514 was_node_completed[i] = false;
515 break;
516 case NodeStates::State::InFound:
517 was_node_found[i] = true;
518 was_node_completed[i] = false;
519 break;
520 case NodeStates::State::InCompleted:
521 was_node_found[i] = false;
522 was_node_completed[i] = true;
523 break;
524 }
525}
526
527NodeStates::State NodeStates::GetState(std::size_t i) {
528 ASSERT(i < node_count);
529 if (was_node_found[i]) {
530 // If our node exists in our found list
531 return NodeStates::State::InFound;
532 } else if (was_node_completed[i]) {
533 // If node is in the completed list
534 return NodeStates::State::InCompleted;
535 } else {
536 // If in neither
537 return NodeStates::State::NoState;
538 }
539}
540
541NodeStates::Stack::Stack() = default;
542NodeStates::Stack::~Stack() = default;
543
544void NodeStates::Stack::Reset(std::size_t size) {
545 // Mark our stack as empty
546 stack.resize(size);
547 stack_size = size;
548 stack_pos = 0;
549 std::fill(stack.begin(), stack.end(), 0);
550}
551
552void NodeStates::Stack::push(s32 val) {
553 ASSERT(stack_pos < stack_size);
554 stack[stack_pos++] = val;
555}
556
557std::size_t NodeStates::Stack::Count() const {
558 return stack_pos;
559}
560
561s32 NodeStates::Stack::top() const {
562 ASSERT(stack_pos > 0);
563 return stack[stack_pos - 1];
564}
565
566s32 NodeStates::Stack::pop() {
567 ASSERT(stack_pos > 0);
568 stack_pos--;
569 return stack[stack_pos];
570}
571
572EdgeMatrix::EdgeMatrix() = default;
573EdgeMatrix::~EdgeMatrix() = default;
574
575void EdgeMatrix::Initialize(std::size_t _node_count) {
576 node_count = _node_count;
577 edge_matrix.resize(node_count * node_count);
578}
579
580bool EdgeMatrix::Connected(s32 a, s32 b) {
581 return GetState(a, b);
582}
583
584void EdgeMatrix::Connect(s32 a, s32 b) {
585 SetState(a, b, true);
586}
587
588void EdgeMatrix::Disconnect(s32 a, s32 b) {
589 SetState(a, b, false);
590}
591
592void EdgeMatrix::RemoveEdges(s32 edge) {
593 for (std::size_t i = 0; i < node_count; i++) {
594 SetState(edge, static_cast<s32>(i), false);
595 }
596}
597
598std::size_t EdgeMatrix::GetNodeCount() const {
599 return node_count;
600}
601
602void EdgeMatrix::SetState(s32 a, s32 b, bool state) {
603 ASSERT(InRange(a, b));
604 edge_matrix.at(a * node_count + b) = state;
605}
606
607bool EdgeMatrix::GetState(s32 a, s32 b) {
608 ASSERT(InRange(a, b));
609 return edge_matrix.at(a * node_count + b);
610}
611
612bool EdgeMatrix::InRange(s32 a, s32 b) const {
613 const std::size_t pos = a * node_count + b;
614 return pos < (node_count * node_count);
615}
616
617} // namespace AudioCore
diff --git a/src/audio_core/splitter_context.h b/src/audio_core/splitter_context.h
new file mode 100644
index 000000000..ea6239fdb
--- /dev/null
+++ b/src/audio_core/splitter_context.h
@@ -0,0 +1,221 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <stack>
8#include <vector>
9#include "audio_core/common.h"
10#include "common/common_funcs.h"
11#include "common/common_types.h"
12#include "common/swap.h"
13
14namespace AudioCore {
15class BehaviorInfo;
16
17class EdgeMatrix {
18public:
19 EdgeMatrix();
20 ~EdgeMatrix();
21
22 void Initialize(std::size_t _node_count);
23 bool Connected(s32 a, s32 b);
24 void Connect(s32 a, s32 b);
25 void Disconnect(s32 a, s32 b);
26 void RemoveEdges(s32 edge);
27 std::size_t GetNodeCount() const;
28
29private:
30 void SetState(s32 a, s32 b, bool state);
31 bool GetState(s32 a, s32 b);
32
33 bool InRange(s32 a, s32 b) const;
34 std::vector<bool> edge_matrix{};
35 std::size_t node_count{};
36};
37
38class NodeStates {
39public:
40 enum class State {
41 NoState = 0,
42 InFound = 1,
43 InCompleted = 2,
44 };
45
46 // Looks to be a fixed size stack. Placed within the NodeStates class based on symbols
47 class Stack {
48 public:
49 Stack();
50 ~Stack();
51
52 void Reset(std::size_t size);
53 void push(s32 val);
54 std::size_t Count() const;
55 s32 top() const;
56 s32 pop();
57
58 private:
59 std::vector<s32> stack{};
60 std::size_t stack_size{};
61 std::size_t stack_pos{};
62 };
63 NodeStates();
64 ~NodeStates();
65
66 void Initialize(std::size_t _node_count);
67 bool Tsort(EdgeMatrix& edge_matrix);
68 std::size_t GetIndexPos() const;
69 const std::vector<s32>& GetIndexList() const;
70
71private:
72 void PushTsortResult(s32 index);
73 bool DepthFirstSearch(EdgeMatrix& edge_matrix);
74 void ResetState();
75 void UpdateState(NodeStates::State state, std::size_t i);
76 NodeStates::State GetState(std::size_t i);
77
78 std::size_t node_count{};
79 std::vector<bool> was_node_found{};
80 std::vector<bool> was_node_completed{};
81 std::size_t index_pos{};
82 std::vector<s32> index_list{};
83 NodeStates::Stack index_stack{};
84};
85
86enum class SplitterMagic : u32_le {
87 SplitterHeader = Common::MakeMagic('S', 'N', 'D', 'H'),
88 DataHeader = Common::MakeMagic('S', 'N', 'D', 'D'),
89 InfoHeader = Common::MakeMagic('S', 'N', 'D', 'I'),
90};
91
92class SplitterInfo {
93public:
94 struct InHeader {
95 SplitterMagic magic{};
96 s32_le info_count{};
97 s32_le data_count{};
98 INSERT_PADDING_WORDS(5);
99 };
100 static_assert(sizeof(SplitterInfo::InHeader) == 0x20,
101 "SplitterInfo::InHeader is an invalid size");
102
103 struct InInfoPrams {
104 SplitterMagic magic{};
105 s32_le send_id{};
106 s32_le sample_rate{};
107 s32_le length{};
108 s32_le resource_id_base{};
109 };
110 static_assert(sizeof(SplitterInfo::InInfoPrams) == 0x14,
111 "SplitterInfo::InInfoPrams is an invalid size");
112
113 struct InDestinationParams {
114 SplitterMagic magic{};
115 s32_le splitter_id{};
116 std::array<float_le, AudioCommon::MAX_MIX_BUFFERS> mix_volumes{};
117 s32_le mix_id{};
118 bool in_use{};
119 INSERT_PADDING_BYTES(3);
120 };
121 static_assert(sizeof(SplitterInfo::InDestinationParams) == 0x70,
122 "SplitterInfo::InDestinationParams is an invalid size");
123};
124
125class ServerSplitterDestinationData {
126public:
127 explicit ServerSplitterDestinationData(s32 id);
128 ~ServerSplitterDestinationData();
129
130 void Update(SplitterInfo::InDestinationParams& header);
131
132 ServerSplitterDestinationData* GetNextDestination();
133 const ServerSplitterDestinationData* GetNextDestination() const;
134 void SetNextDestination(ServerSplitterDestinationData* dest);
135 bool ValidMixId() const;
136 s32 GetMixId() const;
137 bool IsConfigured() const;
138 float GetMixVolume(std::size_t i) const;
139 const std::array<float, AudioCommon::MAX_MIX_BUFFERS>& CurrentMixVolumes() const;
140 const std::array<float, AudioCommon::MAX_MIX_BUFFERS>& LastMixVolumes() const;
141 void MarkDirty();
142 void UpdateInternalState();
143
144private:
145 bool needs_update{};
146 bool in_use{};
147 s32 id{};
148 s32 mix_id{};
149 std::array<float, AudioCommon::MAX_MIX_BUFFERS> current_mix_volumes{};
150 std::array<float, AudioCommon::MAX_MIX_BUFFERS> last_mix_volumes{};
151 ServerSplitterDestinationData* next = nullptr;
152};
153
154class ServerSplitterInfo {
155public:
156 explicit ServerSplitterInfo(s32 id);
157 ~ServerSplitterInfo();
158
159 void InitializeInfos();
160 void ClearNewConnectionFlag();
161 std::size_t Update(SplitterInfo::InInfoPrams& header);
162
163 ServerSplitterDestinationData* GetHead();
164 const ServerSplitterDestinationData* GetHead() const;
165 ServerSplitterDestinationData* GetData(std::size_t depth);
166 const ServerSplitterDestinationData* GetData(std::size_t depth) const;
167
168 bool HasNewConnection() const;
169 s32 GetLength() const;
170
171 void SetHead(ServerSplitterDestinationData* new_head);
172 void SetHeadDepth(s32 length);
173
174private:
175 s32 sample_rate{};
176 s32 id{};
177 s32 send_length{};
178 ServerSplitterDestinationData* head = nullptr;
179 bool new_connection{};
180};
181
182class SplitterContext {
183public:
184 SplitterContext();
185 ~SplitterContext();
186
187 void Initialize(BehaviorInfo& behavior_info, std::size_t splitter_count,
188 std::size_t data_count);
189
190 bool Update(const std::vector<u8>& input, std::size_t& input_offset, std::size_t& bytes_read);
191 bool UsingSplitter() const;
192
193 ServerSplitterInfo& GetInfo(std::size_t i);
194 const ServerSplitterInfo& GetInfo(std::size_t i) const;
195 ServerSplitterDestinationData& GetData(std::size_t i);
196 const ServerSplitterDestinationData& GetData(std::size_t i) const;
197 ServerSplitterDestinationData* GetDestinationData(std::size_t info, std::size_t data);
198 const ServerSplitterDestinationData* GetDestinationData(std::size_t info,
199 std::size_t data) const;
200 void UpdateInternalState();
201
202 std::size_t GetInfoCount() const;
203 std::size_t GetDataCount() const;
204
205private:
206 void Setup(std::size_t info_count, std::size_t data_count, bool is_splitter_bug_fixed);
207 bool UpdateInfo(const std::vector<u8>& input, std::size_t& input_offset,
208 std::size_t& bytes_read, s32 in_splitter_count);
209 bool UpdateData(const std::vector<u8>& input, std::size_t& input_offset,
210 std::size_t& bytes_read, s32 in_data_count);
211 bool RecomposeDestination(ServerSplitterInfo& info, SplitterInfo::InInfoPrams& header,
212 const std::vector<u8>& input, const std::size_t& input_offset);
213
214 std::vector<ServerSplitterInfo> infos{};
215 std::vector<ServerSplitterDestinationData> datas{};
216
217 std::size_t info_count{};
218 std::size_t data_count{};
219 bool bug_fixed{};
220};
221} // namespace AudioCore
diff --git a/src/audio_core/voice_context.cpp b/src/audio_core/voice_context.cpp
new file mode 100644
index 000000000..038595ae0
--- /dev/null
+++ b/src/audio_core/voice_context.cpp
@@ -0,0 +1,531 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include "audio_core/behavior_info.h"
6#include "audio_core/voice_context.h"
7#include "core/memory.h"
8
9namespace AudioCore {
10
11ServerVoiceChannelResource::ServerVoiceChannelResource(s32 id) : id(id) {}
12ServerVoiceChannelResource::~ServerVoiceChannelResource() = default;
13
14bool ServerVoiceChannelResource::InUse() const {
15 return in_use;
16}
17
18float ServerVoiceChannelResource::GetCurrentMixVolumeAt(std::size_t i) const {
19 ASSERT(i < AudioCommon::MAX_MIX_BUFFERS);
20 return mix_volume.at(i);
21}
22
23float ServerVoiceChannelResource::GetLastMixVolumeAt(std::size_t i) const {
24 ASSERT(i < AudioCommon::MAX_MIX_BUFFERS);
25 return last_mix_volume.at(i);
26}
27
28void ServerVoiceChannelResource::Update(VoiceChannelResource::InParams& in_params) {
29 in_use = in_params.in_use;
30 // Update our mix volumes only if it's in use
31 if (in_params.in_use) {
32 std::copy(in_params.mix_volume.begin(), in_params.mix_volume.end(), mix_volume.begin());
33 }
34}
35
36void ServerVoiceChannelResource::UpdateLastMixVolumes() {
37 std::copy(mix_volume.begin(), mix_volume.end(), last_mix_volume.begin());
38}
39
40const std::array<float, AudioCommon::MAX_MIX_BUFFERS>&
41ServerVoiceChannelResource::GetCurrentMixVolume() const {
42 return mix_volume;
43}
44
45const std::array<float, AudioCommon::MAX_MIX_BUFFERS>&
46ServerVoiceChannelResource::GetLastMixVolume() const {
47 return last_mix_volume;
48}
49
50ServerVoiceInfo::ServerVoiceInfo() {
51 Initialize();
52}
53ServerVoiceInfo::~ServerVoiceInfo() = default;
54
55void ServerVoiceInfo::Initialize() {
56 in_params.in_use = false;
57 in_params.node_id = 0;
58 in_params.id = 0;
59 in_params.current_playstate = ServerPlayState::Stop;
60 in_params.priority = 255;
61 in_params.sample_rate = 0;
62 in_params.sample_format = SampleFormat::Invalid;
63 in_params.channel_count = 0;
64 in_params.pitch = 0.0f;
65 in_params.volume = 0.0f;
66 in_params.last_volume = 0.0f;
67 std::memset(in_params.biquad_filter.data(), 0,
68 sizeof(BiquadFilterParameter) * in_params.biquad_filter.size());
69 in_params.wave_buffer_count = 0;
70 in_params.wave_bufffer_head = 0;
71 in_params.mix_id = AudioCommon::NO_MIX;
72 in_params.splitter_info_id = AudioCommon::NO_SPLITTER;
73 in_params.additional_params_address = 0;
74 in_params.additional_params_size = 0;
75 in_params.is_new = false;
76 out_params.played_sample_count = 0;
77 out_params.wave_buffer_consumed = 0;
78 in_params.voice_drop_flag = false;
79 in_params.buffer_mapped = false;
80 in_params.wave_buffer_flush_request_count = 0;
81 std::fill(in_params.was_biquad_filter_enabled.begin(),
82 in_params.was_biquad_filter_enabled.end(), false);
83
84 for (auto& wave_buffer : in_params.wave_buffer) {
85 wave_buffer.start_sample_offset = 0;
86 wave_buffer.end_sample_offset = 0;
87 wave_buffer.is_looping = false;
88 wave_buffer.end_of_stream = false;
89 wave_buffer.buffer_address = 0;
90 wave_buffer.buffer_size = 0;
91 wave_buffer.context_address = 0;
92 wave_buffer.context_size = 0;
93 wave_buffer.sent_to_dsp = true;
94 }
95
96 stored_samples.clear();
97}
98
99void ServerVoiceInfo::UpdateParameters(const VoiceInfo::InParams& voice_in,
100 BehaviorInfo& behavior_info) {
101 in_params.in_use = voice_in.is_in_use;
102 in_params.id = voice_in.id;
103 in_params.node_id = voice_in.node_id;
104 in_params.last_playstate = in_params.current_playstate;
105 switch (voice_in.play_state) {
106 case PlayState::Paused:
107 in_params.current_playstate = ServerPlayState::Paused;
108 break;
109 case PlayState::Stopped:
110 if (in_params.current_playstate != ServerPlayState::Stop) {
111 in_params.current_playstate = ServerPlayState::RequestStop;
112 }
113 break;
114 case PlayState::Started:
115 in_params.current_playstate = ServerPlayState::Play;
116 break;
117 default:
118 UNREACHABLE_MSG("Unknown playstate {}", voice_in.play_state);
119 break;
120 }
121
122 in_params.priority = voice_in.priority;
123 in_params.sorting_order = voice_in.sorting_order;
124 in_params.sample_rate = voice_in.sample_rate;
125 in_params.sample_format = voice_in.sample_format;
126 in_params.channel_count = voice_in.channel_count;
127 in_params.pitch = voice_in.pitch;
128 in_params.volume = voice_in.volume;
129 std::memcpy(in_params.biquad_filter.data(), voice_in.biquad_filter.data(),
130 sizeof(BiquadFilterParameter) * voice_in.biquad_filter.size());
131 in_params.wave_buffer_count = voice_in.wave_buffer_count;
132 in_params.wave_bufffer_head = voice_in.wave_buffer_head;
133 if (behavior_info.IsFlushVoiceWaveBuffersSupported()) {
134 in_params.wave_buffer_flush_request_count += voice_in.wave_buffer_flush_request_count;
135 }
136 in_params.mix_id = voice_in.mix_id;
137 if (behavior_info.IsSplitterSupported()) {
138 in_params.splitter_info_id = voice_in.splitter_info_id;
139 } else {
140 in_params.splitter_info_id = AudioCommon::NO_SPLITTER;
141 }
142
143 std::memcpy(in_params.voice_channel_resource_id.data(),
144 voice_in.voice_channel_resource_ids.data(),
145 sizeof(s32) * in_params.voice_channel_resource_id.size());
146
147 if (behavior_info.IsVoicePlayedSampleCountResetAtLoopPointSupported()) {
148 in_params.behavior_flags.is_played_samples_reset_at_loop_point =
149 voice_in.behavior_flags.is_played_samples_reset_at_loop_point;
150 } else {
151 in_params.behavior_flags.is_played_samples_reset_at_loop_point.Assign(0);
152 }
153 if (behavior_info.IsVoicePitchAndSrcSkippedSupported()) {
154 in_params.behavior_flags.is_pitch_and_src_skipped =
155 voice_in.behavior_flags.is_pitch_and_src_skipped;
156 } else {
157 in_params.behavior_flags.is_pitch_and_src_skipped.Assign(0);
158 }
159
160 if (voice_in.is_voice_drop_flag_clear_requested) {
161 in_params.voice_drop_flag = false;
162 }
163
164 if (in_params.additional_params_address != voice_in.additional_params_address ||
165 in_params.additional_params_size != voice_in.additional_params_size) {
166 in_params.additional_params_address = voice_in.additional_params_address;
167 in_params.additional_params_size = voice_in.additional_params_size;
168 // TODO(ogniK): Reattach buffer, do we actually need to? Maybe just signal to the DSP that
169 // our context is new
170 }
171}
172
173void ServerVoiceInfo::UpdateWaveBuffers(
174 const VoiceInfo::InParams& voice_in,
175 std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& voice_states,
176 BehaviorInfo& behavior_info) {
177 if (voice_in.is_new) {
178 // Initialize our wave buffers
179 for (auto& wave_buffer : in_params.wave_buffer) {
180 wave_buffer.start_sample_offset = 0;
181 wave_buffer.end_sample_offset = 0;
182 wave_buffer.is_looping = false;
183 wave_buffer.end_of_stream = false;
184 wave_buffer.buffer_address = 0;
185 wave_buffer.buffer_size = 0;
186 wave_buffer.context_address = 0;
187 wave_buffer.context_size = 0;
188 wave_buffer.sent_to_dsp = true;
189 }
190
191 // Mark all our wave buffers as invalid
192 for (std::size_t channel = 0; channel < static_cast<std::size_t>(in_params.channel_count);
193 channel++) {
194 for (auto& is_valid : voice_states[channel]->is_wave_buffer_valid) {
195 is_valid = false;
196 }
197 }
198 }
199
200 // Update our wave buffers
201 for (std::size_t i = 0; i < AudioCommon::MAX_WAVE_BUFFERS; i++) {
202 // Assume that we have at least 1 channel voice state
203 const auto have_valid_wave_buffer = voice_states[0]->is_wave_buffer_valid[i];
204
205 UpdateWaveBuffer(in_params.wave_buffer[i], voice_in.wave_buffer[i], in_params.sample_format,
206 have_valid_wave_buffer, behavior_info);
207 }
208}
209
210void ServerVoiceInfo::UpdateWaveBuffer(ServerWaveBuffer& out_wavebuffer,
211 const WaveBuffer& in_wave_buffer, SampleFormat sample_format,
212 bool is_buffer_valid, BehaviorInfo& behavior_info) {
213 if (!is_buffer_valid && out_wavebuffer.sent_to_dsp) {
214 out_wavebuffer.buffer_address = 0;
215 out_wavebuffer.buffer_size = 0;
216 }
217
218 if (!in_wave_buffer.sent_to_server || !in_params.buffer_mapped) {
219 // Validate sample offset sizings
220 if (sample_format == SampleFormat::Pcm16) {
221 const auto buffer_size = in_wave_buffer.buffer_size;
222 if (in_wave_buffer.start_sample_offset < 0 || in_wave_buffer.end_sample_offset < 0 ||
223 (buffer_size < (sizeof(s16) * in_wave_buffer.start_sample_offset)) ||
224 (buffer_size < (sizeof(s16) * in_wave_buffer.end_sample_offset))) {
225 // TODO(ogniK): Write error info
226 return;
227 }
228 }
229 // TODO(ogniK): ADPCM Size error
230
231 out_wavebuffer.sent_to_dsp = false;
232 out_wavebuffer.start_sample_offset = in_wave_buffer.start_sample_offset;
233 out_wavebuffer.end_sample_offset = in_wave_buffer.end_sample_offset;
234 out_wavebuffer.is_looping = in_wave_buffer.is_looping;
235 out_wavebuffer.end_of_stream = in_wave_buffer.end_of_stream;
236
237 out_wavebuffer.buffer_address = in_wave_buffer.buffer_address;
238 out_wavebuffer.buffer_size = in_wave_buffer.buffer_size;
239 out_wavebuffer.context_address = in_wave_buffer.context_address;
240 out_wavebuffer.context_size = in_wave_buffer.context_size;
241 in_params.buffer_mapped =
242 in_wave_buffer.buffer_address != 0 && in_wave_buffer.buffer_size != 0;
243 // TODO(ogniK): Pool mapper attachment
244 // TODO(ogniK): IsAdpcmLoopContextBugFixed
245 }
246}
247
248void ServerVoiceInfo::WriteOutStatus(
249 VoiceInfo::OutParams& voice_out, VoiceInfo::InParams& voice_in,
250 std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& voice_states) {
251 if (voice_in.is_new) {
252 in_params.is_new = true;
253 voice_out.wave_buffer_consumed = 0;
254 voice_out.played_sample_count = 0;
255 voice_out.voice_dropped = false;
256 } else if (!in_params.is_new) {
257 voice_out.wave_buffer_consumed = voice_states[0]->wave_buffer_consumed;
258 voice_out.played_sample_count = voice_states[0]->played_sample_count;
259 voice_out.voice_dropped = in_params.voice_drop_flag;
260 } else {
261 voice_out.wave_buffer_consumed = 0;
262 voice_out.played_sample_count = 0;
263 voice_out.voice_dropped = false;
264 }
265}
266
267const ServerVoiceInfo::InParams& ServerVoiceInfo::GetInParams() const {
268 return in_params;
269}
270
271ServerVoiceInfo::InParams& ServerVoiceInfo::GetInParams() {
272 return in_params;
273}
274
275const ServerVoiceInfo::OutParams& ServerVoiceInfo::GetOutParams() const {
276 return out_params;
277}
278
279ServerVoiceInfo::OutParams& ServerVoiceInfo::GetOutParams() {
280 return out_params;
281}
282
283bool ServerVoiceInfo::ShouldSkip() const {
284 // TODO(ogniK): Handle unmapped wave buffers or parameters
285 return !in_params.in_use || (in_params.wave_buffer_count == 0) || in_params.voice_drop_flag;
286}
287
288bool ServerVoiceInfo::UpdateForCommandGeneration(VoiceContext& voice_context) {
289 std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT> dsp_voice_states{};
290 if (in_params.is_new) {
291 ResetResources(voice_context);
292 in_params.last_volume = in_params.volume;
293 in_params.is_new = false;
294 }
295
296 const s32 channel_count = in_params.channel_count;
297 for (s32 i = 0; i < channel_count; i++) {
298 const auto channel_resource = in_params.voice_channel_resource_id[i];
299 dsp_voice_states[i] =
300 &voice_context.GetDspSharedState(static_cast<std::size_t>(channel_resource));
301 }
302 return UpdateParametersForCommandGeneration(dsp_voice_states);
303}
304
305void ServerVoiceInfo::ResetResources(VoiceContext& voice_context) {
306 const s32 channel_count = in_params.channel_count;
307 for (s32 i = 0; i < channel_count; i++) {
308 const auto channel_resource = in_params.voice_channel_resource_id[i];
309 auto& dsp_state =
310 voice_context.GetDspSharedState(static_cast<std::size_t>(channel_resource));
311 std::memset(&dsp_state, 0, sizeof(VoiceState));
312 voice_context.GetChannelResource(static_cast<std::size_t>(channel_resource))
313 .UpdateLastMixVolumes();
314 }
315}
316
317bool ServerVoiceInfo::UpdateParametersForCommandGeneration(
318 std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& dsp_voice_states) {
319 const s32 channel_count = in_params.channel_count;
320 if (in_params.wave_buffer_flush_request_count > 0) {
321 FlushWaveBuffers(in_params.wave_buffer_flush_request_count, dsp_voice_states,
322 channel_count);
323 in_params.wave_buffer_flush_request_count = 0;
324 }
325
326 switch (in_params.current_playstate) {
327 case ServerPlayState::Play: {
328 for (std::size_t i = 0; i < AudioCommon::MAX_WAVE_BUFFERS; i++) {
329 if (!in_params.wave_buffer[i].sent_to_dsp) {
330 for (s32 channel = 0; channel < channel_count; channel++) {
331 dsp_voice_states[channel]->is_wave_buffer_valid[i] = true;
332 }
333 in_params.wave_buffer[i].sent_to_dsp = true;
334 }
335 }
336 in_params.should_depop = false;
337 return HasValidWaveBuffer(dsp_voice_states[0]);
338 }
339 case ServerPlayState::Paused:
340 case ServerPlayState::Stop: {
341 in_params.should_depop = in_params.last_playstate == ServerPlayState::Play;
342 return in_params.should_depop;
343 }
344 case ServerPlayState::RequestStop: {
345 for (std::size_t i = 0; i < AudioCommon::MAX_WAVE_BUFFERS; i++) {
346 in_params.wave_buffer[i].sent_to_dsp = true;
347 for (s32 channel = 0; channel < channel_count; channel++) {
348 auto* dsp_state = dsp_voice_states[channel];
349
350 if (dsp_state->is_wave_buffer_valid[i]) {
351 dsp_state->wave_buffer_index =
352 (dsp_state->wave_buffer_index + 1) % AudioCommon::MAX_WAVE_BUFFERS;
353 dsp_state->wave_buffer_consumed++;
354 }
355
356 dsp_state->is_wave_buffer_valid[i] = false;
357 }
358 }
359
360 for (s32 channel = 0; channel < channel_count; channel++) {
361 auto* dsp_state = dsp_voice_states[channel];
362 dsp_state->offset = 0;
363 dsp_state->played_sample_count = 0;
364 dsp_state->fraction = 0;
365 std::memset(dsp_state->sample_history.data(), 0,
366 sizeof(s32) * dsp_state->sample_history.size());
367 std::memset(&dsp_state->context, 0, sizeof(dsp_state->context));
368 }
369
370 in_params.current_playstate = ServerPlayState::Stop;
371 in_params.should_depop = in_params.last_playstate == ServerPlayState::Play;
372 return in_params.should_depop;
373 }
374 default:
375 UNREACHABLE_MSG("Invalid playstate {}", in_params.current_playstate);
376 }
377
378 return false;
379}
380
381void ServerVoiceInfo::FlushWaveBuffers(
382 u8 flush_count, std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& dsp_voice_states,
383 s32 channel_count) {
384 auto wave_head = in_params.wave_bufffer_head;
385
386 for (u8 i = 0; i < flush_count; i++) {
387 in_params.wave_buffer[wave_head].sent_to_dsp = true;
388 for (s32 channel = 0; channel < channel_count; channel++) {
389 auto* dsp_state = dsp_voice_states[channel];
390 dsp_state->wave_buffer_consumed++;
391 dsp_state->is_wave_buffer_valid[wave_head] = false;
392 dsp_state->wave_buffer_index =
393 (dsp_state->wave_buffer_index + 1) % AudioCommon::MAX_WAVE_BUFFERS;
394 }
395 wave_head = (wave_head + 1) % AudioCommon::MAX_WAVE_BUFFERS;
396 }
397}
398
399bool ServerVoiceInfo::HasValidWaveBuffer(const VoiceState* state) const {
400 const auto& valid_wb = state->is_wave_buffer_valid;
401 return std::find(valid_wb.begin(), valid_wb.end(), true) != valid_wb.end();
402}
403
404VoiceContext::VoiceContext(std::size_t voice_count) : voice_count(voice_count) {
405 for (std::size_t i = 0; i < voice_count; i++) {
406 voice_channel_resources.emplace_back(static_cast<s32>(i));
407 sorted_voice_info.push_back(&voice_info.emplace_back());
408 voice_states.emplace_back();
409 dsp_voice_states.emplace_back();
410 }
411}
412
413VoiceContext::~VoiceContext() {
414 sorted_voice_info.clear();
415}
416
417std::size_t VoiceContext::GetVoiceCount() const {
418 return voice_count;
419}
420
421ServerVoiceChannelResource& VoiceContext::GetChannelResource(std::size_t i) {
422 ASSERT(i < voice_count);
423 return voice_channel_resources.at(i);
424}
425
426const ServerVoiceChannelResource& VoiceContext::GetChannelResource(std::size_t i) const {
427 ASSERT(i < voice_count);
428 return voice_channel_resources.at(i);
429}
430
431VoiceState& VoiceContext::GetState(std::size_t i) {
432 ASSERT(i < voice_count);
433 return voice_states.at(i);
434}
435
436const VoiceState& VoiceContext::GetState(std::size_t i) const {
437 ASSERT(i < voice_count);
438 return voice_states.at(i);
439}
440
441VoiceState& VoiceContext::GetDspSharedState(std::size_t i) {
442 ASSERT(i < voice_count);
443 return dsp_voice_states.at(i);
444}
445
446const VoiceState& VoiceContext::GetDspSharedState(std::size_t i) const {
447 ASSERT(i < voice_count);
448 return dsp_voice_states.at(i);
449}
450
451ServerVoiceInfo& VoiceContext::GetInfo(std::size_t i) {
452 ASSERT(i < voice_count);
453 return voice_info.at(i);
454}
455
456const ServerVoiceInfo& VoiceContext::GetInfo(std::size_t i) const {
457 ASSERT(i < voice_count);
458 return voice_info.at(i);
459}
460
461ServerVoiceInfo& VoiceContext::GetSortedInfo(std::size_t i) {
462 ASSERT(i < voice_count);
463 return *sorted_voice_info.at(i);
464}
465
466const ServerVoiceInfo& VoiceContext::GetSortedInfo(std::size_t i) const {
467 ASSERT(i < voice_count);
468 return *sorted_voice_info.at(i);
469}
470
471s32 VoiceContext::DecodePcm16(s32* output_buffer, ServerWaveBuffer* wave_buffer, s32 channel,
472 s32 channel_count, s32 buffer_offset, s32 sample_count,
473 Core::Memory::Memory& memory) {
474 if (wave_buffer->buffer_address == 0) {
475 return 0;
476 }
477 if (wave_buffer->buffer_size == 0) {
478 return 0;
479 }
480 if (wave_buffer->end_sample_offset < wave_buffer->start_sample_offset) {
481 return 0;
482 }
483
484 const auto samples_remaining =
485 (wave_buffer->end_sample_offset - wave_buffer->start_sample_offset) - buffer_offset;
486 const auto start_offset = (wave_buffer->start_sample_offset + buffer_offset) * channel_count;
487 const auto buffer_pos = wave_buffer->buffer_address + start_offset;
488
489 s16* buffer_data = reinterpret_cast<s16*>(memory.GetPointer(buffer_pos));
490
491 const auto samples_processed = std::min(sample_count, samples_remaining);
492
493 // Fast path
494 if (channel_count == 1) {
495 for (std::size_t i = 0; i < samples_processed; i++) {
496 output_buffer[i] = buffer_data[i];
497 }
498 } else {
499 for (std::size_t i = 0; i < samples_processed; i++) {
500 output_buffer[i] = buffer_data[i * channel_count + channel];
501 }
502 }
503
504 return samples_processed;
505}
506
507void VoiceContext::SortInfo() {
508 for (std::size_t i = 0; i < voice_count; i++) {
509 sorted_voice_info[i] = &voice_info[i];
510 }
511
512 std::sort(sorted_voice_info.begin(), sorted_voice_info.end(),
513 [](const ServerVoiceInfo* lhs, const ServerVoiceInfo* rhs) {
514 const auto& lhs_in = lhs->GetInParams();
515 const auto& rhs_in = rhs->GetInParams();
516 // Sort by priority
517 if (lhs_in.priority != rhs_in.priority) {
518 return lhs_in.priority > rhs_in.priority;
519 } else {
520 // If the priorities match, sort by sorting order
521 return lhs_in.sorting_order > rhs_in.sorting_order;
522 }
523 });
524}
525
526void VoiceContext::UpdateStateByDspShared() {
527 std::memcpy(voice_states.data(), dsp_voice_states.data(),
528 sizeof(VoiceState) * dsp_voice_states.size());
529}
530
531} // namespace AudioCore
diff --git a/src/audio_core/voice_context.h b/src/audio_core/voice_context.h
new file mode 100644
index 000000000..b1d554766
--- /dev/null
+++ b/src/audio_core/voice_context.h
@@ -0,0 +1,291 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <array>
8#include "audio_core/algorithm/interpolate.h"
9#include "audio_core/codec.h"
10#include "audio_core/common.h"
11#include "common/bit_field.h"
12#include "common/common_funcs.h"
13#include "common/common_types.h"
14
15namespace Core::Memory {
16class Memory;
17}
18
19namespace AudioCore {
20
21class BehaviorInfo;
22class VoiceContext;
23
24enum class SampleFormat : u8 {
25 Invalid = 0,
26 Pcm8 = 1,
27 Pcm16 = 2,
28 Pcm24 = 3,
29 Pcm32 = 4,
30 PcmFloat = 5,
31 Adpcm = 6,
32};
33
34enum class PlayState : u8 {
35 Started = 0,
36 Stopped = 1,
37 Paused = 2,
38};
39
40enum class ServerPlayState {
41 Play = 0,
42 Stop = 1,
43 RequestStop = 2,
44 Paused = 3,
45};
46
47struct BiquadFilterParameter {
48 bool enabled{};
49 INSERT_PADDING_BYTES(1);
50 std::array<s16, 3> numerator{};
51 std::array<s16, 2> denominator{};
52};
53static_assert(sizeof(BiquadFilterParameter) == 0xc, "BiquadFilterParameter is an invalid size");
54
55struct WaveBuffer {
56 u64_le buffer_address{};
57 u64_le buffer_size{};
58 s32_le start_sample_offset{};
59 s32_le end_sample_offset{};
60 u8 is_looping{};
61 u8 end_of_stream{};
62 u8 sent_to_server{};
63 INSERT_PADDING_BYTES(5);
64 u64 context_address{};
65 u64 context_size{};
66 INSERT_PADDING_BYTES(8);
67};
68static_assert(sizeof(WaveBuffer) == 0x38, "WaveBuffer is an invalid size");
69
70struct ServerWaveBuffer {
71 VAddr buffer_address{};
72 std::size_t buffer_size{};
73 s32 start_sample_offset{};
74 s32 end_sample_offset{};
75 bool is_looping{};
76 bool end_of_stream{};
77 VAddr context_address{};
78 std::size_t context_size{};
79 bool sent_to_dsp{true};
80};
81
82struct BehaviorFlags {
83 BitField<0, 1, u16> is_played_samples_reset_at_loop_point;
84 BitField<1, 1, u16> is_pitch_and_src_skipped;
85};
86static_assert(sizeof(BehaviorFlags) == 0x4, "BehaviorFlags is an invalid size");
87
88struct VoiceState {
89 s64 played_sample_count{};
90 s32 offset{};
91 s32 wave_buffer_index{};
92 std::array<bool, AudioCommon::MAX_WAVE_BUFFERS> is_wave_buffer_valid{};
93 s32 wave_buffer_consumed{};
94 std::array<s32, AudioCommon::MAX_SAMPLE_HISTORY> sample_history{};
95 s32 fraction{};
96 VAddr context_address{};
97 Codec::ADPCM_Coeff coeff{};
98 Codec::ADPCMState context{};
99 std::array<s64, 2> biquad_filter_state{};
100 std::array<s32, AudioCommon::MAX_MIX_BUFFERS> previous_samples{};
101 u32 external_context_size{};
102 bool is_external_context_used{};
103 bool voice_dropped{};
104 // TODO(ogniK): Hack until ADPCM streaming is implemented
105 std::vector<s16> adpcm_samples{};
106};
107
108class VoiceChannelResource {
109public:
110 struct InParams {
111 s32_le id{};
112 std::array<float_le, AudioCommon::MAX_MIX_BUFFERS> mix_volume{};
113 bool in_use{};
114 INSERT_PADDING_BYTES(11);
115 };
116 static_assert(sizeof(VoiceChannelResource::InParams) == 0x70, "InParams is an invalid size");
117};
118
119class ServerVoiceChannelResource {
120public:
121 explicit ServerVoiceChannelResource(s32 id);
122 ~ServerVoiceChannelResource();
123
124 bool InUse() const;
125 float GetCurrentMixVolumeAt(std::size_t i) const;
126 float GetLastMixVolumeAt(std::size_t i) const;
127 void Update(VoiceChannelResource::InParams& in_params);
128 void UpdateLastMixVolumes();
129
130 const std::array<float, AudioCommon::MAX_MIX_BUFFERS>& GetCurrentMixVolume() const;
131 const std::array<float, AudioCommon::MAX_MIX_BUFFERS>& GetLastMixVolume() const;
132
133private:
134 s32 id{};
135 std::array<float, AudioCommon::MAX_MIX_BUFFERS> mix_volume{};
136 std::array<float, AudioCommon::MAX_MIX_BUFFERS> last_mix_volume{};
137 bool in_use{};
138};
139
140class VoiceInfo {
141public:
142 struct InParams {
143 s32_le id{};
144 u32_le node_id{};
145 u8 is_new{};
146 u8 is_in_use{};
147 PlayState play_state{};
148 SampleFormat sample_format{};
149 s32_le sample_rate{};
150 s32_le priority{};
151 s32_le sorting_order{};
152 s32_le channel_count{};
153 float_le pitch{};
154 float_le volume{};
155 std::array<BiquadFilterParameter, 2> biquad_filter{};
156 s32_le wave_buffer_count{};
157 s16_le wave_buffer_head{};
158 INSERT_PADDING_BYTES(6);
159 u64_le additional_params_address{};
160 u64_le additional_params_size{};
161 s32_le mix_id{};
162 s32_le splitter_info_id{};
163 std::array<WaveBuffer, 4> wave_buffer{};
164 std::array<u32_le, 6> voice_channel_resource_ids{};
165 // TODO(ogniK): Remaining flags
166 u8 is_voice_drop_flag_clear_requested{};
167 u8 wave_buffer_flush_request_count{};
168 INSERT_PADDING_BYTES(2);
169 BehaviorFlags behavior_flags{};
170 INSERT_PADDING_BYTES(16);
171 };
172 static_assert(sizeof(VoiceInfo::InParams) == 0x170, "InParams is an invalid size");
173
174 struct OutParams {
175 u64_le played_sample_count{};
176 u32_le wave_buffer_consumed{};
177 u8 voice_dropped{};
178 INSERT_PADDING_BYTES(3);
179 };
180 static_assert(sizeof(VoiceInfo::OutParams) == 0x10, "OutParams is an invalid size");
181};
182
183class ServerVoiceInfo {
184public:
185 struct InParams {
186 bool in_use{};
187 bool is_new{};
188 bool should_depop{};
189 SampleFormat sample_format{};
190 s32 sample_rate{};
191 s32 channel_count{};
192 s32 id{};
193 s32 node_id{};
194 s32 mix_id{};
195 ServerPlayState current_playstate{};
196 ServerPlayState last_playstate{};
197 s32 priority{};
198 s32 sorting_order{};
199 float pitch{};
200 float volume{};
201 float last_volume{};
202 std::array<BiquadFilterParameter, AudioCommon::MAX_BIQUAD_FILTERS> biquad_filter{};
203 s32 wave_buffer_count{};
204 s16 wave_bufffer_head{};
205 INSERT_PADDING_BYTES(2);
206 BehaviorFlags behavior_flags{};
207 VAddr additional_params_address{};
208 std::size_t additional_params_size{};
209 std::array<ServerWaveBuffer, AudioCommon::MAX_WAVE_BUFFERS> wave_buffer{};
210 std::array<s32, AudioCommon::MAX_CHANNEL_COUNT> voice_channel_resource_id{};
211 s32 splitter_info_id{};
212 u8 wave_buffer_flush_request_count{};
213 bool voice_drop_flag{};
214 bool buffer_mapped{};
215 std::array<bool, AudioCommon::MAX_BIQUAD_FILTERS> was_biquad_filter_enabled{};
216 };
217
218 struct OutParams {
219 s64 played_sample_count{};
220 s32 wave_buffer_consumed{};
221 };
222
223 ServerVoiceInfo();
224 ~ServerVoiceInfo();
225 void Initialize();
226 void UpdateParameters(const VoiceInfo::InParams& voice_in, BehaviorInfo& behavior_info);
227 void UpdateWaveBuffers(const VoiceInfo::InParams& voice_in,
228 std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& voice_states,
229 BehaviorInfo& behavior_info);
230 void UpdateWaveBuffer(ServerWaveBuffer& out_wavebuffer, const WaveBuffer& in_wave_buffer,
231 SampleFormat sample_format, bool is_buffer_valid,
232 BehaviorInfo& behavior_info);
233 void WriteOutStatus(VoiceInfo::OutParams& voice_out, VoiceInfo::InParams& voice_in,
234 std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& voice_states);
235
236 const InParams& GetInParams() const;
237 InParams& GetInParams();
238
239 const OutParams& GetOutParams() const;
240 OutParams& GetOutParams();
241
242 bool ShouldSkip() const;
243 bool UpdateForCommandGeneration(VoiceContext& voice_context);
244 void ResetResources(VoiceContext& voice_context);
245 bool UpdateParametersForCommandGeneration(
246 std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& dsp_voice_states);
247 void FlushWaveBuffers(u8 flush_count,
248 std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& dsp_voice_states,
249 s32 channel_count);
250
251private:
252 std::vector<s16> stored_samples;
253 InParams in_params{};
254 OutParams out_params{};
255
256 bool HasValidWaveBuffer(const VoiceState* state) const;
257};
258
259class VoiceContext {
260public:
261 VoiceContext(std::size_t voice_count);
262 ~VoiceContext();
263
264 std::size_t GetVoiceCount() const;
265 ServerVoiceChannelResource& GetChannelResource(std::size_t i);
266 const ServerVoiceChannelResource& GetChannelResource(std::size_t i) const;
267 VoiceState& GetState(std::size_t i);
268 const VoiceState& GetState(std::size_t i) const;
269 VoiceState& GetDspSharedState(std::size_t i);
270 const VoiceState& GetDspSharedState(std::size_t i) const;
271 ServerVoiceInfo& GetInfo(std::size_t i);
272 const ServerVoiceInfo& GetInfo(std::size_t i) const;
273 ServerVoiceInfo& GetSortedInfo(std::size_t i);
274 const ServerVoiceInfo& GetSortedInfo(std::size_t i) const;
275
276 s32 DecodePcm16(s32* output_buffer, ServerWaveBuffer* wave_buffer, s32 channel,
277 s32 channel_count, s32 buffer_offset, s32 sample_count,
278 Core::Memory::Memory& memory);
279 void SortInfo();
280 void UpdateStateByDspShared();
281
282private:
283 std::size_t voice_count{};
284 std::vector<ServerVoiceChannelResource> voice_channel_resources{};
285 std::vector<VoiceState> voice_states{};
286 std::vector<VoiceState> dsp_voice_states{};
287 std::vector<ServerVoiceInfo> voice_info{};
288 std::vector<ServerVoiceInfo*> sorted_voice_info{};
289};
290
291} // namespace AudioCore
diff --git a/src/core/hle/service/audio/audren_u.cpp b/src/core/hle/service/audio/audren_u.cpp
index d8359abaa..a2d3ded7b 100644
--- a/src/core/hle/service/audio/audren_u.cpp
+++ b/src/core/hle/service/audio/audren_u.cpp
@@ -26,7 +26,7 @@ namespace Service::Audio {
26 26
27class IAudioRenderer final : public ServiceFramework<IAudioRenderer> { 27class IAudioRenderer final : public ServiceFramework<IAudioRenderer> {
28public: 28public:
29 explicit IAudioRenderer(Core::System& system, AudioCore::AudioRendererParameter audren_params, 29 explicit IAudioRenderer(Core::System& system, AudioCommon::AudioRendererParameter audren_params,
30 const std::size_t instance_number) 30 const std::size_t instance_number)
31 : ServiceFramework("IAudioRenderer") { 31 : ServiceFramework("IAudioRenderer") {
32 // clang-format off 32 // clang-format off
@@ -94,14 +94,15 @@ private:
94 void RequestUpdateImpl(Kernel::HLERequestContext& ctx) { 94 void RequestUpdateImpl(Kernel::HLERequestContext& ctx) {
95 LOG_DEBUG(Service_Audio, "(STUBBED) called"); 95 LOG_DEBUG(Service_Audio, "(STUBBED) called");
96 96
97 auto result = renderer->UpdateAudioRenderer(ctx.ReadBuffer()); 97 std::vector<u8> output_params(ctx.GetWriteBufferSize());
98 auto result = renderer->UpdateAudioRenderer(ctx.ReadBuffer(), output_params);
98 99
99 if (result.Succeeded()) { 100 if (result.IsSuccess()) {
100 ctx.WriteBuffer(result.Unwrap()); 101 ctx.WriteBuffer(output_params);
101 } 102 }
102 103
103 IPC::ResponseBuilder rb{ctx, 2}; 104 IPC::ResponseBuilder rb{ctx, 2};
104 rb.Push(result.Code()); 105 rb.Push(result);
105 } 106 }
106 107
107 void Start(Kernel::HLERequestContext& ctx) { 108 void Start(Kernel::HLERequestContext& ctx) {
@@ -346,7 +347,7 @@ void AudRenU::OpenAudioRenderer(Kernel::HLERequestContext& ctx) {
346 OpenAudioRendererImpl(ctx); 347 OpenAudioRendererImpl(ctx);
347} 348}
348 349
349static u64 CalculateNumPerformanceEntries(const AudioCore::AudioRendererParameter& params) { 350static u64 CalculateNumPerformanceEntries(const AudioCommon::AudioRendererParameter& params) {
350 // +1 represents the final mix. 351 // +1 represents the final mix.
351 return u64{params.effect_count} + params.submix_count + params.sink_count + params.voice_count + 352 return u64{params.effect_count} + params.submix_count + params.sink_count + params.voice_count +
352 1; 353 1;
@@ -375,7 +376,7 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) {
375 constexpr u64 upsampler_manager_size = 0x48; 376 constexpr u64 upsampler_manager_size = 0x48;
376 377
377 // Calculates the part of the size that relates to mix buffers. 378 // Calculates the part of the size that relates to mix buffers.
378 const auto calculate_mix_buffer_sizes = [](const AudioCore::AudioRendererParameter& params) { 379 const auto calculate_mix_buffer_sizes = [](const AudioCommon::AudioRendererParameter& params) {
379 // As of 8.0.0 this is the maximum on voice channels. 380 // As of 8.0.0 this is the maximum on voice channels.
380 constexpr u64 max_voice_channels = 6; 381 constexpr u64 max_voice_channels = 6;
381 382
@@ -397,7 +398,7 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) {
397 }; 398 };
398 399
399 // Calculates the portion of the size related to the mix data (and the sorting thereof). 400 // Calculates the portion of the size related to the mix data (and the sorting thereof).
400 const auto calculate_mix_info_size = [](const AudioCore::AudioRendererParameter& params) { 401 const auto calculate_mix_info_size = [](const AudioCommon::AudioRendererParameter& params) {
401 // The size of the mixing info data structure. 402 // The size of the mixing info data structure.
402 constexpr u64 mix_info_size = 0x940; 403 constexpr u64 mix_info_size = 0x940;
403 404
@@ -447,7 +448,7 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) {
447 }; 448 };
448 449
449 // Calculates the part of the size related to voice channel info. 450 // Calculates the part of the size related to voice channel info.
450 const auto calculate_voice_info_size = [](const AudioCore::AudioRendererParameter& params) { 451 const auto calculate_voice_info_size = [](const AudioCommon::AudioRendererParameter& params) {
451 constexpr u64 voice_info_size = 0x220; 452 constexpr u64 voice_info_size = 0x220;
452 constexpr u64 voice_resource_size = 0xD0; 453 constexpr u64 voice_resource_size = 0xD0;
453 454
@@ -461,7 +462,7 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) {
461 }; 462 };
462 463
463 // Calculates the part of the size related to memory pools. 464 // Calculates the part of the size related to memory pools.
464 const auto calculate_memory_pools_size = [](const AudioCore::AudioRendererParameter& params) { 465 const auto calculate_memory_pools_size = [](const AudioCommon::AudioRendererParameter& params) {
465 const u64 num_memory_pools = sizeof(s32) * (u64{params.effect_count} + params.voice_count); 466 const u64 num_memory_pools = sizeof(s32) * (u64{params.effect_count} + params.voice_count);
466 const u64 memory_pool_info_size = 0x20; 467 const u64 memory_pool_info_size = 0x20;
467 return Common::AlignUp(num_memory_pools * memory_pool_info_size, info_field_alignment_size); 468 return Common::AlignUp(num_memory_pools * memory_pool_info_size, info_field_alignment_size);
@@ -469,7 +470,7 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) {
469 470
470 // Calculates the part of the size related to the splitter context. 471 // Calculates the part of the size related to the splitter context.
471 const auto calculate_splitter_context_size = 472 const auto calculate_splitter_context_size =
472 [](const AudioCore::AudioRendererParameter& params) -> u64 { 473 [](const AudioCommon::AudioRendererParameter& params) -> u64 {
473 if (!IsFeatureSupported(AudioFeatures::Splitter, params.revision)) { 474 if (!IsFeatureSupported(AudioFeatures::Splitter, params.revision)) {
474 return 0; 475 return 0;
475 } 476 }
@@ -488,27 +489,29 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) {
488 }; 489 };
489 490
490 // Calculates the part of the size related to the upsampler info. 491 // Calculates the part of the size related to the upsampler info.
491 const auto calculate_upsampler_info_size = [](const AudioCore::AudioRendererParameter& params) { 492 const auto calculate_upsampler_info_size =
492 constexpr u64 upsampler_info_size = 0x280; 493 [](const AudioCommon::AudioRendererParameter& params) {
493 // Yes, using the buffer size over info alignment size is intentional here. 494 constexpr u64 upsampler_info_size = 0x280;
494 return Common::AlignUp(upsampler_info_size * (u64{params.submix_count} + params.sink_count), 495 // Yes, using the buffer size over info alignment size is intentional here.
495 buffer_alignment_size); 496 return Common::AlignUp(upsampler_info_size *
496 }; 497 (u64{params.submix_count} + params.sink_count),
498 buffer_alignment_size);
499 };
497 500
498 // Calculates the part of the size related to effect info. 501 // Calculates the part of the size related to effect info.
499 const auto calculate_effect_info_size = [](const AudioCore::AudioRendererParameter& params) { 502 const auto calculate_effect_info_size = [](const AudioCommon::AudioRendererParameter& params) {
500 constexpr u64 effect_info_size = 0x2B0; 503 constexpr u64 effect_info_size = 0x2B0;
501 return Common::AlignUp(effect_info_size * params.effect_count, info_field_alignment_size); 504 return Common::AlignUp(effect_info_size * params.effect_count, info_field_alignment_size);
502 }; 505 };
503 506
504 // Calculates the part of the size related to audio sink info. 507 // Calculates the part of the size related to audio sink info.
505 const auto calculate_sink_info_size = [](const AudioCore::AudioRendererParameter& params) { 508 const auto calculate_sink_info_size = [](const AudioCommon::AudioRendererParameter& params) {
506 const u64 sink_info_size = 0x170; 509 const u64 sink_info_size = 0x170;
507 return Common::AlignUp(sink_info_size * params.sink_count, info_field_alignment_size); 510 return Common::AlignUp(sink_info_size * params.sink_count, info_field_alignment_size);
508 }; 511 };
509 512
510 // Calculates the part of the size related to voice state info. 513 // Calculates the part of the size related to voice state info.
511 const auto calculate_voice_state_size = [](const AudioCore::AudioRendererParameter& params) { 514 const auto calculate_voice_state_size = [](const AudioCommon::AudioRendererParameter& params) {
512 const u64 voice_state_size = 0x100; 515 const u64 voice_state_size = 0x100;
513 const u64 additional_size = buffer_alignment_size - 1; 516 const u64 additional_size = buffer_alignment_size - 1;
514 return Common::AlignUp(voice_state_size * params.voice_count + additional_size, 517 return Common::AlignUp(voice_state_size * params.voice_count + additional_size,
@@ -516,7 +519,7 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) {
516 }; 519 };
517 520
518 // Calculates the part of the size related to performance statistics. 521 // Calculates the part of the size related to performance statistics.
519 const auto calculate_perf_size = [](const AudioCore::AudioRendererParameter& params) { 522 const auto calculate_perf_size = [](const AudioCommon::AudioRendererParameter& params) {
520 // Extra size value appended to the end of the calculation. 523 // Extra size value appended to the end of the calculation.
521 constexpr u64 appended = 128; 524 constexpr u64 appended = 128;
522 525
@@ -543,79 +546,81 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) {
543 }; 546 };
544 547
545 // Calculates the part of the size that relates to the audio command buffer. 548 // Calculates the part of the size that relates to the audio command buffer.
546 const auto calculate_command_buffer_size = [](const AudioCore::AudioRendererParameter& params) { 549 const auto calculate_command_buffer_size =
547 constexpr u64 alignment = (buffer_alignment_size - 1) * 2; 550 [](const AudioCommon::AudioRendererParameter& params) {
551 constexpr u64 alignment = (buffer_alignment_size - 1) * 2;
548 552
549 if (!IsFeatureSupported(AudioFeatures::VariadicCommandBuffer, params.revision)) { 553 if (!IsFeatureSupported(AudioFeatures::VariadicCommandBuffer, params.revision)) {
550 constexpr u64 command_buffer_size = 0x18000; 554 constexpr u64 command_buffer_size = 0x18000;
551 555
552 return command_buffer_size + alignment; 556 return command_buffer_size + alignment;
553 } 557 }
554 558
555 // When the variadic command buffer is supported, this means 559 // When the variadic command buffer is supported, this means
556 // the command generator for the audio renderer can issue commands 560 // the command generator for the audio renderer can issue commands
557 // that are (as one would expect), variable in size. So what we need to do 561 // that are (as one would expect), variable in size. So what we need to do
558 // is determine the maximum possible size for a few command data structures 562 // is determine the maximum possible size for a few command data structures
559 // then multiply them by the amount of present commands indicated by the given 563 // then multiply them by the amount of present commands indicated by the given
560 // respective audio parameters. 564 // respective audio parameters.
561 565
562 constexpr u64 max_biquad_filters = 2; 566 constexpr u64 max_biquad_filters = 2;
563 constexpr u64 max_mix_buffers = 24; 567 constexpr u64 max_mix_buffers = 24;
564 568
565 constexpr u64 biquad_filter_command_size = 0x2C; 569 constexpr u64 biquad_filter_command_size = 0x2C;
566 570
567 constexpr u64 depop_mix_command_size = 0x24; 571 constexpr u64 depop_mix_command_size = 0x24;
568 constexpr u64 depop_setup_command_size = 0x50; 572 constexpr u64 depop_setup_command_size = 0x50;
569 573
570 constexpr u64 effect_command_max_size = 0x540; 574 constexpr u64 effect_command_max_size = 0x540;
571 575
572 constexpr u64 mix_command_size = 0x1C; 576 constexpr u64 mix_command_size = 0x1C;
573 constexpr u64 mix_ramp_command_size = 0x24; 577 constexpr u64 mix_ramp_command_size = 0x24;
574 constexpr u64 mix_ramp_grouped_command_size = 0x13C; 578 constexpr u64 mix_ramp_grouped_command_size = 0x13C;
575 579
576 constexpr u64 perf_command_size = 0x28; 580 constexpr u64 perf_command_size = 0x28;
577 581
578 constexpr u64 sink_command_size = 0x130; 582 constexpr u64 sink_command_size = 0x130;
579 583
580 constexpr u64 submix_command_max_size = 584 constexpr u64 submix_command_max_size =
581 depop_mix_command_size + (mix_command_size * max_mix_buffers) * max_mix_buffers; 585 depop_mix_command_size + (mix_command_size * max_mix_buffers) * max_mix_buffers;
582 586
583 constexpr u64 volume_command_size = 0x1C; 587 constexpr u64 volume_command_size = 0x1C;
584 constexpr u64 volume_ramp_command_size = 0x20; 588 constexpr u64 volume_ramp_command_size = 0x20;
585 589
586 constexpr u64 voice_biquad_filter_command_size = 590 constexpr u64 voice_biquad_filter_command_size =
587 biquad_filter_command_size * max_biquad_filters; 591 biquad_filter_command_size * max_biquad_filters;
588 constexpr u64 voice_data_command_size = 0x9C; 592 constexpr u64 voice_data_command_size = 0x9C;
589 const u64 voice_command_max_size = 593 const u64 voice_command_max_size =
590 (params.splitter_count * depop_setup_command_size) + 594 (params.splitter_count * depop_setup_command_size) +
591 (voice_data_command_size + voice_biquad_filter_command_size + volume_ramp_command_size + 595 (voice_data_command_size + voice_biquad_filter_command_size +
592 mix_ramp_grouped_command_size); 596 volume_ramp_command_size + mix_ramp_grouped_command_size);
593 597
594 // Now calculate the individual elements that comprise the size and add them together. 598 // Now calculate the individual elements that comprise the size and add them together.
595 const u64 effect_commands_size = params.effect_count * effect_command_max_size; 599 const u64 effect_commands_size = params.effect_count * effect_command_max_size;
596 600
597 const u64 final_mix_commands_size = 601 const u64 final_mix_commands_size =
598 depop_mix_command_size + volume_command_size * max_mix_buffers; 602 depop_mix_command_size + volume_command_size * max_mix_buffers;
599 603
600 const u64 perf_commands_size = 604 const u64 perf_commands_size =
601 perf_command_size * (CalculateNumPerformanceEntries(params) + max_perf_detail_entries); 605 perf_command_size *
606 (CalculateNumPerformanceEntries(params) + max_perf_detail_entries);
602 607
603 const u64 sink_commands_size = params.sink_count * sink_command_size; 608 const u64 sink_commands_size = params.sink_count * sink_command_size;
604 609
605 const u64 splitter_commands_size = 610 const u64 splitter_commands_size =
606 params.num_splitter_send_channels * max_mix_buffers * mix_ramp_command_size; 611 params.num_splitter_send_channels * max_mix_buffers * mix_ramp_command_size;
607 612
608 const u64 submix_commands_size = params.submix_count * submix_command_max_size; 613 const u64 submix_commands_size = params.submix_count * submix_command_max_size;
609 614
610 const u64 voice_commands_size = params.voice_count * voice_command_max_size; 615 const u64 voice_commands_size = params.voice_count * voice_command_max_size;
611 616
612 return effect_commands_size + final_mix_commands_size + perf_commands_size + 617 return effect_commands_size + final_mix_commands_size + perf_commands_size +
613 sink_commands_size + splitter_commands_size + submix_commands_size + 618 sink_commands_size + splitter_commands_size + submix_commands_size +
614 voice_commands_size + alignment; 619 voice_commands_size + alignment;
615 }; 620 };
616 621
617 IPC::RequestParser rp{ctx}; 622 IPC::RequestParser rp{ctx};
618 const auto params = rp.PopRaw<AudioCore::AudioRendererParameter>(); 623 const auto params = rp.PopRaw<AudioCommon::AudioRendererParameter>();
619 624
620 u64 size = 0; 625 u64 size = 0;
621 size += calculate_mix_buffer_sizes(params); 626 size += calculate_mix_buffer_sizes(params);
@@ -681,7 +686,7 @@ void AudRenU::GetAudioDeviceServiceWithRevisionInfo(Kernel::HLERequestContext& c
681 686
682void AudRenU::OpenAudioRendererImpl(Kernel::HLERequestContext& ctx) { 687void AudRenU::OpenAudioRendererImpl(Kernel::HLERequestContext& ctx) {
683 IPC::RequestParser rp{ctx}; 688 IPC::RequestParser rp{ctx};
684 const auto params = rp.PopRaw<AudioCore::AudioRendererParameter>(); 689 const auto params = rp.PopRaw<AudioCommon::AudioRendererParameter>();
685 IPC::ResponseBuilder rb{ctx, 2, 0, 1}; 690 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
686 691
687 rb.Push(RESULT_SUCCESS); 692 rb.Push(RESULT_SUCCESS);