//========================================================================================
//  PPI.java
//    en:8255 PPI -- It emulates joystick ports.
//    ja:8255 PPI -- ジョイスティックポートをエミュレートします。
//  Copyright (C) 2003-2024 Makoto Kamada
//
//  This file is part of the XEiJ (X68000 Emulator in Java).
//  You can use, modify and redistribute the XEiJ if the conditions are met.
//  Read the XEiJ License for more details.
//  https://stdkmd.net/xeij/
//========================================================================================

package xeij;

import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.util.*;
import javax.swing.*;
import javax.swing.event.*;

import com.fazecast.jSerialComm.*;

public class PPI {

  //ポート
  //  0x00e9a000-0x00e9bfffは下位3ビットだけデコードされる
  //  偶数アドレスをバイトサイズでリード/ライトするとバスエラーになる
  //  偶数アドレスをワードサイズでリードすると上位バイトは0xffになる
  //
  //  0x00e9a001  PPIポートA  ジョイスティック1
  //         7        6        5        4        3        2        1        0
  //    +--------+--------+--------+--------+--------+--------+--------+--------+
  //    |   PA7  |   PA6  |   PA5  |   PA4  |   PA3  |   PA2  |   PA1  |   PA0  |
  //    |    1   |   JS7  |   JS6  |    1   |   JS4  |   JS3  |   JS2  |   JS1  |
  //    |        |    B   |    A   |        |   →   |   ←   |   ↓   |   ↑   |
  //    +--------+--------+--------+--------+--------+--------+--------+--------+
  //    bit7    PA7  1
  //    bit6    PA6  JS7入力。0=Bが押されている
  //    bit5    PA5  JS6入力。0=Aが押されている
  //    bit4    PA4  1
  //    bit3    PA3  JS4入力。0=→が押されている
  //    bit2    PA2  JS3入力。0=←が押されている
  //    bit1    PA1  JS2入力。0=↓が押されている
  //    bit0    PA0  JS1入力。0=↑が押されている
  //
  //  0x00e9a003  PPIポートB  ジョイスティック2
  //         7        6        5        4        3        2        1        0
  //    +--------+--------+--------+--------+--------+--------+--------+--------+
  //    |   PB7  |   PB6  |   PB5  |   PB4  |   PB3  |   PB2  |   PB1  |   PB0  |
  //    |    1   |   JT7  |   JT6  |    1   |   JT4  |   JT3  |   JT2  |   JT1  |
  //    |        |    B   |    A   |        |   →   |   ←   |   ↓   |   ↑   |
  //    +--------+--------+--------+--------+--------+--------+--------+--------+
  //    bit7    PB7  1
  //    bit6    PB6  JT7入力。0=Bが押されている
  //    bit5    PB5  JT6入力。0=Aが押されている
  //    bit4    PB4  1
  //    bit3    PB3  JT4入力。0=→が押されている
  //    bit2    PB2  JT3入力。0=←が押されている
  //    bit1    PB1  JT2入力。0=↓が押されている
  //    bit0    PB0  JT1入力。0=↑が押されている
  //
  //  0x00e9a005  PPIポートC  ADPCMコントロール。初期値は0x0b
  //         7        6        5        4        3        2        1        0
  //    +--------+--------+--------+--------+--------+--------+--------+--------+
  //    |   PC7  |   PC6  |   PC5  |   PC4  |   PC3  |   PC2  |   PC1  |   PC0  |
  //    |   JS7  |   JS6  |   JT8  |   JS8  |      RATIO      |  LEFT  |  RIGHT |
  //    +--------+--------+--------+--------+--------+--------+--------+--------+
  //    bit7    PC7    JS7出力(負論理)
  //    bit6    PC6    JS6出力(負論理)
  //    bit5    PC5    JT8出力
  //    bit4    PC4    JS8出力
  //    bit3-2  PC3-2  ADPCM分周比。00=1/1024,01=1/768,10=1/512,11=inhibited
  //    bit1    PC1    ADPCM出力LEFT。0=出力する,1=出力しない
  //    bit0    PC0    ADPCM出力RIGHT。0=出力する,1=出力しない
  //
  //  0x00e9a007  PPIコントロール
  //    bit7=0  ポートCで出力に設定されているビットの操作
  //      bit3-1  ビット番号
  //      bit0    設定値
  //    bit7=1  モードの設定。0x92に固定
  //      bit6-5  グループA(ポートAとポートCの上位)のモード(0=モード0,1=モード1,2/3=モード2)。モード0に固定
  //      bit4    ポートAの方向(0=出力,1=入力)。入力に固定
  //      bit3    ポートCの上位の方向(0=出力,1=入力)。出力に固定
  //      bit2    グループB(ポートBとポートCの下位)のモード(0=モード0,1=モード1)。モード0に固定
  //      bit1    ポートBの方向(0=出力,1=入力)。入力に固定
  //      bit0    ポートCの下位の方向(0=出力,1=入力)。出力に固定
  //
  //  ボタンのマスク
  //    上と下または左と右のキーが同時に押された場合は両方キャンセルする
  //    SELECTボタンは上下同時押し、RUNボタンは左右同時押しに割り当てられる
  //
  //  bit4とbit7は入力できない
  //    8255の足がプルアップされているので実機ではどうすることもできない(外付けの回路でどうにかなるものではない)
  //    エミュレータで入力できるようにするのは簡単だか対応しているソフトは存在しないだろう
  //
  //  参考
  //    電脳倶楽部67号  B/MDPAD/M6PAD_AN.DOC
  //    電脳倶楽部77号  B/6B/T_EXPAD.DOC
  //
  public static final int PPI_PORT_A  = 0x00e9a001;   //PPIポートA
  public static final int PPI_PORT_B  = 0x00e9a003;   //PPIポートB
  public static final int PPI_PORT_C  = 0x00e9a005;   //PPIポートC
  public static final int PPI_CONTROL = 0x00e9a007;   //PPIコントロール

  //ジョイスティック
  public static Joystick[] ppiJoysticks;
  public static Joystick ppiJoystick1;
  public static Joystick ppiJoystick2;

  //モード
  public static boolean ppiJoyKey;  //true=キーボードの一部をジョイスティックとみなす
  public static boolean ppiJoyAuto;  //true=ポートが繰り返し読み出されている間だけ有効
  public static boolean ppiJoyBlock;  //true=ジョイスティック操作として処理されたキー入力データを取り除く

  //じょいぽーとU君
  public static final boolean PPI_UKUN_ON = true;  //じょいぽーとU君。false=使わない,true=使う
  public static final boolean PPI_UKUN_DEBUG = false;  //デバッグメッセージ。false=出力しない,true=出力する
  //  接続
  public static final int PPI_UKUN_VID = 0x04d8;  //ベンダーID
  public static final int PPI_UKUN_PID = 0xe6b3;  //プロダクトID
  public static boolean ppiUkunRequestedConnection;  //要求された接続。false=接続しない,true=接続する
  public static boolean ppiUkunCurrentConnection;  //現在の接続。false=接続していない,true=接続している
  //  シリアルポート
  public static OldSerialPort ppiUkunOldSerialPort;  //(!jSerialComm)シリアルポート
  public static String ppiUkunPortName;  //ポート名。"COM4"など
  public static OldSerialPort.SerialInputStream ppiUkunOldInputStream;  //(!jSerialComm)入力ストリーム
  public static OldSerialPort.SerialOutputStream ppiUkunOldOutputStream;  //(!jSerialComm)出力ストリーム
  //  モード
  public static final int PPI_UKUN_NOTIFY_A   = 0b0000_0000_0011_0001;  //Notify(A)モード
  public static final int PPI_UKUN_NOTIFY_B   = 0b0000_0001_0011_0001;  //Notify(B)モード
  public static final int PPI_UKUN_COMMAND    = 0b0000_0000_0011_0010;  //Commandモード
  public static final int PPI_UKUN_TRANSITION = 0b0000_0000_0000_0000;  //モード遷移中。I/Oポートのリードは前回の値が返り、ライトは無視される
  public static volatile int ppiUkunRequestedMode;  //要求されたモード。PPI_UKUN_NOTIFY_AまたはPPI_UKUN_COMMANDまたはPPI_UKUN_NOTIFY_B
  public static volatile int ppiUkunCurrentMode;  //現在のモード。PPI_UKUN_NOTIFY_AまたはPPI_UKUN_COMMANDまたはPPI_UKUN_NOTIFY_BまたはPPI_UKUN_TRANSITION
  //  データ
  public static volatile int ppiUkunPortA;  //ポートAのデータ。0b1..1_....
  public static volatile int ppiUkunPortB;  //ポートBのデータ。0b1..1_....
  public static volatile int ppiUkunPortC;  //ポートCのデータ。0b...._0000
  //  受信スレッド
  public static volatile boolean ppiUkunSending;  //false=送信中ではない,true=送信中
  public static volatile Thread ppiUkunThread;  //(!jSerialComm)受信スレッド
  //  ナノタイマー
  public static final boolean PPI_UKUN_NANO = false;  //I/Oポートのアクセス時間を計測する
  public static boolean ppiUkunNanoMeasuring;  //true=計測中
  public static long[] ppiUkunNanoBestArray;  //最短アクセス時間(ns)
  public static long[] ppiUkunNanoWorstArray;  //最長アクセス時間(ns)
  public static long[] ppiUkunNanoTotalArray;  //合計アクセス時間(ns)
  public static long[] ppiUkunNanoCountArray;  //アクセス回数
  //  ウェイト
  public static final long PPI_UKUN_WAIT = XEiJ.TMR_FREQ / (1000000 / 100);  //I/Oポートのアクセスに追加するウェイト(XEiJ.TMR_FREQ単位)。100us
  //  出力間隔調整
  public static final boolean PPI_UKUN_INTERVAL = true;
  public static final long PPI_UKUN_INTERVAL_LIMIT = XEiJ.TMR_FREQ / (1000000L / 500L);  //調整する間隔のずれの上限。500us
  public static boolean ppiUkunIntervalOn;  //true=出力間隔調整を行う
  public static long ppiUkunLastVirtualTime;  //前回のXEiJ.mpuClockTime
  public static long ppiUkunLastRealTime;  //前回のSystem.nanoTime()
  //  メニュー
  public static JCheckBox ppiUkunConnectCheckbox;  //接続チェックボックス
  public static JRadioButton ppiUkunNotifyAModeRadioButton;  //Notify(A)モードラジオボタン
  public static JRadioButton ppiUkunNotifyBModeRadioButton;  //Notify(B)モードラジオボタン
  public static JRadioButton ppiUkunCommandModeRadioButton;  //Commandモードラジオボタン
  //  jSerialComm
  public static final boolean PPI_UKUN_JSERIALCOMM = true;  //false=xeijwin.dllのみ,true=jSerialCommを併用
  public static final String PPI_UKUN_DESCRIPTION = "8255 emulator";  //プロダクト概要
  public static boolean ppiUkunJSerialCommOn;  //じょいぽーとU君でjSerialCommを使う。Windows以外は常にtrue
  public static SerialPort ppiUkunSerialPort;  //シリアルポート
  public static UkunDataListener ppiUkunDataListener;  //シリアルポートデータリスナー
  public static JCheckBox ppiUkunJSerialCommCheckbox;  //jSerialCommチェックボックス。接続していないときだけ有効


