From b686020b496fa277d5b1019372dac0ef74e6ad58 Mon Sep 17 00:00:00 2001 From: Seth Call Date: Mon, 26 Oct 2015 20:20:26 -0500 Subject: [PATCH 01/19] * wip --- .../javascripts/configureTracksHelper2.js | 249 +----------------- .../dialog/configureTrackDialog.js | 4 +- .../assets/javascripts/react-components.js | 1 + .../ConfigureTracks.js.jsx.coffee | 18 ++ .../actions/ConfigureTracksActions.js.coffee | 5 + .../stores/ConfigureTracksStore.js.coffee | 37 +++ .../_configure_tracks_dialog.html.haml | 26 +- 7 files changed, 65 insertions(+), 275 deletions(-) create mode 100644 web/app/assets/javascripts/react-components/ConfigureTracks.js.jsx.coffee create mode 100644 web/app/assets/javascripts/react-components/actions/ConfigureTracksActions.js.coffee create mode 100644 web/app/assets/javascripts/react-components/stores/ConfigureTracksStore.js.coffee diff --git a/web/app/assets/javascripts/configureTracksHelper2.js b/web/app/assets/javascripts/configureTracksHelper2.js index 7c4f07128..57111335b 100644 --- a/web/app/assets/javascripts/configureTracksHelper2.js +++ b/web/app/assets/javascripts/configureTracksHelper2.js @@ -22,136 +22,7 @@ var $instrumentsHolder = null; var isDragging = false; - function removeHoverer($hoverChannel) { - var $channel = $hoverChannel.data('original') - $channel.data('cloned', null); - $hoverChannel.remove(); - } - function hoverIn($channel) { - if(isDragging) return; - - var $container = $channel.closest('.target'); - var inTarget = $container.length > 0; - if(!inTarget) { - $container = $channel.closest('.channels-holder') - } - - var $inputs = $container.find('.ftue-input'); - - var index = $inputs.index($channel); - // $channel.css('padding', '0 5px'); - if(inTarget) { - $channel.data('container', $container) - $channel.addClass('hovering'); - $channel.css('color', 'white') - $channel.css('background-color', '#333'); - $channel.css('border', '#333'); - $channel.css('border-radius', '2px'); - $channel.css('min-width', '49%'); - $channel.css('width', 'auto'); - $channel.css('position', 'absolute'); - $container.css('overflow', 'visible'); - } - else { - var $offsetParent = $channel.offsetParent(); - var parentOffset = $offsetParent.offset(); - - var hoverChannel = $(context._.template($templateAssignablePort.html(), {id: 'bogus', name: $channel.text(), direction: 'bogus'}, { variable: 'data' })); - hoverChannel - .css('position', 'absolute') - .css('color', 'white') - .css('left', $channel.position().left) - .css('top', $channel.position().top) - .css('background-color', '#333') - .css('min-width', $channel.width()) - .css('min-height', $channel.height()) - .css('z-index', 10000) - .css('background-position', '4px 6px') - .css('padding-left', '14px') - .data('original', $channel); - - $channel.data('cloned', hoverChannel); - hoverChannel - .hover(function(e) { - var hoverCheckTimeout = hoverChannel.data('hoverCheckTimeout'); - if(hoverCheckTimeout) { - clearTimeout(hoverCheckTimeout); - hoverChannel.data('hoverCheckTimeout', null); - } - }, function() { removeHoverer($(this)); }) - .mousedown(function(e) { - // because we have obscured the element the user wants to drag, - // we proxy a mousedown on the hover-element to the covered .ftue-input ($channel). - // this causes jquery.drag to get going even though the user clicked a different element - $channel.trigger(e) - }) - hoverChannel.data('hoverCheckTimeout', setTimeout(function() { - // check if element has already been left - hoverChannel.data('hoverCheckTimeout', null); - removeHoverer(hoverChannel); - }, 500)); - hoverChannel.prependTo($offsetParent); - } - - $channel.css('z-index', 10000) - if(inTarget && $inputs.length == 2) { - - if(index == 0) { - $channel.css('right', '50%') - } - else { - $channel.css('left', '51%') - } - } - - } - - function hoverOut($channel) { - - var $cloned = $channel.data('cloned'); - if($cloned) { - return; // let the cloned handle the rest of hover out logic when it's hovered-out - } - - $channel - .removeClass('hovering') - .css('color', '') - .css('font-weight', '') - .css('position', '') - .css('width', '') - .css('background-color', '') - .css('padding', '') - .css('padding-left', '') - .css('background-position', '') - .css('border', '') - .css('border-radius', '') - .css('right', '') - .css('left', '') - .css('min-width', '') - .css('z-index', '') - .css('margin-left', '') - .css('max-width', 'auto'); - - //var $container = $channel.closest('.target'); - var $container = $channel.data('container'); - if($container) { - $container.css('overflow', '') - } - } - - function fixClone($clone) { - $clone - .css('color', '') - .css('font-weight', '') - .css('width', 'auto') - .css('background-color', '') - .css('padding', '') - .css('border', '') - .css('border-radius', '') - .css('right', '') - .css('min-width', '') - } // inputChannelFilter is an optional argument that is used by the Gear Wizard. // basically, if an input channel isn't in there, it's not going to be displayed @@ -176,11 +47,6 @@ var $channel = $(context._.template($templateAssignablePort.html(), $.extend({}, inputChannel, {direction:'in'}), { variable: 'data' })); - $channel.hover( - function() { hoverIn ($(this)) }, - function() { hoverOut($(this)) } - ); - if(forceInputsToUnassign || inputChannel.assignment == ASSIGNMENT.UNASSIGNED) { unassignInputChannel($channel); } @@ -205,10 +71,7 @@ context._.each(outputChannels, function (outputChannel, index) { var $channel = $(context._.template($templateAssignablePort.html(), $.extend({}, outputChannel, {direction:'out'}), { variable: 'data' })); - $channel.hover( - function() { hoverIn ($(this)) }, - function() { hoverOut($(this)) } - ); + if(outputChannel.assignment == ASSIGNMENT.UNASSIGNED) { unassignOutputChannel($channel); @@ -223,29 +86,6 @@ } addChannelToOutput($channel, $output.find('.output-target')); } - - $channel.draggable({ - helper: 'clone', - start: function(e,ui) { - isDragging = true; - var $channel = $(this); - fixClone(ui.helper); - var $output = $channel.closest('.output-target'); - var isUnassigned = $output.length == 0; - if(isUnassigned) { - $outputChannelHolder.find('.output-target').addClass('possible-target'); - } - else { - $outputChannelHolder.find('.output-target').addClass('possible-target'); - $unassignedOutputsHolder.addClass('possible-target'); - } - }, - stop: function() { - isDragging = false; - $outputChannelHolder.find('.output-target').removeClass('possible-target'); - $unassignedOutputsHolder.removeClass('possible-target') - } - }); }); } @@ -439,93 +279,6 @@ $originallyAssignedTrack.attr('output-count', $originallyAssignedTrack.find('.ftue-input:not(.ui-draggable-dragging)').length) } - - function initializeUnassignedOutputDroppable() { - $unassignedOutputsHolder.droppable( - { - accept: '.ftue-input[data-direction="out"]', - activeClass: 'drag-in-progress', - hoverClass: 'drag-hovering', - drop: function( event, ui ) { - var $channel = ui.draggable; - - //$channel.css('left', '0').css('top', '0'); - unassignOutputChannel($channel); - } - }); - } - - function initializeUnassignedInputDroppable() { - $unassignedInputsHolder.droppable( - { - accept: '.ftue-input[data-direction="in"]', - activeClass: 'drag-in-progress', - hoverClass: 'drag-hovering', - drop: function( event, ui ) { - var $channel = ui.draggable; - //$channel.css('left', '0').css('top', '0'); - unassignInputChannel($channel); - } - }); - } - - function initializeOutputDroppables() { - var i; - for(i = 0; i < MAX_OUTPUTS; i++) { - var $target = $(context._.template($templateOutputTarget.html(), {num: i }, { variable: 'data' })); - $outputChannelHolder.append($target); - $target.find('.output-target').droppable( - { - accept: '.ftue-input[data-direction="out"]', - activeClass: 'drag-in-progress', - hoverClass: 'drag-hovering', - drop: function( event, ui ) { - var $slot = $(this); - if($slot.attr('output-count') == 1) { - return false; // max of 1 output per slot - } - var $channel = ui.draggable; - //$channel.css('left', '0').css('top', '0'); - addChannelToOutput($channel, $slot); - } - }); - } - } - - function initializeTrackDroppables() { - var i; - for(i = 0; i < MAX_TRACKS; i++) { - var $target = $(context._.template($templateTrackTarget.html(), {num: i }, { variable: 'data' })); - $tracksHolder.append($target); - $target.find('.track-target').droppable( - { - accept: '.ftue-input[data-direction="in"]', - activeClass: 'drag-in-progress', - hoverClass: 'drag-hovering', - drop: function( event, ui ) { - var $track = $(this); - if($track.attr('track-count') == 2) { - return false; // max of 2 inputs per track - } - - var $channel = ui.draggable; - //$channel.css('left', '0').css('top', '0'); - addChannelToTrack($channel, $track); - } - }); - } - } - - function initializeInstrumentDropdown() { - var i; - for(i = 0; i < MAX_TRACKS; i++) { - var $root = $('
'); - $root.instrumentSelector().attr('data-num', i); - $instrumentsHolder.append($root); - } - } - - function initialize(_$parent) { $parent = _$parent; diff --git a/web/app/assets/javascripts/dialog/configureTrackDialog.js b/web/app/assets/javascripts/dialog/configureTrackDialog.js index 20801cfa6..12558b064 100644 --- a/web/app/assets/javascripts/dialog/configureTrackDialog.js +++ b/web/app/assets/javascripts/dialog/configureTrackDialog.js @@ -247,8 +247,8 @@ $btnAddNewGear = $dialog.find('.btn-add-new-audio-gear'); $btnUpdateTrackSettings = $dialog.find('.btn-update-settings'); - configureTracksHelper = new context.JK.ConfigureTracksHelper(app); - configureTracksHelper.initialize($dialog); + //configureTracksHelper = new context.JK.ConfigureTracksHelper(app); + //configureTracksHelper.initialize($dialog); voiceChatHelper = new context.JK.VoiceChatHelper(app); voiceChatHelper.initialize($dialog, 'configure_track_dialog', true, {vuType: "vertical", lightCount: 10, lightWidth: 3, lightHeight: 17}, 191); diff --git a/web/app/assets/javascripts/react-components.js b/web/app/assets/javascripts/react-components.js index ff86ff8e6..a0d400953 100644 --- a/web/app/assets/javascripts/react-components.js +++ b/web/app/assets/javascripts/react-components.js @@ -3,6 +3,7 @@ //= require_directory ./react-components/helpers //= require_directory ./react-components/actions //= require ./react-components/stores/AppStore +//= require ./react-components/stores/ConfigureTracksStore //= require ./react-components/stores/BrowserMediaStore //= require ./react-components/stores/RecordingStore //= require ./react-components/stores/VideoStore diff --git a/web/app/assets/javascripts/react-components/ConfigureTracks.js.jsx.coffee b/web/app/assets/javascripts/react-components/ConfigureTracks.js.jsx.coffee new file mode 100644 index 000000000..19de40dc0 --- /dev/null +++ b/web/app/assets/javascripts/react-components/ConfigureTracks.js.jsx.coffee @@ -0,0 +1,18 @@ +context = window +rest = context.JK.Rest() +ReactCSSTransitionGroup = React.addons.CSSTransitionGroup +MIX_MODES = context.JK.MIX_MODES + +@ConfigureTracks = React.createClass({ + + mixins: [Reflux.listenTo(@ConfigureTrackStore,"onConfigureTracksChanged")] + + onConfigureTrackChanged: (state) -> + @setState({configureTracks: state) + + render: () -> + + `
+ +
` +}) \ No newline at end of file diff --git a/web/app/assets/javascripts/react-components/actions/ConfigureTracksActions.js.coffee b/web/app/assets/javascripts/react-components/actions/ConfigureTracksActions.js.coffee new file mode 100644 index 000000000..551f91773 --- /dev/null +++ b/web/app/assets/javascripts/react-components/actions/ConfigureTracksActions.js.coffee @@ -0,0 +1,5 @@ +context = window + +@ConfigureTracksActions = Reflux.createActions({ + reset: {} +}) diff --git a/web/app/assets/javascripts/react-components/stores/ConfigureTracksStore.js.coffee b/web/app/assets/javascripts/react-components/stores/ConfigureTracksStore.js.coffee new file mode 100644 index 000000000..0b52520a0 --- /dev/null +++ b/web/app/assets/javascripts/react-components/stores/ConfigureTracksStore.js.coffee @@ -0,0 +1,37 @@ +$ = jQuery +context = window +logger = context.JK.logger + +@ConfigureTracksStore = Reflux.createStore( + { + listenables: @ConfigureTracksActions + + musicPorts: {inputs:[], outputs:[]} + + init: () -> + this.listenTo(context.AppStore, this.onAppInit); + + onAppInit: (@app) -> + + onReset: () -> + logger.debug("ConfigureTracksStore:reset") + @loadChannels() + + + onTrySave: () -> + logger.debug("ConfigureTracksStore:trySave") + + changed: () -> + @state = {musicPorts: @musicPorts} + + loadChannels: (forceInputsToUnassign, inputChannelFilter) -> + # inputChannelFilter is an optional argument that is used by the Gear Wizard. + # basically, if an input channel isn't in there, it's not going to be displayed + @musicPorts = context.jamClient.FTUEGetChannels() + + @changed() + + + + } +) \ No newline at end of file diff --git a/web/app/views/dialogs/_configure_tracks_dialog.html.haml b/web/app/views/dialogs/_configure_tracks_dialog.html.haml index 5611533d1..cb65af8eb 100644 --- a/web/app/views/dialogs/_configure_tracks_dialog.html.haml +++ b/web/app/views/dialogs/_configure_tracks_dialog.html.haml @@ -16,32 +16,8 @@ .tab.no-selection-range{'tab-id' => 'music-audio'} - .column - .certified-audio-profile-section - .sub-header Certified Audio Profile - %select.certified-audio-profile - .clearall + = react_component 'ConfigureTracks', {} - .unused-audio-inputs-section - .sub-header Unused Input Ports - .unassigned-input-channels.channels-holder - - .unused-audio-outputs-section - .sub-header Unused Output Ports - .unassigned-output-channels.channels-holder - - .column - .input-tracks-section - .sub-column - .sub-header Track Input Port(s) - .input-tracks.tracks - .sub-column - .sub-header Instrument - .instruments - - .output-channels-section - .sub-header Audio Output Port - .output-channels .clearall From 52524c774de6701a97b529a35ae2d937a318cacf Mon Sep 17 00:00:00 2001 From: Seth Call Date: Tue, 27 Oct 2015 09:19:14 -0500 Subject: [PATCH 02/19] * wip --- .../javascripts/configureTracksHelper2.js | 16 -------- .../dialog/configureTrackDialog.js | 12 +++--- .../ConfigureTracks.js.jsx.coffee | 34 ++++++++++++--- .../actions/ConfigureTracksActions.js.coffee | 1 + .../stores/ConfigureTracksStore.js.coffee | 41 ++++++++++++++++--- 5 files changed, 71 insertions(+), 33 deletions(-) diff --git a/web/app/assets/javascripts/configureTracksHelper2.js b/web/app/assets/javascripts/configureTracksHelper2.js index 57111335b..d87319a53 100644 --- a/web/app/assets/javascripts/configureTracksHelper2.js +++ b/web/app/assets/javascripts/configureTracksHelper2.js @@ -282,22 +282,6 @@ function initialize(_$parent) { $parent = _$parent; - $templateAssignablePort = $('#template-assignable-port'); - $templateTrackTarget = $('#template-track-target'); - $templateOutputTarget = $('#template-output-target'); - $unassignedInputsHolder = $parent.find('.unassigned-input-channels') - $unassignedOutputsHolder = $parent.find('.unassigned-output-channels'); - $tracksHolder = $parent.find('.tracks'); - $instrumentsHolder = $parent.find('.instruments'); - $outputChannelHolder = $parent.find('.output-channels'); - - - initializeUnassignedInputDroppable(); - initializeTrackDroppables(); - initializeInstrumentDropdown(); - - initializeUnassignedOutputDroppable(); - initializeOutputDroppables(); } this.initialize = initialize; diff --git a/web/app/assets/javascripts/dialog/configureTrackDialog.js b/web/app/assets/javascripts/dialog/configureTrackDialog.js index 12558b064..6720df09f 100644 --- a/web/app/assets/javascripts/dialog/configureTrackDialog.js +++ b/web/app/assets/javascripts/dialog/configureTrackDialog.js @@ -91,7 +91,7 @@ } function validateAudioSettings() { - return configureTracksHelper.trySave(); + return window.ConfigureTracksActions.trySave(); } function showVoiceChatPanel() { @@ -103,7 +103,7 @@ $musicAudioTabSelector.click(function () { // validate voice chat settings if (validateVoiceChatSettings()) { - configureTracksHelper.reset(); + window.ConfigureTracksActions.reset(); voiceChatHelper.reset(); showMusicAudioPanel(); } @@ -113,7 +113,7 @@ // validate audio settings if (validateAudioSettings()) { logger.debug("initializing voice chat helper") - configureTracksHelper.reset(); + window.ConfigureTracksActions.reset(); voiceChatHelper.reset(); showVoiceChatPanel(); } @@ -133,7 +133,7 @@ //}); $btnUpdateTrackSettings.click(function() { - if(configureTracksHelper.trySave() && voiceChatHelper.trySave()) { + if(window.ConfigureTracksActions.trySave() && voiceChatHelper.trySave()) { app.layout.closeDialog('configure-tracks'); } @@ -183,7 +183,7 @@ currentProfile = profile; - configureTracksHelper.reset(); + window.ConfigureTracksActions.reset(); } function beforeShow() { @@ -207,7 +207,7 @@ return; } - configureTracksHelper.reset(); + window.ConfigureTracksActions.reset(); voiceChatHelper.reset(); voiceChatHelper.beforeShow(); } diff --git a/web/app/assets/javascripts/react-components/ConfigureTracks.js.jsx.coffee b/web/app/assets/javascripts/react-components/ConfigureTracks.js.jsx.coffee index 19de40dc0..5c9bae882 100644 --- a/web/app/assets/javascripts/react-components/ConfigureTracks.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/ConfigureTracks.js.jsx.coffee @@ -1,18 +1,42 @@ context = window rest = context.JK.Rest() ReactCSSTransitionGroup = React.addons.CSSTransitionGroup -MIX_MODES = context.JK.MIX_MODES +ASSIGNMENT = context.JK.ASSIGNMENT +VOICE_CHAT = context.JK.VOICE_CHAT +MAX_TRACKS = context.JK.MAX_TRACKS +MAX_OUTPUTS = context.JK.MAX_OUTPUTS +gearUtils = context.JK.GearUtils @ConfigureTracks = React.createClass({ - mixins: [Reflux.listenTo(@ConfigureTrackStore,"onConfigureTracksChanged")] + mixins: [Reflux.listenTo(@ConfigureTracksStore,"onConfigureTracksChanged")] - onConfigureTrackChanged: (state) -> - @setState({configureTracks: state) + getInitialState: () -> + {configureTracks: null} + + onConfigureTracksChanged: (configureTracks) -> + @setState({configureTracks: configureTracks}) render: () -> + console.log("@state.configureTracks", @state.configureTracks) + + liveTracks = [] + + trackAssignments = @state.configureTracks?.trackAssignments + + if trackAssignments + + for track in trackAssignments.inputs.assigned + + + + `
- + +

Session Audio Inputs (Live Performance Tracks)

+ {liveTracks} + +
` }) \ No newline at end of file diff --git a/web/app/assets/javascripts/react-components/actions/ConfigureTracksActions.js.coffee b/web/app/assets/javascripts/react-components/actions/ConfigureTracksActions.js.coffee index 551f91773..0afa11498 100644 --- a/web/app/assets/javascripts/react-components/actions/ConfigureTracksActions.js.coffee +++ b/web/app/assets/javascripts/react-components/actions/ConfigureTracksActions.js.coffee @@ -2,4 +2,5 @@ context = window @ConfigureTracksActions = Reflux.createActions({ reset: {} + trySave: {} }) diff --git a/web/app/assets/javascripts/react-components/stores/ConfigureTracksStore.js.coffee b/web/app/assets/javascripts/react-components/stores/ConfigureTracksStore.js.coffee index 0b52520a0..03f74afd3 100644 --- a/web/app/assets/javascripts/react-components/stores/ConfigureTracksStore.js.coffee +++ b/web/app/assets/javascripts/react-components/stores/ConfigureTracksStore.js.coffee @@ -1,10 +1,15 @@ $ = jQuery context = window logger = context.JK.logger +ASSIGNMENT = context.JK.ASSIGNMENT +VOICE_CHAT = context.JK.VOICE_CHAT +MAX_TRACKS = context.JK.MAX_TRACKS +MAX_OUTPUTS = context.JK.MAX_OUTPUTS +gearUtils = context.JK.GearUtils @ConfigureTracksStore = Reflux.createStore( { - listenables: @ConfigureTracksActions + listenables: ConfigureTracksActions musicPorts: {inputs:[], outputs:[]} @@ -14,24 +19,48 @@ logger = context.JK.logger onAppInit: (@app) -> onReset: () -> - logger.debug("ConfigureTracksStore:reset") + logger.debug("ConfigureTracksStore:reset", this) @loadChannels() onTrySave: () -> logger.debug("ConfigureTracksStore:trySave") + @trySave() + + trySave: () -> changed: () -> - @state = {musicPorts: @musicPorts} + @item = {musicPorts: @musicPorts, trackAssignments: @trackAssignments} + + @trigger(@item) loadChannels: (forceInputsToUnassign, inputChannelFilter) -> # inputChannelFilter is an optional argument that is used by the Gear Wizard. # basically, if an input channel isn't in there, it's not going to be displayed @musicPorts = context.jamClient.FTUEGetChannels() + @trackAssignments = {inputs: {unassigned:[], assigned:[], chat:[]}, outputs: {unassigned:[], assigned: []}} + for input in @musicPorts.inputs + if input.assigment == ASSIGNMENT.UNASSIGNED + @trackAssignments.inputs.unassigned.push(input) + else if input.assignment == ASSIGNMENT.CHAT + @trackAssignments.inputs.chat.push(input) + else + # make sure this assignment isn't already preset (you can have multiple inputs per 'track slot') + found = false + for assigned in @trackAssignments.inputs.assigned + if assigned[0].assignment == input.assignment + assigned.push(input) + found = true + + if !found + @trackAssignments.inputs.assigned.push([input]) + for output in @musiPorts.outputs + if output.assignment == ASSIGNMENT.OUTPUT + @trackAssignments.outputs.assigned.push(output) + else + @trackAssignments.outputs.unassigned.push(output) + @changed() - - - } ) \ No newline at end of file From f7267d38c6758d50b30f2aa82512b075285bbb5e Mon Sep 17 00:00:00 2001 From: Seth Call Date: Fri, 30 Oct 2015 09:59:50 -0500 Subject: [PATCH 03/19] * wip --- .../dialog/configureTrackDialog.js | 3 + web/app/assets/javascripts/fakeJamClient.js | 6 + .../ConfigureLiveTracksDialog.js.jsx.coffee | 68 ++++++++++ .../ConfigureTracks.js.jsx.coffee | 69 +++++++++- .../actions/ConfigureTracksActions.js.coffee | 2 + .../stores/ConfigureTracksStore.js.coffee | 51 ++++++- web/app/assets/javascripts/utils.js | 4 + .../react-components/ConfigureTracks.css.scss | 126 ++++++++++++++++++ .../configureLiveTracksDialog.css.scss | 25 ++++ .../_configureLiveTracksDialog.html.slim | 6 + web/app/views/dialogs/_dialogs.html.haml | 1 + 11 files changed, 349 insertions(+), 12 deletions(-) create mode 100644 web/app/assets/javascripts/react-components/ConfigureLiveTracksDialog.js.jsx.coffee create mode 100644 web/app/assets/stylesheets/client/react-components/ConfigureTracks.css.scss create mode 100644 web/app/assets/stylesheets/dialogs/configureLiveTracksDialog.css.scss create mode 100644 web/app/views/dialogs/_configureLiveTracksDialog.html.slim diff --git a/web/app/assets/javascripts/dialog/configureTrackDialog.js b/web/app/assets/javascripts/dialog/configureTrackDialog.js index 6720df09f..5c631e196 100644 --- a/web/app/assets/javascripts/dialog/configureTrackDialog.js +++ b/web/app/assets/javascripts/dialog/configureTrackDialog.js @@ -214,6 +214,9 @@ function afterShow() { sessionUtils.SessionPageEnter(); + + context.ConfigureTracksActions.vstScan(); + } function onCancel() { diff --git a/web/app/assets/javascripts/fakeJamClient.js b/web/app/assets/javascripts/fakeJamClient.js index 077ad4ced..26fcb4cc3 100644 --- a/web/app/assets/javascripts/fakeJamClient.js +++ b/web/app/assets/javascripts/fakeJamClient.js @@ -1052,6 +1052,10 @@ function GetAutoStart() { return true; } function SaveSettings() {} + + function VSTScan(callback) {setTimeout(eval(callback+ "()"), 1000)} + function hasVstHost() { return false;} + // Javascript Bridge seems to camel-case // Set the instance functions: this.AbortRecording = AbortRecording; @@ -1315,6 +1319,8 @@ this.StopNetworkTest = StopNetworkTest; this.log = log; this.getOperatingMode = getOperatingMode; + this.VSTScan = VSTSCan; + this.hasVstHost = hasVstHost; this.clientID = "devtester"; }; diff --git a/web/app/assets/javascripts/react-components/ConfigureLiveTracksDialog.js.jsx.coffee b/web/app/assets/javascripts/react-components/ConfigureLiveTracksDialog.js.jsx.coffee new file mode 100644 index 000000000..387d1a59f --- /dev/null +++ b/web/app/assets/javascripts/react-components/ConfigureLiveTracksDialog.js.jsx.coffee @@ -0,0 +1,68 @@ +context = window +ConfigureTracksStore = @ConfigureTracksStore +@ConfigureLiveTracksDialog = React.createClass({ + + mixins: [Reflux.listenTo(@ConfigureTracksStore,"onConfigureTracksChanged")] + + onConfigureTracksChanged:(configureTracks) -> + @setState({configureTracks: configureTracks}) + + getInitialState: () -> + {configureTracks: null} + + render: () -> + + inputOneOptions = [] + inputTwoOptions = [] + + defaultSelectionOne = `` + defaultSelectionTwo = `` + + inputOneOptions.push(defaultSelectionOne) + inputTwoOptions.push(defaultSelectionTwo) + + instruments = [] + for displayName, value of context.JK.server_to_client_instrument_map + instruments.push(``) + + if @state.configureTracks? + for input in @state.configureTracks.musicPorts.inputs + item = `` + inputOneOptions.push(item) + inputTwoOptions.push(item) + + `
+
+

Track Type

+ + +
+ +
+

Audio Input Ports

+

Select one or two inputs ports to assign to this track. Note that if you assign a single input port, the app will automatically duplicate this port into a stereo track.

+ + +
+
+

Instrument

+ +
+
+

Audio Effects (optional)

+ +
+
` + + componentDidMount: () -> + $root = $(@getDOMNode()) + context.JK.checkbox($root.find('input[type="checkbox"]')) +}) \ No newline at end of file diff --git a/web/app/assets/javascripts/react-components/ConfigureTracks.js.jsx.coffee b/web/app/assets/javascripts/react-components/ConfigureTracks.js.jsx.coffee index 5c9bae882..e6256f4f3 100644 --- a/web/app/assets/javascripts/react-components/ConfigureTracks.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/ConfigureTracks.js.jsx.coffee @@ -22,21 +22,78 @@ gearUtils = context.JK.GearUtils console.log("@state.configureTracks", @state.configureTracks) liveTracks = [] + outputs = [] trackAssignments = @state.configureTracks?.trackAssignments if trackAssignments - for track in trackAssignments.inputs.assigned + for inputsForTrack in trackAssignments.inputs.assigned + candidate = inputsForTrack[0] + inputs = [] + for input in inputsForTrack + inputs.push(`
{input.name}
`) + if !inputsForTrack.instrument_id? + instrument = `?` + else + instrument = `` + liveTracks.push( + `
+
{candidate.assignment}:{inputs}
+
None
+
{instrument}
+
+ update + delete +
+
`) - `
+ for output, i in trackAssignments.outputs.assigned + outputs.push( + `
+
{i + 1}:
{output.name}
+
`) + + `
-

Session Audio Inputs (Live Performance Tracks)

- {liveTracks} - - +
+
+

Session Audio Inputs (Live Performance Tracks)

+

Plugin

+

Instrument

+
+
+ {liveTracks} +
+ ADD A LIVE TRACK . . . +
+
+
+

Session Audio Outputs (Requires 2 Ports)

+
+ {outputs} +
+ UPDATE OUTPUTS . . . +
+
` + + onUpdateLiveTrack:(liveTrack, e) -> + e.preventDefault() + + onDeleteLiveTrack:(liveTrack, e) -> + e.preventDefault() + + openLiveTrackDialog: (e) -> + e.preventDefault() + + context.JK.app.layout.showDialog('configure-live-tracks-dialog') + + openOutputTrackDialog: (e) -> + e.preventDefault() + + context.JK.app.layout.app.showDialog('configure-output-tracks-dialog') }) \ No newline at end of file diff --git a/web/app/assets/javascripts/react-components/actions/ConfigureTracksActions.js.coffee b/web/app/assets/javascripts/react-components/actions/ConfigureTracksActions.js.coffee index 0afa11498..b4448c579 100644 --- a/web/app/assets/javascripts/react-components/actions/ConfigureTracksActions.js.coffee +++ b/web/app/assets/javascripts/react-components/actions/ConfigureTracksActions.js.coffee @@ -3,4 +3,6 @@ context = window @ConfigureTracksActions = Reflux.createActions({ reset: {} trySave: {} + vstScan: {} + vstScanComplete: {} }) diff --git a/web/app/assets/javascripts/react-components/stores/ConfigureTracksStore.js.coffee b/web/app/assets/javascripts/react-components/stores/ConfigureTracksStore.js.coffee index 03f74afd3..93e842ff1 100644 --- a/web/app/assets/javascripts/react-components/stores/ConfigureTracksStore.js.coffee +++ b/web/app/assets/javascripts/react-components/stores/ConfigureTracksStore.js.coffee @@ -12,9 +12,11 @@ gearUtils = context.JK.GearUtils listenables: ConfigureTracksActions musicPorts: {inputs:[], outputs:[]} + trackNumber: null + editingTrack: null + init: () -> - this.listenTo(context.AppStore, this.onAppInit); onAppInit: (@app) -> @@ -29,8 +31,19 @@ gearUtils = context.JK.GearUtils trySave: () -> + onVstScan: () -> + hasVst = context.jamClient.hasVstHost() + logger.debug("hasVst", hasVst) + if hasVst + logger.debug("vstScan starting") + result = context.jamClient.VSTScan() + logger.debug("vstScan completed", result) + + + onVstScanComplete:() -> + changed: () -> - @item = {musicPorts: @musicPorts, trackAssignments: @trackAssignments} + @item = {musicPorts: @musicPorts, trackAssignments: @trackAssignments, trackNumber: @trackNumber, editingTrack: @editingTrack} @trigger(@item) @@ -39,9 +52,11 @@ gearUtils = context.JK.GearUtils # basically, if an input channel isn't in there, it's not going to be displayed @musicPorts = context.jamClient.FTUEGetChannels() + # let's populate this bad boy @trackAssignments = {inputs: {unassigned:[], assigned:[], chat:[]}, outputs: {unassigned:[], assigned: []}} + for input in @musicPorts.inputs - if input.assigment == ASSIGNMENT.UNASSIGNED + if input.assignment == ASSIGNMENT.UNASSIGNED @trackAssignments.inputs.unassigned.push(input) else if input.assignment == ASSIGNMENT.CHAT @trackAssignments.inputs.chat.push(input) @@ -49,18 +64,42 @@ gearUtils = context.JK.GearUtils # make sure this assignment isn't already preset (you can have multiple inputs per 'track slot') found = false for assigned in @trackAssignments.inputs.assigned - if assigned[0].assignment == input.assignment + if assigned.assignment == input.assignment assigned.push(input) found = true if !found - @trackAssignments.inputs.assigned.push([input]) - for output in @musiPorts.outputs + initial = [input] + initial.assignment = input.assignment # store the assignment on the array itself, so we don't have to check inside the array for an input's assignment (which will all be the same) + @trackAssignments.inputs.assigned.push(initial) + for output in @musicPorts.outputs if output.assignment == ASSIGNMENT.OUTPUT @trackAssignments.outputs.assigned.push(output) else @trackAssignments.outputs.unassigned.push(output) + @loadTrackInstruments(forceInputsToUnassign) @changed() + + loadTrackInstruments: (forceInputsToUnassign) -> + for inputsForTrack in @trackAssignments.inputs.assigned + clientInstrument = context.jamClient.TrackGetInstrument(inputsForTrack[0].assignment) + + instrument = context.JK.client_to_server_instrument_map[clientInstrument]; + + inputsForTrack.instrument_id = instrument?.server_id + + openLiveTrackDialog: (trackNumber) -> + @trackNumber = trackNumber + + @editingTrack = null + for inputsForTrack in @trackAssignments.inputs.assigned + if inputsForTrack.assignment == trackNumber + @editingTrack = inputsForTrack + break + + @changed() + + @app.layout.showDialog('configure-live-tracks-dialog') } ) \ No newline at end of file diff --git a/web/app/assets/javascripts/utils.js b/web/app/assets/javascripts/utils.js index 68aa5b692..c25a3292c 100644 --- a/web/app/assets/javascripts/utils.js +++ b/web/app/assets/javascripts/utils.js @@ -733,6 +733,10 @@ return date.toLocaleTimeString(); } + context.JK.iconMapBase = function() { + return icon_map_base + } + context.JK.formatUtcTime = function(date, change) { if (change) { date.setMinutes(Math.ceil(date.getMinutes() / 30) * 30); diff --git a/web/app/assets/stylesheets/client/react-components/ConfigureTracks.css.scss b/web/app/assets/stylesheets/client/react-components/ConfigureTracks.css.scss new file mode 100644 index 000000000..f76f8276d --- /dev/null +++ b/web/app/assets/stylesheets/client/react-components/ConfigureTracks.css.scss @@ -0,0 +1,126 @@ +@import "client/common"; + +.ConfigureTracks { + + .inputs-view { + border-width:1px 0; + border-color:$ColorText; + border-style:solid; + padding:20px 0; + height:220px; + + .live-tracks { + height:150px; + overflow:auto; + a { + margin-left:3px; + } + } + + .live-input { + display:inline-block; + } + .live-track { + margin-bottom:20px; + } + + a.delete-live-track { + margin-left:20px; + } + .assignment { + display:inline-block; + width:20px; + } + + .input-track-info { + @include border_box_sizing; + width:60%; + display:inline-block; + } + + .plugin-info { + @include border_box_sizing; + width:30%; + display:inline-block; + } + + .plugin-instrument { + @include border_box_sizing; + width:10%; + display:inline-block; + text-align:center; + + img { + vertical-align:middle; + } + } + } + + .outputs-view { + border-width:0 0 1px; + border-color:$ColorText; + border-style:solid; + padding:20px 0; + + .assignment { + display:inline-block; + width:20px; + } + + + .output-tracks { + height:73px; + overflow:auto; + a { + margin-left:3px; + } + } + + + .output-track { + margin-bottom: 20px; + } + + .output-track-info { + display:inline-block; + } + + .output { + display:inline-block; + margin-bottom:0 !important; + } + } + h3 { + display:inline-block; + font-size:14px; + margin:0 0 20px; + @include border_box_sizing; + &.session-audio-inputs-header { + color:white; + font-weight:bold; + width:60%; + } + + &.session-audio-outputs-header { + color:white; + font-weight:bold; + width:60%; + } + + &.plugin-header { + width:30%; + } + &.instrument-header { + width:10%; + text-align:center; + } + } + .live-track-actions { + display:block; + padding-left:30px; + margin-top:10px; + a { + font-size:12px; + } + } +} \ No newline at end of file diff --git a/web/app/assets/stylesheets/dialogs/configureLiveTracksDialog.css.scss b/web/app/assets/stylesheets/dialogs/configureLiveTracksDialog.css.scss new file mode 100644 index 000000000..282c08a2f --- /dev/null +++ b/web/app/assets/stylesheets/dialogs/configureLiveTracksDialog.css.scss @@ -0,0 +1,25 @@ +@import "client/common"; + +#configure-live-tracks-dialog { + width: 800px; + + .track-type { + width:100%; + @include border_box_sizing; + } + .audio-input-ports { + width:50%; + @include border_box_sizing; + float:left; + } + .instrument-selection { + width:50%; + @include border_box_sizing; + float:left; + } + .audio-effects { + width:50%; + @include border_box_sizing; + float:left; + } +} \ No newline at end of file diff --git a/web/app/views/dialogs/_configureLiveTracksDialog.html.slim b/web/app/views/dialogs/_configureLiveTracksDialog.html.slim new file mode 100644 index 000000000..eace5771b --- /dev/null +++ b/web/app/views/dialogs/_configureLiveTracksDialog.html.slim @@ -0,0 +1,6 @@ +.dialog.dialog-overlay-sm.top-parent layout='dialog' layout-id='configure-live-tracks-dialog' id='configure-live-tracks-dialog' + .content-head + = image_tag "content/icon_add.png", {:height => 19, :width => 19, :class => 'content-icon'} + h1 add live track + .dialog-inner + = react_component 'ConfigureLiveTracksDialog', {} diff --git a/web/app/views/dialogs/_dialogs.html.haml b/web/app/views/dialogs/_dialogs.html.haml index 45db4cb3f..761559fdf 100644 --- a/web/app/views/dialogs/_dialogs.html.haml +++ b/web/app/views/dialogs/_dialogs.html.haml @@ -44,3 +44,4 @@ = render 'dialogs/recordingSelectorDialog' = render 'dialogs/soundCloudPlayerDialog' = render 'dialogs/deleteVideoConfirmDialog' += render 'dialogs/configureLiveTracksDialog' \ No newline at end of file From 9e461c61dd7c521a3045f7fe1f417dd9e2882e0e Mon Sep 17 00:00:00 2001 From: Seth Call Date: Fri, 30 Oct 2015 14:43:59 -0500 Subject: [PATCH 04/19] * update deliverable csv for jamtrack users --- admin/app/controllers/email_controller.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/admin/app/controllers/email_controller.rb b/admin/app/controllers/email_controller.rb index 1e0c55182..4c999fd30 100644 --- a/admin/app/controllers/email_controller.rb +++ b/admin/app/controllers/email_controller.rb @@ -15,5 +15,11 @@ class EmailController < ApplicationController headers['Content-Type'] ||= 'text/csv' @users = User.where(subscribe_email: true) + + # if specified, return only users that have redeemed or bought a JamTrack + if params[:any_jam_track] + @users = @users.select('DISTINCT users.id, email, first_name, last_name').joins(:sales => :sale_line_items).where("sale_line_items.product_type = 'JamTrack'") + end + end end \ No newline at end of file From 022fd60b3c953eb92b680e754799a11f579fdabc Mon Sep 17 00:00:00 2001 From: Seth Call Date: Sun, 1 Nov 2015 06:39:51 -0600 Subject: [PATCH 05/19] * wip --- .../dialog/configureTrackDialog.js | 2 +- web/app/assets/javascripts/fakeJamClient.js | 11 ++++- .../ConfigureLiveTracksDialog.js.jsx.coffee | 33 ++++++++++++-- .../stores/ConfigureTracksStore.js.coffee | 38 +++++++++++++--- .../configureLiveTracksDialog.css.scss | 45 +++++++++++++++++++ 5 files changed, 118 insertions(+), 11 deletions(-) diff --git a/web/app/assets/javascripts/dialog/configureTrackDialog.js b/web/app/assets/javascripts/dialog/configureTrackDialog.js index 5c631e196..3b4f478c5 100644 --- a/web/app/assets/javascripts/dialog/configureTrackDialog.js +++ b/web/app/assets/javascripts/dialog/configureTrackDialog.js @@ -215,7 +215,7 @@ function afterShow() { sessionUtils.SessionPageEnter(); - context.ConfigureTracksActions.vstScan(); + //context.ConfigureTracksActions.vstScan(); } diff --git a/web/app/assets/javascripts/fakeJamClient.js b/web/app/assets/javascripts/fakeJamClient.js index 26fcb4cc3..8b1b8477a 100644 --- a/web/app/assets/javascripts/fakeJamClient.js +++ b/web/app/assets/javascripts/fakeJamClient.js @@ -1055,6 +1055,12 @@ function VSTScan(callback) {setTimeout(eval(callback+ "()"), 1000)} function hasVstHost() { return false;} + function getPluginList() { return {vsts:[]} } + + function clearPluginList() {} + function listTrackAssignments() { + return {} + } // Javascript Bridge seems to camel-case // Set the instance functions: @@ -1319,8 +1325,11 @@ this.StopNetworkTest = StopNetworkTest; this.log = log; this.getOperatingMode = getOperatingMode; - this.VSTScan = VSTSCan; + this.VSTScan = VSTScan; this.hasVstHost = hasVstHost; + this.getPluginList = getPluginList; + this.clearPluginList = clearPluginList; + this.listTrackAssignments = listTrackAssignments; this.clientID = "devtester"; }; diff --git a/web/app/assets/javascripts/react-components/ConfigureLiveTracksDialog.js.jsx.coffee b/web/app/assets/javascripts/react-components/ConfigureLiveTracksDialog.js.jsx.coffee index 387d1a59f..308305446 100644 --- a/web/app/assets/javascripts/react-components/ConfigureLiveTracksDialog.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/ConfigureLiveTracksDialog.js.jsx.coffee @@ -12,6 +12,7 @@ ConfigureTracksStore = @ConfigureTracksStore render: () -> + action = 'ADD TRACK' inputOneOptions = [] inputTwoOptions = [] @@ -31,11 +32,17 @@ ConfigureTracksStore = @ConfigureTracksStore inputOneOptions.push(item) inputTwoOptions.push(item) + + if @state.configureTracks.editingTrack? + action = 'EDIT TRACK' + + vsts = [] + vsts.push(``) `

