1/*
2 * Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
3 * Use of this source code is governed by a BSD-style license that can be
4 * found in the LICENSE file.
5 */
6
7var FFT_SIZE = 2048;
8
9var audioContext;
10var tonegen;
11var recorder;
12var drawContext;
13var audioPlay, audioBuffer;
14var audioSourceType = "sweep";
15var recordSourceType = "microphone";
16
17/**
18 * Switches Play/Record tab
19 * @param {string} tab name
20 */
21function switchTab(tabName) {
22  var canvas_detail = document.getElementsByClassName('canvas_detail');
23  switch (tabName) {
24    case 'play_tab':
25      document.getElementById('record_tab').setAttribute('class', '');
26      document.getElementById('record_div').style.display = 'none';
27      document.getElementById('play_div').style.display = 'block';
28      for (var i = 0; i < canvas_detail.length; i++) {
29        canvas_detail[i].style.display = "none";
30      }
31      drawContext.drawBg();
32      break;
33    case 'record_tab':
34      document.getElementById('play_tab').setAttribute('class', '');
35      document.getElementById('play_div').style.display = 'none';
36      document.getElementById('record_div').style.display = 'block';
37      for (var i = 0; i < canvas_detail.length; i++) {
38        canvas_detail[i].style.display = "block";
39      }
40      drawContext.drawCanvas();
41      break;
42  }
43  document.getElementById(tabName).setAttribute('class', 'selected');
44}
45
46function __log(e, data) {
47  log.innerHTML += "\n" + e + " " + (data || '');
48}
49
50function startUserMedia(stream) {
51  var input = audioContext.createMediaStreamSource(stream);
52  recorder = new Recorder(input);
53}
54
55window.onload = function init() {
56  setupSourceLayer(audioSourceType);
57  try {
58    // webkit shim
59    window.AudioContext = window.AudioContext || window.webkitAudioContext;
60    navigator.getUserMedia = navigator.getUserMedia ||
61        navigator.webkitGetUserMedia;
62    window.URL = window.URL || window.webkitURL;
63
64    audioContext = new AudioContext;
65  } catch (e) {
66    alert('No web audio support in this browser!');
67  }
68
69  navigator.getUserMedia({audio: true}, startUserMedia, function(e) {
70    alert('No live audio input: ' + e);
71  });
72
73  /* Initialize global objects */
74  tonegen = new ToneGen();
75  audioPlay = new AudioPlay();
76
77  var canvas = document.getElementById('fr_canvas');
78  drawContext = new DrawCanvas(canvas, audioContext.sampleRate / 2);
79  drawContext.drawBg();
80};
81
82/* For Play tab */
83
84/**
85 * Sets audio source layer
86 * @param {string} audio source type
87 */
88function setupSourceLayer(value) {
89  var sourceTone = document.getElementById('source_tone');
90  var sourceFile = document.getElementById('source_file');
91  var sweepTone = document.getElementsByClassName('sweep_tone');
92  audioSourceType = value;
93  switch (value) {
94    case 'sine':
95      for (var i = 0; i < sweepTone.length; i++) {
96        sweepTone[i].style.display = "none";
97      }
98      document.getElementById('freq_start').value = 1000;
99      document.getElementById('freq_end').value = 1000;
100      sourceTone.style.display = "block";
101      sourceFile.style.display = "none";
102      document.getElementById('play_audio').disabled = false;
103      break;
104    case 'sweep':
105      for (var i = 0; i < sweepTone.length; i++) {
106        sweepTone[i].style.display = "block";
107      }
108      document.getElementById('freq_start').value = 20;
109      document.getElementById('freq_end').value = 12000;
110      sourceTone.style.display = "block";
111      sourceFile.style.display = "none";
112      document.getElementById('play_audio').disabled = false;
113      break;
114    case 'file':
115      sourceTone.style.display = "none";
116      sourceFile.style.display = "block";
117      document.getElementById('play_audio').disabled = true;
118      break;
119  }
120}
121
122/**
123 * Sets left/right gain
124 */
125function gainChanged() {
126  var leftGain = document.getElementById('left_gain').value;
127  var rightGain = document.getElementById('right_gain').value;
128  var gainLabel = document.getElementById('gain_label');
129  gainLabel.innerHTML = 'L(' + leftGain + ') / R(' + rightGain + ')';
130}
131
132/**
133 * Checks sine tone generator parameters and sets audio buffer
134 */
135function toneValueCheckSet() {
136  var passed = true;
137  var freqStart = parseInt(document.getElementById('freq_start').value);
138  var freqEnd = parseInt(document.getElementById('freq_end').value);
139  var duration = parseFloat(document.getElementById('tone_sec').value);
140  var leftGain = parseInt(document.getElementById('left_gain').value);
141  var rightGain = parseInt(document.getElementById('right_gain').value);
142  var sweepLog = document.getElementById('sweep_log').checked;
143
144  function isNumber(value, msg) {
145    if (isNaN(value) || value <= 0) {
146      alert(msg);
147      passed = false;
148    }
149  }
150
151  if (audioSourceType == 'sine') {
152    freqEnd = freqStart;
153  }
154
155  isNumber(freqStart, "Start frequency should be a positive number.");
156  isNumber(freqEnd, "Stop frequency should be a positive number.");
157  isNumber(duration, "Duration should be a positive number.");
158  if (freqEnd > audioContext.sampleRate / 2) {
159    alert('Stop frequency is too large.');
160    passed = false;
161  }
162  if (freqStart < 20) {
163    alert('Start frequency is too small.');
164    passed = false;
165  }
166  if (passed) {
167    /* Passed value check and generate tone buffer */
168    tonegen.setFreq(freqStart, freqEnd, sweepLog);
169    tonegen.setDuration(duration);
170    tonegen.setGain(leftGain / 20, rightGain / 20);
171    tonegen.setSampleRate(audioContext.sampleRate);
172    tonegen.genBuffer();
173    var buffer = tonegen.getBuffer();
174    audioPlay.setBuffer(buffer, document.getElementById('append_tone').checked);
175  }
176  return passed;
177}
178
179function loadAudioFile() {
180  document.getElementById('audio_file').click();
181}
182
183/**
184 * Loads audio file from local drive
185 */
186function changeAudioFile() {
187  function loadAudioDone(filename, buffer) {
188    audioBuffer = buffer;
189    document.getElementById('play_audio').disabled = false;
190  }
191  var input = document.getElementById('audio_file');
192  document.getElementById('play_audio').disabled = true;
193  audioPlay.loadFile(input.files[0], loadAudioDone);
194  input.value = '';
195}
196
197/**
198 * Play audio according source type
199 */
200function playAudioFile() {
201  /**
202   * Callback function to draw frequency response of current buffer
203   */
204  function getInstantBuffer(leftData, rightData, sampleRate) {
205    drawContext.drawInstantCurve(leftData, rightData, sampleRate);
206  }
207
208  var btn = document.getElementById('play_audio');
209  var append = document.getElementById('append_tone').checked;
210  if (btn.className == 'btn-off') {
211    switch (audioSourceType) {
212      case 'sine':
213      case 'sweep':
214        if (toneValueCheckSet()) {
215          audioPlay.play(playAudioFile, getInstantBuffer);
216          btn.className = 'btn-on';
217        }
218        break;
219      case 'file':
220        audioPlay.setBuffer(audioBuffer, append);
221        audioPlay.play(playAudioFile, getInstantBuffer);
222        btn.className = 'btn-on';
223        break;
224    }
225  } else {
226    audioPlay.stop();
227    btn.className = 'btn-off';
228    drawContext.drawBg();
229  }
230}
231
232/* For Record tab */
233
234/**
235 * Sets record source type
236 * @param {string} record source type
237 */
238function setupRecordSource(value) {
239  recordSourceType = value;
240  var autoStop = document.getElementById('auto_stop');
241  if (value == 'audio') {
242    autoStop.disabled = true;
243    autoStop.checked = false;
244  } else {
245    autoStop.disabled = false;
246    autoStop.checked = true;
247  }
248}
249
250function loadButtonClicked() {
251  document.getElementById('sample_file').click();
252}
253
254/**
255 * Loads sample file to draw frequency response curve into canvas
256 */
257function loadSampleFile() {
258  /**
259   * Callback function when file loaded
260   * @param {string} file name
261   * @param {AudioBuffer} file buffer
262   */
263  function addFileToCanvas(filename, buffer) {
264    var newBuffer = [];
265    for (var i = 0; i < buffer.numberOfChannels; i++) {
266      newBuffer.push(buffer.getChannelData(i));
267    }
268    drawContext.add(new AudioCurve(newBuffer, filename, buffer.sampleRate));
269  }
270  var input = document.getElementById('sample_file');
271  audioPlay.loadFile(input.files[0], addFileToCanvas);
272  input.value = '';
273}
274
275/**
276 * Starts/Stops record function
277 */
278function recordButtonClicked() {
279  /**
280   * Callback function to draw frequency response of current recorded buffer
281   */
282  function getInstantBuffer(leftData, rightData, sampleRate, stop) {
283    drawContext.drawInstantCurve(leftData, rightData, sampleRate);
284    if (stop)
285      recordButtonClicked();
286  }
287
288  var btn = document.getElementById('record_btn');
289  if (btn.className == 'btn-off') {
290    var detect = document.getElementById('detect_tone').checked;
291    var autoStop = document.getElementById('auto_stop').checked;
292    var append = document.getElementById('append_tone').checked;
293    if (recordSourceType == 'audio') {
294      switch(audioSourceType) {
295        case 'sine':
296        case 'sweep':
297          if (toneValueCheckSet()) {
298            audioPlay.play(recordButtonClicked);
299            btn.className = 'btn-on';
300          }
301          break;
302        case 'file':
303          audioPlay.setBuffer(audioBuffer, append);
304          audioPlay.play(recordButtonClicked);
305          btn.className = 'btn-on';
306          break;
307      }
308    } else {
309      btn.className = 'btn-on';
310    }
311    recorder.record(getInstantBuffer, detect, autoStop);
312  } else {
313    recorder.stop();
314    if (recordSourceType == 'audio') {
315      audioPlay.stop();
316    }
317    // create WAV download link using audio data blob
318    var filename = new Date().toISOString() + '.wav';
319    buffer = recorder.getBuffer();
320    drawContext.add(new AudioCurve(buffer, filename, audioContext.sampleRate));
321    createDownloadLink(filename);
322    recorder.clear();
323    btn.className = 'btn-off';
324  }
325}
326
327/**
328 * Creates download link of recorded file
329 * @param {string} file name
330 */
331function createDownloadLink(filename) {
332  var blob = recorder.exportWAV();
333  var url = URL.createObjectURL(blob);
334  var table = document.getElementById('record_list');
335  var au = document.createElement('audio');
336  au.controls = true;
337  au.src = url;
338
339  var hf = document.createElement('a');
340  hf.href = url;
341  hf.download = filename;
342  hf.innerHTML = hf.download;
343
344  var tr = table.insertRow(table.rows.length);
345  var td_au = tr.insertCell(0);
346  var td_hf = tr.insertCell(1);
347  td_hf.style = "white-space: nowrap";
348  td_au.appendChild(au);
349  td_hf.appendChild(hf);
350}
351
352/**
353 * Exports frequency response CVS file of curves on the canvas
354 */
355function exportCSV() {
356  var hf = document.getElementById('export_csv');
357  var noctaves = document.getElementById('noctaves').value;
358  content = drawContext.exportCurve(noctaves);
359  var blob = new Blob([content], {type: 'application/octet-stream'});
360  var url = URL.createObjectURL(blob);
361  hf.href = url;
362  hf.download = 'audio.csv';
363}
364