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.Sequence;
30 import javax.sound.midi.Sequencer;
31 import javax.swing.AbstractAction;
32 import javax.swing.Action;
33 import javax.swing.Box;
34 import javax.swing.BoxLayout;
35 import javax.swing.ImageIcon;
36 import javax.swing.JApplet;
37 import javax.swing.JButton;
38 import javax.swing.JComponent;
39 import javax.swing.JEditorPane;
40 import javax.swing.JLabel;
41 import javax.swing.JLayeredPane;
42 import javax.swing.JOptionPane;
43 import javax.swing.JPanel;
44 import javax.swing.JSlider;
45 import javax.swing.JSplitPane;
46 import javax.swing.JToggleButton;
47 import javax.swing.SwingUtilities;
48 import javax.swing.border.Border;
49 import javax.swing.event.ChangeEvent;
50 import javax.swing.event.ChangeListener;
51 import javax.swing.event.HyperlinkEvent;
52 import javax.swing.event.HyperlinkListener;
54 import camidion.chordhelper.anogakki.AnoGakkiPane;
55 import camidion.chordhelper.chorddiagram.CapoComboBoxModel;
56 import camidion.chordhelper.chorddiagram.ChordDiagram;
57 import camidion.chordhelper.chordmatrix.ChordButtonLabel;
58 import camidion.chordhelper.chordmatrix.ChordMatrix;
59 import camidion.chordhelper.chordmatrix.ChordMatrixListener;
60 import camidion.chordhelper.mididevice.MidiDeviceDialog;
61 import camidion.chordhelper.mididevice.MidiDeviceModelList;
62 import camidion.chordhelper.mididevice.MidiSequencerModel;
63 import camidion.chordhelper.mididevice.SequencerMeasureView;
64 import camidion.chordhelper.mididevice.SequencerTimeView;
65 import camidion.chordhelper.mididevice.VirtualMidiDevice;
66 import camidion.chordhelper.midieditor.Base64Dialog;
67 import camidion.chordhelper.midieditor.KeySignatureLabel;
68 import camidion.chordhelper.midieditor.MidiSequenceEditor;
69 import camidion.chordhelper.midieditor.NewSequenceDialog;
70 import camidion.chordhelper.midieditor.PlaylistTableModel;
71 import camidion.chordhelper.midieditor.SequenceTickIndex;
72 import camidion.chordhelper.midieditor.SequenceTrackListTableModel;
73 import camidion.chordhelper.midieditor.TempoSelecter;
74 import camidion.chordhelper.midieditor.TimeSignatureSelecter;
75 import camidion.chordhelper.music.Chord;
76 import camidion.chordhelper.music.Key;
77 import camidion.chordhelper.music.Range;
78 import camidion.chordhelper.pianokeyboard.MidiKeyboardPanel;
79 import camidion.chordhelper.pianokeyboard.PianoKeyboardAdapter;
82 * MIDI Chord Helper - Circle-of-fifth oriented chord pad
86 * Copyright (C) 2004-2016 @きよし - Akiyoshi Kamide
87 * http://www.yk.rim.or.jp/~kamide/music/chordhelper/
89 public class ChordHelperApplet extends JApplet {
90 /////////////////////////////////////////////////////////////////////
92 // JavaScript などからの呼び出しインターフェース
94 /////////////////////////////////////////////////////////////////////
96 * 未保存の修正済み MIDI ファイルがあるかどうか調べます。
97 * @return 未保存の修正済み MIDI ファイルがあれば true
99 public boolean isModified() { return playlistModel.isModified(); }
101 * 指定された小節数の曲を、乱数で自動作曲してプレイリストへ追加します。
102 * @param measureLength 小節数
103 * @return 追加先のインデックス値(0から始まる)。追加できなかったときは -1
104 * @throws InvalidMidiDataException {@link Sequencer#setSequence(Sequence)} を参照
106 public int addRandomSongToPlaylist(int measureLength) throws InvalidMidiDataException {
107 NewSequenceDialog d = midiEditor.newSequenceDialog;
108 d.setRandomChordProgression(measureLength);
109 return playlistModel.addSequenceAndPlay(d.getMidiSequence());
112 * URLで指定されたMIDIファイルをプレイリストへ追加します。
114 * <p>URL の最後の / より後ろの部分がファイル名として取り込まれます。
115 * 指定できる MIDI ファイルには、param タグの midi_file パラメータと同様の制限があります。
117 * @param midiFileUrl 追加するMIDIファイルのURL
118 * @return 追加先のインデックス値(0から始まる)。追加できなかったときは -1
120 public int addToPlaylist(String midiFileUrl) {
122 return playlistModel.addSequenceFromURL(midiFileUrl);
123 } catch( URISyntaxException|IOException|InvalidMidiDataException e ) {
124 midiEditor.showWarning(e.getMessage());
125 } catch( AccessControlException e ) {
127 midiEditor.showError(e.getMessage());
132 * Base64 エンコードされた MIDI ファイルをプレイリストへ追加します。
134 * @param base64EncodedText Base64エンコードされたMIDIファイル
135 * @return 追加先のインデックス値(0から始まる)。追加できなかったときは -1
137 public int addToPlaylistBase64(String base64EncodedText) {
138 return addToPlaylistBase64(base64EncodedText, null);
142 * Base64エンコードされたMIDIファイルをプレイリストへ追加します。
144 * @param base64EncodedText Base64エンコードされたMIDIファイル
145 * @param filename ディレクトリ名を除いたファイル名
146 * @return 追加先のインデックス値(0から始まる)。追加できなかったときは -1
148 public int addToPlaylistBase64(String base64EncodedText, String filename) {
149 Base64Dialog d = midiEditor.base64Dialog;
150 d.setBase64Data(base64EncodedText);
152 return playlistModel.addSequence(d.getMIDIData(), filename);
153 } catch (IOException | InvalidMidiDataException e) {
155 midiEditor.showWarning(e.getMessage());
160 * プレイリスト上で現在選択されているMIDIシーケンスを、
162 * @throws InvalidMidiDataException {@link Sequencer#setSequence(Sequence)} を参照
164 public void play() throws InvalidMidiDataException { play(playlistModel.sequenceListSelectionModel.getMinSelectionIndex()); }
166 * 指定されたインデックス値が示すプレイリスト上のMIDIシーケンスを、
168 * @param index インデックス値(0から始まる)
169 * @throws InvalidMidiDataException {@link Sequencer#setSequence(Sequence)} を参照
171 public void play(int index) throws InvalidMidiDataException {
172 playlistModel.loadToSequencer(index); sequencerModel.start();
175 * シーケンサが実行中かどうかを返します。
176 * {@link Sequencer#isRunning()} の戻り値をそのまま返します。
180 public boolean isRunning() { return sequencerModel.getSequencer().isRunning(); }
182 * シーケンサが再生中かどうかを返します。
185 public boolean isPlaying() { return isRunning(); }
187 * 現在シーケンサにロードされているMIDIデータを
188 * Base64テキストに変換した結果を返します。
189 * @return MIDIデータをBase64テキストに変換した結果
191 public String getMidiDataBase64() {
192 SequenceTrackListTableModel sequenceModel = sequencerModel.getSequenceTrackListTableModel();
193 midiEditor.base64Dialog.setMIDIData(sequenceModel.getMIDIdata());
194 return midiEditor.base64Dialog.getBase64Data();
197 * 現在シーケンサにロードされているMIDIファイルのファイル名を返します。
198 * @return MIDIファイル名(設定されていないときは空文字列)
200 public String getMidiFilename() {
201 SequenceTrackListTableModel seq_model = sequencerModel.getSequenceTrackListTableModel();
202 if( seq_model == null ) return null;
203 String fn = seq_model.getFilename();
204 return fn == null ? "" : fn ;
208 * @param octavePosition オクターブ位置(デフォルト:4)
210 public void setOctavePosition(int octavePosition) {
211 keyboardPanel.keyboardCenterPanel.keyboard.octaveRangeModel.setValue(octavePosition);
214 * 操作対象のMIDIチャンネルを変更します。
215 * @param ch チャンネル番号 - 1(チャンネル1のとき0、デフォルトは0)
217 public void setChannel(int ch) {
218 keyboardPanel.keyboardCenterPanel.keyboard.midiChComboboxModel.setSelectedChannel(ch);
221 * 操作対象のMIDIチャンネルを返します。
222 * @return 操作対象のMIDIチャンネル
224 public int getChannel() {
225 return keyboardPanel.keyboardCenterPanel.keyboard.midiChComboboxModel.getSelectedChannel();
228 * 操作対象のMIDIチャンネルに対してプログラム(音色)を設定します。
229 * @param program 音色(0~127:General MIDI に基づく)
231 public void programChange(int program) {
232 keyboardPanel.keyboardCenterPanel.keyboard.getSelectedChannel().programChange(program);
235 * 操作対象のMIDIチャンネルに対してプログラム(音色)を設定します。
236 * 内部的には {@link #programChange(int)} を呼び出しているだけです。
237 * @param program 音色(0~127:General MIDI に基づく)
239 public void setProgram(int program) { programChange(program); }
241 * 自動転回モードを変更します。初期値は true です。
242 * @param isAuto true:自動転回を行う false:自動転回を行わない
244 public void setAutoInversion(boolean isAuto) {
245 inversionOmissionButton.setAutoInversion(isAuto);
251 * <li>-1:省略しない(デフォルト)</li>
257 public void setOmissionNoteIndex(int index) {
258 inversionOmissionButton.setOmissionNoteIndex(index);
261 * コードダイアグラムの表示・非表示を切り替えます。
262 * @param isVisible 表示するときtrue
264 public void setChordDiagramVisible(boolean isVisible) {
265 keyboardSplitPane.resetToPreferredSizes();
267 keyboardSplitPane.setDividerLocation((double)1.0);
270 * コードダイヤグラムをギターモードに変更します。
271 * 初期状態ではウクレレモードになっています。
273 public void setChordDiagramForGuitar() {
274 chordDiagram.setTargetInstrument(ChordDiagram.Instrument.Guitar);
277 * ダークモード(暗い表示)と明るい表示とを切り替えます。
278 * @param isDark ダークモードのときtrue、明るい表示のときfalse(デフォルト)
280 public void setDarkMode(boolean isDark) {
281 darkModeToggleButton.setSelected(isDark);
286 public static class VersionInfo {
287 public static final String NAME = "MIDI Chord Helper";
288 public static final String VERSION = "Ver.20160623.1";
289 public static final String COPYRIGHT = "Copyright (C) 2004-2016";
290 public static final String AUTHER = "@きよし - Akiyoshi Kamide";
291 public static final String URL = "http://www.yk.rim.or.jp/~kamide/music/chordhelper/";
296 public static String getInfo() {
297 return NAME + " " + VERSION + " " + COPYRIGHT + " " + AUTHER + " " + URL;
301 public String getAppletInfo() { return VersionInfo.getInfo(); }
302 private class AboutMessagePane extends JEditorPane {
304 public AboutMessagePane() { this(true); }
305 public AboutMessagePane(boolean link_enabled) {
306 super( "text/html", "" );
307 String link_string, tooltip = null;
308 if( link_enabled && Desktop.isDesktopSupported() ) {
309 tooltip = "Click this URL to open with your web browser - URLをクリックしてWebブラウザで開く";
311 "<a href=\"" + VersionInfo.URL + "\" title=\"" +
312 tooltip + "\">" + VersionInfo.URL + "</a>" ;
315 link_enabled = false; link_string = VersionInfo.URL;
318 "<html><center><font size=\"+1\">" + VersionInfo.NAME + "</font> " +
319 VersionInfo.VERSION + "<br/><br/>" +
320 VersionInfo.COPYRIGHT + " " + VersionInfo.AUTHER + "<br/>" +
321 link_string + "</center></html>"
323 setToolTipText(tooltip);
325 putClientProperty(JEditorPane.HONOR_DISPLAY_PROPERTIES, Boolean.TRUE);
328 // メッセージ内の <a href=""> ~ </a> によるリンクを
329 // 実際に機能させる(ブラウザで表示されるようにする)ための設定
331 if( ! link_enabled ) return;
333 uri = new URI(VersionInfo.URL);
334 }catch( URISyntaxException use ) {
335 use.printStackTrace();
338 addHyperlinkListener(new HyperlinkListener() {
339 public void hyperlinkUpdate(HyperlinkEvent e) {
340 if(e.getEventType()==HyperlinkEvent.EventType.ACTIVATED) {
342 Desktop.getDesktop().browse(uri);
343 }catch(IOException ioe) {
344 ioe.printStackTrace();
353 public Action openAction = new AbstractAction() {
355 putValue(NAME, "Version info");
356 putValue(SHORT_DESCRIPTION, VersionInfo.NAME + " " + VersionInfo.VERSION);
359 public void actionPerformed(ActionEvent e) {
360 JOptionPane.showMessageDialog(
361 null, AboutMessagePane.this, getValue(NAME).toString(),
362 JOptionPane.INFORMATION_MESSAGE, imageIcon
370 public ImageIcon imageIcon;
374 public Image iconImage;
376 * ボタンの余白を詰めたいときに setMargin() の引数に指定するインセット
378 public static final Insets ZERO_INSETS = new Insets(0,0,0,0);
380 private static final String IMAGE_ICON_PATH = "midichordhelper.png";
382 MidiSequenceEditor midiEditor;
383 PlaylistTableModel playlistModel;
384 MidiSequencerModel sequencerModel;
385 public ChordMatrix chordMatrix;
386 private JPanel keyboardSequencerPanel;
387 private JPanel chordGuide;
388 private Color rootPaneDefaultBgcolor;
389 private Color lyricDisplayDefaultBgcolor;
390 private Border lyricDisplayDefaultBorder;
391 private JSplitPane mainSplitPane;
392 private JSplitPane keyboardSplitPane;
393 private ChordButtonLabel enterButtonLabel;
394 private ChordTextField lyricDisplay;
395 private MidiKeyboardPanel keyboardPanel;
396 private InversionAndOmissionLabel inversionOmissionButton;
397 private JToggleButton darkModeToggleButton;
398 private MidiDeviceDialog midiDeviceDialog;
399 private ChordDiagram chordDiagram;
400 private TempoSelecter tempoSelecter;
401 private TimeSignatureSelecter timesigSelecter;
402 private KeySignatureLabel keysigLabel;
403 private JLabel songTitleLabel = new JLabel();
404 private AnoGakkiPane anoGakkiPane;
405 private JToggleButton anoGakkiToggleButton;
406 private MidiDeviceModelList deviceModelList;
411 URL imageIconUrl = getClass().getResource(IMAGE_ICON_PATH);
412 if( imageIconUrl == null ) {
413 System.out.println("Icon image "+IMAGE_ICON_PATH+" not found");
416 iconImage = (imageIcon = new ImageIcon(imageIconUrl)).getImage();
419 rootPaneDefaultBgcolor = getContentPane().getBackground();
421 // コードダイアグラム、コードボタン、ピアノ鍵盤のセットアップ
422 CapoComboBoxModel capoValueModel = new CapoComboBoxModel();
423 chordDiagram = new ChordDiagram(capoValueModel);
424 chordMatrix = new ChordMatrix(capoValueModel) {{
425 addChordMatrixListener(new ChordMatrixListener(){
426 public void keySignatureChanged() {
427 Key capoKey = getKeySignatureCapo();
428 keyboardPanel.keySelecter.setKey(capoKey);
429 keyboardPanel.keyboardCenterPanel.keyboard.setKeySignature(capoKey);
431 public void chordChanged() { chordOn(); }
433 capoSelecter.checkbox.addItemListener(new ItemListener() {
434 public void itemStateChanged(ItemEvent e) {
436 keyboardPanel.keyboardCenterPanel.keyboard.chordDisplay.clear();
437 chordDiagram.clear();
440 capoSelecter.valueSelecter.addActionListener(new ActionListener() {
441 public void actionPerformed(ActionEvent e) {
443 keyboardPanel.keyboardCenterPanel.keyboard.chordDisplay.clear();
444 chordDiagram.clear();
448 keysigLabel = new KeySignatureLabel() {{
449 addMouseListener(new MouseAdapter() {
450 public void mousePressed(MouseEvent e) { chordMatrix.setKeySignature(getKey()); }
453 keyboardPanel = new MidiKeyboardPanel(chordMatrix) {{
454 keyboardCenterPanel.keyboard.addPianoKeyboardListener(new PianoKeyboardAdapter() {
456 public void pianoKeyPressed(int n, InputEvent e) { chordDiagram.clear(); }
458 keySelecter.keysigCombobox.addActionListener(new ActionListener() {
460 public void actionPerformed(ActionEvent e) {
461 Key key = keySelecter.getKey();
462 key.transpose( - chordMatrix.capoSelecter.getCapo() );
463 chordMatrix.setKeySignature(key);
466 keyboardCenterPanel.keyboard.setPreferredSize(new Dimension(571, 80));
468 VirtualMidiDevice guiMidiDevice = keyboardPanel.keyboardCenterPanel.keyboard.midiDevice;
471 deviceModelList = new MidiDeviceModelList(Arrays.asList(guiMidiDevice));
472 (midiDeviceDialog = new MidiDeviceDialog(deviceModelList)).setIconImage(iconImage);
474 // MIDIデバイス一覧のシーケンサと連携するプレイリストを構築
475 playlistModel = new PlaylistTableModel(sequencerModel = deviceModelList.getSequencerModel());
478 (midiEditor = new MidiSequenceEditor(playlistModel, guiMidiDevice)).setIconImage(iconImage);
480 // メイン画面へのMIDIファイルのドラッグ&ドロップ受付開始
481 new DropTarget(this, DnDConstants.ACTION_COPY_OR_MOVE, midiEditor.dropTargetListener, true);
483 // MIDIエディタのイベントダイアログを、ピアノ鍵盤のイベント送出ダイアログと共用
484 keyboardPanel.setEventDialog(midiEditor.eventDialog);
487 lyricDisplay = new ChordTextField(sequencerModel) {{
488 addActionListener(new ActionListener() {
490 public void actionPerformed(ActionEvent event) {
491 String symbol = event.getActionCommand().trim().split("[ \t\r\n]")[0];
492 chordMatrix.setSelectedChord(symbol);
496 lyricDisplayDefaultBorder = lyricDisplay.getBorder();
497 lyricDisplayDefaultBgcolor = lyricDisplay.getBackground();
499 // メタイベント(テンポ・拍子・調号)を受信して表示するリスナーを登録
500 Sequencer sequencer = sequencerModel.getSequencer();
501 sequencer.addMetaEventListener(tempoSelecter = new TempoSelecter() {{ setEditable(false); }});
502 sequencer.addMetaEventListener(timesigSelecter = new TimeSignatureSelecter() {{ setEditable(false); }});
503 sequencer.addMetaEventListener(new MetaEventListener() {
506 public void meta(MetaMessage msg) {
507 switch(msg.getType()) {
508 case 0x59: // Key signature (2 bytes) : 調号
509 key = new Key(msg.getData());
510 if( SwingUtilities.isEventDispatchThread() ) {
511 keysigLabel.setKeySignature(key);
512 chordMatrix.setKeySignature(key);
514 // MIDIシーケンサのスレッドから呼ばれた場合、GUI更新は自分で行わず、
515 // AWTイベントディスパッチスレッドに依頼する。
516 SwingUtilities.invokeLater(new Runnable() {
519 keysigLabel.setKeySignature(key);
520 chordMatrix.setKeySignature(key);
528 //シーケンサーの時間スライダーの値が変わったときのリスナーを登録
529 sequencerModel.addChangeListener(new ChangeListener() {
531 public void stateChanged(ChangeEvent e) {
532 SequenceTrackListTableModel sequenceTableModel = sequencerModel.getSequenceTrackListTableModel();
533 int loadedSequenceIndex = playlistModel.indexOfSequenceOnSequencer();
534 songTitleLabel.setText(
536 loadedSequenceIndex < 0 ? "[No MIDI file loaded]" :
537 "MIDI file " + loadedSequenceIndex + ": " + (
538 sequenceTableModel == null ||
539 sequenceTableModel.toString() == null ||
540 sequenceTableModel.toString().isEmpty() ?
542 "<font color=maroon>"+sequenceTableModel+"</font>"
546 Sequencer sequencer = sequencerModel.getSequencer();
547 chordMatrix.setPlaying(sequencer.isRunning());
548 if( sequenceTableModel != null ) {
549 SequenceTickIndex tickIndex = sequenceTableModel.getSequenceTickIndex();
550 long tickPos = sequencer.getTickPosition();
551 tickIndex.tickToMeasure(tickPos);
552 chordMatrix.setBeat(tickIndex);
553 if( sequencerModel.getValueIsAdjusting() || ! (sequencer.isRunning() || sequencer.isRecording()) ) {
555 msg = tickIndex.lastMetaMessageAt(
556 SequenceTickIndex.MetaMessageType.TIME_SIGNATURE, tickPos
558 timesigSelecter.setValue(msg==null ? null : msg.getData());
559 msg = tickIndex.lastMetaMessageAt(
560 SequenceTickIndex.MetaMessageType.TEMPO, tickPos
562 tempoSelecter.setTempo(msg==null ? null : msg.getData());
563 msg = tickIndex.lastMetaMessageAt(
564 SequenceTickIndex.MetaMessageType.KEY_SIGNATURE, tickPos
570 Key key = new Key(msg.getData());
571 keysigLabel.setKeySignature(key);
572 chordMatrix.setKeySignature(key);
578 sequencerModel.fireStateChanged();
579 chordGuide = new JPanel() {
581 setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
582 add( Box.createHorizontalStrut(2) );
583 add( chordMatrix.chordGuide );
584 add( Box.createHorizontalStrut(2) );
586 add( Box.createHorizontalStrut(2) );
587 add( enterButtonLabel = new ChordButtonLabel("Enter",chordMatrix) {{
588 addMouseListener(new MouseAdapter() {
589 public void mousePressed(MouseEvent event) {
590 if( (event.getModifiersEx() & InputEvent.BUTTON3_DOWN_MASK) != 0 ) // RightClicked
591 chordMatrix.setSelectedChord((Chord)null);
593 chordMatrix.setSelectedChord(lyricDisplay.getText());
598 add( Box.createHorizontalStrut(5) );
599 add( chordMatrix.chordDisplay );
600 add( Box.createHorizontalStrut(5) );
601 add( darkModeToggleButton = new JToggleButton(new ButtonIcon(ButtonIcon.DARK_MODE_ICON)) {{
602 setMargin(ZERO_INSETS);
603 addItemListener(new ItemListener() {
604 public void itemStateChanged(ItemEvent e) {
605 innerSetDarkMode(darkModeToggleButton.isSelected());
608 setToolTipText("Light / Dark - 明かりを点灯/消灯");
611 add( Box.createHorizontalStrut(5) );
612 add( anoGakkiToggleButton = new JToggleButton(new ButtonIcon(ButtonIcon.ANO_GAKKI_ICON)) {{
614 setMargin(ZERO_INSETS);
616 setToolTipText("あの楽器");
617 addItemListener(new ItemListener() {
618 public void itemStateChanged(ItemEvent e) {
619 keyboardPanel.keyboardCenterPanel.keyboard.anoGakkiPane
620 = anoGakkiToggleButton.isSelected() ? anoGakkiPane : null ;
624 add( Box.createHorizontalStrut(5) );
625 add( inversionOmissionButton = new InversionAndOmissionLabel() );
626 add( Box.createHorizontalStrut(5) );
627 add( chordMatrix.capoSelecter );
628 add( Box.createHorizontalStrut(2) );
631 keyboardSequencerPanel = new JPanel() {{
632 setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
634 add(Box.createVerticalStrut(5));
635 add(keyboardSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, keyboardPanel, chordDiagram) {{
636 setOneTouchExpandable(true);
637 setResizeWeight(1.0);
638 setAlignmentX((float)0.5);
640 add(Box.createVerticalStrut(5));
642 setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
644 setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
645 add( Box.createHorizontalStrut(12) );
647 add( Box.createHorizontalStrut(12) );
648 add( timesigSelecter );
649 add( Box.createHorizontalStrut(12) );
650 add( tempoSelecter );
651 add( Box.createHorizontalStrut(12) );
652 add( new SequencerMeasureView(sequencerModel) );
653 add( Box.createHorizontalStrut(12) );
654 add( songTitleLabel );
655 add( Box.createHorizontalStrut(12) );
656 add( new JButton(midiEditor.openAction) {{ setMargin(ZERO_INSETS); }});
659 setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
660 add(Box.createHorizontalStrut(10));
661 add(new JSlider(sequencerModel));
662 add(new SequencerTimeView(sequencerModel));
663 add(Box.createHorizontalStrut(5));
664 add(new JButton(playlistModel.moveToTopAction) {{ setMargin(ZERO_INSETS); }});
665 add(new JButton(sequencerModel.moveBackwardAction) {{ setMargin(ZERO_INSETS); }});
666 add(new JToggleButton(sequencerModel.startStopAction));
667 add(new JButton(sequencerModel.moveForwardAction) {{ setMargin(ZERO_INSETS); }});
668 add(new JButton(playlistModel.moveToBottomAction) {{ setMargin(ZERO_INSETS); }});
669 add(new JToggleButton(playlistModel.toggleRepeatAction) {{ setMargin(ZERO_INSETS); }});
670 add( Box.createHorizontalStrut(10) );
673 add(new JButton(midiDeviceDialog.openAction));
674 add(new JButton((new AboutMessagePane()).openAction));
678 setContentPane(new JLayeredPane() {
680 add(anoGakkiPane = new AnoGakkiPane(), JLayeredPane.PALETTE_LAYER);
681 addComponentListener(new ComponentAdapter() {
683 public void componentResized(ComponentEvent e) { adjustSize(); }
685 public void componentShown(ComponentEvent e) { adjustSize(); }
686 private void adjustSize() { anoGakkiPane.setBounds(getBounds()); }
688 setLayout(new BorderLayout());
690 add(mainSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, chordMatrix, keyboardSequencerPanel){
692 setResizeWeight(0.5);
693 setAlignmentX((float)0.5);
699 setPreferredSize(new Dimension(750,470));
702 public void destroy() {
703 deviceModelList.closeAllDevices();
707 public void start() {
709 // コードボタンで設定されている現在の調を
711 chordMatrix.fireKeySignatureChanged();
713 // アプレットのパラメータにMIDIファイルのURLが指定されていたら
715 String midi_url = getParameter("midi_file");
717 if( midi_url != null ) {
718 addToPlaylist(midi_url);
721 } catch (InvalidMidiDataException ex) {
722 ex.printStackTrace();
728 sequencerModel.stop(); // MIDI再生を強制終了
731 private void innerSetDarkMode(boolean isDark) {
732 Color col = isDark ? Color.black : null;
733 getContentPane().setBackground(isDark ? Color.black : rootPaneDefaultBgcolor);
734 mainSplitPane.setBackground(col);
735 keyboardSplitPane.setBackground(col);
736 enterButtonLabel.setDarkMode(isDark);
737 chordGuide.setBackground(col);
738 lyricDisplay.setBorder(isDark ? null : lyricDisplayDefaultBorder);
739 lyricDisplay.setBackground(isDark ?
740 chordMatrix.darkModeColorset.backgrounds[2] :
741 lyricDisplayDefaultBgcolor
743 lyricDisplay.setForeground(isDark ? Color.white : null);
744 inversionOmissionButton.setBackground(col);
745 anoGakkiToggleButton.setBackground(col);
746 keyboardSequencerPanel.setBackground(col);
747 chordDiagram.setBackground(col);
748 chordDiagram.titleLabel.setDarkMode(isDark);
749 chordMatrix.setDarkMode(isDark);
750 keyboardPanel.setDarkMode(isDark);
753 private int[] chordOnNotes = null;
756 * <p>この関数を直接呼ぶとアルペジオが効かないので、
757 * chord_matrix.setSelectedChord() を使うことを推奨
760 public void chordOn() {
761 Chord playChord = chordMatrix.getSelectedChord();
763 chordOnNotes != null &&
764 chordMatrix.getNoteIndex() < 0 &&
765 (! chordMatrix.isDragged() || playChord == null)
767 // コードが鳴っている状態で、新たなコードを鳴らそうとしたり、
768 // もう鳴らさないという信号が来た場合は、今鳴っている音を止める。
770 for( int n : chordOnNotes )
771 keyboardPanel.keyboardCenterPanel.keyboard.noteOff(n);
774 if( playChord == null ) {
775 // もう鳴らさないので、歌詞表示に通知して終了
776 if( lyricDisplay != null )
777 lyricDisplay.appendChord(null);
781 if( keyboardPanel.keyboardCenterPanel.keyboard.anoGakkiPane != null ) {
782 JComponent btn = chordMatrix.getSelectedButton();
783 if( btn != null ) anoGakkiPane.start(chordMatrix, btn.getBounds());
785 // コードボタンからのコードを、カポつき演奏キーからオリジナルキーへ変換
786 Key originalKey = chordMatrix.getKeySignatureCapo();
787 Chord originalChord = playChord.clone().transpose(
788 chordMatrix.capoSelecter.getCapo(),
789 chordMatrix.getKeySignature()
791 // 変換後のコードをキーボード画面に設定
792 keyboardPanel.keyboardCenterPanel.keyboard.setChord(originalChord);
794 // 音域を決める。これにより鳴らす音が確定する。
795 Range chordRange = new Range(
796 keyboardPanel.keyboardCenterPanel.keyboard.getChromaticOffset() + 10 +
797 ( keyboardPanel.keyboardCenterPanel.keyboard.getOctaves() / 4 ) * 12,
798 inversionOmissionButton.isAutoInversionMode() ?
799 keyboardPanel.keyboardCenterPanel.keyboard.getChromaticOffset() + 21 :
800 keyboardPanel.keyboardCenterPanel.keyboard.getChromaticOffset() + 33,
802 inversionOmissionButton.isAutoInversionMode()
804 int[] notes = originalChord.toNoteArray(chordRange, originalKey);
806 // 前回鳴らしたコード構成音を覚えておく
807 int[] prevChordOnNotes = null;
808 if( chordMatrix.isDragged() || chordMatrix.getNoteIndex() >= 0 )
809 prevChordOnNotes = Arrays.copyOf(chordOnNotes, chordOnNotes.length);
812 chordOnNotes = new int[notes.length];
814 for( int n : notes ) {
815 if( inversionOmissionButton.getOmissionNoteIndex() == i ) {
818 chordOnNotes[i++] = n;
821 boolean isNoteOn = false;
822 if( prevChordOnNotes != null ) {
823 for( int prevN : prevChordOnNotes ) {
830 // すでに鳴っているのに単音を鳴らそうとする場合、
832 if( isNoteOn && chordMatrix.getNoteIndex() >= 0 &&
833 notes[chordMatrix.getNoteIndex()] - n == 0
835 keyboardPanel.keyboardCenterPanel.keyboard.noteOff(n);
840 keyboardPanel.keyboardCenterPanel.keyboard.noteOn(n);
844 keyboardPanel.keyboardCenterPanel.keyboard.setChord(originalChord);
845 chordMatrix.chordDisplay.setChord(playChord);
847 // コードダイアグラム用にもコードを表示
849 int chordDiagramCapo = chordDiagram.capoSelecterView.getCapo();
850 if( chordDiagramCapo == chordMatrix.capoSelecter.getCapo() )
851 diagramChord = playChord.clone();
853 diagramChord = originalChord.clone().transpose(
854 - chordDiagramCapo, originalKey
856 chordDiagram.setChord(diagramChord);
857 if( chordDiagram.recordTextButton.isSelected() )
858 lyricDisplay.appendChord(diagramChord);