←Previous | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | Next→
    break;
  case 11 + 2:  //M1 D2R
  case 11 + 9:  //M1 DT2
    for (let pn = 0; pn < OPM_PORT_COUNT; pn++) {
      for (let cn = 0; cn < 8; cn++) {
        postOpmSet (pn, 0xc0 + cn, (a[11 + 9] & 3) << 6 | (a[11 + 2] & 31));  //M1 DT2<<6|D2R
      }
    }
    break;
  case 22 + 2:  //C1 D2R
  case 22 + 9:  //C1 DT2
    for (let pn = 0; pn < OPM_PORT_COUNT; pn++) {
      for (let cn = 0; cn < 8; cn++) {
        postOpmSet (pn, 0xd0 + cn, (a[22 + 9] & 3) << 6 | (a[22 + 2] & 31));  //C1 DT2<<6|D2R
      }
    }
    break;
  case 33 + 2:  //M2 D2R
  case 33 + 9:  //M2 DT2
    for (let pn = 0; pn < OPM_PORT_COUNT; pn++) {
      for (let cn = 0; cn < 8; cn++) {
        postOpmSet (pn, 0xc8 + cn, (a[33 + 9] & 3) << 6 | (a[33 + 2] & 31));  //M2 DT2<<6|D2R
      }
    }
    break;
  case 44 + 2:  //C2 D2R
  case 44 + 9:  //C2 DT2
    for (let pn = 0; pn < OPM_PORT_COUNT; pn++) {
      for (let cn = 0; cn < 8; cn++) {
        postOpmSet (pn, 0xd8 + cn, (a[44 + 9] & 3) << 6 | (a[44 + 2] & 31));  //C2 DT2<<6|D2R
      }
    }
    break;
  case 11 + 3:  //M1 RR
  case 11 + 4:  //M1 D1L
    for (let pn = 0; pn < OPM_PORT_COUNT; pn++) {
      for (let cn = 0; cn < 8; cn++) {
        postOpmSet (pn, 0xe0 + cn, (a[11 + 4] & 15) << 4 | (a[11 + 3] & 15));  //M1 D1L<<4|RR
      }
    }
    break;
  case 22 + 3:  //C1 RR
  case 22 + 4:  //C1 D1L
    for (let pn = 0; pn < OPM_PORT_COUNT; pn++) {
      for (let cn = 0; cn < 8; cn++) {
        postOpmSet (pn, 0xf0 + cn, (a[22 + 4] & 15) << 4 | (a[22 + 3] & 15));  //C1 D1L<<4|RR
      }
    }
    break;
  case 33 + 3:  //M2 RR
  case 33 + 4:  //M2 D1L
    for (let pn = 0; pn < OPM_PORT_COUNT; pn++) {
      for (let cn = 0; cn < 8; cn++) {
        postOpmSet (pn, 0xe8 + cn, (a[33 + 4] & 15) << 4 | (a[33 + 3] & 15));  //M2 D1L<<4|RR
      }
    }
    break;
  case 44 + 3:  //C2 RR
  case 44 + 4:  //C2 D1L
    for (let pn = 0; pn < OPM_PORT_COUNT; pn++) {
      for (let cn = 0; cn < 8; cn++) {
        postOpmSet (pn, 0xf8 + cn, (a[44 + 4] & 15) << 4 | (a[44 + 3] & 15));  //C2 D1L<<4|RR
      }
    }
    break;
    //------------------------------------------------
    //TL
    //  M1はCON=7のとき出力スロットになる
    //  M2はCON=5,6,7のとき出力スロットになる
    //  C1はCON=4,5,6,7のとき出力スロットになる
    //  C2はCON=0,1,2,3,4,5,6,7のとき出力スロットになる
    //  出力スロットのTLは音色データの値に現在のボリュームを加えてからレジスタに設定する
    //  出力スロットのTLの音色データの値は最大ボリューム(0とは限らない)なので、
    //  発音中に現在のボリュームを無視して音色データの値をそのままレジスタに設定すると大きな音が出てしまうことがある
  case 11 + 5:  //M1 TL
    {
      let tl = a[11 + 5] & 127;
      if (7 <= (a[0] & 7)) {  //CON=7
        tl = min2 (127, tl + calculateVolume ());
      }
      for (let pn = 0; pn < OPM_PORT_COUNT; pn++) {
        for (let cn = 0; cn < 8; cn++) {
          postOpmSet (pn, 0x60 + cn, tl);  //M1 TL
        }
      }
    }
    break;
  case 22 + 5:  //C1 TL
    {
      let tl = a[22 + 5] & 127;
      if (4 <= (a[0] & 7)) {  //CON=4,5,6,7
        tl = min2 (127, tl + calculateVolume ());
      }
      for (let pn = 0; pn < OPM_PORT_COUNT; pn++) {
        for (let cn = 0; cn < 8; cn++) {
          postOpmSet (pn, 0x70 + cn, tl);  //C1 TL
        }
      }
    }
    break;
  case 33 + 5:  //M2 TL
    {
      let tl = a[33 + 5] & 127;
      if (5 <= (a[0] & 7)) {  //CON=5,6,7
        tl = min2 (127, tl + calculateVolume ());
      }
      for (let pn = 0; pn < OPM_PORT_COUNT; pn++) {
        for (let cn = 0; cn < 8; cn++) {
          postOpmSet (pn, 0x68 + cn, tl);  //M2 TL
        }
      }
    }
    break;
  case 44 + 5:  //C2 TL
    {
      let tl = a[44 + 5] & 127;
      //if (0 <= (a[0] & 7)) {  //CON=0,1,2,3,4,5,6,7
      tl = min2 (127, tl + calculateVolume ());
      //}
      for (let pn = 0; pn < OPM_PORT_COUNT; pn++) {
        for (let cn = 0; cn < 8; cn++) {
          postOpmSet (pn, 0x78 + cn, tl);  //C2 TL
        }
      }
    }
    break;
  case 11 + 7:  //M1 MUL
  case 11 + 8:  //M1 DT1
    for (let pn = 0; pn < OPM_PORT_COUNT; pn++) {
      for (let cn = 0; cn < 8; cn++) {
        postOpmSet (pn, 0x40 + cn, (a[11 + 8] & 7) << 4 | (a[11 + 7] & 15));  //M1 DT1<<4|MUL
      }
    }
    break;
  case 22 + 7:  //C1 MUL
  case 22 + 8:  //C1 DT1
    for (let pn = 0; pn < OPM_PORT_COUNT; pn++) {
      for (let cn = 0; cn < 8; cn++) {
        postOpmSet (pn, 0x50 + cn, (a[22 + 8] & 7) << 4 | (a[22 + 7] & 15));  //C1 DT1<<4|MUL
      }
    }
    break;
  case 33 + 7:  //M2 MUL
  case 33 + 8:  //M2 DT1
    for (let pn = 0; pn < OPM_PORT_COUNT; pn++) {
      for (let cn = 0; cn < 8; cn++) {
        postOpmSet (pn, 0x48 + cn, (a[33 + 8] & 7) << 4 | (a[33 + 7] & 15));  //M2 DT1<<4|MUL
      }
    }
    break;
  case 44 + 7:  //C2 MUL
  case 44 + 8:  //C2 DT1
    for (let pn = 0; pn < OPM_PORT_COUNT; pn++) {
      for (let cn = 0; cn < 8; cn++) {
        postOpmSet (pn, 0x58 + cn, (a[44 + 8] & 7) << 4 | (a[44 + 7] & 15));  //C2 DT1<<4|MUL
      }
    }
    break;
  }
  //<select>を更新する
  if (neiroSelectArray[i] != null &&
      neiroSelectArray[i].selectedIndex != v) {
    neiroSelectArray[i].selectedIndex = v;
  }
  //<textarea>を更新する
  if (neiroTextarea != null &&
      !neiroTextareaBusy) {  //テキストエリアの読み取り中ではない
    neiroTextarea.value = neiroToText (a);
  }
}

