-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathAudioNormalization.cs
118 lines (85 loc) · 3.59 KB
/
AudioNormalization.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using UnityEngine;
using Utility;
namespace SRXDAudioNormalization;
public static class AudioNormalization {
private static float preferredLoudnessLinear;
private static float maxGainLinear;
private static Dictionary<string, float> peaksForTrackData = new();
public static void Init() {
Plugin.PreferredLoudness.BindAndInvoke(value => preferredLoudnessLinear = AudioUtils.DecibelToLinear(value));
Plugin.MaxGain.BindAndInvoke(value => maxGainLinear = AudioUtils.DecibelToLinear(value));
}
public static void UpdateGain(Track track) {
float volume = PlayerSettingsData.Instance.MusicVolume.Value / 100f;
if (!track.IsInEditMode) {
var trackData = track.playStateFirst?.trackData;
if (trackData != null)
volume *= GetVolumeMultiplierForTrackData(trackData);
}
var mixerSettings = SoundEffectAssets.Instance.MixerSettings;
mixerSettings.mainMixer.SetFloat(mixerSettings.musicVolume, AudioUtils.LinearToDecibel(volume));
}
private static float GetVolumeMultiplierForTrackData(PlayableTrackData trackData) {
string hash = trackData.TrackInfoRef.UniqueName;
lock (peaksForTrackData) {
if (peaksForTrackData.TryGetValue(hash, out float peak)) {
if (peak < 0f)
return 1f;
return Mathf.Min(preferredLoudnessLinear / peak, maxGainLinear);
}
}
if (trackData.RawAudioDataHash == 0 && !trackData.LoadAllRawClipInfo())
return 1f;
AudioData clip = default;
bool found = false;
foreach (var subData in trackData.TrackDataList) {
foreach (var assetReference in subData.clipInfoAssetReferences) {
if (!assetReference.TryGetLoadedAsset(out var result)
|| !result.clipAssetReference.TryGetHandle<AudioClip, AudioClipAssetHandle>(out var handle))
continue;
clip = handle.RawData;
found = true;
break;
}
if (found)
break;
}
if (!found)
return 1f;
float[] data = clip.Data;
if (data == null)
return 1f;
lock (peaksForTrackData)
peaksForTrackData.Add(hash, -1f);
Task.Run(() => CalculatePeak(hash, clip.Data, clip.Channels, clip.Frequency));
return 1f;
}
private static void CalculatePeak(string hash, float[] data, int channels, int frequency) {
int peakWidth = 2 * frequency;
float[] buffer = new float[peakWidth];
double runningSum = 0d;
double peak = 0d;
for (int i = 0, j = 0; i < data.Length; i += channels, j++) {
float max = 0f;
for (int k = i; k < i + channels; k++) {
float sample = data[k];
float square = sample * sample;
if (square > max)
max = square;
}
int index = j % peakWidth;
runningSum += max - buffer[index];
buffer[index] = max;
if (runningSum > peak)
peak = runningSum;
}
peak = Math.Sqrt(peak / peakWidth);
// Plugin.Logger.LogMessage($"RMS: {rms:0.000}");
// Plugin.Logger.LogMessage($"Peak: {peak:0.000}");
lock (peaksForTrackData)
peaksForTrackData[hash] = (float) peak;
}
}