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