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 39 namespace blink { 40 41 static 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 52 static 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 63 static 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 74 static 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 85 static bool isValidAudioParamTime(double time, ExceptionState& exceptionState) 86 { 87 return isValidAudioParamTime(time, exceptionState, "Time"); 88 } 89 90 void 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 101 void 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 112 void 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 123 void 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 135 void 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 146 void 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 179 void 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 200 float 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 224 float 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 248 float 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