Track Type

- - +
+
@@ -57,12 +64,30 @@ ConfigureTracksStore = @ConfigureTracksStore

Audio Effects (optional)

+ manage audio plugins + SETTING . . . +
+ +
` + cancel: (e) -> + e.preventDefault() + + update: (e) -> + e.preventDefault() + + vstSettings: (e) -> + e.preventDefault() + ConfigureTracksActions.showVstSettings() + componentDidMount: () -> $root = $(@getDOMNode()) - context.JK.checkbox($root.find('input[type="checkbox"]')) + context.JK.checkbox($root.find('input[type="radio"]')) + }) \ No newline at end of file diff --git a/web/app/assets/javascripts/react-components/stores/ConfigureTracksStore.js.coffee b/web/app/assets/javascripts/react-components/stores/ConfigureTracksStore.js.coffee index 93e842ff1..dbcb830dd 100644 --- a/web/app/assets/javascripts/react-components/stores/ConfigureTracksStore.js.coffee +++ b/web/app/assets/javascripts/react-components/stores/ConfigureTracksStore.js.coffee @@ -7,6 +7,19 @@ MAX_TRACKS = context.JK.MAX_TRACKS MAX_OUTPUTS = context.JK.MAX_OUTPUTS gearUtils = context.JK.GearUtils +### + + QVariantMap scanForPlugins(); +QVariantMap VSTListVsts(); +void VSTClearAll(); +QVariantMap VSTSetTrackAssignment(const QVariantMap vst, const QString& trackId); +QVariantMap VSTListTrackAssignments(); +void VSTShowHideGui(bool show,const QString& trackId); +void VST_ScanForMidiDevices(); +QVariantMap VST_GetMidiDeviceList(); +bool VST_EnableMidiForTrack(const QString& trackId, bool enableMidi, int midiDeviceIndex); +### + @ConfigureTracksStore = Reflux.createStore( { listenables: ConfigureTracksActions @@ -15,14 +28,17 @@ gearUtils = context.JK.GearUtils trackNumber: null editingTrack: null - init: () -> onAppInit: (@app) -> onReset: () -> logger.debug("ConfigureTracksStore:reset", this) + @trackNumber = null + @editingTrack = null @loadChannels() + @onVstScan() + @onMidiScan() onTrySave: () -> @@ -32,15 +48,26 @@ gearUtils = context.JK.GearUtils trySave: () -> onVstScan: () -> - hasVst = context.jamClient.hasVstHost() - logger.debug("hasVst", hasVst) - if hasVst + @hasVst = context.jamClient.hasVstHost() + logger.debug("hasVst", @hasVst) + if @hasVst logger.debug("vstScan starting") result = context.jamClient.VSTScan() logger.debug("vstScan completed", result) + @vstPluginList = context.jamClient.VSTListVsts() + @vstTrackAssignments = context.jamClient.VSTListTrackAssignments() - onVstScanComplete:() -> + console.log("@vstPluginList", @vstPluginList) + console.log("@vstTrackAssignments", @vstTrackAssignments) + + onMidiScan: () -> + @attachedMidiDevices = context.jamClient.listAttachedMidiDevices(); + + console.log("@attachedMidiDevices", @attachedMidiDevices) + + onShowVstSettings: () -> + context.jamClient.showHideVstGui(true, @trackNumber) if @trackNumber? changed: () -> @item = {musicPorts: @musicPorts, trackAssignments: @trackAssignments, trackNumber: @trackNumber, editingTrack: @editingTrack} @@ -79,6 +106,7 @@ gearUtils = context.JK.GearUtils @trackAssignments.outputs.unassigned.push(output) @loadTrackInstruments(forceInputsToUnassign) + @changed() loadTrackInstruments: (forceInputsToUnassign) -> diff --git a/web/app/assets/stylesheets/dialogs/configureLiveTracksDialog.css.scss b/web/app/assets/stylesheets/dialogs/configureLiveTracksDialog.css.scss index 282c08a2f..b87510eab 100644 --- a/web/app/assets/stylesheets/dialogs/configureLiveTracksDialog.css.scss +++ b/web/app/assets/stylesheets/dialogs/configureLiveTracksDialog.css.scss @@ -3,23 +3,68 @@ #configure-live-tracks-dialog { width: 800px; + .dialog-inner { + width:auto; + } + + h3 { + color:white; + font-weight:bold; + margin-bottom:10px; + } + + .actions { + clear:both; + text-align:center; + } .track-type { width:100%; @include border_box_sizing; + margin-bottom:20px; + padding:10px; + + .track-type-option { + margin-bottom:10px; + } + label { + vertical-align:middle; + display:inline-block; + margin-left:10px; + } + .iradio_minimal { + vertical-align:middle; + display:inline-block; + } } .audio-input-ports { width:50%; @include border_box_sizing; float:left; + margin-bottom:40px; + padding:10px; + + select { + width: 90%; + @include border_box_sizing; + margin-bottom:10px; + } + p { + margin-bottom:10px; + line-height:125%; + } } .instrument-selection { width:50%; @include border_box_sizing; float:left; + margin-bottom:40px; + padding:10px; } .audio-effects { width:50%; @include border_box_sizing; float:left; + margin-bottom:20px; + padding:10px; } } \ No newline at end of file From 1da8c7372160452bcd7b289adb4e9eda03ccbf55 Mon Sep 17 00:00:00 2001 From: Seth Call Date: Mon, 9 Nov 2015 15:33:04 -0600 Subject: [PATCH 06/19] * wip --- ruby/lib/jam_ruby/jam_track_importer.rb | 2 +- .../dialog/configureTrackDialog.js | 8 +- web/app/assets/javascripts/globals.js | 4 +- .../assets/javascripts/jquery.manageVsts.js | 71 ++++ .../assets/javascripts/jquery.trackEffects.js | 75 ++++ .../assets/javascripts/react-components.js | 2 +- .../ConfigureAudioTrack.js.jsx.coffee | 0 .../ConfigureLiveTracksDialog.js.jsx.coffee | 367 ++++++++++++++++-- .../ConfigureOutputsDialog.js.jsx.coffee | 87 +++++ .../ConfigureTracks.js.jsx.coffee | 35 +- .../SessionMyTrack.js.jsx.coffee | 38 +- .../SessionMyTracks.js.jsx.coffee | 2 +- .../actions/ConfigureTracksActions.js.coffee | 14 + .../mixins/SessionMyTracksMixin.js.coffee | 28 +- .../stores/ConfigureTracksStore.js.coffee | 288 +++++++++++++- .../stores/SessionStore.js.coffee | 3 + .../wizard/gear/step_configure_tracks.js | 9 +- web/app/assets/stylesheets/client/client.css | 2 + .../stylesheets/client/manageVsts.css.scss | 23 ++ .../react-components/ConfigureTracks.css.scss | 50 ++- .../stylesheets/client/vstEffects.css.scss | 39 ++ .../client/wizard/gearWizard.css.scss | 59 ++- .../configureLiveTracksDialog.css.scss | 103 ++++- .../dialogs/configureOutputsDialog.css.scss | 36 ++ .../dialogs/configureTracksDialog.css.scss | 16 +- web/app/views/clients/_manageVsts.html.slim | 7 + web/app/views/clients/_vstEffects.html.slim | 9 + web/app/views/clients/index.html.erb | 2 + .../wizard/gear/_gear_wizard.html.haml | 11 +- .../_configureLiveTracksDialog.html.slim | 6 +- .../dialogs/_configureOutputsDialog.html.slim | 2 + .../_configure_tracks_dialog.html.haml | 5 +- web/app/views/dialogs/_dialogs.html.haml | 3 +- 33 files changed, 1282 insertions(+), 124 deletions(-) create mode 100644 web/app/assets/javascripts/jquery.manageVsts.js create mode 100644 web/app/assets/javascripts/jquery.trackEffects.js create mode 100644 web/app/assets/javascripts/react-components/ConfigureAudioTrack.js.jsx.coffee create mode 100644 web/app/assets/javascripts/react-components/ConfigureOutputsDialog.js.jsx.coffee create mode 100644 web/app/assets/stylesheets/client/manageVsts.css.scss create mode 100644 web/app/assets/stylesheets/client/vstEffects.css.scss create mode 100644 web/app/assets/stylesheets/dialogs/configureOutputsDialog.css.scss create mode 100644 web/app/views/clients/_manageVsts.html.slim create mode 100644 web/app/views/clients/_vstEffects.html.slim create mode 100644 web/app/views/dialogs/_configureOutputsDialog.html.slim diff --git a/ruby/lib/jam_ruby/jam_track_importer.rb b/ruby/lib/jam_ruby/jam_track_importer.rb index a92aca61b..0cdf0160f 100644 --- a/ruby/lib/jam_ruby/jam_track_importer.rb +++ b/ruby/lib/jam_ruby/jam_track_importer.rb @@ -1352,7 +1352,7 @@ module JamRuby stripped_time = total_time # default to the case where we just start the preview at the beginning burp_gaps.each do |gap| - command_strip_lead_silence = "sox \"#{ogg_44100}\" \"#{out_wav}\" silence 1 #{gap} 1%" + command_strip_lead_silence = "sox \"#{ogg_44100}\" \"#{out_wav}\" silence 1 #{gap} 1%" @@log.debug("stripping silence: " + command_strip_lead_silence) diff --git a/web/app/assets/javascripts/dialog/configureTrackDialog.js b/web/app/assets/javascripts/dialog/configureTrackDialog.js index 3b4f478c5..421cb1d3c 100644 --- a/web/app/assets/javascripts/dialog/configureTrackDialog.js +++ b/web/app/assets/javascripts/dialog/configureTrackDialog.js @@ -50,7 +50,7 @@ function setInstructions(type) { if (type === 'audio') { - $instructions.html('Choose your audio device. Drag and drop to assign input ports to tracks, and specify the instrument for each track. Drag and drop to assign a pair of output ports for session stereo audio monitoring.') + $instructions.html("Click the 'ADD LIVE TRACK' button to add more tracks. You may set up a live track for each instrumental and/or vocal part to perform in sessions. You must also set up exactly two Session Audio Output ports to deliver the stereo audio in your sessions.") return; var os = context.jamClient.GetOSAsString(); $instructions.html(configure_audio_instructions[os]); @@ -91,7 +91,7 @@ } function validateAudioSettings() { - return window.ConfigureTracksActions.trySave(); + return true; } function showVoiceChatPanel() { @@ -133,7 +133,7 @@ //}); $btnUpdateTrackSettings.click(function() { - if(window.ConfigureTracksActions.trySave() && voiceChatHelper.trySave()) { + if(voiceChatHelper.trySave()) { app.layout.closeDialog('configure-tracks'); } @@ -152,7 +152,7 @@ }); $certifiedAudioProfile.html(optionsHtml); - context.JK.dropdown($certifiedAudioProfile); + //context.JK.dropdown($certifiedAudioProfile); } function deviceChanged() { diff --git a/web/app/assets/javascripts/globals.js b/web/app/assets/javascripts/globals.js index c085e548b..86e8972a0 100644 --- a/web/app/assets/javascripts/globals.js +++ b/web/app/assets/javascripts/globals.js @@ -53,7 +53,9 @@ METRONOME_PLAYBACK_MODE_SELECTED: 'metronome_playback_mode_selected', CHECKOUT_SIGNED_IN: 'checkout_signed_in', CHECKOUT_SKIP_SIGN_IN: 'checkout_skip_sign_in', - PREVIEW_PLAYED: 'preview_played' + PREVIEW_PLAYED: 'preview_played', + VST_OPERATION_SELECTED: 'vst_operation_selected', + VST_EFFECT_SELECTED: 'vst_effect_selected' }; context.JK.PLAYBACK_MONITOR_MODE = { diff --git a/web/app/assets/javascripts/jquery.manageVsts.js b/web/app/assets/javascripts/jquery.manageVsts.js new file mode 100644 index 000000000..a14cec2e1 --- /dev/null +++ b/web/app/assets/javascripts/jquery.manageVsts.js @@ -0,0 +1,71 @@ +(function(context, $) { + + "use strict"; + + context.JK = context.JK || {}; + + + // creates an iconic/graphical instrument selector. useful when there is minimal real-estate + + $.fn.manageVsts = function(options) { + + return this.each(function(index) { + + function close() { + $parent.btOff(); + $parent.focus(); + } + + var $parent = $(this); + + function onManageVstSelected() { + var $li = $(this); + var vstOperation = $li.attr('data-manage-vst-option'); + + close(); + $parent.triggerHandler(context.JK.EVENTS.VST_OPERATION_SELECTED, {vstOperation: vstOperation}); + return false; + }; + + // if the user goes into the bubble, remove + function waitForBubbleHover($bubble) { + $bubble.hoverIntent({ + over: function() { + if(timeout) { + clearTimeout(timeout); + timeout = null; + } + }, + out: function() { + $parent.btOff(); + }}); + } + + var timeout = null; + + context.JK.hoverBubble($parent, $('#template-manage-vsts').html(), { + trigger:'none', + cssClass: 'manage-vsts-popup', + spikeGirth:0, + spikeLength:0, + width:190, + closeWhenOthersOpen: true, + offsetParent: $parent.closest('.dialog'), + positions:['bottom'], + preShow: function() { + }, + postShow:function(container) { + $(container).find('li').click(onManageVstSelected) + if(timeout) { + clearTimeout(timeout); + timeout = null; + } + waitForBubbleHover($(container)) + timeout = setTimeout(function() {$parent.btOff()}, 3000) + } + }); + }); + } + + +})(window, jQuery); \ No newline at end of file diff --git a/web/app/assets/javascripts/jquery.trackEffects.js b/web/app/assets/javascripts/jquery.trackEffects.js new file mode 100644 index 000000000..b0a15c74e --- /dev/null +++ b/web/app/assets/javascripts/jquery.trackEffects.js @@ -0,0 +1,75 @@ +(function(context, $) { + + "use strict"; + + context.JK = context.JK || {}; + + + // creates an iconic/graphical instrument selector. useful when there is minimal real-estate + + $.fn.trackEffects = function(options) { + + return this.each(function(index) { + + function close() { + $parent.btOff(); + $parent.focus(); + } + + var $parent = $(this); + + function onOptionSelected() { + var $li = $(this); + var vstOperation = $li.attr('data-manage-vst-option'); + + close(); + $parent.triggerHandler(context.JK.EVENTS.VST_EFFECT_SELECTED, {vstOperation: vstOperation}); + return false; + }; + + // if the user goes into the bubble, remove + function waitForBubbleHover($bubble) { + $bubble.hoverIntent({ + over: function() { + if(timeout) { + clearTimeout(timeout); + timeout = null; + } + }, + out: function() { + $parent.btOff(); + }}); + } + + var timeout = null; + + context.JK.hoverBubble($parent, $('#template-vst-effects').html(), { + trigger:'none', + cssClass: 'vst-effects-popup', + spikeGirth:0, + spikeLength:0, + width:220, + closeWhenOthersOpen: true, + offsetParent: $parent.closest('.screen'), + positions:['bottom'], + preShow: function() { + + }, + postShow:function(container) { + if (options && options['postShow']) { + options['postShow']($(container)) + } + $(container).find('li').click(onOptionSelected) + if(timeout) { + clearTimeout(timeout); + timeout = null; + } + waitForBubbleHover($(container)) + timeout = setTimeout(function() {$parent.btOff()}, 3000) + } + }); + }); + } + + +})(window, jQuery); \ No newline at end of file diff --git a/web/app/assets/javascripts/react-components.js b/web/app/assets/javascripts/react-components.js index a0d400953..75472bb42 100644 --- a/web/app/assets/javascripts/react-components.js +++ b/web/app/assets/javascripts/react-components.js @@ -3,12 +3,12 @@ //= require_directory ./react-components/helpers //= require_directory ./react-components/actions //= require ./react-components/stores/AppStore -//= require ./react-components/stores/ConfigureTracksStore //= require ./react-components/stores/BrowserMediaStore //= require ./react-components/stores/RecordingStore //= require ./react-components/stores/VideoStore //= require ./react-components/stores/SessionStore //= require ./react-components/stores/MixerStore +//= require ./react-components/stores/ConfigureTracksStore //= require ./react-components/stores/JamTrackStore //= require ./react-components/stores/SessionNotificationStore //= require ./react-components/stores/MediaPlaybackStore diff --git a/web/app/assets/javascripts/react-components/ConfigureAudioTrack.js.jsx.coffee b/web/app/assets/javascripts/react-components/ConfigureAudioTrack.js.jsx.coffee new file mode 100644 index 000000000..e69de29bb diff --git a/web/app/assets/javascripts/react-components/ConfigureLiveTracksDialog.js.jsx.coffee b/web/app/assets/javascripts/react-components/ConfigureLiveTracksDialog.js.jsx.coffee index 308305446..6fda3639c 100644 --- a/web/app/assets/javascripts/react-components/ConfigureLiveTracksDialog.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/ConfigureLiveTracksDialog.js.jsx.coffee @@ -2,17 +2,17 @@ context = window ConfigureTracksStore = @ConfigureTracksStore @ConfigureLiveTracksDialog = React.createClass({ - mixins: [Reflux.listenTo(@ConfigureTracksStore,"onConfigureTracksChanged")] + mixins: [Reflux.listenTo(@ConfigureTracksStore,"onConfigureTracksChanged"), Reflux.listenTo(@AppStore, "onAppInit")] onConfigureTracksChanged:(configureTracks) -> @setState({configureTracks: configureTracks}) + onAppInit: (@app) -> + getInitialState: () -> - {configureTracks: null} + {configureTracks: null, midiInterface: null} - render: () -> - - action = 'ADD TRACK' + renderAudio: () -> inputOneOptions = [] inputTwoOptions = [] @@ -21,73 +21,366 @@ ConfigureTracksStore = @ConfigureTracksStore inputOneOptions.push(defaultSelectionOne) inputTwoOptions.push(defaultSelectionTwo) + inputOneValue = '' + inputTwoValue = '' + selectedInstrument = '' + selectedVst = 'NONE' + instruments = [] + instruments.push(``) for displayName, value of context.JK.server_to_client_instrument_map instruments.push(``) - if @state.configureTracks? - for input in @state.configureTracks.musicPorts.inputs - item = `` - inputOneOptions.push(item) - inputTwoOptions.push(item) - - - if @state.configureTracks.editingTrack? - action = 'EDIT TRACK' - vsts = [] - vsts.push(``) - `
-
-

