This commit is contained in:
Seth Call 2015-06-15 13:44:23 -05:00
parent 60302fc1fe
commit c28d8f3e7f
39 changed files with 2209 additions and 103 deletions

View File

@ -161,7 +161,7 @@
/**
setTimeout(function() {
var inputTracks = context.JK.TrackHelpers.getTracks(context.jamClient, 2);
var inputTracks = context.JK.TrackHelpers.getTracks(context.jamClient, 4);
// this is some ugly logic coming up, here's why:
// we need the id (guid) that the backend generated for the new track we just added

View File

@ -54,11 +54,11 @@
//= require react
//= require react_ujs
//= require react-init
//= require react-components
//= require web/signup_helper
//= require web/signin_helper
//= require web/signin
//= require web/tracking
//= require react-components
//= require_directory .
//= require_directory ./dialog
//= require_directory ./wizard

View File

@ -77,6 +77,9 @@
}
if (type === 2) { // BACKEND_MIXER_CHANGE
context.MixerActions.mixersChanged(type, text)
if(context.JK.CurrentSessionModel)
context.JK.CurrentSessionModel.onBackendMixerChanged(type, text)
}

View File

@ -3,7 +3,7 @@
$ = jQuery
context = window
context.JK ||= {};
broadcastActions = context.JK.Actions.Broadcast
broadcastActions = @BroadcastActions
context.JK.ClientInit = class ClientInit
constructor: () ->

View File

@ -11,6 +11,7 @@
var $draggingFaderHandle = null;
var $draggingFader = null;
var $floater = null;
var draggingOrientation = null;
var logger = g.JK.logger;
@ -20,6 +21,7 @@
e.stopPropagation();
var $fader = $(this);
var floaterConvert = $fader.data('floaterConverter')
var sessionModel = window.JK.CurrentSessionModel || null;
var mediaControlsDisabled = $fader.data('media-controls-disabled');
@ -43,7 +45,7 @@
}
}
draggingOrientation = $fader.attr('orientation');
draggingOrientation = $fader.attr('data-orientation');
var offset = $fader.offset();
var position = { top: e.pageY - offset.top, left: e.pageX - offset.left}
@ -53,6 +55,10 @@
return false;
}
if(floaterConvert) {
window.JK.FaderHelpers.setFloaterValue($fader.find('.floater'), floaterConvert(faderPct))
}
$fader.parent().triggerHandler('fader_change', {percentage: faderPct, dragging: false})
setHandlePosition($fader, faderPct);
@ -61,9 +67,9 @@
function setHandlePosition($fader, value) {
var ratio, position;
var $handle = $fader.find('div[control="fader-handle"]');
var $handle = $fader.find('div[data-control="fader-handle"]');
var orientation = $fader.attr('orientation');
var orientation = $fader.attr('data-orientation');
var handleCssAttribute = getHandleCssAttribute($fader);
// required because this method is entered directly when from a callback
@ -81,7 +87,7 @@
}
function faderValue($fader, e, offset) {
var orientation = $fader.attr('orientation');
var orientation = $fader.attr('data-orientation');
var getPercentFunction = getVerticalFaderPercent;
var relativePosition = offset.top;
if (orientation && orientation == 'horizontal') {
@ -92,7 +98,7 @@
}
function getHandleCssAttribute($fader) {
var orientation = $fader.attr('orientation');
var orientation = $fader.attr('data-orientation');
return (orientation === 'horizontal') ? 'left' : 'top';
}
@ -134,12 +140,34 @@
return false;
}
// simple snap feature to stick to the mid point
if(faderPct > 46 && faderPct < 54 && $draggingFader.data('snap')) {
faderPct = 50
var orientation = $draggingFader.attr('data-orientation');
if(orientation == 'horizontal') {
var width = $draggingFader.width()
var left = width / 2
ui.position.left = left
}
else {
var height = $draggingFader.height()
var top = height / 2
ui.position.top = top
}
}
var floaterConvert = $draggingFaderHandle.data('floaterConverter')
if(floaterConvert && $floater) {
window.JK.FaderHelpers.setFloaterValue($floater, floaterConvert(faderPct))
}
$draggingFader.parent().triggerHandler('fader_change', {percentage: faderPct, dragging: true})
}
function onFaderDragStart(e, ui) {
$draggingFaderHandle = $(this);
$draggingFader = $draggingFaderHandle.closest('div[control="fader"]');
$draggingFader = $draggingFaderHandle.closest('div[data-control="fader"]');
$floater = $draggingFaderHandle.find('.floater')
draggingOrientation = $draggingFader.attr('orientation');
var mediaControlsDisabled = $draggingFaderHandle.data('media-controls-disabled');
@ -210,12 +238,12 @@
selector.html(g._.template(templateSource, options));
selector.find('div[control="fader"]')
selector.find('div[data-control="fader"]')
.data('media-controls-disabled', selector.data('media-controls-disabled'))
.data('media-track-opener', selector.data('media-track-opener'))
.data('showHelpAboutMediaMixers', selector.data('showHelpAboutMediaMixers'))
selector.find('div[control="fader-handle"]').draggable({
selector.find('div[data-control="fader-handle"]').draggable({
drag: onFaderDrag,
start: onFaderDragStart,
stop: onFaderDragStop,
@ -233,6 +261,43 @@
}
},
renderFader2: function (selector, userOptions, floaterConverter) {
selector = $(selector);
if (userOptions === undefined) {
throw ("renderFader: userOptions is required");
}
var renderDefaults = {
faderType: "vertical"
};
var options = $.extend({}, renderDefaults, userOptions);
selector.find('div[data-control="fader"]')
.data('media-controls-disabled', selector.data('media-controls-disabled'))
.data('media-track-opener', selector.data('media-track-opener'))
.data('showHelpAboutMediaMixers', selector.data('showHelpAboutMediaMixers'))
.data('floaterConverter', floaterConverter)
.data('snap', userOptions.snap)
selector.find('div[data-control="fader-handle"]').draggable({
drag: onFaderDrag,
start: onFaderDragStart,
stop: onFaderDragStop,
containment: "parent",
axis: options.faderType === 'horizontal' ? 'x' : 'y'
}).data('media-controls-disabled', selector.data('media-controls-disabled'))
.data('media-track-opener', selector.data('media-track-opener'))
.data('showHelpAboutMediaMixers', selector.data('showHelpAboutMediaMixers'))
.data('floaterConverter', floaterConverter)
.data('snap', userOptions.snap)
// Embed any custom styles, applied to the .fader below selector
if ("style" in options) {
for (var key in options.style) {
selector.find(' .fader').css(key, options.style[key]);
}
}
},
convertLinearToDb: function (input) {
// deal with extremes better
@ -263,27 +328,48 @@
// composite function resembling audio taper
if (input <= 1) { return -80; }
if (input <= 28) { return (2 * input - 80); }
if (input <= 79) { return (0.5 * input - 38); }
if (input < 99) { return (0.875 * input - 67.5); }
if (input <= 28) { return Math.round((2 * input - 80)); } // -78 to -24 db
if (input <= 79) { return Math.round((0.5 * input - 38)); } // -24 to 1.5 db
if (input < 99) { return Math.round((0.875 * input - 67.5)); } // 1.625 - 19.125 db
if (input >= 99) { return 20; }
},
convertAudioTaperToPercent: function(db) {
if(db <= -78) { return 0}
if(db <= -24) { return (db + 80) / 2 }
if(db <= 1.5) { return (db + 38) / .5 }
if(db <= 19.125) { return (db + 67.5) / 0.875 }
return 100;
},
setFaderValue: function (faderId, faderValue) {
var $fader = $('[fader-id="' + faderId + '"]');
setFaderValue: function (faderId, faderValue, floaterValue) {
var $fader = $('[data-fader-id="' + faderId + '"]');
this.setHandlePosition($fader, faderValue);
if(floaterValue !== undefined) {
var $floater = $fader.find('.floater')
this.setFloaterValue($floater, floaterValue)
}
},
showFader: function(faderId) {
var $fader = $('[data-fader-id="' + faderId + '"]');
$fader.find('div[data-control="fader-handle"]').show()
},
setHandlePosition: function ($fader, faderValue) {
draggingOrientation = $fader.attr('orientation');
draggingOrientation = $fader.attr('data-orientation');
setHandlePosition($fader, faderValue);
draggingOrientation = null;
},
setFloaterValue: function($floater, floaterValue) {
$floater.text(floaterValue)
},
initialize: function () {
$('body').on('click', 'div[control="fader"]', faderClick);
$('body').on('click', 'div[data-control="fader"]', faderClick);
}
};

View File

@ -4,6 +4,7 @@
context.JK = context.JK || {};
context.JK.FakeJamClient = function(app, p2pMessageFactory) {
var ChannelGroupIds = context.JK.ChannelGroupIds
var logger = context.JK.logger;
logger.info("*** Fake JamClient instance initialized. ***");
@ -169,22 +170,22 @@
function FTUEGetMusicInputs() {
dbg('FTUEGetMusicInputs');
return {
"i~11~MultiChannel (FW AP Multi)~0^i~11~Multichannel (FW AP Multi)~1":
"Multichannel (FW AP Multi) - Channel 1/Multichannel (FW AP Multi) - Channel 2"
"i~11~MultiChannel (FWAPMulti)~0^i~11~Multichannel (FWAPMulti)~1":
"Multichannel (FWAPMulti) - Channel 1/Multichannel (FWAPMulti) - Channel 2"
};
}
function FTUEGetMusicOutputs() {
dbg('FTUEGetMusicOutputs');
return {
"o~11~Multichannel (FW AP Multi)~0^o~11~Multichannel (FW AP Multi)~1":
"Multichannel (FW AP Multi) - Channel 1/Multichannel (FW AP Multi) - Channel 2"
"o~11~Multichannel (FWAPMulti)~0^o~11~Multichannel (FWAPMulti)~1":
"Multichannel (FWAPMulti) - Channel 1/Multichannel (FWAPMulti) - Channel 2"
};
}
function FTUEGetChatInputs() {
dbg('FTUEGetChatInputs');
return {
"i~11~MultiChannel (FW AP Multi)~0^i~11~Multichannel (FW AP Multi)~1":
"Multichannel (FW AP Multi) - Channel 1/Multichannel (FW AP Multi) - Channel 2"
"i~11~MultiChannel (FWAPMulti)~0^i~11~Multichannel (FWAPMulti)~1":
"Multichannel (FWAPMulti) - Channel 1/Multichannel (FWAPMulti) - Channel 2"
};
}
function FTUEGetChannels() {
@ -449,7 +450,7 @@
}
function GetASIODevices() {
var response =[{"device_id":0,"device_name":"Realtek High Definition Audio","device_type": 0,"interfaces":[{"interface_id":0,"interface_name":"Realtek HDA SPDIF Out","pins":[{"is_input":false,"pin_id":0,"pin_name":"PC Speaker"}]},{"interface_id":1,"interface_name":"Realtek HD Audio rear output","pins":[{"is_input":false,"pin_id":0,"pin_name":"PC Speaker"}]},{"interface_id":2,"interface_name":"Realtek HD Audio Mic input","pins":[{"is_input":true,"pin_id":0,"pin_name":"Recording Control"}]},{"interface_id":3,"interface_name":"Realtek HD Audio Line input","pins":[{"is_input":true,"pin_id":0,"pin_name":"Recording Control"}]},{"interface_id":4,"interface_name":"Realtek HD Digital input","pins":[{"is_input":true,"pin_id":0,"pin_name":"Capture"}]},{"interface_id":5,"interface_name":"Realtek HD Audio Stereo input","pins":[{"is_input":true,"pin_id":0,"pin_name":"Recording Control"}]}],"wavert_supported":false},{"device_id":1,"device_name":"M-Audio FW Audiophile","device_type": 1,"interfaces":[{"interface_id":0,"interface_name":"FW AP Multi","pins":[{"is_input":false,"pin_id":0,"pin_name":"Output"},{"is_input":true,"pin_id":1,"pin_name":"Input"}]},{"interface_id":1,"interface_name":"FW AP 1/2","pins":[{"is_input":false,"pin_id":0,"pin_name":"Output"},{"is_input":true,"pin_id":1,"pin_name":"Input"}]},{"interface_id":2,"interface_name":"FW AP SPDIF","pins":[{"is_input":false,"pin_id":0,"pin_name":"Output"},{"is_input":true,"pin_id":1,"pin_name":"Input"}]},{"interface_id":3,"interface_name":"FW AP 3/4","pins":[{"is_input":false,"pin_id":0,"pin_name":"Output"}]}],"wavert_supported":false},{"device_id":2,"device_name":"Virtual Audio Cable","device_type": 2,"interfaces":[{"interface_id":0,"interface_name":"Virtual Cable 2","pins":[{"is_input":true,"pin_id":0,"pin_name":"Capture"},{"is_input":false,"pin_id":1,"pin_name":"Output"}]},{"interface_id":1,"interface_name":"Virtual Cable 1","pins":[{"is_input":true,"pin_id":0,"pin_name":"Capture"},{"is_input":false,"pin_id":1,"pin_name":"Output"}]}],"wavert_supported":false},{"device_id":3,"device_name":"WebCamDV WDM Audio Capture","device_type": 3,"interfaces":[{"interface_id":0,"interface_name":"WebCamDV Audio","pins":[{"is_input":true,"pin_id":0,"pin_name":"Recording Control"},{"is_input":false,"pin_id":1,"pin_name":"Volume Control"}]}],"wavert_supported":false}];
var response =[{"device_id":0,"device_name":"Realtek High Definition Audio","device_type": 0,"interfaces":[{"interface_id":0,"interface_name":"Realtek HDA SPDIF Out","pins":[{"is_input":false,"pin_id":0,"pin_name":"PC Speaker"}]},{"interface_id":1,"interface_name":"Realtek HD Audio rear output","pins":[{"is_input":false,"pin_id":0,"pin_name":"PC Speaker"}]},{"interface_id":2,"interface_name":"Realtek HD Audio Mic input","pins":[{"is_input":true,"pin_id":0,"pin_name":"Recording Control"}]},{"interface_id":3,"interface_name":"Realtek HD Audio Line input","pins":[{"is_input":true,"pin_id":0,"pin_name":"Recording Control"}]},{"interface_id":4,"interface_name":"Realtek HD Digital input","pins":[{"is_input":true,"pin_id":0,"pin_name":"Capture"}]},{"interface_id":5,"interface_name":"Realtek HD Audio Stereo input","pins":[{"is_input":true,"pin_id":0,"pin_name":"Recording Control"}]}],"wavert_supported":false},{"device_id":1,"device_name":"M-Audio FW Audiophile","device_type": 1,"interfaces":[{"interface_id":0,"interface_name":"FWAPMulti","pins":[{"is_input":false,"pin_id":0,"pin_name":"Output"},{"is_input":true,"pin_id":1,"pin_name":"Input"}]},{"interface_id":1,"interface_name":"FW AP 1/2","pins":[{"is_input":false,"pin_id":0,"pin_name":"Output"},{"is_input":true,"pin_id":1,"pin_name":"Input"}]},{"interface_id":2,"interface_name":"FW AP SPDIF","pins":[{"is_input":false,"pin_id":0,"pin_name":"Output"},{"is_input":true,"pin_id":1,"pin_name":"Input"}]},{"interface_id":3,"interface_name":"FW AP 3/4","pins":[{"is_input":false,"pin_id":0,"pin_name":"Output"}]}],"wavert_supported":false},{"device_id":2,"device_name":"Virtual Audio Cable","device_type": 2,"interfaces":[{"interface_id":0,"interface_name":"Virtual Cable 2","pins":[{"is_input":true,"pin_id":0,"pin_name":"Capture"},{"is_input":false,"pin_id":1,"pin_name":"Output"}]},{"interface_id":1,"interface_name":"Virtual Cable 1","pins":[{"is_input":true,"pin_id":0,"pin_name":"Capture"},{"is_input":false,"pin_id":1,"pin_name":"Output"}]}],"wavert_supported":false},{"device_id":3,"device_name":"WebCamDV WDM Audio Capture","device_type": 3,"interfaces":[{"interface_id":0,"interface_name":"WebCamDV Audio","pins":[{"is_input":true,"pin_id":0,"pin_name":"Recording Control"},{"is_input":false,"pin_id":1,"pin_name":"Volume Control"}]}],"wavert_supported":false}];
return response;
}
@ -474,12 +475,22 @@
}
function SessionGetControlState(mixerIds, isMasterOrPersonal) {
dbg("SessionGetControlState");
var groups = [0, 1, 2, 3, 3, 7, 8, 10, 11, 12];
var groups =
[ChannelGroupIds.MasterGroup,
ChannelGroupIds.MonitorGroup,
ChannelGroupIds.AudioInputMusicGroup,
ChannelGroupIds.AudioInputChatGroup,
ChannelGroupIds.AudioInputChatGroup,
ChannelGroupIds.UserMusicInputGroup,
ChannelGroupIds.UserChatInputGroup,
ChannelGroupIds.PeerMediaTrackGroup,
ChannelGroupIds.JamTrackGroup,
ChannelGroupIds.MetronomeGroup];
var names = [
"FW AP Multi",
"FW AP Multi",
"FW AP Multi",
"FW AP Multi",
"FWAPMulti",
"FWAPMulti",
"FWAPMulti",
"FWAPMulti",
"",
"",
"",
@ -533,6 +544,7 @@
stereo: true,
volume_left: -40,
volume_right:-40,
pan: 0,
instrument_id:50, // see globals.js
mode: isMasterOrPersonal,
rid: mixerIds[i]
@ -542,10 +554,10 @@
}
function SessionGetIDs() {
return [
"FW AP Multi_0_10000",
"FW AP Multi_1_10100",
"FW AP Multi_2_10200",
"FW AP Multi_3_10500",
"FWAPMulti_0_10000",
"FWAPMulti_1_10100",
"FWAPMulti_2_10200",
"FWAPMulti_3_10500",
"User@208.191.152.98#",
"User@208.191.152.98_*"
];
@ -612,9 +624,9 @@
function doCallbacks() {
var names = ["vu"];
//var ids = ["FW AP Multi_2_10200", "FW AP Multi_0_10000"];
var ids= ["i~11~MultiChannel (FW AP Multi)~0^i~11~Multichannel (FW AP Multi)~1",
"i~11~MultiChannel (FW AP Multi)~0^i~11~Multichannel (FW AP Multi)~2"];
//var ids = ["FWAPMulti_2_10200", "FWAPMulti_0_10000"];
var ids= ["i~11~MultiChannel (FWAPMulti)~0^i~11~Multichannel (FWAPMulti)~1",
"i~11~MultiChannel (FWAPMulti)~0^i~11~Multichannel (FWAPMulti)~2"];
var args = [];
for (var i=0; i<ids.length; i++) {

View File

@ -313,4 +313,25 @@
context.JK.NAMED_MESSAGES = {
MASTER_VS_PERSONAL_MIX : 'master_vs_personal_mix'
}
})(window,jQuery);
context.JK.ChannelGroupIds = {
"MasterGroup": 0,
"MonitorGroup": 1,
"MasterCatGroup" : 2,
"MonitorCatGroup" : 3,
"AudioInputMusicGroup": 4,
"AudioInputChatGroup": 5,
"MediaTrackGroup": 6,
"StreamOutMusicGroup": 7,
"StreamOutChatGroup": 8,
"StreamOutMediaGroup" : 9,
"UserMusicInputGroup": 10,
"UserChatInputGroup": 11,
"UserMediaInputGroup": 12,
"PeerAudioInputMusicGroup": 13,
"PeerMediaTrackGroup": 14,
"JamTrackGroup": 15,
"MetronomeGroup": 16
};
})(window,jQuery);

View File

@ -478,6 +478,14 @@
});
}
function deleteParticipant(clientId) {
var url = "/api/participants/" + lientId;
return $.ajax({
type: "DELETE",
url: url
});
}
function login(options) {
var url = '/api/auths/login';
@ -1854,6 +1862,7 @@
this.addRecordingLike = addRecordingLike;
this.addPlayablePlay = addPlayablePlay;
this.getSession = getSession;
this.deleteParticipant = deleteParticipant;
this.getClientDownloads = getClientDownloads;
this.createEmailInvitations = createEmailInvitations;
this.createMusicianInvite = createMusicianInvite;

View File

@ -0,0 +1,34 @@
context = window
$ = jQuery
panHelper = class PanHelper
###
Convert the pan value that comes from a backend mixer
to a 0-100 % usable by a draggable panner element
###
convertPanToPercent: (mixerPan) ->
value = (((mixerPan + 90) / 90) * 100) / 2
if value < 0
0
else if value > 100
100
else
value
###
Convert the % value of a draggable panner element
to a mixer-ready pan value
###
convertPercentToPan: (percent) ->
value = 2 * percent / 100 * 90 - 90
if value < -90
-90
else if value > 90
90
else
Math.round(value)
context.JK.PanHelpers = new panHelper()

View File

@ -1,3 +1,8 @@
//= require_directory ./react-components/helpers
//= require_directory ./react-components/actions
//= require ./react-components/stores/AppStore
//= require ./react-components/stores/SessionStore
//= require ./react-components/stores/MixerStore
//= require ./react-components/stores/SessionMyTracksStore
//= require_directory ./react-components/stores
//= require_directory ./react-components

View File

@ -0,0 +1,74 @@
context = window
MixerActions = @MixerActions
@SessionMyTrack = React.createClass({
mixins: [Reflux.listenTo(@SessionMyTracksStore,"onInputsChanged")]
onInputsChanged: (sessionMixers) ->
mixers = sessionMixers.mixers
newMixers = mixers.findMixerForTrack.apply(mixers, this.props.mixerFinder)
this.setState({mixers: newMixers})
getInitialState: () ->
{mixers: this.props.mixers}
handleMute: (e) ->
e.preventDefault()
muting = $(e.currentTarget).is('.enabled')
MixerActions.mute([this.state.mixers.mixer, this.state.mixers.oppositeMixer], muting)
render: () ->
muteMixer = this.state.mixers.muteMixer
vuMixer = this.state.mixers.vuMixer
classes = React.addons.classSet({
'track-icon-mute': true
'enabled' : !muteMixer.mute
'muted' : muteMixer.mute
})
`<div className="session-track my-track">
<div className="name">{this.props.name}</div>
<div className="track-avatar"><img src={this.props.photoUrl}/></div>
<div className="track-instrument"><img height="45" src={this.props.instrumentIcon} width="45" /></div>
<div className="track-controls">
<SessionTrackVU orientation="horizontal" lightCount={4} lightWidth={17} lightHeight={3} side="vul" mixers={this.state.mixers} />
<div className="track-buttons">
<div className={classes} data-control="mute" data-mixer-id={muteMixer.id} onClick={this.handleMute}/>
<div className="track-icon-pan"/>
<div className="track-icon-equalizer" />
</div>
<br className="clearall"/>
</div>
<br className="clearall"/>
</div>`
componentDidMount: () ->
$root = jQuery(this.getDOMNode())
$mute = $root.find('.track-icon-mute')
$pan = $root.find('.track-icon-pan')
context.JK.interactReactBubble(
$mute,
'SessionTrackVolumeHover',
{mixers:this.state.mixers, mixerFinder: this.props.mixerFinder},
{width:235, positions:['right', 'left'], offsetParent:$root.closest('.screen')})
context.JK.interactReactBubble(
$pan,
'SessionTrackPanHover',
{mixers:this.state.mixers, mixerFinder: this.props.mixerFinder},
{width:331, positions:['right', 'left'], offsetParent:$root.closest('.screen')})
})

View File

@ -1,23 +1,75 @@
context = window
ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;
@SessionMyTracks = React.createClass({
render: () ->
mixins: [Reflux.listenTo(@SessionMyTracksStore,"onInputsChanged"), Reflux.listenTo(@AppStore,"onAppInit")]
noTracksNotice = `<div className="session-mytracks-notracks">
<p className="notice">
You have not set up any inputs for your instrument or vocals.
If you want to hear yourself play through the JamKazam app,
and let the app mix your live playing with JamTracks, or with other musicians in online sessions,
<a href="#" className="open-ftue-no-tracks">click here now.</a>
</p>
</div>`
onInputsChanged: (sessionMixers) ->
`<div className="session-my-tracks">
<h2>my live tracks</h2>
<SessionTrackSettingsBtn />
<div className="session-tracks-scroller">
{noTracksNotice}
</div>
</div>`
})
session = sessionMixers.session
mixers = sessionMixers.mixers
tracks = []
if session.inSession()
participant = session.getParticipant(@app.clientId)
name = participant.user.name;
for track in participant.tracks
# try to find mixer info for this track
mixerFinder = [participant.client_id, track, true]
mixerData = mixers.findMixerForTrack(participant.client_id, track, true)
mixerData.mixerFinder = mixerFinder # so that other callers can re-find their mixer data
# todo: sessionModel.setAudioEstablished
instrumentIcon = context.JK.getInstrumentIcon45(track.instrument_id);
photoUrl = context.JK.resolveAvatarUrl(participant.user.photo_url);
tracks.push({track: track, mixerFinder: mixerFinder, mixers: mixerData, name: name, instrumentIcon: instrumentIcon, photoUrl: photoUrl})
# TODO: also deal with chat
this.setState(tracks: tracks, session:session)
render: () ->
content = null
tracks = []
if this.state.tracks.length > 0
for track in this.state.tracks
tracks.push(`<SessionMyTrack key={track.track.client_track_id} {...track} />`)
else if this.state.session? && this.state.session.inSession()
content = `<div className="session-mytracks-notracks">
<p className="notice">
You have not set up any inputs for your instrument or vocals.
If you want to hear yourself play through the JamKazam app,
and let the app mix your live playing with JamTracks, or with other musicians in online sessions,
<a href="#" className="open-ftue-no-tracks">click here now.</a>
</p>
</div>`
`<div className="session-my-tracks">
<h2>my live tracks</h2>
<SessionTrackSettingsBtn />
<div className="session-tracks-scroller">
{content}
<ReactCSSTransitionGroup transitionName="session-track-list" transitionAppear={true}>
{tracks}
</ReactCSSTransitionGroup>
</div>
</div>`
getInitialState:() ->
{tracks:[], session: null}
onAppInit: (app) ->
@app = app
})

View File

@ -1,5 +1,7 @@
context = window
SessionActions = @SessionActions
@SessionScreen = React.createClass({
mixins: [Reflux.listenTo(@AppStore,"onAppInit")]
@ -16,7 +18,7 @@ context = window
<SessionResyncBtn />
<SessionLeaveBtn />
</div>
<div className="tracks">
<div className="tracks" id="new-tracks">
<SessionMyTracks />
<SessionOtherTracks />
<SessionMediaTracks />
@ -27,12 +29,14 @@ context = window
componentDidMount: () ->
@logger = context.JK.logger
beforeShow: () ->
beforeShow: (data) =>
@logger.debug("session beforeShow")
afterShow: () ->
afterShow: (data) ->
@logger.debug("session afterShow")
SessionActions.joinSession.trigger(data.id)
beforeHide: () ->
@logger.debug("session beforeHide")
@ -44,7 +48,6 @@ context = window
onAppInit: (@app) ->
@logger.debug("oh hai")
screenBindings = {
'beforeShow': @beforeShow,
'afterShow': @afterShow,
@ -54,4 +57,5 @@ context = window
};
@app.bindScreen('session2', screenBindings);
})

View File

@ -0,0 +1,49 @@
context = window
logger = context.JK.logger
@SessionTrackGain = React.createClass({
getInitialState: () ->
{
mixers: this.props.mixers,
behaviors: this.props.behaviors || {}
}
faderChanged: (e, data) ->
$target = $(this)
groupId = $target.data('groupId')
mixerIds = [this.state.mixers.mixer.id]
MixerActions.faderChanged(data, mixerIds, groupId)
render: () ->
`<div className="track-gain">
<div className="fader vertical" data-control="fader" data-fader-id={this.state.mixers.mixer.id} data-orientation="vertical">
<div className="handle" data-control="fader-handle">
<img src="/assets/content/slider_gain_vertical.png" width="28" height="11"/>
</div>
</div>
</div>`
componentDidMount: () ->
$root = jQuery(this.getDOMNode())
if !$root.is('.track-gain')
logger.error("unknown root node")
$fader = $root.attr('data-mixer-id', this.state.mixers.mixer.id).data('groupId', this.state.mixers.mixer.groupId).data('mixer', this.state.mixers.mixer).data('opposite-mixer', this.state.mixers.oppositeMixer)
if this.state.behaviors.mediaControlsDisabled
$fader.data('media-controls-disabled', true).data('media-track-opener', this.state.behaviors.mediaTrackOpener) # this we be applied later to the fader handle $element
$fader.data('showHelpAboutMediaMixers', this.state.behaviors.showHelpAboutMediaMixers)
context.JK.FaderHelpers.renderFader2($fader, {faderType: 'vertical'});
# Initialize gain position
MixerActions.initGain(this.state.mixers.mixer)
# watch for fader change events
$fader.on('fader_change', this.faderChanged);
})

View File

@ -0,0 +1,51 @@
context = window
logger = context.JK.logger
@SessionTrackPan = React.createClass({
getInitialState: () ->
{
mixers: this.props.mixers,
behaviors: this.props.behaviors || {}
}
panChanged: (e, data) ->
$target = $(this)
groupId = $target.data('groupId')
mixerIds = [this.state.mixers.mixer.id]
MixerActions.panChanged(data, mixerIds, groupId)
render: () ->
`<div className="track-pan">
<div className="left-label">Left</div>
<div className="right-label">Right</div>
<div className="fader horizontal" data-control="fader" data-fader-id={this.state.mixers.mixer.id} data-orientation="horizontal">
<div className="handle" data-control="fader-handle">
<div className="floater"></div>
<img src="/assets/content/slider_gain_horiz.png" width="11" height="28"/>
</div>
</div>
</div>`
componentDidMount: () ->
$root = jQuery(this.getDOMNode())
if !$root.is('.track-pan')
logger.error("unknown root node")
$fader = $root.attr('data-mixer-id', this.state.mixers.mixer.id).data('groupId', this.state.mixers.mixer.groupId).data('mixer', this.state.mixers.mixer).data('opposite-mixer', this.state.mixers.oppositeMixer)
if this.state.behaviors.mediaControlsDisabled
$fader.data('media-controls-disabled', true).data('media-track-opener', this.state.behaviors.mediaTrackOpener) # this we be applied later to the fader handle $element
$fader.data('showHelpAboutMediaMixers', this.state.behaviors.showHelpAboutMediaMixers)
context.JK.FaderHelpers.renderFader2($fader, {faderType: 'horizontal', snap:true}, context.JK.PanHelpers.convertPercentToPan)
# Initialize gain position
MixerActions.initPan(this.state.mixers.mixer)
# watch for fader change events
$fader.on('fader_change', this.panChanged)
})

View File

@ -0,0 +1,37 @@
context = window
MixerActions = @MixerActions
@SessionTrackPanHover = React.createClass({
mixins: [Reflux.listenTo(@SessionMyTracksStore, "onInputsChanged")]
onInputsChanged: (sessionMixers) ->
mixers = sessionMixers.mixers
newMixers = mixers.findMixerForTrack.apply(mixers, this.props.mixerFinder)
this.setState({mixers: newMixers})
getInitialState: () ->
{mixers: this.props.mixers}
render: () ->
`<div className="track-pan-hover">
<div className="textual-help">
<p>
Use this slider to pan the audio of this track left or right in your personal mix.
This will not pan audio for other musicians in the session.
To pan audio in the master mix for recordings and broadcasts, use the Mixer button in the toolbar.
</p>
</div>
<div className="session-pan">
<SessionTrackPan mixers={this.state.mixers} />
</div>
</div>`
})

View File

@ -1,9 +1,22 @@
context = window
logger = context.JK.logger
@SessionTrackSettingsBtn = React.createClass({
mixins: [Reflux.listenTo(@AppStore,"onAppInit")]
onConfigureSettings: (e) ->
e.preventDefault();
@app.layout.showDialog('configure-tracks')
onAppInit: (app) ->
@app = app
render: () ->
`<a className="session-track-settings">
Settings
</a>`
`<div className="session-track-settings" onClick={this.onConfigureSettings}>
<img src="/assets/content/icon_settings_lg.png" width="18" height="18" />
<span>Settings</span>
</div>`
})

View File

@ -0,0 +1,44 @@
context = window
@SessionTrackVU = React.createClass({
getInitialState: () ->
{mixers: this.props.mixers}
render: () ->
lights = []
redSwitch = Math.round(this.props.lightCount * 0.66);
lightClass = 'vu-red-off'
if this.props.orientation == 'horizontal'
for i in [0..this.props.lightCount-1]
lightClass = if i >= redSwitch then 'vu-red-off' else 'vu-green-off'
lightClasses = React.addons.classSet('vulight', 'vu' + i, lightClass)
lights.push(`<td width={this.props.lightWidth} height={this.props.lightHeight} className={lightClasses}></td>`)
tableClasses = React.addons.classSet('vu', 'horizontal', this.props.side + '-' + this.state.mixers.mixer.mixerId)
`<table className={tableClasses} data-light-count={this.props.lightCount}>
<tr>
{lights}
</tr>
</table>`
else
for i in [0..this.props.lightCount-1].reverse()
lightClass = if (i >= redSwitch) then "vu-red-off" else "vu-green-off"
lightClasses = React.addons.classSet('vulight', 'vu' + i, lightClass)
lights.push(`<tr><td width={this.props.lightWidth} height={this.props.lightHeight} className={lightClasses}></td></tr>`)
tableClasses = React.addons.classSet('vu', 'vertical', this.props.side + '-' + this.state.mixers.mixer.mixerId)
`<table className={tableClasses} data-light-count={this.props.lightCount}>
{lights}
</table>`
})

View File

@ -0,0 +1,91 @@
context = window
MixerActions = @MixerActions
@SessionTrackVolumeHover = React.createClass({
mixins: [Reflux.listenTo(@SessionMyTracksStore,"onInputsChanged")]
onInputsChanged: (sessionMixers) ->
mixers = sessionMixers.mixers
newMixers = mixers.findMixerForTrack.apply(mixers, this.props.mixerFinder)
this.setState({mixers: newMixers})
getInitialState: () ->
{mixers: this.props.mixers}
handleMute: (e) ->
e.preventDefault()
muting = $(e.currentTarget).is('.enabled')
MixerActions.mute([this.state.mixers.mixer, this.state.mixers.oppositeMixer], muting)
handleMuteCheckbox: (e) ->
muting = $(e.target).is(':checked')
MixerActions.mute([this.state.mixers.mixer, this.state.mixers.oppositeMixer], muting)
render: () ->
muteMixer = this.state.mixers.muteMixer
classes = React.addons.classSet({
'track-icon-mute': true
'enabled' : !muteMixer.mute
'muted' : muteMixer.mute
})
`<div className="track-volume-hover">
<div className="session-track track">
<div className="track-vu-left">
<SessionTrackVU orientation="vertical" lightCount={13} lightWidth={3} lightHeight={17} side="vul" mixers={this.state.mixers} />
</div>
<div className="track-vu-right">
<SessionTrackVU orientation="vertical" lightCount={13} lightWidth={3} lightHeight={17} side="vur" mixers={this.state.mixers} />
</div>
<div className="track-label">
<div>Volume</div>
<div>{this.state.mixers.mixer.volume_left}dB</div>
</div>
<SessionTrackGain mixers={this.state.mixers} />
<div className={classes} data-control="mute" data-mixer-id={this.state.mixers.muteMixer.id} onClick={this.handleMute}/>
<input type="checkbox" name="mute"/>
<label for="mute">Mute</label>
</div>
<div className="textual-help">
<p>Use this slider to control the volume of this track in your personal mix.</p>
<p>This will not affect the volume of this track for other musicians in the session.</p>
<p>To adjust master levels for all musicians for recordings and broadcasts, use Mixer button in the toolbar.</p>
</div>
</div>`
componentDidMount: () ->
$root = jQuery(this.getDOMNode())
# initialize icheck
$checkbox = $root.find('input')
context.JK.checkbox($checkbox)
$checkbox.on('ifChanged', this.handleMuteCheckbox);
if this.state.mixers.muteMixer.mute
$checkbox.iCheck('check').attr('checked', true)
else
$checkbox.iCheck('uncheck').attr('checked', false)
componentWillUpdate: (nextProps, nextState) ->
$root = jQuery(this.getDOMNode())
# re-initialize icheck
$checkbox = $root.find('input')
if nextState.mixers.muteMixer.mute
$checkbox.iCheck('check').attr('checked', true)
else
$checkbox.iCheck('uncheck').attr('checked', false)
})

View File

@ -0,0 +1,19 @@
context = window
@TestComponent = React.createClass({
getInitialState: () ->
{something: 1}
tick: () ->
console.log("tick")
this.setState({something: this.state.something + 1})
componentDidMount: () ->
console.log("here")
setInterval(@tick, 1000)
render: () ->
console.log("render")
`<div>{this.state.something}</div>`
})

View File

@ -3,5 +3,3 @@ context = window
@AppActions = Reflux.createActions({
appInit: {}
})
context.JK.Actions.App = @AppActions

View File

@ -1,6 +1,6 @@
context = window
BroadcastActions = Reflux.createActions({
@BroadcastActions = Reflux.createActions({
load: {asyncResult: true},
hide: {}
})

View File

@ -0,0 +1,10 @@
context = window
@MixerActions = Reflux.createActions({
mute: {}
faderChanged: {}
initGain: {}
panChanged: {}
initPan: {}
mixersChanged: {}
})

View File

@ -0,0 +1,8 @@
context = window
@SessionActions = Reflux.createActions({
joinSession: {}
leaveSession: {}
resyncServer: {asyncResult: true}
myTracksChanged: {}
})

View File

@ -0,0 +1,5 @@
context = window
@SessionMyTracksActions = Reflux.createActions({
})

View File

@ -0,0 +1,461 @@
context = window
ChannelGroupIds = context.JK.ChannelGroupIds
MIX_MODES = context.JK.MIX_MODES;
@MixerHelper = class MixerHelper
constructor: (@session, @masterMixers, @personalMixers, @mixMode) ->
@mixersByResourceId = {}
@mixersByTrackId = {}
@allMixers = {}
@currentMixerRangeMin = null
@currentMixerRangeMax = null
@mediaTrackGroups = [ChannelGroupIds.MediaTrackGroup, ChannelGroupIds.JamTrackGroup,
ChannelGroupIds.MetronomeGroup]
@muteBothMasterAndPersonalGroups = [ChannelGroupIds.AudioInputMusicGroup, ChannelGroupIds.MediaTrackGroup,
ChannelGroupIds.JamTrackGroup, ChannelGroupIds.MetronomeGroup]
@organize()
updateMixers: (type, text, @masterMixers, @personalMixers) ->
@organize()
if @session.inSession() && text == 'RebuildAudioIoControl'
SessionActions.myTracksChanged.trigger()
organize: () ->
for masterMixer, i in @masterMixers
@allMixers['M' + masterMixer.id] = masterMixer; # populate allMixers by mixer.id
# populate mixer pair
mixerPair = {}
@mixersByResourceId[masterMixer.rid] = mixerPair
@mixersByTrackId[masterMixer.id] = mixerPair
mixerPair.master = masterMixer;
for personalMixer, i in @personalMixers
@allMixers['P' + personalMixer.id] = personalMixer
# populate other side of mixer pair
mixerPair = @mixersByResourceId[personalMixer.rid]
unless mixerPair
if personalMixer.group_id != ChannelGroupIds.MonitorGroup
logger.warn("there is no master version of ", personalMixer)
mixerPair = {}
@mixersByResourceId[personalMixer.rid] = mixerPair
@mixersByTrackId[personalMixer.id] = mixerPair;
mixerPair.personal = personalMixer;
@groupTypes()
groupTypes: () ->
localMediaMixers = @mixersForGroupIds(@mediaTrackGroups, MIX_MODES.MASTER)
peerLocalMediaMixers = @mixersForGroupId(ChannelGroupIds.PeerMediaTrackGroup, MIX_MODES.MASTER)
logger.debug("localMediaMixers", localMediaMixers)
#logger.debug("peerLocalMediaMixers", peerLocalMediaMixers)
# get the server data regarding various media tracks
recordedBackingTracks = @session.recordedBackingTracks()
backingTracks = @session.backingTracks()
recordedJamTracks = @session.recordedJamTracks()
jamTracks = @session.jamTracks()
###
with mixer info, we use these to decide what kind of tracks are open in the backend
each mixer has a media_type field, which describes the type of media track it is.
* JamTrack
* BackingTrack
* RecordingTrack
* MetronomeTrack
* "" - adhoc track (not supported visually)
it is supposed to be the case that there are only one type of track open at a time, however, that's a business policy/logic
constraint; and may be buggy. **So, we should render whatever we have, so that it's obvious what's really going on.**
so, let's group up all mixers by type, and then ask them to be rendered
###
recordingTrackMixers = []
backingTrackMixers = []
jamTrackMixers = []
metronomeTrackMixers = []
adhocTrackMixers = []
groupByType = (mixers, isLocalMixer) ->
for mixer in mixers
mediaType = mixer.media_type
groupId = mixer.group_id
if mediaType == 'MetronomeTrack' || groupId == ChannelGroupIds.MetronomeGroup
# Metronomes come across with a blank media type, so check group_id:
metronomeTrackMixers.push(mixer)
else if mediaType == null || mediaType == "" || mediaType == 'RecordingTrack'
# additional check; if we can match an id in backing tracks or recorded backing track,
# we need to remove it from the recorded track set, but move it to the backing track set
isJamTrack = false;
if jamTracks
# check if the ID matches that of an open jam track
for jamTrack in jamTracks
if mixer.id == jamTrack.id
isJamTrack = true;
break
if !isJamTrack && recordedJamTracks
# then check if the ID matches that of a open, recorded jam track
for recordedJamTrack in recordedJamTracks
if mixer.id == recordedJamTrack.id
isJamTrack = true
break
if isJamTrack
jamTrackMixers.push(mixer)
else
isBackingTrack = false
if recordedBackingTracks
for recordedBackingTrack in recordedBackingTracks
if mixer.id == 'L' + recordedBackingTrack.client_track_id
isBackingTrack = true
break
if backingTracks
for backingTrack in backingTracks
if mixer.id == 'L' + backingTrack.client_track_id
isBackingTrack = true
break
if isBackingTrack
backingTrackMixers.push(mixer)
else
# couldn't resolve this as a JamTrack or Backing track, must be a normal recorded file
recordingTrackMixers.push(mixer)
else if mediaType == 'PeerMediaTrack' || mediaType == 'BackingTrack'
backingTrackMixers.push(mixer)
else if mediaType == 'JamTrack'
jamTrackMixers.push(mixer);
else if mediaType == null || mediaType == "" || mediaType == 'RecordingTrack'
# mediaType == null is for backwards compat with older clients. Can be removed soon
recordingTrackMixers.push(mixer)
else
logger.warn("Unknown track type: " + mediaType)
adhocTrackMixers.push(mixer)
groupByType(localMediaMixers, true);
groupByType(peerLocalMediaMixers, false);
###
if recordingTrackMixers.length > 0
renderRecordingTracks(recordingTrackMixers)
if backingTrackMixers.length > 0
renderBackingTracks(backingTrackMixers)
if jamTrackMixers.length > 0
renderJamTracks(jamTrackMixers);
if metronomeTrackMixers.length > 0 && @session.jamTracks() == null && @session.recordedJamTracks() == null
renderMetronomeTracks(metronomeTrackMixers);
checkMetronomeTransition();
###
if adhocTrackMixers.length > 0
logger.warn("some tracks are open that we don't know how to show")
mixersForGroupIds: (groupIds, mixMode) ->
foundMixers = []
mixers = if mixMode == MIX_MODES.MASTER then @masterMixers else @personalMixers;
for mixer in mixers
groupIdLen = groupIds.length
for i in groupIdLen
if mixer.group_id == groupIds[i]
foundMixers.push(mixer)
foundMixers
mixersForGroupId: (groupId, mixMode) ->
foundMixers = [];
mixers = if mixMode == MIX_MODES.MASTER then @masterMixers else @personalMixers;
for mixer in mixers
if mixer.group_id == groupId
foundMixers.push(mixer)
foundMixers
getMixer: (mixerId, mode) ->
mode = @mixMode unless mode?
@allMixers[(if mode then 'M' else 'P') + mixerId]
getMixerByTrackId: (trackId, mode) ->
mixerPair = @mixersByTrackId[trackId]
return null unless mixerPair
if mode == undefined
return mixerPair
else
if mode == MIX_MODES.MASTER
return mixerPair.master
else
return mixerPair.personal
groupedMixersForClientId: (clientId, groupIds, usedMixers, mixMode) ->
foundMixers = {};
mixers = if mixMode == MIX_MODES.MASTER then @masterMixers else @personalMixers;
for mixer in mixers
if mixer.client_id == clientId
for groupId in groupIds
if mixer.group_id == groupId
if (mixer.groupId != ChannelGroupIds.UserMusicInputGroup) && !(mixer.id in usedMixers)
mixers = foundMixers[mixer.group_id]
if !mixers
mixers = []
foundMixers[mixer.group_id] = mixers
mixers.push(mixer)
foundMixers
findMixerForTrack: (client_id, track, myTrack) ->
mixer = null # what is the best mixer for this track/client ID?
oppositeMixer = null # what is the corresponding mixer in the opposite mode?
vuMixer = null
muteMixer = null
if myTrack
# when it's your track, look it up by the backend resource ID
mixer = @getMixerByTrackId(track.client_track_id, @mixMode)
vuMixer = mixer
muteMixer = mixer
# sanity checks
if mixer && mixer.group_id != ChannelGroupIds.AudioInputMusicGroup
logger.error("found local mixer that was not of groupID: AudioInputMusicGroup", mixer)
if mixer
# find the matching AudioInputMusicGroup for the opposite mode
oppositeMixer = @getMixerByTrackId(track.client_track_id, !@mixMode)
if @mixMode == MIX_MODES.PERSONAL
muteMixer = oppositeMixer; # make the master mixer the mute mixer
# sanity checks
if !oppositeMixer
logger.error("unable to find opposite mixer for local mixer", mixer)
else if oppositeMixer.group_id != ChannelGroupIds.AudioInputMusicGroup
logger.error("found local mixer in opposite mode that was not of groupID: AudioInputMusicGroup", mixer, oppositeMixer)
else
logger.debug("local track is not present: ", track, mixer)
else
switch @mixMode
when MIX_MODES.MASTER
# when it's a remote track and in master mode, we should find the PeerAudioInputMusicGroup
mixer = @getMixerByTrackId(track.client_track_id, MIX_MODES.MASTER)
# sanity check
if mixer && mixer.group_id != ChannelGroupIds.PeerAudioInputMusicGroup
logger.error("found remote mixer that was not of groupID: PeerAudioInputMusicGroup", mixer)
vuMixer = mixer
muteMixer = mixer
if mixer
# we should be able to find a UserMusicInputGroup for this clientId in personal mode
oppositeMixers = @groupedMixersForClientId(client_id, [ ChannelGroupIds.UserMusicInputGroup], {}, MIX_MODES.PERSONAL)
if oppositeMixers[ChannelGroupIds.UserMusicInputGroup]
oppositeMixer = oppositeMixers[ChannelGroupIds.UserMusicInputGroup][0]
if !oppositeMixer
logger.error("unable to find UserMusicInputGroup corresponding to PeerAudioInputMusicGroup mixer", mixer )
when MIX_MODES.PERSONAL
mixers = @groupedMixersForClientId(client_id, [ ChannelGroupIds.UserMusicInputGroup], {}, MIX_MODES.PERSONAL)
if mixers[ChannelGroupIds.UserMusicInputGroup]
mixer = mixers[ChannelGroupIds.UserMusicInputGroup][0]
vuMixer = mixer
muteMixer = mixer
if mixer
# now grab the PeerAudioInputMusicGroup in master mode to satisfy the 'opposite' mixer
oppositeMixer = @getMixerByTrackId(track.client_track_id, MIX_MODES.MASTER)
if !oppositeMixer
logger.debug("unable to find a PeerAudioInputMusicGroup master mixer matching a UserMusicInput", track.client_track_id, @mixersByTrackId)
else if oppositeMixer.group_id != ChannelGroupIds.PeerAudioInputMusicGroup
logger.error("found remote mixer that was not of groupID: PeerAudioInputMusicGroup", mixer)
vuMixer = oppositeMixer; # for personal mode, use the PeerAudioInputMusicGroup's VUs
{
mixer: mixer,
oppositeMixer: oppositeMixer,
vuMixer: vuMixer,
muteMixer: muteMixer
}
mute: (mixerId, mode, muting) ->
mode = @mixMode unless mode?
@fillTrackVolumeObject(mixerId, mode)
context.trackVolumeObject.mute = muting
context.jamClient.SessionSetControlState(mixerId, mode)
# keep state of mixer in sync with backend
mixer = @getMixer(mixerId, mode)
mixer.mute = muting
faderChanged: (data, mixerIds, groupId) ->
# media tracks are the only controls that sometimes set two mixers right now
hasMasterAndPersonalControls = mixerIds.length == 2
for mixerId, i in mixerIds
broadcast = !(data.dragging) # If fader is still dragging, don't broadcast
mode = undefined
if hasMasterAndPersonalControls
mode = if i == 0 then MIX_MODES.MASTER else MIX_MODES.PERSONAL
mixer = @fillTrackVolumeObject(mixerId, mode, broadcast)
@setMixerVolume(mixer, data.percentage)
# keep state of mixer in sync with backend
mixer = @getMixer(mixer.id, mixer.mode)
mixer.volume_left = context.trackVolumeObject.volL
if groupId == ChannelGroupIds.UserMusicInputGroup
# there may be other mixers with this same ID in the case of a Peer Music Stream, so update them as well
context.JK.FaderHelpers.setFaderValue(mixerId, data.percentage)
initGain: (mixer) ->
gainPercent = context.JK.FaderHelpers.convertAudioTaperToPercent(mixer.volume_left)
context.JK.FaderHelpers.setFaderValue(mixer.id, gainPercent)
context.JK.FaderHelpers.showFader(mixer.id)
panChanged: (data, mixerIds, groupId) ->
# media tracks are the only controls that sometimes set two mixers right now
for mixerId, i in mixerIds
broadcast = !(data.dragging) # If fader is still dragging, don't broadcast
mode = undefined
mixer = @fillTrackVolumeObject(mixerId, mode, broadcast)
@setMixerPan(mixer, data.percentage)
# keep state of mixer in sync with backend
mixer = @getMixer(mixer.id, mixer.mode)
mixer.pan = context.trackVolumeObject.pan
initPan: (mixer) ->
panPercent= context.JK.PanHelpers.convertPanToPercent(mixer.pan)
context.JK.FaderHelpers.setFaderValue(mixer.id, panPercent, mixer.pan)
context.JK.FaderHelpers.showFader(mixer.id)
setMixerPan: (mixer, panPercent) ->
context.trackVolumeObject.pan = context.JK.PanHelpers.convertPercentToPan(panPercent);
context.jamClient.SessionSetControlState(mixer.id, mixer.mode);
setMixerVolume: (mixer, volumePercent) ->
###
// The context.trackVolumeObject has been filled with the mixer values
// that go with mixerId, and the range of that mixer
// has been set in currentMixerRangeMin-Max.
// All that needs doing is to translate the incoming percent
// into the real value ont the sliders range. Set Left/Right
// volumes on trackVolumeObject, and call SetControlState to stick.
###
context.trackVolumeObject.volL = context.JK.FaderHelpers.convertPercentToAudioTaper(volumePercent);
context.trackVolumeObject.volR = context.JK.FaderHelpers.convertPercentToAudioTaper(volumePercent);
context.jamClient.SessionSetControlState(mixer.id, mixer.mode);
percentFromMixerValue: (min, max, value) ->
try
range = Math.abs(max - min)
magnitude = value - min
percent = Math.round(100*(magnitude/range))
percent
catch err
0
percentToMixerValue:(min, max, percent) ->
range = Math.abs(max - min);
multiplier = percent/100; # Change 85 into 0.85
value = min + (multiplier * range);
# Protect against percents < 0 and > 100
if value < min
value = min;
if value > max
value = max;
return value;
fillTrackVolumeObject: (mixerId, mode, broadcast) ->
_broadcast = true
if broadcast?
_broadcast = broadcast
mixer = @getMixer(mixerId, mode)
context.trackVolumeObject.clientID = mixer.client_id
context.trackVolumeObject.broadcast = _broadcast
context.trackVolumeObject.master = mixer.master
context.trackVolumeObject.monitor = mixer.monitor
context.trackVolumeObject.mute = mixer.mute
context.trackVolumeObject.name = mixer.name
context.trackVolumeObject.record = mixer.record
context.trackVolumeObject.volL = mixer.volume_left
context.trackVolumeObject.pan = mixer.pan
# today we treat all tracks as mono, but this is required to make a stereo track happy
# context.trackVolumeObject.volR = mixer.volume_right;
context.trackVolumeObject.volR = mixer.volume_left;
context.trackVolumeObject.loop = mixer.loop;
# trackVolumeObject doesn't have a place for range min/max
@currentMixerRangeMin = mixer.range_low;
@currentMixerRangeMax = mixer.range_high;
mixer
updateVU: (mixerId, value, isClipping) ->
selector = null
pureMixerId = mixerId.replace("_vul", "")
pureMixerId = pureMixerId.replace("_vur", "")
mixer = @getMixer(pureMixerId, @mixMode)
unless mixer
# try again, in the opposite mode (awful that this is necessary)
mixer = @getMixer(pureMixerId, !@mixMode)
if mixer
if mixer.stereo # // stereo track
context.JK.VuHelpers.updateVU2('vul', mixer, value)
else
if mixerId.substr(-4) == "_vul"
# Do the left
context.JK.VuHelpers.updateVU2('vul', mixer, value)
# Do the right
context.JK.VuHelpers.updateVU2('vur', mixer, value)

View File

@ -0,0 +1,80 @@
context = window
@SessionHelper = class SessionHelper
constructor: (app, session) ->
@app = app
@session = session
inSession: () ->
@session?
participants: () ->
if @session
return @session.participants
else
[]
# if any participant has the metronome open, then we say this session has the metronome open
isMetronomeOpen: () ->
metronomeOpen = false;
for participant in @participants()
if participant.metronome_open
metronomeOpen = true
break
metronomeOpen
isPlayingRecording: () ->
# this is the server's state; there is no guarantee that the local tracks
# requested from the backend will have corresponding track information
return !!(@session && @session.claimed_recording);
recordedTracks: () ->
if @session && @session.claimed_recording
@session.claimed_recording.recording.recorded_tracks
else
null
recordedBackingTracks: () ->
if @session && @session.claimed_recording
@session.claimed_recording.recording.recorded_backing_tracks
else
null
backingTracks: () ->
backingTracks = []
# this may be wrong if we loosen the idea that only one person can have a backing track open.
# but for now, the 1st person we find with a backing track open is all there is to find...
for participant in @participants()
if participant.backing_tracks.length > 0
backingTracks = participant.backing_tracks
break
backingTracks
jamTracks: () ->
if @session && @session.jam_track
@session.jam_track.tracks.filter((track)->
track.track_type == 'Track'
)
else
null
recordedJamTracks:() ->
if @session && @session.claimed_recording
@session.claimed_recording.recording.recorded_jam_track_tracks
else
null
getParticipant: (clientId) ->
found = null
for participant in @participants()
if participant.client_id == clientId
found = participant
break
logger.warn('unable to find participant with clientId: ' + clientId) unless found
found

View File

@ -10,6 +10,3 @@ logger = context.JK.logger
@trigger(app)
}
)
context.JK.Stores.App = @AppStore

View File

@ -0,0 +1,109 @@
context = window
logger = context.JK.logger
MIX_MODES = context.JK.MIX_MODES;
@MixerStore = Reflux.createStore(
{
init: ->
# Register with the app store to get @app
this.listenTo(context.AppStore, this.onAppInit);
this.listenTo(context.SessionStore, this.onSessionChange)
this.listenTo(context.MixerActions.mute, this.onMute)
this.listenTo(context.MixerActions.faderChanged, this.onFaderChanged)
this.listenTo(context.MixerActions.initGain, this.onInitGain)
this.listenTo(context.MixerActions.initPan, this.onInitPan)
this.listenTo(context.MixerActions.panChanged, this.onPanChanged)
this.listenTo(context.MixerActions.mixersChanged, this.onMixersChanged)
context.JK.HandleVolumeChangeCallback2 = @handleVolumeChangeCallback
context.JK.HandleMetronomeCallback2 = @handleMetronomeCallback
context.JK.HandleBridgeCallback2 = @handleBridgeCallback
context.JK.HandleBackingTrackSelectedCallback2 = @handleBackingTrackSelectedCallback
handleVolumeChangeCallback: () ->
logger.debug("volume change")
handleMetronomeCallback: () ->
logger.debug("metronome callback")
handleBridgeCallback: (vuData) ->
eventName = null
mixerId = null
value = null
vuInfo = null
for vuInfo in vuData
eventName = vuInfo[0];
vuVal = 0.0;
if eventName == "vu"
mixerId = vuInfo[1];
leftValue = vuInfo[2];
leftClipping = vuInfo[3];
rightValue = vuInfo[4];
rightClipping = vuInfo[5];
# TODO - no guarantee range will be -80 to 20. Get from the
# GetControlState for this mixer which returns min/max
# value is a DB value from -80 to 20. Convert to float from 0.0-1.0
@mixers.updateVU(mixerId + "_vul", (leftValue + 80) / 80, leftClipping)
@mixers.updateVU(mixerId + "_vur", (rightValue + 80) / 80, rightClipping)
handleBackingTrackSelectedCallback: () ->
logger.debug("backing track selected")
onAppInit: (@app) ->
@gearUtils = context.JK.GearUtilsInstance
@sessionUtils = context.JK.SessionUtils
onSessionChange: (session) ->
@session = session
masterMixers = context.jamClient.SessionGetAllControlState(true);
personalMixers = context.jamClient.SessionGetAllControlState(false);
# TODO: grab correct mix mode , line 870 sessionModel.js
@mixers = new context.MixerHelper(session, masterMixers, personalMixers, MIX_MODES.PERSONAL)
this.trigger({session: @session, mixers: @mixers})
onMute: (mixers, muting) ->
for mixer in mixers
@mixers.mute(mixer.id, mixer.mode, muting);
# simulate a state change to cause a UI redraw
this.trigger({session: @session, mixers: @mixers})
onFaderChanged: (data, mixerIds, groupId) ->
@mixers.faderChanged(data, mixerIds, groupId)
this.trigger({session: @session, mixers: @mixers})
onPanChanged: (data, mixerIds, groupId) ->
@mixers.panChanged(data, mixerIds, groupId)
this.trigger({session: @session, mixers: @mixers})
onInitGain: (mixer) ->
@mixers.initGain(mixer)
onInitPan: (mixer) ->
@mixers.initPan(mixer)
onMixersChanged: (type, text) ->
if @mixers
masterMixers = context.jamClient.SessionGetAllControlState(true);
personalMixers = context.jamClient.SessionGetAllControlState(false);
# TODO: grab correct mix mode , line 870 sessionModel.js
@mixers.updateMixers(type, text, masterMixers, personalMixers)
this.trigger({session: @session, mixers: @mixers})
}
)

View File

@ -0,0 +1,21 @@
$ = jQuery
context = window
logger = context.JK.logger
@SessionMyTracksStore = Reflux.createStore(
{
init: ->
# Register with the app store to get @app
this.listenTo(context.AppStore, this.onAppInit);
this.listenTo(context.MixerStore, this.onSessionMixerChange)
onAppInit: (@app) ->
@gearUtils = context.JK.GearUtilsInstance
@sessionUtils = context.JK.SessionUtils
onSessionMixerChange: (sessionMixers) ->
this.trigger(sessionMixers)
}
)

View File

@ -0,0 +1,423 @@
$ = jQuery
context = window
logger = context.JK.logger
rest = context.JK.Rest()
EVENTS = context.JK.EVENTS;
SessionActions = @SessionActions
@SessionStore = Reflux.createStore(
{
listenables: SessionActions
userTracks: null # comes from the backend
currentSessionId: null
currentSession: null
currentOrLastSession: null
startTime: null
currentParticipants: {}
participantsEverSeen: {}
users: {} # // User info for session participants
requestingSessionRefresh: false
pendingSessionRefresh: false
sessionPageEnterTimeout: null
sessionPageEnterDeferred: null
gearUtils: null
sessionUtils: null
init: ->
# Register with the app store to get @app
this.listenTo(context.AppStore, this.onAppInit);
onAppInit: (@app) ->
@gearUtils = context.JK.GearUtilsInstance
@sessionUtils = context.JK.SessionUtils
onJoinSession: (sessionId) ->
# initialize webcamViewer
if gon.global.video_available && gon.global.video_available != "none"
webcamViewer.beforeShow()
# double-check that we are connected to the server via websocket
return unless @ensureConnected()
# update the session data to be empty
@updateCurrentSession(null)
# start setting data for this new session
@currentSessionId = sessionId
@startTime = new Date().getTime()
# let's find out the public/private nature of this session,
# so that we can decide whether we need to validate the audio profile more aggressively
rest.getSessionHistory(@currentSessionId)
.done((musicSession)=>
musicianAccessOnJoin = musicSession.musician_access
shouldVerifyNetwork = musicSession.musician_access;
@gearUtils.guardAgainstInvalidConfiguration(@app, shouldVerifyNetwork).fail(() =>
SessionActions.leaveSession.trigger({location: '/client#/home'})
).done(() =>
result = @sessionUtils.SessionPageEnter();
@gearUtils.guardAgainstActiveProfileMissing(@app, result)
.fail((data) =>
leaveBehavior = {}
if data && data.reason == 'handled'
if data.nav == 'BACK'
leaveBehavior.location = -1
else
leaveBehavior.location = data.nav
else
leaveBehavior.location = '/client#/home';
SessionActions.leaveSession.trigger(leaveBehavior)
).done(() =>
@waitForSessionPageEnterDone()
.done((userTracks) =>
@userTracks = userTracks
@ensureAppropriateProfile(musicianAccessOnJoin)
.done(() =>
logger.debug("user has passed all session guards")
@joinSession()
)
.fail((result) =>
unless result.controlled_location
SessionActions.leaveSession.trigger({location: "/client#/home"})
)
).fail((data) =>
if data == "timeout"
context.JK.alertSupportedNeeded('The audio system has not reported your configured tracks in a timely fashion.')
else if data == 'session_over'
# do nothing; session ended before we got the user track info. just bail
logger.debug("session is over; bailing")
else
context.JK.alertSupportedNeeded('Unable to determine configured tracks due to reason: ' + data)
SessionActions.leaveSession.trigger({location: '/client#/home'})
)
)
)
)
.fail(() =>
logger.error("unable to fetch session history")
)
waitForSessionPageEnterDone: () ->
@sessionPageEnterDeferred = $.Deferred()
# see if we already have tracks; if so, we need to run with these
inputTracks = context.JK.TrackHelpers.getUserTracks(context.jamClient)
logger.debug("isNoInputProfile", @gearUtils.isNoInputProfile())
if inputTracks.length > 0 || @gearUtils.isNoInputProfile()
logger.debug("on page enter, tracks are already available")
@sessionPageEnterDeferred.resolve(inputTracks)
deferred = @sessionPageEnterDeferred
@sessionPageEnterDeferred = null
return deferred
@sessionPageEnterTimeout = setTimeout(()=>
if @sessionPageEnterTimeout
if @sessionPageEnterDeferred
@sessionPageEnterDeferred.reject('timeout')
@sessionPageEnterDeferred = null
@sessionPageEnterTimeout = null
, 5000)
@sessionPageEnterDeferred
ensureAppropriateProfile: (musicianAccess) ->
deferred = new $.Deferred();
if musicianAccess
deferred = context.JK.guardAgainstSinglePlayerProfile(@app)
else
deferred.resolve();
deferred
joinSession: () ->
context.jamClient.SessionRegisterCallback("JK.HandleBridgeCallback2");
#context.jamClient.RegisterRecordingCallbacks("JK.HandleRecordingStartResult", "JK.HandleRecordingStopResult", "JK.HandleRecordingStarted", "JK.HandleRecordingStopped", "JK.HandleRecordingAborted");
context.jamClient.SessionSetConnectionStatusRefreshRate(1000);
#context.JK.HelpBubbleHelper.jamtrackGuideSession($screen.find('li.open-a-jamtrack'), $screen)
# subscribe to events from the recording model
@recordingRegistration()
# tell the server we want to join
rest.joinSession({
client_id: @app.clientId,
ip_address: context.JK.JamServer.publicIP,
as_musician: true,
tracks: @userTracks,
session_id: @currentSessionId,
audio_latency: context.jamClient.FTUEGetExpectedLatency().latency
})
.done((response) =>
unless @inSession()
# the user has left the session before they got joined. We need to issue a leave again to the server to make sure they are out
logger.debug("user left before fully joined to session. telling server again that they have left")
@leaveSessionRest(response.id)
return
logger.debug("calling jamClient.JoinSession");
# on temporary disconnect scenarios, a user may already be in a session when they enter this path
# so we avoid double counting
unless @alreadyInSession()
if response.music_session.participant_count == 1
context.JK.GA.trackSessionMusicians(context.JK.GA.SessionCreationTypes.create);
else
context.JK.GA.trackSessionMusicians(context.JK.GA.SessionCreationTypes.join);
#recordingModel.reset();
context.jamClient.JoinSession({sessionID: response.id});
@refreshCurrentSession(true);
context.JK.JamServer.registerMessageCallback(context.JK.MessageType.SESSION_JOIN, @trackChanges);
context.JK.JamServer.registerMessageCallback(context.JK.MessageType.SESSION_DEPART, @trackChanges);
context.JK.JamServer.registerMessageCallback(context.JK.MessageType.TRACKS_CHANGED, @trackChanges);
context.JK.JamServer.registerMessageCallback(context.JK.MessageType.HEARTBEAT_ACK, @trackChanges);
$(document).trigger(EVENTS.SESSION_STARTED, {session: {id: @currentSessionId}}) if document
@handleAutoOpenJamTrack()
)
.fail((xhr) =>
@updateCurrentSession(null)
if xhr.status == 404
# we tried to join the session, but it's already gone. kick user back to join session screen
leaveBehavior =
location: "/client#/findSession"
notify:
title: "Unable to Join Session",
text: " The session you attempted to join is over."
SessionActions.leaveSession.trigger(leaveBehavior)
else if xhr.status == 422
response = JSON.parse(xhr.responseText);
if response["errors"] && response["errors"]["tracks"] && response["errors"]["tracks"][0] == "Please select at least one track"
@app.notifyAlert("No Inputs Configured", $('<span>You will need to reconfigure your audio device.</span>'))
else if response["errors"] && response["errors"]["music_session"] && response["errors"]["music_session"][0] == ["is currently recording"]
leaveBehavior =
location: "/client#/findSession"
notify:
title: "Unable to Join Session"
text: "The session is currently recording."
SessionActions.leaveSession.trigger(leaveBehavior)
else
@app.notifyServerError(xhr, 'Unable to Join Session');
else
@app.notifyServerError(xhr, 'Unable to Join Session');
)
trackChanges: (header, payload) ->
if @currentTrackChanges < payload.track_changes_counter
# we don't have the latest info. try and go get it
logger.debug("track_changes_counter = stale. refreshing...")
@refreshCurrentSession();
else
if header.type != 'HEARTBEAT_ACK'
# don't log if HEARTBEAT_ACK, or you will see this log all the time
logger.info("track_changes_counter = fresh. skipping refresh...", header, payload)
handleAutoOpenJamTrack: () ->
jamTrack = @sessionUtils.grabAutoOpenJamTrack();
if jamTrack
# give the session to settle just a little (call a timeout of 1 second)
setTimeout(()=>
# tell the server we are about to open a jamtrack
rest.openJamTrack({id: context.JK.CurrentSessionModel.id(), jam_track_id: jamTrack.id})
.done((response) =>
logger.debug("jamtrack opened")
# now actually load the jamtrack
# TODO
# context.JK.CurrentSessionModel.updateSession(response);
# loadJamTrack(jamTrack);
)
.fail((jqXHR) =>
@app.notifyServerError(jqXHR, "Unable to Open JamTrack For Playback")
)
, 1000)
inSession: () ->
!!@currentSessionId
alreadyInSession: () ->
inSession = false
for participant in @participants()
if participant.user.id == context.JK.currentUserId
inSession = true
break
participants: () ->
if @currentSession
@currentSession.participants;
else
[]
refreshCurrentSession: (force) ->
logger.debug("refreshCurrentSession(force=true)") if force
@refreshCurrentSessionRest(force)
refreshCurrentSessionRest: (force) ->
unless @inSession()
logger.debug("refreshCurrentSession skipped: ")
return
if @requestingSessionRefresh
# if someone asks for a refresh while one is going on, we ask for another to queue up
logger.debug("queueing refresh")
@pendingSessionRefresh = true;
else
@requestingSessionRefresh = true
rest.getSession(@currentSessionId)
.done((response) =>
@updateSessionInfo(response, force)
)
.fail((jqXHR) =>
if jqXHR.status != 404
@app.notifyServerError(jqXHR, "Unable to refresh session data")
else
logger.debug("refreshCurrentSessionRest: could not refresh data for session because it's gone")
)
.always(() =>
@requestingSessionRefresh = false
if @pendingSessionRefresh
# and when the request is done, if we have a pending, fire it off again
pendingSessionRefresh = false
@refreshCurrentSessionRest(force)
)
updateSessionInfo: (session, force) ->
if force == true || @currentTrackChanges < session.track_changes_counter
logger.debug("updating current track changes from %o to %o", @currentTrackChanges, session.track_changes_counter)
@currentTrackChanges = session.track_changes_counter;
@sendClientParticipantChanges(@currentSession, session);
@updateCurrentSession(session);
#if(callback != null) {
# callback();
#}
else
logger.info("ignoring refresh because we already have current: " + @currentTrackChanges + ", seen: " + session.track_changes_counter);
leaveSessionRest: () ->
rest.deleteParticipant(@app.clientId);
sendClientParticipantChanges: (oldSession, newSession) ->
joins = []
leaves = []
leaveJoins = []; # Will hold JamClientParticipants
oldParticipants = []; # will be set to session.participants if session
oldParticipantIds = {};
newParticipants = [];
newParticipantIds = {};
if oldSession && oldSession.participants
for oldParticipant in oldSession.participants
oldParticipantIds[oldParticipant.client_id] = oldParticipant
if newSession && newSession.participants
for newParticipant in newSession.participants
newParticipantIds[newParticipant.client_id] = newParticipant
for client_id, participant of newParticipantIds
# grow the 'all participants seen' list
unless (client_id in @participantsEverSeen)
@participantsEverSeen[client_id] = participant;
if client_id in oldParticipantIds
# if the participant is here now, and here before, there is still a chance we missed a
# very fast leave/join. So check if joined_session_at is different
if oldParticipantIds[client_id].joined_session_at != participant.joined_session_at
leaveJoins.push(participant)
else
# new participant id that's not in old participant ids: Join
joins.push(participant);
for client_id, participant of oldParticipantIds
unless (client_id in newParticipantIds)
# old participant id that's not in new participant ids: Leave
leaves.push(participant);
for i, v of joins
if v.client_id != @app.clientId
@participantJoined(newSession, v)
for i,v of leaves
if v.client_id != @app.clientId
@participantLeft(newSession, v)
for i,v of leaveJoins
if v.client_id != @app.clientId
logger.debug("participant had a rapid leave/join")
@participantLeft(newSession, v)
@participantJoined(newSession, v)
participantJoined: (newSession, participant) ->
logger.debug("jamClient.ParticipantJoined", participant.client_id)
context.jamClient.ParticipantJoined(newSession, @toJamClientParticipant(participant));
@currentParticipants[participant.client_id] = {server: participant, client: {audio_established: null}}
participantLeft: (newSession, participant) ->
logger.debug("jamClient.ParticipantLeft", participant.client_id)
context.jamClient.ParticipantLeft(newSession, @toJamClientParticipant(participant));
delete @currentParticipants[participant.client_id]
toJamClientParticipant: (participant) ->
{
userID: "",
clientID: participant.client_id,
tcpPort: 0,
udpPort: 0,
localIPAddress: participant.ip_address, # ?
globalIPAddress: participant.ip_address, # ?
latency: 0,
natType: ""
}
recordingRegistration: () ->
logger.debug("recording registration not hooked up yet")
updateCurrentSession: (sessionData) ->
if sessionData != null
@currentOrLastSession = sessionData
@currentSession = sessionData
console.log("SESSION CHANGED", sessionData)
this.trigger(new context.SessionHelper(@app, @currentSession))
ensureConnected: () ->
unless context.JK.JamServer.connected
leaveBehavior =
location: '/client#/home'
notify:
title: "Not Connected"
text: 'To create or join a session, you must be connected to the server.'
SessionActions.leaveSession.trigger(leaveBehavior)
context.JK.JamServer.connected
}
)

View File

@ -1957,7 +1957,7 @@
// Given a mixerID and a value between 0.0-1.0,
// light up the proper VU lights.
function _updateVU(mixerId, value, isClipping) {
function _updateVU(mixerId, value, isClipping) {
// Special-case for mono tracks. If mono, and it's a _vul id,
// update both sides, otherwise do nothing.
@ -2193,8 +2193,8 @@
// TODO - no guarantee range will be -80 to 20. Get from the
// GetControlState for this mixer which returns min/max
// value is a DB value from -80 to 20. Convert to float from 0.0-1.0
_updateVU(mixerId + "_vul", (leftValue + 80) / 100, leftClipping);
_updateVU(mixerId + "_vur", (rightValue + 80) / 100, rightClipping);
_updateVU(mixerId + "_vul", (leftValue + 80) / 80, leftClipping);
_updateVU(mixerId + "_vur", (rightValue + 80) / 80, rightClipping);
}
else if(eventName === 'connection_status') {
var mixerId = vuInfo[1];

View File

@ -7,6 +7,8 @@
"use strict";
var ChannelGroupIds = context.JK.ChannelGroupIds
context.JK = context.JK || {};
// As these are helper functions, just have a single
@ -20,7 +22,7 @@
var userTracks = context.JK.TrackHelpers.getUserTracks(jamClient, allTracks);
var backingTracks = context.JK.TrackHelpers.getBackingTracks(jamClient, allTracks);
var metronomeTracks = context.JK.TrackHelpers.getTracks(jamClient, 12);
var metronomeTracks = context.JK.TrackHelpers.getTracks(jamClient, ChannelGroupIds.MetronomeGroup);
return {
userTracks: userTracks,
@ -51,7 +53,7 @@
// allTracks is the result of SessionGetAllControlState; as an optimization
getBackingTracks: function(jamClient, allTracks) {
var mediaTracks = context.JK.TrackHelpers.getTracks(jamClient, 4, allTracks);
var mediaTracks = context.JK.TrackHelpers.getTracks(jamClient, ChannelGroupIds.MediaTrackGroup, allTracks);
var backingTracks = []
context._.each(mediaTracks, function(mediaTrack) {
@ -80,7 +82,7 @@
var localMusicTracks = [];
var i;
localMusicTracks = context.JK.TrackHelpers.getTracks(jamClient, 2, allTracks);
localMusicTracks = context.JK.TrackHelpers.getTracks(jamClient, ChannelGroupIds.AudioInputMusicGroup, allTracks);
var trackObjects = [];

View File

@ -206,6 +206,70 @@
})
return $element;
}
/** Creates a hover element that does not dissappear when the user mouses over the hover.
*
* @param $element
* @param text
* @param options
*/
context.JK.interactReactBubble = function($element, reactElementName, reactProps, options) {
if(!options) options = {};
function waitForBubbleHover($bubble) {
$bubble.hoverIntent({
over: function() {
if(timeout) {
clearTimeout(timeout);
timeout = null;
}
},
out: function() {
//$element.btOff();
}});
}
var timeout = null;
options.trigger = 'none'
options.clickAnywhereToClose = true
options.preShow = function(container) {
var reactElement = context[reactElementName]
if(!reactElementName) {
throw "unknown react element" + reactElementName
}
var element = React.createElement(reactElement, reactProps);
var $container = $(container)
React.render(element, $container.find('.react-holder').get(0))
}
options.postShow = function(container) {
if(timeout) {
clearTimeout(timeout);
timeout = null;
}
waitForBubbleHover($(container))
timeout = setTimeout(function() {/**$element.btOff()*/}, 3000)
}
$element.hoverIntent({
over: function() {
$element.btOn();
},
out: function() {
}});
options.cssStyles = {}
options.padding = 0;
context.JK.hoverBubble($element, '<div class="react-holder ' + reactElementName + '"></div>', options)
return $element;
}
/**
* Associates a bubble on hover (by default) with the specified $element, using jquery.bt.js (BeautyTips)
* @param $element The element that should show the bubble when hovered

View File

@ -93,6 +93,53 @@
}
})
},
/**
* Given a selector representing a container for a VU meter and
* a value between 0.0 and 1.0, light the appropriate lights.
*/
updateVU2: function (side, mixer, value) {
// There are 13 VU lights. Figure out how many to
// light based on the incoming value.
var $selector = $('.' + side + '-' + mixer.id)
$selector.each(function() {
var $table = $(this)
var horizontal = $table.is('.horizontal')
var lightCount = Number($table.attr('data-light-count'))
var i = 0;
var state = 'on';
var lights = Math.round(value * lightCount);
var redSwitch = Math.round(lightCount * 0.6666667);
var $light = null;
var colorClass = 'vu-green-';
var thisLightSelector = null;
// Remove all light classes from all lights
var allLightsSelector = $table.find('td');
$(allLightsSelector).removeClass('vu-green-off vu-green-on vu-red-off vu-red-on');
// Set the lights
for (i = 0; i < lightCount; i++) {
colorClass = 'vu-green-';
state = 'on';
if (i >= redSwitch) {
colorClass = 'vu-red-';
}
if (i >= lights) {
state = 'off';
}
var lightIndex = horizontal ? i : lightCount - i - 1;
allLightsSelector.eq(lightIndex).addClass(colorClass + state);
}
})
}
};

