// Copyright 2022 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/modules/webaudio/analyser_handler.h"

#include "third_party/blink/renderer/modules/webaudio/audio_node_input.h"
#include "third_party/blink/renderer/modules/webaudio/audio_node_output.h"
#include "third_party/blink/renderer/modules/webaudio/base_audio_context.h"
#include "third_party/blink/renderer/platform/bindings/exception_messages.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/wtf/text/strcat.h"

namespace blink {

namespace {

constexpr unsigned kDefaultNumberOfInputChannels = 2;
constexpr unsigned kDefaultNumberOfOutputChannels = 1;

}  // namespace

AnalyserHandler::AnalyserHandler(AudioNode& node, float sample_rate)
    : AudioHandler(NodeType::kNodeTypeAnalyser, node, sample_rate) {
  AddInput();
  channel_count_ = kDefaultNumberOfInputChannels;
  AddOutput(kDefaultNumberOfOutputChannels);

  Initialize();
}

bool AnalyserHandler::InitializeAnalyserBuffers() {
  return analyser_.InitializeBuffers(
      GetDeferredTaskHandler().RenderQuantumFrames());
}

scoped_refptr<AnalyserHandler> AnalyserHandler::Create(AudioNode& node,
                                                       float sample_rate) {
  return base::AdoptRef(new AnalyserHandler(node, sample_rate));
}

AnalyserHandler::~AnalyserHandler() {
  Uninitialize();
}

void AnalyserHandler::Process(uint32_t frames_to_process) {
  DCHECK(Context()->IsAudioThread());

  // It's possible that output is not connected. Assign nullptr to indicate
  // such case.
  AudioBus* output_bus = Output(0).RenderingFanOutCount() > 0
      ? Output(0).Bus() : nullptr;

  if (!IsInitialized() && output_bus) {
    output_bus->Zero();
    return;
  }

  scoped_refptr<AudioBus> input_bus = Input(0).Bus();

  // Give the analyser the audio which is passing through this
  // AudioNode.  This must always be done so that the state of the
  // Analyser reflects the current input.
  analyser_.WriteInput(input_bus.get(), frames_to_process);

  // Subsequent steps require `output_bus` to be valid.
  if (!output_bus) {
    return;
  }

  if (!Input(0).IsConnected()) {
    // No inputs, so clear the output, and propagate the silence hint.
    output_bus->Zero();
    return;
  }

  // For in-place processing, our override of pullInputs() will just pass the
  // audio data through unchanged if the channel count matches from input to
  // output (resulting in inputBus == outputBus). Otherwise, do an up-mix to
  // stereo.
  if (input_bus != output_bus) {
    output_bus->CopyFrom(*input_bus);
  }
}

void AnalyserHandler::SetFftSize(unsigned size,
                                 ExceptionState& exception_state) {
  if (!analyser_.SetFftSize(size)) {
    exception_state.ThrowDOMException(
        DOMExceptionCode::kIndexSizeError,
        (size < RealtimeAnalyser::kMinFFTSize ||
         size > RealtimeAnalyser::kMaxFFTSize)
            ? ExceptionMessages::IndexOutsideRange(
                  "FFT size", size, RealtimeAnalyser::kMinFFTSize,
                  ExceptionMessages::kInclusiveBound,
                  RealtimeAnalyser::kMaxFFTSize,
                  ExceptionMessages::kInclusiveBound)
            : StrCat({"The value provided (", String::Number(size),
                      ") is not a power of two."}));
  }
}

void AnalyserHandler::SetMinDecibels(double k,
                                     ExceptionState& exception_state) {
  if (k < MaxDecibels()) {
    analyser_.SetMinDecibels(k);
  } else {
    exception_state.ThrowDOMException(
        DOMExceptionCode::kIndexSizeError,
        ExceptionMessages::IndexExceedsMaximumBound("minDecibels", k,
                                                    MaxDecibels()));
  }
}

void AnalyserHandler::SetMaxDecibels(double k,
                                     ExceptionState& exception_state) {
  if (k > MinDecibels()) {
    analyser_.SetMaxDecibels(k);
  } else {
    exception_state.ThrowDOMException(
        DOMExceptionCode::kIndexSizeError,
        ExceptionMessages::IndexExceedsMinimumBound("maxDecibels", k,
                                                    MinDecibels()));
  }
}

void AnalyserHandler::SetMinMaxDecibels(double min_decibels,
                                        double max_decibels,
                                        ExceptionState& exception_state) {
  if (min_decibels >= max_decibels) {
    exception_state.ThrowDOMException(
        DOMExceptionCode::kIndexSizeError,
        StrCat({"maxDecibels (", String::Number(max_decibels),
                ") must be greater than or equal to minDecibels ( ",
                String::Number(min_decibels), ")."}));
    return;
  }
  analyser_.SetMinDecibels(min_decibels);
  analyser_.SetMaxDecibels(max_decibels);
}

void AnalyserHandler::SetSmoothingTimeConstant(
    double k,
    ExceptionState& exception_state) {
  if (k >= 0 && k <= 1) {
    analyser_.SetSmoothingTimeConstant(k);
  } else {
    exception_state.ThrowDOMException(
        DOMExceptionCode::kIndexSizeError,
        ExceptionMessages::IndexOutsideRange(
            "smoothing value", k, 0.0, ExceptionMessages::kInclusiveBound, 1.0,
            ExceptionMessages::kInclusiveBound));
  }
}

void AnalyserHandler::UpdatePullStatusIfNeeded() {
  Context()->AssertGraphOwner();

  if (Output(0).IsConnected()) {
    // When an AnalyserHandler is connected to a downstream node, it will get
    // pulled by the downstream node, thus remove it from the context's
    // automatic pull list.
    if (need_automatic_pull_) {
      Context()->GetDeferredTaskHandler().RemoveAutomaticPullNode(this);
      need_automatic_pull_ = false;
    }
  } else {
    unsigned number_of_input_connections =
        Input(0).NumberOfRenderingConnections();
    // When an AnalyserHandler is not connected to any downstream node while
    // still connected from upstream node(s), add it to the context's automatic
    // pull list.
    //
    // But don't remove the AnalyserHandler if there are no inputs connected to
    // the node.  The node needs to be pulled so that the internal state is
    // updated with the correct input signal (of zeroes).
    if (number_of_input_connections && !need_automatic_pull_) {
      Context()->GetDeferredTaskHandler().AddAutomaticPullNode(this);
      need_automatic_pull_ = true;
    }
  }
}

bool AnalyserHandler::RequiresTailProcessing() const {
  // Tail time is always non-zero so tail processing is required.
  return true;
}

double AnalyserHandler::TailTime() const {
  return RealtimeAnalyser::kMaxFFTSize /
         static_cast<double>(Context()->sampleRate());
}

void AnalyserHandler::PullInputs(uint32_t frames_to_process) {
  DCHECK(Context()->IsAudioThread());

  AudioBus* output_bus = Output(0).RenderingFanOutCount() > 0
      ? Output(0).Bus() : nullptr;

  Input(0).Pull(output_bus, frames_to_process);
}

void AnalyserHandler::CheckNumberOfChannelsForInput(AudioNodeInput* input) {
  DCHECK(Context()->IsAudioThread());
  Context()->AssertGraphOwner();

  DCHECK_EQ(input, &Input(0));

  unsigned number_of_channels = input->NumberOfChannels();

  if (number_of_channels != Output(0).NumberOfChannels()) {
    // This will propagate the channel count to any nodes connected further
    // downstream in the graph.
    Output(0).SetNumberOfChannels(number_of_channels);
  }

  AudioHandler::CheckNumberOfChannelsForInput(input);

  Context()->GetDeferredTaskHandler().UpdatePullStatusWithFeatureCheck(this);
}

}  // namespace blink
