blob: 88f3998436f143bffc2deb69e0bf69c316ec2711 [file] [log] [blame]
/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
var NN = 100; // Total number of points
var FIXES = 25; // Number of fixed points, evenly spaced in the range [0, NN]
var minmax_boxes = []; // The text input boxes for min/max/step
var fix_boxes = []; // The text input boxes for fixed points
window.onload = function() {
init_minmax();
init_fixes();
init_canvas();
};
// Create min/max/step boxes
function init_minmax() {
var table = document.getElementById('minmax');
var names = ['Min:' , 'Max:', 'Step:'];
for (var i = 0; i < names.length; i++) {
var row = table.insertRow(-1);
var col_name = row.insertCell(-1);
var col_box = row.insertCell(-1);
var col_db = row.insertCell(-1);
var box = document.createElement('input');
box.size = 5;
box.className = 'box';
col_name.appendChild(document.createTextNode(names[i]));
col_name.align = 'right';
col_box.appendChild(box);
col_db.appendChild(document.createTextNode('dB'));
minmax_boxes.push(box);
box.oninput = redraw;
}
}
// Create fixed point boxes
function init_fixes() {
var table = document.getElementById('fixes');
for (var i = 0; i <= FIXES; i++) {
var row = table.insertRow(-1);
var col_name = row.insertCell(-1);
var col_box = row.insertCell(-1);
var col_db = row.insertCell(-1);
var box = document.createElement('input');
box.size = 5;
box.className = 'box';
// round fix_pos (the dB value for this fixed point) to one place
// after decimal point.
var fix_pos = Math.round(i * NN * 10 / FIXES) / 10;
col_name.appendChild(document.createTextNode(fix_pos + ':'));
col_name.align = 'right';
col_box.appendChild(box);
col_db.appendChild(document.createTextNode('dB'));
fix_boxes.push(box);
box.oninput = redraw;
}
}
function init_canvas() {
redraw();
}
// Redraw everything on the canvas. This is run every time any input is changed.
function redraw() {
var backgroundColor = 'black';
var gridColor = 'rgb(200,200,200)';
var dotColor = 'rgb(245,245,0)';
var marginLeft = 60;
var marginBottom = 30;
var marginTop = 20;
var marginRight = 30;
var canvas = document.getElementById('curve');
var ctx = canvas.getContext('2d');
var w = 800;
var h = 400;
canvas.width = w + marginLeft + marginRight;
canvas.height = h + marginBottom + marginTop;
ctx.fillStyle = backgroundColor;
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.lineWidth = 1;
ctx.font = '16px sans-serif';
ctx.textAlign = 'center';
// Set up coordinate system
ctx.translate(marginLeft, h + marginTop);
ctx.scale(1, -1);
// Draw two lines at x = 0 and y = 0 which are solid lines
ctx.strokeStyle = gridColor;
ctx.beginPath();
ctx.moveTo(0, h + marginTop / 2);
ctx.lineTo(0, 0);
ctx.lineTo(w + marginRight / 2, 0);
ctx.stroke();
// Draw vertical lines and labels on x axis
ctx.strokeStyle = gridColor;
ctx.fillStyle = gridColor;
ctx.beginPath();
ctx.setLineDash([1, 4]);
for (var i = 0; i <= FIXES; i++) {
var x = i * w / FIXES;
if (i > 0) {
ctx.moveTo(x, 0);
ctx.lineTo(x, h + marginTop / 2);
}
drawText(ctx, Math.round(i * NN * 10 / FIXES) / 10, x, -20, 'center');
}
ctx.stroke();
ctx.setLineDash([]);
// Draw horizontal lines and labels on y axis
var min = parseFloat(minmax_boxes[0].value);
var max = parseFloat(minmax_boxes[1].value);
var step = parseFloat(minmax_boxes[2].value);
// Soundness checks
if (isNaN(min) || isNaN(max) || isNaN(step)) return;
if (min >= max || step <= 0 || (max - min) / step > 10000) return;
// Let s = minimal multiple of step such that
// vdivs = Math.round((max - min) / s) <= 20
var vdivs;
var s = Math.max(1, Math.floor((max - min) / 20 / step)) * step;
while (true) {
var vdivs = Math.round((max - min) / s);
if (vdivs <= 20) break;
s += step;
}
// Scale from v to y is
// y = (v - min) / s * h / vdivs
ctx.strokeStyle = gridColor;
ctx.fillStyle = gridColor;
ctx.beginPath();
ctx.setLineDash([1, 4]);
for (var i = 0;; i++) {
var v = min + s * i;
var y;
if (v <= max) {
y = i * h / vdivs;
} else {
v = max;
y = (max - min) / s * h / vdivs;
}
drawText(ctx, v.toFixed(2), -5 , y - 4, 'right');
if (i > 0) {
ctx.moveTo(0, y);
ctx.lineTo(w + marginRight / 2, y);
}
if (v >= max) break;
}
ctx.stroke();
ctx.setLineDash([]);
// Draw fixed points
ctx.strokeStyle = dotColor;
ctx.fillStyle = dotColor;
for (var i = 0; i <= FIXES; i++) {
var v = getFix(i);
if (isNaN(v)) continue;
var x = i * w / FIXES;
var y = (v - min) / s * h / vdivs;
ctx.beginPath();
ctx.arc(x, y, 4, 0, 2 * Math.PI);
ctx.stroke();
}
// Draw interpolated points
var points = generatePoints();
for (var i = 0; i <= NN; i++) {
var v = points[i];
if (isNaN(v)) continue;
var x = i * w / NN;
var y = (v - min) / s * h / vdivs;
ctx.beginPath();
ctx.arc(x, y, 2, 0, 2 * Math.PI);
ctx.stroke();
ctx.fill();
}
}
// Returns the value of the fixed point with index i
function getFix(i) {
var v = parseFloat(fix_boxes[i].value);
var min = parseFloat(minmax_boxes[0].value);
var max = parseFloat(minmax_boxes[1].value);
if (isNaN(v)) return v;
if (v > max) v = max;
if (v < min) v = min;
return v;
}
// Returns a value quantized to the given min/max/step
function quantize(v) {
var min = parseFloat(minmax_boxes[0].value);
var max = parseFloat(minmax_boxes[1].value);
var step = parseFloat(minmax_boxes[2].value);
v = min + Math.round((v - min) / step) * step;
if (isNaN(v)) return v;
if (v > max) v = max;
if (v < min) v = min;
return v;
}
// Generate points indexed by 0 to NN, using interpolation and quantization
function generatePoints() {
// Go through all points, for each point:
// (1) Find the left fix: the max defined fixed point <= current point
// (2) Find the right fix: the min defined fixed point >= current point
// (3) If both exist, interpolate value for current point
// (4) Otherwise skip current point
// Returns left fix index for current point, or NaN if it does not exist
var find_left = function(current) {
for (i = FIXES; i >= 0; i--) {
var x = NN * i / FIXES;
if (x <= current && !isNaN(getFix(i))) {
return i;
}
}
return NaN;
};
// Returns right fix index for current point, or NaN if it does not exist
var find_right = function(current) {
for (i = 0; i <= FIXES; i++) {
var x = NN * i / FIXES;
if (x >= current && !isNaN(getFix(i))) {
return i;
}
}
return NaN;
};
// Interpolate value for point x
var interpolate = function(x) {
var left = find_left(x);
if (isNaN(left)) return NaN;
var right = find_right(x);
if (isNaN(right)) return NaN;
var xl = NN * left / FIXES;
var xr = NN * right / FIXES;
var yl = getFix(left);
var yr = getFix(right);
if (xl == xr) return yl;
return yl + (yr - yl) * (x - xl) / (xr - xl);
};
var result = [];
for (var x = 0; x <= NN; x++) {
result.push(quantize(interpolate(x)));
}
return result;
}
function drawText(ctx, s, x, y, align) {
ctx.save();
ctx.translate(x, y);
ctx.scale(1, -1);
ctx.textAlign = align;
ctx.fillText(s, 0, 0);
ctx.restore();
}
// The output config file looks like:
//
// [Speaker]
// volume_curve = explicit
// db_at_100 = 0
// db_at_99 = -75
// db_at_98 = -75
// ...
// db_at_1 = -4500
// db_at_0 = -4800
// [Headphone]
// volume_curve = simple_step
// volume_step = 70
// max_volume = 0
//
function download_config() {
var content = '';
content += '[Speaker]\n';
content += ' volume_curve = explicit\n';
var points = generatePoints();
var last = 0;
for (var i = NN; i >= 0; i--) {
var v = points[i];
if (isNaN(points[i])) v = last;
content += ' db_at_' + i + ' = ' + Math.round(v * 100) + '\n';
}
content += '[Headphone]\n';
content += ' volume_curve = simple_step\n';
content += ' volume_step = 70\n';
content += ' max_volume = 0\n';
save_config(content);
}
function save_config(content) {
var a = document.getElementById('save_config_anchor');
var uriContent = 'data:application/octet-stream,' +
encodeURIComponent(content);
a.href = uriContent;
a.download = 'HDA Intel PCH';
a.click();
}