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