summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/webaudio/the-audio-api/the-audiobuffersourcenode-interface/sub-sample-buffer-stitching.html
blob: 3700bfa8ce806a57a4df6c050a9d299d9274edea (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
<!doctype html>
<html>
  <head>
    <title>
      Test Sub-Sample Accurate Stitching of ABSNs
    </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>
      let audit = Audit.createTaskRunner();

      audit.define(
          {
            label: 'buffer-stitching-1',
            description: 'Subsample buffer stitching, same rates'
          },
          (task, should) => {
            const sampleRate = 44100;
            const bufferRate = 44100;
            const bufferLength = 30;

            // Experimentally determined thresholds.  DO NOT relax these values
            // to far from these values to make the tests pass.
            const errorThreshold = 9.0957e-5;
            const snrThreshold = 85.580;

            // Informative message
            should(sampleRate, 'Test 1: context.sampleRate')
                .beEqualTo(sampleRate);
            testBufferStitching(sampleRate, bufferRate, bufferLength)
                .then(resultBuffer => {
                  const actual = resultBuffer.getChannelData(0);
                  const expected = resultBuffer.getChannelData(1);
                  should(
                      actual,
                      `Stitched sine-wave buffers at sample rate ${bufferRate}`)
                      .beCloseToArray(
                          expected, {absoluteThreshold: errorThreshold});
                  const SNR = 10 * Math.log10(computeSNR(actual, expected));
                  should(SNR, `SNR (${SNR} dB)`)
                      .beGreaterThanOrEqualTo(snrThreshold);
                })
                .then(() => task.done());
          });

      audit.define(
          {
            label: 'buffer-stitching-2',
            description: 'Subsample buffer stitching, different rates'
          },
          (task, should) => {
            const sampleRate = 44100;
            const bufferRate = 43800;
            const bufferLength = 30;

            // Experimentally determined thresholds.  DO NOT relax these values
            // to far from these values to make the tests pass.
            const errorThreshold = 3.8986e-3;
            const snrThreshold = 65.737;

            // Informative message
            should(sampleRate, 'Test 2: context.sampleRate')
                .beEqualTo(sampleRate);
            testBufferStitching(sampleRate, bufferRate, bufferLength)
                .then(resultBuffer => {
                  const actual = resultBuffer.getChannelData(0);
                  const expected = resultBuffer.getChannelData(1);
                  should(
                      actual,
                      `Stitched sine-wave buffers at sample rate ${bufferRate}`)
                      .beCloseToArray(
                          expected, {absoluteThreshold: errorThreshold});
                  const SNR = 10 * Math.log10(computeSNR(actual, expected));
                  should(SNR, `SNR (${SNR} dB)`)
                      .beGreaterThanOrEqualTo(snrThreshold);
                })
                .then(() => task.done());
          });

      audit.run();

      // Create graph to test stitching of consecutive ABSNs.  The context rate
      // is |sampleRate|, and the buffers have a fixed length of |bufferLength|
      // and rate of |bufferRate|.  The |bufferRate| should not be too different
      // from |sampleRate| because of interpolation of the buffer to the context
      // rate.
      function testBufferStitching(sampleRate, bufferRate, bufferLength) {
        // The context for testing.  Channel 0 contains the output from
        // stitching all the buffers together, and channel 1 contains the
        // expected output.
        const context = new OfflineAudioContext(
            {numberOfChannels: 2, length: sampleRate, sampleRate: sampleRate});

        const merger = new ChannelMergerNode(
            context, {numberOfInputs: context.destination.channelCount});

        merger.connect(context.destination);

        // The reference is a sine wave at 440 Hz.
        const ref = new OscillatorNode(context, {frequency: 440, type: 'sine'});
        ref.connect(merger, 0, 1);
        ref.start();

        // The test signal is a bunch of short AudioBufferSources containing
        // bits of a sine wave.
        let waveSignal = new Float32Array(context.length);
        const omega = 2 * Math.PI / bufferRate * ref.frequency.value;
        for (let k = 0; k < context.length; ++k) {
          waveSignal[k] = Math.sin(omega * k);
        }

        // Slice the sine wave into many little buffers to be assigned to ABSNs
        // that are started at the appropriate times to produce a final sine
        // wave.
        for (let k = 0; k < context.length; k += bufferLength) {
          const buffer =
              new AudioBuffer({length: bufferLength, sampleRate: bufferRate});
          buffer.copyToChannel(waveSignal.slice(k, k + bufferLength), 0);

          const src = new AudioBufferSourceNode(context, {buffer: buffer});
          src.connect(merger, 0, 0);
          src.start(k / bufferRate);
        }

        return context.startRendering();
      }
    </script>
  </body>
</html>