Javaアプリ MIDI Chord Helper のソースコード
Revision | 73ad42e8ef6cfdcad295af501765c90268f6ad90 (tree) |
---|---|
Time | 2017-10-01 02:00:51 |
Author | ![]() |
Commiter | Akiyoshi Kamide |
リファクタリング、エラー処理改善など
@@ -13,7 +13,6 @@ import java.io.IOException; | ||
13 | 13 | import java.net.URI; |
14 | 14 | import java.net.URISyntaxException; |
15 | 15 | import java.net.URL; |
16 | -import java.nio.charset.Charset; | |
17 | 16 | import java.util.Arrays; |
18 | 17 | |
19 | 18 | import javax.sound.midi.InvalidMidiDataException; |
@@ -60,7 +59,6 @@ import camidion.chordhelper.midieditor.TempoSelecter; | ||
60 | 59 | import camidion.chordhelper.midieditor.TimeSignatureSelecter; |
61 | 60 | import camidion.chordhelper.music.Chord; |
62 | 61 | import camidion.chordhelper.music.Key; |
63 | -import camidion.chordhelper.music.MIDISpec; | |
64 | 62 | import camidion.chordhelper.music.Range; |
65 | 63 | import camidion.chordhelper.pianokeyboard.MidiKeyboardPanel; |
66 | 64 | import camidion.chordhelper.pianokeyboard.PianoKeyboardAdapter; |
@@ -114,9 +112,7 @@ public class ChordHelperApplet extends JApplet { | ||
114 | 112 | URL url = (new URI(midiFileUrl)).toURL(); |
115 | 113 | String filename = url.getFile().replaceFirst("^.*/",""); |
116 | 114 | Sequence sequence = MidiSystem.getSequence(url); |
117 | - Charset charset = MIDISpec.getCharsetOf(sequence); | |
118 | - if( charset == null ) charset = Charset.defaultCharset(); | |
119 | - int index = playlistModel.add(sequence, charset, filename); | |
115 | + int index = playlistModel.add(sequence, filename); | |
120 | 116 | midiEditor.playlistTable.getSelectionModel().setSelectionInterval(index, index); |
121 | 117 | return index; |
122 | 118 | } catch( URISyntaxException|IOException|InvalidMidiDataException e ) { |
@@ -144,10 +140,8 @@ public class ChordHelperApplet extends JApplet { | ||
144 | 140 | */ |
145 | 141 | public int addToPlaylistBase64(String base64EncodedText, String filename) { |
146 | 142 | Base64Dialog d = midiEditor.playlistTable.base64Dialog; |
147 | - d.setBase64Data(base64EncodedText, filename); | |
148 | - int index = d.addToPlaylist(); | |
149 | - midiEditor.playlistTable.getSelectionModel().setSelectionInterval(index, index); | |
150 | - return index; | |
143 | + d.setBase64TextData(base64EncodedText, filename); | |
144 | + return d.addToPlaylist(); | |
151 | 145 | } |
152 | 146 | /** |
153 | 147 | * プレイリスト上で現在選択されているMIDIシーケンスをシーケンサへロードして再生します。 |
@@ -172,15 +166,13 @@ public class ChordHelperApplet extends JApplet { | ||
172 | 166 | public boolean isPlaying() { return isRunning(); } |
173 | 167 | /** |
174 | 168 | * 現在シーケンサにロードされているMIDIデータをBase64テキストに変換した結果を返します。 |
175 | - * @return MIDIデータをBase64テキストに変換した結果(シーケンサにロードされていない場合null) | |
169 | + * @return MIDIデータをBase64テキストに変換した結果 | |
176 | 170 | * @throws IOException MIDIデータの読み込みに失敗した場合 |
177 | 171 | */ |
178 | 172 | public String getMidiDataBase64() throws IOException { |
179 | - SequenceTrackListTableModel s = sequencerModel.getSequenceTrackListTableModel(); | |
180 | - if( s == null ) return null; | |
181 | 173 | Base64Dialog d = midiEditor.playlistTable.base64Dialog; |
182 | - d.setMIDIData(s.getMIDIdata()); | |
183 | - return d.getBase64Data(); | |
174 | + d.setSequenceModel(sequencerModel.getSequenceTrackListTableModel()); | |
175 | + return d.getBase64TextData(); | |
184 | 176 | } |
185 | 177 | /** |
186 | 178 | * 現在シーケンサにロードされているMIDIファイルのファイル名を返します。 |
@@ -274,7 +266,7 @@ public class ChordHelperApplet extends JApplet { | ||
274 | 266 | */ |
275 | 267 | public static class VersionInfo { |
276 | 268 | public static final String NAME = "MIDI Chord Helper"; |
277 | - public static final String VERSION = "Ver.20170722.1"; | |
269 | + public static final String VERSION = "Ver.20170930.1"; | |
278 | 270 | public static final String COPYRIGHT = "Copyright (C) 2004-2017"; |
279 | 271 | public static final String AUTHER = "@きよし - Akiyoshi Kamide"; |
280 | 272 | public static final String URL = "http://www.yk.rim.or.jp/~kamide/music/chordhelper/"; |
@@ -288,9 +280,6 @@ public class ChordHelperApplet extends JApplet { | ||
288 | 280 | /** ボタンの余白を詰めたいときに setMargin() の引数に指定するインセット */ |
289 | 281 | public static final Insets ZERO_INSETS = new Insets(0,0,0,0); |
290 | 282 | |
291 | - // MIDIエディタダイアログ(Javaアプリメインからもアクセスできるようprivateにしていない) | |
292 | - MidiSequenceEditorDialog midiEditor; | |
293 | - | |
294 | 283 | // GUIコンポーネント(内部保存用) |
295 | 284 | private PlaylistTableModel playlistModel; |
296 | 285 | private MidiSequencerModel sequencerModel; |
@@ -310,6 +299,15 @@ public class ChordHelperApplet extends JApplet { | ||
310 | 299 | private JToggleButton anoGakkiToggleButton; |
311 | 300 | private MidiDeviceTreeModel deviceTreeModel; |
312 | 301 | |
302 | + private MidiSequenceEditorDialog midiEditor; | |
303 | + /** | |
304 | + * MIDIエディタダイアログを返します。 | |
305 | + * @return MIDIエディタダイアログ | |
306 | + */ | |
307 | + public MidiSequenceEditorDialog getMidiEditor() { | |
308 | + return midiEditor; | |
309 | + } | |
310 | + | |
313 | 311 | // アイコン画像 |
314 | 312 | private Image iconImage; |
315 | 313 | public Image getIconImage() { return iconImage; } |
@@ -412,7 +410,8 @@ public class ChordHelperApplet extends JApplet { | ||
412 | 410 | }); |
413 | 411 | // 再生時間位置の移動、シーケンス名の変更、またはシーケンスの入れ替えが発生したときに呼び出されるリスナーを登録 |
414 | 412 | SongTitleLabel songTitleLabel = new SongTitleLabel(); |
415 | - sequencerModel.addChangeListener(e->{ | |
413 | + sequencerModel.addChangeListener(event->{ | |
414 | + MidiSequencerModel sequencerModel = (MidiSequencerModel) event.getSource(); | |
416 | 415 | Sequencer sequencer = sequencerModel.getSequencer(); |
417 | 416 | chordMatrix.setPlaying(sequencer.isRunning()); |
418 | 417 | SequenceTrackListTableModel sequenceModel = sequencerModel.getSequenceTrackListTableModel(); |
@@ -457,8 +456,8 @@ public class ChordHelperApplet extends JApplet { | ||
457 | 456 | add( Box.createHorizontalStrut(5) ); |
458 | 457 | add( darkModeToggleButton = new JToggleButton(new ButtonIcon(ButtonIcon.DARK_MODE_ICON)) {{ |
459 | 458 | setMargin(ZERO_INSETS); |
460 | - addItemListener(e->{ | |
461 | - boolean isDark = darkModeToggleButton.isSelected(); | |
459 | + addItemListener(event->{ | |
460 | + boolean isDark = ((JToggleButton)event.getSource()).isSelected(); | |
462 | 461 | Color col = isDark ? Color.black : null; |
463 | 462 | getContentPane().setBackground(isDark ? Color.black : rootPaneDefaultBgcolor); |
464 | 463 | mainSplitPane.setBackground(col); |
@@ -488,9 +487,9 @@ public class ChordHelperApplet extends JApplet { | ||
488 | 487 | setMargin(ZERO_INSETS); |
489 | 488 | setBorder(null); |
490 | 489 | setToolTipText("あの楽器"); |
491 | - addItemListener(e-> | |
490 | + addItemListener(event-> | |
492 | 491 | keyboardPanel.keyboardCenterPanel.keyboard.anoGakkiPane |
493 | - = anoGakkiToggleButton.isSelected() ? anoGakkiPane : null | |
492 | + = ((JToggleButton)event.getSource()).isSelected() ? anoGakkiPane : null | |
494 | 493 | ); |
495 | 494 | }} ); |
496 | 495 | add( Box.createHorizontalStrut(5) ); |
@@ -104,7 +104,7 @@ public class ChordTextField extends JTextField { | ||
104 | 104 | private Chord currentChord = null; |
105 | 105 | /** |
106 | 106 | * コードを追加します。 |
107 | - * @param chord コード | |
107 | + * @param chord 追加するコード | |
108 | 108 | */ |
109 | 109 | public void appendChord(Chord chord) { |
110 | 110 | if( currentChord == null && chord == null ) |
@@ -25,9 +25,9 @@ import javax.swing.JLabel; | ||
25 | 25 | import javax.swing.JOptionPane; |
26 | 26 | import javax.swing.SwingUtilities; |
27 | 27 | import javax.swing.WindowConstants; |
28 | -import javax.swing.event.TableModelEvent; | |
29 | 28 | |
30 | 29 | import camidion.chordhelper.mididevice.MidiSequencerModel; |
30 | +import camidion.chordhelper.midieditor.MidiSequenceEditorDialog; | |
31 | 31 | import camidion.chordhelper.midieditor.PlaylistTableModel; |
32 | 32 | import camidion.chordhelper.midieditor.SequenceTrackListTableModel; |
33 | 33 |
@@ -41,7 +41,7 @@ public class MidiChordHelper extends JFrame implements AppletStub, AppletContext | ||
41 | 41 | * @throws Exception 何らかの異常が発生した場合にスローされる |
42 | 42 | */ |
43 | 43 | public static void main(String[] args) throws Exception { |
44 | - List<File> fileList = Arrays.asList(args).stream() | |
44 | + List<File> fileList = Arrays.stream(args) | |
45 | 45 | .map(arg -> new File(arg)) |
46 | 46 | .collect(Collectors.toList()); |
47 | 47 | SwingUtilities.invokeLater(()->new MidiChordHelper(fileList)); |
@@ -57,21 +57,17 @@ public class MidiChordHelper extends JFrame implements AppletStub, AppletContext | ||
57 | 57 | private JLabel statusBar = new JLabel("Welcome to "+ChordHelperApplet.VersionInfo.NAME) { |
58 | 58 | { setFont(getFont().deriveFont(Font.PLAIN)); } |
59 | 59 | }; |
60 | - private void updateFilename(SequenceTrackListTableModel sequence) { | |
60 | + private static String titleOf(SequenceTrackListTableModel sequence) { | |
61 | 61 | String title = ChordHelperApplet.VersionInfo.NAME; |
62 | 62 | if( sequence != null ) { |
63 | 63 | String filename = sequence.getFilename(); |
64 | 64 | if( filename != null && ! filename.isEmpty() ) |
65 | 65 | title = filename+" - "+title; |
66 | 66 | } |
67 | - setTitle(title); | |
67 | + return title; | |
68 | 68 | } |
69 | - private void updateFilename(MidiSequencerModel sequencer) { | |
70 | - updateFilename(sequencer.getSequenceTrackListTableModel()); | |
71 | - } | |
72 | - private void updateFilename(TableModelEvent event) { | |
73 | - if( ! PlaylistTableModel.filenameChanged(event) ) return; | |
74 | - updateFilename(((PlaylistTableModel)event.getSource()).getSequencerModel()); | |
69 | + private void setTitleOf(MidiSequencerModel sequencer) { | |
70 | + setTitle(titleOf(sequencer.getSequenceTrackListTableModel())); | |
75 | 71 | } |
76 | 72 | private MidiChordHelper(List<File> fileList) { |
77 | 73 | setTitle(ChordHelperApplet.VersionInfo.NAME); |
@@ -103,13 +99,17 @@ public class MidiChordHelper extends JFrame implements AppletStub, AppletContext | ||
103 | 99 | System.exit(0); |
104 | 100 | } |
105 | 101 | }); |
106 | - PlaylistTableModel playlist = applet.midiEditor.getPlaylistModel(); | |
102 | + MidiSequenceEditorDialog editor = applet.getMidiEditor(); | |
103 | + PlaylistTableModel playlist = editor.getPlaylistModel(); | |
107 | 104 | MidiSequencerModel sequencer = playlist.getSequencerModel(); |
108 | - sequencer.addChangeListener(e->updateFilename((MidiSequencerModel)e.getSource())); | |
109 | - playlist.addTableModelListener(e->updateFilename(e)); | |
110 | - updateFilename(sequencer); | |
105 | + sequencer.addChangeListener(ce->setTitleOf(sequencer)); | |
106 | + playlist.addTableModelListener(tme->{ | |
107 | + if( playlist.isLoadedSequenceChanged(tme, PlaylistTableModel.Column.FILENAME) ) | |
108 | + setTitleOf(sequencer); | |
109 | + }); | |
110 | + setTitleOf(sequencer); | |
111 | 111 | applet.start(); |
112 | - applet.midiEditor.play(fileList); | |
112 | + editor.play(fileList); | |
113 | 113 | }); |
114 | 114 | } |
115 | 115 | @Override |
@@ -4,8 +4,8 @@ import java.awt.event.ActionEvent; | ||
4 | 4 | import java.io.ByteArrayInputStream; |
5 | 5 | import java.io.IOException; |
6 | 6 | import java.io.InputStream; |
7 | -import java.nio.charset.Charset; | |
8 | 7 | import java.util.Base64; |
8 | +import java.util.regex.Matcher; | |
9 | 9 | import java.util.regex.Pattern; |
10 | 10 | |
11 | 11 | import javax.sound.midi.InvalidMidiDataException; |
@@ -21,52 +21,80 @@ import javax.swing.JOptionPane; | ||
21 | 21 | import javax.swing.JPanel; |
22 | 22 | import javax.swing.JScrollPane; |
23 | 23 | import javax.swing.JTextArea; |
24 | +import javax.swing.ListSelectionModel; | |
24 | 25 | import javax.swing.event.DocumentEvent; |
25 | 26 | import javax.swing.event.DocumentListener; |
26 | 27 | |
27 | 28 | import camidion.chordhelper.ButtonIcon; |
28 | 29 | import camidion.chordhelper.ChordHelperApplet; |
29 | -import camidion.chordhelper.music.MIDISpec; | |
30 | 30 | |
31 | 31 | /** |
32 | 32 | * Base64テキスト入力ダイアログ |
33 | 33 | */ |
34 | -public class Base64Dialog extends JDialog implements DocumentListener { | |
35 | - public static final Pattern HEADER_PATTERN = Pattern.compile("^.*:.*$", Pattern.MULTILINE); | |
34 | +public class Base64Dialog extends JDialog { | |
36 | 35 | private JTextArea base64TextArea = new JTextArea(8,56); |
37 | - private void error(String message) { | |
36 | + private PlaylistTable playlistTable; | |
37 | + private void decodeError(String message) { | |
38 | 38 | JOptionPane.showMessageDialog(base64TextArea, (Object)message, |
39 | 39 | ChordHelperApplet.VersionInfo.NAME, JOptionPane.WARNING_MESSAGE); |
40 | 40 | base64TextArea.requestFocusInWindow(); |
41 | 41 | } |
42 | - private String createHeader(String filename) { | |
43 | - return "Content-Type: audio/midi; name=\"" + filename + "\"\n" | |
44 | - + "Content-Transfer-Encoding: base64\n\n"; | |
42 | + private static String createHeaderOfFilename(String filename) { | |
43 | + String header = "Content-Type: audio/midi; name=\""; | |
44 | + if( filename != null ) header += filename; | |
45 | + header += "\"\nContent-Transfer-Encoding: base64\n\n"; | |
46 | + return header; | |
47 | + } | |
48 | + private static String createBase64TextWithHeader(SequenceTrackListTableModel sequenceModel) throws IOException { | |
49 | + if( sequenceModel == null ) return null; | |
50 | + String text = createHeaderOfFilename(sequenceModel.getFilename()); | |
51 | + byte[] midiData = sequenceModel.getMIDIdata(); | |
52 | + if( midiData != null && midiData.length > 0 ) | |
53 | + text += Base64.getMimeEncoder().encodeToString(midiData); | |
54 | + text += "\n"; | |
55 | + return text; | |
56 | + } | |
57 | + private static String bodyOf(String base64TextWithHeader) { | |
58 | + // bodyには":"が含まれないのでヘッダと混同する心配なし | |
59 | + return Pattern.compile("^.*:.*$", Pattern.MULTILINE).matcher(base64TextWithHeader).replaceAll(""); | |
60 | + } | |
61 | + private static String filenameOf(String base64TextWithHeader) { | |
62 | + Matcher m = Pattern.compile("(?i)^Content-Type:.*name=\"(.*)\"$", Pattern.MULTILINE).matcher(base64TextWithHeader); | |
63 | + return m.find() ? m.group(1) : ""; | |
45 | 64 | } |
46 | - public PlaylistTable playlistTable; | |
47 | 65 | /** |
48 | 66 | * 入力されたBase64テキストをデコードし、MIDIシーケンスとしてプレイリストに追加します。 |
49 | 67 | * @return プレイリストに追加されたMIDIシーケンスのインデックス(先頭が0)、追加に失敗した場合は -1 |
50 | 68 | */ |
51 | 69 | public int addToPlaylist() { |
52 | - byte[] midiData = null; | |
70 | + String base64Text = base64TextArea.getText(); | |
71 | + byte[] decodedData; | |
53 | 72 | try { |
54 | - midiData = getMIDIData(); | |
55 | - } catch(Exception e) { | |
56 | - error("Base64デコードに失敗しました。\n"+e); | |
73 | + decodedData = Base64.getMimeDecoder().decode(bodyOf(base64Text).getBytes()); | |
74 | + } catch(Exception ex) { | |
75 | + // 不正なBase64テキストが入力された場合 | |
76 | + decodeError("Base64デコードに失敗しました。\n"+ex); | |
57 | 77 | return -1; |
58 | 78 | } |
59 | - try (InputStream in = new ByteArrayInputStream(midiData)) { | |
60 | - Sequence sequence = MidiSystem.getSequence(in); | |
61 | - Charset charset = MIDISpec.getCharsetOf(sequence); | |
62 | - if( charset == null ) charset = Charset.defaultCharset(); | |
63 | - int index = playlistTable.getModel().add(sequence, charset, null); | |
64 | - playlistTable.getSelectionModel().setSelectionInterval(index, index); | |
65 | - return index; | |
66 | - } catch( IOException|InvalidMidiDataException e ) { | |
67 | - error("Base64デコードした結果をMIDIシーケンスとして読み込めませんでした。\n"+e); | |
79 | + Sequence sequence; | |
80 | + try (InputStream in = new ByteArrayInputStream(decodedData)) { | |
81 | + sequence = MidiSystem.getSequence(in); | |
82 | + } catch( IOException|InvalidMidiDataException ex ) { | |
83 | + // MIDI以外のデータをエンコードしたBase64テキストが入力された場合 | |
84 | + decodeError("Base64デコードした結果をMIDIシーケンスとして読み込めませんでした。\n"+ex); | |
85 | + return -1; | |
86 | + } | |
87 | + int newIndex; | |
88 | + try { | |
89 | + newIndex = playlistTable.getModel().add(sequence, filenameOf(base64Text)); | |
90 | + } catch(Exception ex) { | |
91 | + // 何らかの理由でプレイリストへの追加ができなかった場合 | |
92 | + decodeError("Base64デコードしたMIDIシーケンスをプレイリストに追加できませんでした。\n"+ex); | |
68 | 93 | return -1; |
69 | 94 | } |
95 | + ListSelectionModel sm = playlistTable.getSelectionModel(); | |
96 | + if( sm != null ) sm.setSelectionInterval(newIndex, newIndex); | |
97 | + return newIndex; | |
70 | 98 | } |
71 | 99 | /** |
72 | 100 | * Base64デコードアクション |
@@ -84,8 +112,12 @@ public class Base64Dialog extends JDialog implements DocumentListener { | ||
84 | 112 | public Action clearAction = new AbstractAction("Clear", new ButtonIcon(ButtonIcon.X_ICON)) { |
85 | 113 | { putValue(Action.SHORT_DESCRIPTION, "Base64テキスト欄を消去"); } |
86 | 114 | @Override |
87 | - public void actionPerformed(ActionEvent e) { setText(null); } | |
115 | + public void actionPerformed(ActionEvent e) { base64TextArea.setText(null); } | |
88 | 116 | }; |
117 | + private void setActionEnabled(boolean b) { | |
118 | + addBase64Action.setEnabled(b); | |
119 | + clearAction.setEnabled(b); | |
120 | + } | |
89 | 121 | /** |
90 | 122 | * Base64テキスト入力ダイアログを構築します。 |
91 | 123 | * @param playlistTable Base64デコードされたMIDIシーケンスの追加先プレイリストビュー |
@@ -105,73 +137,47 @@ public class Base64Dialog extends JDialog implements DocumentListener { | ||
105 | 137 | }}); |
106 | 138 | setBounds( 300, 250, 660, 300 ); |
107 | 139 | base64TextArea.setToolTipText("Paste Base64-encoded MIDI sequence here"); |
108 | - base64TextArea.getDocument().addDocumentListener(this); | |
109 | - addBase64Action.setEnabled(false); | |
110 | - clearAction.setEnabled(false); | |
111 | - } | |
112 | - @Override | |
113 | - public void insertUpdate(DocumentEvent e) { | |
114 | - addBase64Action.setEnabled(true); | |
115 | - clearAction.setEnabled(true); | |
116 | - } | |
117 | - @Override | |
118 | - public void removeUpdate(DocumentEvent e) { | |
119 | - if( e.getDocument().getLength() > 0 ) return; | |
120 | - addBase64Action.setEnabled(false); | |
121 | - clearAction.setEnabled(false); | |
140 | + base64TextArea.getDocument().addDocumentListener(new DocumentListener() { | |
141 | + @Override | |
142 | + public void insertUpdate(DocumentEvent e) { | |
143 | + setActionEnabled(true); | |
144 | + } | |
145 | + @Override | |
146 | + public void removeUpdate(DocumentEvent e) { | |
147 | + if( e.getDocument().getLength() > 0 ) return; | |
148 | + setActionEnabled(false); | |
149 | + } | |
150 | + @Override | |
151 | + public void changedUpdate(DocumentEvent e) { } | |
152 | + }); | |
153 | + setActionEnabled(false); | |
122 | 154 | } |
123 | - @Override | |
124 | - public void changedUpdate(DocumentEvent e) { } | |
125 | 155 | /** |
126 | - * バイナリー形式でMIDIデータを返します。 | |
127 | - * @return バイナリー形式のMIDIデータ | |
128 | - * @throws IllegalArgumentException 入力されているテキストが有効なBase64スキームになっていない場合 | |
156 | + * MIDIシーケンスモデルを設定します。 | |
157 | + * @param sequenceModel MIDIシーケンスモデル | |
129 | 158 | */ |
130 | - public byte[] getMIDIData() { | |
131 | - String body = HEADER_PATTERN.matcher(base64TextArea.getText()).replaceAll(""); | |
132 | - return Base64.getMimeDecoder().decode(body.getBytes()); | |
133 | - } | |
134 | - /** | |
135 | - * バイナリー形式のMIDIデータを設定します。 | |
136 | - * @param midiData バイナリー形式のMIDIデータ | |
137 | - */ | |
138 | - public void setMIDIData(byte[] midiData) { setMIDIData(midiData, null); } | |
139 | - /** | |
140 | - * バイナリー形式のMIDIデータを、ファイル名をつけて設定します。 | |
141 | - * @param midiData バイナリー形式のMIDIデータ | |
142 | - * @param filename ファイル名 | |
143 | - */ | |
144 | - public void setMIDIData(byte[] midiData, String filename) { | |
145 | - if( midiData == null || midiData.length == 0 ) return; | |
146 | - if( filename == null ) filename = ""; | |
147 | - setText(createHeader(filename) + Base64.getMimeEncoder().encodeToString(midiData) + "\n"); | |
159 | + public void setSequenceModel(SequenceTrackListTableModel sequenceModel) { | |
160 | + String text; | |
161 | + try { | |
162 | + text = createBase64TextWithHeader(sequenceModel); | |
163 | + } catch (IOException ioex) { | |
164 | + text = "File[" + sequenceModel.getFilename() + "]:" + ioex; | |
165 | + } | |
166 | + base64TextArea.setText(text); | |
148 | 167 | base64TextArea.selectAll(); |
149 | 168 | } |
150 | 169 | /** |
151 | - * Base64形式でMIDIデータを返します。 | |
170 | + * Base64形式でテキスト化されたMIDIデータを返します。 | |
152 | 171 | * @return Base64形式のMIDIデータ |
153 | 172 | */ |
154 | - public String getBase64Data() { return base64TextArea.getText(); } | |
173 | + public String getBase64TextData() { return base64TextArea.getText(); } | |
155 | 174 | /** |
156 | - * Base64形式のMIDIデータを設定します。 | |
157 | - * @param base64Data Base64形式のMIDIデータ | |
175 | + * Base64形式でテキスト化されたMIDIデータを、ヘッダつきで設定します。 | |
176 | + * @param base64TextData Base64形式のMIDIデータ | |
177 | + * @param filename ヘッダに含めるファイル名(nullを指定すると""として設定される) | |
158 | 178 | */ |
159 | - public void setBase64Data(String base64Data) { | |
160 | - setText(null); | |
161 | - base64TextArea.append(base64Data); | |
179 | + public void setBase64TextData(String base64TextData, String filename) { | |
180 | + base64TextArea.setText(createHeaderOfFilename(filename)); | |
181 | + base64TextArea.append(base64TextData); | |
162 | 182 | } |
163 | - /** | |
164 | - * Base64形式のMIDIデータを、ファイル名をつけて設定します。 | |
165 | - * @param base64Data Base64形式のMIDIデータ | |
166 | - * @param filename ファイル名 | |
167 | - */ | |
168 | - public void setBase64Data(String base64Data, String filename) { | |
169 | - setText(createHeader(filename)); | |
170 | - base64TextArea.append(base64Data); | |
171 | - } | |
172 | - /** | |
173 | - * テキスト文字列を設定します。 | |
174 | - * @param text テキスト文字列 | |
175 | - */ | |
176 | - public void setText(String text) { base64TextArea.setText(text); } | |
177 | 183 | } |
@@ -44,7 +44,6 @@ import javax.swing.table.TableColumnModel; | ||
44 | 44 | |
45 | 45 | import camidion.chordhelper.ChordHelperApplet; |
46 | 46 | import camidion.chordhelper.mididevice.MidiSequencerModel; |
47 | -import camidion.chordhelper.music.MIDISpec; | |
48 | 47 | |
49 | 48 | /** |
50 | 49 | * プレイリストビュー(シーケンスリスト) |
@@ -129,20 +128,7 @@ public class PlaylistTable extends JTable { | ||
129 | 128 | } |
130 | 129 | @Override |
131 | 130 | public void actionPerformed(ActionEvent e) { |
132 | - SequenceTrackListTableModel sequenceModel = getSelectedSequenceModel(); | |
133 | - byte[] data = null; | |
134 | - String filename = null; | |
135 | - if( sequenceModel != null ) { | |
136 | - filename = sequenceModel.getFilename(); | |
137 | - try { | |
138 | - data = sequenceModel.getMIDIdata(); | |
139 | - } catch (IOException ioe) { | |
140 | - base64Dialog.setText("File["+filename+"]:"+ioe.toString()); | |
141 | - base64Dialog.setVisible(true); | |
142 | - return; | |
143 | - } | |
144 | - } | |
145 | - base64Dialog.setMIDIData(data, filename); | |
131 | + base64Dialog.setSequenceModel(getSelectedSequenceModel()); | |
146 | 132 | base64Dialog.setVisible(true); |
147 | 133 | } |
148 | 134 | }; |
@@ -314,22 +300,28 @@ public class PlaylistTable extends JTable { | ||
314 | 300 | */ |
315 | 301 | @Override |
316 | 302 | public PlaylistTableModel getModel() { return (PlaylistTableModel)dataModel; } |
303 | + /** | |
304 | + * {@link #add(List)} を呼び出し、このプレイリストにMIDIファイルを追加します。 | |
305 | + * @param files MIDIファイル | |
306 | + * @return 追加されたMIDIファイルのインデックス値(先頭が0、追加されなかった場合は-1) | |
307 | + */ | |
308 | + public int add(File... files) { | |
309 | + return add(Arrays.asList(files)); | |
310 | + } | |
317 | 311 | /** |
318 | 312 | * このプレイリストにMIDIファイルを追加します。追加に失敗した場合はダイアログを表示し、 |
319 | 313 | * 後続のMIDIファイルが残っていればそれを追加するかどうかをユーザに尋ねます。 |
320 | - * @param fileList MIDIファイルのリスト | |
314 | + * @param files MIDIファイルのリスト | |
321 | 315 | * @return 追加されたMIDIファイルのインデックス値(先頭が0、追加されなかった場合は-1) |
322 | 316 | */ |
323 | - public int add(List<File> fileList) { | |
317 | + public int add(List<File> files) { | |
324 | 318 | int firstIndex = -1; |
325 | - Iterator<File> itr = fileList.iterator(); | |
319 | + Iterator<File> itr = files.iterator(); | |
326 | 320 | while(itr.hasNext()) { |
327 | 321 | File file = itr.next(); |
328 | 322 | try (FileInputStream in = new FileInputStream(file)) { |
329 | 323 | Sequence sequence = MidiSystem.getSequence(in); |
330 | - Charset charset = MIDISpec.getCharsetOf(sequence); | |
331 | - if( charset == null ) charset = Charset.defaultCharset(); | |
332 | - int lastIndex = ((PlaylistTableModel)dataModel).add(sequence, charset, file.getName()); | |
324 | + int lastIndex = ((PlaylistTableModel)dataModel).add(sequence, file.getName()); | |
333 | 325 | if( firstIndex < 0 ) firstIndex = lastIndex; |
334 | 326 | } catch(IOException|InvalidMidiDataException e) { |
335 | 327 | String message = "Could not open as MIDI file "+file+"\n"+e; |
@@ -458,7 +450,7 @@ public class PlaylistTable extends JTable { | ||
458 | 450 | ex.printStackTrace(); |
459 | 451 | return; |
460 | 452 | } |
461 | - int firstIndex = PlaylistTable.this.add(Arrays.asList(getSelectedFile())); | |
453 | + int firstIndex = PlaylistTable.this.add(getSelectedFile()); | |
462 | 454 | try { |
463 | 455 | PlaylistTableModel model = getModel(); |
464 | 456 | MidiSequencerModel sequencerModel = model.getSequencerModel(); |
@@ -7,6 +7,7 @@ import java.util.HashMap; | ||
7 | 7 | import java.util.List; |
8 | 8 | import java.util.Map; |
9 | 9 | import java.util.Vector; |
10 | +import java.util.stream.IntStream; | |
10 | 11 | |
11 | 12 | import javax.sound.midi.InvalidMidiDataException; |
12 | 13 | import javax.sound.midi.Sequence; |
@@ -21,7 +22,7 @@ import javax.swing.table.AbstractTableModel; | ||
21 | 22 | |
22 | 23 | import camidion.chordhelper.ButtonIcon; |
23 | 24 | import camidion.chordhelper.mididevice.MidiSequencerModel; |
24 | -import camidion.chordhelper.music.ChordProgression; | |
25 | +import camidion.chordhelper.music.MIDISpec; | |
25 | 26 | |
26 | 27 | /** |
27 | 28 | * プレイリスト(MIDIシーケンスリスト)のテーブルデータモデル |
@@ -40,30 +41,18 @@ public class PlaylistTableModel extends AbstractTableModel { | ||
40 | 41 | * 空のイベントリストモデル |
41 | 42 | */ |
42 | 43 | public final MidiEventTableModel emptyEventListTableModel = new MidiEventTableModel(emptyTrackListTableModel, null); |
43 | - /** | |
44 | - * テーブルモデルの変更を示すイベントが、ファイル名の変更によるものかどうかをチェックします。 | |
45 | - * @param event テーブルモデルの変更を示すイベント | |
46 | - * @return ファイル名の変更による場合true | |
47 | - */ | |
48 | - public static boolean filenameChanged(TableModelEvent event) { | |
49 | - int c = event.getColumn(); | |
50 | - return c == Column.FILENAME.ordinal() || c == TableModelEvent.ALL_COLUMNS ; | |
51 | - } | |
52 | 44 | /** 再生中のシーケンサーの秒位置リスナー */ |
53 | 45 | private ChangeListener mmssPosition = new ChangeListener() { |
54 | 46 | private int value = 0; |
55 | 47 | @Override |
56 | 48 | public void stateChanged(ChangeEvent event) { |
57 | 49 | Object src = event.getSource(); |
58 | - if( src instanceof MidiSequencerModel ) { | |
59 | - MidiSequencerModel sequencerModel = (MidiSequencerModel)src; | |
60 | - int newValue = sequencerModel.getValue() / 1000; | |
61 | - if(value != newValue) { | |
62 | - value = newValue; | |
63 | - int rowIndex = sequenceModelList.indexOf(sequencerModel.getSequenceTrackListTableModel()); | |
64 | - fireTableCellUpdated(rowIndex, Column.POSITION.ordinal()); | |
65 | - } | |
66 | - } | |
50 | + if( ! (src instanceof MidiSequencerModel) ) return; | |
51 | + MidiSequencerModel sequencerModel = (MidiSequencerModel)src; | |
52 | + int newValue = sequencerModel.getValue() / 1000; | |
53 | + if(value == newValue) return; | |
54 | + value = newValue; | |
55 | + fireTableCellUpdated(sequencerModel.getSequenceTrackListTableModel(), Column.POSITION); | |
67 | 56 | } |
68 | 57 | @Override |
69 | 58 | public String toString() { |
@@ -79,38 +68,35 @@ public class PlaylistTableModel extends AbstractTableModel { | ||
79 | 68 | sequencerModel.addChangeListener(mmssPosition); |
80 | 69 | sequencerModel.getSequencer().addMetaEventListener(msg->{ |
81 | 70 | // EOF(0x2F)が来て曲が終わったら次の曲へ進める |
82 | - if(msg.getType() == 0x2F) SwingUtilities.invokeLater(()->{ | |
83 | - try { | |
84 | - goNext(); | |
85 | - } catch (InvalidMidiDataException e) { | |
86 | - throw new RuntimeException("Could not play next sequence after end-of-track",e); | |
87 | - } | |
88 | - }); | |
71 | + if(msg.getType() == 0x2F) SwingUtilities.invokeLater(()->goNext()); | |
89 | 72 | }); |
90 | 73 | } |
91 | 74 | /** |
92 | 75 | * 次の曲へ進みます。 |
93 | - * | |
94 | 76 | * <p>リピートモードの場合は同じ曲をもう一度再生、そうでない場合は次の曲へ進んで再生します。 |
95 | 77 | * 次の曲がなければ、そこで停止します。いずれの場合も曲の先頭へ戻ります。 |
96 | 78 | * </p> |
97 | - * @throws InvalidMidiDataException {@link Sequencer#setSequence(Sequence)} を参照 | |
98 | - * @throws IllegalStateException MIDIシーケンサデバイスが閉じている場合 | |
79 | + * @throws IllegalStateException {@link #loadNext(int)} から | |
80 | + * {@link InvalidMidiDataException} がスローされた場合 | |
81 | + * (MIDIシーケンサデバイスが閉じている状態で呼び出されたことが主な原因) | |
99 | 82 | */ |
100 | - private void goNext() throws InvalidMidiDataException { | |
83 | + private void goNext() { | |
101 | 84 | // とりあえず曲の先頭へ戻る |
102 | 85 | sequencerModel.getSequencer().setMicrosecondPosition(0); |
103 | - if( (Boolean)toggleRepeatAction.getValue(Action.SELECTED_KEY) || loadNext(1) ) { | |
104 | - // リピートモードのときはもう一度同じ曲を、そうでない場合は次の曲を再生開始 | |
105 | - sequencerModel.start(); | |
106 | - } | |
107 | - else { | |
108 | - // 最後の曲が終わったので、停止状態にする | |
109 | - sequencerModel.stop(); | |
110 | - // ここでボタンが停止状態に変わったはずなので、通常であれば再生ボタンが自力で再描画するところだが、 | |
111 | - // セルのレンダラーが描く再生ボタンには効かないようなので、セルを突っついて再表示させる。 | |
112 | - int rowIndex = sequenceModelList.indexOf(sequencerModel.getSequenceTrackListTableModel()); | |
113 | - fireTableCellUpdated(rowIndex, Column.PLAY.ordinal()); | |
86 | + try { | |
87 | + if( (Boolean)toggleRepeatAction.getValue(Action.SELECTED_KEY) || loadNext(1) ) { | |
88 | + // リピートモードのときはもう一度同じ曲を、そうでない場合は次の曲を再生開始 | |
89 | + sequencerModel.start(); | |
90 | + } | |
91 | + else { | |
92 | + // 最後の曲が終わったので、停止状態にする | |
93 | + sequencerModel.stop(); | |
94 | + // ここでボタンが停止状態に変わったはずなので、通常であれば再生ボタンが自力で再描画するところだが、 | |
95 | + // セルのレンダラーが描く再生ボタンには効かないようなので、セルを突っついて再表示させる。 | |
96 | + fireTableCellUpdated(sequencerModel.getSequenceTrackListTableModel(), Column.PLAY); | |
97 | + } | |
98 | + } catch (InvalidMidiDataException ex) { | |
99 | + throw new IllegalStateException("Could not play next sequence after end-of-track",ex); | |
114 | 100 | } |
115 | 101 | } |
116 | 102 | /** |
@@ -287,6 +273,46 @@ public class PlaylistTableModel extends AbstractTableModel { | ||
287 | 273 | } |
288 | 274 | public boolean isCellEditable() { return false; } |
289 | 275 | public Object getValueOf(SequenceTrackListTableModel sequenceModel) { return ""; } |
276 | + /** | |
277 | + * この列が変更されたか調べます。 | |
278 | + * @param event テーブルモデルの変更を示すイベント | |
279 | + * @return この列に変更がある場合true | |
280 | + */ | |
281 | + public boolean isChanged(TableModelEvent event) { | |
282 | + int index = event.getColumn(); | |
283 | + return index == ordinal() || index == TableModelEvent.ALL_COLUMNS ; | |
284 | + } | |
285 | + } | |
286 | + /** | |
287 | + * 連携中のシーケンサにロードされているシーケンスの行の、指定された列が変更されたか調べます。 | |
288 | + * ロードされているシーケンスがない場合、変更なしとみなされます。 | |
289 | + * @param event テーブルモデルの変更を示すイベント | |
290 | + * @param column 対象の列(nullを指定すると、どの列が変更されても、その行の変更だけで変更ありとみなされる) | |
291 | + * @return 変更がある場合true | |
292 | + */ | |
293 | + public boolean isLoadedSequenceChanged(TableModelEvent event, Column column) { | |
294 | + if( column != null && ! column.isChanged(event) ) return false; | |
295 | + SequenceTrackListTableModel loadedSequence = sequencerModel.getSequenceTrackListTableModel(); | |
296 | + return loadedSequence != null && IntStream.rangeClosed(event.getFirstRow(), event.getLastRow()) | |
297 | + .anyMatch( index -> index != TableModelEvent.HEADER_ROW && sequenceModelList.get(index) == loadedSequence ); | |
298 | + } | |
299 | + /** | |
300 | + * [row, column]にあるセルの値が更新されたことを、すべてのリスナーに通知します。 | |
301 | + * @param row 更新されたセルの行 | |
302 | + * @param column 更新されたセルの列 | |
303 | + * @see #fireTableCellUpdated(int, int) | |
304 | + */ | |
305 | + public void fireTableCellUpdated(int row, Column column) { | |
306 | + fireTableCellUpdated(row, column.ordinal()); | |
307 | + } | |
308 | + /** | |
309 | + * [sequence, column]にあるセルの値が更新されたことを、すべてのリスナーに通知します。 | |
310 | + * @param sequence 更新されたMIDIシーケンス | |
311 | + * @param column 更新されたセルの列 | |
312 | + * @see #fireTableCellUpdated(int, int) | |
313 | + */ | |
314 | + public void fireTableCellUpdated(SequenceTrackListTableModel sequence, Column column) { | |
315 | + fireTableCellUpdated(sequenceModelList.indexOf(sequence), column); | |
290 | 316 | } |
291 | 317 | |
292 | 318 | @Override |
@@ -317,7 +343,7 @@ public class PlaylistTableModel extends AbstractTableModel { | ||
317 | 343 | case NAME: |
318 | 344 | // シーケンス名の設定または変更 |
319 | 345 | if( sequenceModelList.get(row).setName(val.toString()) ) |
320 | - fireTableCellUpdated(row, Column.MODIFIED.ordinal()); | |
346 | + fireTableCellUpdated(row, Column.MODIFIED); | |
321 | 347 | fireTableCellUpdated(row, column); |
322 | 348 | break; |
323 | 349 | case CHARSET: |
@@ -326,7 +352,7 @@ public class PlaylistTableModel extends AbstractTableModel { | ||
326 | 352 | seq.setCharset(Charset.forName(val.toString())); |
327 | 353 | fireTableCellUpdated(row, column); |
328 | 354 | // シーケンス名の表示更新 |
329 | - fireTableCellUpdated(row, Column.NAME.ordinal()); | |
355 | + fireTableCellUpdated(row, Column.NAME); | |
330 | 356 | // トラック名の表示更新 |
331 | 357 | seq.fireTableDataChanged(); |
332 | 358 | default: |
@@ -342,20 +368,52 @@ public class PlaylistTableModel extends AbstractTableModel { | ||
342 | 368 | return (int)(sequenceModelList.stream().mapToLong(m -> m.getMicrosecondLength() / 1000L).sum() / 1000L); |
343 | 369 | } |
344 | 370 | /** |
345 | - * MIDIシーケンスを追加します。 | |
346 | - * @param sequence MIDIシーケンス(nullの場合、シーケンスを自動生成して追加) | |
371 | + * ファイル名なしでMIDIシーケンスを追加します。 | |
372 | + * 文字コードは自動的に判別されます(判別に失敗した場合はデフォルトの文字コードが指定されます)。 | |
373 | + * @param sequence MIDIシーケンス | |
374 | + * @return 追加されたシーケンスのインデックス(先頭が 0) | |
375 | + */ | |
376 | + public int add(Sequence sequence) { | |
377 | + return add(sequence, (String)null); | |
378 | + } | |
379 | + /** | |
380 | + * ファイル名を指定してMIDIシーケンスを追加します。 | |
381 | + * 文字コードは自動的に判別されます(判別に失敗した場合はデフォルトの文字コードが指定されます)。 | |
382 | + * @param sequence MIDIシーケンス | |
383 | + * @param filename ファイル名(nullの場合、ファイル名なし) | |
384 | + * @return 追加されたシーケンスのインデックス(先頭が 0) | |
385 | + */ | |
386 | + public int add(Sequence sequence, String filename) { | |
387 | + Charset charset = MIDISpec.getCharsetOf(sequence); | |
388 | + if( charset == null ) charset = Charset.defaultCharset(); | |
389 | + return add(sequence, charset, filename); | |
390 | + } | |
391 | + /** | |
392 | + * ファイル名なしで、文字コードを指定してMIDIシーケンスを追加します。 | |
393 | + * @param sequence MIDIシーケンス | |
394 | + * @param charset MIDIシーケンス内のテキスト文字コード | |
395 | + * @return 追加されたシーケンスのインデックス(先頭が 0) | |
396 | + */ | |
397 | + public int add(Sequence sequence, Charset charset) { | |
398 | + return add(sequence, charset, null); | |
399 | + } | |
400 | + /** | |
401 | + * ファイル名、文字コードを指定してMIDIシーケンスを追加します。 | |
402 | + * @param sequence MIDIシーケンス | |
347 | 403 | * @param charset MIDIシーケンス内のテキスト文字コード |
348 | 404 | * @param filename ファイル名(nullの場合、ファイル名なし) |
349 | 405 | * @return 追加されたシーケンスのインデックス(先頭が 0) |
350 | 406 | */ |
351 | 407 | public int add(Sequence sequence, Charset charset, String filename) { |
352 | - if( sequence == null ) { | |
353 | - sequence = (new ChordProgression()).toMidiSequence(charset); | |
354 | - } | |
355 | - sequenceModelList.add(new SequenceTrackListTableModel(this, sequence, charset, filename)); | |
356 | - int lastIndex = sequenceModelList.size() - 1; | |
357 | - fireTableRowsInserted(lastIndex, lastIndex); | |
358 | - return lastIndex; | |
408 | + //if( sequence == null ) { | |
409 | + // sequence = (new ChordProgression()).toMidiSequence(charset); | |
410 | + //} | |
411 | + SequenceTrackListTableModel sequenceModel = | |
412 | + new SequenceTrackListTableModel(this, sequence, charset, filename); | |
413 | + int newIndex = sequenceModelList.size(); | |
414 | + sequenceModelList.add(sequenceModel); | |
415 | + fireTableRowsInserted(newIndex, newIndex); | |
416 | + return newIndex; | |
359 | 417 | } |
360 | 418 | /** |
361 | 419 | * MIDIシーケンスを除去します。除去されたMIDIシーケンスがシーケンサーにロード済みだった場合、アンロードします。 |
@@ -383,16 +441,13 @@ public class PlaylistTableModel extends AbstractTableModel { | ||
383 | 441 | SequenceTrackListTableModel oldSeq = sequencerModel.getSequenceTrackListTableModel(); |
384 | 442 | SequenceTrackListTableModel newSeq = (newRowIndex < 0 || sequenceModelList.isEmpty() ? null : sequenceModelList.get(newRowIndex)); |
385 | 443 | if( ! sequencerModel.setSequenceTrackListTableModel(newSeq) ) return; |
386 | - int columnIndices[] = { | |
387 | - Column.PLAY.ordinal(), | |
388 | - Column.POSITION.ordinal(), | |
389 | - }; | |
390 | 444 | if( oldSeq != null ) { |
391 | - int oldRowIndex = sequenceModelList.indexOf(oldSeq); | |
392 | - for( int columnIndex : columnIndices ) fireTableCellUpdated(oldRowIndex, columnIndex); | |
445 | + fireTableCellUpdated(oldSeq, Column.PLAY); | |
446 | + fireTableCellUpdated(oldSeq, Column.POSITION); | |
393 | 447 | } |
394 | 448 | if( newSeq != null ) { |
395 | - for( int columnIndex : columnIndices ) fireTableCellUpdated(newRowIndex, columnIndex); | |
449 | + fireTableCellUpdated(newRowIndex, Column.PLAY); | |
450 | + fireTableCellUpdated(newRowIndex, Column.POSITION); | |
396 | 451 | } |
397 | 452 | } |
398 | 453 | /** |
@@ -414,7 +469,7 @@ public class PlaylistTableModel extends AbstractTableModel { | ||
414 | 469 | * @throws IllegalStateException MIDIシーケンサデバイスが閉じている場合 |
415 | 470 | */ |
416 | 471 | public int play(Sequence sequence, Charset charset) throws InvalidMidiDataException { |
417 | - int lastIndex = add(sequence, charset, ""); | |
472 | + int lastIndex = add(sequence, charset); | |
418 | 473 | if( ! sequencerModel.getSequencer().isRunning() ) play(lastIndex); |
419 | 474 | return lastIndex; |
420 | 475 | } |
@@ -5,6 +5,7 @@ import java.io.IOException; | ||
5 | 5 | import java.nio.charset.Charset; |
6 | 6 | import java.util.ArrayList; |
7 | 7 | import java.util.List; |
8 | +import java.util.stream.Stream; | |
8 | 9 | |
9 | 10 | import javax.sound.midi.MidiSystem; |
10 | 11 | import javax.sound.midi.Sequence; |
@@ -47,6 +48,15 @@ public class SequenceTrackListTableModel extends AbstractTableModel { | ||
47 | 48 | } |
48 | 49 | } |
49 | 50 | /** |
51 | + * [row, column]にあるセルの値が更新されたことを、すべてのリスナーに通知します。 | |
52 | + * @param row 更新されたセルの行 | |
53 | + * @param column 更新されたセルの列 | |
54 | + * @see #fireTableCellUpdated(int, int) | |
55 | + */ | |
56 | + public void fireTableCellUpdated(int row, Column column) { | |
57 | + fireTableCellUpdated(row, column.ordinal()); | |
58 | + } | |
59 | + /** | |
50 | 60 | * このモデルを収容している親のプレイリストを返します。 |
51 | 61 | */ |
52 | 62 | public PlaylistTableModel getParent() { return sequenceListTableModel; } |
@@ -103,6 +113,13 @@ public class SequenceTrackListTableModel extends AbstractTableModel { | ||
103 | 113 | public int getRowCount() { |
104 | 114 | return sequence == null ? 0 : sequence.getTracks().length; |
105 | 115 | } |
116 | + /** | |
117 | + * トラックが存在しない、空のシーケンスかどうか調べます。 | |
118 | + * @return トラックが存在しなければtrue | |
119 | + */ | |
120 | + public boolean isEmpty() { | |
121 | + return sequence == null || sequence.getTracks().length == 0; | |
122 | + } | |
106 | 123 | @Override |
107 | 124 | public int getColumnCount() { return Column.values().length; } |
108 | 125 | /** |
@@ -193,7 +210,7 @@ public class SequenceTrackListTableModel extends AbstractTableModel { | ||
193 | 210 | if( ch == trackTableModel.getChannel() ) break; |
194 | 211 | trackTableModel.setChannel(ch); |
195 | 212 | setModified(true); |
196 | - fireTableCellUpdated(row, Column.EVENTS.ordinal()); | |
213 | + fireTableCellUpdated(row, Column.EVENTS); | |
197 | 214 | break; |
198 | 215 | } |
199 | 216 | case TRACK_NAME: trackModelList.get(row).setString((String)val); break; |
@@ -222,6 +239,14 @@ public class SequenceTrackListTableModel extends AbstractTableModel { | ||
222 | 239 | */ |
223 | 240 | public SequenceTickIndex getSequenceTickIndex() { return sequenceTickIndex; } |
224 | 241 | /** |
242 | + * トラックから子モデルを生成します。 | |
243 | + * @param track 対象トラック | |
244 | + * @return 対象トラックから生成した子モデル | |
245 | + */ | |
246 | + private MidiEventTableModel createModelOf(Track track) { | |
247 | + return new MidiEventTableModel(this, track); | |
248 | + } | |
249 | + /** | |
225 | 250 | * MIDIシーケンスを設定します。 |
226 | 251 | * @param sequence MIDIシーケンス(nullを指定するとトラックリストが空になる) |
227 | 252 | */ |
@@ -246,11 +271,12 @@ public class SequenceTrackListTableModel extends AbstractTableModel { | ||
246 | 271 | fireTimeSignatureChanged(); |
247 | 272 | // |
248 | 273 | // トラックリストを再構築 |
249 | - Track tracks[] = sequence.getTracks(); | |
250 | - for(Track track : tracks) trackModelList.add(new MidiEventTableModel(this, track)); | |
274 | + Track[] tracks = sequence.getTracks(); | |
275 | + int newSize = tracks.length; | |
276 | + Stream.of(tracks).forEach(track -> trackModelList.add(createModelOf(track))); | |
251 | 277 | // |
252 | 278 | // トラックが挿入されたことを通知 |
253 | - fireTableRowsInserted(0, tracks.length-1); | |
279 | + fireTableRowsInserted(0, newSize-1); | |
254 | 280 | } |
255 | 281 | /** |
256 | 282 | * 拍子が変更されたとき、シーケンスtickインデックスを再作成します。 |
@@ -289,8 +315,8 @@ public class SequenceTrackListTableModel extends AbstractTableModel { | ||
289 | 315 | * @return 成功したらtrue |
290 | 316 | */ |
291 | 317 | public boolean setName(String name) { |
292 | - if( name.equals(toString()) || sequence == null ) return false; | |
293 | - if( ! MIDISpec.setNameBytesOf(sequence, name.getBytes(charset)) ) return false; | |
318 | + if( name.equals(toString()) || ! MIDISpec.setNameBytesOf(sequence, name.getBytes(charset)) ) | |
319 | + return false; | |
294 | 320 | setModified(true); |
295 | 321 | fireTableDataChanged(); |
296 | 322 | if( isOnSequencer() ) |
@@ -303,9 +329,7 @@ public class SequenceTrackListTableModel extends AbstractTableModel { | ||
303 | 329 | * @throws IOException バイト列の出力に失敗した場合 |
304 | 330 | */ |
305 | 331 | public byte[] getMIDIdata() throws IOException { |
306 | - if( sequence == null || sequence.getTracks().length == 0 ) { | |
307 | - return null; | |
308 | - } | |
332 | + if( isEmpty() ) return null; | |
309 | 333 | try( ByteArrayOutputStream out = new ByteArrayOutputStream() ) { |
310 | 334 | MidiSystem.write(sequence, 1, out); |
311 | 335 | return out.toByteArray(); |
@@ -329,10 +353,8 @@ public class SequenceTrackListTableModel extends AbstractTableModel { | ||
329 | 353 | * @return トラックモデル(見つからない場合null) |
330 | 354 | */ |
331 | 355 | public MidiEventTableModel getSelectedTrackModel(ListSelectionModel selectionModel) { |
332 | - if( sequence == null || selectionModel.isSelectionEmpty() ) return null; | |
333 | - Track tracks[] = sequence.getTracks(); | |
334 | - if( tracks.length == 0 ) return null; | |
335 | - Track t = tracks[selectionModel.getMinSelectionIndex()]; | |
356 | + if( isEmpty() || selectionModel.isSelectionEmpty() ) return null; | |
357 | + Track t = sequence.getTracks()[selectionModel.getMinSelectionIndex()]; | |
336 | 358 | return trackModelList.stream().filter(tm -> tm.getTrack() == t).findFirst().orElse(null); |
337 | 359 | } |
338 | 360 | /** |
@@ -352,9 +374,9 @@ public class SequenceTrackListTableModel extends AbstractTableModel { | ||
352 | 374 | * @return 追加したトラックのインデックス(先頭 0) |
353 | 375 | */ |
354 | 376 | public int createTrack() { |
377 | + int newIndex = getRowCount(); | |
355 | 378 | trackModelList.add(new MidiEventTableModel(this, sequence.createTrack())); |
356 | 379 | setModified(true); |
357 | - int newIndex = getRowCount() - 1; | |
358 | 380 | fireTableRowsInserted(newIndex, newIndex); |
359 | 381 | return newIndex; |
360 | 382 | } |
@@ -182,11 +182,11 @@ public class MIDISpec { | ||
182 | 182 | */ |
183 | 183 | public static byte[] getNameBytesOf(Sequence sequence) { |
184 | 184 | return Arrays.stream(sequence.getTracks()).map(t->getNameBytesOf(t)) |
185 | - .filter(Objects::nonNull).findFirst().orElse(null); | |
185 | + .filter(Objects::nonNull).findFirst().orElse(null); | |
186 | 186 | } |
187 | 187 | /** |
188 | 188 | * シーケンス名のバイト列を設定します。 |
189 | - * <p>先頭のトラックに設定されます。設定に失敗した場合、順に次のトラックへの設定を試みます。 | |
189 | + * <p>先頭のトラックから順に設定を試み、成功したところでtrueを返します。 | |
190 | 190 | * </p> |
191 | 191 | * |
192 | 192 | * @param sequence MIDIシーケンス |
@@ -194,7 +194,8 @@ public class MIDISpec { | ||
194 | 194 | * @return 成功:true、失敗:false |
195 | 195 | */ |
196 | 196 | public static boolean setNameBytesOf(Sequence sequence, byte[] name) { |
197 | - return Arrays.stream(sequence.getTracks()).anyMatch(t->setNameBytesOf(t,name)); | |
197 | + return sequence != null && Arrays.stream(sequence.getTracks()) | |
198 | + .anyMatch(t->setNameBytesOf(t,name)); | |
198 | 199 | } |
199 | 200 | /** |
200 | 201 | * 指定されたMIDIシーケンスからメタイベントのテキスト(名前や歌詞など)を検索し、その文字コードを判定します。 |