  //ポートの値
  public static int ppiPortCData;

  //自動切り替え
  //  PPIのポートが参照されてから一定時間だけJOYKEYを有効にする
  public static final long PPI_CONTINUOUS_ACCESS_SPAN = XEiJ.TMR_FREQ / 10;  //自動切り替えの有効期間(TMR_FREQ単位)。1/10秒
  public static long ppiLastAccessTime;  //最後にPPIのポートがアクセスされた時刻

  //XInput
  public static final boolean PPI_XINPUT_ON = true;  //false=使わない,true=使う
  public static boolean ppiXInputOn;  //false=使わない,true=使う
  public static XInput ppiXInput;  //XInput。nullでなければ動作中
  public static int ppiXInputLastButtons;

  //ウインドウ
  public static JFrame ppiFrame;

  public static JScrollPane ppiConfigurationScrollPane;

  //ppiInit ()
  //  PPIを初期化する
  public static void ppiInit () {
    ppiJoyKey = Settings.sgsGetOnOff ("joykey");
    ppiJoyAuto = Settings.sgsGetOnOff ("joyauto");
    ppiJoyBlock = Settings.sgsGetOnOff ("joyblock");

    ppiJoysticks = new Joystick[] {
      new DummyPad (),
      new Normal2ButtonPad (1),
      new Normal2ButtonPad (2),
      new MegaDrive3ButtonPad (1),
      new MegaDrive3ButtonPad (2),
      new MegaDrive6ButtonPad (1),
      new MegaDrive6ButtonPad (2),
      new CyberStickAnalog (1),
      new CyberStickAnalog (2),
      new CyberStickDigital (1),
      new CyberStickDigital (2),
      new Shiromadokun (1),
      new Shiromadokun (2),
    };

    String id1 = Settings.sgsGetString ("joystick1");  //ジョイスティックポート1に接続するデバイス
    ppiJoystick1 = ppiJoysticks[0];  //DummyPad
    for (Joystick joystick : ppiJoysticks) {
      if (joystick.getId ().equalsIgnoreCase (id1)) {
        ppiJoystick1 = joystick;
      }
    }

    String id2 = Settings.sgsGetString ("joystick2");  //ジョイスティックポート2に接続するデバイス
    ppiJoystick2 = ppiJoysticks[0];  //DummyPad
    for (Joystick joystick : ppiJoysticks) {
      if (joystick.getId ().equalsIgnoreCase (id2)) {
        ppiJoystick2 = joystick;
      }
    }

    if (PPI_UKUN_ON) {
      ppiUkunRequestedConnection = (PPI_UKUN_JSERIALCOMM || XEiJ.prgWindllLoaded) && Settings.sgsGetOnOff ("joyportukun");
      ppiUkunCurrentConnection = false;
      ppiUkunPortName = null;
      ppiUkunOldSerialPort = null;
      ppiUkunOldInputStream = null;
      ppiUkunOldOutputStream = null;
      String paramUkunmode = Settings.sgsGetString ("ukunmode");
      ppiUkunRequestedMode = (paramUkunmode.equalsIgnoreCase ("notifya") ? PPI_UKUN_NOTIFY_A :
                              paramUkunmode.equalsIgnoreCase ("notifyb") ? PPI_UKUN_NOTIFY_B :
                              paramUkunmode.equalsIgnoreCase ("command") ? PPI_UKUN_COMMAND :
                              PPI_UKUN_NOTIFY_A);
      ppiUkunCurrentMode = ppiUkunRequestedMode;
      if (PPI_UKUN_DEBUG) {
        System.out.printf ("UkunCurrentMode 0x%04x\n", ppiUkunCurrentMode);
      }
      ppiUkunPortA = 0b1111_1111;  //0b1..1_....
      ppiUkunPortB = 0b1111_1111;  //0b1..1_....
      ppiUkunPortC = 0b0000_0000;  //0b...._0000
      ppiUkunSending = false;
      ppiUkunThread = null;
      if (PPI_UKUN_NANO) {
        ppiUkunNanoMeasuring = false;
        ppiUkunNanoBestArray = new long[8];
        ppiUkunNanoWorstArray = new long[8];
        ppiUkunNanoTotalArray = new long[8];
        ppiUkunNanoCountArray = new long[8];
      }
      if (PPI_UKUN_INTERVAL) {
        ppiUkunIntervalOn = Settings.sgsGetOnOff ("ukuninterval");
        ppiUkunLastVirtualTime = XEiJ.mpuClockTime;
        ppiUkunLastRealTime = System.nanoTime ();
      }
      if (PPI_UKUN_JSERIALCOMM) {
        ppiUkunJSerialCommOn = !XEiJ.prgIsWindows || Settings.sgsGetOnOff ("ukunjsc");  //Windows以外は常にtrue
        ppiUkunSerialPort = null;
        ppiUkunDataListener = null;
      }  //if PPI_UKUN_JSERIALCOMM
    }  //if PPI_UKUN_ON

    if (PPI_XINPUT_ON) {
      ppiXInputOn = XEiJ.prgWindllLoaded && Settings.sgsGetOnOff ("xinput");
      if (ppiXInputOn) {
        ppiXInputStart ();
      }
      ppiXInputLastButtons = 0;
    }

    ppiReset ();
  }  //ppiInit

  //ppiTini ()
  //  後始末
  public static void ppiTini () {
    Settings.sgsPutOnOff ("joykey", ppiJoyKey);
    Settings.sgsPutOnOff ("joyauto", ppiJoyAuto);
    Settings.sgsPutOnOff ("joyblock", ppiJoyBlock);
    for (Joystick joystick : ppiJoysticks) {
      joystick.tini ();
    }
    Settings.sgsPutString ("joystick1", ppiJoystick1.getId ());
    Settings.sgsPutString ("joystick2", ppiJoystick2.getId ());
    if (PPI_UKUN_ON) {
      Settings.sgsPutOnOff ("joyportukun", ppiUkunRequestedConnection);
      Settings.sgsPutString ("ukunmode",
                             ppiUkunRequestedMode == PPI_UKUN_NOTIFY_A ? "notifya" :
                             ppiUkunRequestedMode == PPI_UKUN_NOTIFY_B ? "notifyb" :
                             ppiUkunRequestedMode == PPI_UKUN_COMMAND ? "command" :
                             "notifya");
      if (PPI_UKUN_INTERVAL) {
        Settings.sgsPutOnOff ("ukuninterval", ppiUkunIntervalOn);
      }
      if (PPI_UKUN_JSERIALCOMM) {
        Settings.sgsPutOnOff ("ukunjsc", !XEiJ.prgIsWindows ? false : ppiUkunJSerialCommOn);  //Windows以外はデフォルトのfalseを保存する。次回再び強制的にtrueになる
      }
      ppiUkunDisconnect ();
    }
    //XInput
    if (PPI_XINPUT_ON) {
      Settings.sgsPutOnOff ("xinput", ppiXInputOn);
      if (ppiXInputOn) {
        ppiXInputOn = false;
        ppiXInputEnd ();
      }
    }
  }  //ppiTini

  //ppiReset ()
  //  リセット
  public static void ppiReset () {
    ppiPortCData = 0;
    ppiLastAccessTime = 0L;
  }


  public static void ppiXInputStart () {
    if (ppiXInput == null) {
      System.out.println (Multilingual.mlnJapanese ?
                          "XInput のポーリングを開始します" :
                          "Starts polling XInput");
      ppiXInput = new XInput ();
    }
  }

  public static void ppiXInputEnd () {
    if (ppiXInput != null) {
      System.out.println (Multilingual.mlnJapanese ?
                          "XInput のポーリングを終了します" :
                          "Ends polling XInput");
      ppiXInput.end ();
      ppiXInput = null;
    }
  }


  //ppiUkunConnect ()
  //  接続する
  //  MPU動作中はI/Oポートのアクセスと衝突させないためコアのスレッドで呼び出すこと
  //  接続はここで完結するので遷移中の状態は考慮しない
  public static void ppiUkunConnect () {
    if (PPI_UKUN_ON && (PPI_UKUN_JSERIALCOMM || XEiJ.prgWindllLoaded)) {
      if (PPI_UKUN_DEBUG) {
        System.out.println ("ppiUkunConnect start");
      }
      ppiUkunRequestedConnection = true;
      if (ppiUkunCurrentConnection) {  //接続している
        ppiUkunAdjustMenu ();
        return;
      }
      if (PPI_UKUN_JSERIALCOMM && ppiUkunJSerialCommOn) {  //jSerialComm
        //ジョイポートU君を探して開く
        if (ppiUkunSerialPort == null) {
          for (SerialPort serialPort : SerialPort.getCommPorts ()) {
            if (serialPort.getPortDescription ().startsWith (PPI_UKUN_DESCRIPTION) &&  //見つかった
                serialPort.openPort ()) {  //開けた
              ppiUkunSerialPort = serialPort;
              break;
            }
          }
        }
        if (ppiUkunSerialPort == null) {  //開けなかった
          System.out.printf (Multilingual.mlnJapanese ?
                             "じょいぽーと U 君 (%s) のシリアルポートが見つかりません\n" :
                             "Serial port for JoyPortUkun (%s) not found\n",
                             PPI_UKUN_DESCRIPTION);
          ppiUkunRequestedConnection = false;
          //ppiUkunCurrentConnection = false;
          ppiUkunAdjustMenu ();
          return;
        }
        ppiUkunPortName = ppiUkunSerialPort.getSystemPortName ();
        ppiUkunSerialPort.setComPortParameters (480000000, 8, SerialPort.ONE_STOP_BIT, SerialPort.NO_PARITY);
        ppiUkunSerialPort.setComPortTimeouts (SerialPort.TIMEOUT_WRITE_BLOCKING | SerialPort.TIMEOUT_READ_BLOCKING, 0, 0);
      } else {  //!jSerialComm
        //ポートを開く
        if (ppiUkunOldSerialPort == null) {
          try {
            ppiUkunOldSerialPort = new OldSerialPort (PPI_UKUN_VID, PPI_UKUN_PID);
          } catch (IOException ioe) {
            System.out.printf (Multilingual.mlnJapanese ?
                               "じょいぽーと U 君 (VID=%04X, PID=%04X) の COM ポートが見つかりません\n" :
                               "COM port for JoyPortUkun (VID=%04X, PID=%04X) not found\n",
                               PPI_UKUN_VID, PPI_UKUN_PID);
            ppiUkunRequestedConnection = false;
            //ppiUkunCurrentConnection = false;
            ppiUkunAdjustMenu ();
            return;
          }
        }
        try {
          ppiUkunPortName = ppiUkunOldSerialPort.getPortName ();
          ppiUkunOldSerialPort.speed ("480000000 b8 pn s1 none");
          ppiUkunOldInputStream = ppiUkunOldSerialPort.getInputStream ();
          ppiUkunOldOutputStream = ppiUkunOldSerialPort.getOutputStream ();
        } catch (IOException ioe) {
          ioe.printStackTrace ();
        }
      }  //if jSerialComm/!
      //接続完了
      ppiUkunCurrentMode = PPI_UKUN_TRANSITION;
      if (PPI_UKUN_DEBUG) {
        System.out.printf ("UkunCurrentMode 0x%04x\n", ppiUkunCurrentMode);
      }
      ppiUkunCurrentConnection = true;  //接続している
      ppiUkunAdjustMenu ();
      if (PPI_UKUN_JSERIALCOMM && ppiUkunJSerialCommOn) {  //jSerialComm
        System.out.printf (Multilingual.mlnJapanese ?
                           "じょいぽーと U 君 (%s, %s) に接続しました\n" :
                           "Connected to JoyPortUkun (%s, %s)\n",
                           ppiUkunPortName, ppiUkunSerialPort.getPortDescription ());
        //シリアルポートデータリスナーを登録する
        if (ppiUkunDataListener == null) {
          ppiUkunSending = false;
          ppiUkunDataListener = new UkunDataListener ();
          ppiUkunSerialPort.addDataListener (ppiUkunDataListener);
        }
        //要求されたモードを送信する
        ppiUkunSerialPort.writeBytes (new byte[] { (byte) ppiUkunRequestedMode }, 1);
      } else {  //!jSerialComm
        System.out.printf (Multilingual.mlnJapanese ?
                           "じょいぽーと U 君 (%s, VID=%04X, PID=%04X) に接続しました\n" :
                           "Connected to JoyPortUkun (%s, VID=%04X, PID=%04X)\n",
                           ppiUkunPortName, PPI_UKUN_VID, PPI_UKUN_PID);
        //受信スレッドを開始する
        if (ppiUkunThread == null) {
          ppiUkunSending = false;
          ppiUkunThread = new UkunThread ();
          ppiUkunThread.start ();
        }
        //要求されたモードを送信する
        try {
          ppiUkunOldOutputStream.write (ppiUkunRequestedMode);
        } catch (IOException ioe) {
          ioe.printStackTrace ();
        }
      }  //if jSerialComm/!
      if (PPI_UKUN_DEBUG) {
        System.out.println ("ppiUkunConnect end");
      }
    }
  }  //ppiUkunConnect

