OSDN Git Service

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