広告の限界を超える|セールス
広告の限界を超える|セールス
2016.09.13
#14
それいけ!フロントエンド

笙(しょう)の和音でみやびな気分!続々「Web Audio API」入門

つっちー

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

前回前々回では、音源として音声ファイルを扱いました。
次は音源もWebAudioAPIで生成してみましょう。

・この記事を読むときは、お使いのブラウザの他のタブ、ウインドウでWebAudioAPIが使用されていないことをご確認願います。
・コードプレビューのJS全体が即時関数で囲われている場合、その即時関数は実際には不要であると考えてください。

Webブラウザは同時に存在できるAudioContextオブジェクトの数に制限をかけています。
通常、AudioContextオブジェクトが1アプリケーションに複数必要となることはなく、この制限に困ることはありません。
ただし、複数のタブ、ウインドウ、iframeでWebAudioAPIが使用される場合にはエラー発生の可能性があるため、本記事では処理を即時終了させることで対策を行っております。

ブラウザ上で音源を生成し再生するWebサイト

Javascript Drone

waa011
http://matt-diamond.com/drone.html
こちらは、以前にも紹介したドローンを再生するWebサイトです。

このドローンは複数の音源を同時発音することで表現されていて、その音源はWebAudioAPIを用いてブラウザ上で生成されています。
今回は、同じようにWebAudioAPIで音源を生成し、雅楽で用いられる管楽器、笙(しょう)(っぽいの)を作っていきます。

「Web Audio API」を使ってWebブラウザで笙(しょう)を作ろう

笙(しょう)ってどんな楽器?

「雅楽」は、お正月などに耳にしたことがあるかと思います。
その中でもひときわ神秘的な雰囲気を醸し出している、ミャーという音、あれが笙です。

合竹(あいたけ)と呼ばれる和音の演奏が中心で、吹いても吸っても音が出るため、音を長く持続できるそうです。
形は翼を立てて休んでいる鳳凰(ほうおう)に見立てられ、音色は天から差し込む光を表すのだとか。素敵ですね。

まず音を出してみる

今回は、音源として、周期的な波形の音を発生させるOscillatorNodeというAudioNodeを使用します。

See the Pen 160801 by lig-dsktschy (@lig-dsktschy) on CodePen.

 

わたしたちは空気の振動を音として感じ取っています。
空気の振動は波形として表現することができ、代表的な波形としては正弦波、矩形波、三角波、のこぎり波の4つがよく挙げられます。

 

160801
Wikipediaより)

 

OscillatorNodeはこの4つの波形を発生させることができ、波形の種類はtype属性に文字列を設定することで指定します。

sine: 正弦波
square: 矩形波
triangle: 三角波
sawtooth: ノコギリ波

 

See the Pen 160802 by lig-dsktschy (@lig-dsktschy) on CodePen.

音の高低は、波形の繰り返し回数によって変動します。
速く多く繰り返されるほど高い音遅く少なく繰り返されるほど低い音となります。

ある時間中の波形の繰り返し回数を周波数とよび、1秒間の回数はHz(ヘルツ)という単位で表されます。つまり、周波数が大きいほど高い音小さいほど低い音ということになります。

OscillatorNodeは、frequencyという属性に、AudioParam(前回記事参照)を継承するオブジェクトを持ち、これによって周波数を表します。
frequencyオブジェクトは、defaultValue属性が440(単位:Hz)に設定されていて、value属性に任意の不動小数点数(単位:Hz)を設定することで、周波数を上書きすることができます。

 

See the Pen 160803 by lig-dsktschy (@lig-dsktschy) on CodePen.

 

では今回も、まずは最終出力に直接接続して再生してみましょう。

 

See the Pen 160804 by lig-dsktschy (@lig-dsktschy) on CodePen.

音源をブラウザ上で生成することができました!

再生を確認できましたら、type属性を変更し、4つの波形にどのような音色の違いがあるのか、確かめてみてください。個人的には、ノコギリ波(sawtooth)の波形が笙の音色に似ているように感じます。

ということで、ここからはtypeをsawtoothに設定して進めていきます。

使い捨てのオブジェクトを使い続ける

OscillatorNodeは、AudioBufferSourceNode(前回前々回記事参照)と同じく、使い捨てのオブジェクトであるため、一度停止した後は再生することができません。
今回のように時間経過による変化がない音源を使用する場合、GainNode(前回記事参照)によって音量を0に切り替えることで、停止を代用することができます。

See the Pen 160805 by lig-dsktschy (@lig-dsktschy) on CodePen.

ただ、この停止方法では、聞こえていないだけで、再生は音量を0にしても続いています。楽曲のように時間経過によって音源の状態が変化していく場合、この停止方法では停止直前と再生直後とで音源の状態が繋がらないため、注意が必要です。

より演奏している感覚に近付けるため、クリック・タッチしている間だけ再生されるようにしてみましょう。

 

