StereoPannerNode.cpp (6958B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim:set ts=2 sw=2 sts=2 et cindent: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "StereoPannerNode.h" 8 9 #include "AlignmentUtils.h" 10 #include "AudioDestinationNode.h" 11 #include "AudioNodeEngine.h" 12 #include "AudioNodeTrack.h" 13 #include "AudioParam.h" 14 #include "AudioParamTimeline.h" 15 #include "PanningUtils.h" 16 #include "Tracing.h" 17 #include "WebAudioUtils.h" 18 #include "mozilla/dom/StereoPannerNodeBinding.h" 19 20 namespace mozilla::dom { 21 22 NS_IMPL_CYCLE_COLLECTION_INHERITED(StereoPannerNode, AudioNode, mPan) 23 24 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(StereoPannerNode) 25 NS_INTERFACE_MAP_END_INHERITING(AudioNode) 26 27 NS_IMPL_ADDREF_INHERITED(StereoPannerNode, AudioNode) 28 NS_IMPL_RELEASE_INHERITED(StereoPannerNode, AudioNode) 29 30 class StereoPannerNodeEngine final : public AudioNodeEngine { 31 public: 32 StereoPannerNodeEngine(AudioNode* aNode, AudioDestinationNode* aDestination) 33 : AudioNodeEngine(aNode), 34 mDestination(aDestination->Track()) 35 // Keep the default value in sync with the default value in 36 // StereoPannerNode::StereoPannerNode. 37 , 38 mPan(0.f) {} 39 40 enum Parameters { PAN }; 41 void RecvTimelineEvent(uint32_t aIndex, AudioParamEvent& aEvent) override { 42 MOZ_ASSERT(mDestination); 43 aEvent.ConvertToTicks(mDestination); 44 45 switch (aIndex) { 46 case PAN: 47 mPan.InsertEvent<int64_t>(aEvent); 48 break; 49 default: 50 NS_ERROR("Bad StereoPannerNode TimelineParameter"); 51 } 52 } 53 54 void GetGainValuesForPanning(float aPanning, bool aMonoToStereo, 55 float& aLeftGain, float& aRightGain) { 56 // Clamp and normalize the panning in [0; 1] 57 aPanning = std::min(std::max(aPanning, -1.f), 1.f); 58 59 if (aMonoToStereo) { 60 aPanning += 1; 61 aPanning /= 2; 62 } else if (aPanning <= 0) { 63 aPanning += 1; 64 } 65 66 aLeftGain = fdlibm_cos(0.5 * M_PI * aPanning); 67 aRightGain = fdlibm_sin(0.5 * M_PI * aPanning); 68 } 69 70 void SetToSilentStereoBlock(AudioBlock* aChunk) { 71 for (uint32_t channel = 0; channel < 2; channel++) { 72 float* samples = aChunk->ChannelFloatsForWrite(channel); 73 for (uint32_t i = 0; i < WEBAUDIO_BLOCK_SIZE; i++) { 74 samples[i] = 0.f; 75 } 76 } 77 } 78 79 void UpmixToStereoIfNeeded(const AudioBlock& aInput, AudioBlock* aOutput) { 80 if (aInput.ChannelCount() == 2) { 81 *aOutput = aInput; 82 } else { 83 MOZ_ASSERT(aInput.ChannelCount() == 1); 84 aOutput->SetBuffer(aInput.GetBuffer()); 85 aOutput->mChannelData.SetLength(2); 86 for (uint32_t i = 0; i < 2; ++i) { 87 aOutput->mChannelData[i] = aInput.ChannelData<float>()[0]; 88 } 89 // 1/sqrt(2) multiplier is because StereoPanner up-mixing differs from 90 // input up-mixing. 91 aOutput->mVolume = M_SQRT1_2 * aInput.mVolume; 92 aOutput->mBufferFormat = AUDIO_FORMAT_FLOAT32; 93 } 94 } 95 96 virtual void ProcessBlock(AudioNodeTrack* aTrack, GraphTime aFrom, 97 const AudioBlock& aInput, AudioBlock* aOutput, 98 bool* aFinished) override { 99 // The output of this node is always stereo, no matter what the inputs are. 100 MOZ_ASSERT(aInput.ChannelCount() <= 2); 101 TRACE("StereoPannerNodeEngine::ProcessBlock"); 102 103 bool monoToStereo = aInput.ChannelCount() == 1; 104 105 if (aInput.IsNull()) { 106 // If input is silent, so is the output 107 aOutput->SetNull(WEBAUDIO_BLOCK_SIZE); 108 } else if (mPan.HasSimpleValue()) { 109 float panning = mPan.GetValue(); 110 // If the panning is 0.0, we can simply copy the input to the 111 // output with gain applied, up-mixing to stereo if needed. 112 if (panning == 0.0f) { 113 UpmixToStereoIfNeeded(aInput, aOutput); 114 } else { 115 // Optimize the case where the panning is constant for this processing 116 // block: we can just apply a constant gain on the left and right 117 // channel 118 float gainL, gainR; 119 120 GetGainValuesForPanning(panning, monoToStereo, gainL, gainR); 121 ApplyStereoPanning(aInput, aOutput, gainL, gainR, panning <= 0); 122 } 123 } else { 124 float computedGain[2 * WEBAUDIO_BLOCK_SIZE + 4]; 125 alignas(16) bool onLeft[WEBAUDIO_BLOCK_SIZE]; 126 127 float values[WEBAUDIO_BLOCK_SIZE]; 128 TrackTime tick = mDestination->GraphTimeToTrackTime(aFrom); 129 mPan.GetValuesAtTime(tick, values, WEBAUDIO_BLOCK_SIZE); 130 131 float* alignedComputedGain = ALIGNED16(computedGain); 132 ASSERT_ALIGNED16(alignedComputedGain); 133 for (size_t counter = 0; counter < WEBAUDIO_BLOCK_SIZE; ++counter) { 134 float left, right; 135 GetGainValuesForPanning(values[counter], monoToStereo, left, right); 136 137 alignedComputedGain[counter] = left; 138 alignedComputedGain[WEBAUDIO_BLOCK_SIZE + counter] = right; 139 onLeft[counter] = values[counter] <= 0; 140 } 141 142 // Apply the gain to the output buffer 143 ApplyStereoPanning(aInput, aOutput, alignedComputedGain, 144 &alignedComputedGain[WEBAUDIO_BLOCK_SIZE], onLeft); 145 } 146 } 147 148 virtual size_t SizeOfIncludingThis( 149 MallocSizeOf aMallocSizeOf) const override { 150 return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); 151 } 152 153 RefPtr<AudioNodeTrack> mDestination; 154 AudioParamTimeline mPan; 155 }; 156 157 StereoPannerNode::StereoPannerNode(AudioContext* aContext) 158 : AudioNode(aContext, 2, ChannelCountMode::Clamped_max, 159 ChannelInterpretation::Speakers) { 160 mPan = 161 CreateAudioParam(StereoPannerNodeEngine::PAN, u"pan"_ns, 0.f, -1.f, 1.f); 162 StereoPannerNodeEngine* engine = 163 new StereoPannerNodeEngine(this, aContext->Destination()); 164 mTrack = AudioNodeTrack::Create( 165 aContext, engine, AudioNodeTrack::NO_TRACK_FLAGS, aContext->Graph()); 166 } 167 168 /* static */ 169 already_AddRefed<StereoPannerNode> StereoPannerNode::Create( 170 AudioContext& aAudioContext, const StereoPannerOptions& aOptions, 171 ErrorResult& aRv) { 172 RefPtr<StereoPannerNode> audioNode = new StereoPannerNode(&aAudioContext); 173 174 audioNode->Initialize(aOptions, aRv); 175 if (NS_WARN_IF(aRv.Failed())) { 176 return nullptr; 177 } 178 179 audioNode->Pan()->SetInitialValue(aOptions.mPan); 180 return audioNode.forget(); 181 } 182 183 size_t StereoPannerNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const { 184 size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf); 185 amount += mPan->SizeOfIncludingThis(aMallocSizeOf); 186 return amount; 187 } 188 189 size_t StereoPannerNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const { 190 return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); 191 } 192 193 JSObject* StereoPannerNode::WrapObject(JSContext* aCx, 194 JS::Handle<JSObject*> aGivenProto) { 195 return StereoPannerNode_Binding::Wrap(aCx, this, aGivenProto); 196 } 197 198 } // namespace mozilla::dom