summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/webaudio/the-audio-api/the-delaynode-interface/no-dezippering.html
blob: ccca103a3bc8583efb101048efb580fe21708ee3 (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
<!DOCTYPE html>
<html>
  <head>
    <title>
      Test DelayNode Has No Dezippering
    </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 id="layout-test-code">
      // The sample rate must be a power of two to avoid any round-off errors in
      // computing when to suspend a context on a rendering quantum boundary.
      // Otherwise this is pretty arbitrary.
      let sampleRate = 16384;

      let audit = Audit.createTaskRunner();

      audit.define(
          {label: 'test0', description: 'Test DelayNode has no dezippering'},
          (task, should) => {
            let context = new OfflineAudioContext(1, sampleRate, sampleRate);

            // Simple integer ramp for testing delay node
            let buffer = new AudioBuffer(
                {length: context.length, sampleRate: context.sampleRate});
            let rampData = buffer.getChannelData(0);
            for (let k = 0; k < rampData.length; ++k) {
              rampData[k] = k + 1;
            }

            // |delay0Frame| is the initial delay in frames. |delay1Frame| is
            // the new delay in frames.  These must be integers.
            let delay0Frame = 64;
            let delay1Frame = 16;

            let src = new AudioBufferSourceNode(context, {buffer: buffer});
            let delay = new DelayNode(
                context, {delayTime: delay0Frame / context.sampleRate});

            src.connect(delay).connect(context.destination);

            // After a render quantum, change the delay to |delay1Frame|.
            context.suspend(RENDER_QUANTUM_FRAMES / context.sampleRate)
                .then(() => {
                  delay.delayTime.value = delay1Frame / context.sampleRate;
                })
                .then(() => context.resume());

            src.start();
            context.startRendering()
                .then(renderedBuffer => {
                  let renderedData = renderedBuffer.getChannelData(0);

                  // The first |delay0Frame| frames should be zero.
                  should(
                      renderedData.slice(0, delay0Frame),
                      'output[0:' + (delay0Frame - 1) + ']')
                      .beConstantValueOf(0);

                  // Now we have the ramp should show up from the delay.
                  let ramp0 =
                      new Float32Array(RENDER_QUANTUM_FRAMES - delay0Frame);
                  for (let k = 0; k < ramp0.length; ++k) {
                    ramp0[k] = rampData[k];
                  }

                  should(
                      renderedData.slice(delay0Frame, RENDER_QUANTUM_FRAMES),
                      'output[' + delay0Frame + ':' +
                          (RENDER_QUANTUM_FRAMES - 1) + ']')
                      .beEqualToArray(ramp0);

                  // After one rendering quantum, the delay is changed to
                  // |delay1Frame|.
                  let ramp1 =
                      new Float32Array(context.length - RENDER_QUANTUM_FRAMES);
                  for (let k = 0; k < ramp1.length; ++k) {
                    // ramp1[k] = 1 + k + RENDER_QUANTUM_FRAMES - delay1Frame;
                    ramp1[k] =
                        rampData[k + RENDER_QUANTUM_FRAMES - delay1Frame];
                  }
                  should(
                      renderedData.slice(RENDER_QUANTUM_FRAMES),
                      'output[' + RENDER_QUANTUM_FRAMES + ':]')
                      .beEqualToArray(ramp1);
                })
                .then(() => task.done());
          });

      audit.define(
          {label: 'test1', description: 'Test value setter and setValueAtTime'},
          (task, should) => {
            testWithAutomation(should, {prefix: '', threshold: 6.5819e-5})
                .then(() => task.done());
          });

      audit.define(
          {label: 'test2', description: 'Test value setter and modulation'},
          (task, should) => {
            testWithAutomation(should, {
              prefix: 'With modulation: ',
              modulator: true
            }).then(() => task.done());
          });

      // Compare .value setter with setValueAtTime, Optionally allow modulation
      // of |delayTime|.
      function testWithAutomation(should, options) {
        let prefix = options.prefix;
        // Channel 0 is the output of delay node using the setter and channel 1
        // is the output using setValueAtTime.
        let context = new OfflineAudioContext(2, sampleRate, sampleRate);

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

        let src = new OscillatorNode(context);

        // |delay0Frame| is the initial delay value in frames. |delay1Frame| is
        // the new delay in frames. The values here are constrained only by the
        // constraints for a DelayNode.  These are pretty arbitrary except we
        // wanted them to be fractional so as not be on a frame boundary to
        // test interpolation compared with |setValueAtTime()|..
        let delay0Frame = 3.1;
        let delay1Frame = 47.2;

        let delayTest = new DelayNode(
            context, {delayTime: delay0Frame / context.sampleRate});
        let delayRef = new DelayNode(
            context, {delayTime: delay0Frame / context.sampleRate});

        src.connect(delayTest).connect(merger, 0, 0);
        src.connect(delayRef).connect(merger, 0, 1);

        if (options.modulator) {
          // Fairly arbitrary modulation of the delay time, with a peak
          // variation of 10 ms.
          let mod = new OscillatorNode(context, {frequency: 1000});
          let modGain = new GainNode(context, {gain: .01});
          mod.connect(modGain);
          modGain.connect(delayTest.delayTime);
          modGain.connect(delayRef.delayTime);
          mod.start();
        }

        // The time at which the delay time of |delayTest| node will be
        // changed.  This MUST be on a render quantum boundary, but is
        // otherwise arbitrary.
        let changeTime = 3 * RENDER_QUANTUM_FRAMES / context.sampleRate;

        // Schedule the delay change on |delayRef| and also apply the value
        // setter for |delayTest| at |changeTime|.
        delayRef.delayTime.setValueAtTime(
            delay1Frame / context.sampleRate, changeTime);
        context.suspend(changeTime)
            .then(() => {
              delayTest.delayTime.value = delay1Frame / context.sampleRate;
            })
            .then(() => context.resume());

        src.start();

        return context.startRendering().then(renderedBuffer => {
          let actual = renderedBuffer.getChannelData(0);
          let expected = renderedBuffer.getChannelData(1);

          let match = should(actual, prefix + '.value setter output')
                          .beCloseToArray(
                              expected, {absoluteThreshold: options.threshold});
          should(
              match,
              prefix + '.value setter output matches setValueAtTime output')
              .beTrue();
        });
      }

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