開発ブログ

BLOG

音程練習のドローンを、音声ファイルを使わずに Web Audio で作る

選んだ音を鳴らし続けるドローンを作った。メトロノームのベース音の mp3 を使わず OscillatorNode で作った理由、倍音を重ねた PeriodicWave でオルガンの音色を作る方法、鳴らしたまま音の高さを変えてもプツッという音が出ないように、周波数を少しずつ近づける方法を書く。

公開 2026年10月12日

選んだ音を鳴らし続ける「ドローン」を使えるようになりました。ドローンに合わせて弾くと、音程がずれたときに音が揺れるように聞こえる(うなり)ので、耳で音程を合わせる練習やロングトーンの練習に使えます。

音は、C〜B の 12 音と、オクターブ(2〜5。A3 が 220 Hz)の組み合わせで選びます。音色はサイン波か、オルガンのような音です。メトロノームと一緒にも鳴らせます。ドローンは、メニューか、画面の下の帯(START などのボタンが並ぶ行)の DRONE から開きます。

ドローンのパネル。D3(146.8 Hz)を鳴らしている。番号 1 は音色の切り替え(サイン波・オルガン)、番号 2 は画面の下の帯の、アクセントカラーで光っている DRONE のボタン
画面の下の帯の DRONE から開いて、D3 を鳴らしているところ
  1. 音色はサイン波とオルガンから選ぶ
  2. 鳴っているあいだは、帯の DRONE が光る

この記事では、ドローンの音をどう作ったかを書きます。音源のファイルを使わずに Web Audio で音を作った理由、オルガンの音色の作り方、鳴らしたまま音を変えても「プツッ」という音が出ないようにする方法、メトロノームとは別に鳴らす仕組みの 4 つです。

ベース音の音源を使わず、その場で音を作る

Metronome.fm には、拍に合わせて低い音を鳴らす「ベース音」がすでにあります。ベース音の音源は、E2〜D#3 の 12 音を、音ごとに 1 つの mp3 にしたものです。

ドローンには、この音源は使いませんでした。mp3 の長さは約 1.7 秒で、拍ごとに鳴らす長さしかありません。一方、ドローンは開いているあいだずっと鳴り続けます。音域も、mp3 の E2〜D#3 に対して、ドローンはオクターブを 2〜5 から選べます。さらにドローンの画面では、基準にする A4 の高さも 415〜466 Hz で変えられます。音の高さは、これらの選び方からその場で決まります。

そこで、音は OscillatorNode で作り、周波数は基準の A4 から平均律で計算します。

export const noteFrequency = (noteIndex, octave, referenceA) => {
  const midi = (octave + 1) * 12 + noteIndex; // A4 = 69
  return referenceA * 2 ** ((midi - 69) / 12);
};

オルガンの音色を倍音で作る

サイン波は、倍音を含まない澄んだ音です。音程は聞き取りやすい一方で、スマホのような小さなスピーカーでは、低い音が聞こえにくいことがあります。そこでもう 1 つの音色として、倍音を含むオルガンのような音も選べるようにしました。

OscillatorNode には、倍音ごとの強さを並べて波の形を決める PeriodicWave を渡せます。配列の 1 つめは直流の成分で、音にはならないので 0 にしています。2 つめが基音で、その後ろに 2 倍音・3 倍音と続きます。

// 基音・2 倍音…の強さ。高い倍音を控えめにして、長く聞いても疲れにくくする
const ORGAN_HARMONICS = [0, 1, 0.5, 0.32, 0.16, 0.1, 0.05, 0.03];

const real = new Float32Array(ORGAN_HARMONICS.length);
const imag = Float32Array.from(ORGAN_HARMONICS);
oscillator.setPeriodicWave(context.createPeriodicWave(real, imag));

PeriodicWave は既定で、できた波の振幅の最大が、倍音の数や強さによらず同じになるようにそろえます(正規化)。倍音を足しても音が割れることはなく、サイン波と同じ音量の設定のまま切り替えられます。アプリと同じ処理で A3 を音声ファイルに書き出して測ると、2 つの音の最大の音量はどちらも -10.9 dB(音が割れる手前の上限を 0 dB とした値)でした。

サイン波とオルガンで、それぞれ A3(220 Hz)を 3 秒。押すと鳴ります

鳴らしたまま音を変えても、プツッという音が出ないようにする

音量や周波数をいきなり変えると、波形が途中で折れて「プツッ」と鳴ります。ドローンは鳴らしたまま音名・オクターブ・音色・音量を変えられるので、値は setTargetAtTime で少しずつ目標に近づけます。

const now = context.currentTime;
// 音の高さは時定数 10ms、音量は 40ms で寄せる
oscillator.frequency.setTargetAtTime(frequency, now, 0.01);
gain.gain.setTargetAtTime(toGain(volume), now, 0.04);

setTargetAtTime は、目標の値へ指数関数のカーブで近づけます。時定数は変わる速さを決める時間で、その 3 倍の時間で差の 95% ほどが埋まります。音の高さ(10ms)なら 30ms ほど、音量(40ms)なら 120ms ほどです。音の高さを短くしたのは、音名を変えたときに音がすべって聞こえないようにするためです。音量は、プツッと鳴らないよう長めにしています。

鳴り始めは、音量を 0 から上げていきます。止めるときは、音量を 0 へ下げていき、十分に下がった 0.3 秒後に OscillatorNode を止めます。

音量の大きさそのものの決め方も書いておきます。音量のスライダー(0〜100%)の値は、2 乗してから、音にかける倍率(GainNode の値)にしています。100% のときの倍率は 0.3 で、メトロノームのベース音の倍率(0.22)と一緒に鳴らしても埋もれない大きさにしました。

const toGain = (volume) => 0.3 * volume ** 2;

メトロノームとは別に鳴らし、閉じたら止める

メトロノームの音は、AudioWorklet が、画面の処理とは別の音声専用のスレッドで拍ごとに鳴らしています。ドローンはこれとは別の AudioContext で鳴らすので、メトロノームを止めても鳴り続け、メトロノームと同時にも鳴らせます。

iPhone では、画面を消してもドローンが鳴り続けるようにしています。そのため、鳴らし始める前に Audio Session を再生用(playback)にします。

ドローンは、帯に DRONE のボタンがあればその上のパネルで、なければモーダルの画面で開きます(帯に出すボタンは表示設定で選べます)。どちらも開いているあいだだけ鳴らし、閉じたら止めます。どちらも画面の下の帯を隠さずに開くので、開いたまま帯の START でメトロノームを鳴らせます。

この記事を共有

X でポストはてなブックマーク