From 114cd909eb623222fa6ad6c62c0e52287ec70328 Mon Sep 17 00:00:00 2001 From: Noah Bright Date: Wed, 18 Sep 2024 09:09:18 -0400 Subject: [PATCH] LibWeb/WebAudio: Define and partially implement AnalyserNode https://webaudio.github.io/web-audio-api/#AnalyserNode Most of the interface is naively implemented. Container types probably need adjusted (Vector is used for all the processing). A Fourier Transform is needed, but that's waiting on either a 3rd party library or a complex number type. There are lots of simple miscellaneous filters that need to be applied. It could be reasonable to implement from scratch, supposing that it can be parallelized. It might be hard to find one library with everything. Not my call though. Some additional scaffolding around blocks and render quanta is probably needed before this is developed much further, which probably comes in at the level of the AudioNode. Co-authored-by: Tim Ledbetter --- Libraries/LibWeb/CMakeLists.txt | 1 + Libraries/LibWeb/WebAudio/AnalyserNode.cpp | 347 ++++++++++++++++++ Libraries/LibWeb/WebAudio/AnalyserNode.h | 83 +++++ Libraries/LibWeb/WebAudio/AnalyserNode.idl | 25 ++ .../LibWeb/WebAudio/BaseAudioContext.cpp | 7 + Libraries/LibWeb/WebAudio/BaseAudioContext.h | 2 + .../LibWeb/WebAudio/BaseAudioContext.idl | 2 +- Libraries/LibWeb/idl_files.cmake | 1 + .../Text/expected/all-window-properties.txt | 1 + .../ctor-analyser.txt | 83 +++++ .../realtimeanalyser-basic.txt | 18 + .../realtimeanalyser-fft-sizing.txt | 48 +++ .../ctor-analyser.html | 183 +++++++++ .../realtimeanalyser-basic.html | 57 +++ .../realtimeanalyser-fft-sizing.html | 54 +++ 15 files changed, 911 insertions(+), 1 deletion(-) create mode 100644 Libraries/LibWeb/WebAudio/AnalyserNode.cpp create mode 100644 Libraries/LibWeb/WebAudio/AnalyserNode.h create mode 100644 Libraries/LibWeb/WebAudio/AnalyserNode.idl create mode 100644 Tests/LibWeb/Text/expected/wpt-import/webaudio/the-audio-api/the-analysernode-interface/ctor-analyser.txt create mode 100644 Tests/LibWeb/Text/expected/wpt-import/webaudio/the-audio-api/the-analysernode-interface/realtimeanalyser-basic.txt create mode 100644 Tests/LibWeb/Text/expected/wpt-import/webaudio/the-audio-api/the-analysernode-interface/realtimeanalyser-fft-sizing.txt create mode 100644 Tests/LibWeb/Text/input/wpt-import/webaudio/the-audio-api/the-analysernode-interface/ctor-analyser.html create mode 100644 Tests/LibWeb/Text/input/wpt-import/webaudio/the-audio-api/the-analysernode-interface/realtimeanalyser-basic.html create mode 100644 Tests/LibWeb/Text/input/wpt-import/webaudio/the-audio-api/the-analysernode-interface/realtimeanalyser-fft-sizing.html diff --git a/Libraries/LibWeb/CMakeLists.txt b/Libraries/LibWeb/CMakeLists.txt index 96a9cd5e0d563..cfc4f56ec2e02 100644 --- a/Libraries/LibWeb/CMakeLists.txt +++ b/Libraries/LibWeb/CMakeLists.txt @@ -786,6 +786,7 @@ set(SOURCES WebAssembly/Module.cpp WebAssembly/Table.cpp WebAssembly/WebAssembly.cpp + WebAudio/AnalyserNode.cpp WebAudio/AudioBuffer.cpp WebAudio/AudioBufferSourceNode.cpp WebAudio/AudioContext.cpp diff --git a/Libraries/LibWeb/WebAudio/AnalyserNode.cpp b/Libraries/LibWeb/WebAudio/AnalyserNode.cpp new file mode 100644 index 0000000000000..5c15ce4bf96dd --- /dev/null +++ b/Libraries/LibWeb/WebAudio/AnalyserNode.cpp @@ -0,0 +1,347 @@ +/* + * Copyright (c) 2024, Noah Bright + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace Web::WebAudio { + +GC_DEFINE_ALLOCATOR(AnalyserNode); + +AnalyserNode::AnalyserNode(JS::Realm& realm, GC::Ref context, AnalyserOptions const& options) + : AudioNode(realm, context) + , m_fft_size(options.fft_size) + , m_max_decibels(options.max_decibels) + , m_min_decibels(options.min_decibels) + , m_smoothing_time_constant(options.smoothing_time_constant) +{ +} + +AnalyserNode::~AnalyserNode() = default; + +WebIDL::ExceptionOr> AnalyserNode::create(JS::Realm& realm, GC::Ref context, AnalyserOptions const& options) +{ + return construct_impl(realm, context, options); +} + +// https://webaudio.github.io/web-audio-api/#current-time-domain-data +Vector AnalyserNode::current_time_domain_data() +{ + dbgln("FIXME: Analyser node: implement current time domain data"); + // The input signal must be down-mixed to mono as if channelCount is 1, channelCountMode is "max" and channelInterpretation is "speakers". + // This is independent of the settings for the AnalyserNode itself. + // The most recent fftSize frames are used for the down-mixing operation. + + // FIXME: definition of "input signal" above unclear + // need to implement up/down mixing somewhere + // https://webaudio.github.io/web-audio-api/#channel-up-mixing-and-down-mixing + Vector ret; + ret.ensure_capacity(m_fft_size); + return ret; +} + +// https://webaudio.github.io/web-audio-api/#blackman-window +Vector AnalyserNode::apply_a_blackman_window(Vector const& x) const +{ + f32 const a = 0.16; + f32 const a0 = 0.5f * (1 - a); + f32 const a1 = 0.5; + f32 const a2 = a * 0.5f; + unsigned long const N = m_fft_size; + + auto w = [&](unsigned long n) { + return a0 - a1 * cos(2 * AK::Pi * (f32)n / (f32)N) + a2 * cos(4 * AK::Pi * (f32)n / (f32)N); + }; + + Vector x_hat; + x_hat.ensure_capacity(m_fft_size); + + // FIXME: Naive + for (unsigned long i = 0; i < m_fft_size; i++) { + x_hat[i] = x[i] * w(i); + }; + + return x_hat; +} + +// https://webaudio.github.io/web-audio-api/#fourier-transform +static Vector apply_a_fourier_transform(Vector const& input) +{ + dbgln("FIXME: Analyser node: implement apply a fourier transform"); + auto ret = Vector(); + ret.ensure_capacity(input.size()); + return ret; +} + +// https://webaudio.github.io/web-audio-api/#smoothing-over-time +Vector AnalyserNode::smoothing_over_time(Vector const& current_block) +{ + auto& X_hat_prev = m_previous_block; + auto tau = m_smoothing_time_constant; + + auto X = apply_a_fourier_transform(current_block); + + // FIXME: Naive + Vector X_hat; + X_hat.ensure_capacity(m_fft_size); + for (unsigned long i = 0; i < m_fft_size; i++) { + // FIMXE: Complex modulus on X[i] + X_hat[i] = tau * X_hat_prev[i] + (1 - tau) * abs(X[i]); + } + + m_previous_block = X_hat; + + return X_hat; +} + +// https://webaudio.github.io/web-audio-api/#conversion-to-db +Vector AnalyserNode::conversion_to_dB(Vector const& X_hat) const +{ + Vector Y; + Y.ensure_capacity(X_hat.size()); + // FIXME: Naive + for (size_t i = 0; i < X_hat.size(); i++) + Y[i] = 20.0f * AK::log(X_hat[i]); + + return Y; +} + +// https://webaudio.github.io/web-audio-api/#current-frequency-data +Vector AnalyserNode::current_frequency_data() +{ + // 1. Compute the current time-domain data. + auto current_time_domain_dat = current_time_domain_data(); + + // 2. Apply a Blackman window to the time domain input data. + auto blackman_windowed_input = apply_a_blackman_window(current_time_domain_dat); + + // 3. Apply a Fourier transform to the windowed time domain input data to get real and imaginary frequency data. + auto frequency_domain_dat = apply_a_fourier_transform(blackman_windowed_input); + + // 4. Smooth over time the frequency domain data. + auto smoothed_data = smoothing_over_time(frequency_domain_dat); + + // 5. Convert to dB. + return conversion_to_dB(smoothed_data); +} + +// https://webaudio.github.io/web-audio-api/#dom-analysernode-getfloatfrequencydata +WebIDL::ExceptionOr AnalyserNode::get_float_frequency_data(GC::Root const& array) +{ + + // Write the current frequency data into array. If array has fewer elements than the frequencyBinCount, + // the excess elements will be dropped. If array has more elements than the frequencyBinCount, the + // excess elements will be ignored. The most recent fftSize frames are used in computing the frequency data. + auto const frequency_data = current_frequency_data(); + + // FIXME: If another call to getFloatFrequencyData() or getByteFrequencyData() occurs within the same render + // quantum as a previous call, the current frequency data is not updated with the same data. Instead, the + // previously computed data is returned. + + auto& vm = this->vm(); + + if (!is(*array->raw_object())) + return vm.throw_completion(JS::ErrorType::NotAnObjectOfType, "Float32Array"); + auto& output_array = static_cast(*array->raw_object()); + + size_t floats_to_write = min(output_array.data().size(), frequency_bin_count()); + for (size_t i = 0; i < floats_to_write; i++) { + output_array.data()[i] = frequency_data[i]; + } + + return {}; +} + +// https://webaudio.github.io/web-audio-api/#dom-analysernode-getbytefrequencydata +WebIDL::ExceptionOr AnalyserNode::get_byte_frequency_data(GC::Root const& array) +{ + // FIXME: If another call to getByteFrequencyData() or getFloatFrequencyData() occurs within the same render + // quantum as a previous call, the current frequency data is not updated with the same data. Instead, + // the previously computed data is returned. + // Need to implement some kind of blocking mechanism, I guess + // Might be more obvious how to handle this when render quantua have some + // more scaffolding + // + + // current_frequency_data returns a vector of size m_fftSize + // FIXME: Ensure sizes are correct after the fourier transform is implemented + // Spec says to write frequencyBinCount bytes, not fftSize + Vector dB_data = current_frequency_data(); + Vector byte_data; + byte_data.ensure_capacity(dB_data.size()); + + // For getByteFrequencyData(), the 𝑌[𝑘] is clipped to lie between minDecibels and maxDecibels + // and then scaled to fit in an unsigned byte such that minDecibels is represented by the + // value 0 and maxDecibels is represented by the value 255. + // FIXME: Naive + f32 delta_dB = m_max_decibels - m_min_decibels; + for (size_t i = 0; i < dB_data.size(); i++) { + auto x = dB_data[i]; + x = max(x, m_min_decibels); + x = min(x, m_max_decibels); + + byte_data[i] = static_cast(255 * (x - m_min_decibels) / delta_dB); + } + + // Write the current frequency data into array. If array’s byte length is less than frequencyBinCount, + // the excess elements will be dropped. If array’s byte length is greater than the frequencyBinCount , + // the excess elements will be ignored. The most recent fftSize frames are used in computing the frequency data. + auto& output_buffer = array->viewed_array_buffer()->buffer(); + size_t bytes_to_write = min(array->byte_length(), frequency_bin_count()); + + for (size_t i = 0; i < bytes_to_write; i++) + output_buffer[i] = byte_data[i]; + + return {}; +} + +// https://webaudio.github.io/web-audio-api/#dom-analysernode-getfloattimedomaindata +WebIDL::ExceptionOr AnalyserNode::get_float_time_domain_data(GC::Root const& array) +{ + // Write the current time-domain data (waveform data) into array. If array has fewer elements than the + // value of fftSize, the excess elements will be dropped. If array has more elements than the value of + // fftSize, the excess elements will be ignored. The most recent fftSize frames are written (after downmixing). + + Vector time_domain_data = current_time_domain_data(); + + auto& vm = this->vm(); + + if (!is(*array->raw_object())) + return vm.throw_completion(JS::ErrorType::NotAnObjectOfType, "Float32Array"); + auto& output_array = static_cast(*array->raw_object()); + + size_t floats_to_write = min(output_array.data().size(), frequency_bin_count()); + for (size_t i = 0; i < floats_to_write; i++) { + output_array.data()[i] = time_domain_data[i]; + } + + return {}; +} + +// https://webaudio.github.io/web-audio-api/#dom-analysernode-getbytetimedomaindata +WebIDL::ExceptionOr AnalyserNode::get_byte_time_domain_data(GC::Root const& array) +{ + // Write the current time-domain data (waveform data) into array. If array’s byte length is less than + // fftSize, the excess elements will be dropped. If array’s byte length is greater than the fftSize, + // the excess elements will be ignored. The most recent fftSize frames are used in computing the byte data. + + Vector time_domain_data = current_time_domain_data(); + + Vector byte_data; + byte_data.ensure_capacity(m_fft_size); + + // FIXME: Naive + for (size_t i = 0; i < m_fft_size; i++) { + auto x = 128 * (1 + time_domain_data[i]); + + x = max(x, 0); + + x = min(x, 255); + + byte_data[i] = static_cast(x); + } + + auto& output_buffer = array->viewed_array_buffer()->buffer(); + size_t bytes_to_write = min(array->byte_length(), fft_size()); + + for (size_t i = 0; i < bytes_to_write; i++) + output_buffer[i] = byte_data[i]; + + return {}; +} + +// https://webaudio.github.io/web-audio-api/#dom-analysernode-fftsize +WebIDL::ExceptionOr AnalyserNode::set_fft_size(unsigned long fft_size) +{ + if (fft_size < 32 || fft_size > 32768 || (fft_size & (fft_size - 1)) != 0) + return WebIDL::IndexSizeError::create(realm(), "Analyser node fftSize not a power of 2 between 32 and 32768"_string); + + // reset previous block to 0s + m_previous_block = Vector(); + m_previous_block.ensure_capacity(fft_size); + + m_fft_size = fft_size; + + // FIXME: Check this: + // Note that increasing fftSize does mean that the current time-domain data must be expanded + // to include past frames that it previously did not. This means that the AnalyserNode + // effectively MUST keep around the last 32768 sample-frames and the current time-domain + // data is the most recent fftSize sample-frames out of that. + return {}; +} + +WebIDL::ExceptionOr AnalyserNode::set_max_decibels(double max_decibels) +{ + if (m_min_decibels >= max_decibels) + return WebIDL::IndexSizeError::create(realm(), "Analyser node minDecibels greater than maxDecibels"_string); + m_max_decibels = max_decibels; + return {}; +} + +WebIDL::ExceptionOr AnalyserNode::set_min_decibels(double min_decibels) +{ + if (min_decibels >= m_max_decibels) + return WebIDL::IndexSizeError::create(realm(), "Analyser node minDecibels greater than maxDecibels"_string); + + m_min_decibels = min_decibels; + return {}; +} + +WebIDL::ExceptionOr AnalyserNode::set_smoothing_time_constant(double smoothing_time_constant) +{ + if (smoothing_time_constant > 1.0 || smoothing_time_constant < 0.0) + return WebIDL::IndexSizeError::create(realm(), "Analyser node smoothingTimeConstant not between 0.0 and 1.0"_string); + + m_smoothing_time_constant = smoothing_time_constant; + return {}; +} + +WebIDL::ExceptionOr> AnalyserNode::construct_impl(JS::Realm& realm, GC::Ref context, AnalyserOptions const& options) +{ + if (options.fft_size < 32 || options.fft_size > 32768 || !is_power_of_two(options.fft_size)) + return WebIDL::IndexSizeError::create(realm, "Analyser node fftSize not a power of 2 between 32 and 32768"_string); + + if (options.min_decibels >= options.max_decibels) + return WebIDL::IndexSizeError::create(realm, "Analyser node minDecibels greater than maxDecibels"_string); + + if (options.smoothing_time_constant > 1.0 || options.smoothing_time_constant < 0.0) + return WebIDL::IndexSizeError::create(realm, "Analyser node smoothingTimeConstant not between 0.0 and 1.0"_string); + + // When the constructor is called with a BaseAudioContext c and an option object option, the user agent + // MUST initialize the AudioNode this, with context and options as arguments. + + auto node = realm.create(realm, context, options); + + // Default options for channel count and interpretation + // https://webaudio.github.io/web-audio-api/#AnalyserNode + AudioNodeDefaultOptions default_options; + default_options.channel_count_mode = Bindings::ChannelCountMode::Max; + default_options.channel_interpretation = Bindings::ChannelInterpretation::Speakers; + default_options.channel_count = 2; + // FIXME: Set tail-time to no + + TRY(node->initialize_audio_node_options(options, default_options)); + + return node; +} + +void AnalyserNode::initialize(JS::Realm& realm) +{ + Base::initialize(realm); + WEB_SET_PROTOTYPE_FOR_INTERFACE(AnalyserNode); +} + +} diff --git a/Libraries/LibWeb/WebAudio/AnalyserNode.h b/Libraries/LibWeb/WebAudio/AnalyserNode.h new file mode 100644 index 0000000000000..bd9233259d3e9 --- /dev/null +++ b/Libraries/LibWeb/WebAudio/AnalyserNode.h @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2024, Noah Bright + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include + +namespace Web::WebAudio { + +// https://webaudio.github.io/web-audio-api/#AnalyserOptions +struct AnalyserOptions : AudioNodeOptions { + unsigned long fft_size { 2048 }; + double max_decibels { -30 }; + double min_decibels { -100 }; + double smoothing_time_constant { 0.8 }; +}; + +// https://webaudio.github.io/web-audio-api/#AnalyserNode +class AnalyserNode : public AudioNode { + WEB_PLATFORM_OBJECT(AnalyserNode, AudioNode); + GC_DECLARE_ALLOCATOR(AnalyserNode); + +public: + virtual ~AnalyserNode() override; + + virtual WebIDL::UnsignedLong number_of_inputs() override { return 1; } + virtual WebIDL::UnsignedLong number_of_outputs() override { return 1; } + + WebIDL::ExceptionOr get_float_frequency_data(GC::Root const& array); // Float32Array + WebIDL::ExceptionOr get_byte_frequency_data(GC::Root const& array); // Uint8Array + WebIDL::ExceptionOr get_float_time_domain_data(GC::Root const& array); // Float32Array + WebIDL::ExceptionOr get_byte_time_domain_data(GC::Root const& array); // Uint8Array + + unsigned long fft_size() const { return m_fft_size; } + unsigned long frequency_bin_count() const { return m_fft_size / 2; } + double max_decibels() const { return m_max_decibels; } + double min_decibels() const { return m_min_decibels; } + double smoothing_time_constant() const { return m_smoothing_time_constant; } + + WebIDL::ExceptionOr set_fft_size(unsigned long); + WebIDL::ExceptionOr set_max_decibels(double); + WebIDL::ExceptionOr set_min_decibels(double); + WebIDL::ExceptionOr set_smoothing_time_constant(double); + + static WebIDL::ExceptionOr> create(JS::Realm&, GC::Ref, AnalyserOptions const& = {}); + static WebIDL::ExceptionOr> construct_impl(JS::Realm&, GC::Ref, AnalyserOptions const& = {}); + +protected: + AnalyserNode(JS::Realm&, GC::Ref, AnalyserOptions const& = {}); + + virtual void initialize(JS::Realm&) override; + +private: + unsigned long m_fft_size; + double m_max_decibels; + double m_min_decibels; + double m_smoothing_time_constant; + + // https://webaudio.github.io/web-audio-api/#current-frequency-data + Vector current_frequency_data(); + + // https://webaudio.github.io/web-audio-api/#current-time-domain-data + Vector current_time_domain_data(); + + // https://webaudio.github.io/web-audio-api/#blackman-window + Vector apply_a_blackman_window(Vector const& x) const; + + // https://webaudio.github.io/web-audio-api/#smoothing-over-time + Vector smoothing_over_time(Vector const& current_block); + // https://webaudio.github.io/web-audio-api/#previous-block + Vector m_previous_block; + + // https://webaudio.github.io/web-audio-api/#conversion-to-db + Vector conversion_to_dB(Vector const& X_hat) const; +}; +} diff --git a/Libraries/LibWeb/WebAudio/AnalyserNode.idl b/Libraries/LibWeb/WebAudio/AnalyserNode.idl new file mode 100644 index 0000000000000..742d0ee847fb5 --- /dev/null +++ b/Libraries/LibWeb/WebAudio/AnalyserNode.idl @@ -0,0 +1,25 @@ +#import +#import + +// https://webaudio.github.io/web-audio-api/#AnalyserOptions +dictionary AnalyserOptions : AudioNodeOptions { + unsigned long fftSize = 2048; + double maxDecibels = -30; + double minDecibels = -100; + double smoothingTimeConstant = 0.8; +}; + +// https://webaudio.github.io/web-audio-api/#AnalyserNode +[Exposed=Window] +interface AnalyserNode : AudioNode { + constructor (BaseAudioContext context, optional AnalyserOptions options = {}); + undefined getFloatFrequencyData (Float32Array array); + undefined getByteFrequencyData (Uint8Array array); + undefined getFloatTimeDomainData (Float32Array array); + undefined getByteTimeDomainData (Uint8Array array); + attribute unsigned long fftSize; + readonly attribute unsigned long frequencyBinCount; + attribute double minDecibels; + attribute double maxDecibels; + attribute double smoothingTimeConstant; +}; diff --git a/Libraries/LibWeb/WebAudio/BaseAudioContext.cpp b/Libraries/LibWeb/WebAudio/BaseAudioContext.cpp index 9842e48265f1f..9b7e49ce2981a 100644 --- a/Libraries/LibWeb/WebAudio/BaseAudioContext.cpp +++ b/Libraries/LibWeb/WebAudio/BaseAudioContext.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -59,6 +60,12 @@ WebIDL::CallbackType* BaseAudioContext::onstatechange() return event_handler_attribute(HTML::EventNames::statechange); } +// https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-createanalyser +WebIDL::ExceptionOr> BaseAudioContext::create_analyser() +{ + return AnalyserNode::create(realm(), *this); +} + // https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-createbiquadfilter WebIDL::ExceptionOr> BaseAudioContext::create_biquad_filter() { diff --git a/Libraries/LibWeb/WebAudio/BaseAudioContext.h b/Libraries/LibWeb/WebAudio/BaseAudioContext.h index 52c05617f548f..5985d4489574a 100644 --- a/Libraries/LibWeb/WebAudio/BaseAudioContext.h +++ b/Libraries/LibWeb/WebAudio/BaseAudioContext.h @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -59,6 +60,7 @@ class BaseAudioContext : public DOM::EventTarget { static WebIDL::ExceptionOr verify_audio_options_inside_nominal_range(JS::Realm&, WebIDL::UnsignedLong number_of_channels, WebIDL::UnsignedLong length, float sample_rate); + WebIDL::ExceptionOr> create_analyser(); WebIDL::ExceptionOr> create_biquad_filter(); WebIDL::ExceptionOr> create_buffer(WebIDL::UnsignedLong number_of_channels, WebIDL::UnsignedLong length, float sample_rate); WebIDL::ExceptionOr> create_buffer_source(); diff --git a/Libraries/LibWeb/WebAudio/BaseAudioContext.idl b/Libraries/LibWeb/WebAudio/BaseAudioContext.idl index 543939d4694ae..7e39ce0a766fb 100644 --- a/Libraries/LibWeb/WebAudio/BaseAudioContext.idl +++ b/Libraries/LibWeb/WebAudio/BaseAudioContext.idl @@ -30,7 +30,7 @@ interface BaseAudioContext : EventTarget { [FIXME] readonly attribute AudioWorklet audioWorklet; attribute EventHandler onstatechange; - [FIXME] AnalyserNode createAnalyser (); + AnalyserNode createAnalyser (); BiquadFilterNode createBiquadFilter (); AudioBuffer createBuffer(unsigned long numberOfChannels, unsigned long length, float sampleRate); AudioBufferSourceNode createBufferSource (); diff --git a/Libraries/LibWeb/idl_files.cmake b/Libraries/LibWeb/idl_files.cmake index 07a13fdb78ced..f971ee2d7e30c 100644 --- a/Libraries/LibWeb/idl_files.cmake +++ b/Libraries/LibWeb/idl_files.cmake @@ -357,6 +357,7 @@ libweb_js_bindings(WebAssembly/Memory) libweb_js_bindings(WebAssembly/Module) libweb_js_bindings(WebAssembly/Table) libweb_js_bindings(WebAssembly/WebAssembly NAMESPACE) +libweb_js_bindings(WebAudio/AnalyserNode) libweb_js_bindings(WebAudio/AudioBuffer) libweb_js_bindings(WebAudio/AudioBufferSourceNode) libweb_js_bindings(WebAudio/AudioContext) diff --git a/Tests/LibWeb/Text/expected/all-window-properties.txt b/Tests/LibWeb/Text/expected/all-window-properties.txt index c907bb628c706..795e56130f331 100644 --- a/Tests/LibWeb/Text/expected/all-window-properties.txt +++ b/Tests/LibWeb/Text/expected/all-window-properties.txt @@ -2,6 +2,7 @@ AbortController AbortSignal AbstractRange AggregateError +AnalyserNode Animation AnimationEffect AnimationEvent diff --git a/Tests/LibWeb/Text/expected/wpt-import/webaudio/the-audio-api/the-analysernode-interface/ctor-analyser.txt b/Tests/LibWeb/Text/expected/wpt-import/webaudio/the-audio-api/the-analysernode-interface/ctor-analyser.txt new file mode 100644 index 0000000000000..ce2b6138b50bb --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/webaudio/the-audio-api/the-analysernode-interface/ctor-analyser.txt @@ -0,0 +1,83 @@ +Harness status: OK + +Found 78 tests + +78 Pass +Pass # AUDIT TASK RUNNER STARTED. +Pass Executing "initialize" +Pass Executing "invalid constructor" +Pass Executing "default constructor" +Pass Executing "test AudioNodeOptions" +Pass Executing "constructor with options" +Pass Executing "construct invalid options" +Pass Executing "setting min/max" +Pass Audit report +Pass > [initialize] +Pass context = new OfflineAudioContext(...) did not throw an exception. +Pass < [initialize] All assertions passed. (total 1 assertions) +Pass > [invalid constructor] +Pass new AnalyserNode() threw TypeError: "AnalyserNode() needs one argument". +Pass new AnalyserNode(1) threw TypeError: "Not an object of type BaseAudioContext". +Pass new AnalyserNode(context, 42) threw TypeError: "Not an object of type AnalyserOptions". +Pass < [invalid constructor] All assertions passed. (total 3 assertions) +Pass > [default constructor] +Pass node0 = new AnalyserNode(context) did not throw an exception. +Pass node0 instanceof AnalyserNode is equal to true. +Pass node0.numberOfInputs is equal to 1. +Pass node0.numberOfOutputs is equal to 1. +Pass node0.channelCount is equal to 2. +Pass node0.channelCountMode is equal to max. +Pass node0.channelInterpretation is equal to speakers. +Pass node0.fftSize is equal to 2048. +Pass node0.frequencyBinCount is equal to 1024. +Pass node0.minDecibels is equal to -100. +Pass node0.maxDecibels is equal to -30. +Pass node0.smoothingTimeConstant is equal to 0.8. +Pass < [default constructor] All assertions passed. (total 12 assertions) +Pass > [test AudioNodeOptions] +Pass new AnalyserNode(c, {channelCount: 17}) did not throw an exception. +Pass node.channelCount is equal to 17. +Pass new AnalyserNode(c, {channelCount: 0}) threw NotSupportedError: "Invalid channel count". +Pass new AnalyserNode(c, {channelCount: 99}) threw NotSupportedError: "Invalid channel count". +Pass new AnalyserNode(c, {channelCountMode: "max"} did not throw an exception. +Pass node.channelCountMode is equal to max. +Pass new AnalyserNode(c, {channelCountMode: "max"}) did not throw an exception. +Pass node.channelCountMode after valid setter is equal to max. +Pass new AnalyserNode(c, {channelCountMode: "clamped-max"}) did not throw an exception. +Pass node.channelCountMode after valid setter is equal to clamped-max. +Pass new AnalyserNode(c, {channelCountMode: "explicit"}) did not throw an exception. +Pass node.channelCountMode after valid setter is equal to explicit. +Pass new AnalyserNode(c, {channelCountMode: "foobar"} threw TypeError: "Invalid value 'foobar' for enumeration type 'ChannelCountMode'". +Pass node.channelCountMode after invalid setter is equal to explicit. +Pass new AnalyserNode(c, {channelInterpretation: "speakers"}) did not throw an exception. +Pass node.channelInterpretation is equal to speakers. +Pass new AnalyserNode(c, {channelInterpretation: "discrete"}) did not throw an exception. +Pass node.channelInterpretation is equal to discrete. +Pass new AnalyserNode(c, {channelInterpretation: "foobar"}) threw TypeError: "Invalid value 'foobar' for enumeration type 'ChannelInterpretation'". +Pass node.channelInterpretation after invalid setter is equal to discrete. +Pass < [test AudioNodeOptions] All assertions passed. (total 20 assertions) +Pass > [constructor with options] +Pass node1 = new AnalyserNode(c, {"fftSize":32,"maxDecibels":1,"minDecibels":-13,"smoothingTimeConstant":0.125}) did not throw an exception. +Pass node1 instanceof AnalyserNode is equal to true. +Pass node1.fftSize is equal to 32. +Pass node1.maxDecibels is equal to 1. +Pass node1.minDecibels is equal to -13. +Pass node1.smoothingTimeConstant is equal to 0.125. +Pass < [constructor with options] All assertions passed. (total 6 assertions) +Pass > [construct invalid options] +Pass node = new AnalyserNode(c, { fftSize: 33 }) threw IndexSizeError: "Analyser node fftSize not a power of 2 between 32 and 32768". +Pass node = new AnalyserNode(c, { maxDecibels: -500 }) threw IndexSizeError: "Analyser node minDecibels greater than maxDecibels". +Pass node = new AnalyserNode(c, { minDecibels: -10 }) threw IndexSizeError: "Analyser node minDecibels greater than maxDecibels". +Pass node = new AnalyserNode(c, { smoothingTimeConstant: 2 }) threw IndexSizeError: "Analyser node smoothingTimeConstant not between 0.0 and 1.0". +Pass node = new AnalyserNode(c, { frequencyBinCount: 33 }) did not throw an exception. +Pass node.frequencyBinCount is equal to 1024. +Pass < [construct invalid options] All assertions passed. (total 6 assertions) +Pass > [setting min/max] +Pass node = new AnalyserNode(c, {"minDecibels":-10,"maxDecibels":20}) did not throw an exception. +Pass node = new AnalyserNode(c, {"maxDecibels":20,"minDecibels":-10}) did not throw an exception. +Pass node = new AnalyserNode(c, {"minDecibels":-200,"maxDecibels":-150}) did not throw an exception. +Pass node = new AnalyserNode(c, {"maxDecibels":-150,"minDecibels":-200}) did not throw an exception. +Pass node = new AnalyserNode(c, {"maxDecibels":-150,"minDecibels":-10}) threw IndexSizeError: "Analyser node minDecibels greater than maxDecibels". +Pass node = new AnalyserNode(c, {"minDecibels":-10,"maxDecibels":-150}) threw IndexSizeError: "Analyser node minDecibels greater than maxDecibels". +Pass < [setting min/max] All assertions passed. (total 6 assertions) +Pass # AUDIT TASK RUNNER FINISHED: 7 tasks ran successfully. \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/webaudio/the-audio-api/the-analysernode-interface/realtimeanalyser-basic.txt b/Tests/LibWeb/Text/expected/wpt-import/webaudio/the-audio-api/the-analysernode-interface/realtimeanalyser-basic.txt new file mode 100644 index 0000000000000..fdfb4ec102344 --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/webaudio/the-audio-api/the-analysernode-interface/realtimeanalyser-basic.txt @@ -0,0 +1,18 @@ +Harness status: OK + +Found 13 tests + +13 Pass +Pass # AUDIT TASK RUNNER STARTED. +Pass Executing "Basic AnalyserNode test" +Pass Audit report +Pass > [Basic AnalyserNode test] +Pass Number of inputs for AnalyserNode is equal to 1. +Pass Number of outputs for AnalyserNode is equal to 1. +Pass Default minDecibels value is equal to -100. +Pass Default maxDecibels value is equal to -30. +Pass Default smoothingTimeConstant value is equal to 0.8. +Pass node.minDecibels = -50.333333333333336 is equal to -50.333333333333336. +Pass node.maxDecibels = -40.333333333333336 is equal to -40.333333333333336. +Pass < [Basic AnalyserNode test] All assertions passed. (total 7 assertions) +Pass # AUDIT TASK RUNNER FINISHED: 1 tasks ran successfully. \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/webaudio/the-audio-api/the-analysernode-interface/realtimeanalyser-fft-sizing.txt b/Tests/LibWeb/Text/expected/wpt-import/webaudio/the-audio-api/the-analysernode-interface/realtimeanalyser-fft-sizing.txt new file mode 100644 index 0000000000000..f2fef458c173f --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/webaudio/the-audio-api/the-analysernode-interface/realtimeanalyser-fft-sizing.txt @@ -0,0 +1,48 @@ +Harness status: OK + +Found 43 tests + +43 Pass +Pass # AUDIT TASK RUNNER STARTED. +Pass Executing "FFT size test" +Pass Audit report +Pass > [FFT size test] Test that re-sizing the FFT arrays does not fail. +Pass Setting fftSize to -1 threw IndexSizeError: "Analyser node fftSize not a power of 2 between 32 and 32768". +Pass Setting fftSize to 0 threw IndexSizeError: "Analyser node fftSize not a power of 2 between 32 and 32768". +Pass Setting fftSize to 1 threw IndexSizeError: "Analyser node fftSize not a power of 2 between 32 and 32768". +Pass Setting fftSize to 2 threw IndexSizeError: "Analyser node fftSize not a power of 2 between 32 and 32768". +Pass Setting fftSize to 3 threw IndexSizeError: "Analyser node fftSize not a power of 2 between 32 and 32768". +Pass Setting fftSize to 4 threw IndexSizeError: "Analyser node fftSize not a power of 2 between 32 and 32768". +Pass Setting fftSize to 5 threw IndexSizeError: "Analyser node fftSize not a power of 2 between 32 and 32768". +Pass Setting fftSize to 8 threw IndexSizeError: "Analyser node fftSize not a power of 2 between 32 and 32768". +Pass Setting fftSize to 9 threw IndexSizeError: "Analyser node fftSize not a power of 2 between 32 and 32768". +Pass Setting fftSize to 16 threw IndexSizeError: "Analyser node fftSize not a power of 2 between 32 and 32768". +Pass Setting fftSize to 17 threw IndexSizeError: "Analyser node fftSize not a power of 2 between 32 and 32768". +Pass Setting fftSize to 32 did not throw an exception. +Pass Setting fftSize to 33 threw IndexSizeError: "Analyser node fftSize not a power of 2 between 32 and 32768". +Pass Setting fftSize to 64 did not throw an exception. +Pass Setting fftSize to 65 threw IndexSizeError: "Analyser node fftSize not a power of 2 between 32 and 32768". +Pass Setting fftSize to 128 did not throw an exception. +Pass Setting fftSize to 129 threw IndexSizeError: "Analyser node fftSize not a power of 2 between 32 and 32768". +Pass Setting fftSize to 256 did not throw an exception. +Pass Setting fftSize to 257 threw IndexSizeError: "Analyser node fftSize not a power of 2 between 32 and 32768". +Pass Setting fftSize to 512 did not throw an exception. +Pass Setting fftSize to 513 threw IndexSizeError: "Analyser node fftSize not a power of 2 between 32 and 32768". +Pass Setting fftSize to 1024 did not throw an exception. +Pass Setting fftSize to 1025 threw IndexSizeError: "Analyser node fftSize not a power of 2 between 32 and 32768". +Pass Setting fftSize to 2048 did not throw an exception. +Pass Setting fftSize to 2049 threw IndexSizeError: "Analyser node fftSize not a power of 2 between 32 and 32768". +Pass Setting fftSize to 4096 did not throw an exception. +Pass Setting fftSize to 4097 threw IndexSizeError: "Analyser node fftSize not a power of 2 between 32 and 32768". +Pass Setting fftSize to 8192 did not throw an exception. +Pass Setting fftSize to 8193 threw IndexSizeError: "Analyser node fftSize not a power of 2 between 32 and 32768". +Pass Setting fftSize to 16384 did not throw an exception. +Pass Setting fftSize to 16385 threw IndexSizeError: "Analyser node fftSize not a power of 2 between 32 and 32768". +Pass Setting fftSize to 32768 did not throw an exception. +Pass Setting fftSize to 32769 threw IndexSizeError: "Analyser node fftSize not a power of 2 between 32 and 32768". +Pass Setting fftSize to 65536 threw IndexSizeError: "Analyser node fftSize not a power of 2 between 32 and 32768". +Pass Setting fftSize to 65537 threw IndexSizeError: "Analyser node fftSize not a power of 2 between 32 and 32768". +Pass Setting fftSize to 131072 threw IndexSizeError: "Analyser node fftSize not a power of 2 between 32 and 32768". +Pass Setting fftSize to 131073 threw IndexSizeError: "Analyser node fftSize not a power of 2 between 32 and 32768". +Pass < [FFT size test] All assertions passed. (total 37 assertions) +Pass # AUDIT TASK RUNNER FINISHED: 1 tasks ran successfully. \ No newline at end of file diff --git a/Tests/LibWeb/Text/input/wpt-import/webaudio/the-audio-api/the-analysernode-interface/ctor-analyser.html b/Tests/LibWeb/Text/input/wpt-import/webaudio/the-audio-api/the-analysernode-interface/ctor-analyser.html new file mode 100644 index 0000000000000..b999e5449b51b --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/webaudio/the-audio-api/the-analysernode-interface/ctor-analyser.html @@ -0,0 +1,183 @@ + + + + + Test Constructor: AnalyserNode + + + + + + + + + + + diff --git a/Tests/LibWeb/Text/input/wpt-import/webaudio/the-audio-api/the-analysernode-interface/realtimeanalyser-basic.html b/Tests/LibWeb/Text/input/wpt-import/webaudio/the-audio-api/the-analysernode-interface/realtimeanalyser-basic.html new file mode 100644 index 0000000000000..ee76f9e2d3ab7 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/webaudio/the-audio-api/the-analysernode-interface/realtimeanalyser-basic.html @@ -0,0 +1,57 @@ + + + + + realtimeanalyser-basic.html + + + + + + + + + + diff --git a/Tests/LibWeb/Text/input/wpt-import/webaudio/the-audio-api/the-analysernode-interface/realtimeanalyser-fft-sizing.html b/Tests/LibWeb/Text/input/wpt-import/webaudio/the-audio-api/the-analysernode-interface/realtimeanalyser-fft-sizing.html new file mode 100644 index 0000000000000..abf84de9ec28c --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/webaudio/the-audio-api/the-analysernode-interface/realtimeanalyser-fft-sizing.html @@ -0,0 +1,54 @@ + + + + + realtimeanalyser-fft-sizing.html + + + + + + + + + +