tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

biquad-testing.js (5545B)


      1 // Globals, to make testing and debugging easier.
      2 let context;
      3 let filter;
      4 let signal;
      5 let renderedBuffer;
      6 let renderedData;
      7 
      8 // Use a power of two to eliminate round-off in converting frame to time
      9 let sampleRate = 32768;
     10 let pulseLengthFrames = .1 * sampleRate;
     11 
     12 // Maximum allowed error for the test to succeed.  Experimentally determined.
     13 let maxAllowedError = 5.9e-8;
     14 
     15 // This must be large enough so that the filtered result is essentially zero.
     16 // See comments for createTestAndRun.  This must be a whole number of frames.
     17 let timeStep = Math.ceil(.1 * sampleRate) / sampleRate;
     18 
     19 // Maximum number of filters we can process (mostly for setting the
     20 // render length correctly.)
     21 let maxFilters = 5;
     22 
     23 // How long to render.  Must be long enough for all of the filters we
     24 // want to test.
     25 let renderLengthSeconds = timeStep * (maxFilters + 1);
     26 
     27 let renderLengthSamples = Math.round(renderLengthSeconds * sampleRate);
     28 
     29 // Number of filters that will be processed.
     30 let nFilters;
     31 
     32 function createImpulseBuffer(context, length) {
     33  let impulse = context.createBuffer(1, length, context.sampleRate);
     34  let data = impulse.getChannelData(0);
     35  for (let k = 1; k < data.length; ++k) {
     36    data[k] = 0;
     37  }
     38  data[0] = 1;
     39 
     40  return impulse;
     41 }
     42 
     43 
     44 function createTestAndRun(context, filterType, testParameters) {
     45  // To test the filters, we apply a signal (an impulse) to each of
     46  // the specified filters, with each signal starting at a different
     47  // time.  The output of the filters is summed together at the
     48  // output.  Thus for filter k, the signal input to the filter
     49  // starts at time k * timeStep.  For this to work well, timeStep
     50  // must be large enough for the output of each filter to have
     51  // decayed to zero with timeStep seconds.  That way the filter
     52  // outputs don't interfere with each other.
     53 
     54  let filterParameters = testParameters.filterParameters;
     55  nFilters = Math.min(filterParameters.length, maxFilters);
     56 
     57  signal = new Array(nFilters);
     58  filter = new Array(nFilters);
     59 
     60  impulse = createImpulseBuffer(context, pulseLengthFrames);
     61 
     62  // Create all of the signal sources and filters that we need.
     63  for (let k = 0; k < nFilters; ++k) {
     64    signal[k] = context.createBufferSource();
     65    signal[k].buffer = impulse;
     66 
     67    filter[k] = context.createBiquadFilter();
     68    filter[k].type = filterType;
     69    filter[k].frequency.value =
     70        context.sampleRate / 2 * filterParameters[k].cutoff;
     71    filter[k].detune.value = (filterParameters[k].detune === undefined) ?
     72        0 :
     73        filterParameters[k].detune;
     74    filter[k].Q.value = filterParameters[k].q;
     75    filter[k].gain.value = filterParameters[k].gain;
     76 
     77    signal[k].connect(filter[k]);
     78    filter[k].connect(context.destination);
     79 
     80    signal[k].start(timeStep * k);
     81  }
     82 
     83  return context.startRendering().then(buffer => {
     84    checkFilterResponse(buffer, filterType, testParameters);
     85  });
     86 }
     87 
     88 function addSignal(dest, src, destOffset) {
     89  // Add src to dest at the given dest offset.
     90  for (let k = destOffset, j = 0; k < dest.length, j < src.length; ++k, ++j) {
     91    dest[k] += src[j];
     92  }
     93 }
     94 
     95 function generateReference(filterType, filterParameters) {
     96  let result = new Array(renderLengthSamples);
     97  let data = new Array(renderLengthSamples);
     98  // Initialize the result array and data.
     99  for (let k = 0; k < result.length; ++k) {
    100    result[k] = 0;
    101    data[k] = 0;
    102  }
    103  // Make data an impulse.
    104  data[0] = 1;
    105 
    106  for (let k = 0; k < nFilters; ++k) {
    107    // Filter an impulse
    108    let detune = (filterParameters[k].detune === undefined) ?
    109        0 :
    110        filterParameters[k].detune;
    111    let frequency = filterParameters[k].cutoff *
    112        Math.pow(2, detune / 1200);  // Apply detune, converting from Cents.
    113 
    114    let filterCoef = createFilter(
    115        filterType, frequency, filterParameters[k].q, filterParameters[k].gain);
    116    let y = filterData(filterCoef, data, renderLengthSamples);
    117 
    118    // Accumulate this filtered data into the final output at the desired
    119    // offset.
    120    addSignal(result, y, timeToSampleFrame(timeStep * k, sampleRate));
    121  }
    122 
    123  return result;
    124 }
    125 
    126 function checkFilterResponse(renderedBuffer, filterType, testParameters) {
    127  let filterParameters = testParameters.filterParameters;
    128  let maxAllowedError = testParameters.threshold;
    129  let should = testParameters.should;
    130 
    131  renderedData = renderedBuffer.getChannelData(0);
    132 
    133  reference = generateReference(filterType, filterParameters);
    134 
    135  let len = Math.min(renderedData.length, reference.length);
    136 
    137  let success = true;
    138 
    139  // Maximum error between rendered data and expected data
    140  let maxError = 0;
    141 
    142  // Sample offset where the maximum error occurred.
    143  let maxPosition = 0;
    144 
    145  // Number of infinities or NaNs that occurred in the rendered data.
    146  let invalidNumberCount = 0;
    147 
    148  should(nFilters, 'Number of filters tested')
    149      .beEqualTo(filterParameters.length);
    150 
    151  // Compare the rendered signal with our reference, keeping
    152  // track of the maximum difference (and the offset of the max
    153  // difference.)  Check for bad numbers in the rendered output
    154  // too.  There shouldn't be any.
    155  for (let k = 0; k < len; ++k) {
    156    let err = Math.abs(renderedData[k] - reference[k]);
    157    if (err > maxError) {
    158      maxError = err;
    159      maxPosition = k;
    160    }
    161    if (!isValidNumber(renderedData[k])) {
    162      ++invalidNumberCount;
    163    }
    164  }
    165 
    166  should(
    167      invalidNumberCount, 'Number of non-finite values in the rendered output')
    168      .beEqualTo(0);
    169 
    170  should(maxError, 'Max error in ' + filterTypeName[filterType] + ' response')
    171      .beLessThanOrEqualTo(maxAllowedError);
    172 }