Index: Source/modules/webaudio/AudioParamTimeline.cpp |
diff --git a/Source/modules/webaudio/AudioParamTimeline.cpp b/Source/modules/webaudio/AudioParamTimeline.cpp |
index 59f7672f736dbce83cfae810622c2c286042d055..7482279a05b2498b21cbdcec0f28584a0b184bd8 100644 |
--- a/Source/modules/webaudio/AudioParamTimeline.cpp |
+++ b/Source/modules/webaudio/AudioParamTimeline.cpp |
@@ -364,9 +364,22 @@ float AudioParamTimeline::valuesForTimeRangeImpl( |
unsigned numberOfCurvePoints = curve ? curve->length() : 0; |
// Curve events have duration, so don't just use next event time. |
- float duration = event.duration(); |
- float durationFrames = duration * sampleRate; |
- float curvePointsPerFrame = static_cast<float>(numberOfCurvePoints) / durationFrames; |
+ double duration = event.duration(); |
+ double durationFrames = duration * sampleRate; |
+ // How much to step the curve index for each frame. We want the curve index to |
+ // be exactly equal to the last index (numberOfCurvePoints - 1) after |
+ // durationFrames - 1 frames. In this way, the last output value will equal the |
+ // last value in the curve array. |
+ double curvePointsPerFrame; |
+ |
+ // If the duration is less than a frame, we want to just output the last curve |
+ // value. Do this by setting curvePointsPerFrame to be more than number of |
+ // points in the curve. Then the curveVirtualIndex will always exceed the last |
+ // curve index, so that the last curve value will be used. |
+ if (durationFrames > 1) |
+ curvePointsPerFrame = (numberOfCurvePoints - 1) / (durationFrames - 1); |
+ else |
+ curvePointsPerFrame = numberOfCurvePoints + 1; |
if (!curve || !curveData || !numberOfCurvePoints || duration <= 0 || sampleRate <= 0) { |
// Error condition - simply propagate previous value. |
@@ -379,33 +392,50 @@ float AudioParamTimeline::valuesForTimeRangeImpl( |
// Save old values and recalculate information based on the curve's duration |
// instead of the next event time. |
unsigned nextEventFillToFrame = fillToFrame; |
- float nextEventFillToTime = fillToTime; |
+ double nextEventFillToTime = fillToTime; |
fillToTime = std::min(endTime, time1 + duration); |
fillToFrame = AudioUtilities::timeToSampleFrame(fillToTime - startTime, sampleRate); |
fillToFrame = std::min(fillToFrame, numberOfValues); |
// Index into the curve data using a floating-point value. |
// We're scaling the number of curve points by the duration (see curvePointsPerFrame). |
- float curveVirtualIndex = 0; |
+ double curveVirtualIndex = 0; |
if (time1 < currentTime) { |
// Index somewhere in the middle of the curve data. |
// Don't use timeToSampleFrame() since we want the exact floating-point frame. |
- float frameOffset = (currentTime - time1) * sampleRate; |
+ double frameOffset = (currentTime - time1) * sampleRate; |
curveVirtualIndex = curvePointsPerFrame * frameOffset; |
} |
- // Render the stretched curve data using nearest neighbor sampling. |
- // Oversampled curve data can be provided if smoothness is desired. |
- for (; writeIndex < fillToFrame; ++writeIndex) { |
- // Ideally we'd use round() from MathExtras, but we're in a tight loop here |
- // and we're trading off precision for extra speed. |
- unsigned curveIndex = static_cast<unsigned>(0.5 + curveVirtualIndex); |
- |
- curveVirtualIndex += curvePointsPerFrame; |
- |
- // Bounds check. |
- if (curveIndex < numberOfCurvePoints) |
- value = curveData[curveIndex]; |
+ // Set the default value in case fillToFrame is 0. |
+ value = curveData[numberOfCurvePoints - 1]; |
+ |
+ // Render the stretched curve data using linear interpolation. Oversampled |
+ // curve data can be provided if sharp discontinuities are desired. |
+ for (unsigned k = 0; writeIndex < fillToFrame; ++writeIndex, ++k) { |
+ // Compute current index this way to minimize round-off that would have |
+ // occurred by incrementing the index by curvePointsPerFrame. |
+ double currentVirtualIndex = curveVirtualIndex + k * curvePointsPerFrame; |
+ unsigned curveIndex0; |
+ |
+ // Clamp index to the last element of the array. |
+ if (currentVirtualIndex < numberOfCurvePoints) { |
+ curveIndex0 = static_cast<unsigned>(currentVirtualIndex); |
+ } else { |
+ curveIndex0 = numberOfCurvePoints - 1; |
+ } |
+ |
+ unsigned curveIndex1 = std::min(curveIndex0 + 1, numberOfCurvePoints - 1); |
+ |
+ // Linearly interpolate between the two nearest curve points. |delta| is |
+ // clamped to 1 because currentVirtualIndex can exceed curveIndex0 by more |
+ // than one. This can happen when we reached the end of the curve but still |
+ // need values to fill out the current rendering quantum. |
+ float c0 = curveData[curveIndex0]; |
+ float c1 = curveData[curveIndex1]; |
+ double delta = std::min(currentVirtualIndex - curveIndex0, 1.0); |
+ |
+ value = c0 + (c1 - c0) * delta; |
values[writeIndex] = value; |
} |