/*
 * Copyright (C) 2010 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:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 * 2.  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.
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") 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 APPLE AND ITS 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 APPLE OR ITS 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.
 */

// Mac-specific FFTFrame implementation.

#include "build/build_config.h"

#if BUILDFLAG(IS_MAC)

#include "base/compiler_specific.h"
#include "third_party/blink/renderer/platform/audio/fft_frame.h"
#include "third_party/blink/renderer/platform/audio/hrtf_panner.h"
#include "third_party/blink/renderer/platform/audio/vector_math.h"
#include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"

namespace blink {

namespace {

constexpr int kMaxFFTPow2Size = 24;
constexpr int kMinFFTPow2Size = 2;

// Thin wrapper around FFTSetup so we can call the appropriate routines to
// construct or release the FFTSetup objects.
class FFTSetupDatum {
 public:
  explicit FFTSetupDatum(unsigned fft_size);
  ~FFTSetupDatum();
  FFTSetup GetSetup() const { return setup_; }

 private:
  FFTSetup setup_;
};

FFTSetupDatum::FFTSetupDatum(unsigned log2fft_size) {
  // We only need power-of-two sized FFTS, so FFT_RADIX2.
  setup_ = vDSP_create_fftsetup(log2fft_size, FFT_RADIX2);
  CHECK(setup_);
}

FFTSetupDatum::~FFTSetupDatum() {
  CHECK(setup_);

  vDSP_destroy_fftsetup(setup_);
}

Vector<std::unique_ptr<FFTSetupDatum>>& FFTSetups() {
  // TODO(rtoy): Let this bake for a bit and then remove the assertions after
  // we're confident the first call is from the main thread.
  static bool first_call = true;

  if (first_call) {
    // Make sure we construct the fft_setups vector below on the main thread.
    // Once constructed, we can access it from any thread.
    CHECK(IsMainThread());
    first_call = false;
  }

  // A vector to hold all of the possible FFT setups we need.  The setups are
  // initialized lazily.
  DEFINE_THREAD_SAFE_STATIC_LOCAL(Vector<std::unique_ptr<FFTSetupDatum>>,
                                  fft_setups, (kMaxFFTPow2Size));

  return fft_setups;
}

void InitializeFFTSetupForSize(wtf_size_t log2fft_size) {
  auto& setup = FFTSetups();

  if (!setup[log2fft_size]) {
    // Make sure allocation of a new setup only occurs on the main thread so we
    // don't have a race condition with multiple threads trying to write to the
    // same element of the vector.
    CHECK(IsMainThread());

    setup[log2fft_size] = std::make_unique<FFTSetupDatum>(log2fft_size);
  }
}

FFTSetup FftSetupForSize(unsigned log2fft_size) {
  auto& setup = FFTSetups();
  return setup[log2fft_size]->GetSetup();
}

}  // namespace

// Normal constructor: allocates for a given FFT size.
void FFTFrame::PlatformConstruct() {
  CHECK_GE(fft_size_, MinFFTSize());
  CHECK_LE(fft_size_, MaxFFTSize());

  // We only allow power of two
  CHECK_EQ(1UL << log2fft_size_, fft_size_);

  real_data_.Allocate(fft_size_ / 2);
  imag_data_.Allocate(fft_size_ / 2);

  // Initialize the FFT setup here so that it will be ready when we compute
  // FFTs.
  InitializeFFTSetupForSize(log2fft_size_);

  // Get a copy of the setup from the table.
  fft_setup_ = FftSetupForSize(log2fft_size_);

  // Setup frame data.
  frame_.realp = real_data_.Data();
  frame_.imagp = imag_data_.Data();
}

void FFTFrame::PlatformDoFFT(base::span<const float> data_span) {
  vDSP_ctoz(reinterpret_cast<const DSPComplex*>(data_span.data()), 2, &frame_,
            1, fft_size_ / 2);
  vDSP_fft_zrip(fft_setup_, &frame_, 1, log2fft_size_, FFT_FORWARD);

  // vDSP_FFT_zrip returns a result that is twice as large as would be expected.
  // (See
  // https://developer.apple.com/documentation/accelerate/1450150-vdsp_fft_zrip)
  // Compensate for that by scaling the input by half so the FFT has
  // the correct scaling.
  float scale = 0.5f;

  base::span<float> real_span = real_data_.as_span();
  base::span<float> imag_span = imag_data_.as_span();
  vector_math::Vsmul(real_span, scale, real_span, fft_size_ / 2);
  vector_math::Vsmul(imag_span, scale, imag_span, fft_size_ / 2);
}

void FFTFrame::PlatformDoInverseFFT(base::span<float> data_span) {
  vDSP_fft_zrip(fft_setup_, &frame_, 1, log2fft_size_, FFT_INVERSE);
  vDSP_ztoc(&frame_, 1, reinterpret_cast<DSPComplex*>(data_span.data()), 2,
            fft_size_ / 2);

  // Do final scaling so that x == IFFT(FFT(x)).
  float scale = 1.0f / fft_size_;
  vector_math::Vsmul(data_span, scale, data_span, fft_size_);
}

unsigned FFTFrame::PlatformMinFFTSize() {
  return 1u << kMinFFTPow2Size;
}

unsigned FFTFrame::PlatformMaxFFTSize() {
  return 1u << kMaxFFTPow2Size;
}

void FFTFrame::PlatformInitialize(float sample_rate) {
  // Initialize the vector now so it's ready for use when we construct
  // FFTFrames.
  FFTSetups();

  // Determine the order of the convolvers used by the HRTF kernel.  Allocate
  // FFT setups for that size and for half that size.  The HRTF kernel uses half
  // size for analysis FFTs.
  //
  // TODO(rtoy): Try to come up with some way so that |Initialize()| doesn't
  // need to know about how the HRTF panner uses FFTs.
  unsigned hrtf_order = static_cast<unsigned>(
      log2(HRTFPanner::FftSizeForSampleRate(sample_rate)));
  InitializeFFTSetupForSize(hrtf_order);
  InitializeFFTSetupForSize(hrtf_order - 1);
}

void FFTFrame::PlatformCleanup() {
  auto& setups = FFTSetups();

  for (wtf_size_t k = 0; k < setups.size(); ++k) {
    setups[k].reset();
  }
}

}  // namespace blink

#endif  // BUILDFLAG(IS_MAC)
