// 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_PARAM_HANDLER_H_
#define THIRD_PARTY_BLINK_RENDERER_MODULES_WEBAUDIO_AUDIO_PARAM_HANDLER_H_

#include <sys/types.h>

#include <atomic>
#include <tuple>

#include "base/gtest_prod_util.h"
#include "base/memory/scoped_refptr.h"
#include "base/synchronization/lock.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_automation_rate.h"
#include "third_party/blink/renderer/core/typed_arrays/array_buffer_view_helpers.h"
#include "third_party/blink/renderer/core/typed_arrays/dom_typed_array.h"
#include "third_party/blink/renderer/modules/webaudio/audio_destination_node.h"
#include "third_party/blink/renderer/modules/webaudio/audio_summing_junction.h"
#include "third_party/blink/renderer/modules/webaudio/base_audio_context.h"
#include "third_party/blink/renderer/modules/webaudio/inspector_helper_mixin.h"
#include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
#include "third_party/blink/renderer/platform/wtf/forward.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/threading.h"
#include "third_party/blink/renderer/platform/wtf/vector.h"

namespace blink {

// AudioParamHandler is an actual implementation of web-exposed AudioParam
// interface. Each of AudioParam object creates and owns an AudioParamHandler,
// and it is responsible for all of AudioParam tasks. An AudioParamHandler
// object is owned by the originator AudioParam object, and some audio
// processing classes have additional references. An AudioParamHandler can
// outlive the owner AudioParam, and it never dies before the owner AudioParam
// dies.
//
// Connected to AudioNodeOutput using AudioNodeWiring.
class MODULES_EXPORT AudioParamHandler final
    : public ThreadSafeRefCounted<AudioParamHandler>,
      public AudioSummingJunction {
 public:
  // Each AudioParam gets an identifier here.  This is mostly for instrospection
  // if warnings or other messages need to be printed. It's useful to know what
  // the AudioParam represents.  The name should include the node type and the
  // name of the AudioParam.
  enum class AudioParamType {
    kParamTypeAudioBufferSourcePlaybackRate,
    kParamTypeAudioBufferSourceDetune,
    kParamTypeBiquadFilterFrequency,
    kParamTypeBiquadFilterQ,
    kParamTypeBiquadFilterGain,
    kParamTypeBiquadFilterDetune,
    kParamTypeDelayDelayTime,
    kParamTypeDynamicsCompressorThreshold,
    kParamTypeDynamicsCompressorKnee,
    kParamTypeDynamicsCompressorRatio,
    kParamTypeDynamicsCompressorAttack,
    kParamTypeDynamicsCompressorRelease,
    kParamTypeGainGain,
    kParamTypeOscillatorFrequency,
    kParamTypeOscillatorDetune,
    kParamTypeStereoPannerPan,
    kParamTypePannerPositionX,
    kParamTypePannerPositionY,
    kParamTypePannerPositionZ,
    kParamTypePannerOrientationX,
    kParamTypePannerOrientationY,
    kParamTypePannerOrientationZ,
    kParamTypeAudioListenerPositionX,
    kParamTypeAudioListenerPositionY,
    kParamTypeAudioListenerPositionZ,
    kParamTypeAudioListenerForwardX,
    kParamTypeAudioListenerForwardY,
    kParamTypeAudioListenerForwardZ,
    kParamTypeAudioListenerUpX,
    kParamTypeAudioListenerUpY,
    kParamTypeAudioListenerUpZ,
    kParamTypeConstantSourceOffset,
    kParamTypeAudioWorklet,
  };

  // Indicates whether automation rate can be changed.
  enum class AutomationRateMode {
    // Rate can't be changed after construction
    kFixed,
    // Rate can be selected
    kVariable
  };

  static scoped_refptr<AudioParamHandler> Create(BaseAudioContext& context,
                                                 AudioParamType param_type,
                                                 double default_value,
                                                 V8AutomationRate::Enum rate,
                                                 AutomationRateMode rate_mode,
                                                 float min_value,
                                                 float max_value) {
    return base::AdoptRef(new AudioParamHandler(context, param_type,
                                                default_value, rate, rate_mode,
                                                min_value, max_value));
  }

  // AudioSummingJunction
  void DidUpdate() override {}

  float Value();
  void SetValue(float value);
  V8AutomationRate::Enum GetAutomationRate() const {
    base::AutoLock rate_locker(RateLock());
    return automation_rate_;
  }
  void SetAutomationRate(V8AutomationRate::Enum automation_rate) {
    base::AutoLock rate_locker(RateLock());
    automation_rate_ = automation_rate;
  }
  float DefaultValue() const { return default_value_; }
  float MinValue() const { return min_value_; }
  float MaxValue() const { return max_value_; }
  void SetValueAtTime(float value,
                      double start_time,
                      ExceptionState& exception_state);
  void LinearRampToValueAtTime(float value,
                               double end_time,
                               float initial_value,
                               double call_time,
                               ExceptionState& exception_state);
  void ExponentialRampToValueAtTime(float value,
                                    double end_time,
                                    float initial_value,
                                    double call_time,
                                    ExceptionState& exception_state);
  void SetTargetAtTime(float target,
                       double start_time,
                       double time_constant,
                       ExceptionState& exception_state);
  void SetValueCurveAtTime(const Vector<float>& curve,
                           double start_time,
                           double duration,
                           ExceptionState& exception_state);
  void CancelScheduledValues(double cancel_time,
                             ExceptionState& exception_state);
  void CancelAndHoldAtTime(double cancel_time, ExceptionState& exception_state);

  // Return a nice name for the AudioParam.
  String GetParamName() const;
  // Set the parameter name for an AudioWorklet.
  void SetCustomParamName(const String name);

  // This should be used only in audio rendering thread.
  AudioDestinationHandler& DestinationHandler() const;

  bool IsAutomationRateFixed() const {
    return rate_mode_ == AutomationRateMode::kFixed;
  }

  // Final value for k-rate parameters, otherwise use
  // calculateSampleAccurateValues() for a-rate.
  // Must be called in the audio thread.
  float FinalValue();

  // An AudioParam needs sample accurate processing if there are
  // automations scheduled or if there are connections.
  bool HasSampleAccurateValues() const;

  bool IsAudioRate() const {
    return automation_rate_ == V8AutomationRate::Enum::kARate;
  }

  // Calculates parameter values starting at the context's current time.  Must
  // be called in the context's render thread.
  void CalculateSampleAccurateValues(base::span<float> values);

  float IntrinsicValue() const {
    return intrinsic_value_.load(std::memory_order_relaxed);
  }

  base::Lock& RateLock() const { return rate_lock_; }

 private:
  class MODULES_EXPORT ParamEvent {
   public:
    enum class Type {
      kSetValue,
      kLinearRampToValue,
      kExponentialRampToValue,
      kSetTarget,
      kSetValueCurve,
      // For cancelValuesAndHold
      kCancelValues,
      // Special marker for the end of a `kSetValueCurve` event.
      kSetValueCurveEnd,
      kLastType
    };

    static std::unique_ptr<ParamEvent> CreateLinearRampEvent(
        float value,
        double time,
        float initial_value,
        double call_time);
    static std::unique_ptr<ParamEvent> CreateExponentialRampEvent(
        float value,
        double time,
        float initial_value,
        double call_time);
    static std::unique_ptr<ParamEvent> CreateSetValueEvent(float value,
                                                           double time);
    static std::unique_ptr<ParamEvent>
    CreateSetTargetEvent(float value, double time, double time_constant);
    static std::unique_ptr<ParamEvent> CreateSetValueCurveEvent(
        const Vector<float>& curve,
        double time,
        double duration);
    static std::unique_ptr<ParamEvent> CreateSetValueCurveEndEvent(float value,
                                                                   double time);
    static std::unique_ptr<ParamEvent> CreateCancelValuesEvent(
        double time,
        std::unique_ptr<ParamEvent> saved_event);
    // Needed for creating a saved event where we want to supply all
    // the possible parameters because we're mostly copying an
    // existing event.
    static std::unique_ptr<ParamEvent> CreateGeneralEvent(
        Type,
        float value,
        double time,
        float initial_value,
        double call_time,
        double time_constant,
        double duration,
        Vector<float>& curve,
        double curve_points_per_second,
        float curve_end_value,
        std::unique_ptr<ParamEvent> saved_event);

    static bool EventPrecedes(const std::unique_ptr<ParamEvent>& a,
                              const std::unique_ptr<ParamEvent>& b) {
      return a->Time() < b->Time();
    }

    Type GetType() const { return type_; }
    float Value() const { return value_; }
    double Time() const { return time_; }
    void SetTime(double new_time) { time_ = new_time; }
    double TimeConstant() const { return time_constant_; }
    double Duration() const { return duration_; }
    const Vector<float>& Curve() const { return curve_; }
    Vector<float>& Curve() { return curve_; }
    float InitialValue() const { return initial_value_; }
    double CallTime() const { return call_time_; }

    double CurvePointsPerSecond() const { return curve_points_per_second_; }
    float CurveEndValue() const { return curve_end_value_; }

    // For CancelValues events. Not valid for any other event.
    ParamEvent* SavedEvent() const;
    bool HasDefaultCancelledValue() const;
    void SetCancelledValue(float);

   private:
    // General event
    ParamEvent(Type type,
               float value,
               double time,
               float initial_value,
               double call_time,
               double time_constant,
               double duration,
               const Vector<float>& curve,
               double curve_points_per_second,
               float curve_end_value,
               std::unique_ptr<ParamEvent> saved_event);

    // Create simplest event needing just a value and time, like
    // setValueAtTime.
    ParamEvent(Type, float value, double time);

    // Create a linear or exponential ramp that requires an initial
    // value and time in case there is no actual event that precedes
    // this event.
    ParamEvent(Type,
               float value,
               double time,
               float initial_value,
               double call_time);

    // Create an event needing a time constant (setTargetAtTime)
    ParamEvent(Type, float value, double time, double time_constant);

    // Create a setValueCurve event
    ParamEvent(Type,
               double time,
               double duration,
               const Vector<float>& curve,
               double curve_points_per_second,
               float curve_end_value);

    // Create CancelValues event
    ParamEvent(Type, double time, std::unique_ptr<ParamEvent> saved_event);

    const Type type_;

    // The value for the event.  The interpretation of this depends on
    // the event type. Not used for SetValueCurve. For CancelValues,
    // it is the end value to use when cancelling a LinearRampToValue
    // or ExponentialRampToValue event.
    float value_;

    // The time for the event. The interpretation of this depends on
    // the event type.
    double time_;

    // Initial value and time to use for linear and exponential ramps that
    // don't have a preceding event.
    const float initial_value_;
    const double call_time_;

    // Only used for SetTarget events
    const double time_constant_;

    // The following items are only used for SetValueCurve events.
    //
    // The duration of the curve.
    const double duration_;
    // The array of curve points.
    Vector<float> curve_;
    // The number of curve points per second. it is used to compute
    // the curve index step when running the automation.
    const double curve_points_per_second_;
    // The default value to use at the end of the curve.  Normally
    // it's the last entry in m_curve, but cancelling a SetValueCurve
    // will set this to a new value.
    const float curve_end_value_;

    // For CancelValues. If CancelValues is in the middle of an event, this
    // holds the event that is being cancelled, so that processing can
    // continue as if the event still existed up until we reach the actual
    // scheduled cancel time.
    const std::unique_ptr<ParamEvent> saved_event_;

    // True if a default value has been assigned to the CancelValues event.
    bool has_default_cancelled_value_;
  };

  friend class AudioNodeWiring;

  FRIEND_TEST_ALL_PREFIXES(AudioParamHandlerTest,
                           TimelinePruningOnDisconnectedNode);

  AudioParamHandler(BaseAudioContext&,
                    AudioParamType,
                    double default_value,
                    V8AutomationRate::Enum rate,
                    AutomationRateMode rate_mode,
                    float min,
                    float max);

  // Compute the value from this AudioParamHandler at the current context
  // frame. Returns two values:
  //
  //   bool has_value - to indicate if the value could be computed from the
  //                    timeline
  //   float value    - the timeline value if `has_value` is true; otherwise
  //                    `default_value` is returned.
  std::tuple<bool, float> ValueForContextTime(AudioDestinationHandler&,
                                              float default_value,
                                              float min_value,
                                              float max_value,
                                              unsigned render_quantum_frames);

  // Given the time range in frames, calculates parameter values into the
  // values buffer and returns the last parameter value calculated for
  // "values" or the defaultValue if none were calculated.  controlRate is
  // the rate (number per second) at which parameter values will be
  // calculated. It should equal sampleRate for sample-accurate parameter
  // changes, and otherwise will usually match the render quantum size such
  // that the parameter value changes once per render quantum.
  float ValuesForFrameRange(size_t start_frame,
                            size_t end_frame,
                            float default_value,
                            base::span<float> values,
                            double sample_rate,
                            double control_rate,
                            float min_value,
                            float max_value,
                            unsigned render_quantum_frames);

  // Returns true if the event was inserted, false if an exception occurred and
  // the event was not inserted.
  bool InsertEvent(std::unique_ptr<ParamEvent>, ExceptionState&)
      EXCLUSIVE_LOCKS_REQUIRED(events_lock_);
  float ValuesForFrameRangeImpl(const size_t start_frame,
                                const size_t end_frame,
                                float default_value,
                                base::span<float> values,
                                const double sample_rate,
                                const double control_rate,
                                unsigned render_quantum_frames)
      EXCLUSIVE_LOCKS_REQUIRED(events_lock_);

  // Produce a nice string describing the event in human-readable form.
  String EventToString(const ParamEvent&) const;

  // Handles the special case where the first event in the timeline
  // starts after `start_frame`.  These initial values are filled using
  // `default_value`.  The updated `current_frame` and `write_index` is
  // returned.
  std::tuple<size_t, size_t> HandleFirstEvent(base::span<float> values,
                                              float default_value,
                                              size_t start_frame,
                                              size_t end_frame,
                                              double sample_rate,
                                              size_t current_frame,
                                              size_t write_index)
      EXCLUSIVE_LOCKS_REQUIRED(events_lock_);

  // Return true if `current_event` starts after `current_frame`, but
  // also takes into account the `next_event` if any.
  bool IsEventCurrent(const ParamEvent* current_event,
                      const ParamEvent* next_event,
                      size_t current_frame,
                      double sample_rate) const;

  // Clamp times to current time, if needed for any new events.  Note,
  // this method can mutate `events_`, so do call this only in safe
  // places.
  void ClampNewEventsToCurrentTime(double current_time)
      EXCLUSIVE_LOCKS_REQUIRED(events_lock_);

  // Handle the case where the last event in the timeline is in the
  // past.  Returns false if any event is not in the past. Otherwise,
  // return true and also fill in `values` with `default_value`.
  // `default_value` may be updated with a new value.
  bool HandleAllEventsInThePast(double current_time,
                                double sample_rate,
                                float& default_value,
                                base::span<float> values,
                                unsigned render_quantum_frames)
      EXCLUSIVE_LOCKS_REQUIRED(events_lock_);

  // Handle processing of CancelValue event. If cancellation happens,
  // value2, time2, and nextEventType will be updated with the new value due
  // to cancellation.  Note that `next_event` or its member can be null.
  std::tuple<float, double, ParamEvent::Type> HandleCancelValues(
      const ParamEvent* current_event,
      ParamEvent* next_event,
      float value2,
      double time2) EXCLUSIVE_LOCKS_REQUIRED(events_lock_);

  // Process a SetTarget event and the next event is a
  // LinearRampToValue or ExponentialRampToValue event.  This requires
  // special handling because the ramp should start at whatever value
  // the SetTarget event has reached at this time, instead of using
  // the value of the SetTarget event.
  void ProcessSetTargetFollowedByRamp(wtf_size_t event_index,
                                      ParamEvent*& current_event,
                                      ParamEvent::Type next_event_type,
                                      size_t current_frame,
                                      double sample_rate,
                                      double control_rate,
                                      float& value)
      EXCLUSIVE_LOCKS_REQUIRED(events_lock_);

  // Handle processing of LinearRampEvent, writing the appropriate
  // values to `values`.  Returns the updated `current_frame`, last
  // computed `value`, and the updated `write_index`.
  std::tuple<size_t, float, size_t> ProcessLinearRamp(
      const size_t fill_to_frame,
      const double time1,
      const double time2,
      const float value1,
      const float value2,
      const double sample_rate,
      base::span<float> values,
      size_t current_frame,
      float value,
      size_t write_index);

  // Handle processing of ExponentialRampEvent, writing the appropriate
  // values to `values`.  Returns the updated `current_frame`, last
  // computed `value`, and the updated `write_index`.
  std::tuple<size_t, float, size_t> ProcessExponentialRamp(
      const size_t fill_to_frame,
      const double time1,
      const double time2,
      const float value1,
      const float value2,
      const double sample_rate,
      base::span<float> values,
      size_t current_frame,
      float value,
      size_t write_index);

  // Handle processing of SetTargetEvent, writing the appropriate
  // values to `values`.  Returns the updated `current_frame`, last
  // computed `value`, and the updated `write_index`.
  std::tuple<size_t, float, size_t> ProcessSetTarget(
      const size_t fill_to_frame,
      const double time1,
      const float value1,
      const double sample_rate,
      const double control_rate,
      const size_t fill_to_end_frame,
      const ParamEvent* const event,
      base::span<float> values,
      size_t current_frame,
      float value,
      size_t write_index);

  // Handle processing of SetValueCurveEvent, writing the appropriate
  // values to `values`.  Returns the updated `current_frame`, last
  // computed `value`, and the updated `write_index`.
  std::tuple<size_t, float, size_t> ProcessSetValueCurve(
      size_t fill_to_frame,
      const double time1,
      const double sample_rate,
      const size_t start_frame,
      const size_t end_frame,
      size_t fill_to_end_frame,
      const ParamEvent* const event,
      base::span<float> values,
      size_t current_frame,
      float value,
      size_t write_index);

  // Handle processing of CancelValuesEvent, writing the appropriate
  // values to `values`.  Returns the updated `current_frame`, last
  // computed `value`, and the updated `write_index`.
  std::tuple<size_t, float, size_t> ProcessCancelValues(
      const size_t fill_to_frame,
      const double time1,
      const double sample_rate,
      const double control_rate,
      const size_t fill_to_end_frame,
      const ParamEvent* const event,
      const wtf_size_t event_index,
      base::span<float> values,
      size_t current_frame,
      float value,
      size_t write_index) EXCLUSIVE_LOCKS_REQUIRED(events_lock_);

  // When cancelling events, remove the items from `events_` starting
  // at the given index.
  void RemoveCancelledEvents(wtf_size_t first_event_to_remove)
      EXCLUSIVE_LOCKS_REQUIRED(events_lock_);

  // Remove old events, but always leave at least one event in the timeline.
  // This is needed in case a new event is added (like linearRamp) that
  // would use a previous event to compute the automation.
  void RemoveOldEvents(wtf_size_t n_events)
      EXCLUSIVE_LOCKS_REQUIRED(events_lock_);

  // sampleAccurate corresponds to a-rate (audio rate) vs. k-rate in the Web
  // Audio specification.
  void CalculateFinalValues(base::span<float> values, bool sample_accurate);
  void CalculateTimelineValues(base::span<float> values);

  // Returns time clamped to current time, if needed for any new events.
  double ClampedToCurrentTime(double time);

  // The type of AudioParam, indicating what this AudioParam represents and
  // what node it belongs to.  Mostly for informational purposes and doesn't
  // affect implementation.
  const AudioParamType param_type_;
  // Name of the AudioParam. This is only used for printing out more
  // informative warnings, and only used for AudioWorklets.  All others have a
  // name derived from the `param_type_`.  Worklets need custom names because
  // they're defined by the user.
  String custom_param_name_;

  std::atomic<float> intrinsic_value_;

  const float default_value_;

  // Protects `automation_rate_`.
  mutable base::Lock rate_lock_;

  // The automation rate of the AudioParam (k-rate or a-rate)
  V8AutomationRate::Enum automation_rate_;

  // `rate_mode_` determines if the user can change the automation rate to a
  // different value.
  const AutomationRateMode rate_mode_;

  // Nominal range for the value
  const float min_value_;
  const float max_value_;

  // Vector of all automation events for the AudioParam.
  Vector<std::unique_ptr<ParamEvent>> events_ GUARDED_BY(events_lock_);

  // Vector of raw pointers to the actual ParamEvent that was
  // inserted.  As new events are added, `new_events_` is updated with
  // the new event.  When the timline is processed, these events are
  // clamped to current time by `ClampNewEventsToCurrentTime`. Access
  // must be locked via `events_lock_`.  Must be maintained together
  // with `events_`.
  HashSet<ParamEvent*> new_events_ GUARDED_BY(events_lock_);

  mutable base::Lock events_lock_;

  // The destination node used to get necessary information like the sample
  // rate and context time.
  scoped_refptr<AudioDestinationHandler> destination_handler_;

  // Audio bus to sum in any connections to the AudioParam.
  scoped_refptr<AudioBus> summing_bus_;
};

}  // namespace blink

#endif  // THIRD_PARTY_BLINK_RENDERER_MODULES_WEBAUDIO_AUDIO_PARAM_HANDLER_H_
