gibberwocky
Version:
music sequencing for Live and Max/MSP
332 lines (268 loc) • 9.49 kB
JavaScript
/* gen_abstraction.js
*
* This object serves as an asbtraction between the codegen for Gen and
* the use of genish.js. In gibberwocky, Gen code is sent to Live/Max for
* modulation purposes while genish.js is used to create waveforms that can
* be sampled for pattern generation.
*
* This abstraction delays interpretation of the Gen(ish) graphs until
* one of the following two events occurs:
*
* 1. The graph is assigned to a property. This means that the graph
* should be interpreted as a Gen graph.
*
* 2. The graph is used to create a WavePattern. This means that the
* graph should be interpreted as a genish graph.
*
* One open problem: what if the same graph is used in two places? Can
* the abstraction hold both types of codegen objects inside of it? Currently
* the abstraction only holds one graph in its 'rendered' property... perhaps
* this could be chagned to 'genRendered' and 'genishRendered' and then the
* appropriate one could be linked to? What happens when multiple wavepatterns
* use the same graph? Does each generated a dot for every s&h value??
*/
const genish = require( 'genish.js' )
const genreq = require( './gen.js' )
/*
assignInputProperties( genishGraph, abstractGraph ) {
for( let input in abstractGraph.inputs ) {
if( typeof abstractGraph.inputs[ input ] === 'number' ) {
let __param = genishGraph.inputs[ input ] = genish.param( abstractGraph.inputs[ input ] )
abstractGraph[ input ] = v => {
__param.value = v
}
}
}
},
*/
const defineMethod = function( obj, methodName, param, priority=0, mode='internal' ) {
obj[ methodName ] = v => {
if( v === undefined ) return param.value
// else, set the value
param.value = v
}
if( !obj.sequences ) obj.sequences = {}
obj[ methodName ].seq = function( values, timings, id=0, delay=0, mode='internal' ) {
if( obj.sequences[ methodName ] === undefined ) obj.sequences[ methodName ] = []
if( obj.sequences[ methodName ][ id ] !== undefined ) obj.sequences[ methodName ][ id ].clear()
const seq = Gibber.Seq( values, timings, methodName, obj, priority, mode )
obj.sequences[ methodName ][ id ] = seq
obj.__client = mode
seq.__tick = seq.tick
seq.tick = function( ...args ) {
param.value = 0
for( let i = 0; i < obj.patterns.length; i++ ) {
let pattern = obj.patterns[ i ]
let graph = obj.graphs[ i ]
pattern.adjust( graph, Gibber.Scheduler.currentTimeInMs - pattern.phase )
}
seq.__tick.apply( seq, args )
}
seq.trackID = obj.id
if( id === 0 ) {
obj[ methodName ].values = obj.sequences[ methodName ][ 0 ].values
obj[ methodName ].timings = obj.sequences[ methodName ][ 0 ].timings
}
obj[ methodName ][ id ] = seq
seq.delay( delay )
seq.start()
// avoid this for gibber objects that don't communicate with Live such as Scale
if( obj.id !== undefined ) Gibber.Communication.send( `select_track ${obj.id}` )
return seq
}
obj[ methodName ].seq.delay = v => obj[ methodName ][ lastId ].delay( v )
obj[ methodName ].seq.stop = function() {
obj.sequences[ methodName ][ 0 ].stop()
return obj
}
obj[ methodName ].seq.start = function() {
obj.sequences[ methodName ][ 0 ].start()
return obj
}
}
const assignClient = function( node, client ) {
if( typeof node === 'object' ) {
if( node.inputs !== undefined ) {
node.inputs.forEach( input => {
input.__client = client
assignClient( input )
})
}
}
}
module.exports = function( Gibber ) {
const gen = genreq( Gibber )
const genfunctions = {}
gen.export( genfunctions )
const __ugenproto__ = {
render( mode, client='live' ) {
let inputs = [], inputNum = 0
this.mode = mode
if( this.name === 'phasor' ) {
if( this.inputs[2] === undefined ) {
this.inputs[2] = { min:0 }
}
}
for( let input of this.inputs ) {
// use input.render as check to make sure this isn't a properties dictionary
if( typeof input === 'object' && input.render !== undefined ) {
inputs.push( input.render( mode, client ) )
}else{
if( mode === 'genish' && typeof input === 'number' ) { // replace numbers with params
let _input = genish.param( input )
defineMethod( this, inputNum, _input )
inputs.push( _input )
}else{
inputs.push( input )
}
}
inputNum++
}
let ugen
if( mode === 'genish' || __gen.enabled === false ) {
ugen = genish[ this.name ]( ...inputs )
}else{
ugen = genfunctions[ this.name ]( ...inputs )
}
this.rendered = ugen
this.rendered.paramID = this.paramID
this.rendered.track = this.track
this.rendered.__client = client
assignClient( this.rendered, client )
if( typeof this.__onrender === 'function' ) { this.__onrender() }
return ugen
},
isGen:true,
}
const __gen = {
__hasGen: true, // is Gen licensed on this machine? true by default
gen,
genish,
Gibber,
enabled: true,
initialized: false,
__waveObjects: require( './waveObjects.js' ),
waveObjects:null,
ugenNames: [
'cycle','phasor','accum','counter',
'add','mul','div','sub',
'sah','noise',
'beats', 'lfo', 'fade', 'sine', 'siner', 'cos', 'cosr', 'liner', 'line',
'abs', 'ceil', 'round', 'floor',
'min','max',
'gt','lt','ltp','gtp','samplerate','rate','clamp',
'ternary', 'selector'
],
ugens:{},
// determine whether or not gen is licensed
// look for error message in environment.js
checkForLicense() {
const volume = Gibber.Live.tracks[0].volume()
__gen.enabled = true
const lfo = __gen.ugens.lfo( .25 )
Gibber.Live.tracks[0].volume( lfo )
setTimeout( ()=> {
Gibber.clear()
setTimeout( ()=> {
Gibber.Live.tracks[0].volume( volume )
Gibber.Environment.suppressErrors = false
Gibber.Communication.send( 'ungen ' + Gibber.Live.tracks[0].volume.properties.id, 'live' )
}, 50 )
}, 250 * 8 )
},
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 )
// }
//}
},
init() {
genish.gen.memory = genish.gen.createMemory( 88200, Float64Array )
const btof = Gibber.Utility.beatsToFrequency
Gibber.subscribe( 'lom_update', ()=> {
if( __gen.initialized === false ) {
__gen.checkForLicense()
__gen.initialized = true
}
})
Gibber.Environment.suppressErrors = true
for( let name of this.ugenNames ) {
this.ugens[ name ] = function( ...inputs ) {
const ugen = Object.create( __ugenproto__ )
ugen.name = name
ugen.inputs = inputs
for( let i = 0; i < inputs.length; i++ ) {
ugen[ i ] = v => {
if( ugen.rendered !== undefined ) {
return ugen.rendered[ i ]( v )
}
}
Gibber.addSequencingToMethod( ugen, i )
}
return ugen
}
}
this.waveObjects = this.__waveObjects( Gibber, this, __ugenproto__ )
Object.assign( this.ugens, this.waveObjects )
genish.lfo = ( frequency = .1, center = .5, amp = .25 ) => {
const g = this.ugens//genish
//console.log( 'lfo', g )
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
}
//genish.fade = function( beats=43, from = 0, to = 1 ) {
// const g = this.ugens//__gen.ugens
// let fade, amt, beatsInSeconds = Gibber.Utility.beatsToFrequency( beats, 120 )
// 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{
// console.log( 'fading in' )
// 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()?
// fade.shouldKill = {
// after: beats,
// final: to
// }
// fade.name = 'fade'
// return fade
//}
},
export( target ) {
Object.assign( target, this.ugens )
},
}
return __gen
}