// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include #include "audio_core/behavior_info.h" #include "audio_core/common.h" #include "audio_core/effect_context.h" #include "audio_core/mix_context.h" #include "audio_core/splitter_context.h" namespace AudioCore { MixContext::MixContext() = default; MixContext::~MixContext() = default; void MixContext::Initialize(const BehaviorInfo& behavior_info, std::size_t mix_count, std::size_t effect_count) { info_count = mix_count; infos.resize(info_count); auto& final_mix = GetInfo(AudioCommon::FINAL_MIX); final_mix.GetInParams().mix_id = AudioCommon::FINAL_MIX; sorted_info.reserve(infos.size()); for (auto& info : infos) { sorted_info.push_back(&info); } for (auto& info : infos) { info.SetEffectCount(effect_count); } // Only initialize our edge matrix and node states if splitters are supported if (behavior_info.IsSplitterSupported()) { node_states.Initialize(mix_count); edge_matrix.Initialize(mix_count); } } void MixContext::UpdateDistancesFromFinalMix() { // Set all distances to be invalid for (std::size_t i = 0; i < info_count; i++) { GetInfo(i).GetInParams().final_mix_distance = AudioCommon::NO_FINAL_MIX; } for (std::size_t i = 0; i < info_count; i++) { auto& info = GetInfo(i); auto& in_params = info.GetInParams(); // Populate our sorted info sorted_info[i] = &info; if (!in_params.in_use) { continue; } auto mix_id = in_params.mix_id; // Needs to be referenced out of scope s32 distance_to_final_mix{AudioCommon::FINAL_MIX}; for (; distance_to_final_mix < static_cast(info_count); distance_to_final_mix++) { if (mix_id == AudioCommon::FINAL_MIX) { // If we're at the final mix, we're done break; } else if (mix_id == AudioCommon::NO_MIX) { // If we have no more mix ids, we're done distance_to_final_mix = AudioCommon::NO_FINAL_MIX; break; } else { const auto& dest_mix = GetInfo(mix_id); const auto dest_mix_distance = dest_mix.GetInParams().final_mix_distance; if (dest_mix_distance == AudioCommon::NO_FINAL_MIX) { // If our current mix isn't pointing to a final mix, follow through mix_id = dest_mix.GetInParams().dest_mix_id; } else { // Our current mix + 1 = final distance distance_to_final_mix = dest_mix_distance + 1; break; } } } // If we're out of range for our distance, mark it as no final mix if (distance_to_final_mix >= static_cast(info_count)) { distance_to_final_mix = AudioCommon::NO_FINAL_MIX; } in_params.final_mix_distance = distance_to_final_mix; } } void MixContext::CalcMixBufferOffset() { s32 offset{}; for (std::size_t i = 0; i < info_count; i++) { auto& info = GetSortedInfo(i); auto& in_params = info.GetInParams(); if (in_params.in_use) { // Only update if in use in_params.buffer_offset = offset; offset += in_params.buffer_count; } } } void MixContext::SortInfo() { // Get the distance to the final mix UpdateDistancesFromFinalMix(); // Sort based on the distance to the final mix std::sort(sorted_info.begin(), sorted_info.end(), [](const ServerMixInfo* lhs, const ServerMixInfo* rhs) { return lhs->GetInParams().final_mix_distance > rhs->GetInParams().final_mix_distance; }); // Calculate the mix buffer offset CalcMixBufferOffset(); } bool MixContext::TsortInfo(SplitterContext& splitter_context) { // If we're not using mixes, just calculate the mix buffer offset if (!splitter_context.UsingSplitter()) { CalcMixBufferOffset(); return true; } // Sort our node states if (!node_states.Tsort(edge_matrix)) { return false; } // Get our sorted list const auto sorted_list = node_states.GetIndexList(); std::size_t info_id{}; for (auto itr = sorted_list.rbegin(); itr != sorted_list.rend(); ++itr) { // Set our sorted info sorted_info[info_id++] = &GetInfo(*itr); } // Calculate the mix buffer offset CalcMixBufferOffset(); return true; } std::size_t MixContext::GetCount() const { return info_count; } ServerMixInfo& MixContext::GetInfo(std::size_t i) { ASSERT(i < info_count); return infos.at(i); } const ServerMixInfo& MixContext::GetInfo(std::size_t i) const { ASSERT(i < info_count); return infos.at(i); } ServerMixInfo& MixContext::GetSortedInfo(std::size_t i) { ASSERT(i < info_count); return *sorted_info.at(i); } const ServerMixInfo& MixContext::GetSortedInfo(std::size_t i) const { ASSERT(i < info_count); return *sorted_info.at(i); } ServerMixInfo& MixContext::GetFinalMixInfo() { return infos.at(AudioCommon::FINAL_MIX); } const ServerMixInfo& MixContext::GetFinalMixInfo() const { return infos.at(AudioCommon::FINAL_MIX); } EdgeMatrix& MixContext::GetEdgeMatrix() { return edge_matrix; } const EdgeMatrix& MixContext::GetEdgeMatrix() const { return edge_matrix; } ServerMixInfo::ServerMixInfo() { Cleanup(); } ServerMixInfo::~ServerMixInfo() = default; const ServerMixInfo::InParams& ServerMixInfo::GetInParams() const { return in_params; } ServerMixInfo::InParams& ServerMixInfo::GetInParams() { return in_params; } bool ServerMixInfo::Update(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix_in, BehaviorInfo& behavior_info, SplitterContext& splitter_context, EffectContext& effect_context) { in_params.volume = mix_in.volume; in_params.sample_rate = mix_in.sample_rate; in_params.buffer_count = mix_in.buffer_count; in_params.in_use = mix_in.in_use; in_params.mix_id = mix_in.mix_id; in_params.node_id = mix_in.node_id; for (std::size_t i = 0; i < mix_in.mix_volume.size(); i++) { std::copy(mix_in.mix_volume[i].begin(), mix_in.mix_volume[i].end(), in_params.mix_volume[i].begin()); } bool require_sort = false; if (behavior_info.IsSplitterSupported()) { require_sort = UpdateConnection(edge_matrix, mix_in, splitter_context); } else { in_params.dest_mix_id = mix_in.dest_mix_id; in_params.splitter_id = AudioCommon::NO_SPLITTER; } ResetEffectProcessingOrder(); const auto effect_count = effect_context.GetCount(); for (std::size_t i = 0; i < effect_count; i++) { auto* effect_info = effect_context.GetInfo(i); if (effect_info->GetMixID() == in_params.mix_id) { effect_processing_order[effect_info->GetProcessingOrder()] = static_cast(i); } } // TODO(ogniK): Update effect processing order return require_sort; } bool ServerMixInfo::HasAnyConnection() const { return in_params.splitter_id != AudioCommon::NO_SPLITTER || in_params.mix_id != AudioCommon::NO_MIX; } void ServerMixInfo::Cleanup() { in_params.volume = 0.0f; in_params.sample_rate = 0; in_params.buffer_count = 0; in_params.in_use = false; in_params.mix_id = AudioCommon::NO_MIX; in_params.node_id = 0; in_params.buffer_offset = 0; in_params.dest_mix_id = AudioCommon::NO_MIX; in_params.splitter_id = AudioCommon::NO_SPLITTER; std::memset(in_params.mix_volume.data(), 0, sizeof(float) * in_params.mix_volume.size()); } void ServerMixInfo::SetEffectCount(std::size_t count) { effect_processing_order.resize(count); ResetEffectProcessingOrder(); } void ServerMixInfo::ResetEffectProcessingOrder() { for (auto& order : effect_processing_order) { order = AudioCommon::NO_EFFECT_ORDER; } } s32 ServerMixInfo::GetEffectOrder(std::size_t i) const { return effect_processing_order.at(i); } bool ServerMixInfo::UpdateConnection(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix_in, SplitterContext& splitter_context) { // Mixes are identical if (in_params.dest_mix_id == mix_in.dest_mix_id && in_params.splitter_id == mix_in.splitter_id && ((in_params.splitter_id == AudioCommon::NO_SPLITTER) || !splitter_context.GetInfo(in_params.splitter_id).HasNewConnection())) { return false; } // Remove current edges for mix id edge_matrix.RemoveEdges(in_params.mix_id); if (mix_in.dest_mix_id != AudioCommon::NO_MIX) { // If we have a valid destination mix id, set our edge matrix edge_matrix.Connect(in_params.mix_id, mix_in.dest_mix_id); } else if (mix_in.splitter_id != AudioCommon::NO_SPLITTER) { // Recurse our splitter linked and set our edges auto& splitter_info = splitter_context.GetInfo(mix_in.splitter_id); const auto length = splitter_info.GetLength(); for (s32 i = 0; i < length; i++) { const auto* splitter_destination = splitter_context.GetDestinationData(mix_in.splitter_id, i); if (splitter_destination == nullptr) { continue; } if (splitter_destination->ValidMixId()) { edge_matrix.Connect(in_params.mix_id, splitter_destination->GetMixId()); } } } in_params.dest_mix_id = mix_in.dest_mix_id; in_params.splitter_id = mix_in.splitter_id; return true; } } // namespace AudioCore