1 package camidion.chordhelper.midieditor;
3 import java.io.ByteArrayOutputStream;
4 import java.io.IOException;
5 import java.nio.charset.Charset;
6 import java.util.ArrayList;
9 import javax.sound.midi.MidiSystem;
10 import javax.sound.midi.Sequence;
11 import javax.sound.midi.Track;
12 import javax.swing.DefaultListSelectionModel;
13 import javax.swing.ListSelectionModel;
14 import javax.swing.table.AbstractTableModel;
16 import camidion.chordhelper.music.MIDISpec;
19 * MIDIシーケンス(トラックリスト)のテーブルデータモデル
21 public class SequenceTrackListTableModel extends AbstractTableModel {
27 TRACK_NUMBER("No.", Integer.class, 20),
29 EVENTS("Events", Integer.class, 40),
31 MUTE("Mute", Boolean.class, 30),
33 SOLO("Solo", Boolean.class, 30),
35 RECORD_CHANNEL("RecCh", String.class, 40),
37 CHANNEL("Ch", String.class, 30),
39 TRACK_NAME("Track name", String.class, 100);
46 * @param widthRatio 幅の割合
47 * @param columnClass 列のクラス
48 * @param perferredWidth 列の適切な幅
50 private Column(String title, Class<?> columnClass, int preferredWidth) {
52 this.columnClass = columnClass;
53 this.preferredWidth = preferredWidth;
56 private PlaylistTableModel sequenceListTableModel;
58 * このモデルを収容している親のプレイリストを返します。
60 public PlaylistTableModel getParent() { return sequenceListTableModel; }
64 private Sequence sequence;
66 * ラップされたMIDIシーケンスのtickインデックス
68 private SequenceTickIndex sequenceTickIndex;
72 private String filename = "";
74 * テキスト部分の文字コード(タイトル、歌詞など)
76 public Charset charset = Charset.defaultCharset();
80 private List<TrackEventListTableModel> trackModelList = new ArrayList<>();
81 private ListSelectionModel trackListSelectionModel = new DefaultListSelectionModel(){
83 setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
89 public ListSelectionModel getSelectionModel() { return trackListSelectionModel; }
91 * MIDIシーケンスとファイル名から {@link SequenceTrackListTableModel} を構築します。
92 * @param sequenceListTableModel 親のプレイリスト
93 * @param sequence MIDIシーケンス
94 * @param filename ファイル名
96 public SequenceTrackListTableModel(
97 PlaylistTableModel sequenceListTableModel,
101 this.sequenceListTableModel = sequenceListTableModel;
102 setSequence(sequence);
103 setFilename(filename);
106 public int getRowCount() {
107 return sequence == null ? 0 : sequence.getTracks().length;
110 public int getColumnCount() {
111 return Column.values().length;
118 public String getColumnName(int column) {
119 return Column.values()[column].title;
126 public Class<?> getColumnClass(int column) {
127 SequenceTrackListTableModel.Column c = Column.values()[column];
130 case SOLO: if( ! isOnSequencer() ) return String.class;
132 default: return c.columnClass;
136 public Object getValueAt(int row, int column) {
137 SequenceTrackListTableModel.Column c = Column.values()[column];
139 case TRACK_NUMBER: return row;
140 case EVENTS: return sequence.getTracks()[row].size();
142 return isOnSequencer() ? sequenceListTableModel.getSequencerModel().getSequencer().getTrackMute(row) : "";
144 return isOnSequencer() ? sequenceListTableModel.getSequencerModel().getSequencer().getTrackSolo(row) : "";
146 return isOnSequencer() ? trackModelList.get(row).getRecordingChannel() : "";
148 int ch = trackModelList.get(row).getChannel();
149 return ch < 0 ? "" : ch + 1 ;
151 case TRACK_NAME: return trackModelList.get(row).toString();
159 public boolean isCellEditable(int row, int column) {
160 SequenceTrackListTableModel.Column c = Column.values()[column];
164 case RECORD_CHANNEL: return isOnSequencer();
166 case TRACK_NAME: return true;
167 default: return false;
174 public void setValueAt(Object val, int row, int column) {
175 SequenceTrackListTableModel.Column c = Column.values()[column];
178 sequenceListTableModel.getSequencerModel().getSequencer().setTrackMute(row, ((Boolean)val).booleanValue());
181 sequenceListTableModel.getSequencerModel().getSequencer().setTrackSolo(row, ((Boolean)val).booleanValue());
184 trackModelList.get(row).setRecordingChannel((String)val);
189 ch = new Integer((String)val);
191 catch( NumberFormatException e ) {
195 if( --ch <= 0 || ch > MIDISpec.MAX_CHANNELS )
197 TrackEventListTableModel trackTableModel = trackModelList.get(row);
198 if( ch == trackTableModel.getChannel() ) break;
199 trackTableModel.setChannel(ch);
201 fireTableCellUpdated(row, Column.EVENTS.ordinal());
205 trackModelList.get(row).setString((String)val);
210 fireTableCellUpdated(row,column);
216 public Sequence getSequence() { return sequence; }
218 * シーケンスtickインデックスを返します。
219 * @return シーケンスtickインデックス
221 public SequenceTickIndex getSequenceTickIndex() { return sequenceTickIndex; }
224 * @param sequence MIDIシーケンス(nullを指定するとトラックリストが空になる)
226 private void setSequence(Sequence sequence) {
229 sequenceListTableModel.getSequencerModel().getSequencer().recordDisable(null); // The "null" means all tracks
232 int oldSize = trackModelList.size();
234 trackModelList.clear();
235 fireTableRowsDeleted(0, oldSize-1);
238 if( (this.sequence = sequence) == null ) {
240 sequenceTickIndex = null;
244 fireTimeSignatureChanged();
247 Track tracks[] = sequence.getTracks();
248 for(Track track : tracks) {
249 trackModelList.add(new TrackEventListTableModel(this, track));
252 Charset cs = MIDISpec.getCharsetOf(sequence);
253 charset = cs==null ? Charset.defaultCharset() : cs;
256 fireTableRowsInserted(0, tracks.length-1);
259 * 拍子が変更されたとき、シーケンスtickインデックスを再作成します。
261 public void fireTimeSignatureChanged() {
262 sequenceTickIndex = new SequenceTickIndex(sequence);
264 private boolean isModified = false;
267 * @return 変更済みのときtrue
269 public boolean isModified() { return isModified; }
272 * @param isModified 変更されたときtrue
274 public void setModified(boolean isModified) { this.isModified = isModified; }
277 * @param filename ファイル名
279 public void setFilename(String filename) { this.filename = filename; }
284 public String getFilename() { return filename; }
286 * このシーケンスを表す文字列としてシーケンス名を返します。シーケンス名がない場合は空文字列を返します。
289 public String toString() {
290 byte b[] = MIDISpec.getNameBytesOf(sequence);
291 return b == null ? "" : new String(b, charset);
298 public boolean setName(String name) {
299 if( name.equals(toString()) ) return false;
300 if( ! MIDISpec.setNameBytesOf(sequence, name.getBytes(charset)) ) return false;
302 fireTableDataChanged();
306 * このシーケンスのMIDIデータのバイト列を返します。
307 * @return MIDIデータのバイト列(ない場合はnull)
308 * @throws IOException バイト列の出力に失敗した場合
310 public byte[] getMIDIdata() throws IOException {
311 if( sequence == null || sequence.getTracks().length == 0 ) {
314 try( ByteArrayOutputStream out = new ByteArrayOutputStream() ) {
315 MidiSystem.write(sequence, 1, out);
316 return out.toByteArray();
317 } catch ( IOException e ) {
322 * 指定のトラックが変更されたことを通知します。
325 public void fireTrackChanged(Track track) {
326 int row = indexOf(track);
327 if( row < 0 ) return;
328 fireTableRowsUpdated(row, row);
329 sequenceListTableModel.fireSequenceModified(this, true);
332 * 選択されているトラックモデルを返します。
333 * @param index トラックのインデックス
334 * @return トラックモデル(見つからない場合null)
336 public TrackEventListTableModel getSelectedTrackModel() {
337 if( trackListSelectionModel.isSelectionEmpty() )
339 int index = trackListSelectionModel.getMinSelectionIndex();
340 Track tracks[] = sequence.getTracks();
341 if( tracks.length != 0 ) {
342 Track track = tracks[index];
343 for( TrackEventListTableModel model : trackModelList )
344 if( model.getTrack() == track )
350 * 指定のトラックがある位置のインデックスを返します。
352 * @return トラックのインデックス(先頭 0、トラックが見つからない場合 -1)
354 public int indexOf(Track track) {
355 Track tracks[] = sequence.getTracks();
356 for( int i=0; i<tracks.length; i++ )
357 if( tracks[i] == track )
362 * 新しいトラックを生成し、末尾に追加します。
363 * @return 追加したトラックのインデックス(先頭 0)
365 public int createTrack() {
366 Track newTrack = sequence.createTrack();
367 trackModelList.add(new TrackEventListTableModel(this, newTrack));
368 int lastRow = getRowCount() - 1;
369 fireTableRowsInserted(lastRow, lastRow);
370 sequenceListTableModel.fireSelectedSequenceModified(true);
371 trackListSelectionModel.setSelectionInterval(lastRow, lastRow);
377 public void deleteSelectedTracks() {
378 if( trackListSelectionModel.isSelectionEmpty() )
380 int minIndex = trackListSelectionModel.getMinSelectionIndex();
381 int maxIndex = trackListSelectionModel.getMaxSelectionIndex();
382 Track tracks[] = sequence.getTracks();
383 for( int i = maxIndex; i >= minIndex; i-- ) {
384 if( ! trackListSelectionModel.isSelectedIndex(i) )
386 sequence.deleteTrack(tracks[i]);
387 trackModelList.remove(i);
389 fireTableRowsDeleted(minIndex, maxIndex);
390 sequenceListTableModel.fireSelectedSequenceModified(true);
393 * このシーケンスモデルのシーケンスをシーケンサーが操作しているか調べます。
394 * @return シーケンサーが操作していたらtrue
396 public boolean isOnSequencer() {
397 return sequence == sequenceListTableModel.getSequencerModel().getSequencer().getSequence();
400 * 録音しようとしているチャンネルの設定されたトラックがあるか調べます。
401 * @return 該当トラックがあればtrue
403 public boolean hasRecordChannel() {
404 int rowCount = getRowCount();
405 for( int row=0; row < rowCount; row++ ) {
406 Object value = getValueAt(row, Column.RECORD_CHANNEL.ordinal());
407 if( ! "OFF".equals(value) ) return true;