gibberwocky
Version:
music sequencing for Live and Max/MSP
343 lines (265 loc) • 11.4 kB
JavaScript
const genish = require( 'genish.js' )
module.exports = function( Gibber ) {
const WavePattern = {
__connectedWidgets: null,
create( abstractGraph, values, mode=null ) {
// might change due to proxy functionality, so use 'let'
let graph = abstractGraph.render( 'genish' ) // convert abstraction to genish.js graph
const patternOutputFnc = function( isViz = false ) {
if( isViz && pattern.vizinit === false ) {
return
}
pattern.run( isViz )
let signalValue = pattern.signalOut()
// edge case... because adjust might lead to a value of 1
// which accum would wrap AFTER the obtaining the current value
// leading to an undefined value for the pattern output (e.g. pattern[ pattern.length ] )
let outputBeforeFilters = signalValue
// if there is an array of values to read from... (signal is a phasor indexing into a values array)
if( pattern.__usesValues === true && isViz === false ) {
// have to wait to declare this array for annotations to be processed
if( pattern.update !== undefined ) {
if( pattern.update.__currentIndex === undefined ) pattern.update.__currentIndex = []
}
if( signalValue === 1 ) signalValue = 0
const scaledSignalValue = signalValue * ( pattern._values.length )
const adjustedSignalValue = Math.abs( scaledSignalValue )//scaledSignalValue < 0 ? pattern._values.length + scaledSignalValue : scaledSignalValue
const roundedSignalValue = Math.floor( adjustedSignalValue )
//console.log( scaledSignalValue, adjustedSignalValue, roundedSignalValue )
outputBeforeFilters = pattern._values[ roundedSignalValue ]
if( pattern.update !== undefined ) {
pattern.update.__currentIndex.push( roundedSignalValue )
}
}
let output = outputBeforeFilters
if( mode === 'midi' ) {
if( pattern.widget === undefined ) {
//console.log( pattern.paramID, Gibber.Environment.codeMarkup.waveform.widgets[ pattern.paramID ] )
pattern.widget = Gibber.Environment.codeMarkup.waveform.widgets[ pattern.paramID ]
}
isViz = false
}
// if we are running the pattern solely to visualize the waveform data...
if( isViz === true && pattern.vizinit && Gibber.Environment.annotations === true ) {
Gibber.Environment.codeMarkup.waveform.updateWidget( pattern.paramID, signalValue, false )
}else if( Gibber.Environment.annotations === true && pattern.widget !== undefined ) {
// mark the last placed value by the visualization as having a "hit",
// which will cause a dot to be drawn on the sparkline.
const idx = 60 + Math.round( pattern.nextTime * 16 )
pattern.widget.values[ idx ] = { value: signalValue, type:'hit' }
if( mode === 'midi' ) {
Gibber.Environment.codeMarkup.waveform.updateWidget( pattern.paramID, signalValue, false )
Gibber.MIDI.send([ 0xb0 + pattern.channel, pattern.ccnum, Math.floor( signalValue ) ], 0 )
}
}
if( typeof pattern.genReplace === 'function' ) { pattern.genReplace( output ) }
if( output === pattern.DNR ) output = null
if( pattern.running === false ) {
Gibber.Environment.animationScheduler.add( pattern.runVisualization, 1000/60 )
pattern.running = true
}
return output
}
patternOutputFnc.wavePattern = true
const pattern = Gibber.Pattern( patternOutputFnc )
patternOutputFnc.pattern = pattern
// check whether or not to use raw signal values
// or index into values array
pattern.__usesValues = values !== undefined && values !== null
if( abstractGraph.patterns === undefined ) {
abstractGraph.patterns = []
abstractGraph.graphs = []
}
abstractGraph.patterns.push( pattern )
abstractGraph.graphs.push( graph )
if( abstractGraph.__listeners === undefined ) {
abstractGraph.__listeners = []
}
const proxyFunction = ( oldAbstractGraph, newAbstractGraph ) => {
graph = newAbstractGraph.render( 'genish' )
if( newAbstractGraph.patterns === undefined ) {
newAbstractGraph.patterns = []
newAbstractGraph.graphs = []
}
newAbstractGraph.patterns.push( pattern )
newAbstractGraph.graphs.push( graph )
// XXX what the heck is the patterns array used for?
newAbstractGraph.widget = oldAbstractGraph.patterns[0].widget
pattern.graph = graph
pattern.signalOut = genish.gen.createCallback( graph, mem, false, false, Float64Array ),
pattern.phase = 0
pattern.initialized = false
pattern.widget = newAbstractGraph.widget
// reset min and max values for sparkline in case amplitudes have changed
pattern.widget.min = Infinity
pattern.widget.max = -Infinity
pattern.widget.values.length = 0
pattern.widget.storage.length = 0
if( newAbstractGraph.__listeners === undefined ) {
newAbstractGraph.__listeners = []
}
newAbstractGraph.__listeners.push( proxyFunction )
}
abstractGraph.__listeners.push( proxyFunction )
pattern.clear = function() {
if( pattern.widget !== undefined ) {
if( abstractGraph.widget !== undefined ) {
delete abstractGraph.widget
}
pattern.widget.clear()
delete pattern.widget
pattern.running = false
}else if( abstractGraph.widget !== undefined ) {
abstractGraph.widget.clear()
delete abstractGraph.widget
pattern.running = false
}
const idx = Gibber.Gen.connected.findIndex( e => e.paramID === pattern.id )
Gibber.Gen.connected.splice( idx, 1 )
pattern.shouldStop = true
}
Gibber.subscribe( 'clear', pattern.clear )
// if memory block has not been defined, create new one by passing in an undefined value
// else reuse exisitng memory block
let mem = genish.gen.memory || 44100 * 2
Object.assign( pattern, {
type: pattern.__usesValues ? 'Lookup' : 'WavePattern',
graph,
abstractGraph,
paramID:abstractGraph.paramID || Math.round( Math.random() * 1000000 ),
_values:values,
signalOut: genish.gen.createCallback( graph, mem, false, false, Float64Array ),
adjust: WavePattern.adjust.bind( pattern ),
phase:0,
run: WavePattern.run.bind( pattern ),
runVisualization: WavePattern.runVisualization.bind( patternOutputFnc ),
running: false,
initialized:false,
vizinit:true,
shouldStop:false,
__listeners:[],
mode
})
if( abstractGraph.paramID === undefined ) abstractGraph.paramID = pattern.paramID
// for assigning an abstract graph to a variable
// and then passing that variable as a pattern to a sequence
if( abstractGraph.widget !== undefined ) {
pattern.widget = abstractGraph.widget
Gibber.Environment.codeMarkup.waveform.widgets[ abstractGraph.paramID ] = pattern.widget
}
if( mode === 'midi' ) {
pattern.widget = Gibber.Environment.codeMarkup.waveform.widgets[ pattern.paramID ]
}
// if( WavePattern.__connectedWidgets === null ) { // initialize
// WavePattern.__connectedWidgets = []
// const update = ()=> {
// Gibber.Environment.animationScheduler.add( update, 1000/60 )
// WavePattern.runWidgets()
// }
// Gibber.Environment.animationScheduler.add( update, 0 )
// }
// WavePattern.__connectedWidgets.push( pattern.widget )
//}
Gibber.Gen.connected.push( pattern )
return pattern
},
runWidgets: function () {
for( let widget of WavePattern.__connectedWidgets ) {
//if( id === 'dirty' ) continue
//const widget = Gibber.Environment.codeMarkup.genWidgets[ id ]
const value = widget.gen()
Gibber.Environment.codeMarkup.updateWidget( id, value )
//if( Gen.__solo === null || ( Gen.__solo.channel === widget.gen.channel && Gen.__solo.ccnum === widget.gen.ccnum) ) {
Gibber.MIDI.send([ 0xb0 + widget.gen.channel, widget.gen.ccnum, value ])
//}
}
},
runVisualization() {
// pass true for visualization run as opposed to audio run
// this represents the patternOutputFunction, pass true to create sparkline
// without triggering output
this( true ) // I LOVE JS
if( this.pattern.shouldStop === false )
Gibber.Environment.animationScheduler.add( this.pattern.runVisualization, 1000 / 60 )
},
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
}
}
}
},
run( isViz = false ) {
let now
if( isViz === true ) {
now = Gibber.Environment.animationScheduler.visualizationTime.base + Gibber.Environment.animationScheduler.visualizationTime.phase //+ 4
}else{
now = Gibber.Scheduler.currentTimeInMs
}
if( this.initialized === true ) {
const adjustment = now - this.phase
this.adjust( this.graph, adjustment )
}else{
this.initialized = true
}
this.phase = now
//debugger;
},
adjust( ugen, ms ) {
if( ugen.shouldNotAdjust === true ) {
ugen.shouldNotAdjust = false
return
}
// subtract one sample for the phase incremenet that occurs during
// the genish.js callback
const numberOfSamplesToAdvance = ( ms/1000 ) * (Gibber.__gen.genish.gen.samplerate )
if( ugen.name !== undefined && ( ugen.name.indexOf( 'accum' ) > -1 || ugen.name.indexOf( 'phasor' ) > -1 ) ) {
if( ugen.name.indexOf( 'accum' ) > -1 ) {
ugen.value += typeof ugen.inputs[0] === 'object'
? numberOfSamplesToAdvance * ugen.inputs[0].value
: numberOfSamplesToAdvance * ugen.inputs[0]
}else{
const range = ugen.max - ugen.min
let __ugen = ugen
while( __ugen.inputs !== undefined ) {
__ugen = __ugen.inputs[0]
}
// needs .value because the result should be a param
const freq = __ugen.value
const incr = (freq * range ) / Gibber.__gen.genish.gen.samplerate
const adjustAmount = (numberOfSamplesToAdvance-1) * incr
ugen.value += adjustAmount
}
// wrap or clamp accum value manuallly
if( ugen.shouldWrap === true ) {
if( ugen.value > ugen.max ) {
while( ugen.value > ugen.max ) {
ugen.value -= ugen.max - ugen.min
}
} else if( ugen.value < ugen.min ) {
while( ugen.value < ugen.min ) {
ugen.value += ugen.max - ugen.min
}
}
}else if( ugen.shouldClamp === true ) {
if( ugen.value > ugen.max ) {
ugen.value = max
}else if( ugen.value < ugen.min ) {
ugen.value = min
}
}
}
if( typeof ugen.inputs !== 'undefined' ) {
ugen.inputs.forEach( u => {
if( typeof u === 'object' || typeof u === 'function' )
WavePattern.adjust( u,ms )
})
}
}
}
return WavePattern.create
}