Track Type

-
-
-
+ instrumentDisabled = true + vstDisabled = true + + if @state.configureTracks? + + selectedInstrument = @state.configureTracks.editingTrack.instrument_id if @state.configureTracks.editingTrack.instrument_id? + + if @state.configureTracks.editingTrack.length == 1 + input = @state.configureTracks.editingTrack[0] + if input.number == 0 + inputOneValue = input.id + else + inputTwoValue = input.id + + if @state.configureTracks.editingTrack.length > 1 + inputOneValue = @state.configureTracks.editingTrack[0].id + inputTwoValue = @state.configureTracks.editingTrack[1].id + + instrumentDisabled = @state.configureTracks.editingTrack.length == 0 + vstDisabled = @state.configureTracks.editingTrack.length == 0 + + for input in @state.configureTracks.musicPorts.inputs + + include = false + # we need to see that this input is unassigned, or one of the two selected + for unassignedInputs in @state.configureTracks.trackAssignments.inputs.unassigned + if unassignedInputs.id == input.id + include = true + break + + if !include + # not see if it's the currently edited track + for currentInput in @state.configureTracks.editingTrack + if currentInput.id == input.id + include = true + + if include + item = `` + inputOneOptions.push(item) + inputTwoOptions.push(item) + + + for plugin in @state.configureTracks.vstPluginList.vsts + if plugin.isInstrument == false && plugin.category == 'Effect' + vsts.push(``) + else if plugin.category == 'NONE' + vsts.push(``) + + if @state.configureTracks.editingTrack.vst? + vstAssignedThisTrack = true + selectedVst = @state.configureTracks.editingTrack.vst.file + + vstSettingBtnClasses = classNames({'button-orange': vstAssignedThisTrack, 'button-grey': !vstAssignedThisTrack}) + `

Audio Input Ports

Select one or two inputs ports to assign to this track. Note that if you assign a single input port, the app will automatically duplicate this port into a stereo track.

- {inputOneOptions} - {inputTwoOptions}

Instrument

- {instruments}

Audio Effects (optional)

- {vsts} - manage audio plugins - SETTING . . . -
- -
` - cancel: (e) -> - e.preventDefault() + renderMidi: () -> + midiInterfaces = [] + midiInterfaces.push(``) + midiInstruments = [] - update: (e) -> + instruments = [] + instruments.push(``) + for displayName, value of context.JK.server_to_client_instrument_map + instruments.push(``) + + selectedMidiInterface = '' + selectedInstrument = '' + selectedMidiInstrument = '' + + instrumentDisabled = true + midiInstrumentDisabled = true + vstAssignedThisTrack = false + + if @state.configureTracks? + + logger.debug("current midi device: " + @state.configureTracks.editingTrack.midiDeviceIndex) + selectedMidiInterface = @state.configureTracks.editingTrack.midiDeviceIndex + + selectedInstrument = @state.configureTracks.editingTrack.instrument_id if @state.configureTracks.editingTrack.instrument_id? + instrumentDisabled = !@state.midiInterface? || !selectedMidiInterface? + midiInstrumentDisabled = !@state.midiInterface? || !selectedMidiInterface? + midiInstrumentDisabled = false + instrumentDisabled = false + + if @state.configureTracks.editingTrack.vst? + vstAssignedThisTrack = true + selectedMidiInstrument = @state.configureTracks.editingTrack.vst.file + vstSettingBtnClasses = classNames({'button-orange': vstAssignedThisTrack, 'button-grey': !vstAssignedThisTrack}) + + for midiDevice in @state.configureTracks.attachedMidiDevices.midiDevices + midiInterfaces.push(``) + + for plugin in @state.configureTracks.vstPluginList.vsts + if plugin.isInstrument == true + midiInstruments.push(``) + else if plugin.category == 'NONE' + midiInstruments.push(``) + + `
+
+

MIDI Interface

+ + scan for connected MIDI interfaces +
+
+

Instrument

+ +
+
+

MIDI Instrument (VST or AU Plugin)

+ + manage audio plugins
+ +
+
` + + render: () -> + + action = 'ADD TRACK' + header = 'add track' + + isAudio = !@state.configureTracks? || @state.configureTracks.trackType == 'audio' + isMidi = !isAudio + + if isAudio + activeElement = @renderAudio() + else + activeElement = @renderMidi() + + if !@state.configureTracks?.newTrack + action = 'CLOSE' + header = 'update track' + else + cancelBtn = `CANCEL` + + `
+
+ +

{header}

+
+
+
+

Track Type

+
+
+
+ + {activeElement} + +
+ {cancelBtn} + {action} +
+
+
` + + inputChanged: (e) -> + $root = $(@getDOMNode()) + $select1 = $root.find('.input-one') + $select2 = $root.find('.input-two') + + audioInput1 = $select1.val() + audioInput2 = $select2.val() + + if audioInput1 == '' + audioInput1 = null + + if audioInput2 == '' + audioInput2 = null + + if audioInput1? && audioInput1 == audioInput2 + e.preventDefault() + # TODO: tell user they can't do this + return + + ConfigureTracksActions.associateInputsWithTrack(audioInput1, audioInput2) + + vstsChanged: (e) -> + $root = $(@getDOMNode()) + $select = $root.find('select.vsts') + vstSelected = $select.val() + if vstSelected? + vstSelected = {file: vstSelected} + + ConfigureTracksActions.associateVSTWithTrack(vstSelected) + + if @state.configureTracks?.trackType == 'midi' + if @state.midiInterface? + ConfigureTracksActions.associateMIDIWithTrack(@state.midiInterface) + + + @setState({midiInterface: null}) + + instrumentSelected: (e) -> + $root = $(@getDOMNode()) + $select = $root.find('.instrument-pick') + + instrumentId = $select.val() + ConfigureTracksActions.associateInstrumentWithTrack(instrumentId) + + + doClose: (e) -> e.preventDefault() + # check that instrument is selected + + $root = $(@getDOMNode()) + $instrument = $root.find('.instrument-pick') + + instrumentId = $instrument.val() + + $select1 = $root.find('.input-one') + $select2 = $root.find('.input-two') + + audioInput1 = $select1.val() + audioInput2 = $select2.val() + + if audioInput1 == '' + audioInput1 = null + + if audioInput2 == '' + audioInput2 = null + + if audioInput1 == null && audioInput2 == null + context.JK.Banner.showAlert("At least one input must be specified.") + return + + if instrumentId == null || instrumentId == '' + context.JK.Banner.showAlert("Please select an instrument.") + return + + @app.layout.closeDialog('configure-live-tracks-dialog', false) + + onCancel: (e) -> + + ConfigureTracksActions.cancelEdit() + + @app.layout.closeDialog('configure-live-tracks-dialog', true) vstSettings: (e) -> e.preventDefault() ConfigureTracksActions.showVstSettings() + componentDidMount: () -> $root = $(@getDOMNode()) - context.JK.checkbox($root.find('input[type="radio"]')) + $radio = context.JK.checkbox($root.find('input[type="radio"]')) + $radio.on("ifChanged", @trackTypeChanged); + componentWillUpdate: () -> + @ignoreICheck = true + $root = $(@getDOMNode()) + $radio = $root.find('input[type="radio"]') + $radio.iCheck('enable') + + componentDidUpdate: () -> + $root = $(@getDOMNode()) + $radio = $root.find('input[type="radio"]') + $radio = context.JK.checkbox($root.find('input[type="radio"]')) + $radio.on("ifChanged", @trackTypeChanged); + if @state.configureTracks.editingTrack.assignment == 1 + $radio.iCheck('disable') + else + $radio.iCheck('enable') + + @ignoreICheck = false + + $manageAudioPlugins = $root.find('.manage-audio-plugins') + + unless $manageAudioPlugins.data('initialized') + $manageAudioPlugins.manageVsts().on(context.JK.EVENTS.VST_OPERATION_SELECTED, @vstOperation).data('initialized', true) + + trackTypeChanged: (event) -> + + if @ignoreICheck + logger.debug("ignoring track type changed") + return + + $checkedType = $(event.target); + value = $checkedType.val() + logger.debug("trackTypeChanged: " + value, $checkedType) + ConfigureTracksActions.desiredTrackType(value) + #@setState({trackType: value}) + + vstOperation: (e, data) -> + + if data.vstOperation == 'scan' + ConfigureTracksActions.vstScan() + else if data.vstOperation == 'clear' + ConfigureTracksActions.clearVsts() + + manageAudioPlugins: (e) -> + e.preventDefault() + + $root = $(@getDOMNode()) + $manageAudioPlugins = $root.find('.manage-audio-plugins') + $manageAudioPlugins.btOn() + + scanMidi: (e) -> + e.preventDefault() + + ConfigureTracksActions.midiScan() + + + midiInterfaceChanged: (e) -> + $root = $(@getDOMNode()) + $select = $root.find('select.midi-select') + midiInterface = $select.val() + + if midiInterface == '' + midiInterface = null + + ConfigureTracksActions.associateMIDIWithTrack(midiInterface) + + @setState({midiInterface: midiInterface}) }) \ No newline at end of file diff --git a/web/app/assets/javascripts/react-components/ConfigureOutputsDialog.js.jsx.coffee b/web/app/assets/javascripts/react-components/ConfigureOutputsDialog.js.jsx.coffee new file mode 100644 index 000000000..41affdad2 --- /dev/null +++ b/web/app/assets/javascripts/react-components/ConfigureOutputsDialog.js.jsx.coffee @@ -0,0 +1,87 @@ +context = window +ConfigureTracksStore = @ConfigureTracksStore +@ConfigureOutputsDialog = React.createClass({ + + mixins: [Reflux.listenTo(@ConfigureTracksStore, "onConfigureTracksChanged"), Reflux.listenTo(@AppStore, "onAppInit")] + + onConfigureTracksChanged: (configureTracks) -> + @setState({configureTracks: configureTracks}) + + onAppInit: (@app) -> + + getInitialState: () -> + {configureTracks: null} + + render: () -> + + outputs = [] + outputs.push(``) + + if @state.configureTracks? + for output in @state.configureTracks.musicPorts.outputs + outputs.push(``) + + `
+
+ +

session audio outputs

+
+
+

Select two audio output ports that will be used to deliver the stereo audio of your sessions to your headphones or monitor.

+ + + +
+
` + + componentDidUpdate: () -> + $root = $(@getDOMNode()) + $output1 = $root.find('.output-1') + $output2 = $root.find('.output-2') + + if @state.configureTracks? && @state.configureTracks.trackAssignments.outputs.assigned.length == 2 + + output1 = @state.configureTracks.trackAssignments.outputs.assigned[0].id + output2 = @state.configureTracks.trackAssignments.outputs.assigned[1].id + + $output1.val(output1) + $output2.val(output2) + + onClose: (e) -> + e.preventDefault() + + $root = $(@getDOMNode()) + $output1 = $root.find('.output-1') + $output2 = $root.find('.output-2') + + output1 = $output1.val() + output2 = $output2.val() + + if output1 == null || output1 == '' + context.JK.Banner.showAlert("Both output ports must have a selection.") + return + + if output2 == null || output2 == '' + context.JK.Banner.showAlert("Both output ports must have a selection.") + return + + if output1? && output1 != '' && output1 == output2 + context.JK.Banner.showAlert("Both output ports can not be the same.") + return + + ConfigureTracksActions.updateOutputs(output1, output2) + + @app.layout.closeDialog('configure-outputs-dialog', false) + + onCancel: (e) -> + + @app.layout.closeDialog('configure-outputs-dialog', true) + } +) diff --git a/web/app/assets/javascripts/react-components/ConfigureTracks.js.jsx.coffee b/web/app/assets/javascripts/react-components/ConfigureTracks.js.jsx.coffee index e6256f4f3..16d89786a 100644 --- a/web/app/assets/javascripts/react-components/ConfigureTracks.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/ConfigureTracks.js.jsx.coffee @@ -19,8 +19,6 @@ gearUtils = context.JK.GearUtils render: () -> - console.log("@state.configureTracks", @state.configureTracks) - liveTracks = [] outputs = [] @@ -40,10 +38,16 @@ gearUtils = context.JK.GearUtils else instrument = `` + trackTypeLabel = 'AUDIO' + + vstName = 'None' + if inputsForTrack.vst + vstName = "#{inputsForTrack.vst.name} by #{inputsForTrack.vst.manuf}" + liveTracks.push( `
-
{candidate.assignment}:{inputs}
-
None
+
{candidate.assignment}:{trackTypeLabel}{inputs}
+
{vstName}
{instrument}
update @@ -68,7 +72,9 @@ gearUtils = context.JK.GearUtils
{liveTracks}
- ADD A LIVE TRACK . . . +
@@ -79,21 +85,36 @@ gearUtils = context.JK.GearUtils UPDATE OUTPUTS . . .
+
` onUpdateLiveTrack:(liveTrack, e) -> e.preventDefault() + ConfigureTracksActions.showEditTrack(liveTrack.assignment) + onDeleteLiveTrack:(liveTrack, e) -> e.preventDefault() + if liveTrack.assignment == 1 + # can't delete the last assignment + context.JK.Banner.showAlert('You can not delete the 1st audio track.') + else + context.JK.Banner.showYesNo({ + title: "Confirm Deletion", + html: "Are you sure you want to delete this live track?", + yes: => + ConfigureTracksActions.deleteTrack(liveTrack.assignment) + }) + + openLiveTrackDialog: (e) -> e.preventDefault() - context.JK.app.layout.showDialog('configure-live-tracks-dialog') + ConfigureTracksActions.showAddNewTrack() openOutputTrackDialog: (e) -> e.preventDefault() - context.JK.app.layout.app.showDialog('configure-output-tracks-dialog') + ConfigureTracksActions.showEditOutputs() }) \ No newline at end of file diff --git a/web/app/assets/javascripts/react-components/SessionMyTrack.js.jsx.coffee b/web/app/assets/javascripts/react-components/SessionMyTrack.js.jsx.coffee index e36502a3c..37f8c0a00 100644 --- a/web/app/assets/javascripts/react-components/SessionMyTrack.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/SessionMyTrack.js.jsx.coffee @@ -1,6 +1,7 @@ context = window MixerActions = @MixerActions +ConfigureTracksActions = @ConfigureTracksActions @SessionMyTrack = React.createClass({ @@ -42,7 +43,9 @@ MixerActions = @MixerActions WebkitTransform: "rotate(#{pan}deg)" } - #
+ if @props.associatedVst? + @equalizerSet = true + vst = `
` `
@@ -55,11 +58,10 @@ MixerActions = @MixerActions
+ {vst}

