OSDN Git Service

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