diff options
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 | ||
| 200 | void 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 | ||
| 42 | void 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 | ||
| 16 | namespace AudioCore { | 20 | namespace AudioCore { |
| 17 | |||
| 18 | constexpr u32 STREAM_SAMPLE_RATE{48000}; | ||
| 19 | constexpr u32 STREAM_NUM_CHANNELS{2}; | ||
| 20 | using VoiceChannelHolder = std::array<VoiceResourceInformation*, 6>; | ||
| 21 | class AudioRenderer::VoiceState { | ||
| 22 | public: | ||
| 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 | |||
| 45 | private: | ||
| 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 | |||
| 57 | class AudioRenderer::EffectState { | ||
| 58 | public: | ||
| 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 | |||
| 73 | private: | ||
| 74 | EffectOutStatus out_status{}; | ||
| 75 | EffectInStatus info{}; | ||
| 76 | }; | ||
| 77 | |||
| 78 | AudioRenderer::AudioRenderer(Core::Timing::CoreTiming& core_timing, Core::Memory::Memory& memory_, | 21 | AudioRenderer::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 | ||
| 114 | ResultVal<std::vector<u8>> AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_params) { | 66 | static 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{ | 70 | ResultCode 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; | |
| 237 | void AudioRenderer::VoiceState::SetWaveIndex(std::size_t index) { | ||
| 238 | wave_index = index & 3; | ||
| 239 | is_refresh_pending = true; | ||
| 240 | } | ||
| 241 | |||
| 242 | std::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 | ||
| 283 | void 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 | ||
| 294 | void 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 | ||
| 407 | void 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 | ||
| 424 | static 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 | ||
| 428 | void AudioRenderer::QueueMixedBuffer(Buffer::Tag tag) { | 158 | void 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 | ||
| 465 | void AudioRenderer::ReleaseAndQueueBuffers() { | 262 | void 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 | ||
| 32 | namespace AudioCore { | 39 | namespace AudioCore { |
| 40 | using DSPStateHolder = std::array<VoiceState*, 6>; | ||
| 33 | 41 | ||
| 34 | class AudioOut; | 42 | class AudioOut; |
| 35 | 43 | ||
| 36 | enum class PlayState : u8 { | ||
| 37 | Started = 0, | ||
| 38 | Stopped = 1, | ||
| 39 | Paused = 2, | ||
| 40 | }; | ||
| 41 | |||
| 42 | enum class Effect : u8 { | ||
| 43 | None = 0, | ||
| 44 | Aux = 2, | ||
| 45 | }; | ||
| 46 | |||
| 47 | enum class EffectStatus : u8 { | ||
| 48 | None = 0, | ||
| 49 | New = 1, | ||
| 50 | }; | ||
| 51 | |||
| 52 | struct 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 | }; | ||
| 70 | static_assert(sizeof(AudioRendererParameter) == 52, "AudioRendererParameter is an invalid size"); | ||
| 71 | |||
| 72 | enum 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 | |||
| 82 | struct MemoryPoolEntry { | ||
| 83 | MemoryPoolStates state; | ||
| 84 | u32_le unknown_4; | ||
| 85 | u32_le unknown_8; | ||
| 86 | u32_le unknown_c; | ||
| 87 | }; | ||
| 88 | static_assert(sizeof(MemoryPoolEntry) == 0x10, "MemoryPoolEntry has wrong size"); | ||
| 89 | |||
| 90 | struct MemoryPoolInfo { | ||
| 91 | u64_le pool_address; | ||
| 92 | u64_le pool_size; | ||
| 93 | MemoryPoolStates pool_state; | ||
| 94 | INSERT_PADDING_WORDS(3); // Unknown | ||
| 95 | }; | ||
| 96 | static_assert(sizeof(MemoryPoolInfo) == 0x20, "MemoryPoolInfo has wrong size"); | ||
| 97 | struct BiquadFilter { | ||
| 98 | u8 enable; | ||
| 99 | INSERT_PADDING_BYTES(1); | ||
| 100 | std::array<s16_le, 3> numerator; | ||
| 101 | std::array<s16_le, 2> denominator; | ||
| 102 | }; | ||
| 103 | static_assert(sizeof(BiquadFilter) == 0xc, "BiquadFilter has wrong size"); | ||
| 104 | |||
| 105 | struct 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 | }; | ||
| 118 | static_assert(sizeof(WaveBuffer) == 0x38, "WaveBuffer has wrong size"); | ||
| 119 | |||
| 120 | struct 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 | }; | ||
| 126 | static_assert(sizeof(VoiceResourceInformation) == 0x70, "VoiceResourceInformation has wrong size"); | ||
| 127 | |||
| 128 | struct 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 | }; | ||
| 153 | static_assert(sizeof(VoiceInfo) == 0x170, "VoiceInfo is wrong size"); | ||
| 154 | |||
| 155 | struct VoiceOutStatus { | ||
| 156 | u64_le played_sample_count; | ||
| 157 | u32_le wave_buffer_consumed; | ||
| 158 | u32_le voice_drops_count; | ||
| 159 | }; | ||
| 160 | static_assert(sizeof(VoiceOutStatus) == 0x10, "VoiceOutStatus has wrong size"); | ||
| 161 | |||
| 162 | struct 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 | }; | ||
| 174 | static_assert(sizeof(AuxInfo) == 0x60, "AuxInfo is an invalid size"); | ||
| 175 | |||
| 176 | struct 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 | }; | ||
| 191 | static_assert(sizeof(EffectInStatus) == 0xc0, "EffectInStatus is an invalid size"); | ||
| 192 | |||
| 193 | struct EffectOutStatus { | ||
| 194 | EffectStatus state; | ||
| 195 | INSERT_PADDING_BYTES(0xf); | ||
| 196 | }; | ||
| 197 | static_assert(sizeof(EffectOutStatus) == 0x10, "EffectOutStatus is an invalid size"); | ||
| 198 | |||
| 199 | struct RendererInfo { | 44 | struct 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 | }; |
| 203 | static_assert(sizeof(RendererInfo) == 0x10, "RendererInfo is an invalid size"); | 48 | static_assert(sizeof(RendererInfo) == 0x10, "RendererInfo is an invalid size"); |
| 204 | 49 | ||
| 205 | struct 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 | }; | ||
| 237 | static_assert(sizeof(UpdateDataHeader) == 0x40, "UpdateDataHeader has wrong size"); | ||
| 238 | |||
| 239 | class AudioRenderer { | 50 | class AudioRenderer { |
| 240 | public: | 51 | public: |
| 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 | ||
| 254 | private: | 66 | private: |
| 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 | ||
| 10 | namespace AudioCore { | 10 | namespace AudioCore { |
| 11 | 11 | ||
| 12 | BehaviorInfo::BehaviorInfo() : process_revision(CURRENT_PROCESS_REVISION) {} | 12 | BehaviorInfo::BehaviorInfo() : process_revision(AudioCommon::CURRENT_PROCESS_REVISION) {} |
| 13 | BehaviorInfo::~BehaviorInfo() = default; | 13 | BehaviorInfo::~BehaviorInfo() = default; |
| 14 | 14 | ||
| 15 | bool 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(¶ms, 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 | |||
| 43 | bool BehaviorInfo::UpdateOutput(std::vector<u8>& buffer, std::size_t offset) { | 15 | bool 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 | ||
| 40 | u32_le BehaviorInfo::GetUserRevision() const { | ||
| 41 | return user_revision; | ||
| 42 | } | ||
| 43 | |||
| 44 | u32_le BehaviorInfo::GetProcessRevision() const { | ||
| 45 | return process_revision; | ||
| 46 | } | ||
| 47 | |||
| 68 | bool BehaviorInfo::IsAdpcmLoopContextBugFixed() const { | 48 | bool BehaviorInfo::IsAdpcmLoopContextBugFixed() const { |
| 69 | return IsRevisionSupported(2, user_revision); | 49 | return AudioCommon::IsRevisionSupported(2, user_revision); |
| 70 | } | 50 | } |
| 71 | 51 | ||
| 72 | bool BehaviorInfo::IsSplitterSupported() const { | 52 | bool BehaviorInfo::IsSplitterSupported() const { |
| 73 | return IsRevisionSupported(2, user_revision); | 53 | return AudioCommon::IsRevisionSupported(2, user_revision); |
| 74 | } | 54 | } |
| 75 | 55 | ||
| 76 | bool BehaviorInfo::IsLongSizePreDelaySupported() const { | 56 | bool BehaviorInfo::IsLongSizePreDelaySupported() const { |
| 77 | return IsRevisionSupported(3, user_revision); | 57 | return AudioCommon::IsRevisionSupported(3, user_revision); |
| 78 | } | 58 | } |
| 79 | 59 | ||
| 80 | bool BehaviorInfo::IsAudioRenererProcessingTimeLimit80PercentSupported() const { | 60 | bool BehaviorInfo::IsAudioRenererProcessingTimeLimit80PercentSupported() const { |
| 81 | return IsRevisionSupported(5, user_revision); | 61 | return AudioCommon::IsRevisionSupported(5, user_revision); |
| 82 | } | 62 | } |
| 83 | 63 | ||
| 84 | bool BehaviorInfo::IsAudioRenererProcessingTimeLimit75PercentSupported() const { | 64 | bool BehaviorInfo::IsAudioRenererProcessingTimeLimit75PercentSupported() const { |
| 85 | return IsRevisionSupported(4, user_revision); | 65 | return AudioCommon::IsRevisionSupported(4, user_revision); |
| 86 | } | 66 | } |
| 87 | 67 | ||
| 88 | bool BehaviorInfo::IsAudioRenererProcessingTimeLimit70PercentSupported() const { | 68 | bool BehaviorInfo::IsAudioRenererProcessingTimeLimit70PercentSupported() const { |
| 89 | return IsRevisionSupported(1, user_revision); | 69 | return AudioCommon::IsRevisionSupported(1, user_revision); |
| 90 | } | 70 | } |
| 91 | 71 | ||
| 92 | bool BehaviorInfo::IsElapsedFrameCountSupported() const { | 72 | bool BehaviorInfo::IsElapsedFrameCountSupported() const { |
| 93 | return IsRevisionSupported(5, user_revision); | 73 | return AudioCommon::IsRevisionSupported(5, user_revision); |
| 94 | } | 74 | } |
| 95 | 75 | ||
| 96 | bool BehaviorInfo::IsMemoryPoolForceMappingEnabled() const { | 76 | bool BehaviorInfo::IsMemoryPoolForceMappingEnabled() const { |
| 97 | return (flags & 1) != 0; | 77 | return (flags & 1) != 0; |
| 98 | } | 78 | } |
| 99 | 79 | ||
| 80 | bool BehaviorInfo::IsFlushVoiceWaveBuffersSupported() const { | ||
| 81 | return AudioCommon::IsRevisionSupported(5, user_revision); | ||
| 82 | } | ||
| 83 | |||
| 84 | bool BehaviorInfo::IsVoicePlayedSampleCountResetAtLoopPointSupported() const { | ||
| 85 | return AudioCommon::IsRevisionSupported(5, user_revision); | ||
| 86 | } | ||
| 87 | |||
| 88 | bool BehaviorInfo::IsVoicePitchAndSrcSkippedSupported() const { | ||
| 89 | return AudioCommon::IsRevisionSupported(5, user_revision); | ||
| 90 | } | ||
| 91 | |||
| 92 | bool BehaviorInfo::IsMixInParameterDirtyOnlyUpdateSupported() const { | ||
| 93 | return AudioCommon::IsRevisionSupported(7, user_revision); | ||
| 94 | } | ||
| 95 | |||
| 96 | bool BehaviorInfo::IsSplitterBugFixed() const { | ||
| 97 | return AudioCommon::IsRevisionSupported(5, user_revision); | ||
| 98 | } | ||
| 99 | |||
| 100 | void 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 @@ | |||
| 14 | namespace AudioCore { | 14 | namespace AudioCore { |
| 15 | class BehaviorInfo { | 15 | class BehaviorInfo { |
| 16 | public: | 16 | public: |
| 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 | ||
| 36 | private: | 64 | private: |
| 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 | |||
| 11 | namespace AudioCore { | ||
| 12 | namespace { | ||
| 13 | static constexpr std::size_t MIX_BUFFER_SIZE = 0x3f00; | ||
| 14 | static constexpr std::size_t SCALED_MIX_BUFFER_SIZE = MIX_BUFFER_SIZE << 15ULL; | ||
| 15 | |||
| 16 | template <std::size_t N> | ||
| 17 | void 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 | |||
| 26 | s32 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 | |||
| 36 | void 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 | |||
| 43 | void 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 | |||
| 51 | CommandGenerator::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) {} | ||
| 59 | CommandGenerator::~CommandGenerator() = default; | ||
| 60 | |||
| 61 | void 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 | |||
| 66 | void 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 | |||
| 86 | void 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 | |||
| 154 | void 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 | |||
| 166 | void CommandGenerator::GenerateFinalMixCommands() { | ||
| 167 | GenerateFinalMixCommand(); | ||
| 168 | } | ||
| 169 | |||
| 170 | void 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 | |||
| 185 | void CommandGenerator::PostCommand() { | ||
| 186 | if (dumping_frame) { | ||
| 187 | dumping_frame = false; | ||
| 188 | } | ||
| 189 | } | ||
| 190 | |||
| 191 | void 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 | |||
| 223 | void 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 | |||
| 247 | void 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 | |||
| 286 | ServerSplitterDestinationData* 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 | |||
| 293 | void 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 | |||
| 312 | void 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 | |||
| 339 | void 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 | |||
| 348 | void 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 | |||
| 390 | void 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 | |||
| 413 | void 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 | |||
| 436 | s32 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 | } | ||
| 475 | s32 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 | |||
| 521 | s32* CommandGenerator::GetMixBuffer(std::size_t index) { | ||
| 522 | return mix_buffer.data() + (index * worker_params.sample_count); | ||
| 523 | } | ||
| 524 | |||
| 525 | const s32* CommandGenerator::GetMixBuffer(std::size_t index) const { | ||
| 526 | return mix_buffer.data() + (index * worker_params.sample_count); | ||
| 527 | } | ||
| 528 | |||
| 529 | std::size_t CommandGenerator::GetMixChannelBufferOffset(s32 channel) const { | ||
| 530 | return worker_params.mix_buffer_count + channel; | ||
| 531 | } | ||
| 532 | |||
| 533 | s32* CommandGenerator::GetChannelMixBuffer(s32 channel) { | ||
| 534 | return GetMixBuffer(worker_params.mix_buffer_count + channel); | ||
| 535 | } | ||
| 536 | |||
| 537 | const s32* CommandGenerator::GetChannelMixBuffer(s32 channel) const { | ||
| 538 | return GetMixBuffer(worker_params.mix_buffer_count + channel); | ||
| 539 | } | ||
| 540 | |||
| 541 | void 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 | |||
| 13 | namespace Core::Memory { | ||
| 14 | class Memory; | ||
| 15 | } | ||
| 16 | |||
| 17 | namespace AudioCore { | ||
| 18 | class MixContext; | ||
| 19 | class SplitterContext; | ||
| 20 | class ServerSplitterDestinationData; | ||
| 21 | class ServerMixInfo; | ||
| 22 | using MixVolumeBuffer = std::array<float, AudioCommon::MAX_MIX_BUFFERS>; | ||
| 23 | class CommandGenerator { | ||
| 24 | public: | ||
| 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 | |||
| 44 | private: | ||
| 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 | ||
| 11 | namespace AudioCore { | 11 | namespace AudioCommon { |
| 12 | namespace Audren { | 12 | namespace Audren { |
| 13 | constexpr ResultCode ERR_INVALID_PARAMETERS{ErrorModule::Audio, 41}; | 13 | constexpr ResultCode ERR_INVALID_PARAMETERS{ErrorModule::Audio, 41}; |
| 14 | } | 14 | constexpr ResultCode ERR_SPLITTER_SORT_FAILED{ErrorModule::Audio, 43}; |
| 15 | } // namespace Audren | ||
| 15 | 16 | ||
| 16 | constexpr u32_le CURRENT_PROCESS_REVISION = Common::MakeMagic('R', 'E', 'V', '8'); | 17 | constexpr u32_le CURRENT_PROCESS_REVISION = Common::MakeMagic('R', 'E', 'V', '8'); |
| 17 | constexpr std::size_t MAX_MIX_BUFFERS = 24; | 18 | constexpr std::size_t MAX_MIX_BUFFERS = 24; |
| 19 | constexpr std::size_t MAX_BIQUAD_FILTERS = 2; | ||
| 20 | constexpr std::size_t MAX_CHANNEL_COUNT = 6; | ||
| 21 | constexpr std::size_t MAX_WAVE_BUFFERS = 4; | ||
| 22 | constexpr std::size_t MAX_SAMPLE_HISTORY = 4; | ||
| 23 | constexpr u32 STREAM_SAMPLE_RATE = 48000; | ||
| 24 | constexpr u32 STREAM_NUM_CHANNELS = 6; | ||
| 25 | constexpr s32 NO_SPLITTER = -1; | ||
| 26 | constexpr s32 NO_MIX = 0x7fffffff; | ||
| 27 | constexpr s32 NO_FINAL_MIX = std::numeric_limits<s32>::min(); | ||
| 28 | constexpr s32 FINAL_MIX = 0; | ||
| 29 | constexpr 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 | ||
| 33 | constexpr std::size_t TOTAL_TEMP_MIX_SIZE = TEMP_MIX_BASE_SIZE + AudioCommon::MAX_SAMPLE_HISTORY; | ||
| 18 | 34 | ||
| 19 | static constexpr u32 VersionFromRevision(u32_le rev) { | 35 | static 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 | 64 | struct 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 | }; | ||
| 77 | static_assert(sizeof(UpdateDataSizes) == 0x38, "UpdateDataSizes is an invalid size"); | ||
| 78 | |||
| 79 | struct UpdateDataHeader { | ||
| 80 | u32_le revision{}; | ||
| 81 | UpdateDataSizes size{}; | ||
| 82 | u32_le total_size{}; | ||
| 83 | }; | ||
| 84 | static_assert(sizeof(UpdateDataHeader) == 0x40, "UpdateDataHeader is an invalid size"); | ||
| 85 | |||
| 86 | struct 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 | }; | ||
| 104 | static_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 { | |||
| 23 | public: | 23 | public: |
| 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, ¶ms, &minimum_latency) != CUBEB_OK) { | 46 | if (cubeb_get_min_latency(ctx, ¶ms, &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 | |||
| 7 | namespace AudioCore { | ||
| 8 | EffectContext::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 | } | ||
| 13 | EffectContext::~EffectContext() = default; | ||
| 14 | |||
| 15 | std::size_t EffectContext::GetCount() const { | ||
| 16 | return effect_count; | ||
| 17 | } | ||
| 18 | |||
| 19 | EffectBase* EffectContext::GetInfo(std::size_t i) { | ||
| 20 | return effects.at(i).get(); | ||
| 21 | } | ||
| 22 | |||
| 23 | EffectStubbed::EffectStubbed() : EffectBase::EffectBase() {} | ||
| 24 | EffectStubbed::~EffectStubbed() = default; | ||
| 25 | |||
| 26 | void EffectStubbed::Update(EffectInfo::InParams& in_params) { | ||
| 27 | if (in_params.is_new) { | ||
| 28 | usage = UsageStatus::New; | ||
| 29 | } | ||
| 30 | } | ||
| 31 | |||
| 32 | EffectBase::EffectBase() = default; | ||
| 33 | EffectBase::~EffectBase() = default; | ||
| 34 | |||
| 35 | UsageStatus 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 | |||
| 15 | namespace AudioCore { | ||
| 16 | enum 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 | |||
| 26 | enum class UsageStatus : u8 { | ||
| 27 | Invalid = 0, | ||
| 28 | New = 1, | ||
| 29 | Initialized = 2, | ||
| 30 | Used = 3, | ||
| 31 | Removed = 4, | ||
| 32 | }; | ||
| 33 | |||
| 34 | struct 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 | }; | ||
| 40 | static_assert(sizeof(BufferMixerParams) == 0x94, "BufferMixerParams is an invalid size"); | ||
| 41 | |||
| 42 | struct 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 | }; | ||
| 55 | static_assert(sizeof(AuxInfo) == 0x60, "AuxInfo is an invalid size"); | ||
| 56 | |||
| 57 | class EffectInfo { | ||
| 58 | public: | ||
| 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 | |||
| 82 | class EffectBase { | ||
| 83 | public: | ||
| 84 | EffectBase(); | ||
| 85 | ~EffectBase(); | ||
| 86 | |||
| 87 | virtual void Update(EffectInfo::InParams& in_params) = 0; | ||
| 88 | UsageStatus GetUsage() const; | ||
| 89 | |||
| 90 | protected: | ||
| 91 | UsageStatus usage{UsageStatus::Invalid}; | ||
| 92 | }; | ||
| 93 | |||
| 94 | class EffectStubbed : public EffectBase { | ||
| 95 | public: | ||
| 96 | explicit EffectStubbed(); | ||
| 97 | ~EffectStubbed(); | ||
| 98 | |||
| 99 | void Update(EffectInfo::InParams& in_params) override; | ||
| 100 | }; | ||
| 101 | |||
| 102 | class EffectContext { | ||
| 103 | public: | ||
| 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 | |||
| 110 | private: | ||
| 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 | |||
| 15 | namespace AudioCore { | ||
| 16 | |||
| 17 | InfoUpdater::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 | |||
| 26 | InfoUpdater::~InfoUpdater() = default; | ||
| 27 | |||
| 28 | bool 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 | |||
| 66 | bool 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 | |||
| 109 | bool 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 | |||
| 138 | bool 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 | |||
| 222 | bool 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 | |||
| 272 | bool 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 | |||
| 292 | ResultCode 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 | |||
| 412 | bool 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 | |||
| 441 | bool InfoUpdater::UpdatePerformanceBuffer() { | ||
| 442 | output_header.size.performance = 0x10; | ||
| 443 | output_offset += 0x10; | ||
| 444 | return true; | ||
| 445 | } | ||
| 446 | |||
| 447 | bool 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 | |||
| 465 | struct RendererInfo { | ||
| 466 | u64_le elasped_frame_count{}; | ||
| 467 | INSERT_PADDING_WORDS(2); | ||
| 468 | }; | ||
| 469 | static_assert(sizeof(RendererInfo) == 0x10, "RendererInfo is an invalid size"); | ||
| 470 | |||
| 471 | bool 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 | |||
| 486 | bool 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 | |||
| 499 | bool 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 | |||
| 11 | namespace AudioCore { | ||
| 12 | |||
| 13 | class BehaviorInfo; | ||
| 14 | class ServerMemoryPoolInfo; | ||
| 15 | class VoiceContext; | ||
| 16 | class EffectContext; | ||
| 17 | class MixContext; | ||
| 18 | class SinkContext; | ||
| 19 | class SplitterContext; | ||
| 20 | |||
| 21 | class InfoUpdater { | ||
| 22 | public: | ||
| 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 | |||
| 46 | private: | ||
| 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 | |||
| 9 | namespace AudioCore { | ||
| 10 | |||
| 11 | ServerMemoryPoolInfo::ServerMemoryPoolInfo() = default; | ||
| 12 | ServerMemoryPoolInfo::~ServerMemoryPoolInfo() = default; | ||
| 13 | bool 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 | |||
| 11 | namespace AudioCore { | ||
| 12 | |||
| 13 | class ServerMemoryPoolInfo { | ||
| 14 | public: | ||
| 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 | |||
| 45 | private: | ||
| 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 | |||
| 10 | namespace AudioCore { | ||
| 11 | MixContext::MixContext() = default; | ||
| 12 | MixContext::~MixContext() = default; | ||
| 13 | |||
| 14 | void 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 | |||
| 30 | void 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 | |||
| 81 | void 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 | |||
| 94 | void 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 | |||
| 109 | bool 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 | |||
| 133 | std::size_t MixContext::GetCount() const { | ||
| 134 | return info_count; | ||
| 135 | } | ||
| 136 | |||
| 137 | ServerMixInfo& MixContext::GetInfo(std::size_t i) { | ||
| 138 | ASSERT(i < info_count); | ||
| 139 | return infos.at(i); | ||
| 140 | } | ||
| 141 | |||
| 142 | const ServerMixInfo& MixContext::GetInfo(std::size_t i) const { | ||
| 143 | ASSERT(i < info_count); | ||
| 144 | return infos.at(i); | ||
| 145 | } | ||
| 146 | |||
| 147 | ServerMixInfo& MixContext::GetSortedInfo(std::size_t i) { | ||
| 148 | ASSERT(i < info_count); | ||
| 149 | return *sorted_info.at(i); | ||
| 150 | } | ||
| 151 | |||
| 152 | const ServerMixInfo& MixContext::GetSortedInfo(std::size_t i) const { | ||
| 153 | ASSERT(i < info_count); | ||
| 154 | return *sorted_info.at(i); | ||
| 155 | } | ||
| 156 | |||
| 157 | ServerMixInfo& MixContext::GetFinalMixInfo() { | ||
| 158 | return infos.at(AudioCommon::FINAL_MIX); | ||
| 159 | } | ||
| 160 | |||
| 161 | const ServerMixInfo& MixContext::GetFinalMixInfo() const { | ||
| 162 | return infos.at(AudioCommon::FINAL_MIX); | ||
| 163 | } | ||
| 164 | |||
| 165 | EdgeMatrix& MixContext::GetEdgeMatrix() { | ||
| 166 | return edge_matrix; | ||
| 167 | } | ||
| 168 | |||
| 169 | const EdgeMatrix& MixContext::GetEdgeMatrix() const { | ||
| 170 | return edge_matrix; | ||
| 171 | } | ||
| 172 | |||
| 173 | ServerMixInfo::ServerMixInfo() { | ||
| 174 | Cleanup(); | ||
| 175 | } | ||
| 176 | ServerMixInfo::~ServerMixInfo() = default; | ||
| 177 | |||
| 178 | const ServerMixInfo::InParams& ServerMixInfo::GetInParams() const { | ||
| 179 | return in_params; | ||
| 180 | } | ||
| 181 | |||
| 182 | ServerMixInfo::InParams& ServerMixInfo::GetInParams() { | ||
| 183 | return in_params; | ||
| 184 | } | ||
| 185 | |||
| 186 | bool 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 | |||
| 212 | bool ServerMixInfo::HasAnyConnection() const { | ||
| 213 | return in_params.splitter_id != AudioCommon::NO_SPLITTER || | ||
| 214 | in_params.mix_id != AudioCommon::NO_MIX; | ||
| 215 | } | ||
| 216 | |||
| 217 | void 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 | |||
| 230 | bool 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 | |||
| 14 | namespace AudioCore { | ||
| 15 | class BehaviorInfo; | ||
| 16 | |||
| 17 | class MixInfo { | ||
| 18 | public: | ||
| 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 | |||
| 45 | class ServerMixInfo { | ||
| 46 | public: | ||
| 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 | |||
| 72 | private: | ||
| 73 | InParams in_params{}; | ||
| 74 | bool UpdateConnection(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix_in, | ||
| 75 | SplitterContext& splitter_context); | ||
| 76 | }; | ||
| 77 | |||
| 78 | class MixContext { | ||
| 79 | public: | ||
| 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 | |||
| 97 | private: | ||
| 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 | |||
| 7 | namespace AudioCore { | ||
| 8 | SinkContext::SinkContext(std::size_t sink_count) : sink_count(sink_count) {} | ||
| 9 | SinkContext::~SinkContext() = default; | ||
| 10 | |||
| 11 | std::size_t SinkContext::GetCount() const { | ||
| 12 | return sink_count; | ||
| 13 | } | ||
| 14 | |||
| 15 | void 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 | |||
| 21 | bool SinkContext::InUse() const { | ||
| 22 | return in_use; | ||
| 23 | } | ||
| 24 | |||
| 25 | std::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 | |||
| 12 | namespace AudioCore { | ||
| 13 | |||
| 14 | enum class SinkTypes : u8 { | ||
| 15 | Invalid = 0, | ||
| 16 | Device = 1, | ||
| 17 | Circular = 2, | ||
| 18 | }; | ||
| 19 | |||
| 20 | enum 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 | |||
| 30 | class SinkInfo { | ||
| 31 | public: | ||
| 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 | |||
| 72 | class SinkContext { | ||
| 73 | public: | ||
| 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 | |||
| 83 | private: | ||
| 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 | |||
| 11 | namespace AudioCore { | ||
| 12 | |||
| 13 | ServerSplitterDestinationData::ServerSplitterDestinationData(s32 id) : id(id) {} | ||
| 14 | ServerSplitterDestinationData::~ServerSplitterDestinationData() = default; | ||
| 15 | |||
| 16 | void 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 | |||
| 40 | ServerSplitterDestinationData* ServerSplitterDestinationData::GetNextDestination() { | ||
| 41 | return next; | ||
| 42 | } | ||
| 43 | |||
| 44 | const ServerSplitterDestinationData* ServerSplitterDestinationData::GetNextDestination() const { | ||
| 45 | return next; | ||
| 46 | } | ||
| 47 | |||
| 48 | void ServerSplitterDestinationData::SetNextDestination(ServerSplitterDestinationData* dest) { | ||
| 49 | next = dest; | ||
| 50 | } | ||
| 51 | |||
| 52 | bool ServerSplitterDestinationData::ValidMixId() const { | ||
| 53 | return GetMixId() != AudioCommon::NO_MIX; | ||
| 54 | } | ||
| 55 | |||
| 56 | s32 ServerSplitterDestinationData::GetMixId() const { | ||
| 57 | return mix_id; | ||
| 58 | } | ||
| 59 | |||
| 60 | bool ServerSplitterDestinationData::IsConfigured() const { | ||
| 61 | return in_use && ValidMixId(); | ||
| 62 | } | ||
| 63 | |||
| 64 | float ServerSplitterDestinationData::GetMixVolume(std::size_t i) const { | ||
| 65 | ASSERT(i < AudioCommon::MAX_MIX_BUFFERS); | ||
| 66 | return current_mix_volumes.at(i); | ||
| 67 | } | ||
| 68 | |||
| 69 | const std::array<float, AudioCommon::MAX_MIX_BUFFERS>& | ||
| 70 | ServerSplitterDestinationData::CurrentMixVolumes() const { | ||
| 71 | return current_mix_volumes; | ||
| 72 | } | ||
| 73 | |||
| 74 | const std::array<float, AudioCommon::MAX_MIX_BUFFERS>& | ||
| 75 | ServerSplitterDestinationData::LastMixVolumes() const { | ||
| 76 | return last_mix_volumes; | ||
| 77 | } | ||
| 78 | |||
| 79 | void ServerSplitterDestinationData::MarkDirty() { | ||
| 80 | needs_update = true; | ||
| 81 | } | ||
| 82 | |||
| 83 | void 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 | |||
| 90 | ServerSplitterInfo::ServerSplitterInfo(s32 id) : id(id) {} | ||
| 91 | ServerSplitterInfo::~ServerSplitterInfo() = default; | ||
| 92 | |||
| 93 | void ServerSplitterInfo::InitializeInfos() { | ||
| 94 | send_length = 0; | ||
| 95 | head = nullptr; | ||
| 96 | new_connection = true; | ||
| 97 | } | ||
| 98 | |||
| 99 | void ServerSplitterInfo::ClearNewConnectionFlag() { | ||
| 100 | new_connection = false; | ||
| 101 | } | ||
| 102 | |||
| 103 | std::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 | |||
| 115 | ServerSplitterDestinationData* ServerSplitterInfo::GetHead() { | ||
| 116 | return head; | ||
| 117 | } | ||
| 118 | |||
| 119 | const ServerSplitterDestinationData* ServerSplitterInfo::GetHead() const { | ||
| 120 | return head; | ||
| 121 | } | ||
| 122 | |||
| 123 | ServerSplitterDestinationData* 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 | |||
| 134 | const 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 | |||
| 145 | bool ServerSplitterInfo::HasNewConnection() const { | ||
| 146 | return new_connection; | ||
| 147 | } | ||
| 148 | |||
| 149 | s32 ServerSplitterInfo::GetLength() const { | ||
| 150 | return send_length; | ||
| 151 | } | ||
| 152 | |||
| 153 | void ServerSplitterInfo::SetHead(ServerSplitterDestinationData* new_head) { | ||
| 154 | head = new_head; | ||
| 155 | } | ||
| 156 | |||
| 157 | void ServerSplitterInfo::SetHeadDepth(s32 length) { | ||
| 158 | send_length = length; | ||
| 159 | } | ||
| 160 | |||
| 161 | SplitterContext::SplitterContext() = default; | ||
| 162 | SplitterContext::~SplitterContext() = default; | ||
| 163 | |||
| 164 | void 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 | |||
| 174 | bool 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 | |||
| 214 | bool SplitterContext::UsingSplitter() const { | ||
| 215 | return info_count > 0 && data_count > 0; | ||
| 216 | } | ||
| 217 | |||
| 218 | ServerSplitterInfo& SplitterContext::GetInfo(std::size_t i) { | ||
| 219 | ASSERT(i < info_count); | ||
| 220 | return infos.at(i); | ||
| 221 | } | ||
| 222 | |||
| 223 | const ServerSplitterInfo& SplitterContext::GetInfo(std::size_t i) const { | ||
| 224 | ASSERT(i < info_count); | ||
| 225 | return infos.at(i); | ||
| 226 | } | ||
| 227 | |||
| 228 | ServerSplitterDestinationData& SplitterContext::GetData(std::size_t i) { | ||
| 229 | ASSERT(i < data_count); | ||
| 230 | return datas.at(i); | ||
| 231 | } | ||
| 232 | |||
| 233 | const ServerSplitterDestinationData& SplitterContext::GetData(std::size_t i) const { | ||
| 234 | ASSERT(i < data_count); | ||
| 235 | return datas.at(i); | ||
| 236 | } | ||
| 237 | |||
| 238 | ServerSplitterDestinationData* 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 | |||
| 245 | const 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 | |||
| 252 | void SplitterContext::UpdateInternalState() { | ||
| 253 | if (data_count == 0) { | ||
| 254 | return; | ||
| 255 | } | ||
| 256 | |||
| 257 | for (auto& data : datas) { | ||
| 258 | data.UpdateInternalState(); | ||
| 259 | } | ||
| 260 | } | ||
| 261 | |||
| 262 | std::size_t SplitterContext::GetInfoCount() const { | ||
| 263 | return info_count; | ||
| 264 | } | ||
| 265 | |||
| 266 | std::size_t SplitterContext::GetDataCount() const { | ||
| 267 | return data_count; | ||
| 268 | } | ||
| 269 | |||
| 270 | void 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 | |||
| 287 | bool 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 | |||
| 327 | bool 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 | |||
| 360 | bool 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 | |||
| 412 | NodeStates::NodeStates() = default; | ||
| 413 | NodeStates::~NodeStates() = default; | ||
| 414 | |||
| 415 | void 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 | |||
| 424 | bool NodeStates::Tsort(EdgeMatrix& edge_matrix) { | ||
| 425 | return DepthFirstSearch(edge_matrix); | ||
| 426 | } | ||
| 427 | |||
| 428 | std::size_t NodeStates::GetIndexPos() const { | ||
| 429 | return index_pos; | ||
| 430 | } | ||
| 431 | |||
| 432 | const std::vector<s32>& NodeStates::GetIndexList() const { | ||
| 433 | return index_list; | ||
| 434 | } | ||
| 435 | |||
| 436 | void NodeStates::PushTsortResult(s32 index) { | ||
| 437 | ASSERT(index < node_count); | ||
| 438 | index_list[index_pos++] = index; | ||
| 439 | } | ||
| 440 | |||
| 441 | bool 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 | |||
| 497 | void 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 | |||
| 510 | void 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 | |||
| 527 | NodeStates::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 | |||
| 541 | NodeStates::Stack::Stack() = default; | ||
| 542 | NodeStates::Stack::~Stack() = default; | ||
| 543 | |||
| 544 | void 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 | |||
| 552 | void NodeStates::Stack::push(s32 val) { | ||
| 553 | ASSERT(stack_pos < stack_size); | ||
| 554 | stack[stack_pos++] = val; | ||
| 555 | } | ||
| 556 | |||
| 557 | std::size_t NodeStates::Stack::Count() const { | ||
| 558 | return stack_pos; | ||
| 559 | } | ||
| 560 | |||
| 561 | s32 NodeStates::Stack::top() const { | ||
| 562 | ASSERT(stack_pos > 0); | ||
| 563 | return stack[stack_pos - 1]; | ||
| 564 | } | ||
| 565 | |||
| 566 | s32 NodeStates::Stack::pop() { | ||
| 567 | ASSERT(stack_pos > 0); | ||
| 568 | stack_pos--; | ||
| 569 | return stack[stack_pos]; | ||
| 570 | } | ||
| 571 | |||
| 572 | EdgeMatrix::EdgeMatrix() = default; | ||
| 573 | EdgeMatrix::~EdgeMatrix() = default; | ||
| 574 | |||
| 575 | void EdgeMatrix::Initialize(std::size_t _node_count) { | ||
| 576 | node_count = _node_count; | ||
| 577 | edge_matrix.resize(node_count * node_count); | ||
| 578 | } | ||
| 579 | |||
| 580 | bool EdgeMatrix::Connected(s32 a, s32 b) { | ||
| 581 | return GetState(a, b); | ||
| 582 | } | ||
| 583 | |||
| 584 | void EdgeMatrix::Connect(s32 a, s32 b) { | ||
| 585 | SetState(a, b, true); | ||
| 586 | } | ||
| 587 | |||
| 588 | void EdgeMatrix::Disconnect(s32 a, s32 b) { | ||
| 589 | SetState(a, b, false); | ||
| 590 | } | ||
| 591 | |||
| 592 | void 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 | |||
| 598 | std::size_t EdgeMatrix::GetNodeCount() const { | ||
| 599 | return node_count; | ||
| 600 | } | ||
| 601 | |||
| 602 | void EdgeMatrix::SetState(s32 a, s32 b, bool state) { | ||
| 603 | ASSERT(InRange(a, b)); | ||
| 604 | edge_matrix.at(a * node_count + b) = state; | ||
| 605 | } | ||
| 606 | |||
| 607 | bool EdgeMatrix::GetState(s32 a, s32 b) { | ||
| 608 | ASSERT(InRange(a, b)); | ||
| 609 | return edge_matrix.at(a * node_count + b); | ||
| 610 | } | ||
| 611 | |||
| 612 | bool 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 | |||
| 14 | namespace AudioCore { | ||
| 15 | class BehaviorInfo; | ||
| 16 | |||
| 17 | class EdgeMatrix { | ||
| 18 | public: | ||
| 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 | |||
| 29 | private: | ||
| 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 | |||
| 38 | class NodeStates { | ||
| 39 | public: | ||
| 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 | |||
| 71 | private: | ||
| 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 | |||
| 86 | enum 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 | |||
| 92 | class SplitterInfo { | ||
| 93 | public: | ||
| 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 | |||
| 125 | class ServerSplitterDestinationData { | ||
| 126 | public: | ||
| 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 | |||
| 144 | private: | ||
| 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 | |||
| 154 | class ServerSplitterInfo { | ||
| 155 | public: | ||
| 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 | |||
| 174 | private: | ||
| 175 | s32 sample_rate{}; | ||
| 176 | s32 id{}; | ||
| 177 | s32 send_length{}; | ||
| 178 | ServerSplitterDestinationData* head = nullptr; | ||
| 179 | bool new_connection{}; | ||
| 180 | }; | ||
| 181 | |||
| 182 | class SplitterContext { | ||
| 183 | public: | ||
| 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 | |||
| 205 | private: | ||
| 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 | |||
| 9 | namespace AudioCore { | ||
| 10 | |||
| 11 | ServerVoiceChannelResource::ServerVoiceChannelResource(s32 id) : id(id) {} | ||
| 12 | ServerVoiceChannelResource::~ServerVoiceChannelResource() = default; | ||
| 13 | |||
| 14 | bool ServerVoiceChannelResource::InUse() const { | ||
| 15 | return in_use; | ||
| 16 | } | ||
| 17 | |||
| 18 | float ServerVoiceChannelResource::GetCurrentMixVolumeAt(std::size_t i) const { | ||
| 19 | ASSERT(i < AudioCommon::MAX_MIX_BUFFERS); | ||
| 20 | return mix_volume.at(i); | ||
| 21 | } | ||
| 22 | |||
| 23 | float ServerVoiceChannelResource::GetLastMixVolumeAt(std::size_t i) const { | ||
| 24 | ASSERT(i < AudioCommon::MAX_MIX_BUFFERS); | ||
| 25 | return last_mix_volume.at(i); | ||
| 26 | } | ||
| 27 | |||
| 28 | void 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 | |||
| 36 | void ServerVoiceChannelResource::UpdateLastMixVolumes() { | ||
| 37 | std::copy(mix_volume.begin(), mix_volume.end(), last_mix_volume.begin()); | ||
| 38 | } | ||
| 39 | |||
| 40 | const std::array<float, AudioCommon::MAX_MIX_BUFFERS>& | ||
| 41 | ServerVoiceChannelResource::GetCurrentMixVolume() const { | ||
| 42 | return mix_volume; | ||
| 43 | } | ||
| 44 | |||
| 45 | const std::array<float, AudioCommon::MAX_MIX_BUFFERS>& | ||
| 46 | ServerVoiceChannelResource::GetLastMixVolume() const { | ||
| 47 | return last_mix_volume; | ||
| 48 | } | ||
| 49 | |||
| 50 | ServerVoiceInfo::ServerVoiceInfo() { | ||
| 51 | Initialize(); | ||
| 52 | } | ||
| 53 | ServerVoiceInfo::~ServerVoiceInfo() = default; | ||
| 54 | |||
| 55 | void 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 | |||
| 99 | void 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 | |||
| 173 | void 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 | |||
| 210 | void 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 | |||
| 248 | void 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 | |||
| 267 | const ServerVoiceInfo::InParams& ServerVoiceInfo::GetInParams() const { | ||
| 268 | return in_params; | ||
| 269 | } | ||
| 270 | |||
| 271 | ServerVoiceInfo::InParams& ServerVoiceInfo::GetInParams() { | ||
| 272 | return in_params; | ||
| 273 | } | ||
| 274 | |||
| 275 | const ServerVoiceInfo::OutParams& ServerVoiceInfo::GetOutParams() const { | ||
| 276 | return out_params; | ||
| 277 | } | ||
| 278 | |||
| 279 | ServerVoiceInfo::OutParams& ServerVoiceInfo::GetOutParams() { | ||
| 280 | return out_params; | ||
| 281 | } | ||
| 282 | |||
| 283 | bool 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 | |||
| 288 | bool 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 | |||
| 305 | void 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 | |||
| 317 | bool 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 | |||
| 381 | void 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 | |||
| 399 | bool 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 | |||
| 404 | VoiceContext::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 | |||
| 413 | VoiceContext::~VoiceContext() { | ||
| 414 | sorted_voice_info.clear(); | ||
| 415 | } | ||
| 416 | |||
| 417 | std::size_t VoiceContext::GetVoiceCount() const { | ||
| 418 | return voice_count; | ||
| 419 | } | ||
| 420 | |||
| 421 | ServerVoiceChannelResource& VoiceContext::GetChannelResource(std::size_t i) { | ||
| 422 | ASSERT(i < voice_count); | ||
| 423 | return voice_channel_resources.at(i); | ||
| 424 | } | ||
| 425 | |||
| 426 | const ServerVoiceChannelResource& VoiceContext::GetChannelResource(std::size_t i) const { | ||
| 427 | ASSERT(i < voice_count); | ||
| 428 | return voice_channel_resources.at(i); | ||
| 429 | } | ||
| 430 | |||
| 431 | VoiceState& VoiceContext::GetState(std::size_t i) { | ||
| 432 | ASSERT(i < voice_count); | ||
| 433 | return voice_states.at(i); | ||
| 434 | } | ||
| 435 | |||
| 436 | const VoiceState& VoiceContext::GetState(std::size_t i) const { | ||
| 437 | ASSERT(i < voice_count); | ||
| 438 | return voice_states.at(i); | ||
| 439 | } | ||
| 440 | |||
| 441 | VoiceState& VoiceContext::GetDspSharedState(std::size_t i) { | ||
| 442 | ASSERT(i < voice_count); | ||
| 443 | return dsp_voice_states.at(i); | ||
| 444 | } | ||
| 445 | |||
| 446 | const VoiceState& VoiceContext::GetDspSharedState(std::size_t i) const { | ||
| 447 | ASSERT(i < voice_count); | ||
| 448 | return dsp_voice_states.at(i); | ||
| 449 | } | ||
| 450 | |||
| 451 | ServerVoiceInfo& VoiceContext::GetInfo(std::size_t i) { | ||
| 452 | ASSERT(i < voice_count); | ||
| 453 | return voice_info.at(i); | ||
| 454 | } | ||
| 455 | |||
| 456 | const ServerVoiceInfo& VoiceContext::GetInfo(std::size_t i) const { | ||
| 457 | ASSERT(i < voice_count); | ||
| 458 | return voice_info.at(i); | ||
| 459 | } | ||
| 460 | |||
| 461 | ServerVoiceInfo& VoiceContext::GetSortedInfo(std::size_t i) { | ||
| 462 | ASSERT(i < voice_count); | ||
| 463 | return *sorted_voice_info.at(i); | ||
| 464 | } | ||
| 465 | |||
| 466 | const ServerVoiceInfo& VoiceContext::GetSortedInfo(std::size_t i) const { | ||
| 467 | ASSERT(i < voice_count); | ||
| 468 | return *sorted_voice_info.at(i); | ||
| 469 | } | ||
| 470 | |||
| 471 | s32 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 | |||
| 507 | void 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 | |||
| 526 | void 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 | |||
| 15 | namespace Core::Memory { | ||
| 16 | class Memory; | ||
| 17 | } | ||
| 18 | |||
| 19 | namespace AudioCore { | ||
| 20 | |||
| 21 | class BehaviorInfo; | ||
| 22 | class VoiceContext; | ||
| 23 | |||
| 24 | enum 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 | |||
| 34 | enum class PlayState : u8 { | ||
| 35 | Started = 0, | ||
| 36 | Stopped = 1, | ||
| 37 | Paused = 2, | ||
| 38 | }; | ||
| 39 | |||
| 40 | enum class ServerPlayState { | ||
| 41 | Play = 0, | ||
| 42 | Stop = 1, | ||
| 43 | RequestStop = 2, | ||
| 44 | Paused = 3, | ||
| 45 | }; | ||
| 46 | |||
| 47 | struct BiquadFilterParameter { | ||
| 48 | bool enabled{}; | ||
| 49 | INSERT_PADDING_BYTES(1); | ||
| 50 | std::array<s16, 3> numerator{}; | ||
| 51 | std::array<s16, 2> denominator{}; | ||
| 52 | }; | ||
| 53 | static_assert(sizeof(BiquadFilterParameter) == 0xc, "BiquadFilterParameter is an invalid size"); | ||
| 54 | |||
| 55 | struct 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 | }; | ||
| 68 | static_assert(sizeof(WaveBuffer) == 0x38, "WaveBuffer is an invalid size"); | ||
| 69 | |||
| 70 | struct 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 | |||
| 82 | struct BehaviorFlags { | ||
| 83 | BitField<0, 1, u16> is_played_samples_reset_at_loop_point; | ||
| 84 | BitField<1, 1, u16> is_pitch_and_src_skipped; | ||
| 85 | }; | ||
| 86 | static_assert(sizeof(BehaviorFlags) == 0x4, "BehaviorFlags is an invalid size"); | ||
| 87 | |||
| 88 | struct 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 | |||
| 108 | class VoiceChannelResource { | ||
| 109 | public: | ||
| 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 | |||
| 119 | class ServerVoiceChannelResource { | ||
| 120 | public: | ||
| 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 | |||
| 133 | private: | ||
| 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 | |||
| 140 | class VoiceInfo { | ||
| 141 | public: | ||
| 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 | |||
| 183 | class ServerVoiceInfo { | ||
| 184 | public: | ||
| 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 | |||
| 251 | private: | ||
| 252 | std::vector<s16> stored_samples; | ||
| 253 | InParams in_params{}; | ||
| 254 | OutParams out_params{}; | ||
| 255 | |||
| 256 | bool HasValidWaveBuffer(const VoiceState* state) const; | ||
| 257 | }; | ||
| 258 | |||
| 259 | class VoiceContext { | ||
| 260 | public: | ||
| 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 | |||
| 282 | private: | ||
| 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 | ||
| 27 | class IAudioRenderer final : public ServiceFramework<IAudioRenderer> { | 27 | class IAudioRenderer final : public ServiceFramework<IAudioRenderer> { |
| 28 | public: | 28 | public: |
| 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 | ||
| 349 | static u64 CalculateNumPerformanceEntries(const AudioCore::AudioRendererParameter& params) { | 350 | static 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 | ||
| 682 | void AudRenU::OpenAudioRendererImpl(Kernel::HLERequestContext& ctx) { | 687 | void 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); |