OSDN Git Service

MIDIデバイス接続画面の変更
authorAkiyoshi Kamide <kamide@yk.rim.or.jp>
Mon, 10 Oct 2016 14:54:38 +0000 (23:54 +0900)
committerAkiyoshi Kamide <kamide@yk.rim.or.jp>
Mon, 10 Oct 2016 14:54:38 +0000 (23:54 +0900)
・デバイスツリーモデルを中心とした内部処理の整理
・接続済みデバイスの表示間隔を広げて枝分かれケーブルを見やすくした

src/camidion/chordhelper/ChordHelperApplet.java
src/camidion/chordhelper/mididevice/MidiDeviceDesktopPane.java
src/camidion/chordhelper/mididevice/MidiDeviceDialog.java
src/camidion/chordhelper/mididevice/MidiDeviceFrame.java
src/camidion/chordhelper/mididevice/MidiDeviceInOutType.java
src/camidion/chordhelper/mididevice/MidiDeviceModel.java
src/camidion/chordhelper/mididevice/MidiDeviceModelManager.java [deleted file]
src/camidion/chordhelper/mididevice/MidiDeviceTreeModel.java
src/camidion/chordhelper/mididevice/MidiSequencerModel.java
src/camidion/chordhelper/mididevice/ReceiverListModel.java
src/camidion/chordhelper/mididevice/TransmitterListModel.java

index 28d771f..f95688f 100644 (file)
@@ -56,7 +56,7 @@ import camidion.chordhelper.chordmatrix.ChordButtonLabel;
 import camidion.chordhelper.chordmatrix.ChordMatrix;
 import camidion.chordhelper.chordmatrix.ChordMatrixListener;
 import camidion.chordhelper.mididevice.MidiDeviceDialog;
-import camidion.chordhelper.mididevice.MidiDeviceModelManager;
+import camidion.chordhelper.mididevice.MidiDeviceTreeModel;
 import camidion.chordhelper.mididevice.MidiSequencerModel;
 import camidion.chordhelper.mididevice.SequencerMeasureView;
 import camidion.chordhelper.mididevice.SequencerTimeView;
