From 383bf1c7ba5dbe7827ab0399b399a0039d852900 Mon Sep 17 00:00:00 2001 From: ShibuyaCyana <39703247+ShibuyaCyana@users.noreply.github.com> Date: Fri, 15 May 2026 00:48:54 +0800 Subject: [PATCH] feat: Customizable mixing matrix --- Assets/Scripts/IO/AudioManager.cs | 238 ++++++------------ Assets/Scripts/Misc/Base/MajEnv.cs | 4 +- .../MixingMatrixConfigConverter.cs | 79 ++++++ Assets/Scripts/Misc/Settings/GameSetting.cs | 20 +- 4 files changed, 171 insertions(+), 170 deletions(-) create mode 100644 Assets/Scripts/Misc/Json/JsonConverters/MixingMatrixConfigConverter.cs diff --git a/Assets/Scripts/IO/AudioManager.cs b/Assets/Scripts/IO/AudioManager.cs index 20be0f2e6..3f6f2bf76 100644 --- a/Assets/Scripts/IO/AudioManager.cs +++ b/Assets/Scripts/IO/AudioManager.cs @@ -622,175 +622,101 @@ public void OpenAsioPannel() } #endif } - static void GenerateMixingMatrix(int chCount) - { - // var matrix = new float[8, 2] - // { - //// Input L R - // { 1f, 0f }, // LF - // { 0f, 1f }, // RF - // { 0f, 0f }, // Center - // { 0f, 0f }, // LFE - // { 0f, 0f }, // LR - // { 0f, 0f }, // RR - // { 0f, 0f }, // LR Center - // { 0f, 0f }, // RR Center - // }; - // 3 channels left - front, right - front, center. - // 4 channels left - front, right - front, left - rear / side, right - rear / side. - // 6 channels(5.1) left - front, right - front, center, LFE, left - rear / side, right - rear / side. - // 8 channels(7.1) left - front, right - front, center, LFE, left - rear / side, right - rear / side, left - rear center, right - rear center. - - // LFE = left - // Center = right + const int INPUT_CHANNELS = 2; // Game output channel count, aka backend input channel count - float[,] matrix; + static bool TryGetUserMatrix(int outputChCount, out float[,] userMatrix) + { + var config = MajEnv.Settings.Audio.MixingMatrix.ByChannelCount; + string? key = outputChCount.ToString(); + return config.TryGetValue(key, out userMatrix) || config.TryGetValue("*", out userMatrix); + } - var isForceMono = MajEnv.Settings.Audio.ForceMono; -#if UNITY_ANDROID || UNITY_IOS - var volumeSettings = new + static bool ValidateMixingMatrix(float[,] matrix, int expectedOutputCh) + { + int matrixInputCh = matrix.GetLength(1); + int matrixOutputCh = matrix.GetLength(0); + if (matrixOutputCh == 0 || matrixInputCh != INPUT_CHANNELS || matrixOutputCh > expectedOutputCh) { - FrontVolume = 1f, - CenterAndLFEVolume = 1f, - SideVolume = 1f, - RearVolume = 1f - }; -#else - var volumeSettings = MajEnv.Settings.Audio.Channel; -#endif - if (isForceMono) + MajDebug.LogWarning($"[Audio] Mixing matrix shape: [{matrix.GetLength(0)}, {matrix.GetLength(1)}], expecting [{expectedOutputCh}, {INPUT_CHANNELS}]. Using fallback."); + return false; + } + + for (int i = 0; i < matrixOutputCh; i++) { - switch (chCount) + bool allZero = true; + for (int j = 0; j < INPUT_CHANNELS; j++) { - case 1:// Mono - matrix = new float[1, 2] - { - { 0.5f, 0.5f } - }; - break; - case 2: // 2.0 - matrix = new float[2, 2] - { - { 0.5f, 0.5f }, - { 0.5f, 0.5f } - }; - break; - case 3: // 3.0 - matrix = new float[3, 2] - { - { 0.5f, 0.5f }, - { 0.5f, 0.5f }, - { 0.5f, 0.5f }, - }; - break; - case 4: // 4.0 - matrix = new float[4, 2] - { - { 0.5f, 0.5f }, - { 0.5f, 0.5f }, - { 0.5f, 0.5f }, - { 0.5f, 0.5f }, - }; - break; - case 6: // 5.1 - matrix = new float[6, 2] - { - { 0.5f, 0.5f }, - { 0.5f, 0.5f }, - { 0.5f, 0.5f }, - { 0.5f, 0.5f }, - { 0.5f, 0.5f }, - { 0.5f, 0.5f }, - }; - break; - case 8: // 7.1 - matrix = new float[8, 2] - { - { 0.5f, 0.5f }, - { 0.5f, 0.5f }, - { 0.5f, 0.5f }, - { 0.5f, 0.5f }, - { 0.5f, 0.5f }, - { 0.5f, 0.5f }, - { 0.5f, 0.5f }, - { 0.5f, 0.5f }, - }; - break; - default: - matrix = new float[1, 2] - { - { 0.5f, 0.5f } - }; + if (Math.Abs(matrix[i, j]) > 0.001f) + { + allZero = false; break; + } + } + if (allZero) + { + MajDebug.LogWarning($"[Audio] Output channel {i} is used but muted."); } } - else + + return true; + } + + static float[,] ExpandMatrix(float[,] matrix, int targetOutputCh) + { + int rows = matrix.GetLength(0); + if (rows >= targetOutputCh) { - switch (chCount) + return matrix; + } + + var expanded = new float[targetOutputCh, INPUT_CHANNELS]; + for (int i = 0; i < rows; i++) + { + for (int j = 0; j < INPUT_CHANNELS; j++) { - case 1:// Mono - matrix = new float[1, 2] - { - { 0.5f, 0.5f } - }; - break; - case 2: // 2.0 - matrix = new float[2, 2] - { - { volumeSettings.FrontVolume, 0f }, - { 0f, volumeSettings.FrontVolume } - }; - break; - case 3: // 3.0 - matrix = new float[3, 2] - { - { volumeSettings.FrontVolume, 0f }, - { volumeSettings.CenterAndLFEVolume/2f, volumeSettings.CenterAndLFEVolume/2f }, - { 0f, volumeSettings.FrontVolume }, - }; - break; - case 4: // 4.0 - matrix = new float[4, 2] - { - { volumeSettings.FrontVolume, 0f }, - { 0f, volumeSettings.FrontVolume }, - { volumeSettings.RearVolume, 0f }, - { 0f, volumeSettings.RearVolume }, - }; - break; - case 6: // 5.1 - matrix = new float[6, 2] - { - { volumeSettings.FrontVolume, 0f }, - { 0f, volumeSettings.FrontVolume }, - { volumeSettings.CenterAndLFEVolume, 0f }, - { 0f, volumeSettings.CenterAndLFEVolume }, - { volumeSettings.SideVolume, 0f }, - { 0f, volumeSettings.SideVolume } - }; - break; - case 8: // 7.1 - matrix = new float[8, 2] - { - { volumeSettings.FrontVolume, 0f }, - { 0f, volumeSettings.FrontVolume }, - { volumeSettings.CenterAndLFEVolume, 0f }, - { 0f, volumeSettings.CenterAndLFEVolume }, - { volumeSettings.SideVolume, 0f }, - { 0f, volumeSettings.SideVolume }, - { volumeSettings.RearVolume, 0f }, - { 0f, volumeSettings.RearVolume }, - }; - break; - default: - matrix = new float[1, 2] - { - { 0.5f, 0.5f } - }; - break; + expanded[i, j] = matrix[i, j]; + } + } + return expanded; + } + + static float[,] GetDefaultMatrix(int outputChCount, bool isForceMono) + { + if (isForceMono) + { + var matrix = new float[outputChCount, INPUT_CHANNELS]; + for (int i = 0; i < outputChCount; i++) + { + matrix[i, 0] = 0.5f; + matrix[i, 1] = 0.5f; } + return matrix; } + return outputChCount switch + { + // [output_ch, input_ch]: row i = [L_weight, R_weight] for output channel i + 1 => new float[1, 2] { { 0.5f, 0.5f } }, + 4 => new float[4, 2] { { 1f, 0f }, { 0f, 1f }, { 0.5f, 0.5f }, { 0.5f, 0.5f } }, + 6 => new float[6, 2] { { 1f, 0f }, { 0f, 1f }, { 0.5f, 0.5f }, { 0.5f, 0.5f }, { 0f, 0f }, { 0f, 0f } }, + 8 => new float[8, 2] { { 1f, 0f }, { 0f, 1f }, { 0.5f, 0.5f }, { 0.5f, 0.5f }, { 0f, 0f }, { 0f, 0f }, { 0f, 0f }, { 0f, 0f } }, + _ => new float[2, 2] { { 1f, 0f }, { 0f, 1f } } + }; + } + + static void GenerateMixingMatrix(int outputChCount) + { + float[,] matrix; + + if (TryGetUserMatrix(outputChCount, out var userMatrix) && ValidateMixingMatrix(userMatrix, outputChCount)) + { + matrix = ExpandMatrix(userMatrix, outputChCount); + } + else + { + MajDebug.LogWarning($"[Audio] Using default matrix."); + matrix = GetDefaultMatrix(outputChCount, MajEnv.Settings.Audio.ForceMono); + } MixingMatrix = matrix; } diff --git a/Assets/Scripts/Misc/Base/MajEnv.cs b/Assets/Scripts/Misc/Base/MajEnv.cs index b47e5bab9..7fed6dd9f 100644 --- a/Assets/Scripts/Misc/Base/MajEnv.cs +++ b/Assets/Scripts/Misc/Base/MajEnv.cs @@ -9,6 +9,7 @@ using MajdataPlay.Buffers; using MajdataPlay.Collections; using MajdataPlay.Extensions; +using MajdataPlay.Json; using MajdataPlay.Net; using MajdataPlay.Numerics; using MajdataPlay.Settings; @@ -141,7 +142,8 @@ public static CancellationToken GlobalCT ObjectCreationHandling = ObjectCreationHandling.Replace, Converters = { - new StringEnumConverter() + new StringEnumConverter(), + new MixingMatrixConfigConverter() } }; diff --git a/Assets/Scripts/Misc/Json/JsonConverters/MixingMatrixConfigConverter.cs b/Assets/Scripts/Misc/Json/JsonConverters/MixingMatrixConfigConverter.cs new file mode 100644 index 000000000..1aea623ca --- /dev/null +++ b/Assets/Scripts/Misc/Json/JsonConverters/MixingMatrixConfigConverter.cs @@ -0,0 +1,79 @@ +using MajdataPlay.Settings; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; + +#nullable enable + +namespace MajdataPlay.Json +{ + public class MixingMatrixConfigConverter : JsonConverter + { + const int INPUT_CHANNELS = 2; + + public override MixingMatrixConfig? ReadJson(JsonReader reader, Type objectType, MixingMatrixConfig? existingValue, bool hasExistingValue, JsonSerializer serializer) + { + var config = new MixingMatrixConfig(); + var obj = JObject.Load(reader); + + foreach (var prop in obj.Properties()) + { + var key = prop.Name; + if (prop.Value is not JArray array) + { + continue; + } + + int rowCount = array.Count; + if (rowCount == 0) + { + continue; + } + + var matrix = new float[rowCount, INPUT_CHANNELS]; + for (int i = 0; i < rowCount; i++) + { + if (array[i] is not JArray row) + { + continue; + } + for (int j = 0; j < Math.Min(row.Count, INPUT_CHANNELS); j++) + { + var val = row[j]?.Value() ?? 0f; + matrix[i, j] = Math.Clamp(val, -1f, 1f); + } + } + + config.ByChannelCount[key] = matrix; + } + + return config; + } + + public override void WriteJson(JsonWriter writer, MixingMatrixConfig? value, JsonSerializer serializer) + { + writer.WriteStartObject(); + + foreach (var kvp in value!.ByChannelCount) + { + writer.WritePropertyName(kvp.Key); + writer.WriteStartArray(); + var matrix = kvp.Value; + int rows = matrix.GetLength(0); + int cols = matrix.GetLength(1); + for (int i = 0; i < rows; i++) + { + writer.WriteStartArray(); + for (int j = 0; j < cols; j++) + { + writer.WriteValue(matrix[i, j]); + } + writer.WriteEndArray(); + } + writer.WriteEndArray(); + } + + writer.WriteEndObject(); + } + } +} diff --git a/Assets/Scripts/Misc/Settings/GameSetting.cs b/Assets/Scripts/Misc/Settings/GameSetting.cs index c3b69fcb0..fd4cd202f 100644 --- a/Assets/Scripts/Misc/Settings/GameSetting.cs +++ b/Assets/Scripts/Misc/Settings/GameSetting.cs @@ -230,10 +230,11 @@ public class SoundOptions #if !(UNITY_ANDROID || UNITY_IOS) public WasapiOptions Wasapi { get; set; } = new(); - + public AsioOptions Asio { get; set; } = new(); - - public ChannelOptions Channel { get; set; } = new(); + + [HideInSettingUI] + public MixingMatrixConfig MixingMatrix { get; set; } = new(); #else public MobileAudioOptions Mobile { get; set; } = new(); #endif @@ -653,18 +654,11 @@ public struct CapacitiveTouchPanelRadiusOffsetConfig public int E { get; set; } } - public class ChannelOptions + public class MixingMatrixConfig { - // Front (LF / RF) - // Rear (LR / RR) - // Side (LS / RS) (rear center) - // CenterAndLFE (LFE / Center) - public float FrontVolume { get; set; } = 1f; - public float CenterAndLFEVolume { get; set; } = 1f; - public float SideVolume { get; set; } = 1f; - public float RearVolume { get; set; } = 1f; - + public Dictionary ByChannelCount { get; set; } = new(); } + public class AsioOptions { public int DeviceIndex { get; set; } = 0;