AudioParam must support fan-in (multiple audio connections)
authorcrogers@google.com <crogers@google.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 17 May 2012 01:14:23 +0000 (01:14 +0000)
committercrogers@google.com <crogers@google.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 17 May 2012 01:14:23 +0000 (01:14 +0000)
https://bugs.webkit.org/show_bug.cgi?id=83610

Reviewed by Kenneth Russell.

Source/WebCore:

Test: webaudio/audioparam-summingjunction.html

* Modules/webaudio/AudioParam.cpp:
* Modules/webaudio/AudioParam.h:
(WebCore::AudioParam::calculateSampleAccurateValues):
(WebCore::AudioParam::calculateAudioRateSignalValues):
Sums intrinsic parameter value with all audio-rate connections.

(WebCore::AudioParam::connect):
(WebCore::AudioParam::disconnect):
Support multiple connections.

(WebCore::AudioParam::hasSampleAccurateValues):
If we have one or more audio-rate connections.

(WebCore::AudioParam::AudioParam):
AudioParam now sub-classes AudioSummingJunction.

LayoutTests:

* webaudio/audioparam-connect-audioratesignal.html:
* webaudio/audioparam-summingjunction-expected.txt: Added.
* webaudio/audioparam-summingjunction.html: Added.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@117372 268f45cc-cd09-0410-ab3c-d52691b4dbfc

LayoutTests/ChangeLog
LayoutTests/webaudio/audioparam-connect-audioratesignal.html
LayoutTests/webaudio/audioparam-summingjunction-expected.txt [new file with mode: 0644]
LayoutTests/webaudio/audioparam-summingjunction.html [new file with mode: 0644]
Source/WebCore/ChangeLog
Source/WebCore/Modules/webaudio/AudioParam.cpp
Source/WebCore/Modules/webaudio/AudioParam.h

index a7750ac..9aeef98 100644 (file)
@@ -1,3 +1,14 @@
+2012-05-16  Chris Rogers  <crogers@google.com>
+
+        AudioParam must support fan-in (multiple audio connections)
+        https://bugs.webkit.org/show_bug.cgi?id=83610
+
+        Reviewed by Kenneth Russell.
+
+        * webaudio/audioparam-connect-audioratesignal.html:
+        * webaudio/audioparam-summingjunction-expected.txt: Added.
+        * webaudio/audioparam-summingjunction.html: Added.
+
 2012-05-16  Julien Chaffraix  <jchaffraix@webkit.org>
 
         Unreviewed gardening after r117339 (table baseline computation change).
