NTTドコモ様_dカーシェア
NTTドコモ様_dカーシェア
2016.08.25
#8
それいけ!フロントエンド

雨音でリラックス!続「Web Audio API」入門

つっちー

こんにちは。フロントエンドエンジニアのつっちーです。

前回は、WebAudioAPIを使って、Webブラウザで動作するピアノを作成しました。
みなさんのブラウザでも弾くことができましたでしょうか。

ピアノの実装では音源と最終出力を直接接続しましたが、次は中間処理を挟んでみましょう。

この中間処理こそ、WebAudioAPIならではの機能であり、HTMLのAudio要素との最大の違いと言える部分です。

雨音を再生し続けてくれるサイト

リラックスや集中のためのBGMとして、雨音を延々と再生し続けてくれる、そんなWebサイトがいくつも公開されています。

たとえばこちら。

Rainy Mood

rainymood
http://www.rainymood.com/

天気としては鬱陶しい雨ですが、こうして音だけ聴くと心地よく感じませんか?
今回は、WebAudioAPIの中間処理を使って、このような雨音再生機能に音質変更機能を追加してみましょう。
 

「Web Audio API」を使って雨音再生サイトを作ろう

AudioBufferSourceNode と MediaElementAudioSourceNode

音声ファイルを音源をとして利用するためのAudioNodeとしては、前回使用したAudioBufferSourceNodeの他に、MediaElementAudioSourceNodeも使用されます。

それぞれに適した音源と利用ケースがあるため、考慮した上で使い分ける必要があります。

AudioBufferSourceNode

・適した音源

短い音源
(例:ピアノや打楽器などのワンショット音源)
音声データ取得時に、データ全体がデコードされるまで待機が必要であるため、長い音源に不向き。

・適した利用ケース

発音タイミングの正確な制御が必要な場合
(例:ゲームの効果音)

・その他の特徴

使い捨てのオブジェクトになるため、再生のたびに再生成が必要。

MediaElementAudioSourceNode

・適した音源

長い音源
(例:楽曲の音源、数十秒持続する音源)
データ全体の取得を待たずに再生開始が可能。

・適した利用ケース

発音タイミングの正確さが重視されず、ループ時に途切れず再生されるべき場合
(例:BGM)
PCのみをサポートする場合。
スマートフォンのブラウザでは、audio要素としてのコントロールは可能だが、WebAudioAPIの音源としては機能しない。

・その他の特徴

一度生成したオブジェクトを繰り返し利用可能。
 

今回は、長く持続する雨音の音源を利用します。

作品名 Rain-Real_Ambi02-1
音源ファイル http://musicisvfr.com/free/se/weather01.html
作者名 Music is VFR

 
途切れずに再生され続ける必要もあり、この場合はMediaElementAudioSourceNodeが適していると言えるでしょう。
ただ、MediaElementAudioSourceNodeに対応していないスマートフォンのブラウザは、残念ですが今回はあきらめることとします。

まずは音を出してみる

MediaElementAudioSourceNodeは、名前が示すとおり、HTMLのaudio要素やvideo要素を音源として使用します。MediaElementAudioSourceNodeによって音源として利用される場合も、audio要素やvideo要素が元々持っている機能はそのまま利用できます。

つまり、再生、停止や、ループ、自動再生機能などが、audio/video要素単独で使用される場合と同様に動作します。audio/video要素の音声に、WebAudioAPIの中間処理によってエフェクトをかけることができる、と考えた方がわかりやすいかもしれません。

HTML

<audio src="media/rain.mp3" id="audio" preload="auto" loop></audio>

JavaScript

var
    // AudioContextを生成
    ctx = new AudioContext(),
    // audio要素を取得
    elAudio = document.getElementById('audio'),
    // AudioContextからAudioNodeを生成
    // MediaElementAudioSourceNode: audio/video要素を音源として扱うAudioNode
    mediaElementSource = ctx.createMediaElementSource(elAudio);

ではまず、前回同様、最終出力に直接接続して再生してみましょう!

AudioBufferSourceNodeと違い、一度生成したAudioNodeを繰り返し利用していることに注目してください。MediaElementAudioSourceNodeの再生/停止には、audio要素の再生/停止メソッドを使用します。

HTML

<button id="button">Play / Stop</button>

JavaScript

// 音源を表すAudioNodeを、最終出力を表すAudioNodeに接続
mediaElementSource.connect(ctx.destination);
// DOMへのイベント登録
var isPlaying = false;
elButton.addEventListener('click', function() {
    elAudio[!isPlaying ? 'play' : 'pause']();
    isPlaying = !isPlaying;
});

以上をまとめて、オマケに雨っぽい背景をつけてみたものが、こちら
[ソースコード(GitHub)]
[サンプル]

降ってきましたね! ただこれはなにも中間処理を挟んでいないため、audio要素をそのまま再生する場合と変わらない結果になりました。

ではいよいよ、音量や音質を変化させていきましょう。

音量を変化させてみる

中間処理を表すAudioNodeの多くは、その属性として、自身が担う処理の状態を表すAudioParamを継承するオブジェクトを持ちます。

AudioParamオブジェクトは、処理の状態を表すvalue属性、valueの初期値を表すdefautValue属性、他にも様々なメソッドを持ち、これらの属性、メソッドは、AudioParamを継承する全てのオブジェクトから同様に扱うことが可能です。

ただし、AudioParamを継承するオブジェクトの名前は、AudioNodeごとに異なるため、注意が必要です。

音量調整処理を表すAudioNodeは、GainNodeです。GainNodeは、gainという属性に、AudioParamを継承したオブジェクトを持ちます。

