1 package camidion.chordhelper.midieditor;
3 import java.io.ByteArrayOutputStream;
4 import java.io.IOException;
5 import java.io.UnsupportedEncodingException;
6 import java.nio.charset.Charset;
7 import java.util.ArrayList;
12 import javax.sound.midi.MidiSystem;
13 import javax.sound.midi.Sequence;
14 import javax.sound.midi.Track;
15 import javax.swing.DefaultListSelectionModel;
16 import javax.swing.ListSelectionModel;
17 import javax.swing.table.AbstractTableModel;
19 import camidion.chordhelper.music.MIDISpec;
22 * MIDIシーケンス(トラックリスト)のテーブルデータモデル
24 public class SequenceTrackListTableModel extends AbstractTableModel {
30 TRACK_NUMBER("No.", Integer.class, 20),
32 EVENTS("Events", Integer.class, 40),
34 MUTE("Mute", Boolean.class, 30),
36 SOLO("Solo", Boolean.class, 30),
38 RECORD_CHANNEL("RecCh", String.class, 40),
40 CHANNEL("Ch", String.class, 30),
42 TRACK_NAME("Track name", String.class, 100);
49 * @param widthRatio 幅の割合
50 * @param columnClass 列のクラス
51 * @param perferredWidth 列の適切な幅
53 private Column(String title, Class<?> columnClass, int preferredWidth) {
55 this.columnClass = columnClass;
56 this.preferredWidth = preferredWidth;
62 PlaylistTableModel sequenceListTableModel;
66 private Sequence sequence;
68 * ラップされたMIDIシーケンスのtickインデックス
70 private SequenceTickIndex sequenceTickIndex;
74 private String filename = "";
76 * テキスト部分の文字コード(タイトル、歌詞など)
78 public Charset charset = Charset.defaultCharset();
82 private List<TrackEventListTableModel> trackModelList = new ArrayList<>();
86 ListSelectionModel trackListSelectionModel = new DefaultListSelectionModel(){
88 setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
92 * MIDIシーケンスとファイル名から {@link SequenceTrackListTableModel} を構築します。
93 * @param sequenceListTableModel 親のプレイリスト
94 * @param sequence MIDIシーケンス
95 * @param filename ファイル名
97 public SequenceTrackListTableModel(
98 PlaylistTableModel sequenceListTableModel,
102 this.sequenceListTableModel = sequenceListTableModel;
103 setSequence(sequence);
104 setFilename(filename);
107 public int getRowCount() {
108 return sequence == null ? 0 : sequence.getTracks().length;
111 public int getColumnCount() {
112 return Column.values().length;
119 public String getColumnName(int column) {
120 return Column.values()[column].title;
127 public Class<?> getColumnClass(int column) {
128 SequenceTrackListTableModel.Column c = Column.values()[column];
131 case SOLO: if( ! isOnSequencer() ) return String.class;
133 default: return c.columnClass;
137 public Object getValueAt(int row, int column) {
138 SequenceTrackListTableModel.Column c = Column.values()[column];
140 case TRACK_NUMBER: return row;
141 case EVENTS: return sequence.getTracks()[row].size();
143 return isOnSequencer() ? sequenceListTableModel.sequencerModel.getSequencer().getTrackMute(row) : "";
145 return isOnSequencer() ? sequenceListTableModel.sequencerModel.getSequencer().getTrackSolo(row) : "";
147 return isOnSequencer() ? trackModelList.get(row).getRecordingChannel() : "";
149 int ch = trackModelList.get(row).getChannel();
150 return ch < 0 ? "" : ch + 1 ;
152 case TRACK_NAME: return trackModelList.get(row).toString();
160 public boolean isCellEditable(int row, int column) {
161 SequenceTrackListTableModel.Column c = Column.values()[column];
165 case RECORD_CHANNEL: return isOnSequencer();
167 case TRACK_NAME: return true;
168 default: return false;
175 public void setValueAt(Object val, int row, int column) {
176 SequenceTrackListTableModel.Column c = Column.values()[column];
179 sequenceListTableModel.sequencerModel.getSequencer().setTrackMute(row, ((Boolean)val).booleanValue());
182 sequenceListTableModel.sequencerModel.getSequencer().setTrackSolo(row, ((Boolean)val).booleanValue());
185 trackModelList.get(row).setRecordingChannel((String)val);
190 ch = new Integer((String)val);
192 catch( NumberFormatException e ) {
196 if( --ch <= 0 || ch > MIDISpec.MAX_CHANNELS )
198 TrackEventListTableModel trackTableModel = trackModelList.get(row);
199 if( ch == trackTableModel.getChannel() ) break;
200 trackTableModel.setChannel(ch);
202 fireTableCellUpdated(row, Column.EVENTS.ordinal());
206 trackModelList.get(row).setString((String)val);
211 fireTableCellUpdated(row,column);
217 public Sequence getSequence() { return sequence; }
219 * シーケンスtickインデックスを返します。
220 * @return シーケンスtickインデックス
222 public SequenceTickIndex getSequenceTickIndex() {
223 return sequenceTickIndex;
227 * @param sequence MIDIシーケンス(nullを指定するとトラックリストが空になる)
229 private void setSequence(Sequence sequence) {
232 sequenceListTableModel.sequencerModel.getSequencer().recordDisable(null); // The "null" means all tracks
235 int oldSize = trackModelList.size();
237 trackModelList.clear();
238 fireTableRowsDeleted(0, oldSize-1);
241 if( (this.sequence = sequence) == null ) {
243 sequenceTickIndex = null;
247 fireTimeSignatureChanged();
250 Track tracks[] = sequence.getTracks();
251 for(Track track : tracks) {
252 trackModelList.add(new TrackEventListTableModel(this, track));
255 byte b[] = MIDISpec.getNameBytesOf(sequence);
256 if( b != null && b.length > 0 ) {
258 String autoDetectedName = new String(b, "JISAutoDetect");
259 Set<Map.Entry<String,Charset>> entrySet;
260 entrySet = Charset.availableCharsets().entrySet();
261 for( Map.Entry<String,Charset> entry : entrySet ) {
262 Charset cs = entry.getValue();
263 if( ! autoDetectedName.equals(new String(b, cs)) )
268 } catch (UnsupportedEncodingException e) {
273 fireTableRowsInserted(0, tracks.length-1);
276 * 拍子が変更されたとき、シーケンスtickインデックスを再作成します。
278 public void fireTimeSignatureChanged() {
279 sequenceTickIndex = new SequenceTickIndex(sequence);
281 private boolean isModified = false;
284 * @return 変更済みのときtrue
286 public boolean isModified() { return isModified; }
289 * @param isModified 変更されたときtrue
291 public void setModified(boolean isModified) { this.isModified = isModified; }
294 * @param filename ファイル名
296 public void setFilename(String filename) { this.filename = filename; }
301 public String getFilename() { return filename; }
303 public String toString() {
304 byte b[] = MIDISpec.getNameBytesOf(sequence);
305 return b == null ? "" : new String(b, charset);
312 public boolean setName(String name) {
313 if( name.equals(toString()) )
315 byte b[] = name.getBytes(charset);
316 if( ! MIDISpec.setNameBytesOf(sequence, b) )
319 fireTableDataChanged();
323 * このシーケンスのMIDIデータのバイト列を返します。
324 * @return MIDIデータのバイト列(失敗した場合null)
326 public byte[] getMIDIdata() {
327 if( sequence == null || sequence.getTracks().length == 0 ) {
330 try( ByteArrayOutputStream out = new ByteArrayOutputStream() ) {
331 MidiSystem.write(sequence, 1, out);
332 return out.toByteArray();
333 } catch ( IOException e ) {
339 * 指定のトラックが変更されたことを通知します。
342 public void fireTrackChanged(Track track) {
343 int row = indexOf(track);
344 if( row < 0 ) return;
345 fireTableRowsUpdated(row, row);
346 sequenceListTableModel.fireSequenceModified(this);
349 * 選択されているトラックモデルを返します。
350 * @param index トラックのインデックス
351 * @return トラックモデル(見つからない場合null)
353 public TrackEventListTableModel getSelectedTrackModel() {
354 if( trackListSelectionModel.isSelectionEmpty() )
356 int index = trackListSelectionModel.getMinSelectionIndex();
357 Track tracks[] = sequence.getTracks();
358 if( tracks.length != 0 ) {
359 Track track = tracks[index];
360 for( TrackEventListTableModel model : trackModelList )
361 if( model.getTrack() == track )
367 * 指定のトラックがある位置のインデックスを返します。
369 * @return トラックのインデックス(先頭 0、トラックが見つからない場合 -1)
371 public int indexOf(Track track) {
372 Track tracks[] = sequence.getTracks();
373 for( int i=0; i<tracks.length; i++ )
374 if( tracks[i] == track )
379 * 新しいトラックを生成し、末尾に追加します。
380 * @return 追加したトラックのインデックス(先頭 0)
382 public int createTrack() {
383 Track newTrack = sequence.createTrack();
384 trackModelList.add(new TrackEventListTableModel(this, newTrack));
385 int lastRow = getRowCount() - 1;
386 fireTableRowsInserted(lastRow, lastRow);
387 sequenceListTableModel.fireSelectedSequenceModified();
388 trackListSelectionModel.setSelectionInterval(lastRow, lastRow);
394 public void deleteSelectedTracks() {
395 if( trackListSelectionModel.isSelectionEmpty() )
397 int minIndex = trackListSelectionModel.getMinSelectionIndex();
398 int maxIndex = trackListSelectionModel.getMaxSelectionIndex();
399 Track tracks[] = sequence.getTracks();
400 for( int i = maxIndex; i >= minIndex; i-- ) {
401 if( ! trackListSelectionModel.isSelectedIndex(i) )
403 sequence.deleteTrack(tracks[i]);
404 trackModelList.remove(i);
406 fireTableRowsDeleted(minIndex, maxIndex);
407 sequenceListTableModel.fireSelectedSequenceModified();
410 * このシーケンスモデルのシーケンスをシーケンサーが操作しているか調べます。
411 * @return シーケンサーが操作していたらtrue
413 public boolean isOnSequencer() {
414 return sequence == sequenceListTableModel.sequencerModel.getSequencer().getSequence();
417 * 録音しようとしているチャンネルの設定されたトラックがあるか調べます。
418 * @return 該当トラックがあればtrue
420 public boolean hasRecordChannel() {
421 int rowCount = getRowCount();
422 for( int row=0; row < rowCount; row++ ) {
423 Object value = getValueAt(row, Column.RECORD_CHANNEL.ordinal());
424 if( ! "OFF".equals(value) ) return true;