Home | History | Annotate | Download | only in webaudio
      1 /*
      2  * Copyright (C) 2011 Google Inc. All rights reserved.
      3  *
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions
      6  * are met:
      7  *
      8  * 1.  Redistributions of source code must retain the above copyright
      9  *     notice, this list of conditions and the following disclaimer.
     10  * 2.  Redistributions in binary form must reproduce the above copyright
     11  *     notice, this list of conditions and the following disclaimer in the
     12  *     documentation and/or other materials provided with the distribution.
     13  *
     14  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
     15  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
     16  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
     17  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
     18  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
     19  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
     20  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
     21  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
     23  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     24  */
     25 
     26 #include "config.h"
     27 
     28 #if ENABLE(WEB_AUDIO)
     29 
     30 #include "modules/webaudio/AudioParamTimeline.h"
     31 
     32 #include "bindings/v8/ExceptionState.h"
     33 #include "core/dom/ExceptionCode.h"
     34 #include "platform/audio/AudioUtilities.h"
     35 #include "platform/FloatConversion.h"
     36 #include "wtf/MathExtras.h"
     37 #include <algorithm>
     38 
     39 using namespace std;
     40 
     41 namespace WebCore {
     42 
     43 void AudioParamTimeline::setValueAtTime(float value, double time)
     44 {
     45     insertEvent(ParamEvent(ParamEvent::SetValue, value, time, 0, 0, nullptr));
     46 }
     47 
     48 void AudioParamTimeline::linearRampToValueAtTime(float value, double time)
     49 {
     50     insertEvent(ParamEvent(ParamEvent::LinearRampToValue, value, time, 0, 0, nullptr));
     51 }
     52 
     53 void AudioParamTimeline::exponentialRampToValueAtTime(float value, double time, ExceptionState& exceptionState)
     54 {
     55     ASSERT(isMainThread());
     56     if (value <= 0) {
     57         exceptionState.throwDOMException(
     58             InvalidStateError,
     59             "Target value for exponential ramp must be positive: " + String::number(value));
     60         return;
     61     }
     62 
     63     insertEvent(ParamEvent(ParamEvent::ExponentialRampToValue, value, time, 0, 0, nullptr));
     64 }
     65 
     66 void AudioParamTimeline::setTargetAtTime(float target, double time, double timeConstant)
     67 {
     68     insertEvent(ParamEvent(ParamEvent::SetTarget, target, time, timeConstant, 0, nullptr));
     69 }
     70 
     71 void AudioParamTimeline::setValueCurveAtTime(Float32Array* curve, double time, double duration)
     72 {
     73     insertEvent(ParamEvent(ParamEvent::SetValueCurve, 0, time, 0, duration, curve));
     74 }
     75 
     76 static bool isValidNumber(float x)
     77 {
     78     return !std::isnan(x) && !std::isinf(x);
     79 }
     80 
     81 void AudioParamTimeline::insertEvent(const ParamEvent& event)
     82 {
     83     // Sanity check the event. Be super careful we're not getting infected with NaN or Inf.
     84     bool isValid = event.type() < ParamEvent::LastType
     85         && isValidNumber(event.value())
     86         && isValidNumber(event.time())
     87         && isValidNumber(event.timeConstant())
     88         && isValidNumber(event.duration())
     89         && event.duration() >= 0;
     90 
     91     ASSERT(isValid);
     92     if (!isValid)
     93         return;
     94 
     95     MutexLocker locker(m_eventsLock);
     96 
     97     unsigned i = 0;
     98     double insertTime = event.time();
     99     for (i = 0; i < m_events.size(); ++i) {
    100         // Overwrite same event type and time.
    101         if (m_events[i].time() == insertTime && m_events[i].type() == event.type()) {
    102             m_events[i] = event;
    103             return;
    104         }
    105 
    106         if (m_events[i].time() > insertTime)
    107             break;
    108     }
    109 
    110     m_events.insert(i, event);
    111 }
    112 
    113 void AudioParamTimeline::cancelScheduledValues(double startTime)
    114 {
    115     MutexLocker locker(m_eventsLock);
    116 
    117     // Remove all events starting at startTime.
    118     for (unsigned i = 0; i < m_events.size(); ++i) {
    119         if (m_events[i].time() >= startTime) {
    120             m_events.remove(i, m_events.size() - i);
    121             break;
    122         }
    123     }
    124 }
    125 
    126 float AudioParamTimeline::valueForContextTime(AudioContext* context, float defaultValue, bool& hasValue)
    127 {
    128     ASSERT(context);
    129 
    130     {
    131         MutexTryLocker tryLocker(m_eventsLock);
    132         if (!tryLocker.locked() || !context || !m_events.size() || context->currentTime() < m_events[0].time()) {
    133             hasValue = false;
    134             return defaultValue;
    135         }
    136     }
    137 
    138     // Ask for just a single value.
    139     float value;
    140     double sampleRate = context->sampleRate();
    141     double startTime = context->currentTime();
    142     double endTime = startTime + 1.1 / sampleRate; // time just beyond one sample-frame
    143     double controlRate = sampleRate / AudioNode::ProcessingSizeInFrames; // one parameter change per render quantum
    144     value = valuesForTimeRange(startTime, endTime, defaultValue, &value, 1, sampleRate, controlRate);
    145 
    146     hasValue = true;
    147     return value;
    148 }
    149 
    150 float AudioParamTimeline::valuesForTimeRange(
    151     double startTime,
    152     double endTime,
    153     float defaultValue,
    154     float* values,
    155     unsigned numberOfValues,
    156     double sampleRate,
    157     double controlRate)
    158 {
    159     // We can't contend the lock in the realtime audio thread.
    160     MutexTryLocker tryLocker(m_eventsLock);
    161     if (!tryLocker.locked()) {
    162         if (values) {
    163             for (unsigned i = 0; i < numberOfValues; ++i)
    164                 values[i] = defaultValue;
    165         }
    166         return defaultValue;
    167     }
    168 
    169     float value = valuesForTimeRangeImpl(startTime, endTime, defaultValue, values, numberOfValues, sampleRate, controlRate);
    170 
    171     return value;
    172 }
    173 
    174 float AudioParamTimeline::valuesForTimeRangeImpl(
    175     double startTime,
    176     double endTime,
    177     float defaultValue,
    178     float* values,
    179     unsigned numberOfValues,
    180     double sampleRate,
    181     double controlRate)
    182 {
    183     ASSERT(values);
    184     if (!values)
    185         return defaultValue;
    186 
    187     // Return default value if there are no events matching the desired time range.
    188     if (!m_events.size() || endTime <= m_events[0].time()) {
    189         for (unsigned i = 0; i < numberOfValues; ++i)
    190             values[i] = defaultValue;
    191         return defaultValue;
    192     }
    193 
    194     // Maintain a running time and index for writing the values buffer.
    195     double currentTime = startTime;
    196     unsigned writeIndex = 0;
    197 
    198     // If first event is after startTime then fill initial part of values buffer with defaultValue
    199     // until we reach the first event time.
    200     double firstEventTime = m_events[0].time();
    201     if (firstEventTime > startTime) {
    202         double fillToTime = min(endTime, firstEventTime);
    203         unsigned fillToFrame = AudioUtilities::timeToSampleFrame(fillToTime - startTime, sampleRate);
    204         fillToFrame = min(fillToFrame, numberOfValues);
    205         for (; writeIndex < fillToFrame; ++writeIndex)
    206             values[writeIndex] = defaultValue;
    207 
    208         currentTime = fillToTime;
    209     }
    210 
    211     float value = defaultValue;
    212 
    213     // Go through each event and render the value buffer where the times overlap,
    214     // stopping when we've rendered all the requested values.
    215     // FIXME: could try to optimize by avoiding having to iterate starting from the very first event
    216     // and keeping track of a "current" event index.
    217     int n = m_events.size();
    218     for (int i = 0; i < n && writeIndex < numberOfValues; ++i) {
    219         ParamEvent& event = m_events[i];
    220         ParamEvent* nextEvent = i < n - 1 ? &(m_events[i + 1]) : 0;
    221 
    222         // Wait until we get a more recent event.
    223         if (nextEvent && nextEvent->time() < currentTime)
    224             continue;
    225 
    226         float value1 = event.value();
    227         double time1 = event.time();
    228         float value2 = nextEvent ? nextEvent->value() : value1;
    229         double time2 = nextEvent ? nextEvent->time() : endTime + 1;
    230 
    231         double deltaTime = time2 - time1;
    232         float k = deltaTime > 0 ? 1 / deltaTime : 0;
    233         double sampleFrameTimeIncr = 1 / sampleRate;
    234 
    235         double fillToTime = min(endTime, time2);
    236         unsigned fillToFrame = AudioUtilities::timeToSampleFrame(fillToTime - startTime, sampleRate);
    237         fillToFrame = min(fillToFrame, numberOfValues);
    238 
    239         ParamEvent::Type nextEventType = nextEvent ? static_cast<ParamEvent::Type>(nextEvent->type()) : ParamEvent::LastType /* unknown */;
    240 
    241         // First handle linear and exponential ramps which require looking ahead to the next event.
    242         if (nextEventType == ParamEvent::LinearRampToValue) {
    243             for (; writeIndex < fillToFrame; ++writeIndex) {
    244                 float x = (currentTime - time1) * k;
    245                 value = (1 - x) * value1 + x * value2;
    246                 values[writeIndex] = value;
    247                 currentTime += sampleFrameTimeIncr;
    248             }
    249         } else if (nextEventType == ParamEvent::ExponentialRampToValue) {
    250             if (value1 <= 0 || value2 <= 0) {
    251                 // Handle negative values error case by propagating previous value.
    252                 for (; writeIndex < fillToFrame; ++writeIndex)
    253                     values[writeIndex] = value;
    254             } else {
    255                 float numSampleFrames = deltaTime * sampleRate;
    256                 // The value goes exponentially from value1 to value2 in a duration of deltaTime seconds (corresponding to numSampleFrames).
    257                 // Compute the per-sample multiplier.
    258                 float multiplier = powf(value2 / value1, 1 / numSampleFrames);
    259 
    260                 // Set the starting value of the exponential ramp. This is the same as multiplier ^
    261                 // AudioUtilities::timeToSampleFrame(currentTime - time1, sampleRate), but is more
    262                 // accurate, especially if multiplier is close to 1.
    263                 value = value1 * powf(value2 / value1,
    264                                       AudioUtilities::timeToSampleFrame(currentTime - time1, sampleRate) / numSampleFrames);
    265 
    266                 for (; writeIndex < fillToFrame; ++writeIndex) {
    267                     values[writeIndex] = value;
    268                     value *= multiplier;
    269                     currentTime += sampleFrameTimeIncr;
    270                 }
    271             }
    272         } else {
    273             // Handle event types not requiring looking ahead to the next event.
    274             switch (event.type()) {
    275             case ParamEvent::SetValue:
    276             case ParamEvent::LinearRampToValue:
    277             case ParamEvent::ExponentialRampToValue:
    278                 {
    279                     currentTime = fillToTime;
    280 
    281                     // Simply stay at a constant value.
    282                     value = event.value();
    283                     for (; writeIndex < fillToFrame; ++writeIndex)
    284                         values[writeIndex] = value;
    285 
    286                     break;
    287                 }
    288 
    289             case ParamEvent::SetTarget:
    290                 {
    291                     currentTime = fillToTime;
    292 
    293                     // Exponential approach to target value with given time constant.
    294                     float target = event.value();
    295                     float timeConstant = event.timeConstant();
    296                     float discreteTimeConstant = static_cast<float>(AudioUtilities::discreteTimeConstantForSampleRate(timeConstant, controlRate));
    297 
    298                     for (; writeIndex < fillToFrame; ++writeIndex) {
    299                         values[writeIndex] = value;
    300                         value += (target - value) * discreteTimeConstant;
    301                     }
    302 
    303                     break;
    304                 }
    305 
    306             case ParamEvent::SetValueCurve:
    307                 {
    308                     Float32Array* curve = event.curve();
    309                     float* curveData = curve ? curve->data() : 0;
    310                     unsigned numberOfCurvePoints = curve ? curve->length() : 0;
    311 
    312                     // Curve events have duration, so don't just use next event time.
    313                     float duration = event.duration();
    314                     float durationFrames = duration * sampleRate;
    315                     float curvePointsPerFrame = static_cast<float>(numberOfCurvePoints) / durationFrames;
    316 
    317                     if (!curve || !curveData || !numberOfCurvePoints || duration <= 0 || sampleRate <= 0) {
    318                         // Error condition - simply propagate previous value.
    319                         currentTime = fillToTime;
    320                         for (; writeIndex < fillToFrame; ++writeIndex)
    321                             values[writeIndex] = value;
    322                         break;
    323                     }
    324 
    325                     // Save old values and recalculate information based on the curve's duration
    326                     // instead of the next event time.
    327                     unsigned nextEventFillToFrame = fillToFrame;
    328                     float nextEventFillToTime = fillToTime;
    329                     fillToTime = min(endTime, time1 + duration);
    330                     fillToFrame = AudioUtilities::timeToSampleFrame(fillToTime - startTime, sampleRate);
    331                     fillToFrame = min(fillToFrame, numberOfValues);
    332 
    333                     // Index into the curve data using a floating-point value.
    334                     // We're scaling the number of curve points by the duration (see curvePointsPerFrame).
    335                     float curveVirtualIndex = 0;
    336                     if (time1 < currentTime) {
    337                         // Index somewhere in the middle of the curve data.
    338                         // Don't use timeToSampleFrame() since we want the exact floating-point frame.
    339                         float frameOffset = (currentTime - time1) * sampleRate;
    340                         curveVirtualIndex = curvePointsPerFrame * frameOffset;
    341                     }
    342 
    343                     // Render the stretched curve data using nearest neighbor sampling.
    344                     // Oversampled curve data can be provided if smoothness is desired.
    345                     for (; writeIndex < fillToFrame; ++writeIndex) {
    346                         // Ideally we'd use round() from MathExtras, but we're in a tight loop here
    347                         // and we're trading off precision for extra speed.
    348                         unsigned curveIndex = static_cast<unsigned>(0.5 + curveVirtualIndex);
    349 
    350                         curveVirtualIndex += curvePointsPerFrame;
    351 
    352                         // Bounds check.
    353                         if (curveIndex < numberOfCurvePoints)
    354                             value = curveData[curveIndex];
    355 
    356                         values[writeIndex] = value;
    357                     }
    358 
    359                     // If there's any time left after the duration of this event and the start
    360                     // of the next, then just propagate the last value.
    361                     for (; writeIndex < nextEventFillToFrame; ++writeIndex)
    362                         values[writeIndex] = value;
    363 
    364                     // Re-adjust current time
    365                     currentTime = nextEventFillToTime;
    366 
    367                     break;
    368                 }
    369             }
    370         }
    371     }
    372 
    373     // If there's any time left after processing the last event then just propagate the last value
    374     // to the end of the values buffer.
    375     for (; writeIndex < numberOfValues; ++writeIndex)
    376         values[writeIndex] = value;
    377 
    378     return value;
    379 }
    380 
    381 } // namespace WebCore
    382 
    383 #endif // ENABLE(WEB_AUDIO)
    384