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();
|
|
}
|
|
});
|
|
}()); |