Web Audio APIを使ってDAW（っぽいもの）を作ってみる

# はじめに
この記事は [オープンストリーム Advent Calendar 2017](https://qiita.com/advent-calendar/2017/opst-advent2017) の 19日目 の記事です。
今回は個人的に興味のあった Web Audio API について調べたり作ったりした記録を残そうと思います。

### Web Audio API とは？
Web Audio API は JavaScript で オーディオを自由自在に操るためのAPIです。
少し古いですが [HTML5 Rocks - Web Audio API の基礎](https://www.html5rocks.com/ja/tutorials/webaudio/intro/) が分かりやすい記事かと思います。
ざっくり例を挙げると…

- オーディオデータを再生する
- マイク経由で録音する
- メディアプレーヤーによくある、音に合わせて変化するビジュアル（ビジュアライザ）を作る
- サイン波などを生成して鳴らす

…などなど、出来ることは多岐に渡るようです。

### 今回の目標
興味はあっても、具体的に作るものを決めないと中々モチベーションが上がりません…orz
そこで今回は、タイトル通り「DAW（っぽいもの）」を作ってみることにしました。

DAW（Digital Audio Workstation）、平たく雑に説明すると「パソコンで音混ぜて音楽作るソフト」です。
（少し補足すると、録音したボーカルやギター等の録音データ(wav)や、打ち込みデータ(midi)を編集したり、調整したり…最終的に音楽として完成させるための統合環境のことです）

流石にDAW全部作る！というのは無理があるので、とりあえずは

- 複数のオーディオファイルを読み込む
- 読み込んだオーディオを再生する
- 複数トラックをミックスして再生する
- 見た目イイ感じのビジュアライザを作る

あたりを挑戦してみました。

# 今回作ったもの
![cap2.PNG](https://qiita-image-store.s3.amazonaws.com/0/216852/4478469d-09a7-48c6-bae7-b39da7285cb9.png)

こんな感じのものを作ってみました。

以下のページで実際に動くデモを公開しています。
[Git Page web-audio-sample](https://ymgd-a.github.io/web-audio-sample/)

ついでに手っ取り早く遊べるよう、適当な音楽も作りました。DL＆解凍してお使いください。
[mp3-samples.zip](https://ymgd-a.github.io/web-audio-sample/mp3-samples.zip)
※ 正確には音楽の部品です。全部合わせてミックスすると音楽になる…というものです。超短いですが…。
※ 打ち込みで個人的に作った曲ですので、著作権云々は言及しません。煮るなり焼くなりお好きに使ってください。

### 遊び方（ヘッドホン、イヤホン 推奨）
1. [mp3-samples.zip](https://ymgd-a.github.io/web-audio-sample/mp3-samples.zip) をDLして解凍します
2. デモページ上部の「入力」にある「参照」ボタンをクリックし、解答した mp3 ファイルをすべて選択します
3. 読み込みが完了したらまずは「再生」ボタンを押してみます
4. 音が流れ、下のビジュアライザがウニョウニョ動いていることを確認します
5. 「停止」ボタンを押し、「トラックリスト」のファイル名を適当に何個かクリック＆選択して「再生」します
6. 複数の音がミックスされていること、ビジュアライザが先ほどと変わっていることを確認します
7. 色々な組み合わせで音をミックスしてみましょう！

※ お手持ちの mp3 や wav などがあればそれでも試せます。
　…が、世の大半の曲は、混ぜられる前提で作られていないので、音が汚くなって面白みにかけるかもです…。

### 動作確認環境
以下のブラウザで正常動作を確認しています。

- Chrome バージョン: 63.0.3239.108（Official Build） （64 ビット）
- Firefox 53.0.3 (32 ビット) … 少し古いです

# 勘所・覚書
手探り状態で色々と調べたり試したりしてなんとか動くものが出来ました。
苦労したところや、こんな風に理解をしました…など、備忘のためにもまとめておきます。

### Context
Web Audio だけに限りませんが、コンテキストという概念です。
今回扱った例では、 AudioContext と OfflineAudioContext の2種類のコンテキストが存在します。
自分を納得させるために **厨房** をイメージしました。
料理を完成させるという **一連の作業** のために、フライパンやまな板やガスコンロ…等の **ツール** を提供し、必要に応じて冷蔵庫などに一時的に **保管** したりできる…といった感覚でしょうか。
### AudioContext
リアルタイムでオーディオデータを扱うためのコンテキストです。
画面に表示させるためのビジュアライザ、スピーカーへの出力時に AudioContext を使用しています。
厨房の例で言えば、 **平日昼間のオフィス街の飲食店の厨房** のようなものでしょうか。
次から次へと来る **細かい** オーダーを、 **リアルタイム** で捌いて料理を提供するための厨房のイメージです。
### OfflineAudioContext
静的なオーディオデータを扱うためのコンテキストです。
リアルタイムでなく、あらかじめあるデータを静的に処理したい場合に使うためのコンテキストです。
複数のオーディオトラック（楽器）を結合し、一つのオーディオへレンダリングする際に使用しています。
厨房の例で言えば、 **学校給食センターの厨房** のようなものでしょうか。
大量の給食を **まとめて** 朝に作り貯めておける厨房のイメージです。

### ノード（Node）
Web Audio でできることの大半は、ノード同士を接続（connect）して実現されます。
正確には`AudioNode`というインターフェースとして定義されており、`AudioBufferSourceNode`や、オーディオデータを解析する`AnalyzerNode`などは、すべて`AudioNode`を実装しています。
例としては、

- 入力元（音源）のノード(AudioBufferSourceNode) → 音量調節をするノード(GainNode)
- 音量調節をするノード(GainNode) → 解析をするノード(AnalyzerNode)
- 解析をするノード → 出力先スピーカーのノード(AudioDestinationNode)

といった形で繋げていき、音を加工・表示しながら最終的な行き先（大半はスピーカー？）を目指します。
接続、配線、どっちをどうすれば…？は常に混乱の元になりがちなので、どうしても慣れるしかなさそうです…。

### ビジュアライザ
`AudioContext#createAnalyser()`メソッドを使用し、リアルタイムで流れてくるオーディオデータ（実態は UInt8 or Float32 の配列）を拾う`AnalyzerNode`のインスタンスを取得します。
画面表示させる目的からすると、UInt8の情報量で十分と思われます。
前者が「波形表示」、後者が「スペクトラムアナライザ」に使用されています。

#### 波形表示
![wav.PNG](https://qiita-image-store.s3.amazonaws.com/0/216852/75d4b146-a5e8-6078-fe2e-0f15e3bb2cae.png)
`AnalyzerNode#getByteTimeDomainData()`メソッドを使用し、一定時間内の波形（連続した数値）を取得することができます。
連続した数値は HTML Canvas 上に直線で描くと、上図のような波形となります。

#### スペクトラムアナライザ
![freq.PNG](https://qiita-image-store.s3.amazonaws.com/0/216852/e13aafc3-ba90-8f27-7c4b-0bba2e1abda9.png)
`AnalyzerNode#getByteFrequencyData()`メソッドを使用し、周波数帯域ごとの音圧レベルを取得することが出来ます。
結果は配列で返却されますが、返ってくる配列の何番目の要素が何 Hz のデータなんだろう？という所で悩みました。
以下が参考になりました。
[What does the FFT data in the Web Audio API correspond to? - Stack Overflow](https://stackoverflow.com/questions/14789283/what-does-the-fft-data-in-the-web-audio-api-correspond-to)

### ミックス
永らく実現方法が分からなかった部分です。
結論から言うと`OfflineAudioContext`で読み込んだファイルをレンダリングし、一つのオーディオデータ（バッファ）を生成することができました。
[AudioParam Viewer - 音の鳴るブログ](http://mohayonao.hatenablog.com/entry/2014/10/21/201351) がヒントになりました！

### その他
- 久々に素の JavaScript でエラい苦労しました。。。
- データ管理周りごちゃごちゃしすぎてソース汚い…orz
- デコードは`AudioContext#decodeAudioData()`メソッドがよしなにやってくれて便利（`OfflineAudioContext`も可）
- デコード対応形式は wav, mp3 以外にも色々ありそう。。。（ブラウザ依存？）

# おわりに
はじめて Web Audio の世界に飛び込んでみましたが、思ったよりもきっちり動くものが作れて感動しました。
しかしノードという概念や、それをプログラムでどう表現するか、どのように接続していくか…などはまだまだイメージが定着しきれておらず、ふわふわとした感じです。。。
その他にも、JavaScript の File API や Canvas、配列の 'ナウい' 感じのメソッドなど、必要とされる技術が多岐に渡るので、躓きが多かったです。
しかし聴いて楽しく、見て楽しいWebアプリができるため、試してみる価値は十分にありかと思います！


# 参考資料
実装にあたっては、以下の記事も参考にさせて頂きました。

- [AudioContext Web APIs MDN](https://developer.mozilla.org/ja/docs/Web/API/AudioContext)
- [OfflineAudioContext Web APIs MDN](https://developer.mozilla.org/en-US/docs/Web/API/OfflineAudioContext)
- [AudioParam Viewer - 音の鳴るブログ](http://mohayonao.hatenablog.com/entry/2014/10/21/201351)
- [Qiita Web Audio APIを利用してオーディオビジュアライザを作成する ~getByteFrequencyDataで取得する配列のどの要素がどの周波数(Hz)の波形データを格納しているか確認する~](https://qiita.com/soarflat/items/004cac51b818b9483764)