xeij/MercuryUnit.java
//========================================================================================
//  MercuryUnit.java
//    en:Mercury-Unit V4
//    ja:まーきゅりーゆにっとV4
//  Copyright (C) 2003-2026 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.util.*;  //Arrays

public class MercuryUnit {

  public static final boolean MU4_ON = true;
  public static boolean mu4OnRequest;
  public static boolean mu4On;
  public static boolean mu4OnPositive;
  public static boolean mu4OnNegative;
  public static boolean mu4OutputEnable;

  public static final boolean MU4_DEBUG = false;

  //mu4Init ()
  //  初期化
  public static void mu4Init () {
    //パラメータ
    mu4OnRequest = Settings.sgsGetOnOff ("mercury");
    mu4OutputEnable = Settings.sgsGetOnOff ("mercuryoe");
    //エルミート補間
    mu4Hermite640 = new Hermite (640, 2500);
    mu4Hermite882 = new Hermite (882, 2500);
    mu4Hermite960 = new Hermite (960, 2500);
    mu4Hermite1280 = new Hermite (1280, 2500);
    mu4Hermite1764 = new Hermite (1764, 2500);
    mu4Hermite1920 = new Hermite (1920, 2500);
    //ノイズ抑制
    for (int i = 0; i <= MU4_ATTACK_FRAMES; i++) {  //MU4_ATTACK_FRAMESを含む
      mu4AttackArray[i] =
        (float) ((1.0 + Math.sin (Math.PI *
                                  (double) (i - MU4_ATTACK_FRAMES / 2) /
                                  (double) MU4_ATTACK_FRAMES)) / 2.0);
    }
    if (MU4_DEBUG) {
      for (int i = 0; i <= MU4_ATTACK_FRAMES; i++) {
        System.out.printf ("mu4AttackArray[%d]=%.6f\n", i, mu4AttackArray[i]);
      }
    }
  }  //mu4Init

  //mu4Tini ()
  //  後始末
  public static void mu4Tini () {
    //パラメータ
    Settings.sgsPutOnOff ("mercury", mu4OnRequest);
    Settings.sgsPutOnOff ("mercuryoe", mu4OutputEnable);
  }  //mu4Tini

  //mu4Reset ()
  //  リセット
  public static void mu4Reset () {
    mu4On = mu4OnRequest;
    mu4OnPositive = mu4On && !XEiJ.specifiedModel.isShodai ();
    mu4OnNegative = mu4On && XEiJ.specifiedModel.isShodai ();
    //  command=0xfc20
    //  status=0xfc2e
    mu4PortUpper = (MU4_U_RESERVED |
                    MU4_U12_TERR |
                    //MU4_U11_EXPCL |
                    //MU4_U10_EXREQ |
                    MU4_U6_INSEL);
    mu4PortCommand = 0;
    mu4PortStatus = (MU4_S3_N32K |
                     MU4_S2_N44K |
                     MU4_S1_N48K);
    //mu4PortUpper &= ~(MU4_U11_EXPCL | MU4_U10_EXREQ);
    mu4EnablePclReq = true;
    mu4RiseExpcl ();
    mu4RiseExreq ();
    mu4EnablePclReq = false;
    mu4SequenceArray = null;
    Arrays.fill (mu4Buffer, 0);  //出力中にリセットされたとき必要
    TickerQueue.tkqRemove (mu4SequenceTicker);
    mu4PortCommand = -1;  //必ず変化したことにする
    mu4SetCommand (MU4_U_RESERVED |
                   MU4_U12_TERR |
                   MU4_U11_EXPCL |
                   MU4_U10_EXREQ |
                   MU4_U6_INSEL);
  }  //mu4Reset

  //d = mu4ReadByte (a)
  //  バイト読み出し
  public static int mu4ReadByte (int a) {
    int a70 = a & 0x70;
    int d = 0;
    if (a70 == 0x10) {  //0x90-0x9f command
      d = (a & 1) == 0 ? mu4PortUpper >> 8 : (mu4PortUpper | mu4PortCommand) & 0xff;
    } else if (a70 == 0x20) {  //0xa0-0xaf status
      d = (a & 1) == 0 ? mu4PortUpper >> 8 : (mu4PortUpper | mu4PortStatus) & 0xff;
    }
    if (MU4_DEBUG) {
      System.out.printf ("%08x mu4ReadByte(0x%08x)=0x%02x\n", XEiJ.regPC0, a, d);
    }
    return d;
  }  //mu4ReadByte

  //d = mu4ReadWord (a)
  //  ワード読み出し
  public static int mu4ReadWord (int a) {
    int a70 = a & 0x70;
    int d = 0;
    if (a70 == 0x10) {  //0x90-0x9f command
      d = mu4PortUpper | mu4PortCommand;
    } else if (a70 == 0x20) {  //0xa0-0xaf status
      d = mu4PortUpper | mu4PortStatus;
    }
    if (MU4_DEBUG) {
      System.out.printf ("%08x mu4ReadWord(0x%08x)=0x%04x\n", XEiJ.regPC0, a, d);
    }
    return d;
  }  //mu4ReadWord

