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;
import javax.swing.table.AbstractTableModel;
import camidion.chordhelper.ButtonIcon;
+import camidion.chordhelper.ChordHelperApplet;
import camidion.chordhelper.mididevice.MidiSequencerModel;
import camidion.chordhelper.music.ChordProgression;
* プレイリスト(MIDIシーケンスリスト)のテーブルデータモデル
*/
public class PlaylistTableModel extends AbstractTableModel {
+ private MidiSequencerModel sequencerModel;
/**
- * MIDIシーケンサモデル
+ * このプレイリストと連携しているMIDIシーケンサモデルを返します。
*/
- public MidiSequencerModel sequencerModel;
+ public MidiSequencerModel getSequencerModel() { return sequencerModel; }
/**
* 空のトラックリストモデル
*/
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 から起動されます。
* <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();
/**
* シーケンスリスト
*/
- 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);
}
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() {
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);
}
};
public boolean isCellEditable() { return true; } // ダブルクリックだけ有効
@Override
public Object getValueOf(SequenceTrackListTableModel sequenceModel) {
- return sequenceModel.isOnSequencer()
- ? sequenceModel.sequenceListTableModel.secondPosition : "";
+ return sequenceModel.isOnSequencer() ? sequenceModel.getParent().secondPosition : "";
}
},
/** シーケンスの時間長(分:秒) */
this.preferredWidth = preferredWidth;
}
public boolean isCellEditable() { return false; }
- public Object getValueOf(SequenceTrackListTableModel sequenceModel) {
- return "";
- }
+ public Object getValueOf(SequenceTrackListTableModel sequenceModel) { return ""; }
}
@Override
@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];
}
@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);
* @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);
}
/**
* 更新済みフラグをセットし、選択されたシーケンスの全ての列を再表示します。
*/
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++ ) {
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)
* デフォルトの内容で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);
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)
/**
* 選択されたシーケンスを除去します。
* 除去されたシーケンスがシーケンサーにロード済みの場合、アンロードします。
+ *
+ * @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);
}
}
/**
/**
* 引数で示された数だけ次へ進めたシーケンスをロードします。
* @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;
}
}