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 6var NN = 100; // Total number of points 7var FIXES = 25; // Number of fixed points, evenly spaced in the range [0, NN] 8var minmax_boxes = []; // The text input boxes for min/max/step 9var fix_boxes = []; // The text input boxes for fixed points 10 11window.onload = function() { 12 init_minmax(); 13 init_fixes(); 14 init_canvas(); 15}; 16 17// Create min/max/step boxes 18function 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 39function 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 61function init_canvas() { 62 redraw(); 63} 64 65// Redraw everything on the canvas. This is run every time any input is changed. 66function 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 186function 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 198function 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 211function 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 265function 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// 289function 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 308function 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