Version 0.0.8
Important (Jan. 2016)
Several of the problems this library was created to solve are addressed in the HTML5 Audio API dev roadmap, most notably connect() returning the AudioNode instance and clarifications/behavior specs for the various setValue methods of the AudioParam. To stay up-to-date on this stuff, you can follow the spec at https://github.com/WebAudio/web-audio-api. Hopefully Mooog's behavior will not conflict with the future standard, but if it does, I'll be updating Mooog to stay with the spec. The tldr; is don't use this in production (not like you would, though, amiright?).
Mooog is inspired by audio mixing boards on the one hand and jQuery chainable syntax on the other. It automatically does a lot of stuff so you don't have to. Mooog's goal is to take some of the tedium out of working with AudioNodes, as well as patching some odd behaviors. With Mooog, instead of writing this:
AudioContext = AudioContext || webkitAudioContext;
var ctxt = new AudioContext();
var osc = ctxt.createOscillator();
var lfo = ctxt.createOscillator();
var gain = ctxt.createGain();
osc.frequency.value = 300;
lfo.type = 'sawtooth';
lfo.frequency.value = 3;
lfo.connect(gain);
gain.gain.value = 40;
gain.connect(osc.frequency);
osc.connect(ctxt.destination);
lfo.start();
osc.start();
...you can write this:
M = new Mooog();
M.node(
{ id:'lfo', node_type:'Oscillator', type:'sawtooth', frequency:3 }
)
.start()
.chain(
M.node( {id:'gain', node_type:'Gain', gain:40} )
)
.chain(
M.node({id:'osc', node_type:'Oscillator', frequency:300}), 'frequency'
)
.start()
Mooog provides a MooogAudioNode
object that can wrap one or more AudioNodes.
At a minimum, it exposes the methods of the wrapped Node (or the first in its internal chain) so you can talk to them just like the underlying AudioNode.
Many of them offer additional functionality. There are also utilities like
an ADSR generator as well as functions to generate common waveshaping curves like Chebyshevs and tanh
.
All nodes with a buffer
property fire a mooog.audioBufferLoaded
event when the requested audio asset has finished loading. Using the same file for multiple buffers can cause duplicate requests if the first request hasn't finished loading and the browser hasn't cached it yet. To prevent this, Mooog caches these buffers internally and automatically subscribes the requesting node to the load event for that file path, setting the buffer when the file is ready, so that each audio asset is only requested once.
mooog.audioBufferLoaded
is a synthetic event fired on the document
element with the file path and loaded AudioBuffer
in the detail
property, so you can listen for it to make sure your audio files are loaded before you do any playback.
There is also a specialized MooogAudioNode object called Track
, which will automatically create panner and gain nodes at the end of its internal chain that can be controlled from a single place and easily create sends to other Track
s. Like the base MooogAudioNode
, it automatically routes the end of its internal chain to the destinationNode.
The Web Audio API is not, and never will be, supported on IE (though it is supported on Edge), which limits its usefulness for general web projects until Edge supplants IE. Even where it is supported, the
API still has not matured (AudioWorkers, for example, are not implemented anywhere yet) so Mooog doesn't worry too much about cross-browser compatibility issues. It does, however, implement a patch
for the absence of the StereoPannerNode on for the deprecated Audio API, since panning is such a basic audio operation and the Mooog Track
object relies on it. Ensuring cross-platform consistency is on the to-do list once the API stabilizes and browser support improves.
Mooog shims the StereoPannerNode
on webkit browsers like Safari that don't support it.
(Adapted from https://github.com/mohayonao/stereo-panner-node)
If you want to jump right in, see the examples. They won't run well on the local filesystem because of CORS restrictions on AJAX audio file loads, so they're also posted on the github project page.
bower install mooog
Mooog sets up a (Webkit)AudioContext object and manages connections to its DestinationNode
automatically.
It takes an optional configuration object with the following properties:
debug
: Output debugging messages to the console. Default: falsedefault_gain
: Gain
objects that are initiated will have their gain automatically set to this value. Default: 0.5default_ramp_type
: adsr
envelopes will be produced using this type of curve ('linear or 'expo'). Default: 'expo'default_send_type
: For sends from Track
objects. Default: 'post'periodic_wave_length
: The PeriodicWave
generator functions calculate up to this many partials. Default: 2048curve_length
: The WaveShaper
curve generator functions produce Float32Array
s of this length. Default: 65536fake_zero
: This number is substituted for zero to prevent errors when zero is passed to an exponential ramp function. Default: 1 / 65536allow_multiple_audiocontexts
: Browsers differ in how many AudioContext
instances they support. Safari supports one, Chrome supports 6, and Firefox appears to have no limit. Therefore, by default, Mooog reuses the same AudioContext even if you initialize multiple Mooog objects, but you can override that with this option. Default: falseNodes are created via the node()
method of the Mooog object, which takes a node definition object with a single required
parameter, node_type
. You can also give it a string id to reference it later on:
M = new Mooog();
var my_oscillator = M.node( {
id: "my_new_node_string_id",
node_type: "Oscillator"
} );
Here we've assigned the node reference to a variable, but we can also reference an initialized node using its id as a single argument:
M.node('my_new_node_string_id');
The node_type
parameter is the name of the AudioNode
as found in its create function, i.e. "Gain" because AudioContext.createGain()
, "BiquadFilter" because AudioContext.createBiquadFilter()
, etc.
Any other parameters you want to set on the AudioNode
can be submitted as part of the node definition:
var my_oscillator = M.node( {
id: "my_new_node_string_id",
node_type: "Oscillator",
frequency: 850,
type: "sawtooth"
} );
If you don't need to set parameters, there is also a shorthand for creating a node where you specify only the id and the type:
var my_oscillator = M.node( "my_new_node_string_id", "Oscillator" );
Nodes automatically route their output to the DestinationNode
of the context. To override this behavior
(useful if you're creating LFOs to modulate AudioParams
, for example), pass connect_to_destination : false
in the node definition object:
var my_oscillator = M.node( {
id: "my_new_node_string_id",
node_type: "Oscillator",
connect_to_destination: false
} );
connect()
works just like the native AudioNode
method, except it returns the source, so you can chain them
to accomplish fan-out more easily:
M.node("my_previously_created_audio_buffer_source")
.connect( M.node({ id: "my_short_delay", node_type: "Delay", delayTime: 0.2 }) )
.connect( M.node({ id: "my_long_delay", node_type: "Delay", delayTime: 1.5 }) );
If you want to link nodes in series, you can use chain()
instead of connect()
.
You can also easily initialize chains of nodes in a single Track object. See below.
chain
returns the destination node, not the source node. It also automatically disconnects the source
from the context's AudioDestinationNode
. To chain
an AudioParam
, use the name of the param as the
second argument.
M.node("my_previously_created_audio_buffer_source")
.chain( M.node({ id: "my_delay", node_type: "Delay", delayTime: 0.5 ) )
.chain( M.node({ id: "my_reverb", node_type: "Convolver", buffer_source_file: "/my-impulse-response.wav" ) );
disconnect()
works like the native function but won't throw an error if the connection doesn't exist. It will output
a warning to the console if Mooog was initialized with debug: true
.
param()
getter/setterAudioNode parameters are a mix of enumerated properties, strings, numbers, and AudioParam
objects. Mooog
supports setting any of these jQuery-style via the param()
getter/setter function.
var osc = M.node('my_oscillator', 'Oscillator');
osc.param('frequency'); // -> returns 440
osc.param('frequency', 800); // -> returns 800
osc.param('frequency'); // -> returns 800
Like jQuery, multiple parameters can be set in the same param call:
osc.param( {frequency: 800, type: 'sawtooth'} );
Internally, param()
actually calls AudioParam.cancelScheduledValues()
and then uses AudioParam.setValueAtTime(value, currentTime)
by default in order to ensure consistent behavior.
Put another way, using param()
will always have the desired effect regardless of whether
other value changes have been scheduled on that parameter, unlike acting on Audioparam.value
directly.
The AudioParam
API provides 5 different methods for scheduling parameter changes. param()
can
be used to call any of them by adding properties to the object submitted. Here are examples using
an oscillator's frequency
parameter. Note the use of from_now
which causes a call to setValueAtTime
at the currentTime if used with linear or exponential ramp functions.
Set frequency
to 800 immediately. (Use setValueAtTime
)
osc.param( {frequency: 800} );
Set frequency
to 800, 4 seconds from now. (Use setValueAtTime
)
osc.param( {frequency: 800, at: 4} );
Ramp frequency
linearly to 800, starting after the last scheduled value change (or now, if
there isn't one) and arriving 4 seconds from now. (Use linearRampToValueAtTime
)
osc.param( {frequency: 800, at: 4, ramp: 'linear'} );
Ramp frequency
linearly to 800, starting now and arriving 4 seconds from now.
osc.param( {frequency: 800, at: 4, ramp: 'linear', from_now: true} );
Ramp frequency
exponentially to 800 over 4 seconds, starting now.
(Use exponentialRampToValueAtTime
)
osc.param( {frequency: 800, at: 4, ramp: 'expo', from_now: true } );
Set frequency
to asymptotically approach 800, beginning 4 seconds from now.
(Use setTargetAtTime
)
osc.param( {frequency: 800, at: 4, ramp: 'expo', timeConstant: 1.5} );
Set frequency
to values 300, 550, 900, 800 over a period of 2 seconds.
(Use setValueCurveAtTime
)
osc.param( {frequency: [300, 550, 900, 800], duration: 2, ramp: 'curve'} );
The rhythmic irregularity of the frequency progression produced by the setValueCurveAtTime()
method is due to the nearest-value interpolation algorithm it uses. The function is meant for much larger arrays
of values describing smooth curves.
The cancel
and at
parameters can be used with any of the ramp
types.
For convenience, you can create ADSR, ASR, or ADS envelopes with the adsr
method of any Node:
MooogAudioNode.adsr( param: mixed, config: object )
param
: An AudioParam
or the string name of the AudioParam
, assumed to be on this
.
config
: Object with the following properties:
s
value will be ignored.
The release stage can be suppressed by passing an array of 2 elements, in which case the
envelope will be an ADS envelope (useful if you're responding to user input or the duration of the note cannot be predetermined.)default_ramp_type
property of the Mooog config objectA very small number fake_zero
is used in place of actual zero if given as the base
, a
, or s
property so that the exponential ramping function doesn't throw an error. fake_zero
defaults to
1/65536 but can be configured when Mooog is initialized.
Mooog includes a Track
object designed to make working with lots of nodes a little easier. You set up and refer to
the Track with a unique string identifier (just like a node), and populate its internal chain of nodes with one or
more additional arguments to the creator function:
M.track( 'my_track', node [, node...] );
Each node
argument can be a node definition object of the type you'd pass to the Mooog.node()
function or an existing
node. The Track object routes the last node in its internal chain through a pan/gain stage. Like all nodes, the Track exposes
the native methods/properties of the first node in its internal chain, but it also exposes the gain
and pan
AudioParams of
the nodes in the pan/gain stage directly.
Tracks can be used interchangeably with nodes as source or destination objects for methods connect()
and chain()
.
Tracks have a send()
function analogous to mixing board sends. Once created, the send (which is a Gain
node) is referenced by string id, just like Tracks and other nodes.
/* create the track */
M.track("my_track", M.node({id:"sin", node_type:"Oscillator", type:"sawtooth"}), { id:"fil",node_type:"BiquadFilter" } );
/* Set up a reverb effect track*/
rev = M.track('reverb', { id:"cv", node_type:"Convolver", buffer_source_file:"/some-impulse-response.wav" });
/* Create the send */
M.track('my_track').send('rev_send', rev, 'post');
/* Come back later and change the gain */
M.track('my_track').send('rev_send').param('gain', 0.25);
Creates a send to another Track
object.
id
: String ID to assign to this send.
destination
: The target Track
object or ID thereof
pre
: Either 'pre' or 'post'. Defaults to config value in the Mooog
object.
gain
: Initial gain value for the send. Defaults to config value in the Mooog
object.
A convenience function for converting MIDI notes to equal temperament Hz
The native versions of the native (Sine, Sawtooth, Triangle, Square) waveforms
are louder than equivalent waveforms created with createPeriodicWave
so if your signal path includes both it may be easier to mix them if you use generated versions of the native waveforms:
Calculates and returns a sawtooth PeriodicWave
up to the nth partial.
Calculates and returns a square PeriodicWave
up to the nth partial.
Calculates and returns a triangle PeriodicWave
up to the nth partial.
Returns a sine PeriodicWave
.
state
property that is either 'stopped' or 'playing'buffer_source_file
indicating the URL of an audio asset from which to create an AudioBuffer and then set the buffer
of the underlying AudioNode
buffer
when the stop()
method is used so you can repeatedly stop
and start
without initializing a new Node.mooog.audioBufferLoaded
event when an external audio asset is completely loaded. The file
path is available in the detail
property.Constructor parameters numberOfInputs
or numberOfOutputs
can be passed in
the configuration object
buffer_source_file
indicating the URL of an audio asset (impulse response) from which to create an AudioBuffer and then set the buffer
of the underlying AudioNode
mooog.audioBufferLoaded
event when an external audio asset is completely loaded. The file path is available in the detail
property.feedback
property that maps to the Gain
of a feedback stage. Defaults to zero, and can be set on initialization: Mooog.node( { node_type: 'Delay', feedback: 0.2 } )
Gain
object is initialized with gain
set to 0.5 instead of 1.0. You can change this default with the default_gain
Mooog config option.state
property that is either 'stopped' or 'playing'gain
so you can repeatedly stop
and start
without initializing a new Node.
This means, of course, that no guarantees about phase can be made for subsequent calls to
start()
. If you need to control phase on nodestart()
you should use a rawOscillatorNode
.
Constructor arguments numberOfInputChannels
, numberOfOutputChannels
and bufferSize
can be
passed in the configuration object.
Includes utility functions tanh for hyperbolic tangent and chebyshev for Chebyshev polynomials, generating Float32Array distortion curves. tanh
takes a single argument representing the coefficient (higher coefficients equal more aggressive shaping). chebyshev
takes a single argument indicating the number of terms to generate (the exponent of the first term).
/* create the waveshaper */
var shaper = M.node("my_waveshaper", M.node({ node_type:"WaveShaper" });
/* Use a tanh waveshaping curve */
shaper.curve = shaper.tanh(2);
/* Use a 5th-order Chebyshev polynomial waveshaper */
shaper.curve = shaper.chebyshev(5);
The MIT License (MIT)
Copyright (c) 2016 Matthew Lima
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
If you're feeling generous, you can throw me some dosh here.
fake_zero
is only used when the ramp type is 'expo'. Changed behavior of from_now
option in param() to use setTimeout in order to correctly pick up param changes scheduled on the same tick..connect
and .chain
buffer
property fire loaded events. Buffer source load requests are cached to avoid logjam on page load.configure_from
and zero_node_setup
buffer_source_file
adsr()