3
3

More than 3 years have passed since last update.

web audio api を使い、audioタグから取得した音声の音波を表示する ( javascript )

Last updated at Posted at 2021-03-24

タイトルのとおり、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を作成します。

dockerfile
FROM nginx:1.19.0-alpine

とりあえずこれだけです。必要最小限です。

default.conf

次にnginxの設定ファイルを書きます。

default.conf
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を作成します。

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ファイルを作ります。

index.html
<html>
  <head>
  </head>
  <body>
    <h1>index.html</h1>
    <hr/>
  </body>
</html>

そしてターミナルで以下のコマンドを打ちます。

$ docker-compose up

そして、ブラウザでhttp://127.0.0.1にアクセスし、以下のような画面が表示されれば動作確認終了です。
スクリーンショット 2021-03-22 23.32.36.png

開発環境の準備は以上です。

2 web audio api を使った音声の再生


これからjavascriptを書いていきますが、その前にhtmlファイルを作成します。
先ほどのindex.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を書いていきます。少し長めです。

index.js
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にアクセスして、オーディオ再生ボタンを押すだけです。
以下のようになります。
音声再生前
スクリーンショット 2021-03-23 0.05.53.png
音声再生中
スクリーンショット 2021-03-23 0.06.21.png

音波が表示されています。また、スライダーの操作を通して、再生位置、音量、パンを調整できるようになっています。

   

3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3