// 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.

#ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_WEBAUDIO_AUDIO_HANDLER_H_
#define THIRD_PARTY_BLINK_RENDERER_MODULES_WEBAUDIO_AUDIO_HANDLER_H_

#include <array>

#include "base/metrics/histogram_functions.h"
#include "base/time/time.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_channel_count_mode.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_channel_interpretation.h"
#include "third_party/blink/renderer/modules/modules_export.h"
#include "third_party/blink/renderer/platform/audio/audio_bus.h"
#include "third_party/blink/renderer/platform/heap/collection_support/heap_hash_set.h"
#include "third_party/blink/renderer/platform/heap/persistent.h"
#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
#include "third_party/blink/renderer/platform/wtf/thread_safe_ref_counted.h"
#include "third_party/blink/renderer/platform/wtf/vector.h"

// Higher values produce more debugging output.
#define DEBUG_AUDIONODE_REFERENCES 0

namespace blink {

class BaseAudioContext;
class AudioNode;
class AudioNodeInput;
class AudioNodeOutput;
class DeferredTaskHandler;
class ExceptionState;

// Helper class for UMA reporting of AudioHandler related metrics.
class AudioHandlerUmaReporter {
 public:
  explicit AudioHandlerUmaReporter(const std::string& handler_type_name,
                                   float sample_rate)
      : metric_name_("WebAudio.AudioNode.ProcessTimeRatio." +
                     handler_type_name),
        sample_rate_(sample_rate) {}

  // Records processing duration and frame count for a Process() call.
  // Reports the average processing time ratio to UMA after `kReportingInterval`
  // calls. |duration|: Time taken by the Process() call. |process_frames|:
  // Number of frames processed in the call.
  void AddProcessDuration(base::TimeDelta duration, int process_frames) {
    total_process_duration_ += duration;
    total_process_frames_ += process_frames;
    process_count_++;
    if (process_count_ >= kReportingInterval) {
      ReportAverageRatioAsPercentage();
      total_process_duration_ = base::TimeDelta();
      total_process_frames_ = 0;
      process_count_ = 0;
    }
  }

 private:
  void ReportAverageRatioAsPercentage() {
    float total_render_time = total_process_frames_ / sample_rate_;
    float average_ratio =
        total_process_duration_.InSecondsF() / total_render_time;
    // Report as a percentage (e.g., 0.5 ratio -> 50%).
    int percentage_to_report = static_cast<int>(average_ratio * 100.0);
    base::UmaHistogramExactLinear(metric_name_, percentage_to_report, 101);
  }

  static constexpr int kReportingInterval = 1000;
  std::string metric_name_;
  float sample_rate_;

  // Total number of frames processed over kReportingInterval calls.
  uint32_t total_process_frames_ = 0;
  // Total processing duration accumulated over kReportingInterval calls.
  base::TimeDelta total_process_duration_;
  // Counter for Process() calls, reset every kReportingInterval.
  int process_count_ = 0;
};

class MODULES_EXPORT AudioHandler : public ThreadSafeRefCounted<AudioHandler> {
 public:
  enum class NodeType {
    kNodeTypeUnknown = 0,
    kNodeTypeDestination = 1,
    kNodeTypeOscillator = 2,
    kNodeTypeAudioBufferSource = 3,
    kNodeTypeMediaElementAudioSource = 4,
    kNodeTypeMediaStreamAudioDestination = 5,
    kNodeTypeMediaStreamAudioSource = 6,
    kNodeTypeScriptProcessor = 7,
    kNodeTypeBiquadFilter = 8,
    kNodeTypePanner = 9,
    kNodeTypeStereoPanner = 10,
    kNodeTypeConvolver = 11,
    kNodeTypeDelay = 12,
    kNodeTypeGain = 13,
    kNodeTypeChannelSplitter = 14,
    kNodeTypeChannelMerger = 15,
    kNodeTypeAnalyser = 16,
    kNodeTypeDynamicsCompressor = 17,
    kNodeTypeWaveShaper = 18,
    kNodeTypeIIRFilter = 19,
    kNodeTypeConstantSource = 20,
    kNodeTypeAudioWorklet = 21,
    kNodeTypeEnd = 22
  };

  AudioHandler(NodeType, AudioNode&, float sample_rate);
  virtual ~AudioHandler();
  // dispose() is called when the owner AudioNode is about to be
  // destructed. This must be called in the main thread, and while the graph
  // lock is held.
  // Do not release resources used by an audio rendering thread in dispose().
  virtual void Dispose();

  // GetNode() returns a valid object until the AudioNode is collected on the
  // main thread, and nullptr thereafter. We must not call GetNode() in an audio
  // rendering thread.
  AudioNode* GetNode() const;

