summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/webaudio/resources/convolution-testing.js
blob: c976f86c784d27257b480cac0b7ac9ce2e8cc1bc (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
let sampleRate = 44100.0;

let renderLengthSeconds = 8;
let pulseLengthSeconds = 1;
let pulseLengthFrames = pulseLengthSeconds * sampleRate;

function createSquarePulseBuffer(context, sampleFrameLength) {
  let audioBuffer =
      context.createBuffer(1, sampleFrameLength, context.sampleRate);

  let n = audioBuffer.length;
  let data = audioBuffer.getChannelData(0);

  for (let i = 0; i < n; ++i)
    data[i] = 1;

  return audioBuffer;
}

// The triangle buffer holds the expected result of the convolution.
// It linearly ramps up from 0 to its maximum value (at the center)
// then linearly ramps down to 0.  The center value corresponds to the
// point where the two square pulses overlap the most.
function createTrianglePulseBuffer(context, sampleFrameLength) {
  let audioBuffer =
      context.createBuffer(1, sampleFrameLength, context.sampleRate);

  let n = audioBuffer.length;
  let halfLength = n / 2;
  let data = audioBuffer.getChannelData(0);

  for (let i = 0; i < halfLength; ++i)
    data[i] = i + 1;

  for (let i = halfLength; i < n; ++i)
    data[i] = n - i - 1;

  return audioBuffer;
}

function log10(x) {
  return Math.log(x) / Math.LN10;
}

function linearToDecibel(x) {
  return 20 * log10(x);
}

// Verify that the rendered result is very close to the reference
// triangular pulse.
function checkTriangularPulse(rendered, reference, should) {
  let match = true;
  let maxDelta = 0;
  let valueAtMaxDelta = 0;
  let maxDeltaIndex = 0;

  for (let i = 0; i < reference.length; ++i) {
    let diff = rendered[i] - reference[i];
    let x = Math.abs(diff);
    if (x > maxDelta) {
      maxDelta = x;
      valueAtMaxDelta = reference[i];
      maxDeltaIndex = i;
    }
  }

  // allowedDeviationFraction was determined experimentally.  It
  // is the threshold of the relative error at the maximum
  // difference between the true triangular pulse and the
  // rendered pulse.
  let allowedDeviationDecibels = -124.41;
  let maxDeviationDecibels = linearToDecibel(maxDelta / valueAtMaxDelta);

  should(
      maxDeviationDecibels,
      'Deviation (in dB) of triangular portion of convolution')
      .beLessThanOrEqualTo(allowedDeviationDecibels);

  return match;
}

// Verify that the rendered data is close to zero for the first part
// of the tail.
function checkTail1(data, reference, breakpoint, should) {
  let isZero = true;
  let tail1Max = 0;

  for (let i = reference.length; i < reference.length + breakpoint; ++i) {
    let mag = Math.abs(data[i]);
    if (mag > tail1Max) {
      tail1Max = mag;
    }
  }

  // Let's find the peak of the reference (even though we know a
  // priori what it is).
  let refMax = 0;
  for (let i = 0; i < reference.length; ++i) {
    refMax = Math.max(refMax, Math.abs(reference[i]));
  }

  // This threshold is experimentally determined by examining the
  // value of tail1MaxDecibels.
  let threshold1 = -129.7;

  let tail1MaxDecibels = linearToDecibel(tail1Max / refMax);
  should(tail1MaxDecibels, 'Deviation in first part of tail of convolutions')
      .beLessThanOrEqualTo(threshold1);

  return isZero;
}

// Verify that the second part of the tail of the convolution is
// exactly zero.
function checkTail2(data, reference, breakpoint, should) {
  let isZero = true;
  let tail2Max = 0;
  // For the second part of the tail, the maximum value should be
  // exactly zero.
  let threshold2 = 0;
  for (let i = reference.length + breakpoint; i < data.length; ++i) {
    if (Math.abs(data[i]) > 0) {
      isZero = false;
      break;
    }
  }

  should(isZero, 'Rendered signal after tail of convolution is silent')
      .beTrue();

  return isZero;
}

function checkConvolvedResult(renderedBuffer, trianglePulse, should) {
  let referenceData = trianglePulse.getChannelData(0);
  let renderedData = renderedBuffer.getChannelData(0);

  let success = true;

  // Verify the triangular pulse is actually triangular.

  success =
      success && checkTriangularPulse(renderedData, referenceData, should);

  // Make sure that portion after convolved portion is totally
  // silent.  But round-off prevents this from being completely
  // true.  At the end of the triangle, it should be close to
  // zero.  If we go farther out, it should be even closer and
  // eventually zero.

  // For the tail of the convolution (where the result would be
  // theoretically zero), we partition the tail into two
  // parts.  The first is the at the beginning of the tail,
  // where we tolerate a small but non-zero value.  The second part is
  // farther along the tail where the result should be zero.

  // breakpoint is the point dividing the first two tail parts
  // we're looking at.  Experimentally determined.
  let breakpoint = 12800;

  success =
      success && checkTail1(renderedData, referenceData, breakpoint, should);

  success =
      success && checkTail2(renderedData, referenceData, breakpoint, should);

  should(success, 'Test signal convolved').message('correctly', 'incorrectly');
}