summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/webaudio/the-audio-api/the-audionode-interface/audionode-channel-rules.html
blob: 9067e6869bcf682e4f3f945d567a2d8a300d9f4b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
<!DOCTYPE html>
<html>
  <head>
    <title>
      audionode-channel-rules.html
    </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>
    <script src="/webaudio/resources/mixing-rules.js"></script>
  </head>
  <body>
    <script id="layout-test-code">
      let audit = Audit.createTaskRunner();
      let context = 0;
      // Use a power of two to eliminate round-off converting frames to time.
      let sampleRate = 32768;
      let renderNumberOfChannels = 8;
      let singleTestFrameLength = 8;
      let testBuffers;

      // A list of connections to an AudioNode input, each of which is to be
      // used in one or more specific test cases.  Each element in the list is a
      // string, with the number of connections corresponding to the length of
      // the string, and each character in the string is from '1' to '8'
      // representing a 1 to 8 channel connection (from an AudioNode output).

      // For example, the string "128" means 3 connections, having 1, 2, and 8
      // channels respectively.

      let connectionsList = [
        '1', '2', '3', '4', '5', '6', '7', '8', '11', '12', '14', '18', '111',
        '122', '123', '124', '128'
      ];

      // A list of mixing rules, each of which will be tested against all of the
      // connections in connectionsList.
      let mixingRulesList = [
        {
          channelCount: 2,
          channelCountMode: 'max',
          channelInterpretation: 'speakers'
        },
        {
          channelCount: 4,
          channelCountMode: 'clamped-max',
          channelInterpretation: 'speakers'
        },

        // Test up-down-mix to some explicit speaker layouts.
        {
          channelCount: 1,
          channelCountMode: 'explicit',
          channelInterpretation: 'speakers'
        },
        {
          channelCount: 2,
          channelCountMode: 'explicit',
          channelInterpretation: 'speakers'
        },
        {
          channelCount: 4,
          channelCountMode: 'explicit',
          channelInterpretation: 'speakers'
        },
        {
          channelCount: 6,
          channelCountMode: 'explicit',
          channelInterpretation: 'speakers'
        },

        {
          channelCount: 2,
          channelCountMode: 'max',
          channelInterpretation: 'discrete'
        },
        {
          channelCount: 4,
          channelCountMode: 'clamped-max',
          channelInterpretation: 'discrete'
        },
        {
          channelCount: 4,
          channelCountMode: 'explicit',
          channelInterpretation: 'discrete'
        },
        {
          channelCount: 8,
          channelCountMode: 'explicit',
          channelInterpretation: 'discrete'
        },
      ];

      let numberOfTests = mixingRulesList.length * connectionsList.length;

      // Print out the information for an individual test case.
      function printTestInformation(
          testNumber, actualBuffer, expectedBuffer, frameLength, frameOffset) {
        let actual = stringifyBuffer(actualBuffer, frameLength);
        let expected =
            stringifyBuffer(expectedBuffer, frameLength, frameOffset);
        debug('TEST CASE #' + testNumber + '\n');
        debug('actual channels:\n' + actual);
        debug('expected channels:\n' + expected);
      }

      function scheduleTest(
          testNumber, connections, channelCount, channelCountMode,
          channelInterpretation) {
        let mixNode = context.createGain();
        mixNode.channelCount = channelCount;
        mixNode.channelCountMode = channelCountMode;
        mixNode.channelInterpretation = channelInterpretation;
        mixNode.connect(context.destination);

        for (let i = 0; i < connections.length; ++i) {
          let connectionNumberOfChannels =
              connections.charCodeAt(i) - '0'.charCodeAt(0);

          let source = context.createBufferSource();
          // Get a buffer with the right number of channels, converting from
          // 1-based to 0-based index.
          let buffer = testBuffers[connectionNumberOfChannels - 1];
          source.buffer = buffer;
          source.connect(mixNode);

          // Start at the right offset.
          let sampleFrameOffset = testNumber * singleTestFrameLength;
          let time = sampleFrameOffset / sampleRate;
          source.start(time);
        }
      }

      function checkTestResult(
          renderedBuffer, testNumber, connections, channelCount,
          channelCountMode, channelInterpretation, should) {
        let s = 'connections: ' + connections + ', ' + channelCountMode;

        // channelCount is ignored in "max" mode.
        if (channelCountMode == 'clamped-max' ||
            channelCountMode == 'explicit') {
          s += '(' + channelCount + ')';
        }

        s += ', ' + channelInterpretation;

        let computedNumberOfChannels = computeNumberOfChannels(
            connections, channelCount, channelCountMode);

        // Create a zero-initialized silent AudioBuffer with
        // computedNumberOfChannels.
        let destBuffer = context.createBuffer(
            computedNumberOfChannels, singleTestFrameLength,
            context.sampleRate);

        // Mix all of the connections into the destination buffer.
        for (let i = 0; i < connections.length; ++i) {
          let connectionNumberOfChannels =
              connections.charCodeAt(i) - '0'.charCodeAt(0);
          let sourceBuffer =
              testBuffers[connectionNumberOfChannels - 1];  // convert from
                                                            // 1-based to
                                                            // 0-based index

          if (channelInterpretation == 'speakers') {
            speakersSum(sourceBuffer, destBuffer);
          } else if (channelInterpretation == 'discrete') {
            discreteSum(sourceBuffer, destBuffer);
          } else {
            alert('Invalid channel interpretation!');
          }
        }

        // Use this when debugging mixing rules.
        // printTestInformation(testNumber, renderedBuffer, destBuffer,
        // singleTestFrameLength, sampleFrameOffset);

        // Validate that destBuffer matches the rendered output.  We need to
        // check the rendered output at a specific sample-frame-offset
        // corresponding to the specific test case we're checking for based on
        // testNumber.

        let sampleFrameOffset = testNumber * singleTestFrameLength;
        for (let c = 0; c < renderNumberOfChannels; ++c) {
          let renderedData = renderedBuffer.getChannelData(c);
          for (let frame = 0; frame < singleTestFrameLength; ++frame) {
            let renderedValue = renderedData[frame + sampleFrameOffset];

            let expectedValue = 0;
            if (c < destBuffer.numberOfChannels) {
              let expectedData = destBuffer.getChannelData(c);
              expectedValue = expectedData[frame];
            }

            // We may need to add an epsilon in the comparison if we add more
            // test vectors.
            if (renderedValue != expectedValue) {
              let message = s + 'rendered: ' + renderedValue +
                  ' expected: ' + expectedValue + ' channel: ' + c +
                  ' frame: ' + frame;
              // testFailed(s);
              should(renderedValue, s).beEqualTo(expectedValue);
              return;
            }
          }
        }

        should(true, s).beTrue();
      }

      function checkResult(buffer, should) {
        // Sanity check result.
        should(buffer.length, 'Rendered number of frames')
            .beEqualTo(numberOfTests * singleTestFrameLength);
        should(buffer.numberOfChannels, 'Rendered number of channels')
            .beEqualTo(renderNumberOfChannels);

        // Check all the tests.
        let testNumber = 0;
        for (let m = 0; m < mixingRulesList.length; ++m) {
          let mixingRules = mixingRulesList[m];
          for (let i = 0; i < connectionsList.length; ++i, ++testNumber) {
            checkTestResult(
                buffer, testNumber, connectionsList[i],
                mixingRules.channelCount, mixingRules.channelCountMode,
                mixingRules.channelInterpretation, should);
          }
        }
      }

      audit.define(
          {label: 'test', description: 'Channel mixing rules for AudioNodes'},
          function(task, should) {

            // Create 8-channel offline audio context.  Each test will render 8
            // sample-frames starting at sample-frame position testNumber * 8.
            let totalFrameLength = numberOfTests * singleTestFrameLength;
            context = new OfflineAudioContext(
                renderNumberOfChannels, totalFrameLength, sampleRate);

            // Set destination to discrete mixing.
            context.destination.channelCount = renderNumberOfChannels;
            context.destination.channelCountMode = 'explicit';
            context.destination.channelInterpretation = 'discrete';

            // Create test buffers from 1 to 8 channels.
            testBuffers = new Array();
            for (let i = 0; i < renderNumberOfChannels; ++i) {
              testBuffers[i] = createShiftedImpulseBuffer(
                  context, i + 1, singleTestFrameLength);
            }

            // Schedule all the tests.
            let testNumber = 0;
            for (let m = 0; m < mixingRulesList.length; ++m) {
              let mixingRules = mixingRulesList[m];
              for (let i = 0; i < connectionsList.length; ++i, ++testNumber) {
                scheduleTest(
                    testNumber, connectionsList[i], mixingRules.channelCount,
                    mixingRules.channelCountMode,
                    mixingRules.channelInterpretation);
              }
            }

            // Render then check results.
            // context.oncomplete = checkResult;
            context.startRendering().then(buffer => {
              checkResult(buffer, should);
              task.done();
            });
            ;
          });

      audit.run();
    </script>
  </body>
</html>