jam-cloud/app/assets/javascripts/ftue.js

442 lines
18 KiB
JavaScript

/**
* FtueAudioSelectionScreen
* Javascript that goes with the screen where initial
* selection of the audio devices takes place.
* Corresponds to /#ftue2
*/
(function(context,$) {
"use strict";
context.JK = context.JK || {};
context.JK.FtueWizard = function(app) {
context.JK.FtueWizard.latencyTimeout = true;
context.JK.FtueWizard.latencyMS = Number.MAX_VALUE;
var logger = context.JK.logger;
var jamClient = context.jamClient;
var win32 = true;
var deviceSetMap = {
'audio-input': jamClient.FTUESetMusicInput,
'audio-output': jamClient.FTUESetMusicOutput,
'voice-chat-input': jamClient.FTUESetChatInput
};
var faderMap = {
'ftue-audio-input-fader': jamClient.FTUESetInputVolume,
'ftue-voice-input-fader': jamClient.FTUESetChatInputVolume,
'ftue-audio-output-fader': jamClient.FTUESetOutputVolume
};
function latencyTimeoutCheck() {
if (context.JK.FtueWizard.latencyTimeout) {
jamClient.FTUERegisterLatencyCallback('');
context.JK.app.setWizardStep("5");
}
}
function afterHide(data) {
// Unsubscribe from FTUE VU callbacks.
jamClient.FTUERegisterVUCallbacks('','','');
}
function beforeShow(data) {
var vuMeters = [
'#ftue-audio-input-vu-left',
'#ftue-audio-input-vu-right',
'#ftue-voice-input-vu-left',
'#ftue-voice-input-vu-right',
'#ftue-audio-output-vu-left',
'#ftue-audio-output-vu-right'
];
$.each(vuMeters, function() {
context.JK.VuHelpers.renderVU(this,
{vuType:"horizontal", lightCount: 12, lightWidth: 15, lightHeight: 3});
});
var faders = context._.keys(faderMap);
$.each(faders, function() {
var fid = this;
context.JK.FaderHelpers.renderFader('#' + fid,
{faderId: fid, faderType:"horizontal", width:163});
});
context.JK.FaderHelpers.subscribe('ftue', faderChange);
}
function afterShow(data) {}
function faderChange(faderId, newValue, dragging) {
var setFunction = faderMap[faderId];
// TODO - using hardcoded range of -80 to 20 for output levels.
var mixerLevel = newValue - 80; // Convert our [0-100] to [-80 - +20] range
setFunction(mixerLevel);
}
function settingsInit() {
jamClient.FTUEInit();
setLevels(0);
}
function setLevels(db) {
if (db < -80 || db > 20) {
throw ("BUG! ftue.js:setLevels db arg must be between -80 and 20");
}
var trackIds = jamClient.SessionGetIDs();
var controlStates = jamClient.SessionGetControlState(trackIds);
$.each(controlStates, function(index, value) {
context.JK.Mixer.fillTrackVolume(value, false);
// Default input/output to 0 DB
context.trackVolumeObject.volL = db;
context.trackVolumeObject.volR = db;
jamClient.SessionSetControlState(trackIds[index]);
});
$.each(context._.keys(faderMap), function(index, faderId) {
// faderChange takes a value from 0-100
var $fader = $('[fader-id="' + faderId + '"]');
context.JK.FaderHelpers.setHandlePosition($fader, db + 80);
});
}
function testComplete() {
logger.debug("Test complete");
var latencyMS = context.JK.FtueWizard.latencyMS;
if (latencyMS <= 20) {
logger.debug(latencyMS + " is <= 20. Setting FTUE status to true");
ftueSave(true); // Save the profile
context.jamClient.FTUESetStatus(true); // No FTUE wizard next time
}
updateGauge();
}
function updateGauge() {
var latencyMS = context.JK.FtueWizard.latencyMS;
// Round to 2 decimal places
latencyMS = (Math.round(latencyMS * 100)) / 100;
logger.debug("Latency Value: " + latencyMS);
if (latencyMS > 20) {
$('#ftue-latency-congrats').hide();
$('#ftue-latency-fail').show();
} else {
$('#ftue-latency-ms').html(latencyMS);
$('#ftue-latency-congrats').show();
$('#ftue-latency-fail').hide();
if (latencyMS <= 10) {
$('[layout-wizard-step="6"] .btnHelp').hide();
$('[layout-wizard-step="6"] .btnRepeat').hide();
}
}
setNeedleValue(latencyMS);
}
// Function to calculate an absolute value and an absolute value range into
// a number of degrees on a circualar "speedometer" gauge. The 0 degrees value
// points straight up to the middle of the real-world value range.
// Arguments:
// value: The real-world value (e.g. 20 milliseconds)
// minValue: The real-world minimum value (e.g. 0 milliseconds)
// maxValue: The real-world maximum value (e.g. 40 milliseconds)
// degreesUsed: The number of degrees used to represent the full real-world
// range. 360 would be the entire circle. 270 would be 3/4ths
// of the circle. The unused gap will be at the bottom of the
// gauge.
// (e.g. 300)
function degreesFromRange(value, minValue, maxValue, degreesUsed) {
if (value > maxValue) { value = maxValue; }
if (value < minValue) { value = minValue; }
var range = maxValue-minValue;
var floatVal = value/range;
var degrees = Math.round(floatVal * degreesUsed);
degrees -= Math.round(degreesUsed/2);
if (degrees < 0) {
degrees += 360;
}
return degrees;
}
// Given a number of MS, and assuming the gauge has a range from
// 0 to 40 ms. Update the gauge to the proper value.
function setNeedleValue(ms) {
logger.debug("setNeedleValue: " + ms);
var deg = degreesFromRange(ms, 0, 40, 300);
// Supporting Firefix, Chrome, Safari, Opera and IE9+.
// Should we need to support < IE9, this will need more work
// to compute the matrix transformations in those browsers -
// and I don't believe they support transparent PNG graphic
// rotation, so we'll have to change the needle itself.
var css = {
//"behavior":"url(-ms-transform.htc)",
/* Firefox */
"-moz-transform":"rotate(" + deg + "deg)",
/* Safari and Chrome */
"-webkit-transform":"rotate(" + deg + "deg)",
/* Opera */
"-o-transform":"rotate(" + deg + "deg)",
/* IE9 */
"-ms-transform":"rotate(" + deg + "deg)"
/* IE6,IE7 */
//"filter": "progid:DXImageTransform.Microsoft.Matrix(sizingMethod='auto expand', M11=0.7071067811865476, M12=-0.7071067811865475, M21=0.7071067811865475, M22=0.7071067811865476)",
/* IE8 */
//"-ms-filter": '"progid:DXImageTransform.Microsoft.Matrix(SizingMethod=\'auto expand\', M11=0.7071067811865476, M12=-0.7071067811865475, M21=0.7071067811865475, M22=0.7071067811865476)"'
};
$('#ftue-latency-numerical').html(ms);
$('#ftue-latency-needle').css(css);
}
function testLatency() {
// we'll just register for call back right here and unregister in the callback.
context.JK.FtueWizard.latencyTimeout = true;
var cbFunc = 'JK.ftueLatencyCallback';
logger.debug("Registering latency callback: " + cbFunc);
jamClient.FTUERegisterLatencyCallback('JK.ftueLatencyCallback');
var now = new Date();
logger.debug("Starting Latency Test..." + now);
context.setTimeout(latencyTimeoutCheck, 20000); // Timeout in 20 seconds
jamClient.FTUEStartLatency();
}
function openASIOControlPanel(evt) {
if (win32) {
logger.debug("Calling FTUEOpenControlPanel()");
jamClient.FTUEOpenControlPanel();
}
}
function asioResync(evt) {
jamClient.FTUERefreshDevices();
}
function ftueSave(persist) {
// Explicitly set inputs and outputs to dropdown values
// before save as the client seems to want this on changes to
// things like frame size, etc..
var $audioSelects = $('[layout-wizard-step="2"] .settings-controls select');
$.each($audioSelects, function(index, value) {
var $select = $(value);
setAudioDevice($select);
});
if (musicInAndOutSet()) {
logger.debug("Calling FTUESave(" + persist + ")");
var response = jamClient.FTUESave(persist);
setLevels(0);
if (response) {
context.alert(response);
}
} else {
logger.debug("Aborting FTUESave as we need input + output selected.");
}
}
function setAsioFrameSize(evt) {
var val = parseFloat($(evt.currentTarget).val(),10);
if (isNaN(val)) {
return;
}
logger.debug("Calling FTUESetFrameSize(" + val + ")");
jamClient.FTUESetFrameSize(val);
ftueSave(false);
}
function setAsioInputLatency(evt) {
var val = parseInt($(evt.currentTarget).val(),10);
if (isNaN(val)) {
return;
}
logger.debug("Calling FTUESetInputLatency(" + val + ")");
jamClient.FTUESetInputLatency(val);
ftueSave(false);
}
function setAsioOutputLatency(evt) {
var val = parseInt($(evt.currentTarget).val(),10);
if (isNaN(val)) {
return;
}
logger.debug("Calling FTUESetOutputLatency(" + val + ")");
jamClient.FTUESetOutputLatency(val);
ftueSave(false);
}
function events() {
$('[layout-wizard-step="2"] .settings-driver select').on('change', audioDriverChanged);
$('[layout-wizard-step="2"] .settings-controls select').on('change', audioDeviceChanged);
$('#btn-asio-control-panel').on('click', openASIOControlPanel);
$('#btn-asio-resync').on('click', asioResync);
$('#asio-framesize').on('change', setAsioFrameSize);
$('#asio-input-latency').on('change', setAsioInputLatency);
$('#asio-output-latency').on('change', setAsioOutputLatency);
}
/**
* This function loads the available audio devices from jamClient, and
* builds up the select dropdowns in the audio-settings step of the FTUE wizard.
*/
function loadAudioDevices() {
var funcs = [
jamClient.FTUEGetMusicInputs,
jamClient.FTUEGetChatInputs,
jamClient.FTUEGetMusicOutputs
];
var selectors = [
'[layout-wizard-step="2"] .audio-input select',
'[layout-wizard-step="2"] .voice-chat-input select',
'[layout-wizard-step="2"] .audio-output select'
];
var optionsHtml = '';
var deviceOptionFunc = function(deviceKey, index, list) {
optionsHtml += '<option value="' + deviceKey + '">' +
devices[deviceKey] + '</option>';
};
for (var i=0; i<funcs.length; i++) {
optionsHtml = '<option selected="selected" value="">Choose...</option>';
var devices = funcs[i](); // returns hash of device id: device name
var $select = $(selectors[i]);
$select.empty();
var sortedDeviceKeys = context._.keys(devices).sort();
context._.each(sortedDeviceKeys, deviceOptionFunc);
$select.html(optionsHtml);
$select.removeAttr("disabled");
$('[layout-wizard-step="2"] .settings-asio select').removeAttr("disabled");
// Set selects to lowest possible values to start:
$('#asio-framesize').val('2.5').change();
$('#asio-input-latency').val('-1').change();
$('#asio-output-latency').val('-1').change();
}
}
/**
* Load available drivers and populate the driver select box.
*/
function loadAudioDrivers() {
var drivers = jamClient.FTUEGetDevices();
var driverOptionFunc = function(driverKey, index, list) {
optionsHtml += '<option value="' + driverKey + '">' +
drivers[driverKey] + '</option>';
};
var optionsHtml = '<option selected="selected" value="">Choose...</option>';
var $select = $('[layout-wizard-step="2"] .settings-driver select');
$select.empty();
var sortedDeviceKeys = context._.keys(drivers).sort();
context._.each(sortedDeviceKeys, driverOptionFunc);
$select.html(optionsHtml);
}
function audioDriverChanged(evt) {
var $select = $(evt.currentTarget);
var driverId = $select.val();
jamClient.FTUESetMusicDevice(driverId);
loadAudioDevices();
}
function audioDeviceChanged(evt) {
var $select = $(evt.currentTarget);
setAudioDevice($select);
if (musicInAndOutSet()) {
ftueSave(false);
setVuCallbacks();
}
}
function setAudioDevice($select) {
var device = $select.data('device');
var deviceId = $select.val();
// Note: We always set, even on the "Choose" value of "", which clears
// the current setting.
var setFunction = deviceSetMap[device];
setFunction(deviceId);
}
/**
* Return a boolean indicating whether both the MusicInput
* and MusicOutput devices are set.
*/
function musicInAndOutSet() {
var audioInput = $('[layout-wizard-step="2"] .audio-input select').val();
var audioOutput = $('[layout-wizard-step="2"] .audio-output select').val();
return (audioInput && audioOutput);
}
function setVuCallbacks() {
jamClient.FTUERegisterVUCallbacks(
"JK.ftueAudioOutputVUCallback",
"JK.ftueAudioInputVUCallback",
"JK.ftueChatInputVUCallback"
);
jamClient.SetVURefreshRate(200);
}
function initialize() {
// If not on windows, hide ASIO settings
if (jamClient.GetOSAsString() != "Win32") {
logger.debug("Not on Win32 - modifying UI for Mac/Linux");
win32 = false;
$('[layout-wizard-step="2"] p[os="win32"]').hide();
$('[layout-wizard-step="2"] p[os="mac"]').show();
$('#btn-asio-control-panel').hide();
$('[layout-wizard-step="2"] .settings-controls select').removeAttr("disabled");
loadAudioDevices();
}
events();
var dialogBindings = { 'beforeShow': beforeShow,
'afterShow': afterShow, 'afterHide': afterHide };
app.bindDialog('ftue', dialogBindings);
app.registerWizardStepFunction("2", settingsInit);
app.registerWizardStepFunction("4", testLatency);
app.registerWizardStepFunction("6", testComplete);
loadAudioDrivers();
}
// Expose publics
this.initialize = initialize;
// Expose degreesFromRange outside for testing
this._degreesFromRange = degreesFromRange;
return this;
};
// Common VU updater taking a dbValue (-80 to 20) and a CSS selector for the VU.
context.JK.ftueVUCallback = function(dbValue, selector) {
// Convert DB into a value from 0.0 - 1.0
var floatValue = (dbValue + 80) / 100;
context.JK.VuHelpers.updateVU(selector, floatValue);
};
context.JK.ftueAudioInputVUCallback = function(dbValue) {
context.JK.ftueVUCallback(dbValue, '#ftue-audio-input-vu-left');
context.JK.ftueVUCallback(dbValue, '#ftue-audio-input-vu-right');
};
context.JK.ftueAudioOutputVUCallback = function(dbValue) {
context.JK.ftueVUCallback(dbValue, '#ftue-audio-output-vu-left');
context.JK.ftueVUCallback(dbValue, '#ftue-audio-output-vu-right');
};
context.JK.ftueChatInputVUCallback = function(dbValue) {
context.JK.ftueVUCallback(dbValue, '#ftue-voice-input-vu-left');
context.JK.ftueVUCallback(dbValue, '#ftue-voice-input-vu-right');
};
// Latency Callback
context.JK.ftueLatencyCallback = function(latencyMS) {
// We always show gauge screen if we hit this.
// Clear out the 'timeout' variable.
context.JK.FtueWizard.latencyTimeout = false;
var now = new Date();
context.console.debug("ftueLatencyCallback: " + now);
context.JK.FtueWizard.latencyMS = latencyMS;
// Unregister callback:
context.jamClient.FTUERegisterLatencyCallback('');
// Go to 'congrats' screen -- although latency may be too high.
context.JK.app.setWizardStep("6");
};
})(window,jQuery);