* VRFS-1575 - step 2 of FTUE almost done

This commit is contained in:
Seth Call 2014-05-09 13:39:27 -05:00
parent 9b18849544
commit d39f91e186
13 changed files with 1427 additions and 920 deletions

View File

@ -39,3 +39,4 @@
//= require utils
//= require custom_controls
//= require_directory .
//= require_directory ./gear

View File

@ -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;

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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;
}
}
}

View File

@ -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" }