  //ppiUkunDisconnect ()
  //  切断する
  //  MPU動作中はI/Oポートのアクセスと衝突させないためコアのスレッドで呼び出すこと
  //  切断はここで完結するので遷移中の状態は考慮しない
  public static void ppiUkunDisconnect () {
    if (PPI_UKUN_ON) {
      if (PPI_UKUN_DEBUG) {
        System.out.println ("ppiUkunDisconnect start");
      }
      ppiUkunRequestedConnection = false;
      if (!ppiUkunCurrentConnection) {  //接続していない
        ppiUkunAdjustMenu ();
        return;
      }
      //切断開始
      ppiUkunCurrentMode = PPI_UKUN_TRANSITION;
      if (PPI_UKUN_DEBUG) {
        System.out.printf ("UkunCurrentMode 0x%04x\n", ppiUkunCurrentMode);
      }
      ppiUkunCurrentConnection = false;  //接続していない
      ppiUkunAdjustMenu ();
      if (PPI_UKUN_JSERIALCOMM && ppiUkunJSerialCommOn) {  //jSerialComm
        if (ppiUkunSerialPort != null) {
          //シリアルポートデータリスナーを解除する
          ppiUkunSerialPort.removeDataListener ();
          ppiUkunDataListener = null;
          //ポートを閉じる
          ppiUkunSerialPort.closePort ();
          ppiUkunSerialPort = null;
        }
      } else {  //!jSerialComm
        //ポートを閉じる
        if (ppiUkunOldSerialPort != null) {
          try {
            ppiUkunOldSerialPort.close ();
          } catch (IOException ioe) {
            ioe.printStackTrace ();
          }
          ppiUkunOldSerialPort = null;
          ppiUkunOldInputStream = null;
          ppiUkunOldOutputStream = null;
        }
        //受信スレッドが終了するまで待つ
        while (ppiUkunThread != null) {
        }
      }  //if jSerialComm/!
      ppiUkunSending = false;
      //切断完了
      ppiUkunCurrentMode = ppiUkunRequestedMode;
      if (PPI_UKUN_DEBUG) {
        System.out.printf ("UkunCurrentMode 0x%04x\n", ppiUkunCurrentMode);
      }
      ppiUkunAdjustMenu ();
      System.out.printf (Multilingual.mlnJapanese ?
                         "じょいぽーと U 君 (%s) を切り離しました\n" :
                         "Disconnected JoyPortUkun (%s)\n",
                         ppiUkunPortName);
      ppiUkunPortName = null;
      if (PPI_UKUN_DEBUG) {
        System.out.println ("ppiUkunDisconnect end");
      }
    }
  }  //ppiUkunDisconnect

  //ppiUkunNotifyAMode ()
  //  Notify(A)モード
  public static void ppiUkunNotifyAMode () {
    if (PPI_UKUN_ON) {
      if (PPI_UKUN_DEBUG) {
        System.out.println ("ppiUkunNotifyAMode start");
      }
      if (!ppiUkunRequestedConnection &&
          !ppiUkunCurrentConnection) {  //接続していない
        //Notify(A)モード
        ppiUkunRequestedMode = PPI_UKUN_NOTIFY_A;
        ppiUkunCurrentMode = PPI_UKUN_NOTIFY_A;
        if (PPI_UKUN_DEBUG) {
          System.out.printf ("UkunCurrentMode 0x%04x\n", ppiUkunCurrentMode);
        }
        ppiUkunAdjustMenu ();
      } else if (ppiUkunRequestedConnection &&
                 ppiUkunCurrentConnection &&  //接続している
                 ((ppiUkunRequestedMode == PPI_UKUN_COMMAND &&
                   ppiUkunCurrentMode == PPI_UKUN_COMMAND) ||  //Commandモード
                  (ppiUkunRequestedMode == PPI_UKUN_NOTIFY_B &&
                   ppiUkunCurrentMode == PPI_UKUN_NOTIFY_B))) {  //Notify(B)モード
        //遷移中
        ppiUkunRequestedMode = PPI_UKUN_NOTIFY_A;
        ppiUkunCurrentMode = PPI_UKUN_TRANSITION;
        if (PPI_UKUN_DEBUG) {
          System.out.printf ("UkunCurrentMode 0x%04x\n", ppiUkunCurrentMode);
        }
        ppiUkunAdjustMenu ();
        if (PPI_UKUN_JSERIALCOMM && ppiUkunJSerialCommOn) {  //jSerialComm
          //シリアルポートデータリスナーを登録する
          if (ppiUkunDataListener == null) {
            ppiUkunSending = false;
            ppiUkunDataListener = new UkunDataListener ();
            ppiUkunSerialPort.addDataListener (ppiUkunDataListener);
          }
          //Notify(A)モードを送信する
          ppiUkunSerialPort.writeBytes (new byte[] { (byte) PPI_UKUN_NOTIFY_A }, 1);
        } else {  //!jSerialComm
          //受信スレッドを開始する
          if (ppiUkunThread == null) {
            ppiUkunSending = false;
            ppiUkunThread = new UkunThread ();
            ppiUkunThread.start ();
          }
          //Notify(A)モードを送信する
          try {
            ppiUkunOldOutputStream.write (PPI_UKUN_NOTIFY_A & 0xff);
          } catch (IOException ioe) {
            ioe.printStackTrace ();
          }
        }  //if jSerialComm/!
      }
      if (PPI_UKUN_DEBUG) {
        System.out.println ("ppiUkunNotifyAMode end");
      }
    }
  }  //ppiUkunNotifyAMode

  //ppiUkunNotifyBMode ()
  //  Notify(B)モード
  public static void ppiUkunNotifyBMode () {
    if (PPI_UKUN_ON) {
      if (PPI_UKUN_DEBUG) {
        System.out.println ("ppiUkunNotifyBMode start");
      }
      if (!ppiUkunRequestedConnection &&
          !ppiUkunCurrentConnection) {  //接続していない
        //Notify(B)モード
        ppiUkunRequestedMode = PPI_UKUN_NOTIFY_B;
        ppiUkunCurrentMode = PPI_UKUN_NOTIFY_B;
        if (PPI_UKUN_DEBUG) {
          System.out.printf ("UkunCurrentMode 0x%04x\n", ppiUkunCurrentMode);
        }
        ppiUkunAdjustMenu ();
      } else if (ppiUkunRequestedConnection &&
                 ppiUkunCurrentConnection &&  //接続している
                 ((ppiUkunRequestedMode == PPI_UKUN_NOTIFY_A &&
                   ppiUkunCurrentMode == PPI_UKUN_NOTIFY_A) ||  //Notify(A)モード
                  (ppiUkunRequestedMode == PPI_UKUN_COMMAND &&
                   ppiUkunCurrentMode == PPI_UKUN_COMMAND))) {  //Commandモード
        //遷移中
        ppiUkunRequestedMode = PPI_UKUN_NOTIFY_B;
        ppiUkunCurrentMode = PPI_UKUN_TRANSITION;
        if (PPI_UKUN_DEBUG) {
          System.out.printf ("UkunCurrentMode 0x%04x\n", ppiUkunCurrentMode);
        }
        ppiUkunAdjustMenu ();
        if (PPI_UKUN_JSERIALCOMM && ppiUkunJSerialCommOn) {  //jSerialComm
          //シリアルポートデータリスナーを登録する
          if (ppiUkunDataListener == null) {
            ppiUkunSending = false;
            ppiUkunDataListener = new UkunDataListener ();
            ppiUkunSerialPort.addDataListener (ppiUkunDataListener);
          }
          //Notify(B)モードを送信する
          ppiUkunSerialPort.writeBytes (new byte[] { (byte) PPI_UKUN_NOTIFY_B }, 1);
        } else {  //!jSerialComm
          //受信スレッドを開始する
          if (ppiUkunThread == null) {
            ppiUkunSending = false;
            ppiUkunThread = new UkunThread ();
            ppiUkunThread.start ();
          }
          //Notify(B)モードを送信する
          try {
            ppiUkunOldOutputStream.write (PPI_UKUN_NOTIFY_B & 0xff);
          } catch (IOException ioe) {
            ioe.printStackTrace ();
          }
        }  //if jSerialComm/!
      }
      if (PPI_UKUN_DEBUG) {
        System.out.println ("ppiUkunNotifyBMode end");
      }
    }
  }  //ppiUkunNotifyBMode

