/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef DelayBuffer_h_
#define DelayBuffer_h_

#include "nsTArray.h"
#include "AudioSegment.h"
#include "mozilla/dom/AudioNodeBinding.h" // for ChannelInterpretation

namespace mozilla {

class DelayBuffer final
{
  typedef dom::ChannelInterpretation ChannelInterpretation;

public:
  // See WebAudioUtils::ComputeSmoothingRate() for frame to frame exponential
  // |smoothingRate| multiplier.
  DelayBuffer(double aMaxDelayTicks, double aSmoothingRate)
    : mSmoothingRate(aSmoothingRate)
    , mCurrentDelay(-1.0)
    // Round the maximum delay up to the next tick.
    , mMaxDelayTicks(ceil(aMaxDelayTicks))
    , mCurrentChunk(0)
    // mLastReadChunk is initialized in EnsureBuffer
#ifdef DEBUG
    , mHaveWrittenBlock(false)
#endif
  {
    // The 180 second limit in AudioContext::CreateDelay() and the
    // 1 << MEDIA_TIME_FRAC_BITS limit on sample rate provide a limit on the
    // maximum delay.
    MOZ_ASSERT(aMaxDelayTicks <=
               std::numeric_limits<decltype(mMaxDelayTicks)>::max());
  }

  // Write a WEBAUDIO_BLOCK_SIZE block for aChannelCount channels.
  void Write(const AudioBlock& aInputChunk);

  // Read a block with an array of delays, in ticks, for each sample frame.
  // Each delay should be >= 0 and <= MaxDelayTicks().
  void Read(const double aPerFrameDelays[WEBAUDIO_BLOCK_SIZE],
            AudioBlock* aOutputChunk,
            ChannelInterpretation aChannelInterpretation);
  // Read a block with a constant delay, which will be smoothed with the
  // previous delay.  The delay should be >= 0 and <= MaxDelayTicks().
  void Read(double aDelayTicks, AudioBlock* aOutputChunk,
            ChannelInterpretation aChannelInterpretation);

  // Read into one of the channels of aOutputChunk, given an array of
  // delays in ticks.  This is useful when delays are different on different
  // channels.  aOutputChunk must have already been allocated with at least as
  // many channels as were in any of the blocks passed to Write().
  void ReadChannel(const double aPerFrameDelays[WEBAUDIO_BLOCK_SIZE],
                   AudioBlock* aOutputChunk, uint32_t aChannel,
                   ChannelInterpretation aChannelInterpretation);

  // Advance the buffer pointer
  void NextBlock()
  {
    mCurrentChunk = (mCurrentChunk + 1) % mChunks.Length();
#ifdef DEBUG
    MOZ_ASSERT(mHaveWrittenBlock);
    mHaveWrittenBlock = false;
#endif
  }

  void Reset() {
    mChunks.Clear();
    mCurrentDelay = -1.0;
  };

  int MaxDelayTicks() const { return mMaxDelayTicks; }

  size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const;

private:
  void ReadChannels(const double aPerFrameDelays[WEBAUDIO_BLOCK_SIZE],
                    AudioBlock* aOutputChunk,
                    uint32_t aFirstChannel, uint32_t aNumChannelsToRead,
                    ChannelInterpretation aChannelInterpretation);
  bool EnsureBuffer();
  int PositionForDelay(int aDelay);
  int ChunkForPosition(int aPosition);
  int OffsetForPosition(int aPosition);
  int ChunkForDelay(int aDelay);
  void UpdateUpmixChannels(int aNewReadChunk, uint32_t channelCount,
                           ChannelInterpretation aChannelInterpretation);

  // Circular buffer for capturing delayed samples.
  FallibleTArray<AudioChunk> mChunks;
  // Cache upmixed channel arrays.
  AutoTArray<const float*,GUESS_AUDIO_CHANNELS> mUpmixChannels;
  double mSmoothingRate;
  // Current delay, in fractional ticks
  double mCurrentDelay;
  // Maximum delay, in ticks
  int mMaxDelayTicks;
  // The current position in the circular buffer.  The next write will be to
  // this chunk, and the next read may begin before this chunk.
  int mCurrentChunk;
  // The chunk owning the pointers in mUpmixChannels
  int mLastReadChunk;
#ifdef DEBUG
  bool mHaveWrittenBlock;
#endif
};

} // namespace mozilla

#endif // DelayBuffer_h_
