OSDN Git Service

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