View File

@ -55,6 +55,14 @@ $poor: #980006;
$error: #980006;
$fair: #cc9900;
$labelFontFamily: Arial, Helvetica, sans-serif;
$labelFontSize: 12px;
@mixin labelFont {
font-family: $labelFontFamily;
font-size: $labelFontSize;
}
@mixin border_box_sizing {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;

View File

@ -7,10 +7,11 @@
color: #fff;
font-weight: 600;
font-size: 24px;
margin-bottom: 15px;
}
.tracks {
position:absolute;
position: absolute;
@include border_box_sizing;
top: 71px;
bottom: 0;
@ -19,58 +20,326 @@
.session-my-tracks, .session-other-tracks, .session-media-tracks, .session-notifications {
@include border_box_sizing;
float:left;
width:25%;
float: left;
width: 25%;
border-right: 1px solid #4c4c4c;
padding:15px;
height:100%;
padding: 15px;
height: 100%;
margin-bottom: 15px;
}
.session-notifications {
border-right-width:0;
border-right-width: 0;
}
.in-session-controls {
width:100%;
padding:11px 0px 11px 0px;
background-color:#4c4c4c;
min-height:20px;
position:relative;
min-width:690px;
width: 100%;
padding: 11px 0px 11px 0px;
background-color: #4c4c4c;
min-height: 20px;
position: relative;
min-width: 690px;
.label {
float:left;
font-size:12px;
color:#ccc;
float: left;
font-size: 12px;
color: #ccc;
margin: 0px 0px 0px 4px;
}
.block {
float:left;
float: left;
margin: 6px 8px 0px 8px;
}
a {
img {
margin-right:3px;
margin-right: 3px;
}
}
}
.session-tracks-scroller {
position:relative;
overflow-x:hidden;
overflow-y:auto;
width:100%;
position: relative;
overflow-x: hidden;
overflow-y: auto;
width: 100%;
}
p {
line-height:125%;
margin:0;
line-height: 125%;
margin: 0;
}
.session-my-tracks {
.session-track {
float:left;
margin: 10px 0;
padding: 6px 6px 6px 10px;
color: $ColorTextTypical;
background-color: #242323;
border-radius: 6px;
min-height: 76px;
max-width: 210px;
@include border_box_sizing;
}
}
.react-holder {
&.SessionTrackVolumeHover {
height:331px;
width:235px;
.session-track {
float:left;
background-color: #242323;
border-radius: 4px;
display: inline-block;
height: 300px;
margin-right: 14px;
position: relative;
width: 70px;
margin-top:19px;
margin-left:24px;
}
.track-icon-mute {
float:none;
position: absolute;
top: 246px;
left: 29px;
}
.track-gain {
position:absolute;
width:28px;
height:209px;
top:32px;
left:23px;
}
.fader {
height:209px;
}
.handle {
bottom:0%;
display:none;
}
.textual-help {
float:left;
width:100px;
}
p {
font-size:12px;
padding:0;
margin:16px 0 0;
line-height:125%;
&:nth-child(1) {
margin-top:19px;
}
}
.icheckbox_minimal {
position:absolute;
top: 271px;
left: 12px;
}
input {
position:absolute;
top: 271px;
left: 12px;
}
label {
@include labelFont;
position:absolute;
top:273px;
left:34px
}
}
&.SessionTrackPanHover {
width:331px;
height:197px;
padding:15px;
@include border_box_sizing;
.session-pan {
.textual-help {
float:left;
width:100px;
}
}
p {
font-size:12px;
padding:0;
line-height:125%;
}
.track-pan {
background-color: #242323;
border-radius: 4px;
display: inline-block;
height: 70px;
position: relative;
width: 300px;
margin-top:15px;
}
.fader {
position:absolute;
width:205px;
height:24px;
top:34px;
left:44px;
background-image: url('/assets/content/bkg_slider_gain_horiz_24.png');
}
.handle {
display:none;
img {
position:absolute;
left:-5px;
}
}
.left-label {
@include labelFont;
position:absolute;
left:13px;
top:40px;
}
.right-label {
@include labelFont;
position:absolute;
right:12px;
top:40px;
}
.floater {
width:20px;
text-align:center;
top:-22px;
left:-8px;
@include labelFont;
position:absolute;
}
}
}
.session-track {
.name {
width: 100%;
margin-bottom: 6px;
@include labelFont;
}
.track-avatar {
float: left;
padding: 1px;
width: 44px;
height: 44px;
background-color: #ed3618;
-webkit-border-radius: 22px;
-moz-border-radius: 22px;
border-radius: 22px;
img {
width: 44px;
height: 44px;
-webkit-border-radius: 22px;
-moz-border-radius: 22px;
border-radius: 22px;
}
}
.track-instrument {
float: left;
padding: 1px;
margin-left: 5px;
}
}
table.vu {
float: left;
td {
border: 3px solid #242323;
}
}
.track-controls {
margin-top: 2px;
margin-left: 10px;
float:left
}
.track-buttons {
margin-top:22px;
padding:0 0 0 3px;
}
.track-icon-mute {
float:left;
position:relative;
top:0;
left:0;
}
.track-icon-pan {
float:left;
cursor: pointer;
width: 20px;
height: 20px;
background-image:url('/assets/content/icon_pan.png');
background-repeat:no-repeat;
text-align: center;
margin-left:10px;
}
.track-icon-equalizer {
float:left;
cursor: pointer;
width: 20px;
height: 20px;
background-image:url('/assets/content/icon_equalizer.png');
background-repeat:no-repeat;
text-align: center;
margin-left:7px;
}
.session-track-list-enter {
opacity: 0.01;
transition: opacity .5s ease-in;
&.session-track-list-enter-active {
opacity: 1;
}
}
.session-track-list-leave {
opacity:1;
transition: opacity .5s ease-in;
&.session-track-list-leave-active {
opacity: 0.01;
}
}
.session-track-settings {
height:18px;
cursor:pointer;
span {
top: -4px;
position: relative;
left:3px;
}
}
}

