UNPKG

gibberwocky

Version:

music sequencing for Live and Max/MSP

274 lines (219 loc) 8.81 kB
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 }