michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim:set ts=2 sw=2 sts=2 et cindent: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "OscillatorNode.h" michael@0: #include "AudioNodeEngine.h" michael@0: #include "AudioNodeStream.h" michael@0: #include "AudioDestinationNode.h" michael@0: #include "WebAudioUtils.h" michael@0: #include "blink/PeriodicWave.h" michael@0: michael@0: namespace mozilla { michael@0: namespace dom { michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_INHERITED(OscillatorNode, AudioNode, michael@0: mPeriodicWave, mFrequency, mDetune) michael@0: michael@0: NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(OscillatorNode) michael@0: NS_INTERFACE_MAP_END_INHERITING(AudioNode) michael@0: michael@0: NS_IMPL_ADDREF_INHERITED(OscillatorNode, AudioNode) michael@0: NS_IMPL_RELEASE_INHERITED(OscillatorNode, AudioNode) michael@0: michael@0: static const float sLeakTriangle = 0.995f; michael@0: static const float sLeak = 0.999f; michael@0: michael@0: class DCBlocker michael@0: { michael@0: public: michael@0: // These are sane defauts when the initial mPhase is zero michael@0: DCBlocker(float aLastInput = 0.0f, michael@0: float aLastOutput = 0.0f, michael@0: float aPole = 0.995) michael@0: :mLastInput(aLastInput), michael@0: mLastOutput(aLastOutput), michael@0: mPole(aPole) michael@0: { michael@0: MOZ_ASSERT(aPole > 0); michael@0: } michael@0: michael@0: inline float Process(float aInput) michael@0: { michael@0: float out; michael@0: michael@0: out = mLastOutput * mPole + aInput - mLastInput; michael@0: mLastOutput = out; michael@0: mLastInput = aInput; michael@0: michael@0: return out; michael@0: } michael@0: private: michael@0: float mLastInput; michael@0: float mLastOutput; michael@0: float mPole; michael@0: }; michael@0: michael@0: michael@0: class OscillatorNodeEngine : public AudioNodeEngine michael@0: { michael@0: public: michael@0: OscillatorNodeEngine(AudioNode* aNode, AudioDestinationNode* aDestination) michael@0: : AudioNodeEngine(aNode) michael@0: , mSource(nullptr) michael@0: , mDestination(static_cast (aDestination->Stream())) michael@0: , mStart(-1) michael@0: , mStop(TRACK_TICKS_MAX) michael@0: // Keep the default values in sync with OscillatorNode::OscillatorNode. michael@0: , mFrequency(440.f) michael@0: , mDetune(0.f) michael@0: , mType(OscillatorType::Sine) michael@0: , mPhase(0.) michael@0: // mSquare, mTriangle, and mSaw are not used for default type "sine". michael@0: // They are initialized if and when switching to the OscillatorTypes that michael@0: // use them. michael@0: // mFinalFrequency, mNumberOfHarmonics, mSignalPeriod, mAmplitudeAtZero, michael@0: // mPhaseIncrement, and mPhaseWrap are initialized in michael@0: // UpdateParametersIfNeeded() when mRecomputeParameters is set. michael@0: , mRecomputeParameters(true) michael@0: , mCustomLength(0) michael@0: { michael@0: } michael@0: michael@0: void SetSourceStream(AudioNodeStream* aSource) michael@0: { michael@0: mSource = aSource; michael@0: } michael@0: michael@0: enum Parameters { michael@0: FREQUENCY, michael@0: DETUNE, michael@0: TYPE, michael@0: PERIODICWAVE, michael@0: START, michael@0: STOP, michael@0: }; michael@0: void SetTimelineParameter(uint32_t aIndex, michael@0: const AudioParamTimeline& aValue, michael@0: TrackRate aSampleRate) MOZ_OVERRIDE michael@0: { michael@0: mRecomputeParameters = true; michael@0: switch (aIndex) { michael@0: case FREQUENCY: michael@0: MOZ_ASSERT(mSource && mDestination); michael@0: mFrequency = aValue; michael@0: WebAudioUtils::ConvertAudioParamToTicks(mFrequency, mSource, mDestination); michael@0: break; michael@0: case DETUNE: michael@0: MOZ_ASSERT(mSource && mDestination); michael@0: mDetune = aValue; michael@0: WebAudioUtils::ConvertAudioParamToTicks(mDetune, mSource, mDestination); michael@0: break; michael@0: default: michael@0: NS_ERROR("Bad OscillatorNodeEngine TimelineParameter"); michael@0: } michael@0: } michael@0: michael@0: virtual void SetStreamTimeParameter(uint32_t aIndex, TrackTicks aParam) michael@0: { michael@0: switch (aIndex) { michael@0: case START: mStart = aParam; break; michael@0: case STOP: mStop = aParam; break; michael@0: default: michael@0: NS_ERROR("Bad OscillatorNodeEngine StreamTimeParameter"); michael@0: } michael@0: } michael@0: michael@0: virtual void SetInt32Parameter(uint32_t aIndex, int32_t aParam) michael@0: { michael@0: switch (aIndex) { michael@0: case TYPE: michael@0: // Set the new type. michael@0: mType = static_cast(aParam); michael@0: if (mType != OscillatorType::Custom) { michael@0: // Forget any previous custom data. michael@0: mCustomLength = 0; michael@0: mCustom = nullptr; michael@0: mPeriodicWave = nullptr; michael@0: mRecomputeParameters = true; michael@0: } michael@0: // Update BLIT integrators with the new initial conditions. michael@0: switch (mType) { michael@0: case OscillatorType::Sine: michael@0: mPhase = 0.0; michael@0: break; michael@0: case OscillatorType::Square: michael@0: mPhase = 0.0; michael@0: // Initial integration condition is -0.5, because our michael@0: // square has 50% duty cycle. michael@0: mSquare = -0.5; michael@0: break; michael@0: case OscillatorType::Triangle: michael@0: // Initial mPhase and related integration condition so the michael@0: // triangle is in the middle of the first upward slope. michael@0: // XXX actually do the maths and put the right number here. michael@0: mPhase = (float)(M_PI / 2); michael@0: mSquare = 0.5; michael@0: mTriangle = 0.0; michael@0: break; michael@0: case OscillatorType::Sawtooth: michael@0: // Initial mPhase so the oscillator starts at the michael@0: // middle of the ramp, per spec. michael@0: mPhase = (float)(M_PI / 2); michael@0: // mSaw = 0 when mPhase = pi/2. michael@0: mSaw = 0.0; michael@0: break; michael@0: case OscillatorType::Custom: michael@0: // Custom waveforms don't use BLIT. michael@0: break; michael@0: default: michael@0: NS_ERROR("Bad OscillatorNodeEngine type parameter."); michael@0: } michael@0: // End type switch. michael@0: break; michael@0: case PERIODICWAVE: michael@0: MOZ_ASSERT(aParam >= 0, "negative custom array length"); michael@0: mCustomLength = static_cast(aParam); michael@0: break; michael@0: default: michael@0: NS_ERROR("Bad OscillatorNodeEngine Int32Parameter."); michael@0: } michael@0: // End index switch. michael@0: } michael@0: michael@0: virtual void SetBuffer(already_AddRefed aBuffer) michael@0: { michael@0: MOZ_ASSERT(mCustomLength, "Custom buffer sent before length"); michael@0: mCustom = aBuffer; michael@0: MOZ_ASSERT(mCustom->GetChannels() == 2, michael@0: "PeriodicWave should have sent two channels"); michael@0: mPeriodicWave = WebCore::PeriodicWave::create(mSource->SampleRate(), michael@0: mCustom->GetData(0), mCustom->GetData(1), mCustomLength); michael@0: } michael@0: michael@0: void IncrementPhase() michael@0: { michael@0: mPhase += mPhaseIncrement; michael@0: if (mPhase > mPhaseWrap) { michael@0: mPhase -= mPhaseWrap; michael@0: } michael@0: } michael@0: michael@0: // Square and triangle are using a bipolar band-limited impulse train, saw is michael@0: // using a normal band-limited impulse train. michael@0: bool UsesBipolarBLIT() { michael@0: return mType == OscillatorType::Square || mType == OscillatorType::Triangle; michael@0: } michael@0: michael@0: void UpdateParametersIfNeeded(TrackTicks ticks, size_t count) michael@0: { michael@0: double frequency, detune; michael@0: michael@0: bool simpleFrequency = mFrequency.HasSimpleValue(); michael@0: bool simpleDetune = mDetune.HasSimpleValue(); michael@0: michael@0: // Shortcut if frequency-related AudioParam are not automated, and we michael@0: // already have computed the frequency information and related parameters. michael@0: if (simpleFrequency && simpleDetune && !mRecomputeParameters) { michael@0: return; michael@0: } michael@0: michael@0: if (simpleFrequency) { michael@0: frequency = mFrequency.GetValue(); michael@0: } else { michael@0: frequency = mFrequency.GetValueAtTime(ticks, count); michael@0: } michael@0: if (simpleDetune) { michael@0: detune = mDetune.GetValue(); michael@0: } else { michael@0: detune = mDetune.GetValueAtTime(ticks, count); michael@0: } michael@0: michael@0: mFinalFrequency = frequency * pow(2., detune / 1200.); michael@0: mRecomputeParameters = false; michael@0: michael@0: // When using bipolar BLIT, we divide the signal period by two, because we michael@0: // are using two BLIT out of phase. michael@0: mSignalPeriod = UsesBipolarBLIT() ? 0.5 * mSource->SampleRate() / mFinalFrequency michael@0: : mSource->SampleRate() / mFinalFrequency; michael@0: // Wrap the phase accordingly: michael@0: mPhaseWrap = UsesBipolarBLIT() || mType == OscillatorType::Sine ? 2 * M_PI michael@0: : M_PI; michael@0: // Even number of harmonics for bipolar blit, odd otherwise. michael@0: mNumberOfHarmonics = UsesBipolarBLIT() ? 2 * floor(0.5 * mSignalPeriod) michael@0: : 2 * floor(0.5 * mSignalPeriod) + 1; michael@0: mPhaseIncrement = mType == OscillatorType::Sine ? 2 * M_PI / mSignalPeriod michael@0: : M_PI / mSignalPeriod; michael@0: mAmplitudeAtZero = mNumberOfHarmonics / mSignalPeriod; michael@0: } michael@0: michael@0: void FillBounds(float* output, TrackTicks ticks, michael@0: uint32_t& start, uint32_t& end) michael@0: { michael@0: MOZ_ASSERT(output); michael@0: static_assert(TrackTicks(WEBAUDIO_BLOCK_SIZE) < UINT_MAX, michael@0: "WEBAUDIO_BLOCK_SIZE overflows interator bounds."); michael@0: start = 0; michael@0: if (ticks < mStart) { michael@0: start = mStart - ticks; michael@0: for (uint32_t i = 0; i < start; ++i) { michael@0: output[i] = 0.0; michael@0: } michael@0: } michael@0: end = WEBAUDIO_BLOCK_SIZE; michael@0: if (ticks + end > mStop) { michael@0: end = mStop - ticks; michael@0: for (uint32_t i = end; i < WEBAUDIO_BLOCK_SIZE; ++i) { michael@0: output[i] = 0.0; michael@0: } michael@0: } michael@0: } michael@0: michael@0: float BipolarBLIT() michael@0: { michael@0: float blit; michael@0: float denom = sin(mPhase); michael@0: michael@0: if (fabs(denom) < std::numeric_limits::epsilon()) { michael@0: if (mPhase < 0.1f || mPhase > 2 * M_PI - 0.1f) { michael@0: blit = mAmplitudeAtZero; michael@0: } else { michael@0: blit = -mAmplitudeAtZero; michael@0: } michael@0: } else { michael@0: blit = sin(mNumberOfHarmonics * mPhase); michael@0: blit /= mSignalPeriod * denom; michael@0: } michael@0: return blit; michael@0: } michael@0: michael@0: float UnipolarBLIT() michael@0: { michael@0: float blit; michael@0: float denom = sin(mPhase); michael@0: michael@0: if (fabs(denom) <= std::numeric_limits::epsilon()) { michael@0: blit = mAmplitudeAtZero; michael@0: } else { michael@0: blit = sin(mNumberOfHarmonics * mPhase); michael@0: blit /= mSignalPeriod * denom; michael@0: } michael@0: michael@0: return blit; michael@0: } michael@0: michael@0: void ComputeSine(float * aOutput, TrackTicks ticks, uint32_t aStart, uint32_t aEnd) michael@0: { michael@0: for (uint32_t i = aStart; i < aEnd; ++i) { michael@0: UpdateParametersIfNeeded(ticks, i); michael@0: michael@0: aOutput[i] = sin(mPhase); michael@0: michael@0: IncrementPhase(); michael@0: } michael@0: } michael@0: michael@0: void ComputeSquare(float * aOutput, TrackTicks ticks, uint32_t aStart, uint32_t aEnd) michael@0: { michael@0: for (uint32_t i = aStart; i < aEnd; ++i) { michael@0: UpdateParametersIfNeeded(ticks, i); michael@0: // Integration to get us a square. It turns out we can have a michael@0: // pure integrator here. michael@0: mSquare = mSquare * sLeak + BipolarBLIT(); michael@0: aOutput[i] = mSquare; michael@0: // maybe we want to apply a gain, the wg has not decided yet michael@0: aOutput[i] *= 1.5; michael@0: IncrementPhase(); michael@0: } michael@0: } michael@0: michael@0: void ComputeSawtooth(float * aOutput, TrackTicks ticks, uint32_t aStart, uint32_t aEnd) michael@0: { michael@0: float dcoffset; michael@0: for (uint32_t i = aStart; i < aEnd; ++i) { michael@0: UpdateParametersIfNeeded(ticks, i); michael@0: // DC offset so the Saw does not ramp up to infinity when integrating. michael@0: dcoffset = mFinalFrequency / mSource->SampleRate(); michael@0: // Integrate and offset so we get mAmplitudeAtZero sawtooth. We have a michael@0: // very low frequency component somewhere here, but I'm not sure where. michael@0: mSaw = mSaw * sLeak + (UnipolarBLIT() - dcoffset); michael@0: // reverse the saw so we are spec compliant michael@0: aOutput[i] = -mSaw * 1.5; michael@0: michael@0: IncrementPhase(); michael@0: } michael@0: } michael@0: michael@0: void ComputeTriangle(float * aOutput, TrackTicks ticks, uint32_t aStart, uint32_t aEnd) michael@0: { michael@0: for (uint32_t i = aStart; i < aEnd; ++i) { michael@0: UpdateParametersIfNeeded(ticks, i); michael@0: // Integrate to get a square michael@0: mSquare += BipolarBLIT(); michael@0: // Leaky integrate to get a triangle. We get too much dc offset if we don't michael@0: // leaky integrate here. michael@0: // C6 = k0 / period michael@0: // (period is samplingrate / frequency, k0 = (PI/2)/(2*PI)) = 0.25 michael@0: float C6 = 0.25 / (mSource->SampleRate() / mFinalFrequency); michael@0: mTriangle = mTriangle * sLeakTriangle + mSquare + C6; michael@0: // DC Block, and scale back to [-1.0; 1.0] michael@0: aOutput[i] = mDCBlocker.Process(mTriangle) / (mSignalPeriod/2) * 1.5; michael@0: michael@0: IncrementPhase(); michael@0: } michael@0: } michael@0: michael@0: void ComputeCustom(float* aOutput, michael@0: TrackTicks ticks, michael@0: uint32_t aStart, michael@0: uint32_t aEnd) michael@0: { michael@0: MOZ_ASSERT(mPeriodicWave, "No custom waveform data"); michael@0: michael@0: uint32_t periodicWaveSize = mPeriodicWave->periodicWaveSize(); michael@0: // Mask to wrap wave data indices into the range [0,periodicWaveSize). michael@0: uint32_t indexMask = periodicWaveSize - 1; michael@0: MOZ_ASSERT(periodicWaveSize && (periodicWaveSize & indexMask) == 0, michael@0: "periodicWaveSize must be power of 2"); michael@0: float* higherWaveData = nullptr; michael@0: float* lowerWaveData = nullptr; michael@0: float tableInterpolationFactor; michael@0: // Phase increment at frequency of 1 Hz. michael@0: // mPhase runs [0,periodicWaveSize) here instead of [0,2*M_PI). michael@0: float basePhaseIncrement = michael@0: static_cast(periodicWaveSize) / mSource->SampleRate(); michael@0: michael@0: for (uint32_t i = aStart; i < aEnd; ++i) { michael@0: UpdateParametersIfNeeded(ticks, i); michael@0: mPeriodicWave->waveDataForFundamentalFrequency(mFinalFrequency, michael@0: lowerWaveData, michael@0: higherWaveData, michael@0: tableInterpolationFactor); michael@0: // Bilinear interpolation between adjacent samples in each table. michael@0: float floorPhase = floorf(mPhase); michael@0: uint32_t j1 = floorPhase; michael@0: j1 &= indexMask; michael@0: uint32_t j2 = j1 + 1; michael@0: j2 &= indexMask; michael@0: michael@0: float sampleInterpolationFactor = mPhase - floorPhase; michael@0: michael@0: float lower = (1.0f - sampleInterpolationFactor) * lowerWaveData[j1] + michael@0: sampleInterpolationFactor * lowerWaveData[j2]; michael@0: float higher = (1.0f - sampleInterpolationFactor) * higherWaveData[j1] + michael@0: sampleInterpolationFactor * higherWaveData[j2]; michael@0: aOutput[i] = (1.0f - tableInterpolationFactor) * lower + michael@0: tableInterpolationFactor * higher; michael@0: michael@0: // Calculate next phase position from wrapped value j1 to avoid loss of michael@0: // precision at large values. michael@0: mPhase = michael@0: j1 + sampleInterpolationFactor + basePhaseIncrement * mFinalFrequency; michael@0: } michael@0: } michael@0: michael@0: void ComputeSilence(AudioChunk *aOutput) michael@0: { michael@0: aOutput->SetNull(WEBAUDIO_BLOCK_SIZE); michael@0: } michael@0: michael@0: virtual void ProcessBlock(AudioNodeStream* aStream, michael@0: const AudioChunk& aInput, michael@0: AudioChunk* aOutput, michael@0: bool* aFinished) MOZ_OVERRIDE michael@0: { michael@0: MOZ_ASSERT(mSource == aStream, "Invalid source stream"); michael@0: michael@0: TrackTicks ticks = aStream->GetCurrentPosition(); michael@0: if (mStart == -1) { michael@0: ComputeSilence(aOutput); michael@0: return; michael@0: } michael@0: michael@0: if (ticks >= mStop) { michael@0: // We've finished playing. michael@0: ComputeSilence(aOutput); michael@0: *aFinished = true; michael@0: return; michael@0: } michael@0: if (ticks + WEBAUDIO_BLOCK_SIZE < mStart) { michael@0: // We're not playing yet. michael@0: ComputeSilence(aOutput); michael@0: return; michael@0: } michael@0: michael@0: AllocateAudioBlock(1, aOutput); michael@0: float* output = static_cast( michael@0: const_cast(aOutput->mChannelData[0])); michael@0: michael@0: uint32_t start, end; michael@0: FillBounds(output, ticks, start, end); michael@0: michael@0: // Synthesize the correct waveform. michael@0: switch(mType) { michael@0: case OscillatorType::Sine: michael@0: ComputeSine(output, ticks, start, end); michael@0: break; michael@0: case OscillatorType::Square: michael@0: ComputeSquare(output, ticks, start, end); michael@0: break; michael@0: case OscillatorType::Triangle: michael@0: ComputeTriangle(output, ticks, start, end); michael@0: break; michael@0: case OscillatorType::Sawtooth: michael@0: ComputeSawtooth(output, ticks, start, end); michael@0: break; michael@0: case OscillatorType::Custom: michael@0: ComputeCustom(output, ticks, start, end); michael@0: break; michael@0: default: michael@0: ComputeSilence(aOutput); michael@0: }; michael@0: michael@0: } michael@0: michael@0: virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE michael@0: { michael@0: size_t amount = AudioNodeEngine::SizeOfExcludingThis(aMallocSizeOf); michael@0: michael@0: // Not owned: michael@0: // - mSource michael@0: // - mDestination michael@0: // - mFrequency (internal ref owned by node) michael@0: // - mDetune (internal ref owned by node) michael@0: michael@0: if (mCustom) { michael@0: amount += mCustom->SizeOfIncludingThis(aMallocSizeOf); michael@0: } michael@0: michael@0: if (mPeriodicWave) { michael@0: amount += mPeriodicWave->sizeOfIncludingThis(aMallocSizeOf); michael@0: } michael@0: michael@0: return amount; michael@0: } michael@0: michael@0: virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE michael@0: { michael@0: return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); michael@0: } michael@0: michael@0: DCBlocker mDCBlocker; michael@0: AudioNodeStream* mSource; michael@0: AudioNodeStream* mDestination; michael@0: TrackTicks mStart; michael@0: TrackTicks mStop; michael@0: AudioParamTimeline mFrequency; michael@0: AudioParamTimeline mDetune; michael@0: OscillatorType mType; michael@0: float mPhase; michael@0: float mFinalFrequency; michael@0: uint32_t mNumberOfHarmonics; michael@0: float mSignalPeriod; michael@0: float mAmplitudeAtZero; michael@0: float mPhaseIncrement; michael@0: float mSquare; michael@0: float mTriangle; michael@0: float mSaw; michael@0: float mPhaseWrap; michael@0: bool mRecomputeParameters; michael@0: nsRefPtr mCustom; michael@0: uint32_t mCustomLength; michael@0: nsAutoPtr mPeriodicWave; michael@0: }; michael@0: michael@0: OscillatorNode::OscillatorNode(AudioContext* aContext) michael@0: : AudioNode(aContext, michael@0: 2, michael@0: ChannelCountMode::Max, michael@0: ChannelInterpretation::Speakers) michael@0: , mType(OscillatorType::Sine) michael@0: , mFrequency(new AudioParam(MOZ_THIS_IN_INITIALIZER_LIST(), michael@0: SendFrequencyToStream, 440.0f)) michael@0: , mDetune(new AudioParam(MOZ_THIS_IN_INITIALIZER_LIST(), michael@0: SendDetuneToStream, 0.0f)) michael@0: , mStartCalled(false) michael@0: , mStopped(false) michael@0: { michael@0: OscillatorNodeEngine* engine = new OscillatorNodeEngine(this, aContext->Destination()); michael@0: mStream = aContext->Graph()->CreateAudioNodeStream(engine, MediaStreamGraph::SOURCE_STREAM); michael@0: engine->SetSourceStream(static_cast (mStream.get())); michael@0: mStream->AddMainThreadListener(this); michael@0: } michael@0: michael@0: OscillatorNode::~OscillatorNode() michael@0: { michael@0: } michael@0: michael@0: size_t michael@0: OscillatorNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const michael@0: { michael@0: size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf); michael@0: michael@0: // For now only report if we know for sure that it's not shared. michael@0: amount += mPeriodicWave->SizeOfExcludingThisIfNotShared(aMallocSizeOf); michael@0: amount += mFrequency->SizeOfIncludingThis(aMallocSizeOf); michael@0: amount += mDetune->SizeOfIncludingThis(aMallocSizeOf); michael@0: return amount; michael@0: } michael@0: michael@0: size_t michael@0: OscillatorNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const michael@0: { michael@0: return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); michael@0: } michael@0: michael@0: JSObject* michael@0: OscillatorNode::WrapObject(JSContext* aCx) michael@0: { michael@0: return OscillatorNodeBinding::Wrap(aCx, this); michael@0: } michael@0: michael@0: void michael@0: OscillatorNode::SendFrequencyToStream(AudioNode* aNode) michael@0: { michael@0: OscillatorNode* This = static_cast(aNode); michael@0: SendTimelineParameterToStream(This, OscillatorNodeEngine::FREQUENCY, *This->mFrequency); michael@0: } michael@0: michael@0: void michael@0: OscillatorNode::SendDetuneToStream(AudioNode* aNode) michael@0: { michael@0: OscillatorNode* This = static_cast(aNode); michael@0: SendTimelineParameterToStream(This, OscillatorNodeEngine::DETUNE, *This->mDetune); michael@0: } michael@0: michael@0: void michael@0: OscillatorNode::SendTypeToStream() michael@0: { michael@0: if (mType == OscillatorType::Custom) { michael@0: // The engine assumes we'll send the custom data before updating the type. michael@0: SendPeriodicWaveToStream(); michael@0: } michael@0: SendInt32ParameterToStream(OscillatorNodeEngine::TYPE, static_cast(mType)); michael@0: } michael@0: michael@0: void OscillatorNode::SendPeriodicWaveToStream() michael@0: { michael@0: NS_ASSERTION(mType == OscillatorType::Custom, michael@0: "Sending custom waveform to engine thread with non-custom type"); michael@0: AudioNodeStream* ns = static_cast(mStream.get()); michael@0: MOZ_ASSERT(ns, "Missing node stream."); michael@0: MOZ_ASSERT(mPeriodicWave, "Send called without PeriodicWave object."); michael@0: SendInt32ParameterToStream(OscillatorNodeEngine::PERIODICWAVE, michael@0: mPeriodicWave->DataLength()); michael@0: nsRefPtr data = michael@0: mPeriodicWave->GetThreadSharedBuffer(); michael@0: ns->SetBuffer(data.forget()); michael@0: } michael@0: michael@0: void michael@0: OscillatorNode::Start(double aWhen, ErrorResult& aRv) michael@0: { michael@0: if (!WebAudioUtils::IsTimeValid(aWhen)) { michael@0: aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); michael@0: return; michael@0: } michael@0: michael@0: if (mStartCalled) { michael@0: aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); michael@0: return; michael@0: } michael@0: mStartCalled = true; michael@0: michael@0: AudioNodeStream* ns = static_cast(mStream.get()); michael@0: if (!ns) { michael@0: // Nothing to play, or we're already dead for some reason michael@0: return; michael@0: } michael@0: michael@0: // TODO: Perhaps we need to do more here. michael@0: ns->SetStreamTimeParameter(OscillatorNodeEngine::START, michael@0: Context(), aWhen); michael@0: michael@0: MarkActive(); michael@0: } michael@0: michael@0: void michael@0: OscillatorNode::Stop(double aWhen, ErrorResult& aRv) michael@0: { michael@0: if (!WebAudioUtils::IsTimeValid(aWhen)) { michael@0: aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); michael@0: return; michael@0: } michael@0: michael@0: if (!mStartCalled) { michael@0: aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); michael@0: return; michael@0: } michael@0: michael@0: AudioNodeStream* ns = static_cast(mStream.get()); michael@0: if (!ns || !Context()) { michael@0: // We've already stopped and had our stream shut down michael@0: return; michael@0: } michael@0: michael@0: // TODO: Perhaps we need to do more here. michael@0: ns->SetStreamTimeParameter(OscillatorNodeEngine::STOP, michael@0: Context(), std::max(0.0, aWhen)); michael@0: } michael@0: michael@0: void michael@0: OscillatorNode::NotifyMainThreadStateChanged() michael@0: { michael@0: if (mStream->IsFinished()) { michael@0: class EndedEventDispatcher : public nsRunnable michael@0: { michael@0: public: michael@0: explicit EndedEventDispatcher(OscillatorNode* aNode) michael@0: : mNode(aNode) {} michael@0: NS_IMETHODIMP Run() michael@0: { michael@0: // If it's not safe to run scripts right now, schedule this to run later michael@0: if (!nsContentUtils::IsSafeToRunScript()) { michael@0: nsContentUtils::AddScriptRunner(this); michael@0: return NS_OK; michael@0: } michael@0: michael@0: mNode->DispatchTrustedEvent(NS_LITERAL_STRING("ended")); michael@0: return NS_OK; michael@0: } michael@0: private: michael@0: nsRefPtr mNode; michael@0: }; michael@0: if (!mStopped) { michael@0: // Only dispatch the ended event once michael@0: NS_DispatchToMainThread(new EndedEventDispatcher(this)); michael@0: mStopped = true; michael@0: } michael@0: michael@0: // Drop the playing reference michael@0: // Warning: The below line might delete this. michael@0: MarkInactive(); michael@0: } michael@0: } michael@0: michael@0: } michael@0: } michael@0: