UNPKG

gibberwocky

Version:

music sequencing for Live and Max/MSP

1,232 lines (978 loc) 45.1 kB
const acorn = require( 'acorn' ) const callDepths = [ 'SCORE', 'THIS.METHOD', 'THIS.METHOD.SEQ', 'THIS.METHOD[ 0 ].SEQ', 'THIS.METHOD.VALUES.REVERSE.SEQ', 'THIS.METHOD[ 0 ].VALUES.REVERSE.SEQ', 'TRACKS[0].METHOD[ 0 ].VALUES.REVERSE.SEQ', 'TRACKS[0].METHOD.SEQ', 'TRACKS[0].METHOD[0].SEQ', 'TRACKS[0].METHOD.VALUES.REVERSE.SEQ', 'TRACKS[0].METHOD[0].VALUES.REVERSE.SEQ' ] const trackNames = [ 'this', 'tracks', 'master', 'returns' ] const COLORS = { FILL:'rgba(46,50,53,1)', STROKE:'#eee', DOT:'rgba(89, 151, 198, 1)'//'rgba(0,0,255,1)' } const Utility = require( './utility.js' ) const $ = Utility.create let Marker = { genWidgets: { dirty:false }, _patternTypes: [ 'values', 'timings', 'index' ], globalIdentifiers:{}, prepareObject( obj ) { obj.markup = { textMarkers: {}, cssClasses: {} } }, process( code, position, codemirror, track ) { let shouldParse = code.includes( '.seq' ) || code.includes( 'Steps(' ) || code.includes( 'Score(' ), shouldParseGen = true, isGen = false if( shouldParseGen ) { // check for gen~ assignment const found = Gibber.__gen.gen.names.reduce( (r,v) => { const idx = code.indexOf( v ) if( idx !== -1 ){ if( v === 'eq' && code[ idx - 1 ] !== 's' ) return r if( code[ idx + v.length ] !== '(' || code[ idx + v.length + 1 ] !== '(' ) return r r = true } return r }, false ) if( found === true ) { shouldParse = true isGen = true } if( isGen === false ) { for( let ugen of Gibber.__gen.ugenNames ) { let idx = code.indexOf( ugen ) if( idx !== -1 && code.charAt( idx + ugen.length ) === '(' ) { shouldParse = true isGen = true break; } } } } if( !shouldParse ) return const tree = acorn.parse( code, { locations:true, ecmaVersion:6 } ).body for( let node of tree ) { if( node.type === 'ExpressionStatement' ) { // not control flow node.verticalOffset = position.start.line node.horizontalOffset = position.horizontalOffset === undefined ? 0 : position.horizontalOffset try { if( isGen ) { this.processGen( node, codemirror, track ) }else{ this._process[ node.type ]( node, codemirror, track ) } } catch( error ) { console.log( 'error processing annotation for', node.expression.type, error ) } } } }, processGen( node, cm, track ) { let ch = node.end, line = node.verticalOffset, start = ch - 1, end = node.end // check to see if a given object is a proxy that already has // a widget created; if so, don't make another one! if( node.expression.type === 'AssignmentExpression' ) { const __obj = window[ node.expression.left.name ] if( __obj !== undefined ) { if( __obj.widget !== undefined ) { return } } } cm.replaceRange( ') ', { line, ch:start }, { line, ch } ) const widget = document.createElement( 'canvas' ) widget.ctx = widget.getContext('2d') widget.style.display = 'inline-block' widget.style.verticalAlign = 'middle' widget.style.height = '1.1em' widget.style.width = '60px' widget.style.backgroundColor = 'transparent' widget.style.borderLeft = '1px solid #666' widget.style.borderRight = '1px solid #666' widget.setAttribute( 'width', 60 ) widget.setAttribute( 'height', 13 ) widget.ctx.fillStyle = COLORS.FILL widget.ctx.strokeStyle = COLORS.STROKE widget.ctx.lineWidth = .5 widget.gen = Gibber.__gen.gen.lastConnected widget.values = [] widget.min = 10000 widget.max = -10000 let isAssignment = false if( widget.gen === null || widget.gen === undefined ) { if( node.expression.type === 'AssignmentExpression' ) { isAssignment = true widget.gen = window[ node.expression.left.name ] if( widget.gen.widget !== undefined ) { widget.gen.widget.parentNode.removeChild( widget.gen.widget ) } widget.gen.widget = widget } }else{ if( widget.gen.widget !== undefined && widget.gen.widget !== widget ) { isAssignment = true widget.gen = window[ node.expression.left.name ] } } Gibber.__gen.gen.lastConnected = null for( let i = 0; i < 120; i++ ) widget.values[ i ] = 0 if( isAssignment === false ) { let oldWidget = Marker.genWidgets[ widget.gen.paramID ] if( oldWidget !== undefined ) { oldWidget.parentNode.removeChild( oldWidget ) } } Marker.genWidgets[ widget.gen.paramID ] = widget widget.gen.widget = widget widget.mark = cm.markText({ line, ch }, { line, ch:end+1 }, { replacedWith:widget }) widget.mark.__clear = widget.mark.clear widget.clear = ()=> widget.mark.clear() widget.mark.clear = function() { widget.mark.__clear() } }, // currently called when a network snapshot message is received providing ugen state.. // needs to also be called for wavepatterns. updateWidget( id, __value ) { const widget = typeof id !== 'object' ? Marker.genWidgets[ id ] : id if( widget === undefined ) return const value = parseFloat( __value ) if( typeof widget.values[72] !== 'object' ) { widget.values[ 72 ] = value } if( value > widget.max ) { widget.max = value }else if( value < widget.min ) { widget.min = value } widget.values.shift() Marker.genWidgets.dirty = true }, // called by animation scheduler if Marker.genWidgets.dirty === true drawWidgets() { Marker.genWidgets.dirty = false for( let key in Marker.genWidgets ) { let widget = Marker.genWidgets[ key ] if( typeof widget === 'object' && widget.ctx !== undefined ) { widget.ctx.fillStyle = COLORS.FILL widget.ctx.fillRect( 0,0, widget.width, widget.height ) widget.ctx.beginPath() widget.ctx.moveTo( 0, widget.height / 2 + 1 ) const range = widget.max - widget.min const wHeight = widget.height * .9 + .5 for( let i = 0, len = widget.width; i < len; i++ ) { const data = widget.values[ i ] const shouldDrawDot = typeof data === 'object' const value = shouldDrawDot ? data.value : data const scaledValue = ( value - widget.min ) / range const yValue = scaledValue * wHeight - .5 if( shouldDrawDot === true ) { widget.ctx.fillStyle = COLORS.DOT widget.ctx.fillRect( i-1, wHeight - yValue - 1, 2, 2) }else{ widget.ctx.lineTo( i, wHeight - yValue ) } } widget.ctx.stroke() } } }, clear() { for( let key in Marker.genWidgets ) { let widget = Marker.genWidgets[ key ] if( typeof widget === 'object' ) { widget.mark.clear() //widget.parentNode.removeChild( widget ) } } Marker.genWidgets = { dirty:false } }, _process: { ExpressionStatement( expressionNode, codemirror, track ){ Marker._process[ expressionNode.expression.type ]( expressionNode, codemirror, track ) }, AssignmentExpression: function( expressionNode, codemirror, track ) { // if the right-hand op is not a literal and we have a defined annotation for it (in Marker.functions) if( expressionNode.expression.right.type !== 'Literal' && Marker.functions[ expressionNode.expression.right.callee.name ] ) { Marker.functions[ expressionNode.expression.right.callee.name ]( expressionNode.expression.right, codemirror, track, expressionNode.expression.left.name, expressionNode.verticalOffset, expressionNode.horizontalOffset ) }else{ // if it's a gen~ object we need to store a reference so that we can create wavepattern // annotations when appropriate. const left = expressionNode.expression.left const right= expressionNode.expression.right Marker.globalIdentifiers[ left.name ] = right } }, CallExpression( expressionNode, codemirror, track ) { let [ components, depthOfCall, index ] = Marker._getCallExpressionHierarchy( expressionNode.expression ), args = expressionNode.expression.arguments, usesThis, targetPattern, isTrack, method, target // if index is passed as argument to .seq call... if( args.length > 2 ) { index = args[ 2 ].value } //console.log( "depth of call", depthOfCall, components, index ) let valuesPattern, timingsPattern, valuesNode, timingsNode switch( callDepths[ depthOfCall ] ) { case 'SCORE': //console.log( 'score no assignment?', components, expressionNode.expression ) if( Marker.functions[ expressionNode.expression.callee.name ] ) { Marker.functions[ expressionNode.expression.callee.name ]( expressionNode.expression, codemirror, track, expressionNode.verticalOffset ) } break; case 'THIS.METHOD': // also for calls to Score([]).start() break; case 'THIS.METHOD.SEQ': if( components.includes( 'tracks' ) ) { //expressionNode.expression.callee.object.object.type !== 'ThisExpression' ) { let objName = expressionNode.expression.callee.object.object.name track = window[ objName ] method = track[ components[ 1 ] ][ index ] }else if( components.includes( 'this' ) ){ track = Gibber.currentTrack method = track[ components[ 1 ] ][ index ] }else{ let objName = expressionNode.expression.callee.object.object.name track = window[ components[0] ] method = track[ components[ 1 ] ][ index ] } if( !track.markup ) { Marker.prepareObject( track ) } valuesPattern = method.values timingsPattern = method.timings valuesNode = args[ 0 ] timingsNode= args[ 1 ] valuesPattern.codemirror = timingsPattern.codemirror = codemirror if( valuesNode ) { Marker._markPattern[ valuesNode.type ]( valuesNode, expressionNode, components, codemirror, track, index, 'values', valuesPattern ) } if( timingsNode ) { Marker._markPattern[ timingsNode.type ]( timingsNode, expressionNode, components, codemirror, track, index, 'timings', timingsPattern ) } break; case 'THIS.METHOD[ 0 ].SEQ': // will this ever happen??? I guess after it has been sequenced once? isTrack = trackNames.includes( components[0] ) target = null track = window[ components[0] ][ components[1] ] if( !isTrack ) { // not a track! XXX please, please get a better parsing method / rules... target = track track = Gibber.currentTrack } valuesPattern = target === null ? track[ components[2] ][ index ].values : target[ components[2] ].values timingsPattern = target === null ? track[ components[2] ][ index ].timings : target[ components[2] ].timings //track[ components[2] ][ index ].timings valuesNode = args[0] timingsNode = args[1] valuesPattern.codemirror = codemirror if( timingsPattern !== undefined ) timingsPattern.codemirror = codemirror if( valuesNode ) { Marker._markPattern[ valuesNode.type ]( valuesNode, expressionNode, components, codemirror, track, index, 'values', valuesPattern ) } if( timingsNode ) { Marker._markPattern[ timingsNode.type ]( timingsNode, expressionNode, components, codemirror, track, index, 'timings', timingsPattern ) } break; case 'THIS.METHOD.VALUES.REVERSE.SEQ': usesThis = components.includes( 'this' ) isTrack = components.includes( 'tracks' ) if( isTrack ) { // XXX this won't ever get called here, right? track = window[ components[0] ][ components[1] ] targetPattern = track[ components[2] ][ components[3] ] method = targetPattern[ components[4] ] }else{ track = usesThis ? Gibber.currentTrack : window[ components[0] ], targetPattern = track[ components[1] ][ components[2] ], method = targetPattern[ components[3] ] } valuesPattern = method.values timingsPattern = method.timings valuesNode = args[0] timingsNode = args[1] valuesPattern.codemirror = timingsPattern.codemirror = codemirror if( valuesNode ) { Marker._markPattern[ valuesNode.type ]( valuesNode, expressionNode, components, codemirror, track, index, 'values', valuesPattern ) } if( timingsNode ) { Marker._markPattern[ timingsNode.type ]( timingsNode, expressionNode, components, codemirror, track, index, 'timings', timingsPattern ) } break; case 'THIS.METHOD[ 0 ].VALUES.REVERSE.SEQ': // most useful? usesThis = components.includes( 'this' ) isTrack = components.includes( 'tracks' ) // tracks['1-Impulse 606'].devices['Impulse']['Global Time'] if( isTrack ) { track = window[ components[0] ][ components[1] ] targetPattern = track[ components[2] ][ components[3] ] method = targetPattern[ components[4] ] }else{ track = usesThis ? Gibber.currentTrack : window[ components[0] ], targetPattern = track[ components[1] ][ index ][ components[3] ], method = targetPattern[ components[4] ] } valuesPattern = method.values timingsPattern = method.timings valuesNode = args[ 0 ] timingsNode= args[ 1 ] valuesPattern.codemirror = timingsPattern.codemirror = codemirror if( !isTrack ) components.splice( 2,index ) if( valuesNode ) { Marker._markPattern[ valuesNode.type ]( valuesNode, expressionNode, components, codemirror, track, index, 'values', valuesPattern ) } if( timingsNode ) { Marker._markPattern[ timingsNode.type ]( timingsNode, expressionNode, components, codemirror, track, index, 'timings', timingsPattern ) } break; case 'TRACKS[0].METHOD[ 0 ].VALUES.REVERSE.SEQ': track = window[ 'tracks' ][ components[ 1 ] ] components[3] = components[3] valuesPattern = track[ components[ 2 ] ][ components[3] ][ components[4] ][ components[5] ].values timingsPattern = track[ components[ 2 ] ][ components[3] ][ components[4] ][ components[5] ].timings valuesNode = args[ 0 ] timingsNode= args[ 1 ] valuesPattern.codemirror = timingsPattern.codemirror = codemirror if( valuesNode ) { Marker._markPattern[ valuesNode.type ]( valuesNode, expressionNode, components, codemirror, track, index, 'values', valuesPattern ) } if( timingsNode ) { Marker._markPattern[ timingsNode.type ]( timingsNode, expressionNode, components, codemirror, track, index, 'timings', timingsPattern ) } break; default: console.log( 'default annotation error' ) break; } }, }, _createBorderCycleFunction( classNamePrefix, patternObject ) { let modCount = 0, lastBorder = null, lastClassName = null let cycle = function( isArray = false ) { let className = '.' + classNamePrefix + '_' + patternObject.update.currentIndex, border = 'top' switch( modCount++ % 4 ) { case 1: border = 'right'; break; case 2: border = 'bottom'; break; case 3: border = 'left'; break; } // for a pattern holding arrays... like for chord() if( isArray === true ) { switch( border ) { case 'left': $( className ).remove( 'annotation-' + lastBorder + '-border-cycle' ) $( className + '_start' ).add( 'annotation-left-border-cycle' ) break; case 'right': $( className ).remove( 'annotation-' + lastBorder + '-border-cycle' ) $( className + '_end' ).add( 'annotation-right-border-cycle' ) break; case 'top': $( className ).add( 'annotation-top-border-cycle' ) $( className+'_start' ).remove( 'annotation-left-border-cycle' ) $( className+'_start' ).add( 'annotation-top-border-cycle' ) $( className+'_end' ).add( 'annotation-top-border-cycle' ) break; case 'bottom': $( className ).add( 'annotation-bottom-border-cycle' ) $( className+'_end' ).remove( 'annotation-right-border-cycle' ) $( className+'_end' ).add( 'annotation-bottom-border-cycle' ) $( className+'_start' ).add( 'annotation-bottom-border-cycle' ) break; default: $( className ).add( 'annotation-' + border + '-border-cycle' ) $( className+'_start' ).remove( 'annotation-' + border + '-border-cycle' ) $( className+'_end' ).remove( 'annotation-' + border + '-border-cycle' ) break; } }else{ $( className ).remove( 'annotation-' + border + '-border' ) $( className ).add( 'annotation-' + border + '-border-cycle' ) if( lastBorder !== null ) { $( className ).remove( 'annotation-' + lastBorder + '-border-cycle' ) $( className ).add( 'annotation-' + lastBorder + '-border' ) } } lastBorder = border lastClassName = className //console.log( 'cycle idx:', patternObject.idx ) } cycle.clear = function() { modCount = 1 if( lastClassName !== null ) { $( lastClassName ).remove( 'annotation-left-border' ) $( lastClassName ).remove( 'annotation-left-border-cycle' ) $( lastClassName ).remove( 'annotation-right-border' ) $( lastClassName ).remove( 'annotation-right-border-cycle' ) $( lastClassName ).remove( 'annotation-top-border' ) $( lastClassName ).remove( 'annotation-top-border-cycle' ) $( lastClassName ).remove( 'annotation-bottom-border' ) $( lastClassName ).remove( 'annotation-bottom-border-cycle' ) } lastBorder = null } return cycle }, _addPatternUpdates( patternObject, className ) { let cycle = Marker._createBorderCycleFunction( className, patternObject ) patternObject.update = () => { // if( !patternObject.update.shouldUpdate ) return cycle() } }, _addPatternFilter( patternObject ) { patternObject.filters.push( args => { const wait = Utility.beatsToMs( patternObject.nextTime + .5, Gibber.Scheduler.bpm ), idx = args[ 2 ], shouldUpdate = patternObject.update.shouldUpdate // delay is used to ensure that timings pattern is processed after values pattern, // because changing the mark of the values pattern messes up the mark of the timings // pattern; reversing their order of execution fixes this. if( patternObject.__delayAnnotations === true ) { Gibber.Environment.animationScheduler.add( () => { patternObject.update.currentIndex = idx patternObject.update() }, wait + 1 ) }else{ Gibber.Environment.animationScheduler.add( () => { patternObject.update.currentIndex = idx patternObject.update() }, wait ) } return args }) }, _markPattern: { Literal( patternNode, containerNode, components, cm, track, index=0, patternType, patternObject ) { let [ className, start, end ] = Marker._getNamesAndPosition( patternNode, containerNode, components, index, patternType ), cssName = className + '_0', marker = cm.markText( start, end, { 'className': cssName + ' annotation-border', inclusiveLeft: true, inclusiveRight: true }) track.markup.textMarkers[ className ] = marker if( track.markup.cssClasses[ className ] === undefined ) track.markup.cssClasses[ className ] = [] track.markup.cssClasses[ className ][ index ] = cssName Marker._addPatternUpdates( patternObject, className ) Marker._addPatternFilter( patternObject ) patternObject.patternName = className patternObject._onchange = () => { Marker._updatePatternContents( patternObject, className, track ) } }, UnaryExpression( patternNode, containerNode, components, cm, channel, index=0, patternType, patternObject ) { // -1 etc. let [ className, start, end ] = Marker._getNamesAndPosition( patternNode, containerNode, components, index, patternType ), cssName = className + '_0', marker = cm.markText( start, end, { 'className': cssName + ' annotation', inclusiveLeft: true, inclusiveRight: true }) channel.markup.textMarkers[ className ] = marker if( channel.markup.cssClasses[ className ] === undefined ) channel.markup.cssClasses[ className ] = [] channel.markup.cssClasses[ className ][ index ] = cssName let start2 = Object.assign( {}, start ) start2.ch += 1 let marker2 = cm.markText( start, start2, { 'className': cssName + ' annotation-no-right-border', inclusiveLeft: true, inclusiveRight: true }) let marker3 = cm.markText( start2, end, { 'className': cssName + ' annotation-no-left-border', inclusiveLeft: true, inclusiveRight: true }) Marker._addPatternUpdates( patternObject, className ) Marker._addPatternFilter( patternObject ) patternObject.patternName = className patternObject._onchange = () => { Marker._updatePatternContents( patternObject, className, channel ) } }, BinaryExpression( patternNode, containerNode, components, cm, track, index=0, patternType, patternObject ) { // TODO: same as literal, refactor? let [ className, start, end ] = Marker._getNamesAndPosition( patternNode, containerNode, components, index, patternType ), cssName = className + '_0', marker = cm.markText( start, end, { 'className': cssName + ' annotation annotation-border' , startStyle: 'annotation-no-right-border', endStyle: 'annotation-no-left-border', inclusiveLeft:true, inclusiveRight:true } ) track.markup.textMarkers[ className ] = marker const divStart = Object.assign( {}, start ) const divEnd = Object.assign( {}, end ) divStart.ch += 1 divEnd.ch -= 1 const marker2 = cm.markText( divStart, divEnd, { className:'annotation-binop-border' }) if( track.markup.cssClasses[ className ] === undefined ) track.markup.cssClasses[ className ] = [] track.markup.cssClasses[ className ][ index ] = cssName //setTimeout( () => { $( '.' + cssName )[ 1 ].classList.add( 'annotation-no-horizontal-border' ) }, 250 ) patternObject.patternName = className Marker._addPatternUpdates( patternObject, className ) Marker._addPatternFilter( patternObject ) }, ArrayExpression( patternNode, containerNode, components, cm, channel, index=0, patternType, patternObject ) { let [ patternName, start, end ] = Marker._getNamesAndPosition( patternNode, containerNode, components, index, patternType ) let count = 0 for( let element of patternNode.elements ) { let cssClassName = patternName + '_' + count, elementStart = Object.assign( {}, start ), elementEnd = Object.assign( {}, end ), marker elementStart.ch = element.start + containerNode.horizontalOffset elementEnd.ch = element.end + containerNode.horizontalOffset if( element.type === 'BinaryExpression' ) { marker = cm.markText( elementStart, elementEnd, { 'className': cssClassName + ' annotation', startStyle: 'annotation-no-right-border', endStyle: 'annotation-no-left-border', inclusiveLeft:true, inclusiveRight:true }) // create specific border for operator: top, bottom, no sides const divStart = Object.assign( {}, elementStart ) const divEnd = Object.assign( {}, elementEnd ) divStart.ch += 1 divEnd.ch -= 1 const marker2 = cm.markText( divStart, divEnd, { className:cssClassName + '_binop annotation-binop' }) }else if (element.type === 'UnaryExpression' ) { marker = cm.markText( elementStart, elementEnd, { 'className': cssClassName + ' annotation', inclusiveLeft: true, inclusiveRight: true }) let start2 = Object.assign( {}, elementStart ) start2.ch += 1 let marker2 = cm.markText( elementStart, start2, { 'className': cssClassName + ' annotation-no-right-border', inclusiveLeft: true, inclusiveRight: true }) let marker3 = cm.markText( start2, elementEnd, { 'className': cssClassName + ' annotation-no-left-border', inclusiveLeft: true, inclusiveRight: true }) }else if( element.type === 'ArrayExpression' ) { marker = cm.markText( elementStart, elementEnd, { 'className': cssClassName + ' annotation', inclusiveLeft:true, inclusiveRight:true, startStyle:'annotation-left-border-start', endStyle: 'annotation-right-border-end', }) // mark opening array bracket const arrayStart_start = Object.assign( {}, elementStart ) const arrayStart_end = Object.assign( {}, elementStart ) arrayStart_end.ch += 1 cm.markText( arrayStart_start, arrayStart_end, { className:cssClassName + '_start' }) // mark closing array bracket const arrayEnd_start = Object.assign( {}, elementEnd ) const arrayEnd_end = Object.assign( {}, elementEnd ) arrayEnd_start.ch -=1 cm.markText( arrayEnd_start, arrayEnd_end, { className:cssClassName + '_end' }) }else{ marker = cm.markText( elementStart, elementEnd, { 'className': cssClassName + ' annotation', inclusiveLeft:true, inclusiveRight:true }) } if( channel.markup.textMarkers[ patternName ] === undefined ) channel.markup.textMarkers[ patternName ] = [] channel.markup.textMarkers[ patternName ][ count ] = marker if( channel.markup.cssClasses[ patternName ] === undefined ) channel.markup.cssClasses[ patternName ] = [] channel.markup.cssClasses[ patternName ][ count ] = cssClassName count++ } let highlighted = { className:null, isArray:false }, cycle = Marker._createBorderCycleFunction( patternName, patternObject ) patternObject.patternType = patternType patternObject.patternName = patternName patternObject.update = () => { let className = '.' + patternName className += '_' + patternObject.update.currentIndex if( highlighted.className !== className ) { // remove any previous annotations for this pattern if( highlighted.className !== null ) { if( highlighted.isArray === false && highlighted.className ) { $( highlighted.className ).remove( 'annotation-border' ) }else{ $( highlighted.className ).remove( 'annotation-array' ) $( highlighted.className + '_start' ).remove( 'annotation-border-left' ) $( highlighted.className + '_end' ).remove( 'annotation-border-right' ) if( $( highlighted.className + '_binop' ).length > 0 ) { $( highlighted.className + '_binop' ).remove( 'annotation-binop-border' ) } } } // add annotation for current pattern element if( Array.isArray( patternObject.values[ patternObject.update.currentIndex ] ) ) { $( className ).add( 'annotation-array' ) $( className + '_start' ).add( 'annotation-border-left' ) $( className + '_end' ).add( 'annotation-border-right' ) highlighted.isArray = true }else{ $( className ).add( 'annotation-border' ) if( $( className + '_binop' ).length > 0 ) { $( className + '_binop' ).add( 'annotation-binop-border' ) } highlighted.isArray = false } highlighted.className = className //cycle.clear() }else{ cycle( true ) // pass true for arrays } } patternObject.clear = () => { if( highlighted.className !== null ) { $( highlighted.className ).remove( 'annotation-border' ) } cycle.clear() } Marker._addPatternFilter( patternObject ) patternObject._onchange = () => { Marker._updatePatternContents( patternObject, patternName, channel ) } }, // CallExpression denotes an array (or other object) that calls a method, like .rnd() // could also represent an anonymous function call, like Rndi(19,40) CallExpression( patternNode, containerNode, components, cm, track, index=0, patternType, patternObject ) { var args = Array.prototype.slice.call( arguments, 0 ) // console.log( patternNode.callee.type, patternObject ) if( patternNode.callee.type === 'MemberExpression' && patternNode.callee.object.type === 'ArrayExpression' ) { args[ 0 ] = patternNode.callee.object Marker._markPattern.ArrayExpression.apply( null, args ) } else if (patternNode.callee.type === 'Identifier' ) { // function like Euclid Marker._markPattern.Identifier.apply( null, args ) } }, FunctionExpression( patternNode, containerNode, components, cm, channel, index=0, patternType, patternObject ) { const args = Array.prototype.slice.call( arguments, 0 ) Marker._markPattern.Identifier( ...args ) }, ArrowFunctionExpression( patternNode, containerNode, components, cm, channel, index=0, patternType, patternObject ) { const args = Array.prototype.slice.call( arguments, 0 ) Marker._markPattern.Identifier( ...args ) }, Identifier( patternNode, containerNode, components, cm, track, index=0, patternType, patternObject ) { // mark up anonymous functions with comments here... let [ className, start, end ] = Marker._getNamesAndPosition( patternNode, containerNode, components, index, patternType ), commentStart = end, commentEnd = {}, marker = null Object.assign( commentEnd, commentStart ) commentEnd.ch += 1 marker = cm.markText( commentStart, commentEnd, { className }) // if( track.markup.textMarkers[ className ] === undefined ) track.markup.textMarkers[ className ] = [] // track.markup.textMarkers[ className ][ 0 ] = marker // console.log( 'name', patternNode.callee.name ) let updateName = typeof patternNode.callee !== 'undefined' ? patternNode.callee.name : patternNode.name if( Marker.patternUpdates[ updateName ] ) { patternObject.update = Marker.patternUpdates[ updateName ]( patternObject, marker, className, cm, track ) } else { patternObject.update = Marker.patternUpdates.anonymousFunction( patternObject, marker, className, cm, track ) } patternObject.patternName = className // store value changes in array and then pop them every time the annotation is updated patternObject.update.value = [] Marker._addPatternFilter( patternObject ) }, }, patternUpdates: { Euclid: ( patternObject, marker, className, cm, track ) => { let val ='/* ' + patternObject.values.join('') + ' */', pos = marker.find(), end = Object.assign( {}, pos.to ), annotationStartCh = pos.from.ch + 3, annotationEndCh = annotationStartCh + 1, memberAnnotationStart = Object.assign( {}, pos.from ), memberAnnotationEnd = Object.assign( {}, pos.to ), initialized = false, markStart = null, commentMarker, currentMarker, chEnd end.ch = pos.from.ch + val.length pos.to.ch -= 1 cm.replaceRange( val, pos.from, pos.to ) patternObject.commentMarker = cm.markText( pos.from, end, { className, atomic:false}) //replacedWith:element }) track.markup.textMarkers[ className ] = {} let mark = () => { // first time through, use the position given to us by the parser let range,start, end if( initialized === false ) { memberAnnotationStart.ch = annotationStartCh memberAnnotationEnd.ch = annotationEndCh initialized = true }else{ // after the first time through, every update to the pattern store the current // position of the first element (in markStart) before replacing. Use this to generate position // info. REPLACING TEXT REMOVES TEXT MARKERS. range = markStart start = range.from memberAnnotationStart.ch = start.ch memberAnnotationEnd.ch = start.ch + 1 } for( let i = 0; i < patternObject.values.length; i++ ) { track.markup.textMarkers[ className ][ i ] = cm.markText( memberAnnotationStart, memberAnnotationEnd, { 'className': `${className}_${i}` } ) memberAnnotationStart.ch += 1 memberAnnotationEnd.ch += 1 } if( start !== undefined ) { start.ch -= 3 end = Object.assign({}, start ) end.ch = memberAnnotationEnd.ch + 3 patternObject.commentMarker = cm.markText( start, end, { className, atomic:true }) } } mark() // XXX: there's a bug when you sequence pattern transformations, and then insert newlines ABOVE the annotation let count = 0, span, update, activeSpans = [] update = () => { let currentIdx = count++ % patternObject.values.length if( span !== undefined ) { span.remove( 'euclid0' ) } let spanName = `.${className}_${currentIdx}`, currentValue = patternObject.values[ currentIdx ] span = $( spanName ) if( currentValue === 1 ) { span.add( 'euclid1' ) activeSpans.push( span ) setTimeout( ()=> { activeSpans.forEach( _span => _span.remove( 'euclid1' ) ) activeSpans.length = 0 }, 50 ) }else{ span.add( 'euclid0' ) } } patternObject._onchange = () => { let delay = Utility.beatsToMs( 1, Gibber.Scheduler.bpm ) markStart = track.markup.textMarkers[ className ][ 0 ].find() Gibber.Environment.animationScheduler.add( () => { for( let i = 0; i < patternObject.values.length; i++ ) { let markerCh = track.markup.textMarkers[ className ][ i ], pos = markerCh.find() marker.doc.replaceRange( '' + patternObject.values[ i ], pos.from, pos.to ) } mark() }, delay ) } patternObject.clear = () => { try{ let commentPos = patternObject.commentMarker.find() cm.replaceRange( '', commentPos.from, commentPos.to ) patternObject.commentMarker.clear() } catch( e ) { console.log( 'euclid annotation error:', e ) } // yes, I just did that XXX } return update }, anonymousFunction: ( patternObject, marker, className, cm ) => { patternObject.commentMarker = marker let update = () => { if( !patternObject.commentMarker ) return let patternValue = '' + patternObject.update.value.pop() if( patternValue.length > 8 ) patternValue = patternValue.slice(0,8) let val ='/* ' + patternValue + ' */,', pos = patternObject.commentMarker.find(), end = Object.assign( {}, pos.to ) //pos.from.ch += 1 end.ch = pos.from.ch + val.length //pos.from.ch += 1 cm.replaceRange( val, pos.from, pos.to ) if( patternObject.commentMarker ) patternObject.commentMarker.clear() patternObject.commentMarker = cm.markText( pos.from, end, { className, atomic:false }) } patternObject.clear = () => { try{ let commentPos = patternObject.commentMarker.find() commentPos.to.ch -= 1 // XXX wish I didn't have to do this cm.replaceRange( '', commentPos.from, commentPos.to ) patternObject.commentMarker.clear() delete patternObject.commentMarker } catch( e ) {} // yes, I just did that XXX } return update } }, functions:{ Score( node, cm, track, objectName, vOffset=0 ) { let timelineNodes = node.arguments[ 0 ].elements //console.log( timelineNodes ) track.markup.textMarkers[ 'score' ] = [] for( let i = 0; i < timelineNodes.length; i+=2 ) { let timeNode = timelineNodes[ i ], functionNode = timelineNodes[ i + 1 ] functionNode.loc.start.line += vOffset - 1 functionNode.loc.end.line += vOffset - 1 functionNode.loc.start.ch = functionNode.loc.start.column functionNode.loc.end.ch = functionNode.loc.end.column let marker = cm.markText( functionNode.loc.start, functionNode.loc.end, { className:`score${i/2}` } ) track.markup.textMarkers[ 'score' ][ i/2 ] = marker } let lastClass = 'score0' $( '.' + lastClass ).add( 'scoreCurrentIndex' ) // TODO: global object usage is baaaad methinks? window[ objectName ].onadvance = ( idx ) => { $( '.' + lastClass ).remove( 'scoreCurrentIndex' ) lastClass = `score${idx}` $( '.' + lastClass ).add( 'scoreCurrentIndex' ) } }, Steps( node, cm, track, objectName, vOffset=0 ) { let steps = node.arguments[ 0 ].properties track.markup.textMarkers[ 'step' ] = [] track.markup.textMarkers[ 'step' ].children = [] let mark = ( _step, _key, _cm, _track ) => { for( let i = 0; i < _step.value.length; i++ ) { let pos = { loc:{ start:{}, end:{}} } Object.assign( pos.loc.start, _step.loc.start ) Object.assign( pos.loc.end , _step.loc.end ) pos.loc.start.ch += i pos.loc.end.ch = pos.loc.start.ch + 1 let posMark = _cm.markText( pos.loc.start, pos.loc.end, { className:`step_${_key}_${i}` }) _track.markup.textMarkers.step[ _key ].pattern[ i ] = posMark } } for( let key in steps ) { let step = steps[ key ].value if( step && step.value ) { // ensure it is a correctly formed step step.loc.start.line += vOffset - 1 step.loc.end.line += vOffset - 1 step.loc.start.ch = step.loc.start.column + 1 step.loc.end.ch = step.loc.end.column - 1 let marker = cm.markText( step.loc.start, step.loc.end, { className:`step${key}` } ) track.markup.textMarkers.step[ key ] = marker track.markup.textMarkers.step[ key ].pattern = [] mark( step, key, cm, track ) let count = 0, span, update, _key = steps[ key ].key.value, patternObject = window[ objectName ].seqs[ _key ].values update = () => { let currentIdx = update.currentIndex // count++ % step.value.length if( span !== undefined ) { span.remove( 'euclid0' ) span.remove( 'euclid1' ) } let spanName = `.step_${key}_${currentIdx}`, currentValue = patternObject.update.value.pop() //step.value[ currentIdx ] span = $( spanName ) if( currentValue !== Gibber.Seq.DO_NOT_OUTPUT ) { span.add( 'euclid1' ) setTimeout( ()=> { span.remove( 'euclid1' ) }, 50 ) } span.add( 'euclid0' ) } patternObject._onchange = () => { let delay = Utility.beatsToMs( 1, Gibber.Scheduler.bpm ) Gibber.Environment.animationScheduler.add( () => { marker.doc.replaceRange( patternObject.values.join(''), step.loc.start, step.loc.end ) mark( step, key, cm, track ) }, delay ) } patternObject.update = update patternObject.update.value = [] Marker._addPatternFilter( patternObject ) } } }, }, _updatePatternContents( pattern, patternClassName, track ) { let marker, pos, newMarker if( pattern.values.length > 1 ) { // array of values for( let i = 0; i < pattern.values.length; i++) { marker = track.markup.textMarkers[ patternClassName ][ i ] pos = marker.find() marker.doc.replaceRange( '' + pattern.values[ i ], pos.from, pos.to ) } }else{ // single literal marker = track.markup.textMarkers[ patternClassName ] pos = marker.find() marker.doc.replaceRange( '' + pattern.values[ 0 ], pos.from, pos.to ) // newMarker = marker.doc.markText( pos.from, pos.to, { className: patternClassName + ' annotation-border' } ) // track.markup.textMarkers[ patternClassName ] = newMarker } }, _getNamesAndPosition( patternNode, containerNode, components, index=0, patternType ) { let start = patternNode.loc.start, end = patternNode.loc.end, className = components.slice( 0 ), cssName = null, marker if( className.includes( 'this' ) ) className.shift() if( className.includes( 'tracks' ) ) { //className = [ 'tracks', components[1] ].concat( components.slice( 2, components.length - 1 ) ) if( index !== 0 ) { className.splice( 3, 0, index ) } }else{ if( index !== 0 ) { className.splice( 1, 0, index ) // insert index into array } } className.push( patternType ) className = className.join( '_' ) let expr = /\[\]/gi className = className.replace( expr, '' ) expr = /\-/gi className = className.replace( expr, '_' ) expr = /\ /gi className = className.replace( expr, '_' ) start.line += containerNode.verticalOffset - 1 end.line += containerNode.verticalOffset - 1 start.ch = start.column + containerNode.horizontalOffset end.ch = end.column + containerNode.horizontalOffset return [ className, start, end ] }, _getCallExpressionHierarchy( expr ) { let callee = expr.callee, obj = callee.object, components = [], index = 0, depth = 0 while( obj !== undefined ) { let pushValue = null if( obj.type === 'ThisExpression' ) { pushValue = 'this' }else if( obj.property && obj.property.name ){ pushValue = obj.property.name }else if( obj.property && obj.property.type === 'Literal' ){ // array index pushValue = obj.property.value // don't fall for tracks[0] etc. if( depth > 1 ) index = obj.property.value }else if( obj.type === 'Identifier' ) { pushValue = obj.name } if( pushValue !== null ) components.push( pushValue ) depth++ obj = obj.object } components.reverse() if( callee.property ) components.push( callee.property.name ) return [ components, depth, index ] }, } module.exports = Marker