| /* |
| * Copyright (C) 2010 Google Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
| * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY |
| * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
| * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
| * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
| * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
| * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "third_party/blink/renderer/modules/webaudio/audio_param.h" |
| |
| #include "third_party/blink/renderer/core/inspector/console_message.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/bindings/exception_state.h" |
| #include "third_party/blink/renderer/platform/histogram.h" |
| #include "third_party/blink/renderer/platform/wtf/math_extras.h" |
| |
| namespace blink { |
| |
| const double AudioParamHandler::kDefaultSmoothingConstant = 0.05; |
| const double AudioParamHandler::kSnapThreshold = 0.001; |
| |
| 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) { |
| // The destination MUST exist because we need the destination handler for the |
| // AudioParam. |
| CHECK(context.destination()); |
| |
| destination_handler_ = &context.destination()->GetAudioDestinationHandler(); |
| timeline_.SetSmoothedValue(default_value); |
| } |
| |
| AudioDestinationHandler& AudioParamHandler::DestinationHandler() const { |
| 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()) { |
| bool has_value; |
| float timeline_value = timeline_.ValueForContextTime( |
| DestinationHandler(), v, has_value, MinValue(), MaxValue()); |
| |
| 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_); |
| NoBarrierStore(&intrinsic_value_, new_value); |
| } |
| |
| 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. |
| bool use_timeline_value = false; |
| float value = |
| timeline_.ValueForContextTime(DestinationHandler(), IntrinsicValue(), |
| use_timeline_value, MinValue(), MaxValue()); |
| |
| 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) { |
| bool is_safe = |
| GetDeferredTaskHandler().IsAudioThread() && values && number_of_values; |
| DCHECK(is_safe); |
| if (!is_safe) |
| return; |
| |
| CalculateFinalValues(values, number_of_values, IsAudioRate()); |
| } |
| |
| void AudioParamHandler::CalculateFinalValues(float* values, |
| unsigned number_of_values, |
| bool sample_accurate) { |
| bool is_good = |
| GetDeferredTaskHandler().IsAudioThread() && values && number_of_values; |
| DCHECK(is_good); |
| if (!is_good) |
| return; |
| |
| // 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. |
| bool has_value; |
| float value = IntrinsicValue(); |
| float timeline_value = timeline_.ValueForContextTime( |
| DestinationHandler(), value, has_value, MinValue(), MaxValue()); |
| |
| if (has_value) |
| value = timeline_value; |
| |
| for (unsigned k = 0; k < number_of_values; ++k) { |
| values[k] = value; |
| } |
| SetIntrinsicValue(value); |
| } |
| |
| // Now 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. |
| scoped_refptr<AudioBus> summing_bus = |
| AudioBus::Create(1, number_of_values, false); |
| summing_bus->SetChannelMemory(0, values, number_of_values); |
| |
| for (unsigned i = 0; i < NumberOfRenderingConnections(); ++i) { |
| AudioNodeOutput* output = RenderingOutput(i); |
| DCHECK(output); |
| |
| // Render audio from this output. |
| AudioBus* connection_bus = |
| output->Pull(nullptr, AudioUtilities::kRenderQuantumFrames); |
| |
| // Sum, with unity-gain. |
| summing_bus->SumFrom(*connection_bus); |
| } |
| } |
| |
| void AudioParamHandler::CalculateTimelineValues(float* values, |
| unsigned number_of_values) { |
| // Calculate values for this render quantum. Normally |
| // |numberOfValues| will equal to |
| // AudioUtilities::kRenderQuantumFrames (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())); |
| } |
| |
| void AudioParamHandler::Connect(AudioNodeOutput& output) { |
| DCHECK(GetDeferredTaskHandler().IsGraphOwner()); |
| |
| if (outputs_.Contains(&output)) |
| return; |
| |
| output.AddParam(*this); |
| outputs_.insert(&output); |
| ChangedOutputs(); |
| } |
| |
| void AudioParamHandler::Disconnect(AudioNodeOutput& output) { |
| DCHECK(GetDeferredTaskHandler().IsGraphOwner()); |
| |
| if (outputs_.Contains(&output)) { |
| outputs_.erase(&output); |
| ChangedOutputs(); |
| output.RemoveParam(*this); |
| } |
| } |
| |
| int AudioParamHandler::ComputeQHistogramValue(float new_value) const { |
| // For the Q value, assume a useful range is [0, 25] and that 0.25 dB |
| // resolution is good enough. Then, we can map the floating point Q value (in |
| // dB) to an integer just by multipling by 4 and rounding. |
| new_value = clampTo(new_value, 0.0, 25.0); |
| return static_cast<int>(4 * new_value + 0.5); |
| } |
| |
| // ---------------------------------------------------------------- |
| |
| AudioParam::AudioParam(BaseAudioContext& context, |
| AudioParamType param_type, |
| double default_value, |
| AudioParamHandler::AutomationRate rate, |
| AudioParamHandler::AutomationRateMode rate_mode, |
| float min_value, |
| float max_value) |
| : handler_(AudioParamHandler::Create(context, |
| param_type, |
| default_value, |
| rate, |
| rate_mode, |
| min_value, |
| max_value)), |
| context_(context) {} |
| |
| AudioParam* AudioParam::Create(BaseAudioContext& context, |
| AudioParamType param_type, |
| double default_value) { |
| return new AudioParam(context, param_type, default_value, |
| AudioParamHandler::AutomationRate::kAudio, |
| AudioParamHandler::AutomationRateMode::kVariable, |
| -std::numeric_limits<float>::max(), |
| std::numeric_limits<float>::max()); |
| } |
| |
| AudioParam* AudioParam::Create(BaseAudioContext& context, |
| AudioParamType param_type, |
| double default_value, |
| AudioParamHandler::AutomationRate rate, |
| AudioParamHandler::AutomationRateMode rate_mode, |
| float min_value, |
| float max_value) { |
| DCHECK_LE(min_value, max_value); |
| |
| return new AudioParam(context, param_type, default_value, rate, rate_mode, |
| min_value, max_value); |
| } |
| |
| void AudioParam::Trace(blink::Visitor* visitor) { |
| visitor->Trace(context_); |
| ScriptWrappable::Trace(visitor); |
| } |
| |
| float AudioParam::value() const { |
| return Handler().Value(); |
| } |
| |
| void AudioParam::WarnIfOutsideRange(const String& param_method, float value) { |
| if (value < minValue() || value > maxValue()) { |
| Context()->GetExecutionContext()->AddConsoleMessage(ConsoleMessage::Create( |
| kJSMessageSource, kWarningMessageLevel, |
| Handler().GetParamName() + "." + param_method + " " + |
| String::Number(value) + " outside nominal range [" + |
| String::Number(minValue()) + ", " + String::Number(maxValue()) + |
| "]; value will be clamped.")); |
| } |
| } |
| |
| void AudioParam::setValue(float value) { |
| WarnIfOutsideRange("value", value); |
| Handler().SetValue(value); |
| } |
| |
| void AudioParam::setValue(float value, ExceptionState& exception_state) { |
| WarnIfOutsideRange("value", value); |
| |
| // This is to signal any errors, if necessary, about conflicting |
| // automations. |
| setValueAtTime(value, Context()->currentTime(), exception_state); |
| // This is to change the value so that an immediate query for the |
| // value returns the expected values. |
| Handler().SetValue(value); |
| } |
| |
| float AudioParam::defaultValue() const { |
| return Handler().DefaultValue(); |
| } |
| |
| float AudioParam::minValue() const { |
| return Handler().MinValue(); |
| } |
| |
| float AudioParam::maxValue() const { |
| return Handler().MaxValue(); |
| } |
| |
| void AudioParam::SetParamType(AudioParamType param_type) { |
| Handler().SetParamType(param_type); |
| } |
| |
| void AudioParam::SetCustomParamName(const String name) { |
| Handler().SetCustomParamName(name); |
| } |
| |
| String AudioParam::automationRate() const { |
| switch (Handler().GetAutomationRate()) { |
| case AudioParamHandler::AutomationRate::kAudio: |
| return "a-rate"; |
| case AudioParamHandler::AutomationRate::kControl: |
| return "k-rate"; |
| default: |
| NOTREACHED(); |
| return "a-rate"; |
| } |
| } |
| |
| void AudioParam::setAutomationRate(const String& rate, |
| ExceptionState& exception_state) { |
| if (Handler().IsAutomationRateFixed()) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kInvalidStateError, |
| Handler().GetParamName() + |
| ".automationRate is fixed and cannot be changed to \"" + rate + |
| "\""); |
| return; |
| } |
| |
| if (rate == "a-rate") { |
| Handler().SetAutomationRate(AudioParamHandler::AutomationRate::kAudio); |
| } else if (rate == "k-rate") { |
| Handler().SetAutomationRate(AudioParamHandler::AutomationRate::kControl); |
| } |
| } |
| |
| AudioParam* AudioParam::setValueAtTime(float value, |
| double time, |
| ExceptionState& exception_state) { |
| WarnIfOutsideRange("setValueAtTime value", value); |
| Handler().Timeline().SetValueAtTime(value, time, exception_state); |
| return this; |
| } |
| |
| AudioParam* AudioParam::linearRampToValueAtTime( |
| float value, |
| double time, |
| ExceptionState& exception_state) { |
| WarnIfOutsideRange("linearRampToValueAtTime value", value); |
| Handler().Timeline().LinearRampToValueAtTime( |
| value, time, Handler().IntrinsicValue(), Context()->currentTime(), |
| exception_state); |
| |
| return this; |
| } |
| |
| AudioParam* AudioParam::exponentialRampToValueAtTime( |
| float value, |
| double time, |
| ExceptionState& exception_state) { |
| WarnIfOutsideRange("exponentialRampToValue value", value); |
| Handler().Timeline().ExponentialRampToValueAtTime( |
| value, time, Handler().IntrinsicValue(), Context()->currentTime(), |
| exception_state); |
| |
| return this; |
| } |
| |
| AudioParam* AudioParam::setTargetAtTime(float target, |
| double time, |
| double time_constant, |
| ExceptionState& exception_state) { |
| WarnIfOutsideRange("setTargetAtTime value", target); |
| Handler().Timeline().SetTargetAtTime(target, time, time_constant, |
| exception_state); |
| |
| // Don't update the histogram here. It's not clear in normal usage if the |
| // parameter value will actually reach |target|. |
| return this; |
| } |
| |
| AudioParam* AudioParam::setValueCurveAtTime(const Vector<float>& curve, |
| double time, |
| double duration, |
| ExceptionState& exception_state) { |
| float min = minValue(); |
| float max = maxValue(); |
| |
| // Find the first value in the curve (if any) that is outside the |
| // nominal range. It's probably not necessary to produce a warning |
| // on every value outside the nominal range. |
| for (unsigned k = 0; k < curve.size(); ++k) { |
| float value = curve[k]; |
| |
| if (value < min || value > max) { |
| WarnIfOutsideRange("setValueCurveAtTime value", value); |
| break; |
| } |
| } |
| |
| Handler().Timeline().SetValueCurveAtTime(curve, time, duration, |
| exception_state); |
| |
| // We could update the histogram with every value in the curve, due to |
| // interpolation, we'll probably be missing many values. So we don't update |
| // the histogram. setValueCurveAtTime is probably a fairly rare method |
| // anyway. |
| return this; |
| } |
| |
| AudioParam* AudioParam::cancelScheduledValues(double start_time, |
| ExceptionState& exception_state) { |
| Handler().Timeline().CancelScheduledValues(start_time, exception_state); |
| return this; |
| } |
| |
| AudioParam* AudioParam::cancelAndHoldAtTime(double start_time, |
| ExceptionState& exception_state) { |
| Handler().Timeline().CancelAndHoldAtTime(start_time, exception_state); |
| return this; |
| } |
| |
| } // namespace blink |