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.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;
21 import java.net.URISyntaxException;
23 import java.security.AccessControlException;
24 import java.util.Arrays;
25 import java.util.Vector;
27 import javax.sound.midi.InvalidMidiDataException;
28 import javax.sound.midi.MetaEventListener;
29 import javax.sound.midi.MetaMessage;
30 import javax.sound.midi.Sequence;
31 import javax.sound.midi.Sequencer;
32 import javax.swing.Box;
33 import javax.swing.BoxLayout;
34 import javax.swing.ImageIcon;
35 import javax.swing.JApplet;
36 import javax.swing.JButton;
37 import javax.swing.JComponent;
38 import javax.swing.JEditorPane;
39 import javax.swing.JLabel;
40 import javax.swing.JLayeredPane;
41 import javax.swing.JOptionPane;
42 import javax.swing.JPanel;
43 import javax.swing.JSlider;
44 import javax.swing.JSplitPane;
45 import javax.swing.JToggleButton;
46 import javax.swing.SwingUtilities;
47 import javax.swing.border.Border;
48 import javax.swing.event.ChangeEvent;
49 import javax.swing.event.ChangeListener;
50 import javax.swing.event.HyperlinkEvent;
51 import javax.swing.event.HyperlinkListener;
53 import camidion.chordhelper.anogakki.AnoGakkiPane;
54 import camidion.chordhelper.chorddiagram.ChordDiagram;
55 import camidion.chordhelper.chordmatrix.ChordButtonLabel;
56 import camidion.chordhelper.chordmatrix.ChordMatrix;
57 import camidion.chordhelper.chordmatrix.ChordMatrixListener;
58 import camidion.chordhelper.mididevice.MidiDeviceDialog;
59 import camidion.chordhelper.mididevice.MidiDeviceModelList;
60 import camidion.chordhelper.mididevice.SequencerMeasureView;
61 import camidion.chordhelper.mididevice.SequencerTimeView;
62 import camidion.chordhelper.mididevice.VirtualMidiDevice;
63 import camidion.chordhelper.midieditor.Base64Dialog;
64 import camidion.chordhelper.midieditor.KeySignatureLabel;
65 import camidion.chordhelper.midieditor.SequenceTickIndex;
66 import camidion.chordhelper.midieditor.SequenceTrackListTableModel;
67 import camidion.chordhelper.midieditor.TempoSelecter;
68 import camidion.chordhelper.midieditor.TimeSignatureSelecter;
69 import camidion.chordhelper.music.Chord;
70 import camidion.chordhelper.music.Key;
71 import camidion.chordhelper.music.Range;
72 import camidion.chordhelper.pianokeyboard.MidiKeyboardPanel;
73 import camidion.chordhelper.pianokeyboard.PianoKeyboardAdapter;
76 * MIDI Chord Helper - Circle-of-fifth oriented chord pad
80 * Copyright (C) 2004-2014 @きよし - Akiyoshi Kamide
81 * http://www.yk.rim.or.jp/~kamide/music/chordhelper/
83 public class ChordHelperApplet extends JApplet {
84 /////////////////////////////////////////////////////////////////////
86 // JavaScript などからの呼び出しインターフェース
88 /////////////////////////////////////////////////////////////////////
90 * 未保存の修正済み MIDI ファイルがあるかどうか調べます。
91 * @return 未保存の修正済み MIDI ファイルがあれば true
93 public boolean isModified() {
94 return deviceModelList.editorDialog.sequenceListTable.getModel().isModified();
97 * 指定された小節数の曲を、乱数で自動作曲してプレイリストへ追加します。
98 * @param measureLength 小節数
99 * @return 追加先のインデックス値(0から始まる)。追加できなかったときは -1
101 public int addRandomSongToPlaylist(int measureLength) {
102 deviceModelList.editorDialog.newSequenceDialog.setRandomChordProgression(measureLength);
103 Sequence sequence = deviceModelList.editorDialog.newSequenceDialog.getMidiSequence();
104 return deviceModelList.editorDialog.sequenceListTable.getModel().addSequenceAndPlay(sequence);
107 * URLで指定されたMIDIファイルをプレイリストへ追加します。
109 * <p>URL の最後の / より後ろの部分がファイル名として取り込まれます。
110 * 指定できる MIDI ファイルには、param タグの midi_file パラメータと同様の制限があります。
112 * @param midiFileUrl 追加するMIDIファイルのURL
113 * @return 追加先のインデックス値(0から始まる)。追加できなかったときは -1
115 public int addToPlaylist(String midiFileUrl) {
117 return deviceModelList.editorDialog.sequenceListTable.getModel().addSequenceFromURL(midiFileUrl);
118 } catch( URISyntaxException|IOException|InvalidMidiDataException e ) {
119 deviceModelList.editorDialog.showWarning(e.getMessage());
120 } catch( AccessControlException e ) {
122 deviceModelList.editorDialog.showError(e.getMessage());
127 * Base64 エンコードされた MIDI ファイルをプレイリストへ追加します。
129 * @param base64EncodedText Base64エンコードされたMIDIファイル
130 * @return 追加先のインデックス値(0から始まる)。追加できなかったときは -1
132 public int addToPlaylistBase64(String base64EncodedText) {
133 return addToPlaylistBase64(base64EncodedText, null);
137 * Base64エンコードされたMIDIファイルをプレイリストへ追加します。
139 * @param base64EncodedText Base64エンコードされたMIDIファイル
140 * @param filename ディレクトリ名を除いたファイル名
141 * @return 追加先のインデックス値(0から始まる)。追加できなかったときは -1
143 public int addToPlaylistBase64(String base64EncodedText, String filename) {
144 Base64Dialog d = deviceModelList.editorDialog.base64Dialog;
145 d.setBase64Data(base64EncodedText);
147 return deviceModelList.editorDialog.sequenceListTable.getModel().addSequence(d.getMIDIData(), filename);
148 } catch (IOException | InvalidMidiDataException e) {
150 deviceModelList.editorDialog.showWarning(e.getMessage());
155 * プレイリスト上で現在選択されているMIDIシーケンスを、
159 play(deviceModelList.editorDialog.sequenceListTable.getModel().sequenceListSelectionModel.getMinSelectionIndex());
162 * 指定されたインデックス値が示すプレイリスト上のMIDIシーケンスを、
164 * @param index インデックス値(0から始まる)
166 public void play(int index) {
167 deviceModelList.editorDialog.sequenceListTable.getModel().loadToSequencer(index);
168 deviceModelList.getSequencerModel().start();
171 * シーケンサが実行中かどうかを返します。
172 * {@link Sequencer#isRunning()} の戻り値をそのまま返します。
176 public boolean isRunning() {
177 return deviceModelList.getSequencerModel().getSequencer().isRunning();
180 * シーケンサが再生中かどうかを返します。
183 public boolean isPlaying() { return isRunning(); }
185 * 現在シーケンサにロードされているMIDIデータを
186 * Base64テキストに変換した結果を返します。
187 * @return MIDIデータをBase64テキストに変換した結果
189 public String getMidiDataBase64() {
190 SequenceTrackListTableModel sequenceModel =
191 deviceModelList.editorDialog.sequenceListTable.getModel().sequencerModel.getSequenceTrackListTableModel();
192 deviceModelList.editorDialog.base64Dialog.setMIDIData(sequenceModel.getMIDIdata());
193 return deviceModelList.editorDialog.base64Dialog.getBase64Data();
196 * 現在シーケンサにロードされているMIDIファイルのファイル名を返します。
197 * @return MIDIファイル名(設定されていないときは空文字列)
199 public String getMidiFilename() {
200 SequenceTrackListTableModel seq_model = deviceModelList.getSequencerModel().getSequenceTrackListTableModel();
201 if( seq_model == null ) return null;
202 String fn = seq_model.getFilename();
203 return fn == null ? "" : fn ;
207 * @param octavePosition オクターブ位置(デフォルト:4)
209 public void setOctavePosition(int octavePosition) {
210 keyboardPanel.keyboardCenterPanel.keyboard.octaveRangeModel.setValue(octavePosition);
213 * 操作対象のMIDIチャンネルを変更します。
214 * @param ch チャンネル番号 - 1(チャンネル1のとき0、デフォルトは0)
216 public void setChannel(int ch) {
217 keyboardPanel.keyboardCenterPanel.keyboard.midiChComboboxModel.setSelectedChannel(ch);
220 * 操作対象のMIDIチャンネルを返します。
221 * @return 操作対象のMIDIチャンネル
223 public int getChannel() {
224 return keyboardPanel.keyboardCenterPanel.keyboard.midiChComboboxModel.getSelectedChannel();
227 * 操作対象のMIDIチャンネルに対してプログラム(音色)を設定します。
228 * @param program 音色(0~127:General MIDI に基づく)
230 public void programChange(int program) {
231 keyboardPanel.keyboardCenterPanel.keyboard.getSelectedChannel().programChange(program);
234 * 操作対象のMIDIチャンネルに対してプログラム(音色)を設定します。
235 * 内部的には {@link #programChange(int)} を呼び出しているだけです。
236 * @param program 音色(0~127:General MIDI に基づく)
238 public void setProgram(int program) { programChange(program); }
240 * 自動転回モードを変更します。初期値は true です。
241 * @param isAuto true:自動転回を行う false:自動転回を行わない
243 public void setAutoInversion(boolean isAuto) {
244 inversionOmissionButton.setAutoInversion(isAuto);
250 * <li>-1:省略しない(デフォルト)</li>
256 public void setOmissionNoteIndex(int index) {
257 inversionOmissionButton.setOmissionNoteIndex(index);
260 * コードダイアグラムの表示・非表示を切り替えます。
261 * @param isVisible 表示するときtrue
263 public void setChordDiagramVisible(boolean isVisible) {
264 keyboardSplitPane.resetToPreferredSizes();
266 keyboardSplitPane.setDividerLocation((double)1.0);
269 * コードダイヤグラムをギターモードに変更します。
270 * 初期状態ではウクレレモードになっています。
272 public void setChordDiagramForGuitar() {
273 chordDiagram.setTargetInstrument(ChordDiagram.Instrument.Guitar);
276 * ダークモード(暗い表示)と明るい表示とを切り替えます。
277 * @param isDark ダークモードのときtrue、明るい表示のときfalse(デフォルト)
279 public void setDarkMode(boolean isDark) {
280 darkModeToggleButton.setSelected(isDark);
285 public static class VersionInfo {
286 public static final String NAME = "MIDI Chord Helper";
287 public static final String VERSION = "Ver.20160502.1";
288 public static final String COPYRIGHT = "Copyright (C) 2004-2016";
289 public static final String AUTHER = "@きよし - Akiyoshi Kamide";
290 public static final String URL = "http://www.yk.rim.or.jp/~kamide/music/chordhelper/";
295 public static String getInfo() {
296 return NAME + " " + VERSION + " " + COPYRIGHT + " " + AUTHER + " " + URL;
300 public String getAppletInfo() { return VersionInfo.getInfo(); }
301 private class AboutMessagePane extends JEditorPane implements ActionListener {
303 public AboutMessagePane() { this(true); }
304 public AboutMessagePane(boolean link_enabled) {
305 super( "text/html", "" );
306 String link_string, tooltip = null;
307 if( link_enabled && Desktop.isDesktopSupported() ) {
308 tooltip = "Click this URL to open with your web browser - URLをクリックしてWebブラウザで開く";
310 "<a href=\"" + VersionInfo.URL + "\" title=\"" +
311 tooltip + "\">" + VersionInfo.URL + "</a>" ;
314 link_enabled = false; link_string = VersionInfo.URL;
317 "<html><center><font size=\"+1\">" + VersionInfo.NAME + "</font> " +
318 VersionInfo.VERSION + "<br/><br/>" +
319 VersionInfo.COPYRIGHT + " " + VersionInfo.AUTHER + "<br/>" +
320 link_string + "</center></html>"
322 setToolTipText(tooltip);
324 putClientProperty(JEditorPane.HONOR_DISPLAY_PROPERTIES, Boolean.TRUE);
327 // メッセージ内の <a href=""> ~ </a> によるリンクを
328 // 実際に機能させる(ブラウザで表示されるようにする)ための設定
330 if( ! link_enabled ) return;
332 uri = new URI(VersionInfo.URL);
333 }catch( URISyntaxException use ) {
334 use.printStackTrace();
337 addHyperlinkListener(new HyperlinkListener() {
338 public void hyperlinkUpdate(HyperlinkEvent e) {
339 if(e.getEventType()==HyperlinkEvent.EventType.ACTIVATED) {
341 Desktop.getDesktop().browse(uri);
342 }catch(IOException ioe) {
343 ioe.printStackTrace();
350 public void actionPerformed(ActionEvent e) {
351 JOptionPane.showMessageDialog(
352 null, this, "Version info",
353 JOptionPane.INFORMATION_MESSAGE, imageIcon
358 public boolean isConfirmedToExit() {
359 return ! isModified() || JOptionPane.showConfirmDialog(
361 "MIDI file not saved, exit anyway ?\n保存されていないMIDIファイルがありますが、終了してよろしいですか?",
363 JOptionPane.YES_NO_OPTION,
364 JOptionPane.WARNING_MESSAGE
365 ) == JOptionPane.YES_OPTION ;
370 public ImageIcon imageIcon;
372 * ボタンの余白を詰めたいときに setMargin() の引数に指定するインセット
374 public static final Insets ZERO_INSETS = new Insets(0,0,0,0);
376 public ChordMatrix chordMatrix;
377 MidiDeviceModelList deviceModelList;
379 private JPanel keyboardSequencerPanel;
380 private JPanel chordGuide;
381 private Color rootPaneDefaultBgcolor;
382 private Color lyricDisplayDefaultBgcolor;
383 private Border lyricDisplayDefaultBorder;
384 private JSplitPane mainSplitPane;
385 private JSplitPane keyboardSplitPane;
386 private ChordButtonLabel enterButtonLabel;
387 private ChordTextField lyricDisplay;
388 private MidiKeyboardPanel keyboardPanel;
389 private InversionAndOmissionLabel inversionOmissionButton;
390 private JToggleButton darkModeToggleButton;
391 private MidiDeviceDialog midiConnectionDialog;
392 private ChordDiagram chordDiagram;
393 private TempoSelecter tempoSelecter;
394 private TimeSignatureSelecter timesigSelecter;
395 private KeySignatureLabel keysigLabel;
396 private JLabel songTitleLabel;
397 private AnoGakkiPane anoGakkiPane;
398 private JToggleButton anoGakkiToggleButton;
401 String imageIconPath = "midichordhelper.png";
402 URL imageIconUrl = getClass().getResource(imageIconPath);
403 if( imageIconUrl == null ) {
404 System.out.println("Icon image "+imageIconPath+" not found");
408 imageIcon = new ImageIcon(imageIconUrl);
410 Image iconImage = (imageIcon == null) ? null : imageIcon.getImage();
411 rootPaneDefaultBgcolor = getContentPane().getBackground();
412 chordMatrix = new ChordMatrix() {{
413 addChordMatrixListener(new ChordMatrixListener(){
414 public void keySignatureChanged() {
415 Key capoKey = getKeySignatureCapo();
416 keyboardPanel.keySelecter.setKey(capoKey);
417 keyboardPanel.keyboardCenterPanel.keyboard.setKeySignature(capoKey);
419 public void chordChanged() { chordOn(); }
422 chordMatrix.capoSelecter.checkbox.addItemListener(
424 public void itemStateChanged(ItemEvent e) {
426 keyboardPanel.keyboardCenterPanel.keyboard.chordDisplay.clear();
427 chordDiagram.clear();
431 chordMatrix.capoSelecter.valueSelecter.addActionListener(
432 new ActionListener() {
433 public void actionPerformed(ActionEvent e) {
435 keyboardPanel.keyboardCenterPanel.keyboard.chordDisplay.clear();
436 chordDiagram.clear();
440 keyboardPanel = new MidiKeyboardPanel(chordMatrix) {{
441 keyboardCenterPanel.keyboard.addPianoKeyboardListener(
442 new PianoKeyboardAdapter() {
444 public void pianoKeyPressed(int n, InputEvent e) {
445 chordDiagram.clear();
449 keySelecter.keysigCombobox.addActionListener(
450 new ActionListener() {
452 public void actionPerformed(ActionEvent e) {
453 Key key = keySelecter.getKey();
454 key.transpose( - chordMatrix.capoSelecter.getCapo() );
455 chordMatrix.setKeySignature(key);
459 keyboardCenterPanel.keyboard.setPreferredSize(new Dimension(571, 80));
461 deviceModelList = new MidiDeviceModelList(
462 new Vector<VirtualMidiDevice>() {
464 add(keyboardPanel.keyboardCenterPanel.keyboard.midiDevice);
468 deviceModelList.editorDialog.setIconImage(iconImage);
469 new DropTarget(this, DnDConstants.ACTION_COPY_OR_MOVE, deviceModelList.editorDialog, true);
470 keyboardPanel.setEventDialog(deviceModelList.editorDialog.eventDialog);
471 midiConnectionDialog = new MidiDeviceDialog(deviceModelList);
472 midiConnectionDialog.setIconImage(iconImage);
473 lyricDisplay = new ChordTextField(deviceModelList.getSequencerModel()) {{
474 addActionListener(new ActionListener() {
476 public void actionPerformed(ActionEvent event) {
477 String symbol = event.getActionCommand().trim().split("[ \t\r\n]")[0];
478 chordMatrix.setSelectedChord(symbol);
482 lyricDisplayDefaultBorder = lyricDisplay.getBorder();
483 lyricDisplayDefaultBgcolor = lyricDisplay.getBackground();
484 chordDiagram = new ChordDiagram(this);
485 tempoSelecter = new TempoSelecter() {{
487 deviceModelList.getSequencerModel().getSequencer().addMetaEventListener(this);
489 timesigSelecter = new TimeSignatureSelecter() {{
491 deviceModelList.getSequencerModel().getSequencer().addMetaEventListener(this);
493 keysigLabel = new KeySignatureLabel() {{
494 addMouseListener(new MouseAdapter() {
495 public void mousePressed(MouseEvent e) {
496 chordMatrix.setKeySignature(getKey());
500 deviceModelList.getSequencerModel().getSequencer().addMetaEventListener(
501 new MetaEventListener() {
502 class SetKeySignatureRunnable implements Runnable {
504 public SetKeySignatureRunnable(Key key) {
508 public void run() { setKeySignature(key); }
511 public void meta(MetaMessage msg) {
512 switch(msg.getType()) {
513 case 0x59: // Key signature (2 bytes) : 調号
514 Key key = new Key(msg.getData());
515 if( ! SwingUtilities.isEventDispatchThread() ) {
516 SwingUtilities.invokeLater(
517 new SetKeySignatureRunnable(key)
520 setKeySignature(key);
524 private void setKeySignature(Key key) {
525 keysigLabel.setKeySignature(key);
526 chordMatrix.setKeySignature(key);
530 songTitleLabel = new JLabel();
531 //シーケンサーの時間スライダーの値が変わったときのリスナーを登録
532 deviceModelList.getSequencerModel().addChangeListener(new ChangeListener() {
534 public void stateChanged(ChangeEvent e) {
535 SequenceTrackListTableModel sequenceTableModel = deviceModelList.getSequencerModel().getSequenceTrackListTableModel();
536 int loadedSequenceIndex = deviceModelList.editorDialog.sequenceListTable.getModel().indexOfSequenceOnSequencer();
537 songTitleLabel.setText(
539 loadedSequenceIndex < 0 ? "[No MIDI file loaded]" :
540 "MIDI file " + loadedSequenceIndex + ": " + (
541 sequenceTableModel == null ||
542 sequenceTableModel.toString() == null ||
543 sequenceTableModel.toString().isEmpty() ?
545 "<font color=maroon>"+sequenceTableModel+"</font>"
549 Sequencer sequencer = deviceModelList.getSequencerModel().getSequencer();
550 chordMatrix.setPlaying(sequencer.isRunning());
551 if( sequenceTableModel != null ) {
552 SequenceTickIndex tickIndex = sequenceTableModel.getSequenceTickIndex();
553 long tickPos = sequencer.getTickPosition();
554 tickIndex.tickToMeasure(tickPos);
555 chordMatrix.setBeat(tickIndex);
557 deviceModelList.getSequencerModel().getValueIsAdjusting() ||
558 ! (sequencer.isRunning() || sequencer.isRecording())
561 msg = tickIndex.lastMetaMessageAt(
562 SequenceTickIndex.MetaMessageType.TIME_SIGNATURE, tickPos
564 timesigSelecter.setValue(msg==null ? null : msg.getData());
565 msg = tickIndex.lastMetaMessageAt(
566 SequenceTickIndex.MetaMessageType.TEMPO, tickPos
568 tempoSelecter.setTempo(msg==null ? null : msg.getData());
569 msg = tickIndex.lastMetaMessageAt(
570 SequenceTickIndex.MetaMessageType.KEY_SIGNATURE, tickPos
576 Key key = new Key(msg.getData());
577 keysigLabel.setKeySignature(key);
578 chordMatrix.setKeySignature(key);
584 deviceModelList.getSequencerModel().fireStateChanged();
585 chordGuide = new JPanel() {
587 setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
588 add( Box.createHorizontalStrut(2) );
589 add( chordMatrix.chordGuide );
590 add( Box.createHorizontalStrut(2) );
592 add( Box.createHorizontalStrut(2) );
593 add( enterButtonLabel = new ChordButtonLabel("Enter",chordMatrix) {{
594 addMouseListener(new MouseAdapter() {
595 public void mousePressed(MouseEvent event) {
596 if( (event.getModifiersEx() & InputEvent.BUTTON3_DOWN_MASK) != 0 ) // RightClicked
597 chordMatrix.setSelectedChord((Chord)null);
599 chordMatrix.setSelectedChord(lyricDisplay.getText());
604 add( Box.createHorizontalStrut(5) );
605 add( chordMatrix.chordDisplay );
606 add( Box.createHorizontalStrut(5) );
607 add( darkModeToggleButton = new JToggleButton(new ButtonIcon(ButtonIcon.DARK_MODE_ICON)) {{
608 setMargin(ZERO_INSETS);
609 addItemListener(new ItemListener() {
610 public void itemStateChanged(ItemEvent e) {
611 innerSetDarkMode(darkModeToggleButton.isSelected());
614 setToolTipText("Light / Dark - 明かりを点灯/消灯");
617 add( Box.createHorizontalStrut(5) );
618 add( anoGakkiToggleButton = new JToggleButton(
619 new ButtonIcon(ButtonIcon.ANO_GAKKI_ICON)
622 setMargin(ZERO_INSETS);
624 setToolTipText("あの楽器");
627 public void itemStateChanged(ItemEvent e) {
628 keyboardPanel.keyboardCenterPanel.keyboard.anoGakkiPane
629 = anoGakkiToggleButton.isSelected() ? anoGakkiPane : null ;
634 add( Box.createHorizontalStrut(5) );
635 add( inversionOmissionButton = new InversionAndOmissionLabel() );
636 add( Box.createHorizontalStrut(5) );
637 add( chordMatrix.capoSelecter );
638 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(
646 JSplitPane.HORIZONTAL_SPLIT, keyboardPanel, chordDiagram
648 setOneTouchExpandable(true);
649 setResizeWeight(1.0);
650 setAlignmentX((float)0.5);
652 add(Box.createVerticalStrut(5));
654 setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
656 setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
657 add( Box.createHorizontalStrut(12) );
659 add( Box.createHorizontalStrut(12) );
660 add( timesigSelecter );
661 add( Box.createHorizontalStrut(12) );
662 add( tempoSelecter );
663 add( Box.createHorizontalStrut(12) );
664 add( new SequencerMeasureView(deviceModelList.getSequencerModel()) );
665 add( Box.createHorizontalStrut(12) );
666 add( songTitleLabel );
667 add( Box.createHorizontalStrut(12) );
668 add( new JButton(deviceModelList.editorDialog.openAction) {{ setMargin(ZERO_INSETS); }});
671 setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
672 add( Box.createHorizontalStrut(10) );
673 add( new JSlider(deviceModelList.getSequencerModel()) );
674 add( new SequencerTimeView(deviceModelList.getSequencerModel()) );
675 add( Box.createHorizontalStrut(5) );
676 add( new JButton(deviceModelList.editorDialog.sequenceListTable.getModel().moveToTopAction) {{
677 setMargin(ZERO_INSETS);
679 add(new JButton(deviceModelList.getSequencerModel().moveBackwardAction) {{
680 setMargin(ZERO_INSETS);
682 add(new JToggleButton(deviceModelList.getSequencerModel().startStopAction));
683 add(new JButton(deviceModelList.getSequencerModel().moveForwardAction) {{
684 setMargin(ZERO_INSETS);
686 add(new JButton(deviceModelList.editorDialog.sequenceListTable.getModel().moveToBottomAction) {{
687 setMargin(ZERO_INSETS);
689 add(new JToggleButton(deviceModelList.editorDialog.sequenceListTable.getModel().toggleRepeatAction) {{
690 setMargin(ZERO_INSETS);
692 add( Box.createHorizontalStrut(10) );
696 "MIDI device connection",
697 new ButtonIcon( ButtonIcon.MIDI_CONNECTOR_ICON )
699 addActionListener(midiConnectionDialog);
701 add(new JButton("Version info") {{
702 setToolTipText(VersionInfo.NAME + " " + VersionInfo.VERSION);
703 addActionListener(new AboutMessagePane());
708 setContentPane(new JLayeredPane() {
710 add(anoGakkiPane = new AnoGakkiPane(), JLayeredPane.PALETTE_LAYER);
711 addComponentListener(new ComponentAdapter() {
713 public void componentResized(ComponentEvent e) {
717 public void componentShown(ComponentEvent e) {
720 private void adjustSize() {
721 anoGakkiPane.setBounds(getBounds());
724 setLayout(new BorderLayout());
726 add(mainSplitPane = new JSplitPane(
727 JSplitPane.VERTICAL_SPLIT,
728 chordMatrix, keyboardSequencerPanel
731 setResizeWeight(0.5);
732 setAlignmentX((float)0.5);
738 setPreferredSize(new Dimension(750,470));
741 public void start() {
743 // コードボタンで設定されている現在の調を
745 chordMatrix.fireKeySignatureChanged();
747 // アプレットのパラメータにMIDIファイルのURLが指定されていたら
749 String midi_url = getParameter("midi_file");
751 if( midi_url != null ) {
752 addToPlaylist(midi_url);
758 deviceModelList.getSequencerModel().stop(); // MIDI再生を強制終了
761 private void innerSetDarkMode(boolean isDark) {
762 Color col = isDark ? Color.black : null;
763 getContentPane().setBackground(
764 isDark ? Color.black : rootPaneDefaultBgcolor
766 mainSplitPane.setBackground(col);
767 keyboardSplitPane.setBackground(col);
768 enterButtonLabel.setDarkMode(isDark);
769 chordGuide.setBackground(col);
770 lyricDisplay.setBorder(isDark ? null : lyricDisplayDefaultBorder);
771 lyricDisplay.setBackground(isDark ?
772 chordMatrix.darkModeColorset.backgrounds[2] :
773 lyricDisplayDefaultBgcolor
775 lyricDisplay.setForeground(isDark ? Color.white : null);
776 inversionOmissionButton.setBackground(col);
777 anoGakkiToggleButton.setBackground(col);
778 keyboardSequencerPanel.setBackground(col);
779 chordDiagram.setBackground(col);
780 chordDiagram.titleLabel.setDarkMode(isDark);
781 chordMatrix.setDarkMode(isDark);
782 keyboardPanel.setDarkMode(isDark);
785 private int[] chordOnNotes = null;
788 * <p>この関数を直接呼ぶとアルペジオが効かないので、
789 * chord_matrix.setSelectedChord() を使うことを推奨
792 public void chordOn() {
793 Chord playChord = chordMatrix.getSelectedChord();
795 chordOnNotes != null &&
796 chordMatrix.getNoteIndex() < 0 &&
797 (! chordMatrix.isDragged() || playChord == null)
799 // コードが鳴っている状態で、新たなコードを鳴らそうとしたり、
800 // もう鳴らさないという信号が来た場合は、今鳴っている音を止める。
802 for( int n : chordOnNotes )
803 keyboardPanel.keyboardCenterPanel.keyboard.noteOff(n);
806 if( playChord == null ) {
807 // もう鳴らさないので、歌詞表示に通知して終了
808 if( lyricDisplay != null )
809 lyricDisplay.appendChord(null);
813 if( keyboardPanel.keyboardCenterPanel.keyboard.anoGakkiPane != null ) {
814 JComponent btn = chordMatrix.getSelectedButton();
815 if( btn != null ) anoGakkiPane.start(chordMatrix, btn.getBounds());
817 // コードボタンからのコードを、カポつき演奏キーからオリジナルキーへ変換
818 Key originalKey = chordMatrix.getKeySignatureCapo();
819 Chord originalChord = playChord.clone().transpose(
820 chordMatrix.capoSelecter.getCapo(),
821 chordMatrix.getKeySignature()
823 // 変換後のコードをキーボード画面に設定
824 keyboardPanel.keyboardCenterPanel.keyboard.setChord(originalChord);
826 // 音域を決める。これにより鳴らす音が確定する。
827 Range chordRange = new Range(
828 keyboardPanel.keyboardCenterPanel.keyboard.getChromaticOffset() + 10 +
829 ( keyboardPanel.keyboardCenterPanel.keyboard.getOctaves() / 4 ) * 12,
830 inversionOmissionButton.isAutoInversionMode() ?
831 keyboardPanel.keyboardCenterPanel.keyboard.getChromaticOffset() + 21 :
832 keyboardPanel.keyboardCenterPanel.keyboard.getChromaticOffset() + 33,
834 inversionOmissionButton.isAutoInversionMode()
836 int[] notes = originalChord.toNoteArray(chordRange, originalKey);
838 // 前回鳴らしたコード構成音を覚えておく
839 int[] prevChordOnNotes = null;
840 if( chordMatrix.isDragged() || chordMatrix.getNoteIndex() >= 0 )
841 prevChordOnNotes = Arrays.copyOf(chordOnNotes, chordOnNotes.length);
844 chordOnNotes = new int[notes.length];
846 for( int n : notes ) {
847 if( inversionOmissionButton.getOmissionNoteIndex() == i ) {
850 chordOnNotes[i++] = n;
853 boolean isNoteOn = false;
854 if( prevChordOnNotes != null ) {
855 for( int prevN : prevChordOnNotes ) {
862 // すでに鳴っているのに単音を鳴らそうとする場合、
864 if( isNoteOn && chordMatrix.getNoteIndex() >= 0 &&
865 notes[chordMatrix.getNoteIndex()] - n == 0
867 keyboardPanel.keyboardCenterPanel.keyboard.noteOff(n);
872 keyboardPanel.keyboardCenterPanel.keyboard.noteOn(n);
876 keyboardPanel.keyboardCenterPanel.keyboard.setChord(originalChord);
877 chordMatrix.chordDisplay.setChord(playChord);
879 // コードダイアグラム用にもコードを表示
881 int chordDiagramCapo = chordDiagram.capoSelecterView.getCapo();
882 if( chordDiagramCapo == chordMatrix.capoSelecter.getCapo() )
883 diagramChord = playChord.clone();
885 diagramChord = originalChord.clone().transpose(
886 - chordDiagramCapo, originalKey
888 chordDiagram.setChord(diagramChord);
889 if( chordDiagram.recordTextButton.isSelected() )
890 lyricDisplay.appendChord(diagramChord);