gainオブジェクトは、defaultValue属性が1に設定されていて、value属性に元の音量を1、消音状態を0とする浮動小数点数を設定することで、音量を上書きすることができます。

// AudioContextからGainNodeを生成
// GainNode: 中間処理(音量調整処理)を表すAudioNode
gain = ctx.createGain();
// GainNodeを、接続された音源を半分の音量に処理するよう設定する
gain.gain.value = 0.5;

このGainNodeを、音源を表すAudioNodeと、最終出力を表すAudioNodeの間に接続することで、設定した音量で音源を再生する準備の完了です。

// 音源を表すAudioNodeを、中間処理(音量調整処理)を表すAudioNodeに接続
mediaElementSource.connect(gain);
// 中間処理を表すAudioNodeを、最終出力を表すAudioNodeに接続
gain.connect(ctx.destination);

以上をレンジバーから調節できるようにしたものが、こちら。
[ソースコード(GitHub)]
[サンプル]

静かな雨に調節できました!

複数の中間処理を挟んでみる

中間処理は、複数接続することで追加が可能です。音量に加えて、音質も調整するため、ローパスフィルター、ハイパスフィルターと呼ばれる処理を追加してみましょう。

ローパスフィルターは、設定した周波数よりも低い周波数を通過させ、それより高い周波数を遮断する処理のことを指します。ハイパスフィルターはその逆で、設定した周波数よりも高い周波数を通過させ、それより低い周波数を遮断する処理のことを指します。

これらの処理は、BiquadFilterNodeというAudioNodeによって表すことができます。

// AudioContextからBiquadFilterNodeを生成
biquadFilter = ctx.createBiquadFilter();

BiquadFilterNodeは、typeという属性を変化させることで、さまざまなフィルター処理を表します。

type属性は値として文字列をとり、’lowpass’を設定することでローパスフィルターを、’highpass’を設定することでハイパスフィルターを表すAudioNodeとして機能します。

他にも設定可能な文字列がいくつか存在しますが、今回は効果の分かりやすいこの二つを使用してみます。

// BiquadFilterNodeを、ローパスフィルター処理を行うよう設定する
biquadFilter.type = 'lowpass';

BiquadFilterNodeは、AudioParamを継承するオブジェクトを複数持っており、その内のfrequencyという属性によって、境界となる周波数(単位:Hz)を設定することができます。

frequencyオブジェクトは、defaultValue属性が350に設定されていて、value属性のとりうる最小値は10、最大値は音源のサンプルレートの半分の値です。

// BiquadFilterNodeを、1000Hz以の周波数をカットするローパスフィルターとして機能させる
biquadFilter.frequency.value = 1000;

value属性の範囲が前述のようになる理由は、この範囲外の周波数はデジタル信号からアナログ信号へ復元できず、また人間の可聴範囲(20~20000Hz)からも大きく外れているためです。

音源のサンプルレートはファイルのプロパティとして確認でき、多くの場合44100Hzに設定されています。

samplerate

このBiquadFilterNodeを、音源を表すAudioNodeと、最終出力を表すAudioNodeの間に接続。これで、フィルター処理を加えた音源を再生する準備の完了です。

// 音源を表すAudioNodeを、中間処理(音量調整処理)を表すAudioNodeに接続
mediaElementSource.connect(gain);
// 中間処理(音量調整処理)を表すAudioNodeを、さらに中間処理(フィルター処理)を表すAudioNodeに接続
gain.connect(biquadFilter);
// 中間処理(フィルター処理)を表すAudioNodeを、最終出力を表すAudioNodeに接続
biquadFilter.connect(ctx.destination);

以上をセレクトボックスとレンジバーによって変更できるようにしたものが、こちら。
[ソースコード(GitHub)]
[サンプル]

このサンプルのソースコード中には、disconnectという新しいメソッドが使われており、これでAudioNodeから次のAudioNodeへの接続を切断することができます。

引数にAudioNodeを指定することで、そのAudioNodeとの接続のみを切断することも可能ですが、ここでは引数を指定せず全ての接続を切断しています。

connectメソッドにより接続されたAudioNodeは、disconnectメソッドを用いて明示的に切断しない限り、接続が持続します。これは、ひとつのAudioNodeから複数のAudioNodeへ接続しうるためです。

このサンプルでは、

BiquadFilterがオフの場合は
GainNode → 最終出力

BiquadFilterがローパスフィルターかハイパスフィルターに設定されている場合は
GainNode → BiquadFilterNode → 最終出力

と、AudioNode同士の接続を変更するために使用されています。

// GainNode、BiquadFilterNodeともに、一度切断
gain.disconnect();
biquadFilter.disconnect();
// BiquadFilterがオフの場合は
if (text === 'off') {
    // GainNode → 最終出力
    gain.connect(ctx.destination);
// BiquadFilterがローパスフィルターかハイパスフィルターに設定されている場合は
} else {
    // GainNode → BiquadFilterNode → 最終出力
    gain.connect(biquadFilter);
    biquadFilter.connect(ctx.destination);
}

これで、音量に加えて、低くこもった音、高く軽い音など、雨音の音質が調整できるようになりました。完成です!!

まとめ

今回は雨音に対して効果がわかりやすい中間処理をピックアップして利用しましたが、遅延や聞こえてくる方向など、この他にもいろんな要素を調整することができます。

また、音源ファイルを置き換えれば、他の環境音や、楽曲にも同様の効果をつけることができますので、ぜひ試してみてください。

ではまた! つっちーでした。