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');
}
|