Help us understand the problem. What is going on with this article?

Web Audio APIでコインの音を作る

More than 3 years have passed since last update.

Web Audio APIを使えばブラウザでも簡単に音楽プログラミングができる!と思いたいのですが、どうやらAPIがあるだけでは不十分で少しばかりは音楽プログラミングの知識が必要そうです。ということで、この記事では世界で最も有名な効果音「コインの音」を題材に楽譜のパラメータ化とWeb Audio APIでの実装をできるだけ丁寧に解説します。

楽譜

sheet music

楽譜を手軽に探すには「coin sheet music」などで画像検索します。コインの音は商標出願されていますので、今回はそれを参考にします。(画像は商標プラットフォームのスクリーンショットです。)

この楽譜からは次の点が読み取れます。

  • テンポが180
  • 最初の音が十六分音符の「シ」で次の音は「ミ」
  • 全体の長さは四分音符が3つぶん

楽譜がまったく読めないという場合は、音階(ドレミファソラシド)と音長(四分音符とか)が読める程度に覚えれば良いと思います。(初学者向け教本の冒頭20ページくらいを把握していれば十分です。)

楽譜をパラメータ化する

楽譜を元にプログラムを書くにあたって次のパラメータを計算します。

  • 音高(pitch)
  • 音長(duration)

音高 (pitch)

Web Audio APIのOscillatorNodeでは音の高さを周波数(frequency)で指定します。周波数というのは1秒間に振動が繰り返される回数で、周波数が大きければ音は高くなります。音高の周波数については次の点を知っておくと良いでしょう。

  • 周波数が 2倍 になれば 1オクターブ 上がる
  • 周波数が 1/2倍 になれば 1オクターブ 下がる
  • 440Hzが「ラ」

しかし、音高を周波数で直接扱うのは面倒なので、多くの場合ではMIDIノート番号を経由して周波数を計算します。MIDIノート番号を簡単に説明するとピアノの鍵盤に番号を振ったものです。MIDIノート番号と音高の関係については次の点を知っておくと良いでしょう。

  • MIDIノート番号が +12 で 1オクターブ 上がる(周波数が 2倍 になる)
  • MIDIノート番号が -12 で 1オクターブ 下がる(周波数が 1/2倍 になる)
  • 69 が 「ラ」の 440Hz

MIDIノート番号から周波数への変換は次の関数が使えます。

function mtof(midi) {
  return 440 * Math.pow(2, (midi - 69) / 12);
}

var si = mtof(83); // →  987.7666025122483 「シ」の音
var mi = mtof(88); // → 1318.5102276514797 「ミ」の音

mtof

MIDIノート番号がパッと出てこない場合は、60が「ド」、69が「ラ」くらいを覚えておいてノートに鍵盤の絵を書いて確認したり、安めのキーボードに直接番号を書いておくと便利です。

midi keyboard

音長 (duration)

Web Audio APIでは音の長さを含む時間やスケジューリングのタイミングはで指定します。音長と時間の関係については次の点を知っておくと良いでしょう。

  • テンポ120 のとき 四分音符 は 0.5秒 である
  • テンポが 2倍 になると 長さは 1/2倍 になる
  • 八分音符 は 四分音符 の 1/2倍 の長さ 0.25秒 である
  • 二分音符 は 四分音符 の 2倍 の長さ 1.0秒 である

音の長さは次の関数で計算できます。

function tdur(tempo, length) {
  return (60 / tempo) * (4 / length);
}

var t1 = tdur(180, 16);     // → 0.08333333333333333 「テンポ180 16分音符」
var t2 = tdur(180,  4) * 3; // → 1                   「テンポ180  4分音符 x 3」

tdur

実装

上記のmtoftdurで音高と音長を元の楽譜にプロットすると次のようになります。

annotated sheet music

これをWeb Audio APIで実装します。

https://jsfiddle.net/mohayonao/c9Ly3nLo/

function coin(destination, playbackTime) {
  var t0 = playbackTime;
  var t1 = t0 + tdur(180, 16);
  var t2 = t0 + tdur(180, 4) * 3;
  var si = mtof(83);
  var mi = mtof(88);
  var audioContext = destination.context;
  var oscillator = audioContext.createOscillator();
  var gain = audioContext.createGain();

  oscillator.type = "square";
  oscillator.frequency.setValueAtTime(si, t0);
  oscillator.frequency.setValueAtTime(mi, t1);
  oscillator.start(t0);
  oscillator.stop(t2);
  oscillator.connect(gain);

  gain.gain.setValueAtTime(0.5, t0);
  gain.gain.setValueAtTime(0.5, t1);
  gain.gain.linearRampToValueAtTime(0, t2);
  gain.connect(destination);
}

function mtof(midi) {
  return 440 * Math.pow(2, (midi - 69) / 12);
}

function tdur(tempo, length) {
  return (60 / tempo) * (4 / length);
}

以下はこのコードについて解説です。

オーディオコンテキスト

Web Audio APIはオーディオコンテキスト(AudioContext)というオブジェクトを中心にデザインされています。オーディオコンテキストの主な役割はオーディオノード(AudioNode)を生成するメソッドの提供と、オーディオノードを組み合わせたオーディオグラフにもとづいた音声処理を行うことにあります。ひとつのオーディオコンテキストでも複雑なオーディオグラフを扱えますので、通常はひとつのドキュメント(オーディオアプリケーション)につきひとつのオーディオコンテキストしか必要ありません。

オーディオコンテキストは次のように生成します。

