blob: 6c931b88ac48e61478ec753b723761f890fb5a1e [file] [log] [blame]
// Copyright 2022 The Chromium Authors. All rights reserved.
// 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 "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 {
constexpr double kDefaultSmoothingConstant = 0.05;
constexpr double kSnapThreshold = 0.001;
// Replace NaN values in `values` with `default_value`.
void HandleNaNValues(float* values,
unsigned number_of_values,
float default_value) {
unsigned k = 0;
#if defined(ARCH_CPU_X86_FAMILY)
if (number_of_values >= 4) {
__m128 defaults = _mm_set1_ps(default_value);
for (k = 0; k < number_of_values; k += 4) {
__m128 v = _mm_loadu_ps(values + 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);
_mm_storeu_ps(values + k, result);
}
}
#elif defined(CPU_ARM_NEON)
if (number_of_values >= 4) {
uint32x4_t defaults =
reinterpret_cast<uint32x4_t>(vdupq_n_f32(default_value));
for (k = 0; k < number_of_values; k += 4) {
float32x4_t v = vld1q_f32(values + 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));
vst1q_f32(values + k, reinterpret_cast<float32x4_t>(result));
}
}
#endif
for (; k < number_of_values; ++k) {
if (std::isnan(values[k])) {
values[k] = 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();
}
timeline_.SetSmoothedValue(default_value);
}
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_ == kParamTypeAudioWorklet);
custom_param_name_ = name;
}
String AudioParamHandler::GetParamName() const {
switch (GetParamType()) {
case kParamTypeAudioBufferSourcePlaybackRate:
return "AudioBufferSource.playbackRate";
case kParamTypeAudioBufferSourceDetune:
return "AudioBufferSource.detune";
case kParamTypeBiquadFilterFrequency:
return "BiquadFilter.frequency";
case kParamTypeBiquadFilterQ:
return "BiquadFilter.Q";
case kParamTypeBiquadFilterGain:
return "BiquadFilter.gain";
case kParamTypeBiquadFilterDetune:
return "BiquadFilter.detune";
case kParamTypeDelayDelayTime:
return "Delay.delayTime";
case kParamTypeDynamicsCompressorThreshold:
return "DynamicsCompressor.threshold";
case kParamTypeDynamicsCompressorKnee:
return "DynamicsCompressor.knee";
case kParamTypeDynamicsCompressorRatio:
return "DynamicsCompressor.ratio";
case kParamTypeDynamicsCompressorAttack:
return "DynamicsCompressor.attack";
case kParamTypeDynamicsCompressorRelease:
return "DynamicsCompressor.release";
case kParamTypeGainGain:
return "Gain.gain";
case kParamTypeOscillatorFrequency:
return "Oscillator.frequency";
case kParamTypeOscillatorDetune:
return "Oscillator.detune";
case kParamTypeStereoPannerPan:
return "StereoPanner.pan";
case kParamTypePannerPositionX:
return "Panner.positionX";
case kParamTypePannerPositionY:
return "Panner.positionY";
case kParamTypePannerPositionZ:
return "Panner.positionZ";
case kParamTypePannerOrientationX:
return "Panner.orientationX";
case kParamTypePannerOrientationY:
return "Panner.orientationY";
case kParamTypePannerOrientationZ:
return "Panner.orientationZ";
case kParamTypeAudioListenerPositionX:
return "AudioListener.positionX";
case kParamTypeAudioListenerPositionY:
return "AudioListener.positionY";
case kParamTypeAudioListenerPositionZ:
return "AudioListener.positionZ";
case kParamTypeAudioListenerForwardX:
return "AudioListener.forwardX";
case kParamTypeAudioListenerForwardY:
return "AudioListener.forwardY";
case kParamTypeAudioListenerForwardZ:
return "AudioListener.forwardZ";
case kParamTypeAudioListenerUpX:
return "AudioListener.upX";
case kParamTypeAudioListenerUpY:
return "AudioListener.upY";
case kParamTypeAudioListenerUpZ:
return "AudioListener.upZ";
case kParamTypeConstantSourceOffset:
return "ConstantSource.offset";
case 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::SmoothedValue() {
return timeline_.SmoothedValue();
}
bool AudioParamHandler::Smooth() {
// If values have been explicitly scheduled on the timeline, then use the
// exact value. Smoothing effectively is performed by the timeline.
auto [use_timeline_value, value] = timeline_.ValueForContextTime(
DestinationHandler(), IntrinsicValue(), MinValue(), MaxValue(),
GetDeferredTaskHandler().RenderQuantumFrames());
float smoothed_value = timeline_.SmoothedValue();
if (smoothed_value == value) {
// Smoothed value has already approached and snapped to value.
SetIntrinsicValue(value);
return true;
}
if (use_timeline_value) {
timeline_.SetSmoothedValue(value);
} else {
// Dezipper - exponential approach.
smoothed_value += (value - smoothed_value) * kDefaultSmoothingConstant;
// If we get close enough then snap to actual value.
// FIXME: the threshold needs to be adjustable depending on range - but
// this is OK general purpose value.
if (fabs(smoothed_value - value) < kSnapThreshold) {
smoothed_value = value;
}
timeline_.SetSmoothedValue(smoothed_value);
}
SetIntrinsicValue(value);
return false;
}
float AudioParamHandler::FinalValue() {
float value = IntrinsicValue();
CalculateFinalValues(&value, 1, false);
return value;
}
void AudioParamHandler::CalculateSampleAccurateValues(
float* values,
unsigned number_of_values) {
DCHECK(GetDeferredTaskHandler().IsAudioThread());
DCHECK(values);
DCHECK_GT(number_of_values, 0u);
CalculateFinalValues(values, number_of_values, IsAudioRate());
}
void AudioParamHandler::CalculateFinalValues(float* values,
unsigned number_of_values,
bool sample_accurate) {
DCHECK(GetDeferredTaskHandler().IsAudioThread());
DCHECK(values);
DCHECK_GT(number_of_values, 0u);
// 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, number_of_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;
}
for (unsigned k = 0; k < number_of_values; ++k) {
values[k] = 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(number_of_values, 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,
sample_accurate ? number_of_values : 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) {
for (unsigned k = 0; k < number_of_values; ++k) {
values[k] = 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, number_of_values, DefaultValue());
}
vector_math::Vclip(values, 1, &min_value, &max_value, values, 1,
number_of_values);
}
}
void AudioParamHandler::CalculateTimelineValues(float* values,
unsigned number_of_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 + number_of_values;
// 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, number_of_values,
sample_rate, sample_rate, MinValue(), MaxValue(),
GetDeferredTaskHandler().RenderQuantumFrames()));
}
} // namespace blink