View File

@ -2,8 +2,8 @@
<!-- Vertical Fader -->
<script type="text/template" id="template-fader-v">
<div class="fader vertical" control="fader" fader-id="{{ faderId }}" orientation="vertical" style="height:{{height}}px;">
<div class="handle" style="bottom:0%;" control="fader-handle">
<div class="fader vertical" data-control="fader" data-fader-id="{{ faderId }}" data-orientation="vertical" style="height:{{height}}px;">
<div class="handle" style="bottom:0%;" data-control="fader-handle">
<%= image_tag "content/slider_gain_vertical.png", {:width => 28, :height => 11} %>
</div>
</div>
@ -11,8 +11,8 @@
<!-- Horizontal Fader -->
<script type="text/template" id="template-fader-h">
<div class="fader horizontal" control="fader" fader-id="{{ faderId }}" orientation="horizontal" style="width:{{width}}px;">
<div class="handle" style="left:0%;" control="fader-handle">
<div class="fader horizontal" data-control="fader" data-fader-id="{{ faderId }}" data-orientation="horizontal" style="width:{{width}}px;">
<div class="handle" style="left:0%;" data-control="fader-handle">
<%= image_tag "content/slider_volume.png", {:height => 17, :width => 8} %>
</div>
</div>

View File

@ -302,7 +302,7 @@
var sessionScreen = new JK.SessionScreen(JK.app);
sessionScreen.initialize(localRecordingsDialog, recordingFinishedDialog, JK.FriendSelectorDialogInstance);
AppActions.appInit(JK.app)
AppActions.appInit.trigger(JK.app)
var sessionSettingsDialog = new JK.SessionSettingsDialog(JK.app, sessionScreen);
sessionSettingsDialog.initialize();