  //ppiUkunCommandMode ()
  //  Commandモード
  public static void ppiUkunCommandMode () {
    if (PPI_UKUN_ON) {
      if (PPI_UKUN_DEBUG) {
        System.out.println ("ppiUkunCommandMode start");
      }
      if (!ppiUkunRequestedConnection &&
          !ppiUkunCurrentConnection) {  //接続していない
        //Commandモード
        ppiUkunRequestedMode = PPI_UKUN_COMMAND;
        ppiUkunCurrentMode = PPI_UKUN_COMMAND;
        if (PPI_UKUN_DEBUG) {
          System.out.printf ("UkunCurrentMode 0x%04x\n", ppiUkunCurrentMode);
        }
        ppiUkunAdjustMenu ();
      } else if (ppiUkunRequestedConnection &&
                 ppiUkunCurrentConnection &&  //接続している
                 ((ppiUkunRequestedMode == PPI_UKUN_NOTIFY_A &&
                   ppiUkunCurrentMode == PPI_UKUN_NOTIFY_A) ||  //Notify(A)モード
                  (ppiUkunRequestedMode == PPI_UKUN_NOTIFY_B &&
                   ppiUkunCurrentMode == PPI_UKUN_NOTIFY_B))) {  //Notify(B)モード
        //遷移中
        ppiUkunRequestedMode = PPI_UKUN_COMMAND;
        ppiUkunCurrentMode = PPI_UKUN_TRANSITION;
        if (PPI_UKUN_DEBUG) {
          System.out.printf ("UkunCurrentMode 0x%04x\n", ppiUkunCurrentMode);
        }
        ppiUkunAdjustMenu ();
        if (PPI_UKUN_JSERIALCOMM && ppiUkunJSerialCommOn) {  //jSerialComm
          //シリアルポートデータリスナーを登録する
          if (ppiUkunDataListener == null) {
            ppiUkunSending = false;
            ppiUkunDataListener = new UkunDataListener ();
            ppiUkunSerialPort.addDataListener (ppiUkunDataListener);
          }
          //Commandモードを送信する
          ppiUkunSerialPort.writeBytes (new byte[] { (byte) PPI_UKUN_COMMAND }, 1);
        } else {  //!jSerialComm
          //受信スレッドを開始する
          if (ppiUkunThread == null) {
            ppiUkunSending = false;
            ppiUkunThread = new UkunThread ();
            ppiUkunThread.start ();
          }
          //Commandモードを送信する
          try {
            ppiUkunOldOutputStream.write (PPI_UKUN_COMMAND);
          } catch (IOException ioe) {
            ioe.printStackTrace ();
          }
        }  //if jSerialComm/!
      }
      if (PPI_UKUN_DEBUG) {
        System.out.println ("ppiUkunCommandMode end");
      }
    }
  }  //ppiUkunCommandMode

  //ppiUkunMenuConnect ()
  //  メニューからの接続
  public static void ppiUkunMenuConnect () {
    if (PPI_UKUN_ON) {
      XEiJ.tmrTimer.schedule (new TimerTask () {
        @Override public void run () {
          ppiUkunConnect ();
        }
      }, 0L);
    }
  }  //ppiUkunMenuConnect

  //ppiUkunMenuDisconnect ()
  //  メニューからの切断
  public static void ppiUkunMenuDisconnect () {
    if (PPI_UKUN_ON) {
      XEiJ.tmrTimer.schedule (new TimerTask () {
        @Override public void run () {
          ppiUkunDisconnect ();
        }
      }, 0L);
    }
  }  //ppiUkunMenuDisconnect

  //ppiUkunMenuNotifyAMode ()
  //  メニューからのNotify(A)モード
  public static void ppiUkunMenuNotifyAMode () {
    if (PPI_UKUN_ON) {
      XEiJ.tmrTimer.schedule (new TimerTask () {
        @Override public void run () {
          ppiUkunNotifyAMode ();
        }
      }, 0L);
    }
  }  //ppiUkunMenuNotifyAMode

  //ppiUkunMenuNotifyBMode ()
  //  メニューからのNotify(B)モード
  public static void ppiUkunMenuNotifyBMode () {
    if (PPI_UKUN_ON) {
      XEiJ.tmrTimer.schedule (new TimerTask () {
        @Override public void run () {
          ppiUkunNotifyBMode ();
        }
      }, 0L);
    }
  }  //ppiUkunMenuNotifyBMode

  //ppiUkunMenuCommandMode ()
  //  メニューからのCommandモード
  public static void ppiUkunMenuCommandMode () {
    if (PPI_UKUN_ON) {
      XEiJ.tmrTimer.schedule (new TimerTask () {
        @Override public void run () {
          ppiUkunCommandMode ();
        }
      }, 0L);
    }
  }  //ppiUkunMenuCommandMode

  //ppiUkunAdjustMenu ()
  //  メニューを調整する
  public static void ppiUkunAdjustMenu () {
    if (PPI_UKUN_ON) {
      if (ppiUkunConnectCheckbox != null) {
        //接続チェックボックスと要求された接続を合わせる
        if (ppiUkunConnectCheckbox.isSelected () != ppiUkunRequestedConnection) {
          ppiUkunConnectCheckbox.setSelected (ppiUkunRequestedConnection);
        }
        //  遷移中は操作禁止
        if (ppiUkunConnectCheckbox.isEnabled () != (ppiUkunRequestedConnection == ppiUkunCurrentConnection)) {
          ppiUkunConnectCheckbox.setEnabled (ppiUkunRequestedConnection == ppiUkunCurrentConnection);
        }
      }
      if (ppiUkunNotifyAModeRadioButton != null) {
        //Notify(A)モードラジオボタンと要求されたモードを合わせる
        if (ppiUkunNotifyAModeRadioButton.isSelected () != (ppiUkunRequestedMode == PPI_UKUN_NOTIFY_A)) {
          ppiUkunNotifyAModeRadioButton.setSelected (ppiUkunRequestedMode == PPI_UKUN_NOTIFY_A);
        }
        //  接続中かつ遷移中は操作禁止
        if (ppiUkunNotifyAModeRadioButton.isEnabled () != !(ppiUkunCurrentConnection && ppiUkunRequestedMode != ppiUkunCurrentMode)) {
          ppiUkunNotifyAModeRadioButton.setEnabled (!(ppiUkunCurrentConnection && ppiUkunRequestedMode != ppiUkunCurrentMode));
        }
      }
      if (ppiUkunNotifyBModeRadioButton != null) {
        //Notify(B)モードラジオボタンと要求されたモードを合わせる
        if (ppiUkunNotifyBModeRadioButton.isSelected () != (ppiUkunRequestedMode == PPI_UKUN_NOTIFY_B)) {
          ppiUkunNotifyBModeRadioButton.setSelected (ppiUkunRequestedMode == PPI_UKUN_NOTIFY_B);
        }
        //  接続中かつ遷移中は操作禁止
        if (ppiUkunNotifyBModeRadioButton.isEnabled () != !(ppiUkunCurrentConnection && ppiUkunRequestedMode != ppiUkunCurrentMode)) {
          ppiUkunNotifyBModeRadioButton.setEnabled (!(ppiUkunCurrentConnection && ppiUkunRequestedMode != ppiUkunCurrentMode));
        }
      }
      if (ppiUkunCommandModeRadioButton != null) {
        //Commandモードラジオボタンと要求されたモードを合わせる
        if (ppiUkunCommandModeRadioButton.isSelected () != (ppiUkunRequestedMode == PPI_UKUN_COMMAND)) {
          ppiUkunCommandModeRadioButton.setSelected (ppiUkunRequestedMode == PPI_UKUN_COMMAND);
        }
        //  接続中かつ遷移中は操作禁止
        if (ppiUkunCommandModeRadioButton.isEnabled () != !(ppiUkunCurrentConnection && ppiUkunRequestedMode != ppiUkunCurrentMode)) {
          ppiUkunCommandModeRadioButton.setEnabled (!(ppiUkunCurrentConnection && ppiUkunRequestedMode != ppiUkunCurrentMode));
        }
      }
      if (PPI_UKUN_JSERIALCOMM) {
        if (ppiUkunJSerialCommCheckbox != null) {
          //jSerialCommチェックボックスと現在の状態を合わせる
          if (ppiUkunJSerialCommCheckbox.isSelected () != ppiUkunJSerialCommOn) {
            ppiUkunJSerialCommCheckbox.setSelected (ppiUkunJSerialCommOn);
          }
          //  Windows以外と接続中は操作禁止
          if (ppiUkunJSerialCommCheckbox.isEnabled () != (XEiJ.prgIsWindows && !ppiUkunCurrentConnection)) {
            ppiUkunJSerialCommCheckbox.setEnabled (XEiJ.prgIsWindows && !ppiUkunCurrentConnection);
          }
        }
      }
    }
  }  //ppiUkunAdjustMenu

  //class UkunDataListener
  //  シリアルポートデータリスナー
  public static class UkunDataListener implements SerialPortDataListener {
    @Override public int getListeningEvents () {
      return SerialPort.LISTENING_EVENT_DATA_AVAILABLE;
    }
    @Override public void serialEvent (SerialPortEvent spe) {
      if (spe.getEventType () != SerialPort.LISTENING_EVENT_DATA_AVAILABLE) {
        return;
      }
      byte[] bytes = new byte[1];
      while (ppiUkunSerialPort.bytesAvailable () != 0 &&
             ppiUkunSerialPort.readBytes (bytes, 1) != 0) {  //1バイトずつ受信する
        int data = bytes[0] & 0xff;
        if (PPI_UKUN_DEBUG) {
          System.out.printf ("UkunDataListener received 0x%02x\n", data);
        }
        //変化通知
        //  0b1..0_....  portA  0b1..1_....
        //  0b1..1_....  portB  0b1..1_....
        //  0b0000_....  portC  0b...._0000
        //完了通知
        //  0b0011_1111  completed
        //モード通知
        //  0b0011_0001  Notify mode
        //  0b0011_0010  Command mode
        if ((data & 0b1001_0000) == 0b1000_0000) {  //0b1..0_.... portA
          ppiUkunPortA = 0b1001_0000 | data;  //0b1..1_....
        } else if ((data & 0b1001_0000) == 0b1001_0000) {  //0b1..1_.... portB
          ppiUkunPortB = data;  //0b1..1_....
        } else if (data <= 0b0000_1111) {  //0b0000_.... portC
          ppiUkunPortC = data << 4;  //0b...._0000
        } else if (data == 0b0011_1111) {  //completed
          ppiUkunSending = false;
        } else if (data == 0b0011_0001) {  //Notify mode
          if (ppiUkunRequestedMode == PPI_UKUN_NOTIFY_B) {
            System.out.printf (Multilingual.mlnJapanese ?
                               "じょいぽーと U 君 (%s) は Notify(B) モードで初期化されました\n" :
                               "JoyPortUkun (%s) has been initialized in Notify(B) mode\n",
                               ppiUkunPortName);
            //ppiUkunRequestedMode = PPI_UKUN_NOTIFY_B;
            ppiUkunCurrentMode = PPI_UKUN_NOTIFY_B;
          } else {
            System.out.printf (Multilingual.mlnJapanese ?
                               "じょいぽーと U 君 (%s) は Notify(A) モードで初期化されました\n" :
                               "JoyPortUkun (%s) has been initialized in Notify(A) mode\n",
                               ppiUkunPortName);
            ppiUkunRequestedMode = PPI_UKUN_NOTIFY_A;
            ppiUkunCurrentMode = PPI_UKUN_NOTIFY_A;
          }
          if (PPI_UKUN_DEBUG) {
            System.out.printf ("UkunCurrentMode 0x%04x\n", ppiUkunCurrentMode);
          }
          ppiUkunAdjustMenu ();
        } else if (data == 0b0011_0010) {  //Command mode
          ppiUkunRequestedMode = PPI_UKUN_COMMAND;
          ppiUkunCurrentMode = PPI_UKUN_COMMAND;
          if (PPI_UKUN_DEBUG) {
            System.out.printf ("UkunCurrentMode 0x%04x\n", ppiUkunCurrentMode);
          }
          ppiUkunAdjustMenu ();
          System.out.printf (Multilingual.mlnJapanese ?
                             "じょいぽーと U 君 (%s) は Command モードで初期化されました\n" :
                             "JoyPortUkun (%s) has been initialized in Command mode\n",
                             ppiUkunPortName);
          //シリアルポートデータリスナーを解除する
          ppiUkunSerialPort.removeDataListener ();
          ppiUkunDataListener = null;
          break;
        } else {
          System.out.printf ("UkunDataListener received unknown data 0x%02x\n", data);
          //シリアルポートデータリスナーを解除する
          ppiUkunSerialPort.removeDataListener ();
          ppiUkunDataListener = null;
          break;
        }
      }  //while
    }  //serialEvent
  }  //class UkunDataListener

