@@ -22,6 +22,7 @@ export class VolumeTransformer extends Transform {
2222 lookaheadBuffer ;
2323 lookaheadIndex ;
2424 lookaheadFull ;
25+ _reusableOutputBuffer = null ;
2526 currentVolume ;
2627 targetVolume ;
2728 startVolume ;
@@ -141,9 +142,18 @@ export class VolumeTransformer extends Transform {
141142 if ( abs <= this . _thresholdValue || this . _limitHeadroom <= 0 )
142143 return value ;
143144 const normalizedOvershoot = ( abs - this . _thresholdValue ) / this . _limitHeadroom ;
144- const softened = 1 - Math . exp ( - normalizedOvershoot * this . limiterSoftness ) ;
145+ // Fast approximation of 1 - exp(-x) for small x
146+ // 1 - exp(-x) \approx x - x^2/2 + x^3/6
147+ const x = normalizedOvershoot * this . limiterSoftness ;
148+ let softened ;
149+ if ( x < 0.1 ) {
150+ softened = x * ( 1 - x * 0.5 ) ;
151+ }
152+ else {
153+ softened = 1 - Math . exp ( - x ) ;
154+ }
145155 const limited = this . _thresholdValue + this . _limitHeadroom * softened ;
146- return Math . sign ( value ) * Math . min ( INT16_MAX , limited ) ;
156+ return ( value < 0 ? - 1 : 1 ) * ( limited > INT16_MAX ? INT16_MAX : limited ) ;
147157 }
148158 _clampToInt16 ( value ) {
149159 if ( value >= INT16_MAX )
@@ -186,7 +196,10 @@ export class VolumeTransformer extends Transform {
186196 const gainStep = usableSamples > 1 ? ( gainEnd - gainStart ) / ( usableSamples - 1 ) : 0 ;
187197 let gain = gainStart ;
188198 if ( this . lookaheadSamples > 0 ) {
189- const outputBuffer = alignedBufferIfRequired ( chunk . length ) ;
199+ if ( ! this . _reusableOutputBuffer || this . _reusableOutputBuffer . length < chunk . length ) {
200+ this . _reusableOutputBuffer = alignedBufferIfRequired ( chunk . length ) ;
201+ }
202+ const outputBuffer = this . _reusableOutputBuffer . subarray ( 0 , chunk . length ) ;
190203 const outputView = new Int16Array ( outputBuffer . buffer , outputBuffer . byteOffset , usableSamples ) ;
191204 if ( useBufferOps ) {
192205 for ( let i = 0 ; i < usableSamples ; i ++ ) {
@@ -202,36 +215,59 @@ export class VolumeTransformer extends Transform {
202215 }
203216 }
204217 else if ( view ) {
205- for ( let i = 0 ; i < view . length ; i ++ ) {
218+ const len = view . length ;
219+ const lookaheadBuffer = this . lookaheadBuffer ;
220+ const lookaheadSamples = this . lookaheadSamples ;
221+ let lookaheadIndex = this . lookaheadIndex ;
222+ for ( let i = 0 ; i < len ; i ++ ) {
206223 const rawSample = view [ i ] ?? 0 ;
207224 const scaled = rawSample * gain ;
208225 const limited = this . _applyLimiter ( scaled ) ;
209- const outputSample = this . lookaheadBuffer [ this . lookaheadIndex ] ?? 0 ;
210- this . lookaheadBuffer [ this . lookaheadIndex ] = limited ;
211- this . lookaheadIndex =
212- ( this . lookaheadIndex + 1 ) % this . lookaheadSamples ;
226+ const outputSample = lookaheadBuffer [ lookaheadIndex ] ?? 0 ;
227+ lookaheadBuffer [ lookaheadIndex ] = limited ;
228+ lookaheadIndex = ( lookaheadIndex + 1 ) % lookaheadSamples ;
213229 outputView [ i ] = this . _clampToInt16 ( outputSample ) ;
214230 gain += gainStep ;
215231 }
232+ this . lookaheadIndex = lookaheadIndex ;
216233 }
217234 if ( this . lookaheadIndex === 0 )
218235 this . lookaheadFull = true ;
219236 return outputBuffer ;
220237 }
221238 if ( useBufferOps ) {
222- for ( let i = 0 ; i < usableSamples ; i ++ ) {
223- const scaled = chunk . readInt16LE ( i * 2 ) * gain ;
224- const limited = this . _applyLimiter ( scaled ) ;
225- chunk . writeInt16LE ( this . _clampToInt16 ( limited ) , i * 2 ) ;
226- gain += gainStep ;
239+ if ( gainStart === gainEnd ) {
240+ for ( let i = 0 ; i < usableSamples ; i ++ ) {
241+ const scaled = chunk . readInt16LE ( i * 2 ) * gainStart ;
242+ const limited = this . _applyLimiter ( scaled ) ;
243+ chunk . writeInt16LE ( this . _clampToInt16 ( limited ) , i * 2 ) ;
244+ }
245+ }
246+ else {
247+ for ( let i = 0 ; i < usableSamples ; i ++ ) {
248+ const scaled = chunk . readInt16LE ( i * 2 ) * gain ;
249+ const limited = this . _applyLimiter ( scaled ) ;
250+ chunk . writeInt16LE ( this . _clampToInt16 ( limited ) , i * 2 ) ;
251+ gain += gainStep ;
252+ }
227253 }
228254 }
229255 else if ( view ) {
230- for ( let i = 0 ; i < view . length ; i ++ ) {
231- const scaled = ( view [ i ] ?? 0 ) * gain ;
232- const limited = this . _applyLimiter ( scaled ) ;
233- view [ i ] = this . _clampToInt16 ( limited ) ;
234- gain += gainStep ;
256+ const len = view . length ;
257+ if ( gainStart === gainEnd ) {
258+ for ( let i = 0 ; i < len ; i ++ ) {
259+ const scaled = ( view [ i ] ?? 0 ) * gainStart ;
260+ const limited = this . _applyLimiter ( scaled ) ;
261+ view [ i ] = this . _clampToInt16 ( limited ) ;
262+ }
263+ }
264+ else {
265+ for ( let i = 0 ; i < len ; i ++ ) {
266+ const scaled = ( view [ i ] ?? 0 ) * gain ;
267+ const limited = this . _applyLimiter ( scaled ) ;
268+ view [ i ] = this . _clampToInt16 ( limited ) ;
269+ gain += gainStep ;
270+ }
235271 }
236272 }
237273 return chunk ;
0 commit comments