//  プリセット音色を設定する
//  すべてのチャンネルに設定される
function setPresetNeiro (neiroNumber) {
  let a = NEIRO_DATA[neiroNumber];
  for (let i = 0; i < 55; i++) {
    setNeiroElement (i, a[i]);
  }
}

//  OPMのレジスタに書き込む
function postOpmSet (pn, a, d) {
  if (audioAvailable) {
    opmpcmProcessor.port.postMessage ({
    t: audioContext.currentTime,
    c: "opmset",
    p: pn,
    a: a,
    d: d
    });
  }
}



let panelWidth;
let panelHeight;
let panelElement;

function resetPanelSize () {
  panelWidth = max2 (320, floor (min2 (getWindowWidth () * 0.9, getWindowHeight () * 0.9 * (16 / 9))));
  panelHeight = floor (panelWidth * (9 / 16));
}



function createTextURL (x, y, url) {
  return addEvent (setStyle (createText (x, y, url),
                             "cursor", "pointer"),
                   "click",
                   function () {
                     window.open (url, "_blank");
                   });
}



//コントロールパネル

const buttonSelectedColor = "#ffffff";
const buttonNotSelectedColor = "#808080";

let controlPanelArray;

let docButtonArray = [];
let neiroButtonArray = [];
let normalButtonArray = [];
let parallelButtonArray = [];
let crossButtonArray = [];
let floorFixedButtonArray = [];

