Update Exception handling in WebAudio.
[WebKit-https.git] / Source / WebCore / Modules / webaudio / AudioScheduledSourceNode.cpp
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 "AudioScheduledSourceNode.h"
30
31 #include "AudioContext.h"
32 #include "AudioUtilities.h"
33 #include "Event.h"
34 #include "ScriptController.h"
35 #include <algorithm>
36 #include <wtf/MathExtras.h>
37
38 using namespace std;
39
40 namespace WebCore {
41
42 const double AudioScheduledSourceNode::UnknownTime = -1;
43
44 AudioScheduledSourceNode::AudioScheduledSourceNode(AudioContext* context, float sampleRate)
45     : AudioNode(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         context()->incrementActiveSourceCount();
93     }
94
95     quantumFrameOffset = startFrame > quantumStartFrame ? startFrame - quantumStartFrame : 0;
96     quantumFrameOffset = min(quantumFrameOffset, quantumFrameSize); // clamp to valid range
97     nonSilentFramesToProcess = quantumFrameSize - quantumFrameOffset;
98
99     if (!nonSilentFramesToProcess) {
100         // Output silence.
101         outputBus->zero();
102         return;
103     }
104
105     // Handle silence before we start playing.
106     // Zero any initial frames representing silence leading up to a rendering start time in the middle of the quantum.
107     if (quantumFrameOffset) {
108         for (unsigned i = 0; i < outputBus->numberOfChannels(); ++i)
109             memset(outputBus->channel(i)->mutableData(), 0, sizeof(float) * quantumFrameOffset);
110     }
111
112     // Handle silence after we're done playing.
113     // If the end time is somewhere in the middle of this time quantum, then zero out the
114     // frames from the end time to the very end of the quantum.
115     if (m_endTime != UnknownTime && endFrame >= quantumStartFrame && endFrame < quantumEndFrame) {
116         size_t zeroStartFrame = endFrame - quantumStartFrame;
117         size_t framesToZero = quantumFrameSize - zeroStartFrame;
118
119         bool isSafe = zeroStartFrame < quantumFrameSize && framesToZero <= quantumFrameSize && zeroStartFrame + framesToZero <= quantumFrameSize;
120         ASSERT(isSafe);
121
122         if (isSafe) {
123             if (framesToZero > nonSilentFramesToProcess)
124                 nonSilentFramesToProcess = 0;
125             else
126                 nonSilentFramesToProcess -= framesToZero;
127
128             for (unsigned i = 0; i < outputBus->numberOfChannels(); ++i)
129                 memset(outputBus->channel(i)->mutableData() + zeroStartFrame, 0, sizeof(float) * framesToZero);
130         }
131
132         finish();
133     }
134
135     return;
136 }
137
138 void AudioScheduledSourceNode::start(double when, ExceptionCode& ec)
139 {
140     ASSERT(isMainThread());
141
142     if (ScriptController::processingUserGesture())
143         context()->removeBehaviorRestriction(AudioContext::RequireUserGestureForAudioStartRestriction);
144
145     if (m_playbackState != UNSCHEDULED_STATE) {
146         ec = INVALID_STATE_ERR;
147         return;
148     }
149
150     m_startTime = when;
151     m_playbackState = SCHEDULED_STATE;
152 }
153
154 void AudioScheduledSourceNode::stop(double when, ExceptionCode& ec)
155 {
156     ASSERT(isMainThread());
157     if (!(m_playbackState == SCHEDULED_STATE || m_playbackState == PLAYING_STATE)) {
158         ec = INVALID_STATE_ERR;
159         return;
160     }
161     
162     when = max(0.0, when);
163     m_endTime = when;
164 }
165
166 #if ENABLE(LEGACY_WEB_AUDIO)
167 void AudioScheduledSourceNode::noteOn(double when, ExceptionCode& ec)
168 {
169     start(when, ec);
170 }
171
172 void AudioScheduledSourceNode::noteOff(double when, ExceptionCode& ec)
173 {
174     stop(when, ec);
175 }
176 #endif
177
178 void AudioScheduledSourceNode::setOnended(PassRefPtr<EventListener> listener)
179 {
180     m_hasEndedListener = listener;
181     setAttributeEventListener(eventNames().endedEvent, listener);
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         callOnMainThread(&AudioScheduledSourceNode::notifyEndedDispatch, this);
195 }
196
197 void AudioScheduledSourceNode::notifyEndedDispatch(void* userData)
198 {
199     static_cast<AudioScheduledSourceNode*>(userData)->notifyEnded();
200 }
201
202 void AudioScheduledSourceNode::notifyEnded()
203 {
204     EventListener* listener = onended();
205     if (!listener)
206         return;
207
208     RefPtr<Event> event = Event::create(eventNames().endedEvent, FALSE, FALSE);
209     event->setTarget(this);
210     listener->handleEvent(context()->scriptExecutionContext(), event.get());
211 }
212
213 } // namespace WebCore
214
215 #endif // ENABLE(WEB_AUDIO)