1 import java.awt.Color;
\r
2 import java.awt.Component;
\r
3 import java.awt.Desktop;
\r
4 import java.awt.Dimension;
\r
5 import java.awt.Graphics;
\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.InputEvent;
\r
13 import java.awt.event.ItemEvent;
\r
14 import java.awt.event.ItemListener;
\r
15 import java.awt.event.MouseAdapter;
\r
16 import java.awt.event.MouseEvent;
\r
17 import java.awt.event.MouseListener;
\r
18 import java.io.IOException;
\r
19 import java.io.UnsupportedEncodingException;
\r
20 import java.net.URI;
\r
21 import java.net.URISyntaxException;
\r
22 import java.net.URL;
\r
23 import java.util.Arrays;
\r
24 import java.util.Vector;
\r
26 import javax.sound.midi.MetaEventListener;
\r
27 import javax.sound.midi.MetaMessage;
\r
28 import javax.sound.midi.Sequencer;
\r
29 import javax.swing.Box;
\r
30 import javax.swing.BoxLayout;
\r
31 import javax.swing.ButtonGroup;
\r
32 import javax.swing.ImageIcon;
\r
33 import javax.swing.JApplet;
\r
34 import javax.swing.JButton;
\r
35 import javax.swing.JCheckBoxMenuItem;
\r
36 import javax.swing.JComponent;
\r
37 import javax.swing.JEditorPane;
\r
38 import javax.swing.JLabel;
\r
39 import javax.swing.JOptionPane;
\r
40 import javax.swing.JPanel;
\r
41 import javax.swing.JPopupMenu;
\r
42 import javax.swing.JRadioButtonMenuItem;
\r
43 import javax.swing.JSlider;
\r
44 import javax.swing.JSplitPane;
\r
45 import javax.swing.JTextField;
\r
46 import javax.swing.JToggleButton;
\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
52 import javax.swing.event.PopupMenuEvent;
\r
53 import javax.swing.event.PopupMenuListener;
\r
56 * MIDI Chord Helper - Circle-of-fifth oriented chord pad
\r
60 * Copyright (C) 2004-2013 @きよし - Akiyoshi Kamide
\r
61 * http://www.yk.rim.or.jp/~kamide/music/chordhelper/
\r
63 public class ChordHelperApplet extends JApplet {
\r
64 /////////////////////////////////////////////////////////////////////
\r
66 // JavaScript などからの呼び出しインターフェース
\r
68 /////////////////////////////////////////////////////////////////////
\r
70 * 未保存の修正済み MIDI ファイルがあるかどうか調べます。
\r
71 * @return 未保存の修正済み MIDI ファイルがあれば true
\r
73 public boolean isModified() {
\r
74 return editorDialog.sequenceListTableModel.isModified();
\r
77 * 指定された小節数の曲を、乱数で自動作曲してプレイリストへ追加します。
\r
78 * @param measureLength 小節数
\r
79 * @return 追加先のインデックス値(0から始まる)。追加できなかったときは -1
\r
81 public int addRandomSongToPlaylist(int measureLength) {
\r
82 editorDialog.newSequenceDialog.setRandomChordProgression(measureLength);
\r
83 return editorDialog.addSequenceAndPlay(
\r
84 editorDialog.newSequenceDialog.getMidiSequence()
\r
88 * URLで指定されたMIDIファイルをプレイリストへ追加します。
\r
90 * <p>URL の最後の / より後ろの部分がファイル名として取り込まれます。
\r
91 * 指定できる MIDI ファイルには、param タグの midi_file パラメータと同様の制限があります。
\r
93 * @param midiFileUrl 追加するMIDIファイルのURL
\r
94 * @return 追加先のインデックス値(0から始まる)。追加できなかったときは -1
\r
96 public int addToPlaylist(String midiFileUrl) {
\r
97 return editorDialog.addSequenceFromURL(midiFileUrl);
\r
100 * Base64 エンコードされた MIDI ファイルをプレイリストへ追加します。
\r
102 * @param base64EncodedText Base64エンコードされたMIDIファイル
\r
103 * @return 追加先のインデックス値(0から始まる)。追加できなかったときは -1
\r
105 public int addToPlaylistBase64(String base64EncodedText) {
\r
106 return addToPlaylistBase64(base64EncodedText, null);
\r
110 * Base64エンコードされたMIDIファイルをプレイリストへ追加します。
\r
112 * @param base64EncodedText Base64エンコードされたMIDIファイル
\r
113 * @param filename ディレクトリ名を除いたファイル名
\r
114 * @return 追加先のインデックス値(0から始まる)。追加できなかったときは -1
\r
116 public int addToPlaylistBase64(String base64EncodedText, String filename) {
\r
117 Base64Dialog d = editorDialog.base64Dialog;
\r
118 d.setBase64Data(base64EncodedText);
\r
119 return editorDialog.addSequence(d.getMIDIData(), filename);
\r
122 * プレイリスト上で現在選択されているMIDIシーケンスを、
\r
123 * シーケンサへロードして再生します。
\r
125 public void play() {
\r
126 play(editorDialog.sequenceListSelectionModel.getMinSelectionIndex());
\r
129 * 指定されたインデックス値が示すプレイリスト上のMIDIシーケンスを、
\r
130 * シーケンサへロードして再生します。
\r
131 * @param index インデックス値(0から始まる)
\r
133 public void play(int index) {
\r
134 editorDialog.sequenceListTableModel.loadToSequencer(index);
\r
135 deviceModelList.sequencerModel.start();
\r
138 * シーケンサが実行中かどうかを返します。
\r
139 * {@link Sequencer#isRunning()} の戻り値をそのまま返します。
\r
141 * @return 実行中のときtrue
\r
143 public boolean isRunning() {
\r
144 return deviceModelList.sequencerModel.getSequencer().isRunning();
\r
147 * シーケンサが再生中かどうかを返します。
\r
148 * @return 再生中のときtrue
\r
150 public boolean isPlaying() { return isRunning(); }
\r
152 * 現在シーケンサにロードされているMIDIデータを
\r
153 * Base64テキストに変換した結果を返します。
\r
154 * @return MIDIデータをBase64テキストに変換した結果
\r
156 public String getMidiDataBase64() {
\r
157 SequenceTrackListTableModel sequenceModel =
\r
158 editorDialog.sequenceListTableModel.sequencerModel.getSequenceTableModel();
\r
159 editorDialog.base64Dialog.setMIDIData(sequenceModel.getMIDIdata());
\r
160 return editorDialog.base64Dialog.getBase64Data();
\r
163 * 現在シーケンサにロードされているMIDIファイルのファイル名を返します。
\r
164 * @return MIDIファイル名(設定されていないときは空文字列)
\r
166 public String getMidiFilename() {
\r
167 SequenceTrackListTableModel seq_model = deviceModelList.sequencerModel.getSequenceTableModel();
\r
168 if( seq_model == null ) return null;
\r
169 String fn = seq_model.getFilename();
\r
170 return fn == null ? "" : fn ;
\r
174 * @param octavePosition オクターブ位置(デフォルト:4)
\r
176 public void setOctavePosition(int octavePosition) {
\r
177 keyboardPanel.keyboardCenterPanel.keyboard.octaveRangeModel.setValue(octavePosition);
\r
180 * 操作対象のMIDIチャンネルを変更します。
\r
181 * @param ch チャンネル番号 - 1(チャンネル1のとき0、デフォルトは0)
\r
183 public void setChannel(int ch) {
\r
184 keyboardPanel.keyboardCenterPanel.keyboard.midiChComboboxModel.setSelectedChannel(ch);
\r
187 * 操作対象のMIDIチャンネルを返します。
\r
188 * @return 操作対象のMIDIチャンネル
\r
190 public int getChannel() {
\r
191 return keyboardPanel.keyboardCenterPanel.keyboard.midiChComboboxModel.getSelectedChannel();
\r
194 * 操作対象のMIDIチャンネルに対してプログラム(音色)を設定します。
\r
195 * @param program 音色(0~127:General MIDI に基づく)
\r
197 public void programChange(int program) {
\r
198 keyboardPanel.keyboardCenterPanel.keyboard.getSelectedChannel().programChange(program);
\r
201 * 操作対象のMIDIチャンネルに対してプログラム(音色)を設定します。
\r
202 * 内部的には {@link #programChange(int)} を呼び出しているだけです。
\r
203 * @param program 音色(0~127:General MIDI に基づく)
\r
205 public void setProgram(int program) { programChange(program); }
\r
207 * 自動転回モードを変更します。初期値は true です。
\r
208 * @param isAuto true:自動転回を行う false:自動転回を行わない
\r
210 public void setAutoInversion(boolean isAuto) {
\r
211 inversionOmissionButton.setAutoInversion(isAuto);
\r
217 * <li>-1:省略しない(デフォルト)</li>
\r
218 * <li>0:ルート音を省略</li>
\r
223 public void setOmissionNoteIndex(int index) {
\r
224 inversionOmissionButton.setOmissionNoteIndex(index);
\r
227 * コードダイアグラムの表示・非表示を切り替えます。
\r
228 * @param isVisible 表示するときtrue
\r
230 public void setChordDiagramVisible(boolean isVisible) {
\r
231 keyboardSplitPane.resetToPreferredSizes();
\r
233 keyboardSplitPane.setDividerLocation((double)1.0);
\r
236 * コードダイヤグラムをギターモードに変更します。
\r
237 * 初期状態ではウクレレモードになっています。
\r
239 public void setChordDiagramForGuitar() {
\r
240 chordDiagram.setTargetInstrument(ChordDiagram.TargetInstrument.Guitar);
\r
243 * ダークモード(暗い表示)と明るい表示とを切り替えます。
\r
244 * @param isDark ダークモードのときtrue、明るい表示のときfalse(デフォルト)
\r
246 public void setDarkMode(boolean isDark) {
\r
247 darkModeToggleButton.setSelected(isDark);
\r
252 public static class VersionInfo {
\r
253 public static final String NAME = "MIDI Chord Helper";
\r
254 public static final String VERSION = "Ver.20131124.1";
\r
255 public static final String COPYRIGHT = "Copyright (C) 2004-2013";
\r
256 public static final String AUTHER = "@きよし - Akiyoshi Kamide";
\r
257 public static final String URL = "http://www.yk.rim.or.jp/~kamide/music/chordhelper/";
\r
258 public static String getInfo() {
\r
259 return NAME + " " + VERSION + " " + COPYRIGHT + " " + AUTHER + " " + URL;
\r
263 public String getAppletInfo() {
\r
264 return VersionInfo.getInfo();
\r
266 private class AboutMessagePane extends JEditorPane implements ActionListener {
\r
268 public AboutMessagePane() { this(true); }
\r
269 public AboutMessagePane(boolean link_enabled) {
\r
270 super( "text/html", "" );
\r
271 String link_string, tooltip = null;
\r
272 if( link_enabled && Desktop.isDesktopSupported() ) {
\r
273 tooltip = "Click this URL to open with your web browser - URLをクリックしてWebブラウザで開く";
\r
275 "<a href=\"" + VersionInfo.URL + "\" title=\"" +
\r
276 tooltip + "\">" + VersionInfo.URL + "</a>" ;
\r
279 link_enabled = false; link_string = VersionInfo.URL;
\r
282 "<html><center><font size=\"+1\">" + VersionInfo.NAME + "</font> " +
\r
283 VersionInfo.VERSION + "<br/><br/>" +
\r
284 VersionInfo.COPYRIGHT + " " + VersionInfo.AUTHER + "<br/>" +
\r
285 link_string + "</center></html>"
\r
287 setToolTipText(tooltip);
\r
289 putClientProperty(JEditorPane.HONOR_DISPLAY_PROPERTIES, Boolean.TRUE);
\r
290 setEditable(false);
\r
292 // メッセージ内の <a href=""> ~ </a> によるリンクを
\r
293 // 実際に機能させる(ブラウザで表示されるようにする)ための設定
\r
295 if( ! link_enabled ) return;
\r
297 uri = new URI(VersionInfo.URL);
\r
298 }catch( URISyntaxException use ) {
\r
299 use.printStackTrace();
\r
302 addHyperlinkListener(new HyperlinkListener() {
\r
303 public void hyperlinkUpdate(HyperlinkEvent e) {
\r
304 if(e.getEventType()==HyperlinkEvent.EventType.ACTIVATED) {
\r
306 Desktop.getDesktop().browse(uri);
\r
307 }catch(IOException ioe) {
\r
308 ioe.printStackTrace();
\r
315 public void actionPerformed(ActionEvent e) {
\r
316 JOptionPane.showMessageDialog(
\r
317 null, this, "Version info",
\r
318 JOptionPane.INFORMATION_MESSAGE, imageIcon
\r
323 public boolean isConfirmedToExit() {
\r
324 return ! isModified() || JOptionPane.showConfirmDialog(
\r
326 "MIDI file not saved, exit anyway ?\n保存されていないMIDIファイルがありますが、終了してよろしいですか?",
\r
328 JOptionPane.YES_NO_OPTION,
\r
329 JOptionPane.WARNING_MESSAGE
\r
330 ) == JOptionPane.YES_OPTION ;
\r
332 // アプリケーションのアイコンイメージ
\r
333 public ImageIcon imageIcon;
\r
334 // ボタンの余白を詰めたいときは setMargin() の引数にこれを指定する
\r
335 public static final Insets ZERO_INSETS = new Insets(0,0,0,0);
\r
337 private JPanel keyboardSequencerPanel;
\r
338 private JPanel chordGuide;
\r
339 private Color rootPaneDefaultBgcolor;
\r
340 private Color lyricDisplayDefaultBgcolor;
\r
341 private Border lyricDisplayDefaultBorder;
\r
342 private JSplitPane mainSplitPane;
\r
343 private JSplitPane keyboardSplitPane;
\r
344 private ChordButtonLabel enterButtonLabel;
\r
345 private ChordTextField lyricDisplay;
\r
346 private MidiKeyboardPanel keyboardPanel;
\r
347 ChordMatrix chordMatrix;
\r
348 private InversionAndOmissionLabel inversionOmissionButton;
\r
349 private JToggleButton darkModeToggleButton;
\r
350 MidiDeviceModelList deviceModelList;
\r
351 MidiDeviceDialog midiConnectionDialog;
\r
352 MidiEditor editorDialog;
\r
353 ChordDiagram chordDiagram;
\r
354 TempoSelecter tempoSelecter = new TempoSelecter() {
\r
355 { setEditable(false); }
\r
357 TimeSignatureSelecter timesigSelecter = new TimeSignatureSelecter() {
\r
358 { setEditable(false); }
\r
360 KeySignatureLabel keysigLabel = new KeySignatureLabel() {
\r
362 addMouseListener(new MouseAdapter() {
\r
363 public void mousePressed(MouseEvent e) {
\r
364 chordMatrix.setKeySignature(keysigLabel.getKey());
\r
369 JLabel songTitleLabel = new JLabel();
\r
372 AnoGakkiLayeredPane anoGakkiLayeredPane;
\r
373 JToggleButton anoGakkiToggleButton;
\r
375 public void init() {
\r
376 String imageIconPath = "images/midichordhelper.png";
\r
377 URL imageIconUrl = getClass().getResource(imageIconPath);
\r
378 if( imageIconUrl == null ) {
\r
379 // System.out.println("icon "+imageIconPath+" not found");
\r
383 imageIcon = new ImageIcon(imageIconUrl);
\r
385 Image iconImage = (imageIcon == null) ? null : imageIcon.getImage();
\r
386 rootPaneDefaultBgcolor = getContentPane().getBackground();
\r
387 chordMatrix = new ChordMatrix() {{
\r
388 addChordMatrixListener(new ChordMatrixListener(){
\r
389 public void keySignatureChanged() {
\r
390 Music.Key capoKey = getKeySignatureCapo();
\r
391 keyboardPanel.keySelecter.setKey(capoKey);
\r
392 keyboardPanel.keyboardCenterPanel.keyboard.setKeySignature(capoKey);
\r
394 public void chordChanged() { chordOn(); }
\r
397 chordMatrix.capoSelecter.checkbox.addItemListener(
\r
398 new ItemListener() {
\r
399 public void itemStateChanged(ItemEvent e) {
\r
401 keyboardPanel.keyboardCenterPanel.keyboard.chordDisplay.setNote(-1);
\r
402 chordDiagram.clear();
\r
406 chordMatrix.capoSelecter.valueSelecter.addActionListener(
\r
407 new ActionListener() {
\r
408 public void actionPerformed(ActionEvent e) {
\r
410 keyboardPanel.keyboardCenterPanel.keyboard.chordDisplay.setNote(-1);
\r
411 chordDiagram.clear();
\r
415 keyboardPanel = new MidiKeyboardPanel(chordMatrix) {{
\r
416 keyboardCenterPanel.keyboard.addPianoKeyboardListener(
\r
417 new PianoKeyboardAdapter() {
\r
418 public void pianoKeyPressed(int n, InputEvent e) {
\r
419 chordDiagram.clear();
\r
423 keySelecter.keysigCombobox.addActionListener(
\r
424 new ActionListener() {
\r
425 public void actionPerformed(ActionEvent e) {
\r
426 Music.Key key = keySelecter.getKey();
\r
427 key.transpose( - chordMatrix.capoSelecter.getCapo() );
\r
428 chordMatrix.setKeySignature(key);
\r
432 keyboardCenterPanel.keyboard.setPreferredSize(new Dimension(571, 80));
\r
434 deviceModelList = new MidiDeviceModelList(
\r
435 new Vector<VirtualMidiDevice>() {
\r
437 add(keyboardPanel.keyboardCenterPanel.keyboard.midiDevice);
\r
441 editorDialog = new MidiEditor(deviceModelList.sequencerModel);
\r
442 editorDialog.setIconImage(iconImage);
\r
443 new DropTarget(this, DnDConstants.ACTION_COPY_OR_MOVE, editorDialog, true);
\r
444 deviceModelList.setMidiEditor(editorDialog);
\r
445 keyboardPanel.eventDialog = editorDialog.eventCellEditor.eventDialog;
\r
446 midiConnectionDialog = new MidiDeviceDialog(deviceModelList);
\r
447 midiConnectionDialog.setIconImage(iconImage);
\r
448 lyricDisplay = new ChordTextField() {{
\r
449 addActionListener(new ActionListener() {
\r
450 public void actionPerformed(ActionEvent event) {
\r
451 chordMatrix.setSelectedChord(
\r
452 event.getActionCommand().trim().split("[ \t\r\n]")[0]
\r
457 lyricDisplayDefaultBorder = lyricDisplay.getBorder();
\r
458 lyricDisplayDefaultBgcolor = lyricDisplay.getBackground();
\r
462 chordDiagram = new ChordDiagram(this);
\r
466 deviceModelList.sequencerModel.getSequencer().addMetaEventListener(
\r
467 new MetaEventListener() {
\r
469 public void meta(MetaMessage msg) {
\r
470 switch(msg.getType()) {
\r
471 case 0x01: // Text(任意のテキスト:コメントなど)
\r
472 case 0x02: // Copyright(著作権表示)
\r
473 case 0x05: // Lyrics(歌詞)
\r
474 case 0x06: // Marker
\r
475 case 0x03: // Sequence Name / Track Name(曲名またはトラック名)
\r
476 lyricDisplay.addLyric(msg.getData());
\r
478 case 0x51: // Tempo (3 bytes) - テンポ
\r
479 tempoSelecter.setTempo(msg.getData());
\r
481 case 0x58: // Time signature (4 bytes) - 拍子
\r
482 timesigSelecter.setValue(msg.getData());
\r
484 case 0x59: // Key signature (2 bytes) : 調号
\r
485 Music.Key key = new Music.Key(msg.getData());
\r
486 keysigLabel.setKeySignature(key);
\r
487 chordMatrix.setKeySignature(key);
\r
493 deviceModelList.sequencerModel.addChangeListener(new ChangeListener() {
\r
495 public void stateChanged(ChangeEvent e) {
\r
496 SequenceTrackListTableModel sequenceTableModel = deviceModelList.sequencerModel.getSequenceTableModel();
\r
497 int loadedSequenceIndex = editorDialog.sequenceListTableModel.getLoadedIndex();
\r
498 songTitleLabel.setText(
\r
500 loadedSequenceIndex < 0 ? "[No MIDI file loaded]" :
\r
501 "MIDI file " + loadedSequenceIndex + ": " + (
\r
502 sequenceTableModel == null ||
\r
503 sequenceTableModel.toString() == null ||
\r
504 sequenceTableModel.toString().isEmpty() ?
\r
506 "<font color=maroon>"+sequenceTableModel+"</font>"
\r
510 Sequencer sequencer = deviceModelList.sequencerModel.getSequencer();
\r
511 chordMatrix.setPlaying(sequencer.isRunning());
\r
512 if( sequenceTableModel != null ) {
\r
513 SequenceTickIndex tickIndex = sequenceTableModel.getSequenceTickIndex();
\r
514 long tickPos = sequencer.getTickPosition();
\r
515 tickIndex.tickToMeasure(tickPos);
\r
516 chordMatrix.setBeat(tickIndex);
\r
518 deviceModelList.sequencerModel.getValueIsAdjusting() ||
\r
519 ! (sequencer.isRunning() || sequencer.isRecording())
\r
522 msg = tickIndex.lastMetaMessageAt(SequenceTickIndex.TIME_SIGNATURE, tickPos);
\r
523 timesigSelecter.setValue(msg==null ? null : msg.getData());
\r
524 msg = tickIndex.lastMetaMessageAt(SequenceTickIndex.TEMPO, tickPos);
\r
525 tempoSelecter.setTempo(msg==null ? null : msg.getData());
\r
526 msg = tickIndex.lastMetaMessageAt(SequenceTickIndex.KEY_SIGNATURE, tickPos);
\r
528 keysigLabel.clear();
\r
530 Music.Key key = new Music.Key(msg.getData());
\r
531 keysigLabel.setKeySignature(key);
\r
532 chordMatrix.setKeySignature(key);
\r
538 deviceModelList.sequencerModel.fireStateChanged();
\r
539 chordGuide = new JPanel() {
\r
541 setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
\r
542 add( Box.createHorizontalStrut(2) );
\r
543 add( chordMatrix.chordGuide );
\r
544 add( Box.createHorizontalStrut(2) );
\r
545 add( lyricDisplay );
\r
546 add( Box.createHorizontalStrut(2) );
\r
547 add( enterButtonLabel = new ChordButtonLabel("Enter",chordMatrix) {{
\r
548 addMouseListener(new MouseAdapter() {
\r
549 public void mousePressed(MouseEvent e) {
\r
550 if( (e.getModifiersEx() & InputEvent.BUTTON3_DOWN_MASK) != 0 ) // RightClicked
\r
551 chordMatrix.setSelectedChord( (Music.Chord)null );
\r
553 chordMatrix.setSelectedChord( lyricDisplay.getText() );
\r
557 add( Box.createHorizontalStrut(5) );
\r
558 add( chordMatrix.chordDisplay );
\r
559 add( Box.createHorizontalStrut(5) );
\r
560 add( darkModeToggleButton = new JToggleButton(new ButtonIcon(ButtonIcon.DARK_MODE_ICON)) {{
\r
561 setMargin(ZERO_INSETS);
\r
562 addItemListener(new ItemListener() {
\r
563 public void itemStateChanged(ItemEvent e) {
\r
564 innerSetDarkMode(darkModeToggleButton.isSelected());
\r
567 setToolTipText("Light / Dark - 明かりを点灯/消灯");
\r
570 add( Box.createHorizontalStrut(5) );
\r
571 add( anoGakkiToggleButton = new JToggleButton(
\r
572 new ButtonIcon(ButtonIcon.ANO_GAKKI_ICON)
\r
575 setMargin(ZERO_INSETS);
\r
577 setToolTipText("あの楽器");
\r
579 new ItemListener() {
\r
580 public void itemStateChanged(ItemEvent e) {
\r
581 keyboardPanel.keyboardCenterPanel.keyboard.anoGakkiLayeredPane
\r
582 = anoGakkiToggleButton.isSelected() ? anoGakkiLayeredPane : null ;
\r
587 add( Box.createHorizontalStrut(5) );
\r
588 add( inversionOmissionButton = new InversionAndOmissionLabel() );
\r
589 add( Box.createHorizontalStrut(5) );
\r
590 add( chordMatrix.capoSelecter );
\r
591 add( Box.createHorizontalStrut(2) );
\r
594 keyboardSequencerPanel = new JPanel() {{
\r
595 setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
\r
597 add(Box.createVerticalStrut(5));
\r
598 add(keyboardSplitPane = new JSplitPane(
\r
599 JSplitPane.HORIZONTAL_SPLIT, keyboardPanel, chordDiagram
\r
601 setOneTouchExpandable(true);
\r
602 setResizeWeight(1.0);
\r
603 setAlignmentX((float)0.5);
\r
605 add(Box.createVerticalStrut(5));
\r
606 add(new JPanel() {{
\r
607 setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
\r
608 add(new JPanel() {{
\r
609 setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
\r
610 add( Box.createHorizontalStrut(12) );
\r
611 add( keysigLabel );
\r
612 add( Box.createHorizontalStrut(12) );
\r
613 add( timesigSelecter );
\r
614 add( Box.createHorizontalStrut(12) );
\r
615 add( tempoSelecter );
\r
616 add( Box.createHorizontalStrut(12) );
\r
617 add( new MeasureIndicator(deviceModelList.sequencerModel) );
\r
618 add( Box.createHorizontalStrut(12) );
\r
619 add( songTitleLabel );
\r
620 add( Box.createHorizontalStrut(12) );
\r
621 add( new JButton("Edit/Playlist/Speed", new ButtonIcon(ButtonIcon.EDIT_ICON)) {{
\r
622 setMargin(ZERO_INSETS);
\r
623 addActionListener(editorDialog);
\r
626 add(new JPanel() {{
\r
627 setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
\r
628 add( Box.createHorizontalStrut(10) );
\r
629 add( new JSlider(deviceModelList.sequencerModel) );
\r
630 add( new TimeIndicator(deviceModelList.sequencerModel) );
\r
631 add( Box.createHorizontalStrut(5) );
\r
632 add( new JButton(editorDialog.sequenceListTableModel.moveToTopAction) {{
\r
633 setMargin(ZERO_INSETS);
\r
635 add(new JButton(deviceModelList.sequencerModel.moveBackwardAction) {{
\r
636 setMargin(ZERO_INSETS);
\r
638 add(new JToggleButton(deviceModelList.sequencerModel.startStopAction));
\r
639 add(new JButton(deviceModelList.sequencerModel.moveForwardAction) {{
\r
640 setMargin(ZERO_INSETS);
\r
642 add(new JButton(editorDialog.sequenceListTableModel.moveToBottomAction) {{
\r
643 setMargin(ZERO_INSETS);
\r
645 add(new JToggleButton(deviceModelList.sequencerModel.toggleRepeatAction) {{
\r
646 setMargin(ZERO_INSETS);
\r
648 add( Box.createHorizontalStrut(10) );
\r
650 add(new JPanel() {{
\r
652 "MIDI device connection",
\r
653 new ButtonIcon( ButtonIcon.MIDI_CONNECTOR_ICON )
\r
655 addActionListener(midiConnectionDialog);
\r
657 add(new JButton("Version info") {{
\r
658 setToolTipText(VersionInfo.NAME + " " + VersionInfo.VERSION);
\r
659 addActionListener(new AboutMessagePane());
\r
664 mainSplitPane = new JSplitPane(
\r
665 JSplitPane.VERTICAL_SPLIT, chordMatrix, keyboardSequencerPanel
\r
667 setResizeWeight(0.5);
\r
668 setAlignmentX((float)0.5);
\r
671 anoGakkiLayeredPane = new AnoGakkiLayeredPane() {{ add(mainSplitPane); }};
\r
672 setContentPane(anoGakkiLayeredPane);
\r
673 setPreferredSize(new Dimension(750,470));
\r
675 /////////////////////////////////////////
\r
679 public void start() {
\r
681 // コードボタンで設定されている現在の調を
\r
683 chordMatrix.fireKeySignatureChanged();
\r
685 // アプレットのパラメータにMIDIファイルのURLが指定されていたら
\r
687 String midi_url = getParameter("midi_file");
\r
689 if( midi_url != null ) {
\r
690 addToPlaylist(midi_url);
\r
695 public void stop() {
\r
696 deviceModelList.sequencerModel.stop(); // MIDI再生を強制終了
\r
699 private void innerSetDarkMode(boolean is_dark) {
\r
700 Color col = is_dark ? Color.black : null;
\r
701 // Color fgcol = is_dark ? Color.pink : null;
\r
702 getContentPane().setBackground(
\r
703 is_dark ? Color.black : rootPaneDefaultBgcolor
\r
705 mainSplitPane.setBackground( col );
\r
706 keyboardSplitPane.setBackground( col );
\r
707 enterButtonLabel.setDarkMode( is_dark );
\r
708 chordGuide.setBackground( col );
\r
709 lyricDisplay.setBorder( is_dark ? null : lyricDisplayDefaultBorder );
\r
710 lyricDisplay.setBackground( is_dark ?
\r
711 chordMatrix.darkModeColorset.backgrounds[2] : lyricDisplayDefaultBgcolor
\r
713 lyricDisplay.setForeground( is_dark ? Color.white : null );
\r
714 inversionOmissionButton.setBackground( col );
\r
715 anoGakkiToggleButton.setBackground( col );
\r
716 keyboardSequencerPanel.setBackground( col );
\r
717 chordDiagram.setBackground( col );
\r
718 chordDiagram.titleLabel.setDarkMode( is_dark );
\r
719 chordMatrix.setDarkMode( is_dark );
\r
720 keyboardPanel.setDarkMode( is_dark );
\r
723 private int[] chordOnNotes = null;
\r
726 * <p>この関数を直接呼ぶとアルペジオが効かないので、
\r
727 * chord_matrix.setSelectedChord() を使うことを推奨
\r
730 public void chordOn() {
\r
731 Music.Chord playChord = chordMatrix.getSelectedChord();
\r
733 chordOnNotes != null &&
\r
734 chordMatrix.getNoteIndex() < 0 &&
\r
735 (! chordMatrix.isDragged() || playChord == null)
\r
737 // コードが鳴っている状態で、新たなコードを鳴らそうとしたり、
\r
738 // もう鳴らさないという信号が来た場合は、今鳴っている音を止める。
\r
740 for( int n : chordOnNotes )
\r
741 keyboardPanel.keyboardCenterPanel.keyboard.noteOff(n);
\r
742 chordOnNotes = null;
\r
744 if( playChord == null ) {
\r
745 // もう鳴らさないので、歌詞表示に通知して終了
\r
746 if( lyricDisplay != null )
\r
747 lyricDisplay.currentChord = null;
\r
751 if( keyboardPanel.keyboardCenterPanel.keyboard.anoGakkiLayeredPane != null ) {
\r
752 JComponent btn = chordMatrix.getSelectedButton();
\r
754 anoGakkiLayeredPane.start(chordMatrix,btn);
\r
756 // コードボタンからのコードを、カポつき演奏キーからオリジナルキーへ変換
\r
757 Music.Key originalKey = chordMatrix.getKeySignatureCapo();
\r
758 Music.Chord originalChord = playChord.clone().transpose(
\r
759 chordMatrix.capoSelecter.getCapo(),
\r
760 chordMatrix.getKeySignature()
\r
762 // 変換後のコードをキーボード画面に設定
\r
763 keyboardPanel.keyboardCenterPanel.keyboard.setChord(originalChord);
\r
765 // 音域を決める。これにより鳴らす音が確定する。
\r
766 Music.Range chordRange = new Music.Range(
\r
767 keyboardPanel.keyboardCenterPanel.keyboard.getChromaticOffset() + 10 +
\r
768 ( keyboardPanel.keyboardCenterPanel.keyboard.getOctaves() / 4 ) * 12,
\r
769 inversionOmissionButton.isAutoInversionMode() ?
\r
770 keyboardPanel.keyboardCenterPanel.keyboard.getChromaticOffset() + 21 :
\r
771 keyboardPanel.keyboardCenterPanel.keyboard.getChromaticOffset() + 33,
\r
773 inversionOmissionButton.isAutoInversionMode()
\r
775 int[] notes = originalChord.toNoteArray(chordRange, originalKey);
\r
777 // 前回鳴らしたコード構成音を覚えておく
\r
778 int[] prevChordOnNotes = null;
\r
779 if( chordMatrix.isDragged() || chordMatrix.getNoteIndex() >= 0 )
\r
780 prevChordOnNotes = Arrays.copyOf(chordOnNotes, chordOnNotes.length);
\r
783 chordOnNotes = new int[notes.length];
\r
785 for( int n : notes ) {
\r
786 if( inversionOmissionButton.getOmissionNoteIndex() == i ) {
\r
789 chordOnNotes[i++] = n;
\r
792 boolean isNoteOn = false;
\r
793 if( prevChordOnNotes != null ) {
\r
794 for( int prevN : prevChordOnNotes ) {
\r
801 // すでに鳴っているのに単音を鳴らそうとする場合、
\r
802 // 鳴らそうとしている音を一旦止める。
\r
803 if( isNoteOn && chordMatrix.getNoteIndex() >= 0 &&
\r
804 notes[chordMatrix.getNoteIndex()] - n == 0
\r
806 keyboardPanel.keyboardCenterPanel.keyboard.noteOff(n);
\r
809 // その音が鳴っていなかったら鳴らす。
\r
811 keyboardPanel.keyboardCenterPanel.keyboard.noteOn(n);
\r
815 keyboardPanel.keyboardCenterPanel.keyboard.setChord(originalChord);
\r
816 chordMatrix.chordDisplay.setChord(playChord);
\r
818 // コードダイアグラム用にもコードを表示
\r
819 Music.Chord diagramChord;
\r
820 int chordDiagramCapo = chordDiagram.capoSelecterView.getCapo();
\r
821 if( chordDiagramCapo == chordMatrix.capoSelecter.getCapo() )
\r
822 diagramChord = playChord.clone();
\r
824 diagramChord = originalChord.clone().transpose(
\r
825 - chordDiagramCapo, originalKey
\r
827 chordDiagram.setChord(diagramChord);
\r
828 if( chordDiagram.recordTextButton.isSelected() )
\r
829 lyricDisplay.appendChord(diagramChord);
\r
833 /***************************************************************************
\r
837 ***************************************************************************/
\r
839 class ChordDisplay extends JLabel {
\r
840 Music.Chord chord = null;
\r
841 PianoKeyboard keyboard = null;
\r
842 ChordMatrix chordMatrix = null;
\r
843 String defaultString = null;
\r
844 int noteNumber = -1;
\r
845 private boolean isDark = false;
\r
846 private boolean isMouseEntered = false;
\r
848 public ChordDisplay(String defaultString, ChordMatrix chordMatrix, PianoKeyboard keyboard) {
\r
849 super(defaultString, JLabel.CENTER);
\r
850 this.defaultString = defaultString;
\r
851 this.keyboard = keyboard;
\r
852 this.chordMatrix = chordMatrix;
\r
853 if( chordMatrix != null ) {
\r
854 addMouseListener(new MouseAdapter() {
\r
855 public void mousePressed(MouseEvent e) {
\r
856 if( chord != null ) { // コードが表示されている場合
\r
857 if( (e.getModifiersEx() & InputEvent.BUTTON3_DOWN_MASK) != 0 ) {
\r
859 ChordDisplay.this.chordMatrix.setSelectedChord((Music.Chord)null);
\r
863 // キーボードが指定されている場合、オリジナルキー(カポ反映済)のコードを使う。
\r
864 if( ChordDisplay.this.keyboard == null )
\r
865 ChordDisplay.this.chordMatrix.setSelectedChord(chord);
\r
867 ChordDisplay.this.chordMatrix.setSelectedChordCapo(chord);
\r
870 else if( noteNumber >= 0 ) { // 音階が表示されている場合
\r
871 ChordDisplay.this.keyboard.noteOn(noteNumber);
\r
874 public void mouseReleased(MouseEvent e) {
\r
875 if( noteNumber >= 0 )
\r
876 ChordDisplay.this.keyboard.noteOff(noteNumber);
\r
878 public void mouseEntered(MouseEvent e) {
\r
879 mouseEntered(true);
\r
881 public void mouseExited(MouseEvent e) {
\r
882 mouseEntered(false);
\r
884 private void mouseEntered(boolean isMouseEntered) {
\r
885 ChordDisplay.this.isMouseEntered = isMouseEntered;
\r
886 if( noteNumber >= 0 || chord != null )
\r
890 addMouseWheelListener(this.chordMatrix);
\r
893 public void paint(Graphics g) {
\r
895 Dimension d = getSize();
\r
896 if( isMouseEntered && (noteNumber >= 0 || chord != null) ) {
\r
897 g.setColor(Color.gray);
\r
898 g.drawRect( 0, 0, d.width-1, d.height-1 );
\r
901 private void setChordText() {
\r
902 setText( chord.toHtmlString(isDark ? "#FFCC33" : "maroon") );
\r
904 void setNote(int note_no) { setNote( note_no, false ); }
\r
905 void setNote(int note_no, boolean is_rhythm_part) {
\r
906 setToolTipText(null);
\r
908 this.noteNumber = note_no;
\r
909 if( note_no < 0 ) {
\r
913 setText(defaultString);
\r
916 if( is_rhythm_part ) {
\r
918 "MIDI note No." + note_no + " : "
\r
919 + MIDISpec.getPercussionName(note_no)
\r
924 "Note: " + Music.NoteSymbol.noteNoToSymbol(note_no)
\r
925 + " - MIDI note No." + note_no + " : "
\r
926 + Math.round(Music.noteNoToFrequency(note_no)) + "Hz" );
\r
929 void setChord(Music.Chord chord) {
\r
930 this.chord = chord;
\r
931 this.noteNumber = -1;
\r
932 if( chord == null ) {
\r
933 setText( defaultString );
\r
934 setToolTipText( null );
\r
938 setToolTipText( "Chord: " + chord.toName() );
\r
941 void setDarkMode(boolean is_dark) {
\r
942 this.isDark = is_dark;
\r
943 if( chord != null ) setChordText();
\r
950 class InversionAndOmissionLabel extends JLabel
\r
951 implements MouseListener, PopupMenuListener
\r
953 JPopupMenu popup_menu;
\r
954 ButtonGroup omission_group = new ButtonGroup();
\r
955 ButtonIcon icon = new ButtonIcon(ButtonIcon.INVERSION_ICON);
\r
956 JRadioButtonMenuItem radioButtonitems[] = new JRadioButtonMenuItem[4];
\r
957 JCheckBoxMenuItem cb_inversion;
\r
959 public InversionAndOmissionLabel() {
\r
961 popup_menu = new JPopupMenu();
\r
963 cb_inversion = new JCheckBoxMenuItem("Auto Inversion",true)
\r
965 popup_menu.addSeparator();
\r
966 omission_group.add(
\r
967 radioButtonitems[0] = new JRadioButtonMenuItem("All notes",true)
\r
969 popup_menu.add(radioButtonitems[0]);
\r
970 omission_group.add(
\r
971 radioButtonitems[1] = new JRadioButtonMenuItem("Omit 5th")
\r
973 popup_menu.add(radioButtonitems[1]);
\r
974 omission_group.add(
\r
975 radioButtonitems[2] = new JRadioButtonMenuItem("Omit 3rd (Power Chord)")
\r
977 popup_menu.add(radioButtonitems[2]);
\r
978 omission_group.add(
\r
979 radioButtonitems[3] = new JRadioButtonMenuItem("Omit root")
\r
981 popup_menu.add(radioButtonitems[3]);
\r
982 addMouseListener(this);
\r
983 popup_menu.addPopupMenuListener(this);
\r
984 setToolTipText("Automatic inversion and Note omission - 自動転回と省略音の設定");
\r
986 public void mousePressed(MouseEvent e) {
\r
987 Component c = e.getComponent();
\r
988 if( c == this ) popup_menu.show( c, 0, getHeight() );
\r
990 public void mouseReleased(MouseEvent e) { }
\r
991 public void mouseEntered(MouseEvent e) { }
\r
992 public void mouseExited(MouseEvent e) { }
\r
993 public void mouseClicked(MouseEvent e) { }
\r
994 public void popupMenuCanceled(PopupMenuEvent e) { }
\r
995 public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
\r
996 repaint(); // To repaint icon image
\r
998 public void popupMenuWillBecomeVisible(PopupMenuEvent e) { }
\r
999 public boolean isAutoInversionMode() {
\r
1000 return cb_inversion.isSelected();
\r
1002 public void setAutoInversion(boolean is_auto) {
\r
1003 cb_inversion.setSelected(is_auto);
\r
1005 public int getOmissionNoteIndex() {
\r
1006 if( radioButtonitems[3].isSelected() ) { // Root
\r
1009 else if( radioButtonitems[2].isSelected() ) { // 3rd
\r
1012 else if( radioButtonitems[1].isSelected() ) { // 5th
\r
1015 else { // No omission
\r
1019 public void setOmissionNoteIndex(int index) {
\r
1021 case 0: radioButtonitems[3].setSelected(true); break;
\r
1022 case 1: radioButtonitems[2].setSelected(true); break;
\r
1023 case 2: radioButtonitems[1].setSelected(true); break;
\r
1024 default: radioButtonitems[0].setSelected(true); break;
\r
1029 class ChordTextField extends JTextField {
\r
1030 Music.Chord currentChord = null;
\r
1031 private long lyricArrivedTime = System.nanoTime();
\r
1032 public ChordTextField() {
\r
1035 // JTextField は、サイズ設定をしないとリサイズ時に縦に伸び過ぎてしまう。
\r
1036 // 1行しか入力できないので、縦に伸びすぎるのはスペースがもったいない。
\r
1037 // そこで、このような現象を防止するために、最大サイズを明示的に
\r
1040 // To reduce resized height, set maximum size to screen size.
\r
1043 java.awt.Toolkit.getDefaultToolkit().getScreenSize()
\r
1046 public void appendChord(Music.Chord chord) {
\r
1047 if( currentChord == null && chord == null )
\r
1049 if( currentChord != null && chord != null && chord.equals(currentChord) )
\r
1051 String delimiter = ""; // was "\n"
\r
1052 setText( getText() + (chord == null ? delimiter : chord + " ") );
\r
1053 currentChord = ( chord == null ? null : chord.clone() );
\r
1055 public void addLyric(byte[] data) {
\r
1056 long startTime = System.nanoTime();
\r
1058 String additionalLyric;
\r
1060 additionalLyric = (new String(data,"JISAutoDetect")).trim();
\r
1061 } catch( UnsupportedEncodingException e ) {
\r
1062 additionalLyric = (new String(data)).trim();
\r
1064 String lyric = getText();
\r
1065 if( startTime - lyricArrivedTime > 1000000000L /* 1sec */
\r
1067 additionalLyric.length() > 8 || additionalLyric.isEmpty()
\r
1068 || lyric == null || lyric.isEmpty()
\r
1071 // 長い歌詞や空白が来たり、追加先に歌詞がなかった場合は上書きする。
\r
1072 // ただし、前回から充分に時間が経っていない場合は上書きしない。
\r
1073 setText(additionalLyric);
\r
1076 // 短い歌詞だった場合は、既存の歌詞に追加する
\r
1077 setText( lyric + " " + additionalLyric );
\r
1079 setCaretPosition(getText().length());
\r
1080 lyricArrivedTime = startTime;
\r