Web Audio APIでボリューム・パン・フィルタいじるサンプル
ブラウザゲーに音つけたいからWeb Audio APIいじりはじめた。
今回はあらかじめ用意したwavファイルを音源にしました。オシレータは使ってません。(ピコピコしたまぎらわしい音ですみません)
※音が出ます
Master
<div id="app-wrapper">
<div id="app-cover">
<p style="color: red">※音が出ます</p>
<button type="button" id="start-button">開始</button>
</div>
<div class="track">
Master
<dl class="slider">
<dt>Vol</dt>
<dd>0<input type="range" min="0" max="1" step="0.01" value="0.8" id="volume_master" />1</dd>
</dl>
</div>
<div class="track">
<select id="select-audio_t1"></select>
<button type="button" class="play-button" id="play_t1">play</button>
<dl class="slider">
<dt>Vol</dt>
<dd>0<input type="range" min="0" max="1" step="0.01" value="0.8" id="volume_t1" />1</dd>
</dl>
<dl class="slider">
<dt>Pan</dt>
<dd>L<input type="range" min="-1" max="1" step="0.01" value="0" id="pan_t1" />R</dd>
</dl>
<dl class="slider">
<dt>LP</dt>
<dd>0<input type="range" min="0" max="20000" step="10" value="20000" id="lp_t1" />20000</dd>
</dl>
</div>
<div class="track">
<select id="select-audio_t2"></select
><button type="button" class="play-button" id="play_t2">play</button>
<dl class="slider">
<dt>Vol</dt>
<dd>0<input type="range" min="0" max="1" step="0.01" value="0.8" id="volume_t2" />1</dd>
</dl>
<dl class="slider">
<dt>Pan</dt>
<dd>L<input type="range" min="-1" max="1" step="0.01" value="0" id="pan_t2" />R</dd>
</dl>
<dl class="slider">
<dt>LP</dt>
<dd>0<input type="range" min="0" max="20000" step="10" value="20000" id="lp_t2" />20000</dd>
</dl>
</div>
</div>
button {
padding: 3px 8px;
}
input[type='range'] {
margin: 0 7px;
}
#app-wrapper {
position: relative;
border-radius: 4px;
}
#app-cover {
position: absolute;
top: 0;
left: 0;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
width: 100%;
height: 100%;
background-color: rgba(255, 255, 255, 0.5);
}
.track {
margin: 5px 0;
padding: 5px;
max-width: 500px;
border: 1px solid #999;
border-radius: 3px;
}
.slider {
display: flex;
align-items: center;
}
.slider dt {
width: 3em;
}
{
/** @type {AudioContext | null} */
let _ctx = null;
/**
* トラックを作成
* @param {AudioNode} destination 出力先ノード
*/
const createTrack = (destination) => {
// Gainを生成して出力先ノードにつなげる
const gain = _ctx.createGain();
gain.connect(destination);
gain.gain.value = 0.7;
// Pannerを生成してGainに繋げる
const panner = _ctx.createStereoPanner();
panner.connect(gain);
// BiquadFilterを生成してPannerに繋げる
const filter = _ctx.createBiquadFilter();
filter.connect(panner);
filter.type = 'lowpass';
filter.frequency.value = 20000;
return {
gain,
panner,
filter,
audioBuffer: null,
bufferSource: null,
/**
* トラックの終端ノード (ここではBiquadFilter)
*/
terminal: filter,
/**
* トラックにAudioBufferを割り当てる
* @param {AudioBuffer} audioBuffer
*/
assign(audioBuffer) {
this.audioBuffer = audioBuffer;
return this;
},
/**
* トラック音量をセット
* @param {number} value 音量 (0 ~ 1)
*/
set volume(value) {
this.gain.gain.value = value;
},
/**
* トラックのパンをセット
* @param {number} value パン (-1 ~ 1)
*/
set pan(value) {
this.panner.pan.value = value;
},
/**
* トラックのLowPassフィルタのカットオフをセット
* @param {number} value カットオフ周波数 (0 ~ 20000)
*/
set lowPassFilterFrequency(value) {
this.filter.frequency.value = value;
},
/**
* トラックに割り当てられたAudioBufferを再生する
*/
play() {
if (!this.audioBuffer) {
console.log('?');
return;
}
if (this.bufferSource) {
console.log('??');
// 再生中なら止める
this.bufferSource.stop();
}
// BufferSource生成して再生
this.bufferSource = _ctx.createBufferSource();
this.bufferSource.buffer = this.audioBuffer;
this.bufferSource.connect(this.terminal); // 終端のノードにつなぐ
this.bufferSource.start(0);
console.log('!! play');
},
};
};
/**
* 指定した音声ファイルを全て取得してデコード
* @param {string[]} fileUrlList 音声ファイルのURLの配列
* @returns {Promise<void>}
*/
const loadSamples = async (fileUrlList) => {
const audioBuffers = {};
const load = async (url) => {
const res = await fetch(url);
const arrayBuffer = await res.arrayBuffer();
const audioBuffer = await _ctx.decodeAudioData(arrayBuffer);
audioBuffers[url] = audioBuffer;
};
await Promise.all(fileUrlList.map(load));
return audioBuffers;
};
// 開始ボタンを押したら
document.getElementById('start-button').addEventListener('click', (e) => {
document.getElementById('app-cover').style.display = 'none';
// 初期化 (ユーザー操作をトリガーに初期化しないと動かないからここでやってる)
window.AudioContext = window.AudioContext || window.webkitAudioContext;
if (!window.AudioContext) {
throw new Error('AudioContextに対応していないブラウザです。');
}
_ctx = new AudioContext();
// 音声をロード…
const urls = ['/assets/entry/20180506-webaudio/a.wav', '/assets/entry/20180506-webaudio/b.wav', '/assets/entry/20180506-webaudio/c.wav', '/assets/entry/20180506-webaudio/d.wav'];
loadSamples(urls).then((audioBuffers) => {
// 全てロード完了したらセレクトボックスに表示
let options = '';
for (const url of urls) {
options += `<option value="${url}">${url}</option>`;
}
document.getElementById('select-audio_t1').innerHTML = options;
document.getElementById('select-audio_t2').innerHTML = options;
// トラック作成
const master = createTrack(_ctx.destination);
const tracks = {
master,
// 1と2はMasterの末端ノードにつなぐ
t1: createTrack(master.terminal).assign(audioBuffers[urls[0]]),
t2: createTrack(master.terminal).assign(audioBuffers[urls[0]]),
};
// トラックごとの操作ボタンなどの準備
['t1', 't2'].forEach((trackName) => {
document.getElementById(`play_${trackName}`).addEventListener('click', (e) => {
tracks[trackName].play();
});
document.getElementById(`select-audio_${trackName}`).addEventListener('change', (e) => {
tracks[trackName].assign(audioBuffers[e.target.value]);
});
document.getElementById(`volume_${trackName}`).addEventListener('change', (e) => {
tracks[trackName].volume = e.target.value;
});
document.getElementById(`pan_${trackName}`).addEventListener('change', (e) => {
tracks[trackName].pan = e.target.value;
});
document.getElementById(`lp_${trackName}`).addEventListener('change', (e) => {
tracks[trackName].lowPassFilterFrequency = e.target.value;
});
});
});
});
}
基本は AudioContext
でノードを作って、ノード同士を繋げていく感じだった。繋げていく感じを掴むために、ここで作ったサンプルでは、トラック音量とマスター音量を用意してみた。各トラック内で LowPassフィルター → パンナー → Gain というふうに繋げて、それらをさらにマスタートラックに繋げた。
ざっくりした使い方は次のサイト様を参考にしました。
Getting Started with Web Audio API - HTML5 Rocks
フィルターとかパンナーとか細かいところは、g200kgさんのサイトを参考にしました。
Web Audio API 解説 - 01.前説 | g200kg Music & Software
それと、自分が使ってるブラウザがWeb Audio APIにどれだけ対応してるかを、g200kgさんの下記ツールでチェックできます。便利です。