Chromium Code Reviews| Index: third_party/WebKit/Source/modules/webaudio/AudioParamTimeline.cpp |
| diff --git a/third_party/WebKit/Source/modules/webaudio/AudioParamTimeline.cpp b/third_party/WebKit/Source/modules/webaudio/AudioParamTimeline.cpp |
| index 3d12a3f92f7125c433ba5cb7e5a8e831ad5255c6..e845b6010eb023fe94f103a0d5c78310518b37e4 100644 |
| --- a/third_party/WebKit/Source/modules/webaudio/AudioParamTimeline.cpp |
| +++ b/third_party/WebKit/Source/modules/webaudio/AudioParamTimeline.cpp |
| @@ -807,74 +807,20 @@ float AudioParamTimeline::valuesForFrameRangeImpl(size_t startFrame, |
| // Return default value if there are no events matching the desired time |
| // range. |
| if (!m_events.size() || (endFrame / sampleRate <= m_events[0]->time())) { |
| - for (unsigned i = 0; i < numberOfValues; ++i) |
| - values[i] = defaultValue; |
| + fillWithDefault(values, defaultValue, numberOfValues, 0); |
| + |
| return defaultValue; |
| } |
| int numberOfEvents = m_events.size(); |
| if (numberOfEvents > 0) { |
| - bool clampedSomeEventTime = false; |
| double currentTime = startFrame / sampleRate; |
| + clampToCurrentTime(numberOfEvents, startFrame, sampleRate); |
| - // Look at all the events in the timeline and check to see if any needs |
| - // to clamp the start time to the current time. |
| - for (int k = 0; k < numberOfEvents; ++k) { |
| - ParamEvent* event = m_events[k].get(); |
| - |
| - // We're examining the event for the first time and the event time is |
| - // in the past so clamp the event time to the current time (start of |
| - // the rendering quantum). |
| - if (event->needsTimeClampCheck()) { |
| - if (event->time() < currentTime) { |
| - event->setTime(currentTime); |
| - clampedSomeEventTime = true; |
| - } |
| - |
| - // In all cases, we can clear the flag because the event is either |
| - // in the future, or we've already checked it (just now). |
| - event->clearTimeClampCheck(); |
| - } |
| - } |
| - |
| - if (clampedSomeEventTime) { |
| - // If we clamped some event time to current time, we need to |
| - // sort the event list in time order again, but it must be |
| - // stable! |
| - std::stable_sort(m_events.begin(), m_events.end(), |
| - ParamEvent::eventPreceeds); |
| - } |
| - |
| - // Optimize the case where the last event is in the past. |
| - ParamEvent* lastEvent = m_events[m_events.size() - 1].get(); |
| - ParamEvent::Type lastEventType = lastEvent->getType(); |
| - double lastEventTime = lastEvent->time(); |
| - |
| - // If the last event is in the past and the event has ended, then we can |
| - // just propagate the same value. Except for SetTarget which lasts |
| - // "forever". SetValueCurve also has an explicit SetValue at the end of |
| - // the curve, so we don't need to worry that SetValueCurve time is a |
| - // start time, not an end time. |
| - // |
| - // Allow at least one render quantum to go by before handling this |
| - // to allow k-rate parameters to finish processing the event. See |
| - // crbug.com/672857. Due to possible roundoff, arbirtrarily wait |
| - // for 1.5 render quanta instead of 1. |
| - if (lastEventTime + |
| - 1.5 * AudioUtilities::kRenderQuantumFrames / sampleRate < |
| - currentTime && |
| - lastEventType != ParamEvent::SetTarget) { |
| - // The event has finished, so just copy the default value out. |
| - // Since all events are now also in the past, we can just remove all |
| - // timeline events too because |defaultValue| has the expected |
| - // value. |
| - for (unsigned i = 0; i < numberOfValues; ++i) |
| - values[i] = defaultValue; |
| - m_smoothedValue = defaultValue; |
| - m_events.clear(); |
| + if (handleAllEventsInThePast(currentTime, sampleRate, defaultValue, |
| + numberOfValues, values)) |
| return defaultValue; |
| - } |
| } |
| // Maintain a running time (frame) and index for writing the values buffer. |
| @@ -883,23 +829,9 @@ float AudioParamTimeline::valuesForFrameRangeImpl(size_t startFrame, |
| // If first event is after startFrame then fill initial part of values buffer |
| // with defaultValue until we reach the first event time. |
| - double firstEventTime = m_events[0]->time(); |
| - if (firstEventTime > startFrame / sampleRate) { |
| - // |fillToFrame| is an exclusive upper bound, so use ceil() to compute the |
| - // bound from the firstEventTime. |
| - size_t fillToFrame = endFrame; |
| - double firstEventFrame = ceil(firstEventTime * sampleRate); |
| - if (endFrame > firstEventFrame) |
| - fillToFrame = static_cast<size_t>(firstEventFrame); |
| - DCHECK_GE(fillToFrame, startFrame); |
| - |
| - fillToFrame -= startFrame; |
| - fillToFrame = std::min(fillToFrame, static_cast<size_t>(numberOfValues)); |
| - for (; writeIndex < fillToFrame; ++writeIndex) |
| - values[writeIndex] = defaultValue; |
| - |
| - currentFrame += fillToFrame; |
| - } |
| + std::tie(currentFrame, writeIndex) = |
| + handleFirstEvent(values, defaultValue, numberOfValues, startFrame, |
| + endFrame, sampleRate, currentFrame, writeIndex); |
| float value = defaultValue; |
| @@ -911,41 +843,13 @@ float AudioParamTimeline::valuesForFrameRangeImpl(size_t startFrame, |
| ParamEvent* nextEvent = i < numberOfEvents - 1 ? m_events[i + 1].get() : 0; |
| // Wait until we get a more recent event. |
| - // |
| - // WARNING: due to round-off it might happen that nextEvent->time() is |
| - // just larger than currentFrame/sampleRate. This means that we will end |
| - // up running the |event| again. The code below had better be prepared |
| - // for this case! What should happen is the fillToFrame should be 0 so |
| - // that while the event is actually run again, nothing actually gets |
| - // computed, and we move on to the next event. |
| - // |
| - // An example of this case is setValueCurveAtTime. The time at which |
| - // setValueCurveAtTime ends (and the setValueAtTime begins) might be |
| - // just past currentTime/sampleRate. Then setValueCurveAtTime will be |
| - // processed again before advancing to setValueAtTime. The number of |
| - // frames to be processed should be zero in this case. |
| - if (nextEvent && nextEvent->time() < currentFrame / sampleRate) { |
| - // But if the current event is a SetValue event and the event time is |
| - // between currentFrame - 1 and curentFrame (in time). we don't want to |
| - // skip it. If we do skip it, the SetValue event is completely skipped |
| - // and not applied, which is wrong. Other events don't have this problem. |
| - // (Because currentFrame is unsigned, we do the time check in this funny, |
| - // but equivalent way.) |
| - double eventFrame = event->time() * sampleRate; |
| - |
| - // Condition is currentFrame - 1 < eventFrame <= currentFrame, but |
| - // currentFrame is unsigned and could be 0, so use |
| - // currentFrame < eventFrame + 1 instead. |
| - if (!((event->getType() == ParamEvent::SetValue && |
| - (eventFrame <= currentFrame) && |
| - (currentFrame < eventFrame + 1)))) { |
| - // This is not the special SetValue event case, and nextEvent is |
| - // in the past. We can skip processing of this event since it's |
| - // in past. We keep track of this event in lastSkippedEventIndex |
| - // to note what events we've skipped. |
| - lastSkippedEventIndex = i; |
| - continue; |
| - } |
| + if (!isEventCurrent(event, nextEvent, currentFrame, sampleRate)) { |
| + // This is not the special SetValue event case, and nextEvent is |
| + // in the past. We can skip processing of this event since it's |
| + // in past. We keep track of this event in lastSkippedEventIndex |
| + // to note what events we've skipped. |
| + lastSkippedEventIndex = i; |
| + continue; |
| } |
| // If there's no next event, set nextEventType to LastType to indicate that. |
| @@ -953,58 +857,8 @@ float AudioParamTimeline::valuesForFrameRangeImpl(size_t startFrame, |
| nextEvent ? static_cast<ParamEvent::Type>(nextEvent->getType()) |
| : ParamEvent::LastType; |
| - // If the current event is SetTarget and the next event is a |
| - // LinearRampToValue or ExponentialRampToValue, special handling is needed. |
| - // In this case, the linear and exponential ramp should start at wherever |
| - // the SetTarget processing has reached. |
| - if (event->getType() == ParamEvent::SetTarget && |
| - (nextEventType == ParamEvent::LinearRampToValue || |
| - nextEventType == ParamEvent::ExponentialRampToValue)) { |
| - // Replace the SetTarget with a SetValue to set the starting time and |
| - // value for the ramp using the current frame. We need to update |value| |
| - // appropriately depending on whether the ramp has started or not. |
| - // |
| - // If SetTarget starts somewhere between currentFrame - 1 and |
| - // currentFrame, we directly compute the value it would have at |
| - // currentFrame. If not, we update the value from the value from |
| - // currentFrame - 1. |
| - // |
| - // Can't use the condition currentFrame - 1 <= t0 * sampleRate <= |
| - // currentFrame because currentFrame is unsigned and could be 0. Instead, |
| - // compute the condition this way, |
| - // where f = currentFrame and Fs = sampleRate: |
| - // |
| - // f - 1 <= t0 * Fs <= f |
| - // 2 * f - 2 <= 2 * Fs * t0 <= 2 * f |
| - // -2 <= 2 * Fs * t0 - 2 * f <= 0 |
| - // -1 <= 2 * Fs * t0 - 2 * f + 1 <= 1 |
| - // abs(2 * Fs * t0 - 2 * f + 1) <= 1 |
| - if (fabs(2 * sampleRate * event->time() - 2 * currentFrame + 1) <= 1) { |
| - // SetTarget is starting somewhere between currentFrame - 1 and |
| - // currentFrame. Compute the value the SetTarget would have at the |
| - // currentFrame. |
| - value = event->value() + |
| - (value - event->value()) * |
| - exp(-(currentFrame / sampleRate - event->time()) / |
| - event->timeConstant()); |
| - } else { |
| - // SetTarget has already started. Update |value| one frame because it's |
| - // the value from the previous frame. |
| - float discreteTimeConstant = static_cast<float>( |
| - AudioUtilities::discreteTimeConstantForSampleRate( |
| - event->timeConstant(), controlRate)); |
| - value += (event->value() - value) * discreteTimeConstant; |
| - } |
| - |
| - // Insert a SetValueEvent to mark the starting value and time. |
| - // Clear the clamp check because this doesn't need it. |
| - m_events[i] = |
| - ParamEvent::createSetValueEvent(value, currentFrame / sampleRate); |
| - m_events[i]->clearTimeClampCheck(); |
| - |
| - // Update our pointer to the current event because we just changed it. |
| - event = m_events[i].get(); |
| - } |
| + processSetTargetFollowedByRamp(i, event, nextEventType, currentFrame, |
| + sampleRate, controlRate, value); |
| float value1 = event->value(); |
| double time1 = event->time(); |
| @@ -1013,80 +867,12 @@ float AudioParamTimeline::valuesForFrameRangeImpl(size_t startFrame, |
| double time2 = nextEvent ? nextEvent->time() : endFrame / sampleRate + 1; |
| // Check to see if an event was cancelled. |
| - if (nextEventType == ParamEvent::CancelValues) { |
| - switch (event->getType()) { |
| - case ParamEvent::LinearRampToValue: |
| - case ParamEvent::ExponentialRampToValue: |
| - case ParamEvent::SetValue: { |
| - // These three events potentially establish a starting value for |
| - // the following event, so we need to examine the cancelled |
| - // event to see what to do. |
| - const ParamEvent* savedEvent = nextEvent->savedEvent(); |
| - |
| - // Update the end time and type to pretend that we're running |
| - // this saved event type. |
| - time2 = nextEvent->time(); |
| - nextEventType = savedEvent->getType(); |
| - |
| - if (nextEvent->hasDefaultCancelledValue()) { |
| - // We've already established a value for the cancelled |
| - // event, so just return it. |
| - value2 = nextEvent->value(); |
| - } else { |
| - // If the next event would have been a LinearRamp or |
| - // ExponentialRamp, we need to compute a new end value for |
| - // the event so that the curve works continues as if it were |
| - // not cancelled. |
| - switch (savedEvent->getType()) { |
| - case ParamEvent::LinearRampToValue: |
| - value2 = |
| - linearRampAtTime(nextEvent->time(), value1, time1, |
| - savedEvent->value(), savedEvent->time()); |
| - break; |
| - case ParamEvent::ExponentialRampToValue: |
| - value2 = exponentialRampAtTime(nextEvent->time(), value1, time1, |
| - savedEvent->value(), |
| - savedEvent->time()); |
| - break; |
| - case ParamEvent::SetValueCurve: |
| - case ParamEvent::SetValue: |
| - case ParamEvent::SetTarget: |
| - case ParamEvent::CancelValues: |
| - // These cannot be possible types for the saved event |
| - // because they can't be created. |
| - // createCancelValuesEvent doesn't allow them (SetValue, |
| - // SetTarget, CancelValues) or cancelScheduledValues() |
| - // doesn't create such an event (SetValueCurve). |
| - NOTREACHED(); |
| - break; |
| - case ParamEvent::LastType: |
| - // Illegal event type. |
| - NOTREACHED(); |
| - break; |
| - } |
| - |
| - // Cache the new value so we don't keep computing it over and over. |
| - nextEvent->setCancelledValue(value2); |
| - } |
| - } break; |
| - case ParamEvent::SetValueCurve: |
| - // Everything needed for this was handled when cancelling was |
| - // done. |
| - break; |
| - case ParamEvent::SetTarget: |
| - case ParamEvent::CancelValues: |
| - // Nothing special needs to be done for SetTarget or |
| - // CancelValues followed by CancelValues. |
| - break; |
| - case ParamEvent::LastType: |
| - NOTREACHED(); |
| - break; |
| - } |
| - } |
| + std::tie(value2, time2, nextEventType) = |
| + handleCancelValues(event, nextEvent, value2, time2); |
| DCHECK_GE(time2, time1); |
| - double deltaTime = time2 - time1; |
| - float k = deltaTime > 0 ? 1 / deltaTime : 0; |
| + // double deltaTime = time2 - time1; |
| + // float k = deltaTime > 0 ? 1 / deltaTime : 0; |
|
hongchan
2017/02/10 23:37:29
These are commented out.
Raymond Toy
2017/02/14 19:55:52
Done.
|
| // |fillToEndFrame| is the exclusive upper bound of the last frame to be |
| // computed for this event. It's either the last desired frame (|endFrame|) |
| @@ -1108,111 +894,20 @@ float AudioParamTimeline::valuesForFrameRangeImpl(size_t startFrame, |
| size_t fillToFrame = fillToEndFrame - startFrame; |
| fillToFrame = std::min(fillToFrame, static_cast<size_t>(numberOfValues)); |
| + const AutomationState currentState = { |
| + numberOfValues, startFrame, endFrame, sampleRate, controlRate, |
| + fillToFrame, fillToEndFrame, value1, time1, value2, |
| + time2, event, i, |
| + }; |
| + |
| // First handle linear and exponential ramps which require looking ahead to |
| // the next event. |
| if (nextEventType == ParamEvent::LinearRampToValue) { |
| - const float valueDelta = value2 - value1; |
| -#if CPU(X86) || CPU(X86_64) |
| - if (fillToFrame > writeIndex) { |
| - // Minimize in-loop operations. Calculate starting value and increment. |
| - // Next step: value += inc. |
| - // value = value1 + |
| - // (currentFrame/sampleRate - time1) * k * (value2 - value1); |
| - // inc = 4 / sampleRate * k * (value2 - value1); |
| - // Resolve recursion by expanding constants to achieve a 4-step loop |
| - // unrolling. |
| - // value = value1 + |
| - // ((currentFrame/sampleRate - time1) + i * sampleFrameTimeIncr) * k |
| - // * (value2 -value1), i in 0..3 |
| - __m128 vValue = |
| - _mm_mul_ps(_mm_set_ps1(1 / sampleRate), _mm_set_ps(3, 2, 1, 0)); |
| - vValue = |
| - _mm_add_ps(vValue, _mm_set_ps1(currentFrame / sampleRate - time1)); |
| - vValue = _mm_mul_ps(vValue, _mm_set_ps1(k * valueDelta)); |
| - vValue = _mm_add_ps(vValue, _mm_set_ps1(value1)); |
| - __m128 vInc = _mm_set_ps1(4 / sampleRate * k * valueDelta); |
| - |
| - // Truncate loop steps to multiple of 4. |
| - unsigned fillToFrameTrunc = |
| - writeIndex + ((fillToFrame - writeIndex) / 4) * 4; |
| - // Compute final time. |
| - DCHECK_LE(fillToFrameTrunc, numberOfValues); |
| - currentFrame += fillToFrameTrunc - writeIndex; |
| - |
| - // Process 4 loop steps. |
| - for (; writeIndex < fillToFrameTrunc; writeIndex += 4) { |
| - _mm_storeu_ps(values + writeIndex, vValue); |
| - vValue = _mm_add_ps(vValue, vInc); |
| - } |
| - } |
| - // Update |value| with the last value computed so that the |
| - // .value attribute of the AudioParam gets the correct linear |
| - // ramp value, in case the following loop doesn't execute. |
| - if (writeIndex >= 1) |
| - value = values[writeIndex - 1]; |
| -#endif |
| - // Serially process remaining values. |
| - for (; writeIndex < fillToFrame; ++writeIndex) { |
| - float x = (currentFrame / sampleRate - time1) * k; |
| - // value = (1 - x) * value1 + x * value2; |
| - value = value1 + x * valueDelta; |
| - values[writeIndex] = value; |
| - ++currentFrame; |
| - } |
| + std::tie(currentFrame, value, writeIndex) = processLinearRamp( |
| + currentState, values, currentFrame, value, writeIndex); |
| } else if (nextEventType == ParamEvent::ExponentialRampToValue) { |
| - if (value1 * value2 <= 0) { |
| - // It's an error if value1 and value2 have opposite signs or if one of |
| - // them is zero. Handle this by propagating the previous value, and |
| - // making it the default. |
| - value = value1; |
| - |
| - for (; writeIndex < fillToFrame; ++writeIndex) |
| - values[writeIndex] = value; |
| - } else { |
| - double numSampleFrames = deltaTime * sampleRate; |
| - // The value goes exponentially from value1 to value2 in a duration of |
| - // deltaTime seconds according to |
| - // |
| - // v(t) = v1*(v2/v1)^((t-t1)/(t2-t1)) |
| - // |
| - // Let c be currentFrame and F be the sampleRate. Then we want to |
| - // sample v(t) at times t = (c + k)/F for k = 0, 1, ...: |
| - // |
| - // v((c+k)/F) = v1*(v2/v1)^(((c/F+k/F)-t1)/(t2-t1)) |
| - // = v1*(v2/v1)^((c/F-t1)/(t2-t1)) |
| - // *(v2/v1)^((k/F)/(t2-t1)) |
| - // = v1*(v2/v1)^((c/F-t1)/(t2-t1)) |
| - // *[(v2/v1)^(1/(F*(t2-t1)))]^k |
| - // |
| - // Thus, this can be written as |
| - // |
| - // v((c+k)/F) = V*m^k |
| - // |
| - // where |
| - // V = v1*(v2/v1)^((c/F-t1)/(t2-t1)) |
| - // m = (v2/v1)^(1/(F*(t2-t1))) |
| - |
| - // Compute the per-sample multiplier. |
| - float multiplier = powf(value2 / value1, 1 / numSampleFrames); |
| - // Set the starting value of the exponential ramp. |
| - value = value1 * powf(value2 / value1, |
| - (currentFrame / sampleRate - time1) / deltaTime); |
| - |
| - for (; writeIndex < fillToFrame; ++writeIndex) { |
| - values[writeIndex] = value; |
| - value *= multiplier; |
| - ++currentFrame; |
| - } |
| - // |value| got updated one extra time in the above loop. Restore it to |
| - // the last computed value. |
| - if (writeIndex >= 1) |
| - value /= multiplier; |
| - |
| - // Due to roundoff it's possible that value exceeds value2. Clip value |
| - // to value2 if we are within 1/2 frame of time2. |
| - if (currentFrame > time2 * sampleRate - 0.5) |
| - value = value2; |
| - } |
| + std::tie(currentFrame, value, writeIndex) = processExponentialRamp( |
| + currentState, values, currentFrame, value, writeIndex); |
| } else { |
| // Handle event types not requiring looking ahead to the next event. |
| switch (event->getType()) { |
| @@ -1222,41 +917,14 @@ float AudioParamTimeline::valuesForFrameRangeImpl(size_t startFrame, |
| // Simply stay at a constant value. |
| value = event->value(); |
| - |
| - for (; writeIndex < fillToFrame; ++writeIndex) |
| - values[writeIndex] = value; |
| + writeIndex = fillWithDefault(values, value, fillToFrame, writeIndex); |
| break; |
| } |
| case ParamEvent::CancelValues: { |
| - // If the previous event was a SetTarget or ExponentialRamp |
| - // event, the current value is one sample behind. Update |
| - // the sample value by one sample, but only at the start of |
| - // this CancelValues event. |
| - if (event->hasDefaultCancelledValue()) { |
| - value = event->value(); |
| - } else { |
| - double cancelFrame = time1 * sampleRate; |
| - if (i >= 1 && cancelFrame <= currentFrame && |
| - currentFrame < cancelFrame + 1) { |
| - ParamEvent::Type lastEventType = m_events[i - 1]->getType(); |
| - if (lastEventType == ParamEvent::SetTarget) { |
| - float target = m_events[i - 1]->value(); |
| - float timeConstant = m_events[i - 1]->timeConstant(); |
| - float discreteTimeConstant = static_cast<float>( |
| - AudioUtilities::discreteTimeConstantForSampleRate( |
| - timeConstant, controlRate)); |
| - value += (target - value) * discreteTimeConstant; |
| - } |
| - } |
| - } |
| - |
| - // Simply stay at the current value. |
| - for (; writeIndex < fillToFrame; ++writeIndex) |
| - values[writeIndex] = value; |
| - |
| - currentFrame = fillToEndFrame; |
| + std::tie(currentFrame, value, writeIndex) = processCancelValues( |
| + currentState, values, currentFrame, value, writeIndex); |
| break; |
| } |
| @@ -1273,282 +941,20 @@ float AudioParamTimeline::valuesForFrameRangeImpl(size_t startFrame, |
| // Simply stay at a constant value from the last time. We don't want |
| // to use the value of the event in case value1 * value2 < 0. In this |
| // case we should propagate the previous value, which is in |value|. |
| - for (; writeIndex < fillToFrame; ++writeIndex) |
| - values[writeIndex] = value; |
| + writeIndex = fillWithDefault(values, value, fillToFrame, writeIndex); |
| break; |
| } |
| case ParamEvent::SetTarget: { |
| - // Exponential approach to target value with given time constant. |
| - // |
| - // v(t) = v2 + (v1 - v2)*exp(-(t-t1/tau)) |
| - // |
| - |
| - float target = event->value(); |
| - float timeConstant = event->timeConstant(); |
| - float discreteTimeConstant = static_cast<float>( |
| - AudioUtilities::discreteTimeConstantForSampleRate(timeConstant, |
| - controlRate)); |
| - |
| - // Set the starting value correctly. This is only needed when the |
| - // current time is "equal" to the start time of this event. This is |
| - // to get the sampling correct if the start time of this automation |
| - // isn't on a frame boundary. Otherwise, we can just continue from |
| - // where we left off from the previous rendering quantum. |
| - { |
| - double rampStartFrame = time1 * sampleRate; |
| - // Condition is c - 1 < r <= c where c = currentFrame and r = |
| - // rampStartFrame. Compute it this way because currentFrame is |
| - // unsigned and could be 0. |
| - if (rampStartFrame <= currentFrame && |
| - currentFrame < rampStartFrame + 1) { |
| - value = |
| - target + |
| - (value - target) * |
| - exp(-(currentFrame / sampleRate - time1) / timeConstant); |
| - } else { |
| - // Otherwise, need to compute a new value bacause |value| is the |
| - // last computed value of SetTarget. Time has progressed by one |
| - // frame, so we need to update the value for the new frame. |
| - value += (target - value) * discreteTimeConstant; |
| - } |
| - } |
| - |
| - // If the value is close enough to the target, just fill in the data |
| - // with the target value. |
| - if (fabs(value - target) < kSetTargetThreshold * fabs(target) || |
| - (!target && fabs(value) < kSetTargetZeroThreshold)) { |
| - for (; writeIndex < fillToFrame; ++writeIndex) |
| - values[writeIndex] = target; |
| - } else { |
| -#if CPU(X86) || CPU(X86_64) |
| - if (fillToFrame > writeIndex) { |
| - // Resolve recursion by expanding constants to achieve a 4-step |
| - // loop unrolling. |
| - // |
| - // v1 = v0 + (t - v0) * c |
| - // v2 = v1 + (t - v1) * c |
| - // v2 = v0 + (t - v0) * c + (t - (v0 + (t - v0) * c)) * c |
| - // v2 = v0 + (t - v0) * c + (t - v0) * c - (t - v0) * c * c |
| - // v2 = v0 + (t - v0) * c * (2 - c) |
| - // Thus c0 = c, c1 = c*(2-c). The same logic applies to c2 and c3. |
| - const float c0 = discreteTimeConstant; |
| - const float c1 = c0 * (2 - c0); |
| - const float c2 = c0 * ((c0 - 3) * c0 + 3); |
| - const float c3 = c0 * (c0 * ((4 - c0) * c0 - 6) + 4); |
| - |
| - float delta; |
| - __m128 vC = _mm_set_ps(c2, c1, c0, 0); |
| - __m128 vDelta, vValue, vResult; |
| - |
| - // Process 4 loop steps. |
| - unsigned fillToFrameTrunc = |
| - writeIndex + ((fillToFrame - writeIndex) / 4) * 4; |
| - DCHECK_LE(fillToFrameTrunc, numberOfValues); |
| - |
| - for (; writeIndex < fillToFrameTrunc; writeIndex += 4) { |
| - delta = target - value; |
| - vDelta = _mm_set_ps1(delta); |
| - vValue = _mm_set_ps1(value); |
| - |
| - vResult = _mm_add_ps(vValue, _mm_mul_ps(vDelta, vC)); |
| - _mm_storeu_ps(values + writeIndex, vResult); |
| - |
| - // Update value for next iteration. |
| - value += delta * c3; |
| - } |
| - } |
| -#endif |
| - // Serially process remaining values |
| - for (; writeIndex < fillToFrame; ++writeIndex) { |
| - values[writeIndex] = value; |
| - value += (target - value) * discreteTimeConstant; |
| - } |
| - // The previous loops may have updated |value| one extra time. |
| - // Reset it to the last computed value. |
| - if (writeIndex >= 1) |
| - value = values[writeIndex - 1]; |
| - currentFrame = fillToEndFrame; |
| - } |
| + std::tie(currentFrame, value, writeIndex) = processSetTarget( |
| + currentState, values, currentFrame, value, writeIndex); |
| break; |
| } |
| case ParamEvent::SetValueCurve: { |
| - Vector<float> curve = event->curve(); |
| - float* curveData = curve.data(); |
| - unsigned numberOfCurvePoints = curve.size(); |
| - |
| - float curveEndValue = event->curveEndValue(); |
| - |
| - // Curve events have duration, so don't just use next event time. |
| - double duration = event->duration(); |
| - // How much to step the curve index for each frame. This is basically |
| - // the term (N - 1)/Td in the specification. |
| - double curvePointsPerFrame = |
| - event->curvePointsPerSecond() / sampleRate; |
| - |
| - if (!numberOfCurvePoints || duration <= 0 || sampleRate <= 0) { |
| - // Error condition - simply propagate previous value. |
| - currentFrame = fillToEndFrame; |
| - for (; writeIndex < fillToFrame; ++writeIndex) |
| - values[writeIndex] = value; |
| - break; |
| - } |
| - |
| - // Save old values and recalculate information based on the curve's |
| - // duration instead of the next event time. |
| - size_t nextEventFillToFrame = fillToFrame; |
| - |
| - // fillToEndFrame = min(endFrame, |
| - // ceil(sampleRate * (time1 + duration))), |
| - // but compute this carefully in case sampleRate*(time1 + duration) is |
| - // huge. fillToEndFrame is an exclusive upper bound of the last frame |
| - // to be computed, so ceil is used. |
| - { |
| - double curveEndFrame = ceil(sampleRate * (time1 + duration)); |
| - if (endFrame > curveEndFrame) |
| - fillToEndFrame = static_cast<size_t>(curveEndFrame); |
| - else |
| - fillToEndFrame = endFrame; |
| - } |
| - |
| - // |fillToFrame| can be less than |startFrame| when the end of the |
| - // setValueCurve automation has been reached, but the next automation |
| - // has not yet started. In this case, |fillToFrame| is clipped to |
| - // |time1|+|duration| above, but |startFrame| will keep increasing |
| - // (because the current time is increasing). |
| - fillToFrame = |
| - (fillToEndFrame < startFrame) ? 0 : fillToEndFrame - startFrame; |
| - fillToFrame = |
| - std::min(fillToFrame, static_cast<size_t>(numberOfValues)); |
| - |
| - // Index into the curve data using a floating-point value. |
| - // We're scaling the number of curve points by the duration (see |
| - // curvePointsPerFrame). |
| - double curveVirtualIndex = 0; |
| - if (time1 < currentFrame / sampleRate) { |
| - // Index somewhere in the middle of the curve data. |
| - // Don't use timeToSampleFrame() since we want the exact |
| - // floating-point frame. |
| - double frameOffset = currentFrame - time1 * sampleRate; |
| - curveVirtualIndex = curvePointsPerFrame * frameOffset; |
| - } |
| - |
| - // Set the default value in case fillToFrame is 0. |
| - value = curveEndValue; |
| - |
| - // Render the stretched curve data using linear interpolation. |
| - // Oversampled curve data can be provided if sharp discontinuities are |
| - // desired. |
| - unsigned k = 0; |
| -#if CPU(X86) || CPU(X86_64) |
| - if (fillToFrame > writeIndex) { |
| - const __m128 vCurveVirtualIndex = _mm_set_ps1(curveVirtualIndex); |
| - const __m128 vCurvePointsPerFrame = |
| - _mm_set_ps1(curvePointsPerFrame); |
| - const __m128 vNumberOfCurvePointsM1 = |
| - _mm_set_ps1(numberOfCurvePoints - 1); |
| - const __m128 vN1 = _mm_set_ps1(1.0f); |
| - const __m128 vN4 = _mm_set_ps1(4.0f); |
| - |
| - __m128 vK = _mm_set_ps(3, 2, 1, 0); |
| - int aCurveIndex0[4]; |
| - int aCurveIndex1[4]; |
| - |
| - // Truncate loop steps to multiple of 4 |
| - unsigned truncatedSteps = ((fillToFrame - writeIndex) / 4) * 4; |
| - unsigned fillToFrameTrunc = writeIndex + truncatedSteps; |
| - DCHECK_LE(fillToFrameTrunc, numberOfValues); |
| - |
| - for (; writeIndex < fillToFrameTrunc; writeIndex += 4) { |
| - // Compute current index this way to minimize round-off that would |
| - // have occurred by incrementing the index by curvePointsPerFrame. |
| - __m128 vCurrentVirtualIndex = _mm_add_ps( |
| - vCurveVirtualIndex, _mm_mul_ps(vK, vCurvePointsPerFrame)); |
| - vK = _mm_add_ps(vK, vN4); |
| - |
| - // Clamp index to the last element of the array. |
| - __m128i vCurveIndex0 = _mm_cvttps_epi32( |
| - _mm_min_ps(vCurrentVirtualIndex, vNumberOfCurvePointsM1)); |
| - __m128i vCurveIndex1 = _mm_cvttps_epi32( |
| - _mm_min_ps(_mm_add_ps(vCurrentVirtualIndex, vN1), |
| - vNumberOfCurvePointsM1)); |
| - |
| - // 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. |
| - _mm_storeu_si128((__m128i*)aCurveIndex0, vCurveIndex0); |
| - _mm_storeu_si128((__m128i*)aCurveIndex1, vCurveIndex1); |
| - __m128 vC0 = _mm_set_ps( |
| - curveData[aCurveIndex0[3]], curveData[aCurveIndex0[2]], |
| - curveData[aCurveIndex0[1]], curveData[aCurveIndex0[0]]); |
| - __m128 vC1 = _mm_set_ps( |
| - curveData[aCurveIndex1[3]], curveData[aCurveIndex1[2]], |
| - curveData[aCurveIndex1[1]], curveData[aCurveIndex1[0]]); |
| - __m128 vDelta = |
| - _mm_min_ps(_mm_sub_ps(vCurrentVirtualIndex, |
| - _mm_cvtepi32_ps(vCurveIndex0)), |
| - vN1); |
| - |
| - __m128 vValue = |
| - _mm_add_ps(vC0, _mm_mul_ps(_mm_sub_ps(vC1, vC0), vDelta)); |
| - |
| - _mm_storeu_ps(values + writeIndex, vValue); |
| - } |
| - // Pass along k to the serial loop. |
| - k = truncatedSteps; |
| - } |
| - if (writeIndex >= 1) |
| - value = values[writeIndex - 1]; |
| -#endif |
| - for (; 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. |
| - DCHECK_LT(curveIndex0, numberOfCurvePoints); |
| - DCHECK_LT(curveIndex1, numberOfCurvePoints); |
| - float c0 = curveData[curveIndex0]; |
| - float c1 = curveData[curveIndex1]; |
| - double delta = std::min(currentVirtualIndex - curveIndex0, 1.0); |
| - |
| - value = c0 + (c1 - c0) * delta; |
| - |
| - values[writeIndex] = value; |
| - } |
| - |
| - // If there's any time left after the duration of this event and the |
| - // start of the next, then just propagate the last value of the |
| - // curveData. Don't modify |value| unless there is time left. |
| - if (writeIndex < nextEventFillToFrame) { |
| - value = curveEndValue; |
| - for (; writeIndex < nextEventFillToFrame; ++writeIndex) |
| - values[writeIndex] = value; |
| - } |
| - |
| - // Re-adjust current time |
| - currentFrame += nextEventFillToFrame; |
| - |
| + std::tie(currentFrame, value, writeIndex) = processSetValueCurve( |
| + currentState, values, currentFrame, value, writeIndex); |
| break; |
| } |
| case ParamEvent::LastType: |
| @@ -1567,12 +973,780 @@ float AudioParamTimeline::valuesForFrameRangeImpl(size_t startFrame, |
| // If there's any time left after processing the last event then just |
| // propagate the last value to the end of the values buffer. |
| - for (; writeIndex < numberOfValues; ++writeIndex) |
| - values[writeIndex] = value; |
| + writeIndex = fillWithDefault(values, value, numberOfValues, writeIndex); |
| // This value is used to set the .value attribute of the AudioParam. it |
| // should be the last computed value. |
| return values[numberOfValues - 1]; |
| } |
| +std::tuple<size_t, unsigned> AudioParamTimeline::handleFirstEvent( |
|
hongchan
2017/02/10 23:37:29
From this to the end, you just copied/pasted and s
Raymond Toy
2017/02/14 19:55:52
Yes, I basically copied and pasted the bodies from
|
| + float* values, |
| + float defaultValue, |
| + unsigned numberOfValues, |
| + size_t startFrame, |
| + size_t endFrame, |
| + double sampleRate, |
| + size_t currentFrame, |
| + unsigned writeIndex) { |
| + double firstEventTime = m_events[0]->time(); |
| + if (firstEventTime > startFrame / sampleRate) { |
| + // |fillToFrame| is an exclusive upper bound, so use ceil() to compute the |
| + // bound from the firstEventTime. |
| + size_t fillToFrame = endFrame; |
| + double firstEventFrame = ceil(firstEventTime * sampleRate); |
| + if (endFrame > firstEventFrame) |
| + fillToFrame = static_cast<size_t>(firstEventFrame); |
| + DCHECK_GE(fillToFrame, startFrame); |
| + |
| + fillToFrame -= startFrame; |
| + fillToFrame = std::min(fillToFrame, static_cast<size_t>(numberOfValues)); |
| + writeIndex = fillWithDefault(values, defaultValue, fillToFrame, writeIndex); |
| + |
| + currentFrame += fillToFrame; |
| + } |
| + |
| + return std::make_tuple(currentFrame, writeIndex); |
| +} |
| + |
| +bool AudioParamTimeline::isEventCurrent(const ParamEvent* event, |
| + const ParamEvent* nextEvent, |
| + size_t currentFrame, |
| + double sampleRate) { |
| + // |
|
hongchan
2017/02/10 23:37:29
Remove this line.
Raymond Toy
2017/02/14 19:55:52
Done.
|
| + // WARNING: due to round-off it might happen that nextEvent->time() is |
| + // just larger than currentFrame/sampleRate. This means that we will end |
| + // up running the |event| again. The code below had better be prepared |
| + // for this case! What should happen is the fillToFrame should be 0 so |
| + // that while the event is actually run again, nothing actually gets |
| + // computed, and we move on to the next event. |
| + // |
| + // An example of this case is setValueCurveAtTime. The time at which |
| + // setValueCurveAtTime ends (and the setValueAtTime begins) might be |
| + // just past currentTime/sampleRate. Then setValueCurveAtTime will be |
| + // processed again before advancing to setValueAtTime. The number of |
| + // frames to be processed should be zero in this case. |
| + if (nextEvent && nextEvent->time() < currentFrame / sampleRate) { |
| + // But if the current event is a SetValue event and the event time is |
| + // between currentFrame - 1 and curentFrame (in time). we don't want to |
| + // skip it. If we do skip it, the SetValue event is completely skipped |
| + // and not applied, which is wrong. Other events don't have this problem. |
| + // (Because currentFrame is unsigned, we do the time check in this funny, |
| + // but equivalent way.) |
| + double eventFrame = event->time() * sampleRate; |
| + |
| + // Condition is currentFrame - 1 < eventFrame <= currentFrame, but |
| + // currentFrame is unsigned and could be 0, so use |
| + // currentFrame < eventFrame + 1 instead. |
| + if (!((event->getType() == ParamEvent::SetValue && |
| + (eventFrame <= currentFrame) && (currentFrame < eventFrame + 1)))) { |
| + // This is not the special SetValue event case, and nextEvent is |
| + // in the past. We can skip processing of this event since it's |
| + // in past. |
| + return false; |
| + } |
| + } |
| + return true; |
| +} |
| + |
| +void AudioParamTimeline::clampToCurrentTime(int numberOfEvents, |
| + size_t startFrame, |
| + double sampleRate) { |
| + if (numberOfEvents > 0) { |
| + bool clampedSomeEventTime = false; |
| + double currentTime = startFrame / sampleRate; |
| + |
| + // Look at all the events in the timeline and check to see if any needs |
| + // to clamp the start time to the current time. |
| + for (int k = 0; k < numberOfEvents; ++k) { |
| + ParamEvent* event = m_events[k].get(); |
| + |
| + // We're examining the event for the first time and the event time is |
| + // in the past so clamp the event time to the current time (start of |
| + // the rendering quantum). |
| + if (event->needsTimeClampCheck()) { |
| + if (event->time() < currentTime) { |
| + event->setTime(currentTime); |
| + clampedSomeEventTime = true; |
| + } |
| + |
| + // In all cases, we can clear the flag because the event is either |
| + // in the future, or we've already checked it (just now). |
| + event->clearTimeClampCheck(); |
| + } |
| + } |
| + |
| + if (clampedSomeEventTime) { |
| + // If we clamped some event time to current time, we need to |
| + // sort the event list in time order again, but it must be |
| + // stable! |
| + std::stable_sort(m_events.begin(), m_events.end(), |
| + ParamEvent::eventPreceeds); |
| + } |
| + } |
| +} |
| + |
| +bool AudioParamTimeline::handleAllEventsInThePast(double currentTime, |
| + double sampleRate, |
| + float defaultValue, |
| + unsigned numberOfValues, |
| + float* values) { |
| + // Optimize the case where the last event is in the past. |
| + ParamEvent* lastEvent = m_events[m_events.size() - 1].get(); |
| + ParamEvent::Type lastEventType = lastEvent->getType(); |
| + double lastEventTime = lastEvent->time(); |
| + |
| + // If the last event is in the past and the event has ended, then we can |
| + // just propagate the same value. Except for SetTarget which lasts |
| + // "forever". SetValueCurve also has an explicit SetValue at the end of |
| + // the curve, so we don't need to worry that SetValueCurve time is a |
| + // start time, not an end time. |
| + if (lastEventTime + 1.5 * AudioUtilities::kRenderQuantumFrames / sampleRate < |
| + currentTime && |
| + lastEventType != ParamEvent::SetTarget) { |
| + // The event has finished, so just copy the default value out. |
| + // Since all events are now also in the past, we can just remove all |
| + // timeline events too because |defaultValue| has the expected |
| + // value. |
| + fillWithDefault(values, defaultValue, numberOfValues, 0); |
| + m_smoothedValue = defaultValue; |
| + m_events.clear(); |
| + return true; |
| + } |
| + |
| + return false; |
| +} |
| + |
| +void AudioParamTimeline::processSetTargetFollowedByRamp( |
| + int eventIndex, |
| + ParamEvent*& event, |
| + ParamEvent::Type nextEventType, |
| + size_t currentFrame, |
| + double sampleRate, |
| + double controlRate, |
| + float& value) { |
| + // If the current event is SetTarget and the next event is a |
| + // LinearRampToValue or ExponentialRampToValue, special handling is needed. |
| + // In this case, the linear and exponential ramp should start at wherever |
| + // the SetTarget processing has reached. |
| + if (event->getType() == ParamEvent::SetTarget && |
| + (nextEventType == ParamEvent::LinearRampToValue || |
| + nextEventType == ParamEvent::ExponentialRampToValue)) { |
| + // Replace the SetTarget with a SetValue to set the starting time and |
| + // value for the ramp using the current frame. We need to update |value| |
| + // appropriately depending on whether the ramp has started or not. |
| + // |
| + // If SetTarget starts somewhere between currentFrame - 1 and |
| + // currentFrame, we directly compute the value it would have at |
| + // currentFrame. If not, we update the value from the value from |
| + // currentFrame - 1. |
| + // |
| + // Can't use the condition currentFrame - 1 <= t0 * sampleRate <= |
| + // currentFrame because currentFrame is unsigned and could be 0. Instead, |
| + // compute the condition this way, |
| + // where f = currentFrame and Fs = sampleRate: |
| + // |
| + // f - 1 <= t0 * Fs <= f |
| + // 2 * f - 2 <= 2 * Fs * t0 <= 2 * f |
| + // -2 <= 2 * Fs * t0 - 2 * f <= 0 |
| + // -1 <= 2 * Fs * t0 - 2 * f + 1 <= 1 |
| + // abs(2 * Fs * t0 - 2 * f + 1) <= 1 |
| + if (fabs(2 * sampleRate * event->time() - 2 * currentFrame + 1) <= 1) { |
| + // SetTarget is starting somewhere between currentFrame - 1 and |
| + // currentFrame. Compute the value the SetTarget would have at the |
| + // currentFrame. |
| + value = event->value() + |
| + (value - event->value()) * |
| + exp(-(currentFrame / sampleRate - event->time()) / |
| + event->timeConstant()); |
| + } else { |
| + // SetTarget has already started. Update |value| one frame because it's |
| + // the value from the previous frame. |
| + float discreteTimeConstant = |
| + static_cast<float>(AudioUtilities::discreteTimeConstantForSampleRate( |
| + event->timeConstant(), controlRate)); |
| + value += (event->value() - value) * discreteTimeConstant; |
| + } |
| + |
| + // Insert a SetValueEvent to mark the starting value and time. |
| + // Clear the clamp check because this doesn't need it. |
| + m_events[eventIndex] = |
| + ParamEvent::createSetValueEvent(value, currentFrame / sampleRate); |
| + m_events[eventIndex]->clearTimeClampCheck(); |
| + |
| + // Update our pointer to the current event because we just changed it. |
| + event = m_events[eventIndex].get(); |
| + } |
| +} |
| + |
| +std::tuple<float, double, AudioParamTimeline::ParamEvent::Type> |
| +AudioParamTimeline::handleCancelValues(const ParamEvent* currentEvent, |
| + ParamEvent* nextEvent, |
| + float value2, |
| + double time2) { |
| + DCHECK(currentEvent); |
| + |
| + ParamEvent::Type nextEventType = |
| + nextEvent ? nextEvent->getType() : ParamEvent::LastType; |
| + |
| + if (nextEvent && nextEvent->getType() == ParamEvent::CancelValues) { |
| + float value1 = currentEvent->value(); |
| + double time1 = currentEvent->time(); |
| + |
| + switch (currentEvent->getType()) { |
| + case ParamEvent::LinearRampToValue: |
| + case ParamEvent::ExponentialRampToValue: |
| + case ParamEvent::SetValue: { |
| + // These three events potentially establish a starting value for |
| + // the following event, so we need to examine the cancelled |
| + // event to see what to do. |
| + const ParamEvent* savedEvent = nextEvent->savedEvent(); |
| + |
| + // Update the end time and type to pretend that we're running |
| + // this saved event type. |
| + time2 = nextEvent->time(); |
| + nextEventType = savedEvent->getType(); |
| + |
| + if (nextEvent->hasDefaultCancelledValue()) { |
| + // We've already established a value for the cancelled |
| + // event, so just return it. |
| + value2 = nextEvent->value(); |
| + } else { |
| + // If the next event would have been a LinearRamp or |
| + // ExponentialRamp, we need to compute a new end value for |
| + // the event so that the curve works continues as if it were |
| + // not cancelled. |
| + switch (savedEvent->getType()) { |
| + case ParamEvent::LinearRampToValue: |
| + value2 = |
| + linearRampAtTime(nextEvent->time(), value1, time1, |
| + savedEvent->value(), savedEvent->time()); |
| + break; |
| + case ParamEvent::ExponentialRampToValue: |
| + value2 = exponentialRampAtTime(nextEvent->time(), value1, time1, |
| + savedEvent->value(), |
| + savedEvent->time()); |
| + break; |
| + case ParamEvent::SetValueCurve: |
| + case ParamEvent::SetValue: |
| + case ParamEvent::SetTarget: |
| + case ParamEvent::CancelValues: |
| + // These cannot be possible types for the saved event |
| + // because they can't be created. |
| + // createCancelValuesEvent doesn't allow them (SetValue, |
| + // SetTarget, CancelValues) or cancelScheduledValues() |
| + // doesn't create such an event (SetValueCurve). |
| + NOTREACHED(); |
| + break; |
| + case ParamEvent::LastType: |
| + // Illegal event type. |
| + NOTREACHED(); |
| + break; |
| + } |
| + |
| + // Cache the new value so we don't keep computing it over and over. |
| + nextEvent->setCancelledValue(value2); |
| + } |
| + } break; |
| + case ParamEvent::SetValueCurve: |
| + // Everything needed for this was handled when cancelling was |
| + // done. |
| + break; |
| + case ParamEvent::SetTarget: |
| + case ParamEvent::CancelValues: |
| + // Nothing special needs to be done for SetTarget or |
| + // CancelValues followed by CancelValues. |
| + break; |
| + case ParamEvent::LastType: |
| + NOTREACHED(); |
| + break; |
| + } |
| + } |
| + |
| + return std::make_tuple(value2, time2, nextEventType); |
| +} |
| + |
| +std::tuple<size_t, float, unsigned> AudioParamTimeline::processLinearRamp( |
| + const AutomationState& currentState, |
| + float* values, |
| + size_t currentFrame, |
| + float value, |
| + unsigned writeIndex) { |
| + auto numberOfValues = currentState.numberOfValues; |
| + auto fillToFrame = currentState.fillToFrame; |
| + auto time1 = currentState.time1; |
| + auto time2 = currentState.time2; |
| + auto value1 = currentState.value1; |
| + auto value2 = currentState.value2; |
| + auto sampleRate = currentState.sampleRate; |
| + |
| + double deltaTime = time2 - time1; |
| + float k = deltaTime > 0 ? 1 / deltaTime : 0; |
| + const float valueDelta = value2 - value1; |
| +#if CPU(X86) || CPU(X86_64) |
| + if (fillToFrame > writeIndex) { |
| + // Minimize in-loop operations. Calculate starting value and increment. |
| + // Next step: value += inc. |
| + // value = value1 + |
| + // (currentFrame/sampleRate - time1) * k * (value2 - value1); |
| + // inc = 4 / sampleRate * k * (value2 - value1); |
| + // Resolve recursion by expanding constants to achieve a 4-step loop |
| + // unrolling. |
| + // value = value1 + |
| + // ((currentFrame/sampleRate - time1) + i * sampleFrameTimeIncr) * k |
| + // * (value2 -value1), i in 0..3 |
| + __m128 vValue = |
| + _mm_mul_ps(_mm_set_ps1(1 / sampleRate), _mm_set_ps(3, 2, 1, 0)); |
| + vValue = _mm_add_ps(vValue, _mm_set_ps1(currentFrame / sampleRate - time1)); |
| + vValue = _mm_mul_ps(vValue, _mm_set_ps1(k * valueDelta)); |
| + vValue = _mm_add_ps(vValue, _mm_set_ps1(value1)); |
| + __m128 vInc = _mm_set_ps1(4 / sampleRate * k * valueDelta); |
| + |
| + // Truncate loop steps to multiple of 4. |
| + unsigned fillToFrameTrunc = |
| + writeIndex + ((fillToFrame - writeIndex) / 4) * 4; |
| + // Compute final time. |
| + DCHECK_LE(fillToFrameTrunc, numberOfValues); |
| + currentFrame += fillToFrameTrunc - writeIndex; |
| + |
| + // Process 4 loop steps. |
| + for (; writeIndex < fillToFrameTrunc; writeIndex += 4) { |
| + _mm_storeu_ps(values + writeIndex, vValue); |
| + vValue = _mm_add_ps(vValue, vInc); |
| + } |
| + } |
| + // Update |value| with the last value computed so that the |
| + // .value attribute of the AudioParam gets the correct linear |
| + // ramp value, in case the following loop doesn't execute. |
| + if (writeIndex >= 1) |
| + value = values[writeIndex - 1]; |
| +#endif |
| + // Serially process remaining values. |
| + for (; writeIndex < fillToFrame; ++writeIndex) { |
| + float x = (currentFrame / sampleRate - time1) * k; |
| + // value = (1 - x) * value1 + x * value2; |
| + value = value1 + x * valueDelta; |
| + values[writeIndex] = value; |
| + ++currentFrame; |
| + } |
| + |
| + return std::make_tuple(currentFrame, value, writeIndex); |
| +} |
| + |
| +std::tuple<size_t, float, unsigned> AudioParamTimeline::processExponentialRamp( |
| + const AutomationState& currentState, |
| + float* values, |
| + size_t currentFrame, |
| + float value, |
| + unsigned writeIndex) { |
| + auto fillToFrame = currentState.fillToFrame; |
| + auto time1 = currentState.time1; |
| + auto time2 = currentState.time2; |
| + auto value1 = currentState.value1; |
| + auto value2 = currentState.value2; |
| + auto sampleRate = currentState.sampleRate; |
| + |
| + if (value1 * value2 <= 0) { |
| + // It's an error if value1 and value2 have opposite signs or if one of |
| + // them is zero. Handle this by propagating the previous value, and |
| + // making it the default. |
| + value = value1; |
| + |
| + for (; writeIndex < fillToFrame; ++writeIndex) |
| + values[writeIndex] = value; |
| + } else { |
| + double deltaTime = time2 - time1; |
| + double numSampleFrames = deltaTime * sampleRate; |
| + // The value goes exponentially from value1 to value2 in a duration of |
| + // deltaTime seconds according to |
| + // |
| + // v(t) = v1*(v2/v1)^((t-t1)/(t2-t1)) |
| + // |
| + // Let c be currentFrame and F be the sampleRate. Then we want to |
| + // sample v(t) at times t = (c + k)/F for k = 0, 1, ...: |
| + // |
| + // v((c+k)/F) = v1*(v2/v1)^(((c/F+k/F)-t1)/(t2-t1)) |
| + // = v1*(v2/v1)^((c/F-t1)/(t2-t1)) |
| + // *(v2/v1)^((k/F)/(t2-t1)) |
| + // = v1*(v2/v1)^((c/F-t1)/(t2-t1)) |
| + // *[(v2/v1)^(1/(F*(t2-t1)))]^k |
| + // |
| + // Thus, this can be written as |
| + // |
| + // v((c+k)/F) = V*m^k |
| + // |
| + // where |
| + // V = v1*(v2/v1)^((c/F-t1)/(t2-t1)) |
| + // m = (v2/v1)^(1/(F*(t2-t1))) |
| + |
| + // Compute the per-sample multiplier. |
| + float multiplier = powf(value2 / value1, 1 / numSampleFrames); |
| + // Set the starting value of the exponential ramp. |
| + value = value1 * powf(value2 / value1, |
| + (currentFrame / sampleRate - time1) / deltaTime); |
| + |
| + for (; writeIndex < fillToFrame; ++writeIndex) { |
| + values[writeIndex] = value; |
| + value *= multiplier; |
| + ++currentFrame; |
| + } |
| + // |value| got updated one extra time in the above loop. Restore it to |
| + // the last computed value. |
| + if (writeIndex >= 1) |
| + value /= multiplier; |
| + |
| + // Due to roundoff it's possible that value exceeds value2. Clip value |
| + // to value2 if we are within 1/2 frame of time2. |
| + if (currentFrame > time2 * sampleRate - 0.5) |
| + value = value2; |
| + } |
| + |
| + return std::make_tuple(currentFrame, value, writeIndex); |
| +} |
| + |
| +std::tuple<size_t, float, unsigned> AudioParamTimeline::processSetTarget( |
| + const AutomationState& currentState, |
| + float* values, |
| + size_t currentFrame, |
| + float value, |
| + unsigned writeIndex) { |
| + auto numberOfValues = currentState.numberOfValues; |
| + auto fillToFrame = currentState.fillToFrame; |
| + auto time1 = currentState.time1; |
| + auto value1 = currentState.value1; |
| + auto sampleRate = currentState.sampleRate; |
| + auto controlRate = currentState.controlRate; |
| + auto fillToEndFrame = currentState.fillToEndFrame; |
| + auto event = currentState.event; |
| + |
| + // Exponential approach to target value with given time constant. |
| + // |
| + // v(t) = v2 + (v1 - v2)*exp(-(t-t1/tau)) |
| + // |
| + float target = value1; |
| + float timeConstant = event->timeConstant(); |
| + float discreteTimeConstant = |
| + static_cast<float>(AudioUtilities::discreteTimeConstantForSampleRate( |
| + timeConstant, controlRate)); |
| + |
| + // Set the starting value correctly. This is only needed when the |
| + // current time is "equal" to the start time of this event. This is |
| + // to get the sampling correct if the start time of this automation |
| + // isn't on a frame boundary. Otherwise, we can just continue from |
| + // where we left off from the previous rendering quantum. |
| + { |
| + double rampStartFrame = time1 * sampleRate; |
| + // Condition is c - 1 < r <= c where c = currentFrame and r = |
| + // rampStartFrame. Compute it this way because currentFrame is |
| + // unsigned and could be 0. |
| + if (rampStartFrame <= currentFrame && currentFrame < rampStartFrame + 1) { |
| + value = target + |
| + (value - target) * |
| + exp(-(currentFrame / sampleRate - time1) / timeConstant); |
| + } else { |
| + // Otherwise, need to compute a new value bacause |value| is the |
| + // last computed value of SetTarget. Time has progressed by one |
| + // frame, so we need to update the value for the new frame. |
| + value += (target - value) * discreteTimeConstant; |
| + } |
| + } |
| + |
| + // If the value is close enough to the target, just fill in the data |
| + // with the target value. |
| + if (fabs(value - target) < kSetTargetThreshold * fabs(target) || |
| + (!target && fabs(value) < kSetTargetZeroThreshold)) { |
| + for (; writeIndex < fillToFrame; ++writeIndex) |
| + values[writeIndex] = target; |
| + } else { |
| +#if CPU(X86) || CPU(X86_64) |
| + if (fillToFrame > writeIndex) { |
| + // Resolve recursion by expanding constants to achieve a 4-step |
| + // loop unrolling. |
| + // |
| + // v1 = v0 + (t - v0) * c |
| + // v2 = v1 + (t - v1) * c |
| + // v2 = v0 + (t - v0) * c + (t - (v0 + (t - v0) * c)) * c |
| + // v2 = v0 + (t - v0) * c + (t - v0) * c - (t - v0) * c * c |
| + // v2 = v0 + (t - v0) * c * (2 - c) |
| + // Thus c0 = c, c1 = c*(2-c). The same logic applies to c2 and c3. |
| + const float c0 = discreteTimeConstant; |
| + const float c1 = c0 * (2 - c0); |
| + const float c2 = c0 * ((c0 - 3) * c0 + 3); |
| + const float c3 = c0 * (c0 * ((4 - c0) * c0 - 6) + 4); |
| + |
| + float delta; |
| + __m128 vC = _mm_set_ps(c2, c1, c0, 0); |
| + __m128 vDelta, vValue, vResult; |
| + |
| + // Process 4 loop steps. |
| + unsigned fillToFrameTrunc = |
| + writeIndex + ((fillToFrame - writeIndex) / 4) * 4; |
| + DCHECK_LE(fillToFrameTrunc, numberOfValues); |
| + |
| + for (; writeIndex < fillToFrameTrunc; writeIndex += 4) { |
| + delta = target - value; |
| + vDelta = _mm_set_ps1(delta); |
| + vValue = _mm_set_ps1(value); |
| + |
| + vResult = _mm_add_ps(vValue, _mm_mul_ps(vDelta, vC)); |
| + _mm_storeu_ps(values + writeIndex, vResult); |
| + |
| + // Update value for next iteration. |
| + value += delta * c3; |
| + } |
| + } |
| +#endif |
| + // Serially process remaining values |
| + for (; writeIndex < fillToFrame; ++writeIndex) { |
| + values[writeIndex] = value; |
| + value += (target - value) * discreteTimeConstant; |
| + } |
| + // The previous loops may have updated |value| one extra time. |
| + // Reset it to the last computed value. |
| + if (writeIndex >= 1) |
| + value = values[writeIndex - 1]; |
| + currentFrame = fillToEndFrame; |
| + } |
| + |
| + return std::make_tuple(currentFrame, value, writeIndex); |
| +} |
| + |
| +std::tuple<size_t, float, unsigned> AudioParamTimeline::processSetValueCurve( |
| + const AutomationState& currentState, |
| + float* values, |
| + size_t currentFrame, |
| + float value, |
| + unsigned writeIndex) { |
| + auto numberOfValues = currentState.numberOfValues; |
| + auto fillToFrame = currentState.fillToFrame; |
| + auto time1 = currentState.time1; |
| + auto sampleRate = currentState.sampleRate; |
| + auto startFrame = currentState.startFrame; |
| + auto endFrame = currentState.endFrame; |
| + auto fillToEndFrame = currentState.fillToEndFrame; |
| + auto event = currentState.event; |
| + |
| + const Vector<float> curve = event->curve(); |
| + const float* curveData = curve.data(); |
| + unsigned numberOfCurvePoints = curve.size(); |
| + |
| + float curveEndValue = event->curveEndValue(); |
| + |
| + // Curve events have duration, so don't just use next event time. |
| + double duration = event->duration(); |
| + // How much to step the curve index for each frame. This is basically |
| + // the term (N - 1)/Td in the specification. |
| + double curvePointsPerFrame = event->curvePointsPerSecond() / sampleRate; |
| + |
| + if (!numberOfCurvePoints || duration <= 0 || sampleRate <= 0) { |
| + // Error condition - simply propagate previous value. |
| + currentFrame = fillToEndFrame; |
| + for (; writeIndex < fillToFrame; ++writeIndex) |
| + values[writeIndex] = value; |
| + return std::make_tuple(currentFrame, value, writeIndex); |
| + } |
| + |
| + // Save old values and recalculate information based on the curve's |
| + // duration instead of the next event time. |
| + size_t nextEventFillToFrame = fillToFrame; |
| + |
| + // fillToEndFrame = min(endFrame, |
| + // ceil(sampleRate * (time1 + duration))), |
| + // but compute this carefully in case sampleRate*(time1 + duration) is |
| + // huge. fillToEndFrame is an exclusive upper bound of the last frame |
| + // to be computed, so ceil is used. |
| + { |
| + double curveEndFrame = ceil(sampleRate * (time1 + duration)); |
| + if (endFrame > curveEndFrame) |
| + fillToEndFrame = static_cast<size_t>(curveEndFrame); |
| + else |
| + fillToEndFrame = endFrame; |
| + } |
| + |
| + // |fillToFrame| can be less than |startFrame| when the end of the |
| + // setValueCurve automation has been reached, but the next automation |
| + // has not yet started. In this case, |fillToFrame| is clipped to |
| + // |time1|+|duration| above, but |startFrame| will keep increasing |
| + // (because the current time is increasing). |
| + fillToFrame = (fillToEndFrame < startFrame) ? 0 : fillToEndFrame - startFrame; |
| + fillToFrame = std::min(fillToFrame, static_cast<size_t>(numberOfValues)); |
| + |
| + // Index into the curve data using a floating-point value. |
| + // We're scaling the number of curve points by the duration (see |
| + // curvePointsPerFrame). |
| + double curveVirtualIndex = 0; |
| + if (time1 < currentFrame / sampleRate) { |
| + // Index somewhere in the middle of the curve data. |
| + // Don't use timeToSampleFrame() since we want the exact |
| + // floating-point frame. |
| + double frameOffset = currentFrame - time1 * sampleRate; |
| + curveVirtualIndex = curvePointsPerFrame * frameOffset; |
| + } |
| + |
| + // Set the default value in case fillToFrame is 0. |
| + value = curveEndValue; |
| + |
| + // Render the stretched curve data using linear interpolation. |
| + // Oversampled curve data can be provided if sharp discontinuities are |
| + // desired. |
| + unsigned k = 0; |
| +#if CPU(X86) || CPU(X86_64) |
| + if (fillToFrame > writeIndex) { |
| + const __m128 vCurveVirtualIndex = _mm_set_ps1(curveVirtualIndex); |
| + const __m128 vCurvePointsPerFrame = _mm_set_ps1(curvePointsPerFrame); |
| + const __m128 vNumberOfCurvePointsM1 = _mm_set_ps1(numberOfCurvePoints - 1); |
| + const __m128 vN1 = _mm_set_ps1(1.0f); |
| + const __m128 vN4 = _mm_set_ps1(4.0f); |
| + |
| + __m128 vK = _mm_set_ps(3, 2, 1, 0); |
| + int aCurveIndex0[4]; |
| + int aCurveIndex1[4]; |
| + |
| + // Truncate loop steps to multiple of 4 |
| + unsigned truncatedSteps = ((fillToFrame - writeIndex) / 4) * 4; |
| + unsigned fillToFrameTrunc = writeIndex + truncatedSteps; |
| + DCHECK_LE(fillToFrameTrunc, numberOfValues); |
| + |
| + for (; writeIndex < fillToFrameTrunc; writeIndex += 4) { |
| + // Compute current index this way to minimize round-off that would |
| + // have occurred by incrementing the index by curvePointsPerFrame. |
| + __m128 vCurrentVirtualIndex = |
| + _mm_add_ps(vCurveVirtualIndex, _mm_mul_ps(vK, vCurvePointsPerFrame)); |
| + vK = _mm_add_ps(vK, vN4); |
| + |
| + // Clamp index to the last element of the array. |
| + __m128i vCurveIndex0 = _mm_cvttps_epi32( |
| + _mm_min_ps(vCurrentVirtualIndex, vNumberOfCurvePointsM1)); |
| + __m128i vCurveIndex1 = _mm_cvttps_epi32(_mm_min_ps( |
| + _mm_add_ps(vCurrentVirtualIndex, vN1), vNumberOfCurvePointsM1)); |
| + |
| + // 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. |
| + _mm_storeu_si128((__m128i*)aCurveIndex0, vCurveIndex0); |
| + _mm_storeu_si128((__m128i*)aCurveIndex1, vCurveIndex1); |
| + __m128 vC0 = |
| + _mm_set_ps(curveData[aCurveIndex0[3]], curveData[aCurveIndex0[2]], |
| + curveData[aCurveIndex0[1]], curveData[aCurveIndex0[0]]); |
| + __m128 vC1 = |
| + _mm_set_ps(curveData[aCurveIndex1[3]], curveData[aCurveIndex1[2]], |
| + curveData[aCurveIndex1[1]], curveData[aCurveIndex1[0]]); |
| + __m128 vDelta = _mm_min_ps( |
| + _mm_sub_ps(vCurrentVirtualIndex, _mm_cvtepi32_ps(vCurveIndex0)), vN1); |
| + |
| + __m128 vValue = _mm_add_ps(vC0, _mm_mul_ps(_mm_sub_ps(vC1, vC0), vDelta)); |
| + |
| + _mm_storeu_ps(values + writeIndex, vValue); |
| + } |
| + // Pass along k to the serial loop. |
| + k = truncatedSteps; |
| + } |
| + if (writeIndex >= 1) |
| + value = values[writeIndex - 1]; |
| +#endif |
| + for (; 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. |
| + DCHECK_LT(curveIndex0, numberOfCurvePoints); |
| + DCHECK_LT(curveIndex1, numberOfCurvePoints); |
| + float c0 = curveData[curveIndex0]; |
| + float c1 = curveData[curveIndex1]; |
| + double delta = std::min(currentVirtualIndex - curveIndex0, 1.0); |
| + |
| + value = c0 + (c1 - c0) * delta; |
| + |
| + values[writeIndex] = value; |
| + } |
| + |
| + // If there's any time left after the duration of this event and the |
| + // start of the next, then just propagate the last value of the |
| + // curveData. Don't modify |value| unless there is time left. |
| + if (writeIndex < nextEventFillToFrame) { |
| + value = curveEndValue; |
| + for (; writeIndex < nextEventFillToFrame; ++writeIndex) |
| + values[writeIndex] = value; |
| + } |
| + |
| + // Re-adjust current time |
| + currentFrame += nextEventFillToFrame; |
| + |
| + return std::make_tuple(currentFrame, value, writeIndex); |
| +} |
| + |
| +std::tuple<size_t, float, unsigned> AudioParamTimeline::processCancelValues( |
| + const AutomationState& currentState, |
| + float* values, |
| + size_t currentFrame, |
| + float value, |
| + unsigned writeIndex) { |
| + auto fillToFrame = currentState.fillToFrame; |
| + auto time1 = currentState.time1; |
| + auto sampleRate = currentState.sampleRate; |
| + auto controlRate = currentState.controlRate; |
| + auto fillToEndFrame = currentState.fillToEndFrame; |
| + auto event = currentState.event; |
| + auto eventIndex = currentState.eventIndex; |
| + |
| + // If the previous event was a SetTarget or ExponentialRamp |
| + // event, the current value is one sample behind. Update |
| + // the sample value by one sample, but only at the start of |
| + // this CancelValues event. |
| + if (event->hasDefaultCancelledValue()) { |
| + value = event->value(); |
| + } else { |
| + double cancelFrame = time1 * sampleRate; |
| + if (eventIndex >= 1 && cancelFrame <= currentFrame && |
| + currentFrame < cancelFrame + 1) { |
| + ParamEvent::Type lastEventType = m_events[eventIndex - 1]->getType(); |
| + if (lastEventType == ParamEvent::SetTarget) { |
| + float target = m_events[eventIndex - 1]->value(); |
| + float timeConstant = m_events[eventIndex - 1]->timeConstant(); |
| + float discreteTimeConstant = static_cast<float>( |
| + AudioUtilities::discreteTimeConstantForSampleRate(timeConstant, |
| + controlRate)); |
| + value += (target - value) * discreteTimeConstant; |
| + } |
| + } |
| + } |
| + |
| + // Simply stay at the current value. |
| + for (; writeIndex < fillToFrame; ++writeIndex) |
| + values[writeIndex] = value; |
| + |
| + currentFrame = fillToEndFrame; |
| + |
| + return std::make_tuple(currentFrame, value, writeIndex); |
| +} |
| + |
| +unsigned AudioParamTimeline::fillWithDefault(float* values, |
| + float defaultValue, |
| + size_t endFrame, |
| + unsigned writeIndex) { |
| + size_t index = writeIndex; |
| + |
| + for (; index < endFrame; ++index) |
| + values[index] = defaultValue; |
| + |
| + return index; |
| +} |
| + |
| } // namespace blink |