Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implementing a JavaScript AudioNode

Is it possible to implement a custom AudioNode with the Web Audio API?

I would like to build a node that will contain several other nodes (ChannelSplitters and AnalyserNodes). Ideally, I will be able to connect to this custom node like any other AudioNode. For example,

var customNode = new CustomNode();
mediaStreamSource = context.createMediaStreamSource(userMedia);

// This will not work, as I need to know what to implement in CustomNode
mediaStreamSource.connect(customNode);
customNode.connect(context.destination);

According to the MDN documentation, an AudioNode implements an EventTarget interface. Is that all that is used for shuffling audio around? And if so, how can implement this interface in a way to handle audio?

like image 429
Brad Avatar asked Nov 10 '13 21:11

Brad


People also ask

What is AudioNode?

The AudioNode interface is a generic interface for representing an audio processing module. Examples include: an audio source (e.g. an HTML <audio> or <video> element, an OscillatorNode , etc.), the audio destination, intermediate processing module (e.g. a filter like BiquadFilterNode or ConvolverNode ), or.

What is window AudioContext?

The AudioContext interface represents an audio-processing graph built from audio modules linked together, each represented by an AudioNode . An audio context controls both the creation of the nodes it contains and the execution of the audio processing, or decoding.

What is modern Webaudio API?

The Web Audio API provides a powerful and versatile system for controlling audio on the Web, allowing developers to choose audio sources, add effects to audio, create audio visualizations, apply spatial effects (such as panning) and much more.


2 Answers

AudioNode class file

"use strict";

var AudioNode = global.AudioNode;
var AudioNode$connect;
var AudioNode$disconnect;

function connect() {
  var args = [].slice.call(arguments);

  if (args.length && typeof args[0].__connectFrom === "function") {
    args[0].__connectFrom.apply(args[0], [ this ].concat(args.slice(1)));
  } else {
    AudioNode$connect.apply(this, args);
  }
}

function disconnect() {
  var args = [].slice.call(arguments);

  if (args.length && typeof args[0].__disconnectFrom === "function") {
    args[0].__disconnectFrom.apply(args[0], [ this ].concat(args.slice(1)));
  } else {
    AudioNode$disconnect.apply(this, args);
  }
}

function use() {
  if (typeof AudioNode !== "undefined" && AudioNode.prototype.connect !== connect) {
    AudioNode$connect = AudioNode.prototype.connect;
    AudioNode$disconnect = AudioNode.prototype.disconnect;

    AudioNode.prototype.connect = connect;
    AudioNode.prototype.disconnect = disconnect;
  }
}

function unuse() {
  if (typeof AudioNode !== "undefined" && AudioNode.prototype.connect === connect) {
    AudioNode.prototype.connect = AudioNode$connect;
    AudioNode.prototype.disconnect = AudioNode$disconnect;
  }
}

module.exports = {
  use: use,
  unuse: unuse,
};

AudioNode test file

"use strict";

var assert = require("power-assert");
var PowerAudioNode = require("../");

function CustomAudioNode(audioContext) {
  this.audioContext = audioContext;
  this.gain1 = audioContext.createGain();
  this.gain2 = audioContext.createGain();
  this.inlet = this.gain1;
  this.outlet = this.gain2;
}

CustomAudioNode.prototype.connect = function() {
  this.gain1.connect(this.gain2);
  this.gain2.connect.apply(this.gain2, arguments);
};

CustomAudioNode.prototype.disconnect = function() {
  this.gain1.disconnect();
  this.gain2.disconnect.apply(this.gain2, arguments);
};

CustomAudioNode.prototype.__connectFrom = function(source) {
  source.connect(this.gain1);
};

CustomAudioNode.prototype.__disconnectFrom = function(source) {
  source.disconnect();
};

