RSS Twitter Facebook

2016/12/21 (2016年12月 のアーカイブ)

最近のWebAudio APIでちょっと書いてみる


このところハードウェアばかりいじってて、たまに触らないとWebAudio APIの使い方を忘れそうなのでちょっと書いてみた。 スコアを演奏するのを最近のWebAudio APIでどれだけ簡単に書けるか、という試みです。
actx=new AudioContext();
score=[
  10,7,-3,12,-1,10,-2,7,3,-6,  10,7,-3,12,-1,10,-2,7,3,-6,
  17,8,-4,17,8,-2,14,8,-6,     15,7,-4,15,7,-2,10,7,-6,
  12,8,-4,12,8,-2,15,12,-3,14,10,-1,12,8,-2,  10,7,-3,12,8,-1,10,7,-2,7,3,-6,
  12,8,-4,12,8,-2,15,12,-3,14,10,-1,12,8,-2,  10,7,-3,12,8,-1,10,7,-2,7,3,-6,
  17,14,-4,17,14,-2,20,17,-3,17,14,11,-1,14,11,-2,15,12,-4,15,12,-2,19,15,-6,
  15,-2,10,-2,7,-2,10,-3,8,-1,5,-2,15,3,-6,
];
p=-1;
t=actx.currentTime;
setInterval(()=>{
  while(actx.currentTime>t-2){
    n=score[p=++p%score.length];
    if(n>0){
      o=new OscillatorNode(actx,{frequency:220,detune:n*100});
      g=new GainNode(actx,{gain:.3});
      o.connect(g).connect(actx.destination);
      o.start(t);
      o.stop(t+4);
      g.gain.setTargetAtTime(0,t,.9);
    }
    else
      t-=n*.4;
  }
},100);
こんな感じになりました。まあ世間はクリスマスのようですしそれっぽい感じになっています。 なお最近 WebAudio APIに色々と追加されている機能を使っていますので Chrome でしか動きません。

デモはこちらにあります。 test-kiyoshi.html
ちょっと解説。
2行目。 score という配列に入っているのが演奏データです。プラスの値とマイナスの値が混じっていますが、プラスは発音の指示、マイナスは指定の時間だけ待ついわゆるステップタイムになります。単音の演奏ではプラスとマイナスの値を交互に指定する事になりますが、プラスの値を複数続ける事で和音の発生も可能です。

11行目。 t=actx.currentTime がありますが、この t は現在時刻ではなく、次に発音するタイミングを先読み処理するために使用します。処理全体は setInterval() で回っていますが、音の発生タイミングは後で start()、stop()、setTargetAtTime() に渡されるこの t で決まります。この先読み処理をしないと処理が重くなったりタブが裏に回ったりした時にテンポが崩れる事になります。while(actx.currentTime>t-2) というループで2秒先のデータまで読んでいます。

16行目。1つの発音に対し、OscillatorとGainをそれぞれ作成し、setTargetAtTime()で音の減衰を作っています。WebAudio APIを触り始めた人が最初にはまる罠として、一度止めたOscillatorは再使用できないという仕様があるのですが、元々の設計が意図していたのは、発音1回に対してOscillatorを1つ生成するというこんな感じの使い方だったと思われます。まあ、シンセ的なものを作ろうとするとそれじゃすまないんですけどね。

さて、ノードを作るのにactx.createOscillator()じゃなくてnew OscillatorNode(actx,...)を使っています。これが使えるのは今は Chrome だけですが、一緒にパラメータの値を {frequency:220, detune:n*100} のように渡せるのが楽でいいですね。

ここでscoreから読みだした発音データを detune に渡しています。楽譜上の音の高さ(MIDIノート番号とか)を周波数に変換するには、
440*Math.pow(2,(note-69)/12)
なんていう式が良く使われてたりするのですが、オシレータには detune というcent (100セント=半音) 単位のパラメータがあり、そちらを使うとノートの番号から簡単に駆動できます。1200セントで1オクターブです。

そして18行目の
o.connect(g).connect(actx.destination)
こういう風にノードの接続を繋げて書けるのが最近の WebAudio API です。うん、書きやすい。

23行目。さて、scoreから読んだ値がマイナスの場合には、発音すべきタイミングを進めます。これで、実時間 currentTime が t の 2秒前まできた時にオシレータが準備される事になります。

WebAudio API、扱うための予備知識みたいなものが結構色々必要なのでとっつきにくいかなぁとは思うのですが、慣れてくれば簡単に書けますよ。

※なお、「きよしこの夜」って楽曲と英語歌詞の著作権は消滅してるんですが、日本語歌詞はまだ著作権残ってるんですよね。音楽系扱う時はこのあたり注意しましょう。

Posted by g200kg : 2016/12/21 01:42:39