JavaScriptでWaveファイルを解析する

アイコン

このトピックのインスピレーションの下で作られました。

誰もが慣れている通常のJavaScriptは、ファイルシステムまたはバイナリデータを操作する手段を提供しないため、以下で説明するものはすべてnode.jsについてのものです。



WAVファイル


Waveは、デジタル化されたオーディオデータの形式です。 標準のRIFF構造を使用します。 データは3つの部分に分割できます

  1. 見出し
  2. フォーマットセクション
  3. データ


より多くのデータがあるかもしれませんが、これは通常使用されません。



ファイル解析自体


var http = require('http'); var fs = require('fs'); var sys= require('sys') var Canvas = require('canvas');
      
      





必要なモジュールを接続し、ウェーブファイルのウェーブを描画するためのノードキャンバスが必要です。

 var path = '/my/files/TH.wav'; //   . var wave = {}; //          fs.readFile(path, function (err, data) { if (err) throw err; //         data
      
      





ファイル解析の取得...



見出し


最初の部分は最も単純です。 また、3バイトの4バイトに分割することもできます

  1. ファイルタイプを含む-「RIFF」
  2. ファイルサイズ
  3. ラベル「wave」が含まれています


 var text = ''; var j = 0 for (var i = j; i < j + 4; i++) { text += String.fromCharCode(data[i]); } j = i; wave.type = text; //   - «RIFF» var text = ''; for (var i = j; i < j + 4; i++) { var byt = data[i].toString(2); if (byt.length != 8) { byt = addByte(byt) } text = byt + text; } j = i; wave.size = parseInt(text, 2); //     8    ,    .
      
      







ここには微妙な点が1つあります。デフォルトでは、読み取りバイトが10番目のシステムに転送されるため、さらに不都合が生じるため、バイトの先頭に欠落ビットを追加するaddByte関数を導入します。



 function addByte(byt) { while (8 != byt.length) { byt = '0' + byt; } return byt; }
      
      







 var text = ''; for (var i = j; i < j + 4; i++) { text += String.fromCharCode(data[i]); } j = i; console.log(j + ' Label -' + text); wave.label = text; // «wave»
      
      







データ形式セクション




