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