-package camidion.chordhelper;\r
-import java.awt.BorderLayout;\r
-import java.awt.Color;\r
-import java.awt.Desktop;\r
-import java.awt.Dimension;\r
-import java.awt.Image;\r
-import java.awt.Insets;\r
-import java.awt.dnd.DnDConstants;\r
-import java.awt.dnd.DropTarget;\r
-import java.awt.event.ActionEvent;\r
-import java.awt.event.ActionListener;\r
-import java.awt.event.ComponentAdapter;\r
-import java.awt.event.ComponentEvent;\r
-import java.awt.event.InputEvent;\r
-import java.awt.event.ItemEvent;\r
-import java.awt.event.ItemListener;\r
-import java.awt.event.MouseAdapter;\r
-import java.awt.event.MouseEvent;\r
-import java.io.IOException;\r
-import java.net.URI;\r
-import java.net.URISyntaxException;\r
-import java.net.URL;\r
-import java.security.AccessControlException;\r
-import java.util.Arrays;\r
-import java.util.Vector;\r
-\r
-import javax.sound.midi.InvalidMidiDataException;\r
-import javax.sound.midi.MetaEventListener;\r
-import javax.sound.midi.MetaMessage;\r
-import javax.sound.midi.Sequence;\r
-import javax.sound.midi.Sequencer;\r
-import javax.swing.Box;\r
-import javax.swing.BoxLayout;\r
-import javax.swing.ImageIcon;\r
-import javax.swing.JApplet;\r
-import javax.swing.JButton;\r
-import javax.swing.JComponent;\r
-import javax.swing.JEditorPane;\r
-import javax.swing.JLabel;\r
-import javax.swing.JLayeredPane;\r
-import javax.swing.JOptionPane;\r
-import javax.swing.JPanel;\r
-import javax.swing.JSlider;\r
-import javax.swing.JSplitPane;\r
-import javax.swing.JToggleButton;\r
-import javax.swing.SwingUtilities;\r
-import javax.swing.border.Border;\r
-import javax.swing.event.ChangeEvent;\r
-import javax.swing.event.ChangeListener;\r
-import javax.swing.event.HyperlinkEvent;\r
-import javax.swing.event.HyperlinkListener;\r
-\r
-import camidion.chordhelper.anogakki.AnoGakkiPane;\r
-import camidion.chordhelper.chorddiagram.ChordDiagram;\r
-import camidion.chordhelper.chordmatrix.ChordButtonLabel;\r
-import camidion.chordhelper.chordmatrix.ChordMatrix;\r
-import camidion.chordhelper.chordmatrix.ChordMatrixListener;\r
-import camidion.chordhelper.mididevice.MidiDeviceDialog;\r
-import camidion.chordhelper.mididevice.MidiDeviceModelList;\r
-import camidion.chordhelper.mididevice.SequencerMeasureView;\r
-import camidion.chordhelper.mididevice.SequencerTimeView;\r
-import camidion.chordhelper.mididevice.VirtualMidiDevice;\r
-import camidion.chordhelper.midieditor.Base64Dialog;\r
-import camidion.chordhelper.midieditor.KeySignatureLabel;\r
-import camidion.chordhelper.midieditor.SequenceTickIndex;\r
-import camidion.chordhelper.midieditor.SequenceTrackListTableModel;\r
-import camidion.chordhelper.midieditor.TempoSelecter;\r
-import camidion.chordhelper.midieditor.TimeSignatureSelecter;\r
-import camidion.chordhelper.music.Chord;\r
-import camidion.chordhelper.music.Key;\r
-import camidion.chordhelper.music.Range;\r
-import camidion.chordhelper.pianokeyboard.MidiKeyboardPanel;\r
-import camidion.chordhelper.pianokeyboard.PianoKeyboardAdapter;\r
-\r
-/**\r
- * MIDI Chord Helper - Circle-of-fifth oriented chord pad\r
- * (アプレットクラス)\r
- *\r
- * @auther\r
- * Copyright (C) 2004-2014 @きよし - Akiyoshi Kamide\r
- * http://www.yk.rim.or.jp/~kamide/music/chordhelper/\r
- */\r
-public class ChordHelperApplet extends JApplet {\r
- /////////////////////////////////////////////////////////////////////\r
- //\r
- // JavaScript などからの呼び出しインターフェース\r
- //\r
- /////////////////////////////////////////////////////////////////////\r
- /**\r
- * 未保存の修正済み MIDI ファイルがあるかどうか調べます。\r
- * @return 未保存の修正済み MIDI ファイルがあれば true\r
- */\r
- public boolean isModified() {\r
- return deviceModelList.editorDialog.sequenceListTable.getModel().isModified();\r
- }\r
- /**\r
- * 指定された小節数の曲を、乱数で自動作曲してプレイリストへ追加します。\r
- * @param measureLength 小節数\r
- * @return 追加先のインデックス値(0から始まる)。追加できなかったときは -1\r
- */\r
- public int addRandomSongToPlaylist(int measureLength) {\r
- deviceModelList.editorDialog.newSequenceDialog.setRandomChordProgression(measureLength);\r
- Sequence sequence = deviceModelList.editorDialog.newSequenceDialog.getMidiSequence();\r
- return deviceModelList.editorDialog.sequenceListTable.getModel().addSequenceAndPlay(sequence);\r
- }\r
- /**\r
- * URLで指定されたMIDIファイルをプレイリストへ追加します。\r
- *\r
- * <p>URL の最後の / より後ろの部分がファイル名として取り込まれます。\r
- * 指定できる MIDI ファイルには、param タグの midi_file パラメータと同様の制限があります。\r
- * </p>\r
- * @param midiFileUrl 追加するMIDIファイルのURL\r
- * @return 追加先のインデックス値(0から始まる)。追加できなかったときは -1\r
- */\r
- public int addToPlaylist(String midiFileUrl) {\r
- try {\r
- return deviceModelList.editorDialog.sequenceListTable.getModel().addSequenceFromURL(midiFileUrl);\r
- } catch( URISyntaxException|IOException|InvalidMidiDataException e ) {\r
- deviceModelList.editorDialog.showWarning(e.getMessage());\r
- } catch( AccessControlException e ) {\r
- e.printStackTrace();\r
- deviceModelList.editorDialog.showError(e.getMessage());\r
- }\r
- return -1;\r
- }\r
- /**\r
- * Base64 エンコードされた MIDI ファイルをプレイリストへ追加します。\r
- *\r
- * @param base64EncodedText Base64エンコードされたMIDIファイル\r
- * @return 追加先のインデックス値(0から始まる)。追加できなかったときは -1\r
- */\r
- public int addToPlaylistBase64(String base64EncodedText) {\r
- return addToPlaylistBase64(base64EncodedText, null);\r
- }\r
- /**\r
- * ファイル名を指定して、\r
- * Base64エンコードされたMIDIファイルをプレイリストへ追加します。\r
- *\r
- * @param base64EncodedText Base64エンコードされたMIDIファイル\r
- * @param filename ディレクトリ名を除いたファイル名\r
- * @return 追加先のインデックス値(0から始まる)。追加できなかったときは -1\r
- */\r
- public int addToPlaylistBase64(String base64EncodedText, String filename) {\r
- Base64Dialog d = deviceModelList.editorDialog.base64Dialog;\r
- d.setBase64Data(base64EncodedText);\r
- try {\r
- return deviceModelList.editorDialog.sequenceListTable.getModel().addSequence(d.getMIDIData(), filename);\r
- } catch (IOException | InvalidMidiDataException e) {\r
- e.printStackTrace();\r
- deviceModelList.editorDialog.showWarning(e.getMessage());\r
- return -1;\r
- }\r
- }\r
- /**\r
- * プレイリスト上で現在選択されているMIDIシーケンスを、\r
- * シーケンサへロードして再生します。\r
- */\r
- public void play() {\r
- play(deviceModelList.editorDialog.sequenceListTable.getModel().sequenceListSelectionModel.getMinSelectionIndex());\r
- }\r
- /**\r
- * 指定されたインデックス値が示すプレイリスト上のMIDIシーケンスを、\r
- * シーケンサへロードして再生します。\r
- * @param index インデックス値(0から始まる)\r
- */\r
- public void play(int index) {\r
- deviceModelList.editorDialog.sequenceListTable.getModel().loadToSequencer(index);\r
- deviceModelList.getSequencerModel().start();\r
- }\r
- /**\r
- * シーケンサが実行中かどうかを返します。\r
- * {@link Sequencer#isRunning()} の戻り値をそのまま返します。\r
- *\r
- * @return 実行中のときtrue\r
- */\r
- public boolean isRunning() {\r
- return deviceModelList.getSequencerModel().getSequencer().isRunning();\r
- }\r
- /**\r
- * シーケンサが再生中かどうかを返します。\r
- * @return 再生中のときtrue\r
- */\r
- public boolean isPlaying() { return isRunning(); }\r
- /**\r
- * 現在シーケンサにロードされているMIDIデータを\r
- * Base64テキストに変換した結果を返します。\r
- * @return MIDIデータをBase64テキストに変換した結果\r
- */\r
- public String getMidiDataBase64() {\r
- SequenceTrackListTableModel sequenceModel =\r
- deviceModelList.editorDialog.sequenceListTable.getModel().sequencerModel.getSequenceTrackListTableModel();\r
- deviceModelList.editorDialog.base64Dialog.setMIDIData(sequenceModel.getMIDIdata());\r
- return deviceModelList.editorDialog.base64Dialog.getBase64Data();\r
- }\r
- /**\r
- * 現在シーケンサにロードされているMIDIファイルのファイル名を返します。\r
- * @return MIDIファイル名(設定されていないときは空文字列)\r
- */\r
- public String getMidiFilename() {\r
- SequenceTrackListTableModel seq_model = deviceModelList.getSequencerModel().getSequenceTrackListTableModel();\r
- if( seq_model == null ) return null;\r
- String fn = seq_model.getFilename();\r
- return fn == null ? "" : fn ;\r
- }\r
- /**\r
- * オクターブ位置を設定します。\r
- * @param octavePosition オクターブ位置(デフォルト:4)\r
- */\r
- public void setOctavePosition(int octavePosition) {\r
- keyboardPanel.keyboardCenterPanel.keyboard.octaveRangeModel.setValue(octavePosition);\r
- }\r
- /**\r
- * 操作対象のMIDIチャンネルを変更します。\r
- * @param ch チャンネル番号 - 1(チャンネル1のとき0、デフォルトは0)\r
- */\r
- public void setChannel(int ch) {\r
- keyboardPanel.keyboardCenterPanel.keyboard.midiChComboboxModel.setSelectedChannel(ch);\r
- }\r
- /**\r
- * 操作対象のMIDIチャンネルを返します。\r
- * @return 操作対象のMIDIチャンネル\r
- */\r
- public int getChannel() {\r
- return keyboardPanel.keyboardCenterPanel.keyboard.midiChComboboxModel.getSelectedChannel();\r
- }\r
- /**\r
- * 操作対象のMIDIチャンネルに対してプログラム(音色)を設定します。\r
- * @param program 音色(0~127:General MIDI に基づく)\r
- */\r
- public void programChange(int program) {\r
- keyboardPanel.keyboardCenterPanel.keyboard.getSelectedChannel().programChange(program);\r
- }\r
- /**\r
- * 操作対象のMIDIチャンネルに対してプログラム(音色)を設定します。\r
- * 内部的には {@link #programChange(int)} を呼び出しているだけです。\r
- * @param program 音色(0~127:General MIDI に基づく)\r
- */\r
- public void setProgram(int program) { programChange(program); }\r
- /**\r
- * 自動転回モードを変更します。初期値は true です。\r
- * @param isAuto true:自動転回を行う false:自動転回を行わない\r
- */\r
- public void setAutoInversion(boolean isAuto) {\r
- inversionOmissionButton.setAutoInversion(isAuto);\r
- }\r
- /**\r
- * 省略したい構成音を指定します。\r
- * @param index\r
- * <ul>\r
- * <li>-1:省略しない(デフォルト)</li>\r
- * <li>0:ルート音を省略</li>\r
- * <li>1:三度を省略</li>\r
- * <li>2:五度を省略</li>\r
- * </ul>\r
- */\r
- public void setOmissionNoteIndex(int index) {\r
- inversionOmissionButton.setOmissionNoteIndex(index);\r
- }\r
- /**\r
- * コードダイアグラムの表示・非表示を切り替えます。\r
- * @param isVisible 表示するときtrue\r
- */\r
- public void setChordDiagramVisible(boolean isVisible) {\r
- keyboardSplitPane.resetToPreferredSizes();\r
- if( ! isVisible )\r
- keyboardSplitPane.setDividerLocation((double)1.0);\r
- }\r
- /**\r
- * コードダイヤグラムをギターモードに変更します。\r
- * 初期状態ではウクレレモードになっています。\r
- */\r
- public void setChordDiagramForGuitar() {\r
- chordDiagram.setTargetInstrument(ChordDiagram.Instrument.Guitar);\r
- }\r
- /**\r
- * ダークモード(暗い表示)と明るい表示とを切り替えます。\r
- * @param isDark ダークモードのときtrue、明るい表示のときfalse(デフォルト)\r
- */\r
- public void setDarkMode(boolean isDark) {\r
- darkModeToggleButton.setSelected(isDark);\r
- }\r
- /**\r
- * バージョン情報\r
- */\r
- public static class VersionInfo {\r
- public static final String NAME = "MIDI Chord Helper";\r
- public static final String VERSION = "Ver.20150104.1";\r
- public static final String COPYRIGHT = "Copyright (C) 2004-2014";\r
- public static final String AUTHER = "@きよし - Akiyoshi Kamide";\r
- public static final String URL = "http://www.yk.rim.or.jp/~kamide/music/chordhelper/";\r
- /**\r
- * バージョン情報を返します。\r
- * @return バージョン情報\r
- */\r
- public static String getInfo() {\r
- return NAME + " " + VERSION + " " + COPYRIGHT + " " + AUTHER + " " + URL;\r
- }\r
- }\r
- @Override\r
- public String getAppletInfo() { return VersionInfo.getInfo(); }\r
- private class AboutMessagePane extends JEditorPane implements ActionListener {\r
- URI uri = null;\r
- public AboutMessagePane() { this(true); }\r
- public AboutMessagePane(boolean link_enabled) {\r
- super( "text/html", "" );\r
- String link_string, tooltip = null;\r
- if( link_enabled && Desktop.isDesktopSupported() ) {\r
- tooltip = "Click this URL to open with your web browser - URLをクリックしてWebブラウザで開く";\r
- link_string =\r
- "<a href=\"" + VersionInfo.URL + "\" title=\"" +\r
- tooltip + "\">" + VersionInfo.URL + "</a>" ;\r
- }\r
- else {\r
- link_enabled = false; link_string = VersionInfo.URL;\r
- }\r
- setText(\r
- "<html><center><font size=\"+1\">" + VersionInfo.NAME + "</font> " +\r
- VersionInfo.VERSION + "<br/><br/>" +\r
- VersionInfo.COPYRIGHT + " " + VersionInfo.AUTHER + "<br/>" +\r
- link_string + "</center></html>"\r
- );\r
- setToolTipText(tooltip);\r
- setOpaque(false);\r
- putClientProperty(JEditorPane.HONOR_DISPLAY_PROPERTIES, Boolean.TRUE);\r
- setEditable(false);\r
- //\r
- // メッセージ内の <a href=""> ~ </a> によるリンクを\r
- // 実際に機能させる(ブラウザで表示されるようにする)ための設定\r
- //\r
- if( ! link_enabled ) return;\r
- try {\r
- uri = new URI(VersionInfo.URL);\r
- }catch( URISyntaxException use ) {\r
- use.printStackTrace();\r
- return;\r
- }\r
- addHyperlinkListener(new HyperlinkListener() {\r
- public void hyperlinkUpdate(HyperlinkEvent e) {\r
- if(e.getEventType()==HyperlinkEvent.EventType.ACTIVATED) {\r
- try{\r
- Desktop.getDesktop().browse(uri);\r
- }catch(IOException ioe) {\r
- ioe.printStackTrace();\r
- }\r
- }\r
- }\r
- });\r
- }\r
- @Override\r
- public void actionPerformed(ActionEvent e) {\r
- JOptionPane.showMessageDialog(\r
- null, this, "Version info",\r
- JOptionPane.INFORMATION_MESSAGE, imageIcon\r
- );\r
- }\r
- }\r
- // 終了してよいか確認する\r
- public boolean isConfirmedToExit() {\r
- return ! isModified() || JOptionPane.showConfirmDialog(\r
- this,\r
- "MIDI file not saved, exit anyway ?\n保存されていないMIDIファイルがありますが、終了してよろしいですか?",\r
- VersionInfo.NAME,\r
- JOptionPane.YES_NO_OPTION,\r
- JOptionPane.WARNING_MESSAGE\r
- ) == JOptionPane.YES_OPTION ;\r
- }\r
- /**\r
- * アプリケーションのアイコンイメージ\r
- */\r
- public ImageIcon imageIcon;\r
- /**\r
- * ボタンの余白を詰めたいときに setMargin() の引数に指定するインセット\r
- */\r
- public static final Insets ZERO_INSETS = new Insets(0,0,0,0);\r
- //\r
- public ChordMatrix chordMatrix;\r
- MidiDeviceModelList deviceModelList;\r
- //\r
- private JPanel keyboardSequencerPanel;\r
- private JPanel chordGuide;\r
- private Color rootPaneDefaultBgcolor;\r
- private Color lyricDisplayDefaultBgcolor;\r
- private Border lyricDisplayDefaultBorder;\r
- private JSplitPane mainSplitPane;\r
- private JSplitPane keyboardSplitPane;\r
- private ChordButtonLabel enterButtonLabel;\r
- private ChordTextField lyricDisplay;\r
- private MidiKeyboardPanel keyboardPanel;\r
- private InversionAndOmissionLabel inversionOmissionButton;\r
- private JToggleButton darkModeToggleButton;\r
- private MidiDeviceDialog midiConnectionDialog;\r
- private ChordDiagram chordDiagram;\r
- private TempoSelecter tempoSelecter;\r
- private TimeSignatureSelecter timesigSelecter;\r
- private KeySignatureLabel keysigLabel;\r
- private JLabel songTitleLabel;\r
- private AnoGakkiPane anoGakkiPane;\r
- private JToggleButton anoGakkiToggleButton;\r
-\r
- public void init() {\r
- String imageIconPath = "midichordhelper.png";\r
- URL imageIconUrl = getClass().getResource(imageIconPath);\r
- if( imageIconUrl == null ) {\r
- System.out.println("Icon image "+imageIconPath+" not found");\r
- imageIcon = null;\r
- }\r
- else {\r
- imageIcon = new ImageIcon(imageIconUrl);\r
- }\r
- Image iconImage = (imageIcon == null) ? null : imageIcon.getImage();\r
- rootPaneDefaultBgcolor = getContentPane().getBackground();\r
- chordMatrix = new ChordMatrix() {{\r
- addChordMatrixListener(new ChordMatrixListener(){\r
- public void keySignatureChanged() {\r
- Key capoKey = getKeySignatureCapo();\r
- keyboardPanel.keySelecter.setKey(capoKey);\r
- keyboardPanel.keyboardCenterPanel.keyboard.setKeySignature(capoKey);\r
- }\r
- public void chordChanged() { chordOn(); }\r
- });\r
- }};\r
- chordMatrix.capoSelecter.checkbox.addItemListener(\r
- new ItemListener() {\r
- public void itemStateChanged(ItemEvent e) {\r
- chordOn();\r
- keyboardPanel.keyboardCenterPanel.keyboard.chordDisplay.clear();\r
- chordDiagram.clear();\r
- }\r
- }\r
- );\r
- chordMatrix.capoSelecter.valueSelecter.addActionListener(\r
- new ActionListener() {\r
- public void actionPerformed(ActionEvent e) {\r
- chordOn();\r
- keyboardPanel.keyboardCenterPanel.keyboard.chordDisplay.clear();\r
- chordDiagram.clear();\r
- }\r
- }\r
- );\r
- keyboardPanel = new MidiKeyboardPanel(chordMatrix) {{\r
- keyboardCenterPanel.keyboard.addPianoKeyboardListener(\r
- new PianoKeyboardAdapter() {\r
- @Override\r
- public void pianoKeyPressed(int n, InputEvent e) {\r
- chordDiagram.clear();\r
- }\r
- }\r
- );\r
- keySelecter.keysigCombobox.addActionListener(\r
- new ActionListener() {\r
- @Override\r
- public void actionPerformed(ActionEvent e) {\r
- Key key = keySelecter.getKey();\r
- key.transpose( - chordMatrix.capoSelecter.getCapo() );\r
- chordMatrix.setKeySignature(key);\r
- }\r
- }\r
- );\r
- keyboardCenterPanel.keyboard.setPreferredSize(new Dimension(571, 80));\r
- }};\r
- deviceModelList = new MidiDeviceModelList(\r
- new Vector<VirtualMidiDevice>() {\r
- {\r
- add(keyboardPanel.keyboardCenterPanel.keyboard.midiDevice);\r
- }\r
- }\r
- );\r
- deviceModelList.editorDialog.setIconImage(iconImage);\r
- new DropTarget(this, DnDConstants.ACTION_COPY_OR_MOVE, deviceModelList.editorDialog, true);\r
- keyboardPanel.setEventDialog(deviceModelList.editorDialog.eventDialog);\r
- midiConnectionDialog = new MidiDeviceDialog(deviceModelList);\r
- midiConnectionDialog.setIconImage(iconImage);\r
- lyricDisplay = new ChordTextField(deviceModelList.getSequencerModel()) {{\r
- addActionListener(new ActionListener() {\r
- @Override\r
- public void actionPerformed(ActionEvent event) {\r
- String symbol = event.getActionCommand().trim().split("[ \t\r\n]")[0];\r
- chordMatrix.setSelectedChord(symbol);\r
- }\r
- });\r
- }};\r
- lyricDisplayDefaultBorder = lyricDisplay.getBorder();\r
- lyricDisplayDefaultBgcolor = lyricDisplay.getBackground();\r
- chordDiagram = new ChordDiagram(this);\r
- tempoSelecter = new TempoSelecter() {{\r
- setEditable(false);\r
- deviceModelList.getSequencerModel().getSequencer().addMetaEventListener(this);\r
- }};\r
- timesigSelecter = new TimeSignatureSelecter() {{\r
- setEditable(false);\r
- deviceModelList.getSequencerModel().getSequencer().addMetaEventListener(this);\r
- }};\r
- keysigLabel = new KeySignatureLabel() {{\r
- addMouseListener(new MouseAdapter() {\r
- public void mousePressed(MouseEvent e) {\r
- chordMatrix.setKeySignature(getKey());\r
- }\r
- });\r
- }};\r
- deviceModelList.getSequencerModel().getSequencer().addMetaEventListener(\r
- new MetaEventListener() {\r
- class SetKeySignatureRunnable implements Runnable {\r
- Key key;\r
- public SetKeySignatureRunnable(Key key) {\r
- this.key = key;\r
- }\r
- @Override\r
- public void run() { setKeySignature(key); }\r
- }\r
- @Override\r
- public void meta(MetaMessage msg) {\r
- switch(msg.getType()) {\r
- case 0x59: // Key signature (2 bytes) : 調号\r
- Key key = new Key(msg.getData());\r
- if( ! SwingUtilities.isEventDispatchThread() ) {\r
- SwingUtilities.invokeLater(\r
- new SetKeySignatureRunnable(key)\r
- );\r
- }\r
- setKeySignature(key);\r
- break;\r
- }\r
- }\r
- private void setKeySignature(Key key) {\r
- keysigLabel.setKeySignature(key);\r
- chordMatrix.setKeySignature(key);\r
- }\r
- }\r
- );\r
- songTitleLabel = new JLabel();\r
- //シーケンサーの時間スライダーの値が変わったときのリスナーを登録\r
- deviceModelList.getSequencerModel().addChangeListener(new ChangeListener() {\r
- @Override\r
- public void stateChanged(ChangeEvent e) {\r
- SequenceTrackListTableModel sequenceTableModel = deviceModelList.getSequencerModel().getSequenceTrackListTableModel();\r
- int loadedSequenceIndex = deviceModelList.editorDialog.sequenceListTable.getModel().indexOfSequenceOnSequencer();\r
- songTitleLabel.setText(\r
- "<html>"+(\r
- loadedSequenceIndex < 0 ? "[No MIDI file loaded]" :\r
- "MIDI file " + loadedSequenceIndex + ": " + (\r
- sequenceTableModel == null ||\r
- sequenceTableModel.toString() == null ||\r
- sequenceTableModel.toString().isEmpty() ?\r
- "[Untitled]" :\r
- "<font color=maroon>"+sequenceTableModel+"</font>"\r
- )\r
- )+"</html>"\r
- );\r
- Sequencer sequencer = deviceModelList.getSequencerModel().getSequencer();\r
- chordMatrix.setPlaying(sequencer.isRunning());\r
- if( sequenceTableModel != null ) {\r
- SequenceTickIndex tickIndex = sequenceTableModel.getSequenceTickIndex();\r
- long tickPos = sequencer.getTickPosition();\r
- tickIndex.tickToMeasure(tickPos);\r
- chordMatrix.setBeat(tickIndex);\r
- if(\r
- deviceModelList.getSequencerModel().getValueIsAdjusting() ||\r
- ! (sequencer.isRunning() || sequencer.isRecording())\r
- ) {\r
- MetaMessage msg;\r
- msg = tickIndex.lastMetaMessageAt(\r
- SequenceTickIndex.MetaMessageType.TIME_SIGNATURE, tickPos\r
- );\r
- timesigSelecter.setValue(msg==null ? null : msg.getData());\r
- msg = tickIndex.lastMetaMessageAt(\r
- SequenceTickIndex.MetaMessageType.TEMPO, tickPos\r
- );\r
- tempoSelecter.setTempo(msg==null ? null : msg.getData());\r
- msg = tickIndex.lastMetaMessageAt(\r
- SequenceTickIndex.MetaMessageType.KEY_SIGNATURE, tickPos\r
- );\r
- if( msg == null ) {\r
- keysigLabel.clear();\r
- }\r
- else {\r
- Key key = new Key(msg.getData());\r
- keysigLabel.setKeySignature(key);\r
- chordMatrix.setKeySignature(key);\r
- }\r
- }\r
- }\r
- }\r
- });\r
- deviceModelList.getSequencerModel().fireStateChanged();\r
- chordGuide = new JPanel() {\r
- {\r
- setLayout(new BoxLayout(this, BoxLayout.X_AXIS));\r
- add( Box.createHorizontalStrut(2) );\r
- add( chordMatrix.chordGuide );\r
- add( Box.createHorizontalStrut(2) );\r
- add( lyricDisplay );\r
- add( Box.createHorizontalStrut(2) );\r
- add( enterButtonLabel = new ChordButtonLabel("Enter",chordMatrix) {{\r
- addMouseListener(new MouseAdapter() {\r
- public void mousePressed(MouseEvent event) {\r
- if( (event.getModifiersEx() & InputEvent.BUTTON3_DOWN_MASK) != 0 ) // RightClicked\r
- chordMatrix.setSelectedChord((Chord)null);\r
- else {\r
- chordMatrix.setSelectedChord(lyricDisplay.getText());\r
- }\r
- }\r
- });\r
- }});\r
- add( Box.createHorizontalStrut(5) );\r
- add( chordMatrix.chordDisplay );\r
- add( Box.createHorizontalStrut(5) );\r
- add( darkModeToggleButton = new JToggleButton(new ButtonIcon(ButtonIcon.DARK_MODE_ICON)) {{\r
- setMargin(ZERO_INSETS);\r
- addItemListener(new ItemListener() {\r
- public void itemStateChanged(ItemEvent e) {\r
- innerSetDarkMode(darkModeToggleButton.isSelected());\r
- }\r
- });\r
- setToolTipText("Light / Dark - 明かりを点灯/消灯");\r
- setBorder(null);\r
- }});\r
- add( Box.createHorizontalStrut(5) );\r
- add( anoGakkiToggleButton = new JToggleButton(\r
- new ButtonIcon(ButtonIcon.ANO_GAKKI_ICON)\r
- ) {{\r
- setOpaque(false);\r
- setMargin(ZERO_INSETS);\r
- setBorder( null );\r
- setToolTipText("あの楽器");\r
- addItemListener(\r
- new ItemListener() {\r
- public void itemStateChanged(ItemEvent e) {\r
- keyboardPanel.keyboardCenterPanel.keyboard.anoGakkiPane\r
- = anoGakkiToggleButton.isSelected() ? anoGakkiPane : null ;\r
- }\r
- }\r
- );\r
- }} );\r
- add( Box.createHorizontalStrut(5) );\r
- add( inversionOmissionButton = new InversionAndOmissionLabel() );\r
- add( Box.createHorizontalStrut(5) );\r
- add( chordMatrix.capoSelecter );\r
- add( Box.createHorizontalStrut(2) );\r
- }\r
- };\r
- keyboardSequencerPanel = new JPanel() {{\r
- setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));\r
- add(chordGuide);\r
- add(Box.createVerticalStrut(5));\r
- add(keyboardSplitPane = new JSplitPane(\r
- JSplitPane.HORIZONTAL_SPLIT, keyboardPanel, chordDiagram\r
- ) {{\r
- setOneTouchExpandable(true);\r
- setResizeWeight(1.0);\r
- setAlignmentX((float)0.5);\r
- }});\r
- add(Box.createVerticalStrut(5));\r
- add(new JPanel() {{\r
- setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));\r
- add(new JPanel() {{\r
- setLayout(new BoxLayout(this, BoxLayout.X_AXIS));\r
- add( Box.createHorizontalStrut(12) );\r
- add( keysigLabel );\r
- add( Box.createHorizontalStrut(12) );\r
- add( timesigSelecter );\r
- add( Box.createHorizontalStrut(12) );\r
- add( tempoSelecter );\r
- add( Box.createHorizontalStrut(12) );\r
- add( new SequencerMeasureView(deviceModelList.getSequencerModel()) );\r
- add( Box.createHorizontalStrut(12) );\r
- add( songTitleLabel );\r
- add( Box.createHorizontalStrut(12) );\r
- add( new JButton(deviceModelList.editorDialog.openAction) {{ setMargin(ZERO_INSETS); }});\r
- }});\r
- add(new JPanel() {{\r
- setLayout(new BoxLayout(this, BoxLayout.X_AXIS));\r
- add( Box.createHorizontalStrut(10) );\r
- add( new JSlider(deviceModelList.getSequencerModel()) );\r
- add( new SequencerTimeView(deviceModelList.getSequencerModel()) );\r
- add( Box.createHorizontalStrut(5) );\r
- add( new JButton(deviceModelList.editorDialog.sequenceListTable.getModel().moveToTopAction) {{\r
- setMargin(ZERO_INSETS);\r
- }});\r
- add(new JButton(deviceModelList.getSequencerModel().moveBackwardAction) {{\r
- setMargin(ZERO_INSETS);\r
- }});\r
- add(new JToggleButton(deviceModelList.getSequencerModel().startStopAction));\r
- add(new JButton(deviceModelList.getSequencerModel().moveForwardAction) {{\r
- setMargin(ZERO_INSETS);\r
- }});\r
- add(new JButton(deviceModelList.editorDialog.sequenceListTable.getModel().moveToBottomAction) {{\r
- setMargin(ZERO_INSETS);\r
- }});\r
- add(new JToggleButton(deviceModelList.editorDialog.sequenceListTable.getModel().toggleRepeatAction) {{\r
- setMargin(ZERO_INSETS);\r
- }});\r
- add( Box.createHorizontalStrut(10) );\r
- }});\r
- add(new JPanel() {{\r
- add(new JButton(\r
- "MIDI device connection",\r
- new ButtonIcon( ButtonIcon.MIDI_CONNECTOR_ICON )\r
- ) {{\r
- addActionListener(midiConnectionDialog);\r
- }});\r
- add(new JButton("Version info") {{\r
- setToolTipText(VersionInfo.NAME + " " + VersionInfo.VERSION);\r
- addActionListener(new AboutMessagePane());\r
- }});\r
- }});\r
- }});\r
- }};\r
- setContentPane(new JLayeredPane() {\r
- {\r
- add(anoGakkiPane = new AnoGakkiPane(), JLayeredPane.PALETTE_LAYER);\r
- addComponentListener(new ComponentAdapter() {\r
- @Override\r
- public void componentResized(ComponentEvent e) {\r
- adjustSize();\r
- }\r
- @Override\r
- public void componentShown(ComponentEvent e) {\r
- adjustSize();\r
- }\r
- private void adjustSize() {\r
- anoGakkiPane.setBounds(getBounds());\r
- }\r
- });\r
- setLayout(new BorderLayout());\r
- setOpaque(true);\r
- add(mainSplitPane = new JSplitPane(\r
- JSplitPane.VERTICAL_SPLIT,\r
- chordMatrix, keyboardSequencerPanel\r
- ){\r
- {\r
- setResizeWeight(0.5);\r
- setAlignmentX((float)0.5);\r
- setDividerSize(5);\r
- }\r
- });\r
- }\r
- });\r
- setPreferredSize(new Dimension(750,470));\r
- }\r
- @Override\r
- public void start() {\r
- //\r
- // コードボタンで設定されている現在の調を\r
- // ピアノキーボードに伝える\r
- chordMatrix.fireKeySignatureChanged();\r
- //\r
- // アプレットのパラメータにMIDIファイルのURLが指定されていたら\r
- // それを再生する\r
- String midi_url = getParameter("midi_file");\r
- System.gc();\r
- if( midi_url != null ) {\r
- addToPlaylist(midi_url);\r
- play();\r
- }\r
- }\r
- @Override\r
- public void stop() {\r
- deviceModelList.getSequencerModel().stop(); // MIDI再生を強制終了\r
- System.gc();\r
- }\r
- private void innerSetDarkMode(boolean isDark) {\r
- Color col = isDark ? Color.black : null;\r
- getContentPane().setBackground(\r
- isDark ? Color.black : rootPaneDefaultBgcolor\r
- );\r
- mainSplitPane.setBackground(col);\r
- keyboardSplitPane.setBackground(col);\r
- enterButtonLabel.setDarkMode(isDark);\r
- chordGuide.setBackground(col);\r
- lyricDisplay.setBorder(isDark ? null : lyricDisplayDefaultBorder);\r
- lyricDisplay.setBackground(isDark ?\r
- chordMatrix.darkModeColorset.backgrounds[2] :\r
- lyricDisplayDefaultBgcolor\r
- );\r
- lyricDisplay.setForeground(isDark ? Color.white : null);\r
- inversionOmissionButton.setBackground(col);\r
- anoGakkiToggleButton.setBackground(col);\r
- keyboardSequencerPanel.setBackground(col);\r
- chordDiagram.setBackground(col);\r
- chordDiagram.titleLabel.setDarkMode(isDark);\r
- chordMatrix.setDarkMode(isDark);\r
- keyboardPanel.setDarkMode(isDark);\r
- }\r
-\r
- private int[] chordOnNotes = null;\r
- /**\r
- * 和音を発音します。\r
- * <p>この関数を直接呼ぶとアルペジオが効かないので、\r
- * chord_matrix.setSelectedChord() を使うことを推奨\r
- * </p>\r
- */\r
- public void chordOn() {\r
- Chord playChord = chordMatrix.getSelectedChord();\r
- if(\r
- chordOnNotes != null &&\r
- chordMatrix.getNoteIndex() < 0 &&\r
- (! chordMatrix.isDragged() || playChord == null)\r
- ) {\r
- // コードが鳴っている状態で、新たなコードを鳴らそうとしたり、\r
- // もう鳴らさないという信号が来た場合は、今鳴っている音を止める。\r
- //\r
- for( int n : chordOnNotes )\r
- keyboardPanel.keyboardCenterPanel.keyboard.noteOff(n);\r
- chordOnNotes = null;\r
- }\r
- if( playChord == null ) {\r
- // もう鳴らさないので、歌詞表示に通知して終了\r
- if( lyricDisplay != null )\r
- lyricDisplay.appendChord(null);\r
- return;\r
- }\r
- // あの楽器っぽい表示\r
- if( keyboardPanel.keyboardCenterPanel.keyboard.anoGakkiPane != null ) {\r
- JComponent btn = chordMatrix.getSelectedButton();\r
- if( btn != null ) anoGakkiPane.start(chordMatrix, btn.getBounds());\r
- }\r
- // コードボタンからのコードを、カポつき演奏キーからオリジナルキーへ変換\r
- Key originalKey = chordMatrix.getKeySignatureCapo();\r
- Chord originalChord = playChord.clone().transpose(\r
- chordMatrix.capoSelecter.getCapo(),\r
- chordMatrix.getKeySignature()\r
- );\r
- // 変換後のコードをキーボード画面に設定\r
- keyboardPanel.keyboardCenterPanel.keyboard.setChord(originalChord);\r
- //\r
- // 音域を決める。これにより鳴らす音が確定する。\r
- Range chordRange = new Range(\r
- keyboardPanel.keyboardCenterPanel.keyboard.getChromaticOffset() + 10 +\r
- ( keyboardPanel.keyboardCenterPanel.keyboard.getOctaves() / 4 ) * 12,\r
- inversionOmissionButton.isAutoInversionMode() ?\r
- keyboardPanel.keyboardCenterPanel.keyboard.getChromaticOffset() + 21 :\r
- keyboardPanel.keyboardCenterPanel.keyboard.getChromaticOffset() + 33,\r
- -2,\r
- inversionOmissionButton.isAutoInversionMode()\r
- );\r
- int[] notes = originalChord.toNoteArray(chordRange, originalKey);\r
- //\r
- // 前回鳴らしたコード構成音を覚えておく\r
- int[] prevChordOnNotes = null;\r
- if( chordMatrix.isDragged() || chordMatrix.getNoteIndex() >= 0 )\r
- prevChordOnNotes = Arrays.copyOf(chordOnNotes, chordOnNotes.length);\r
- //\r
- // 次に鳴らす構成音を決める\r
- chordOnNotes = new int[notes.length];\r
- int i = 0;\r
- for( int n : notes ) {\r
- if( inversionOmissionButton.getOmissionNoteIndex() == i ) {\r
- i++; continue;\r
- }\r
- chordOnNotes[i++] = n;\r
- //\r
- // その音が今鳴っているか調べる\r
- boolean isNoteOn = false;\r
- if( prevChordOnNotes != null ) {\r
- for( int prevN : prevChordOnNotes ) {\r
- if( n == prevN ) {\r
- isNoteOn = true;\r
- break;\r
- }\r
- }\r
- }\r
- // すでに鳴っているのに単音を鳴らそうとする場合、\r
- // 鳴らそうとしている音を一旦止める。\r
- if( isNoteOn && chordMatrix.getNoteIndex() >= 0 &&\r
- notes[chordMatrix.getNoteIndex()] - n == 0\r
- ) {\r
- keyboardPanel.keyboardCenterPanel.keyboard.noteOff(n);\r
- isNoteOn = false;\r
- }\r
- // その音が鳴っていなかったら鳴らす。\r
- if( ! isNoteOn )\r
- keyboardPanel.keyboardCenterPanel.keyboard.noteOn(n);\r
- }\r
- //\r
- // コードを表示\r
- keyboardPanel.keyboardCenterPanel.keyboard.setChord(originalChord);\r
- chordMatrix.chordDisplay.setChord(playChord);\r
- //\r
- // コードダイアグラム用にもコードを表示\r
- Chord diagramChord;\r
- int chordDiagramCapo = chordDiagram.capoSelecterView.getCapo();\r
- if( chordDiagramCapo == chordMatrix.capoSelecter.getCapo() )\r
- diagramChord = playChord.clone();\r
- else\r
- diagramChord = originalChord.clone().transpose(\r
- - chordDiagramCapo, originalKey\r
- );\r
- chordDiagram.setChord(diagramChord);\r
- if( chordDiagram.recordTextButton.isSelected() )\r
- lyricDisplay.appendChord(diagramChord);\r
- }\r
-\r
-}\r
-\r
+package camidion.chordhelper;
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Desktop;
+import java.awt.Dimension;
+import java.awt.Image;
+import java.awt.Insets;
+import java.awt.dnd.DnDConstants;
+import java.awt.dnd.DropTarget;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ComponentAdapter;
+import java.awt.event.ComponentEvent;
+import java.awt.event.InputEvent;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.security.AccessControlException;
+import java.util.Arrays;
+
+import javax.sound.midi.InvalidMidiDataException;
+import javax.sound.midi.MetaEventListener;
+import javax.sound.midi.MetaMessage;
+import javax.sound.midi.Sequence;
+import javax.sound.midi.Sequencer;
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+import javax.swing.Box;
+import javax.swing.BoxLayout;
+import javax.swing.ImageIcon;
+import javax.swing.JApplet;
+import javax.swing.JButton;
+import javax.swing.JComponent;
+import javax.swing.JEditorPane;
+import javax.swing.JLabel;
+import javax.swing.JLayeredPane;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JSlider;
+import javax.swing.JSplitPane;
+import javax.swing.JToggleButton;
+import javax.swing.SwingUtilities;
+import javax.swing.border.Border;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import javax.swing.event.HyperlinkEvent;
+import javax.swing.event.HyperlinkListener;
+
+import camidion.chordhelper.anogakki.AnoGakkiPane;
+import camidion.chordhelper.chorddiagram.CapoComboBoxModel;
+import camidion.chordhelper.chorddiagram.ChordDiagram;
+import camidion.chordhelper.chordmatrix.ChordButtonLabel;
+import camidion.chordhelper.chordmatrix.ChordMatrix;
+import camidion.chordhelper.chordmatrix.ChordMatrixListener;
+import camidion.chordhelper.mididevice.MidiDeviceDialog;
+import camidion.chordhelper.mididevice.MidiSequencerModel;
+import camidion.chordhelper.mididevice.MidiTransceiverListModelList;
+import camidion.chordhelper.mididevice.SequencerMeasureView;
+import camidion.chordhelper.mididevice.SequencerTimeView;
+import camidion.chordhelper.mididevice.VirtualMidiDevice;
+import camidion.chordhelper.midieditor.Base64Dialog;
+import camidion.chordhelper.midieditor.KeySignatureLabel;
+import camidion.chordhelper.midieditor.MidiSequenceEditor;
+import camidion.chordhelper.midieditor.NewSequenceDialog;
+import camidion.chordhelper.midieditor.PlaylistTableModel;
+import camidion.chordhelper.midieditor.SequenceTickIndex;
+import camidion.chordhelper.midieditor.SequenceTrackListTableModel;
+import camidion.chordhelper.midieditor.TempoSelecter;
+import camidion.chordhelper.midieditor.TimeSignatureSelecter;
+import camidion.chordhelper.music.Chord;
+import camidion.chordhelper.music.Key;
+import camidion.chordhelper.music.Range;
+import camidion.chordhelper.pianokeyboard.MidiKeyboardPanel;
+import camidion.chordhelper.pianokeyboard.PianoKeyboardAdapter;
+
+/**
+ * MIDI Chord Helper - Circle-of-fifth oriented chord pad
+ * (アプレットクラス)
+ *
+ * @auther
+ * Copyright (C) 2004-2016 @きよし - Akiyoshi Kamide
+ * http://www.yk.rim.or.jp/~kamide/music/chordhelper/
+ */
+public class ChordHelperApplet extends JApplet {
+ /////////////////////////////////////////////////////////////////////
+ //
+ // JavaScript などからの呼び出しインターフェース
+ //
+ /////////////////////////////////////////////////////////////////////
+ /**
+ * 未保存の修正済み MIDI ファイルがあるかどうか調べます。
+ * @return 未保存の修正済み MIDI ファイルがあれば true
+ */
+ public boolean isModified() { return playlistModel.isModified(); }
+ /**
+ * 指定された小節数の曲を、乱数で自動作曲してプレイリストへ追加します。
+ * @param measureLength 小節数
+ * @return 追加先のインデックス値(0から始まる)。追加できなかったときは -1
+ * @throws InvalidMidiDataException {@link Sequencer#setSequence(Sequence)} を参照
+ */
+ public int addRandomSongToPlaylist(int measureLength) throws InvalidMidiDataException {
+ NewSequenceDialog d = midiEditor.newSequenceDialog;
+ d.setRandomChordProgression(measureLength);
+ return playlistModel.addSequenceAndPlay(d.getMidiSequence());
+ }
+ /**
+ * URLで指定されたMIDIファイルをプレイリストへ追加します。
+ *
+ * <p>URL の最後の / より後ろの部分がファイル名として取り込まれます。
+ * 指定できる MIDI ファイルには、param タグの midi_file パラメータと同様の制限があります。
+ * </p>
+ * @param midiFileUrl 追加するMIDIファイルのURL
+ * @return 追加先のインデックス値(0から始まる)。追加できなかったときは -1
+ */
+ public int addToPlaylist(String midiFileUrl) {
+ try {
+ return playlistModel.addSequenceFromURL(midiFileUrl);
+ } catch( URISyntaxException|IOException|InvalidMidiDataException e ) {
+ midiEditor.showWarning(e.getMessage());
+ } catch( AccessControlException e ) {
+ e.printStackTrace();
+ midiEditor.showError(e.getMessage());
+ }
+ return -1;
+ }
+ /**
+ * Base64 エンコードされた MIDI ファイルをプレイリストへ追加します。
+ *
+ * @param base64EncodedText Base64エンコードされたMIDIファイル
+ * @return 追加先のインデックス値(0から始まる)。追加できなかったときは -1
+ */
+ public int addToPlaylistBase64(String base64EncodedText) {
+ return addToPlaylistBase64(base64EncodedText, null);
+ }
+ /**
+ * ファイル名を指定して、
+ * Base64エンコードされたMIDIファイルをプレイリストへ追加します。
+ *
+ * @param base64EncodedText Base64エンコードされたMIDIファイル
+ * @param filename ディレクトリ名を除いたファイル名
+ * @return 追加先のインデックス値(0から始まる)。追加できなかったときは -1
+ */
+ public int addToPlaylistBase64(String base64EncodedText, String filename) {
+ Base64Dialog d = midiEditor.base64Dialog;
+ d.setBase64Data(base64EncodedText);
+ try {
+ return playlistModel.addSequence(d.getMIDIData(), filename);
+ } catch (IOException | InvalidMidiDataException e) {
+ e.printStackTrace();
+ midiEditor.showWarning(e.getMessage());
+ return -1;
+ }
+ }
+ /**
+ * プレイリスト上で現在選択されているMIDIシーケンスを、
+ * シーケンサへロードして再生します。
+ * @throws InvalidMidiDataException {@link Sequencer#setSequence(Sequence)} を参照
+ */
+ public void play() throws InvalidMidiDataException { play(playlistModel.sequenceListSelectionModel.getMinSelectionIndex()); }
+ /**
+ * 指定されたインデックス値が示すプレイリスト上のMIDIシーケンスを、
+ * シーケンサへロードして再生します。
+ * @param index インデックス値(0から始まる)
+ * @throws InvalidMidiDataException {@link Sequencer#setSequence(Sequence)} を参照
+ */
+ public void play(int index) throws InvalidMidiDataException {
+ playlistModel.loadToSequencer(index); sequencerModel.start();
+ }
+ /**
+ * シーケンサが実行中かどうかを返します。
+ * {@link Sequencer#isRunning()} の戻り値をそのまま返します。
+ *
+ * @return 実行中のときtrue
+ */
+ public boolean isRunning() { return sequencerModel.getSequencer().isRunning(); }
+ /**
+ * シーケンサが再生中かどうかを返します。
+ * @return 再生中のときtrue
+ */
+ public boolean isPlaying() { return isRunning(); }
+ /**
+ * 現在シーケンサにロードされているMIDIデータを
+ * Base64テキストに変換した結果を返します。
+ * @return MIDIデータをBase64テキストに変換した結果
+ */
+ public String getMidiDataBase64() {
+ SequenceTrackListTableModel sequenceModel = sequencerModel.getSequenceTrackListTableModel();
+ midiEditor.base64Dialog.setMIDIData(sequenceModel.getMIDIdata());
+ return midiEditor.base64Dialog.getBase64Data();
+ }
+ /**
+ * 現在シーケンサにロードされているMIDIファイルのファイル名を返します。
+ * @return MIDIファイル名(設定されていないときは空文字列)
+ */
+ public String getMidiFilename() {
+ SequenceTrackListTableModel seq_model = sequencerModel.getSequenceTrackListTableModel();
+ if( seq_model == null ) return null;
+ String fn = seq_model.getFilename();
+ return fn == null ? "" : fn ;
+ }
+ /**
+ * オクターブ位置を設定します。
+ * @param octavePosition オクターブ位置(デフォルト:4)
+ */
+ public void setOctavePosition(int octavePosition) {
+ keyboardPanel.keyboardCenterPanel.keyboard.octaveRangeModel.setValue(octavePosition);
+ }
+ /**
+ * 操作対象のMIDIチャンネルを変更します。
+ * @param ch チャンネル番号 - 1(チャンネル1のとき0、デフォルトは0)
+ */
+ public void setChannel(int ch) {
+ keyboardPanel.keyboardCenterPanel.keyboard.midiChComboboxModel.setSelectedChannel(ch);
+ }
+ /**
+ * 操作対象のMIDIチャンネルを返します。
+ * @return 操作対象のMIDIチャンネル
+ */
+ public int getChannel() {
+ return keyboardPanel.keyboardCenterPanel.keyboard.midiChComboboxModel.getSelectedChannel();
+ }
+ /**
+ * 操作対象のMIDIチャンネルに対してプログラム(音色)を設定します。
+ * @param program 音色(0~127:General MIDI に基づく)
+ */
+ public void programChange(int program) {
+ keyboardPanel.keyboardCenterPanel.keyboard.getSelectedChannel().programChange(program);
+ }
+ /**
+ * 操作対象のMIDIチャンネルに対してプログラム(音色)を設定します。
+ * 内部的には {@link #programChange(int)} を呼び出しているだけです。
+ * @param program 音色(0~127:General MIDI に基づく)
+ */
+ public void setProgram(int program) { programChange(program); }
+ /**
+ * 自動転回モードを変更します。初期値は true です。
+ * @param isAuto true:自動転回を行う false:自動転回を行わない
+ */
+ public void setAutoInversion(boolean isAuto) {
+ inversionOmissionButton.setAutoInversion(isAuto);
+ }
+ /**
+ * 省略したい構成音を指定します。
+ * @param index
+ * <ul>
+ * <li>-1:省略しない(デフォルト)</li>
+ * <li>0:ルート音を省略</li>
+ * <li>1:三度を省略</li>
+ * <li>2:五度を省略</li>
+ * </ul>
+ */
+ public void setOmissionNoteIndex(int index) {
+ inversionOmissionButton.setOmissionNoteIndex(index);
+ }
+ /**
+ * コードダイアグラムの表示・非表示を切り替えます。
+ * @param isVisible 表示するときtrue
+ */
+ public void setChordDiagramVisible(boolean isVisible) {
+ keyboardSplitPane.resetToPreferredSizes();
+ if( ! isVisible )
+ keyboardSplitPane.setDividerLocation((double)1.0);
+ }
+ /**
+ * コードダイヤグラムをギターモードに変更します。
+ * 初期状態ではウクレレモードになっています。
+ */
+ public void setChordDiagramForGuitar() {
+ chordDiagram.setTargetInstrument(ChordDiagram.Instrument.Guitar);
+ }
+ /**
+ * ダークモード(暗い表示)と明るい表示とを切り替えます。
+ * @param isDark ダークモードのときtrue、明るい表示のときfalse(デフォルト)
+ */
+ public void setDarkMode(boolean isDark) {
+ darkModeToggleButton.setSelected(isDark);
+ }
+ /**
+ * バージョン情報
+ */
+ public static class VersionInfo {
+ public static final String NAME = "MIDI Chord Helper";
+ public static final String VERSION = "Ver.20160612.1";
+ public static final String COPYRIGHT = "Copyright (C) 2004-2016";
+ public static final String AUTHER = "@きよし - Akiyoshi Kamide";
+ public static final String URL = "http://www.yk.rim.or.jp/~kamide/music/chordhelper/";
+ /**
+ * バージョン情報を返します。
+ * @return バージョン情報
+ */
+ public static String getInfo() {
+ return NAME + " " + VERSION + " " + COPYRIGHT + " " + AUTHER + " " + URL;
+ }
+ }
+ @Override
+ public String getAppletInfo() { return VersionInfo.getInfo(); }
+ private class AboutMessagePane extends JEditorPane {
+ URI uri = null;
+ public AboutMessagePane() { this(true); }
+ public AboutMessagePane(boolean link_enabled) {
+ super( "text/html", "" );
+ String link_string, tooltip = null;
+ if( link_enabled && Desktop.isDesktopSupported() ) {
+ tooltip = "Click this URL to open with your web browser - URLをクリックしてWebブラウザで開く";
+ link_string =
+ "<a href=\"" + VersionInfo.URL + "\" title=\"" +
+ tooltip + "\">" + VersionInfo.URL + "</a>" ;
+ }
+ else {
+ link_enabled = false; link_string = VersionInfo.URL;
+ }
+ setText(
+ "<html><center><font size=\"+1\">" + VersionInfo.NAME + "</font> " +
+ VersionInfo.VERSION + "<br/><br/>" +
+ VersionInfo.COPYRIGHT + " " + VersionInfo.AUTHER + "<br/>" +
+ link_string + "</center></html>"
+ );
+ setToolTipText(tooltip);
+ setOpaque(false);
+ putClientProperty(JEditorPane.HONOR_DISPLAY_PROPERTIES, Boolean.TRUE);
+ setEditable(false);
+ //
+ // メッセージ内の <a href=""> ~ </a> によるリンクを
+ // 実際に機能させる(ブラウザで表示されるようにする)ための設定
+ //
+ if( ! link_enabled ) return;
+ try {
+ uri = new URI(VersionInfo.URL);
+ }catch( URISyntaxException use ) {
+ use.printStackTrace();
+ return;
+ }
+ addHyperlinkListener(new HyperlinkListener() {
+ public void hyperlinkUpdate(HyperlinkEvent e) {
+ if(e.getEventType()==HyperlinkEvent.EventType.ACTIVATED) {
+ try{
+ Desktop.getDesktop().browse(uri);
+ }catch(IOException ioe) {
+ ioe.printStackTrace();
+ }
+ }
+ }
+ });
+ }
+ /**
+ * バージョン情報を開くアクション
+ */
+ public Action openAction = new AbstractAction() {
+ {
+ putValue(NAME, "Version info");
+ putValue(SHORT_DESCRIPTION, VersionInfo.NAME + " " + VersionInfo.VERSION);
+ }
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ JOptionPane.showMessageDialog(
+ null, AboutMessagePane.this, getValue(NAME).toString(),
+ JOptionPane.INFORMATION_MESSAGE, imageIcon
+ );
+ }
+ };
+ }
+ /**
+ * アプリケーションのイメージアイコン
+ */
+ public ImageIcon imageIcon;
+ /**
+ * アプリケーションのアイコンイメージ
+ */
+ public Image iconImage;
+ /**
+ * ボタンの余白を詰めたいときに setMargin() の引数に指定するインセット
+ */
+ public static final Insets ZERO_INSETS = new Insets(0,0,0,0);
+ //
+ private static final String IMAGE_ICON_PATH = "midichordhelper.png";
+ //
+ MidiSequenceEditor midiEditor;
+ PlaylistTableModel playlistModel;
+ MidiSequencerModel sequencerModel;
+ public ChordMatrix chordMatrix;
+ private JPanel keyboardSequencerPanel;
+ private JPanel chordGuide;
+ private Color rootPaneDefaultBgcolor;
+ private Color lyricDisplayDefaultBgcolor;
+ private Border lyricDisplayDefaultBorder;
+ private JSplitPane mainSplitPane;
+ private JSplitPane keyboardSplitPane;
+ private ChordButtonLabel enterButtonLabel;
+ private ChordTextField lyricDisplay;
+ private MidiKeyboardPanel keyboardPanel;
+ private InversionAndOmissionLabel inversionOmissionButton;
+ private JToggleButton darkModeToggleButton;
+ private MidiDeviceDialog midiDeviceDialog;
+ private ChordDiagram chordDiagram;
+ private TempoSelecter tempoSelecter;
+ private TimeSignatureSelecter timesigSelecter;
+ private KeySignatureLabel keysigLabel;
+ private JLabel songTitleLabel = new JLabel();
+ private AnoGakkiPane anoGakkiPane;
+ private JToggleButton anoGakkiToggleButton;
+ private MidiTransceiverListModelList deviceModelList;
+
+ public void init() {
+ //
+ // アイコンイメージの取得
+ URL imageIconUrl = getClass().getResource(IMAGE_ICON_PATH);
+ if( imageIconUrl == null ) {
+ System.out.println("Icon image "+IMAGE_ICON_PATH+" not found");
+ }
+ else {
+ iconImage = (imageIcon = new ImageIcon(imageIconUrl)).getImage();
+ }
+ // 背景色の取得
+ rootPaneDefaultBgcolor = getContentPane().getBackground();
+ //
+ // コードダイアグラム、コードボタン、ピアノ鍵盤のセットアップ
+ CapoComboBoxModel capoValueModel = new CapoComboBoxModel();
+ chordDiagram = new ChordDiagram(capoValueModel);
+ chordMatrix = new ChordMatrix(capoValueModel) {{
+ addChordMatrixListener(new ChordMatrixListener(){
+ public void keySignatureChanged() {
+ Key capoKey = getKeySignatureCapo();
+ keyboardPanel.keySelecter.setKey(capoKey);
+ keyboardPanel.keyboardCenterPanel.keyboard.setKeySignature(capoKey);
+ }
+ public void chordChanged() { chordOn(); }
+ });
+ capoSelecter.checkbox.addItemListener(new ItemListener() {
+ public void itemStateChanged(ItemEvent e) {
+ chordOn();
+ keyboardPanel.keyboardCenterPanel.keyboard.chordDisplay.clear();
+ chordDiagram.clear();
+ }
+ });
+ capoSelecter.valueSelecter.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ chordOn();
+ keyboardPanel.keyboardCenterPanel.keyboard.chordDisplay.clear();
+ chordDiagram.clear();
+ }
+ });
+ }};
+ keysigLabel = new KeySignatureLabel() {{
+ addMouseListener(new MouseAdapter() {
+ public void mousePressed(MouseEvent e) { chordMatrix.setKeySignature(getKey()); }
+ });
+ }};
+ keyboardPanel = new MidiKeyboardPanel(chordMatrix) {{
+ keyboardCenterPanel.keyboard.addPianoKeyboardListener(new PianoKeyboardAdapter() {
+ @Override
+ public void pianoKeyPressed(int n, InputEvent e) { chordDiagram.clear(); }
+ });
+ keySelecter.keysigCombobox.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ Key key = keySelecter.getKey();
+ key.transpose( - chordMatrix.capoSelecter.getCapo() );
+ chordMatrix.setKeySignature(key);
+ }
+ });
+ keyboardCenterPanel.keyboard.setPreferredSize(new Dimension(571, 80));
+ }};
+ VirtualMidiDevice guiMidiDevice = keyboardPanel.keyboardCenterPanel.keyboard.midiDevice;
+ //
+ // MIDIデバイス一覧を構築
+ deviceModelList = new MidiTransceiverListModelList(Arrays.asList(guiMidiDevice));
+ (midiDeviceDialog = new MidiDeviceDialog(deviceModelList)).setIconImage(iconImage);
+ //
+ // MIDIデバイス一覧のシーケンサと連携するプレイリストを構築
+ playlistModel = new PlaylistTableModel(sequencerModel = deviceModelList.getSequencerModel());
+ //
+ // MIDIエディタダイアログの構築
+ (midiEditor = new MidiSequenceEditor(playlistModel, guiMidiDevice)).setIconImage(iconImage);
+ //
+ // メイン画面へのMIDIファイルのドラッグ&ドロップ受付開始
+ new DropTarget(this, DnDConstants.ACTION_COPY_OR_MOVE, midiEditor.dropTargetListener, true);
+ //
+ // MIDIエディタのイベントダイアログを、ピアノ鍵盤のイベント送出ダイアログと共用
+ keyboardPanel.setEventDialog(midiEditor.eventDialog);
+ //
+ // 歌詞表示
+ lyricDisplay = new ChordTextField(sequencerModel) {{
+ addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent event) {
+ String symbol = event.getActionCommand().trim().split("[ \t\r\n]")[0];
+ chordMatrix.setSelectedChord(symbol);
+ }
+ });
+ }};
+ lyricDisplayDefaultBorder = lyricDisplay.getBorder();
+ lyricDisplayDefaultBgcolor = lyricDisplay.getBackground();
+ //
+ // メタイベント(テンポ・拍子・調号)を受信して表示するリスナーを登録
+ Sequencer sequencer = sequencerModel.getSequencer();
+ sequencer.addMetaEventListener(tempoSelecter = new TempoSelecter() {{ setEditable(false); }});
+ sequencer.addMetaEventListener(timesigSelecter = new TimeSignatureSelecter() {{ setEditable(false); }});
+ sequencer.addMetaEventListener(new MetaEventListener() {
+ private Key key;
+ @Override
+ public void meta(MetaMessage msg) {
+ switch(msg.getType()) {
+ case 0x59: // Key signature (2 bytes) : 調号
+ key = new Key(msg.getData());
+ if( SwingUtilities.isEventDispatchThread() ) {
+ keysigLabel.setKeySignature(key);
+ chordMatrix.setKeySignature(key);
+ } else {
+ // MIDIシーケンサのスレッドから呼ばれた場合、GUI更新は自分で行わず、
+ // AWTイベントディスパッチスレッドに依頼する。
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ keysigLabel.setKeySignature(key);
+ chordMatrix.setKeySignature(key);
+ }
+ });
+ }
+ break;
+ }
+ }
+ });
+ //シーケンサーの時間スライダーの値が変わったときのリスナーを登録
+ sequencerModel.addChangeListener(new ChangeListener() {
+ @Override
+ public void stateChanged(ChangeEvent e) {
+ SequenceTrackListTableModel sequenceTableModel = sequencerModel.getSequenceTrackListTableModel();
+ int loadedSequenceIndex = playlistModel.indexOfSequenceOnSequencer();
+ songTitleLabel.setText(
+ "<html>"+(
+ loadedSequenceIndex < 0 ? "[No MIDI file loaded]" :
+ "MIDI file " + loadedSequenceIndex + ": " + (
+ sequenceTableModel == null ||
+ sequenceTableModel.toString() == null ||
+ sequenceTableModel.toString().isEmpty() ?
+ "[Untitled]" :
+ "<font color=maroon>"+sequenceTableModel+"</font>"
+ )
+ )+"</html>"
+ );
+ Sequencer sequencer = sequencerModel.getSequencer();
+ chordMatrix.setPlaying(sequencer.isRunning());
+ if( sequenceTableModel != null ) {
+ SequenceTickIndex tickIndex = sequenceTableModel.getSequenceTickIndex();
+ long tickPos = sequencer.getTickPosition();
+ tickIndex.tickToMeasure(tickPos);
+ chordMatrix.setBeat(tickIndex);
+ if( sequencerModel.getValueIsAdjusting() || ! (sequencer.isRunning() || sequencer.isRecording()) ) {
+ MetaMessage msg;
+ msg = tickIndex.lastMetaMessageAt(
+ SequenceTickIndex.MetaMessageType.TIME_SIGNATURE, tickPos
+ );
+ timesigSelecter.setValue(msg==null ? null : msg.getData());
+ msg = tickIndex.lastMetaMessageAt(
+ SequenceTickIndex.MetaMessageType.TEMPO, tickPos
+ );
+ tempoSelecter.setTempo(msg==null ? null : msg.getData());
+ msg = tickIndex.lastMetaMessageAt(
+ SequenceTickIndex.MetaMessageType.KEY_SIGNATURE, tickPos
+ );
+ if( msg == null ) {
+ keysigLabel.clear();
+ }
+ else {
+ Key key = new Key(msg.getData());
+ keysigLabel.setKeySignature(key);
+ chordMatrix.setKeySignature(key);
+ }
+ }
+ }
+ }
+ });
+ sequencerModel.fireStateChanged();
+ chordGuide = new JPanel() {
+ {
+ setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
+ add( Box.createHorizontalStrut(2) );
+ add( chordMatrix.chordGuide );
+ add( Box.createHorizontalStrut(2) );
+ add( lyricDisplay );
+ add( Box.createHorizontalStrut(2) );
+ add( enterButtonLabel = new ChordButtonLabel("Enter",chordMatrix) {{
+ addMouseListener(new MouseAdapter() {
+ public void mousePressed(MouseEvent event) {
+ if( (event.getModifiersEx() & InputEvent.BUTTON3_DOWN_MASK) != 0 ) // RightClicked
+ chordMatrix.setSelectedChord((Chord)null);
+ else {
+ chordMatrix.setSelectedChord(lyricDisplay.getText());
+ }
+ }
+ });
+ }});
+ add( Box.createHorizontalStrut(5) );
+ add( chordMatrix.chordDisplay );
+ add( Box.createHorizontalStrut(5) );
+ add( darkModeToggleButton = new JToggleButton(new ButtonIcon(ButtonIcon.DARK_MODE_ICON)) {{
+ setMargin(ZERO_INSETS);
+ addItemListener(new ItemListener() {
+ public void itemStateChanged(ItemEvent e) {
+ innerSetDarkMode(darkModeToggleButton.isSelected());
+ }
+ });
+ setToolTipText("Light / Dark - 明かりを点灯/消灯");
+ setBorder(null);
+ }});
+ add( Box.createHorizontalStrut(5) );
+ add( anoGakkiToggleButton = new JToggleButton(new ButtonIcon(ButtonIcon.ANO_GAKKI_ICON)) {{
+ setOpaque(false);
+ setMargin(ZERO_INSETS);
+ setBorder( null );
+ setToolTipText("あの楽器");
+ addItemListener(new ItemListener() {
+ public void itemStateChanged(ItemEvent e) {
+ keyboardPanel.keyboardCenterPanel.keyboard.anoGakkiPane
+ = anoGakkiToggleButton.isSelected() ? anoGakkiPane : null ;
+ }
+ });
+ }} );
+ add( Box.createHorizontalStrut(5) );
+ add( inversionOmissionButton = new InversionAndOmissionLabel() );
+ add( Box.createHorizontalStrut(5) );
+ add( chordMatrix.capoSelecter );
+ add( Box.createHorizontalStrut(2) );
+ }
+ };
+ keyboardSequencerPanel = new JPanel() {{
+ setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
+ add(chordGuide);
+ add(Box.createVerticalStrut(5));
+ add(keyboardSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, keyboardPanel, chordDiagram) {{
+ setOneTouchExpandable(true);
+ setResizeWeight(1.0);
+ setAlignmentX((float)0.5);
+ }});
+ add(Box.createVerticalStrut(5));
+ add(new JPanel() {{
+ setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
+ add(new JPanel() {{
+ setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
+ add( Box.createHorizontalStrut(12) );
+ add( keysigLabel );
+ add( Box.createHorizontalStrut(12) );
+ add( timesigSelecter );
+ add( Box.createHorizontalStrut(12) );
+ add( tempoSelecter );
+ add( Box.createHorizontalStrut(12) );
+ add( new SequencerMeasureView(sequencerModel) );
+ add( Box.createHorizontalStrut(12) );
+ add( songTitleLabel );
+ add( Box.createHorizontalStrut(12) );
+ add( new JButton(midiEditor.openAction) {{ setMargin(ZERO_INSETS); }});
+ }});
+ add(new JPanel() {{
+ setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
+ add(Box.createHorizontalStrut(10));
+ add(new JSlider(sequencerModel));
+ add(new SequencerTimeView(sequencerModel));
+ add(Box.createHorizontalStrut(5));
+ add(new JButton(playlistModel.moveToTopAction) {{ setMargin(ZERO_INSETS); }});
+ add(new JButton(sequencerModel.moveBackwardAction) {{ setMargin(ZERO_INSETS); }});
+ add(new JToggleButton(sequencerModel.startStopAction));
+ add(new JButton(sequencerModel.moveForwardAction) {{ setMargin(ZERO_INSETS); }});
+ add(new JButton(playlistModel.moveToBottomAction) {{ setMargin(ZERO_INSETS); }});
+ add(new JToggleButton(playlistModel.toggleRepeatAction) {{ setMargin(ZERO_INSETS); }});
+ add( Box.createHorizontalStrut(10) );
+ }});
+ add(new JPanel() {{
+ add(new JButton(midiDeviceDialog.openAction));
+ add(new JButton((new AboutMessagePane()).openAction));
+ }});
+ }});
+ }};
+ setContentPane(new JLayeredPane() {
+ {
+ add(anoGakkiPane = new AnoGakkiPane(), JLayeredPane.PALETTE_LAYER);
+ addComponentListener(new ComponentAdapter() {
+ @Override
+ public void componentResized(ComponentEvent e) { adjustSize(); }
+ @Override
+ public void componentShown(ComponentEvent e) { adjustSize(); }
+ private void adjustSize() { anoGakkiPane.setBounds(getBounds()); }
+ });
+ setLayout(new BorderLayout());
+ setOpaque(true);
+ add(mainSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, chordMatrix, keyboardSequencerPanel){
+ {
+ setResizeWeight(0.5);
+ setAlignmentX((float)0.5);
+ setDividerSize(5);
+ }
+ });
+ }
+ });
+ setPreferredSize(new Dimension(750,470));
+ }
+ @Override
+ public void destroy() {
+ deviceModelList.closeAllDevices();
+ super.destroy();
+ }
+ @Override
+ public void start() {
+ //
+ // コードボタンで設定されている現在の調を
+ // ピアノキーボードに伝える
+ chordMatrix.fireKeySignatureChanged();
+ //
+ // アプレットのパラメータにMIDIファイルのURLが指定されていたら
+ // それを再生する
+ String midi_url = getParameter("midi_file");
+ System.gc();
+ if( midi_url != null ) {
+ addToPlaylist(midi_url);
+ try {
+ play();
+ } catch (InvalidMidiDataException ex) {
+ ex.printStackTrace();
+ }
+ }
+ }
+ @Override
+ public void stop() {
+ sequencerModel.stop(); // MIDI再生を強制終了
+ System.gc();
+ }
+ private void innerSetDarkMode(boolean isDark) {
+ Color col = isDark ? Color.black : null;
+ getContentPane().setBackground(isDark ? Color.black : rootPaneDefaultBgcolor);
+ mainSplitPane.setBackground(col);
+ keyboardSplitPane.setBackground(col);
+ enterButtonLabel.setDarkMode(isDark);
+ chordGuide.setBackground(col);
+ lyricDisplay.setBorder(isDark ? null : lyricDisplayDefaultBorder);
+ lyricDisplay.setBackground(isDark ?
+ chordMatrix.darkModeColorset.backgrounds[2] :
+ lyricDisplayDefaultBgcolor
+ );
+ lyricDisplay.setForeground(isDark ? Color.white : null);
+ inversionOmissionButton.setBackground(col);
+ anoGakkiToggleButton.setBackground(col);
+ keyboardSequencerPanel.setBackground(col);
+ chordDiagram.setBackground(col);
+ chordDiagram.titleLabel.setDarkMode(isDark);
+ chordMatrix.setDarkMode(isDark);
+ keyboardPanel.setDarkMode(isDark);
+ }
+
+ private int[] chordOnNotes = null;
+ /**
+ * 和音を発音します。
+ * <p>この関数を直接呼ぶとアルペジオが効かないので、
+ * chord_matrix.setSelectedChord() を使うことを推奨
+ * </p>
+ */
+ public void chordOn() {
+ Chord playChord = chordMatrix.getSelectedChord();
+ if(
+ chordOnNotes != null &&
+ chordMatrix.getNoteIndex() < 0 &&
+ (! chordMatrix.isDragged() || playChord == null)
+ ) {
+ // コードが鳴っている状態で、新たなコードを鳴らそうとしたり、
+ // もう鳴らさないという信号が来た場合は、今鳴っている音を止める。
+ //
+ for( int n : chordOnNotes )
+ keyboardPanel.keyboardCenterPanel.keyboard.noteOff(n);
+ chordOnNotes = null;
+ }
+ if( playChord == null ) {
+ // もう鳴らさないので、歌詞表示に通知して終了
+ if( lyricDisplay != null )
+ lyricDisplay.appendChord(null);
+ return;
+ }
+ // あの楽器っぽい表示
+ if( keyboardPanel.keyboardCenterPanel.keyboard.anoGakkiPane != null ) {
+ JComponent btn = chordMatrix.getSelectedButton();
+ if( btn != null ) anoGakkiPane.start(chordMatrix, btn.getBounds());
+ }
+ // コードボタンからのコードを、カポつき演奏キーからオリジナルキーへ変換
+ Key originalKey = chordMatrix.getKeySignatureCapo();
+ Chord originalChord = playChord.clone().transpose(
+ chordMatrix.capoSelecter.getCapo(),
+ chordMatrix.getKeySignature()
+ );
+ // 変換後のコードをキーボード画面に設定
+ keyboardPanel.keyboardCenterPanel.keyboard.setChord(originalChord);
+ //
+ // 音域を決める。これにより鳴らす音が確定する。
+ Range chordRange = new Range(
+ keyboardPanel.keyboardCenterPanel.keyboard.getChromaticOffset() + 10 +
+ ( keyboardPanel.keyboardCenterPanel.keyboard.getOctaves() / 4 ) * 12,
+ inversionOmissionButton.isAutoInversionMode() ?
+ keyboardPanel.keyboardCenterPanel.keyboard.getChromaticOffset() + 21 :
+ keyboardPanel.keyboardCenterPanel.keyboard.getChromaticOffset() + 33,
+ -2,
+ inversionOmissionButton.isAutoInversionMode()
+ );
+ int[] notes = originalChord.toNoteArray(chordRange, originalKey);
+ //
+ // 前回鳴らしたコード構成音を覚えておく
+ int[] prevChordOnNotes = null;
+ if( chordMatrix.isDragged() || chordMatrix.getNoteIndex() >= 0 )
+ prevChordOnNotes = Arrays.copyOf(chordOnNotes, chordOnNotes.length);
+ //
+ // 次に鳴らす構成音を決める
+ chordOnNotes = new int[notes.length];
+ int i = 0;
+ for( int n : notes ) {
+ if( inversionOmissionButton.getOmissionNoteIndex() == i ) {
+ i++; continue;
+ }
+ chordOnNotes[i++] = n;
+ //
+ // その音が今鳴っているか調べる
+ boolean isNoteOn = false;
+ if( prevChordOnNotes != null ) {
+ for( int prevN : prevChordOnNotes ) {
+ if( n == prevN ) {
+ isNoteOn = true;
+ break;
+ }
+ }
+ }
+ // すでに鳴っているのに単音を鳴らそうとする場合、
+ // 鳴らそうとしている音を一旦止める。
+ if( isNoteOn && chordMatrix.getNoteIndex() >= 0 &&
+ notes[chordMatrix.getNoteIndex()] - n == 0
+ ) {
+ keyboardPanel.keyboardCenterPanel.keyboard.noteOff(n);
+ isNoteOn = false;
+ }
+ // その音が鳴っていなかったら鳴らす。
+ if( ! isNoteOn )
+ keyboardPanel.keyboardCenterPanel.keyboard.noteOn(n);
+ }
+ //
+ // コードを表示
+ keyboardPanel.keyboardCenterPanel.keyboard.setChord(originalChord);
+ chordMatrix.chordDisplay.setChord(playChord);
+ //
+ // コードダイアグラム用にもコードを表示
+ Chord diagramChord;
+ int chordDiagramCapo = chordDiagram.capoSelecterView.getCapo();
+ if( chordDiagramCapo == chordMatrix.capoSelecter.getCapo() )
+ diagramChord = playChord.clone();
+ else
+ diagramChord = originalChord.clone().transpose(
+ - chordDiagramCapo, originalKey
+ );
+ chordDiagram.setChord(diagramChord);
+ if( chordDiagram.recordTextButton.isSelected() )
+ lyricDisplay.appendChord(diagramChord);
+ }
+
+}
+