summaryrefslogtreecommitdiffstats
path: root/dom/media/webaudio/test/blink/convolution-testing.js
blob: 98ff0c775608780bdf1c9730b66879403b7a6f0b (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
var sampleRate = 44100.0;

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

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

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

    for (var 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) {
    var audioBuffer = context.createBuffer(1, sampleFrameLength, context.sampleRate);

    var n = audioBuffer.length;
    var halfLength = n / 2;
    var data = audioBuffer.getChannelData(0);
    
    for (var i = 0; i < halfLength; ++i)
        data[i] = i + 1;

    for (var 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) {
    var match = true;
    var maxDelta = 0;
    var valueAtMaxDelta = 0;
    var maxDeltaIndex = 0;

    for (var i = 0; i < reference.length; ++i) {
        var diff = rendered[i] - reference[i];
        var 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.
    var allowedDeviationDecibels = -129.4;
    var maxDeviationDecibels = linearToDecibel(maxDelta / valueAtMaxDelta);

    if (maxDeviationDecibels <= allowedDeviationDecibels) {
        testPassed("Triangular portion of convolution is correct.");
    } else {
        testFailed("Triangular portion of convolution is not correct.  Max deviation = " + maxDeviationDecibels + " dB at " + maxDeltaIndex);
        match = false;
    }

    return match;
}        

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

    for (var i = reference.length; i < reference.length + breakpoint; ++i) {
        var 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).
    var refMax = 0;
    for (var 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.
    var threshold1 = -129.7;

    var tail1MaxDecibels = linearToDecibel(tail1Max/refMax);
    if (tail1MaxDecibels <= threshold1) {
        testPassed("First part of tail of convolution is sufficiently small.");
    } else {
        testFailed("First part of tail of convolution is not sufficiently small: " + tail1MaxDecibels + " dB");
        isZero = false;
    }

    return isZero;
}

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

    if (isZero) {
        testPassed("Rendered signal after tail of convolution is silent.");
    } else {
        testFailed("Rendered signal after tail of convolution should be silent.");
    }

    return isZero;
}

function checkConvolvedResult(trianglePulse) {
    return function(event) {
        var renderedBuffer = event.renderedBuffer;

        var referenceData = trianglePulse.getChannelData(0);
        var renderedData = renderedBuffer.getChannelData(0);
    
        var success = true;
    
        // Verify the triangular pulse is actually triangular.

        success = success && checkTriangularPulse(renderedData, referenceData);
        
        // 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.
        var breakpoint = 12800;

        success = success && checkTail1(renderedData, referenceData, breakpoint);
        
        success = success && checkTail2(renderedData, referenceData, breakpoint);
        
        if (success) {
            testPassed("Test signal was correctly convolved.");
        } else {
            testFailed("Test signal was not correctly convolved.");
        }

        finishJSTest();
    }
}