@@ -283,7 +283,7 @@ public class ChordHelperApplet extends JApplet {
         */
        public static class VersionInfo {
                public static final String      NAME = "MIDI Chord Helper";
-               public static final String      VERSION = "Ver.20161005.1";
+               public static final String      VERSION = "Ver.20161010.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/";
@@ -406,7 +406,7 @@ public class ChordHelperApplet extends JApplet {
        private KeySignatureLabel keysigLabel;
        private AnoGakkiPane anoGakkiPane;
        private JToggleButton anoGakkiToggleButton;
-       private MidiDeviceModelManager deviceModelManager;
+       private MidiDeviceTreeModel deviceTreeModel;
 
        public void init() {
                loadIconImage();
@@ -463,13 +463,15 @@ public class ChordHelperApplet extends JApplet {
                }};
                VirtualMidiDevice guiMidiDevice = keyboardPanel.keyboardCenterPanel.keyboard.midiDevice;
                //
-               // MIDIデバイスマネージャを構築
-               deviceModelManager = new MidiDeviceModelManager(guiMidiDevice);
-               MidiDeviceDialog midiDeviceDialog = new MidiDeviceDialog(deviceModelManager);
-               midiDeviceDialog.setIconImage(iconImage);
+               // MIDIデバイスツリーモデルを構築
+               deviceTreeModel = new MidiDeviceTreeModel(guiMidiDevice);
+               //
+               // MIDIシーケンサと連携するプレイリストモデルを構築
+               playlistModel = new PlaylistTableModel(sequencerModel = deviceTreeModel.getSequencerModel());
                //
-               // MIDIデバイス一覧のシーケンサと連携するプレイリストを構築
-               playlistModel = new PlaylistTableModel(sequencerModel = deviceModelManager.getSequencerModel());
+               // MIDIデバイスダイアログの構築
+               MidiDeviceDialog midiDeviceDialog = new MidiDeviceDialog(deviceTreeModel);
+               midiDeviceDialog.setIconImage(iconImage);
                //
                // MIDIエディタダイアログの構築
                (midiEditor = new MidiSequenceEditorDialog(playlistModel, guiMidiDevice)).setIconImage(iconImage);
@@ -708,7 +710,7 @@ public class ChordHelperApplet extends JApplet {
        }
        @Override
        public void destroy() {
-               deviceModelManager.close();
+               deviceTreeModel.closeAllDevices();
                super.destroy();
        }
        @Override
index e3f21d7..afc9c2c 100644 (file)
@@ -87,7 +87,6 @@ public class MidiDeviceDesktopPane extends JDesktopPane implements TreeSelection
                });
                // デバイスツリーが変更されたときの更新処理を予約
                MidiDeviceTreeModel deviceTreeModel = deviceTreeView.getModel();
-               List<MidiDeviceModel> deviceModelList = deviceTreeModel.getDeviceModelList();
                TreeModelListener treeModelListener = new TreeModelListener() {
                        @Override
                        public void treeNodesChanged(TreeModelEvent e) { }
@@ -99,18 +98,17 @@ public class MidiDeviceDesktopPane extends JDesktopPane implements TreeSelection
                        public void treeStructureChanged(TreeModelEvent e) {
                                //
                                // 削除されたデバイスモデルに対するデバイスフレームをマップから外す
-                               List<MidiDeviceModel> removingDeviceModels = new ArrayList<>();
+                               List<MidiDeviceModel> deviceModelsToRemove = new ArrayList<>();
                                for( MidiDeviceModel m : frameMap.keySet() ) {
-                                       if( ! deviceModelList.contains(m) ) removingDeviceModels.add(m);
+                                       if( ! deviceTreeModel.contains(m) ) deviceModelsToRemove.add(m);
                                }
-                               for( MidiDeviceModel m : removingDeviceModels ) {
+                               for( MidiDeviceModel m : deviceModelsToRemove ) {
                                        MidiDeviceFrame frame = frameMap.remove(m);
                                        if( frame != null ) remove(frame);
                                }
-                               removingDeviceModels.clear();
                                //
                                // 新しいデバイスモデルに対するデバイスフレームを生成してマップに登録
-                               for( MidiDeviceModel deviceModel : deviceModelList ) {
+                               for( MidiDeviceModel deviceModel : deviceTreeModel ) {
                                        if( frameMap.containsKey(deviceModel) ) continue;
                                        MidiDeviceFrame frame = new MidiDeviceFrame(deviceModel, cablePane);
                                        frameMap.put(deviceModel, frame);
@@ -148,11 +146,11 @@ public class MidiDeviceDesktopPane extends JDesktopPane implements TreeSelection
                // 表示したデバイスフレームを整列
                int toX = 10;
                int toY = 10;
-               for( MidiDeviceModel deviceModel : deviceModelList ) {
+               for( MidiDeviceModel deviceModel : deviceTreeModel ) {
                        if( ! deviceModel.getMidiDevice().isOpen() ) continue;
                        frameMap.get(deviceModel).setLocation(toX, toY);
                        toX = (toX == 10 ? 270 : 10);
-                       toY += 50;
+                       toY += 70;
                }
                deviceTreeView.expandAll();
                //
index efecea3..353bcc4 100644 (file)
@@ -20,7 +20,6 @@ import camidion.chordhelper.ButtonIcon;
  */
 public class MidiDeviceDialog extends JDialog {
        public static final Icon MIDI_CONNECTER_ICON = new ButtonIcon(ButtonIcon.MIDI_CONNECTOR_ICON);
-       public static final String MSGS = "Microsoft GS Wavetable Synth";
        /**
         * MIDIデバイスダイアログを開くアクション
         */
@@ -37,12 +36,12 @@ public class MidiDeviceDialog extends JDialog {
        };
        /**
         * MIDIデバイスダイアログを構築します。
-        * @param deviceModelManager デバイスモデルマネージャ
+        * @param deviceTreeModel デバイスツリーモデル
         */
-       public MidiDeviceDialog(final MidiDeviceModelManager deviceModelManager) {
+       public MidiDeviceDialog(MidiDeviceTreeModel deviceTreeModel) {
                setTitle(openAction.getValue(Action.NAME).toString());
                setBounds( 300, 300, 820, 540 );
-               MidiDeviceTreeView deviceTreeView = new MidiDeviceTreeView(deviceModelManager.getTreeModel());
+               MidiDeviceTreeView deviceTreeView = new MidiDeviceTreeView(deviceTreeModel);
                final MidiDeviceInfoPane deviceInfoPane = new MidiDeviceInfoPane();
                deviceTreeView.addTreeSelectionListener(deviceInfoPane);
                MidiDeviceDesktopPane desktopPane = new MidiDeviceDesktopPane(deviceTreeView, deviceInfoPane, this);
@@ -60,7 +59,7 @@ public class MidiDeviceDialog extends JDialog {
                                                        addActionListener(new ActionListener() {
                                                                @Override
                                                                public void actionPerformed(ActionEvent e) {
-                                                                       deviceModelManager.updateMidiDeviceList();
+                                                                       deviceTreeModel.updateMidiDeviceList();
                                                                        deviceTreeView.expandAll();
                                                                }
                                                        });
@@ -70,7 +69,7 @@ public class MidiDeviceDialog extends JDialog {
                                                        addActionListener(new ActionListener() {
                                                                @Override
                                                                public void actionPerformed(ActionEvent e) {
-                                                                       deviceModelManager.resetMicrosecondPosition();
+                                                                       deviceTreeModel.resetMicrosecondPosition();
                                                                }
                                                        });
                                                }});
index 1381fba..27751ee 100644 (file)
@@ -29,16 +29,6 @@ public class MidiDeviceFrame extends JInternalFrame {
         */
        public MidiDeviceModel getMidiDeviceModel() { return deviceModel; }
        /**
-        * このデバイスフレームに貼り付けられたMIDIトランスミッタリストビューを取得します。
-        * @return MIDIトランスミッタリストビュー
-        */
-       public TransmitterListView getMidiTransmitterListView() { return transmitterListView; }
-       /**
-        * このデバイスフレームに貼り付けられたMIDIトランシーバリストビューを取得します。
-        * @return MIDIトランシーバリストビュー
-        */
-       public ReceiverListView getMidiReceiverListView() { return receiverListView; }
-       /**
         * MIDIデバイスモデルからフレームビューを構築します。
         */
        public MidiDeviceFrame(MidiDeviceModel deviceModel, MidiCablePane cablePane) {
@@ -67,20 +57,18 @@ public class MidiDeviceFrame extends JInternalFrame {
                        setLayout(new BorderLayout());
                        ReceiverListModel rxListModel = getMidiDeviceModel().getReceiverListModel();
                        if( rxListModel != null ) {
-                               receiverListView = new ReceiverListView(rxListModel, cablePane);
                                add(rxPanel = new JPanel() {{
                                        setLayout(new BorderLayout());
                                        add(new JLabel("Rx") {{ setVerticalAlignment(TOP); }}, BorderLayout.WEST);
-                                       add(receiverListView);
+                                       add(receiverListView = new ReceiverListView(rxListModel, cablePane));
                                }}, BorderLayout.NORTH);
                        }
                        TransmitterListModel txListModel = getMidiDeviceModel().getTransmitterListModel();
                        if( txListModel != null ) {
-                               transmitterListView = new TransmitterListView(txListModel, cablePane);
                                add(txPanel = new JPanel() {{
                                        setLayout(new BorderLayout());
                                        add(new JLabel("Tx") {{ setVerticalAlignment(TOP); }}, BorderLayout.WEST);
-                                       add(transmitterListView);
+                                       add(transmitterListView = new TransmitterListView(txListModel, cablePane));
                                }}, rxListModel == null ? BorderLayout.NORTH : BorderLayout.SOUTH);
                        }
                }}));
index 26588aa..d0c7553 100644 (file)
@@ -25,12 +25,20 @@ public enum MidiDeviceInOutType {
         * {@link #MIDI_IN}と{@link #MIDI_OUT}の両方をサポートしたデバイスを表します。
         */
        MIDI_IN_OUT("MIDI input/output devices (MIDI sequencer etc.)", "I/O");
-       private String description;
-       private String shortName;
+
        private MidiDeviceInOutType(String description, String shortName) {
                this.description = description;
                this.shortName = shortName;
        }
+       public static MidiDeviceInOutType getValueFor(TransmitterListModel txListModel, ReceiverListModel rxListModel) {
+               // tx:IN rx:OUT
+               return rxListModel == null ?
+                       (txListModel == null ? MIDI_NONE : MIDI_IN) :
+                       (txListModel == null ? MIDI_OUT  : MIDI_IN_OUT);
+       }
        public String getDescription() { return description; }
+       private String description;
        public String getShortName() { return shortName; }
+       private String shortName;
+
 }
index 0432485..41da29b 100644 (file)
@@ -1,6 +1,7 @@
 package camidion.chordhelper.mididevice;
 
 import javax.sound.midi.MidiDevice;
+import javax.sound.midi.MidiSystem;
 import javax.sound.midi.MidiUnavailableException;
 import javax.sound.midi.Receiver;
 import javax.sound.midi.Transmitter;
@@ -21,7 +22,7 @@ public class MidiDeviceModel {
         * @return このリストのMIDIデバイスの入出力タイプ
         */
        public MidiDeviceInOutType getInOutType() { return ioType; }
-       private MidiDeviceInOutType ioType = MidiDeviceInOutType.MIDI_NONE;
+       private MidiDeviceInOutType ioType;
        /**
         * 対象MIDIデバイスを返します。
         * @return 対象MIDIデバイス
@@ -32,9 +33,7 @@ public class MidiDeviceModel {
         * 対象MIDIデバイスの名前を返します。
         */
        @Override
-       public String toString() {
-               return device.getDeviceInfo().toString();
-       }
+       public String toString() { return device.getDeviceInfo().toString(); }
        /**
         * {@link Transmitter} のリストモデルを返します。サポートしていない場合はnullを返します。
         * @return リストモデルまたはnull
@@ -48,30 +47,33 @@ public class MidiDeviceModel {
        public ReceiverListModel getReceiverListModel() { return rxListModel; }
        private ReceiverListModel rxListModel;
        /**
-        * ã\81\93ã\81®MIDIã\83\87ã\83\90ã\82¤ã\82¹ã\83¢ã\83\87ã\83«ã\82\92å\8f\8e容ã\81\97ã\81¦ã\81\84ã\82\8bã\83ªã\82¹ã\83\88を返します。
+        * ã\81\93ã\81®MIDIã\83\87ã\83\90ã\82¤ã\82¹ã\83¢ã\83\87ã\83«ã\82\92å\8f\8e容ã\81\97ã\81¦ã\81\84ã\82\8bã\83\84ã\83ªã\83¼ã\83¢ã\83\87ã\83«を返します。
         */
-       public MidiDeviceModelManager getDeviceModelManager() { return deviceModelManager; }
-       protected MidiDeviceModelManager deviceModelManager;
+       public MidiDeviceTreeModel getDeviceTreeModel() { return deviceTreeModel; }
+       protected MidiDeviceTreeModel deviceTreeModel;
+       /**
+        * MIDIデバイス情報からMIDIデバイスモデルを構築します。
+        *
+        * @param deviceInfo 対象MIDIデバイス情報
+        * @param deviceTreeModel 収容先のMIDIデバイスツリーモデル
+        * @throws MidiUnavailableException {@link MidiSystem#getMidiDevice(MidiDevice.Info)}からの例外
+        */
+       public MidiDeviceModel(MidiDevice.Info deviceInfo, MidiDeviceTreeModel deviceTreeModel) throws MidiUnavailableException {
+               this(MidiSystem.getMidiDevice(deviceInfo), deviceTreeModel);
+       }
        /**
-        * MIDIデバイスモデルを構築します。
+        * MIDIã\83\87ã\83\90ã\82¤ã\82¹ã\81\8bã\82\89ã\83¢ã\83\87ã\83«ã\82\92æ§\8bç¯\89ã\81\97ã\81¾ã\81\99ã\80\82
         *
         * @param device 対象MIDIデバイス
-        * @param deviceModelManager このMIDIデバイスモデルのマネージャー(接続相手となりうるMIDIデバイス)
+        * @param deviceTreeModel このMIDIデバイスモデルのマネージャー(接続相手となりうるMIDIデバイス)
         */
-       public MidiDeviceModel(MidiDevice device, MidiDeviceModelManager deviceModelManager) {
-               this.deviceModelManager = deviceModelManager;
+       public MidiDeviceModel(MidiDevice device, MidiDeviceTreeModel deviceTreeModel) {
+               this.deviceTreeModel = deviceTreeModel;
                this.device = device;
-               if( device.getMaxTransmitters() != 0 ) {
-                       txListModel = new TransmitterListModel(this);
-               }
-               if( device.getMaxReceivers() != 0 ) {
-                       rxListModel = new ReceiverListModel(this);
-                       ioType = txListModel != null ? MidiDeviceInOutType.MIDI_IN_OUT :MidiDeviceInOutType.MIDI_OUT;
-               }
-               else {
-                       ioType = txListModel != null ? MidiDeviceInOutType.MIDI_IN :MidiDeviceInOutType.MIDI_NONE;
-               }
-               treePath = new TreePath(new Object[] {deviceModelManager, ioType ,this});
+               if( device.getMaxTransmitters() != 0 ) txListModel = new TransmitterListModel(this);
+               if( device.getMaxReceivers() != 0 ) rxListModel = new ReceiverListModel(this);
+               ioType = MidiDeviceInOutType.getValueFor(txListModel, rxListModel);
+               treePath = new TreePath(new Object[] {deviceTreeModel, ioType ,this});
        }
        /**
         * このMIDIデバイスを開きます。
@@ -91,7 +93,7 @@ public class MidiDeviceModel {
         * それらも全て閉じます。
         */
        public void close() {
-               if( rxListModel != null ) rxListModel.closePeerTransmitters();
+               if( rxListModel != null ) rxListModel.closeTransmitters();
                device.close();
        }
 }
diff --git a/src/camidion/chordhelper/mididevice/MidiDeviceModelManager.java b/src/camidion/chordhelper/mididevice/MidiDeviceModelManager.java
deleted file mode 100644 (file)
index 0245156..0000000
+++ /dev/null
@@ -1,210 +0,0 @@
-package camidion.chordhelper.mididevice;
-
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.Vector;
-
-import javax.sound.midi.MidiDevice;
-import javax.sound.midi.MidiSystem;
-import javax.sound.midi.MidiUnavailableException;
-import javax.sound.midi.Receiver;
-import javax.sound.midi.Sequencer;
-import javax.sound.midi.Synthesizer;
-import javax.sound.midi.Transmitter;
-
-import camidion.chordhelper.ChordHelperApplet;
-
-/**
- * 仮想MIDIデバイスを含めたすべてのMIDIデバイスモデル {@link MidiDeviceModel} を管理するオブジェクト
- */
-public class MidiDeviceModelManager {
-
-       private List<MidiDeviceModel> deviceModelList = new Vector<>();
-       public List<MidiDeviceModel> getDeviceModelList() { return deviceModelList; }
-
-       private MidiSequencerModel sequencerModel;
-       public MidiSequencerModel getSequencerModel() { return sequencerModel; }
-
-       private MidiDeviceTreeModel treeModel;
-       public MidiDeviceTreeModel getTreeModel() { return treeModel; }
-       public void setTreeModel(MidiDeviceTreeModel treeModel) { this.treeModel = treeModel; }
-
-       public MidiDeviceModelManager(VirtualMidiDevice guiVirtualDevice) {
-               List<MidiDevice.Info> deviceInfos = Arrays.asList(MidiSystem.getMidiDeviceInfo());
-               //
-               // GUI仮想MIDIデバイス
-               MidiDeviceModel guiModel = new MidiDeviceModel(guiVirtualDevice, this);
-               deviceModelList.add(guiModel);
-               //
-               // シーケンサ
-               Sequencer sequencer = null;
-               try {
-                       sequencer = MidiSystem.getSequencer(false);
-                       deviceModelList.add(sequencerModel = new MidiSequencerModel(sequencer, this));
-               } catch( MidiUnavailableException e ) {
-                       System.out.println(ChordHelperApplet.VersionInfo.NAME +" : MIDI sequencer unavailable");
-                       e.printStackTrace();
-               }
-               // その他のリアルMIDIデバイス
-               MidiDeviceModel synthModel = null;
-               MidiDeviceModel firstMidiInModel = null;
-               MidiDeviceModel firstMidiOutModel = null;
-               for( MidiDevice.Info info : deviceInfos ) {
-                       MidiDevice device;
-                       try {
-                               device = MidiSystem.getMidiDevice(info);
-                       } catch( MidiUnavailableException e ) {
-                               e.printStackTrace();
-                               continue;
-                       }
-                       // シーケンサはすでに取得済みなのでスキップ
-                       if( device instanceof Sequencer ) continue;
-                       //
-                       // Java内蔵シンセサイザ
-                       if( device instanceof Synthesizer ) {
-                               try {
-                                       device = MidiSystem.getSynthesizer();
-                                       deviceModelList.add(synthModel = new MidiDeviceModel(device, this));
-                               } catch( MidiUnavailableException e ) {
-                                       System.out.println(ChordHelperApplet.VersionInfo.NAME +
-                                                       " : Java internal MIDI synthesizer unavailable");
-                                       e.printStackTrace();
-                               }
-                               continue;
-                       }
-                       // その他のMIDIデバイス
-                       MidiDeviceModel m;
-                       deviceModelList.add(m = new MidiDeviceModel(device, this));
-                       //
-                       // 最初の MIDI OUT(Windowsの場合は通常、内蔵音源 Microsoft GS Wavetable SW Synth)
-                       if( firstMidiOutModel == null && m.getReceiverListModel() != null ) firstMidiOutModel = m;
-                       //
-                       // 最初の MIDI IN(USB MIDI インターフェースにつながったMIDIキーボードなど)
-                       if( firstMidiInModel == null && m.getTransmitterListModel() != null ) firstMidiInModel = m;
-               }
-               // MIDIデバイスを開く。
-               //   NOTE: 必ず MIDI OUT Rx デバイスを先に開くこと。
-               //
-               //   そうすれば、後から開いた MIDI IN Tx デバイスからのタイムスタンプのほうが「若く」なるので、
-               //   相手の MIDI OUT Rx デバイスは「信号が遅れてやってきた」と認識、遅れを取り戻そうとして
-               //   即座に音を出してくれる。
-               //
-               //   開く順序が逆になると「進みすぎるから遅らせよう」として無用なレイテンシーが発生する原因になる。
-               try {
-                       // デバイスを開く
-                       MidiDeviceModel modelsToOpen[] = {synthModel, firstMidiOutModel, sequencerModel, firstMidiInModel};
-                       for( MidiDeviceModel m : modelsToOpen ) if( m != null ) m.open();
-                       guiModel.open();
-                       //
-                       // 初期接続
-                       // GUI → GUI、各音源、シーケンサ
-                       TransmitterListModel txListModel;
-                       if( (txListModel = guiModel.getTransmitterListModel() ) != null) {
-                               txListModel.connectToFirstReceiverOfDevices(guiModel,sequencerModel,synthModel,firstMidiOutModel);
-                       }
-                       // MIDI IN → GUI、各音源、シーケンサ
-                       if( firstMidiInModel != null && (txListModel = firstMidiInModel.getTransmitterListModel()) != null) {
-                               txListModel.connectToFirstReceiverOfDevices(guiModel,sequencerModel,synthModel,firstMidiOutModel);
-                       }
-                       // シーケンサ → GUI、各音源
-                       if( sequencerModel != null && (txListModel = sequencerModel.getTransmitterListModel()) != null) {
-                               txListModel.connectToFirstReceiverOfDevices(guiModel,synthModel,firstMidiOutModel);
-                       }
-               } catch( MidiUnavailableException ex ) {
-                       ex.printStackTrace();
-               }
-               treeModel = new MidiDeviceTreeModel(deviceModelList);
-       }
-       /**
-        * MIDIデバイスリストを最新の状態に更新します。USB-MIDIデバイスの着脱後に使います。
-        */
-       public void updateMidiDeviceList() {
-               //
-               // USBから抜いたMIDIデバイスを相手のMIDIデバイスに接続したまま
-               // MidiSystem.getMidiDeviceInfo()を呼ぶと、Java VM が落ちることがある。
-               //
-               // それを回避するため、接続を一旦全部閉じる。
-               //
-               // 各Receiverごとに相手のTransmitterを閉じ、デバイスモデル同士の接続を覚えておく。
-               Map<MidiDeviceModel, Set<MidiDeviceModel>> rxToTxConnections = new HashMap<>();
-               for(MidiDeviceModel m : deviceModelList) {
-                       ReceiverListModel rxListModel = m.getReceiverListModel();
-                       if( rxListModel == null ) continue;
-                       Set<MidiDeviceModel> txDeviceModels = rxListModel.closePeerTransmitters();
-                       if( txDeviceModels.isEmpty() ) continue;
-                       rxToTxConnections.put(m, txDeviceModels);
-               }
-               // 最新のMIDIデバイス情報を取得
-               MidiDevice.Info[] infoArray = MidiSystem.getMidiDeviceInfo();
-               //
-               // 追加されたデバイスの情報
-               List<MidiDevice.Info> additionalDeviceInfos = new Vector<>(Arrays.asList(infoArray));
-               //
-               // 取り外されたデバイスのモデル
-               List<MidiDeviceModel> removingDeviceModels = new Vector<>();
-               //
-               // 既存デバイスをスキャン
-               for(MidiDeviceModel m : deviceModelList) {
-                       MidiDevice device = m.getMidiDevice();
-                       if( device instanceof VirtualMidiDevice ) continue;
-                       if( ! additionalDeviceInfos.remove(device.getDeviceInfo()) ) {
-                               // 最新のMIDIデバイス情報リストから除去しようとして、すでに存在していなかった場合、
-                               // そのデバイスはすでに取り外されている
-                               m.close();
-                               removingDeviceModels.add(m);
-                       }
-               }
-               // 取り外されたデバイスのモデルを除去
-               deviceModelList.removeAll(removingDeviceModels);
-               //
-               // 追加されたデバイスをモデル化してデバイスモデルリストに追加
-               for( MidiDevice.Info info : additionalDeviceInfos ) {
-                       try {
-                               MidiDevice device = MidiSystem.getMidiDevice(info);
-                               MidiDeviceModel m = new MidiDeviceModel(device, this);
-                               deviceModelList.add(m);
-                       } catch( MidiUnavailableException e ) {
-                               e.printStackTrace();
-                       }
-               }
-               // デバイスモデル同士の接続を復元
-               Set<MidiDeviceModel> rxDeviceModels = rxToTxConnections.keySet();
-               for( MidiDeviceModel rxm : rxDeviceModels ) {
-                       if( ! deviceModelList.contains(rxm) ) continue;
-                       List<Receiver> rxList = rxm.getReceiverListModel().getTransceivers();
-                       for( Receiver rx : rxList ) {
-                               Set<MidiDeviceModel> txDeviceModels = rxToTxConnections.get(rxm);
-                               for( MidiDeviceModel txm : txDeviceModels ) {
-                                       try {
-                                               txm.getTransmitterListModel().openTransmitter().setReceiver(rx);
-                                       } catch( MidiUnavailableException e ) {
-                                               e.printStackTrace();
-                                       }
-                               }
-                       }
-               }
-               // デバイスツリーの更新を通知
-               treeModel.setDeviceModelList(deviceModelList);
-       }
-       /**
-        * {@link Transmitter}を持つすべてのデバイス(例:MIDIキーボードなど)について、
-        * {@link MidiDeviceModel#resetMicrosecondPosition()}でマイクロ秒位置をリセットします。
-        */
-       public void resetMicrosecondPosition() {
-               for(MidiDeviceModel m : deviceModelList) {
-                       TransmitterListModel txListModel = m.getTransmitterListModel();
-                       if( txListModel != null ) txListModel.resetMicrosecondPosition();
-               }
-       }
-       /**
-        * すべてのMIDIデバイスを閉じます。
-        */
-       public void close() {
-               for(MidiDeviceModel m : deviceModelList) m.getMidiDevice().close();
-               deviceModelList.clear();
-               treeModel = null;
-       }
-}
index bd316f3..d76da27 100644 (file)
 package camidion.chordhelper.mididevice;
 
+import java.util.AbstractList;
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
 import java.util.EnumMap;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
+import java.util.Vector;
 
+import javax.sound.midi.MidiDevice;
+import javax.sound.midi.MidiSystem;
+import javax.sound.midi.MidiUnavailableException;
+import javax.sound.midi.Receiver;
+import javax.sound.midi.Sequencer;
+import javax.sound.midi.Synthesizer;
+import javax.sound.midi.Transmitter;
 import javax.swing.event.EventListenerList;
 import javax.swing.event.TreeModelEvent;
 import javax.swing.event.TreeModelListener;
 import javax.swing.tree.TreeModel;
 import javax.swing.tree.TreePath;
 
+import camidion.chordhelper.ChordHelperApplet;
+
 /**
- * MIDIデバイスを{@link MidiDeviceInOutType}で分類して参照できるようにするツリーモデル
+ * 仮想MIDIデバイスを含めたすべてのMIDIデバイスモデル{@link MidiDeviceModel}をツリー構造で管理するモデル。
+ * 読み取り専用のMIDIデバイスリストとしても、
+ * I/Oタイプで分類されたMIDIデバイスツリーモデルとしても参照できます。
  */
-public class MidiDeviceTreeModel implements TreeModel {
+public class MidiDeviceTreeModel extends AbstractList<MidiDeviceModel> implements TreeModel {
+       @Override
+       public String toString() { return "MIDI devices"; }
 
-       private List<MidiDeviceModel> deviceModelList;
-       public List<MidiDeviceModel> getDeviceModelList() { return deviceModelList; }
-       public void setDeviceModelList(List<MidiDeviceModel> deviceModelList) {
-               group.clear();
-               this.deviceModelList = deviceModelList;
-               createGroup();
-               fireTreeStructureChanged(this, null, null, null);
+       // MIDIデバイスモデルリストとツリー
+       protected List<MidiDeviceModel> deviceModelList = new Vector<>();
+       protected Map<MidiDeviceInOutType, List<MidiDeviceModel>> deviceModelTree; {
+               deviceModelTree = new EnumMap<>(MidiDeviceInOutType.class);
+               deviceModelTree.put(MidiDeviceInOutType.MIDI_OUT, new ArrayList<>());
+               deviceModelTree.put(MidiDeviceInOutType.MIDI_IN, new ArrayList<>());
+               deviceModelTree.put(MidiDeviceInOutType.MIDI_IN_OUT, new ArrayList<>());
+       };
+       /**
+        * MIDIデバイスモデルを追加します。
+        *
+        * @param dm 追加するMIDIデバイスモデル
+        * @return 呼出しの結果としてこのリストが変更された場合はtrue
+        */
+       protected boolean addDeviceModel(MidiDeviceModel dm) {
+               if( ! deviceModelList.add(dm) ) return false;
+               deviceModelTree.get(dm.getInOutType()).add(dm); return true;
        }
-
-       private Map<MidiDeviceInOutType, List<MidiDeviceModel>> group = new EnumMap<>(MidiDeviceInOutType.class);
-       private void createGroup() {
-               for(MidiDeviceInOutType ioType : MidiDeviceInOutType.values()) {
-                       if( ioType != MidiDeviceInOutType.MIDI_NONE ) group.put(ioType, new ArrayList<>());
-               }
-               for( MidiDeviceModel m : deviceModelList ) group.get(m.getInOutType()).add(m);
+       /**
+        * MIDIデバイス情報をモデルとして追加します。
+        *
+        * @param info 追加するMIDIデバイス情報({@link MidiDevice.Info})
+        * @return 呼出しの結果としてこのリストが変更された場合はtrue
+        * @throws MidiUnavailableException {@link MidiDeviceModel#MidiDeviceModel(MidiDevice.Info, MidiDeviceTreeModel) MidiDeviceModelコンストラクタ}からの例外
+        */
+       protected boolean addDeviceModel(MidiDevice.Info info) throws MidiUnavailableException {
+               return addDeviceModel(new MidiDeviceModel(info, this));
        }
-       public Map<MidiDeviceInOutType, List<MidiDeviceModel>> getGroup() { return group; }
-
-       public MidiDeviceTreeModel(List<MidiDeviceModel> deviceModelList) {
-               this.deviceModelList = deviceModelList;
-               createGroup();
+       /**
+        * リストとツリーモデルから、指定されたMIDIデバイスモデル(複数指定可)を除去します。
+        *
+        * @param deviceModelsToRemove 除去したいMIDIデバイスモデル
+        * @return 指定されたMIDIデバイスモデルがこのリストに含まれていた場合はtrue
+        */
+       protected boolean removeDeviceModels(Collection<MidiDeviceModel> deviceModelsToRemove) {
+               if( ! deviceModelList.removeAll(deviceModelsToRemove) ) return false;
+               for( MidiDeviceModel dm : deviceModelsToRemove )
+                       deviceModelTree.get(dm.getInOutType()).remove(dm);
+               return true;
+       }
+       /**
+        * リスト、ツリーモデル、接続マップから、指定されたMIDIデバイスモデル(複数指定可)を除去します。
+        *
+        * @param deviceModelsToRemove 除去したいMIDIデバイスモデル
+        * @param rxToTxConnections MIDIデバイスモデル接続マップ({@link #disconnectAllDevices()}の戻り値)
+        * @return 指定されたMIDIデバイスモデルがこのリストに含まれていた場合はtrue
+        */
+       protected boolean removeDeviceModels(
+                       Collection<MidiDeviceModel> deviceModelsToRemove,
+                       Map<MidiDeviceModel, Collection<MidiDeviceModel>> rxToTxConnections) {
+               if( ! removeDeviceModels(deviceModelsToRemove) ) return false;
+               Set<MidiDeviceModel> rxModels = rxToTxConnections.keySet();
+               rxModels.removeAll(deviceModelsToRemove);
+               for( MidiDeviceModel m : rxModels )
+                       rxToTxConnections.get(m).removeAll(deviceModelsToRemove);
+               return true;
        }
 
+       /**
+        * このMIDIデバイスツリーモデルに登録されているMIDIシーケンサーモデルを返します。
+        * @return MIDIシーケンサーモデル
+        */
+       public MidiSequencerModel getSequencerModel() { return sequencerModel; }
+       protected MidiSequencerModel sequencerModel;
+       //
+       // リスト用インターフェース
        @Override
-       public String toString() { return "MIDI devices"; }
+       public MidiDeviceModel get(int index) { return deviceModelList.get(index); }
+       @Override
+       public int size() { return deviceModelList.size(); }
+       //
+       // ツリーモデル用インターフェース
        @Override
        public Object getRoot() { return this; }
        @Override
        public int getChildCount(Object parent) {
                if( parent == getRoot() ) return MidiDeviceInOutType.values().length - 1;
-               if( parent instanceof MidiDeviceInOutType ) return group.get(parent).size();
+               if( parent instanceof MidiDeviceInOutType ) return deviceModelTree.get(parent).size();
                return 0;
        }
        @Override
        public Object getChild(Object parent, int index) {
                if( parent == getRoot() ) return MidiDeviceInOutType.values()[index + 1];
-               if( parent instanceof MidiDeviceInOutType ) return group.get(parent).get(index);
+               if( parent instanceof MidiDeviceInOutType ) return deviceModelTree.get(parent).get(index);
                return null;
        }
        @Override
        public int getIndexOfChild(Object parent, Object child) {
-               if( parent == getRoot() ) return ((MidiDeviceInOutType)child).ordinal() - 1;
-               if( parent instanceof MidiDeviceInOutType ) return group.get(parent).indexOf(child);
+               if( parent == getRoot() && child instanceof MidiDeviceInOutType ) {
+                       return ((MidiDeviceInOutType)child).ordinal() - 1;
+               }
+               if( parent instanceof MidiDeviceInOutType ) return deviceModelTree.get(parent).indexOf(child);
                return -1;
        }
        @Override
        public boolean isLeaf(Object node) { return node instanceof MidiDeviceModel; }
        @Override
        public void valueForPathChanged(TreePath path, Object newValue) {}
-       //
-       private EventListenerList listenerList = new EventListenerList();
        @Override
        public void addTreeModelListener(TreeModelListener listener) {
                listenerList.add(TreeModelListener.class, listener);
@@ -75,6 +141,7 @@ public class MidiDeviceTreeModel implements TreeModel {
        public void removeTreeModelListener(TreeModelListener listener) {
                listenerList.remove(TreeModelListener.class, listener);
        }
+       protected EventListenerList listenerList = new EventListenerList();
        protected void fireTreeStructureChanged(Object source, Object[] path, int[] childIndices, Object[] children) {
                Object[] listeners = listenerList.getListenerList();
                for (int i = listeners.length-2; i>=0; i-=2) {
@@ -85,4 +152,219 @@ public class MidiDeviceTreeModel implements TreeModel {
                        }
                }
        }
+
+       /**
+        * {@link MidiSystem#getMidiDeviceInfo()} の結果を、不変の {@link List} として返します。
+        *
+        * <p>注意点:MIDIデバイスをUSBから抜いて、他のデバイスとの接続を切断せずに
+        * {@link MidiSystem#getMidiDeviceInfo()}を呼び出すと
+        * (少なくとも Windows 10 で)Java VM がクラッシュすることがあります。
+        * </p>
+        * @return インストールされているMIDIデバイスの情報のリスト
+        */
+       public static List<MidiDevice.Info> getMidiDeviceInfo() {
+               return Arrays.asList(MidiSystem.getMidiDeviceInfo());
+       }
+
+       /**
+        * 指定されたMIDIデバイスモデルをまとめて開きます。
+        *
+        * @param toOpenList 開きたいMIDIデバイスモデルのコレクション
+        */
+       public void openDevices(Collection<MidiDeviceModel> toOpenList) {
+               for( MidiDeviceModel toOpen : toOpenList ) {
+                       try {
+                               toOpen.open();
+                       } catch( MidiUnavailableException ex ) {
+                               System.out.println("Cannot open MIDI device " + toOpen);
+                               ex.printStackTrace();
+                       }
+               }
+       }
+
+       /**
+        * 引数で与えられたGUI仮想MIDIデバイスと、{@link #getMidiDeviceInfo()}から取得したMIDIデバイス情報から、
+        * MIDIデバイスツリーモデルを初期構築します。
+        *
+        * @param guiVirtualDevice 管理対象に含めるGUI仮想MIDIデバイス
+        */
+       public MidiDeviceTreeModel(VirtualMidiDevice guiVirtualDevice) {
+               MidiDeviceModel guiModel = new MidiDeviceModel(guiVirtualDevice, this);
+               addDeviceModel(guiModel);
+               try {
+                       addDeviceModel(sequencerModel = new MidiSequencerModel(MidiSystem.getSequencer(false), this));
+               } catch( MidiUnavailableException e ) {
+                       System.out.println(ChordHelperApplet.VersionInfo.NAME +" : MIDI sequencer unavailable");
+                       e.printStackTrace();
+               }
+               MidiDeviceModel synthModel = null;
+               MidiDeviceModel firstMidiInModel = null;
+               MidiDeviceModel firstMidiOutModel = null;
+               for( MidiDevice.Info info : getMidiDeviceInfo() ) {
+                       MidiDevice device;
+                       try {
+                               device = MidiSystem.getMidiDevice(info);
+                       } catch( MidiUnavailableException e ) {
+                               e.printStackTrace();
+                               continue;
+                       }
+                       if( device instanceof Sequencer ) continue; // シーケンサはすでに取得済み
+                       if( device instanceof Synthesizer ) { // Java内蔵シンセサイザ
+                               try {
+                                       addDeviceModel(synthModel = new MidiDeviceModel(MidiSystem.getSynthesizer(), this));
+                               } catch( MidiUnavailableException e ) {
+                                       System.out.println(ChordHelperApplet.VersionInfo.NAME +
+                                                       " : Java internal MIDI synthesizer unavailable");
+                                       e.printStackTrace();
+                               }
+                               continue;
+                       }
+                       MidiDeviceModel m = new MidiDeviceModel(device, this);
+                       //
+                       // 最初の MIDI OUT(Windowsの場合は通常、内蔵音源 Microsoft GS Wavetable SW Synth)
+                       if( firstMidiOutModel == null && m.getReceiverListModel() != null ) firstMidiOutModel = m;
+                       //
+                       // 最初の MIDI IN(USB MIDI インターフェースにつながったMIDIキーボードなど)
+                       if( firstMidiInModel == null && m.getTransmitterListModel() != null ) firstMidiInModel = m;
+                       //
+                       addDeviceModel(m);
+               }
+               // 開くMIDIデバイスモデルの一覧を作成
+               //
+               //   NOTE: 必ず MIDI OUT Rx デバイスを先に開くこと。
+               //
+               //   そうすれば、後から開いた MIDI IN Tx デバイスからのタイムスタンプのほうが「若く」なるので、
+               //   相手の MIDI OUT Rx デバイスは「信号が遅れてやってきた」と認識、遅れを取り戻そうとして
+               //   即座に音を出してくれる。
+               //
+               //   開く順序が逆になると「進みすぎるから遅らせよう」として無用なレイテンシーが発生する原因になる。
+               //
+               List<MidiDeviceModel> toOpenList = new ArrayList<>();
+               for( MidiDeviceModel toOpen : Arrays.asList(
+                               synthModel, firstMidiOutModel, sequencerModel, guiModel, firstMidiInModel) ) {
+                       if( toOpen != null ) toOpenList.add(toOpen);
+               }
+               // MIDIデバイスモデルを開く
+               openDevices(toOpenList);
+               //
+               // 初期接続マップを作成(開いたデバイスを相互に接続する)
+               Map<MidiDeviceModel, Collection<MidiDeviceModel>> initialConnection = new LinkedHashMap<>();
+               for( MidiDeviceModel rxm : toOpenList ) {
+                       if( rxm.getReceiverListModel() == null ) continue;
+                       List<MidiDeviceModel> txmList;
+                       initialConnection.put(rxm, txmList = new ArrayList<>());
+                       for( MidiDeviceModel txm : toOpenList ) {
+                               if( txm.getTransmitterListModel() == null ) continue;
+                               //
+                               // Tx/Rx両方を持つデバイスでは
+                               // ・シーケンサーモデルは自分自身には接続しない
+                               // ・GUIデバイスモデルでは自分自身にも接続する
+                               if( txm == sequencerModel && txm == rxm ) continue;
+                               //
+                               txmList.add(txm);
+                       }
+               }
+               // 初期接続を実行
+               connectDevices(initialConnection);
+       }
+       /**
+        * すべてのMIDIデバイスを閉じます。
+        */
+       public void closeAllDevices() {
+               for(MidiDeviceModel m : deviceModelList) m.getMidiDevice().close();
+       }
+       /**
+        * デバイス間の接続をすべて切断します。
+        * 各{@link Receiver}ごとに相手デバイスの{@link Transmitter}を閉じ、
+        * その時どのように接続されていたかを示すマップを返します。
+        *
+        * @return MIDIデバイスモデル接続マップ(再接続時に{@link #connectDevices(Map)}に指定可)
+        * <ul>
+        * <li>キー:各{@link Receiver}を持つMIDIデバイスモデル</li>
+        * <li>値:接続相手だった{@link Transmitter}を持つMIDIデバイスモデルのコレクション</li>
+        * </ul>
+        */
+       public Map<MidiDeviceModel, Collection<MidiDeviceModel>> disconnectAllDevices() {
+               Map<MidiDeviceModel, Collection<MidiDeviceModel>> rxToTxConnections = new LinkedHashMap<>();
+               for(MidiDeviceModel m : deviceModelList) {
+                       ReceiverListModel rxListModel = m.getReceiverListModel();
+                       if( rxListModel == null ) continue;
+                       Collection<MidiDeviceModel> txDeviceModels = rxListModel.closeTransmitters();
+                       if( txDeviceModels.isEmpty() ) continue;
+                       rxToTxConnections.put(m, txDeviceModels);
+               }
+               return rxToTxConnections;
+       }
+       /**
+        * デバイス間の接続を復元します。
+        *
+        * @param rxToTxConnections {@link #disconnectAllDevices()}が返したMIDIデバイスモデル接続マップ
+        * <ul>
+        * <li>キー:{@link Receiver}側デバイスモデル</li>
+        * <li>値:{@link Transmitter}側デバイスモデルのコレクション</li>
+        * </ul>
+        */
+       public void connectDevices(Map<MidiDeviceModel, Collection<MidiDeviceModel>> rxToTxConnections) {
+               for( MidiDeviceModel rxm : rxToTxConnections.keySet() ) {
+                       if( rxm == null ) continue;
+                       Receiver rx = rxm.getReceiverListModel().getTransceivers().get(0);
+                       for( MidiDeviceModel txm : rxToTxConnections.get(rxm) ) {
+                               if( txm == null ) continue;
+                               try {
+                                       txm.getTransmitterListModel().openTransmitter().setReceiver(rx);
+                               } catch( MidiUnavailableException e ) {
+                                       e.printStackTrace();
+                               }
+                       }
+               }
+       }
+       /**
+        * USB-MIDIデバイスの着脱後、MIDIデバイスリストを最新の状態に更新します。
+        *
+        * <p>USBからMIDIデバイスを抜いた場合に {@link #getMidiDeviceInfo()} で
+        * Java VM クラッシュが発生する現象を回避するため、更新前に全デバイスの接続を一時切断し、
+        * 更新完了後に接続を復元します。
+        * </p>
+        */
+       public void updateMidiDeviceList() {
+               // 一時切断
+               Map<MidiDeviceModel, Collection<MidiDeviceModel>> rxToTxConnections = disconnectAllDevices();
+               //
+               // 追加・削除されたMIDIデバイスを特定
+               List<MidiDevice.Info> toAdd = new Vector<>(getMidiDeviceInfo());
+               List<MidiDeviceModel> toRemove = new Vector<>();
+               for(MidiDeviceModel m : deviceModelList) {
+                       MidiDevice d = m.getMidiDevice();
+                       if( d instanceof VirtualMidiDevice || toAdd.remove(d.getDeviceInfo()) ) continue;
+                       d.close();
+                       toRemove.add(m);
+               }
+               // 削除されたデバイスのモデルを除去
+               removeDeviceModels(toRemove, rxToTxConnections);
+               //
+               // 追加されたデバイスのモデルを登録
+               for( MidiDevice.Info info : toAdd ) {
+                       try {
+                               addDeviceModel(info);
+                       } catch( MidiUnavailableException e ) {
+                               e.printStackTrace();
+                       }
+               }
+               // 再接続
+               connectDevices(rxToTxConnections);
+               //
+               // リスナーに通知してツリー表示を更新してもらう
+               fireTreeStructureChanged(this, null, null, null);
+       }
+       /**
+        * {@link Transmitter}を持つすべてのデバイス(例:MIDIキーボードなど)について、
+        * {@link MidiDeviceModel#resetMicrosecondPosition()}でマイクロ秒位置をリセットします。
+        */
+       public void resetMicrosecondPosition() {
+               for(MidiDeviceModel m : deviceModelList) {
+                       TransmitterListModel txListModel = m.getTransmitterListModel();
+                       if( txListModel != null ) txListModel.resetMicrosecondPosition();
+               }
+       }
+
 }
index fae059e..ee13697 100644 (file)
@@ -33,10 +33,10 @@ public class MidiSequencerModel extends MidiDeviceModel implements BoundedRangeM
        /**
         * MIDIシーケンサモデルを構築します。
         * @param sequencer シーケンサーMIDIデバイス
-        * @param deviceModelList 親のMIDIデバイスモデルリスト
+        * @param deviceModelTree 親のMIDIデバイスツリーモデル
         */
-       public MidiSequencerModel(Sequencer sequencer, MidiDeviceModelManager deviceModelList) {
-               super(sequencer, deviceModelList);
+       public MidiSequencerModel(Sequencer sequencer, MidiDeviceTreeModel deviceModelTree) {
+               super(sequencer, deviceModelTree);
        }
        /**
         * このシーケンサーの再生スピード調整モデル
@@ -124,7 +124,7 @@ public class MidiSequencerModel extends MidiDeviceModel implements BoundedRangeM
                timeRangeUpdater.start();
                SequenceTrackListTableModel sequenceTableModel = getSequenceTrackListTableModel();
                if( sequenceTableModel != null && sequenceTableModel.hasRecordChannel() ) {
-                       deviceModelManager.resetMicrosecondPosition();
+                       deviceTreeModel.resetMicrosecondPosition();
                        System.gc();
                        sequencer.startRecording();
                }
index 6b9b544..0da6495 100644 (file)
@@ -1,6 +1,6 @@
 package camidion.chordhelper.mididevice;
 
-import java.util.HashSet;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Set;
 
@@ -26,22 +26,24 @@ public class ReceiverListModel extends AbstractTransceiverListModel<Receiver> {
                if( device.getReceivers().isEmpty() ) device.getReceiver();
        }
        /**
-        * このリストモデルの{@link Receiver}に接続された{@link Transmitter}を全て閉じます。
+        * このリストモデルの{@link Receiver}に接続された{@link Transmitter}を全て閉じ、
+        * 接続相手だったMIDIデバイスモデルのユニークな集合を返します。
         *
         * @return 閉じた{@link Transmitter}の{@link MidiDeviceModel}の集合
         */
-       public Set<MidiDeviceModel> closePeerTransmitters() {
-               List<Receiver> rxList = deviceModel.getMidiDevice().getReceivers();
-               List<MidiDeviceModel> deviceModelList = deviceModel.getDeviceModelManager().getDeviceModelList();
-               Set<MidiDeviceModel> peedDeviceModels = new HashSet<>();
-               for( Receiver rx : rxList ) {
-                       for( MidiDeviceModel peedDeviceModel : deviceModelList ) {
-                               if( peedDeviceModel == deviceModel ) continue;
-                               TransmitterListModel txListModel = peedDeviceModel.getTransmitterListModel();
-                               if( txListModel == null || txListModel.closeTransmittersConnectedTo(rx).isEmpty() ) continue;
-                               peedDeviceModels.add(peedDeviceModel);
+       public Set<MidiDeviceModel> closeTransmitters() {
+               Set<MidiDeviceModel> peerDeviceModelSet = new LinkedHashSet<>();
+               List<Receiver> rxList = getTransceivers();
+               if( ! rxList.isEmpty() ) {
+                       for( MidiDeviceModel peerDeviceModel : deviceModel.getDeviceTreeModel() ) {
+                               if( peerDeviceModel == deviceModel ) continue;
+                               TransmitterListModel txListModel = peerDeviceModel.getTransmitterListModel();
+                               if( txListModel == null ) continue;
+                               for( Receiver rx : rxList )
+                                       if( ! txListModel.closeTransmittersFor(rx).isEmpty() )
+                                               peerDeviceModelSet.add(peerDeviceModel);
                        }
                }
-               return peedDeviceModels;
+               return peerDeviceModelSet;
        }
 }
index 7642fb4..f4b7a9e 100644 (file)
@@ -1,7 +1,8 @@
 package camidion.chordhelper.mididevice;
 
+import java.util.ArrayList;
+import java.util.Collection;
 import java.util.List;
-import java.util.Vector;
 
 import javax.sound.midi.MidiDevice;
 import javax.sound.midi.MidiUnavailableException;
@@ -47,20 +48,7 @@ public class TransmitterListModel extends AbstractTransceiverListModel<Transmitt
                return tx;
        }
        /**
-        * 相手のMIDIデバイスが持つ最初の{@link Receiver}を、
-        * このリストモデルの新規{@link Transmitter}に接続します。
-        *
-        * @param anotherDeviceModels 接続相手のMIDIデバイス(複数指定可)
-        * @throws MidiUnavailableException リソースの制約のためにトランスミッタを使用できない場合にスローされる
-        */
-       public void connectToFirstReceiverOfDevices(MidiDeviceModel... anotherDeviceModels) throws MidiUnavailableException {
-               for( MidiDeviceModel anotherDeviceModel : anotherDeviceModels ) {
-                       List<Receiver> rxList = anotherDeviceModel.getMidiDevice().getReceivers();
-                       if( ! rxList.isEmpty() ) deviceModel.getTransmitterListModel().openTransmitter().setReceiver(rxList.get(0));
-               }
-       }
-       /**
-        * 指定の{@link Transmitter}を閉じ、要素数が1個減ったことをこのモデルを参照しているビューへ通知します。
+        * 指定された{@link Transmitter}を閉じ、要素数が1個減ったことをこのモデルを参照しているビューへ通知します。
         *
         * @param tx このリストモデルで開いている{@link Transmitter}
         */
@@ -70,21 +58,28 @@ public class TransmitterListModel extends AbstractTransceiverListModel<Transmitt
                fireIntervalRemoved(this, index, index);
        }
        /**
+        * 指定された複数の{@link Transmitter}を閉じ、要素数が大きく減ったことをこのモデルを参照しているビューへ通知します。
+        *
+        * @param txc このリストモデルで開いている{@link Transmitter}のコレクション
+        */
+       public void closeTransmitters(Collection<Transmitter> txc) {
+               if( txc.isEmpty() ) return;
+               int length = getSize();
+               for( Transmitter tx : txc ) tx.close();
+               fireIntervalRemoved(this, 0, length);
+       }
+       /**
         * このリストモデルにある{@link Transmitter}のうち、
-        * 引数で指定された{@link Receiver}へデータを送信しているものを全て閉じます。
-        * 閉じるとリストから自動的に削除されるので、表示の更新も行います。
+        * 引数で指定された{@link Receiver}へデータを送信しているものを探し、
+        * それらを{@link #closeTransmitters(Collection)}で閉じます。
+        *
         * @return 閉じた{@link Transmitter}のリスト
         */
-       public List<Transmitter> closeTransmittersConnectedTo(Receiver rx) {
-               List<Transmitter> closeTxList = new Vector<Transmitter>();
-               List<Transmitter> txList = getTransceivers();
-               for( Transmitter tx : txList ) if( tx.getReceiver() == rx ) closeTxList.add(tx);
-               if( ! closeTxList.isEmpty() ) {
-                       int length = getSize();
-                       for( Transmitter tx : closeTxList ) tx.close();
-                       fireIntervalRemoved(this, 0, length);
-               }
-               return closeTxList;
+       public List<Transmitter> closeTransmittersFor(Receiver rx) {
+               List<Transmitter> txToClose = new ArrayList<Transmitter>();
+               for( Transmitter tx : getTransceivers() ) if( tx.getReceiver() == rx ) txToClose.add(tx);
+               closeTransmitters(txToClose);
+               return txToClose;
        }
        /**
         * マイクロ秒位置をリセットします。
@@ -103,35 +98,38 @@ public class TransmitterListModel extends AbstractTransceiverListModel<Transmitt
                MidiDevice device = deviceModel.getMidiDevice();
                if( device instanceof Sequencer || ! device.isOpen() ) return;
                //
-               // デバイスを閉じる前に接続状態を把握
-               List<Receiver> peerRxList = new Vector<Receiver>();
-               List<Transmitter> txList = device.getTransmitters();
-               for( Transmitter tx : txList ) {
+               // 接続状態を保存
+               // 自分Tx → 相手Rx
+               List<Receiver> peerRxList = new ArrayList<Receiver>();
+               for( Transmitter tx : device.getTransmitters() ) {
                        Receiver rx = tx.getReceiver();
                        if( rx != null ) peerRxList.add(rx);
                }
-               List<Transmitter> peerTxList = new Vector<Transmitter>();
-               List<MidiDeviceModel> deviceModelList = deviceModel.getDeviceModelManager().getDeviceModelList();
+               // 自分Rx ← 相手Tx
+               List<Transmitter> peerTxList = new ArrayList<Transmitter>();
                List<Receiver> rxList = device.getReceivers();
-               for( Receiver rx : rxList ) {
-                       for( MidiDeviceModel m : deviceModelList ) {
-                               if( m == deviceModel ) continue;
+               if( ! rxList.isEmpty() ) {
+                       for( MidiDeviceModel m : deviceModel.getDeviceTreeModel() ) {
+                               if( m == deviceModel ) continue; // 「自分Rx ← 自分Tx」は重複するのでスキップ
                                List<Transmitter> peerSourceTxList = m.getMidiDevice().getTransmitters();
-                               for( Transmitter tx : peerSourceTxList ) if( tx.getReceiver() == rx ) peerTxList.add(tx);
+                               for( Transmitter tx : peerSourceTxList ) {
+                                       for( Receiver rx : rxList ) if( tx.getReceiver() == rx ) peerTxList.add(tx);
+                               }
                        }
                }
-               // いったん閉じて開く(ここでマイクロ秒位置がリセットされる)
-               // その後、元通りに接続し直す
-               device.close();
+               device.close(); // 一旦閉じる
                try {
-                       device.open();
+                       device.open(); // 再び開くことでマイクロ秒位置がリセットされる
+                       //
+                       // 接続を復元
+                       // 自分Tx → 相手Rx
                        for( Receiver peerRx : peerRxList ) openTransmitter().setReceiver(peerRx);
+                       // 自分Rx ← 相手Tx
                        if( ! rxList.isEmpty() ) {
-                               Receiver rx = rxList.get(0);
-                               for( Transmitter peerTx : peerTxList ) peerTx.setReceiver(rx);
+                               for( Transmitter peerTx : peerTxList ) peerTx.setReceiver(rxList.get(0));
                        }
                } catch( MidiUnavailableException e ) {
                        e.printStackTrace();
                }
        }
-}
\ No newline at end of file
+}