roulett2.htm

戻る

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

<HEAD>
<META CHARSET="Shift_JIS">
<TITLE>ルーレット(Web Audio API 版)</TITLE>
</HEAD>

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

<DIV ALIGN=CENTER>
<BR>

<FORM>
<TABLE><TR>
<TD ALIGN=CENTER VALIGN=TOP>
<DIV STYLE="position:relative">
<CANVAS ID="img" WIDTH=240 HEIGHT=240></CANVAS>
<CANVAS ID="det" WIDTH=242 HEIGHT=242 STYLE="position:absolute; left:-1px; top:-1px; visibility:hidden"></CANVAS>
<IMG SRC="images/pointer.png" WIDTH=16 HEIGHT=12 STYLE="position:absolute; left:234px; top:114px">
</DIV>
<BR>
<INPUT TYPE=BUTTON ID="start" VALUE="スタート" onClick="start_stop()" STYLE="font-size:large">
</TD>
<TD WIDTH=30></TD>
<TD ALIGN=LEFT VALIGN=TOP>
<INPUT TYPE=BUTTON ID="load" VALUE="選択肢" onClick="load_sel()"><BR>
<DIV STYLE="width:12em; overflow:hidden; background-color:white; border:solid 1px; padding:2px">
<PRE ID="sel" STYLE="margin:0; cursor:default; -moz-user-select:none; -webkit-user-select:none"></PRE>
</DIV><BR>
<LABEL><INPUT ID="div_1" TYPE=RADIO NAME="div" onClick="sel_div(1)">分割なし</LABEL>
<LABEL><INPUT ID="div_2" TYPE=RADIO NAME="div" onClick="sel_div(2)">2分割</LABEL>
<LABEL><INPUT ID="div_3" TYPE=RADIO NAME="div" CHECKED onClick="sel_div(3)">3分割</LABEL>
</TD>
</TR></TABLE>
</FORM>

</DIV>

<PRE ID="mea" STYLE="float:left; margin:0; visibility:hidden"></PRE><!-- テキスト サイズ計測用 -->

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

// ファイル読み込み
function load_sel() {
  elem_file = document.createElement("INPUT");
  elem_file.type = "FILE";
  elem_file.onchange = file_sel;
  elem_file.click();
}

function file_sel() {
  file_reader = new FileReader();
  file_reader.onload = file_sel2;
  file_reader.readAsText(elem_file.files.item(0), "Shift_JIS");
}

function file_sel2() {
  sel_str = file_reader.result.split(/\r\n|\n|\r/);
  file_reader = undefined;

  if(!sel_str[sel_str.length - 1].length)
    sel_str.length--;
  var cnt = 12 - sel_str.length;
  if(cnt < 0)
    sel_str.length = 12;
  var str = sel_str.join("\u200b\n");
  for(; cnt > 0; cnt--)
    str += "\u200b\n";
  elem_sel.textContent = str + "\u200b";

  if(!sel_str.length) {  // 選択肢なし
    elem_det.style.visibility = "hidden";

    with(ctx_img) {
      clearRect(0, 0, 240, 240);
      beginPath();
      arc(120, 120, 119.5, 0, PI2, false);
      fillStyle = "white";
      fill();
      stroke();
    }

    n_sel = 0;

    elem_start.disabled = true;
    return;
  }
  elem_start.disabled = false;

  wheel();  // 盤面作成
}

// 分割数選択
function sel_div(n) {
  n_div = n;

  if(n_sel)  // 選択肢あり
    wheel();  // 盤面作成
}

// スタート/ストップ
function start_stop() {
  if(speed) {  // 回転中
    // 曲を停止
    scr_node.onaudioprocess = null;
    scr_node.disconnect();

    elem_start.disabled = true;

    decel = 0.996 + Math.random() * 0.002;  // 減速割合
  }
  else {
    elem_det.style.visibility = "hidden";

    elem_start.value = "ストップ";
    elem_load.disabled = elem_div_1.disabled = elem_div_2.disabled = elem_div_3.disabled = true;

    speed = Math.PI / 90;
    decel = 1;

    i_tune = Math.floor(Math.random() * tunes.length);
    i_note = 0;
    note_len = 0;

    // 曲を開始
    scr_node.connect(gain_node);
    scr_node.onaudioprocess = tune;

    int_id = setInterval(rotate, 16);
  }
}

