gibberwocky
Version:
music sequencing for Live and Max/MSP
1,537 lines (1,192 loc) • 2.76 MB
JavaScript
(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
const Queue = require( './priorityqueue.js' )
let Gibber = null
let Scheduler = {
currentTime : performance.now(),
queue: new Queue( ( a, b ) => a.time - b.time ),
visualizationTime: {
init:true,
base:0,
phase:0,
},
init( __Gibber ) {
Gibber = __Gibber
window.requestAnimationFrame( this.onAnimationFrame )
},
clear() {
this.queue.data.length = 0
},
add( func, offset, idx ) {
let time = this.currentTime + offset
this.queue.push({ func, time })
return time
},
runSoon( evt ) {
try{
evt.func()
}catch(e) {
console.log( 'annotation error:', e.toString() )
}
},
run( timestamp, dt ) {
let nextEvent = this.queue.peek()
//if( nextEvent === undefined ) return
if( nextEvent !== undefined && this.queue.length > 0 && nextEvent.time <= timestamp ) {
// remove event
this.queue.pop()
setTimeout( ()=> this.runSoon( nextEvent ) )
//try{
// nextEvent.func()
//}catch( e ) {
// console.log( e )
// Gibber.Environment.error( 'annotation error:', e.toString() )
//}
// call recursively
this.run( timestamp )
}
if( Gibber.Environment.codeMarkup.waveform.widgets.dirty === true ) {
Gibber.Environment.codeMarkup.waveform.drawWidgets()
}
},
onAnimationFrame( timestamp ) {
window.requestAnimationFrame( this.onAnimationFrame )
const diff = timestamp - this.currentTime
this.currentTime = timestamp
this.visualizationTime.phase += diff
this.run( timestamp, diff )
},
updateVisualizationTime( ms ) {
if( this.visualizationTime.init === true ) {
this.visualizationTime.base += ms
this.visualizationTime.phase = 0
}else{
this.visualizationTime.init = true
}
},
}
Scheduler.onAnimationFrame = Scheduler.onAnimationFrame.bind( Scheduler )
module.exports = Scheduler
},{"./priorityqueue.js":42}],2:[function(require,module,exports){
const Utility = require( '../../utility.js' )
const $ = Utility.create
module.exports = function( Marker ) {
'use strict'
const ArrayExpression = function( patternNode, state, seq, patternType, container=null, index=0, isLookup=false ) {
if( patternNode.processed === true ) return
const cm = state.cm
const track = seq.object
const patternObject = seq[ patternType ]
const [ patternName, start, end ] = Marker._getNamesAndPosition( patternNode, state, patternType, index )
const cssName = patternName
patternObject.markers = []
if( track.markup === undefined ) Marker.prepareObject( track )
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.loc.start.column// + Marker.offset.horizontal
elementEnd.ch = element.loc.end.column // + Marker.offset.horizontal
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' })
patternObject.markers.push( marker, marker2 )
}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
})
patternObject.markers.push( marker, marker2, marker3 )
}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
const marker2 = cm.markText( arrayEnd_start, arrayEnd_end, { className:cssClassName + '_end' })
patternObject.markers.push( marker, marker2 )
}else{
marker = cm.markText( elementStart, elementEnd, {
'className': cssClassName + ' annotation',
//inclusiveLeft:true, inclusiveRight:true
})
patternObject.markers.push( marker )
}
if( track.markup.textMarkers[ patternName ] === undefined ) track.markup.textMarkers[ patternName ] = []
track.markup.textMarkers[ patternName ][ count ] = marker
if( track.markup.cssClasses[ patternName ] === undefined ) track.markup.cssClasses[ patternName ] = []
track.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
const values = isLookup === false ? patternObject.values : patternObject._values
if( Array.isArray( 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()
}
}
// check to see if a clear function already exists and save reference
// XXX should clear be saved somewhere else... on the update function?
let __clear = null
if( patternObject.clear !== undefined ) __clear = patternObject.clear
patternObject.clear = () => {
if( highlighted.className !== null ) { $( highlighted.className ).remove( 'annotation-border' ) }
cycle.clear()
patternObject.markers.forEach( marker => marker.clear() )
if( __clear !== null ) __clear()
}
Marker._addPatternFilter( patternObject )
patternObject._onchange = () => { Marker._updatePatternContents( patternObject, patternName, track ) }
}
return ArrayExpression
}
},{"../../utility.js":49}],3:[function(require,module,exports){
module.exports = function( Marker ) {
'use strict'
// 1/4, 1/8 etc.
const BinaryExpression = function( patternNode, state, seq, patternType,container=null, index=0 ) {
if( patternNode.processed === true ) return
const cm = state.cm
const seqTarget = seq.object
const patternObject = seq[ patternType ]
const [ className, start, end ] = Marker._getNamesAndPosition( patternNode, state, patternType, index )
const cssName = className
const marker = cm.markText(
start,
end,
{
'className': cssName + ' annotation annotation-border' ,
startStyle: 'annotation-no-right-border',
endStyle: 'annotation-no-left-border',
//inclusiveLeft:true,
//inclusiveRight:true
}
)
if( seqTarget.markup === undefined ) Marker.prepareObject( seqTarget )
seqTarget.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( seqTarget.markup.cssClasses[ className ] === undefined ) seqTarget.markup.cssClasses[ className ] = []
seqTarget.markup.cssClasses[ className ][ index ] = cssName
patternObject.marker = marker
Marker.finalizePatternAnnotation( patternObject, className, seqTarget, marker )
}
return BinaryExpression
}
},{}],4:[function(require,module,exports){
module.exports = function( Marker ) {
'use strict'
// CallExpression denotes an array (or other object) that calls a method, like .rnd()
// could also represent an anonymous function call, like Rndi(19,40)
const CallExpression = function( patternNode, state, seq, patternType, index=0 ) {
if( patternNode.processed === true ) return
var args = Array.prototype.slice.call( arguments, 0 )
if( patternNode.callee.type === 'MemberExpression' && patternNode.callee.object.type === 'ArrayExpression' ) {
args[ 0 ] = patternNode.callee.object
args[ 0 ].offset = Marker.offset
Marker.patternMarkupFunctions.ArrayExpression( ...args )
} else if (patternNode.callee.type === 'Identifier' ) {
// function like Euclid or gen~d
Marker.patternMarkupFunctions.Identifier( ...args )
}
}
return CallExpression
}
},{}],5:[function(require,module,exports){
const __Identifier = function( Marker ) {
const mark = function( node, state, patternType, seqNumber ) {
const [ className, start, end ] = Marker._getNamesAndPosition( node, state, patternType, seqNumber )
const cssName = className + '_' + seqNumber
const commentStart = end
// we define the comment range as being one character, this
// only defines the range of characters that will be replaced
// by the eventual longer comment string.
const commentEnd = Object.assign( {}, commentStart )
commentEnd.ch += 1
const line = end.line
const lineTxt = state.cm.getLine( line )
let ch = end.ch
// for whatever reason, sometimes the ch value leads to
// an undefined final character in the string. In that case,
// work back until we find the actual final character.
let lastChar = lineTxt[ ch ]
while( lastChar === undefined ) {
ch--
lastChar = lineTxt[ ch ]
}
// different replacements are used for use in sequencers, when a callexpression
// creating a wavepattern is often followed by a comma, vs when a wavepattern is
// assigned to a variable, when no comma is present
if( lastChar === ',' ) {
state.cm.replaceRange( ' ,', { line, ch:ch }, { line, ch:ch + 1 } )
}else if( lastChar === ')' ){
state.cm.replaceRange( ') ', { line, ch:ch }, { line, ch:ch + 1 } )
}else{
state.cm.replaceRange( lastChar + ' ', { line, ch:ch }, { line, ch:ch + 1 } )
}
const marker = state.cm.markText( commentStart, commentEnd, { className })
return [ marker, className ]
}
// Typically this is used with named functions. For example, if you store an
// Arp in the variable 'a' and pass 'a' into a sequence, 'a' is the Identifier
// and this function will be called to mark up the associated pattern.
const Identifier = function( patternNode, state, seq, patternType, containerNode, seqNumber ) {
if( patternNode.processed === true ) return
const cm = state.cm
const track = seq.object
const patternObject = seq[ patternType ]
const [ marker, className ] = mark( patternNode, state, patternType, seqNumber )
// WavePatterns can also be passed as named functions; make sure we forward
// these to the appropriate markup functions
if( patternObject.type === 'WavePattern' || patternObject.isGen ) { //|| patternObject.type === 'Lookup' ) {
if( patternObject.widget === undefined ) { // if wavepattern is inlined to .seq
Marker.processGen( containerNode, cm, track, patternObject, seq )
}else{
patternObject.update = Marker.patternUpdates.anonymousFunction( patternObject, marker, className, cm, track )
}
}else{
let updateName = typeof patternNode.callee !== 'undefined' ? patternNode.callee.name : patternNode.name
// this doesn't work for variables storing lookups, as there's no array to highlight
// if( patternObject.type === 'Lookup' ) updateName = 'Lookup'
if( Marker.patternUpdates[ updateName ] ) {
if( updateName !== 'Lookup' ) {
patternObject.update = Marker.patternUpdates[ updateName ]( patternObject, marker, className, cm, track, patternNode )
}else{
Marker.patternUpdates[ updateName ]( patternObject, marker, className, cm, track, patternNode, patternType, seqNumber )
}
} 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 = []
if( updateName !== 'Lookup' )
Marker._addPatternFilter( patternObject )
}
patternObject.marker = marker
}
return Identifier
}
module.exports = __Identifier
},{}],6:[function(require,module,exports){
module.exports = function( Marker ) {
// Marker.patternMarkupFunctions[ valuesNode.type ]( valuesNode, state, seq, 'values', container, seqNumber )
const Literal = function( patternNode, state, seq, patternType, container=null, index=0 ) {
if( patternNode.processed === true ) return
const cm = state.cm
const seqTarget = seq.object
const patternObject = seq[ patternType ]
const [ className, start, end ] = Marker._getNamesAndPosition( patternNode, state, patternType, index )
const cssName = className
const marker = cm.markText( start, end, {
'className': cssName + ' annotation-border',
//inclusiveLeft: true,
//inclusiveRight: true
})
if( seqTarget.markup === undefined ) Marker.prepareObject( seqTarget )
seqTarget.markup.textMarkers[ className ] = marker
if( seqTarget.markup.cssClasses[ className ] === undefined ) seqTarget.markup.cssClasses[ className ] = []
seqTarget.markup.cssClasses[ className ][ index ] = cssName
patternObject.marker = marker
Marker.finalizePatternAnnotation( patternObject, className, seqTarget, marker )
}
return Literal
}
},{}],7:[function(require,module,exports){
module.exports = function( Marker ) {
// for negative literals e.g. -10
const UnaryExpression = function( patternNode, state, seq, patternType, container=null, index=0 ) {
if( patternNode.processed === true ) return
const cm = state.cm
const seqTarget = seq.object
const patternObject = seq[ patternType ]
const [ className, start, end ] = Marker._getNamesAndPosition( patternNode, state, patternType, index )
const cssName = className
marker = cm.markText( start, end, {
'className': cssName + ' annotation',
//inclusiveLeft: true,
//inclusiveRight: true
})
if( seqTarget.markup === undefined ) Marker.prepareObject( seqTarget )
seqTarget.markup.textMarkers[ className ] = marker
if( seqTarget.markup.cssClasses[ className ] === undefined ) seqTarget.markup.cssClasses[ className ] = []
seqTarget.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
})
patternObject.marker = marker
Marker.finalizePatternAnnotation( patternObject, className )
}
return UnaryExpression
}
},{}],8:[function(require,module,exports){
const Utility = require( '../../utility.js' )
const $ = Utility.create
const EuclidAnnotation = require( '../update/euclidAnnotation.js' )
module.exports = function( node, cm, track, objectName, state, cb ) {
const Marker = Gibber.Environment.codeMarkup
const steps = node.arguments[ 0 ].properties
const Identifier = Marker.patternMarkupFunctions.Identifier
track.markup.textMarkers[ 'step' ] = []
track.markup.textMarkers[ 'step' ].children = []
const hexSteps = window[ objectName ]
const objectClassName = objectName + '_steps'
let count = 0
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 += Marker.offset.vertical - 1
step.loc.end.line += Marker.offset.vertical - 1
step.loc.start.ch = step.loc.end.column - 1
step.loc.end.ch = step.loc.end.column
const pattern = hexSteps.seqs[ steps[ key ].key.value ].timings
// we estimate whether or not a comma was used to separate between
// key / value pairs. If there's more than one pattern and this
// isn't the last time through the for loop, we assume there is a
// comma (otherwise an error would occur).
const useComma = count++ != steps.length - 1 && steps.length > 1
if( useComma === true ) {
// move off of end quote to comma
step.loc.start.ch += 1
step.loc.end.ch += 1
// replace comma with a comma and a space
cm.replaceRange( ", ", step.loc.start, step.loc.end )
step.loc.start.ch += 1
step.loc.end.ch += 1
}else{
// replace end quote with a quote and a space
cm.replaceRange( "' ", step.loc.start, step.loc.end )
step.loc.start.ch += 1
step.loc.end.ch += 1
}
let className = objectClassName + '_' + key
let marker = cm.markText( step.loc.start, step.loc.end, { className } )
pattern.update = EuclidAnnotation( pattern, marker, className, cm, track )
pattern.patternName = className
// store value changes in array and then pop them every time the annotation is updated
pattern.update.value = []
Marker._addPatternFilter( pattern )
pattern.marker = marker
/*
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 )
}
*/
}
}
}
},{"../../utility.js":49,"../update/euclidAnnotation.js":13}],9:[function(require,module,exports){
const Utility = require( '../../utility.js' )
const $ = Utility.create
module.exports = function( 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' )
}
}
},{"../../utility.js":49}],10:[function(require,module,exports){
const Utility = require( '../../utility.js' )
const $ = Utility.create
module.exports = function( node, cm, track, objectName, state, cb ) {
const Marker = Gibber.Environment.codeMarkup // tsk tsk tsk global...
const steps = node.arguments[ 0 ].properties
track.markup.textMarkers[ 'step' ] = []
track.markup.textMarkers[ 'step' ].children = []
const 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 += Marker.offset.vertical - 1
step.loc.end.line += Marker.offset.vertical - 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 )
}
}
}
},{"../../utility.js":49}],11:[function(require,module,exports){
module.exports = ( 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
}
},{}],12:[function(require,module,exports){
const Utility = require( '../../utility.js' )
const $ = Utility.create
module.exports = function( classNamePrefix, patternObject ) {
let modCount = 0,
lastBorder = null,
lastClassName = null
const cycle = function( isArray = false ) {
let className = '.' + classNamePrefix,
border = 'top'
// accommodate arrays
if( patternObject.values.length > 1 || patternObject.type === 'Lookup' ) {
className += '_' + patternObject.update.currentIndex
}
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
}
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
}
},{"../../utility.js":49}],13:[function(require,module,exports){
const Utility = require( '../../utility.js' )
const $ = Utility.create
module.exports = ( 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 })
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 )
// deliberate ==
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 = () => {
const commentPos = patternObject.commentMarker.find()
// if this gets called twice...
if( commentPos === undefined ) return
cm.replaceRange( '', commentPos.from, { line:commentPos.to.line, ch:commentPos.to.ch+1 } )
patternObject.commentMarker.clear()
}
return update
}
},{"../../utility.js":49}],14:[function(require,module,exports){
module.exports = ( patternObject, marker, className, cm, track, patternNode, patternType, seqNumber ) => {
Gibber.Environment.codeMarkup.processGen( patternNode, cm, null, patternObject, null, -1 )
patternNode.arguments[1].offset = patternNode.offset
Gibber.Environment.codeMarkup.patternMarkupFunctions.ArrayExpression(
patternNode.arguments[1],
cm.__state,
{ object:patternObject, [ patternType ]: patternObject },
patternType,
null,
seqNumber,
true
)
}
},{}],15:[function(require,module,exports){
module.exports = function( Marker ) {
const strip = function( unstripped ) {
//const unstripped = node.property.type === 'Identifier' ? node.property.name : node.property.raw )
const stripped = unstripped[0] === '"' || unstripped[0] === "'" ? unstripped.slice(1,-1) : unstripped
return stripped
}
const seqSuffixes = ['once', 'repeat', 'stop']
return {
Literal( node, state, cb ) {
state.push( node.value )
},
Identifier( node, state, cb ) {
state.push( strip( node.name ) )
},
AssignmentExpression( expression, state, cb ) {
// the only assignments we're interested in for annotation purposes are
// wavepatterns / gen(ish) expressions, and standalone objects like
// Steps / Arp / Scores. Everything else we can ignore e.g. bass = tracks[0]
// first check to see if the right operand is a callexpression
if( expression.right.type === 'CallExpression' ) {
// if standalone object (Steps, Arp, Score etc.)
if( Marker.standalone[ expression.right.callee.name ] ) {
const obj = window[ expression.left.name ]
if( obj.markup === undefined ) Marker.prepareObject( obj )
Marker.standalone[ expression.right.callee.name ](
expression.right,
state.cm,
obj,
expression.left.name,
state,
cb
)
}else{
// if it's a gen~ object we need to store a reference so that we can create wavepattern
// annotations when appropriate.
const left = expression.left
const right= expression.right
Marker.globalIdentifiers[ left.name ] = right
// XXX does this need a track object? passing null...
// Marker.processGen( expression, state.cm, null)
}
}
},
CallExpression( node, state, cb ) {
cb( node.callee, state )
let endIdx = state.length - 1
const end = state[ endIdx ]
const foundSequence = state.indexOf( 'seq' ) > -1 // end === 'seq'
const isMessage = state.indexOf( 'message' ) > -1
// check to see if .once, .repeat, or .stop is appended to call to .seq
if( seqSuffixes.indexOf( end ) > -1 ) {
endIdx--
if( seqSuffixes.indexOf( node.callee.property.name ) > -1 ) return
//node = node.callee.property
}
if( isMessage === false ) {
if( foundSequence === true ){
const hasSeqNumber = node.arguments.length > 2
let seqNumber = 0
if( hasSeqNumber === true ) {
seqNumber = node.arguments[2].raw
}
const seq = Marker.getObj( state.slice( 0, endIdx ), true, seqNumber )
Marker.markPatternsForSeq( seq, node.arguments, state, cb, node, seqNumber )
}else{
Marker.processGen( node, state.cm, null, null, null, state.indexOf('seq') > -1 ? 0 : -1 )
}
}else{
if( foundSequence === true ){
const hasSeqNumber = node.arguments.length > 2
let seqNumber = 0
if( hasSeqNumber === true ) {
seqNumber = node.arguments[2].raw
}
state[0] = 'message'
state[1] = node.arguments[0].value
state[2] = 'seq'
let seq
if( node.arguments.length > 1 ) {
// this is the call to .seq
state[1] = node.callee.object.arguments[0].value
seq = window.message( node.callee.object.arguments[0].value )
Marker.markPatternsForSeq( seq, node.arguments, state, cb, node, seqNumber )
}else{
// this is the call to message(), which has one argument, the message prefix
seq = window.message( node.arguments[0].value )
}
}
}
},
MemberExpression( node, state, cb ) {
// XXX why was this here?
//if( node.object.name === 'tracks' ) state.length = 0
// for any member name, make sure to get rid of potential quotes surrounding it using
// the strip function.
if( node.object.type !== 'Identifier' ) {
if( node.property ) {
const unstripped = node.property.type === 'Identifier' ? node.property.name : node.property.raw
state.unshift( strip( unstripped ) )
}
cb( node.object, state )
}else{
if( node.property !== undefined ) { // if the objects is an array member, e.g. tracks[0]
state.unshift( strip( node.property.raw || node.property.name ) )
}
state.unshift( strip( node.object.name ) )
}
},
}
}
},{}],16:[function(require,module,exports){
const COLORS = {
FILL:'rgba(46,50,53,1)',
STROKE:'#aaa',
DOT:'rgba(89, 151, 198, 1)'//'rgba(0,0,255,1)'
}
let Gibber = null
const Waveform = {
widgets: { dirty:false },
createWaveformWidget( line, closeParenStart, ch, isAssignment, node, cm, patternObject, track, isSeq=true ) {
let widget = document.createElement( 'canvas' )
widget.padding = 40
widget.waveWidth = 60
widget.ctx = widget.getContext('2d')
widget.style.display = 'inline-block'
widget.style.verticalAlign = 'middle'
widget.style.height = '1.1em'
widget.style.width = ((widget.padding * 2 + widget.waveWidth) * window.devicePixelRation ) + 'px'
widget.style.backgroundColor = 'transparent'
widget.style.margin = '0 1em'
//widget.style.borderLeft = '1px solid #666'
//widget.style.borderRight = '1px solid #666'
widget.setAttribute( 'width', widget.padding * 2 + widget.waveWidth )
widget.setAttribute( 'height', 13 )
widget.ctx.fillStyle = COLORS.FILL
widget.ctx.strokeStyle = COLORS.STROKE
widget.ctx.font = '10px monospace'
widget.ctx.lineWidth = 1
widget.gen = patternObject !== null ? patternObject : Gibber.__gen.gen.lastConnected.shift()
widget.values = []
widget.storage = []
widget.min = 10000
widget.max = -10000
if( widget.gen === null || widget.gen === undefined ) {
if( node.expression !== undefined && 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( node.type === 'CallExpression' ) {
const state = cm.__state
if( node.callee.name !== 'Lookup' ) {
const objName = `${state[0]}`
const track = window.signals[0]//window[ objName ][ state[1] ]
let wave
if( state.length > 2 ) {
wave = track[ node.callee.object.property.value][ node.arguments[2].value ]
}else{
wave = track()
}
if( wave !== undefined && wave.values.type === 'WavePattern' ) {
widget.gen = wave.values
widget.gen.paramID += '_' + node.arguments[2].value
//widget.gen.widget = widget
}
isAssignment = true
}else{
widget.gen = patternObject
}
//if( seq !== undefined && seq.timings.type === 'WavePattern' ) {
//}
}
}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
let replaced = false
//if( isAssignment === false && isSeq === false ) {
// if( widget.gen !== null ) {
// let oldWidget = Waveform.widgets[ widget.gen.paramID ]
// if( oldWidget !== undefined ) {
// //oldWidget.parentNode.removeChild( oldWidget )
// console.log( 'replaced' )
// widget = oldWidget
// replaced = true
// }
// }
//}
if( replaced === false ) {
widget.mark = cm.markText({ line, ch:ch }, { line, ch:ch+1 }, { replacedWith:widget })
widget.mark.__clear = widget.mark.clear
widget.mark.clear = function() {
const pos = widget.mark.find()
if( pos === undefined ) return
widget.mark.__clear()
if( isSeq === true ) { // only replace for waveforms inside of a .seq() call
cm.replaceRange( '', { line:pos.from.line, ch:pos.from.ch }, { line:pos.from.line, ch:pos.to.ch } )
}
}
}
widget.clear = ()=> widget.mark.clear()
if( widget.gen !== null ) {
//console.log( 'paramID = ', widget.gen.paramID )
Waveform.widgets[ widget.gen.paramID ] = widget
widget.gen.widget = widget
}
if( patternObject !== null ) {
patternObject.mark = widget.mark
if( patternObject === Gibber.Gen.lastConnected[0] ) Gibber.Gen.lastConnected.shift()
}
widget.onclick = ()=> {
widget.min = Infinity
widget.max = -Infinity
widget.storage.length = 0
}
},
clear() {
for( let key in Waveform.widgets ) {
let widget = Waveform.widgets[ key ]
if( typeof widget === 'object' ) {
widget.mark.clear()
//widget.parentNode.removeChild( widget )
}
}
Waveform.widgets = { dirty:false }
},
// currently called when a network snapshot message is received providing ugen state..
// needs to also be called for wavepatterns.
updateWidget( id, __value, isFromMax = true ) {
const widget = typeof id !== 'object' ? Waveform.widgets[ id ] : id
if( widget === undefined ) return
let value = parseFloat( __value )
// XXX why does beats generate a downward ramp?
if( isFromMax ) value = 1 - value
if( typeof widget.values[76] !== 'object' ) {
widget.values[ 76 ] = value
widget.storage.push( value )
}
if( widget.storage.length > 120 ) {
widget.max = Math.max.apply( null, widget.storage )
widget.min = Math.min.apply( null, widget.storage )
widget.storage.length = 0
} else if( value > widget.max ) {
widget.max = value
}else if( value < widget.min ) {
widget.min = value
}
widget.values.shift()
Waveform.widgets.dirty = true
},
// called by animation scheduler if Waveform.widgets.dirty === true
drawWidgets() {
Waveform.widgets.dirty = false
const drawn = []
for( let key in Waveform.widgets ) {
if( key === 'dirty' ) continue
const widget = Waveform.widgets[ key ]
// ensure that a widget does not get drawn more
// than once per frame
if( drawn.indexOf( widget ) !== -1 ) continue
if( typeof widget === 'object' && widget.ctx !== undefined ) {
widget.ctx.fillStyle = COLORS.FILL
widget.ctx.fillRect( 0,0, widget.width, widget.height )
// draw left border
widget.ctx.beginPath()
widget.ctx.moveTo( widget.padding + .5, 0.5 )
widget.ctx.lineTo( widget.padding + .5, widget.height + .5 )
widget.ctx.stroke()
// draw right border
widget.ctx.beginPath()
widget.ctx.moveTo( widget.padding + widget.waveWidth + .5, .5 )
widget.ctx.lineTo( widget.padding + widget.waveWidth + .5, widget.height + .5 )
widget.ctx.stroke()
// draw waveform
widget.ctx.beginPath()
widget.ctx.moveTo( widget.padding, widget.height / 2 + 1 )
const range = widget.max - widget.min
const wHeight = widget.height * .85 + .45
for( let i = 0, len = widget.waveWidth; 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 + widget.padding -1, wHeight - yValue - 1.5, 3, 3)
widget.ctx.lineTo( i + widget.padding + .5, wHeight - yValue - 1.5 )
}else{
widget.ctx.lineTo( i + widget.padding + .5, wHeight - yValue )
}
}
widget.ctx.stroke()
// draw min/max
widget.ctx.fillStyle = COLORS.STROKE
widget.ctx.textAlign = 'right'
widget.ctx.fillText( widget.min.toFixed(2), widget.padding - 2, widget.height )
widget.ctx.textAlign = 'left'
widget.ctx.fillText( widget.max.toFixed(2), widget.waveWidth + widget.padding + 2, widget.height / 2 )
// draw corners
widget.ctx.beginPath()
widget.ctx.moveTo( .5, 3.5 )
widget.ctx.lineTo( .5, .5 )
widget.ctx.lineTo( 3.5, .5)
widget.ctx.moveTo( .5, widget.height - 3.5 )
widget.ctx.lineTo( .5, widget.height - .5 )
widget.ctx.lineTo( 3.5, widget.height - .5 )
const right = widget.padding * 2 + widget.waveWidth - .5
widget.ctx.moveTo( right, 3.5 )
widget.ctx.lineTo( right, .5 )
widget.ctx.lineTo( right - 3, .5 )
widget.ctx.moveTo( right, widget.height - 3.5 )
widget.ctx.lineTo( right, widget.height - .5 )
widget.ctx.lineTo( right - 3, widget.height - .5 )
widget.ctx.stroke()
drawn.push( widget )
}
}
}
}
module.exports = function( __Gibber ) {
Gibber = __Gibber
return Waveform
}
},{}],17:[function(require,module,exports){
module.exports = function( Gibber ) {
let Arp = function( chord = [0,2,4,6], octaves = 1, pattern = 'updown2' ) {
let notes, arp
if( typeof chord === 'string' ) {
// TODO: doesn't work... numbers can't be MIDI numbers because they go through scale conversion
let _chord = Gibber.Theory.Chord.create( chord )
chord = _chord.notes
}
notes = Gibber.Pattern.apply( null, chord.slice( 0 ) )
if( pattern === 'down' ) notes.reverse()
let maxLength = notes.values.length * octaves,
dir = pattern !== 'down' ? 'up' : 'down'
arp = ()=> {
arp.phase++
if( arp.phase >= maxLength ) {
arp.phase = 0
}
const range = arp.notes.end - arp.notes.start
if( arp.phase !== 0 && arp.phase % range === 0 ) {
if( dir === 'up' ) {
if( arp.octave < octaves ) {
arp.octave += 1
}else{
if( pattern === 'up' ) {
arp.octave = 1
}else{
dir = 'down'
notes.reverse()
}
}
}else{
if( arp.octave > 1 ) {
arp.octave += -1
}else{
if( pattern === 'down' ) {
arp.octave = octaves
} else {
dir = 'up'
notes.reverse()
}
}
}
}
let octaveMod,
note = arp.notes()
//note = Gibber.Theory.Note.convertToMIDI( note )
for( let i = 1; i < arp.octave; i++ ) {
note += 7
}
let methodNames = [
'rotate','switch','invert','reset', 'flip',
'transpose','reverse','shuffle','scale',
'store', 'range', 'set'
]
for( let key of methodNames ) {
arp[ key ] = notes[ key ].bind( notes )
Gibber.addSequencingToMethod( arp, key )
}
//arp.transpose = notes.transpose.bind( notes )
//arp.reset = notes.reset.bind( notes )
//arp.reverse = notes.reverse.bind( notes )
//arp.rotate = notes.rotate.bind( notes )
return note
}
arp.octave = 0
arp.phase = -1
arp.notes = notes
return arp
}
Arp.patterns = {
up( array ) {
return array
},
down( array ) {
return array.reverse()
},
updown( array ) {
let _tmp = array.slice( 0 )
_tmp.reverse()
return array.concat( _tmp )
},
updown2( array ) { // do not repeat highest and lowest notes
var tmp = array.slice( 0 )
tmp.pop()
tmp.reverse()
tmp.pop()
return array.concat( tmp )
}
}
return Arp
}
},{}],18:[function(require,module,exports){
module.exports = function( Gibber ) {
let Pattern = Gibber.Pattern
const numberToBinaryArray = num => {
let str = Number( num ).toString( 2 )
while( str.length < 8 ) {
str = '0' + str
}
// need to use parseFloat an