Web Audio APIで作ったaudio/wav形式の音声を保存する

この(↓)のアプリを作っている間に得た知見
https://github.com/taku-o/myukkurivoice


Web Audio APIで作った音声を保存する方法は、調べると色々出てくるのですが、

  • Recorder.js
  • MediaStream Recording API
  • ScriptProcessorNode


この記事では、自分にはこれが簡単そうだなと思った方法を書きます。

node wave-recorder を使う

node wave-recorder
https://www.npmjs.com/package/wave-recorder


このライブラリを使うと、おおよそこんな感じのコード(↓)で、Web Audio APIで加工した音声データをファイルに保存できます。
YATTAZE簡単!!
少なくとも他の方法よりは。そう見える。

var fs = require('fs');

function to_array_buffer(buf_wav) {
  var a_buffer = new ArrayBuffer(buf_wav.length);
  var view = new Uint8Array(a_buffer);
  for (var i = 0; i < buf_wav.length; ++i) {
    view[i] = buf_wav[i];
  }
  return a_buffer;
};

var a_buffer = to_array_buffer(buf_wav);

var audioCtx = new window.AudioContext();
audioCtx.decodeAudioData(a_buffer).then(
  function(decodedData) {
    // source
    var sourceNode = audioCtx.createBufferSource();
    sourceNode.buffer = decodedData;
    sourceNode.onended = function() {
      recorder.end();                                    // ここでend()しないと超巨大なファイルが出来る
    };

    // gain
    var gainNode = audioCtx.createGain();
    gainNode.gain.value = volume;

    // recorder
    var recorder = WaveRecorder(audioCtx, {              // ← recorder作って
      channels: 1,                                       // ←
      bitDepth: 16                                       // ←
    });                                                  // ←
    recorder.pipe(fs.createWriteStream(wav_file_path));  // ←

    // connect and start
    sourceNode.connect(gainNode);
    gainNode.connect(recorder.input);                    // ← 繋げる
    sourceNode.start(0);
});

でも、なんか作った音声ファイルの最後の方が切れるんだけど

ただし、良いことだけでなく、node wave-recorderを試してみると問題も見つかったのです。
作成した音声ファイルの、最後の方がファイルに保存されないのです。
(自分の技量の問題もあるでしょう)

# 音声
こんにちわ

# ファイルに保存された状態 (最後の方が欠ける)
こんにちw


下のコードで、音声データの加工が終わった時にファイルへの書き出しを閉じているのですが、
onendedイベントのタイミングでは、ストリームを閉じるのが少し早かったりするのでしょう。

// source
var sourceNode = audioCtx.createBufferSource();
sourceNode.buffer = decodedData;
sourceNode.onended = function() {
  recorder.end();
};


なお、これを見て、自分が取った解決策が↓である。

// source
var sourceNode = audioCtx.createBufferSource();
sourceNode.buffer = decodedData;
sourceNode.onended = function() {
  // onendedのタイミングでは出力が終わっていない
  setTimeout(function(){
    recorder.end();
  }, 100);
};

汚い!!
でも、事件は現場で起きているんだ!!
(これは、時間があるときに解決を試みることにする)

おまけ。node-wav

node-wav
https://www.npmjs.com/package/node-wav


このようなライブラリも見つけた。wave-recorderから呼び出されていた。
このライブラリはWeb Audio APIで作った音を保存するものではない。


が、ストリームをファイルに保存する機能がある。
audio/wav形式のファイルのヘッダとか、フォーマットとか、その辺の処理をやってくれる。
加工した音声をストリームの形式に持っていけるなら、便利に扱えそうです。
良さそうだったが、今回は使用しなかった。


READMEにはその辺の機能の説明は載っていないが、
落としてみて、lib/file-writer.js とか読むと幸せになれそう。

おわり

最終的には、MediaStream Recording API に移行したい。