  //mu4WriteByte (a, d)
  //  バイト書き込み
  public static void mu4WriteByte (int a, int d) {
    d &= 0xff;
    if (MU4_DEBUG) {
      System.out.printf ("%08x mu4WriteByte(0x%08x,0x%02x)\n", XEiJ.regPC0, a, d);
    }
    int a70 = a & 0x70;
    if (a70 == 0x10) {  //0x90-0x9f command
      int t = mu4PortUpper | mu4PortCommand;
      if ((a & 1) == 0) {
        mu4SetCommand ((d << 8) | (t & 0xff));
      } else {
        mu4SetCommand ((t & 0xff00) | d);
      }
    }
  }  //mu4WriteByte

  //mu4WriteWord (a, d)
  //  ワード書き込み
  public static void mu4WriteWord (int a, int d) {
    d &= 0xffff;
    if (MU4_DEBUG) {
      System.out.printf ("%08x mu4WriteWord(0x%08x,0x%04x)\n", XEiJ.regPC0, a, d);
    }
    int a70 = a & 0x70;
    if (a70 == 0x00 &&  //0x80-0x8f data
        mu4Mode == MU4_MODE_OUT &&
        mu4OutputEnable &&  //出力許可
        0 <= mu4WritePointer) {  //リクエストあり
      if ((mu4DataChannels == 1 ||  //mono
           (mu4WritePointer & 1) == 0) &&  //left
          (mu4PortCommand & MU4_C2_LG) != 0) {  //L-on
        mu4NaiKamoLeft = false;
        if (mu4NaiDesuLeft) {
          mu4NaiDesuLeft = false;  //ない→ある
          mu4AttackIndexLeft = 1;  //アタック開始
        }
        mu4DataArray[mu4WritePointer] = Math.round (mu4AttackArray[mu4AttackIndexLeft] * (short) d);
        mu4AttackIndexLeft = Math.min (MU4_ATTACK_FRAMES, mu4AttackIndexLeft + 1);
      }
      if ((mu4DataChannels == 1 ||  //mono
           (mu4WritePointer & 1) != 0) &&  //right
          (mu4PortCommand & MU4_C3_RG) != 0) {  //R-on
        mu4NaiKamoRight = false;
        if (mu4NaiDesuRight) {
          mu4NaiDesuRight = false;  //ない→ある
          mu4AttackIndexRight = 1;  //アタック開始
        }
        mu4DataArray[mu4WritePointer + (2 - mu4DataChannels)] = Math.round (mu4AttackArray[mu4AttackIndexRight] * (short) d);
        mu4AttackIndexRight = Math.min (MU4_ATTACK_FRAMES, mu4AttackIndexRight + 1);
      }
      if (false) {  //1回のEXREQに対して複数回wirteされたときfalse=最後,true=最初を有効にする
        mu4WritePointer = -1;
      }
      mu4DataWritten = true;
    } else if (a70 == 0x10) {  //0x90-0x9f command
      mu4SetCommand (d);
    }
  }  //mu4WriteWord

  //mu4HsyncStart (hsyncTime)
  //  水平同期パルス開始
  //    M256のときシーケンスを開始する
  //  CRTCが呼び出す
  //    垂直同期パルス期間は除いてある
  public static void mu4HsyncStart (long hsyncTime) {
    if (mu4Mode != MU4_MODE_M256) {
      return;
    }
    mu4SequenceArray = (mu4DataChannels == 1 ?  //mono
                        mu4DataRate < 32000 ? MU4_M256_LOW_MONO : MU4_M256_HIGH_MONO
                        :  //stereo
                        mu4DataRate < 32000 ? MU4_M256_LOW_STEREO : MU4_M256_HIGH_STEREO);
    if (mu4SequenceArray == MU4_M256_LOW_STEREO &&
        (mu4PortUpper & MU4_U54_CLKSEL) == MU4_U54_32K) {
      mu4SequenceArray = MU4_M256_LOW_MONO;  //不自然。シミュレータのバグ?
    }
    //水平同期パルス開始時刻
    //  XEiJ.mpuClockTimeでは水平同期パルスが遅延していたときシーケンスが欠落して画面が乱れる
    //  水平同期パルスと一緒に遅延させることで欠落を避ける
    mu4BlockTime = hsyncTime;
    //フレーム番号
    mu4FrameNumber = 0;
    //シーケンス内インデックス
    mu4SequenceIndex = 0;
    //シーケンスティッカーを予約する
    TickerQueue.tkqAdd (
      mu4SequenceTicker,  //シーケンスティッカー
      mu4SequenceTime = mu4BlockTime +  //水平同期パルス開始時刻
      mu4SequenceArray[mu4SequenceIndex++] *  //シーケンス内サイクル数
      XEiJ.TMR_FREQ / mu4DataFrequency);  //サイクル数を時刻に変換する。順序に注意
  }  //mu4HsyncStart

