gibberwocky
Version:
music sequencing for Live and Max/MSP
350 lines (276 loc) • 8.27 kB
JavaScript
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' ] ] ]
*/