void AudioBufferSourceNode::startPlaying(BufferPlaybackMode playbackMode, double when, double grainOffset, double grainDuration, ExceptionCode& ec) { ASSERT(isMainThread()); context()->nodeWillBeginPlayback(); if (m_playbackState != UNSCHEDULED_STATE) { ec = INVALID_STATE_ERR; return; } if (!std::isfinite(when) || (when < 0)) { ec = INVALID_STATE_ERR; return; } if (!std::isfinite(grainOffset) || (grainOffset < 0)) { ec = INVALID_STATE_ERR; return; } if (!std::isfinite(grainDuration) || (grainDuration < 0)) { ec = INVALID_STATE_ERR; return; } if (!buffer()) return; m_isGrain = playbackMode == Partial; if (m_isGrain) { // Do sanity checking of grain parameters versus buffer size. double bufferDuration = buffer()->duration(); m_grainOffset = std::min(bufferDuration, grainOffset); double maxDuration = bufferDuration - m_grainOffset; m_grainDuration = std::min(maxDuration, grainDuration); } else { m_grainOffset = 0.0; m_grainDuration = buffer()->duration(); } m_startTime = when; // We call timeToSampleFrame here since at playbackRate == 1 we don't want to go through linear interpolation // at a sub-sample position since it will degrade the quality. // When aligned to the sample-frame the playback will be identical to the PCM data stored in the buffer. // Since playbackRate == 1 is very common, it's worth considering quality. if (totalPitchRate() < 0) m_virtualReadIndex = AudioUtilities::timeToSampleFrame(m_grainOffset + m_grainDuration, buffer()->sampleRate()) - 1; else m_virtualReadIndex = AudioUtilities::timeToSampleFrame(m_grainOffset, buffer()->sampleRate()); m_playbackState = SCHEDULED_STATE; }
void AudioBufferSourceNode::renderFromBuffer(AudioBus* bus, unsigned destinationFrameOffset, size_t numberOfFrames) { ASSERT(context()->isAudioThread()); // Basic sanity checking ASSERT(bus); ASSERT(buffer()); if (!bus || !buffer()) return; unsigned numberOfChannels = this->numberOfChannels(); unsigned busNumberOfChannels = bus->numberOfChannels(); bool channelCountGood = numberOfChannels && numberOfChannels == busNumberOfChannels; ASSERT(channelCountGood); if (!channelCountGood) return; // Sanity check destinationFrameOffset, numberOfFrames. size_t destinationLength = bus->length(); bool isLengthGood = destinationLength <= 4096 && numberOfFrames <= 4096; ASSERT(isLengthGood); if (!isLengthGood) return; bool isOffsetGood = destinationFrameOffset <= destinationLength && destinationFrameOffset + numberOfFrames <= destinationLength; ASSERT(isOffsetGood); if (!isOffsetGood) return; // Potentially zero out initial frames leading up to the offset. if (destinationFrameOffset) { for (unsigned i = 0; i < numberOfChannels; ++i) memset(m_destinationChannels[i], 0, sizeof(float) * destinationFrameOffset); } // Offset the pointers to the correct offset frame. unsigned writeIndex = destinationFrameOffset; size_t bufferLength = buffer()->length(); double bufferSampleRate = buffer()->sampleRate(); // Calculate the start and end frames in our buffer that we want to play. // If m_isGrain is true, then we will be playing a portion of the total buffer. unsigned startFrame = m_isGrain ? AudioUtilities::timeToSampleFrame(m_grainOffset, bufferSampleRate) : 0; // Avoid converting from time to sample-frames twice by computing // the grain end time first before computing the sample frame. unsigned endFrame = m_isGrain ? AudioUtilities::timeToSampleFrame(m_grainOffset + m_grainDuration, bufferSampleRate) : bufferLength; ASSERT(endFrame >= startFrame); if (endFrame < startFrame) return; unsigned deltaFrames = endFrame - startFrame; // This is a HACK to allow for HRTF tail-time - avoids glitch at end. // FIXME: implement tailTime for each AudioNode for a more general solution to this problem. if (m_isGrain) endFrame += 512; // Do some sanity checking. if (startFrame >= bufferLength) startFrame = !bufferLength ? 0 : bufferLength - 1; if (endFrame > bufferLength) endFrame = bufferLength; if (m_virtualReadIndex >= endFrame) m_virtualReadIndex = startFrame; // reset to start double pitchRate = totalPitchRate(); // Get local copy. double virtualReadIndex = m_virtualReadIndex; // Render loop - reading from the source buffer to the destination using linear interpolation. int framesToProcess = numberOfFrames; const float** sourceChannels = m_sourceChannels.get(); float** destinationChannels = m_destinationChannels.get(); // Optimize for the very common case of playing back with pitchRate == 1. // We can avoid the linear interpolation. if (pitchRate == 1 && virtualReadIndex == floor(virtualReadIndex)) { unsigned readIndex = static_cast<unsigned>(virtualReadIndex); while (framesToProcess > 0) { int framesToEnd = endFrame - readIndex; int framesThisTime = min(framesToProcess, framesToEnd); framesThisTime = max(0, framesThisTime); for (unsigned i = 0; i < numberOfChannels; ++i) memcpy(destinationChannels[i] + writeIndex, sourceChannels[i] + readIndex, sizeof(float) * framesThisTime); writeIndex += framesThisTime; readIndex += framesThisTime; framesToProcess -= framesThisTime; // Wrap-around. if (readIndex >= endFrame) { readIndex -= deltaFrames; if (renderSilenceAndFinishIfNotLooping(bus, writeIndex, framesToProcess)) break; } } virtualReadIndex = readIndex; } else { while (framesToProcess--) { unsigned readIndex = static_cast<unsigned>(virtualReadIndex); double interpolationFactor = virtualReadIndex - readIndex; // For linear interpolation we need the next sample-frame too. unsigned readIndex2 = readIndex + 1; if (readIndex2 >= endFrame) { if (loop()) { // Make sure to wrap around at the end of the buffer. readIndex2 -= deltaFrames; } else readIndex2 = readIndex; } // Final sanity check on buffer access. // FIXME: as an optimization, try to get rid of this inner-loop check and put assertions and guards before the loop. if (readIndex >= bufferLength || readIndex2 >= bufferLength) break; // Linear interpolation. for (unsigned i = 0; i < numberOfChannels; ++i) { float* destination = destinationChannels[i]; const float* source = sourceChannels[i]; double sample1 = source[readIndex]; double sample2 = source[readIndex2]; double sample = (1.0 - interpolationFactor) * sample1 + interpolationFactor * sample2; destination[writeIndex] = narrowPrecisionToFloat(sample); } writeIndex++; virtualReadIndex += pitchRate; // Wrap-around, retaining sub-sample position since virtualReadIndex is floating-point. if (virtualReadIndex >= endFrame) { virtualReadIndex -= deltaFrames; if (renderSilenceAndFinishIfNotLooping(bus, writeIndex, framesToProcess)) break; } } } m_virtualReadIndex = virtualReadIndex; }
void AudioBufferSourceNode::renderFromBuffer(AudioBus* bus, unsigned destinationFrameOffset, size_t numberOfFrames) { ASSERT(context()->isAudioThread()); // Basic sanity checking ASSERT(bus); ASSERT(buffer()); if (!bus || !buffer()) return; unsigned numberOfChannels = this->numberOfChannels(); unsigned busNumberOfChannels = bus->numberOfChannels(); // FIXME: we can add support for sources with more than two channels, but this is not a common case. bool channelCountGood = numberOfChannels == busNumberOfChannels && (numberOfChannels == 1 || numberOfChannels == 2); ASSERT(channelCountGood); if (!channelCountGood) return; // Get the destination pointers. float* destinationL = bus->channel(0)->data(); ASSERT(destinationL); if (!destinationL) return; float* destinationR = (numberOfChannels < 2) ? 0 : bus->channel(1)->data(); bool isStereo = destinationR; // Sanity check destinationFrameOffset, numberOfFrames. size_t destinationLength = bus->length(); bool isLengthGood = destinationLength <= 4096 && numberOfFrames <= 4096; ASSERT(isLengthGood); if (!isLengthGood) return; bool isOffsetGood = destinationFrameOffset <= destinationLength && destinationFrameOffset + numberOfFrames <= destinationLength; ASSERT(isOffsetGood); if (!isOffsetGood) return; // Potentially zero out initial frames leading up to the offset. if (destinationFrameOffset) { memset(destinationL, 0, sizeof(float) * destinationFrameOffset); if (destinationR) memset(destinationR, 0, sizeof(float) * destinationFrameOffset); } // Offset the pointers to the correct offset frame. destinationL += destinationFrameOffset; if (destinationR) destinationR += destinationFrameOffset; size_t bufferLength = buffer()->length(); double bufferSampleRate = buffer()->sampleRate(); // Calculate the start and end frames in our buffer that we want to play. // If m_isGrain is true, then we will be playing a portion of the total buffer. unsigned startFrame = m_isGrain ? static_cast<unsigned>(m_grainOffset * bufferSampleRate) : 0; unsigned endFrame = m_isGrain ? static_cast<unsigned>(startFrame + m_grainDuration * bufferSampleRate) : bufferLength; ASSERT(endFrame >= startFrame); if (endFrame < startFrame) return; unsigned deltaFrames = endFrame - startFrame; // This is a HACK to allow for HRTF tail-time - avoids glitch at end. // FIXME: implement tailTime for each AudioNode for a more general solution to this problem. if (m_isGrain) endFrame += 512; // Do some sanity checking. if (startFrame >= bufferLength) startFrame = !bufferLength ? 0 : bufferLength - 1; if (endFrame > bufferLength) endFrame = bufferLength; if (m_virtualReadIndex >= endFrame) m_virtualReadIndex = startFrame; // reset to start // Get pointers to the start of the sample buffer. float* sourceL = m_buffer->getChannelData(0)->data(); float* sourceR = m_buffer->numberOfChannels() == 2 ? m_buffer->getChannelData(1)->data() : 0; double pitchRate = totalPitchRate(); // Get local copy. double virtualReadIndex = m_virtualReadIndex; // Render loop - reading from the source buffer to the destination using linear interpolation. // FIXME: optimize for the very common case of playing back with pitchRate == 1. // We can avoid the linear interpolation. int framesToProcess = numberOfFrames; while (framesToProcess--) { unsigned readIndex = static_cast<unsigned>(virtualReadIndex); double interpolationFactor = virtualReadIndex - readIndex; // For linear interpolation we need the next sample-frame too. unsigned readIndex2 = readIndex + 1; if (readIndex2 >= endFrame) { if (loop()) { // Make sure to wrap around at the end of the buffer. readIndex2 -= deltaFrames; } else readIndex2 = readIndex; } // Final sanity check on buffer access. // FIXME: as an optimization, try to get rid of this inner-loop check and put assertions and guards before the loop. if (readIndex >= bufferLength || readIndex2 >= bufferLength) break; // Linear interpolation. double sampleL1 = sourceL[readIndex]; double sampleL2 = sourceL[readIndex2]; double sampleL = (1.0 - interpolationFactor) * sampleL1 + interpolationFactor * sampleL2; *destinationL++ = narrowPrecisionToFloat(sampleL); if (isStereo) { double sampleR1 = sourceR[readIndex]; double sampleR2 = sourceR[readIndex2]; double sampleR = (1.0 - interpolationFactor) * sampleR1 + interpolationFactor * sampleR2; *destinationR++ = narrowPrecisionToFloat(sampleR); } virtualReadIndex += pitchRate; // Wrap-around, retaining sub-sample position since virtualReadIndex is floating-point. if (virtualReadIndex >= endFrame) { virtualReadIndex -= deltaFrames; if (!loop()) { // If we're not looping, then stop playing when we get to the end. m_isPlaying = false; if (framesToProcess > 0) { // We're not looping and we've reached the end of the sample data, but we still need to provide more output, // so generate silence for the remaining. memset(destinationL, 0, sizeof(float) * framesToProcess); if (isStereo) memset(destinationR, 0, sizeof(float) * framesToProcess); } finish(); break; } } } m_virtualReadIndex = virtualReadIndex; }
bool AudioBufferSourceNode::renderFromBuffer(AudioBus* bus, unsigned destinationFrameOffset, size_t numberOfFrames) { ASSERT(context()->isAudioThread()); // Basic sanity checking ASSERT(bus); ASSERT(buffer()); if (!bus || !buffer()) return false; unsigned numberOfChannels = this->numberOfChannels(); unsigned busNumberOfChannels = bus->numberOfChannels(); bool channelCountGood = numberOfChannels && numberOfChannels == busNumberOfChannels; ASSERT(channelCountGood); if (!channelCountGood) return false; // Sanity check destinationFrameOffset, numberOfFrames. size_t destinationLength = bus->length(); bool isLengthGood = destinationLength <= 4096 && numberOfFrames <= 4096; ASSERT(isLengthGood); if (!isLengthGood) return false; bool isOffsetGood = destinationFrameOffset <= destinationLength && destinationFrameOffset + numberOfFrames <= destinationLength; ASSERT(isOffsetGood); if (!isOffsetGood) return false; // Potentially zero out initial frames leading up to the offset. if (destinationFrameOffset) { for (unsigned i = 0; i < numberOfChannels; ++i) memset(m_destinationChannels[i], 0, sizeof(float) * destinationFrameOffset); } // Offset the pointers to the correct offset frame. unsigned writeIndex = destinationFrameOffset; size_t bufferLength = buffer()->length(); double bufferSampleRate = buffer()->sampleRate(); // Avoid converting from time to sample-frames twice by computing // the grain end time first before computing the sample frame. unsigned endFrame = m_isGrain ? AudioUtilities::timeToSampleFrame(m_grainOffset + m_grainDuration, bufferSampleRate) : bufferLength; // This is a HACK to allow for HRTF tail-time - avoids glitch at end. // FIXME: implement tailTime for each AudioNode for a more general solution to this problem. // https://bugs.webkit.org/show_bug.cgi?id=77224 if (m_isGrain) endFrame += 512; // Do some sanity checking. if (endFrame > bufferLength) endFrame = bufferLength; // If the .loop attribute is true, then values of m_loopStart == 0 && m_loopEnd == 0 implies // that we should use the entire buffer as the loop, otherwise use the loop values in m_loopStart and m_loopEnd. double virtualEndFrame = endFrame; double virtualDeltaFrames = endFrame; if (loop() && (m_loopStart || m_loopEnd) && m_loopStart >= 0 && m_loopEnd > 0 && m_loopStart < m_loopEnd) { // Convert from seconds to sample-frames. double loopStartFrame = m_loopStart * buffer()->sampleRate(); double loopEndFrame = m_loopEnd * buffer()->sampleRate(); virtualEndFrame = std::min(loopEndFrame, virtualEndFrame); virtualDeltaFrames = virtualEndFrame - loopStartFrame; } // If we're looping and the offset (virtualReadIndex) is past the end of the loop, wrap back to // the beginning of the loop. For other cases, nothing needs to be done. if (loop() && m_virtualReadIndex >= virtualEndFrame) m_virtualReadIndex = (m_loopStart < 0) ? 0 : (m_loopStart * buffer()->sampleRate()); double pitchRate = totalPitchRate(); // Sanity check that our playback rate isn't larger than the loop size. if (pitchRate > virtualDeltaFrames) return false; // Get local copy. double virtualReadIndex = m_virtualReadIndex; // Render loop - reading from the source buffer to the destination using linear interpolation. int framesToProcess = numberOfFrames; const float** sourceChannels = m_sourceChannels.get(); float** destinationChannels = m_destinationChannels.get(); ASSERT(virtualReadIndex >= 0); ASSERT(virtualDeltaFrames >= 0); ASSERT(virtualEndFrame >= 0); // Optimize for the very common case of playing back with pitchRate == 1. // We can avoid the linear interpolation. if (pitchRate == 1 && virtualReadIndex == floor(virtualReadIndex) && virtualDeltaFrames == floor(virtualDeltaFrames) && virtualEndFrame == floor(virtualEndFrame)) { unsigned readIndex = static_cast<unsigned>(virtualReadIndex); unsigned deltaFrames = static_cast<unsigned>(virtualDeltaFrames); endFrame = static_cast<unsigned>(virtualEndFrame); while (framesToProcess > 0) { int framesToEnd = endFrame - readIndex; int framesThisTime = std::min(framesToProcess, framesToEnd); framesThisTime = std::max(0, framesThisTime); for (unsigned i = 0; i < numberOfChannels; ++i) memcpy(destinationChannels[i] + writeIndex, sourceChannels[i] + readIndex, sizeof(float) * framesThisTime); writeIndex += framesThisTime; readIndex += framesThisTime; framesToProcess -= framesThisTime; // It can happen that framesThisTime is 0. Assert that we will actually exit the loop in // this case. framesThisTime is 0 only if readIndex >= endFrame; ASSERT(framesThisTime ? true : readIndex >= endFrame); // Wrap-around. if (readIndex >= endFrame) { readIndex -= deltaFrames; if (renderSilenceAndFinishIfNotLooping(bus, writeIndex, framesToProcess)) break; } } virtualReadIndex = readIndex; } else { while (framesToProcess--) { unsigned readIndex = static_cast<unsigned>(virtualReadIndex); double interpolationFactor = virtualReadIndex - readIndex; // For linear interpolation we need the next sample-frame too. unsigned readIndex2 = readIndex + 1; if (readIndex2 >= bufferLength) { if (loop()) { // Make sure to wrap around at the end of the buffer. readIndex2 = static_cast<unsigned>(virtualReadIndex + 1 - virtualDeltaFrames); } else readIndex2 = readIndex; } // Final sanity check on buffer access. // FIXME: as an optimization, try to get rid of this inner-loop check and put assertions and guards before the loop. if (readIndex >= bufferLength || readIndex2 >= bufferLength) break; // Linear interpolation. for (unsigned i = 0; i < numberOfChannels; ++i) { float* destination = destinationChannels[i]; const float* source = sourceChannels[i]; double sample1 = source[readIndex]; double sample2 = source[readIndex2]; double sample = (1.0 - interpolationFactor) * sample1 + interpolationFactor * sample2; destination[writeIndex] = narrowPrecisionToFloat(sample); } writeIndex++; virtualReadIndex += pitchRate; // Wrap-around, retaining sub-sample position since virtualReadIndex is floating-point. if (virtualReadIndex >= virtualEndFrame) { virtualReadIndex -= virtualDeltaFrames; if (renderSilenceAndFinishIfNotLooping(bus, writeIndex, framesToProcess)) break; } } } bus->clearSilentFlag(); m_virtualReadIndex = virtualReadIndex; return true; }
bool AudioBufferSourceNode::renderFromBuffer(AudioBus* bus, unsigned destinationFrameOffset, size_t numberOfFrames) { ASSERT(context()->isAudioThread()); // Basic sanity checking ASSERT(bus); ASSERT(buffer()); if (!bus || !buffer()) return false; unsigned numberOfChannels = this->numberOfChannels(); unsigned busNumberOfChannels = bus->numberOfChannels(); bool channelCountGood = numberOfChannels && numberOfChannels == busNumberOfChannels; ASSERT(channelCountGood); if (!channelCountGood) return false; // Sanity check destinationFrameOffset, numberOfFrames. size_t destinationLength = bus->length(); bool isLengthGood = destinationLength <= 4096 && numberOfFrames <= 4096; ASSERT(isLengthGood); if (!isLengthGood) return false; bool isOffsetGood = destinationFrameOffset <= destinationLength && destinationFrameOffset + numberOfFrames <= destinationLength; ASSERT(isOffsetGood); if (!isOffsetGood) return false; // Potentially zero out initial frames leading up to the offset. if (destinationFrameOffset) { for (unsigned i = 0; i < numberOfChannels; ++i) memset(m_destinationChannels[i], 0, sizeof(float) * destinationFrameOffset); } // Offset the pointers to the correct offset frame. unsigned writeIndex = destinationFrameOffset; size_t bufferLength = buffer()->length(); double bufferSampleRate = buffer()->sampleRate(); double pitchRate = totalPitchRate(); bool reverse = pitchRate < 0; // Avoid converting from time to sample-frames twice by computing // the grain end time first before computing the sample frame. unsigned maxFrame; if (m_isGrain) maxFrame = AudioUtilities::timeToSampleFrame(m_grainOffset + m_grainDuration, bufferSampleRate); else maxFrame = bufferLength; // Do some sanity checking. if (maxFrame > bufferLength) maxFrame = bufferLength; if (reverse && m_virtualReadIndex <= 0) m_virtualReadIndex = maxFrame - 1; else if (!reverse && m_virtualReadIndex >= maxFrame) m_virtualReadIndex = 0; // reset to start // If the .loop attribute is true, then values of m_loopStart == 0 && m_loopEnd == 0 implies // that we should use the entire buffer as the loop, otherwise use the loop values in m_loopStart and m_loopEnd. double virtualMaxFrame = maxFrame; double virtualMinFrame = 0; double virtualDeltaFrames = maxFrame; if (loop() && (m_loopStart || m_loopEnd) && m_loopStart >= 0 && m_loopEnd > 0 && m_loopStart < m_loopEnd) { // Convert from seconds to sample-frames. double loopMinFrame = m_loopStart * buffer()->sampleRate(); double loopMaxFrame = m_loopEnd * buffer()->sampleRate(); virtualMaxFrame = std::min(loopMaxFrame, virtualMaxFrame); virtualMinFrame = std::max(loopMinFrame, virtualMinFrame); virtualDeltaFrames = virtualMaxFrame - virtualMinFrame; } // Sanity check that our playback rate isn't larger than the loop size. if (fabs(pitchRate) >= virtualDeltaFrames) return false; // Get local copy. double virtualReadIndex = m_virtualReadIndex; bool needsInterpolation = virtualReadIndex != floor(virtualReadIndex) || virtualDeltaFrames != floor(virtualDeltaFrames) || virtualMaxFrame != floor(virtualMaxFrame) || virtualMinFrame != floor(virtualMinFrame); // Render loop - reading from the source buffer to the destination using linear interpolation. int framesToProcess = numberOfFrames; const float** sourceChannels = m_sourceChannels.get(); float** destinationChannels = m_destinationChannels.get(); // Optimize for the very common case of playing back with pitchRate == 1. // We can avoid the linear interpolation. if (pitchRate == 1 && !needsInterpolation) { unsigned readIndex = static_cast<unsigned>(virtualReadIndex); unsigned deltaFrames = static_cast<unsigned>(virtualDeltaFrames); maxFrame = static_cast<unsigned>(virtualMaxFrame); while (framesToProcess > 0) { int framesToEnd = maxFrame - readIndex; int framesThisTime = std::min(framesToProcess, framesToEnd); framesThisTime = std::max(0, framesThisTime); for (unsigned i = 0; i < numberOfChannels; ++i) memcpy(destinationChannels[i] + writeIndex, sourceChannels[i] + readIndex, sizeof(float) * framesThisTime); writeIndex += framesThisTime; readIndex += framesThisTime; framesToProcess -= framesThisTime; // Wrap-around. if (readIndex >= maxFrame) { readIndex -= deltaFrames; if (renderSilenceAndFinishIfNotLooping(bus, writeIndex, framesToProcess)) break; } } virtualReadIndex = readIndex; } else if (pitchRate == -1 && !needsInterpolation) { int readIndex = static_cast<int>(virtualReadIndex); int deltaFrames = static_cast<int>(virtualDeltaFrames); int minFrame = static_cast<int>(virtualMinFrame) - 1; while (framesToProcess > 0) { int framesToEnd = readIndex - minFrame; int framesThisTime = std::min<int>(framesToProcess, framesToEnd); framesThisTime = std::max<int>(0, framesThisTime); while (framesThisTime--) { for (unsigned i = 0; i < numberOfChannels; ++i) { float* destination = destinationChannels[i]; const float* source = sourceChannels[i]; destination[writeIndex] = source[readIndex]; } ++writeIndex; --readIndex; --framesToProcess; } // Wrap-around. if (readIndex <= minFrame) { readIndex += deltaFrames; if (renderSilenceAndFinishIfNotLooping(bus, writeIndex, framesToProcess)) break; } } virtualReadIndex = readIndex; } else if (!pitchRate) { unsigned readIndex = static_cast<unsigned>(virtualReadIndex); for (unsigned i = 0; i < numberOfChannels; ++i) std::fill_n(destinationChannels[i], framesToProcess, sourceChannels[i][readIndex]); } else if (reverse) { unsigned maxFrame = static_cast<unsigned>(virtualMaxFrame); unsigned minFrame = static_cast<unsigned>(floorf(virtualMinFrame)); while (framesToProcess--) { unsigned readIndex = static_cast<unsigned>(floorf(virtualReadIndex)); double interpolationFactor = virtualReadIndex - readIndex; unsigned readIndex2 = readIndex + 1; if (readIndex2 >= maxFrame) readIndex2 = loop() ? minFrame : maxFrame - 1; // Linear interpolation. for (unsigned i = 0; i < numberOfChannels; ++i) { float* destination = destinationChannels[i]; const float* source = sourceChannels[i]; double sample1 = source[readIndex]; double sample2 = source[readIndex2]; double sample = (1.0 - interpolationFactor) * sample1 + interpolationFactor * sample2; destination[writeIndex] = narrowPrecisionToFloat(sample); } writeIndex++; virtualReadIndex += pitchRate; // Wrap-around, retaining sub-sample position since virtualReadIndex is floating-point. if (virtualReadIndex < virtualMinFrame) { virtualReadIndex += virtualDeltaFrames; if (renderSilenceAndFinishIfNotLooping(bus, writeIndex, framesToProcess)) break; } } } else { while (framesToProcess--) { unsigned readIndex = static_cast<unsigned>(virtualReadIndex); double interpolationFactor = virtualReadIndex - readIndex; // For linear interpolation we need the next sample-frame too. unsigned readIndex2 = readIndex + 1; if (readIndex2 >= bufferLength) { if (loop()) { // Make sure to wrap around at the end of the buffer. readIndex2 = static_cast<unsigned>(virtualReadIndex + 1 - virtualDeltaFrames); } else readIndex2 = readIndex; } // Final sanity check on buffer access. // FIXME: as an optimization, try to get rid of this inner-loop check and put assertions and guards before the loop. if (readIndex >= bufferLength || readIndex2 >= bufferLength) break; // Linear interpolation. for (unsigned i = 0; i < numberOfChannels; ++i) { float* destination = destinationChannels[i]; const float* source = sourceChannels[i]; double sample1 = source[readIndex]; double sample2 = source[readIndex2]; double sample = (1.0 - interpolationFactor) * sample1 + interpolationFactor * sample2; destination[writeIndex] = narrowPrecisionToFloat(sample); } writeIndex++; virtualReadIndex += pitchRate; // Wrap-around, retaining sub-sample position since virtualReadIndex is floating-point. if (virtualReadIndex >= virtualMaxFrame) { virtualReadIndex -= virtualDeltaFrames; if (renderSilenceAndFinishIfNotLooping(bus, writeIndex, framesToProcess)) break; } } } bus->clearSilentFlag(); m_virtualReadIndex = virtualReadIndex; return true; }