  // context() returns a valid object until the BaseAudioContext dies, and
  // returns nullptr otherwise.  This always returns a valid object in an audio
  // rendering thread, and inside dispose().  We must not call context() in the
  // destructor.
  virtual BaseAudioContext* Context() const;
  void ClearContext() { context_ = nullptr; }

  DeferredTaskHandler& GetDeferredTaskHandler() const {
    return *deferred_task_handler_;
  }

  NodeType GetNodeType() const { return node_type_; }
  const char* NodeTypeName() const;

  // This object has been connected to another object. This might have
  // existing connections from others.
  // This function must be called after acquiring a connection reference.
  void MakeConnection();

  // This object will be disconnected from another object. This might have
  // remaining connections from others.  This function must be called before
  // releasing a connection reference.
  //
  // This can be called from main thread or context's audio thread.  It must be
  // called while the context's graph lock is held.
  void BreakConnectionWithLock();

  // The AudioNodeInput(s) (if any) will already have their input data available
  // when process() is called.  Subclasses will take this input data and put the
  // results in the AudioBus(s) of its AudioNodeOutput(s) (if any).
  // Called from context's audio thread.
  virtual void Process(uint32_t frames_to_process) = 0;

  // Like process(), but only causes the automations to process; the
  // normal processing of the node is bypassed.  By default, we assume
  // no AudioParams need to be updated.
  virtual void ProcessOnlyAudioParams(uint32_t frames_to_process) {}

  // No significant resources should be allocated until initialize() is called.
  // Processing may not occur until a node is initialized.
  virtual void Initialize();
  virtual void Uninitialize();

  bool IsInitialized() const { return is_initialized_; }

  unsigned NumberOfInputs() const { return inputs_.size(); }
  unsigned NumberOfOutputs() const { return outputs_.size(); }

  // The argument must be less than numberOfInputs().
  AudioNodeInput& Input(unsigned);
  // The argument must be less than numberOfOutputs().
  AudioNodeOutput& Output(unsigned);
  const AudioNodeOutput& Output(unsigned) const;

  // processIfNecessary() is called by our output(s) when the rendering graph
  // needs this AudioNode to process.  This method ensures that the AudioNode
  // will only process once per rendering time quantum even if it's called
  // repeatedly.  This handles the case of "fanout" where an output is connected
  // to multiple AudioNode inputs.  Called from context's audio thread.
  virtual void ProcessIfNecessary(uint32_t frames_to_process);

  // Called when a new connection has been made to one of our inputs or the
  // connection number of channels has changed.  This potentially gives us
  // enough information to perform a lazy initialization or, if necessary, a
  // re-initialization.  Called from main thread.
  virtual void CheckNumberOfChannelsForInput(AudioNodeInput*);

#if DEBUG_AUDIONODE_REFERENCES
  static void PrintNodeCounts();
#endif
#if DEBUG_AUDIONODE_REFERENCES > 1
  void TailProcessingDebug(String note, bool flag);
  void AddTailProcessingDebug();
  void RemoveTailProcessingDebug(bool disable_outputs);
#endif

  // True if the node has a tail time or latency time that requires
  // special tail processing to behave properly.  Ideally, this can be
  // checked using TailTime and LatencyTime, but these aren't
  // available on the main thread, and the tail processing check can
  // happen on the main thread.
  virtual bool RequiresTailProcessing() const = 0;

  // TailTime() is the length of time (not counting latency time) where
  // non-zero output may occur after continuous silent input.
  virtual double TailTime() const = 0;

  // LatencyTime() is the length of time it takes for non-zero output to
  // appear after non-zero input is provided. This only applies to processing
  // delay which is an artifact of the processing algorithm chosen and is
  // *not* part of the intrinsic desired effect. For example, a "delay" effect
  // is expected to delay the signal, and thus would not be considered
  // latency.
  virtual double LatencyTime() const = 0;

  // PropagatesSilence() should return true if the node will generate silent
  // output when given silent input. By default, AudioNode will take TailTime()
  // and LatencyTime() into account when determining whether the node will
  // propagate silence.
  virtual bool PropagatesSilence() const;
  bool InputsAreSilent() const;
  void SilenceOutputs();
  void UnsilenceOutputs();

  void EnableOutputsIfNecessary();
  void DisableOutputsIfNecessary();

  // Enables the outputs of this node and all downstream nodes iteratively.
  void EnableOutputs();

  // Disables the outputs of this node and all downstream nodes iteratively.
  // This is the entry point for disabling propagation.
  void DisableOutputs();

  unsigned ChannelCount() const;
  virtual void SetChannelCount(unsigned, ExceptionState&);

