OSDN Git Service

・再生スピード調整をよりわかりやすくした
[midichordhelper/MIDIChordHelper.git] / src / camidion / chordhelper / midieditor / PlaylistTableModel.java
index 6bd4e12..48873cc 100644 (file)
@@ -18,10 +18,12 @@ import javax.sound.midi.MetaEventListener;
 import javax.sound.midi.MetaMessage;
 import javax.sound.midi.MidiSystem;
 import javax.sound.midi.Sequence;
+import javax.sound.midi.Sequencer;
 import javax.swing.AbstractAction;
 import javax.swing.Action;
 import javax.swing.DefaultListSelectionModel;
 import javax.swing.Icon;
+import javax.swing.JOptionPane;
 import javax.swing.ListSelectionModel;
 import javax.swing.SwingUtilities;
 import javax.swing.event.ChangeEvent;
@@ -31,6 +33,7 @@ import javax.swing.event.ListSelectionListener;
 import javax.swing.table.AbstractTableModel;
 
 import camidion.chordhelper.ButtonIcon;
+import camidion.chordhelper.ChordHelperApplet;
 import camidion.chordhelper.mididevice.MidiSequencerModel;
 import camidion.chordhelper.music.ChordProgression;
 
@@ -38,10 +41,11 @@ import camidion.chordhelper.music.ChordProgression;
  * プレイリスト(MIDIシーケンスリスト)のテーブルデータモデル
  */
 public class PlaylistTableModel extends AbstractTableModel {
+       private MidiSequencerModel sequencerModel;
        /**
-        * MIDIシーケンサモデル
+        * このプレイリストと連携しているMIDIシーケンサモデルを返します。
         */
-       public MidiSequencerModel sequencerModel;
+       public MidiSequencerModel getSequencerModel() { return sequencerModel; }
        /**
         * 空のトラックリストモデル
         */
@@ -58,23 +62,38 @@ public class PlaylistTableModel extends AbstractTableModel {
                        setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
                }
        };
