OSDN Git Service

リファクタリング
[midichordhelper/MIDIChordHelper.git] / src / camidion / chordhelper / midieditor / SequenceTrackListTableModel.java
1 package camidion.chordhelper.midieditor;
2
3 import java.io.ByteArrayOutputStream;
4 import java.io.IOException;
5 import java.nio.charset.Charset;
6 import java.util.ArrayList;
7 import java.util.List;
8
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;
15
16 import camidion.chordhelper.music.MIDISpec;
17
18 /**
19  * MIDIシーケンス(トラックリスト)のテーブルデータモデル
20  */
21 public class SequenceTrackListTableModel extends AbstractTableModel {
22         /**
23          * 列の列挙型
24          */
25         public enum Column {
26                 /** トラック番号 */
27                 TRACK_NUMBER("No.", Integer.class, 20),
28                 /** イベント数 */
29                 EVENTS("Events", Integer.class, 40),
30                 /** Mute */
31                 MUTE("Mute", Boolean.class, 30),
32                 /** Solo */
33                 SOLO("Solo", Boolean.class, 30),
34                 /** 録音するMIDIチャンネル */
35                 RECORD_CHANNEL("RecCh", String.class, 40),
36                 /** MIDIチャンネル */
37                 CHANNEL("Ch", String.class, 30),
38                 /** トラック名 */
39                 TRACK_NAME("Track name", String.class, 100);
40                 String title;
41                 Class<?> columnClass;
42                 int preferredWidth;
43                 /**
44                  * 列の識別子を構築します。
45                  * @param title 列のタイトル
46                  * @param widthRatio 幅の割合
47                  * @param columnClass 列のクラス
48                  * @param perferredWidth 列の適切な幅
49                  */
50                 private Column(String title, Class<?> columnClass, int preferredWidth) {
51                         this.title = title;
52                         this.columnClass = columnClass;
53                         this.preferredWidth = preferredWidth;
54                 }
55         }
56         private PlaylistTableModel sequenceListTableModel;
57         /**
58          * このモデルを収容している親のプレイリストを返します。
59          */
60         public PlaylistTableModel getParent() { return sequenceListTableModel; }
61         /**
62          * ラップされたMIDIシーケンス
63          */
64         private Sequence sequence;
65         /**
66          * ラップされたMIDIシーケンスのtickインデックス
67          */
68         private SequenceTickIndex sequenceTickIndex;
69         /**
70          * MIDIファイル名
71          */
72         private String filename = "";
73         /**
74          * テキスト部分の文字コード(タイトル、歌詞など)
75          */
76         public Charset charset = Charset.defaultCharset();
77         /**
78          * トラックリスト
79          */
80         private List<TrackEventListTableModel> trackModelList = new ArrayList<>();
81         private ListSelectionModel trackListSelectionModel = new DefaultListSelectionModel(){
82                 {
83                         setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
84                 }
85         };
86         /**
87          * 選択状態を返します。
88          */
89         public ListSelectionModel getSelectionModel() { return trackListSelectionModel; }
90         /**
91          * MIDIシーケンスとファイル名から {@link SequenceTrackListTableModel} を構築します。
92          * @param sequenceListTableModel 親のプレイリスト
93          * @param sequence MIDIシーケンス
94          * @param filename ファイル名
95          */
96         public SequenceTrackListTableModel(
97                 PlaylistTableModel sequenceListTableModel,
98                 Sequence sequence,
99                 String filename
100         ) {
101                 this.sequenceListTableModel = sequenceListTableModel;
102                 setSequence(sequence);
103                 setFilename(filename);
104         }
105         @Override
106         public int getRowCount() {
107                 return sequence == null ? 0 : sequence.getTracks().length;
108         }
109         @Override
110         public int getColumnCount() {
111                 return Column.values().length;
112         }
113         /**
114          * 列名を返します。
115          * @return 列名
116          */
117         @Override
118         public String getColumnName(int column) {
119                 return Column.values()[column].title;
120         }
121         /**
122          * 指定された列の型を返します。
123          * @return 指定された列の型
124          */
125         @Override
126         public Class<?> getColumnClass(int column) {
127                 SequenceTrackListTableModel.Column c = Column.values()[column];
128                 switch(c) {
129                 case MUTE:
130                 case SOLO: if( ! isOnSequencer() ) return String.class;
131                         // FALLTHROUGH
132                 default: return c.columnClass;
133                 }
134         }
135         @Override
136         public Object getValueAt(int row, int column) {
137                 SequenceTrackListTableModel.Column c = Column.values()[column];
138                 switch(c) {
139                 case TRACK_NUMBER: return row;
140                 case EVENTS: return sequence.getTracks()[row].size();
141                 case MUTE:
142                         return isOnSequencer() ? sequenceListTableModel.getSequencerModel().getSequencer().getTrackMute(row) : "";
143                 case SOLO:
144                         return isOnSequencer() ? sequenceListTableModel.getSequencerModel().getSequencer().getTrackSolo(row) : "";
145                 case RECORD_CHANNEL:
146                         return isOnSequencer() ? trackModelList.get(row).getRecordingChannel() : "";
147                 case CHANNEL: {
148                         int ch = trackModelList.get(row).getChannel();
149                         return ch < 0 ? "" : ch + 1 ;
150                 }
151                 case TRACK_NAME: return trackModelList.get(row).toString();
152                 default: return "";
153                 }
154         }
155         /**
156          * セルが編集可能かどうかを返します。
157          */
158         @Override
159         public boolean isCellEditable(int row, int column) {
160                 SequenceTrackListTableModel.Column c = Column.values()[column];
161                 switch(c) {
162                 case MUTE:
163                 case SOLO:
164                 case RECORD_CHANNEL: return isOnSequencer();
165                 case CHANNEL:
166                 case TRACK_NAME: return true;
167                 default: return false;
168                 }
169         }
170         /**
171          * 列の値を設定します。
172          */
173         @Override
174         public void setValueAt(Object val, int row, int column) {
175                 SequenceTrackListTableModel.Column c = Column.values()[column];
176                 switch(c) {
177                 case MUTE:
178                         sequenceListTableModel.getSequencerModel().getSequencer().setTrackMute(row, ((Boolean)val).booleanValue());
179                         break;
180                 case SOLO:
181                         sequenceListTableModel.getSequencerModel().getSequencer().setTrackSolo(row, ((Boolean)val).booleanValue());
182                         break;
183                 case RECORD_CHANNEL:
184                         trackModelList.get(row).setRecordingChannel((String)val);
185                         break;
186                 case CHANNEL: {
187                         Integer ch;
188                         try {
189                                 ch = new Integer((String)val);
190                         }
191                         catch( NumberFormatException e ) {
192                                 ch = -1;
193                                 break;
194                         }
195                         if( --ch <= 0 || ch > MIDISpec.MAX_CHANNELS )
196                                 break;
197                         TrackEventListTableModel trackTableModel = trackModelList.get(row);
198                         if( ch == trackTableModel.getChannel() ) break;
199                         trackTableModel.setChannel(ch);
200                         setModified(true);
201                         fireTableCellUpdated(row, Column.EVENTS.ordinal());
202                         break;
203                 }
204                 case TRACK_NAME:
205                         trackModelList.get(row).setString((String)val);
206                         break;
207                 default:
208                         break;
209                 }
210                 fireTableCellUpdated(row,column);
211         }
212         /**
213          * MIDIシーケンスを返します。
214          * @return MIDIシーケンス
215          */
216         public Sequence getSequence() { return sequence; }
217         /**
218          * シーケンスtickインデックスを返します。
219          * @return シーケンスtickインデックス
220          */
221         public SequenceTickIndex getSequenceTickIndex() { return sequenceTickIndex; }
222         /**
223          * MIDIシーケンスを設定します。
224          * @param sequence MIDIシーケンス(nullを指定するとトラックリストが空になる)
225          */
226         private void setSequence(Sequence sequence) {
227                 //
228                 // 旧シーケンスの録音モードを解除
229                 sequenceListTableModel.getSequencerModel().getSequencer().recordDisable(null); // The "null" means all tracks
230                 //
231                 // トラックリストをクリア
232                 int oldSize = trackModelList.size();
233                 if( oldSize > 0 ) {
234                         trackModelList.clear();
235                         fireTableRowsDeleted(0, oldSize-1);
236                 }
237                 // 新シーケンスに置き換える
238                 if( (this.sequence = sequence) == null ) {
239                         // 新シーケンスがない場合
240                         sequenceTickIndex = null;
241                         return;
242                 }
243                 // tickインデックスを再構築
244                 fireTimeSignatureChanged();
245                 //
246                 // トラックリストを再構築
247                 Track tracks[] = sequence.getTracks();
248                 for(Track track : tracks) {
249                         trackModelList.add(new TrackEventListTableModel(this, track));
250                 }
251                 // 文字コードの判定
252                 Charset cs = MIDISpec.getCharsetOf(sequence);
253                 charset = cs==null ? Charset.defaultCharset() : cs;
254                 //
255                 // トラックが挿入されたことを通知
256                 fireTableRowsInserted(0, tracks.length-1);
257         }
258         /**
259          * 拍子が変更されたとき、シーケンスtickインデックスを再作成します。
260          */
261         public void fireTimeSignatureChanged() {
262                 sequenceTickIndex = new SequenceTickIndex(sequence);
263         }
264         private boolean isModified = false;
265         /**
266          * 変更されたかどうかを返します。
267          * @return 変更済みのときtrue
268          */
269         public boolean isModified() { return isModified; }
270         /**
271          * 変更されたかどうかを設定します。
272          * @param isModified 変更されたときtrue
273          */
274         public void setModified(boolean isModified) { this.isModified = isModified; }
275         /**
276          * ファイル名を設定します。
277          * @param filename ファイル名
278          */
279         public void setFilename(String filename) { this.filename = filename; }
280         /**
281          * ファイル名を返します。
282          * @return ファイル名
283          */
284         public String getFilename() { return filename; }
285         /**
286          * このシーケンスを表す文字列としてシーケンス名を返します。シーケンス名がない場合は空文字列を返します。
287          */
288         @Override
289         public String toString() {
290                 byte b[] = MIDISpec.getNameBytesOf(sequence);
291                 return b == null ? "" : new String(b, charset);
292         }
293         /**
294          * シーケンス名を設定します。
295          * @param name シーケンス名
296          * @return 成功したらtrue
297          */
298         public boolean setName(String name) {
299                 if( name.equals(toString()) ) return false;
300                 if( ! MIDISpec.setNameBytesOf(sequence, name.getBytes(charset)) ) return false;
301                 setModified(true);
302                 fireTableDataChanged();
303                 return true;
304         }
305         /**
306          * このシーケンスのMIDIデータのバイト列を返します。
307          * @return MIDIデータのバイト列(ない場合はnull)
308          * @throws IOException バイト列の出力に失敗した場合
309          */
310         public byte[] getMIDIdata() throws IOException {
311                 if( sequence == null || sequence.getTracks().length == 0 ) {
312                         return null;
313                 }
314                 try( ByteArrayOutputStream out = new ByteArrayOutputStream() ) {
315                         MidiSystem.write(sequence, 1, out);
316                         return out.toByteArray();
317                 } catch ( IOException e ) {
318                         throw e;
319                 }
320         }
321         /**
322          * 指定のトラックが変更されたことを通知します。
323          * @param track トラック
324          */
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);
330         }
331         /**
332          * 選択されているトラックモデルを返します。
333          * @param index トラックのインデックス
334          * @return トラックモデル(見つからない場合null)
335          */
336         public TrackEventListTableModel getSelectedTrackModel() {
337                 if( trackListSelectionModel.isSelectionEmpty() )
338                         return null;
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 )
345                                         return model;
346                 }
347                 return null;
348         }
349         /**
350          * 指定のトラックがある位置のインデックスを返します。
351          * @param track トラック
352          * @return トラックのインデックス(先頭 0、トラックが見つからない場合 -1)
353          */
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 )
358                                 return i;
359                 return -1;
360         }
361         /**
362          * 新しいトラックを生成し、末尾に追加します。
363          * @return 追加したトラックのインデックス(先頭 0)
364          */
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);
372                 return lastRow;
373         }
374         /**
375          * 選択されているトラックを削除します。
376          */
377         public void deleteSelectedTracks() {
378                 if( trackListSelectionModel.isSelectionEmpty() )
379                         return;
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) )
385                                 continue;
386                         sequence.deleteTrack(tracks[i]);
387                         trackModelList.remove(i);
388                 }
389                 fireTableRowsDeleted(minIndex, maxIndex);
390                 sequenceListTableModel.fireSelectedSequenceModified(true);
391         }
392         /**
393          * このシーケンスモデルのシーケンスをシーケンサーが操作しているか調べます。
394          * @return シーケンサーが操作していたらtrue
395          */
396         public boolean isOnSequencer() {
397                 return sequence == sequenceListTableModel.getSequencerModel().getSequencer().getSequence();
398         }
399         /**
400          * 録音しようとしているチャンネルの設定されたトラックがあるか調べます。
401          * @return 該当トラックがあればtrue
402          */
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;
408                 }
409                 return false;
410         }
411 }