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