roulett3.htm

戻る

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

<HEAD>
<META CHARSET="Shift_JIS">
<TITLE>ルーレット(Web Audio API 版 2)</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) {  // 回転中
    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;

    // 曲を開始
    next_time = aud_ctx.currentTime;
    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() {
  if(decel < 1)  // 減速を開始した
    return;

  while(next_time < aud_ctx.currentTime + 0.1) {
    var i_freq;
    var note_len;
    for(; ; ) {
      if(!i_note) {  // 曲終わり
        // 次の曲を選択
        var i = Math.floor(Math.random() * (tunes.length - 1));
        if(i >= 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 == "_") {  // 休符
            i_freq = -1;
          }
          else {
            if(!i_note)
              continue;
            i_freq = note_oct * 12 + 3 + n_i[c.charCodeAt() - "A".charCodeAt()];
            switch(notes.charAt(i_note)) {  // 次の 1 文字を調べる
            case "+":  // ♯
              i_freq++;
              if(++i_note == notes.length)
                i_note = 0;
              break;
            case "-":  // ♭
              i_freq--;
              if(++i_note == notes.length)
                i_note = 0;
              break;
            }
            if(i_freq >= 88)
              i_freq = -1;
          }
          // 音符/休符の長さ
          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) {
            var n_val = parseInt(n, 10);
            if(n_val) {
              note_len = n_val * 0.05442;
              break;
            }
          }
        }
      }
    }

    if(i_freq >= 0) {  // 休符でない
      with(aud_ctx.createOscillator()) {
        frequency.value = freq[i_freq];
        onended = osc_ended;  // Firefox 25,26 では機能しない
        connect(gain_node);
        start(next_time);
        stop(next_time + note_len);
      }
    }

    next_time += note_len;
  }

  setTimeout(tune, 25);
}

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()) {
    gain_node = createGain();
    gain_node.gain.value = 0.3;
    gain_node.connect(destination);
  }

  // 音階周波数テーブル作成
  // [48] が 440Hz
  freq = new Float32Array(88);
  for(i = 0; i < 88; i++)
    freq[i] = 440 * Math.pow(2, (i - 48) / 12);

  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>