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/v8/ExceptionState.h"
     32 #include "core/dom/ExceptionCode.h"
     33 #include "core/events/Event.h"
     34 #include "platform/audio/AudioUtilities.h"
     35 #include "modules/webaudio/AudioContext.h"
     36 #include <algorithm>
     37 #include "wtf/MathExtras.h"
     38 
     39 using namespace std;
     40 
     41 namespace WebCore {
     42 
     43 const double AudioScheduledSourceNode::UnknownTime = -1;
     44 
     45 AudioScheduledSourceNode::AudioScheduledSourceNode(AudioContext* context, float sampleRate)
     46     : AudioSourceNode(context, sampleRate)
     47     , m_playbackState(UNSCHEDULED_STATE)
     48     , m_startTime(0)
     49     , m_endTime(UnknownTime)
     50     , m_hasEndedListener(false)
     51     , m_stopCalled(false)
     52 {
     53 }
     54 
     55 void AudioScheduledSourceNode::updateSchedulingInfo(size_t quantumFrameSize,
     56                                                     AudioBus* outputBus,
     57                                                     size_t& quantumFrameOffset,
     58                                                     size_t& nonSilentFramesToProcess)
     59 {
     60     ASSERT(outputBus);
     61     if (!outputBus)
     62         return;
     63 
     64     ASSERT(quantumFrameSize == AudioNode::ProcessingSizeInFrames);
     65     if (quantumFrameSize != AudioNode::ProcessingSizeInFrames)
     66         return;
     67 
     68     double sampleRate = this->sampleRate();
     69 
     70     // quantumStartFrame     : Start frame of the current time quantum.
     71     // quantumEndFrame       : End frame of the current time quantum.
     72     // startFrame            : Start frame for this source.
     73     // endFrame              : End frame for this source.
     74     size_t quantumStartFrame = context()->currentSampleFrame();
     75     size_t quantumEndFrame = quantumStartFrame + quantumFrameSize;
     76     size_t startFrame = AudioUtilities::timeToSampleFrame(m_startTime, sampleRate);
     77     size_t endFrame = m_endTime == UnknownTime ? 0 : AudioUtilities::timeToSampleFrame(m_endTime, sampleRate);
     78 
     79     // If we know the end time and it's already passed, then don't bother doing any more rendering this cycle.
     80     if (m_endTime != UnknownTime && endFrame <= quantumStartFrame)
     81         finish();
     82 
     83     if (m_playbackState == UNSCHEDULED_STATE || m_playbackState == FINISHED_STATE || startFrame >= quantumEndFrame) {
     84         // Output silence.
     85         outputBus->zero();
     86         nonSilentFramesToProcess = 0;
     87         return;
     88     }
     89 
     90     // Check if it's time to start playing.
     91     if (m_playbackState == SCHEDULED_STATE) {
     92         // Increment the active source count only if we're transitioning from SCHEDULED_STATE to PLAYING_STATE.
     93         m_playbackState = PLAYING_STATE;
     94         context()->incrementActiveSourceCount();
     95     }
     96 
     97     quantumFrameOffset = startFrame > quantumStartFrame ? startFrame - quantumStartFrame : 0;
     98     quantumFrameOffset = min(quantumFrameOffset, quantumFrameSize); // clamp to valid range
     99     nonSilentFramesToProcess = quantumFrameSize - quantumFrameOffset;
    100 
    101     if (!nonSilentFramesToProcess) {
    102         // Output silence.
    103         outputBus->zero();
    104         return;
    105     }
    106 
    107     // Handle silence before we start playing.
    108     // Zero any initial frames representing silence leading up to a rendering start time in the middle of the quantum.
    109     if (quantumFrameOffset) {
    110         for (unsigned i = 0; i < outputBus->numberOfChannels(); ++i)
    111             memset(outputBus->channel(i)->mutableData(), 0, sizeof(float) * quantumFrameOffset);
    112     }
    113 
    114     // Handle silence after we're done playing.
    115     // If the end time is somewhere in the middle of this time quantum, then zero out the
    116     // frames from the end time to the very end of the quantum.
    117     if (m_endTime != UnknownTime && endFrame >= quantumStartFrame && endFrame < quantumEndFrame) {
    118         size_t zeroStartFrame = endFrame - quantumStartFrame;
    119         size_t framesToZero = quantumFrameSize - zeroStartFrame;
    120 
    121         bool isSafe = zeroStartFrame < quantumFrameSize && framesToZero <= quantumFrameSize && zeroStartFrame + framesToZero <= quantumFrameSize;
    122         ASSERT(isSafe);
    123 
    124         if (isSafe) {
    125             if (framesToZero > nonSilentFramesToProcess)
    126                 nonSilentFramesToProcess = 0;
    127             else
    128                 nonSilentFramesToProcess -= framesToZero;
    129 
    130             for (unsigned i = 0; i < outputBus->numberOfChannels(); ++i)
    131                 memset(outputBus->channel(i)->mutableData() + zeroStartFrame, 0, sizeof(float) * framesToZero);
    132         }
    133 
    134         finish();
    135     }
    136 
    137     return;
    138 }
    139 
    140 
    141 void AudioScheduledSourceNode::start(double when, ExceptionState& exceptionState)
    142 {
    143     ASSERT(isMainThread());
    144 
    145     if (m_playbackState != UNSCHEDULED_STATE) {
    146         exceptionState.throwDOMException(
    147             InvalidStateError,
    148             "cannot call start more than once.");
    149         return;
    150     }
    151 
    152     m_startTime = when;
    153     m_playbackState = SCHEDULED_STATE;
    154 }
    155 
    156 void AudioScheduledSourceNode::stop(double when, ExceptionState& exceptionState)
    157 {
    158     ASSERT(isMainThread());
    159 
    160     if (m_stopCalled) {
    161         exceptionState.throwDOMException(
    162             InvalidStateError,
    163             "cannot call stop more than once.");
    164     } else if (m_playbackState == UNSCHEDULED_STATE) {
    165         exceptionState.throwDOMException(
    166             InvalidStateError,
    167             "cannot call stop without calling start first.");
    168     } else {
    169         // This can only happen from the SCHEDULED_STATE or PLAYING_STATE. The UNSCHEDULED_STATE is
    170         // handled above, and the FINISHED_STATE is only reachable after stop() has been called, and
    171         // hence m_stopCalled is true. But that case is handled above.
    172         when = max(0.0, when);
    173         m_endTime = when;
    174         m_stopCalled = true;
    175     }
    176 }
    177 
    178 void AudioScheduledSourceNode::setOnended(PassRefPtr<EventListener> listener, DOMWrapperWorld* isolatedWorld)
    179 {
    180     m_hasEndedListener = listener;
    181     setAttributeEventListener(EventTypeNames::ended, listener, isolatedWorld);
    182 }
    183 
    184 void AudioScheduledSourceNode::finish()
    185 {
    186     if (m_playbackState != FINISHED_STATE) {
    187         // Let the context dereference this AudioNode.
    188         context()->notifyNodeFinishedProcessing(this);
    189         m_playbackState = FINISHED_STATE;
    190         context()->decrementActiveSourceCount();
    191     }
    192 
    193     if (m_hasEndedListener) {
    194         // |task| will keep the AudioScheduledSourceNode alive until the listener has been handled.
    195         OwnPtr<NotifyEndedTask> task = adoptPtr(new NotifyEndedTask(this));
    196         callOnMainThread(&AudioScheduledSourceNode::notifyEndedDispatch, task.leakPtr());
    197     }
    198 }
    199 
    200 void AudioScheduledSourceNode::notifyEndedDispatch(void* userData)
    201 {
    202     OwnPtr<NotifyEndedTask> task = adoptPtr(static_cast<NotifyEndedTask*>(userData));
    203 
    204     task->notifyEnded();
    205 }
    206 
    207 AudioScheduledSourceNode::NotifyEndedTask::NotifyEndedTask(PassRefPtr<AudioScheduledSourceNode> sourceNode)
    208     : m_scheduledNode(sourceNode)
    209 {
    210 }
    211 
    212 void AudioScheduledSourceNode::NotifyEndedTask::notifyEnded()
    213 {
    214     RefPtr<Event> event = Event::create(EventTypeNames::ended);
    215     event->setTarget(m_scheduledNode);
    216     m_scheduledNode->dispatchEvent(event.get());
    217 }
    218 
    219 } // namespace WebCore
    220 
    221 #endif // ENABLE(WEB_AUDIO)
    222