blob: 0c16348a2cb465573a9e88daddeaad0347b381b4 [file] [log] [blame]
// 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/analyser_handler.h"
#include "third_party/blink/renderer/modules/webaudio/audio_node_input.h"
#include "third_party/blink/renderer/modules/webaudio/audio_node_output.h"
#include "third_party/blink/renderer/modules/webaudio/base_audio_context.h"
#include "third_party/blink/renderer/platform/bindings/exception_messages.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/wtf/text/strcat.h"
namespace blink {
namespace {
constexpr unsigned kDefaultNumberOfInputChannels = 2;
constexpr unsigned kDefaultNumberOfOutputChannels = 1;
} // namespace
AnalyserHandler::AnalyserHandler(AudioNode& node, float sample_rate)
: AudioHandler(NodeType::kNodeTypeAnalyser, node, sample_rate),
analyser_(
node.context()->GetDeferredTaskHandler().RenderQuantumFrames()) {
AddInput();
channel_count_ = kDefaultNumberOfInputChannels;
AddOutput(kDefaultNumberOfOutputChannels);
Initialize();
}
scoped_refptr<AnalyserHandler> AnalyserHandler::Create(AudioNode& node,
float sample_rate) {
return base::AdoptRef(new AnalyserHandler(node, sample_rate));
}
AnalyserHandler::~AnalyserHandler() {
Uninitialize();
}
void AnalyserHandler::Process(uint32_t frames_to_process) {
DCHECK(Context()->IsAudioThread());
// It's possible that output is not connected. Assign nullptr to indicate
// such case.
AudioBus* output_bus = Output(0).RenderingFanOutCount() > 0
? Output(0).Bus() : nullptr;
if (!IsInitialized() && output_bus) {
output_bus->Zero();
return;
}
scoped_refptr<AudioBus> input_bus = Input(0).Bus();
// Give the analyser the audio which is passing through this
// AudioNode. This must always be done so that the state of the
// Analyser reflects the current input.
analyser_.WriteInput(input_bus.get(), frames_to_process);
// Subsequent steps require `output_bus` to be valid.
if (!output_bus) {
return;
}
if (!Input(0).IsConnected()) {
// No inputs, so clear the output, and propagate the silence hint.
output_bus->Zero();
return;
}
// For in-place processing, our override of pullInputs() will just pass the
// audio data through unchanged if the channel count matches from input to
// output (resulting in inputBus == outputBus). Otherwise, do an up-mix to
// stereo.
if (input_bus != output_bus) {
output_bus->CopyFrom(*input_bus);
}
}
void AnalyserHandler::SetFftSize(unsigned size,
ExceptionState& exception_state) {
if (!analyser_.SetFftSize(size)) {
exception_state.ThrowDOMException(
DOMExceptionCode::kIndexSizeError,
(size < RealtimeAnalyser::kMinFFTSize ||
size > RealtimeAnalyser::kMaxFFTSize)
? ExceptionMessages::IndexOutsideRange(
"FFT size", size, RealtimeAnalyser::kMinFFTSize,
ExceptionMessages::kInclusiveBound,
RealtimeAnalyser::kMaxFFTSize,
ExceptionMessages::kInclusiveBound)
: StrCat({"The value provided (", String::Number(size),
") is not a power of two."}));
}
}
void AnalyserHandler::SetMinDecibels(double k,
ExceptionState& exception_state) {
if (k < MaxDecibels()) {
analyser_.SetMinDecibels(k);
} else {
exception_state.ThrowDOMException(
DOMExceptionCode::kIndexSizeError,
ExceptionMessages::IndexExceedsMaximumBound("minDecibels", k,
MaxDecibels()));
}
}
void AnalyserHandler::SetMaxDecibels(double k,
ExceptionState& exception_state) {
if (k > MinDecibels()) {
analyser_.SetMaxDecibels(k);
} else {
exception_state.ThrowDOMException(
DOMExceptionCode::kIndexSizeError,
ExceptionMessages::IndexExceedsMinimumBound("maxDecibels", k,
MinDecibels()));
}
}
void AnalyserHandler::SetMinMaxDecibels(double min_decibels,
double max_decibels,
ExceptionState& exception_state) {
if (min_decibels >= max_decibels) {
exception_state.ThrowDOMException(
DOMExceptionCode::kIndexSizeError,
StrCat({"maxDecibels (", String::Number(max_decibels),
") must be greater than or equal to minDecibels ( ",
String::Number(min_decibels), ")."}));
return;
}
analyser_.SetMinDecibels(min_decibels);
analyser_.SetMaxDecibels(max_decibels);
}
void AnalyserHandler::SetSmoothingTimeConstant(
double k,
ExceptionState& exception_state) {
if (k >= 0 && k <= 1) {
analyser_.SetSmoothingTimeConstant(k);
} else {
exception_state.ThrowDOMException(
DOMExceptionCode::kIndexSizeError,
ExceptionMessages::IndexOutsideRange(
"smoothing value", k, 0.0, ExceptionMessages::kInclusiveBound, 1.0,
ExceptionMessages::kInclusiveBound));
}
}
void AnalyserHandler::UpdatePullStatusIfNeeded() {
Context()->AssertGraphOwner();
if (Output(0).IsConnected()) {
// When an AnalyserHandler is connected to a downstream node, it will get
// pulled by the downstream node, thus remove it from the context's
// automatic pull list.
if (need_automatic_pull_) {
Context()->GetDeferredTaskHandler().RemoveAutomaticPullNode(this);
need_automatic_pull_ = false;
}
} else {
unsigned number_of_input_connections =
Input(0).NumberOfRenderingConnections();
// When an AnalyserHandler is not connected to any downstream node while
// still connected from upstream node(s), add it to the context's automatic
// pull list.
//
// But don't remove the AnalyserHandler if there are no inputs connected to
// the node. The node needs to be pulled so that the internal state is
// updated with the correct input signal (of zeroes).
if (number_of_input_connections && !need_automatic_pull_) {
Context()->GetDeferredTaskHandler().AddAutomaticPullNode(this);
need_automatic_pull_ = true;
}
}
}
bool AnalyserHandler::RequiresTailProcessing() const {
// Tail time is always non-zero so tail processing is required.
return true;
}
double AnalyserHandler::TailTime() const {
return RealtimeAnalyser::kMaxFFTSize /
static_cast<double>(Context()->sampleRate());
}
void AnalyserHandler::PullInputs(uint32_t frames_to_process) {
DCHECK(Context()->IsAudioThread());
AudioBus* output_bus = Output(0).RenderingFanOutCount() > 0
? Output(0).Bus() : nullptr;
Input(0).Pull(output_bus, frames_to_process);
}
void AnalyserHandler::CheckNumberOfChannelsForInput(AudioNodeInput* input) {
DCHECK(Context()->IsAudioThread());
Context()->AssertGraphOwner();
DCHECK_EQ(input, &Input(0));
unsigned number_of_channels = input->NumberOfChannels();
if (number_of_channels != Output(0).NumberOfChannels()) {
// This will propagate the channel count to any nodes connected further
// downstream in the graph.
Output(0).SetNumberOfChannels(number_of_channels);
}
AudioHandler::CheckNumberOfChannelsForInput(input);
Context()->GetDeferredTaskHandler().UpdatePullStatusWithFeatureCheck(this);
}
} // namespace blink