1/*
2 * Copyright (C) 2010, 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 "AudioNodeInput.h"
30
31#include "AudioContext.h"
32#include "AudioNode.h"
33#include "AudioNodeOutput.h"
34#include <algorithm>
35
36using namespace std;
37
38namespace WebCore {
39
40AudioNodeInput::AudioNodeInput(AudioNode* node)
41    : AudioSummingJunction(node->context())
42    , m_node(node)
43{
44    // Set to mono by default.
45    m_internalSummingBus = AudioBus::create(1, AudioNode::ProcessingSizeInFrames);
46}
47
48void AudioNodeInput::connect(AudioNodeOutput* output)
49{
50    ASSERT(context()->isGraphOwner());
51
52    ASSERT(output && node());
53    if (!output || !node())
54        return;
55
56    // Check if we're already connected to this output.
57    if (m_outputs.contains(output))
58        return;
59
60    output->addInput(this);
61    m_outputs.add(output);
62    changedOutputs();
63
64    // Sombody has just connected to us, so count it as a reference.
65    node()->ref(AudioNode::RefTypeConnection);
66}
67
68void AudioNodeInput::disconnect(AudioNodeOutput* output)
69{
70    ASSERT(context()->isGraphOwner());
71
72    ASSERT(output && node());
73    if (!output || !node())
74        return;
75
76    // First try to disconnect from "active" connections.
77    if (m_outputs.contains(output)) {
78        m_outputs.remove(output);
79        changedOutputs();
80        output->removeInput(this);
81        node()->deref(AudioNode::RefTypeConnection); // Note: it's important to return immediately after all deref() calls since the node may be deleted.
82        return;
83    }
84
85    // Otherwise, try to disconnect from disabled connections.
86    if (m_disabledOutputs.contains(output)) {
87        m_disabledOutputs.remove(output);
88        output->removeInput(this);
89        node()->deref(AudioNode::RefTypeConnection); // Note: it's important to return immediately after all deref() calls since the node may be deleted.
90        return;
91    }
92
93    ASSERT_NOT_REACHED();
94}
95
96void AudioNodeInput::disable(AudioNodeOutput* output)
97{
98    ASSERT(context()->isGraphOwner());
99
100    ASSERT(output && node());
101    if (!output || !node())
102        return;
103
104    ASSERT(m_outputs.contains(output));
105
106    m_disabledOutputs.add(output);
107    m_outputs.remove(output);
108    changedOutputs();
109
110    // Propagate disabled state to outputs.
111    node()->disableOutputsIfNecessary();
112}
113
114void AudioNodeInput::enable(AudioNodeOutput* output)
115{
116    ASSERT(context()->isGraphOwner());
117
118    ASSERT(output && node());
119    if (!output || !node())
120        return;
121
122    ASSERT(m_disabledOutputs.contains(output));
123
124    // Move output from disabled list to active list.
125    m_outputs.add(output);
126    m_disabledOutputs.remove(output);
127    changedOutputs();
128
129    // Propagate enabled state to outputs.
130    node()->enableOutputsIfNecessary();
131}
132
133void AudioNodeInput::didUpdate()
134{
135    node()->checkNumberOfChannelsForInput(this);
136}
137
138void AudioNodeInput::updateInternalBus()
139{
140    ASSERT(context()->isAudioThread() && context()->isGraphOwner());
141
142    unsigned numberOfInputChannels = numberOfChannels();
143
144    if (numberOfInputChannels == m_internalSummingBus->numberOfChannels())
145        return;
146
147    m_internalSummingBus = AudioBus::create(numberOfInputChannels, AudioNode::ProcessingSizeInFrames);
148}
149
150unsigned AudioNodeInput::numberOfChannels() const
151{
152    AudioNode::ChannelCountMode mode = node()->internalChannelCountMode();
153    if (mode == AudioNode::Explicit)
154        return node()->channelCount();
155
156    // Find the number of channels of the connection with the largest number of channels.
157    unsigned maxChannels = 1; // one channel is the minimum allowed
158
159    for (HashSet<AudioNodeOutput*>::iterator i = m_outputs.begin(); i != m_outputs.end(); ++i) {
160        AudioNodeOutput* output = *i;
161        // Use output()->numberOfChannels() instead of output->bus()->numberOfChannels(),
162        // because the calling of AudioNodeOutput::bus() is not safe here.
163        maxChannels = max(maxChannels, output->numberOfChannels());
164    }
165
166    if (mode == AudioNode::ClampedMax)
167        maxChannels = min(maxChannels, static_cast<unsigned>(node()->channelCount()));
168
169    return maxChannels;
170}
171
172AudioBus* AudioNodeInput::bus()
173{
174    ASSERT(context()->isAudioThread());
175
176    // Handle single connection specially to allow for in-place processing.
177    if (numberOfRenderingConnections() == 1 && node()->internalChannelCountMode() == AudioNode::Max)
178        return renderingOutput(0)->bus();
179
180    // Multiple connections case or complex ChannelCountMode (or no connections).
181    return internalSummingBus();
182}
183
184AudioBus* AudioNodeInput::internalSummingBus()
185{
186    ASSERT(context()->isAudioThread());
187
188    return m_internalSummingBus.get();
189}
190
191void AudioNodeInput::sumAllConnections(AudioBus* summingBus, size_t framesToProcess)
192{
193    ASSERT(context()->isAudioThread());
194
195    // We shouldn't be calling this method if there's only one connection, since it's less efficient.
196    ASSERT(numberOfRenderingConnections() > 1 || node()->internalChannelCountMode() != AudioNode::Max);
197
198    ASSERT(summingBus);
199    if (!summingBus)
200        return;
201
202    summingBus->zero();
203
204    AudioBus::ChannelInterpretation interpretation = node()->internalChannelInterpretation();
205
206    for (unsigned i = 0; i < numberOfRenderingConnections(); ++i) {
207        AudioNodeOutput* output = renderingOutput(i);
208        ASSERT(output);
209
210        // Render audio from this output.
211        AudioBus* connectionBus = output->pull(0, framesToProcess);
212
213        // Sum, with unity-gain.
214        summingBus->sumFrom(*connectionBus, interpretation);
215    }
216}
217
218AudioBus* AudioNodeInput::pull(AudioBus* inPlaceBus, size_t framesToProcess)
219{
220    ASSERT(context()->isAudioThread());
221
222    // Handle single connection case.
223    if (numberOfRenderingConnections() == 1 && node()->internalChannelCountMode() == AudioNode::Max) {
224        // The output will optimize processing using inPlaceBus if it's able.
225        AudioNodeOutput* output = this->renderingOutput(0);
226        return output->pull(inPlaceBus, framesToProcess);
227    }
228
229    AudioBus* internalSummingBus = this->internalSummingBus();
230
231    if (!numberOfRenderingConnections()) {
232        // At least, generate silence if we're not connected to anything.
233        // FIXME: if we wanted to get fancy, we could propagate a 'silent hint' here to optimize the downstream graph processing.
234        internalSummingBus->zero();
235        return internalSummingBus;
236    }
237
238    // Handle multiple connections case.
239    sumAllConnections(internalSummingBus, framesToProcess);
240
241    return internalSummingBus;
242}
243
244} // namespace WebCore
245
246#endif // ENABLE(WEB_AUDIO)
247