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 "DelayBuffer.h" michael@0: michael@0: #include "mozilla/PodOperations.h" michael@0: #include "AudioChannelFormat.h" michael@0: #include "AudioNodeEngine.h" michael@0: michael@0: namespace mozilla { michael@0: michael@0: size_t michael@0: DelayBuffer::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const michael@0: { michael@0: size_t amount = 0; michael@0: amount += mChunks.SizeOfExcludingThis(aMallocSizeOf); michael@0: for (size_t i = 0; i < mChunks.Length(); i++) { michael@0: amount += mChunks[i].SizeOfExcludingThis(aMallocSizeOf, false); michael@0: } michael@0: michael@0: amount += mUpmixChannels.SizeOfExcludingThis(aMallocSizeOf); michael@0: return amount; michael@0: } michael@0: michael@0: void michael@0: DelayBuffer::Write(const AudioChunk& aInputChunk) michael@0: { michael@0: // We must have a reference to the buffer if there are channels michael@0: MOZ_ASSERT(aInputChunk.IsNull() == !aInputChunk.mChannelData.Length()); michael@0: #ifdef DEBUG michael@0: MOZ_ASSERT(!mHaveWrittenBlock); michael@0: mHaveWrittenBlock = true; michael@0: #endif michael@0: michael@0: if (!EnsureBuffer()) { michael@0: return; michael@0: } michael@0: michael@0: if (mCurrentChunk == mLastReadChunk) { michael@0: mLastReadChunk = -1; // invalidate cache michael@0: } michael@0: mChunks[mCurrentChunk] = aInputChunk; michael@0: } michael@0: michael@0: void michael@0: DelayBuffer::Read(const double aPerFrameDelays[WEBAUDIO_BLOCK_SIZE], michael@0: AudioChunk* aOutputChunk, michael@0: ChannelInterpretation aChannelInterpretation) michael@0: { michael@0: int chunkCount = mChunks.Length(); michael@0: if (!chunkCount) { michael@0: aOutputChunk->SetNull(WEBAUDIO_BLOCK_SIZE); michael@0: return; michael@0: } michael@0: michael@0: // Find the maximum number of contributing channels to determine the output michael@0: // channel count that retains all signal information. Buffered blocks will michael@0: // be upmixed if necessary. michael@0: // michael@0: // First find the range of "delay" offsets backwards from the current michael@0: // position. Note that these may be negative for frames that are after the michael@0: // current position (including i). michael@0: double minDelay = aPerFrameDelays[0]; michael@0: double maxDelay = minDelay; michael@0: for (unsigned i = 1; i < WEBAUDIO_BLOCK_SIZE; ++i) { michael@0: minDelay = std::min(minDelay, aPerFrameDelays[i] - i); michael@0: maxDelay = std::max(maxDelay, aPerFrameDelays[i] - i); michael@0: } michael@0: michael@0: // Now find the chunks touched by this range and check their channel counts. michael@0: int oldestChunk = ChunkForDelay(int(maxDelay) + 1); michael@0: int youngestChunk = ChunkForDelay(minDelay); michael@0: michael@0: uint32_t channelCount = 0; michael@0: for (int i = oldestChunk; true; i = (i + 1) % chunkCount) { michael@0: channelCount = GetAudioChannelsSuperset(channelCount, michael@0: mChunks[i].ChannelCount()); michael@0: if (i == youngestChunk) { michael@0: break; michael@0: } michael@0: } michael@0: michael@0: if (channelCount) { michael@0: AllocateAudioBlock(channelCount, aOutputChunk); michael@0: ReadChannels(aPerFrameDelays, aOutputChunk, michael@0: 0, channelCount, aChannelInterpretation); michael@0: } else { michael@0: aOutputChunk->SetNull(WEBAUDIO_BLOCK_SIZE); michael@0: } michael@0: michael@0: // Remember currentDelayFrames for the next ProcessBlock call michael@0: mCurrentDelay = aPerFrameDelays[WEBAUDIO_BLOCK_SIZE - 1]; michael@0: } michael@0: michael@0: void michael@0: DelayBuffer::ReadChannel(const double aPerFrameDelays[WEBAUDIO_BLOCK_SIZE], michael@0: const AudioChunk* aOutputChunk, uint32_t aChannel, michael@0: ChannelInterpretation aChannelInterpretation) michael@0: { michael@0: if (!mChunks.Length()) { michael@0: float* outputChannel = static_cast michael@0: (const_cast(aOutputChunk->mChannelData[aChannel])); michael@0: PodZero(outputChannel, WEBAUDIO_BLOCK_SIZE); michael@0: return; michael@0: } michael@0: michael@0: ReadChannels(aPerFrameDelays, aOutputChunk, michael@0: aChannel, 1, aChannelInterpretation); michael@0: } michael@0: michael@0: void michael@0: DelayBuffer::ReadChannels(const double aPerFrameDelays[WEBAUDIO_BLOCK_SIZE], michael@0: const AudioChunk* aOutputChunk, michael@0: uint32_t aFirstChannel, uint32_t aNumChannelsToRead, michael@0: ChannelInterpretation aChannelInterpretation) michael@0: { michael@0: uint32_t totalChannelCount = aOutputChunk->mChannelData.Length(); michael@0: uint32_t readChannelsEnd = aFirstChannel + aNumChannelsToRead; michael@0: MOZ_ASSERT(readChannelsEnd <= totalChannelCount); michael@0: michael@0: if (mUpmixChannels.Length() != totalChannelCount) { michael@0: mLastReadChunk = -1; // invalidate cache michael@0: } michael@0: michael@0: float* const* outputChannels = reinterpret_cast michael@0: (const_cast(aOutputChunk->mChannelData.Elements())); michael@0: for (uint32_t channel = aFirstChannel; michael@0: channel < readChannelsEnd; ++channel) { michael@0: PodZero(outputChannels[channel], WEBAUDIO_BLOCK_SIZE); michael@0: } michael@0: michael@0: for (unsigned i = 0; i < WEBAUDIO_BLOCK_SIZE; ++i) { michael@0: double currentDelay = aPerFrameDelays[i]; michael@0: MOZ_ASSERT(currentDelay >= 0.0); michael@0: MOZ_ASSERT(currentDelay <= (mChunks.Length() - 1) * WEBAUDIO_BLOCK_SIZE); michael@0: michael@0: // Interpolate two input frames in case the read position does not match michael@0: // an integer index. michael@0: // Use the larger delay, for the older frame, first, as this is more michael@0: // likely to use the cached upmixed channel arrays. michael@0: int floorDelay = int(currentDelay); michael@0: double interpolationFactor = currentDelay - floorDelay; michael@0: int positions[2]; michael@0: positions[1] = PositionForDelay(floorDelay) + i; michael@0: positions[0] = positions[1] - 1; michael@0: michael@0: for (unsigned tick = 0; tick < ArrayLength(positions); ++tick) { michael@0: int readChunk = ChunkForPosition(positions[tick]); michael@0: // mVolume is not set on default initialized chunks so handle null michael@0: // chunks specially. michael@0: if (!mChunks[readChunk].IsNull()) { michael@0: int readOffset = OffsetForPosition(positions[tick]); michael@0: UpdateUpmixChannels(readChunk, totalChannelCount, michael@0: aChannelInterpretation); michael@0: double multiplier = interpolationFactor * mChunks[readChunk].mVolume; michael@0: for (uint32_t channel = aFirstChannel; michael@0: channel < readChannelsEnd; ++channel) { michael@0: outputChannels[channel][i] += multiplier * michael@0: static_cast(mUpmixChannels[channel])[readOffset]; michael@0: } michael@0: } michael@0: michael@0: interpolationFactor = 1.0 - interpolationFactor; michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: DelayBuffer::Read(double aDelayTicks, AudioChunk* aOutputChunk, michael@0: ChannelInterpretation aChannelInterpretation) michael@0: { michael@0: const bool firstTime = mCurrentDelay < 0.0; michael@0: double currentDelay = firstTime ? aDelayTicks : mCurrentDelay; michael@0: michael@0: double computedDelay[WEBAUDIO_BLOCK_SIZE]; michael@0: michael@0: for (unsigned i = 0; i < WEBAUDIO_BLOCK_SIZE; ++i) { michael@0: // If the value has changed, smoothly approach it michael@0: currentDelay += (aDelayTicks - currentDelay) * mSmoothingRate; michael@0: computedDelay[i] = currentDelay; michael@0: } michael@0: michael@0: Read(computedDelay, aOutputChunk, aChannelInterpretation); michael@0: } michael@0: michael@0: bool michael@0: DelayBuffer::EnsureBuffer() michael@0: { michael@0: if (mChunks.Length() == 0) { michael@0: // The length of the buffer is at least one block greater than the maximum michael@0: // delay so that writing an input block does not overwrite the block that michael@0: // would subsequently be read at maximum delay. Also round up to the next michael@0: // block size, so that no block of writes will need to wrap. michael@0: const int chunkCount = (mMaxDelayTicks + 2 * WEBAUDIO_BLOCK_SIZE - 1) >> michael@0: WEBAUDIO_BLOCK_SIZE_BITS; michael@0: if (!mChunks.SetLength(chunkCount)) { michael@0: return false; michael@0: } michael@0: michael@0: mLastReadChunk = -1; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: int michael@0: DelayBuffer::PositionForDelay(int aDelay) { michael@0: // Adding mChunks.Length() keeps integers positive for defined and michael@0: // appropriate bitshift, remainder, and bitwise operations. michael@0: return ((mCurrentChunk + mChunks.Length()) * WEBAUDIO_BLOCK_SIZE) - aDelay; michael@0: } michael@0: michael@0: int michael@0: DelayBuffer::ChunkForPosition(int aPosition) michael@0: { michael@0: MOZ_ASSERT(aPosition >= 0); michael@0: return (aPosition >> WEBAUDIO_BLOCK_SIZE_BITS) % mChunks.Length(); michael@0: } michael@0: michael@0: int michael@0: DelayBuffer::OffsetForPosition(int aPosition) michael@0: { michael@0: MOZ_ASSERT(aPosition >= 0); michael@0: return aPosition & (WEBAUDIO_BLOCK_SIZE - 1); michael@0: } michael@0: michael@0: int michael@0: DelayBuffer::ChunkForDelay(int aDelay) michael@0: { michael@0: return ChunkForPosition(PositionForDelay(aDelay)); michael@0: } michael@0: michael@0: void michael@0: DelayBuffer::UpdateUpmixChannels(int aNewReadChunk, uint32_t aChannelCount, michael@0: ChannelInterpretation aChannelInterpretation) michael@0: { michael@0: if (aNewReadChunk == mLastReadChunk) { michael@0: MOZ_ASSERT(mUpmixChannels.Length() == aChannelCount); michael@0: return; michael@0: } michael@0: michael@0: static const float silenceChannel[WEBAUDIO_BLOCK_SIZE] = {}; michael@0: michael@0: NS_WARN_IF_FALSE(mHaveWrittenBlock || aNewReadChunk != mCurrentChunk, michael@0: "Smoothing is making feedback delay too small."); michael@0: michael@0: mLastReadChunk = aNewReadChunk; michael@0: // Missing assignment operator is bug 976927 michael@0: mUpmixChannels.ReplaceElementsAt(0, mUpmixChannels.Length(), michael@0: mChunks[aNewReadChunk].mChannelData); michael@0: MOZ_ASSERT(mUpmixChannels.Length() <= aChannelCount); michael@0: if (mUpmixChannels.Length() < aChannelCount) { michael@0: if (aChannelInterpretation == ChannelInterpretation::Speakers) { michael@0: AudioChannelsUpMix(&mUpmixChannels, aChannelCount, silenceChannel); michael@0: MOZ_ASSERT(mUpmixChannels.Length() == aChannelCount, michael@0: "We called GetAudioChannelsSuperset to avoid this"); michael@0: } else { michael@0: // Fill up the remaining channels with zeros michael@0: for (uint32_t channel = mUpmixChannels.Length(); michael@0: channel < aChannelCount; ++channel) { michael@0: mUpmixChannels.AppendElement(silenceChannel); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: } // mozilla