  //mu4ExackStart ()
  //  EXACK開始
  //    ACKIGでないときEXREQを出していたら引っ込める
  //  HD63450が呼び出す
  public static void mu4ExackStart () {
    if ((mu4PortUpper & MU4_U8_ACKIG) != 0) {  //ACKIGでない
      mu4RiseExreq ();
    }
  }  //mu4ExackStart



  //周波数変換後のデータの配列
  public static final int[] mu4Buffer = new int[2 * 2500];

  //mu4FillBuffer ()
  //  周波数変換後のデータを作る
  public static void mu4FillBuffer () {
    if (mu4Mode != MU4_MODE_OUT) {  //OUTでない
      return;
    }
    if (MU4_DEBUG) {
      System.out.printf ("%08x mu4FillBuffer()\n", XEiJ.regPC0);
    }
    //フレーム番号を巻き戻す
    mu4FrameNumber -= mu4DataFrames;
    //ブロック開始時刻を進める
    mu4BlockTime += MU4_BLOCK_TIME;
    //シーケンスティッカーを再設定する
    TickerQueue.tkqAdd (
      mu4SequenceTicker,  //シーケンスティッカー
      mu4SequenceTime = mu4BlockTime +  //ブロック開始時刻
      (mu4DataCycles * mu4FrameNumber + mu4SequenceArray[mu4SequenceIndex - 1]) *  //ブロック内サイクル数
      XEiJ.TMR_FREQ / mu4DataFrequency);  //サイクル数を時刻に変換する。順序に注意
    //書き込みがなければゼロを返して終わり
    if (!mu4DataWritten) {
      Arrays.fill (mu4Buffer, 0);
      return;
    }
    mu4DataWritten = false;
    //周波数変換を行う
    switch (mu4DataFrames) {
    case 640:
      mu4Hermite640.convert ();
      break;
    case 882:
      mu4Hermite882.convert ();
      break;
    case 960:
      mu4Hermite960.convert ();
      break;
    case 1280:
      mu4Hermite1280.convert ();
      break;
    case 1764:
      mu4Hermite1764.convert ();
      break;
    case 1920:
      mu4Hermite1920.convert ();
      break;
    }
    //溢れた位置に書き込まれた4フレーム分のデータを先頭に移す
    for (int i = 0; i < 2 * 4; i++) {
      mu4DataArray[i] = mu4DataArray[2 * mu4DataFrames + i];
      mu4DataWritten = mu4DataWritten || mu4DataArray[i] != 0;
    }
    Arrays.fill (mu4DataArray, 2 * 4, 2 * (mu4DataFrames + 4), 0);
  }  //mu4FillBuffer



  //ポート
  //  command/status bit12-4
  static final int MU4_U_RESERVED = 7 << 13;
  static final int MU4_U12_TERR   = 1 << 12;  //0=TERR
  static final int MU4_U11_EXPCL  = 1 << 11;  //0=EXPCL
  static final int MU4_U10_EXREQ  = 1 << 10;  //0=EXREQ
  static final int MU4_U9_M256    = 1 << 9;  //0=M256
  static final int MU4_U8_ACKIG   = 1 << 8;  //0=ACKIG
  static final int MU4_U7_CLKRATE = 1 << 7;  //0=16k/22k/24k,1=32k/44k/48k
  static final int MU4_U6_INSEL   = 1 << 6;  //0=optical,1=coaxial
  static final int MU4_U54_CLKSEL = 3 << 4;  //01=16k/32k,00/10=22k/44k,11=24k/48k
  static final int MU4_U54_32K    = 1 << 4;
  static final int MU4_U54_48K    = 3 << 4;
  //  command bit3-0
  static final int MU4_C3_RG      = 1 << 3;  //0=R-off,1=R-on
  static final int MU4_C2_LG      = 1 << 2;  //0=L-off,1=L-on
  static final int MU4_C1_STEREO  = 1 << 1;  //0=mono,1=stereo
  static final int MU4_C0_OUT     = 1 << 0;  //0=in,1=out
  //  status bit3-0
  static final int MU4_S3_N32K    = 1 << 3;  //0=32k
  static final int MU4_S2_N44K    = 1 << 2;  //0=44k
  static final int MU4_S1_N48K    = 1 << 1;  //0=48k
  static final int MU4_S0_ERR     = 1 << 0;  //1=error
  //
  static int mu4PortUpper;  //command/status bit12-bit4
  static int mu4PortCommand;  //command bit3-0
  static int mu4PortStatus;  //status bit3-0

