wave2.htm

戻る

<!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>