  //class UkunThread
  //  受信スレッド
  public static class UkunThread extends Thread {
    @Override public void run () {
      if (PPI_UKUN_DEBUG) {
        System.out.println ("UkunThread start");
      }
      try {
        while (ppiUkunCurrentConnection) {
          //受信する
          //  受信するかキャンセルされるまでブロックする
          int data = ppiUkunOldInputStream.read ();
          if (PPI_UKUN_DEBUG) {
            System.out.printf ("UkunThread received 0x%02x\n", data);
          }
          //変化通知
          //  0b1..0_....  portA  0b1..1_....
          //  0b1..1_....  portB  0b1..1_....
          //  0b0000_....  portC  0b...._0000
          //完了通知
          //  0b0011_1111  completed
          //モード通知
          //  0b0011_0001  Notify mode
          //  0b0011_0010  Command mode
          if ((data & 0b1001_0000) == 0b1000_0000) {  //0b1..0_.... portA
            ppiUkunPortA = 0b1001_0000 | data;  //0b1..1_....
          } else if ((data & 0b1001_0000) == 0b1001_0000) {  //0b1..1_.... portB
            ppiUkunPortB = data;  //0b1..1_....
          } else if (data <= 0b0000_1111) {  //0b0000_.... portC
            ppiUkunPortC = data << 4;  //0b...._0000
          } else if (data == 0b0011_1111) {  //completed
            ppiUkunSending = false;
          } else if (data == 0b0011_0001) {  //Notify mode
            if (ppiUkunRequestedMode == PPI_UKUN_NOTIFY_B) {
              System.out.printf (Multilingual.mlnJapanese ?
                                 "じょいぽーと U 君 (%s) は Notify(B) モードで初期化されました\n" :
                                 "JoyPortUkun (%s) has been initialized in Notify(B) mode\n",
                                 ppiUkunPortName);
              //ppiUkunRequestedMode = PPI_UKUN_NOTIFY_B;
              ppiUkunCurrentMode = PPI_UKUN_NOTIFY_B;
            } else {
              System.out.printf (Multilingual.mlnJapanese ?
                                 "じょいぽーと U 君 (%s) は Notify(A) モードで初期化されました\n" :
                                 "JoyPortUkun (%s) has been initialized in Notify(A) mode\n",
                                 ppiUkunPortName);
              ppiUkunRequestedMode = PPI_UKUN_NOTIFY_A;
              ppiUkunCurrentMode = PPI_UKUN_NOTIFY_A;
            }
            if (PPI_UKUN_DEBUG) {
              System.out.printf ("UkunCurrentMode 0x%04x\n", ppiUkunCurrentMode);
            }
            ppiUkunAdjustMenu ();
          } else if (data == 0b0011_0010) {  //Command mode
            ppiUkunRequestedMode = PPI_UKUN_COMMAND;
            ppiUkunCurrentMode = PPI_UKUN_COMMAND;
            if (PPI_UKUN_DEBUG) {
              System.out.printf ("UkunCurrentMode 0x%04x\n", ppiUkunCurrentMode);
            }
            ppiUkunAdjustMenu ();
            System.out.printf (Multilingual.mlnJapanese ?
                               "じょいぽーと U 君 (%s) は Command モードで初期化されました\n" :
                               "JoyPortUkun (%s) has been initialized in Command mode\n",
                               ppiUkunPortName);
            //受信スレッドを終了する
            break;
          } else if (data < 0) {  //キャンセルされた
            //受信スレッドを終了する
            break;
          } else {
            System.out.printf ("UkunThread received unknown data 0x%02x\n", data);
            //受信スレッドを終了する
            break;
          }
        }  //while
      } catch (IOException ioe) {
        ioe.printStackTrace ();
      }
      ppiUkunThread = null;
      if (PPI_UKUN_DEBUG) {
        System.out.println ("UkunThread end");
      }
    }
  }  //class UkunThread

  //ppiUkunNanoStart ()
  //  計測開始
  public static void ppiUkunNanoStart () {
    if (PPI_UKUN_ON && PPI_UKUN_NANO) {
      XEiJ.tmrTimer.schedule (new TimerTask () {
        @Override public void run () {
          if (!ppiUkunNanoMeasuring) {
            ppiUkunNanoMeasuring = true;
            Arrays.fill (ppiUkunNanoBestArray, Long.MAX_VALUE);
            Arrays.fill (ppiUkunNanoWorstArray, 0L);
            Arrays.fill (ppiUkunNanoTotalArray, 0L);
            Arrays.fill (ppiUkunNanoCountArray, 0L);
          }
        }
      }, 0L);
    }
  }

  //ppiUkunNanoEnd ()
  //  計測終了
  public static void ppiUkunNanoEnd () {
    if (PPI_UKUN_ON && PPI_UKUN_NANO) {
      XEiJ.tmrTimer.schedule (new TimerTask () {
        @Override public void run () {
          if (ppiUkunNanoMeasuring) {
            ppiUkunNanoMeasuring = false;
            System.out.println ("Nano timer");
            for (int i = 0; i < 8; i++) {
              long best = ppiUkunNanoBestArray[i];
              long worst = ppiUkunNanoWorstArray[i];
              long total = ppiUkunNanoTotalArray[i];
              long count = ppiUkunNanoCountArray[i];
              if (count != 0L) {
                System.out.printf ("  %s %s best=%d(ns) average=%d(ns) worst=%d(ns) total=%d(ns) count=%d\n",
                                   i < 2 ? "PORT A" : i < 4 ? "PORT B" : i < 6 ? "PORT C" : "CONTROL WORD",
                                   (i & 1) == 0 ? "READ" : "WRITE",
                                   best,
                                   total / count,
                                   worst,
                                   total,
                                   count);
              }
            }
          }
        }
      }, 0L);
    }
  }


  //ppiStart ()
  public static void ppiStart () {
    if (RestorableFrame.rfmGetOpened (Settings.SGS_PPI_FRAME_KEY)) {
      ppiOpen ();
    }
    if (PPI_UKUN_ON) {
      if (ppiUkunRequestedConnection) {
        ppiUkunConnect ();
      }
    }
  }

  //ppiOpen ()
  //  ジョイスティックの設定ウインドウを開く
  public static void ppiOpen () {
    if (ppiFrame == null) {
      ppiMakeFrame ();
    }
    XEiJ.pnlExitFullScreen (false);
    ppiFrame.setVisible (true);
  }