  //モード
  static final int MU4_MODE_M256 = 0;  //M256
  static final int MU4_MODE_OUT  = 1;  //OUT
  static final int MU4_MODE_IN   = 2;  //IN。未対応
  static int mu4Mode;

  //EXPCLとEXREQの出力許可
  static boolean mu4EnablePclReq;  //false=禁止,true=許可

  //データオシレータ周波数。1秒あたりのデータオシレータサイクル数
  static int mu4DataFrequency;  //12288000=16k/32k,16934400=22k/44k,18432000=24k/48k

  //データフレームサイクル数。1データフレームあたりのデータオシレータサイクル数
  static int mu4DataCycles;  //384=high,768=low

  //データサンプリング周波数。1秒あたりのデータフレーム数
  //  データオシレータ周波数/データフレームサイクル数
  static int mu4DataRate;  //16000,22050,24000,32000,44100,48000

  //データチャンネル数。1データフレームあたりの要素数
  static int mu4DataChannels;  //1=mono,2=stereo

  //ブロック周波数。1秒あたりのブロック数
  //  SoundSource.SND_BLOCK_FREQと同じ
  static final int MU4_BLOCK_RATE = 25;

  //1ブロックあたりのデータフレーム数
  //  データサンプリング周波数/ブロック周波数
  static int mu4DataFrames;

  //ラインサンプリング周波数。1秒あたりのラインフレーム数
  //  OPM.OPM_SAMPLE_FREQと同じ
  static final int MU4_LINE_RATE = 62500;

  //ラインチャンネル数。1ラインフレームあたりの要素数
  //  SoundSource.SND_CHANNELSと同じ
  static final int MU4_LINE_CHANNELS = 2;

  //1ブロックあたりのラインフレーム数
  //  ラインサンプリング周波数/ブロック周波数
  static final int MU4_LINE_FRAMES = MU4_LINE_RATE / MU4_BLOCK_RATE;

  //1ブロックあたりのライン要素数
  //  ラインチャンネル数*ラインフレーム数
  static final int MU4_LINE_ELEMENTS = MU4_LINE_CHANNELS * MU4_LINE_FRAMES;

  //シーケンス
  //  フレームまたは水平同期パルスの先頭を起点として
  //  EXPCLとEXREQをアサートまたはネゲートする時刻を
  //  データオシレータのサイクル数で記述したもの
  //  OUTのときはデータフレームサイクル数を周期として繰り返す
  static final int MU4_FALL_EXPCL = 0;  //EXPCLをアサートする
  static final int MU4_RISE_EXPCL = 1;  //EXPCLをネゲートする
  static final int MU4_FALL_EXREQ = 2;  //EXREQをアサートする
  static final int MU4_RISE_EXREQ = 3;  //EXREQをネゲートする
  //0xfd11 M256 16k mono
  //0xfd13 M256 16k stereo  不自然。シミュレータのバグ?
  //0xfd21 M256 22k mono
  //0xfd31 M256 24k mono
  static final int[] MU4_M256_LOW_MONO = {
    2, MU4_FALL_EXPCL,
    20, MU4_FALL_EXREQ,
    283, MU4_RISE_EXREQ,
    385, MU4_RISE_EXPCL,
  };
  //0xfd23 M256 22k stereo
  //0xfd33 M256 24k stereo
  static final int[] MU4_M256_LOW_STEREO = {
    2, MU4_FALL_EXPCL,
    283, MU4_RISE_EXREQ,
    385, MU4_RISE_EXPCL,
    404, MU4_FALL_EXREQ,
  };
  //0xfd91 M256 32k mono
  //0xfda1 M256 44k mono
  //0xfdb1 M256 48k mono
  static final int[] MU4_M256_HIGH_MONO = {
    2, MU4_FALL_EXPCL,
    10, MU4_FALL_EXREQ,
    142, MU4_RISE_EXREQ,
    193, MU4_RISE_EXPCL,
  };
  //0xfd93 M256 32k stereo
  //0xfda3 M256 44k stereo
  //0xfdb3 M256 48k stereo
  static final int[] MU4_M256_HIGH_STEREO = {
    2, MU4_FALL_EXPCL,
    10, MU4_FALL_EXREQ,
    142, MU4_RISE_EXREQ,
    193, MU4_RISE_EXPCL,
    202, MU4_FALL_EXREQ,
    334, MU4_RISE_EXREQ,
  };
  //0xfe5d OUT 16k mono
  //0xfe6d OUT 22k mono
  //0xfe7d OUT 24k mono
  static final int[] MU4_OUT_LOW_MONO = {
    0, MU4_FALL_EXPCL,
    379, MU4_FALL_EXREQ,
    384, MU4_RISE_EXPCL,
    667, MU4_RISE_EXREQ,
  };
  //0xfe5f OUT 16k stereo
  //0xfe6f OUT 22k stereo
  //0xfe7f OUT 24k stereo
  static final int[] MU4_OUT_LOW_STEREO = {
    0, MU4_FALL_EXPCL,
    283, MU4_RISE_EXREQ,
    379, MU4_FALL_EXREQ,
    384, MU4_RISE_EXPCL,
    667, MU4_RISE_EXREQ,
    763, MU4_FALL_EXREQ,
  };
  //0xfedd OUT 32k mono
  //0xfeed OUT 44k mono
  //0xfefd OUT 48k mono
  static final int[] MU4_OUT_HIGH_MONO = {
    0, MU4_FALL_EXPCL,
    190, MU4_FALL_EXREQ,
    192, MU4_RISE_EXPCL,
    334, MU4_RISE_EXREQ,
  };
  //0xfedf OUT 32k stereo
  //0xfeef OUT 44k stereo
  //0xfeff OUT 48k stereo
  static final int[] MU4_OUT_HIGH_STEREO = {
    0, MU4_FALL_EXPCL,
    142, MU4_RISE_EXREQ,
    190, MU4_FALL_EXREQ,
    192, MU4_RISE_EXPCL,
    334, MU4_RISE_EXREQ,
    382, MU4_FALL_EXREQ,
  };

