You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
316 lines
8.4 KiB
316 lines
8.4 KiB
/* 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();
|
|
}
|