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 "BiquadFilterNode.h" michael@0: #include "AudioNodeEngine.h" michael@0: #include "AudioNodeStream.h" michael@0: #include "AudioDestinationNode.h" michael@0: #include "PlayingRefChangeHandler.h" michael@0: #include "WebAudioUtils.h" michael@0: #include "blink/Biquad.h" michael@0: #include "mozilla/Preferences.h" michael@0: #include "AudioParamTimeline.h" michael@0: michael@0: namespace mozilla { michael@0: namespace dom { michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_INHERITED(BiquadFilterNode, AudioNode, michael@0: mFrequency, mDetune, mQ, mGain) michael@0: michael@0: NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(BiquadFilterNode) michael@0: NS_INTERFACE_MAP_END_INHERITING(AudioNode) michael@0: michael@0: NS_IMPL_ADDREF_INHERITED(BiquadFilterNode, AudioNode) michael@0: NS_IMPL_RELEASE_INHERITED(BiquadFilterNode, AudioNode) michael@0: michael@0: static void michael@0: SetParamsOnBiquad(WebCore::Biquad& aBiquad, michael@0: float aSampleRate, michael@0: BiquadFilterType aType, michael@0: double aFrequency, michael@0: double aQ, michael@0: double aGain, michael@0: double aDetune) michael@0: { michael@0: const double nyquist = aSampleRate * 0.5; michael@0: double normalizedFrequency = aFrequency / nyquist; michael@0: michael@0: if (aDetune) { michael@0: normalizedFrequency *= std::pow(2.0, aDetune / 1200); michael@0: } michael@0: michael@0: switch (aType) { michael@0: case BiquadFilterType::Lowpass: michael@0: aBiquad.setLowpassParams(normalizedFrequency, aQ); michael@0: break; michael@0: case BiquadFilterType::Highpass: michael@0: aBiquad.setHighpassParams(normalizedFrequency, aQ); michael@0: break; michael@0: case BiquadFilterType::Bandpass: michael@0: aBiquad.setBandpassParams(normalizedFrequency, aQ); michael@0: break; michael@0: case BiquadFilterType::Lowshelf: michael@0: aBiquad.setLowShelfParams(normalizedFrequency, aGain); michael@0: break; michael@0: case BiquadFilterType::Highshelf: michael@0: aBiquad.setHighShelfParams(normalizedFrequency, aGain); michael@0: break; michael@0: case BiquadFilterType::Peaking: michael@0: aBiquad.setPeakingParams(normalizedFrequency, aQ, aGain); michael@0: break; michael@0: case BiquadFilterType::Notch: michael@0: aBiquad.setNotchParams(normalizedFrequency, aQ); michael@0: break; michael@0: case BiquadFilterType::Allpass: michael@0: aBiquad.setAllpassParams(normalizedFrequency, aQ); michael@0: break; michael@0: default: michael@0: NS_NOTREACHED("We should never see the alternate names here"); michael@0: break; michael@0: } michael@0: } michael@0: michael@0: class BiquadFilterNodeEngine : public AudioNodeEngine michael@0: { michael@0: public: michael@0: BiquadFilterNodeEngine(AudioNode* aNode, AudioDestinationNode* aDestination) michael@0: : AudioNodeEngine(aNode) michael@0: , mSource(nullptr) michael@0: , mDestination(static_cast (aDestination->Stream())) michael@0: // Keep the default values in sync with the default values in michael@0: // BiquadFilterNode::BiquadFilterNode michael@0: , mType(BiquadFilterType::Lowpass) michael@0: , mFrequency(350.f) michael@0: , mDetune(0.f) michael@0: , mQ(1.f) michael@0: , mGain(0.f) 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 Parameteres { michael@0: TYPE, michael@0: FREQUENCY, michael@0: DETUNE, michael@0: Q, michael@0: GAIN michael@0: }; michael@0: void SetInt32Parameter(uint32_t aIndex, int32_t aValue) MOZ_OVERRIDE michael@0: { michael@0: switch (aIndex) { michael@0: case TYPE: mType = static_cast(aValue); break; michael@0: default: michael@0: NS_ERROR("Bad BiquadFilterNode Int32Parameter"); michael@0: } michael@0: } michael@0: void SetTimelineParameter(uint32_t aIndex, michael@0: const AudioParamTimeline& aValue, michael@0: TrackRate aSampleRate) MOZ_OVERRIDE michael@0: { michael@0: MOZ_ASSERT(mSource && mDestination); michael@0: switch (aIndex) { michael@0: case FREQUENCY: michael@0: mFrequency = aValue; michael@0: WebAudioUtils::ConvertAudioParamToTicks(mFrequency, mSource, mDestination); michael@0: break; michael@0: case DETUNE: michael@0: mDetune = aValue; michael@0: WebAudioUtils::ConvertAudioParamToTicks(mDetune, mSource, mDestination); michael@0: break; michael@0: case Q: michael@0: mQ = aValue; michael@0: WebAudioUtils::ConvertAudioParamToTicks(mQ, mSource, mDestination); michael@0: break; michael@0: case GAIN: michael@0: mGain = aValue; michael@0: WebAudioUtils::ConvertAudioParamToTicks(mGain, mSource, mDestination); michael@0: break; michael@0: default: michael@0: NS_ERROR("Bad BiquadFilterNodeEngine TimelineParameter"); michael@0: } 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: float inputBuffer[WEBAUDIO_BLOCK_SIZE]; michael@0: michael@0: if (aInput.IsNull()) { michael@0: bool hasTail = false; michael@0: for (uint32_t i = 0; i < mBiquads.Length(); ++i) { michael@0: if (mBiquads[i].hasTail()) { michael@0: hasTail = true; michael@0: break; michael@0: } michael@0: } michael@0: if (!hasTail) { michael@0: if (!mBiquads.IsEmpty()) { michael@0: mBiquads.Clear(); michael@0: michael@0: nsRefPtr refchanged = michael@0: new PlayingRefChangeHandler(aStream, PlayingRefChangeHandler::RELEASE); michael@0: aStream->Graph()-> michael@0: DispatchToMainThreadAfterStreamStateUpdate(refchanged.forget()); michael@0: } michael@0: michael@0: aOutput->SetNull(WEBAUDIO_BLOCK_SIZE); michael@0: return; michael@0: } michael@0: michael@0: PodArrayZero(inputBuffer); michael@0: michael@0: } else if(mBiquads.Length() != aInput.mChannelData.Length()){ michael@0: if (mBiquads.IsEmpty()) { michael@0: nsRefPtr refchanged = michael@0: new PlayingRefChangeHandler(aStream, PlayingRefChangeHandler::ADDREF); michael@0: aStream->Graph()-> michael@0: DispatchToMainThreadAfterStreamStateUpdate(refchanged.forget()); michael@0: } else { // Help people diagnose bug 924718 michael@0: NS_WARNING("BiquadFilterNode channel count changes may produce audio glitches"); michael@0: } michael@0: michael@0: // Adjust the number of biquads based on the number of channels michael@0: mBiquads.SetLength(aInput.mChannelData.Length()); michael@0: } michael@0: michael@0: uint32_t numberOfChannels = mBiquads.Length(); michael@0: AllocateAudioBlock(numberOfChannels, aOutput); michael@0: michael@0: TrackTicks pos = aStream->GetCurrentPosition(); michael@0: michael@0: double freq = mFrequency.GetValueAtTime(pos); michael@0: double q = mQ.GetValueAtTime(pos); michael@0: double gain = mGain.GetValueAtTime(pos); michael@0: double detune = mDetune.GetValueAtTime(pos); michael@0: michael@0: for (uint32_t i = 0; i < numberOfChannels; ++i) { michael@0: const float* input; michael@0: if (aInput.IsNull()) { michael@0: input = inputBuffer; michael@0: } else { michael@0: input = static_cast(aInput.mChannelData[i]); michael@0: if (aInput.mVolume != 1.0) { michael@0: AudioBlockCopyChannelWithScale(input, aInput.mVolume, inputBuffer); michael@0: input = inputBuffer; michael@0: } michael@0: } michael@0: SetParamsOnBiquad(mBiquads[i], aStream->SampleRate(), mType, freq, q, gain, detune); michael@0: michael@0: mBiquads[i].process(input, michael@0: static_cast(const_cast(aOutput->mChannelData[i])), michael@0: aInput.GetDuration()); michael@0: } michael@0: } michael@0: michael@0: virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE michael@0: { michael@0: // Not owned: michael@0: // - mSource - probably not owned michael@0: // - mDestination - probably not owned michael@0: // - AudioParamTimelines - counted in the AudioNode michael@0: size_t amount = AudioNodeEngine::SizeOfExcludingThis(aMallocSizeOf); michael@0: amount += mBiquads.SizeOfExcludingThis(aMallocSizeOf); 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: private: michael@0: AudioNodeStream* mSource; michael@0: AudioNodeStream* mDestination; michael@0: BiquadFilterType mType; michael@0: AudioParamTimeline mFrequency; michael@0: AudioParamTimeline mDetune; michael@0: AudioParamTimeline mQ; michael@0: AudioParamTimeline mGain; michael@0: nsTArray mBiquads; michael@0: }; michael@0: michael@0: BiquadFilterNode::BiquadFilterNode(AudioContext* aContext) michael@0: : AudioNode(aContext, michael@0: 2, michael@0: ChannelCountMode::Max, michael@0: ChannelInterpretation::Speakers) michael@0: , mType(BiquadFilterType::Lowpass) michael@0: , mFrequency(new AudioParam(MOZ_THIS_IN_INITIALIZER_LIST(), michael@0: SendFrequencyToStream, 350.f)) michael@0: , mDetune(new AudioParam(MOZ_THIS_IN_INITIALIZER_LIST(), michael@0: SendDetuneToStream, 0.f)) michael@0: , mQ(new AudioParam(MOZ_THIS_IN_INITIALIZER_LIST(), michael@0: SendQToStream, 1.f)) michael@0: , mGain(new AudioParam(MOZ_THIS_IN_INITIALIZER_LIST(), michael@0: SendGainToStream, 0.f)) michael@0: { michael@0: BiquadFilterNodeEngine* engine = new BiquadFilterNodeEngine(this, aContext->Destination()); michael@0: mStream = aContext->Graph()->CreateAudioNodeStream(engine, MediaStreamGraph::INTERNAL_STREAM); michael@0: engine->SetSourceStream(static_cast (mStream.get())); michael@0: } michael@0: michael@0: michael@0: size_t michael@0: BiquadFilterNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const michael@0: { michael@0: size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf); michael@0: michael@0: if (mFrequency) { michael@0: amount += mFrequency->SizeOfIncludingThis(aMallocSizeOf); michael@0: } michael@0: michael@0: if (mDetune) { michael@0: amount += mDetune->SizeOfIncludingThis(aMallocSizeOf); michael@0: } michael@0: michael@0: if (mQ) { michael@0: amount += mQ->SizeOfIncludingThis(aMallocSizeOf); michael@0: } michael@0: michael@0: if (mGain) { michael@0: amount += mGain->SizeOfIncludingThis(aMallocSizeOf); michael@0: } michael@0: michael@0: return amount; michael@0: } michael@0: michael@0: size_t michael@0: BiquadFilterNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const michael@0: { michael@0: return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); michael@0: } michael@0: michael@0: JSObject* michael@0: BiquadFilterNode::WrapObject(JSContext* aCx) michael@0: { michael@0: return BiquadFilterNodeBinding::Wrap(aCx, this); michael@0: } michael@0: michael@0: void michael@0: BiquadFilterNode::SetType(BiquadFilterType aType) michael@0: { michael@0: if (!Preferences::GetBool("media.webaudio.legacy.BiquadFilterNode")) { michael@0: // Do not accept the alternate enum values unless the legacy pref michael@0: // has been turned on. michael@0: switch (aType) { michael@0: case BiquadFilterType::_0: michael@0: case BiquadFilterType::_1: michael@0: case BiquadFilterType::_2: michael@0: case BiquadFilterType::_3: michael@0: case BiquadFilterType::_4: michael@0: case BiquadFilterType::_5: michael@0: case BiquadFilterType::_6: michael@0: case BiquadFilterType::_7: michael@0: // Do nothing in order to emulate setting an invalid enum value. michael@0: return; michael@0: default: michael@0: // Shut up the compiler warning michael@0: break; michael@0: } michael@0: } michael@0: michael@0: // Handle the alternate enum values michael@0: switch (aType) { michael@0: case BiquadFilterType::_0: aType = BiquadFilterType::Lowpass; break; michael@0: case BiquadFilterType::_1: aType = BiquadFilterType::Highpass; break; michael@0: case BiquadFilterType::_2: aType = BiquadFilterType::Bandpass; break; michael@0: case BiquadFilterType::_3: aType = BiquadFilterType::Lowshelf; break; michael@0: case BiquadFilterType::_4: aType = BiquadFilterType::Highshelf; break; michael@0: case BiquadFilterType::_5: aType = BiquadFilterType::Peaking; break; michael@0: case BiquadFilterType::_6: aType = BiquadFilterType::Notch; break; michael@0: case BiquadFilterType::_7: aType = BiquadFilterType::Allpass; break; michael@0: default: michael@0: // Shut up the compiler warning michael@0: break; michael@0: } michael@0: michael@0: mType = aType; michael@0: SendInt32ParameterToStream(BiquadFilterNodeEngine::TYPE, michael@0: static_cast(aType)); michael@0: } michael@0: michael@0: void michael@0: BiquadFilterNode::GetFrequencyResponse(const Float32Array& aFrequencyHz, michael@0: const Float32Array& aMagResponse, michael@0: const Float32Array& aPhaseResponse) michael@0: { michael@0: aFrequencyHz.ComputeLengthAndData(); michael@0: aMagResponse.ComputeLengthAndData(); michael@0: aPhaseResponse.ComputeLengthAndData(); michael@0: michael@0: uint32_t length = std::min(std::min(aFrequencyHz.Length(), aMagResponse.Length()), michael@0: aPhaseResponse.Length()); michael@0: if (!length) { michael@0: return; michael@0: } michael@0: michael@0: nsAutoArrayPtr frequencies(new float[length]); michael@0: float* frequencyHz = aFrequencyHz.Data(); michael@0: const double nyquist = Context()->SampleRate() * 0.5; michael@0: michael@0: // Normalize the frequencies michael@0: for (uint32_t i = 0; i < length; ++i) { michael@0: frequencies[i] = static_cast(frequencyHz[i] / nyquist); michael@0: } michael@0: michael@0: const double currentTime = Context()->CurrentTime(); michael@0: michael@0: double freq = mFrequency->GetValueAtTime(currentTime); michael@0: double q = mQ->GetValueAtTime(currentTime); michael@0: double gain = mGain->GetValueAtTime(currentTime); michael@0: double detune = mDetune->GetValueAtTime(currentTime); michael@0: michael@0: WebCore::Biquad biquad; michael@0: SetParamsOnBiquad(biquad, Context()->SampleRate(), mType, freq, q, gain, detune); michael@0: biquad.getFrequencyResponse(int(length), frequencies, aMagResponse.Data(), aPhaseResponse.Data()); michael@0: } michael@0: michael@0: void michael@0: BiquadFilterNode::SendFrequencyToStream(AudioNode* aNode) michael@0: { michael@0: BiquadFilterNode* This = static_cast(aNode); michael@0: SendTimelineParameterToStream(This, BiquadFilterNodeEngine::FREQUENCY, *This->mFrequency); michael@0: } michael@0: michael@0: void michael@0: BiquadFilterNode::SendDetuneToStream(AudioNode* aNode) michael@0: { michael@0: BiquadFilterNode* This = static_cast(aNode); michael@0: SendTimelineParameterToStream(This, BiquadFilterNodeEngine::DETUNE, *This->mDetune); michael@0: } michael@0: michael@0: void michael@0: BiquadFilterNode::SendQToStream(AudioNode* aNode) michael@0: { michael@0: BiquadFilterNode* This = static_cast(aNode); michael@0: SendTimelineParameterToStream(This, BiquadFilterNodeEngine::Q, *This->mQ); michael@0: } michael@0: michael@0: void michael@0: BiquadFilterNode::SendGainToStream(AudioNode* aNode) michael@0: { michael@0: BiquadFilterNode* This = static_cast(aNode); michael@0: SendTimelineParameterToStream(This, BiquadFilterNodeEngine::GAIN, *This->mGain); michael@0: } michael@0: michael@0: } michael@0: } michael@0: