tor-browser

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

audioparam-nominal-range.html (17931B)


      1 <!DOCTYPE html>
      2 <html>
      3  <head>
      4    <title>
      5      Test AudioParam Nominal Range Values
      6    </title>
      7    <script src="/resources/testharness.js"></script>
      8    <script src="/resources/testharnessreport.js"></script>
      9    <script src="/webaudio/resources/audit-util.js"></script>
     10    <script src="/webaudio/resources/audit.js"></script>
     11  </head>
     12  <body>
     13    <script id="layout-test-code">
     14      // Some arbitrary sample rate for the offline context.
     15      let sampleRate = 48000;
     16 
     17      // The actual contexts to use.  Generally use the offline context for
     18      // testing except for the media nodes which require an AudioContext.
     19      let offlineContext;
     20      let audioContext;
     21 
     22      // The set of all methods that we've tested for verifying that we tested
     23      // all of the necessary objects.
     24      let testedMethods = new Set();
     25 
     26      // The most positive single float value (the value just before infinity).
     27      // Be careful when changing this value!  Javascript only uses double
     28      // floats, so the value here should be the max single-float value,
     29      // converted directly to a double-float value.  This also depends on
     30      // Javascript reading this value and producing the desired double-float
     31      // value correctly.
     32      let mostPositiveFloat = 3.4028234663852886e38;
     33 
     34      let audit = Audit.createTaskRunner();
     35 
     36      // Array describing the tests that should be run.  |testOfflineConfigs| is
     37      // for tests that can use an offline context. |testOnlineConfigs| is for
     38      // tests that need to use an online context.  Offline contexts are
     39      // preferred when possible.
     40      let testOfflineConfigs = [
     41        {
     42          // The name of the method to create the particular node to be tested.
     43          creator: 'createGain',
     44 
     45          // Any args to pass to the creator function.
     46          args: [],
     47 
     48          // The min/max limits for each AudioParam of the node.  This is a
     49          // dictionary whose keys are
     50          // the names of each AudioParam in the node.  Don't define this if the
     51          // node doesn't have any
     52          // AudioParam attributes.
     53          limits: {
     54            gain: {
     55              // The expected min and max values for this AudioParam.
     56              minValue: -mostPositiveFloat,
     57              maxValue: mostPositiveFloat
     58            }
     59          }
     60        },
     61        {
     62          creator: 'createDelay',
     63          // Just specify a non-default value for the maximum delay so we can
     64          // make sure the limits are
     65          // set correctly.
     66          args: [1.5],
     67          limits: {delayTime: {minValue: 0, maxValue: 1.5}}
     68        },
     69        {
     70          creator: 'createBufferSource',
     71          args: [],
     72          limits: {
     73            playbackRate:
     74                {minValue: -mostPositiveFloat, maxValue: mostPositiveFloat},
     75            detune: {minValue: -mostPositiveFloat, maxValue: mostPositiveFloat}
     76          }
     77        },
     78        {
     79          creator: 'createStereoPanner',
     80          args: [],
     81          limits: {pan: {minValue: -1, maxValue: 1}}
     82        },
     83        {
     84          creator: 'createDynamicsCompressor',
     85          args: [],
     86          // Do not set limits for reduction;  it's currently an AudioParam but
     87          // should be a float.
     88          // So let the test fail for reduction.  When reduction is changed,
     89          // this test will then
     90          // correctly pass.
     91          limits: {
     92            threshold: {minValue: -100, maxValue: 0},
     93            knee: {minValue: 0, maxValue: 40},
     94            ratio: {minValue: 1, maxValue: 20},
     95            attack: {minValue: 0, maxValue: 1},
     96            release: {minValue: 0, maxValue: 1}
     97          }
     98        },
     99        {
    100          creator: 'createBiquadFilter',
    101          args: [],
    102          limits: {
    103            gain: {
    104              minValue: -mostPositiveFloat,
    105              // This complicated expression is used to get all the arithmetic
    106              // to round to the correct single-precision float value for the
    107              // desired max.  This also assumes that the implication computes
    108              // the limit as 40 * log10f(std::numeric_limits<float>::max()).
    109              maxValue:
    110                  Math.fround(40 * Math.fround(Math.log10(mostPositiveFloat)))
    111            },
    112            Q: {minValue: -mostPositiveFloat, maxValue: mostPositiveFloat},
    113            frequency: {minValue: 0, maxValue: sampleRate / 2},
    114            detune: {
    115              minValue: -Math.fround(1200 * Math.log2(mostPositiveFloat)),
    116              maxValue: Math.fround(1200 * Math.log2(mostPositiveFloat))
    117            }
    118          }
    119        },
    120        {
    121          creator: 'createOscillator',
    122          args: [],
    123          limits: {
    124            frequency: {minValue: -sampleRate / 2, maxValue: sampleRate / 2},
    125            detune: {
    126              minValue: -Math.fround(1200 * Math.log2(mostPositiveFloat)),
    127              maxValue: Math.fround(1200 * Math.log2(mostPositiveFloat))
    128            }
    129          }
    130        },
    131        {
    132          creator: 'createPanner',
    133          args: [],
    134          limits: {
    135            positionX: {
    136              minValue: -mostPositiveFloat,
    137              maxValue: mostPositiveFloat,
    138            },
    139            positionY: {
    140              minValue: -mostPositiveFloat,
    141              maxValue: mostPositiveFloat,
    142            },
    143            positionZ: {
    144              minValue: -mostPositiveFloat,
    145              maxValue: mostPositiveFloat,
    146            },
    147            orientationX: {
    148              minValue: -mostPositiveFloat,
    149              maxValue: mostPositiveFloat,
    150            },
    151            orientationY: {
    152              minValue: -mostPositiveFloat,
    153              maxValue: mostPositiveFloat,
    154            },
    155            orientationZ: {
    156              minValue: -mostPositiveFloat,
    157              maxValue: mostPositiveFloat,
    158            }
    159          },
    160        },
    161        {
    162          creator: 'createConstantSource',
    163          args: [],
    164          limits: {
    165            offset: {minValue: -mostPositiveFloat, maxValue: mostPositiveFloat}
    166          }
    167        },
    168        // These nodes don't have AudioParams, but we want to test them anyway.
    169        // Any arguments for the
    170        // constructor are pretty much arbitrary; they just need to be valid.
    171        {
    172          creator: 'createBuffer',
    173          args: [1, 1, sampleRate],
    174        },
    175        {creator: 'createIIRFilter', args: [[1, 2], [1, .9]]},
    176        {
    177          creator: 'createWaveShaper',
    178          args: [],
    179        },
    180        {
    181          creator: 'createConvolver',
    182          args: [],
    183        },
    184        {
    185          creator: 'createAnalyser',
    186          args: [],
    187        },
    188        {
    189          creator: 'createScriptProcessor',
    190          args: [0],
    191        },
    192        {
    193          creator: 'createPeriodicWave',
    194          args: [Float32Array.from([0, 0]), Float32Array.from([1, 0])],
    195        },
    196        {
    197          creator: 'createChannelSplitter',
    198          args: [],
    199        },
    200        {
    201          creator: 'createChannelMerger',
    202          args: [],
    203        },
    204      ];
    205 
    206      let testOnlineConfigs = [
    207        {creator: 'createMediaElementSource', args: [new Audio()]},
    208        {creator: 'createMediaStreamDestination', args: []}
    209        // Can't currently test MediaStreamSource because we're using an offline
    210        // context.
    211      ];
    212 
    213      // Create the contexts so we can use it in the following test.
    214      audit.define('initialize', (task, should) => {
    215        // Just any context so that we can create the nodes.
    216        should(() => {
    217          offlineContext = new OfflineAudioContext(1, 1, sampleRate);
    218        }, 'Create offline context for tests').notThrow();
    219        should(() => {
    220          onlineContext = new AudioContext();
    221        }, 'Create online context for tests').notThrow();
    222        task.done();
    223      });
    224 
    225      // Create a task for each entry in testOfflineConfigs
    226      for (let test in testOfflineConfigs) {
    227        let config = testOfflineConfigs[test]
    228        audit.define('Offline ' + config.creator, (function(c) {
    229                       return (task, should) => {
    230                         let node = offlineContext[c.creator](...c.args);
    231                         testLimits(should, c.creator, node, c.limits);
    232                         task.done();
    233                       };
    234                     })(config));
    235      }
    236 
    237      for (let test in testOnlineConfigs) {
    238        let config = testOnlineConfigs[test]
    239        audit.define('Online ' + config.creator, (function(c) {
    240                       return (task, should) => {
    241                         let node = onlineContext[c.creator](...c.args);
    242                         testLimits(should, c.creator, node, c.limits);
    243                         task.done();
    244                       };
    245                     })(config));
    246      }
    247 
    248      // Test the AudioListener params that were added for the automated Panner
    249      audit.define('AudioListener', (task, should) => {
    250        testLimits(should, '', offlineContext.listener, {
    251          positionX: {
    252            minValue: -mostPositiveFloat,
    253            maxValue: mostPositiveFloat,
    254          },
    255          positionY: {
    256            minValue: -mostPositiveFloat,
    257            maxValue: mostPositiveFloat,
    258          },
    259          positionZ: {
    260            minValue: -mostPositiveFloat,
    261            maxValue: mostPositiveFloat,
    262          },
    263          forwardX: {
    264            minValue: -mostPositiveFloat,
    265            maxValue: mostPositiveFloat,
    266          },
    267          forwardY: {
    268            minValue: -mostPositiveFloat,
    269            maxValue: mostPositiveFloat,
    270          },
    271          forwardZ: {
    272            minValue: -mostPositiveFloat,
    273            maxValue: mostPositiveFloat,
    274          },
    275          upX: {
    276            minValue: -mostPositiveFloat,
    277            maxValue: mostPositiveFloat,
    278          },
    279          upY: {
    280            minValue: -mostPositiveFloat,
    281            maxValue: mostPositiveFloat,
    282          },
    283          upZ: {
    284            minValue: -mostPositiveFloat,
    285            maxValue: mostPositiveFloat,
    286          }
    287        });
    288        task.done();
    289      });
    290 
    291      // Verify that we have tested all the create methods available on the
    292      // context.
    293      audit.define('verifyTests', (task, should) => {
    294        let allNodes = new Set();
    295        // Create the set of all "create" methods from the context.
    296        for (let method in offlineContext) {
    297          if (typeof offlineContext[method] === 'function' &&
    298              method.substring(0, 6) === 'create') {
    299            allNodes.add(method);
    300          }
    301        }
    302 
    303        // Compute the difference between the set of all create methods on the
    304        // context and the set of tests that we've run.
    305        let diff = new Set([...allNodes].filter(x => !testedMethods.has(x)));
    306 
    307        // Can't currently test a MediaStreamSourceNode, so remove it from the
    308        // diff set.
    309        diff.delete('createMediaStreamSource');
    310 
    311        // It's a test failure if we didn't test all of the create methods in
    312        // the context (except createMediaStreamSource, of course).
    313        let output = [];
    314        if (diff.size) {
    315          for (let item of diff)
    316            output.push(' ' + item.substring(6));
    317        }
    318 
    319        should(output.length === 0, 'Number of nodes not tested')
    320            .message(': 0', ': ' + output);
    321 
    322        task.done();
    323      });
    324 
    325      // Simple test of a few automation methods to verify we get warnings.
    326      audit.define('automation', (task, should) => {
    327        // Just use a DelayNode for testing because the audio param has finite
    328        // limits.
    329        should(() => {
    330          let d = offlineContext.createDelay();
    331 
    332          // The console output should have the warnings that we're interested
    333          // in.
    334          d.delayTime.setValueAtTime(-1, 0);
    335          d.delayTime.linearRampToValueAtTime(2, 1);
    336          d.delayTime.exponentialRampToValueAtTime(3, 2);
    337          d.delayTime.setTargetAtTime(-1, 3, .1);
    338          d.delayTime.setValueCurveAtTime(
    339              Float32Array.from([.1, .2, 1.5, -1]), 4, .1);
    340        }, 'Test automations (check console logs)').notThrow();
    341        task.done();
    342      });
    343 
    344      audit.run();
    345 
    346      // Is |object| an AudioParam?  We determine this by checking the
    347      // constructor name.
    348      function isAudioParam(object) {
    349        return object && object.constructor.name === 'AudioParam';
    350      }
    351 
    352      // Does |limitOptions| exist and does it have valid values for the
    353      // expected min and max values?
    354      function hasValidLimits(limitOptions) {
    355        return limitOptions && (typeof limitOptions.minValue === 'number') &&
    356            (typeof limitOptions.maxValue === 'number');
    357      }
    358 
    359      // Check the min and max values for the AudioParam attribute named
    360      // |paramName| for the |node|. The expected limits is given by the
    361      // dictionary |limits|.  If some test fails, add the name of the failed
    362      function validateAudioParamLimits(should, node, paramName, limits) {
    363        let nodeName = node.constructor.name;
    364        let parameter = node[paramName];
    365        let prefix = nodeName + '.' + paramName;
    366 
    367        let success = true;
    368        if (hasValidLimits(limits[paramName])) {
    369          // Verify that the min and max values for the parameter are correct.
    370          let isCorrect = should(parameter.minValue, prefix + '.minValue')
    371                              .beEqualTo(limits[paramName].minValue);
    372          isCorrect = should(parameter.maxValue, prefix + '.maxValue')
    373                          .beEqualTo(limits[paramName].maxValue) &&
    374              isCorrect;
    375 
    376          // Verify that the min and max attributes are read-only.  |testValue|
    377          // MUST be a number that can be represented exactly the same way as
    378          // both a double and single float.  A small integer works nicely.
    379          const testValue = 42;
    380          parameter.minValue = testValue;
    381          let isReadOnly;
    382          isReadOnly =
    383              should(parameter.minValue, `${prefix}.minValue = ${testValue}`)
    384                  .notBeEqualTo(testValue);
    385 
    386          should(isReadOnly, prefix + '.minValue is read-only').beEqualTo(true);
    387 
    388          isCorrect = isReadOnly && isCorrect;
    389 
    390          parameter.maxValue = testValue;
    391          isReadOnly =
    392              should(parameter.maxValue, `${prefix}.maxValue = ${testValue}`)
    393                  .notBeEqualTo(testValue);
    394          should(isReadOnly, prefix + '.maxValue is read-only').beEqualTo(true);
    395 
    396          isCorrect = isReadOnly && isCorrect;
    397 
    398          // Now try to set the parameter outside the nominal range.
    399          let newValue = 2 * limits[paramName].minValue - 1;
    400 
    401          let isClipped = true;
    402          let clippingTested = false;
    403          // If the new value is beyond float the largest single-precision
    404          // float, skip the test because Chrome throws an error.
    405          if (newValue >= -mostPositiveFloat) {
    406            parameter.value = newValue;
    407            clippingTested = true;
    408            isClipped =
    409                should(
    410                    parameter.value, 'Set ' + prefix + '.value = ' + newValue)
    411                    .beEqualTo(parameter.minValue) &&
    412                isClipped;
    413          }
    414 
    415          newValue = 2 * limits[paramName].maxValue + 1;
    416 
    417          if (newValue <= mostPositiveFloat) {
    418            parameter.value = newValue;
    419            clippingTested = true;
    420            isClipped =
    421                should(
    422                    parameter.value, 'Set ' + prefix + '.value = ' + newValue)
    423                    .beEqualTo(parameter.maxValue) &&
    424                isClipped;
    425          }
    426 
    427          if (clippingTested) {
    428            should(
    429                isClipped,
    430                prefix + ' was clipped to lie within the nominal range')
    431                .beEqualTo(true);
    432          }
    433 
    434          isCorrect = isCorrect && isClipped;
    435 
    436          success = isCorrect && success;
    437        } else {
    438          // Test config didn't specify valid limits.  Fail this test!
    439          should(
    440              clippingTested,
    441              'Limits for ' + nodeName + '.' + paramName +
    442                  ' were correctly defined')
    443              .beEqualTo(false);
    444 
    445          success = false;
    446        }
    447 
    448        return success;
    449      }
    450 
    451      // Test all of the AudioParams for |node| using the expected values in
    452      // |limits|. |creatorName| is the name of the method to create the node,
    453      // and is used to keep trakc of which tests we've run.
    454      function testLimits(should, creatorName, node, limits) {
    455        let nodeName = node.constructor.name;
    456        testedMethods.add(creatorName);
    457 
    458        let success = true;
    459 
    460        // List of all of the AudioParams that were tested.
    461        let audioParams = [];
    462 
    463        // List of AudioParams that failed the test.
    464        let incorrectParams = [];
    465 
    466        // Look through all of the keys for the node and extract just the
    467        // AudioParams
    468        Object.keys(node.__proto__).forEach(function(paramName) {
    469          if (isAudioParam(node[paramName])) {
    470            audioParams.push(paramName);
    471            let isValid = validateAudioParamLimits(
    472                should, node, paramName, limits, incorrectParams);
    473            if (!isValid)
    474              incorrectParams.push(paramName);
    475 
    476            success = isValid && success;
    477          }
    478        });
    479 
    480        // Print an appropriate message depending on whether there were
    481        // AudioParams defined or not.
    482        if (audioParams.length) {
    483          let message =
    484              'Nominal ranges for AudioParam(s) of ' + node.constructor.name;
    485          should(success, message)
    486              .message('are correct', 'are incorrect for: ' + +incorrectParams);
    487          return success;
    488        } else {
    489          should(!limits, nodeName)
    490              .message(
    491                  'has no AudioParams as expected',
    492                  'has no AudioParams but test expected ' + limits);
    493        }
    494      }
    495    </script>
    496  </body>
    497 </html>