voicerec.htm

戻る

<!DOCTYPE HTML>
<HTML LANG="ja">

<HEAD>
<META CHARSET="Shift_JIS">
<TITLE>ボイス レコーダー</TITLE>
</HEAD>

<BODY STYLE="background-color:#CCFFFF">

<DIV ALIGN=CENTER>
<BR>
<B>ボイス レコーダー</B>
<BR><BR>

<FORM>
<TABLE><TR><TD NOWRAP>
<INPUT TYPE=BUTTON ID="new" VALUE="録音" onClick="record(false)">
<INPUT TYPE=BUTTON ID="app" VALUE="追加録音" DISABLED onClick="record(true)" STYLE="margin-left:4px"><BR><BR>
<INPUT TYPE=BUTTON ID="ply" VALUE="再生" DISABLED onClick="play()">
<SPAN STYLE="margin-left:1em">音量</SPAN><INPUT TYPE=RANGE ID="vol" MIN="0" MAX="1" STEP="any" VALUE="0.5" onInput="set_vol()"><BR><BR>
<SPAN ID="ela"></SPAN><SPAN ID="len"></SPAN><BR>
<INPUT TYPE=BUTTON ID="stp" VALUE="停止" DISABLED onClick="stop()"><BR><BR>
<INPUT TYPE=BUTTON ID="sav" VALUE="保存..." DISABLED onClick="save()"><BR><BR>
<INPUT TYPE=BUTTON ID="lod" VALUE="読込..." onClick="load()">
<SPAN ID="nam" STYLE="white-space:pre; display:inline-block; min-width:20em"></SPAN>
</TD></TR></TABLE>
</FORM>

</DIV>

<A ID="dl" STYLE="display:none"></A><!--保存処理用-->

<SCRIPT TYPE="text/javascript">
<!--

// 録音/追加録音
function record(append) {
  window.append = append;

  elem_new.disabled = elem_append.disabled = elem_play.disabled = elem_save.disabled = elem_load.disabled = true;

  if(navigator.mozGetUserMedia == undefined)
    navigator.getUserMedia({video:false, audio:true}, success, error);
  else
    navigator.mozGetUserMedia({video:false, audio:true}, success, error);
}

function success(stream) {
  elem_stop.disabled = false;
  mode = true;

  // リサンプリング用オフライン コンテキスト作成
  create_rec_off();

  if(append) {  // 追加録音
    var data_len = data.length;
    if(data_len) {  // データあり
      frac_len = data[data_len - 1].length;
      elapsed = (data_len - 1) * 8000 + frac_len;
      var sec = data_len;
      if(frac_len == 8000) {  // 1秒未満の半端なし
        append = false;
      }
      else {
        sec--;
        var i = Math.ceil(frac_len * sample_rate / 8000);
        while(i_rec_off < i)
          rec_off_pcm[i_rec_off++] = 0;
      }
      show_elapsed(sec);
    }
    else {
      elapsed = 0;
      elem_elapsed.textContent = "0:00";
      append = false;
    }
  }
  else {
    data = [];

    elapsed = 0;
    elem_elapsed.textContent = "0:00";
  }
  elem_length.textContent = "";
  elem_name.textContent = "";

  rec_str = aud_ctx.createMediaStreamSource(stream);
  rec_str.connect(rec_scr);
  rec_scr.onaudioprocess = rec_proc;
}

function error(err) {
  alert("マイクが使用できません");

  elem_new.disabled = elem_load.disabled = false;
  if(data != undefined)
    elem_append.disabled = elem_play.disabled = elem_save.disabled = false;
}

// 録音用プロセッサ
function rec_proc(e) {
  var pcm = e.inputBuffer.getChannelData(0);

  for(var i_pcm = 0; i_pcm < 2048; i_pcm++) {
    if(i_rec_off == sample_rate) {
      // リサンプリング起動
      with(rec_off_ctx.createBufferSource()) {
        buffer = rec_off_buf;
        connect(rec_off_ctx.destination);
        start();
      }
      rec_off_ctx.startRendering();

      // リサンプリング用オフライン コンテキスト作成
      create_rec_off();
    }

    rec_off_pcm[i_rec_off++] = pcm[i_pcm];
  }
}

