| /* |
| * Copyright (C) 2010 Google Inc. All rights reserved. |
| * Copyright (C) 2017-2021 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * 3. Neither the name of Apple Inc. ("Apple") nor the names of |
| * its contributors may be used to endorse or promote products derived |
| * from this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
| * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY |
| * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
| * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
| * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
| * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
| * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "config.h" |
| |
| #if ENABLE(WEB_AUDIO) |
| |
| #include "AudioBuffer.h" |
| |
| #include "AudioContext.h" |
| #include "AudioFileReader.h" |
| #include "WebCoreOpaqueRoot.h" |
| #include <JavaScriptCore/JSCInlines.h> |
| #include <JavaScriptCore/TypedArrayInlines.h> |
| |
| namespace WebCore { |
| |
| RefPtr<AudioBuffer> AudioBuffer::create(unsigned numberOfChannels, size_t numberOfFrames, float sampleRate, LegacyPreventDetaching preventDetaching) |
| { |
| if (!BaseAudioContext::isSupportedSampleRate(sampleRate) || !numberOfChannels || numberOfChannels > AudioContext::maxNumberOfChannels || !numberOfFrames) |
| return nullptr; |
| |
| auto buffer = adoptRef(*new AudioBuffer(numberOfChannels, numberOfFrames, sampleRate, preventDetaching)); |
| if (!buffer->originalLength()) |
| return nullptr; |
| |
| return buffer; |
| } |
| |
| ExceptionOr<Ref<AudioBuffer>> AudioBuffer::create(const AudioBufferOptions& options) |
| { |
| if (!options.numberOfChannels) |
| return Exception { NotSupportedError, "Number of channels cannot be 0."_s }; |
| |
| if (options.numberOfChannels > AudioContext::maxNumberOfChannels) |
| return Exception { NotSupportedError, "Number of channels cannot be more than max supported."_s }; |
| |
| if (!options.length) |
| return Exception { NotSupportedError, "Length must be at least 1."_s }; |
| |
| if (!BaseAudioContext::isSupportedSampleRate(options.sampleRate)) |
| return Exception { NotSupportedError, "Sample rate is not in the supported range."_s }; |
| |
| auto buffer = adoptRef(*new AudioBuffer(options.numberOfChannels, options.length, options.sampleRate)); |
| if (!buffer->originalLength()) |
| return Exception { NotSupportedError, "Channel was not able to be created."_s }; |
| |
| return buffer; |
| } |
| |
| RefPtr<AudioBuffer> AudioBuffer::createFromAudioFileData(const void* data, size_t dataSize, bool mixToMono, float sampleRate) |
| { |
| RefPtr<AudioBus> bus = createBusFromInMemoryAudioFile(data, dataSize, mixToMono, sampleRate); |
| if (!bus) |
| return nullptr; |
| return adoptRef(*new AudioBuffer(*bus)); |
| } |
| |
| AudioBuffer::AudioBuffer(unsigned numberOfChannels, size_t length, float sampleRate, LegacyPreventDetaching preventDetaching) |
| : m_sampleRate(sampleRate) |
| , m_originalLength(length) |
| , m_isDetachable(preventDetaching == LegacyPreventDetaching::No) |
| { |
| if (static_cast<uint64_t>(m_originalLength) > s_maxLength) { |
| invalidate(); |
| return; |
| } |
| |
| Vector<RefPtr<Float32Array>> channels; |
| channels.reserveInitialCapacity(numberOfChannels); |
| |
| for (unsigned i = 0; i < numberOfChannels; ++i) { |
| auto channelDataArray = Float32Array::tryCreate(m_originalLength); |
| if (!channelDataArray) { |
| invalidate(); |
| return; |
| } |
| |
| if (preventDetaching == LegacyPreventDetaching::Yes) |
| channelDataArray->setDetachable(false); |
| |
| channels.uncheckedAppend(WTFMove(channelDataArray)); |
| } |
| |
| m_channels = WTFMove(channels); |
| m_channelWrappers = FixedVector<JSValueInWrappedObject> { m_channels.size() }; |
| } |
| |
| AudioBuffer::AudioBuffer(AudioBus& bus) |
| : m_sampleRate(bus.sampleRate()) |
| , m_originalLength(bus.length()) |
| { |
| if (static_cast<uint64_t>(m_originalLength) > s_maxLength) { |
| invalidate(); |
| return; |
| } |
| |
| // Copy audio data from the bus to the Float32Arrays we manage. |
| unsigned numberOfChannels = bus.numberOfChannels(); |
| Vector<RefPtr<Float32Array>> channels; |
| channels.reserveInitialCapacity(numberOfChannels); |
| for (unsigned i = 0; i < numberOfChannels; ++i) { |
| auto channelDataArray = Float32Array::tryCreate(m_originalLength); |
| if (!channelDataArray) { |
| invalidate(); |
| return; |
| } |
| |
| channelDataArray->setRange(bus.channel(i)->data(), m_originalLength, 0); |
| channels.uncheckedAppend(WTFMove(channelDataArray)); |
| } |
| |
| m_channels = WTFMove(channels); |
| m_channelWrappers = FixedVector<JSValueInWrappedObject> { m_channels.size() }; |
| } |
| |
| void AudioBuffer::invalidate() |
| { |
| releaseMemory(); |
| m_originalLength = 0; |
| } |
| |
| void AudioBuffer::releaseMemory() |
| { |
| Locker locker { m_channelsLock }; |
| m_channels = { }; |
| m_channelWrappers = { }; |
| } |
| |
| ExceptionOr<JSC::JSValue> AudioBuffer::getChannelData(JSDOMGlobalObject& globalObject, unsigned channelIndex) |
| { |
| ASSERT(m_channelWrappers.size() == m_channels.size()); |
| if (channelIndex >= m_channelWrappers.size()) |
| return Exception { IndexSizeError, "Index must be less than number of channels."_s }; |
| |
| auto& channelData = m_channels[channelIndex]; |
| auto constructJSArray = [&] { |
| return JSC::JSFloat32Array::create(globalObject.vm(), globalObject.typedArrayStructure(JSC::TypeFloat32), channelData.copyRef()); |
| }; |
| |
| if (globalObject.worldIsNormal()) { |
| if (!m_channelWrappers[channelIndex]) |
| m_channelWrappers[channelIndex].setWeakly(constructJSArray()); |
| return m_channelWrappers[channelIndex].getValue(); |
| } |
| return constructJSArray(); |
| } |
| |
| template<typename Visitor> |
| void AudioBuffer::visitChannelWrappers(Visitor& visitor) |
| { |
| Locker locker { m_channelsLock }; |
| for (auto& channelWrapper : m_channelWrappers) |
| channelWrapper.visit(visitor); |
| } |
| |
| template void AudioBuffer::visitChannelWrappers(JSC::AbstractSlotVisitor&); |
| template void AudioBuffer::visitChannelWrappers(JSC::SlotVisitor&); |
| |
| RefPtr<Float32Array> AudioBuffer::channelData(unsigned channelIndex) |
| { |
| if (channelIndex >= m_channels.size()) |
| return nullptr; |
| if (hasDetachedChannelBuffer()) |
| return Float32Array::create(0); |
| return m_channels[channelIndex].copyRef(); |
| } |
| |
| float* AudioBuffer::rawChannelData(unsigned channelIndex) |
| { |
| if (channelIndex >= m_channels.size()) |
| return nullptr; |
| if (hasDetachedChannelBuffer()) |
| return nullptr; |
| return m_channels[channelIndex]->data(); |
| } |
| |
| ExceptionOr<void> AudioBuffer::copyFromChannel(Ref<Float32Array>&& destination, unsigned channelNumber, unsigned bufferOffset) |
| { |
| if (destination->isShared()) |
| return Exception { TypeError, "Destination may not be a shared buffer."_s }; |
| |
| if (channelNumber >= m_channels.size()) |
| return Exception { IndexSizeError, "Not a valid channelNumber."_s }; |
| |
| Float32Array* channelData = m_channels[channelNumber].get(); |
| |
| size_t dataLength = channelData->length(); |
| |
| if (bufferOffset >= dataLength) |
| return { }; |
| |
| size_t count = dataLength - bufferOffset; |
| count = std::min(destination.get().length(), count); |
| |
| const float* src = channelData->data(); |
| float* dst = destination->data(); |
| |
| ASSERT(src); |
| ASSERT(dst); |
| |
| memmove(dst, src + bufferOffset, count * sizeof(*src)); |
| return { }; |
| } |
| |
| ExceptionOr<void> AudioBuffer::copyToChannel(Ref<Float32Array>&& source, unsigned channelNumber, unsigned bufferOffset) |
| { |
| if (source->isShared()) |
| return Exception { TypeError, "Source may not be a shared buffer."_s }; |
| |
| if (channelNumber >= m_channels.size()) |
| return Exception { IndexSizeError, "Not a valid channelNumber."_s }; |
| |
| Float32Array* channelData = m_channels[channelNumber].get(); |
| |
| size_t dataLength = channelData->length(); |
| |
| if (bufferOffset >= dataLength) |
| return { }; |
| |
| size_t count = dataLength - bufferOffset; |
| count = std::min(source.get().length(), count); |
| |
| const float* src = source->data(); |
| float* dst = channelData->data(); |
| |
| ASSERT(src); |
| ASSERT(dst); |
| |
| memmove(dst + bufferOffset, src, count * sizeof(*dst)); |
| return { }; |
| } |
| |
| void AudioBuffer::zero() |
| { |
| for (auto& channel : m_channels) |
| channel->zeroFill(); |
| } |
| |
| size_t AudioBuffer::memoryCost() const |
| { |
| // memoryCost() may be invoked concurrently from a GC thread, and we need to be careful |
| // about what data we access here and how. We need to hold a lock to prevent m_channels |
| // from being changed while we iterate it, but calling channel->byteLength() is safe |
| // because it doesn't involve chasing any pointers that can be nullified while the |
| // AudioBuffer is alive. |
| Locker locker { m_channelsLock }; |
| size_t cost = 0; |
| for (auto& channel : m_channels) |
| cost += channel->byteLength(); |
| return cost; |
| } |
| |
| bool AudioBuffer::hasDetachedChannelBuffer() const |
| { |
| for (auto& channel : m_channels) { |
| if (channel->isDetached()) |
| return true; |
| } |
| return false; |
| } |
| |
| bool AudioBuffer::topologyMatches(const AudioBuffer& other) const |
| { |
| return numberOfChannels() == other.numberOfChannels() && length() == other.length() && sampleRate() == other.sampleRate(); |
| } |
| |
| bool AudioBuffer::copyTo(AudioBuffer& other) const |
| { |
| if (!topologyMatches(other)) |
| return false; |
| |
| if (hasDetachedChannelBuffer() || other.hasDetachedChannelBuffer()) |
| return false; |
| |
| for (unsigned channelIndex = 0; channelIndex < numberOfChannels(); ++channelIndex) |
| memcpy(other.rawChannelData(channelIndex), m_channels[channelIndex]->data(), length() * sizeof(float)); |
| |
| return true; |
| } |
| |
| Ref<AudioBuffer> AudioBuffer::clone(ShouldCopyChannelData shouldCopyChannelData) const |
| { |
| auto clone = AudioBuffer::create(numberOfChannels(), length(), sampleRate(), m_isDetachable ? LegacyPreventDetaching::No : LegacyPreventDetaching::Yes); |
| ASSERT(clone); |
| if (shouldCopyChannelData == ShouldCopyChannelData::Yes) |
| copyTo(*clone); |
| return clone.releaseNonNull(); |
| } |
| |
| WebCoreOpaqueRoot root(AudioBuffer* buffer) |
| { |
| return WebCoreOpaqueRoot { buffer }; |
| } |
| |
| } // namespace WebCore |
| |
| #endif // ENABLE(WEB_AUDIO) |