UNPKG

gibberwocky

Version:

music sequencing for Live and Max/MSP

350 lines (276 loc) 8.27 kB
module.exports = function( Gibber ) { const binops = [ 'min','max','add','sub','mul','div','rdiv','mod','rsub','rmod','absdiff', 'and','or','gt','eq','eqp','gte','gtep','gtp','lt','lte','ltep','ltp','neq', 'step' ] const monops = [ 'abs','acos','acosh','asin','asinh','atan','atan2','atanh','cos','cosh','degrees', 'fastcos','fastsin','fasttan','hypot','radians','sin','sinh','tan','tanh', 'floor', 'ceil', 'sign', 'trunc', 'fract', 'param' ] const noops = [ 'noise' ] let Gen = { lastConnected:[], names:[], connected: [], isGen:true, debug:false, init() { Gen.createBinopFunctions() Gen.createMonopFunctions() Gen.names.push( ...binops ) Gen.names.push( ...monops ) Gen.names.push( ...Object.keys( Gen.constants ) ) Gen.names.push( ...Object.keys( Gen.functions ) ) Gen.names.push( ...Object.keys( Gen.composites ) ) Gibber.subscribe( 'clear', ()=> Gen.lastConnected.length = 0 ) }, // if property is !== ugen (it's a number) a Param must be made using a default create( name ) { // rate needs custom function to skip sequencing input and only sequence rate adjustment const params = Array.prototype.slice.call( arguments, 1 ) if( name === 'rate' ) return Gen.createRate( name, ...params ) let obj = Object.create( this ), count = 0 obj.name = name obj.active = false for( let key of Gen.functions[ name ].properties ) { let value = params[ count++ ] obj[ key ] = v => { if( v === undefined ) { return value }else{ value = v if( obj.active ) { if( obj.__client === 'live' ) { Gibber.Communication.send( `genp ${obj.paramID} ${obj[ key ].uid} ${v}` ) }else if( obj.__client === 'max' ) { Gibber.Communication.send( `sig ${obj.paramID} param ${obj[ key ].uid} ${v}`, 'max' ) } } } } obj[ key ].uid = Gen.getUID() Gibber.addSequencingToMethod( obj, key ) } return obj }, createRate( name ) { let obj = Object.create( this ), count = 0, param = arguments[1] obj.name = 'rate' obj.active = false let value = param //console.log( 'value:', value, 'args:', arguments ) obj[ 0 ] = v => { if( v === undefined ) { return value }else{ value = v if( obj.active ) { Gibber.Communication.send( `genp ${obj.paramID} ${obj[ 0 ].uid} ${v}` ) } } } Gen.getUID() // leave 0 behind... obj[ 0 ].uid = Gen.getUID() Gibber.addSequencingToMethod( obj, '0' ) return obj }, createBinopFunctions() { for( let key of binops ) { Gen.functions[ key ] = { properties:['0','1'], str:key } } }, createMonopFunctions() { for( let key of monops ) { Gen.functions[ key ] = { properties:['0'], str:key } } }, assignTrackAndParamID: function( track, id ) { this.paramID = id this.track = track let count = 0, param while( param = this[ count++ ] ) { if( typeof param() === 'object' ) { param().assignTrackAndParamID( track, id ) } } }, clear() { for( let key in Gibber.Communication.connected ) { if( Gibber.Communication.connected[ key ] === true ) { for( let ugen of Gen.connected ) { Gibber.Communication.send( `ungen ${ugen.paramID}`, key ) } } } Gen.connected.length = 0 }, constants: { degtorad: Math.PI / 180, E : Math.E, halfpi: Math.PI / 2, invpi : Math.PI * - 1, ln10 : Math.LN10, ln2 : Math.LN2, log10e: Math.LOG10E, log2e : Math.LOG2E, pi : Math.PI, sqrt2 : Math.SQRT2, sqrt1_2: Math.SQRT1_2, twopi : Math.PI * 2, time : 'time', samplerate: 'samplerate' }, functions: { phasor: { properties:[ '0' ], str:'phasor' }, cycle: { properties:[ '0' ], str:'cycle' }, rate: { properties:[ '0' ], str:'rate' }, noise: { properties:[], str:'noise' }, accum: { properties:[ '0','1' ], str:'accum' }, counter:{ properties:[ '0','1' ], str:'counter' }, scale: { properties: ['0', '1', '2', '3'], str:'scale' }, sah: { properties: ['0', '1', '2'], str:'sah' }, clamp: { properties: ['0', '1', '2'], str:'clamp' }, ternary:{ properties: ['0', '1', '2'], str:'ternary' }, selector:{ properties: ['0', '1', '2'], str:'selector' }, }, _count: 0, getUID() { return 'p' + Gen._count++ }, time: 'time', out() { let paramArray = [], body, out body = this.gen( paramArray ) out = paramArray.join( ';' ) if( paramArray.length ) { out += ';' } out += 'out1=' out += body + ';' if( Gen.debug ) console.log( out ) return out }, gen( paramArray ) { let def = Gen.functions[ this.name ], str = def.str + '(', count = 0 // tell Gibber that this gen object is part of an active gen graph // so that changes to it are forwarded to m4l this.active = true if( this.name === 'rate' ) { str += 'in1, ' let pName = this[ 0 ].uid str += pName paramArray.push( `Param ${pName}(${this[0]()})` ) }else{ for( let property of def.properties ) { let p = this[ property ](), uid = this[ property ].uid //console.log( this.name, property, def.properties, uid ) if( Gen.isPrototypeOf( p ) ) { str += p.gen( paramArray ) }else if( typeof p === 'number' ) { let pName = uid str += pName paramArray.push( `Param ${pName}(${p})` ) }else if( p === Gen.time ) { str += p }else if( typeof p === 'string' ) { str += p }else{ console.log( 'CODEGEN ERROR:', p ) } if( count++ < def.properties.length - 1 ) str += ',' } } str += ')' return str }, composites: { lfo( frequency = .1, amp = .5, center = .5 ) { const g = Gen.ugens let _cycle = g.cycle( frequency ), _mul = g.mul( _cycle, amp ), _add = g.add( center, _mul ) _add.frequency = (v) => { if( v === undefined ) { return _cycle[ 0 ]() }else{ _cycle[0]( v ) } } _add.amp = (v) => { if( v === undefined ) { return _mul[ 1 ]() }else{ _mul[1]( v ) } } _add.center = (v) => { if( v === undefined ) { return _add[ 0 ]() }else{ _add[0]( v ) } } Gibber.addSequencingToMethod( _add, 'frequency', 0, null, 'max' ) Gibber.addSequencingToMethod( _add, 'amp' ) Gibber.addSequencingToMethod( _add, 'center' ) return _add }, fade( time = 1, from = 1, to = 0 ) { let g = Gen.ugens let fade, amt, beatsInSeconds = time * ( 60 / Gibber.Live.LOM.bpm ) if( from > to ) { amt = from - to fade = g.gtp( g.sub( from, g.accum( g.div( amt, g.mul(beatsInSeconds, g.samplerate ) ), 0 ) ), to ) }else{ amt = to - from fade = g.add( from, g.ltp( g.accum( g.div( amt, g.mul( beatsInSeconds, g.samplerate ) ), 0 ), to ) ) } // XXX should this be available in ms? msToBeats()? let numbeats = time / 4 fade.shouldKill = { after: numbeats, final: to } return fade }, beats( num ) { return Gen.ugens.rate( num ) // beat( n ) => rate(in1, n) // final string should be rate( in1, num ) } }, ugens:{}, export( obj ) { for( let key in Gen.functions ) { this.ugens[ key ] = Gen.create.bind( Gen, key ) } Object.assign( this.ugens, Gen.constants ) Object.assign( this.ugens, Gen.composites ) Object.assign( obj, this.ugens ) } } Gen.init() return Gen } /* a = LFO( .5, .25, .5 ) // lfo has frequency, amplitude and bias -> // every array indicates presence of new ugen a.graph = [ 'add', 'bias', [ 'mul', 'amp', [ 'cycle', 'frequency' ] ] ] */