UNPKG

genish.js

Version:
310 lines (248 loc) 8.81 kB
'use strict' /* gen.js * * low-level code generation for unit generators * */ const MemoryHelper = require( 'memory-helper' ) const EE = require( 'events' ).EventEmitter const gen = { accum:0, getUID() { return this.accum++ }, debug:false, samplerate: 44100, // change on audiocontext creation shouldLocalize: false, graph:null, globals:{ windows: {}, }, mode:'worklet', /* closures * * Functions that are included as arguments to master callback. Examples: Math.abs, Math.random etc. * XXX Should probably be renamed callbackProperties or something similar... closures are no longer used. */ closures: new Set(), params: new Set(), inputs: new Set(), parameters: new Set(), endBlock: new Set(), histories: new Map(), memo: {}, //data: {}, /* export * * place gen functions into another object for easier reference */ export( obj ) {}, addToEndBlock( v ) { this.endBlock.add( ' ' + v ) }, requestMemory( memorySpec, immutable=false ) { for( let key in memorySpec ) { let request = memorySpec[ key ] //console.log( 'requesting ' + key + ':' , JSON.stringify( request ) ) if( request.length === undefined ) { console.log( 'undefined length for:', key ) continue } request.idx = gen.memory.alloc( request.length, immutable ) } }, createMemory( amount=4096, type ) { const mem = MemoryHelper.create( amount, type ) return mem }, createCallback( ugen, mem, debug = false, shouldInlineMemory=false, memType = Float64Array ) { let isStereo = Array.isArray( ugen ) && ugen.length > 1, callback, channel1, channel2 if( typeof mem === 'number' || mem === undefined ) { this.memory = this.createMemory( mem, memType ) }else{ this.memory = mem } this.outputIdx = this.memory.alloc( 2, true ) this.emit( 'memory init' ) //console.log( 'cb memory:', mem ) this.graph = ugen this.memo = {} this.endBlock.clear() this.closures.clear() this.inputs.clear() this.params.clear() this.globals = { windows:{} } this.parameters.clear() this.functionBody = " 'use strict'\n" if( shouldInlineMemory===false ) { this.functionBody += this.mode === 'worklet' ? " var memory = this.memory\n\n" : " var memory = gen.memory\n\n" } // call .gen() on the head of the graph we are generating the callback for //console.log( 'HEAD', ugen ) for( let i = 0; i < 1 + isStereo; i++ ) { if( typeof ugen[i] === 'number' ) continue //let channel = isStereo ? ugen[i].gen() : ugen.gen(), let channel = isStereo ? this.getInput( ugen[i] ) : this.getInput( ugen ), body = '' // if .gen() returns array, add ugen callback (graphOutput[1]) to our output functions body // and then return name of ugen. If .gen() only generates a number (for really simple graphs) // just return that number (graphOutput[0]). body += Array.isArray( channel ) ? channel[1] + '\n' + channel[0] : channel // split body to inject return keyword on last line body = body.split('\n') //if( debug ) console.log( 'functionBody length', body ) // next line is to accommodate memo as graph head if( body[ body.length -1 ].trim().indexOf('let') > -1 ) { body.push( '\n' ) } // get index of last line let lastidx = body.length - 1 // insert return keyword body[ lastidx ] = ' memory[' + (this.outputIdx + i) + '] = ' + body[ lastidx ] + '\n' this.functionBody += body.join('\n') } this.histories.forEach( value => { if( value !== null ) value.gen() }) const returnStatement = isStereo ? ` return [ memory[${this.outputIdx}], memory[${this.outputIdx + 1}] ]` : ` return memory[${this.outputIdx}]` this.functionBody = this.functionBody.split('\n') if( this.endBlock.size ) { this.functionBody = this.functionBody.concat( Array.from( this.endBlock ) ) this.functionBody.push( returnStatement ) }else{ this.functionBody.push( returnStatement ) } // reassemble function body this.functionBody = this.functionBody.join('\n') // we can only dynamically create a named function by dynamically creating another function // to construct the named function! sheesh... // if( shouldInlineMemory === true ) { this.parameters.add( 'memory' ) } let paramString = '' if( this.mode === 'worklet' ) { for( let name of this.parameters.values() ) { paramString += name + ',' } paramString = paramString.slice(0,-1) } const separator = this.parameters.size !== 0 && this.inputs.size > 0 ? ', ' : '' let inputString = '' if( this.mode === 'worklet' ) { for( let ugen of this.inputs.values() ) { inputString += ugen.name + ',' } inputString = inputString.slice(0,-1) } let buildString = this.mode === 'worklet' ? `return function( ${inputString} ${separator} ${paramString} ){ \n${ this.functionBody }\n}` : `return function gen( ${ [...this.parameters].join(',') } ){ \n${ this.functionBody }\n}` if( this.debug || debug ) console.log( buildString ) callback = new Function( buildString )() // assign properties to named function for( let dict of this.closures.values() ) { let name = Object.keys( dict )[0], value = dict[ name ] callback[ name ] = value } for( let dict of this.params.values() ) { let name = Object.keys( dict )[0], ugen = dict[ name ] Object.defineProperty( callback, name, { configurable: true, get() { return ugen.value }, set(v){ ugen.value = v } }) //callback[ name ] = value } callback.members = this.closures callback.data = this.data callback.params = this.params callback.inputs = this.inputs callback.parameters = this.parameters//.slice( 0 ) callback.out = this.memory.heap.subarray( this.outputIdx, this.outputIdx + 2 ) callback.isStereo = isStereo //if( MemoryHelper.isPrototypeOf( this.memory ) ) callback.memory = this.memory.heap this.histories.clear() return callback }, /* getInputs * * Called by each individual ugen when their .gen() method is called to resolve their various inputs. * If an input is a number, return the number. If * it is an ugen, call .gen() on the ugen, memoize the result and return the result. If the * ugen has previously been memoized return the memoized value. * */ getInputs( ugen ) { return ugen.inputs.map( gen.getInput ) }, getInput( input ) { let isObject = typeof input === 'object', processedInput if( isObject ) { // if input is a ugen... //console.log( input.name, gen.memo[ input.name ] ) if( gen.memo[ input.name ] ) { // if it has been memoized... processedInput = gen.memo[ input.name ] }else if( Array.isArray( input ) ) { gen.getInput( input[0] ) gen.getInput( input[1] ) }else{ // if not memoized generate code if( typeof input.gen !== 'function' ) { console.log( 'no gen found:', input, input.gen ) input = input.graph } let code = input.gen() //if( code.indexOf( 'Object' ) > -1 ) console.log( 'bad input:', input, code ) if( Array.isArray( code ) ) { if( !gen.shouldLocalize ) { gen.functionBody += code[1] }else{ gen.codeName = code[0] gen.localizedCode.push( code[1] ) } //console.log( 'after GEN' , this.functionBody ) processedInput = code[0] }else{ processedInput = code } } }else{ // it input is a number processedInput = input } return processedInput }, startLocalize() { this.localizedCode = [] this.shouldLocalize = true }, endLocalize() { this.shouldLocalize = false return [ this.codeName, this.localizedCode.slice(0) ] }, free( graph ) { if( Array.isArray( graph ) ) { // stereo ugen for( let channel of graph ) { this.free( channel ) } } else { if( typeof graph === 'object' ) { if( graph.memory !== undefined ) { for( let memoryKey in graph.memory ) { this.memory.free( graph.memory[ memoryKey ].idx ) } } if( Array.isArray( graph.inputs ) ) { for( let ugen of graph.inputs ) { this.free( ugen ) } } } } } } gen.__proto__ = new EE() module.exports = gen