2 * Copyright (C) 2011 Google Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
30 #include "modules/webaudio/AudioParamTimeline.h"
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"
41 static bool isValidAudioParamValue(float value, ExceptionState& exceptionState)
43 if (std::isfinite(value))
46 exceptionState.throwDOMException(
48 "Target value must be a finite number: " + String::number(value));
52 static bool isPositiveAudioParamValue(float value, ExceptionState& exceptionState)
54 if (std::isfinite(value) && (value > 0))
57 exceptionState.throwDOMException(
59 "Target value must be a finite positive number: " + String::number(value));
63 static bool isValidAudioParamTime(double time, ExceptionState& exceptionState, String message)
65 if (std::isfinite(time) && (time >= 0))
68 exceptionState.throwDOMException(
70 message + " must be a finite non-negative number: " + String::number(time));
74 static bool isPositiveAudioParamTime(double time, ExceptionState& exceptionState, String message)
76 if (std::isfinite(time) && (time > 0))
79 exceptionState.throwDOMException(
81 message + " must be a finite positive number: " + String::number(time));
85 static bool isValidAudioParamTime(double time, ExceptionState& exceptionState)
87 return isValidAudioParamTime(time, exceptionState, "Time");
90 void AudioParamTimeline::setValueAtTime(float value, double time, ExceptionState& exceptionState)
92 ASSERT(isMainThread());
94 if (!isValidAudioParamValue(value, exceptionState)
95 || !isValidAudioParamTime(time, exceptionState))
98 insertEvent(ParamEvent(ParamEvent::SetValue, value, time, 0, 0, nullptr));
101 void AudioParamTimeline::linearRampToValueAtTime(float value, double time, ExceptionState& exceptionState)
103 ASSERT(isMainThread());
105 if (!isValidAudioParamValue(value, exceptionState)
106 || !isValidAudioParamTime(time, exceptionState))
109 insertEvent(ParamEvent(ParamEvent::LinearRampToValue, value, time, 0, 0, nullptr));
112 void AudioParamTimeline::exponentialRampToValueAtTime(float value, double time, ExceptionState& exceptionState)
114 ASSERT(isMainThread());
116 if (!isPositiveAudioParamValue(value, exceptionState)
117 || !isValidAudioParamTime(time, exceptionState))
120 insertEvent(ParamEvent(ParamEvent::ExponentialRampToValue, value, time, 0, 0, nullptr));
123 void AudioParamTimeline::setTargetAtTime(float target, double time, double timeConstant, ExceptionState& exceptionState)
125 ASSERT(isMainThread());
127 if (!isValidAudioParamValue(target, exceptionState)
128 || !isValidAudioParamTime(time, exceptionState)
129 || !isValidAudioParamTime(timeConstant, exceptionState, "Time constant"))
132 insertEvent(ParamEvent(ParamEvent::SetTarget, target, time, timeConstant, 0, nullptr));
135 void AudioParamTimeline::setValueCurveAtTime(Float32Array* curve, double time, double duration, ExceptionState& exceptionState)
137 ASSERT(isMainThread());
139 if (!isValidAudioParamTime(time, exceptionState)
140 || !isPositiveAudioParamTime(duration, exceptionState, "Duration"))
143 insertEvent(ParamEvent(ParamEvent::SetValueCurve, 0, time, 0, duration, curve));
146 void AudioParamTimeline::insertEvent(const ParamEvent& event)
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;
161 MutexLocker locker(m_eventsLock);
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()) {
172 if (m_events[i].time() > insertTime)
176 m_events.insert(i, event);
179 void AudioParamTimeline::cancelScheduledValues(double startTime, ExceptionState& exceptionState)
181 ASSERT(isMainThread());
183 if (!std::isfinite(startTime)) {
184 exceptionState.throwDOMException(
186 "Time must be a finite number: " + String::number(startTime));
189 MutexLocker locker(m_eventsLock);
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);
200 float AudioParamTimeline::valueForContextTime(AudioContext* context, float defaultValue, bool& hasValue)
205 MutexTryLocker tryLocker(m_eventsLock);
206 if (!tryLocker.locked() || !context || !m_events.size() || context->currentTime() < m_events[0].time()) {
212 // Ask for just a single 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);
224 float AudioParamTimeline::valuesForTimeRange(
229 unsigned numberOfValues,
233 // We can't contend the lock in the realtime audio thread.
234 MutexTryLocker tryLocker(m_eventsLock);
235 if (!tryLocker.locked()) {
237 for (unsigned i = 0; i < numberOfValues; ++i)
238 values[i] = defaultValue;
243 float value = valuesForTimeRangeImpl(startTime, endTime, defaultValue, values, numberOfValues, sampleRate, controlRate);
248 float AudioParamTimeline::valuesForTimeRangeImpl(
253 unsigned numberOfValues,
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;
268 // Maintain a running time and index for writing the values buffer.
269 double currentTime = startTime;
270 unsigned writeIndex = 0;
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;
282 currentTime = fillToTime;
285 float value = defaultValue;
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;
296 // Wait until we get a more recent event.
297 if (nextEvent && nextEvent->time() < currentTime)
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;
305 double deltaTime = time2 - time1;
306 float k = deltaTime > 0 ? 1 / deltaTime : 0;
307 double sampleFrameTimeIncr = 1 / sampleRate;
309 double fillToTime = std::min(endTime, time2);
310 unsigned fillToFrame = AudioUtilities::timeToSampleFrame(fillToTime - startTime, sampleRate);
311 fillToFrame = std::min(fillToFrame, numberOfValues);
313 ParamEvent::Type nextEventType = nextEvent ? static_cast<ParamEvent::Type>(nextEvent->type()) : ParamEvent::LastType /* unknown */;
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;
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;
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);
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);
340 for (; writeIndex < fillToFrame; ++writeIndex) {
341 values[writeIndex] = value;
343 currentTime += sampleFrameTimeIncr;
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:
353 currentTime = fillToTime;
355 // Simply stay at a constant value.
356 value = event.value();
357 for (; writeIndex < fillToFrame; ++writeIndex)
358 values[writeIndex] = value;
363 case ParamEvent::SetTarget:
365 currentTime = fillToTime;
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));
372 for (; writeIndex < fillToFrame; ++writeIndex) {
373 values[writeIndex] = value;
374 value += (target - value) * discreteTimeConstant;
380 case ParamEvent::SetValueCurve:
382 Float32Array* curve = event.curve();
383 float* curveData = curve ? curve->data() : 0;
384 unsigned numberOfCurvePoints = curve ? curve->length() : 0;
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;
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;
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);
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;
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);
424 curveVirtualIndex += curvePointsPerFrame;
427 if (curveIndex < numberOfCurvePoints)
428 value = curveData[curveIndex];
430 values[writeIndex] = value;
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;
438 // Re-adjust current time
439 currentTime = nextEventFillToTime;
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;
457 #endif // ENABLE(WEB_AUDIO)