  //ppiMakeFrame ()
  //  ジョイスティックポートの設定ウインドウを作る
  //  ここでは開かない
  public static void ppiMakeFrame () {

    //アクションリスナー
    ActionListener actionListener = new ActionListener () {
      @Override public void actionPerformed (ActionEvent ae) {
        Object source = ae.getSource ();
        String command = ae.getActionCommand ();
        switch (command) {
        case "Consider part of keyboard as joystick":
          //キーボードの一部をジョイスティックとみなす
          ppiJoyKey = ((JCheckBox) ae.getSource ()).isSelected ();
          break;
        case "Enabled only while the port is read repeatedly":
          //ポートが繰り返し読み出されている間だけ有効
          ppiJoyAuto = ((JCheckBox) ae.getSource ()).isSelected ();
          break;
        case "Remove key input data processed as a joystick operation":
          //ジョイスティック操作として処理されたキー入力データを取り除く
          ppiJoyBlock = ((JCheckBox) ae.getSource ()).isSelected ();
          break;
          //
        case "JoyPortUkun":  //じょいぽーと U 君
          if (PPI_UKUN_ON) {
            if (((JCheckBox) ae.getSource ()).isSelected ()) {  //接続
              ppiUkunMenuConnect ();
            } else {  //切断
              ppiUkunMenuDisconnect ();
            }
          }
          break;
        case "Notify(A) mode":  //Notify(A) モード
          if (PPI_UKUN_ON) {
            ppiUkunMenuNotifyAMode ();
          }
          break;
        case "Notify(B) mode":  //Notify(B) モード
          if (PPI_UKUN_ON) {
            ppiUkunMenuNotifyBMode ();
          }
          break;
        case "Command mode":  //Command モード
          if (PPI_UKUN_ON) {
            ppiUkunMenuCommandMode ();
          }
          break;
        case "jSerialComm":
          if (PPI_UKUN_ON && PPI_UKUN_JSERIALCOMM && !ppiUkunCurrentConnection) {  //接続中は操作禁止
            ppiUkunJSerialCommOn = !XEiJ.prgIsWindows || ((JCheckBox) ae.getSource ()).isSelected ();  //Windows以外は常にtrue
            ppiUkunAdjustMenu ();
          }
          break;
        case "Output interval adjustment":  //出力間隔調整
          if (PPI_UKUN_ON && PPI_UKUN_INTERVAL) {
            boolean on = ((JCheckBox) ae.getSource ()).isSelected ();
            if (ppiUkunIntervalOn != on) {
              if (on) {
                XEiJ.tmrTimer.schedule (new TimerTask () {
                  @Override public void run () {
                    ppiUkunLastVirtualTime = XEiJ.mpuClockTime;
                    ppiUkunLastRealTime = System.nanoTime ();
                    ppiUkunIntervalOn = true;
                  }
                }, 0L);                
              } else {
                XEiJ.tmrTimer.schedule (new TimerTask () {
                  @Override public void run () {
                    ppiUkunIntervalOn = false;
                  }
                }, 0L);                
              }
            }
          }
          break;
        case "Nano timer":  //ナノタイマー
          if (PPI_UKUN_ON && PPI_UKUN_NANO) {
            if (((JCheckBox) ae.getSource ()).isSelected ()) {  //計測開始
              ppiUkunNanoStart ();
            } else {  //計測終了
              ppiUkunNanoEnd ();
            }
          }
          break;
        case "XInput":
          if (PPI_XINPUT_ON) {
            if (((JCheckBox) ae.getSource ()).isSelected ()) {  //on
              if (!ppiXInputOn) {  //off→on
                ppiXInputOn = true;
                ppiXInputStart ();
              }
            } else {  //off
              if (ppiXInputOn) {  //on→off
                ppiXInputOn = false;
                ppiXInputEnd ();
              }
            }
          }
        }
      }
    };

    //ジョイスティックポートのメニュー
    //       0   1  2
    //   0  ポート  接続
    //   1   1   2
    //   2  -------------------------
    //   3  RB  RB  なし
    //   4  RB  RB  ノーマル2ボタンパッド#1
    //   5  RB  RB  ノーマル2ボタンパッド#2
    //   6  RB  RB  メガドラ3ボタンパッド#1
    //   7  RB  RB  メガドラ3ボタンパッド#2
    //   8  RB  RB  メガドラ6ボタンパッド#1
    //   9  RB  RB  メガドラ6ボタンパッド#2
    //  10  RB  RB  サイバースティック(デジタルモード)#1
    //  12  RB  RB  サイバースティック(デジタルモード)#2
    //  13  RB  RB  サイバースティック(アナログモード)#1
    //  14  RB  RB  サイバースティック(アナログモード)#2
    //  15  RB  RB  白窓君#1
    //  16  RB  RB  白窓君#2
    ButtonGroup port1Group = new ButtonGroup ();
    ButtonGroup port2Group = new ButtonGroup ();
    ActionListener port1Listener = new ActionListener () {
      @Override public void actionPerformed (ActionEvent ae) {
        Joystick joyStick = ppiJoysticks[Integer.parseInt (ae.getActionCommand ())];
        if (ppiJoystick1 != joyStick) {
          ppiJoystick1.reset ();
          ppiJoystick1 = joyStick;
        }
        ppiConfigurationScrollPane.setViewportView (joyStick.getConfigurationPanel ());
      }
    };
    ActionListener port2Listener = new ActionListener () {
      @Override public void actionPerformed (ActionEvent ae) {
        Joystick joyStick = ppiJoysticks[Integer.parseInt (ae.getActionCommand ())];
        if (ppiJoystick2 != joyStick) {
          ppiJoystick2.reset ();
          ppiJoystick2 = joyStick;
        }
        ppiConfigurationScrollPane.setViewportView (joyStick.getConfigurationPanel ());
      }
    };
    ArrayList<Object> cellList = new ArrayList<Object> ();
    cellList.add (Multilingual.mlnText (ComponentFactory.createLabel ("Port"), "ja", "ポート"));  //(0,0)-(1,0)
    cellList.add (Multilingual.mlnText (ComponentFactory.createLabel ("Connect to"), "ja", "接続"));  //(2,0)-(2,1)
    cellList.add ("1");  //(0,1)
    cellList.add ("2");  //(1,1)
    cellList.add (ComponentFactory.createHorizontalSeparator ());  //(0,2)-(2,2)
    for (int i = 0; i < ppiJoysticks.length; i++) {
      Joystick joyStick = ppiJoysticks[i];
      cellList.add (ComponentFactory.setText (ComponentFactory.createRadioButton (
        port1Group, joyStick == ppiJoystick1, String.valueOf (i), port1Listener), ""));  //(0,3+i)
      cellList.add (ComponentFactory.setText (ComponentFactory.createRadioButton (
        port2Group, joyStick == ppiJoystick2, String.valueOf (i), port2Listener), ""));  //(1,3+i)
      cellList.add (Multilingual.mlnText (ComponentFactory.createLabel (joyStick.getNameEn ()), "ja", joyStick.getNameJa ()));  //(3,3)
    }
    JScrollPane portMenuPanel = new JScrollPane (
      ComponentFactory.createGridPanel (
        3, 3 + ppiJoysticks.length,
        "paddingLeft=3,paddingRight=3,center",   //gridStyles
        "",  //colStyles
        "italic;italic;colSpan=3,widen",  //rowStyles
        "colSpan=2;rowSpan=2",  //cellStyles
        cellList.toArray (new Object[0])));

    //個々のジョイスティックの設定パネル
    ppiConfigurationScrollPane = new JScrollPane ((((ppiJoystick1 instanceof DummyPad) &&
                                                    !(ppiJoystick2 instanceof DummyPad)) ||  //2だけ接続されている
                                                   (!(ppiJoystick1 instanceof Shiromadokun) &&
                                                    (ppiJoystick2 instanceof Shiromadokun))  //2だけ白窓君
                                                   ? ppiJoystick2 : ppiJoystick1).getConfigurationPanel ());

    //ウインドウ
    ButtonGroup ukunGroup = new ButtonGroup ();
    ppiFrame = Multilingual.mlnTitle (
      ComponentFactory.createRestorableSubFrame (
        Settings.SGS_PPI_FRAME_KEY,
        "Joystick port settings",
        null,
        ComponentFactory.setEmptyBorder (
          ComponentFactory.createVerticalBox (
            //
            ComponentFactory.createFlowPanel (
              ppiUkunConnectCheckbox =
              !(PPI_UKUN_ON && (PPI_UKUN_JSERIALCOMM || XEiJ.prgWindllLoaded)) ? null :
              Multilingual.mlnText (
                ComponentFactory.setEnabled (
                  ComponentFactory.createCheckBox (ppiUkunRequestedConnection, "JoyPortUkun", actionListener),
                  ppiUkunRequestedConnection == ppiUkunCurrentConnection),
                "ja", "じょいぽーと U 君")
              ),
            !(PPI_UKUN_ON && (PPI_UKUN_JSERIALCOMM || XEiJ.prgWindllLoaded)) ? null :
            ComponentFactory.createFlowPanel (
              20, 0,
              ppiUkunNotifyAModeRadioButton =
              Multilingual.mlnText (
                ComponentFactory.setEnabled (
                  ComponentFactory.createRadioButton (ukunGroup, ppiUkunRequestedMode == PPI_UKUN_NOTIFY_A, "Notify(A) mode", actionListener),
                  !(ppiUkunCurrentConnection && ppiUkunRequestedMode != ppiUkunCurrentMode)),
                "ja", "Notify(A) モード"),
              ppiUkunNotifyBModeRadioButton =
              Multilingual.mlnText (
                ComponentFactory.setEnabled (
                  ComponentFactory.createRadioButton (ukunGroup, ppiUkunRequestedMode == PPI_UKUN_NOTIFY_B, "Notify(B) mode", actionListener),
                  !(ppiUkunCurrentConnection && ppiUkunRequestedMode != ppiUkunCurrentMode)),
                "ja", "Notify(B) モード"),
              ppiUkunCommandModeRadioButton =
              Multilingual.mlnText (
                ComponentFactory.setEnabled (
                  ComponentFactory.createRadioButton (ukunGroup, ppiUkunRequestedMode == PPI_UKUN_COMMAND, "Command mode", actionListener),
                  !(ppiUkunCurrentConnection && ppiUkunRequestedMode != ppiUkunCurrentMode)),
                "ja", "Command モード")
              ),
            !(PPI_UKUN_ON && (PPI_UKUN_JSERIALCOMM || XEiJ.prgWindllLoaded)) ? null :
            ComponentFactory.createFlowPanel (
              20, 0,
              ppiUkunJSerialCommCheckbox =
              ComponentFactory.setEnabled (
                ComponentFactory.createCheckBox (ppiUkunJSerialCommOn, "jSerialComm", actionListener),
                XEiJ.prgIsWindows && !ppiUkunCurrentConnection),
              Multilingual.mlnText (
                ComponentFactory.createCheckBox (ppiUkunIntervalOn, "Output interval adjustment", actionListener),
                "ja", "出力間隔調整"),
              !PPI_UKUN_NANO ? null :
              Multilingual.mlnText (
                ComponentFactory.createCheckBox (ppiUkunNanoMeasuring, "Nano timer", actionListener),
                "ja", "ナノタイマー")
              ),
            !(PPI_UKUN_ON && (PPI_UKUN_JSERIALCOMM || XEiJ.prgWindllLoaded)) ? null : ComponentFactory.createHorizontalSeparator (),
            //
            !(PPI_XINPUT_ON && XEiJ.prgWindllLoaded) ? null :
            ComponentFactory.createFlowPanel (
              ComponentFactory.createCheckBox (ppiXInputOn, "XInput", actionListener)
              ),
            !(PPI_XINPUT_ON && XEiJ.prgIsWindows) ? null : ComponentFactory.createHorizontalSeparator (),
            //
            ComponentFactory.createFlowPanel (
              Multilingual.mlnText (
                ComponentFactory.createCheckBox (
                  ppiJoyKey,
                  "Consider part of keyboard as joystick",
                  actionListener),
                "ja", "キーボードの一部をジョイスティックとみなす")
              ),
            ComponentFactory.createFlowPanel (
              Multilingual.mlnText (
                ComponentFactory.createCheckBox (
                  ppiJoyAuto,
                  "Enabled only while the port is read repeatedly",
                  actionListener),
                "ja", "ポートが繰り返し読み出されている間だけ有効")
              ),
            ComponentFactory.createFlowPanel (
              Multilingual.mlnText (
                ComponentFactory.createCheckBox (
                  ppiJoyBlock,
                  "Remove key input data processed as a joystick operation",
                  actionListener),
                "ja", "ジョイスティック操作として処理されたキー入力データを取り除く")
              ),
            Box.createVerticalStrut (5),
            ComponentFactory.createHorizontalBox (
              ComponentFactory.createHorizontalSplitPane (
                portMenuPanel,
                ppiConfigurationScrollPane)
              )
            ),
          5, 5, 5, 5)),
      "ja", "ジョイスティックポート設定");
  }  //ppiMakeFrame()

  //consume = ppiInput (ke, pressed)
  //  JOYKEYの処理
  //  consume  true=入力をキーボードに渡さない
  public static boolean ppiInput (KeyEvent ke, boolean pressed) {
    boolean consume = false;
    if (ppiJoyKey && (!ppiJoyAuto || XEiJ.mpuClockTime < ppiLastAccessTime + PPI_CONTINUOUS_ACCESS_SPAN)) {
      if (ppiJoystick1.input (ke, pressed) ||
          ppiJoystick2.input (ke, pressed)) {
        //押されたときだけキーボード入力を取り除く
        //  特に自動有効化のときは、押されている間に有効になって離されたデータだけ取り除かれると、
        //  キーボード側は押されたままになっていると判断してリピートが止まらなくなる
        consume = pressed && ppiJoyBlock;
      }
    }
    return consume;
  }


  //ppiUkunAdjustInterval ()
  //  出力間隔調整
  //  PPIへの書き込みの間隔を仮想時間と実時間でそれぞれ計測し、
  //  500usを上限として、
  //  仮想時間の間隔の方が短ければウェイトを、
  //  実時間の間隔の方が短ければ空ループを追加することで、
  //  仮想時間の間隔と実時間の間隔のずれを軽減する。
  //  空ループを追加するとき負荷率が高くなる。
  public static void ppiUkunAdjustInterval () {
    long virtualTime = XEiJ.mpuClockTime;  //仮想時間。ピコ秒
    long realTime = System.nanoTime ();  //実時間。ナノ秒
    long virtualInterval = virtualTime - ppiUkunLastVirtualTime;  //仮想時間の間隔
    long realInterval = realTime - ppiUkunLastRealTime;  //実時間の間隔
    ppiUkunLastVirtualTime = virtualTime;
    ppiUkunLastRealTime = realTime;
    realInterval *= XEiJ.TMR_FREQ / 1000000000L;  //ナノ秒→ピコ秒
    if (virtualInterval <= realInterval) {  //仮想時間の間隔の方が短い。ウェイトを追加する
      long adjustmentTime = realInterval - virtualInterval;
      adjustmentTime = Math.min (PPI_UKUN_INTERVAL_LIMIT, adjustmentTime);
      XEiJ.mpuClockTime += adjustmentTime;
    } else {  //実時間の間隔の方が短い。空ループを追加する
      long adjustmentTime = virtualInterval - realInterval;
      adjustmentTime = Math.min (PPI_UKUN_INTERVAL_LIMIT, adjustmentTime);
      adjustmentTime /= XEiJ.TMR_FREQ / 1000000000L;  //ピコ秒→ナノ秒
      if (adjustmentTime != 0L) {
        if (true) {  //空ループを追加する
          while (System.nanoTime () < realTime + adjustmentTime) {
          }
        } else {  //スリープを追加する
          try {
            Thread.sleep (adjustmentTime / 1000000L, (int) (adjustmentTime % 1000000L));
          } catch (InterruptedException ie) {
          }
        }
      }
    }
  }  //ppiUkunAdjustInterval


