OSDN Git Service

・文字コード判定方法の改善
[midichordhelper/MIDIChordHelper.git] / src / camidion / chordhelper / ChordHelperApplet.java
index 22f7a85..d27db2e 100644 (file)
-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);
+       }
+
+}
+