let buttonX;
let buttonY;

//addButton (buttonArray, active, selected, margin, path, click)
//  コントロールパネルにボタンを追加する
//  buttonArray  ボタンを保存する配列
//  active       true=hoverでcursorがpointerになる
//  selected     true=strokeがselectedColor,false=strokeがnotSelectedColor,
//  margin       右側の余白
//  paint        SVG要素の配列を返す関数
//  click        クリックされたとき呼び出す関数
function addButton (buttonArray, active, selected, margin, path, title, click) {
  setDefaultFill ("#000000");  //塗り潰しておかないとクリックできない
  //setDefaultStroke ("#ffffff");
  setDefaultStrokeLinecap ("round");
  setDefaultStrokeLinejoin ("round");
  setDefaultStrokeWidth (2);
  //                         1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3
  //     0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
  //   0・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・
  //   1・・・・●・・・・・・・・・・・・・・・・・・・・・・・●・・・
  //   2・・●・・・・・・・・・・・・・・・・・・・・・・・・・・・●・
  //   3・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・
  //   4・●・・・・・・・・・・・・・・・・・・・・・・・・・・・・・●
  //   5・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・
  //   6・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・
  //   7・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・
  //   8・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・
  //   9・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・
  //  10・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・
  //  11・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・
  //  12・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・
  //  13・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・
  //  14・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・
  //  15・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・
  //  16・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・
  //  17・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・
  //  18・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・
  //  19・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・
  //  20・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・
  //  21・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・
  //  22・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・
  //  23・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・
  //  24・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・
  //  25・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・
  //  26・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・
  //  27・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・
  //  28・●・・・・・・・・・・・・・・・・・・・・・・・・・・・・・●
  //  29・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・
  //  30・・●・・・・・・・・・・・・・・・・・・・・・・・・・・・●・
  //  31・・・・●・・・・・・・・・・・・・・・・・・・・・・・●・・・
  for (let screenNumber = 0; screenNumber <= 2; screenNumber++) {
    let button = buttonArray[screenNumber] =
      appendNode (setAttr (createSvg (buttonX, buttonY, 32, 32, 0, 0, 32, 32),
                           "title", title),
                  createPath ("M 4 1 l -2 1 l -1 2 v 24 l 1 2 l 2 1 h 24 l 2 -1 l 1 -2 v -24 l -1 -2 l -2 -1 z" + " " + path));
    appendNode (controlPanelArray[screenNumber], addEvent (button, "click", click));
  }
  setButtonActiveSelected (buttonArray, active, selected);
  buttonX += 32 + margin;
}

//setButtonActiveSelected (buttonArray, active, selected)
//  ボタンの状態を設定する
//  active       true=hoverでcursorがpointerになる
//  selected     true=strokeがselectedColor,false=strokeがnotSelectedColor,
function setButtonActiveSelected (buttonArray, active, selected) {
  let stroke = selected ? buttonSelectedColor : buttonNotSelectedColor;
  for (let screenNumber = 0; screenNumber < 3; screenNumber++) {
    let button = buttonArray[screenNumber];
    setStyle (button,
              "cursor", active ? "pointer" : "default");
    for (let i = 0; i < button.childNodes.length; i++) {
      setStroke (button.childNodes[i], stroke);
    }
  }
}



//========================================================================
//ポップアップウインドウ
//  pw$innerBox  内側の箱。内容を書き込む
//  pw$interval  動作間隔[ms]
//  pw$max       最大の位置
//  pw$n         現在の位置。0..pw$max
//  pw$outerBox  外側の箱。document.bodyに追加する
//  pw$step      増分。-1=閉じている途中,0=動いていない,1=開いている途中
class PopupWindow {

