OSDN Git Service

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