2024-04-25 15:13:51 +12:00
/*
* 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>
2024-04-27 12:09:58 +12:00
# include <LibWeb/Bindings/AudioBufferPrototype.h>
2024-04-25 15:13:51 +12:00
# include <LibWeb/Bindings/Intrinsics.h>
# include <LibWeb/WebAudio/AudioBuffer.h>
# include <LibWeb/WebAudio/BaseAudioContext.h>
# include <LibWeb/WebIDL/DOMException.h>
namespace Web : : WebAudio {
2024-11-15 04:01:23 +13:00
GC_DEFINE_ALLOCATOR ( AudioBuffer ) ;
2024-04-25 15:13:51 +12:00
2024-11-15 04:01:23 +13:00
WebIDL : : ExceptionOr < GC : : Ref < AudioBuffer > > AudioBuffer : : create ( JS : : Realm & realm , WebIDL : : UnsignedLong number_of_channels , WebIDL : : UnsignedLong length , float sample_rate )
2024-05-26 11:56:11 +12:00
{
return construct_impl ( realm , { number_of_channels , length , sample_rate } ) ;
}
2024-11-15 04:01:23 +13:00
WebIDL : : ExceptionOr < GC : : Ref < AudioBuffer > > AudioBuffer : : construct_impl ( JS : : Realm & realm , AudioBufferOptions const & options )
2024-04-25 15:13:51 +12:00
{
// 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]].
2024-11-14 05:50:17 +13:00
auto buffer = realm . create < AudioBuffer > ( realm , options ) ;
2024-04-25 15:13:51 +12:00
// 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
2024-11-15 04:01:23 +13:00
WebIDL : : ExceptionOr < GC : : Ref < JS : : Float32Array > > AudioBuffer : : get_channel_data ( WebIDL : : UnsignedLong channel ) const
2024-04-25 15:13:51 +12:00
{
if ( channel > = m_channels . size ( ) )
2025-08-07 19:31:52 -04:00
return WebIDL : : IndexSizeError : : create ( realm ( ) , " Channel index is out of range " _utf16 ) ;
2024-04-25 15:13:51 +12:00
return m_channels [ channel ] ;
}
// https://webaudio.github.io/web-audio-api/#dom-audiobuffer-copyfromchannel
2024-11-15 04:01:23 +13:00
WebIDL : : ExceptionOr < void > AudioBuffer : : copy_from_channel ( GC : : Root < WebIDL : : BufferSource > const & destination , WebIDL : : UnsignedLong channel_number , WebIDL : : UnsignedLong buffer_offset ) const
2024-04-25 15:13:51 +12:00
{
2024-05-12 14:20:40 +12:00
// The copyFromChannel() method copies the samples from the specified channel of the AudioBuffer to the destination array.
//
// Let buffer be the AudioBuffer with Nb frames, let Nf be the number of elements in the destination array, and k be the value
// of bufferOffset. Then the number of frames copied from buffer to destination is max(0,min(Nb− k,Nf)). If this is less than Nf,
// then the remaining elements of destination are not modified.
auto & vm = this - > vm ( ) ;
if ( ! is < JS : : Float32Array > ( * destination - > raw_object ( ) ) )
return vm . throw_completion < JS : : TypeError > ( JS : : ErrorType : : NotAnObjectOfType , " Float32Array " ) ;
auto & float32_array = static_cast < JS : : Float32Array & > ( * destination - > raw_object ( ) ) ;
2025-01-18 22:14:08 +00:00
if ( float32_array . viewed_array_buffer ( ) - > is_shared_array_buffer ( ) )
return vm . throw_completion < JS : : TypeError > ( JS : : ErrorType : : SharedArrayBuffer , " Float32Array " ) ;
2024-05-12 14:20:40 +12:00
auto const channel = TRY ( get_channel_data ( channel_number ) ) ;
auto channel_length = channel - > data ( ) . size ( ) ;
if ( buffer_offset > = channel_length )
return { } ;
u32 count = min ( float32_array . data ( ) . size ( ) , channel_length - buffer_offset ) ;
channel - > data ( ) . slice ( buffer_offset , count ) . copy_to ( float32_array . data ( ) ) ;
return { } ;
2024-04-25 15:13:51 +12:00
}
// https://webaudio.github.io/web-audio-api/#dom-audiobuffer-copytochannel
2024-11-15 04:01:23 +13:00
WebIDL : : ExceptionOr < void > AudioBuffer : : copy_to_channel ( GC : : Root < WebIDL : : BufferSource > const & source , WebIDL : : UnsignedLong channel_number , WebIDL : : UnsignedLong buffer_offset )
2024-04-25 15:13:51 +12:00
{
2024-05-26 16:33:27 +12:00
// The copyToChannel() method copies the samples to the specified channel of the AudioBuffer from the source array.
//
// A UnknownError may be thrown if source cannot be copied to the buffer.
//
// Let buffer be the AudioBuffer with Nb frames, let Nf be the number of elements in the source array, and k be the value
// of bufferOffset. Then the number of frames copied from source to the buffer is max(0,min(Nb− k,Nf)). If this is less than Nf,
// then the remaining elements of buffer are not modified.
auto & vm = this - > vm ( ) ;
if ( ! is < JS : : Float32Array > ( * source - > raw_object ( ) ) )
return vm . throw_completion < JS : : TypeError > ( JS : : ErrorType : : NotAnObjectOfType , " Float32Array " ) ;
auto const & float32_array = static_cast < JS : : Float32Array const & > ( * source - > raw_object ( ) ) ;
2025-01-18 22:14:08 +00:00
if ( float32_array . viewed_array_buffer ( ) - > is_shared_array_buffer ( ) )
return vm . throw_completion < JS : : TypeError > ( JS : : ErrorType : : SharedArrayBuffer , " Float32Array " ) ;
2024-05-26 16:33:27 +12:00
auto channel = TRY ( get_channel_data ( channel_number ) ) ;
auto channel_length = channel - > data ( ) . size ( ) ;
if ( buffer_offset > = channel_length )
return { } ;
u32 count = min ( float32_array . data ( ) . size ( ) , channel_length - buffer_offset ) ;
float32_array . data ( ) . slice ( 0 , count ) . copy_to ( channel - > data ( ) . slice ( buffer_offset , count ) ) ;
return { } ;
2024-04-25 15:13:51 +12:00
}
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 )
{
WEB_SET_PROTOTYPE_FOR_INTERFACE ( AudioBuffer ) ;
2025-04-20 16:22:57 +02:00
Base : : initialize ( realm ) ;
2024-04-25 15:13:51 +12:00
}
void AudioBuffer : : visit_edges ( Cell : : Visitor & visitor )
{
Base : : visit_edges ( visitor ) ;
visitor . visit ( m_channels ) ;
}
}