1 package camidion.chordhelper;
2 import java.awt.BorderLayout;
4 import java.awt.Desktop;
5 import java.awt.Dimension;
7 import java.awt.Insets;
8 import java.awt.event.ActionEvent;
9 import java.awt.event.ActionListener;
10 import java.awt.event.ComponentAdapter;
11 import java.awt.event.ComponentEvent;
12 import java.awt.event.InputEvent;
13 import java.awt.event.ItemEvent;
14 import java.awt.event.ItemListener;
15 import java.awt.event.MouseAdapter;
16 import java.awt.event.MouseEvent;
17 import java.io.IOException;
19 import java.net.URISyntaxException;
21 import java.security.AccessControlException;
22 import java.util.Arrays;
24 import javax.sound.midi.InvalidMidiDataException;
25 import javax.sound.midi.MetaEventListener;
26 import javax.sound.midi.MetaMessage;
27 import javax.sound.midi.Sequence;
28 import javax.sound.midi.Sequencer;
29 import javax.swing.AbstractAction;
30 import javax.swing.Action;
31 import javax.swing.Box;
32 import javax.swing.BoxLayout;
33 import javax.swing.ImageIcon;
34 import javax.swing.JApplet;
35 import javax.swing.JButton;
36 import javax.swing.JComponent;
37 import javax.swing.JEditorPane;
38 import javax.swing.JLabel;
39 import javax.swing.JLayeredPane;
40 import javax.swing.JOptionPane;
41 import javax.swing.JPanel;
42 import javax.swing.JSlider;
43 import javax.swing.JSplitPane;
44 import javax.swing.JToggleButton;
45 import javax.swing.SwingUtilities;
46 import javax.swing.border.Border;
47 import javax.swing.event.ChangeEvent;
48 import javax.swing.event.ChangeListener;
49 import javax.swing.event.HyperlinkEvent;
50 import javax.swing.event.HyperlinkListener;
52 import camidion.chordhelper.anogakki.AnoGakkiPane;
53 import camidion.chordhelper.chorddiagram.CapoComboBoxModel;
54 import camidion.chordhelper.chorddiagram.ChordDiagram;
55 import camidion.chordhelper.chordmatrix.ChordButtonLabel;
56 import camidion.chordhelper.chordmatrix.ChordMatrix;
57 import camidion.chordhelper.chordmatrix.ChordMatrixListener;
58 import camidion.chordhelper.mididevice.MidiDeviceDialog;
59 import camidion.chordhelper.mididevice.MidiDeviceTreeModel;
60 import camidion.chordhelper.mididevice.MidiSequencerModel;
61 import camidion.chordhelper.mididevice.SequencerMeasureView;
62 import camidion.chordhelper.mididevice.SequencerTimeView;
63 import camidion.chordhelper.mididevice.VirtualMidiDevice;
64 import camidion.chordhelper.midieditor.Base64Dialog;
65 import camidion.chordhelper.midieditor.KeySignatureLabel;
66 import camidion.chordhelper.midieditor.MidiSequenceEditorDialog;
67 import camidion.chordhelper.midieditor.NewSequenceDialog;
68 import camidion.chordhelper.midieditor.PlaylistTableModel;
69 import camidion.chordhelper.midieditor.SequenceTickIndex;
70 import camidion.chordhelper.midieditor.SequenceTrackListTableModel;
71 import camidion.chordhelper.midieditor.TempoSelecter;
72 import camidion.chordhelper.midieditor.TimeSignatureSelecter;
73 import camidion.chordhelper.music.Chord;
74 import camidion.chordhelper.music.Key;
75 import camidion.chordhelper.music.Range;
76 import camidion.chordhelper.pianokeyboard.MidiKeyboardPanel;
77 import camidion.chordhelper.pianokeyboard.PianoKeyboardAdapter;
80 * MIDI Chord Helper - Circle-of-fifth oriented chord pad
84 * Copyright (C) 2004-2016 @きよし - Akiyoshi Kamide
85 * http://www.yk.rim.or.jp/~kamide/music/chordhelper/
87 public class ChordHelperApplet extends JApplet {
88 /////////////////////////////////////////////////////////////////////
90 // JavaScript などからの呼び出しインターフェース
92 /////////////////////////////////////////////////////////////////////
94 * 未保存の修正済み MIDI ファイルがあるかどうか調べます。
95 * @return 未保存の修正済み MIDI ファイルがあれば true
97 public boolean isModified() { return playlistModel.isModified(); }
99 * 指定された小節数の曲を、乱数で自動作曲してプレイリストへ追加します。
100 * @param measureLength 小節数
101 * @return 追加先のインデックス値(0から始まる)。追加できなかったときは -1
102 * @throws InvalidMidiDataException {@link Sequencer#setSequence(Sequence)} を参照
104 public int addRandomSongToPlaylist(int measureLength) throws InvalidMidiDataException {
105 NewSequenceDialog d = midiEditor.newSequenceDialog;
106 d.setRandomChordProgression(measureLength);
107 return playlistModel.addSequenceAndPlay(d.getMidiSequence());
110 * URLで指定されたMIDIファイルをプレイリストへ追加します。
112 * <p>URL の最後の / より後ろの部分がファイル名として取り込まれます。
113 * 指定できる MIDI ファイルには、param タグの midi_file パラメータと同様の制限があります。
115 * @param midiFileUrl 追加するMIDIファイルのURL
116 * @return 追加先のインデックス値(0から始まる)。追加できなかったときは -1
118 public int addToPlaylist(String midiFileUrl) {
120 return playlistModel.addSequenceFromURL(midiFileUrl);
121 } catch( URISyntaxException|IOException|InvalidMidiDataException e ) {
122 midiEditor.showWarning(e.getMessage());
123 } catch( AccessControlException e ) {
125 midiEditor.showError(e.getMessage());
130 * Base64 エンコードされた MIDI ファイルをプレイリストへ追加します。
132 * @param base64EncodedText Base64エンコードされたMIDIファイル
133 * @return 追加先のインデックス値(0から始まる)。追加できなかったときは -1
135 public int addToPlaylistBase64(String base64EncodedText) {
136 return addToPlaylistBase64(base64EncodedText, null);
140 * Base64エンコードされたMIDIファイルをプレイリストへ追加します。
142 * @param base64EncodedText Base64エンコードされたMIDIファイル
143 * @param filename ディレクトリ名を除いたファイル名
144 * @return 追加先のインデックス値(0から始まる)。追加できなかったときは -1
146 public int addToPlaylistBase64(String base64EncodedText, String filename) {
147 Base64Dialog d = midiEditor.base64Dialog;
148 d.setBase64Data(base64EncodedText);
150 return playlistModel.addSequence(d.getMIDIData(), filename);
151 } catch (Exception e) {
153 midiEditor.showWarning(e.toString());
158 * プレイリスト上で現在選択されているMIDIシーケンスを、
160 * @throws InvalidMidiDataException {@link Sequencer#setSequence(Sequence)} を参照
162 public void play() throws InvalidMidiDataException { play(playlistModel.sequenceListSelectionModel.getMinSelectionIndex()); }
164 * 指定されたインデックス値が示すプレイリスト上のMIDIシーケンスを、
166 * @param index インデックス値(0から始まる)
167 * @throws InvalidMidiDataException {@link Sequencer#setSequence(Sequence)} を参照
169 public void play(int index) throws InvalidMidiDataException {
170 playlistModel.loadToSequencer(index); sequencerModel.start();
173 * シーケンサが実行中かどうかを返します。
174 * {@link Sequencer#isRunning()} の戻り値をそのまま返します。
178 public boolean isRunning() { return sequencerModel.getSequencer().isRunning(); }
180 * シーケンサが再生中かどうかを返します。
183 public boolean isPlaying() { return isRunning(); }
185 * 現在シーケンサにロードされているMIDIデータを
186 * Base64テキストに変換した結果を返します。
187 * @return MIDIデータをBase64テキストに変換した結果
189 public String getMidiDataBase64() {
190 SequenceTrackListTableModel sequenceModel = sequencerModel.getSequenceTrackListTableModel();
191 midiEditor.base64Dialog.setMIDIData(sequenceModel.getMIDIdata());
192 return midiEditor.base64Dialog.getBase64Data();
195 * 現在シーケンサにロードされているMIDIファイルのファイル名を返します。
196 * @return MIDIファイル名(設定されていないときは空文字列)
198 public String getMidiFilename() {
199 SequenceTrackListTableModel seq_model = sequencerModel.getSequenceTrackListTableModel();
200 if( seq_model == null ) return null;
201 String fn = seq_model.getFilename();
202 return fn == null ? "" : fn ;
206 * @param octavePosition オクターブ位置(デフォルト:4)
208 public void setOctavePosition(int octavePosition) {
209 keyboardPanel.keyboardCenterPanel.keyboard.octaveRangeModel.setValue(octavePosition);
212 * 操作対象のMIDIチャンネルを変更します。
213 * @param ch チャンネル番号 - 1(チャンネル1のとき0、デフォルトは0)
215 public void setChannel(int ch) {
216 keyboardPanel.keyboardCenterPanel.keyboard.midiChComboboxModel.setSelectedChannel(ch);
219 * 操作対象のMIDIチャンネルを返します。
220 * @return 操作対象のMIDIチャンネル
222 public int getChannel() {
223 return keyboardPanel.keyboardCenterPanel.keyboard.midiChComboboxModel.getSelectedChannel();
226 * 操作対象のMIDIチャンネルに対してプログラム(音色)を設定します。
227 * @param program 音色(0~127:General MIDI に基づく)
229 public void programChange(int program) {
230 keyboardPanel.keyboardCenterPanel.keyboard.getSelectedChannel().programChange(program);
233 * 操作対象のMIDIチャンネルに対してプログラム(音色)を設定します。
234 * 内部的には {@link #programChange(int)} を呼び出しているだけです。
235 * @param program 音色(0~127:General MIDI に基づく)
237 public void setProgram(int program) { programChange(program); }
239 * 自動転回モードを変更します。初期値は true です。
240 * @param isAuto true:自動転回を行う false:自動転回を行わない
242 public void setAutoInversion(boolean isAuto) {
243 inversionOmissionButton.setAutoInversion(isAuto);
249 * <li>-1:省略しない(デフォルト)</li>
255 public void setOmissionNoteIndex(int index) {
256 inversionOmissionButton.setOmissionNoteIndex(index);
259 * コードダイアグラムの表示・非表示を切り替えます。
260 * @param isVisible 表示するときtrue
262 public void setChordDiagramVisible(boolean isVisible) {
263 keyboardSplitPane.resetToPreferredSizes();
265 keyboardSplitPane.setDividerLocation((double)1.0);
268 * コードダイヤグラムをギターモードに変更します。
269 * 初期状態ではウクレレモードになっています。
271 public void setChordDiagramForGuitar() {
272 chordDiagram.setTargetInstrument(ChordDiagram.Instrument.Guitar);
275 * ダークモード(暗い表示)と明るい表示とを切り替えます。
276 * @param isDark ダークモードのときtrue、明るい表示のときfalse(デフォルト)
278 public void setDarkMode(boolean isDark) {
279 darkModeToggleButton.setSelected(isDark);
284 public static class VersionInfo {
285 public static final String NAME = "MIDI Chord Helper";
286 public static final String VERSION = "Ver.20161218.1";
287 public static final String COPYRIGHT = "Copyright (C) 2004-2016";
288 public static final String AUTHER = "@きよし - Akiyoshi Kamide";
289 public static final String URL = "http://www.yk.rim.or.jp/~kamide/music/chordhelper/";
294 public static String getInfo() {
295 return NAME + " " + VERSION + " " + COPYRIGHT + " " + AUTHER + " " + URL;
299 public String getAppletInfo() { return VersionInfo.getInfo(); }
300 private class AboutMessagePane extends JEditorPane {
302 public AboutMessagePane() { this(true); }
303 public AboutMessagePane(boolean link_enabled) {
304 super( "text/html", "" );
305 String link_string, tooltip = null;
306 if( link_enabled && Desktop.isDesktopSupported() ) {
307 tooltip = "Click this URL to open with your web browser - URLをクリックしてWebブラウザで開く";
309 "<a href=\"" + VersionInfo.URL + "\" title=\"" +
310 tooltip + "\">" + VersionInfo.URL + "</a>" ;
313 link_enabled = false; link_string = VersionInfo.URL;
316 "<html><center><font size=\"+1\">" + VersionInfo.NAME + "</font> " +
317 VersionInfo.VERSION + "<br/><br/>" +
318 VersionInfo.COPYRIGHT + " " + VersionInfo.AUTHER + "<br/>" +
319 link_string + "</center></html>"
321 setToolTipText(tooltip);
323 putClientProperty(JEditorPane.HONOR_DISPLAY_PROPERTIES, Boolean.TRUE);
326 // メッセージ内の <a href=""> ~ </a> によるリンクを
327 // 実際に機能させる(ブラウザで表示されるようにする)ための設定
329 if( ! link_enabled ) return;
331 uri = new URI(VersionInfo.URL);
332 }catch( URISyntaxException use ) {
333 use.printStackTrace();
336 addHyperlinkListener(new HyperlinkListener() {
337 public void hyperlinkUpdate(HyperlinkEvent e) {
338 if(e.getEventType()==HyperlinkEvent.EventType.ACTIVATED) {
340 Desktop.getDesktop().browse(uri);
341 }catch(IOException ioe) {
342 ioe.printStackTrace();
351 public Action openAction = new AbstractAction() {
353 putValue(NAME, "Version info");
354 putValue(SHORT_DESCRIPTION, VersionInfo.NAME + " " + VersionInfo.VERSION);
357 public void actionPerformed(ActionEvent e) {
358 JOptionPane.showMessageDialog(
359 null, AboutMessagePane.this, getValue(NAME).toString(),
360 JOptionPane.INFORMATION_MESSAGE, imageIcon
368 public ImageIcon imageIcon;
372 public Image iconImage;
373 private static final String IMAGE_ICON_PATH = "midichordhelper.png";
374 private void loadIconImage() {
375 URL imageIconUrl = getClass().getResource(IMAGE_ICON_PATH);
376 if( imageIconUrl == null ) {
377 System.out.println("Icon image " + IMAGE_ICON_PATH + " not found");
380 iconImage = (imageIcon = new ImageIcon(imageIconUrl)).getImage();
384 * ボタンの余白を詰めたいときに setMargin() の引数に指定するインセット
386 public static final Insets ZERO_INSETS = new Insets(0,0,0,0);
389 MidiSequenceEditorDialog midiEditor;
390 PlaylistTableModel playlistModel;
391 MidiSequencerModel sequencerModel;
392 private ChordMatrix chordMatrix;
393 private JPanel keyboardSequencerPanel;
394 private JPanel chordGuide;
395 private Color rootPaneDefaultBgcolor;
396 private Color lyricDisplayDefaultBgcolor;
397 private Border lyricDisplayDefaultBorder;
398 private JSplitPane mainSplitPane;
399 private JSplitPane keyboardSplitPane;
400 private ChordButtonLabel enterButtonLabel;
401 private ChordTextField lyricDisplay;
402 private MidiKeyboardPanel keyboardPanel;
403 private InversionAndOmissionLabel inversionOmissionButton;
404 private JToggleButton darkModeToggleButton;
405 private ChordDiagram chordDiagram;
406 private KeySignatureLabel keysigLabel;
407 private AnoGakkiPane anoGakkiPane;
408 private JToggleButton anoGakkiToggleButton;
409 private MidiDeviceTreeModel deviceTreeModel;
415 rootPaneDefaultBgcolor = getContentPane().getBackground();
417 // コードダイアグラム、コードボタン、ピアノ鍵盤のセットアップ
418 CapoComboBoxModel capoValueModel = new CapoComboBoxModel();
419 chordDiagram = new ChordDiagram(capoValueModel);
420 chordMatrix = new ChordMatrix(capoValueModel) {{
421 addChordMatrixListener(new ChordMatrixListener(){
422 public void keySignatureChanged() {
423 Key capoKey = getKeySignatureCapo();
424 keyboardPanel.keySelecter.setSelectedKey(capoKey);
425 keyboardPanel.keyboardCenterPanel.keyboard.setKeySignature(capoKey);
427 public void chordChanged() { chordOn(); }
429 capoSelecter.checkbox.addItemListener(new ItemListener() {
430 public void itemStateChanged(ItemEvent e) {
432 keyboardPanel.keyboardCenterPanel.keyboard.chordDisplay.clear();
433 chordDiagram.clear();
436 capoSelecter.valueSelecter.addActionListener(new ActionListener() {
437 public void actionPerformed(ActionEvent e) {
439 keyboardPanel.keyboardCenterPanel.keyboard.chordDisplay.clear();
440 chordDiagram.clear();
444 keysigLabel = new KeySignatureLabel() {{
445 addMouseListener(new MouseAdapter() {
446 public void mousePressed(MouseEvent e) { chordMatrix.setKeySignature(getKey()); }
449 keyboardPanel = new MidiKeyboardPanel(chordMatrix) {{
450 keyboardCenterPanel.keyboard.addPianoKeyboardListener(new PianoKeyboardAdapter() {
452 public void pianoKeyPressed(int n, InputEvent e) { chordDiagram.clear(); }
454 keySelecter.keysigCombobox.addActionListener(new ActionListener() {
456 public void actionPerformed(ActionEvent e) {
457 Key key = keySelecter.getSelectedKey();
458 key.transpose( - chordMatrix.capoSelecter.getCapo() );
459 chordMatrix.setKeySignature(key);
462 keyboardCenterPanel.keyboard.setPreferredSize(new Dimension(571, 80));
464 VirtualMidiDevice guiMidiDevice = keyboardPanel.keyboardCenterPanel.keyboard.midiDevice;
467 deviceTreeModel = new MidiDeviceTreeModel(guiMidiDevice);
469 // MIDIシーケンサと連携するプレイリストモデルを構築
470 playlistModel = new PlaylistTableModel(sequencerModel = deviceTreeModel.getSequencerModel());
473 MidiDeviceDialog midiDeviceDialog = new MidiDeviceDialog(deviceTreeModel);
474 midiDeviceDialog.setIconImage(iconImage);
477 (midiEditor = new MidiSequenceEditorDialog(playlistModel, guiMidiDevice)).setIconImage(iconImage);
479 // メイン画面へのMIDIファイルのドラッグ&ドロップ受付開始
480 setTransferHandler(midiEditor.transferHandler);
482 // MIDIエディタのイベントダイアログを、ピアノ鍵盤のイベント送出ダイアログと共用
483 keyboardPanel.setEventDialog(midiEditor.eventDialog);
486 (lyricDisplay = new ChordTextField(sequencerModel)).addActionListener(new ActionListener() {
488 public void actionPerformed(ActionEvent event) {
489 chordMatrix.setSelectedChord(event.getActionCommand().trim().split("[ \t\r\n]")[0]);
492 lyricDisplayDefaultBorder = lyricDisplay.getBorder();
493 lyricDisplayDefaultBgcolor = lyricDisplay.getBackground();
495 // メタイベント(テンポ・拍子・調号)を受信して表示するリスナーを登録
496 TempoSelecter tempoSelecter = new TempoSelecter() {{ setEditable(false); }};
497 TimeSignatureSelecter timesigSelecter = new TimeSignatureSelecter() {{ setEditable(false); }};
498 sequencerModel.getSequencer().addMetaEventListener(new MetaEventListener() {
500 public void meta(MetaMessage msg) {
501 switch(msg.getType()) {
502 case 0x51: // Tempo (3 bytes) - テンポ
503 byte[] qpm = msg.getData();
504 if( SwingUtilities.isEventDispatchThread() ) {
505 tempoSelecter.setTempo(qpm);
507 // MIDIシーケンサのスレッドから呼ばれた場合、GUI更新は自分で行わず、
508 // AWTイベントディスパッチスレッドに依頼する。
509 SwingUtilities.invokeLater(new Runnable() {
511 public void run() { tempoSelecter.setTempo(qpm);}
515 case 0x58: // Time signature (4 bytes) - 拍子
516 byte[] timesig = msg.getData();
517 if( SwingUtilities.isEventDispatchThread() ) {
518 timesigSelecter.setValue(timesig);
520 SwingUtilities.invokeLater(new Runnable() {
522 public void run() { timesigSelecter.setValue(timesig);}
526 case 0x59: // Key signature (2 bytes) : 調号
527 Key key = new Key(msg.getData());
528 if( SwingUtilities.isEventDispatchThread() ) {
529 setKeySignature(key);
531 SwingUtilities.invokeLater(new Runnable() {
533 public void run() { setKeySignature(key); }
541 //シーケンサーの時間スライダーの値が変わったときのリスナーを登録
542 JLabel songTitleLabel = new JLabel();
543 sequencerModel.addChangeListener(new ChangeListener() {
545 public void stateChanged(ChangeEvent e) {
546 SequenceTrackListTableModel sequenceTableModel = sequencerModel.getSequenceTrackListTableModel();
547 int loadedSequenceIndex = playlistModel.indexOfSequenceOnSequencer();
548 songTitleLabel.setText(
550 loadedSequenceIndex < 0 ? "[No MIDI file loaded]" :
551 "MIDI file " + loadedSequenceIndex + ": " + (
552 sequenceTableModel == null ||
553 sequenceTableModel.toString() == null ||
554 sequenceTableModel.toString().isEmpty() ?
556 "<font color=maroon>"+sequenceTableModel+"</font>"
560 Sequencer sequencer = sequencerModel.getSequencer();
561 chordMatrix.setPlaying(sequencer.isRunning());
562 if( sequenceTableModel != null ) {
563 SequenceTickIndex tickIndex = sequenceTableModel.getSequenceTickIndex();
564 long tickPos = sequencer.getTickPosition();
565 tickIndex.tickToMeasure(tickPos);
566 chordMatrix.setBeat(tickIndex);
567 if( sequencerModel.getValueIsAdjusting() || ! (sequencer.isRunning() || sequencer.isRecording()) ) {
569 msg = tickIndex.lastMetaMessageAt(
570 SequenceTickIndex.MetaMessageType.TIME_SIGNATURE, tickPos
572 timesigSelecter.setValue(msg==null ? null : msg.getData());
573 msg = tickIndex.lastMetaMessageAt(
574 SequenceTickIndex.MetaMessageType.TEMPO, tickPos
576 tempoSelecter.setTempo(msg==null ? null : msg.getData());
577 msg = tickIndex.lastMetaMessageAt(
578 SequenceTickIndex.MetaMessageType.KEY_SIGNATURE, tickPos
584 setKeySignature(new Key(msg.getData()));
590 sequencerModel.fireStateChanged();
591 chordGuide = new JPanel() {{
592 setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
593 add( Box.createHorizontalStrut(2) );
594 add( chordMatrix.chordGuide );
595 add( Box.createHorizontalStrut(2) );
597 add( Box.createHorizontalStrut(2) );
598 add( enterButtonLabel = new ChordButtonLabel("Enter",chordMatrix) {{
599 addMouseListener(new MouseAdapter() {
600 public void mousePressed(MouseEvent event) {
601 if( (event.getModifiersEx() & InputEvent.BUTTON3_DOWN_MASK) != 0 ) // RightClicked
602 chordMatrix.setSelectedChord((Chord)null);
604 chordMatrix.setSelectedChord(lyricDisplay.getText());
609 add( Box.createHorizontalStrut(5) );
610 add( chordMatrix.chordDisplay );
611 add( Box.createHorizontalStrut(5) );
612 add( darkModeToggleButton = new JToggleButton(new ButtonIcon(ButtonIcon.DARK_MODE_ICON)) {{
613 setMargin(ZERO_INSETS);
614 addItemListener(new ItemListener() {
615 public void itemStateChanged(ItemEvent e) {
616 innerSetDarkMode(darkModeToggleButton.isSelected());
619 setToolTipText("Light / Dark - 明かりを点灯/消灯");
622 add( Box.createHorizontalStrut(5) );
623 add( anoGakkiToggleButton = new JToggleButton(new ButtonIcon(ButtonIcon.ANO_GAKKI_ICON)) {{
625 setMargin(ZERO_INSETS);
627 setToolTipText("あの楽器");
628 addItemListener(new ItemListener() {
629 public void itemStateChanged(ItemEvent e) {
630 keyboardPanel.keyboardCenterPanel.keyboard.anoGakkiPane
631 = anoGakkiToggleButton.isSelected() ? anoGakkiPane : null ;
635 add( Box.createHorizontalStrut(5) );
636 add( inversionOmissionButton = new InversionAndOmissionLabel() );
637 add( Box.createHorizontalStrut(5) );
638 add( chordMatrix.capoSelecter );
639 add( Box.createHorizontalStrut(2) );
641 keyboardSequencerPanel = new JPanel() {{
642 setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
644 add(Box.createVerticalStrut(5));
645 add(keyboardSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, keyboardPanel, chordDiagram) {{
646 setOneTouchExpandable(true);
647 setResizeWeight(1.0);
648 setAlignmentX((float)0.5);
650 add(Box.createVerticalStrut(5));
652 setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
654 setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
655 add( Box.createHorizontalStrut(12) );
657 add( Box.createHorizontalStrut(12) );
658 add( timesigSelecter );
659 add( Box.createHorizontalStrut(12) );
660 add( tempoSelecter );
661 add( Box.createHorizontalStrut(12) );
662 add( new SequencerMeasureView(sequencerModel) );
663 add( Box.createHorizontalStrut(12) );
664 add( songTitleLabel );
665 add( Box.createHorizontalStrut(12) );
666 add( new JButton(midiEditor.openAction) {{ setMargin(ZERO_INSETS); }});
669 setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
670 add(Box.createHorizontalStrut(10));
671 add(new JSlider(sequencerModel));
672 add(new SequencerTimeView(sequencerModel));
673 add(Box.createHorizontalStrut(5));
674 add(new JButton(playlistModel.moveToTopAction) {{ setMargin(ZERO_INSETS); }});
675 add(new JButton(sequencerModel.moveBackwardAction) {{ setMargin(ZERO_INSETS); }});
676 add(new JToggleButton(sequencerModel.startStopAction));
677 add(new JButton(sequencerModel.moveForwardAction) {{ setMargin(ZERO_INSETS); }});
678 add(new JButton(playlistModel.moveToBottomAction) {{ setMargin(ZERO_INSETS); }});
679 add(new JToggleButton(playlistModel.toggleRepeatAction) {{ setMargin(ZERO_INSETS); }});
680 add( Box.createHorizontalStrut(10) );
683 add(new JButton(midiDeviceDialog.openAction));
684 add(new JButton((new AboutMessagePane()).openAction));
688 setContentPane(new JLayeredPane() {
690 add(anoGakkiPane = new AnoGakkiPane(), JLayeredPane.PALETTE_LAYER);
691 addComponentListener(new ComponentAdapter() {
693 public void componentResized(ComponentEvent e) { adjustSize(); }
695 public void componentShown(ComponentEvent e) { adjustSize(); }
696 private void adjustSize() { anoGakkiPane.setBounds(getBounds()); }
698 setLayout(new BorderLayout());
700 add(mainSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, chordMatrix, keyboardSequencerPanel){
702 setResizeWeight(0.5);
703 setAlignmentX((float)0.5);
709 setPreferredSize(new Dimension(750,470));
712 public void destroy() {
713 deviceTreeModel.closeAllDevices();
717 public void start() {
719 // コードボタンで設定されている現在の調を
721 chordMatrix.fireKeySignatureChanged();
723 // アプレットのパラメータにMIDIファイルのURLが指定されていたら
725 String midi_url = getParameter("midi_file");
727 if( midi_url != null ) {
728 addToPlaylist(midi_url);
731 } catch (InvalidMidiDataException ex) {
732 ex.printStackTrace();
738 sequencerModel.stop(); // MIDI再生を強制終了
741 private void innerSetDarkMode(boolean isDark) {
742 Color col = isDark ? Color.black : null;
743 getContentPane().setBackground(isDark ? Color.black : rootPaneDefaultBgcolor);
744 mainSplitPane.setBackground(col);
745 keyboardSplitPane.setBackground(col);
746 enterButtonLabel.setDarkMode(isDark);
747 chordGuide.setBackground(col);
748 lyricDisplay.setBorder(isDark ? null : lyricDisplayDefaultBorder);
749 lyricDisplay.setBackground(isDark ?
750 chordMatrix.darkModeColorset.backgrounds[2] :
751 lyricDisplayDefaultBgcolor
753 lyricDisplay.setForeground(isDark ? Color.white : null);
754 inversionOmissionButton.setBackground(col);
755 anoGakkiToggleButton.setBackground(col);
756 keyboardSequencerPanel.setBackground(col);
757 chordDiagram.setBackground(col);
758 chordDiagram.titleLabel.setDarkMode(isDark);
759 chordMatrix.setDarkMode(isDark);
760 keyboardPanel.setDarkMode(isDark);
763 private void setKeySignature(Key key) {
764 keysigLabel.setKeySignature(key);
765 chordMatrix.setKeySignature(key);
768 private int[] chordOnNotes = null;
771 * <p>この関数を直接呼ぶとアルペジオが効かないので、
772 * chord_matrix.setSelectedChord() を使うことを推奨
775 public void chordOn() {
776 Chord playChord = chordMatrix.getSelectedChord();
778 chordOnNotes != null &&
779 chordMatrix.getNoteIndex() < 0 &&
780 (! chordMatrix.isDragged() || playChord == null)
782 // コードが鳴っている状態で、新たなコードを鳴らそうとしたり、
783 // もう鳴らさないという信号が来た場合は、今鳴っている音を止める。
785 for( int n : chordOnNotes )
786 keyboardPanel.keyboardCenterPanel.keyboard.noteOff(n);
789 if( playChord == null ) {
790 // もう鳴らさないので、歌詞表示に通知して終了
791 if( lyricDisplay != null )
792 lyricDisplay.appendChord(null);
796 if( keyboardPanel.keyboardCenterPanel.keyboard.anoGakkiPane != null ) {
797 JComponent btn = chordMatrix.getSelectedButton();
798 if( btn != null ) anoGakkiPane.start(chordMatrix, btn.getBounds());
800 // コードボタンからのコードを、カポつき演奏キーからオリジナルキーへ変換
801 Key originalKey = chordMatrix.getKeySignatureCapo();
802 Chord originalChord = playChord.clone().transpose(
803 chordMatrix.capoSelecter.getCapo(),
804 chordMatrix.getKeySignature()
806 // 変換後のコードをキーボード画面に設定
807 keyboardPanel.keyboardCenterPanel.keyboard.setChord(originalChord);
809 // 音域を決める。これにより鳴らす音が確定する。
810 Range chordRange = new Range(
811 keyboardPanel.keyboardCenterPanel.keyboard.getChromaticOffset() + 10 +
812 ( keyboardPanel.keyboardCenterPanel.keyboard.getOctaves() / 4 ) * 12,
813 inversionOmissionButton.isAutoInversionMode() ?
814 keyboardPanel.keyboardCenterPanel.keyboard.getChromaticOffset() + 21 :
815 keyboardPanel.keyboardCenterPanel.keyboard.getChromaticOffset() + 33,
817 inversionOmissionButton.isAutoInversionMode()
819 int[] notes = originalChord.toNoteArray(chordRange, originalKey);
821 // 前回鳴らしたコード構成音を覚えておく
822 int[] prevChordOnNotes = null;
823 if( chordMatrix.isDragged() || chordMatrix.getNoteIndex() >= 0 )
824 prevChordOnNotes = Arrays.copyOf(chordOnNotes, chordOnNotes.length);
827 chordOnNotes = new int[notes.length];
829 for( int n : notes ) {
830 if( inversionOmissionButton.getOmissionNoteIndex() == i ) {
833 chordOnNotes[i++] = n;
836 boolean isNoteOn = false;
837 if( prevChordOnNotes != null ) {
838 for( int prevN : prevChordOnNotes ) {
845 // すでに鳴っているのに単音を鳴らそうとする場合、
847 if( isNoteOn && chordMatrix.getNoteIndex() >= 0 &&
848 notes[chordMatrix.getNoteIndex()] - n == 0
850 keyboardPanel.keyboardCenterPanel.keyboard.noteOff(n);
855 keyboardPanel.keyboardCenterPanel.keyboard.noteOn(n);
859 keyboardPanel.keyboardCenterPanel.keyboard.setChord(originalChord);
860 chordMatrix.chordDisplay.setChord(playChord);
862 // コードダイアグラム用にもコードを表示
864 int chordDiagramCapo = chordDiagram.capoSelecterView.getCapo();
865 if( chordDiagramCapo == chordMatrix.capoSelecter.getCapo() )
866 diagramChord = playChord.clone();
868 diagramChord = originalChord.clone().transpose(
869 - chordDiagramCapo, originalKey
871 chordDiagram.setChord(diagramChord);
872 if( chordDiagram.recordTextButton.isSelected() )
873 lyricDisplay.appendChord(diagramChord);