// 回転
function rotate() {
  var prev_theta = theta;
  var n;
  speed *= decel;
  if((theta += speed) > PI2) {
    theta -= PI2;
  }
  else {
    if(decel < 1) {  // 減速中
      if((n = Math.floor((theta + angle_2) / angle)) != Math.floor((prev_theta + angle_2) / angle)) {
                                                                                // 選択肢の境を越えた
        // 減速中の音
        with(aud_ctx.createOscillator()) {
          frequency.value = 900;
          onended = osc_ended;  // Firefox 25,26 では機能しない
          connect(gain_node);
          start(0);
          stop(aud_ctx.currentTime + 0.09);
        }
      }
    }
  }

  draw_wheel();  // 盤面を描画する

  if(speed < 0.0002) {
    // 停止
    clearInterval(int_id);

    speed = 0;

    // 確定の表示
    with(ctx_det) {
      clearRect(0, 0, 242, 242);
      beginPath();
      if(n_pie == 1) {
        arc(121, 121, 119.5, 0, PI2, false);
      }
      else {
        moveTo(121, 121);
        var a = theta - n * angle;
        arc(121, 121, 119.5, - angle_2 + a, angle_2 + a, false);
        closePath();
      }
      stroke();
    }

    det_cnt = 9;
    int_id = setInterval(stopped, 80);
  }
}

// 確定表示
function stopped() {
  if(det_cnt-- & 0x1) {
    elem_det.style.visibility = "visible";

    // 確定時の音
    with(aud_ctx.createOscillator()) {
      frequency.value = 1400;
      onended = osc_ended;  // Firefox 25,26 では機能しない
      connect(gain_node);
      start(0);
      stop(aud_ctx.currentTime + 0.08);
    }

    if(!det_cnt) {  // 確定表示終了
      clearInterval(int_id);

      elem_start.value = "スタート";
      elem_start.disabled
        = elem_load.disabled = elem_div_1.disabled = elem_div_2.disabled = elem_div_3.disabled = false;
    }
  }
  else {
    elem_det.style.visibility = "hidden";
  }
}

// 盤面を作成する
function wheel() {
  elem_det.style.visibility = "hidden";

  n_sel = sel_str.length;  // 選択肢数
  if(n_sel > 12 / n_div)
    n_sel = 12 / n_div;
  n_pie = n_sel * n_div;  // 盤の分割数

  angle = (angle_2 = Math.PI / n_pie) * 2;  // 扇形の角度とその 1/2
  if(n_pie > 2) {
    tan = Math.tan(angle_2);
    var y_2 = 121 * tan;
  }

  var elem_mea = document.getElementById("mea");
  elem_mea.style.font = elem_pie[0].getContext("2d").font;
  for(var i = 0; i < n_sel; i++) {
    with(elem_pie[i].getContext("2d")) {
      // 背景
      fillStyle = colors[i];
      if(n_pie > 2) {
        clearRect(0, 0, 120, 240);
        beginPath();
        moveTo(0, 120);
        lineTo(121, 120 - y_2);
        lineTo(121, 120 + y_2);
        fill();
      }
      else {
        fillRect(0, 0, 120, 240);
      }

      // テキスト
      // サイズ調整(最小は 8 ピクセルとする)
      elem_mea.textContent = sel_str[i];
      for(size = 14; size >= 8; size--) {
        elem_mea.style.fontSize = String(size) + "px";
        var h_2 = elem_mea.offsetHeight / 2;
        var x1 = (n_pie > 2) ? h_2 / tan : 0;
        var w = x1 + elem_mea.offsetWidth;
        if(w * w + h_2 * h_2 <= 14161/* 119*119 */)
          break;
      }
      var x2 = Math.sqrt(14161/* 119*119 */ - h_2 * h_2);
      var x;
      if(size == 7) {  // 8 ピクセルでも収まりきらない
        x = x1;
        save();
        beginPath();
        rect(0, 0, x2, 240);
        clip();
      }
      else {
        x = (x1 + x2 - elem_mea.offsetWidth) / 2;
      }
      fillStyle = "black";
      font = elem_mea.style.font;
      fillText(sel_str[i], x, 120);
      if(size == 7)
        restore();
    }
  }

  theta = 0;

  draw_wheel();  // 盤面を描画する
}

// 盤面を描画する
function draw_wheel() {
  with(ctx_img) {
    clearRect(0, 0, 240, 240);

    beginPath();
    arc(120, 120, 119.5, 0, PI2, false);

    // 各扇形
    save();
    clip();
    fillStyle = (n_sel == 1) ? colors[0] : "white";
    fillRect(0, 0, 240, 240);
    var i_pie = 0;
    for(var cnt = n_div; cnt; cnt--) {
      for(var i = 0; i < n_sel; i++) {
        save();
        translate(120, 120);
        rotate(theta - i_pie * angle);
        drawImage(elem_pie[i], 0, -120);
        restore();

        i_pie++;
      }
    }
    restore();

    // 周囲
    stroke();
  }
}

