-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathgating.go
More file actions
126 lines (110 loc) · 2.59 KB
/
gating.go
File metadata and controls
126 lines (110 loc) · 2.59 KB
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
119
120
121
122
123
124
125
126
package ebur128
import (
"math"
"sort"
)
const absGateLUFS = -70.0
// energy level corresponding to -70 LUFS
var absThreshold = math.Pow(10, (absGateLUFS+0.691)/10)
func energyToLoudness(energy float64) float64 {
if energy < 1e-20 {
return loudnessUnmeasurable
}
return -0.691 + 10*math.Log10(energy)
}
// 400ms blocks, 75% overlap, absolute + relative gate.
func integratedLoudness(subBlockEnergies []float64) (float64, int) {
n := len(subBlockEnergies)
if n < gatingBlocks {
return loudnessUnmeasurable, 0
}
numBlocks := n - gatingBlocks + 1
blockEnergies := make([]float64, numBlocks)
for i := range numBlocks {
var sum float64
for j := range gatingBlocks {
sum += subBlockEnergies[i+j]
}
blockEnergies[i] = sum / float64(gatingBlocks)
}
// first pass: absolute gate
var sum float64
var count int
for _, e := range blockEnergies {
if e >= absThreshold {
sum += e
count++
}
}
if count == 0 {
return loudnessUnmeasurable, 0
}
// second pass: relative gate (mean - 10 LU)
meanEnergy := sum / float64(count)
relThreshold := meanEnergy * math.Pow(10, -10.0/10.0)
sum = 0
count = 0
for _, e := range blockEnergies {
if e >= absThreshold && e >= relThreshold {
sum += e
count++
}
}
if count == 0 {
return loudnessUnmeasurable, 0
}
return energyToLoudness(sum / float64(count)), count
}
// 3s windows, 100ms step, 10th/95th percentile.
func loudnessRange(subBlockEnergies []float64) (float64, int) {
n := len(subBlockEnergies)
if n < shortTermBlocks {
return 0.0, 0
}
numWindows := n - shortTermBlocks + 1
windowEnergies := make([]float64, numWindows)
for i := range numWindows {
var sum float64
for j := range shortTermBlocks {
sum += subBlockEnergies[i+j]
}
windowEnergies[i] = sum / float64(shortTermBlocks)
}
var sum float64
var count int
for _, e := range windowEnergies {
if e >= absThreshold {
sum += e
count++
}
}
if count == 0 {
return 0.0, 0
}
// relative gate: mean - 20 LU
meanEnergy := sum / float64(count)
relThreshold := meanEnergy * math.Pow(10, -20.0/10.0)
gated := make([]float64, 0, count)
for _, e := range windowEnergies {
if e >= absThreshold && e >= relThreshold {
gated = append(gated, energyToLoudness(e))
}
}
if len(gated) < 2 {
return 0.0, len(gated)
}
sort.Float64s(gated)
low := percentile(gated, 0.10)
high := percentile(gated, 0.95)
return high - low, len(gated)
}
// ceil indexing per EBU TECH 3342
func percentile(sorted []float64, p float64) float64 {
n := len(sorted)
idx := int(math.Ceil(float64(n)*p)) - 1
idx = max(idx, 0)
if idx >= n {
idx = n - 1
}
return sorted[idx]
}