  //  リード
  //  FM音源レジスタのアクセスウェイトのためのPPIの空読みは、ジョイスティックのデータを得ることが目的ではないので、
  //  ジョイスティックポートが連続的に読み出されているとみなさない
  public static int ppiReadByte (int a) {
    int d;
    //
    long t;
    if (PPI_UKUN_NANO) {
      t = System.nanoTime ();
    }
    //
    switch (a & 7) {
      //
    case PPI_PORT_A & 7:
      if (PPI_UKUN_ON &&
          ppiUkunCurrentConnection) {  //接続している
        if (ppiUkunCurrentMode == PPI_UKUN_NOTIFY_B) {  //Notify(B)モード
          if (ppiUkunSending) {
            XEiJ.mpuClockTime += PPI_UKUN_WAIT;
            do {
            } while (ppiUkunSending);
          }
        } else if (ppiUkunCurrentMode == PPI_UKUN_COMMAND) {  //Commandモード
          XEiJ.mpuClockTime += PPI_UKUN_WAIT;
          if (PPI_UKUN_JSERIALCOMM && ppiUkunJSerialCommOn) {  //jSerialComm
            byte[] b = new byte[] { (byte) 0b0011_1010 };  //リードポートA
            ppiUkunSerialPort.writeBytes (b, 1);
            ppiUkunSerialPort.readBytes (b, 1);
            ppiUkunPortA = b[0] & 0xff;
          } else {  //!jSerialComm
            try {
              ppiUkunOldOutputStream.write (0b0011_1010);  //リードポートA
              ppiUkunPortA = ppiUkunOldInputStream.read ();
            } catch (IOException ioe) {
              ioe.printStackTrace ();
              ppiUkunDisconnect ();
            }
          }  //if jSerialComm/!
        }
        d = ppiUkunPortA;
      } else {  //接続していない
        if (XEiJ.regOC >> 6 != 0b0100_101_000) {  //TST.B以外
          ppiLastAccessTime = XEiJ.mpuClockTime;
        }
        d = ppiJoystick1.readByte () & 0xff;
      }
      break;
      //
    case PPI_PORT_B & 7:
      if (PPI_UKUN_ON &&
          ppiUkunCurrentConnection) {  //接続している
        if (ppiUkunCurrentMode == PPI_UKUN_NOTIFY_B) {  //Notify(B)モード
          if (ppiUkunSending) {
            XEiJ.mpuClockTime += PPI_UKUN_WAIT;
            do {
            } while (ppiUkunSending);
          }
        } else if (ppiUkunCurrentMode == PPI_UKUN_COMMAND) {  //Commandモード
          XEiJ.mpuClockTime += PPI_UKUN_WAIT;
          if (PPI_UKUN_JSERIALCOMM && ppiUkunJSerialCommOn) {  //jSerialComm
            byte[] b = new byte[] { (byte) 0b0011_1011 };  //リードポートB
            ppiUkunSerialPort.writeBytes (b, 1);
            ppiUkunSerialPort.readBytes (b, 1);
            ppiUkunPortB = b[0] & 0xff;
          } else {  //!jSerialComm
            try {
              ppiUkunOldOutputStream.write (0b0011_1011);  //リードポートB
              ppiUkunPortB = ppiUkunOldInputStream.read ();
            } catch (IOException ioe) {
              ioe.printStackTrace ();
              ppiUkunDisconnect ();
            }
          }  //if jSerialComm/!
        }
        d = ppiUkunPortB;
      } else {  //接続していない
        if (XEiJ.regOC >> 6 != 0b0100_101_000) {  //TST.B以外
          ppiLastAccessTime = XEiJ.mpuClockTime;
        }
        d = ppiJoystick2.readByte () & 0xff;
      }
      break;
      //
    case PPI_PORT_C & 7:
      if (PPI_UKUN_ON &&
          ppiUkunCurrentConnection) {  //接続している
        XEiJ.mpuClockTime += PPI_UKUN_WAIT;
        if (ppiUkunCurrentMode == PPI_UKUN_NOTIFY_B) {  //Notify(B)モード
          if (ppiUkunSending) {
            XEiJ.mpuClockTime += PPI_UKUN_WAIT;
            do {
            } while (ppiUkunSending);
          }
        } else if (ppiUkunCurrentMode == PPI_UKUN_COMMAND) {  //Commandモード
          XEiJ.mpuClockTime += PPI_UKUN_WAIT;
          if (PPI_UKUN_JSERIALCOMM && ppiUkunJSerialCommOn) {  //jSerialComm
            byte[] b = new byte[] { (byte) 0b0011_1100 };  //リードポートC
            ppiUkunSerialPort.writeBytes (b, 1);
            ppiUkunSerialPort.readBytes (b, 1);
            ppiUkunPortC = b[0] & 0xf0;
          } else {  //!jSerialComm
            try {
              ppiUkunOldOutputStream.write (0b0011_1100);  //リードポートC
              ppiUkunPortC = ppiUkunOldInputStream.read () & 0xf0;
            } catch (IOException ioe) {
              ioe.printStackTrace ();
              ppiUkunDisconnect ();
            }
          }  //if jSerialComm/!
        }
        d = (ppiUkunPortC |  //上位4ビット
             (ppiPortCData & 0x0f));  //下位4ビット
      } else {  //接続していない
        d = ppiPortCData;
      }
      break;
    default:
      d = 0xff;
    }  //switch a&7
    //
    if (PPI_UKUN_NANO) {
      t = System.nanoTime () - t;
      int i = a & 6;  //0,2,4,6
      if (t < ppiUkunNanoBestArray[i]) {
        ppiUkunNanoBestArray[i] = t;
      }
      if (ppiUkunNanoWorstArray[i] < t) {
        ppiUkunNanoWorstArray[i] = t;
      }
      ppiUkunNanoTotalArray[i] += t;
      ppiUkunNanoCountArray[i]++;
    }
    //
    return d;
  }  //ppiReadByte

