-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathvolume.js
137 lines (108 loc) · 3.7 KB
/
volume.js
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
127
128
129
130
131
132
133
134
135
136
137
import { DEFAULT } from "./defaults.js"
class Volume {
constructor() {
this._FMaxOut = 20000
this._FNoiseIn = 1
this._FNoiseOut = 2000
this._CalcBeta()
this._FAttackSamples = Math.round(DEFAULT.RATE * 0.005)
this._FHoldSamples = this._FAttackSamples
this._MakeAttackShape()
}
set NoiseInDb(Value = 76) {
this._FNoiseIn = Math.pow(10, 0.05 * Value)
this._CalcBeta()
}
set NoiseOutDb(Value) {
this._FNoiseOut = Math.min(0.25 * this._FMaxOut, Math.pow(10, 0.05 * Value))
this._CalcBeta()
}
set MaxOut(Value) {
this._FMaxOut = Value;
this._CalcBeta()
}
set AttackSamples(Value) {
this._FAttackSamples = Math.max(1, Value)
this._MakeAttackShape()
}
set HoldSamples(Value) {
this._FHoldSamples = Math.max(1, Value)
this._MakeAttackShape()
}
set AgcEnabled(Value) {
if (Value && !this._FAgcEnabled) this._Reset()
this._FAgcEnabled = Value
}
_CalcBeta() {
this._FBeta = this._FNoiseIn / Math.log(this._FMaxOut / (this._FMaxOut - this._FNoiseOut))
this._FDefaultGain = this._FNoiseOut / this._FNoiseIn
}
_MakeAttackShape() {
this._FLen = 2 * (this._FAttackSamples + this._FHoldSamples) + 1
this._FAttackShape = new Float32Array(this._FLen)
// attack shape
for (let i = 0; i < this._FAttackSamples; i++) {
this._FAttackShape[i] = Math.log(0.5 - 0.5 * Math.cos((i + 1) * Math.PI / (this._FAttackSamples + 1)))
this._FAttackShape[this._FLen - 1 - i] = this._FAttackShape[i]
}
this._Reset()
}
_Reset() {
this._FRealBuf = new Float32Array(this._FLen)
// TODO: Check if / how we can/should clear out ComplexBuffer?
/*
ClearReIm(FComplexBuf);
SetLengthReIm(FComplexBuf, FLen);
*/
this._FMagBuf = new Float32Array(this._FLen);
this._FBufIdx = 0
}
CalcAgcGain() {
// look at both sides of the sample
// and find the max. magnitude, weighed by FAttackShape
let Envelope = 1E-10
let Di = this._FBufIdx
for (let Wi = 0; Wi < this._FLen; Wi++) {
let Sample = this._FMagBuf[Di] + this._FAttackShape[Wi]
if (Sample > Envelope) Envelope = Sample
Di++
if (Di === this._FLen) Di = 0
}
// envelope
this._FEnvelope = Envelope;
Envelope = Math.exp(Envelope)
// gain
return this._FMaxOut * (1 - Math.exp(-Envelope / this._FBeta)) / Envelope
}
_ApplyAgc(V) {
// store data
this._FRealBuf[this._FBufIdx] = V
this._FMagBuf[this._FBufIdx] = Math.log(Math.abs(V + 1E-10))
// increment index
this._FBufIdx = (this._FBufIdx + 1) % this._FLen
// output
let result = this._FRealBuf[(this._FBufIdx + Math.floor(this._FLen / 2)) % this._FLen] * this.CalcAgcGain()
return result
}
_ApplyDefaultGain(V) {
let result = Math.min(this._FMaxOut, Math.max(-this._FMaxOut, V * this._FDefaultGain))
this._FIsOverload = this._FIsOverload || (Math.abs(result) === this._FMaxOut)
return result
}
Process(Data) {
// Whats this?
this._FIsOverload = false
let result = new Array(Data.length)
if (this._FAgcEnabled) {
for (let i = 0; i < result.length; i++) {
result[i] = this._ApplyAgc(Data[i])
}
} else {
for (let i = 0; i < result.length; i++) {
result[i] = this._ApplyDefaultGain(Data[i])
}
}
return result
}
}
export { Volume }