[web-audio-api] Constructibility & De-sugaring of static methods (#250)

The following issue was raised by the W3C TAG as part of their [review of the Web Audio API](https://github.com/w3ctag/spec-reviews/blob/master/2013/07/WebAudio.md)

### Constructibility & De-sugaring of static methods

The current API defines 26 interfaces:

```
$ cat webaudio.idl | grep -i interface | cut -d " " -f 2
AudioContext
OfflineAudioContext
OfflineAudioCompletionEvent
AudioNode
AudioDestinationNode
AudioParam
GainNode
DelayNode
AudioBuffer
AudioBufferSourceNode
MediaElementAudioSourceNode
ScriptProcessorNode
AudioProcessingEvent
PannerNode
AudioListener
ConvolverNode
AnalyserNode
ChannelSplitterNode
ChannelMergerNode
DynamicsCompressorNode
BiquadFilterNode
WaveShaperNode
OscillatorNode
PeriodicWave
MediaStreamAudioSourceNode
MediaStreamAudioDestinationNode
```

Of these, only two are marked constructible:

```
$ cat webaudio.idl | grep -A 1 -i constructor | grep interface | cut -d " " -f 2
AudioContext
OfflineAudioContext
```

Most of the types represented by the non-constructable interfaces _are_ visible in the API through normal use. For instance, to get a `PannerNode` instance a developer currently uses:

```
var panner = context.createPanner();
```

Where `context` is an instance of `AudioContext`  (or one of its subclasses). Prickly questions arise from this arrangement:

 1. Assuming that the static methods on the `context` are desirable shortcuts for wiring up the context of a `Node` instance to the `context` against which it runs, _how_ does that context get set in a way that would allow pure JS objects to describe it?
 2. By what privileged mechanism does the system create instances of these types if they do not have constructors?
 3. Are these types in any way subclassable? If not, why not?
 4. If the intent is to mirror other DOM APIs, it's curious to have `create*()` methods but no factory (e.g.: `createElement("tagname")`)

Adding constructors and context-setting methods (or constructor params) for most of the interfaces that lack them would answer #'s 1 and 2 and largely obviate 4. E.g.:

```js
// A possible de-sugaring for createPanner() when ctors are defined:
AudioContext.prototype.createPanner = function() {
 var p = new PannerNode();
 p.context = this;
 return p; 
};

// An alternative that provides the context via the PannerNode ctor:
AudioContext.prototype.createPanner = function() {
 return new PannerNode({ context: this });
};

// An alternative that uses a positional context param:
AudioContext.prototype.createPanner = function(attributes) {
  return new PannerNode(this, attributes);
};
```

Either constructor style allows these `AudioNode` types to conceptually be modeled more cleanly as JS objects which could self-host.

Of course, this requires answering the follow-on questions "what happens if the context is invalid, changes, or is never set?", but those are reasonable to ask and their answers don't need to be complicated (certainly not for v1).

An alternative design might locate the constructors on the context directly, but this seems to create as many problems as it solves.

Using the constructor style from the last variant, we can re-work one of the examples from Section 7:

```js
...
var context = new AudioContext();
...

function playSound() {
    var oneShotSound = context.createBufferSource();
    oneShotSound.buffer = dogBarkingBuffer;

    // Create a filter, panner, and gain node. 
    
    var lowpass = context.createBiquadFilter();

    var panner = context.createPanner();
    panner.panningModel = "equalpower";
    panner.distanceModel = "linear";

    var gainNode2 = context.createGain();
    

    // Make connections 
    oneShotSound.connect(lowpass);
    lowpass.connect(panner);
    panner.connect(gainNode2);
    gainNode2.connect(compressor);

    oneShotSound.start(context.currentTime + 0.75);
}
```

to:

```js
...
var context = new AudioContext();
...

function playSound() {
    var oneShotSound = new BufferSource(context, { buffer: dogBarkingBuffer });

    // Create a filter, panner, and gain node. 
    var lowpass = new BiquadFilterNode(context);
    var panner = new PannerNode(context, { 
      panningModel: "equalpower",
      distanceModel: "linear"
    });
    var gainNode2 = new GainNode(context);

    // Make connections 
    oneShotSound.connect(lowpass);
    lowpass.connect(panner);
    panner.connect(gainNode2);
    gainNode2.connect(compressor);

    oneShotSound.start(context.currentTime + 0.75);
}
```


---
Reply to this email directly or view it on GitHub:
https://github.com/WebAudio/web-audio-api/issues/250

Received on Thursday, 17 October 2013 12:22:10 UTC