Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
238 changes: 82 additions & 156 deletions Assets/Scripts/IO/AudioManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
4 changes: 3 additions & 1 deletion Assets/Scripts/Misc/Base/MajEnv.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -141,7 +142,8 @@ public static CancellationToken GlobalCT
ObjectCreationHandling = ObjectCreationHandling.Replace,
Converters =
{
new StringEnumConverter()
new StringEnumConverter(),
new MixingMatrixConfigConverter()
}
};

Expand Down
Original file line number Diff line number Diff line change
@@ -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<MixingMatrixConfig>
{
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<float>() ?? 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();
}
}
}
20 changes: 7 additions & 13 deletions Assets/Scripts/Misc/Settings/GameSetting.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<string, float[,]> ByChannelCount { get; set; } = new();
}

public class AsioOptions
{
public int DeviceIndex { get; set; } = 0;
Expand Down