summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/webaudio/the-audio-api/the-convolvernode-interface/realtime-conv.html
blob: 8668e9d5ac9933cf92c6cc84cb22e24f03b6e045 (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
<!DOCTYPE html>
<html>
  <head>
    <title>
      Test Convolver on Real-time Context
    </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/convolution-testing.js"></script>
  </head>
  <body>
    <script id="layout-test-code">
      const audit = Audit.createTaskRunner();
      // Choose a length that is larger enough to cause multiple threads to be
      // used in the convolver.  For browsers that don't support this, this
      // value doesn't matter.
      const pulseLength = 16384;

      // The computed SNR should be at least this large.  This value depends on
      // teh platform and browser.  Don't set this value to be to much lower
      // than this. It probably indicates a fairly inaccurate convolver or
      // constant source node automations that should be fixed instead.
      const minRequiredSNR = 77.03;

      // To test the real-time convolver, we convolve two square pulses together
      // to produce a triangular pulse.  To verify the result is correct we
      // compare it against a constant source node configured to generate the
      // expected ramp.
      audit.define(
          {label: 'test', description: 'Test convolver with real-time context'},
          (task, should) => {
            // Use a power of two for the sample rate to eliminate round-off in
            // computing times from frames.
            const context = new AudioContext({sampleRate: 16384});

            // Square pulse for the convolver impulse response.
            const squarePulse = new AudioBuffer(
                {length: pulseLength, sampleRate: context.sampleRate});
            squarePulse.getChannelData(0).fill(1);

            const convolver = new ConvolverNode(
                context, {buffer: squarePulse, disableNormalization: true});

            // Square pulse for testing
            const srcSquare = new ConstantSourceNode(context, {offset: 0});
            srcSquare.connect(convolver);

            // Reference ramp.  Automations on this constant source node will
            // generate the desired ramp.
            const srcRamp = new ConstantSourceNode(context, {offset: 0});

            // Use these gain nodes to compute the difference between the
            // convolver output and the expected ramp to create the error
            // signal.
            const inverter = new GainNode(context, {gain: -1});
            const sum = new GainNode(context, {gain: 1});
            convolver.connect(sum);
            srcRamp.connect(inverter).connect(sum);

            // Square the error signal using this gain node.
            const squarer = new GainNode(context, {gain: 0});
            sum.connect(squarer);
            sum.connect(squarer.gain);

            // Merge the error signal and the square source so we can integrate
            // the error signal to find an SNR.
            const merger = new ChannelMergerNode(context, {numberOfInputs: 2});

            squarer.connect(merger, 0, 0);
            srcSquare.connect(merger, 0, 1);

            // For simplicity, use a ScriptProcessor to integrate the error
            // signal. The square pulse signal is used as a gate over which the
            // integration is done.  When the pulse ends, the SNR is computed
            // and the test ends.

            // |doSum| is used to determine when to integrate and when it
            // becomes false, it signals the end of integration.
            let doSum = false;

            // |signalSum| is the energy in the square pulse.  |errorSum| is the
            // energy in the error signal.
            let signalSum = 0;
            let errorSum = 0;

            let spn = context.createScriptProcessor(0, 2, 1);
            spn.onaudioprocess = (event) => {
              // Sum the values on the first channel when the second channel is
              // not zero.  When the second channel goes from non-zero to 0,
              // dump the value out and terminate the test.
              let c0 = event.inputBuffer.getChannelData(0);
              let c1 = event.inputBuffer.getChannelData(1);

              for (let k = 0; k < c1.length; ++k) {
                if (c1[k] == 0) {
                  if (doSum) {
                    doSum = false;
                    // Square wave is now silent and we were integration, so we
                    // can stop now and verify the SNR.
                    should(10 * Math.log10(signalSum / errorSum), 'SNR')
                        .beGreaterThanOrEqualTo(minRequiredSNR);
                    spn.onaudioprocess = null;
                    task.done();
                  }
                } else {
                  // Signal is non-zero so sum up the values.
                  doSum = true;
                  errorSum += c0[k];
                  signalSum += c1[k] * c1[k];
                }
              }
            };

            merger.connect(spn).connect(context.destination);

            // Schedule everything to start a bit in the futurefrom now, and end
            // pulseLength frames later.
            let now = context.currentTime;

            // |startFrame| is the number of frames to schedule ahead for
            // testing.
            const startFrame = 512;
            const startTime = startFrame / context.sampleRate;
            const pulseDuration = pulseLength / context.sampleRate;

            // Create a square pulse in the constant source node.
            srcSquare.offset.setValueAtTime(1, now + startTime);
            srcSquare.offset.setValueAtTime(0, now + startTime + pulseDuration);

            // Create the reference ramp.
            srcRamp.offset.setValueAtTime(1, now + startTime);
            srcRamp.offset.linearRampToValueAtTime(
                pulseLength,
                now + startTime + pulseDuration - 1 / context.sampleRate);
            srcRamp.offset.linearRampToValueAtTime(
                0,
                now + startTime + 2 * pulseDuration - 1 / context.sampleRate);

            // Start the ramps!
            srcRamp.start();
            srcSquare.start();
          });

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