コード例 #1
0
void WaveShaperDSPKernel::processCurve(const float* source, float* destination, size_t framesToProcess)
{
    ASSERT(source);
    ASSERT(destination);
    ASSERT(waveShaperProcessor());

    DOMFloat32Array* curve = waveShaperProcessor()->curve();
    if (!curve) {
        // Act as "straight wire" pass-through if no curve is set.
        memcpy(destination, source, sizeof(float) * framesToProcess);
        return;
    }

    float* curveData = curve->data();
    int curveLength = curve->length();

    ASSERT(curveData);

    if (!curveData || !curveLength) {
        memcpy(destination, source, sizeof(float) * framesToProcess);
        return;
    }

    // Apply waveshaping curve.
    for (unsigned i = 0; i < framesToProcess; ++i) {
        const float input = source[i];

        // Calculate a virtual index based on input -1 -> +1 with -1 being curve[0], +1 being
        // curve[curveLength - 1], and 0 being at the center of the curve data. Then linearly
        // interpolate between the two points in the curve.
        double virtualIndex = 0.5 * (input + 1) * (curveLength - 1);
        double output;

        if (virtualIndex < 0) {
            // input < -1, so use curve[0]
            output = curveData[0];
        } else if (virtualIndex >= curveLength - 1) {
            // input >= 1, so use last curve value
            output = curveData[curveLength - 1];
        } else {
            // The general case where -1 <= input < 1, where 0 <= virtualIndex < curveLength - 1,
            // so interpolate between the nearest samples on the curve.
            unsigned index1 = static_cast<unsigned>(virtualIndex);
            unsigned index2 = index1 + 1;
            double interpolationFactor = virtualIndex - index1;

            double value1 = curveData[index1];
            double value2 = curveData[index2];

            output = (1.0 - interpolationFactor) * value1 + interpolationFactor * value2;
        }
        destination[i] = output;
    }
}
コード例 #2
0
float AudioParamTimeline::valuesForFrameRangeImpl(
    size_t startFrame,
    size_t endFrame,
    float defaultValue,
    float* values,
    unsigned numberOfValues,
    double sampleRate,
    double controlRate)
{
    ASSERT(values);
    if (!values)
        return defaultValue;

    // 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;
        return defaultValue;
    }

    // Maintain a running time (frame) and index for writing the values buffer.
    size_t currentFrame = startFrame;
    unsigned writeIndex = 0;

    // 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 = std::min(endFrame, static_cast<size_t>(ceil(firstEventTime * sampleRate)));
        ASSERT(fillToFrame >= startFrame);
        fillToFrame -= startFrame;
        fillToFrame = std::min(fillToFrame, static_cast<size_t>(numberOfValues));
        for (; writeIndex < fillToFrame; ++writeIndex)
            values[writeIndex] = defaultValue;

        currentFrame += fillToFrame;
    }

    float value = defaultValue;

    // Go through each event and render the value buffer where the times overlap,
    // stopping when we've rendered all the requested values.
    // FIXME: could try to optimize by avoiding having to iterate starting from the very first event
    // and keeping track of a "current" event index.
    int n = m_events.size();
    for (int i = 0; i < n && writeIndex < numberOfValues; ++i) {
        ParamEvent& event = m_events[i];
        ParamEvent* nextEvent = i < n - 1 ? &(m_events[i + 1]) : 0;

        // Wait until we get a more recent event.
        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.type() == ParamEvent::SetValue
                && (eventFrame <= currentFrame)
                && (currentFrame < eventFrame + 1))))
                continue;
        }

        float value1 = event.value();
        double time1 = event.time();
        float value2 = nextEvent ? nextEvent->value() : value1;
        double time2 = nextEvent ? nextEvent->time() : endFrame / sampleRate + 1;

        double deltaTime = time2 - time1;
        float k = deltaTime > 0 ? 1 / deltaTime : 0;

        // |fillToEndFrame| is the exclusive upper bound of the last frame to be computed for this
        // event.  It's either the last desired frame (|endFrame|) or derived from the end time of
        // the next event (time2). We compute ceil(time2*sampleRate) because fillToEndFrame is the
        // exclusive upper bound.  Consider the case where |startFrame| = 128 and time2 = 128.1
        // (assuming sampleRate = 1).  Since time2 is greater than 128, we want to output a value
        // for frame 128.  This requires that fillToEndFrame be at least 129.  This is achieved by
        // ceil(time2).
        size_t fillToEndFrame = std::min(endFrame, static_cast<size_t>(ceil(time2 * sampleRate)));
        ASSERT(fillToEndFrame >= startFrame);
        size_t fillToFrame = fillToEndFrame - startFrame;
        fillToFrame = std::min(fillToFrame, static_cast<size_t>(numberOfValues));

        ParamEvent::Type nextEventType = nextEvent ? static_cast<ParamEvent::Type>(nextEvent->type()) : ParamEvent::LastType /* unknown */;

        // 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)
            // 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.
            currentFrame += fillToFrameTrunc - writeIndex;

            // Process 4 loop steps.
            for (; writeIndex < fillToFrameTrunc; writeIndex += 4) {
                _mm_storeu_ps(values + writeIndex, vValue);
                vValue = _mm_add_ps(vValue, vInc);
            }
