OSDN Git Service

リファクタリング for ハロウィンバージョン
[midichordhelper/MIDIChordHelper.git] / src / MIDIEditor.java
1 \r
2 import java.awt.Component;\r
3 import java.awt.Container;\r
4 import java.awt.Dimension;\r
5 import java.awt.FlowLayout;\r
6 import java.awt.Insets;\r
7 import java.awt.datatransfer.DataFlavor;\r
8 import java.awt.datatransfer.Transferable;\r
9 import java.awt.dnd.DnDConstants;\r
10 import java.awt.dnd.DropTarget;\r
11 import java.awt.dnd.DropTargetDragEvent;\r
12 import java.awt.dnd.DropTargetDropEvent;\r
13 import java.awt.dnd.DropTargetEvent;\r
14 import java.awt.dnd.DropTargetListener;\r
15 import java.awt.event.ActionEvent;\r
16 import java.awt.event.ActionListener;\r
17 import java.awt.event.ItemEvent;\r
18 import java.awt.event.ItemListener;\r
19 import java.awt.event.MouseEvent;\r
20 import java.io.ByteArrayInputStream;\r
21 import java.io.ByteArrayOutputStream;\r
22 import java.io.EOFException;\r
23 import java.io.File;\r
24 import java.io.FileInputStream;\r
25 import java.io.FileNotFoundException;\r
26 import java.io.FileOutputStream;\r
27 import java.io.IOException;\r
28 import java.io.InputStream;\r
29 import java.net.MalformedURLException;\r
30 import java.net.URI;\r
31 import java.net.URISyntaxException;\r
32 import java.net.URL;\r
33 import java.security.AccessControlException;\r
34 import java.util.ArrayList;\r
35 import java.util.EventObject;\r
36 import java.util.Vector;\r
37 \r
38 import javax.sound.midi.InvalidMidiDataException;\r
39 import javax.sound.midi.MetaMessage;\r
40 import javax.sound.midi.MidiChannel;\r
41 import javax.sound.midi.MidiEvent;\r
42 import javax.sound.midi.MidiMessage;\r
43 import javax.sound.midi.MidiSystem;\r
44 import javax.sound.midi.Sequence;\r
45 import javax.sound.midi.Sequencer;\r
46 import javax.sound.midi.ShortMessage;\r
47 import javax.sound.midi.SysexMessage;\r
48 import javax.sound.midi.Track;\r
49 import javax.swing.AbstractAction;\r
50 import javax.swing.AbstractCellEditor;\r
51 import javax.swing.Action;\r
52 import javax.swing.Box;\r
53 import javax.swing.BoxLayout;\r
54 import javax.swing.DefaultCellEditor;\r
55 import javax.swing.Icon;\r
56 import javax.swing.JButton;\r
57 import javax.swing.JCheckBox;\r
58 import javax.swing.JComboBox;\r
59 import javax.swing.JDialog;\r
60 import javax.swing.JFileChooser;\r
61 import javax.swing.JLabel;\r
62 import javax.swing.JOptionPane;\r
63 import javax.swing.JPanel;\r
64 import javax.swing.JScrollPane;\r
65 import javax.swing.JSplitPane;\r
66 import javax.swing.JTable;\r
67 import javax.swing.JToggleButton;\r
68 import javax.swing.ListSelectionModel;\r
69 import javax.swing.event.ChangeEvent;\r
70 import javax.swing.event.ChangeListener;\r
71 import javax.swing.event.ListSelectionEvent;\r
72 import javax.swing.event.ListSelectionListener;\r
73 import javax.swing.filechooser.FileNameExtensionFilter;\r
74 import javax.swing.table.AbstractTableModel;\r
75 import javax.swing.table.TableCellEditor;\r
76 import javax.swing.table.TableColumnModel;\r
77 import javax.swing.table.TableModel;\r
78 \r
79 /**\r
80  * MIDI Editor/Playlist for MIDI Chord Helper\r
81  *\r
82  * @author\r
83  *      Copyright (C) 2006-2013 Akiyoshi Kamide\r
84  *      http://www.yk.rim.or.jp/~kamide/music/chordhelper/\r
85  */\r
86 class MidiEditor extends JDialog implements DropTargetListener, ListSelectionListener {\r
87         Insets  zero_insets = new Insets(0,0,0,0);\r
88 \r
89         MidiDeviceManager deviceManager;\r
90 \r
91         SequenceListModel seqListModel;\r
92         JFileChooser    file_chooser = null;\r
93         Base64Dialog    base64_dialog = null;\r
94         NewSequenceDialog       new_seq_dialog;\r
95         MidiEventDialog eventDialog = new MidiEventDialog();\r
96 \r
97         MidiEvent copied_events[] = null;\r
98         int copied_events_PPQ = 0;\r
99 \r
100         ListSelectionModel\r
101         seq_selection_model,\r
102         track_selection_model,\r
103         event_selection_model;\r
104 \r
105         JTable\r
106         sequence_table_view,\r
107         track_table_view,\r
108         event_table_view;\r
109 \r
110         JScrollPane\r
111         scrollable_sequence_table,\r
112         scrollable_track_table_view,\r
113         scrollable_event_table_view;\r
114 \r
115         JLabel\r
116         total_time_label,\r
117         tracks_label,\r
118         midi_events_label;\r
119 \r
120         JButton\r
121         add_new_sequence_button, delete_sequence_button,\r
122         base64_encode_button = null,\r
123         add_midi_file_button,\r
124         save_midi_file_button,\r
125         jump_sequence_button,\r
126         add_track_button, remove_track_button,\r
127         add_event_button, jump_event_button,\r
128         cut_event_button, copy_event_button,\r
129         paste_event_button, remove_event_button;\r
130 \r
131         JCheckBox pair_note_checkbox;\r
132 \r
133         JButton forward_button, backward_button;\r
134         JToggleButton play_pause_button;\r
135 \r
136         JSplitPane\r
137         sequence_split_pane, track_split_pane;\r
138 \r
139         VirtualMidiDevice midiDevice = new AbstractVirtualMidiDevice() {\r
140                 {\r
141                         info = new MyInfo();\r
142                         setMaxReceivers(0);\r
143                 }\r
144                 class MyInfo extends Info {\r
145                         protected MyInfo() {\r
146                                 super(\r
147                                         "MIDI Editor",\r
148                                         "Unknown vendor",\r
149                                         "MIDI sequence editor",\r
150                                         ""\r
151                                 );\r
152                         }\r
153                 }\r
154         };\r
155 \r
156         MidiEventCellEditor event_cell_editor;\r
157 \r
158         class MidiEventCellEditor extends AbstractCellEditor implements TableCellEditor {\r
159                 MidiEvent[] midi_events_to_be_removed; // 削除対象にする変更前イベント(null可)\r
160                 MidiTrackModel midi_track_model; // 対象トラック\r
161                 MidiSequenceModel seq_model;   // 対象シーケンス\r
162                 MidiEvent sel_midi_evt = null; // 選択されたイベント\r
163                 int sel_index = -1; // 選択されたイベントの場所\r
164                 long current_tick = 0; // 選択されたイベントのtick位置\r
165 \r
166                 TickPositionModel tick_position_model = new TickPositionModel();\r
167                 JToggleButton.ToggleButtonModel\r
168                 pair_note_on_off_model = new JToggleButton.ToggleButtonModel();\r
169 \r
170                 JButton edit_event_button = new JButton();\r
171 \r
172                 Action cancel_action = new AbstractAction() {\r
173                         { putValue(NAME,"Cancel"); }\r
174                         public void actionPerformed(ActionEvent e) {\r
175                                 fireEditingCanceled();\r
176                                 eventDialog.setVisible(false);\r
177                         }\r
178                 };\r
179 \r
180                 private void setSelectedEvent() {\r
181                         seq_model = seqListModel.getSequenceModel(seq_selection_model);\r
182                         eventDialog.midiMessageForm.durationForm.setPPQ(\r
183                                         seq_model.getSequence().getResolution()\r
184                                         );\r
185                         tick_position_model.setSequenceIndex(\r
186                                         seq_model.getSequenceIndex()\r
187                                         );\r
188                         sel_index = -1;\r
189                         current_tick = 0;\r
190                         sel_midi_evt = null;\r
191                         midi_track_model = (MidiTrackModel)event_table_view.getModel();\r
192                         if( ! event_selection_model.isSelectionEmpty() ) {\r
193                                 sel_index = event_selection_model.getMinSelectionIndex();\r
194                                 sel_midi_evt = midi_track_model.getMidiEvent(sel_index);\r
195                                 current_tick = sel_midi_evt.getTick();\r
196                                 tick_position_model.setTickPosition(current_tick);\r
197                         }\r
198                 }\r
199 \r
200                 // 指定のTick位置へジャンプ\r
201                 Action query_jump_event_action = new AbstractAction() {\r
202                         { putValue(NAME,"Jump to ..."); }\r
203                         public void actionPerformed(ActionEvent e) {\r
204                                 setSelectedEvent();\r
205                                 eventDialog.setTitle("Jump selection to");\r
206                                 eventDialog.okButton.setAction(jump_event_action);\r
207                                 eventDialog.openTickForm();\r
208                         }\r
209                 };\r
210                 Action jump_event_action = new AbstractAction() {\r
211                         { putValue(NAME,"Jump"); }\r
212                         public void actionPerformed(ActionEvent e) {\r
213                                 scrollToEventAt(\r
214                                                 tick_position_model.getTickPosition()\r
215                                                 );\r
216                                 eventDialog.setVisible(false);\r
217                         }\r
218                 };\r
219 \r
220                 // 指定のTick位置へ貼り付け\r
221                 Action query_paste_event_action = new AbstractAction() {\r
222                         { putValue(NAME,"Paste to ..."); }\r
223                         public void actionPerformed(ActionEvent e) {\r
224                                 setSelectedEvent();\r
225                                 eventDialog.setTitle("Paste to");\r
226                                 eventDialog.okButton.setAction(paste_event_action);\r
227                                 eventDialog.openTickForm();\r
228                         }\r
229                 };\r
230                 Action paste_event_action = new AbstractAction() {\r
231                         { putValue(NAME,"Paste"); }\r
232                         public void actionPerformed(ActionEvent e) {\r
233                                 long tick = tick_position_model.getTickPosition();\r
234                                 ((MidiTrackModel)event_table_view.getModel()).addMidiEvents(\r
235                                                 copied_events, tick, copied_events_PPQ\r
236                                                 );\r
237                                 scrollToEventAt( tick );\r
238                                 seqListModel.fireSequenceChanged(seq_selection_model);\r
239                                 eventDialog.setVisible(false);\r
240                         }\r
241                 };\r
242 \r
243                 // イベントの追加(または変更)\r
244                 Action query_add_event_action = new AbstractAction() {\r
245                         { putValue(NAME,"New"); }\r
246                         public void actionPerformed(ActionEvent e) {\r
247                                 setSelectedEvent();\r
248                                 midi_events_to_be_removed = null;\r
249                                 eventDialog.setTitle("Add a new MIDI event");\r
250                                 eventDialog.okButton.setAction(add_event_action);\r
251                                 int ch = midi_track_model.getChannel();\r
252                                 if( ch >= 0 ) {\r
253                                         eventDialog.midiMessageForm.channelText.setSelectedChannel(ch);\r
254                                 }\r
255                                 eventDialog.openEventForm();\r
256                         }\r
257                 };\r
258                 Action add_event_action = new AbstractAction() {\r
259                         { putValue(NAME,"OK"); }\r
260                         public void actionPerformed(ActionEvent e) {\r
261                                 long tick = tick_position_model.getTickPosition();\r
262                                 MidiMessage midi_msg = eventDialog.midiMessageForm.getMessage();\r
263                                 MidiEvent new_midi_event = new MidiEvent(midi_msg,tick);\r
264                                 if( midi_events_to_be_removed != null ) {\r
265                                         midi_track_model.removeMidiEvents(midi_events_to_be_removed);\r
266                                 }\r
267                                 if( ! midi_track_model.addMidiEvent(new_midi_event) ) {\r
268                                         System.out.println("addMidiEvent failure");\r
269                                         return;\r
270                                 }\r
271                                 if(\r
272                                         pair_note_on_off_model.isSelected() &&\r
273                                         eventDialog.midiMessageForm.isNote()\r
274                                 ) {\r
275                                         ShortMessage sm = eventDialog.midiMessageForm.getPartnerMessage();\r
276                                         if( sm == null ) scrollToEventAt( tick );\r
277                                         else {\r
278                                                 int duration = eventDialog.midiMessageForm.durationForm.getDuration();\r
279                                                 if( eventDialog.midiMessageForm.isNote(false) ) { // Note Off\r
280                                                         duration = -duration;\r
281                                                 }\r
282                                                 long partner_tick = tick + (long)duration;\r
283                                                 if( partner_tick < 0L ) partner_tick = 0L;\r
284                                                 MidiEvent partner_midi_event =\r
285                                                                 new MidiEvent( (MidiMessage)sm, partner_tick );\r
286                                                 if( ! midi_track_model.addMidiEvent(partner_midi_event) ) {\r
287                                                         System.out.println("addMidiEvent failure (note on/off partner message)");\r
288                                                 }\r
289                                                 scrollToEventAt( partner_tick > tick ? partner_tick : tick );\r
290                                         }\r
291                                 }\r
292                                 seqListModel.fireSequenceChanged(seq_model);\r
293                                 eventDialog.setVisible(false);\r
294                                 fireEditingStopped();\r
295                         }\r
296                 };\r
297 \r
298                 // Constructor\r
299                 //\r
300                 public MidiEventCellEditor() {\r
301                         edit_event_button.setHorizontalAlignment(JButton.LEFT);\r
302                         eventDialog.cancel_button.setAction(cancel_action);\r
303                         eventDialog.midiMessageForm.setOutputMidiChannels(\r
304                                         midiDevice.getChannels()\r
305                                         );\r
306                         eventDialog.tick_position_form.setModel(tick_position_model);\r
307                         edit_event_button.addActionListener(\r
308                                 new ActionListener() {\r
309                                         public void actionPerformed(ActionEvent e) {\r
310                                                 setSelectedEvent();\r
311                                                 if( sel_midi_evt == null ) return;\r
312                                                 MidiEvent partner_event = null;\r
313                                                 eventDialog.midiMessageForm.setMessage( sel_midi_evt.getMessage() );\r
314                                                 if( eventDialog.midiMessageForm.isNote() ) {\r
315                                                         int partner_index = midi_track_model.getIndexOfPartnerFor(sel_index);\r
316                                                         if( partner_index < 0 ) {\r
317                                                                 eventDialog.midiMessageForm.durationForm.setDuration(0);\r
318                                                         }\r
319                                                         else {\r
320                                                                 partner_event = midi_track_model.getMidiEvent(partner_index);\r
321                                                                 long partner_tick = partner_event.getTick();\r
322                                                                 long duration = current_tick > partner_tick ?\r
323                                                                                 current_tick - partner_tick : partner_tick - current_tick ;\r
324                                                                 eventDialog.midiMessageForm.durationForm.setDuration((int)duration);\r
325                                                         }\r
326                                                 }\r
327                                                 MidiEvent events[];\r
328                                                 if( partner_event == null ) {\r
329                                                         events = new MidiEvent[1];\r
330                                                         events[0] = sel_midi_evt;\r
331                                                 }\r
332                                                 else {\r
333                                                         events = new MidiEvent[2];\r
334                                                         events[0] = sel_midi_evt;\r
335                                                         events[1] = partner_event;\r
336                                                 }\r
337                                                 midi_events_to_be_removed = events;\r
338                                                 eventDialog.setTitle("Change MIDI event");\r
339                                                 eventDialog.okButton.setAction(add_event_action);\r
340                                                 eventDialog.openEventForm();\r
341                                         }\r
342                                 }\r
343                         );\r
344                         pair_note_on_off_model.addItemListener(new ItemListener() {\r
345                                 public void itemStateChanged(ItemEvent e) {\r
346                                         eventDialog.midiMessageForm.durationForm.setEnabled(\r
347                                                         pair_note_on_off_model.isSelected()\r
348                                                         );\r
349                                 }\r
350                         });\r
351                         pair_note_on_off_model.setSelected(true);\r
352                 }\r
353                 // TableCellEditor\r
354                 //\r
355                 public boolean isCellEditable(EventObject e) {\r
356                         // ダブルクリックで編集\r
357                         return e instanceof MouseEvent && ((MouseEvent)e).getClickCount() == 2;\r
358                 }\r
359                 public Object getCellEditorValue() {\r
360                         return "";\r
361                 }\r
362                 public Component getTableCellEditorComponent(\r
363                                 JTable table, Object value, boolean isSelected,\r
364                                 int row, int column\r
365                                 ) {\r
366                         edit_event_button.setText((String)value);\r
367                         return edit_event_button;\r
368                 }\r
369         }\r
370 \r
371         public Action move_to_top_action = new AbstractAction() {\r
372                 {\r
373                         putValue( SHORT_DESCRIPTION,\r
374                                         "Move to top or previous song - 曲の先頭または前の曲へ戻る"\r
375                                         );\r
376                         putValue( LARGE_ICON_KEY, new ButtonIcon(ButtonIcon.TOP_ICON) );\r
377                 }\r
378                 public void actionPerformed( ActionEvent event ) {\r
379                         if( deviceManager.getSequencer().getTickPosition() <= 40 )\r
380                                 loadNext(-1);\r
381                         deviceManager.timeRangeModel.setValue(0);\r
382                 }\r
383         };\r
384         public Action move_to_bottom_action = new AbstractAction() {\r
385                 {\r
386                         putValue( SHORT_DESCRIPTION, "Move to next song - 次の曲へ進む" );\r
387                         putValue( LARGE_ICON_KEY, new ButtonIcon(ButtonIcon.BOTTOM_ICON) );\r
388                 }\r
389                 public void actionPerformed( ActionEvent event ) {\r
390                         if( loadNext(1) ) deviceManager.timeRangeModel.setValue(0);\r
391                 }\r
392         };\r
393         //\r
394         // Constructor\r
395         //\r
396         public MidiEditor(MidiDeviceManager deviceManager) {\r
397                 this.deviceManager = deviceManager;\r
398                 setTitle("MIDI Editor/Playlist - MIDI Chord Helper");\r
399                 setBounds( 150, 200, 850, 500 );\r
400                 setLayout(new FlowLayout());\r
401                 Icon delete_icon = new ButtonIcon(ButtonIcon.X_ICON);\r
402                 new DropTarget(\r
403                         this, DnDConstants.ACTION_COPY_OR_MOVE, this, true\r
404                 );\r
405                 total_time_label = new JLabel();\r
406                 //\r
407                 // Buttons (Sequence)\r
408                 //\r
409                 add_new_sequence_button = new JButton("New");\r
410                 add_new_sequence_button.setToolTipText("Generate new song - 新しい曲を生成");\r
411                 add_new_sequence_button.setMargin(zero_insets);\r
412                 add_new_sequence_button.addActionListener(\r
413                         new ActionListener() {\r
414                                 public void actionPerformed(ActionEvent e) {\r
415                                         new_seq_dialog.setVisible(true);\r
416                                 }\r
417                         }\r
418                 );\r
419                 add_midi_file_button = new JButton("Open");\r
420                 add_midi_file_button.setMargin(zero_insets);\r
421                 add_midi_file_button.addActionListener(\r
422                         new ActionListener() {\r
423                                 public void actionPerformed(ActionEvent e) {\r
424                                         if(\r
425                                                         file_chooser == null ||\r
426                                                         file_chooser.showOpenDialog(MidiEditor.this) != JFileChooser.APPROVE_OPTION\r
427                                                         ) return;\r
428                                         addSequenceFromMidiFile(file_chooser.getSelectedFile());\r
429                                 }\r
430                         }\r
431                 );\r
432                 //\r
433                 play_pause_button = new JToggleButton(\r
434                                 deviceManager.timeRangeModel.startStopAction\r
435                                 );\r
436                 backward_button = new JButton(move_to_top_action);\r
437                 backward_button.setMargin(zero_insets);\r
438                 forward_button = new JButton(move_to_bottom_action);\r
439                 forward_button.setMargin(zero_insets);\r
440                 //\r
441                 jump_sequence_button = new JButton("Jump");\r
442                 jump_sequence_button.setToolTipText("Move to selected song - 選択した曲へ進む");\r
443                 jump_sequence_button.setMargin(zero_insets);\r
444                 jump_sequence_button.addActionListener(\r
445                         new ActionListener() {\r
446                                 public void actionPerformed(ActionEvent e) {\r
447                                         load( seq_selection_model.getMinSelectionIndex() );\r
448                                 }\r
449                         }\r
450                 );\r
451                 save_midi_file_button = new JButton("Save");\r
452                 save_midi_file_button.setMargin(zero_insets);\r
453                 save_midi_file_button.addActionListener(\r
454                         new ActionListener() {\r
455                                 public void actionPerformed(ActionEvent e) {\r
456                                         if( file_chooser == null ) return;\r
457                                         File midi_file;\r
458                                         MidiSequenceModel seq_model =\r
459                                                         seqListModel.getSequenceModel(seq_selection_model);\r
460                                         String filename = seq_model.getFilename();\r
461                                         if( filename != null && ! filename.isEmpty() ) {\r
462                                                 midi_file = new File(filename);\r
463                                                 file_chooser.setSelectedFile(midi_file);\r
464                                         }\r
465                                         if( file_chooser.showSaveDialog(MidiEditor.this) != JFileChooser.APPROVE_OPTION ) {\r
466                                                 return;\r
467                                         }\r
468                                         midi_file = file_chooser.getSelectedFile();\r
469                                         if( midi_file.exists() && ! confirm(\r
470                                                         "Overwrite " + midi_file.getName() + " ?\n"\r
471                                                                         + midi_file.getName() + " を上書きしてよろしいですか?"\r
472                                                         ) ) {\r
473                                                 return;\r
474                                         }\r
475                                         FileOutputStream fos;\r
476                                         try {\r
477                                                 fos = new FileOutputStream(midi_file);\r
478                                         }\r
479                                         catch( FileNotFoundException ex ) {\r
480                                                 showError( midi_file.getName() + ": Cannot open to write" );\r
481                                                 ex.printStackTrace();\r
482                                                 return;\r
483                                         }\r
484                                         try {\r
485                                                 fos.write(seq_model.getMIDIdata());\r
486                                                 fos.close();\r
487                                                 seq_model.setModified(false);\r
488                                         }\r
489                                         catch( IOException ex ) {\r
490                                                 showError( midi_file.getName() + ": I/O Error" );\r
491                                                 ex.printStackTrace();\r
492                                         }\r
493                                 }\r
494                         }\r
495                 );\r
496                 delete_sequence_button = new JButton("Delete", delete_icon);\r
497                 delete_sequence_button.setMargin(zero_insets);\r
498                 delete_sequence_button.addActionListener(\r
499                         new ActionListener() {\r
500                                 public void actionPerformed(ActionEvent e) {\r
501                                         if(\r
502                                                         file_chooser != null &&\r
503                                                         seqListModel.getSequenceModel(seq_selection_model).isModified() &&\r
504                                                         ! confirm(\r
505                                                                         "Selected MIDI sequence not saved - delete it ?\n" +\r
506                                                                                         "選択したMIDIシーケンスは保存されていませんが、削除しますか?"\r
507                                                                         )\r
508                                                         ) return;\r
509                                         seqListModel.removeSequence(seq_selection_model);\r
510                                         total_time_label.setText( seqListModel.getTotalLength() );\r
511                                 }\r
512                         }\r
513                 );\r
514                 //\r
515                 // Buttons (Track)\r
516                 //\r
517                 tracks_label = new JLabel("Tracks");\r
518                 add_track_button = new JButton("New");\r
519                 add_track_button.setMargin(zero_insets);\r
520                 add_track_button.addActionListener(\r
521                                 new ActionListener() {\r
522                                         public void actionPerformed(ActionEvent e) {\r
523                                                 seqListModel.getSequenceModel(seq_selection_model).createTrack();\r
524                                                 int n_tracks = seqListModel.getSequenceModel(seq_selection_model).getRowCount();\r
525                                                 if( n_tracks > 0 ) {\r
526                                                         // Select a created track\r
527                                                         track_selection_model.setSelectionInterval(\r
528                                                                         n_tracks - 1, n_tracks - 1\r
529                                                                         );\r
530                                                 }\r
531                                                 seqListModel.fireSequenceChanged(seq_selection_model);\r
532                                         }\r
533                                 }\r
534                                 );\r
535                 remove_track_button = new JButton("Delete", delete_icon);\r
536                 remove_track_button.setMargin(zero_insets);\r
537                 remove_track_button.addActionListener(\r
538                                 new ActionListener() {\r
539                                         public void actionPerformed(ActionEvent e) {\r
540                                                 if( ! confirm(\r
541                                                                 "Do you want to delete selected track ?\n選択したトラックを削除しますか?"\r
542                                                                 )) return;\r
543                                                 seqListModel.getSequenceModel(\r
544                                                                 seq_selection_model\r
545                                                                 ).deleteTracks( track_selection_model );\r
546                                                 seqListModel.fireSequenceChanged(seq_selection_model);\r
547                                         }\r
548                                 }\r
549                                 );\r
550                 JPanel track_button_panel = new JPanel();\r
551                 track_button_panel.add(add_track_button);\r
552                 track_button_panel.add(remove_track_button);\r
553                 //\r
554                 // Buttons (Event)\r
555                 //\r
556                 event_cell_editor = new MidiEventCellEditor();\r
557                 add_event_button = new JButton(event_cell_editor.query_add_event_action);\r
558                 add_event_button.setMargin(zero_insets);\r
559                 jump_event_button = new JButton(event_cell_editor.query_jump_event_action);\r
560                 jump_event_button.setMargin(zero_insets);\r
561                 paste_event_button = new JButton(event_cell_editor.query_paste_event_action);\r
562                 paste_event_button.setMargin(zero_insets);\r
563                 remove_event_button = new JButton("Delete", delete_icon);\r
564                 remove_event_button.setMargin(zero_insets);\r
565                 remove_event_button.addActionListener(\r
566                         new ActionListener() {\r
567                                 public void actionPerformed(ActionEvent e) {\r
568                                         if( ! confirm(\r
569                                                         "Do you want to delete selected event ?\n選択したMIDIイベントを削除しますか?"\r
570                                                         )) return;\r
571                                         ((MidiTrackModel)event_table_view.getModel()).removeMidiEvents( event_selection_model );\r
572                                         seqListModel.fireSequenceChanged(seq_selection_model);\r
573                                 }\r
574                         }\r
575                 );\r
576                 cut_event_button = new JButton("Cut");\r
577                 cut_event_button.setMargin(zero_insets);\r
578                 cut_event_button.addActionListener(\r
579                         new ActionListener() {\r
580                                 public void actionPerformed(ActionEvent e) {\r
581                                         if( ! confirm(\r
582                                                         "Do you want to cut selected event ?\n選択したMIDIイベントを切り取りますか?"\r
583                                                         )) return;\r
584                                         MidiTrackModel track_model = (MidiTrackModel)event_table_view.getModel();\r
585                                         copied_events = track_model.getMidiEvents( event_selection_model );\r
586                                         copied_events_PPQ = seqListModel.getSequenceModel(\r
587                                                         seq_selection_model\r
588                                                         ).getSequence().getResolution();\r
589                                         track_model.removeMidiEvents( copied_events );\r
590                                         seqListModel.fireSequenceChanged(seq_selection_model);\r
591                                 }\r
592                         }\r
593                 );\r
594                 copy_event_button = new JButton("Copy");\r
595                 copy_event_button.setMargin(zero_insets);\r
596                 copy_event_button.addActionListener(\r
597                         new ActionListener() {\r
598                                 public void actionPerformed(ActionEvent e) {\r
599                                         copied_events = ((MidiTrackModel)event_table_view.getModel()).getMidiEvents(\r
600                                                         event_selection_model\r
601                                                         );\r
602                                         copied_events_PPQ = seqListModel.getSequenceModel(\r
603                                                         seq_selection_model\r
604                                                         ).getSequence().getResolution();\r
605                                         updateButtonStatus();\r
606                                 }\r
607                         }\r
608                 );\r
609                 pair_note_checkbox = new JCheckBox( "Pair NoteON/OFF" );\r
610                 pair_note_checkbox.setModel(event_cell_editor.pair_note_on_off_model);\r
611                 //\r
612                 // Tables\r
613                 //\r
614                 MidiSequenceModel empty_track_table_model = new MidiSequenceModel(\r
615                         seqListModel = new SequenceListModel(deviceManager)\r
616                 );\r
617                 sequence_table_view = new JTable( seqListModel );\r
618                 track_table_view = new JTable( empty_track_table_model );\r
619                 event_table_view = new JTable( new MidiTrackModel() );\r
620                 //\r
621                 seqListModel.sizeColumnWidthToFit( sequence_table_view );\r
622                 //\r
623                 TableColumnModel track_column_model = track_table_view.getColumnModel();\r
624                 empty_track_table_model.sizeColumnWidthToFit(track_column_model);\r
625                 //\r
626                 seq_selection_model = sequence_table_view.getSelectionModel();\r
627                 seq_selection_model.setSelectionMode( ListSelectionModel.SINGLE_SELECTION );\r
628                 seq_selection_model.addListSelectionListener(\r
629                         new ListSelectionListener() {\r
630                                 public void valueChanged(ListSelectionEvent e) {\r
631                                         if( e.getValueIsAdjusting() ) return;\r
632                                         sequenceSelectionChanged();\r
633                                         track_selection_model.setSelectionInterval(0,0);\r
634                                 }\r
635                         }\r
636                 );\r
637                 JScrollPane scrollable_sequence_table = new JScrollPane(sequence_table_view);\r
638                 //\r
639                 track_selection_model = track_table_view.getSelectionModel();\r
640                 track_selection_model.setSelectionMode(\r
641                                 ListSelectionModel.MULTIPLE_INTERVAL_SELECTION\r
642                                 );\r
643                 track_selection_model.addListSelectionListener(this);\r
644                 JScrollPane scrollable_track_table_view\r
645                 = new JScrollPane(track_table_view);\r
646                 //\r
647                 event_selection_model = event_table_view.getSelectionModel();\r
648                 event_selection_model.setSelectionMode(\r
649                                 ListSelectionModel.MULTIPLE_INTERVAL_SELECTION\r
650                                 );\r
651                 event_selection_model.addListSelectionListener(this);\r
652                 scrollable_event_table_view\r
653                 = new JScrollPane(event_table_view);\r
654 \r
655                 base64_dialog = new Base64Dialog(this);\r
656                 if( base64_dialog.isBase64Available() ) {\r
657                         base64_encode_button = new JButton( "Base64 Encode" );\r
658                         base64_encode_button.setMargin(zero_insets);\r
659                         base64_encode_button.addActionListener(\r
660                                 new ActionListener() {\r
661                                         public void actionPerformed(ActionEvent e) {\r
662                                                 MidiSequenceModel seq_model =\r
663                                                                 seqListModel.getSequenceModel(seq_selection_model);\r
664                                                 base64_dialog.setMIDIData(\r
665                                                                 seq_model.getMIDIdata(), seq_model.getFilename()\r
666                                                                 );\r
667                                                 base64_dialog.setVisible(true);\r
668                                         }\r
669                                 }\r
670                         );\r
671                 }\r
672                 new_seq_dialog = new NewSequenceDialog(this);\r
673                 new_seq_dialog.setChannels( midiDevice.getChannels() );\r
674 \r
675                 JPanel button_panel = new JPanel();\r
676                 button_panel.setLayout( new BoxLayout( button_panel, BoxLayout.LINE_AXIS ) );\r
677                 button_panel.add( total_time_label );\r
678                 button_panel.add( Box.createRigidArea(new Dimension(10, 0)) );\r
679                 button_panel.add( add_new_sequence_button );\r
680                 button_panel.add( Box.createRigidArea(new Dimension(5, 0)) );\r
681                 button_panel.add( add_midi_file_button );\r
682                 button_panel.add( Box.createRigidArea(new Dimension(5, 0)) );\r
683                 button_panel.add( backward_button );\r
684                 button_panel.add( Box.createRigidArea(new Dimension(5, 0)) );\r
685                 button_panel.add( play_pause_button );\r
686                 button_panel.add( Box.createRigidArea(new Dimension(5, 0)) );\r
687                 button_panel.add( forward_button );\r
688                 button_panel.add( Box.createRigidArea(new Dimension(5, 0)) );\r
689                 button_panel.add( jump_sequence_button );\r
690                 button_panel.add( Box.createRigidArea(new Dimension(5, 0)) );\r
691                 button_panel.add( save_midi_file_button );\r
692                 if( base64_encode_button != null ) {\r
693                         button_panel.add( Box.createRigidArea(new Dimension(5, 0)) );\r
694                         button_panel.add( base64_encode_button );\r
695                 }\r
696                 button_panel.add( Box.createRigidArea(new Dimension(5, 0)) );\r
697                 button_panel.add( delete_sequence_button );\r
698                 button_panel.add( Box.createRigidArea(new Dimension(5, 0)) );\r
699                 button_panel.add( new SpeedSlider(deviceManager.speedSliderModel) );\r
700 \r
701                 JPanel playlist_panel = new JPanel();\r
702                 playlist_panel.setLayout(\r
703                                 new BoxLayout( playlist_panel, BoxLayout.Y_AXIS )\r
704                                 );\r
705                 playlist_panel.add( scrollable_sequence_table );\r
706                 playlist_panel.add( Box.createRigidArea(new Dimension(0, 10)) );\r
707                 playlist_panel.add( button_panel );\r
708                 playlist_panel.add( Box.createRigidArea(new Dimension(0, 10)) );\r
709 \r
710                 sequenceSelectionChanged();\r
711                 total_time_label.setText( seqListModel.getTotalLength() );\r
712 \r
713                 try {\r
714                         file_chooser = new JFileChooser();\r
715                         FileNameExtensionFilter filter = new FileNameExtensionFilter(\r
716                                         "MIDI sequence (*.mid)", "mid"\r
717                                         );\r
718                         file_chooser.setFileFilter(filter);\r
719                 }\r
720                 catch( ExceptionInInitializerError e ) {\r
721                         file_chooser = null;\r
722                 }\r
723                 catch( NoClassDefFoundError e ) {\r
724                         file_chooser = null;\r
725                 }\r
726                 catch( AccessControlException e ) {\r
727                         file_chooser = null;\r
728                 }\r
729                 if( file_chooser == null ) {\r
730                         // Applet cannot access local files\r
731                         add_midi_file_button.setVisible(false);\r
732                         save_midi_file_button.setVisible(false);\r
733                 }\r
734                 //\r
735                 // Lists and input panel\r
736                 //\r
737                 JPanel track_list_panel = new JPanel();\r
738                 track_list_panel.setLayout(new BoxLayout( track_list_panel, BoxLayout.PAGE_AXIS ));\r
739                 track_list_panel.add( tracks_label );\r
740                 track_list_panel.add( Box.createRigidArea(new Dimension(0, 5)) );\r
741                 track_list_panel.add( scrollable_track_table_view );\r
742                 track_list_panel.add( Box.createRigidArea(new Dimension(0, 5)) );\r
743                 track_list_panel.add( track_button_panel );\r
744                 //\r
745                 JPanel event_list_panel = new JPanel();\r
746                 event_list_panel.add( midi_events_label = new JLabel("No track selected") );\r
747                 event_list_panel.add(scrollable_event_table_view);\r
748                 //\r
749                 JPanel event_button_panel = new JPanel();\r
750                 event_button_panel.add(pair_note_checkbox);\r
751                 event_button_panel.add(jump_event_button);\r
752                 event_button_panel.add(add_event_button);\r
753                 event_button_panel.add(copy_event_button);\r
754                 event_button_panel.add(cut_event_button);\r
755                 event_button_panel.add(paste_event_button);\r
756                 event_button_panel.add(remove_event_button);\r
757                 //\r
758                 event_list_panel.add( event_button_panel );\r
759                 event_list_panel.setLayout(\r
760                                 new BoxLayout( event_list_panel, BoxLayout.Y_AXIS )\r
761                                 );\r
762                 //\r
763                 track_split_pane = new JSplitPane(\r
764                         JSplitPane.HORIZONTAL_SPLIT,\r
765                         track_list_panel, event_list_panel\r
766                 );\r
767                 track_split_pane.setDividerLocation(300);\r
768                 sequence_split_pane = new JSplitPane(\r
769                         JSplitPane.VERTICAL_SPLIT,\r
770                         playlist_panel, track_split_pane\r
771                 );\r
772                 sequence_split_pane.setDividerLocation(160);\r
773                 Container cp = getContentPane();\r
774                 cp.setLayout( new BoxLayout( cp, BoxLayout.Y_AXIS ) );\r
775                 cp.add(Box.createVerticalStrut(2));\r
776                 cp.add(sequence_split_pane);\r
777                 //\r
778                 seq_selection_model.setSelectionInterval(0,0);\r
779                 updateButtonStatus();\r
780         }\r
781         public void dragEnter(DropTargetDragEvent event) {\r
782                 if( event.isDataFlavorSupported(DataFlavor.javaFileListFlavor) )\r
783                         event.acceptDrag(DnDConstants.ACTION_COPY_OR_MOVE);\r
784         }\r
785         public void dragExit(DropTargetEvent event) {}\r
786         public void dragOver(DropTargetDragEvent event) {}\r
787         public void dropActionChanged(DropTargetDragEvent event) {}\r
788         @SuppressWarnings("unchecked")\r
789         public void drop(DropTargetDropEvent event) {\r
790                 event.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);\r
791                 try {\r
792                         int action = event.getDropAction();\r
793                         if ( (action & DnDConstants.ACTION_COPY_OR_MOVE) != 0 ) {\r
794                                 Transferable t = event.getTransferable();\r
795                                 Object data = t.getTransferData(DataFlavor.javaFileListFlavor);\r
796                                 loadAndPlay((java.util.List<File>)data);\r
797                                 event.dropComplete(true);\r
798                                 return;\r
799                         }\r
800                         event.dropComplete(false);\r
801                 }\r
802                 catch (Exception ex) {\r
803                         ex.printStackTrace();\r
804                         event.dropComplete(false);\r
805                 }\r
806         }\r
807         public void valueChanged(ListSelectionEvent e) {\r
808                 boolean is_adjusting = e.getValueIsAdjusting();\r
809                 if( is_adjusting ) return;\r
810                 Object src = e.getSource();\r
811                 if( src == track_selection_model ) {\r
812                         if(\r
813                                 seqListModel.getSequenceModel(seq_selection_model) == null\r
814                                 ||\r
815                                 track_selection_model.isSelectionEmpty()\r
816                         ) {\r
817                                 midi_events_label.setText("MIDI Events (No track selected)");\r
818                                 event_table_view.setModel(new MidiTrackModel());\r
819                         }\r
820                         else {\r
821                                 int sel_index = track_selection_model.getMinSelectionIndex();\r
822                                 MidiTrackModel track_model\r
823                                 = seqListModel.getSequenceModel(seq_selection_model).getTrackModel(sel_index);\r
824                                 if( track_model == null ) {\r
825                                         midi_events_label.setText("MIDI Events (No track selected)");\r
826                                         event_table_view.setModel(new MidiTrackModel());\r
827                                 }\r
828                                 else {\r
829                                         midi_events_label.setText(\r
830                                                 String.format("MIDI Events (in track No.%d)", sel_index)\r
831                                         );\r
832                                         event_table_view.setModel(track_model);\r
833                                         TableColumnModel tcm = event_table_view.getColumnModel();\r
834                                         track_model.sizeColumnWidthToFit(tcm);\r
835                                         tcm.getColumn( MidiTrackModel.COLUMN_MESSAGE ).setCellEditor(event_cell_editor);\r
836                                 }\r
837                         }\r
838                         updateButtonStatus();\r
839                         event_selection_model.setSelectionInterval(0,0);\r
840                 }\r
841                 else if( src == event_selection_model ) {\r
842                         if( ! event_selection_model.isSelectionEmpty() ) {\r
843                                 MidiTrackModel track_model\r
844                                 = (MidiTrackModel)event_table_view.getModel();\r
845                                 int min_index = event_selection_model.getMinSelectionIndex();\r
846                                 if( track_model.hasTrack() ) {\r
847                                         MidiEvent midi_event = track_model.getMidiEvent(min_index);\r
848                                         MidiMessage msg = midi_event.getMessage();\r
849                                         if( msg instanceof ShortMessage ) {\r
850                                                 ShortMessage sm = (ShortMessage)msg;\r
851                                                 int cmd = sm.getCommand();\r
852                                                 if( cmd == 0x80 || cmd == 0x90 || cmd == 0xA0 ) {\r
853                                                         // ノート番号を持つ場合、音を鳴らす。\r
854                                                         MidiChannel out_midi_channels[] = midiDevice.getChannels();\r
855                                                         int ch = sm.getChannel();\r
856                                                         int note = sm.getData1();\r
857                                                         int vel = sm.getData2();\r
858                                                         out_midi_channels[ch].noteOn( note, vel );\r
859                                                         out_midi_channels[ch].noteOff( note, vel );\r
860                                                 }\r
861                                         }\r
862                                 }\r
863                                 if( pair_note_checkbox.isSelected() ) {\r
864                                         int max_index = event_selection_model.getMaxSelectionIndex();\r
865                                         int partner_index;\r
866                                         for( int i=min_index; i<=max_index; i++ ) {\r
867                                                 if(\r
868                                                                 event_selection_model.isSelectedIndex(i)\r
869                                                                 &&\r
870                                                                 (partner_index = track_model.getIndexOfPartnerFor(i)) >= 0\r
871                                                                 &&\r
872                                                                 ! event_selection_model.isSelectedIndex(partner_index)\r
873                                                                 ) {\r
874                                                         event_selection_model.addSelectionInterval(\r
875                                                                         partner_index, partner_index\r
876                                                                         );\r
877                                                 }\r
878                                         }\r
879                                 }\r
880                         }\r
881                         updateButtonStatus();\r
882                 }\r
883         }\r
884         private void showError( String message ) {\r
885                 JOptionPane.showMessageDialog(\r
886                         this, message,\r
887                         ChordHelperApplet.VersionInfo.NAME,\r
888                         JOptionPane.ERROR_MESSAGE\r
889                 );\r
890         }\r
891         private void showWarning( String message ) {\r
892                 JOptionPane.showMessageDialog(\r
893                         this, message,\r
894                         ChordHelperApplet.VersionInfo.NAME,\r
895                         JOptionPane.WARNING_MESSAGE\r
896                 );\r
897         }\r
898         private boolean confirm( String message ) {\r
899                 return JOptionPane.showConfirmDialog(\r
900                         this, message,\r
901                         ChordHelperApplet.VersionInfo.NAME,\r
902                         JOptionPane.YES_NO_OPTION,\r
903                         JOptionPane.WARNING_MESSAGE\r
904                 ) == JOptionPane.YES_OPTION ;\r
905         }\r
906         public void setVisible(boolean is_to_visible) {\r
907                 if( is_to_visible && isVisible() ) toFront();\r
908                 else super.setVisible(is_to_visible);\r
909         }\r
910         public void sequenceSelectionChanged() {\r
911                 MidiSequenceModel seq_model\r
912                 = seqListModel.getSequenceModel(seq_selection_model);\r
913                 jump_sequence_button.setEnabled( seq_model != null );\r
914                 save_midi_file_button.setEnabled( seq_model != null );\r
915                 add_track_button.setEnabled( seq_model != null );\r
916                 if( base64_encode_button != null )\r
917                         base64_encode_button.setEnabled( seq_model != null );\r
918 \r
919                 if( seq_model != null ) {\r
920                         int sel_index = seq_selection_model.getMinSelectionIndex();\r
921                         delete_sequence_button.setEnabled(true);\r
922                         track_table_view.setModel(seq_model);\r
923                         TableColumnModel tcm = track_table_view.getColumnModel();\r
924                         seq_model.sizeColumnWidthToFit(tcm);\r
925                         tcm.getColumn(MidiSequenceModel.COLUMN_RECORD_CHANNEL).setCellEditor(\r
926                                         seq_model.new RecordChannelCellEditor()\r
927                                         );\r
928                         track_selection_model.setSelectionInterval(0,0);\r
929                         tracks_label.setText(\r
930                                         String.format("Tracks (in MIDI file No.%d)", sel_index)\r
931                                         );\r
932                         // event_cell_editor.setSequenceModel(seq_model);\r
933                 }\r
934                 else {\r
935                         delete_sequence_button.setEnabled(false);\r
936                         track_table_view.setModel(new MidiSequenceModel(seqListModel));\r
937                         tracks_label.setText("Tracks (No MIDI file selected)");\r
938                 }\r
939                 updateButtonStatus();\r
940         }\r
941         public void updateButtonStatus() {\r
942                 boolean is_track_selected = (\r
943                                 ! track_selection_model.isSelectionEmpty()\r
944                                 &&\r
945                                 seqListModel.getSequenceModel(seq_selection_model) != null\r
946                                 &&\r
947                                 seqListModel.getSequenceModel(seq_selection_model).getRowCount() > 0\r
948                                 );\r
949                 //\r
950                 // Track list\r
951                 remove_track_button.setEnabled( is_track_selected );\r
952                 //\r
953                 TableModel tm = event_table_view.getModel();\r
954                 if( ! (tm instanceof MidiTrackModel) ) return;\r
955                 //\r
956                 MidiTrackModel track_model = (MidiTrackModel)tm;\r
957                 jump_sequence_button.setEnabled(\r
958                                 track_model != null && track_model.getRowCount() > 0\r
959                                 );\r
960                 // Event list\r
961                 boolean is_event_selected = (\r
962                                 !(\r
963                                                 event_selection_model.isSelectionEmpty() ||\r
964                                                 track_model == null || track_model.getRowCount() == 0\r
965                                                 ) && is_track_selected\r
966                                 );\r
967                 copy_event_button.setEnabled( is_event_selected );\r
968                 remove_event_button.setEnabled( is_event_selected );\r
969                 cut_event_button.setEnabled( is_event_selected );\r
970                 jump_event_button.setEnabled(\r
971                                 track_model != null && is_track_selected\r
972                                 );\r
973                 add_event_button.setEnabled(\r
974                                 track_model != null && is_track_selected\r
975                                 );\r
976                 paste_event_button.setEnabled(\r
977                                 track_model != null && is_track_selected &&\r
978                                 copied_events != null && copied_events.length > 0\r
979                                 );\r
980         }\r
981         public String getMIDIdataBase64() {\r
982                 base64_dialog.setMIDIData(\r
983                                 deviceManager.timeRangeModel.getSequenceModel().getMIDIdata()\r
984                                 );\r
985                 return base64_dialog.getBase64Data();\r
986         }\r
987         public int addSequence() {\r
988                 return addSequence(new_seq_dialog.getMidiSequence());\r
989         }\r
990         public int addSequence(Sequence seq) {\r
991                 int last_index = seqListModel.addSequence(seq);\r
992                 total_time_label.setText( seqListModel.getTotalLength() );\r
993                 if( ! deviceManager.getSequencer().isRunning() ) {\r
994                         loadAndPlay(last_index);\r
995                 }\r
996                 return last_index;\r
997         }\r
998         public int addSequenceFromBase64Text(String base64_encoded_text, String filename) {\r
999                 base64_dialog.setBase64Data( base64_encoded_text );\r
1000                 return addSequenceFromMidiData( base64_dialog.getMIDIData(), filename );\r
1001         }\r
1002         public int addSequenceFromBase64Text() {\r
1003                 return addSequenceFromMidiData( base64_dialog.getMIDIData(), null );\r
1004         }\r
1005         public int addSequenceFromMidiData(byte[] data, String filename) {\r
1006                 int last_index;\r
1007                 try {\r
1008                         last_index = seqListModel.addSequence(data,filename);\r
1009                 } catch( InvalidMidiDataException e ) {\r
1010                         showWarning("MIDI data invalid");\r
1011                         return -1;\r
1012                 }\r
1013                 total_time_label.setText( seqListModel.getTotalLength() );\r
1014                 return last_index;\r
1015         }\r
1016         public int addSequenceFromMidiFile(File midi_file) {\r
1017                 int last_index;\r
1018                 try {\r
1019                         last_index = seqListModel.addSequence(midi_file);\r
1020                 } catch( FileNotFoundException e ) {\r
1021                         showWarning( midi_file.getName() + " : not found" );\r
1022                         return -1;\r
1023                 } catch( InvalidMidiDataException e ) {\r
1024                         showWarning( midi_file.getName() + " : MIDI data invalid" );\r
1025                         return -1;\r
1026                 } catch( AccessControlException e ) {\r
1027                         showError( midi_file.getName() + ": Cannot access" );\r
1028                         e.printStackTrace();\r
1029                         return -1;\r
1030                 }\r
1031                 total_time_label.setText( seqListModel.getTotalLength() );\r
1032                 return last_index;\r
1033         }\r
1034         public int addSequenceFromURL(String midi_file_url) {\r
1035                 int last_index;\r
1036                 try {\r
1037                         last_index = seqListModel.addSequence(midi_file_url);\r
1038                 } catch( InvalidMidiDataException e ) {\r
1039                         showWarning( midi_file_url + " : MIDI data invalid" );\r
1040                         return -1;\r
1041                 } catch( AccessControlException e ) {\r
1042                         showError( midi_file_url + ": Cannot access" );\r
1043                         e.printStackTrace();\r
1044                         return -1;\r
1045                 }\r
1046                 total_time_label.setText( seqListModel.getTotalLength() );\r
1047                 return last_index;\r
1048         }\r
1049         public void load(int index) {\r
1050                 seqListModel.loadToSequencer(index);\r
1051                 sequenceSelectionChanged();\r
1052         }\r
1053         public boolean loadNext(int offset) {\r
1054                 boolean retval = seqListModel.loadNext(offset);\r
1055                 sequenceSelectionChanged();\r
1056                 return retval;\r
1057         }\r
1058         public void loadAndPlay(int index) {\r
1059                 load(index);\r
1060                 deviceManager.timeRangeModel.start();\r
1061         }\r
1062         public void loadAndPlay() {\r
1063                 loadAndPlay( seq_selection_model.getMinSelectionIndex() );\r
1064         }\r
1065         public void loadAndPlay( java.util.List<File> fileList ) {\r
1066                 int lastIndex = -1;\r
1067                 int nextIndex = -1;\r
1068                 for( File f : fileList ) {\r
1069                         lastIndex = addSequenceFromMidiFile(f);\r
1070                         if( nextIndex == -1 ) nextIndex = lastIndex;\r
1071                 }\r
1072                 if( deviceManager.getSequencer().isRunning() ) {\r
1073                         setVisible(true);\r
1074                 }\r
1075                 else if( nextIndex >= 0 ) {\r
1076                         loadAndPlay(nextIndex);\r
1077                 }\r
1078         }\r
1079         public boolean isModified() {\r
1080                 return seqListModel.isModified();\r
1081         }\r
1082         public boolean isRecordable() {\r
1083                 MidiSequenceModel seq_model =\r
1084                                 seqListModel.getSequenceModel(seq_selection_model);\r
1085                 return seq_model == null ? false : seq_model.isRecordable();\r
1086         }\r
1087         public void scrollToEventAt( long tick ) {\r
1088                 MidiTrackModel track_model = (MidiTrackModel)event_table_view.getModel();\r
1089                 scrollToEventAt( track_model.tickToIndex(tick) );\r
1090         }\r
1091         public void scrollToEventAt( int index ) {\r
1092                 scrollable_event_table_view.getVerticalScrollBar().setValue(\r
1093                                 index * event_table_view.getRowHeight()\r
1094                                 );\r
1095                 event_selection_model.setSelectionInterval( index, index );\r
1096         }\r
1097 }\r
1098 \r
1099 /////////////////////////////////////////////////////////////\r
1100 //\r
1101 // プレイリスト\r
1102 //\r
1103 class SequenceListModel extends AbstractTableModel\r
1104 {\r
1105         public static final int COLUMN_SEQ_NUMBER       = 0;\r
1106         public static final int COLUMN_MODIFIED = 1;\r
1107         public static final int COLUMN_DIVISION_TYPE    = 2;\r
1108         public static final int COLUMN_RESOLUTION       = 3;\r
1109         public static final int COLUMN_TRACKS           = 4;\r
1110         public static final int COLUMN_SEQ_POSITION     = 5;\r
1111         public static final int COLUMN_SEQ_LENGTH       = 6;\r
1112         public static final int COLUMN_FILENAME = 7;\r
1113         public static final int COLUMN_SEQ_NAME = 8;\r
1114         static String column_titles[] = {\r
1115                 "No.",\r
1116                 "Modified",\r
1117                 "DivType",\r
1118                 "Resolution",\r
1119                 "Tracks",\r
1120                 "Position",\r
1121                 "Length",\r
1122                 "Filename",\r
1123                 "Sequence name",\r
1124         };\r
1125         static int column_width_ratios[] = {\r
1126                 2, 6, 6, 6, 6, 6, 6, 16, 40,\r
1127         };\r
1128 \r
1129         private ArrayList<MidiSequenceModel>\r
1130         sequences = new ArrayList<MidiSequenceModel>();\r
1131 \r
1132         MidiDeviceManager device_manager;\r
1133         int second_position = 0;\r
1134 \r
1135         public SequenceListModel( MidiDeviceManager device_manager ) {\r
1136                 (this.device_manager = device_manager).timeRangeModel.addChangeListener(\r
1137                                 new ChangeListener() {\r
1138                                         public void stateChanged(ChangeEvent e) {\r
1139                                                 int sec_pos = SequenceListModel.this.device_manager.timeRangeModel.getValue() / 1000;\r
1140                                                 if( second_position == sec_pos ) return;\r
1141                                                 second_position = sec_pos;\r
1142                                                 fireTableCellUpdated( getLoadedIndex(), COLUMN_SEQ_POSITION );\r
1143                                         }\r
1144                                 }\r
1145                                 );\r
1146         }\r
1147 \r
1148         // TableModel\r
1149         //\r
1150         public int getRowCount() { return sequences.size(); }\r
1151         public int getColumnCount() { return column_titles.length; }\r
1152         public String getColumnName(int column) {\r
1153                 return column_titles[column];\r
1154         }\r
1155         public Class<?> getColumnClass(int column) {\r
1156                 switch(column) {\r
1157                 case COLUMN_MODIFIED: return Boolean.class;\r
1158                 case COLUMN_SEQ_NUMBER:\r
1159                 case COLUMN_RESOLUTION:\r
1160                 case COLUMN_TRACKS: return Integer.class;\r
1161                 default: return String.class;\r
1162                 }\r
1163         }\r
1164         public Object getValueAt(int row, int column) {\r
1165                 switch(column) {\r
1166                 case COLUMN_SEQ_NUMBER: return row;\r
1167                 case COLUMN_MODIFIED: return sequences.get(row).isModified();\r
1168                 case COLUMN_DIVISION_TYPE: {\r
1169                         float div_type = sequences.get(row).getSequence().getDivisionType();\r
1170                         if( div_type == Sequence.PPQ ) return "PPQ";\r
1171                         else if( div_type == Sequence.SMPTE_24 ) return "SMPTE_24";\r
1172                         else if( div_type == Sequence.SMPTE_25 ) return "SMPTE_25";\r
1173                         else if( div_type == Sequence.SMPTE_30 ) return "SMPTE_30";\r
1174                         else if( div_type == Sequence.SMPTE_30DROP ) return "SMPTE_30DROP";\r
1175                         else return "[Unknown]";\r
1176                 }\r
1177                 case COLUMN_RESOLUTION: return sequences.get(row).getSequence().getResolution();\r
1178                 case COLUMN_TRACKS: return sequences.get(row).getSequence().getTracks().length;\r
1179                 case COLUMN_SEQ_POSITION: {\r
1180                         Sequence loaded_seq = device_manager.getSequencer().getSequence();\r
1181                         if( loaded_seq != null && loaded_seq == sequences.get(row).getSequence() )\r
1182                                 return String.format( "%02d:%02d", second_position/60, second_position%60 );\r
1183                         else\r
1184                                 return "";\r
1185                 }\r
1186                 case COLUMN_SEQ_LENGTH: {\r
1187                         long usec = sequences.get(row).getSequence().getMicrosecondLength();\r
1188                         int sec = (int)( (usec < 0 ? usec += 0x100000000L : usec) / 1000L / 1000L );\r
1189                         return String.format( "%02d:%02d", sec/60, sec%60 );\r
1190                 }\r
1191                 case COLUMN_FILENAME: {\r
1192                         String filename = sequences.get(row).getFilename();\r
1193                         return filename == null ? "" : filename;\r
1194                 }\r
1195                 case COLUMN_SEQ_NAME: {\r
1196                         String seq_name = sequences.get(row).toString();\r
1197                         return seq_name == null ? "" : seq_name;\r
1198                 }\r
1199                 default: return "";\r
1200                 }\r
1201         }\r
1202         public boolean isCellEditable( int row, int column ) {\r
1203                 return column == COLUMN_FILENAME || column == COLUMN_SEQ_NAME ;\r
1204         }\r
1205         public void setValueAt(Object val, int row, int column) {\r
1206                 switch(column) {\r
1207                 case COLUMN_FILENAME:\r
1208                         // ファイル名の変更\r
1209                         String filename = (String)val;\r
1210                         sequences.get(row).setFilename(filename);\r
1211                         fireTableCellUpdated(row, COLUMN_FILENAME);\r
1212                         break;\r
1213                 case COLUMN_SEQ_NAME:\r
1214                         // シーケンス名の設定または変更\r
1215                         if( sequences.get(row).setName((String)val) )\r
1216                                 fireTableCellUpdated(row, COLUMN_MODIFIED);\r
1217                         break;\r
1218                 }\r
1219         }\r
1220         // Methods\r
1221         //\r
1222         public void sizeColumnWidthToFit( JTable table_view ) {\r
1223                 TableColumnModel column_model = table_view.getColumnModel();\r
1224                 int total_width = column_model.getTotalColumnWidth();\r
1225                 int i, total_width_ratio;\r
1226                 for( i=0, total_width_ratio = 0; i<column_width_ratios.length; i++ ) {\r
1227                         total_width_ratio += column_width_ratios[i];\r
1228                 }\r
1229                 for( i=0; i<column_width_ratios.length; i++ ) {\r
1230                         column_model.getColumn(i).setPreferredWidth(\r
1231                                         total_width * column_width_ratios[i] / total_width_ratio\r
1232                                         );\r
1233                 }\r
1234         }\r
1235         public boolean isModified() {\r
1236                 for( MidiSequenceModel seq_model : sequences ) {\r
1237                         if( seq_model.isModified() ) return true;\r
1238                 }\r
1239                 return false;\r
1240         }\r
1241         public void setModified( ListSelectionModel sel_model, boolean is_modified ) {\r
1242                 int min_index = sel_model.getMinSelectionIndex();\r
1243                 int max_index = sel_model.getMaxSelectionIndex();\r
1244                 for( int i = min_index; i <= max_index; i++ ) {\r
1245                         if( sel_model.isSelectedIndex(i) ) {\r
1246                                 sequences.get(i).setModified(is_modified);\r
1247                                 fireTableCellUpdated(i, COLUMN_MODIFIED);\r
1248                         }\r
1249                 }\r
1250         }\r
1251         public MidiSequenceModel getSequenceModel(ListSelectionModel sel_model) {\r
1252                 if( sel_model.isSelectionEmpty() ) return null;\r
1253                 int sel_index = sel_model.getMinSelectionIndex();\r
1254                 if( sel_index >= sequences.size() ) return null;\r
1255                 return sequences.get(sel_index);\r
1256         }\r
1257         public void fireSequenceChanged( ListSelectionModel sel_model ) {\r
1258                 if( sel_model.isSelectionEmpty() ) return;\r
1259                 fireSequenceChanged(\r
1260                                 sel_model.getMinSelectionIndex(),\r
1261                                 sel_model.getMaxSelectionIndex()\r
1262                                 );\r
1263         }\r
1264         public void fireSequenceChanged( MidiSequenceModel seq_model ) {\r
1265                 for( int index=0; index<sequences.size(); index++ )\r
1266                         if( sequences.get(index) == seq_model )\r
1267                                 fireSequenceChanged(index,index);\r
1268         }\r
1269         public void fireSequenceChanged( int min_index, int max_index ) {\r
1270                 for( int index = min_index; index <= max_index; index++ ) {\r
1271                         MidiSequenceModel seq_model = sequences.get(index);\r
1272                         seq_model.setModified(true);\r
1273                         if( device_manager.getSequencer().getSequence() == seq_model.getSequence() ) {\r
1274                                 // シーケンサーに対して、同じシーケンスを再度セットする。\r
1275                                 // (これをやらないと更新が反映されないため)\r
1276                                 device_manager.timeRangeModel.setSequenceModel(seq_model);\r
1277                         }\r
1278                 }\r
1279                 fireTableRowsUpdated( min_index, max_index );\r
1280         }\r
1281         public int addSequence() {\r
1282                 Sequence seq = (new Music.ChordProgression()).toMidiSequence();\r
1283                 return seq == null ? -1 : addSequence(seq,null);\r
1284         }\r
1285         public int addSequence( Sequence seq ) {\r
1286                 return addSequence( seq, "" );\r
1287         }\r
1288         public int addSequence( Sequence seq, String filename ) {\r
1289                 MidiSequenceModel seq_model = new MidiSequenceModel(this);\r
1290                 seq_model.setSequence(seq);\r
1291                 seq_model.setFilename(filename);\r
1292                 sequences.add(seq_model);\r
1293                 int last_index = sequences.size() - 1;\r
1294                 fireTableRowsInserted( last_index, last_index );\r
1295                 return last_index;\r
1296         }\r
1297         public int addSequence( byte[] midiData, String filename )\r
1298                         throws InvalidMidiDataException\r
1299                         {\r
1300                 return ( midiData == null ) ?\r
1301                                 addSequence() :\r
1302                                         addSequence( new ByteArrayInputStream(midiData), filename ) ;\r
1303                         }\r
1304         public int addSequence( File midi_file )\r
1305                         throws InvalidMidiDataException, FileNotFoundException\r
1306                         {\r
1307                 FileInputStream fis = new FileInputStream(midi_file);\r
1308                 int retval = addSequence( fis, midi_file.getName() );\r
1309                 try {\r
1310                         fis.close();\r
1311                 } catch( IOException ex ) {\r
1312                         ex.printStackTrace();\r
1313                 }\r
1314                 return retval;\r
1315                         }\r
1316         public int addSequence( InputStream in, String filename )\r
1317                         throws InvalidMidiDataException\r
1318                         {\r
1319                 if( in == null ) return addSequence();\r
1320                 Sequence seq;\r
1321                 try {\r
1322                         seq = MidiSystem.getSequence(in);\r
1323                 } catch ( InvalidMidiDataException e ) {\r
1324                         throw e;\r
1325                 } catch ( EOFException e ) {\r
1326                         // No MIDI data\r
1327                         return -1;\r
1328                 } catch ( IOException e ) {\r
1329                         e.printStackTrace();\r
1330                         return -1;\r
1331                 }\r
1332                 return addSequence( seq, filename );\r
1333                         }\r
1334         public int addSequence( String midi_file_url )\r
1335                         throws InvalidMidiDataException, AccessControlException\r
1336                         {\r
1337                 URL url = toURL( midi_file_url );\r
1338                 if( url == null ) {\r
1339                         return -1;\r
1340                 }\r
1341                 Sequence seq;\r
1342                 try {\r
1343                         seq = MidiSystem.getSequence(url);\r
1344                 } catch ( InvalidMidiDataException e ) {\r
1345                         throw e;\r
1346                 } catch( EOFException e ) {\r
1347                         // No MIDI data\r
1348                         return -1;\r
1349                 } catch( IOException e ) {\r
1350                         e.printStackTrace();\r
1351                         return -1;\r
1352                 } catch( AccessControlException e ) {\r
1353                         throw e;\r
1354                 }\r
1355                 return addSequence( seq, url.getFile().replaceFirst("^.*/","") );\r
1356                         }\r
1357         public void removeSequence( ListSelectionModel sel_model ) {\r
1358                 if( sel_model.isSelectionEmpty() ) return;\r
1359                 int sel_index = sel_model.getMinSelectionIndex();\r
1360                 if( sequences.get(sel_index) == device_manager.timeRangeModel.getSequenceModel() )\r
1361                         device_manager.timeRangeModel.setSequenceModel(null);\r
1362                 sequences.remove(sel_index);\r
1363                 fireTableRowsDeleted( sel_index, sel_index );\r
1364         }\r
1365         public void loadToSequencer( int index ) {\r
1366                 int loaded_index = getLoadedIndex();\r
1367                 if( loaded_index == index ) return;\r
1368                 MidiSequenceModel seq_model = sequences.get(index);\r
1369                 device_manager.timeRangeModel.setSequenceModel(seq_model);\r
1370                 seq_model.fireTableDataChanged();\r
1371                 fireTableCellUpdated( loaded_index, COLUMN_SEQ_POSITION );\r
1372                 fireTableCellUpdated( index, COLUMN_SEQ_POSITION );\r
1373         }\r
1374         public int getLoadedIndex() {\r
1375                 MidiSequenceModel seq_model = device_manager.timeRangeModel.getSequenceModel();\r
1376                 for( int i=0; i<sequences.size(); i++ )\r
1377                         if( sequences.get(i) == seq_model ) return i;\r
1378                 return -1;\r
1379         }\r
1380         public boolean loadNext( int offset ) {\r
1381                 int loaded_index = getLoadedIndex();\r
1382                 int index = (loaded_index < 0 ? 0 : loaded_index + offset);\r
1383                 if( index < 0 || index >= sequences.size() ) return false;\r
1384                 loadToSequencer( index );\r
1385                 return true;\r
1386         }\r
1387         public int getTotalSeconds() {\r
1388                 int total_sec = 0;\r
1389                 long usec;\r
1390                 for( MidiSequenceModel seq_model : sequences ) {\r
1391                         usec = seq_model.getSequence().getMicrosecondLength();\r
1392                         total_sec += (int)( (usec < 0 ? usec += 0x100000000L : usec) / 1000L / 1000L );\r
1393                 }\r
1394                 return total_sec;\r
1395         }\r
1396         public String getTotalLength() {\r
1397                 int sec = getTotalSeconds();\r
1398                 return String.format( "MIDI file playlist - Total length = %02d:%02d", sec/60, sec%60 );\r
1399         }\r
1400         //\r
1401         // 文字列を URL オブジェクトに変換\r
1402         //\r
1403         public URL toURL( String url_string ) {\r
1404                 if( url_string == null || url_string.isEmpty() ) {\r
1405                         return null;\r
1406                 }\r
1407                 URI uri = null;\r
1408                 URL url = null;\r
1409                 try {\r
1410                         uri = new URI(url_string);\r
1411                         url = uri.toURL();\r
1412                 } catch( URISyntaxException e ) {\r
1413                         e.printStackTrace();\r
1414                 } catch( MalformedURLException e ) {\r
1415                         e.printStackTrace();\r
1416                 }\r
1417                 return url;\r
1418         }\r
1419 }\r
1420 \r
1421 //////////////////////////////////////////////////////////\r
1422 //\r
1423 // Track List (MIDI Sequence) Model\r
1424 //\r
1425 //////////////////////////////////////////////////////////\r
1426 class MidiSequenceModel extends AbstractTableModel\r
1427 {\r
1428         public static final int COLUMN_TRACK_NUMBER     = 0;\r
1429         public static final int COLUMN_EVENTS           = 1;\r
1430         public static final int COLUMN_MUTE             = 2;\r
1431         public static final int COLUMN_SOLO             = 3;\r
1432         public static final int COLUMN_RECORD_CHANNEL   = 4;\r
1433         public static final int COLUMN_CHANNEL  = 5;\r
1434         public static final int COLUMN_TRACK_NAME       = 6;\r
1435         public static final String column_titles[] = {\r
1436                 "No.", "Events", "Mute", "Solo", "RecCh", "Ch", "Track name"\r
1437         };\r
1438         public static final int column_width_ratios[] = {\r
1439                 30, 60, 40, 40, 60, 40, 200\r
1440         };\r
1441         private SequenceListModel seq_list_model;\r
1442         private Sequence seq;\r
1443         private SequenceIndex seq_index;\r
1444         private String filename = "";\r
1445         private boolean is_modified = false;\r
1446         private ArrayList<MidiTrackModel> track_models\r
1447         = new ArrayList<MidiTrackModel>();\r
1448 \r
1449         class RecordChannelCellEditor extends DefaultCellEditor {\r
1450                 public RecordChannelCellEditor() {\r
1451                         super(new JComboBox<String>() {\r
1452                                 {\r
1453                                         addItem("OFF");\r
1454                                         for( int i=1; i <= MIDISpec.MAX_CHANNELS; i++ )\r
1455                                                 addItem( String.format( "%d", i ) );\r
1456                                         addItem("ALL");\r
1457                                 }\r
1458                         });\r
1459                 }\r
1460         }\r
1461 \r
1462         public MidiSequenceModel( SequenceListModel slm ) {\r
1463                 seq_list_model = slm;\r
1464         }\r
1465         //\r
1466         // TableModel interface\r
1467         //\r
1468         public int getRowCount() {\r
1469                 return seq == null ? 0 : seq.getTracks().length;\r
1470         }\r
1471         public int getColumnCount() {\r
1472                 return column_titles.length;\r
1473         }\r
1474         public String getColumnName(int column) {\r
1475                 return column_titles[column];\r
1476         }\r
1477         public Class<?> getColumnClass(int column) {\r
1478                 switch(column) {\r
1479                 case COLUMN_TRACK_NUMBER:\r
1480                 case COLUMN_EVENTS:\r
1481                         return Integer.class;\r
1482                 case COLUMN_MUTE:\r
1483                 case COLUMN_SOLO:\r
1484                         return\r
1485                                         (seq == getSequencer().getSequence()) ?\r
1486                                                         Boolean.class : String.class;\r
1487                 case COLUMN_RECORD_CHANNEL:\r
1488                 case COLUMN_CHANNEL:\r
1489                 case COLUMN_TRACK_NAME:\r
1490                         return String.class;\r
1491                 default: return super.getColumnClass(column);\r
1492                 }\r
1493         }\r
1494         public Object getValueAt(int row, int column) {\r
1495                 switch(column) {\r
1496                 case COLUMN_TRACK_NUMBER: return row;\r
1497                 case COLUMN_EVENTS:\r
1498                         return seq.getTracks()[row].size();\r
1499                 case COLUMN_MUTE:\r
1500                         return (seq == getSequencer().getSequence()) ?\r
1501                                         getSequencer().getTrackMute(row) : "";\r
1502                 case COLUMN_SOLO:\r
1503                         return (seq == getSequencer().getSequence()) ?\r
1504                                         getSequencer().getTrackSolo(row) : "";\r
1505                 case COLUMN_RECORD_CHANNEL:\r
1506                         return (seq == getSequencer().getSequence()) ?\r
1507                                         track_models.get(row).getRecordingChannel() : "";\r
1508                 case COLUMN_CHANNEL: {\r
1509                         int ch = track_models.get(row).getChannel();\r
1510                         return ch < 0 ? "" : ch + 1 ;\r
1511                 }\r
1512                 case COLUMN_TRACK_NAME:\r
1513                         return track_models.get(row).toString();\r
1514                 default: return "";\r
1515                 }\r
1516         }\r
1517         public boolean isCellEditable( int row, int column ) {\r
1518                 switch(column) {\r
1519                 case COLUMN_MUTE:\r
1520                 case COLUMN_SOLO:\r
1521                 case COLUMN_RECORD_CHANNEL:\r
1522                         return seq == getSequencer().getSequence();\r
1523                 case COLUMN_CHANNEL:\r
1524                 case COLUMN_TRACK_NAME:\r
1525                         return true;\r
1526                 default:\r
1527                         return false;\r
1528                 }\r
1529         }\r
1530         public void setValueAt(Object val, int row, int column) {\r
1531                 switch(column) {\r
1532                 case COLUMN_MUTE:\r
1533                         getSequencer().setTrackMute( row, ((Boolean)val).booleanValue() );\r
1534                         break;\r
1535                 case COLUMN_SOLO:\r
1536                         getSequencer().setTrackSolo( row, ((Boolean)val).booleanValue() );\r
1537                         break;\r
1538                 case COLUMN_RECORD_CHANNEL:\r
1539                         track_models.get(row).setRecordingChannel((String)val);\r
1540                         break;\r
1541                 case COLUMN_CHANNEL: {\r
1542                         Integer ch;\r
1543                         try {\r
1544                                 ch = new Integer((String)val);\r
1545                         }\r
1546                         catch( NumberFormatException e ) {\r
1547                                 ch = -1;\r
1548                                 break;\r
1549                         }\r
1550                         if( --ch <= 0 || ch > MIDISpec.MAX_CHANNELS )\r
1551                                 break;\r
1552                         MidiTrackModel track_model = track_models.get(row);\r
1553                         int old_ch = track_model.getChannel();\r
1554                         if( ch == old_ch ) break;\r
1555                         track_model.setChannel(ch);\r
1556                         setModified(true);\r
1557                         fireTableCellUpdated(row,COLUMN_EVENTS);\r
1558                         break;\r
1559                 }\r
1560                 case COLUMN_TRACK_NAME:\r
1561                         track_models.get(row).setString((String)val);\r
1562                         break;\r
1563                 }\r
1564                 fireTableCellUpdated(row,column);\r
1565         }\r
1566         // Methods (Table view)\r
1567         //\r
1568         public void sizeColumnWidthToFit( TableColumnModel column_model ) {\r
1569                 int total_width = column_model.getTotalColumnWidth();\r
1570                 int i, total_width_ratio = 0;\r
1571                 for( i=0; i<column_width_ratios.length; i++ ) {\r
1572                         total_width_ratio += column_width_ratios[i];\r
1573                 }\r
1574                 for( i=0; i<column_width_ratios.length; i++ ) {\r
1575                         column_model.getColumn(i).setPreferredWidth(\r
1576                                         total_width * column_width_ratios[i] / total_width_ratio\r
1577                                         );\r
1578                 }\r
1579         }\r
1580         // Methods (sequence)\r
1581         //\r
1582         public Sequence getSequence() { return this.seq; }\r
1583         public void setSequence( Sequence seq ) {\r
1584                 //\r
1585                 getSequencer().recordDisable(null); // The "null" means all tracks\r
1586                 //\r
1587                 this.seq = seq;\r
1588                 int old_size = track_models.size();\r
1589                 if( old_size > 0 ) {\r
1590                         track_models.clear();\r
1591                         fireTableRowsDeleted(0, old_size-1);\r
1592                 }\r
1593                 if( seq == null ) {\r
1594                         seq_index = null;\r
1595                 }\r
1596                 else {\r
1597                         seq_index = new SequenceIndex( seq );\r
1598                         Track tklist[] = seq.getTracks();\r
1599                         for( Track tk : tklist )\r
1600                                 track_models.add( new MidiTrackModel( tk, this ) );\r
1601                         fireTableRowsInserted(0, tklist.length-1);\r
1602                 }\r
1603         }\r
1604         public SequenceIndex getSequenceIndex() {\r
1605                 return this.seq_index;\r
1606         }\r
1607         public void setModified(boolean is_modified) {\r
1608                 this.is_modified = is_modified;\r
1609         }\r
1610         public boolean isModified() { return is_modified; }\r
1611         public void setFilename(String filename) {\r
1612                 this.filename = filename;\r
1613         }\r
1614         public String getFilename() { return filename; }\r
1615         public String toString() {\r
1616                 return MIDISpec.getNameOf(seq);\r
1617         }\r
1618         public boolean setName( String name ) {\r
1619                 if( name.equals(toString()) )\r
1620                         return false;\r
1621                 if( ! MIDISpec.setNameOf(seq,name) )\r
1622                         return false;\r
1623                 setModified(true);\r
1624                 fireTableDataChanged();\r
1625                 return true;\r
1626         }\r
1627         public byte[] getMIDIdata() {\r
1628                 if( seq == null || seq.getTracks().length == 0 ) {\r
1629                         return null;\r
1630                 }\r
1631                 /*\r
1632     int[] file_types = MidiSystem.getMidiFileTypes(seq);\r
1633     for( int i : file_types )\r
1634       System.out.println( "Supported MIDI file type : " + i );\r
1635                  */\r
1636                 ByteArrayOutputStream out = new ByteArrayOutputStream();\r
1637                 try {\r
1638                         MidiSystem.write(seq, 1, out);\r
1639                         return out.toByteArray();\r
1640                 } catch ( IOException e ) {\r
1641                         e.printStackTrace();\r
1642                         return null;\r
1643                 }\r
1644         }\r
1645         public void fireTimeSignatureChanged() {\r
1646                 seq_index = new SequenceIndex( seq );\r
1647         }\r
1648         public void fireTrackChanged( Track tk ) {\r
1649                 int row = getTrackRow(tk);\r
1650                 if( row < 0 ) return;\r
1651                 fireTableRowsUpdated( row, row );\r
1652                 fireSequenceChanged();\r
1653         }\r
1654         public void fireSequenceChanged() {\r
1655                 seq_list_model.fireSequenceChanged(this);\r
1656         }\r
1657         public MidiTrackModel getTrackModel( int index ) {\r
1658                 Track tracks[] = seq.getTracks();\r
1659                 if( tracks.length == 0 ) return null;\r
1660                 Track tk = tracks[index];\r
1661                 for( MidiTrackModel model : track_models )\r
1662                         if( model.getTrack() == tk )\r
1663                                 return model;\r
1664                 return null;\r
1665         }\r
1666         public int getTrackRow( Track tk ) {\r
1667                 Track tracks[] = seq.getTracks();\r
1668                 for( int i=0; i<tracks.length; i++ )\r
1669                         if( tracks[i] == tk )\r
1670                                 return i;\r
1671                 return -1;\r
1672         }\r
1673         public void createTrack() {\r
1674                 Track tk = seq.createTrack();\r
1675                 track_models.add( new MidiTrackModel( tk, this ) );\r
1676                 int last_row = seq.getTracks().length - 1;\r
1677                 fireTableRowsInserted( last_row, last_row );\r
1678         }\r
1679         public void deleteTracks( ListSelectionModel selection_model ) {\r
1680                 if( selection_model.isSelectionEmpty() )\r
1681                         return;\r
1682                 int min_sel_index = selection_model.getMinSelectionIndex();\r
1683                 int max_sel_index = selection_model.getMaxSelectionIndex();\r
1684                 Track tklist[] = seq.getTracks();\r
1685                 for( int i = max_sel_index; i >= min_sel_index; i-- ) {\r
1686                         if( ! selection_model.isSelectedIndex(i) )\r
1687                                 continue;\r
1688                         seq.deleteTrack( tklist[i] );\r
1689                         track_models.remove(i);\r
1690                 }\r
1691                 fireTableRowsDeleted( min_sel_index, max_sel_index );\r
1692         }\r
1693         //\r
1694         // Methods (sequencer)\r
1695         //\r
1696         public Sequencer getSequencer() {\r
1697                 return seq_list_model.device_manager.getSequencer();\r
1698         }\r
1699         public boolean isRecordable() {\r
1700                 if( seq != getSequencer().getSequence() ) return false;\r
1701                 int num_row = getRowCount();\r
1702                 String s;\r
1703                 for( int row=0; row<num_row; row++ ) {\r
1704                         s = (String)getValueAt(\r
1705                                         row, COLUMN_RECORD_CHANNEL\r
1706                                         );\r
1707                         if( s.equals("OFF") ) continue;\r
1708                         return true;\r
1709                 }\r
1710                 return false;\r
1711         }\r
1712 }\r
1713 \r
1714 ////////////////////////////////////////////////////////\r
1715 //\r
1716 // Event List (Track) Model\r
1717 //\r
1718 ////////////////////////////////////////////////////////\r
1719 class MidiTrackModel extends AbstractTableModel\r
1720 {\r
1721         public static final int COLUMN_EVENT_NUMBER     = 0;\r
1722         public static final int COLUMN_TICK_POSITION    = 1;\r
1723         public static final int COLUMN_MEASURE_POSITION = 2;\r
1724         public static final int COLUMN_BEAT_POSITION            = 3;\r
1725         public static final int COLUMN_EXTRA_TICK_POSITION      = 4;\r
1726         public static final int COLUMN_MESSAGE  = 5;\r
1727         public static final String column_titles[] = {\r
1728                 "No.", "TickPos.", "Measure", "Beat", "ExTick", "MIDI Message",\r
1729         };\r
1730         public static final int column_width_ratios[] = {\r
1731                 30, 40, 20,20,20, 280,\r
1732         };\r
1733         private Track track;\r
1734         private MidiSequenceModel parent_model;\r
1735         //\r
1736         // Constructor\r
1737         //\r
1738         public MidiTrackModel() { } // To create empty model\r
1739         public MidiTrackModel( MidiSequenceModel parent_model ) {\r
1740                 this.parent_model = parent_model;\r
1741         }\r
1742         public MidiTrackModel( Track tk, MidiSequenceModel parent_model ) {\r
1743                 this.track = tk;\r
1744                 this.parent_model = parent_model;\r
1745         }\r
1746         // TableModel interface\r
1747         //\r
1748         public int getRowCount() {\r
1749                 return track == null ? 0 : track.size();\r
1750         }\r
1751         public int getColumnCount() {\r
1752                 return column_titles.length;\r
1753         }\r
1754         public String getColumnName(int column) {\r
1755                 return column_titles[column];\r
1756         }\r
1757         public Class<?> getColumnClass(int column) {\r
1758                 switch(column) {\r
1759                 case COLUMN_EVENT_NUMBER:\r
1760                         return Integer.class;\r
1761                 case COLUMN_TICK_POSITION:\r
1762                         return Long.class;\r
1763                 case COLUMN_MEASURE_POSITION:\r
1764                 case COLUMN_BEAT_POSITION:\r
1765                 case COLUMN_EXTRA_TICK_POSITION:\r
1766                         return Integer.class;\r
1767                         // case COLUMN_MESSAGE:\r
1768                         default:\r
1769                                 return String.class;\r
1770                 }\r
1771                 // return getValueAt(0,column).getClass();\r
1772         }\r
1773         public Object getValueAt(int row, int column) {\r
1774                 switch(column) {\r
1775                 case COLUMN_EVENT_NUMBER:\r
1776                         return row;\r
1777 \r
1778                 case COLUMN_TICK_POSITION:\r
1779                         return track.get(row).getTick();\r
1780 \r
1781                 case COLUMN_MEASURE_POSITION:\r
1782                         return parent_model.getSequenceIndex().tickToMeasure(\r
1783                                         track.get(row).getTick()\r
1784                                         ) + 1;\r
1785 \r
1786                 case COLUMN_BEAT_POSITION:\r
1787                         parent_model.getSequenceIndex().tickToMeasure(\r
1788                                         track.get(row).getTick()\r
1789                                         );\r
1790                         return parent_model.getSequenceIndex().last_beat + 1;\r
1791 \r
1792                 case COLUMN_EXTRA_TICK_POSITION:\r
1793                         parent_model.getSequenceIndex().tickToMeasure(\r
1794                                         track.get(row).getTick()\r
1795                                         );\r
1796                         return parent_model.getSequenceIndex().last_extra_tick;\r
1797 \r
1798                 case COLUMN_MESSAGE:\r
1799                         return msgToString(\r
1800                                         track.get(row).getMessage()\r
1801                                         );\r
1802                 default: return "";\r
1803                 }\r
1804         }\r
1805         public boolean isCellEditable(int row, int column) {\r
1806                 switch(column) {\r
1807                 // case COLUMN_EVENT_NUMBER:\r
1808                 case COLUMN_TICK_POSITION:\r
1809                 case COLUMN_MEASURE_POSITION:\r
1810                 case COLUMN_BEAT_POSITION:\r
1811                 case COLUMN_EXTRA_TICK_POSITION:\r
1812                 case COLUMN_MESSAGE:\r
1813                         return true;\r
1814                 default: return false;\r
1815                 }\r
1816         }\r
1817         public void setValueAt(Object value, int row, int column) {\r
1818                 long tick;\r
1819                 switch(column) {\r
1820                 // case COLUMN_EVENT_NUMBER:\r
1821                 case COLUMN_TICK_POSITION:\r
1822                         tick = (Long)value;\r
1823                         break;\r
1824                 case COLUMN_MEASURE_POSITION:\r
1825                         tick = parent_model.getSequenceIndex().measureToTick(\r
1826                                         (Integer)value - 1,\r
1827                                         (Integer)getValueAt( row, COLUMN_BEAT_POSITION ) - 1,\r
1828                                         (Integer)getValueAt( row, COLUMN_EXTRA_TICK_POSITION )\r
1829                                         );\r
1830                         break;\r
1831                 case COLUMN_BEAT_POSITION:\r
1832                         tick = parent_model.getSequenceIndex().measureToTick(\r
1833                                         (Integer)getValueAt( row, COLUMN_MEASURE_POSITION ) - 1,\r
1834                                         (Integer)value - 1,\r
1835                                         (Integer)getValueAt( row, COLUMN_EXTRA_TICK_POSITION )\r
1836                                         );\r
1837                         break;\r
1838                 case COLUMN_EXTRA_TICK_POSITION:\r
1839                         tick = parent_model.getSequenceIndex().measureToTick(\r
1840                                         (Integer)getValueAt( row, COLUMN_MEASURE_POSITION ) - 1,\r
1841                                         (Integer)getValueAt( row, COLUMN_BEAT_POSITION ) - 1,\r
1842                                         (Integer)value\r
1843                                         );\r
1844                         break;\r
1845                 case COLUMN_MESSAGE:\r
1846                         return;\r
1847                 default: return;\r
1848                 }\r
1849                 changeEventTick(row,tick);\r
1850         }\r
1851 \r
1852         // Methods (Table view)\r
1853         //\r
1854         public void sizeColumnWidthToFit( TableColumnModel column_model ) {\r
1855                 int total_width = column_model.getTotalColumnWidth();\r
1856                 int i, total_width_ratio = 0;\r
1857                 for( i=0; i<column_width_ratios.length; i++ ) {\r
1858                         total_width_ratio += column_width_ratios[i];\r
1859                 }\r
1860                 for( i=0; i<column_width_ratios.length; i++ ) {\r
1861                         column_model.getColumn(i).setPreferredWidth(\r
1862                                         total_width * column_width_ratios[i] / total_width_ratio\r
1863                                         );\r
1864                 }\r
1865         }\r
1866         // Methods\r
1867         //\r
1868         private boolean isRhythmPart(int ch) {\r
1869                 return (ch == 9);\r
1870         }\r
1871         // トラックオブジェクトの取得\r
1872         public Track getTrack() { return track; }\r
1873         //\r
1874         // 文字列としてトラック名を返す\r
1875         public String toString() {\r
1876                 return MIDISpec.getNameOf(track);\r
1877         }\r
1878         // トラック名をセットする\r
1879         public boolean setString( String name ) {\r
1880                 if(\r
1881                                 name.equals(toString())\r
1882                                 ||\r
1883                                 ! MIDISpec.setNameOf( track, name )\r
1884                                 )\r
1885                         return false;\r
1886                 parent_model.setModified(true);\r
1887                 parent_model.fireSequenceChanged();\r
1888                 fireTableDataChanged();\r
1889                 return true;\r
1890         }\r
1891         //\r
1892         // 録音中の MIDI チャンネル\r
1893         private String rec_ch = "OFF";\r
1894         public String getRecordingChannel() {\r
1895                 return rec_ch;\r
1896         }\r
1897         public void setRecordingChannel(String ch_str) {\r
1898                 Sequencer sequencer = parent_model.getSequencer();\r
1899                 if( ch_str.equals("OFF") ) {\r
1900                         sequencer.recordDisable( track );\r
1901                 }\r
1902                 else if( ch_str.equals("ALL") ) {\r
1903                         sequencer.recordEnable( track, -1 );\r
1904                 }\r
1905                 else {\r
1906                         try {\r
1907                                 int ch = Integer.decode(ch_str).intValue() - 1;\r
1908                                 sequencer.recordEnable( track, ch );\r
1909                         } catch( NumberFormatException nfe ) {\r
1910                                 sequencer.recordDisable( track );\r
1911                                 rec_ch = "OFF";\r
1912                                 return;\r
1913                         }\r
1914                 }\r
1915                 rec_ch = ch_str;\r
1916         }\r
1917         //\r
1918         // 対象MIDIチャンネル\r
1919         public int getChannel() {\r
1920                 MidiMessage msg;\r
1921                 ShortMessage smsg;\r
1922                 int index, ch, prev_ch = -1, track_size = track.size();\r
1923                 for( index=0; index < track_size; index++ ) {\r
1924                         msg = track.get(index).getMessage();\r
1925                         if( ! (msg instanceof ShortMessage) )\r
1926                                 continue;\r
1927                         smsg = (ShortMessage)msg;\r
1928                         if( ! MIDISpec.isChannelMessage(smsg) )\r
1929                                 continue;\r
1930                         ch = smsg.getChannel();\r
1931                         if( prev_ch >= 0 && prev_ch != ch ) {\r
1932                                 // MIDIチャンネルが統一されていない場合\r
1933                                 return -1;\r
1934                         }\r
1935                         prev_ch = ch;\r
1936                 }\r
1937                 // すべてのMIDIチャンネルが同じならそれを返す\r
1938                 return prev_ch;\r
1939         }\r
1940         public void setChannel(int ch) {\r
1941                 // すべてのチャンネルメッセージに対して\r
1942                 // 同一のMIDIチャンネルをセットする\r
1943                 MidiMessage msg;\r
1944                 ShortMessage smsg;\r
1945                 int index, track_size = track.size();\r
1946                 for( index=0; index < track_size; index++ ) {\r
1947                         msg = track.get(index).getMessage();\r
1948                         if(\r
1949                                         ! (msg instanceof ShortMessage)\r
1950                                         ||\r
1951                                         ! MIDISpec.isChannelMessage(smsg = (ShortMessage)msg)\r
1952                                         ||\r
1953                                         smsg.getChannel() == ch\r
1954                                         )\r
1955                                 continue;\r
1956                         try {\r
1957                                 smsg.setMessage(\r
1958                                                 smsg.getCommand(), ch,\r
1959                                                 smsg.getData1(), smsg.getData2()\r
1960                                                 );\r
1961                         }\r
1962                         catch( InvalidMidiDataException e ) {\r
1963                                 e.printStackTrace();\r
1964                         }\r
1965                         parent_model.setModified(true);\r
1966                 }\r
1967                 parent_model.fireTrackChanged( track );\r
1968                 parent_model.fireSequenceChanged();\r
1969                 fireTableDataChanged();\r
1970         }\r
1971         //\r
1972         // MIDI イベントの tick 位置変更\r
1973         public void changeEventTick(int row, long new_tick) {\r
1974                 MidiEvent old_midi_event = track.get(row);\r
1975                 if( old_midi_event.getTick() == new_tick ) {\r
1976                         return;\r
1977                 }\r
1978                 MidiMessage msg = old_midi_event.getMessage();\r
1979                 MidiEvent new_midi_event = new MidiEvent(msg,new_tick);\r
1980                 track.remove(old_midi_event);\r
1981                 track.add(new_midi_event);\r
1982                 fireTableDataChanged();\r
1983                 //\r
1984                 if( MIDISpec.isEOT(msg) ) {\r
1985                         // EOTの場所が変わると曲の長さが変わるので、親モデルへ通知する。\r
1986                         parent_model.fireSequenceChanged();\r
1987                 }\r
1988         }\r
1989         //\r
1990         // MIDI tick から位置を取得(バイナリーサーチ)\r
1991         public int tickToIndex( long tick ) {\r
1992                 if( track == null ) return 0;\r
1993                 int min_index = 0;\r
1994                 int max_index = track.size() - 1;\r
1995                 long current_tick;\r
1996                 int current_index;\r
1997                 while( min_index < max_index ) {\r
1998                         current_index = (min_index + max_index) / 2 ;\r
1999                         current_tick = track.get(current_index).getTick();\r
2000                         if( tick > current_tick ) {\r
2001                                 min_index = current_index + 1;\r
2002                         }\r
2003                         else if( tick < current_tick ) {\r
2004                                 max_index = current_index - 1;\r
2005                         }\r
2006                         else {\r
2007                                 return current_index;\r
2008                         }\r
2009                 }\r
2010                 return (min_index + max_index) / 2;\r
2011         }\r
2012         // NoteOn/NoteOff ペアの一方のインデックスから、\r
2013         // その相手を返す\r
2014         public int getIndexOfPartnerFor( int index ) {\r
2015                 if( track == null || index >= track.size() ) return -1;\r
2016                 MidiMessage msg = track.get(index).getMessage();\r
2017                 if( ! (msg instanceof ShortMessage) ) return -1;\r
2018                 ShortMessage sm = (ShortMessage)msg;\r
2019                 int cmd = sm.getCommand();\r
2020                 int i;\r
2021                 int ch = sm.getChannel();\r
2022                 int note = sm.getData1();\r
2023                 MidiMessage partner_msg;\r
2024                 ShortMessage partner_sm;\r
2025                 int partner_cmd;\r
2026 \r
2027                 switch( cmd ) {\r
2028                 case 0x90: // NoteOn\r
2029                 if( sm.getData2() > 0 ) {\r
2030                         // Search NoteOff event forward\r
2031                         for( i = index + 1; i < track.size(); i++ ) {\r
2032                                 partner_msg = track.get(i).getMessage();\r
2033                                 if( ! (partner_msg instanceof ShortMessage ) ) continue;\r
2034                                 partner_sm = (ShortMessage)partner_msg;\r
2035                                 partner_cmd = partner_sm.getCommand();\r
2036                                 if( partner_cmd != 0x80 && partner_cmd != 0x90 ||\r
2037                                                 partner_cmd == 0x90 && partner_sm.getData2() > 0\r
2038                                                 ) {\r
2039                                         // Not NoteOff\r
2040                                         continue;\r
2041                                 }\r
2042                                 if( ch != partner_sm.getChannel() || note != partner_sm.getData1() ) {\r
2043                                         // Not my partner\r
2044                                         continue;\r
2045                                 }\r
2046                                 return i;\r
2047                         }\r
2048                         break;\r
2049                 }\r
2050                 // When velocity is 0, it means Note Off, so no break.\r
2051                 case 0x80: // NoteOff\r
2052                         // Search NoteOn event backward\r
2053                         for( i = index - 1; i >= 0; i-- ) {\r
2054                                 partner_msg = track.get(i).getMessage();\r
2055                                 if( ! (partner_msg instanceof ShortMessage ) ) continue;\r
2056                                 partner_sm = (ShortMessage)partner_msg;\r
2057                                 partner_cmd = partner_sm.getCommand();\r
2058                                 if( partner_cmd != 0x90 || partner_sm.getData2() <= 0 ) {\r
2059                                         // Not NoteOn\r
2060                                         continue;\r
2061                                 }\r
2062                                 if( ch != partner_sm.getChannel() || note != partner_sm.getData1() ) {\r
2063                                         // Not my partner\r
2064                                         continue;\r
2065                                 }\r
2066                                 return i;\r
2067                         }\r
2068                         break;\r
2069                 }\r
2070                 // Not found\r
2071                 return -1;\r
2072         }\r
2073         //\r
2074         public boolean isTimeSignature( MidiMessage msg ) {\r
2075                 // 拍子記号のとき True を返す\r
2076                 return\r
2077                                 (msg instanceof MetaMessage)\r
2078                                 &&\r
2079                                 ((MetaMessage)msg).getType() == 0x58;\r
2080         }\r
2081         public boolean isNote( int index ) { // Note On または Note Off のとき True を返す\r
2082                 MidiEvent midi_evt = getMidiEvent(index);\r
2083                 MidiMessage msg = midi_evt.getMessage();\r
2084                 if( ! (msg instanceof ShortMessage) ) return false;\r
2085                 int cmd = ((ShortMessage)msg).getCommand();\r
2086                 return cmd == ShortMessage.NOTE_ON || cmd == ShortMessage.NOTE_OFF ;\r
2087         }\r
2088         public boolean hasTrack() { return track != null; }\r
2089         //\r
2090         // イベントの取得\r
2091         //\r
2092         public MidiEvent getMidiEvent( int index ) {\r
2093                 return track.get(index);\r
2094         }\r
2095         public MidiEvent[] getMidiEvents( ListSelectionModel sel_model ) {\r
2096                 Vector<MidiEvent> events = new Vector<MidiEvent>();\r
2097                 if( ! sel_model.isSelectionEmpty() ) {\r
2098                         int min_sel_index = sel_model.getMinSelectionIndex();\r
2099                         int max_sel_index = sel_model.getMaxSelectionIndex();\r
2100                         for( int i = min_sel_index; i <= max_sel_index; i++ )\r
2101                                 if( sel_model.isSelectedIndex(i) )\r
2102                                         events.add(track.get(i));\r
2103                 }\r
2104                 return events.toArray(new MidiEvent[1]);\r
2105         }\r
2106         //\r
2107         // イベントの追加\r
2108         //\r
2109         public boolean addMidiEvent( MidiEvent midi_event ) {\r
2110                 if( !(track.add(midi_event)) )\r
2111                         return false;\r
2112                 if( isTimeSignature(midi_event.getMessage()) )\r
2113                         parent_model.fireTimeSignatureChanged();\r
2114                 parent_model.fireTrackChanged( track );\r
2115                 int last_index = track.size() - 1;\r
2116                 fireTableRowsInserted( last_index-1, last_index-1 );\r
2117                 return true;\r
2118         }\r
2119         public boolean addMidiEvents(\r
2120                         MidiEvent midi_events[],\r
2121                         long destination_tick,\r
2122                         int midi_events_ppq\r
2123                         ) {\r
2124                 int dest_ppq = parent_model.getSequence().getResolution();\r
2125                 boolean done = false, has_time_signature = false;\r
2126                 long event_tick = 0;\r
2127                 long first_event_tick = -1;\r
2128                 MidiEvent new_midi_event;\r
2129                 MidiMessage msg;\r
2130                 for( MidiEvent midi_event : midi_events ) {\r
2131                         event_tick = midi_event.getTick();\r
2132                         msg = midi_event.getMessage();\r
2133                         if( first_event_tick < 0 ) {\r
2134                                 first_event_tick = event_tick;\r
2135                                 new_midi_event = new MidiEvent(\r
2136                                                 msg, destination_tick\r
2137                                                 );\r
2138                         }\r
2139                         else {\r
2140                                 new_midi_event = new MidiEvent(\r
2141                                                 msg,\r
2142                                                 destination_tick + (event_tick - first_event_tick) * dest_ppq / midi_events_ppq\r
2143                                                 );\r
2144                         }\r
2145                         if( ! track.add(new_midi_event) ) continue;\r
2146                         done = true;\r
2147                         if( isTimeSignature(msg) ) has_time_signature = true;\r
2148                 }\r
2149                 if( done ) {\r
2150                         if( has_time_signature )\r
2151                                 parent_model.fireTimeSignatureChanged();\r
2152                         parent_model.fireTrackChanged( track );\r
2153                         int last_index = track.size() - 1;\r
2154                         int old_last_index = last_index - midi_events.length;\r
2155                         fireTableRowsInserted( old_last_index, last_index );\r
2156                 }\r
2157                 return done;\r
2158         }\r
2159         //\r
2160         // イベントの削除\r
2161         //\r
2162         public void removeMidiEvents( MidiEvent midi_events[] ) {\r
2163                 boolean had_time_signature = false;\r
2164                 for( MidiEvent midi_event : midi_events ) {\r
2165                         if( isTimeSignature(midi_event.getMessage()) )\r
2166                                 had_time_signature = true;\r
2167                         track.remove(midi_event);\r
2168                 }\r
2169                 if( had_time_signature )\r
2170                         parent_model.fireTimeSignatureChanged();\r
2171                 parent_model.fireTrackChanged( track );\r
2172                 int last_index = track.size() - 1;\r
2173                 int old_last_index = last_index + midi_events.length;\r
2174                 if( last_index < 0 ) last_index = 0;\r
2175                 fireTableRowsDeleted( old_last_index, last_index );\r
2176         }\r
2177         public void removeMidiEvents( ListSelectionModel sel_model ) {\r
2178                 removeMidiEvents( getMidiEvents(sel_model) );\r
2179         }\r
2180         //\r
2181         // イベントの表示\r
2182         //\r
2183         public String msgToString(MidiMessage msg) {\r
2184                 String str = "";\r
2185                 if( msg instanceof ShortMessage ) {\r
2186                         ShortMessage shortmsg = (ShortMessage)msg;\r
2187                         int status = msg.getStatus();\r
2188                         String status_name = MIDISpec.getStatusName(status);\r
2189                         int data1 = shortmsg.getData1();\r
2190                         int data2 = shortmsg.getData2();\r
2191                         if( MIDISpec.isChannelMessage(status) ) {\r
2192                                 int ch = shortmsg.getChannel();\r
2193                                 String ch_prefix = "Ch."+(ch+1) + ": ";\r
2194                                 String status_prefix = (\r
2195                                                 status_name == null ? String.format("status=0x%02X",status) : status_name\r
2196                                                 ) + ": ";\r
2197                                 int cmd = shortmsg.getCommand();\r
2198                                 switch( cmd ) {\r
2199                                 case ShortMessage.NOTE_OFF:\r
2200                                 case ShortMessage.NOTE_ON:\r
2201                                         str += ch_prefix + status_prefix + data1;\r
2202                                         str += ":[";\r
2203                                         if( isRhythmPart(ch) ) {\r
2204                                                 str += MIDISpec.getPercussionName(data1);\r
2205                                         }\r
2206                                         else {\r
2207                                                 str += Music.NoteSymbol.noteNoToSymbol(data1);\r
2208                                         }\r
2209                                         str +="] Velocity=" + data2;\r
2210                                         break;\r
2211                                 case ShortMessage.POLY_PRESSURE:\r
2212                                         str += ch_prefix + status_prefix + "Note=" + data1 + " Pressure=" + data2;\r
2213                                         break;\r
2214                                 case ShortMessage.PROGRAM_CHANGE:\r
2215                                         str += ch_prefix + status_prefix + data1 + ":[" + MIDISpec.instrument_names[data1] + "]";\r
2216                                         if( data2 != 0 ) str += " data2=" + data2;\r
2217                                         break;\r
2218                                 case ShortMessage.CHANNEL_PRESSURE:\r
2219                                         str += ch_prefix + status_prefix + data1;\r
2220                                         if( data2 != 0 ) str += " data2=" + data2;\r
2221                                         break;\r
2222                                 case ShortMessage.PITCH_BEND:\r
2223                                 {\r
2224                                         int val = (\r
2225                                                         (data1 & 0x7F) | ( (data2 & 0x7F) << 7 )\r
2226                                                         );\r
2227                                         str += ch_prefix + status_prefix + ( (val-8192) * 100 / 8191) + "% (" + val + ")";\r
2228                                 }\r
2229                                 break;\r
2230                                 case ShortMessage.CONTROL_CHANGE:\r
2231                                 {\r
2232                                         // Control / Mode message name\r
2233                                         String ctrl_name = MIDISpec.getControllerName(data1);\r
2234                                         str += ch_prefix + (data1 < 0x78 ? "CtrlChg: " : "ModeMsg: ");\r
2235                                         if( ctrl_name == null ) {\r
2236                                                 str += " No.=" + data1 + " Value=" + data2;\r
2237                                                 return str;\r
2238                                         }\r
2239                                         str += ctrl_name;\r
2240                                         //\r
2241                                         // Controller's value\r
2242                                         switch( data1 ) {\r
2243                                         case 0x40: case 0x41: case 0x42: case 0x43: case 0x45:\r
2244                                                 str += " " + ( data2==0x3F?"OFF":data2==0x40?"ON":data2 );\r
2245                                                 break;\r
2246                                         case 0x44: // Legato Footswitch\r
2247                                                 str += " " + ( data2==0x3F?"Normal":data2==0x40?"Legato":data2 );\r
2248                                                 break;\r
2249                                         case 0x7A: // Local Control\r
2250                                                 str += " " + ( data2==0x00?"OFF":data2==0x7F?"ON":data2 );\r
2251                                                 break;\r
2252                                         default:\r
2253                                                 str += " " + data2;\r
2254                                                 break;\r
2255                                         }\r
2256                                 }\r
2257                                 break;\r
2258 \r
2259                                 default:\r
2260                                         // Never reached here\r
2261                                         break;\r
2262                                 }\r
2263                         }\r
2264                         else { // System Message\r
2265                                 str += (status_name == null ? ("status="+status) : status_name );\r
2266                                 str += " (" + data1 + "," + data2 + ")";\r
2267                         }\r
2268                         return str;\r
2269                 }\r
2270                 else if( msg instanceof MetaMessage ) {\r
2271                         MetaMessage metamsg = (MetaMessage)msg;\r
2272                         byte[] msgdata = metamsg.getData();\r
2273                         int msgtype = metamsg.getType();\r
2274                         str += "Meta: ";\r
2275                         String meta_name = MIDISpec.getMetaName(msgtype);\r
2276                         if( meta_name == null ) {\r
2277                                 str += "Unknown MessageType="+msgtype + " Values=(";\r
2278                                 for( byte b : msgdata ) str += String.format( " %02X", b );\r
2279                                 str += " )";\r
2280                                 return str;\r
2281                         }\r
2282                         // Add the message type name\r
2283                         str += meta_name;\r
2284                         //\r
2285                         // Add the text data\r
2286                         if( MIDISpec.hasMetaText(msgtype) ) {\r
2287                                 str +=" ["+(new String(msgdata))+"]";\r
2288                                 return str;\r
2289                         }\r
2290                         // Add the numeric data\r
2291                         switch(msgtype) {\r
2292                         case 0x00: // Sequence Number (for MIDI Format 2)\r
2293                                 if( msgdata.length == 2 ) {\r
2294                                         str += String.format(\r
2295                                                         ": %04X",\r
2296                                                         ((msgdata[0] & 0xFF) << 8) | (msgdata[1] & 0xFF)\r
2297                                                         );\r
2298                                         break;\r
2299                                 }\r
2300                                 str += ": Size not 2 byte : data=(";\r
2301                                 for( byte b : msgdata ) str += String.format( " %02X", b );\r
2302                                 str += " )";\r
2303                                 break;\r
2304                         case 0x20: // MIDI Ch.Prefix\r
2305                         case 0x21: // MIDI Output Port\r
2306                                 if( msgdata.length == 1 ) {\r
2307                                         str += String.format( ": %02X", msgdata[0] & 0xFF );\r
2308                                         break;\r
2309                                 }\r
2310                                 str += ": Size not 1 byte : data=(";\r
2311                                 for( byte b : msgdata ) str += String.format( " %02X", b );\r
2312                                 str += " )";\r
2313                                 break;\r
2314                         case 0x51: // Tempo\r
2315                                 str += ": " + MIDISpec.byteArrayToQpmTempo( msgdata ) + "[QPM] (";\r
2316                                 for( byte b : msgdata ) str += String.format( " %02X", b );\r
2317                                 str += " )";\r
2318                                 break;\r
2319                         case 0x54: // SMPTE Offset\r
2320                                 if( msgdata.length == 5 ) {\r
2321                                         str += ": "\r
2322                                                         + (msgdata[0] & 0xFF) + ":"\r
2323                                                         + (msgdata[1] & 0xFF) + ":"\r
2324                                                         + (msgdata[2] & 0xFF) + "."\r
2325                                                         + (msgdata[3] & 0xFF) + "."\r
2326                                                         + (msgdata[4] & 0xFF);\r
2327                                         break;\r
2328                                 }\r
2329                                 str += ": Size not 5 byte : data=(";\r
2330                                 for( byte b : msgdata ) str += String.format( " %02X", b );\r
2331                                 str += " )";\r
2332                                 break;\r
2333                         case 0x58: // Time Signature\r
2334                                 if( msgdata.length == 4 ) {\r
2335                                         str +=": " + msgdata[0] + "/" + (1 << msgdata[1]);\r
2336                                         str +=", "+msgdata[2]+"[clk/beat], "+msgdata[3]+"[32nds/24clk]";\r
2337                                         break;\r
2338                                 }\r
2339                                 str += ": Size not 4 byte : data=(";\r
2340                                 for( byte b : msgdata ) str += String.format( " %02X", b );\r
2341                                 str += " )";\r
2342                                 break;\r
2343                         case 0x59: // Key Signature\r
2344                                 if( msgdata.length == 2 ) {\r
2345                                         Music.Key key = new Music.Key(msgdata);\r
2346                                         str += ": " + key.signatureDescription();\r
2347                                         str += " (" + key.toStringIn(Music.SymbolLanguage.NAME) + ")";\r
2348                                         break;\r
2349                                 }\r
2350                                 str += ": Size not 2 byte : data=(";\r
2351                                 for( byte b : msgdata ) str += String.format( " %02X", b );\r
2352                                 str += " )";\r
2353                                 break;\r
2354                         case 0x7F: // Sequencer Specific Meta Event\r
2355                                 str += " (";\r
2356                                 for( byte b : msgdata ) str += String.format( " %02X", b );\r
2357                                 str += " )";\r
2358                                 break;\r
2359                         }\r
2360                         return str;\r
2361                 }\r
2362                 else if( msg instanceof SysexMessage ) {\r
2363                         SysexMessage sysexmsg = (SysexMessage)msg;\r
2364                         int status = sysexmsg.getStatus();\r
2365                         byte[] msgdata = sysexmsg.getData();\r
2366                         int data_byte_pos = 1;\r
2367                         switch( status ) {\r
2368                         case SysexMessage.SYSTEM_EXCLUSIVE:\r
2369                                 str += "SysEx: ";\r
2370                                 break;\r
2371                         case SysexMessage.SPECIAL_SYSTEM_EXCLUSIVE:\r
2372                                 str += "SysEx(Special): ";\r
2373                                 break;\r
2374                         default:\r
2375                                 str += "SysEx: Invalid (status="+status+") ";\r
2376                                 break;\r
2377                         }\r
2378                         if( msgdata.length < 1 ) {\r
2379                                 str += " Invalid data size: " + msgdata.length;\r
2380                                 return str;\r
2381                         }\r
2382                         int manufacturer_id = (int)(msgdata[0] & 0xFF );\r
2383                         int device_id = (int)(msgdata[1] & 0xFF);\r
2384                         int model_id = (int)(msgdata[2] & 0xFF);\r
2385                         String manufacturer_name\r
2386                         = MIDISpec.getSysExManufacturerName(manufacturer_id);\r
2387                         if( manufacturer_name == null ) {\r
2388                                 manufacturer_name = String.format( "[Manufacturer code %02X]", msgdata[0] );\r
2389                         }\r
2390                         str += manufacturer_name + String.format( " (DevID=0x%02X)", device_id );\r
2391                         switch( manufacturer_id ) {\r
2392                         case 0x7E: // Non-Realtime Universal\r
2393                                 data_byte_pos++;\r
2394                                 int sub_id_1 = (int)(msgdata[2] & 0xFF);\r
2395                                 int sub_id_2 = (int)(msgdata[3] & 0xFF);\r
2396                                 switch( sub_id_1 ) {\r
2397                                 case 0x09: // General MIDI (GM)\r
2398                                         switch( sub_id_2 ) {\r
2399                                         case 0x01: str += " GM System ON"; return str;\r
2400                                         case 0x02: str += " GM System OFF"; return str;\r
2401                                         }\r
2402                                         break;\r
2403                                 default:\r
2404                                         break;\r
2405                                 }\r
2406                                 break;\r
2407                                 // case 0x7F: // Realtime Universal\r
2408                         case 0x41: // Roland\r
2409                                 data_byte_pos++;\r
2410                                 switch( model_id ) {\r
2411                                 case 0x42:\r
2412                                         str += " [GS]"; data_byte_pos++;\r
2413                                         if( msgdata[3]==0x12 ) {\r
2414                                                 str += "DT1:"; data_byte_pos++;\r
2415                                                 switch( msgdata[4] ) {\r
2416                                                 case 0x00:\r
2417                                                         if( msgdata[5]==0x00 ) {\r
2418                                                                 if( msgdata[6]==0x7F ) {\r
2419                                                                         if( msgdata[7]==0x00 ) {\r
2420                                                                                 str += " [88] System Mode Set (Mode 1: Single Module)"; return str;\r
2421                                                                         }\r
2422                                                                         else if( msgdata[7]==0x01 ) {\r
2423                                                                                 str += " [88] System Mode Set (Mode 2: Double Module)"; return str;\r
2424                                                                         }\r
2425                                                                 }\r
2426                                                         }\r
2427                                                         else if( msgdata[5]==0x01 ) {\r
2428                                                                 int port = (msgdata[7] & 0xFF);\r
2429                                                                 str += String.format(\r
2430                                                                                 " [88] Ch.Msg Rx Port: Block=0x%02X, Port=%s",\r
2431                                                                                 msgdata[6],\r
2432                                                                                 port==0?"A":port==1?"B":String.format("0x%02X",port)\r
2433                                                                                 );\r
2434                                                                 return str;\r
2435                                                         }\r
2436                                                         break;\r
2437                                                 case 0x40:\r
2438                                                         if( msgdata[5]==0x00 ) {\r
2439                                                                 switch( msgdata[6] ) {\r
2440                                                                 case 0x00: str += " Master Tune: "; data_byte_pos += 3; break;\r
2441                                                                 case 0x04: str += " Master Volume: "; data_byte_pos += 3; break;\r
2442                                                                 case 0x05: str += " Master Key Shift: "; data_byte_pos += 3; break;\r
2443                                                                 case 0x06: str += " Master Pan: "; data_byte_pos += 3; break;\r
2444                                                                 case 0x7F:\r
2445                                                                         switch( msgdata[7] ) {\r
2446                                                                         case 0x00: str += " GS Reset"; return str;\r
2447                                                                         case 0x7F: str += " Exit GS Mode"; return str;\r
2448                                                                         }\r
2449                                                                         break;\r
2450                                                                 }\r
2451                                                         }\r
2452                                                         else if( msgdata[5]==0x01 ) {\r
2453                                                                 switch( msgdata[6] ) {\r
2454                                                                 // case 0x00: str += ""; break;\r
2455                                                                 // case 0x10: str += ""; break;\r
2456                                                                 case 0x30: str += " Reverb Macro: "; data_byte_pos += 3; break;\r
2457                                                                 case 0x31: str += " Reverb Character: "; data_byte_pos += 3; break;\r
2458                                                                 case 0x32: str += " Reverb Pre-LPF: "; data_byte_pos += 3; break;\r
2459                                                                 case 0x33: str += " Reverb Level: "; data_byte_pos += 3; break;\r
2460                                                                 case 0x34: str += " Reverb Time: "; data_byte_pos += 3; break;\r
2461                                                                 case 0x35: str += " Reverb Delay FB: "; data_byte_pos += 3; break;\r
2462                                                                 case 0x36: str += " Reverb Chorus Level: "; data_byte_pos += 3; break;\r
2463                                                                 case 0x37: str += " [88] Reverb Predelay Time: "; data_byte_pos += 3; break;\r
2464                                                                 case 0x38: str += " Chorus Macro: "; data_byte_pos += 3; break;\r
2465                                                                 case 0x39: str += " Chorus Pre-LPF: "; data_byte_pos += 3; break;\r
2466                                                                 case 0x3A: str += " Chorus Level: "; data_byte_pos += 3; break;\r
2467                                                                 case 0x3B: str += " Chorus FB: "; data_byte_pos += 3; break;\r
2468                                                                 case 0x3C: str += " Chorus Delay: "; data_byte_pos += 3; break;\r
2469                                                                 case 0x3D: str += " Chorus Rate: "; data_byte_pos += 3; break;\r
2470                                                                 case 0x3E: str += " Chorus Depth: "; data_byte_pos += 3; break;\r
2471                                                                 case 0x3F: str += " Chorus Send Level To Reverb: "; data_byte_pos += 3; break;\r
2472                                                                 case 0x40: str += " [88] Chorus Send Level To Delay: "; data_byte_pos += 3; break;\r
2473                                                                 case 0x50: str += " [88] Delay Macro: "; data_byte_pos += 3; break;\r
2474                                                                 case 0x51: str += " [88] Delay Pre-LPF: "; data_byte_pos += 3; break;\r
2475                                                                 case 0x52: str += " [88] Delay Time Center: "; data_byte_pos += 3; break;\r
2476                                                                 case 0x53: str += " [88] Delay Time Ratio Left: "; data_byte_pos += 3; break;\r
2477                                                                 case 0x54: str += " [88] Delay Time Ratio Right: "; data_byte_pos += 3; break;\r
2478                                                                 case 0x55: str += " [88] Delay Level Center: "; data_byte_pos += 3; break;\r
2479                                                                 case 0x56: str += " [88] Delay Level Left: "; data_byte_pos += 3; break;\r
2480                                                                 case 0x57: str += " [88] Delay Level Right: "; data_byte_pos += 3; break;\r
2481                                                                 case 0x58: str += " [88] Delay Level: "; data_byte_pos += 3; break;\r
2482                                                                 case 0x59: str += " [88] Delay FB: "; data_byte_pos += 3; break;\r
2483                                                                 case 0x5A: str += " [88] Delay Send Level To Reverb: "; data_byte_pos += 3; break;\r
2484                                                                 }\r
2485                                                         }\r
2486                                                         else if( msgdata[5]==0x02 ) {\r
2487                                                                 switch( msgdata[6] ) {\r
2488                                                                 case 0x00: str += " [88] EQ Low Freq: "; data_byte_pos += 3; break;\r
2489                                                                 case 0x01: str += " [88] EQ Low Gain: "; data_byte_pos += 3; break;\r
2490                                                                 case 0x02: str += " [88] EQ High Freq: "; data_byte_pos += 3; break;\r
2491                                                                 case 0x03: str += " [88] EQ High Gain: "; data_byte_pos += 3; break;\r
2492                                                                 }\r
2493                                                         }\r
2494                                                         else if( msgdata[5]==0x03 ) {\r
2495                                                                 if( msgdata[6] == 0x00 ) {\r
2496                                                                         str += " [Pro] EFX Type: "; data_byte_pos += 3;\r
2497                                                                 }\r
2498                                                                 else if( msgdata[6] >= 0x03 && msgdata[6] <= 0x16 ) {\r
2499                                                                         str += String.format(" [Pro] EFX Param %d", msgdata[6]-2 );\r
2500                                                                         data_byte_pos += 3;\r
2501                                                                 }\r
2502                                                                 else if( msgdata[6] == 0x17 ) {\r
2503                                                                         str += " [Pro] EFX Send Level To Reverb: "; data_byte_pos += 3;\r
2504                                                                 }\r
2505                                                                 else if( msgdata[6] == 0x18 ) {\r
2506                                                                         str += " [Pro] EFX Send Level To Chorus: "; data_byte_pos += 3;\r
2507                                                                 }\r
2508                                                                 else if( msgdata[6] == 0x19 ) {\r
2509                                                                         str += " [Pro] EFX Send Level To Delay: "; data_byte_pos += 3;\r
2510                                                                 }\r
2511                                                                 else if( msgdata[6] == 0x1B ) {\r
2512                                                                         str += " [Pro] EFX Ctrl Src1: "; data_byte_pos += 3;\r
2513                                                                 }\r
2514                                                                 else if( msgdata[6] == 0x1C ) {\r
2515                                                                         str += " [Pro] EFX Ctrl Depth1: "; data_byte_pos += 3;\r
2516                                                                 }\r
2517                                                                 else if( msgdata[6] == 0x1D ) {\r
2518                                                                         str += " [Pro] EFX Ctrl Src2: "; data_byte_pos += 3;\r
2519                                                                 }\r
2520                                                                 else if( msgdata[6] == 0x1E ) {\r
2521                                                                         str += " [Pro] EFX Ctrl Depth2: "; data_byte_pos += 3;\r
2522                                                                 }\r
2523                                                                 else if( msgdata[6] == 0x1F ) {\r
2524                                                                         str += " [Pro] EFX Send EQ Switch: "; data_byte_pos += 3;\r
2525                                                                 }\r
2526                                                         }\r
2527                                                         else if( (msgdata[5] & 0xF0) == 0x10 ) {\r
2528                                                                 int ch = (msgdata[5] & 0x0F);\r
2529                                                                 if( ch <= 9 ) ch--; else if( ch == 0 ) ch = 9;\r
2530                                                                 if( msgdata[6]==0x02 ) {\r
2531                                                                         str += String.format(\r
2532                                                                                         " Rx Ch: Part=%d(0x%02X) Ch=0x%02X", (ch+1),  msgdata[5], msgdata[7]\r
2533                                                                                         );\r
2534                                                                         return str;\r
2535                                                                 }\r
2536                                                                 else if( msgdata[6]==0x15 ) {\r
2537                                                                         String map;\r
2538                                                                         switch( msgdata[7] ) {\r
2539                                                                         case 0: map = " NormalPart"; break;\r
2540                                                                         case 1: map = " DrumMap1"; break;\r
2541                                                                         case 2: map = " DrumMap2"; break;\r
2542                                                                         default: map = String.format("0x%02X",msgdata[7]); break;\r
2543                                                                         }\r
2544                                                                         str += String.format(\r
2545                                                                                         " Rhythm Part: Ch=%d(0x%02X) Map=%s",\r
2546                                                                                         (ch+1), msgdata[5],\r
2547                                                                                         map\r
2548                                                                                         );\r
2549                                                                         return str;\r
2550                                                                 }\r
2551                                                         }\r
2552                                                         else if( (msgdata[5] & 0xF0) == 0x40 ) {\r
2553                                                                 int ch = (msgdata[5] & 0x0F);\r
2554                                                                 if( ch <= 9 ) ch--; else if( ch == 0 ) ch = 9;\r
2555                                                                 int dt = (msgdata[7] & 0xFF);\r
2556                                                                 if( msgdata[6]==0x20 ) {\r
2557                                                                         str += String.format(\r
2558                                                                                         " [88] EQ: Ch=%d(0x%02X) %s",\r
2559                                                                                         (ch+1), msgdata[5],\r
2560                                                                                         dt==0 ? "OFF" : dt==1 ? "ON" : String.format("0x%02X",dt)\r
2561                                                                                         );\r
2562                                                                 }\r
2563                                                                 else if( msgdata[6]==0x22 ) {\r
2564                                                                         str += String.format(\r
2565                                                                                         " [Pro] Part EFX Assign: Ch=%d(0x%02X) %s",\r
2566                                                                                         (ch+1), msgdata[5],\r
2567                                                                                         dt==0 ? "ByPass" : dt==1 ? "EFX" : String.format("0x%02X",dt)\r
2568                                                                                         );\r
2569                                                                 }\r
2570                                                         }\r
2571                                                         break;\r
2572                                                 } // [4]\r
2573                                         } // [3] [DT1]\r
2574                                         break; // [GS]\r
2575                                 case 0x45:\r
2576                                         str += " [GS-LCD]"; data_byte_pos++;\r
2577                                         if( msgdata[3]==0x12 ) {\r
2578                                                 str += " [DT1]"; data_byte_pos++;\r
2579                                                 if( msgdata[4]==0x10 && msgdata[5]==0x00 && msgdata[6]==0x00 ) {\r
2580                                                         data_byte_pos += 3;\r
2581                                                         str += " Disp [" +(new String(\r
2582                                                                         msgdata, data_byte_pos, msgdata.length - data_byte_pos - 2\r
2583                                                                         ))+ "]";\r
2584                                                 }\r
2585                                         } // [3] [DT1]\r
2586                                         break;\r
2587                                 case 0x14: str += " [D-50]"; data_byte_pos++; break;\r
2588                                 case 0x16: str += " [MT-32]"; data_byte_pos++; break;\r
2589                                 } // [2] model_id\r
2590                                 break;\r
2591                         case 0x43: // Yamaha (XG)\r
2592                                 data_byte_pos++;\r
2593                                 if( model_id == 0x4C ) {\r
2594                                         str += " [XG]";\r
2595                                         if( msgdata[3]==0 && msgdata[4]==0 && msgdata[5]==0x7E && msgdata[6]==0 ) {\r
2596                                                 str += " XG System ON"; return str;\r
2597                                         }\r
2598                                         data_byte_pos++;\r
2599                                 }\r
2600                                 break;\r
2601                         default:\r
2602                                 break;\r
2603                         }\r
2604                         int i;\r
2605                         str += " data=(";\r
2606                         for( i = data_byte_pos; i<msgdata.length-1; i++ ) {\r
2607                                 str += String.format( " %02X", msgdata[i] );\r
2608                         }\r
2609                         if( i < msgdata.length && (int)(msgdata[i] & 0xFF) != 0xF7 ) {\r
2610                                 str+=" [ Invalid EOX " + String.format( "%02X", msgdata[i] ) + " ]";\r
2611                         }\r
2612                         str += " )";\r
2613                         return str;\r
2614                 }\r
2615                 byte[] msg_data = msg.getMessage();\r
2616                 str += "(";\r
2617                 for( byte b : msg_data ) {\r
2618                         str += String.format( " %02X", b );\r
2619                 }\r
2620                 str += " )";\r
2621                 return str;\r
2622         }\r
2623 }\r
2624 \r
2625 ///////////////////////////////////////////////////////////////////////////\r
2626 //\r
2627 // MIDI シーケンスデータのインデックス\r
2628 //\r
2629 // 拍子、テンポ、調だけを抜き出したトラックを保持するためのインデックス。\r
2630 //\r
2631 // 指定の MIDI tick の位置におけるテンポ、調、拍子を取得したり、\r
2632 // 拍子情報から MIDI tick と小節位置との間の変換を行うために使います。\r
2633 //\r
2634 class SequenceIndex {\r
2635 \r
2636         private Track timesig_positions;\r
2637         private Track tempo_positions;\r
2638         private Track keysig_positions;\r
2639         private Sequence tmp_seq;\r
2640 \r
2641         public int       ticks_per_whole_note;\r
2642 \r
2643         public SequenceIndex( Sequence source_seq ) {\r
2644                 try {\r
2645                         int ppq = source_seq.getResolution();\r
2646                         ticks_per_whole_note = ppq * 4;\r
2647                         tmp_seq = new Sequence(Sequence.PPQ, ppq, 3);\r
2648                         Track[] tmp_tracks = tmp_seq.getTracks();\r
2649                         timesig_positions = tmp_tracks[0];\r
2650                         tempo_positions = tmp_tracks[1];\r
2651                         keysig_positions = tmp_tracks[2];\r
2652                         Track[] tracks = source_seq.getTracks();\r
2653                         for( Track tk : tracks ) {\r
2654                                 for( int i_evt = 0 ; i_evt < tk.size(); i_evt++ ) {\r
2655                                         MidiEvent evt = tk.get(i_evt);\r
2656                                         MidiMessage msg = evt.getMessage();\r
2657                                         if( ! (msg instanceof MetaMessage) ) continue;\r
2658                                         switch( ((MetaMessage)msg).getType() ) {\r
2659                                         case 0x51: tempo_positions.add(evt); break;\r
2660                                         case 0x58: timesig_positions.add(evt); break;\r
2661                                         case 0x59: keysig_positions.add(evt); break;\r
2662                                         default: break;\r
2663                                         }\r
2664                                 }\r
2665                         }\r
2666                 }\r
2667                 catch ( InvalidMidiDataException e ) {\r
2668                         e.printStackTrace();\r
2669                 }\r
2670         }\r
2671 \r
2672         private MetaMessage lastMessageAt( Track tk, long tick_position ) {\r
2673                 if( tk == null ) return null;\r
2674                 MidiEvent evt;\r
2675                 MetaMessage msg;\r
2676                 for( int i_evt = tk.size() - 1 ; i_evt >= 0; i_evt-- ) {\r
2677                         evt = tk.get(i_evt);\r
2678                         if( evt.getTick() > tick_position ) continue;\r
2679                         msg = (MetaMessage)( evt.getMessage() );\r
2680                         if( msg.getType() != 0x2F /* EOT */ ) return msg;\r
2681                 }\r
2682                 return null;\r
2683         }\r
2684         public MetaMessage lastTimeSignatureAt( long tick_position ) {\r
2685                 return lastMessageAt( timesig_positions, tick_position );\r
2686         }\r
2687         public MetaMessage lastKeySignatureAt( long tick_position ) {\r
2688                 return lastMessageAt( keysig_positions, tick_position );\r
2689         }\r
2690         public MetaMessage lastTempoAt( long tick_position ) {\r
2691                 return lastMessageAt( tempo_positions, tick_position );\r
2692         }\r
2693         public int getResolution() { return tmp_seq.getResolution(); }\r
2694 \r
2695         // MIDI tick を小節位置に変換\r
2696         public int last_measure;\r
2697         public int last_beat;\r
2698         public int last_extra_tick;\r
2699         public int ticks_per_beat;\r
2700         public byte timesig_upper;\r
2701         public byte timesig_lower_index;\r
2702         int tickToMeasure(long tick_position) {\r
2703                 byte extra_beats = 0;\r
2704                 MidiEvent evt = null;\r
2705                 MidiMessage msg = null;\r
2706                 byte[] data = null;\r
2707                 long current_tick = 0L;\r
2708                 long next_timesig_tick = 0L;\r
2709                 long prev_tick = 0L;\r
2710                 long duration = 0L;\r
2711                 last_measure = 0;\r
2712                 int measures, beats;\r
2713                 int i_evt = 0;\r
2714                 timesig_upper = 4;\r
2715                 timesig_lower_index = 2; // =log2(4)\r
2716                                 if( timesig_positions != null ) {\r
2717                                         do {\r
2718                                                 // Check current time-signature event\r
2719                                                 if( i_evt < timesig_positions.size() ) {\r
2720                                                         msg = (evt = timesig_positions.get(i_evt)).getMessage();\r
2721                                                         current_tick = next_timesig_tick = evt.getTick();\r
2722                                                         if(\r
2723                                                                         current_tick > tick_position || (\r
2724                                                                                         msg.getStatus() == 0xFF && ((MetaMessage)msg).getType() == 0x2F /* EOT */\r
2725                                                                                         )\r
2726                                                                         ) {\r
2727                                                                 current_tick = tick_position;\r
2728                                                         }\r
2729                                                 }\r
2730                                                 else { // No event\r
2731                                                         current_tick = next_timesig_tick = tick_position;\r
2732                                                 }\r
2733                                                 // Add measure from last event\r
2734                                                 //\r
2735                                                 ticks_per_beat = ticks_per_whole_note >> timesig_lower_index;\r
2736                         duration = current_tick - prev_tick;\r
2737                         beats = (int)( duration / ticks_per_beat );\r
2738                         last_extra_tick = (int)(duration % ticks_per_beat);\r
2739                         measures = beats / timesig_upper;\r
2740                         extra_beats = (byte)(beats % timesig_upper);\r
2741                         last_measure += measures;\r
2742                         if( next_timesig_tick > tick_position ) break;  // Not reached to next time signature\r
2743                         //\r
2744                         // Reached to the next time signature, so get it.\r
2745                         if( ( data = ((MetaMessage)msg).getData() ).length > 0 ) { // To skip EOT, check the data length.\r
2746                                 timesig_upper = data[0];\r
2747                                 timesig_lower_index = data[1];\r
2748                         }\r
2749                         if( current_tick == tick_position )  break;  // Calculation complete\r
2750                         //\r
2751                         // Calculation incomplete, so prepare for next\r
2752                         //\r
2753                         if( extra_beats > 0 ) {\r
2754                                 //\r
2755                                 // Extra beats are treated as 1 measure\r
2756                                 last_measure++;\r
2757                         }\r
2758                         prev_tick = current_tick;\r
2759                         i_evt++;\r
2760                                         } while( true );\r
2761                                 }\r
2762                                 last_beat = extra_beats;\r
2763                                 return last_measure;\r
2764         }\r
2765 \r
2766         // 小節位置を MIDI tick に変換\r
2767         public long measureToTick( int measure ) {\r
2768                 return measureToTick( measure, 0, 0 );\r
2769         }\r
2770         public long measureToTick( int measure, int beat, int extra_tick ) {\r
2771                 MidiEvent evt = null;\r
2772                 MidiMessage msg = null;\r
2773                 byte[] data = null;\r
2774                 long tick = 0L;\r
2775                 long prev_tick = 0L;\r
2776                 long duration = 0L;\r
2777                 long duration_sum = 0L;\r
2778                 long estimated_ticks;\r
2779                 int ticks_per_beat;\r
2780                 int i_evt = 0;\r
2781                 timesig_upper = 4;\r
2782                 timesig_lower_index = 2; // =log2(4)\r
2783                                 do {\r
2784                                         ticks_per_beat = ticks_per_whole_note >> timesig_lower_index;\r
2785                                         estimated_ticks = ((measure * timesig_upper) + beat) * ticks_per_beat + extra_tick;\r
2786                                         if( timesig_positions == null || i_evt > timesig_positions.size() ) {\r
2787                                                 return duration_sum + estimated_ticks;\r
2788                                         }\r
2789                                         msg = (evt = timesig_positions.get(i_evt)).getMessage();\r
2790                                         if( msg.getStatus() == 0xFF && ((MetaMessage)msg).getType() == 0x2F /* EOT */ ) {\r
2791                                                 return duration_sum + estimated_ticks;\r
2792                                         }\r
2793                                         duration = (tick = evt.getTick()) - prev_tick;\r
2794                                         if( duration >= estimated_ticks ) {\r
2795                                                 return duration_sum + estimated_ticks;\r
2796                                         }\r
2797                                         // Re-calculate measure (ignore extra beats/ticks)\r
2798                                         measure -= ( duration / (ticks_per_beat * timesig_upper) );\r
2799                                         duration_sum += duration;\r
2800                                         //\r
2801                                         // Get next time-signature\r
2802                                         data = ( (MetaMessage)msg ).getData();\r
2803                                         timesig_upper = data[0];\r
2804                                         timesig_lower_index = data[1];\r
2805                                         prev_tick = tick;\r
2806                                         i_evt++;\r
2807                                 } while( true );\r
2808         }\r
2809 }\r