💡 Add mute and hide controls

Enable to set whether an instrument should be shown or played in
instruments window.
This commit is contained in:
matteo78 2014-11-09 22:59:46 +01:00
parent 49b98d447b
commit bcbb2a7a69
14 changed files with 202 additions and 97 deletions

View File

@ -44,6 +44,22 @@ input[type="url"]:focus, input[type="week"]:focus {
padding: 0.75em 2em;
}
/* icon button */
.bt.icon {
-webkit-appearance: none;
width: 32px;
height: 32px;
border: 0;
padding: 0;
cursor: pointer;
color: white;
background: center center no-repeat none;
text-indent: -10000em;
}
/* primary button */
.bt.primary {
border: 1px solid #e0e0e0;

View File

@ -258,25 +258,6 @@ a {
text-align: center;
}
.main .control button {
-webkit-appearance: none;
width: 32px;
height: 32px;
border: 0;
cursor: pointer;
color: white;
background: none;
}
.main .control .switch-play-state, .main .control .open-channels-window,
.main .control .close {
background: center center no-repeat;
text-indent: -10000em;
}
.main .control > * {
display: block;
margin: 0 auto 1em auto;
@ -424,6 +405,28 @@ a {
background-image: url('../images/icons/instruments/organ.png');
}
/** actions **/
.vex .channels li button.sound, .vex .channels li button.notes {
width: 16px;
height: 16px;
}
.vex .channels li button.sound {
background-image: url('../images/icons/instruments/sound-on.png');
}
.vex .channels li button.sound.off {
background-image: url('../images/icons/instruments/sound-off.png');
}
.vex .channels li button.notes {
background-image: url('../images/icons/instruments/notes-on.png');
}
.vex .channels li button.notes.off {
background-image: url('../images/icons/instruments/notes-off.png');
}
/**
* UI Components
*/

Binary file not shown.

After

Width:  |  Height:  |  Size: 274 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 227 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 294 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 354 B

View File

@ -7,9 +7,10 @@
/**
* Modal for managing channels
*
* @prop {channels: Array} List of channels
* @prop {open: func} Called to open the window
* @prop {close: func} Called to close the window
* @prop {channels: Array} List of channels
* @prop {switch: func} Called to switch a param on given channel
* @prop {open: func} Called to open the window
* @prop {close: func} Called to close the window
*/
App.components.modal('Channel', {
displayName: 'Board',
@ -18,6 +19,7 @@
propTypes: {
channels: React.PropTypes.array,
switch: React.PropTypes.func,
open: React.PropTypes.func,
close: React.PropTypes.func
},
@ -26,21 +28,37 @@
return {
channels: [],
switch: function () {},
open: function () {},
close: function () {}
};
},
/**
* Creates a function to switch a param on given channel
*
* @param {channel: object} Channel object
*/
switch: function (channel) {
return function (param) {
this.props.switch(param, channel.id);
}.bind(this);
},
/**
* Render modal
*/
render: function () {
var title = 'Instruments', channels;
console.log('rendering!');
channels = this.props.channels.map(function (channel) {
channel.key = 'channel-' + channel.id;
channel.switch = this.switch(channel);
return App.components.Channel.Channel(channel);
});
}, this);
return React.DOM.form({
className: 'vex-dialog-form channels'

View File

@ -1,10 +1,17 @@
/*jshint browser:true */
/*globals React, App */
(function () {
'use strict';
/**
* Display channel
* Display a channel
*
* @prop {id: number} Channel ID
* @prop {program: number} Program number
* @prop {sound: bool} Whether sound should be played or not
* @prop {notes: bool} Whether notes should be displayed or not
* @prop {switch: func} Called to switch a param
*/
App.components.create('Channel', {
displayName: 'Channel',
@ -191,25 +198,38 @@
propTypes: {
id: React.PropTypes.number.isRequired,
program: React.PropTypes.number.isRequired,
muted: React.PropTypes.bool,
solo: React.PropTypes.bool
sound: React.PropTypes.bool,
notes: React.PropTypes.bool,
switch: React.PropTypes.func
},
getDefaultProps: function () {
return {
id: 0,
program: 0,
muted: false,
solo: false
sound: true,
notes: true,
switch: function () {}
};
},
/**
* Create a function to switch given parameter
*
* @param {param: string} Parameter name
*/
switcher: function (param) {
return function (e) {
this.props.switch(param);
e.preventDefault();
}.bind(this);
},
/**
* Render channel
*/
render: function () {
var title = 'Gestion des canaux', type, name,
channel = App.components.Channel.Channel;
var type, name, channel = App.components.Channel.Channel;
if (this.props.id === 9) {
type = channel.types[2];
@ -222,7 +242,21 @@
return React.DOM.li({
className: 'channel ' + type,
'data-channel': this.props.id
}, React.DOM.span(null, name));
}, React.DOM.span(null, [
name,
React.DOM.button({
key: 'sound',
className: 'bt icon sound ' +
((this.props.notes) ? 'on' : 'off'),
onClick: this.switcher('notes')
}, 'Afficher/masquer'),
React.DOM.button({
key: 'notes',
className: 'bt icon notes ' +
((this.props.sound) ? 'on' : 'off'),
onClick: this.switcher('sound')
}, 'Jouer/Ne pas jouer')
]));
}
});
}());

View File

@ -86,20 +86,20 @@
if (this.props.opened) {
controls.push(React.DOM.button({
key: 'switchPlayState',
className: (this.props.playing) ?
'switch-play-state pause' : 'switch-play-state',
className: 'bt icon ' + ((this.props.playing) ?
'switch-play-state pause' : 'switch-play-state'),
onClick: this.switchPlayState
}, 'Play/pause'));
controls.push(React.DOM.button({
key: 'openChannelsWindow',
className: 'open-channels-window',
className: 'bt icon open-channels-window',
onClick: this.props.showChannelsModal
}, 'Ouvrir le gestionnaire de canaux'));
controls.push(App.components.UI.Selector({
key: 'setPlaySpeed',
className: 'set-play-speed',
className: 'bt icon set-play-speed',
values: [1, 0.5, 0.3, 1, 2, 3],
value: this.props.speed,
@ -109,14 +109,14 @@
controls.push(React.DOM.button({
key: 'close',
className: 'close',
className: 'bt icon close',
onClick: this.props.close
}));
} else {
controls.push(React.DOM.button({
key: 'close',
className: 'close',
className: 'bt icon close',
onClick: window.close
}));

View File

@ -1,4 +1,4 @@
/*jshint es5: true */
/*jshint browser:true */
/*globals App, React */
(function () {
@ -294,11 +294,13 @@
if (event.time >= start && event.time < end) {
switch (event.type) {
case 'noteOn':
this.refs.keyboard.on(
event.note,
event.channel,
event.velocity
);
if (this.state.meta.channels[event.channel].sound) {
this.refs.keyboard.on(
event.note,
event.channel,
event.velocity
);
}
break;
case 'noteOff':
this.refs.keyboard.off(
@ -348,6 +350,39 @@
e.preventDefault();
},
/**
* Display a modal for managing channels
*/
showChannelsModal: function () {
if (this.state.url === '') {
return;
}
this.channelsModal.open();
},
/**
* Switch given parameter on channel
*
* @param {param: string} Parameter to switch
* @param {channel: number} Channel ID
*/
switchChannelParam: function (param, channel) {
var channels = this.state.meta.channels.slice(0),
length = channels.length, i;
for (i = 0; i < length; i += 1) {
if (channels[i].id === channel) {
channels[i][param] = !channels[i][param];
}
}
this.state.meta.channels = channels;
this.setState({
meta: this.state.meta
});
},
/**
* Delegates
*/
@ -377,18 +412,6 @@
});
},
showChannelsModal: function () {
if (this.state.url === '') {
return;
}
var modal = App.components.Channel.Modal({
channels: this.state.meta.channels
});
modal.open();
},
/**
* Render panel
*/
@ -405,6 +428,7 @@
ref: 'noteboard',
notes: this.state.meta.notes,
channels: this.state.meta.channels,
playing: this.state.playing,
speed: this.state.speed,
@ -444,6 +468,11 @@
onDragEnd: this.resume
});
this.channelsModal = App.components.Channel.Board({
channels: this.state.meta.channels,
switch: this.switchChannelParam
});
return React.DOM.div({
className: 'main',
onDragOver: this.dragOver,

View File

@ -1,3 +1,4 @@
/*jshint browser:true */
/*globals React, App */
(function () {
@ -7,6 +8,7 @@
* Displays a noteboard from given notes
*
* @prop {notes: array} Array of notes to show
* @prop {channels: array} Array of configured channels
* @prop {time: int} Playback time
* @prop {length: int} Playback length
* @prop {speed: int} Playback speed
@ -34,7 +36,8 @@
* State and props config
*/
propTypes: {
notes: React.PropTypes.array.isRequired,
notes: React.PropTypes.array,
channels: React.PropTypes.array,
time: React.PropTypes.number,
length: React.PropTypes.number,
@ -50,7 +53,7 @@
getDefaultProps: function () {
return {
notes: [],
channels: [],
playing: false,
time: 0,
length: 0,
@ -151,6 +154,7 @@
className: 'score',
notes: this.props.notes,
channels: this.props.channels,
time: this.props.time
});
} else {

View File

@ -1,4 +1,5 @@
/*globals React, Kinetic, App */
/*jshint browser:true */
/*globals React, App */
(function () {
'use strict';
@ -6,8 +7,9 @@
/**
* Display a set of notes
*
* @prop {notes: array} List of notes
* @prop {time: int} Current playing time
* @prop {notes: array} List of notes
* @prop {channels: array} List of configured channels
* @prop {time: int} Current playing time
*/
App.components.create('Note', {
displayName: 'Score',
@ -35,12 +37,12 @@
propTypes: {
notes: React.PropTypes.array.isRequired,
channels: React.PropTypes.array.isRequired,
time: React.PropTypes.number
},
getDefaultProps: function () {
return {
notes: [],
time: 0
};
},
@ -170,7 +172,8 @@
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;
colors = App.components.Note.Score.channels, count = 0,
channels = this.props.channels;
ctx = this.getDOMNode().getContext('2d');
ctx.clearRect(0, 0, width, height);
@ -183,7 +186,8 @@
note = notes[i];
if (note.start + note.length > time &&
note.start < time + maxTime) {
note.start < time + maxTime &&
channels[note.channel].notes) {
offset = this.getOffset(note.note);
count += 1;
@ -201,7 +205,7 @@
noteWidth = Math.floor(xUnit / 2);
}
ctx.fillStyle = channels[note.channel];
ctx.fillStyle = colors[note.channel];
ctx.strokeRect(x, y, noteWidth, noteHeight);
ctx.fillRect(x, y, noteWidth, noteHeight);
}

View File

@ -1,6 +1,6 @@
/*globals React, App */
/*globals jQuery, React, App */
(function () {
(function ($) {
'use strict';
/**
@ -82,10 +82,15 @@
* Render handle
*/
render: function () {
return React.DOM.button({
className: 'selector',
onClick: this.click
}, this.state.value);
var props = $.extend({}, this.props);
delete props.value;
delete props.index;
props.onClick = this.click;
props.className = 'selector ' +
((props.className) ? props.className : '');
return React.DOM.button(props, this.state.value);
}
});
}());
}(jQuery));

View File

@ -28,15 +28,13 @@
*
* Represent a channel object
*/
function Channel(options) {
options = options || {};
this.id = options.id || 0;
this.meta = options.meta || {};
this.program = options.program || 0;
this.muted = options.muted || false;
this.hidden = options.solo || false;
this.pending = options.pending || {};
function Channel(id) {
this.id = id;
this.meta = {};
this.program = 0;
this.sound = true;
this.notes = true;
this.pending = {};
}
/**
@ -69,14 +67,13 @@
*/
function pushNote(meta, event, time) {
var channel = event.channel,
note = event.noteNumber,
data = {
start: time,
channel: channel,
note: note
};
note = event.noteNumber;
meta.channels[channel].pending[note] = data;
meta.channels[channel].pending[note] = new Note({
start: time,
channel: channel,
note: note
});
}
/**
@ -152,7 +149,7 @@
function parseData(data) {
return new window.Promise(function (resolve, reject) {
var tracks, events, tracksLength, eventsLength, length,
event, helpLink, i, j,
event, i, j,
// parsing data
timeline = [], meta = {},
channelPrefix = null, metaObject,
@ -166,14 +163,11 @@
try {
data = new MidiFile(data);
} catch (err) {
helpLink = 'http://fr.wikipedia.org/wiki/Fichier_midi';
if (err === 'Bad .mid file - header not found') {
reject(new Error(
'Le fichier n\'est pas un fichier MIDI ' +
'valide. Ce logiciel ne prend en charge ' +
'que les fichiers de type ' +
'<a href="' + helpLink + '">MIDI</a>.'
'que les fichiers de type MIDI.'
));
} else {
reject(new Error(
@ -206,9 +200,7 @@
length = 16;
for (i = 0; i < length; i += 1) {
meta.channels[i] = new Channel({
id: i
});
meta.channels[i] = new Channel(i);
}
// parse all events