FLAC3.htm

戻る

<!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 4)</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 sel_file() {
  elem_ttl.textContent = elem_art.textContent = "";
  elem_play.disabled = true;

  data = undefined;

  file_reader = new FileReader();
  file_reader.onload = sel_file_fr;
  file_reader.onerror = sel_file_err;
  file_reader.readAsBinaryString(elem_file.files.item(0));
}

function sel_file_fr() {
  data = file_reader.result;
  file_reader = undefined;

  sel_file2();
}

function sel_file_err() {
  alert("ファイル読み込みでエラーが発生しました。");
}

function sel_file2() {
  // METADATA_BLOCK
  switch(get_metadata()) {
  case -1:
    alert(ERR_FORMAT);
    return;
  case -2:
    return;
  }

  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;
  i_data = i_audio;
  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
        switch(get_frame()) {
        case -1:
          alert(ERR_FORMAT);
          // fall thru
        case -2:
          aud_end();
          return;

        case 1:  // EOF
          eos = 1;  // ファイル読み込み終了
          break;

        default:  // データあり
          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;
        }
        if(buff_off == block_size)  // データ終了
          break;
      }

      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 aud_end() {
  elem_aud = undefined;
  samples = undefined;

  elem_time.textContent = "";
  elem_play.value = "再生";
  running = false;
  elem_file.disabled = false;
}

// FLAC ****************************************************

// METADATA_BLOCK
// 戻り値 - 0: 正常,-1: エラー,-2: エラー(メッセージ出力済み)
function get_metadata() {
  if(data.substr(0, 4) != "fLaC")
    return -1;
  i_data = 4;

  for(; ; ) {
    var i;

    // METADATA_BLOCK_HEADER
    if(i_data + 4 > data.length)
      return -1;
    var flg_typ = data_int1();
    var len = data_int3();
    if(i_data + len > data.length)
      return -1;
    // METADATA_BLOCK_DATA
    switch(flg_typ & 0x7f) {  // BLOCK_TYPE
    case 0:  // STREAMINFO
      if(len < 34)
        return -1;
      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)
        return -1;
      n_channels = ((w & 0xe00) >> 9) + 1;  // number of channels
      if(n_channels > 6)
        return -1;
      sample_bits_si = ((w & 0x1f0) >> 4) + 1;  // bits per sample
      if(sample_bits_si < 4)
        return -1;
      if(sample_bits_si > 24) {
        alert("このプログラムは、24 ビットを超えるサンプル サイズには対応していません。");
        return -2;
      }
      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 i_skip = i_data + len;

      if(len < 4)
        return -1;
      if((len = data_int4_le()) & 0x80000000)  // vendor_length
        return -1;
      if(i_data + len > data.length)
        return -1;
      i_data += len;  // vendor_string

      if(i_data + 4 > data.length)
        return -1;
      var n_comments = data_int4_le();  // user_comment_list_length
      if(n_comments & 0x80000000)
        return -1;
      var f = 0x3;
      for(i = 0; i < n_comments; i++) {
        if(i_data + 4 > data.length)
          return -1;
        if((len = data_int4_le()) & 0x80000000)  // length
          return -1;
        if(i_data + len > data.length)
          return -1;
        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;
      }

      i_data = i_skip;
      break;

    case 127:
      return -1;

    default:
      i_data += len;
    }

    if(flg_typ & 0x80)  // last metadata block
      break;
  }

  i_audio = i_data;  // オーディオ開始位置保存

  buff0 = new Int32Array(256);
  buff1 = (n_channels == 1) ? undefined : new Int32Array(256);

  return 0;
}

// FRAME
// 戻り値 - 0: 正常,1: EOF,-1: エラー,-2: エラー(メッセージ出力済み)
function get_frame() {
  var w;
  var i;

  // FRAME_HEADER
  var i_header = i_data;

  if(i_data == data.length)  // EOF
    return 1;

  if(i_data + 5 > data.length)
    return -1;

  w = data_int2();  // sync code, blocking strategy
  if((w & 0xfffc) != 0xfff8)  // sync code ?
    return -1;
  var blocking = w & 0x1;  // blocking strategy(未使用)

  var bs_sr = data_int1();  // block size, sample rate

  w = data_int1();  // channel assignment, sample size
  var channel_assign = w >> 4;  // channel assignment
  if(channel_assign & 0x8) {
    if(n_channels != 2)
      return -1;
    if(channel_assign > 10)
      return -1;
  }
  else {
    if(channel_assign + 1 != n_channels)
      return -1;
  }
  // 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:
    return -1;
  }

  // sample number/frame number(未使用)
  var number;
  w = data_int1();
  if(w & 0x80) {
    if(!(w & 0x40))
      return -1;
    var c = 1;
    for(var bit = 0x20; bit; bit >>= 1) {
      if(!(w & bit))
        break;
      c++;
    }
    if(!bit)
      return -1;
    number = w & (0x3f >> c);
    for(; c; c--) {
      if(i_data + 1 > data.length)
        return -1;
      w = data_int1();
      if((w & 0xc0) != 0x80)
        return -1;
      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:
      return -1;
    case 1:
      block_size = 192;
      break;
    case 6:
      if(i_data + 1 > data.length)
        return -1;
      block_size = data_int1() + 1;
      break;
    case 7:
      if(i_data + 2 > data.length)
        return -1;
      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)
      return -1;
    w = data_int1() * 1000;
    break;
  case 13:
    if(i_data + 2 > data.length)
      return -1;
    w = data_int2();
    break;
  case 14:
    if(i_data + 2 > data.length)
      return -1;
    w = data_int2() * 10;
    break;
  case 15:
    return -1;
  default:
    w = sample_rate_tbl[w];
  }
  if(sample_rate) {
    if(w != sample_rate) {
      alert("このプログラムは、可変サンプリング レートには対応していません。");
      return -2;
    }
  }
  else {
    sample_rate = w;
  }

  // CRC-8(未使用)
  if(i_data + 1 > data.length)
    return -1;
  data_int1();

  if(block_size > buff0.length) {
    buff0 = new Int32Array(block_size);
    if(n_channels > 1)
      buff1 = new Int32Array(block_size);
  }

  // ここからはビット単位で読み込み
  try {
    data_bit = 0;

    for(var i_ch = 0; i_ch < n_channels; i_ch++) {
      // SUBFRAME
      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
        return -1;
      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)
          return -1;
        // 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
        if(residual(buff, order))
          return -1;

        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
        return -1;
      }
      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
          if(residual(buff, order))
            return -1;

          // データ
          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
          return -1;
        }
      }
      else if(sub_type & 0x06) {  // reserved
        return -1;
      }
      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;
        }
      }
    }

    // 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
    // 単に,バッファに残っているビットを捨てるだけ
  }
  catch(e if e == 0) {
    return -1;
  }

  // FRAME_FOOTER
  if(i_data + 2 > data.length)
    return -1;
  // CRC-16
  // x^16 + x^15 + x^2 + 1
  var crc = 0;
  for(i = i_header; i < i_data; )
    crc = ((crc & 0xff) << 8) ^ crc_tbl[(crc >> 8) ^ data.charCodeAt(i++)];
  if(data_int2() != crc)
    return -1;

  return 0;
}

// Residual
// 戻り値 - 0: 正常,-1: エラー
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
    return -1;
  }

  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)
        return -1;
      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;
  }

  return 0;
}

// データの配列から 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];

  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>