See the Pen 160806 by lig-dsktschy (@lig-dsktschy) on CodePen.

ちょっと楽器感が出てきたのではないでしょうか?
次は和音を発生させてみます。

和音をつくってみる

まず、笙が出す音の周波数を、配列として列挙しましょう。
平均律(前々回記事参照)とは異なる調律がされているため、こちらのサイトを参考にさせていただきました。
>>笙吹きロバの笙の調律コーナー

See the Pen 160807 by lig-dsktschy (@lig-dsktschy) on CodePen.

次に、上で挙げた配列を用いて、笙の和音の構成もまた、配列として列挙します。
Wikipediaを参考に、6音で構成される和音のみピックアップしました。

 

See the Pen 160808 by lig-dsktschy (@lig-dsktschy) on CodePen.

OscillatorNodeは、1つにつき1つの波形しか発生させることができないため、和音は複数のOscillatorNodeを同時に再生させることで表現します。
和音を構成する6音分のOscillatorNodeを、それぞれの周波数に設定し、最終出力まで接続しましょう。

 

See the Pen 160809 by lig-dsktschy (@lig-dsktschy) on CodePen.

和音を再生することができましたね!
さらに、クリック・タッチのたび和音の種類がランダムに選択されるようにしておきます。

 

See the Pen 160810 by lig-dsktschy (@lig-dsktschy) on CodePen.

実際に演奏される際の音の入り、音の終わりは、上のサンプルのようにいきなり最大音量、いきなり無音というわけではなく、なめらかに音量が上下します。
ということで、仕上げにフェードイン・フェードアウトをつけてみましょう。

フェードイン・フェードアウトさせてみる

AudioParamオブジェクトは、value属性、defaultValue属性の他に、時間とともに値を変動させる便利なメソッドをいくつか持っており、それらはオートメーションメソッドと呼ばれています。

オートメーションメソッドの一つに、value属性の値を、現在の値から指定した値まで、指定した時間までの間に変動させる、linearRampToValueAtTimeというメソッドがあります。
これによってGainNodeのgain属性の値を変動させることで、フェードイン・フェードアウトを実現しましょう。

WebAudioAPIでは、現在時刻をAudioContextのcurrentTime属性に保持しており、単位は秒となっています。

See the Pen 160811 by lig-dsktschy (@lig-dsktschy) on CodePen.

linearRampToValueAtTimeを使用する場合、value属性の現在の値を現在時刻と結びつけておく必要があります。これをせずに実行すると、値が即時に変動してしまったりと、設定内容とは異なる挙動が発生してしまいます。

value属性の現在の値と現在時刻とを結び付けるには、同じくオートメーションメソッドのsetValueAtTimeメソッドを使いましょう。
setValueAtTimeメソッドは、value属性の値を、指定した値へ、指定した時間に即時変更します。

これを、目標値をvalue属性の現在の値、変更時刻を現在時刻として実行することで、結び付けは完了です。
現在の値から現在の値への上書きなので、見かけ上は何も発生しません。

 

See the Pen 160812 by lig-dsktschy (@lig-dsktschy) on CodePen.

次に、フェードインの最中にフェードアウト、もしくは、フェードアウトの最中にフェードインが発生した場合の対策を施しましょう。
これもまた、後に発生したメソッドの完了後に、先に発生していたメソッドの目標値へ再度変動してしまうなど、想定外の挙動が発生してしまうためです。

AudioParamオブジェクトのcancelScheduledValuesメソッドは、オートメーションメソッドによって未来に登録されている値の変動を、すべてキャンセルしてくれます。
これをsetValueAtTimeの前に実行しておくことで、イベント発生中の別イベント発生に対処することができます。

 

See the Pen 160813 by lig-dsktschy (@lig-dsktschy) on CodePen.

また、このままでは、途中から発生したフェードイン・フェードアウトにも常に3秒の時間をかけてしまうため、最後にそこも対策しておきましょう。

 

See the Pen 160814 by lig-dsktschy (@lig-dsktschy) on CodePen.

このページではこれ以上AudioContextオブジェクトを作れない……! というわけで、最後の実行結果はこちらでご確認ください。

 

See the Pen 1608-completed by lig-dsktschy (@lig-dsktschy) on CodePen.


[実行結果]

ついでに背景もクリック・タッチに連動させてあります。天から差し込む光を表……せているでしょうか? 完成です!

まとめ

今回は、音源をブラウザ上で生成して扱う方法を紹介しました。

WebAudioAPIでは、4つの波形以外にも、オリジナルの波形を生成したり、複雑なノイズを生成することも可能です。

音声ファイルを予め音源として用意する場合と比べ、複雑なことを実現するのは大変ですが、その分もっと自由に音を生成することができるはずです。
ぜひ、いろいろ試してみてください。

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