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