  constructor (x, y, width, height) {

    this.pw$n = 0;
    this.pw$max = 10;
    this.pw$step = 0;
    this.pw$interval = 30;

    //外側の箱
    this.pw$outerBox = setStyle (createNodeDiv (),
                                 "backgroundColor", "transparent",
                                 "border", "#6b6bb6 solid 4px",
                                 "borderRadius", "8px",
                                 "display", "none",
                                 "height", height + "vh",
                                 "left", x + "vw",
                                 "overflow", "hidden",
                                 "position", "absolute",
                                 "top", y + "vh",
                                 "width", width + "vw");

    //内側の箱
    this.pw$innerBox = setStyle (createNodeDiv (),
                                 "backgroundColor", "rgba(0,0,0,0.8)",  //背景を半透明にする
                                 "height", "100%",
                                 "left", "0px",
                                 "overflow", "auto",
                                 "padding", "0px",
                                 "position", "absolute",
                                 "top", "0px",
                                 "width", "100%");

    //クローズボタン
    //  innerBoxの右上に表示する
    const d = 30;  //マルの直径
    const p = 3;  //マルとバツの隙間
    const m = 6;  //右と上のマージン
    let closeButton = appendNode (setStyle (createNodeDiv (),
                                            "backgroundColor", "#1e1e49",
                                            "border", "#8d8ddb solid 2px",
                                            "borderRadius", "50%",
                                            "cursor", "pointer",
                                            "height", d + "px",
                                            "margin", m + "px " + (12 + m) + "px 0px auto",
                                            "position", "relative",
                                            "width", d + "px"
                                            ),
                                  setStyle (createNodeDiv (),
                                            "backgroundColor", "#8d8ddb",
                                            "borderStyle", "none",
                                            "height", "2px",
                                            "left", p + "px",
                                            "position", "absolute",
                                            "top", (d / 2 - 3) + "px",
                                            "transform", "rotate(-45deg)",
                                            "width", (d - p * 2 - 4) + "px"
                                            ),
                                  setStyle (createNodeDiv (),
                                            "backgroundColor", "#8d8ddb",
                                            "borderStyle", "none",
                                            "height", "2px",
                                            "left", p + "px",
                                            "position", "absolute",
                                            "top", (d / 2 - 3) + "px",
                                            "transform", "rotate(45deg)",
                                            "width", (d - p * 2 - 4) + "px"
                                            ));

    addEvent (closeButton,
              "click",
              this.pw$close.bind (this));

    appendNode (this.pw$outerBox,
                this.pw$innerBox,
                closeButton);
  }

  //  完全に閉じているか
  pw$isClosed () {
    return this.pw$n == 0;
  }

  //  完全に開いているか
  pw$isOpen () {
    return this.pw$n == this.pw$max;
  }

  //  開閉する
  pw$openOrClose () {
    if (this.pw$isClosed ()) {  //完全に閉じている
      this.pw$open ();  //開く
    } else if (this.pw$isOpen ()) {  //完全に開いている
      this.pw$close ();  //閉じる
    }
  }

  //  開く
  pw$open () {
    if (this.pw$isClosed ()) {  //完全に閉じている
      setStyle (this.pw$outerBox,
                "opacity", 0,
                "display", "block");
      this.pw$step = 1;  //閉じる
      setTimeout (this.pw$timer.bind (this), this.pw$interval);
    }
  }

  //  閉じる
  pw$close () {
    if (this.pw$isOpen ()) {  //完全に開いている
      this.pw$step = -1;  //開く
      setTimeout (this.pw$timer.bind (this), this.pw$interval);
    }
  }

  //タイマー
  pw$timer () {
    this.pw$n = max2 (0, min2 (this.pw$max, this.pw$n + this.pw$step));
    setStyle (this.pw$outerBox,
              "opacity", this.pw$n / this.pw$max);
    if (this.pw$isOpen ()) {  //開き終わった
      this.pw$step = 0;
    } else if (this.pw$isClosed ()) {  //閉じ終わった
      this.pw$step = 0;
      setStyle (this.pw$outerBox,
                "display", "none",
                "opacity", 1);
    } else {  //継続
      setTimeout (this.pw$timer.bind (this), this.pw$interval);
    }
  }

}



const NEIRO_COL_NAME = [["FLCON", "SLOT", "WAVE", "SYNC", "SPEED", "PMD", "AMD", "PMS", "AMS", "RLPAN", ""],
                        ["AR", "D1R", "D2R", "RR", "D1L", "TL", "KS", "MUL", "DT1", "DT2", "AMSEN"]];
const NEIRO_ROW_NAME = ["", "M1", "C1", "M2", "C2"];

let neiroSelectArray = [];  //<select>の配列
let neiroTextarea = null;  //<textarea>
let neiroTextareaBusy = false;  //true=テキストエリアの読み取り中
let focusedElement = null;

//  音色をテキストに変換する
function neiroToText (a) {
  return "(v1,0," + a.join (",") + ")";
}

