* VRFS-1575 - step 2 of FTUE almost done
This commit is contained in:
parent
9b18849544
commit
d39f91e186
|
|
@ -39,3 +39,4 @@
|
|||
//= require utils
|
||||
//= require custom_controls
|
||||
//= require_directory .
|
||||
//= require_directory ./gear
|
||||
|
|
|
|||
|
|
@ -38,7 +38,15 @@
|
|||
function RestartApplication() {
|
||||
|
||||
}
|
||||
function FTUECancel() {
|
||||
|
||||
}
|
||||
function FTUEGetMusicProfileName() {
|
||||
return "default"
|
||||
}
|
||||
function FTUESetMusicProfileName() {
|
||||
|
||||
}
|
||||
function FTUEGetInputLatency() {
|
||||
dbg("FTUEGetInputLatency");
|
||||
return 2;
|
||||
|
|
@ -85,6 +93,9 @@
|
|||
function FTUEGetStatus() { return ftueStatus; }
|
||||
function FTUESetStatus(b) { ftueStatus = b; }
|
||||
function FTUESetMusicDevice(id) { dbg("FTUESetMusicDevice"); }
|
||||
function FTUEGetAudioDevices() {
|
||||
return {"devices":[{"display_name":"Built-in","guid":"Built-in","input_count":1,"name":"Built-in","output_count":1,"port_audio_name":"Built-in"},{"display_name":"JamKazam Virtual Monitor","guid":"JamKazam Virtual Monitor","input_count":0,"name":"JamKazam Virtual Monitor","output_count":1,"port_audio_name":"JamKazam Virtual Monitor"}]}
|
||||
}
|
||||
function FTUEGetDevices() {
|
||||
dbg('FTUEGetMusicDevices');
|
||||
return {
|
||||
|
|
@ -113,6 +124,11 @@
|
|||
"Multichannel (FW AP Multi) - Channel 1/Multichannel (FW AP Multi) - Channel 2"
|
||||
};
|
||||
}
|
||||
function FTUESetInputMusicDevice() { }
|
||||
function FTUESetOutputMusicDevice() { }
|
||||
function FTUEGetInputMusicDevice() { return null; }
|
||||
function FTUEGetOutputMusicDevice() { return null; }
|
||||
|
||||
function FTUESetMusicInput() { dbg('FTUESetMusicInput'); }
|
||||
function FTUESetChatInput() { dbg('FTUESetChatInput'); }
|
||||
function FTUESetMusicOutput() { dbg('FTUESetMusicOutput'); }
|
||||
|
|
@ -656,10 +672,17 @@
|
|||
this.connected = true;
|
||||
|
||||
// FTUE (round 3)
|
||||
this.FTUESetInputMusicDevice = FTUESetInputMusicDevice;
|
||||
this.FTUESetOutputMusicDevice = FTUESetOutputMusicDevice;
|
||||
this.FTUEGetInputMusicDevice = FTUEGetInputMusicDevice;
|
||||
this.FTUEGetOutputMusicDevice = FTUEGetOutputMusicDevice;
|
||||
this.FTUEGetChatInputVolume = FTUEGetChatInputVolume;
|
||||
this.FTUEGetChatInputs = FTUEGetChatInputs;
|
||||
this.FTUEGetDevices = FTUEGetDevices;
|
||||
this.FTUEGetFrameSize = FTUEGetFrameSize;
|
||||
this.FTUECancel = FTUECancel;
|
||||
this.FTUEGetMusicProfileName = FTUEGetMusicProfileName;
|
||||
this.FTUESetMusicProfileName = FTUESetMusicProfileName;
|
||||
this.FTUEGetInputLatency = FTUEGetInputLatency;
|
||||
this.FTUEGetInputVolume = FTUEGetInputVolume;
|
||||
this.FTUEGetMusicInputs = FTUEGetMusicInputs;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,237 @@
|
|||
(function (context, $) {
|
||||
|
||||
"use strict";
|
||||
|
||||
|
||||
context.JK = context.JK || {};
|
||||
context.JK.GearWizard = function (app) {
|
||||
|
||||
var $dialog = null;
|
||||
var $wizardSteps = null;
|
||||
var $currentWizardStep = null;
|
||||
var step = 0;
|
||||
var $templateSteps = null;
|
||||
var $templateButtons = null;
|
||||
var $templateAudioPort = null;
|
||||
var $ftueButtons = null;
|
||||
var $btnBack = null;
|
||||
var $btnNext = null;
|
||||
var $btnClose = null;
|
||||
var $btnCancel = null;
|
||||
|
||||
var self = this;
|
||||
|
||||
var TOTAL_STEPS = 7;
|
||||
var STEP_INTRO = 0;
|
||||
var STEP_SELECT_DEVICE = 1;
|
||||
var STEP_SELECT_TRACKS = 2;
|
||||
var STEP_SELECT_CHAT = 3;
|
||||
var STEP_DIRECT_MONITOR = 4;
|
||||
var STEP_ROUTER_NETWORK = 5;
|
||||
var STEP_SUCCESS = 6;
|
||||
|
||||
var stepUnderstandGear = new context.JK.StepUnderstandGear(app, this);
|
||||
var stepSelectGear = new context.JK.StepSelectGear(app, this);
|
||||
var stepConfigureTracks = new context.JK.StepConfigureTracks(app, this);
|
||||
var stepConfigureVoiceChat = new context.JK.StepConfigureVoiceChat(app, this);
|
||||
var stepDirectMonitoring = new context.JK.StepDirectMonitoring(app, this);
|
||||
var stepNetworkTest = new context.JK.StepNetworkTest(app, this);
|
||||
var stepSuccess = new context.JK.StepSuccess(app, this);
|
||||
|
||||
var STEPS = {
|
||||
0: stepUnderstandGear,
|
||||
1: stepSelectGear,
|
||||
2: stepConfigureTracks,
|
||||
3: stepConfigureVoiceChat,
|
||||
4: stepDirectMonitoring,
|
||||
5: stepNetworkTest,
|
||||
6: stepSuccess
|
||||
}
|
||||
|
||||
function beforeShowStep($step) {
|
||||
var stepInfo = STEPS[step];
|
||||
|
||||
if (!stepInfo) {
|
||||
throw "unknown step: " + step;
|
||||
}
|
||||
|
||||
stepInfo.beforeShow.call(stepInfo);
|
||||
}
|
||||
|
||||
function moveToStep() {
|
||||
var $nextWizardStep = $wizardSteps.filter($('[layout-wizard-step=' + step + ']'));
|
||||
|
||||
$wizardSteps.hide();
|
||||
|
||||
$currentWizardStep = $nextWizardStep;
|
||||
|
||||
var $ftueSteps = $(context._.template($templateSteps.html(), {}, { variable: 'data' }));
|
||||
var $activeStep = $ftueSteps.find('.ftue-stepnumber[data-step-number="' + step + '"]');
|
||||
$activeStep.addClass('.active');
|
||||
$activeStep.next().show(); // show the .ftue-step-title
|
||||
$currentWizardStep.find('.ftuesteps').replaceWith($ftueSteps);
|
||||
|
||||
// update buttons
|
||||
var $ftueButtonsContent = $(context._.template($templateButtons.html(), {}, {variable: 'data'}));
|
||||
|
||||
|
||||
$btnBack = $ftueButtonsContent.find('.btn-back');
|
||||
$btnNext = $ftueButtonsContent.find('.btn-next');
|
||||
$btnClose = $ftueButtonsContent.find('.btn-close');
|
||||
$btnCancel = $ftueButtonsContent.find('.btn-cancel');
|
||||
|
||||
// hide back button if 1st step or last step
|
||||
if (step == 0 && step == TOTAL_STEPS - 1) {
|
||||
$btnBack.hide();
|
||||
}
|
||||
// hide next button if not on last step
|
||||
if (step == TOTAL_STEPS - 1) {
|
||||
$btnNext.hide();
|
||||
}
|
||||
// hide close if on last step
|
||||
if (step != TOTAL_STEPS - 1) {
|
||||
$btnClose.hide();
|
||||
}
|
||||
// hide cancel if not on last step
|
||||
if (step == TOTAL_STEPS - 1) {
|
||||
$btnCancel.hide();
|
||||
}
|
||||
|
||||
$btnNext.on('click', next);
|
||||
$btnBack.on('click', back);
|
||||
$btnClose.on('click', closeDialog);
|
||||
$btnCancel.on('click', closeDialog);
|
||||
|
||||
$ftueButtons.empty();
|
||||
$ftueButtons.append($ftueButtonsContent);
|
||||
|
||||
beforeShowStep($currentWizardStep);
|
||||
$currentWizardStep.show();
|
||||
|
||||
}
|
||||
|
||||
function reset() {
|
||||
$currentWizardStep = null;
|
||||
}
|
||||
|
||||
// checks if we already have a profile called 'FTUE...'; if not, create one. if so, re-use it.
|
||||
function findOrCreateFTUEProfile() {
|
||||
var profileName = context.jamClient.FTUEGetMusicProfileName();
|
||||
|
||||
logger.debug("current profile name: " + profileName);
|
||||
|
||||
if(profileName && profileName.indexOf('FTUE') == 0) {
|
||||
|
||||
}
|
||||
else {
|
||||
var newProfileName = 'FTUEAttempt-' + new Date().getTime().toString();
|
||||
logger.debug("setting FTUE-prefixed profile name to: " + newProfileName);
|
||||
context.jamClient.FTUESetMusicProfileName(newProfileName);
|
||||
}
|
||||
|
||||
var profileName = context.jamClient.FTUEGetMusicProfileName();
|
||||
|
||||
logger.debug("name on exit: " + profileName);
|
||||
|
||||
}
|
||||
|
||||
function beforeShow(args) {
|
||||
context.jamClient.FTUECancel();
|
||||
context.jamClient.FTUESetStatus(false);
|
||||
findOrCreateFTUEProfile();
|
||||
|
||||
step = args.d1;
|
||||
if (!step) step = 0;
|
||||
step = parseInt(step);
|
||||
moveToStep();
|
||||
}
|
||||
|
||||
function afterShow() {
|
||||
|
||||
}
|
||||
|
||||
function afterHide() {
|
||||
context.jamClient.FTUESetStatus(true);
|
||||
context.jamClient.FTUECancel();
|
||||
}
|
||||
|
||||
function back() {
|
||||
if ($(this).is('.button-grey')) return false;
|
||||
step = step - 1;
|
||||
moveToStep();
|
||||
return false;
|
||||
}
|
||||
|
||||
function next() {
|
||||
if ($(this).is('.button-grey')) return false;
|
||||
|
||||
step = step + 1;
|
||||
|
||||
moveToStep();
|
||||
return false;
|
||||
}
|
||||
|
||||
function closeDialog() {
|
||||
app.layout.closeDialog('gear-wizard');
|
||||
return false;
|
||||
}
|
||||
|
||||
function events() {
|
||||
|
||||
}
|
||||
|
||||
function route() {
|
||||
|
||||
}
|
||||
|
||||
function setNextState(enabled) {
|
||||
|
||||
if(!$btnNext) return;
|
||||
|
||||
$btnNext.removeClass('button-orange button-grey');
|
||||
|
||||
if (enabled) {
|
||||
$btnNext.addClass('button-orange');
|
||||
}
|
||||
else {
|
||||
$btnNext.addClass('button-grey');
|
||||
}
|
||||
}
|
||||
|
||||
function initialize() {
|
||||
|
||||
// on initial page load, we are not in the FTUE. so cancel the FTUE and call FTUESetStatus(true) if needed
|
||||
if(context.jamClient.FTUEGetStatus() == false) {
|
||||
context.jamClient.FTUESetStatus(true);
|
||||
}
|
||||
context.jamClient.FTUECancel();
|
||||
|
||||
var dialogBindings = { beforeShow: beforeShow, afterShow: afterShow, afterHide: afterHide };
|
||||
|
||||
app.bindDialog('gear-wizard', dialogBindings);
|
||||
|
||||
$dialog = $('#gear-wizard-dialog');
|
||||
$wizardSteps = $dialog.find('.wizard-step');
|
||||
$templateSteps = $('#template-ftuesteps');
|
||||
$templateButtons = $('#template-ftue-buttons');
|
||||
$ftueButtons = $dialog.find('.ftue-buttons');
|
||||
|
||||
stepUnderstandGear.initialize($wizardSteps.filter($('[layout-wizard-step=0]')));
|
||||
stepSelectGear.initialize($wizardSteps.filter($('[layout-wizard-step=1]')));
|
||||
stepConfigureTracks.initialize($wizardSteps.filter($('[layout-wizard-step=2]')));
|
||||
stepConfigureVoiceChat.initialize($wizardSteps.filter($('[layout-wizard-step=3]')));
|
||||
stepDirectMonitoring.initialize($wizardSteps.filter($('[layout-wizard-step=4]')));
|
||||
stepNetworkTest.initialize($wizardSteps.filter($('[layout-wizard-step=5]')));
|
||||
stepSuccess.initialize($wizardSteps.filter($('[layout-wizard-step=6]')));
|
||||
|
||||
events();
|
||||
}
|
||||
|
||||
this.setNextState = setNextState;
|
||||
this.initialize = initialize;
|
||||
|
||||
self = this;
|
||||
return this;
|
||||
};
|
||||
|
||||
})(window, jQuery);
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
(function (context, $) {
|
||||
|
||||
"use strict";
|
||||
|
||||
context.JK = context.JK || {};
|
||||
context.JK.StepConfigureTracks = function (app) {
|
||||
|
||||
var $step = null;
|
||||
|
||||
function initialize(_$step) {
|
||||
$step = _$step;
|
||||
}
|
||||
|
||||
this.initialize = initialize;
|
||||
|
||||
return this;
|
||||
}
|
||||
})(window, jQuery);
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
(function (context, $) {
|
||||
|
||||
"use strict";
|
||||
|
||||
context.JK = context.JK || {};
|
||||
context.JK.StepConfigureVoiceChat = function (app) {
|
||||
|
||||
var $step = null;
|
||||
|
||||
function initialize(_$step) {
|
||||
$step = _$step;
|
||||
}
|
||||
|
||||
this.initialize = initialize;
|
||||
|
||||
return this;
|
||||
}
|
||||
})(window, jQuery);
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
(function (context, $) {
|
||||
|
||||
"use strict";
|
||||
|
||||
context.JK = context.JK || {};
|
||||
context.JK.StepDirectMonitoring = function (app) {
|
||||
|
||||
var $step = null;
|
||||
|
||||
function initialize(_$step) {
|
||||
$step = _$step;
|
||||
}
|
||||
|
||||
this.initialize = initialize;
|
||||
|
||||
return this;
|
||||
}
|
||||
})(window, jQuery);
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
(function (context, $) {
|
||||
|
||||
"use strict";
|
||||
|
||||
context.JK = context.JK || {};
|
||||
context.JK.StepNetworkTest = function (app) {
|
||||
|
||||
var $step = null;
|
||||
|
||||
function initialize(_$step) {
|
||||
$step = _$step;
|
||||
}
|
||||
|
||||
this.initialize = initialize;
|
||||
|
||||
return this;
|
||||
}
|
||||
})(window, jQuery);
|
||||
|
|
@ -0,0 +1,952 @@
|
|||
(function (context, $) {
|
||||
|
||||
"use strict";
|
||||
|
||||
context.JK = context.JK || {};
|
||||
context.JK.StepSelectGear = function (app, $dialog) {
|
||||
|
||||
var ASSIGNMENT = context.JK.ASSIGNMENT;
|
||||
var VOICE_CHAT = context.JK.VOICE_CHAT;
|
||||
var self = null;
|
||||
var $step = null;
|
||||
var $watchVideoInput = null;
|
||||
var $watchVideoOutput = null;
|
||||
var $audioInput = null;
|
||||
var $audioOutput = null;
|
||||
var $bufferIn = null;
|
||||
var $bufferOut = null;
|
||||
var $frameSize = null;
|
||||
var $inputChannels = null;
|
||||
var $outputChannels = null;
|
||||
var $knobs = null;
|
||||
var $scoreReport = null;
|
||||
var $latencyScoreSection = null;
|
||||
var $latencyScore = null;
|
||||
var $latencyHeader = null;
|
||||
var $ioHeader = null;
|
||||
var $ioScoreSection = null;
|
||||
var $ioRate = null;
|
||||
var $ioRateScore = null;
|
||||
var $ioVar = null;
|
||||
var $ioVarScore = null;
|
||||
var $ioCountdown = null;
|
||||
var $ioCountdownSecs = null;
|
||||
var $resultsText = null;
|
||||
var $asioInputControlBtn = null;
|
||||
var $asioOutputControlBtn = null;
|
||||
var $resyncBtn = null;
|
||||
var $templateAudioPort = null;
|
||||
|
||||
var operatingSystem = null;
|
||||
var iCheckIgnore = false;
|
||||
|
||||
// cached values between
|
||||
var deviceInformation = null;
|
||||
var selectedDeviceInfo = null;
|
||||
var musicPorts = null;
|
||||
var validLatencyScore = false;
|
||||
var validIOScore = false;
|
||||
|
||||
var audioDeviceBehavior = {
|
||||
MacOSX_builtin: {
|
||||
display: 'MacOSX Built-In',
|
||||
videoURL: undefined,
|
||||
showKnobs: false,
|
||||
showASIO: false
|
||||
},
|
||||
MacOSX_interface: {
|
||||
display: 'MacOSX external interface',
|
||||
videoURL: undefined,
|
||||
showKnobs: false,
|
||||
showASIO: false
|
||||
},
|
||||
Win32_wdm: {
|
||||
display: 'Windows WDM',
|
||||
videoURL: undefined,
|
||||
showKnobs: true,
|
||||
showASIO: false
|
||||
},
|
||||
Win32_asio: {
|
||||
display: 'Windows ASIO',
|
||||
videoURL: undefined,
|
||||
showKnobs: true,
|
||||
showASIO: false
|
||||
},
|
||||
Win32_asio4all: {
|
||||
display: 'Windows ASIO4ALL',
|
||||
videoURL: undefined,
|
||||
showKnobs: false,
|
||||
showASIO: true
|
||||
},
|
||||
Linux: {
|
||||
display: 'Linux',
|
||||
videoURL: undefined,
|
||||
showKnobs: true,
|
||||
showASIO: false
|
||||
}
|
||||
}
|
||||
|
||||
var ASIO_SETTINGS_DEFAULT_TEXT = 'ASIO SETTINGS...';
|
||||
var ASIO_SETTINGS_INPUT_TEXT = 'ASIO INPUT SETTINGS...';
|
||||
var ASIO_SETTINGS_OUTPUT_TEXT = 'ASIO OUTPUT SETTINGS...';
|
||||
|
||||
// should return one of:
|
||||
// * MacOSX_builtin
|
||||
// * MACOSX_interface
|
||||
// * Win32_wdm
|
||||
// * Win32_asio
|
||||
// * Win32_asio4all
|
||||
// * Linux
|
||||
function determineDeviceType(deviceId, displayName) {
|
||||
if (operatingSystem == "MacOSX") {
|
||||
if (displayName.toLowerCase().trim() == "built-in") {
|
||||
return "MacOSX_builtin";
|
||||
}
|
||||
else {
|
||||
return "MacOSX_interface";
|
||||
}
|
||||
}
|
||||
else if (operatingSystem == "Win32") {
|
||||
if (context.jamClient.FTUEIsMusicDeviceWDM(deviceId)) {
|
||||
return "Win32_wdm";
|
||||
}
|
||||
else if (displayName.toLowerCase().indexOf("asio4all") > -1) {
|
||||
return "Win32_asio4all"
|
||||
}
|
||||
else {
|
||||
return "Win32_asio";
|
||||
}
|
||||
}
|
||||
else {
|
||||
return "Linux";
|
||||
}
|
||||
}
|
||||
|
||||
function loadDevices() {
|
||||
|
||||
var oldDevices = context.jamClient.FTUEGetDevices(false);
|
||||
var devices = context.jamClient.FTUEGetAudioDevices();
|
||||
console.log("oldDevices: " + JSON.stringify(oldDevices));
|
||||
console.log("devices: " + JSON.stringify(devices));
|
||||
|
||||
var loadedDevices = {};
|
||||
|
||||
// augment these devices by determining their type
|
||||
context._.each(devices.devices, function (device) {
|
||||
|
||||
if (device.name == "JamKazam Virtual Monitor") {
|
||||
return;
|
||||
}
|
||||
|
||||
var deviceInfo = {};
|
||||
|
||||
deviceInfo.id = device.guid;
|
||||
deviceInfo.type = determineDeviceType(device.guid, device.display_name);
|
||||
console.log("deviceInfo.type: " + deviceInfo.type)
|
||||
deviceInfo.displayType = audioDeviceBehavior[deviceInfo.type].display;
|
||||
deviceInfo.displayName = device.display_name;
|
||||
|
||||
loadedDevices[device.guid] = deviceInfo;
|
||||
|
||||
logger.debug("loaded device: ", deviceInfo);
|
||||
})
|
||||
|
||||
deviceInformation = loadedDevices;
|
||||
|
||||
logger.debug(context.JK.dlen(deviceInformation) + " devices loaded.", deviceInformation);
|
||||
}
|
||||
|
||||
// returns a deviceInfo hash for the device matching the deviceId, or undefined.
|
||||
function findDevice(deviceId) {
|
||||
return deviceInformation[deviceId];
|
||||
}
|
||||
|
||||
function selectedAudioInput() {
|
||||
return $audioInput.val();
|
||||
}
|
||||
|
||||
function selectedAudioOutput() {
|
||||
return $audioOutput.val();
|
||||
}
|
||||
|
||||
function selectedFramesize() {
|
||||
return parseFloat($frameSize.val());
|
||||
}
|
||||
|
||||
function selectedBufferIn() {
|
||||
return parseFloat($frameSize.val());
|
||||
}
|
||||
|
||||
function selectedBufferOut() {
|
||||
return parseFloat($frameSize.val());
|
||||
}
|
||||
|
||||
function initializeNextButtonState() {
|
||||
$dialog.setNextState(validLatencyScore && validIOScore);
|
||||
}
|
||||
|
||||
function initializeAudioInput() {
|
||||
var optionsHtml = '';
|
||||
optionsHtml = '<option selected="selected" value="">Choose...</option>';
|
||||
context._.each(deviceInformation, function (deviceInfo, deviceId) {
|
||||
|
||||
console.log(arguments)
|
||||
optionsHtml += '<option title="' + deviceInfo.displayName + '" value="' + deviceId + '">' + deviceInfo.displayName + '</option>';
|
||||
});
|
||||
$audioInput.html(optionsHtml);
|
||||
context.JK.dropdown($audioInput);
|
||||
|
||||
initializeAudioInputChanged();
|
||||
}
|
||||
|
||||
function initializeAudioOutput() {
|
||||
var optionsHtml = '';
|
||||
optionsHtml = '<option selected="selected" value="">Same as Input</option>';
|
||||
context._.each(deviceInformation, function (deviceInfo, deviceId) {
|
||||
optionsHtml += '<option title="' + deviceInfo.displayName + '" value="' + deviceId + '">' + deviceInfo.displayName + '</option>';
|
||||
});
|
||||
$audioOutput.html(optionsHtml);
|
||||
context.JK.dropdown($audioOutput);
|
||||
|
||||
initializeAudioOutputChanged();
|
||||
}
|
||||
|
||||
function initializeFramesize() {
|
||||
context.JK.dropdown($frameSize);
|
||||
}
|
||||
|
||||
function initializeBuffers() {
|
||||
context.JK.dropdown($bufferIn);
|
||||
context.JK.dropdown($bufferOut);
|
||||
}
|
||||
|
||||
|
||||
// reloads the backend's channel state for the currently selected audio devices,
|
||||
// and update's the UI accordingly
|
||||
function initializeChannels() {
|
||||
musicPorts = jamClient.FTUEGetChannels();
|
||||
console.log("musicPorts: %o", JSON.stringify(musicPorts));
|
||||
|
||||
initializeInputPorts(musicPorts);
|
||||
initializeOutputPorts(musicPorts);
|
||||
}
|
||||
|
||||
// select 2 (or 1) inputs and 2 outputs for the user. required to get a latency score
|
||||
// also, arguably convenient
|
||||
function autoSelectMinimumValidChannels() {
|
||||
|
||||
var $allInputs = $inputChannels.find('input[type="checkbox"]');
|
||||
|
||||
if ($allInputs.length == 0) {
|
||||
// ERROR: not enough channels
|
||||
context.JK.Banner.showAlert('To be a valid input audio device, the device must have at least 1 input channel.');
|
||||
return false;
|
||||
}
|
||||
|
||||
var $allOutputs = $outputChannels.find('input[type="checkbox"]');
|
||||
if ($allOutputs.length < 2) {
|
||||
// ERROR: not enough channels
|
||||
context.JK.Banner.showAlert('To be a valid output audio device, the device must have at least 2 output channels.');
|
||||
return false;
|
||||
}
|
||||
|
||||
// ensure 1, or preferably 2, input channels are selected
|
||||
var $assignedInputs = $inputChannels.find('input[type="checkbox"]:checked');
|
||||
var $unassignedInputs = $inputChannels.find('input[type="checkbox"]:not(:checked)');
|
||||
if ($assignedInputs.length == 0) {
|
||||
if ($allInputs.length >= 2) {
|
||||
$unassignedInputs.eq(0).iCheck('check').attr('checked', 'checked');
|
||||
// this is required because iCheck change handler re-writes the inputs. So we have to refetch unassigned outputs
|
||||
$unassignedInputs = $inputChannels.find('input[type="checkbox"]:not(:checked)');
|
||||
$unassignedInputs.eq(0).iCheck('check').attr('checked', 'checked');
|
||||
}
|
||||
else {
|
||||
$unassignedInputs.eq(0).iCheck('check').attr('checked', 'checked');
|
||||
}
|
||||
}
|
||||
|
||||
// ensure 2 outputs are selected
|
||||
var $assignedOutputs = $outputChannels.find('input[type="checkbox"]:checked');
|
||||
var $unassignedOutputs = $outputChannels.find('input[type="checkbox"]:not(:checked)');
|
||||
|
||||
console.log("outputs:", $assignedOutputs, $unassignedOutputs);
|
||||
if ($assignedOutputs.length == 0) {
|
||||
console.log("selecting both outputs")
|
||||
$unassignedOutputs.eq(0).iCheck('check').attr('checked', 'checked');
|
||||
// this is required because iCheck change handler re-writes the inputs. So we have to refetch unassigned outputs
|
||||
$unassignedOutputs = $outputChannels.find('input[type="checkbox"]:not(:checked)');
|
||||
$unassignedOutputs.eq(0).iCheck('check').attr('checked', 'checked');
|
||||
}
|
||||
else if ($assignedOutputs.length == 1) {
|
||||
$unassignedOutputs.eq(0).iCheck('check').attr('checked', 'checked');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// during this phase of the FTUE, we have to assign selected input channels
|
||||
// to tracks. The user, however, does not have a way to indicate which channel
|
||||
// goes to which track (that's not until the next step of the wizard).
|
||||
// so, we just auto-generate a valid assignment
|
||||
function newInputAssignment() {
|
||||
var assigned = 0;
|
||||
context._.each(musicPorts.inputs, function (inputChannel) {
|
||||
if (isChannelAssigned(inputChannel)) {
|
||||
assigned += 1;
|
||||
}
|
||||
});
|
||||
|
||||
var newAssignment = Math.floor(assigned / 2) + 1;
|
||||
return newAssignment;
|
||||
}
|
||||
|
||||
function inputChannelChanged() {
|
||||
if (iCheckIgnore) return;
|
||||
|
||||
var $checkbox = $(this);
|
||||
var channelId = $checkbox.attr('data-id');
|
||||
var isChecked = $checkbox.is(':checked');
|
||||
|
||||
if (isChecked) {
|
||||
var newAssignment = newInputAssignment();
|
||||
logger.debug("assigning input channel %o to track: %o", channelId, newAssignment);
|
||||
context.jamClient.TrackSetAssignment(channelId, true, newAssignment);
|
||||
}
|
||||
else {
|
||||
logger.debug("unassigning input channel %o", channelId);
|
||||
context.jamClient.TrackSetAssignment(channelId, true, ASSIGNMENT.UNASSIGNED);
|
||||
// unassigning creates a hole in our auto-assigned tracks. reassign them all to keep it consistent
|
||||
var $assignedInputs = $inputChannels.find('input[type="checkbox"]:checked');
|
||||
var assigned = 0;
|
||||
context._.each($assignedInputs, function (assignedInput) {
|
||||
var $assignedInput = $(assignedInput);
|
||||
var assignedChannelId = $assignedInput.attr('data-id');
|
||||
var newAssignment = Math.floor(assigned / 2) + 1;
|
||||
logger.debug("re-assigning input channel %o to track: %o", assignedChannelId, newAssignment);
|
||||
context.jamClient.TrackSetAssignment(assignedChannelId, true, newAssignment);
|
||||
assigned += 1;
|
||||
});
|
||||
}
|
||||
|
||||
initializeChannels();
|
||||
}
|
||||
|
||||
// should be called in a ifChanged callback if you want to cancel.
|
||||
// you have to use this instead of 'return false' like a typical input 'change' event.
|
||||
function cancelICheckChange($checkbox) {
|
||||
iCheckIgnore = true;
|
||||
var checked = $checkbox.is(':checked');
|
||||
setTimeout(function () {
|
||||
if (checked) $checkbox.iCheck('uncheck').removeAttr('checked');
|
||||
else $checkbox.iCheck('check').attr('checked', 'checked');
|
||||
iCheckIgnore = false;
|
||||
}, 1);
|
||||
}
|
||||
|
||||
function outputChannelChanged() {
|
||||
if (iCheckIgnore) return;
|
||||
var $checkbox = $(this);
|
||||
var channelId = $checkbox.attr('data-id');
|
||||
var isChecked = $checkbox.is(':checked');
|
||||
|
||||
// don't allow more than 2 output channels selected at once
|
||||
if ($outputChannels.find('input[type="checkbox"]:checked').length > 2) {
|
||||
context.JK.Banner.showAlert('You can only have a maximum of 2 output ports selected.');
|
||||
// can't allow uncheck of last output
|
||||
cancelICheckChange($checkbox);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isChecked) {
|
||||
logger.debug("assigning output channel %o", channelId);
|
||||
context.jamClient.TrackSetAssignment(channelId, true, ASSIGNMENT.OUTPUT);
|
||||
}
|
||||
else {
|
||||
logger.debug("unassigning output channel %o", channelId);
|
||||
context.jamClient.TrackSetAssignment(channelId, true, ASSIGNMENT.UNASSIGNED);
|
||||
}
|
||||
|
||||
initializeChannels();
|
||||
}
|
||||
|
||||
// checks if it's an assigned OUTPUT or ASSIGNED CHAT
|
||||
function isChannelAssigned(channel) {
|
||||
return channel.assignment == ASSIGNMENT.CHAT || channel.assignment == ASSIGNMENT.OUTPUT || channel.assignment > 0;
|
||||
}
|
||||
|
||||
function initializeInputPorts(musicPorts) {
|
||||
$inputChannels.empty();
|
||||
var inputPorts = musicPorts.inputs;
|
||||
context._.each(inputPorts, function (inputChannel) {
|
||||
var $inputChannel = $(context._.template($templateAudioPort.html(), inputChannel, { variable: 'data' }));
|
||||
var $checkbox = $inputChannel.find('input');
|
||||
if (isChannelAssigned(inputChannel)) {
|
||||
$checkbox.attr('checked', 'checked');
|
||||
}
|
||||
context.JK.checkbox($checkbox);
|
||||
$checkbox.on('ifChanged', inputChannelChanged);
|
||||
$inputChannels.append($inputChannel);
|
||||
});
|
||||
}
|
||||
|
||||
function initializeOutputPorts(musicPorts) {
|
||||
$outputChannels.empty();
|
||||
var outputChannels = musicPorts.outputs;
|
||||
context._.each(outputChannels, function (outputChannel) {
|
||||
var $outputPort = $(context._.template($templateAudioPort.html(), outputChannel, { variable: 'data' }));
|
||||
var $checkbox = $outputPort.find('input');
|
||||
if (isChannelAssigned(outputChannel)) {
|
||||
$checkbox.attr('checked', 'checked');
|
||||
}
|
||||
context.JK.checkbox($checkbox);
|
||||
$checkbox.on('ifChanged', outputChannelChanged);
|
||||
$outputChannels.append($outputPort);
|
||||
});
|
||||
}
|
||||
|
||||
function initializeFormElements() {
|
||||
if (!deviceInformation) throw "devices are not initialized";
|
||||
|
||||
initializeAudioInput();
|
||||
initializeAudioOutput();
|
||||
initializeFramesize();
|
||||
initializeBuffers();
|
||||
}
|
||||
|
||||
function resetFrameBuffers() {
|
||||
$frameSize.val('2.5');
|
||||
$bufferIn.val('0');
|
||||
$bufferOut.val('0');
|
||||
}
|
||||
|
||||
function clearInputPorts() {
|
||||
$inputChannels.empty();
|
||||
}
|
||||
|
||||
function clearOutputPorts() {
|
||||
$outputChannels.empty();
|
||||
}
|
||||
|
||||
function resetScoreReport() {
|
||||
$ioHeader.hide();
|
||||
$latencyHeader.hide();
|
||||
$ioRate.hide();
|
||||
$ioRateScore.empty();
|
||||
$ioVar.hide();
|
||||
$ioVarScore.empty();
|
||||
$latencyScore.empty();
|
||||
$resultsText.removeAttr('latency-score');
|
||||
$resultsText.removeAttr('io-var-score');
|
||||
$resultsText.removeAttr('io-rate-score');
|
||||
}
|
||||
|
||||
function renderLatencyScore(latencyValue, latencyClass) {
|
||||
// latencyValue == null implies starting condition
|
||||
if (latencyValue) {
|
||||
$latencyScore.text(latencyValue + ' ms');
|
||||
}
|
||||
else {
|
||||
$latencyScore.text('');
|
||||
}
|
||||
$latencyHeader.show();
|
||||
$resultsText.attr('latency-score', latencyClass);
|
||||
$latencyScoreSection.removeClass('good acceptable bad unknown starting').addClass(latencyClass);
|
||||
}
|
||||
|
||||
// std deviation is the worst value between in/out
|
||||
// media is the worst value between in/out
|
||||
// io is the value returned by the backend, which has more info
|
||||
// ioClass is the pre-computed rollup class describing the result in simple terms of 'good', 'acceptable', bad'
|
||||
function renderIOScore(std, median, ioData, ioClass, ioRateClass, ioVarClass) {
|
||||
$ioRateScore.text(median !== null ? median : '');
|
||||
$ioVarScore.text(std !== null ? std : '');
|
||||
if (ioClass && ioClass != "starting") {
|
||||
$ioRate.show();
|
||||
$ioVar.show();
|
||||
}
|
||||
if(ioClass == 'starting') {
|
||||
$ioHeader.show();
|
||||
}
|
||||
$resultsText.attr('io-rate-score', ioRateClass);
|
||||
$resultsText.attr('io-var-score', ioVarClass);
|
||||
$ioScoreSection.removeClass('good acceptable bad unknown starting skip')
|
||||
if (ioClass) {
|
||||
$ioScoreSection.addClass(ioClass);
|
||||
}
|
||||
// TODO: show help bubble of all data in IO data
|
||||
}
|
||||
|
||||
function updateScoreReport(latencyResult) {
|
||||
var latencyClass = "neutral";
|
||||
var latencyValue = 'N/A';
|
||||
var validLatency = false;
|
||||
if (latencyResult && latencyResult.latencyknown) {
|
||||
var latencyValue = latencyResult.latency;
|
||||
latencyValue = Math.round(latencyValue * 100) / 100;
|
||||
if (latencyValue <= 10) {
|
||||
latencyClass = "good";
|
||||
validLatency = true;
|
||||
} else if (latencyValue <= 20) {
|
||||
latencyClass = "acceptable";
|
||||
validLatency = true;
|
||||
} else {
|
||||
latencyClass = "bad";
|
||||
}
|
||||
}
|
||||
else {
|
||||
latencyClass = 'unknown';
|
||||
}
|
||||
|
||||
validLatencyScore = validLatency;
|
||||
|
||||
renderLatencyScore(latencyValue, latencyClass);
|
||||
}
|
||||
|
||||
function audioInputDeviceUnselected() {
|
||||
validLatencyScore = false;
|
||||
validIOScore = false;
|
||||
initializeNextButtonState();
|
||||
resetFrameBuffers();
|
||||
clearInputPorts();
|
||||
}
|
||||
|
||||
function renderScoringStarted() {
|
||||
validLatencyScore = false;
|
||||
validIOScore = false;
|
||||
initializeNextButtonState();
|
||||
resetScoreReport();
|
||||
freezeAudioInteraction();
|
||||
renderLatencyScore(null, 'starting');
|
||||
renderIOScore(null, null, null, null, null, null);
|
||||
}
|
||||
|
||||
function renderScoringStopped() {
|
||||
initializeNextButtonState();
|
||||
unfreezeAudioInteraction();
|
||||
}
|
||||
|
||||
|
||||
function freezeAudioInteraction() {
|
||||
$audioInput.attr("disabled", "disabled").easyDropDown('disable');
|
||||
$audioOutput.attr("disabled", "disabled").easyDropDown('disable');
|
||||
$frameSize.attr("disabled", "disabled").easyDropDown('disable');
|
||||
$bufferIn.attr("disabled", "disabled").easyDropDown('disable');
|
||||
$bufferOut.attr("disabled", "disabled").easyDropDown('disable');
|
||||
$asioInputControlBtn.on("click", false);
|
||||
$asioOutputControlBtn.on("click", false);
|
||||
$resyncBtn.on('click', false);
|
||||
iCheckIgnore = true;
|
||||
$inputChannels.find('input[type="checkbox"]').iCheck('disable');
|
||||
$outputChannels.find('input[type="checkbox"]').iCheck('disable');
|
||||
}
|
||||
|
||||
function unfreezeAudioInteraction() {
|
||||
$audioInput.removeAttr("disabled").easyDropDown('enable');
|
||||
$audioOutput.removeAttr("disabled").easyDropDown('enable');
|
||||
$frameSize.removeAttr("disabled").easyDropDown('enable');
|
||||
$bufferIn.removeAttr("disabled").easyDropDown('enable');
|
||||
$bufferOut.removeAttr("disabled").easyDropDown('enable');
|
||||
$asioInputControlBtn.off("click", false);
|
||||
$asioOutputControlBtn.off("click", false);
|
||||
$resyncBtn.off('click', false);
|
||||
$inputChannels.find('input[type="checkbox"]').iCheck('enable');
|
||||
$outputChannels.find('input[type="checkbox"]').iCheck('enable');
|
||||
iCheckIgnore = false;
|
||||
}
|
||||
|
||||
// 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";
|
||||
setSaveButtonState($saveButton, true);
|
||||
} else if (latency.latency <= 20) {
|
||||
latencyClass = "acceptable";
|
||||
setSaveButtonState($saveButton, true);
|
||||
} else {
|
||||
latencyClass = "bad";
|
||||
setSaveButtonState($saveButton, false);
|
||||
}
|
||||
} else {
|
||||
latencyClass = "unknown";
|
||||
setSaveButtonState($saveButton, false);
|
||||
}
|
||||
|
||||
$('.ms-label', $report).html(latencyValue);
|
||||
$('p', $report).html('milliseconds');
|
||||
|
||||
$report.removeClass('good acceptable bad unknown');
|
||||
$report.addClass(latencyClass);
|
||||
|
||||
var instructionClasses = ['neutral', 'good', 'acceptable', 'unknown', 'bad', 'start', 'loading'];
|
||||
$.each(instructionClasses, function (idx, val) {
|
||||
$('p.' + val, $instructions).hide();
|
||||
});
|
||||
if (latency === 'loading') {
|
||||
$('p.loading', $instructions).show();
|
||||
} else {
|
||||
$('p.' + latencyClass, $instructions).show();
|
||||
renderStopNewFtueLatencyTesting();
|
||||
}
|
||||
}
|
||||
|
||||
function initializeWatchVideo() {
|
||||
$watchVideoInput.unbind('click').click(function () {
|
||||
|
||||
var audioDevice = findDevice(selectedAudioInput());
|
||||
if (!audioDevice) {
|
||||
context.JK.Banner.showAlert('You must first choose an Audio Input Device so that we can determine which video to show you.');
|
||||
}
|
||||
else {
|
||||
var videoURL = audioDeviceBehavior[audioDevice.type].videoURL;
|
||||
|
||||
if (videoURL) {
|
||||
$(this).attr('href', videoURL);
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
context.JK.Banner.showAlert('No help video for this type of device (' + audioDevice.displayType + ')');
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
$watchVideoOutput.unbind('click').click(function () {
|
||||
|
||||
var audioDevice = findDevice(selectedAudioOutput());
|
||||
if (!audioDevice) {
|
||||
throw "this button should be hidden";
|
||||
}
|
||||
else {
|
||||
var videoURL = audioDeviceBehavior[audioDevice.type].videoURL;
|
||||
|
||||
if (videoURL) {
|
||||
$(this).attr('href', videoURL);
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
context.JK.Banner.showAlert('No help video for this type of device (' + audioDevice.displayType + ')');
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
function initializeASIOButtons() {
|
||||
$asioInputControlBtn.unbind('click').click(function () {
|
||||
context.jamClient.FTUEOpenControlPanel(); // TODO: supply with ID when VRFS-1707 is done
|
||||
});
|
||||
$asioOutputControlBtn.unbind('click').click(function () {
|
||||
context.jamClient.FTUEOpenControlPanel(); // TODO: supply with ID when VRFS-1707 is done
|
||||
});
|
||||
}
|
||||
|
||||
function initializeKnobs() {
|
||||
$frameSize.unbind('change').change(function () {
|
||||
jamClient.FTUESetFrameSize(selectedFramesize());
|
||||
});
|
||||
|
||||
$bufferIn.unbind('change').change(function () {
|
||||
jamClient.FTUESetInputLatency(selectedBufferIn());
|
||||
});
|
||||
|
||||
$bufferOut.unbind('change').change(function () {
|
||||
jamClient.FTUESetOutputLatency(selectedBufferOut());
|
||||
});
|
||||
}
|
||||
|
||||
function initializeResync() {
|
||||
$resyncBtn.unbind('click').click(function () {
|
||||
attemptScore();
|
||||
return false;
|
||||
})
|
||||
}
|
||||
|
||||
function renderIOScoringStarted(secondsLeft) {
|
||||
$ioCountdownSecs.text(secondsLeft);
|
||||
$ioCountdown.show();
|
||||
}
|
||||
|
||||
function renderIOScoringStopped() {
|
||||
$ioCountdown.hide();
|
||||
}
|
||||
|
||||
function renderIOCountdown(secondsLeft) {
|
||||
$ioCountdownSecs.text(secondsLeft);
|
||||
}
|
||||
|
||||
// sets currentlySelectedDeviceInfo, which contains id, behavior, and info for input and output device
|
||||
function cacheCurrentAudioInfo() {
|
||||
|
||||
var audioInputDeviceId = selectedAudioInput();
|
||||
var audioOutputDeviceId = selectedAudioOutput();
|
||||
if (!audioInputDeviceId) {
|
||||
audioInputDeviceUnselected();
|
||||
return false;
|
||||
}
|
||||
|
||||
var audioInputDevice = findDevice(audioInputDeviceId);
|
||||
if (!audioInputDevice) {
|
||||
context.JK.alertSupportedNeeded('Unable to find information for input device: ' + audioInputDeviceId);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!audioOutputDeviceId) {
|
||||
audioOutputDeviceId = audioInputDeviceId;
|
||||
}
|
||||
var audioOutputDevice = findDevice(audioOutputDeviceId);
|
||||
if (!audioInputDevice) {
|
||||
context.JK.alertSupportedNeeded('Unable to find information for output device: ' + audioOutputDeviceId);
|
||||
return false;
|
||||
}
|
||||
|
||||
var input = findDevice(audioInputDeviceId);
|
||||
var output = findDevice(audioOutputDeviceId);
|
||||
|
||||
var inputBehavior = audioDeviceBehavior[input.type];
|
||||
var outputBehavior = audioDeviceBehavior[output.type];
|
||||
|
||||
selectedDeviceInfo = {
|
||||
input: {
|
||||
id: audioInputDeviceId,
|
||||
info: input,
|
||||
behavior: inputBehavior
|
||||
},
|
||||
output: {
|
||||
id: audioOutputDeviceId,
|
||||
info: output,
|
||||
behavior: outputBehavior
|
||||
}
|
||||
}
|
||||
console.log("selectedDeviceInfo", selectedDeviceInfo);
|
||||
}
|
||||
|
||||
function changeDevice() {
|
||||
|
||||
var audioInputDeviceId = selectedDeviceInfo.input.id;
|
||||
var audioOutputDeviceId = selectedDeviceInfo.output.id;
|
||||
|
||||
// don't re-assign input/output audio devices because it disturbs input/output track association
|
||||
if (jamClient.FTUEGetInputMusicDevice() != audioInputDeviceId) {
|
||||
jamClient.FTUESetInputMusicDevice(audioInputDeviceId);
|
||||
}
|
||||
if (jamClient.FTUEGetOutputMusicDevice() != audioOutputDeviceId) {
|
||||
jamClient.FTUESetOutputMusicDevice(audioOutputDeviceId);
|
||||
}
|
||||
|
||||
initializeChannels();
|
||||
|
||||
var validDevice = autoSelectMinimumValidChannels();
|
||||
|
||||
if (!validDevice) {
|
||||
return false;
|
||||
}
|
||||
|
||||
jamClient.FTUESetInputLatency(selectedBufferIn());
|
||||
jamClient.FTUESetOutputLatency(selectedBufferOut());
|
||||
jamClient.FTUESetFrameSize(selectedFramesize());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function audioDeviceChanged() {
|
||||
cacheCurrentAudioInfo();
|
||||
updateDialogForCurrentDevices();
|
||||
if (changeDevice()) {
|
||||
attemptScore();
|
||||
}
|
||||
}
|
||||
|
||||
function updateDialogForCurrentDevices() {
|
||||
var inputBehavior = selectedDeviceInfo.input.behavior;
|
||||
var outputBehavior = selectedDeviceInfo.output.behavior;
|
||||
|
||||
// handle framesize/buffers
|
||||
if (inputBehavior && (inputBehavior.showKnobs || outputBehavior.showKnobs)) {
|
||||
$knobs.css('visibility', 'visible')
|
||||
}
|
||||
else {
|
||||
$knobs.css('visibility', 'hidden')
|
||||
}
|
||||
|
||||
// handle ASIO
|
||||
if (inputBehavior) {
|
||||
if (inputBehavior.showASIO && !outputBehavior.showASIO) {
|
||||
// show single ASIO button
|
||||
$asioInputControlBtn.text(ASIO_SETTINGS_DEFAULT_TEXT).show();
|
||||
$asioOutputControlBtn.hide();
|
||||
}
|
||||
else if (!inputBehavior.showASIO && outputBehavior.showASIO) {
|
||||
// show single ASIO button
|
||||
$asioInputControlBtn.text(ASIO_SETTINGS_DEFAULT_TEXT).show();
|
||||
$asioOutputControlBtn.hide();
|
||||
}
|
||||
else if (inputBehavior.showASIO && outputBehavior.showASIO) {
|
||||
// show two ASIO buttons
|
||||
$asioInputControlBtn.text(ASIO_SETTINGS_INPUT_TEXT).show();
|
||||
$asioOutputControlBtn.text(ASIO_SETTINGS_OUTPUT_TEXT).show();
|
||||
}
|
||||
else {
|
||||
// show no ASIO buttons
|
||||
$asioInputControlBtn.hide();
|
||||
$asioOutputControlBtn.hide();
|
||||
}
|
||||
}
|
||||
else {
|
||||
// show no ASIO buttons
|
||||
$asioInputControlBtn.hide();
|
||||
$asioOutputControlBtn.hide();
|
||||
}
|
||||
|
||||
// handle resync button
|
||||
if (inputBehavior) {
|
||||
$resyncBtn.css('visibility', 'visible');
|
||||
}
|
||||
else {
|
||||
$resyncBtn.css('visibility', 'hidden');
|
||||
}
|
||||
}
|
||||
|
||||
function attemptScore() {
|
||||
renderScoringStarted();
|
||||
|
||||
// timer exists to give UI time to update for renderScoringStarted before blocking nature of jamClient.FTUESave(save) kicks in
|
||||
setTimeout(function () {
|
||||
logger.debug("Calling FTUESave(false)");
|
||||
jamClient.FTUESave(false);
|
||||
|
||||
var latency = jamClient.FTUEGetExpectedLatency();
|
||||
console.log("FTUEGetExpectedLatency: %o", latency);
|
||||
|
||||
updateScoreReport(latency);
|
||||
|
||||
// if there was a valid latency score, go on to the next step
|
||||
if (validLatencyScore) {
|
||||
renderIOScore(null, null, null, 'starting', 'starting', 'starting');
|
||||
var testTimeSeconds = 10; // allow 10 seconds for IO to establish itself
|
||||
context.jamClient.FTUEStartIoPerfTest();
|
||||
renderIOScoringStarted(testTimeSeconds);
|
||||
renderIOCountdown(testTimeSeconds);
|
||||
var interval = setInterval(function () {
|
||||
testTimeSeconds -= 1;
|
||||
renderIOCountdown(testTimeSeconds);
|
||||
if (testTimeSeconds == 0) {
|
||||
clearInterval(interval);
|
||||
renderIOScoringStopped();
|
||||
var io = context.jamClient.FTUEGetIoPerfData();
|
||||
|
||||
// take the higher variance, which is apparently actually std dev
|
||||
var std = io.in_var > io.out_var ? io.in_var : io.out_var;
|
||||
std = Math.round(std * 100) / 100;
|
||||
// take the furthest-off-from-target io rate
|
||||
var median = Math.abs(io.in_median - io.in_target) > Math.abs(io.out_median - io.out_target) ? [io.in_median, io.in_target] : [io.out_median, io.out_target];
|
||||
var medianTarget = median[1];
|
||||
median = Math.round(median[0]);
|
||||
|
||||
var stdIOClass = 'bad';
|
||||
if (std <= 0.50) {
|
||||
stdIOClass = 'good';
|
||||
}
|
||||
else if (std <= 1.00) {
|
||||
stdIOClass = 'acceptable';
|
||||
}
|
||||
|
||||
var medianIOClass = 'bad';
|
||||
if (Math.abs(median - medianTarget) <= 1) {
|
||||
medianIOClass = 'good';
|
||||
}
|
||||
else if (Math.abs(median - medianTarget) <= 2) {
|
||||
medianIOClass = 'acceptable';
|
||||
}
|
||||
|
||||
// take worst between median or std
|
||||
var ioClassToNumber = {bad: 2, acceptable: 1, good: 0}
|
||||
var aggregrateIOClass = ioClassToNumber[stdIOClass] > ioClassToNumber[medianIOClass] ? stdIOClass : medianIOClass;
|
||||
|
||||
// now base the overall IO score based on both values.
|
||||
renderIOScore(std, median, io, aggregrateIOClass, medianIOClass, stdIOClass);
|
||||
|
||||
// lie for now until IO questions finalize
|
||||
validIOScore = true;
|
||||
|
||||
renderScoringStopped();
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
else {
|
||||
renderIOScore(null, null, null, 'skip', 'skip', 'skip');
|
||||
renderScoringStopped();
|
||||
}
|
||||
}, 250);
|
||||
}
|
||||
|
||||
function initializeAudioInputChanged() {
|
||||
$audioInput.unbind('change').change(audioDeviceChanged);
|
||||
}
|
||||
|
||||
function initializeAudioOutputChanged() {
|
||||
$audioOutput.unbind('change').change(audioDeviceChanged);
|
||||
}
|
||||
|
||||
function beforeShow() {
|
||||
loadDevices();
|
||||
initializeFormElements();
|
||||
initializeNextButtonState();
|
||||
initializeWatchVideo();
|
||||
initializeASIOButtons();
|
||||
initializeKnobs();
|
||||
initializeResync();
|
||||
}
|
||||
|
||||
function initialize(_$step) {
|
||||
$step = _$step;
|
||||
|
||||
$watchVideoInput = $step.find('.watch-video.audio-input');
|
||||
$watchVideoOutput = $step.find('.watch-video.audio-output');
|
||||
$audioInput = $step.find('.select-audio-input-device');
|
||||
$audioOutput = $step.find('.select-audio-output-device');
|
||||
$bufferIn = $step.find('.select-buffer-in');
|
||||
$bufferOut = $step.find('.select-buffer-out');
|
||||
$frameSize = $step.find('.select-frame-size');
|
||||
$inputChannels = $step.find('.input-ports');
|
||||
$outputChannels = $step.find('.output-ports');
|
||||
$knobs = $step.find('.frame-and-buffers');
|
||||
$scoreReport = $step.find('.results');
|
||||
$latencyScoreSection = $scoreReport.find('.latency-score-section');
|
||||
$latencyScore = $scoreReport.find('.latency-score');
|
||||
$latencyHeader = $scoreReport.find('.latency');
|
||||
$ioHeader = $scoreReport.find('.io');
|
||||
$ioScoreSection = $scoreReport.find('.io-score-section');
|
||||
$ioRate = $scoreReport.find('.io-rate');
|
||||
$ioRateScore = $scoreReport.find('.io-rate-score');
|
||||
$ioVar = $scoreReport.find('.io-var');
|
||||
$ioVarScore = $scoreReport.find('.io-var-score');
|
||||
$ioCountdown = $scoreReport.find('.io-countdown');
|
||||
$ioCountdownSecs = $scoreReport.find('.io-countdown .secs');
|
||||
$resultsText = $scoreReport.find('.results-text');
|
||||
$asioInputControlBtn = $step.find('.asio-settings-input-btn');
|
||||
$asioOutputControlBtn = $step.find('.asio-settings-output-btn');
|
||||
$resyncBtn = $step.find('.resync-btn');
|
||||
$templateAudioPort = $('#template-audio-port');
|
||||
|
||||
operatingSystem = context.jamClient.GetOSAsString();
|
||||
}
|
||||
|
||||
this.beforeShow = beforeShow;
|
||||
this.initialize = initialize;
|
||||
|
||||
self = this;
|
||||
return this;
|
||||
};
|
||||
|
||||
})(window, jQuery);
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
(function (context, $) {
|
||||
|
||||
"use strict";
|
||||
|
||||
context.JK = context.JK || {};
|
||||
context.JK.StepSuccess = function (app) {
|
||||
|
||||
var $step = null;
|
||||
|
||||
function initialize(_$step) {
|
||||
$step = _$step;
|
||||
}
|
||||
|
||||
this.initialize = initialize;
|
||||
|
||||
return this;
|
||||
}
|
||||
})(window, jQuery);
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
(function (context, $) {
|
||||
|
||||
"use strict";
|
||||
|
||||
context.JK = context.JK || {};
|
||||
context.JK.StepUnderstandGear = function (app) {
|
||||
|
||||
var $step = null;
|
||||
|
||||
function beforeShow() {
|
||||
var $watchVideo = $step.find('.watch-video');
|
||||
var videoUrl = 'https://www.youtube.com/watch?v=VexH4834o9I';
|
||||
if (operatingSystem == "Win32") {
|
||||
$watchVideo.attr('href', 'https://www.youtube.com/watch?v=VexH4834o9I');
|
||||
}
|
||||
$watchVideo.attr('href', videoUrl);
|
||||
}
|
||||
|
||||
function initialize(_$step) {
|
||||
$step = _$step;
|
||||
}
|
||||
|
||||
this.initialize = initialize;
|
||||
|
||||
return this;
|
||||
}
|
||||
})(window, jQuery);
|
||||
|
|
@ -1,909 +0,0 @@
|
|||
(function (context, $) {
|
||||
|
||||
"use strict";
|
||||
|
||||
|
||||
context.JK = context.JK || {};
|
||||
context.JK.GearWizard = function (app) {
|
||||
|
||||
var ASSIGNMENT = context.JK.ASSIGNMENT;
|
||||
var VOICE_CHAT = context.JK.VOICE_CHAT;
|
||||
|
||||
var $dialog = null;
|
||||
var $wizardSteps = null;
|
||||
var $currentWizardStep = null;
|
||||
var step = 0;
|
||||
var $templateSteps = null;
|
||||
var $templateButtons = null;
|
||||
var $templateAudioPort = null;
|
||||
var $ftueButtons = null;
|
||||
var self = null;
|
||||
var operatingSystem = null;
|
||||
|
||||
// populated by loadDevices
|
||||
var deviceInformation = null;
|
||||
var musicPorts = null;
|
||||
|
||||
|
||||
var validLatencyScore = false;
|
||||
var validIOScore = false;
|
||||
|
||||
// SELECT TRACKS STATE
|
||||
|
||||
var TOTAL_STEPS = 7;
|
||||
var STEP_INTRO = 0;
|
||||
var STEP_SELECT_DEVICE = 1;
|
||||
var STEP_SELECT_TRACKS = 2;
|
||||
var STEP_SELECT_CHAT = 3;
|
||||
var STEP_DIRECT_MONITOR = 4;
|
||||
var STEP_ROUTER_NETWORK = 5;
|
||||
var STEP_SUCCESS = 6;
|
||||
|
||||
var PROFILE_DEV_SEP_TOKEN = '^';
|
||||
|
||||
var iCheckIgnore = false;
|
||||
|
||||
var audioDeviceBehavior = {
|
||||
MacOSX_builtin: {
|
||||
display: 'MacOSX Built-In',
|
||||
videoURL: undefined
|
||||
},
|
||||
MacOSX_interface: {
|
||||
display: 'MacOSX external interface',
|
||||
videoURL: undefined
|
||||
},
|
||||
Win32_wdm: {
|
||||
display: 'Windows WDM',
|
||||
videoURL: undefined
|
||||
},
|
||||
Win32_asio: {
|
||||
display: 'Windows ASIO',
|
||||
videoURL: undefined
|
||||
},
|
||||
Win32_asio4all: {
|
||||
display: 'Windows ASIO4ALL',
|
||||
videoURL: undefined
|
||||
},
|
||||
Linux: {
|
||||
display: 'Linux',
|
||||
videoURL: undefined
|
||||
}
|
||||
}
|
||||
|
||||
function beforeShowIntro() {
|
||||
var $watchVideo = $currentWizardStep.find('.watch-video');
|
||||
var videoUrl = 'https://www.youtube.com/watch?v=VexH4834o9I';
|
||||
if (operatingSystem == "Win32") {
|
||||
$watchVideo.attr('href', 'https://www.youtube.com/watch?v=VexH4834o9I');
|
||||
}
|
||||
$watchVideo.attr('href', videoUrl);
|
||||
}
|
||||
|
||||
function beforeSelectDevice() {
|
||||
|
||||
var $watchVideoInput = $currentWizardStep.find('.watch-video.audio-input');
|
||||
var $watchVideoOutput = $currentWizardStep.find('.watch-video.audio-output');
|
||||
var $audioInput = $currentWizardStep.find('.select-audio-input-device');
|
||||
var $audioOutput = $currentWizardStep.find('.select-audio-output-device');
|
||||
var $bufferIn = $currentWizardStep.find('.select-buffer-in');
|
||||
var $bufferOut = $currentWizardStep.find('.select-buffer-out');
|
||||
var $frameSize = $currentWizardStep.find('.select-frame-size');
|
||||
var $inputChannels = $currentWizardStep.find('.input-ports');
|
||||
var $outputChannels = $currentWizardStep.find('.output-ports');
|
||||
var $scoreReport = $currentWizardStep.find('.results');
|
||||
var $latencyScoreSection = $scoreReport.find('.latency-score-section');
|
||||
var $latencyScore = $scoreReport.find('.latency-score');
|
||||
var $ioScoreSection = $scoreReport.find('.io-score-section');
|
||||
var $ioRateScore = $scoreReport.find('.io-rate-score');
|
||||
var $ioVarScore = $scoreReport.find('.io-var-score');
|
||||
var $ioCountdown = $scoreReport.find('.io-countdown');
|
||||
var $ioCountdownSecs = $scoreReport.find('.io-countdown .secs');
|
||||
var $nextButton = $ftueButtons.find('.btn-next');
|
||||
var $asioControlPanelBtn = $currentWizardStep.find('.asio-settings-btn');
|
||||
var $resyncBtn = $currentWizardStep.find('resync-btn')
|
||||
|
||||
// should return one of:
|
||||
// * MacOSX_builtin
|
||||
// * MACOSX_interface
|
||||
// * Win32_wdm
|
||||
// * Win32_asio
|
||||
// * Win32_asio4all
|
||||
// * Linux
|
||||
function determineDeviceType(deviceId, displayName) {
|
||||
if (operatingSystem == "MacOSX") {
|
||||
if (displayName.toLowerCase().trim() == "built-in") {
|
||||
return "MacOSX_builtin";
|
||||
}
|
||||
else {
|
||||
return "MacOSX_interface";
|
||||
}
|
||||
}
|
||||
else if (operatingSystem == "Win32") {
|
||||
if (context.jamClient.FTUEIsMusicDeviceWDM(deviceId)) {
|
||||
return "Win32_wdm";
|
||||
}
|
||||
else if (displayName.toLowerCase().indexOf("asio4all") > -1) {
|
||||
return "Win32_asio4all"
|
||||
}
|
||||
else {
|
||||
return "Win32_asio";
|
||||
}
|
||||
}
|
||||
else {
|
||||
return "Linux";
|
||||
}
|
||||
}
|
||||
|
||||
function loadDevices() {
|
||||
|
||||
var oldDevices = context.jamClient.FTUEGetDevices(false);
|
||||
var devices = context.jamClient.FTUEGetAudioDevices();
|
||||
console.log("oldDevices: " + JSON.stringify(oldDevices));
|
||||
console.log("devices: " + JSON.stringify(devices));
|
||||
|
||||
var loadedDevices = {};
|
||||
|
||||
// augment these devices by determining their type
|
||||
context._.each(devices.devices, function (device) {
|
||||
|
||||
if(device.name == "JamKazam Virtual Monitor") {
|
||||
return;
|
||||
}
|
||||
|
||||
var deviceInfo = {};
|
||||
|
||||
deviceInfo.id = device.guid;
|
||||
deviceInfo.type = determineDeviceType(device.guid, device.display_name);
|
||||
console.log("deviceInfo.type: " + deviceInfo.type)
|
||||
deviceInfo.displayType = audioDeviceBehavior[deviceInfo.type].display;
|
||||
deviceInfo.displayName = device.display_name;
|
||||
|
||||
loadedDevices[device.guid] = deviceInfo;
|
||||
|
||||
logger.debug("loaded device: ", deviceInfo);
|
||||
})
|
||||
|
||||
deviceInformation = loadedDevices;
|
||||
|
||||
logger.debug(context.JK.dlen(deviceInformation) + " devices loaded.", deviceInformation);
|
||||
}
|
||||
|
||||
// returns a deviceInfo hash for the device matching the deviceId, or undefined.
|
||||
function findDevice(deviceId) {
|
||||
return deviceInformation[deviceId];
|
||||
}
|
||||
|
||||
function selectedAudioInput() {
|
||||
return $audioInput.val();
|
||||
}
|
||||
|
||||
function selectedAudioOutput() {
|
||||
return $audioOutput.val();
|
||||
}
|
||||
|
||||
function selectedFramesize() {
|
||||
return parseFloat($frameSize.val());
|
||||
}
|
||||
|
||||
function selectedBufferIn() {
|
||||
return parseFloat($frameSize.val());
|
||||
}
|
||||
|
||||
function selectedBufferOut() {
|
||||
return parseFloat($frameSize.val());
|
||||
}
|
||||
|
||||
function initializeNextButtonState() {
|
||||
$nextButton.removeClass('button-orange button-grey');
|
||||
|
||||
if (validLatencyScore) $nextButton.addClass('button-orange');
|
||||
else $nextButton.addClass('button-grey');
|
||||
}
|
||||
|
||||
function initializeAudioInput() {
|
||||
var optionsHtml = '';
|
||||
optionsHtml = '<option selected="selected" value="">Choose...</option>';
|
||||
context._.each(deviceInformation, function (deviceInfo, deviceId) {
|
||||
|
||||
console.log(arguments)
|
||||
optionsHtml += '<option title="' + deviceInfo.displayName + '" value="' + deviceId + '">' + deviceInfo.displayName + '</option>';
|
||||
});
|
||||
$audioInput.html(optionsHtml);
|
||||
context.JK.dropdown($audioInput);
|
||||
|
||||
initializeAudioInputChanged();
|
||||
}
|
||||
|
||||
function initializeAudioOutput() {
|
||||
var optionsHtml = '';
|
||||
optionsHtml = '<option selected="selected" value="">Same as Input...</option>';
|
||||
context._.each(deviceInformation, function (deviceInfo, deviceId) {
|
||||
optionsHtml += '<option title="' + deviceInfo.displayName + '" value="' + deviceId + '">' + deviceInfo.displayName + '</option>';
|
||||
});
|
||||
$audioOutput.html(optionsHtml);
|
||||
context.JK.dropdown($audioOutput);
|
||||
|
||||
initializeAudioOutputChanged();
|
||||
}
|
||||
|
||||
function initializeFramesize() {
|
||||
context.JK.dropdown($frameSize);
|
||||
}
|
||||
|
||||
function initializeBuffers() {
|
||||
context.JK.dropdown($bufferIn);
|
||||
context.JK.dropdown($bufferOut);
|
||||
}
|
||||
|
||||
|
||||
// reloads the backend's channel state for the currently selected audio devices,
|
||||
// and update's the UI accordingly
|
||||
function initializeChannels() {
|
||||
musicPorts = jamClient.FTUEGetChannels();
|
||||
console.log("musicPorts: %o", JSON.stringify(musicPorts));
|
||||
|
||||
initializeInputPorts(musicPorts);
|
||||
initializeOutputPorts(musicPorts);
|
||||
}
|
||||
|
||||
// during this phase of the FTUE, we have to assign selected input channels
|
||||
// to tracks. The user, however, does not have a way to indicate which channel
|
||||
// goes to which track (that's not until the next step of the wizard).
|
||||
// so, we just auto-generate a valid assignment
|
||||
function newInputAssignment() {
|
||||
var assigned = 0;
|
||||
context._.each(musicPorts.inputs, function(inputChannel) {
|
||||
if(isChannelAssigned(inputChannel)) {
|
||||
assigned += 1;
|
||||
}
|
||||
});
|
||||
|
||||
var newAssignment = Math.floor(assigned / 2) + 1;
|
||||
return newAssignment;
|
||||
}
|
||||
|
||||
function inputChannelChanged() {
|
||||
if(iCheckIgnore) return;
|
||||
|
||||
var $checkbox = $(this);
|
||||
var channelId = $checkbox.attr('data-id');
|
||||
var isChecked = $checkbox.is(':checked');
|
||||
|
||||
if(isChecked) {
|
||||
var newAssignment = newInputAssignment();
|
||||
logger.debug("assigning input channel %o to track: %o", channelId, newAssignment);
|
||||
context.jamClient.TrackSetAssignment(channelId, true, newAssignment);
|
||||
}
|
||||
else {
|
||||
logger.debug("unassigning input channel %o", channelId);
|
||||
context.jamClient.TrackSetAssignment(channelId, true, ASSIGNMENT.UNASSIGNED);
|
||||
// unassigning creates a hole in our auto-assigned tracks. reassign them all to keep it consistent
|
||||
var $assignedInputs = $inputChannels.find('input[type="checkbox"]:checked');
|
||||
var assigned = 0;
|
||||
context._.each($assignedInputs, function(assignedInput) {
|
||||
var $assignedInput = $(assignedInput);
|
||||
var assignedChannelId = $assignedInput.attr('data-id');
|
||||
var newAssignment = Math.floor(assigned / 2) + 1;
|
||||
logger.debug("re-assigning input channel %o to track: %o", assignedChannelId, newAssignment);
|
||||
context.jamClient.TrackSetAssignment(assignedChannelId, true, newAssignment);
|
||||
assigned += 1;
|
||||
});
|
||||
}
|
||||
|
||||
initializeChannels();
|
||||
}
|
||||
|
||||
// should be called in a ifChanged callback if you want to cancel.
|
||||
// you have to use this instead of 'return false' like a typical input 'change' event.
|
||||
function cancelICheckChange($checkbox) {
|
||||
iCheckIgnore = true;
|
||||
var checked = $checkbox.is(':checked');
|
||||
setTimeout(function() {
|
||||
if(checked) $checkbox.iCheck('uncheck').removeAttr('checked');
|
||||
else $checkbox.iCheck('check').attr('checked', 'checked');
|
||||
iCheckIgnore = false;
|
||||
}, 1);
|
||||
}
|
||||
|
||||
function outputChannelChanged() {
|
||||
if(iCheckIgnore) return;
|
||||
var $checkbox = $(this);
|
||||
var channelId = $checkbox.attr('data-id');
|
||||
var isChecked = $checkbox.is(':checked');
|
||||
|
||||
// don't allow more than 2 output channels selected at once
|
||||
if($outputChannels.find('input[type="checkbox"]:checked').length > 2) {
|
||||
context.JK.Banner.showAlert('You can only have a maximum of 2 output ports selected.');
|
||||
// can't allow uncheck of last output
|
||||
cancelICheckChange($checkbox);
|
||||
return;
|
||||
}
|
||||
|
||||
if(isChecked) {
|
||||
logger.debug("assigning output channel %o", channelId);
|
||||
context.jamClient.TrackSetAssignment(channelId, true, ASSIGNMENT.OUTPUT);
|
||||
}
|
||||
else {
|
||||
logger.debug("unassigning output channel %o", channelId);
|
||||
context.jamClient.TrackSetAssignment(channelId, true, ASSIGNMENT.UNASSIGNED);
|
||||
}
|
||||
|
||||
initializeChannels();
|
||||
}
|
||||
|
||||
// checks if it's an assigned OUTPUT or ASSIGNED CHAT
|
||||
function isChannelAssigned(channel) {
|
||||
return channel.assignment == ASSIGNMENT.CHAT || channel.assignment == ASSIGNMENT.OUTPUT || channel.assignment > 0;
|
||||
}
|
||||
|
||||
function initializeInputPorts(musicPorts) {
|
||||
$inputChannels.empty();
|
||||
var inputPorts = musicPorts.inputs;
|
||||
context._.each(inputPorts, function(inputChannel) {
|
||||
var $inputChannel = $(context._.template($templateAudioPort.html(), inputChannel, { variable: 'data' }));
|
||||
var $checkbox = $inputChannel.find('input');
|
||||
if(isChannelAssigned(inputChannel)) {
|
||||
$checkbox.attr('checked', 'checked');
|
||||
}
|
||||
context.JK.checkbox($checkbox);
|
||||
$checkbox.on('ifChanged', inputChannelChanged);
|
||||
$inputChannels.append($inputChannel);
|
||||
});
|
||||
}
|
||||
|
||||
function initializeOutputPorts(musicPorts) {
|
||||
$outputChannels.empty();
|
||||
var outputChannels = musicPorts.outputs;
|
||||
context._.each(outputChannels, function(outputChannel) {
|
||||
var $outputPort = $(context._.template($templateAudioPort.html(), outputChannel, { variable: 'data' }));
|
||||
var $checkbox = $outputPort.find('input');
|
||||
if(isChannelAssigned(outputChannel)) {
|
||||
$checkbox.attr('checked', 'checked');
|
||||
}
|
||||
context.JK.checkbox($checkbox);
|
||||
$checkbox.on('ifChanged', outputChannelChanged);
|
||||
$outputChannels.append($outputPort);
|
||||
});
|
||||
}
|
||||
|
||||
function initializeFormElements() {
|
||||
if (!deviceInformation) throw "devices are not initialized";
|
||||
|
||||
initializeAudioInput();
|
||||
initializeAudioOutput();
|
||||
initializeFramesize();
|
||||
initializeBuffers();
|
||||
}
|
||||
|
||||
function resetFrameBuffers() {
|
||||
$frameSize.val('2.5');
|
||||
$bufferIn.val('0');
|
||||
$bufferOut.val('0');
|
||||
}
|
||||
|
||||
function clearInputPorts() {
|
||||
$inputChannels.empty();
|
||||
}
|
||||
|
||||
function clearOutputPorts() {
|
||||
$outputChannels.empty();
|
||||
}
|
||||
|
||||
function resetScoreReport() {
|
||||
$ioRateScore.empty();
|
||||
$ioVarScore.empty();
|
||||
$latencyScore.empty();
|
||||
}
|
||||
|
||||
function renderLatencyScore(latencyValue, latencyClass) {
|
||||
if(latencyValue) {
|
||||
$latencyScore.text(latencyValue + ' ms');
|
||||
}
|
||||
else {
|
||||
$latencyScore.text('');
|
||||
}
|
||||
$latencyScoreSection.removeClass('good acceptable bad unknown starting').addClass(latencyClass);
|
||||
}
|
||||
|
||||
// std deviation is the worst value between in/out
|
||||
// media is the worst value between in/out
|
||||
// io is the value returned by the backend, which has more info
|
||||
// ioClass is the pre-computed rollup class describing the result in simple terms of 'good', 'acceptable', bad'
|
||||
function renderIOScore(std, median, ioData, ioClass) {
|
||||
$ioRateScore.text(median ? median : '');
|
||||
$ioVarScore.text(std ? std : '');
|
||||
$ioScoreSection.removeClass('good acceptable bad unknown starting skip').addClass(ioClass);
|
||||
// TODO: show help bubble of all data in IO data
|
||||
}
|
||||
|
||||
function updateScoreReport(latencyResult) {
|
||||
var latencyClass = "neutral";
|
||||
var latencyValue = 'N/A';
|
||||
var validLatency = false;
|
||||
if (latencyResult && latencyResult.latencyknown) {
|
||||
var latencyValue = latencyResult.latency;
|
||||
latencyValue = Math.round(latencyValue * 100) / 100;
|
||||
if (latencyValue <= 10) {
|
||||
latencyClass = "good";
|
||||
validLatency = true;
|
||||
} else if (latencyValue <= 20) {
|
||||
latencyClass = "acceptable";
|
||||
validLatency = true;
|
||||
} else {
|
||||
latencyClass = "bad";
|
||||
}
|
||||
}
|
||||
else {
|
||||
latencyClass = 'unknown';
|
||||
}
|
||||
|
||||
validLatencyScore = validLatency;
|
||||
|
||||
renderLatencyScore(latencyValue, latencyClass);
|
||||
}
|
||||
|
||||
function audioInputDeviceUnselected() {
|
||||
validLatencyScore = false;
|
||||
initializeNextButtonState();
|
||||
resetFrameBuffers();
|
||||
clearInputPorts();
|
||||
}
|
||||
|
||||
function renderScoringStarted() {
|
||||
validLatencyScore = false;
|
||||
initializeNextButtonState();
|
||||
resetScoreReport();
|
||||
freezeAudioInteraction();
|
||||
renderLatencyScore(null, 'starting');
|
||||
}
|
||||
|
||||
function renderScoringStopped() {
|
||||
initializeNextButtonState();
|
||||
unfreezeAudioInteraction();
|
||||
}
|
||||
|
||||
|
||||
function freezeAudioInteraction() {
|
||||
$audioInput.attr("disabled", "disabled").easyDropDown('disable');
|
||||
$audioOutput.attr("disabled", "disabled").easyDropDown('disable');
|
||||
$frameSize.attr("disabled", "disabled").easyDropDown('disable');
|
||||
$bufferIn.attr("disabled", "disabled").easyDropDown('disable');
|
||||
$bufferOut.attr("disabled", "disabled").easyDropDown('disable');
|
||||
$asioControlPanelBtn.on("click", false);
|
||||
$resyncBtn.on('click', false);
|
||||
iCheckIgnore = true;
|
||||
$inputChannels.find('input[type="checkbox"]').iCheck('disable');
|
||||
$outputChannels.find('input[type="checkbox"]').iCheck('disable');
|
||||
}
|
||||
|
||||
function unfreezeAudioInteraction() {
|
||||
$audioInput.removeAttr("disabled").easyDropDown('enable');
|
||||
$audioOutput.removeAttr("disabled").easyDropDown('enable');
|
||||
$frameSize.removeAttr("disabled").easyDropDown('enable');
|
||||
$bufferIn.removeAttr("disabled").easyDropDown('enable');
|
||||
$bufferOut.removeAttr("disabled").easyDropDown('enable');
|
||||
$asioControlPanelBtn.off("click", false);
|
||||
$resyncBtn.off('click', false);
|
||||
$inputChannels.find('input[type="checkbox"]').iCheck('enable');
|
||||
$outputChannels.find('input[type="checkbox"]').iCheck('enable');
|
||||
iCheckIgnore = false;
|
||||
}
|
||||
|
||||
// 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";
|
||||
setSaveButtonState($saveButton, true);
|
||||
} else if (latency.latency <= 20) {
|
||||
latencyClass = "acceptable";
|
||||
setSaveButtonState($saveButton, true);
|
||||
} else {
|
||||
latencyClass = "bad";
|
||||
setSaveButtonState($saveButton, false);
|
||||
}
|
||||
} else {
|
||||
latencyClass = "unknown";
|
||||
setSaveButtonState($saveButton, false);
|
||||
}
|
||||
|
||||
$('.ms-label', $report).html(latencyValue);
|
||||
$('p', $report).html('milliseconds');
|
||||
|
||||
$report.removeClass('good acceptable bad unknown');
|
||||
$report.addClass(latencyClass);
|
||||
|
||||
var instructionClasses = ['neutral', 'good', 'acceptable', 'unknown', 'bad', 'start', 'loading'];
|
||||
$.each(instructionClasses, function (idx, val) {
|
||||
$('p.' + val, $instructions).hide();
|
||||
});
|
||||
if (latency === 'loading') {
|
||||
$('p.loading', $instructions).show();
|
||||
} else {
|
||||
$('p.' + latencyClass, $instructions).show();
|
||||
renderStopNewFtueLatencyTesting();
|
||||
}
|
||||
}
|
||||
|
||||
function initializeWatchVideo() {
|
||||
$watchVideoInput.unbind('click').click(function () {
|
||||
|
||||
var audioDevice = findDevice(selectedAudioInput());
|
||||
if (!audioDevice) {
|
||||
context.JK.Banner.showAlert('You must first choose an Audio Input Device so that we can determine which video to show you.');
|
||||
}
|
||||
else {
|
||||
var videoURL = audioDeviceBehavior[audioDevice.type].videoURL;
|
||||
|
||||
if (videoURL) {
|
||||
$(this).attr('href', videoURL);
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
context.JK.Banner.showAlert('No help video for this type of device (' + audioDevice.displayType + ')');
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
$watchVideoOutput.unbind('click').click(function () {
|
||||
|
||||
var audioDevice = findDevice(selectedAudioOutput());
|
||||
if (!audioDevice) {
|
||||
throw "this button should be hidden";
|
||||
}
|
||||
else {
|
||||
var videoURL = audioDeviceBehavior[audioDevice.type].videoURL;
|
||||
|
||||
if (videoURL) {
|
||||
$(this).attr('href', videoURL);
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
context.JK.Banner.showAlert('No help video for this type of device (' + audioDevice.displayType + ')');
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
function renderIOScoringStarted(secondsLeft) {
|
||||
$ioCountdownSecs.text(secondsLeft);
|
||||
$ioCountdown.show();
|
||||
}
|
||||
|
||||
function renderIOScoringStopped() {
|
||||
$ioCountdown.hide();
|
||||
}
|
||||
|
||||
function renderIOCountdown(secondsLeft) {
|
||||
$ioCountdownSecs.text(secondsLeft);
|
||||
}
|
||||
|
||||
function attemptScore() {
|
||||
var audioInputDeviceId = selectedAudioInput();
|
||||
var audioOutputDeviceId = selectedAudioOutput();
|
||||
if (!audioInputDeviceId) {
|
||||
audioInputDeviceUnselected();
|
||||
return false;
|
||||
}
|
||||
|
||||
var audioInputDevice = findDevice(audioInputDeviceId);
|
||||
if (!audioInputDevice) {
|
||||
context.JK.alertSupportedNeeded('Unable to find information for input device: ' + audioInputDeviceId);
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!audioOutputDeviceId) {
|
||||
audioOutputDeviceId = audioInputDeviceId;
|
||||
}
|
||||
var audioOutputDevice = findDevice(audioOutputDeviceId);
|
||||
if (!audioInputDevice) {
|
||||
context.JK.alertSupportedNeeded('Unable to find information for output device: ' + audioOutputDeviceId);
|
||||
return false;
|
||||
}
|
||||
|
||||
jamClient.FTUESetInputMusicDevice(audioInputDeviceId);
|
||||
jamClient.FTUESetOutputMusicDevice(audioOutputDeviceId);
|
||||
|
||||
initializeChannels();
|
||||
|
||||
jamClient.FTUESetInputLatency(selectedBufferIn());
|
||||
jamClient.FTUESetOutputLatency(selectedBufferOut());
|
||||
jamClient.FTUESetFrameSize(selectedFramesize());
|
||||
|
||||
renderScoringStarted();
|
||||
logger.debug("Calling FTUESave(false)");
|
||||
jamClient.FTUESave(false);
|
||||
|
||||
var latency = jamClient.FTUEGetExpectedLatency();
|
||||
console.log("FTUEGetExpectedLatency: %o", latency);
|
||||
|
||||
updateScoreReport(latency);
|
||||
|
||||
// if there was a valid latency score, go on to the next step
|
||||
if(validLatencyScore) {
|
||||
renderIOScore(null, null, null, 'starting');
|
||||
var testTimeSeconds = 10; // allow 10 seconds for IO to establish itself
|
||||
context.jamClient.FTUEStartIoPerfTest();
|
||||
renderIOScoringStarted(testTimeSeconds);
|
||||
renderIOCountdown(testTimeSeconds);
|
||||
var interval = setInterval(function() {
|
||||
testTimeSeconds -= 1;
|
||||
renderIOCountdown(testTimeSeconds);
|
||||
if(testTimeSeconds == 0) {
|
||||
clearInterval(interval);
|
||||
renderIOScoringStopped();
|
||||
var io = context.jamClient.FTUEGetIoPerfData();
|
||||
|
||||
console.log("io: ", io);
|
||||
|
||||
// take the higher variance, which is apparently actually std dev
|
||||
var std = io.in_var > io.out_var ? io.in_var : io.out_var;
|
||||
std = Math.round(std * 100) / 100;
|
||||
// take the furthest-off-from-target io rate
|
||||
var median = Math.abs(io.in_median - io.in_target ) > Math.abs(io.out_median - io.out_target ) ? [io.in_median, io.in_target] : [io.out_median, io.out_target];
|
||||
var medianTarget = median[1];
|
||||
median = Math.round(median[0]);
|
||||
|
||||
var stdIOClass = 'bad';
|
||||
if(std <= 0.50) {
|
||||
stdIOClass = 'good';
|
||||
}
|
||||
else if(std <= 1.00) {
|
||||
stdIOClass = 'acceptable';
|
||||
}
|
||||
|
||||
var medianIOClass = 'bad';
|
||||
if(Math.abs(median - medianTarget) <= 1) {
|
||||
medianIOClass = 'good';
|
||||
}
|
||||
else if(Math.abs(median - medianTarget) <= 2) {
|
||||
medianIOClass = 'acceptable';
|
||||
}
|
||||
|
||||
// now base the overall IO score based on both values.
|
||||
renderIOScore(std, median, io, ioClass);
|
||||
|
||||
// lie for now until IO questions finalize
|
||||
validIOScore = true;
|
||||
|
||||
renderScoringStopped();
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
else {
|
||||
renderIOScore(null, null, null, 'skip');
|
||||
renderScoringStopped();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function initializeAudioInputChanged() {
|
||||
$audioInput.unbind('change').change(attemptScore);
|
||||
}
|
||||
|
||||
function initializeAudioOutputChanged() {
|
||||
|
||||
}
|
||||
|
||||
function initializeStep() {
|
||||
loadDevices();
|
||||
initializeFormElements();
|
||||
initializeNextButtonState();
|
||||
initializeWatchVideo();
|
||||
}
|
||||
|
||||
initializeStep();
|
||||
}
|
||||
|
||||
function beforeSelectTracks() {
|
||||
|
||||
}
|
||||
|
||||
function beforeSelectChat() {
|
||||
|
||||
}
|
||||
|
||||
function beforeDirectMonitor() {
|
||||
|
||||
}
|
||||
|
||||
function beforeTestNetwork() {
|
||||
|
||||
}
|
||||
|
||||
function beforeSuccess() {
|
||||
|
||||
}
|
||||
|
||||
var STEPS = {
|
||||
0: {
|
||||
beforeShow: beforeShowIntro
|
||||
},
|
||||
1: {
|
||||
beforeShow: beforeSelectDevice
|
||||
},
|
||||
2: {
|
||||
beforeShow: beforeSelectTracks
|
||||
},
|
||||
3: {
|
||||
beforeShow: beforeSelectChat
|
||||
},
|
||||
4: {
|
||||
beforeShow: beforeDirectMonitor
|
||||
},
|
||||
5: {
|
||||
beforeShow: beforeTestNetwork
|
||||
},
|
||||
6: {
|
||||
beforeShow: beforeSuccess
|
||||
}
|
||||
}
|
||||
|
||||
function beforeShowStep($step) {
|
||||
var stepInfo = STEPS[step];
|
||||
|
||||
if (!stepInfo) {
|
||||
throw "unknown step: " + step;
|
||||
}
|
||||
|
||||
stepInfo.beforeShow.call(self);
|
||||
}
|
||||
|
||||
function moveToStep() {
|
||||
var $nextWizardStep = $wizardSteps.filter($('[layout-wizard-step=' + step + ']'));
|
||||
|
||||
$wizardSteps.hide();
|
||||
|
||||
$currentWizardStep = $nextWizardStep;
|
||||
|
||||
var $ftueSteps = $(context._.template($templateSteps.html(), {}, { variable: 'data' }));
|
||||
var $activeStep = $ftueSteps.find('.ftue-stepnumber[data-step-number="' + step + '"]');
|
||||
$activeStep.addClass('.active');
|
||||
$activeStep.next().show(); // show the .ftue-step-title
|
||||
$currentWizardStep.find('.ftuesteps').replaceWith($ftueSteps);
|
||||
beforeShowStep($currentWizardStep);
|
||||
$currentWizardStep.show();
|
||||
|
||||
// update buttons
|
||||
var $ftueButtonsContent = $(context._.template($templateButtons.html(), {}, {variable: 'data'}));
|
||||
|
||||
|
||||
var $btnBack = $ftueButtonsContent.find('.btn-back');
|
||||
var $btnNext = $ftueButtonsContent.find('.btn-next');
|
||||
var $btnClose = $ftueButtonsContent.find('.btn-close');
|
||||
var $btnCancel = $ftueButtonsContent.find('.btn-cancel');
|
||||
|
||||
// hide back button if 1st step or last step
|
||||
if (step == 0 && step == TOTAL_STEPS - 1) {
|
||||
$btnBack.hide();
|
||||
}
|
||||
// hide next button if not on last step
|
||||
if (step == TOTAL_STEPS - 1) {
|
||||
$btnNext.hide();
|
||||
}
|
||||
// hide close if on last step
|
||||
if (step != TOTAL_STEPS - 1) {
|
||||
$btnClose.hide();
|
||||
}
|
||||
// hide cancel if not on last step
|
||||
if (step == TOTAL_STEPS - 1) {
|
||||
$btnCancel.hide();
|
||||
}
|
||||
|
||||
$btnNext.on('click', next);
|
||||
$btnBack.on('click', back);
|
||||
$btnClose.on('click', closeDialog);
|
||||
$btnCancel.on('click', closeDialog);
|
||||
|
||||
$ftueButtons.empty();
|
||||
$ftueButtons.append($ftueButtonsContent);
|
||||
}
|
||||
|
||||
function reset() {
|
||||
$currentWizardStep = null;
|
||||
}
|
||||
|
||||
// checks if we already have a profile called 'FTUE...'; if not, create one. if so, re-use it.
|
||||
function findOrCreateFTUEProfile() {
|
||||
var profileName = context.jamClient.FTUEGetMusicProfileName();
|
||||
|
||||
logger.debug("current profile name: " + profileName);
|
||||
|
||||
if(profileName && profileName.indexOf('FTUE') == 0) {
|
||||
|
||||
}
|
||||
else {
|
||||
var newProfileName = 'FTUEAttempt-' + new Date().getTime().toString();
|
||||
logger.debug("setting FTUE-prefixed profile name to: " + newProfileName);
|
||||
context.jamClient.FTUESetMusicProfileName(newProfileName);
|
||||
}
|
||||
|
||||
var profileName = context.jamClient.FTUEGetMusicProfileName();
|
||||
|
||||
logger.debug("name on exit: " + profileName);
|
||||
|
||||
}
|
||||
|
||||
function beforeShow(args) {
|
||||
context.jamClient.FTUECancel();
|
||||
findOrCreateFTUEProfile();
|
||||
|
||||
step = args.d1;
|
||||
if (!step) step = 0;
|
||||
step = parseInt(step);
|
||||
moveToStep();
|
||||
}
|
||||
|
||||
function afterShow() {
|
||||
|
||||
}
|
||||
|
||||
function afterHide() {
|
||||
context.jamClient.FTUECancel();
|
||||
}
|
||||
|
||||
function back() {
|
||||
if ($(this).is('.button-grey')) return;
|
||||
step = step - 1;
|
||||
moveToStep();
|
||||
return false;
|
||||
}
|
||||
|
||||
function next() {
|
||||
if ($(this).is('.button-grey')) return;
|
||||
|
||||
step = step + 1;
|
||||
|
||||
moveToStep();
|
||||
return false;
|
||||
}
|
||||
|
||||
function closeDialog() {
|
||||
app.layout.closeDialog('gear-wizard');
|
||||
return false;
|
||||
}
|
||||
|
||||
function events() {
|
||||
}
|
||||
|
||||
function route() {
|
||||
|
||||
}
|
||||
|
||||
function initialize() {
|
||||
|
||||
var dialogBindings = { beforeShow: beforeShow, afterShow: afterShow, afterHide: afterHide };
|
||||
|
||||
app.bindDialog('gear-wizard', dialogBindings);
|
||||
|
||||
$dialog = $('#gear-wizard-dialog');
|
||||
$wizardSteps = $dialog.find('.wizard-step');
|
||||
$templateSteps = $('#template-ftuesteps');
|
||||
$templateButtons = $('#template-ftue-buttons');
|
||||
$templateAudioPort = $('#template-audio-port');
|
||||
$ftueButtons = $dialog.find('.ftue-buttons');
|
||||
|
||||
operatingSystem = context.jamClient.GetOSAsString();
|
||||
|
||||
events();
|
||||
}
|
||||
|
||||
this.initialize = initialize;
|
||||
|
||||
self = this;
|
||||
return this;
|
||||
};
|
||||
|
||||
})(window, jQuery);
|
||||
|
|
@ -115,6 +115,7 @@
|
|||
|
||||
&.list.ports {
|
||||
height:100px;
|
||||
overflow:auto;
|
||||
}
|
||||
|
||||
&.instructions {
|
||||
|
|
@ -151,18 +152,32 @@
|
|||
margin-bottom:20px;
|
||||
}
|
||||
|
||||
.asio-settings-btn, .resync-btn {
|
||||
.asio-settings-input-btn, .asio-settings-output-btn, .resync-btn {
|
||||
width:80%;
|
||||
display:inline-block;
|
||||
text-align:center;
|
||||
}
|
||||
|
||||
.asio-settings-btn {
|
||||
.asio-settings-input-btn, .asio-settings-output-btn {
|
||||
margin-top:10px;
|
||||
}
|
||||
|
||||
|
||||
.asio-settings-input-btn {
|
||||
display:none;
|
||||
}
|
||||
|
||||
.asio-settings-output-btn {
|
||||
display:none;
|
||||
}
|
||||
|
||||
.resync-btn {
|
||||
margin-top:10px;
|
||||
visibility:hidden;
|
||||
}
|
||||
|
||||
.frame-and-buffers {
|
||||
display:none;
|
||||
}
|
||||
|
||||
.framesize {
|
||||
|
|
@ -197,12 +212,24 @@
|
|||
}
|
||||
}
|
||||
|
||||
.audio-port {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.audio-channels {
|
||||
margin-top:15px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.ftue-box.results {
|
||||
height: 230px !important;
|
||||
padding:0;
|
||||
|
||||
.io, .latency {
|
||||
display:none;
|
||||
}
|
||||
|
||||
.scoring-section {
|
||||
font-size:15px;
|
||||
@include border_box_sizing;
|
||||
|
|
@ -220,6 +247,11 @@
|
|||
&.unknown {
|
||||
background-color:#999;
|
||||
}
|
||||
&.skip {
|
||||
.io-skip-msg {
|
||||
display:inline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.io-countdown {
|
||||
|
|
@ -236,9 +268,48 @@
|
|||
|
||||
.io-skip-msg {
|
||||
display:none;
|
||||
}
|
||||
|
||||
.scoring-section.skip & {
|
||||
display:inline;
|
||||
.io-rate {
|
||||
display:none;
|
||||
}
|
||||
.io-var {
|
||||
display:none;
|
||||
}
|
||||
|
||||
ul.results-text {
|
||||
padding:10px 8px;
|
||||
|
||||
li {
|
||||
display:none
|
||||
}
|
||||
|
||||
&[latency-score="good"] li.latency-good {
|
||||
display:list-item;
|
||||
}
|
||||
&[latency-score="acceptable"] li.latency-acceptable {
|
||||
display:list-item;
|
||||
}
|
||||
&[latency-score="bad"] li.latency-bad {
|
||||
display:list-item;
|
||||
}
|
||||
&[io-var-score="good"] li.io-var-good {
|
||||
display:list-item;
|
||||
}
|
||||
&[io-var-score="acceptable"] li.io-var-acceptable {
|
||||
display:list-item;
|
||||
}
|
||||
&[io-var-score="bad"] li.io-var-bad {
|
||||
display:list-item;
|
||||
}
|
||||
&[io-rate-score="good"] li.io-rate-good {
|
||||
display:list-item;
|
||||
}
|
||||
&[io-rate-score="acceptable"] li.io-rate-acceptable {
|
||||
display:list-item;
|
||||
}
|
||||
&[io-rate-score="bad"] li.io-rate-bad {
|
||||
display:list-item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,15 +37,16 @@
|
|||
%h2 Audio Input Device
|
||||
%select.w100.select-audio-input-device
|
||||
%option None
|
||||
%h2 Audio Input Ports
|
||||
%h2.audio-channels Audio Input Ports
|
||||
.ftue-box.list.ports.input-ports
|
||||
%a.button-orange.asio-settings-btn ASIO SETTINGS...
|
||||
%a.button-orange.asio-settings-input-btn ASIO SETTINGS...
|
||||
%a.button-orange.asio-settings-output-btn ASIO SETTINGS...
|
||||
%a.button-orange.resync-btn RESYNC
|
||||
.wizard-step-column
|
||||
%h2 Audio Output Device
|
||||
%select.w100.select-audio-output-device
|
||||
%option Same as input
|
||||
%h2 Audio Output Ports
|
||||
%h2.audio-channels Audio Output Ports
|
||||
.ftue-box.list.ports.output-ports
|
||||
.frame-and-buffers
|
||||
.framesize
|
||||
|
|
@ -91,13 +92,27 @@
|
|||
.p5
|
||||
.io I/O
|
||||
%span.io-skip-msg
|
||||
Skipped
|
||||
Not Tested
|
||||
%span.io-countdown
|
||||
%span.secs
|
||||
seconds left
|
||||
%span.io-rate-score
|
||||
%span.io-var-score
|
||||
|
||||
%span.io-rate<
|
||||
Rate=
|
||||
%span.io-rate-score>
|
||||
%span.io-var<
|
||||
Var=
|
||||
%span.io-var-score>
|
||||
.clearall
|
||||
%ul.results-text
|
||||
%li.latency-good Your latency is good.
|
||||
%li.latency-acceptable Your latency is acceptable.
|
||||
%li.latency-bad Your latency is poor.
|
||||
%li.io-rate-good Your I/O rate is good.
|
||||
%li.io-rate-acceptable Your I/O rate is acceptable.
|
||||
%li.io-rate-bad Your I/O rate is poor.
|
||||
%li.io-var-good Your I/O variance is good.
|
||||
%li.io-var-acceptable Your I/O variance is acceptable.
|
||||
%li.io-var-bad Your I/O variance is poor.
|
||||
.clearall
|
||||
|
||||
.wizard-step{ 'layout-wizard-step' => "2", 'dialog-title' => "Configure Tracks", 'dialog-purpose' => "ConfigureTracks" }
|
||||
|
|
|
|||
Loading…
Reference in New Issue