OSDN Git Service

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