@@ -9,29 +9,48 @@ import { Source } from './source';
9
9
10
10
import type { ClipType } from '../clips' ;
11
11
import type { ArgumentTypes } from '../types' ;
12
+ import type { FastSamplerOptions } from './audio.types' ;
12
13
13
14
export class AudioSource extends Source {
15
+ private decoding = false ;
16
+
14
17
public readonly type : ClipType = 'audio' ;
15
18
public audioBuffer ?: AudioBuffer ;
16
19
17
20
public async decode (
18
21
numberOfChannels : number = 2 ,
19
22
sampleRate : number = 48000 ,
23
+ cache = false ,
20
24
) : Promise < AudioBuffer > {
25
+ // make sure audio is not decoded multiple times
26
+ if ( this . decoding && cache ) {
27
+ await new Promise ( this . resolve ( 'update' ) ) ;
28
+
29
+ if ( this . audioBuffer ) {
30
+ return this . audioBuffer ;
31
+ }
32
+ }
33
+
34
+ this . decoding = true ;
21
35
const buffer = await this . arrayBuffer ( ) ;
22
36
23
37
const ctx = new OfflineAudioContext ( numberOfChannels , 1 , sampleRate ) ;
24
38
25
- this . audioBuffer = await ctx . decodeAudioData ( buffer ) ;
26
- this . duration . seconds = this . audioBuffer . duration ;
39
+ const audioBuffer = await ctx . decodeAudioData ( buffer ) ;
40
+ this . duration . seconds = audioBuffer . duration ;
41
+ if ( cache ) this . audioBuffer = audioBuffer ;
27
42
43
+ this . decoding = false ;
28
44
this . trigger ( 'update' , undefined ) ;
29
45
30
- return this . audioBuffer ;
46
+ return audioBuffer ;
31
47
}
32
48
49
+ /**
50
+ * @deprecated Use fastsampler instead.
51
+ */
33
52
public async samples ( numberOfSampes = 60 , windowSize = 50 , min = 0 ) : Promise < number [ ] > {
34
- const buffer = this . audioBuffer ?? ( await this . decode ( 1 , 16e3 ) ) ;
53
+ const buffer = this . audioBuffer ?? ( await this . decode ( 1 , 3000 , true ) ) ;
35
54
36
55
const window = Math . round ( buffer . sampleRate / windowSize ) ;
37
56
const length = buffer . sampleRate * buffer . duration - window ;
@@ -50,6 +69,43 @@ export class AudioSource extends Source {
50
69
return res . map ( ( v ) => Math . round ( ( v / Math . max ( ...res ) ) * ( 100 - min ) ) + min ) ;
51
70
}
52
71
72
+ /**
73
+ * Fast sampler that uses a window size to calculate the max value of the samples in the window.
74
+ * @param options - Sampling options.
75
+ * @returns An array of the max values of the samples in the window.
76
+ */
77
+ public async fastsampler ( { length = 60 , start = 0 , stop, logarithmic = false } : FastSamplerOptions ) : Promise < Float32Array > {
78
+ if ( typeof start === 'object' ) start = start . millis ;
79
+ if ( typeof stop === 'object' ) stop = stop . millis ;
80
+
81
+ const sampleRate = 3000 ;
82
+ const audioBuffer = this . audioBuffer ?? ( await this . decode ( 1 , sampleRate , true ) ) ;
83
+ const channelData = audioBuffer . getChannelData ( 0 ) ;
84
+
85
+ const firstSample = Math . floor ( Math . max ( start * sampleRate / 1000 , 0 ) ) ;
86
+ const lastSample = stop
87
+ ? Math . floor ( Math . min ( stop * sampleRate / 1000 , audioBuffer . length ) )
88
+ : audioBuffer . length ;
89
+
90
+ const windowSize = Math . floor ( ( lastSample - firstSample ) / length ) ;
91
+ const result = new Float32Array ( length ) ;
92
+
93
+ for ( let i = 0 ; i < length ; i ++ ) {
94
+ const start = firstSample + i * windowSize ;
95
+ const end = start + windowSize ;
96
+ let min = Infinity ;
97
+ let max = - Infinity ;
98
+
99
+ for ( let j = start ; j < end ; j ++ ) {
100
+ const sample = channelData [ j ] ;
101
+ if ( sample < min ) min = sample ;
102
+ if ( sample > max ) max = sample ;
103
+ }
104
+ result [ i ] = logarithmic ? Math . log2 ( 1 + max ) : max ;
105
+ }
106
+ return result ;
107
+ }
108
+
53
109
public async thumbnail ( ...args : ArgumentTypes < this[ 'samples' ] > ) : Promise < HTMLElement > {
54
110
const samples = await this . samples ( ...args ) ;
55
111
0 commit comments