audioparam-method-chaining.html (5347B)
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <title>AudioParam Method Chaining</title> 5 <script src="/resources/testharness.js"></script> 6 <script src="/resources/testharnessreport.js"></script> 7 <script src="/webaudio/resources/audit-util.js"></script> 8 <script src="/webaudio/resources/audioparam-testing.js"></script> 9 </head> 10 <body> 11 <script> 12 const sampleRate = 8000; 13 14 // Create a dummy array for setValueCurveAtTime method. 15 const curveArray = new Float32Array([5.0, 6.0]); 16 17 // Method and argument combinations to test method chaining 18 const methodDictionary = [ 19 {name: 'setValueAtTime', args: [1.0, 0.0]}, 20 {name: 'linearRampToValueAtTime', args: [2.0, 1.0]}, 21 {name: 'exponentialRampToValueAtTime', args: [3.0, 2.0]}, 22 {name: 'setTargetAtTime', args: [4.0, 2.0, 0.5]}, 23 {name: 'setValueCurveAtTime', args: [curveArray, 5.0, 1.0]}, 24 {name: 'cancelScheduledValues', args: [6.0]} 25 ]; 26 27 test(() => { 28 const context = new AudioContext(); 29 methodDictionary.forEach(({name, args}) => { 30 const sourceParam = context.createGain().gain; 31 const returnParam = sourceParam[name](...args); 32 assert_equals( 33 returnParam, sourceParam, 34 `AudioParam.${name}() should return same AudioParam for chaining` 35 ); 36 }); 37 }, 'AudioParam: Each method returns the same object to allow chaining'); 38 39 // Task: test method chaining with invalid operation. 40 promise_test(async () => { 41 const context = new OfflineAudioContext(1, sampleRate, sampleRate); 42 43 const osc = context.createOscillator(); 44 const amp1 = context.createGain(); 45 const amp2 = context.createGain(); 46 47 osc.connect(amp1); 48 osc.connect(amp2); 49 amp1.connect(context.destination); 50 amp2.connect(context.destination); 51 52 // The first operation fails with an exception, thus the second one 53 // should not have effect on the parameter value. Instead, it should 54 // maintain the default value of 1.0. 55 assert_throws_js( 56 RangeError, 57 () => amp1.gain.setValueAtTime(0.25, -1.0) 58 .linearRampToValueAtTime(2.0, 1.0), 59 'Chained call should throw if setValueAtTime() called ' + 60 'with a negative end time' 61 ); 62 63 // The first operation succeeds but the second fails due to zero target 64 // value for the exponential ramp. Thus only the first should have 65 // effect on the parameter value, setting the value to 0.5. 66 assert_throws_js( 67 RangeError, 68 () => amp2.gain.setValueAtTime(0.5, 0.0) 69 .exponentialRampToValueAtTime(0.0, 1.0), 70 'Chained call should throw if exponentialRampToValueAtTime() ' + 71 'called with a zero target value' 72 ); 73 74 osc.start(); 75 osc.stop(1.0); 76 77 const renderedBuffer = await context.startRendering(); 78 79 assert_equals( 80 amp1.gain.value, 1.0, 81 'amp1.gain.value should remain default 1 because setValueAtTime threw' 82 ); 83 84 assert_equals( 85 amp2.gain.value, 0.5, 86 'amp2.gain.value should be set by setValueAtTime ' + 87 'since exponentialRampToValueAtTime threw' 88 ); 89 }, 'AudioParam: Chaining with invalid operations does ' + 90 'not apply later effects'); 91 92 // Task: verify if the method chaining actually works. Create an arbitrary 93 // envelope and compare the result with the expected one created by JS 94 // code. 95 promise_test(async () => { 96 const context = new OfflineAudioContext(1, sampleRate * 4, sampleRate); 97 const constantBuffer = createConstantBuffer(context, 1, 1.0); 98 99 const source = context.createBufferSource(); 100 source.buffer = constantBuffer; 101 source.loop = true; 102 103 const envelope = context.createGain(); 104 source.connect(envelope); 105 envelope.connect(context.destination); 106 107 // Apply a series of scheduled events via method chaining. 108 envelope.gain.setValueAtTime(0.0, 0.0) 109 .linearRampToValueAtTime(1.0, 1.0) 110 .exponentialRampToValueAtTime(0.5, 2.0) 111 .setTargetAtTime(0.001, 2.0, 0.5); 112 113 source.start(); 114 115 const rendered = await context.startRendering(); 116 117 // Create expected envelope using helper functions 118 const expectedEnvelope = [ 119 ...createLinearRampArray(0.0, 1.0, 0.0, 1.0, sampleRate), 120 ...createExponentialRampArray(1.0, 2.0, 1.0, 0.5, sampleRate), 121 ...createExponentialApproachArray(2.0, 4.0, 0.5, 0.001, 122 sampleRate, 0.5) 123 ]; 124 125 // There are slight differences between JS implementation of 126 // AudioParam envelope and the internal implementation. (i.e. 127 // double/float and rounding up) The error threshold is adjusted 128 // empirically through the local testing. 129 assert_array_approx_equals( 130 rendered.getChannelData(0), 131 expectedEnvelope, 132 4.0532e-6, 133 'Rendered gain envelope should match expected ' + 134 'values from scheduled events' 135 ); 136 }, 'AudioParam: Chaining of envelope methods schedules ' + 137 'values as expected'); 138 </script> 139 </body> 140 </html>