<!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";
if(href != "")
URL.revokeObjectURL(href);
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>
|