| OLD | NEW |
| 1 <!doctype html> | 1 <!DOCTYPE html> |
| 2 <html> | 2 <html> |
| 3 <head> | 3 <head> |
| 4 <title>Biquad Automation Test</title> | 4 <title> |
| 5 Biquad Automation Test |
| 6 </title> |
| 5 <script src="../../resources/testharness.js"></script> | 7 <script src="../../resources/testharness.js"></script> |
| 6 <script src="../../resources/testharnessreport.js"></script> | 8 <script src="../../resources/testharnessreport.js"></script> |
| 7 <script src="../resources/audit-util.js"></script> | 9 <script src="../resources/audit-util.js"></script> |
| 8 <script src="../resources/audit.js"></script> | 10 <script src="../resources/audit.js"></script> |
| 9 <script src="../resources/biquad-filters.js"></script> | 11 <script src="../resources/biquad-filters.js"></script> |
| 10 <script src="../resources/audioparam-testing.js"></script> | 12 <script src="../resources/audioparam-testing.js"></script> |
| 11 </head> | 13 </head> |
| 12 <body> | 14 <body> |
| 13 <script> | 15 <script id="layout-test-code"> |
| 14 | 16 // Don't need to run these tests at high sampling rate, so just use a low |
| 15 | 17 // one to reduce memory usage and complexity. |
| 16 // Don't need to run these tests at high sampling rate, so just use a low
one to reduce memory | 18 let sampleRate = 16000; |
| 17 // usage and complexity. | |
| 18 var sampleRate = 16000; | |
| 19 | 19 |
| 20 // How long to render for each test. | 20 // How long to render for each test. |
| 21 var renderDuration = 0.25; | 21 let renderDuration = 0.25; |
| 22 // Where to end the automations. Fairly arbitrary, but must end before | 22 // Where to end the automations. Fairly arbitrary, but must end before |
| 23 // the renderDuration. | 23 // the renderDuration. |
| 24 var automationEndTime = renderDuration / 2; | 24 let automationEndTime = renderDuration / 2; |
| 25 | 25 |
| 26 var audit = Audit.createTaskRunner(); | 26 let audit = Audit.createTaskRunner(); |
| 27 | 27 |
| 28 // The definition of the linear ramp automation function. | 28 // The definition of the linear ramp automation function. |
| 29 function linearRamp(t, v0, v1, t0, t1) { | 29 function linearRamp(t, v0, v1, t0, t1) { |
| 30 return v0 + (v1 - v0) * (t - t0) / (t1 - t0); | 30 return v0 + (v1 - v0) * (t - t0) / (t1 - t0); |
| 31 } | 31 } |
| 32 | 32 |
| 33 // Generate the filter coefficients for the specified filter using the giv
en parameters for | 33 // Generate the filter coefficients for the specified filter using the |
| 34 // the given duration. |filterTypeFunction| is a function that returns th
e filter | 34 // given parameters for the given duration. |filterTypeFunction| is a |
| 35 // coefficients for one set of parameters. |parameters| is a property bag
that contains the | 35 // function that returns the filter coefficients for one set of |
| 36 // start and end values (as an array) for each of the biquad attributes.
The properties are | 36 // parameters. |parameters| is a property bag that contains the start and |
| 37 // |freq|, |Q|, |gain|, and |detune|. |duration| is the number of seconds
for which the | 37 // end values (as an array) for each of the biquad attributes. The |
| 38 // coefficients are generated. | 38 // properties are |freq|, |Q|, |gain|, and |detune|. |duration| is the |
| 39 // number of seconds for which the coefficients are generated. |
| 39 // | 40 // |
| 40 // A property bag with properties |b0|, |b1|, |b2|, |a1|, |a2|. Each prop
ery is an array | 41 // A property bag with properties |b0|, |b1|, |b2|, |a1|, |a2|. Each |
| 41 // consisting of the coefficients for the time-varying biquad filter. | 42 // propery is an array consisting of the coefficients for the time-varying |
| 42 function generateFilterCoefficients(filterTypeFunction, parameters, durati
on) { | 43 // biquad filter. |
| 43 var renderEndFrame = Math.ceil(renderDuration * sampleRate); | 44 function generateFilterCoefficients( |
| 44 var endFrame = Math.ceil(duration * sampleRate); | 45 filterTypeFunction, parameters, duration) { |
| 45 var nCoef = renderEndFrame; | 46 let renderEndFrame = Math.ceil(renderDuration * sampleRate); |
| 46 var b0 = new Float64Array(nCoef); | 47 let endFrame = Math.ceil(duration * sampleRate); |
| 47 var b1 = new Float64Array(nCoef); | 48 let nCoef = renderEndFrame; |
| 48 var b2 = new Float64Array(nCoef); | 49 let b0 = new Float64Array(nCoef); |
| 49 var a1 = new Float64Array(nCoef); | 50 let b1 = new Float64Array(nCoef); |
| 50 var a2 = new Float64Array(nCoef); | 51 let b2 = new Float64Array(nCoef); |
| 52 let a1 = new Float64Array(nCoef); |
| 53 let a2 = new Float64Array(nCoef); |
| 51 | 54 |
| 52 var k = 0; | 55 let k = 0; |
| 53 // If the property is not given, use the defaults. | 56 // If the property is not given, use the defaults. |
| 54 var freqs = parameters.freq || [350, 350]; | 57 let freqs = parameters.freq || [350, 350]; |
| 55 var qs = parameters.Q || [1, 1]; | 58 let qs = parameters.Q || [1, 1]; |
| 56 var gains = parameters.gain || [0, 0]; | 59 let gains = parameters.gain || [0, 0]; |
| 57 var detunes = parameters.detune || [0, 0]; | 60 let detunes = parameters.detune || [0, 0]; |
| 58 | 61 |
| 59 for (var frame = 0; frame <= endFrame; ++frame) { | 62 for (let frame = 0; frame <= endFrame; ++frame) { |
| 60 // Apply linear ramp at frame |frame|. | 63 // Apply linear ramp at frame |frame|. |
| 61 var f = linearRamp(frame / sampleRate, freqs[0], freqs[1], 0, durati
on); | 64 let f = |
| 62 var q = linearRamp(frame / sampleRate, qs[0], qs[1], 0, duration); | 65 linearRamp(frame / sampleRate, freqs[0], freqs[1], 0, duration); |
| 63 var g = linearRamp(frame / sampleRate, gains[0], gains[1], 0, durati
on); | 66 let q = linearRamp(frame / sampleRate, qs[0], qs[1], 0, duration); |
| 64 var d = linearRamp(frame / sampleRate, detunes[0], detunes[1], 0, du
ration); | 67 let g = |
| 68 linearRamp(frame / sampleRate, gains[0], gains[1], 0, duration); |
| 69 let d = linearRamp( |
| 70 frame / sampleRate, detunes[0], detunes[1], 0, duration); |
| 65 | 71 |
| 66 // Compute actual frequency parameter | 72 // Compute actual frequency parameter |
| 67 f = f * Math.pow(2, d / 1200); | 73 f = f * Math.pow(2, d / 1200); |
| 68 | 74 |
| 69 // Compute filter coefficients | 75 // Compute filter coefficients |
| 70 var coef = filterTypeFunction(f / (sampleRate / 2), q, g); | 76 let coef = filterTypeFunction(f / (sampleRate / 2), q, g); |
| 71 b0[k] = coef.b0; | 77 b0[k] = coef.b0; |
| 72 b1[k] = coef.b1; | 78 b1[k] = coef.b1; |
| 73 b2[k] = coef.b2; | 79 b2[k] = coef.b2; |
| 74 a1[k] = coef.a1; | 80 a1[k] = coef.a1; |
| 75 a2[k] = coef.a2; | 81 a2[k] = coef.a2; |
| 76 ++k; | 82 ++k; |
| 77 } | 83 } |
| 78 | 84 |
| 79 // Fill the rest of the arrays with the constant value to the end of | 85 // Fill the rest of the arrays with the constant value to the end of |
| 80 // the rendering duration. | 86 // the rendering duration. |
| 81 b0.fill(b0[endFrame], endFrame + 1); | 87 b0.fill(b0[endFrame], endFrame + 1); |
| 82 b1.fill(b1[endFrame], endFrame + 1); | 88 b1.fill(b1[endFrame], endFrame + 1); |
| 83 b2.fill(b2[endFrame], endFrame + 1); | 89 b2.fill(b2[endFrame], endFrame + 1); |
| 84 a1.fill(a1[endFrame], endFrame + 1); | 90 a1.fill(a1[endFrame], endFrame + 1); |
| 85 a2.fill(a2[endFrame], endFrame + 1); | 91 a2.fill(a2[endFrame], endFrame + 1); |
| 86 | 92 |
| 87 return {b0: b0, b1: b1, b2: b2, a1: a1, a2: a2}; | 93 return {b0: b0, b1: b1, b2: b2, a1: a1, a2: a2}; |
| 88 } | 94 } |
| 89 | 95 |
| 90 // Apply the given time-varying biquad filter to the given signal, |signal
|. |coef| should be | 96 // Apply the given time-varying biquad filter to the given signal, |
| 91 // the time-varying coefficients of the filter, as returned by |generateFi
lterCoefficients|. | 97 // |signal|. |coef| should be the time-varying coefficients of the |
| 98 // filter, as returned by |generateFilterCoefficients|. |
| 92 function timeVaryingFilter(signal, coef) { | 99 function timeVaryingFilter(signal, coef) { |
| 93 var length = signal.length; | 100 let length = signal.length; |
| 94 // Use double precision for the internal computations. | 101 // Use double precision for the internal computations. |
| 95 var y = new Float64Array(length); | 102 let y = new Float64Array(length); |
| 96 | 103 |
| 97 // Prime the pump. (Assumes the signal has length >= 2!) | 104 // Prime the pump. (Assumes the signal has length >= 2!) |
| 98 y[0] = coef.b0[0] * signal[0]; | 105 y[0] = coef.b0[0] * signal[0]; |
| 99 y[1] = coef.b0[1] * signal[1] + coef.b1[1] * signal[0] - coef.a1[1] * y[
0]; | 106 y[1] = |
| 107 coef.b0[1] * signal[1] + coef.b1[1] * signal[0] - coef.a1[1] * y[0]; |
| 100 | 108 |
| 101 for (var n = 2; n < length; ++n) { | 109 for (let n = 2; n < length; ++n) { |
| 102 y[n] = coef.b0[n] * signal[n] + coef.b1[n] * signal[n-1] + coef.b2[n]
* signal[n-2]; | 110 y[n] = coef.b0[n] * signal[n] + coef.b1[n] * signal[n - 1] + |
| 103 y[n] -= coef.a1[n] * y[n-1] + coef.a2[n] * y[n-2]; | 111 coef.b2[n] * signal[n - 2]; |
| 112 y[n] -= coef.a1[n] * y[n - 1] + coef.a2[n] * y[n - 2]; |
| 104 } | 113 } |
| 105 | 114 |
| 106 // But convert the result to single precision for comparison. | 115 // But convert the result to single precision for comparison. |
| 107 return y.map(Math.fround); | 116 return y.map(Math.fround); |
| 108 } | 117 } |
| 109 | 118 |
| 110 // Configure the audio graph using |context|. Returns the biquad filter n
ode and the | 119 // Configure the audio graph using |context|. Returns the biquad filter |
| 111 // AudioBuffer used for the source. | 120 // node and the AudioBuffer used for the source. |
| 112 function configureGraph(context, toneFrequency) { | 121 function configureGraph(context, toneFrequency) { |
| 113 // The source is just a simple sine wave. | 122 // The source is just a simple sine wave. |
| 114 var src = context.createBufferSource(); | 123 let src = context.createBufferSource(); |
| 115 var b = context.createBuffer(1, renderDuration * sampleRate, sampleRate)
; | 124 let b = |
| 116 var data = b.getChannelData(0); | 125 context.createBuffer(1, renderDuration * sampleRate, sampleRate); |
| 117 var omega = 2 * Math.PI * toneFrequency / sampleRate; | 126 let data = b.getChannelData(0); |
| 118 for (var k = 0; k < data.length; ++k) { | 127 let omega = 2 * Math.PI * toneFrequency / sampleRate; |
| 128 for (let k = 0; k < data.length; ++k) { |
| 119 data[k] = Math.sin(omega * k); | 129 data[k] = Math.sin(omega * k); |
| 120 } | 130 } |
| 121 src.buffer = b; | 131 src.buffer = b; |
| 122 var f = context.createBiquadFilter(); | 132 let f = context.createBiquadFilter(); |
| 123 src.connect(f); | 133 src.connect(f); |
| 124 f.connect(context.destination); | 134 f.connect(context.destination); |
| 125 | 135 |
| 126 src.start(); | 136 src.start(); |
| 127 | 137 |
| 128 return {filter: f, source: b}; | 138 return {filter: f, source: b}; |
| 129 } | 139 } |
| 130 | 140 |
| 131 function createFilterVerifier(should, filterCreator, threshold, parameters
, input, message) { | 141 function createFilterVerifier( |
| 132 return function (resultBuffer) { | 142 should, filterCreator, threshold, parameters, input, message) { |
| 133 var actual = resultBuffer.getChannelData(0); | 143 return function(resultBuffer) { |
| 134 var coefs = generateFilterCoefficients(filterCreator, parameters, auto
mationEndTime); | 144 let actual = resultBuffer.getChannelData(0); |
| 145 let coefs = generateFilterCoefficients( |
| 146 filterCreator, parameters, automationEndTime); |
| 135 | 147 |
| 136 reference = timeVaryingFilter(input, coefs); | 148 reference = timeVaryingFilter(input, coefs); |
| 137 | 149 |
| 138 should(actual, message).beCloseToArray(reference, { | 150 should(actual, message).beCloseToArray(reference, { |
| 139 absoluteThreshold: threshold | 151 absoluteThreshold: threshold |
| 140 }); | 152 }); |
| 141 }; | 153 }; |
| 142 } | 154 } |
| 143 | 155 |
| 144 // Automate just the frequency parameter. A bandpass filter is used where
the center | 156 // Automate just the frequency parameter. A bandpass filter is used where |
| 145 // frequency is swept across the source (which is a simple tone). | 157 // the center frequency is swept across the source (which is a simple |
| 146 audit.define("automate-freq", (task, should) => { | 158 // tone). |
| 147 var context = new OfflineAudioContext(1, renderDuration * sampleRate, sa
mpleRate); | 159 audit.define('automate-freq', (task, should) => { |
| 160 let context = |
| 161 new OfflineAudioContext(1, renderDuration * sampleRate, sampleRate); |
| 148 | 162 |
| 149 // Center frequency of bandpass filter and also the frequency of the tes
t tone. | 163 // Center frequency of bandpass filter and also the frequency of the |
| 150 var centerFreq = 10*440; | 164 // test tone. |
| 165 let centerFreq = 10 * 440; |
| 151 | 166 |
| 152 // Sweep the frequency +/- 5*440 Hz from the center. This should cause | 167 // Sweep the frequency +/- 5*440 Hz from the center. This should cause |
| 153 // the output to be low at the beginning and end of the test where the | 168 // the output to be low at the beginning and end of the test where the |
| 154 // tone is outside the pass band of the filter, but high in the middle | 169 // tone is outside the pass band of the filter, but high in the middle |
| 155 // of the automation time where the tone is near the center of the pass | 170 // of the automation time where the tone is near the center of the pass |
| 156 // band. Make sure the frequency sweep stays inside the Nyquist | 171 // band. Make sure the frequency sweep stays inside the Nyquist |
| 157 // frequency. | 172 // frequency. |
| 158 var parameters = { | 173 let parameters = {freq: [centerFreq - 5 * 440, centerFreq + 5 * 440]}; |
| 159 freq: [centerFreq - 5*440, centerFreq + 5*440] | 174 let graph = configureGraph(context, centerFreq); |
| 160 }; | 175 let f = graph.filter; |
| 161 var graph = configureGraph(context, centerFreq); | 176 let b = graph.source; |
| 162 var f = graph.filter; | 177 |
| 163 var b = graph.source; | 178 f.type = 'bandpass'; |
| 164 | |
| 165 f.type = "bandpass"; | |
| 166 f.frequency.setValueAtTime(parameters.freq[0], 0); | 179 f.frequency.setValueAtTime(parameters.freq[0], 0); |
| 167 f.frequency.linearRampToValueAtTime(parameters.freq[1], automationEndTim
e); | 180 f.frequency.linearRampToValueAtTime( |
| 168 | 181 parameters.freq[1], automationEndTime); |
| 169 context.startRendering() | 182 |
| 170 .then(createFilterVerifier(should, createBandpassFilter, 4.6455e-6, | 183 context.startRendering() |
| 171 parameters, b.getChannelData(0), | 184 .then(createFilterVerifier( |
| 172 "Output of bandpass filter with frequency automation")) | 185 should, createBandpassFilter, 4.6455e-6, parameters, |
| 173 .then(() => task.done()); | 186 b.getChannelData(0), |
| 174 }); | 187 'Output of bandpass filter with frequency automation')) |
| 175 | 188 .then(() => task.done()); |
| 176 // Automate just the Q parameter. A bandpass filter is used where the Q o
f the filter is | 189 }); |
| 177 // swept. | 190 |
| 178 audit.define("automate-q", (task, should) => { | 191 // Automate just the Q parameter. A bandpass filter is used where the Q |
| 179 var context = new OfflineAudioContext(1, renderDuration * sampleRate, sa
mpleRate); | 192 // of the filter is swept. |
| 193 audit.define('automate-q', (task, should) => { |
| 194 let context = |
| 195 new OfflineAudioContext(1, renderDuration * sampleRate, sampleRate); |
| 180 | 196 |
| 181 // The frequency of the test tone. | 197 // The frequency of the test tone. |
| 182 var centerFreq = 440; | 198 let centerFreq = 440; |
| 183 | 199 |
| 184 // Sweep the Q paramter between 1 and 200. This will cause the output o
f the filter to pass | 200 // Sweep the Q paramter between 1 and 200. This will cause the output |
| 185 // most of the tone at the beginning to passing less of the tone at the
end. This is | 201 // of the filter to pass most of the tone at the beginning to passing |
| 186 // because we set center frequency of the bandpass filter to be slightly
off from the actual | 202 // less of the tone at the end. This is because we set center frequency |
| 187 // tone. | 203 // of the bandpass filter to be slightly off from the actual tone. |
| 188 var parameters = { | 204 let parameters = { |
| 189 Q: [1, 200], | 205 Q: [1, 200], |
| 190 // Center frequency of the bandpass filter is just 25 Hz above the ton
e frequency. | 206 // Center frequency of the bandpass filter is just 25 Hz above the |
| 207 // tone frequency. |
| 191 freq: [centerFreq + 25, centerFreq + 25] | 208 freq: [centerFreq + 25, centerFreq + 25] |
| 192 }; | 209 }; |
| 193 var graph = configureGraph(context, centerFreq); | 210 let graph = configureGraph(context, centerFreq); |
| 194 var f = graph.filter; | 211 let f = graph.filter; |
| 195 var b = graph.source; | 212 let b = graph.source; |
| 196 | 213 |
| 197 f.type = "bandpass"; | 214 f.type = 'bandpass'; |
| 198 f.frequency.value = parameters.freq[0]; | 215 f.frequency.value = parameters.freq[0]; |
| 199 f.Q.setValueAtTime(parameters.Q[0], 0); | 216 f.Q.setValueAtTime(parameters.Q[0], 0); |
| 200 f.Q.linearRampToValueAtTime(parameters.Q[1], automationEndTime); | 217 f.Q.linearRampToValueAtTime(parameters.Q[1], automationEndTime); |
| 201 | 218 |
| 202 context.startRendering() | 219 context.startRendering() |
| 203 .then(createFilterVerifier(should, createBandpassFilter, 9.8348e-7, | 220 .then(createFilterVerifier( |
| 204 parameters, b.getChannelData(0), | 221 should, createBandpassFilter, 9.8348e-7, parameters, |
| 205 "Output of bandpass filter with Q automation")) | 222 b.getChannelData(0), |
| 206 .then(() => task.done()); | 223 'Output of bandpass filter with Q automation')) |
| 207 }); | 224 .then(() => task.done()); |
| 208 | 225 }); |
| 209 // Automate just the gain of the lowshelf filter. A test tone will be in
the lowshelf part of | 226 |
| 210 // the filter. The output will vary as the gain of the lowshelf is change
d. | 227 // Automate just the gain of the lowshelf filter. A test tone will be in |
| 211 audit.define("automate-gain", (task, should) => { | 228 // the lowshelf part of the filter. The output will vary as the gain of |
| 212 var context = new OfflineAudioContext(1, renderDuration * sampleRate, sa
mpleRate); | 229 // the lowshelf is changed. |
| 230 audit.define('automate-gain', (task, should) => { |
| 231 let context = |
| 232 new OfflineAudioContext(1, renderDuration * sampleRate, sampleRate); |
| 213 | 233 |
| 214 // Frequency of the test tone. | 234 // Frequency of the test tone. |
| 215 var centerFreq = 440; | 235 let centerFreq = 440; |
| 216 | 236 |
| 217 // Set the cutoff frequency of the lowshelf to be significantly higher t
han the test tone. | 237 // Set the cutoff frequency of the lowshelf to be significantly higher |
| 218 // Sweep the gain from 20 dB to -20 dB. (We go from 20 to -20 to easily
verify that the | 238 // than the test tone. Sweep the gain from 20 dB to -20 dB. (We go from |
| 219 // filter didn't go unstable.) | 239 // 20 to -20 to easily verify that the filter didn't go unstable.) |
| 220 var parameters = { | 240 let parameters = {freq: [3500, 3500], gain: [20, -20]}; |
| 221 freq: [3500, 3500], | 241 let graph = configureGraph(context, centerFreq); |
| 222 gain: [20, -20] | 242 let f = graph.filter; |
| 223 }; | 243 let b = graph.source; |
| 224 var graph = configureGraph(context, centerFreq); | 244 |
| 225 var f = graph.filter; | 245 f.type = 'lowshelf'; |
| 226 var b = graph.source; | |
| 227 | |
| 228 f.type = "lowshelf"; | |
| 229 f.frequency.value = parameters.freq[0]; | 246 f.frequency.value = parameters.freq[0]; |
| 230 f.gain.setValueAtTime(parameters.gain[0], 0); | 247 f.gain.setValueAtTime(parameters.gain[0], 0); |
| 231 f.gain.linearRampToValueAtTime(parameters.gain[1], automationEndTime); | 248 f.gain.linearRampToValueAtTime(parameters.gain[1], automationEndTime); |
| 232 | 249 |
| 233 context.startRendering() | 250 context.startRendering() |
| 234 .then(createFilterVerifier(should, createLowShelfFilter, 2.7657e-5, | 251 .then(createFilterVerifier( |
| 235 parameters, b.getChannelData(0), | 252 should, createLowShelfFilter, 2.7657e-5, parameters, |
| 236 "Output of lowshelf filter with gain automation")) | 253 b.getChannelData(0), |
| 237 .then(() => task.done()); | 254 'Output of lowshelf filter with gain automation')) |
| 238 }); | 255 .then(() => task.done()); |
| 239 | 256 }); |
| 240 // Automate just the detune parameter. Basically the same test as for the
frequncy parameter | 257 |
| 241 // but we just use the detune parameter to modulate the frequency paramete
r. | 258 // Automate just the detune parameter. Basically the same test as for the |
| 242 audit.define("automate-detune", (task, should) => { | 259 // frequncy parameter but we just use the detune parameter to modulate the |
| 243 var context = new OfflineAudioContext(1, renderDuration * sampleRate, sa
mpleRate); | 260 // frequency parameter. |
| 244 var centerFreq = 10*440; | 261 audit.define('automate-detune', (task, should) => { |
| 245 var parameters = { | 262 let context = |
| 263 new OfflineAudioContext(1, renderDuration * sampleRate, sampleRate); |
| 264 let centerFreq = 10 * 440; |
| 265 let parameters = { |
| 246 freq: [centerFreq, centerFreq], | 266 freq: [centerFreq, centerFreq], |
| 247 detune: [-10*1200, 10*1200] | 267 detune: [-10 * 1200, 10 * 1200] |
| 248 }; | 268 }; |
| 249 var graph = configureGraph(context, centerFreq); | 269 let graph = configureGraph(context, centerFreq); |
| 250 var f = graph.filter; | 270 let f = graph.filter; |
| 251 var b = graph.source; | 271 let b = graph.source; |
| 252 | 272 |
| 253 f.type = "bandpass"; | 273 f.type = 'bandpass'; |
| 254 f.frequency.value = parameters.freq[0]; | 274 f.frequency.value = parameters.freq[0]; |
| 255 f.detune.setValueAtTime(parameters.detune[0], 0); | 275 f.detune.setValueAtTime(parameters.detune[0], 0); |
| 256 f.detune.linearRampToValueAtTime(parameters.detune[1], automationEndTime
); | 276 f.detune.linearRampToValueAtTime( |
| 257 | 277 parameters.detune[1], automationEndTime); |
| 258 context.startRendering() | 278 |
| 259 .then(createFilterVerifier(should, createBandpassFilter, 3.1471e-5, | 279 context.startRendering() |
| 260 parameters, b.getChannelData(0), | 280 .then(createFilterVerifier( |
| 261 "Output of bandpass filter with detune automation")) | 281 should, createBandpassFilter, 3.1471e-5, parameters, |
| 262 .then(() => task.done()); | 282 b.getChannelData(0), |
| 263 }); | 283 'Output of bandpass filter with detune automation')) |
| 264 | 284 .then(() => task.done()); |
| 265 // Automate all of the filter parameters at once. This is a basic check t
hat everything is | 285 }); |
| 266 // working. A peaking filter is used because it uses all of the parameter
s. | 286 |
| 267 audit.define("automate-all", (task, should) => { | 287 // Automate all of the filter parameters at once. This is a basic check |
| 268 var context = new OfflineAudioContext(1, renderDuration * sampleRate, sa
mpleRate); | 288 // that everything is working. A peaking filter is used because it uses |
| 269 var graph = configureGraph(context, 10*440); | 289 // all of the parameters. |
| 270 var f = graph.filter; | 290 audit.define('automate-all', (task, should) => { |
| 271 var b = graph.source; | 291 let context = |
| 292 new OfflineAudioContext(1, renderDuration * sampleRate, sampleRate); |
| 293 let graph = configureGraph(context, 10 * 440); |
| 294 let f = graph.filter; |
| 295 let b = graph.source; |
| 272 | 296 |
| 273 // Sweep all of the filter parameters. These are pretty much arbitrary. | 297 // Sweep all of the filter parameters. These are pretty much arbitrary. |
| 274 var parameters = { | 298 let parameters = { |
| 275 freq: [8000, 100], | 299 freq: [8000, 100], |
| 276 Q: [f.Q.value, .0001], | 300 Q: [f.Q.value, .0001], |
| 277 gain: [f.gain.value, 20], | 301 gain: [f.gain.value, 20], |
| 278 detune: [2400, -2400] | 302 detune: [2400, -2400] |
| 279 }; | 303 }; |
| 280 | 304 |
| 281 f.type = "peaking"; | 305 f.type = 'peaking'; |
| 282 // Set starting points for all parameters of the filter. Start at 10 kH
z for the center | 306 // Set starting points for all parameters of the filter. Start at 10 |
| 283 // frequency, and the defaults for Q and gain. | 307 // kHz for the center frequency, and the defaults for Q and gain. |
| 284 f.frequency.setValueAtTime(parameters.freq[0], 0); | 308 f.frequency.setValueAtTime(parameters.freq[0], 0); |
| 285 f.Q.setValueAtTime(parameters.Q[0], 0); | 309 f.Q.setValueAtTime(parameters.Q[0], 0); |
| 286 f.gain.setValueAtTime(parameters.gain[0], 0); | 310 f.gain.setValueAtTime(parameters.gain[0], 0); |
| 287 f.detune.setValueAtTime(parameters.detune[0], 0); | 311 f.detune.setValueAtTime(parameters.detune[0], 0); |
| 288 | 312 |
| 289 // Linear ramp each parameter | 313 // Linear ramp each parameter |
| 290 f.frequency.linearRampToValueAtTime(parameters.freq[1], automationEndTim
e); | 314 f.frequency.linearRampToValueAtTime( |
| 315 parameters.freq[1], automationEndTime); |
| 291 f.Q.linearRampToValueAtTime(parameters.Q[1], automationEndTime); | 316 f.Q.linearRampToValueAtTime(parameters.Q[1], automationEndTime); |
| 292 f.gain.linearRampToValueAtTime(parameters.gain[1], automationEndTime); | 317 f.gain.linearRampToValueAtTime(parameters.gain[1], automationEndTime); |
| 293 f.detune.linearRampToValueAtTime(parameters.detune[1], automationEndTime
); | 318 f.detune.linearRampToValueAtTime( |
| 294 | 319 parameters.detune[1], automationEndTime); |
| 295 context.startRendering() | 320 |
| 296 .then(createFilterVerifier(should, createPeakingFilter, 6.2907e-4, | 321 context.startRendering() |
| 297 parameters, b.getChannelData(0), | 322 .then(createFilterVerifier( |
| 298 "Output of peaking filter with automation of all parameters")) | 323 should, createPeakingFilter, 6.2907e-4, parameters, |
| 299 .then(() => task.done()); | 324 b.getChannelData(0), |
| 300 }); | 325 'Output of peaking filter with automation of all parameters')) |
| 301 | 326 .then(() => task.done()); |
| 302 // Test that modulation of the frequency parameter of the filter works. A
sinusoid of 440 Hz | 327 }); |
| 303 // is the test signal that is applied to a bandpass biquad filter. The fr
equency parameter of | 328 |
| 304 // the filter is modulated by a sinusoid at 103 Hz, and the frequency modu
lation varies from | 329 // Test that modulation of the frequency parameter of the filter works. A |
| 305 // 116 to 412 Hz. (This test was taken from the description in | 330 // sinusoid of 440 Hz is the test signal that is applied to a bandpass |
| 331 // biquad filter. The frequency parameter of the filter is modulated by a |
| 332 // sinusoid at 103 Hz, and the frequency modulation varies from 116 to 412 |
| 333 // Hz. (This test was taken from the description in |
| 306 // https://github.com/WebAudio/web-audio-api/issues/509#issuecomment-94731
355) | 334 // https://github.com/WebAudio/web-audio-api/issues/509#issuecomment-94731
355) |
| 307 audit.define("modulation", (task, should) => { | 335 audit.define('modulation', (task, should) => { |
| 308 var context = new OfflineAudioContext(1, renderDuration * sampleRate, sa
mpleRate); | 336 let context = |
| 309 | 337 new OfflineAudioContext(1, renderDuration * sampleRate, sampleRate); |
| 310 // Create a graph with the sinusoidal source at 440 Hz as the input to a
biquad filter. | 338 |
| 311 var graph = configureGraph(context, 440); | 339 // Create a graph with the sinusoidal source at 440 Hz as the input to a |
| 312 var f = graph.filter; | 340 // biquad filter. |
| 313 var b = graph.source; | 341 let graph = configureGraph(context, 440); |
| 314 | 342 let f = graph.filter; |
| 315 f.type = "bandpass"; | 343 let b = graph.source; |
| 344 |
| 345 f.type = 'bandpass'; |
| 316 f.Q.value = 5; | 346 f.Q.value = 5; |
| 317 f.frequency.value = 264; | 347 f.frequency.value = 264; |
| 318 | 348 |
| 319 // Create the modulation source, a sinusoid with frequency 103 Hz and am
plitude 148. (The | 349 // Create the modulation source, a sinusoid with frequency 103 Hz and |
| 320 // amplitude of 148 is added to the filter's frequency value of 264 to p
roduce a sinusoidal | 350 // amplitude 148. (The amplitude of 148 is added to the filter's |
| 321 // modulation of the frequency parameter from 116 to 412 Hz.) | 351 // frequency value of 264 to produce a sinusoidal modulation of the |
| 322 var mod = context.createBufferSource(); | 352 // frequency parameter from 116 to 412 Hz.) |
| 323 var mbuffer = context.createBuffer(1, renderDuration * sampleRate, sampl
eRate); | 353 let mod = context.createBufferSource(); |
| 324 var d = mbuffer.getChannelData(0); | 354 let mbuffer = |
| 325 var omega = 2 * Math.PI * 103 / sampleRate; | 355 context.createBuffer(1, renderDuration * sampleRate, sampleRate); |
| 326 for (var k = 0; k < d.length; ++k) { | 356 let d = mbuffer.getChannelData(0); |
| 357 let omega = 2 * Math.PI * 103 / sampleRate; |
| 358 for (let k = 0; k < d.length; ++k) { |
| 327 d[k] = 148 * Math.sin(omega * k); | 359 d[k] = 148 * Math.sin(omega * k); |
| 328 } | 360 } |
| 329 mod.buffer = mbuffer; | 361 mod.buffer = mbuffer; |
| 330 | 362 |
| 331 mod.connect(f.frequency); | 363 mod.connect(f.frequency); |
| 332 | 364 |
| 333 mod.start(); | 365 mod.start(); |
| 334 context.startRendering() | 366 context.startRendering() |
| 335 .then(function (resultBuffer) { | 367 .then(function(resultBuffer) { |
| 336 var actual = resultBuffer.getChannelData(0); | 368 let actual = resultBuffer.getChannelData(0); |
| 337 // Compute the filter coefficients using the mod sine wave | 369 // Compute the filter coefficients using the mod sine wave |
| 338 | 370 |
| 339 var endFrame = Math.ceil(renderDuration * sampleRate); | 371 let endFrame = Math.ceil(renderDuration * sampleRate); |
| 340 var nCoef = endFrame; | 372 let nCoef = endFrame; |
| 341 var b0 = new Float64Array(nCoef); | 373 let b0 = new Float64Array(nCoef); |
| 342 var b1 = new Float64Array(nCoef); | 374 let b1 = new Float64Array(nCoef); |
| 343 var b2 = new Float64Array(nCoef); | 375 let b2 = new Float64Array(nCoef); |
| 344 var a1 = new Float64Array(nCoef); | 376 let a1 = new Float64Array(nCoef); |
| 345 var a2 = new Float64Array(nCoef); | 377 let a2 = new Float64Array(nCoef); |
| 346 | 378 |
| 347 // Generate the filter coefficients when the frequency varies from
116 to 248 Hz using | 379 // Generate the filter coefficients when the frequency varies from |
| 348 // the 103 Hz sinusoid. | 380 // 116 to 248 Hz using the 103 Hz sinusoid. |
| 349 for (var k = 0; k < nCoef; ++k) { | 381 for (let k = 0; k < nCoef; ++k) { |
| 350 var freq = f.frequency.value + d[k]; | 382 let freq = f.frequency.value + d[k]; |
| 351 var c = createBandpassFilter(freq / (sampleRate / 2), f.Q.value,
f.gain.value); | 383 let c = createBandpassFilter( |
| 352 b0[k] = c.b0; | 384 freq / (sampleRate / 2), f.Q.value, f.gain.value); |
| 353 b1[k] = c.b1; | 385 b0[k] = c.b0; |
| 354 b2[k] = c.b2; | 386 b1[k] = c.b1; |
| 355 a1[k] = c.a1; | 387 b2[k] = c.b2; |
| 356 a2[k] = c.a2; | 388 a1[k] = c.a1; |
| 357 } | 389 a2[k] = c.a2; |
| 358 reference = timeVaryingFilter(b.getChannelData(0), | 390 } |
| 359 {b0: b0, b1: b1, b2: b2, a1: a1, a2: a2}); | 391 reference = timeVaryingFilter( |
| 360 | 392 b.getChannelData(0), |
| 361 should(actual, | 393 {b0: b0, b1: b1, b2: b2, a1: a1, a2: a2}); |
| 362 "Output of bandpass filter with sinusoidal modulation of bandpa
ss center frequency" | 394 |
| 363 ) | 395 should( |
| 364 .beCloseToArray(reference, { | 396 actual, |
| 365 absoluteThreshold: 3.9787e-5 | 397 'Output of bandpass filter with sinusoidal modulation of bandp
ass center frequency') |
| 366 }); | 398 .beCloseToArray(reference, {absoluteThreshold: 3.9787e-5}); |
| 367 }) | 399 }) |
| 368 .then(() => task.done()); | 400 .then(() => task.done()); |
| 369 }); | 401 }); |
| 370 | 402 |
| 371 audit.run(); | 403 audit.run(); |
| 372 </script> | 404 </script> |
| 373 </body> | 405 </body> |
| 374 </html> | 406 </html> |
| OLD | NEW |