1 package camidion.chordhelper;
2 import java.awt.BorderLayout;
4 import java.awt.Dimension;
6 import java.awt.Insets;
7 import java.awt.event.ActionEvent;
8 import java.awt.event.ComponentAdapter;
9 import java.awt.event.ComponentEvent;
10 import java.awt.event.InputEvent;
11 import java.awt.event.MouseAdapter;
12 import java.awt.event.MouseEvent;
13 import java.io.IOException;
14 import java.net.URISyntaxException;
16 import java.security.AccessControlException;
17 import java.util.Arrays;
19 import javax.sound.midi.InvalidMidiDataException;
20 import javax.sound.midi.MetaMessage;
21 import javax.sound.midi.Sequence;
22 import javax.sound.midi.Sequencer;
23 import javax.swing.Box;
24 import javax.swing.BoxLayout;
25 import javax.swing.ImageIcon;
26 import javax.swing.JApplet;
27 import javax.swing.JButton;
28 import javax.swing.JComponent;
29 import javax.swing.JLabel;
30 import javax.swing.JLayeredPane;
31 import javax.swing.JPanel;
32 import javax.swing.JSlider;
33 import javax.swing.JSplitPane;
34 import javax.swing.JToggleButton;
35 import javax.swing.SwingUtilities;
36 import javax.swing.border.Border;
38 import camidion.chordhelper.anogakki.AnoGakkiPane;
39 import camidion.chordhelper.chorddiagram.CapoComboBoxModel;
40 import camidion.chordhelper.chorddiagram.ChordDiagram;
41 import camidion.chordhelper.chordmatrix.ChordButtonLabel;
42 import camidion.chordhelper.chordmatrix.ChordMatrix;
43 import camidion.chordhelper.chordmatrix.ChordMatrixListener;
44 import camidion.chordhelper.mididevice.MidiDeviceDialog;
45 import camidion.chordhelper.mididevice.MidiDeviceTreeModel;
46 import camidion.chordhelper.mididevice.MidiSequencerModel;
47 import camidion.chordhelper.mididevice.SequencerMeasureView;
48 import camidion.chordhelper.mididevice.SequencerTimeView;
49 import camidion.chordhelper.mididevice.VirtualMidiDevice;
50 import camidion.chordhelper.midieditor.Base64Dialog;
51 import camidion.chordhelper.midieditor.KeySignatureLabel;
52 import camidion.chordhelper.midieditor.MidiSequenceEditorDialog;
53 import camidion.chordhelper.midieditor.NewSequenceDialog;
54 import camidion.chordhelper.midieditor.PlaylistTableModel;
55 import camidion.chordhelper.midieditor.SequenceTickIndex;
56 import camidion.chordhelper.midieditor.SequenceTrackListTableModel;
57 import camidion.chordhelper.midieditor.TempoSelecter;
58 import camidion.chordhelper.midieditor.TimeSignatureSelecter;
59 import camidion.chordhelper.music.Chord;
60 import camidion.chordhelper.music.Key;
61 import camidion.chordhelper.music.Range;
62 import camidion.chordhelper.pianokeyboard.MidiKeyboardPanel;
63 import camidion.chordhelper.pianokeyboard.PianoKeyboardAdapter;
66 * MIDI Chord Helper - Circle-of-fifth oriented chord pad
70 * Copyright (C) 2004-2017 @きよし - Akiyoshi Kamide
71 * http://www.yk.rim.or.jp/~kamide/music/chordhelper/
73 public class ChordHelperApplet extends JApplet {
74 /////////////////////////////////////////////////////////////////////
76 // JavaScript などからの呼び出しインターフェース
78 /////////////////////////////////////////////////////////////////////
80 * 未保存の修正済み MIDI ファイルがあるかどうか調べます。
81 * @return 未保存の修正済み MIDI ファイルがあれば true
83 public boolean isModified() {
84 return playlistModel.getSequenceModelList().stream().anyMatch(m -> m.isModified());
87 * 指定された小節数の曲を、乱数で自動作曲してプレイリストへ追加します。
88 * @param measureLength 小節数
89 * @return 追加先のインデックス値(0から始まる)。追加できなかったときは -1
90 * @throws InvalidMidiDataException {@link Sequencer#setSequence(Sequence)} を参照
92 public int addRandomSongToPlaylist(int measureLength) throws InvalidMidiDataException {
93 NewSequenceDialog d = midiEditor.newSequenceDialog;
94 d.setRandomChordProgression(measureLength);
95 return playlistModel.addSequenceAndPlay(d.getMidiSequence());
98 * URLで指定されたMIDIファイルをプレイリストへ追加します。
100 * <p>URL の最後の / より後ろの部分がファイル名として取り込まれます。
101 * 指定できる MIDI ファイルには、param タグの midi_file パラメータと同様の制限があります。
103 * @param midiFileUrl 追加するMIDIファイルのURL
104 * @return 追加先のインデックス値(0から始まる)。追加できなかったときは -1
106 public int addToPlaylist(String midiFileUrl) {
108 return playlistModel.addSequenceFromURL(midiFileUrl);
109 } catch( URISyntaxException|IOException|InvalidMidiDataException e ) {
110 midiEditor.showWarning(e);
111 } catch( AccessControlException e ) {
112 midiEditor.showError(e);
117 * Base64 エンコードされた MIDI ファイルをプレイリストへ追加します。
119 * @param base64EncodedText Base64エンコードされたMIDIファイル
120 * @return 追加先のインデックス値(0から始まる)。追加できなかったときは -1
122 public int addToPlaylistBase64(String base64EncodedText) {
123 return addToPlaylistBase64(base64EncodedText, null);
127 * Base64エンコードされたMIDIファイルをプレイリストへ追加します。
129 * @param base64EncodedText Base64エンコードされたMIDIファイル
130 * @param filename ディレクトリ名を除いたファイル名
131 * @return 追加先のインデックス値(0から始まる)。追加できなかったときは -1
133 public int addToPlaylistBase64(String base64EncodedText, String filename) {
134 Base64Dialog d = midiEditor.base64Dialog;
135 d.setBase64Data(base64EncodedText);
137 return playlistModel.addSequence(d.getMIDIData(), filename);
138 } catch (Exception e) {
139 midiEditor.showWarning(e);
144 * プレイリスト上で現在選択されているMIDIシーケンスを、
146 * @throws InvalidMidiDataException {@link Sequencer#setSequence(Sequence)} を参照
148 public void play() throws InvalidMidiDataException {
149 play(playlistModel.sequenceListSelectionModel.getMinSelectionIndex());
152 * 指定されたインデックス値が示すプレイリスト上のMIDIシーケンスを、
154 * @param index インデックス値(0から始まる)
155 * @throws InvalidMidiDataException {@link Sequencer#setSequence(Sequence)} を参照
157 public void play(int index) throws InvalidMidiDataException {
158 playlistModel.loadToSequencer(index); sequencerModel.start();
161 * シーケンサが実行中かどうかを返します。
162 * {@link Sequencer#isRunning()} の戻り値をそのまま返します。
166 public boolean isRunning() { return sequencerModel.getSequencer().isRunning(); }
168 * シーケンサが再生中かどうかを返します。
171 public boolean isPlaying() { return isRunning(); }
173 * 現在シーケンサにロードされているMIDIデータを
174 * Base64テキストに変換した結果を返します。
175 * @return MIDIデータをBase64テキストに変換した結果
177 public String getMidiDataBase64() {
178 SequenceTrackListTableModel sequenceModel = sequencerModel.getSequenceTrackListTableModel();
179 midiEditor.base64Dialog.setMIDIData(sequenceModel.getMIDIdata());
180 return midiEditor.base64Dialog.getBase64Data();
183 * 現在シーケンサにロードされているMIDIファイルのファイル名を返します。
184 * @return MIDIファイル名(設定されていないときは空文字列)
186 public String getMidiFilename() {
187 SequenceTrackListTableModel seq_model = sequencerModel.getSequenceTrackListTableModel();
188 if( seq_model == null ) return null;
189 String fn = seq_model.getFilename();
190 return fn == null ? "" : fn ;
194 * @param octavePosition オクターブ位置(デフォルト:4)
196 public void setOctavePosition(int octavePosition) {
197 keyboardPanel.keyboardCenterPanel.keyboard.octaveRangeModel.setValue(octavePosition);
200 * 操作対象のMIDIチャンネルを変更します。
201 * @param ch チャンネル番号 - 1(チャンネル1のとき0、デフォルトは0)
203 public void setChannel(int ch) {
204 keyboardPanel.keyboardCenterPanel.keyboard.midiChComboboxModel.setSelectedChannel(ch);
207 * 操作対象のMIDIチャンネルを返します。
208 * @return 操作対象のMIDIチャンネル
210 public int getChannel() {
211 return keyboardPanel.keyboardCenterPanel.keyboard.midiChComboboxModel.getSelectedChannel();
214 * 操作対象のMIDIチャンネルに対してプログラム(音色)を設定します。
215 * @param program 音色(0~127:General MIDI に基づく)
217 public void programChange(int program) {
218 keyboardPanel.keyboardCenterPanel.keyboard.getSelectedChannel().programChange(program);
221 * 操作対象のMIDIチャンネルに対してプログラム(音色)を設定します。
222 * 内部的には {@link #programChange(int)} を呼び出しているだけです。
223 * @param program 音色(0~127:General MIDI に基づく)
225 public void setProgram(int program) { programChange(program); }
227 * 自動転回モードを変更します。初期値は true です。
228 * @param isAuto true:自動転回を行う false:自動転回を行わない
230 public void setAutoInversion(boolean isAuto) {
231 inversionOmissionButton.setAutoInversion(isAuto);
237 * <li>-1:省略しない(デフォルト)</li>
243 public void setOmissionNoteIndex(int index) {
244 inversionOmissionButton.setOmissionNoteIndex(index);
247 * コードダイアグラムの表示・非表示を切り替えます。
248 * @param isVisible 表示するときtrue
250 public void setChordDiagramVisible(boolean isVisible) {
251 keyboardSplitPane.resetToPreferredSizes();
253 keyboardSplitPane.setDividerLocation((double)1.0);
256 * コードダイヤグラムをギターモードに変更します。
257 * 初期状態ではウクレレモードになっています。
259 public void setChordDiagramForGuitar() {
260 chordDiagram.setTargetInstrument(ChordDiagram.Instrument.Guitar);
263 * ダークモード(暗い表示)と明るい表示とを切り替えます。
264 * @param isDark ダークモードのときtrue、明るい表示のときfalse(デフォルト)
266 public void setDarkMode(boolean isDark) {
267 darkModeToggleButton.setSelected(isDark);
272 public static class VersionInfo {
273 public static final String NAME = "MIDI Chord Helper";
274 public static final String VERSION = "Ver.20170320.1";
275 public static final String COPYRIGHT = "Copyright (C) 2004-2017";
276 public static final String AUTHER = "@きよし - Akiyoshi Kamide";
277 public static final String URL = "http://www.yk.rim.or.jp/~kamide/music/chordhelper/";
280 public String getAppletInfo() {
281 return VersionInfo.NAME
282 + " " + VersionInfo.VERSION
283 + " " + VersionInfo.COPYRIGHT
284 + " " + VersionInfo.AUTHER
285 + " " + VersionInfo.URL;
288 * ボタンの余白を詰めたいときに setMargin() の引数に指定するインセット
290 public static final Insets ZERO_INSETS = new Insets(0,0,0,0);
293 MidiSequenceEditorDialog midiEditor;
294 PlaylistTableModel playlistModel;
295 MidiSequencerModel sequencerModel;
296 private ChordMatrix chordMatrix;
297 private JPanel keyboardSequencerPanel;
298 private JPanel chordGuide;
299 private Color rootPaneDefaultBgcolor;
300 private Color lyricDisplayDefaultBgcolor;
301 private Border lyricDisplayDefaultBorder;
302 private JSplitPane mainSplitPane;
303 private JSplitPane keyboardSplitPane;
304 private ChordButtonLabel enterButtonLabel;
305 private ChordTextField lyricDisplay;
306 private MidiKeyboardPanel keyboardPanel;
307 private InversionAndOmissionLabel inversionOmissionButton;
308 private JToggleButton darkModeToggleButton;
309 private ChordDiagram chordDiagram;
310 private KeySignatureLabel keysigLabel;
311 private AnoGakkiPane anoGakkiPane;
312 private JToggleButton anoGakkiToggleButton;
313 private MidiDeviceTreeModel deviceTreeModel;
316 private Image iconImage;
317 public Image getIconImage() { return iconImage; }
318 private ImageIcon imageIcon;
319 public ImageIcon getImageIcon() { return imageIcon; }
323 URL imageIconUrl = getClass().getResource("midichordhelper.png");
324 if( imageIconUrl != null ) {
325 iconImage = (imageIcon = new ImageIcon(imageIconUrl)).getImage();
327 AboutMessagePane about = new AboutMessagePane(imageIcon);
330 rootPaneDefaultBgcolor = getContentPane().getBackground();
332 // コードダイアグラム、コードボタン、ピアノ鍵盤のセットアップ
333 CapoComboBoxModel capoComboBoxModel = new CapoComboBoxModel();
334 chordDiagram = new ChordDiagram(capoComboBoxModel);
335 chordMatrix = new ChordMatrix(capoComboBoxModel) {{
336 addChordMatrixListener(new ChordMatrixListener(){
337 public void keySignatureChanged() {
338 Key capoKey = getKeySignatureCapo();
339 keyboardPanel.keySelecter.setSelectedKey(capoKey);
340 keyboardPanel.keyboardCenterPanel.keyboard.setKeySignature(capoKey);
342 public void chordChanged() { chordOn(); }
344 capoSelecter.checkbox.addItemListener(e->{
346 keyboardPanel.keyboardCenterPanel.keyboard.chordDisplay.clear();
347 chordDiagram.clear();
349 capoSelecter.valueSelecter.addActionListener(e->{
351 keyboardPanel.keyboardCenterPanel.keyboard.chordDisplay.clear();
352 chordDiagram.clear();
355 keysigLabel = new KeySignatureLabel() {{
356 addMouseListener(new MouseAdapter() {
358 public void mousePressed(MouseEvent e) { chordMatrix.setKeySignature(getKey()); }
361 keyboardPanel = new MidiKeyboardPanel(chordMatrix) {{
362 keyboardCenterPanel.keyboard.addPianoKeyboardListener(new PianoKeyboardAdapter() {
364 public void pianoKeyPressed(int n, InputEvent e) { chordDiagram.clear(); }
366 keySelecter.getKeysigCombobox().addActionListener(
367 e -> chordMatrix.setKeySignature(
368 keySelecter.getSelectedKey().transposedKey(
369 -chordMatrix.capoSelecter.getCapo()
373 keyboardCenterPanel.keyboard.setPreferredSize(new Dimension(571, 80));
375 VirtualMidiDevice guiMidiDevice = keyboardPanel.keyboardCenterPanel.keyboard.midiDevice;
378 deviceTreeModel = new MidiDeviceTreeModel(guiMidiDevice);
380 // MIDIシーケンサと連携するプレイリストモデルを構築
381 playlistModel = new PlaylistTableModel(sequencerModel = deviceTreeModel.getSequencerModel());
384 MidiDeviceDialog midiDeviceDialog = new MidiDeviceDialog(deviceTreeModel);
385 midiDeviceDialog.setIconImage(iconImage);
388 (midiEditor = new MidiSequenceEditorDialog(playlistModel, guiMidiDevice)).setIconImage(iconImage);
390 // メイン画面へのMIDIファイルのドラッグ&ドロップ受付開始
391 setTransferHandler(midiEditor.transferHandler);
393 // MIDIエディタのイベントダイアログを、ピアノ鍵盤のイベント送出ダイアログと共用
394 keyboardPanel.setEventDialog(midiEditor.eventDialog);
397 (lyricDisplay = new ChordTextField(sequencerModel)).addActionListener((ActionEvent e)->{
398 chordMatrix.setSelectedChord(e.getActionCommand().trim().split("[ \t\r\n]")[0]);
400 lyricDisplayDefaultBorder = lyricDisplay.getBorder();
401 lyricDisplayDefaultBgcolor = lyricDisplay.getBackground();
403 // メタイベント(テンポ・拍子・調号)を受信して表示するリスナーを登録
404 TempoSelecter tempoSelecter = new TempoSelecter() {{ setEditable(false); }};
405 TimeSignatureSelecter timesigSelecter = new TimeSignatureSelecter() {{ setEditable(false); }};
406 sequencerModel.getSequencer().addMetaEventListener(msg->{
407 switch(msg.getType()) {
408 case 0x51: // Tempo (3 bytes) - テンポ
409 SwingUtilities.invokeLater(()->tempoSelecter.setTempo(msg.getData()));
411 case 0x58: // Time signature (4 bytes) - 拍子
412 SwingUtilities.invokeLater(()->timesigSelecter.setValue(msg.getData()));
414 case 0x59: // Key signature (2 bytes) : 調号
415 SwingUtilities.invokeLater(()->setKeySignature(new Key(msg.getData())));
419 //シーケンサーの時間スライダーの値が変わったときのリスナーを登録
420 JLabel songTitleLabel = new JLabel();
421 sequencerModel.addChangeListener(e->{
422 SequenceTrackListTableModel sequenceTableModel = sequencerModel.getSequenceTrackListTableModel();
423 int loadedSequenceIndex = playlistModel.indexOfSequenceOnSequencer();
424 songTitleLabel.setText(
426 loadedSequenceIndex < 0 ? "[No MIDI file loaded]" :
427 "MIDI file " + loadedSequenceIndex + ": " + (
428 sequenceTableModel == null ||
429 sequenceTableModel.toString() == null ||
430 sequenceTableModel.toString().isEmpty() ?
432 "<font color=maroon>"+sequenceTableModel+"</font>"
436 Sequencer sequencer = sequencerModel.getSequencer();
437 chordMatrix.setPlaying(sequencer.isRunning());
438 if( sequenceTableModel != null ) {
439 SequenceTickIndex tickIndex = sequenceTableModel.getSequenceTickIndex();
440 long tickPos = sequencer.getTickPosition();
441 tickIndex.tickToMeasure(tickPos);
442 chordMatrix.setBeat(tickIndex);
443 if( sequencerModel.getValueIsAdjusting() || ! (sequencer.isRunning() || sequencer.isRecording()) ) {
445 msg = tickIndex.lastMetaMessageAt(
446 SequenceTickIndex.MetaMessageType.TIME_SIGNATURE, tickPos
448 timesigSelecter.setValue(msg==null ? null : msg.getData());
449 msg = tickIndex.lastMetaMessageAt(
450 SequenceTickIndex.MetaMessageType.TEMPO, tickPos
452 tempoSelecter.setTempo(msg==null ? null : msg.getData());
453 msg = tickIndex.lastMetaMessageAt(
454 SequenceTickIndex.MetaMessageType.KEY_SIGNATURE, tickPos
456 if(msg == null) keysigLabel.clear();
457 else setKeySignature(new Key(msg.getData()));
461 sequencerModel.fireStateChanged();
462 chordGuide = new JPanel() {{
463 setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
464 add( Box.createHorizontalStrut(2) );
465 add( chordMatrix.chordGuide );
466 add( Box.createHorizontalStrut(2) );
468 add( Box.createHorizontalStrut(2) );
469 add( enterButtonLabel = new ChordButtonLabel("Enter",chordMatrix) {{
470 addMouseListener(new MouseAdapter() {
471 public void mousePressed(MouseEvent event) {
472 if( (event.getModifiersEx() & InputEvent.BUTTON3_DOWN_MASK) != 0 ) // RightClicked
473 chordMatrix.setSelectedChord((Chord)null);
475 chordMatrix.setSelectedChord(lyricDisplay.getText());
480 add( Box.createHorizontalStrut(5) );
481 add( chordMatrix.chordDisplay );
482 add( Box.createHorizontalStrut(5) );
483 add( darkModeToggleButton = new JToggleButton(new ButtonIcon(ButtonIcon.DARK_MODE_ICON)) {{
484 setMargin(ZERO_INSETS);
485 addItemListener(e->innerSetDarkMode(darkModeToggleButton.isSelected()));
486 setToolTipText("Light / Dark - 明かりを点灯/消灯");
489 add( Box.createHorizontalStrut(5) );
490 add( anoGakkiToggleButton = new JToggleButton(new ButtonIcon(ButtonIcon.ANO_GAKKI_ICON)) {{
492 setMargin(ZERO_INSETS);
494 setToolTipText("あの楽器");
496 e -> keyboardPanel.keyboardCenterPanel.keyboard.anoGakkiPane
497 = anoGakkiToggleButton.isSelected() ? anoGakkiPane : null
500 add( Box.createHorizontalStrut(5) );
501 add( inversionOmissionButton = new InversionAndOmissionLabel() );
502 add( Box.createHorizontalStrut(5) );
503 add( chordMatrix.capoSelecter );
504 add( Box.createHorizontalStrut(2) );
506 keyboardSequencerPanel = new JPanel() {{
507 setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
509 add(Box.createVerticalStrut(5));
510 add(keyboardSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, keyboardPanel, chordDiagram) {{
511 setOneTouchExpandable(true);
512 setResizeWeight(1.0);
513 setAlignmentX((float)0.5);
515 add(Box.createVerticalStrut(5));
517 setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
519 setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
520 add(Box.createHorizontalStrut(12));
522 add(Box.createHorizontalStrut(12));
523 add(timesigSelecter);
524 add(Box.createHorizontalStrut(12));
526 add(Box.createHorizontalStrut(12));
527 add(new SequencerMeasureView(sequencerModel));
528 add(Box.createHorizontalStrut(12));
530 add(Box.createHorizontalStrut(12));
531 add(new JButton(midiEditor.openAction) {{ setMargin(ZERO_INSETS); }});
534 setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
535 add(Box.createHorizontalStrut(10));
536 add(new JSlider(sequencerModel));
537 add(new SequencerTimeView(sequencerModel));
538 add(Box.createHorizontalStrut(5));
539 add(new JButton(playlistModel.getMoveToTopAction()) {{ setMargin(ZERO_INSETS); }});
540 add(new JButton(sequencerModel.getMoveBackwardAction()) {{ setMargin(ZERO_INSETS); }});
541 add(new JToggleButton(sequencerModel.getStartStopAction()));
542 add(new JButton(sequencerModel.getMoveForwardAction()) {{ setMargin(ZERO_INSETS); }});
543 add(new JButton(playlistModel.getMoveToBottomAction()) {{ setMargin(ZERO_INSETS); }});
544 add(new JToggleButton(playlistModel.getToggleRepeatAction()) {{ setMargin(ZERO_INSETS); }});
545 add( Box.createHorizontalStrut(10) );
548 add(new JButton(midiDeviceDialog.openAction));
549 add(new JButton(about.getOpenAction()));
553 setContentPane(new JLayeredPane() {
555 add(anoGakkiPane = new AnoGakkiPane(), JLayeredPane.PALETTE_LAYER);
556 addComponentListener(new ComponentAdapter() {
558 public void componentResized(ComponentEvent e) { adjustSize(); }
560 public void componentShown(ComponentEvent e) { adjustSize(); }
561 private void adjustSize() { anoGakkiPane.setBounds(getBounds()); }
563 setLayout(new BorderLayout());
565 add(mainSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, chordMatrix, keyboardSequencerPanel){
567 setResizeWeight(0.5);
568 setAlignmentX((float)0.5);
574 setPreferredSize(new Dimension(750,470));
577 public void destroy() {
578 deviceTreeModel.closeAllDevices();
582 public void start() {
584 // コードボタンで設定されている現在の調を
586 chordMatrix.fireKeySignatureChanged();
588 // アプレットのパラメータにMIDIファイルのURLが指定されていたら
590 String midi_url = getParameter("midi_file");
592 if( midi_url != null ) {
593 addToPlaylist(midi_url);
596 } catch (InvalidMidiDataException ex) {
597 ex.printStackTrace();
603 sequencerModel.stop(); // MIDI再生を強制終了
606 private void innerSetDarkMode(boolean isDark) {
607 Color col = isDark ? Color.black : null;
608 getContentPane().setBackground(isDark ? Color.black : rootPaneDefaultBgcolor);
609 mainSplitPane.setBackground(col);
610 keyboardSplitPane.setBackground(col);
611 enterButtonLabel.setDarkMode(isDark);
612 chordGuide.setBackground(col);
613 lyricDisplay.setBorder(isDark ? null : lyricDisplayDefaultBorder);
614 lyricDisplay.setBackground(isDark ?
615 chordMatrix.darkModeColorset.backgrounds[2] :
616 lyricDisplayDefaultBgcolor
618 lyricDisplay.setForeground(isDark ? Color.white : null);
619 inversionOmissionButton.setBackground(col);
620 anoGakkiToggleButton.setBackground(col);
621 keyboardSequencerPanel.setBackground(col);
622 chordDiagram.setBackground(col);
623 chordDiagram.titleLabel.setDarkMode(isDark);
624 chordMatrix.setDarkMode(isDark);
625 keyboardPanel.setDarkMode(isDark);
628 private void setKeySignature(Key key) {
629 keysigLabel.setKey(key);
630 chordMatrix.setKeySignature(key);
633 private int[] chordOnNotes = null;
636 * <p>この関数を直接呼ぶとアルペジオが効かないので、
637 * chordMatrix.setSelectedChord() を使うことを推奨
640 public void chordOn() {
641 Chord playChord = chordMatrix.getSelectedChord();
643 chordOnNotes != null &&
644 chordMatrix.getNoteIndex() < 0 &&
645 (! chordMatrix.isDragged() || playChord == null)
647 // コードが鳴っている状態で、新たなコードを鳴らそうとしたり、
648 // もう鳴らさないという信号が来た場合は、今鳴っている音を止める。
650 for( int n : chordOnNotes )
651 keyboardPanel.keyboardCenterPanel.keyboard.noteOff(n);
654 if( playChord == null ) {
655 // もう鳴らさないので、歌詞表示に通知して終了
656 if( lyricDisplay != null ) lyricDisplay.appendChord(null);
660 if( keyboardPanel.keyboardCenterPanel.keyboard.anoGakkiPane != null ) {
661 JComponent btn = chordMatrix.getSelectedButton();
662 if( btn != null ) anoGakkiPane.start(chordMatrix, btn.getBounds());
664 // コードボタンからのコードを、カポつき演奏キーからオリジナルキーへ変換
665 Key originalKey = chordMatrix.getKeySignatureCapo();
666 Chord originalChord = playChord.transposedNewChord(
667 chordMatrix.capoSelecter.getCapo(),
668 chordMatrix.getKeySignature()
670 // 変換後のコードをキーボード画面に設定
671 keyboardPanel.keyboardCenterPanel.keyboard.setChord(originalChord);
673 // 音域を決める。これにより鳴らす音が確定する。
674 Range chordRange = new Range(
675 keyboardPanel.keyboardCenterPanel.keyboard.getChromaticOffset() + 10 +
676 ( keyboardPanel.keyboardCenterPanel.keyboard.getOctaves() / 4 ) * 12,
677 inversionOmissionButton.isAutoInversionMode() ?
678 keyboardPanel.keyboardCenterPanel.keyboard.getChromaticOffset() + 21 :
679 keyboardPanel.keyboardCenterPanel.keyboard.getChromaticOffset() + 33,
681 inversionOmissionButton.isAutoInversionMode()
683 int[] notes = originalChord.toNoteArray(chordRange, originalKey);
685 // 前回鳴らしたコード構成音を覚えておく
686 int[] prevChordOnNotes = null;
687 if( chordMatrix.isDragged() || chordMatrix.getNoteIndex() >= 0 )
688 prevChordOnNotes = Arrays.copyOf(chordOnNotes, chordOnNotes.length);
691 chordOnNotes = new int[notes.length];
693 for( int n : notes ) {
694 if( inversionOmissionButton.getOmissionNoteIndex() == i ) {
697 chordOnNotes[i++] = n;
700 boolean isNoteOn = false;
701 if( prevChordOnNotes != null ) {
702 for( int prevN : prevChordOnNotes ) {
709 // すでに鳴っているのに単音を鳴らそうとする場合、
711 if( isNoteOn && chordMatrix.getNoteIndex() >= 0 &&
712 notes[chordMatrix.getNoteIndex()] - n == 0
714 keyboardPanel.keyboardCenterPanel.keyboard.noteOff(n);
719 keyboardPanel.keyboardCenterPanel.keyboard.noteOn(n);
723 keyboardPanel.keyboardCenterPanel.keyboard.setChord(originalChord);
724 chordMatrix.chordDisplay.setChord(playChord);
726 // コードダイアグラム用にもコードを表示
728 int chordDiagramCapo = chordDiagram.capoSelecterView.getCapo();
729 if( chordDiagramCapo == chordMatrix.capoSelecter.getCapo() )
730 diagramChord = playChord;
732 diagramChord = originalChord.transposedNewChord(-chordDiagramCapo, originalKey);
733 chordDiagram.setChord(diagramChord);
734 if( chordDiagram.recordTextButton.isSelected() )
735 lyricDisplay.appendChord(diagramChord);