Bug 865253 - Part 3: Direct generation of fixed oscillator types. r=ehsan, r=webaudio
authorRalph Giles <giles@mozilla.com>
Thu, 06 Jun 2013 16:54:28 -0700
changeset 153801 cfeac11731aea8f41a57fab2f2a6ff50697ccf36
parent 153800 5c95ce1874b3dd02387fd1afb961b962746353ed
child 153802 78a602878032be2dacf76c7bf2c1fcf9cbf6647a
push id2859
push userakeybl@mozilla.com
push dateMon, 16 Sep 2013 19:14:59 +0000
treeherdermozilla-beta@87d3c51cd2bf [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersehsan, webaudio
bugs865253
milestone25.0a2
Bug 865253 - Part 3: Direct generation of fixed oscillator types. r=ehsan, r=webaudio Simple direct calculation of the fixed types. We probably want to use the PeriodicWave machinery to generate the pre-defined waveforms once it's available. In either case we can optimize this significantly by caching the generated waveform when the frequency is stationary over the block.
content/media/webaudio/OscillatorNode.cpp
--- a/content/media/webaudio/OscillatorNode.cpp
+++ b/content/media/webaudio/OscillatorNode.cpp
@@ -30,16 +30,17 @@ public:
     , mSource(nullptr)
     , mDestination(static_cast<AudioNodeStream*> (aDestination->Stream()))
     , mStart(0)
     , mStop(TRACK_TICKS_MAX)
     // Keep the default value in sync with the default value in OscillatorNode::OscillatorNode.
     , mFrequency(440.f)
     , mDetune(0.f)
     , mType(OscillatorType::Sine)
+    , mPhase(0.)
   {
   }
 
   void SetSourceStream(AudioNodeStream* aSource)
   {
     mSource = aSource;
   }
 
@@ -83,34 +84,196 @@ public:
   {
     switch (aIndex) {
     case TYPE: mType = static_cast<OscillatorType>(aParam); break;
     default:
       NS_ERROR("Bad OscillatorNodeEngine Int32Parameter");
     }
   }
 
+  double ComputeFrequency(TrackTicks ticks, size_t count)
+  {
+    double frequency, detune;
+    if (mFrequency.HasSimpleValue()) {
+      frequency = mFrequency.GetValue();
+    } else {
+      frequency = mFrequency.GetValueAtTime(ticks, count);
+    }
+    if (mDetune.HasSimpleValue()) {
+      detune = mDetune.GetValue();
+    } else {
+      detune = mDetune.GetValueAtTime(ticks, count);
+    }
+    return frequency * pow(2., detune / 1200.);
+  }
+
+  void FillBounds(float* output, TrackTicks ticks,
+                  uint32_t& start, uint32_t& end)
+  {
+    MOZ_ASSERT(output);
+    static_assert(TrackTicks(WEBAUDIO_BLOCK_SIZE) < UINT_MAX,
+        "WEBAUDIO_BLOCK_SIZE overflows interator bounds.");
+    start = 0;
+    if (ticks < mStart) {
+      start = mStart - ticks;
+      for (uint32_t i = 0; i < start; ++i) {
+        output[i] = 0.0;
+      }
+    }
+    end = WEBAUDIO_BLOCK_SIZE;
+    if (ticks + end > mStop) {
+      end = mStop - ticks;
+      for (uint32_t i = end; i < WEBAUDIO_BLOCK_SIZE; ++i) {
+        output[i] = 0.0;
+      }
+    }
+  }
+
+  void ComputeSine(AudioChunk *aOutput)
+  {
+    AllocateAudioBlock(1, aOutput);
+    float* output = static_cast<float*>(const_cast<void*>(aOutput->mChannelData[0]));
+
+    TrackTicks ticks = mSource->GetCurrentPosition();
+    uint32_t start, end;
+    FillBounds(output, ticks, start, end);
+
+    double rate = 2.*M_PI / mSource->SampleRate();
+    double phase = mPhase;
+    for (uint32_t i = start; i < end; ++i) {
+      phase += ComputeFrequency(ticks, i) * rate;
+      output[i] = sin(phase);
+    }
+    mPhase = phase;
+    while (mPhase > 2.0*M_PI) {
+      // Rescale to avoid precision reductions on long runs.
+      mPhase -= 2.0*M_PI;
+    }
+  }
+
+  void ComputeSquare(AudioChunk *aOutput)
+  {
+    AllocateAudioBlock(1, aOutput);
+    float* output = static_cast<float*>(const_cast<void*>(aOutput->mChannelData[0]));
+
+    TrackTicks ticks = mSource->GetCurrentPosition();
+    uint32_t start, end;
+    FillBounds(output, ticks, start, end);
+
+    double rate = 1.0 / mSource->SampleRate();
+    double phase = mPhase;
+    for (uint32_t i = start; i < end; ++i) {
+      phase += ComputeFrequency(ticks, i) * rate;
+      if (phase > 1.0) {
+        phase -= 1.0;
+      }
+      output[i] = phase < 0.5 ? 1.0 : -1.0;
+    }
+    mPhase = phase;
+  }
+
+  void ComputeSawtooth(AudioChunk *aOutput)
+  {
+    AllocateAudioBlock(1, aOutput);
+    float* output = static_cast<float*>(const_cast<void*>(aOutput->mChannelData[0]));
+
+    TrackTicks ticks = mSource->GetCurrentPosition();
+    uint32_t start, end;
+    FillBounds(output, ticks, start, end);
+
+    double rate = 1.0 / mSource->SampleRate();
+    double phase = mPhase;
+    for (uint32_t i = start; i < end; ++i) {
+      phase += ComputeFrequency(ticks, i) * rate;
+      if (phase > 1.0) {
+        phase -= 1.0;
+      }
+      output[i] = phase < 0.5 ? 2.0*phase : 2.0*(phase - 1.0);
+    }
+    mPhase = phase;
+  }
+
+  void ComputeTriangle(AudioChunk *aOutput)
+  {
+    AllocateAudioBlock(1, aOutput);
+    float* output = static_cast<float*>(const_cast<void*>(aOutput->mChannelData[0]));
+
+    TrackTicks ticks = mSource->GetCurrentPosition();
+    uint32_t start, end;
+    FillBounds(output, ticks, start, end);
+
+    double rate = 1.0 / mSource->SampleRate();
+    double phase = mPhase;
+    for (uint32_t i = start; i < end; ++i) {
+      phase += ComputeFrequency(ticks, i) * rate;
+      if (phase > 1.0) {
+        phase -= 1.0;
+      }
+      if (phase < 0.25) {
+        output[i] = 4.0*phase;
+      } else if (phase < 0.75) {
+        output[i] = 1.0 - 4.0*(phase - 0.25);
+      } else {
+        output[i] = 4.0*(phase - 0.75) - 1.0;
+      }
+    }
+    mPhase = phase;
+  }
+
+  void ComputeSilence(AudioChunk *aOutput)
+  {
+    aOutput->SetNull(WEBAUDIO_BLOCK_SIZE);
+  }
+
   virtual void ProduceAudioBlock(AudioNodeStream* aStream,
                                  const AudioChunk& aInput,
                                  AudioChunk* aOutput,
                                  bool* aFinished) MOZ_OVERRIDE
   {
     MOZ_ASSERT(mSource == aStream, "Invalid source stream");
 
-    // TODO: Synthesize a waveform here.
-    *aOutput = aInput;
+    TrackTicks ticks = aStream->GetCurrentPosition();
+    if (ticks + WEBAUDIO_BLOCK_SIZE < mStart) {
+      // We're not playing yet.
+      ComputeSilence(aOutput);
+      return;
+    }
+    if (ticks >= mStop) {
+      // We've finished playing.
+      ComputeSilence(aOutput);
+      *aFinished = true;
+      return;
+    }
+    // Synthesize the correct waveform.
+    switch (mType) {
+      case OscillatorType::Sine:
+        ComputeSine(aOutput);
+        break;
+      case OscillatorType::Square:
+        ComputeSquare(aOutput);
+        break;
+      case OscillatorType::Sawtooth:
+        ComputeSawtooth(aOutput);
+        break;
+      case OscillatorType::Triangle:
+        ComputeTriangle(aOutput);
+        break;
+      default:
+        ComputeSilence(aOutput);
+    }
   }
 
   AudioNodeStream* mSource;
   AudioNodeStream* mDestination;
   TrackTicks mStart;
   TrackTicks mStop;
   AudioParamTimeline mFrequency;
   AudioParamTimeline mDetune;
   OscillatorType mType;
+  double mPhase;
 };
 
 OscillatorNode::OscillatorNode(AudioContext* aContext)
   : AudioNode(aContext,
               2,
               ChannelCountMode::Max,
               ChannelInterpretation::Speakers)
   , mType(OscillatorType::Sine)