![]() |
<!DOCTYPE HTML> <HTML LANG="ja"> <HEAD> <META CHARSET="Shift_JIS"> <TITLE>FLAC プレイヤー ダイナ</TITLE> </HEAD> <BODY STYLE="background-color:#CCFFFF"> <DIV STYLE="text-align:center"> <BR> <B><SPAN STYLE="color:#CC0000">FLAC プレイヤー ダイナ<BR>(Firefox 5)</SPAN></B> <BR><BR> <FORM> <TABLE STYLE="margin-left:auto; margin-right:auto"><TR><TD STYLE="text-align:left; white-space:nowrap"> <B>ファイル:</B><BR> <INPUT TYPE=FILE ID="file" SIZE=60 onChange="sel_file()"><BR><BR> <B>タイトル:</B> <SPAN ID="ttl" STYLE="white-space:pre"></SPAN><BR><BR> <B>アーティスト:</B> <SPAN ID="art" STYLE="white-space:pre"></SPAN><BR><BR> <INPUT TYPE=BUTTON ID="play" VALUE="再生" DISABLED onClick="play_stop()"> <SPAN ID="time" STYLE="margin-left:1em"></SPAN> </TD></TR></TABLE> </FORM> </DIV> <SCRIPT TYPE="text/javascript"> <!-- function read_file(len, cb) { var end = file_pos + len; if(end > file.size) { alert(ERR_FORMAT); return; } file_cb = cb; file_reader.readAsBinaryString((slice) ? file.slice(file_pos, end) : file.mozSlice(file_pos, end)); file_pos = end; } function file_end() { data = file_reader.result; i_data = 0; file_cb(); } function file_err() { alert("ファイル読み込みでエラーが発生しました。"); } function sel_file() { elem_ttl.textContent = elem_art.textContent = ""; elem_play.disabled = true; file = elem_file.files.item(0); file_pos = 0; get_metadata(); } function sel_file_end() { elem_play.disabled = false; } function play_stop() { if(running) { // 停止 running = false; return; } elem_file.disabled = true; elem_play.value = "停止"; running = true; // 再生開始 elem_aud = undefined; samples = undefined; samples_len = 0; samples_written = 0; eos = 0; buff_off = block_size = 0; elem_time.textContent = "0:00"; last_sec = 0; file_pos = pos_audio; data = ""; i_data = 0; sample_rate = 0; setTimeout(frame, 0); } // 再生 function frame() { if(!running) { // 停止 aud_end(); return; } for(; ; ) { if(samples_written) { if(samples_written - elem_aud.mozCurrentSampleOffset() >= min_ahead) break; } for(; ; ) { if(samples != undefined) { if(samples_len == samples.length) break; } if(buff_off == block_size) { if(eos) break; // FRAME get_frame(); return; } for(; buff_off < block_size; buff_off++) { if(samples_len == samples.length) break; samples[samples_len++] = buff0[buff_off] / max_val; if(n_channels > 1) samples[samples_len++] = buff1[buff_off] / max_val; } } if(elem_aud == undefined) { elem_aud = new Audio(); elem_aud.mozSetup((n_channels == 1) ? 1 : 2, sample_rate); } var written = elem_aud.mozWriteAudio(samples.subarray(0, samples_len)); samples_written += written; if(written < samples_len) samples.set(samples.subarray(written, samples_len)); // 残りをシフト samples_len -= written; if(eos && buff_off == block_size) { // データ終了 eos = 2; break; } } var sec = Math.floor(elem_aud.mozCurrentSampleOffset() / ((n_channels == 1) ? sample_rate : (sample_rate << 1))); if(sec != last_sec) { elem_time.textContent = String(Math.floor(sec / 60)) + ":" + ("0" + String(sec % 60)).substr(-2); last_sec = sec; } if(eos == 2) { setTimeout(aud_end, Math.floor((samples_written - elem_aud.mozCurrentSampleOffset()) / ((n_channels == 1) ? sample_rate : (sample_rate << 1)) * 1000) + 100); return; } setTimeout(frame, 100); } function frame_cont(eof) { if(eof) { eos = 1; // ファイル読み込み終了 } else { if(samples == undefined) { min_ahead = (n_channels == 1) ? sample_rate >> 1 : (sample_rate & ~0x1); // 500ms samples = new Float32Array((min_ahead >> 2) & ~0x1); // 125ms 分 } max_val = 0x1 << (sample_bits - 1); if(n_samples) { // total samples あり var n = samples_written + samples_len; if(n_channels > 1) n /= 2; if(n + block_size >= n_samples) { block_size = n_samples - n; eos = 1; // ファイル読み込み終了 } } buff_off = 0; } frame(); } function aud_end() { elem_aud = undefined; samples = undefined; data = undefined; elem_time.textContent = ""; elem_play.value = "再生"; running = false; elem_file.disabled = false; } // FLAC **************************************************** // メタデータ function get_metadata() { read_file(4, get_metadata_2); } function get_metadata_2() { if(data.substr(0, 4) != "fLaC") { alert(ERR_FORMAT); return; } get_metadata_3(); } // METADATA_BLOCK function get_metadata_3() { // METADATA_BLOCK_HEADER read_file(4, get_metadata_4); } function get_metadata_4() { flg_typ = data_int1(); // flag, block type len_meta = data_int3(); // length of metadata // METADATA_BLOCK_DATA read_file(len_meta, get_metadata_5); } function get_metadata_5() { var i; try { switch(flg_typ & 0x7f) { // BLOCK_TYPE case 0: // STREAMINFO if(len_meta < 34) throw 0; min_block = data_int2(); // minimum block size max_block = data_int2(); // maximum block size min_frame = data_int3(); // minimum frame size max_frame = data_int3(); // maximum frame size var w = data_int4(); sample_rate_si = w >>> 12; // sample rate if(sample_rate_si == 0 || sample_rate_si > 655350) throw 0; n_channels = ((w & 0xe00) >> 9) + 1; // number of channels if(n_channels > 6) throw 0; sample_bits_si = ((w & 0x1f0) >> 4) + 1; // bits per sample if(sample_bits_si < 4) throw 0; if(sample_bits_si > 24) { alert("このプログラムは、24 ビットを超えるサンプル サイズには対応していません。"); return; } n_samples = (((w & 0xf) << 16) | data_int2()) * 0x10000; // total samples n_samples += data_int2(); // MD5 signature signature = new Array(); signature.length = 16; for(i = 0; i < 16; i++) signature[i] = data_int1(); break; case 4: // VORBIS_COMMENT // 曲タイトル,アーティスト名を取得 var len; if(len_meta < 4) throw 0; if((len = data_int4_le()) & 0x80000000) // vendor_length throw 0; if(i_data + len > len_meta) throw 0; i_data += len; // vendor_string if(i_data + 4 > data.length) throw 0; var n_comments = data_int4_le(); // user_comment_list_length if(n_comments & 0x80000000) throw 0; var f = 0x3; for(i = 0; i < n_comments; i++) { if(i_data + 4 > len_meta) throw 0; if((len = data_int4_le()) & 0x80000000) // length throw 0; if(i_data + len > len_meta) throw 0; var comment = data.substr(i_data, len); // comment i_data += len; if(comment.substr(0, 6).toUpperCase() == "TITLE=") { elem_ttl.textContent = utf8to16(comment.substr(6)); f &= ~0x1; } if(comment.substr(0, 7).toUpperCase() == "ARTIST=") { elem_art.textContent = utf8to16(comment.substr(7)); f &= ~0x2; } if(!f) break; } break; case 127: throw 0; } } catch(e if e == 0) { alert(ERR_FORMAT); return; } if(flg_typ & 0x80) { // last metadata block pos_audio = file_pos; // オーディオ開始位置保存 buff0 = new Int32Array(256); buff1 = (n_channels == 1) ? undefined : new Int32Array(256); sel_file_end(); return; } get_metadata_3(); } // FRAME function get_frame() { // FRAME_HEADER // FRAME_HEADER は最大 16 バイトなので,16 バイト読み込む. // (ただし,ファイルの終端を超えない範囲で) data = data.substr(i_data); if(!data.length && file_pos == file.size) { // EOF frame_cont(true); return; } var len = 16 - data.length; if(len > 0) { if(file_pos + len > file.size) { len = file.size - file_pos; if(data.length + len < 5) { frame_error(ERR_FORMAT); return; } } if(len > 0) { prev_data = data; read_file(len, get_frame_2); return; } } i_data = 0; get_frame_3(); } function get_frame_2() { data = prev_data + data; get_frame_3(); } function get_frame_3() { var w; try { w = data_int2(); // sync code, blocking strategy if((w & 0xfffc) != 0xfff8) // sync code ? throw 0; var blocking = w & 0x1; // blocking strategy(未使用) var bs_sr = data_int1(); // block size, sample rate w = data_int1(); // channel assignment, sample size channel_assign = w >> 4; // channel assignment if(channel_assign & 0x8) { if(n_channels != 2) throw 0; if(channel_assign > 10) throw 0; } else { if(channel_assign + 1 != n_channels) throw 0; } // bits per sample switch((w & 0xe) >> 1) { case 0: sample_bits = sample_bits_si; break; case 1: sample_bits = 8; break; case 2: sample_bits = 12; break; case 4: sample_bits = 16; break; case 5: sample_bits = 20; break; case 6: sample_bits = 24; break; default: throw 0; } // sample number/frame number(未使用) var number; w = data_int1(); if(w & 0x80) { if(!(w & 0x40)) throw 0; var c = 1; for(var bit = 0x20; bit; bit >>= 1) { if(!(w & bit)) break; c++; } if(!bit) throw 0; number = w & (0x3f >> c); for(; c; c--) { if(i_data + 1 > data.length) throw 0; w = data_int1(); if((w & 0xc0) != 0x80) throw 0; number = number * 64 + (w & 0x3f); } } else { number = w; } // block size if((w = bs_sr >> 4) & 0x8) { block_size = 1 << w; // 256/512/1024/2048/4096/8192/16384/32768 } else { switch(w) { case 0: throw 0; case 1: block_size = 192; break; case 6: if(i_data + 1 > data.length) throw 0; block_size = data_int1() + 1; break; case 7: if(i_data + 2 > data.length) throw 0; block_size = data_int2() + 1; break; default: block_size = 576 << (w - 2); // 576/1152/2304/4608 } } // sample rate switch(w = bs_sr & 0xf) { case 0: w = sample_rate_si; break; case 12: if(i_data + 1 > data.length) throw 0; w = data_int1() * 1000; break; case 13: if(i_data + 2 > data.length) throw 0; w = data_int2(); break; case 14: if(i_data + 2 > data.length) throw 0; w = data_int2() * 10; break; case 15: throw 0; default: w = sample_rate_tbl[w]; } if(sample_rate) { if(w != sample_rate) { frame_error("このプログラムは、可変サンプリング レートには対応していません。"); return; } } else { sample_rate = w; } // CRC-8(未使用) if(i_data + 1 > data.length) throw 0; data_int1(); } catch(e if e == 0) { frame_error(ERR_FORMAT); return; } // CRC-16 // x^16 + x^15 + x^2 + 1 crc_16 = 0; for(var i = 0; i < i_data; ) crc_16 = ((crc_16 & 0xff) << 8) ^ crc_tbl[(crc_16 >> 8) ^ data.charCodeAt(i++)]; if(block_size > buff0.length) { buff0 = new Int32Array(block_size); if(n_channels > 1) buff1 = new Int32Array(block_size); } // ここからはビット単位で読み込み data_bit = 0; i_ch = 0; get_frame_4(); } function get_frame_4() { // SUBFRAME // SUBFRAME は可変長なので,1 チャネルの SUBFRAME 全体が含まれると思われるバイト数読み込む. // (ただし,ファイルの終端を超えない範囲で) // このプログラムでは対応サンプル サイズを最大 24 ビットとしている. // サンプル サイズ調整により,SUBFRAME のサンプル サイズは 1 ビット増える場合がある. // したがって,SUBFRAME のデコード後のデータの大きさは最大で ブロック長×25 ビット. // デコード前の大きさは,通常はデコード後の大きさより小さいはずであるが,条件によっては // デコード後より大きくなる場合もあるかも知れない. // (そのような場合は SUBFRAME_VERBATIM を使うはずなので,デコード前の大きさの方が大きい // 場合は考慮しなくても,たぶん大丈夫だろうとは思うが) // 念のため,ブロック長×32 ビット分読み込むようにする. // (ただし,そこには SUBFRAME_HEADER も含まれる) data = data.substr(i_data); var len = (block_size << 2) - data.length; if(len > 0) { if(file_pos + len > file.size) len = file.size - file_pos; if(len > 0) { prev_data = data; read_file(len, get_frame_5); return; } } i_data = 0; get_frame_6(); } function get_frame_5() { data = prev_data + data; get_frame_6(); } function get_frame_6() { var w; var i; try { var buff; switch(i_ch) { case 0: buff = buff0; break; case 1: buff = buff1; break; default: buff = null; } // sample size 調整 var sample_bits_adj = sample_bits; switch(channel_assign) { case 8: // left/side if(i_ch == 1) sample_bits_adj++; break; case 9: // right/side if(i_ch == 0) sample_bits_adj++; break; case 10: // mid/side if(i_ch == 1) sample_bits_adj++; break; } if(get_bits(1)) // zero throw 0; var sub_type = get_bits(6); // subframe type // wasted bits-per-sample w = get_bits(1); var wasted = 0; if(w) { for(; ; ) { wasted++; if(get_bits(1)) break; } sample_bits_adj -= wasted; } var sample_sign = 0xffffffff << (sample_bits_adj - 1); var order; // predictor order if (sub_type & 0x20) { // SUBFRAME_LPC order = (sub_type & 0x1f) + 1; // order i = 0; if(buff) { for(; i < order; i++) buff[i] = (get_bits(sample_bits_adj) + sample_sign) ^ sample_sign; } else { for(; i < order; i++) get_bits(sample_bits_adj); } var prec = get_bits(4) + 1; // coefficient precision if(prec == 16) throw 0; // coefficient shift w = get_bits(5); // シフトがマイナスになることがあるのか? var shift; var shift_dir; if(w & 0x10) { // マイナス shift = 0x10000 >> (w & 0xf); shift_dir = false; } else { shift = 1 << w; shift_dir = true; } w = 0xffffffff << (prec - 1); for(i = 0; i < order; i++) coeff[i] = (get_bits(prec) + w) ^ w; // RESIDUAL residual(buff, order); if(buff) { for(i = order; i < block_size; i++) { var sum = 0; for(var j = 0, k = i - 1; j < order; j++, k--) sum += buff[k] * coeff[j]; buff[i] += Math.floor((shift_dir) ? sum / shift : sum * shift); } } } else if(sub_type & 0x10) { // reserved throw 0; } else if(sub_type & 0x08) { order = sub_type & 0x07; if(order <= 4) { // SUBFRAME_FIXED i = 0; if(buff) { for(; i < order; i++) buff[i] = (get_bits(sample_bits_adj) + sample_sign) ^ sample_sign; } else { for(; i < order; i++) get_bits(sample_bits_adj); } // RESIDUAL residual(buff, order); // データ if(buff) { switch(order) { case 1: for(; i < block_size; i++) buff[i] += buff[i - 1]; break; case 2: for(; i < block_size; i++) buff[i] += (buff[i - 1] << 1) - buff[i - 2]; break; case 3: for(; i < block_size; i++) buff[i] += 3 * (buff[i - 1] - buff[i - 2]) + buff[i - 3]; break; case 4: for(; i < block_size; i++) buff[i] += ((buff[i - 1] + buff[i - 3]) << 2) - 6 * buff[i - 2] - buff[i - 4]; break; } } } else { // reserved throw 0; } } else if(sub_type & 0x06) { // reserved throw 0; } else if(sub_type & 0x01) { // SUBFRAME_VERBATIM i = 0; if(buff) { for(; i < block_size; i++) buff[i] = (get_bits(sample_bits_adj) + sample_sign) ^ sample_sign; } else { for(; i < block_size; i++) get_bits(sample_bits_adj); } } else { // SUBFRAME_CONSTANT w = (get_bits(sample_bits_adj) + sample_sign) ^ sample_sign; if(buff) { for(i = 0; i < block_size; i++) buff[i] = w; } } // wasted bits if(wasted) { if(buff) { for(i = 0; i < block_size; i++) buff[i] <<= wasted; } } } catch(e if e == 0) { frame_error(ERR_FORMAT); return; } // CRC-16 // x^16 + x^15 + x^2 + 1 for(i = 0; i < i_data; ) crc_16 = ((crc_16 & 0xff) << 8) ^ crc_tbl[(crc_16 >> 8) ^ data.charCodeAt(i++)]; if(++i_ch < n_channels) { // 次チャネル get_frame_4(); return; } // Interchannel Decorrelation if(channel_assign >= 8) { i = 0; switch(channel_assign) { case 8: // left/side for(; i < block_size; i++) buff1[i] = buff0[i] - buff1[i]; break; case 9: // right/side for(; i < block_size; i++) buff0[i] += buff1[i]; break; default: // mid/side for(; i < block_size; i++) { var side = buff1[i]; var mid = (buff0[i] << 1) | (side & 0x1); buff0[i] = (mid + side) >> 1; buff1[i] = (mid - side) >> 1; } } } // zero-padding // 単に,バッファに残っているビットを捨てるだけ // FRAME_FOOTER data = data.substr(i_data); var len = 2 - data.length; if(len > 0) { if(file_pos + len > file.size) { len = file.size - file_pos; if(data.length + len < 2) { frame_error(ERR_FORMAT); return; } } if(len > 0) { prev_data = data; read_file(len, get_frame_7); return; } } i_data = 0; get_frame_8(); } function get_frame_7() { data = prev_data + data; get_frame_8(); } function get_frame_8() { // CRC-16 if(data_int2() != crc_16) { frame_error(ERR_FORMAT); return; } frame_cont(false); } // Residual function residual(buff, pred_order) { var param_bits; var escape; switch(get_bits(2)) { // coding method case 0: // RESIDUAL_CODING_METHOD_PARTITIONED_RICE param_bits = 4; escape = 15; break; case 1: // RESIDUAL_CODING_METHOD_PARTITIONED_RICE2 param_bits = 5; escape = 31; break; default: // reserved throw 0; } var part_order = get_bits(4); // partition order var n_parts = 1 << part_order; var part_size = block_size >> part_order; // partition size var off = 0; for(var i_part = 0; i_part < n_parts; i_part++) { // RICE_PARTITION,RICE2_PARTITION var param = get_bits(param_bits); // rice parameter var i = (i_part) ? 0 : pred_order; if(param == escape) { // escape var n = get_bits(5); if(!n) throw 0; if(buff) { var sign = 0xffffffff << (n - 1); for(; i < part_size; i++) buff[off + i] = (get_bits(n) + sign) ^ sign; } else { for(; i < part_size; i++) get_bits(n); } } else { if(buff) { for(; i < part_size; i++) { var val = 0; while(!get_bits(1)) val++; if(param) val = (val << param) | get_bits(param); buff[off + i] = (val & 0x1) ? ~(val >> 1) : val >> 1; } } else { for(; i < part_size; i++) { while(!get_bits(1)) ; if(param) get_bits(param); } } } off += part_size; } } function frame_error(msg) { alert(msg); aud_end(); } // データの配列から 1 バイト Integer を取り出す. function data_int1() { return data.charCodeAt(i_data++); } // データの配列から 2 バイト Integer を取り出す. function data_int2() { var val = (data.charCodeAt(i_data) << 8) | data.charCodeAt(i_data + 1); i_data += 2 return val; } // データの配列から 3 バイト Integer を取り出す. function data_int3() { var val = (data.charCodeAt(i_data) << 16) | (data.charCodeAt(i_data + 1) << 8) | data.charCodeAt(i_data + 2); i_data += 3 return val; } // データの配列から 4 バイト Integer を取り出す. // 結果の MSB が 1 の場合,Integer の値としては負になる. function data_int4() { var val = (data.charCodeAt(i_data) << 24) | (data.charCodeAt(i_data + 1) << 16) | (data.charCodeAt(i_data + 2) << 8) | data.charCodeAt(i_data + 3); i_data += 4 return val; } // データの配列から little endian の 4 バイト Integer を取り出す. // 結果の MSB が 1 の場合,Integer の値としては負になる. function data_int4_le() { var val = data.charCodeAt(i_data) | (data.charCodeAt(i_data + 1) << 8) | (data.charCodeAt(i_data + 2) << 16) | (data.charCodeAt(i_data + 3) << 24); i_data += 4 return val; } // ビット単位読み込み function get_bits(n) { var bits = 0; for(; n; n--) { if(!data_bit) { if(i_data == data.length) throw 0; data_byte = data.charCodeAt(i_data++); data_bit = 0x80; } bits <<= 1; if(data_byte & data_bit) bits |= 0x1; data_bit >>= 1; } return bits; } // UTF-8 → UTF-16 変換 function utf8to16(utf8) { var utf16 = ""; for(var i = 0; i < utf8.length; ) { var c = utf8.charCodeAt(i); if ((c & 0xe0) == 0xc0) { utf16 += String.fromCharCode(((c & 0x1f) << 6) | utf8.charCodeAt(i + 1) & 0x3f); i += 2; } else if((c & 0xf0) == 0xe0) { utf16 += String.fromCharCode(((c & 0x0f) << 12) | ((utf8.charCodeAt(i + 1) & 0x3f) << 6) | utf8.charCodeAt(i + 2) & 0x3f); i += 3; } else { utf16 += utf8[i++]; } } return utf16; } //---------------------------------------------------------- // sample rate テーブル sample_rate_tbl = [0, 88200, 176400, 192000, 8000, 16000, 22050, 24000, 32000, 44100, 48000, 96000]; ERR_FORMAT = "ファイルの形式が正しくないか、このプログラムでは対応していないバージョンのファイルです。"; coeff = new Int16Array(32); // coefficients // CRC テーブル // x^16 + x^15 + x^2 + 1 crc_tbl = new Uint16Array(256); crc_tbl[0x00] = 0; crc_tbl[0x01] = 0x8005; crc_tbl[0x02] = 0x800f; crc_tbl[0x04] = 0x801b; crc_tbl[0x08] = 0x8033; crc_tbl[0x10] = 0x8063; crc_tbl[0x20] = 0x80c3; crc_tbl[0x40] = 0x8183; crc_tbl[0x80] = 0x8303; for(i = 3; i < 256; i++) crc_tbl[i] = crc_tbl[i & 0x01] ^ crc_tbl[i & 0x02] ^ crc_tbl[i & 0x04] ^ crc_tbl[i & 0x08] ^ crc_tbl[i & 0x10] ^ crc_tbl[i & 0x20] ^ crc_tbl[i & 0x40] ^ crc_tbl[i & 0x80]; file_reader = new FileReader(); file_reader.onload = file_end; file_reader.onerror = file_err; slice = (typeof File.prototype.slice == "function"); running = false; elem_file = document.getElementById("file"); elem_ttl = document.getElementById("ttl"); elem_art = document.getElementById("art"); elem_play = document.getElementById("play"); elem_time = document.getElementById("time"); // ページを再ロードしたときのため document.forms[0].reset(); elem_file.disabled = false; elem_play.disabled = true; //--> </SCRIPT> </BODY> </HTML> |