Home | History | Annotate | Download | only in webaudio
      1 /*
      2  * Copyright (C) 2012, 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  * 1.  Redistributions of source code must retain the above copyright
      8  *    notice, this list of conditions and the following disclaimer.
      9  * 2.  Redistributions in binary form must reproduce the above copyright
     10  *    notice, this list of conditions and the following disclaimer in the
     11  *    documentation and/or other materials provided with the distribution.
     12  *
     13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY
     14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
     15  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
     16  * DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY
     17  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
     18  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
     19  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
     20  * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     21  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
     22  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     23  */
     24 
     25 #include "config.h"
     26 
     27 #if ENABLE(WEB_AUDIO)
     28 
     29 #include "modules/webaudio/AudioScheduledSourceNode.h"
     30 
     31 #include "bindings/core/v8/ExceptionState.h"
     32 #include "core/dom/CrossThreadTask.h"
     33 #include "core/dom/ExceptionCode.h"
     34 #include "modules/EventModules.h"
     35 #include "modules/webaudio/AudioContext.h"
     36 #include "platform/audio/AudioUtilities.h"
     37 #include "wtf/MathExtras.h"
     38 #include <algorithm>
     39 
     40 namespace blink {
     41 
     42 const double AudioScheduledSourceNode::UnknownTime = -1;
     43 
     44 AudioScheduledSourceNode::AudioScheduledSourceNode(AudioContext* context, float sampleRate)
     45     : AudioSourceNode(context, sampleRate)
     46     , m_playbackState(UNSCHEDULED_STATE)
     47     , m_startTime(0)
     48     , m_endTime(UnknownTime)
     49     , m_hasEndedListener(false)
     50 {
     51 }
     52 
     53 void AudioScheduledSourceNode::updateSchedulingInfo(size_t quantumFrameSize,
     54                                                     AudioBus* outputBus,
     55                                                     size_t& quantumFrameOffset,
     56                                                     size_t& nonSilentFramesToProcess)
     57 {
     58     ASSERT(outputBus);
     59     if (!outputBus)
     60         return;
     61 
     62     ASSERT(quantumFrameSize == AudioNode::ProcessingSizeInFrames);
     63     if (quantumFrameSize != AudioNode::ProcessingSizeInFrames)
     64         return;
     65 
     66     double sampleRate = this->sampleRate();
     67 
     68     // quantumStartFrame     : Start frame of the current time quantum.
     69     // quantumEndFrame       : End frame of the current time quantum.
     70     // startFrame            : Start frame for this source.
     71     // endFrame              : End frame for this source.
     72     size_t quantumStartFrame = context()->currentSampleFrame();
     73     size_t quantumEndFrame = quantumStartFrame + quantumFrameSize;
     74     size_t startFrame = AudioUtilities::timeToSampleFrame(m_startTime, sampleRate);
     75     size_t endFrame = m_endTime == UnknownTime ? 0 : AudioUtilities::timeToSampleFrame(m_endTime, sampleRate);
     76 
     77     // If we know the end time and it's already passed, then don't bother doing any more rendering this cycle.
     78     if (m_endTime != UnknownTime && endFrame <= quantumStartFrame)
     79         finish();
     80 
     81     if (m_playbackState == UNSCHEDULED_STATE || m_playbackState == FINISHED_STATE || startFrame >= quantumEndFrame) {
     82         // Output silence.
     83         outputBus->zero();
     84         nonSilentFramesToProcess = 0;
     85         return;
     86     }
     87 
     88     // Check if it's time to start playing.
     89     if (m_playbackState == SCHEDULED_STATE) {
     90         // Increment the active source count only if we're transitioning from SCHEDULED_STATE to PLAYING_STATE.
     91         m_playbackState = PLAYING_STATE;
     92     }
     93 
     94     quantumFrameOffset = startFrame > quantumStartFrame ? startFrame - quantumStartFrame : 0;
     95     quantumFrameOffset = std::min(quantumFrameOffset, quantumFrameSize); // clamp to valid range
     96     nonSilentFramesToProcess = quantumFrameSize - quantumFrameOffset;
     97 
     98     if (!nonSilentFramesToProcess) {
     99         // Output silence.
    100         outputBus->zero();
    101         return;
    102     }
    103 
    104     // Handle silence before we start playing.
    105     // Zero any initial frames representing silence leading up to a rendering start time in the middle of the quantum.
    106     if (quantumFrameOffset) {
    107         for (unsigned i = 0; i < outputBus->numberOfChannels(); ++i)
    108             memset(outputBus->channel(i)->mutableData(), 0, sizeof(float) * quantumFrameOffset);
    109     }
    110 
    111     // Handle silence after we're done playing.
    112     // If the end time is somewhere in the middle of this time quantum, then zero out the
    113     // frames from the end time to the very end of the quantum.
    114     if (m_endTime != UnknownTime && endFrame >= quantumStartFrame && endFrame < quantumEndFrame) {
    115         size_t zeroStartFrame = endFrame - quantumStartFrame;
    116         size_t framesToZero = quantumFrameSize - zeroStartFrame;
    117 
    118         bool isSafe = zeroStartFrame < quantumFrameSize && framesToZero <= quantumFrameSize && zeroStartFrame + framesToZero <= quantumFrameSize;
    119         ASSERT(isSafe);
    120 
    121         if (isSafe) {
    122             if (framesToZero > nonSilentFramesToProcess)
    123                 nonSilentFramesToProcess = 0;
    124             else
    125                 nonSilentFramesToProcess -= framesToZero;
    126 
    127             for (unsigned i = 0; i < outputBus->numberOfChannels(); ++i)
    128                 memset(outputBus->channel(i)->mutableData() + zeroStartFrame, 0, sizeof(float) * framesToZero);
    129         }
    130 
    131         finish();
    132     }
    133 
    134     return;
    135 }
    136 
    137 void AudioScheduledSourceNode::start(double when, ExceptionState& exceptionState)
    138 {
    139     ASSERT(isMainThread());
    140 
    141     if (m_playbackState != UNSCHEDULED_STATE) {
    142         exceptionState.throwDOMException(
    143             InvalidStateError,
    144             "cannot call start more than once.");
    145         return;
    146     }
    147 
    148     if (!std::isfinite(when) || (when < 0)) {
    149         exceptionState.throwDOMException(
    150             InvalidStateError,
    151             "Start time must be a finite non-negative number: " + String::number(when));
    152         return;
    153     }
    154 
    155     m_startTime = when;
    156     m_playbackState = SCHEDULED_STATE;
    157 }
    158 
    159 void AudioScheduledSourceNode::stop(double when, ExceptionState& exceptionState)
    160 {
    161     ASSERT(isMainThread());
    162 
    163     if (m_playbackState == UNSCHEDULED_STATE) {
    164         exceptionState.throwDOMException(
    165             InvalidStateError,
    166             "cannot call stop without calling start first.");
    167         return;
    168     }
    169 
    170     if (!std::isfinite(when) || (when < 0)) {
    171         exceptionState.throwDOMException(
    172             InvalidStateError,
    173             "Stop time must be a finite non-negative number: " + String::number(when));
    174         return;
    175     }
    176 
    177     // stop() can be called more than once, with the last call to stop taking effect, unless the
    178     // source has already stopped due to earlier calls to stop. No exceptions are thrown in any
    179     // case.
    180     when = std::max(0.0, when);
    181     m_endTime = when;
    182 }
    183 
    184 void AudioScheduledSourceNode::setOnended(PassRefPtr<EventListener> listener)
    185 {
    186     m_hasEndedListener = listener;
    187     setAttributeEventListener(EventTypeNames::ended, listener);
    188 }
    189 
    190 void AudioScheduledSourceNode::finish()
    191 {
    192     if (m_playbackState != FINISHED_STATE) {
    193         // Let the context dereference this AudioNode.
    194         context()->notifyNodeFinishedProcessing(this);
    195         m_playbackState = FINISHED_STATE;
    196     }
    197 
    198     if (m_hasEndedListener && context()->executionContext()) {
    199         context()->executionContext()->postTask(createCrossThreadTask(&AudioScheduledSourceNode::notifyEnded, this));
    200     }
    201 }
    202 
    203 void AudioScheduledSourceNode::notifyEnded()
    204 {
    205     dispatchEvent(Event::create(EventTypeNames::ended));
    206 }
    207 
    208 } // namespace blink
    209 
    210 #endif // ENABLE(WEB_AUDIO)
    211