gibberwocky
Version:
music sequencing for Live and Max/MSP
608 lines (486 loc) • 20.2 kB
JavaScript
let Gibber = {
Utility: require( './utility.js' ),
Communication: require( './communication.js' ),
Environment: require( './environment.js' ),
Scheduler: require( './clock.js' ),
Theory: require( './theory.js' ),
Examples: require( './example.js' ),
Live: null,
Track: null,
Gen: null,
Euclid: null,
Hex: null,
Seq: null,
Score: null,
Pattern: null,
Arp: null,
currentTrack: null,
codemirror: null,
max: null,
'$': null,
export() {
window.Steps = this.Steps
window.HexSteps = this.HexSteps
window.Automata = this.Automata
window.Seq = this.Seq
window.Score = this.Score
window.Track = this.Track
window.Scheduler = this.Scheduler
window.Pattern = this.Pattern
window.Euclid = this.Euclid
window.Hex = this.Hex
window.Arp = this.Arp
window.Communication = this.Communication
window.log = this.log
window.clear = this.clear
window.Theory = this.Theory
window.Lookup = this.WavePattern
window.channels = this.MIDI.channels
window.MIDI = this.MIDI
window.Max = this.Max
window.signals = this.Max.signals
window.params = this.Max.params
// XXX probably safe to remove old namespace reference...
window.namespace = this.Max.message
window.message = this.Max.message
window.devices = this.Max.devices
window.patchers = this.Max.patchers
window.connect = this.Communication.connect.bind( this.Communication )
Gibber.__gen.export( window )
this.Theory.export( window )
this.Utility.export( window )
},
init(shouldCreateEnvironment=true ) {
this.isStandalone = shouldCreateEnvironment
this.$ = Gibber.Utility.create
if( this.isStandalone === true ) {
this.Environment.init( Gibber )
}
this.Theory.init( Gibber )
this.log = this.Environment.log
if( this.Environment.debug ) {
this.Scheduler.mockRun()
}else{
this.MIDI.init( Gibber )
this.Communication.init( Gibber )
}
this.Scheduler.init( this )
//this.currentTrack = this.Track( this, 1 ) // TODO: how to determine actual "id" from Max?
//this.initSingletons( window )
this.__gen.init( this )
this.export()
},
singleton( target, key ) {
if( Array.isArray( key ) ) {
for( let i = 0; i < key.length; i++ ) {
Gibber.singleton( target, key[ i ] )
}
return
}
if( target[ key ] !== undefined ) {
delete target[ key ]
}
let proxy = null
Object.defineProperty( target, key, {
configurable:true,
get() { return proxy },
set(v) {
if( proxy !== null && proxy.clear ) {
proxy.clear()
}
if( proxy !== null && proxy.__listeners !== undefined ) {
for( let listener of proxy.__listeners ) {
listener( proxy, v )
}
}
if( proxy !== null && v !== null ) {
if( proxy.isGen && v.isGen ) {
for( let key in proxy.sequences ) {
let sequences = proxy.sequences[ key ]
for( let sequence of sequences ) {
sequence.target = v
}
}
v.sequences = proxy.sequences
}
}
proxy = v
}
})
},
initSingletons: function( target ) {
var letters = "abcdefghjklmnopqrstuwxyz"
for(var l = 0; l < letters.length; l++) {
var lt = letters.charAt(l);
Gibber.singleton( target, lt )
}
},
clear() {
for( let i = 0; i < this.Seq._seqs.length; i++ ){
this.Seq._seqs[ i ].clear()
}
if( Gibber.currentTrack !== null && Gibber.isStandalone === true ) {
setTimeout( () => {
for( let key in Gibber.currentTrack.markup.textMarkers ) {
let marker = Gibber.currentTrack.markup.textMarkers[ key ]
if( Array.isArray( marker ) ) {
marker.forEach( m => m.clear() )
}else{
if( marker.clear ) marker.clear()
}
}
}, 250 )
}
Gibber.Scheduler.clear()
Gibber.Gen.clear()
Gibber.Environment.clear()
Gibber.publish( 'clear' )
Gibber.initSingletons( window )
},
createPubSub() {
const events = {}
this.subscribe = function( key, fcn ) {
if( typeof events[ key ] === 'undefined' ) {
events[ key ] = []
}
events[ key ].push( fcn )
}
this.unsubscribe = function( key, fcn ) {
if( typeof events[ key ] !== 'undefined' ) {
const arr = events[ key ]
arr.splice( arr.indexOf( fcn ), 1 )
}
}
this.publish = function( key, data ) {
if( typeof events[ key ] !== 'undefined' ) {
const arr = events[ key ]
arr.forEach( v => v( data ) )
}
}
},
addSequencingToMethod( obj, methodName, priority, overrideName, mode ) {
if( !obj.sequences ) obj.sequences = {}
if( overrideName === undefined || overrideName === null ) overrideName = methodName
let lastId = 0
if( mode !== undefined && (obj.__client === undefined || obj.__client === null ) ) obj.__client = mode
obj[ methodName ].seq = function( values, timings, id=0, delay=0 ) {
let seq
lastId = id
if( obj.sequences[ methodName ] === undefined ) obj.sequences[ methodName ] = []
if( obj.sequences[ methodName ][ id ] ) obj.sequences[ methodName ][ id ].clear()
obj.sequences[ methodName ][ id ] = seq = Gibber.Seq( values, timings, overrideName, obj, priority, mode )
// if the target is another sequencer (like for per-sequencer velocity control) it won't
// have an id property.. use existing trackID property instead.
seq.trackID = obj.id === undefined ? obj.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
if( delay !== 0 ) seq.delay( delay )
seq.start()
// avoid this for gibber objects that don't communicate with Live such as Scale
if( mode === 'live' && obj.id !== undefined ) Gibber.Communication.send( `select_track ${obj.id}` )
// setup code annotations to place values and widget onto pattern object
// not gen~ object
if( typeof values === 'object' && values.isGen ) {
Gibber.Gen.lastConnected.push( seq.values )
}
if( typeof timing === 'object' && timings.isGen ) {
Gibber.Gen.lastConnected.push( seq.timings )
}
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
}
},
addSequencingToProtoMethod( proto, methodName ) {
proto[ methodName ].seq = function( values, timings, id = 0 ) {
if( this.sequences === undefined ) this.sequences = {}
if( this.sequences[ methodName ] === undefined ) this.sequences[ methodName ] = []
if( this.sequences[ methodName ][ id ] ) this.sequences[ methodName ][ id ].stop()
this.sequences[ methodName ][ id ] = Gibber.Seq( values, timings, methodName, this ).start() // 'this' will never be correct reference
if( id === 0 ) {
this.values = this.sequences[ methodName ][ 0 ].values
this.timings = this.sequences[ methodName ][ 0 ].timings
}
this[ id ] = this.sequences[ methodName ][ id ]
this.seq.stop = function() {
this.sequences[ methodName ][ 0 ].stop()
return this
}.bind( this )
this.seq.start = function() {
this.sequences[ methodName ][ 0 ].start()
return this
}.bind( this )
}
},
// XXX THIS MUST BE REFACTORED. UGH.
addMethod( obj, methodName, parameter, _trackID, mode='live' ) {
let v = mode === 'live' ? parameter.value : 0,
p,
trackID = isNaN( _trackID ) ? obj.id : _trackID
let seqKey = null
if( mode === 'live' ) {
seqKey = `${trackID} ${obj.id} ${parameter.id}`
}else if ( mode === 'max' ){
seqKey = `${parameter} ${methodName}`
}else{
seqKey = methodName
}
//console.log( "add method trackID", trackID )
if( mode === 'live' && methodName === null ) methodName = parameter.name
if( parameter === null ) parameter = ''
if( typeof methodName === 'string' && methodName.indexOf('cc') === -1 ) {
Gibber.Seq.proto.externalMessages[ seqKey ] = ( value, beat ) => {
let msg = mode === 'live'
? `add ${beat} set ${parameter.id} ${value}`
: `add ${beat} set ${parameter} ${methodName} ${value}`
return msg
}
}
obj[ methodName ] = p = _v => {
if( p.properties !== null && p.properties.quantized === 1 ) _v = Math.round( _v )
const hasGen = Gibber.__gen.enabled
if( _v !== undefined ) {
_v.__client = mode
if( typeof _v === 'object' && _v.isGen ) {
let __v = hasGen === true ? _v.render( 'gen', mode ) : _v.render( 'genish', mode )
__v.__client = _v.__client = mode
const __id = isNaN( parameter ) ? parameter.id : parameter+'0000'+_trackID
if( hasGen ) {
_v.paramID = __id
__v.assignTrackAndParamID( trackID, __id )
}else{
Gibber.__gen.assignTrackAndParamID.call( _v, trackID, __id )
}
// if a gen is not already connected to this parameter, push
const prevGen = Gibber.Gen.connected.find( e => e.paramID === __id )
const genAlreadyAssigned = prevGen !== undefined
if( genAlreadyAssigned === false && mode !== 'midi' ) {
Gibber.Gen.connected.push( __v )
}
if( hasGen === true && mode !== 'midi' ) {
if( mode === 'live' ) {
Gibber.Communication.send( `gen ${parameter.id} "${__v.out()}"`, 'live' )
}else{
Gibber.Communication.send( `sig ${parameter.id} expr "${__v.out()}"`, 'max' )
}
if( genAlreadyAssigned === true ) {
prevGen.clear()
prevGen.shouldStop = true
const idx = Gibber.Gen.connected.findIndex( e => e.paramID === __id )
Gibber.Gen.connected.splice( idx, 1 )
}
}else{
if( genAlreadyAssigned === true ) {
prevGen.clear()
prevGen.shouldStop = true
const idx = Gibber.Gen.connected.findIndex( e => e.paramID === __id )
Gibber.Gen.connected.splice( idx, 1 )
}
_v.wavePattern = Gibber.WavePattern( _v, null, mode )
if( mode === 'midi' ) {
_v.wavePattern.channel = _trackID
_v.wavePattern.ccnum = parameter
}
_v.wavePattern.genReplace = function( out ) {
if( mode === 'live' ) {
// set min/max for live only
out = Math.min( out, 1 )
out = Math.max( 0, out )
Gibber.Communication.send( `set ${parameter.id} ${out}` )
}else if( mode === 'max' ) {
Gibber.Communication.send( `sig ${parameter.id} expr "out1=${out};"` )
}
}
_v.wavePattern( false )
__v = _v
}
if( mode === 'live' ) Gibber.Communication.send( `select_track ${ trackID }` )
Gibber.__gen.gen.lastConnected.push( hasGen === true ? __v : _v )
// disconnects for fades etc.
// XXX reconfigure for hasGen === false
if( typeof _v.shouldKill === 'object' ) {
Gibber.Utility.future( ()=> {
if( hasGen ) {
Gibber.Communication.send( `ungen ${parameter.id}`, 'live' )
Gibber.Communication.send( `set ${parameter.id} ${_v.shouldKill.final}` )
}else{
Gibber.Communication.send( `ungen ${parameter.id}` )
//_v.wavePattern.clear()
const prevGen = Gibber.Gen.connected.find( e => e.paramID === parameter.id )
prevGen.clear()
_v.patterns[0].shouldStop = true
const idx = Gibber.Gen.connected.findIndex( e => e.paramID === parameter.id )
Gibber.Gen.connected.splice( idx, 1 )
obj[ methodName ]( _v.shouldKill.final )
Gibber.Communication.send( `set ${parameter.id} ${_v.shouldKill.final}`, 'live' )
}
let widget = Gibber.Environment.codeMarkup.waveform.widgets[ parameter.id ]
if( widget !== undefined && widget.mark !== undefined ) {
widget.mark.clear()
}
delete Gibber.Environment.codeMarkup.waveform.widgets[ parameter.id ]
}, _v.shouldKill.after )
}
v = hasGen === true ? __v : _v
}else{
v = typeof _v === 'object' && _v.isGen ? ( hasGen === true ? _v.render( 'gen', mode ) : _v.render('genish', mode ) ) : _v
v.__client = mode
if( v.isGen ) {
if( hasGen ) {
if( mode === 'live' ) {
Gibber.Communication.send( `ungen ${parameter.id}`, 'live' )
}
}
let widget = Gibber.Environment.codeMarkup.waveform.widgets[ parameter.id ]
if( widget !== undefined && widget.mark !== undefined ) {
widget.mark.clear()
}
delete Gibber.Environment.codeMarkup.waveform.widgets[ parameter.id ]
}
if( mode === 'live' ) {
Gibber.Communication.send( `set ${parameter.id} ${v}`, 'live' )
}else if( mode === 'max' ) {
// how to know if this is a signal? shouldn't be assuming this.
if( parameter !== null ) {
Gibber.Communication.send( `set ${parameter} ${methodName} ${v}`, 'max' )
}
// Gibber.Communication.send( `sig ${parameter.id} expr "out1=${v};"`, 'max' )
}else if( mode === 'midi' ) {
let msg = [ 0xb0 + _trackID, parameter, v ]
const __id = isNaN( parameter ) ? parameter.id : parameter+'0000'+_trackID
const prevGen = Gibber.Gen.connected.find( e => e.paramID === __id )
if( prevGen !== undefined ) {
prevGen.clear()
prevGen.shouldStop = true
const idx = Gibber.Gen.connected.findIndex( e => e.paramID === __id )
Gibber.Gen.connected.splice( idx, 1 )
Gibber.MIDI.send( msg, 100 )
}else{
Gibber.MIDI.send( msg, 0 )
}
}
}
}else{
return v
}
}
p.properties = parameter
Gibber.addSequencingToMethod( obj, methodName, 0, seqKey, mode )
},
addMIDIMethod( obj, methodName, channel, ccnum ) {
let v = 0,//parameter.value,
p,
seqKey = `${channel} cc ${ccnum}`
//console.log( "add method trackID", trackID )
Gibber.Seq.proto.externalMessages[ seqKey ] = ( val, offset=null ) => {
let msg = [ 0xb0 + channel, ccnum, val ]
const baseTime = offset !== null ? window.performance.now() + offset : window.performance.now()
Gibber.MIDI.send( msg, baseTime )
}
obj[ methodName ] = p = ( initialGraph, shouldTransform=true ) => {
let transformedGraph = null, finishedGraph = null
//if( p.properties.quantized === 1 ) _v = Math.round( _v )
if( typeof initialGraph === 'object' ) initialGraph.isGen = typeof initialGraph.gen === 'function'
if( initialGraph !== undefined ) {
if( typeof initialGraph === 'object' && initialGraph.isGen ) {
if( shouldTransform === true ) { // affine transform -1:1 to 0:127
transformedGraph = Gibber.Gen.genish.clamp(
Gibber.Gen.genish.floor(
Gibber.Gen.genish.mul(
Gibber.Gen.genish.div(
Gibber.Gen.genish.add( 1, initialGraph ),
2
),
127
)
),
0, 127 )
}
finishedGraph = shouldTransform ?
Gibber.Gen.genish.gen.createCallback( transformedGraph ) :
Gibber.Gen.genish.gen.createCallback( initialGraph )
Gibber.Gen.assignTrackAndParamID( finishedGraph, channel, ccnum )
// if a gen is not already connected to this parameter, push
if( Gibber.Gen.connected.find( e => e.ccnum === ccnum && e.channel === channel ) === undefined ) {
Gibber.Gen.connected.push( finishedGraph )
}
Gibber.Gen.lastConnected = finishedGraph
if( '__widget__' in initialGraph ) {
initialGraph.__widget__.place()
}
// disconnects for fades etc.
//if( typeof _v.shouldKill === 'object' ) {
// Gibber.Utility.future( ()=> {
// Gibber.Communication.send( `ungen ${parameter.id}` )
// Gibber.Communication.send( `set ${parameter.id} ${_v.shouldKill.final}` )
// let widget = Gibber.Environment.codeMarkup.genWidgets[ parameter.id ]
// if( widget !== undefined && widget.mark !== undefined ) {
// widget.mark.clear()
// }
// delete Gibber.Environment.codeMarkup.genWidgets[ parameter.id ]
// }, _v.shouldKill.after )
//}
v = finishedGraph
v.isGen = true
}else{
// if there was a gen assigned and now a number is being assigned...
if( v.isGen ) {
console.log( 'removing gen', v )
let widget = Gibber.Environment.codeMarkup.genWidgets[ v.id ]
if( widget !== undefined && widget.mark !== undefined ) {
widget.mark.clear()
}
delete Gibber.Environment.codeMarkup.genWidgets[ v.id ]
}
v = initialGraph
Gibber.Seq.proto.externalMessages[ seqKey ]( v )
//Gibber.Communication.send( `set ${parameter.id} ${v}` )
}
}else{
return v
}
}
// solo cc output for midi mapping
let soloing = false
obj[ methodName ].solo = function() {
if( soloing === false ) {
Gibber.Gen.__solo = { channel, ccnum }
}else{
Gibber.Gen.__solo = null
}
soloing = !soloing
}
//p.properties = parameter
Gibber.addSequencingToMethod( obj, methodName, 0, seqKey )
}
}
// must be called before requiring objects that use pubsub
Gibber.createPubSub()
Gibber.Pattern = require( './pattern.js' )( Gibber )
Gibber.Seq = require( './seq.js' )( Gibber )
Gibber.Score = require( './score.js' )( Gibber )
Gibber.Arp = require( './arp.js' )( Gibber )
Gibber.Euclid = require( './euclidean.js')( Gibber )
Gibber.Automata= require( './automata.js' )( Gibber )
Gibber.Hex = require( './hex.js')( Gibber )
Gibber.Steps = require( './steps.js' )( Gibber )
Gibber.HexSteps= require( './hexSteps.js' )( Gibber )
Gibber.Live = require( './live.js' )( Gibber )
Gibber.Max = require( './max.js' )( Gibber )
Gibber.Track = require( './track.js')( Gibber )
Gibber.__gen = require( './gen_abstraction.js' )( Gibber )
Gibber.Channel = require( './channel.js' )( Gibber )
Gibber.MIDI = require( './midi.js' )
Gibber.WavePattern = require( './wavePattern.js' )( Gibber )
Gibber.Gen = Gibber.__gen.gen
module.exports = Gibber