1 package camidion.chordhelper;
2 import java.awt.BorderLayout;
4 import java.awt.Dimension;
6 import java.awt.Insets;
7 import java.awt.event.ComponentAdapter;
8 import java.awt.event.ComponentEvent;
9 import java.awt.event.InputEvent;
10 import java.awt.event.MouseAdapter;
11 import java.awt.event.MouseEvent;
12 import java.io.IOException;
14 import java.net.URISyntaxException;
16 import java.util.Arrays;
18 import javax.sound.midi.InvalidMidiDataException;
19 import javax.sound.midi.MetaMessage;
20 import javax.sound.midi.MidiSystem;
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.JOptionPane;
32 import javax.swing.JPanel;
33 import javax.swing.JSlider;
34 import javax.swing.JSplitPane;
35 import javax.swing.JToggleButton;
36 import javax.swing.SwingUtilities;
37 import javax.swing.border.Border;
39 import camidion.chordhelper.anogakki.AnoGakkiPane;
40 import camidion.chordhelper.chorddiagram.CapoComboBoxModel;
41 import camidion.chordhelper.chorddiagram.ChordDiagram;
42 import camidion.chordhelper.chordmatrix.ChordButtonLabel;
43 import camidion.chordhelper.chordmatrix.ChordMatrix;
44 import camidion.chordhelper.chordmatrix.ChordMatrixListener;
45 import camidion.chordhelper.mididevice.MidiDeviceDialog;
46 import camidion.chordhelper.mididevice.MidiDeviceTreeModel;
47 import camidion.chordhelper.mididevice.MidiSequencerModel;
48 import camidion.chordhelper.mididevice.SequencerMeasureView;
49 import camidion.chordhelper.mididevice.SequencerTimeView;
50 import camidion.chordhelper.mididevice.VirtualMidiDevice;
51 import camidion.chordhelper.midieditor.Base64Dialog;
52 import camidion.chordhelper.midieditor.KeySignatureLabel;
53 import camidion.chordhelper.midieditor.MidiSequenceEditorDialog;
54 import camidion.chordhelper.midieditor.NewSequenceDialog;
55 import camidion.chordhelper.midieditor.PlaylistTableModel;
56 import camidion.chordhelper.midieditor.SequenceTickIndex;
57 import camidion.chordhelper.midieditor.SequenceTrackListTableModel;
58 import camidion.chordhelper.midieditor.TempoSelecter;
59 import camidion.chordhelper.midieditor.TimeSignatureSelecter;
60 import camidion.chordhelper.music.Chord;
61 import camidion.chordhelper.music.Key;
62 import camidion.chordhelper.music.Range;
63 import camidion.chordhelper.pianokeyboard.MidiKeyboardPanel;
64 import camidion.chordhelper.pianokeyboard.PianoKeyboardAdapter;
67 * MIDI Chord Helper - Circle-of-fifth oriented chord pad
71 * Copyright (C) 2004-2017 @きよし - Akiyoshi Kamide
72 * http://www.yk.rim.or.jp/~kamide/music/chordhelper/
74 public class ChordHelperApplet extends JApplet {
75 /////////////////////////////////////////////////////////////////////
77 // JavaScript などからの呼び出しインターフェース
79 /////////////////////////////////////////////////////////////////////
81 * 未保存の修正済み MIDI ファイルがあるかどうか調べます。
82 * @return 未保存の修正済み MIDI ファイルがあれば true
84 public boolean isModified() {
85 return playlistModel.getSequenceModelList().stream().anyMatch(m -> m.isModified());
88 * 指定された小節数の曲を、乱数で自動作曲してプレイリストへ追加し、再生します。
89 * @param measureLength 小節数
90 * @return 追加先のインデックス値(0から始まる)。追加できなかったときは -1
91 * @throws InvalidMidiDataException {@link Sequencer#setSequence(Sequence)} を参照
92 * @throws IllegalStateException MIDIシーケンサデバイスが閉じている場合
94 public int addRandomSongToPlaylist(int measureLength) throws InvalidMidiDataException {
95 NewSequenceDialog d = midiEditor.newSequenceDialog;
96 d.setRandomChordProgression(measureLength);
97 return playlistModel.play(d.getMidiSequence());
100 * URLで指定されたMIDIファイルをプレイリストへ追加します。
102 * <p>URL の最後の / より後ろの部分がファイル名として取り込まれます。
103 * 指定できる MIDI ファイルには、param タグの midi_file パラメータと同様の制限があります。
105 * @param midiFileUrl 追加するMIDIファイルのURL
106 * @return 追加先のインデックス値(0から始まる)。追加できなかったときは -1
108 public int addToPlaylist(String midiFileUrl) {
110 URL url = (new URI(midiFileUrl)).toURL();
111 String filename = url.getFile().replaceFirst("^.*/","");
112 Sequence sequence = MidiSystem.getSequence(url);
113 return playlistModel.add(sequence, filename);
114 } catch( URISyntaxException|IOException|InvalidMidiDataException e ) {
115 JOptionPane.showMessageDialog(null, e, VersionInfo.NAME, JOptionPane.WARNING_MESSAGE);
116 } catch( Exception e ) {
117 JOptionPane.showMessageDialog(null, e, VersionInfo.NAME, JOptionPane.ERROR_MESSAGE);
122 * Base64 エンコードされた MIDI ファイルをプレイリストへ追加します。
124 * @param base64EncodedText Base64エンコードされたMIDIファイル
125 * @return 追加先のインデックス値(0から始まる)。追加できなかったときは -1
127 public int addToPlaylistBase64(String base64EncodedText) {
128 return addToPlaylistBase64(base64EncodedText, null);
131 * ファイル名を指定して、Base64エンコードされたMIDIファイルをプレイリストへ追加します。
133 * @param base64EncodedText Base64エンコードされたMIDIファイル
134 * @param filename ディレクトリ名を除いたファイル名
135 * @return 追加先のインデックス値(0から始まる)。追加できなかったときは -1
137 public int addToPlaylistBase64(String base64EncodedText, String filename) {
138 Base64Dialog d = midiEditor.sequenceListTable.base64Dialog;
139 d.setBase64Data(base64EncodedText, filename);
140 return d.addToPlaylist();
143 * プレイリスト上で現在選択されているMIDIシーケンスをシーケンサへロードして再生します。
146 play(playlistModel.sequenceListSelectionModel.getMinSelectionIndex());
149 * 指定されたインデックス値が示すプレイリスト上のMIDIシーケンスをシーケンサへロードして再生します。
150 * @param index インデックス値(0から始まる)
152 public void play(int index) { midiEditor.play(index); }
154 * シーケンサが実行中かどうかを返します。
155 * {@link Sequencer#isRunning()} の戻り値をそのまま返します。
159 public boolean isRunning() { return sequencerModel.getSequencer().isRunning(); }
161 * シーケンサが再生中かどうかを返します。
164 public boolean isPlaying() { return isRunning(); }
166 * 現在シーケンサにロードされているMIDIデータをBase64テキストに変換した結果を返します。
167 * @return MIDIデータをBase64テキストに変換した結果(シーケンサにロードされていない場合null)
168 * @throws IOException MIDIデータの読み込みに失敗した場合
170 public String getMidiDataBase64() throws IOException {
171 SequenceTrackListTableModel s = sequencerModel.getSequenceTrackListTableModel();
172 if( s == null ) return null;
173 Base64Dialog d = midiEditor.sequenceListTable.base64Dialog;
174 d.setMIDIData(s.getMIDIdata());
175 return d.getBase64Data();
178 * 現在シーケンサにロードされているMIDIファイルのファイル名を返します。
179 * @return MIDIファイル名(設定されていないときは空文字列、シーケンサにロードされていない場合null)
181 public String getMidiFilename() {
182 SequenceTrackListTableModel s = sequencerModel.getSequenceTrackListTableModel();
183 if( s == null ) return null;
184 String fn = s.getFilename();
185 return fn == null ? "" : fn;
189 * @param octavePosition オクターブ位置(デフォルト:4)
191 public void setOctavePosition(int octavePosition) {
192 keyboardPanel.keyboardCenterPanel.keyboard.octaveRangeModel.setValue(octavePosition);
195 * 操作対象のMIDIチャンネルを変更します。
196 * @param ch チャンネル番号 - 1(チャンネル1のとき0、デフォルトは0)
198 public void setChannel(int ch) {
199 keyboardPanel.keyboardCenterPanel.keyboard.midiChComboboxModel.setSelectedChannel(ch);
202 * 操作対象のMIDIチャンネルを返します。
203 * @return 操作対象のMIDIチャンネル
205 public int getChannel() {
206 return keyboardPanel.keyboardCenterPanel.keyboard.midiChComboboxModel.getSelectedChannel();
209 * 操作対象のMIDIチャンネルに対してプログラム(音色)を設定します。
210 * @param program 音色(0~127:General MIDI に基づく)
212 public void programChange(int program) {
213 keyboardPanel.keyboardCenterPanel.keyboard.getSelectedChannel().programChange(program);
216 * 操作対象のMIDIチャンネルに対してプログラム(音色)を設定します。
217 * 内部的には {@link #programChange(int)} を呼び出しているだけです。
218 * @param program 音色(0~127:General MIDI に基づく)
220 public void setProgram(int program) { programChange(program); }
222 * 自動転回モードを変更します。初期値は true です。
223 * @param isAuto true:自動転回を行う false:自動転回を行わない
225 public void setAutoInversion(boolean isAuto) {
226 inversionOmissionButton.setAutoInversion(isAuto);
232 * <li>-1:省略しない(デフォルト)</li>
238 public void setOmissionNoteIndex(int index) {
239 inversionOmissionButton.setOmissionNoteIndex(index);
242 * コードダイアグラムの表示・非表示を切り替えます。
243 * @param isVisible 表示するときtrue
245 public void setChordDiagramVisible(boolean isVisible) {
246 keyboardSplitPane.resetToPreferredSizes();
247 if( ! isVisible ) keyboardSplitPane.setDividerLocation((double)1.0);
250 * コードダイヤグラムをギターモードに変更します。
251 * 初期状態ではウクレレモードになっています。
253 public void setChordDiagramForGuitar() {
254 chordDiagram.setTargetInstrument(ChordDiagram.Instrument.Guitar);
257 * ダークモード(暗い表示)と明るい表示とを切り替えます。
258 * @param isDark ダークモードのときtrue、明るい表示のときfalse(デフォルト)
260 public void setDarkMode(boolean isDark) {
261 darkModeToggleButton.setSelected(isDark);
266 public static class VersionInfo {
267 public static final String NAME = "MIDI Chord Helper";
268 public static final String VERSION = "Ver.20170419.1";
269 public static final String COPYRIGHT = "Copyright (C) 2004-2017";
270 public static final String AUTHER = "@きよし - Akiyoshi Kamide";
271 public static final String URL = "http://www.yk.rim.or.jp/~kamide/music/chordhelper/";
274 public String getAppletInfo() {
275 return String.join(" ",
276 VersionInfo.NAME, VersionInfo.VERSION,
277 VersionInfo.COPYRIGHT ,VersionInfo.AUTHER, VersionInfo.URL);
279 /** ボタンの余白を詰めたいときに setMargin() の引数に指定するインセット */
280 public static final Insets ZERO_INSETS = new Insets(0,0,0,0);
282 // MIDIエディタダイアログ(Javaアプリメインからもアクセスできるようprivateにしていない)
283 MidiSequenceEditorDialog midiEditor;
286 private PlaylistTableModel playlistModel;
287 private MidiSequencerModel sequencerModel;
288 private ChordMatrix chordMatrix;
289 private JPanel keyboardSequencerPanel;
290 private JPanel chordGuide;
291 private Color rootPaneDefaultBgcolor;
292 private Color lyricDisplayDefaultBgcolor;
293 private Border lyricDisplayDefaultBorder;
294 private JSplitPane mainSplitPane;
295 private JSplitPane keyboardSplitPane;
296 private ChordButtonLabel enterButtonLabel;
297 private ChordTextField lyricDisplay;
298 private MidiKeyboardPanel keyboardPanel;
299 private InversionAndOmissionLabel inversionOmissionButton;
300 private JToggleButton darkModeToggleButton;
301 private ChordDiagram chordDiagram;
302 private KeySignatureLabel keysigLabel;
303 private AnoGakkiPane anoGakkiPane;
304 private JToggleButton anoGakkiToggleButton;
305 private MidiDeviceTreeModel deviceTreeModel;
308 private Image iconImage;
309 public Image getIconImage() { return iconImage; }
310 private ImageIcon imageIcon;
311 public ImageIcon getImageIcon() { return imageIcon; }
316 URL imageIconUrl = getClass().getResource("midichordhelper.png");
317 if( imageIconUrl != null ) {
318 iconImage = (imageIcon = new ImageIcon(imageIconUrl)).getImage();
320 AboutMessagePane about = new AboutMessagePane(imageIcon);
323 rootPaneDefaultBgcolor = getContentPane().getBackground();
325 // コードダイアグラム、コードボタン、ピアノ鍵盤、およびそれらの仮想MIDIデバイスを生成
326 CapoComboBoxModel capoComboBoxModel = new CapoComboBoxModel();
327 chordDiagram = new ChordDiagram(capoComboBoxModel);
328 chordMatrix = new ChordMatrix(capoComboBoxModel) {
329 private void clearChord() {
331 keyboardPanel.keyboardCenterPanel.keyboard.chordDisplay.clear();
332 chordDiagram.clear();
335 addChordMatrixListener(new ChordMatrixListener(){
336 public void keySignatureChanged() {
337 keyboardPanel.setCapoKey(getKeySignatureCapo());
339 public void chordChanged() { chordOn(); }
341 capoSelecter.checkbox.addItemListener(e->clearChord());
342 capoSelecter.valueSelecter.addActionListener(e->clearChord());
345 keysigLabel = new KeySignatureLabel() {{
346 addMouseListener(new MouseAdapter() {
348 public void mousePressed(MouseEvent e) {
349 chordMatrix.setKeySignature(getKey());
353 keyboardPanel = new MidiKeyboardPanel(chordMatrix) {{
354 keyboardCenterPanel.keyboard.addPianoKeyboardListener(new PianoKeyboardAdapter() {
356 public void pianoKeyPressed(int n, InputEvent e) { chordDiagram.clear(); }
358 keySelecter.getKeysigCombobox().addActionListener(e->chordMatrix.setKeySignature(
359 keySelecter.getSelectedKey().transposedKey(-chordMatrix.capoSelecter.getCapo())
361 keyboardCenterPanel.keyboard.setPreferredSize(new Dimension(571, 80));
363 // MIDIデバイスとMIDIエディタのセットアップ
364 VirtualMidiDevice guiMidiDevice = keyboardPanel.keyboardCenterPanel.keyboard.midiDevice;
365 deviceTreeModel = new MidiDeviceTreeModel(guiMidiDevice);
366 sequencerModel = deviceTreeModel.getSequencerModel();
367 playlistModel = new PlaylistTableModel(sequencerModel);
368 MidiDeviceDialog midiDeviceDialog = new MidiDeviceDialog(deviceTreeModel);
369 midiDeviceDialog.setIconImage(iconImage);
370 midiEditor = new MidiSequenceEditorDialog(playlistModel, guiMidiDevice, midiDeviceDialog.getOpenAction());
371 midiEditor.setIconImage(iconImage);
373 // メイン画面へのMIDIファイルのドラッグ&ドロップ受付開始
374 setTransferHandler(midiEditor.transferHandler);
376 // MIDIエディタのイベントダイアログを、ピアノ鍵盤のイベント送出ダイアログと共用
377 keyboardPanel.setEventDialog(midiEditor.eventListTable.eventDialog);
380 (lyricDisplay = new ChordTextField(sequencerModel)).addActionListener(
381 e->chordMatrix.setSelectedChord(e.getActionCommand().trim().split("[ \t\r\n]")[0])
383 lyricDisplayDefaultBorder = lyricDisplay.getBorder();
384 lyricDisplayDefaultBgcolor = lyricDisplay.getBackground();
386 // メタイベント(テンポ・拍子・調号)を受信して表示するリスナーを登録
387 TempoSelecter tempoSelecter = new TempoSelecter() {{ setEditable(false); }};
388 TimeSignatureSelecter timesigSelecter = new TimeSignatureSelecter() {{ setEditable(false); }};
389 sequencerModel.getSequencer().addMetaEventListener(msg->{
390 switch(msg.getType()) {
391 case 0x51: // Tempo (3 bytes) - テンポ
392 SwingUtilities.invokeLater(()->tempoSelecter.setTempo(msg.getData()));
394 case 0x58: // Time signature (4 bytes) - 拍子
395 SwingUtilities.invokeLater(()->timesigSelecter.setValue(msg.getData()));
397 case 0x59: // Key signature (2 bytes) : 調号
398 SwingUtilities.invokeLater(()->setKeySignature(new Key(msg.getData())));
402 //シーケンサーの時間スライダーの値が変わったときのリスナーを登録
403 JLabel songTitleLabel = new JLabel();
404 sequencerModel.addChangeListener(e->{
405 SequenceTrackListTableModel sequenceModel = sequencerModel.getSequenceTrackListTableModel();
406 int loadedSequenceIndex = playlistModel.getSequenceModelList().indexOf(sequenceModel);
407 songTitleLabel.setText("<html>"+(
408 loadedSequenceIndex < 0 ? "[No MIDI file loaded]" :
409 "MIDI file " + loadedSequenceIndex + ": " + (
410 sequenceModel == null || sequenceModel.toString().isEmpty() ?
412 "<font color=maroon>"+sequenceModel+"</font>"
415 Sequencer sequencer = sequencerModel.getSequencer();
416 chordMatrix.setPlaying(sequencer.isRunning());
417 if( sequenceModel != null ) {
418 SequenceTickIndex tickIndex = sequenceModel.getSequenceTickIndex();
419 long tickPos = sequencer.getTickPosition();
420 tickIndex.tickToMeasure(tickPos);
421 chordMatrix.setBeat(tickIndex);
422 if( sequencerModel.getValueIsAdjusting() || ! (sequencer.isRunning() || sequencer.isRecording()) ) {
424 msg = tickIndex.lastMetaMessageAt(
425 SequenceTickIndex.MetaMessageType.TIME_SIGNATURE, tickPos
427 timesigSelecter.setValue(msg==null ? null : msg.getData());
428 msg = tickIndex.lastMetaMessageAt(
429 SequenceTickIndex.MetaMessageType.TEMPO, tickPos
431 tempoSelecter.setTempo(msg==null ? null : msg.getData());
432 msg = tickIndex.lastMetaMessageAt(
433 SequenceTickIndex.MetaMessageType.KEY_SIGNATURE, tickPos
435 if(msg == null) keysigLabel.clear(); else setKeySignature(new Key(msg.getData()));
439 sequencerModel.fireStateChanged();
440 chordGuide = new JPanel() {{
441 setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
442 add( Box.createHorizontalStrut(2) );
443 add( chordMatrix.chordGuide );
444 add( Box.createHorizontalStrut(2) );
446 add( Box.createHorizontalStrut(2) );
447 add( enterButtonLabel = new ChordButtonLabel("Enter",chordMatrix) {{
448 addMouseListener(new MouseAdapter() {
449 public void mousePressed(MouseEvent event) {
450 boolean rightClicked = (event.getModifiersEx() & InputEvent.BUTTON3_DOWN_MASK) != 0;
452 chordMatrix.setSelectedChord((Chord)null);
454 chordMatrix.setSelectedChord(lyricDisplay.getText());
458 add( Box.createHorizontalStrut(5) );
459 add( chordMatrix.chordDisplay );
460 add( Box.createHorizontalStrut(5) );
461 add( darkModeToggleButton = new JToggleButton(new ButtonIcon(ButtonIcon.DARK_MODE_ICON)) {{
462 setMargin(ZERO_INSETS);
463 addItemListener(e->innerSetDarkMode(darkModeToggleButton.isSelected()));
464 setToolTipText("Light / Dark - 明かりを点灯/消灯");
467 add( Box.createHorizontalStrut(5) );
468 add( anoGakkiToggleButton = new JToggleButton(new ButtonIcon(ButtonIcon.ANO_GAKKI_ICON)) {{
470 setMargin(ZERO_INSETS);
472 setToolTipText("あの楽器");
474 keyboardPanel.keyboardCenterPanel.keyboard.anoGakkiPane
475 = anoGakkiToggleButton.isSelected() ? anoGakkiPane : null
478 add( Box.createHorizontalStrut(5) );
479 add( inversionOmissionButton = new InversionAndOmissionLabel() );
480 add( Box.createHorizontalStrut(5) );
481 add( chordMatrix.capoSelecter );
482 add( Box.createHorizontalStrut(2) );
484 keyboardSequencerPanel = new JPanel() {{
485 setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
487 add(Box.createVerticalStrut(5));
488 add(keyboardSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, keyboardPanel, chordDiagram) {{
489 setOneTouchExpandable(true);
490 setResizeWeight(1.0);
491 setAlignmentX((float)0.5);
493 add(Box.createVerticalStrut(5));
495 setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
497 setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
498 add(Box.createHorizontalStrut(12));
500 add(Box.createHorizontalStrut(12));
501 add(timesigSelecter);
502 add(Box.createHorizontalStrut(12));
504 add(Box.createHorizontalStrut(12));
505 add(new SequencerMeasureView(sequencerModel));
506 add(Box.createHorizontalStrut(12));
508 add(Box.createHorizontalStrut(12));
509 add(new JButton(midiEditor.openAction) {{ setMargin(ZERO_INSETS); }});
512 setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
513 add(Box.createHorizontalStrut(10));
514 add(new JSlider(sequencerModel));
515 add(new SequencerTimeView(sequencerModel));
516 add(Box.createHorizontalStrut(5));
517 add(new JButton(playlistModel.getMoveToTopAction()) {{ setMargin(ZERO_INSETS); }});
518 add(new JButton(sequencerModel.getMoveBackwardAction()) {{ setMargin(ZERO_INSETS); }});
519 add(new JToggleButton(sequencerModel.getStartStopAction()));
520 add(new JButton(sequencerModel.getMoveForwardAction()) {{ setMargin(ZERO_INSETS); }});
521 add(new JButton(playlistModel.getMoveToBottomAction()) {{ setMargin(ZERO_INSETS); }});
522 add(new JToggleButton(playlistModel.getToggleRepeatAction()) {{ setMargin(ZERO_INSETS); }});
523 add( Box.createHorizontalStrut(10) );
526 add(new JButton(midiDeviceDialog.getOpenAction()));
527 add(new JButton(about.getOpenAction()));
531 setContentPane(new JLayeredPane() {
533 add(anoGakkiPane = new AnoGakkiPane(), JLayeredPane.PALETTE_LAYER);
534 addComponentListener(new ComponentAdapter() {
536 public void componentResized(ComponentEvent e) { adjustSize(); }
538 public void componentShown(ComponentEvent e) { adjustSize(); }
539 private void adjustSize() { anoGakkiPane.setBounds(getBounds()); }
541 setLayout(new BorderLayout());
543 add(mainSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, chordMatrix, keyboardSequencerPanel){
545 setResizeWeight(0.5);
546 setAlignmentX((float)0.5);
552 setPreferredSize(new Dimension(750,470));
555 public void destroy() { deviceTreeModel.forEach(m -> m.close()); }
557 public void start() {
559 // コードボタンで設定されている現在の調をピアノキーボードに伝える
560 chordMatrix.fireKeySignatureChanged();
562 // アプレットのパラメータにMIDIファイルのURLが指定されていたらそれを再生する
563 String midiUrl = getParameter("midi_file");
564 if( midiUrl != null ) try {
565 play(addToPlaylist(midiUrl));
566 } catch (Exception e) {
567 JOptionPane.showMessageDialog(null, e, VersionInfo.NAME, JOptionPane.WARNING_MESSAGE);
571 public void stop() { sequencerModel.stop(); }
573 private void innerSetDarkMode(boolean isDark) {
574 Color col = isDark ? Color.black : null;
575 getContentPane().setBackground(isDark ? Color.black : rootPaneDefaultBgcolor);
576 mainSplitPane.setBackground(col);
577 keyboardSplitPane.setBackground(col);
578 enterButtonLabel.setDarkMode(isDark);
579 chordGuide.setBackground(col);
580 lyricDisplay.setBorder(isDark ? null : lyricDisplayDefaultBorder);
581 lyricDisplay.setBackground(isDark ?
582 chordMatrix.darkModeColorset.backgrounds[2] :
583 lyricDisplayDefaultBgcolor
585 lyricDisplay.setForeground(isDark ? Color.white : null);
586 inversionOmissionButton.setBackground(col);
587 anoGakkiToggleButton.setBackground(col);
588 keyboardSequencerPanel.setBackground(col);
589 chordDiagram.setBackground(col);
590 chordDiagram.titleLabel.setDarkMode(isDark);
591 chordMatrix.setDarkMode(isDark);
592 keyboardPanel.setDarkMode(isDark);
595 private void setKeySignature(Key key) {
596 keysigLabel.setKey(key);
597 chordMatrix.setKeySignature(key);
600 private int[] chordOnNotes = null;
603 * <p>この関数を直接呼ぶとアルペジオが効かないので、
604 * chordMatrix.setSelectedChord() を使うことを推奨。
607 public void chordOn() {
608 Chord playChord = chordMatrix.getSelectedChord();
610 chordOnNotes != null &&
611 chordMatrix.getNoteIndex() < 0 &&
612 (! chordMatrix.isDragged() || playChord == null)
614 // コードが鳴っている状態で、新たなコードを鳴らそうとしたり、
615 // もう鳴らさないという信号が来た場合は、今鳴っている音を止める。
616 Arrays.stream(chordOnNotes).forEach(n->keyboardPanel.keyboardCenterPanel.keyboard.noteOff(n));
619 if( playChord == null ) {
620 // もう鳴らさないので、歌詞表示に通知して終了
621 if( lyricDisplay != null ) lyricDisplay.appendChord(null);
625 if( keyboardPanel.keyboardCenterPanel.keyboard.anoGakkiPane != null ) {
626 JComponent btn = chordMatrix.getSelectedButton();
627 if( btn != null ) anoGakkiPane.start(chordMatrix, btn.getBounds());
629 // コードボタンからのコードを、カポつき演奏キーからオリジナルキーへ変換
630 Key originalKey = chordMatrix.getKeySignatureCapo();
631 Chord originalChord = playChord.transposedNewChord(
632 chordMatrix.capoSelecter.getCapo(),
633 chordMatrix.getKeySignature()
635 // 変換後のコードをキーボード画面に設定
636 keyboardPanel.keyboardCenterPanel.keyboard.setChord(originalChord);
638 // 音域を決める。これにより鳴らす音が確定する。
639 Range chordRange = new Range(
640 keyboardPanel.keyboardCenterPanel.keyboard.getChromaticOffset() + 10 +
641 ( keyboardPanel.keyboardCenterPanel.keyboard.getOctaves() / 4 ) * 12,
642 inversionOmissionButton.isAutoInversionMode() ?
643 keyboardPanel.keyboardCenterPanel.keyboard.getChromaticOffset() + 21 :
644 keyboardPanel.keyboardCenterPanel.keyboard.getChromaticOffset() + 33,
646 inversionOmissionButton.isAutoInversionMode()
648 int[] notes = originalChord.toNoteArray(chordRange, originalKey);
650 // 前回鳴らしたコード構成音を覚えておく
651 int[] prevChordOnNotes = null;
652 if( chordMatrix.isDragged() || chordMatrix.getNoteIndex() >= 0 )
653 prevChordOnNotes = Arrays.copyOf(chordOnNotes, chordOnNotes.length);
656 chordOnNotes = new int[notes.length];
658 for( int n : notes ) {
659 if( inversionOmissionButton.getOmissionNoteIndex() == i ) {
662 chordOnNotes[i++] = n;
665 boolean isNoteOn = prevChordOnNotes != null && Arrays.stream(prevChordOnNotes).anyMatch(prevN -> prevN == n);
666 // すでに鳴っているのに単音を鳴らそうとする場合、
668 if( isNoteOn && chordMatrix.getNoteIndex() >= 0 &&
669 notes[chordMatrix.getNoteIndex()] - n == 0
671 keyboardPanel.keyboardCenterPanel.keyboard.noteOff(n);
675 if( ! isNoteOn ) keyboardPanel.keyboardCenterPanel.keyboard.noteOn(n);
679 keyboardPanel.keyboardCenterPanel.keyboard.setChord(originalChord);
680 chordMatrix.chordDisplay.setChord(playChord);
682 // コードダイアグラム用にもコードを表示
684 int chordDiagramCapo = chordDiagram.capoSelecterView.getCapo();
685 if( chordDiagramCapo == chordMatrix.capoSelecter.getCapo() )
686 diagramChord = playChord;
688 diagramChord = originalChord.transposedNewChord(-chordDiagramCapo, originalKey);
689 chordDiagram.setChord(diagramChord);
690 if( chordDiagram.recordTextButton.isSelected() )
691 lyricDisplay.appendChord(diagramChord);