/* 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(); }