  //  ライト
  public static void ppiWriteByte (int a, int d) {
    d &= 0xff;
    //
    long t;
    if (PPI_UKUN_NANO) {
      t = System.nanoTime ();
    }
    //
    switch (a & 7) {
      //
    case PPI_PORT_A & 7:
      if (PPI_UKUN_ON &&
          ppiUkunCurrentConnection) {  //接続している
        if (PPI_UKUN_INTERVAL && ppiUkunIntervalOn) {
          ppiUkunAdjustInterval ();
        } else {
          XEiJ.mpuClockTime += PPI_UKUN_WAIT;
        }
        if (ppiUkunCurrentMode == PPI_UKUN_NOTIFY_A) {  //Notify(A)モード
          ppiUkunSending = true;
          if (PPI_UKUN_JSERIALCOMM && ppiUkunJSerialCommOn) {  //jSerialComm
            ppiUkunSerialPort.writeBytes (new byte[] { (byte) (0b1000_0000 | (d & 0b0110_1111)) }, 1);  //ライトポートA
          } else {  //!jSerialComm
            try {
              ppiUkunOldOutputStream.write (0b1000_0000 | (d & 0b0110_1111));  //ライトポートA
            } catch (IOException ioe) {
              ioe.printStackTrace ();
              ppiUkunDisconnect ();
            }
          }  //if jSerialComm/!
          while (ppiUkunSending) {
          }
        } else if (ppiUkunCurrentMode == PPI_UKUN_NOTIFY_B) {  //Notify(B)モード
          while (ppiUkunSending) {
          }
          ppiUkunSending = true;
          if (PPI_UKUN_JSERIALCOMM && ppiUkunJSerialCommOn) {  //jSerialComm
            ppiUkunSerialPort.writeBytes (new byte[] { (byte) (0b1000_0000 | (d & 0b0110_1111)) }, 1);  //ライトポートA
          } else {  //!jSerialComm
            try {
              ppiUkunOldOutputStream.write (0b1000_0000 | (d & 0b0110_1111));  //ライトポートA
            } catch (IOException ioe) {
              ioe.printStackTrace ();
              ppiUkunDisconnect ();
            }
          }  //if jSerialComm/!
        } else if (ppiUkunCurrentMode == PPI_UKUN_COMMAND) {  //Commandモード
          if (PPI_UKUN_JSERIALCOMM && ppiUkunJSerialCommOn) {  //jSerialComm
            ppiUkunSerialPort.writeBytes (new byte[] { (byte) (0b1000_0000 | (d & 0b0110_1111)) }, 1);  //ライトポートA
          } else {  //!jSerialComm
            try {
              ppiUkunOldOutputStream.write (0b1000_0000 | (d & 0b0110_1111));  //ライトポートA
            } catch (IOException ioe) {
              ioe.printStackTrace ();
              ppiUkunDisconnect ();
            }
          }  //if jSerialComm/!
        }
      } else {  //接続していない
        ppiJoystick1.writeByte (d);
      }
      break;
      //
    case PPI_PORT_B & 7:
      if (PPI_UKUN_ON &&
          ppiUkunCurrentConnection) {  //接続している
        if (PPI_UKUN_INTERVAL && ppiUkunIntervalOn) {
          ppiUkunAdjustInterval ();
        } else {
          XEiJ.mpuClockTime += PPI_UKUN_WAIT;
        }
        if (ppiUkunCurrentMode == PPI_UKUN_NOTIFY_A) {  //Notify(A)モード
          ppiUkunSending = true;
          if (PPI_UKUN_JSERIALCOMM && ppiUkunJSerialCommOn) {  //jSerialComm
            ppiUkunSerialPort.writeBytes (new byte[] { (byte) (0b1001_0000 | (d & 0b0110_1111)) }, 1);  //ライトポートB
          } else {  //!jSerialComm
            try {
              ppiUkunOldOutputStream.write (0b1001_0000 | (d & 0b0110_1111));  //ライトポートB
            } catch (IOException ioe) {
              ioe.printStackTrace ();
              ppiUkunDisconnect ();
            }
          }  //if jSerialComm/!
          while (ppiUkunSending) {
          }
        } else if (ppiUkunCurrentMode == PPI_UKUN_NOTIFY_B) {  //Notify(B)モード
          while (ppiUkunSending) {
          }
          ppiUkunSending = true;
          if (PPI_UKUN_JSERIALCOMM && ppiUkunJSerialCommOn) {  //jSerialComm
            ppiUkunSerialPort.writeBytes (new byte[] { (byte) (0b1001_0000 | (d & 0b0110_1111)) }, 1);  //ライトポートB
          } else {  //!jSerialComm
            try {
              ppiUkunOldOutputStream.write (0b1001_0000 | (d & 0b0110_1111));  //ライトポートB
            } catch (IOException ioe) {
              ioe.printStackTrace ();
              ppiUkunDisconnect ();
            }
          }  //if jSerialComm/!
        } else if (ppiUkunCurrentMode == PPI_UKUN_COMMAND) {  //Commandモード
          if (PPI_UKUN_JSERIALCOMM && ppiUkunJSerialCommOn) {  //jSerialComm
            ppiUkunSerialPort.writeBytes (new byte[] { (byte) (0b1001_0000 | (d & 0b0110_1111)) }, 1);  //ライトポートB
          } else {  //!jSerialComm
            try {
              ppiUkunOldOutputStream.write (0b1001_0000 | (d & 0b0110_1111));  //ライトポートB
            } catch (IOException ioe) {
              ioe.printStackTrace ();
              ppiUkunDisconnect ();
            }
          }  //if jSerialComm/!
        }
      } else {  //接続していない
        ppiJoystick2.writeByte (d);
      }
      break;
      //
    case PPI_PORT_C & 7:
      ppiPortCData = d;
      //下位4ビット
      ADPCM.pcmSetPan (d);  //パン
      ADPCM.pcmDivider = d >> 2 & 3;  //分周比。0=1/1024,1=1/768,2=1/512,3=inhibited
      ADPCM.pcmUpdateRepeatInterval ();
      //上位4ビット
      if (PPI_UKUN_ON &&
          ppiUkunCurrentConnection) {  //接続している
        if (PPI_UKUN_INTERVAL && ppiUkunIntervalOn) {
          ppiUkunAdjustInterval ();
        } else {
          XEiJ.mpuClockTime += PPI_UKUN_WAIT;
        }
        if (ppiUkunCurrentMode == PPI_UKUN_NOTIFY_A) {  //Notify(A)モード
          ppiUkunSending = true;
          if (PPI_UKUN_JSERIALCOMM && ppiUkunJSerialCommOn) {  //jSerialComm
            ppiUkunSerialPort.writeBytes (new byte[] { (byte) (d >> 4) }, 1);  //ライトポートC
          } else {  //!jSerialComm
            try {
              ppiUkunOldOutputStream.write (d >> 4);  //ライトポートC
            } catch (IOException ioe) {
              ioe.printStackTrace ();
              ppiUkunDisconnect ();
            }
          }  //if jSerialComm/!
          while (ppiUkunSending) {
          }
        } else if (ppiUkunCurrentMode == PPI_UKUN_NOTIFY_B) {  //Notify(B)モード
          while (ppiUkunSending) {
          }
          ppiUkunSending = true;
          if (PPI_UKUN_JSERIALCOMM && ppiUkunJSerialCommOn) {  //jSerialComm
            ppiUkunSerialPort.writeBytes (new byte[] { (byte) (d >> 4) }, 1);  //ライトポートC
          } else {  //!jSerialComm
            try {
              ppiUkunOldOutputStream.write (d >> 4);  //ライトポートC
            } catch (IOException ioe) {
              ioe.printStackTrace ();
              ppiUkunDisconnect ();
            }
          }  //if jSerialComm/!
        } else if (ppiUkunCurrentMode == PPI_UKUN_COMMAND) {  //Commandモード
          if (PPI_UKUN_JSERIALCOMM && ppiUkunJSerialCommOn) {  //jSerialComm
            ppiUkunSerialPort.writeBytes (new byte[] { (byte) (d >> 4) }, 1);  //ライトポートC
          } else {  //!jSerialComm
            try {
              ppiUkunOldOutputStream.write (d >> 4);  //ライトポートC
            } catch (IOException ioe) {
              ioe.printStackTrace ();
              ppiUkunDisconnect ();
            }
          }  //if jSerialComm/!
        }
      } else {  //接続していない
        ppiJoystick1.setPin8 (d >> 4 & 1);
        ppiJoystick2.setPin8 (d >> 5 & 1);
        ppiJoystick1.setPin6 ((d >> 6 & 1) ^ 1);
        ppiJoystick1.setPin7 ((d >> 7 & 1) ^ 1);
      }
      break;
      //
    case PPI_CONTROL & 7:
      if ((d & 0b1000_0000) == 0b0000_0000) {  //0b0..._nnnx
        int n = (d >> 1) & 7;  //ビット番号
        int x = d & 1;  //データ
        ppiPortCData = ppiPortCData & ~(1 << n) | x << n;
        if (n < 4) {  //下位4ビット
          switch (n) {
          case 0:
          case 1:
            ADPCM.pcmSetPan (ppiPortCData & 3);  //パン
            break;
          case 2:
          case 3:
            ADPCM.pcmDivider = ppiPortCData >> 2 & 3;  //分周比。0=1/1024,1=1/768,2=1/512,3=inhibited
            ADPCM.pcmUpdateRepeatInterval ();
            break;
          }
        } else {  //上位4ビット
          if (PPI_UKUN_ON &&
              ppiUkunCurrentConnection) {  //接続している
            if (PPI_UKUN_INTERVAL && ppiUkunIntervalOn) {
              ppiUkunAdjustInterval ();
            } else {
              XEiJ.mpuClockTime += PPI_UKUN_WAIT;
            }
            if (ppiUkunCurrentMode == PPI_UKUN_NOTIFY_A) {  //Notify(A)モード
              ppiUkunSending = true;
              if (PPI_UKUN_JSERIALCOMM && ppiUkunJSerialCommOn) {  //jSerialComm
                ppiUkunSerialPort.writeBytes (new byte[] { (byte) (0b0001_0000 | (d & 0b0000_1111)) }, 1);  //ライトコントロールワード
              } else {  //!jSerialComm
                try {
                  ppiUkunOldOutputStream.write (0b0001_0000 | (d & 0b0000_1111));  //ライトコントロールワード
                } catch (IOException ioe) {
                  ioe.printStackTrace ();
                  ppiUkunDisconnect ();
                }
              }  //if jSerialComm/!
              while (ppiUkunSending) {
              }
            } else if (ppiUkunCurrentMode == PPI_UKUN_NOTIFY_B) {  //Notify(B)モード
              while (ppiUkunSending) {
              }
              ppiUkunSending = true;
              if (PPI_UKUN_JSERIALCOMM && ppiUkunJSerialCommOn) {  //jSerialComm
                ppiUkunSerialPort.writeBytes (new byte[] { (byte) (0b0001_0000 | (d & 0b0000_1111)) }, 1);  //ライトコントロールワード
              } else {  //!jSerialComm
                try {
                  ppiUkunOldOutputStream.write (0b0001_0000 | (d & 0b0000_1111));  //ライトコントロールワード
                } catch (IOException ioe) {
                  ioe.printStackTrace ();
                  ppiUkunDisconnect ();
                }
              }  //if jSerialComm/!
            } else if (ppiUkunCurrentMode == PPI_UKUN_COMMAND) {  //Commandモード
              if (PPI_UKUN_JSERIALCOMM && ppiUkunJSerialCommOn) {  //jSerialComm
                ppiUkunSerialPort.writeBytes (new byte[] { (byte) (0b0001_0000 | (d & 0b0000_1111)) }, 1);  //ライトコントロールワード
              } else {  //!jSerialComm
                try {
                  ppiUkunOldOutputStream.write (0b0001_0000 | (d & 0b0000_1111));  //ライトコントロールワード
                } catch (IOException ioe) {
                  ioe.printStackTrace ();
                  ppiUkunDisconnect ();
                }
              }  //if jSerialComm/!
            }
          } else {  //接続していない
            switch (n) {
            case 4:
              ppiJoystick1.setPin8 (x);
              break;
            case 5:
              ppiJoystick2.setPin8 (x);
              break;
            case 6:
              ppiJoystick1.setPin6 (x ^ 1);
              break;
            case 7:
              ppiJoystick1.setPin7 (x ^ 1);
              break;
            }
          }
        }  //if 下位/上位
      } else if ((d & 0b1000_0100) == 0b1000_0000){  //0b1..._.0..
        if (PPI_UKUN_ON &&
            ppiUkunCurrentConnection) {  //接続している
          if (PPI_UKUN_INTERVAL && ppiUkunIntervalOn) {
            ppiUkunAdjustInterval ();
          } else {
            XEiJ.mpuClockTime += PPI_UKUN_WAIT;
          }
          if (ppiUkunCurrentMode == PPI_UKUN_NOTIFY_A) {  //Notify(A)モード
            ppiUkunSending = true;
            if (PPI_UKUN_JSERIALCOMM && ppiUkunJSerialCommOn) {  //jSerialComm
              ppiUkunSerialPort.writeBytes (new byte[] { (byte) (0b0100_0000 |
                                                                 (d & 0b0111_1000) >> 1 |
                                                                 (d & 0b0000_0011)) }, 1);  //ライトコントロールワード
            } else {  //!jSerialComm
              try {
                ppiUkunOldOutputStream.write (0b0100_0000 |
                                              (d & 0b0111_1000) >> 1 |
                                              (d & 0b0000_0011));  //ライトコントロールワード
              } catch (IOException ioe) {
                ioe.printStackTrace ();
                ppiUkunDisconnect ();
              }
            }  //if jSerialComm/!
            while (ppiUkunSending) {
            }
          } else if (ppiUkunCurrentMode == PPI_UKUN_NOTIFY_B) {  //Notify(B)モード
            while (ppiUkunSending) {
            }
            ppiUkunSending = true;
            if (PPI_UKUN_JSERIALCOMM && ppiUkunJSerialCommOn) {  //jSerialComm
              ppiUkunSerialPort.writeBytes (new byte[] { (byte) (0b0100_0000 |
                                                                 (d & 0b0111_1000) >> 1 |
                                                                 (d & 0b0000_0011)) }, 1);  //ライトコントロールワード
            } else {  //!jSerialComm
              try {
                ppiUkunOldOutputStream.write (0b0100_0000 |
                                              (d & 0b0111_1000) >> 1 |
                                              (d & 0b0000_0011));  //ライトコントロールワード
              } catch (IOException ioe) {
                ioe.printStackTrace ();
                ppiUkunDisconnect ();
              }
            }  //if jSerialComm/!
          } else if (ppiUkunCurrentMode == PPI_UKUN_COMMAND) {  //Commandモード
            if (PPI_UKUN_JSERIALCOMM && ppiUkunJSerialCommOn) {  //jSerialComm
              ppiUkunSerialPort.writeBytes (new byte[] { (byte) (0b0100_0000 |
                                                                 (d & 0b0111_1000) >> 1 |
                                                                 (d & 0b0000_0011)) }, 1);  //ライトコントロールワード
            } else {  //!jSerialComm
              try {
                ppiUkunOldOutputStream.write (0b0100_0000 |
                                              (d & 0b0111_1000) >> 1 |
                                              (d & 0b0000_0011));  //ライトコントロールワード
              } catch (IOException ioe) {
                ioe.printStackTrace ();
                ppiUkunDisconnect ();
              }
            }  //if jSerialComm/!
          }
        } else {  //接続していない
          //!!! 未対応
        }
      } else {  //0b1..._.1..
        //!!! 非対応
      }
    }  //switch a&7
    //
    if (PPI_UKUN_NANO) {
      t = System.nanoTime () - t;
      int i = (a & 6) | 1;  //1,3,5,7
      if (t < ppiUkunNanoBestArray[i]) {
        ppiUkunNanoBestArray[i] = t;
      }
      if (ppiUkunNanoWorstArray[i] < t) {
        ppiUkunNanoWorstArray[i] = t;
      }
      ppiUkunNanoTotalArray[i] += t;
      ppiUkunNanoCountArray[i]++;
    }
    //
  }  //ppiWriteByte

}  //class PPI
