gibber.lib
Version:
standalone libraries from Gibber to include in web pages
1,579 lines (1,324 loc) • 1.68 MB
JavaScript
!function(e){if("object"==typeof exports)module.exports=e();else if("function"==typeof define&&define.amd)define(e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.Gibber=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(_dereq_,module,exports){
(function (global){
!function (root, factory) {
if (typeof define === "function" && define.amd) {
define([], factory);
} else if (typeof exports === "object") {
module.exports = factory();
} else {
root.Gibberish = factory();
}
}(this, function () {
/**#Gibberish - Miscellaneous
Gibberish is the main object used to manage the audio graph and perform codegen functions. All constructors are also inside of the Gibberish object. Gibberish can automatically generate an appropriate web audio callback for you; if you want to use this you must execute the Gibberish.init() command before creating any Gibberish ugens.
## Example Usage##
`// make a sine wave
Gibberish.init();
a = new Gibberish.Sine().connect();`
## Constructor
**param** *bufferSize*: Integer. Default 1024. The size of the buffer to be calculated. Since JavaScript is single-threaded, setting exceedingly large values for this will yield to stuttering in graphics and user interface performance.
- - - -
**/
/**###Gibberish.audioFiles : property
Array. Anytime an audiofile is loaded (normally using the Sampler ugen) the resulting sample buffer is stored in this array so that it can be immediately recalled.
**/
/**###Gibberish.callback : property
String. Whenever Gibberish performs code generation the resulting callback is stored here.
**/
/**###Gibberish.out : property
Object. The is the 'master' bus that everything eventually gets routed to if you're using the auto-generated calback. This bus is initialized in the call to Gibberish.init.
**/
/**###Gibberish.dirtied : property
Array. A list of objects that need to be codegen'd
**/
/**###Gibberish.isDirty : property
Booelan. Whether or codegen should be performed.
**/
/**###Gibberish.codeblock : property
Array. During codegen, each ugen's codeblock is inserted into this array. Once all the ugens have codegen'd, the array is concatenated to form the callback.
**/
/**###Gibberish.upvalues : property
Array. Each ugen's callback function is stored in this array; the contents of the array become upvalues to the master callback function when it is codegen'd.
**/
/**###Gibberish.debug : property
Boolean. Default false. When true, the callbackString is printed to the console whenever a codegen is performed
**/
/**###Gibberish.memo : property
Object. Used in the codegen process to make sure codegen for each ugen is only performed once.
**/
var Gibberish = {
memo : {},
codeblock : [],
analysisCodeblock : [],
analysisUgens : [],
dirtied : [],
id : 0,
isDirty : false, // whether or not callback needs to codegen'd
out : null, // main output bus
debug : false,
callback : '',
audioFiles : {},
sequencers : [],
callbackArgs : ['input'], // names of function arguments for main audio callback
callbackObjects : [], // ugen function callbacks used in main audio callback
analysisCallbackArgs : [],
analysisCallbackObjects : [],
onBlock: null,
/**###Gibberish.createCallback : method
Perform codegen on all dirty ugens and re-create the audio callback. This method is called automatically in the default Gibberish sample loop whenever Gibberish.isDirty is true.
**/
createCallback : function() {
this.memo = {};
this.codeblock.length = 0;
this.callbackArgs.length = 0;
this.callbackObjects.length = 0;
this.analysisCallbackArgs.length = 0;
/* generate code for dirty ugens */
/*for(var i = 0; i < this.dirtied.length; i++) {
this.dirtied[i].codegen();
}*/
this.dirtied.length = 0;
this.codestring = ''
this.args = ['input']
this.memo = {};
this.out.codegen()
var codeblockStore = this.codeblock.slice(0)
// we must push these here because the callback arguments are at the start of the string,
// but we have to wait to codegen the analysis ugens until after their targets have been codegen'd
if(this.analysisUgens.length > 0) {
this.analysisCodeblock.length = 0;
for(var i = 0; i < this.analysisUgens.length; i++) {
this.analysisCallbackArgs.push( this.analysisUgens[i].analysisSymbol )
}
}
this.args = this.args.concat( this.callbackArgs )
this.args = this.args.concat( this.analysisCallbackArgs )
/* concatenate code for all ugens */
//this.memo = {};
this.codestring += codeblockStore.join('\t') //this.codeblock.join("\t");
this.codestring += "\n\t";
/* analysis codeblock */
if(this.analysisUgens.length > 0) {
this.analysisCodeblock.length = 0;
for(var i = 0; i < this.analysisUgens.length; i++) {
this.codeblock.length = 0;
this.analysisUgens[i].analysisCodegen();
/*
if(this.codestring !== 'undefined' ) {
this.codestring += this.codeblock.join("");
this.codestring += "\n\t";
this.analysisCodeblock.push ( this.analysisUgens[i].analysisCodegen() );
}
*/
}
this.codestring += this.analysisCodeblock.join('\n\t');
this.codestring += '\n\t';
}
this.codestring += 'return ' + this.out.variable +';\n';
this.callbackString = this.codestring;
if( this.debug ) console.log( this.callbackString );
return [this.args, this.codestring];
},
/**###Gibberish.audioProcess : method
The default audio callback used in Webkit browsers. This callback starts running as soon as Gibberish.init() is called.
param **Audio Event** : Object. The HTML5 audio event object.
**/
audioProcess : function(e){
var bufferL = e.outputBuffer.getChannelData(0),
bufferR = e.outputBuffer.getChannelData(1),
input = e.inputBuffer.getChannelData(0),
me = Gibberish,
callback = me.callback,
sequencers = me.sequencers,
out = Gibberish.out.callback,
objs = me.callbackObjects.slice(0),
callbackArgs, callbackBody, _callback, val
if( me.onBlock !== null ) me.onBlock( me.context )
objs.unshift(0)
for(var i = 0, _bl = e.outputBuffer.length; i < _bl; i++){
for(var j = 0; j < sequencers.length; j++) { sequencers[j].tick(); }
if(me.isDirty) {
_callback = me.createCallback();
try{
callback = me.callback = new Function( _callback[0], _callback[1] )
}catch( e ) {
console.error( "ERROR WITH CALLBACK : \n\n", _callback )
}
me.isDirty = false;
objs = me.callbackObjects.slice(0)
objs.unshift(0)
}
//console.log( "CB", callback )
objs[0] = input[i]
val = callback.apply( null, objs );
bufferL[i] = val[0];
bufferR[i] = val[1];
}
},
/**###Gibberish.audioProcessFirefox : method
The default audio callback used in Firefox. This callback starts running as soon as Gibberish.init() is called.
param **Sound Data** : Object. The buffer of audio data to be filled
**/
audioProcessFirefox : function(soundData) { // callback for firefox
var me = Gibberish,
callback = me.callback,
sequencers = me.sequencers,
objs = me.callbackObjects.slice(0),
_callback
objs.unshift(0)
for (var i=0, size=soundData.length; i<size; i+=2) {
for(var j = 0; j < sequencers.length; j++) { sequencers[j].tick(); }
if(me.isDirty) {
_callback = me.createCallback();
try {
callback = me.callback = new Function( _callback[0], _callback[1] )
}catch( e ) {
console.error( 'ERROR WITH CALLBACK : \n\n', callback )
}
me.isDirty = false;
objs = me.callbackObjects.slice(0)
objs.unshift(0)
}
var val = callback.apply(null, objs);
soundData[i] = val[0];
soundData[i+1] = val[1];
}
},
/**###Gibberish.clear : method
Remove all objects from Gibberish graph and perform codegen... kills all running sound and CPU usage.
**/
clear : function() {
this.out.inputs.length = 0;
this.analysisUgens.length = 0;
this.sequencers.length = 0;
this.callbackArgs.length = 2
this.callbackObjects.length = 1
Gibberish.dirty(this.out);
},
/**###Gibberish.dirty : method
Tell Gibberish a ugen needs to be codegen'd and mark the entire callback as needing regeneration
param **Ugen** : Object. The ugen that is 'dirtied'... that has a property value changed.
**/
dirty : function(ugen) {
if(typeof ugen !== 'undefined') {
var found = false;
for(var i = 0; i < this.dirtied.length; i++) {
if(this.dirtied[i].variable === ugen.variable) found = true;
}
if(!found) {
this.isDirty = true;
this.dirtied.push(ugen);
}
}else{
this.isDirty = true;
}
},
/**###Gibberish.generateSymbol : method
Generate a unique symbol for a given ugen using its name and a unique id number.
param **name** : String. The name of the ugen; for example, reverb, delay etc.
**/
generateSymbol : function(name) {
return name + "_" + this.id++;
},
// as taken from here: https://wiki.mozilla.org/Audio_Data_API#Standardization_Note
// only the number of channels is changed in the audio.mozSetup() call
/**###Gibberish.AudioDataDestination : method
Used to generate callback for Firefox.
param **sampleRate** : String. The sampleRate for the audio callback to run at. NOT THE BUFFER SIZE.
param **readFn** : Function. The audio callback to use.
**/
AudioDataDestination : function(sampleRate, readFn) { // for Firefox Audio Data API
// Initialize the audio output.
var audio = new Audio();
audio.mozSetup(2, sampleRate);
var currentWritePosition = 0;
var prebufferSize = sampleRate / 2; // buffer 500ms
var tail = null, tailPosition;
// The function called with regular interval to populate
// the audio output buffer.
setInterval(function() {
var written;
// Check if some data was not written in previous attempts.
if(tail) {
written = audio.mozWriteAudio(tail.subarray(tailPosition));
currentWritePosition += written;
tailPosition += written;
if(tailPosition < tail.length) {
// Not all the data was written, saving the tail...
return; // ... and exit the function.
}
tail = null;
}
// Check if we need add some data to the audio output.
var currentPosition = audio.mozCurrentSampleOffset();
var available = currentPosition + prebufferSize - currentWritePosition;
if(available > 0) {
// Request some sound data from the callback function.
var soundData = new Float32Array(available);
readFn(soundData);
// Writting the data.
written = audio.mozWriteAudio(soundData);
currentPosition = audio.mozCurrentSampleOffset();
if(written < soundData.length) {
// Not all the data was written, saving the tail.
tail = soundData;
tailPosition = written;
}
currentWritePosition += written;
}
}, 100);
},
/**###Gibberish.AudioDataDestination : method
Create a callback and start it running. Note that in iOS audio callbacks can only be created in response to user events. Thus, in iOS this method assigns an event handler to the HTML body that creates the callback as soon as the body is touched; at that point the event handler is removed.
**/
init : function() {
// TODO: GET A BETTER TEST FOR THIS. The problem is that browserify adds a process object... not sure how robust
// testing for the presence of the version property will be
var isNode = typeof global !== 'undefined',
bufferSize = typeof arguments[0] === 'undefined' ? 1024 : arguments[0],
audioContext,
start
if( typeof webkitAudioContext !== 'undefined' ) {
audioContext = webkitAudioContext
}else if ( typeof AudioContext !== 'undefined' ) {
audioContext = AudioContext
}
// we will potentially delay start of audio until touch of screen for iOS devices
start = function() {
if( typeof audioContext !== 'undefined' ) {
if( document && document.documentElement && 'ontouchstart' in document.documentElement ) {
window.removeEventListener('touchstart', start);
if('ontouchstart' in document.documentElement){ // required to start audio under iOS 6
var mySource = Gibberish.context.createBufferSource();
mySource.connect(Gibberish.context.destination);
mySource.noteOn(0);
}
}
}else{
alert('Your browser does not support javascript audio synthesis. Please download a modern web browser that is not Internet Explorer.')
}
if( Gibberish.onstart ) Gibberish.onstart()
}
Gibberish.context = new audioContext();
Gibberish.node = Gibberish.context.createScriptProcessor(bufferSize, 2, 2, Gibberish.context.sampleRate);
Gibberish.node.onaudioprocess = Gibberish.audioProcess;
Gibberish.node.connect(Gibberish.context.destination);
Gibberish.out = new Gibberish.Bus2();
Gibberish.out.codegen(); // make sure bus is first upvalue so that clearing works correctly
Gibberish.dirty(Gibberish.out);
if( document && document.documentElement && 'ontouchstart' in document.documentElement ) {
window.addEventListener('touchstart', start);
}else{
start();
}
return this;
},
/**###Gibberish.makePanner : method
Create and return an object that can be used to pan a stereo source.
**/
// makePanner : function() {
// var sin = Math.sin;
// var cos = Math.cos;
// var sqrtTwoOverTwo = Math.sqrt(2) / 2;
//
// var f = function(val, pan, array) {
// var isObject = typeof val === 'object';
// var l = isObject ? val[0] : val;
// var r = isObject ? val[1] : val;
//
// array[0] = l * (sqrtTwoOverTwo * (cos(pan) - sin(pan)) );
// array[1] = r * (sqrtTwoOverTwo * (cos(pan) + sin(pan)) );
//
// return array;
// };
//
// return f;
// },
makePanner : function() {
// thanks to grrrwaaa for this
// create pan curve arrays (once-only):
var panTableL = [], panTableR = [];
var sqrtTwoOverTwo = Math.sqrt(2) / 2;
for( var i = 0; i < 1024; i++ ) {
var pan = -1 + ( i / 1024 ) * 2;
panTableL[i] = (sqrtTwoOverTwo * (Math.cos(pan) - Math.sin(pan)) );
panTableR[i] = (sqrtTwoOverTwo * (Math.cos(pan) + Math.sin(pan)) );
}
return function(val, pan, output) {
var isObject = typeof val === 'object',
l = isObject ? val[0] : val,
r = isObject ? val[1] : val,
_index, index, frac, index2, val1, val2;
_index = ((pan + 1) * 1023) / 2
index = _index | 0
frac = _index - index;
index = index & 1023;
index2 = index === 1023 ? 0 : index + 1;
val1 = panTableL[index];
val2 = panTableL[index2];
output[0] = ( val1 + ( frac * (val2 - val1) ) ) * l;
val1 = panTableR[index];
val2 = panTableR[index2];
output[1] = ( val1 + ( frac * (val2 - val1) ) ) * r;
return output;
}
},
// IMPORTANT: REMEMBER THIS IS OVERRIDDEN IN GIBBER
defineUgenProperty : function(key, initValue, obj) {
var prop = obj.properties[key] = {
value: initValue,
binops: [],
parent : obj,
name : key,
};
Object.defineProperty(obj, key, {
configurable: true,
get: function() { return prop.value },
set: function(val) {
prop.value = val;
Gibberish.dirty(obj);
},
});
},
/**###Gibberish.polyInit : method
For ugens with polyphony, add metaprogramming that passes on property changes to the 'children' of the polyphonic object. Polyphonic ugens in Gibberish are just single instances that are routed into a shared bus, along with a few special methods for voice allocation etc.
param **Ugen** : Object. The polyphonic ugen
**/
polyInit : function(ugen) {
ugen.mod = ugen.polyMod;
ugen.removeMod = ugen.removePolyMod;
ugen.voicesClear = function() {
if( ugen.children.length > 0 ) {
for( var i = 0; i < ugen.children.length; i++ ) {
ugen.children[ i ].disconnect()
}
ugen.children.length = 0
ugen.voiceCount = 0
}
}
for(var key in ugen.polyProperties) {
(function(_key) {
var value = ugen.polyProperties[_key];
Object.defineProperty(ugen, _key, {
configurable: true,
get : function() { return value; },
set : function(val) {
value = val;
for(var i = 0; i < ugen.children.length; i++) {
ugen.children[i][_key] = value;
}
},
});
})(key);
}
var maxVoices = ugen.maxVoices
Object.defineProperty( ugen, 'maxVoices', {
get: function() { return maxVoices },
set: function(v) { maxVoices = v; this.voicesClear(); this.initVoices() }
})
},
/**###Gibberish.interpolate : method
Similiar to makePanner, this method returns a function that can be used to linearly interpolate between to values. The resulting function takes an array and a floating point position index and returns a value.
**/
interpolate : function(arr, phase){
var index = phase | 0, // round down
index2 = index + 1 > arr.length - 1 ? 0 : index + 1;
frac = phase - index;
return arr[index] + frac * (arr[index2] - arr[index]);
},
pushUnique : function(item, array) {
var obj = item;
var shouldAdd = true;
for(var j = 0; j < array.length; j++) {
if(obj === array[j]) {
shouldAdd = false;
break;
}
}
if(shouldAdd) {
array.push(obj);
}
},
export : function(key, obj) {
for(var _key in Gibberish[key]) {
//console.log("exporting", _key, "from", key);
obj[_key] = Gibberish[key][_key];
}
},
/**###Gibberish.ugen : method
Creates a prototype object that is used by all ugens.
**/
ugen : function() {
Gibberish.extend(this, {
/**#Ugen - Miscellaneous
The prototype object that all ugens inherit from
**/
/**###Ugen.processProperties : method
Used to assign and process arguments passed to the constructor functions of ugens.
param **argumentList** : Array. A list of arguments (may be a single dictionary) passed to a ugen constructor.
**/
processProperties : function(args){
if(typeof arguments[0][0] === 'object' && typeof arguments[0][0].type === 'undefined' && !Array.isArray(arguments[0][0]) && arguments[0][0].name !== 'op') {
var dict = arguments[0][0];
for(var key in dict) {
if(typeof dict[key] !== 'undefined') {
if(typeof this.properties[key] === 'object' && typeof this.properties[key].binops !== 'undefined') {
this.properties[key].value = dict[key];
}else{
this[key] = dict[key];
}
}
}
}else{
var i = 0;
for(var key in this.properties) {
if(typeof this.properties[key] === 'object' && typeof this.properties[key].binops !== 'undefined') {
if(typeof arguments[0][i] !== 'undefined'){
this.properties[key].value = arguments[0][i++];
}
}else{
if(typeof arguments[0][i] !== 'undefined') {
this.properties[key] = arguments[0][i++];
}
}
}
}
return this;
},
valueOf: function() {
this.codegen()
//console.log( "VALUEOF", this.variable )
return this.variable
},
/**###Ugen.codegen : method
Generates output code (as a string) used inside audio callback
**/
codegen : function() {
var s = '',
v = null,
initialized = false;
if(Gibberish.memo[this.symbol]) {
//console.log("MEMO" + this.symbol);
return Gibberish.memo[this.symbol];
}else{
// we generate the symbol and use it to create our codeblock, but only if the ugen doesn't already have a variable assigned.
// since the memo is cleared every time the callback is created, we need to check to see if this exists.
v = this.variable ? this.variable : Gibberish.generateSymbol('v');
Gibberish.memo[this.symbol] = v;
this.variable = v;
}
s += 'var ' + v + " = " + this.symbol + "(";
for(var key in this.properties) {
var property = this.properties[key];
var value = '';
//if(this.name === "single_sample_delay") { console.log( "SSD PROP" + key ); }
if( Array.isArray( property.value ) ) {
if(property.value.length === 0) value = 0; // primarily for busses
for(var i = 0; i < property.value.length; i++) {
var member = property.value[i];
if( typeof member === 'object' ) {
value += member !== null ? member.valueOf() : 'null';
}else{
if(typeof property.value === 'function') {
value += property.value();
}else{
value += property.value;
}
}
value += i < property.value.length - 1 ? ', ' : '';
}
}else if( typeof property.value === 'object' ) {
if( property.value !== null) {
value = property.value.codegen ? property.value.valueOf() : property.value
}
}else if( property.name !== 'undefined'){
if(typeof property.value === 'function') {
value = property.value();
}else{
value = property.value;
}
}
if(property.binops.length != 0) {
for( var k = 0; k < property.binops.length; k++) {
s += '(' // all leading parenthesis...
}
for(var j = 0; j < property.binops.length; j++) {
var op = property.binops[j],
val;
if( typeof op.ugen === 'number') {
val = op.ugen;
}else{
val = op.ugen !== null ? op.ugen.valueOf() : 'null';
}
if(op.binop === "=") {
s = s.replace(value, "");
s += val;
}else if(op.binop === "++"){
s += ' + Math.abs(' + val + ')';
}else{
if( j === 0) s+= value
s += " " + op.binop + " " + val + ")";
}
}
}else{
s += value
}
s += ", ";
}
if(s.charAt(s.length - 1) === " ")
s = s.slice(0, -2); // remove trailing spaces
s += ");\n";
this.codeblock = s;
if( Gibberish.codeblock.indexOf( this.codeblock ) === -1 ) Gibberish.codeblock.push( this.codeblock )
if( Gibberish.callbackArgs.indexOf( this.symbol ) === -1 && this.name !== 'op') { Gibberish.callbackArgs.push( this.symbol ) }
if( Gibberish.callbackObjects.indexOf( this.callback ) === -1 && this.name !== 'op' ) { Gibberish.callbackObjects.push( this.callback ) }
this.dirty = false;
return v;
},
/**###Ugen.defineUgenProperty : method
Creates getters and setters for ugen properties that automatically dirty the ugen whenever the property value is changed.
param **key** : String. The name of a property to add getter / setters for.
param **value** : Any. The initival value to set the property to
**/
/**###Ugen.init : method
Initialize ugen by calling defineUgenProperty for every key in the ugen's properties dictionary, generating a unique id for the ugen and various other small tasks.
**/
init : function() {
if(!this.initalized) {
this.symbol = Gibberish.generateSymbol(this.name);
this.codeblock = null;
this.variable = null;
}
if(typeof this.properties === 'undefined') {
this.properties = {};
}
if(!this.initialized) {
this.destinations = [];
for(var key in this.properties) {
Gibberish.defineUgenProperty(key, this.properties[key], this);
}
}
if(arguments.length > 0 && typeof arguments[0][0] === 'object' && arguments[0][0].type === 'undefined') {
var options = arguments[0][0];
for(var key in options) {
this[key] = options[key];
}
}
this.initialized = true;
return this;
},
/**###Ugen.mod : method
Modulate a property of a ugen on a per-sample basis.
param **key** : String. The name of the property to modulate
param **value** : Any. The object or number value to modulate the property with
param **op** : String. Default "+". The operation to perform. Can be +,-,*,/,= or ++. ++ adds and returns the absolute value.
**/
mod : function(name, value, op) {
var property = this.properties[ name ];
var mod = { ugen:value, binop:op };
property.binops.push( mod );
Gibberish.dirty( this );
},
/**###Ugen.removeMod : method
Remove a modulation from a ugen.
param **key** : String. The name of the property to remove the modulation from
param **arg** : Number or Object. Optional. This determines which modulation to remove if more than one are assigned to the property. If this argument is undefined, all modulations are removed. If the argument is a number, the number represents a modulation in the order that they were applied (an array index). If the argument is an object, it removes a modulation that
is using a matching object as the modulator.
**/
removeMod : function(name, arg) {
if(typeof arg === 'undefined' ) {
this.properties[name].binops.length = 0;
}else if(typeof arg === 'number') {
this.properties[name].binops.splice(arg, 1);
}else if(typeof arg === 'object') {
for(var i = 0, j = this.properties[name].binops.length; i < j; i++) {
if(this.properties[name].binops[i].ugen === arg) {
this.properties[name].binops.splice(i, 1);
}
}
};
Gibberish.dirty( this );
},
/**###Ugen.polyMod : method
Applies a modulation to all children of a polyphonic ugen
param **key** : String. The name of the property to modulate
param **value** : Any. The object or number value to modulate the property with
param **op** : String. Default "+". The operation to perform. Can be +,-,*,/,= or ++. ++ adds and returns the absolute value.
**/
polyMod : function(name, modulator, type) {
for(var i = 0; i < this.children.length; i++) {
this.children[i].mod(name, modulator, type);
}
Gibberish.dirty(this);
},
/**###Ugen.removePolyMod : method
Removes a modulation from all children of a polyphonic ugen. The arguments
param **arg** : Number or Object. Optional. This determines which modulation to remove if more than one are assigned to the property. If this argument is undefined, all modulations are removed. If the argument is a number, the number represents a modulation in the order that they were applied (an array index). If the argument is an object, it removes a modulation that
is using a matching object as the modulator.
**/
removePolyMod : function() {
var args = Array.prototype.slice.call(arguments, 0);
if(arguments[0] !== "amp" && arguments[0] !== "pan") {
for(var i = 0; i < this.children.length; i++) {
this.children[i].removeMod.apply(this.children[i], args);
}
}else{
this.removeMod.apply(this, args);
}
Gibberish.dirty(this);
},
smooth : function(property, amount) {
var op = new Gibberish.OnePole();
this.mod(property, op, "=");
},
/**###Ugen.connect : method
Connect the output of a ugen to a bus.
param **bus** : Bus ugen. Optional. The bus to connect the ugen to. If no argument is passed the ugen is connect to Gibberish.out. Gibberish.out is automatically created when Gibberish.init() is called and can be thought of as the master stereo output for Gibberish.
**/
connect : function(bus, position) {
if(typeof bus === 'undefined') bus = Gibberish.out;
if(this.destinations.indexOf(bus) === -1 ){
bus.addConnection( this, 1, position );
this.destinations.push( bus );
}
return this;
},
/**###Ugen.send : method
Send an arbitrary amount of output to a bus
param **bus** : Bus ugen. The bus to send the ugen to.
param **amount** : Float. The amount of signal to send to the bus.
**/
send : function(bus, amount) {
if(this.destinations.indexOf(bus) === -1 ){
bus.addConnection( this, amount );
this.destinations.push( bus );
}else{
bus.adjustSendAmount(this, amount);
}
return this;
},
/**###Ugen.disconnect : method
Disconnect a ugen from a bus (or all busses). This stops all audio and signal processing for the ugen.
param **bus** : Bus ugen. Optional. The bus to disconnect the ugen from. If this argument is undefined the ugen will be disconnected from all busses.
**/
disconnect : function(bus, tempDisconnect ) { // tempDisconnect is used to do a short disconnect and reconnect
var idx
if( !tempDisconnect ) {
/*if( this.children ) {
for(var i = 0; i < this.children.length; i++) {
this.children[i].disconnect( this )
}
}else if( typeof this.input === 'object' ) {
this.input.disconnect( null, tempDisconnect )
}*/
/*var idx = Gibberish.callbackArgs.indexOf( this.symbol )
Gibberish.callbackArgs.splice(idx, 1)
idx = Gibberish.callbackObjects.indexOf( this.callback )
Gibberish.callbackObjects.splice(idx, 1)*/
}
if( !bus ) {
for(var i = 0; i < this.destinations.length; i++) {
this.destinations[i].removeConnection( this );
}
this.destinations = [];
}else{
idx = this.destinations.indexOf(bus);
if(idx > -1) {
this.destinations.splice(idx, 1);
}
bus.removeConnection( this );
}
Gibberish.dirty( this )
return this;
},
});
},
};
Array2 = function() {
this.length = 0;
};
Array2.prototype = [];
Array2.prototype.remove = function(arg, searchDeep) { // searchDeep when true removes -all- matches, when false returns first one found.
searchDeep = typeof searchDeep === 'undefined' ? true : searchDeep;
if(typeof arg === "undefined") { // clear all
for(var i = 0; i < this.length; i++) {
delete this[i];
}
this.length = 0;
}else if(typeof arg === "number") {
this.splice(arg,1);
}else if(typeof arg === "string"){ // find named member and remove
var removeMe = [];
for(var i = 0; i < this.length; i++) {
var member = this[i];
if(member.type === arg || member.name === arg) {
if(!searchDeep) {
this.splice(i,1);
return;
}else{
removeMe.push(i);
}
}
}
for(var i = 0; i < removeMe.length; i++) {
this.splice( removeMe[i], 1);
}
}else if(typeof arg === "object") {
var idx = this.indexOf(arg);
while(idx > -1) {
this.splice(idx,1);
idx = this.indexOf(arg);
}
}
if(this.parent) Gibberish.dirty(this.parent);
};
Array2.prototype.get = function(arg) {
if(typeof arg === "number") {
return this[arg];
}else if(typeof arg === "string"){ // find named member and remove
for(var i = 0; i < this.length; i++) {
var member = this[i];
if(member.name === arg) {
return member;
}
}
}else if(typeof arg === "object") {
var idx = this.indexOf(arg);
if(idx > -1) {
return this[idx];
}
}
return null;
};
Array2.prototype.replace = function(oldObj, newObj) {
newObj.parent = this;
newObj.input = oldObj.input;
if(typeof oldObj != "number") {
var idx = this.indexOf(oldObj);
if(idx > -1) {
this.splice(idx, 1, newObj);
}
}else{
this.splice(oldObj, 1, newObj);
}
if(this.parent) Gibberish.dirty(this.parent);
};
Array2.prototype.insert = function(v, pos) {
v.parent = this;
this.input = this.parent;
if(Array.isArray(v)) {
for(var i = 0; i < v.length; i++) {
this.splice(pos + i, 0, v[i]);
}
}else{
this.splice(pos,0,v);
}
if(this.parent) Gibberish.dirty(this.parent);
};
Array2.prototype.add = function() {
for(var i = 0; i < arguments.length; i++) {
arguments[i].parent = this;
arguments[i].input = this.parent;
//console.log(this.parent, this.parent.channels);
//if(typeof this.parent.channels === "number") {
//console.log("CHANGING CHANNELS");
//arguments[i].channels = this.parent.channels;
//}
this.push(arguments[i]);
}
//console.log("ADDING ::: this.parent = ", this.parent)
if(this.parent) {
console.log("DIRTYING");
Gibberish.dirty(this.parent);
}
};
var rnd = Math.random;
Gibberish.rndf = function(min, max, number, canRepeat) {
canRepeat = typeof canRepeat === "undefined" ? true : canRepeat;
if(typeof number === "undefined" && typeof min != "object") {
if(arguments.length == 1) {
max = arguments[0]; min = 0;
}else if(arguments.length == 2) {
min = arguments[0];
max = arguments[1];
}else{
min = 0;
max = 1;
}
var diff = max - min,
r = Math.random(),
rr = diff * r
return min + rr;
}else{
var output = [];
var tmp = [];
if(typeof number === "undefined") {
number = max || min.length;
}
for(var i = 0; i < number; i++) {
var num;
if(typeof arguments[0] === "object") {
num = arguments[0][rndi(0, arguments[0].length - 1)];
}else{
if(canRepeat) {
num = Gibberish.rndf(min, max);
}else{
num = Gibberish.rndf(min, max);
while(tmp.indexOf(num) > -1) {
num = Gibberish.rndf(min, max);
}
tmp.push(num);
}
}
output.push(num);
}
return output;
}
};
Gibberish.Rndf = function() {
var _min, _max, quantity, random = Math.random, canRepeat;
if(arguments.length === 0) {
_min = 0; _max = 1;
}else if(arguments.length === 1) {
_max = arguments[0]; _min = 0;
}else if(arguments.length === 2) {
_min = arguments[0]; _max = arguments[1];
}else if(arguments.length === 3) {
_min = arguments[0]; _max = arguments[1]; quantity = arguments[2];
}else{
_min = arguments[0]; _max = arguments[1]; quantity = arguments[2]; canRepeat = arguments[3];
}
return function() {
var value, min, max, range;
min = typeof _min === 'function' ? _min() : _min
max = typeof _max === 'function' ? _max() : _max
if( typeof quantity === 'undefined') {
value = Gibberish.rndf( min, max )
}else{
value = Gibberish.rndf( min, max, quantity, canRepeat )
}
return value;
}
};
Gibberish.rndi = function( min, max, number, canRepeat ) {
var range;
if(arguments.length === 0) {
min = 0; max = 1;
}else if(arguments.length === 1) {
max = arguments[0]; min = 0;
}else if( arguments.length === 2 ){
min = arguments[0]; max = arguments[1];
}else{
min = arguments[0]; max = arguments[1]; number = arguments[2]; canRepeat = arguments[3];
}
range = max - min
if( range < number ) canRepeat = true
if( typeof number === 'undefined' ) {
range = max - min
return Math.round( min + Math.random() * range );
}else{
var output = [];
var tmp = [];
for(var i = 0; i < number; i++) {
var num;
if(canRepeat) {
num = Gibberish.rndi(min, max);
}else{
num = Gibberish.rndi(min, max);
while(tmp.indexOf(num) > -1) {
num = Gibberish.rndi(min, max);
}
tmp.push(num);
}
output.push(num);
}
return output;
}
};
Gibberish.Rndi = function() {
var _min, _max, quantity, random = Math.random, round = Math.round, canRepeat, range;
if(arguments.length === 0) {
_min = 0; _max = 1;
}else if(arguments.length === 1) {
_max = arguments[0]; _min = 0;
}else if(arguments.length === 2) {
_min = arguments[0]; _max = arguments[1];
}else if(arguments.length === 3) {
_min = arguments[0]; _max = arguments[1]; quantity = arguments[2];
}else{
_min = arguments[0]; _max = arguments[1]; quantity = arguments[2]; canRepeat = arguments[3];
}
range = _max - _min
if( typeof quantity === 'number' && range < quantity ) canRepeat = true
return function() {
var value, min, max, range;
min = typeof _min === 'function' ? _min() : _min
max = typeof _max === 'function' ? _max() : _max
if( typeof quantity === 'undefined') {
value = Gibberish.rndi( min, max )
}else{
value = Gibberish.rndi( min, max, quantity, canRepeat )
}
return value;
}
};
Gibberish.extend = function(destination, source) {
for (var property in source) {
var keys = property.split(".");
if(source[property] instanceof Array && source[property].length < 100) { // don't copy large array buffers
destination[property] = source[property].slice(0);
if(property === "fx") {
destination[property].parent = source[property].parent;
}
}else if (typeof source[property] === "object" && source[property] !== null && !(source[property] instanceof Float32Array) ) {
destination[property] = destination[property] || {};
arguments.callee(destination[property], source[property]);
} else {
destination[property] = source[property];
}
}
return destination;
};
Function.prototype.clone=function(){
return eval('['+this.toString()+']')[0];
};
String.prototype.format = function(i, safe, arg) {
function format() {
var str = this,
len = arguments.length + 1;
for (i = 0; i < len; arg = arguments[i++]) {
safe = arg; //typeof arg === 'object' ? JSON.stringify(arg) : arg;
str = str.replace(RegExp('\\{' + (i - 1) + '\\}', 'g'), safe);
}
return str;
}
format.native = String.prototype.format;
return format;
}();
Gibberish.future = function(func, time) {
var seq = new Gibberish.Sequencer({
values:[
function(){},
function() {
func();
seq.stop();
seq.disconnect();
}
],
durations:[ time ]
}).start()
seq.cancel = function() {
seq.stop();
seq.disconnect();
}
return seq
}
Gibberish.Proxy = function() {
var value = 0;
Gibberish.extend(this, {
name: 'proxy',
type: 'effect',
properties : {},
callback : function() {
return value;
},
}).init();
this.input = arguments[0];
value = this.input.parent[ this.input.name ];
delete this.input.parent[ this.input.name ];
this.input.parent.properties[ this.input.name ].value = this;
Object.defineProperty( this.input.parent, this.input.name, {
get : function(){ return value; },
set : function(_value) { value = _value; }
});
Gibberish.dirty(this.input.parent);
};
Gibberish.Proxy.prototype = new Gibberish.ugen();
Gibberish.Proxy2 = function() {
var input = arguments[0],
name = arguments[1],
phase = 0
Gibberish.extend( this, {
name: 'proxy2',
type: 'effect',
properties : { },
callback : function() {
var v = input[ name ]
// if( phase++ % 44100 === 0 ) console.log( v, input, name)
return Array.isArray( v ) ? ( v[0] + v[1] + v[2] ) / 3 : v
},
}).init();
this.getInput = function() { return input }
this.setInput = function( v ) { input = v }
this.getName = function() { return name }
this.setName = function( v ) { name = v }
};
Gibberish.Proxy2.prototype = new Gibberish.ugen();
Gibberish.Proxy3 = function() {
var input = arguments[0],
name = arguments[1],
phase = 0
Gibberish.extend( this, {
name: 'proxy3',
type: 'effect',
properties : { },
callback : function() {
var v = input[ name ]
//if( phase++ % 44100 === 0 ) console.log( v, input, name)
return v || 0
},
})
this.init();
this.codegen = function() {
// if(Gibberish.memo[this.symbol]) {
// return Gibberish.memo[this.symbol];
// }
console.log(" CALLED ")
if( ! this.variable ) this.variable = Gibberish.generateSymbol('v');
Gibberish.callbackArgs.push( this.symbol )
Gibberish.callbackObjects.push( this.callback )
this.codeblock = "var " + this.variable + " = " + this.symbol + "(" + input.properties[ name ].codegen() + ");\n"
}
};
Gibberish.Proxy3.prototype = new Gibberish.ugen();
Gibberish.oscillator = function() {
this.type = 'oscillator';
this.oscillatorInit = function() {
this.fx = new Array2;
this.fx.parent = this;
return this;
}
};
Gibberish.oscillator.prototype = new Gibberish.ugen();
Gibberish._oscillator = new Gibberish.oscillator();
/**#Gibberish.Table - Oscillator
An wavetable oscillator.
## Example Usage##
`// fill the wavetable with random samples
Gibberish.init();
a = new Gibberish.Table();
var t = []
for( var i = 0; i < 1024; i++ ) { t[ i ] = Gibberish.rndf(-1,1) }
a.setTable( t )
a.connect()
`
- - - -
**/
/**###Gibberish.Table.frequency : property
Number. From 20 - 20000 hz.
**/
/**###Gibberish.Table.amp : property
Number. A linear value specifying relative amplitude, ostensibly from 0..1 but can be higher, or lower when used for modulation.
**/
Gibberish.Wavetable = function() {
var phase = 0,
table = null,
tableFreq = Gibberish.context.sampleRate / 1024,
signHistory = 0,
flip = 0;
this.properties = {
frequency : 440,
amp : .25,
sync: 0
};
/**###Gibberish.Wavetable.setTable : method
Assign an array representing one cycle of a waveform to use.
param **table** Float32Array. Assign an array to be used as the wavetable.
**/
this.getTable = function() { return table; }
this.setTable = function(_table) { table = _table; tableFreq = Gibberish.context.sampleRate / table.length }
this.getTableFreq = function() { return tableFreq }
this.setTableFreq = function( v ) { tableFreq = v; }
this.getPhase = function() { return phase }
this.setPhase = function(v) { phase = v }
/**###Gibberish.Wavetable.callback : method
Returns a single sample of output.
param **frequency** Number. The frequency to be used to calculate output.
param **amp** Number. The amplitude to be used to calculate output.
**/
this.callback = function(frequency, amp, sync) {
var index, frac, index2, val1, val2, sign;
phase += frequency / tableFreq;
while(phase >= 1024) phase -= 1024;
index = phase | 0;
frac = phase - index;
index = index & 1023;
index2 = index === 1023 ? 0 : index + 1;
val1 = table[index];
val2 = table[index2];
// sign = typeof sync == 'number' ? sync ? sync < 0 ? -1 : 1 : isNaN(sync) ? NaN : 0 : NaN;
// if( sign !== signHistory && sign !== 0) {
// flip++
//
// if( flip === 2 ){
// phase = 0
// flip = 0
// }
// //console.log( "FLIP", sign, signHistory, count, sync )
// }
//
// if( sign !== 0 ) signHistory = sign
return ( val1 + ( frac * (val2 - val1) ) ) * amp;
}
}
Gibberish.Wavetable.prototype = Gibberish._oscillator;
Gibberish.Table = function( table ) {
this.__proto__ = new Gibberish.Wavetable();
this.name = 'table';
var pi_2 = Math.PI * 2
if( typeof table === 'undefined' ) {
table = new Float32Array(1024);
for(var i = 1024; i--;) { table[i] = Math.sin( (i / 1024) * pi_2); }
}
this.setTable( table );
this.init();
this.oscillatorInit();
//this.processProperties( arguments );
}
Gibberish.asmSine = function (stdlib, foreign, heap) {
"use asm";
var sin = stdlib.Math.sin;
var phase = 0.0;
var out = new stdlib.Float32Array(heap);
var floor = stdlib.Math.floor;
var tableFreq = 0.0;
function init() {
var i = 1024;
var j = 1024.0;
var test = 0.0;
for (; i = (i - 1) | 0; ) {
j = j - 1.0;
out[i >> 2] = +(sin( +(j / 1024.0) * 6.2848));
}
tableFreq = 44100.0 / 1024.0;
}
function gen(freq, amp, sr) {
freq = +freq;
amp = +amp;
sr = +sr;
var index = 0.0,
index1 = 0,
index2 = 0,
frac = 0.0,
val1 = 0.0,
val2 = 0.0;
phase = +(phase + freq / tableFreq);
if(phase >= 1024.0) phase = +(phase - 1024.0);
index = +floor(phase);
frac = phase - index;
index1 = (~~index);
if((index1 | 0) == (1024 | 0)) {
index2 = 0
} else {
index2 = (index1 + 1) | 0;
}
val1 = +out[ index1 >> 2 ];
val2 = +out[ index2 >> 2 ];
return +((val1 + (frac * (val2 - val1))) * amp);
}
function get(idx) {
idx = idx|0;
return +out[idx >> 2];
}
return {
init:init,
gen: gen,
get: get,
}
};
/*
phase += frequency / tableFreq;
while(phase >= 1024) phase -= 1024;
index = phase | 0;
frac = phase - index;
index = index & 1023;
index2 = index === 1023 ? 0 : index + 1;
val1 = table[index];
val2 = table[index2];
return ( val1 + ( frac * (val2 - val1) ) ) * amp;
*/
/*function gen (freq, amp, sr) {
freq = +freq;
amp = +amp;
sr = +sr;
phase = +(phase + +(+(freq / sr) * 3.14159 * 2.0));
return +(+sin(phase) * amp);
}*/
//var pi_2 = (3.14159 * 2.0);
Gibberish.asmSine2 = function () {
this.properties = { frequency:440.0, amp:.5, sr: Gibberish.context.sampleRate }
this.name = 'sine'
var buf = new ArrayBuffer(4096);
var asm = Gibberish.asmSine(window, null, buf);
asm.init();
this.getTable = function() { return buf; }
this.get = asm.get;
this.callback = asm.gen;
this.init();
this.oscillatorInit();
this.processProperties( arguments );
return this;
}
Gibberish.asmSine2.prototype = Gibberish._oscillator;
/**#Gibberish.Sine - Oscillator
A sinewave calculated on a per-sample basis.
## Example Usage##
`// make a sine wave
Gibberish.init();
a = new Gibberish.Sine().connect();`
- - - -
**/
/**###Gibberish.Sine.frequency : property
Number. From 20 - 20000 hz.
**/
/**###Gibberish.Sine.amp : property
Number. A linear value specifying relative amplitude, ostensibly from 0..1 but can be higher, or lower when used for modulation.
**/
Gibberish.Sine = function() {
this.__proto__ = new Gibberish.Wavetable();
this.name = 'sine';
var pi_2 = Math.PI * 2,
table = new Float32Array(1024);
for(var i = 1024; i--;) { table[i] = Math.sin( (i / 1024) * pi_2); }
this.setTable( table );
this.init( arguments );
this.oscillatorInit();
this.processProperties( arguments );
};
/**#Gibberish.Sine2 - Oscillator
A sinewave calculated on a per-sample basis that can be panned.
## Example Usage##
`// make a sine wave
Gibberish.init();
a = new Gibberish.Sine2(880, .5, -.25).connect();`
- - - -
**/
/**###Gibberish.Sine2.frequency : property
Number. From 20 - 20000 hz.
**/
/**###Gibberish.Sine2.amp : property
Number. A linear value specifying relative amplitude, ostensibly from 0..1 but can be higher, or lower when used for modulation.
**/
/**###Gibberish.Sine2.pan : property
Number. -1..1. The position of the sinewave in the stereo spectrum
**/
Gibberish.Sine2 = function() {
this.__proto__ = new Gibberish.Sine();
this.name = "sine2";
var sine = this.__proto__.callback,
panner = Gibberish.makePanner(),
output = [0,0];
/**###Gibberish.Sine2.callback : method
Returns a stereo sample of output as an array.
param **frequency** Number. The frequency to be used to calculate output.
param **amp** Number. The amplitude to be used to calculate output.
param **pan** Number. The position in the stereo spectrum of the signal.
**/
this.callback = function(frequency, amp, pan) {
var out = sine(frequency, amp);
output = panner(out, pan, output);
return output;
}
this.init();
this.oscillatorInit();
Gibberish.defineUgenProp