Line data Source code
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 "DelayBuffer.h"
8 :
9 : #include "mozilla/PodOperations.h"
10 : #include "AudioChannelFormat.h"
11 : #include "AudioNodeEngine.h"
12 :
13 : namespace mozilla {
14 :
15 : size_t
16 0 : DelayBuffer::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
17 : {
18 0 : size_t amount = 0;
19 0 : amount += mChunks.ShallowSizeOfExcludingThis(aMallocSizeOf);
20 0 : for (size_t i = 0; i < mChunks.Length(); i++) {
21 0 : amount += mChunks[i].SizeOfExcludingThis(aMallocSizeOf, false);
22 : }
23 :
24 0 : amount += mUpmixChannels.ShallowSizeOfExcludingThis(aMallocSizeOf);
25 0 : return amount;
26 : }
27 :
28 : void
29 0 : DelayBuffer::Write(const AudioBlock& aInputChunk)
30 : {
31 : // We must have a reference to the buffer if there are channels
32 0 : MOZ_ASSERT(aInputChunk.IsNull() == !aInputChunk.ChannelCount());
33 : #ifdef DEBUG
34 0 : MOZ_ASSERT(!mHaveWrittenBlock);
35 0 : mHaveWrittenBlock = true;
36 : #endif
37 :
38 0 : if (!EnsureBuffer()) {
39 0 : return;
40 : }
41 :
42 0 : if (mCurrentChunk == mLastReadChunk) {
43 0 : mLastReadChunk = -1; // invalidate cache
44 : }
45 0 : mChunks[mCurrentChunk] = aInputChunk.AsAudioChunk();
46 : }
47 :
48 : void
49 0 : DelayBuffer::Read(const double aPerFrameDelays[WEBAUDIO_BLOCK_SIZE],
50 : AudioBlock* aOutputChunk,
51 : ChannelInterpretation aChannelInterpretation)
52 : {
53 0 : int chunkCount = mChunks.Length();
54 0 : if (!chunkCount) {
55 0 : aOutputChunk->SetNull(WEBAUDIO_BLOCK_SIZE);
56 0 : return;
57 : }
58 :
59 : // Find the maximum number of contributing channels to determine the output
60 : // channel count that retains all signal information. Buffered blocks will
61 : // be upmixed if necessary.
62 : //
63 : // First find the range of "delay" offsets backwards from the current
64 : // position. Note that these may be negative for frames that are after the
65 : // current position (including i).
66 0 : double minDelay = aPerFrameDelays[0];
67 0 : double maxDelay = minDelay;
68 0 : for (unsigned i = 1; i < WEBAUDIO_BLOCK_SIZE; ++i) {
69 0 : minDelay = std::min(minDelay, aPerFrameDelays[i] - i);
70 0 : maxDelay = std::max(maxDelay, aPerFrameDelays[i] - i);
71 : }
72 :
73 : // Now find the chunks touched by this range and check their channel counts.
74 0 : int oldestChunk = ChunkForDelay(int(maxDelay) + 1);
75 0 : int youngestChunk = ChunkForDelay(minDelay);
76 :
77 0 : uint32_t channelCount = 0;
78 0 : for (int i = oldestChunk; true; i = (i + 1) % chunkCount) {
79 0 : channelCount = GetAudioChannelsSuperset(channelCount,
80 0 : mChunks[i].ChannelCount());
81 0 : if (i == youngestChunk) {
82 0 : break;
83 : }
84 : }
85 :
86 0 : if (channelCount) {
87 0 : aOutputChunk->AllocateChannels(channelCount);
88 : ReadChannels(aPerFrameDelays, aOutputChunk,
89 0 : 0, channelCount, aChannelInterpretation);
90 : } else {
91 0 : aOutputChunk->SetNull(WEBAUDIO_BLOCK_SIZE);
92 : }
93 :
94 : // Remember currentDelayFrames for the next ProcessBlock call
95 0 : mCurrentDelay = aPerFrameDelays[WEBAUDIO_BLOCK_SIZE - 1];
96 : }
97 :
98 : void
99 0 : DelayBuffer::ReadChannel(const double aPerFrameDelays[WEBAUDIO_BLOCK_SIZE],
100 : AudioBlock* aOutputChunk, uint32_t aChannel,
101 : ChannelInterpretation aChannelInterpretation)
102 : {
103 0 : if (!mChunks.Length()) {
104 0 : float* outputChannel = aOutputChunk->ChannelFloatsForWrite(aChannel);
105 0 : PodZero(outputChannel, WEBAUDIO_BLOCK_SIZE);
106 0 : return;
107 : }
108 :
109 : ReadChannels(aPerFrameDelays, aOutputChunk,
110 0 : aChannel, 1, aChannelInterpretation);
111 : }
112 :
113 : void
114 0 : DelayBuffer::ReadChannels(const double aPerFrameDelays[WEBAUDIO_BLOCK_SIZE],
115 : AudioBlock* aOutputChunk,
116 : uint32_t aFirstChannel, uint32_t aNumChannelsToRead,
117 : ChannelInterpretation aChannelInterpretation)
118 : {
119 0 : uint32_t totalChannelCount = aOutputChunk->ChannelCount();
120 0 : uint32_t readChannelsEnd = aFirstChannel + aNumChannelsToRead;
121 0 : MOZ_ASSERT(readChannelsEnd <= totalChannelCount);
122 :
123 0 : if (mUpmixChannels.Length() != totalChannelCount) {
124 0 : mLastReadChunk = -1; // invalidate cache
125 : }
126 :
127 0 : for (uint32_t channel = aFirstChannel;
128 0 : channel < readChannelsEnd; ++channel) {
129 0 : PodZero(aOutputChunk->ChannelFloatsForWrite(channel), WEBAUDIO_BLOCK_SIZE);
130 : }
131 :
132 0 : for (unsigned i = 0; i < WEBAUDIO_BLOCK_SIZE; ++i) {
133 0 : double currentDelay = aPerFrameDelays[i];
134 0 : MOZ_ASSERT(currentDelay >= 0.0);
135 0 : MOZ_ASSERT(currentDelay <= (mChunks.Length() - 1) * WEBAUDIO_BLOCK_SIZE);
136 :
137 : // Interpolate two input frames in case the read position does not match
138 : // an integer index.
139 : // Use the larger delay, for the older frame, first, as this is more
140 : // likely to use the cached upmixed channel arrays.
141 0 : int floorDelay = int(currentDelay);
142 0 : double interpolationFactor = currentDelay - floorDelay;
143 : int positions[2];
144 0 : positions[1] = PositionForDelay(floorDelay) + i;
145 0 : positions[0] = positions[1] - 1;
146 :
147 0 : for (unsigned tick = 0; tick < ArrayLength(positions); ++tick) {
148 0 : int readChunk = ChunkForPosition(positions[tick]);
149 : // mVolume is not set on default initialized chunks so handle null
150 : // chunks specially.
151 0 : if (!mChunks[readChunk].IsNull()) {
152 0 : int readOffset = OffsetForPosition(positions[tick]);
153 : UpdateUpmixChannels(readChunk, totalChannelCount,
154 0 : aChannelInterpretation);
155 0 : double multiplier = interpolationFactor * mChunks[readChunk].mVolume;
156 0 : for (uint32_t channel = aFirstChannel;
157 0 : channel < readChannelsEnd; ++channel) {
158 0 : aOutputChunk->ChannelFloatsForWrite(channel)[i] += multiplier *
159 0 : mUpmixChannels[channel][readOffset];
160 : }
161 : }
162 :
163 0 : interpolationFactor = 1.0 - interpolationFactor;
164 : }
165 : }
166 0 : }
167 :
168 : void
169 0 : DelayBuffer::Read(double aDelayTicks, AudioBlock* aOutputChunk,
170 : ChannelInterpretation aChannelInterpretation)
171 : {
172 0 : const bool firstTime = mCurrentDelay < 0.0;
173 0 : double currentDelay = firstTime ? aDelayTicks : mCurrentDelay;
174 :
175 : double computedDelay[WEBAUDIO_BLOCK_SIZE];
176 :
177 0 : for (unsigned i = 0; i < WEBAUDIO_BLOCK_SIZE; ++i) {
178 : // If the value has changed, smoothly approach it
179 0 : currentDelay += (aDelayTicks - currentDelay) * mSmoothingRate;
180 0 : computedDelay[i] = currentDelay;
181 : }
182 :
183 0 : Read(computedDelay, aOutputChunk, aChannelInterpretation);
184 0 : }
185 :
186 : bool
187 0 : DelayBuffer::EnsureBuffer()
188 : {
189 0 : if (mChunks.Length() == 0) {
190 : // The length of the buffer is at least one block greater than the maximum
191 : // delay so that writing an input block does not overwrite the block that
192 : // would subsequently be read at maximum delay. Also round up to the next
193 : // block size, so that no block of writes will need to wrap.
194 0 : const int chunkCount = (mMaxDelayTicks + 2 * WEBAUDIO_BLOCK_SIZE - 1) >>
195 0 : WEBAUDIO_BLOCK_SIZE_BITS;
196 0 : if (!mChunks.SetLength(chunkCount, fallible)) {
197 0 : return false;
198 : }
199 :
200 0 : mLastReadChunk = -1;
201 : }
202 0 : return true;
203 : }
204 :
205 : int
206 0 : DelayBuffer::PositionForDelay(int aDelay) {
207 : // Adding mChunks.Length() keeps integers positive for defined and
208 : // appropriate bitshift, remainder, and bitwise operations.
209 0 : return ((mCurrentChunk + mChunks.Length()) * WEBAUDIO_BLOCK_SIZE) - aDelay;
210 : }
211 :
212 : int
213 0 : DelayBuffer::ChunkForPosition(int aPosition)
214 : {
215 0 : MOZ_ASSERT(aPosition >= 0);
216 0 : return (aPosition >> WEBAUDIO_BLOCK_SIZE_BITS) % mChunks.Length();
217 : }
218 :
219 : int
220 0 : DelayBuffer::OffsetForPosition(int aPosition)
221 : {
222 0 : MOZ_ASSERT(aPosition >= 0);
223 0 : return aPosition & (WEBAUDIO_BLOCK_SIZE - 1);
224 : }
225 :
226 : int
227 0 : DelayBuffer::ChunkForDelay(int aDelay)
228 : {
229 0 : return ChunkForPosition(PositionForDelay(aDelay));
230 : }
231 :
232 : void
233 0 : DelayBuffer::UpdateUpmixChannels(int aNewReadChunk, uint32_t aChannelCount,
234 : ChannelInterpretation aChannelInterpretation)
235 : {
236 0 : if (aNewReadChunk == mLastReadChunk) {
237 0 : MOZ_ASSERT(mUpmixChannels.Length() == aChannelCount);
238 0 : return;
239 : }
240 :
241 0 : NS_WARNING_ASSERTION(mHaveWrittenBlock || aNewReadChunk != mCurrentChunk,
242 : "Smoothing is making feedback delay too small.");
243 :
244 0 : mLastReadChunk = aNewReadChunk;
245 0 : mUpmixChannels = mChunks[aNewReadChunk].ChannelData<float>();
246 0 : MOZ_ASSERT(mUpmixChannels.Length() <= aChannelCount);
247 0 : if (mUpmixChannels.Length() < aChannelCount) {
248 0 : if (aChannelInterpretation == ChannelInterpretation::Speakers) {
249 0 : AudioChannelsUpMix(&mUpmixChannels,
250 0 : aChannelCount, SilentChannel::ZeroChannel<float>());
251 0 : MOZ_ASSERT(mUpmixChannels.Length() == aChannelCount,
252 : "We called GetAudioChannelsSuperset to avoid this");
253 : } else {
254 : // Fill up the remaining channels with zeros
255 0 : for (uint32_t channel = mUpmixChannels.Length();
256 0 : channel < aChannelCount; ++channel) {
257 0 : mUpmixChannels.AppendElement(SilentChannel::ZeroChannel<float>());
258 : }
259 : }
260 : }
261 : }
262 :
263 : } // namespace mozilla
|