// 録音停止用プロセッサ
function stop_proc(e) {
  rec_scr.onaudioprocess = null;
  rec_str.disconnect();
  rec_str = undefined;

  rec_len = Math.floor(i_rec_off * 8000 / sample_rate);
  if(!rec_len) {  // 1秒未満の半端なし
    setTimeout(rec_stop, 50);
    return;
  }

  while(i_rec_off < sample_rate)
    rec_off_pcm[i_rec_off++] = 0;

  // リサンプリング起動
  with(rec_off_ctx.createBufferSource()) {
    buffer = rec_off_buf;
    connect(rec_off_ctx.destination);
    start();
  }
  rec_off_ctx.startRendering();
}

// 録音時リサンプリング用オフライン コンテキスト作成
function create_rec_off() {
  // サポートされることが保障されている sampleRate の最低値は,W3C の
  // ドキュメントでは 8192,Mozilla のドキュメントでは 22050 となっている.
  // Firefox 25 では 8000 も指定可能.
  with(rec_off_ctx = new OfflineAudioContext(1, 8000, 8000)) {
    oncomplete = rec_off_comp;

    rec_off_pcm = (rec_off_buf = createBuffer(1, sample_rate, sample_rate)).getChannelData(0);
    i_rec_off = 0;
  }
}

// 録音時リサンプリング完了
function rec_off_comp(e) {
  var pcm = e.renderedBuffer.getChannelData(0);

  if(rec_scr.onaudioprocess == null)  // 録音終了
    setTimeout(rec_stop, 50);
  else
    rec_len = 8000;

  var comp = new Uint8Array(rec_len);
  var i_pcm = 0;
  if(append) {  // 追加
    // 1秒未満の半端をコピー
    var old_comp = data.pop();
    for(; i_pcm < frac_len; i_pcm++)
      comp[i_pcm] = old_comp[i_pcm];
    append = false;
  }
  // エンコード
  for(; i_pcm < rec_len; i_pcm++) {
    var val = pcm[i_pcm];
    if     (val >= 1) {
      val = 0xff;
    }
    else if(val <= -1) {
      val = 0x7f;
    }
    else {
      var sign = (val >= 0);
      val = Math.floor(2047 * ((sign) ? val : - val));
      if(val & 0x7e0) {  // > 31
        var exp = 1;
        while(val & 0x7e0) {
          val >>= 1;
          exp++;
        }
        val = (exp << 4) | val & 0xf;
      }
      if(sign)  // プラス
        val |= 0x80;
    }
    comp[i_pcm] = val ^ 0x55;
  }

  data.push(comp);

  elapsed += rec_len;
  show_elapsed(Math.floor(elapsed / 8000));
}

function rec_stop() {
  show_length(Math.round(elapsed / 8000));
  stop_2();
}

// 再生
function play() {
  i_data = 0;

  // PCM 作成
  data_pcm();
  if(pcm_ready < 0)  // データなし
    return;

  elem_new.disabled = elem_append.disabled = elem_play.disabled = elem_save.disabled = elem_load.disabled = true;
  elem_stop.disabled = false;
  mode = false;

  play_pcm = new Float32Array(0);  // ダミー
  i_play = 0;
  elapsed = 0;
}

// 再生用プロセッサ
function play_proc(e) {
  var pcm = e.outputBuffer.getChannelData(0);

  var i_pcm;
  for(i_pcm = 0; i_pcm < 2048; i_pcm++) {
    if(i_play == play_pcm.length) {
      if(pcm_ready != 1) {  // データ終了/PCM 作成未完了
        if(pcm_ready < 0) {  // データ終了
          play_scr.onaudioprocess = null;
          play_scr.disconnect();

          setTimeout(stop_2, 50);
        }
        for(var i = i_pcm; i < 2048; i++)
          pcm[i] = 0;
        break;
      }

      play_pcm = play_pcm_next;
      i_play = 0;

      // PCM 作成
      data_pcm();
    }
    pcm[i_pcm] = play_pcm[i_play++];
  }

  elapsed += i_pcm;
  show_elapsed(Math.floor(elapsed / sample_rate));
}

// 再生用 PCM 作成
function data_pcm() {
  if(i_data == data.length) {  // データ終了
    pcm_ready = -1;
    return;
  }

  pcm_ready = 0;

  var comp = data[i_data++];
  var len = comp.length;
  // リサンプリング用オフライン コンテキスト作成
  with(new OfflineAudioContext(1, Math.ceil(len * sample_rate / 8000), sample_rate)) {
    oncomplete = play_off_comp;

    var buff = createBuffer(1, len, 8000);
    var pcm = buff.getChannelData(0);

    for(var i_pcm = 0; i_pcm < len; i_pcm++) {
      // デコード
      var val = comp[i_pcm] ^ 0x55;
      var exp = (val & 0x70) >> 4;
      var val2 = ((val & 0x1f) << 1) | 0x1;
      if(exp > 1)
        val2 = (0x20 | val2) << (exp - 1);
      val2 /= 4095;
      pcm[i_pcm] = (val & 0x80) ? val2 : - val2;
    }

    // リサンプリング起動
    var src = createBufferSource();
    src.buffer = buff;
    src.connect(destination);
    src.start();
    startRendering();
  }
}

