![]() |
<!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> |