/**#Interface A singleton object holding all widget constructors and a couple of other methods / properties. It is automatically created as soon as interface.js is loaded. **/ /**###Interface.extend : method This method deep copies all the properties and methods of one object to another. param **destination** Object. The object that properties and methods will be inserted into. param **source** Object. The object providing the properties and methods for copying. **/ /**###Interface.mouseDown : property Boolean. This property tells whether the left mouse button (in non-touch browsers) is currently pressed. **/ /**###Interface.useTouch : property Boolean. Whether or not a touch UI browser is being used. **/ /**###Interface.isAndroid : property Boolean. Whether or not the browser is running under Android. This is used to determine the range of accelerometer values generated. **/ var Interface = { extend : function(destination, source) { for (var property in source) { var keys = property.split("."); if(source[property] instanceof Array && source[property].length < 100) { // don't copy large array buffers destination[property] = source[property].slice(0); } else { destination[property] = source[property]; } } return destination; }, isAndroid : (function() { var ua = navigator.userAgent.toLowerCase(); return ua.indexOf("android") > -1; })(), keyCodeToChar : {8:"Backspace",9:"Tab",13:"Enter",16:"Shift",17:"Ctrl",18:"Alt",19:"Pause/Break",20:"Caps Lock",27:"Esc",32:"Space",33:"Page Up",34:"Page Down",35:"End",36:"Home",37:"Left",38:"Up",39:"Right",40:"Down",45:"Insert",46:"Delete",48:"0",49:"1",50:"2",51:"3",52:"4",53:"5",54:"6",55:"7",56:"8",57:"9",65:"A",66:"B",67:"C",68:"D",69:"E",70:"F",71:"G",72:"H",73:"I",74:"J",75:"K",76:"L",77:"M",78:"N",79:"O",80:"P",81:"Q",82:"R",83:"S",84:"T",85:"U",86:"V",87:"W",88:"X",89:"Y",90:"Z",91:"Windows",93:"Right Click",96:"Numpad 0",97:"Numpad 1",98:"Numpad 2",99:"Numpad 3",100:"Numpad 4",101:"Numpad 5",102:"Numpad 6",103:"Numpad 7",104:"Numpad 8",105:"Numpad 9",106:"Numpad *",107:"Numpad +",109:"Numpad -",110:"Numpad .",111:"Numpad /",112:"F1",113:"F2",114:"F3",115:"F4",116:"F5",117:"F6",118:"F7",119:"F8",120:"F9",121:"F10",122:"F11",123:"F12",144:"Num Lock",145:"Scroll Lock",182:"My Computer",183:"My Calculator",186:";",187:"=",188:",",189:"-",190:".",191:"/",192:"`",219:"[",220:"\\",221:"]",222:"'"}, panels : [], mouseDown : false, useTouch : 'ontouchstart' in document.documentElement, widgets : [], }; Interface.Presets = { dictionary : typeof localStorage.interfacejs === 'undefined' ? {} : JSON.parse( localStorage.interfacejs ), save : function(name) { var preset = []; for(var h = 0; h < Interface.panels.length; h++) { var panel = Interface.panels[h]; preset[h] = []; for(var i = 0; i < panel.children.length; i++) { var widget = panel.children[i]; if(typeof widget.children === 'object') { var children = []; for(var j = 0; j < widget.children.length; j++) { children[j] = widget.children[j].value; } preset[h][i] = children; }else{ preset[h][i] = widget.value; } } this.dictionary[ name ] = preset; localStorage.interfacejs = JSON.stringify( this.dictionary ); } }, load : function(name) { var preset = this.dictionary[ name ]; for(var h = 0; h < Interface.panels.length; h++) { var panel = Interface.panels[h]; for(var i = 0; i < panel.children.length; i++) { var widget = panel.children[i]; if(typeof widget.children === 'object') { for(var j = 0; j < widget.children.length; j++) { widget.children[j].setValue( preset[h][i][j] ); } }else{ widget.setValue( preset[h][i] ); } } } }, list : function() { return Object.keys( this.dictionary ); }, }; /**#Interface.Panel - Widget A panel is a container for on-screen widgets. There can be multiple panels in a HTML page. Panels are the starting point for event processing in Interface.js. **/ /**###Interface.Panel.children : property Array. An array of all widgets displayed in the panel **/ /**###Interface.Panel.shouldDraw : property Boolean. Whenever the panel refreshes itself it will redraw widgets found in this array. **/ /**###Interface.Panel.fps : property Number. The number of times the panel should refresh itself per second. **/ /**###Interface.Panel.useRelativeSizesAndPositions : property Boolean. This determines whether widgets in the panel uses sizes/positions relative to the size of the panel or use absolute pixel coordinates. **/ /**###Interface.Panel.container : property HTMLElement. The HTMLElement (such as a div tag) containing the Panel. **/ /**###Interface.Panel.canvas : property HTMLElement. The canvas element that the Panel draws onto. This element is created when the panel is initialized. **/ /**###Interface.Panel.touchEvent : method The starting point for on-screen all touch event handling in a Panel. This method distributes events to all child widgets. param **event** HTML Touch Event Object. **/ /**###Interface.Panel.mouseEvent : method The starting point for on-screen all mouse event handling in a Panel. This method distributes events to all child widgets. param **event** HTML Mouse Event Object. **/ /**###Interface.Panel.init : method Initialization method called automatically when panel is instantiated. **/ /**###Interface.Panel.x : property Number. The x position of the panel in absolute coordinates relative to the window. **/ /**###Interface.Panel.y : property Number. The y position of the panel in absolute coordinates relative to the window. **/ /**###Interface.Panel.width : property Number. The width of the panel in pixels **/ /**###Interface.Panel.width : property Number. The height of the panel in pixels **/ /**###Interface.Panel.draw : method This method tells all 'dirty' widgets stored in the shouldDraw property to draw themselves. **/ /**###Interface.Panel.refresh : method Clear the entire panel and then tell all widgets to draw themselves. **/ /**###Interface.Panel.add : method Add a new widget to the panel param **widget** Object. The widget to be added. Motion widgets do not need to be added to the Panel **/ /**###Interface.Panel.setBackgroundColor : method Set the background color the panel using a css color value. param **cssColor** String. Any valid css color, such as 'red', '#f00', or 'rgb(255,0,0)'. **/ /**###Interface.Panel.background : property String. The default background color for all widgets in the panel. THIS IS NOT THE BACKGROUND COLOR FOR THE PANEL. Any valid css color, such as 'red', '#f00', or 'rgb(255,0,0)' can be assigned to this property. **/ /**###Interface.Panel.fill : property String. The default fill color for all widgets in the panel. Any valid css color, such as 'red', '#f00', or 'rgb(255,0,0)' can be assigned to this property. **/ /**###Interface.Panel.stroke : property String. The default stroke color for all widgets in the panel. Any valid css color, such as 'red', '#f00', or 'rgb(255,0,0)' can be assigned to this property. **/ Interface.Panel = function() { var self = this, _container = arguments.length >= 1 ? arguments[0].container : undefined; Interface.extend(this, { type: 'Panel', active: true, children: [], shouldDraw : [], fps : 30, useRelativeSizesAndPositions : true, labelSize: '12px', font:'normal 12px Helvetica', serializeMe: ['fps', 'useRelativeSizesAndPositions', 'labelSize', 'font', 'background', 'fill', 'stroke', 'backgroundColor'], container: (function() { if(typeof _container === 'undefined') { $('body').css({ margin : 0, padding: 0, }); var d = $('
'); d.css({ width:$(window).width(), height:$(window).height(), display:'block', margin:0, padding:0, position:'absolute', left:0, top:0 }); $('body').append(d); return d; }else{ return _container; } })(), canvas: document.createElement('canvas'), touchEvent : function(event) { if(self.active) { if( typeof event.changedTouches === 'undefined' && event.originalEvent ) { event.changedTouches = event.originalEvent.changedTouches } for (var j = 0; j < event.changedTouches.length; j++){ var touch = event.changedTouches.item(j); for(var i = 0; i < self.children.length; i++) { touch.x = touch.pageX - self.x; touch.y = touch.pageY - self.y; touch.type = event.type; self.children[i].touchEvent(touch); } //var breakCheck = this.events[event.type].call(this, touch); //if(breakCheck) break; } event.preventDefault(); // HTML Elements must simulate touch events in their touchEvent method } }, mouseEvent : function(e) { if(self.active) { if(e.type === 'mousedown') { Interface.mouseDown = true; }else if(e.type === 'mouseup') { Interface.mouseDown = false; } var event = { x : e.offsetX || (e.pageX - self.x), // pageX and pageY is for firefox compatibility y : e.offsetY || (e.pageY - self.y), type: e.type, } //console.log("MOUSE", event, self.y, e.pageY, e.layerY, e.clientY, e ); for(var i = 0; i < self.children.length; i++) { self.children[i].mouseEvent(event); } } }, init : function() { var offset = $(this.container).offset(); this.width = $(this.container).width(); this.height = $(this.container).height(); this.x = offset.left; this.y = offset.top; if( isNaN(this.x) ) this.x = 0; if( isNaN(this.y) ) this.y = 0; $(this.canvas).attr({ 'width': this.width, 'height': this.height, }); $(this.container).css({ 'user-select': 'none', '-webkit-user-select': 'none'}); $(this.container).append(this.canvas); this.ctx = this.canvas.getContext( '2d' ); this.ctx.translate(0.5, 0.5); this.ctx.lineWidth = 1; if(Interface.useTouch) { $(this.container).on( 'touchstart', this.touchEvent ); $(this.container).on( 'touchmove', this.touchEvent ); $(this.container).on( 'touchend', this.touchEvent ); }else{ $(this.container).on( 'mousedown', this.mouseEvent ); $(this.container).on( 'mousemove', this.mouseEvent ); $(this.container).on( 'mouseup', this.mouseEvent ); } $( this.container ).css({ outline: 'none' }) $( this.container ).attr( 'tabindex', 5 ) $( this.container ).on( 'keydown', this.keydown.bind( this ) ) $( this.container ).on( 'keyup', this.keyup.bind( this ) ) }, keydown: function(e) { for( var i = 0; i < this.children.length; i++ ) { if( this.children[i].onkeydown ) this.children[i].onkeydown(e) } }, keyup: function(e) { for( var i = 0; i < this.children.length; i++ ) { if( this.children[i].onkeyup ) this.children[i].onkeyup(e) } }, draw : function() { if(this.active) { for(var i = 0; i < this.shouldDraw.length; i++) { this.shouldDraw[i].draw(); } this.shouldDraw.length = 0; } $.publish('/draw') }, getWidgetWithName: function( name ) { for(var i = 0; i < this.children.length; i++) { if( this.children[i].name === name) { return this.children[i]; } } }, redoBoundaries : function() { var offset = $(this.container).offset(); this.width = $(this.container).width(); this.height = $(this.container).height(); this.x = offset.left; this.y = offset.top; if( isNaN(this.x) ) this.x = 0; if( isNaN(this.y) ) this.y = 0; $(this.canvas).attr({ 'width': this.width, 'height': this.height, }); this.ctx.translate(0.5, 0.5); this.ctx.lineWidth = 1; this.refresh(); }, refresh: function() { if(this.active) { this.ctx.clearRect(0,0,this.canvas.width, this.canvas.height); for(var i = 0; i < this.children.length; i++) { this.children[i].draw(); } } }, add: function() { for(var i = 0; i < arguments.length; i++) { var widget = arguments[i]; widget.panel = this; widget.canvas = this.canvas; widget.container = this.container; widget.ctx = this.ctx; this.children.push( widget ); if(widget._init && !widget.added) widget._init(); if(widget.oninit && !widget.added) widget.oninit(); widget.draw(); widget.added = true; if(typeof widget.add === 'function') widget.add(); } }, clear : function() { this.ctx.clearRect( 0,0,this.width,this.height ); this.children.length = 0; }, remove: function(widget) { this.ctx.clearRect( widget._x(), widget._y(), widget._width(), widget._height() ); if(typeof widget.children !== 'undefined' && widget.type !== "XY") { for(var i = 0; i < widget.children.length; i++) { this.children.splice( this.children.indexOf( widget.children[i] ), 1 ); Interface.widgets.splice( Interface.widgets.indexOf( widget.children[i] ), 1 ); } }else{ if(this.children.indexOf( widget ) > -1) { this.children.splice( this.children.indexOf( widget ), 1 ); Interface.widgets.splice( Interface.widgets.indexOf( widget ), 1 ); if(typeof widget.remove === 'function') widget.remove(); } } }, }); if(typeof arguments[0] !== 'undefined') Interface.extend(this, arguments[0]); if(this.backgroundColor) this.setBackgroundColor( this.backgroundColor ); this.init(); this.timer = setInterval( function() { self.draw(); }, Math.round(1000 / this.fps) ); var childBackground ='#000', childFill = '#666', childStroke = '#999', background = 'transparent', self = this, useRelativeSizesAndPositions = this.useRelativeSizesAndPositions; Object.defineProperties(this, { 'useRelativeSizesAndPositions' : { get: function() { return useRelativeSizesAndPositions; }, set: function(val) { if(val !== useRelativeSizesAndPositions) { useRelativeSizesAndPositions = val; if(val === false) { for(var i = 0; i < this.children.length; i++) { var child = this.children[i]; child.bounds = [ Math.round( child.x * this.width ), Math.round( child.y * this.height ), Math.round( child.width * this.width ), Math.round( child.height * this.height ), ]; } }else{ for(var i = 0; i < this.children.length; i++) { var child = this.children[i]; child.bounds = [ child.x / this.width, child.y / this.height, child.width / this.width, child.height / this.height, ]; } } } this.refresh(); }, }, 'background': { get: function() { return background; }, set: function(val) { background = val; $(this.container).css({ backgroundColor:background }); }, }, 'childBackground': { get: function() { return childBackground; }, set: function(val) { childBackground = val; self.refresh(); }, }, 'childStroke': { get: function() { return childStroke; }, set: function(val) { childStroke = val; self.refresh(); }, }, 'childFill': { get: function() { return childFill; }, set: function(val) { childFill = val; self.refresh(); }, } }); if(arguments[0]) { if(arguments[0].childBackground) this.childBackground = arguments[0].childBackground; if(arguments[0].childFill) this.childFill = arguments[0].childFill; if(arguments[0].childStroke) this.childStroke = arguments[0].childStroke; } Interface.panels.push( this ); }; var convertMouseEvent = function(eventName) { switch(eventName) { case 'mousedown': return 'touchmousedown'; case 'mousemove': return 'touchmousemove'; case 'mouseup': return 'touchmouseup'; default: return eventName; } }; var convertTouchEvent = function(eventName) { switch(eventName) { case 'touchstart': return 'touchmousedown'; case 'touchmove': return 'touchmousemove'; case 'touchend': return 'touchmouseup'; default: return eventName; } }; /**#Interface.Widget - Widget The prototype object for all Interface.js widgets. These methods and properties are inherited by all widgets. **/ /**###Interface.Widget.x : property Number. The horizontal position of the widget inside of its parent panel. By default, this position is determined relative to the size of the widget's containing panel, but absolute values can also be used if the panel's useRelativeSizesAndPositions property is set to false. **/ /**###Interface.Widget.y : property Number. The vertical position of the widget inside of its parent panel. By default, this position is determined relative to the size of the widget's containing panel, but absolute values can also be used if the panel's useRelativeSizesAndPositions property is set to false. **/ /**###Interface.Widget.width : property Number. The width of the widget. By default, this is determined relative to the size of the widget's containing panel, but absolute values can also be used if the panel's useRelativeSizesAndPositions property is set to false. **/ /**###Interface.Widget.height : property Number. The width of the widget. By default, this is determined relative to the size of the widget's containing panel, but absolute values can also be used if the panel's useRelativeSizesAndPositions property is set to false. **/ /**###Interface.Widget.bounds : property Array. A shorthand to set x,y,width and height simultaneously upon initialization. By default, these values are determined relative to the size of the widget's containing panel, but absolute values can also be used if the panel's useRelativeSizesAndPositions property is set to false. **/ /**###Interface.Widget.min : property Number. Default 0. The minimum value the widget should output. **/ /**###Interface.Widget.max : property Number. Default 1. The maximum value the widget should output. **/ /**###Interface.Widget.ontouchstart : method Function. A user defined event handler for whenever a touch begins over a widget. **/ /**###Interface.Widget.ontouchmove : method Function. A user defined event handler for whenever a touch moves over a widget. **/ /**###Interface.Widget.ontouchend : method Function. A user defined event handler for whenever a touch ends. **/ /**###Interface.Widget.onmousedown : method Function. A user defined event handler for whenever a mouse press occurs over a widget. **/ /**###Interface.Widget.onmousemove : method Function. A user defined event handler for whenever a mouse moves over a widget while its button is pressed. **/ /**###Interface.Widget.onmouseup : method Function. A user defined event handler for whenever a mouse press ends. **/ /**###Interface.Widget.ontouchmousedown : method Function. A user defined event handler for whenever a mouse press or touch occurs over a widget. **/ /**###Interface.Widget.ontouchmousemove : method Function. A user defined event handler for whenever a mouse or touch moves over a widget. **/ /**###Interface.Widget.ontouchmouseup : method Function. A user defined event handler for whenever a mouse press ends or a touch leaves the screen. **/ /**###Interface.Widget.init : method This method is called as soon as widgets are created. It copies properties passed in the constructor to the widget and also copies some default property values. param **options** Object. A dictionary of options for the widget to be initilized with. **/ /**###Interface.Widget.refresh : method Tell the widget to redraw itself. This method adds the widget to the shouldDraw array of the parent panel. **/ /**###Interface.Widget.setValue : method Programmatically change the value of the widget. You can optionally not have the widget redraw itself when calling this method. param **value** Number or String. The new value for the widget. param **doNotDraw** Optional, default false. Whether or not the widget should redraw itself. **/ /**###Interface.Widget.hitTest : method Given an HTML touch or mouse event, determine if the event overlaps a graphical widget. param **event** HTMLEvent. The touch or mouse event to check **/ /**###Interface.Widget.hitTest : method Given an HTML touch or mouse event, determine if the event overlaps a graphical widget. param **event** HTMLEvent. The touch or mouse event to check **/ /**###Interface.Widget.draw : method Tell the widget to draw itself. This method must be overridden by every graphical widget. **/ /**###Interface.Widget.mouseEvent : method The default mouse event handler for the widget. This method also calls any user defined mouse event handlers. This method should probably never be called manually, but you might want to override it. param **event** HTMLEvent. The mouse event to process **/ /**###Interface.Widget.mouseEvent : method The default touch event handler for the widget. This method also calls any user defined touch event handlers. This method should probably never be called manually, but you might want to override it. param **event** HTMLEvent. The touch event to process **/ /**###Interface.Widget.sendTargetMessage : method If the widget has a target and key property, set the key property or call the key method on the target using the widgets current value. **/ /**###Interface.Widget._background : method returns Color. If the widget has a background color specified, return that, otherwise return the background color of the widget's parent panel. **/ /**###Interface.Widget._stroke : method returns Color. If the widget has a stroke color specified, return that, otherwise return the stroke color of the widget's parent panel. **/ /**###Interface.Widget._fill : method returns Color. If the widget has a fill color specified, return that, otherwise return the fill color of the widget's parent panel. **/ /**###Interface.Widget._x : method returns Number. Return the widget's x position as a pixel value relative to the position of the panel. Note that this method will always return the pixel value, even if the panel uses relative values to determine sizes and positions. **/ /**###Interface.Widget._y : method returns Number. Return the widget's y position as a pixel value relative to the position of the panel. Note that this method will always return the pixel value, even if the panel uses relative values to determine sizes and positions. **/ /**###Interface.Widget._width : method returns Number. Return the widget's width. Note that this method will always return a size in pixels, even if the panel uses relative values to determine sizes and positions. **/ /**###Interface.Widget._height : method returns Number. Return the widget's height. Note that this method will always return a size in pixels, even if the panel uses relative values to determine sizes and positions. **/ var __widgetCount = 0; var widgetDefaults = { hasFocus : false, requiresFocus : true, min : 0, max : 1, value : 0, lastValue : null, name : null, events : { ontouchstart : null, ontouchmove : null, ontouchend : null, onmousedown : null, onmousemove : null, onmouseup : null, ontouchmousedown : null, ontouchmousemove : null, ontouchmouseup : null, onvaluechange : null, onboundschange : null, }, } Interface.Widget = { init : function( options ) { this.added = false; Interface.extend( this, widgetDefaults); if( typeof options === 'undefined' ) options = {} this.name = options.name || this.type + "_" + __widgetCount++; this.target = "OSC"; this.key = "/" + this.name; Interface.extend( this, options); if(this.bounds) { this.x = options.bounds[0]; this.y = options.bounds[1]; this.width = options.bounds[2]; this.height = options.bounds[3]; } if(this.colors) { this.background = options.colors[0]; this.fill = options.colors[1]; this.stroke = options.colors[2]; } this.focusedTouches = []; if(this.value) this.setValue(this.value, true); var bounds = this.bounds || [this.x, this.y, this.width, this.height], x = this.x, y = this.y, width = this.width, height = this.height, value = this.value; Object.defineProperties(this, { bounds : { configurable: true, get : function() { return bounds; }, set : function(_bounds) { bounds = _bounds; this.x = bounds[0]; this.y = bounds[1]; this.width = bounds[2]; this.height = bounds[3]; if( this.onboundschange ) this.onboundschange() } }, x : { configurable: true, get : function() { return x; }, set : function(val) { this.clear(); x = val; if( this.onboundschange ) this.onboundschange(); this.refresh(); }, }, y : { configurable: true, get : function() { return y; }, set : function(val) { this.clear(); y = val; if( this.onboundschange ) this.onboundschange(); this.refresh(); }, }, width : { configurable: true, get : function() { return width; }, set : function(val) { this.clear(); width = val; if( this.onboundschange ) this.onboundschange(); this.refresh(); }, }, height : { configurable: true, get : function() { return height; }, set : function(val) { this.clear(); height = val; if( this.onboundschange ) this.onboundschange(); this.refresh(); }, }, /*value : { configurable: true, get : function() { return value; }, set : function(val) { if(value !== val) { value = val; this.refresh(); } }, },*/ }); Interface.widgets.push( this ); }, clear : function() { if( this.panel ) { // must check in case widget is Acc or Gyro this.panel.ctx.clearRect( this._x(), this._y(), this._width(), this._height() ); } }, refresh : function() { if(this.panel && this.panel.shouldDraw.indexOf(this) === -1) { this.panel.shouldDraw.push(this); } }, setValue : function(value, doNotDraw) { var r = this.max - this.min, v = value; this.value = value; if(this.min !== 0 || this.max !== 1) { v -= this.min; this._value = v / r; }else{ this._value = this.value; } if(!doNotDraw) this.refresh(); }, hitTest : function(e) { if(e.x >= this._x() && e.x <= this._x() + this._width()) { if(e.y >= this._y() && e.y <= this._y() + this._height()) { return true; } } return false; }, mouseEvent : function(e) { var isHit = this.hitTest(e); var touchMouseName = convertMouseEvent(e.type); if(isHit || this.hasFocus || !this.requiresFocus) { if(e.type === 'mousedown') this.hasFocus = true; if(this[e.type]) this[e.type](e, isHit); // normal event if(this['on'+e.type]) this['on'+e.type](e, isHit); // user defined event if(this['on'+touchMouseName]) this['on'+touchMouseName](e, isHit); // user defined event } if(e.type === 'mouseup') this.hasFocus = false; }, touchEvent : function(touch) { // event type is stored in touch by Panel var isHit = this.hitTest(touch); var touchMouseName = convertTouchEvent(touch.type); if(isHit || this.hasFocus || !this.requiresFocus) { if(touch.type === 'touchstart') { this.focusedTouches.push(touch); this.hasFocus = true; } if(this[touch.type]) this[touch.type](touch, isHit); // normal event if(this['on'+touch.type]) this['on'+touch.type](touch, isHit); // user defined event if(this['on'+touchMouseName]) this['on'+touchMouseName](touch, isHit); // user defined event } if(touch.type === 'touchend') { for(var i = 0; i < this.focusedTouches.length; i++) { if(this.focusedTouches[i].id === touch.id) { this.focusedTouches.splice(i, 1); if(this.focusedTouches.length === 0) this.hasFocus = false; break; } } } }, draw : function() {}, sendTargetMessage : function() { if(this.target && this.key) { if(this.target === "OSC") { if(Interface.OSC) { if(typeof this.values === 'undefined') { var tt = typeof this.value === 'string' ? 's' : 'f'; Interface.OSC.send(this.key, tt, [ this.value ] ); }else{ if(typeof this.sendValues === 'undefined') { var tt = ''; for(var i = 0; i < this.values.length; i++) { tt += typeof this.value === 'string' ? 's' : 'f'; } Interface.OSC.send( this.key, tt, this.values ); }else{ this.sendValues(); } } } }else if(this.target === "MIDI") { if(Interface.MIDI && typeof this.values === 'undefined') { Interface.MIDI.send( this.key[0],this.key[1],this.key[2], this.value ) } }else{ if(typeof this.target[this.key] === 'function') { this.target[this.key]( this.values || this.value ); }else{ this.target[this.key] = this.values || this.value; } } } }, _background : function() { return this.background || this.panel.childBackground; }, _stroke : function() { return this.stroke || this.panel.childStroke; }, _fill : function() { return this.fill || this.panel.childFill; }, _x : function() { return this.panel.useRelativeSizesAndPositions ? Math.floor(this.x * this.panel.width) : this.x; }, _y : function() { return this.panel.useRelativeSizesAndPositions ? Math.floor(this.y * this.panel.height) : this.y; }, _width : function() { return this.panel.useRelativeSizesAndPositions ? Math.floor(this.width * this.panel.width) : this.width; }, _height : function() { return this.panel.useRelativeSizesAndPositions ? Math.floor(this.height * this.panel.height) : this.height; }, _font : function() { var font = this.font || this.panel.font; return font; }, label:null, _serializeMe : [ "background", "stroke", "fill", "x", "y", "width", "height", "value", "label", "onmousedown", "onmousemove", "onmouseup", "ontouchmousedown", "ontouchmousemove", "ontouchmouseup", "ontouchstart", "ontouchmove", "ontouchend", "onvaluechange", "name", "type", "target", "key" ], }; Interface.HBox = function() { var me = this Interface.extend(this, { type : 'HBox', children: [], proxyPanel : { active:true, x:0, y:0, width:1, height:1, shouldDraw:[], useRelativeSizesAndPositions:true }, // needed for absolute widths / heights which are set in _init call add: function() { for( var i = 0; i < arguments.length; i++ ) { var child = arguments[ i ] if( this.children.indexOf( child ) === -1 ) this.children.push( child ) child.panel = this.proxyPanel child.ctx = this.panel.ctx } this.layout() this.draw() }, layout : function() { var w = (this.width / this.children.length) / this.width, _widthUsed = 0; for( var i = 0; i < this.children.length; i++ ) { var child = this.children[ i ] child.x = _widthUsed + this.x child.y = this.y * (1 / this.height) child.width = w child.height = 1 _widthUsed += w } return this }, draw: function() { this.proxyPanel.width = this._width() this.proxyPanel.height = this._height() for( var i = 0; i < this.children.length; i++ ) { this.children[ i ].draw() } }, refresh: function() { for( var i = 0; i < this.proxyPanel.shouldDraw.length; i++ ) { this.proxyPanel.shouldDraw[ i ].draw() } this.proxyPanel.shouldDraw.length = 0 }, mouseEvent: function(e){ // e.x -= this._x() // e.y -= this._y() for( var i = 0; i < this.children.length; i++ ) { var child = this.children[ i ] this.children[ i ].mouseEvent( e ) } }, touchEvent: function(e){ // e.x -= this._x() // e.y -= this._y() for( var i = 0; i < this.children.length; i++ ) { var child = this.children[ i ] this.children[ i ].touchEvent( e ) } }, _init: function() { this.useRelativeSizesAndPositions = this.panel.useRelativeSizesAndPositions this.proxyPanel.width = this._width() this.proxyPanel.height = this._height() this.proxyPanel.__proto__ = this.panel $.subscribe('/draw', this.refresh.bind( this ) ) Object.defineProperties(this, { bounds : { configurable: true, get : function() { return bounds; }, set : function(_bounds) { bounds = _bounds; this.x = bounds[0]; this.y = bounds[1]; this.width = bounds[2]; this.height = bounds[3]; this.layout() this.draw() } }, }) } }) .init( arguments[0] ) }; Interface.HBox.prototype = Interface.Widget; Interface.VBox = function() { Interface.extend(this, { type : 'VBox', children: [], proxyPanel : { active:true, x:0, y:0, width:1, height:1, shouldDraw:[], useRelativeSizesAndPositions:true }, // needed for absolute widths / heights which are set in _init call add: function() { for( var i = 0; i < arguments.length; i++ ) { var child = arguments[ i ] this.children.push( child ) child.panel = this.proxyPanel child.ctx = this.panel.ctx } this.layout() this.draw() }, layout : function() { var h = this.height / this.children.length, _heightUsed = 0; for( var i = 0; i < this.children.length; i++ ) { var child = this.children[ i ] child.x = this.x child.y = (this.y + _heightUsed ) * (1 / this.height) // child.width = 1 child.height = h * ( 1/this.height) _heightUsed += h } return this }, draw: function() { this.proxyPanel.width = this._width() this.proxyPanel.height = this._height() for( var i = 0; i < this.children.length; i++ ) { this.children[ i ].draw() } }, refresh: function() { for( var i = 0; i < this.proxyPanel.shouldDraw.length; i++ ) { this.proxyPanel.shouldDraw[ i ].draw() } this.proxyPanel.shouldDraw.length = 0 }, mouseEvent: function(e){ // e.x -= this._x() // e.y -= this._y() for( var i = 0; i < this.children.length; i++ ) { var child = this.children[ i ] this.children[ i ].mouseEvent( e ) } }, touchEvent: function(e){ e.x -= this._x() e.y -= this._y() for( var i = 0; i < this.children.length; i++ ) { var child = this.children[ i ] this.children[ i ].touchEvent( e ) } }, _init: function() { this.useRelativeSizesAndPositions = this.panel.useRelativeSizesAndPositions this.proxyPanel.width = this._width() this.proxyPanel.height = this._height() this.proxyPanel.__proto__ = this.panel $.subscribe('/draw', this.refresh.bind( this ) ) Object.defineProperties(this, { bounds : { configurable: true, get : function() { return bounds; }, set : function(_bounds) { bounds = _bounds; this.x = bounds[0]; this.y = bounds[1]; this.width = bounds[2]; this.height = bounds[3]; this.layout() this.draw() } }, }) } }) .init( arguments[0] ) }; Interface.VBox.prototype = Interface.Widget; /**#Interface.Slider - Widget A vertical or horizontal slider. ## Example Usage## `a = new Interface.Slider({ bounds:[0,0,1,.2], isVertical:false }); panel = new Interface.Panel(); panel.add(a); ` ## Constructor **param** *properties*: Object. A dictionary of property values (see below) to set for the slider on initialization. - - - - **/ /**###Interface.Slider.isVertical : property Boolean. Whether or not the slider draws itself vertically or horizontally. Note this does not affect the boundaries of the slider, just the orientation of the slider's movement. **/ Interface.Slider = function() { Interface.extend(this, { type : 'Slider', isVertical : true, serializeMe : ["isVertical"], draw : function() { var x = this._x(), y = this._y(), width = this._width(), height= this._height(); this.ctx.fillStyle = this._background(); this.ctx.fillRect( x, y, width, height ); this.ctx.fillStyle = this._fill(); if(this.isVertical) { this.ctx.fillRect( x, y + height - this._value * height, width, this._value * height); }else{ this.ctx.fillRect( x, y, width * this._value, height); } if(this.label) { this.ctx.fillStyle = this._stroke(); this.ctx.textBaseline = 'middle'; this.ctx.textAlign = 'center'; this.ctx.font = this._font(); this.ctx.fillText(this.label, x + width / 2, y + height / 2); } this.ctx.strokeStyle = this._stroke(); this.ctx.strokeRect( x, y, width, height ); }, changeValue : function( xOffset, yOffset ) { if(this.hasFocus || !this.requiresFocus) { this._value = this.isVertical ? 1 - (yOffset / this._height()) : xOffset / this._width(); if(this._value < 0) { this._value = 0; // this.hasFocus = false; }else if(this._value > 1) { this._value = 1; // this.hasFocus = false; } this.value = this.min + (this.max - this.min) * this._value; if(this.value !== this.lastValue) { this.sendTargetMessage(); if(this.onvaluechange) this.onvaluechange(); this.refresh(); this.lastValue = this.value; } } }, mousedown : function(e, hit) { if(hit && Interface.mouseDown) this.changeValue( e.x - this._x(), e.y - this._y() ); }, mousemove : function(e, hit) { if(hit && Interface.mouseDown) this.changeValue( e.x - this._x(), e.y - this._y() ); }, mouseup : function(e, hit) { if(hit && Interface.mouseDown) this.changeValue( e.x - this._x(), e.y - this._y() ); }, touchstart : function(e, hit) { if(hit) this.changeValue( e.x - this._x(), e.y - this._y() ); }, touchmove : function(e, hit) { if(hit) this.changeValue( e.x - this._x(), e.y - this._y() ); }, touchend : function(e, hit) { if(hit) this.changeValue( e.x - this._x(), e.y - this._y() ); }, }) .init( arguments[0] ); }; Interface.Slider.prototype = Interface.Widget; /**#Interface.Crossfader - Widget A horizontal crossfader. ## Example Usage## `a = new Interface.Crossfader({ bounds:[0,0,1,.2], crossfaderWidth:20 }); panel = new Interface.Panel(); panel.add(a); ` ## Constructor **param** *properties*: Object. A dictionary of property values (see below) to set for the crossfader on initialization. - - - - **/ /**###Interface.Crossfader.crossfaderWidth : property Boolean. The width of the rectangle indicating the current position of the crossfader, in pixel values. TODO: use relative values when used by the panel. **/ Interface.Crossfader = function() { Interface.extend(this, { type : 'Crossfader', crossfaderWidth: 30, serializeMe : ["crossfaderWidth"], _value : .5, draw : function() { var x = this._x(), y = this._y(), width = this._width(), height= this._height(); this.ctx.fillStyle = this._background(); this.ctx.fillRect( x, y, width, height ); this.ctx.fillStyle = this._fill(); this.ctx.fillRect( x + (width - this.crossfaderWidth) * this._value, y, this.crossfaderWidth, height); this.ctx.strokeStyle = this._stroke(); this.ctx.strokeRect( x, y, width, height ); }, changeValue : function( xOffset, yOffset ) { if(this.hasFocus || !this.requiresFocus) { this._value = xOffset / this._width(); if(this._value < 0) { this._value = 0; //this.hasFocus = false; }else if(this._value > 1) { this._value = 1; //this.hasFocus = false; } this.value = this.min + (this.max - this.min) * this._value; if(this.value !== this.lastValue) { this.sendTargetMessage(); if(this.onvaluechange) this.onvaluechange(); this.refresh(); this.lastValue = this.value; } } }, mousedown : function(e) { this.changeValue( e.x - this._x(), e.y - this._y() ); }, mousemove : function(e) { this.changeValue( e.x - this._x(), e.y - this._y() ); }, mouseup : function(e) { this.changeValue( e.x - this._x(), e.y - this._y() ); }, touchstart : function(e, hit) { if(hit) this.changeValue( e.x - this._x(), e.y - this._y() ); }, touchmove : function(e, hit) { if(hit) this.changeValue( e.x - this._x(), e.y - this._y() ); }, touchend : function(e, hit) { if(hit) this.changeValue( e.x - this._x(), e.y - this._y() ); }, }) .init( arguments[0] ); }; Interface.Crossfader.prototype = Interface.Widget; /**#Interface.Button - Widget A button with a variety of on/off modes ## Example Usage## `a = new Interface.Button({ bounds:[0,0,.25,.25], mode:'contact', label:'test' }); panel = new Interface.Panel(); panel.add(a); ` ## Constructor **param** *properties*: Object. A dictionary of property values (see below) to set for the button on initialization. - - - - **/ /**###Interface.Button.mode : property String. Can be 'toggle', 'momentary' or 'contact'. In toggle mode, the button turns on when it is pressed and off when it is pressed again. In momentary mode, the button turns on when pressed and off when released. In contact mode, the button briefly flashes when pressed and sends its value. **/ /**###Interface.Button.label : property String. A text label to print in the center of the button. **/ Interface.Button = function() { Interface.extend(this, { type : 'Button', _value: 0, serializeMe : ["mode", "label"], mode : 'toggle', isMouseOver : false, isTouchOver : false, label : null, requiresFocus : false, draw : function() { var x = this._x(), y = this._y(), width = this._width(), height= this._height(); if(this._value) { this.ctx.fillStyle = this._fill(); }else{ this.ctx.fillStyle = this._background(); } this.ctx.fillRect( x, y, width, height ); if(this.label !== null) { this.ctx.fillStyle = this._stroke(); this.ctx.textBaseline = 'middle'; this.ctx.textAlign = 'center'; this.ctx.font = this._font(); this.ctx.fillText(this.label, x + width / 2, y + height / 2); } this.ctx.strokeStyle = this._stroke(); this.ctx.strokeRect( x, y, width, height ); }, changeValue : function( xOffset, yOffset ) { if(this.hasFocus || !this.requiresFocus) { this._value = !this._value; this.value = this._value ? this.max : this.min; if(this.value !== this.lastValue || this.mode === 'contact') { this.sendTargetMessage(); if(this.onvaluechange) this.onvaluechange(); this.draw(); this.lastValue = this.value; } } }, setValue : function(value, doNotDraw) { var r = this.max - this.min, v = value; this.value = value; if(this.min !== 0 || this.max !== 1) { v -= this.min; this._value = v / r; }else{ this._value = this.value; } this.lastValue = this.value; if(!doNotDraw && this.mode !== 'contact') this.refresh(); }, mousedown : function(e, hit) { if(hit && Interface.mouseDown) { this.isMouseOver = true; this.changeValue(); if(this.mode === 'contact') { var self = this; setTimeout( function() { self._value = 0; self.draw(); }, 75); } } }, mousemove : function(e, hit) { if(!this.requiresFocus && hit && Interface.mouseDown && !this.isMouseOver) { this.isMouseOver = true; if(this.mode !== 'contact') { this.changeValue();// e.x - this.x, e.y - this.y ); }else{ this._value = 1; this.draw(); var self = this; setTimeout( function() { self._value = 0; self.draw(); }, 75); } }else if(!hit && this.isMouseOver) { console.log( 'moved off!' ) this.isMouseOver = false; } }, mouseup : function(e) { if(this.mode === 'momentary') { this.changeValue();// e.x - this.x, e.y - this.y ); this.isMouseOver = false; } }, touchstart : function(e, hit) { if(hit) { this.isTouchOver = true; this.changeValue(); if(this.mode === 'contact') { var self = this; setTimeout( function() { self._value = 0; self.draw(); }, 75); } } }, touchmove : function(e, hit) { if(!this.requiresFocus && hit && !this.isTouchOver) { this.isTouchOver = true; if(this.mode !== 'contact') { this.changeValue();// e.x - this.x, e.y - this.y ); }else{ this._value = 1; this.draw(); var self = this; setTimeout( function() { self._value = 0; self.draw(); }, 75); } }else if(!hit && this.isTouchOver) { this.isTouchOver = false; } }, touchend : function(e) { this.isTouchOver = false; if(this.mode === 'momentary') this.changeValue();// e.x - this.x, e.y - this.y ); }, }) .init( arguments[0] ); }; Interface.Button.prototype = Interface.Widget; /**#Interface.ButtonV - Widget A button with a customizable shape and variety of on/off modes *contributed by Jonathan Simozar ## Example Usage## `a = new Interface.ButtonV({ bounds:[.25,0,.125,.8], points: [{x:1,y:0},{x:.5,y:0},{x:.5,y:.5},{x:0,y:.5},{x:0,y:1},{x:1,y:1},{x:1,y:0}], mode:'contact', label:'test', textLocation : {x:.5, y:.75}, }); panel = new Interface.Panel(); panel.add(a); ` ## Constructor **param** *properties*: Object. A dictionary of property values (see below) to set for the button on initialization. - - - - **/ /**###Interface.ButtonV.points : property Array. A set of coordinates used to customize the button shape. The coordinates are connected in the order of the indices. The first and last point must be the same. **/ /**###Interface.ButtonV.mode : property String. Can be 'toggle', 'momentary' or 'contact'. In toggle mode, the button turns on when it is pressed and off when it is pressed again. In momentary mode, the button turns on when pressed and off when released. In contact mode, the button briefly flashes when pressed and sends its value. **/ /**###Interface.ButtonV.label : property String. A text label to print at the textLocation coordinates of the button. **/ /**###Interface.ButtonV.textLocation : property Set. A set of x and y coordinates which position the the label within the bounds. **/ Interface.ButtonV = function() { Interface.extend(this, { type : 'ButtonV', _value: 0, serializeMe : ["mode", "label"], mode : 'toggle', isMouseOver : false, isTouchOver : false, label : null, points : [{x : 0, y : 0}, {x : 0, y : 1}, {x : 1,y : 1}, {x : 1, y : 0}, {x : 0, y : 0}], textLocation : {x:.5, y:.5}, draw : function() { var x = this._x(), y = this._y(), i = 0, width = this._width(), height= this._height(); if(this._value) { this.ctx.fillStyle = this._fill(); }else{ this.ctx.fillStyle = this._background(); } this.ctx.beginPath(); this.ctx.strokeStyle = this._stroke(); for (i; i < this.points.length; i++) { if (i === 0) { this.ctx.moveTo(x + this.points[i].x*width, y + this.points[i].y*height); } else this.ctx.lineTo(x + this.points[i].x*width, y + this.points[i].y*height); } //this.points[i].x is how to reference points.x this.ctx.lineTo(x + this.points[0].x*width, y + this.points[0].y*height); this.ctx.closePath(); this.ctx.fill(); this.ctx.stroke(); if(this.label !== null) { this.ctx.fillStyle = this._stroke(); this.ctx.textBaseline = 'middle'; this.ctx.textAlign = 'center'; this.ctx.font = this._font(); this.ctx.fillText(this.label, x + width*this.textLocation.x, y + height*this.textLocation.y); } }, changeValue : function( xOffset, yOffset ) { if(this.hasFocus || !this.requiresFocus) { this._value = !this._value; this.value = this._value ? this.max : this.min; if(this.value !== this.lastValue || this.mode === 'contact') { this.sendTargetMessage(); if(this.onvaluechange) this.onvaluechange(); this.draw(); this.lastValue = this.value; } } }, hitTest : function(e) { var w = this._width(), h = this._height(), x = this._x(), y = this._y(); if(e.x >= x && e.x <= x + w) { if(e.y >= y && e.y <= y + h) { var i = 0, p = this.points, sides = 0; for (i; i < p.length - 1; i++) { if(p[i+1].x > p[i].x) { if((p[i].x * w + x) <= e.x && e.x < (p[i+1].x * w + x)) { var yval = (p[i+1].y - p[i].y)/(p[i+1].x - p[i].x) * h/w * (e.x - p[i].x * w + x) + p[i].y * h + y; if(yval - e.y < 0) sides++; } } else if (p[i+1].x < p[i].x) { if(p[i].x * w + x >= e.x && e.x > p[i+1].x * w + x) { var yval = (p[i+1].y - p[i].y)/(p[i+1].x - p[i].x) * h/w * (e.x - p[i].x * w + x) + p[i].y * h + y; if(yval - e.y < 0) sides++; } } } if (sides % 2 == 1) return true; } } return false; }, setValue : function(value, doNotDraw) { var r = this.max - this.min, v = value; this.value = value; if(this.min !== 0 || this.max !== 1) { v -= this.min; this._value = v / r; }else{ this._value = this.value; } this.lastValue = this.value; if(!doNotDraw && this.mode !== 'contact') this.refresh(); }, mousedown : function(e, hit) { if(hit && Interface.mouseDown) { this.isMouseOver = true; this.changeValue(); if(this.mode === 'contact') { var self = this; setTimeout( function() { self._value = 0; self.draw(); }, 75); } } }, mousemove : function(e, hit) { if(!this.requiresFocus && hit && Interface.mouseDown && !this.isMouseOver) { this.isMouseOver = true; if(this.mode !== 'contact') { this.changeValue();// e.x - this.x, e.y - this.y ); }else{ this._value = 1; this.draw(); var self = this; setTimeout( function() { self._value = 0; self.draw(); }, 75); } }else if(!hit && this.isMouseOver) { this.isMouseOver = false; if(this.mode !== 'contact') { this.changeValue();// e.x - this.x, e.y - this.y ); }else{ this._value = 1; this.draw(); var self = this; setTimeout( function() { self._value = 0; self.draw(); }, 75); } } }, mouseup : function(e) { if(this.mode === 'momentary') { if( this.requiresFocus || ( !this.requiresFocus && this.isMouseOver) ) { this.isMouseOver = false; this.changeValue(); } } }, touchstart : function(e, hit) { if(hit) { this.isTouchOver = true; this.changeValue(); if(this.mode === 'contact') { var self = this; setTimeout( function() { self._value = 0; self.draw(); }, 75); } } }, touchmove : function(e, hit) { if(!this.requiresFocus && hit && !this.isTouchOver) { this.isTouchOver = true; if(this.mode !== 'contact') { this.changeValue();// e.x - this.x, e.y - this.y ); }else{ this._value = 1; this.draw(); var self = this; setTimeout( function() { self._value = 0; self.draw(); }, 75); } }else if(!hit && this.isTouchOver) { this.isTouchOver = false; if(this.mode !== 'contact') { this.changeValue();// e.x - this.x, e.y - this.y ); }else{ this._value = 1; this.draw(); var self = this; setTimeout( function() { self._value = 0; self.draw(); }, 75); } }else if(!hit && this.isTouchOver) { this.isTouchOver = false; } }, touchend : function(e) { if( this.momentary && this.requiresFocus || ( !this.requiresFocus && this.isTouchOver) ) { this.changeValue(); } this.isTouchOver = false; }, }) .init( arguments[0] ); }; Interface.ButtonV.prototype = Interface.Widget; /**#Interface.Piano - Widget A piano with adjustable ranges of pitches *contributed by Jonathan Simozar, with modifications by thecharlie ## Example Usage## `var c = new Interface.Piano({ bounds:[0,0,.8,.5], startletter : "C", startoctave : 3, endletter : "C", endoctave : 5, noteLabels : true, target: synth, onvaluechange : function() {this.target.note (this.frequency, this.value)}, }); panel = new Interface.Panel(); panel.add(a); ` ## Constructor **param** *properties*: Object. A dictionary of property values (see below) to set for the piano on initialization. - - - - **/ /**###Interface.Piano.onvaluechange : method The event handler fired whenever a piano update is received. Used to fire the the event handler for when a button update is recieved. **/ /**###Interface.Piano.endoctave : property Number. A number corresponding to the ending octave of the last note in the desired range. **/ /**###Interface.Piano.startletter : property String. A letter corresponding to the starting pitch for the desired range. To start on an accidental use sharps, not flats. For example, C#. **/ /**###Interface.Piano.startoctave : property Number. A number corresponding to the starting octave of the first note in the desired range. **/ /**###Interface.Piano.endletter : property String. A letter corresponding to the ending pitch for the desired range. To end on an accidental use sharps, not flats. For example, C#. **/ /**###Interface.Piano.endoctave : property Number. A number corresponding to the ending octave of the last note in the desired range. **/ /**###Interface.Piano.noteLabels : property Boolean. A boolean corresponding to showing the note labels when true and hiding the note labels when false. **/ /**###Interface.Piano.target : property Object. The instrument used to make sound on each key. **/ Interface.Piano = function() { Interface.extend(this, { type : 'Piano', _value: 0, serializeMe : ['mode', 'label'], mode : 'toggle', isMouseOver : false, isTouchOver : false, label : null, startletter : 'C', startoctave : 3, endletter : 'C', endoctave : 5, target : null, noteLabels : false, _initialized : false, keyMap: [ 'Z','S','X','D','C','V','G','B','H','N','J','M',','], children: [], play: function( noteNum, duration ) { if( isNaN(duration) ) { duration = 4410 } if( typeof Gibber !== 'undefined' ) { duration = Gibber.Clock.time( duration ) } var child = this.children[ noteNum ] if( child ) { child.changeValue() future( function() { if( child._value == 1 ) child.changeValue() }, duration ) } }, onkeyup: function( e ) { var c = Interface.keyCodeToChar[ e.keyCode ], keyNum = this.keyMap.indexOf( c ), child = this.children[ keyNum ] if( typeof child !== 'undefined' && child._value == 1 ) { child.changeValue() } }, onkeydown: function( e ) { var c = Interface.keyCodeToChar[ e.keyCode ], keyNum = this.keyMap.indexOf( c ), child = this.children[ keyNum ] if( typeof child !== 'undefined' && child._value == 0 ) { child.changeValue() } }, onvaluechange : function() { this.values = [this.frequency,this.value] }, onboundschange: function() { if( this._initialized) this.placeKeys() }, draw : function() { for( var i = 0; i < this.children.length; i++ ) { this.children[i].refresh() } return this }, placeKeys: function() { var x = this._x(), y = this._y(), width = this._width(), height = this._height(), octave = this.startoctave, startnote = 0, endnote = 0, keylabel = ["0","C","C#/Db","D","D#/Eb","E","F","F#/Gb","G","G#/Ab","A","A#/Bb","B"], keyid = ["0","C","C#","D","D#","E","F","F#","G","G#","A","A#","B"], keynid = [0, 1, 2, 2, 3, 3, 4, 5, 5, 6, 6, 7, 7], notes = ( endnote + this.endoctave * 12) - (startnote + this.startoctave * 12) + 1, dist = ( keynid[ endnote ] + this.endoctave * 7 ) - ( keynid[ startnote ] + this.startoctave * 7 ) + 1, j = 0; if( this._initialized ) { this.clear() for( var i = this.children.length - 1; i >= 0; i-- ) { var key = this.children.pop() this.panel.remove( key ) } } for (var i = 1; i < 13; i++) { if ( this.startletter === keyid[ i ] ) startnote = i; if ( this.endletter === keyid[ i ] ) endnote = i; } if ( [ 2,4,7,9,11 ].indexOf( endnote ) > -1 ) dist--; for (var i = 0; i < notes - 1; i++ ) { var points, textLocation, bg, fg, bounds, label switch( startnote ) { case 1: points = [{x:0,y:0},{x:.6,y:0},{x:.6,y:.625},{x:1,y:.625},{x:1,y:1},{x:0,y:1},{x:0,y:0}] // left bg = this._fill() textLocation = { x:.5, y:.75 } fg = this._background() bounds = [ j/dist*this.width + this.x, this.y, this.width/dist, this.height ] label = this.noteLabels ? keylabel[startnote] + octave : null break; case 2: points = [{x:.1,y:0},{x:.7,y:0},{x:.7,y:1},{x:.1,y:1},{x:.1,y:0}] //black textLocation = { x:.3925, y:.5 } bg = this._background() fg = this._fill() bounds = [(j-.5)/dist *this.width + this.x, this.y,this.width/dist,.625*this.height] label = this.noteLabels ? keylabel[startnote] : null break; case 3: points = [{x:.2,y:0},{x:.8,y:0},{x:.8,y:.625},{x:1,y:.625},{x:1,y:1},{x:0,y:1},{x:0,y:.625},{x:.2,y:.625},{x:.2,y:0}] // mid textLocation = { x:.5, y:.75 } bg = this._fill() fg = this._background() bounds = [j/dist*this.width + this.x,this.y,this.width/dist,this.height] label = this.noteLabels ? keylabel[startnote] : null break; case 4: points = [{x:.3,y:0},{x:.9,y:0},{x:.9,y:1},{x:.3,y:1},{x:.3,y:0}], //black textLocation = {x:.6075, y:.5} bg = this._background() fg = this._fill() bounds = [(j-.5)/dist *this.width + this.x, this.y,this.width/dist,.625*this.height] label = this.noteLabels ? keylabel[startnote] : null break; case 5: points = [{x:1,y:0},{x:.4,y:0},{x:.4,y:.625},{x:0,y:.625},{x:0,y:1},{x:1,y:1},{x:1,y:0}] //right textLocation = {x:.5, y:.75} bg = this._fill() fg = this._background() bounds = [j/dist*this.width+ this.x,this.y,this.width/dist,this.height] label = this.noteLabels ? keylabel[startnote] : null break; case 6: points = [{x:0,y:0},{x:0.57142857,y:0},{x:0.57142857,y:.625},{x:1,y:.625},{x:1,y:1},{x:0,y:1},{x:0,y:0}] //left textLocation = {x:.5, y:.75} bg = this._fill() fg = this._background() bounds = [j/dist*this.width+ this.x,this.y,this.width/dist,this.height] label = this.noteLabels ? keylabel[startnote] : null break; case 7: points = [{x:0.07142857,y:0},{x:0.64285714,y:0},{x:0.64285714,y:1},{x:0.07142857,y:1},{x:0.07142857,y:0}] //black textLocation = {x:.3925, y:.5} bg = this._background() fg = this._fill() bounds = [(j-.5)/dist*this.width + this.x, this.y,this.width/dist,.625*this.height] label = this.noteLabels ? keylabel[startnote] : null break; case 8: points = [{x:0.14285714,y:0},{x:0.71428571,y:0},{x:0.71428571,y:.625},{x:1,y:.625},{x:1,y:1},{x:0,y:1},{x:0,y:.625},{x:0.14285714,y:.625},{x:0.14285714,y:0}], //middle textLocation = {x:.5, y:.75} bg = this._fill() fg = this._background() bounds = [j/dist*this.width + this.x,this.y,this.width/dist,this.height] label = this.noteLabels ? keylabel[startnote] : null break; case 9: points = [{x:0.21428571,y:0},{x:0.78571428,y:0},{x:0.78571428,y:1},{x:0.21428571,y:1},{x:0.21428571,y:0}] //black bg = this._background() fg = this._fill() bounds = [(j-.5)/dist*this.width + this.x, this.y,this.width/dist,.625*this.height] label = this.noteLabels ? keylabel[startnote] : null break; case 10: points = [{x:0.28571428,y:0},{x:0.85714285,y:0},{x:0.85714285,y:.625},{x:1,y:.625},{x:1,y:1},{x:0,y:1},{x:0,y:.625},{x:0.28571428,y:.625},{x:0.28571428,y:0}], //middle bg = this._fill() fg = this._background() textLocation = {x:.5, y:.75} bounds = [j/dist*this.width + this.x,this.y,this.width/dist,this.height] label = this.noteLabels ? keylabel[startnote] : null break; case 11: points = [{x:0.35714285,y:0},{x:0.92857142,y:0},{x:0.92857142,y:1},{x:0.35714285,y:1},{x:0.35714285,y:0}], //black bg = this._background() fg = this._fill() textLocation = {x:.6075, y:.5} bounds = [(j-.5)/dist*this.width + this.x, this.y,this.width/dist,.625*this.height] label = this.noteLabels ? keylabel[startnote] : null break; case 12: points = [{x:1,y:0},{x:0.42857142,y:0},{x:0.42857142,y:.625},{x:0,y:.625},{x:0,y:1},{x:1,y:1},{x:1,y:0}] //right bg = this._fill() fg = this._background() textLocation = {x:.5, y:.75} bounds = [j/dist*this.width+ this.x,this.y,this.width/dist,this.height] label = this.noteLabels ? keylabel[startnote] : null break; } var _key = new Interface.ButtonV({ points: points, textLocation : textLocation, target : this.target, onvaluechange: this.onvaluechange, frequency: Math.pow(2,(startnote + 12*octave - 49)/12)*261.626, background: bg, fill: fg, stroke: this._stroke(), bounds: bounds, label: label, requiresFocus : false, mode:'momentary', }); if ( [ 2,4,7,9,11 ].indexOf( startnote ) === -1 ) j++; this.children.push(_key) this.panel.add(_key); startnote++; if (startnote > 12) { startnote = 1; octave++; } } if (startnote == 2 || startnote == 4 || startnote == 7 || startnote == 9 || startnote == 11) var pkeys = new Interface.ButtonV({ points: [{x:.166,y:0},{x:.5,y:0},{x:.5,y:1},{x:.166,y:1},{x:.166,y:0}], //black target : this.target, onvaluechange: this.onvaluechange, background: this._background(), frequency: Math.pow(2,(startnote + 12*octave - 49)/12)*261.626, bounds:[(j-.5)/dist*this.width + this.x, this.y,this.width/dist,.625*this.height], label: this.noteLabels ? keylabel[startnote] : null, stroke: this._stroke(), requiresFocus : false, mode:'momentary' }); else if (startnote == 1) var pkeys = new Interface.ButtonV({ textLocation : {x:.5, y:.75}, target : this.target, onvaluechange: this.onvaluechange, frequency: Math.pow(2,(startnote + 12*octave - 49)/12)*261.626, background: this._fill(), fill: this._background(), stroke: this._stroke(), bounds:[j/dist*this.width + this.x,this.y,this.width/dist,this.height], label: this.noteLabels ? keylabel[startnote] + octave : null, requiresFocus : false, mode:'momentary' }); else if (startnote == 4) var pkeys = new Interface.ButtonV({ textLocation : {x:.5, y:.75}, target : this.target, onvaluechange: this.onvaluechange, frequency: Math.pow(2,(startnote + 12*octave - 49)/12)*261.626, background: this._fill(), fill: this._background(), stroke: this._stroke(), bounds:[j/dist*this.width + this.x,this.y,this.width/dist,this.height], label: this.noteLabels ? keylabel[startnote] : null, requiresFocus : false, mode:'momentary' }); else var pkeys = new Interface.ButtonV({ points: [{x:1,y:0},{x:.33,y:0},{x:.33,y:.625},{x:0,y:.625},{x:0,y:1},{x:1,y:1},{x:1,y:0}], //right textLocation : {x:.5, y:.75}, target : this.target, onvaluechange: this.onvaluechange, frequency: Math.pow(2,(startnote + 12*octave - 49)/12)*261.626, background: this._fill(), fill: this._background(), stroke: this._stroke(), bounds:[j/dist*this.width + this.x,this.y,this.width/dist,this.height], label: this.noteLabels ? keylabel[startnote] : null, requiresFocus : false, mode:'momentary' }); this.children.push(pkeys) this.panel.add(pkeys); this._initialized = true }, _init : function() { this.placeKeys() } }) .init( arguments[0] ); }; Interface.Piano.prototype = Interface.Widget; /**#Interface.Knob - Widget A virtual knob. ## Example Usage## `a = new Interface.Knob({ x:.1, y:.1, radius:.3 }); panel = new Interface.Panel(); panel.add(a); ` ## Constructor **param** *properties*: Object. A dictionary of property values (see below) to set for the knob on initialization. - - - - **/ /**###Interface.Knob.radius : property Number. The size of the Knob. **/ /**###Interface.Knob.knobBuffer : property Number. The size of the space in the middle of the knob. **/ /**###Interface.Knob.centerZero : property Number. If true, the knob is centered at zero. Useful for panning knobs. **/ /**###Interface.Knob.usesRotation : property Number. If true, the knob value is determined by the slope of the touch or mouse event in relation to the knob. When false, the user simply presses the knob and moves their finger/mouse up and down to change its value. **/ Interface.Knob = function() { Interface.extend(this, { type : 'Knob', _value: 0, serializeMe : ["usesRotation", "knobBuffer"], knobBuffer:3, lastPosition: 0, usesRotation: true, draw : function() { var x = this._x(), y = this._y(), width = this._width(), height= this._height(), radius = width / 2; this.ctx.clearRect(x, y, radius * 2,radius * 2); this.ctx.strokeStyle = this._stroke(); this.ctx.fillStyle = this._background(); // draw background of widget first var angle0 = Math.PI * .6; var angle1 = Math.PI * .4; this.ctx.beginPath(); this.ctx.arc(x + radius, y + radius, radius - this.knobBuffer, angle0, angle1, false); this.ctx.arc(x + radius, y + radius, (radius - this.knobBuffer) * .3 , angle1, angle0, true); this.ctx.closePath(); this.ctx.fill(); this.ctx.fillStyle = this._fill(); // now draw foreground... if(this.centerZero) { var angle3 = Math.PI * 1.5; var angle4; if(this._value >= .5) { angle4 = Math.PI * (1.5 + (this._value - .5) * 1.8); // from 1.5 to 2.4 }else{ angle4 = Math.PI * (1.5 - ((1 - this._value * 2) * .9)); // from 1.5 to .6 } if(this._value > Math.PI * 1.8) this._value -= Math.PI * 1.8; // wrap around this.ctx.beginPath(); this.ctx.arc(x + radius, y + radius, radius - this.knobBuffer, angle3, angle4, (this._value < .5)); this.ctx.arc(x + radius, y + radius, (radius - this.knobBuffer) * 0.3, angle4, angle3, (this._value > .5)); this.ctx.closePath(); // if(this._value > .495 && this._value < .505) { // draw circle if centered? // this.ctx.beginPath(); // this.ctx.arc(this.x + radius , this.y + radius, (radius - this.knobBuffer) * .3, 0, Math.PI*2, true); // this.ctx.closePath(); // } this.ctx.fill(); } else { if(!this.isInverted) { var angle2 = Math.PI * .6 + this._value * 1.8 * Math.PI; if(angle2 > 2 * Math.PI) angle2 -= 2 * Math.PI; }else{ var angle2 = Math.PI * (0.4 - (1.8 * this._value)); } this.ctx.beginPath(); if(!this.isInverted) { this.ctx.arc(x + radius, y + radius, radius - this.knobBuffer, angle0, angle2, false); this.ctx.arc(x + radius, y + radius, (radius - this.knobBuffer) * .3, angle2, angle0, true); } else { this.ctx.arc(x + radius, y + radius, radius - this.knobBuffer, angle1, angle2 ,true); this.ctx.arc(x + radius, y + radius, (radius - this.knobBuffer) * .3, angle2, angle1, false); } this.ctx.closePath(); this.ctx.fill(); } this.ctx.beginPath(); this.ctx.arc(x + radius, y + radius, radius - this.knobBuffer, angle0, angle1, false); this.ctx.arc(x + radius, y + radius, (radius - this.knobBuffer) * .3 , angle1, angle0, true); this.ctx.closePath(); this.ctx.stroke(); if(this.label !== null) { this.ctx.fillStyle = this._stroke(); this.ctx.textBaseline = 'middle'; this.ctx.textAlign = 'center'; this.ctx.font = this._font(); this.ctx.fillText(this.label, x + radius, y + radius * 2.25); } }, setValue : function(value, doNotDraw) { var r = this.max - this.min, v = value; this.lastValue = this.value; this.value = value; if(this.min !== 0 || this.max !== 1) { v -= this.min; this._value = v / r; }else{ this._value = this.value; } if(this.value !== this.lastValue) { this.sendTargetMessage(); if(this.onvaluechange) this.onvaluechange(); this.refresh(); this.lastValue = this.value; } if(!doNotDraw) this.refresh(); }, changeValue : function( xOffset, yOffset ) { if(this.hasFocus || !this.requiresFocus) { var radius = this._width() / 2; this.lastValue = this.value; if(!this.usesRotation) { if (this.lastPosition != -1) { this._value -= (yOffset - this.lastPosition) / (radius * 2); } }else{ var xdiff = radius - xOffset; var ydiff = radius - yOffset; var angle = Math.PI + Math.atan2(ydiff, xdiff); this._value = ((angle + (Math.PI * 1.5)) % (Math.PI * 2)) / (Math.PI * 2); if(this.lastRotationValue > .8 && this._value < .2) { this._value = 1; }else if(this.lastRotationValue < .2 && this._value > .8) { this._value = 0; } } if (this._value > 1) this._value = 1; if (this._value < 0) this._value = 0; this.lastRotationValue = this._value; this.lastPosition = yOffset; var range = this.max - this.min; this.value = this.min + this._value * range; if(this.value !== this.lastValue) { this.sendTargetMessage(); if(this.onvaluechange) this.onvaluechange(); this.refresh(); this.lastValue = this.value; } } }, hitTest : function(e) { if( e.x >= this._x() && e.x < this._x() + this._width() ) { if( e.y >= this._y() && e.y < this._y() + this._width() ) { return true; } } return false; }, mousedown : function(e) { this.lastPosition = e.y - this._y(); this.changeValue( e.x - this._x(), e.y - this._y() ); }, mousemove : function(e) { this.changeValue( e.x - this._x(), e.y - this._y() ); }, mouseup : function(e) {}, touchstart : function(e) { this.lastPosition = e.y - this._y(); this.changeValue( e.x - this._x(), e.y - this._y() ); }, touchmove : function(e) { this.changeValue( e.x - this._x(), e.y - this._y() ); }, touchend : function(e) {}, _init : function() { var width = this.width, height = this.height; Object.defineProperty(this, 'width', { configurable: true, get : function() { return width; }, set : function(_width) { this.clear(); width = height = _width; this.refresh(); } }); Object.defineProperty(this, 'height', { configurable: true, get : function() { return height; }, set : function(_height) { height = _height; } }); }, }) .init( arguments[0] ); }; Interface.Knob.prototype = Interface.Widget; function sign(n) { if(n < 0) return -1; return 1; } /**#Interface.XY - Widget A multitouch XY controller with optional built-in physics. ## Example Usage## `a = new Interface.XY({ x:0, y:0, numChildren:2 }); panel = new Interface.Panel(); panel.add(a); ` ## Constructor **param** *properties*: Object. A dictionary of property values (see below) to set for the XY on initialization. - - - - **/ /**###Interface.XY.childWidth : property Number. The size of the children, currently in pixels. TODO: use relative values when the panel is using relative sizes and positions. **/ /**###Interface.XY.usePhysics : property Boolean. Wheter or not the physics engine should be turned on. **/ /**###Interface.XY.friction : property Number. Default .9. The amount of friction in the physics system. High values mean children will decelerate quicker. **/ /**###Interface.XY.maxVelocity : property Number. Default 10. The maximum velocity for each child. **/ /**###Interface.XY.detectCollisions : property Boolean. Default true. When true, children bounce off one another. **/ /**###Interface.XY.values : property Array. An array of objects taking the form {x,y} that store the x and y positions of every child. So, to get the x position of child #0: myXY.values[0].x **/ /**###Interface.XY.children : property Array. An array of objects representing the various children of the widget. **/ /**###Interface.XY.animate : method This is called to run the physics engine, draw widgets with updated positions, change values of widgets and call appropriate event handlers. **/ Interface.XY = function() { var self = this, posDiff = {x:0, y:0}, velDiff = {x:0, y:0}, normal = {x:0, y:0}, cDot = 0; Interface.extend(this, { type : 'XY', _value : 0, serializeMe : ["childWidth", "childHeight", "numChildren", "usePhysics", "values", "friction", "maxVelocity", "detectCollisions", "fps"], childWidth : 25, childHeight : 25, children : [], values : [], // objects containing x and y values _values : [], // serialized floats alternating between x and y numChildren : 1, usePhysics : true, friction : .9, activeTouch : null, maxVelocity : 10, detectCollisions : true, touchCount : 0, timer : null, fps : 30, outputInitialValues: true, rainbow: function() { //console.log("RAINBOW", this.children.length); for(var i = 0; i < this.children.length; i++) { var child = this.children[i]; child.fill = Interface.XY.colors[i % Interface.XY.colors.length]; //'rgba('+Math.round(Math.random()*255)+','+Math.round(Math.random()*255)+','+Math.round(Math.random()*255)+',.2)'; //console.log("YUM", child.fill) } //this.refresh() }, remove: function() { this.stopAnimation(); Interface.widgets.splice( Interface.widgets.indexOf( this ), 1 ); }, add : function() { if(this.usePhysics) this.startAnimation(); }, startAnimation : function() { if(this.timer === null) { this.timer = setInterval( function() { self.refresh(); }, (1 / this.fps) * 1000); } }, stopAnimation : function() { clearInterval(this.timer); this.timer = null; }, animate : function() { var x = this._x(), y = this._y(), width = this._width(), height = this._height(), shouldrunvaluechange = false; for(var i = 0; i < this.children.length; i++) { var moveX = moveY = false, child = this.children[i]; if(child.x + child.vx < width && child.x + child.vx > 0) { child.x += child.vx; }else{ if(child.x + child.vx >= width && child.vx > 0 ) { child.vx *= -1; }else if(child.x + child.vx <= 0 && child.vx < 0) { child.vx *= -1; }else{ child.x += child.vx; } } if(child.y + child.vy < height && child.y + child.vy > 0) { child.y += child.vy; }else{ if(child.y + child.vy >= height && child.vy > 0 ) { child.vy *= -1; }else if(child.y + child.vy <= 0 && child.vy < 0) { child.vy *= -1; }else{ child.y += child.vy; } } child.vx *= this.friction; child.vy *= this.friction; var newValueX = child.x / width; var newValueY = child.y / height; var range = this.max - this.min; if(this.values[child.id].x !== newValueX || this.values[child.id].y !== newValueY) { this.values[child.id].x = this.min + range * newValueX; this.values[child.id].y = this.min + range * newValueY; shouldrunvaluechange = true; } if(this.detectCollisions) { if(!child.collideFlag) { this.collisionTest(child); }else{ child.collideFlag = false; } } child.vx = Math.abs(child.vx) > this.maxVelocity ? this.maxVelocity * sign(child.vx) : child.vx; child.vy = Math.abs(child.vy) > this.maxVelocity ? this.maxVelocity * sign(child.vy): child.vy; } if(shouldrunvaluechange) { this.sendTargetMessage(); if(this.onvaluechange) { this.onvaluechange(); } } }, // MultiXY sends out all child values in serialized xy pairs sendValues : function() { var tt = ''; this._values.length = 0; for(var i = 0; i < this.values.length; i++) { tt += 'ff'; this._values.push( this.values[i].x ); this._values.push( this.values[i].y ); } if(this.target === "OSC") { if(Interface.OSC) { Interface.OSC.send( this.key, tt, this._values ); } } }, collisionTest : function(c1) { var cw2 = (this.childWidth * 2) * (this.childWidth * 2); for(var i = 0; i < this.children.length; i++) { var c2 = this.children[i]; if(c1.id !== c2.id) { var distance = Math.pow(c1.x - c2.x, 2) + Math.pow(c1.y - c2.y, 2); if(distance < cw2) { // avoid square root by raising the distance check this.collide(c1, c2) } } } }, collide : function(c1,c2) { // posDiff, velDiff and normal are upvalues for gc performance posDiff.x = c1.x - c2.x; posDiff.y = c1.y - c2.y; velDiff.x = c1.vx - c2.vx; velDiff.y = c1.vy - c2.vy; cDot = Math.sqrt( Math.pow(posDiff.x, 2) + Math.pow(posDiff.y, 2) ); normal.x = posDiff.x / cDot; normal.y = posDiff.y / cDot; var d = (normal.x * velDiff.x) + (normal.y * velDiff.y); c2.vx = c1.vx + d * normal.x; c2.vy = c1.vy + d * normal.y; c1.vx = c2.vx - d * normal.x; c1.vy = c2.vy - d * normal.y; c2.x -= normal.x; c2.y -= normal.y; c1.x += normal.x; c1.y += normal.y; c1.vx = Math.abs(c1.vx) > this.maxVelocity ? this.maxVelocity * sign(c1.vx) : c1.vx; c1.vy = Math.abs(c1.vy) > this.maxVelocity ? this.maxVelocity * sign(c1.vy) : c1.vy; c2.vx = Math.abs(c2.vx) > this.maxVelocity ? this.maxVelocity * sign(c2.vx) : c2.vx; c2.vy = Math.abs(c2.vy) > this.maxVelocity ? this.maxVelocity * sign(c2.vy) : c2.vy; c1.collideFlag = true; c2.collideFlag = true; }, draw : function() { var x = this._x(), y = this._y(), width = this._width(), height= this._height(); if(this.usePhysics) this.animate(); this.ctx.fillStyle = this._background(); //this.ctx.fillRect( this.x, this.y, this.width, this.height ); this.ctx.strokeStyle = this._stroke(); //this.ctx.strokeRect( this.x, this.y, this.width, this.height ); this.ctx.save(); this.ctx.beginPath(); this.ctx.moveTo(x, y); this.ctx.lineTo(x + width, y); this.ctx.lineTo(x + width, y + height); this.ctx.lineTo(x, y + height); this.ctx.lineTo(x, y); this.ctx.fill(); this.ctx.stroke(); this.ctx.clip(); this.ctx.fillStyle = this._fill(); for(var i = 0; i < this.children.length; i++) { var child = this.children[i]; this.ctx.lineWidth = 2 this.ctx.fillStyle = child.fill || this._fill(); this.ctx.beginPath(); this.ctx.arc(x + child.x, y + child.y, this.childWidth, 0, Math.PI*2, true); this.ctx.closePath(); this.ctx.fill(); this.ctx.stroke(); //this.ctx.fillRect( this.x + child.x, this.y + child.y, this.childWidth, this.childHeight); this.ctx.textBaseline = 'middle'; this.ctx.textAlign = 'center'; this.ctx.fillStyle = this._stroke(); this.ctx.font = this._font(); this.ctx.fillText(child.id, x + child.x, y + child.y); } this.ctx.closePath(); this.ctx.restore(); }, changeValue : function( touch, xOffset, yOffset ) { if(this.hasFocus || !this.requiresFocus) { touch.x = xOffset; if(touch.x < 0 ) touch.x = 0; if(touch.x > this._width()) touch.x = this._width(); touch.y = yOffset;// - this.half; if(touch.y < 0) touch.y = 0; if(touch.y > this._height()) touch.y = this._height(); this.values[touch.id].x = xOffset / this._width(); this.values[touch.id].y = yOffset / this._height(); if(this.onvaluechange) this.onvaluechange(); if(!this.usePhysics) { this.sendTargetMessage(); this.refresh(); } } }, makeChildren : function() { for(var i = 0; i < this.numChildren; i++) { var x = Math.random() * this._width(), y = Math.random() * this._height() this.children.push({ id:i, x:x, y:y, vx:0, vy:0, collideFlag:false, isActive:false, lastPosition:null, }); this.values.push({ x:null, y:null }); } }, touchEvent : function(touch) { var isHit = this.hitTest(touch); var touchMouseName = convertTouchEvent(touch.type); if(isHit) { if(touch.type === 'touchstart') { this.hasFocus = true; this.touchCount++; this.trackTouch(touch.x - this._x(), touch.y - this._y(), touch); }else{ if(this[touch.type]) this[touch.type](touch, isHit, touch.childID); // normal event } if(this['on'+touch.type]) this['on'+touch.type](touch, isHit, touch.childId); // user defined event if(this['on'+touchMouseName]) this['on'+touchMouseName](touch, isHit); // user defined event }else if(touch.type === 'touchend'){ this.touchCount--; if(this.touchCount === 0) { this.hasFocus = false; }else if(this.touchCount < 0 ) { this.touchCount = 0; } this.touchend(touch) if(this['on'+touch.type]) this['on'+touch.type](touch, isHit, touch.childId); // user defined event if(this['on'+touchMouseName]) this['on'+touchMouseName](touch, isHit); // user defined event } }, trackMouse : function(xPos, yPos, id) { var closestDiff = 10000, touchFound = null, touchNum = null; for(var i = 0; i < this.children.length; i++) { var touch = this.children[i], xdiff = Math.abs(touch.x - xPos), ydiff = Math.abs(touch.y - yPos); if(xdiff + ydiff < closestDiff) { closestDiff = xdiff + ydiff; touchFound = touch; touchNum = i; } } touchFound.isActive = true; touchFound.vx = 0; touchFound.vy = 0; if(touchFound != null) { this.changeValue(touchFound, xPos, yPos); } this.activeTouch = touchFound; this.activeTouch.lastTouch = null; this.lastTouched = touchFound; }, mousedown : function(e) { if(this.hitTest(e)) { this.trackMouse(e.x - this._x(), e.y - this._y()); } }, mousemove : function(e) { if(this.hitTest(e) && this.activeTouch !== null) { if(this.activeTouch.lastTouch === null) { this.activeTouch.lastTouch = {x:e.x - this._x(), y:e.y - this._y()}; }else{ var now = {x:e.x - this._x(), y:e.y - this._y()}; this.activeTouch.velocity = {x:now.x - this.activeTouch.lastTouch.x, y:now.y - this.activeTouch.lastTouch.y }; this.activeTouch.lastTouch = now; } this.changeValue(this.activeTouch, e.x - this._x(), e.y - this._y()); } }, mouseup : function(e) { if(this.activeTouch !== null) { this.activeTouch.vx = this.activeTouch.velocity.x; this.activeTouch.vy = this.activeTouch.velocity.y; this.activeTouch.lastTouch = null; this.activeTouch = null; } for(var i = 0; i < this.children.length; i++) { this.children[i].isActive = false; } }, trackTouch : function(xPos, yPos, _touch) { var closestDiff = 10000; var touchFound = null; var touchNum = null; for(var i = 0; i < this.children.length; i++) { var touch = this.children[i]; var xdiff = Math.abs(touch.x - xPos); var ydiff = Math.abs(touch.y - yPos); if(xdiff + ydiff < closestDiff && !touch.isActive) { closestDiff = xdiff + ydiff; touchFound = touch; touchNum = i; } } touchFound.isActive = true; touchFound.vx = 0; touchFound.vy = 0; touchFound.identifier = _touch.identifier; touchFound.childID = touchNum; if(touchFound != null) this.changeValue(touchFound, xPos, yPos); this.lastTouched = touchFound; return touchFound.childID; }, touchstart : function(touch) { // if(this.hitTest(touch)) { // this.trackTouch(touch.x - this.x, touch.y - this.y, touch); // } }, touchmove : function(touch) { for(var t = 0; t < this.children.length; t++) { _t = this.children[t]; if(touch.identifier === _t.identifier) { this.changeValue(_t, touch.x - this._x(), touch.y - this._y()); var now = {x:touch.x - this._x(), y:touch.y - this._y()}; if(_t.lastPosition !== null) { _t.velocity = {x:now.x - _t.lastPosition.x, y:now.y - _t.lastPosition.y }; } _t.lastPosition = now; } } }, touchend : function(touch) { var found = false; var tu = null; for(var t = 0; t < this.children.length; t++) { var _t = this.children[t]; if(touch.identifier === _t.identifier) { if( _t.velocity ) { _t.vx = _t.velocity.x; _t.vy = _t.velocity.y; } _t.lastPosition = null; _t.isActive = false; found = true; tu = t.childID; } } if(found) { this.touchUp = tu; } //if(!found) console.log("NOT FOUND", touch.identifier); }, _init : function() { this.makeChildren(); if( this.outputInitialValues ) { this.sendTargetMessage(); } }, }) .init( arguments[0] ); this.requiresFocus = false; // is a widget default... must set after init. this.half = this.childWidth / 2; var numChildren = this.numChildren; Object.defineProperty(this, 'numChildren', { get : function() { return numChildren; }, set : function(_numChildren) { var temp = _numChildren; while(_numChildren > numChildren) { this.children.push({ id:this.children.length, x:Math.random() * this._width(), y:Math.random() * this._height(), vx:0, vy:0, collideFlag:false, isActive:false, lastPosition:null, }); this.values.push({ x:null, y: null}); numChildren++; } while(_numChildren < numChildren) { this.chidren.pop(); this.values.pop(); numChildren--; } this.refresh(); numChildren = _numChildren; } }); }; Interface.XY.prototype = Interface.Widget; Interface.XY.colors = [ 'rgba(255,0,0,.35)', 'rgba(0,255,0,.35)', 'rgba(0,0,255,.35)', 'rgba(0,255,255,.35)', 'rgba(255,0,255,.35)', 'rgba(255,255,0,.35)', ]; /**#Interface.Menu - Widget A multi-option dropdown menu. ## Example Usage## `a = new Interface.Menu({x:0, y:0, options:['red', 'yellow', 'green'] }); a.onvaluechange = function() { b.background = this.value; } b = new Interface.Slider({x:.5, y:.5, width:.2, height:.3}); panel = new Interface.Panel(); panel.add(a,b); ` ## Constructor **param** *properties*: Object. A dictionary of property values (see below) to set for the menu on initialization. - - - - **/ /**###Interface.Menu.options : property Array. A list of values found in the menu. **/ /**###Interface.Menu.css : property Object. A dictionary of css keys / values to be applied to the menu. **/ /**###Interface.Menu.onvaluechange : method The event handler fired whenever the selected menu option changes. param **newValue** Number or String. The new menu value. param **oldValue** Number or String. The previous menu value. **/ Interface.Menu = function() { Interface.extend(this, { type : 'Menu', _value: 0, serializeMe : ["options", "fontSize"], options: [], fontSize:15, touchEvent: function(e) { // we have to simulate this since the actual event was cancelled to avoid scrolling behavior if(this.hitTest(e)) { e.stopPropagation(); /*var evt = document.createEvent('TouchEvent'); evt.initUIEvent('touchstart', true, true); evt.view = window; evt.screenX = e.screenX; evt.screenY = e.screenY; evt.clientX = e.clientX; evt.clientY = e.clientY; evt.bubbles = false; evt.view = window; evt.altKey = false; evt.ctrlKey = false; evt.shiftKey = false; evt.metaKey = false; this.element.dispatchEvent(evt);*/ } }, _init : function() { this.element = $(" tag. ## Example Usage## `a = new Interface.TextField({x:0, y:0, width:.5, height:.5, value:'starting value', onvaluechange: function() { alert( this.value ); } }); panel = new Interface.Panel(); panel.add(a); ` ## Constructor **param** *properties*: Object. A dictionary of property values (see below) to set for the textfield on initialization. - - - - **/ /**###Interface.TextField.fontSize : property Number. The size in pixels for the font used in the text field **/ /**###Interface.TextField.css : property Object. Extra css that you would like to apply to the input element **/ Interface.TextField = function() { Interface.extend(this, { type : 'TextField', serializeMe : ["fontSize"], fontSize: 15, touchEvent: function(e) { // we have to simulate this since the actual event was cancelled to avoid scrolling behavior if(this.hitTest(e)) { var evt = document.createEvent('TouchEvent'); evt.initUIEvent('touchstart', true, true); evt.view = window; evt.screenX = e.screenX; evt.screenY = e.screenY; evt.clientX = e.clientX; evt.clientY = e.clientY; evt.bubbles = false; evt.view = window; evt.altKey = false; evt.ctrlKey = false; evt.shiftKey = false; evt.metaKey = false; this.element.dispatchEvent(evt); } }, _init : function() { this.element = $(""); if(this.value !== 0) { this.element.val( this.value ); } this.element.css({ position:'absolute', backgroundColor:this._background(), color:this._fill(), left: this._x() + this.panel.x, top: this._y() + this.panel.y, width: this._width(), height: this._height(), fontSize: this.fontSize, display:'block', border: '1px solid ' + this._stroke(), }); if(this.css) this.element.css( this.css ); var self = this; this.element.change( function(obj) { var oldValue = self.value; self.value = self.element.val(); self.sendTargetMessage(); self.onvaluechange(self.value, oldValue); } ); $(this.container).append(this.element); }, }) .init( arguments[0] ); }; Interface.TextField.prototype = Interface.Widget; /**#Interface.MultiSlider - Widget Multiple vertical sliders that share the same event handlers and colors. When a MultiSlider sends OSC, it comes in the form of an integer (representing the number of the slide moved) and a float (representing the value of the slider moved). Any onvaluechange method attached to the MultiSlider widget should have a similar signature; see the example below. ## Example Usage## `b = new Interface.Label({ bounds:[.5,.5,.5,.5], size:12 }); a = new Interface.MultiSlider({ bounds:[0,0,.5,.5], fill:'red', count:8, onvaluechange : function( sliderNumber, sliderValue) { b.setValue('number : ' + sliderNumber + ', value : ' + sliderValue) }, }); panel = new Interface.Panel(); panel.add(a,b); ` ## Constructor **param** *properties*: Object. A dictionary of property values (see below) to set for the multislider on initialization. - - - - **/ /**###Interface.MultiSlider.count : property Number. The number of sliders in the widget **/ Interface.MultiSlider = function() { Interface.extend(this, { type : 'MultiSlider', isVertical : true, serializeMe : ["isVertical", "count", "values"], values: [], _values: [], count:16, draw : function() { var x = this._x(), y = this._y(), width = this._width(), height= this._height(), sliderWidth = width / this.count; this.ctx.fillStyle = this._background(); this.ctx.fillRect( x, y, width, height ); this.ctx.fillStyle = this._fill(); this.ctx.strokeStyle = this._stroke(); for(var i = 0; i < this.count; i++) { var sliderX = i * sliderWidth + x; this.ctx.fillRect( sliderX, y + height - this._values[i] * height, sliderWidth, this._values[i] * height); this.ctx.strokeRect( sliderX, y, sliderWidth, height ); } }, setValue : function( sliderNum, value ) { this.values[ sliderNum ] = value this._values[ sliderNum ] = value this.refresh() }, resetValues : function() { for( var i = 0; i < this.count; i++ ) { this.values[ i ] = this.min + (this.max - this.min) * this._values[ i ]; if(this.target !== "OSC") { this.sendTargetMessage(); }else{ if(Interface.OSC) Interface.OSC.send( this.key, 'if', [ sliderHit, this.values[ sliderHit ] ] ); } if(this.onvaluechange) this.onvaluechange(sliderHit, this.values[ sliderHit ]); } this.refresh(); }, changeValue : function( xOffset, yOffset ) { if(this.hasFocus || !this.requiresFocus) { var width = this._width(), sliderWidth = width / this.count, sliderHit = Math.floor( xOffset / sliderWidth ) _value = 0; _value = 1 - ( yOffset / this._height() ); if(_value < 0) { _value = 0; // this.hasFocus = false; }else if(_value > 1) { _value = 1; // this.hasFocus = false; } this.values[ sliderHit ] = this.min + (this.max - this.min) * _value; this._values[ sliderHit ] = _value; if(this.target !== "OSC") { this.sendTargetMessage(); }else{ if(Interface.OSC) Interface.OSC.send( this.key, 'if', [ sliderHit, this.values[ sliderHit ] ] ); } if(this.onvaluechange) this.onvaluechange(sliderHit, this.values[ sliderHit ]); this.refresh(); //this.lastValue = this.value; //} } }, mousedown : function(e, hit) { if(hit && Interface.mouseDown) this.changeValue( e.x - this._x(), e.y - this._y() ); }, mousemove : function(e, hit) { if(hit && Interface.mouseDown) this.changeValue( e.x - this._x(), e.y - this._y() ); }, mouseup : function(e, hit) { if(hit && Interface.mouseDown) this.changeValue( e.x - this._x(), e.y - this._y() ); }, touchstart : function(e, hit) { if(hit) this.changeValue( e.x - this._x(), e.y - this._y() ); }, touchmove : function(e, hit) { if(hit) this.changeValue( e.x - this._x(), e.y - this._y() ); }, touchend : function(e, hit) { if(hit) this.changeValue( e.x - this._x(), e.y - this._y() ); }, onvaluechange : function(id, value) {}, }) .init( arguments[0] ); var x = this.x, y = this.y, width = this.width, height = this.height, bounds = [x,y,width,height], count = this.count; delete this.bounds; Object.defineProperties(this, { x : { get : function() { return x; }, set: function(_x) { x = _x; this.refresh(); } }, y : { get : function() { return y; }, set: function(_y) { y = _y; this.refresh(); } }, width : { get : function() { return width; }, set: function(_width) { width = _width; this.refresh(); } }, height : { get : function() { return height; }, set: function(_height) { height = _height; this.refresh(); } }, bounds : { get : function() { return bounds; }, set : function(_bounds) { bounds = _bounds; x = bounds[0]; y = bounds[1]; width = bounds[2]; height = bounds[3]; this.refresh(); } }, count : { get : function() { return count; }, set : function(_count) { count = _count; this.refresh(); }, } }) }; Interface.MultiSlider.prototype = Interface.Widget; /**#Interface.MultiButton - Widget Multiple buttons that share the same event handlers and colors. When a MultiButton sends OSC, it comes in the form of three integers representing the row of the button pressed, the column of the button pressed, and the button's value. Any onvaluechange method attached to the MultiButton widget should have a similar signature; see the example below. ## Example Usage## `b = new Interface.Label({ bounds:[.5,.5,.5,.5], size:12 }); a = new Interface.MultiButton({ bounds:[0,0,.5,.5], fill:'white', rows: 4, columns: 4, onvaluechange : function( row, column, value) { b.setValue('row : ' + row + ', column : ' + column + ', value : ' + value) }, }); panel = new Interface.Panel(); panel.add(a,b); ` ## Constructor **param** *properties*: Object. A dictionary of property values (see below) to set for the multibutton on initialization. - - - - **/ /**###Interface.MultiButton.rows : property Number. The number of rows of buttons in the widgets. When combined with the columns property this determines the overall number of buttons in the widget. **/ /**###Interface.MultiButton.columns : property Number. The number of columns of buttons in the widgets. When combined with the rows property this determines the overall number of buttons in the widget. **/ /**###Interface.MultiButton.mode : property String. Can be 'toggle', 'momentary' or 'contact'. In toggle mode, the button turns on when it is pressed and off when it is pressed again. In momentary mode, the button turns on when pressed and off when released. In contact mode, the button briefly flashes when pressed and sends its value. **/ Interface.MultiButton = function() { Interface.extend(this, { type : 'MultiButton', mode : 'toggle', serializeMe : ["mode", "rows", "columns", "requiresFocus"], rows: 8, values: [], _values: [], lastValues: [], mouseOver : null, columns: 8, draw : function() { var x = this._x(), y = this._y(), width = this._width(), height= this._height(), childWidth = width / this.columns, childHeight = height / this.rows; this.ctx.strokeStyle = this._stroke(); for(var i = 0; i < this.rows; i++) { for(var j = 0; j < this.columns; j++) { var _x = x + j * childWidth, _y = y + i * childHeight, btn = i * this.columns + j; if(this._values[ btn ]) { this.ctx.fillStyle = this._fill(); }else{ this.ctx.fillStyle = this._background(); } this.ctx.fillRect( _x, _y, childWidth, childHeight ); this.ctx.strokeRect( _x, _y, childWidth, childHeight ); } } }, setValue : function( row, col, value ) { var btnNum = row * this.columns + col this._values[ btnNum ] = this.values[ btnNum ] = this.lastValues[ btnNum ] = value this.draw() }, changeValue : function( xOffset, yOffset ) { if(this.hasFocus || !this.requiresFocus) { var width = this._width(), height = this._height(), buttonWidth = width / this.columns, columnHit = Math.floor( xOffset / buttonWidth ), buttonHeight = height / this.rows, rowHit = Math.floor( yOffset / buttonHeight), buttonHit = (rowHit * this.columns) + columnHit, _value = 0; if( buttonHit !== this.mouseOver ) { this._values[ buttonHit ] = !this._values[ buttonHit ]; this.values[ buttonHit ] = this._values[ buttonHit ] ? this.max : this.min; if(this.values[ buttonHit ] !== this.lastValues[ buttonHit ] || this.mode === 'contact') { if(this.target !== "OSC") { this.sendTargetMessage(); }else{ if(Interface.OSC) Interface.OSC.send( this.key, 'iif', [ rowHit, columnHit, this.values[ buttonHit ] ] ); } if(this.onvaluechange) this.onvaluechange( rowHit, columnHit, this.values[ buttonHit ]); this.draw(); this.lastValues[ buttonHit ] = this.values[ buttonHit ]; if(this.mode === 'contact') { var self = this; setTimeout( function() { self._values[ buttonHit ] = 0; self.draw(); }, 75); } } this.mouseOver = buttonHit; } } }, mousedown : function(e, hit) { if(hit && Interface.mouseDown) { this.changeValue( e.x - this._x(), e.y - this._y() ); } }, mousemove : function(e, hit) { if(hit && Interface.mouseDown) { this.changeValue( e.x - this._x(), e.y - this._y() ); } }, mouseup : function(e, hit) { if(hit && Interface.mouseDown) this.changeValue( e.x - this._x(), e.y - this._y() ); this.mouseOver = null; }, touchstart : function(e, hit) { if(hit) this.changeValue( e.x - this._x(), e.y - this._y() ); }, touchmove : function(e, hit) { if(hit) this.changeValue( e.x - this._x(), e.y - this._y() ); }, touchend : function(e, hit) { if(hit) this.changeValue( e.x - this._x(), e.y - this._y() ); }, }) .init( arguments[0] ); this.requiresFocus = false; var x = this.x, y = this.y, width = this.width, height = this.height, bounds = [x,y,width,height] rows = this.rows, columns = this.columns; delete this.bounds; Object.defineProperties(this, { x : { get : function() { return x; }, set: function(_x) { x = _x; this.refresh(); } }, y : { get : function() { return y; }, set: function(_y) { y = _y; this.refresh(); } }, width : { get : function() { return width; }, set: function(_width) { width = _width; this.refresh(); } }, height : { get : function() { return height; }, set: function(_height) { height = _height; this.refresh(); } }, bounds : { get : function() { return bounds; }, set : function(_bounds) { bounds = _bounds; x = bounds[0]; y = bounds[1]; width = bounds[2]; height = bounds[3]; this.refresh(); } }, rows : { get : function() { return rows; }, set : function(_rows) { rows = _rows; this.refresh(); }, }, columns : { get : function() { return columns; }, set : function(_columns) { columns = _columns; this.refresh(); }, }, }); }; Interface.MultiButton.prototype = Interface.Widget; /**#Interface.Accelerometer - Widget Access to the Accelerometer. Unlike the Orientation widget, this is only found on mobile devices. ## Example Usage## `var a = new Interface.Panel(); var accelerometer = new Interface.Accelerometer({ onvaluechange : function(_x,_y,_z) { x.setValue(_x); y.setValue(_y); z.setValue(_z); } }).start(); var x = new Interface.Slider({ label: 'x', bounds:[.05,.05,.2,.9] }); var y = new Interface.Slider({ label: 'y', bounds:[.25,.05,.2,.9] }); var z = new Interface.Slider({ label: 'z', bounds:[.45,.05,.2,.9] }); a.background = 'black'; a.add(x,y,z); ` ## Constructor **param** *properties*: Object. A dictionary of property values (see below) to set for the accelerometer on initialization. - - - - **/ /**###Interface.Accelerometer.x : property Number. A read-only property that gives the current accleration on the x-axis **/ /**###Interface.Accelerometer.y : property Number. A read-only property that gives the current accleration on the y-axis **/ /**###Interface.Accelerometer.z : property Number. A read-only property that gives the current accleration on the z-axis **/ /**###Interface.Accelerometer.start : method Starts emitting values from the Accelerometer measurements **/ /**###Interface.Accelerometer.stop : method Stop emitting values from the Accelerometer measurements **/ /**###Interface.Accelerometer.onvaluechange : method The event handler fired whenever an accelerometer update is received param **x** Number. The x-acceleration of the sensor param **y** Number. The y-acceleration of the sensor param **x** Number. The z-acceleration of the sensor **/ Interface.Accelerometer = function() { var self = this, metersPerSecondSquared = 9.80665; Interface.extend(this, { type:"Accelerometer", serializeMe : ["delay"], delay : 100, // measured in ms min: 0, max: 1, values : [0,0,0], update : function(event) { var acceleration = event.acceleration; self.x = self.values[0] = self.min + ((((0 - self.hardwareMin) + acceleration.x) / self.hardwareRange ) * self.max); self.y = self.values[1] = self.min + ((((0 - self.hardwareMin) + acceleration.y) / self.hardwareRange ) * self.max); self.z = self.values[2] = self.min + ((((0 - self.hardwareMin) + acceleration.z) / self.hardwareRange ) * self.max); if(typeof self.onvaluechange !== 'undefined') { self.onvaluechange(self.x, self.y, self.z); } self.sendTargetMessage(); }, start : function() { window.addEventListener('devicemotion', this.update, true); return this; }, stop : function() { window.removeEventListener('devicemotion', this.update); return this; }, }) .init( arguments[0] ); if(!Interface.isAndroid) { this.hardwareMin = -2.307 * metersPerSecondSquared; // as found here: http://www.iphonedevsdk.com/forum/iphone-sdk-development/4822-maximum-accelerometer-reading.html this.hardwareMax = 2.307 * metersPerSecondSquared; // -1 to 1 works much better for devices without gyros to measure tilt, -2 to 2 much better to measure force }else{ this.hardwareMin = metersPerSecondSquared; this.hardwareMax = metersPerSecondSquared; } this.hardwareRange = this.hardwareMax - this.hardwareMin; }; Interface.Accelerometer.prototype = Interface.Widget; /**#Interface.Orientation - Widget Access to the device's Orientation. This is only found on mobile devices, with the exception of Google Chrome, which provides pitch and roll. ## Example Usage## `var a = new Interface.Panel() var orientation = new Interface.Orientation({ onvaluechange : function(_pitch, _roll, _yaw, _heading) { pitch.setValue(_pitch); roll.setValue(_roll); yaw.setValue(_yaw); } }); var pitch = new Interface.Slider({ label: 'pitch', bounds:[.05,.05,.2,.9] }); var roll = new Interface.Slider({ label: 'roll', bounds:[.25,.05,.2,.9] }); var yaw = new Interface.Slider({ label: 'yaw', bounds:[.45,.05,.2,.9] }); a.add(pitch, roll, yaw); ` ## Constructor **param** *properties*: Object. A dictionary of property values (see below) to set for the orientation on initialization. - - - - **/ /**###Interface.Orientation.pitch : property Number. A read-only property that gives the current orientation on the x-axis **/ /**###Interface.Orientation.roll : property Number. A read-only property that gives the current orientation on the y-axis **/ /**###Interface.Orientation.yaw : property Number. A read-only property that gives the current orientation on the z-axis **/ /**###Interface.Orientation.start : method Starts emitting values from the Orientation measurements **/ /**###Interface.Orientation.stop : method Stop emitting values from the Orientation measurements **/ /**###Interface.Orientation.onvaluechange : method The event handler fired whenever an orientation update is received param **pitch** Number. The pitch of the sensor param **roll** Number. The roll of the sensor param **yaw** Number. The yaw of the sensor param **heading** Number. The heading of the sensor, this corresponds to the compass direction detected. **/ Interface.Orientation = function() { var _self = this; Interface.extend(this, { type:"Orientation", serializeMe : ["delay"], delay : 100, // measured in ms values : [0,0,0], update : function(orientation) { _self.roll = _self.values[0] = _self.min + ((90 + orientation.gamma) / 180 ) * _self.max ; _self.pitch = _self.values[1] = _self.min + ((180 + orientation.beta) / 360 ) * _self.max ; _self.yaw = _self.values[2] = _self.min + (orientation.alpha / 360 ) * _self.max ; if( !isNaN(orientation.webkitCompassHeading) ) { _self.heading = _self.min + ((orientation.webkitCompassHeading / 360 ) * _self.max ); } _self.sendTargetMessage(); if(typeof _self.onvaluechange !== 'undefined') { _self.onvaluechange(_self.pitch, _self.roll, _self.yaw, _self.heading); } }, start : function() { window.addEventListener('deviceorientation', function (event) { _self.update(event); }, true); return this; }, stop : function() { window.removeEventListener('deviceorientation'); }, }) .init( arguments[0] ); }; Interface.Orientation.prototype = Interface.Widget; Interface.Range = function() { Interface.extend(this, { type:"Range", serializeMe : ["handleSize"], handleSize: 20, values:[0,1], _values:[0,1], draw : function() { var x = this._x(), y = this._y(), width = this._width(), height= this._height(); this.ctx.fillStyle = this._background(); this.ctx.clearRect(x, y, width, height); var rightHandlePos = x + (this._values[1] * width) - this.handleSize; var leftHandlePos = x + this._values[0] * width; this.ctx.fillStyle = this._background(); this.ctx.fillRect(x, y, width, height); this.ctx.fillStyle = this._fill(); this.ctx.fillRect(leftHandlePos, y, rightHandlePos - leftHandlePos, height); this.ctx.fillStyle = this._stroke(); this.ctx.fillRect(leftHandlePos, y, this.handleSize, height); //this.ctx.fillStyle = "rgba(0,255,0,.25)"; this.ctx.fillRect(rightHandlePos, y, this.handleSize, height); this.ctx.strokeStyle = this._stroke(); this.ctx.strokeRect(x, y, width, height); }, changeValue : function( xOffset, yOffset ) { if(this.hasFocus || !this.requiresFocus) { var value = this.isVertical ? 1 - (yOffset / this._height()) : xOffset / this._width(); if(value < 0) { value = 0; }else if(value > 1) { value = 1; } var range = this.max - this.min if(Math.abs( value - this._values[0]) < Math.abs( value - this._values[1])) { this._values[0] = value; this.values[0] = this.min + range * value; }else{ this._values[1] = value; this.values[1] = this.min + range * value; } this.refresh(); if(this.values[0] !== this.lastLeftValue || this.values[1] !== this.lastRightValue) { if(this.onvaluechange) this.onvaluechange(this.values[0], this.values[1]); this.refresh(); this.lastLeftValue = this.values[0]; this.lastRightValue = this.values[1]; this.sendTargetMessage(); } } }, mousedown : function(e, hit) { if(hit && Interface.mouseDown) this.changeValue( e.x - this._x(), e.y - this._y() ); }, mousemove : function(e, hit) { if(hit && Interface.mouseDown) this.changeValue( e.x - this._x(), e.y - this._y() ); }, mouseup : function(e, hit) { if(hit && Interface.mouseDown) this.changeValue( e.x - this._x(), e.y - this._y() ); }, touchstart : function(e, hit) { if(hit) this.changeValue( e.x - this._x(), e.y - this._y() ); }, touchmove : function(e, hit) { if(hit) this.changeValue( e.x - this._x(), e.y - this._y() ); }, touchend : function(e, hit) { if(hit) this.changeValue( e.x - this._x(), e.y - this._y() ); }, }) .init( arguments[0] ); } Interface.Range.prototype = Interface.Widget; Interface.Paint = function() { Interface.extend( this, { lines: [], startTime: 0, isAnimating: false, animationPoint: 0, timer: null, shouldLoop: true, prevTimestamp:null, values:[0,0], draw : function() { var x = this._x(), y = this._y(), width = this._width(), height= this._height(); this.ctx.fillStyle = this._background(); //this.ctx.fillRect( this.x, this.y, this.width, this.height ); this.ctx.strokeStyle = this._stroke(); //this.ctx.strokeRect( this.x, this.y, this.width, this.height ); this.ctx.save(); this.ctx.beginPath(); this.ctx.moveTo(x, y); this.ctx.lineTo(x + width, y); this.ctx.lineTo(x + width, y + height); this.ctx.lineTo(x, y + height); this.ctx.lineTo(x, y); this.ctx.fill(); this.ctx.stroke(); this.ctx.clip(); this.ctx.fillStyle = this._fill(); if( this.lines.length >= 1 ) { this.ctx.lineWidth = 8 for( var i = 0; i < this.lines.length; i++ ) { var points = this.lines[ i ] if( points.length >= 2 ) { this.ctx.moveTo( points[0].x * width, points[0].y * height ) this.ctx.beginPath() for ( var j = 1; j < points.length - 2; j++ ) { var xc = ( points[ j ].x + points[ j + 1 ].x ) / 2 var yc = ( points[ j ].y + points[ j + 1 ].y ) / 2 this.ctx.quadraticCurveTo( points[ j ].x * width, points[ j ].y * height, xc * width, yc * height ) } this.ctx.stroke() //this.ctx.closePath(); } } } this.ctx.restore(); }, /* touchEvent : function(touch) { var isHit = this.hitTest(touch); var touchMouseName = convertTouchEvent(touch.type); if(isHit) { if(touch.type === 'touchstart') { this.hasFocus = true; this.touchCount++; this.trackTouch(touch.x - this._x(), touch.y - this._y(), touch); }else{ if(this[touch.type]) this[touch.type](touch, isHit, touch.childID); // normal event } if(this['on'+touch.type]) this['on'+touch.type](touch, isHit, touch.childId); // user defined event if(this['on'+touchMouseName]) this['on'+touchMouseName](touch, isHit); // user defined event }else if(touch.type === 'touchend'){ this.touchCount--; if(this.touchCount === 0) { this.hasFocus = false; }else if(this.touchCount < 0 ) { this.touchCount = 0; } this.touchend(touch) if(this['on'+touch.type]) this['on'+touch.type](touch, isHit, touch.childId); // user defined event if(this['on'+touchMouseName]) this['on'+touchMouseName](touch, isHit); // user defined event } }, trackMouse : function(xPos, yPos, id) {}, */ animate : function(co) { var me = this; if( this.isAnimating === false ) return //console.log( this.lineNo, this._lines.length, this.speedMod ) var line = this.lines[ 0 ] // if( typeof this.line === 'undefined' ) { // this.context.fillStyle = '#fff' // this.context.fillRect( 0, 0, this.canvas[0].width, this.canvas[0].height ) // this.lineNo = this.pointNo = this.lines.length = 0 // if( this.shouldLoop ) setTimeout( function() { me.drawBackground(); me.animate() }, this.endTime ) // return // } var point = line[ this.animationPoint ], nextPoint = line[ this.animationPoint + 1 ] //console.log( point, nextPoint ) if( this.animationPoint >= line.length - 1 ) { if( this.shouldLoop ) { this.animationPoint = 0 this.draw() //var time = this.shouldLoop ? this.endTime : (this.point.timestamp - this.prevTimestamp) this.timeout = setTimeout( function() { me.animate() }, 5 ) return }else{ this.ctx.fillStyle = this._background() this.ctx.fillRect( 0, 0, this._width(), this._height() ) if( this.speedMod !== 0 ) return } } this.ctx.save(); this.ctx.strokeStyle = '#f00' this.ctx.lineWidth = 8 this.ctx.beginPath() this.ctx.moveTo( point.x * this._width(), point.y * this._height() ) // var xc = ( point.x + nextPoint.x ) / 2 // var yc = ( point.y + nextPoint.y ) / 2 // this.ctx.quadraticCurveTo( nextPoint.x * this._width(), nextPoint.y * this._height(), xc * this._width(), yc * this._height() ) this.ctx.lineTo( nextPoint.x * this._width(), nextPoint.y * this._height() ) this.ctx.stroke() this.ctx.restore() this.timeout = setTimeout( function() { me.animate() }, point.timestamp - this.prevTimestamp ) this.prevTimestamp = point.timestamp this.animationPoint++ this.values = [ point.x, point.y ] this.sendTargetMessage() }, startAnimation: function() { var self = this this.animate() }, stopAnimation: function() { if( this.timer ) { clearInterval( this.timer ) } }, mousedown : function(e) { if(this.hitTest(e)) { this.lines = [] this.animationPoint = 0 if( this.lines.length === 0 ) { this.startTime = Date.now() }else{ // if( this.lines[ this.lines.length - 1 ].length < 2 ) { // this.lines.pop() // } } this.lines.push( [] ) this.isDrawing = true; this.isAnimating = false; } }, mousemove : function(e) { if(this.hitTest(e) && this.activeTouch !== null) { //ctx.fillStyle = '#000' //if( e.pageX > canvas.width ) isDrawing = false if( this.isDrawing ) { var points = this.lines[ this.lines.length - 1 ] if( points ) { points.push({ x:e.x / this._width(), y:e.y / this._height(), timestamp: Date.now() - this.startTime }) this.draw() } } } }, mouseup : function(e) { this.isDrawing = false if( this.lines.length > 0 ) { this.isAnimating = true; this.animate() } }, touchstart : function(touch) { if(this.hitTest(touch)) { this.lines = [] this.animationPoint = 0 if( this.lines.length === 0 ) { this.startTime = Date.now() } this.lines.push( [] ) this.isDrawing = true; this.isAnimating = false; } this.activeTouch = touch }, touchmove : function(touch) { if(this.hitTest(touch) && this.activeTouch !== null) { if( this.isDrawing ) { var points = this.lines[ this.lines.length - 1 ] if( points ) { points.push({ x:touch.x / this._width(), y:touch.y / this._height(), timestamp: Date.now() - this.startTime }) this.draw() } } } }, touchend : function(touch) { this.isDrawing = false if( this.lines.length > 0 ) { this.isAnimating = true; this.animate() } }, }) .init( arguments[0] ); } Interface.Paint.prototype = Interface.Widget; Interface.Patchbay = function() { Interface.extend(this, { type:"Patchbay", points: [], minWidth:80, cableWidth:5, start:null, over:null, connections:[], rowLength:null, selectedConnection: null, patchOutlineWidth:3, draw : function() { var x = this._x(), y = this._y(), width = this._width(), height = this._height(), length = this.points.length this.ctx.fillStyle = this._background(); this.ctx.strokeStyle = this._stroke(); this.ctx.clearRect(x, y, width, height); this.layout() this.drawSegments() this.drawPatchPoints() this.drawConnections() //this.drawLabels() }, layout: function() { var x = this._x(), y = this._y(), width = this._width(), height = this._height() this.rows = 1 this.patchWidth = width / this.points.length if( this.patchWidth < this.minWidth ) { this.patchWidth = this.minWidth } this.rows = Math.ceil( (this.patchWidth * this.points.length) / width ) this.patchHeight = height / this.rows this.columns = Math.floor( width / this.patchWidth ) }, drawSegments : function() { var x = this._x(), y = this._y(), width = this._width(), height = this._height(), length = this.points.length this.ctx.fillStyle = this._fill(); var totalWidth = 0, row = 1 //console.log("SEGMENT, START:", this.start, 'OVER:', this.over ) for( var i = 0; i < this.points.length; i++ ) { if( this.start === i ) { this.ctx.fillStyle = "#777" this.ctx.fillRect(x + totalWidth, y + (this.patchHeight * (row-1)), this.patchWidth, this.patchHeight ); }else if( this.over === i ) { this.ctx.fillStyle = "#744" this.ctx.fillRect(x + totalWidth, y + (this.patchHeight * (row-1)), this.patchWidth, this.patchHeight ); } this.ctx.fillStyle = this._stroke() this.ctx.textBaseline = 'middle' this.ctx.textAlign = 'center' this.ctx.font = this._font() this.ctx.font = 'normal 12px Helvetica' if( typeof this.points[i].name !== 'undefined' ) { this.ctx.fillText( this.points[ i ].name , totalWidth + this.patchWidth / 2, y + ((row-1) * this.patchHeight + .1 * this.patchHeight) ) } if( typeof this.points[i].name2 !== 'undefined' ) { this.ctx.fillText( this.points[ i ].name2 , totalWidth + this.patchWidth / 2, y + ((row-1) * this.patchHeight + .9 * this.patchHeight) ) } totalWidth += this.patchWidth this.points[ i ].row = row if( totalWidth + this.patchWidth > width ) { totalWidth = 0 row++ } } }, drawPatchPoints : function() { var x = this._x(), y = this._y(), width = this._width(), height = this._height(), length = this.points.length this.ctx.fillStyle = this._background(); var totalWidth = 0, row = 1 for( var i = 0; i < this.points.length; i++ ) { //this.ctx.fillRect(totalWidth, y, patchWidth, patchHeight); this.ctx.beginPath() this.ctx.arc( totalWidth + this.patchWidth / 2, y + this.patchHeight / 2 + (this.patchHeight * (row-1)), this.patchWidth/4, 0, Math.PI*2, true); this.ctx.closePath() this.ctx.fill() this.ctx.lineWidth = this.patchOutlineWidth this.ctx.stroke() this.points[i].row = row this.ctx.lineWidth = 1 this.ctx.strokeRect(totalWidth, y + (this.patchHeight * (row-1)), this.patchWidth, this.patchHeight ); totalWidth += this.patchWidth if( totalWidth + this.patchWidth > width ) { totalWidth = 0 row++ } } //console.log("TOTAL ROWS = ", row ) }, drawConnections : function() { var x = this._x(), y = this._y(), width = this._width(), height = this._height() this.ctx.lineWidth = this.cableWidth for( var i = 0; i < this.connections.length; i++ ) { var connection = this.connections[ i ], origin = this.connections[ i ][ 0 ], destination = this.connections[ i ][ 1 ], startX = x + this.patchWidth * (origin % this.columns) + this.patchWidth / 2, startY = y + (this.patchHeight / 2) + (this.patchHeight * Math.floor(origin / this.columns) ), endX = x + this.patchWidth * (destination % this.columns) + this.patchWidth / 2, endY = y + (this.patchHeight / 2) + (this.patchHeight * Math.floor(destination / this.columns) ), ctrl1X = startX, ctrl1Y = startY + this.patchHeight * .5, ctrl2X = endX, ctrl2Y = endY + this.patchHeight * .5 //console.log( "ORIGIN", this.points[origin].row, "DESTINATION", this.points[destination].row ) if( connection.selected ) { this.ctx.strokeStyle = '#0f0' }else{ var grd = this.ctx.createLinearGradient(startX, startY, endX, endY); grd.addColorStop( 0.000, 'rgba(64, 64, 64, 1.000)' ) grd.addColorStop( 1.000, 'rgba(204, 204, 204, 1.000)' ) this.ctx.strokeStyle = grd } this.ctx.beginPath(); this.ctx.moveTo( startX, startY ) this.ctx.bezierCurveTo( ctrl1X, ctrl1Y, ctrl2X, ctrl2Y, endX, endY ); this.ctx.stroke() connection.edge = [startX, startY, ctrl1X, ctrl1Y, ctrl2X, ctrl2Y, endX, endY] } }, _init : function() { var x = this._x(), y = this._y(), width = this._width(), height= this._height() this.patchWidth = width / this.points.length this.patchHeight = height this.rows = 1 }, createConnection : function( connection ) { var start = this.points[ connection[0] ], end = this.points[ connection[1] ] if( end.output !== false ) { this.connections.push( connection ) if( this.onconnection ) { this.onconnection( start, end ) } } }, changeValue : function( xOffset, yOffset ) { }, hitTestEdges: function(e) { var hit = false, x = e.x - this._x(), y = e.y - this._y() for( var i = 0; i < this.connections.length; i++ ) { var edge = this.connections[ i ].edge this.ctx.beginPath() this.ctx.moveTo( edge[0], edge[1] ) this.ctx.bezierCurveTo( edge[2], edge[3], edge[4], edge[5], edge[6], edge[7] ); if( this.ctx.isPointInStroke( x,y ) ) { this.connections.forEach( function( elem, index, array ){ elem.selected = false }) this.connections[ i ].selected = true this.selectedConnection = this.connections[ i ] hit = true break; } } return hit }, mousedown : function(e, hit) { if( hit && Interface.mouseDown ) { if( !this.hitTestEdges( e ) ) { //this.start = Math.floor( ( e.x - this._x() / this._width() / this.rows ) / ( this._width() / this.points.length / this.rows ) ) var _x = Math.floor( ( e.x - this._x() / this._width() ) / ( this._width() / this.columns ) ), _y = Math.floor( ( e.y - this._y() / this._height()) / ( this._height() / this.rows ) ) this.start = _y * this.columns + _x if( this.selectedConnection !== null ) { this.selectedConnection.selected = false this.selectedConnection = null } } this.draw() } }, mousemove : function(e, hit) { if( hit && Interface.mouseDown ) { var _x = Math.floor( ( e.x - this._x() / this._width() ) / ( this._width() / this.columns) ), _y = Math.floor( ( e.y - this._y() / this._height()) / ( this._height() / this.rows ) ) var prevOver = this.over this.over = _y * this.columns + _x if( this.over !== prevOver ) { this.draw() } } }, mouseup : function(e, hit) { if( hit ) { var _x = Math.floor( ( e.x - this._x() / this._width() ) / ( this._width() / this.columns ) ), _y = Math.floor( ( e.y - this._y() / this._height()) / ( this._height() / this.rows ) ), over = _y * this.columns + _x // var over = Math.floor( ( e.x - this._x() / this._width() / this.rows ) / ( this._width() / this.points.length / this.rows ) ) if( this.start !== over && this.start !== null ) { var connection = [ this.start, over ], isFound = false for( var i = 0; i < this.connections.length; i++ ) { if( this.connections[i][0] === connection[0] && this.connections[i][1] === connection[1] ) { isFound = true } } if( !isFound ) this.createConnection( connection ) } } this.over = null this.start = null this.draw() }, onkeydown: function(e) { var key = Interface.keyCodeToChar[ e.keyCode ] if( key === 'Delete' || key === 'Backspace' ) { if( this.selectedConnection !== null ) { this.deleteConnection( this.selectedConnection ) e.preventDefault() } } }, deleteConnection: function( connection ) { this.connections.splice( this.connections.indexOf( connection ), 1 ) if( this.ondisconnection ) { this.ondisconnection( this.points[ connection[0] ], this.points[ connection[1] ] ) } this.draw() }, touchstart : function(e, hit) { if(hit) this.changeValue( e.x - this._x(), e.y - this._y() ); }, touchmove : function(e, hit) { if(hit) this.changeValue( e.x - this._x(), e.y - this._y() ); }, touchend : function(e, hit) { if(hit) this.changeValue( e.x - this._x(), e.y - this._y() ); }, }) .init( arguments[0] ); } Interface.Patchbay.prototype = Interface.Widget; Interface.defineChildProperties = function(widget, properties) { for(var j = 0; j < properties.length; j++) { (function() { var key = properties[j]; var val = widget[key] Object.defineProperty(widget, key, { get: function() { return val; }, set: function(_val) { val = _val; for(var i = 0; i < widget.children.length; i++) { widget.children[i][key] = val; } } }); })(); } }; // pub/sub for jquery && zepto, see https://github.com/martinjuhasz/pubsub-zepto/blob/master/pubsub.js (function ($) { var cache = {}; $.publish = function(/* String */topic, /* Array? */args){ if(typeof cache[topic] === 'object') { cache[topic].forEach(function(property){ property.apply($, args || []); }); } }; $.subscribe = function(/* String */topic, /* Function */callback){ if(!cache[topic]){ cache[topic] = []; } cache[topic].push(callback); return [topic, callback]; // Array }; $.unsubscribe = function(/* Array */handle){ var t = handle[0]; cache[t] && $.each(cache[t], function(idx){ if(this == handle[1]){ cache[t].splice(idx, 1); } }); }; })(window.jQuery);