gibberwocky
Version:
music sequencing for Live and Max/MSP
463 lines (377 loc) • 13.6 kB
JavaScript
// singleton
let Gibber = null,
CodeMirror = require( 'codemirror' )
require( '../node_modules/codemirror/mode/javascript/javascript.js' )
//require( '../node_modules/codemirror/addon/edit/matchbrackets.js' )
require( '../node_modules/codemirror/addon/edit/closebrackets.js' )
require( '../node_modules/codemirror/addon/hint/show-hint.js' )
require( '../node_modules/codemirror/addon/hint/javascript-hint.js' )
require( './tabs-standalone.microlib-latest.js' )
const types = [ 'live', 'max', 'midi' ]
let Environment = {
codeMarkup: require( './codeMarkup.js' ),
debug: false,
_codemirror: CodeMirror,
animationScheduler: require( './animationScheduler.js' ),
lomView: require( './lomView.js' ),
momView: require( './momView.js' ),
consoleDiv:null,
consoleList:null,
annotations:true,
suppressErrors:false,
isConnected:false,
init( gibber ) {
Gibber = gibber
this.codeMarkup = this.codeMarkup( Gibber )
this.createCodeMirror()
this.createSidePanel()
this.setupSplit()
this.sidebar = document.querySelector( '#sidebar' )
this.sidebar.isVisible = 1
//this.lomView.init( Gibber )
this.animationScheduler.init( Gibber )
this.codeMarkup.init()
this.editorWidth = document.querySelector( '#editor' ).style.width
this.Storage.init()
this.setupClockSelection()
//this.toggleSidebar()
},
setServer( server ) {
// only reset tutorial view / change sync on first connection... reconnects don't trigger this
if( this.isConnected === false ) {
if( server === 'max' ) {
Environment.__eventFire( Environment.schematabs._element[0].firstElementChild.firstElementChild.nextSibling, 'click' )
Environment.__eventFire( Environment.demotabs._element[0].firstElementChild.firstElementChild.nextSibling, 'click' )
Environment.__eventFire( document.querySelector('#maxSyncRadio'), 'click' )
Environment.__eventFire( document.querySelector('#maxDemosView').firstElementChild.firstElementChild, 'click' )
}else if( server === 'live' ) {
Environment.__eventFire( Environment.demotabs._element[0].firstElementChild.firstElementChild, 'click' )
Environment.__eventFire( Environment.schematabs._element[0].firstElementChild.firstElementChild, 'click' )
Environment.__eventFire( document.querySelector('#liveSyncRadio'), 'click' )
Environment.__eventFire( document.querySelector('#liveDemosView').firstElementChild.firstElementChild, 'click' )
}
this.isConnected = true
}
},
__eventFire(el, etype){
if (el.fireEvent) {
el.fireEvent('on' + etype);
} else {
var evObj = document.createEvent('Events');
evObj.initEvent(etype, true, false);
el.dispatchEvent(evObj);
}
},
createSidePanel() {
this.tabs = new ML.Tabs( '#tabs' )
this.demotabs = new ML.Tabs( '#demoTabs' )
this.schematabs = new ML.Tabs( '#schemaTabs' )
this.createConsole()
this.createDemoLists()
},
clear() {
if( Gibber !== null && Gibber.isStandalone === true ) {
this.codeMarkup.clear()
this.animationScheduler.clear()
}
},
setupSplit() {
let splitDiv = document.querySelector( '#splitBar' ),
editor = document.querySelector( '#editor' ),
sidebar = document.querySelector( '#sidebar' ),
mousemove, mouseup
mouseup = evt => {
window.removeEventListener( 'mousemove', mousemove )
window.removeEventListener( 'mouseup', mouseup )
}
mousemove = evt => {
let splitPos = evt.clientX
editor.style.width = splitPos + 'px'
sidebar.style.left = splitPos + 'px'
sidebar.style.width = (window.innerWidth - splitPos) + 'px'
}
splitDiv.addEventListener( 'mousedown', evt => {
window.addEventListener( 'mousemove', mousemove )
window.addEventListener( 'mouseup', mouseup )
})
},
setupClockSelection() {
const syncs = ['max','live','clock']
for( let sync of syncs ) {
document.querySelector( '#' + sync + 'SyncRadio' ).onclick = ()=> {
Gibber.Scheduler.__sync__ = sync
localStorage.setItem('sync', sync)
}
}
},
createCodeMirror() {
CodeMirror.keyMap.gibber = this.keymap
this.codemirror = CodeMirror( document.querySelector('#editor'), {
mode:'javascript',
keyMap:'gibber',
autofocus:true,
value: Gibber.Examples.live.introduction,
matchBrackets: true,
autoCloseBrackets: true,
extraKeys: {"Ctrl-Space": "autocomplete"},
//theme:'the-matrix'
})
this.codemirror.setSize( null, '100%' )
},
createConsole() {
//this.console = //CodeMirror( document.querySelector('#console'), { mode:'javascript', autofocus:false, lineWrapping:true })
//this.console.setSize( null, '100%' )
let list = document.createElement( 'ul' )
list.setAttribute( 'id', 'console_list' )
Environment.consoleList = list
Environment.consoleDiv = document.querySelector( '#console' )
Environment.consoleDiv.appendChild( list )
Environment.overrideError()
},
overrideError() {
console.__error = console.error
console.error = function(...args) {
Gibber.Environment.error.apply( null, args )
console.__error.apply( console, args )
}
},
replaceError() {
console.error = console.__error
},
createDemoLists() {
for( let type of types ) {
let container = document.querySelector(`#${type}DemosView`),
list = document.createElement( 'ul' )
for( let demoName in Gibber.Examples[ type ] ) {
let li = document.createElement( 'li' ),
txt = Gibber.Examples[ type ][ demoName ]
li.innerText = demoName
li.addEventListener( 'click', () => {
Environment.codemirror.setValue( txt )
})
list.appendChild( li )
}
container.innerHTML = ''
container.appendChild( list )
}
},
log( ...args ) {
console.log( ...args )
if( Gibber !== null && Gibber.isStandalone == true ) {
let consoleItem = Environment.createConsoleItem( args )
Environment.consoleList.appendChild( consoleItem )
consoleItem.scrollIntoView()
return consoleItem
}
},
error( ...args ) {
if( Environment.suppressErrors === false ) {
if( args[0] === 'error Gen not authorized on this install' ) {
Gibber.__gen.enabled = false
//args[0] = 'error Compiling Gen graphs is not authorized for this install of Max; using genish.js for modulation'
Gibber.log( 'Gen is not authorized on this computer; using genish.js for modulation.' )
return
}
let consoleItem = Environment.createConsoleItem( args )
consoleItem.setAttribute( 'class', 'console_error' )
Environment.consoleList.appendChild( consoleItem )
consoleItem.scrollIntoView()
}
},
createConsoleItem( args ) {
let li = document.createElement( 'li' )
li.innerText = args.join( ' ' )
return li
},
clearConsole() {
document.querySelector( '#console_list' ).innerHTML = ''
},
keymap : {
fallthrough:'default',
// execute now
'Shift-Enter'(cm) {
try {
const selectedCode = Environment.getSelectionCodeColumn( cm, false )
const func = new Function( selectedCode.code )
Environment.flash( cm, selectedCode.selection )
func()
}catch( e ) {
console.log( e )
Environment.log( 'error with immediately executed code:', e )
}
},
'Ctrl-Enter'( cm ) {
try {
const selectedCode = Environment.getSelectionCodeColumn( cm, false )
Environment.flash( cm, selectedCode.selection )
const func = new Function( selectedCode.code ).bind( Gibber.currentTrack ),
markupFunction = () => {
Environment.codeMarkup.process(
selectedCode.code,
selectedCode.selection,
cm,
Gibber.currentTrack
)
}
markupFunction.origin = func
const isConnected = Gibber.Communication.connected.live || Gibber.Communication.connected.max
if( !Environment.debug && isConnected === true) {
Gibber.Scheduler.functionsToExecute.push( func )
if( Environment.annotations === true )
Gibber.Scheduler.functionsToExecute.push( markupFunction )
}else{
func()
if( Environment.annotations === true )
markupFunction()
}
} catch (e) {
console.log( e )
Environment.log( 'ERROR', e )
}
},
'Alt-Enter'( cm ) {
try {
let selectedCode = Environment.getSelectionCodeColumn( cm, true )
Environment.flash( cm, selectedCode.selection )
let func = new Function( selectedCode.code ).bind( Gibber.currentTrack ),
markupFunction = () => {
Environment.codeMarkup.process(
selectedCode.code,
selectedCode.selection,
cm,
Gibber.currentTrack
)
}
markupFunction.origin = func
if( !Environment.debug ) {
Gibber.Scheduler.functionsToExecute.push( func );
if( Environment.annotations === true )
Gibber.Scheduler.functionsToExecute.push( markupFunction )
}else{
func()
if( Environment.annotations === true )
markupFunction()
}
} catch (e) {
console.log( e )
Environment.log( 'ERROR', e )
}
},
'Ctrl-.'( cm ) {
Gibber.clear()
Gibber.log( 'All sequencers stopped.' )
},
'Shift-Ctrl-C'( cm ) {
Environment.toggleSidebar()
},
'Shift-Ctrl-P'( cm ) {
Environment.Storage.save()
},
'Ctrl-S'( cm ) {
cm.replaceSelection('.seq(\n\n)')
cm.execCommand('goLineUp')
cm.replaceSelection(' ')
},
'Shift-Ctrl-S'( cm ) {
const value = cm.getValue()
Environment.Storage.values.savedText = value
Environment.Storage.save()
log( 'code saved.' )
},
'Shift-Ctrl-L'( cm ) {
const value = Environment.Storage.values.savedText
cm.setValue( value )
log( 'code loaded.' )
},
'Ctrl-,'( cm ) {
cm.execCommand('goLineEnd')
cm.replaceSelection(',\n')
cm.execCommand('goLineStart')
cm.replaceSelection(' ')
}
},
toggleSidebar() {
Environment.sidebar.isVisible = !Environment.sidebar.isVisible
let editor = document.querySelector( '#editor' )
if( !Environment.sidebar.isVisible ) {
Environment.editorWidth = editor.style.width
editor.style.width = '100%'
}else{
editor.style.width = Environment.editorWidth
}
Environment.sidebar.style.display = Environment.sidebar.isVisible ? 'block' : 'none'
},
getSelectionCodeColumn( cm, findBlock ) {
let pos = cm.getCursor(),
text = null
if( !findBlock ) {
text = cm.getDoc().getSelection()
if ( text === "") {
text = cm.getLine( pos.line )
}else{
pos = { start: cm.getCursor('start'), end: cm.getCursor('end') }
//pos = null
}
}else{
let startline = pos.line,
endline = pos.line,
pos1, pos2, sel
while ( startline > 0 && cm.getLine( startline ) !== "" ) { startline-- }
while ( endline < cm.lineCount() && cm.getLine( endline ) !== "" ) { endline++ }
pos1 = { line: startline, ch: 0 }
pos2 = { line: endline, ch: 0 }
text = cm.getRange( pos1, pos2 )
pos = { start: pos1, end: pos2 }
}
if( pos.start === undefined ) {
let lineNumber = pos.line,
start = 0,
end = text.length
pos = { start:{ line:lineNumber, ch:start }, end:{ line:lineNumber, ch: end } }
}
return { selection: pos, code: text }
},
flash(cm, pos) {
let sel,
cb = function() { sel.clear() }
if (pos !== null) {
if( pos.start ) { // if called from a findBlock keymap
sel = cm.markText( pos.start, pos.end, { className:"CodeMirror-highlight" } );
}else{ // called with single line
sel = cm.markText( { line: pos.line, ch:0 }, { line: pos.line, ch:null }, { className: "CodeMirror-highlight" } )
}
}else{ // called with selected block
sel = cm.markText( cm.getCursor(true), cm.getCursor(false), { className: "CodeMirror-highlight" } );
}
window.setTimeout(cb, 250);
},
Storage : {
values : null,
savedText: null,
init : function() {
Storage.prototype.setObject = function( key, value ) { this.setItem( key, JSON.stringify( value ) ); }
Storage.prototype.getObject = function( key ) { var value = this.getItem( key ); return value && JSON.parse( value ); }
this.values = localStorage.getObject( 'gibberwocky' )
if ( !this.values ) {
this.values = {
onload:null,
savedText:null
}
this.save()
}
},
save : function() {
localStorage.setObject( "gibberwocky", this.values );
},
runUserSetup: function() {
if( this.values.onload ) {
try{
eval( this.values.onload )
}catch(e) {
Environment.log( 'There was an error running your preload code:\n' + Enviroment.Storage.values.onload )
}
}
}
}
}
module.exports = Environment