automation-rate-testing.js (12980B)
1 // Test k-rate vs a-rate AudioParams. 2 // 3 // |options| describes how the testing of the AudioParam should be done: 4 // 5 // sourceNodeName: name of source node to use for testing; defaults to 6 // 'OscillatorNode'. If set to 'none', then no source node 7 // is created for testing and it is assumed that the AudioNode 8 // under test are sources and need to be started. 9 // verifyPieceWiseConstant: if true, verify that the k-rate output is 10 // piecewise constant for each render quantum. 11 // nodeName: name of the AudioNode to be tested 12 // nodeOptions: options to be used in the AudioNode constructor 13 // 14 // prefix: Prefix for all output messages (to make them unique for 15 // testharness) 16 // 17 // rateSettings: A vector of dictionaries specifying how to set the automation 18 // rate(s): 19 // name: Name of the AudioParam 20 // value: The automation rate for the AudioParam given by |name|. 21 // 22 // automations: A vector of dictionaries specifying how to automate each 23 // AudioParam: 24 // name: Name of the AudioParam 25 // 26 // methods: A vector of dictionaries specifying the automation methods to 27 // be used for testing: 28 // name: Automation method to call 29 // options: Arguments for the automation method 30 // 31 // Testing is somewhat rudimentary. We create two nodes of the same type. One 32 // node uses the default automation rates for each AudioParam (expecting them to 33 // be a-rate). The second node sets the automation rate of AudioParams to 34 // "k-rate". The set is speciified by |options.rateSettings|. 35 // 36 // For both of these nodes, the same set of automation methods (given by 37 // |options.automations|) is applied. A simple oscillator is connected to each 38 // node which in turn are connected to different channels of an offline context. 39 // Channel 0 is the k-rate node output; channel 1, the a-rate output; and 40 // channel 2, the difference between the outputs. 41 // 42 // Success is declared if the difference signal is not exactly zero. This means 43 // the automations did different things, as expected. 44 // 45 // The promise from |startRendering| is returned. 46 function doTest(context, should, options) { 47 let merger = new ChannelMergerNode( 48 context, {numberOfInputs: context.destination.channelCount}); 49 merger.connect(context.destination); 50 51 let src = null; 52 53 // Skip creating a source to drive the graph if |sourceNodeName| is 'none'. 54 // If |sourceNodeName| is given, use that, else default to OscillatorNode. 55 if (options.sourceNodeName !== 'none') { 56 src = new window[options.sourceNodeName || 'OscillatorNode'](context); 57 } 58 59 let kRateNode = new window[options.nodeName](context, options.nodeOptions); 60 let aRateNode = new window[options.nodeName](context, options.nodeOptions); 61 let inverter = new GainNode(context, {gain: -1}); 62 63 // Set kRateNode filter to use k-rate params. 64 options.rateSettings.forEach(setting => { 65 kRateNode[setting.name].automationRate = setting.value; 66 // Mostly for documentation in the output. These should always 67 // pass. 68 should( 69 kRateNode[setting.name].automationRate, 70 `${options.prefix}: Setting ${ 71 setting.name 72 }.automationRate to "${setting.value}"`) 73 .beEqualTo(setting.value); 74 }); 75 76 // Run through all automations for each node separately. (Mostly to keep 77 // output of automations together.) 78 options.automations.forEach(param => { 79 param.methods.forEach(method => { 80 // Most for documentation in the output. These should never throw. 81 let message = `${param.name}.${method.name}(${method.options})` 82 should(() => { 83 kRateNode[param.name][method.name](...method.options); 84 }, options.prefix + ': k-rate node: ' + message).notThrow(); 85 }); 86 }); 87 options.automations.forEach(param => { 88 param.methods.forEach(method => { 89 // Most for documentation in the output. These should never throw. 90 let message = `${param.name}.${method.name}(${method.options})` 91 should(() => { 92 aRateNode[param.name][method.name](...method.options); 93 }, options.prefix + ': a-rate node:' + message).notThrow(); 94 }); 95 }); 96 97 // Connect the source, if specified. 98 if (src) { 99 src.connect(kRateNode); 100 src.connect(aRateNode); 101 } 102 103 // The k-rate result is channel 0, and the a-rate result is channel 1. 104 kRateNode.connect(merger, 0, 0); 105 aRateNode.connect(merger, 0, 1); 106 107 // Compute the difference between the a-rate and k-rate results and send 108 // that to channel 2. 109 kRateNode.connect(merger, 0, 2); 110 aRateNode.connect(inverter).connect(merger, 0, 2); 111 112 if (src) { 113 src.start(); 114 } else { 115 // If there's no source, then assume the test nodes are sources and start 116 // them. 117 kRateNode.start(); 118 aRateNode.start(); 119 } 120 121 return context.startRendering().then(renderedBuffer => { 122 let kRateOutput = renderedBuffer.getChannelData(0); 123 let aRateOutput = renderedBuffer.getChannelData(1); 124 let diff = renderedBuffer.getChannelData(2); 125 126 // Some informative messages to print out values of the k-rate and 127 // a-rate outputs. These should always pass. 128 should( 129 kRateOutput, `${options.prefix}: Output of k-rate ${options.nodeName}`) 130 .beEqualToArray(kRateOutput); 131 should( 132 aRateOutput, `${options.prefix}: Output of a-rate ${options.nodeName}`) 133 .beEqualToArray(aRateOutput); 134 135 // The real test. If k-rate AudioParam is working correctly, the 136 // k-rate result MUST differ from the a-rate result. 137 should( 138 diff, 139 `${ 140 options.prefix 141 }: Difference between a-rate and k-rate ${options.nodeName}`) 142 .notBeConstantValueOf(0); 143 144 if (options.verifyPieceWiseConstant) { 145 // Verify that the output from the k-rate parameter is step-wise 146 // constant. 147 for (let k = 0; k < kRateOutput.length; k += 128) { 148 should( 149 kRateOutput.slice(k, k + 128), 150 `${options.prefix} k-rate output [${k}: ${k + 127}]`) 151 .beConstantValueOf(kRateOutput[k]); 152 } 153 } 154 }); 155 } 156 157 // Test k-rate vs a-rate AudioParams. 158 // 159 // |options| describes how the testing of the AudioParam should be done: 160 // 161 // sourceNodeName: name of source node to use for testing; defaults to 162 // 'OscillatorNode'. If set to 'none', then no source node 163 // is created for testing and it is assumed that the AudioNode 164 // under test are sources and need to be started. 165 // verifyPieceWiseConstant: if true, verify that the k-rate output is 166 // piecewise constant for each render quantum. 167 // nodeName: name of the AudioNode to be tested 168 // nodeOptions: options to be used in the AudioNode constructor 169 // 170 // prefix: Prefix for all output messages (to make them unique for 171 // testharness) 172 // 173 // rateSettings: A vector of dictionaries specifying how to set the automation 174 // rate(s): 175 // name: Name of the AudioParam 176 // value: The automation rate for the AudioParam given by |name|. 177 // 178 // automations: A vector of dictionaries specifying how to automate each 179 // AudioParam: 180 // name: Name of the AudioParam 181 // 182 // methods: A vector of dictionaries specifying the automation methods to 183 // be used for testing: 184 // name: Automation method to call 185 // options: Arguments for the automation method 186 // 187 // Testing is somewhat rudimentary. We create two nodes of the same type. One 188 // node uses the default automation rates for each AudioParam (expecting them to 189 // be a-rate). The second node sets the automation rate of AudioParams to 190 // "k-rate". The set is speciified by |options.rateSettings|. 191 // 192 // For both of these nodes, the same set of automation methods (given by 193 // |options.automations|) is applied. A simple oscillator is connected to each 194 // node which in turn are connected to different channels of an offline context. 195 // Channel 0 is the k-rate node output; channel 1, the a-rate output; and 196 // channel 2, the difference between the outputs. 197 // 198 // Success is declared if the difference signal is not exactly zero. This means 199 // the automations did different things, as expected. 200 // 201 // The promise from |startRendering| is returned. 202 function doTest_W3TH(context, options) { 203 const merger = new ChannelMergerNode(context, { 204 numberOfInputs: context.destination.channelCount 205 }); 206 merger.connect(context.destination); 207 208 let src = null; 209 210 // Skip creating a source to drive the graph if |sourceNodeName| is 'none'. 211 // If |sourceNodeName| is given, use that, else default to OscillatorNode. 212 if (options.sourceNodeName !== 'none') { 213 src = new window[options.sourceNodeName || 'OscillatorNode'](context); 214 } 215 216 const kRateNode = new window[options.nodeName](context, options.nodeOptions); 217 const aRateNode = new window[options.nodeName](context, options.nodeOptions); 218 const inverter = new GainNode(context, { gain: -1 }); 219 220 // Set kRateNode filter to use k-rate params. 221 options.rateSettings.forEach(setting => { 222 kRateNode[setting.name].automationRate = setting.value; 223 // Mostly for documentation in the output. These should always 224 // pass. 225 assert_equals( 226 kRateNode[setting.name].automationRate, setting.value, 227 `${options.prefix}: Setting ${setting.name}.automationRate ` + 228 `to "${setting.value}"`); 229 }); 230 231 // Run through all automations for each node separately. (Mostly to keep 232 // output of automations together.) 233 options.automations.forEach(param => { 234 param.methods.forEach(method => { 235 // Mostly for documentation in the output. These should never throw. 236 const message = `${param.name}.${method.name}(${method.options})`; 237 try { 238 kRateNode[param.name][method.name](...method.options); 239 } catch (e) { 240 assert_unreached( 241 `${options.prefix}: k-rate node: ${message} threw: ${e.message}`); 242 } 243 }); 244 }); 245 options.automations.forEach(param => { 246 param.methods.forEach(method => { 247 // Mostly for documentation in the output. These should never throw. 248 const message = `${param.name}.${method.name}(${method.options})`; 249 try { 250 aRateNode[param.name][method.name](...method.options); 251 } catch (e) { 252 assert_unreached( 253 `${options.prefix}: a-rate node: ${message} threw: ${e.message}`); 254 } 255 }); 256 }); 257 258 // Connect the source, if specified. 259 if (src) { 260 src.connect(kRateNode); 261 src.connect(aRateNode); 262 } 263 264 // The k-rate result is channel 0, and the a-rate result is channel 1. 265 kRateNode.connect(merger, 0, 0); 266 aRateNode.connect(merger, 0, 1); 267 268 // Compute the difference between the a-rate and k-rate results and send 269 // that to channel 2. 270 kRateNode.connect(merger, 0, 2); 271 aRateNode.connect(inverter).connect(merger, 0, 2); 272 273 if (src) { 274 src.start(); 275 } else { 276 // If there's no source, then assume the test nodes are sources and start 277 // them. 278 kRateNode.start(); 279 aRateNode.start(); 280 } 281 282 return context.startRendering().then(renderedBuffer => { 283 const kRateOutput = renderedBuffer.getChannelData(0); 284 const aRateOutput = renderedBuffer.getChannelData(1); 285 const diff = renderedBuffer.getChannelData(2); 286 287 // Some informative messages to print out values of the k-rate and 288 // a-rate outputs. These should always pass. 289 // (In testharness, assertions only report on failure, 290 // so we sanity-check types.) 291 assert_true( 292 kRateOutput instanceof Float32Array, 293 `${options.prefix}: Output of k-rate `+ 294 `${options.nodeName} is Float32Array`); 295 assert_true( 296 aRateOutput instanceof Float32Array, 297 `${options.prefix}: Output of a-rate `+ 298 `${options.nodeName} is Float32Array`); 299 300 // The real test. If k-rate AudioParam is working correctly, the 301 // k-rate result MUST differ from the a-rate result. 302 let allZero = true; 303 for (let i = 0; i < diff.length; ++i) { 304 if (diff[i] !== 0) {allZero = false; break;} 305 } 306 assert_false( 307 allZero, 308 `${options.prefix}: Difference between a-rate and k-rate` + 309 `${options.nodeName} must not be identically 0`); 310 311 if (options.verifyPieceWiseConstant) { 312 // Verify that the output from the k-rate parameter is step-wise 313 // constant. 314 for (let k = 0; k < kRateOutput.length; k += 128) { 315 const end = Math.min(k + 128, kRateOutput.length); 316 const v0 = kRateOutput[k]; 317 for (let i = k + 1; i < end; ++i) { 318 assert_equals( 319 kRateOutput[i], 320 v0, 321 `${options.prefix} k-rate output [${k}: ${end - 1}]` + 322 `should be piecewise constant`, 323 ); 324 } 325 } 326 } 327 }); 328 }