summaryrefslogtreecommitdiffstats
path: root/src/audio_core/renderer/mix
diff options
context:
space:
mode:
authorKelebek1 <eeeedddccc@hotmail.co.uk>2022-07-17 00:48:45 +0200
committerKelebek1 <eeeedddccc@hotmail.co.uk>2022-07-22 02:11:32 +0200
commit458da8a94877677f086f06cdeecf959ec4283a33 (patch)
tree583166d77602ad90a0d552f37de8729ad80fd6c1 /src/audio_core/renderer/mix
parentMerge pull request #8598 from Link4565/recv-dontwait (diff)
downloadyuzu-458da8a94877677f086f06cdeecf959ec4283a33.tar
yuzu-458da8a94877677f086f06cdeecf959ec4283a33.tar.gz
yuzu-458da8a94877677f086f06cdeecf959ec4283a33.tar.bz2
yuzu-458da8a94877677f086f06cdeecf959ec4283a33.tar.lz
yuzu-458da8a94877677f086f06cdeecf959ec4283a33.tar.xz
yuzu-458da8a94877677f086f06cdeecf959ec4283a33.tar.zst
yuzu-458da8a94877677f086f06cdeecf959ec4283a33.zip
Diffstat (limited to 'src/audio_core/renderer/mix')
-rw-r--r--src/audio_core/renderer/mix/mix_context.cpp141
-rw-r--r--src/audio_core/renderer/mix/mix_context.h124
-rw-r--r--src/audio_core/renderer/mix/mix_info.cpp120
-rw-r--r--src/audio_core/renderer/mix/mix_info.h124
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