describe("PowerAudioNode", function() {
  describe("use(): void", function() {
    before(PowerAudioNode.use);
    before(PowerAudioNode.use);
    it("works", function() {
      var audioContext = new global.AudioContext();
      var oscillator = audioContext.createOscillator();
      var customAudioNode = new CustomAudioNode(audioContext);
      var compressor = audioContext.createDynamicsCompressor();

      oscillator.connect(customAudioNode);
      customAudioNode.connect(compressor);
      compressor.connect(audioContext.destination);

      assert(audioContext.destination.$isConnectedFrom(compressor));
      assert(compressor.$isConnectedFrom(customAudioNode.outlet));
      assert(customAudioNode.inlet.$isConnectedFrom(oscillator));

      oscillator.disconnect(customAudioNode);
      customAudioNode.disconnect();
      compressor.disconnect();

      assert(!audioContext.destination.$isConnectedFrom(compressor));
      assert(!compressor.$isConnectedFrom(customAudioNode.outlet));
      assert(!customAudioNode.inlet.$isConnectedFrom(oscillator));
    });
  });
  describe("unuse(): void", function() {
    before(PowerAudioNode.unuse);
    it("works", function() {
      var audioContext = new global.AudioContext();
      var oscillator = audioContext.createOscillator();
      var customAudioNode = new CustomAudioNode(audioContext);
      var compressor = audioContext.createDynamicsCompressor();

      assert.throws(function() {
        oscillator.connect(customAudioNode);
      });
      customAudioNode.connect(compressor);
      compressor.connect(audioContext.destination);

      assert(audioContext.destination.$isConnectedFrom(compressor));
      assert(compressor.$isConnectedFrom(customAudioNode.outlet));
      assert(!customAudioNode.inlet.$isConnectedFrom(oscillator));

      oscillator.disconnect();
      customAudioNode.disconnect();
      compressor.disconnect();

      assert(!audioContext.destination.$isConnectedFrom(compressor));
      assert(!compressor.$isConnectedFrom(customAudioNode.outlet));
      assert(!customAudioNode.inlet.$isConnectedFrom(oscillator));
    });
  });
});
like image 117
BendYourTaxes Avatar answered Oct 17 '22 18:10

BendYourTaxes


This article seems to have a method for doing just what you're looking for.

Basic premise:

function MyCustomNode(){
  this.input = audioContext.createGainNode();
  var output = audioContext.createGainNode();
  this.connect = function(target){
     output.connect(target);
  };
}

Example:

function AudioBus(){
  this.input = audioContext.createGainNode();
  var output = audioContext.createGainNode();
  var custom = new MyCustomNode();

  this.input.connect(custom);
  custom.connect(output);

  this.connect = function(target){
     output.connect(target);
  };
}

//create some native oscillators and our custom audio bus
var bus = new AudioBus(),
    instrument1 = audioContext.createOscillator(),
    instrument2 = audioContext.createOscillator(),
    instrument3 = audioContext.createOscillator();

//connect our instruments to the same bus
instrument1.connect(bus.input);
instrument2.connect(bus.input);
instrument3.connect(bus.input);
bus.connect(audioContext.destination);

Edit: The question could be a possible duplicate of Creating a custom echo node with web-audio, but I believe the answer you're looking for is the one from @MattDiamond. It's not exactly a pretty solution, but it seems to get the job done:

function FeedbackDelayNode(context, delay, feedback){
  this.delayTime.value = delay;
  this.gainNode = context.createGainNode();
  this.gainNode.gain.value = feedback;
  this.connect(this.gainNode);
  this.gainNode.connect(this);
}

function FeedbackDelayFactory(context, delayTime, feedback){
  var delay = context.createDelayNode(delayTime + 1);
  FeedbackDelayNode.call(delay, context, delayTime, feedback);
  return delay;
}

AudioContext.prototype.createFeedbackDelay = function(delay, feedback){
  return FeedbackDelayFactory(this, delay, feedback);
};
like image 5
idbehold Avatar answered Oct 17 '22 17:10

idbehold