<!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;
}
// Opera 60 以上でも動くようにするため
if(aud_ctx.resume !== undefined)
aud_ctx.resume();
if(osc_ini) {
for(i_op = 0; i_op < 4; i_op++)
osc[i_op].start();
osc_ini = false;
}
}
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 osc_ini = true;
let elem_ul;
let file_reader;
// ページを再ロードしたときのため
document.forms[0].reset();
elem_alg.selectedIndex = 0;
alg_sel();
//-->
</SCRIPT>
</BODY>
</HTML>
|