1
1
import IcecastMetadataStats from 'icecast-metadata-stats'
2
2
3
+ export enum ReplayGainMode {
4
+ None ,
5
+ Track ,
6
+ Album ,
7
+ _Length
8
+ }
9
+
10
+ type ReplayGain = {
11
+ trackGain : number // dB
12
+ trackPeak : number // 0.0-1.0
13
+ albumGain : number // dB
14
+ albumPeak : number // 0.0-1.0
15
+ }
16
+
3
17
export class AudioController {
4
18
private audio = new Audio ( )
5
19
private handle = - 1
6
20
private volume = 1.0
7
21
private fadeDuration = 200
8
22
private buffer = new Audio ( )
9
23
private statsListener : any = null
24
+ private replayGainMode = ReplayGainMode . None
25
+ private replayGain : ReplayGain | null = null
26
+ private preAmp = 0.0
10
27
11
28
ontimeupdate : ( value : number ) => void = ( ) => { /* do nothing */ }
12
29
ondurationchange : ( value : number ) => void = ( ) => { /* do nothing */ }
@@ -30,7 +47,12 @@ export class AudioController {
30
47
setVolume ( value : number ) {
31
48
this . cancelFade ( )
32
49
this . volume = value
33
- this . audio . volume = value
50
+ this . audio . volume = value * this . replayGainFactor ( )
51
+ }
52
+
53
+ setReplayGainMode ( value : ReplayGainMode ) {
54
+ this . replayGainMode = value
55
+ this . setVolume ( this . volume )
34
56
}
35
57
36
58
setPlaybackRate ( value : number ) {
@@ -55,7 +77,9 @@ export class AudioController {
55
77
await this . fadeIn ( this . fadeDuration / 2.0 )
56
78
}
57
79
58
- async changeTrack ( options : { url ?: string , paused ?: boolean , isStream ?: boolean , playbackRate ?: number } ) {
80
+ async changeTrack ( options : { url ?: string , paused ?: boolean , replayGain ?: ReplayGain , isStream ?: boolean , playbackRate ?: number } ) {
81
+ this . replayGain = options . replayGain || null
82
+
59
83
if ( this . audio ) {
60
84
this . cancelFade ( )
61
85
endPlayback ( this . audio , this . fadeDuration )
@@ -128,6 +152,11 @@ export class AudioController {
128
152
private fadeFromTo ( from : number , to : number , duration : number ) {
129
153
console . info ( `AudioController: start fade (${ from } , ${ to } , ${ duration } )` )
130
154
const startTime = Date . now ( )
155
+
156
+ const replayGainFactor = this . replayGainFactor ( )
157
+ from *= replayGainFactor
158
+ to *= replayGainFactor
159
+
131
160
const step = ( to - from ) / duration
132
161
if ( duration <= 0.0 ) {
133
162
this . audio . volume = to
@@ -150,6 +179,39 @@ export class AudioController {
150
179
run ( )
151
180
} )
152
181
}
182
+
183
+ private replayGainFactor ( ) : number {
184
+ if ( this . replayGainMode === ReplayGainMode . None ) {
185
+ return 1.0
186
+ }
187
+ if ( ! this . replayGain ) {
188
+ console . warn ( 'AudioController: no ReplayGain information' )
189
+ return 1.0
190
+ }
191
+
192
+ const gain = this . replayGainMode === ReplayGainMode . Track
193
+ ? this . replayGain . trackGain
194
+ : this . replayGain . albumGain
195
+
196
+ const peak = this . replayGainMode === ReplayGainMode . Track
197
+ ? this . replayGain . trackPeak
198
+ : this . replayGain . albumPeak
199
+
200
+ if ( ! Number . isFinite ( gain ) || ! Number . isFinite ( peak ) || peak <= 0 ) {
201
+ console . warn ( 'AudioController: invalid ReplayGain settings' , this . replayGain )
202
+ return 1.0
203
+ }
204
+
205
+ // Implementing min(10^((RG + Gpre-amp)/20), 1/peakamplitude)
206
+ // https://wiki.hydrogenaud.io/index.php?title=ReplayGain_2.0_specification
207
+ const gainFactor = Math . pow ( 10 , ( gain + this . preAmp ) / 20 )
208
+ const peakFactor = 1 / peak
209
+ const factor = Math . min ( gainFactor , peakFactor )
210
+
211
+ console . info ( 'AudioController: calculated ReplayGain factor' , factor )
212
+
213
+ return factor
214
+ }
153
215
}
154
216
155
217
function endPlayback ( audio : HTMLAudioElement , duration : number ) {
0 commit comments