Skip to content

Commit

Permalink
LibWeb: Begin implementing the interface for AudioBuffer
Browse files Browse the repository at this point in the history
Implement the constructor and getChannelData function, working towards
the functionality that we need in order to implement
OfflineAudioContext.
  • Loading branch information
shannonbooth committed Apr 25, 2024
1 parent 4a37f77 commit 5c158f3
Show file tree
Hide file tree
Showing 7 changed files with 271 additions and 0 deletions.
18 changes: 18 additions & 0 deletions Tests/LibWeb/Text/expected/WebAudio/AudioBuffer.txt
Original file line number Diff line number Diff line change
@@ -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 changes: 48 additions & 0 deletions Tests/LibWeb/Text/input/WebAudio/AudioBuffer.html
Original file line number Diff line number Diff line change
@@ -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>
1 change: 1 addition & 0 deletions Userland/Libraries/LibWeb/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
115 changes: 115 additions & 0 deletions Userland/Libraries/LibWeb/WebAudio/AudioBuffer.cpp
Original file line number Diff line number Diff line change
@@ -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 changes: 65 additions & 0 deletions Userland/Libraries/LibWeb/WebAudio/AudioBuffer.h
Original file line number Diff line number Diff line change
@@ -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 changes: 23 additions & 0 deletions Userland/Libraries/LibWeb/WebAudio/AudioBuffer.idl
Original file line number Diff line number Diff line change
@@ -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);
};
1 change: 1 addition & 0 deletions Userland/Libraries/LibWeb/idl_files.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down

0 comments on commit 5c158f3

Please sign in to comment.