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;
26 import javax.sound.midi.InvalidMidiDataException;
27 import javax.sound.midi.MetaEventListener;
28 import javax.sound.midi.MetaMessage;
29 import javax.sound.midi.Sequencer;
30 import javax.swing.AbstractAction;
31 import javax.swing.Action;
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.MidiTransceiverListModelList;
60 import camidion.chordhelper.mididevice.SequencerMeasureView;
61 import camidion.chordhelper.mididevice.SequencerTimeView;
62 import camidion.chordhelper.midieditor.Base64Dialog;
63 import camidion.chordhelper.midieditor.KeySignatureLabel;
64 import camidion.chordhelper.midieditor.MidiSequenceEditor;
65 import camidion.chordhelper.midieditor.NewSequenceDialog;
66 import camidion.chordhelper.midieditor.SequenceTickIndex;
67 import camidion.chordhelper.midieditor.SequenceTrackListTableModel;
68 import camidion.chordhelper.midieditor.TempoSelecter;
69 import camidion.chordhelper.midieditor.TimeSignatureSelecter;
70 import camidion.chordhelper.music.Chord;
71 import camidion.chordhelper.music.Key;
72 import camidion.chordhelper.music.Range;
73 import camidion.chordhelper.pianokeyboard.MidiKeyboardPanel;
74 import camidion.chordhelper.pianokeyboard.PianoKeyboardAdapter;
77 * MIDI Chord Helper - Circle-of-fifth oriented chord pad
81 * Copyright (C) 2004-2016 @きよし - Akiyoshi Kamide
82 * http://www.yk.rim.or.jp/~kamide/music/chordhelper/
84 public class ChordHelperApplet extends JApplet {
85 /////////////////////////////////////////////////////////////////////
87 // JavaScript などからの呼び出しインターフェース
89 /////////////////////////////////////////////////////////////////////
91 * 未保存の修正済み MIDI ファイルがあるかどうか調べます。
92 * @return 未保存の修正済み MIDI ファイルがあれば true
94 public boolean isModified() {
95 return deviceModelList.getEditorDialog().sequenceListTable.getModel().isModified();
98 * 指定された小節数の曲を、乱数で自動作曲してプレイリストへ追加します。
99 * @param measureLength 小節数
100 * @return 追加先のインデックス値(0から始まる)。追加できなかったときは -1
102 public int addRandomSongToPlaylist(int measureLength) {
103 MidiSequenceEditor editor = deviceModelList.getEditorDialog();
104 NewSequenceDialog d = editor.newSequenceDialog;
105 d.setRandomChordProgression(measureLength);
106 return editor.sequenceListTable.getModel().addSequenceAndPlay(d.getMidiSequence());
109 * URLで指定されたMIDIファイルをプレイリストへ追加します。
111 * <p>URL の最後の / より後ろの部分がファイル名として取り込まれます。
112 * 指定できる MIDI ファイルには、param タグの midi_file パラメータと同様の制限があります。
114 * @param midiFileUrl 追加するMIDIファイルのURL
115 * @return 追加先のインデックス値(0から始まる)。追加できなかったときは -1
117 public int addToPlaylist(String midiFileUrl) {
118 MidiSequenceEditor editor = deviceModelList.getEditorDialog();
120 return editor.sequenceListTable.getModel().addSequenceFromURL(midiFileUrl);
121 } catch( URISyntaxException|IOException|InvalidMidiDataException e ) {
122 editor.showWarning(e.getMessage());
123 } catch( AccessControlException e ) {
125 editor.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 MidiSequenceEditor editor = deviceModelList.getEditorDialog();
148 Base64Dialog d = editor.base64Dialog;
149 d.setBase64Data(base64EncodedText);
151 return editor.sequenceListTable.getModel().addSequence(d.getMIDIData(), filename);
152 } catch (IOException | InvalidMidiDataException e) {
154 editor.showWarning(e.getMessage());
159 * プレイリスト上で現在選択されているMIDIシーケンスを、
163 play(deviceModelList.getEditorDialog().sequenceListTable.getModel().sequenceListSelectionModel.getMinSelectionIndex());
166 * 指定されたインデックス値が示すプレイリスト上のMIDIシーケンスを、
168 * @param index インデックス値(0から始まる)
170 public void play(int index) {
171 deviceModelList.getEditorDialog().sequenceListTable.getModel().loadToSequencer(index);
172 deviceModelList.getSequencerModel().start();
175 * シーケンサが実行中かどうかを返します。
176 * {@link Sequencer#isRunning()} の戻り値をそのまま返します。
180 public boolean isRunning() {
181 return deviceModelList.getSequencerModel().getSequencer().isRunning();
184 * シーケンサが再生中かどうかを返します。
187 public boolean isPlaying() { return isRunning(); }
189 * 現在シーケンサにロードされているMIDIデータを
190 * Base64テキストに変換した結果を返します。
191 * @return MIDIデータをBase64テキストに変換した結果
193 public String getMidiDataBase64() {
194 MidiSequenceEditor editor = deviceModelList.getEditorDialog();
195 SequenceTrackListTableModel sequenceModel =
196 editor.sequenceListTable.getModel().sequencerModel.getSequenceTrackListTableModel();
197 editor.base64Dialog.setMIDIData(sequenceModel.getMIDIdata());
198 return editor.base64Dialog.getBase64Data();
201 * 現在シーケンサにロードされているMIDIファイルのファイル名を返します。
202 * @return MIDIファイル名(設定されていないときは空文字列)
204 public String getMidiFilename() {
205 SequenceTrackListTableModel seq_model = deviceModelList.getSequencerModel().getSequenceTrackListTableModel();
206 if( seq_model == null ) return null;
207 String fn = seq_model.getFilename();
208 return fn == null ? "" : fn ;
212 * @param octavePosition オクターブ位置(デフォルト:4)
214 public void setOctavePosition(int octavePosition) {
215 keyboardPanel.keyboardCenterPanel.keyboard.octaveRangeModel.setValue(octavePosition);
218 * 操作対象のMIDIチャンネルを変更します。
219 * @param ch チャンネル番号 - 1(チャンネル1のとき0、デフォルトは0)
221 public void setChannel(int ch) {
222 keyboardPanel.keyboardCenterPanel.keyboard.midiChComboboxModel.setSelectedChannel(ch);
225 * 操作対象のMIDIチャンネルを返します。
226 * @return 操作対象のMIDIチャンネル
228 public int getChannel() {
229 return keyboardPanel.keyboardCenterPanel.keyboard.midiChComboboxModel.getSelectedChannel();
232 * 操作対象のMIDIチャンネルに対してプログラム(音色)を設定します。
233 * @param program 音色(0~127:General MIDI に基づく)
235 public void programChange(int program) {
236 keyboardPanel.keyboardCenterPanel.keyboard.getSelectedChannel().programChange(program);
239 * 操作対象のMIDIチャンネルに対してプログラム(音色)を設定します。
240 * 内部的には {@link #programChange(int)} を呼び出しているだけです。
241 * @param program 音色(0~127:General MIDI に基づく)
243 public void setProgram(int program) { programChange(program); }
245 * 自動転回モードを変更します。初期値は true です。
246 * @param isAuto true:自動転回を行う false:自動転回を行わない
248 public void setAutoInversion(boolean isAuto) {
249 inversionOmissionButton.setAutoInversion(isAuto);
255 * <li>-1:省略しない(デフォルト)</li>
261 public void setOmissionNoteIndex(int index) {
262 inversionOmissionButton.setOmissionNoteIndex(index);
265 * コードダイアグラムの表示・非表示を切り替えます。
266 * @param isVisible 表示するときtrue
268 public void setChordDiagramVisible(boolean isVisible) {
269 keyboardSplitPane.resetToPreferredSizes();
271 keyboardSplitPane.setDividerLocation((double)1.0);
274 * コードダイヤグラムをギターモードに変更します。
275 * 初期状態ではウクレレモードになっています。
277 public void setChordDiagramForGuitar() {
278 chordDiagram.setTargetInstrument(ChordDiagram.Instrument.Guitar);
281 * ダークモード(暗い表示)と明るい表示とを切り替えます。
282 * @param isDark ダークモードのときtrue、明るい表示のときfalse(デフォルト)
284 public void setDarkMode(boolean isDark) {
285 darkModeToggleButton.setSelected(isDark);
290 public static class VersionInfo {
291 public static final String NAME = "MIDI Chord Helper";
292 public static final String VERSION = "Ver.20160518.1";
293 public static final String COPYRIGHT = "Copyright (C) 2004-2016";
294 public static final String AUTHER = "@きよし - Akiyoshi Kamide";
295 public static final String URL = "http://www.yk.rim.or.jp/~kamide/music/chordhelper/";
300 public static String getInfo() {
301 return NAME + " " + VERSION + " " + COPYRIGHT + " " + AUTHER + " " + URL;
305 public String getAppletInfo() { return VersionInfo.getInfo(); }
306 private class AboutMessagePane extends JEditorPane {
308 public AboutMessagePane() { this(true); }
309 public AboutMessagePane(boolean link_enabled) {
310 super( "text/html", "" );
311 String link_string, tooltip = null;
312 if( link_enabled && Desktop.isDesktopSupported() ) {
313 tooltip = "Click this URL to open with your web browser - URLをクリックしてWebブラウザで開く";
315 "<a href=\"" + VersionInfo.URL + "\" title=\"" +
316 tooltip + "\">" + VersionInfo.URL + "</a>" ;
319 link_enabled = false; link_string = VersionInfo.URL;
322 "<html><center><font size=\"+1\">" + VersionInfo.NAME + "</font> " +
323 VersionInfo.VERSION + "<br/><br/>" +
324 VersionInfo.COPYRIGHT + " " + VersionInfo.AUTHER + "<br/>" +
325 link_string + "</center></html>"
327 setToolTipText(tooltip);
329 putClientProperty(JEditorPane.HONOR_DISPLAY_PROPERTIES, Boolean.TRUE);
332 // メッセージ内の <a href=""> ~ </a> によるリンクを
333 // 実際に機能させる(ブラウザで表示されるようにする)ための設定
335 if( ! link_enabled ) return;
337 uri = new URI(VersionInfo.URL);
338 }catch( URISyntaxException use ) {
339 use.printStackTrace();
342 addHyperlinkListener(new HyperlinkListener() {
343 public void hyperlinkUpdate(HyperlinkEvent e) {
344 if(e.getEventType()==HyperlinkEvent.EventType.ACTIVATED) {
346 Desktop.getDesktop().browse(uri);
347 }catch(IOException ioe) {
348 ioe.printStackTrace();
357 public Action openAction = new AbstractAction() {
359 putValue(NAME, "Version info");
360 putValue(SHORT_DESCRIPTION, VersionInfo.NAME + " " + VersionInfo.VERSION);
363 public void actionPerformed(ActionEvent e) {
364 JOptionPane.showMessageDialog(
365 null, AboutMessagePane.this, getValue(NAME).toString(),
366 JOptionPane.INFORMATION_MESSAGE, imageIcon
372 public boolean isConfirmedToExit() {
373 return ! isModified() || JOptionPane.showConfirmDialog(
375 "MIDI file not saved, exit anyway ?\n保存されていないMIDIファイルがありますが、終了してよろしいですか?",
377 JOptionPane.YES_NO_OPTION,
378 JOptionPane.WARNING_MESSAGE
379 ) == JOptionPane.YES_OPTION ;
384 public ImageIcon imageIcon;
386 * ボタンの余白を詰めたいときに setMargin() の引数に指定するインセット
388 public static final Insets ZERO_INSETS = new Insets(0,0,0,0);
390 public ChordMatrix chordMatrix;
391 MidiTransceiverListModelList deviceModelList;
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 MidiDeviceDialog midiDeviceDialog;
406 private ChordDiagram chordDiagram;
407 private TempoSelecter tempoSelecter;
408 private TimeSignatureSelecter timesigSelecter;
409 private KeySignatureLabel keysigLabel;
410 private JLabel songTitleLabel;
411 private AnoGakkiPane anoGakkiPane;
412 private JToggleButton anoGakkiToggleButton;
415 String imageIconPath = "midichordhelper.png";
416 URL imageIconUrl = getClass().getResource(imageIconPath);
417 if( imageIconUrl == null ) {
418 System.out.println("Icon image "+imageIconPath+" not found");
422 imageIcon = new ImageIcon(imageIconUrl);
424 Image iconImage = (imageIcon == null) ? null : imageIcon.getImage();
425 rootPaneDefaultBgcolor = getContentPane().getBackground();
426 chordMatrix = new ChordMatrix() {{
427 addChordMatrixListener(new ChordMatrixListener(){
428 public void keySignatureChanged() {
429 Key capoKey = getKeySignatureCapo();
430 keyboardPanel.keySelecter.setKey(capoKey);
431 keyboardPanel.keyboardCenterPanel.keyboard.setKeySignature(capoKey);
433 public void chordChanged() { chordOn(); }
436 chordMatrix.capoSelecter.checkbox.addItemListener(
438 public void itemStateChanged(ItemEvent e) {
440 keyboardPanel.keyboardCenterPanel.keyboard.chordDisplay.clear();
441 chordDiagram.clear();
445 chordMatrix.capoSelecter.valueSelecter.addActionListener(
446 new ActionListener() {
447 public void actionPerformed(ActionEvent e) {
449 keyboardPanel.keyboardCenterPanel.keyboard.chordDisplay.clear();
450 chordDiagram.clear();
454 keyboardPanel = new MidiKeyboardPanel(chordMatrix) {{
455 keyboardCenterPanel.keyboard.addPianoKeyboardListener(
456 new PianoKeyboardAdapter() {
458 public void pianoKeyPressed(int n, InputEvent e) { chordDiagram.clear(); }
461 keySelecter.keysigCombobox.addActionListener(
462 new ActionListener() {
464 public void actionPerformed(ActionEvent e) {
465 Key key = keySelecter.getKey();
466 key.transpose( - chordMatrix.capoSelecter.getCapo() );
467 chordMatrix.setKeySignature(key);
471 keyboardCenterPanel.keyboard.setPreferredSize(new Dimension(571, 80));
473 deviceModelList = new MidiTransceiverListModelList(
474 Arrays.asList(keyboardPanel.keyboardCenterPanel.keyboard.midiDevice)
476 deviceModelList.getEditorDialog().setIconImage(iconImage);
477 new DropTarget(this, DnDConstants.ACTION_COPY_OR_MOVE, deviceModelList.getEditorDialog().dropTargetListener, true);
478 keyboardPanel.setEventDialog(deviceModelList.getEditorDialog().eventDialog);
479 (midiDeviceDialog = new MidiDeviceDialog(deviceModelList)).setIconImage(iconImage);
480 lyricDisplay = new ChordTextField(deviceModelList.getSequencerModel()) {{
481 addActionListener(new ActionListener() {
483 public void actionPerformed(ActionEvent event) {
484 String symbol = event.getActionCommand().trim().split("[ \t\r\n]")[0];
485 chordMatrix.setSelectedChord(symbol);
489 lyricDisplayDefaultBorder = lyricDisplay.getBorder();
490 lyricDisplayDefaultBgcolor = lyricDisplay.getBackground();
491 chordDiagram = new ChordDiagram(this);
492 tempoSelecter = new TempoSelecter() {{
494 deviceModelList.getSequencerModel().getSequencer().addMetaEventListener(this);
496 timesigSelecter = new TimeSignatureSelecter() {{
498 deviceModelList.getSequencerModel().getSequencer().addMetaEventListener(this);
500 keysigLabel = new KeySignatureLabel() {{
501 addMouseListener(new MouseAdapter() {
502 public void mousePressed(MouseEvent e) { chordMatrix.setKeySignature(getKey()); }
505 deviceModelList.getSequencerModel().getSequencer().addMetaEventListener(
506 new MetaEventListener() {
507 class SetKeySignatureRunnable implements Runnable {
509 public SetKeySignatureRunnable(Key key) { this.key = key; }
511 public void run() { setKeySignature(key); }
514 public void meta(MetaMessage msg) {
515 switch(msg.getType()) {
516 case 0x59: // Key signature (2 bytes) : 調号
517 Key key = new Key(msg.getData());
518 if( ! SwingUtilities.isEventDispatchThread() ) {
519 SwingUtilities.invokeLater(new SetKeySignatureRunnable(key));
521 setKeySignature(key);
525 private void setKeySignature(Key key) {
526 keysigLabel.setKeySignature(key);
527 chordMatrix.setKeySignature(key);
531 songTitleLabel = new JLabel();
532 //シーケンサーの時間スライダーの値が変わったときのリスナーを登録
533 deviceModelList.getSequencerModel().addChangeListener(new ChangeListener() {
535 public void stateChanged(ChangeEvent e) {
536 SequenceTrackListTableModel sequenceTableModel = deviceModelList.getSequencerModel().getSequenceTrackListTableModel();
537 int loadedSequenceIndex = deviceModelList.getEditorDialog().sequenceListTable.getModel().indexOfSequenceOnSequencer();
538 songTitleLabel.setText(
540 loadedSequenceIndex < 0 ? "[No MIDI file loaded]" :
541 "MIDI file " + loadedSequenceIndex + ": " + (
542 sequenceTableModel == null ||
543 sequenceTableModel.toString() == null ||
544 sequenceTableModel.toString().isEmpty() ?
546 "<font color=maroon>"+sequenceTableModel+"</font>"
550 Sequencer sequencer = deviceModelList.getSequencerModel().getSequencer();
551 chordMatrix.setPlaying(sequencer.isRunning());
552 if( sequenceTableModel != null ) {
553 SequenceTickIndex tickIndex = sequenceTableModel.getSequenceTickIndex();
554 long tickPos = sequencer.getTickPosition();
555 tickIndex.tickToMeasure(tickPos);
556 chordMatrix.setBeat(tickIndex);
558 deviceModelList.getSequencerModel().getValueIsAdjusting() ||
559 ! (sequencer.isRunning() || sequencer.isRecording())
562 msg = tickIndex.lastMetaMessageAt(
563 SequenceTickIndex.MetaMessageType.TIME_SIGNATURE, tickPos
565 timesigSelecter.setValue(msg==null ? null : msg.getData());
566 msg = tickIndex.lastMetaMessageAt(
567 SequenceTickIndex.MetaMessageType.TEMPO, tickPos
569 tempoSelecter.setTempo(msg==null ? null : msg.getData());
570 msg = tickIndex.lastMetaMessageAt(
571 SequenceTickIndex.MetaMessageType.KEY_SIGNATURE, tickPos
577 Key key = new Key(msg.getData());
578 keysigLabel.setKeySignature(key);
579 chordMatrix.setKeySignature(key);
585 deviceModelList.getSequencerModel().fireStateChanged();
586 chordGuide = new JPanel() {
588 setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
589 add( Box.createHorizontalStrut(2) );
590 add( chordMatrix.chordGuide );
591 add( Box.createHorizontalStrut(2) );
593 add( Box.createHorizontalStrut(2) );
594 add( enterButtonLabel = new ChordButtonLabel("Enter",chordMatrix) {{
595 addMouseListener(new MouseAdapter() {
596 public void mousePressed(MouseEvent event) {
597 if( (event.getModifiersEx() & InputEvent.BUTTON3_DOWN_MASK) != 0 ) // RightClicked
598 chordMatrix.setSelectedChord((Chord)null);
600 chordMatrix.setSelectedChord(lyricDisplay.getText());
605 add( Box.createHorizontalStrut(5) );
606 add( chordMatrix.chordDisplay );
607 add( Box.createHorizontalStrut(5) );
608 add( darkModeToggleButton = new JToggleButton(new ButtonIcon(ButtonIcon.DARK_MODE_ICON)) {{
609 setMargin(ZERO_INSETS);
610 addItemListener(new ItemListener() {
611 public void itemStateChanged(ItemEvent e) {
612 innerSetDarkMode(darkModeToggleButton.isSelected());
615 setToolTipText("Light / Dark - 明かりを点灯/消灯");
618 add( Box.createHorizontalStrut(5) );
619 add( anoGakkiToggleButton = new JToggleButton(
620 new ButtonIcon(ButtonIcon.ANO_GAKKI_ICON)
623 setMargin(ZERO_INSETS);
625 setToolTipText("あの楽器");
628 public void itemStateChanged(ItemEvent e) {
629 keyboardPanel.keyboardCenterPanel.keyboard.anoGakkiPane
630 = 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) );
642 keyboardSequencerPanel = new JPanel() {{
643 setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
645 add(Box.createVerticalStrut(5));
646 add(keyboardSplitPane = new JSplitPane(
647 JSplitPane.HORIZONTAL_SPLIT, keyboardPanel, chordDiagram
649 setOneTouchExpandable(true);
650 setResizeWeight(1.0);
651 setAlignmentX((float)0.5);
653 add(Box.createVerticalStrut(5));
655 setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
657 setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
658 add( Box.createHorizontalStrut(12) );
660 add( Box.createHorizontalStrut(12) );
661 add( timesigSelecter );
662 add( Box.createHorizontalStrut(12) );
663 add( tempoSelecter );
664 add( Box.createHorizontalStrut(12) );
665 add( new SequencerMeasureView(deviceModelList.getSequencerModel()) );
666 add( Box.createHorizontalStrut(12) );
667 add( songTitleLabel );
668 add( Box.createHorizontalStrut(12) );
669 add( new JButton(deviceModelList.getEditorDialog().openAction) {{ setMargin(ZERO_INSETS); }});
672 setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
673 add( Box.createHorizontalStrut(10) );
674 add( new JSlider(deviceModelList.getSequencerModel()) );
675 add( new SequencerTimeView(deviceModelList.getSequencerModel()) );
676 add( Box.createHorizontalStrut(5) );
677 add( new JButton(deviceModelList.getEditorDialog().sequenceListTable.getModel().moveToTopAction) {{
678 setMargin(ZERO_INSETS);
680 add(new JButton(deviceModelList.getSequencerModel().moveBackwardAction) {{
681 setMargin(ZERO_INSETS);
683 add(new JToggleButton(deviceModelList.getSequencerModel().startStopAction));
684 add(new JButton(deviceModelList.getSequencerModel().moveForwardAction) {{
685 setMargin(ZERO_INSETS);
687 add(new JButton(deviceModelList.getEditorDialog().sequenceListTable.getModel().moveToBottomAction) {{
688 setMargin(ZERO_INSETS);
690 add(new JToggleButton(deviceModelList.getEditorDialog().sequenceListTable.getModel().toggleRepeatAction) {{
691 setMargin(ZERO_INSETS);
693 add( Box.createHorizontalStrut(10) );
696 add(new JButton(midiDeviceDialog.openAction));
697 add(new JButton((new AboutMessagePane()).openAction));
701 setContentPane(new JLayeredPane() {
703 add(anoGakkiPane = new AnoGakkiPane(), JLayeredPane.PALETTE_LAYER);
704 addComponentListener(new ComponentAdapter() {
706 public void componentResized(ComponentEvent e) { adjustSize(); }
708 public void componentShown(ComponentEvent e) { adjustSize(); }
709 private void adjustSize() { anoGakkiPane.setBounds(getBounds()); }
711 setLayout(new BorderLayout());
713 add(mainSplitPane = new JSplitPane(
714 JSplitPane.VERTICAL_SPLIT, chordMatrix, keyboardSequencerPanel
717 setResizeWeight(0.5);
718 setAlignmentX((float)0.5);
724 setPreferredSize(new Dimension(750,470));
727 public void start() {
729 // コードボタンで設定されている現在の調を
731 chordMatrix.fireKeySignatureChanged();
733 // アプレットのパラメータにMIDIファイルのURLが指定されていたら
735 String midi_url = getParameter("midi_file");
737 if( midi_url != null ) {
738 addToPlaylist(midi_url);
744 deviceModelList.getSequencerModel().stop(); // MIDI再生を強制終了
747 private void innerSetDarkMode(boolean isDark) {
748 Color col = isDark ? Color.black : null;
749 getContentPane().setBackground(
750 isDark ? Color.black : rootPaneDefaultBgcolor
752 mainSplitPane.setBackground(col);
753 keyboardSplitPane.setBackground(col);
754 enterButtonLabel.setDarkMode(isDark);
755 chordGuide.setBackground(col);
756 lyricDisplay.setBorder(isDark ? null : lyricDisplayDefaultBorder);
757 lyricDisplay.setBackground(isDark ?
758 chordMatrix.darkModeColorset.backgrounds[2] :
759 lyricDisplayDefaultBgcolor
761 lyricDisplay.setForeground(isDark ? Color.white : null);
762 inversionOmissionButton.setBackground(col);
763 anoGakkiToggleButton.setBackground(col);
764 keyboardSequencerPanel.setBackground(col);
765 chordDiagram.setBackground(col);
766 chordDiagram.titleLabel.setDarkMode(isDark);
767 chordMatrix.setDarkMode(isDark);
768 keyboardPanel.setDarkMode(isDark);
771 private int[] chordOnNotes = null;
774 * <p>この関数を直接呼ぶとアルペジオが効かないので、
775 * chord_matrix.setSelectedChord() を使うことを推奨
778 public void chordOn() {
779 Chord playChord = chordMatrix.getSelectedChord();
781 chordOnNotes != null &&
782 chordMatrix.getNoteIndex() < 0 &&
783 (! chordMatrix.isDragged() || playChord == null)
785 // コードが鳴っている状態で、新たなコードを鳴らそうとしたり、
786 // もう鳴らさないという信号が来た場合は、今鳴っている音を止める。
788 for( int n : chordOnNotes )
789 keyboardPanel.keyboardCenterPanel.keyboard.noteOff(n);
792 if( playChord == null ) {
793 // もう鳴らさないので、歌詞表示に通知して終了
794 if( lyricDisplay != null )
795 lyricDisplay.appendChord(null);
799 if( keyboardPanel.keyboardCenterPanel.keyboard.anoGakkiPane != null ) {
800 JComponent btn = chordMatrix.getSelectedButton();
801 if( btn != null ) anoGakkiPane.start(chordMatrix, btn.getBounds());
803 // コードボタンからのコードを、カポつき演奏キーからオリジナルキーへ変換
804 Key originalKey = chordMatrix.getKeySignatureCapo();
805 Chord originalChord = playChord.clone().transpose(
806 chordMatrix.capoSelecter.getCapo(),
807 chordMatrix.getKeySignature()
809 // 変換後のコードをキーボード画面に設定
810 keyboardPanel.keyboardCenterPanel.keyboard.setChord(originalChord);
812 // 音域を決める。これにより鳴らす音が確定する。
813 Range chordRange = new Range(
814 keyboardPanel.keyboardCenterPanel.keyboard.getChromaticOffset() + 10 +
815 ( keyboardPanel.keyboardCenterPanel.keyboard.getOctaves() / 4 ) * 12,
816 inversionOmissionButton.isAutoInversionMode() ?
817 keyboardPanel.keyboardCenterPanel.keyboard.getChromaticOffset() + 21 :
818 keyboardPanel.keyboardCenterPanel.keyboard.getChromaticOffset() + 33,
820 inversionOmissionButton.isAutoInversionMode()
822 int[] notes = originalChord.toNoteArray(chordRange, originalKey);
824 // 前回鳴らしたコード構成音を覚えておく
825 int[] prevChordOnNotes = null;
826 if( chordMatrix.isDragged() || chordMatrix.getNoteIndex() >= 0 )
827 prevChordOnNotes = Arrays.copyOf(chordOnNotes, chordOnNotes.length);
830 chordOnNotes = new int[notes.length];
832 for( int n : notes ) {
833 if( inversionOmissionButton.getOmissionNoteIndex() == i ) {
836 chordOnNotes[i++] = n;
839 boolean isNoteOn = false;
840 if( prevChordOnNotes != null ) {
841 for( int prevN : prevChordOnNotes ) {
848 // すでに鳴っているのに単音を鳴らそうとする場合、
850 if( isNoteOn && chordMatrix.getNoteIndex() >= 0 &&
851 notes[chordMatrix.getNoteIndex()] - n == 0
853 keyboardPanel.keyboardCenterPanel.keyboard.noteOff(n);
858 keyboardPanel.keyboardCenterPanel.keyboard.noteOn(n);
862 keyboardPanel.keyboardCenterPanel.keyboard.setChord(originalChord);
863 chordMatrix.chordDisplay.setChord(playChord);
865 // コードダイアグラム用にもコードを表示
867 int chordDiagramCapo = chordDiagram.capoSelecterView.getCapo();
868 if( chordDiagramCapo == chordMatrix.capoSelecter.getCapo() )
869 diagramChord = playChord.clone();
871 diagramChord = originalChord.clone().transpose(
872 - chordDiagramCapo, originalKey
874 chordDiagram.setChord(diagramChord);
875 if( chordDiagram.recordTextButton.isSelected() )
876 lyricDisplay.appendChord(diagramChord);