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
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
80 * MIDI Editor/Playlist for MIDI Chord Helper
\r
83 * Copyright (C) 2006-2013 Akiyoshi Kamide
\r
84 * http://www.yk.rim.or.jp/~kamide/music/chordhelper/
\r
86 class MidiEditor extends JDialog implements DropTargetListener, ListSelectionListener {
\r
87 Insets zero_insets = new Insets(0,0,0,0);
\r
89 MidiDeviceManager deviceManager;
\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
97 MidiEvent copied_events[] = null;
\r
98 int copied_events_PPQ = 0;
\r
101 seq_selection_model,
\r
102 track_selection_model,
\r
103 event_selection_model;
\r
106 sequence_table_view,
\r
111 scrollable_sequence_table,
\r
112 scrollable_track_table_view,
\r
113 scrollable_event_table_view;
\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
131 JCheckBox pair_note_checkbox;
\r
133 JButton forward_button, backward_button;
\r
134 JToggleButton play_pause_button;
\r
137 sequence_split_pane, track_split_pane;
\r
139 VirtualMidiDevice midiDevice = new AbstractVirtualMidiDevice() {
\r
141 info = new MyInfo();
\r
142 setMaxReceivers(0);
\r
144 class MyInfo extends Info {
\r
145 protected MyInfo() {
\r
149 "MIDI sequence editor",
\r
156 MidiEventCellEditor event_cell_editor;
\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
166 TickPositionModel tick_position_model = new TickPositionModel();
\r
167 JToggleButton.ToggleButtonModel
\r
168 pair_note_on_off_model = new JToggleButton.ToggleButtonModel();
\r
170 JButton edit_event_button = new JButton();
\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
180 private void setSelectedEvent() {
\r
181 seq_model = seqListModel.getSequenceModel(seq_selection_model);
\r
182 eventDialog.midi_message_form.durationForm.setPPQ(
\r
183 seq_model.getSequence().getResolution()
\r
185 tick_position_model.setSequenceIndex(
\r
186 seq_model.getSequenceIndex()
\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
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.ok_button.setAction(jump_event_action);
\r
207 eventDialog.openTickForm();
\r
210 Action jump_event_action = new AbstractAction() {
\r
211 { putValue(NAME,"Jump"); }
\r
212 public void actionPerformed(ActionEvent e) {
\r
214 tick_position_model.getTickPosition()
\r
216 eventDialog.setVisible(false);
\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.ok_button.setAction(paste_event_action);
\r
227 eventDialog.openTickForm();
\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
237 scrollToEventAt( tick );
\r
238 seqListModel.fireSequenceChanged(seq_selection_model);
\r
239 eventDialog.setVisible(false);
\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.ok_button.setAction(add_event_action);
\r
251 int ch = midi_track_model.getChannel();
\r
253 eventDialog.midi_message_form.channelText.setSelectedChannel(ch);
\r
255 eventDialog.openEventForm();
\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.midi_message_form.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
267 if( ! midi_track_model.addMidiEvent(new_midi_event) ) {
\r
268 System.out.println("addMidiEvent failure");
\r
272 pair_note_on_off_model.isSelected() &&
\r
273 eventDialog.midi_message_form.isNote()
\r
275 ShortMessage sm = eventDialog.midi_message_form.getPartnerMessage();
\r
276 if( sm == null ) scrollToEventAt( tick );
\r
278 int duration = eventDialog.midi_message_form.durationForm.getDuration();
\r
279 if( eventDialog.midi_message_form.isNote(false) ) { // Note Off
\r
280 duration = -duration;
\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
289 scrollToEventAt( partner_tick > tick ? partner_tick : tick );
\r
292 seqListModel.fireSequenceChanged(seq_model);
\r
293 eventDialog.setVisible(false);
\r
294 fireEditingStopped();
\r
300 public MidiEventCellEditor() {
\r
301 edit_event_button.setHorizontalAlignment(JButton.LEFT);
\r
302 eventDialog.cancel_button.setAction(cancel_action);
\r
303 eventDialog.midi_message_form.setOutputMidiChannels(
\r
304 midiDevice.getChannels()
\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.midi_message_form.setMessage( sel_midi_evt.getMessage() );
\r
314 if( eventDialog.midi_message_form.isNote() ) {
\r
315 int partner_index = midi_track_model.getIndexOfPartnerFor(sel_index);
\r
316 if( partner_index < 0 ) {
\r
317 eventDialog.midi_message_form.durationForm.setDuration(0);
\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.midi_message_form.durationForm.setDuration((int)duration);
\r
327 MidiEvent events[];
\r
328 if( partner_event == null ) {
\r
329 events = new MidiEvent[1];
\r
330 events[0] = sel_midi_evt;
\r
333 events = new MidiEvent[2];
\r
334 events[0] = sel_midi_evt;
\r
335 events[1] = partner_event;
\r
337 midi_events_to_be_removed = events;
\r
338 eventDialog.setTitle("Change MIDI event");
\r
339 eventDialog.ok_button.setAction(add_event_action);
\r
340 eventDialog.openEventForm();
\r
344 pair_note_on_off_model.addItemListener(new ItemListener() {
\r
345 public void itemStateChanged(ItemEvent e) {
\r
346 eventDialog.midi_message_form.durationForm.setEnabled(
\r
347 pair_note_on_off_model.isSelected()
\r
351 pair_note_on_off_model.setSelected(true);
\r
355 public boolean isCellEditable(EventObject e) {
\r
357 return e instanceof MouseEvent && ((MouseEvent)e).getClickCount() == 2;
\r
359 public Object getCellEditorValue() {
\r
362 public Component getTableCellEditorComponent(
\r
363 JTable table, Object value, boolean isSelected,
\r
364 int row, int column
\r
366 edit_event_button.setText((String)value);
\r
367 return edit_event_button;
\r
371 public Action move_to_top_action = new AbstractAction() {
\r
373 putValue( SHORT_DESCRIPTION,
\r
374 "Move to top or previous song - 曲の先頭または前の曲へ戻る"
\r
376 putValue( LARGE_ICON_KEY, new ButtonIcon(ButtonIcon.TOP_ICON) );
\r
378 public void actionPerformed( ActionEvent event ) {
\r
379 if( deviceManager.getSequencer().getTickPosition() <= 40 )
\r
381 deviceManager.timeRangeModel.setValue(0);
\r
384 public Action move_to_bottom_action = new AbstractAction() {
\r
386 putValue( SHORT_DESCRIPTION, "Move to next song - 次の曲へ進む" );
\r
387 putValue( LARGE_ICON_KEY, new ButtonIcon(ButtonIcon.BOTTOM_ICON) );
\r
389 public void actionPerformed( ActionEvent event ) {
\r
390 if( loadNext(1) ) deviceManager.timeRangeModel.setValue(0);
\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
403 this, DnDConstants.ACTION_COPY_OR_MOVE, this, true
\r
405 total_time_label = new JLabel();
\r
407 // Buttons (Sequence)
\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
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
425 file_chooser == null ||
\r
426 file_chooser.showOpenDialog(MidiEditor.this) != JFileChooser.APPROVE_OPTION
\r
428 addSequenceFromMidiFile(file_chooser.getSelectedFile());
\r
433 play_pause_button = new JToggleButton(
\r
434 deviceManager.timeRangeModel.startStopAction
\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
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
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
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
465 if( file_chooser.showSaveDialog(MidiEditor.this) != JFileChooser.APPROVE_OPTION ) {
\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
475 FileOutputStream fos;
\r
477 fos = new FileOutputStream(midi_file);
\r
479 catch( FileNotFoundException ex ) {
\r
480 showError( midi_file.getName() + ": Cannot open to write" );
\r
481 ex.printStackTrace();
\r
485 fos.write(seq_model.getMIDIdata());
\r
487 seq_model.setModified(false);
\r
489 catch( IOException ex ) {
\r
490 showError( midi_file.getName() + ": I/O Error" );
\r
491 ex.printStackTrace();
\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
502 file_chooser != null &&
\r
503 seqListModel.getSequenceModel(seq_selection_model).isModified() &&
\r
505 "Selected MIDI sequence not saved - delete it ?\n" +
\r
506 "選択したMIDIシーケンスは保存されていませんが、削除しますか?"
\r
509 seqListModel.removeSequence(seq_selection_model);
\r
510 total_time_label.setText( seqListModel.getTotalLength() );
\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
531 seqListModel.fireSequenceChanged(seq_selection_model);
\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
541 "Do you want to delete selected track ?\n選択したトラックを削除しますか?"
\r
543 seqListModel.getSequenceModel(
\r
544 seq_selection_model
\r
545 ).deleteTracks( track_selection_model );
\r
546 seqListModel.fireSequenceChanged(seq_selection_model);
\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
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
569 "Do you want to delete selected event ?\n選択したMIDIイベントを削除しますか?"
\r
571 ((MidiTrackModel)event_table_view.getModel()).removeMidiEvents( event_selection_model );
\r
572 seqListModel.fireSequenceChanged(seq_selection_model);
\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
582 "Do you want to cut selected event ?\n選択したMIDIイベントを切り取りますか?"
\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
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
602 copied_events_PPQ = seqListModel.getSequenceModel(
\r
603 seq_selection_model
\r
604 ).getSequence().getResolution();
\r
605 updateButtonStatus();
\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
614 MidiSequenceModel empty_track_table_model = new MidiSequenceModel(
\r
615 seqListModel = new SequenceListModel(deviceManager)
\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
621 seqListModel.sizeColumnWidthToFit( sequence_table_view );
\r
623 TableColumnModel track_column_model = track_table_view.getColumnModel();
\r
624 empty_track_table_model.sizeColumnWidthToFit(track_column_model);
\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
637 JScrollPane scrollable_sequence_table = new JScrollPane(sequence_table_view);
\r
639 track_selection_model = track_table_view.getSelectionModel();
\r
640 track_selection_model.setSelectionMode(
\r
641 ListSelectionModel.MULTIPLE_INTERVAL_SELECTION
\r
643 track_selection_model.addListSelectionListener(this);
\r
644 JScrollPane scrollable_track_table_view
\r
645 = new JScrollPane(track_table_view);
\r
647 event_selection_model = event_table_view.getSelectionModel();
\r
648 event_selection_model.setSelectionMode(
\r
649 ListSelectionModel.MULTIPLE_INTERVAL_SELECTION
\r
651 event_selection_model.addListSelectionListener(this);
\r
652 scrollable_event_table_view
\r
653 = new JScrollPane(event_table_view);
\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
667 base64_dialog.setVisible(true);
\r
672 new_seq_dialog = new NewSequenceDialog(this);
\r
673 new_seq_dialog.setChannels( midiDevice.getChannels() );
\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
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
701 JPanel playlist_panel = new JPanel();
\r
702 playlist_panel.setLayout(
\r
703 new BoxLayout( playlist_panel, BoxLayout.Y_AXIS )
\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
710 sequenceSelectionChanged();
\r
711 total_time_label.setText( seqListModel.getTotalLength() );
\r
714 file_chooser = new JFileChooser();
\r
715 FileNameExtensionFilter filter = new FileNameExtensionFilter(
\r
716 "MIDI sequence (*.mid)", "mid"
\r
718 file_chooser.setFileFilter(filter);
\r
720 catch( ExceptionInInitializerError e ) {
\r
721 file_chooser = null;
\r
723 catch( NoClassDefFoundError e ) {
\r
724 file_chooser = null;
\r
726 catch( AccessControlException e ) {
\r
727 file_chooser = null;
\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
735 // Lists and input panel
\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
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
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
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
763 track_split_pane = new JSplitPane(
\r
764 JSplitPane.HORIZONTAL_SPLIT,
\r
765 track_list_panel, event_list_panel
\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
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
778 seq_selection_model.setSelectionInterval(0,0);
\r
779 updateButtonStatus();
\r
781 public void dragEnter(DropTargetDragEvent event) {
\r
782 if( event.isDataFlavorSupported(DataFlavor.javaFileListFlavor) )
\r
783 event.acceptDrag(DnDConstants.ACTION_COPY_OR_MOVE);
\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
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
800 event.dropComplete(false);
\r
802 catch (Exception ex) {
\r
803 ex.printStackTrace();
\r
804 event.dropComplete(false);
\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
813 seqListModel.getSequenceModel(seq_selection_model) == null
\r
815 track_selection_model.isSelectionEmpty()
\r
817 midi_events_label.setText("MIDI Events (No track selected)");
\r
818 event_table_view.setModel(new MidiTrackModel());
\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
829 midi_events_label.setText(
\r
830 String.format("MIDI Events (in track No.%d)", sel_index)
\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
838 updateButtonStatus();
\r
839 event_selection_model.setSelectionInterval(0,0);
\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
863 if( pair_note_checkbox.isSelected() ) {
\r
864 int max_index = event_selection_model.getMaxSelectionIndex();
\r
866 for( int i=min_index; i<=max_index; i++ ) {
\r
868 event_selection_model.isSelectedIndex(i)
\r
870 (partner_index = track_model.getIndexOfPartnerFor(i)) >= 0
\r
872 ! event_selection_model.isSelectedIndex(partner_index)
\r
874 event_selection_model.addSelectionInterval(
\r
875 partner_index, partner_index
\r
881 updateButtonStatus();
\r
884 private void showError( String message ) {
\r
885 JOptionPane.showMessageDialog(
\r
887 ChordHelperApplet.VersionInfo.NAME,
\r
888 JOptionPane.ERROR_MESSAGE
\r
891 private void showWarning( String message ) {
\r
892 JOptionPane.showMessageDialog(
\r
894 ChordHelperApplet.VersionInfo.NAME,
\r
895 JOptionPane.WARNING_MESSAGE
\r
898 private boolean confirm( String message ) {
\r
899 return JOptionPane.showConfirmDialog(
\r
901 ChordHelperApplet.VersionInfo.NAME,
\r
902 JOptionPane.YES_NO_OPTION,
\r
903 JOptionPane.WARNING_MESSAGE
\r
904 ) == JOptionPane.YES_OPTION ;
\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
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
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
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
932 // event_cell_editor.setSequenceModel(seq_model);
\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
939 updateButtonStatus();
\r
941 public void updateButtonStatus() {
\r
942 boolean is_track_selected = (
\r
943 ! track_selection_model.isSelectionEmpty()
\r
945 seqListModel.getSequenceModel(seq_selection_model) != null
\r
947 seqListModel.getSequenceModel(seq_selection_model).getRowCount() > 0
\r
951 remove_track_button.setEnabled( is_track_selected );
\r
953 TableModel tm = event_table_view.getModel();
\r
954 if( ! (tm instanceof MidiTrackModel) ) return;
\r
956 MidiTrackModel track_model = (MidiTrackModel)tm;
\r
957 jump_sequence_button.setEnabled(
\r
958 track_model != null && track_model.getRowCount() > 0
\r
961 boolean is_event_selected = (
\r
963 event_selection_model.isSelectionEmpty() ||
\r
964 track_model == null || track_model.getRowCount() == 0
\r
965 ) && is_track_selected
\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
973 add_event_button.setEnabled(
\r
974 track_model != null && is_track_selected
\r
976 paste_event_button.setEnabled(
\r
977 track_model != null && is_track_selected &&
\r
978 copied_events != null && copied_events.length > 0
\r
981 public String getMIDIdataBase64() {
\r
982 base64_dialog.setMIDIData(
\r
983 deviceManager.timeRangeModel.getSequenceModel().getMIDIdata()
\r
985 return base64_dialog.getBase64Data();
\r
987 public int addSequence() {
\r
988 return addSequence(new_seq_dialog.getMidiSequence());
\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
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
1002 public int addSequenceFromBase64Text() {
\r
1003 return addSequenceFromMidiData( base64_dialog.getMIDIData(), null );
\r
1005 public int addSequenceFromMidiData(byte[] data, String filename) {
\r
1008 last_index = seqListModel.addSequence(data,filename);
\r
1009 } catch( InvalidMidiDataException e ) {
\r
1010 showWarning("MIDI data invalid");
\r
1013 total_time_label.setText( seqListModel.getTotalLength() );
\r
1014 return last_index;
\r
1016 public int addSequenceFromMidiFile(File midi_file) {
\r
1019 last_index = seqListModel.addSequence(midi_file);
\r
1020 } catch( FileNotFoundException e ) {
\r
1021 showWarning( midi_file.getName() + " : not found" );
\r
1023 } catch( InvalidMidiDataException e ) {
\r
1024 showWarning( midi_file.getName() + " : MIDI data invalid" );
\r
1026 } catch( AccessControlException e ) {
\r
1027 showError( midi_file.getName() + ": Cannot access" );
\r
1028 e.printStackTrace();
\r
1031 total_time_label.setText( seqListModel.getTotalLength() );
\r
1032 return last_index;
\r
1034 public int addSequenceFromURL(String midi_file_url) {
\r
1037 last_index = seqListModel.addSequence(midi_file_url);
\r
1038 } catch( InvalidMidiDataException e ) {
\r
1039 showWarning( midi_file_url + " : MIDI data invalid" );
\r
1041 } catch( AccessControlException e ) {
\r
1042 showError( midi_file_url + ": Cannot access" );
\r
1043 e.printStackTrace();
\r
1046 total_time_label.setText( seqListModel.getTotalLength() );
\r
1047 return last_index;
\r
1049 public void load(int index) {
\r
1050 seqListModel.loadToSequencer(index);
\r
1051 sequenceSelectionChanged();
\r
1053 public boolean loadNext(int offset) {
\r
1054 boolean retval = seqListModel.loadNext(offset);
\r
1055 sequenceSelectionChanged();
\r
1058 public void loadAndPlay(int index) {
\r
1060 deviceManager.timeRangeModel.start();
\r
1062 public void loadAndPlay() {
\r
1063 loadAndPlay( seq_selection_model.getMinSelectionIndex() );
\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
1072 if( deviceManager.getSequencer().isRunning() ) {
\r
1075 else if( nextIndex >= 0 ) {
\r
1076 loadAndPlay(nextIndex);
\r
1079 public boolean isModified() {
\r
1080 return seqListModel.isModified();
\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
1087 public void scrollToEventAt( long tick ) {
\r
1088 MidiTrackModel track_model = (MidiTrackModel)event_table_view.getModel();
\r
1089 scrollToEventAt( track_model.tickToIndex(tick) );
\r
1091 public void scrollToEventAt( int index ) {
\r
1092 scrollable_event_table_view.getVerticalScrollBar().setValue(
\r
1093 index * event_table_view.getRowHeight()
\r
1095 event_selection_model.setSelectionInterval( index, index );
\r
1099 /////////////////////////////////////////////////////////////
\r
1103 class SequenceListModel extends AbstractTableModel
\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
1125 static int column_width_ratios[] = {
\r
1126 2, 6, 6, 6, 6, 6, 6, 16, 40,
\r
1129 private ArrayList<MidiSequenceModel>
\r
1130 sequences = new ArrayList<MidiSequenceModel>();
\r
1132 MidiDeviceManager device_manager;
\r
1133 int second_position = 0;
\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
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
1155 public Class<?> getColumnClass(int 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
1164 public Object getValueAt(int row, int 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
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
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
1191 case COLUMN_FILENAME: {
\r
1192 String filename = sequences.get(row).getFilename();
\r
1193 return filename == null ? "" : filename;
\r
1195 case COLUMN_SEQ_NAME: {
\r
1196 String seq_name = sequences.get(row).toString();
\r
1197 return seq_name == null ? "" : seq_name;
\r
1199 default: return "";
\r
1202 public boolean isCellEditable( int row, int column ) {
\r
1203 return column == COLUMN_FILENAME || column == COLUMN_SEQ_NAME ;
\r
1205 public void setValueAt(Object val, int row, int column) {
\r
1207 case COLUMN_FILENAME:
\r
1209 String filename = (String)val;
\r
1210 sequences.get(row).setFilename(filename);
\r
1211 fireTableCellUpdated(row, COLUMN_FILENAME);
\r
1213 case COLUMN_SEQ_NAME:
\r
1215 if( sequences.get(row).setName((String)val) )
\r
1216 fireTableCellUpdated(row, COLUMN_MODIFIED);
\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
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
1235 public boolean isModified() {
\r
1236 for( MidiSequenceModel seq_model : sequences ) {
\r
1237 if( seq_model.isModified() ) return true;
\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
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
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
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
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
1279 fireTableRowsUpdated( min_index, max_index );
\r
1281 public int addSequence() {
\r
1282 Sequence seq = (new Music.ChordProgression()).toMidiSequence();
\r
1283 return seq == null ? -1 : addSequence(seq,null);
\r
1285 public int addSequence( Sequence seq ) {
\r
1286 return addSequence( seq, "" );
\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
1297 public int addSequence( byte[] midiData, String filename )
\r
1298 throws InvalidMidiDataException
\r
1300 return ( midiData == null ) ?
\r
1302 addSequence( new ByteArrayInputStream(midiData), filename ) ;
\r
1304 public int addSequence( File midi_file )
\r
1305 throws InvalidMidiDataException, FileNotFoundException
\r
1307 FileInputStream fis = new FileInputStream(midi_file);
\r
1308 int retval = addSequence( fis, midi_file.getName() );
\r
1311 } catch( IOException ex ) {
\r
1312 ex.printStackTrace();
\r
1316 public int addSequence( InputStream in, String filename )
\r
1317 throws InvalidMidiDataException
\r
1319 if( in == null ) return addSequence();
\r
1322 seq = MidiSystem.getSequence(in);
\r
1323 } catch ( InvalidMidiDataException e ) {
\r
1325 } catch ( EOFException e ) {
\r
1328 } catch ( IOException e ) {
\r
1329 e.printStackTrace();
\r
1332 return addSequence( seq, filename );
\r
1334 public int addSequence( String midi_file_url )
\r
1335 throws InvalidMidiDataException, AccessControlException
\r
1337 URL url = toURL( midi_file_url );
\r
1338 if( url == null ) {
\r
1343 seq = MidiSystem.getSequence(url);
\r
1344 } catch ( InvalidMidiDataException e ) {
\r
1346 } catch( EOFException e ) {
\r
1349 } catch( IOException e ) {
\r
1350 e.printStackTrace();
\r
1352 } catch( AccessControlException e ) {
\r
1355 return addSequence( seq, url.getFile().replaceFirst("^.*/","") );
\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
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
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
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
1387 public int getTotalSeconds() {
\r
1388 int total_sec = 0;
\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
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
1401 // 文字列を URL オブジェクトに変換
\r
1403 public URL toURL( String url_string ) {
\r
1404 if( url_string == null || url_string.isEmpty() ) {
\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
1421 //////////////////////////////////////////////////////////
\r
1423 // Track List (MIDI Sequence) Model
\r
1425 //////////////////////////////////////////////////////////
\r
1426 class MidiSequenceModel extends AbstractTableModel
\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
1438 public static final int column_width_ratios[] = {
\r
1439 30, 60, 40, 40, 60, 40, 200
\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
1449 class RecordChannelCellEditor extends DefaultCellEditor {
\r
1450 public RecordChannelCellEditor() {
\r
1451 super(new JComboBox<String>() {
\r
1454 for( int i=1; i <= MIDISpec.MAX_CHANNELS; i++ )
\r
1455 addItem( String.format( "%d", i ) );
\r
1462 public MidiSequenceModel( SequenceListModel slm ) {
\r
1463 seq_list_model = slm;
\r
1466 // TableModel interface
\r
1468 public int getRowCount() {
\r
1469 return seq == null ? 0 : seq.getTracks().length;
\r
1471 public int getColumnCount() {
\r
1472 return column_titles.length;
\r
1474 public String getColumnName(int column) {
\r
1475 return column_titles[column];
\r
1477 public Class<?> getColumnClass(int column) {
\r
1479 case COLUMN_TRACK_NUMBER:
\r
1480 case COLUMN_EVENTS:
\r
1481 return Integer.class;
\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
1494 public Object getValueAt(int row, int column) {
\r
1496 case COLUMN_TRACK_NUMBER: return row;
\r
1497 case COLUMN_EVENTS:
\r
1498 return seq.getTracks()[row].size();
\r
1500 return (seq == getSequencer().getSequence()) ?
\r
1501 getSequencer().getTrackMute(row) : "";
\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
1512 case COLUMN_TRACK_NAME:
\r
1513 return track_models.get(row).toString();
\r
1514 default: return "";
\r
1517 public boolean isCellEditable( int row, int column ) {
\r
1521 case COLUMN_RECORD_CHANNEL:
\r
1522 return seq == getSequencer().getSequence();
\r
1523 case COLUMN_CHANNEL:
\r
1524 case COLUMN_TRACK_NAME:
\r
1530 public void setValueAt(Object val, int row, int column) {
\r
1533 getSequencer().setTrackMute( row, ((Boolean)val).booleanValue() );
\r
1536 getSequencer().setTrackSolo( row, ((Boolean)val).booleanValue() );
\r
1538 case COLUMN_RECORD_CHANNEL:
\r
1539 track_models.get(row).setRecordingChannel((String)val);
\r
1541 case COLUMN_CHANNEL: {
\r
1544 ch = new Integer((String)val);
\r
1546 catch( NumberFormatException e ) {
\r
1550 if( --ch <= 0 || ch > MIDISpec.MAX_CHANNELS )
\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
1560 case COLUMN_TRACK_NAME:
\r
1561 track_models.get(row).setString((String)val);
\r
1564 fireTableCellUpdated(row,column);
\r
1566 // Methods (Table view)
\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
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
1580 // Methods (sequence)
\r
1582 public Sequence getSequence() { return this.seq; }
\r
1583 public void setSequence( Sequence seq ) {
\r
1585 getSequencer().recordDisable(null); // The "null" means all tracks
\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
1593 if( seq == null ) {
\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
1604 public SequenceIndex getSequenceIndex() {
\r
1605 return this.seq_index;
\r
1607 public void setModified(boolean is_modified) {
\r
1608 this.is_modified = is_modified;
\r
1610 public boolean isModified() { return is_modified; }
\r
1611 public void setFilename(String filename) {
\r
1612 this.filename = filename;
\r
1614 public String getFilename() { return filename; }
\r
1615 public String toString() {
\r
1616 return MIDISpec.getNameOf(seq);
\r
1618 public boolean setName( String name ) {
\r
1619 if( name.equals(toString()) )
\r
1621 if( ! MIDISpec.setNameOf(seq,name) )
\r
1623 setModified(true);
\r
1624 fireTableDataChanged();
\r
1627 public byte[] getMIDIdata() {
\r
1628 if( seq == null || seq.getTracks().length == 0 ) {
\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
1636 ByteArrayOutputStream out = new ByteArrayOutputStream();
\r
1638 MidiSystem.write(seq, 1, out);
\r
1639 return out.toByteArray();
\r
1640 } catch ( IOException e ) {
\r
1641 e.printStackTrace();
\r
1645 public void fireTimeSignatureChanged() {
\r
1646 seq_index = new SequenceIndex( seq );
\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
1654 public void fireSequenceChanged() {
\r
1655 seq_list_model.fireSequenceChanged(this);
\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
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
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
1679 public void deleteTracks( ListSelectionModel selection_model ) {
\r
1680 if( selection_model.isSelectionEmpty() )
\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
1688 seq.deleteTrack( tklist[i] );
\r
1689 track_models.remove(i);
\r
1691 fireTableRowsDeleted( min_sel_index, max_sel_index );
\r
1694 // Methods (sequencer)
\r
1696 public Sequencer getSequencer() {
\r
1697 return seq_list_model.device_manager.getSequencer();
\r
1699 public boolean isRecordable() {
\r
1700 if( seq != getSequencer().getSequence() ) return false;
\r
1701 int num_row = getRowCount();
\r
1703 for( int row=0; row<num_row; row++ ) {
\r
1704 s = (String)getValueAt(
\r
1705 row, COLUMN_RECORD_CHANNEL
\r
1707 if( s.equals("OFF") ) continue;
\r
1714 ////////////////////////////////////////////////////////
\r
1716 // Event List (Track) Model
\r
1718 ////////////////////////////////////////////////////////
\r
1719 class MidiTrackModel extends AbstractTableModel
\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
1730 public static final int column_width_ratios[] = {
\r
1731 30, 40, 20,20,20, 280,
\r
1733 private Track track;
\r
1734 private MidiSequenceModel parent_model;
\r
1738 public MidiTrackModel() { } // To create empty model
\r
1739 public MidiTrackModel( MidiSequenceModel parent_model ) {
\r
1740 this.parent_model = parent_model;
\r
1742 public MidiTrackModel( Track tk, MidiSequenceModel parent_model ) {
\r
1744 this.parent_model = parent_model;
\r
1746 // TableModel interface
\r
1748 public int getRowCount() {
\r
1749 return track == null ? 0 : track.size();
\r
1751 public int getColumnCount() {
\r
1752 return column_titles.length;
\r
1754 public String getColumnName(int column) {
\r
1755 return column_titles[column];
\r
1757 public Class<?> getColumnClass(int 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
1769 return String.class;
\r
1771 // return getValueAt(0,column).getClass();
\r
1773 public Object getValueAt(int row, int column) {
\r
1775 case COLUMN_EVENT_NUMBER:
\r
1778 case COLUMN_TICK_POSITION:
\r
1779 return track.get(row).getTick();
\r
1781 case COLUMN_MEASURE_POSITION:
\r
1782 return parent_model.getSequenceIndex().tickToMeasure(
\r
1783 track.get(row).getTick()
\r
1786 case COLUMN_BEAT_POSITION:
\r
1787 parent_model.getSequenceIndex().tickToMeasure(
\r
1788 track.get(row).getTick()
\r
1790 return parent_model.getSequenceIndex().last_beat + 1;
\r
1792 case COLUMN_EXTRA_TICK_POSITION:
\r
1793 parent_model.getSequenceIndex().tickToMeasure(
\r
1794 track.get(row).getTick()
\r
1796 return parent_model.getSequenceIndex().last_extra_tick;
\r
1798 case COLUMN_MESSAGE:
\r
1799 return msgToString(
\r
1800 track.get(row).getMessage()
\r
1802 default: return "";
\r
1805 public boolean isCellEditable(int row, int 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
1814 default: return false;
\r
1817 public void setValueAt(Object value, int row, int column) {
\r
1820 // case COLUMN_EVENT_NUMBER:
\r
1821 case COLUMN_TICK_POSITION:
\r
1822 tick = (Long)value;
\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
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
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
1845 case COLUMN_MESSAGE:
\r
1849 changeEventTick(row,tick);
\r
1852 // Methods (Table view)
\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
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
1868 private boolean isRhythmPart(int ch) {
\r
1872 public Track getTrack() { return track; }
\r
1875 public String toString() {
\r
1876 return MIDISpec.getNameOf(track);
\r
1879 public boolean setString( String name ) {
\r
1881 name.equals(toString())
\r
1883 ! MIDISpec.setNameOf( track, name )
\r
1886 parent_model.setModified(true);
\r
1887 parent_model.fireSequenceChanged();
\r
1888 fireTableDataChanged();
\r
1892 // 録音中の MIDI チャンネル
\r
1893 private String rec_ch = "OFF";
\r
1894 public String getRecordingChannel() {
\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
1902 else if( ch_str.equals("ALL") ) {
\r
1903 sequencer.recordEnable( track, -1 );
\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
1919 public int getChannel() {
\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
1927 smsg = (ShortMessage)msg;
\r
1928 if( ! MIDISpec.isChannelMessage(smsg) )
\r
1930 ch = smsg.getChannel();
\r
1931 if( prev_ch >= 0 && prev_ch != ch ) {
\r
1932 // MIDIチャンネルが統一されていない場合
\r
1937 // すべてのMIDIチャンネルが同じならそれを返す
\r
1940 public void setChannel(int ch) {
\r
1941 // すべてのチャンネルメッセージに対して
\r
1942 // 同一のMIDIチャンネルをセットする
\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
1949 ! (msg instanceof ShortMessage)
\r
1951 ! MIDISpec.isChannelMessage(smsg = (ShortMessage)msg)
\r
1953 smsg.getChannel() == ch
\r
1958 smsg.getCommand(), ch,
\r
1959 smsg.getData1(), smsg.getData2()
\r
1962 catch( InvalidMidiDataException e ) {
\r
1963 e.printStackTrace();
\r
1965 parent_model.setModified(true);
\r
1967 parent_model.fireTrackChanged( track );
\r
1968 parent_model.fireSequenceChanged();
\r
1969 fireTableDataChanged();
\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
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
1984 if( MIDISpec.isEOT(msg) ) {
\r
1985 // EOTの場所が変わると曲の長さが変わるので、親モデルへ通知する。
\r
1986 parent_model.fireSequenceChanged();
\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
2003 else if( tick < current_tick ) {
\r
2004 max_index = current_index - 1;
\r
2007 return current_index;
\r
2010 return (min_index + max_index) / 2;
\r
2012 // NoteOn/NoteOff ペアの一方のインデックスから、
\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
2021 int ch = sm.getChannel();
\r
2022 int note = sm.getData1();
\r
2023 MidiMessage partner_msg;
\r
2024 ShortMessage partner_sm;
\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
2042 if( ch != partner_sm.getChannel() || note != partner_sm.getData1() ) {
\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
2062 if( ch != partner_sm.getChannel() || note != partner_sm.getData1() ) {
\r
2074 public boolean isTimeSignature( MidiMessage msg ) {
\r
2075 // 拍子記号のとき True を返す
\r
2077 (msg instanceof MetaMessage)
\r
2079 ((MetaMessage)msg).getType() == 0x58;
\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
2088 public boolean hasTrack() { return track != null; }
\r
2092 public MidiEvent getMidiEvent( int index ) {
\r
2093 return track.get(index);
\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
2104 return events.toArray(new MidiEvent[1]);
\r
2109 public boolean addMidiEvent( MidiEvent midi_event ) {
\r
2110 if( !(track.add(midi_event)) )
\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
2119 public boolean addMidiEvents(
\r
2120 MidiEvent midi_events[],
\r
2121 long destination_tick,
\r
2122 int midi_events_ppq
\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
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
2140 new_midi_event = new MidiEvent(
\r
2142 destination_tick + (event_tick - first_event_tick) * dest_ppq / midi_events_ppq
\r
2145 if( ! track.add(new_midi_event) ) continue;
\r
2147 if( isTimeSignature(msg) ) has_time_signature = true;
\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
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
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
2177 public void removeMidiEvents( ListSelectionModel sel_model ) {
\r
2178 removeMidiEvents( getMidiEvents(sel_model) );
\r
2183 public String msgToString(MidiMessage msg) {
\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
2197 int cmd = shortmsg.getCommand();
\r
2199 case ShortMessage.NOTE_OFF:
\r
2200 case ShortMessage.NOTE_ON:
\r
2201 str += ch_prefix + status_prefix + data1;
\r
2203 if( isRhythmPart(ch) ) {
\r
2204 str += MIDISpec.getPercussionName(data1);
\r
2207 str += Music.NoteSymbol.noteNoToSymbol(data1);
\r
2209 str +="] Velocity=" + data2;
\r
2211 case ShortMessage.POLY_PRESSURE:
\r
2212 str += ch_prefix + status_prefix + "Note=" + data1 + " Pressure=" + data2;
\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
2218 case ShortMessage.CHANNEL_PRESSURE:
\r
2219 str += ch_prefix + status_prefix + data1;
\r
2220 if( data2 != 0 ) str += " data2=" + data2;
\r
2222 case ShortMessage.PITCH_BEND:
\r
2225 (data1 & 0x7F) | ( (data2 & 0x7F) << 7 )
\r
2227 str += ch_prefix + status_prefix + ( (val-8192) * 100 / 8191) + "% (" + val + ")";
\r
2230 case ShortMessage.CONTROL_CHANGE:
\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
2241 // Controller's value
\r
2243 case 0x40: case 0x41: case 0x42: case 0x43: case 0x45:
\r
2244 str += " " + ( data2==0x3F?"OFF":data2==0x40?"ON":data2 );
\r
2246 case 0x44: // Legato Footswitch
\r
2247 str += " " + ( data2==0x3F?"Normal":data2==0x40?"Legato":data2 );
\r
2249 case 0x7A: // Local Control
\r
2250 str += " " + ( data2==0x00?"OFF":data2==0x7F?"ON":data2 );
\r
2253 str += " " + data2;
\r
2260 // Never reached here
\r
2264 else { // System Message
\r
2265 str += (status_name == null ? ("status="+status) : status_name );
\r
2266 str += " (" + data1 + "," + data2 + ")";
\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
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
2282 // Add the message type name
\r
2285 // Add the text data
\r
2286 if( MIDISpec.hasMetaText(msgtype) ) {
\r
2287 str +=" ["+(new String(msgdata))+"]";
\r
2290 // Add the numeric data
\r
2292 case 0x00: // Sequence Number (for MIDI Format 2)
\r
2293 if( msgdata.length == 2 ) {
\r
2294 str += String.format(
\r
2296 ((msgdata[0] & 0xFF) << 8) | (msgdata[1] & 0xFF)
\r
2300 str += ": Size not 2 byte : data=(";
\r
2301 for( byte b : msgdata ) str += String.format( " %02X", b );
\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
2310 str += ": Size not 1 byte : data=(";
\r
2311 for( byte b : msgdata ) str += String.format( " %02X", b );
\r
2314 case 0x51: // Tempo
\r
2315 str += ": " + MIDISpec.byteArrayToQpmTempo( msgdata ) + "[QPM] (";
\r
2316 for( byte b : msgdata ) str += String.format( " %02X", b );
\r
2319 case 0x54: // SMPTE Offset
\r
2320 if( msgdata.length == 5 ) {
\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
2329 str += ": Size not 5 byte : data=(";
\r
2330 for( byte b : msgdata ) str += String.format( " %02X", b );
\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
2339 str += ": Size not 4 byte : data=(";
\r
2340 for( byte b : msgdata ) str += String.format( " %02X", b );
\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
2350 str += ": Size not 2 byte : data=(";
\r
2351 for( byte b : msgdata ) str += String.format( " %02X", b );
\r
2354 case 0x7F: // Sequencer Specific Meta Event
\r
2356 for( byte b : msgdata ) str += String.format( " %02X", b );
\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
2371 case SysexMessage.SPECIAL_SYSTEM_EXCLUSIVE:
\r
2372 str += "SysEx(Special): ";
\r
2375 str += "SysEx: Invalid (status="+status+") ";
\r
2378 if( msgdata.length < 1 ) {
\r
2379 str += " Invalid data size: " + msgdata.length;
\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
2390 str += manufacturer_name + String.format( " (DevID=0x%02X)", device_id );
\r
2391 switch( manufacturer_id ) {
\r
2392 case 0x7E: // Non-Realtime Universal
\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
2407 // case 0x7F: // Realtime Universal
\r
2408 case 0x41: // Roland
\r
2410 switch( model_id ) {
\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
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
2422 else if( msgdata[7]==0x01 ) {
\r
2423 str += " [88] System Mode Set (Mode 2: Double Module)"; return str;
\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
2432 port==0?"A":port==1?"B":String.format("0x%02X",port)
\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
2445 switch( msgdata[7] ) {
\r
2446 case 0x00: str += " GS Reset"; return str;
\r
2447 case 0x7F: str += " Exit GS Mode"; return str;
\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
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
2494 else if( msgdata[5]==0x03 ) {
\r
2495 if( msgdata[6] == 0x00 ) {
\r
2496 str += " [Pro] EFX Type: "; data_byte_pos += 3;
\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
2502 else if( msgdata[6] == 0x17 ) {
\r
2503 str += " [Pro] EFX Send Level To Reverb: "; data_byte_pos += 3;
\r
2505 else if( msgdata[6] == 0x18 ) {
\r
2506 str += " [Pro] EFX Send Level To Chorus: "; data_byte_pos += 3;
\r
2508 else if( msgdata[6] == 0x19 ) {
\r
2509 str += " [Pro] EFX Send Level To Delay: "; data_byte_pos += 3;
\r
2511 else if( msgdata[6] == 0x1B ) {
\r
2512 str += " [Pro] EFX Ctrl Src1: "; data_byte_pos += 3;
\r
2514 else if( msgdata[6] == 0x1C ) {
\r
2515 str += " [Pro] EFX Ctrl Depth1: "; data_byte_pos += 3;
\r
2517 else if( msgdata[6] == 0x1D ) {
\r
2518 str += " [Pro] EFX Ctrl Src2: "; data_byte_pos += 3;
\r
2520 else if( msgdata[6] == 0x1E ) {
\r
2521 str += " [Pro] EFX Ctrl Depth2: "; data_byte_pos += 3;
\r
2523 else if( msgdata[6] == 0x1F ) {
\r
2524 str += " [Pro] EFX Send EQ Switch: "; data_byte_pos += 3;
\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
2536 else if( msgdata[6]==0x15 ) {
\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
2544 str += String.format(
\r
2545 " Rhythm Part: Ch=%d(0x%02X) Map=%s",
\r
2546 (ch+1), msgdata[5],
\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
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
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
2587 case 0x14: str += " [D-50]"; data_byte_pos++; break;
\r
2588 case 0x16: str += " [MT-32]"; data_byte_pos++; break;
\r
2591 case 0x43: // Yamaha (XG)
\r
2593 if( model_id == 0x4C ) {
\r
2595 if( msgdata[3]==0 && msgdata[4]==0 && msgdata[5]==0x7E && msgdata[6]==0 ) {
\r
2596 str += " XG System ON"; return str;
\r
2606 for( i = data_byte_pos; i<msgdata.length-1; i++ ) {
\r
2607 str += String.format( " %02X", msgdata[i] );
\r
2609 if( i < msgdata.length && (int)(msgdata[i] & 0xFF) != 0xF7 ) {
\r
2610 str+=" [ Invalid EOX " + String.format( "%02X", msgdata[i] ) + " ]";
\r
2615 byte[] msg_data = msg.getMessage();
\r
2617 for( byte b : msg_data ) {
\r
2618 str += String.format( " %02X", b );
\r
2625 ///////////////////////////////////////////////////////////////////////////
\r
2627 // MIDI シーケンスデータのインデックス
\r
2629 // 拍子、テンポ、調だけを抜き出したトラックを保持するためのインデックス。
\r
2631 // 指定の MIDI tick の位置におけるテンポ、調、拍子を取得したり、
\r
2632 // 拍子情報から MIDI tick と小節位置との間の変換を行うために使います。
\r
2634 class SequenceIndex {
\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
2641 public int ticks_per_whole_note;
\r
2643 public SequenceIndex( Sequence source_seq ) {
\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
2667 catch ( InvalidMidiDataException e ) {
\r
2668 e.printStackTrace();
\r
2672 private MetaMessage lastMessageAt( Track tk, long tick_position ) {
\r
2673 if( tk == null ) return null;
\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
2684 public MetaMessage lastTimeSignatureAt( long tick_position ) {
\r
2685 return lastMessageAt( timesig_positions, tick_position );
\r
2687 public MetaMessage lastKeySignatureAt( long tick_position ) {
\r
2688 return lastMessageAt( keysig_positions, tick_position );
\r
2690 public MetaMessage lastTempoAt( long tick_position ) {
\r
2691 return lastMessageAt( tempo_positions, tick_position );
\r
2693 public int getResolution() { return tmp_seq.getResolution(); }
\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
2712 int measures, beats;
\r
2714 timesig_upper = 4;
\r
2715 timesig_lower_index = 2; // =log2(4)
\r
2716 if( timesig_positions != null ) {
\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
2723 current_tick > tick_position || (
\r
2724 msg.getStatus() == 0xFF && ((MetaMessage)msg).getType() == 0x2F /* EOT */
\r
2727 current_tick = tick_position;
\r
2730 else { // No event
\r
2731 current_tick = next_timesig_tick = tick_position;
\r
2733 // Add measure from last event
\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
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
2749 if( current_tick == tick_position ) break; // Calculation complete
\r
2751 // Calculation incomplete, so prepare for next
\r
2753 if( extra_beats > 0 ) {
\r
2755 // Extra beats are treated as 1 measure
\r
2758 prev_tick = current_tick;
\r
2762 last_beat = extra_beats;
\r
2763 return last_measure;
\r
2766 // 小節位置を MIDI tick に変換
\r
2767 public long measureToTick( int measure ) {
\r
2768 return measureToTick( measure, 0, 0 );
\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
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
2781 timesig_upper = 4;
\r
2782 timesig_lower_index = 2; // =log2(4)
\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
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
2793 duration = (tick = evt.getTick()) - prev_tick;
\r
2794 if( duration >= estimated_ticks ) {
\r
2795 return duration_sum + estimated_ticks;
\r
2797 // Re-calculate measure (ignore extra beats/ticks)
\r
2798 measure -= ( duration / (ticks_per_beat * timesig_upper) );
\r
2799 duration_sum += duration;
\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