|
1 |
| -<!doctype html> |
| 1 | +<!DOCTYPE html> |
2 | 2 | <html>
|
3 | 3 | <head>
|
4 | 4 | <title>Test AudioParam events very close in time</title>
|
5 | 5 | <script src="/resources/testharness.js"></script>
|
6 | 6 | <script src="/resources/testharnessreport.js"></script>
|
7 | 7 | <script src="/webaudio/resources/audit-util.js"></script>
|
8 |
| - <script src="/webaudio/resources/audit.js"></script> |
9 | 8 | </head>
|
10 |
| - |
11 | 9 | <body>
|
12 | 10 | <script>
|
13 |
| - const audit = Audit.createTaskRunner(); |
14 |
| - |
15 | 11 | // Largest sample rate that is required to be supported and is a power of
|
16 | 12 | // two, to eliminate round-off as much as possible.
|
17 | 13 | const sampleRate = 65536;
|
|
27 | 23 | // epsneg is the smallest x such that 1 - x != 1
|
28 | 24 | const epsneg = 5.551115123125784e-17;
|
29 | 25 |
|
30 |
| - audit.define( |
31 |
| - {label: 'no-nan', description: 'NaN does not occur'}, |
32 |
| - (task, should) => { |
33 |
| - const context = new OfflineAudioContext({ |
34 |
| - numberOfChannels: 1, |
35 |
| - sampleRate: sampleRate, |
36 |
| - length: testFrames |
37 |
| - }); |
38 |
| - |
39 |
| - const src0 = new ConstantSourceNode(context, {offset: 0}); |
40 |
| - |
41 |
| - // This should always succeed. We just want to print out a message |
42 |
| - // that |src0| is a constant source node for the following |
43 |
| - // processing. |
44 |
| - should(src0, 'src0 = new ConstantSourceNode(context, {offset: 0})') |
45 |
| - .beEqualTo(src0); |
46 |
| - |
47 |
| - src0.connect(context.destination); |
48 |
| - |
49 |
| - // Values for the first event (setValue). |time1| MUST be 0. |
50 |
| - const time1 = 0; |
51 |
| - const value1 = 10; |
52 |
| - |
53 |
| - // Values for the second event (linearRamp). |value2| must be huge, |
54 |
| - // and |time2| must be small enough that 1/|time2| overflows a |
55 |
| - // single float. This value is the least positive single float. |
56 |
| - const value2 = floatMax; |
57 |
| - const time2 = 1.401298464324817e-45; |
58 |
| - |
59 |
| - // These should always succeed; the messages are just informational |
60 |
| - // to show the events that we scheduled. |
61 |
| - should( |
62 |
| - src0.offset.setValueAtTime(value1, time1), |
63 |
| - `src0.offset.setValueAtTime(${value1}, ${time1})`) |
64 |
| - .beEqualTo(src0.offset); |
65 |
| - should( |
66 |
| - src0.offset.linearRampToValueAtTime(value2, time2), |
67 |
| - `src0.offset.linearRampToValueAtTime(${value2}, ${time2})`) |
68 |
| - .beEqualTo(src0.offset); |
69 |
| - |
70 |
| - src0.start(); |
71 |
| - |
72 |
| - context.startRendering() |
73 |
| - .then(buffer => { |
74 |
| - const output = buffer.getChannelData(0); |
75 |
| - |
76 |
| - // Since time1 = 0, the output at frame 0 MUST be value1. |
77 |
| - should(output[0], 'output[0]').beEqualTo(value1); |
78 |
| - |
79 |
| - // Since time2 < 1, output from frame 1 and later must be a |
80 |
| - // constant. |
81 |
| - should(output.slice(1), 'output[1]') |
82 |
| - .beConstantValueOf(value2); |
83 |
| - }) |
84 |
| - .then(() => task.done()); |
85 |
| - }); |
86 |
| - |
87 |
| - audit.define( |
88 |
| - {label: 'interpolation', description: 'Interpolation of linear ramp'}, |
89 |
| - (task, should) => { |
90 |
| - const context = new OfflineAudioContext({ |
91 |
| - numberOfChannels: 1, |
92 |
| - sampleRate: sampleRate, |
93 |
| - length: testFrames |
94 |
| - }); |
95 |
| - |
96 |
| - const src1 = new ConstantSourceNode(context, {offset: 0}); |
97 |
| - |
98 |
| - // This should always succeed. We just want to print out a message |
99 |
| - // that |src1| is a constant source node for the following |
100 |
| - // processing. |
101 |
| - should(src1, 'src1 = new ConstantSourceNode(context, {offset: 0})') |
102 |
| - .beEqualTo(src1); |
103 |
| - |
104 |
| - src1.connect(context.destination); |
105 |
| - |
106 |
| - const frame = 1; |
107 |
| - |
108 |
| - // These time values are arranged so that time1 < frame/sampleRate < |
109 |
| - // time2. This means we need to interpolate to get a value at given |
110 |
| - // frame. |
111 |
| - // |
112 |
| - // The values are not so important, but |value2| should be huge. |
113 |
| - const time1 = frame * (1 - epsneg) / context.sampleRate; |
114 |
| - const value1 = 1e15; |
115 |
| - |
116 |
| - const time2 = frame * (1 + epspos) / context.sampleRate; |
117 |
| - const value2 = floatMax; |
118 |
| - |
119 |
| - should( |
120 |
| - src1.offset.setValueAtTime(value1, time1), |
121 |
| - `src1.offset.setValueAtTime(${value1}, ${time1})`) |
122 |
| - .beEqualTo(src1.offset); |
123 |
| - should( |
124 |
| - src1.offset.linearRampToValueAtTime(value2, time2), |
125 |
| - `src1.offset.linearRampToValueAtTime(${value2}, ${time2})`) |
126 |
| - .beEqualTo(src1.offset); |
127 |
| - |
128 |
| - src1.start(); |
129 |
| - |
130 |
| - context.startRendering() |
131 |
| - .then(buffer => { |
132 |
| - const output = buffer.getChannelData(0); |
133 |
| - |
134 |
| - // Sanity check |
135 |
| - should(time2 - time1, 'Event time difference') |
136 |
| - .notBeEqualTo(0); |
137 |
| - |
138 |
| - // Because 0 < time1 < 1, output must be 0 at time 0. |
139 |
| - should(output[0], 'output[0]').beEqualTo(0); |
140 |
| - |
141 |
| - // Because time1 < 1/sampleRate < time2, we need to |
142 |
| - // interpolate the value between these times to determine the |
143 |
| - // output at frame 1. |
144 |
| - const t = frame / context.sampleRate; |
145 |
| - const v = value1 + |
146 |
| - (value2 - value1) * (t - time1) / (time2 - time1); |
147 |
| - |
148 |
| - should(output[1], 'output[1]').beCloseTo(v, {threshold: 0}); |
149 |
| - |
150 |
| - // Because 1 < time2 < 2, the output at frame 2 and higher is |
151 |
| - // constant. |
152 |
| - should(output.slice(2), 'output[2:]') |
153 |
| - .beConstantValueOf(value2); |
154 |
| - }) |
155 |
| - .then(() => task.done()); |
156 |
| - }); |
157 |
| - |
158 |
| - audit.run(); |
| 26 | + promise_test(async t => { |
| 27 | + const context = new OfflineAudioContext({ |
| 28 | + numberOfChannels: 1, |
| 29 | + sampleRate, |
| 30 | + length: testFrames |
| 31 | + }); |
| 32 | + |
| 33 | + const src0 = new ConstantSourceNode(context, {offset: 0}); |
| 34 | + |
| 35 | + // This should always succeed. We just want to print out a message |
| 36 | + // that |src0| is a constant source node for the following |
| 37 | + // processing. |
| 38 | + assert_equals( |
| 39 | + src0, src0, |
| 40 | + 'src0 = new ConstantSourceNode(context, {offset: 0}) ' + |
| 41 | + 'should succeed'); |
| 42 | + |
| 43 | + src0.connect(context.destination); |
| 44 | + |
| 45 | + // Values for the first event (setValue). |time1| MUST be 0. |
| 46 | + const time1 = 0; |
| 47 | + const value1 = 10; |
| 48 | + |
| 49 | + // Values for the second event (linearRamp). |value2| must be huge, |
| 50 | + // and |time2| must be small enough that 1/|time2| overflows a |
| 51 | + // single float. This value is the least positive single float. |
| 52 | + const time2 = 1.401298464324817e-45; |
| 53 | + const value2 = floatMax; |
| 54 | + |
| 55 | + // These should always succeed; the messages are just informational |
| 56 | + // to show the events that we scheduled. |
| 57 | + assert_equals( |
| 58 | + src0.offset.setValueAtTime(value1, time1), |
| 59 | + src0.offset, |
| 60 | + `src0.offset.setValueAtTime(${value1}, ${time1}) should ` + |
| 61 | + `return AudioParam`); |
| 62 | + assert_equals( |
| 63 | + src0.offset.linearRampToValueAtTime(value2, time2), |
| 64 | + src0.offset, |
| 65 | + `src0.offset.linearRampToValueAtTime(${value2}, ${time2}) should ` + |
| 66 | + `return AudioParam`); |
| 67 | + |
| 68 | + src0.start(); |
| 69 | + |
| 70 | + const renderedBuffer = await context.startRendering(); |
| 71 | + const output = renderedBuffer.getChannelData(0); |
| 72 | + |
| 73 | + // Since time1 = 0, the output at frame 0 MUST be value1. |
| 74 | + assert_equals( |
| 75 | + output[0], value1, 'Frame 0 should equal initial value set'); |
| 76 | + |
| 77 | + // Since time2 < 1, output from frame 1 and later must be a |
| 78 | + // constant. |
| 79 | + assert_array_constant_value( |
| 80 | + output.slice(1), |
| 81 | + value2, |
| 82 | + 'Frames 1+ should equal ramp target value'); |
| 83 | + }, 'NaN should not occur during extreme linearRampToValueAtTime events'); |
| 84 | + |
| 85 | + promise_test(async t => { |
| 86 | + const context = new OfflineAudioContext({ |
| 87 | + numberOfChannels: 1, |
| 88 | + sampleRate, |
| 89 | + length: testFrames |
| 90 | + }); |
| 91 | + |
| 92 | + const src1 = new ConstantSourceNode(context, {offset: 0}); |
| 93 | + |
| 94 | + // This should always succeed. We just want to print out a message |
| 95 | + // that |src1| is a constant source node for the following |
| 96 | + // processing. |
| 97 | + assert_equals( |
| 98 | + src1, src1, |
| 99 | + 'src1 = new ConstantSourceNode(context, {offset: 0}) ' + |
| 100 | + 'should succeed'); |
| 101 | + |
| 102 | + src1.connect(context.destination); |
| 103 | + |
| 104 | + const frameIndex = 1; |
| 105 | + |
| 106 | + // These time values are arranged so that time1 < frame/sampleRate < |
| 107 | + // time2. This means we need to interpolate to get a value at given |
| 108 | + // frame. |
| 109 | + // |
| 110 | + // The values are not so important, but |value2| should be huge. |
| 111 | + const time1 = frameIndex * (1 - epsneg) / context.sampleRate; |
| 112 | + const value1 = 1e15; |
| 113 | + |
| 114 | + const time2 = frameIndex * (1 + epspos) / context.sampleRate; |
| 115 | + const value2 = floatMax; |
| 116 | + |
| 117 | + assert_equals( |
| 118 | + src1.offset.setValueAtTime(value1, time1), |
| 119 | + src1.offset, |
| 120 | + `src1.offset.setValueAtTime(${value1}, ${time1}) should ` + |
| 121 | + `return AudioParam`); |
| 122 | + assert_equals( |
| 123 | + src1.offset.linearRampToValueAtTime(value2, time2), |
| 124 | + src1.offset, |
| 125 | + `src1.offset.linearRampToValueAtTime(${value2}, ${time2}) should ` + |
| 126 | + `return AudioParam`); |
| 127 | + |
| 128 | + src1.start(); |
| 129 | + |
| 130 | + const renderedBuffer = await context.startRendering(); |
| 131 | + const output = renderedBuffer.getChannelData(0); |
| 132 | + |
| 133 | + // Sanity check |
| 134 | + assert_not_equals( |
| 135 | + time2 - time1, 0, |
| 136 | + 'Sanity check: time1 and time2 should not be equal'); |
| 137 | + // Because 0 < time1 < 1, output must be 0 at time 0. |
| 138 | + assert_equals( |
| 139 | + output[0], 0, 'output[0] should be 0 before any automation'); |
| 140 | + |
| 141 | + // Because time1 < 1/sampleRate < time2, we need to |
| 142 | + // interpolate the value between these times to determine the |
| 143 | + // output at frame 1. |
| 144 | + const sampleTime = frameIndex / context.sampleRate; |
| 145 | + const interpolated = |
| 146 | + value1 + (value2 - value1) * (sampleTime - time1) / (time2 - time1); |
| 147 | + assert_approx_equals( |
| 148 | + output[1], |
| 149 | + interpolated, 0, |
| 150 | + 'Interpolated value at frame 1 should match ramp'); |
| 151 | + |
| 152 | + // Because 1 < time2 < 2, the output at frame 2 and higher is |
| 153 | + // constant. |
| 154 | + assert_array_constant_value( |
| 155 | + output.slice(2), value2, 'output[2:] should be constant at value2'); |
| 156 | + }, 'Interpolation of linear ramp between very close time values'); |
159 | 157 | </script>
|
160 | 158 | </body>
|
161 | 159 | </html>
|
0 commit comments