  //シーケンスの配列
  static int[] mu4SequenceArray;

  //1ブロックの時間
  //  SoundSource.SND_BLOCK_TIMEと同じ
  static final long MU4_BLOCK_TIME = XEiJ.TMR_FREQ / MU4_BLOCK_RATE;

  //ブロック開始時刻
  //  SoundSource.sndBlockClock-MU4_BLOCK_TIME
  //  M256のときは水平同期パルス開始時刻
  static long mu4BlockTime;

  //フレーム番号
  //  現在のシーケンスのブロック内フレーム番号
  //  M256のときは0のみ
  static int mu4FrameNumber;

  //シーケンス内インデックス
  static int mu4SequenceIndex;

  //LRカウンタ
  //  シーケンス側がmu4WritePointerを決めるときに使う
  //  それ以外はmu4DataChannelsとmu4WritePointerの奇遇で判断すること
  static int mu4LRCounter;

  //データ書き込み位置
  //  常にステレオ
  //  -1=リクエストなし
  static int mu4WritePointer;

  //周波数変換前のデータの配列
  //  常にステレオ
  //  モノラルのデータをフレーム単位でパンできるようにするため
  static final int[] mu4DataArray = new int[2 * (1920 + 4)];

  //データ書き込みフラグ
  static boolean mu4DataWritten;

  //シーケンスティッカーの実行時刻
  static long mu4SequenceTime;



  static void mu4DumpVars () {
    System.out.printf ("mu4Mode=%d\n", mu4Mode);
    System.out.printf ("mu4EnablePclReq=%b\n", mu4EnablePclReq);
    System.out.printf ("mu4DataFrequency=%d\n", mu4DataFrequency);
    System.out.printf ("mu4DataCycles=%d\n", mu4DataCycles);
    System.out.printf ("mu4DataRate=%d\n", mu4DataRate);
    System.out.printf ("mu4DataChannels=%d\n", mu4DataChannels);
    System.out.printf ("mu4DataFrames=%d\n", mu4DataFrames);
    System.out.printf ("mu4BlockTime=%d\n", mu4BlockTime);
    System.out.printf ("mu4FrameNumber=%d\n", mu4FrameNumber);
    System.out.printf ("mu4SequenceIndex=%d\n", mu4SequenceIndex);
    System.out.printf ("mu4LRCounter=%d\n", mu4LRCounter);
    System.out.printf ("mu4WritePointer=%d\n", mu4WritePointer);
    System.out.printf ("mu4DataWritten=%b\n", mu4DataWritten);
  }  //mu4DumpVars

  //mu4SetCommand (d)
  //  コマンド書き込み
  static void mu4SetCommand (int d) {
    if (MU4_DEBUG) {
      System.out.printf ("%08x mu4SetCommand(0x%04x)\n", XEiJ.regPC0, d);
    }
    int prevMode = mu4Mode;
    int prevDataRate = mu4DataRate;
    int prevDataChannels = mu4DataChannels;
    int prevUpper = mu4PortUpper;
    int prevCommand = mu4PortCommand;
    mu4PortUpper = ((mu4PortUpper & (MU4_U_RESERVED |
                                     MU4_U12_TERR |
                                     MU4_U11_EXPCL |
                                     MU4_U10_EXREQ)) |
                    (d & (MU4_U9_M256 |
                          MU4_U8_ACKIG |
                          MU4_U7_CLKRATE |
                          MU4_U6_INSEL |
                          MU4_U54_CLKSEL)));
    mu4PortCommand = d & (MU4_C3_RG |
                          MU4_C2_LG |
                          MU4_C1_STEREO |
                          MU4_C0_OUT);
    //
    //EXPCLとEXREQの出力許可
    mu4EnablePclReq = (mu4PortUpper & (MU4_U9_M256 | MU4_U8_ACKIG)) != 0;
    //下位バイトが変化していないときは何もしない
    if ((((mu4PortUpper | mu4PortCommand) ^ (prevUpper | prevCommand)) & 0xff) == 0) {
      return;
    }
    //モード
    mu4Mode = ((mu4PortUpper & MU4_U9_M256) == 0 ? MU4_MODE_M256 :
               (mu4PortCommand & MU4_C0_OUT) != 0 ? MU4_MODE_OUT :
               MU4_MODE_IN);
    //データオシレータ周波数
    mu4DataFrequency = ((mu4PortUpper & MU4_U54_CLKSEL) == MU4_U54_32K ? 12288000 :
                        (mu4PortUpper & MU4_U54_CLKSEL) != MU4_U54_48K ? 16934400 :
                        18432000);
    //データフレームサイクル数
    mu4DataCycles = (mu4PortUpper & MU4_U7_CLKRATE) == 0 ? 768 : 384;
    //データサンプリング周波数
    mu4DataRate = mu4DataFrequency / mu4DataCycles;
    //データチャンネル数
    mu4DataChannels = (mu4PortCommand & MU4_C1_STEREO) == 0 ? 1 : 2;
    //
    if (mu4Mode != prevMode ||  //モードが変わったか
        mu4DataRate != prevDataRate ||  //サンプリング周波数が変わったか
        mu4DataChannels != prevDataChannels) {  //データチャンネル数が変わったとき
      if (mu4Mode == MU4_MODE_M256) {  //M256
        //ここではシーケンスを開始しない
        mu4SequenceArray = null;
      } else if (mu4Mode == MU4_MODE_OUT) {  //OUT
        //1ブロックあたりのデータフレーム数
        mu4DataFrames = mu4DataRate / MU4_BLOCK_RATE;
        //シーケンスの配列
        mu4SequenceArray = (mu4DataChannels == 1 ?  //mono
                            mu4DataRate < 32000 ? MU4_OUT_LOW_MONO : MU4_OUT_HIGH_MONO
                            :  //stereo
                            mu4DataRate < 32000 ? MU4_OUT_LOW_STEREO : MU4_OUT_HIGH_STEREO);
        //ブロック開始時刻
        mu4BlockTime = SoundSource.sndBlockClock - MU4_BLOCK_TIME;
        //フレーム番号
        //  max(0,min(フレーム数,ceil((現在時刻-ブロック開始時刻)*フレーム数/ブロック時間)))
        mu4FrameNumber =
          Math.max (0,
                    Math.min (mu4DataFrames,
                              (int) (((XEiJ.mpuClockTime - mu4BlockTime) * mu4DataFrames +
                                      (MU4_BLOCK_TIME - 1)) / MU4_BLOCK_TIME)));
        //シーケンス内インデックス
        mu4SequenceIndex = 0;
        //LRカウンタ
        mu4LRCounter = 0;
        //データ書き込み位置
        mu4WritePointer = -1;
        if (mu4DataRate != prevDataRate ||  //データサンプリング周波数が変わったか
            mu4DataChannels != prevDataChannels) {  //データチャンネル数が変わったとき
          //データの配列をゼロクリアする
          Arrays.fill (mu4DataArray, 0, 2 * (mu4DataFrames + 4), 0);
        }
        //データ書き込みフラグ
        mu4DataWritten = false;
        if (MU4_DEBUG) {
          mu4DumpVars ();
        }
        //ノイズ抑制
        mu4AttackIndexLeft = 1;
        mu4AttackIndexRight = 1;
        mu4NaiKamoLeft = true;
        mu4NaiKamoRight = true;
        mu4NaiDesuLeft = true;
        mu4NaiDesuRight = true;
        //シーケンスティッカーを予約する
        TickerQueue.tkqAdd (
          mu4SequenceTicker,  //シーケンスティッカー
          mu4SequenceTime = mu4BlockTime +  //ブロック開始時刻
          (mu4DataCycles * mu4FrameNumber + mu4SequenceArray[mu4SequenceIndex++]) *  //ブロック内サイクル数
          XEiJ.TMR_FREQ / mu4DataFrequency);  //サイクル数を時刻に変換する。順序に注意
      } else {  //IN
        //!!! 未対応
        mu4SequenceArray = null;
      }
    }
  }  //mu4SetCommand

  //mu4SequenceTicker
  //  シーケンスティッカー
  static final TickerQueue.Ticker mu4SequenceTicker = new TickerQueue.Ticker () {
    @Override protected void tick () {
      if (mu4SequenceArray == null) {
        return;
      }
      switch (mu4SequenceArray[mu4SequenceIndex++]) {  //コマンド
      case MU4_FALL_EXPCL:
        mu4FallExpcl ();
        break;
      case MU4_RISE_EXPCL:
        mu4RiseExpcl ();
        break;
      case MU4_FALL_EXREQ:
        if (mu4Mode == MU4_MODE_OUT) {
          //データ書き込み位置
          mu4WritePointer = 2 * (mu4FrameNumber + 3) + mu4LRCounter;
          //LRカウンタ
          mu4LRCounter ^= 1;
          //ノイズ抑制
          mu4DataArray[mu4WritePointer] = (int) (mu4DataArray[mu4WritePointer - 2] * MU4_DECAY_RATIO);  //端数は切り捨てる。roundだと1→1で0にならない
          if (mu4DataChannels == 1 ||  //mono
              (mu4WritePointer & 1) == 0) {  //left
            mu4NaiDesuLeft = mu4NaiDesuLeft || mu4NaiKamoLeft;
            mu4NaiKamoLeft = true;
          }
          if (mu4DataChannels == 1 ||  //mono
              (mu4WritePointer & 1) != 0) {  //right
            mu4NaiDesuRight = mu4NaiDesuRight || mu4NaiKamoRight;
            mu4NaiKamoRight = true;
          }
        }
        mu4FallExreq (mu4SequenceTime);
        break;
      case MU4_RISE_EXREQ:
        mu4RiseExreq ();
        break;
      }
      if (mu4SequenceArray.length <= mu4SequenceIndex) {  //シーケンス終了
        if (mu4Mode == MU4_MODE_M256) {  //M256のとき
          mu4SequenceArray = null;  //ここでは次のシーケンスを開始しない
          return;
        }
        //OUT
        mu4FrameNumber++;
        mu4SequenceIndex = 0;
        mu4LRCounter = 0;
      }
      //シーケンスティッカーを予約する
      TickerQueue.tkqAdd (
        mu4SequenceTicker,  //シーケンスティッカー
        mu4SequenceTime = mu4BlockTime +  //ブロック開始時刻
        (mu4DataCycles * mu4FrameNumber + mu4SequenceArray[mu4SequenceIndex++]) *  //ブロック内サイクル数
        XEiJ.TMR_FREQ / mu4DataFrequency);  //サイクル数を時刻に変換する。順序に注意
    }  //tick
  };  //mu4Ticker

  //mu4FallExpcl ()
  //  EXPCLをアサートする
  static void mu4FallExpcl () {
    if ((mu4PortUpper & MU4_U11_EXPCL) != 0) {
      mu4PortUpper &= ~MU4_U11_EXPCL;
      if (mu4EnablePclReq) {
        HD63450.dmaFallPCL (2);
      }
    }
  }  //mu4FallExpcl

  //mu4RiseExpcl ()
  //  EXPCLをネゲートする
  static void mu4RiseExpcl () {
    if ((mu4PortUpper & MU4_U11_EXPCL) == 0) {
      mu4PortUpper |= MU4_U11_EXPCL;
      if (mu4EnablePclReq) {
        HD63450.dmaRisePCL (2);
      }
    }
  }  //mu4RiseExpcl

  //mu4FallExreq (time)
  //  EXREQをアサートする
  static void mu4FallExreq (long time) {
    if ((mu4PortUpper & MU4_U10_EXREQ) != 0) {
      mu4PortUpper &= ~MU4_U10_EXREQ;
      if (mu4EnablePclReq) {
        HD63450.dmaFallREQ (2, time);
      }
    }
  }  //mu4FallExreq

  //mu4RiseExreq ()
  //  EXREQをネゲートする
  static void mu4RiseExreq () {
    if ((mu4PortUpper & MU4_U10_EXREQ) == 0) {
      mu4PortUpper |= MU4_U10_EXREQ;
      if (mu4EnablePclReq) {
        HD63450.dmaRiseREQ (2);
      }
    }
  }  //mu4RiseExreq



  //エルミート補間
  static Hermite mu4Hermite640;
  static Hermite mu4Hermite882;
  static Hermite mu4Hermite960;
  static Hermite mu4Hermite1280;
  static Hermite mu4Hermite1764;
  static Hermite mu4Hermite1920;

  //class Hermite
  //  エルミート補間
  static class Hermite {
    int inputFrames, outputFrames;
    int inputFactor, outputFactor;
    float[] coeffArray;
    int[] indexArray;
    Hermite (int inputFrames, int outputFrames) {
      this.inputFrames = inputFrames;
      this.outputFrames = outputFrames;
      int g = gcd (inputFrames, outputFrames);
      inputFactor = inputFrames / g;
      outputFactor = outputFrames / g;
      coeffArray = new float[4 * outputFactor];
      indexArray = new int[outputFactor];
      int inputIndex1 = 0;
      int ratio1 = 0;
      for (int outputIndex = 0; outputIndex < outputFactor; outputIndex++) {
        int inputIndex0 = inputIndex1;
        int ratio0 = ratio1;
        ratio1 += inputFactor;
        while (outputFactor <= ratio1) {
          ratio1 -= outputFactor;
          inputIndex1++;
        }
        //
        //  エルミート補間の係数を求める
        //
        //  f(0)=p0
        //  f(1)=p1
        //  f'(0)=(p1-pm)/2
        //  f'(1)=(p2-p0)/2
        //  を満たす3次関数を作る
        //
        //  f(x)=a*x^3+b*x^2+c*x+d
        //  f'(x)=3*a*x^2+2*b*x+c
        //
        //  f:=sub(first(solve({sub(x=0,a*x^3+b*x^2+c*x+d)=p0,
        //                      sub(x=1,a*x^3+b*x^2+c*x+d)=p1,
        //                      sub(x=0,3*a*x^2+2*b*x+c)=(p1-pm)/2,
        //                      sub(x=1,3*a*x^2+2*b*x+c)=(p2-p0)/2},
        //                     {a,b,c,d})),
        //         a*x^3+b*x^2+c*x+d);
        //  cm:=part(coeff(f,pm),2);
        //  c0:=part(coeff(f,p0),2);
        //  c1:=part(coeff(f,p1),2);
        //  c2:=part(coeff(f,p2),2);
        //  f-(cm*pm+c0*p0+c1*p1+c2*p2);
        //
        //  f(x)=cm*pm+c0*p0+c1*p1+c2*p2
        //  cm=(-x^3+2*x^2-x)/2
        //  c0=(3*x^3-5*x^2+2)/2
        //  c1=(-3*x^3+4*x^2+x)/2
        //  c2=(x^3-x^2)/2
        //
        //  周波数変換ではxは高々4桁通りなのでx毎に{cm,c0,c1,c2}をあらかじめ計算しておける
        //  乗算は4回で済むので(見た目よりは)高速に補間できる
        //
        double x = (double) ratio0 / (double) outputFactor;
        coeffArray[4 * outputIndex] = (float) (((-0.5 * x + 1.0) * x - 0.5) * x);  //cm
        coeffArray[4 * outputIndex + 1] = (float) ((1.5 * x - 2.5) * x * x + 1.0);  //c0
        coeffArray[4 * outputIndex + 2] = (float) (((-1.5 * x + 2.0) * x + 0.5) * x);  //c1
        coeffArray[4 * outputIndex + 3] = (float) ((0.5 * x - 0.5) * x * x);  //c2
        indexArray[outputIndex] = inputIndex0;
      }
    }
    void convert () {
      for (int inputIndex = 0, outputIndex = 0;
           inputIndex < inputFrames;
           inputIndex += inputFactor, outputIndex += outputFactor) {
        for (int od = 0; od < outputFactor; od++) {
          int ii = inputIndex + indexArray[od];
          int oi = outputIndex + od;
          float cm = coeffArray[4 * od];
          float c0 = coeffArray[4 * od + 1];
          float c1 = coeffArray[4 * od + 2];
          float c2 = coeffArray[4 * od + 3];
          mu4Buffer[2 * oi + 0] = Math.round (cm * mu4DataArray[2 * ii + 0] +
                                              c0 * mu4DataArray[2 * ii + 2] +
                                              c1 * mu4DataArray[2 * ii + 4] +
                                              c2 * mu4DataArray[2 * ii + 6]);
          mu4Buffer[2 * oi + 1] = Math.round (cm * mu4DataArray[2 * ii + 1] +
                                              c0 * mu4DataArray[2 * ii + 3] +
                                              c1 * mu4DataArray[2 * ii + 5] +
                                              c2 * mu4DataArray[2 * ii + 7]);
        }
      }
    }  //convert
    int gcd (int x, int y) {
      while (y != 0) {
        int t = x % y;
        x = y;
        y = t;
      }
      return x;
    }  //gcd
  }  //class Hermite



  //ノイズ抑制
  //  アタックとディケイに5ms~10msの時間をかけることでノイズを抑制する
  //  アタックは三角関数、ディケイは指数関数を使う
  //  通常のデータはノイズを抑制する加工がされているはず
  //  ディスクアクセス中にデータが欠落したときなどに効果がある
  static final float MU4_DECAY_RATIO = 0.97F;  //ディケイ率
  static final int MU4_ATTACK_FRAMES = 300;  //アタックにかけるフレーム数
  static final float mu4AttackArray[] = new float[MU4_ATTACK_FRAMES + 1];  //アタックの係数の配列。(1+sin(-pi/2..pi/2))/2
  static int mu4AttackIndexLeft;  //mu4AttackArrayのインデックス
  static int mu4AttackIndexRight;
  static boolean mu4NaiKamoLeft;  //データないかも
  static boolean mu4NaiKamoRight;
  static boolean mu4NaiDesuLeft;  //データないです
  static boolean mu4NaiDesuRight;



}  //class MercuryUnit