💡 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; 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 */ /* primary button */
.bt.primary { .bt.primary {
border: 1px solid #e0e0e0; border: 1px solid #e0e0e0;

View File

@ -258,25 +258,6 @@ a {
text-align: center; 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 > * { .main .control > * {
display: block; display: block;
margin: 0 auto 1em auto; margin: 0 auto 1em auto;
@ -424,6 +405,28 @@ a {
background-image: url('../images/icons/instruments/organ.png'); 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 * 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 * Modal for managing channels
* *
* @prop {channels: Array} List of channels * @prop {channels: Array} List of channels
* @prop {open: func} Called to open the window * @prop {switch: func} Called to switch a param on given channel
* @prop {close: func} Called to close the window * @prop {open: func} Called to open the window
* @prop {close: func} Called to close the window
*/ */
App.components.modal('Channel', { App.components.modal('Channel', {
displayName: 'Board', displayName: 'Board',
@ -18,6 +19,7 @@
propTypes: { propTypes: {
channels: React.PropTypes.array, channels: React.PropTypes.array,
switch: React.PropTypes.func,
open: React.PropTypes.func, open: React.PropTypes.func,
close: React.PropTypes.func close: React.PropTypes.func
}, },
@ -26,21 +28,37 @@
return { return {
channels: [], channels: [],
switch: function () {},
open: function () {}, open: function () {},
close: 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 modal
*/ */
render: function () { render: function () {
var title = 'Instruments', channels; var title = 'Instruments', channels;
console.log('rendering!');
channels = this.props.channels.map(function (channel) { channels = this.props.channels.map(function (channel) {
channel.key = 'channel-' + channel.id; channel.key = 'channel-' + channel.id;
channel.switch = this.switch(channel);
return App.components.Channel.Channel(channel); return App.components.Channel.Channel(channel);
}); }, this);
return React.DOM.form({ return React.DOM.form({
className: 'vex-dialog-form channels' className: 'vex-dialog-form channels'

View File

@ -1,10 +1,17 @@
/*jshint browser:true */
/*globals React, App */ /*globals React, App */
(function () { (function () {
'use strict'; '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', { App.components.create('Channel', {
displayName: 'Channel', displayName: 'Channel',
@ -191,25 +198,38 @@
propTypes: { propTypes: {
id: React.PropTypes.number.isRequired, id: React.PropTypes.number.isRequired,
program: React.PropTypes.number.isRequired, program: React.PropTypes.number.isRequired,
muted: React.PropTypes.bool, sound: React.PropTypes.bool,
solo: React.PropTypes.bool notes: React.PropTypes.bool,
switch: React.PropTypes.func
}, },
getDefaultProps: function () { getDefaultProps: function () {
return { return {
id: 0, sound: true,
program: 0, notes: true,
muted: false,
solo: false 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 channel
*/ */
render: function () { render: function () {
var title = 'Gestion des canaux', type, name, var type, name, channel = App.components.Channel.Channel;
channel = App.components.Channel.Channel;
if (this.props.id === 9) { if (this.props.id === 9) {
type = channel.types[2]; type = channel.types[2];
@ -222,7 +242,21 @@
return React.DOM.li({ return React.DOM.li({
className: 'channel ' + type, className: 'channel ' + type,
'data-channel': this.props.id '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) { if (this.props.opened) {
controls.push(React.DOM.button({ controls.push(React.DOM.button({
key: 'switchPlayState', key: 'switchPlayState',
className: (this.props.playing) ? className: 'bt icon ' + ((this.props.playing) ?
'switch-play-state pause' : 'switch-play-state', 'switch-play-state pause' : 'switch-play-state'),
onClick: this.switchPlayState onClick: this.switchPlayState
}, 'Play/pause')); }, 'Play/pause'));
controls.push(React.DOM.button({ controls.push(React.DOM.button({
key: 'openChannelsWindow', key: 'openChannelsWindow',
className: 'open-channels-window', className: 'bt icon open-channels-window',
onClick: this.props.showChannelsModal onClick: this.props.showChannelsModal
}, 'Ouvrir le gestionnaire de canaux')); }, 'Ouvrir le gestionnaire de canaux'));
controls.push(App.components.UI.Selector({ controls.push(App.components.UI.Selector({
key: 'setPlaySpeed', key: 'setPlaySpeed',
className: 'set-play-speed', className: 'bt icon set-play-speed',
values: [1, 0.5, 0.3, 1, 2, 3], values: [1, 0.5, 0.3, 1, 2, 3],
value: this.props.speed, value: this.props.speed,
@ -109,14 +109,14 @@
controls.push(React.DOM.button({ controls.push(React.DOM.button({
key: 'close', key: 'close',
className: 'close', className: 'bt icon close',
onClick: this.props.close onClick: this.props.close
})); }));
} else { } else {
controls.push(React.DOM.button({ controls.push(React.DOM.button({
key: 'close', key: 'close',
className: 'close', className: 'bt icon close',
onClick: window.close onClick: window.close
})); }));

View File

@ -1,4 +1,4 @@
/*jshint es5: true */ /*jshint browser:true */
/*globals App, React */ /*globals App, React */
(function () { (function () {
@ -294,11 +294,13 @@
if (event.time >= start && event.time < end) { if (event.time >= start && event.time < end) {
switch (event.type) { switch (event.type) {
case 'noteOn': case 'noteOn':
this.refs.keyboard.on( if (this.state.meta.channels[event.channel].sound) {
event.note, this.refs.keyboard.on(
event.channel, event.note,
event.velocity event.channel,
); event.velocity
);
}
break; break;
case 'noteOff': case 'noteOff':
this.refs.keyboard.off( this.refs.keyboard.off(
@ -348,6 +350,39 @@
e.preventDefault(); 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 * 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 * Render panel
*/ */
@ -405,6 +428,7 @@
ref: 'noteboard', ref: 'noteboard',
notes: this.state.meta.notes, notes: this.state.meta.notes,
channels: this.state.meta.channels,
playing: this.state.playing, playing: this.state.playing,
speed: this.state.speed, speed: this.state.speed,
@ -444,6 +468,11 @@
onDragEnd: this.resume onDragEnd: this.resume
}); });
this.channelsModal = App.components.Channel.Board({
channels: this.state.meta.channels,
switch: this.switchChannelParam
});
return React.DOM.div({ return React.DOM.div({
className: 'main', className: 'main',
onDragOver: this.dragOver, onDragOver: this.dragOver,

View File

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

View File

@ -1,4 +1,5 @@
/*globals React, Kinetic, App */ /*jshint browser:true */
/*globals React, App */
(function () { (function () {
'use strict'; 'use strict';
@ -6,8 +7,9 @@
/** /**
* Display a set of notes * Display a set of notes
* *
* @prop {notes: array} List of notes * @prop {notes: array} List of notes
* @prop {time: int} Current playing time * @prop {channels: array} List of configured channels
* @prop {time: int} Current playing time
*/ */
App.components.create('Note', { App.components.create('Note', {
displayName: 'Score', displayName: 'Score',
@ -35,12 +37,12 @@
propTypes: { propTypes: {
notes: React.PropTypes.array.isRequired, notes: React.PropTypes.array.isRequired,
channels: React.PropTypes.array.isRequired,
time: React.PropTypes.number time: React.PropTypes.number
}, },
getDefaultProps: function () { getDefaultProps: function () {
return { return {
notes: [],
time: 0 time: 0
}; };
}, },
@ -170,7 +172,8 @@
xUnit = width / 52, yUnit = xUnit * 5, xUnit = width / 52, yUnit = xUnit * 5,
maxTime = Math.ceil(height / yUnit), maxTime = Math.ceil(height / yUnit),
note, length, i, ctx, offset, x, y, noteWidth, noteHeight, 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 = this.getDOMNode().getContext('2d');
ctx.clearRect(0, 0, width, height); ctx.clearRect(0, 0, width, height);
@ -183,7 +186,8 @@
note = notes[i]; note = notes[i];
if (note.start + note.length > time && if (note.start + note.length > time &&
note.start < time + maxTime) { note.start < time + maxTime &&
channels[note.channel].notes) {
offset = this.getOffset(note.note); offset = this.getOffset(note.note);
count += 1; count += 1;
@ -201,7 +205,7 @@
noteWidth = Math.floor(xUnit / 2); noteWidth = Math.floor(xUnit / 2);
} }
ctx.fillStyle = channels[note.channel]; ctx.fillStyle = colors[note.channel];
ctx.strokeRect(x, y, noteWidth, noteHeight); ctx.strokeRect(x, y, noteWidth, noteHeight);
ctx.fillRect(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'; 'use strict';
/** /**
@ -82,10 +82,15 @@
* Render handle * Render handle
*/ */
render: function () { render: function () {
return React.DOM.button({ var props = $.extend({}, this.props);
className: 'selector',
onClick: this.click delete props.value;
}, this.state.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 * Represent a channel object
*/ */
function Channel(options) { function Channel(id) {
options = options || {}; this.id = id;
this.meta = {};
this.id = options.id || 0; this.program = 0;
this.meta = options.meta || {}; this.sound = true;
this.program = options.program || 0; this.notes = true;
this.muted = options.muted || false; this.pending = {};
this.hidden = options.solo || false;
this.pending = options.pending || {};
} }
/** /**
@ -69,14 +67,13 @@
*/ */
function pushNote(meta, event, time) { function pushNote(meta, event, time) {
var channel = event.channel, var channel = event.channel,
note = event.noteNumber, note = event.noteNumber;
data = {
start: time,
channel: channel,
note: note
};
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) { function parseData(data) {
return new window.Promise(function (resolve, reject) { return new window.Promise(function (resolve, reject) {
var tracks, events, tracksLength, eventsLength, length, var tracks, events, tracksLength, eventsLength, length,
event, helpLink, i, j, event, i, j,
// parsing data // parsing data
timeline = [], meta = {}, timeline = [], meta = {},
channelPrefix = null, metaObject, channelPrefix = null, metaObject,
@ -166,14 +163,11 @@
try { try {
data = new MidiFile(data); data = new MidiFile(data);
} catch (err) { } catch (err) {
helpLink = 'http://fr.wikipedia.org/wiki/Fichier_midi';
if (err === 'Bad .mid file - header not found') { if (err === 'Bad .mid file - header not found') {
reject(new Error( reject(new Error(
'Le fichier n\'est pas un fichier MIDI ' + 'Le fichier n\'est pas un fichier MIDI ' +
'valide. Ce logiciel ne prend en charge ' + 'valide. Ce logiciel ne prend en charge ' +
'que les fichiers de type ' + 'que les fichiers de type MIDI.'
'<a href="' + helpLink + '">MIDI</a>.'
)); ));
} else { } else {
reject(new Error( reject(new Error(
@ -206,9 +200,7 @@
length = 16; length = 16;
for (i = 0; i < length; i += 1) { for (i = 0; i < length; i += 1) {
meta.channels[i] = new Channel({ meta.channels[i] = new Channel(i);
id: i
});
} }
// parse all events // parse all events