- -
` @@ -96,6 +98,8 @@ MixerActions = @MixerActions context.JK.helpBubble($root.find('.disabled-track-overlay'), 'missing-my-tracks', {}, {positions:['top'], offsetParent: $root.closest('.top-parent')}) + @initializeVstEffects() + componentWillUpdate: (nextProps, nextState) -> $root = $(this.getDOMNode()) $mute = $root.find('.track-icon-mute') @@ -116,4 +120,32 @@ MixerActions = @MixerActions $mute.on("mouseleave", false) $pan.on("mouseentere", false) $pan.on("mouseleave", false) + + componentDidUpdate:() -> + @initializeVstEffects() + + initializeVstEffects: () -> + $root = $(this.getDOMNode()) + $equalizer = $root.find('.track-icon-equalizer') + if $equalizer.length > 0 && !$equalizer.data('initialized') + logger.debug("initializing trackEffects", $equalizer) + $equalizer.trackEffects({postShow: @prepVstEffects}).on(context.JK.EVENTS.VST_EFFECT_SELECTED, @vstOperation).data('initialized', true).click(() => + logger.debug("clicked!") + $equalizer.btOn() + ) + prepVstEffects: ($container) -> + $container.find('.vst-name').text(@props.associatedVst?.name) + + vstOperation: (e, data) -> + logger.debug("track effect selection: " + data.vstOperation) + + if !@props.associatedVst? + logger.warn("no associated VST") + return + + if data.vstOperation == 'open-vst' + ConfigureTracksActions.showVstSettings(@props.associatedVst.track) + else + ConfigureTracksActions.showEditTrack(@props.associatedVst.track + 1) + }) diff --git a/web/app/assets/javascripts/react-components/SessionMyTracks.js.jsx.coffee b/web/app/assets/javascripts/react-components/SessionMyTracks.js.jsx.coffee index 97323d6f8..017255665 100644 --- a/web/app/assets/javascripts/react-components/SessionMyTracks.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/SessionMyTracks.js.jsx.coffee @@ -5,7 +5,7 @@ ReactCSSTransitionGroup = React.addons.CSSTransitionGroup; @SessionMyTracks = React.createClass({ - mixins: [@SessionMyTracksMixin, Reflux.listenTo(@SessionMyTracksStore,"onInputsChanged"), Reflux.listenTo(@AppStore,"onAppInit")] + mixins: [@SessionMyTracksMixin, Reflux.listenTo(@SessionMyTracksStore,"onInputsChanged"), Reflux.listenTo(@AppStore,"onAppInit"), Reflux.listenTo(@ConfigureTracksStore, "onConfigureTracksChanged")] goToFtue: (e) -> e.preventDefault() diff --git a/web/app/assets/javascripts/react-components/actions/ConfigureTracksActions.js.coffee b/web/app/assets/javascripts/react-components/actions/ConfigureTracksActions.js.coffee index b4448c579..4a2890385 100644 --- a/web/app/assets/javascripts/react-components/actions/ConfigureTracksActions.js.coffee +++ b/web/app/assets/javascripts/react-components/actions/ConfigureTracksActions.js.coffee @@ -3,6 +3,20 @@ context = window @ConfigureTracksActions = Reflux.createActions({ reset: {} trySave: {} + midiScan: {} vstScan: {} vstScanComplete: {} + clearVsts: {} + cancelEdit: {} + deleteTrack: {} + updateOutputs: {} + showAddNewTrack: {} + showEditTrack: {} + showEditOutputs: {} + showVstSettings: {} + associateInputsWithTrack: {} + associateInstrumentWithTrack: {} + associateVSTWithTrack: {} + associateMIDIWithTrack: {} + desiredTrackType: {} }) diff --git a/web/app/assets/javascripts/react-components/mixins/SessionMyTracksMixin.js.coffee b/web/app/assets/javascripts/react-components/mixins/SessionMyTracksMixin.js.coffee index 9d808485c..858bb8ad7 100644 --- a/web/app/assets/javascripts/react-components/mixins/SessionMyTracksMixin.js.coffee +++ b/web/app/assets/javascripts/react-components/mixins/SessionMyTracksMixin.js.coffee @@ -4,9 +4,21 @@ context = window onInputsChanged: (sessionMixers) -> + @sessionMixers = sessionMixers - session = sessionMixers.session - mixers = sessionMixers.mixers + @recompute() + + onConfigureTracksChanged: (configureTracks) -> + + @configureTracks = configureTracks + + @recompute() + + recompute: () -> + return if !@sessionMixers? + + session = @sessionMixers.session + mixers = @sessionMixers.mixers tracks = [] @@ -38,11 +50,21 @@ context = window instrumentIcon = context.JK.getInstrumentIcon45(track.instrument_id); trackName = "#{name}: #{track.instrument}" - tracks.push({track: track, mixerFinder: mixerFinder, mixers: mixerData, hasMixer:hasMixer, name: name, trackName: trackName, instrumentIcon: instrumentIcon, photoUrl: photoUrl, clientId: participant.client_id}) + associatedVst = null + # find any VST info + if hasMixer && @configureTracks? + for vst in @configureTracks.vstTrackAssignments.vsts + if vst.track == mixerData.mixer.track - 1 && vst.name != 'NONE' + logger.debug("found VST on track", vst, track) + associatedVst = vst + break + tracks.push({track: track, mixerFinder: mixerFinder, mixers: mixerData, hasMixer:hasMixer, name: name, trackName: trackName, instrumentIcon: instrumentIcon, photoUrl: photoUrl, clientId: participant.client_id, associatedVst: associatedVst}) else logger.warn("SessionMyTracks: unable to find participant") this.setState(tracks: tracks, session:session, chat: chat) + + } \ No newline at end of file diff --git a/web/app/assets/javascripts/react-components/stores/ConfigureTracksStore.js.coffee b/web/app/assets/javascripts/react-components/stores/ConfigureTracksStore.js.coffee index dbcb830dd..eea6c8bd1 100644 --- a/web/app/assets/javascripts/react-components/stores/ConfigureTracksStore.js.coffee +++ b/web/app/assets/javascripts/react-components/stores/ConfigureTracksStore.js.coffee @@ -24,21 +24,39 @@ bool VST_EnableMidiForTrack(const QString& trackId, bool enableMidi, int midiDev { listenables: ConfigureTracksActions - musicPorts: {inputs:[], outputs:[]} + musicPorts: {inputs: [], outputs: []} trackNumber: null editingTrack: null + vstPluginList: {vsts: []} + vstTrackAssignments: {vsts: []} + attachedMidiDevices: {midiDevices: []} + midiTrackAssignments: {tracks: []} + trackType: 'audio' + init: () -> + this.listenTo(context.AppStore, this.onAppInit) + this.listenTo(context.MixerStore, this.onMixersChanged) onAppInit: (@app) -> + editingTrackValid: () -> + true + + onMixersChanged: (mixers) -> + @loadChannels() + @loadTrackInstruments() + @changed() + onReset: () -> logger.debug("ConfigureTracksStore:reset", this) @trackNumber = null @editingTrack = null @loadChannels() - @onVstScan() - @onMidiScan() + @loadTrackInstruments() + @performVstScan() + @performMidiScan() + @changed() onTrySave: () -> @@ -48,29 +66,88 @@ bool VST_EnableMidiForTrack(const QString& trackId, bool enableMidi, int midiDev trySave: () -> onVstScan: () -> + @performVstScan(true) + + performVstScan: (sendChanged) -> @hasVst = context.jamClient.hasVstHost() logger.debug("hasVst", @hasVst) if @hasVst logger.debug("vstScan starting") - result = context.jamClient.VSTScan() + result = context.jamClient.VSTScan("window.ConfigureTracksStore.onVstScanComplete") logger.debug("vstScan completed", result) - @vstPluginList = context.jamClient.VSTListVsts() - @vstTrackAssignments = context.jamClient.VSTListTrackAssignments() + onClearVsts: () -> + context.jamClient.VSTClearAll() - console.log("@vstPluginList", @vstPluginList) - console.log("@vstTrackAssignments", @vstTrackAssignments) + setTimeout((() => + @listVsts() + + @changed() + ), 250) + + onVstScanComplete: () -> + @listVsts() + @changed() + + + listVsts: () -> + + @vstPluginList = context.jamClient.VSTListVsts() + @vstTrackAssignments = context.jamClient.VSTListTrackAssignments() + + console.log("@vstTrackAssignments", @vstTrackAssignments) onMidiScan: () -> - @attachedMidiDevices = context.jamClient.listAttachedMidiDevices(); + @performMidiScan() + @changed() - console.log("@attachedMidiDevices", @attachedMidiDevices) + performMidiScan: () -> + context.jamClient.VST_ScanForMidiDevices(); + @attachedMidiDevices = context.jamClient.VST_GetMidiDeviceList(); - onShowVstSettings: () -> - context.jamClient.showHideVstGui(true, @trackNumber) if @trackNumber? + # trackNumber is 0-based, and optional + onShowVstSettings: (trackNumber) -> + if !trackNumber? + trackNumber = @trackNumber - 1 if @trackNumber? + + logger.debug("show VST GUI", trackNumber) + + context.jamClient.VSTShowHideGui(true, trackNumber) if trackNumber? changed: () -> - @item = {musicPorts: @musicPorts, trackAssignments: @trackAssignments, trackNumber: @trackNumber, editingTrack: @editingTrack} + @editingTrack = [] + @editingTrack.assignment = @trackNumber + + if @trackNumber? + + for inputsForTrack in @trackAssignments.inputs.assigned + if inputsForTrack.assignment == @trackNumber + @editingTrack = inputsForTrack + break + + # slap on vst, if any, from list of vst assignments + for vst in @vstTrackAssignments.vsts + if vst.track == @editingTrack.assignment - 1 + @editingTrack.vst = vst + @editingTrack.midiDeviceIndex = vst.midiDeviceIndex + break + + if @editingTrack.vst? + logger.debug("current track has a VST assigned:" + @editingTrack.vst.file) + + @item = { + musicPorts: @musicPorts, + trackAssignments: @trackAssignments, + trackNumber: @trackNumber, + editingTrack: @editingTrack, + vstPluginList: @vstPluginList, + vstTrackAssignments: @vstTrackAssignments, + attachedMidiDevices: @attachedMidiDevices, + nextTrackNumber: @nextTrackNumber, + newTrack: @newTrack, + midiTrackAssignments: @midiTrackAssignments, + trackType: @trackType + } @trigger(@item) @@ -80,7 +157,9 @@ bool VST_EnableMidiForTrack(const QString& trackId, bool enableMidi, int midiDev @musicPorts = context.jamClient.FTUEGetChannels() # let's populate this bad boy - @trackAssignments = {inputs: {unassigned:[], assigned:[], chat:[]}, outputs: {unassigned:[], assigned: []}} + @trackAssignments = {inputs: {unassigned: [], assigned: [], chat: []}, outputs: {unassigned: [], assigned: []}} + + nextTrackNumber = 0 for input in @musicPorts.inputs if input.assignment == ASSIGNMENT.UNASSIGNED @@ -88,6 +167,8 @@ bool VST_EnableMidiForTrack(const QString& trackId, bool enableMidi, int midiDev else if input.assignment == ASSIGNMENT.CHAT @trackAssignments.inputs.chat.push(input) else + nextTrackNumber = input.assignment if input.assignment > nextTrackNumber + # make sure this assignment isn't already preset (you can have multiple inputs per 'track slot') found = false for assigned in @trackAssignments.inputs.assigned @@ -105,29 +186,196 @@ bool VST_EnableMidiForTrack(const QString& trackId, bool enableMidi, int midiDev else @trackAssignments.outputs.unassigned.push(output) - @loadTrackInstruments(forceInputsToUnassign) + @nextTrackNumber = nextTrackNumber + 1 - @changed() loadTrackInstruments: (forceInputsToUnassign) -> for inputsForTrack in @trackAssignments.inputs.assigned - clientInstrument = context.jamClient.TrackGetInstrument(inputsForTrack[0].assignment) + clientInstrument = context.jamClient.TrackGetInstrument(inputsForTrack.assignment) instrument = context.JK.client_to_server_instrument_map[clientInstrument]; inputsForTrack.instrument_id = instrument?.server_id + + onAssociateInputsWithTrack: (inputId1, inputId2) -> + return unless @trackNumber? + + for inputs in @editingTrack + context.jamClient.TrackSetAssignment(inputs.id, true, ASSIGNMENT.UNASSIGNED) + + if inputId1? + logger.debug("setting input1 #{inputId1} to #{@trackNumber}") + context.jamClient.TrackSetAssignment(inputId1, true, @trackNumber) + + if inputId2? + logger.debug("setting input2 #{inputId2} to #{@trackNumber}") + context.jamClient.TrackSetAssignment(inputId2, true, @trackNumber) + + result = context.jamClient.TrackSaveAssignments(); + + if(!result || result.length == 0) + + else + context.JK.Banner.showAlert('Unable to save assignments. ' + result); + + onAssociateInstrumentWithTrack: (instrumentId) -> + return unless @trackNumber? + + logger.debug("context.jamClient.TrackSetInstrument(trackNumber, track.instrument_id)", @trackNumber, instrumentId) + + if instrumentId != null && instrumentId != '' + context.jamClient.TrackSetInstrument(@trackNumber, context.JK.instrument_id_to_instrument[instrumentId].client_id) + else + context.jamClient.TrackSetInstrument(@trackNumber, 0) + + if(!result || result.length == 0) + + else + context.JK.Banner.showAlert('Unable to save assignments. ' + result); + + + result = context.jamClient.TrackSaveAssignments() + + if(!result || result.length == 0) + + else + context.JK.Banner.showAlert('Unable to save assignments. ' + result); + + onAssociateVSTWithTrack: (vst) -> + if vst? + logger.debug("associating track:#{@trackNumber} with VST:#{vst.file}") + + found = null + for knownVst in @vstPluginList.vsts + if knownVst.file == vst.file + found = knownVst + break + if found? + context.jamClient.VSTSetTrackAssignment(found, @trackNumber - 1) + else + logger.error("unable to locate vst for #{vst}") + else + logger.debug("unassociated track:#{@trackNumber} with VST") + # no way to unset VST assignment yet + + setTimeout((() => ( + @listVsts() + + @changed() + )), 250) + + onCancelEdit: () -> + if @newTrack + for input in @editingTrack + context.jamClient.TrackSetAssignment(input.id, true, ASSIGNMENT.UNASSIGNED) + result = context.jamClient.TrackSaveAssignments() + if(!result || result.length == 0) + + else + context.JK.Banner.showAlert('Unable to save assignments. ' + result); + else + logger.error("unable to process cancel for an existing track") + + onDeleteTrack: (assignment) -> + track = null + for inputsForTrack in @trackAssignments.inputs.assigned + if inputsForTrack.assignment == assignment + track = inputsForTrack + break + + if track? + for input in inputsForTrack + context.jamClient.TrackSetAssignment(input.id, true, ASSIGNMENT.UNASSIGNED) + result = context.jamClient.TrackSaveAssignments() + + if(!result || result.length == 0) + + else + context.JK.Banner.showAlert('Unable to save assignments. ' + result); + else + logger.error("unable to find track to delete") + + onShowAddNewTrack: () -> + + # check if we have what we need... namely, free ports + + if @trackAssignments.inputs.unassigned.length == 0 + context.JK.Banner.showAlert('You have no more unassigned input ports.

You can free some up by editing an AUDIO track.') + return + + @openLiveTrackDialog(@nextTrackNumber) + + onShowEditTrack: (trackNumber) -> + @openLiveTrackDialog(trackNumber) + openLiveTrackDialog: (trackNumber) -> @trackNumber = trackNumber + logger.debug("opening live track dialog for track #{trackNumber}") - @editingTrack = null + @newTrack = true for inputsForTrack in @trackAssignments.inputs.assigned - if inputsForTrack.assignment == trackNumber - @editingTrack = inputsForTrack + logger.debug("inputsForTrack.assignment @trackNumber", inputsForTrack.assignment, @trackNumber ) + if inputsForTrack.assignment == @trackNumber + @newTrack = false break + if @newTrack + @trackType = 'audio' + else + if @trackNumber == 1 + @trackType = 'audio' + else + @trackType = 'audio' + for trackAssignment in @vstTrackAssignments.vsts + if trackAssignment.track == @trackNumber - 1 + if trackAssignment.midiDeviceIndex > -1 + logger.debug("editing midi track") + @trackType = 'midi' + break + + if @newTrack + # ensure that we always have an instrument set (50 = electric guitar + context.jamClient.TrackSetInstrument(@trackNumber, 50) + @changed() @app.layout.showDialog('configure-live-tracks-dialog') + + onDesiredTrackType: (trackType) -> + @trackType = trackType + + @changed() + + onUpdateOutputs: (outputId1, outputId2) -> + + context.jamClient.TrackSetAssignment(outputId1, true, ASSIGNMENT.OUTPUT); + context.jamClient.TrackSetAssignment(outputId2, true, ASSIGNMENT.OUTPUT); + + result = context.jamClient.TrackSaveAssignments(); + + if(!result || result.length == 0) + + else + context.JK.Banner.showAlert('Unable to save assignments. ' + result); + + onShowEditOutputs: () -> + @app.layout.showDialog('configure-outputs-dialog') + + onAssociateMIDIWithTrack: (midiInterface) -> + + if !midiInterface? || midiInterface == '' + logger.debug("disabling midiInterface:#{midiInterface}, track:#{@trackNumber - 1}") + context.jamClient.VST_EnableMidiForTrack(@trackNumber - 1, false, 0) + else + logger.debug("enabling midiInterface:#{midiInterface}, track:#{@trackNumber - 1}") + context.jamClient.VST_EnableMidiForTrack(@trackNumber - 1, true, midiInterface) + + setTimeout((() => ( + @listVsts() + + @changed() + )), 250) + } ) \ No newline at end of file diff --git a/web/app/assets/javascripts/react-components/stores/SessionStore.js.coffee b/web/app/assets/javascripts/react-components/stores/SessionStore.js.coffee index b0bbb0ac7..08e291141 100644 --- a/web/app/assets/javascripts/react-components/stores/SessionStore.js.coffee +++ b/web/app/assets/javascripts/react-components/stores/SessionStore.js.coffee @@ -10,6 +10,7 @@ SessionActions = @SessionActions RecordingActions = @RecordingActions NotificationActions = @NotificationActions VideoActions = @VideoActions +ConfigureTracksActions = @ConfigureTracksActions @SessionStore = Reflux.createStore( { @@ -731,6 +732,8 @@ VideoActions = @VideoActions $(document).trigger(EVENTS.SESSION_STARTED, {session: {id: @currentSessionId}}) if document @handleAutoOpenJamTrack() + + ConfigureTracksActions.reset() ) .fail((xhr) => @updateCurrentSession(null) diff --git a/web/app/assets/javascripts/wizard/gear/step_configure_tracks.js b/web/app/assets/javascripts/wizard/gear/step_configure_tracks.js index 81c330620..e40b81609 100644 --- a/web/app/assets/javascripts/wizard/gear/step_configure_tracks.js +++ b/web/app/assets/javascripts/wizard/gear/step_configure_tracks.js @@ -21,14 +21,14 @@ } function handleNext() { - var saved = configureTracksHelper.trySave(); + /** var saved = configureTracksHelper.trySave(); if(saved) { context.JK.GA.trackConfigureTracksCompletion(context.JK.detectOS()); successfullyAssignedOnce = true; } - - return saved; +*/ + return context.ConfigureTracksStore.editingTrackValid() } function newSession() { @@ -38,7 +38,8 @@ function beforeShow() { var forceInputsToUnassigned = !successfullyAssignedOnce; - configureTracksHelper.reset(forceInputsToUnassigned, wizard.getChosenInputs()) + window.ConfigureTracksActions.reset(forceInputsToUnassigned, wizard.getChosenInputs()); + //configureTracksHelper.reset(forceInputsToUnassigned, wizard.getChosenInputs()) } function initialize(_$step, _wizard) { diff --git a/web/app/assets/stylesheets/client/client.css b/web/app/assets/stylesheets/client/client.css index 277f98cdb..1c0385193 100644 --- a/web/app/assets/stylesheets/client/client.css +++ b/web/app/assets/stylesheets/client/client.css @@ -53,6 +53,8 @@ *= require dialogs/dialog *= require ./iconInstrumentSelect *= require ./muteSelect + *= require ./manageVsts + *= require ./vstEffects *= require ./metronomePlaybackModeSelect *= require ./terms *= require ./createSession diff --git a/web/app/assets/stylesheets/client/manageVsts.css.scss b/web/app/assets/stylesheets/client/manageVsts.css.scss new file mode 100644 index 000000000..a7063e390 --- /dev/null +++ b/web/app/assets/stylesheets/client/manageVsts.css.scss @@ -0,0 +1,23 @@ +@import "client/common"; + +.manage-vsts-popup { + .bt-content { + height:38px; + width:190px; + background-color:#333; + overflow:auto; + border:1px solid #ED3618; + text-align:left; + font-family: 'Raleway', Arial, Helvetica, sans-serif; + ul { + @include vertical-align-column; + height:100%; + margin-left: 0 !important; + } + li { + font-size:12px; + margin-left:0 !important; + list-style-type: none; + } + } +} \ No newline at end of file diff --git a/web/app/assets/stylesheets/client/react-components/ConfigureTracks.css.scss b/web/app/assets/stylesheets/client/react-components/ConfigureTracks.css.scss index f76f8276d..72d90891a 100644 --- a/web/app/assets/stylesheets/client/react-components/ConfigureTracks.css.scss +++ b/web/app/assets/stylesheets/client/react-components/ConfigureTracks.css.scss @@ -10,7 +10,7 @@ height:220px; .live-tracks { - height:150px; + height:165px; overflow:auto; a { margin-left:3px; @@ -19,6 +19,12 @@ .live-input { display:inline-block; + &:before { + content: '(' + } + &:after { + content: ')' + } } .live-track { margin-bottom:20px; @@ -36,12 +42,44 @@ @include border_box_sizing; width:60%; display:inline-block; + + &.one { + .live-input { + width:50%; + @include border_box_sizing; + } + } + + &.two { + .live-input { + max-width:40%; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + @include border_box_sizing; + &:nth-of-type(1) { + padding-right:5px; + } + &:nth-of-type(2) { + padding-left:5px; + } + } + } + } + + .track-type-label { + display:inline-block; + white-space:nowrap; + padding-right:7px; } .plugin-info { @include border_box_sizing; width:30%; display:inline-block; + white-space:nowrap; + text-overflow: ellipsis; + overflow:hidden; } .plugin-instrument { @@ -88,6 +126,12 @@ .output { display:inline-block; margin-bottom:0 !important; + &:before { + content: '(' + } + &:after { + content: ')' + } } } h3 { @@ -117,8 +161,8 @@ } .live-track-actions { display:block; - padding-left:30px; - margin-top:10px; + padding-left: 17px; + margin-top: 5px; a { font-size:12px; } diff --git a/web/app/assets/stylesheets/client/vstEffects.css.scss b/web/app/assets/stylesheets/client/vstEffects.css.scss new file mode 100644 index 000000000..945e49b10 --- /dev/null +++ b/web/app/assets/stylesheets/client/vstEffects.css.scss @@ -0,0 +1,39 @@ +@import "client/common"; + +.vst-effects-popup { + margin-top: 3px; + margin-left: 120px; + + .bt-content { + width:220px; + background-color:#333; + overflow:auto; + border:1px solid #ED3618; + text-align:left; + font-family: 'Raleway', Arial, Helvetica, sans-serif; + ul { + @include vertical-align-column; + height:100%; + margin-left: 0 !important; + } + li { + font-size:12px; + margin-left:0 !important; + list-style-type: none; + margin-bottom:0 !important; + + padding:7px 0 10px 0; + + .vst-name { + text-overflow:ellipsis; + overflow:hidden; + } + &:nth-of-type(1) { + + border-width:0 0 1px 0 !important; + border-style:solid !important;; + border-color:#ED3618 !important;; + } + } + } +} \ No newline at end of file diff --git a/web/app/assets/stylesheets/client/wizard/gearWizard.css.scss b/web/app/assets/stylesheets/client/wizard/gearWizard.css.scss index 5e6084efc..d7bbb1f80 100644 --- a/web/app/assets/stylesheets/client/wizard/gearWizard.css.scss +++ b/web/app/assets/stylesheets/client/wizard/gearWizard.css.scss @@ -197,7 +197,7 @@ width: 25%; &:nth-of-type(2) { - width: 21%; + width: 71%; } &:nth-of-type(3) { @@ -217,9 +217,64 @@ margin-top: 45px; } - .output-channels, .unassigned-output-channels { + .outputs-view { display:none; } + + .inputs-view { + padding:0; + border-width:0; + + h3.session-audio-inputs-header { + font-weight:normal; + width:70%; + font-size:14px; + color:white; + } + + h3.plugin-header { + width:20%; + font-size:14px; + color:white; + } + + h3.instrument-header { + width:10%; + font-size:14px; + color:white; + } + + .input-track-info { + width:70%; + } + + .plugin-info { + width:20%; + } + + .plugin-instrument { + width:10%; + } + + .live-tracks { + height:198px; + } + .live-input { + display:block; + max-width:90%; + &:before { + content: '' + } + &:after { + content: '' + } + } + .input-track-info .live-input { + padding-left:19px; + padding-right:0; + } + .add-track-action { text-align:center;} + } } .wizard-step[layout-wizard-step="3"] { diff --git a/web/app/assets/stylesheets/dialogs/configureLiveTracksDialog.css.scss b/web/app/assets/stylesheets/dialogs/configureLiveTracksDialog.css.scss index b87510eab..684cd70b3 100644 --- a/web/app/assets/stylesheets/dialogs/configureLiveTracksDialog.css.scss +++ b/web/app/assets/stylesheets/dialogs/configureLiveTracksDialog.css.scss @@ -13,6 +13,9 @@ margin-bottom:10px; } + .manage-audio-plugins { + font-size:12px; + } .actions { clear:both; text-align:center; @@ -20,7 +23,6 @@ .track-type { width:100%; @include border_box_sizing; - margin-bottom:20px; padding:10px; .track-type-option { @@ -37,7 +39,7 @@ } } .audio-input-ports { - width:50%; + width:60%; @include border_box_sizing; float:left; margin-bottom:40px; @@ -53,18 +55,99 @@ line-height:125%; } } - .instrument-selection { - width:50%; - @include border_box_sizing; - float:left; - margin-bottom:40px; - padding:10px; + + .audio { + .instrument-selection { + width:40%; + @include border_box_sizing; + float:left; + margin-bottom:26px; + padding:10px; + select { + width:90%; + } + } } - .audio-effects { - width:50%; + + .midi-interface { @include border_box_sizing; float:left; margin-bottom:20px; padding:10px; + position:relative; + width:50%; + + select { + width:80%; + margin-bottom:10px; + } + a { + font-size:12px; + } + } + .midi { + .instrument-selection { + width:50%; + @include border_box_sizing; + float:left; + margin-bottom:26px; + padding:10px; + select { + width:80%; + } + } + } + + .midi-instrument { + @include border_box_sizing; + float:left; + margin-bottom:20px; + padding:10px; + position:relative; + width:50%; + clear:both; + + select { + width:80%; + margin-bottom:10px; + } + } + .audio-effects { + width:40%; + @include border_box_sizing; + float:left; + margin-bottom:20px; + padding:10px; + position:relative; + + select { + width:90%; + margin-bottom:20px; + } + + a.manage-audio-plugins { + position:relative; + } + .down-arrow { + cursor:pointer; + width: 0; + height: 0; + border-left: 8px solid transparent; + border-right: 8px solid transparent; + border-top: 8px solid #fc0; + position: absolute; + top: 2px; + right: -20px; + } + .settings-holder { + right:10%; + text-align:right; + position:absolute; + margin-top: -27px; + @include border_box_sizing; + padding: 10px 0 10px 10px; + } + a.button-orange { + } } } \ No newline at end of file diff --git a/web/app/assets/stylesheets/dialogs/configureOutputsDialog.css.scss b/web/app/assets/stylesheets/dialogs/configureOutputsDialog.css.scss new file mode 100644 index 000000000..b0e9e28a4 --- /dev/null +++ b/web/app/assets/stylesheets/dialogs/configureOutputsDialog.css.scss @@ -0,0 +1,36 @@ +@import "client/common"; + +#configure-outputs-dialog { + width: 425px; + + .dialog-inner { + width: auto; + } + + h3 { + color: white; + font-weight: bold; + margin-bottom: 10px; + } + + .actions { + clear: both; + text-align: center; + } + + p { + margin-bottom:10px; + line-height:125%; + } + + select { + width: 100%; + + &.output-1 { + margin-bottom:15px; + } + &.output-2 { + margin-bottom:50px; + } + } +} \ No newline at end of file diff --git a/web/app/assets/stylesheets/dialogs/configureTracksDialog.css.scss b/web/app/assets/stylesheets/dialogs/configureTracksDialog.css.scss index 46b1bd830..aa776ede7 100644 --- a/web/app/assets/stylesheets/dialogs/configureTracksDialog.css.scss +++ b/web/app/assets/stylesheets/dialogs/configureTracksDialog.css.scss @@ -2,8 +2,6 @@ @charset "UTF-8"; #configure-tracks-dialog { - min-height: 700px; - max-height: 700px; width:800px; &[current-screen="account/audio"] { @@ -120,21 +118,21 @@ } .buttons { - bottom: 25px; - position: absolute; - right: 25px; - left:25px; + position:static; + margin-top:20px; + text-align:center; } .btn-add-new-audio-gear { - float:left; + position:absolute; + left:20px; } .btn-cancel { - float:right; + } .btn-update-settings { - float:right; + } } \ No newline at end of file diff --git a/web/app/views/clients/_manageVsts.html.slim b/web/app/views/clients/_manageVsts.html.slim new file mode 100644 index 000000000..d78dcc704 --- /dev/null +++ b/web/app/views/clients/_manageVsts.html.slim @@ -0,0 +1,7 @@ +script type='text/template' id='template-manage-vsts' + ul + li data-manage-vst-option="scan" + a href='#' scan for new or updated plugins + + li data-manage-vst-option="clear" + a href='#' clear plug-in list \ No newline at end of file diff --git a/web/app/views/clients/_vstEffects.html.slim b/web/app/views/clients/_vstEffects.html.slim new file mode 100644 index 000000000..4085f9ecc --- /dev/null +++ b/web/app/views/clients/_vstEffects.html.slim @@ -0,0 +1,9 @@ +script type='text/template' id='template-vst-effects' + ul + li data-manage-vst-option="open-vst" + a href='#' + | Open  + span.vst-name + + li data-manage-vst-option="update-track" + a href='#' Update Track . . . \ No newline at end of file diff --git a/web/app/views/clients/index.html.erb b/web/app/views/clients/index.html.erb index 962e21298..16f30f3a1 100644 --- a/web/app/views/clients/index.html.erb +++ b/web/app/views/clients/index.html.erb @@ -20,6 +20,8 @@ <%= render "jamServer" %> <%= render "iconInstrumentSelect" %> <%= render "muteSelect" %> +<%= render "manageVsts" %> +<%= render "vstEffects" %> <%= render "metronome_playback_mode" %> <%= render "clients/wizard/buttons" %> <%= render "clients/wizard/gear/gear_wizard" %> diff --git a/web/app/views/clients/wizard/gear/_gear_wizard.html.haml b/web/app/views/clients/wizard/gear/_gear_wizard.html.haml index c2c1d9b50..4f8f9ab87 100644 --- a/web/app/views/clients/wizard/gear/_gear_wizard.html.haml +++ b/web/app/views/clients/wizard/gear/_gear_wizard.html.haml @@ -81,16 +81,7 @@ .center %a.button-orange.watch-video{href:'https://www.youtube.com/watch?v=SjMeMZpKNR4', rel:'external'} WATCH VIDEO .wizard-step-column - %h2 Unassigned Ports - .unassigned-input-channels.channels-holder - .wizard-step-column - %h2 Track Input Port(s) - .tracks - .wizard-step-column - %h2 Instrument - .instruments - .output-channels - .unassigned-output-channels.channels-holder + = react_component 'ConfigureTracks', {} .wizard-step{ 'layout-wizard-step' => "3", 'dialog-title' => "Configure Voice Chat", 'dialog-purpose' => "ConfigureVoiceChat" } .ftuesteps diff --git a/web/app/views/dialogs/_configureLiveTracksDialog.html.slim b/web/app/views/dialogs/_configureLiveTracksDialog.html.slim index eace5771b..2c488ddaa 100644 --- a/web/app/views/dialogs/_configureLiveTracksDialog.html.slim +++ b/web/app/views/dialogs/_configureLiveTracksDialog.html.slim @@ -1,6 +1,2 @@ .dialog.dialog-overlay-sm.top-parent layout='dialog' layout-id='configure-live-tracks-dialog' id='configure-live-tracks-dialog' - .content-head - = image_tag "content/icon_add.png", {:height => 19, :width => 19, :class => 'content-icon'} - h1 add live track - .dialog-inner - = react_component 'ConfigureLiveTracksDialog', {} + = react_component 'ConfigureLiveTracksDialog', {} diff --git a/web/app/views/dialogs/_configureOutputsDialog.html.slim b/web/app/views/dialogs/_configureOutputsDialog.html.slim new file mode 100644 index 000000000..bb6f10e7c --- /dev/null +++ b/web/app/views/dialogs/_configureOutputsDialog.html.slim @@ -0,0 +1,2 @@ +.dialog.dialog-overlay-sm.top-parent layout='dialog' layout-id='configure-outputs-dialog' id='configure-outputs-dialog' + = react_component 'ConfigureOutputsDialog', {} diff --git a/web/app/views/dialogs/_configure_tracks_dialog.html.haml b/web/app/views/dialogs/_configure_tracks_dialog.html.haml index cb65af8eb..42d6d1d2e 100644 --- a/web/app/views/dialogs/_configure_tracks_dialog.html.haml +++ b/web/app/views/dialogs/_configure_tracks_dialog.html.haml @@ -4,7 +4,7 @@ %h1 configure tracks .dialog-inner .dialog-tabs - %a.selected.tab-configure-audio Music Audio + %a.selected.tab-configure-audio Inputs & Outputs %a.tab-configure-voice Voice Chat .instructions @@ -48,5 +48,6 @@ .buttons %a.btn-add-new-audio-gear.button-grey{'layout-link' => 'add-new-audio-gear'} ADD NEW AUDIO GEAR - %a.button-orange.btn-update-settings{href:'#'} UPDATE SETTINGS %a.button-grey.btn-cancel{href:'#'} CANCEL + %a.button-orange.btn-update-settings{href:'#'} SAVE SETTINGS + diff --git a/web/app/views/dialogs/_dialogs.html.haml b/web/app/views/dialogs/_dialogs.html.haml index 761559fdf..2c45d7df2 100644 --- a/web/app/views/dialogs/_dialogs.html.haml +++ b/web/app/views/dialogs/_dialogs.html.haml @@ -44,4 +44,5 @@ = render 'dialogs/recordingSelectorDialog' = render 'dialogs/soundCloudPlayerDialog' = render 'dialogs/deleteVideoConfirmDialog' -= render 'dialogs/configureLiveTracksDialog' \ No newline at end of file += render 'dialogs/configureLiveTracksDialog' += render 'dialogs/configureOutputsDialog' \ No newline at end of file From c3626ebe229e7ae00efc5163490be7adf651c3ee Mon Sep 17 00:00:00 2001 From: Seth Call Date: Wed, 11 Nov 2015 05:39:28 -0600 Subject: [PATCH 07/19] * wip --- .../react-components/stores/ConfigureTracksStore.js.coffee | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/web/app/assets/javascripts/react-components/stores/ConfigureTracksStore.js.coffee b/web/app/assets/javascripts/react-components/stores/ConfigureTracksStore.js.coffee index eea6c8bd1..c97387807 100644 --- a/web/app/assets/javascripts/react-components/stores/ConfigureTracksStore.js.coffee +++ b/web/app/assets/javascripts/react-components/stores/ConfigureTracksStore.js.coffee @@ -86,9 +86,10 @@ bool VST_EnableMidiForTrack(const QString& trackId, bool enableMidi, int midiDev ), 250) onVstScanComplete: () -> - @listVsts() - @changed() - + setTimeout((() => + @listVsts() + @changed() + ), 0) listVsts: () -> From 5fad4953bcb46c45c3538a82523d3e11670c92ba Mon Sep 17 00:00:00 2001 From: Seth Call Date: Fri, 13 Nov 2015 14:00:37 -0600 Subject: [PATCH 08/19] * vst working --- .../ConfigureLiveTracksDialog.js.jsx.coffee | 3 +- .../ConfigureTracks.js.jsx.coffee | 2 +- .../mixins/SessionMyTracksMixin.js.coffee | 1 + .../stores/ConfigureTracksStore.js.coffee | 49 ++++++++++++++----- web/config/application.rb | 2 + web/config/environments/development.rb | 2 + web/config/initializers/gon.rb | 1 + 7 files changed, 45 insertions(+), 15 deletions(-) diff --git a/web/app/assets/javascripts/react-components/ConfigureLiveTracksDialog.js.jsx.coffee b/web/app/assets/javascripts/react-components/ConfigureLiveTracksDialog.js.jsx.coffee index 6fda3639c..5beede426 100644 --- a/web/app/assets/javascripts/react-components/ConfigureLiveTracksDialog.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/ConfigureLiveTracksDialog.js.jsx.coffee @@ -122,12 +122,11 @@ ConfigureTracksStore = @ConfigureTracksStore midiInstruments = [] instruments = [] - instruments.push(``) for displayName, value of context.JK.server_to_client_instrument_map instruments.push(``) selectedMidiInterface = '' - selectedInstrument = '' + selectedInstrument = context.JK.client_to_server_instrument_map[50].server_id # default to electric guitar selectedMidiInstrument = '' instrumentDisabled = true diff --git a/web/app/assets/javascripts/react-components/ConfigureTracks.js.jsx.coffee b/web/app/assets/javascripts/react-components/ConfigureTracks.js.jsx.coffee index 16d89786a..24cd6a140 100644 --- a/web/app/assets/javascripts/react-components/ConfigureTracks.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/ConfigureTracks.js.jsx.coffee @@ -41,7 +41,7 @@ gearUtils = context.JK.GearUtils trackTypeLabel = 'AUDIO' vstName = 'None' - if inputsForTrack.vst + if inputsForTrack.vst? && inputsForTrack.vst != 'NONE' vstName = "#{inputsForTrack.vst.name} by #{inputsForTrack.vst.manuf}" liveTracks.push( diff --git a/web/app/assets/javascripts/react-components/mixins/SessionMyTracksMixin.js.coffee b/web/app/assets/javascripts/react-components/mixins/SessionMyTracksMixin.js.coffee index 858bb8ad7..5b8f55e99 100644 --- a/web/app/assets/javascripts/react-components/mixins/SessionMyTracksMixin.js.coffee +++ b/web/app/assets/javascripts/react-components/mixins/SessionMyTracksMixin.js.coffee @@ -54,6 +54,7 @@ context = window associatedVst = null # find any VST info if hasMixer && @configureTracks? + console.log("checking associations", @configureTracks.vstTrackAssignments.vsts, mixerData.mixer) for vst in @configureTracks.vstTrackAssignments.vsts if vst.track == mixerData.mixer.track - 1 && vst.name != 'NONE' logger.debug("found VST on track", vst, track) diff --git a/web/app/assets/javascripts/react-components/stores/ConfigureTracksStore.js.coffee b/web/app/assets/javascripts/react-components/stores/ConfigureTracksStore.js.coffee index c97387807..87339f22c 100644 --- a/web/app/assets/javascripts/react-components/stores/ConfigureTracksStore.js.coffee +++ b/web/app/assets/javascripts/react-components/stores/ConfigureTracksStore.js.coffee @@ -33,7 +33,6 @@ bool VST_EnableMidiForTrack(const QString& trackId, bool enableMidi, int midiDev midiTrackAssignments: {tracks: []} trackType: 'audio' - init: () -> this.listenTo(context.AppStore, this.onAppInit) this.listenTo(context.MixerStore, this.onMixersChanged) @@ -69,7 +68,7 @@ bool VST_EnableMidiForTrack(const QString& trackId, bool enableMidi, int midiDev @performVstScan(true) performVstScan: (sendChanged) -> - @hasVst = context.jamClient.hasVstHost() + @hasVst = gon.global.vst_enabled & context.jamClient.hasVstHost() logger.debug("hasVst", @hasVst) if @hasVst logger.debug("vstScan starting") @@ -103,11 +102,19 @@ bool VST_EnableMidiForTrack(const QString& trackId, bool enableMidi, int midiDev @changed() performMidiScan: () -> + + if !@hasVst + logger.debug("performMidiScan skipped due to no VST") + return context.jamClient.VST_ScanForMidiDevices(); @attachedMidiDevices = context.jamClient.VST_GetMidiDeviceList(); # trackNumber is 0-based, and optional onShowVstSettings: (trackNumber) -> + if !@hasVst + logger.debug("onShowVstSettings skipped due to no VST") + return + if !trackNumber? trackNumber = @trackNumber - 1 if @trackNumber? @@ -126,15 +133,16 @@ bool VST_EnableMidiForTrack(const QString& trackId, bool enableMidi, int midiDev @editingTrack = inputsForTrack break - # slap on vst, if any, from list of vst assignments - for vst in @vstTrackAssignments.vsts - if vst.track == @editingTrack.assignment - 1 - @editingTrack.vst = vst - @editingTrack.midiDeviceIndex = vst.midiDeviceIndex - break - if @editingTrack.vst? - logger.debug("current track has a VST assigned:" + @editingTrack.vst.file) + # slap on vst, if any, from list of vst assignments + for vst in @vstTrackAssignments.vsts + if vst.track == @editingTrack.assignment - 1 + @editingTrack.vst = vst + @editingTrack.midiDeviceIndex = vst.midiDeviceIndex + break + + if @editingTrack.vst? + logger.debug("current track has a VST assigned:" + @editingTrack.vst.file) @item = { musicPorts: @musicPorts, @@ -192,8 +200,15 @@ bool VST_EnableMidiForTrack(const QString& trackId, bool enableMidi, int midiDev loadTrackInstruments: (forceInputsToUnassign) -> for inputsForTrack in @trackAssignments.inputs.assigned + clientInstrument = context.jamClient.TrackGetInstrument(inputsForTrack.assignment) + if clientInstrument == 0 + logger.debug("defaulting track instrument for assignment #{@trackNumber}") + # ensure that we always have an instrument set (50 = electric guitar + context.jamClient.TrackSetInstrument(inputsForTrack.assignment, 50) + clientInstrument = 50 + instrument = context.JK.client_to_server_instrument_map[clientInstrument]; inputsForTrack.instrument_id = instrument?.server_id @@ -244,6 +259,11 @@ bool VST_EnableMidiForTrack(const QString& trackId, bool enableMidi, int midiDev context.JK.Banner.showAlert('Unable to save assignments. ' + result); onAssociateVSTWithTrack: (vst) -> + + if !@hasVst + logger.debug("onAssociateVSTWithTrack skipped due to no VST") + return + if vst? logger.debug("associating track:#{@trackNumber} with VST:#{vst.file}") @@ -336,8 +356,13 @@ bool VST_EnableMidiForTrack(const QString& trackId, bool enableMidi, int midiDev break if @newTrack - # ensure that we always have an instrument set (50 = electric guitar - context.jamClient.TrackSetInstrument(@trackNumber, 50) + + assignment = context.jamClient.TrackGetInstrument(@trackNumber) + + if assignment == 0 + logger.debug("defaulting track instrument for assignment #{@trackNumber}") + # ensure that we always have an instrument set (50 = electric guitar + context.jamClient.TrackSetInstrument(@trackNumber, 50) @changed() diff --git a/web/config/application.rb b/web/config/application.rb index 5d9fc97d6..3ea3b0500 100644 --- a/web/config/application.rb +++ b/web/config/application.rb @@ -377,5 +377,7 @@ if defined?(Bundler) config.download_tracker_day_range = 30 config.max_user_ip_address = 10 config.max_multiple_users_same_ip = 2 + + config.vst_enabled = true end end diff --git a/web/config/environments/development.rb b/web/config/environments/development.rb index dbc9da309..fdfbb2d01 100644 --- a/web/config/environments/development.rb +++ b/web/config/environments/development.rb @@ -102,4 +102,6 @@ SampleApp::Application.configure do config.react.variant = :development config.time_shift_style = :sox # or sbsms + + config.vst_enabled = true end diff --git a/web/config/initializers/gon.rb b/web/config/initializers/gon.rb index ba60612db..d58d5fa10 100644 --- a/web/config/initializers/gon.rb +++ b/web/config/initializers/gon.rb @@ -22,4 +22,5 @@ Gon.global.jamtrack_landing_bubbles_enabled = Rails.application.config.jamtrack_ Gon.global.jamtrack_browser_bubbles_enabled = Rails.application.config.jamtrack_browser_bubbles_enabled Gon.global.bugsnag_key = Rails.application.config.bugsnag_key Gon.global.bugsnag_notify_release_stages = Rails.application.config.bugsnag_notify_release_stages +Gon.global.vst_enabled = Rails.application.config.vst_enabled Gon.global.env = Rails.env From bec9388aae86429a8fd595b8f8d375ab93f9794a Mon Sep 17 00:00:00 2001 From: Seth Call Date: Fri, 13 Nov 2015 15:35:13 -0600 Subject: [PATCH 09/19] * vst fixems --- .../ConfigureLiveTracksDialog.js.jsx.coffee | 14 ++++++++++++-- .../mixins/SessionMyTracksMixin.js.coffee | 10 +++++++++- .../stores/ConfigureTracksStore.js.coffee | 6 ++++++ .../dialogs/configureLiveTracksDialog.css.scss | 13 +++++++++++++ 4 files changed, 40 insertions(+), 3 deletions(-) diff --git a/web/app/assets/javascripts/react-components/ConfigureLiveTracksDialog.js.jsx.coffee b/web/app/assets/javascripts/react-components/ConfigureLiveTracksDialog.js.jsx.coffee index 5beede426..ea4d5a7d7 100644 --- a/web/app/assets/javascripts/react-components/ConfigureLiveTracksDialog.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/ConfigureLiveTracksDialog.js.jsx.coffee @@ -37,8 +37,15 @@ ConfigureTracksStore = @ConfigureTracksStore instrumentDisabled = true vstDisabled = true + if @state.configureTracks? + if @state.configureTracks.scanningVsts + scan = + `
+
Scanning your system
for VST & AU plug-ins...
+
` + selectedInstrument = @state.configureTracks.editingTrack.instrument_id if @state.configureTracks.editingTrack.instrument_id? if @state.configureTracks.editingTrack.length == 1 @@ -113,6 +120,7 @@ ConfigureTracksStore = @ConfigureTracksStore + {scan}
` @@ -320,7 +328,8 @@ ConfigureTracksStore = @ConfigureTracksStore @ignoreICheck = true $root = $(@getDOMNode()) $radio = $root.find('input[type="radio"]') - $radio.iCheck('enable') + #$radio.iCheck('enable') + $radio.iCheck('disable') componentDidUpdate: () -> $root = $(@getDOMNode()) @@ -330,7 +339,8 @@ ConfigureTracksStore = @ConfigureTracksStore if @state.configureTracks.editingTrack.assignment == 1 $radio.iCheck('disable') else - $radio.iCheck('enable') + $radio.iCheck('disable') + #$radio.iCheck('enable') @ignoreICheck = false diff --git a/web/app/assets/javascripts/react-components/mixins/SessionMyTracksMixin.js.coffee b/web/app/assets/javascripts/react-components/mixins/SessionMyTracksMixin.js.coffee index 5b8f55e99..7fc97a850 100644 --- a/web/app/assets/javascripts/react-components/mixins/SessionMyTracksMixin.js.coffee +++ b/web/app/assets/javascripts/react-components/mixins/SessionMyTracksMixin.js.coffee @@ -54,9 +54,17 @@ context = window associatedVst = null # find any VST info if hasMixer && @configureTracks? + + # bug in the backend; track is wrong for personal mixers (always 1), but correct for master mix + trackAssignment = -1 + if @props.mode == context.JK.MIX_MODES.MASTER + trackAssignment = mixerData.mixer.track + else + trackAssignment = mixerData.oppositeMixer?.track + console.log("checking associations", @configureTracks.vstTrackAssignments.vsts, mixerData.mixer) for vst in @configureTracks.vstTrackAssignments.vsts - if vst.track == mixerData.mixer.track - 1 && vst.name != 'NONE' + if vst.track == trackAssignment - 1 && vst.name != 'NONE' logger.debug("found VST on track", vst, track) associatedVst = vst break diff --git a/web/app/assets/javascripts/react-components/stores/ConfigureTracksStore.js.coffee b/web/app/assets/javascripts/react-components/stores/ConfigureTracksStore.js.coffee index 87339f22c..6a78c5955 100644 --- a/web/app/assets/javascripts/react-components/stores/ConfigureTracksStore.js.coffee +++ b/web/app/assets/javascripts/react-components/stores/ConfigureTracksStore.js.coffee @@ -31,6 +31,7 @@ bool VST_EnableMidiForTrack(const QString& trackId, bool enableMidi, int midiDev vstTrackAssignments: {vsts: []} attachedMidiDevices: {midiDevices: []} midiTrackAssignments: {tracks: []} + scanningVsts: false trackType: 'audio' init: () -> @@ -67,11 +68,14 @@ bool VST_EnableMidiForTrack(const QString& trackId, bool enableMidi, int midiDev onVstScan: () -> @performVstScan(true) + @changed() + performVstScan: (sendChanged) -> @hasVst = gon.global.vst_enabled & context.jamClient.hasVstHost() logger.debug("hasVst", @hasVst) if @hasVst logger.debug("vstScan starting") + @scanningVsts = true result = context.jamClient.VSTScan("window.ConfigureTracksStore.onVstScanComplete") logger.debug("vstScan completed", result) @@ -86,6 +90,7 @@ bool VST_EnableMidiForTrack(const QString& trackId, bool enableMidi, int midiDev onVstScanComplete: () -> setTimeout((() => + @scanningVsts = false @listVsts() @changed() ), 0) @@ -155,6 +160,7 @@ bool VST_EnableMidiForTrack(const QString& trackId, bool enableMidi, int midiDev nextTrackNumber: @nextTrackNumber, newTrack: @newTrack, midiTrackAssignments: @midiTrackAssignments, + scanningVsts: @scanningVsts, trackType: @trackType } diff --git a/web/app/assets/stylesheets/dialogs/configureLiveTracksDialog.css.scss b/web/app/assets/stylesheets/dialogs/configureLiveTracksDialog.css.scss index 684cd70b3..c69210e75 100644 --- a/web/app/assets/stylesheets/dialogs/configureLiveTracksDialog.css.scss +++ b/web/app/assets/stylesheets/dialogs/configureLiveTracksDialog.css.scss @@ -150,4 +150,17 @@ a.button-orange { } } + + .vstScan { + + margin-top:20px; + + .spinner-small { + float:left; + } + + span { + font-size:12px; + } + } } \ No newline at end of file From c2772398a88915e6efb5efea5b1562a4f793a0c1 Mon Sep 17 00:00:00 2001 From: Seth Call Date: Thu, 19 Nov 2015 22:07:38 -0600 Subject: [PATCH 10/19] * importer --- ruby/lib/jam_ruby/jam_track_importer.rb | 64 ++++++++++++++++--- web/app/assets/javascripts/backend_alerts.js | 3 + web/app/assets/javascripts/globals.js | 3 +- .../actions/ConfigureTracksActions.js.coffee | 1 + web/lib/tasks/jam_tracks.rake | 10 +++ 5 files changed, 71 insertions(+), 10 deletions(-) diff --git a/ruby/lib/jam_ruby/jam_track_importer.rb b/ruby/lib/jam_ruby/jam_track_importer.rb index 0cdf0160f..22b8fc929 100644 --- a/ruby/lib/jam_ruby/jam_track_importer.rb +++ b/ruby/lib/jam_ruby/jam_track_importer.rb @@ -29,6 +29,7 @@ module JamRuby end def finish(reason, detail) + @@log.info("JamTrackImporter:#{self.name} #{reason}") self.reason = reason self.detail = detail end @@ -231,6 +232,7 @@ module JamRuby original_artist = parsed_metalocation[1] name = parsed_metalocation[2] + JamTrackImporter.summaries[:unique_artists] << original_artist success = dry_run_metadata(metadata, original_artist, name) @@ -269,6 +271,11 @@ module JamRuby @storage_format == 'Tency' end + def is_tim_tracks_storage? + assert_storage_set + @storage_format == 'TimTracks' + end + def assert_storage_set raise "no storage_format set" if @storage_format.nil? end @@ -276,7 +283,7 @@ module JamRuby def parse_metalocation(metalocation) # metalocation = mapped/4 Non Blondes - What's Up - 6475/meta.yml - if is_tency_storage? + if is_tency_storage? || is_tim_tracks_storage? suffix = '/meta.yml' @@ -305,15 +312,20 @@ module JamRuby return nil end - last_dash = metalocation.rindex('-') - if last_dash - song = metalocation[(first_dash+3)...last_dash].strip - bits << song + if is_tim_tracks_storage? + song = metalocation[(first_dash+3)..-1].strip else - finish("invalid_metalocation", "metalocation not valid #{metalocation}") - return nil + last_dash = metalocation.rindex('-') + if last_dash + song = metalocation[(first_dash+3)...last_dash].strip + bits << song + else + finish("invalid_metalocation", "metalocation not valid #{metalocation}") + return nil + end end + bits << 'meta.yml' bits else @@ -516,6 +528,9 @@ module JamRuby jam_track.vendor_id = metadata[:id] jam_track.licensor = JamTrackLicensor.find_by_name('Tency Music') #add_licensor_metadata('Tency Music', metalocation) + elsif is_tim_tracks_storage? + jam_track.vendor_id = metadata[:id] + jam_track.licensor = JamTrackLicensor.find_by_name('Tim Waurick') end else if !options[:resync_audio] @@ -664,7 +679,7 @@ module JamRuby instrument = 'other' part = 'Bouzouki' elsif potential_instrument == 'claps' || potential_instrument == 'hand claps' - instrument = 'computer' + instrument = 'other' part = 'Claps' else found_instrument = Instrument.find_by_id(potential_instrument) @@ -774,7 +789,6 @@ module JamRuby end end end - end @@ -1606,6 +1620,8 @@ module JamRuby def song_storage_manager if is_tency_storage? tency_s3_manager + elsif is_tim_tracks_storage? + tim_tracks_s3_manager else s3_manager end @@ -1619,6 +1635,10 @@ module JamRuby @tency_s3_manager ||= S3Manager.new('jamkazam-tency', APP_CONFIG.aws_access_key_id, APP_CONFIG.aws_secret_access_key) end + def tim_tracks_s3_manager + @tim_tracks_s3_manager ||= S3Manager.new('jamkazam-timtracks', APP_CONFIG.aws_access_key_id, APP_CONFIG.aws_secret_access_key) + end + def s3_manager @s3_manager ||= S3Manager.new(APP_CONFIG.aws_bucket_jamtracks, APP_CONFIG.aws_access_key_id, APP_CONFIG.aws_secret_access_key) end @@ -1657,10 +1677,32 @@ module JamRuby @storage_format == 'Tency' end + def is_tim_tracks_storage? + assert_storage_set + @storage_format == 'TimTracks' + end + def assert_storage_set raise "no storage_format set" if @storage_format.nil? end + def iterate_tim_tracks_song_storage(&blk) + count = 0 + song_storage_manager.list_directories('mapped').each do |song| + @@log.debug("searching through song directory '#{song}'") + + metalocation = "#{song}meta.yml" + + metadata = load_metalocation(metalocation) + + blk.call(metadata, metalocation) + + count += 1 + #break if count > 100 + + end + end + def iterate_tency_song_storage(&blk) count = 0 song_storage_manager.list_directories('mapped').each do |song| @@ -1700,6 +1742,10 @@ module JamRuby iterate_tency_song_storage do |metadata, metalocation| blk.call(metadata, metalocation) end + elsif is_tim_tracks_storage? + iterate_tim_tracks_song_storage do |metadata, metalocation| + blk.call(metadata, metalocation) + end else iterate_default_song_storage do |metadata, metalocation| blk.call(metadata, metalocation) diff --git a/web/app/assets/javascripts/backend_alerts.js b/web/app/assets/javascripts/backend_alerts.js index bf5b1993b..2bd2b3737 100644 --- a/web/app/assets/javascripts/backend_alerts.js +++ b/web/app/assets/javascripts/backend_alerts.js @@ -137,6 +137,9 @@ else if(type === ALERT_NAMES.VIDEO_WINDOW_CLOSED) { context.VideoActions.videoWindowClosed() } + else if (type === ALERT_NAMES.VST_CHANGED) { + context.ConfigureTracksActions.onVstChanged() + } else if((!context.JK.CurrentSessionModel || !context.JK.CurrentSessionModel.inSession()) && (ALERT_NAMES.INPUT_IO_RATE == type || ALERT_NAMES.INPUT_IO_JTR == type || ALERT_NAMES.OUTPUT_IO_RATE == type || ALERT_NAMES.OUTPUT_IO_JTR== type)) { // squelch these events if not in session diff --git a/web/app/assets/javascripts/globals.js b/web/app/assets/javascripts/globals.js index 86e8972a0..f6e13e0d4 100644 --- a/web/app/assets/javascripts/globals.js +++ b/web/app/assets/javascripts/globals.js @@ -128,7 +128,8 @@ RECORDING_DONE :48, //the recording writer thread is done VIDEO_WINDOW_OPENED :49, //video window opened VIDEO_WINDOW_CLOSED :50, - LAST_ALERT : 51 + VST_CHANGED: 51, // VST state changed + LAST_ALERT : 52 } // recreate eThresholdType enum from MixerDialog.h context.JK.ALERT_TYPES = { diff --git a/web/app/assets/javascripts/react-components/actions/ConfigureTracksActions.js.coffee b/web/app/assets/javascripts/react-components/actions/ConfigureTracksActions.js.coffee index 4a2890385..7b5b3aa2d 100644 --- a/web/app/assets/javascripts/react-components/actions/ConfigureTracksActions.js.coffee +++ b/web/app/assets/javascripts/react-components/actions/ConfigureTracksActions.js.coffee @@ -19,4 +19,5 @@ context = window associateVSTWithTrack: {} associateMIDIWithTrack: {} desiredTrackType: {} + vstChanged: {} }) diff --git a/web/lib/tasks/jam_tracks.rake b/web/lib/tasks/jam_tracks.rake index bbabe08e5..f8bdc47e0 100644 --- a/web/lib/tasks/jam_tracks.rake +++ b/web/lib/tasks/jam_tracks.rake @@ -9,6 +9,11 @@ namespace :jam_tracks do JamTrackImporter.dry_run end + task timtracks_dry_run: :environment do |task, args| + JamTrackImporter.storage_format = 'TimTracks' + JamTrackImporter.dry_run + end + task tency_create_masters: :environment do |task, args| JamTrackImporter.storage_format = 'Tency' JamTrackImporter.create_masters @@ -75,6 +80,11 @@ namespace :jam_tracks do JamTrackImporter.synchronize_all(skip_audio_upload:false) end + task sync_tim_tracks: :environment do |task, args| + JamTrackImporter.storage_format = 'TimTracks' + JamTrackImporter.synchronize_all(skip_audio_upload:false) + end + task tency_dups: :environment do |task, args| end From 83b7009c510440fa96b85e225d8cc5aa7c0c9982 Mon Sep 17 00:00:00 2001 From: Seth Call Date: Fri, 20 Nov 2015 16:18:08 -0600 Subject: [PATCH 11/19] * wip --- web/app/assets/javascripts/globals.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/web/app/assets/javascripts/globals.js b/web/app/assets/javascripts/globals.js index f6e13e0d4..148b5ecc5 100644 --- a/web/app/assets/javascripts/globals.js +++ b/web/app/assets/javascripts/globals.js @@ -193,7 +193,8 @@ 48: {"title": "", "message": ""}, // RECORDING_DONE 49: {"title": "", "message": ""}, // VIDEO_WINDOW_OPENED 50: {"title": "", "message": ""}, // VIDEO_WINDOW_CLOSED - 51: {"title": "", "message": ""} // LAST_ALERT + 51: {"title": "", "message": ""}, // VST_CHANGED + 52: {"title": "", "message": ""} // LAST_ALERT }; // add the alert's name to the ALERT_TYPES structure From 143506afd56a78524733c64873da8183dec21c47 Mon Sep 17 00:00:00 2001 From: Seth Call Date: Sat, 21 Nov 2015 15:29:15 -0600 Subject: [PATCH 12/19] * wip --- web/app/assets/javascripts/backend_alerts.js | 3 ++- .../stores/ConfigureTracksStore.js.coffee | 12 +++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/web/app/assets/javascripts/backend_alerts.js b/web/app/assets/javascripts/backend_alerts.js index 2bd2b3737..705f9ec29 100644 --- a/web/app/assets/javascripts/backend_alerts.js +++ b/web/app/assets/javascripts/backend_alerts.js @@ -138,7 +138,8 @@ context.VideoActions.videoWindowClosed() } else if (type === ALERT_NAMES.VST_CHANGED) { - context.ConfigureTracksActions.onVstChanged() + console.log("VST CHANGED!") + context.ConfigureTracksActions.vstChanged() } else if((!context.JK.CurrentSessionModel || !context.JK.CurrentSessionModel.inSession()) && (ALERT_NAMES.INPUT_IO_RATE == type || ALERT_NAMES.INPUT_IO_JTR == type || ALERT_NAMES.OUTPUT_IO_RATE == type || ALERT_NAMES.OUTPUT_IO_JTR== type)) { diff --git a/web/app/assets/javascripts/react-components/stores/ConfigureTracksStore.js.coffee b/web/app/assets/javascripts/react-components/stores/ConfigureTracksStore.js.coffee index 6a78c5955..e53fc4b70 100644 --- a/web/app/assets/javascripts/react-components/stores/ConfigureTracksStore.js.coffee +++ b/web/app/assets/javascripts/react-components/stores/ConfigureTracksStore.js.coffee @@ -77,7 +77,6 @@ bool VST_EnableMidiForTrack(const QString& trackId, bool enableMidi, int midiDev logger.debug("vstScan starting") @scanningVsts = true result = context.jamClient.VSTScan("window.ConfigureTracksStore.onVstScanComplete") - logger.debug("vstScan completed", result) onClearVsts: () -> context.jamClient.VSTClearAll() @@ -89,10 +88,21 @@ bool VST_EnableMidiForTrack(const QString& trackId, bool enableMidi, int midiDev ), 250) onVstScanComplete: () -> + # XXX must wait a long time to get track assignments after scan/ + console.log("vst scan complete") setTimeout((() => @scanningVsts = false @listVsts() @changed() + ), 1000 ) + + onVstChanged: () -> + seTitemout() + logger.debug("vst changed") + + setTimeout((() => + @listVsts() + @changed() ), 0) listVsts: () -> From dd8e3757665f3190aaddd2c47bcd29c754894005 Mon Sep 17 00:00:00 2001 From: Seth Call Date: Fri, 4 Dec 2015 12:08:40 -0600 Subject: [PATCH 13/19] * scan weirdness --- .../stores/ConfigureTracksStore.js.coffee | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/web/app/assets/javascripts/react-components/stores/ConfigureTracksStore.js.coffee b/web/app/assets/javascripts/react-components/stores/ConfigureTracksStore.js.coffee index e53fc4b70..ed27b9b74 100644 --- a/web/app/assets/javascripts/react-components/stores/ConfigureTracksStore.js.coffee +++ b/web/app/assets/javascripts/react-components/stores/ConfigureTracksStore.js.coffee @@ -90,11 +90,12 @@ bool VST_EnableMidiForTrack(const QString& trackId, bool enableMidi, int midiDev onVstScanComplete: () -> # XXX must wait a long time to get track assignments after scan/ console.log("vst scan complete") + @scanningVsts = false setTimeout((() => - @scanningVsts = false + console.log("!!") @listVsts() @changed() - ), 1000 ) + ), 1000 ) onVstChanged: () -> seTitemout() @@ -156,6 +157,10 @@ bool VST_EnableMidiForTrack(const QString& trackId, bool enableMidi, int midiDev @editingTrack.midiDeviceIndex = vst.midiDeviceIndex break + for inputsForTrack in @trackAssignments.inputs.assigned + if vst.track == inputsForTrack.assignment - 1 + inputsForTrack.vst = vst + if @editingTrack.vst? logger.debug("current track has a VST assigned:" + @editingTrack.vst.file) From 6d5db83aea95975166c306d75a64d6ebf98abcaa Mon Sep 17 00:00:00 2001 From: Seth Call Date: Fri, 4 Dec 2015 15:29:59 -0600 Subject: [PATCH 14/19] * midi not working still --- .../dialog/configureTrackDialog.js | 8 ++-- .../ConfigureLiveTracksDialog.js.jsx.coffee | 42 +++++++++++++++---- .../stores/ConfigureTracksStore.js.coffee | 25 +++++++---- .../stores/SessionStore.js.coffee | 2 +- .../wizard/gear/step_configure_tracks.js | 2 +- .../configureLiveTracksDialog.css.scss | 18 ++++++++ 6 files changed, 75 insertions(+), 22 deletions(-) diff --git a/web/app/assets/javascripts/dialog/configureTrackDialog.js b/web/app/assets/javascripts/dialog/configureTrackDialog.js index 421cb1d3c..17989b205 100644 --- a/web/app/assets/javascripts/dialog/configureTrackDialog.js +++ b/web/app/assets/javascripts/dialog/configureTrackDialog.js @@ -103,7 +103,7 @@ $musicAudioTabSelector.click(function () { // validate voice chat settings if (validateVoiceChatSettings()) { - window.ConfigureTracksActions.reset(); + window.ConfigureTracksActions.reset(false); voiceChatHelper.reset(); showMusicAudioPanel(); } @@ -113,7 +113,7 @@ // validate audio settings if (validateAudioSettings()) { logger.debug("initializing voice chat helper") - window.ConfigureTracksActions.reset(); + window.ConfigureTracksActions.reset(false); voiceChatHelper.reset(); showVoiceChatPanel(); } @@ -183,7 +183,7 @@ currentProfile = profile; - window.ConfigureTracksActions.reset(); + window.ConfigureTracksActions.reset(false); } function beforeShow() { @@ -207,7 +207,7 @@ return; } - window.ConfigureTracksActions.reset(); + window.ConfigureTracksActions.reset(false); voiceChatHelper.reset(); voiceChatHelper.beforeShow(); } diff --git a/web/app/assets/javascripts/react-components/ConfigureLiveTracksDialog.js.jsx.coffee b/web/app/assets/javascripts/react-components/ConfigureLiveTracksDialog.js.jsx.coffee index ea4d5a7d7..3bc426721 100644 --- a/web/app/assets/javascripts/react-components/ConfigureLiveTracksDialog.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/ConfigureLiveTracksDialog.js.jsx.coffee @@ -257,14 +257,13 @@ ConfigureTracksStore = @ConfigureTracksStore $root = $(@getDOMNode()) $select = $root.find('select.vsts') vstSelected = $select.val() - if vstSelected? + if vstSelected != 'NONE' vstSelected = {file: vstSelected} - ConfigureTracksActions.associateVSTWithTrack(vstSelected) - if @state.configureTracks?.trackType == 'midi' - if @state.midiInterface? - ConfigureTracksActions.associateMIDIWithTrack(@state.midiInterface) + @updateMidiAssociations() + else + ConfigureTracksActions.associateVSTWithTrack(vstSelected) @setState({midiInterface: null}) @@ -329,7 +328,7 @@ ConfigureTracksStore = @ConfigureTracksStore $root = $(@getDOMNode()) $radio = $root.find('input[type="radio"]') #$radio.iCheck('enable') - $radio.iCheck('disable') + $radio.iCheck('enable') componentDidUpdate: () -> $root = $(@getDOMNode()) @@ -339,8 +338,7 @@ ConfigureTracksStore = @ConfigureTracksStore if @state.configureTracks.editingTrack.assignment == 1 $radio.iCheck('disable') else - $radio.iCheck('disable') - #$radio.iCheck('enable') + $radio.iCheck('enable') @ignoreICheck = false @@ -382,14 +380,40 @@ ConfigureTracksStore = @ConfigureTracksStore midiInterfaceChanged: (e) -> + + @updateMidiAssociations() + + updateMidiAssociations: (e) -> $root = $(@getDOMNode()) $select = $root.find('select.midi-select') midiInterface = $select.val() + $select = $root.find('select.vsts') + vstSelected = $select.val() + + logger.debug("updateMidiAssocations", vstSelected, midiInterface) + if vstSelected != 'NONE' + vstSelected = {file: vstSelected} + else + vstSelected = null + if midiInterface == '' midiInterface = null - ConfigureTracksActions.associateMIDIWithTrack(midiInterface) + midi = @state.midiInterface || midiInterface + + if vstSelected? && midi? + logger.debug("updating midi:#{midi} & vst: #{vstSelected.file}") + + ConfigureTracksActions.associateVSTWithTrack(vstSelected) + setTimeout((() => + ConfigureTracksActions.associateMIDIWithTrack(midi) + ), 250) + + else if midi? + logger.debug("updating midi:#{midiInterface}") + ConfigureTracksActions.associateMIDIWithTrack(midiInterface) @setState({midiInterface: midiInterface}) + }) \ No newline at end of file diff --git a/web/app/assets/javascripts/react-components/stores/ConfigureTracksStore.js.coffee b/web/app/assets/javascripts/react-components/stores/ConfigureTracksStore.js.coffee index ed27b9b74..b3c2b23a0 100644 --- a/web/app/assets/javascripts/react-components/stores/ConfigureTracksStore.js.coffee +++ b/web/app/assets/javascripts/react-components/stores/ConfigureTracksStore.js.coffee @@ -48,14 +48,16 @@ bool VST_EnableMidiForTrack(const QString& trackId, bool enableMidi, int midiDev @loadTrackInstruments() @changed() - onReset: () -> + onReset: (force) -> logger.debug("ConfigureTracksStore:reset", this) @trackNumber = null @editingTrack = null @loadChannels() @loadTrackInstruments() - @performVstScan() - @performMidiScan() + + if force || context.jamClient.hasVstAssignment() + @performVstScan() + @performMidiScan() @changed() @@ -92,13 +94,12 @@ bool VST_EnableMidiForTrack(const QString& trackId, bool enableMidi, int midiDev console.log("vst scan complete") @scanningVsts = false setTimeout((() => - console.log("!!") @listVsts() @changed() - ), 1000 ) + ), 100 ) onVstChanged: () -> - seTitemout() + setTimeout() logger.debug("vst changed") setTimeout((() => @@ -164,6 +165,9 @@ bool VST_EnableMidiForTrack(const QString& trackId, bool enableMidi, int midiDev if @editingTrack.vst? logger.debug("current track has a VST assigned:" + @editingTrack.vst.file) + logger.debug("trackAssignments:", @trackAssignments) + logger.debug("editingTrack:", @editingTrack) + @item = { musicPorts: @musicPorts, trackAssignments: @trackAssignments, @@ -286,7 +290,7 @@ bool VST_EnableMidiForTrack(const QString& trackId, bool enableMidi, int midiDev return if vst? - logger.debug("associating track:#{@trackNumber} with VST:#{vst.file}") + logger.debug("associating track:#{@trackNumber - 1} with VST:#{vst.file}") found = null for knownVst in @vstPluginList.vsts @@ -385,6 +389,9 @@ bool VST_EnableMidiForTrack(const QString& trackId, bool enableMidi, int midiDev # ensure that we always have an instrument set (50 = electric guitar context.jamClient.TrackSetInstrument(@trackNumber, 50) + @performVstScan() + @performMidiScan() + @changed() @app.layout.showDialog('configure-live-tracks-dialog') @@ -392,6 +399,8 @@ bool VST_EnableMidiForTrack(const QString& trackId, bool enableMidi, int midiDev onDesiredTrackType: (trackType) -> @trackType = trackType + if @trackType == 'midi' + @trackNumber = 100 @changed() onUpdateOutputs: (outputId1, outputId2) -> @@ -411,6 +420,8 @@ bool VST_EnableMidiForTrack(const QString& trackId, bool enableMidi, int midiDev onAssociateMIDIWithTrack: (midiInterface) -> + @trackNumber = 100 + if !midiInterface? || midiInterface == '' logger.debug("disabling midiInterface:#{midiInterface}, track:#{@trackNumber - 1}") context.jamClient.VST_EnableMidiForTrack(@trackNumber - 1, false, 0) diff --git a/web/app/assets/javascripts/react-components/stores/SessionStore.js.coffee b/web/app/assets/javascripts/react-components/stores/SessionStore.js.coffee index 08e291141..8ef0c06cb 100644 --- a/web/app/assets/javascripts/react-components/stores/SessionStore.js.coffee +++ b/web/app/assets/javascripts/react-components/stores/SessionStore.js.coffee @@ -733,7 +733,7 @@ ConfigureTracksActions = @ConfigureTracksActions @handleAutoOpenJamTrack() - ConfigureTracksActions.reset() + ConfigureTracksActions.reset(false) ) .fail((xhr) => @updateCurrentSession(null) diff --git a/web/app/assets/javascripts/wizard/gear/step_configure_tracks.js b/web/app/assets/javascripts/wizard/gear/step_configure_tracks.js index e40b81609..72e97aad3 100644 --- a/web/app/assets/javascripts/wizard/gear/step_configure_tracks.js +++ b/web/app/assets/javascripts/wizard/gear/step_configure_tracks.js @@ -38,7 +38,7 @@ function beforeShow() { var forceInputsToUnassigned = !successfullyAssignedOnce; - window.ConfigureTracksActions.reset(forceInputsToUnassigned, wizard.getChosenInputs()); + window.ConfigureTracksActions.reset(false); //configureTracksHelper.reset(forceInputsToUnassigned, wizard.getChosenInputs()) } diff --git a/web/app/assets/stylesheets/dialogs/configureLiveTracksDialog.css.scss b/web/app/assets/stylesheets/dialogs/configureLiveTracksDialog.css.scss index c69210e75..212e6869d 100644 --- a/web/app/assets/stylesheets/dialogs/configureLiveTracksDialog.css.scss +++ b/web/app/assets/stylesheets/dialogs/configureLiveTracksDialog.css.scss @@ -111,6 +111,24 @@ width:80%; margin-bottom:10px; } + + .down-arrow { + cursor:pointer; + width: 0; + height: 0; + border-left: 8px solid transparent; + border-right: 8px solid transparent; + border-top: 8px solid #fc0; + position: relative; + top: -8px; + right: -125px; + } + + .settings-holder { + float: right; + margin-right: 65px; + margin-top: -1px; + } } .audio-effects { width:40%; From 1d3548cdf9e0eac3a1b4704f96968c8dc0e5ebb8 Mon Sep 17 00:00:00 2001 From: Seth Call Date: Tue, 8 Dec 2015 13:37:18 -0600 Subject: [PATCH 15/19] * fix misnamed active music sessions var --- ruby/lib/jam_ruby/connection_manager.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ruby/lib/jam_ruby/connection_manager.rb b/ruby/lib/jam_ruby/connection_manager.rb index 748e50eb9..12a915ce3 100644 --- a/ruby/lib/jam_ruby/connection_manager.rb +++ b/ruby/lib/jam_ruby/connection_manager.rb @@ -386,7 +386,7 @@ SQL end # go by who joined earliest - earliest = active_music_sessions.connections.order(:joined_session_at).first + earliest = active_music_session.connections.order(:joined_session_at).first if earliest music_session.session_controller = earliest From 6314748225502f061bfed07b9a791e739fb7a4e4 Mon Sep 17 00:00:00 2001 From: Seth Call Date: Wed, 9 Dec 2015 11:32:24 -0600 Subject: [PATCH 16/19] * wip --- admin/app/admin/crash_dumps.rb | 27 +++-- admin/app/admin/download_tracker.rb | 3 +- ruby/lib/jam_ruby/models/crash_dump.rb | 5 + ruby/lib/jam_ruby/models/jam_track_right.rb | 8 +- .../assets/javascripts/react-components.js | 1 + .../SessionOtherTrack.js.jsx.coffee | 10 ++ .../SessionStatsHover.js.jsx.coffee | 30 +++++ .../actions/SessionStatsActions.js.coffee | 5 + .../helpers/ConnectionStatsHelper.js.coffee | 0 .../stores/SessionStatsStore.js.coffee | 110 ++++++++++++++++++ .../stores/SessionStore.js.coffee | 13 +++ .../react-components/SessionTrack.css.scss | 13 +++ web/app/helpers/client_helper.rb | 1 + web/config/application.rb | 18 +++ 14 files changed, 225 insertions(+), 19 deletions(-) create mode 100644 web/app/assets/javascripts/react-components/SessionStatsHover.js.jsx.coffee create mode 100644 web/app/assets/javascripts/react-components/actions/SessionStatsActions.js.coffee create mode 100644 web/app/assets/javascripts/react-components/helpers/ConnectionStatsHelper.js.coffee create mode 100644 web/app/assets/javascripts/react-components/stores/SessionStatsStore.js.coffee diff --git a/admin/app/admin/crash_dumps.rb b/admin/app/admin/crash_dumps.rb index bcfcd9106..4d85ae8ae 100644 --- a/admin/app/admin/crash_dumps.rb +++ b/admin/app/admin/crash_dumps.rb @@ -1,28 +1,27 @@ ActiveAdmin.register JamRuby::CrashDump, :as => 'Crash Dump' do # Note: a lame thing is it's not obvious how to make it search on email instead of user_id. filter :timestamp - filter :user_email, :as => :string filter :client_id + filter :user_id + menu :parent => 'Misc' + config.sort_order = 'created_at DESC' + index do + column 'User' do |oo| oo.user ? link_to(oo.user.email, oo.user.admin_url, {:title => oo.user.email}) : '' end + column "Client Version", :client_version + column "Client Type", :client_type + column "Download" do |post| + link_to 'Link', post.sign_url + end column "Timestamp" do |post| (post.timestamp || post.created_at).strftime('%b %d %Y, %H:%M') end - column "Client Type", :client_type - column "Dump URL" do |post| - link_to post.uri, post.uri + column "Description" do |post| + post.description end - - column "User ID", :user_id - - # FIXME (?): This isn't performant (though it likely doesn't matter). Could probably do a join. - column "User Email" do |post| - unless post.user_id.nil? - post.user_email - end - end - column "Client ID", :client_id actions + end end diff --git a/admin/app/admin/download_tracker.rb b/admin/app/admin/download_tracker.rb index 2577736e5..6d0daa013 100644 --- a/admin/app/admin/download_tracker.rb +++ b/admin/app/admin/download_tracker.rb @@ -10,10 +10,11 @@ ActiveAdmin.register JamRuby::DownloadTracker, :as => 'DownloadTrackers' do index do column 'User' do |oo| oo.user ? link_to(oo.user.email, oo.user.admin_url, {:title => oo.user.email}) : '' end - column 'Remote IP' do |oo| oo.remote_ip end + column 'Created' do |oo| oo.created_at end column 'JamTrack' do |oo| oo.jam_track end column 'Paid' do |oo| oo.paid end column 'Blacklisted?' do |oo| IpBlacklist.listed(oo.remote_ip) ? 'Yes' : 'No' end + column 'Remote IP' do |oo| oo.remote_ip end column "" do |oo| link_to 'Blacklist This IP', "download_trackers/#{oo.id}/blacklist_by_ip" end diff --git a/ruby/lib/jam_ruby/models/crash_dump.rb b/ruby/lib/jam_ruby/models/crash_dump.rb index 13087fed0..22874a299 100644 --- a/ruby/lib/jam_ruby/models/crash_dump.rb +++ b/ruby/lib/jam_ruby/models/crash_dump.rb @@ -1,6 +1,8 @@ module JamRuby class CrashDump < ActiveRecord::Base + include JamRuby::S3ManagerMixin + self.table_name = "crash_dumps" self.primary_key = 'id' @@ -23,5 +25,8 @@ module JamRuby self.user.email end + def sign_url(expiration_time = 3600 * 24 * 7, secure=true) + s3_manager.sign_url(self[:ri], {:expires => expiration_time, :secure => secure}) + end end end diff --git a/ruby/lib/jam_ruby/models/jam_track_right.rb b/ruby/lib/jam_ruby/models/jam_track_right.rb index 63558f84a..b400771a5 100644 --- a/ruby/lib/jam_ruby/models/jam_track_right.rb +++ b/ruby/lib/jam_ruby/models/jam_track_right.rb @@ -111,10 +111,10 @@ module JamRuby # the idea is that this is used when a user who has the rights to this tries to download this JamTrack # we would verify their rights (can_download?), and generates a URL in response to the click so that they can download # but the url is short lived enough so that it wouldn't be easily shared - def sign_url(expiration_time = 120, bitrate=48, secure=true) - field_name = (bitrate==48) ? "url_48" : "url_44" - s3_manager.sign_url(self[field_name], {:expires => expiration_time, :secure => secure}) - end + def sign_url(expiration_time = 120, bitrate=48, secure=true) + field_name = (bitrate==48) ? "url_48" : "url_44" + s3_manager.sign_url(self[field_name], {:expires => expiration_time, :secure => secure}) + end def delete_s3_files remove_url_48! diff --git a/web/app/assets/javascripts/react-components.js b/web/app/assets/javascripts/react-components.js index ff86ff8e6..2b0e733d8 100644 --- a/web/app/assets/javascripts/react-components.js +++ b/web/app/assets/javascripts/react-components.js @@ -7,6 +7,7 @@ //= require ./react-components/stores/RecordingStore //= require ./react-components/stores/VideoStore //= require ./react-components/stores/SessionStore +//= require ./react-components/stores/SessionStatsStore //= require ./react-components/stores/MixerStore //= require ./react-components/stores/JamTrackStore //= require ./react-components/stores/SessionNotificationStore diff --git a/web/app/assets/javascripts/react-components/SessionOtherTrack.js.jsx.coffee b/web/app/assets/javascripts/react-components/SessionOtherTrack.js.jsx.coffee index 68a6ccf70..0964bf602 100644 --- a/web/app/assets/javascripts/react-components/SessionOtherTrack.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/SessionOtherTrack.js.jsx.coffee @@ -4,6 +4,11 @@ MixerActions = @MixerActions @SessionOtherTrack = React.createClass({ + mixins: [Reflux.listenTo(@SessionStatsStore,"onStatsChanged")] + + onStatsChanged: (stats) -> + @setState({stats: stats}) + handleMute: (e) -> e.preventDefault() @@ -48,6 +53,10 @@ MixerActions = @MixerActions WebkitTransform: "rotate(#{pan}deg)" } + connectionState = @state.stats.clientState(this.props.participant.client_id) + connectionStateClasses = { 'track-connection-state': true} + connectionStateClasses[connectionState] = true + `
@@ -59,6 +68,7 @@ MixerActions = @MixerActions
+

