BPM分析ツール

DJ用にBPM分析ツールを作りました。
BPMを合わせて曲を繋ぐときに使います。
かなり軽くて便利です。

BPMとは

Beats Per Minuteの略で、1分間に何回拍を刻んだかを数えた値です。
たいてい4分の4拍子での4分音符を数えます。
だいたい巷の音楽は80〜130ぐらいでしょうか。

準備

  1. メモ帳に下記のソースコードをコピーして「bpm.html」という名前で保存します。
  2. bpm.html」をダブルクリックしてインターネットエクスプローラーが開けばOK。

使い方

  1. リズムに合わせてPCキーボードのキー(なんでもいいですが、例えば[R])を叩きます。※4小節(16回)ぐらいは叩きましょう。
  2. 終了時に[E]のキーをリズムに合わせて叩きます。

結果

  • HH:mm:ss SSS・・・キーを叩いた時分秒ミリ秒
  • FROM UTC・・・キーを叩いた時刻のUTC表現
  • GateTime・・・次のキーが叩かれるまでの間隔(ミリ秒)
  • GROOVE・・・それぞれの拍のGateTimeの平均
<html>
<head>
  <meta http-equiv="Content-Type" content="text/html;charset=Shift_JIS">
  <style type="text/css">
    <!-- 
    h1 {
      padding: 50px 0 50px 0;
      font-family: 'arial', sans-serif;
    }
    table {
      font-size:10pt;
      background-color: #DCDCDC;
      margin:8;
      padding:2;
      border-collapse: collapse;
      border: 1 solid #000000;
    }
    th {
      font-family: 'arial', sans-serif;
      font-weight: normal;
      background-color: #A9A9A9;
      border: 1 solid #000000;
    }
    td {
      border: 1 solid #000000;
    }
    -->
  </style>
  <script type="text/javascript">
    <!--
    var beat = 4;
    var beatsCount = 1;
    var tmpCount = 1;
    var count = 1;
    var startTime = 0;
    var endTime = 0;
    var beatData = new Array();
    var beatDataList = new Array();
    var gatetimeList = new Array();
    var reportData = '';
    
    /* キーイベント */
    document.onkeydown = function(e) { 

	  if (e != null) { 
	    keycode = e.which; 
	    e.preventDefault(); 
	    e.stopPropagation(); 
	  } else { 
	    keycode = event.keyCode; 
	    event.returnValue = false; 
	    event.cancelBubble = true; 
	  } 

	  keychar = String.fromCharCode(keycode).toUpperCase(); 
	  
	  if ('E' == keychar) {
	    countBeat();
	    if ((count-1) % beat > 0) {
	      beatDataList[beatsCount-1] = beatData;
	    }
	    report();
	    return;
	  }
      countBeat();
	}
    
    
    /* リズムカウント */
    function countBeat() {
    
      if (count % beat == 1) {
        beatData = new Array();
      }
      
      beatData[tmpCount-1] = new Date();
      
      if (count % beat == 0) {
        beatDataList[beatsCount-1] = beatData;
        beatsCount++;
        tmpCount = 0;
      }
      
      tmpCount++;
      count++;
    }
    
    
    /* 小節単位分析 */
    function analize() {
      
      previousTime = -1;
      startCount = true;
      idx = 0;
      
      reportData += '<tr><th>No.</th><th>小節</th><th>拍</th><th>HH:mm:ss SSS</th><th>FROM UTC</th><th>GateTime</th></tr>';
      
      for (i=0;i<beatDataList.length;i++) {
        
        for (j=0;j<beatDataList[i].length;j++) {
        
          if (startCount) {
            startTime = beatDataList[i][j].getTime();
            startCount = false;
          }
            
          if (i == beatDataList.length-1 && j == beatDataList[i].length-1) {
            endTime = beatDataList[i][j].getTime();
          }
          
          if (previousTime > 0) {
            gatetimeList[idx++] = (beatDataList[i][j].getTime() - previousTime);
          }
          if (idx > 0) {
            reportData += '<td>' + gatetimeList[idx-1];
            reportData += '</td></tr>';
          }
          if (i == beatDataList.length-1 && j == beatDataList[i].length-1) {
            reportData += '<tr><td>E</td>'; 
          } else {
            reportData += '<tr><td>' + (i*beat+j+1) + '</td>'; 
          }
          reportData += '<td>' + (i+1) + '</td><td>' + (j+1) + '</td><td>'; 
          reportData += beatDataList[i][j].getHours() + ':'; 
          reportData += beatDataList[i][j].getMinutes() + ':'; 
          reportData += beatDataList[i][j].getSeconds() + ' '; 
          reportData += beatDataList[i][j].getMilliseconds();
          reportData += '</td><td>' + beatDataList[i][j].getTime();
          reportData += '</td>';
          if (i == beatDataList.length-1 && j == beatDataList[i].length-1) {
            reportData += '</tr>';
          }
          previousTime = beatDataList[i][j].getTime();
        }
      }
      reportData += '\n';
      
    }
    
    
    /* GROOVE */
    function average() {

      idx = 0;
      beatArray = new Array(beat);
      for (i=0;i<beatArray.length;i++) {
        beatArray[i] = 0;
      }
      for (i=0;i<gatetimeList.length;i++) {
        if (idx % beat == 0) {
          idx = 0;
        }
        beatArray[idx++] += gatetimeList[i];
      }
      reportData += '<tr><th colspan="2">GROOVE(平均間隔)</th></tr>';
      
      for (i=0;i<beatArray.length;i++) {
        beatTimes = Math.floor((count-2) / beat) + (i < ((count-2) % beat) ? 1 : 0);
        reportData += '<tr><td>' + (i+1) + '拍目(' + beatTimes + '回)</td><td>';
        if (beatTimes > 0) {
          reportData += Math.round(beatArray[i] * 100 / beatTimes) / 100000 + '(秒)</td></tr>';
        }
        reportData += '\n';
      }
    }
    
    
    /* BPM */
    function bpm() {
      reportData += '<tr><th>BPM</th>';
      reportData += '<td>';
      reportData += 6000000 / Math.round((endTime - startTime) * 100 / (count-2));
      reportData += '</td></tr>';
    }
    
    
    /* 分析結果表示 */
    function report() {
    
      reportData += '<table>';
      analize();
      reportData += '</table>';
      
      reportData += '<table>';
      average();
      reportData += '</table>';
      
      reportData += '<table>';
      bpm();
      reportData += '</table>'
      
      document.getElementById('report').innerHTML = reportData;
    }
    // -->
  </script>
</head>
<body>
  <input type="button" value="CLEAR" onClick="window.location.reload();" />
  <div id="report"></div>
</body>
</html>

どこかのサーバーにアップすればもっと使いやすいんだけど、それはまた今度。
アップしたらこのブログを更新します。