  V8ChannelCountMode::Enum GetChannelCountMode() const;
  virtual void SetChannelCountMode(V8ChannelCountMode::Enum, ExceptionState&);

  V8ChannelInterpretation::Enum ChannelInterpretation() const;
  virtual void SetChannelInterpretation(V8ChannelInterpretation::Enum,
                                        ExceptionState&);

  V8ChannelCountMode::Enum InternalChannelCountMode() const {
    return channel_count_mode_;
  }
  AudioBus::ChannelInterpretation InternalChannelInterpretation() const {
    return channel_interpretation_;
  }

  void UpdateChannelCountMode();
  void UpdateChannelInterpretation();

  // Called when this node's outputs may have become connected or disconnected
  // to handle automatic pull nodes.
  virtual void UpdatePullStatusIfNeeded() {}

 protected:
  // Inputs and outputs must be created before the AudioHandler is
  // initialized.
  void AddInput();
  void AddOutput(unsigned number_of_channels);

  // Called by processIfNecessary() to cause all parts of the rendering graph
  // connected to us to process.  Each rendering quantum, the audio data for
  // each of the AudioNode's inputs will be available after this method is
  // called.  Called from context's audio thread.
  virtual void PullInputs(uint32_t frames_to_process);

  // Force all inputs to take any channel interpretation changes into account.
  void UpdateChannelsForInputs();

  // Set the (internal) channelCountMode and channelInterpretation
  // accordingly. Use this in the node constructors to set the internal state
  // correctly if the node uses values different from the defaults.
  void SetInternalChannelCountMode(V8ChannelCountMode::Enum);
  void SetInternalChannelInterpretation(AudioBus::ChannelInterpretation);

  // The last time (context time) that his handler ran its Process() method.
  // For each render quantum, we only want to process just once to handle fanout
  // of this handler.
  double last_processing_time_ = -1;

  // The last time (context time) when this node did not have silent inputs.
  double last_non_silent_time_ = 0;

  unsigned channel_count_ = 2;

  // The new channel count mode that will be used to set the actual mode in the
  // pre or post rendering phase.
  V8ChannelCountMode::Enum new_channel_count_mode_;

  // The new channel interpretation that will be used to set the actual
  // interpretation in the pre or post rendering phase.
  AudioBus::ChannelInterpretation new_channel_interpretation_;

  std::unique_ptr<AudioHandlerUmaReporter> uma_reporter_;

 private:
  void SetNodeType(NodeType);

  // https://chromium.googlesource.com/chromium/src/+/refs/heads/main/docs/media/capture/README.md#logs
  void SendLogMessage(const String& function_name, const String& message);

  // Enables this node's outputs and enqueues downstream handlers that might
  // need enabling into `worklist`. Called by `EnableOutputs()`.
  void EnableOutputsInternal(Vector<scoped_refptr<AudioHandler>>& worklist);

  // Disables this node's outputs and enqueues downstream handlers that might
  // need disabling into `worklist`. Called by `DisableOutputs()`.
  void DisableOutputsInternal(Vector<scoped_refptr<AudioHandler>>& worklist);

  bool is_initialized_ = false;
  NodeType node_type_ = NodeType::kNodeTypeUnknown;

  // The owner AudioNode. Accessed only on the main thread.
  const WeakPersistent<AudioNode> node_;

  // This untraced member is safe because this is cleared for all of live
  // AudioHandlers when the BaseAudioContext dies.  Do not access m_context
  // directly, use context() instead.
  // See http://crbug.com/404527 for the detail.
  UntracedMember<BaseAudioContext> context_;

  // Legal to access even when `context_` may be gone, such as during the
  // destructor.
  const scoped_refptr<DeferredTaskHandler> deferred_task_handler_;

  Vector<std::unique_ptr<AudioNodeInput>> inputs_;
  Vector<std::unique_ptr<AudioNodeOutput>> outputs_;

  int connection_ref_count_ = 0;

  bool is_disabled_ = false;

  // Used to trigger one single textlog indicating that processing started as
  // intended. Set to true once in the first call to the ProcessIfNecessary
  // callback.
  bool is_processing_ = false;

#if DEBUG_AUDIONODE_REFERENCES
  static bool is_node_count_initialized_;
  static std::array<int, static_cast<int>(NodeType::kNodeTypeEnd)> node_count_;
#endif

  V8ChannelCountMode::Enum channel_count_mode_;
  AudioBus::ChannelInterpretation channel_interpretation_;
};

}  // namespace blink

#endif  // THIRD_PARTY_BLINK_RENDERER_MODULES_WEBAUDIO_AUDIO_HANDLER_H_
