// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

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

#include "base/compiler_specific.h"
#include "third_party/blink/renderer/platform/audio/audio_bus.h"
#include "third_party/blink/renderer/platform/audio/audio_utilities.h"
#include "third_party/blink/renderer/platform/wtf/math_extras.h"
#include "third_party/fdlibm/ieee754.h"

namespace blink {

// Implement equal-power panning algorithm for mono or stereo input.
// See: http://webaudio.github.io/web-audio-api/#panning-algorithm

StereoPanner::StereoPanner(float sample_rate) {}

void StereoPanner::PanWithSampleAccurateValues(
    const AudioBus* input_bus,
    AudioBus* output_bus,
    base::span<const float> pan_values) {
  const size_t frames_to_process = pan_values.size();
  DCHECK(input_bus);
  DCHECK_LE(frames_to_process, input_bus->length());
  DCHECK_GE(input_bus->NumberOfChannels(), 1u);
  DCHECK_LE(input_bus->NumberOfChannels(), 2u);

  unsigned number_of_input_channels = input_bus->NumberOfChannels();

  DCHECK(output_bus);
  DCHECK_EQ(output_bus->NumberOfChannels(), 2u);
  DCHECK_LE(frames_to_process, output_bus->length());

  base::span<const float> source_l =
      input_bus->Channel(0)->Span().first(frames_to_process);
  base::span<const float> source_r =
      (number_of_input_channels > 1 ? input_bus->Channel(1)->Span()
                                    : input_bus->Channel(0)->Span())
          .first(frames_to_process);
  base::span<float> destination_l =
      output_bus->ChannelByType(AudioBus::kChannelLeft)
          ->MutableSpan()
          .first(frames_to_process);
  base::span<float> destination_r =
      output_bus->ChannelByType(AudioBus::kChannelRight)
          ->MutableSpan()
          .first(frames_to_process);

  double gain_l, gain_r, pan_radian;

  if (number_of_input_channels == 1) {  // For mono source case.
    for (size_t i = 0; i < frames_to_process; ++i) {
      float input_l = source_l[i];
      double pan = ClampTo(pan_values[i], -1.0, 1.0);
      // Pan from left to right [-1; 1] will be normalized as [0; 1].
      pan_radian = (pan * 0.5 + 0.5) * kPiOverTwoDouble;
      gain_l = fdlibm::cos(pan_radian);
      gain_r = fdlibm::sin(pan_radian);
      destination_l[i] = static_cast<float>(input_l * gain_l);
      destination_r[i] = static_cast<float>(input_l * gain_r);
    }
  } else {  // For stereo source case.
    for (size_t i = 0; i < frames_to_process; ++i) {
      float input_l = source_l[i];
      float input_r = source_r[i];
      double pan = ClampTo(pan_values[i], -1.0, 1.0);
      // Normalize [-1; 0] to [0; 1]. Do nothing when [0; 1].
      pan_radian = (pan <= 0 ? pan + 1 : pan) * kPiOverTwoDouble;
      gain_l = fdlibm::cos(pan_radian);
      gain_r = fdlibm::sin(pan_radian);
      if (pan <= 0) {
        destination_l[i] = static_cast<float>(input_l + input_r * gain_l);
        destination_r[i] = static_cast<float>(input_r * gain_r);
      } else {
        destination_l[i] = static_cast<float>(input_l * gain_l);
        destination_r[i] = static_cast<float>(input_r + input_l * gain_r);
      }
    }
  }
}

void StereoPanner::PanToTargetValue(const AudioBus* input_bus,
                                    AudioBus* output_bus,
                                    float pan_value,
                                    size_t frames_to_process) {
  DCHECK(input_bus);
  DCHECK_LE(frames_to_process, input_bus->length());
  DCHECK_GE(input_bus->NumberOfChannels(), 1u);
  DCHECK_LE(input_bus->NumberOfChannels(), 2u);

  unsigned number_of_input_channels = input_bus->NumberOfChannels();

  DCHECK(output_bus);
  DCHECK_EQ(output_bus->NumberOfChannels(), 2u);
  DCHECK_LE(frames_to_process, output_bus->length());

  base::span<const float> source_l =
      input_bus->Channel(0)->Span().first(frames_to_process);
  base::span<const float> source_r =
      (number_of_input_channels > 1 ? input_bus->Channel(1)->Span()
                                    : input_bus->Channel(0)->Span())
          .first(frames_to_process);
  base::span<float> destination_l =
      output_bus->ChannelByType(AudioBus::kChannelLeft)
          ->MutableSpan()
          .first(frames_to_process);
  base::span<float> destination_r =
      output_bus->ChannelByType(AudioBus::kChannelRight)
          ->MutableSpan()
          .first(frames_to_process);

  float target_pan = ClampTo(pan_value, -1.0, 1.0);

  if (number_of_input_channels == 1) {  // For mono source case.
    // Pan from left to right [-1; 1] will be normalized as [0; 1].
    double pan_radian = (target_pan * 0.5 + 0.5) * kPiOverTwoDouble;

    double gain_l = fdlibm::cos(pan_radian);
    double gain_r = fdlibm::sin(pan_radian);

    // TODO(rtoy): This can be vectorized using vector_math::Vsmul
    for (size_t i = 0; i < frames_to_process; ++i) {
      float input_l = source_l[i];
      destination_l[i] = static_cast<float>(input_l * gain_l);
      destination_r[i] = static_cast<float>(input_l * gain_r);
    }
  } else {  // For stereo source case.
    // Normalize [-1; 0] to [0; 1] for the left pan position (<= 0), and
    // do nothing when [0; 1].
    double pan_radian =
        (target_pan <= 0 ? target_pan + 1 : target_pan) * kPiOverTwoDouble;

    double gain_l = fdlibm::cos(pan_radian);
    double gain_r = fdlibm::sin(pan_radian);

    // TODO(rtoy): Consider moving the if statement outside the loop
    // since |target_pan| is constant inside the loop.
    for (size_t i = 0; i < frames_to_process; ++i) {
      float input_l = source_l[i];
      float input_r = source_r[i];
      if (target_pan <= 0) {
        // When [-1; 0], keep left channel intact and equal-power pan the
        // right channel only.
        destination_l[i] = static_cast<float>(input_l + input_r * gain_l);
        destination_r[i] = static_cast<float>(input_r * gain_r);
      } else {
        // When [0; 1], keep right channel intact and equal-power pan the
        // left channel only.
        destination_l[i] = static_cast<float>(input_l * gain_l);
        destination_r[i] = static_cast<float>(input_r + input_l * gain_r);
      }
    }
  }
}

}  // namespace blink
