<!DOCTYPE HTML>
<HTML LANG="ja">
<HEAD>
<META CHARSET="Shift_JIS">
<TITLE>波形合成(Web Audio API 版)</TITLE>
</HEAD>
<BODY onLoad="resize()" onResize="resize()" STYLE="background-color:#CCFFFF">
<CENTER>
<B>波形合成(Web Audio API 版)</B>
<BR>
<FORM>
<TABLE><TR>
<TD VALIGN=TOP>
<TABLE BORDER=0 CELLSPACING=0 CELLPADDING=0><TR>
<TD VALIGN=MIDDLE><CANVAS ID="img" WIDTH=589 HEIGHT=321></CANVAS></TD>
<TD WIDTH=5></TD>
<TD VALIGN=MIDDLE>t</TD>
</TR></TABLE>
sin t + a<SUB>2</SUB>・sin(2t+α<SUB>2</SUB>) + a<SUB>3</SUB>・sin(3t+α<SUB>3</SUB>) + … + a<SUB>N</SUB>・sin(Nt+α<SUB>N</SUB>)<BR>
周波数 300 Hz のとき N=73,900 Hz のとき N=24<BR><BR>
周波数
<LABEL><INPUT TYPE=RADIO NAME="freq" CHECKED onClick="freq_300()">300 Hz</LABEL>
<LABEL><INPUT TYPE=RADIO NAME="freq" onClick="freq_900()">900 Hz</LABEL><BR><BR>
<INPUT TYPE=BUTTON VALUE="波形生成" onClick="generate()">
<INPUT TYPE=BUTTON ID="play" VALUE="音発生" onClick="play_stop()" STYLE="margin-left:2em"><BR><BR>
パラメータ
<INPUT TYPE=BUTTON VALUE="矩形波" onClick="square()" STYLE="margin-left:1em">
<INPUT TYPE=BUTTON VALUE="三角波" onClick="triangle()">
<INPUT TYPE=BUTTON VALUE="鋸歯状波" onClick="sawtooth()">
<INPUT TYPE=BUTTON VALUE="クリア" onClick="clear_()" STYLE="margin-left:1em"><BR>
<SPAN STYLE="visibility:hidden">パラメータ</SPAN>
<INPUT TYPE=BUTTON VALUE="読込" onClick="load()" STYLE="margin-left:1em">
<INPUT TYPE=BUTTON VALUE="保存" onClick="save()" STYLE="margin-left:1em">
</TD>
<TD WIDTH=10></TD>
<TD VALIGN=TOP>
<TABLE BORDER=0 CELLSPACING=0 CELLPADDING=0>
<THEAD>
<TR STYLE="float:left"><TH ID="h_n">n</TH><TH STYLE="width:4px"></TH><TH ID="h_a">a<SUB>n</SUB></TH><TH ID="h_p">α<SUB>n</SUB>(度)</TH></TR>
</THEAD>
<TBODY ID="params" STYLE="float:left; overflow:scroll">
<TR>
<TD ID="b_n" ALIGN=RIGHT>2</TD><TD ROWSPAN=72 STYLE="width:4px"></TD>
<TD ID="b_a"><INPUT TYPE=TEXT ID="a2" SIZE=30></TD>
<TD ID="b_p"><INPUT TYPE=TEXT ID="p2" SIZE=20></TD>
</TR>
<SCRIPT TYPE="text/javascript">
<!--
for(n = 3; n <= 73; n++) {
str_n = String(n);
document.writeln("<TR", (n > 24) ? " ID='row" + str_n + "'" : "", "><TD ALIGN=RIGHT>", str_n, "</TD>",
"<TD><INPUT TYPE=TEXT ID='a", str_n, "' SIZE=30></TD>",
"<TD><INPUT TYPE=TEXT ID='p", str_n, "' SIZE=20></TD></TR>");
}
//-->
</SCRIPT>
</TBODY>
</TABLE>
</TD>
</TR></TABLE>
</FORM>
</CENTER>
<A ID="dl" DOWNLOAD="wave.dat" STYLE="display:none"></A><!--パラメータ保存処理用-->
<SCRIPT TYPE="text/javascript">
<!--
function resize() {
elem_params.style.height = "";
var rect = elem_params.getBoundingClientRect();
if(Math.ceil(rect.bottom) > document.documentElement.clientHeight) {
var h = document.documentElement.clientHeight - 8 - Math.ceil(rect.top);
elem_params.style.height = ((h > 0) ? String(h) : "1") + "px";
}
}
// 符号なし小数 キー入力チェック
function check_ufloat(e) {
return (e.which < 0x20 || e.which >= 0x30 && e.which <= 0x39 || e.which == 0x2e
|| e.ctrlKey || e.metaKey);
}
// 符号付き小数 キー入力チェック
function check_sfloat(e) {
return (e.which < 0x20 || e.which >= 0x30 && e.which <= 0x39 || e.which == 0x2e || e.which == 0x2d
|| e.ctrlKey || e.metaKey);
}
function sel_entire(e) {
if(e.detail == 2) { // ダブル クリック
e.currentTarget.select();
e.preventDefault();
}
}
function freq_300() {
var off = elem_params.scrollTop;
for(var i = 0; i < 49; i++)
elem_row[i].style.display = "";
elem_params.scrollTop = off;
resize();
freq = false;
}
function freq_900() {
for(var i = 0; i < 49; i++)
elem_row[i].style.display = "none";
resize();
freq = true;
}
// 波形生成
function generate() {
if(aud_src != undefined) // 発音中
play_stop(); // 発音停止
for(var i = 1; i < 73; i++)
elem_a[i].style.backgroundColor = elem_p[i].style.backgroundColor = "";
// 基音
for(var i = 0; i < 588; i++)
wave[i] = Math.sin(i * dth_1);
// 倍音
var err = false;
var N = (freq) ? 24 : 73;
for(var i_ap = 1; i_ap < N; i_ap++) {
var str;
// an
var an;
str = elem_a[i_ap].value.trim();
if(str.length) {
an = (str.search(/^\s*\d*\.?\d*\s*$/) == -1) ? NaN : parseFloat(str);
if((isNaN(an)) ? true : (an >= 1.)) {
elem_a[i_ap].style.backgroundColor = "#ffc0a0";
err = true;
}
}
else {
an = 0.;
}
// αn
var pn;
str = elem_p[i_ap].value.trim();
if(str.length) {
pn = (str.search(/^\s*-?\d*\.?\d*\s*$/) == -1) ? NaN : parseFloat(str);
if((isNaN(pn)) ? true : (pn <= -360. || pn >= 360.)) {
elem_p[i_ap].style.backgroundColor = "#ffc0a0";
err = true;
}
}
else {
pn = 0.;
}
if(err)
continue;
if(an != 0.) {
pn = pn / 180. * Math.PI;
var dth = dth_1 * (i_ap + 1);
for(var i = 0; i < 588; i++)
wave[i] += an * Math.sin(i * dth + pn);
}
}
if(err) {
// 基音のみ
for(var i = 0; i < 588; i++)
wave[i] = Math.sin(i * dth_1);
}
var max = 0.;
for(i = 0; i < 588; i++) {
var val = Math.abs(wave[i]);
if(val > max)
max = val;
}
// 描画
ctx_img.fillStyle = "black";
ctx_img.fillRect(0, 0, 589, 321);
ctx_img.strokeStyle = "#006000";
ctx_img.beginPath();
ctx_img.moveTo(-1, 160);
ctx_img.lineTo(589, 160);
ctx_img.stroke();
ctx_img.strokeStyle = "white";
var k = 158 / max;
ctx_img.beginPath();
ctx_img.moveTo(0, 160 - k * wave[0]);
for(var i = 1; i < 588; i++)
ctx_img.lineTo(i, 160 - k * wave[i]);
ctx_img.lineTo(588, 160 - k * wave[0]);
ctx_img.stroke();
// PCM データ作成
var n_pcm = (freq) ? 49 : 147;
aud_buf = aud_ctx.createBuffer(1, n_pcm, 44100);
var pcm = aud_buf.getChannelData(0);
var step = (freq) ? 12 : 4;
max *= 10; // 音量を控え目に
for(var i = 0, j = 0; i < n_pcm; i++, j += step)
pcm[i] = wave[j] / max;
if(err)
alert("パラメータの入力に誤りがあります。");
}
// 音発生/停止
function play_stop() {
if(aud_src != undefined) { // 発音中
// 発音停止
elem_play.value = "音発生";
aud_src.stop(0); // Opera では,パラメータを省略するとエラーになる
aud_src.disconnect();
aud_src = undefined;
return;
}
// 発音開始
elem_play.value = "音停止";
aud_src = aud_ctx.createBufferSource();
aud_src.loop = true;
aud_src.buffer = aud_buf;
aud_src.connect(aud_ctx.destination);
aud_src.start(0); // Opera では,パラメータをすべて省略するとエラーになる
}
// 矩形波パラメータ セット
function square() {
for(var i = 1; i < 73; i++) {
elem_a[i].value = (i & 0x1) ? "" : String(1. / (i + 1));
elem_p[i].value = "";
}
}
// 三角波パラメータ セット
function triangle() {
for(var i = 1; i < 73; i++) {
var n = i + 1;
elem_a[i].value = (i & 0x1) ? "" : String(1. / (n * n));
elem_p[i].value = ((i & 0x3) == 2) ? "180" : "";
}
}
// 鋸歯状波パラメータ セット
function sawtooth() {
for(var i = 1; i < 73; i++) {
elem_a[i].value = String(1. / (i + 1));
elem_p[i].value = "";
}
}
// パラメータ クリア
function clear_() {
for(var i = 1; i < 73; i++)
elem_a[i].value = elem_p[i].value = "";
}
// パラメータ読み込み
function load() {
elem_ul = document.createElement("INPUT");
elem_ul.type = "FILE";
elem_ul.onchange = ul_sel;
elem_ul.click();
}
function ul_sel() {
file_reader = new FileReader();
file_reader.onload = ul_sel2;
file_reader.readAsText(elem_ul.files.item(0), "UTF-8");
}
function ul_sel2() {
var rows = file_reader.result.split("\n");
file_reader = undefined;
for(var i = 1; i < 73; i++) {
if(i > rows.length) {
for(; i < 73; i++)
elem_a[i].value = elem_p[i].value = "";
break;
}
var a_p = rows[i - 1].split("\0");
elem_a[i].value = a_p[0];
elem_p[i].value = (a_p.length == 1) ? "" : a_p[1];
}
}
// パラメータ保存
function save() {
var elem = document.getElementById("dl");
if(elem.download == undefined)
return;
var params = "";
for(var i = 1; i < 73; i++)
params += elem_a[i].value + "\0" + elem_p[i].value + "\n";
elem.href = "data:application/octet-stream," + encodeURI(params);
elem.click();
}
//----------------------------------------------------------
elem_play = document.getElementById("play");
elem_params = document.getElementById("params");
elem_row = new Array();
elem_row.length = 49;
elem_a = new Array();
elem_a.length = 73;
elem_p = new Array();
elem_p.length = 73;
for(i = 1; i < 73; i++) {
str_n = String(i + 1);
if(i >= 24)
elem_row[i - 24] = document.getElementById("row" + str_n);
elem_a[i] = elem = document.getElementById("a" + str_n);
elem.onkeypress = check_ufloat;
elem.onmousedown = sel_entire;
elem_p[i] = elem = document.getElementById("p" + str_n);
elem.onkeypress = check_sfloat;
elem.onmousedown = sel_entire;
}
ctx_img = document.getElementById("img").getContext("2d");
ctx_img.translate(0.5, 0.5);
ctx_img.lineWidth = 1;
aud_ctx = (window.webkitAudioContext == undefined) ? new AudioContext() : new webkitAudioContext();
aud_src = undefined;
wave = new Float32Array(588);
dth_1 = 2 * Math.PI / 588;
freq = false; // 300 Hz
// 各項目 サイズ固定
elem = document.getElementById("b_n");
document.getElementById("h_n").style.width = elem.style.width = String(elem.clientWidth) + "px";
elem = document.getElementById("b_a");
document.getElementById("h_a").style.width = elem.style.width = String(elem.clientWidth) + "px";
elem = document.getElementById("b_p");
document.getElementById("h_p").style.width = elem.style.width = String(elem.clientWidth) + "px";
elem_params.style.width = String(elem_params.offsetWidth) + "px";
elem_params.style.overflow = "auto";
// ページを再ロードしたときのため
document.forms[0].reset();
elem_params.scrollTop = 0;
generate();
//-->
</SCRIPT>
</BODY>
</HTML>
|