@@ -9,29 +9,48 @@ import { Source } from './source';
99
1010import type { ClipType } from '../clips' ;
1111import type { ArgumentTypes } from '../types' ;
12+ import type { FastSamplerOptions } from './audio.types' ;
1213
1314export class AudioSource extends Source {
15+ private decoding = false ;
16+
1417 public readonly type : ClipType = 'audio' ;
1518 public audioBuffer ?: AudioBuffer ;
1619
1720 public async decode (
1821 numberOfChannels : number = 2 ,
1922 sampleRate : number = 48000 ,
23+ cache = false ,
2024 ) : 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 ;
2135 const buffer = await this . arrayBuffer ( ) ;
2236
2337 const ctx = new OfflineAudioContext ( numberOfChannels , 1 , sampleRate ) ;
2438
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 ;
2742
43+ this . decoding = false ;
2844 this . trigger ( 'update' , undefined ) ;
2945
30- return this . audioBuffer ;
46+ return audioBuffer ;
3147 }
3248
49+ /**
50+ * @deprecated Use fastsampler instead.
51+ */
3352 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 ) ) ;
3554
3655 const window = Math . round ( buffer . sampleRate / windowSize ) ;
3756 const length = buffer . sampleRate * buffer . duration - window ;
@@ -50,6 +69,43 @@ export class AudioSource extends Source {
5069 return res . map ( ( v ) => Math . round ( ( v / Math . max ( ...res ) ) * ( 100 - min ) ) + min ) ;
5170 }
5271
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+
53109 public async thumbnail ( ...args : ArgumentTypes < this[ 'samples' ] > ) : Promise < HTMLElement > {
54110 const samples = await this . samples ( ...args ) ;
55111
0 commit comments