tor-browser

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

detune-limiting.html (4645B)


      1 <!DOCTYPE html>
      2 <html>
      3  <head>
      4    <title>Oscillator Detune: High Detune Limits and Automation</title>
      5    <script src="/resources/testharness.js"></script>
      6    <script src="/resources/testharnessreport.js"></script>
      7    <script src="/webaudio/resources/audit-util.js"></script>
      8  </head>
      9  <body>
     10    <script>
     11      const sampleRate = 44100;
     12      const renderLengthSeconds = 0.125;
     13      const totalFrames = sampleRate * renderLengthSeconds;
     14      promise_test(async t => {
     15        const context = new OfflineAudioContext(2, totalFrames, sampleRate);
     16        const merger = new ChannelMergerNode(context, {
     17          numberOfInputs: context.destination.channelCount
     18        });
     19        merger.connect(context.destination);
     20 
     21        // Set the detune so the oscillator goes beyond the Nyquist frequency
     22        // and verify if it produces silence.
     23        const oscFrequency = 1;
     24        const detunedFrequency = sampleRate;
     25        const detuneValue = Math.fround(1200 * Math.log2(detunedFrequency));
     26        const testOsc = new OscillatorNode(context, {
     27          frequency: oscFrequency,
     28          detune: detuneValue
     29        });
     30        testOsc.connect(merger, 0, 1);
     31        const computedFrequency
     32            = oscFrequency * Math.pow(2, detuneValue / 1200);
     33        const refOsc = new OscillatorNode(context, {
     34          frequency: computedFrequency
     35        });
     36        refOsc.connect(merger, 0, 0);
     37        testOsc.start();
     38        refOsc.start();
     39        const renderedBuffer = await context.startRendering();
     40        const expected = renderedBuffer.getChannelData(0);
     41        const actual = renderedBuffer.getChannelData(1);
     42        assert_greater_than_equal(
     43            refOsc.frequency.value,
     44            context.sampleRate / 2,
     45            'Reference oscillator frequency should be ≥ Nyquist'
     46        );
     47        assert_constant_value(
     48            expected,
     49            0,
     50            `Reference output (freq: ${refOsc.frequency.value}) must be zero`
     51        );
     52        assert_array_equal_within_eps(
     53            actual,
     54            expected,
     55            0,
     56            `Test oscillator output (freq: ${oscFrequency}, ` +
     57                `detune: ${detuneValue}) must match reference output`
     58        );
     59      }, 'Oscillator with large detune produces 0 output at Nyquist or above');
     60      promise_test(async t => {
     61        const context = new OfflineAudioContext(1, totalFrames, sampleRate);
     62        const baseFrequency = 1;
     63        const rampEndTime = renderLengthSeconds / 2;
     64        const detuneEnd = 1e7;
     65        const oscillator
     66            = new OscillatorNode(context, { frequency: baseFrequency });
     67        oscillator.detune.linearRampToValueAtTime(detuneEnd, rampEndTime);
     68        oscillator.connect(context.destination);
     69        oscillator.start();
     70        const renderedBuffer = await context.startRendering();
     71        const audio = renderedBuffer.getChannelData(0);
     72        // At some point, the computed oscillator frequency will go
     73        // above Nyquist.  Determine at what time this occurrs.  The
     74        // computed frequency is f * 2^(d/1200) where |f| is the
     75        // oscillator frequency and |d| is the detune value.  Thus,
     76        // find |d| such that Nyquist = f*2^(d/1200). That is, d =
     77        // 1200*log2(Nyquist/f)
     78        const nyquist = context.sampleRate / 2;
     79        const criticalDetune = 1200 * Math.log2(nyquist / baseFrequency);
     80        // Now figure out at what point on the linear ramp does the
     81        // detune value reach this critical value.  For a linear ramp:
     82        //
     83        //   v(t) = V0+(V1-V0)*(t-T0)/(T1-T0)
     84        //
     85        // Thus,
     86        //
     87        //   t = ((T1-T0)*v(t) + T0*V1 - T1*V0)/(V1-V0)
     88        //
     89        // In this test, T0 = 0, V0 = 0, T1 = rampEnd, V1 =
     90        // detuneEnd, and v(t) = criticalDetune
     91        const criticalTime = (rampEndTime * criticalDetune) / detuneEnd;
     92        const criticalFrame = Math.ceil(criticalTime * sampleRate);
     93        assert_less_than_equal(
     94            criticalFrame,
     95            audio.length,
     96            'Critical frame should lie within audio buffer length'
     97        );
     98        assert_not_constant_value(
     99            audio.slice(0, criticalFrame),
    100            0,
    101            `Oscillator output [0:${criticalFrame - 1}] should not ` +
    102                `be zero before exceeding Nyquist`
    103        );
    104        assert_constant_value(
    105            audio.slice(criticalFrame),
    106            0,
    107            `Oscillator output [${criticalFrame}:] should be zero ` +
    108                `after exceeding Nyquist`
    109        );
    110      }, 'Oscillator with detune automation becomes silent above Nyquist');
    111    </script>
    112  </body>
    113 </html>