diff --git a/web/app/assets/javascripts/react-components/SessionStatsHover.js.jsx.coffee b/web/app/assets/javascripts/react-components/SessionStatsHover.js.jsx.coffee new file mode 100644 index 000000000..a2e17c8cc --- /dev/null +++ b/web/app/assets/javascripts/react-components/SessionStatsHover.js.jsx.coffee @@ -0,0 +1,30 @@ +context = window +ChannelGroupIds = context.JK.ChannelGroupIds +MixerActions = @MixerActions +ptrCount = 0 + +@SessionStatsHover = React.createClass({ + + propTypes: { + clientId: React.PropTypes.string + } + + mixins: [Reflux.listenTo(@SessionStatsStore, "onStatsChanged")] + + render: () -> + `
+ +
` + + onStatsChanged: (stats) -> + @setState({stats: stats}) + + getInitialState: () -> + {stats: window.SessionStatsStore.stats} + + + closeHover: (e) -> + e.preventDefault() + $container = $(this.getDOMNode()).closest('.react-holder') + $container.data('bt').btOff() +}) \ No newline at end of file diff --git a/web/app/assets/javascripts/react-components/actions/SessionStatsActions.js.coffee b/web/app/assets/javascripts/react-components/actions/SessionStatsActions.js.coffee new file mode 100644 index 000000000..23dd5ef48 --- /dev/null +++ b/web/app/assets/javascripts/react-components/actions/SessionStatsActions.js.coffee @@ -0,0 +1,5 @@ +context = window + +@SessionStatsActions = Reflux.createActions({ + pushStats: {} +}) \ No newline at end of file diff --git a/web/app/assets/javascripts/react-components/helpers/ConnectionStatsHelper.js.coffee b/web/app/assets/javascripts/react-components/helpers/ConnectionStatsHelper.js.coffee new file mode 100644 index 000000000..e69de29bb diff --git a/web/app/assets/javascripts/react-components/stores/SessionStatsStore.js.coffee b/web/app/assets/javascripts/react-components/stores/SessionStatsStore.js.coffee new file mode 100644 index 000000000..70f3e28ed --- /dev/null +++ b/web/app/assets/javascripts/react-components/stores/SessionStatsStore.js.coffee @@ -0,0 +1,110 @@ +$ = jQuery +context = window +logger = context.JK.logger +rest = context.JK.Rest() +EVENTS = context.JK.EVENTS +MIX_MODES = context.JK.MIX_MODES + +SessionActions = @SessionActions + +SessionStatThresholds = gon.session_stat_thresholds +NetworkThresholds = SessionStatThresholds.network +SystemThresholds = SessionStatThresholds.system +AudioThresholds = SessionStatThresholds.audio + +@SessionStatsStore = Reflux.createStore( + + rawStats: null + + onPushStats: (stats) -> + + logger.debug("connection stats", connectionStats) + @rawStats = stats + @changed() + + classify: (holder, field, threshold) -> + value = holder[field] + fieldLevel = field = '-level' + fieldThreshold = threshold[field] + if value? && fieldThreshold? + + if fieldThreshold.inverse + if value <= threshold.poor + holder[fieldLevel] = 'poor' + else if value <= threshold.warn + holder[fieldLevel] = 'warn' + else + holder[fieldLevel] = 'good' + else if fieldThreshold.eql + if value == threshold.poor + holder[fieldLevel] = 'poor' + else if value == threshold.warn + holder[fieldLevel] = 'warn' + else + holder[fieldLevel] = 'good' + else + if value >= threshold.poor + holder[fieldLevel] = 'poor' + else if value >= threshold.warn + holder[fieldLevel] = 'warn' + else + holder[fieldLevel] = 'good' + + + changed: () -> + for participant in @rawStats + + # CPU is 0-100 + if participant.cpu? + @classify(participant, 'cpu', SystemThresholds) + + network = participant.network + + if network? + # audio_bitrate: 256 + # net_bitrate: 286.19244384765625 + # ping: 0.08024691045284271 + # ping_var: 0.6403124332427979 + # pkt_loss: 100 + # wifi: false + + @classify(network, 'audio_bitrate', NetworkThresholds) + @classify(network, 'ping', NetworkThresholds) + @classify(network, 'pkt_loss', NetworkThresholds) + @classify(network, 'wifi', NetworkThresholds) + + audio = participant.audio + + if audio? + # acpu: 5.148329734802246 + # audio_in: "Fast Track" + # audio_in_type: "Core Audio" + # cpu: 22.44668960571289 + # framesize: 2.5 + # in_latency: 5.020833492279053 + # input_iio_jitter: -0.0015926361083984375 + # input_jitter: 0.2977011799812317 + # input_median: 400.16632080078125 + # io_out_latency: "Expected Latency = 9.54 +/- 1.00 ms [Raw/PaBuff/PaRing Latency: 9.54 / 12.04 / 0.00 ms]" + # out_latency: 4.520833492279053 + # output_iio_jitter: -0.07366180419921875 + # output_jitter: 0.40290364623069763 + # output_median: 400.0581970214844 + # output_name: 4 + # samplerate: 48000 + + if audio.in_latency? and audio.out_latency? + audio.latency = in_latency + out_latency + + @classify(audio, 'audio_in_type', AudioThresholds) + @classify(audio, 'framesize', AudioThresholds) + @classify(audio, 'latency', AudioThresholds) + @classify(audio, 'input_jitter', AudioThresholds) + @classify(audio, 'output_jitter', AudioThresholds) + + @stats = @rawStats + @trigger(@stats) + + + +) \ No newline at end of file diff --git a/web/app/assets/javascripts/react-components/stores/SessionStore.js.coffee b/web/app/assets/javascripts/react-components/stores/SessionStore.js.coffee index b0bbb0ac7..61f28e2d6 100644 --- a/web/app/assets/javascripts/react-components/stores/SessionStore.js.coffee +++ b/web/app/assets/javascripts/react-components/stores/SessionStore.js.coffee @@ -731,6 +731,8 @@ VideoActions = @VideoActions $(document).trigger(EVENTS.SESSION_STARTED, {session: {id: @currentSessionId}}) if document @handleAutoOpenJamTrack() + + @watchBackendStats() ) .fail((xhr) => @updateCurrentSession(null) @@ -762,6 +764,13 @@ VideoActions = @VideoActions @app.notifyServerError(xhr, 'Unable to Join Session'); ) + watchBackendStats: () -> + @backendStatsInterval = window.setInterval((() => (@updateBackendStats())), 1000) + + updateBackendStats: () -> + connectionStats = window.jamClient.getConnectionDetail('') + SessionStatsActions.pushStats(connectionStats) + trackChanges: (header, payload) -> if @currentTrackChanges < payload.track_changes_counter # we don't have the latest info. try and go get it @@ -1050,6 +1059,10 @@ VideoActions = @VideoActions @userTracks = null; @startTime = null; + if @backendStatsInterval? + window.clearInterval(@backendStatsInterval) + @backendStatsInterval = null + if @joinDeferred?.state() == 'resolved' $(document).trigger(EVENTS.SESSION_ENDED, {session: {id: @currentSessionId}}) diff --git a/web/app/assets/stylesheets/client/react-components/SessionTrack.css.scss b/web/app/assets/stylesheets/client/react-components/SessionTrack.css.scss index 82614b21e..700469329 100644 --- a/web/app/assets/stylesheets/client/react-components/SessionTrack.css.scss +++ b/web/app/assets/stylesheets/client/react-components/SessionTrack.css.scss @@ -119,6 +119,19 @@ } } + .track-connection-state { + width:20px; + height:20px; + float:left; + cursor:pointer; + text-align: center; + margin-left:10px; + } + + .circle { + + } + .track-icon-equalizer { float:left; cursor: pointer; diff --git a/web/app/helpers/client_helper.rb b/web/app/helpers/client_helper.rb index f6febfb1d..d9dba9952 100644 --- a/web/app/helpers/client_helper.rb +++ b/web/app/helpers/client_helper.rb @@ -69,6 +69,7 @@ module ClientHelper gon.ftue_maximum_gear_latency = Rails.application.config.ftue_maximum_gear_latency gon.musician_search_meta = MusicianSearch.search_filter_meta gon.band_search_meta = BandSearch.search_filter_meta + gon.session_stat_thresholds = Rails.application.config.session_stat_thresholds # is this the native client or browser? @nativeClient = is_native_client? diff --git a/web/config/application.rb b/web/config/application.rb index 5d9fc97d6..84296bfd3 100644 --- a/web/config/application.rb +++ b/web/config/application.rb @@ -377,5 +377,23 @@ if defined?(Bundler) config.download_tracker_day_range = 30 config.max_user_ip_address = 10 config.max_multiple_users_same_ip = 2 + config.session_stat_thresholds = { + network: { + wifi: {warn: true, poor: true, eql: true}, + audio_bitrate: {warn: 200, poor: 135, inverse:true}, + ping: {warn: 40, poor: 70}, + pkt_loss: {warn: 3, poor: 10} + }, + system: { + cpu: {warn: 70, poor:85} + }, + audio: { + audio_in_type: {warn: 'WDM', poor: 'WDM', eql: true}, + framesize: {warn: 2.6, poor: 2.6}, + latency: {warn: 10, poor: 20}, + input_jitter: {warn: 0.5, poor: 1}, + output_jitter: {warn: 0.5, poor: 1}, + } + } end end From 3388ec14db4b21aab0ca7654b66d61495fa367d2 Mon Sep 17 00:00:00 2001 From: Seth Call Date: Thu, 10 Dec 2015 05:21:59 -0600 Subject: [PATCH 17/19] * stats wip --- .../SessionOtherTrack.js.jsx.coffee | 29 +- .../SessionStatsHover.js.jsx.coffee | 123 +++++- .../helpers/ConnectionStatsHelper.js.coffee | 0 .../stores/SessionStatsStore.js.coffee | 177 +++++---- .../react-components/SessionScreen.css.scss | 357 +++++++++++------- .../react-components/SessionTrack.css.scss | 29 +- web/config/application.rb | 4 +- 7 files changed, 485 insertions(+), 234 deletions(-) delete mode 100644 web/app/assets/javascripts/react-components/helpers/ConnectionStatsHelper.js.coffee diff --git a/web/app/assets/javascripts/react-components/SessionOtherTrack.js.jsx.coffee b/web/app/assets/javascripts/react-components/SessionOtherTrack.js.jsx.coffee index 0964bf602..0460c9819 100644 --- a/web/app/assets/javascripts/react-components/SessionOtherTrack.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/SessionOtherTrack.js.jsx.coffee @@ -7,7 +7,15 @@ MixerActions = @MixerActions mixins: [Reflux.listenTo(@SessionStatsStore,"onStatsChanged")] onStatsChanged: (stats) -> - @setState({stats: stats}) + @setState({stats: stats[@props.participant.client_id]}) + + getInitialState: () -> + stats = window.SessionStatsStore.stats + if stats? + clientStats = stats[@props.participant.client_id] + else + clientStats = null + {stats: clientStats} handleMute: (e) -> e.preventDefault() @@ -53,9 +61,13 @@ MixerActions = @MixerActions WebkitTransform: "rotate(#{pan}deg)" } - connectionState = @state.stats.clientState(this.props.participant.client_id) + classification = @state.stats?.classification + + if !classification? + classification = 'unknown' + connectionStateClasses = { 'track-connection-state': true} - connectionStateClasses[connectionState] = true + connectionStateClasses[classification] = true `
@@ -68,7 +80,7 @@ MixerActions = @MixerActions
-
+

@@ -87,6 +99,7 @@ MixerActions = @MixerActions $root = $(this.getDOMNode()) $mute = $root.find('.track-icon-mute') $pan = $root.find('.track-icon-pan') + $connectionState = $root.find('.track-connection-state') context.JK.interactReactBubble( $mute, @@ -106,6 +119,14 @@ MixerActions = @MixerActions , {width:331, positions:['right', 'left'], offsetParent:$root.closest('.screen')}) + context.JK.interactReactBubble( + $connectionState, + 'SessionStatsHover', + () => + {participant: this.props.participant} + , + {width:380, positions:['right', 'left'], offsetParent:$root.closest('.screen')}) + unless this.props.hasMixer $mute.on("mouseenter", false) $mute.on("mouseleave", false) diff --git a/web/app/assets/javascripts/react-components/SessionStatsHover.js.jsx.coffee b/web/app/assets/javascripts/react-components/SessionStatsHover.js.jsx.coffee index a2e17c8cc..e2d6ee021 100644 --- a/web/app/assets/javascripts/react-components/SessionStatsHover.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/SessionStatsHover.js.jsx.coffee @@ -3,6 +3,21 @@ ChannelGroupIds = context.JK.ChannelGroupIds MixerActions = @MixerActions ptrCount = 0 +StatsInfo = { + system: { + cpu: { + good: "Your computer's processor is not overworked by JamKazam or the your system.", + warn: "Your computer's processor is being heavily used. There is little spare capacity, and this means your processor may not be able to handle all of it's tasks, causing audio quality, latency, and other issues.", + poor: "Your computer's processor is being very heavily used. There is little spare capacity, and this means your processor may not be able to handle all of it's tasks, causing audio quality, latency, and other issues." + }, + network: { + + }, + audio: { + } + } +} + @SessionStatsHover = React.createClass({ propTypes: { @@ -11,16 +26,116 @@ ptrCount = 0 mixins: [Reflux.listenTo(@SessionStatsStore, "onStatsChanged")] - render: () -> - `
+ hover: (type, field) -> + logger.debug("hover! #{type} #{field}") + @setState({hoverType: type, hoverField:field}) + hoverOut: () -> + logger.debug("hover out!") + @setState({hoverType: null, hoverField: null}) + + + stat: (properties, type, name, field, value) -> + classes = {'status-icon': true} + classifier = properties[field + '_level'] + classes[classifier] = true + `
- {name}
{value}
` + + render: () -> + extraInfo = 'Hover over a stat to learn more.' + + if @state.hoverType? + type = @state.hoverType + field = @state.hoverField + + extraInfo = 'No extra info for this metric.' + + classifier = @state.stats?[type]?[field + '_level'] + + if classifier? + info = StatsInfo[type]?[field]?[classifier] + if info? + extraInfo = info + + # "Windows WDM-KS" + + computerStats = [] + networkStats = [] + audioStats = [] + + network = @state.stats?.network + system = @state.stats?.system + audio = @state.stats?.audio + + if system? + if system.cpu? + computerStats.push(@stat(system, 'system', 'Processor', 'cpu', Math.round(system.cpu) + ' %')) + + if audio? + if audio.latency? + audioStats.push(@stat(audio, 'audio', 'Latency', 'latency', audio.latency.toFixed(1) + ' ms')) + if audio.input_jitter? + audioStats.push(@stat(audio, 'audio', 'Input Jitter', 'input_jitter', audio.input_jitter.toFixed(2))) + if audio.output_jitter? + audioStats.push(@stat(audio, 'audio', 'Output Jitter', 'output_jitter', audio.output_jitter.toFixed(2))) + + if network? + + if network.net_bitrate? + networkStats.push(@stat(network, 'network', 'Bandwidth', 'net_bitrate', Math.round(network.net_bitrate) + ' k')) + + if network.ping? + networkStats.push(@stat(network, 'network', 'Latency', 'ping', (network.ping / 2).toFixed(1) + ' ms')) + #if network.jitter? + # networkStats. + if network.pkt_loss? + networkStats.push(@stat(network, 'network', 'Packet Loss', 'pkt_loss', network.pkt_loss.toFixed(1) + ' %')) + + if network.wifi? + if network.wifi + value = 'Wi-Fi' + else + value = 'Ethernet' + networkStats.push(@stat(network, 'network', 'Connectivity', 'wifi', value)) + + + `
+

Session Diagnostics & Stats: {this.props.participant.user.name}

+ +
+
+

Computer

+ {computerStats} +
+
+

Audio Interface

+ {audioStats} +
+
+

Internet

+ {networkStats} +
+
+
+ {extraInfo} +
` onStatsChanged: (stats) -> - @setState({stats: stats}) + stats = window.SessionStatsStore.stats + if stats? + clientStats = stats[@props.participant.client_id] + else + clientStats = null + @setState({stats: clientStats}) getInitialState: () -> - {stats: window.SessionStatsStore.stats} + stats = window.SessionStatsStore.stats + if stats? + clientStats = stats[@props.participant.client_id] + else + clientStats = null + {stats: clientStats, hoverType: null, hoverField: null} closeHover: (e) -> diff --git a/web/app/assets/javascripts/react-components/helpers/ConnectionStatsHelper.js.coffee b/web/app/assets/javascripts/react-components/helpers/ConnectionStatsHelper.js.coffee deleted file mode 100644 index e69de29bb..000000000 diff --git a/web/app/assets/javascripts/react-components/stores/SessionStatsStore.js.coffee b/web/app/assets/javascripts/react-components/stores/SessionStatsStore.js.coffee index 70f3e28ed..88fb06be8 100644 --- a/web/app/assets/javascripts/react-components/stores/SessionStatsStore.js.coffee +++ b/web/app/assets/javascripts/react-components/stores/SessionStatsStore.js.coffee @@ -13,98 +13,123 @@ SystemThresholds = SessionStatThresholds.system AudioThresholds = SessionStatThresholds.audio @SessionStatsStore = Reflux.createStore( + { + listenables: @SessionStatsActions + rawStats: null - rawStats: null + onPushStats: (stats) -> + @rawStats = stats + @changed() - onPushStats: (stats) -> + classify: (holder, field, threshold) -> + value = holder[field] + fieldLevel = field + '_level' + fieldThreshold = threshold[field] + if value? && fieldThreshold? - logger.debug("connection stats", connectionStats) - @rawStats = stats - @changed() - - classify: (holder, field, threshold) -> - value = holder[field] - fieldLevel = field = '-level' - fieldThreshold = threshold[field] - if value? && fieldThreshold? - - if fieldThreshold.inverse - if value <= threshold.poor - holder[fieldLevel] = 'poor' - else if value <= threshold.warn - holder[fieldLevel] = 'warn' + if fieldThreshold.inverse + if value <= fieldThreshold.poor + holder[fieldLevel] = 'poor' + @participantClassification = 3 + else if value <= fieldThreshold.warn + holder[fieldLevel] = 'warn' + @participantClassification = 2 if @participantClassification == 1 + else + holder[fieldLevel] = 'good' + else if fieldThreshold.eql + if value == fieldThreshold.poor + holder[fieldLevel] = 'poor' + @participantClassification = 3 + else if value == fieldThreshold.warn + holder[fieldLevel] = 'warn' + @participantClassification = 2 if @participantClassification == 1 + else + holder[fieldLevel] = 'good' else - holder[fieldLevel] = 'good' - else if fieldThreshold.eql - if value == threshold.poor - holder[fieldLevel] = 'poor' - else if value == threshold.warn - holder[fieldLevel] = 'warn' - else - holder[fieldLevel] = 'good' - else - if value >= threshold.poor - holder[fieldLevel] = 'poor' - else if value >= threshold.warn - holder[fieldLevel] = 'warn' - else - holder[fieldLevel] = 'good' + if value >= fieldThreshold.poor + holder[fieldLevel] = 'poor' + @participantClassification = 3 + else if value >= fieldThreshold.warn + holder[fieldLevel] = 'warn' + @participantClassification = 2 if @participantClassification == 1 + else + holder[fieldLevel] = 'good' - changed: () -> - for participant in @rawStats + changed: () -> + @stats = {} - # CPU is 0-100 - if participant.cpu? - @classify(participant, 'cpu', SystemThresholds) + console.log("raw stats", @rawStats) + for participant in @rawStats - network = participant.network + @participantClassification = 1 # 1=good, 2=warn, 3=poor - if network? - # audio_bitrate: 256 - # net_bitrate: 286.19244384765625 - # ping: 0.08024691045284271 - # ping_var: 0.6403124332427979 - # pkt_loss: 100 - # wifi: false + # CPU is 0-100 + if participant.cpu? + system = {cpu: participant.cpu} - @classify(network, 'audio_bitrate', NetworkThresholds) - @classify(network, 'ping', NetworkThresholds) - @classify(network, 'pkt_loss', NetworkThresholds) - @classify(network, 'wifi', NetworkThresholds) + @classify(system, 'cpu', SystemThresholds) - audio = participant.audio + participant.system = system - if audio? - # acpu: 5.148329734802246 - # audio_in: "Fast Track" - # audio_in_type: "Core Audio" - # cpu: 22.44668960571289 - # framesize: 2.5 - # in_latency: 5.020833492279053 - # input_iio_jitter: -0.0015926361083984375 - # input_jitter: 0.2977011799812317 - # input_median: 400.16632080078125 - # io_out_latency: "Expected Latency = 9.54 +/- 1.00 ms [Raw/PaBuff/PaRing Latency: 9.54 / 12.04 / 0.00 ms]" - # out_latency: 4.520833492279053 - # output_iio_jitter: -0.07366180419921875 - # output_jitter: 0.40290364623069763 - # output_median: 400.0581970214844 - # output_name: 4 - # samplerate: 48000 + network = participant.network - if audio.in_latency? and audio.out_latency? - audio.latency = in_latency + out_latency + if network? + # audio_bitrate: 256 + # net_bitrate: 286.19244384765625 + # ping: 0.08024691045284271 + # ping_var: 0.6403124332427979 + # pkt_loss: 100 + # wifi: false - @classify(audio, 'audio_in_type', AudioThresholds) - @classify(audio, 'framesize', AudioThresholds) - @classify(audio, 'latency', AudioThresholds) - @classify(audio, 'input_jitter', AudioThresholds) - @classify(audio, 'output_jitter', AudioThresholds) + @classify(network, 'net_bitrate', NetworkThresholds) + @classify(network, 'ping', NetworkThresholds) + @classify(network, 'pkt_loss', NetworkThresholds) + @classify(network, 'wifi', NetworkThresholds) - @stats = @rawStats - @trigger(@stats) + audio = participant.audio + + if audio? + # acpu: 5.148329734802246 + # audio_in: "Fast Track" + # audio_in_type: "Core Audio" + # cpu: 22.44668960571289 + # framesize: 2.5 + # in_latency: 5.020833492279053 + # input_iio_jitter: -0.0015926361083984375 + # input_jitter: 0.2977011799812317 + # input_median: 400.16632080078125 + # io_out_latency: "Expected Latency = 9.54 +/- 1.00 ms [Raw/PaBuff/PaRing Latency: 9.54 / 12.04 / 0.00 ms]" + # out_latency: 4.520833492279053 + # output_iio_jitter: -0.07366180419921875 + # output_jitter: 0.40290364623069763 + # output_median: 400.0581970214844 + # output_name: 4 + # samplerate: 48000 + + if audio.cpu? + system = {cpu: audio.cpu} + @classify(system, 'cpu', SystemThresholds) + participant.system = system + + if audio.in_latency? and audio.out_latency? + audio.latency = audio.in_latency + audio.out_latency + @classify(audio, 'framesize', AudioThresholds) + @classify(audio, 'latency', AudioThresholds) + @classify(audio, 'input_jitter', AudioThresholds) + @classify(audio, 'output_jitter', AudioThresholds) + switch @participantClassification + when 1 then participant.classification = 'good' + when 2 then participant.classification = 'warn' + when 3 then participant.classification = 'poor' + else + participant.classification = 'unknown' + + @stats[participant.id] = participant + + @trigger(@stats) + } ) \ No newline at end of file diff --git a/web/app/assets/stylesheets/client/react-components/SessionScreen.css.scss b/web/app/assets/stylesheets/client/react-components/SessionScreen.css.scss index bea31605f..61640625c 100644 --- a/web/app/assets/stylesheets/client/react-components/SessionScreen.css.scss +++ b/web/app/assets/stylesheets/client/react-components/SessionScreen.css.scss @@ -6,6 +6,7 @@ $session-screen-divider: 1190px; @content; } } + @mixin session-normal { @media (min-width: #{$session-screen-divider}) { @content; @@ -13,97 +14,166 @@ $session-screen-divider: 1190px; } #session-screen { - .session-container { - overflow-x: hidden; + .session-container { + overflow-x: hidden; + } + + .session-track { + @include session-small { + max-width: 120px; } - .session-track { + &.metronome, &.jam-track, &.recorded-track, &.backing-track { @include session-small { - max-width: 120px; + height: auto; } - - &.metronome, &.jam-track, &.recorded-track, &.backing-track { + .track-icon-pan { @include session-small { - height:auto; - } - .track-icon-pan { - @include session-small { - margin-right:0; - } - } - - .track-buttons { - @include session-small { - margin:12px 0 0; - } - } - - table.vu { - @include session-small { - margin-top:5px; - } - } - - .track-controls { - @include session-small { - margin-right:8px; - } - } - - .track-icon-pan { - @include session-small { - margin-right:2px; - } - } - .track-instrument { - @include session-small { - margin: -4px 12px 0 0; - } + margin-right: 0; } } - &.jam-track-category, &.recorded-category { - .track-controls { - @include session-small { - margin-top:5px; - margin-right:8px; - } + + .track-buttons { + @include session-small { + margin: 12px 0 0; } - table.vu { - @include session-small { - margin-top:0; - } + } + + table.vu { + @include session-small { + margin-top: 5px; } - .jam-track-header { - @include session-small { - float:left; - } + } + + .track-controls { + @include session-small { + margin-right: 8px; } - .name { - @include session-small { - float:left; - } + } + + .track-icon-pan { + @include session-small { + margin-right: 2px; } - .track-buttons { - @include session-small { - margin-top:12px; - } + } + .track-instrument { + @include session-small { + margin: -4px 12px 0 0; } } } + &.jam-track-category, &.recorded-category { + .track-controls { + @include session-small { + margin-top: 5px; + margin-right: 8px; + } + } + table.vu { + @include session-small { + margin-top: 0; + } + } + .jam-track-header { + @include session-small { + float: left; + } + } + .name { + @include session-small { + float: left; + } + } + .track-buttons { + @include session-small { + margin-top: 12px; + } + } + } + } - .track-controls { - @include session-small { - margin-top:8px; + .track-controls { + @include session-small { + margin-top: 8px; + } + } + + .track-buttons { + @include session-small { + margin-left: 14px; + } + } + + .stats-info { + float: left; + width: 120px; + border-radius: 15px; + border-color: #fc0; + border-width: 1px; + border-style: solid; + padding:10px; + height: 313px; + margin-top: 20px; + } + .stats-area { + float: left; + padding: 20px; + color: #cccccc; + + h3 { + color: white; + } + + .stat { + margin: 7px 0; + } + + .title { + display: inline-block; + width: 100px; + line-height: 20px; + height: 20px; + vertical-align: middle; + } + + .stats-holder { + margin-top: 20px; + h3 { + margin: 0 0 10px 0; + } + } + .status-icon { + border-radius: 10px; + width: 20px; + height: 20px; + margin-right: 10px; + display: inline-block; + vertical-align: middle; + &.poor { + background-color: $poor; + } + &.warn { + background-color: $fair; + } + &.unknown { + background-color: $unknown; + } + &.good { + background-color: $good; } } - .track-buttons { - @include session-small { - margin-left:14px; - } + .stat-value { + width: 50px; + display: inline-block; + height: 20px; + vertical-align: middle; + line-height: 20px; } - h2 { + } + + h2 { color: #fff; font-weight: 600; font-size: 24px; @@ -125,18 +195,18 @@ $session-screen-divider: 1190px; padding: 10px; height: 100%; margin-bottom: 15px; - color:$ColorTextTypical; - overflow:hidden; - position:relative; + color: $ColorTextTypical; + overflow: hidden; + position: relative; } .session-media-tracks { - width:34%; + width: 34%; } .session-notifications { border-right-width: 0; - display:none; //temp + display: none; //temp } .in-session-controls { @@ -162,20 +232,20 @@ $session-screen-divider: 1190px; a { img, .volume-icon { - vertical-align:top; + vertical-align: top; margin-right: 4px; } span { - vertical-align:middle; + vertical-align: middle; } } .button-grey { - margin:0 5px; + margin: 0 5px; padding: 3px 7px; &.session-leave { - margin-right:10px; + margin-right: 10px; } } } @@ -192,15 +262,15 @@ $session-screen-divider: 1190px; bottom: 0; left: 0; right: 0; - text-align:left; + text-align: left; &.media-options-showing { - top:180px; + top: 180px; } border-right: 1px solid #4c4c4c; - margin-bottom:10px; - margin-top:10px; + margin-bottom: 10px; + margin-top: 10px; } p { @@ -209,57 +279,56 @@ $session-screen-divider: 1190px; } .download-jamtrack { - margin-top:20px; + margin-top: 20px; } - .when-empty { - margin-top:25px; - margin-left:22px; - color:$ColorTextTypical; - overflow:hidden; + margin-top: 25px; + margin-left: 22px; + color: $ColorTextTypical; + overflow: hidden; } .session-track-settings { - height:20px; - cursor:pointer; - padding-bottom:1px; // to line up with SessionOtherTracks - color:$ColorTextTypical; + height: 20px; + cursor: pointer; + padding-bottom: 1px; // to line up with SessionOtherTracks + color: $ColorTextTypical; &:hover { - color:white; + color: white; } span { top: -5px; position: relative; - left:3px; + left: 3px; } } .session-invite-musicians { - height:20px; + height: 20px; cursor: pointer; - color:$ColorTextTypical; + color: $ColorTextTypical; &:hover { - color:white; + color: white; } span { - top:-5px; - position:relative; - left:3px; + top: -5px; + position: relative; + left: 3px; } } .closeAudio, .session-clear-notifications { cursor: pointer; - color:$ColorTextTypical; - height:20px; + color: $ColorTextTypical; + height: 20px; img { - top:-2px + top: -2px } span { top: -5px; @@ -268,115 +337,113 @@ $session-screen-divider: 1190px; } } - - .open-media-file-header, .use-metronome-header { - font-size:14px; - line-height:100%; - margin:0; + font-size: 14px; + line-height: 100%; + margin: 0; img { - position:relative; - top:3px; + position: relative; + top: 3px; } } .open-media-file-header { img { - vertical-align:middle; + vertical-align: middle; } .open-text { - margin-left:5px; - vertical-align:bottom; + margin-left: 5px; + vertical-align: bottom; } } .use-metronome-header { clear: both; a { - color:$ColorTextTypical; + color: $ColorTextTypical; &:hover { text-decoration: underline; - color:white; + color: white; } } } .open-media-file-options { - font-size:14px; + font-size: 14px; margin: 7px 0 0 7px !important; - color:$ColorTextTypical; + color: $ColorTextTypical; li { - margin-bottom:5px !important; - margin-left:38px !important; + margin-bottom: 5px !important; + margin-left: 38px !important; a { text-decoration: none; &:hover { text-decoration: underline; - color:white; + color: white; } - color:$ColorTextTypical; + color: $ColorTextTypical; } } } .open-metronome { - margin-left:5px; + margin-left: 5px; } .media-options { - padding-bottom:10px; + padding-bottom: 10px; } .session-mytracks-notracks p.notice { - font-size:14px; + font-size: 14px; } .session-notification { color: white; background-color: #666666; border-radius: 6px; min-height: 36px; - width:100%; - position:relative; + width: 100%; + position: relative; @include border_box_sizing; - padding:6px; - margin:10px 0; + padding: 6px; + margin: 10px 0; &.has-details { - cursor:pointer; + cursor: pointer; } .msg { - font-size:14px; + font-size: 14px; } .detail { - font-size:12px; - margin-top:5px; + font-size: 12px; + margin-top: 5px; } .notify-help { - color:#ffcc00; + color: #ffcc00; text-decoration: none; - font-size:12px; - margin-left:5px; + font-size: 12px; + margin-left: 5px; &:hover { - text-decoration:underline !important; + text-decoration: underline !important; } } } .close-window { - text-align:center; - clear:both; + text-align: center; + clear: both; } .session-volume-settings .volume-icon { - display:inline-block; - width:14px; - height:14px; - background-image:url('/assets/content/icon_mute_sm.png'); - background-repeat:no-repeat; + display: inline-block; + width: 14px; + height: 14px; + background-image: url('/assets/content/icon_mute_sm.png'); + background-repeat: no-repeat; &.muted { background-position: 0 0; diff --git a/web/app/assets/stylesheets/client/react-components/SessionTrack.css.scss b/web/app/assets/stylesheets/client/react-components/SessionTrack.css.scss index 700469329..30cc9f430 100644 --- a/web/app/assets/stylesheets/client/react-components/SessionTrack.css.scss +++ b/web/app/assets/stylesheets/client/react-components/SessionTrack.css.scss @@ -126,10 +126,20 @@ cursor:pointer; text-align: center; margin-left:10px; - } - - .circle { + border-radius:10px; + &.poor { + background-color:$poor; + } + &.warn { + background-color:$fair; + } + &.unknown { + background-color:$unknown; + } + &.good { + background-color:$good; + } } .track-icon-equalizer { @@ -370,6 +380,7 @@ } } + &.SessionTrackVolumeHover { .session-track { margin-bottom:0; @@ -381,6 +392,18 @@ height:380px ! important; } + &.SessionStatsHover { + width:380px; + height:400px; + @include border_box_sizing; + + h3 { + margin-top:20px; + color: white; + margin-left:20px; + } + } + &.SessionTrackPanHover { width:331px; height:197px; diff --git a/web/config/application.rb b/web/config/application.rb index 84296bfd3..e36417da7 100644 --- a/web/config/application.rb +++ b/web/config/application.rb @@ -380,7 +380,7 @@ if defined?(Bundler) config.session_stat_thresholds = { network: { wifi: {warn: true, poor: true, eql: true}, - audio_bitrate: {warn: 200, poor: 135, inverse:true}, + net_bitrate: {warn: 210, poor: 155, inverse:true}, ping: {warn: 40, poor: 70}, pkt_loss: {warn: 3, poor: 10} }, @@ -388,7 +388,7 @@ if defined?(Bundler) cpu: {warn: 70, poor:85} }, audio: { - audio_in_type: {warn: 'WDM', poor: 'WDM', eql: true}, + framesize: {warn: 2.6, poor: 2.6}, latency: {warn: 10, poor: 20}, input_jitter: {warn: 0.5, poor: 1}, From 0376b206cf8f3926815ab5b6a7d9113a992e30fd Mon Sep 17 00:00:00 2001 From: Seth Call Date: Thu, 10 Dec 2015 20:52:13 -0600 Subject: [PATCH 18/19] * stats workip --- .../SessionMyTrack.js.jsx.coffee | 2 +- .../SessionOtherTracks.js.jsx.coffee | 5 ++ .../SessionStatsHover.js.jsx.coffee | 73 +++++++++++++++++-- .../stores/SessionStatsStore.js.coffee | 1 + .../react-components/SessionScreen.css.scss | 4 +- .../react-components/SessionTrack.css.scss | 7 +- web/config/application.rb | 10 +-- 7 files changed, 85 insertions(+), 17 deletions(-) diff --git a/web/app/assets/javascripts/react-components/SessionMyTrack.js.jsx.coffee b/web/app/assets/javascripts/react-components/SessionMyTrack.js.jsx.coffee index e36502a3c..fc2a1b424 100644 --- a/web/app/assets/javascripts/react-components/SessionMyTrack.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/SessionMyTrack.js.jsx.coffee @@ -51,7 +51,7 @@ MixerActions = @MixerActions
- +
diff --git a/web/app/assets/javascripts/react-components/SessionOtherTracks.js.jsx.coffee b/web/app/assets/javascripts/react-components/SessionOtherTracks.js.jsx.coffee index 311f0402a..c78245c1a 100644 --- a/web/app/assets/javascripts/react-components/SessionOtherTracks.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/SessionOtherTracks.js.jsx.coffee @@ -17,6 +17,11 @@ ReactCSSTransitionGroup = React.addons.CSSTransitionGroup for participant in session.otherParticipants() + if participant.user.id == context.JK.currentUserId + participant.user.possessive = "Your" + else + participant.user.possessive = participant.user.name + "'s" + name = participant.user.name tracks = [] firstTrack = participant.tracks[0] diff --git a/web/app/assets/javascripts/react-components/SessionStatsHover.js.jsx.coffee b/web/app/assets/javascripts/react-components/SessionStatsHover.js.jsx.coffee index e2d6ee021..a25ada81c 100644 --- a/web/app/assets/javascripts/react-components/SessionStatsHover.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/SessionStatsHover.js.jsx.coffee @@ -6,14 +6,58 @@ ptrCount = 0 StatsInfo = { system: { cpu: { - good: "Your computer's processor is not overworked by JamKazam or the your system.", - warn: "Your computer's processor is being heavily used. There is little spare capacity, and this means your processor may not be able to handle all of it's tasks, causing audio quality, latency, and other issues.", - poor: "Your computer's processor is being very heavily used. There is little spare capacity, and this means your processor may not be able to handle all of it's tasks, causing audio quality, latency, and other issues." + good: (user, stats) -> "#{user.possessive} computer processor is not overworked by JamKazam or the your system.", + warn: (user, stats) -> "#{user.possessive} computer processor is being heavily used. There is little spare capacity, and this means your processor may not be able to handle all of it's tasks, causing audio quality, latency, and other issues.", + poor: (user, stats) -> "#{user.possessive} computer processor is being very heavily used. There is little spare capacity, and this means your processor may not be able to handle all of it's tasks, causing audio quality, latency, and other issues." + } + }, + network: { + wifi: { + good: (user, stats) -> "#{user.name} is using wired ethernet.", + warn: (user, stats) -> "#{user.name} is using Wi-Fi, which will create audio quality issues and additional latency.", + poor: (user, stats) -> "#{user.name} is using Wi-Fi, which will create audio quality issues and additional latency.", }, - network: { - + net_bitrate: { + good: (user, stats) -> "#{user.name} has enough bandwidth to send you a high quality audio stream.", + warn: (user, stats) -> "#{user.name} has bandwidth to send you a degraded, but sufficient, audio stream.", + poor: (user, stats) -> "#{user.name} has not enough bandwidth to send you a decent quality audio stream.", }, - audio: { + ping: { + good: (user, stats) -> "The internet connection between you and #{user.name} has very low latency.", + warn: (user, stats) -> "The internet connection between you and #{user.name} has average latency, which may affect staying in sync.", + poor: (user, stats) -> "The internet connection between you and #{user.name} has high latency, making it very difficult to stay in sync.", + }, + pkt_loss: { + good: (user, stats) -> "The internet connection between you and #{user.name} loses a small % of packets. It should not affect your audio quality.", + warn: (user, stats) -> "The internet connection between you and #{user.name} loses a significant % of packets. This may result in periodical audio artifacts.", + poor: (user, stats) -> "The internet connection between you and #{user.name} loses a high % of packets. This will result in frequent audio artifacts.", + }, + audiojq_median: { + good: (user, stats) -> "JamKazam has to maintain a only a small buffer of audio to preserve audio quality, resulting in minimal added latency.", + warn: (user, stats) -> "JamKazam has to maintain a significant buffer of audio to preserve audio quality, resulting in potentially noticeable additional latency.", + poor: (user, stats) -> "JamKazam has to maintain a large buffer of audio to preserve audio quality, resulting in noticeabley added latency.", + } + }, + audio: { + framesize: { + good: (user, stats) -> "#{user.possessive} gear is reading and writing audio data at a very high rate, keeping gear-added latency low.", + warn: (user, stats) -> "#{user.possessive} gear is reading and writing audio at a average rate, causing a few milliseconds extra latency compared to a 2.5 Frame Size.", + poor: (user, stats) -> "#{user.possessive} gear is reading and writing audio at a slow rate, causing a decent amount of latency before the internet is involved.", + }, + latency: { + good: (user, stats) -> "#{user.possessive} gear has a small amount of latency.", + warn: (user, stats) -> "#{user.possessive} gear has a significant amount of latency.", + poor: (user, stats) -> "#{user.possessive} gear has a large amount of latency, making it difficult to play in time." + }, + input_jitter: { + good: (user, stats) -> "#{user.possessive} gear has a small amount of input jitter, meaning it is keeping good time with JamKazam as it reads in your input signal.", + warn: (user, stats) -> "#{user.possessive} gear has a significant amount of input jitter, meaning it might be periodically adding delay or audio artifacts to your inputs.", + poor: (user, stats) -> "#{user.possessive} gear has a large amount of input jitter, meaning it likely adding delay and audio artifacts to your inputs.", + }, + output_jitter: { + good: (user, stats) -> "#{user.possessive} gear has a smal amount of output jitter, meaning it is keeping good time with your JamKazam as writes out your audio output.", + warn: (user, stats) -> "#{user.possessive} gear has a large amount of output jitter, meaning it might be periodically adding delay and audio artifacts to your output.", + poor: (user, stats) -> "#{user.possessive} gear has a large amount of output jitter, meaning it likely adding delay and audio artifacts to your output.", } } } @@ -39,7 +83,7 @@ StatsInfo = { classes = {'status-icon': true} classifier = properties[field + '_level'] classes[classifier] = true - `
- {name}
{value}
` + `
- {name}
{value}
` render: () -> extraInfo = 'Hover over a stat to learn more.' @@ -53,7 +97,7 @@ StatsInfo = { classifier = @state.stats?[type]?[field + '_level'] if classifier? - info = StatsInfo[type]?[field]?[classifier] + info = StatsInfo[type]?[field]?[classifier](@props.participant.user, @state.stats) if info? extraInfo = info @@ -72,6 +116,17 @@ StatsInfo = { computerStats.push(@stat(system, 'system', 'Processor', 'cpu', Math.round(system.cpu) + ' %')) if audio? + if audio.framesize? && audio.audio_in_type? + audio_type = '?' + audio_long = audio.audio_in_type.toLowerCase() + if audio_long.indexOf('asio') > -1 + audio_type = 'ASIO' + else if audio_long.indexOf('wdm') > -1 + audio_type = 'WDM' + else if audio_long.indexOf('core') > -1 + audio_type = 'Core Audio' + audioStats.push(@stat(audio, 'audio', 'Gear Driver', 'framesize', audio_type + ' @ ' + audio.framesize.toFixed(1))) + if audio.latency? audioStats.push(@stat(audio, 'audio', 'Latency', 'latency', audio.latency.toFixed(1) + ' ms')) if audio.input_jitter? @@ -87,6 +142,8 @@ StatsInfo = { if network.ping? networkStats.push(@stat(network, 'network', 'Latency', 'ping', (network.ping / 2).toFixed(1) + ' ms')) #if network.jitter? + if network.audiojq_median? + networkStats.push(@stat(network, 'network', 'Jitter Queue', 'audiojq_median', network.audiojq_median.toFixed(1))) # networkStats. if network.pkt_loss? networkStats.push(@stat(network, 'network', 'Packet Loss', 'pkt_loss', network.pkt_loss.toFixed(1) + ' %')) diff --git a/web/app/assets/javascripts/react-components/stores/SessionStatsStore.js.coffee b/web/app/assets/javascripts/react-components/stores/SessionStatsStore.js.coffee index 88fb06be8..f6afe837a 100644 --- a/web/app/assets/javascripts/react-components/stores/SessionStatsStore.js.coffee +++ b/web/app/assets/javascripts/react-components/stores/SessionStatsStore.js.coffee @@ -82,6 +82,7 @@ AudioThresholds = SessionStatThresholds.audio # pkt_loss: 100 # wifi: false + @classify(network, 'audiojq_median', NetworkThresholds) @classify(network, 'net_bitrate', NetworkThresholds) @classify(network, 'ping', NetworkThresholds) @classify(network, 'pkt_loss', NetworkThresholds) diff --git a/web/app/assets/stylesheets/client/react-components/SessionScreen.css.scss b/web/app/assets/stylesheets/client/react-components/SessionScreen.css.scss index 61640625c..64413bfbc 100644 --- a/web/app/assets/stylesheets/client/react-components/SessionScreen.css.scss +++ b/web/app/assets/stylesheets/client/react-components/SessionScreen.css.scss @@ -112,12 +112,12 @@ $session-screen-divider: 1190px; border-width: 1px; border-style: solid; padding:10px; - height: 313px; + height: 341px; margin-top: 20px; } .stats-area { float: left; - padding: 20px; + padding: 0 20px 20px 20px; color: #cccccc; h3 { diff --git a/web/app/assets/stylesheets/client/react-components/SessionTrack.css.scss b/web/app/assets/stylesheets/client/react-components/SessionTrack.css.scss index 30cc9f430..1a2e424b4 100644 --- a/web/app/assets/stylesheets/client/react-components/SessionTrack.css.scss +++ b/web/app/assets/stylesheets/client/react-components/SessionTrack.css.scss @@ -157,6 +157,11 @@ } } + &.my-track { + width:235px; + max-width:235px; + } + // media overrides &.backing-track, &.recorded-track, &.jam-track, &.metronome, &.recorded-category, &.jam-track-category { @@ -394,7 +399,7 @@ &.SessionStatsHover { width:380px; - height:400px; + height:450px; @include border_box_sizing; h3 { diff --git a/web/config/application.rb b/web/config/application.rb index e36417da7..05814067b 100644 --- a/web/config/application.rb +++ b/web/config/application.rb @@ -382,17 +382,17 @@ if defined?(Bundler) wifi: {warn: true, poor: true, eql: true}, net_bitrate: {warn: 210, poor: 155, inverse:true}, ping: {warn: 40, poor: 70}, - pkt_loss: {warn: 3, poor: 10} + pkt_loss: {warn: 3, poor: 10}, + audiojq_median: {warn: 2.1, poor: 5.1} }, system: { - cpu: {warn: 70, poor:85} + cpu: {warn: 70, poor:85} }, audio: { - framesize: {warn: 2.6, poor: 2.6}, - latency: {warn: 10, poor: 20}, + latency: {warn: 10, poor: 20}, input_jitter: {warn: 0.5, poor: 1}, - output_jitter: {warn: 0.5, poor: 1}, + output_jitter: {warn: 0.5, poor: 1}, } } end From 8e73c5bb80dc0bb5144870b2a6510feb8d7aabe6 Mon Sep 17 00:00:00 2001 From: Seth Call Date: Thu, 10 Dec 2015 22:07:56 -0600 Subject: [PATCH 19/19] * stats and vst together --- .../SessionMyTrack.js.jsx.coffee | 35 ++++++++++++++++++ .../SessionStatsHover.js.jsx.coffee | 37 +++++++++++++------ .../stores/SessionStatsStore.js.coffee | 1 + web/app/assets/javascripts/utils.js | 3 +- .../react-components/SessionScreen.css.scss | 5 +++ .../react-components/SessionTrack.css.scss | 4 ++ web/config/application.rb | 2 + 7 files changed, 75 insertions(+), 12 deletions(-) diff --git a/web/app/assets/javascripts/react-components/SessionMyTrack.js.jsx.coffee b/web/app/assets/javascripts/react-components/SessionMyTrack.js.jsx.coffee index e5a400560..4f46ca51d 100644 --- a/web/app/assets/javascripts/react-components/SessionMyTrack.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/SessionMyTrack.js.jsx.coffee @@ -5,6 +5,18 @@ ConfigureTracksActions = @ConfigureTracksActions @SessionMyTrack = React.createClass({ + mixins: [Reflux.listenTo(@SessionStatsStore,"onStatsChanged")] + + onStatsChanged: (stats) -> + @setState({stats: stats[@props.clientId]}) + + getInitialState: () -> + stats = window.SessionStatsStore.stats + if stats? + clientStats = stats[@props.clientId] + else + clientStats = null + {stats: clientStats} handleMute: (e) -> e.preventDefault() @@ -43,6 +55,14 @@ ConfigureTracksActions = @ConfigureTracksActions WebkitTransform: "rotate(#{pan}deg)" } + classification = @state.stats?.classification + + if !classification? + classification = 'unknown' + + connectionStateClasses = { 'track-connection-state': true} + connectionStateClasses[classification] = true + if @props.associatedVst? @equalizerSet = true vst = `
` @@ -59,6 +79,7 @@ ConfigureTracksActions = @ConfigureTracksActions
{vst} +

@@ -73,6 +94,7 @@ ConfigureTracksActions = @ConfigureTracksActions $root = $(this.getDOMNode()) $mute = $root.find('.track-icon-mute') $pan = $root.find('.track-icon-pan') + $connectionState = $root.find('.track-connection-state') context.JK.interactReactBubble( $mute, @@ -90,6 +112,19 @@ ConfigureTracksActions = @ConfigureTracksActions , {width:331, positions:['right', 'left'], offsetParent:$root.closest('.top-parent')}) + context.JK.interactReactBubble( + $connectionState, + 'SessionStatsHover', + () => + {participant: {client_id: this.props.clientId, user: name: 'You', possessive: 'Your'}, } + , + {width:380, positions:['right', 'left'], offsetParent:$root.closest('.screen'), extraClasses: 'self'}) + + unless this.props.hasMixer + $mute.on("mouseenter", false) + $mute.on("mouseleave", false) + $pan.on("mouseentere", false) + $pan.on("mouseleave", false) unless this.props.hasMixer $mute.on("mouseenter", false) $mute.on("mouseleave", false) diff --git a/web/app/assets/javascripts/react-components/SessionStatsHover.js.jsx.coffee b/web/app/assets/javascripts/react-components/SessionStatsHover.js.jsx.coffee index a25ada81c..d0120d481 100644 --- a/web/app/assets/javascripts/react-components/SessionStatsHover.js.jsx.coffee +++ b/web/app/assets/javascripts/react-components/SessionStatsHover.js.jsx.coffee @@ -55,9 +55,14 @@ StatsInfo = { poor: (user, stats) -> "#{user.possessive} gear has a large amount of input jitter, meaning it likely adding delay and audio artifacts to your inputs.", }, output_jitter: { - good: (user, stats) -> "#{user.possessive} gear has a smal amount of output jitter, meaning it is keeping good time with your JamKazam as writes out your audio output.", - warn: (user, stats) -> "#{user.possessive} gear has a large amount of output jitter, meaning it might be periodically adding delay and audio artifacts to your output.", + good: (user, stats) -> "#{user.possessive} gear has a small amount of output jitter, meaning it is keeping good time with your JamKazam as writes out your audio output.", + warn: (user, stats) -> "#{user.possessive} gear has a significant amount of output jitter, meaning it might be periodically adding delay and audio artifacts to your output.", poor: (user, stats) -> "#{user.possessive} gear has a large amount of output jitter, meaning it likely adding delay and audio artifacts to your output.", + }, + audio_in_type: { + good: (user, stats) -> "#{user.name} using an ideal driver type for #{user.possessive.toLowerCase()} gear.", + warn: (user, stats) -> "#{user.name} using a problematic driver type for #{user.possessive.toLowerCase()} gear.", + poor: (user, stats) -> "#{user.name} using a driver type considered problematic.", } } } @@ -116,7 +121,7 @@ StatsInfo = { computerStats.push(@stat(system, 'system', 'Processor', 'cpu', Math.round(system.cpu) + ' %')) if audio? - if audio.framesize? && audio.audio_in_type? + if audio.audio_in_type? audio_type = '?' audio_long = audio.audio_in_type.toLowerCase() if audio_long.indexOf('asio') > -1 @@ -124,9 +129,17 @@ StatsInfo = { else if audio_long.indexOf('wdm') > -1 audio_type = 'WDM' else if audio_long.indexOf('core') > -1 - audio_type = 'Core Audio' - audioStats.push(@stat(audio, 'audio', 'Gear Driver', 'framesize', audio_type + ' @ ' + audio.framesize.toFixed(1))) - + audio_type = 'CoreAudio' + audioStats.push(@stat(audio, 'audio', 'Gear Driver', 'audio_in_type', audio_type)) + if audio.framesize? + framesize = '?' + if audio.framesize == 2.5 + framesize = '2.5' + else if audio.framesize == 5 + framesize = '5' + else if audio.framesize == 10 + framesize = '10' + audioStats.push(@stat(audio, 'audio', 'Frame Size', 'framesize', framesize)) if audio.latency? audioStats.push(@stat(audio, 'audio', 'Latency', 'latency', audio.latency.toFixed(1) + ' ms')) if audio.input_jitter? @@ -134,8 +147,8 @@ StatsInfo = { if audio.output_jitter? audioStats.push(@stat(audio, 'audio', 'Output Jitter', 'output_jitter', audio.output_jitter.toFixed(2))) + networkTag = null if network? - if network.net_bitrate? networkStats.push(@stat(network, 'network', 'Bandwidth', 'net_bitrate', Math.round(network.net_bitrate) + ' k')) @@ -155,6 +168,11 @@ StatsInfo = { value = 'Ethernet' networkStats.push(@stat(network, 'network', 'Connectivity', 'wifi', value)) + networkTag = + `
+

Internet

+ {networkStats} +
` `

Session Diagnostics & Stats: {this.props.participant.user.name}

@@ -168,10 +186,7 @@ StatsInfo = {

Audio Interface

{audioStats}
-
-

Internet

- {networkStats} -
+ {networkTag}
{extraInfo} diff --git a/web/app/assets/javascripts/react-components/stores/SessionStatsStore.js.coffee b/web/app/assets/javascripts/react-components/stores/SessionStatsStore.js.coffee index f6afe837a..a59d3fa99 100644 --- a/web/app/assets/javascripts/react-components/stores/SessionStatsStore.js.coffee +++ b/web/app/assets/javascripts/react-components/stores/SessionStatsStore.js.coffee @@ -121,6 +121,7 @@ AudioThresholds = SessionStatThresholds.audio @classify(audio, 'latency', AudioThresholds) @classify(audio, 'input_jitter', AudioThresholds) @classify(audio, 'output_jitter', AudioThresholds) + @classify(audio, 'audio_in_type', AudioThresholds) switch @participantClassification when 1 then participant.classification = 'good' diff --git a/web/app/assets/javascripts/utils.js b/web/app/assets/javascripts/utils.js index c25a3292c..e4d626e0e 100644 --- a/web/app/assets/javascripts/utils.js +++ b/web/app/assets/javascripts/utils.js @@ -284,7 +284,8 @@ options.cssStyles = {} options.padding = 0; - context.JK.hoverBubble($element, '
', options) + var extra = options.extraClasses || '' + context.JK.hoverBubble($element, '
', options) return $element; } diff --git a/web/app/assets/stylesheets/client/react-components/SessionScreen.css.scss b/web/app/assets/stylesheets/client/react-components/SessionScreen.css.scss index 64413bfbc..89ee81304 100644 --- a/web/app/assets/stylesheets/client/react-components/SessionScreen.css.scss +++ b/web/app/assets/stylesheets/client/react-components/SessionScreen.css.scss @@ -104,6 +104,10 @@ $session-screen-divider: 1190px; } } + .self.SessionStatsHover .stats-info { + height:200px; + } + .stats-info { float: left; width: 120px; @@ -169,6 +173,7 @@ $session-screen-divider: 1190px; height: 20px; vertical-align: middle; line-height: 20px; + margin-right:6px; } } diff --git a/web/app/assets/stylesheets/client/react-components/SessionTrack.css.scss b/web/app/assets/stylesheets/client/react-components/SessionTrack.css.scss index 1a2e424b4..af76f6614 100644 --- a/web/app/assets/stylesheets/client/react-components/SessionTrack.css.scss +++ b/web/app/assets/stylesheets/client/react-components/SessionTrack.css.scss @@ -407,6 +407,10 @@ color: white; margin-left:20px; } + + &.self { + height:290px; + } } &.SessionTrackPanHover { diff --git a/web/config/application.rb b/web/config/application.rb index 2ae22519d..f89f8250d 100644 --- a/web/config/application.rb +++ b/web/config/application.rb @@ -389,6 +389,8 @@ if defined?(Bundler) cpu: {warn: 70, poor:85} }, audio: { + audio_in_type: {warn: 'Windows WDM-KS', poor: nil, eql:true}, + audio_out_type: {warn: 'Windows WDM-KS', poor: nil, eql:true}, framesize: {warn: 2.6, poor: 2.6}, latency: {warn: 10, poor: 20}, input_jitter: {warn: 0.5, poor: 1},