タイトルのとおり、audioタグから音声を取得し、web audio api を用いて音波を表示できるようにします。(ついでに、音量、再生位置、panをユーザーがブラウザを通して動的に操作できるようにします。)
本記事の大まかな流れは以下の通りです。
1, 開発環境の準備
2, web audio api を使った音声の再生、音波の表示
最終的なディレクトリは次の通りとなります。
root/
├ html/
│ ├ index.html
│ └ index.js
│
├ audio/
│ └ audio.mp3
│
├ nginx/
│ └ default.conf
│
├ DockerfileNginx
└ docker-compose.yml
1 開発環境の準備
最初にwebサーバーを立てて、httpによってhtmlファイル、オーディオファイルを開けるようにします。web audio apiということだけあって、webサーバーを立てないと、オーディオファイルを取り扱えません。web audio api を使うときには、まず後述するAudioContextを作成してオーディオファイルと結びつけるのですが、その際オーディオファイルをhttpで取得しないと、音が正常に再生されません。 私は最初、webサーバーを立てずに、audioタグのオーディオファイルをファイルパスから取得していたのですが、そのために少し時間を溶かしました。DockerfileNginx
まずはDockerfileを作成します。FROM nginx:1.19.0-alpine
とりあえずこれだけです。必要最小限です。
default.conf
次にnginxの設定ファイルを書きます。server {
listen 80;
client_max_body_size 20M;
location / {
root /usr/share/nginx/html/;
index index.html
try_files $uri $uri/ /index.html;
}
}
上記簡単な解説は、
listen 80;
ポート80番でrequestを待ち受けます。80番はhttpのデフォルトのポート番号。
client_max_body_size 20M
これはデフォルトで1Mですが、1Mだとオーディオファイルを取り扱えないので、20Mで設定しました。
location / { ...
・root以下は、htmlファイルを格納するパスを記載します。
・index index.html → /usr/share/nginx/html/内に格納されていて、これからブラウザで表示したいファイル名を指定します。
docker-compose.yml
最後にdocker-compose.ymlを作成します。version: '3.7'
services:
nginx:
build:
context: .
dockerfile: DockerfileNginx
ports:
- "80:80"
volumes:
- ./html:/usr/share/nginx/html
- ./nginx/:/etc/nginx/conf.d/
上記簡単な解説は、
- ./html:/usr/share/nginx/html
ローカルのhtml/ディレクトリ内のhtmlファイルをdockerのnginxコンテナ内(usr/share/nginx/html)にマウントしています。
- ./nginx/:/etc/nginx/conf.d/
先ほど作成したdefault.confファイルの設定をdockerのnginxコンテナに反映させるために、ローカルのnginx/ディレクトリをdockerの/etc/nginx/conf.dディレクトリにマウントしています。
webサーバー動作確認
試しにnginxを動かしてみます。 まず以下のようなhtmlファイルを作ります。<html>
<head>
</head>
<body>
<h1>index.html</h1>
<hr/>
</body>
</html>
そしてターミナルで以下のコマンドを打ちます。
$ docker-compose up
そして、ブラウザでhttp://127.0.0.1にアクセスし、以下のような画面が表示されれば動作確認終了です。
開発環境の準備は以上です。
2 web audio api を使った音声の再生
これからjavascriptを書いていきますが、その前にhtmlファイルを作成します。 先ほどのindex.htmlを次のように書き換えます。<html>
<head>
<meta charset='UTF-8'>
<title>web audio api demo</title>
<script src='./index.js' defer></script>
</head>
<body>
<h1>web audio api</h1>
<hr/>
<audio src='./audio/audio.mp3'
crossOrigin='anonymous'
></audio><br/>
<button>
<span>Play/Pause</span>
</button><br/>
time
<input type='range' id='time' min='0' value='0' step='1' /><br/>
volume
<input type='range' id='volume' min='0' max='0.16' value='0.08' step='0.001' /><br/>
panner
<input type='range' id='panner' min='-1' max='1' value='0' step='0.01'/><br/>
<hr/>
<div>canvas</div>
<canvas id='mycanvas' width='640' height='400'></canvas>
</body>
</html>
本当はaudioタグとcanvasタグが重要でinputタグは飾りですが、あとで使うので、今まとめて書いてます。
次に、javascriptを書いていきます。少し長めです。
console.clear()
//audioタグから、オーディオエレメントを作ります。
const audioElement = document.querySelector('audio');
//AudioContextを作ります。
const audioContext = new AudioContext();
//異なるドメインから音声ファイルを取得する時に必要な設定です。本当は、今回は設定する必要がありません。
audioContext.crossOrigin='anonymous';
//AudioContextにaudioElementを紐づけます。
const track = audioContext.createMediaElementSource(audioElement);
//Gain Nodeを作ります。再生時に音量を設定するためのノードです。
const gainNode = audioContext.createGain();
gainNode.gain.setValueAtTime(0.08,0)
//Stereo Panner Nodeを作ります。パンを設定するためのノードです。
const pannerOptions = { pan : 0 };
const pannerNode = new StereoPannerNode(audioContext,pannerOptions);
//Analyzer Nodeを作ります。音波のビジュアライズをするために必要なノードです。
const analyserNode = audioContext.createAnalyser();
analyserNode.fftSize = 2048;//use for fast fourier transformation
const bufferLength = analyserNode.frequencyBinCount;//half the size of analyserNode.fftSize
const dataArray = new Uint8Array(bufferLength);
//今まで作成してきたノードを全て繋ぎます。
track.connect(gainNode).connect(pannerNode).connect(analyserNode).connect(audioContext.destination);
//音波の壁画のために、canvasを作成します。
//またfunction draw()以下でcanvas常に音波を表示できるようにします。
const canvas = document.getElementById('mycanvas');
const canvasContext = canvas.getContext('2d');
const WIDTH=canvas.width
const HEIGHT=canvas.height
canvasContext.strokeRect(0,0,WIDTH,HEIGHT);
function draw(){
drawVisual = requestAnimationFrame(draw);
analyserNode.getByteTimeDomainData(dataArray);//音波データをUint8Array配列(dataArray)にコピー
canvasContext.fillStyle = 'rgb(200,200,200)';
canvasContext.fillRect(0,0,WIDTH,HEIGHT);
canvasContext.lineWidth = 3;
canvasContext.strokeStyle = 'rgb(0,0,0)';
canvasContext.beginPath();
const sliceWidth = WIDTH * (1.0/bufferLength);
var x = 0;
for(var i=0; i<bufferLength; i++){
var v = dataArray[i] / 128.0;
var y = v * HEIGHT/2 ;
if(i ===0){
canvasContext.moveTo(x,y);
}else{
canvasContext.lineTo(x,y);
}
x+=sliceWidth;
}
canvasContext.lineTo(WIDTH,HEIGHT/2);
canvasContext.stroke();
};
draw();
//おまけ。再生位置、音量、パンをユーザーが画面上から設定できるようにします。
//再生ボタン作成
const playButton = document.querySelector('button');
//再生位置を自由に動かせるように、スライダーを作成。
const timeControl = document.querySelector('#time')
timeControl.setAttribute('max',audioElement.duration)
timeControl.addEventListener('input',function(){
audioElement.currentTime=this.value;
},false)
audioElement.addEventListener('timeupdate',function(){
timeControl.value = audioElement.currentTime
})
//音量を調整できるように、スライダーを作成。
const volumeControl = document.querySelector('#volume');
volumeControl.addEventListener('input',function(){
gainNode.gain.value=this.value;
},false)
playButton.addEventListener('click',function(){
//ユーザーのアクションがあるまで、オーディをファイルは'suspended'状態となり自動再生されません。
//なのでボタンクリックと同時に、resume()で再生されるようにしています。
if (audioContext.state==='suspended'){
audioContext.resume();
}
// 再生した時と一時停止した時のロジック
if (this.dataset.playing === 'false'){
audioElement.play();
this.dataset.playing='true'
} else if (this.dataset.playing==='true'){
audioElement.pause();
this.dataset.playing = 'false'
}
},false)
audioElement.addEventListener('ended',()=>{
playButton.dataset.playing='false';
})
//パンを作成
const pannerControl = document.querySelector('#panner')
pannerControl.addEventListener('input',function(){
pannerNode.pan.value=this.value;
},false)
上記jsファイルを書き終えた後は、http://127.0.0.1にアクセスして、オーディオ再生ボタンを押すだけです。
以下のようになります。
音声再生前
音声再生中
音波が表示されています。また、スライダーの操作を通して、再生位置、音量、パンを調整できるようになっています。