![]() |
<!DOCTYPE HTML> <HTML LANG="ja"> <HEAD> <META CHARSET="Shift_JIS"> <TITLE>FM 音源みたいなもの</TITLE> </HEAD> <BODY STYLE="background-color:#CCFFFF"> <DIV STYLE="text-align:center"> <BR> <B>FM 音源みたいなもの</B> <BR><BR> <FORM> <DIV STYLE="display:inline-block; text-align:left"> アルゴリズム: <SELECT ID="alg" onChange="alg_sel()"> <OPTION>1 <OPTION>2 <OPTION>3 <OPTION>4 <OPTION>5 <OPTION>6 <OPTION>7 </SELECT> <SPAN STYLE="display:inline-block; width:211px; height:81px; overflow:hidden; vertical-align:top; margin-left:2px"> <IMG ID="fig" SRC="images/algo.gif" WIDTH=211 HEIGHT=567 STYLE="position:relative; left:0; top:0"> </SPAN> <BR><BR> <TABLE BORDER=1 CELLSPACING=0> <TR><TD STYLE="background-color:#CCCCCC"></TD> <TD ALIGN=CENTER STYLE="background-color:#CCCCCC">周波数(Hz)</TD> <TD ALIGN=CENTER STYLE="background-color:#CCCCCC">出力レベル</TD> <TD ALIGN=CENTER STYLE="background-color:#CCCCCC">変調レベル</TD> </TR> <TR><TD ALIGN=LEFT STYLE="background-color:#CCCCCC">OP1</TD> <TD STYLE="background-color:white"><INPUT TYPE=TEXT ID="freq0" SIZE=18 STYLE="border-style:none; background-color:white"></TD> <TD STYLE="background-color:white"><INPUT TYPE=TEXT ID="out0" SIZE=18 STYLE="border-style:none; background-color:white"></TD> <TD STYLE="background-color:white"><INPUT TYPE=TEXT ID="mod0" SIZE=18 STYLE="border-style:none; background-color:white"></TD> </TR> <TR><TD ALIGN=LEFT STYLE="background-color:#CCCCCC">OP2</TD> <TD STYLE="background-color:white"><INPUT TYPE=TEXT ID="freq1" SIZE=18 STYLE="border-style:none; background-color:white"></TD> <TD STYLE="background-color:white"><INPUT TYPE=TEXT ID="out1" SIZE=18 STYLE="border-style:none; background-color:white"></TD> <TD STYLE="background-color:white"><INPUT TYPE=TEXT ID="mod1" SIZE=18 STYLE="border-style:none; background-color:white"></TD> </TR> <TR><TD ALIGN=LEFT STYLE="background-color:#CCCCCC">OP3</TD> <TD STYLE="background-color:white"><INPUT TYPE=TEXT ID="freq2" SIZE=18 STYLE="border-style:none; background-color:white"></TD> <TD STYLE="background-color:white"><INPUT TYPE=TEXT ID="out2" SIZE=18 STYLE="border-style:none; background-color:white"></TD> <TD STYLE="background-color:white"><INPUT TYPE=TEXT ID="mod2" SIZE=18 STYLE="border-style:none; background-color:white"></TD> </TR> <TR><TD ALIGN=LEFT STYLE="background-color:#CCCCCC">OP4</TD> <TD STYLE="background-color:white"><INPUT TYPE=TEXT ID="freq3" SIZE=18 STYLE="border-style:none; background-color:white"></TD> <TD STYLE="background-color:white"><INPUT TYPE=TEXT ID="out3" SIZE=18 STYLE="border-style:none; background-color:white"></TD> <TD STYLE="background-color:white"><INPUT TYPE=TEXT ID="mod3" SIZE=18 STYLE="border-style:none; background-color:white"></TD> </TR> </TABLE> <BR> <TABLE BORDER=1 CELLSPACING=0> <TR><TD STYLE="background-color:#CCCCCC"></TD> <TD ALIGN=CENTER STYLE="background-color:#CCCCCC">AR(/ms)</TD> <TD ALIGN=CENTER STYLE="background-color:#CCCCCC">DR(/ms)</TD> <TD ALIGN=CENTER STYLE="background-color:#CCCCCC">SR(/ms)</TD> <TD ALIGN=CENTER STYLE="background-color:#CCCCCC">RR(/ms)</TD> <TD ALIGN=CENTER STYLE="background-color:#CCCCCC">SL</TD> </TR> <TR><TD ALIGN=LEFT STYLE="background-color:#CCCCCC">OP1</TD> <TD STYLE="background-color:white"><INPUT TYPE=TEXT ID="ar0" SIZE=18 STYLE="border-style:none; background-color:white"></TD> <TD STYLE="background-color:white"><INPUT TYPE=TEXT ID="dr0" SIZE=18 STYLE="border-style:none; background-color:white"></TD> <TD STYLE="background-color:white"><INPUT TYPE=TEXT ID="sr0" SIZE=18 STYLE="border-style:none; background-color:white"></TD> <TD STYLE="background-color:white"><INPUT TYPE=TEXT ID="rr0" SIZE=18 STYLE="border-style:none; background-color:white"></TD> <TD STYLE="background-color:white"><INPUT TYPE=TEXT ID="sl0" SIZE=18 STYLE="border-style:none; background-color:white"></TD> </TR> <TR><TD ALIGN=LEFT STYLE="background-color:#CCCCCC">OP2</TD> <TD STYLE="background-color:white"><INPUT TYPE=TEXT ID="ar1" SIZE=18 STYLE="border-style:none; background-color:white"></TD> <TD STYLE="background-color:white"><INPUT TYPE=TEXT ID="dr1" SIZE=18 STYLE="border-style:none; background-color:white"></TD> <TD STYLE="background-color:white"><INPUT TYPE=TEXT ID="sr1" SIZE=18 STYLE="border-style:none; background-color:white"></TD> <TD STYLE="background-color:white"><INPUT TYPE=TEXT ID="rr1" SIZE=18 STYLE="border-style:none; background-color:white"></TD> <TD STYLE="background-color:white"><INPUT TYPE=TEXT ID="sl1" SIZE=18 STYLE="border-style:none; background-color:white"></TD> </TR> <TR><TD ALIGN=LEFT STYLE="background-color:#CCCCCC">OP3</TD> <TD STYLE="background-color:white"><INPUT TYPE=TEXT ID="ar2" SIZE=18 STYLE="border-style:none; background-color:white"></TD> <TD STYLE="background-color:white"><INPUT TYPE=TEXT ID="dr2" SIZE=18 STYLE="border-style:none; background-color:white"></TD> <TD STYLE="background-color:white"><INPUT TYPE=TEXT ID="sr2" SIZE=18 STYLE="border-style:none; background-color:white"></TD> <TD STYLE="background-color:white"><INPUT TYPE=TEXT ID="rr2" SIZE=18 STYLE="border-style:none; background-color:white"></TD> <TD STYLE="background-color:white"><INPUT TYPE=TEXT ID="sl2" SIZE=18 STYLE="border-style:none; background-color:white"></TD> </TR> <TR><TD ALIGN=LEFT STYLE="background-color:#CCCCCC">OP4</TD> <TD STYLE="background-color:white"><INPUT TYPE=TEXT ID="ar3" SIZE=18 STYLE="border-style:none; background-color:white"></TD> <TD STYLE="background-color:white"><INPUT TYPE=TEXT ID="dr3" SIZE=18 STYLE="border-style:none; background-color:white"></TD> <TD STYLE="background-color:white"><INPUT TYPE=TEXT ID="sr3" SIZE=18 STYLE="border-style:none; background-color:white"></TD> <TD STYLE="background-color:white"><INPUT TYPE=TEXT ID="rr3" SIZE=18 STYLE="border-style:none; background-color:white"></TD> <TD STYLE="background-color:white"><INPUT TYPE=TEXT ID="sl3" SIZE=18 STYLE="border-style:none; background-color:white"></TD> </TR> </TABLE> <BR> OPn: オペレータ n (n = 1,2,3,4)<BR> AR: アタック レート,DR: ディケイ レート,SR: サステイン レート,RR: リリース レート,SL: サステイン レベル<BR><BR> <INPUT TYPE=BUTTON VALUE="パラメータ読込" onClick="load_para()"> <INPUT TYPE=BUTTON VALUE="パラメータ保存" onClick="save_para()" STYLE="margin-left:1em"> <INPUT TYPE=BUTTON VALUE="パラメータ クリア" onClick="clear_para()" STYLE="margin-left:1em"> </DIV> <BR><BR> <INPUT TYPE=BUTTON VALUE="発音" onMouseDown="key_on()" onMouseUp="key_off()" onMouseOut="key_off()" STYLE="font-size:large"> </FORM> </DIV> <A ID="dl" DOWNLOAD="FMsynth.dat" STYLE="display:none"></A><!--パラメータ保存処理用--> <SCRIPT TYPE="text/javascript"> <!-- // 小数 キー入力チェック function check_float(e) { return (e.which < 0x20 || e.which >= 0x30 && e.which <= 0x39 || e.which == 0x2e || e.ctrlKey || e.metaKey); } // アルゴリズム選択 function alg_sel() { for(let i_op = 0; i_op < 4; i_op++) { env[i_op].gain.cancelScheduledValues(0); env[i_op].gain.value = 0; // Opera で必要 } et[0] = 0; out[0].disconnect(); out[1].disconnect(); out[2].disconnect(); out[0].connect(feedback); switch(elem_alg.selectedIndex) { case 0: out[0].connect(mod[1]); out[1].connect(mod[2]); out[2].connect(mod[3]); break; case 1: out[0].connect(mod[2]); out[1].connect(mod[2]); out[2].connect(mod[3]); break; case 2: out[0].connect(mod[3]); out[1].connect(mod[2]); out[2].connect(mod[3]); break; case 3: out[0].connect(mod[1]); out[1].connect(mod[3]); out[2].connect(mod[3]); break; case 4: out[0].connect(mod[1]); out[1].connect(aud_ctx.destination); out[2].connect(mod[3]); break; case 5: out[0].connect(mod[1]); out[0].connect(mod[2]); out[0].connect(mod[3]); out[1].connect(aud_ctx.destination); out[2].connect(aud_ctx.destination); break; case 6: out[0].connect(aud_ctx.destination); out[1].connect(aud_ctx.destination); out[2].connect(aud_ctx.destination); break; } // 接続図 elem_fig.style.top = String(elem_alg.selectedIndex * -81) + "px"; } // パラメータ読み込み function load_para() { elem_ul = document.createElement("INPUT"); elem_ul.type = "FILE"; elem_ul.onchange = ul_sel; elem_ul.click(); } function ul_sel() { clear_para(); file_reader = new FileReader(); file_reader.onload = ul_sel2; file_reader.readAsText(elem_ul.files.item(0), "utf-8"); } function ul_sel2() { let vals = file_reader.result.split("\0"); file_reader = undefined; if(vals.length != 33) return; let i = parseInt(vals[0], 10); if(!isNaN(i)) { if(i >= 0 && i < 7) { elem_alg.selectedIndex = i; alg_sel(); } } i = 1; for(let i_op = 0; i_op < 4; i_op++) { elem_freq[i_op].value = vals[i++]; elem_out[i_op].value = vals[i++]; elem_mod[i_op].value = vals[i++]; elem_ar[i_op].value = vals[i++]; elem_dr[i_op].value = vals[i++]; elem_sr[i_op].value = vals[i++]; elem_rr[i_op].value = vals[i++]; elem_sl[i_op].value = vals[i++]; } } // パラメータ保存 function save_para() { let elem = document.getElementById("dl"); if(elem.download == undefined) return; let params = String(elem_alg.selectedIndex); for(let i_op = 0; i_op < 4; i_op++) { params += "\0" + elem_freq[i_op].value; params += "\0" + elem_out[i_op].value; params += "\0" + elem_mod[i_op].value; params += "\0" + elem_ar[i_op].value; params += "\0" + elem_dr[i_op].value; params += "\0" + elem_sr[i_op].value; params += "\0" + elem_rr[i_op].value; params += "\0" + elem_sl[i_op].value; } elem.href = "data:application/octet-stream," + encodeURI(params); elem.click(); } // パラメータ クリア function clear_para() { document.forms[0].reset(); elem_alg.selectedIndex = 0; alg_sel(); } // 発音開始 function key_on() { let i_op; for(i_op = 0; i_op < 4; i_op++) { if(to_id[i_op] != undefined) { clearTimeout(to_id[i_op]); to_id[i_op] = undefined; } } // パラメータ設定 for(i_op = 0; i_op < 4; i_op++) { let val; // 周波数 val = str_float(elem_freq[i_op].value); osc[i_op].frequency.value = val; mod2[i_op].gain.value = val * aud_ctx.sampleRate / 100; // 出力レベル val = str_float(elem_out[i_op].value); if(val > 1) { alert("出力レベルは 0 〜 1 の値を入力してください。"); return; } out[i_op].gain.value = val; // 変調レベル val = str_float(elem_mod[i_op].value); if(val > 1) { alert("変調レベルは 0 〜 1 の値を入力してください。"); return; } mod[i_op].gain.value = val; // アタック レート val = str_float(elem_ar[i_op].value); if(val > 1) { alert("アタック レートは 0 〜 1 の値を入力してください。"); return; } ar[i_op] = val; // ディケイ レート val = str_float(elem_dr[i_op].value); if(val > 1) { alert("ディケイ レートは 0 〜 1 の値を入力してください。"); return; } dr[i_op] = val; // サステイン レート val = str_float(elem_sr[i_op].value); if(val > 1) { alert("サステイン レートは 0 〜 1 の値を入力してください。"); return; } sr[i_op] = val; // リリース レート val = str_float(elem_rr[i_op].value); if(val > 1) { alert("リリース レートは 0 〜 1 の値を入力してください。"); return; } rr[i_op] = val; // サステイン レベル val = str_float(elem_sl[i_op].value); if(val > 1) { alert("サステイン レベルは 0 〜 1 の値を入力してください。"); return; } sl[i_op] = val; } let ct = aud_ctx.currentTime; for(i_op = 0; i_op < 4; i_op++) { env[i_op].gain.cancelScheduledValues(0); env[i_op].gain.value = 0; // Opera で必要 if(ar[i_op]) { // アタック レートあり env[i_op].gain.setValueAtTime(0, ct); env[i_op].gain.linearRampToValueAtTime(1, dt[i_op] = ct + 0.001 / ar[i_op]); } else { env[i_op].gain.setValueAtTime(1, dt[i_op] = ct); } if(sl[i_op]) { // サステイン レベルあり if(dr[i_op]) env[i_op].gain.linearRampToValueAtTime(sl[i_op], st[i_op] = dt[i_op] + (1 - sl[i_op]) / (dr[i_op] * 1000)); else env[i_op].gain.setValueAtTime(sl[i_op], st[i_op] = dt[i_op]); } else { sl[i_op] = 1; st[i_op] = dt[i_op]; } if(sr[i_op]) // サステイン レートあり env[i_op].gain.linearRampToValueAtTime(0, et[i_op] = st[i_op] + sl[i_op] / (sr[i_op] * 1000)); else et[i_op] = Number.MAX_VALUE; } } function str_float(str) { str = str.trim(); let val = 0; if(str.length) { if(str.search(/^\s*\d*\.?\d*\s*$/) != -1) { val = parseFloat(str); if(isNaN(val)) val = 0; } } return val; } // 発音停止 function key_off() { if(!et[0]) return; let ct = aud_ctx.currentTime; for(let i_op = 0; i_op < 4; i_op++) { env[i_op].gain.cancelScheduledValues(0); env[i_op].gain.value = 0; // Opera で必要 if(rr[i_op]) { // リリース レートあり if(ct < et[i_op]) { // 出力中 let val; if(ct < st[i_op]) { // サステイン前 if(ct < dt[i_op]) // アタック中 val = 1 - (dt[i_op] - ct) * (ar[i_op] * 1000); else // ディケイ中 val = 1 - (ct - dt[i_op]) * (dr[i_op] * 1000); } else { // サステイン中 val = sl[i_op] - (ct - st[i_op]) * (sr[i_op] * 1000); } env[i_op].gain.setValueAtTime(val, ct); let time = val / rr[i_op]; env[i_op].gain.linearRampToValueAtTime(0, ct + time / 1000); to_id[i_op] = setTimeout(clear_auto, time, i_op); } } else { env[i_op].gain.setValueAtTime(0, 0); } } et[0] = 0; } function clear_auto(i_op) { env[i_op].gain.cancelScheduledValues(0); env[i_op].gain.value = 0; // Opera で必要 to_id[i_op] = undefined; } const osc = new Array(); osc.length = 4; const env = new Array(); env.length = 4; const out = new Array(); out.length = 4; const mod = new Array(); mod.length = 4; const dif = new Array(); dif.length = 4; const mod2 = new Array(); mod2.length = 4; let feedback; const aud_ctx = new AudioContext(); with(aud_ctx) { // オペレータ作成 for(let i_op = 0; i_op < 4; i_op++) { osc[i_op] = createOscillator(); osc[i_op].start(); env[i_op] = createGain(); // エンベロープ作成用 env[i_op].gain.value = 0; osc[i_op].connect(env[i_op]); out[i_op] = createGain(); // 出力レベル用 env[i_op].connect(out[i_op]); mod2[i_op] = createGain(); // 周波数変位調整用 mod2[i_op].connect(osc[i_op].frequency); dif[i_op] = createIIRFilter([1.0, -1.0], [1.0]); dif[i_op].connect(mod2[i_op]); mod[i_op] = createGain(); // 変調レベル用 mod[i_op].connect(dif[i_op]); } // オペレータ 1 フィードバック // 循環する接続を作る場合は,間に DelayNode を挟む必要がある feedback = createDelay(); feedback.connect(mod[0]); out[3].connect(destination); } const ar = new Float64Array(4); // アタック レート const dr = new Float64Array(4); // ディケイ レート const sr = new Float64Array(4); // サステイン レート const rr = new Float64Array(4); // リリース レート const sl = new Float64Array(4); // サステイン レベル const dt = new Float64Array(4); // ディケイ開始時刻 const st = new Float64Array(4); // サステイン開始時刻 const et = new Float64Array(4); // サステインで出力がゼロになる時刻 et[0] = 0; const to_id = [undefined, undefined, undefined, undefined]; const elem_alg = document.getElementById("alg"); const elem_fig = document.getElementById("fig"); const elem_freq = new Array(); elem_freq.length = 4; const elem_out = new Array(); elem_out.length = 4; const elem_mod = new Array(); elem_mod.length = 4; const elem_ar = new Array(); elem_ar.length = 4; const elem_dr = new Array(); elem_dr.length = 4; const elem_sr = new Array(); elem_sr.length = 4; const elem_rr = new Array(); elem_rr.length = 4; const elem_sl = new Array(); elem_sl.length = 4; for(let i_op = 0; i_op < 4; i_op++) { let str_i = String(i_op); (elem_freq[i_op] = document.getElementById("freq" + str_i)).onkeypress = check_float; (elem_out[i_op] = document.getElementById("out" + str_i)).onkeypress = check_float; (elem_mod[i_op] = document.getElementById("mod" + str_i)).onkeypress = check_float; (elem_ar[i_op] = document.getElementById("ar" + str_i)).onkeypress = check_float; (elem_dr[i_op] = document.getElementById("dr" + str_i)).onkeypress = check_float; (elem_sr[i_op] = document.getElementById("sr" + str_i)).onkeypress = check_float; (elem_rr[i_op] = document.getElementById("rr" + str_i)).onkeypress = check_float; (elem_sl[i_op] = document.getElementById("sl" + str_i)).onkeypress = check_float; } let elem_ul; let file_reader; // ページを再ロードしたときのため document.forms[0].reset(); elem_alg.selectedIndex = 0; alg_sel(); //--> </SCRIPT> </BODY> </HTML> |