• 7
    Like
  • 0
    Comment

■WebAudio入門■

この記事は WebAudio Web MIDI API Advent Calendar 2016の1日目です。

■昔話■

いわゆるパソコン通信が若干なりを潜めた頃、心地よいダイヤルアップの音を横目に回線をつないで、
「検索するときにはGoogleがいいんだよ!」とドヤ顔で自慢しつつ、Googleにアクセスしていました。
驚くべきことに今とほぼ同じトップのレイアウトだった記憶があります。

そいで雑誌で見かけた探してほしいサイトの情報を検索欄に入力。
ようやっと出てきたサイトがmarqueeタグ使いまくっててどこからDLしたのか分からないフリーのmidiバリバリ鳴ってて、
「うるせゴルァ!」と開口一番速攻でスピーカーの音量0%にした時代からかなり時間が経ちました。

HTML5Audio, WebAudio、もしくはWebGLみたいなもっとスピーカーやらヘッドホンやらGPUを揺らして騒がしくしましょう的なアプローチが
現時点で比較的新しい技術的パラダイムとして認知されているのはとても面白いと思います。

いまだに新作のゲームやらのサイトで音が最初から鳴るあの具合は毎回ドキっとして、
それ以上思考が回らないです(素直)
もちろん大半は音再生だけのためのFlashだったりします(♪マークをクリックすると音が鳴らなくなるアレ)
もっとWebAudio(HTML5 Audio)使っても良いのにね。

■はじめに & 前提■

HTML5 AudioのWebAudioAPIは、ブラウザ上でサウンドを合成、再生するための上位APIです。
理解する前提として簡単なJavaScriptの文法、仕組みを理解している必要があります。
また、DirectSoundや簡単な音声処理について理解している必要があります。

この記事は100行以内に記載したソースを解説して終わろうと思います。

■参考サイト■

https://www.html5rocks.com/ja/tutorials/webaudio/intro/

■全ソース■

<script>

//(0)----------------------------------------------------
var buffer_size  = 16384; //power 2
var divide       = 10;
var finetune     = 512.0;
var gain         = 0.01;

//https://www.youtube.com/watch?v=oFXMxIvZeMk
var score =
[
    0, 12, 11 - 0, 12, 16, 12, 11 - 0, 12,
    0, 12, 11 - 1, 12, 16, 12, 11 - 1, 12,
    0, 12, 11 - 2, 12, 16, 12, 11 - 2, 12,
    0, 12, 11 - 3, 12, 16, 12, 11 - 3, 12,
];

//webaudio.

//(1)----------------------------------------------------
var audio        = new (window.webkitAudioContext || window.AudioContext);

//(2)----------------------------------------------------
var scriptproc   = audio.createScriptProcessor(buffer_size);

//(3)----------------------------------------------------
scriptproc.connect(audio.destination);
scriptproc.onaudioprocess = audiohandler;

//(4)----------------------------------------------------
var sample_pnote = audio.sampleRate / divide;
var basepitch    = finetune / audio.sampleRate;

//(5)----------------------------------------------------
var voice = function() {
    this.ph = 0;
    this.acc = 0;
    this.on = function(note) {
        //http://ja.wikipedia.org/wiki/%E5%B9%B3%E5%9D%87%E5%BE%8B
        this.acc = Math.pow(2.0, note / 12.0) * basepitch;
    }

    this.get = function() {
        this.ph += this.acc;
        var g0 = Math.sin(this.ph * 3.141592 * 2);
        return g0 > 0.0 ? -1 : 1;
    }
};
vo = new voice();


var remain = 0;
var index  = 0;
function audiohandler(event) {
    //(6)----------------------------------------------------
    var outL = event.outputBuffer.getChannelData(0);
    var outR = event.outputBuffer.getChannelData(1);
    for(var i = 0 ; i < outL.length; i++) {
        remain--;
        if(remain <= 0) {
            var note = (score[index % score.length]);
            vo.on(note);
            remain = sample_pnote;
            index++;
            console.log(note);
        }
        var out  = vo.get()
        outL[i]  = out * gain;
        outR[i]  = outL[i];
    }
}

</script>

■実行方法■

↑のソースをtest.htmlみたいな感じで保存してブラウザにD&Dすれば音が鳴ると思います。

■ソース概要■

このソースは某シューティングゲームの有名なフレーズを矩形波を作り出し、
WebAudioに波形の信号バッファを渡して繰り返し再生するものです。

■少しずつ解説■

(0)
もろもろ使う定数群です。

(1)
WebAudioを使うには、AudioContextを取得しなければなりません。
今後も変わる可能性がありますが、慣習に従って以下の通り取得を試みます。

var audio = new (window.webkitAudioContext || window.AudioContext);

(2)
createScriptProcessorはJavaScriptから直接音声処理を行うためのNode
ScriptProcessorNodeを生成します。要はこれで生成したAPIをベースにして
音声処理を書くことになります。
ここで(0)で定義したバッファサイズを渡しています。

(3)
取得したscriptprocのconnectに(1)で取得した出力先を指定します。
併せて、音声処理が必要になった際にコールされるコールバックを指定します(audiohandler)

(4)
AudioContextで取得したオーディオバッファのビットレートに応じて、
(0)で定義したサンプルのピッチを指定します。

(5)
voiceという名で音声処理を行うfunction群を定義しておきます。
onメソッドは、渡されたbasepitchに基づいて、noteを平均律で位相の差分を計算します。
getメソッドは、onメソッドで決定した位相差分に基づいて矩形波を生成します。
もっと簡易な方法があるかもしれませんが、ここではsin波の符号で矩形波を簡易的に生成し、戻り値として返却しています。

(6)
(3)で指定したコールバックの処理を記述しています。ここは簡易なシーケンサです。
event.outputBuffer.getChannelData(0 or 1)で左(0)、右(1)の
チャンネルのバッファを取得することができます。
(5)で指定した処理が終わったら(remainがなくなったら)
左のチャンネルの長さ分のバッファをscoreで平均律に従ってpitchを上下させて詰める、という処理をします。
※古典的にテーブルで処理するのもありですが、あえて関数pow使って簡易的に記載しています。

■平均律■

https://ja.wikipedia.org/wiki/%E5%B9%B3%E5%9D%87%E5%BE%8B

なお、詰めるサンプルデータは1.0~-1.0の浮動小数点数を入れてあげます。
超えた場合はブラウザによってnormalizeされたり、音がそもそも出なくなったり、内部的にIEEE_FLOATの
フォーマットになってる場合はデバイス依存になりますので、挙動が不定になります。

サンプルコードでは右のチャンネルのバッファには左のチャンネルの内容をそのままコピーします。
別にステレオイメージャーなことはしないからです。

■まとめ : 全体の流れ■

audiohandlerがChrome, Firefoxの仕様に応じてコールされ、受け取ったオーディオバッファに音声波形を詰めて、
ネイティブの音声処理用APIに渡されてスピーカーが鳴るという仕組みです。

■終わりに■

JS + ブラウザで簡単に音声処理を書くことができます。
短いソースで可読性を失わずに書けるのは大きな魅力だと思います。

Win32やらMacネイティブで音声処理を下回りから記載していくと結構面倒なコードだらけになるので、
簡単な音声処理のラフを書く場合はとても便利だと思います。

JS書きまくってる方 or Win32やらネイティブで音声処理書きまくってる方には簡単な内容だったかもしれません。
ここ変だよ!というのがあれば遠慮なくコメントに。

かけあしになってしまいましたが、ありがとうございました。