| 1 | /* |
| 2 | * Copyright (C) 2010, Google Inc. All rights reserved. |
| 3 | * |
| 4 | * Redistribution and use in source and binary forms, with or without |
| 5 | * modification, are permitted provided that the following conditions |
| 6 | * are met: |
| 7 | * 1. Redistributions of source code must retain the above copyright |
| 8 | * notice, this list of conditions and the following disclaimer. |
| 9 | * 2. Redistributions in binary form must reproduce the above copyright |
| 10 | * notice, this list of conditions and the following disclaimer in the |
| 11 | * documentation and/or other materials provided with the distribution. |
| 12 | * |
| 13 | * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY |
| 14 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
| 15 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| 16 | * DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY |
| 17 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
| 18 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
| 19 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON |
| 20 | * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 21 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
| 22 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 23 | */ |
| 24 | |
| 25 | #include "config.h" |
| 26 | |
| 27 | #if ENABLE(WEB_AUDIO) |
| 28 | |
| 29 | #include "AudioBufferSourceNode.h" |
| 30 | |
| 31 | #include "AudioBuffer.h" |
| 32 | #include "AudioContext.h" |
| 33 | #include "AudioNodeOutput.h" |
| 34 | #include "AudioParam.h" |
| 35 | #include "AudioUtilities.h" |
| 36 | #include "FloatConversion.h" |
| 37 | #include "PannerNode.h" |
| 38 | #include "ScriptExecutionContext.h" |
| 39 | #include <wtf/IsoMallocInlines.h> |
| 40 | |
| 41 | namespace WebCore { |
| 42 | |
| 43 | WTF_MAKE_ISO_ALLOCATED_IMPL(AudioBufferSourceNode); |
| 44 | |
| 45 | const double DefaultGrainDuration = 0.020; // 20ms |
| 46 | |
| 47 | // Arbitrary upper limit on playback rate. |
| 48 | // Higher than expected rates can be useful when playing back oversampled buffers |
| 49 | // to minimize linear interpolation aliasing. |
| 50 | const double MaxRate = 1024; |
| 51 | |
| 52 | Ref<AudioBufferSourceNode> AudioBufferSourceNode::create(AudioContext& context, float sampleRate) |
| 53 | { |
| 54 | return adoptRef(*new AudioBufferSourceNode(context, sampleRate)); |
| 55 | } |
| 56 | |
| 57 | AudioBufferSourceNode::AudioBufferSourceNode(AudioContext& context, float sampleRate) |
| 58 | : AudioScheduledSourceNode(context, sampleRate) |
| 59 | , m_buffer(nullptr) |
| 60 | , m_isLooping(false) |
| 61 | , m_loopStart(0) |
| 62 | , m_loopEnd(0) |
| 63 | , m_virtualReadIndex(0) |
| 64 | , m_isGrain(false) |
| 65 | , m_grainOffset(0.0) |
| 66 | , m_grainDuration(DefaultGrainDuration) |
| 67 | , m_lastGain(1.0) |
| 68 | , m_pannerNode(nullptr) |
| 69 | { |
| 70 | setNodeType(NodeTypeAudioBufferSource); |
| 71 | |
| 72 | m_gain = AudioParam::create(context, "gain" , 1.0, 0.0, 1.0); |
| 73 | m_playbackRate = AudioParam::create(context, "playbackRate" , 1.0, -MaxRate, MaxRate); |
| 74 | |
| 75 | // Default to mono. A call to setBuffer() will set the number of output channels to that of the buffer. |
| 76 | addOutput(std::make_unique<AudioNodeOutput>(this, 1)); |
| 77 | |
| 78 | initialize(); |
| 79 | } |
| 80 | |
| 81 | AudioBufferSourceNode::~AudioBufferSourceNode() |
| 82 | { |
| 83 | clearPannerNode(); |
| 84 | uninitialize(); |
| 85 | } |
| 86 | |
| 87 | void AudioBufferSourceNode::process(size_t framesToProcess) |
| 88 | { |
| 89 | auto& outputBus = *output(0)->bus(); |
| 90 | |
| 91 | if (!isInitialized()) { |
| 92 | outputBus.zero(); |
| 93 | return; |
| 94 | } |
| 95 | |
| 96 | // The audio thread can't block on this lock, so we use std::try_to_lock instead. |
| 97 | std::unique_lock<Lock> lock(m_processMutex, std::try_to_lock); |
| 98 | if (!lock.owns_lock()) { |
| 99 | // Too bad - the try_lock() failed. We must be in the middle of changing buffers and were already outputting silence anyway. |
| 100 | outputBus.zero(); |
| 101 | return; |
| 102 | } |
| 103 | |
| 104 | if (!buffer()) { |
| 105 | outputBus.zero(); |
| 106 | return; |
| 107 | } |
| 108 | |
| 109 | // After calling setBuffer() with a buffer having a different number of channels, there can in rare cases be a slight delay |
| 110 | // before the output bus is updated to the new number of channels because of use of tryLocks() in the context's updating system. |
| 111 | // In this case, if the buffer has just been changed and we're not quite ready yet, then just output silence. |
| 112 | if (numberOfChannels() != buffer()->numberOfChannels()) { |
| 113 | outputBus.zero(); |
| 114 | return; |
| 115 | } |
| 116 | |
| 117 | size_t quantumFrameOffset = 0; |
| 118 | size_t bufferFramesToProcess = 0; |
| 119 | updateSchedulingInfo(framesToProcess, outputBus, quantumFrameOffset, bufferFramesToProcess); |
| 120 | |
| 121 | if (!bufferFramesToProcess) { |
| 122 | outputBus.zero(); |
| 123 | return; |
| 124 | } |
| 125 | |
| 126 | for (unsigned i = 0; i < outputBus.numberOfChannels(); ++i) |
| 127 | m_destinationChannels[i] = outputBus.channel(i)->mutableData(); |
| 128 | |
| 129 | // Render by reading directly from the buffer. |
| 130 | if (!renderFromBuffer(&outputBus, quantumFrameOffset, bufferFramesToProcess)) { |
| 131 | outputBus.zero(); |
| 132 | return; |
| 133 | } |
| 134 | |
| 135 | // Apply the gain (in-place) to the output bus. |
| 136 | float totalGain = gain()->value() * m_buffer->gain(); |
| 137 | outputBus.copyWithGainFrom(outputBus, &m_lastGain, totalGain); |
| 138 | outputBus.clearSilentFlag(); |
| 139 | } |
| 140 | |
| 141 | // Returns true if we're finished. |
| 142 | bool AudioBufferSourceNode::renderSilenceAndFinishIfNotLooping(AudioBus*, unsigned index, size_t framesToProcess) |
| 143 | { |
| 144 | if (!loop()) { |
| 145 | // If we're not looping, then stop playing when we get to the end. |
| 146 | |
| 147 | if (framesToProcess > 0) { |
| 148 | // We're not looping and we've reached the end of the sample data, but we still need to provide more output, |
| 149 | // so generate silence for the remaining. |
| 150 | for (unsigned i = 0; i < numberOfChannels(); ++i) |
| 151 | memset(m_destinationChannels[i] + index, 0, sizeof(float) * framesToProcess); |
| 152 | } |
| 153 | |
| 154 | finish(); |
| 155 | return true; |
| 156 | } |
| 157 | return false; |
| 158 | } |
| 159 | |
| 160 | bool AudioBufferSourceNode::renderFromBuffer(AudioBus* bus, unsigned destinationFrameOffset, size_t numberOfFrames) |
| 161 | { |
| 162 | ASSERT(context().isAudioThread()); |
| 163 | |
| 164 | // Basic sanity checking |
| 165 | ASSERT(bus); |
| 166 | ASSERT(buffer()); |
| 167 | if (!bus || !buffer()) |
| 168 | return false; |
| 169 | |
| 170 | unsigned numberOfChannels = this->numberOfChannels(); |
| 171 | unsigned busNumberOfChannels = bus->numberOfChannels(); |
| 172 | |
| 173 | bool channelCountGood = numberOfChannels && numberOfChannels == busNumberOfChannels; |
| 174 | ASSERT(channelCountGood); |
| 175 | if (!channelCountGood) |
| 176 | return false; |
| 177 | |
| 178 | // Sanity check destinationFrameOffset, numberOfFrames. |
| 179 | size_t destinationLength = bus->length(); |
| 180 | |
| 181 | bool isLengthGood = destinationLength <= 4096 && numberOfFrames <= 4096; |
| 182 | ASSERT(isLengthGood); |
| 183 | if (!isLengthGood) |
| 184 | return false; |
| 185 | |
| 186 | bool isOffsetGood = destinationFrameOffset <= destinationLength && destinationFrameOffset + numberOfFrames <= destinationLength; |
| 187 | ASSERT(isOffsetGood); |
| 188 | if (!isOffsetGood) |
| 189 | return false; |
| 190 | |
| 191 | // Potentially zero out initial frames leading up to the offset. |
| 192 | if (destinationFrameOffset) { |
| 193 | for (unsigned i = 0; i < numberOfChannels; ++i) |
| 194 | memset(m_destinationChannels[i], 0, sizeof(float) * destinationFrameOffset); |
| 195 | } |
| 196 | |
| 197 | // Offset the pointers to the correct offset frame. |
| 198 | unsigned writeIndex = destinationFrameOffset; |
| 199 | |
| 200 | size_t bufferLength = buffer()->length(); |
| 201 | double bufferSampleRate = buffer()->sampleRate(); |
| 202 | double pitchRate = totalPitchRate(); |
| 203 | bool reverse = pitchRate < 0; |
| 204 | |
| 205 | // Avoid converting from time to sample-frames twice by computing |
| 206 | // the grain end time first before computing the sample frame. |
| 207 | unsigned maxFrame; |
| 208 | if (m_isGrain) |
| 209 | maxFrame = AudioUtilities::timeToSampleFrame(m_grainOffset + m_grainDuration, bufferSampleRate); |
| 210 | else |
| 211 | maxFrame = bufferLength; |
| 212 | |
| 213 | // Do some sanity checking. |
| 214 | if (maxFrame > bufferLength) |
| 215 | maxFrame = bufferLength; |
| 216 | if (reverse && m_virtualReadIndex <= 0) |
| 217 | m_virtualReadIndex = maxFrame - 1; |
| 218 | else if (!reverse && m_virtualReadIndex >= maxFrame) |
| 219 | m_virtualReadIndex = 0; // reset to start |
| 220 | |
| 221 | // If the .loop attribute is true, then values of m_loopStart == 0 && m_loopEnd == 0 implies |
| 222 | // that we should use the entire buffer as the loop, otherwise use the loop values in m_loopStart and m_loopEnd. |
| 223 | double virtualMaxFrame = maxFrame; |
| 224 | double virtualMinFrame = 0; |
| 225 | double virtualDeltaFrames = maxFrame; |
| 226 | |
| 227 | if (loop() && (m_loopStart || m_loopEnd) && m_loopStart >= 0 && m_loopEnd > 0 && m_loopStart < m_loopEnd) { |
| 228 | // Convert from seconds to sample-frames. |
| 229 | double loopMinFrame = m_loopStart * buffer()->sampleRate(); |
| 230 | double loopMaxFrame = m_loopEnd * buffer()->sampleRate(); |
| 231 | |
| 232 | virtualMaxFrame = std::min(loopMaxFrame, virtualMaxFrame); |
| 233 | virtualMinFrame = std::max(loopMinFrame, virtualMinFrame); |
| 234 | virtualDeltaFrames = virtualMaxFrame - virtualMinFrame; |
| 235 | } |
| 236 | |
| 237 | |
| 238 | // Sanity check that our playback rate isn't larger than the loop size. |
| 239 | if (fabs(pitchRate) >= virtualDeltaFrames) |
| 240 | return false; |
| 241 | |
| 242 | // Get local copy. |
| 243 | double virtualReadIndex = m_virtualReadIndex; |
| 244 | |
| 245 | bool needsInterpolation = virtualReadIndex != floor(virtualReadIndex) |
| 246 | || virtualDeltaFrames != floor(virtualDeltaFrames) |
| 247 | || virtualMaxFrame != floor(virtualMaxFrame) |
| 248 | || virtualMinFrame != floor(virtualMinFrame); |
| 249 | |
| 250 | // Render loop - reading from the source buffer to the destination using linear interpolation. |
| 251 | int framesToProcess = numberOfFrames; |
| 252 | |
| 253 | const float** sourceChannels = m_sourceChannels.get(); |
| 254 | float** destinationChannels = m_destinationChannels.get(); |
| 255 | |
| 256 | // Optimize for the very common case of playing back with pitchRate == 1. |
| 257 | // We can avoid the linear interpolation. |
| 258 | if (pitchRate == 1 && !needsInterpolation) { |
| 259 | unsigned readIndex = static_cast<unsigned>(virtualReadIndex); |
| 260 | unsigned deltaFrames = static_cast<unsigned>(virtualDeltaFrames); |
| 261 | maxFrame = static_cast<unsigned>(virtualMaxFrame); |
| 262 | while (framesToProcess > 0) { |
| 263 | int framesToEnd = maxFrame - readIndex; |
| 264 | int framesThisTime = std::min(framesToProcess, framesToEnd); |
| 265 | framesThisTime = std::max(0, framesThisTime); |
| 266 | |
| 267 | for (unsigned i = 0; i < numberOfChannels; ++i) |
| 268 | memcpy(destinationChannels[i] + writeIndex, sourceChannels[i] + readIndex, sizeof(float) * framesThisTime); |
| 269 | |
| 270 | writeIndex += framesThisTime; |
| 271 | readIndex += framesThisTime; |
| 272 | framesToProcess -= framesThisTime; |
| 273 | |
| 274 | // Wrap-around. |
| 275 | if (readIndex >= maxFrame) { |
| 276 | readIndex -= deltaFrames; |
| 277 | if (renderSilenceAndFinishIfNotLooping(bus, writeIndex, framesToProcess)) |
| 278 | break; |
| 279 | } |
| 280 | } |
| 281 | virtualReadIndex = readIndex; |
| 282 | } else if (pitchRate == -1 && !needsInterpolation) { |
| 283 | int readIndex = static_cast<int>(virtualReadIndex); |
| 284 | int deltaFrames = static_cast<int>(virtualDeltaFrames); |
| 285 | int minFrame = static_cast<int>(virtualMinFrame) - 1; |
| 286 | while (framesToProcess > 0) { |
| 287 | int framesToEnd = readIndex - minFrame; |
| 288 | int framesThisTime = std::min<int>(framesToProcess, framesToEnd); |
| 289 | framesThisTime = std::max<int>(0, framesThisTime); |
| 290 | |
| 291 | while (framesThisTime--) { |
| 292 | for (unsigned i = 0; i < numberOfChannels; ++i) { |
| 293 | float* destination = destinationChannels[i]; |
| 294 | const float* source = sourceChannels[i]; |
| 295 | |
| 296 | destination[writeIndex] = source[readIndex]; |
| 297 | } |
| 298 | |
| 299 | ++writeIndex; |
| 300 | --readIndex; |
| 301 | --framesToProcess; |
| 302 | } |
| 303 | |
| 304 | // Wrap-around. |
| 305 | if (readIndex <= minFrame) { |
| 306 | readIndex += deltaFrames; |
| 307 | if (renderSilenceAndFinishIfNotLooping(bus, writeIndex, framesToProcess)) |
| 308 | break; |
| 309 | } |
| 310 | } |
| 311 | virtualReadIndex = readIndex; |
| 312 | } else if (!pitchRate) { |
| 313 | unsigned readIndex = static_cast<unsigned>(virtualReadIndex); |
| 314 | |
| 315 | for (unsigned i = 0; i < numberOfChannels; ++i) |
| 316 | std::fill_n(destinationChannels[i], framesToProcess, sourceChannels[i][readIndex]); |
| 317 | } else if (reverse) { |
| 318 | unsigned maxFrame = static_cast<unsigned>(virtualMaxFrame); |
| 319 | unsigned minFrame = static_cast<unsigned>(floorf(virtualMinFrame)); |
| 320 | |
| 321 | while (framesToProcess--) { |
| 322 | unsigned readIndex = static_cast<unsigned>(floorf(virtualReadIndex)); |
| 323 | double interpolationFactor = virtualReadIndex - readIndex; |
| 324 | |
| 325 | unsigned readIndex2 = readIndex + 1; |
| 326 | if (readIndex2 >= maxFrame) |
| 327 | readIndex2 = loop() ? minFrame : maxFrame - 1; |
| 328 | |
| 329 | // Linear interpolation. |
| 330 | for (unsigned i = 0; i < numberOfChannels; ++i) { |
| 331 | float* destination = destinationChannels[i]; |
| 332 | const float* source = sourceChannels[i]; |
| 333 | |
| 334 | double sample1 = source[readIndex]; |
| 335 | double sample2 = source[readIndex2]; |
| 336 | double sample = (1.0 - interpolationFactor) * sample1 + interpolationFactor * sample2; |
| 337 | |
| 338 | destination[writeIndex] = narrowPrecisionToFloat(sample); |
| 339 | } |
| 340 | |
| 341 | writeIndex++; |
| 342 | |
| 343 | virtualReadIndex += pitchRate; |
| 344 | |
| 345 | // Wrap-around, retaining sub-sample position since virtualReadIndex is floating-point. |
| 346 | if (virtualReadIndex < virtualMinFrame) { |
| 347 | virtualReadIndex += virtualDeltaFrames; |
| 348 | if (renderSilenceAndFinishIfNotLooping(bus, writeIndex, framesToProcess)) |
| 349 | break; |
| 350 | } |
| 351 | } |
| 352 | } else { |
| 353 | while (framesToProcess--) { |
| 354 | unsigned readIndex = static_cast<unsigned>(virtualReadIndex); |
| 355 | double interpolationFactor = virtualReadIndex - readIndex; |
| 356 | |
| 357 | // For linear interpolation we need the next sample-frame too. |
| 358 | unsigned readIndex2 = readIndex + 1; |
| 359 | if (readIndex2 >= bufferLength) { |
| 360 | if (loop()) { |
| 361 | // Make sure to wrap around at the end of the buffer. |
| 362 | readIndex2 = static_cast<unsigned>(virtualReadIndex + 1 - virtualDeltaFrames); |
| 363 | } else |
| 364 | readIndex2 = readIndex; |
| 365 | } |
| 366 | |
| 367 | // Final sanity check on buffer access. |
| 368 | // FIXME: as an optimization, try to get rid of this inner-loop check and put assertions and guards before the loop. |
| 369 | if (readIndex >= bufferLength || readIndex2 >= bufferLength) |
| 370 | break; |
| 371 | |
| 372 | // Linear interpolation. |
| 373 | for (unsigned i = 0; i < numberOfChannels; ++i) { |
| 374 | float* destination = destinationChannels[i]; |
| 375 | const float* source = sourceChannels[i]; |
| 376 | |
| 377 | double sample1 = source[readIndex]; |
| 378 | double sample2 = source[readIndex2]; |
| 379 | double sample = (1.0 - interpolationFactor) * sample1 + interpolationFactor * sample2; |
| 380 | |
| 381 | destination[writeIndex] = narrowPrecisionToFloat(sample); |
| 382 | } |
| 383 | writeIndex++; |
| 384 | |
| 385 | virtualReadIndex += pitchRate; |
| 386 | |
| 387 | // Wrap-around, retaining sub-sample position since virtualReadIndex is floating-point. |
| 388 | if (virtualReadIndex >= virtualMaxFrame) { |
| 389 | virtualReadIndex -= virtualDeltaFrames; |
| 390 | if (renderSilenceAndFinishIfNotLooping(bus, writeIndex, framesToProcess)) |
| 391 | break; |
| 392 | } |
| 393 | } |
| 394 | } |
| 395 | |
| 396 | bus->clearSilentFlag(); |
| 397 | |
| 398 | m_virtualReadIndex = virtualReadIndex; |
| 399 | |
| 400 | return true; |
| 401 | } |
| 402 | |
| 403 | |
| 404 | void AudioBufferSourceNode::reset() |
| 405 | { |
| 406 | m_virtualReadIndex = 0; |
| 407 | m_lastGain = gain()->value(); |
| 408 | } |
| 409 | |
| 410 | void AudioBufferSourceNode::setBuffer(RefPtr<AudioBuffer>&& buffer) |
| 411 | { |
| 412 | ASSERT(isMainThread()); |
| 413 | DEBUG_LOG(LOGIDENTIFIER); |
| 414 | |
| 415 | // The context must be locked since changing the buffer can re-configure the number of channels that are output. |
| 416 | AudioContext::AutoLocker contextLocker(context()); |
| 417 | |
| 418 | // This synchronizes with process(). |
| 419 | std::lock_guard<Lock> lock(m_processMutex); |
| 420 | |
| 421 | if (buffer) { |
| 422 | // Do any necesssary re-configuration to the buffer's number of channels. |
| 423 | unsigned numberOfChannels = buffer->numberOfChannels(); |
| 424 | ASSERT(numberOfChannels <= AudioContext::maxNumberOfChannels()); |
| 425 | |
| 426 | output(0)->setNumberOfChannels(numberOfChannels); |
| 427 | |
| 428 | m_sourceChannels = makeUniqueArray<const float*>(numberOfChannels); |
| 429 | m_destinationChannels = makeUniqueArray<float*>(numberOfChannels); |
| 430 | |
| 431 | for (unsigned i = 0; i < numberOfChannels; ++i) |
| 432 | m_sourceChannels[i] = buffer->channelData(i)->data(); |
| 433 | } |
| 434 | |
| 435 | m_virtualReadIndex = 0; |
| 436 | m_buffer = WTFMove(buffer); |
| 437 | } |
| 438 | |
| 439 | unsigned AudioBufferSourceNode::numberOfChannels() |
| 440 | { |
| 441 | return output(0)->numberOfChannels(); |
| 442 | } |
| 443 | |
| 444 | ExceptionOr<void> AudioBufferSourceNode::start(double when, double grainOffset, Optional<double> optionalGrainDuration) |
| 445 | { |
| 446 | double grainDuration = 0; |
| 447 | if (optionalGrainDuration) |
| 448 | grainDuration = optionalGrainDuration.value(); |
| 449 | else if (buffer()) |
| 450 | grainDuration = buffer()->duration() - grainOffset; |
| 451 | |
| 452 | return startPlaying(Partial, when, grainOffset, grainDuration); |
| 453 | } |
| 454 | |
| 455 | ExceptionOr<void> AudioBufferSourceNode::startPlaying(BufferPlaybackMode playbackMode, double when, double grainOffset, double grainDuration) |
| 456 | { |
| 457 | ASSERT(isMainThread()); |
| 458 | ALWAYS_LOG(LOGIDENTIFIER, "when = " , when, ", offset = " , grainOffset, ", duration = " , grainDuration); |
| 459 | |
| 460 | context().nodeWillBeginPlayback(); |
| 461 | |
| 462 | if (m_playbackState != UNSCHEDULED_STATE) |
| 463 | return Exception { InvalidStateError }; |
| 464 | |
| 465 | if (!std::isfinite(when) || (when < 0)) |
| 466 | return Exception { InvalidStateError }; |
| 467 | |
| 468 | if (!std::isfinite(grainOffset) || (grainOffset < 0)) |
| 469 | return Exception { InvalidStateError }; |
| 470 | |
| 471 | if (!std::isfinite(grainDuration) || (grainDuration < 0)) |
| 472 | return Exception { InvalidStateError }; |
| 473 | |
| 474 | if (!buffer()) |
| 475 | return { }; |
| 476 | |
| 477 | m_isGrain = playbackMode == Partial; |
| 478 | if (m_isGrain) { |
| 479 | // Do sanity checking of grain parameters versus buffer size. |
| 480 | double bufferDuration = buffer()->duration(); |
| 481 | |
| 482 | m_grainOffset = std::min(bufferDuration, grainOffset); |
| 483 | |
| 484 | double maxDuration = bufferDuration - m_grainOffset; |
| 485 | m_grainDuration = std::min(maxDuration, grainDuration); |
| 486 | } else { |
| 487 | m_grainOffset = 0.0; |
| 488 | m_grainDuration = buffer()->duration(); |
| 489 | } |
| 490 | |
| 491 | m_startTime = when; |
| 492 | |
| 493 | // We call timeToSampleFrame here since at playbackRate == 1 we don't want to go through linear interpolation |
| 494 | // at a sub-sample position since it will degrade the quality. |
| 495 | // When aligned to the sample-frame the playback will be identical to the PCM data stored in the buffer. |
| 496 | // Since playbackRate == 1 is very common, it's worth considering quality. |
| 497 | if (totalPitchRate() < 0) |
| 498 | m_virtualReadIndex = AudioUtilities::timeToSampleFrame(m_grainOffset + m_grainDuration, buffer()->sampleRate()) - 1; |
| 499 | else |
| 500 | m_virtualReadIndex = AudioUtilities::timeToSampleFrame(m_grainOffset, buffer()->sampleRate()); |
| 501 | |
| 502 | m_playbackState = SCHEDULED_STATE; |
| 503 | |
| 504 | return { }; |
| 505 | } |
| 506 | |
| 507 | double AudioBufferSourceNode::totalPitchRate() |
| 508 | { |
| 509 | double dopplerRate = 1.0; |
| 510 | if (m_pannerNode) |
| 511 | dopplerRate = m_pannerNode->dopplerRate(); |
| 512 | |
| 513 | // Incorporate buffer's sample-rate versus AudioContext's sample-rate. |
| 514 | // Normally it's not an issue because buffers are loaded at the AudioContext's sample-rate, but we can handle it in any case. |
| 515 | double sampleRateFactor = 1.0; |
| 516 | if (buffer()) |
| 517 | sampleRateFactor = buffer()->sampleRate() / sampleRate(); |
| 518 | |
| 519 | double basePitchRate = playbackRate()->value(); |
| 520 | |
| 521 | double totalRate = dopplerRate * sampleRateFactor * basePitchRate; |
| 522 | |
| 523 | totalRate = std::max(-MaxRate, std::min(MaxRate, totalRate)); |
| 524 | |
| 525 | bool isTotalRateValid = !std::isnan(totalRate) && !std::isinf(totalRate); |
| 526 | ASSERT(isTotalRateValid); |
| 527 | if (!isTotalRateValid) |
| 528 | totalRate = 1.0; |
| 529 | |
| 530 | return totalRate; |
| 531 | } |
| 532 | |
| 533 | bool AudioBufferSourceNode::looping() |
| 534 | { |
| 535 | static bool firstTime = true; |
| 536 | if (firstTime) { |
| 537 | context().addConsoleMessage(MessageSource::JS, MessageLevel::Warning, "AudioBufferSourceNode 'looping' attribute is deprecated. Use 'loop' instead."_s ); |
| 538 | firstTime = false; |
| 539 | } |
| 540 | |
| 541 | return m_isLooping; |
| 542 | } |
| 543 | |
| 544 | void AudioBufferSourceNode::setLooping(bool looping) |
| 545 | { |
| 546 | static bool firstTime = true; |
| 547 | if (firstTime) { |
| 548 | context().addConsoleMessage(MessageSource::JS, MessageLevel::Warning, "AudioBufferSourceNode 'looping' attribute is deprecated. Use 'loop' instead."_s ); |
| 549 | firstTime = false; |
| 550 | } |
| 551 | |
| 552 | m_isLooping = looping; |
| 553 | } |
| 554 | |
| 555 | bool AudioBufferSourceNode::propagatesSilence() const |
| 556 | { |
| 557 | return !isPlayingOrScheduled() || hasFinished() || !m_buffer; |
| 558 | } |
| 559 | |
| 560 | void AudioBufferSourceNode::setPannerNode(PannerNode* pannerNode) |
| 561 | { |
| 562 | if (m_pannerNode != pannerNode && !hasFinished()) { |
| 563 | if (pannerNode) |
| 564 | pannerNode->ref(AudioNode::RefTypeConnection); |
| 565 | if (m_pannerNode) |
| 566 | m_pannerNode->deref(AudioNode::RefTypeConnection); |
| 567 | |
| 568 | m_pannerNode = pannerNode; |
| 569 | } |
| 570 | } |
| 571 | |
| 572 | void AudioBufferSourceNode::clearPannerNode() |
| 573 | { |
| 574 | if (m_pannerNode) { |
| 575 | m_pannerNode->deref(AudioNode::RefTypeConnection); |
| 576 | m_pannerNode = nullptr; |
| 577 | } |
| 578 | } |
| 579 | |
| 580 | void AudioBufferSourceNode::finish() |
| 581 | { |
| 582 | clearPannerNode(); |
| 583 | ASSERT(!m_pannerNode); |
| 584 | AudioScheduledSourceNode::finish(); |
| 585 | } |
| 586 | |
| 587 | } // namespace WebCore |
| 588 | |
| 589 | #endif // ENABLE(WEB_AUDIO) |
| 590 | |