![]() |
<!DOCTYPE HTML> <HTML LANG="ja"> <HEAD> <META CHARSET="Shift_JIS"> <TITLE>波形合成</TITLE> </HEAD> <BODY onLoad="resize()" onResize="resize()" STYLE="background-color:#CCFFFF"> <CENTER> <B>波形合成 (Firefox 20)</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; freq = false; } function freq_900() { for(var i = 0; i < 49; i++) elem_row[i].style.display = "none"; freq = true; } // 波形生成 function generate() { if(playing) // 発音中 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 データ作成 n_pcm = (freq) ? 49 : 147; 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(playing) { // 発音中 // 発音停止 elem_play.value = "音発生"; playing = false; return; } // 発音開始 elem_play.value = "音停止"; playing = true; pcm_off = 0; audio(); } function audio() { if(!playing) // 発音停止 return; for(; ; ) { if(samples_written - elem_aud.mozCurrentSampleOffset() >= 22050/* 500ms */) break; var written = elem_aud.mozWriteAudio(pcm.subarray(pcm_off, n_pcm - pcm_off)); samples_written += written; if((pcm_off += written) == n_pcm) pcm_off = 0; } setTimeout(audio, 100); } // 矩形波パラメータ セット 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; elem_aud = new Audio(); elem_aud.mozSetup(1, 44100); samples_written = 0; wave = new Float32Array(588); dth_1 = 2 * Math.PI / 588; pcm = new Float32Array(147); freq = false; // 300 Hz playing = false; // 各項目 サイズ固定 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> |