// 曲
function tune(e) {
  var pcm = e.outputBuffer.getChannelData(0);
  for(var i_pcm = 0; i_pcm < pcm.length; i_pcm++) {
    var i;

    if(!note_len) {  // 1 音符/休符 終わり
      for(; ; ) {
        if(!i_note) {  // 曲終わり
          // 次の曲を選択
          if((i = Math.floor(Math.random() * (tunes.length - 1))) >= i_tune)
            i++;
          notes = tunes[i_tune = i];
          note_oct = 4;
        }

        var c = notes.charAt(i_note);  // 楽譜から 1 文字取り出し
        if(++i_note == notes.length)
          i_note = 0;

          if(c == "*") {
            // オクターブの設定
            if(!i_note)
              continue;
            c = notes.charAt(i_note);  // 次の 1 文字取り出し
            if(++i_note == notes.length)
              i_note = 0;
            if(c >= "0" && c <= "8")
              note_oct = c.charCodeAt() - "0".charCodeAt();
          }
          else {
            if(c >= "A" && c <= "G" || c == "_") {
              if(c == "_") {  // 休符
                note_a1 = 0;
            }
            else {
              if(!i_note)
                continue;
              i = note_oct * 12 + 3 + n_i[c.charCodeAt() - "A".charCodeAt()];
              switch(notes.charAt(i_note)) {  // 次の 1 文字を調べる
              case "+":  // ♯
                i++;
                if(++i_note == notes.length)
                  i_note = 0;
                break;
              case "-":  // ♭
                i--;
                if(++i_note == notes.length)
                  i_note = 0;
                break;
              }
              if(i >= 0 && i < 88)
                note_a1 = freq_a[i];
            }
            // 音符/休符の長さ
            var n = "";
            while(i_note) {
              c = notes.charAt(i_note);  // 次の 1 文字を調べる
              if(c < "0" || c > "9")
                break;
              n += c;
              if(++i_note == notes.length)
                i_note = 0;
            }
            if(n.length) {
              if((note_len = parseInt(n, 10) * UNIT_LEN)) {
                note_cnt = 0;
                break;
              }
            }
          }
        }
      }
    }

    if(note_a1 == 0) {  // 休符
      val = 0;
    }
    else {
      i = Math.round(note_cnt++ * note_a1) & 0xfff;
      switch(i & 0xc00) {
      case 0x000:
        val = sin[i];
        break;
      case 0x400:
        val = sin[2048 - i];
        break;
      case 0x800:
        val = - sin[i - 2048];
        break;
      default:
        val = - sin[4096 - i];
      }
      if(note_len < DEC_LEN)
        val *= note_len / DEC_LEN;
    }
    pcm[i_pcm] = val;

    note_len--;
  }
}

function dummy(e) {
  var pcm = e.outputBuffer.getChannelData(0);
  for(var i_pcm = 0; i_pcm < pcm.length; i_pcm++)
    pcm[i_pcm] = 0;
}

