getUserMediaで音声を拾いリアルタイムで波形を出力する

# 概要
getUserMediaでブラウザから音声を拾い、画面に波形を出力します。

![出力結果サンプル](https://i.gyazo.com/41a72bb11350a7fe8c2118d2d7712d2d.gif)

人間の声を分析して色々やりたいと思い、第一歩としてこちらを取り組みました。

# 環境
Windows 10 Home 64Bit
Chrome 63

# 超大まかな前提
- getUserMediaでマイクの音声を拾う
- onAudioProcessで1024バイトずつバッファを溜めていく（今回はバッファの内容は使わないが、wave化してサーバに送信などする予定）
- getChannelDataで返ってくるデータは生データなので、このままでは解析しにくい
- 離散フーリエ変換を行うことで周波数帯ごとの解析がしやすくなる（スペクトラム分析）
- audioContext.createAnalyser()でanalyserを作り、getByteFrequencyDataを利用すると上記フーリエ変換をした状態の値が取れる

# ソースコード
```html:voice_analyse.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>音声解析</title>
    <script
      src="https://code.jquery.com/jquery-3.2.1.js"></script>
  </head>
  <body>
    <button onclick="startRecording()">解析開始</button>
    <button onclick="endRecording()">解析終了</button>
    <hr>
    <canvas id="canvas" width="500" height="500"></canvas>
    <script src="voice_analyse.js"></script>
  </body>
</html>
```

```javascript:voice_analyse.js
// クロスブラウザ定義
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia;

// 変数定義
var localMediaStream = null;
var localScriptProcessor = null;
var audioContext = new AudioContext();
var bufferSize = 1024;
var audioData = []; // 録音データ
var recordingFlg = false;

// キャンバス
var canvas = document.getElementById('canvas');
var canvasContext = canvas.getContext('2d');

// 音声解析
var audioAnalyser = null;


// 録音バッファ作成（録音中自動で繰り返し呼び出される）
var onAudioProcess = function(e) {
	if (!recordingFlg) return;
	
	// 音声のバッファを作成
	var input = e.inputBuffer.getChannelData(0);
	var bufferData = new Float32Array(bufferSize);
	for (var i = 0; i < bufferSize; i++) {
		bufferData[i] = input[i];
	}
	audioData.push(bufferData);
	
	// 波形を解析
	analyseVoice();
};

// 解析用処理
var analyseVoice = function() {
	var fsDivN = audioContext.sampleRate / audioAnalyser.fftSize;
	var spectrums = new Uint8Array(audioAnalyser.frequencyBinCount);
	audioAnalyser.getByteFrequencyData(spectrums);
	canvasContext.clearRect(0, 0, canvas.width, canvas.height);
	
	canvasContext.beginPath();
	
	for (var i = 0, len = spectrums.length; i < len; i++) {
		//canvasにおさまるように線を描画
		var x = (i / len) * canvas.width;
		var y = (1 - (spectrums[i] / 255)) * canvas.height;
		if (i === 0) {
			canvasContext.moveTo(x, y);
		} else {
			canvasContext.lineTo(x, y);
		}
		var f = Math.floor(i * fsDivN);  // index -> frequency;
		
		// 500 Hz単位にy軸の線とラベル出力
		if ((f % 500) === 0) {
			var text = (f < 1000) ? (f + ' Hz') : ((f / 1000) + ' kHz');
			// Draw grid (X)
			canvasContext.fillRect(x, 0, 1, canvas.height);
			// Draw text (X)
			canvasContext.fillText(text, x, canvas.height);
		}
	}
	
	canvasContext.stroke();
	
	// x軸の線とラベル出力
	var textYs = ['1.00', '0.50', '0.00'];
	for (var i = 0, len = textYs.length; i < len; i++) {
		var text = textYs[i];
		var gy   = (1 - parseFloat(text)) * canvas.height;
		// Draw grid (Y)
		canvasContext.fillRect(0, gy, canvas.width, 1);
		// Draw text (Y)
		canvasContext.fillText(text, 0, gy);
	}
}


// 解析開始
var startRecording = function() {
	recordingFlg = true;
	navigator.getUserMedia({audio: true}, function(stream) {
		// 録音関連
		localMediaStream = stream;
		var scriptProcessor = audioContext.createScriptProcessor(bufferSize, 1, 1);
		localScriptProcessor = scriptProcessor;
		var mediastreamsource = audioContext.createMediaStreamSource(stream);
		mediastreamsource.connect(scriptProcessor);
		scriptProcessor.onaudioprocess = onAudioProcess;
		scriptProcessor.connect(audioContext.destination);
		
		// 音声解析関連
		audioAnalyser = audioContext.createAnalyser();
		audioAnalyser.fftSize = 2048;
		frequencyData = new Uint8Array(audioAnalyser.frequencyBinCount);
		timeDomainData = new Uint8Array(audioAnalyser.frequencyBinCount);
		mediastreamsource.connect(audioAnalyser);
	},
	function(e) {
		console.log(e);
	});
};

// 解析終了
var endRecording = function() {
	recordingFlg = false;
	
	//audioDataをサーバに送信するなど終了処理
};
```

#今後
人の声の特性をとらえ、声の録音を行ったあとに自動で音声をサーバに送るなどしたいです。


#参考

##録音部分
https://developers.google.com/web/fundamentals/media/recording-audio/?hl=ja
http://var.blog.jp/archives/62330155.html
##音声解析部分
https://qiita.com/niisan-tokyo/items/764acfeec77d8092eb73
http://docolog.cocolog-nifty.com/papalog/2014/03/post-3de1.html
https://www.g200kg.com/jp/docs/webaudio/analyser.html
https://developer.mozilla.org/ja/docs/Web/API/AudioContext/createBufferSource
http://kagigotonet.hatenablog.com/entry/2014/09/16/124606
https://weblike-curtaincall.ssl-lolipop.jp/portfolio-web-sounder/webaudioapi-visualization/draw-wave