@@ -77,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,
@@ -96,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/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
new file mode 100644
index 000000000..d0120d481
--- /dev/null
+++ b/web/app/assets/javascripts/react-components/SessionStatsHover.js.jsx.coffee
@@ -0,0 +1,217 @@
+context = window
+ChannelGroupIds = context.JK.ChannelGroupIds
+MixerActions = @MixerActions
+ptrCount = 0
+
+StatsInfo = {
+ system: {
+ cpu: {
+ 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.",
+ },
+ 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.",
+ },
+ 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 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.",
+ }
+ }
+}
+
+@SessionStatsHover = React.createClass({
+
+ propTypes: {
+ clientId: React.PropTypes.string
+ }
+
+ mixins: [Reflux.listenTo(@SessionStatsStore, "onStatsChanged")]
+
+ 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
+ `
`
+
+ 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](@props.participant.user, @state.stats)
+ 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.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 = '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?
+ 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)))
+
+ networkTag = null
+ 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?
+ 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) + ' %'))
+
+ if network.wifi?
+ if network.wifi
+ value = 'Wi-Fi'
+ else
+ value = 'Ethernet'
+ networkStats.push(@stat(network, 'network', 'Connectivity', 'wifi', value))
+
+ networkTag =
+ `
+
Internet
+ {networkStats}
+ `
+
+ `
+
Session Diagnostics & Stats: {this.props.participant.user.name}
+
+
+
+
Computer
+ {computerStats}
+
+
+
Audio Interface
+ {audioStats}
+
+ {networkTag}
+
+
+ {extraInfo}
+
+
`
+
+ onStatsChanged: (stats) ->
+ stats = window.SessionStatsStore.stats
+ if stats?
+ clientStats = stats[@props.participant.client_id]
+ else
+ clientStats = null
+ @setState({stats: clientStats})
+
+ getInitialState: () ->
+ stats = window.SessionStatsStore.stats
+ if stats?
+ clientStats = stats[@props.participant.client_id]
+ else
+ clientStats = null
+ {stats: clientStats, hoverType: null, hoverField: null}
+
+
+ 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/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/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/mixins/SessionMyTracksMixin.js.coffee b/web/app/assets/javascripts/react-components/mixins/SessionMyTracksMixin.js.coffee
index 9d808485c..7fc97a850 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,30 @@ 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?
+ # 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 == trackAssignment - 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
new file mode 100644
index 000000000..b3c2b23a0
--- /dev/null
+++ b/web/app/assets/javascripts/react-components/stores/ConfigureTracksStore.js.coffee
@@ -0,0 +1,439 @@
+$ = 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
+
+###
+
+ 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
+
+ musicPorts: {inputs: [], outputs: []}
+ trackNumber: null
+ editingTrack: null
+ vstPluginList: {vsts: []}
+ vstTrackAssignments: {vsts: []}
+ attachedMidiDevices: {midiDevices: []}
+ midiTrackAssignments: {tracks: []}
+ scanningVsts: false
+ 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: (force) ->
+ logger.debug("ConfigureTracksStore:reset", this)
+ @trackNumber = null
+ @editingTrack = null
+ @loadChannels()
+ @loadTrackInstruments()
+
+ if force || context.jamClient.hasVstAssignment()
+ @performVstScan()
+ @performMidiScan()
+ @changed()
+
+
+ onTrySave: () ->
+ logger.debug("ConfigureTracksStore:trySave")
+ @trySave()
+
+ trySave: () ->
+
+ 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")
+
+ onClearVsts: () ->
+ context.jamClient.VSTClearAll()
+
+ setTimeout((() =>
+ @listVsts()
+
+ @changed()
+ ), 250)
+
+ onVstScanComplete: () ->
+ # XXX must wait a long time to get track assignments after scan/
+ console.log("vst scan complete")
+ @scanningVsts = false
+ setTimeout((() =>
+ @listVsts()
+ @changed()
+ ), 100 )
+
+ onVstChanged: () ->
+ setTimeout()
+ logger.debug("vst changed")
+
+ setTimeout((() =>
+ @listVsts()
+ @changed()
+ ), 0)
+
+ listVsts: () ->
+
+ @vstPluginList = context.jamClient.VSTListVsts()
+ @vstTrackAssignments = context.jamClient.VSTListTrackAssignments()
+
+ console.log("@vstTrackAssignments", @vstTrackAssignments)
+
+ onMidiScan: () ->
+ @performMidiScan()
+ @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?
+
+ logger.debug("show VST GUI", trackNumber)
+
+ context.jamClient.VSTShowHideGui(true, trackNumber) if trackNumber?
+
+ changed: () ->
+ @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
+
+ 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)
+
+ logger.debug("trackAssignments:", @trackAssignments)
+ logger.debug("editingTrack:", @editingTrack)
+
+ @item = {
+ musicPorts: @musicPorts,
+ trackAssignments: @trackAssignments,
+ trackNumber: @trackNumber,
+ editingTrack: @editingTrack,
+ vstPluginList: @vstPluginList,
+ vstTrackAssignments: @vstTrackAssignments,
+ attachedMidiDevices: @attachedMidiDevices,
+ nextTrackNumber: @nextTrackNumber,
+ newTrack: @newTrack,
+ midiTrackAssignments: @midiTrackAssignments,
+ scanningVsts: @scanningVsts,
+ trackType: @trackType
+ }
+
+ @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()
+
+ # let's populate this bad boy
+ @trackAssignments = {inputs: {unassigned: [], assigned: [], chat: []}, outputs: {unassigned: [], assigned: []}}
+
+ nextTrackNumber = 0
+
+ for input in @musicPorts.inputs
+ if input.assignment == ASSIGNMENT.UNASSIGNED
+ @trackAssignments.inputs.unassigned.push(input)
+ 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
+ if assigned.assignment == input.assignment
+ assigned.push(input)
+ found = true
+
+ if !found
+ 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)
+
+ @nextTrackNumber = nextTrackNumber + 1
+
+
+ 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
+
+
+ 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 !@hasVst
+ logger.debug("onAssociateVSTWithTrack skipped due to no VST")
+ return
+
+ if vst?
+ logger.debug("associating track:#{@trackNumber - 1} 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}")
+
+ @newTrack = true
+ for inputsForTrack in @trackAssignments.inputs.assigned
+ 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
+
+ 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)
+
+ @performVstScan()
+ @performMidiScan()
+
+ @changed()
+
+ @app.layout.showDialog('configure-live-tracks-dialog')
+
+ onDesiredTrackType: (trackType) ->
+ @trackType = trackType
+
+ if @trackType == 'midi'
+ @trackNumber = 100
+ @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) ->
+
+ @trackNumber = 100
+
+ 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/SessionStatsStore.js.coffee b/web/app/assets/javascripts/react-components/stores/SessionStatsStore.js.coffee
new file mode 100644
index 000000000..a59d3fa99
--- /dev/null
+++ b/web/app/assets/javascripts/react-components/stores/SessionStatsStore.js.coffee
@@ -0,0 +1,137 @@
+$ = 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(
+ {
+ listenables: @SessionStatsActions
+ rawStats: null
+
+ onPushStats: (stats) ->
+ @rawStats = stats
+ @changed()
+
+ classify: (holder, field, threshold) ->
+ value = holder[field]
+ fieldLevel = field + '_level'
+ fieldThreshold = threshold[field]
+ if value? && fieldThreshold?
+
+ 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
+ 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: () ->
+ @stats = {}
+
+ console.log("raw stats", @rawStats)
+ for participant in @rawStats
+
+ @participantClassification = 1 # 1=good, 2=warn, 3=poor
+
+ # CPU is 0-100
+ if participant.cpu?
+ system = {cpu: participant.cpu}
+
+ @classify(system, 'cpu', SystemThresholds)
+
+ participant.system = system
+
+ 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, 'audiojq_median', NetworkThresholds)
+ @classify(network, 'net_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.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)
+ @classify(audio, 'audio_in_type', 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/javascripts/react-components/stores/SessionStore.js.coffee b/web/app/assets/javascripts/react-components/stores/SessionStore.js.coffee
index b0bbb0ac7..780126b38 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,9 @@ VideoActions = @VideoActions
$(document).trigger(EVENTS.SESSION_STARTED, {session: {id: @currentSessionId}}) if document
@handleAutoOpenJamTrack()
+
+ @watchBackendStats()
+ ConfigureTracksActions.reset(false)
)
.fail((xhr) =>
@updateCurrentSession(null)
@@ -762,6 +766,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 +1061,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/javascripts/utils.js b/web/app/assets/javascripts/utils.js
index 68aa5b692..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;
}
@@ -733,6 +734,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/javascripts/wizard/gear/step_configure_tracks.js b/web/app/assets/javascripts/wizard/gear/step_configure_tracks.js
index 81c330620..72e97aad3 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(false);
+ //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
new file mode 100644
index 000000000..72d90891a
--- /dev/null
+++ b/web/app/assets/stylesheets/client/react-components/ConfigureTracks.css.scss
@@ -0,0 +1,170 @@
+@import "client/common";
+
+.ConfigureTracks {
+
+ .inputs-view {
+ border-width:1px 0;
+ border-color:$ColorText;
+ border-style:solid;
+ padding:20px 0;
+ height:220px;
+
+ .live-tracks {
+ height:165px;
+ overflow:auto;
+ a {
+ margin-left:3px;
+ }
+ }
+
+ .live-input {
+ display:inline-block;
+ &:before {
+ content: '('
+ }
+ &:after {
+ content: ')'
+ }
+ }
+ .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;
+
+ &.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 {
+ @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;
+ &:before {
+ content: '('
+ }
+ &:after {
+ content: ')'
+ }
+ }
+ }
+ 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: 17px;
+ margin-top: 5px;
+ a {
+ font-size:12px;
+ }
+ }
+}
\ 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..89ee81304 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,171 @@ $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;
+ }
+ }
+
+ .self.SessionStatsHover .stats-info {
+ height:200px;
+ }
+
+ .stats-info {
+ float: left;
+ width: 120px;
+ border-radius: 15px;
+ border-color: #fc0;
+ border-width: 1px;
+ border-style: solid;
+ padding:10px;
+ height: 341px;
+ margin-top: 20px;
+ }
+ .stats-area {
+ float: left;
+ padding: 0 20px 20px 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;
+ margin-right:6px;
}
- h2 {
+ }
+
+ h2 {
color: #fff;
font-weight: 600;
font-size: 24px;
@@ -125,18 +200,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 +237,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 +267,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 +284,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 +342,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 82614b21e..af76f6614 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,29 @@
}
}
+ .track-connection-state {
+ width:20px;
+ height:20px;
+ float:left;
+ cursor:pointer;
+ text-align: center;
+ margin-left:10px;
+ border-radius:10px;
+
+ &.poor {
+ background-color:$poor;
+ }
+ &.warn {
+ background-color:$fair;
+ }
+ &.unknown {
+ background-color:$unknown;
+ }
+ &.good {
+ background-color:$good;
+ }
+ }
+
.track-icon-equalizer {
float:left;
cursor: pointer;
@@ -134,6 +157,11 @@
}
}
+ &.my-track {
+ width:235px;
+ max-width:235px;
+ }
+
// media overrides
&.backing-track, &.recorded-track, &.jam-track, &.metronome, &.recorded-category, &.jam-track-category {
@@ -357,6 +385,7 @@
}
}
+
&.SessionTrackVolumeHover {
.session-track {
margin-bottom:0;
@@ -368,6 +397,22 @@
height:380px ! important;
}
+ &.SessionStatsHover {
+ width:380px;
+ height:450px;
+ @include border_box_sizing;
+
+ h3 {
+ margin-top:20px;
+ color: white;
+ margin-left:20px;
+ }
+
+ &.self {
+ height:290px;
+ }
+ }
+
&.SessionTrackPanHover {
width:331px;
height:197px;
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
new file mode 100644
index 000000000..212e6869d
--- /dev/null
+++ b/web/app/assets/stylesheets/dialogs/configureLiveTracksDialog.css.scss
@@ -0,0 +1,184 @@
+@import "client/common";
+
+#configure-live-tracks-dialog {
+ width: 800px;
+
+ .dialog-inner {
+ width:auto;
+ }
+
+ h3 {
+ color:white;
+ font-weight:bold;
+ margin-bottom:10px;
+ }
+
+ .manage-audio-plugins {
+ font-size:12px;
+ }
+ .actions {
+ clear:both;
+ text-align:center;
+ }
+ .track-type {
+ width:100%;
+ @include border_box_sizing;
+ 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:60%;
+ @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%;
+ }
+ }
+
+ .audio {
+ .instrument-selection {
+ width:40%;
+ @include border_box_sizing;
+ float:left;
+ margin-bottom:26px;
+ padding:10px;
+ select {
+ width:90%;
+ }
+ }
+ }
+
+ .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;
+ }
+
+ .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%;
+ @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 {
+ }
+ }
+
+ .vstScan {
+
+ margin-top:20px;
+
+ .spinner-small {
+ float:left;
+ }
+
+ span {
+ font-size:12px;
+ }
+ }
+}
\ 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/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/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 0dd83c6ae..5bbd11c83 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
new file mode 100644
index 000000000..2c488ddaa
--- /dev/null
+++ b/web/app/views/dialogs/_configureLiveTracksDialog.html.slim
@@ -0,0 +1,2 @@
+.dialog.dialog-overlay-sm.top-parent layout='dialog' layout-id='configure-live-tracks-dialog' id='configure-live-tracks-dialog'
+ = 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 5611533d1..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
@@ -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
@@ -72,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 45db4cb3f..2c45d7df2 100644
--- a/web/app/views/dialogs/_dialogs.html.haml
+++ b/web/app/views/dialogs/_dialogs.html.haml
@@ -44,3 +44,5 @@
= render 'dialogs/recordingSelectorDialog'
= render 'dialogs/soundCloudPlayerDialog'
= render 'dialogs/deleteVideoConfirmDialog'
+= render 'dialogs/configureLiveTracksDialog'
+= render 'dialogs/configureOutputsDialog'
\ No newline at end of file
diff --git a/web/config/application.rb b/web/config/application.rb
index 5d9fc97d6..f89f8250d 100644
--- a/web/config/application.rb
+++ b/web/config/application.rb
@@ -377,5 +377,27 @@ 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},
+ net_bitrate: {warn: 210, poor: 155, inverse:true},
+ ping: {warn: 40, poor: 70},
+ pkt_loss: {warn: 3, poor: 10},
+ audiojq_median: {warn: 2.1, poor: 5.1}
+ },
+ system: {
+ 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},
+ output_jitter: {warn: 0.5, poor: 1},
+ }
+ }
+ 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
diff --git a/web/lib/tasks/jam_tracks.rake b/web/lib/tasks/jam_tracks.rake
index e764a6de2..88bf63d04 100644
--- a/web/lib/tasks/jam_tracks.rake
+++ b/web/lib/tasks/jam_tracks.rake
@@ -95,6 +95,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