diff options
Diffstat (limited to 'src/audio_core/renderer/mix')
-rw-r--r-- | src/audio_core/renderer/mix/mix_context.cpp | 141 | ||||
-rw-r--r-- | src/audio_core/renderer/mix/mix_context.h | 124 | ||||
-rw-r--r-- | src/audio_core/renderer/mix/mix_info.cpp | 120 | ||||
-rw-r--r-- | src/audio_core/renderer/mix/mix_info.h | 124 |
4 files changed, 509 insertions, 0 deletions
diff --git a/src/audio_core/renderer/mix/mix_context.cpp b/src/audio_core/renderer/mix/mix_context.cpp new file mode 100644 index 000000000..2427c83ed --- /dev/null +++ b/src/audio_core/renderer/mix/mix_context.cpp @@ -0,0 +1,141 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <ranges> + +#include "audio_core/renderer/mix/mix_context.h" +#include "audio_core/renderer/splitter/splitter_context.h" + +namespace AudioCore::AudioRenderer { + +void MixContext::Initialize(std::span<MixInfo*> sorted_mix_infos_, std::span<MixInfo> mix_infos_, + const u32 count_, std::span<s32> effect_process_order_buffer_, + const u32 effect_count_, std::span<u8> node_states_workbuffer, + const u64 node_buffer_size, std::span<u8> edge_matrix_workbuffer, + const u64 edge_matrix_size) { + count = count_; + sorted_mix_infos = sorted_mix_infos_; + mix_infos = mix_infos_; + effect_process_order_buffer = effect_process_order_buffer_; + effect_count = effect_count_; + + if (node_states_workbuffer.size() > 0 && edge_matrix_workbuffer.size() > 0) { + node_states.Initialize(node_states_workbuffer, node_buffer_size, count); + edge_matrix.Initialize(edge_matrix_workbuffer, edge_matrix_size, count); + } + + for (s32 i = 0; i < count; i++) { + sorted_mix_infos[i] = &mix_infos[i]; + } +} + +MixInfo* MixContext::GetSortedInfo(const s32 index) { + return sorted_mix_infos[index]; +} + +void MixContext::SetSortedInfo(const s32 index, MixInfo& mix_info) { + sorted_mix_infos[index] = &mix_info; +} + +MixInfo* MixContext::GetInfo(const s32 index) { + return &mix_infos[index]; +} + +MixInfo* MixContext::GetFinalMixInfo() { + return &mix_infos[0]; +} + +s32 MixContext::GetCount() const { + return count; +} + +void MixContext::UpdateDistancesFromFinalMix() { + for (s32 i = 0; i < count; i++) { + mix_infos[i].distance_from_final_mix = InvalidDistanceFromFinalMix; + } + + for (s32 i = 0; i < count; i++) { + auto& mix_info{mix_infos[i]}; + sorted_mix_infos[i] = &mix_info; + + if (!mix_info.in_use) { + continue; + } + + auto mix_id{mix_info.mix_id}; + auto distance_to_final_mix{FinalMixId}; + + while (distance_to_final_mix < count) { + if (mix_id == FinalMixId) { + break; + } + + if (mix_id == UnusedMixId) { + distance_to_final_mix = InvalidDistanceFromFinalMix; + break; + } + + auto distance_from_final_mix{mix_infos[mix_id].distance_from_final_mix}; + if (distance_from_final_mix != InvalidDistanceFromFinalMix) { + distance_to_final_mix = distance_from_final_mix + 1; + break; + } + + distance_to_final_mix++; + mix_id = mix_infos[mix_id].dst_mix_id; + } + + if (distance_to_final_mix >= count) { + distance_to_final_mix = InvalidDistanceFromFinalMix; + } + mix_info.distance_from_final_mix = distance_to_final_mix; + } +} + +void MixContext::SortInfo() { + UpdateDistancesFromFinalMix(); + + std::ranges::sort(sorted_mix_infos, [](const MixInfo* lhs, const MixInfo* rhs) { + return lhs->distance_from_final_mix > rhs->distance_from_final_mix; + }); + + CalcMixBufferOffset(); +} + +void MixContext::CalcMixBufferOffset() { + s16 offset{0}; + for (s32 i = 0; i < count; i++) { + auto mix_info{sorted_mix_infos[i]}; + if (mix_info->in_use) { + const auto buffer_count{mix_info->buffer_count}; + mix_info->buffer_offset = offset; + offset += buffer_count; + } + } +} + +bool MixContext::TSortInfo(const SplitterContext& splitter_context) { + if (!splitter_context.UsingSplitter()) { + CalcMixBufferOffset(); + return true; + } + + if (!node_states.Tsort(edge_matrix)) { + return false; + } + + std::vector<s32> sorted_results{node_states.GetSortedResuls()}; + const auto result_size{std::min(count, static_cast<s32>(sorted_results.size()))}; + for (s32 i = 0; i < result_size; i++) { + sorted_mix_infos[i] = &mix_infos[sorted_results[i]]; + } + + CalcMixBufferOffset(); + return true; +} + +EdgeMatrix& MixContext::GetEdgeMatrix() { + return edge_matrix; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/mix/mix_context.h b/src/audio_core/renderer/mix/mix_context.h new file mode 100644 index 000000000..da3aa2829 --- /dev/null +++ b/src/audio_core/renderer/mix/mix_context.h @@ -0,0 +1,124 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <span> + +#include "audio_core/renderer/mix/mix_info.h" +#include "audio_core/renderer/nodes/edge_matrix.h" +#include "audio_core/renderer/nodes/node_states.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +class SplitterContext; + +/* + * Manages mixing states, sorting and building a node graph to describe a mix order. + */ +class MixContext { +public: + /** + * Initialize the mix context. + * + * @param sorted_mix_infos - Buffer for the sorted mix infos. + * @param mix_infos - Buffer for the mix infos. + * @param effect_process_order_buffer - Buffer for the effect process orders. + * @param effect_count - Number of effects in the buffer. + * @param node_states_workbuffer - Buffer for node states. + * @param node_buffer_size - Size of the node states buffer. + * @param edge_matrix_workbuffer - Buffer for edge matrix. + * @param edge_matrix_size - Size of the edge matrix buffer. + */ + void Initialize(std::span<MixInfo*> sorted_mix_infos, std::span<MixInfo> mix_infos, u32 count_, + std::span<s32> effect_process_order_buffer, u32 effect_count, + std::span<u8> node_states_workbuffer, u64 node_buffer_size, + std::span<u8> edge_matrix_workbuffer, u64 edge_matrix_size); + + /** + * Get a sorted mix at the given index. + * + * @param index - Index of sorted mix. + * @return The sorted mix. + */ + MixInfo* GetSortedInfo(s32 index); + + /** + * Set the sorted info at the given index. + * + * @param index - Index of sorted mix. + * @param mix_info - The new mix for this index. + */ + void SetSortedInfo(s32 index, MixInfo& mix_info); + + /** + * Get a mix at the given index. + * + * @param index - Index of mix. + * @return The mix. + */ + MixInfo* GetInfo(s32 index); + + /** + * Get the final mix. + * + * @return The final mix. + */ + MixInfo* GetFinalMixInfo(); + + /** + * Get the current number of mixes. + * + * @return The number of active mixes. + */ + s32 GetCount() const; + + /** + * Update all of the mixes' distance from the final mix. + * Needs to be called after altering the mix graph. + */ + void UpdateDistancesFromFinalMix(); + + /** + * Non-splitter sort, sorts the sorted mixes based on their distance from the final mix. + */ + void SortInfo(); + + /** + * Re-calculate the mix buffer offsets for each mix after altering the mix. + */ + void CalcMixBufferOffset(); + + /** + * Splitter sort, traverse the splitter node graph and sort the sorted mixes from results. + * + * @param splitter_context - Splitter context for the sort. + * @return True if the sort was successful, othewise false. + */ + bool TSortInfo(const SplitterContext& splitter_context); + + /** + * Get the edge matrix used for the mix graph. + * + * @return The edge matrix used. + */ + EdgeMatrix& GetEdgeMatrix(); + +private: + /// Array of sorted mixes + std::span<MixInfo*> sorted_mix_infos{}; + /// Array of mixes + std::span<MixInfo> mix_infos{}; + /// Number of active mixes + s32 count{}; + /// Array of effect process orderings + std::span<s32> effect_process_order_buffer{}; + /// Number of effects in the process ordering buffer + u64 effect_count{}; + /// Node states used in splitter sort + NodeStates node_states{}; + /// Edge matrix for connected nodes used in splitter sort + EdgeMatrix edge_matrix{}; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/mix/mix_info.cpp b/src/audio_core/renderer/mix/mix_info.cpp new file mode 100644 index 000000000..cc18e57ee --- /dev/null +++ b/src/audio_core/renderer/mix/mix_info.cpp @@ -0,0 +1,120 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/behavior/behavior_info.h" +#include "audio_core/renderer/effect/effect_context.h" +#include "audio_core/renderer/mix/mix_info.h" +#include "audio_core/renderer/nodes/edge_matrix.h" +#include "audio_core/renderer/splitter/splitter_context.h" + +namespace AudioCore::AudioRenderer { + +MixInfo::MixInfo(std::span<s32> effect_order_buffer_, s32 effect_count_, BehaviorInfo& behavior) + : effect_order_buffer{effect_order_buffer_}, effect_count{effect_count_}, + long_size_pre_delay_supported{behavior.IsLongSizePreDelaySupported()} { + ClearEffectProcessingOrder(); +} + +void MixInfo::Cleanup() { + mix_id = UnusedMixId; + dst_mix_id = UnusedMixId; + dst_splitter_id = UnusedSplitterId; +} + +void MixInfo::ClearEffectProcessingOrder() { + for (s32 i = 0; i < effect_count; i++) { + effect_order_buffer[i] = -1; + } +} + +bool MixInfo::Update(EdgeMatrix& edge_matrix, const InParameter& in_params, + EffectContext& effect_context, SplitterContext& splitter_context, + const BehaviorInfo& behavior) { + volume = in_params.volume; + sample_rate = in_params.sample_rate; + buffer_count = static_cast<s16>(in_params.buffer_count); + in_use = in_params.in_use; + mix_id = in_params.mix_id; + node_id = in_params.node_id; + mix_volumes = in_params.mix_volumes; + + bool sort_required{false}; + if (behavior.IsSplitterSupported()) { + sort_required = UpdateConnection(edge_matrix, in_params, splitter_context); + } else { + if (dst_mix_id != in_params.dest_mix_id) { + dst_mix_id = in_params.dest_mix_id; + sort_required = true; + } + dst_splitter_id = UnusedSplitterId; + } + + ClearEffectProcessingOrder(); + + // Check all effects, and set their order if they belong to this mix. + const auto count{effect_context.GetCount()}; + for (u32 i = 0; i < count; i++) { + const auto& info{effect_context.GetInfo(i)}; + if (mix_id == info.GetMixId()) { + const auto processing_order{info.GetProcessingOrder()}; + if (processing_order > effect_count) { + break; + } + effect_order_buffer[processing_order] = i; + } + } + + return sort_required; +} + +bool MixInfo::UpdateConnection(EdgeMatrix& edge_matrix, const InParameter& in_params, + SplitterContext& splitter_context) { + auto has_new_connection{false}; + if (dst_splitter_id != UnusedSplitterId) { + auto& splitter_info{splitter_context.GetInfo(dst_splitter_id)}; + has_new_connection = splitter_info.HasNewConnection(); + } + + // Check if this mix matches the input parameters. + // If everything is the same, don't bother updating. + if (dst_mix_id == in_params.dest_mix_id && dst_splitter_id == in_params.dest_splitter_id && + !has_new_connection) { + return false; + } + + // Reset the mix in the graph, as we're about to update it. + edge_matrix.RemoveEdges(mix_id); + + if (in_params.dest_mix_id == UnusedMixId) { + if (in_params.dest_splitter_id != UnusedSplitterId) { + // If the splitter is used, connect this mix to each active destination. + auto& splitter_info{splitter_context.GetInfo(in_params.dest_splitter_id)}; + auto const destination_count{splitter_info.GetDestinationCount()}; + + for (u32 i = 0; i < destination_count; i++) { + auto destination{ + splitter_context.GetDesintationData(in_params.dest_splitter_id, i)}; + + if (destination) { + const auto destination_id{destination->GetMixId()}; + if (destination_id != UnusedMixId) { + edge_matrix.Connect(mix_id, destination_id); + } + } + } + } + } else { + // If the splitter is not used, only connect this mix to its destination. + edge_matrix.Connect(mix_id, in_params.dest_mix_id); + } + + dst_mix_id = in_params.dest_mix_id; + dst_splitter_id = in_params.dest_splitter_id; + return true; +} + +bool MixInfo::HasAnyConnection() const { + return dst_mix_id != UnusedMixId || dst_splitter_id != UnusedSplitterId; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/mix/mix_info.h b/src/audio_core/renderer/mix/mix_info.h new file mode 100644 index 000000000..b5fa4c0c7 --- /dev/null +++ b/src/audio_core/renderer/mix/mix_info.h @@ -0,0 +1,124 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <array> +#include <span> + +#include "audio_core/common/common.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +class EdgeMatrix; +class SplitterContext; +class EffectContext; +class BehaviorInfo; + +/** + * A single mix, which may feed through other mixes in a chain until reaching the final output mix. + */ +class MixInfo { +public: + struct InParameter { + /* 0x000 */ f32 volume; + /* 0x004 */ u32 sample_rate; + /* 0x008 */ u32 buffer_count; + /* 0x00C */ bool in_use; + /* 0x00D */ bool is_dirty; + /* 0x010 */ s32 mix_id; + /* 0x014 */ u32 effect_count; + /* 0x018 */ s32 node_id; + /* 0x01C */ char unk01C[0x8]; + /* 0x024 */ std::array<std::array<f32, MaxMixBuffers>, MaxMixBuffers> mix_volumes; + /* 0x924 */ s32 dest_mix_id; + /* 0x928 */ s32 dest_splitter_id; + /* 0x92C */ char unk92C[0x4]; + }; + static_assert(sizeof(InParameter) == 0x930, "MixInfo::InParameter has the wrong size!"); + + struct InDirtyParameter { + /* 0x00 */ u32 magic; + /* 0x04 */ s32 count; + /* 0x08 */ char unk08[0x18]; + }; + static_assert(sizeof(InDirtyParameter) == 0x20, + "MixInfo::InDirtyParameter has the wrong size!"); + + MixInfo(std::span<s32> effect_order_buffer, s32 effect_count, BehaviorInfo& behavior); + + /** + * Clean up the mix, resetting it to a default state. + */ + void Cleanup(); + + /** + * Clear the effect process order for all effects in this mix. + */ + void ClearEffectProcessingOrder(); + + /** + * Update the mix according to the given parameters. + * + * @param edge_matrix - Updated with new splitter node connections, if supported. + * @param in_params - Input parameters. + * @param effect_context - Used to update the effect orderings. + * @param splitter_context - Used to update the mix graph if supported. + * @param behavior - Used for checking which features are supported. + * @return True if the mix was updated and a sort is required, otherwise false. + */ + bool Update(EdgeMatrix& edge_matrix, const InParameter& in_params, + EffectContext& effect_context, SplitterContext& splitter_context, + const BehaviorInfo& behavior); + + /** + * Update the mix's connection in the node graph according to the given parameters. + * + * @param edge_matrix - Updated with new splitter node connections, if supported. + * @param in_params - Input parameters. + * @param splitter_context - Used to update the mix graph if supported. + * @return True if the mix was updated and a sort is required, otherwise false. + */ + bool UpdateConnection(EdgeMatrix& edge_matrix, const InParameter& in_params, + SplitterContext& splitter_context); + + /** + * Check if this mix is connected to any other. + * + * @return True if the mix has a connection, otherwise false. + */ + bool HasAnyConnection() const; + + /// Volume of this mix + f32 volume{}; + /// Sample rate of this mix + u32 sample_rate{}; + /// Number of buffers in this mix + s16 buffer_count{}; + /// Is this mix in use? + bool in_use{}; + /// Is this mix enabled? + bool enabled{}; + /// Id of this mix + s32 mix_id{UnusedMixId}; + /// Node id of this mix + s32 node_id{}; + /// Buffer offset for this mix + s16 buffer_offset{}; + /// Distance to the final mix + s32 distance_from_final_mix{InvalidDistanceFromFinalMix}; + /// Array of effect orderings of all effects in this mix + std::span<s32> effect_order_buffer; + /// Number of effects in this mix + const s32 effect_count; + /// Id for next mix in the chain + s32 dst_mix_id{UnusedMixId}; + /// Mixing volumes for this mix used when this mix is chained with another + std::array<std::array<f32, MaxMixBuffers>, MaxMixBuffers> mix_volumes{}; + /// Id for next mix in the graph when splitter is used + s32 dst_splitter_id{UnusedSplitterId}; + /// Is a longer pre-delay time supported for the reverb effect? + const bool long_size_pre_delay_supported; +}; + +} // namespace AudioCore::AudioRenderer |