Home | History | Annotate | Download | only in volume_tuning
      1 /* Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
      2  * Use of this source code is governed by a BSD-style license that can be
      3  * found in the LICENSE file.
      4  */
      5 
      6 var NN = 100;    // Total number of points
      7 var FIXES = 25;  // Number of fixed points, evenly spaced in the range [0, NN]
      8 var minmax_boxes = []; // The text input boxes for min/max/step
      9 var fix_boxes = [];  // The text input boxes for fixed points
     10 
     11 window.onload = function() {
     12   init_minmax();
     13   init_fixes();
     14   init_canvas();
     15 };
     16 
     17 // Create min/max/step boxes
     18 function init_minmax() {
     19   var table = document.getElementById('minmax');
     20   var names = ['Min:' , 'Max:', 'Step:'];
     21   for (var i = 0; i < names.length; i++) {
     22     var row = table.insertRow(-1);
     23     var col_name = row.insertCell(-1);
     24     var col_box = row.insertCell(-1);
     25     var col_db = row.insertCell(-1);
     26     var box = document.createElement('input');
     27     box.size = 5;
     28     box.className = 'box';
     29     col_name.appendChild(document.createTextNode(names[i]));
     30     col_name.align = 'right';
     31     col_box.appendChild(box);
     32     col_db.appendChild(document.createTextNode('dB'));
     33     minmax_boxes.push(box);
     34     box.oninput = redraw;
     35   }
     36 }
     37 
     38 // Create fixed point boxes
     39 function init_fixes() {
     40   var table = document.getElementById('fixes');
     41   for (var i = 0; i <= FIXES; i++) {
     42     var row = table.insertRow(-1);
     43     var col_name = row.insertCell(-1);
     44     var col_box = row.insertCell(-1);
     45     var col_db = row.insertCell(-1);
     46     var box = document.createElement('input');
     47     box.size = 5;
     48     box.className = 'box';
     49     // round fix_pos (the dB value for this fixed point) to one place
     50     // after decimal point.
     51     var fix_pos = Math.round(i * NN * 10 / FIXES) / 10;
     52     col_name.appendChild(document.createTextNode(fix_pos + ':'));
     53     col_name.align = 'right';
     54     col_box.appendChild(box);
     55     col_db.appendChild(document.createTextNode('dB'));
     56     fix_boxes.push(box);
     57     box.oninput = redraw;
     58   }
     59 }
     60 
     61 function init_canvas() {
     62   redraw();
     63 }
     64 
     65 // Redraw everything on the canvas. This is run every time any input is changed.
     66 function redraw() {
     67   var backgroundColor = 'black';
     68   var gridColor = 'rgb(200,200,200)';
     69   var dotColor = 'rgb(245,245,0)';
     70   var marginLeft = 60;
     71   var marginBottom = 30;
     72   var marginTop = 20;
     73   var marginRight = 30;
     74   var canvas = document.getElementById('curve');
     75   var ctx = canvas.getContext('2d');
     76   var w = 800;
     77   var h = 400;
     78   canvas.width = w + marginLeft + marginRight;
     79   canvas.height = h + marginBottom + marginTop;
     80   ctx.fillStyle = backgroundColor;
     81   ctx.fillRect(0, 0, canvas.width, canvas.height);
     82   ctx.lineWidth = 1;
     83   ctx.font = '16px sans-serif';
     84   ctx.textAlign = 'center';
     85 
     86   // Set up coordinate system
     87   ctx.translate(marginLeft, h + marginTop);
     88   ctx.scale(1, -1);
     89 
     90   // Draw two lines at x = 0 and y = 0 which are solid lines
     91   ctx.strokeStyle = gridColor;
     92   ctx.beginPath();
     93   ctx.moveTo(0, h + marginTop / 2);
     94   ctx.lineTo(0, 0);
     95   ctx.lineTo(w + marginRight / 2, 0);
     96   ctx.stroke();
     97 
     98   // Draw vertical lines and labels on x axis
     99   ctx.strokeStyle = gridColor;
    100   ctx.fillStyle = gridColor;
    101   ctx.beginPath();
    102   ctx.setLineDash([1, 4]);
    103   for (var i = 0; i <= FIXES; i++) {
    104     var x = i * w / FIXES;
    105     if (i > 0) {
    106       ctx.moveTo(x, 0);
    107       ctx.lineTo(x, h + marginTop / 2);
    108     }
    109     drawText(ctx, Math.round(i * NN * 10 / FIXES) / 10, x, -20, 'center');
    110   }
    111   ctx.stroke();
    112   ctx.setLineDash([]);
    113 
    114   // Draw horizontal lines and labels on y axis
    115   var min = parseFloat(minmax_boxes[0].value);
    116   var max = parseFloat(minmax_boxes[1].value);
    117   var step = parseFloat(minmax_boxes[2].value);
    118 
    119   // Sanity checks
    120   if (isNaN(min) || isNaN(max) || isNaN(step)) return;
    121   if (min >= max || step <= 0 || (max - min) / step > 10000) return;
    122 
    123   // Let s = minimal multiple of step such that
    124   // vdivs = Math.round((max - min) / s) <= 20
    125   var vdivs;
    126   var s = Math.max(1, Math.floor((max - min) / 20 / step)) * step;
    127   while (true) {
    128     var vdivs = Math.round((max - min) / s);
    129     if (vdivs <= 20) break;
    130     s += step;
    131   }
    132 
    133   // Scale from v to y is
    134   // y = (v - min) / s * h / vdivs
    135   ctx.strokeStyle = gridColor;
    136   ctx.fillStyle = gridColor;
    137   ctx.beginPath();
    138   ctx.setLineDash([1, 4]);
    139   for (var i = 0;; i++) {
    140     var v = min + s * i;
    141     var y;
    142     if (v <= max) {
    143       y = i * h / vdivs;
    144     } else {
    145       v = max;
    146       y = (max - min) / s * h / vdivs;
    147     }
    148     drawText(ctx, v.toFixed(2), -5 , y - 4, 'right');
    149     if (i > 0) {
    150       ctx.moveTo(0, y);
    151       ctx.lineTo(w + marginRight / 2, y);
    152     }
    153     if (v >= max) break;
    154   }
    155   ctx.stroke();
    156   ctx.setLineDash([]);
    157 
    158   // Draw fixed points
    159   ctx.strokeStyle = dotColor;
    160   ctx.fillStyle = dotColor;
    161   for (var i = 0; i <= FIXES; i++) {
    162     var v = getFix(i);
    163     if (isNaN(v)) continue;
    164     var x = i * w / FIXES;
    165     var y = (v - min) / s * h / vdivs;
    166     ctx.beginPath();
    167     ctx.arc(x, y, 4, 0, 2 * Math.PI);
    168     ctx.stroke();
    169   }
    170 
    171   // Draw interpolated points
    172   var points = generatePoints();
    173   for (var i = 0; i <= NN; i++) {
    174     var v = points[i];
    175     if (isNaN(v)) continue;
    176     var x = i * w / NN;
    177     var y = (v - min) / s * h / vdivs;
    178     ctx.beginPath();
    179     ctx.arc(x, y, 2, 0, 2 * Math.PI);
    180     ctx.stroke();
    181     ctx.fill();
    182   }
    183 }
    184 
    185 // Returns the value of the fixed point with index i
    186 function getFix(i) {
    187   var v = parseFloat(fix_boxes[i].value);
    188   var min = parseFloat(minmax_boxes[0].value);
    189   var max = parseFloat(minmax_boxes[1].value);
    190 
    191   if (isNaN(v)) return v;
    192   if (v > max) v = max;
    193   if (v < min) v = min;
    194   return v;
    195 }
    196 
    197 // Returns a value quantized to the given min/max/step
    198 function quantize(v) {
    199   var min = parseFloat(minmax_boxes[0].value);
    200   var max = parseFloat(minmax_boxes[1].value);
    201   var step = parseFloat(minmax_boxes[2].value);
    202 
    203   v = min + Math.round((v - min) / step) * step;
    204   if (isNaN(v)) return v;
    205   if (v > max) v = max;
    206   if (v < min) v = min;
    207   return v;
    208 }
    209 
    210 // Generate points indexed by 0 to NN, using interpolation and quantization
    211 function generatePoints() {
    212   // Go through all points, for each point:
    213   // (1) Find the left fix: the max defined fixed point <= current point
    214   // (2) Find the right fix: the min defined fixed point >= current point
    215   // (3) If both exist, interpolate value for current point
    216   // (4) Otherwise skip current point
    217 
    218   // Returns left fix index for current point, or NaN if it does not exist
    219   var find_left = function(current) {
    220     for (i = FIXES; i >= 0; i--) {
    221       var x = NN * i / FIXES;
    222       if (x <= current && !isNaN(getFix(i))) {
    223         return i;
    224       }
    225     }
    226     return NaN;
    227   };
    228 
    229   // Returns right fix index for current point, or NaN if it does not exist
    230   var find_right = function(current) {
    231     for (i = 0; i <= FIXES; i++) {
    232       var x = NN * i / FIXES;
    233       if (x >= current && !isNaN(getFix(i))) {
    234         return i;
    235       }
    236     }
    237     return NaN;
    238   };
    239 
    240   // Interpolate value for point x
    241   var interpolate = function(x) {
    242     var left = find_left(x);
    243     if (isNaN(left)) return NaN;
    244 
    245     var right = find_right(x);
    246     if (isNaN(right)) return NaN;
    247 
    248     var xl = NN * left / FIXES;
    249     var xr = NN * right / FIXES;
    250     var yl = getFix(left);
    251     var yr = getFix(right);
    252 
    253     if (xl == xr) return yl;
    254 
    255     return yl + (yr - yl) * (x - xl) / (xr - xl);
    256   };
    257 
    258   var result = [];
    259   for (var x = 0; x <= NN; x++) {
    260     result.push(quantize(interpolate(x)));
    261   }
    262   return result;
    263 }
    264 
    265 function drawText(ctx, s, x, y, align) {
    266   ctx.save();
    267   ctx.translate(x, y);
    268   ctx.scale(1, -1);
    269   ctx.textAlign = align;
    270   ctx.fillText(s, 0, 0);
    271   ctx.restore();
    272 }
    273 
    274 // The output config file looks like:
    275 //
    276 // [Speaker]
    277 //   volume_curve = explicit
    278 //   db_at_100 = 0
    279 //   db_at_99 = -75
    280 //   db_at_98 = -75
    281 //   ...
    282 //   db_at_1 = -4500
    283 //   db_at_0 = -4800
    284 // [Headphone]
    285 //   volume_curve = simple_step
    286 //   volume_step = 70
    287 //   max_volume = 0
    288 //
    289 function download_config() {
    290   var content = '';
    291   content += '[Speaker]\n';
    292   content += '  volume_curve = explicit\n';
    293   var points = generatePoints();
    294   var last = 0;
    295   for (var i = NN; i >= 0; i--) {
    296     var v = points[i];
    297     if (isNaN(points[i])) v = last;
    298     content += '  db_at_' + i + ' = ' + Math.round(v * 100) + '\n';
    299   }
    300 
    301   content += '[Headphone]\n';
    302   content += '  volume_curve = simple_step\n';
    303   content += '  volume_step = 70\n';
    304   content += '  max_volume = 0\n';
    305   save_config(content);
    306 }
    307 
    308 function save_config(content) {
    309   var a = document.getElementById('save_config_anchor');
    310   var uriContent = 'data:application/octet-stream,' +
    311       encodeURIComponent(content);
    312   a.href = uriContent;
    313   a.download = 'HDA Intel PCH';
    314   a.click();
    315 }
    316