index 546eb70..a05361a 100644 (file)
@@ -87,6 +87,10 @@ function runTest() {
 
     // Create a gain node controlling the gain of constantSource and make the connections.
     var gainNode = context.createGainNode();
+
+    // Intrinsic baseline gain of zero.
+    gainNode.gain.value = 0;
+
     constantSource.connect(gainNode);
     gainNode.connect(context.destination);
 
diff --git a/LayoutTests/webaudio/audioparam-summingjunction-expected.txt b/LayoutTests/webaudio/audioparam-summingjunction-expected.txt
new file mode 100644 (file)
index 0000000..e528a09
--- /dev/null
@@ -0,0 +1,6 @@
+PASS Rendered signal is of correct length.
+PASS Rendered signal matches sum of two audio-rate gain changing signals plus baseline gain.
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/webaudio/audioparam-summingjunction.html b/LayoutTests/webaudio/audioparam-summingjunction.html
new file mode 100644 (file)
index 0000000..ecfc7be
--- /dev/null
@@ -0,0 +1,132 @@
+<!DOCTYPE html>
+
+<!--
+Tests that multiple audio-rate signals (AudioNode outputs) can be connected to an AudioParam
+and that these signals are summed, along with the AudioParams intrinsic value.
+-->
+
+<html>
+<head>
+<link rel="stylesheet" href="../fast/js/resources/js-test-style.css"/>
+<script src="resources/audio-testing.js"></script>
+<script src="resources/mix-testing.js"></script>
+<script src="../fast/js/resources/js-test-pre.js"></script>
+
+</head>
+<body>
+
+<script>
+
+var sampleRate = 44100.0;
+var lengthInSeconds = 1;
+
+var context = 0;
+
+// Buffers used by the two gain controlling sources.
+var linearRampBuffer;
+var toneBuffer;
+var toneFrequency = 440;
+
+// Arbitrary non-zero value.
+var baselineGain = 5;
+
+// Allow for a small round-off error.
+var maxAllowedError = 1e-6;
+
+function checkResult(event) {
+    var renderedBuffer = event.renderedBuffer;
+    var renderedData = renderedBuffer.getChannelData(0);
+
+    // Get buffer data from the two sources used to control gain.
+    var linearRampData = linearRampBuffer.getChannelData(0);
+    var toneData = toneBuffer.getChannelData(0);
+
+    var n = renderedBuffer.length;
+
+    if (n == linearRampBuffer.length) {
+        testPassed("Rendered signal is of correct length.");
+    } else {
+        testFailed("Rendered signal is not of correct length.");
+    }
+
+    // Check that the rendered result exactly matches the sum of the intrinsic gain plus the two sources used to control gain.
+    // This is because we're changing the gain of a signal having constant value 1.
+    var success = true;
+    for (var i = 0; i < n; ++i) {
+        var expectedValue = baselineGain + linearRampData[i] + toneData[i];
+        var error = Math.abs(expectedValue - renderedData[i]);
+
+        if (error > maxAllowedError) {
+            success = false;
+            break;
+        }
+    }
+
+    if (success) {
+        testPassed("Rendered signal matches sum of two audio-rate gain changing signals plus baseline gain.");
+    } else {
+        testFailed("Rendered signal differs from the sum of two audio-rate gain changing signals plus baseline gain.");
+    }
+
+    finishJSTest();
+}
+
+function runTest() {
+    if (window.layoutTestController) {
+        layoutTestController.dumpAsText();
+        layoutTestController.waitUntilDone();
+    }
+
+    window.jsTestIsAsync = true;
+
+    var sampleFrameLength = sampleRate * lengthInSeconds;
+
+    // Create offline audio context.
+    context = new webkitAudioContext(1, sampleFrameLength, sampleRate);
+
+    // Create buffer used by the source which will have its gain controlled.
+    var constantOneBuffer = createConstantBuffer(context, sampleFrameLength, 1);
+    var constantSource = context.createBufferSource();
+    constantSource.buffer = constantOneBuffer;
+
+    // Create 1st buffer used to control gain (a linear ramp).
+    linearRampBuffer = createLinearRampBuffer(context, sampleFrameLength);
+    var gainSource1 = context.createBufferSource();
+    gainSource1.buffer = linearRampBuffer;
+
+    // Create 2nd buffer used to control gain (a simple sine wave tone).
+    toneBuffer = createToneBuffer(context, toneFrequency, lengthInSeconds, 1);
+    var gainSource2 = context.createBufferSource();
+    gainSource2.buffer = toneBuffer;
+
+    // Create a gain node controlling the gain of constantSource and make the connections.
+    var gainNode = context.createGainNode();
+
+    // Intrinsic baseline gain.
+    // This gain value should be summed with gainSource1 and gainSource2.
+    gainNode.gain.value = baselineGain;
+
+    constantSource.connect(gainNode);
+    gainNode.connect(context.destination);
+
+    // Connect two audio-rate signals to control the .gain AudioParam.
+    gainSource1.connect(gainNode.gain);
+    gainSource2.connect(gainNode.gain);
+
+    // Start all sources at time 0.
+    constantSource.noteOn(0);
+    gainSource1.noteOn(0);
+    gainSource2.noteOn(0);
+
+    context.oncomplete = checkResult;
+    context.startRendering();
+}
+
+runTest();
+successfullyParsed = true;
+
+</script>
+<script src="../fast/js/resources/js-test-post.js"></script>
+
+</body>
+</html>
index cfcef1d..ca56c47 100644 (file)
@@ -1,3 +1,28 @@
+2012-05-16  Chris Rogers  <crogers@google.com>
+
+        AudioParam must support fan-in (multiple audio connections)
+        https://bugs.webkit.org/show_bug.cgi?id=83610
+
+        Reviewed by Kenneth Russell.
+
+        Test: webaudio/audioparam-summingjunction.html
+
+        * Modules/webaudio/AudioParam.cpp:
+        * Modules/webaudio/AudioParam.h:
+        (WebCore::AudioParam::calculateSampleAccurateValues):
+        (WebCore::AudioParam::calculateAudioRateSignalValues):
+        Sums intrinsic parameter value with all audio-rate connections.
+
+        (WebCore::AudioParam::connect):
+        (WebCore::AudioParam::disconnect):
+        Support multiple connections.
+
+        (WebCore::AudioParam::hasSampleAccurateValues):
+        If we have one or more audio-rate connections.
+
+        (WebCore::AudioParam::AudioParam):
+        AudioParam now sub-classes AudioSummingJunction.
+
 2012-05-16  Kentaro Hara  <haraken@chromium.org>
 
         [V8] Pass Isolate to createV8HTMLWrapper() and createV8SVGWrapper()
index dec7087..15c6743 100644 (file)
@@ -101,7 +101,7 @@ void AudioParam::calculateSampleAccurateValues(float* values, unsigned numberOfV
     if (!isSafe)
         return;
 
-    if (m_audioRateSignal)
+    if (numberOfRenderingConnections())
         calculateAudioRateSignalValues(values, numberOfValues);
     else
         calculateTimelineValues(values, numberOfValues);
@@ -109,26 +109,40 @@ void AudioParam::calculateSampleAccurateValues(float* values, unsigned numberOfV
 
 void AudioParam::calculateAudioRateSignalValues(float* values, unsigned numberOfValues)
 {
-    // FIXME: support fan-in (multiple audio connections to this parameter with unity-gain summing).
-    // https://bugs.webkit.org/show_bug.cgi?id=83610
-    ASSERT(m_audioRateSignal);
-
-    AudioBus* bus = m_audioRateSignal->pull(0, numberOfValues);
-    bool isBusGood = bus && bus->numberOfChannels() && bus->length() >= numberOfValues;
-    ASSERT(isBusGood);
-    if (!isBusGood)
+    bool isGood = numberOfRenderingConnections() && numberOfValues;
+    ASSERT(isGood);
+    if (!isGood)
         return;
 
-    if (bus->numberOfChannels() == 1) {
-        // The normal case is to deal with a mono audio-rate signal.
-        memcpy(values, bus->channel(0)->data(), sizeof(float) * numberOfValues);
+    // The calculated result will be the "intrinsic" value summed with all audio-rate connections.
+
+    if (m_timeline.hasValues()) {
+        // Calculate regular timeline values, if we have any.
+        calculateTimelineValues(values, numberOfValues);
     } else {
-        // Do a standard mixdown to one channel if necessary.
-        AudioBus wrapperBus(1, numberOfValues, false);
-        wrapperBus.setChannelMemory(0, values, numberOfValues);
-        wrapperBus.copyFrom(*bus); // Mixdown.
+        // Otherwise set values array to our constant value.
+        float value = m_value; // Cache in local.
+
+        // FIXME: can be optimized if we create a new VectorMath function.
+        for (unsigned i = 0; i < numberOfValues; ++i)
+            values[i] = value;
+    }
+
+    // Now sum all of the audio-rate connections together (unity-gain summing junction).
+    // Note that connections would normally be mono, but we mix down to mono if necessary.
+    AudioBus summingBus(1, numberOfValues, false);
+    summingBus.setChannelMemory(0, values, numberOfValues);
+
+    for (unsigned i = 0; i < numberOfRenderingConnections(); ++i) {
+        AudioNodeOutput* output = renderingOutput(i);
+        ASSERT(output);
+
+        // Render audio from this output.
+        AudioBus* connectionBus = output->pull(0, numberOfValues);
+
+        // Sum, with unity-gain.
+        summingBus.sumFrom(*connectionBus);
     }
-    m_value = values[0]; // Update to first value.
 }
 
 void AudioParam::calculateTimelineValues(float* values, unsigned numberOfValues)
@@ -144,33 +158,35 @@ void AudioParam::calculateTimelineValues(float* values, unsigned numberOfValues)
     m_value = m_timeline.valuesForTimeRange(startTime, endTime, narrowPrecisionToFloat(m_value), values, numberOfValues, sampleRate, sampleRate);
 }
 
-void AudioParam::connect(AudioNodeOutput* audioRateSignal)
+void AudioParam::connect(AudioNodeOutput* output)
 {
     ASSERT(context()->isGraphOwner());
-    ASSERT(audioRateSignal);
-    if (!audioRateSignal)
+
+    ASSERT(output);
+    if (!output)
         return;
 
-    if (m_audioRateSignal && m_audioRateSignal != audioRateSignal) {
-        // Because we don't currently support fan-in we must explicitly disconnect from an old output.
-        m_audioRateSignal->removeParam(this);
-    }
+    if (m_outputs.contains(output))
+        return;
 
-    audioRateSignal->addParam(this);
-    m_audioRateSignal = audioRateSignal;
+    output->addParam(this);
+    m_outputs.add(output);
+    changedOutputs();
 }
 
-void AudioParam::disconnect(AudioNodeOutput* audioRateSignal)
+void AudioParam::disconnect(AudioNodeOutput* output)
 {
     ASSERT(context()->isGraphOwner());
-    ASSERT(audioRateSignal);
-    if (!audioRateSignal)
+
+    ASSERT(output);
+    if (!output)
         return;
 
-    // FIXME: support fan-in (multiple audio connections to this parameter with unity-gain summing).
-    // https://bugs.webkit.org/show_bug.cgi?id=83610
-    if (m_audioRateSignal == audioRateSignal)
-        m_audioRateSignal = 0;
+    if (m_outputs.contains(output)) {
+        m_outputs.remove(output);
+        changedOutputs();
+        output->removeParam(this);
+    }
 }
 
 } // namespace WebCore
index 3bd27ab..90fde3b 100644 (file)
@@ -31,6 +31,7 @@
 
 #include "AudioContext.h"
 #include "AudioParamTimeline.h"
+#include "AudioSummingJunction.h"
 #include "PlatformString.h"
 #include <sys/types.h>
 #include <wtf/Float32Array.h>
@@ -41,7 +42,7 @@ namespace WebCore {
 
 class AudioNodeOutput;
 
-class AudioParam : public RefCounted<AudioParam> {
+class AudioParam : public AudioSummingJunction, public RefCounted<AudioParam> {
 public:
     static const double DefaultSmoothingConstant;
     static const double SnapThreshold;
@@ -51,7 +52,9 @@ public:
         return adoptRef(new AudioParam(context, name, defaultValue, minValue, maxValue, units));
     }
 
-    AudioContext* context() { return m_context.get(); }
+    // AudioSummingJunction
+    virtual bool canUpdateState() OVERRIDE { return true; }
+    virtual void didUpdate() OVERRIDE { }
 
     float value();
     void setValue(float);
@@ -84,7 +87,7 @@ public:
     void setValueCurveAtTime(Float32Array* curve, float time, float duration) { m_timeline.setValueCurveAtTime(curve, time, duration); }
     void cancelScheduledValues(float startTime) { m_timeline.cancelScheduledValues(startTime); }
 
-    bool hasSampleAccurateValues() { return m_timeline.hasValues() || m_audioRateSignal; }
+    bool hasSampleAccurateValues() { return m_timeline.hasValues() || numberOfRenderingConnections(); }
     
     // Calculates numberOfValues parameter values starting at the context's current time.
     // Must be called in the context's render thread.
@@ -96,7 +99,7 @@ public:
 
 protected:
     AudioParam(AudioContext* context, const String& name, double defaultValue, double minValue, double maxValue, unsigned units = 0)
-        : m_context(context)
+        : AudioSummingJunction(context)
         , m_name(name)
         , m_value(defaultValue)
         , m_defaultValue(defaultValue)
@@ -105,7 +108,6 @@ protected:
         , m_units(units)
         , m_smoothedValue(defaultValue)
         , m_smoothingConstant(DefaultSmoothingConstant)
-        , m_audioRateSignal(0)
     {
     }
 
@@ -113,7 +115,6 @@ private:
     void calculateAudioRateSignalValues(float* values, unsigned numberOfValues);
     void calculateTimelineValues(float* values, unsigned numberOfValues);
 
-    RefPtr<AudioContext> m_context;
     String m_name;
     double m_value;
     double m_defaultValue;
@@ -126,11 +127,6 @@ private:
     double m_smoothingConstant;
     
     AudioParamTimeline m_timeline;
-
-    // An audio-rate signal directly providing parameter values.
-    // FIXME: support fan-in (multiple audio connections to this parameter with unity-gain summing).
-    // https://bugs.webkit.org/show_bug.cgi?id=83610
-    AudioNodeOutput* m_audioRateSignal;
 };
 
 } // namespace WebCore