AudioBuffer.cpp 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. /*
  2. * Copyright (c) 2024, Shannon Booth <shannon@serenityos.org>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include <LibJS/Runtime/Completion.h>
  7. #include <LibJS/Runtime/Realm.h>
  8. #include <LibJS/Runtime/TypedArray.h>
  9. #include <LibJS/Runtime/VM.h>
  10. #include <LibWeb/Bindings/AudioBufferPrototype.h>
  11. #include <LibWeb/Bindings/Intrinsics.h>
  12. #include <LibWeb/WebAudio/AudioBuffer.h>
  13. #include <LibWeb/WebAudio/BaseAudioContext.h>
  14. #include <LibWeb/WebIDL/DOMException.h>
  15. namespace Web::WebAudio {
  16. JS_DEFINE_ALLOCATOR(AudioBuffer);
  17. WebIDL::ExceptionOr<JS::NonnullGCPtr<AudioBuffer>> AudioBuffer::create(JS::Realm& realm, WebIDL::UnsignedLong number_of_channels, WebIDL::UnsignedLong length, float sample_rate)
  18. {
  19. return construct_impl(realm, { number_of_channels, length, sample_rate });
  20. }
  21. WebIDL::ExceptionOr<JS::NonnullGCPtr<AudioBuffer>> AudioBuffer::construct_impl(JS::Realm& realm, AudioBufferOptions const& options)
  22. {
  23. auto& vm = realm.vm();
  24. // 1. If any of the values in options lie outside its nominal range, throw a NotSupportedError exception and abort the following steps.
  25. TRY(BaseAudioContext::verify_audio_options_inside_nominal_range(realm, options.number_of_channels, options.length, options.sample_rate));
  26. // 2. Let b be a new AudioBuffer object.
  27. // 3. Respectively assign the values of the attributes numberOfChannels, length, sampleRate of the AudioBufferOptions passed in the
  28. // constructor to the internal slots [[number of channels]], [[length]], [[sample rate]].
  29. auto buffer = vm.heap().allocate<AudioBuffer>(realm, realm, options);
  30. // 4. Set the internal slot [[internal data]] of this AudioBuffer to the result of calling CreateByteDataBlock([[length]] * [[number of channels]]).
  31. buffer->m_channels.ensure_capacity(options.number_of_channels);
  32. for (WebIDL::UnsignedLong i = 0; i < options.number_of_channels; ++i)
  33. buffer->m_channels.unchecked_append(TRY(JS::Float32Array::create(realm, options.length)));
  34. return buffer;
  35. }
  36. AudioBuffer::~AudioBuffer() = default;
  37. // https://webaudio.github.io/web-audio-api/#dom-audiobuffer-samplerate
  38. float AudioBuffer::sample_rate() const
  39. {
  40. // The sample-rate for the PCM audio data in samples per second. This MUST return the value of [[sample rate]].
  41. return m_sample_rate;
  42. }
  43. // https://webaudio.github.io/web-audio-api/#dom-audiobuffer-length
  44. WebIDL::UnsignedLong AudioBuffer::length() const
  45. {
  46. // Length of the PCM audio data in sample-frames. This MUST return the value of [[length]].
  47. return m_length;
  48. }
  49. // https://webaudio.github.io/web-audio-api/#dom-audiobuffer-duration
  50. double AudioBuffer::duration() const
  51. {
  52. // Duration of the PCM audio data in seconds.
  53. // This is computed from the [[sample rate]] and the [[length]] of the AudioBuffer by performing a division between the [[length]] and the [[sample rate]].
  54. return m_length / static_cast<double>(m_sample_rate);
  55. }
  56. // https://webaudio.github.io/web-audio-api/#dom-audiobuffer-numberofchannels
  57. WebIDL::UnsignedLong AudioBuffer::number_of_channels() const
  58. {
  59. // The number of discrete audio channels. This MUST return the value of [[number of channels]].
  60. return m_channels.size();
  61. }
  62. // https://webaudio.github.io/web-audio-api/#dom-audiobuffer-getchanneldata
  63. WebIDL::ExceptionOr<JS::NonnullGCPtr<JS::Float32Array>> AudioBuffer::get_channel_data(WebIDL::UnsignedLong channel) const
  64. {
  65. if (channel >= m_channels.size())
  66. return WebIDL::IndexSizeError::create(realm(), "Channel index is out of range"_fly_string);
  67. return m_channels[channel];
  68. }
  69. // https://webaudio.github.io/web-audio-api/#dom-audiobuffer-copyfromchannel
  70. WebIDL::ExceptionOr<void> AudioBuffer::copy_from_channel(JS::Handle<WebIDL::BufferSource> const& destination, WebIDL::UnsignedLong channel_number, WebIDL::UnsignedLong buffer_offset) const
  71. {
  72. // The copyFromChannel() method copies the samples from the specified channel of the AudioBuffer to the destination array.
  73. //
  74. // Let buffer be the AudioBuffer with Nb frames, let Nf be the number of elements in the destination array, and k be the value
  75. // 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,
  76. // then the remaining elements of destination are not modified.
  77. auto& vm = this->vm();
  78. if (!is<JS::Float32Array>(*destination->raw_object()))
  79. return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAnObjectOfType, "Float32Array");
  80. auto& float32_array = static_cast<JS::Float32Array&>(*destination->raw_object());
  81. auto const channel = TRY(get_channel_data(channel_number));
  82. auto channel_length = channel->data().size();
  83. if (buffer_offset >= channel_length)
  84. return {};
  85. u32 count = min(float32_array.data().size(), channel_length - buffer_offset);
  86. channel->data().slice(buffer_offset, count).copy_to(float32_array.data());
  87. return {};
  88. }
  89. // https://webaudio.github.io/web-audio-api/#dom-audiobuffer-copytochannel
  90. WebIDL::ExceptionOr<void> AudioBuffer::copy_to_channel(JS::Handle<WebIDL::BufferSource> const& source, WebIDL::UnsignedLong channel_number, WebIDL::UnsignedLong buffer_offset)
  91. {
  92. // The copyToChannel() method copies the samples to the specified channel of the AudioBuffer from the source array.
  93. //
  94. // A UnknownError may be thrown if source cannot be copied to the buffer.
  95. //
  96. // Let buffer be the AudioBuffer with Nb frames, let Nf be the number of elements in the source array, and k be the value
  97. // 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,
  98. // then the remaining elements of buffer are not modified.
  99. auto& vm = this->vm();
  100. if (!is<JS::Float32Array>(*source->raw_object()))
  101. return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAnObjectOfType, "Float32Array");
  102. auto const& float32_array = static_cast<JS::Float32Array const&>(*source->raw_object());
  103. auto channel = TRY(get_channel_data(channel_number));
  104. auto channel_length = channel->data().size();
  105. if (buffer_offset >= channel_length)
  106. return {};
  107. u32 count = min(float32_array.data().size(), channel_length - buffer_offset);
  108. float32_array.data().slice(0, count).copy_to(channel->data().slice(buffer_offset, count));
  109. return {};
  110. }
  111. AudioBuffer::AudioBuffer(JS::Realm& realm, AudioBufferOptions const& options)
  112. : Bindings::PlatformObject(realm)
  113. , m_length(options.length)
  114. , m_sample_rate(options.sample_rate)
  115. {
  116. }
  117. void AudioBuffer::initialize(JS::Realm& realm)
  118. {
  119. Base::initialize(realm);
  120. WEB_SET_PROTOTYPE_FOR_INTERFACE(AudioBuffer);
  121. }
  122. void AudioBuffer::visit_edges(Cell::Visitor& visitor)
  123. {
  124. Base::visit_edges(visitor);
  125. visitor.visit(m_channels);
  126. }
  127. }