218 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
		
		
			
		
	
	
			218 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
|  | /*globals React, Kinetic, App */ | ||
|  | 
 | ||
|  | (function () { | ||
|  |     'use strict'; | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Display a set of notes | ||
|  |      * | ||
|  |      * @prop {notes: array} List of notes | ||
|  |      * @prop {time: int}    Current playing time | ||
|  |      */ | ||
|  |     App.components.create('Note', { | ||
|  |         displayName: 'Score', | ||
|  |          | ||
|  |         statics: { | ||
|  |             channels: [ | ||
|  |                 'hsl(225, 49%, 44%)', | ||
|  |                 'hsl(22.5, 84%, 56%)', | ||
|  |                 'hsl(45, 53%, 39%)', | ||
|  |                 'hsl(67.5, 84%, 56%)', | ||
|  |                 'hsl(90, 84%, 56%)', | ||
|  |                 'hsl(112.5, 84%, 56%)', | ||
|  |                 'hsl(135, 84%, 56%)', | ||
|  |                 'hsl(157.5, 84%, 56%)', | ||
|  |                 'hsl(180, 84%, 56%)', | ||
|  |                 'hsl(202.5, 84%, 56%)', | ||
|  |                 'hsl(247.5, 84%, 56%)', | ||
|  |                 'hsl(270, 84%, 56%)', | ||
|  |                 'hsl(292.5, 84%, 56%)', | ||
|  |                 'hsl(315, 84%, 56%)', | ||
|  |                 'hsl(337.5, 84%, 56%)', | ||
|  |                 'hsl(0, 84%, 56%)' | ||
|  |             ] | ||
|  |         }, | ||
|  |          | ||
|  |         propTypes: { | ||
|  |             notes: React.PropTypes.array.isRequired, | ||
|  |             time: React.PropTypes.number | ||
|  |         }, | ||
|  |          | ||
|  |         getDefaultProps: function () { | ||
|  |             return { | ||
|  |                 notes: [], | ||
|  |                 time: 0 | ||
|  |             }; | ||
|  |         }, | ||
|  |          | ||
|  |         getInitialState: function () { | ||
|  |             return { | ||
|  |                 notes: [], | ||
|  |                 time: 0, | ||
|  |                  | ||
|  |                 width: 0, | ||
|  |                 height: 0 | ||
|  |             }; | ||
|  |         }, | ||
|  |          | ||
|  |         /** | ||
|  |          * When component is mounted, create Kinetic stage and | ||
|  |          * attach resizing event | ||
|  |          */ | ||
|  |         componentDidMount: function () { | ||
|  |             var notes; | ||
|  |              | ||
|  |             window.addEventListener('resize', this.resize); | ||
|  |              | ||
|  |             notes = this.props.notes; | ||
|  |             this.sort(notes); | ||
|  |              | ||
|  |             this.setState({ | ||
|  |                 notes: notes, | ||
|  |                 time: this.props.time | ||
|  |             }, this.resize); | ||
|  |         }, | ||
|  |          | ||
|  |         componentWillUnmount: function () { | ||
|  |             window.removeEventListener('resize', this.resize); | ||
|  |         }, | ||
|  |          | ||
|  |         /** | ||
|  |          * Redraw canvas on each props update | ||
|  |          */ | ||
|  |         componentWillReceiveProps: function (nextProps) { | ||
|  |             if (nextProps.notes !== this.props.notes) { | ||
|  |                 this.sort(); | ||
|  |             } | ||
|  |              | ||
|  |             this.setState({ | ||
|  |                 notes: nextProps.notes, | ||
|  |                 time: nextProps.time | ||
|  |             }, this.draw); | ||
|  |         }, | ||
|  |          | ||
|  |         /** | ||
|  |          * Never rebuild canvas element | ||
|  |          */ | ||
|  |         shouldComponentUpdate: function (props) { | ||
|  |             return false; | ||
|  |         }, | ||
|  |          | ||
|  |         /** | ||
|  |          * Set canvas size | ||
|  |          * | ||
|  |          * @param {callback: func}  Called when size is updated | ||
|  |          */ | ||
|  |         resize: function (callback) { | ||
|  |             var canvas = this.getDOMNode(), | ||
|  |                 parent = canvas.parentNode, | ||
|  |                 width = parent.clientWidth, | ||
|  |                 height = parent.clientHeight, | ||
|  |                 ctx = canvas.getContext('2d'); | ||
|  |              | ||
|  |             canvas.width = width; | ||
|  |             canvas.height = height; | ||
|  |              | ||
|  |             this.setState({ | ||
|  |                 width: width, | ||
|  |                 height: height | ||
|  |             }, this.draw); | ||
|  |         }, | ||
|  |          | ||
|  |         /** | ||
|  |          * Get note offset by note ID | ||
|  |          * | ||
|  |          * @param {id: int} Note ID | ||
|  |          */ | ||
|  |         getOffset: function (id) { | ||
|  |             return id - App.MIDI.keyOffset - | ||
|  |                 App.components.Note.Board.black.filter(function (el) { | ||
|  |                     return el < id; | ||
|  |                 }).length; | ||
|  |         }, | ||
|  |          | ||
|  |         /** | ||
|  |          * Get whether a note is black or not | ||
|  |          * | ||
|  |          * @param {id: int} Note ID | ||
|  |          */ | ||
|  |         isBlack: function (id) { | ||
|  |             return (App.components.Note.Board.black.indexOf(id) > -1); | ||
|  |         }, | ||
|  |          | ||
|  |         /** | ||
|  |          * Sort notes by length | ||
|  |          * | ||
|  |          * @param {notes: array}    Notes list | ||
|  |          */ | ||
|  |         sort: function (notes) { | ||
|  |             var length = notes.length, i; | ||
|  |              | ||
|  |             for (i = 0; i < length; i += 1) { | ||
|  |                 notes[i].id = i; | ||
|  |             } | ||
|  | 
 | ||
|  |             notes.sort(function (a, b) { | ||
|  |                 if (a.length === b.length) { | ||
|  |                     return b.start - a.start; | ||
|  |                 } | ||
|  | 
 | ||
|  |                 return b.length - a.length; | ||
|  |             }); | ||
|  |         }, | ||
|  |          | ||
|  |         /** | ||
|  |          * Draw notes | ||
|  |          */ | ||
|  |         draw: function () { | ||
|  |             var notes = this.state.notes, time = this.state.time, | ||
|  |                 width = this.state.width, height = this.state.height, | ||
|  |                 xUnit = width / 52, yUnit = xUnit * 5, | ||
|  |                 maxTime = Math.ceil(height / yUnit), | ||
|  |                 note, length, i, ctx, offset, x, y, noteWidth, noteHeight, | ||
|  |                 channels = App.components.Note.Score.channels, count = 0; | ||
|  |              | ||
|  |             ctx = this.getDOMNode().getContext('2d'); | ||
|  |             ctx.clearRect(0, 0, width, height); | ||
|  |             ctx.strokeStyle = '#262626'; | ||
|  |             ctx.lineWidth = 2; | ||
|  |              | ||
|  |             length = notes.length; | ||
|  |              | ||
|  |             for (i = 0; i < length; i += 1) { | ||
|  |                 note = notes[i]; | ||
|  |                  | ||
|  |                 if (note.start + note.length > time && | ||
|  |                         note.start < time + maxTime) { | ||
|  |                     offset = this.getOffset(note.note); | ||
|  |                     count += 1; | ||
|  |                      | ||
|  |                     x = Math.floor(xUnit * offset); | ||
|  |                     y = Math.floor(height - yUnit * (note.start + note.length - time)); | ||
|  |                     noteWidth = Math.floor(xUnit); | ||
|  |                     noteHeight = Math.floor(yUnit * note.length); | ||
|  |                      | ||
|  |                     if (note.channel === 9 && noteHeight < 10) { | ||
|  |                         noteHeight = 10; | ||
|  |                     } | ||
|  |                      | ||
|  |                     if (this.isBlack(note.note)) { | ||
|  |                         x -= Math.floor(xUnit / 4); | ||
|  |                         noteWidth = Math.floor(xUnit / 2); | ||
|  |                     } | ||
|  |                      | ||
|  |                     ctx.fillStyle = channels[note.channel]; | ||
|  |                     ctx.strokeRect(x, y, noteWidth, noteHeight); | ||
|  |                     ctx.fillRect(x, y, noteWidth, noteHeight); | ||
|  |                 } | ||
|  |             } | ||
|  |         }, | ||
|  | 
 | ||
|  |         /** | ||
|  |          * Render score canvas | ||
|  |          */ | ||
|  |         render: function () { | ||
|  |             return React.DOM.canvas(); | ||
|  |         } | ||
|  |     }); | ||
|  | }()); |