import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
-import java.nio.charset.Charset;
import java.util.Arrays;
import javax.sound.midi.InvalidMidiDataException;
import camidion.chordhelper.midieditor.TimeSignatureSelecter;
import camidion.chordhelper.music.Chord;
import camidion.chordhelper.music.Key;
-import camidion.chordhelper.music.MIDISpec;
import camidion.chordhelper.music.Range;
import camidion.chordhelper.pianokeyboard.MidiKeyboardPanel;
import camidion.chordhelper.pianokeyboard.PianoKeyboardAdapter;
URL url = (new URI(midiFileUrl)).toURL();
String filename = url.getFile().replaceFirst("^.*/","");
Sequence sequence = MidiSystem.getSequence(url);
- Charset charset = MIDISpec.getCharsetOf(sequence);
- if( charset == null ) charset = Charset.defaultCharset();
- int index = playlistModel.add(sequence, charset, filename);
+ int index = playlistModel.add(sequence, filename);
midiEditor.playlistTable.getSelectionModel().setSelectionInterval(index, index);
return index;
} catch( URISyntaxException|IOException|InvalidMidiDataException e ) {
*/
public int addToPlaylistBase64(String base64EncodedText, String filename) {
Base64Dialog d = midiEditor.playlistTable.base64Dialog;
- d.setBase64Data(base64EncodedText, filename);
- int index = d.addToPlaylist();
- midiEditor.playlistTable.getSelectionModel().setSelectionInterval(index, index);
- return index;
+ d.setBase64TextData(base64EncodedText, filename);
+ return d.addToPlaylist();
}
/**
* プレイリスト上で現在選択されているMIDIシーケンスをシーケンサへロードして再生します。
public boolean isPlaying() { return isRunning(); }
/**
* 現在シーケンサにロードされているMIDIデータをBase64テキストに変換した結果を返します。
- * @return MIDIデータをBase64テキストに変換した結果(シーケンサにロードされていない場合null)
+ * @return MIDIデータをBase64テキストに変換した結果
* @throws IOException MIDIデータの読み込みに失敗した場合
*/
public String getMidiDataBase64() throws IOException {
- SequenceTrackListTableModel s = sequencerModel.getSequenceTrackListTableModel();
- if( s == null ) return null;
Base64Dialog d = midiEditor.playlistTable.base64Dialog;
- d.setMIDIData(s.getMIDIdata());
- return d.getBase64Data();
+ d.setSequenceModel(sequencerModel.getSequenceTrackListTableModel());
+ return d.getBase64TextData();
}
/**
* 現在シーケンサにロードされているMIDIファイルのファイル名を返します。
*/
public static class VersionInfo {
public static final String NAME = "MIDI Chord Helper";
- public static final String VERSION = "Ver.20170722.1";
+ public static final String VERSION = "Ver.20170930.1";
public static final String COPYRIGHT = "Copyright (C) 2004-2017";
public static final String AUTHER = "@きよし - Akiyoshi Kamide";
public static final String URL = "http://www.yk.rim.or.jp/~kamide/music/chordhelper/";
/** ボタンの余白を詰めたいときに setMargin() の引数に指定するインセット */
public static final Insets ZERO_INSETS = new Insets(0,0,0,0);
- // MIDIエディタダイアログ(Javaアプリメインからもアクセスできるようprivateにしていない)
- MidiSequenceEditorDialog midiEditor;
-
// GUIコンポーネント(内部保存用)
private PlaylistTableModel playlistModel;
private MidiSequencerModel sequencerModel;
private JToggleButton anoGakkiToggleButton;
private MidiDeviceTreeModel deviceTreeModel;
+ private MidiSequenceEditorDialog midiEditor;
+ /**
+ * MIDIエディタダイアログを返します。
+ * @return MIDIエディタダイアログ
+ */
+ public MidiSequenceEditorDialog getMidiEditor() {
+ return midiEditor;
+ }
+
// アイコン画像
private Image iconImage;
public Image getIconImage() { return iconImage; }
});
// 再生時間位置の移動、シーケンス名の変更、またはシーケンスの入れ替えが発生したときに呼び出されるリスナーを登録
SongTitleLabel songTitleLabel = new SongTitleLabel();
- sequencerModel.addChangeListener(e->{
+ sequencerModel.addChangeListener(event->{
+ MidiSequencerModel sequencerModel = (MidiSequencerModel) event.getSource();
Sequencer sequencer = sequencerModel.getSequencer();
chordMatrix.setPlaying(sequencer.isRunning());
SequenceTrackListTableModel sequenceModel = sequencerModel.getSequenceTrackListTableModel();
add( Box.createHorizontalStrut(5) );
add( darkModeToggleButton = new JToggleButton(new ButtonIcon(ButtonIcon.DARK_MODE_ICON)) {{
setMargin(ZERO_INSETS);
- addItemListener(e->{
- boolean isDark = darkModeToggleButton.isSelected();
+ addItemListener(event->{
+ boolean isDark = ((JToggleButton)event.getSource()).isSelected();
Color col = isDark ? Color.black : null;
getContentPane().setBackground(isDark ? Color.black : rootPaneDefaultBgcolor);
mainSplitPane.setBackground(col);
setMargin(ZERO_INSETS);
setBorder(null);
setToolTipText("あの楽器");
- addItemListener(e->
+ addItemListener(event->
keyboardPanel.keyboardCenterPanel.keyboard.anoGakkiPane
- = anoGakkiToggleButton.isSelected() ? anoGakkiPane : null
+ = ((JToggleButton)event.getSource()).isSelected() ? anoGakkiPane : null
);
}} );
add( Box.createHorizontalStrut(5) );
private Chord currentChord = null;
/**
* コードを追加します。
- * @param chord コード
+ * @param chord 追加するコード
*/
public void appendChord(Chord chord) {
if( currentChord == null && chord == null )
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import javax.swing.WindowConstants;
-import javax.swing.event.TableModelEvent;
import camidion.chordhelper.mididevice.MidiSequencerModel;
+import camidion.chordhelper.midieditor.MidiSequenceEditorDialog;
import camidion.chordhelper.midieditor.PlaylistTableModel;
import camidion.chordhelper.midieditor.SequenceTrackListTableModel;
* @throws Exception 何らかの異常が発生した場合にスローされる
*/
public static void main(String[] args) throws Exception {
- List<File> fileList = Arrays.asList(args).stream()
+ List<File> fileList = Arrays.stream(args)
.map(arg -> new File(arg))
.collect(Collectors.toList());
SwingUtilities.invokeLater(()->new MidiChordHelper(fileList));
private JLabel statusBar = new JLabel("Welcome to "+ChordHelperApplet.VersionInfo.NAME) {
{ setFont(getFont().deriveFont(Font.PLAIN)); }
};
- private void updateFilename(SequenceTrackListTableModel sequence) {
+ private static String titleOf(SequenceTrackListTableModel sequence) {
String title = ChordHelperApplet.VersionInfo.NAME;
if( sequence != null ) {
String filename = sequence.getFilename();
if( filename != null && ! filename.isEmpty() )
title = filename+" - "+title;
}
- setTitle(title);
+ return title;
}
- private void updateFilename(MidiSequencerModel sequencer) {
- updateFilename(sequencer.getSequenceTrackListTableModel());
- }
- private void updateFilename(TableModelEvent event) {
- if( ! PlaylistTableModel.filenameChanged(event) ) return;
- updateFilename(((PlaylistTableModel)event.getSource()).getSequencerModel());
+ private void setTitleOf(MidiSequencerModel sequencer) {
+ setTitle(titleOf(sequencer.getSequenceTrackListTableModel()));
}
private MidiChordHelper(List<File> fileList) {
setTitle(ChordHelperApplet.VersionInfo.NAME);
System.exit(0);
}
});
- PlaylistTableModel playlist = applet.midiEditor.getPlaylistModel();
+ MidiSequenceEditorDialog editor = applet.getMidiEditor();
+ PlaylistTableModel playlist = editor.getPlaylistModel();
MidiSequencerModel sequencer = playlist.getSequencerModel();
- sequencer.addChangeListener(e->updateFilename((MidiSequencerModel)e.getSource()));
- playlist.addTableModelListener(e->updateFilename(e));
- updateFilename(sequencer);
+ sequencer.addChangeListener(ce->setTitleOf(sequencer));
+ playlist.addTableModelListener(tme->{
+ if( playlist.isLoadedSequenceChanged(tme, PlaylistTableModel.Column.FILENAME) )
+ setTitleOf(sequencer);
+ });
+ setTitleOf(sequencer);
applet.start();
- applet.midiEditor.play(fileList);
+ editor.play(fileList);
});
}
@Override
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
-import java.nio.charset.Charset;
import java.util.Base64;
+import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.sound.midi.InvalidMidiDataException;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
+import javax.swing.ListSelectionModel;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import camidion.chordhelper.ButtonIcon;
import camidion.chordhelper.ChordHelperApplet;
-import camidion.chordhelper.music.MIDISpec;
/**
* Base64テキスト入力ダイアログ
*/
-public class Base64Dialog extends JDialog implements DocumentListener {
- public static final Pattern HEADER_PATTERN = Pattern.compile("^.*:.*$", Pattern.MULTILINE);
+public class Base64Dialog extends JDialog {
private JTextArea base64TextArea = new JTextArea(8,56);
- private void error(String message) {
+ private PlaylistTable playlistTable;
+ private void decodeError(String message) {
JOptionPane.showMessageDialog(base64TextArea, (Object)message,
ChordHelperApplet.VersionInfo.NAME, JOptionPane.WARNING_MESSAGE);
base64TextArea.requestFocusInWindow();
}
- private String createHeader(String filename) {
- return "Content-Type: audio/midi; name=\"" + filename + "\"\n"
- + "Content-Transfer-Encoding: base64\n\n";
+ private static String createHeaderOfFilename(String filename) {
+ String header = "Content-Type: audio/midi; name=\"";
+ if( filename != null ) header += filename;
+ header += "\"\nContent-Transfer-Encoding: base64\n\n";
+ return header;
+ }
+ private static String createBase64TextWithHeader(SequenceTrackListTableModel sequenceModel) throws IOException {
+ if( sequenceModel == null ) return null;
+ String text = createHeaderOfFilename(sequenceModel.getFilename());
+ byte[] midiData = sequenceModel.getMIDIdata();
+ if( midiData != null && midiData.length > 0 )
+ text += Base64.getMimeEncoder().encodeToString(midiData);
+ text += "\n";
+ return text;
+ }
+ private static String bodyOf(String base64TextWithHeader) {
+ // bodyには":"が含まれないのでヘッダと混同する心配なし
+ return Pattern.compile("^.*:.*$", Pattern.MULTILINE).matcher(base64TextWithHeader).replaceAll("");
+ }
+ private static String filenameOf(String base64TextWithHeader) {
+ Matcher m = Pattern.compile("(?i)^Content-Type:.*name=\"(.*)\"$", Pattern.MULTILINE).matcher(base64TextWithHeader);
+ return m.find() ? m.group(1) : "";
}
- public PlaylistTable playlistTable;
/**
* 入力されたBase64テキストをデコードし、MIDIシーケンスとしてプレイリストに追加します。
* @return プレイリストに追加されたMIDIシーケンスのインデックス(先頭が0)、追加に失敗した場合は -1
*/
public int addToPlaylist() {
- byte[] midiData = null;
+ String base64Text = base64TextArea.getText();
+ byte[] decodedData;
try {
- midiData = getMIDIData();
- } catch(Exception e) {
- error("Base64デコードに失敗しました。\n"+e);
+ decodedData = Base64.getMimeDecoder().decode(bodyOf(base64Text).getBytes());
+ } catch(Exception ex) {
+ // 不正なBase64テキストが入力された場合
+ decodeError("Base64デコードに失敗しました。\n"+ex);
return -1;
}
- try (InputStream in = new ByteArrayInputStream(midiData)) {
- Sequence sequence = MidiSystem.getSequence(in);
- Charset charset = MIDISpec.getCharsetOf(sequence);
- if( charset == null ) charset = Charset.defaultCharset();
- int index = playlistTable.getModel().add(sequence, charset, null);
- playlistTable.getSelectionModel().setSelectionInterval(index, index);
- return index;
- } catch( IOException|InvalidMidiDataException e ) {
- error("Base64デコードした結果をMIDIシーケンスとして読み込めませんでした。\n"+e);
+ Sequence sequence;
+ try (InputStream in = new ByteArrayInputStream(decodedData)) {
+ sequence = MidiSystem.getSequence(in);
+ } catch( IOException|InvalidMidiDataException ex ) {
+ // MIDI以外のデータをエンコードしたBase64テキストが入力された場合
+ decodeError("Base64デコードした結果をMIDIシーケンスとして読み込めませんでした。\n"+ex);
+ return -1;
+ }
+ int newIndex;
+ try {
+ newIndex = playlistTable.getModel().add(sequence, filenameOf(base64Text));
+ } catch(Exception ex) {
+ // 何らかの理由でプレイリストへの追加ができなかった場合
+ decodeError("Base64デコードしたMIDIシーケンスをプレイリストに追加できませんでした。\n"+ex);
return -1;
}
+ ListSelectionModel sm = playlistTable.getSelectionModel();
+ if( sm != null ) sm.setSelectionInterval(newIndex, newIndex);
+ return newIndex;
}
/**
* Base64デコードアクション
public Action clearAction = new AbstractAction("Clear", new ButtonIcon(ButtonIcon.X_ICON)) {
{ putValue(Action.SHORT_DESCRIPTION, "Base64テキスト欄を消去"); }
@Override
- public void actionPerformed(ActionEvent e) { setText(null); }
+ public void actionPerformed(ActionEvent e) { base64TextArea.setText(null); }
};
+ private void setActionEnabled(boolean b) {
+ addBase64Action.setEnabled(b);
+ clearAction.setEnabled(b);
+ }
/**
* Base64テキスト入力ダイアログを構築します。
* @param playlistTable Base64デコードされたMIDIシーケンスの追加先プレイリストビュー
}});
setBounds( 300, 250, 660, 300 );
base64TextArea.setToolTipText("Paste Base64-encoded MIDI sequence here");
- base64TextArea.getDocument().addDocumentListener(this);
- addBase64Action.setEnabled(false);
- clearAction.setEnabled(false);
- }
- @Override
- public void insertUpdate(DocumentEvent e) {
- addBase64Action.setEnabled(true);
- clearAction.setEnabled(true);
- }
- @Override
- public void removeUpdate(DocumentEvent e) {
- if( e.getDocument().getLength() > 0 ) return;
- addBase64Action.setEnabled(false);
- clearAction.setEnabled(false);
+ base64TextArea.getDocument().addDocumentListener(new DocumentListener() {
+ @Override
+ public void insertUpdate(DocumentEvent e) {
+ setActionEnabled(true);
+ }
+ @Override
+ public void removeUpdate(DocumentEvent e) {
+ if( e.getDocument().getLength() > 0 ) return;
+ setActionEnabled(false);
+ }
+ @Override
+ public void changedUpdate(DocumentEvent e) { }
+ });
+ setActionEnabled(false);
}
- @Override
- public void changedUpdate(DocumentEvent e) { }
/**
- * バイナリー形式でMIDIデータを返します。
- * @return バイナリー形式のMIDIデータ
- * @throws IllegalArgumentException 入力されているテキストが有効なBase64スキームになっていない場合
+ * MIDIシーケンスモデルを設定します。
+ * @param sequenceModel MIDIシーケンスモデル
*/
- public byte[] getMIDIData() {
- String body = HEADER_PATTERN.matcher(base64TextArea.getText()).replaceAll("");
- return Base64.getMimeDecoder().decode(body.getBytes());
- }
- /**
- * バイナリー形式のMIDIデータを設定します。
- * @param midiData バイナリー形式のMIDIデータ
- */
- public void setMIDIData(byte[] midiData) { setMIDIData(midiData, null); }
- /**
- * バイナリー形式のMIDIデータを、ファイル名をつけて設定します。
- * @param midiData バイナリー形式のMIDIデータ
- * @param filename ファイル名
- */
- public void setMIDIData(byte[] midiData, String filename) {
- if( midiData == null || midiData.length == 0 ) return;
- if( filename == null ) filename = "";
- setText(createHeader(filename) + Base64.getMimeEncoder().encodeToString(midiData) + "\n");
+ public void setSequenceModel(SequenceTrackListTableModel sequenceModel) {
+ String text;
+ try {
+ text = createBase64TextWithHeader(sequenceModel);
+ } catch (IOException ioex) {
+ text = "File[" + sequenceModel.getFilename() + "]:" + ioex;
+ }
+ base64TextArea.setText(text);
base64TextArea.selectAll();
}
/**
- * Base64形式でMIDIデータを返します。
+ * Base64形式でテキスト化されたMIDIデータを返します。
* @return Base64形式のMIDIデータ
*/
- public String getBase64Data() { return base64TextArea.getText(); }
+ public String getBase64TextData() { return base64TextArea.getText(); }
/**
- * Base64形式のMIDIデータを設定します。
- * @param base64Data Base64形式のMIDIデータ
+ * Base64形式でテキスト化されたMIDIデータを、ヘッダつきで設定します。
+ * @param base64TextData Base64形式のMIDIデータ
+ * @param filename ヘッダに含めるファイル名(nullを指定すると""として設定される)
*/
- public void setBase64Data(String base64Data) {
- setText(null);
- base64TextArea.append(base64Data);
+ public void setBase64TextData(String base64TextData, String filename) {
+ base64TextArea.setText(createHeaderOfFilename(filename));
+ base64TextArea.append(base64TextData);
}
- /**
- * Base64形式のMIDIデータを、ファイル名をつけて設定します。
- * @param base64Data Base64形式のMIDIデータ
- * @param filename ファイル名
- */
- public void setBase64Data(String base64Data, String filename) {
- setText(createHeader(filename));
- base64TextArea.append(base64Data);
- }
- /**
- * テキスト文字列を設定します。
- * @param text テキスト文字列
- */
- public void setText(String text) { base64TextArea.setText(text); }
}
import camidion.chordhelper.ChordHelperApplet;
import camidion.chordhelper.mididevice.MidiSequencerModel;
-import camidion.chordhelper.music.MIDISpec;
/**
* プレイリストビュー(シーケンスリスト)
}
@Override
public void actionPerformed(ActionEvent e) {
- SequenceTrackListTableModel sequenceModel = getSelectedSequenceModel();
- byte[] data = null;
- String filename = null;
- if( sequenceModel != null ) {
- filename = sequenceModel.getFilename();
- try {
- data = sequenceModel.getMIDIdata();
- } catch (IOException ioe) {
- base64Dialog.setText("File["+filename+"]:"+ioe.toString());
- base64Dialog.setVisible(true);
- return;
- }
- }
- base64Dialog.setMIDIData(data, filename);
+ base64Dialog.setSequenceModel(getSelectedSequenceModel());
base64Dialog.setVisible(true);
}
};
*/
@Override
public PlaylistTableModel getModel() { return (PlaylistTableModel)dataModel; }
+ /**
+ * {@link #add(List)} を呼び出し、このプレイリストにMIDIファイルを追加します。
+ * @param files MIDIファイル
+ * @return 追加されたMIDIファイルのインデックス値(先頭が0、追加されなかった場合は-1)
+ */
+ public int add(File... files) {
+ return add(Arrays.asList(files));
+ }
/**
* このプレイリストにMIDIファイルを追加します。追加に失敗した場合はダイアログを表示し、
* 後続のMIDIファイルが残っていればそれを追加するかどうかをユーザに尋ねます。
- * @param fileList MIDIファイルのリスト
+ * @param files MIDIファイルのリスト
* @return 追加されたMIDIファイルのインデックス値(先頭が0、追加されなかった場合は-1)
*/
- public int add(List<File> fileList) {
+ public int add(List<File> files) {
int firstIndex = -1;
- Iterator<File> itr = fileList.iterator();
+ Iterator<File> itr = files.iterator();
while(itr.hasNext()) {
File file = itr.next();
try (FileInputStream in = new FileInputStream(file)) {
Sequence sequence = MidiSystem.getSequence(in);
- Charset charset = MIDISpec.getCharsetOf(sequence);
- if( charset == null ) charset = Charset.defaultCharset();
- int lastIndex = ((PlaylistTableModel)dataModel).add(sequence, charset, file.getName());
+ int lastIndex = ((PlaylistTableModel)dataModel).add(sequence, file.getName());
if( firstIndex < 0 ) firstIndex = lastIndex;
} catch(IOException|InvalidMidiDataException e) {
String message = "Could not open as MIDI file "+file+"\n"+e;
ex.printStackTrace();
return;
}
- int firstIndex = PlaylistTable.this.add(Arrays.asList(getSelectedFile()));
+ int firstIndex = PlaylistTable.this.add(getSelectedFile());
try {
PlaylistTableModel model = getModel();
MidiSequencerModel sequencerModel = model.getSequencerModel();
import java.util.List;
import java.util.Map;
import java.util.Vector;
+import java.util.stream.IntStream;
import javax.sound.midi.InvalidMidiDataException;
import javax.sound.midi.Sequence;
import camidion.chordhelper.ButtonIcon;
import camidion.chordhelper.mididevice.MidiSequencerModel;
-import camidion.chordhelper.music.ChordProgression;
+import camidion.chordhelper.music.MIDISpec;
/**
* プレイリスト(MIDIシーケンスリスト)のテーブルデータモデル
* 空のイベントリストモデル
*/
public final MidiEventTableModel emptyEventListTableModel = new MidiEventTableModel(emptyTrackListTableModel, null);
- /**
- * テーブルモデルの変更を示すイベントが、ファイル名の変更によるものかどうかをチェックします。
- * @param event テーブルモデルの変更を示すイベント
- * @return ファイル名の変更による場合true
- */
- public static boolean filenameChanged(TableModelEvent event) {
- int c = event.getColumn();
- return c == Column.FILENAME.ordinal() || c == TableModelEvent.ALL_COLUMNS ;
- }
/** 再生中のシーケンサーの秒位置リスナー */
private ChangeListener mmssPosition = new ChangeListener() {
private int value = 0;
@Override
public void stateChanged(ChangeEvent event) {
Object src = event.getSource();
- if( src instanceof MidiSequencerModel ) {
- MidiSequencerModel sequencerModel = (MidiSequencerModel)src;
- int newValue = sequencerModel.getValue() / 1000;
- if(value != newValue) {
- value = newValue;
- int rowIndex = sequenceModelList.indexOf(sequencerModel.getSequenceTrackListTableModel());
- fireTableCellUpdated(rowIndex, Column.POSITION.ordinal());
- }
- }
+ if( ! (src instanceof MidiSequencerModel) ) return;
+ MidiSequencerModel sequencerModel = (MidiSequencerModel)src;
+ int newValue = sequencerModel.getValue() / 1000;
+ if(value == newValue) return;
+ value = newValue;
+ fireTableCellUpdated(sequencerModel.getSequenceTrackListTableModel(), Column.POSITION);
}
@Override
public String toString() {
sequencerModel.addChangeListener(mmssPosition);
sequencerModel.getSequencer().addMetaEventListener(msg->{
// EOF(0x2F)が来て曲が終わったら次の曲へ進める
- if(msg.getType() == 0x2F) SwingUtilities.invokeLater(()->{
- try {
- goNext();
- } catch (InvalidMidiDataException e) {
- throw new RuntimeException("Could not play next sequence after end-of-track",e);
- }
- });
+ if(msg.getType() == 0x2F) SwingUtilities.invokeLater(()->goNext());
});
}
/**
* 次の曲へ進みます。
- *
* <p>リピートモードの場合は同じ曲をもう一度再生、そうでない場合は次の曲へ進んで再生します。
* 次の曲がなければ、そこで停止します。いずれの場合も曲の先頭へ戻ります。
* </p>
- * @throws InvalidMidiDataException {@link Sequencer#setSequence(Sequence)} を参照
- * @throws IllegalStateException MIDIシーケンサデバイスが閉じている場合
+ * @throws IllegalStateException {@link #loadNext(int)} から
+ * {@link InvalidMidiDataException} がスローされた場合
+ * (MIDIシーケンサデバイスが閉じている状態で呼び出されたことが主な原因)
*/
- private void goNext() throws InvalidMidiDataException {
+ private void goNext() {
// とりあえず曲の先頭へ戻る
sequencerModel.getSequencer().setMicrosecondPosition(0);
- if( (Boolean)toggleRepeatAction.getValue(Action.SELECTED_KEY) || loadNext(1) ) {
- // リピートモードのときはもう一度同じ曲を、そうでない場合は次の曲を再生開始
- sequencerModel.start();
- }
- else {
- // 最後の曲が終わったので、停止状態にする
- sequencerModel.stop();
- // ここでボタンが停止状態に変わったはずなので、通常であれば再生ボタンが自力で再描画するところだが、
- // セルのレンダラーが描く再生ボタンには効かないようなので、セルを突っついて再表示させる。
- int rowIndex = sequenceModelList.indexOf(sequencerModel.getSequenceTrackListTableModel());
- fireTableCellUpdated(rowIndex, Column.PLAY.ordinal());
+ try {
+ if( (Boolean)toggleRepeatAction.getValue(Action.SELECTED_KEY) || loadNext(1) ) {
+ // リピートモードのときはもう一度同じ曲を、そうでない場合は次の曲を再生開始
+ sequencerModel.start();
+ }
+ else {
+ // 最後の曲が終わったので、停止状態にする
+ sequencerModel.stop();
+ // ここでボタンが停止状態に変わったはずなので、通常であれば再生ボタンが自力で再描画するところだが、
+ // セルのレンダラーが描く再生ボタンには効かないようなので、セルを突っついて再表示させる。
+ fireTableCellUpdated(sequencerModel.getSequenceTrackListTableModel(), Column.PLAY);
+ }
+ } catch (InvalidMidiDataException ex) {
+ throw new IllegalStateException("Could not play next sequence after end-of-track",ex);
}
}
/**
}
public boolean isCellEditable() { return false; }
public Object getValueOf(SequenceTrackListTableModel sequenceModel) { return ""; }
+ /**
+ * この列が変更されたか調べます。
+ * @param event テーブルモデルの変更を示すイベント
+ * @return この列に変更がある場合true
+ */
+ public boolean isChanged(TableModelEvent event) {
+ int index = event.getColumn();
+ return index == ordinal() || index == TableModelEvent.ALL_COLUMNS ;
+ }
+ }
+ /**
+ * 連携中のシーケンサにロードされているシーケンスの行の、指定された列が変更されたか調べます。
+ * ロードされているシーケンスがない場合、変更なしとみなされます。
+ * @param event テーブルモデルの変更を示すイベント
+ * @param column 対象の列(nullを指定すると、どの列が変更されても、その行の変更だけで変更ありとみなされる)
+ * @return 変更がある場合true
+ */
+ public boolean isLoadedSequenceChanged(TableModelEvent event, Column column) {
+ if( column != null && ! column.isChanged(event) ) return false;
+ SequenceTrackListTableModel loadedSequence = sequencerModel.getSequenceTrackListTableModel();
+ return loadedSequence != null && IntStream.rangeClosed(event.getFirstRow(), event.getLastRow())
+ .anyMatch( index -> index != TableModelEvent.HEADER_ROW && sequenceModelList.get(index) == loadedSequence );
+ }
+ /**
+ * [row, column]にあるセルの値が更新されたことを、すべてのリスナーに通知します。
+ * @param row 更新されたセルの行
+ * @param column 更新されたセルの列
+ * @see #fireTableCellUpdated(int, int)
+ */
+ public void fireTableCellUpdated(int row, Column column) {
+ fireTableCellUpdated(row, column.ordinal());
+ }
+ /**
+ * [sequence, column]にあるセルの値が更新されたことを、すべてのリスナーに通知します。
+ * @param sequence 更新されたMIDIシーケンス
+ * @param column 更新されたセルの列
+ * @see #fireTableCellUpdated(int, int)
+ */
+ public void fireTableCellUpdated(SequenceTrackListTableModel sequence, Column column) {
+ fireTableCellUpdated(sequenceModelList.indexOf(sequence), column);
}
@Override
case NAME:
// シーケンス名の設定または変更
if( sequenceModelList.get(row).setName(val.toString()) )
- fireTableCellUpdated(row, Column.MODIFIED.ordinal());
+ fireTableCellUpdated(row, Column.MODIFIED);
fireTableCellUpdated(row, column);
break;
case CHARSET:
seq.setCharset(Charset.forName(val.toString()));
fireTableCellUpdated(row, column);
// シーケンス名の表示更新
- fireTableCellUpdated(row, Column.NAME.ordinal());
+ fireTableCellUpdated(row, Column.NAME);
// トラック名の表示更新
seq.fireTableDataChanged();
default:
return (int)(sequenceModelList.stream().mapToLong(m -> m.getMicrosecondLength() / 1000L).sum() / 1000L);
}
/**
- * MIDIシーケンスを追加します。
- * @param sequence MIDIシーケンス(nullの場合、シーケンスを自動生成して追加)
+ * ファイル名なしでMIDIシーケンスを追加します。
+ * 文字コードは自動的に判別されます(判別に失敗した場合はデフォルトの文字コードが指定されます)。
+ * @param sequence MIDIシーケンス
+ * @return 追加されたシーケンスのインデックス(先頭が 0)
+ */
+ public int add(Sequence sequence) {
+ return add(sequence, (String)null);
+ }
+ /**
+ * ファイル名を指定してMIDIシーケンスを追加します。
+ * 文字コードは自動的に判別されます(判別に失敗した場合はデフォルトの文字コードが指定されます)。
+ * @param sequence MIDIシーケンス
+ * @param filename ファイル名(nullの場合、ファイル名なし)
+ * @return 追加されたシーケンスのインデックス(先頭が 0)
+ */
+ public int add(Sequence sequence, String filename) {
+ Charset charset = MIDISpec.getCharsetOf(sequence);
+ if( charset == null ) charset = Charset.defaultCharset();
+ return add(sequence, charset, filename);
+ }
+ /**
+ * ファイル名なしで、文字コードを指定してMIDIシーケンスを追加します。
+ * @param sequence MIDIシーケンス
+ * @param charset MIDIシーケンス内のテキスト文字コード
+ * @return 追加されたシーケンスのインデックス(先頭が 0)
+ */
+ public int add(Sequence sequence, Charset charset) {
+ return add(sequence, charset, null);
+ }
+ /**
+ * ファイル名、文字コードを指定してMIDIシーケンスを追加します。
+ * @param sequence MIDIシーケンス
* @param charset MIDIシーケンス内のテキスト文字コード
* @param filename ファイル名(nullの場合、ファイル名なし)
* @return 追加されたシーケンスのインデックス(先頭が 0)
*/
public int add(Sequence sequence, Charset charset, String filename) {
- if( sequence == null ) {
- sequence = (new ChordProgression()).toMidiSequence(charset);
- }
- sequenceModelList.add(new SequenceTrackListTableModel(this, sequence, charset, filename));
- int lastIndex = sequenceModelList.size() - 1;
- fireTableRowsInserted(lastIndex, lastIndex);
- return lastIndex;
+ //if( sequence == null ) {
+ // sequence = (new ChordProgression()).toMidiSequence(charset);
+ //}
+ SequenceTrackListTableModel sequenceModel =
+ new SequenceTrackListTableModel(this, sequence, charset, filename);
+ int newIndex = sequenceModelList.size();
+ sequenceModelList.add(sequenceModel);
+ fireTableRowsInserted(newIndex, newIndex);
+ return newIndex;
}
/**
* MIDIシーケンスを除去します。除去されたMIDIシーケンスがシーケンサーにロード済みだった場合、アンロードします。
SequenceTrackListTableModel oldSeq = sequencerModel.getSequenceTrackListTableModel();
SequenceTrackListTableModel newSeq = (newRowIndex < 0 || sequenceModelList.isEmpty() ? null : sequenceModelList.get(newRowIndex));
if( ! sequencerModel.setSequenceTrackListTableModel(newSeq) ) return;
- int columnIndices[] = {
- Column.PLAY.ordinal(),
- Column.POSITION.ordinal(),
- };
if( oldSeq != null ) {
- int oldRowIndex = sequenceModelList.indexOf(oldSeq);
- for( int columnIndex : columnIndices ) fireTableCellUpdated(oldRowIndex, columnIndex);
+ fireTableCellUpdated(oldSeq, Column.PLAY);
+ fireTableCellUpdated(oldSeq, Column.POSITION);
}
if( newSeq != null ) {
- for( int columnIndex : columnIndices ) fireTableCellUpdated(newRowIndex, columnIndex);
+ fireTableCellUpdated(newRowIndex, Column.PLAY);
+ fireTableCellUpdated(newRowIndex, Column.POSITION);
}
}
/**
* @throws IllegalStateException MIDIシーケンサデバイスが閉じている場合
*/
public int play(Sequence sequence, Charset charset) throws InvalidMidiDataException {
- int lastIndex = add(sequence, charset, "");
+ int lastIndex = add(sequence, charset);
if( ! sequencerModel.getSequencer().isRunning() ) play(lastIndex);
return lastIndex;
}
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
+import java.util.stream.Stream;
import javax.sound.midi.MidiSystem;
import javax.sound.midi.Sequence;
}
}
/**
+ * [row, column]にあるセルの値が更新されたことを、すべてのリスナーに通知します。
+ * @param row 更新されたセルの行
+ * @param column 更新されたセルの列
+ * @see #fireTableCellUpdated(int, int)
+ */
+ public void fireTableCellUpdated(int row, Column column) {
+ fireTableCellUpdated(row, column.ordinal());
+ }
+ /**
* このモデルを収容している親のプレイリストを返します。
*/
public PlaylistTableModel getParent() { return sequenceListTableModel; }
public int getRowCount() {
return sequence == null ? 0 : sequence.getTracks().length;
}
+ /**
+ * トラックが存在しない、空のシーケンスかどうか調べます。
+ * @return トラックが存在しなければtrue
+ */
+ public boolean isEmpty() {
+ return sequence == null || sequence.getTracks().length == 0;
+ }
@Override
public int getColumnCount() { return Column.values().length; }
/**
if( ch == trackTableModel.getChannel() ) break;
trackTableModel.setChannel(ch);
setModified(true);
- fireTableCellUpdated(row, Column.EVENTS.ordinal());
+ fireTableCellUpdated(row, Column.EVENTS);
break;
}
case TRACK_NAME: trackModelList.get(row).setString((String)val); break;
*/
public SequenceTickIndex getSequenceTickIndex() { return sequenceTickIndex; }
/**
+ * トラックから子モデルを生成します。
+ * @param track 対象トラック
+ * @return 対象トラックから生成した子モデル
+ */
+ private MidiEventTableModel createModelOf(Track track) {
+ return new MidiEventTableModel(this, track);
+ }
+ /**
* MIDIシーケンスを設定します。
* @param sequence MIDIシーケンス(nullを指定するとトラックリストが空になる)
*/
fireTimeSignatureChanged();
//
// トラックリストを再構築
- Track tracks[] = sequence.getTracks();
- for(Track track : tracks) trackModelList.add(new MidiEventTableModel(this, track));
+ Track[] tracks = sequence.getTracks();
+ int newSize = tracks.length;
+ Stream.of(tracks).forEach(track -> trackModelList.add(createModelOf(track)));
//
// トラックが挿入されたことを通知
- fireTableRowsInserted(0, tracks.length-1);
+ fireTableRowsInserted(0, newSize-1);
}
/**
* 拍子が変更されたとき、シーケンスtickインデックスを再作成します。
* @return 成功したらtrue
*/
public boolean setName(String name) {
- if( name.equals(toString()) || sequence == null ) return false;
- if( ! MIDISpec.setNameBytesOf(sequence, name.getBytes(charset)) ) return false;
+ if( name.equals(toString()) || ! MIDISpec.setNameBytesOf(sequence, name.getBytes(charset)) )
+ return false;
setModified(true);
fireTableDataChanged();
if( isOnSequencer() )
* @throws IOException バイト列の出力に失敗した場合
*/
public byte[] getMIDIdata() throws IOException {
- if( sequence == null || sequence.getTracks().length == 0 ) {
- return null;
- }
+ if( isEmpty() ) return null;
try( ByteArrayOutputStream out = new ByteArrayOutputStream() ) {
MidiSystem.write(sequence, 1, out);
return out.toByteArray();
* @return トラックモデル(見つからない場合null)
*/
public MidiEventTableModel getSelectedTrackModel(ListSelectionModel selectionModel) {
- if( sequence == null || selectionModel.isSelectionEmpty() ) return null;
- Track tracks[] = sequence.getTracks();
- if( tracks.length == 0 ) return null;
- Track t = tracks[selectionModel.getMinSelectionIndex()];
+ if( isEmpty() || selectionModel.isSelectionEmpty() ) return null;
+ Track t = sequence.getTracks()[selectionModel.getMinSelectionIndex()];
return trackModelList.stream().filter(tm -> tm.getTrack() == t).findFirst().orElse(null);
}
/**
* @return 追加したトラックのインデックス(先頭 0)
*/
public int createTrack() {
+ int newIndex = getRowCount();
trackModelList.add(new MidiEventTableModel(this, sequence.createTrack()));
setModified(true);
- int newIndex = getRowCount() - 1;
fireTableRowsInserted(newIndex, newIndex);
return newIndex;
}
*/
public static byte[] getNameBytesOf(Sequence sequence) {
return Arrays.stream(sequence.getTracks()).map(t->getNameBytesOf(t))
- .filter(Objects::nonNull).findFirst().orElse(null);
+ .filter(Objects::nonNull).findFirst().orElse(null);
}
/**
* シーケンス名のバイト列を設定します。
- * <p>å\85\88é ã\81®ã\83\88ã\83©ã\83\83ã\82¯ã\81«è¨å®\9aã\81\95ã\82\8cã\81¾ã\81\99ã\80\82è¨å®\9aã\81«å¤±æ\95\97ã\81\97ã\81\9få ´å\90\88ã\80\81é \86ã\81«æ¬¡ã\81®ã\83\88ã\83©ã\83\83ã\82¯ã\81¸ã\81®è¨å®\9aã\82\92試ã\81¿ます。
+ * <p>å\85\88é ã\81®ã\83\88ã\83©ã\83\83ã\82¯ã\81\8bã\82\89é \86ã\81«è¨å®\9aã\82\92試ã\81¿ã\80\81æ\88\90å\8a\9fã\81\97ã\81\9fã\81¨ã\81\93ã\82\8dã\81§trueã\82\92è¿\94ã\81\97ます。
* </p>
*
* @param sequence MIDIシーケンス
* @return 成功:true、失敗:false
*/
public static boolean setNameBytesOf(Sequence sequence, byte[] name) {
- return Arrays.stream(sequence.getTracks()).anyMatch(t->setNameBytesOf(t,name));
+ return sequence != null && Arrays.stream(sequence.getTracks())
+ .anyMatch(t->setNameBytesOf(t,name));
}
/**
* 指定されたMIDIシーケンスからメタイベントのテキスト(名前や歌詞など)を検索し、その文字コードを判定します。