//  テキストを音色に変換する
//  音色パラメータの配列を返す
//  変換できなかったときはnullを返す
function textToNeiro (text) {
  const remark = "(?:\\/[^\\x0d\\x0a]*\\x0d?\\x0a?)";  //注釈
  const blank = "(?:\\x20|\\x09|\\x0d|\\x0a|" + remark + ")*";  //空白
  const element = blank + "([0-9]+)" + blank + "\\,?";  //要素
  const head = blank + "\\([Vv]" + element + element;  //命令  "( V"は不可。[1]=音色番号,[2]=開始位置
  const tail = blank + "\\)";
  let a = currentNeiro.concat ();  //現在のパラメータをコピーする
  let rHead = new RegExp (head, "g");
  let rElement = new RegExp (element, "g");  //[3..]=値
  let rTail = new RegExp (tail, "g");
  let mHead = rHead.exec (text);
  if (mHead) {
    //let n = parseInt (mHead[1], 10);  //音色番号
    let i0 = parseInt (mHead[2], 10);  //開始位置
    if (i0 < 0 || 55 <= i0) {  //範囲外
      return null;
    }
    rElement.lastIndex = rHead.lastIndex;
    for (let i = i0; i < 55; i++) {
      let mElement = rElement.exec (text);
      if (mElement) {
        let v = parseInt (mElement[1], 10);  //値
        if (NEIRO_ELEMENT_MASK[i] < v) {  //範囲外
          return null;
        }
        a[i] = v;
      } else {
        return null;
      }
    }
    rTail.lastIndex = rElement.lastIndex;
    let mTail = rTail.exec (text);
    if (mTail) {
      return a;
    }
  }
  return null;
}

function textareaToNeiro () {
  if (!neiroTextareaBusy) {
    neiroTextareaBusy = true;
    let a = textToNeiro (neiroTextarea.value);
    if (a) {
      for (let i = 0; i < 55; i++) {
        setNeiroElement (i, a[i]);
      }
    }
    neiroTextareaBusy = false;
  }
}

//  FM音源ウインドウを作る
function makeOpmWindow () {
  let neiroForm = createNodeForm ();
  //
  appendNode (neiroForm,
              appendNode (createNodeH2 (),
                          appendNode (setAttr (createNodeSpan (),
                                               "className", "en"),
                                      "FM sound source (YM2151)"),
                          appendNode (setAttr (createNodeSpan (),
                                               "className", "ja"),
                                      "FM 音源 (YM2151)")));
  //
  appendNode (neiroForm,
              appendNode (createNodeH3 (),
                          appendNode (setAttr (createNodeSpan (),
                                               "className", "en"),
                                      "Tone parameters"),
                          appendNode (setAttr (createNodeSpan (),
                                               "className", "ja"),
                                      "音色パラメータ")));
  let tbody = createNodeTbody ();
  for (let row = 0; row < 5; row++) {
    let tr = createNodeTr ();
    if (row <= 1) {
      appendNode (tr,
                  createNodeTh ());
      for (let col = 0; col < 11; col++) {
        appendNode (tr,
                    appendNode (createNodeTh (),
                                NEIRO_COL_NAME[row][col]));
      }
      appendNode (tbody,
                  tr);
      tr = createNodeTr ();
    }
    appendNode (tr,
                appendNode (createNodeTh (),
                            NEIRO_ROW_NAME[row]));
    for (let col = 0; col < 11; col++) {
      let index = col + 11 * row;
      let select = neiroSelectArray[index] = setAttr (createNodeSelect (),
                                                      "name", "tone" + index);
      for (let value = 0; value <= NEIRO_ELEMENT_MASK[index]; value++) {
        appendNode (select,
                    appendNode (setAttr (createNodeOption (),
                                         "value", index + ":" + value),
                                "" + value));
      }
      select.selectedIndex = NEIRO_DATA[0][index];
      addEvent (select,
                "change",
                function (e) {
                  let iivv = this.options[this.selectedIndex].value.split (":");
                  let ii = parseInt (iivv[0], 10);
                  let vv = parseInt (iivv[1], 10);
                  setNeiroElement (ii, vv);
                });
      appendNode (tr,
                  appendNode (createNodeTd (),
                              select));
    }
    appendNode (tbody,
                tr);
  }
  appendNode (neiroForm,
              appendNode (createNodeTable (),
                          tbody));
  //
  //  onblur   フォーカスを失った
  //  onfocus  フォーカスを得た
  //  onkeyup  テキストが変化した可能性がある
  //  onpaste  テキストが変化する可能性がある
  neiroTextarea = addEvent (setStyle (setAttr (createNodeTextarea (),
                                               "value", neiroToText (currentNeiro)),
                                      "display", "block",
                                      "height", "5em",
                                      "margin", "1em auto",
                                      "position", "relative",
                                      "width", "30em"),
                            "blur",
                            () => {
                              focusedElement = null;
                            },
                            "focus",
                            () => {
                              focusedElement = neiroTextarea;
                            },
                            "keyup",
                            textareaToNeiro,
                            "paste",
                            () => {
                              setTimeout (textareaToNeiro, 100);
                            });
  appendNode (neiroForm,
              neiroTextarea);
  //
  appendNode (neiroForm,
              appendNode (createNodeH3 (),
                          appendNode (setAttr (createNodeSpan (),
                                               "className", "en"),
                                      "Preset tones"),
                          appendNode (setAttr (createNodeSpan (),
                                               "className", "ja"),
                                      "プリセット音色")));
  for (let k = 0; k < 2; k++) {  //0=68SND.ZMS,1=VIP.ZMS
    let s = k == 0 ? "68SND.ZMS" : "VIP.ZMS";
    let g = k == 0 ? 0 : 68;
    let n = k == 0 ? 68 : 200;
    let ol = createNodeOl ();
    for (let i = 0; i < n; i++) {
      appendNode (ol,
                  appendNode (createNodeLi (),
                              appendNode (createNodeLabel (),
                                          setAttr (addEvent (createNodeInput (),
                                                             "click",
                                                             function (e) {
                                                               setPresetNeiro (parseInt (this.value, 10));
                                                             }),
                                                   "name", "neiro",
                                                   "type", "radio",
                                                   "value", "" + (g + i),
                                                   g + i == 0 ? "checked" : null, "checked"),
                                          appendNode (setAttr (createNodeSpan (),
                                                               "className", "en"),
                                                      NEIRO_DATA[g + i][55]),
                                          appendNode (setAttr (createNodeSpan (),
                                                               "className", "ja"),
                                                      NEIRO_DATA[g + i][56]))));
    }  //for i
    appendNode (neiroForm,
                appendNode (createNodeH4 (),
                            appendNode (setAttr (createNodeSpan (),
                                                 "className", "en"),
                                        "Tones defined in " + s),
                            appendNode (setAttr (createNodeSpan (),
                                                 "className", "ja"),
                                        s + " で定義されている音色")),
                ol);
  }  //for k
  opmWindow = new PopupWindow (10, 5, 80, 90);
  appendNode (opmWindow.pw$innerBox,
              neiroForm);
  appendNode (document.body,
              opmWindow.pw$outerBox);
}