var audioContext = new AudioContext();

注意点としてSafariでは今だにwebkitプリフィクスが必要なので、事前にAudioContextを初期化しておくと良いでしょう。

window.AudioContext = window.AudioContext || window.webkitAudioContext;

オーディオグラフ

Web Audio APIには音を生成したり加工したりするための部品がいくつか用意されています。その部品のことをオーディノードと呼びます。そして、複数のオーディオノードを組み合わせたものをオーディオグラフと呼びます。

Web Audio APIのオーディオグラフは通常、OscillatorNodeやAudioBufferSourceNodeといった音源ノードで音を生成して、GainNodeやBiquadFilterNodeなどの変換ノードで音を加工、最終的にはAudioDestinationNodeという出力ノードで音声出力を行うように接続します。

コインの音では基本的な音を生成するOscillatorNodeと音量を調整するGainNode、2つのオーディオノードを使います。(デモページではさらにAnalyserNodeという分析ノードを使って波形を表示しています。)

make-coin-sound-graph.png

スケジューリング

Web Audio APIではオーディオノードの動作や将来のパラメータの変化を正確なタイミングでスケジューリングできます。スケジューリング機能を使わずにJavaScriptのタイマーAPIなどを駆使してリアルタイムにパラメータを設定することもできますが、タイミングが不正確になったりプログラムが複雑になる場合が多いです。

コインの音では t0 t1 t2、3つのタイミングでスケジューリングを行います。

OscillatorNode

OscillatorNodeは基本的な音を生成するためのオーディオノードです。typeで音の種類、frequencyで音の高さを指定できます。OscillatorNodeはAudioContextのcreateOscillator()メソッドで生成します。

var oscillator = audioContext.createOscillator();

type

音の種類を基本波形から指定します。基本波形は "sine", "triangle", "sawtooth", "square" の4つが定義されています。それぞれに音色的な特徴があるのですが、とりあえず右にいくにつれて派手に(キラキラに)なると覚えておけば良いかも知れません。

oscillator.type

コインの音では "square" を使います。

oscillator.type = "square";

frequency

音の高さを周波数で指定します。

コインの音ではmtof関数で既に計算してあるsimiに、それぞれt0t1で設定されるようにスケジューリングしています。

oscillator.frequency.setValueAtTime(si, t0);
oscillator.frequency.setValueAtTime(mi, t1);

setValueAtTime(value, startTime)startTimeになったらvalueの値をセットするメソッドです。

oscillator.frequency

再生/停止

OscillatorNodeはstart(when)stop(when)メソッドで再生と停止をスケジューリングできます。OscillatorNodeの再生は一度きりでstop()で指定したタイミングで自動的にオーディオグラフから破棄されます。破棄されたOscillatorNodeはもう一度再生することはできません。もう一度音を出したい場合はその都度OscillatorNodeを生成する必要があります。音の長さが最初から分かっている場合は、同時に呼び出しておくと簡単で良いでしょう。

コインの音ではtdur関数で既に計算してあるt0で発音を開始し、t2で停止するようにスケジューリングしています。

oscillator.start(t0);
oscillator.stop(t2);

接続

オーディオノードを組み合わせてオーディオグラフにするにはconnect(destination)メソッドを使用します。

コインの音ではOscillatorNodeをGainNodeに接続したいので次のように記述します。

oscillator.connect(gain);

接続したオーディオノードを切断するにはdisconnect()メソッドを使いますが、上述のように不要になったオーディオノードは自動的にオーディオグラフから削除されるので、明示的に呼び出す必要は特にありません。

connect()した以上、どうしてもdisconnect()したい!対称性!!!という場合は、次のようにOscillatorNodeの停止イベントで切断すれば良いです。

oscillator.onended = function() {
  oscillator.disconnect();
};

GainNode

GainNode は音の強弱や音量の遷移(エンベロープと呼ばれます)を制御するためのオーディオノードです。gainで音の大きさを指定できます。GainNodeはAudioContextのcreateGain()メソッドで生成します。

var gain = audioContext.createGain();

gain

音の大きさを比率で指定します。

コインの音ではt1からt2にかけて減衰するような台形型エンベロープをスケジューリングしています。

gain.gain.setValueAtTime(0.5, t0);
gain.gain.setValueAtTime(0.5, t1);
gain.gain.linearRampToValueAtTime(0, t2);

linearRampToValueAtTime(value, endTime)endTimevalueの値になるように線形変化させるメソッドです。このメソッドには変化の終端の値と時間を指定します。ですから必ず変化の始端になるメソッドとペアで使用する必要があります。

gain.gain

使う

coinは関数化してあるので呼び出すだけでコインの音が鳴ります。引数destinationで任意のオーディオノードに接続でき、playbackTimeで好きなタイミングに発音できます。こういう仕組みにしておくと、この音をフィルタで加工したり、リズミカルに鳴らすことが容易になります。

document.getElementById("coin").onclick = function() {
  coin(audioContext.destination, audioContext.currentTime);
};

おわり

というわけで思ったよりも長くなってしまいましたが、Web Audio APIでコインの音をつくってみました。単純な音ですが、Web Audio APIの基本が詰まった良い題材だと思います。この音から色々な音作りの練習をはじめてみると良いかも知れません。

それでは良いウェブオーディオライフを!:santa:

参考

おかしな部分や不明な点があれば気軽にコメントしてください。

mohayonao
友達がいないひと。JavaScriptで音だして遊んだりする。
https://mohayonao.github.io
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした