/*
 * Copyright (C) 2013 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "third_party/blink/renderer/platform/audio/up_sampler.h"

#include <memory>

#include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h"
#include "third_party/blink/renderer/platform/wtf/math_extras.h"
#include "third_party/fdlibm/ieee754.h"

namespace blink {

namespace {

constexpr size_t kKernelSize = 128;

// Computes ideal band-limited filter coefficients to sample in between each
// source sample-frame.  This filter will be used to compute the odd
// sample-frames of the output.
std::unique_ptr<AudioFloatArray> MakeKernel(size_t size) {
  std::unique_ptr<AudioFloatArray> kernel =
      std::make_unique<AudioFloatArray>(size);

  // Blackman window parameters.
  double alpha = 0.16;
  double a0 = 0.5 * (1.0 - alpha);
  double a1 = 0.5;
  double a2 = 0.5 * alpha;

  int n = kernel->size();
  int half_size = n / 2;
  double subsample_offset = -0.5;

  for (int i = 0; i < n; ++i) {
    // Compute the sinc() with offset.
    double s = kPiDouble * (i - half_size - subsample_offset);
    double sinc = !s ? 1.0 : fdlibm::sin(s) / s;

    // Compute Blackman window, matching the offset of the sinc().
    double x = (i - subsample_offset) / n;
    double window = a0 - a1 * fdlibm::cos(kTwoPiDouble * x) +
                    a2 * fdlibm::cos(kTwoPiDouble * 2.0 * x);

    // Window the sinc() function.
    (*kernel)[i] = sinc * window;
  }

  return kernel;
}

}  // namespace

UpSampler::UpSampler(unsigned input_block_size)
    : input_block_size_(input_block_size),
      temp_buffer_(input_block_size),
      input_buffer_(kKernelSize / 2 + input_block_size) {
  std::unique_ptr<AudioFloatArray> convolution_kernel = MakeKernel(kKernelSize);
  if (input_block_size_ <= kKernelSize) {
    // Use direct convolution because SimpleFFTConvolver requires the block size
    // to be at least as large as the kernel size.  Direct convolution is also
    // faster than FFT convolution for small input block sizes.
    direct_convolver_ = std::make_unique<DirectConvolver>(
        input_block_size_, std::move(convolution_kernel));
  } else {
    // Otherwise, use FFT convolution because it is faster than direct
    // convolution for large input block sizes.
    simple_fft_convolver_ = std::make_unique<SimpleFFTConvolver>(
        input_block_size_, *convolution_kernel);
  }
}

void UpSampler::Process(base::span<const float> source,
                        base::span<float> dest) {
  TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("webaudio.audionode"),
               "UpSampler::Process");
  const size_t source_frames_to_process = source.size();
  const size_t convolution_kernel_size =
      direct_convolver_ ? direct_convolver_->ConvolutionKernelSize()
                        : simple_fft_convolver_->ConvolutionKernelSize();

  DCHECK_EQ(source_frames_to_process, input_block_size_);

  DCHECK_EQ(source_frames_to_process, temp_buffer_.size());

  size_t half_kernel_size = convolution_kernel_size / 2;

  DCHECK_EQ(dest.size(), source_frames_to_process * 2);
  DCHECK_EQ(input_buffer_.size(), half_kernel_size + source_frames_to_process);

  base::span<float> input_buffer_span = input_buffer_.as_span();

  // Copy source samples to the end of input buffer.
  input_buffer_span.subspan(half_kernel_size, source_frames_to_process)
      .copy_from(source);

  // Copy even sample-frames 0,2,4,6... (delayed by the linear phase delay)
  // directly into dest.
  base::span<const float> delayed_input =
      input_buffer_span.first(source_frames_to_process);
  for (size_t i = 0; i < source_frames_to_process; ++i) {
    dest[i * 2] = delayed_input[i];
  }

  // Compute odd sample-frames 1,3,5,7...
  base::span<float> odd_samples = temp_buffer_.as_span();
  if (direct_convolver_) {
    direct_convolver_->Process(source, odd_samples);
  } else {
    simple_fft_convolver_->Process(source, odd_samples);
  }

  for (unsigned i = 0; i < source_frames_to_process; ++i) {
    dest[i * 2 + 1] = odd_samples[i];
  }

  // Shift the history buffer.
  input_buffer_span.first(half_kernel_size)
      .copy_from(input_buffer_span.subspan(source_frames_to_process,
                                           half_kernel_size));
}

void UpSampler::Reset() {
  if (direct_convolver_) {
    direct_convolver_->Reset();
  }
  if (simple_fft_convolver_) {
    simple_fft_convolver_->Reset();
  }
  input_buffer_.Zero();
}

size_t UpSampler::LatencyFrames() const {
  const size_t convolution_kernel_size =
      direct_convolver_ ? direct_convolver_->ConvolutionKernelSize()
                        : simple_fft_convolver_->ConvolutionKernelSize();
  // Divide by two since this is a linear phase kernel and the delay is at the
  // center of the kernel.
  return convolution_kernel_size / 2;
}

}  // namespace blink
