UNPKG

pd-fileutils

Version:

A set of utilities for Pure Data files : parser, image generator.

1,479 lines (1,218 loc) 125 kB
/* Copyright 2013 Chris Wilson Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ /* This monkeypatch library is intended to be included in projects that are written to the proper AudioContext spec (instead of webkitAudioContext), and that use the new naming and proper bits of the Web Audio API (e.g. using BufferSourceNode.start() instead of BufferSourceNode.noteOn()), but may have to run on systems that only support the deprecated bits. This library should be harmless to include if the browser supports unprefixed "AudioContext", and/or if it supports the new names. The patches this library handles: if window.AudioContext is unsupported, it will be aliased to webkitAudioContext(). if AudioBufferSourceNode.start() is unimplemented, it will be routed to noteOn() or noteGrainOn(), depending on parameters. The following aliases only take effect if the new names are not already in place: AudioBufferSourceNode.stop() is aliased to noteOff() AudioContext.createGain() is aliased to createGainNode() AudioContext.createDelay() is aliased to createDelayNode() AudioContext.createScriptProcessor() is aliased to createJavaScriptNode() AudioContext.createPeriodicWave() is aliased to createWaveTable() OscillatorNode.start() is aliased to noteOn() OscillatorNode.stop() is aliased to noteOff() OscillatorNode.setPeriodicWave() is aliased to setWaveTable() AudioParam.setTargetAtTime() is aliased to setTargetValueAtTime() This library does NOT patch the enumerated type changes, as it is recommended in the specification that implementations support both integer and string types for AudioPannerNode.panningModel, AudioPannerNode.distanceModel BiquadFilterNode.type and OscillatorNode.type. */ (function (global, exports, perf) { 'use strict'; function fixSetTarget(param) { if (!param) // if NYI, just return return; if (!param.setTargetAtTime) param.setTargetAtTime = param.setTargetValueAtTime; } if (window.hasOwnProperty('webkitAudioContext') && !window.hasOwnProperty('AudioContext')) { window.AudioContext = webkitAudioContext; if (!AudioContext.prototype.hasOwnProperty('createGain')) AudioContext.prototype.createGain = AudioContext.prototype.createGainNode; if (!AudioContext.prototype.hasOwnProperty('createDelay')) AudioContext.prototype.createDelay = AudioContext.prototype.createDelayNode; if (!AudioContext.prototype.hasOwnProperty('createScriptProcessor')) AudioContext.prototype.createScriptProcessor = AudioContext.prototype.createJavaScriptNode; if (!AudioContext.prototype.hasOwnProperty('createPeriodicWave')) AudioContext.prototype.createPeriodicWave = AudioContext.prototype.createWaveTable; AudioContext.prototype.internal_createGain = AudioContext.prototype.createGain; AudioContext.prototype.createGain = function() { var node = this.internal_createGain(); fixSetTarget(node.gain); return node; }; AudioContext.prototype.internal_createDelay = AudioContext.prototype.createDelay; AudioContext.prototype.createDelay = function(maxDelayTime) { var node = maxDelayTime ? this.internal_createDelay(maxDelayTime) : this.internal_createDelay(); fixSetTarget(node.delayTime); return node; }; AudioContext.prototype.internal_createBufferSource = AudioContext.prototype.createBufferSource; AudioContext.prototype.createBufferSource = function() { var node = this.internal_createBufferSource(); if (!node.start) { node.start = function ( when, offset, duration ) { if ( offset || duration ) this.noteGrainOn( when, offset, duration ); else this.noteOn( when ); } } if (!node.stop) node.stop = node.noteOff; fixSetTarget(node.playbackRate); return node; }; AudioContext.prototype.internal_createDynamicsCompressor = AudioContext.prototype.createDynamicsCompressor; AudioContext.prototype.createDynamicsCompressor = function() { var node = this.internal_createDynamicsCompressor(); fixSetTarget(node.threshold); fixSetTarget(node.knee); fixSetTarget(node.ratio); fixSetTarget(node.reduction); fixSetTarget(node.attack); fixSetTarget(node.release); return node; }; AudioContext.prototype.internal_createBiquadFilter = AudioContext.prototype.createBiquadFilter; AudioContext.prototype.createBiquadFilter = function() { var node = this.internal_createBiquadFilter(); fixSetTarget(node.frequency); fixSetTarget(node.detune); fixSetTarget(node.Q); fixSetTarget(node.gain); return node; }; if (AudioContext.prototype.hasOwnProperty( 'createOscillator' )) { AudioContext.prototype.internal_createOscillator = AudioContext.prototype.createOscillator; AudioContext.prototype.createOscillator = function() { var node = this.internal_createOscillator(); if (!node.start) node.start = node.noteOn; if (!node.stop) node.stop = node.noteOff; if (!node.setPeriodicWave) node.setPeriodicWave = node.setWaveTable; fixSetTarget(node.frequency); fixSetTarget(node.detune); return node; }; } } }(window)); ;!function(exports, undefined) { var isArray = Array.isArray ? Array.isArray : function _isArray(obj) { return Object.prototype.toString.call(obj) === "[object Array]"; }; var defaultMaxListeners = 10; function init() { this._events = new Object; } function configure(conf) { if (conf) { conf.delimiter && (this.delimiter = conf.delimiter); conf.maxListeners && (this._events.maxListeners = conf.maxListeners); conf.wildcard && (this.wildcard = conf.wildcard); if (this.wildcard) { this.listenerTree = new Object; } } } function EventEmitter(conf) { this._events = new Object; configure.call(this, conf); } // // Attention, function return type now is array, always ! // It has zero elements if no any matches found and one or more // elements (leafs) if there are matches // function searchListenerTree(handlers, type, tree, i) { if (!tree) { return []; } var listeners=[], leaf, len, branch, xTree, xxTree, isolatedBranch, endReached, typeLength = type.length, currentType = type[i], nextType = type[i+1]; if (i === typeLength && tree._listeners) { // // If at the end of the event(s) list and the tree has listeners // invoke those listeners. // if (typeof tree._listeners === 'function') { handlers && handlers.push(tree._listeners); return [tree]; } else { for (leaf = 0, len = tree._listeners.length; leaf < len; leaf++) { handlers && handlers.push(tree._listeners[leaf]); } return [tree]; } } if ((currentType === '*' || currentType === '**') || tree[currentType]) { // // If the event emitted is '*' at this part // or there is a concrete match at this patch // if (currentType === '*') { for (branch in tree) { if (branch !== '_listeners' && tree.hasOwnProperty(branch)) { listeners = listeners.concat(searchListenerTree(handlers, type, tree[branch], i+1)); } } return listeners; } else if(currentType === '**') { endReached = (i+1 === typeLength || (i+2 === typeLength && nextType === '*')); if(endReached && tree._listeners) { // The next element has a _listeners, add it to the handlers. listeners = listeners.concat(searchListenerTree(handlers, type, tree, typeLength)); } for (branch in tree) { if (branch !== '_listeners' && tree.hasOwnProperty(branch)) { if(branch === '*' || branch === '**') { if(tree[branch]._listeners && !endReached) { listeners = listeners.concat(searchListenerTree(handlers, type, tree[branch], typeLength)); } listeners = listeners.concat(searchListenerTree(handlers, type, tree[branch], i)); } else if(branch === nextType) { listeners = listeners.concat(searchListenerTree(handlers, type, tree[branch], i+2)); } else { // No match on this one, shift into the tree but not in the type array. listeners = listeners.concat(searchListenerTree(handlers, type, tree[branch], i)); } } } return listeners; } listeners = listeners.concat(searchListenerTree(handlers, type, tree[currentType], i+1)); } xTree = tree['*']; if (xTree) { // // If the listener tree will allow any match for this part, // then recursively explore all branches of the tree // searchListenerTree(handlers, type, xTree, i+1); } xxTree = tree['**']; if(xxTree) { if(i < typeLength) { if(xxTree._listeners) { // If we have a listener on a '**', it will catch all, so add its handler. searchListenerTree(handlers, type, xxTree, typeLength); } // Build arrays of matching next branches and others. for(branch in xxTree) { if(branch !== '_listeners' && xxTree.hasOwnProperty(branch)) { if(branch === nextType) { // We know the next element will match, so jump twice. searchListenerTree(handlers, type, xxTree[branch], i+2); } else if(branch === currentType) { // Current node matches, move into the tree. searchListenerTree(handlers, type, xxTree[branch], i+1); } else { isolatedBranch = {}; isolatedBranch[branch] = xxTree[branch]; searchListenerTree(handlers, type, { '**': isolatedBranch }, i+1); } } } } else if(xxTree._listeners) { // We have reached the end and still on a '**' searchListenerTree(handlers, type, xxTree, typeLength); } else if(xxTree['*'] && xxTree['*']._listeners) { searchListenerTree(handlers, type, xxTree['*'], typeLength); } } return listeners; } function growListenerTree(type, listener) { type = typeof type === 'string' ? type.split(this.delimiter) : type.slice(); // // Looks for two consecutive '**', if so, don't add the event at all. // for(var i = 0, len = type.length; i+1 < len; i++) { if(type[i] === '**' && type[i+1] === '**') { return; } } var tree = this.listenerTree; var name = type.shift(); while (name) { if (!tree[name]) { tree[name] = new Object; } tree = tree[name]; if (type.length === 0) { if (!tree._listeners) { tree._listeners = listener; } else if(typeof tree._listeners === 'function') { tree._listeners = [tree._listeners, listener]; } else if (isArray(tree._listeners)) { tree._listeners.push(listener); if (!tree._listeners.warned) { var m = defaultMaxListeners; if (typeof this._events.maxListeners !== 'undefined') { m = this._events.maxListeners; } if (m > 0 && tree._listeners.length > m) { tree._listeners.warned = true; console.error('(node) warning: possible EventEmitter memory ' + 'leak detected. %d listeners added. ' + 'Use emitter.setMaxListeners() to increase limit.', tree._listeners.length); console.trace(); } } } return true; } name = type.shift(); } return true; }; // By default EventEmitters will print a warning if more than // 10 listeners are added to it. This is a useful default which // helps finding memory leaks. // // Obviously not all Emitters should be limited to 10. This function allows // that to be increased. Set to zero for unlimited. EventEmitter.prototype.delimiter = '.'; EventEmitter.prototype.setMaxListeners = function(n) { this._events || init.call(this); this._events.maxListeners = n; }; EventEmitter.prototype.event = ''; EventEmitter.prototype.once = function(event, fn) { this.many(event, 1, fn); return this; }; EventEmitter.prototype.many = function(event, ttl, fn) { var self = this; if (typeof fn !== 'function') { throw new Error('many only accepts instances of Function'); } function listener() { if (--ttl === 0) { self.off(event, listener); } fn.apply(this, arguments); }; listener._origin = fn; this.on(event, listener); return self; }; EventEmitter.prototype.emit = function() { this._events || init.call(this); var type = arguments[0]; if (type === 'newListener') { if (!this._events.newListener) { return false; } } // Loop through the *_all* functions and invoke them. if (this._all) { var l = arguments.length; var args = new Array(l - 1); for (var i = 1; i < l; i++) args[i - 1] = arguments[i]; for (i = 0, l = this._all.length; i < l; i++) { this.event = type; this._all[i].apply(this, args); } } // If there is no 'error' event listener then throw. if (type === 'error') { if (!this._all && !this._events.error && !(this.wildcard && this.listenerTree.error)) { if (arguments[1] instanceof Error) { throw arguments[1]; // Unhandled 'error' event } else { throw new Error("Uncaught, unspecified 'error' event."); } return false; } } var handler; if(this.wildcard) { handler = []; var ns = typeof type === 'string' ? type.split(this.delimiter) : type.slice(); searchListenerTree.call(this, handler, ns, this.listenerTree, 0); } else { handler = this._events[type]; } if (typeof handler === 'function') { this.event = type; if (arguments.length === 1) { handler.call(this); } else if (arguments.length > 1) switch (arguments.length) { case 2: handler.call(this, arguments[1]); break; case 3: handler.call(this, arguments[1], arguments[2]); break; // slower default: var l = arguments.length; var args = new Array(l - 1); for (var i = 1; i < l; i++) args[i - 1] = arguments[i]; handler.apply(this, args); } return true; } else if (handler) { var l = arguments.length; var args = new Array(l - 1); for (var i = 1; i < l; i++) args[i - 1] = arguments[i]; var listeners = handler.slice(); for (var i = 0, l = listeners.length; i < l; i++) { this.event = type; listeners[i].apply(this, args); } return (listeners.length > 0) || this._all; } else { return this._all; } }; EventEmitter.prototype.on = function(type, listener) { if (typeof type === 'function') { this.onAny(type); return this; } if (typeof listener !== 'function') { throw new Error('on only accepts instances of Function'); } this._events || init.call(this); // To avoid recursion in the case that type == "newListeners"! Before // adding it to the listeners, first emit "newListeners". this.emit('newListener', type, listener); if(this.wildcard) { growListenerTree.call(this, type, listener); return this; } if (!this._events[type]) { // Optimize the case of one listener. Don't need the extra array object. this._events[type] = listener; } else if(typeof this._events[type] === 'function') { // Adding the second element, need to change to array. this._events[type] = [this._events[type], listener]; } else if (isArray(this._events[type])) { // If we've already got an array, just append. this._events[type].push(listener); // Check for listener leak if (!this._events[type].warned) { var m = defaultMaxListeners; if (typeof this._events.maxListeners !== 'undefined') { m = this._events.maxListeners; } if (m > 0 && this._events[type].length > m) { this._events[type].warned = true; console.error('(node) warning: possible EventEmitter memory ' + 'leak detected. %d listeners added. ' + 'Use emitter.setMaxListeners() to increase limit.', this._events[type].length); console.trace(); } } } return this; }; EventEmitter.prototype.onAny = function(fn) { if(!this._all) { this._all = []; } if (typeof fn !== 'function') { throw new Error('onAny only accepts instances of Function'); } // Add the function to the event listener collection. this._all.push(fn); return this; }; EventEmitter.prototype.addListener = EventEmitter.prototype.on; EventEmitter.prototype.off = function(type, listener) { if (typeof listener !== 'function') { throw new Error('removeListener only takes instances of Function'); } var handlers,leafs=[]; if(this.wildcard) { var ns = typeof type === 'string' ? type.split(this.delimiter) : type.slice(); leafs = searchListenerTree.call(this, null, ns, this.listenerTree, 0); } else { // does not use listeners(), so no side effect of creating _events[type] if (!this._events[type]) return this; handlers = this._events[type]; leafs.push({_listeners:handlers}); } for (var iLeaf=0; iLeaf<leafs.length; iLeaf++) { var leaf = leafs[iLeaf]; handlers = leaf._listeners; if (isArray(handlers)) { var position = -1; for (var i = 0, length = handlers.length; i < length; i++) { if (handlers[i] === listener || (handlers[i].listener && handlers[i].listener === listener) || (handlers[i]._origin && handlers[i]._origin === listener)) { position = i; break; } } if (position < 0) { return this; } if(this.wildcard) { leaf._listeners.splice(position, 1) } else { this._events[type].splice(position, 1); } if (handlers.length === 0) { if(this.wildcard) { delete leaf._listeners; } else { delete this._events[type]; } } } else if (handlers === listener || (handlers.listener && handlers.listener === listener) || (handlers._origin && handlers._origin === listener)) { if(this.wildcard) { delete leaf._listeners; } else { delete this._events[type]; } } } return this; }; EventEmitter.prototype.offAny = function(fn) { var i = 0, l = 0, fns; if (fn && this._all && this._all.length > 0) { fns = this._all; for(i = 0, l = fns.length; i < l; i++) { if(fn === fns[i]) { fns.splice(i, 1); return this; } } } else { this._all = []; } return this; }; EventEmitter.prototype.removeListener = EventEmitter.prototype.off; EventEmitter.prototype.removeAllListeners = function(type) { if (arguments.length === 0) { !this._events || init.call(this); return this; } if(this.wildcard) { var ns = typeof type === 'string' ? type.split(this.delimiter) : type.slice(); var leafs = searchListenerTree.call(this, null, ns, this.listenerTree, 0); for (var iLeaf=0; iLeaf<leafs.length; iLeaf++) { var leaf = leafs[iLeaf]; leaf._listeners = null; } } else { if (!this._events[type]) return this; this._events[type] = null; } return this; }; EventEmitter.prototype.listeners = function(type) { if(this.wildcard) { var handlers = []; var ns = typeof type === 'string' ? type.split(this.delimiter) : type.slice(); searchListenerTree.call(this, handlers, ns, this.listenerTree, 0); return handlers; } this._events || init.call(this); if (!this._events[type]) this._events[type] = []; if (!isArray(this._events[type])) { this._events[type] = [this._events[type]]; } return this._events[type]; }; EventEmitter.prototype.listenersAny = function() { if(this._all) { return this._all; } else { return []; } }; exports.EventEmitter2 = EventEmitter; }(window); /* * Copyright (c) 2011-2013 Chris McCormick, Sébastien Piquemal <sebpiq@gmail.com> * * This file is part of WebPd. See https://github.com/sebpiq/WebPd for documentation * * WebPd is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * WebPd is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with WebPd. If not, see <http://www.gnu.org/licenses/>. * */ (function(){ // EventEmitter2 offers the same API as node's EventEmitter this.EventEmitter = EventEmitter2; var Pd = this.Pd = { // Default sample rate to use for the patches. Beware, if the browser doesn't // support this sample rate, the actual sample rate of a patch might be different. sampleRate: 44100, // Default block size to use for patches. blockSize: 1024, // The number of audio channels on the output channelCount: 2, debugMode: false, // Array type to use. If the browser support Float arrays, this will be Float array type. arrayType: Array, // Array slice function, to unify slicing float arrays and normal arrays. arraySlice: function (array, start, end) { return array.slice(start, end); } }; // use a Float32Array if we have it if (typeof Float32Array !== "undefined") { Pd.arrayType = Float32Array; Pd.arraySlice = function (array, start, end) { return array.subarray(start, end); }; } // Returns true if the current browser supports WebPd, false otherwise. Pd.isSupported = function() { // Web audio API - Chrome, Safari var test = typeof window === 'undefined' ? null : window.webkitAudioContext || window.AudioContext; if (test) return true; // All the rest return false; }; // Every new patch and object registers itself using this function. // Named objects are stored, so that they can be found with // `Pd.getNamedObject` and `Pd.getUniquelyNamedObject`. // TODO: destroy object or patch, clean references Pd.register = function(obj) { if (obj.type === 'abstract') return; if (obj instanceof Pd.Patch) { if (this._patches.indexOf(obj) === -1) { this._patches.push(obj); obj.id = this._generateId(); } // For normal named objects, we just find the right entry in the map, // and add the object to an array of objects with the same name. } else if (obj instanceof Pd.NamedObject) { var storeNamedObject = function(oldName, newName) { var objType = obj.type, nameMap = Pd._namedObjects[obj.type], objList; if (!nameMap) nameMap = Pd._namedObjects[objType] = {}; objList = nameMap[newName]; if (!objList) objList = nameMap[newName] = []; if (objList.indexOf(obj) === -1) objList.push(obj); // Removing old mapping if (oldName) { objList = nameMap[oldName]; objList.splice(objList.indexOf(obj), 1); } }; obj.on('change:name', storeNamedObject); storeNamedObject(null, obj.name); // For uniquely named objects, we add directly the object to the corresponding // entry in the map (no arrays there). } else if (obj instanceof Pd.UniquelyNamedObject) { var storeNamedObject = function(oldName, newName) { var objType = obj.type, nameMap = Pd._uniquelyNamedObjects[obj.type], objList; if (!nameMap) nameMap = Pd._uniquelyNamedObjects[objType] = {}; if (nameMap.hasOwnProperty(newName) && nameMap[newName] !== obj) throw new Error('there is already an object with name "' + newName + '"'); nameMap[newName] = obj; // Removing old mapping if (oldName) nameMap[oldName] = undefined; }; obj.on('change:name', storeNamedObject); storeNamedObject(null, obj.name); } }; Pd._patches = []; Pd._namedObjects = {}; Pd._uniquelyNamedObjects = {}; // Returns an object list given the object `type` and `name`. Pd.getNamedObject = function(type, name) { return ((this._namedObjects[type] || {})[name] || []); }; // Returns an object given the object `type` and `name`, or `null` if this object doesn't exist. Pd.getUniquelyNamedObject = function(type, name) { return ((this._uniquelyNamedObjects[type] || {})[name] || null); }; // Returns true if an object is an array, false otherwise. Pd.isArray = Array.isArray || function(obj) { return toString.call(obj) === '[object Array]'; }; // Returns true if an object is a number, false otherwise. // If `val` is NaN, the function returns false. Pd.isNumber = function(val) { return typeof val === 'number' && !isNaN(val); }; // Returns true if an object is a string, false otherwise. Pd.isString = function(val) { return typeof val === 'string'; }; // Returns true if an object is a function, false otherwise. // TODO: function vs [object Function] ? Pd.isFunction = function(obj) { return typeof obj === 'function'; }; // Simple prototype inheritance. Used like so : // // var ChildObject = function() {}; // // Pd.extend(ChildObject.prototype, ParentObject.prototype, { // // anOverridenMethod: function() { // ParentObject.prototype.anOverridenMethod.apply(this, arguments); // // do more stuff ... // }, // // aNewMethod: function() { // // do stuff ... // } // // }); Pd.extend = function(obj) { var sources = Array.prototype.slice.call(arguments, 1), i, length, source, prop; for (i = 0, length = sources.length; i < length; i++) { source = sources[i]; for (prop in source) { obj[prop] = source[prop]; } } return obj; }; Pd.chainExtend = function() { var sources = Array.prototype.slice.call(arguments, 0), parent = this, child = function() { parent.apply(this, arguments); }; // Fix instanceof child.prototype = new parent(); // extend with new properties Pd.extend.apply(this, [child.prototype, parent.prototype].concat(sources)); child.extend = this.extend; return child; }; // Simple mixin to add functionalities for generating unique ids. // Each prototype inheriting from this mixin has a separate id counter. // Therefore ids are not unique globally but unique for each prototype. Pd.UniqueIdsBase = { // Every time it is called, this method returns a new unique id. _generateId: function() { this._idCounter++; return this._idCounter; }, // Counter used internally to assign a unique id to objects // this counter should never be decremented to ensure the id unicity _idCounter: -1 }; Pd.extend(Pd, Pd.UniqueIdsBase); // Returns a function `transfer(msg)`, that takes a message array as input, and constructs // the output message. For example : // // transfer = Pd.makeMsgTransfer([56, '$1', 'bla', '$2-$1']); // transfer([89, 'bli']); // [56, 89, 'bla', 'bli-89'] // Pd.makeMsgTransfer = function(rawOutArray) { var transfer = [], i, length, rawOutVal, matchDollar, func; rawOutArray = rawOutArray.slice(0); // Creates an array of transfer functions `inVal -> outVal`. for (i = 0, length = rawOutArray.length; i < length; i++) { rawOutVal = rawOutArray[i]; matchDollar = dollarVarRe.exec(rawOutVal); // If the transfer is a dollar var : // ['bla', 789] - ['$1'] -> ['bla'] // ['bla', 789] - ['$2'] -> [789] if (matchDollar && matchDollar[0] === rawOutVal) { transfer.push( (function(rawOutVal) { var inInd = parseInt(matchDollar[1], 10) - 1; // -1, because $1 corresponds to value 0. return function(inArray) { if (inInd >= inArray.length || inInd < 0 ) throw new Error('$' + (inInd + 1) + ': argument number out of range'); return inArray[inInd]; }; })(rawOutVal) ); // If the transfer is a string containing dollar var : // ['bla', 789] - ['bla$2'] -> ['bla789'] } else if (matchDollar) { transfer.push( (function(rawOutVal) { var j, matched, dollarVars = [], inInd; while (matched = dollarVarReGlob.exec(rawOutVal)) { dollarVars.push([matched[0], parseInt(matched[1], 10) - 1]); // -1, because $1 corresponds to value 0. } return function(inArray) { var outVal = rawOutVal.substr(0); for (j = 0; matched = dollarVars[j]; j++) { inInd = matched[1]; if (inInd >= inArray.length || inInd < 0 ) throw new Error('$' + (inInd + 1) + ': argument number out of range'); outVal = outVal.replace(matched[0], inArray[inInd]); } return outVal; }; })(rawOutVal) ); // Else the input doesn't matter } else { transfer.push( (function(outVal) { return function() { return outVal; }; })(rawOutVal) ); } } return function(inArray) { var outArray = []; for (i = 0; func = transfer[i]; i++) outArray[i] = func(inArray); return outArray; }; }; // Takes a list of object arguments which might contain abbreviations, and returns // a copy of that list, abbreviations replaced by the corresponding full word. // TODO: patch, $1, $2, ... // TODO: doesn't this belong to compat instead ? Pd.resolveArgs = function(args, patch) { var i, length, arg, matchDollar, cleaned = args.slice(0), patchInd, patchArgs = (patch) ? [patch.id] : []; for (i = 0, length = args.length; i < length; i++) { arg = args[i]; if (arg === 'b') cleaned[i] = 'bang'; else if (arg === 'f') cleaned[i] = 'float'; else if (arg === 's') cleaned[i] = 'symbol'; else if (arg === 'a') cleaned[i] = 'anything'; else if (arg === 'l') cleaned[i] = 'list'; else if (matchDollar = dollarVarRe.exec(arg)) { // If the transfer is a dollar var : // ['bla', 789] - ['$1'] -> ['bla'] // ['bla', 789] - ['$2'] -> [789] if (matchDollar[0] === arg) { patchInd = parseInt(matchDollar[1], 10); if (patchInd >= patchArgs.length || patchInd < 0 ) throw new Error('$' + patchInd + ': argument number out of range'); cleaned[i] = patchArgs[patchInd]; // If the transfer is a string containing dollar var : // ['bla', 789] - ['bla$2'] -> ['bla789'] } else { while (matchDollar = dollarVarReGlob.exec(arg)) { patchInd = parseInt(matchDollar[1], 10); if (patchInd >= patchArgs.length || patchInd < 0 ) throw new Error('$' + patchInd + ': argument number out of range'); arg = arg.replace(matchDollar[0], patchArgs[patchInd]); } cleaned[i] = arg; } } } return cleaned; }; // Regular expressions to deal with dollar-args var dollarVarRe = /\$(\d+)/, dollarVarReGlob = /\$(\d+)/g; // Fills array with zeros Pd.fillWithZeros = function(array, start) { var i, length, start = start !== undefined ? start : 0; for (i = start, length = array.length; i < length; i++) { array[i] = 0; } return array; }; // Returns a brand, new, clean, buffer Pd.newBuffer = function(channels) { if (channels === undefined) channels = 1; return new Pd.arrayType(Pd.blockSize * channels); }; Pd.notImplemented = function() { throw new Error('Not implemented !'); }; }).call(this); /* * Copyright (c) 2011-2013 Chris McCormick, Sébastien Piquemal <sebpiq@gmail.com> * * This file is part of WebPd. See https://github.com/sebpiq/WebPd for documentation * * WebPd is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * WebPd is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with WebPd. If not, see <http://www.gnu.org/licenses/>. * */ (function(Pd){ var AudioDriverInterface = function(desiredSampleRate, blockSize) { // what sample rate we will operate at (might change depending on driver so use getSampleRate() this._sampleRate = desiredSampleRate; this._blockSize = blockSize; this._channelCount = 2; }; Pd.extend(AudioDriverInterface.prototype, { // fetch the current sample rate we are operating at getSampleRate: function() { Pd.notImplemented(); }, // Stop the audio from playing stop: function() { Pd.notImplemented(); }, // Start the audio playing with the supplied function as the audio-block generator play: function(generator) { Pd.notImplemented(); }, // test whether this driver is currently playing audio isPlaying: function() { Pd.notImplemented(); } }); var WAAAdapter = function(desiredSampleRate, blockSize) { AudioDriverInterface.prototype.constructor.apply(this, arguments); if (_audioContext === null) _audioContext = new AudioContext; this._blockSize = blockSize; }; var _audioContext = null; Pd.extend(WAAAdapter.prototype, AudioDriverInterface.prototype, { // fetch the current sample rate we are operating at getSampleRate: function() { return _audioContext.sampleRate; }, // Stop the audio from playing stop: function() { this._playing = false; this._scriptNode.disconnect(); this._scriptNode = null; }, // Start the audio playing with the supplied function as the audio-block generator play: function(generator) { var self = this this._scriptNode = _audioContext.createScriptProcessor(this._blockSize, 1, this._channelCount); this._playing = true; this._scriptNode.onaudioprocess = function(event) { var outputBuffer = event.outputBuffer , ch, block = generator(); for (ch = 0; ch < self._channelCount; ch++) outputBuffer.getChannelData(ch).set(block[ch]); } this._scriptNode.connect(_audioContext.destination); }, // test whether this driver is currently playing audio isPlaying: function() { return this._playing; } }); Pd.AudioDriver = WAAAdapter; })(this.Pd); /* * Copyright (c) 2011-2013 Chris McCormick, Sébastien Piquemal <sebpiq@gmail.com> * * This file is part of WebPd. See https://github.com/sebpiq/WebPd for documentation * * WebPd is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * WebPd is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with WebPd. If not, see <http://www.gnu.org/licenses/>. * */ (function(Pd){ var BasePortlet = function(obj, id) { this.obj = obj; this.id = id; this.init(); }; Pd.extend(BasePortlet.prototype, { init: function() {}, connect: function(other) { Pd.notImplemented(); }, disconnect: function(other) { Pd.notImplemented(); }, // Generic function for connecting the calling portlet // with `otherPortlet`. _genericConnect: function(allConn, otherPortlet) { if (allConn.indexOf(otherPortlet) !== -1) return; allConn.push(otherPortlet); otherPortlet.connect(this); }, // Generic function for disconnecting the calling portlet // from `otherPortlet`. _genericDisconnect: function(allConn, otherPortlet) { var connInd = allConn.indexOf(otherPortlet); if (connInd === -1) return; allConn.splice(connInd, 1); otherPortlet.disconnect(this); } }); BasePortlet.extend = Pd.chainExtend; var BaseInlet = BasePortlet.extend({ init: function() { this.sources = []; }, // Connects the inlet to the outlet `source`. // If the connection already exists, nothing happens. connect: function(source) { this._genericConnect(this.sources, source); this.obj.emit('inletConnect'); }, // Disconnects the inlet from the outlet `source`. // If the connection didn't exist, nothing happens. disconnect: function(source) { this._genericDisconnect(this.sources, source); this.obj.emit('inletDisconnect'); }, // message received callback message: function() { this.obj.message.apply(this.obj, [this.id].concat(Array.prototype.slice.call(arguments))); }, // Returns a buffer to read dsp data from. getBuffer: function() { Pd.notImplemented(); }, // Returns true if the inlet has dsp sources, false otherwise hasDspSources: function() { Pd.notImplemented(); } }); var BaseOutlet = BasePortlet.extend({ init: function() { this.sinks = []; }, // Connects the outlet to the inlet `sink`. // If the connection already exists, nothing happens. connect: function(sink) { this._genericConnect(this.sinks, sink); }, // Disconnects the outlet from the inlet `sink`. // If the connection didn't exist, nothing happens. disconnect: function(sink) { this._genericDisconnect(this.sinks, sink); }, // Returns a buffer to write dsp data to. getBuffer: function() { Pd.notImplemented(); }, // Sends a message to all sinks message: function() { Pd.notImplemented(); } }); // message inlet. Simply receives messages and dispatches them to // the inlet's object. Pd['inlet'] = BaseInlet.extend({ getBuffer: function() { throw (new Error ('No dsp buffer on a message inlet')); }, hasDspSources: function() { throw (new Error ('A message inlet cannot have dsp sources')); } }); // dsp inlet. Pulls dsp data from all sources. Also accepts messages. Pd['inlet~'] = BaseInlet.extend({ init: function() { BaseInlet.prototype.init.apply(this, arguments); this.dspSources = []; this._buffer = Pd.newBuffer(); this._zerosBuffer = Pd.newBuffer(); Pd.fillWithZeros(this._zerosBuffer); }, getBuffer: function() { var dspSources = this.dspSources; // if more than one dsp source, we have to sum the signals. if (dspSources.length > 1) { var buffer = this._buffer, sourceBuff, i, j, len1, len2; Pd.fillWithZeros(buffer); for (i = 0, len1 = dspSources.length; i < len1; i++) { sourceBuff = dspSources[i].getBuffer(); for (j = 0, len2 = buffer.length; j < len2; j++) { buffer[j] += sourceBuff[j]; } } return buffer; // if only one dsp source, we can pass the signal as is. } else if (dspSources.length === 1) { return dspSources[0].getBuffer(); // if no dsp source, just pass some zeros } else { return this._zerosBuffer; } }, connect: function(source) { if (source instanceof Pd['outlet~']) this.dspSources.push(source); BaseInlet.prototype.connect.apply(this, arguments); }, disconnect: function(source) { var ind = this.dspSources.indexOf(source); if (ind !== -1) this.dspSources.splice(ind, 1); BaseInlet.prototype.disconnect.apply(this, arguments); }, hasDspSources: function() { return this.dspSources.length > 0; } }); // message outlet. Dispatches messages to all the sinks Pd['outlet'] = BaseOutlet.extend({ getBuffer: function() { throw (new Error ('No dsp buffer on a message outlet')); }, message: function() { var sinks = this.sinks, sink, i, length; for (i = 0, length = sinks.length; i < length; i++) { sink = sinks[i]; sink.message.apply(sink, arguments); } } }); // dsp outlet. Only contains a buffer, written to by the outlet's object. Pd['outlet~'] = BaseOutlet.extend({ init: function() { BaseOutlet.prototype.init.apply(this, arguments); this._buffer = Pd.newBuffer(); }, getBuffer: function() { return this._buffer; }, message: function() { throw (new Error ('message received on dsp outlet, pas bon')); } }); })(this.Pd); /* * Copyright (c) 2011-2013 Chris McCormick, Sébastien Piquemal <sebpiq@gmail.com> * * This file is part of WebPd. See https://github.com/sebpiq/WebPd for documentation * * WebPd is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * WebPd is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with WebPd. If not, see <http://www.gnu.org/licenses/>. * */ (function(Pd){ /******************** Base Object *****************/ Pd.Object = function (patch, args) { // Base attributes. // `frame` corresponds to the last frame that was run. this.patch = (patch || null); this.id = null; this.frame = -1; // Attributes mostly used for compatibility and GUIs. this._args = args = args || []; this._guiData = {}; // create inlets and outlets specified in the object's proto this.inlets = []; this.outlets = []; var outletTypes = this.outletTypes, inletTypes = this.inletTypes, i, length; for (i = 0, length = outletTypes.length; i < length; i++) { this.outlets[i] = new Pd[outletTypes[i]](this, i); } for (i = 0, length = inletTypes.length; i < length; i++) { this.inlets[i] = new Pd[inletTypes[i]](this, i); } // initializes the object, handling the creation arguments if (this.resolveArgs) args = Pd.resolveArgs(args, patch); this.init.apply(this, args); if (this.type !== 'abstract') { Pd.register(this); if (patch) patch.addObject(this); } }; Pd.extend(Pd.Object.prototype, EventEmitter.prototype, Pd.UniqueIdsBase, { // This is used to choose in which order objects must be loaded, when the patch // is started. For example [loadbang] must go last. Higher priorities go first. loadPriority: 0, // set to true if this object is a dsp sink (e.g. [dac~], [outlet~], [print~] endPoint: false, // if the object is an endpoint, this is used to choose in which order endpoints // dsp is run. Higher priorities go first. endPointPriority: 0, // 'outlet' / 'outlet~' outletTypes: [], // 'inlet' / 'inlet~' inletTypes: [], // Type of the object. If type is 'abstract' `Pd.register` ignores the object. type: 'abstract', // List of available abbreviations for that object. abbreviations: undefined, // If this is true, `Pd.resolveArgs` is applied to the object's arguments. resolveArgs: true, // Returns inlet `id` if it exists. i: function(id) { if (id < this.inlets.length) return this.inlets[id]; else throw (new Error('invalid inlet ' + id)); }, // Returns outlet `id` if it exists. o: function(id) { if (id < this.outlets.length) return this.outlets[id]; else throw (new Error('invalid outlet ' + id)); }, /******************** Methods to implement *****************/ // This method is called when the object is created. // At this stage, the object can belong to a patch or not. init: function() {}, // This method is called by the patch when it starts playing. load: function() {}, // method run every frame for this object dspTick: function() {}, // method run when this object receives a message at any inlet message: function(inletnumber, message) {}, /********************** Helper methods *********************/ assertIsNumber: function(val, errorMsg) { if (!Pd.isNumber(val)) throw (new Error(errorMsg)); }, assertIsArray: function(val, errorMsg) { if (!Pd.isArray(val)) throw (new Error(errorMsg)); }, assertIsString: function(val, errorMsg) { if (!Pd.isString(val)) throw (new Error(errorMsg)); }, assertIsBang: function(val, errorMsg) { if (val !== 'bang') throw (new Error(errorMsg)); }, /******************** Basic dspTicks ************************/ dspTickNoOp: function() {