File: audio_param_handler.cc

package info (click to toggle)
chromium 138.0.7204.183-1
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 6,071,908 kB
  • sloc: cpp: 34,937,088; ansic: 7,176,967; javascript: 4,110,704; python: 1,419,953; asm: 946,768; xml: 739,971; pascal: 187,324; sh: 89,623; perl: 88,663; objc: 79,944; sql: 50,304; cs: 41,786; fortran: 24,137; makefile: 21,806; php: 13,980; tcl: 13,166; yacc: 8,925; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (327 lines) | stat: -rw-r--r-- 12,767 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
// 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/audio_param_handler.h"

#include <algorithm>

#include "base/containers/span.h"
#include "build/build_config.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.h"
#include "third_party/blink/renderer/core/inspector/console_message.h"
#include "third_party/blink/renderer/modules/webaudio/audio_graph_tracer.h"
#include "third_party/blink/renderer/modules/webaudio/audio_node.h"
#include "third_party/blink/renderer/modules/webaudio/audio_node_output.h"
#include "third_party/blink/renderer/platform/audio/audio_utilities.h"
#include "third_party/blink/renderer/platform/audio/vector_math.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/heap/garbage_collected.h"
#include "third_party/blink/renderer/platform/wtf/math_extras.h"

#if defined(ARCH_CPU_X86_FAMILY)
#include <xmmintrin.h>
#elif defined(CPU_ARM_NEON)
#include <arm_neon.h>
#endif