function osc_ended(e) {
  e.currentTarget.disconnect();
}

  colors = ["#FF9999", "#99FF99", "#FFFF99", "#9999FF", "#FF99FF", "#99FFFF",   // 背景色
            "#FFCC99", "#99CCFF", "#FF99CC", "#99FFCC", "#CC99FF", "#CCFF99"];
  sel_str = ["カツ丼", "カレーライス", "ラーメン", "ハンバーガー"];  // 選択肢文字列
  tunes = [  // 曲テーブル
    "*4G3_1G4G4E4G4A4G4E7_1E4D11_1E4D7_1G3_1G4G4E4G4A4G4E7_1D8E4D4C11_1"
  +   "G3_1G4G4E4G4A4G4E7_1E4D11_1E4D7_1G3_1G4G4E4G4A4G4E7_1D8E4D4C11_1",
    "*4C2D2E4G4G6A2G4E4C6D2E4E4D4C4D11_1C2D2E4G4G6A2G4E4C6D2E4E4D4D4C11_1"
  +   "C2D2E4G4G6A2G4E4C6D2E4E4D4C4D11_1C2D2E4G4G6A2G4E4C6D2E4E4D4D4C11_1",
    "*4G2G2G2_2G2G2G2_2G2G2*5C2_2D2_2E2_2*4G2G2G2_2G2G2*5C2_2E2_2D2_2*4B2_2G2_2"
  +   "G2G2G2_2G2G2G2_2G2G2*5C2_2D2_2E2_2C2E2G10F2E2D2C2_2E2_2C2_2"
  + "*4G2G2G2_2G2G2G2_2G2G2*5C2_2D2_2E2_2*4G2G2G2_2G2G2*5C2_2E2_2D2_2*4B2_2G2_2"
  +   "G2G2G2_2G2G2G2_2G2G2*5C2_2D2_2E2_2C2E2G10F2E2D2C2_2E2_2C2_2",
    "*4A2G2C2_2*5C2_2C2_2*4A2G2C2_2*5C2_2C2_2*4A2G2C2_2*5C2_2*3A2_2*5C2_2*3G2_2*4B2_2B2_2"
  + "A2G2*3G2_2*4B2_2B2_2A2G2*3G2_2*4B2_2B2_2A2G2*3G2_2*4B2_2*3A2_2*4B2_2C2_2*5C2_2C2_2"
  + "*4A2G2*5E2_2C2_2C2_2*4A2G2*5E2_2C2_2C2_2*4A2G2*5E2_2C2_2G2_2C2_2A2_2*4B2_2B2_2"
  + "A2G2*5A2_2*4B2_2B2_2A2G2*5A2_2*4B2_2B2_2A2G2*5A2_2*4B2_2*5G2_2*4B2_2*5E2_2C2_2C2_2"
  ];
  n_i = [9, 11, 0, 2, 4, 5, 7];  // 音名 - 周波数インデックス 変換

  // 盤面表示用 Canvas
  (ctx_img = (elem_img = document.getElementById("img")).getContext("2d")).strokeStyle = "black";

  // 作業用 Canvas
  elem_pie = new Array();
  elem_pie.length = 12;
  for(i = 0; i < 12; i++) {
    with(elem_pie[i] = document.createElement("CANVAS")) {
      width = 120;
      height = 240;
      with(getContext("2d")) {
        textAlign = "left";
        textBaseline = "middle";
      }
    }
  }

  // 確定表示用 Canvas
  with(ctx_det = (elem_det = document.getElementById("det")).getContext("2d")) {
    strokeStyle = "red";
    lineWidth = 2;
  }

  // 選択肢
  (elem_sel = document.getElementById("sel"))
    .textContent = sel_str.join("\n")
                     + "\n\u200b\n\u200b\n\u200b\n\u200b\n\u200b\n\u200b\n\u200b\n\u200b";  // 選択肢サンプル

  n_div = 3;  // 分割数

  PI2 = 2 * Math.PI;

  wheel();  // 盤面作成

  speed = 0;

  with(aud_ctx = (window.webkitAudioContext == undefined) ? new AudioContext() : new webkitAudioContext()) {
    scr_node = createScriptProcessor(4096, 0, 1);  // Opera では,bufferSize に 0 を指定すると
                                                   // シンタックス エラーになる
    gain_node = createGain();
    gain_node.gain.value = 0.3;
    gain_node.connect(destination);
    UNIT_LEN = Math.round(2400 * sampleRate / 44100);
    DEC_LEN = Math.round(UNIT_LEN / 120);
  }
  // ページ ロード後の初回の再生で,初めのうちしばらく音が
  // 途切れがちになるので,無音を再生しておく
  scr_node.connect(gain_node);
  scr_node.onaudioprocess = dummy;

  // 音階周波数テーブル作成
  // [48] が 440Hz
  // sin テーブルのインデックスに対応する値に換算して格納する
  freq_a = new Float32Array(88);
  a = 440 / aud_ctx.sampleRate * 4096;
  for(i = 0; i < 88; i++)
    freq_a[i] = a * Math.pow(2, (i - 48) / 12);

  // sin テーブル作成
  sin = new Float32Array(1025);
  sin[0] = 0;
  sin[1024] = 1;
  for(i = 1; i < 1024; i++)
    sin[i] = Math.sin(i * Math.PI / 2048);

  elem_start = document.getElementById("start");
  elem_load = document.getElementById("load");
  elem_div_1 = document.getElementById("div_1");
  elem_div_2 = document.getElementById("div_2");
  elem_div_3 = document.getElementById("div_3");

  // ページを再ロードしたときのため
  document.forms[0].reset();
  elem_start.disabled
    = elem_load.disabled = elem_div_1.disabled = elem_div_2.disabled = elem_div_3.disabled = false;

//-->
</SCRIPT>

</BODY>

</HTML>