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/core/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
39namespace blink {
40
41static bool isValidAudioParamValue(float value, ExceptionState& exceptionState)
42{
43    if (std::isfinite(value))
44        return true;
45
46    exceptionState.throwDOMException(
47        InvalidStateError,
48        "Target value must be a finite number: " + String::number(value));
49    return false;
50}
51
52static bool isPositiveAudioParamValue(float value, ExceptionState& exceptionState)
53{
54    if (std::isfinite(value) && (value > 0))
55        return true;
56
57    exceptionState.throwDOMException(
58        InvalidStateError,
59        "Target value must be a finite positive number: " + String::number(value));
60    return false;
61}
62
63static bool isValidAudioParamTime(double time, ExceptionState& exceptionState, String message)
64{
65    if (std::isfinite(time) && (time >= 0))
66        return true;
67
68    exceptionState.throwDOMException(
69        InvalidStateError,
70        message + " must be a finite non-negative number: " + String::number(time));
71    return false;
72}
73
74static bool isPositiveAudioParamTime(double time, ExceptionState& exceptionState, String message)
75{
76    if (std::isfinite(time) && (time > 0))
77        return true;
78
79    exceptionState.throwDOMException(
80        InvalidStateError,
81        message + " must be a finite positive number: " + String::number(time));
82    return false;
83}
84
85static bool isValidAudioParamTime(double time, ExceptionState& exceptionState)
86{
87    return isValidAudioParamTime(time, exceptionState, "Time");
88}
89
90void AudioParamTimeline::setValueAtTime(float value, double time, ExceptionState& exceptionState)
91{
92    ASSERT(isMainThread());
93
94    if (!isValidAudioParamValue(value, exceptionState)
95        || !isValidAudioParamTime(time, exceptionState))
96        return;
97
98    insertEvent(ParamEvent(ParamEvent::SetValue, value, time, 0, 0, nullptr));
99}
100
101void AudioParamTimeline::linearRampToValueAtTime(float value, double time, ExceptionState& exceptionState)
102{
103    ASSERT(isMainThread());
104
105    if (!isValidAudioParamValue(value, exceptionState)
106        || !isValidAudioParamTime(time, exceptionState))
107        return;
108
109    insertEvent(ParamEvent(ParamEvent::LinearRampToValue, value, time, 0, 0, nullptr));
110}
111
112void AudioParamTimeline::exponentialRampToValueAtTime(float value, double time, ExceptionState& exceptionState)
113{
114    ASSERT(isMainThread());
115
116    if (!isPositiveAudioParamValue(value, exceptionState)
117        || !isValidAudioParamTime(time, exceptionState))
118        return;
119
120    insertEvent(ParamEvent(ParamEvent::ExponentialRampToValue, value, time, 0, 0, nullptr));
121}
122
123void AudioParamTimeline::setTargetAtTime(float target, double time, double timeConstant, ExceptionState& exceptionState)
124{
125    ASSERT(isMainThread());
126
127    if (!isValidAudioParamValue(target, exceptionState)
128        || !isValidAudioParamTime(time, exceptionState)
129        || !isValidAudioParamTime(timeConstant, exceptionState, "Time constant"))
130        return;
131
132    insertEvent(ParamEvent(ParamEvent::SetTarget, target, time, timeConstant, 0, nullptr));
133}
134
135void AudioParamTimeline::setValueCurveAtTime(Float32Array* curve, double time, double duration, ExceptionState& exceptionState)
136{
137    ASSERT(isMainThread());
138
139    if (!isValidAudioParamTime(time, exceptionState)
140        || !isPositiveAudioParamTime(duration, exceptionState, "Duration"))
141        return;
142
143    insertEvent(ParamEvent(ParamEvent::SetValueCurve, 0, time, 0, duration, curve));
144}
145
146void AudioParamTimeline::insertEvent(const ParamEvent& event)
147{
148    // Sanity check the event. Be super careful we're not getting infected with NaN or Inf. These
149    // should have been handled by the caller.
150    bool isValid = event.type() < ParamEvent::LastType
151        && std::isfinite(event.value())
152        && std::isfinite(event.time())
153        && std::isfinite(event.timeConstant())
154        && std::isfinite(event.duration())
155        && event.duration() >= 0;
156
157    ASSERT(isValid);
158    if (!isValid)
159        return;
160
161    MutexLocker locker(m_eventsLock);
162
163    unsigned i = 0;
164    double insertTime = event.time();
165    for (i = 0; i < m_events.size(); ++i) {
166        // Overwrite same event type and time.
167        if (m_events[i].time() == insertTime && m_events[i].type() == event.type()) {
168            m_events[i] = event;
169            return;
170        }
171
172        if (m_events[i].time() > insertTime)
173            break;
174    }
175
176    m_events.insert(i, event);
177}
178
179void AudioParamTimeline::cancelScheduledValues(double startTime, ExceptionState& exceptionState)
180{
181    ASSERT(isMainThread());
182
183    if (!std::isfinite(startTime)) {
184        exceptionState.throwDOMException(
185            InvalidStateError,
186            "Time must be a finite number: " + String::number(startTime));
187    }
188
189    MutexLocker locker(m_eventsLock);
190
191    // Remove all events starting at startTime.
192    for (unsigned i = 0; i < m_events.size(); ++i) {
193        if (m_events[i].time() >= startTime) {
194            m_events.remove(i, m_events.size() - i);
195            break;
196        }
197    }
198}
199
200float AudioParamTimeline::valueForContextTime(AudioContext* context, float defaultValue, bool& hasValue)
201{
202    ASSERT(context);
203
204    {
205        MutexTryLocker tryLocker(m_eventsLock);
206        if (!tryLocker.locked() || !context || !m_events.size() || context->currentTime() < m_events[0].time()) {
207            hasValue = false;
208            return defaultValue;
209        }
210    }
211
212    // Ask for just a single value.
213    float value;
214    double sampleRate = context->sampleRate();
215    double startTime = context->currentTime();
216    double endTime = startTime + 1.1 / sampleRate; // time just beyond one sample-frame
217    double controlRate = sampleRate / AudioNode::ProcessingSizeInFrames; // one parameter change per render quantum
218    value = valuesForTimeRange(startTime, endTime, defaultValue, &value, 1, sampleRate, controlRate);
219
220    hasValue = true;
221    return value;
222}
223
224float AudioParamTimeline::valuesForTimeRange(
225    double startTime,
226    double endTime,
227    float defaultValue,
228    float* values,
229    unsigned numberOfValues,
230    double sampleRate,
231    double controlRate)
232{
233    // We can't contend the lock in the realtime audio thread.
234    MutexTryLocker tryLocker(m_eventsLock);
235    if (!tryLocker.locked()) {
236        if (values) {
237            for (unsigned i = 0; i < numberOfValues; ++i)
238                values[i] = defaultValue;
239        }
240        return defaultValue;
241    }
242
243    float value = valuesForTimeRangeImpl(startTime, endTime, defaultValue, values, numberOfValues, sampleRate, controlRate);
244
245    return value;
246}
247
248float AudioParamTimeline::valuesForTimeRangeImpl(
249    double startTime,
250    double endTime,
251    float defaultValue,
252    float* values,
253    unsigned numberOfValues,
254    double sampleRate,
255    double controlRate)
256{
257    ASSERT(values);
258    if (!values)
259        return defaultValue;
260
261    // Return default value if there are no events matching the desired time range.
262    if (!m_events.size() || endTime <= m_events[0].time()) {
263        for (unsigned i = 0; i < numberOfValues; ++i)
264            values[i] = defaultValue;
265        return defaultValue;
266    }
267
268    // Maintain a running time and index for writing the values buffer.
269    double currentTime = startTime;
270    unsigned writeIndex = 0;
271
272    // If first event is after startTime then fill initial part of values buffer with defaultValue
273    // until we reach the first event time.
274    double firstEventTime = m_events[0].time();
275    if (firstEventTime > startTime) {
276        double fillToTime = std::min(endTime, firstEventTime);
277        unsigned fillToFrame = AudioUtilities::timeToSampleFrame(fillToTime - startTime, sampleRate);
278        fillToFrame = std::min(fillToFrame, numberOfValues);
279        for (; writeIndex < fillToFrame; ++writeIndex)
280            values[writeIndex] = defaultValue;
281
282        currentTime = fillToTime;
283    }
284
285    float value = defaultValue;
286
287    // Go through each event and render the value buffer where the times overlap,
288    // stopping when we've rendered all the requested values.
289    // FIXME: could try to optimize by avoiding having to iterate starting from the very first event
290    // and keeping track of a "current" event index.
291    int n = m_events.size();
292    for (int i = 0; i < n && writeIndex < numberOfValues; ++i) {
293        ParamEvent& event = m_events[i];
294        ParamEvent* nextEvent = i < n - 1 ? &(m_events[i + 1]) : 0;
295
296        // Wait until we get a more recent event.
297        if (nextEvent && nextEvent->time() < currentTime)
298            continue;
299
300        float value1 = event.value();
301        double time1 = event.time();
302        float value2 = nextEvent ? nextEvent->value() : value1;
303        double time2 = nextEvent ? nextEvent->time() : endTime + 1;
304
305        double deltaTime = time2 - time1;
306        float k = deltaTime > 0 ? 1 / deltaTime : 0;
307        double sampleFrameTimeIncr = 1 / sampleRate;
308
309        double fillToTime = std::min(endTime, time2);
310        unsigned fillToFrame = AudioUtilities::timeToSampleFrame(fillToTime - startTime, sampleRate);
311        fillToFrame = std::min(fillToFrame, numberOfValues);
312
313        ParamEvent::Type nextEventType = nextEvent ? static_cast<ParamEvent::Type>(nextEvent->type()) : ParamEvent::LastType /* unknown */;
314
315        // First handle linear and exponential ramps which require looking ahead to the next event.
316        if (nextEventType == ParamEvent::LinearRampToValue) {
317            for (; writeIndex < fillToFrame; ++writeIndex) {
318                float x = (currentTime - time1) * k;
319                value = (1 - x) * value1 + x * value2;
320                values[writeIndex] = value;
321                currentTime += sampleFrameTimeIncr;
322            }
323        } else if (nextEventType == ParamEvent::ExponentialRampToValue) {
324            if (value1 <= 0 || value2 <= 0) {
325                // Handle negative values error case by propagating previous value.
326                for (; writeIndex < fillToFrame; ++writeIndex)
327                    values[writeIndex] = value;
328            } else {
329                float numSampleFrames = deltaTime * sampleRate;
330                // The value goes exponentially from value1 to value2 in a duration of deltaTime seconds (corresponding to numSampleFrames).
331                // Compute the per-sample multiplier.
332                float multiplier = powf(value2 / value1, 1 / numSampleFrames);
333
334                // Set the starting value of the exponential ramp. This is the same as multiplier ^
335                // AudioUtilities::timeToSampleFrame(currentTime - time1, sampleRate), but is more
336                // accurate, especially if multiplier is close to 1.
337                value = value1 * powf(value2 / value1,
338                                      AudioUtilities::timeToSampleFrame(currentTime - time1, sampleRate) / numSampleFrames);
339
340                for (; writeIndex < fillToFrame; ++writeIndex) {
341                    values[writeIndex] = value;
342                    value *= multiplier;
343                    currentTime += sampleFrameTimeIncr;
344                }
345            }
346        } else {
347            // Handle event types not requiring looking ahead to the next event.
348            switch (event.type()) {
349            case ParamEvent::SetValue:
350            case ParamEvent::LinearRampToValue:
351            case ParamEvent::ExponentialRampToValue:
352                {
353                    currentTime = fillToTime;
354
355                    // Simply stay at a constant value.
356                    value = event.value();
357                    for (; writeIndex < fillToFrame; ++writeIndex)
358                        values[writeIndex] = value;
359
360                    break;
361                }
362
363            case ParamEvent::SetTarget:
364                {
365                    currentTime = fillToTime;
366
367                    // Exponential approach to target value with given time constant.
368                    float target = event.value();
369                    float timeConstant = event.timeConstant();
370                    float discreteTimeConstant = static_cast<float>(AudioUtilities::discreteTimeConstantForSampleRate(timeConstant, controlRate));
371
372                    for (; writeIndex < fillToFrame; ++writeIndex) {
373                        values[writeIndex] = value;
374                        value += (target - value) * discreteTimeConstant;
375                    }
376
377                    break;
378                }
379
380            case ParamEvent::SetValueCurve:
381                {
382                    Float32Array* curve = event.curve();
383                    float* curveData = curve ? curve->data() : 0;
384                    unsigned numberOfCurvePoints = curve ? curve->length() : 0;
385
386                    // Curve events have duration, so don't just use next event time.
387                    float duration = event.duration();
388                    float durationFrames = duration * sampleRate;
389                    float curvePointsPerFrame = static_cast<float>(numberOfCurvePoints) / durationFrames;
390
391                    if (!curve || !curveData || !numberOfCurvePoints || duration <= 0 || sampleRate <= 0) {
392                        // Error condition - simply propagate previous value.
393                        currentTime = fillToTime;
394                        for (; writeIndex < fillToFrame; ++writeIndex)
395                            values[writeIndex] = value;
396                        break;
397                    }
398
399                    // Save old values and recalculate information based on the curve's duration
400                    // instead of the next event time.
401                    unsigned nextEventFillToFrame = fillToFrame;
402                    float nextEventFillToTime = fillToTime;
403                    fillToTime = std::min(endTime, time1 + duration);
404                    fillToFrame = AudioUtilities::timeToSampleFrame(fillToTime - startTime, sampleRate);
405                    fillToFrame = std::min(fillToFrame, numberOfValues);
406
407                    // Index into the curve data using a floating-point value.
408                    // We're scaling the number of curve points by the duration (see curvePointsPerFrame).
409                    float curveVirtualIndex = 0;
410                    if (time1 < currentTime) {
411                        // Index somewhere in the middle of the curve data.
412                        // Don't use timeToSampleFrame() since we want the exact floating-point frame.
413                        float frameOffset = (currentTime - time1) * sampleRate;
414                        curveVirtualIndex = curvePointsPerFrame * frameOffset;
415                    }
416
417                    // Render the stretched curve data using nearest neighbor sampling.
418                    // Oversampled curve data can be provided if smoothness is desired.
419                    for (; writeIndex < fillToFrame; ++writeIndex) {
420                        // Ideally we'd use round() from MathExtras, but we're in a tight loop here
421                        // and we're trading off precision for extra speed.
422                        unsigned curveIndex = static_cast<unsigned>(0.5 + curveVirtualIndex);
423
424                        curveVirtualIndex += curvePointsPerFrame;
425
426                        // Bounds check.
427                        if (curveIndex < numberOfCurvePoints)
428                            value = curveData[curveIndex];
429
430                        values[writeIndex] = value;
431                    }
432
433                    // If there's any time left after the duration of this event and the start
434                    // of the next, then just propagate the last value.
435                    for (; writeIndex < nextEventFillToFrame; ++writeIndex)
436                        values[writeIndex] = value;
437
438                    // Re-adjust current time
439                    currentTime = nextEventFillToTime;
440
441                    break;
442                }
443            }
444        }
445    }
446
447    // If there's any time left after processing the last event then just propagate the last value
448    // to the end of the values buffer.
449    for (; writeIndex < numberOfValues; ++writeIndex)
450        values[writeIndex] = value;
451
452    return value;
453}
454
455} // namespace blink
456
457#endif // ENABLE(WEB_AUDIO)
458