gibberwocky
Version:
music sequencing for Live and Max/MSP
456 lines (366 loc) • 12.1 kB
JavaScript
module.exports = function( Gibber ) {
let PatternProto = Object.create( function(){} )
Object.assign( PatternProto, {
DNR: -987654321,
concat( _pattern ) { this.values = this.values.concat( _pattern.values ) },
toString() { return this.values.toString() },
valueOf() { return this.values },
getLength() {
let l
if( this.start <= this.end ) {
l = this.end - this.start + 1
}else{
l = this.values.length + this.end - this.start + 1
}
return l
},
runFilters( val, idx ) {
let args = [ val, 1, idx ] // 1 is phaseModifier
for( let filter of this.filters ) {
args = filter( args, this )
}
return args
},
checkForUpdateFunction( name, ...args ) {
if( this.__delayAnnotations === true ) {
setTimeout( ()=> {
if( this.listeners[ name ] ) {
this.listeners[ name ].apply( this, args )
}else if( Pattern.listeners[ name ] ) {
Pattern.listeners[ name ].apply( this, args )
}
}, 5 )
}else{
if( this.listeners[ name ] ) {
this.listeners[ name ].apply( this, args )
}else if( Pattern.listeners[ name ] ) {
Pattern.listeners[ name ].apply( this, args )
}
}
},
// used when _onchange has not been assigned to individual patterns
_onchange() {},
})
let Pattern = function( ...args ) {
/*
*if( ! ( this instanceof Pattern ) ) {
* let args = Array.prototype.slice.call( arguments, 0 )
* return Gibber.construct( Pattern, args )
*}
*/
let isFunction = args.length === 1 && typeof args[0] === 'function'
let fnc = function() {
let len = fnc.getLength(),
idx, val, args
if( len === 1 ) {
idx = 0
}else{
idx = fnc.phase > -1 ? Math.floor( fnc.start + (fnc.phase % len ) ) : Math.floor( fnc.end + (fnc.phase % len ) )
}
if( isFunction ) {
val = fnc.values[ 0 ]()
args = fnc.runFilters( val, idx )
val = args[0]
}else{
val = fnc.values[ Math.floor( idx % fnc.values.length ) ]
args = fnc.runFilters( val, idx )
fnc.phase += fnc.stepSize * args[ 1 ]
val = args[ 0 ]
}
// check to see if value is a function, and if so evaluate it
//if( typeof val === 'function' ) {
//val = val()
//}
/*else if ( Array.isArray( val ) ) {
// if val is an Array, loop through array and evaluate any functions found there. TODO: IS THIS SMART?
for( let i = 0; i < val.length; i++ ){
if( typeof val[ i ] === 'function' ) {
val[ i ] = val[ i ]()
}
}
}
*/
// if pattern has update function, add new value to array
// values are popped when updated by animation scheduler
if( fnc.update && fnc.update.value ) fnc.update.value.unshift( val )
if( val === fnc.DNR ) val = null
return val
}
Object.assign( fnc, {
start : 0,
end : 0,
phase : 0,
values : args,
// wrap annotation update in setTimeout( func, 0 )
__delayAnnotations:false,
//values : typeof arguments[0] !== 'string' || arguments.length > 1 ? Array.prototype.slice.call( arguments, 0 ) : arguments[0].split(''),
original : null,
storage : [],
stepSize : 1,
integersOnly : false,
filters : [],
__listeners: [],
onchange : null,
range() {
let start, end
if( Array.isArray( arguments[0] ) ) {
start = arguments[0][0]
end = arguments[0][1]
}else{
start = arguments[0]
end = arguments[1]
}
if( start < end ) {
fnc.start = start
fnc.end = end
}else{
fnc.start = end
fnc.end = start
}
this.checkForUpdateFunction( 'range', fnc )
return fnc
},
set() {
let args = Array.isArray( arguments[ 0 ] ) ? arguments[ 0 ] : arguments
fnc.values.length = 0
for( let i = 0; i < args.length; i++ ) {
fnc.values.push( args[ i ] )
}
fnc.end = fnc.values.length - 1
// if( fnc.end > fnc.values.length - 1 ) {
// fnc.end = fnc.values.length - 1
// }else if( fnc.end < )
fnc._onchange()
return fnc
},
reverse() {
//fnc.values.reverse();
let array = fnc.values,
left = null,
right = null,
length = array.length,
temporary;
for ( left = 0, right = length - 1; left < right; left += 1, right -= 1 ) {
temporary = array[ left ]
array[ left ] = array[ right ]
array[ right ] = temporary;
}
fnc._onchange()
return fnc
},
// humanize: function( randomMin, randomMax ) {
// let lastAmt = 0
//
// for( let i = 0; i < this.filters.length; i++ ) {
// if( this.filters[ i ].humanize ) {
// lastAmt = this.filters[ i ].lastAmt
// this.filters.splice( i, 1 )
// break;
// }
// }
//
// let filter = function( args ) {
// console.log( filter.lastAmt, args[0])
// args[ 0 ] -= filter.lastAmt
// filter.lastAmt = Gibber.Clock.time( Gibber.Utilities.rndi( randomMin, randomMax ) )
//
// console.log( "LA", filter.lastAmt )
// args[0] += filter.lastAmt
//
// return args
// }
// filter.lastAmt = lastAmt
// filter.humanize = true
//
// this.filters.push( filter )
//
// return this
// },
repeat() {
let counts = {}
for( let i = 0; i < arguments.length; i +=2 ) {
counts[ arguments[ i ] ] = {
phase: 0,
target: arguments[ i + 1 ]
}
}
let repeating = false, repeatValue = null, repeatIndex = null
let filter = function( args ) {
let value = args[ 0 ], phaseModifier = args[ 1 ], output = args
//console.log( args, counts )
if( repeating === false && counts[ value ] ) {
repeating = true
repeatValue = value
repeatIndex = args[2]
}
if( repeating === true ) {
if( counts[ repeatValue ].phase !== counts[ repeatValue ].target ) {
output[ 0 ] = repeatValue
output[ 1 ] = 0
output[ 2 ] = repeatIndex
//[ val, 1, idx ]
counts[ repeatValue ].phase++
}else{
counts[ repeatValue ].phase = 0
output[ 1 ] = 1
if( value !== repeatValue ) {
repeating = false
}else{
counts[ repeatValue ].phase++
}
}
}
return output
}
fnc.filters.push( filter )
return fnc
},
reset() { fnc.values = fnc.original.slice( 0 ); fnc._onchange(); return fnc; },
store() { fnc.storage[ fnc.storage.length ] = fnc.values.slice( 0 ); return fnc; },
transpose( amt ) {
for( let i = 0; i < fnc.values.length; i++ ) {
let val = fnc.values[ i ]
if( Array.isArray( val ) ) {
for( let j = 0; j < val.length; j++ ) {
if( typeof val[ j ] === 'number' ) {
val[ j ] = fnc.integersOnly ? Math.round( val[ j ] + amt ) : val[ j ] + amt
}
}
}else{
if( typeof val === 'number' ) {
fnc.values[ i ] = fnc.integersOnly ? Math.round( fnc.values[ i ] + amt ) : fnc.values[ i ] + amt
}
}
}
fnc._onchange()
return fnc
},
shuffle() {
Gibber.Utility.shuffle( fnc.values )
fnc._onchange()
return fnc
},
scale( amt ) {
fnc.values.map( (val, idx, array) => {
if( Array.isArray( val ) ) {
array[ idx ] = val.map( inside => {
if( typeof inside === 'number' ) {
return fnc.integersOnly ? Math.round( inside * amt ) : inside * amt
} else {
return inside
}
})
}else{
if( typeof val === 'number' ) {
array[ idx ] = fnc.integersOnly ? Math.round( val * amt ) : val * amt
}
}
})
fnc._onchange()
return fnc
},
flip() {
let start = [],
ordered = null
ordered = fnc.values.filter( function(elem) {
let shouldPush = start.indexOf( elem ) === -1
if( shouldPush ) start.push( elem )
return shouldPush
})
ordered = ordered.sort( function( a,b ){ return a - b } )
for( let i = 0; i < fnc.values.length; i++ ) {
let pos = ordered.indexOf( fnc.values[ i ] )
fnc.values[ i ] = ordered[ ordered.length - pos - 1 ]
}
fnc._onchange()
return fnc
},
invert() {
let prime0 = fnc.values[ 0 ]
for( let i = 1; i < fnc.values.length; i++ ) {
if( typeof fnc.values[ i ] === 'number' ) {
let inverse = prime0 + (prime0 - fnc.values[ i ])
fnc.values[ i ] = inverse
}
}
fnc._onchange()
return fnc
},
switch( to ) {
if( fnc.storage[ to ] ) {
fnc.values = fnc.storage[ to ].slice( 0 )
}
fnc._onchange()
return fnc
},
rotate( amt ) {
if( amt > 0 ) {
while( amt > 0 ) {
let end = fnc.values.pop()
fnc.values.unshift( end )
amt--
}
}else if( amt < 0 ) {
while( amt < 0 ) {
let begin = fnc.values.shift()
fnc.values.push( begin )
amt++
}
}
fnc._onchange()
return fnc
}
})
fnc.filters.pattern = fnc
fnc.retrograde = fnc.reverse.bind( fnc )
fnc.end = fnc.values.length - 1
fnc.original = fnc.values.slice( 0 )
fnc.storage[ 0 ] = fnc.original.slice( 0 )
fnc.integersOnly = fnc.values.every( function( n ) { return n === +n && n === (n|0); })
let methodNames = [
'rotate','switch','invert','reset', 'flip',
'transpose','reverse','shuffle','scale',
'store', 'range', 'set'
]
for( let key of methodNames ) { Gibber.addSequencingToMethod( fnc, key, 1 ) }
fnc.listeners = {}
fnc.sequences = {}
// TODO: Gibber.createProxyProperties( fnc, { 'stepSize':0, 'start':0, 'end':0 })
fnc.__proto__ = PatternProto
return fnc
}
Pattern.listeners = {}
Pattern.listeners.range = function( fnc ) {
//if( !Notation.isRunning ) return
// TODO: don't use Gibber.currentTrack, store the object in the pattern
var obj = Gibber.currentTrack,
rangeStart = obj.markup.textMarkers[ fnc.patternName ][ fnc.start ].find(),
rangeEnd = obj.markup.textMarkers[ fnc.patternName ][ fnc.end ].find()
if( !fnc.range.init ) {
fnc.range.init = true
var ptrnStart = obj.markup.textMarkers[ fnc.patternName ][ 0 ].find(),
ptrnEnd = obj.markup.textMarkers[ fnc.patternName ][ obj.markup.textMarkers[ fnc.patternName ].length - 1 ].find()
//fnc.column.editor.markText( ptrnStart.from, ptrnEnd.to, { className:'rangeOutside' })
//Gibber.Environment.codemirror.markText( ptrnStart.from, ptrnEnd.to, { className:'pattern-update-range-outside' })
if( !Pattern.listeners.range.initialzied ) Pattern.listeners.range.init()
}
if( fnc.range.mark ) fnc.range.mark.clear()
//fnc.range.mark = fnc.column.editor.markText( rangeStart.from, rangeEnd.to, { className:'rangeInside' })
// TODO: Dont use GE.codemirror... how else do I get this? stored in pattern is created?
fnc.range.mark = Gibber.Environment.codemirror.markText( rangeStart.from, rangeEnd.to, { className:'pattern-update-range-inside' })
}
Pattern.listeners.range.init = function() {
//$.injectCSS({
// '.rangeOutside': {
// 'color':'#666 !important'
// },
// '.rangeInside': {
// 'color':'rgba(102, 153, 221, 1) !important'
// }
//})
Pattern.listeners.range.initialized = true
}
Pattern.prototype = PatternProto
return Pattern
}