DartでWeb Audio APIを使って音を鳴らす

DartでWeb Audio APIを使って音を鳴らしてみます。

以下のページを参考にします。

[Getting Started with Web Audio API - HTML5 Rocks](http://www.html5rocks.com/ja/tutorials/webaudio/intro/)

[Getting Started with the Web Audio API | Dart: Structured web apps](https://www.dartlang.org/samples/webaudio/)

上のページではJavaScriptでの説明になっていますが、Web Audio APIはWeb標準のAPIですので、言語が変わってもAPI仕様は変わりません。(変わりません、というかWeb標準API自体が、API仕様を統一させましょう、というものなので、仕様を変えてしまっては非難を浴びるだけです)

下のページは、上の解説を元にしたDartでのサンプルです。

## 下ごしらえ

`pubspec.yaml`には特別な記述は行いません。

```dart:pubspec.yaml
name: dartwebaudio
dependencies:
  browser: any
```

`pub get`して依存パッケージを取得しておきます。

```
$ pub get
```

以下の様なディレクトリ構成でファイルを作成していきます。

```
├── pubspec.yaml
└── web
    ├── dartwebaudio.dart
    ├── index.html
    └── sample.ogg
```

HTML側には、音再生用のボタンを用意しておきます。

```html:index.html
<title>Web Audio on Dart</title>
<input type="button" value="loading..."></input>
<script type="application/dart" src="dartwebaudio.dart"></script>
<script src="packages/browser/dart.js"></script>
```

ボタンに表示する文言を`"loading..."`にして、音素材の読み込みが完了したら`"play"`に切り替えて再生できる状態になったことがわかるようにする工夫をしてみます。

下ごしらえが済みましたので、Web Audio APIを使っていきます。

## Dartのコードを書く

まずは`dart:html`と`dart:web_audio`を`import`しておきます。

```dart:dartwebaudio.dart
import 'dart:html';
import 'dart:web_audio';
```

Web Audioで用いる各種変数を用意しておきます。

```dart:dartwebaudio.dart
AudioContext context;
AudioBuffer sampleBuffer;
```

`AudioContext`を作っておきます。

```dart:dartwebaudio.dart
main() {
  context = new AudioContext();
}
```

次に音素材を読み込みます。

音素材の対応フォーマットはWebブラウザごとに異なりますので注意が必要です。

主要Webブラウザごとの対応フォーマットについては以下のページが詳しいです。

[Media formats supported by the HTML audio and video elements - HTML | MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Supported_media_formats#Browser_compatibility)

今回は素直に(?)OGGで行きましょう。sample.oggを適当に作成しましょう。Audacityを使うと簡単に作成できます。

では、このsample.oggを読み込ませる処理を書きます。

```dart:dartwebaudio.dart
// main関数を省略
HttpRequest request = new HttpRequest()
  ..requestType = 'arraybuffer'
  ..open('GET', 'sample.ogg');

request.onLoad.listen((e) => _onLoad(request));

request.onError.listen((e) {
  throw 'File load error';
});

request.send();
```

`HttpRequest`で`requestType`を`'arraybuffer'`とし、`'GET'`で`'sample.ogg'`を読み込みます。

データ読み込まれた時に`_onLoad`関数が呼ばれるように、`HttpRequest.onLoad`に`_onLoad`を`listen`させておきます。

最後に、`request.send()`で読み込みを開始させます。

`_onLoad`関数を用意します。

```dart:dartwebaudio.dart
_onLoad(HttpRequest request) {
  context
    .decodeAudioData(request.response)
    .then((AudioBuffer buffer) {
      sampleBuffer = buffer;

      InputElement playButton = document.query('#playbutton');
      playButton
        ..value = 'play'
        ..onClick.listen((e) => _play());
    });
}
```

`AudioContext.decodeAudioData()`に`HttpRequest`で得たバッファデータを渡すことで、音源データをデコードさせます。

`decodeAudioData()`は`Future`のオブジェクトを返しますので、`then()`でデコード完了後の処理を渡します。

`decodeAudioData()`が返す`Future`は`Future<AudioBuffer>`となっており、`then()`で`AudioBuffer`のオブジェクトを受け取ることができます。このバッファを`sampleBuffer`に入れておきます。

この`then()`が呼ばれた時点で音素材の読み込みとデコードが完了しているので、音再生用のボタンの文字を`"loading..."`から`"play"`に差し替えます。

そして、このボタンが押された時に呼ばれる処理を設定します。再生時に読んでいる`_play()`の処理は以下のようになります。

```dart:dartwebaudio.dart
_play() {
  AudioBufferSourceNode source = context.createBufferSource()
    ..buffer = sampleBuffer
    ..connectNode(context.destination)
    ..start(0);
}
```

`AudioContext.createBufferSource()`で`AudioBufferSourceNode`のオブジェクトを作成し、これの`buffer`プロパティに先ほど用意した`sampleBuffer`を設定します。

`AudioSourceNode.connectNode()`で`AudioContext.destination`へノードを接続します。Web Audioにおけるノード接続の仕組みは音の様々な操作加工のために使用でき、ノードの接続を操作することによってそれを行うことができます。今回は、単にバッファの音源をそのまま再生するだけで良いので、`AudioBufferSourceNode`を最終的な音出力のノードとなる`AudioContext.destination`へ接続しています。

その後、`AudioBufferSourceNode.start()`を呼び、音の再生を行います。引数の`0`は、再生されるタイミングを指示するもので、今回は即時再生されてほしいので`0`を指定します。

以上で、音の再生が可能になります。

`dartwebaudio.dart`の全体像は以下のようになります。

```dart:dartwebaudio.dart
import 'dart:html';
import 'dart:web_audio';

AudioContext context;
AudioBuffer sampleBuffer;

main() {
  context = new AudioContext();

  HttpRequest request = new HttpRequest()
    ..responseType = 'arraybuffer'
    ..open('GET', 'sample.ogg');

  request.onLoad.listen((e) => _onLoad(request));

  request.onError.listen((e) {
    throw 'File load error';
  });

  request.send();
}

_onLoad(HttpRequest request) {
  context
    .decodeAudioData(request.response)
    .then((AudioBuffer buffer) {
      sampleBuffer = buffer;

      InputElement playButton = document.query('#playbutton');
      playButton
        ..value = 'play'
        ..onClick.listen((e) => _play());
  });
}

_play() {
  AudioBufferSourceNode source = context.createBufferSource()
    ..buffer = sampleBuffer
    ..connectNode(context.destination)
    ..start(0);
}
```

## DEMO

DEMOとして以下のページを用意しました。今回のサンプルを動かすことができます。

[Web Audio on Dart](http://zukkun.bitbucket.org/demos/dartwebaudio/)

## 動作環境

`dart2js`にてJavaScriptへコンパイルされていますので、Web Audio APIに対応したブラウザであれば、動かすことが可能だという想定でしたが、FirefoxとSafariではエラーを確認しており、今のところChromeでしか動作を確認できていません。

## 型指定

Dartの型指定はオプショナルで、`var`としてJavaScriptのように変数が型を持たない記述方法も可能ですが、今回はなるべく変数の型を指定する書き方にしました。これにより、Dartの型チェックの恩恵を受けることができます。