| <!doctype html> |
| <html> |
| <head> |
| <title>Test AudioParam events very close in time</title> |
| <script src="/resources/testharness.js"></script> |
| <script src="/resources/testharnessreport.js"></script> |
| <script src="/webaudio/resources/audit-util.js"></script> |
| <script src="/webaudio/resources/audit.js"></script> |
| </head> |
| |
| <body> |
| <script> |
| const audit = Audit.createTaskRunner(); |
| |
| // Largest sample rate that is required to be supported and is a power of |
| // two, to eliminate round-off as much as possible. |
| const sampleRate = 65536; |
| |
| // Only need one render quantum for testing. |
| const testFrames = 128; |
| |
| // Largest representable single-float number |
| const floatMax = Math.fround(3.4028234663852886e38); |
| |
| // epspos is the smallest x such that 1 + x != 1 |
| const epspos = 1.1102230246251568e-16; |
| // epsneg is the smallest x such that 1 - x != 1 |
| const epsneg = 5.551115123125784e-17; |
| |
| audit.define( |
| {label: 'no-nan', description: 'NaN does not occur'}, |
| (task, should) => { |
| const context = new OfflineAudioContext({ |
| numberOfChannels: 1, |
| sampleRate: sampleRate, |
| length: testFrames |
| }); |
| |
| const src0 = new ConstantSourceNode(context, {offset: 0}); |
| |
| // This should always succeed. We just want to print out a message |
| // that |src0| is a constant source node for the following |
| // processing. |
| should(src0, 'src0 = new ConstantSourceNode(context, {offset: 0})') |
| .beEqualTo(src0); |
| |
| src0.connect(context.destination); |
| |
| // Values for the first event (setValue). |time1| MUST be 0. |
| const time1 = 0; |
| const value1 = 10; |
| |
| // Values for the second event (linearRamp). |value2| must be huge, |
| // and |time2| must be small enough that 1/|time2| overflows a |
| // single float. This value is the least positive single float. |
| const value2 = floatMax; |
| const time2 = 1.401298464324817e-45; |
| |
| // These should always succeed; the messages are just informational |
| // to show the events that we scheduled. |
| should( |
| src0.offset.setValueAtTime(value1, time1), |
| `src0.offset.setValueAtTime(${value1}, ${time1})`) |
| .beEqualTo(src0.offset); |
| should( |
| src0.offset.linearRampToValueAtTime(value2, time2), |
| `src0.offset.linearRampToValueAtTime(${value2}, ${time2})`) |
| .beEqualTo(src0.offset); |
| |
| src0.start(); |
| |
| context.startRendering() |
| .then(buffer => { |
| const output = buffer.getChannelData(0); |
| |
| // Since time1 = 0, the output at frame 0 MUST be value1. |
| should(output[0], 'output[0]').beEqualTo(value1); |
| |
| // Since time2 < 1, output from frame 1 and later must be a |
| // constant. |
| should(output.slice(1), 'output[1]') |
| .beConstantValueOf(value2); |
| }) |
| .then(() => task.done()); |
| }); |
| |
| audit.define( |
| {label: 'interpolation', description: 'Interpolation of linear ramp'}, |
| (task, should) => { |
| const context = new OfflineAudioContext({ |
| numberOfChannels: 1, |
| sampleRate: sampleRate, |
| length: testFrames |
| }); |
| |
| const src1 = new ConstantSourceNode(context, {offset: 0}); |
| |
| // This should always succeed. We just want to print out a message |
| // that |src1| is a constant source node for the following |
| // processing. |
| should(src1, 'src1 = new ConstantSourceNode(context, {offset: 0})') |
| .beEqualTo(src1); |
| |
| src1.connect(context.destination); |
| |
| const frame = 1; |
| |
| // These time values are arranged so that time1 < frame/sampleRate < |
| // time2. This means we need to interpolate to get a value at given |
| // frame. |
| // |
| // The values are not so important, but |value2| should be huge. |
| const time1 = frame * (1 - epsneg) / context.sampleRate; |
| const value1 = 1e15; |
| |
| const time2 = frame * (1 + epspos) / context.sampleRate; |
| const value2 = floatMax; |
| |
| should( |
| src1.offset.setValueAtTime(value1, time1), |
| `src1.offset.setValueAtTime(${value1}, ${time1})`) |
| .beEqualTo(src1.offset); |
| should( |
| src1.offset.linearRampToValueAtTime(value2, time2), |
| `src1.offset.linearRampToValueAtTime(${value2}, ${time2})`) |
| .beEqualTo(src1.offset); |
| |
| src1.start(); |
| |
| context.startRendering() |
| .then(buffer => { |
| const output = buffer.getChannelData(0); |
| |
| // Sanity check |
| should(time2 - time1, 'Event time difference') |
| .notBeEqualTo(0); |
| |
| // Because 0 < time1 < 1, output must be 0 at time 0. |
| should(output[0], 'output[0]').beEqualTo(0); |
| |
| // Because time1 < 1/sampleRate < time2, we need to |
| // interpolate the value between these times to determine the |
| // output at frame 1. |
| const t = frame / context.sampleRate; |
| const v = value1 + |
| (value2 - value1) * (t - time1) / (time2 - time1); |
| |
| should(output[1], 'output[1]').beCloseTo(v, {threshold: 0}); |
| |
| // Because 1 < time2 < 2, the output at frame 2 and higher is |
| // constant. |
| should(output.slice(2), 'output[2:]') |
| .beConstantValueOf(value2); |
| }) |
| .then(() => task.done()); |
| }); |
| |
| audit.run(); |
| </script> |
| </body> |
| </html> |