//  divで三角形を作る
//                              /\
//                            /    \
//                          /        \
//                        /            \
//            □□□□□/□□□□□□□□\□□□□□
//            □      /                    \      □
//            □    /                        \    □
//            □  /■■■■■■■■■■■■■■\  □
//            □/  ■                      /■  \□
//            /    ■                    /  ■    \
//          /□    ■                  /    ■    □\
//        /  □    ■                /      ■    □  \
//      /    □    ■              /        ■    □    \
//    /      □    ■            /          ■    □      \
//    \      □    ■          /            ■    □      /
//      \    □    ■        /              ■    □    /
//        \  □    ■      /                ■    □  /
//          \□    ■    /                  ■    □/
//            \    ■  /                    ■    /
//            □\  ■/                      ■  /□
//            □  \■■■■■■■■■■■■■■/  □
//            □    \                        /    □
//            □      \                    /      □
//            □□□□□\□□□□□□□□/□□□□□
//                        \            /
//                          \        /
//                            \    /
//                              \/
//
//  三角形(-70,-70)-(-70,70)-(70,-70)をmatrixで(x1,y1),(x2,y2),(x3,y3)に変換する
//    matrix
//      (x3-x1)/140 (x2-x1)/140 (x2+x3)/2
//      (y3-y1)/140 (y2-y1)/140 (y2+y3)/2
//  cssのleft,top,width,heightは整数
//  cssのtransformは回転の中心が(0,0)ではなくtransform開始前の矩形の中心
//  cssのtransformは書いた順番に変換行列が掛け合わされる。すなわち最後に書いた変換が最初に掛かり最初に書いた変換が最後に掛かる
function makeTriangle (x1, y1, x2, y2, x3, y3, color) {
  return appendNode (setStyle (createNodeDiv (),
                               "height", "140px",
                               "left", "-70px",
                               "overflow", "hidden",
                               "position", "absolute",
                               "top", "-70px",
                               "transform",
                               "matrix(" +
                               (1 / 140 * (x3 - x1)) + "," + (1 / 140 * (y3 - y1)) + "," +
                               (1 / 140 * (x2 - x1)) + "," + (1 / 140 * (y2 - y1)) + "," +
                               (1 / 2 * (x2 + x3)) + "," + (1 / 2 * (y2 + y3)) + ")",
                               "width", "140px"),
                     setStyle (createNodeDiv (),
                               "backgroundColor", color,
                               "height", "198px",
                               "left", "-99px",
                               "position", "absolute",
                               "top", "-99px",
                               "transform", "rotate(45deg)",
                               "width", "198px"));
}



const PCM_FREQ = [3906, 5208, 7812, 10416, 15625, 20833, 31250];