+       /** 再生中のシーケンサーの秒位置リスナー */
+       private ChangeListener secondPosition = new ChangeListener() {
+               private int value = 0;
+               @Override
+               public void stateChanged(ChangeEvent event) {
+                       Object src = event.getSource();
+                       if( src instanceof MidiSequencerModel ) {
+                               int newValue = ((MidiSequencerModel)src).getValue() / 1000;
+                               if(value == newValue) return;
+                               value = newValue;
+                               int rowIndex = indexOfSequenceOnSequencer();
+                               fireTableCellUpdated(rowIndex, Column.POSITION.ordinal());
+                       }
+               }
+               @Override
+               public String toString() {
+                       return String.format("%02d:%02d", value/60, value%60);
+               }
+       };
        /**
         * 新しいプレイリストのテーブルモデルを構築します。
-        * @param sequencerModel MIDIシーケンサーモデル
+        * @param sequencerModel 連携するMIDIシーケンサーモデル
         */
        public PlaylistTableModel(MidiSequencerModel sequencerModel) {
                this.sequencerModel = sequencerModel;
-               //
-               // 秒位置を監視
-               sequencerModel.addChangeListener(secondPosition = new SecondPosition());
-               //
-               // メタイベントを監視
+               sequencerModel.addChangeListener(secondPosition);
                sequencerModel.getSequencer().addMetaEventListener(
                        new MetaEventListener() {
                                /**
                                 * {@inheritDoc}
                                 *
-                                * <p>EOT (End Of Trackã\80\81type==0x2F) ã\82\92å\8f\97ä¿¡ã\81\97ã\81\9fã\81¨ã\81\8dã\81®å\87¦ç\90\86ã\81§す。
+                                * <p>EOT (End Of Trackã\80\81type==0x2F) ã\82\92å\8f\97ä¿¡ã\81\97ã\81\9fã\81¨ã\81\8dã\80\81次ã\81®æ\9b²ã\81¸é\80²ã\81¿ã\81¾す。
                                 * </p>
                                 * <p>これは MetaEventListener のための実装なので、多くの場合
                                 * Swing EDT ではなく MIDI シーケンサの EDT から起動されます。
@@ -107,13 +126,13 @@ public class PlaylistTableModel extends AbstractTableModel {
         * <p>リピートモードの場合は同じ曲をもう一度再生、
         * そうでない場合は次の曲へ進んで再生します。
         * 次の曲がなければ、そこで停止します。
-        * いずれの場合もの先頭へ戻ります。
+        * いずれの場合もの先頭へ戻ります。
         * </p>
         */
        private void goNext() {
                // とりあえず曲の先頭へ戻る
                sequencerModel.getSequencer().setMicrosecondPosition(0);
-               if( (Boolean)toggleRepeatAction.getValue(Action.SELECTED_KEY) || loadNext(1)) {
+               if( (Boolean)toggleRepeatAction.getValue(Action.SELECTED_KEY) || loadNext(1) ) {
                        // リピートモードのときはもう一度同じ曲を、
                        // そうでない場合は次の曲を再生開始
                        sequencerModel.start();
@@ -134,13 +153,15 @@ public class PlaylistTableModel extends AbstractTableModel {
        /**
         * シーケンスリスト
         */
-       List<SequenceTrackListTableModel> sequenceList = new Vector<>();
+       private List<SequenceTrackListTableModel> sequenceList = new Vector<>();
+       /**
+        * このプレイリストが保持している {@link SequenceTrackListTableModel} のリストを返します。
+        */
+       public List<SequenceTrackListTableModel> getSequenceList() { return sequenceList; }
        /**
         * 行が選択されているときだけイネーブルになるアクション
         */
-       public abstract class SelectedSequenceAction extends AbstractAction
-               implements ListSelectionListener
-       {
+       public abstract class SelectedSequenceAction extends AbstractAction implements ListSelectionListener {
                public SelectedSequenceAction(String name, Icon icon, String tooltip) {
                        super(name,icon); init(tooltip);
                }
@@ -175,31 +196,6 @@ public class PlaylistTableModel extends AbstractTableModel {
                public void actionPerformed(ActionEvent event) { }
        };
        /**
-        * 再生中のシーケンサーの秒位置
-        */
-       private class SecondPosition implements ChangeListener {
-               private int value = 0;
-               /**
-                * 再生中のシーケンサーの秒位置が変わったときに表示を更新します。
-                * @param event 変更イベント
-                */
-               @Override
-               public void stateChanged(ChangeEvent event) {
-                       Object src = event.getSource();
-                       if( src instanceof MidiSequencerModel ) {
-                               int newValue = ((MidiSequencerModel)src).getValue() / 1000;
-                               if(value == newValue) return;
-                               value = newValue;
-                               int rowIndex = indexOfSequenceOnSequencer();
-                               fireTableCellUpdated(rowIndex, Column.POSITION.ordinal());
-                       }
-               }
-               @Override
-               public String toString() {
-                       return String.format("%02d:%02d", value/60, value%60);
-               }
-       }
-       /**
         * 曲の先頭または前の曲へ戻るアクション
         */
        public Action moveToTopAction = new AbstractAction() {
@@ -210,8 +206,7 @@ public class PlaylistTableModel extends AbstractTableModel {
                        putValue(LARGE_ICON_KEY, new ButtonIcon(ButtonIcon.TOP_ICON));
                }
                public void actionPerformed(ActionEvent event) {
-                       if( sequencerModel.getSequencer().getTickPosition() <= 40 )
-                               loadNext(-1);
+                       if( sequencerModel.getSequencer().getTickPosition() <= 40 ) loadNext(-1);
                        sequencerModel.setValue(0);
                }
        };
@@ -245,8 +240,7 @@ public class PlaylistTableModel extends AbstractTableModel {
                        public boolean isCellEditable() { return true; } // ダブルクリックだけ有効
                        @Override
                        public Object getValueOf(SequenceTrackListTableModel sequenceModel) {
-                               return sequenceModel.isOnSequencer()
-                                       ? sequenceModel.sequenceListTableModel.secondPosition : "";
+                               return sequenceModel.isOnSequencer() ? sequenceModel.getParent().secondPosition : "";
                        }
                },
                /** シーケンスの時間長(分:秒) */
@@ -336,9 +330,7 @@ public class PlaylistTableModel extends AbstractTableModel {
                        this.preferredWidth = preferredWidth;
                }
                public boolean isCellEditable() { return false; }
-               public Object getValueOf(SequenceTrackListTableModel sequenceModel) {
-                       return "";
-               }
+               public Object getValueOf(SequenceTrackListTableModel sequenceModel) { return ""; }
        }
 
        @Override
@@ -346,19 +338,13 @@ public class PlaylistTableModel extends AbstractTableModel {
        @Override
        public int getColumnCount() { return Column.values().length; }
        @Override
-       public String getColumnName(int column) {
-               return Column.values()[column].title;
-       }
+       public String getColumnName(int column) { return Column.values()[column].title; }
        @Override
-       public Class<?> getColumnClass(int column) {
-               return Column.values()[column].columnClass;
-       }
+       public Class<?> getColumnClass(int column) { return Column.values()[column].columnClass; }
        @Override
        public boolean isCellEditable(int row, int column) {
                return Column.values()[column].isCellEditable();
        }
-       /** 再生中のシーケンサーの秒位置 */
-       private SecondPosition secondPosition;
        @Override
        public Object getValueAt(int row, int column) {
                PlaylistTableModel.Column c = Column.values()[column];
@@ -366,8 +352,7 @@ public class PlaylistTableModel extends AbstractTableModel {
        }
        @Override
        public void setValueAt(Object val, int row, int column) {
-               PlaylistTableModel.Column c = Column.values()[column];
-               switch(c) {
+               switch(Column.values()[column]) {
                case FILENAME:
                        // ファイル名の変更
                        sequenceList.get(row).setFilename((String)val);
@@ -433,11 +418,9 @@ public class PlaylistTableModel extends AbstractTableModel {
         * @return 選択されたMIDIシーケンスのテーブルモデル(非選択時はnull)
         */
        public SequenceTrackListTableModel getSelectedSequenceModel() {
-               if( sequenceListSelectionModel.isSelectionEmpty() )
-                       return null;
+               if( sequenceListSelectionModel.isSelectionEmpty() ) return null;
                int selectedIndex = sequenceListSelectionModel.getMinSelectionIndex();
-               if( selectedIndex >= sequenceList.size() )
-                       return null;
+               if( selectedIndex >= sequenceList.size() ) return null;
                return sequenceList.get(selectedIndex);
        }
        /**
@@ -455,8 +438,7 @@ public class PlaylistTableModel extends AbstractTableModel {
         * 更新済みフラグをセットし、選択されたシーケンスの全ての列を再表示します。
         */
        public void fireSelectedSequenceModified() {
-               if( sequenceListSelectionModel.isSelectionEmpty() )
-                       return;
+               if( sequenceListSelectionModel.isSelectionEmpty() ) return;
                int minIndex = sequenceListSelectionModel.getMinSelectionIndex();
                int maxIndex = sequenceListSelectionModel.getMaxSelectionIndex();
                for( int index = minIndex; index <= maxIndex; index++ ) {
@@ -465,44 +447,7 @@ public class PlaylistTableModel extends AbstractTableModel {
                fireTableRowsUpdated(minIndex, maxIndex);
        }
        /**
-        * バイト列とファイル名からMIDIシーケンスを追加します。
-        * バイト列が null の場合、空のMIDIシーケンスを追加します。
-        * @param data バイト列
-        * @param filename ファイル名
-        * @return 追加先インデックス(先頭が 0)
-        * @throws IOException ファイル読み込みに失敗した場合
-        * @throws InvalidMidiDataException MIDIデータが正しくない場合
-        */
-       public int addSequence(byte[] data, String filename)
-               throws IOException, InvalidMidiDataException
-       {
-               if( data == null ) return addDefaultSequence();
-               int lastIndex;
-               try (InputStream in = new ByteArrayInputStream(data)) {
-                       Sequence seq = MidiSystem.getSequence(in);
-                       lastIndex = addSequence(seq, filename);
-               } catch( IOException|InvalidMidiDataException e ) {
-                       throw e;
-               }
-               sequenceListSelectionModel.setSelectionInterval(lastIndex, lastIndex);
-               return lastIndex;
-       }
-       /**
-        * MIDIシーケンスを追加します。
-        * シーケンサーが停止中の場合、追加したシーケンスから再生を開始します。
-        * @param sequence MIDIシーケンス
-        * @return 追加先インデックス(先頭が 0)
-        */
-       public int addSequenceAndPlay(Sequence sequence) {
-               int lastIndex = addSequence(sequence,"");
-               if( ! sequencerModel.getSequencer().isRunning() ) {
-                       loadToSequencer(lastIndex);
-                       sequencerModel.start();
-               }
-               return lastIndex;
-       }
-       /**
-        * MIDIシーケンスを追加します。
+        * ファイル名つきのMIDIシーケンスを追加します。
         * @param sequence MIDIシーケンス
         * @param filename ファイル名
         * @return 追加されたシーケンスのインデックス(先頭が 0)
@@ -517,20 +462,40 @@ public class PlaylistTableModel extends AbstractTableModel {
         * デフォルトの内容でMIDIシーケンスを作成して追加します。
         * @return 追加されたMIDIシーケンスのインデックス(先頭が 0)
         */
-       public int addDefaultSequence() {
+       public int addSequence() {
                Sequence seq = (new ChordProgression()).toMidiSequence();
                return seq == null ? -1 : addSequence(seq,null);
        }
        /**
+        * バイト列とファイル名からMIDIシーケンスを追加します。
+        * バイト列が null の場合、空のMIDIシーケンスを追加します。
+        * @param data バイト列
+        * @param filename ファイル名
+        * @return 追加先インデックス(先頭が 0)
+        * @throws IOException ファイル読み込みに失敗した場合
+        * @throws InvalidMidiDataException MIDIデータが正しくない場合
+        */
+       public int addSequence(byte[] data, String filename) throws IOException, InvalidMidiDataException {
+               if( data == null ) return addSequence();
+               int lastIndex;
+               try (InputStream in = new ByteArrayInputStream(data)) {
+                       lastIndex = addSequence(MidiSystem.getSequence(in), filename);
+               } catch( IOException|InvalidMidiDataException e ) {
+                       throw e;
+               }
+               sequenceListSelectionModel.setSelectionInterval(lastIndex, lastIndex);
+               return lastIndex;
+       }
+       /**
         * MIDIファイルを追加します。
-        * ファイルが null の場合、のMIDIシーケンスを追加します。
+        * ファイルが null の場合、デフォルトのMIDIシーケンスを追加します。
         * @param midiFile MIDIファイル
         * @return 追加先インデックス(先頭が 0)
         * @throws InvalidMidiDataException ファイル内のMIDIデータが正しくない場合
         * @throws IOException ファイル入出力に失敗した場合
         */
        public int addSequence(File midiFile) throws InvalidMidiDataException, IOException {
-               if( midiFile == null ) return addDefaultSequence();
+               if( midiFile == null ) return addSequence();
                int lastIndex;
                try (FileInputStream in = new FileInputStream(midiFile)) {
                        Sequence seq = MidiSystem.getSequence(in);
@@ -542,6 +507,20 @@ public class PlaylistTableModel extends AbstractTableModel {
                return lastIndex;
        }
        /**
+        * MIDIシーケンスを追加し、再生されていなかった場合は追加したシーケンスから再生を開始します。
+        * @param sequence MIDIシーケンス
+        * @return 追加されたシーケンスのインデックス(先頭が 0)
+        * @throws InvalidMidiDataException {@link Sequencer#setSequence(Sequence)} を参照
+        */
+       public int addSequenceAndPlay(Sequence sequence) throws InvalidMidiDataException {
+               int lastIndex = addSequence(sequence,"");
+               if( ! sequencerModel.getSequencer().isRunning() ) {
+                       loadToSequencer(lastIndex);
+                       sequencerModel.start();
+               }
+               return lastIndex;
+       }
+       /**
         * 複数のMIDIファイルを追加します。
         * @param fileList 追加するMIDIファイルのリスト
         * @return 追加先の最初のインデックス(先頭が 0、追加されなかった場合は -1)
@@ -574,36 +553,39 @@ public class PlaylistTableModel extends AbstractTableModel {
        /**
         * 選択されたシーケンスを除去します。
         * 除去されたシーケンスがシーケンサーにロード済みの場合、アンロードします。
+        *
+        * @throws InvalidMidiDataException {@link Sequencer#setSequence(Sequence)} を参照
         */
-       public void removeSelectedSequence() {
+       public void removeSelectedSequence() throws InvalidMidiDataException {
                if( sequenceListSelectionModel.isSelectionEmpty() ) return;
                int selectedIndex = sequenceListSelectionModel.getMinSelectionIndex();
-               if( sequenceList.remove(selectedIndex).isOnSequencer() )
-                       sequencerModel.setSequenceTrackListTableModel(null);
+               SequenceTrackListTableModel removedSequence = sequenceList.remove(selectedIndex);
+               if( removedSequence.isOnSequencer() ) sequencerModel.setSequenceTrackListTableModel(null);
                fireTableRowsDeleted(selectedIndex, selectedIndex);
        }
        /**
-        * 指定したインデックス位置のシーケンスをシーケンサーにロードします。
-        * すでに同じIDのシーケンスがロードされていた場合はスキップします。
-        * @param index シーケンスのインデックス位置(-1 を指定するとアンロードされます)
+        * テーブル内の指定したインデックス位置にあるシーケンスをシーケンサーにロードします。
+        * インデックスに -1 を指定するとアンロードされます。
+        * 変更点がある場合、リスナー(テーブルビュー)に通知します。
+        *
+        * @param newRowIndex ロードするシーケンスのインデックス位置、アンロードするときは -1
+        * @throws InvalidMidiDataException {@link Sequencer#setSequence(Sequence)} を参照
         */
-       public void loadToSequencer(int index) {
+       public void loadToSequencer(int newRowIndex) throws InvalidMidiDataException {
                SequenceTrackListTableModel oldSeq = sequencerModel.getSequenceTrackListTableModel();
-               SequenceTrackListTableModel newSeq = (index < 0 ? null : sequenceList.get(index));
-               if(oldSeq == newSeq) return;
+               SequenceTrackListTableModel newSeq = (newRowIndex < 0 ? null : sequenceList.get(newRowIndex));
+               if( oldSeq == newSeq ) return;
                sequencerModel.setSequenceTrackListTableModel(newSeq);
-               //
-               // 変更点をテーブルビューに通知して再表示してもらう
                int columnIndices[] = {
                        Column.PLAY.ordinal(),
                        Column.POSITION.ordinal(),
                };
                if( oldSeq != null ) {
-                       int oldIndex = sequenceList.indexOf(oldSeq);
-                       for( int columnIndex : columnIndices ) fireTableCellUpdated(oldIndex, columnIndex);
+                       int oldRowIndex = sequenceList.indexOf(oldSeq);
+                       for( int columnIndex : columnIndices ) fireTableCellUpdated(oldRowIndex, columnIndex);
                }
                if( newSeq != null ) {
-                       for( int columnIndex : columnIndices ) fireTableCellUpdated(index, columnIndex);
+                       for( int columnIndex : columnIndices ) fireTableCellUpdated(newRowIndex, columnIndex);
                }
        }
        /**
@@ -617,13 +599,22 @@ public class PlaylistTableModel extends AbstractTableModel {
        /**
         * 引数で示された数だけ次へ進めたシーケンスをロードします。
         * @param offset 進みたいシーケンス数
-        * @return 成功したらtrue
+        * @return true:ロード成功、false:これ以上進めない
+        * @throws InvalidMidiDataException {@link Sequencer#setSequence(Sequence)} を参照
         */
        public boolean loadNext(int offset) {
                int loadedIndex = indexOfSequenceOnSequencer();
                int index = (loadedIndex < 0 ? 0 : loadedIndex + offset);
                if( index < 0 || index >= sequenceList.size() ) return false;
-               loadToSequencer(index);
+               try {
+                       loadToSequencer(index);
+               } catch (InvalidMidiDataException ex) {
+                       ex.printStackTrace();
+                       JOptionPane.showMessageDialog(
+                               null, ex.getMessage(),
+                               ChordHelperApplet.VersionInfo.NAME, JOptionPane.ERROR_MESSAGE);
+                       return false;
+               }
                return true;
        }
 }