// 再生時リサンプリング完了
function play_off_comp(e) {
  play_pcm_next = new Float32Array(e.renderedBuffer.getChannelData(0));
  pcm_ready = 1;

  if(i_data == 1) {  // 初回
    // 再生開始
    play_scr.connect(gain_node);
    play_scr.onaudioprocess = play_proc;
  }
}

function set_vol() {
  gain_node.gain.value = Number(elem_volume.value);
}

// 停止
function stop() {
  if(mode) {  // 録音中
    rec_scr.onaudioprocess = stop_proc;
  }
  else {  // 再生中
    play_scr.onaudioprocess = null;
    play_scr.disconnect();

    stop_2();
  }
}

function stop_2() {
  elem_elapsed.textContent = "0:00";
  elem_new.disabled = elem_append.disabled = elem_play.disabled = elem_save.disabled = elem_load.disabled = false;
  elem_stop.disabled = true;
}

// 経過時間表示
function show_elapsed(sec) {
  elem_elapsed.textContent = String(Math.floor(sec / 60)) + ":" + ("0" + String(sec % 60)).substr(-2);
}

// 録音長表示
function show_length(sec) {
  elem_length.textContent = "/" + String(Math.floor(sec / 60)) + ":" + ("0" + String(sec % 60)).substr(-2);
}

// 保存
function save() {
  with(document.getElementById("dl")) {
    if(download == undefined)
      return;
    download = String(Math.floor(Date.now() / 1000)) + ".dat";
    href = URL.createObjectURL(new Blob(data));
    click();
  }
}

// 読み込み
function load() {
  with(elem_file = document.createElement("INPUT")) {
    type = "FILE";
    onchange = file_sel;
    click();
  }
}

function file_sel() {
  var file = elem_file.files.item(0);
  elem_name.textContent = file.name;
  file_reader = new FileReader();
  file_reader.onload = file_sel2;
  file_reader.readAsArrayBuffer(file);

  elem_new.disabled = elem_append.disabled = elem_play.disabled = elem_save.disabled = elem_load.disabled = true;
}

function file_sel2() {
  var buff = file_reader.result;
  file_reader = undefined;

  var len = buff.byteLength;
  data = [];
  var sec;
  if(len) {  // データあり
    // 1秒ずつに分ける
    for(var off = 0; ; off += 8000) {
      if(len <= 8000) {
        data.push(new Uint8Array(buff, off));
        break;
      }
      data.push(new Uint8Array(buff, off, 8000));
      len -= 8000;
    }
    sec = data.length;
    if(len < 4000)  // 0.5秒未満
      sec--;
  }
  else {
    sec = 0;
  }
  elem_elapsed.textContent = "0:00";
  show_length(sec);

  elem_new.disabled = elem_append.disabled = elem_play.disabled = elem_save.disabled = elem_load.disabled = false;
}

  with(aud_ctx = new AudioContext()) {
    gain_node = createGain();
    gain_node.gain.value = 0.5;
    gain_node.connect(destination);
    rec_scr = createScriptProcessor(2048, 1, 0);
    play_scr = createScriptProcessor(2048, 0, 1);
    sample_rate = sampleRate;
  }

  elem_new = document.getElementById("new");
  elem_append = document.getElementById("app");
  elem_play = document.getElementById("ply");
  elem_volume = document.getElementById("vol");
  elem_elapsed = document.getElementById("ela");
  elem_length = document.getElementById("len");
  elem_stop = document.getElementById("stp");
  elem_save = document.getElementById("sav");
  elem_load = document.getElementById("lod");
  elem_name = document.getElementById("nam");

  data = undefined;

  // ページを再ロードしたときのため
  document.forms[0].reset();
  elem_append.disabled = elem_play.disabled = elem_stop.disabled = elem_save.disabled = true;
  elem_new.disabled = elem_load.disabled = false;

//-->
</SCRIPT>

</BODY>

</HTML>