//  ADPCM音源ウインドウを作る
function makePcmWindow () {
  let form = createNodeForm ();
  //
  appendNode (form,
              appendNode (createNodeH2 (),
                          appendNode (setAttr (createNodeSpan (),
                                               "className", "en"),
                                      "ADPCM sound source (MSM6258V)"),
                          appendNode (setAttr (createNodeSpan (),
                                               "className", "ja"),
                                      "ADPCM 音源 (MSM6258V)")));
  //
  appendNode (form,
              appendNode (createNodeH3 (),
                          appendNode (setAttr (createNodeSpan (),
                                               "className", "en"),
                                      "Start or stop playing ADPCM data"),
                          appendNode (setAttr (createNodeSpan (),
                                               "className", "ja"),
                                      "ADPCM データの再生の開始または終了")),
              appendNode (setStyle (createNodeDiv (),
                                    "height", "60px",
                                    "margin", "1em auto",
                                    "position", "relative",
                                    "width", "150px"),
                          addEvent (appendNode (setStyle (createNodeDiv (),
                                                          "cursor", "pointer",
                                                          "height", "60px",
                                                          "left", "0px",
                                                          "top", "0px",
                                                          "position", "absolute",
                                                          "width", "60px"),
                                                setStyle (createNodeDiv (),
                                                          "backgroundColor", "#1e1e49",
                                                          "border", "#8d8ddb solid 4px",
                                                          "borderRadius", "50%",
                                                          "left", "0px",
                                                          "height", "60px",
                                                          "position", "absolute",
                                                          "top", "0px",
                                                          "width", "60px"),
                                                makeTriangle (5 / 3 * 30, 30,
                                                              2 / 3 * 30, (1 - 1 / sqrt (3)) * 30,
                                                              2 / 3 * 30, (1 + 1 / sqrt (3)) * 30,
                                                              "#8d8ddb")),
                                    "click",
                                    function (e) {
                                      opmpcmProcessor.port.postMessage ({
                                      t: audioContext.currentTime,
                                      c: "pcmstart"
                                      });
                                    }),
                          addEvent (appendNode (setStyle (createNodeDiv (),
                                                          "cursor", "pointer",
                                                          "height", "60px",
                                                          "left", "90px",
                                                          "position", "absolute",
                                                          "top", "0px",
                                                          "width", "60px"),
                                                setStyle (createNodeDiv (),
                                                          "backgroundColor", "#1e1e49",
                                                          "border", "#8d8ddb solid 4px",
                                                          "borderRadius", "50%",
                                                          "left", "0px",
                                                          "height", "60px",
                                                          "position", "absolute",
                                                          "top", "0px",
                                                          "width", "60px"),
                                                setStyle (createNodeDiv (),
                                                          "backgroundColor", "#8d8ddb",
                                                          "height", "30px",
                                                          "left", "15px",
                                                          "position", "absolute",
                                                          "top", "15px",
                                                          "width", "30px")),
                                    "click",
                                    function (e) {
                                      opmpcmProcessor.port.postMessage ({
                                      t: audioContext.currentTime,
                                      c: "pcmstop"
                                      });
                                    })));
  //
  appendNode (form,
              appendNode (createNodeH3 (),
                          appendNode (setAttr (createNodeSpan (),
                                               "className", "en"),
                                      "ADPCM data"),
                          appendNode (setAttr (createNodeSpan (),
                                               "className", "ja"),
                                      "ADPCM データ")),
              appendNode (createNodeP (),
                          appendNode (setAttr (createNodeSpan (),
                                               "className", "en"),
                                      "Choose or drop an *.PCM file for X68000."),
                          appendNode (setAttr (createNodeSpan (),
                                               "className", "ja"),
                                      "X68000 用の *.PCM ファイルを選択するか放り込んでください"),
                          addEvent (setStyle (setAttr (createNodeInput (),
                                                       "accept", ".pcm",
                                                       "name", "file",
                                                       "type", "file"),
                                              "backgroundColor", "#1e1e49",
                                              "border", "#8d8ddb solid 4px",
                                              "borderRadius", "8px",
                                              "cursor", "pointer",
                                              "display", "block",
                                              "height", "80px",
                                              "position", "relative",
                                              "width", "400px"),  //ドロップターゲットを大きくする
                                    "change",
                                    //同じファイルを2回続けて選択すると2回目はchangeイベントが発生しない
                                    //changeイベントで再生を開始する方法は好ましくない
                                    function (e) {
                                      let files = e.target.files;
                                      let r = new FileReader ();
                                      r.onloadend = function () {
                                        opmpcmProcessor.port.postMessage ({
                                        t: audioContext.currentTime,
                                        c: "pcmset",
                                        d: new Uint8Array (r.result)
                                        });
                                      };
                                      r.readAsArrayBuffer (files[0]);
                                    })));
  //
  let ol = createNodeOl ();
  for (let i = 0; i < PCM_FREQ.length; i++) {
    appendNode (ol,
                appendNode (createNodeLi (),
                            appendNode (createNodeLabel (),
                                        addEvent (setAttr (createNodeInput (),
                                                           "name", "pcmfreq",
                                                           "type", "radio",
                                                           "value", "" + i,
                                                           i == 4 ? "checked" : null, "checked"),
                                                  "click",
                                                  function (e) {
                                                    opmpcmProcessor.port.postMessage ({
                                                    t: audioContext.currentTime,
                                                    c: "pcmset",
                                                    f: parseInt (this.value, 10)
                                                    });
                                                  }),
                                        PCM_FREQ[i] + "Hz")));
  }
  appendNode (form,
              appendNode (createNodeH3 (),
                          appendNode (setAttr (createNodeSpan (),
                                               "className", "en"),
                                      "ADPCM sampling frequency"),
                          appendNode (setAttr (createNodeSpan (),
                                               "className", "ja"),
                                      "ADPCM サンプリング周波数")),
              ol);
  //
  appendNode (form,
              appendNode (createNodeH3 (),
                          appendNode (setAttr (createNodeSpan (),
                                               "className", "en"),
                                      "ADPCM output"),
                          appendNode (setAttr (createNodeSpan (),
                                               "className", "ja"),
                                      "ADPCM 出力")),
              appendNode (createNodeOl (),
                          appendNode (createNodeLi (),
                                      appendNode (createNodeLabel (),
                                                  addEvent (setAttr (createNodeInput (),
                                                                     "name", "pcmleft",
                                                                     "type", "checkbox",
                                                                     "checked", "checked"),
                                                            "click",
                                                            function (e) {
                                                              opmpcmProcessor.port.postMessage ({
                                                              t: audioContext.currentTime,
                                                              c: "pcmset",
                                                              l: this.checked
                                                              });
                                                            }),
                                                  appendNode (setAttr (createNodeSpan (),
                                                                       "className", "en"),
                                                              "Left"),
                                                  appendNode (setAttr (createNodeSpan (),
                                                                       "className", "ja"),
                                                              "左"))),
                          appendNode (createNodeLi (),
                                      appendNode (createNodeLabel (),
                                                  addEvent (setAttr (createNodeInput (),
                                                                     "name", "pcmright",
                                                                     "type", "checkbox",
                                                                     "checked", "checked"),
                                                            "click",
                                                            function (e) {
                                                              opmpcmProcessor.port.postMessage ({
                                                              t: audioContext.currentTime,
                                                              c: "pcmset",
                                                              r: this.checked
                                                              });
                                                            }),
                                                  appendNode (setAttr (createNodeSpan (),
                                                                       "className", "en"),
                                                              "Right"),
                                                  appendNode (setAttr (createNodeSpan (),
                                                                       "className", "ja"),
                                                              "右")))));
  //
  pcmWindow = new PopupWindow (10, 5, 80, 90);
  appendNode (pcmWindow.pw$innerBox,
              form);
  appendNode (document.body,
              pcmWindow.pw$outerBox);
}