namespace blink {

namespace {

// Replace NaN values in `values` with `default_value`.
void HandleNaNValues(base::span<float> values, float default_value) {
  unsigned k = 0;
#if defined(ARCH_CPU_X86_FAMILY)
  if (values.size() >= 4) {
    __m128 defaults = _mm_set1_ps(default_value);
    for (k = 0; k < values.size(); k += 4) {
      // SAFETY: The for loop condition has been checked k < values.size().
      __m128 v = _mm_loadu_ps(UNSAFE_BUFFERS(values.data() + k));
      // cmpuord returns all 1's if v is NaN for each elmeent of v.
      __m128 isnan = _mm_cmpunord_ps(v, v);
      // Replace NaN parts with default.
      __m128 result = _mm_and_ps(isnan, defaults);
      // Merge in the parts that aren't NaN
      result = _mm_or_ps(_mm_andnot_ps(isnan, v), result);
      // SAFETY: The for loop condition has been checked k < values.size().
      _mm_storeu_ps(UNSAFE_BUFFERS(values.data() + k), result);
    }
  }
#elif defined(CPU_ARM_NEON)
  if (values.size() >= 4) {
    uint32x4_t defaults =
        reinterpret_cast<uint32x4_t>(vdupq_n_f32(default_value));
    for (k = 0; k < values.size(); k += 4) {
      // SAFETY: The for loop condition has been checked k < values.size().
      float32x4_t v = vld1q_f32(UNSAFE_BUFFERS(values.data() + k));
      // Returns true (all ones) if v is not NaN
      uint32x4_t is_not_nan = vceqq_f32(v, v);
      // Get the parts that are not NaN
      uint32x4_t result =
          vandq_u32(is_not_nan, reinterpret_cast<uint32x4_t>(v));
      // Replace the parts that are NaN with the default and merge with previous
      // result.  (Note: vbic_u32(x, y) = x and not y)
      result = vorrq_u32(result, vbicq_u32(defaults, is_not_nan));
      // SAFETY: The for loop condition has been checked k < values.size().
      vst1q_f32(UNSAFE_BUFFERS(values.data() + k),
                reinterpret_cast<float32x4_t>(result));
    }
  }
#endif

  std::ranges::replace_if(
      values.subspan(k), [](float value) { return std::isnan(value); },
      default_value);
}

}  // namespace

AudioParamHandler::AudioParamHandler(BaseAudioContext& context,
                                     AudioParamType param_type,
                                     double default_value,
                                     AutomationRate rate,
                                     AutomationRateMode rate_mode,
                                     float min_value,
                                     float max_value)
    : AudioSummingJunction(context.GetDeferredTaskHandler()),
      param_type_(param_type),
      intrinsic_value_(default_value),
      default_value_(default_value),
      automation_rate_(rate),
      rate_mode_(rate_mode),
      min_value_(min_value),
      max_value_(max_value),
      summing_bus_(
          AudioBus::Create(1,
                           GetDeferredTaskHandler().RenderQuantumFrames(),
                           false)) {
  // An AudioParam needs the destination handler to run the timeline.  But the
  // destination may have been destroyed (e.g. page gone), so the destination is
  // null.  However, if the destination is gone, the AudioParam will never get
  // pulled, so this is ok.  We have checks for the destination handler existing
  // when the AudioParam want to use it.
  if (context.destination()) {
    destination_handler_ = &context.destination()->GetAudioDestinationHandler();
  }
}

AudioDestinationHandler& AudioParamHandler::DestinationHandler() const {
  CHECK(destination_handler_);
  return *destination_handler_;
}

void AudioParamHandler::SetParamType(AudioParamType param_type) {
  param_type_ = param_type;
}

void AudioParamHandler::SetCustomParamName(const String name) {
  DCHECK(param_type_ == AudioParamType::kParamTypeAudioWorklet);
  custom_param_name_ = name;
}

String AudioParamHandler::GetParamName() const {
  switch (GetParamType()) {
    case AudioParamType::kParamTypeAudioBufferSourcePlaybackRate:
      return "AudioBufferSource.playbackRate";
    case AudioParamType::kParamTypeAudioBufferSourceDetune:
      return "AudioBufferSource.detune";
    case AudioParamType::kParamTypeBiquadFilterFrequency:
      return "BiquadFilter.frequency";
    case AudioParamType::kParamTypeBiquadFilterQ:
      return "BiquadFilter.Q";
    case AudioParamType::kParamTypeBiquadFilterGain:
      return "BiquadFilter.gain";
    case AudioParamType::kParamTypeBiquadFilterDetune:
      return "BiquadFilter.detune";
    case AudioParamType::kParamTypeDelayDelayTime:
      return "Delay.delayTime";
    case AudioParamType::kParamTypeDynamicsCompressorThreshold:
      return "DynamicsCompressor.threshold";
    case AudioParamType::kParamTypeDynamicsCompressorKnee:
      return "DynamicsCompressor.knee";
    case AudioParamType::kParamTypeDynamicsCompressorRatio:
      return "DynamicsCompressor.ratio";
    case AudioParamType::kParamTypeDynamicsCompressorAttack:
      return "DynamicsCompressor.attack";
    case AudioParamType::kParamTypeDynamicsCompressorRelease:
      return "DynamicsCompressor.release";
    case AudioParamType::kParamTypeGainGain:
      return "Gain.gain";
    case AudioParamType::kParamTypeOscillatorFrequency:
      return "Oscillator.frequency";
    case AudioParamType::kParamTypeOscillatorDetune:
      return "Oscillator.detune";
    case AudioParamType::kParamTypeStereoPannerPan:
      return "StereoPanner.pan";
    case AudioParamType::kParamTypePannerPositionX:
      return "Panner.positionX";
    case AudioParamType::kParamTypePannerPositionY:
      return "Panner.positionY";
    case AudioParamType::kParamTypePannerPositionZ:
      return "Panner.positionZ";
    case AudioParamType::kParamTypePannerOrientationX:
      return "Panner.orientationX";
    case AudioParamType::kParamTypePannerOrientationY:
      return "Panner.orientationY";
    case AudioParamType::kParamTypePannerOrientationZ:
      return "Panner.orientationZ";
    case AudioParamType::kParamTypeAudioListenerPositionX:
      return "AudioListener.positionX";
    case AudioParamType::kParamTypeAudioListenerPositionY:
      return "AudioListener.positionY";
    case AudioParamType::kParamTypeAudioListenerPositionZ:
      return "AudioListener.positionZ";
    case AudioParamType::kParamTypeAudioListenerForwardX:
      return "AudioListener.forwardX";
    case AudioParamType::kParamTypeAudioListenerForwardY:
      return "AudioListener.forwardY";
    case AudioParamType::kParamTypeAudioListenerForwardZ:
      return "AudioListener.forwardZ";
    case AudioParamType::kParamTypeAudioListenerUpX:
      return "AudioListener.upX";
    case AudioParamType::kParamTypeAudioListenerUpY:
      return "AudioListener.upY";
    case AudioParamType::kParamTypeAudioListenerUpZ:
      return "AudioListener.upZ";
    case AudioParamType::kParamTypeConstantSourceOffset:
      return "ConstantSource.offset";
    case AudioParamType::kParamTypeAudioWorklet:
      return custom_param_name_;
    default:
      NOTREACHED();
  }
}

float AudioParamHandler::Value() {
  // Update value for timeline.
  float v = IntrinsicValue();
  if (GetDeferredTaskHandler().IsAudioThread()) {
    auto [has_value, timeline_value] = timeline_.ValueForContextTime(
        DestinationHandler(), v, MinValue(), MaxValue(),
        GetDeferredTaskHandler().RenderQuantumFrames());

    if (has_value) {
      v = timeline_value;
    }
  }

  SetIntrinsicValue(v);
  return v;
}

void AudioParamHandler::SetIntrinsicValue(float new_value) {
  new_value = ClampTo(new_value, min_value_, max_value_);
  intrinsic_value_.store(new_value, std::memory_order_relaxed);
}

void AudioParamHandler::SetValue(float value) {
  SetIntrinsicValue(value);
}

float AudioParamHandler::FinalValue() {
  float value = IntrinsicValue();
  CalculateFinalValues(base::span_from_ref(value), false);
  return value;
}

void AudioParamHandler::CalculateSampleAccurateValues(
    base::span<float> values) {
  DCHECK(GetDeferredTaskHandler().IsAudioThread());
  DCHECK(!values.empty());

  CalculateFinalValues(values, IsAudioRate());
}

void AudioParamHandler::CalculateFinalValues(base::span<float> values,
                                             bool sample_accurate) {
  DCHECK(GetDeferredTaskHandler().IsAudioThread());
  DCHECK(!values.empty());

  // The calculated result will be the "intrinsic" value summed with all
  // audio-rate connections.

  if (sample_accurate) {
    // Calculate sample-accurate (a-rate) intrinsic values.
    CalculateTimelineValues(values);
  } else {
    // Calculate control-rate (k-rate) intrinsic value.
    float value = IntrinsicValue();
    auto [has_value, timeline_value] = timeline_.ValueForContextTime(
        DestinationHandler(), value, MinValue(), MaxValue(),
        GetDeferredTaskHandler().RenderQuantumFrames());

    if (has_value) {
      value = timeline_value;
    }

    std::ranges::fill(values, value);
    SetIntrinsicValue(value);
  }

  // If there are any connections, sum all of the audio-rate connections
  // together (unity-gain summing junction).  Note that connections would
  // normally be mono, but we mix down to mono if necessary.
  if (NumberOfRenderingConnections() > 0) {
    DCHECK_LE(values.size(), GetDeferredTaskHandler().RenderQuantumFrames());

    // If we're not sample accurate, we only need one value, so make the summing
    // bus have length 1.  When the connections are added in, only the first
    // value will be added.  Which is exactly what we want.
    summing_bus_->SetChannelMemory(0, values.data(),
                                   sample_accurate ? values.size() : 1);

    for (unsigned i = 0; i < NumberOfRenderingConnections(); ++i) {
      AudioNodeOutput* output = RenderingOutput(i);
      DCHECK(output);

      // Render audio from this output.
      AudioBus* connection_bus =
          output->Pull(nullptr, GetDeferredTaskHandler().RenderQuantumFrames());

      // Sum, with unity-gain.
      summing_bus_->SumFrom(*connection_bus);
    }

    // If we're not sample accurate, duplicate the first element of `values` to
    // all of the elements.
    if (!sample_accurate) {
      std::ranges::fill(values, values[0]);
    }

    float min_value = MinValue();
    float max_value = MaxValue();

    if (NumberOfRenderingConnections() > 0) {
      // AudioParams by themselves don't produce NaN because of the finite min
      // and max values.  But an input to an AudioParam could have NaNs.
      //
      // NaN values in AudioParams must be replaced by the AudioParam's
      // defaultValue.  Then these values must be clamped to lie in the nominal
      // range between the AudioParam's minValue and maxValue.
      //
      // See https://webaudio.github.io/web-audio-api/#computation-of-value.
      HandleNaNValues(values, DefaultValue());
    }

    vector_math::Vclip(values, 1, &min_value, &max_value, values, 1);
  }
}

void AudioParamHandler::CalculateTimelineValues(base::span<float> values) {
  // Calculate values for this render quantum.  Normally
  // `number_of_values` will equal to
  // GetDeferredTaskHandler().RenderQuantumFrames() (the render quantum size).
  double sample_rate = DestinationHandler().SampleRate();
  size_t start_frame = DestinationHandler().CurrentSampleFrame();
  size_t end_frame = start_frame + values.size();

  // Note we're running control rate at the sample-rate.
  // Pass in the current value as default value.
  SetIntrinsicValue(timeline_.ValuesForFrameRange(
      start_frame, end_frame, IntrinsicValue(), values, sample_rate,
      sample_rate, MinValue(), MaxValue(),
      GetDeferredTaskHandler().RenderQuantumFrames()));
}

}  // namespace blink