mirror of
https://github.com/SerenityOS/serenity
synced 2024-10-15 12:23:15 +00:00
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.
This commit is contained in:
parent
5cb6d495bb
commit
0c8a98ac94
18
Tests/LibWeb/Text/expected/WebAudio/AudioBuffer.txt
Normal file
18
Tests/LibWeb/Text/expected/WebAudio/AudioBuffer.txt
Normal file
|
@ -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'
|
48
Tests/LibWeb/Text/input/WebAudio/AudioBuffer.html
Normal file
48
Tests/LibWeb/Text/input/WebAudio/AudioBuffer.html
Normal file
|
@ -0,0 +1,48 @@
|
|||
<script src="../include.js"></script>
|
||||
<script>
|
||||
test(() => {
|
||||
// Invalid constructors
|
||||
const invalidOptions = [
|
||||
{ numberOfChannels: 0, length: 17, sampleRate: 10_002 }, // 0 channels (below min)
|
||||
{ numberOfChannels: 33, length: 17, sampleRate: 10_002 }, // 33 channels (above max)
|
||||
{ numberOfChannels: 3, length: 0, sampleRate: 10_002 }, // 0 length
|
||||
{ numberOfChannels: 3, length: 17, sampleRate: 7999 }, // 7999 sample rate (below min)
|
||||
{ numberOfChannels: 3, length: 17, sampleRate: 192001 }, // 192001 sample rate (above max)
|
||||
];
|
||||
|
||||
for (let invalidOption of invalidOptions) {
|
||||
try {
|
||||
const buffer = new AudioBuffer(invalidOption);
|
||||
println(`FAIL: created buffer ${buffer}`);
|
||||
} catch (e) {
|
||||
println(`Error creating AudioBuffer: '${e}'`);
|
||||
}
|
||||
}
|
||||
|
||||
// Valid Constructor
|
||||
const options = { numberOfChannels: 3, length: 17, sampleRate: 10_002 };
|
||||
const buffer = new AudioBuffer(options);
|
||||
|
||||
println(buffer.numberOfChannels);
|
||||
println(buffer.length);
|
||||
println(buffer.sampleRate);
|
||||
|
||||
// Check each of the channels
|
||||
for (let k = 0; k < options.numberOfChannels; ++k) {
|
||||
const data = buffer.getChannelData(k);
|
||||
println(`Got ${data.constructor.name}, length = ${data.length}`);
|
||||
println(data.length);
|
||||
|
||||
const dataAgain = buffer.getChannelData(k);
|
||||
println(`Data equals itself: ${data === dataAgain}`);
|
||||
}
|
||||
|
||||
// Out of range channel
|
||||
try {
|
||||
buffer.getChannelData(options.numberOfChannels);
|
||||
println("FAIL: No exception thrown");
|
||||
} catch (e) {
|
||||
println(`Error getting channel data: '${e}'`);
|
||||
}
|
||||
});
|
||||
</script>
|
|
@ -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
|
||||
|
|
115
Userland/Libraries/LibWeb/WebAudio/AudioBuffer.cpp
Normal file
115
Userland/Libraries/LibWeb/WebAudio/AudioBuffer.cpp
Normal file
|
@ -0,0 +1,115 @@
|
|||
/*
|
||||
* Copyright (c) 2024, Shannon Booth <shannon@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibJS/Runtime/Completion.h>
|
||||
#include <LibJS/Runtime/Realm.h>
|
||||
#include <LibJS/Runtime/TypedArray.h>
|
||||
#include <LibJS/Runtime/VM.h>
|
||||
#include <LibWeb/Bindings/Intrinsics.h>
|
||||
#include <LibWeb/WebAudio/AudioBuffer.h>
|
||||
#include <LibWeb/WebAudio/BaseAudioContext.h>
|
||||
#include <LibWeb/WebIDL/DOMException.h>
|
||||
|
||||
namespace Web::WebAudio {
|
||||
|
||||
JS_DEFINE_ALLOCATOR(AudioBuffer);
|
||||
|
||||
WebIDL::ExceptionOr<JS::NonnullGCPtr<AudioBuffer>> 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<AudioBuffer>(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<double>(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<JS::NonnullGCPtr<JS::Float32Array>> 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<void> AudioBuffer::copy_from_channel(JS::Handle<WebIDL::BufferSource> 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<void> AudioBuffer::copy_to_channel(JS::Handle<WebIDL::BufferSource>&, 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);
|
||||
}
|
||||
|
||||
}
|
65
Userland/Libraries/LibWeb/WebAudio/AudioBuffer.h
Normal file
65
Userland/Libraries/LibWeb/WebAudio/AudioBuffer.h
Normal file
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* Copyright (c) 2024, Shannon Booth <shannon@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Vector.h>
|
||||
#include <LibJS/Forward.h>
|
||||
#include <LibJS/Runtime/ArrayBuffer.h>
|
||||
#include <LibWeb/Bindings/PlatformObject.h>
|
||||
#include <LibWeb/WebIDL/Buffers.h>
|
||||
#include <LibWeb/WebIDL/ExceptionOr.h>
|
||||
#include <LibWeb/WebIDL/Types.h>
|
||||
|
||||
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<JS::NonnullGCPtr<AudioBuffer>> 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<JS::NonnullGCPtr<JS::Float32Array>> get_channel_data(WebIDL::UnsignedLong channel) const;
|
||||
WebIDL::ExceptionOr<void> copy_from_channel(JS::Handle<WebIDL::BufferSource> const&, WebIDL::UnsignedLong channel_number, WebIDL::UnsignedLong buffer_offset = 0) const;
|
||||
WebIDL::ExceptionOr<void> copy_to_channel(JS::Handle<WebIDL::BufferSource>&, 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<JS::NonnullGCPtr<JS::Float32Array>> 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]]
|
||||
};
|
||||
|
||||
}
|
23
Userland/Libraries/LibWeb/WebAudio/AudioBuffer.idl
Normal file
23
Userland/Libraries/LibWeb/WebAudio/AudioBuffer.idl
Normal file
|
@ -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);
|
||||
};
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue