Web Audio APIで、簡単なシンセサイザーを作ってみる / 2: 音をフェードアウトさせる

前回からの続きです。

* 前回  
[Web Audio APIで、簡単なシンセサイザーを作ってみる / 1: まずは音を鳴らす
](http://qiita.com/tomoya_ozawa/items/5f3677a00e538415d9d5)

## 音がブツブツ鳴るのを防ぐ

前回、とりあえずキーボードを押すと音が鳴るところまで行きましたが(前回の成果は[こちら](https://jsfiddle.net/scnb3csn/2/))、キーを離す際に、「ブツッ」という途切れた音がします。

なぜこのような音が鳴るかというと、音量が急激にゼロになってしまうためです。
音の波は、1 ~ -1の範囲の中で、値が変化していくのですが(CD音源の場合だと、1秒間に44,100回変化します)、前回作ったコードでは、キーを離した瞬間に即座にオシレーターが停止 = 音量がゼロになる仕様です。
このような急激な音量の変化は、「ブツッ」というクリックノイズを発生させます。

音の波を扱う際は、必ず

* 最初は音量0の地点から音を出し、
* 終了する際は0の地点で音を止める

ようにしましょう。
最悪、音響機器を傷める可能性があります。
(傷めるようなコードを公開して申し訳ありません...)

## コード

そんなわけで、以下がブツブツ音が鳴らないよう、修正したコードです。
音を止める直前に、フェードアウトするようにしました。
このフェードアウトの時間は、0.01秒といった、ごく短い間で構いません。

ためしに、変数releaseの値を変更してみると、違いがわかりやすいかもしれません。

サンプルは[こちら](https://jsfiddle.net/7ao436ha/)です。

```javascript:synth.js
'use strict';

// Web Audio APIを利用するためのインスタンス生成
var audioContext = new AudioContext();
// キーごとにMIDIノートナンバーを割り振る
var keymap = {
  // Zキー = C4
  90: 60,
  // Sキー = C#4
  83: 61,
  // Xキー = D4
  88: 62,
  // Dキー = D#4
  68: 63,
  // Cキー = E4
  67: 64,
  // Vキー = F4
  86: 65,
  // Gキー = F#4
  71: 66,
  // Bキー = G4
  66: 67,
  // Hキー = G#4
  72: 68,
  // Nキー = A4
  78: 69,
  // Jキー = A#4
  74: 70,
  // Mキー = B4
  77: 71,
  // ,キー = C5
  188: 72
};

// キーダウンした際の処理
document.onkeydown = function(keyDownEvent) {
  // キー押しっぱなしの状態で発火した場合は、動作を終了する
  if (keyDownEvent.repeat === true) {
    return;
  }

  // オシレーターを作成
  var osciillatorNode = audioContext.createOscillator();
  // エンベロープジェネレーターを作成
  var envelopeGen = audioContext.createGain();

  // MIDIノートナンバーを周波数に変換
  var freq = 440.0 * Math.pow(2.0, (keymap[keyDownEvent.keyCode] - 69.0) / 12.0);

  // オシレーターの周波数を決定
  osciillatorNode.frequency.value = freq;

  // オシレーターをエンベロープジェネレーターに接続
  osciillatorNode.connect(envelopeGen);
  // エンベロープジェネレーターを最終出力にx接続
  envelopeGen.connect(audioContext.destination);
  // 現在の時間を取得
  var t1 = audioContext.currentTime;

  envelopeGen.gain.cancelScheduledValues(t1);
  envelopeGen.gain.setValueAtTime(1, t1);
  // オシレーター動作
  osciillatorNode.start();

  // キーを離した際に音が止まるよう、イベントを登録する
  document.addEventListener('keyup', checkKeyUp);

  // キーを離したかどうかチェック
  function checkKeyUp(keyUpEvent) {
    var release = 0.1;

    // 離したキーが、押下したキーで無い場合は処理を行わない
    if (keyUpEvent.keyCode !== keyDownEvent.keyCode) {
      return;
    }
    var keyUpTime = audioContext.currentTime;

    // キーをリリースした後にクリック音がなるのを防ぐため、現在の音量をセットする
    envelopeGen.gain.setValueAtTime(envelopeGen.gain.value, keyUpTime);

    envelopeGen.gain.linearRampToValueAtTime(0, keyUpTime + release);

    // オシレーターを停止する
    osciillatorNode.stop(keyUpTime + release);
    // 自身のイベントを削除
    removeEventListener('keyup', checkKeyUp);
  }
};
```

変更があった箇所から解説します。

```js
  // エンベロープジェネレーターを作成
  var envelopeGen = audioContext.createGain();
```

新しい概念がでてきました。エンベローブジェネレーターです。
簡単に言えば、ボリュームのつまみを新しく作成した、と思っていただければ問題ありません。
このボリュームのつまみを使って、音をブツブツ言わせることを防ぎます。

今までは、オシレーターを最終出力先であるスピーカーに直接接続していましたが、オシレーターとスピーカーの間に、ボリュームのつまみを接続します。

```js
// オシレーターをエンベロープジェネレーターに接続
  osciillatorNode.connect(envelopeGen);
  // エンベロープジェネレーターを最終出力にx接続
  envelopeGen.connect(audioContext.destination);
```
osciillatorNode.connect（）関数で、接続先を先ほど作成したボリュームのつまみに接続します。
さらに、ボリュームのつまみは、envelopeGen.connect()で、スピーカーを接続先に指定します。
これで間にボリュームを噛ませることに成功しました。ギターのエフェクターみたいなもんですね。

さて、そんなエンベローブジェネレーターですが、初期のボリュームは0になっているので、きちんとボリュームのつまみをあげなければ、音が出ません。
そこで、上記のように、ボリュームを設定しなければなりません。

```js
  // 現在の時間を取得
  var t1 = audioContext.currentTime;

  envelopeGen.gain.cancelScheduledValues(t1);
  envelopeGen.gain.setValueAtTime(1, t1);
  // オシレーター動作
  osciillatorNode.start();
```

まずは、[audioContext.currentTime](https://developer.mozilla.org/ja/docs/Web/API/AudioContext/currentTime)ですが、こちらはWeb Audio APIにて使用可能な時計です。
返す値は「ファイルを開いてから経過した秒数」です。これを使うことにより、音楽を扱う上で非常に重要な、時間を自由自在に操ることが可能です。

(例えば、今から5秒間音を鳴らしたい、といった場合には、
開始時にaudioContext.currentTimeにて現在時間を取得し、
終了時間を（audioContext.currentTime + 5）と指定すればいいわけです。)

setValueAtTime()は、ボリュームのつまみの位置を決定する関数です。第一引数がボリューム、第二引数が、音量を変更する時間です。
よって、ボリュームは最大の1、変更時間は現在とします。

cancelScheduledValues（）を使用することにより、引数以降のスケジュールをキャンセルすることが可能です。連続で操作した場合を踏まえ、このような操作を行う場合は、その前に過去のスケジュールをキャンセルすることをおすすめします。


```js
  function checkKeyUp(keyUpEvent) {
    var release = 0.1;

    // 離したキーが、押下したキーで無い場合は処理を行わない
    if (keyUpEvent.keyCode !== keyDownEvent.keyCode) {
      return;
    }
    var keyUpTime = audioContext.currentTime;

    // キーをリリースした後にクリック音がなるのを防ぐため、現在の音量をセットする
    envelopeGen.gain.setValueAtTime(envelopeGen.gain.value, keyUpTime);

    envelopeGen.gain.linearRampToValueAtTime(0, keyUpTime + release);

    // オシレーターを停止する
    osciillatorNode.stop(keyUpTime + release);
    // 自身のイベントを削除
    removeEventListener('keyup', checkKeyUp);
  }

```

自分で書いてて、果たして正解なのか、よくわかりませんが...

まず、キーを離した瞬間に、現在の音量を改めてsetValueAtTime()します。
なんだかおかしな話ですが、このようにした場合、クリック音を発生させずに音量を減少させることができました。

その後、linearRampToValueAtTime()にて、音量を減少させていきます。
第一引数は最終的な音量、第二引数が最終音量にたどり着くまでの時間です。
この関数は、setValueAtTime()と違い、第二引数の時間に最終的な音量にたどり着くよう、徐々に値が変化します。

音量が0になった時点でオシレーターを停止させます。
このようにすれば、不快なクリック音を発生させずに音を停止することが可能です。

## ADSRエンベローブジェネレーターの作成を断念

さて、変数releaseの値を1に変更して鳴らしてみましょう。
音が弦楽器のように、さらにゆっくり減衰することに気がつくはずです。

シンセサイザーでは、音の立ち上がり(Attack)、音の減衰(Decay)、減衰後の保持(Sustain)、余韻(Release)のそれぞれの時間を調節することにより、バイオリンのようなゆっくりした立ち上がりの音から、スネアドラムのような鋭角な音まで、幅広く音をシミュレートすることが可能です。
この機能を[ADSR](https://ja.wikipedia.org/wiki/ADSR)といったりします。

が、Web Audio APIでは、linearRampToValueAtTime()等で音を減衰、増加時にキャンセルをかけると音量が跳ね上がってしまったり、ここらへんのエンベローブの調節に難があるように見受けられます。
Web Audio APIで作られたシンセを見てみても、UIでごまかしていて、実際にはほとんど機能していないものも見受けられ。。

そんなわけで、早々にADSRの機能を作ることを断念したのでした。
どなたか、良い実装をご存知であれば、教えて下さい。。。

気を取り直して、次回は、シンセ以外のこともやっていきたいと思います。

## 参考文献
Web Audio APIとは全く関係無いですが、
サウンドプログラミングについては、以下の書籍が非常にわかりやすいかと思います。

* [Pd Recipe Book -Pure Dataではじめるサウンドプログラミング(単行本) ](
http://www.amazon.co.jp/Recipe-Book-Pure-Data%E3%81%A7%E3%81%AF%E3%81%98%E3%82%81%E3%82%8B%E3%82%B5%E3%82%A6%E3%83%B3%E3%83%89%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0-%E5%8D%98%E8%A1%8C%E6%9C%AC/dp/4861007801)

* [Pure Data -チュートリアル&リファレンス](http://www.amazon.co.jp/Recipe-Book-Pure-Data%E3%81%A7%E3%81%AF%E3%81%98%E3%82%81%E3%82%8B%E3%82%B5%E3%82%A6%E3%83%B3%E3%83%89%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0-%E5%8D%98%E8%A1%8C%E6%9C%AC/dp/4861007801)

ビジュアルプログラミング言語Pure Data用の書籍なので、jsとは全く違いますが、サウンドプログラミングの基本がわかりやすく学べましたので、おすすめです。