//========================================================================
//エントリ
function windowOnload () {

  setStyle (document.body,
            "backgroundColor", "#000000",
            "color", "#ffffff",
            "margin", "0px",
            "overflow", "hidden",  //スクロールバーを表示しない
            "padding", "0px",
            "position", "relative"
            );

  //説明ウインドウ
  docWindow = new PopupWindow (2, 2, 96, 96);
  appendNode (docWindow.pw$innerBox,
              setStyle (getElementById ("head"),
                        "backgroundColor", "transparent",
                        "backgroundImage", "none"),
              setStyle (getElementById ("body"),
                        "backgroundColor", "transparent",
                        "backgroundImage", "none"),
              setStyle (getElementById ("foot"),
                        "backgroundColor", "transparent",
                        "backgroundImage", "none"));

  globalScreenArray = [];
  for (let screenNumber = 0; screenNumber <= 2; screenNumber++) {
    let screen = globalScreenArray[screenNumber] = {};
    screen.sc$screenNumber = screenNumber;
    screen.sc$visibleArray = [[], [], []];  //表示されている多面体の配列
  }

  resetSize ();

  svgElementArray = [];
  controlPanelArray = [];

  for (let screenNumber = 0; screenNumber <= 2; screenNumber++) {
    let screen = globalScreenArray[screenNumber];
    let svgElement = svgElementArray[screenNumber] =
      setStyle (setDisplay (createRootSvg (screen.sc$width, screen.sc$height,
                                           screen.sc$viewX, screen.sc$viewY, screen.sc$viewWidth, screen.sc$viewHeight),
                            screenNumber == 0 ? "block" : "none"),
                "left", screen.sc$left + "px",
                "position", "absolute",
                "top", screen.sc$top + "px");
←Previous | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | Next→