gibberish-dsp
Version:
Gibberish is designed to be an optimized API for audio synthesis using per-sample techniques.
320 lines (275 loc) • 9.89 kB
JavaScript
const genish = require( 'genish.js' ),
AWPF = require( './external/audioworklet-polyfill.js' )
module.exports = function( Gibberish ) {
let uid = 0
const utilities = {
Make: function( props ){
const name = props.name || 'Ugen' + (Math.floor( Math.random()*10000 ) )
const type = props.type || 'Ugen'
const properties = props.properties || {}
const block = `
const ugen = Object.create( Gibberish.prototypes[ '${type}' ] )
const graphfnc = ${props.constructor.toString()}
const proxy = Gibberish.factory( ugen, graphfnc(), '${name}', ${JSON.stringify(properties)} )
if( typeof props === 'object' ) Object.assign( proxy, props )
return proxy`
Gibberish[ name ] = new Function( 'props', block )
Gibberish.worklet.port.postMessage({
name,
address:'addConstructor',
constructorString:`function( Gibberish ) {
const fnc = ${Gibberish[ name ].toString()}
return fnc
}`
})
return Gibberish[ name ]
},
createContext( ctx, cb, resolve, bufferSize=2048 ) {
let AC = typeof AudioContext === 'undefined' ? webkitAudioContext : AudioContext
AWPF( window, bufferSize )
const start = () => {
if( typeof AC !== 'undefined' ) {
this.ctx = Gibberish.ctx = ctx === undefined ? new AC({ latencyHint:.025 }) : ctx
genish.gen.samplerate = this.ctx.sampleRate
genish.utilities.ctx = this.ctx
if( document && document.documentElement && 'ontouchstart' in document.documentElement ) {
window.removeEventListener( 'touchstart', start )
}else{
window.removeEventListener( 'mousedown', start )
window.removeEventListener( 'keydown', start )
}
const mySource = utilities.ctx.createBufferSource()
mySource.connect( utilities.ctx.destination )
mySource.start()
}
if( typeof cb === 'function' ) cb( resolve )
}
if( document && document.documentElement && 'ontouchstart' in document.documentElement ) {
window.addEventListener( 'touchstart', start )
}else{
window.addEventListener( 'mousedown', start )
window.addEventListener( 'keydown', start )
}
return Gibberish.ctx
},
createWorklet( resolve ) {
Gibberish.ctx.audioWorklet.addModule( Gibberish.workletPath ).then( () => {
Gibberish.worklet = new AudioWorkletNode( Gibberish.ctx, 'gibberish', { outputChannelCount:[2] } )
Gibberish.worklet.connect( Gibberish.ctx.destination )
Gibberish.worklet.port.onmessage = event => {
const callback = Gibberish.utilities.workletHandlers[ event.data.address ]
if( typeof callback === 'function' ) callback( event )
}
Gibberish.worklet.ugens = new Map()
resolve()
})
},
future( fnc, time, dict ) {
const keys = Object.keys( dict )
const code = `
const fnc = ${fnc.toString()}
const args = [${keys.map( key => typeof dict[key] === 'object' ? dict[ key ].id : `'${dict[ key]}'` ).join(',')}]
const objs = args.map( v => typeof v === 'number' ? Gibberish.processor.ugens.get(v) : v )
Gibberish.scheduler.add( ${time}, ()=> fnc( ...objs ), 1 )
`
Gibberish.worklet.port.postMessage({
address:'eval',
code
})
},
workletHandlers: {
phase( event ) {
Gibberish.phase = event.data.value
if( typeof Gibberish.onphaseupdate === 'function' ) {
Gibberish.onphaseupdate( Gibberish.phase )
}
},
__sequencer( event ) {
const message = event.data
const id = message.id
const eventName = message.name
const obj = Gibberish.worklet.ugens.get( id )
if( obj !== undefined && obj.publish !== undefined )
obj.publish( eventName, message )
},
callback( event ) {
if( typeof Gibberish.oncallback === 'function' ) {
Gibberish.oncallback( event.data.code )
}
},
get( event ) {
let name = event.data.name
let value
if( name[0] === 'Gibberish' ) {
value = Gibberish
name.shift()
}
for( let segment of name ) {
value = value[ segment ]
}
Gibberish.worklet.port.postMessage({
address:'set',
name:'Gibberish.' + name.join('.'),
value
})
},
state( event ){
const messages = event.data.messages
if( messages.length === 0 ) return
// XXX is preventProxy actually used?
Gibberish.preventProxy = true
Gibberish.proxyEnabled = false
let i = 0
while( i < messages.length ) {
const id = messages[ i ]
const propName = messages[ i + 1 ]
const valueL = messages[ i + 2 ]
const valueR = messages[ i + 3 ]
const value = valueL
const obj = Gibberish.worklet.ugens.get( id )
if( Gibberish.worklet.debug === true ) {
if( propName !== 'output' ) console.log( propName, value, id )
}
if( typeof propName !== 'string' ) continue
if( obj !== undefined && propName.indexOf('.') === -1 && propName !== 'id' ) {
if( obj[ propName ] !== undefined ) {
if( typeof obj[ propName ] !== 'function' ) {
if( propName === 'output' ) {
obj[ propName ] = [ valueL, valueR ]
}else{
obj[ propName ] = value
}
}else{
obj[ propName ]( value )
}
}else{
obj[ propName ] = value
}
}else if( obj !== undefined ) {
const propSplit = propName.split('.')
if( obj[ propSplit[ 0 ] ] !== undefined ) {
if( propSplit[1] !== undefined ) {
//console.log( obj, propSplit[0], propSplit[1], value )
if( typeof obj[ propSplit[ 0 ] ][ propSplit[ 1 ] ] !== 'function' ) {
obj[ propSplit[ 0 ] ][ propSplit[ 1 ] ] = value
}else{
obj[ propSplit[ 0 ] ][ propSplit[ 1 ] ]( value )
}
}
}else{
//console.log( 'undefined split property!', id, propSplit[0], propSplit[1], value, obj )
}
}
// XXX double check and make sure this isn't getting sent back to processornode...
// console.log( propName, value, obj )
i += propName === 'output' ? 4 : 3
}
Gibberish.preventProxy = false
Gibberish.proxyEnabled = true
}
},
createPubSub( obj ) {
const events = {}
obj.on = function( key, fcn ) {
if( typeof events[ key ] === 'undefined' ) {
events[ key ] = []
}
events[ key ].push( fcn )
return obj
}
obj.off = function( key, fcn ) {
if( typeof events[ key ] !== 'undefined' ) {
const arr = events[ key ]
arr.splice( arr.indexOf( fcn ), 1 )
}
return obj
}
obj.publish = function( key, data ) {
if( typeof events[ key ] !== 'undefined' ) {
const arr = events[ key ]
arr.forEach( v => v( data ) )
}
return obj
}
},
wrap( func, ...args ) {
const out = {
action:'wrap',
value:func,
// must return objects containing only the id number to avoid
// creating circular JSON references that would result from passing actual ugens
args: args.map( v => { return { id:v.id } })
}
return out
},
// for wrapping upvalues in a dictionary and passing function across thread
// to be reconstructed.
// ex; wrapped = fn( ()=> { return Math.random() * test }, { test:20 })
// syn.note.seq( wrapped, 1/4 )
fn( fnc, dict={}) {
const fncstr = fnc.toString()
const firstBracketIdx = fncstr.indexOf('{')
const code = fncstr.slice(firstBracketIdx+1, -1 )
const s = { requiresRender:true, filters:[], fncstr:code, args:[], dict, addFilter( f ) { this.filters.push(f) } }
return s
},
run( fnc ) {
const str = fnc.tostring()
const idx = str.indexof('=>') + 2
const code = str.slice( idx ).trim()
Gibberish.worklet.port.postMessage({
address:'eval',
code
})
},
export( obj ) {
obj.wrap = this.wrap
obj.future = this.future
obj.Make = this.Make
},
getUID() { return uid++ },
base64 : {
_keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
// will return a Uint8Array type
decodeArrayBuffer: function(input) {
var bytes = (input.length/4) * 3;
var ab = new ArrayBuffer(bytes);
this.decode(input, ab);
return ab;
},
decode: function(input, arrayBuffer) {
//get last chars to see if are valid
var lkey1 = this._keyStr.indexOf(input.charAt(input.length-1));
var lkey2 = this._keyStr.indexOf(input.charAt(input.length-2));
var bytes = (input.length/4) * 3;
if (lkey1 == 64) bytes--; //padding chars, so skip
if (lkey2 == 64) bytes--; //padding chars, so skip
var uarray;
var chr1, chr2, chr3;
var enc1, enc2, enc3, enc4;
var i = 0;
var j = 0;
if (arrayBuffer)
uarray = new Uint8Array(arrayBuffer);
else
uarray = new Uint8Array(bytes);
input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
for (i=0; i<bytes; i+=3) {
//get the 3 octects in 4 ascii chars
enc1 = this._keyStr.indexOf(input.charAt(j++));
enc2 = this._keyStr.indexOf(input.charAt(j++));
enc3 = this._keyStr.indexOf(input.charAt(j++));
enc4 = this._keyStr.indexOf(input.charAt(j++));
chr1 = (enc1 << 2) | (enc2 >> 4);
chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
chr3 = ((enc3 & 3) << 6) | enc4;
uarray[i] = chr1;
if (enc3 != 64) uarray[i+1] = chr2;
if (enc4 != 64) uarray[i+2] = chr3;
}
return uarray;
}
}
}
return utilities
}