-
-
Notifications
You must be signed in to change notification settings - Fork 3.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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.
- Loading branch information
1 parent
4a37f77
commit 5c158f3
Showing
7 changed files
with
271 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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]] | ||
}; | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters