OPMLog.java
     1: //========================================================================================
     2: //  OPMLog.java
     3: //    en:OPM log
     4: //    ja:OPMログ
     5: //  Copyright (C) 2003-2024 Makoto Kamada
     6: //
     7: //  This file is part of the XEiJ (X68000 Emulator in Java).
     8: //  You can use, modify and redistribute the XEiJ if the conditions are met.
     9: //  Read the XEiJ License for more details.
    10: //  https://stdkmd.net/xeij/
    11: //========================================================================================
    12: 
    13: package xeij;
    14: 
    15: import java.awt.*;  //Graphics
    16: import java.awt.event.*;  //ActionEvent,ActionListener
    17: import java.awt.geom.*;  //Line2D
    18: import java.awt.image.*;  //BufferedImage
    19: import java.io.*;  //File
    20: import java.util.*;  //TimerTask
    21: import javax.sound.sampled.*;  //AudioFormat,AudioSystem,SourceDataLine
    22: import javax.swing.*;  //JPanel
    23: import javax.swing.border.*;  //EmptyBorder
    24: import javax.swing.event.*;  //ChangeEvent,ChangeListener
    25: 
    26: public class OPMLog {
    27: 
    28:   public static final boolean OLG_ON = true;
    29: 
    30:   //log2(3.58/4.00)
    31:   public static final double LOG2_358_400 = -0.16004041251046827164899520744140640962;
    32: 
    33:   public static final int OLG_BUFFER_SIZE = 1024 * 1024;
    34:   public static final long OLG_UNIT = 1000000L;  //経過時刻の単位の逆数。1000000=1us
    35:   public static final int[] olgBuffer = new int[OLG_BUFFER_SIZE];  //バッファ。[0]=経過時刻(OLG_UNIT),[1]=アドレス<<8|データ or 0x10000=CSMKON or -1=END
    36: 
    37:   //記録
    38:   public static final int OLG_LIMIT_S = 900;  //最大記録時間(s)
    39:   public static boolean olgRecording;  //記録中
    40:   public static int olgLength;  //使用したバッファの長さ。エンドコードを含む
    41:   public static long olgStartTimePS;  //記録開始時刻(TMR_FREQ)
    42:   public static int olgCounter;  //カウンタ
    43: 
    44:   //ページ
    45:   public static final int[] OLG_US_PER_PX = new int[] {
    46:     1,  //1us/px 1ms/1000px
    47:     2,  //2us/px 2ms/1000px
    48:     5,  //5us/px 5ms/1000px
    49:     10,  //10us/px 10ms/1000px
    50:     20,  //20us/px 20ms/1000px
    51:     50,  //50us/px 50ms/1000px
    52:     100,  //100us/px 100ms/1000px
    53:     200,  //200us/px 200ms/1000px
    54:     500,  //500us/px 500ms/1000px
    55:     1000,  //1ms/px 1s/1000px
    56:     2000,  //2ms/px 2s/1000px
    57:     5000,  //5ms/px 5s/1000px
    58:     10000,  //10ms/px 10s/1000px
    59:     20000,  //20ms/px 20s/1000px
    60:     50000,  //50ms/px 50s/1000px
    61:     100000,  //100ms/px 100s/1000px
    62:   };
    63:   public static final int OLG_RANGE_COUNT = OLG_US_PER_PX.length;
    64:   public static int olgRangeIndex;  //OLG_RANGE_TEXTのインデックス
    65:   public static int olgUsPerPx;  //1pxあたりの時間
    66:   public static int olgRangeUs;  //表示範囲の時間
    67:   public static int olgStartUs;  //表示範囲の開始時刻
    68:   public static int olgEndUs;  //表示範囲の終了時刻
    69:   public static int olgPxToUs (int px) {
    70:     return olgStartUs + olgUsPerPx * px;
    71:   }
    72:   public static int olgUsToPx (int us) {
    73:     return (us - olgStartUs) / olgUsPerPx;
    74:   }
    75:   public static final boolean[] olgChannelMask = new boolean[8];
    76: 
    77:   //キャレット
    78:   public static int olgCaretPx;  //キャレットのX座標
    79:   public static int olgCaretUs;  //キャレットの時刻
    80: 
    81:   //マスク
    82:   public static int olgMaskLeftUs;  //左側マスクの終了時刻。-1=なし
    83:   public static int olgMaskRightUs;  //右側マスクの開始時刻。-1=なし
    84: 
    85:   public static java.util.Timer olgTimer;
    86:   public static TimerTask olgForcedTerminationTask;  //強制終了タスク
    87: 
    88:   public static File olgLastMMLFile;
    89:   public static javax.swing.filechooser.FileFilter olgMMLFilter;
    90:   public static File olgLastFile;
    91:   public static javax.swing.filechooser.FileFilter olgFilter;
    92: 
    93:   //ストローク
    94:   public static BasicStroke olgSolidStroke;
    95:   public static BasicStroke olgDashStroke;
    96:   public static BasicStroke olgWaveStroke;
    97: 
    98:   //ペイント
    99:   public static TexturePaint olgMaskPaint;
   100: 
   101:   //楽譜
   102:   public static final int OLG_SCORE_WIDTH = 1200;  //描画領域の幅
   103:   public static final int OLG_SCORE_HEIGHT = 388;  //描画領域の高さ。64*12/6208*485=60。64*12/6208*388=48。12で割り切れてちょうどよい
   104:   public static final int OLG_SCORE_H_MARGIN = 10;  //左右のマージン
   105:   public static final int OLG_SCORE_V_MARGIN = 6;  //上下のマージン
   106:   public static final int OLG_SCORE_IMAGE_WIDTH = OLG_SCORE_H_MARGIN + OLG_SCORE_WIDTH + OLG_SCORE_H_MARGIN;  //イメージの幅
   107:   public static final int OLG_WAVE_RADIUS = 48;
   108:   public static final int OLG_WAVE_Y1 = OLG_SCORE_V_MARGIN + OLG_SCORE_HEIGHT;
   109:   public static final int OLG_WAVE_Y0 = OLG_WAVE_Y1 + OLG_WAVE_RADIUS;
   110:   public static final int OLG_WAVE_YM1 = OLG_WAVE_Y0 + OLG_WAVE_RADIUS;
   111:   public static final int OLG_SCORE_IMAGE_HEIGHT = OLG_WAVE_YM1 + OLG_SCORE_V_MARGIN;  //イメージの高さ
   112:   public static final int OLG_KON1ST_HEIGHT = 9;  //バーのキーオンしたときの高さ(奇数)
   113:   public static final int OLG_KON2ND_HEIGHT = 7;  //バーのキーオンしたままKC,KFが変更されたときの高さ(奇数)
   114:   public static final int OLG_BAR_HEIGHT = 3;  //バーの高さ(奇数)
   115:   public static final Color[] OLG_KON_COLOR = new Color[] {
   116:     Color.getHSBColor (0.000F, 0.5F, 1.0F),
   117:     Color.getHSBColor (0.125F, 0.5F, 1.0F),
   118:     Color.getHSBColor (0.250F, 0.5F, 1.0F),
   119:     Color.getHSBColor (0.375F, 0.5F, 1.0F),
   120:     Color.getHSBColor (0.500F, 0.5F, 1.0F),
   121:     Color.getHSBColor (0.625F, 0.5F, 1.0F),
   122:     Color.getHSBColor (0.750F, 0.5F, 1.0F),
   123:     Color.getHSBColor (0.875F, 0.5F, 1.0F),
   124:   };  //チャンネル→バーの左端の色
   125:   public static final Color[] OLG_BAR_COLOR = new Color[] {
   126:     Color.getHSBColor (0.000F, 0.5F, 0.5F),
   127:     Color.getHSBColor (0.125F, 0.5F, 0.5F),
   128:     Color.getHSBColor (0.250F, 0.5F, 0.5F),
   129:     Color.getHSBColor (0.375F, 0.5F, 0.5F),
   130:     Color.getHSBColor (0.500F, 0.5F, 0.5F),
   131:     Color.getHSBColor (0.625F, 0.5F, 0.5F),
   132:     Color.getHSBColor (0.750F, 0.5F, 0.5F),
   133:     Color.getHSBColor (0.875F, 0.5F, 0.5F),
   134:   };  //チャンネル→バーの色
   135:   public static BufferedImage olgCanvasImage;
   136:   public static int[] olgCanvasBitmap;
   137:   public static ScrollCanvas olgCanvas;
   138:   public static int olgScaleShift;
   139: 
   140:   //音色
   141:   public static final char[] OLG_TONE_BASE = (
   142:     //         1111111111222222222233333
   143:     //1234567890123456789012345678901234
   144:     "   FC SL WA SY SP PD AD PS AS PN   " +
   145:     "   -- -- -- -- -- -- -- -- -- -- --" +
   146:     "   AR 1R 2R RR 1L TL KS ML T1 T2 AE" +
   147:     "M1 -- -- -- -- -- -- -- -- -- -- --" +
   148:     "C1 -- -- -- -- -- -- -- -- -- -- --" +
   149:     "M2 -- -- -- -- -- -- -- -- -- -- --" +
   150:     "C2 -- -- -- -- -- -- -- -- -- -- --" +
   151:     "   KC KF                           " +
   152:     "   -- --                           ").toCharArray ();
   153:   public static final int OLG_TONE_CHAR_WIDTH = 6;  //文字の幅
   154:   public static final int OLG_TONE_LINE_HEIGHT = 10;  //行の高さ
   155:   public static final int OLG_TONE_COLS = 35;  //桁数
   156:   public static final int OLG_TONE_ROWS = 9;  //行数
   157:   public static final int OLG_TONE_H_SPACE = 6;  //水平方向の間隔
   158:   public static final int OLG_TONE_V_SPACE = 10;  //垂直方向の間隔
   159:   public static final int OLG_TONE_WIDTH = (OLG_TONE_CHAR_WIDTH * OLG_TONE_COLS + OLG_TONE_H_SPACE) * 4 - OLG_TONE_H_SPACE;  //描画領域の幅
   160:   public static final int OLG_TONE_HEIGHT = (OLG_TONE_LINE_HEIGHT * OLG_TONE_ROWS + OLG_TONE_V_SPACE) * 2 - OLG_TONE_V_SPACE;  //描画領域の高さ
   161:   public static final int OLG_TONE_H_MARGIN = 6;  //左右のマージン
   162:   public static final int OLG_TONE_V_MARGIN = 10;  //上下のマージン
   163:   public static final int OLG_TONE_IMAGE_WIDTH = OLG_TONE_H_MARGIN + OLG_TONE_WIDTH + OLG_TONE_H_MARGIN;  //イメージの幅
   164:   public static final int OLG_TONE_IMAGE_HEIGHT = OLG_TONE_V_MARGIN + OLG_TONE_HEIGHT + OLG_TONE_V_MARGIN;  //イメージの高さ
   165:   public static BufferedImage olgToneImage;
   166:   public static int[] olgToneBitmap;
   167:   public static JPanel olgTonePanel;
   168: 
   169:   //ダンプ
   170:   //  OLG_SCORE_IMAGE_WIDTH=1220
   171:   //  OLG_SCORE_IMAGE_HEIGHT=400
   172:   //  OLG_TONE_IMAGE_WIDTH=870
   173:   //  OLG_TONE_IMAGE_HEIGHT=210
   174:   public static final int OLG_DUMP_TEXT_AREA_WIDTH = 340;
   175:   public static final int OLG_DUMP_TEXT_AREA_HEIGHT = 180;
   176:   public static ScrollTextArea olgDumpTextArea;
   177: 
   178:   //メニュー
   179:   public static JRadioButtonMenuItem[] olgScaleMenuItem;
   180: 
   181:   //ウインドウ
   182:   public static JFrame olgFrame;
   183:   //
   184:   public static JButton olgStartButton;
   185:   public static JButton olgEndButton;
   186:   //
   187:   public static JButton olgPlusButton;
   188:   public static JButton olgMinusButton;
   189: 
   190:   //再生
   191:   public static final int OPM_CHANNELS = 2;
   192:   public static YM2151 olgYM2151;
   193:   public static int[] opmBuffer;
   194: 
   195:   //  初期化
   196:   public static void olgInit () {
   197:     //olgBuffer = new int[OLG_BUFFER_SIZE];
   198:     //
   199:     olgRecording = false;
   200:     olgBuffer[0] = 0;
   201:     olgBuffer[1] = -1;
   202:     olgLength = 2;
   203:     olgStartTimePS = 0L;
   204:     olgCounter = 0;
   205:     //
   206:     olgRangeIndex = OLG_RANGE_COUNT - 1;
   207:     olgUsPerPx = OLG_US_PER_PX[olgRangeIndex];
   208:     olgRangeUs = olgUsPerPx * OLG_SCORE_WIDTH;
   209:     olgStartUs = 0;
   210:     olgEndUs = olgRangeUs;
   211:     //olgChannelMask = new boolean[8];
   212:     Arrays.fill (olgChannelMask, true);
   213:     //
   214:     olgCaretPx = 0;
   215:     olgCaretUs = 0;
   216:     //
   217:     olgMaskLeftUs = -1;
   218:     olgMaskRightUs = -1;
   219:     //
   220:     olgTimer = new java.util.Timer ();
   221:     olgForcedTerminationTask = null;
   222:     //
   223:     olgLastFile = new File (System.getProperty ("user.dir") + File.separator + "opmlog.dat");
   224:     olgFilter = new javax.swing.filechooser.FileFilter () {
   225:       @Override public boolean accept (File file) {
   226:         String name = file.getName ();
   227:         String lowerName = name.toLowerCase ();
   228:         return (file.isDirectory () ||
   229:                 (file.isFile () &&
   230:                  lowerName.endsWith (".dat")));
   231:       }
   232:       @Override public String getDescription () {
   233:         return (Multilingual.mlnJapanese ?
   234:                 "データファイル (*.dat)" :
   235:                 "Data files (*.dat)");
   236:       }
   237:     };
   238:     //
   239:     olgLastMMLFile = new File (System.getProperty ("user.dir") + File.separator + "a.opm");
   240:     olgMMLFilter = new javax.swing.filechooser.FileFilter () {
   241:       @Override public boolean accept (File file) {
   242:         String name = file.getName ();
   243:         String lowerName = name.toLowerCase ();
   244:         return (file.isDirectory () ||
   245:                 (file.isFile () &&
   246:                  (lowerName.endsWith (".opm") ||
   247:                   lowerName.endsWith (".zms"))));
   248:       }
   249:       @Override public String getDescription () {
   250:         return (Multilingual.mlnJapanese ?
   251:                 "MML ファイル (*.opm *.zms)" :
   252:                 "MML files (*.opm *.zms)");
   253:       }
   254:     };
   255: 
   256:     olgYM2151 = new YM2151 ();
   257:     opmBuffer = null;
   258:     olgYM2151.setListener (new YM2151.Listener () {
   259:       @Override public void timerA (int clocks) {
   260:       }
   261:       @Override public void timerB (int clocks) {
   262:       }
   263:       @Override public void busy (int clocks) {
   264:       }
   265:       @Override public boolean isBusy () {
   266:         return false;
   267:       }
   268:       @Override public void irq (boolean asserted) {
   269:       }
   270:       @Override public void control (int data) {
   271:       }
   272:       @Override public void written (int pointer, int address, int data) {
   273:       }
   274:     });
   275:     olgYM2151.reset ();
   276: 
   277:   }
   278: 
   279:   //  後始末
   280:   public static void olgTini () {
   281:     olgTimer.cancel ();
   282:   }
   283: 
   284:   public static void olgStart () {
   285:     if (RestorableFrame.rfmGetOpened (Settings.SGS_OLG_FRAME_KEY)) {
   286:       olgOpen ();
   287:     }
   288:   }
   289: 
   290:   public static void olgOpen () {
   291:     if (olgFrame == null) {
   292:       olgMakeFrame ();
   293:     }
   294:     XEiJ.pnlExitFullScreen (false);
   295:     olgFrame.setVisible (true);
   296:   }
   297: 
   298:   public static void olgMakeFrame () {
   299: 
   300:     //ストローク
   301:     olgSolidStroke = new BasicStroke (1.0F, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10.0F);
   302:     olgDashStroke = new BasicStroke (1.0F, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10.0F, new float[] { 1.0F, 1.0F }, 0.0F);
   303:     olgWaveStroke = new BasicStroke (1.0F, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 10.0F);
   304: 
   305:     //ペイント
   306:     {
   307:       BufferedImage image = new BufferedImage (4, 4, BufferedImage.TYPE_INT_ARGB);
   308:       int[] bitmap = ((DataBufferInt) image.getRaster ().getDataBuffer ()).getData ();
   309:       Arrays.fill (bitmap, 0x00000000);
   310:       bitmap[3] = 0x80808080;
   311:       bitmap[6] = 0x80808080;
   312:       bitmap[9] = 0x80808080;
   313:       bitmap[12] = 0x80808080;
   314:       olgMaskPaint = new TexturePaint (image, new Rectangle2D.Float (0.0F, 0.0F, 4.0F, 4.0F));
   315:     }
   316: 
   317:     //楽譜キャンバス
   318:     olgCanvasImage = new BufferedImage (OLG_SCORE_IMAGE_WIDTH, OLG_SCORE_IMAGE_HEIGHT, BufferedImage.TYPE_INT_RGB);
   319:     olgCanvasBitmap = ((DataBufferInt) olgCanvasImage.getRaster ().getDataBuffer ()).getData ();
   320:     olgCanvas = new ScrollCanvas (olgCanvasImage);
   321:     olgCanvas.setMargin (10, 10);
   322:     olgCanvas.setMatColor (new Color (LnF.lnfRGB[4]));
   323:     olgScaleShift = 0;
   324:     ComponentFactory.setPreferredSize (olgCanvas,
   325:                                        OLG_SCORE_IMAGE_WIDTH + 10 + 20 + 20,
   326:                                        OLG_SCORE_IMAGE_HEIGHT + 10 + 20 + 30);  //マージンとスクロールバーとタイトルボーダー
   327: 
   328:     //アクションリスナー
   329:     ActionListener listener = new ActionListener () {
   330:       @Override public void actionPerformed (ActionEvent ae) {
   331:         Object source = ae.getSource ();
   332:         String command = ae.getActionCommand ();
   333:         switch (command) {
   334:         case "Load MML":
   335:           olgLoadMML ();
   336:           break;
   337:         case "Load Log":
   338:           olgLoadLog ();
   339:           break;
   340:         case "Save Log":
   341:           olgSaveLog ();
   342:           break;
   343:         case "Quit":
   344:           olgFrame.setVisible (false);
   345:           break;
   346:           //
   347:         case "Adjust":
   348:           olgAdjust ();
   349:           break;
   350:           //
   351:         case "6.25%":
   352:           if (olgScaleShift != -4) {
   353:             olgCanvas.setScaleShift (-4);
   354:           }
   355:           break;
   356:         case "12.5%":
   357:           if (olgScaleShift != -3) {
   358:             olgCanvas.setScaleShift (-3);
   359:           }
   360:           break;
   361:         case "25%":
   362:           if (olgScaleShift != -2) {
   363:             olgCanvas.setScaleShift (-2);
   364:           }
   365:           break;
   366:         case "50%":
   367:           if (olgScaleShift != -1) {
   368:             olgCanvas.setScaleShift (-1);
   369:           }
   370:           break;
   371:         case "100%":
   372:           if (olgScaleShift != 0) {
   373:             olgCanvas.setScaleShift (0);
   374:           }
   375:           break;
   376:         case "200%":
   377:           if (olgScaleShift != 1) {
   378:             olgCanvas.setScaleShift (1);
   379:           }
   380:           break;
   381:         case "400%":
   382:           if (olgScaleShift != 2) {
   383:             olgCanvas.setScaleShift (2);
   384:           }
   385:           break;
   386:         case "800%":
   387:           if (olgScaleShift != 3) {
   388:             olgCanvas.setScaleShift (3);
   389:           }
   390:           break;
   391:         case "1600%":
   392:           if (olgScaleShift != 4) {
   393:             olgCanvas.setScaleShift (4);
   394:           }
   395:           break;
   396:           //
   397:         case "●":
   398:           olgRecordStart ();
   399:           break;
   400:         case "■":
   401:           olgRecordEnd ();
   402:           break;
   403:           //
   404:         case "+":
   405:           olgRangeIndex = Math.max (0, olgRangeIndex - 1);
   406:           ComponentFactory.setEnabled (olgPlusButton, 0 < olgRangeIndex);
   407:           ComponentFactory.setEnabled (olgMinusButton, olgRangeIndex < OLG_RANGE_COUNT - 1);
   408:           olgUsPerPx = OLG_US_PER_PX[olgRangeIndex];
   409:           olgRangeUs = olgUsPerPx * OLG_SCORE_WIDTH;
   410:           olgCaretUs = (olgCaretUs / olgUsPerPx) * olgUsPerPx;
   411:           olgStartUs = Math.max (0, olgCaretUs - olgUsPerPx * olgCaretPx);
   412:           olgCaretPx = (olgCaretUs - olgStartUs) / olgUsPerPx;
   413:           olgEndUs = olgStartUs + olgRangeUs;
   414:           olgPaint ();
   415:           break;
   416:         case "-":
   417:           olgRangeIndex = Math.min (OLG_RANGE_COUNT - 1, olgRangeIndex + 1);
   418:           ComponentFactory.setEnabled (olgPlusButton, 0 < olgRangeIndex);
   419:           ComponentFactory.setEnabled (olgMinusButton, olgRangeIndex < OLG_RANGE_COUNT - 1);
   420:           olgUsPerPx = OLG_US_PER_PX[olgRangeIndex];
   421:           olgRangeUs = olgUsPerPx * OLG_SCORE_WIDTH;
   422:           olgCaretUs = (olgCaretUs / olgUsPerPx) * olgUsPerPx;
   423:           olgStartUs = Math.max (0, olgCaretUs - olgUsPerPx * olgCaretPx);
   424:           olgCaretPx = (olgCaretUs - olgStartUs) / olgUsPerPx;
   425:           olgEndUs = olgStartUs + olgRangeUs;
   426:           olgPaint ();
   427:           break;
   428:         case "丨<":
   429:           olgCaretUs = 0;
   430:           olgStartUs = 0;
   431:           olgCaretPx = 0;
   432:           olgEndUs = olgStartUs + olgRangeUs;
   433:           olgPaint ();
   434:           break;
   435:         case "≪≪":
   436:           olgMoveCaret (-400);
   437:           break;
   438:         case "≪":
   439:           olgMoveCaret (-20);
   440:           break;
   441:         case "<":
   442:           olgMoveCaret (-1);
   443:           break;
   444:         case ">":
   445:           olgMoveCaret (1);
   446:           break;
   447:         case "≫":
   448:           olgMoveCaret (20);
   449:           break;
   450:         case "≫≫":
   451:           olgMoveCaret (400);
   452:           break;
   453:         case ">丨":
   454:           olgCaretUs = (Math.max (0, olgBuffer[olgLength - 2] - 1) / olgUsPerPx) * olgUsPerPx;
   455:           olgStartUs = Math.max (0, olgCaretUs - olgUsPerPx * olgCaretPx);
   456:           olgCaretPx = (olgCaretUs - olgStartUs) / olgUsPerPx;
   457:           olgEndUs = olgStartUs + olgRangeUs;
   458:           olgPaint ();
   459:           break;
   460:           //
   461:         case "0":
   462:           olgChannelMask[0] = ((JCheckBox) source).isSelected ();
   463:           olgMakeWaveData ();
   464:           olgPaint ();
   465:           break;
   466:         case "1":
   467:           olgChannelMask[1] = ((JCheckBox) source).isSelected ();
   468:           olgMakeWaveData ();
   469:           olgPaint ();
   470:           break;
   471:         case "2":
   472:           olgChannelMask[2] = ((JCheckBox) source).isSelected ();
   473:           olgMakeWaveData ();
   474:           olgPaint ();
   475:           break;
   476:         case "3":
   477:           olgChannelMask[3] = ((JCheckBox) source).isSelected ();
   478:           olgMakeWaveData ();
   479:           olgPaint ();
   480:           break;
   481:         case "4":
   482:           olgChannelMask[4] = ((JCheckBox) source).isSelected ();
   483:           olgMakeWaveData ();
   484:           olgPaint ();
   485:           break;
   486:         case "5":
   487:           olgChannelMask[5] = ((JCheckBox) source).isSelected ();
   488:           olgMakeWaveData ();
   489:           olgPaint ();
   490:           break;
   491:         case "6":
   492:           olgChannelMask[6] = ((JCheckBox) source).isSelected ();
   493:           olgMakeWaveData ();
   494:           olgPaint ();
   495:           break;
   496:         case "7":
   497:           olgChannelMask[7] = ((JCheckBox) source).isSelected ();
   498:           olgMakeWaveData ();
   499:           olgPaint ();
   500:           break;
   501:           //
   502:         case "From":
   503:           if (olgMaskRightUs == -1 || olgCaretUs < olgMaskRightUs) {
   504:             olgMaskLeftUs = olgCaretUs;
   505:             olgPaint ();
   506:           }
   507:           break;
   508:         case "To":
   509:           if (olgMaskLeftUs == -1 || olgMaskLeftUs < olgCaretUs) {
   510:             olgMaskRightUs = olgCaretUs;
   511:             olgPaint ();
   512:           }
   513:           break;
   514:         case "All":
   515:           olgMaskLeftUs = -1;
   516:           olgMaskRightUs = -1;
   517:           olgPaint ();
   518:           break;
   519:           //
   520:         case "\u25b7":
   521:           olgPlayStart ();
   522:           break;
   523:         case "□":
   524:           olgPlayEnd ();
   525:           break;
   526:         }
   527:       }
   528:     };
   529: 
   530:     //メニューバー
   531:     ButtonGroup zoomGroup = new ButtonGroup ();
   532:     olgScaleMenuItem = new JRadioButtonMenuItem[9];
   533:     JMenuBar menuBar = ComponentFactory.createMenuBar (
   534:       //ファイルメニュー
   535:       Multilingual.mlnText (
   536:         ComponentFactory.createMenu (
   537:           "File", 'F',
   538:           Multilingual.mlnText (ComponentFactory.createMenuItem ("Load MML", 'M', listener), "ja", "ロード MML"),
   539:           ComponentFactory.createHorizontalSeparator (),
   540:           Multilingual.mlnText (ComponentFactory.createMenuItem ("Load Log", 'L', listener), "ja", "ロード ログ"),
   541:           Multilingual.mlnText (ComponentFactory.createMenuItem ("Save Log", 'S', listener), "ja", "セーブ ログ"),
   542:           ComponentFactory.createHorizontalSeparator (),
   543:           Multilingual.mlnText (ComponentFactory.createMenuItem ("Quit", 'Q', listener), "ja", "終了")
   544:           ),
   545:         "ja", "ファイル"),
   546:       //編集メニュー
   547:       Multilingual.mlnText (
   548:         ComponentFactory.createMenu (
   549:           "Edit", 'E',
   550:           Multilingual.mlnText (ComponentFactory.createMenuItem ("Adjust", 'A', listener), "ja", "アジャスト")
   551:           ),
   552:         "ja", "編集"),
   553:       //表示メニュー
   554:       Multilingual.mlnText (
   555:         ComponentFactory.createMenu (
   556:           "Display", 'D',
   557:           olgScaleMenuItem[0] = ComponentFactory.createRadioButtonMenuItem (zoomGroup, olgScaleShift == -4, "6.25%", '1', listener),
   558:           olgScaleMenuItem[1] = ComponentFactory.createRadioButtonMenuItem (zoomGroup, olgScaleShift == -3, "12.5%", '2', listener),
   559:           olgScaleMenuItem[2] = ComponentFactory.createRadioButtonMenuItem (zoomGroup, olgScaleShift == -2, "25%", '3', listener),
   560:           olgScaleMenuItem[3] = ComponentFactory.createRadioButtonMenuItem (zoomGroup, olgScaleShift == -1, "50%", '4', listener),
   561:           olgScaleMenuItem[4] = ComponentFactory.createRadioButtonMenuItem (zoomGroup, olgScaleShift ==  0, "100%", '5', listener),
   562:           olgScaleMenuItem[5] = ComponentFactory.createRadioButtonMenuItem (zoomGroup, olgScaleShift ==  1, "200%", '6', listener),
   563:           olgScaleMenuItem[6] = ComponentFactory.createRadioButtonMenuItem (zoomGroup, olgScaleShift ==  2, "400%", '7', listener),
   564:           olgScaleMenuItem[7] = ComponentFactory.createRadioButtonMenuItem (zoomGroup, olgScaleShift ==  3, "800%", '8', listener),
   565:           olgScaleMenuItem[8] = ComponentFactory.createRadioButtonMenuItem (zoomGroup, olgScaleShift ==  4, "1600%", '9', listener)
   566:           ),
   567:         "ja", "表示")
   568:       );
   569: 
   570:     //スケールシフトリスナー
   571:     olgCanvas.addScaleShiftListener (new ScrollCanvas.ScaleShiftListener () {
   572:       @Override public void scaleShiftChanged (int scaleShift) {
   573:         if (-4 <= scaleShift && scaleShift <= 4) {
   574:           olgScaleShift = scaleShift;
   575:           olgScaleMenuItem[4 + scaleShift].setSelected (true);
   576:         }
   577:       }
   578:     });
   579: 
   580:     //マウスリスナー
   581:     olgCanvas.addMouseListener (new MouseAdapter () {
   582:       @Override public void mouseClicked (MouseEvent me) {
   583:         MouseEvent2D me2D = (MouseEvent2D) me;
   584:         int x = (int) me2D.getX2D ();
   585:         int y = (int) me2D.getY2D ();
   586:         if (OLG_SCORE_H_MARGIN <= x && x < OLG_SCORE_H_MARGIN + OLG_SCORE_WIDTH &&
   587:             OLG_SCORE_V_MARGIN <= y && y < OLG_WAVE_YM1) {
   588:           x -= OLG_SCORE_H_MARGIN;
   589:           y -= OLG_SCORE_V_MARGIN;
   590:           //クリックされた位置にキャレットを動かす
   591:           olgCaretUs = Math.min ((Math.max (0, olgBuffer[olgLength - 2] - 1) / olgUsPerPx) * olgUsPerPx,
   592:                                  olgPxToUs (x));
   593:           olgCaretPx = olgUsToPx (olgCaretUs);
   594:           if (me.getClickCount () == 2) {  //ダブルクリックのとき
   595:             //センタリングする
   596:             olgStartUs = Math.max (0, olgStartUs + olgUsPerPx * (olgCaretPx - (OLG_SCORE_WIDTH >> 1)));
   597:             olgCaretPx = (olgCaretUs - olgStartUs) / olgUsPerPx;
   598:             olgEndUs = olgStartUs + olgRangeUs;
   599:           }
   600:           olgPaint ();
   601:         }
   602:       }
   603:     });
   604: 
   605:     //キーリスナー
   606:     olgCanvas.setFocusable (true);
   607:     olgCanvas.addKeyListener (new KeyAdapter () {
   608:       @Override public void keyPressed (KeyEvent ke) {
   609:         int keyCode = ke.getKeyCode ();
   610:         int modifierEx = ke.getModifiersEx ();
   611:         boolean shift = (modifierEx & InputEvent.SHIFT_DOWN_MASK) == InputEvent.SHIFT_DOWN_MASK;
   612:         boolean ctrl = (modifierEx & InputEvent.CTRL_DOWN_MASK) == InputEvent.CTRL_DOWN_MASK;
   613:         int dxPx = 0;
   614:         switch (keyCode) {
   615:         case KeyEvent.VK_LEFT:
   616:           dxPx = ctrl ? -100 : shift ? -10 : -1;
   617:           break;
   618:         case KeyEvent.VK_RIGHT:
   619:           dxPx = ctrl ? 100 : shift ? 10 : 1;
   620:           break;
   621:         }
   622:         if (dxPx != 0) {
   623:           olgMoveCaret (dxPx);
   624:           ke.consume ();
   625:         }
   626:       }
   627:     });
   628: 
   629:     //録音ボックス
   630:     Box recordBox = Multilingual.mlnTitledBorder (
   631:       ComponentFactory.setTitledLineBorder (
   632:         ComponentFactory.createHorizontalBox (
   633:           olgStartButton = ComponentFactory.setEnabled (
   634:             ComponentFactory.createButton ("●", listener),
   635:             true),
   636:           olgEndButton = ComponentFactory.setEnabled (
   637:             ComponentFactory.createButton ("■", listener),
   638:             false)),
   639:         "Record"),
   640:       "ja", "録音");
   641: 
   642:     //拡大ボックス
   643:     Box zoomBox = Multilingual.mlnTitledBorder (
   644:       ComponentFactory.setTitledLineBorder (
   645:         ComponentFactory.createHorizontalBox (
   646:           olgPlusButton =
   647:           ComponentFactory.setEnabled (
   648:             ComponentFactory.createButton ("+", listener),
   649:             0 < olgRangeIndex),
   650:           olgMinusButton =
   651:           ComponentFactory.setEnabled (
   652:             ComponentFactory.createButton ("-", listener),
   653:             olgRangeIndex < OLG_RANGE_COUNT - 1)),
   654:         "Zoom"),
   655:       "ja", "拡大");
   656: 
   657:     //移動ボックス
   658:     Box moveBox = Multilingual.mlnTitledBorder (
   659:       ComponentFactory.setTitledLineBorder (
   660:         ComponentFactory.createHorizontalBox (
   661:           ComponentFactory.createButton ("丨<", listener),
   662:           ComponentFactory.createButton ("≪≪", listener),
   663:           ComponentFactory.createButton ("≪", listener),
   664:           ComponentFactory.createButton ("<", listener),
   665:           ComponentFactory.createButton (">", listener),
   666:           ComponentFactory.createButton ("≫", listener),
   667:           ComponentFactory.createButton ("≫≫", listener),
   668:           ComponentFactory.createButton (">丨", listener)),
   669:         "Move"),
   670:       "ja", "移動");
   671: 
   672:     //チャンネルボックス
   673:     Box channelBox = Multilingual.mlnTitledBorder (
   674:       ComponentFactory.setTitledLineBorder (
   675:         ComponentFactory.createHorizontalBox (
   676:           ComponentFactory.setEmptyBorder (
   677:             ComponentFactory.setColor (ComponentFactory.createCheckBox (olgChannelMask[0], "0", listener), Color.black, OLG_KON_COLOR[0]),
   678:             4, 4, 4, 4),
   679:           ComponentFactory.setEmptyBorder (
   680:             ComponentFactory.setColor (ComponentFactory.createCheckBox (olgChannelMask[1], "1", listener), Color.black, OLG_KON_COLOR[1]),
   681:             4, 4, 4, 4),
   682:           ComponentFactory.setEmptyBorder (
   683:             ComponentFactory.setColor (ComponentFactory.createCheckBox (olgChannelMask[2], "2", listener), Color.black, OLG_KON_COLOR[2]),
   684:             4, 4, 4, 4),
   685:           ComponentFactory.setEmptyBorder (
   686:             ComponentFactory.setColor (ComponentFactory.createCheckBox (olgChannelMask[3], "3", listener), Color.black, OLG_KON_COLOR[3]),
   687:             4, 4, 4, 4),
   688:           ComponentFactory.setEmptyBorder (
   689:             ComponentFactory.setColor (ComponentFactory.createCheckBox (olgChannelMask[4], "4", listener), Color.black, OLG_KON_COLOR[4]),
   690:             4, 4, 4, 4),
   691:           ComponentFactory.setEmptyBorder (
   692:             ComponentFactory.setColor (ComponentFactory.createCheckBox (olgChannelMask[5], "5", listener), Color.black, OLG_KON_COLOR[5]),
   693:             4, 4, 4, 4),
   694:           ComponentFactory.setEmptyBorder (
   695:             ComponentFactory.setColor (ComponentFactory.createCheckBox (olgChannelMask[6], "6", listener), Color.black, OLG_KON_COLOR[6]),
   696:             4, 4, 4, 4),
   697:           ComponentFactory.setEmptyBorder (
   698:             ComponentFactory.setColor (ComponentFactory.createCheckBox (olgChannelMask[7], "7", listener), Color.black, OLG_KON_COLOR[7]),
   699:             4, 4, 4, 4)
   700:           ),
   701:         "Channel"),
   702:       "ja", "チャンネル");
   703: 
   704:     //範囲ボックス
   705:     Box rangeBox = Multilingual.mlnTitledBorder (
   706:       ComponentFactory.setTitledLineBorder (
   707:         ComponentFactory.createHorizontalBox (
   708:           Multilingual.mlnText (ComponentFactory.createButton ("From", listener), "ja", "ここから"),
   709:           Multilingual.mlnText (ComponentFactory.createButton ("To", listener), "ja", "ここまで"),
   710:           Multilingual.mlnText (ComponentFactory.createButton ("All", listener), "ja", "全部")),
   711:         "Range"),
   712:       "ja", "範囲");
   713: 
   714:     //再生ボックス
   715:     Box playBox = Multilingual.mlnTitledBorder (
   716:       ComponentFactory.setTitledLineBorder (
   717:         ComponentFactory.createHorizontalBox (
   718:           ComponentFactory.createButton ("\u25b7", listener),  //右向き白三角
   719:           ComponentFactory.createButton ("□", listener)),
   720:         "Play"),
   721:       "ja", "再生");
   722: 
   723:     //ツールボックス
   724:     Box toolBox = ComponentFactory.createHorizontalBox (
   725:       recordBox,
   726:       zoomBox,
   727:       moveBox,
   728:       channelBox,
   729:       rangeBox,
   730:       playBox,
   731:       Box.createHorizontalGlue ());
   732: 
   733:     //音色パネル
   734:     olgToneImage = new BufferedImage (OLG_TONE_IMAGE_WIDTH, OLG_TONE_IMAGE_HEIGHT, BufferedImage.TYPE_INT_RGB);
   735:     olgToneBitmap = ((DataBufferInt) olgToneImage.getRaster ().getDataBuffer ()).getData ();
   736:     olgTonePanel = ComponentFactory.setPreferredSize (
   737:       new JPanel () {
   738:         @Override public void paintComponent (Graphics g) {
   739:           super.paintComponent (g);
   740:           g.drawImage (olgToneImage, 0, 0, null);
   741:         }
   742:       },
   743:       OLG_TONE_IMAGE_WIDTH,
   744:       OLG_TONE_IMAGE_HEIGHT);
   745: 
   746:     //ダンプテキストエリア
   747:     olgDumpTextArea = ComponentFactory.createScrollTextArea ("",
   748:                                                              OLG_DUMP_TEXT_AREA_WIDTH,
   749:                                                              OLG_DUMP_TEXT_AREA_HEIGHT);
   750:     olgDumpTextArea.setLineWrap (true);
   751: 
   752:     //ウインドウ
   753:     olgFrame = Multilingual.mlnTitle (
   754:       ComponentFactory.createRestorableSubFrame (
   755:         Settings.SGS_OLG_FRAME_KEY,
   756:         "OPM log",
   757:         menuBar,
   758:         ComponentFactory.createVerticalBox (
   759:           toolBox,
   760:           Multilingual.mlnTitledBorder (
   761:             ComponentFactory.setTitledLineBorder (olgCanvas, "Score and wave"),
   762:             "ja", "楽譜と波形"),
   763:           ComponentFactory.createHorizontalBox (
   764:             Multilingual.mlnTitledBorder (
   765:               ComponentFactory.setTitledLineBorder (olgDumpTextArea, "Log"),
   766:               "ja", "ログ"),
   767:             Multilingual.mlnTitledBorder (
   768:               ComponentFactory.setTitledLineBorder (
   769:                 ComponentFactory.createHorizontalBox (olgTonePanel), "Tone"),
   770:               "ja", "音色")))),
   771:       "ja", "OPM ログ");
   772: 
   773:     olgPaint ();
   774: 
   775:   }  //olgMakeFrame
   776: 
   777:   public static void olgMoveCaret (int dxPx) {
   778:     olgCaretUs = Math.max (0,
   779:                            Math.min ((Math.max (0, olgBuffer[olgLength - 2] - 1) / olgUsPerPx) * olgUsPerPx,
   780:                                      olgCaretUs + olgUsPerPx * dxPx));
   781:     olgCaretPx = (olgCaretUs - olgStartUs) / olgUsPerPx;
   782:     if (olgCaretPx < 0) {
   783:       olgStartUs = Math.max (0, olgStartUs - olgRangeUs);
   784:       olgCaretPx = (olgCaretUs - olgStartUs) / olgUsPerPx;
   785:       olgEndUs = olgStartUs + olgRangeUs;
   786:     } else if (OLG_SCORE_WIDTH <= olgCaretPx) {
   787:       olgStartUs = olgStartUs + olgRangeUs;
   788:       olgCaretPx = (olgCaretUs - olgStartUs) / olgUsPerPx;
   789:       olgEndUs = olgStartUs + olgRangeUs;
   790:     }
   791:     olgPaint ();
   792:   }
   793: 
   794:   //録音開始
   795:   public static void olgRecordStart () {
   796:     if (olgRecording) {
   797:       return;
   798:     }
   799:     olgRecording = true;
   800:     olgLength = 0;
   801:     olgStartTimePS = XEiJ.mpuClockTime;
   802:     olgCounter = 0;
   803:     //
   804:     olgStartButton.setEnabled (false);
   805:     olgEndButton.setEnabled (true);
   806:     //
   807:     olgForcedTerminationTask = new TimerTask () {
   808:       @Override public void run () {
   809:         olgRecordEnd ();
   810:       }
   811:     };
   812:     olgTimer.schedule (olgForcedTerminationTask, 1000L * 900);  //900秒で強制終了する
   813:     //
   814:     for (int a = 0x00; a <= 0xff; a++) {
   815:       if (a == 0x08) {  //KON
   816:       } else if (a == 0x19) {  //AMD/PMD
   817:         for (int i = 0; i < 2; i++) {
   818:           olgBuffer[olgCounter++] = 0;
   819:           olgBuffer[olgCounter++] = a << 8 | (i << 7 | OPM.opmRegister[264 + i]);
   820:         }
   821:       } else {
   822:         olgBuffer[olgCounter++] = 0;
   823:         olgBuffer[olgCounter++] = a << 8 | OPM.opmRegister[a];
   824:       }
   825:     }
   826:     //KONは最後に出力する
   827:     for (int ch = 0; ch < 8; ch++) {
   828:       olgBuffer[olgCounter++] = 0;
   829:       olgBuffer[olgCounter++] = 0x08 << 8 | (OPM.opmRegister[256 + ch] << 3 | ch);
   830:     }
   831:   }
   832: 
   833:   //録音終了
   834:   public static void olgRecordEnd () {
   835:     if (!olgRecording) {
   836:       return;
   837:     }
   838:     //
   839:     olgBuffer[olgCounter++] = (int) ((XEiJ.mpuClockTime - olgStartTimePS) / (XEiJ.TMR_FREQ / OLG_UNIT));  //経過時間(OLG_UNIT)
   840:     olgBuffer[olgCounter++] = -1;
   841:     //
   842:     olgRecording = false;
   843:     olgLength = olgCounter;
   844:     olgStartTimePS = 0L;
   845:     olgCounter = 0;
   846:     //
   847:     if (olgForcedTerminationTask != null) {
   848:       olgForcedTerminationTask.cancel ();
   849:       olgForcedTerminationTask = null;
   850:     }
   851:     //
   852:     //olgStartButton.setEnabled (false);  //まだ次の記録を開始できない
   853:     olgEndButton.setEnabled (false);
   854:     //
   855:     olgTimer.schedule (new TimerTask () {
   856:       @Override public void run () {
   857:         olgMakeWaveData ();
   858:         olgPaint ();
   859:         olgStartButton.setEnabled (true);  //次の記録を開始できる
   860:         //olgEndButton.setEnabled (false);
   861:       }
   862:     }, 0L);
   863:   }
   864: 
   865:   //YM2151への書き込みを記録する
   866:   public static void olgSetData (int a, int d) {
   867:     if (olgRecording) {  //記録中
   868:       if (OLG_BUFFER_SIZE - 2 <= olgCounter) {  //バッファが一杯になった
   869:         olgRecordEnd ();  //記録を終了する
   870:       } else {
   871:         olgBuffer[olgCounter++] = (int) ((XEiJ.mpuClockTime - olgStartTimePS) / (XEiJ.TMR_FREQ / OLG_UNIT));  //経過時間(OLG_UNIT)
   872:         olgBuffer[olgCounter++] = a << 8 | d;  //アドレス<<8|データ
   873:       }
   874:     }
   875:   }
   876: 
   877:   //タイマAオーバーフローによる全スロットキーONを記録する
   878:   public static void olgSetCSMKON () {
   879:     if (olgRecording) {  //記録中
   880:       if (OLG_BUFFER_SIZE - 2 <= olgCounter) {  //バッファが一杯になった
   881:         olgRecordEnd ();  //記録を終了する
   882:       } else {
   883:         olgBuffer[olgCounter++] = (int) ((XEiJ.mpuClockTime - olgStartTimePS) / (XEiJ.TMR_FREQ / OLG_UNIT));  //経過時間(OLG_UNIT)
   884:         olgBuffer[olgCounter++] = 0x10000;  //CSMKON
   885:       }
   886:     }
   887:   }
   888: 
   889:   private static final int[] addressToData = new int[266];  //[address]=data,[256+ch]=slotMask,[264]=AMD,[265]=PMD
   890:   private static final int[] addressToUs = new int[266];  //addressToDataに最後に書き込まれた時刻(us)
   891:   private static final int[] channelToKindex = new int[8];  //[channel]=kindex。kindex=((kc-(kc>>2))<<6)|kf。(0..96)<<6|(0..63)=(0..6207)
   892:   private static final int[] channelToKonUs = new int[8];  //[channel]=キーオンまたはキーオンしたままkc,kfが変化した時刻(us)。-1=キーオンしていない
   893:   private static final int[] caretAddressToData = new int[266];  //キャレットを跨いだときのaddressToData
   894:   private static final int[] caretAddressToUs = new int[266];  //キャレットを跨いだときのaddressToUs
   895:   private static final int[] lastCaretAddressToUs = new int[266];  //最後に表示したときのcaretAddressToUs
   896:   private static final boolean[] strong = new boolean[OLG_TONE_BASE.length];  //lastCaretAddressToUsとcaretAddressToUsが異なる表示位置
   897: 
   898:   //    |           1111111111222222222233333
   899:   //    | 01234567890123456789012345678901234
   900:   //  --+------------------------------------
   901:   //   0|    FC SL WA SY SP PD AD PS AS PN   
   902:   //   1|    -- -- -- -- -- -- -- -- -- -- --
   903:   //   2|    AR 1R 2R RR 1L TL KS ML T1 T2 AE
   904:   //   3| M1 -- -- -- -- -- -- -- -- -- -- --
   905:   //   4| C1 -- -- -- -- -- -- -- -- -- -- --
   906:   //   5| M2 -- -- -- -- -- -- -- -- -- -- --
   907:   //   6| C2 -- -- -- -- -- -- -- -- -- -- --
   908:   //   7|    KC KF
   909:   //   8|    -- --
   910:   private static final int[] TONE_MAP = new int[] {
   911:     //[0]=address,[1]=row,[2]=mask1,[3]=col1,[4]=mask2,[5]=col2
   912:     0x20, 1, 0b11000000, 30, 0b00111111,  3,  //PAN,FLCON
   913:     0x28, 8, 0b01111111,  3,          0,  0,  //KC
   914:     0x30, 8, 0b11111100,  6,          0,  0,  //KF
   915:     0x38, 1, 0b01110000, 24, 0b00000011, 27,  //PMS,AMS
   916:     //
   917:     0x40, 3, 0b01110000, 27, 0b00001111, 24,  //M1 DT1,MUL
   918:     0x48, 5, 0b01110000, 27, 0b00001111, 24,  //M2 DT1,MUL
   919:     0x50, 4, 0b01110000, 27, 0b00001111, 24,  //C1 DT1,MUL
   920:     0x58, 6, 0b01110000, 27, 0b00001111, 24,  //C2 DT1,MUL
   921:     //
   922:     0x60, 3, 0b01111111, 18,          0,  0,  //M1 TL
   923:     0x68, 5, 0b01111111, 18,          0,  0,  //M2 TL
   924:     0x70, 4, 0b01111111, 18,          0,  0,  //C1 TL
   925:     0x78, 6, 0b01111111, 18,          0,  0,  //C2 TL
   926:     //
   927:     0x80, 3, 0b11000000, 21, 0b00011111,  3,  //M1 KS,AR
   928:     0x88, 5, 0b11000000, 21, 0b00011111,  3,  //M2 KS,AR
   929:     0x90, 4, 0b11000000, 21, 0b00011111,  3,  //C1 KS,AR
   930:     0x98, 6, 0b11000000, 21, 0b00011111,  3,  //C2 KS,AR
   931:     //
   932:     0xa0, 3, 0b10000000, 33, 0b00011111,  6,  //M1 AMSEN,D1R
   933:     0xa8, 5, 0b10000000, 33, 0b00011111,  6,  //M2 AMSEN,D1R
   934:     0xb0, 4, 0b10000000, 33, 0b00011111,  6,  //C1 AMSEN,D1R
   935:     0xb8, 6, 0b10000000, 33, 0b00011111,  6,  //C2 AMSEN,D1R
   936:     //
   937:     0xc0, 3, 0b11000000, 30, 0b00011111,  9,  //M1 DT2,D2R
   938:     0xc8, 5, 0b11000000, 30, 0b00011111,  9,  //M2 DT2,D2R
   939:     0xd0, 4, 0b11000000, 30, 0b00011111,  9,  //C1 DT2,D2R
   940:     0xd8, 6, 0b11000000, 30, 0b00011111,  9,  //C2 DT2,D2R
   941:     //
   942:     0xe0, 3, 0b11110000, 15, 0b00001111, 12,  //M1 D1L,RR
   943:     0xe8, 5, 0b11110000, 15, 0b00001111, 12,  //M2 D1L,RR
   944:     0xf0, 4, 0b11110000, 15, 0b00001111, 12,  //C1 D1L,RR
   945:     0xf8, 6, 0b11110000, 15, 0b00001111, 12,  //C2 D1L,RR
   946:     //
   947:     256,  1, 0b00001111,  6,          0,  0,  //SLOT
   948:   };
   949: 
   950:   //描画
   951:   public static void olgPaint () {
   952:     Graphics2D g2 = olgCanvasImage.createGraphics ();
   953:     //背景
   954:     g2.setColor (Color.black);
   955:     g2.fillRect (0, 0, OLG_SCORE_IMAGE_WIDTH, OLG_SCORE_IMAGE_HEIGHT);
   956:     //時刻
   957:     {
   958:       g2.setColor (Color.darkGray);
   959:       g2.setStroke (olgSolidStroke);
   960:       int stepPx = 10;
   961:       int stepUs = olgUsPerPx * stepPx;
   962:       int us0 = ((olgStartUs + (stepUs - 1)) / stepUs) * stepUs;  //startをstepの倍数に切り上げる
   963:       int px0 = olgUsToPx (us0);
   964:       for (int px = px0; px < OLG_SCORE_WIDTH; px += stepPx) {
   965:         int us = olgPxToUs (px);
   966:         if (us % (stepUs * 10) != 0) {
   967:           g2.drawLine (OLG_SCORE_H_MARGIN + px, OLG_SCORE_V_MARGIN,
   968:                        //OLG_SCORE_H_MARGIN + px, OLG_SCORE_V_MARGIN + OLG_SCORE_HEIGHT - 1);
   969:                        OLG_SCORE_H_MARGIN + px, OLG_WAVE_YM1);
   970:         }
   971:       }
   972:     }
   973:     //周波数
   974:     g2.setColor (Color.darkGray);
   975:     g2.setStroke (olgSolidStroke);
   976:     //  3  4  5  6  7  8  9 10 11  0  1  2
   977:     //  C  C# D  D# E  F  F# G  G# A  A# B
   978:     for (int oct12 = -6; oct12 < 12 * 8 - 6; oct12++) {
   979:       if ((1 << ((oct12 + 12) % 12) & (1 << 2 | 1 << 3 | 1 << 5 | 1 << 7 | 1 << 8 | 1 << 10)) != 0) {
   980:         double oct = oct12 / 12.0;
   981:         int kindex = (int) Math.floor (0.5 + 3584.0 + 64.0 * 12.0 * (oct - 4.0 + LOG2_358_400));
   982:         int y = OLG_SCORE_HEIGHT * (6208 - 1 - kindex) / 6208;
   983:         g2.drawLine (OLG_SCORE_H_MARGIN, OLG_SCORE_V_MARGIN + y,
   984:                      OLG_SCORE_H_MARGIN + OLG_SCORE_WIDTH - 1, OLG_SCORE_V_MARGIN + y);
   985:       }
   986:     }
   987:     //時刻
   988:     g2.setFont (LnF.lnfMonospacedFont12);
   989:     g2.setStroke (olgSolidStroke);
   990:     {
   991:       int stepPx = 100;
   992:       int stepUs = olgUsPerPx * stepPx;
   993:       int us0 = ((olgStartUs + (stepUs - 1)) / stepUs) * stepUs;  //startをstepの倍数に切り上げる
   994:       int px0 = olgUsToPx (us0);
   995:       for (int px = px0; px < OLG_SCORE_WIDTH; px += stepPx) {
   996:         int us = olgPxToUs (px);
   997:         g2.setColor (Color.gray);
   998:         g2.drawLine (OLG_SCORE_H_MARGIN + px, OLG_SCORE_V_MARGIN,
   999:                      //OLG_SCORE_H_MARGIN + px, OLG_SCORE_V_MARGIN + OLG_SCORE_HEIGHT - 1);
  1000:                      OLG_SCORE_H_MARGIN + px, OLG_WAVE_YM1);
  1001:         g2.setColor (Color.white);
  1002:         g2.drawString (String.valueOf ((double) us / 1000000.0) + "s",
  1003:                        OLG_SCORE_H_MARGIN + px + 3, OLG_SCORE_V_MARGIN + OLG_SCORE_HEIGHT - 2);
  1004:       }
  1005:     }
  1006:     //周波数
  1007:     //  kc=0..127
  1008:     //  kf=0..63
  1009:     //  kindex=((kc-(kc>>2))<<6)|kf=((0..96)<<6)|(0..63)=0..6207
  1010:     //  3.58MHzのときkc=74,kf=0,kindex=3584が440Hz
  1011:     //  4.00MHzのときkc=74,kf=0,kindex=3584が4.00/3.58*440Hz
  1012:     //  perl -e "print 2**((((0x4a-(0x4a>>2))<<6|0)-((0x4a-(0x4a>>2))<<6|0))/(12*64))*4.00/3.58*440"
  1013:     //  491.620111731844
  1014:     //  perl -e "print 2**((((0x47-(0x47>>2))<<6|0)-((0x4a-(0x4a>>2))<<6|0))/(12*64))*4.00/3.58*440"
  1015:     //  437.98372735391
  1016:     //  2^(oct-4)*440Hzのkindexは
  1017:     //  kindex=3584+(64*12)*((oct-4)+log2(3.58/4.00))
  1018:     g2.setFont (LnF.lnfMonospacedFont12);
  1019:     g2.setStroke (olgSolidStroke);
  1020:     for (int oct = 0; oct < 8; oct++) {
  1021:       int kindex = (int) Math.floor (0.5 + 3584.0 + 64.0 * 12.0 * ((double) (oct - 4) + LOG2_358_400));
  1022:       int y = OLG_SCORE_HEIGHT * (6208 - 1 - kindex) / 6208;
  1023:       g2.setColor (Color.gray);
  1024:       g2.drawLine (OLG_SCORE_H_MARGIN, OLG_SCORE_V_MARGIN + y,
  1025:                    OLG_SCORE_H_MARGIN + OLG_SCORE_WIDTH - 1, OLG_SCORE_V_MARGIN + y);
  1026:       g2.setColor (Color.white);
  1027:       g2.drawString ("o" + oct + "a", OLG_SCORE_H_MARGIN + 3, OLG_SCORE_V_MARGIN + y - 2);
  1028:     }
  1029:     //波形
  1030:     g2.setColor (Color.darkGray);
  1031:     g2.drawLine (OLG_SCORE_H_MARGIN, OLG_WAVE_Y1,
  1032:                  OLG_SCORE_H_MARGIN + OLG_SCORE_WIDTH - 1, OLG_WAVE_Y1);
  1033:     g2.setColor (Color.gray);
  1034:     g2.drawLine (OLG_SCORE_H_MARGIN, OLG_WAVE_Y0,
  1035:                  OLG_SCORE_H_MARGIN + OLG_SCORE_WIDTH - 1, OLG_WAVE_Y0);
  1036:     g2.setColor (Color.darkGray);
  1037:     g2.drawLine (OLG_SCORE_H_MARGIN, OLG_WAVE_YM1,
  1038:                  OLG_SCORE_H_MARGIN + OLG_SCORE_WIDTH - 1, OLG_WAVE_YM1);
  1039:     if (opmBuffer != null) {
  1040:       Path2D pathL = new Path2D.Float ();
  1041:       Path2D pathR = new Path2D.Float ();
  1042:       for (int x = 0; x < OLG_SCORE_WIDTH; x++) {
  1043:         int us = olgPxToUs (x);
  1044:         int i = OPM_CHANNELS * (us >> 4);
  1045:         if (opmBuffer.length <= i) {
  1046:           break;
  1047:         }
  1048:         int yL = (OLG_WAVE_RADIUS * opmBuffer[i    ]) >> 15;
  1049:         int yR = (OLG_WAVE_RADIUS * opmBuffer[i + 1]) >> 15;
  1050:         if (x == 0) {
  1051:           pathL.moveTo ((float) (OLG_SCORE_H_MARGIN + x), (float) (OLG_WAVE_Y0 - yL));
  1052:           pathR.moveTo ((float) (OLG_SCORE_H_MARGIN + x), (float) (OLG_WAVE_Y0 - yR));
  1053:         } else {
  1054:           pathL.lineTo ((float) (OLG_SCORE_H_MARGIN + x), (float) (OLG_WAVE_Y0 - yL));
  1055:           pathR.lineTo ((float) (OLG_SCORE_H_MARGIN + x), (float) (OLG_WAVE_Y0 - yR));
  1056:         }
  1057:       }
  1058:       g2.setStroke (olgWaveStroke);
  1059:       g2.setColor (Color.yellow);
  1060:       g2.draw (pathL);
  1061:       g2.setColor (Color.cyan);
  1062:       g2.draw (pathR);
  1063:     }
  1064:     //データ
  1065:     Arrays.fill (addressToData, 0);
  1066:     Arrays.fill (addressToUs, -1);
  1067:     Arrays.fill (channelToKindex, 0);
  1068:     Arrays.fill (channelToKonUs, -1);
  1069:     Arrays.fill (caretAddressToData, 0);
  1070:     Arrays.fill (caretAddressToUs, -1);
  1071:     //Arrays.fill (lastCaretAddressToUs, -1);
  1072:     int caretIndex = -1;  //olgCaretUs以上の最初のolgBufferのインデックス
  1073:     g2.setStroke (olgSolidStroke);
  1074:     for (int index = 0; index < olgLength; index += 2) {
  1075:       int us = olgBuffer[index];
  1076:       int ad = olgBuffer[index + 1];
  1077:       if (olgEndUs <= us || ad == -1) {
  1078:         break;
  1079:       }
  1080:       if (ad == 0x10000) {  //CSMKON
  1081:         continue;
  1082:       }
  1083:       if (caretIndex == -1 && olgCaretUs <= us) {
  1084:         caretIndex = index;
  1085:         System.arraycopy (addressToData, 0,
  1086:                           caretAddressToData, 0,
  1087:                           addressToData.length);
  1088:         System.arraycopy (addressToUs, 0,
  1089:                           caretAddressToUs, 0,
  1090:                           addressToUs.length);
  1091:       }
  1092:       int a = ad >> 8;
  1093:       int d = ad & 255;
  1094:       addressToData[a] = d;
  1095:       addressToUs[a] = us;
  1096:       if (!((a == 0x08 && !olgChannelMask[d & 7]) ||  //KONでチャンネルがマスクされている、または
  1097:             (0x10 <= a && a <= 0x12) ||  //CLKA1,CLKA2,CLKBのいずれか、または
  1098:             (0x20 <= a && !olgChannelMask[a & 7]))) {  //0x20~0xffでチャンネルがマスクされている、でなければ
  1099:         g2.setColor (a == 0x08 ? OLG_KON_COLOR[d & 7] :
  1100:                      0x20 <= a ? OLG_KON_COLOR[a & 7] :
  1101:                      Color.white);
  1102:         int px = olgUsToPx (us);
  1103:         g2.drawLine (OLG_SCORE_H_MARGIN + px, OLG_SCORE_V_MARGIN + OLG_SCORE_HEIGHT - 14 - (OLG_KON1ST_HEIGHT - 1),
  1104:                      OLG_SCORE_H_MARGIN + px, OLG_SCORE_V_MARGIN + OLG_SCORE_HEIGHT - 14);
  1105:       }
  1106:       if (a == 0x08) {  //KON
  1107:         int channel = d & 7;
  1108:         if (channelToKonUs[channel] != -1) {
  1109:           olgDrawKoff (g2, channel, channelToKindex[channel], channelToKonUs[channel], us);
  1110:           channelToKonUs[channel] = -1;
  1111:         }
  1112:         int slotMask = (d >> 3) & 15;
  1113:         addressToData[256 + channel] = slotMask;
  1114:         addressToUs[256 + channel] = us;
  1115:         if (slotMask != 0) {
  1116:           olgDrawKon1st (g2, channel, channelToKindex[channel], us);
  1117:           channelToKonUs[channel] = us;
  1118:         }
  1119:       } else if (a == 0x19) {  //AMD/PMD
  1120:         int aa = (d & 128) == 0 ? 264 : 265;
  1121:         addressToData[aa] = d & 127;
  1122:         addressToUs[aa] = us;
  1123:       } else if (0x28 <= a && a < 0x30) {  //KC
  1124:         int channel = a & 7;
  1125:         int kc = d & 127;
  1126:         int kf = (addressToData[0x30 + channel] >> 2) & 63;
  1127:         int kindex = ((kc - (kc >> 2)) << 6) | kf;
  1128:         if (channelToKonUs[channel] != -1) {
  1129:           olgDrawKoff (g2, channel, channelToKindex[channel], channelToKonUs[channel], us);
  1130:           olgDrawKon2nd (g2, channel, kindex, us);
  1131:           channelToKonUs[channel] = us;
  1132:         }
  1133:         channelToKindex[channel] = kindex;
  1134:       } else if (0x30 <= a && a < 0x38) {  //KF
  1135:         int channel = a & 7;
  1136:         int kc = addressToData[0x28 + channel] & 127;
  1137:         int kf = (d >> 2) & 63;
  1138:         int kindex = ((kc - (kc >> 2)) << 6) | kf;
  1139:         if (channelToKonUs[channel] != -1) {
  1140:           olgDrawKoff (g2, channel, channelToKindex[channel], channelToKonUs[channel], us);
  1141:           olgDrawKon2nd (g2, channel, kindex, us);
  1142:           channelToKonUs[channel] = us;
  1143:         }
  1144:         channelToKindex[channel] = kindex;
  1145:       }
  1146:     }  //for index
  1147:     for (int channel = 0; channel < 8; channel++) {
  1148:       if (channelToKonUs[channel] != -1) {  //キーオンしたまま終了した
  1149:         olgDrawKoff (g2, channel, channelToKindex[channel], channelToKonUs[channel], Math.min (olgBuffer[olgLength - 2], olgEndUs));
  1150:         channelToKonUs[channel] = -1;
  1151:       }
  1152:     }
  1153:     //マスク左側
  1154:     if (olgMaskLeftUs != -1 &&
  1155:         olgStartUs < olgMaskLeftUs) {
  1156:       int px0 = 0;
  1157:       int px1 = olgUsToPx (Math.min (olgEndUs, olgMaskLeftUs));
  1158:       g2.setPaint (olgMaskPaint);
  1159:       g2.fillRect (OLG_SCORE_H_MARGIN + px0, OLG_SCORE_V_MARGIN, px1 - px0, OLG_SCORE_HEIGHT);
  1160:     }
  1161:     //マスク右側
  1162:     if (olgMaskRightUs != -1 &&
  1163:         olgMaskRightUs < olgEndUs) {
  1164:       int px0 = olgUsToPx (Math.max (olgStartUs, olgMaskRightUs));
  1165:       int px1 = OLG_SCORE_WIDTH;
  1166:       g2.setPaint (olgMaskPaint);
  1167:       g2.fillRect (OLG_SCORE_H_MARGIN + px0, OLG_SCORE_V_MARGIN, px1 - px0, OLG_SCORE_HEIGHT);
  1168:     }
  1169:     //キャレット
  1170:     g2.setColor (Color.green);
  1171:     g2.setStroke (olgDashStroke);
  1172:     g2.drawLine (OLG_SCORE_H_MARGIN + olgCaretPx, OLG_SCORE_V_MARGIN,
  1173:                  //OLG_SCORE_H_MARGIN + olgCaretPx, OLG_SCORE_V_MARGIN + OLG_SCORE_HEIGHT - 1);
  1174:                  OLG_SCORE_H_MARGIN + olgCaretPx, OLG_WAVE_YM1);
  1175:     olgCanvas.repaint ();
  1176:     //
  1177:     //音色
  1178:     Arrays.fill (olgToneBitmap, 0xff000000);
  1179:     for (int ch = 0; ch < 8; ch++) {
  1180:       Arrays.fill (strong, false);
  1181:       for (int j = 0; j < TONE_MAP.length; j += 6) {
  1182:         int address = TONE_MAP[j    ];
  1183:         int row     = TONE_MAP[j + 1];
  1184:         int mask1   = TONE_MAP[j + 2];
  1185:         int col1    = TONE_MAP[j + 3];
  1186:         int mask2   = TONE_MAP[j + 4];
  1187:         int col2    = TONE_MAP[j + 5];
  1188:         int a = address + ch;
  1189:         XEiJ.fmtHex2 (OLG_TONE_BASE, OLG_TONE_COLS * row + col1, (caretAddressToData[a] & mask1) >> Integer.numberOfTrailingZeros (mask1));
  1190:         if (mask2 != 0) {
  1191:           XEiJ.fmtHex2 (OLG_TONE_BASE, OLG_TONE_COLS * row + col2, (caretAddressToData[a] & mask2) >> Integer.numberOfTrailingZeros (mask2));
  1192:         }
  1193:         if (lastCaretAddressToUs[a] != caretAddressToUs[a]) {
  1194:           strong[OLG_TONE_COLS * row + col1    ] = true;
  1195:           strong[OLG_TONE_COLS * row + col1 + 1] = true;
  1196:           if (mask2 != 0) {
  1197:             strong[OLG_TONE_COLS * row + col2    ] = true;
  1198:             strong[OLG_TONE_COLS * row + col2 + 1] = true;
  1199:           }
  1200:         }
  1201:       }  //for j
  1202:       {
  1203:         int a = 0x18;  //LFRQ
  1204:         int d = caretAddressToData[a];
  1205:         int row = 1;
  1206:         int col = 15;
  1207:         XEiJ.fmtHex2 (OLG_TONE_BASE, OLG_TONE_COLS * row + col, d & 255);
  1208:         if (lastCaretAddressToUs[a] != caretAddressToUs[a]) {
  1209:           strong[OLG_TONE_COLS * row + col    ] = true;
  1210:           strong[OLG_TONE_COLS * row + col + 1] = true;
  1211:         }
  1212:       }
  1213:       {
  1214:         int a = 0x1b;  //W
  1215:         int d = caretAddressToData[a];
  1216:         int row = 1;
  1217:         int col = 9;
  1218:         XEiJ.fmtHex2 (OLG_TONE_BASE, OLG_TONE_COLS * row + col, d & 3);
  1219:         if (lastCaretAddressToUs[a] != caretAddressToUs[a]) {
  1220:           strong[OLG_TONE_COLS * row + col    ] = true;
  1221:           strong[OLG_TONE_COLS * row + col + 1] = true;
  1222:         }
  1223:       }
  1224:       {
  1225:         int a = 264;  //AMD
  1226:         int d = caretAddressToData[a];
  1227:         int row = 1;
  1228:         int col = 21;
  1229:         XEiJ.fmtHex2 (OLG_TONE_BASE, OLG_TONE_COLS * row + col, d);
  1230:         if (lastCaretAddressToUs[a] != caretAddressToUs[a]) {
  1231:           strong[OLG_TONE_COLS * row + col    ] = true;
  1232:           strong[OLG_TONE_COLS * row + col + 1] = true;
  1233:         }
  1234:       }
  1235:       {
  1236:         int a = 265;  //PMD
  1237:         int d = caretAddressToData[a];
  1238:         int row = 1;
  1239:         int col = 18;
  1240:         XEiJ.fmtHex2 (OLG_TONE_BASE, OLG_TONE_COLS * row + col, d);
  1241:         if (lastCaretAddressToUs[a] != caretAddressToUs[a]) {
  1242:           strong[OLG_TONE_COLS * row + col    ] = true;
  1243:           strong[OLG_TONE_COLS * row + col + 1] = true;
  1244:         }
  1245:       }
  1246:       int chCol = ch & 3;
  1247:       int chRow = ch >> 2;
  1248:       int chX = OLG_TONE_H_MARGIN + (OLG_TONE_CHAR_WIDTH * OLG_TONE_COLS + OLG_TONE_H_SPACE) * chCol;
  1249:       int chY = OLG_TONE_V_MARGIN + (OLG_TONE_LINE_HEIGHT * OLG_TONE_ROWS + OLG_TONE_V_SPACE) * chRow;
  1250:       int rgb = (olgChannelMask[ch] ? (caretAddressToData[256 + ch] != 0 ? OLG_KON_COLOR : OLG_BAR_COLOR)[ch] : Color.darkGray).getRGB ();
  1251:       for (int row = 0; row < OLG_TONE_ROWS; row++) {
  1252:         for (int col = 0; col < OLG_TONE_COLS; col++) {
  1253:           int c = OLG_TONE_BASE[col + OLG_TONE_COLS * row];
  1254:           for (int v = 0; v < 8; v++) {
  1255:             int t = FontPage.Lcd.LCD6X8_FONT[8 * c + v] << 24;
  1256:             for (int u = 0; u < 6; u++) {
  1257:               olgToneBitmap[chX + OLG_TONE_CHAR_WIDTH * col + u +
  1258:                             OLG_TONE_IMAGE_WIDTH * (chY + OLG_TONE_LINE_HEIGHT * row + v)] = t < 0 ? rgb : 0xff000000;
  1259:               t <<= 1;
  1260:             }
  1261:           }
  1262:           if (strong[col + OLG_TONE_COLS * row]) {
  1263:             for (int u = 0; u < 6; u++) {
  1264:               olgToneBitmap[chX + OLG_TONE_CHAR_WIDTH * col + u +
  1265:                             OLG_TONE_IMAGE_WIDTH * (chY + OLG_TONE_LINE_HEIGHT * row + 8)] = rgb;
  1266:             }
  1267:           }
  1268:         }  //for col
  1269:       }  //for row
  1270:     }  //for ch
  1271:     System.arraycopy (caretAddressToUs, 0,
  1272:                       lastCaretAddressToUs, 0,
  1273:                       caretAddressToUs.length);
  1274:     olgTonePanel.repaint ();
  1275:     //
  1276:     //ダンプ
  1277:     if (caretIndex == -1) {  //olgCaretUs以上のデータが存在しない
  1278:       caretIndex = Math.max (0, olgLength - 2);
  1279:     }
  1280:     {
  1281:       //範囲を決める
  1282:       //  下限をolgUsPerPxずつ減らしてindexが2*10以上減ったら止まる
  1283:       int leftUs = olgCaretUs;
  1284:       int leftIndex = caretIndex;
  1285:       while (Math.max (0, caretIndex - 2 * 10) < leftIndex) {
  1286:         leftUs = Math.max (0, leftUs - olgUsPerPx);
  1287:         while (0 < leftIndex && leftUs <= olgBuffer[leftIndex - 2]) {
  1288:           leftIndex -= 2;
  1289:         }
  1290:       }
  1291:       //  上限をolgUsPerPxずつ増やしてindexが2*10以上増えたら止まる
  1292:       int rightUs = olgCaretUs;
  1293:       int rightIndex = caretIndex;
  1294:       while (rightIndex < Math.min (olgLength - 2, caretIndex + 2 * 10)) {
  1295:         rightUs = Math.min (olgBuffer[olgLength - 2], rightUs + olgUsPerPx);
  1296:         while (rightIndex < olgLength - 2 && olgBuffer[rightIndex + 2] <= rightUs) {
  1297:           rightIndex += 2;
  1298:         }
  1299:       }
  1300:       //ダンプする
  1301:       StringBuilder sb = new StringBuilder ();
  1302:       for (int index = leftIndex; index <= rightIndex; index += 2) {
  1303:         if (index == caretIndex) {
  1304:           sb.append ("--------------------\n");
  1305:         }
  1306:         int us = olgBuffer[index];
  1307:         int ad = olgBuffer[index + 1];
  1308:         sb.append (String.valueOf ((double) us / 1000000.0)).append ("s");
  1309:         if (ad == -1) {
  1310:           sb.append (" -1 END\n");
  1311:           break;
  1312:         }
  1313:         sb.append (" 0x");
  1314:         XEiJ.fmtHex4 (sb, ad);
  1315:         if (ad == 0x10000) {
  1316:           sb.append (" CSMKON\n");
  1317:           continue;
  1318:         }
  1319:         int a = (ad >> 8) & 255;
  1320:         int d = ad & 255;
  1321:         if (a < 0x20) {
  1322:           switch (a) {
  1323:           case 0x01:
  1324:             sb.append (" LFORESET=").append ((d >> 1) & 1);
  1325:             break;
  1326:           case 0x08:
  1327:             sb.append (" KON[").append (d & 7).append ("]=").append ((d >> 3) & 15);
  1328:             break;
  1329:           case 0x0f:
  1330:             sb.append (" NE=").append ((d >> 7) & 1);
  1331:             sb.append (" NFRQ=").append (d & 31);
  1332:             break;
  1333:           case 0x10:
  1334:             sb.append (" CLKA1=").append (d & 255);
  1335:             break;
  1336:           case 0x11:
  1337:             sb.append (" CLKA2=").append (d & 3);
  1338:             break;
  1339:           case 0x12:
  1340:             sb.append (" CLKB=").append (d & 255);
  1341:             break;
  1342:           case 0x14:
  1343:             sb.append (" CSM=").append ((d >> 7) & 1);
  1344:             sb.append (" RESETB=").append ((d >> 5) & 1);
  1345:             sb.append (" RESETA=").append ((d >> 4) & 1);
  1346:             sb.append (" IRQENB=").append ((d >> 3) & 1);
  1347:             sb.append (" IRQENA=").append ((d >> 2) & 1);
  1348:             sb.append (" LOADB=").append ((d >> 1) & 1);
  1349:             sb.append (" LOADA=").append (d & 1);
  1350:             break;
  1351:           case 0x18:
  1352:             sb.append (" LFRQ=").append (d & 255);
  1353:             break;
  1354:           case 0x19:
  1355:             if (((d >> 7) & 1) == 0) {
  1356:               sb.append (" AMD=").append (d & 127);
  1357:             } else {
  1358:               sb.append (" PMD=").append (d & 127);
  1359:             }
  1360:             break;
  1361:           case 0x1b:
  1362:             sb.append (" CT1=").append ((d >> 7) & 1);
  1363:             sb.append (" CT2=").append ((d >> 6) & 1);
  1364:             sb.append (" W=").append (d & 3);
  1365:             break;
  1366:           }
  1367:         } else if (a < 0x40) {
  1368:           sb.append (" CH").append (a & 7);
  1369:           switch (a >> 3) {
  1370:           case 0x20 >> 3:
  1371:             sb.append (" R=").append ((d >> 7) & 1);
  1372:             sb.append (" L=").append ((d >> 6) & 1);
  1373:             sb.append (" FL=").append ((d >> 3) & 7);
  1374:             sb.append (" CON=").append (d & 7);
  1375:             break;
  1376:           case 0x28 >> 3:
  1377:             sb.append (" KC=").append (d & 127);
  1378:             break;
  1379:           case 0x30 >> 3:
  1380:             sb.append (" KF=").append ((d >> 2) & 63);
  1381:             break;
  1382:           case 0x38 >> 3:
  1383:             sb.append (" PMS=").append ((d >> 4) & 7);
  1384:             sb.append (" AMS=").append (d & 3);
  1385:             break;
  1386:           }
  1387:         } else {
  1388:           sb.append (" CH").append (a & 7);
  1389:           sb.append (((a >> 3) & 3) == 0 ? " M1" :
  1390:                      ((a >> 3) & 3) == 1 ? " M2" :
  1391:                      ((a >> 3) & 3) == 2 ? " C1" : " C2");
  1392:           switch (a >> 5) {
  1393:           case 0x40 >> 5:
  1394:             sb.append (" DT1=").append ((d >> 4) & 7);
  1395:             sb.append (" MUL=").append (d & 15);
  1396:             break;
  1397:           case 0x60 >> 5:
  1398:             sb.append (" TL=").append (d & 127);
  1399:             break;
  1400:           case 0x80 >> 5:
  1401:             sb.append (" KS=").append ((d >> 6) & 3);
  1402:             sb.append (" AR=").append (d & 31);
  1403:             break;
  1404:           case 0xa0 >> 5:
  1405:             sb.append (" AMSEN=").append ((d >> 7) & 1);
  1406:             sb.append (" D1R=").append (d & 31);
  1407:             break;
  1408:           case 0xc0 >> 5:
  1409:             sb.append (" DT2=").append ((d >> 6) & 3);
  1410:             sb.append (" D2R=").append (d & 31);
  1411:             break;
  1412:           case 0xe0 >> 5:
  1413:             sb.append (" D1L=").append ((d >> 4) & 15);
  1414:             sb.append (" RR=").append (d & 15);
  1415:             break;
  1416:           }
  1417:         }
  1418:         sb.append ("\n");
  1419:       }  //for index
  1420:       String text = sb.toString ();
  1421:       olgDumpTextArea.setText (text);
  1422:       olgDumpTextArea.setCaretPosition (text.length ());
  1423:       olgDumpTextArea.setCaretPosition (text.indexOf ("----"));
  1424:     }
  1425:   }
  1426: 
  1427:   //バーを描く
  1428:   public static void olgDrawKon1st (Graphics2D g2, int channel, int kindex, int us) {
  1429:     if (olgStartUs <= us && us < olgEndUs &&
  1430:         olgChannelMask[channel]) {
  1431:       int py = OLG_SCORE_HEIGHT * (6208 - 1 - kindex) / 6208;
  1432:       int px = Math.max (0, us - olgStartUs) / olgUsPerPx;
  1433:       g2.setColor (OLG_KON_COLOR[channel]);
  1434:       g2.drawLine (OLG_SCORE_H_MARGIN + px, OLG_SCORE_V_MARGIN + py - (OLG_KON1ST_HEIGHT >> 1),
  1435:                    OLG_SCORE_H_MARGIN + px, OLG_SCORE_V_MARGIN + py + (OLG_KON1ST_HEIGHT >> 1));
  1436:     }
  1437:   }
  1438:   public static void olgDrawKon2nd (Graphics2D g2, int channel, int kindex, int us) {
  1439:     if (olgStartUs <= us && us < olgEndUs &&
  1440:         olgChannelMask[channel]) {
  1441:       int py = OLG_SCORE_HEIGHT * (6208 - 1 - kindex) / 6208;
  1442:       int px = Math.max (0, us - olgStartUs) / olgUsPerPx;
  1443:       g2.setColor (OLG_BAR_COLOR[channel]);
  1444:       g2.drawLine (OLG_SCORE_H_MARGIN + px, OLG_SCORE_V_MARGIN + py - (OLG_KON2ND_HEIGHT >> 1),
  1445:                    OLG_SCORE_H_MARGIN + px, OLG_SCORE_V_MARGIN + py + (OLG_KON2ND_HEIGHT >> 1));
  1446:     }
  1447:   }
  1448:   public static void olgDrawKoff (Graphics2D g2, int channel, int kindex, int leftUs, int rightUs) {
  1449:     if (olgStartUs < rightUs && leftUs < olgEndUs &&
  1450:         olgChannelMask[channel]) {
  1451:       int py = OLG_SCORE_HEIGHT * (6208 - 1 - kindex) / 6208;
  1452:       int px0 = Math.max (0, leftUs - olgStartUs) / olgUsPerPx;
  1453:       int px1 = (rightUs - olgStartUs) / olgUsPerPx;
  1454:       if (px0 + 1 < px1) {
  1455:         g2.setColor (OLG_BAR_COLOR[channel]);
  1456:         g2.fillRect (OLG_SCORE_H_MARGIN + px0 + 1, OLG_SCORE_V_MARGIN + py - (OLG_BAR_HEIGHT >> 1),
  1457:                      px1 - (px0 + 1), OLG_BAR_HEIGHT);
  1458:       }
  1459:     }
  1460:   }
  1461: 
  1462:   //  MMLをロードする
  1463:   public static void olgLoadMML () {
  1464:     JFileChooser2 fileChooser = new JFileChooser2 (olgLastMMLFile);
  1465:     fileChooser.setFileFilter (olgMMLFilter);
  1466:     if (fileChooser.showOpenDialog (null) != JFileChooser.APPROVE_OPTION) {
  1467:       return;
  1468:     }
  1469:     File file = fileChooser.getSelectedFile ();
  1470:     String program = XEiJ.rscGetTextFile (file.getPath ());
  1471:     MMLCompiler compiler = new MMLCompiler ();
  1472:     int[] array = compiler.compile (program);
  1473:     if (array == null) {  //エラー
  1474:       JOptionPane.showMessageDialog (null, compiler.getError ());
  1475:       return;
  1476:     }
  1477:     olgLastMMLFile = file;
  1478:     System.arraycopy (array, 0, olgBuffer, 0, array.length);
  1479:     olgLength = array.length;
  1480:     olgMakeWaveData ();
  1481:     olgPaint ();
  1482:   }
  1483: 
  1484:   //  ログをロードする
  1485:   public static void olgLoadLog () {
  1486:     JFileChooser2 fileChooser = new JFileChooser2 (olgLastFile);
  1487:     fileChooser.setFileFilter (olgFilter);
  1488:     if (fileChooser.showOpenDialog (null) != JFileChooser.APPROVE_OPTION) {
  1489:       return;
  1490:     }
  1491:     File file = fileChooser.getSelectedFile ();
  1492:     byte[] b = XEiJ.rscGetFile (file.getPath ());
  1493:     //バイト配列の長さは8以上かつバッファの長さの4倍以下かつ8の倍数であること
  1494:     boolean error = !(8 <= b.length && b.length <= 4 * OLG_BUFFER_SIZE && b.length % 8 == 0);
  1495:     if (!error) {
  1496:       olgLength = b.length >> 2;
  1497:       for (int i = 0; i < olgLength; i++) {
  1498:         olgBuffer[i] = ((b[4 * i    ]      ) << 24 |
  1499:                         (b[4 * i + 1] & 255) << 16 |
  1500:                         (b[4 * i + 2] & 255) <<  8 |
  1501:                         (b[4 * i + 3] & 255));
  1502:       }
  1503:       int lastUs = 0;
  1504:       for (int i = 0; i < olgLength; i += 2) {
  1505:         int us = olgBuffer[i];
  1506:         int ad = olgBuffer[i + 1];
  1507:         //時刻は0..999999999かつ昇順であること
  1508:         //アドレスとデータは最後以外は0..0x10000、最後は-1であること
  1509:         if (!(0 <= us && us <= 999999999 && lastUs <= us &&
  1510:               (i < olgLength - 2 ? 0 <= ad && ad <= 0x10000 : ad == -1))) {
  1511:           error = true;
  1512:           break;
  1513:         }
  1514:         lastUs = us;
  1515:       }
  1516:       if (!error) {
  1517:         olgLastFile = file;
  1518:       }
  1519:     }
  1520:     if (error) {  //エラー
  1521:       olgBuffer[0] = 0;
  1522:       olgBuffer[1] = -1;
  1523:       olgLength = 2;
  1524:     }
  1525:     olgMakeWaveData ();
  1526:     olgPaint ();
  1527:   }
  1528: 
  1529:   //  ログをセーブする
  1530:   public static void olgSaveLog () {
  1531:     byte[] b = new byte[4 * olgLength];
  1532:     for (int i = 0; i < olgLength; i++) {
  1533:       int t = olgBuffer[i];
  1534:       b[4 * i    ] = (byte) (t >> 24);  //big-endian
  1535:       b[4 * i + 1] = (byte) (t >> 16);
  1536:       b[4 * i + 2] = (byte) (t >>  8);
  1537:       b[4 * i + 3] = (byte)  t;
  1538:     }
  1539:     JFileChooser2 fileChooser = new JFileChooser2 (olgLastFile);
  1540:     fileChooser.setFileFilter (olgFilter);
  1541:     if (fileChooser.showSaveDialog (null) != JFileChooser.APPROVE_OPTION) {
  1542:       return;
  1543:     }
  1544:     File file = fileChooser.getSelectedFile ();
  1545:     if (XEiJ.rscPutFile (file.getPath (), b, 0, 4 * olgLength)) {
  1546:       olgLastFile = file;
  1547:     }
  1548:   }
  1549: 
  1550:   //アジャスト
  1551:   //  機能
  1552:   //    時刻が0でないレジスタ設定を最初のキーオンの時刻が1000000の倍数になるようにずらす
  1553:   //  手順
  1554:   //    時刻が0でない最初のレジスタ設定またはエンドコードの時刻us1を探す
  1555:   //    時刻が0でない最初のキーオンの時刻us2を探す。なければ何もしない
  1556:   //    us2-us1+1を1000000の倍数に切り上げた時刻us3を求める
  1557:   //    時刻が0でないレジスタ設定またはエンドコードの時刻にus3-us2を加える
  1558:   public static void olgAdjust () {
  1559:     int us1 = 0;  //時刻が0でない最初のレジスタ設定またはエンドコードの時刻
  1560:     int us2 = 0;  //時刻が0でない最初のキーオンの時刻
  1561:     for (int i = 0; i < olgLength; i += 2) {
  1562:       int us = olgBuffer[i];
  1563:       if (us == 0) {
  1564:         continue;
  1565:       }
  1566:       int ad = olgBuffer[i + 1];
  1567:       if (us1 == 0) {  //最初のレジスタ設定またはエンドコード
  1568:         us1 = us;
  1569:       }
  1570:       if (ad == 0x10000) {  //CSMKON
  1571:         us2 = us;
  1572:         break;
  1573:       }
  1574:       if (ad != -1) {  //CSMKON,END以外
  1575:         int a = ad >> 8;
  1576:         int d = ad & 255;
  1577:         if (a == 0x08) {  //KON
  1578:           int mask = (d >> 3) & 15;
  1579:           if (mask != 0) {  //キーオン
  1580:             us2 = us;
  1581:             break;
  1582:           }
  1583:         }
  1584:       }
  1585:     }  //for i
  1586:     if (us2 == 0) {  //キーオンがない
  1587:       return;
  1588:     }
  1589:     int us3 = ((us2 - us1 + 1 + (1000000 - 1)) / 1000000) * 1000000;  //us2-us1+1を1000000の倍数に切り上げた時刻
  1590:     for (int i = 0; i < olgLength; i += 2) {
  1591:       int us = olgBuffer[i];
  1592:       if (us == 0) {
  1593:         continue;
  1594:       }
  1595:       olgBuffer[i] += us3 - us2;
  1596:     }
  1597:     olgPaint ();
  1598:   }
  1599: 
  1600:   private static final int VOLUME = 1024;  //1024=1
  1601:   private static int samples62500;  //62500Hzのサンプル数
  1602:   private static int samples48000;  //48000Hzのサンプル数
  1603:   private static byte[] sampleBuffer;  //出力するデータ
  1604:   private static SourceDataLine sourceDataLine;  //ライン
  1605:   private static int totalBytes;  //出力するバイト数
  1606:   private static int writtenBytes;  //出力したバイト数
  1607:   private static TimerTask playTask;  //再生タスク
  1608: 
  1609:   //波形データを作る
  1610:   public static void olgMakeWaveData () {
  1611:     olgYM2151.reset ();
  1612:     olgYM2151.setChannelMask ((olgChannelMask[0] ? 1 << 0 : 0) |
  1613:                               (olgChannelMask[1] ? 1 << 1 : 0) |
  1614:                               (olgChannelMask[2] ? 1 << 2 : 0) |
  1615:                               (olgChannelMask[3] ? 1 << 3 : 0) |
  1616:                               (olgChannelMask[4] ? 1 << 4 : 0) |
  1617:                               (olgChannelMask[5] ? 1 << 5 : 0) |
  1618:                               (olgChannelMask[6] ? 1 << 6 : 0) |
  1619:                               (olgChannelMask[7] ? 1 << 7 : 0));
  1620:     samples62500 = 0;
  1621:     samples48000 = 0;
  1622:     int totalUs = olgBuffer[olgLength - 2];
  1623:     if (totalUs == 0) {
  1624:       return;
  1625:     }
  1626:     //OPM→int[]
  1627:     samples62500 = totalUs >> 4;  //1000000/62500=16
  1628:     olgYM2151.allocate (OPM_CHANNELS * samples62500);
  1629:     opmBuffer = olgYM2151.getBuffer ();
  1630:     int minUS = 0;
  1631:     for (int index = 0; index < olgLength; index += 2) {
  1632:       int us = olgBuffer[index];
  1633:       int ad = olgBuffer[index + 1];
  1634:       if (ad == -1) {
  1635:         break;
  1636:       }
  1637:       us = Math.max (minUS, us);
  1638:       int pointer = OPM_CHANNELS * (us >> 4);
  1639:       if (OPM_CHANNELS * samples62500 <= pointer) {
  1640:         break;
  1641:       }
  1642:       minUS = us + 16;  //次の時刻の最小値。パラメータは同時刻に書き込めるがキーオンとキーオフは同時刻に書き込めない
  1643:       olgYM2151.generate (pointer);
  1644:       if (ad == 0x10000) {  //CSMKON
  1645:         olgYM2151.timerAExpired ();
  1646:       } else {
  1647:         olgYM2151.writeAddress (ad >> 8);
  1648:         olgYM2151.writeData (ad);
  1649:       }
  1650:     }
  1651:     olgYM2151.fill ();
  1652:     //int[]→byte[]
  1653:     samples48000 = (int) (((long) samples62500 * 48000L) / 62500L);
  1654:     sampleBuffer = new byte[2 * OPM_CHANNELS * samples48000];
  1655:     for (int i48000 = 0; i48000 < samples48000; i48000++) {
  1656:       int i62500 = (int) (((long) i48000 * 62500L) / 48000L);
  1657:       if (OPM_CHANNELS == 1) {
  1658:         int m = (opmBuffer[i62500] * VOLUME) >> 10;
  1659:         m = Math.max (-32768, Math.min (32767, m));
  1660:         sampleBuffer[2 * i48000    ] = (byte) m;
  1661:         sampleBuffer[2 * i48000 + 1] = (byte) (m >> 8);
  1662:       } else {
  1663:         int l = (opmBuffer[2 * i62500    ] * VOLUME) >> 10;
  1664:         int r = (opmBuffer[2 * i62500 + 1] * VOLUME) >> 10;
  1665:         l = Math.max (-32768, Math.min (32767, l));
  1666:         r = Math.max (-32768, Math.min (32767, r));
  1667:         sampleBuffer[4 * i48000    ] = (byte) l;
  1668:         sampleBuffer[4 * i48000 + 1] = (byte) (l >> 8);
  1669:         sampleBuffer[4 * i48000 + 2] = (byte) r;
  1670:         sampleBuffer[4 * i48000 + 3] = (byte) (r >> 8);
  1671:       }
  1672:     }
  1673:   }
  1674: 
  1675:   //再生開始
  1676:   //  バッファサイズ500msでラインを開く
  1677:   //  100msずつ割り込む
  1678:   //    出力するデータがあるとき
  1679:   //      200msまたは残り全部の少ない方を求める
  1680:   //      出力できるとき
  1681:   //        出力する
  1682:   //    出力するデータがないとき
  1683:   //      ラインが空のとき
  1684:   //        ラインを閉じる
  1685:   public static void olgPlayStart () {
  1686:     int fromUs = olgMaskLeftUs != -1 ? olgMaskLeftUs : 0;
  1687:     int toUs = olgMaskRightUs != -1 ? olgMaskRightUs : olgBuffer[olgLength - 2];
  1688:     int from62500 = fromUs >> 4;  //1000000/62500=16
  1689:     int to62500 = toUs >> 4;  //1000000/62500=16
  1690:     int from48000 = (int) (((long) from62500 * 48000L) / 62500L);
  1691:     int to48000 = (int) (((long) to62500 * 48000L) / 62500L);
  1692:     int fromBytes = 2 * OPM_CHANNELS * from48000;
  1693:     int toBytes = 2 * OPM_CHANNELS * to48000;
  1694:     totalBytes = toBytes;
  1695:     writtenBytes = fromBytes;
  1696:     playTask = null;
  1697:     //バッファサイズ500msでラインを開く
  1698:     try {
  1699:       AudioFormat audioFormat = new AudioFormat (48000.0F,  //sampleRate
  1700:                                                  16,  //sampleSizeInBits
  1701:                                                  OPM_CHANNELS,  //channels
  1702:                                                  true,  //signed
  1703:                                                  false);  //bigEndian
  1704:       sourceDataLine = AudioSystem.getSourceDataLine (audioFormat);
  1705:       sourceDataLine.open (audioFormat, 2 * OPM_CHANNELS * 48000 * 500 / 1000);
  1706:       sourceDataLine.start ();
  1707:     } catch (Exception e) {
  1708:       e.printStackTrace ();
  1709:       return;
  1710:     }
  1711:     //100msずつ割り込む
  1712:     playTask = new TimerTask () {
  1713:       @Override public void run () {
  1714:         if (writtenBytes < totalBytes) {  //出力するデータがあるとき
  1715:           //200msまたは残り全部の少ない方を求める
  1716:           int thisTimeBytes = Math.min (2 * OPM_CHANNELS * 48000 * 200 / 1000, totalBytes - writtenBytes);
  1717:           if (thisTimeBytes <= sourceDataLine.available ()) {  //出力できるとき
  1718:             //出力する
  1719:             try {
  1720:               sourceDataLine.write (sampleBuffer, writtenBytes, thisTimeBytes);
  1721:               writtenBytes += thisTimeBytes;
  1722:             } catch (Exception e) {
  1723:               e.printStackTrace ();
  1724:               writtenBytes = totalBytes;
  1725:             }
  1726:           }
  1727:         } else {  //出力するデータがないとき
  1728:           if (!sourceDataLine.isRunning ()) {  //ラインが空のとき
  1729:             //ラインを閉じる
  1730:             playTask.cancel ();
  1731:             playTask = null;
  1732:             try {
  1733:               sourceDataLine.stop ();
  1734:               sourceDataLine.close ();
  1735:             } catch (Exception e) {
  1736:               e.printStackTrace ();
  1737:             }
  1738:           }
  1739:         }
  1740:       }
  1741:     };
  1742:     olgTimer.scheduleAtFixedRate (playTask, 0L, 100L);
  1743:   }
  1744: 
  1745:   //再生終了
  1746:   //  出力するデータの残りを0にする
  1747:   public static void olgPlayEnd () {
  1748:     writtenBytes = totalBytes;
  1749:   }
  1750: 
  1751: }