@@ -158,36 +158,14 @@ class AudioParam extends DspObject {
158158 throw DOMErr ( 'setValueCurveAtTime overlaps an existing event' , 'NotSupportedError' )
159159 }
160160 }
161+ // Store as regular Array to ensure float64 interpolation during truncation
162+ // (Float32Array.slice preserves type, causing float32 precision loss)
163+ if ( ArrayBuffer . isView ( values ) ) values = Array . from ( values )
161164 this . #automationEventList. add ( createSetValueCurveAutomationEvent ( values , startTime , duration ) )
162165 this . #paramVersion++
163166 return this
164167 }
165168
166- // Wraps AutomationEventList.getValue to fix exponentialRamp with opposite-sign
167- // or zero start values. The spec says: if v0 and v1 have opposite signs or v0
168- // is zero, v(t) = v0 for T0 <= t < T1. The library returns 0 in this case.
169- #getValue( time ) {
170- let val = this . #automationEventList. getValue ( time )
171- if ( val === 0 ) {
172- let events = this . #automationEventList. _automationEvents
173- for ( let i = 0 ; i < events . length ; i ++ ) {
174- let e = events [ i ]
175- if ( e . type === 'exponentialRampToValue' && time < e . endTime ) {
176- // Find the previous event's end value (the ramp's start value)
177- let prev = events [ i - 1 ]
178- let v0 = prev === undefined ? this . #defaultValue
179- : prev . type === 'setValueCurve' ? prev . values [ prev . values . length - 1 ]
180- : prev . value
181- // Spec: opposite signs or v0 == 0 → hold v0
182- if ( ( v0 > 0 && e . value < 0 ) || ( v0 < 0 && e . value > 0 ) || v0 === 0 )
183- return v0
184- break
185- }
186- }
187- }
188- return val
189- }
190-
191169 _tick ( ) {
192170 super . _tick ( )
193171 this . _dsp ( this . _outBuf )
@@ -213,25 +191,25 @@ class AudioParam extends DspObject {
213191 let inputBuf = this . _input . _tick ( )
214192 let ch0 = inputBuf . getChannelData ( 0 )
215193 for ( let i = 0 ; i < BLOCK_SIZE ; i ++ )
216- array [ i ] = this . #getValue( ( f0 + i ) / sr ) + ch0 [ i ]
194+ array [ i ] = this . #automationEventList . getValue ( ( f0 + i ) / sr ) + ch0 [ i ]
217195 // NaN can enter from input — flush to default
218196 let def = this . #defaultValue
219197 for ( let i = 0 ; i < BLOCK_SIZE ; i ++ ) if ( isNaN ( array [ i ] ) ) array [ i ] = def
220198 } else {
221- let v0 = this . #getValue( f0 / sr )
222- let v1 = this . #getValue( ( f0 + BLOCK_SIZE - 1 ) / sr )
199+ let v0 = this . #automationEventList . getValue ( f0 / sr )
200+ let v1 = this . #automationEventList . getValue ( ( f0 + BLOCK_SIZE - 1 ) / sr )
223201 if ( v0 === v1 ) {
224202 array . fill ( v0 )
225203 this . #cachedVersion = this . #paramVersion
226204 this . #cachedValue = v0
227205 } else {
228206 this . #cachedVersion = - 1
229207 for ( let i = 0 ; i < BLOCK_SIZE ; i ++ )
230- array [ i ] = this . #getValue( ( f0 + i ) / sr )
208+ array [ i ] = this . #automationEventList . getValue ( ( f0 + i ) / sr )
231209 }
232210 }
233211 } else {
234- let val = this . #getValue( f0 / sr )
212+ let val = this . #automationEventList . getValue ( f0 / sr )
235213 if ( hasInput ) {
236214 let inputBuf = this . _input . _tick ( )
237215 val += inputBuf . getChannelData ( 0 ) [ 0 ]
@@ -256,54 +234,13 @@ class AudioParam extends DspObject {
256234 cancelAndHoldAtTime ( cancelTime ) {
257235 _assertTime ( cancelTime )
258236
259- // Snapshot setValueCurve events that span cancelTime so we can fix
260- // truncation precision after the library processes the cancelAndHold.
261- let events = this . #automationEventList. _automationEvents
262- let origCurve = null
263- for ( let e of events ) {
264- if ( e . type === 'setValueCurve' &&
265- e . startTime < cancelTime &&
266- e . startTime + e . duration > cancelTime ) {
267- origCurve = {
268- values : Array . from ( e . values ) , // copy as float64
269- startTime : e . startTime ,
270- duration : e . duration
271- }
272- break
273- }
274- }
275-
276237 this . #automationEventList. add ( createCancelAndHoldAutomationEvent ( cancelTime ) )
277238 this . #paramVersion++
278239
279- events = this . #automationEventList. _automationEvents
280-
281- // Fix truncated setValueCurve precision: the library resamples curve values
282- // through Float32Array which loses precision. Recompute in float64.
283- if ( origCurve ) {
284- let last = events [ events . length - 1 ]
285- if ( last && last . type === 'setValueCurve' &&
286- last . startTime === origCurve . startTime ) {
287- let newDuration = cancelTime - origCurve . startTime
288- let ratio = ( origCurve . values . length - 1 ) / origCurve . duration
289- let length = Math . max ( 2 , 1 + Math . ceil ( newDuration * ratio ) )
290- let fraction = ( newDuration / ( length - 1 ) ) * ratio
291- let values = origCurve . values . slice ( 0 , length )
292- if ( fraction < 1 ) {
293- for ( let i = 1 ; i < length ; i ++ ) {
294- let factor = ( fraction * i ) % 1
295- values [ i ] = origCurve . values [ i - 1 ] * ( 1 - factor ) + origCurve . values [ i ] * factor
296- }
297- }
298- last . values = values
299- last . duration = newDuration
300- }
301- }
302-
303- // The library stores held values (truncated ramp endpoints, setValue for
304- // setTarget) in float64. But the Web Audio spec outputs through Float32Array,
305- // so subsequent automations should start from the float32-rounded held value.
306- // Apply Math.fround to the last event's value to match spec precision.
240+ // The library stores held values in float64, but Web Audio spec outputs
241+ // through Float32Array — subsequent automations must start from the
242+ // float32-rounded held value. Apply Math.fround to match spec precision.
243+ let events = this . #automationEventList. _automationEvents
307244 let last = events [ events . length - 1 ]
308245 if ( last ) {
309246 if ( ( last . type === 'linearRampToValue' || last . type === 'exponentialRampToValue' )
0 commit comments