データ形式セクションはヘッダーの直後にあり、キーワード「fmt」で始まります



 var text = ''; for (var i = j; i < j + 4; i++) { text += String.fromCharCode(data[i]); //text += data[i].toString(16); } j = i;
      
      







次はファイルオプションです。



サイズ、バイト 名前 説明
4 チャンクデータサイズ ファイルに関するデータを含むバイト数が含まれています
2 圧縮コード ファイル圧縮の存在を示すコードが含まれます(wavファイルにはMPEGでもピンチされたサウンドが含まれる場合がありますが、これらの機能は使用されません)、ほとんどの場合1があります。 圧縮なし
2 チャンネル数 チャンネル数、wavファイルにはマルチチャンネル録音が含まれる場合があります。たとえば5.1
4 サンプルレート サンプリングレート、通常44100-CDサンプルレート
4 1秒あたりの平均バイト数 ファイルビットレート
2 ブロック整列 すべてのチャンネルが配置されている1フレームのサウンド、または別の言い方もできます-サンプルサイズ
2 サンプルあたりの重要なビット 1つのチャネルのフレームをエンコードするためのビット数(!)




さらに、wavファイルを作成するプログラムは、ここで何かを書き込むことができますが、多くの場合、これらのプログラムによって後で理解されます。



 // extra bytes fmt var text = ''; for (var i = j; i < j + 4; i++) { var byt = data[i].toString(2); if (byt.length != 8) { byt = addByte(byt) } text = byt + text; } j = i; wave.extra_bytes_fmt = parseInt(text, 2); //Compression code var text = ''; for (var i = j; i < j + 2; i++) { var byt = data[i].toString(2); if (byt.length != 8) { byt = addByte(byt) } text = byt + text; } j = i; var compression = ''; switch (parseInt(text, 2)) { case 0: compression = 'Unknown'; break; case 1: compression = 'PCM/uncompressed'; break; case 2: compression = 'Microsoft ADPCM'; break; case 6: compression = 'ITU G.711 a-law'; break; case 7: compression = 'ITU G.711 µ-law'; break; case 17: compression = 'IMA ADPCM'; break; case 20: compression = 'ITU G.723 ADPCM (Yamaha)'; break; case 49: compression = 'GSM 6.10'; break; case 64: compression = 'ITU G.721 ADPCM'; break; case 80: compression = 'MPEG'; break; case 65536: compression = 'Experimental'; break; default: compression = 'Other'; break; } wave.compression = compression; //Number of channels var text = ''; for (var i = j; i < j + 2; i++) { var byt = data[i].toString(2); if (byt.length != 8) { byt = addByte(byt) } text = byt + text; } j = i; console.log(j + ' Number of channels - ' + parseInt(text, 2)); wave.number_of_channels = parseInt(text, 2); //Sample rate var text = ''; for (var i = j; i < j + 4; i++) { var byt = data[i].toString(2); if (byt.length != 8) { byt = addByte(byt) } text = byt + text; } j = i; console.log(j + ' Sample rate - ' + parseInt(text, 2) + ' hz '); wave.sample_rate = parseInt(text, 2); //Average bytes per second var text = ''; for (var i = j; i < j + 4; i++) { var byt = data[i].toString(2); if (byt.length != 8) { byt = addByte(byt) } text = byt + text; } j = i; wave.average_bytes_per_second = parseInt(text, 2) * 8 / 1000; //        / //Block align var text = ''; for (var i = j; i < j + 2; i++) { var byt = data[i].toString(2); if (byt.length != 8) { byt = addByte(byt) } text = byt + text; } j = i; wave.block_align = parseInt(text, 2); //Significant bits per sample var text = ''; for (var i = j; i < j + 2; i++) { var byt = data[i].toString(2); if (byt.length != 8) { byt = addByte(byt) } text = byt + text; } j = i; wave.significant_bits_per_sample = parseInt(text, 2); //Extra format bytes var text = ''; for (var i = j; i < j + 2; i++) { var byt = data[i].toString(2); if (byt.length != 8) { byt = addByte(byt) } text = byt + text; } j = i; wave.extra_format_bytes = parseInt(text, 2); //end fmt
      
      







データ




fmtセクションの追加フィールドの数はあまり予測できないため(実際の状況はextra_bytes_formatフィールドに反映されないことが多いため)、「data」キーワードをタッチで見つけるのが最も簡単です。



 while (!(text == 'data' || j == wave.size)) { text = String.fromCharCode(data[j]) + String.fromCharCode(data[j + 1]) + String.fromCharCode(data[j + 2]) + String.fromCharCode(data[j + 3]); j++; } wave.data_position = j;
      
      







キーワードの後に​​4バイトがデータサイズを含む必要があります

 var text = ''; for (var i = j; i < j + 4; i++) { var byt = data[i].toString(2); if (byt.length != 8) { byt = addByte(byt) } text = byt + text; } j = i; wave.chunk_size = parseInt(text, 2);
      
      







これで、データを自分で受信できるようになりました。上記で必要なものはすべて取得しました。 このトピックでは、他のオプションは非常にまれなので、2チャネルの古典的な例を検討します。



 //sound wave.lc = []; wave.rc = []; var k = 16; /*       -      ,    k */ wave.n = wave.block_align * k; while (j < wave.size) { var text = ''; for (var i = j; i < j + wave.block_align; i++) { var byt = data[i].toString(2); if (byt.length != 8) { byt = addByte(byt) } text = text + byt; } var s1 = text.slice(0, text.length / 2); if (s1[0] == 1) { s1 = -(parseInt(text.slice(1, text.length / 2), 2)) } else { s1 = parseInt(text.slice(0, text.length / 2), 2) } var s2 = text.slice(text.length / 2, text.length); if (s2[0] == 1) { s2 = -(parseInt(text.slice(text.length / 2 + 1, text.length), 2)) } else { s2 = parseInt(text.slice(text.length / 2, text.length), 2) } /*  1   8 ,   ,   (16,24, 32… ),      */ wave.lc.push(s1); wave.rc.push(s2); j = i; j += wave.n; }
      
      







node.jsライブラリ(canvas-node)のおかげで、波を描くことができます。



波を描く




ライブラリとブラウザの通常のキャンバスを使用して作業できます

 var canvas = new Canvas(900, 300); var ctx = canvas.getContext('2d'); var canvas2 = new Canvas(900, 300); var ctx2 = canvas2.getContext('2d'); ctx.strokeStyle = 'rgba(0,187,255,1)'; ctx.beginPath(); ctx.moveTo(0, 150); ctx2.strokeStyle = 'rgba(0,187,255,1)'; ctx2.beginPath(); ctx2.moveTo(0, 150); wave.k = 900 / wave.lc.length; wave.l = 300 / Math.pow(2, wave.significant_bits_per_sample); //               900  300 var q = Math.pow(2, wave.significant_bits_per_sample) / 2; /*  node.js       FreeBSD,       */ var web = http.createServer(function (req, res) { res.writeHead(200, { 'Content-Type': 'text/html' }); for (var i = 1; i < wave.lc.length; i++) { if (wave.lc[i] > 0) { var y = 150 + Math.floor(wave.lc[i] * wave.l) } else { var y = 150 + Math.floor((-q - wave.lc[i]) * wave.l) } if (wave.lc[i] == 0) y = 150 ctx.lineTo(Math.floor(i * wave.k), y); } ctx.stroke(); res.write('<img src="' + canvas.toDataURL() + '" /><br/>'); //   for (var i = 1; i < wave.rc.length; i++) { if (wave.rc[i] > 0) { var y = 150 + Math.floor(wave.rc[i] * wave.l) } else { var y = 150 + Math.floor((-q - wave.rc[i]) * wave.l) } if (wave.rc[i] == 0) y = 150 ctx2.lineTo(Math.floor(i * wave.k), y); } ctx2.stroke(); res.write('<img src="' + canvas2.toDataURL() + '" /><br/>'); //    res.end(); }).listen(8000);
      
      







まとめ


波



ps品質と最適でないコードでごめんね。 できるだけ簡単に書くように努めただけでなく、私はちょうど学んでいます。



All Articles