#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;
            }
        } else if (nextEventType == ParamEvent::ExponentialRampToValue) {
            if (value1 <= 0 || value2 <= 0) {
                // Handle negative values error case by propagating previous value.
                for (; writeIndex < fillToFrame; ++writeIndex)
                    values[writeIndex] = value;
            } else {
                float 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;
                }
            }
        } else {
            // Handle event types not requiring looking ahead to the next event.
            switch (event.type()) {
            case ParamEvent::SetValue:
            case ParamEvent::LinearRampToValue:
            case ParamEvent::ExponentialRampToValue:
                {
                    currentFrame = fillToEndFrame;

                    // Simply stay at a constant value.
                    value = event.value();
                    for (; writeIndex < fillToFrame; ++writeIndex)
                        values[writeIndex] = value;

                    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);
                    }
#if CPU(X86) || CPU(X86_64)
                    // 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;
                    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;
                    }

                    currentFrame = fillToEndFrame;

                    break;
                }

            case ParamEvent::SetValueCurve:
                {
                    DOMFloat32Array* curve = event.curve();
                    float* curveData = curve ? curve->data() : 0;
                    unsigned numberOfCurvePoints = curve ? curve->length() : 0;

                    // Curve events have duration, so don't just use next event time.
                    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.
                        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;

                    // Use ceil here for the same reason as using ceil above: fillToEndFrame is an
                    // exclusive upper bound of the last frame to be computed.
                    fillToEndFrame = std::min(endFrame, static_cast<size_t>(ceil(sampleRate*(time1 + duration))));
                    // |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 = curveData[numberOfCurvePoints - 1];

                    // 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)
                    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;
                    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 the above loop was run, pass along the last computed value.
                    if (truncatedSteps > 0) {
                        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.
                        ASSERT(curveIndex0 < numberOfCurvePoints);
                        ASSERT(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.
                    value = curveData[numberOfCurvePoints - 1];
                    for (; writeIndex < nextEventFillToFrame; ++writeIndex)
                        values[writeIndex] = value;

                    // Re-adjust current time
                    currentFrame = nextEventFillToFrame;

                    break;
                }
            case ParamEvent::LastType:
                ASSERT_NOT_REACHED();
                break;
            }
        }
    }

    // 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;

    return value;
}
コード例 #3
0
float AudioParamTimeline::valuesForTimeRangeImpl(
    double startTime,
    double endTime,
    float defaultValue,
    float* values,
    unsigned numberOfValues,
    double sampleRate,
    double controlRate)
{
    ASSERT(values);
    if (!values)
        return defaultValue;

    // Return default value if there are no events matching the desired time range.
    if (!m_events.size() || endTime <= m_events[0].time()) {
        for (unsigned i = 0; i < numberOfValues; ++i)
            values[i] = defaultValue;
        return defaultValue;
    }

    // Maintain a running time and index for writing the values buffer.
    double currentTime = startTime;
    unsigned writeIndex = 0;

    // If first event is after startTime then fill initial part of values buffer with defaultValue
    // until we reach the first event time.
    double firstEventTime = m_events[0].time();
    if (firstEventTime > startTime) {
        double fillToTime = std::min(endTime, firstEventTime);
        unsigned fillToFrame = AudioUtilities::timeToSampleFrame(fillToTime - startTime, sampleRate);
        fillToFrame = std::min(fillToFrame, numberOfValues);
        for (; writeIndex < fillToFrame; ++writeIndex)
            values[writeIndex] = defaultValue;

        currentTime = fillToTime;
    }

    float value = defaultValue;

    // Go through each event and render the value buffer where the times overlap,
    // stopping when we've rendered all the requested values.
    // FIXME: could try to optimize by avoiding having to iterate starting from the very first event
    // and keeping track of a "current" event index.
    int n = m_events.size();
    for (int i = 0; i < n && writeIndex < numberOfValues; ++i) {
        ParamEvent& event = m_events[i];
        ParamEvent* nextEvent = i < n - 1 ? &(m_events[i + 1]) : 0;

        // Wait until we get a more recent event.
        if (nextEvent && nextEvent->time() < currentTime)
            continue;

        float value1 = event.value();
        double time1 = event.time();
        float value2 = nextEvent ? nextEvent->value() : value1;
        double time2 = nextEvent ? nextEvent->time() : endTime + 1;

        double deltaTime = time2 - time1;
        float k = deltaTime > 0 ? 1 / deltaTime : 0;
        double sampleFrameTimeIncr = 1 / sampleRate;

        double fillToTime = std::min(endTime, time2);
        unsigned fillToFrame = AudioUtilities::timeToSampleFrame(fillToTime - startTime, sampleRate);
        fillToFrame = std::min(fillToFrame, numberOfValues);

        ParamEvent::Type nextEventType = nextEvent ? static_cast<ParamEvent::Type>(nextEvent->type()) : ParamEvent::LastType /* unknown */;

        // First handle linear and exponential ramps which require looking ahead to the next event.
        if (nextEventType == ParamEvent::LinearRampToValue) {
            for (; writeIndex < fillToFrame; ++writeIndex) {
                float x = (currentTime - time1) * k;
                value = (1 - x) * value1 + x * value2;
                values[writeIndex] = value;
                currentTime += sampleFrameTimeIncr;
            }
        } else if (nextEventType == ParamEvent::ExponentialRampToValue) {
            if (value1 <= 0 || value2 <= 0) {
                // Handle negative values error case by propagating previous value.
                for (; writeIndex < fillToFrame; ++writeIndex)
                    values[writeIndex] = value;
            } else {
                float numSampleFrames = deltaTime * sampleRate;
                // The value goes exponentially from value1 to value2 in a duration of deltaTime seconds (corresponding to numSampleFrames).
                // Compute the per-sample multiplier.
                float multiplier = powf(value2 / value1, 1 / numSampleFrames);

                // Set the starting value of the exponential ramp. This is the same as multiplier ^
                // AudioUtilities::timeToSampleFrame(currentTime - time1, sampleRate), but is more
                // accurate, especially if multiplier is close to 1.
                value = value1 * powf(value2 / value1,
                    AudioUtilities::timeToSampleFrame(currentTime - time1, sampleRate) / numSampleFrames);

                for (; writeIndex < fillToFrame; ++writeIndex) {
                    values[writeIndex] = value;
                    value *= multiplier;
                    currentTime += sampleFrameTimeIncr;
                }
            }
        } else {
            // Handle event types not requiring looking ahead to the next event.
            switch (event.type()) {
            case ParamEvent::SetValue:
            case ParamEvent::LinearRampToValue:
            case ParamEvent::ExponentialRampToValue:
                {
                    currentTime = fillToTime;

                    // Simply stay at a constant value.
                    value = event.value();
                    for (; writeIndex < fillToFrame; ++writeIndex)
                        values[writeIndex] = value;

                    break;
                }

            case ParamEvent::SetTarget:
                {
                    currentTime = fillToTime;

                    // Exponential approach to target value with given time constant.
                    float target = event.value();
                    float timeConstant = event.timeConstant();
                    float discreteTimeConstant = static_cast<float>(AudioUtilities::discreteTimeConstantForSampleRate(timeConstant, controlRate));

                    for (; writeIndex < fillToFrame; ++writeIndex) {
                        values[writeIndex] = value;
                        value += (target - value) * discreteTimeConstant;
                    }

                    break;
                }

            case ParamEvent::SetValueCurve:
                {
                    DOMFloat32Array* curve = event.curve();
                    float* curveData = curve ? curve->data() : 0;
                    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;

                    if (!curve || !curveData || !numberOfCurvePoints || duration <= 0 || sampleRate <= 0) {
                        // Error condition - simply propagate previous value.
                        currentTime = fillToTime;
                        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.
                    unsigned nextEventFillToFrame = fillToFrame;
                    float 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;
                    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;
                        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];

                        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.
                    for (; writeIndex < nextEventFillToFrame; ++writeIndex)
                        values[writeIndex] = value;

                    // Re-adjust current time
                    currentTime = nextEventFillToTime;

                    break;
                }
            }
        }
    }

    // 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;

    return value;
}