/*globals React, App */ (function () { 'use strict'; /** * Displays a key on keyboard * * @prop {note: int} MIDI note identifier * @prop {canPlay: function} Whether the note can be manually played */ App.components.create('Key', { displayName: 'Key', mixins: [React.addons.PureRenderMixin], propTypes: { note: React.PropTypes.number.isRequired }, getInitialState: function () { return { channels: [] }; }, getDefaultProps: function () { return { note: 0, canPlay: function () {} }; }, /** * Start playing note on given channel with given velocity * * @param {channel: number} Channel ID * @param {velocity: number} Note velocity */ on: function (channel, velocity) { if (this.state.channels.indexOf(channel) > -1) { return; } App.MIDI.output.noteOn( (channel === -1) ? 0 : channel, this.props.note, velocity, 0 ); this.setState({ channels: this.state.channels.concat([ channel ]) }); }, /** * Stop playing note on previous channel * * @param {channel: number} Channel ID * @param {channel: Array} Channels IDs (default: all current) */ off: function (channels) { var i, length, channel, current, index; current = this.state.channels.slice(0); if (channels === undefined) { channels = this.state.channels; } else if (!Array.isArray(channels)) { channels = [channels]; } length = channels.length; for (i = 0; i < length; i += 1) { channel = channels[i]; App.MIDI.output.noteOff( (channel === -1) ? 0 : channel, this.props.note, 0 ); index = current.indexOf(channel); // remove channels from current playing if (index > -1) { current.splice(index, 1); } } this.setState({ channels: current }); }, /** * Start playing note on mouse over */ mouseOver: function () { if (!this.props.canPlay()) { return; } this.mouseDown(); }, /** * Play on mouse down */ mouseDown: function (e) { if (e && e.button !== 0) { return; } this.on(-1, 127); }, /** * Render key */ render: function () { var black = App.components.Note.Board.black, offset, pianoNote = this.props.note, style, channels, channel, isBlack = (black.indexOf(this.props.note) > -1); offset = pianoNote - black.filter(function (el) { return el < pianoNote; }).length - App.MIDI.keyOffset; // if it is a black key, we position it absolutely; // otherwise we just play on its width to avoid rounding // errors causing display glitches if (isBlack) { style = { left: 'calc(100% / 52 * ' + offset + ')', zIndex: App.MIDI.keyOffset + 88 }; } else { style = { width: 'calc(100% / 52 * ' + (offset + 1) + ')', zIndex: App.MIDI.keyOffset + 88 - pianoNote }; } // get channel ID channels = this.state.channels; if (channels.length > 0) { channel = channels[channels.length - 1]; } else { channel = false; } return React.DOM.span({ className: 'key', onMouseDown: this.mouseDown, onMouseOver: this.mouseOver, onMouseUp: this.off.bind(this, -1), onMouseOut: this.off.bind(this, -1), style: style, 'data-channel': channel, 'data-black': isBlack }); } }); }());