From 0c8a98ac948051f40660fd4d0d130e22daa4fb9f Mon Sep 17 00:00:00 2001 From: Shannon Booth Date: Thu, 25 Apr 2024 15:13:51 +1200 Subject: [PATCH] LibWeb: Begin implementing the interface for AudioBuffer Implement the constructor and getChannelData function, working towards the functionality that we need in order to implement OfflineAudioContext. --- .../Text/expected/WebAudio/AudioBuffer.txt | 18 +++ .../Text/input/WebAudio/AudioBuffer.html | 48 ++++++++ Userland/Libraries/LibWeb/CMakeLists.txt | 1 + .../Libraries/LibWeb/WebAudio/AudioBuffer.cpp | 115 ++++++++++++++++++ .../Libraries/LibWeb/WebAudio/AudioBuffer.h | 65 ++++++++++ .../Libraries/LibWeb/WebAudio/AudioBuffer.idl | 23 ++++ Userland/Libraries/LibWeb/idl_files.cmake | 1 + 7 files changed, 271 insertions(+) create mode 100644 Tests/LibWeb/Text/expected/WebAudio/AudioBuffer.txt create mode 100644 Tests/LibWeb/Text/input/WebAudio/AudioBuffer.html create mode 100644 Userland/Libraries/LibWeb/WebAudio/AudioBuffer.cpp create mode 100644 Userland/Libraries/LibWeb/WebAudio/AudioBuffer.h create mode 100644 Userland/Libraries/LibWeb/WebAudio/AudioBuffer.idl diff --git a/Tests/LibWeb/Text/expected/WebAudio/AudioBuffer.txt b/Tests/LibWeb/Text/expected/WebAudio/AudioBuffer.txt new file mode 100644 index 00000000000..ad4dd8093de --- /dev/null +++ b/Tests/LibWeb/Text/expected/WebAudio/AudioBuffer.txt @@ -0,0 +1,18 @@ +Error creating AudioBuffer: 'NotSupportedError: Number of channels must not be '0'' +Error creating AudioBuffer: 'NotSupportedError: Number of channels is greater than allowed range' +Error creating AudioBuffer: 'NotSupportedError: Length of buffer must be at least 1' +Error creating AudioBuffer: 'NotSupportedError: Sample rate is outside of allowed range' +Error creating AudioBuffer: 'NotSupportedError: Sample rate is outside of allowed range' +3 +17 +10002 +Got Float32Array, length = 17 +17 +Data equals itself: true +Got Float32Array, length = 17 +17 +Data equals itself: true +Got Float32Array, length = 17 +17 +Data equals itself: true +Error getting channel data: 'IndexSizeError: Channel index is out of range' diff --git a/Tests/LibWeb/Text/input/WebAudio/AudioBuffer.html b/Tests/LibWeb/Text/input/WebAudio/AudioBuffer.html new file mode 100644 index 00000000000..cf243fdfc98 --- /dev/null +++ b/Tests/LibWeb/Text/input/WebAudio/AudioBuffer.html @@ -0,0 +1,48 @@ + + diff --git a/Userland/Libraries/LibWeb/CMakeLists.txt b/Userland/Libraries/LibWeb/CMakeLists.txt index 19a59d08726..a9eecec0ca4 100644 --- a/Userland/Libraries/LibWeb/CMakeLists.txt +++ b/Userland/Libraries/LibWeb/CMakeLists.txt @@ -653,6 +653,7 @@ set(SOURCES WebAssembly/Module.cpp WebAssembly/Table.cpp WebAssembly/WebAssembly.cpp + WebAudio/AudioBuffer.cpp WebAudio/AudioContext.cpp WebAudio/BaseAudioContext.cpp WebDriver/Capabilities.cpp diff --git a/Userland/Libraries/LibWeb/WebAudio/AudioBuffer.cpp b/Userland/Libraries/LibWeb/WebAudio/AudioBuffer.cpp new file mode 100644 index 00000000000..e55e7c3348b --- /dev/null +++ b/Userland/Libraries/LibWeb/WebAudio/AudioBuffer.cpp @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2024, Shannon Booth + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Web::WebAudio { + +JS_DEFINE_ALLOCATOR(AudioBuffer); + +WebIDL::ExceptionOr> AudioBuffer::construct_impl(JS::Realm& realm, AudioBufferOptions const& options) +{ + auto& vm = realm.vm(); + + // 1. If any of the values in options lie outside its nominal range, throw a NotSupportedError exception and abort the following steps. + TRY(BaseAudioContext::verify_audio_options_inside_nominal_range(realm, options.number_of_channels, options.length, options.sample_rate)); + + // 2. Let b be a new AudioBuffer object. + // 3. Respectively assign the values of the attributes numberOfChannels, length, sampleRate of the AudioBufferOptions passed in the + // constructor to the internal slots [[number of channels]], [[length]], [[sample rate]]. + auto buffer = vm.heap().allocate(realm, realm, options); + + // 4. Set the internal slot [[internal data]] of this AudioBuffer to the result of calling CreateByteDataBlock([[length]] * [[number of channels]]). + buffer->m_channels.ensure_capacity(options.number_of_channels); + for (WebIDL::UnsignedLong i = 0; i < options.number_of_channels; ++i) + buffer->m_channels.unchecked_append(TRY(JS::Float32Array::create(realm, options.length))); + + return buffer; +} + +AudioBuffer::~AudioBuffer() = default; + +// https://webaudio.github.io/web-audio-api/#dom-audiobuffer-samplerate +float AudioBuffer::sample_rate() const +{ + // The sample-rate for the PCM audio data in samples per second. This MUST return the value of [[sample rate]]. + return m_sample_rate; +} + +// https://webaudio.github.io/web-audio-api/#dom-audiobuffer-length +WebIDL::UnsignedLong AudioBuffer::length() const +{ + // Length of the PCM audio data in sample-frames. This MUST return the value of [[length]]. + return m_length; +} + +// https://webaudio.github.io/web-audio-api/#dom-audiobuffer-duration +double AudioBuffer::duration() const +{ + // Duration of the PCM audio data in seconds. + // This is computed from the [[sample rate]] and the [[length]] of the AudioBuffer by performing a division between the [[length]] and the [[sample rate]]. + return m_length / static_cast(m_sample_rate); +} + +// https://webaudio.github.io/web-audio-api/#dom-audiobuffer-numberofchannels +WebIDL::UnsignedLong AudioBuffer::number_of_channels() const +{ + // The number of discrete audio channels. This MUST return the value of [[number of channels]]. + return m_channels.size(); +} + +// https://webaudio.github.io/web-audio-api/#dom-audiobuffer-getchanneldata +WebIDL::ExceptionOr> AudioBuffer::get_channel_data(WebIDL::UnsignedLong channel) const +{ + if (channel >= m_channels.size()) + return WebIDL::IndexSizeError::create(realm(), "Channel index is out of range"_fly_string); + + return m_channels[channel]; +} + +// https://webaudio.github.io/web-audio-api/#dom-audiobuffer-copyfromchannel +WebIDL::ExceptionOr AudioBuffer::copy_from_channel(JS::Handle const&, WebIDL::UnsignedLong channel_number, WebIDL::UnsignedLong buffer_offset) const +{ + (void)channel_number; + (void)buffer_offset; + return WebIDL::NotSupportedError::create(realm(), "FIXME: Implement AudioBuffer:copy_from_channel:"_fly_string); +} + +// https://webaudio.github.io/web-audio-api/#dom-audiobuffer-copytochannel +WebIDL::ExceptionOr AudioBuffer::copy_to_channel(JS::Handle&, WebIDL::UnsignedLong channel_number, WebIDL::UnsignedLong buffer_offset) const +{ + (void)channel_number; + (void)buffer_offset; + return WebIDL::NotSupportedError::create(realm(), "FIXME: Implement AudioBuffer:copy_to_channel:"_fly_string); +} + +AudioBuffer::AudioBuffer(JS::Realm& realm, AudioBufferOptions const& options) + : Bindings::PlatformObject(realm) + , m_length(options.length) + , m_sample_rate(options.sample_rate) +{ +} + +void AudioBuffer::initialize(JS::Realm& realm) +{ + Base::initialize(realm); + WEB_SET_PROTOTYPE_FOR_INTERFACE(AudioBuffer); +} + +void AudioBuffer::visit_edges(Cell::Visitor& visitor) +{ + Base::visit_edges(visitor); + visitor.visit(m_channels); +} + +} diff --git a/Userland/Libraries/LibWeb/WebAudio/AudioBuffer.h b/Userland/Libraries/LibWeb/WebAudio/AudioBuffer.h new file mode 100644 index 00000000000..205a807fff6 --- /dev/null +++ b/Userland/Libraries/LibWeb/WebAudio/AudioBuffer.h @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2024, Shannon Booth + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace Web::WebAudio { + +struct AudioBufferOptions { + WebIDL::UnsignedLong number_of_channels { 1 }; + WebIDL::UnsignedLong length {}; + float sample_rate {}; +}; + +// https://webaudio.github.io/web-audio-api/#AudioContext +class AudioBuffer final : public Bindings::PlatformObject { + WEB_PLATFORM_OBJECT(AudioBuffer, Bindings::PlatformObject); + JS_DECLARE_ALLOCATOR(AudioBuffer); + +public: + static WebIDL::ExceptionOr> construct_impl(JS::Realm&, AudioBufferOptions const&); + + virtual ~AudioBuffer() override; + + float sample_rate() const; + WebIDL::UnsignedLong length() const; + double duration() const; + WebIDL::UnsignedLong number_of_channels() const; + WebIDL::ExceptionOr> get_channel_data(WebIDL::UnsignedLong channel) const; + WebIDL::ExceptionOr copy_from_channel(JS::Handle const&, WebIDL::UnsignedLong channel_number, WebIDL::UnsignedLong buffer_offset = 0) const; + WebIDL::ExceptionOr copy_to_channel(JS::Handle&, WebIDL::UnsignedLong channel_number, WebIDL::UnsignedLong buffer_offset = 0) const; + +private: + explicit AudioBuffer(JS::Realm&, AudioBufferOptions const&); + + virtual void initialize(JS::Realm&) override; + virtual void visit_edges(Cell::Visitor&) override; + + // https://webaudio.github.io/web-audio-api/#dom-audiobuffer-number-of-channels-slot + // The number of audio channels for this AudioBuffer, which is an unsigned long. + // + // https://webaudio.github.io/web-audio-api/#dom-audiobuffer-internal-data-slot + // A data block holding the audio sample data. + Vector> m_channels; // [[internal data]] / [[number_of_channels]] + + // https://webaudio.github.io/web-audio-api/#dom-audiobuffer-length-slot + // The length of each channel of this AudioBuffer, which is an unsigned long. + WebIDL::UnsignedLong m_length {}; // [[length]] + + // https://webaudio.github.io/web-audio-api/#dom-audiobuffer-sample-rate-slot + // The sample-rate, in Hz, of this AudioBuffer, a float. + float m_sample_rate {}; // [[sample rate]] +}; + +} diff --git a/Userland/Libraries/LibWeb/WebAudio/AudioBuffer.idl b/Userland/Libraries/LibWeb/WebAudio/AudioBuffer.idl new file mode 100644 index 00000000000..6ffc20bf773 --- /dev/null +++ b/Userland/Libraries/LibWeb/WebAudio/AudioBuffer.idl @@ -0,0 +1,23 @@ +// https://webaudio.github.io/web-audio-api/#AudioBufferOptions +dictionary AudioBufferOptions { + unsigned long numberOfChannels = 1; + required unsigned long length; + required float sampleRate; +}; + +// https://webaudio.github.io/web-audio-api/#AudioBuffer +[Exposed=Window] +interface AudioBuffer { + constructor (AudioBufferOptions options); + readonly attribute float sampleRate; + readonly attribute unsigned long length; + readonly attribute double duration; + readonly attribute unsigned long numberOfChannels; + Float32Array getChannelData(unsigned long channel); + undefined copyFromChannel(Float32Array destination, + unsigned long channelNumber, + optional unsigned long bufferOffset = 0); + undefined copyToChannel(Float32Array source, + unsigned long channelNumber, + optional unsigned long bufferOffset = 0); +}; diff --git a/Userland/Libraries/LibWeb/idl_files.cmake b/Userland/Libraries/LibWeb/idl_files.cmake index 2b5ad67d0cc..a32c60709b3 100644 --- a/Userland/Libraries/LibWeb/idl_files.cmake +++ b/Userland/Libraries/LibWeb/idl_files.cmake @@ -293,6 +293,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/AudioBuffer) libweb_js_bindings(WebAudio/AudioContext) libweb_js_bindings(WebAudio/BaseAudioContext) libweb_js_bindings(WebGL/WebGLContextEvent)