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