From d20713144e48824e0be37d7a680b20284f0cf701 Mon Sep 17 00:00:00 2001 From: Seth Call Date: Thu, 13 Feb 2014 20:17:16 +0000 Subject: [PATCH] * VRFS-1062 - mostly done. l2m there is an open question on before moving on --- web/app/assets/javascripts/faderHelpers.js | 425 +++--- web/app/assets/javascripts/ftue.js | 1579 ++++++++++---------- web/app/assets/javascripts/session.js | 1 + 3 files changed, 1012 insertions(+), 993 deletions(-) diff --git a/web/app/assets/javascripts/faderHelpers.js b/web/app/assets/javascripts/faderHelpers.js index 4fce20c60..49f4814fc 100644 --- a/web/app/assets/javascripts/faderHelpers.js +++ b/web/app/assets/javascripts/faderHelpers.js @@ -1,241 +1,248 @@ /** -* Functions related to faders (slider controls for volume). -* These functions are intimately tied to the markup defined in -* the templates in _faders.html.erb -*/ -(function(g, $) { + * Functions related to faders (slider controls for volume). + * These functions are intimately tied to the markup defined in + * the templates in _faders.html.erb + */ +(function (g, $) { - "use strict"; + "use strict"; - g.JK = g.JK || {}; + g.JK = g.JK || {}; - var $draggingFaderHandle = null; - var $draggingFader = null; - var draggingOrientation = null; + var $draggingFaderHandle = null; + var $draggingFader = null; + var draggingOrientation = null; - var subscribers = {}; - var logger = g.JK.logger; + var subscribers = {}; + var logger = g.JK.logger; - function faderClick(e) { - e.stopPropagation(); - - var $fader = $(this); - draggingOrientation = $fader.attr('orientation'); - var faderId = $fader.attr("fader-id"); - var offset = $fader.offset(); - var position = { top: e.pageY - offset.top, left: e.pageX - offset.left} + function faderClick(e) { + e.stopPropagation(); - var faderPct = faderValue($fader, e, position); + var $fader = $(this); + draggingOrientation = $fader.attr('orientation'); + var faderId = $fader.attr("fader-id"); + var offset = $fader.offset(); + var position = { top: e.pageY - offset.top, left: e.pageX - offset.left} - if (faderPct < 0 || faderPct > 100) { - return false; - } + var faderPct = faderValue($fader, e, position); - // Notify subscribers of value change - g._.each(subscribers, function(changeFunc, index, list) { - if (faderId === index) { - changeFunc(faderId, faderPct, false); - } - }); - - setHandlePosition($fader, faderPct); - return false; + if (faderPct < 0 || faderPct > 100) { + return false; } - function setHandlePosition($fader, value) { - var ratio, position; - var $handle = $fader.find('div[control="fader-handle"]'); - var handleCssAttribute = getHandleCssAttribute($fader); + // Notify subscribers of value change + g._.each(subscribers, function (changeFunc, index, list) { + if (faderId === index) { + changeFunc(faderId, faderPct, false); + } + }); - if(draggingOrientation === "horizontal") { - ratio = value / 100; - position = ((ratio * $fader.width()) - (ratio * handleWidth(draggingOrientation))) + 'px'; - } - else { - ratio = (100 - value) / 100; - position = ((ratio * $fader.height()) - (ratio * handleWidth(draggingOrientation))) + 'px'; - } - $handle.css(handleCssAttribute, position); + setHandlePosition($fader, faderPct); + return false; + } + + function setHandlePosition($fader, value) { + var ratio, position; + var $handle = $fader.find('div[control="fader-handle"]'); + + var orientation = $fader.attr('orientation'); + var handleCssAttribute = getHandleCssAttribute($fader); + + // required because this method is entered directly when from a callback + + if (draggingOrientation === "horizontal") { + ratio = value / 100; + position = ((ratio * $fader.width()) - (ratio * handleWidth(draggingOrientation))) + 'px'; + } + else { + ratio = (100 - value) / 100; + position = ((ratio * $fader.height()) - (ratio * handleWidth(draggingOrientation))) + 'px'; } - function faderValue($fader, e, offset) { - var orientation = $fader.attr('orientation'); - var getPercentFunction = getVerticalFaderPercent; - var relativePosition = offset.top; - if (orientation && orientation == 'horizontal') { - getPercentFunction = getHorizontalFaderPercent; - relativePosition = offset.left; - } - return getPercentFunction(relativePosition, $fader); + $handle.css(handleCssAttribute, position); + } + + function faderValue($fader, e, offset) { + var orientation = $fader.attr('orientation'); + var getPercentFunction = getVerticalFaderPercent; + var relativePosition = offset.top; + if (orientation && orientation == 'horizontal') { + getPercentFunction = getHorizontalFaderPercent; + relativePosition = offset.left; + } + return getPercentFunction(relativePosition, $fader); + } + + function getHandleCssAttribute($fader) { + var orientation = $fader.attr('orientation'); + return (orientation === 'horizontal') ? 'left' : 'top'; + } + + function getVerticalFaderPercent(eventY, $fader) { + return getFaderPercent(eventY, $fader, 'vertical'); + } + + function getHorizontalFaderPercent(eventX, $fader) { + return getFaderPercent(eventX, $fader, 'horizontal'); + } + + /** + * Returns the current value of the fader as int percent 0-100 + */ + function getFaderPercent(value, $fader, orientation) { + var faderSize, faderPct; + + // the handle takes up room, and all calculations use top. So when the + // handle *looks* like it's at the bottom by the user, it won't give a 0% value. + // so, we subtract handleWidth from the size of it's parent + + if (orientation === "horizontal") { + faderSize = $fader.width(); + faderPct = Math.round(( value + (value / faderSize * handleWidth(orientation))) / faderSize * 100); + } + else { + faderSize = $fader.height(); + faderPct = Math.round((faderSize - handleWidth(orientation) - value) / (faderSize - handleWidth(orientation)) * 100); } - function getHandleCssAttribute($fader) { - var orientation = $fader.attr('orientation'); - return (orientation === 'horizontal') ? 'left' : 'top'; + return faderPct; + } + + function onFaderDrag(e, ui) { + var faderId = $draggingFader.attr("fader-id"); + var faderPct = faderValue($draggingFader, e, ui.position); + + // protect against attempts to drag outside of the slider, which jquery.draggable sometimes allows + if (faderPct < 0 || faderPct > 100) { + return false; } - function getVerticalFaderPercent(eventY, $fader) { - return getFaderPercent(eventY, $fader, 'vertical'); + // Notify subscribers of value change + g._.each(subscribers, function (changeFunc, index, list) { + if (faderId === index) { + changeFunc(faderId, faderPct, true); + } + }); + } + + function onFaderDragStart(e, ui) { + $draggingFaderHandle = $(this); + $draggingFader = $draggingFaderHandle.closest('div[control="fader"]'); + draggingOrientation = $draggingFader.attr('orientation'); + } + + function onFaderDragStop(e, ui) { + var faderId = $draggingFader.attr("fader-id"); + var faderPct = faderValue($draggingFader, e, ui.position); + + // protect against attempts to drag outside of the slider, which jquery.draggable sometimes allows + // do not return 'false' though, because that stops future drags from working, for some reason + if (faderPct < 0 || faderPct > 100) { + return; } - function getHorizontalFaderPercent(eventX, $fader) { - return getFaderPercent(eventX, $fader, 'horizontal'); - } + // Notify subscribers of value change + g._.each(subscribers, function (changeFunc, index, list) { + if (faderId === index) { + changeFunc(faderId, faderPct, false); + } + }); + $draggingFaderHandle = null; + $draggingFader = null; + draggingOrientation = null; + } + + function handleWidth(orientation) { + return orientation === "horizontal" ? 8 : 11; + } + + g.JK.FaderHelpers = { /** - * Returns the current value of the fader as int percent 0-100 + * Subscribe to fader change events. Provide a subscriber id + * and a function in the form: change(faderId, newValue) which + * will be called anytime a fader changes value. */ - function getFaderPercent(value, $fader, orientation) { - var faderSize, faderPct; + subscribe: function (subscriberId, changeFunction) { + subscribers[subscriberId] = changeFunction; + }, - // the handle takes up room, and all calculations use top. So when the - // handle *looks* like it's at the bottom by the user, it won't give a 0% value. - // so, we subtract handleWidth from the size of it's parent + /** + * Render a fader into the element identifed by the provided + * selector, with the provided options. + */ + renderFader: function (selector, userOptions) { + if (userOptions === undefined) { + throw ("renderFader: userOptions is required"); + } + if (!(userOptions.hasOwnProperty("faderId"))) { + throw ("renderFader: userOptions.faderId is required"); + } + var renderDefaults = { + faderType: "vertical", + height: 83, // only used for vertical + width: 83 // only used for horizontal + }; + var options = $.extend({}, renderDefaults, userOptions); + var templateSelector = (options.faderType === 'horizontal') ? + "#template-fader-h" : '#template-fader-v'; + var templateSource = $(templateSelector).html(); - if (orientation === "horizontal") { - faderSize = $fader.width(); - faderPct = Math.round( ( value + (value / faderSize * handleWidth(orientation))) / faderSize * 100); - } - else { - faderSize = $fader.height(); - faderPct = Math.round((faderSize - handleWidth(orientation) - value)/(faderSize - handleWidth(orientation)) * 100); + $(selector).html(g._.template(templateSource, options)); + + $('div[control="fader-handle"]', $(selector)).draggable({ + drag: onFaderDrag, + start: onFaderDragStart, + stop: onFaderDragStop, + containment: "parent", + axis: options.faderType === 'horizontal' ? 'x' : 'y' + }) + + // Embed any custom styles, applied to the .fader below selector + if ("style" in options) { + for (var key in options.style) { + $(selector + ' .fader').css(key, options.style[key]); } + } + }, - return faderPct; + convertLinearToDb: function (input) { + var temp; + temp = 0.0; + // coefficients + var a = -8.0013990435159329E+01; + var b = 4.1755639785242042E+00; + var c = -1.4036729740086906E-01; + var d = 3.1788545454166156E-03; + var f = -3.5148685730880861E-05; + var g = 1.4221429222004657E-07; + temp += a + + b * input + + c * Math.pow(input, 2.0) + + d * Math.pow(input, 3.0) + + f * Math.pow(input, 4.0) + + g * Math.pow(input, 5.0); + + return temp; + }, + + setFaderValue: function (faderId, faderValue) { + var $fader = $('[fader-id="' + faderId + '"]'); + this.setHandlePosition($fader, faderValue); + }, + + setHandlePosition: function ($fader, faderValue) { + draggingOrientation = $fader.attr('orientation'); + setHandlePosition($fader, faderValue); + draggingOrientation = null; + }, + + initialize: function () { + $('body').on('click', 'div[control="fader"]', faderClick); } - function onFaderDrag(e, ui) { - var faderId = $draggingFader.attr("fader-id"); - var faderPct = faderValue($draggingFader, e, ui.position); - - // protect against attempts to drag outside of the slider, which jquery.draggable sometimes allows - if (faderPct < 0 || faderPct > 100) { - return false; - } - - // Notify subscribers of value change - g._.each(subscribers, function(changeFunc, index, list) { - if (faderId === index) { - changeFunc(faderId, faderPct, true); - } - }); - } - - function onFaderDragStart(e, ui) { - $draggingFaderHandle = $(this); - $draggingFader = $draggingFaderHandle.closest('div[control="fader"]'); - draggingOrientation = $draggingFader.attr('orientation'); - } - - function onFaderDragStop(e, ui) { - var faderId = $draggingFader.attr("fader-id"); - var faderPct = faderValue($draggingFader, e, ui.position); - - // protect against attempts to drag outside of the slider, which jquery.draggable sometimes allows - // do not return 'false' though, because that stops future drags from working, for some reason - if (faderPct < 0 || faderPct > 100) { - return; - } - - // Notify subscribers of value change - g._.each(subscribers, function(changeFunc, index, list) { - if (faderId === index) { - changeFunc(faderId, faderPct, false); - } - }); - $draggingFaderHandle = null; - $draggingFader = null; - draggingOrientation = null; - } - - function handleWidth(orientation) { - return orientation === "horizontal" ? 8 : 11; - } - - g.JK.FaderHelpers = { - - /** - * Subscribe to fader change events. Provide a subscriber id - * and a function in the form: change(faderId, newValue) which - * will be called anytime a fader changes value. - */ - subscribe: function(subscriberId, changeFunction) { - subscribers[subscriberId] = changeFunction; - }, - - /** - * Render a fader into the element identifed by the provided - * selector, with the provided options. - */ - renderFader: function(selector, userOptions) { - if (userOptions === undefined) { - throw ("renderFader: userOptions is required"); - } - if (!(userOptions.hasOwnProperty("faderId"))) { - throw ("renderFader: userOptions.faderId is required"); - } - var renderDefaults = { - faderType: "vertical", - height: 83, // only used for vertical - width: 83 // only used for horizontal - }; - var options = $.extend({}, renderDefaults, userOptions); - var templateSelector = (options.faderType === 'horizontal') ? - "#template-fader-h" : '#template-fader-v'; - var templateSource = $(templateSelector).html(); - - $(selector).html(g._.template(templateSource, options)); - - $('div[control="fader-handle"]', $(selector)).draggable({ - drag: onFaderDrag, - start: onFaderDragStart, - stop: onFaderDragStop, - containment: "parent", - axis: options.faderType === 'horizontal' ? 'x' : 'y' - }) - - // Embed any custom styles, applied to the .fader below selector - if ("style" in options) { - for (var key in options.style) { - $(selector + ' .fader').css(key, options.style[key]); - } - } - }, - - convertLinearToDb: function(input) { - var temp; - temp = 0.0; - // coefficients - var a = -8.0013990435159329E+01; - var b = 4.1755639785242042E+00; - var c = -1.4036729740086906E-01; - var d = 3.1788545454166156E-03; - var f = -3.5148685730880861E-05; - var g = 1.4221429222004657E-07; - temp += a + - b * input + - c * Math.pow(input, 2.0) + - d * Math.pow(input, 3.0) + - f * Math.pow(input, 4.0) + - g * Math.pow(input, 5.0); - - return temp; - }, - - setFaderValue: function(faderId, faderValue) { - var $fader = $('[fader-id="' + faderId + '"]'); - this.setHandlePosition($fader, faderValue); - }, - - setHandlePosition: function($fader, faderValue) { - setHandlePosition($fader, faderValue); - }, - - initialize: function() { - $('body').on('click', 'div[control="fader"]', faderClick); - } - - }; + }; })(window, jQuery); \ No newline at end of file diff --git a/web/app/assets/javascripts/ftue.js b/web/app/assets/javascripts/ftue.js index 610b81d77..9587649c7 100644 --- a/web/app/assets/javascripts/ftue.js +++ b/web/app/assets/javascripts/ftue.js @@ -1,793 +1,804 @@ /** -* FtueAudioSelectionScreen -* Javascript that goes with the screen where initial -* selection of the audio devices takes place. -* Corresponds to /#ftue2 -*/ -(function(context,$) { - - "use strict"; - - context.JK = context.JK || {}; - context.JK.FtueWizard = function(app) { - context.JK.FtueWizard.latencyTimeout = true; - context.JK.FtueWizard.latencyMS = Number.MAX_VALUE; - var rest = context.JK.Rest(); - - var logger = context.JK.logger; - var jamClient = context.jamClient; - var win32 = true; - - var deviceSetMap = { - 'audio-input': jamClient.FTUESetMusicInput, - 'audio-output': jamClient.FTUESetMusicOutput, - 'voice-chat-input': jamClient.FTUESetChatInput - }; - - var faderMap = { - 'ftue-2-audio-input-fader': jamClient.FTUESetInputVolume, - 'ftue-2-voice-input-fader': jamClient.FTUESetOutputVolume, - 'ftue-audio-input-fader': jamClient.FTUESetInputVolume, - 'ftue-voice-input-fader': jamClient.FTUESetChatInputVolume, - 'ftue-audio-output-fader': jamClient.FTUESetOutputVolume - }; - - function latencyTimeoutCheck() { - if (context.JK.FtueWizard.latencyTimeout) { - jamClient.FTUERegisterLatencyCallback(''); - context.JK.app.setWizardStep("5"); - } - } - - function afterHide(data) { - // Unsubscribe from FTUE VU callbacks. - jamClient.FTUERegisterVUCallbacks('','',''); - } - - function beforeShow(data) { - var vuMeters = [ - '#ftue-2-audio-input-vu-left', - '#ftue-2-audio-input-vu-right', - '#ftue-2-voice-input-vu-left', - '#ftue-2-voice-input-vu-right', - '#ftue-audio-input-vu-left', - '#ftue-audio-input-vu-right', - '#ftue-voice-input-vu-left', - '#ftue-voice-input-vu-right', - '#ftue-audio-output-vu-left', - '#ftue-audio-output-vu-right' - ]; - $.each(vuMeters, function() { - context.JK.VuHelpers.renderVU(this, - {vuType:"horizontal", lightCount: 12, lightWidth: 15, lightHeight: 3}); - }); - - var faders = context._.keys(faderMap); - $.each(faders, function() { - var fid = this; - context.JK.FaderHelpers.renderFader('#' + fid, - {faderId: fid, faderType:"horizontal", width:163}); - context.JK.FaderHelpers.subscribe(fid, faderChange); - }); - } - - function afterShow(data) {} - - function faderChange(faderId, newValue, dragging) { - var setFunction = faderMap[faderId]; - // TODO - using hardcoded range of -80 to 20 for output levels. - var mixerLevel = newValue - 80; // Convert our [0-100] to [-80 - +20] range - setFunction(mixerLevel); - } - - function settingsInit() { - jamClient.FTUEInit(); - setLevels(0); - // Always reset the driver select box to "Choose..." which forces everything - // to sync properly when the user reselects their driver of choice. - // VRFS-375 and VRFS-561 - $('[layout-wizard="ftue"] [layout-wizard-step="0"] .settings-2-device select').val(""); - $('[layout-wizard="ftue"] [layout-wizard-step="0"] .settings-2-voice select').val(""); - $('[layout-wizard="ftue"] [layout-wizard-step="2"] .asio-settings .settings-driver select').val(""); - } - - function setLevels(db) { - if (db < -80 || db > 20) { - throw ("BUG! ftue.js:setLevels db arg must be between -80 and 20"); - } - var trackIds = jamClient.SessionGetIDs(); - var controlStates = jamClient.SessionGetControlState(trackIds); - $.each(controlStates, function(index, value) { - context.JK.Mixer.fillTrackVolume(value, false); - // Default input/output to 0 DB - context.trackVolumeObject.volL = db; - context.trackVolumeObject.volR = db; - jamClient.SessionSetControlState(trackIds[index]); - }); - $.each(context._.keys(faderMap), function(index, faderId) { - // faderChange takes a value from 0-100 - var $fader = $('[fader-id="' + faderId + '"]'); - context.JK.FaderHelpers.setHandlePosition($fader, db + 80); - }); - } - - function testComplete() { - logger.debug("Test complete"); - var latencyMS = context.JK.FtueWizard.latencyMS; - var ftueSucceeded = latencyMS <= 20; - if (ftueSucceeded) { - logger.debug(latencyMS + " is <= 20. Setting FTUE status to true"); - ftueSave(true); // Save the profile - context.jamClient.FTUESetStatus(true); // No FTUE wizard next time - rest.userCertifiedGear({success:true}); - - // notify anyone curious about how it went - $('div[layout-id=ftue]').trigger('ftue_success'); - } - else { - rest.userCertifiedGear({success:false, reason:"latency=" + latencyMS}); - } - - updateGauge(); - } - - function updateGauge() { - var latencyMS = context.JK.FtueWizard.latencyMS; - // Round to 2 decimal places - latencyMS = (Math.round(latencyMS * 100)) / 100; - logger.debug("Latency Value: " + latencyMS); - if (latencyMS > 20) { - $('#ftue-latency-congrats').hide(); - $('#ftue-latency-fail').show(); - } else { - $('#ftue-latency-ms').html(latencyMS); - $('#ftue-latency-congrats').show(); - $('#ftue-latency-fail').hide(); - if (latencyMS <= 10) { - $('[layout-wizard-step="6"] .btnHelp').hide(); - $('[layout-wizard-step="6"] .btnRepeat').hide(); - } - } - setNeedleValue(latencyMS); - } - - // Function to calculate an absolute value and an absolute value range into - // a number of degrees on a circualar "speedometer" gauge. The 0 degrees value - // points straight up to the middle of the real-world value range. - // Arguments: - // value: The real-world value (e.g. 20 milliseconds) - // minValue: The real-world minimum value (e.g. 0 milliseconds) - // maxValue: The real-world maximum value (e.g. 40 milliseconds) - // degreesUsed: The number of degrees used to represent the full real-world - // range. 360 would be the entire circle. 270 would be 3/4ths - // of the circle. The unused gap will be at the bottom of the - // gauge. - // (e.g. 300) - function degreesFromRange(value, minValue, maxValue, degreesUsed) { - if (value > maxValue) { value = maxValue; } - if (value < minValue) { value = minValue; } - var range = maxValue-minValue; - var floatVal = value/range; - var degrees = Math.round(floatVal * degreesUsed); - degrees -= Math.round(degreesUsed/2); - if (degrees < 0) { - degrees += 360; - } - return degrees; - } - - // Given a number of MS, and assuming the gauge has a range from - // 0 to 40 ms. Update the gauge to the proper value. - function setNeedleValue(ms) { - logger.debug("setNeedleValue: " + ms); - var deg = degreesFromRange(ms, 0, 40, 300); - - // Supporting Firefix, Chrome, Safari, Opera and IE9+. - // Should we need to support < IE9, this will need more work - // to compute the matrix transformations in those browsers - - // and I don't believe they support transparent PNG graphic - // rotation, so we'll have to change the needle itself. - var css = { - //"behavior":"url(-ms-transform.htc)", - /* Firefox */ - "-moz-transform":"rotate(" + deg + "deg)", - /* Safari and Chrome */ - "-webkit-transform":"rotate(" + deg + "deg)", - /* Opera */ - "-o-transform":"rotate(" + deg + "deg)", - /* IE9 */ - "-ms-transform":"rotate(" + deg + "deg)" - /* IE6,IE7 */ - //"filter": "progid:DXImageTransform.Microsoft.Matrix(sizingMethod='auto expand', M11=0.7071067811865476, M12=-0.7071067811865475, M21=0.7071067811865475, M22=0.7071067811865476)", - /* IE8 */ - //"-ms-filter": '"progid:DXImageTransform.Microsoft.Matrix(SizingMethod=\'auto expand\', M11=0.7071067811865476, M12=-0.7071067811865475, M21=0.7071067811865475, M22=0.7071067811865476)"' - }; - - $('#ftue-latency-numerical').html(ms); - $('#ftue-latency-needle').css(css); - - } - - function testLatency() { - // we'll just register for call back right here and unregister in the callback. - context.JK.FtueWizard.latencyTimeout = true; - var cbFunc = 'JK.ftueLatencyCallback'; - logger.debug("Registering latency callback: " + cbFunc); - jamClient.FTUERegisterLatencyCallback('JK.ftueLatencyCallback'); - var now = new Date(); - logger.debug("Starting Latency Test..." + now); - context.setTimeout(latencyTimeoutCheck, 300 * 1000); // Timeout to 5 minutes - jamClient.FTUEStartLatency(); - } - - function openASIOControlPanel(evt) { - if (win32) { - logger.debug("Calling FTUEOpenControlPanel()"); - jamClient.FTUEOpenControlPanel(); - } - } - - function asioResync(evt) { - jamClient.FTUERefreshDevices(); - ftueSave(false); - } - - function ftueSave(persist) { - // Explicitly set inputs and outputs to dropdown values - // before save as the client seems to want this on changes to - // things like frame size, etc.. - var $audioSelects = $('[layout-wizard-step="2"] .settings-controls select'); - $.each($audioSelects, function(index, value) { - var $select = $(value); - setAudioDevice($select); - }); - if (musicInAndOutSet()) { - - // If there is no voice-chat-input selected, let the back-end know - // that we're using music for voice-chat. - if ($('[layout-wizard-step="2"] select[data-device="voice-chat-input"]').val()) { - // Voice input selected - jamClient.TrackSetChatEnable(true); - } else { - // No voice input selected. - jamClient.TrackSetChatEnable(false); - } - - setDefaultInstrumentFromProfile(); - - logger.debug("Calling FTUESave(" + persist + ")"); - var response = jamClient.FTUESave(persist); - setLevels(0); - if (response) { - logger.warn(response); - // TODO - we may need to do something about errors on save. - // per VRFS-368, I'm hiding the alert, and logging a warning. - // context.alert(response); - } - } else { - logger.debug("Aborting FTUESave as we need input + output selected."); - } - } - - function setAsioFrameSize(evt) { - var val = parseFloat($(evt.currentTarget).val(),10); - if (isNaN(val)) { - return; - } - logger.debug("Calling FTUESetFrameSize(" + val + ")"); - jamClient.FTUESetFrameSize(val); - ftueSave(false); - } - function setAsioInputLatency(evt) { - var val = parseInt($(evt.currentTarget).val(),10); - if (isNaN(val)) { - return; - } - logger.debug("Calling FTUESetInputLatency(" + val + ")"); - jamClient.FTUESetInputLatency(val); - ftueSave(false); - } - function setAsioOutputLatency(evt) { - var val = parseInt($(evt.currentTarget).val(),10); - if (isNaN(val)) { - return; - } - logger.debug("Calling FTUESetOutputLatency(" + val + ")"); - jamClient.FTUESetOutputLatency(val); - ftueSave(false); - } - - function videoLinkClicked(evt) { - var myOS = jamClient.GetOSAsString(); - var link; - if (myOS === 'MacOSX') { - link = $(evt.currentTarget).attr('external-link-mac'); - } else if (myOS === 'Win32') { - link = $(evt.currentTarget).attr('external-link-win'); - } - if (link) { - context.jamClient.OpenSystemBrowser(link); - } - } - - function events() { - $('.ftue-video-link').hover( - function(evt) { // handlerIn - $(this).addClass('hover'); - }, - function(evt) { // handlerOut - $(this).removeClass('hover'); - } - ); - $('.ftue-video-link').on('click', videoLinkClicked); - $('[layout-wizard-step="2"] .settings-driver select').on('change', audioDriverChanged); - $('[layout-wizard-step="2"] .settings-controls select').on('change', audioDeviceChanged); - $('#btn-asio-control-panel').on('click', openASIOControlPanel); - $('#btn-asio-resync').on('click', asioResync); - $('#asio-framesize').on('change', setAsioFrameSize); - $('#asio-input-latency').on('change', setAsioInputLatency); - $('#asio-output-latency').on('change', setAsioOutputLatency); - // New FTUE events - $('.ftue-new .settings-2-device select').on('change', newFtueAudioDeviceChanged); - $('.ftue-new .settings-2-voice select').on('change', newFtueAudioDeviceChanged); - $('#btn-ftue-2-asio-resync').on('click', newFtueAsioResync); - $('#btn-ftue-2-asio-control-panel').on('click', openASIOControlPanel); - $('#ftue-2-asio-framesize').on('change', newFtueSetAsioFrameSize); - $('#ftue-2-asio-input-latency').on('change', newFtueSetAsioInputLatency); - $('#ftue-2-asio-output-latency').on('change', newFtueSetAsioOutputLatency); - $('#btn-ftue-2-save').on('click', newFtueSaveSettingsHandler); - } - - /** - * This function loads the available audio devices from jamClient, and - * builds up the select dropdowns in the audio-settings step of the FTUE wizard. - */ - function loadAudioDevices() { - var funcs = [ - jamClient.FTUEGetMusicInputs, - jamClient.FTUEGetChatInputs, - jamClient.FTUEGetMusicOutputs - ]; - var selectors = [ - '[layout-wizard-step="2"] .audio-input select', - '[layout-wizard-step="2"] .voice-chat-input select', - '[layout-wizard-step="2"] .audio-output select' - ]; - var optionsHtml = ''; - var deviceOptionFunc = function(deviceKey, index, list) { - optionsHtml += ''; - }; - for (var i=0; i' + - drivers[driverKey] + ''; - }; - - var optionsHtml = ''; - var selectors = [ - '[layout-wizard-step="0"] .settings-2-device select', - '[layout-wizard-step="0"] .settings-2-voice select', - '[layout-wizard-step="2"] .settings-driver select' - ]; - var sortedDeviceKeys = context._.keys(drivers).sort(); - context._.each(sortedDeviceKeys, driverOptionFunc); - $.each(selectors, function(index, selector) { - var $select = $(selector); - $select.empty(); - $select.html(optionsHtml); - context.JK.dropdown($select); - }); - } - - /** Once a configuration is decided upon, we set the user's default instrument based on data from their profile */ - function setDefaultInstrumentFromProfile() { - var defaultInstrumentId; - if (context.JK.userMe.instruments && context.JK.userMe.instruments.length > 0) { - defaultInstrumentId = context.JK.instrument_id_to_instrument[context.JK.userMe.instruments[0].instrument_id].client_id; - } - else { - defaultInstrumentId = context.JK.server_to_client_instrument_map['Other'].client_id; - } - - jamClient.TrackSetInstrument(1, defaultInstrumentId); - } - - /** - * Handler for the new FTUE save button. - */ - function newFtueSaveSettingsHandler(evt) { - evt.preventDefault(); - var $saveButton = $('#btn-ftue-2-save'); - if ($saveButton.hasClass('disabled')) { - return; - } - var selectedAudioDevice = $('.ftue-new .settings-2-device select').val(); - if (!(selectedAudioDevice)) { - app.notify({ - title: "Please select an audio device", - text: "Please choose a usable audio device, or select cancel." - }); - return false; - } - - setDefaultInstrumentFromProfile(); - - jamClient.FTUESave(true); - jamClient.FTUESetStatus(true); // No FTUE wizard next time - rest.userCertifiedGear({success:true}); - app.layout.closeDialog('ftue'); - if (app.afterFtue) { - // If there's a function to invoke, invoke it. - app.afterFtue(); - app.afterFtue = null; - } - return false; - } - - // Handler for when the audio device is changed in the new FTUE screen - // This works differently from the old FTUE. There is no input/output selection, - // as soon as the user chooses a driver, we auto-assign inputs and outputs. - // We also call jamClient.FTUEGetExpectedLatency, which returns a structure like: - // { latency: 11.1875, latencyknown: true, latencyvar: 1} - function newFtueAudioDeviceChanged(evt) { - var $select = $(evt.currentTarget); - - var $audioSelect = $('.ftue-new .settings-2-device select'); - var $voiceSelect = $('.ftue-new .settings-2-voice select'); - var audioDriverId = $audioSelect.val(); - var voiceDriverId = $voiceSelect.val(); - jamClient.FTUESetMusicDevice(audioDriverId); - jamClient.FTUESetChatInput(voiceDriverId); - if (voiceDriverId) { // Let the back end know whether a voice device is selected - jamClient.TrackSetChatEnable(true); - } else { - jamClient.TrackSetChatEnable(false); - } - if (!audioDriverId) { - // reset back to 'Choose...' - newFtueEnableControls(false); - return; - } - var musicInputs = jamClient.FTUEGetMusicInputs(); - var musicOutputs = jamClient.FTUEGetMusicOutputs(); - - // set the music input to the first available input, - // and output to the first available output - var kin = null, kout = null, k = null; - // TODO FIXME - this jamClient call returns a dictionary. - // It's difficult to know what to auto-choose. - // For example, with my built-in audio, the keys I get back are - // digital in, line in, mic in and stereo mix. Which should we pick for them? - for (k in musicInputs) { - kin = k; - break; - } - for (k in musicOutputs) { - kout = k; - break; - } - var result; - if (kin && kout) { - jamClient.FTUESetMusicInput(kin); - jamClient.FTUESetMusicOutput(kout); - } else { - // TODO FIXME - how to handle a driver selection where we are unable to - // autoset both inputs and outputs? (I'd think this could happen if either - // the input or output side returned no values) - return; - } - - newFtueEnableControls(true); - newFtueOsSpecificSettings(); - setLevels(0); - newFtueUpdateLatencyView('loading'); - jamClient.FTUESave(false); - setVuCallbacks(); - - var latency = jamClient.FTUEGetExpectedLatency(); - newFtueUpdateLatencyView(latency); - } - - function newFtueSave(persist) { - newFtueUpdateLatencyView('loading'); - jamClient.FTUESave(persist); - var latency = jamClient.FTUEGetExpectedLatency(); - newFtueUpdateLatencyView(latency); - } - - function newFtueAsioResync(evt) { - // In theory, we should be calling the following, but it causes - // us to not have both inputs/outputs loaded, and simply calling - // FTUE Save appears to resync things. - //jamClient.FTUERefreshDevices(); - newFtueSave(false); - } - - function newFtueSetAsioFrameSize(evt) { - var val = parseFloat($(evt.currentTarget).val(),10); - if (isNaN(val)) { - return; - } - logger.debug("Calling FTUESetFrameSize(" + val + ")"); - jamClient.FTUESetFrameSize(val); - newFtueSave(false); - } - function newFtueSetAsioInputLatency(evt) { - var val = parseInt($(evt.currentTarget).val(),10); - if (isNaN(val)) { - return; - } - logger.debug("Calling FTUESetInputLatency(" + val + ")"); - jamClient.FTUESetInputLatency(val); - newFtueSave(false); - } - function newFtueSetAsioOutputLatency(evt) { - var val = parseInt($(evt.currentTarget).val(),10); - if (isNaN(val)) { - return; - } - logger.debug("Calling FTUESetOutputLatency(" + val + ")"); - jamClient.FTUESetOutputLatency(val); - newFtueSave(false); - } - - // Enable or Disable the frame/buffer controls in the new FTUE screen - function newFtueEnableControls(enable) { - var $frame = $('#ftue-2-asio-framesize'); - var $bin = $('#ftue-2-asio-input-latency'); - var $bout = $('#ftue-2-asio-output-latency'); - if (enable) { - $frame.removeAttr("disabled").easyDropDown('enable'); - $bin.removeAttr("disabled").easyDropDown('enable'); - $bout.removeAttr("disabled").easyDropDown('enable'); - } else { - $frame.attr("disabled", "disabled").easyDropDown('disable'); - $bin.attr("disabled", "disabled").easyDropDown('disable'); - $bout.attr("disabled", "disabled".easyDropDown('disable')); - } - } - - // Based on OS and Audio Hardware, set Frame/Buffer settings appropriately - // and show/hide the ASIO button. - function newFtueOsSpecificSettings() { - var $frame = $('#ftue-2-asio-framesize'); - var $bin = $('#ftue-2-asio-input-latency'); - var $bout = $('#ftue-2-asio-output-latency'); - var $asioBtn = $('#btn-ftue-2-asio-control-panel'); - if (jamClient.GetOSAsString() === "Win32") { - if (jamClient.FTUEHasControlPanel()) { - // Win32 + ControlPanel = ASIO - // frame=2.5, buffers=0 - $asioBtn.show(); - $frame.val('2.5'); - $bin.val('0'); - $bout.val('0'); - } else { - // Win32, no ControlPanel = WDM/Kernel Streaming - // frame=10, buffers=0 - $asioBtn.hide(); - $frame.val('10'); - // TODO FIXME - the old FTUE set the buffers to 1 for WDM/Kernel streaming - // The new FTUE spec says to use 0, as I've done here... - $bin.val('0'); - $bout.val('0'); - } - } else { // Assuming Mac. TODO: Linux check here - // frame=2.5, buffers=0 - $asioBtn.hide(); - $frame.val('2.5'); - $bin.val('0'); - $bout.val('0'); - } - - } - - // Given a latency structure, update the view. - function newFtueUpdateLatencyView(latency) { - var $report = $('.ftue-new .latency .report'); - var $instructions = $('.ftue-new .latency .instructions'); - var latencyClass = "neutral"; - var latencyValue = "N/A"; - var $saveButton = $('#btn-ftue-2-save'); - if (latency && latency.latencyknown) { - latencyValue = latency.latency; - // Round latency to two decimal places. - latencyValue = Math.round(latencyValue * 100) / 100; - if (latency.latency <= 10) { - latencyClass = "good"; - $saveButton.removeClass('disabled'); - } else if (latency.latency <= 20) { - latencyClass = "acceptable"; - $saveButton.removeClass('disabled'); - } else { - latencyClass = "bad"; - $saveButton.addClass('disabled'); - } - } else { - latencyClass = "unknown"; - $saveButton.addClass('disabled'); - } - - $('.ms-label', $report).html(latencyValue); - $('p', $report).html('milliseconds'); - - $report.removeClass('good acceptable bad'); - $report.addClass(latencyClass); - - var instructionClasses = ['neutral', 'good', 'acceptable', 'bad', 'start', 'loading']; - $.each(instructionClasses, function(idx, val) { - $('p.' + val, $instructions).hide(); - }); - if (latency === 'loading') { - $('p.loading', $instructions).show(); - } else { - $('p.' + latencyClass, $instructions).show(); - } - } - - function audioDriverChanged(evt) { - var $select = $(evt.currentTarget); - var driverId = $select.val(); - jamClient.FTUESetMusicDevice(driverId); - loadAudioDevices(); - setAsioSettingsVisibility(); - } - - function audioDeviceChanged(evt) { - var $select = $(evt.currentTarget); - setAudioDevice($select); - if (musicInAndOutSet()) { - ftueSave(false); - setVuCallbacks(); - } - } - - function setAudioDevice($select) { - var device = $select.data('device'); - var deviceId = $select.val(); - // Note: We always set, even on the "Choose" value of "", which clears - // the current setting. - var setFunction = deviceSetMap[device]; - setFunction(deviceId); - } - - /** - * Return a boolean indicating whether both the MusicInput - * and MusicOutput devices are set. - */ - function musicInAndOutSet() { - var audioInput = $('[layout-wizard-step="2"] .audio-input select').val(); - var audioOutput = $('[layout-wizard-step="2"] .audio-output select').val(); - return (audioInput && audioOutput); - } - - function setVuCallbacks() { - jamClient.FTUERegisterVUCallbacks( - "JK.ftueAudioOutputVUCallback", - "JK.ftueAudioInputVUCallback", - "JK.ftueChatInputVUCallback" - ); - jamClient.SetVURefreshRate(200); - } - - function setAsioSettingsVisibility() { - logger.debug("jamClient.FTUEHasControlPanel()=" + jamClient.FTUEHasControlPanel()); - if (jamClient.FTUEHasControlPanel()) { - logger.debug("Showing ASIO button"); - $('#btn-asio-control-panel').show(); - } - else { - logger.debug("Hiding ASIO button"); - $('#btn-asio-control-panel').hide(); - } - } - - function initialize() { - // If not on windows, hide ASIO settings - if (jamClient.GetOSAsString() != "Win32") { - logger.debug("Not on Win32 - modifying UI for Mac/Linux"); - win32 = false; - $('[layout-wizard-step="2"] p[os="win32"]').hide(); - $('[layout-wizard-step="2"] p[os="mac"]').show(); - $('#btn-asio-control-panel').hide(); - $('[layout-wizard-step="2"] .settings-controls select').removeAttr("disabled"); - loadAudioDevices(); - } - - setAsioSettingsVisibility(); - - events(); - var dialogBindings = { 'beforeShow': beforeShow, - 'afterShow': afterShow, 'afterHide': afterHide }; - app.bindDialog('ftue', dialogBindings); - app.registerWizardStepFunction("0", settingsInit); - app.registerWizardStepFunction("2", settingsInit); - app.registerWizardStepFunction("4", testLatency); - app.registerWizardStepFunction("6", testComplete); - loadAudioDrivers(); - } - - // Expose publics - this.initialize = initialize; - - // Expose degreesFromRange outside for testing - this._degreesFromRange = degreesFromRange; - - return this; + * FtueAudioSelectionScreen + * Javascript that goes with the screen where initial + * selection of the audio devices takes place. + * Corresponds to /#ftue2 + */ +(function (context, $) { + + "use strict"; + + context.JK = context.JK || {}; + context.JK.FtueWizard = function (app) { + context.JK.FtueWizard.latencyTimeout = true; + context.JK.FtueWizard.latencyMS = Number.MAX_VALUE; + var rest = context.JK.Rest(); + + var logger = context.JK.logger; + var jamClient = context.jamClient; + var win32 = true; + + var deviceSetMap = { + 'audio-input': jamClient.FTUESetMusicInput, + 'audio-output': jamClient.FTUESetMusicOutput, + 'voice-chat-input': jamClient.FTUESetChatInput }; - - - // Common VU updater taking a dbValue (-80 to 20) and a CSS selector for the VU. - context.JK.ftueVUCallback = function(dbValue, selector) { - // Convert DB into a value from 0.0 - 1.0 - var floatValue = (dbValue + 80) / 100; - context.JK.VuHelpers.updateVU(selector, floatValue); + var faderMap = { + 'ftue-2-audio-input-fader': jamClient.FTUESetInputVolume, + 'ftue-2-voice-input-fader': jamClient.FTUESetOutputVolume, + 'ftue-audio-input-fader': jamClient.FTUESetInputVolume, + 'ftue-voice-input-fader': jamClient.FTUESetChatInputVolume, + 'ftue-audio-output-fader': jamClient.FTUESetOutputVolume }; - context.JK.ftueAudioInputVUCallback = function(dbValue) { - context.JK.ftueVUCallback(dbValue, '#ftue-2-audio-input-vu-left'); - context.JK.ftueVUCallback(dbValue, '#ftue-2-audio-input-vu-right'); - context.JK.ftueVUCallback(dbValue, '#ftue-audio-input-vu-left'); - context.JK.ftueVUCallback(dbValue, '#ftue-audio-input-vu-right'); - }; - context.JK.ftueAudioOutputVUCallback = function(dbValue) { - context.JK.ftueVUCallback(dbValue, '#ftue-audio-output-vu-left'); - context.JK.ftueVUCallback(dbValue, '#ftue-audio-output-vu-right'); - }; - context.JK.ftueChatInputVUCallback = function(dbValue) { - context.JK.ftueVUCallback(dbValue, '#ftue-2-voice-input-vu-left'); - context.JK.ftueVUCallback(dbValue, '#ftue-2-voice-input-vu-right'); - context.JK.ftueVUCallback(dbValue, '#ftue-voice-input-vu-left'); - context.JK.ftueVUCallback(dbValue, '#ftue-voice-input-vu-right'); - }; + function latencyTimeoutCheck() { + if (context.JK.FtueWizard.latencyTimeout) { + jamClient.FTUERegisterLatencyCallback(''); + context.JK.app.setWizardStep("5"); + } + } - // Latency Callback - context.JK.ftueLatencyCallback = function(latencyMS) { - // We always show gauge screen if we hit this. - // Clear out the 'timeout' variable. - context.JK.FtueWizard.latencyTimeout = false; - var now = new Date(); - context.console.debug("ftueLatencyCallback: " + now); - context.JK.FtueWizard.latencyMS = latencyMS; + function afterHide(data) { + // Unsubscribe from FTUE VU callbacks. + jamClient.FTUERegisterVUCallbacks('', '', ''); + } - // Unregister callback: - context.jamClient.FTUERegisterLatencyCallback(''); - // Go to 'congrats' screen -- although latency may be too high. - context.JK.app.setWizardStep("6"); - }; + function beforeShow(data) { + var vuMeters = [ + '#ftue-2-audio-input-vu-left', + '#ftue-2-audio-input-vu-right', + '#ftue-2-voice-input-vu-left', + '#ftue-2-voice-input-vu-right', + '#ftue-audio-input-vu-left', + '#ftue-audio-input-vu-right', + '#ftue-voice-input-vu-left', + '#ftue-voice-input-vu-right', + '#ftue-audio-output-vu-left', + '#ftue-audio-output-vu-right' + ]; + $.each(vuMeters, function () { + context.JK.VuHelpers.renderVU(this, + {vuType: "horizontal", lightCount: 12, lightWidth: 15, lightHeight: 3}); + }); + + var faders = context._.keys(faderMap); + $.each(faders, function () { + var fid = this; + context.JK.FaderHelpers.renderFader('#' + fid, + {faderId: fid, faderType: "horizontal", width: 163}); + context.JK.FaderHelpers.subscribe(fid, faderChange); + }); + } + + function afterShow(data) { + } + + function faderChange(faderId, newValue, dragging) { + var setFunction = faderMap[faderId]; + // TODO - using hardcoded range of -80 to 20 for output levels. + var mixerLevel = newValue - 80; // Convert our [0-100] to [-80 - +20] range + setFunction(mixerLevel); + } + + function settingsInit() { + jamClient.FTUEInit(); + setLevels(0); + // Always reset the driver select box to "Choose..." which forces everything + // to sync properly when the user reselects their driver of choice. + // VRFS-375 and VRFS-561 + $('[layout-wizard="ftue"] [layout-wizard-step="0"] .settings-2-device select').val(""); + $('[layout-wizard="ftue"] [layout-wizard-step="0"] .settings-2-voice select').val(""); + $('[layout-wizard="ftue"] [layout-wizard-step="2"] .asio-settings .settings-driver select').val(""); + } + + function setLevels(db) { + if (db < -80 || db > 20) { + throw ("BUG! ftue.js:setLevels db arg must be between -80 and 20"); + } + var trackIds = jamClient.SessionGetIDs(); + var controlStates = jamClient.SessionGetControlState(trackIds); + $.each(controlStates, function (index, value) { + context.JK.Mixer.fillTrackVolume(value, false); + // Default input/output to 0 DB + context.trackVolumeObject.volL = db; + context.trackVolumeObject.volR = db; + jamClient.SessionSetControlState(trackIds[index]); + }); + $.each(context._.keys(faderMap), function (index, faderId) { + // faderChange takes a value from 0-100 + var $fader = $('[fader-id="' + faderId + '"]'); + + var faderPct = db + 80; + context.JK.FaderHelpers.setHandlePosition($fader, faderPct); + faderChange(faderId, faderPct); + }); + } + + function testComplete() { + logger.debug("Test complete"); + var latencyMS = context.JK.FtueWizard.latencyMS; + var ftueSucceeded = latencyMS <= 20; + if (ftueSucceeded) { + logger.debug(latencyMS + " is <= 20. Setting FTUE status to true"); + ftueSave(true); // Save the profile + context.jamClient.FTUESetStatus(true); // No FTUE wizard next time + rest.userCertifiedGear({success: true}); + + // notify anyone curious about how it went + $('div[layout-id=ftue]').trigger('ftue_success'); + } + else { + rest.userCertifiedGear({success: false, reason: "latency=" + latencyMS}); + } + + updateGauge(); + } + + function updateGauge() { + var latencyMS = context.JK.FtueWizard.latencyMS; + // Round to 2 decimal places + latencyMS = (Math.round(latencyMS * 100)) / 100; + logger.debug("Latency Value: " + latencyMS); + if (latencyMS > 20) { + $('#ftue-latency-congrats').hide(); + $('#ftue-latency-fail').show(); + } else { + $('#ftue-latency-ms').html(latencyMS); + $('#ftue-latency-congrats').show(); + $('#ftue-latency-fail').hide(); + if (latencyMS <= 10) { + $('[layout-wizard-step="6"] .btnHelp').hide(); + $('[layout-wizard-step="6"] .btnRepeat').hide(); + } + } + setNeedleValue(latencyMS); + } + + // Function to calculate an absolute value and an absolute value range into + // a number of degrees on a circualar "speedometer" gauge. The 0 degrees value + // points straight up to the middle of the real-world value range. + // Arguments: + // value: The real-world value (e.g. 20 milliseconds) + // minValue: The real-world minimum value (e.g. 0 milliseconds) + // maxValue: The real-world maximum value (e.g. 40 milliseconds) + // degreesUsed: The number of degrees used to represent the full real-world + // range. 360 would be the entire circle. 270 would be 3/4ths + // of the circle. The unused gap will be at the bottom of the + // gauge. + // (e.g. 300) + function degreesFromRange(value, minValue, maxValue, degreesUsed) { + if (value > maxValue) { + value = maxValue; + } + if (value < minValue) { + value = minValue; + } + var range = maxValue - minValue; + var floatVal = value / range; + var degrees = Math.round(floatVal * degreesUsed); + degrees -= Math.round(degreesUsed / 2); + if (degrees < 0) { + degrees += 360; + } + return degrees; + } + + // Given a number of MS, and assuming the gauge has a range from + // 0 to 40 ms. Update the gauge to the proper value. + function setNeedleValue(ms) { + logger.debug("setNeedleValue: " + ms); + var deg = degreesFromRange(ms, 0, 40, 300); + + // Supporting Firefix, Chrome, Safari, Opera and IE9+. + // Should we need to support < IE9, this will need more work + // to compute the matrix transformations in those browsers - + // and I don't believe they support transparent PNG graphic + // rotation, so we'll have to change the needle itself. + var css = { + //"behavior":"url(-ms-transform.htc)", + /* Firefox */ + "-moz-transform": "rotate(" + deg + "deg)", + /* Safari and Chrome */ + "-webkit-transform": "rotate(" + deg + "deg)", + /* Opera */ + "-o-transform": "rotate(" + deg + "deg)", + /* IE9 */ + "-ms-transform": "rotate(" + deg + "deg)" + /* IE6,IE7 */ + //"filter": "progid:DXImageTransform.Microsoft.Matrix(sizingMethod='auto expand', M11=0.7071067811865476, M12=-0.7071067811865475, M21=0.7071067811865475, M22=0.7071067811865476)", + /* IE8 */ + //"-ms-filter": '"progid:DXImageTransform.Microsoft.Matrix(SizingMethod=\'auto expand\', M11=0.7071067811865476, M12=-0.7071067811865475, M21=0.7071067811865475, M22=0.7071067811865476)"' + }; + + $('#ftue-latency-numerical').html(ms); + $('#ftue-latency-needle').css(css); + + } + + function testLatency() { + // we'll just register for call back right here and unregister in the callback. + context.JK.FtueWizard.latencyTimeout = true; + var cbFunc = 'JK.ftueLatencyCallback'; + logger.debug("Registering latency callback: " + cbFunc); + jamClient.FTUERegisterLatencyCallback('JK.ftueLatencyCallback'); + var now = new Date(); + logger.debug("Starting Latency Test..." + now); + context.setTimeout(latencyTimeoutCheck, 300 * 1000); // Timeout to 5 minutes + jamClient.FTUEStartLatency(); + } + + function openASIOControlPanel(evt) { + if (win32) { + logger.debug("Calling FTUEOpenControlPanel()"); + jamClient.FTUEOpenControlPanel(); + } + } + + function asioResync(evt) { + jamClient.FTUERefreshDevices(); + ftueSave(false); + } + + function ftueSave(persist) { + // Explicitly set inputs and outputs to dropdown values + // before save as the client seems to want this on changes to + // things like frame size, etc.. + var $audioSelects = $('[layout-wizard-step="2"] .settings-controls select'); + $.each($audioSelects, function (index, value) { + var $select = $(value); + setAudioDevice($select); + }); + if (musicInAndOutSet()) { + + // If there is no voice-chat-input selected, let the back-end know + // that we're using music for voice-chat. + if ($('[layout-wizard-step="2"] select[data-device="voice-chat-input"]').val()) { + // Voice input selected + jamClient.TrackSetChatEnable(true); + } else { + // No voice input selected. + jamClient.TrackSetChatEnable(false); + } + + setDefaultInstrumentFromProfile(); + + logger.debug("Calling FTUESave(" + persist + ")"); + var response = jamClient.FTUESave(persist); + setLevels(0); + if (response) { + logger.warn(response); + // TODO - we may need to do something about errors on save. + // per VRFS-368, I'm hiding the alert, and logging a warning. + // context.alert(response); + } + } else { + logger.debug("Aborting FTUESave as we need input + output selected."); + } + } + + function setAsioFrameSize(evt) { + var val = parseFloat($(evt.currentTarget).val(), 10); + if (isNaN(val)) { + return; + } + logger.debug("Calling FTUESetFrameSize(" + val + ")"); + jamClient.FTUESetFrameSize(val); + ftueSave(false); + } + + function setAsioInputLatency(evt) { + var val = parseInt($(evt.currentTarget).val(), 10); + if (isNaN(val)) { + return; + } + logger.debug("Calling FTUESetInputLatency(" + val + ")"); + jamClient.FTUESetInputLatency(val); + ftueSave(false); + } + + function setAsioOutputLatency(evt) { + var val = parseInt($(evt.currentTarget).val(), 10); + if (isNaN(val)) { + return; + } + logger.debug("Calling FTUESetOutputLatency(" + val + ")"); + jamClient.FTUESetOutputLatency(val); + ftueSave(false); + } + + function videoLinkClicked(evt) { + var myOS = jamClient.GetOSAsString(); + var link; + if (myOS === 'MacOSX') { + link = $(evt.currentTarget).attr('external-link-mac'); + } else if (myOS === 'Win32') { + link = $(evt.currentTarget).attr('external-link-win'); + } + if (link) { + context.jamClient.OpenSystemBrowser(link); + } + } + + function events() { + $('.ftue-video-link').hover( + function (evt) { // handlerIn + $(this).addClass('hover'); + }, + function (evt) { // handlerOut + $(this).removeClass('hover'); + } + ); + $('.ftue-video-link').on('click', videoLinkClicked); + $('[layout-wizard-step="2"] .settings-driver select').on('change', audioDriverChanged); + $('[layout-wizard-step="2"] .settings-controls select').on('change', audioDeviceChanged); + $('#btn-asio-control-panel').on('click', openASIOControlPanel); + $('#btn-asio-resync').on('click', asioResync); + $('#asio-framesize').on('change', setAsioFrameSize); + $('#asio-input-latency').on('change', setAsioInputLatency); + $('#asio-output-latency').on('change', setAsioOutputLatency); + // New FTUE events + $('.ftue-new .settings-2-device select').on('change', newFtueAudioDeviceChanged); + $('.ftue-new .settings-2-voice select').on('change', newFtueAudioDeviceChanged); + $('#btn-ftue-2-asio-resync').on('click', newFtueAsioResync); + $('#btn-ftue-2-asio-control-panel').on('click', openASIOControlPanel); + $('#ftue-2-asio-framesize').on('change', newFtueSetAsioFrameSize); + $('#ftue-2-asio-input-latency').on('change', newFtueSetAsioInputLatency); + $('#ftue-2-asio-output-latency').on('change', newFtueSetAsioOutputLatency); + $('#btn-ftue-2-save').on('click', newFtueSaveSettingsHandler); + } + + /** + * This function loads the available audio devices from jamClient, and + * builds up the select dropdowns in the audio-settings step of the FTUE wizard. + */ + function loadAudioDevices() { + var funcs = [ + jamClient.FTUEGetMusicInputs, + jamClient.FTUEGetChatInputs, + jamClient.FTUEGetMusicOutputs + ]; + var selectors = [ + '[layout-wizard-step="2"] .audio-input select', + '[layout-wizard-step="2"] .voice-chat-input select', + '[layout-wizard-step="2"] .audio-output select' + ]; + var optionsHtml = ''; + var deviceOptionFunc = function (deviceKey, index, list) { + optionsHtml += ''; + }; + for (var i = 0; i < funcs.length; i++) { + optionsHtml = ''; + + var devices = funcs[i](); // returns hash of device id: device name + var $select = $(selectors[i]); + $select.empty(); + var sortedDeviceKeys = context._.keys(devices).sort(); + context._.each(sortedDeviceKeys, deviceOptionFunc); + $select.html(optionsHtml); + context.JK.dropdown($select); + $select.removeAttr("disabled"); + $('[layout-wizard-step="2"] .settings-asio select').removeAttr("disabled").easyDropDown('enable') + // Set selects to lowest possible values to start: + $('#asio-framesize').val('2.5').change(); + $('#asio-input-latency').val('0').change(); + $('#asio-output-latency').val('0').change(); + // Special-case for a non-ASIO device, set to 1 + if (jamClient.GetOSAsString() === "Win32") { // Limit this check to Windows only. + if (!(jamClient.FTUEHasControlPanel())) { + $('#asio-input-latency').val('1').change(); + $('#asio-output-latency').val('1').change(); + } + } + } + } + + /** + * Load available drivers and populate the driver select box. + */ + function loadAudioDrivers() { + var drivers = jamClient.FTUEGetDevices(); + + var driverOptionFunc = function (driverKey, index, list) { + optionsHtml += ''; + }; + + var optionsHtml = ''; + var selectors = [ + '[layout-wizard-step="0"] .settings-2-device select', + '[layout-wizard-step="0"] .settings-2-voice select', + '[layout-wizard-step="2"] .settings-driver select' + ]; + var sortedDeviceKeys = context._.keys(drivers).sort(); + context._.each(sortedDeviceKeys, driverOptionFunc); + $.each(selectors, function (index, selector) { + var $select = $(selector); + $select.empty(); + $select.html(optionsHtml); + context.JK.dropdown($select); + }); + } + + /** Once a configuration is decided upon, we set the user's default instrument based on data from their profile */ + function setDefaultInstrumentFromProfile() { + var defaultInstrumentId; + if (context.JK.userMe.instruments && context.JK.userMe.instruments.length > 0) { + defaultInstrumentId = context.JK.instrument_id_to_instrument[context.JK.userMe.instruments[0].instrument_id].client_id; + } + else { + defaultInstrumentId = context.JK.server_to_client_instrument_map['Other'].client_id; + } + + jamClient.TrackSetInstrument(1, defaultInstrumentId); + } + + /** + * Handler for the new FTUE save button. + */ + function newFtueSaveSettingsHandler(evt) { + evt.preventDefault(); + var $saveButton = $('#btn-ftue-2-save'); + if ($saveButton.hasClass('disabled')) { + return; + } + var selectedAudioDevice = $('.ftue-new .settings-2-device select').val(); + if (!(selectedAudioDevice)) { + app.notify({ + title: "Please select an audio device", + text: "Please choose a usable audio device, or select cancel." + }); + return false; + } + + setDefaultInstrumentFromProfile(); + + jamClient.FTUESave(true); + jamClient.FTUESetStatus(true); // No FTUE wizard next time + rest.userCertifiedGear({success: true}); + app.layout.closeDialog('ftue'); + if (app.afterFtue) { + // If there's a function to invoke, invoke it. + app.afterFtue(); + app.afterFtue = null; + } + return false; + } + + // Handler for when the audio device is changed in the new FTUE screen + // This works differently from the old FTUE. There is no input/output selection, + // as soon as the user chooses a driver, we auto-assign inputs and outputs. + // We also call jamClient.FTUEGetExpectedLatency, which returns a structure like: + // { latency: 11.1875, latencyknown: true, latencyvar: 1} + function newFtueAudioDeviceChanged(evt) { + var $select = $(evt.currentTarget); + + var $audioSelect = $('.ftue-new .settings-2-device select'); + var $voiceSelect = $('.ftue-new .settings-2-voice select'); + var audioDriverId = $audioSelect.val(); + var voiceDriverId = $voiceSelect.val(); + jamClient.FTUESetMusicDevice(audioDriverId); + jamClient.FTUESetChatInput(voiceDriverId); + if (voiceDriverId) { // Let the back end know whether a voice device is selected + jamClient.TrackSetChatEnable(true); + } else { + jamClient.TrackSetChatEnable(false); + } + if (!audioDriverId) { + // reset back to 'Choose...' + newFtueEnableControls(false); + return; + } + var musicInputs = jamClient.FTUEGetMusicInputs(); + var musicOutputs = jamClient.FTUEGetMusicOutputs(); + + // set the music input to the first available input, + // and output to the first available output + var kin = null, kout = null, k = null; + // TODO FIXME - this jamClient call returns a dictionary. + // It's difficult to know what to auto-choose. + // For example, with my built-in audio, the keys I get back are + // digital in, line in, mic in and stereo mix. Which should we pick for them? + for (k in musicInputs) { + kin = k; + break; + } + for (k in musicOutputs) { + kout = k; + break; + } + var result; + if (kin && kout) { + jamClient.FTUESetMusicInput(kin); + jamClient.FTUESetMusicOutput(kout); + } else { + // TODO FIXME - how to handle a driver selection where we are unable to + // autoset both inputs and outputs? (I'd think this could happen if either + // the input or output side returned no values) + return; + } + + newFtueEnableControls(true); + newFtueOsSpecificSettings(); + setLevels(0); + newFtueUpdateLatencyView('loading'); + jamClient.FTUESave(false); + setVuCallbacks(); + + var latency = jamClient.FTUEGetExpectedLatency(); + newFtueUpdateLatencyView(latency); + } + + function newFtueSave(persist) { + newFtueUpdateLatencyView('loading'); + jamClient.FTUESave(persist); + var latency = jamClient.FTUEGetExpectedLatency(); + newFtueUpdateLatencyView(latency); + } + + function newFtueAsioResync(evt) { + // In theory, we should be calling the following, but it causes + // us to not have both inputs/outputs loaded, and simply calling + // FTUE Save appears to resync things. + //jamClient.FTUERefreshDevices(); + newFtueSave(false); + } + + function newFtueSetAsioFrameSize(evt) { + var val = parseFloat($(evt.currentTarget).val(), 10); + if (isNaN(val)) { + return; + } + logger.debug("Calling FTUESetFrameSize(" + val + ")"); + jamClient.FTUESetFrameSize(val); + newFtueSave(false); + } + + function newFtueSetAsioInputLatency(evt) { + var val = parseInt($(evt.currentTarget).val(), 10); + if (isNaN(val)) { + return; + } + logger.debug("Calling FTUESetInputLatency(" + val + ")"); + jamClient.FTUESetInputLatency(val); + newFtueSave(false); + } + + function newFtueSetAsioOutputLatency(evt) { + var val = parseInt($(evt.currentTarget).val(), 10); + if (isNaN(val)) { + return; + } + logger.debug("Calling FTUESetOutputLatency(" + val + ")"); + jamClient.FTUESetOutputLatency(val); + newFtueSave(false); + } + + // Enable or Disable the frame/buffer controls in the new FTUE screen + function newFtueEnableControls(enable) { + var $frame = $('#ftue-2-asio-framesize'); + var $bin = $('#ftue-2-asio-input-latency'); + var $bout = $('#ftue-2-asio-output-latency'); + if (enable) { + $frame.removeAttr("disabled").easyDropDown('enable'); + $bin.removeAttr("disabled").easyDropDown('enable'); + $bout.removeAttr("disabled").easyDropDown('enable'); + } else { + $frame.attr("disabled", "disabled").easyDropDown('disable'); + $bin.attr("disabled", "disabled").easyDropDown('disable'); + $bout.attr("disabled", "disabled".easyDropDown('disable')); + } + } + + // Based on OS and Audio Hardware, set Frame/Buffer settings appropriately + // and show/hide the ASIO button. + function newFtueOsSpecificSettings() { + var $frame = $('#ftue-2-asio-framesize'); + var $bin = $('#ftue-2-asio-input-latency'); + var $bout = $('#ftue-2-asio-output-latency'); + var $asioBtn = $('#btn-ftue-2-asio-control-panel'); + if (jamClient.GetOSAsString() === "Win32") { + if (jamClient.FTUEHasControlPanel()) { + // Win32 + ControlPanel = ASIO + // frame=2.5, buffers=0 + $asioBtn.show(); + $frame.val('2.5'); + $bin.val('0'); + $bout.val('0'); + } else { + // Win32, no ControlPanel = WDM/Kernel Streaming + // frame=10, buffers=0 + $asioBtn.hide(); + $frame.val('10'); + // TODO FIXME - the old FTUE set the buffers to 1 for WDM/Kernel streaming + // The new FTUE spec says to use 0, as I've done here... + $bin.val('0'); + $bout.val('0'); + } + } else { // Assuming Mac. TODO: Linux check here + // frame=2.5, buffers=0 + $asioBtn.hide(); + $frame.val('2.5'); + $bin.val('0'); + $bout.val('0'); + } + + } + + // Given a latency structure, update the view. + function newFtueUpdateLatencyView(latency) { + var $report = $('.ftue-new .latency .report'); + var $instructions = $('.ftue-new .latency .instructions'); + var latencyClass = "neutral"; + var latencyValue = "N/A"; + var $saveButton = $('#btn-ftue-2-save'); + if (latency && latency.latencyknown) { + latencyValue = latency.latency; + // Round latency to two decimal places. + latencyValue = Math.round(latencyValue * 100) / 100; + if (latency.latency <= 10) { + latencyClass = "good"; + $saveButton.removeClass('disabled'); + } else if (latency.latency <= 20) { + latencyClass = "acceptable"; + $saveButton.removeClass('disabled'); + } else { + latencyClass = "bad"; + $saveButton.addClass('disabled'); + } + } else { + latencyClass = "unknown"; + $saveButton.addClass('disabled'); + } + + $('.ms-label', $report).html(latencyValue); + $('p', $report).html('milliseconds'); + + $report.removeClass('good acceptable bad'); + $report.addClass(latencyClass); + + var instructionClasses = ['neutral', 'good', 'acceptable', 'bad', 'start', 'loading']; + $.each(instructionClasses, function (idx, val) { + $('p.' + val, $instructions).hide(); + }); + if (latency === 'loading') { + $('p.loading', $instructions).show(); + } else { + $('p.' + latencyClass, $instructions).show(); + } + } + + function audioDriverChanged(evt) { + var $select = $(evt.currentTarget); + var driverId = $select.val(); + jamClient.FTUESetMusicDevice(driverId); + loadAudioDevices(); + setAsioSettingsVisibility(); + } + + function audioDeviceChanged(evt) { + var $select = $(evt.currentTarget); + setAudioDevice($select); + if (musicInAndOutSet()) { + ftueSave(false); + setVuCallbacks(); + } + } + + function setAudioDevice($select) { + var device = $select.data('device'); + var deviceId = $select.val(); + // Note: We always set, even on the "Choose" value of "", which clears + // the current setting. + var setFunction = deviceSetMap[device]; + setFunction(deviceId); + } + + /** + * Return a boolean indicating whether both the MusicInput + * and MusicOutput devices are set. + */ + function musicInAndOutSet() { + var audioInput = $('[layout-wizard-step="2"] .audio-input select').val(); + var audioOutput = $('[layout-wizard-step="2"] .audio-output select').val(); + return (audioInput && audioOutput); + } + + function setVuCallbacks() { + jamClient.FTUERegisterVUCallbacks( + "JK.ftueAudioOutputVUCallback", + "JK.ftueAudioInputVUCallback", + "JK.ftueChatInputVUCallback" + ); + jamClient.SetVURefreshRate(200); + } + + function setAsioSettingsVisibility() { + logger.debug("jamClient.FTUEHasControlPanel()=" + jamClient.FTUEHasControlPanel()); + if (jamClient.FTUEHasControlPanel()) { + logger.debug("Showing ASIO button"); + $('#btn-asio-control-panel').show(); + } + else { + logger.debug("Hiding ASIO button"); + $('#btn-asio-control-panel').hide(); + } + } + + function initialize() { + // If not on windows, hide ASIO settings + if (jamClient.GetOSAsString() != "Win32") { + logger.debug("Not on Win32 - modifying UI for Mac/Linux"); + win32 = false; + $('[layout-wizard-step="2"] p[os="win32"]').hide(); + $('[layout-wizard-step="2"] p[os="mac"]').show(); + $('#btn-asio-control-panel').hide(); + $('[layout-wizard-step="2"] .settings-controls select').removeAttr("disabled"); + loadAudioDevices(); + } + + setAsioSettingsVisibility(); + + events(); + var dialogBindings = { 'beforeShow': beforeShow, + 'afterShow': afterShow, 'afterHide': afterHide }; + app.bindDialog('ftue', dialogBindings); + app.registerWizardStepFunction("0", settingsInit); + app.registerWizardStepFunction("2", settingsInit); + app.registerWizardStepFunction("4", testLatency); + app.registerWizardStepFunction("6", testComplete); + loadAudioDrivers(); + } + + // Expose publics + this.initialize = initialize; + + // Expose degreesFromRange outside for testing + this._degreesFromRange = degreesFromRange; + + return this; + }; - })(window,jQuery); \ No newline at end of file + // Common VU updater taking a dbValue (-80 to 20) and a CSS selector for the VU. + context.JK.ftueVUCallback = function (dbValue, selector) { + // Convert DB into a value from 0.0 - 1.0 + var floatValue = (dbValue + 80) / 100; + context.JK.VuHelpers.updateVU(selector, floatValue); + }; + + context.JK.ftueAudioInputVUCallback = function (dbValue) { + context.JK.ftueVUCallback(dbValue, '#ftue-2-audio-input-vu-left'); + context.JK.ftueVUCallback(dbValue, '#ftue-2-audio-input-vu-right'); + context.JK.ftueVUCallback(dbValue, '#ftue-audio-input-vu-left'); + context.JK.ftueVUCallback(dbValue, '#ftue-audio-input-vu-right'); + }; + context.JK.ftueAudioOutputVUCallback = function (dbValue) { + context.JK.ftueVUCallback(dbValue, '#ftue-audio-output-vu-left'); + context.JK.ftueVUCallback(dbValue, '#ftue-audio-output-vu-right'); + }; + context.JK.ftueChatInputVUCallback = function (dbValue) { + context.JK.ftueVUCallback(dbValue, '#ftue-2-voice-input-vu-left'); + context.JK.ftueVUCallback(dbValue, '#ftue-2-voice-input-vu-right'); + context.JK.ftueVUCallback(dbValue, '#ftue-voice-input-vu-left'); + context.JK.ftueVUCallback(dbValue, '#ftue-voice-input-vu-right'); + }; + + // Latency Callback + context.JK.ftueLatencyCallback = function (latencyMS) { + // We always show gauge screen if we hit this. + // Clear out the 'timeout' variable. + context.JK.FtueWizard.latencyTimeout = false; + var now = new Date(); + context.console.debug("ftueLatencyCallback: " + now); + context.JK.FtueWizard.latencyMS = latencyMS; + + // Unregister callback: + context.jamClient.FTUERegisterLatencyCallback(''); + // Go to 'congrats' screen -- although latency may be too high. + context.JK.app.setWizardStep("6"); + }; + + +})(window, jQuery); \ No newline at end of file diff --git a/web/app/assets/javascripts/session.js b/web/app/assets/javascripts/session.js index 90e669640..7540d7f75 100644 --- a/web/app/assets/javascripts/session.js +++ b/web/app/assets/javascripts/session.js @@ -512,6 +512,7 @@ context.JK.FaderHelpers.subscribe(faderId, faderChanged); // Visually update fader to underlying mixer start value. // Always do this, even if gainPercent is zero. + context.JK.FaderHelpers.setFaderValue(faderId, gainPercent); }