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 "core/dom/Event.h" 32 #include "core/platform/audio/AudioUtilities.h" 33 #include "modules/webaudio/AudioContext.h" 34 #include <algorithm> 35 #include "wtf/MathExtras.h" 36 37 using namespace std; 38 39 namespace WebCore { 40 41 const double AudioScheduledSourceNode::UnknownTime = -1; 42 43 AudioScheduledSourceNode::AudioScheduledSourceNode(AudioContext* context, float sampleRate) 44 : AudioSourceNode(context, sampleRate) 45 , m_playbackState(UNSCHEDULED_STATE) 46 , m_startTime(0) 47 , m_endTime(UnknownTime) 48 , m_hasEndedListener(false) 49 { 50 } 51 52 void AudioScheduledSourceNode::updateSchedulingInfo(size_t quantumFrameSize, 53 AudioBus* outputBus, 54 size_t& quantumFrameOffset, 55 size_t& nonSilentFramesToProcess) 56 { 57 ASSERT(outputBus); 58 if (!outputBus) 59 return; 60 61 ASSERT(quantumFrameSize == AudioNode::ProcessingSizeInFrames); 62 if (quantumFrameSize != AudioNode::ProcessingSizeInFrames) 63 return; 64 65 double sampleRate = this->sampleRate(); 66 67 // quantumStartFrame : Start frame of the current time quantum. 68 // quantumEndFrame : End frame of the current time quantum. 69 // startFrame : Start frame for this source. 70 // endFrame : End frame for this source. 71 size_t quantumStartFrame = context()->currentSampleFrame(); 72 size_t quantumEndFrame = quantumStartFrame + quantumFrameSize; 73 size_t startFrame = AudioUtilities::timeToSampleFrame(m_startTime, sampleRate); 74 size_t endFrame = m_endTime == UnknownTime ? 0 : AudioUtilities::timeToSampleFrame(m_endTime, sampleRate); 75 76 // If we know the end time and it's already passed, then don't bother doing any more rendering this cycle. 77 if (m_endTime != UnknownTime && endFrame <= quantumStartFrame) 78 finish(); 79 80 if (m_playbackState == UNSCHEDULED_STATE || m_playbackState == FINISHED_STATE || startFrame >= quantumEndFrame) { 81 // Output silence. 82 outputBus->zero(); 83 nonSilentFramesToProcess = 0; 84 return; 85 } 86 87 // Check if it's time to start playing. 88 if (m_playbackState == SCHEDULED_STATE) { 89 // Increment the active source count only if we're transitioning from SCHEDULED_STATE to PLAYING_STATE. 90 m_playbackState = PLAYING_STATE; 91 context()->incrementActiveSourceCount(); 92 } 93 94 quantumFrameOffset = startFrame > quantumStartFrame ? startFrame - quantumStartFrame : 0; 95 quantumFrameOffset = 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) 138 { 139 ASSERT(isMainThread()); 140 if (m_playbackState != UNSCHEDULED_STATE) 141 return; 142 143 m_startTime = when; 144 m_playbackState = SCHEDULED_STATE; 145 } 146 147 void AudioScheduledSourceNode::stop(double when) 148 { 149 ASSERT(isMainThread()); 150 if (!(m_playbackState == SCHEDULED_STATE || m_playbackState == PLAYING_STATE)) 151 return; 152 153 when = max(0.0, when); 154 m_endTime = when; 155 } 156 157 void AudioScheduledSourceNode::noteOn(double when) 158 { 159 start(when); 160 } 161 162 void AudioScheduledSourceNode::noteOff(double when) 163 { 164 stop(when); 165 } 166 167 void AudioScheduledSourceNode::setOnended(PassRefPtr<EventListener> listener, DOMWrapperWorld* isolatedWorld) 168 { 169 m_hasEndedListener = listener; 170 setAttributeEventListener(eventNames().endedEvent, listener, isolatedWorld); 171 } 172 173 void AudioScheduledSourceNode::finish() 174 { 175 if (m_playbackState != FINISHED_STATE) { 176 // Let the context dereference this AudioNode. 177 context()->notifyNodeFinishedProcessing(this); 178 m_playbackState = FINISHED_STATE; 179 context()->decrementActiveSourceCount(); 180 } 181 182 if (m_hasEndedListener) { 183 // |task| will keep the AudioScheduledSourceNode alive until the listener has been handled. 184 OwnPtr<NotifyEndedTask> task = adoptPtr(new NotifyEndedTask(this)); 185 callOnMainThread(&AudioScheduledSourceNode::notifyEndedDispatch, task.leakPtr()); 186 } 187 } 188 189 void AudioScheduledSourceNode::notifyEndedDispatch(void* userData) 190 { 191 OwnPtr<NotifyEndedTask> task = adoptPtr(static_cast<NotifyEndedTask*>(userData)); 192 193 task->notifyEnded(); 194 } 195 196 AudioScheduledSourceNode::NotifyEndedTask::NotifyEndedTask(PassRefPtr<AudioScheduledSourceNode> sourceNode) 197 : m_scheduledNode(sourceNode) 198 { 199 } 200 201 void AudioScheduledSourceNode::NotifyEndedTask::notifyEnded() 202 { 203 RefPtr<Event> event = Event::create(eventNames().endedEvent, FALSE, FALSE); 204 event->setTarget(m_scheduledNode); 205 m_scheduledNode->dispatchEvent(event.get()); 206 } 207 208 } // namespace WebCore 209 210 #endif // ENABLE(WEB_AUDIO) 211