shonen.hateblo.jp

やったこと,しらべたことを書く.

WEBブラウザ上で生成したメロディを再生する方法(WEB Audio API)

introduction

特定のフォーマットのファイルを準備せず,音を動的に生成して再生する

  • 通常WEBブラウザ上で音を鳴らす時は,音楽ファイル(mp3やmidiなど)を準備して,それを再生する場合が殆ど.
  • DTMのようなものをWEBブラウザ上で作りたい場合,すべての音階の音楽ファイルを用意するか,音を動的に生成できるようにするか.

javascriptで音を生成するには?

Web MIDI API について試したところ,OS標準のMIDIは反応しなかった.

なので,特に事前準備が少なそうなWeb Audio API を使うことにします.

参考URL

1

非常に分かりやすい日本語のチュートリアルサイトです.僕が追加で説明する必要は無いと思います.

weblike-curtaincall.ssl-lolipop.jp

2

いい感じに見やすく,リファレンスマニュアルとしてまとまっています

www.javascripture.com

コーディング

仕組みについては,参考URL1を見てください.

音を鳴らす

たった5行で音が鳴ります.

    const context = new AudioContext();
    const oscillator = context.createOscillator();
    
    // oscillator -> context.destination
    oscillator.connect(context.destination);
    
    oscillator.start(0);
    setTimeout(()=>{ oscillator.stop(0); }, 1000);

oscillator という Node が正弦波を生成してcontext.destination に流し込んでいる訳ですね.

最後の行を setTimeout(()=>{ oscillator.disconnect(); }, 1000); に書き換えても音は止まる. 繋がっているコードを引き抜いているイメージでしょうか*1

音を変える・同時に鳴らす

OscillatorNode のプロパティについては https://www.javascripture.com/OscillatorNode が参考になります.

同時に2つの OscillatorNode を鳴らすには,2つとも context.destination に繋いでしまえば良さそう.

    const context = new AudioContext();
    const oscillator1 = context.createOscillator();
    const oscillator2 = context.createOscillator();
    
    oscillator1.connect(context.destination);
    oscillator2.connect(context.destination);
    oscillator1.type = 'square';
    // note: effectiveFrequency = frequency * pow(2, detune / 1200)
    oscillator1.detune.value = 0;
    oscillator2.detune.value = 0;
    
    oscillator1.start(0);
    oscillator2.start(0);
    setTimeout(()=>{ oscillator1.stop(0); }, 2000);
    setTimeout(()=>{ oscillator2.stop(0); }, 2000);
    
    setInterval(()=>{ oscillator1.detune.value += 100; }, 200);
    setInterval(()=>{ oscillator2.frequency.value += 10; }, 20);

音の大きさを変える

音の大きさを変えるパラメータが無いなぁと思ったら,GainNode でコントロールするんですね.

つまり, OscillatorNode -> GainNode -> context.destination と繋ぎます.

    const context = new AudioContext();
    const oscillator1 = context.createOscillator();
    
    const gain = context.createGain();
    gain.gain.value = 0.2;
    
    oscillator1.connect(gain);
    gain.connect(context.destination);
    
    oscillator1.type = 'square';
    oscillator1.detune.value = 0;
    
    oscillator1.start(0);
    setTimeout(()=>{ oscillator1.stop(0); }, 2000);

delay

DelayNode があったので,これを使ってみます.

ただし,OscillatorNode -> DelayNode -> context.destination と繋げると本当にただ遅延して流れるだけ. なので,反響しているかのように音を鳴らすには OscillatorNode -> context.destinationOscillatorNode -> DelayNode -> context.destination の2つのパスによる接続が必要.

下の例では,OscillatorNode -> GainNode[分岐点] -> [合流点]context.destination と,GainNode[分岐点] -> DelayNode -> GainNode -> [合流点]context.destination で繋いでいます.

    const context = new AudioContext();
    const oscillator1 = context.createOscillator();
    
    const gain = context.createGain();
    gain.gain.value = 0.5;
    
    const delay = context.createDelay();
    delay.delayTime.value = 0.12;
    const delaygain = context.createGain();
    delaygain.gain.value = 0.3;
    
    oscillator1.connect(gain);
    gain.connect(context.destination);
    gain.connect(delay);
    delay.connect(delaygain);
    delaygain.connect(context.destination);
    
    oscillator1.type = 'square';
    oscillator1.detune.value = 0;
    
    oscillator1.start(0);
    setTimeout(()=>{ oscillator1.stop(0); }, 2000);
    
    setInterval(()=>{ oscillator1.detune.value += 100; }, 200);

感想

たのしい!!

caustic3 の modular っぽい.

CAUSTIC 3 | Single Cell Software

*1:このコードで試しても差は感じられないと思います.後半で説明しているdelayのサンプルではstopとdisconnectの違いを感じ取ることができるかもしれません.