OSDN Git Service

リファクタリング
[midichordhelper/MIDIChordHelper.git] / src / camidion / chordhelper / ChordHelperApplet.java
1 package camidion.chordhelper;
2 import java.awt.BorderLayout;
3 import java.awt.Color;
4 import java.awt.Desktop;
5 import java.awt.Dimension;
6 import java.awt.Image;
7 import java.awt.Insets;
8 import java.awt.dnd.DnDConstants;
9 import java.awt.dnd.DropTarget;
10 import java.awt.event.ActionEvent;
11 import java.awt.event.ActionListener;
12 import java.awt.event.ComponentAdapter;
13 import java.awt.event.ComponentEvent;
14 import java.awt.event.InputEvent;
15 import java.awt.event.ItemEvent;
16 import java.awt.event.ItemListener;
17 import java.awt.event.MouseAdapter;
18 import java.awt.event.MouseEvent;
19 import java.io.IOException;
20 import java.net.URI;
21 import java.net.URISyntaxException;
22 import java.net.URL;
23 import java.security.AccessControlException;
24 import java.util.Arrays;
25
26 import javax.sound.midi.InvalidMidiDataException;
27 import javax.sound.midi.MetaEventListener;
28 import javax.sound.midi.MetaMessage;
29 import javax.sound.midi.Sequencer;
30 import javax.swing.AbstractAction;
31 import javax.swing.Action;
32 import javax.swing.Box;
33 import javax.swing.BoxLayout;
34 import javax.swing.ImageIcon;
35 import javax.swing.JApplet;
36 import javax.swing.JButton;
37 import javax.swing.JComponent;
38 import javax.swing.JEditorPane;
39 import javax.swing.JLabel;
40 import javax.swing.JLayeredPane;
41 import javax.swing.JOptionPane;
42 import javax.swing.JPanel;
43 import javax.swing.JSlider;
44 import javax.swing.JSplitPane;
45 import javax.swing.JToggleButton;
46 import javax.swing.SwingUtilities;
47 import javax.swing.border.Border;
48 import javax.swing.event.ChangeEvent;
49 import javax.swing.event.ChangeListener;
50 import javax.swing.event.HyperlinkEvent;
51 import javax.swing.event.HyperlinkListener;
52
53 import camidion.chordhelper.anogakki.AnoGakkiPane;
54 import camidion.chordhelper.chorddiagram.CapoComboBoxModel;
55 import camidion.chordhelper.chorddiagram.ChordDiagram;
56 import camidion.chordhelper.chordmatrix.ChordButtonLabel;
57 import camidion.chordhelper.chordmatrix.ChordMatrix;
58 import camidion.chordhelper.chordmatrix.ChordMatrixListener;
59 import camidion.chordhelper.mididevice.MidiDeviceDialog;
60 import camidion.chordhelper.mididevice.MidiSequencerModel;
61 import camidion.chordhelper.mididevice.MidiTransceiverListModelList;
62 import camidion.chordhelper.mididevice.SequencerMeasureView;
63 import camidion.chordhelper.mididevice.SequencerTimeView;
64 import camidion.chordhelper.mididevice.VirtualMidiDevice;
65 import camidion.chordhelper.midieditor.Base64Dialog;
66 import camidion.chordhelper.midieditor.KeySignatureLabel;
67 import camidion.chordhelper.midieditor.MidiSequenceEditor;
68 import camidion.chordhelper.midieditor.NewSequenceDialog;
69 import camidion.chordhelper.midieditor.PlaylistTableModel;
70 import camidion.chordhelper.midieditor.SequenceTickIndex;
71 import camidion.chordhelper.midieditor.SequenceTrackListTableModel;
72 import camidion.chordhelper.midieditor.TempoSelecter;
73 import camidion.chordhelper.midieditor.TimeSignatureSelecter;
74 import camidion.chordhelper.music.Chord;
75 import camidion.chordhelper.music.Key;
76 import camidion.chordhelper.music.Range;
77 import camidion.chordhelper.pianokeyboard.MidiKeyboardPanel;
78 import camidion.chordhelper.pianokeyboard.PianoKeyboardAdapter;
79
80 /**
81  * MIDI Chord Helper - Circle-of-fifth oriented chord pad
82  * (アプレットクラス)
83  *
84  *      @auther
85  *              Copyright (C) 2004-2016 @きよし - Akiyoshi Kamide
86  *              http://www.yk.rim.or.jp/~kamide/music/chordhelper/
87  */
88 public class ChordHelperApplet extends JApplet {
89         /////////////////////////////////////////////////////////////////////
90         //
91         // JavaScript などからの呼び出しインターフェース
92         //
93         /////////////////////////////////////////////////////////////////////
94         /**
95          * 未保存の修正済み MIDI ファイルがあるかどうか調べます。
96          * @return 未保存の修正済み MIDI ファイルがあれば true
97          */
98         public boolean isModified() { return playlistModel.isModified(); }
99         /**
100          * 指定された小節数の曲を、乱数で自動作曲してプレイリストへ追加します。
101          * @param measureLength 小節数
102          * @return 追加先のインデックス値(0から始まる)。追加できなかったときは -1
103          */
104         public int addRandomSongToPlaylist(int measureLength) {
105                 NewSequenceDialog d = midiEditor.newSequenceDialog;
106                 d.setRandomChordProgression(measureLength);
107                 return playlistModel.addSequenceAndPlay(d.getMidiSequence());
108         }
109         /**
110          * URLで指定されたMIDIファイルをプレイリストへ追加します。
111          *
112          * <p>URL の最後の / より後ろの部分がファイル名として取り込まれます。
113          * 指定できる MIDI ファイルには、param タグの midi_file パラメータと同様の制限があります。
114          * </p>
115          * @param midiFileUrl 追加するMIDIファイルのURL
116          * @return 追加先のインデックス値(0から始まる)。追加できなかったときは -1
117          */
118         public int addToPlaylist(String midiFileUrl) {
119                 try {
120                         return playlistModel.addSequenceFromURL(midiFileUrl);
121                 } catch( URISyntaxException|IOException|InvalidMidiDataException e ) {
122                         midiEditor.showWarning(e.getMessage());
123                 } catch( AccessControlException e ) {
124                         e.printStackTrace();
125                         midiEditor.showError(e.getMessage());
126                 }
127                 return -1;
128         }
129         /**
130          * Base64 エンコードされた MIDI ファイルをプレイリストへ追加します。
131          *
132          * @param base64EncodedText Base64エンコードされたMIDIファイル
133          * @return 追加先のインデックス値(0から始まる)。追加できなかったときは -1
134          */
135         public int addToPlaylistBase64(String base64EncodedText) {
136                 return addToPlaylistBase64(base64EncodedText, null);
137         }
138         /**
139          * ファイル名を指定して、
140          * Base64エンコードされたMIDIファイルをプレイリストへ追加します。
141          *
142          * @param base64EncodedText Base64エンコードされたMIDIファイル
143          * @param filename ディレクトリ名を除いたファイル名
144          * @return 追加先のインデックス値(0から始まる)。追加できなかったときは -1
145          */
146         public int addToPlaylistBase64(String base64EncodedText, String filename) {
147                 Base64Dialog d = midiEditor.base64Dialog;
148                 d.setBase64Data(base64EncodedText);
149                 try {
150                         return playlistModel.addSequence(d.getMIDIData(), filename);
151                 } catch (IOException | InvalidMidiDataException e) {
152                         e.printStackTrace();
153                         midiEditor.showWarning(e.getMessage());
154                         return -1;
155                 }
156         }
157         /**
158          * プレイリスト上で現在選択されているMIDIシーケンスを、
159          * シーケンサへロードして再生します。
160          */
161         public void play() { play(playlistModel.sequenceListSelectionModel.getMinSelectionIndex()); }
162         /**
163          * 指定されたインデックス値が示すプレイリスト上のMIDIシーケンスを、
164          * シーケンサへロードして再生します。
165          * @param index インデックス値(0から始まる)
166          */
167         public void play(int index) { playlistModel.loadToSequencer(index); sequencerModel.start(); }
168         /**
169          * シーケンサが実行中かどうかを返します。
170          * {@link Sequencer#isRunning()} の戻り値をそのまま返します。
171          *
172          * @return 実行中のときtrue
173          */
174         public boolean isRunning() { return sequencerModel.getSequencer().isRunning(); }
175         /**
176          * シーケンサが再生中かどうかを返します。
177          * @return 再生中のときtrue
178          */
179         public boolean isPlaying() { return isRunning(); }
180         /**
181          * 現在シーケンサにロードされているMIDIデータを
182          * Base64テキストに変換した結果を返します。
183          * @return MIDIデータをBase64テキストに変換した結果
184          */
185         public String getMidiDataBase64() {
186                 SequenceTrackListTableModel sequenceModel = sequencerModel.getSequenceTrackListTableModel();
187                 midiEditor.base64Dialog.setMIDIData(sequenceModel.getMIDIdata());
188                 return midiEditor.base64Dialog.getBase64Data();
189         }
190         /**
191          * 現在シーケンサにロードされているMIDIファイルのファイル名を返します。
192          * @return MIDIファイル名(設定されていないときは空文字列)
193          */
194         public String getMidiFilename() {
195                 SequenceTrackListTableModel seq_model = sequencerModel.getSequenceTrackListTableModel();
196                 if( seq_model == null ) return null;
197                 String fn = seq_model.getFilename();
198                 return fn == null ? "" : fn ;
199         }
200         /**
201          * オクターブ位置を設定します。
202          * @param octavePosition オクターブ位置(デフォルト:4)
203          */
204         public void setOctavePosition(int octavePosition) {
205                 keyboardPanel.keyboardCenterPanel.keyboard.octaveRangeModel.setValue(octavePosition);
206         }
207         /**
208          * 操作対象のMIDIチャンネルを変更します。
209          * @param ch チャンネル番号 - 1(チャンネル1のとき0、デフォルトは0)
210          */
211         public void setChannel(int ch) {
212                 keyboardPanel.keyboardCenterPanel.keyboard.midiChComboboxModel.setSelectedChannel(ch);
213         }
214         /**
215          * 操作対象のMIDIチャンネルを返します。
216          * @return 操作対象のMIDIチャンネル
217          */
218         public int getChannel() {
219                 return keyboardPanel.keyboardCenterPanel.keyboard.midiChComboboxModel.getSelectedChannel();
220         }
221         /**
222          * 操作対象のMIDIチャンネルに対してプログラム(音色)を設定します。
223          * @param program 音色(0~127:General MIDI に基づく)
224          */
225         public void programChange(int program) {
226                 keyboardPanel.keyboardCenterPanel.keyboard.getSelectedChannel().programChange(program);
227         }
228         /**
229          * 操作対象のMIDIチャンネルに対してプログラム(音色)を設定します。
230          * 内部的には {@link #programChange(int)} を呼び出しているだけです。
231          * @param program 音色(0~127:General MIDI に基づく)
232          */
233         public void setProgram(int program) { programChange(program); }
234         /**
235          * 自動転回モードを変更します。初期値は true です。
236          * @param isAuto true:自動転回を行う false:自動転回を行わない
237          */
238         public void setAutoInversion(boolean isAuto) {
239                 inversionOmissionButton.setAutoInversion(isAuto);
240         }
241         /**
242          * 省略したい構成音を指定します。
243          * @param index
244          * <ul>
245          * <li>-1:省略しない(デフォルト)</li>
246          * <li>0:ルート音を省略</li>
247          * <li>1:三度を省略</li>
248          * <li>2:五度を省略</li>
249          * </ul>
250          */
251         public void setOmissionNoteIndex(int index) {
252                 inversionOmissionButton.setOmissionNoteIndex(index);
253         }
254         /**
255          * コードダイアグラムの表示・非表示を切り替えます。
256          * @param isVisible 表示するときtrue
257          */
258         public void setChordDiagramVisible(boolean isVisible) {
259                 keyboardSplitPane.resetToPreferredSizes();
260                 if( ! isVisible )
261                         keyboardSplitPane.setDividerLocation((double)1.0);
262         }
263         /**
264          * コードダイヤグラムをギターモードに変更します。
265          * 初期状態ではウクレレモードになっています。
266          */
267         public void setChordDiagramForGuitar() {
268                 chordDiagram.setTargetInstrument(ChordDiagram.Instrument.Guitar);
269         }
270         /**
271          * ダークモード(暗い表示)と明るい表示とを切り替えます。
272          * @param isDark ダークモードのときtrue、明るい表示のときfalse(デフォルト)
273          */
274         public void setDarkMode(boolean isDark) {
275                 darkModeToggleButton.setSelected(isDark);
276         }
277         /**
278          * バージョン情報
279          */
280         public static class VersionInfo {
281                 public static final String      NAME = "MIDI Chord Helper";
282                 public static final String      VERSION = "Ver.20160528.1";
283                 public static final String      COPYRIGHT = "Copyright (C) 2004-2016";
284                 public static final String      AUTHER = "@きよし - Akiyoshi Kamide";
285                 public static final String      URL = "http://www.yk.rim.or.jp/~kamide/music/chordhelper/";
286                 /**
287                  * バージョン情報を返します。
288                  * @return バージョン情報
289                  */
290                 public static String getInfo() {
291                         return NAME + " " + VERSION + " " + COPYRIGHT + " " + AUTHER + " " + URL;
292                 }
293         }
294         @Override
295         public String getAppletInfo() { return VersionInfo.getInfo(); }
296         private class AboutMessagePane extends JEditorPane {
297                 URI uri = null;
298                 public AboutMessagePane() { this(true); }
299                 public AboutMessagePane(boolean link_enabled) {
300                         super( "text/html", "" );
301                         String link_string, tooltip = null;
302                         if( link_enabled && Desktop.isDesktopSupported() ) {
303                                 tooltip = "Click this URL to open with your web browser - URLをクリックしてWebブラウザで開く";
304                                 link_string =
305                                         "<a href=\"" + VersionInfo.URL + "\" title=\"" +
306                                         tooltip + "\">" + VersionInfo.URL + "</a>" ;
307                         }
308                         else {
309                                 link_enabled = false; link_string = VersionInfo.URL;
310                         }
311                         setText(
312                                 "<html><center><font size=\"+1\">" + VersionInfo.NAME + "</font>  " +
313                                                 VersionInfo.VERSION + "<br/><br/>" +
314                                                 VersionInfo.COPYRIGHT + " " + VersionInfo.AUTHER + "<br/>" +
315                                                 link_string + "</center></html>"
316                         );
317                         setToolTipText(tooltip);
318                         setOpaque(false);
319                         putClientProperty(JEditorPane.HONOR_DISPLAY_PROPERTIES, Boolean.TRUE);
320                         setEditable(false);
321                         //
322                         // メッセージ内の <a href=""> ~ </a> によるリンクを
323                         // 実際に機能させる(ブラウザで表示されるようにする)ための設定
324                         //
325                         if( ! link_enabled ) return;
326                         try {
327                                 uri = new URI(VersionInfo.URL);
328                         }catch( URISyntaxException use ) {
329                                 use.printStackTrace();
330                                 return;
331                         }
332                         addHyperlinkListener(new HyperlinkListener() {
333                                 public void hyperlinkUpdate(HyperlinkEvent e) {
334                                         if(e.getEventType()==HyperlinkEvent.EventType.ACTIVATED) {
335                                                 try{
336                                                         Desktop.getDesktop().browse(uri);
337                                                 }catch(IOException ioe) {
338                                                         ioe.printStackTrace();
339                                                 }
340                                         }
341                                 }
342                         });
343                 }
344                 /**
345                  * バージョン情報を開くアクション
346                  */
347                 public Action openAction = new AbstractAction() {
348                         {
349                                 putValue(NAME, "Version info");
350                                 putValue(SHORT_DESCRIPTION, VersionInfo.NAME + " " + VersionInfo.VERSION);
351                         }
352                         @Override
353                         public void actionPerformed(ActionEvent e) {
354                                 JOptionPane.showMessageDialog(
355                                         null, AboutMessagePane.this, getValue(NAME).toString(),
356                                         JOptionPane.INFORMATION_MESSAGE, imageIcon
357                                 );
358                         }
359                 };
360         }
361         /**
362          * アプリケーションのイメージアイコン
363          */
364         public ImageIcon imageIcon;
365         /**
366          * アプリケーションのアイコンイメージ
367          */
368         public Image iconImage;
369         /**
370          * ボタンの余白を詰めたいときに setMargin() の引数に指定するインセット
371          */
372         public static final Insets ZERO_INSETS = new Insets(0,0,0,0);
373         //
374         private static final String IMAGE_ICON_PATH = "midichordhelper.png";
375         //
376         MidiSequenceEditor midiEditor;
377         PlaylistTableModel playlistModel;
378         MidiSequencerModel sequencerModel;
379         public ChordMatrix chordMatrix;
380         private JPanel keyboardSequencerPanel;
381         private JPanel chordGuide;
382         private Color rootPaneDefaultBgcolor;
383         private Color lyricDisplayDefaultBgcolor;
384         private Border lyricDisplayDefaultBorder;
385         private JSplitPane mainSplitPane;
386         private JSplitPane keyboardSplitPane;
387         private ChordButtonLabel enterButtonLabel;
388         private ChordTextField  lyricDisplay;
389         private MidiKeyboardPanel keyboardPanel;
390         private InversionAndOmissionLabel inversionOmissionButton;
391         private JToggleButton darkModeToggleButton;
392         private MidiDeviceDialog midiDeviceDialog;
393         private ChordDiagram chordDiagram;
394         private TempoSelecter tempoSelecter;
395         private TimeSignatureSelecter timesigSelecter;
396         private KeySignatureLabel keysigLabel;
397         private JLabel songTitleLabel = new JLabel();
398         private AnoGakkiPane anoGakkiPane;
399         private JToggleButton anoGakkiToggleButton;
400
401         public void init() {
402                 //
403                 // アイコンイメージの取得
404                 URL imageIconUrl = getClass().getResource(IMAGE_ICON_PATH);
405                 if( imageIconUrl == null ) {
406                         System.out.println("Icon image "+IMAGE_ICON_PATH+" not found");
407                 }
408                 else {
409                         iconImage = (imageIcon = new ImageIcon(imageIconUrl)).getImage();
410                 }
411                 // 背景色の取得
412                 rootPaneDefaultBgcolor = getContentPane().getBackground();
413                 //
414                 // コードダイアグラム、コードボタン、ピアノ鍵盤のセットアップ
415                 CapoComboBoxModel capoValueModel = new CapoComboBoxModel();
416                 chordDiagram = new ChordDiagram(capoValueModel);
417                 chordMatrix = new ChordMatrix(capoValueModel) {{
418                         addChordMatrixListener(new ChordMatrixListener(){
419                                 public void keySignatureChanged() {
420                                         Key capoKey = getKeySignatureCapo();
421                                         keyboardPanel.keySelecter.setKey(capoKey);
422                                         keyboardPanel.keyboardCenterPanel.keyboard.setKeySignature(capoKey);
423                                 }
424                                 public void chordChanged() { chordOn(); }
425                         });
426                 }};
427                 keysigLabel = new KeySignatureLabel() {{
428                         addMouseListener(new MouseAdapter() {
429                                 public void mousePressed(MouseEvent e) { chordMatrix.setKeySignature(getKey()); }
430                         });
431                 }};
432                 chordMatrix.capoSelecter.checkbox.addItemListener(new ItemListener() {
433                         public void itemStateChanged(ItemEvent e) {
434                                 chordOn();
435                                 keyboardPanel.keyboardCenterPanel.keyboard.chordDisplay.clear();
436                                 chordDiagram.clear();
437                         }
438                 });
439                 chordMatrix.capoSelecter.valueSelecter.addActionListener(new ActionListener() {
440                         public void actionPerformed(ActionEvent e) {
441                                 chordOn();
442                                 keyboardPanel.keyboardCenterPanel.keyboard.chordDisplay.clear();
443                                 chordDiagram.clear();
444                         }
445                 });
446                 keyboardPanel = new MidiKeyboardPanel(chordMatrix) {{
447                         keyboardCenterPanel.keyboard.addPianoKeyboardListener(new PianoKeyboardAdapter() {
448                                 @Override
449                                 public void pianoKeyPressed(int n, InputEvent e) { chordDiagram.clear(); }
450                         });
451                         keySelecter.keysigCombobox.addActionListener(new ActionListener() {
452                                 @Override
453                                 public void actionPerformed(ActionEvent e) {
454                                         Key key = keySelecter.getKey();
455                                         key.transpose( - chordMatrix.capoSelecter.getCapo() );
456                                         chordMatrix.setKeySignature(key);
457                                 }
458                         });
459                         keyboardCenterPanel.keyboard.setPreferredSize(new Dimension(571, 80));
460                 }};
461                 VirtualMidiDevice guiMidiDevice = keyboardPanel.keyboardCenterPanel.keyboard.midiDevice;
462                 //
463                 // MIDIデバイス一覧を構築
464                 MidiTransceiverListModelList deviceModelList = new MidiTransceiverListModelList(Arrays.asList(guiMidiDevice));
465                 (midiDeviceDialog = new MidiDeviceDialog(deviceModelList)).setIconImage(iconImage);
466                 //
467                 // MIDIデバイス一覧のシーケンサと連携するプレイリストを構築
468                 playlistModel = new PlaylistTableModel(sequencerModel = deviceModelList.getSequencerModel());
469                 //
470                 // MIDIエディタダイアログの構築
471                 (midiEditor = new MidiSequenceEditor(playlistModel, guiMidiDevice)).setIconImage(iconImage);
472                 //
473                 // メイン画面へのMIDIファイルのドラッグ&ドロップ受付開始
474                 new DropTarget(this, DnDConstants.ACTION_COPY_OR_MOVE, midiEditor.dropTargetListener, true);
475                 //
476                 // MIDIエディタのイベントダイアログを、ピアノ鍵盤のイベント送出ダイアログと共用
477                 keyboardPanel.setEventDialog(midiEditor.eventDialog);
478                 //
479                 // 歌詞表示
480                 lyricDisplay = new ChordTextField(sequencerModel) {{
481                         addActionListener(new ActionListener() {
482                                 @Override
483                                 public void actionPerformed(ActionEvent event) {
484                                         String symbol = event.getActionCommand().trim().split("[ \t\r\n]")[0];
485                                         chordMatrix.setSelectedChord(symbol);
486                                 }
487                         });
488                 }};
489                 lyricDisplayDefaultBorder = lyricDisplay.getBorder();
490                 lyricDisplayDefaultBgcolor = lyricDisplay.getBackground();
491                 //
492                 // メタイベント(テンポ・拍子・調号)を受信して表示するリスナーを登録
493                 Sequencer sequencer = sequencerModel.getSequencer();
494                 sequencer.addMetaEventListener(tempoSelecter = new TempoSelecter() {{ setEditable(false); }});
495                 sequencer.addMetaEventListener(timesigSelecter = new TimeSignatureSelecter() {{ setEditable(false); }});
496                 sequencer.addMetaEventListener(new MetaEventListener() {
497                         private Key key;
498                         @Override
499                         public void meta(MetaMessage msg) {
500                                 switch(msg.getType()) {
501                                 case 0x59: // Key signature (2 bytes) : 調号
502                                         key = new Key(msg.getData());
503                                         if( SwingUtilities.isEventDispatchThread() ) {
504                                                 keysigLabel.setKeySignature(key);
505                                                 chordMatrix.setKeySignature(key);
506                                         } else {
507                                                 // MIDIシーケンサのスレッドから呼ばれた場合、GUI更新は自分で行わず、
508                                                 // AWTイベントディスパッチスレッドに依頼する。
509                                                 SwingUtilities.invokeLater(new Runnable() {
510                                                         @Override
511                                                         public void run() {
512                                                                 keysigLabel.setKeySignature(key);
513                                                                 chordMatrix.setKeySignature(key);
514                                                         }
515                                                 });
516                                         }
517                                         break;
518                                 }
519                         }
520                 });
521                 //シーケンサーの時間スライダーの値が変わったときのリスナーを登録
522                 sequencerModel.addChangeListener(new ChangeListener() {
523                         @Override
524                         public void stateChanged(ChangeEvent e) {
525                                 SequenceTrackListTableModel sequenceTableModel = sequencerModel.getSequenceTrackListTableModel();
526                                 int loadedSequenceIndex = playlistModel.indexOfSequenceOnSequencer();
527                                 songTitleLabel.setText(
528                                         "<html>"+(
529                                                 loadedSequenceIndex < 0 ? "[No MIDI file loaded]" :
530                                                 "MIDI file " + loadedSequenceIndex + ": " + (
531                                                         sequenceTableModel == null ||
532                                                         sequenceTableModel.toString() == null ||
533                                                         sequenceTableModel.toString().isEmpty() ?
534                                                         "[Untitled]" :
535                                                         "<font color=maroon>"+sequenceTableModel+"</font>"
536                                                 )
537                                         )+"</html>"
538                                 );
539                                 Sequencer sequencer = sequencerModel.getSequencer();
540                                 chordMatrix.setPlaying(sequencer.isRunning());
541                                 if( sequenceTableModel != null ) {
542                                         SequenceTickIndex tickIndex = sequenceTableModel.getSequenceTickIndex();
543                                         long tickPos = sequencer.getTickPosition();
544                                         tickIndex.tickToMeasure(tickPos);
545                                         chordMatrix.setBeat(tickIndex);
546                                         if( sequencerModel.getValueIsAdjusting() || ! (sequencer.isRunning() || sequencer.isRecording()) ) {
547                                                 MetaMessage msg;
548                                                 msg = tickIndex.lastMetaMessageAt(
549                                                         SequenceTickIndex.MetaMessageType.TIME_SIGNATURE, tickPos
550                                                 );
551                                                 timesigSelecter.setValue(msg==null ? null : msg.getData());
552                                                 msg = tickIndex.lastMetaMessageAt(
553                                                         SequenceTickIndex.MetaMessageType.TEMPO, tickPos
554                                                 );
555                                                 tempoSelecter.setTempo(msg==null ? null : msg.getData());
556                                                 msg = tickIndex.lastMetaMessageAt(
557                                                         SequenceTickIndex.MetaMessageType.KEY_SIGNATURE, tickPos
558                                                 );
559                                                 if( msg == null ) {
560                                                         keysigLabel.clear();
561                                                 }
562                                                 else {
563                                                         Key key = new Key(msg.getData());
564                                                         keysigLabel.setKeySignature(key);
565                                                         chordMatrix.setKeySignature(key);
566                                                 }
567                                         }
568                                 }
569                         }
570                 });
571                 sequencerModel.fireStateChanged();
572                 chordGuide = new JPanel() {
573                         {
574                                 setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
575                                 add( Box.createHorizontalStrut(2) );
576                                 add( chordMatrix.chordGuide );
577                                 add( Box.createHorizontalStrut(2) );
578                                 add( lyricDisplay );
579                                 add( Box.createHorizontalStrut(2) );
580                                 add( enterButtonLabel = new ChordButtonLabel("Enter",chordMatrix) {{
581                                         addMouseListener(new MouseAdapter() {
582                                                 public void mousePressed(MouseEvent event) {
583                                                         if( (event.getModifiersEx() & InputEvent.BUTTON3_DOWN_MASK) != 0 ) // RightClicked
584                                                                 chordMatrix.setSelectedChord((Chord)null);
585                                                         else {
586                                                                 chordMatrix.setSelectedChord(lyricDisplay.getText());
587                                                         }
588                                                 }
589                                         });
590                                 }});
591                                 add( Box.createHorizontalStrut(5) );
592                                 add( chordMatrix.chordDisplay );
593                                 add( Box.createHorizontalStrut(5) );
594                                 add( darkModeToggleButton = new JToggleButton(new ButtonIcon(ButtonIcon.DARK_MODE_ICON)) {{
595                                         setMargin(ZERO_INSETS);
596                                         addItemListener(new ItemListener() {
597                                                 public void itemStateChanged(ItemEvent e) {
598                                                         innerSetDarkMode(darkModeToggleButton.isSelected());
599                                                 }
600                                         });
601                                         setToolTipText("Light / Dark - 明かりを点灯/消灯");
602                                         setBorder(null);
603                                 }});
604                                 add( Box.createHorizontalStrut(5) );
605                                 add( anoGakkiToggleButton = new JToggleButton(new ButtonIcon(ButtonIcon.ANO_GAKKI_ICON)) {{
606                                         setOpaque(false);
607                                         setMargin(ZERO_INSETS);
608                                         setBorder( null );
609                                         setToolTipText("あの楽器");
610                                         addItemListener(new ItemListener() {
611                                                 public void itemStateChanged(ItemEvent e) {
612                                                         keyboardPanel.keyboardCenterPanel.keyboard.anoGakkiPane
613                                                         = anoGakkiToggleButton.isSelected() ? anoGakkiPane : null ;
614                                                 }
615                                         });
616                                 }} );
617                                 add( Box.createHorizontalStrut(5) );
618                                 add( inversionOmissionButton = new InversionAndOmissionLabel() );
619                                 add( Box.createHorizontalStrut(5) );
620                                 add( chordMatrix.capoSelecter );
621                                 add( Box.createHorizontalStrut(2) );
622                         }
623                 };
624                 keyboardSequencerPanel = new JPanel() {{
625                         setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
626                         add(chordGuide);
627                         add(Box.createVerticalStrut(5));
628                         add(keyboardSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, keyboardPanel, chordDiagram) {{
629                                 setOneTouchExpandable(true);
630                                 setResizeWeight(1.0);
631                                 setAlignmentX((float)0.5);
632                         }});
633                         add(Box.createVerticalStrut(5));
634                         add(new JPanel() {{
635                                 setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
636                                 add(new JPanel() {{
637                                         setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
638                                         add( Box.createHorizontalStrut(12) );
639                                         add( keysigLabel );
640                                         add( Box.createHorizontalStrut(12) );
641                                         add( timesigSelecter );
642                                         add( Box.createHorizontalStrut(12) );
643                                         add( tempoSelecter );
644                                         add( Box.createHorizontalStrut(12) );
645                                         add( new SequencerMeasureView(sequencerModel) );
646                                         add( Box.createHorizontalStrut(12) );
647                                         add( songTitleLabel );
648                                         add( Box.createHorizontalStrut(12) );
649                                         add( new JButton(midiEditor.openAction) {{ setMargin(ZERO_INSETS); }});
650                                 }});
651                                 add(new JPanel() {{
652                                         setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
653                                         add(Box.createHorizontalStrut(10));
654                                         add(new JSlider(sequencerModel));
655                                         add(new SequencerTimeView(sequencerModel));
656                                         add(Box.createHorizontalStrut(5));
657                                         add(new JButton(playlistModel.moveToTopAction) {{ setMargin(ZERO_INSETS); }});
658                                         add(new JButton(sequencerModel.moveBackwardAction) {{ setMargin(ZERO_INSETS); }});
659                                         add(new JToggleButton(sequencerModel.startStopAction));
660                                         add(new JButton(sequencerModel.moveForwardAction) {{ setMargin(ZERO_INSETS); }});
661                                         add(new JButton(playlistModel.moveToBottomAction) {{ setMargin(ZERO_INSETS); }});
662                                         add(new JToggleButton(playlistModel.toggleRepeatAction) {{ setMargin(ZERO_INSETS); }});
663                                         add( Box.createHorizontalStrut(10) );
664                                 }});
665                                 add(new JPanel() {{
666                                         add(new JButton(midiDeviceDialog.openAction));
667                                         add(new JButton((new AboutMessagePane()).openAction));
668                                 }});
669                         }});
670                 }};
671                 setContentPane(new JLayeredPane() {
672                         {
673                                 add(anoGakkiPane = new AnoGakkiPane(), JLayeredPane.PALETTE_LAYER);
674                                 addComponentListener(new ComponentAdapter() {
675                                         @Override
676                                         public void componentResized(ComponentEvent e) { adjustSize(); }
677                                         @Override
678                                         public void componentShown(ComponentEvent e) { adjustSize(); }
679                                         private void adjustSize() { anoGakkiPane.setBounds(getBounds()); }
680                                 });
681                                 setLayout(new BorderLayout());
682                                 setOpaque(true);
683                                 add(mainSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, chordMatrix, keyboardSequencerPanel){
684                                         {
685                                                 setResizeWeight(0.5);
686                                                 setAlignmentX((float)0.5);
687                                                 setDividerSize(5);
688                                         }
689                                 });
690                         }
691                 });
692                 setPreferredSize(new Dimension(750,470));
693         }
694         @Override
695         public void start() {
696                 //
697                 // コードボタンで設定されている現在の調を
698                 // ピアノキーボードに伝える
699                 chordMatrix.fireKeySignatureChanged();
700                 //
701                 // アプレットのパラメータにMIDIファイルのURLが指定されていたら
702                 // それを再生する
703                 String midi_url = getParameter("midi_file");
704                 System.gc();
705                 if( midi_url != null ) {
706                         addToPlaylist(midi_url);
707                         play();
708                 }
709         }
710         @Override
711         public void stop() {
712                 sequencerModel.stop(); // MIDI再生を強制終了
713                 System.gc();
714         }
715         private void innerSetDarkMode(boolean isDark) {
716                 Color col = isDark ? Color.black : null;
717                 getContentPane().setBackground(isDark ? Color.black : rootPaneDefaultBgcolor);
718                 mainSplitPane.setBackground(col);
719                 keyboardSplitPane.setBackground(col);
720                 enterButtonLabel.setDarkMode(isDark);
721                 chordGuide.setBackground(col);
722                 lyricDisplay.setBorder(isDark ? null : lyricDisplayDefaultBorder);
723                 lyricDisplay.setBackground(isDark ?
724                         chordMatrix.darkModeColorset.backgrounds[2] :
725                         lyricDisplayDefaultBgcolor
726                 );
727                 lyricDisplay.setForeground(isDark ? Color.white : null);
728                 inversionOmissionButton.setBackground(col);
729                 anoGakkiToggleButton.setBackground(col);
730                 keyboardSequencerPanel.setBackground(col);
731                 chordDiagram.setBackground(col);
732                 chordDiagram.titleLabel.setDarkMode(isDark);
733                 chordMatrix.setDarkMode(isDark);
734                 keyboardPanel.setDarkMode(isDark);
735         }
736
737         private int[] chordOnNotes = null;
738         /**
739          * 和音を発音します。
740          * <p>この関数を直接呼ぶとアルペジオが効かないので、
741          * chord_matrix.setSelectedChord() を使うことを推奨
742          * </p>
743          */
744         public void chordOn() {
745                 Chord playChord = chordMatrix.getSelectedChord();
746                 if(
747                         chordOnNotes != null &&
748                         chordMatrix.getNoteIndex() < 0 &&
749                         (! chordMatrix.isDragged() || playChord == null)
750                 ) {
751                         // コードが鳴っている状態で、新たなコードを鳴らそうとしたり、
752                         // もう鳴らさないという信号が来た場合は、今鳴っている音を止める。
753                         //
754                         for( int n : chordOnNotes )
755                                 keyboardPanel.keyboardCenterPanel.keyboard.noteOff(n);
756                         chordOnNotes = null;
757                 }
758                 if( playChord == null ) {
759                         // もう鳴らさないので、歌詞表示に通知して終了
760                         if( lyricDisplay != null )
761                                 lyricDisplay.appendChord(null);
762                         return;
763                 }
764                 // あの楽器っぽい表示
765                 if( keyboardPanel.keyboardCenterPanel.keyboard.anoGakkiPane != null ) {
766                         JComponent btn = chordMatrix.getSelectedButton();
767                         if( btn != null ) anoGakkiPane.start(chordMatrix, btn.getBounds());
768                 }
769                 // コードボタンからのコードを、カポつき演奏キーからオリジナルキーへ変換
770                 Key originalKey = chordMatrix.getKeySignatureCapo();
771                 Chord originalChord = playChord.clone().transpose(
772                         chordMatrix.capoSelecter.getCapo(),
773                         chordMatrix.getKeySignature()
774                 );
775                 // 変換後のコードをキーボード画面に設定
776                 keyboardPanel.keyboardCenterPanel.keyboard.setChord(originalChord);
777                 //
778                 // 音域を決める。これにより鳴らす音が確定する。
779                 Range chordRange = new Range(
780                         keyboardPanel.keyboardCenterPanel.keyboard.getChromaticOffset() + 10 +
781                         ( keyboardPanel.keyboardCenterPanel.keyboard.getOctaves() / 4 ) * 12,
782                         inversionOmissionButton.isAutoInversionMode() ?
783                         keyboardPanel.keyboardCenterPanel.keyboard.getChromaticOffset() + 21 :
784                         keyboardPanel.keyboardCenterPanel.keyboard.getChromaticOffset() + 33,
785                         -2,
786                         inversionOmissionButton.isAutoInversionMode()
787                 );
788                 int[] notes = originalChord.toNoteArray(chordRange, originalKey);
789                 //
790                 // 前回鳴らしたコード構成音を覚えておく
791                 int[] prevChordOnNotes = null;
792                 if( chordMatrix.isDragged() || chordMatrix.getNoteIndex() >= 0 )
793                         prevChordOnNotes = Arrays.copyOf(chordOnNotes, chordOnNotes.length);
794                 //
795                 // 次に鳴らす構成音を決める
796                 chordOnNotes = new int[notes.length];
797                 int i = 0;
798                 for( int n : notes ) {
799                         if( inversionOmissionButton.getOmissionNoteIndex() == i ) {
800                                 i++; continue;
801                         }
802                         chordOnNotes[i++] = n;
803                         //
804                         // その音が今鳴っているか調べる
805                         boolean isNoteOn = false;
806                         if( prevChordOnNotes != null ) {
807                                 for( int prevN : prevChordOnNotes ) {
808                                         if( n == prevN ) {
809                                                 isNoteOn = true;
810                                                 break;
811                                         }
812                                 }
813                         }
814                         // すでに鳴っているのに単音を鳴らそうとする場合、
815                         // 鳴らそうとしている音を一旦止める。
816                         if( isNoteOn && chordMatrix.getNoteIndex() >= 0 &&
817                                 notes[chordMatrix.getNoteIndex()] - n == 0
818                         ) {
819                                 keyboardPanel.keyboardCenterPanel.keyboard.noteOff(n);
820                                 isNoteOn = false;
821                         }
822                         // その音が鳴っていなかったら鳴らす。
823                         if( ! isNoteOn )
824                                 keyboardPanel.keyboardCenterPanel.keyboard.noteOn(n);
825                 }
826                 //
827                 // コードを表示
828                 keyboardPanel.keyboardCenterPanel.keyboard.setChord(originalChord);
829                 chordMatrix.chordDisplay.setChord(playChord);
830                 //
831                 // コードダイアグラム用にもコードを表示
832                 Chord diagramChord;
833                 int chordDiagramCapo = chordDiagram.capoSelecterView.getCapo();
834                 if( chordDiagramCapo == chordMatrix.capoSelecter.getCapo() )
835                         diagramChord = playChord.clone();
836                 else
837                         diagramChord = originalChord.clone().transpose(
838                                 - chordDiagramCapo, originalKey
839                         );
840                 chordDiagram.setChord(diagramChord);
841                 if( chordDiagram.recordTextButton.isSelected() )
842                         lyricDisplay.appendChord(diagramChord);
843         }
844
845 }
846