import java.awt.Dimension;
import java.awt.Image;
import java.awt.Insets;
-import java.awt.event.ActionEvent;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.InputEvent;
return addToPlaylistBase64(base64EncodedText, null);
}
/**
- * ファイル名を指定して、
- * Base64エンコードされたMIDIファイルをプレイリストへ追加します。
+ * ファイル名を指定して、Base64エンコードされたMIDIファイルをプレイリストへ追加します。
*
* @param base64EncodedText Base64エンコードされたMIDIファイル
* @param filename ディレクトリ名を除いたファイル名
* @return MIDIファイル名(設定されていないときは空文字列)
*/
public String getMidiFilename() {
- SequenceTrackListTableModel seq_model = sequencerModel.getSequenceTrackListTableModel();
- if( seq_model == null ) return null;
- String fn = seq_model.getFilename();
- return fn == null ? "" : fn ;
+ SequenceTrackListTableModel s = sequencerModel.getSequenceTrackListTableModel();
+ if( s == null ) return null;
+ String fn = s.getFilename();
+ if( fn == null ) return null;
+ return fn;
}
/**
* オクターブ位置を設定します。
*/
public static class VersionInfo {
public static final String NAME = "MIDI Chord Helper";
- public static final String VERSION = "Ver.20170326.1";
+ public static final String VERSION = "Ver.20170327.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/";
// MIDIエディタのイベントダイアログを、ピアノ鍵盤のイベント送出ダイアログと共用
keyboardPanel.setEventDialog(midiEditor.eventDialog);
//
- // 歌詞表示
- (lyricDisplay = new ChordTextField(sequencerModel)).addActionListener((ActionEvent e)->{
- chordMatrix.setSelectedChord(e.getActionCommand().trim().split("[ \t\r\n]")[0]);
- });
+ // 歌詞表示/コード入力フィールド
+ (lyricDisplay = new ChordTextField(sequencerModel)).addActionListener(
+ e->chordMatrix.setSelectedChord(
+ e.getActionCommand().trim().split("[ \t\r\n]")[0]
+ )
+ );
lyricDisplayDefaultBorder = lyricDisplay.getBorder();
lyricDisplayDefaultBgcolor = lyricDisplay.getBackground();
//
add( enterButtonLabel = new ChordButtonLabel("Enter",chordMatrix) {{
addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent event) {
- if( (event.getModifiersEx() & InputEvent.BUTTON3_DOWN_MASK) != 0 ) // RightClicked
+ boolean rightClicked = (event.getModifiersEx() & InputEvent.BUTTON3_DOWN_MASK) != 0;
+ if( rightClicked )
chordMatrix.setSelectedChord((Chord)null);
- else {
+ else
chordMatrix.setSelectedChord(lyricDisplay.getText());
- }
}
});
}});
add( anoGakkiToggleButton = new JToggleButton(new ButtonIcon(ButtonIcon.ANO_GAKKI_ICON)) {{
setOpaque(false);
setMargin(ZERO_INSETS);
- setBorder( null );
+ setBorder(null);
setToolTipText("あの楽器");
addItemListener(
e -> keyboardPanel.keyboardCenterPanel.keyboard.anoGakkiPane
}
@Override
public void destroy() {
- deviceTreeModel.closeAllDevices();
+ deviceTreeModel.forEach(m -> m.close());
super.destroy();
}
@Override
* コードを文字列で設定します。
* @param chordSymbol コード名
*/
- public void setSelectedChord(String chordSymbol) throws IllegalArgumentException {
+ public void setSelectedChord(String chordSymbol) {
Chord chord = null;
if( chordSymbol != null && ! chordSymbol.isEmpty() ) {
try {
import java.beans.PropertyVetoException;
import java.util.HashMap;
import java.util.Map;
+import java.util.Objects;
import java.util.stream.Collectors;
import javax.sound.midi.MidiUnavailableException;
frameOfModel.keySet().stream()
.filter(m-> ! deviceTreeModel.contains(m))
.collect(Collectors.toSet()).stream()
- .map(m-> frameOfModel.remove(m))
- .filter(f-> f != null)
+ .map(m-> frameOfModel.remove(m)).filter(Objects::nonNull)
.forEach(f-> remove(f));
//
// ツリーに追加されたMIDIデバイスモデルのフレームを生成
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
import java.util.Vector;
+import java.util.function.Function;
import java.util.stream.Collectors;
import javax.sound.midi.MidiDevice;
public class MidiDeviceTreeModel extends AbstractList<MidiDeviceModel> implements TreeModel {
@Override
public String toString() { return "MIDI devices"; }
-
+ /**
+ * リスト本体
+ */
protected List<MidiDeviceModel> deviceModelList = new Vector<>();
@Override
public int size() { return deviceModelList.size(); }
/**
* {@link AbstractList#add(E)}の操作を内部的に行います。
* 指定された要素をこのリストの最後に追加し、ツリー構造にも反映します。
- *
* @param dm 追加するMIDIデバイスモデル
* @return true({@link AbstractList#add(E)} と同様)
*/
*/
protected boolean removeAllInternally(Collection<?> c) {
if( ! deviceModelList.removeAll(c) ) return false;
- c.stream().filter(o -> o instanceof MidiDeviceModel).map(o -> (MidiDeviceModel)o)
- .forEach(mdm -> deviceModelTree.get(mdm.getInOutType()).remove(mdm));
+ c.stream().filter(o -> o instanceof MidiDeviceModel).forEach(
+ o -> deviceModelTree.get(((MidiDeviceModel)o).getInOutType()).remove(o)
+ );
return true;
}
@Override
// シーケンサー
try {
addInternally(sequencerModel = new MidiSequencerModel(MidiSystem.getSequencer(false), this));
- } catch( MidiUnavailableException e ) {
- System.out.println(ChordHelperApplet.VersionInfo.NAME +" : MIDI sequencer unavailable");
- e.printStackTrace();
+ } catch( MidiUnavailableException ex ) {
+ String title = ChordHelperApplet.VersionInfo.NAME;
+ String message = "MIDI sequencer unavailable\n" + ex;
+ JOptionPane.showMessageDialog(null, message, title, JOptionPane.ERROR_MESSAGE);
}
// システムで使用可能な全MIDIデバイス(シーケンサーはすでに取得済みなので除外)
for( MidiDevice device : getMidiDeviceInfo().stream().map(info -> {
try {
return MidiSystem.getMidiDevice(info);
- } catch( MidiUnavailableException e ) {
- e.printStackTrace();
+ } catch( MidiUnavailableException ex ) {
+ String title = ChordHelperApplet.VersionInfo.NAME;
+ String message = "MIDI device '" + info + "' unavailable\n" + ex;
+ JOptionPane.showMessageDialog(null, message, title, JOptionPane.ERROR_MESSAGE);
return null;
}
}).filter(
if( device instanceof Synthesizer ) { // Java内蔵シンセサイザの場合
try {
addInternally(synthModel = new MidiDeviceModel(MidiSystem.getSynthesizer(), this));
- } catch( MidiUnavailableException e ) {
- System.out.println(ChordHelperApplet.VersionInfo.NAME +
- " : Java internal MIDI synthesizer unavailable");
- e.printStackTrace();
+ } catch( MidiUnavailableException ex ) {
+ String title = ChordHelperApplet.VersionInfo.NAME;
+ String message = "Java internal MIDI synthesizer unavailable\n" + ex;
+ JOptionPane.showMessageDialog(null, message, title, JOptionPane.ERROR_MESSAGE);
}
continue;
}
//
addInternally(m);
}
- // MIDIデバイスモデルを開く
+ // MIDIデバイスモデルを開く。
//
// NOTE: 必ず MIDI OUT Rx デバイスを先に開くこと。
//
//
// 開く順序が逆になると「進みすぎるから遅らせよう」として無用なレイテンシーが発生する原因になる。
//
- List<MidiDeviceModel> openedMidiDeviceModelList = new ArrayList<>();
- Arrays.asList(
+ List<MidiDeviceModel> openedDeviceModels = Arrays.asList(
synthModel,
firstMidiOutModel,
sequencerModel,
guiModel,
firstMidiInModel
- ).stream().filter(mdm -> mdm != null).forEach(mdm->{
+ ).stream().filter(Objects::nonNull).filter(dm->{
try {
- mdm.open();
- openedMidiDeviceModelList.add(mdm);
+ dm.open();
+ return true;
} catch( MidiUnavailableException ex ) {
String title = ChordHelperApplet.VersionInfo.NAME;
- String message = "Cannot open MIDI device '"+mdm+"'\n"
- + "MIDIデバイス "+mdm+" を開くことができません。\n\n" + ex;
+ String message = "Cannot open MIDI device '"+dm+"'\n"
+ + "MIDIデバイス "+dm+" を開くことができません。\n\n" + ex;
JOptionPane.showMessageDialog(null, message, title, JOptionPane.ERROR_MESSAGE);
+ return false;
}
- });
- // 初期接続マップを作成(開いたデバイスを相互に接続する)
+ }).collect(Collectors.toList());
+ //
+ // 開いたデバイスを相互に接続する。
// 自身のTx/Rx同士の接続は、シーケンサーモデルはなし、それ以外(GUIデバイスモデル)はあり。
- Map<MidiDeviceModel, Collection<MidiDeviceModel>> initialConnection = new LinkedHashMap<>();
- openedMidiDeviceModelList.stream().filter(rxm ->
- rxm.getReceiverListModel() != null
- ).forEach(rxm -> {
- List<MidiDeviceModel> txmList;
- initialConnection.put(rxm, txmList = new ArrayList<>());
- openedMidiDeviceModelList.stream().filter(txm ->
- txm.getTransmitterListModel() != null
- && !(txm == sequencerModel && txm == rxm)
- ).forEach(txm -> txmList.add(txm));
- });
- // 初期接続を実行
- connectDevices(initialConnection);
- }
- /**
- * すべてのMIDIデバイスを閉じます。
- */
- public void closeAllDevices() {
- deviceModelList.forEach(m -> m.getMidiDevice().close());
+ connectDevices(
+ openedDeviceModels.stream().filter(rxm->
+ Objects.nonNull(rxm.getReceiverListModel())
+ ).collect(Collectors.toMap(Function.identity(), rxm->
+ openedDeviceModels.stream().filter(txm->
+ Objects.nonNull(txm.getTransmitterListModel())
+ && !(txm == sequencerModel && txm == rxm)
+ ).collect(Collectors.toList())
+ ))
+ );
}
/**
* デバイス間の接続をすべて切断します。
* </ul>
*/
public void connectDevices(Map<MidiDeviceModel, Collection<MidiDeviceModel>> rxToTxConnections) {
- rxToTxConnections.keySet().stream().filter(rxm -> rxm != null).forEach(rxm -> {
+ rxToTxConnections.keySet().stream().forEach(rxm -> {
Receiver rx = rxm.getReceiverListModel().getTransceivers().get(0);
- rxToTxConnections.get(rxm).stream().filter(txm -> txm != null).forEach(txm -> {
+ rxToTxConnections.get(rxm).stream().filter(Objects::nonNull).forEach(txm -> {
try {
txm.getTransmitterListModel().openTransmitter().setReceiver(rx);
} catch( MidiUnavailableException e ) {
*/
public void resetMicrosecondPosition() {
deviceModelList.stream().map(dm -> dm.getTransmitterListModel())
- .filter(tlm -> tlm != null)
+ .filter(Objects::nonNull)
.forEach(tlm -> tlm.resetMicrosecondPosition());
}
package camidion.chordhelper.mididevice;
import java.util.List;
+import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
List<MidiDeviceModel> allDeviceModels = deviceModel.getDeviceTreeModel();
return allDeviceModels.stream().filter(
peer -> peer != deviceModel
- && peer.getTransmitterListModel() != null
+ && Objects.nonNull(peer.getTransmitterListModel())
&& ! getTransceivers().stream()
.map(rx -> peer.getTransmitterListModel().closeTransmittersFor(rx))
.flatMap(closedTxList -> closedTxList.stream())
package camidion.chordhelper.mididevice;
-import java.util.ArrayList;
-import java.util.Collection;
import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
import javax.sound.midi.MidiDevice;
import javax.sound.midi.MidiUnavailableException;
fireIntervalRemoved(this, index, index);
}
/**
- * 指定された複数の{@link Transmitter}を閉じ、要素数が大きく減ったことをこのモデルを参照しているビューへ通知します。
- *
- * @param txc このリストモデルで開いている{@link Transmitter}のコレクション
- */
- public void closeTransmitters(Collection<Transmitter> txc) {
- if( txc.isEmpty() ) return;
- int length = getSize();
- for( Transmitter tx : txc ) tx.close();
- fireIntervalRemoved(this, 0, length);
- }
- /**
* このリストモデルにある{@link Transmitter}のうち、
- * å¼\95æ\95°ã\81§æ\8c\87å®\9aã\81\95ã\82\8cã\81\9f{@link Receiver}ã\81¸ã\83\87ã\83¼ã\82¿ã\82\92é\80\81ä¿¡ã\81\97ã\81¦ã\81\84ã\82\8bã\82\82ã\81®ã\82\92æ\8e¢ã\81\97、
- * それらを{@link #closeTransmitters(Collection)}で閉じます。
- *
- * @return 閉じた{@link Transmitter}のリスト
+ * å¼\95æ\95°ã\81§æ\8c\87å®\9aã\81\95ã\82\8cã\81\9f{@link Receiver}ã\81¸ã\83\87ã\83¼ã\82¿ã\82\92é\80\81ä¿¡ã\81\97ã\81¦ã\81\84ã\82\8bã\82\82ã\81®ã\81\8cã\81\82ã\82\8cã\81°、
+ * それらをすべて閉じ、要素数が大きく減ったことをこのモデルを参照しているビューへ通知します。
+ * @param rx 対象の{@link Receiver}
+ * @return 閉じた{@link Transmitter}のリスト(一つも閉じられなかった場合は空のリスト)
*/
public List<Transmitter> closeTransmittersFor(Receiver rx) {
- List<Transmitter> txToClose = new ArrayList<Transmitter>();
- for( Transmitter tx : getTransceivers() ) if( tx.getReceiver() == rx ) txToClose.add(tx);
- closeTransmitters(txToClose);
+ List<Transmitter> txToClose = getTransceivers().stream()
+ .filter(tx -> tx.getReceiver() == rx)
+ .collect(Collectors.toList());
+ txToClose.forEach(tx -> tx.close());
+ if( ! txToClose.isEmpty() ) fireIntervalRemoved(this, 0, getSize());
return txToClose;
}
/**
if( device instanceof Sequencer || ! device.isOpen() ) return;
//
// 接続状態を保存
- // 自分Tx → 相手Rx
- List<Receiver> peerRxList = new ArrayList<Receiver>();
- for( Transmitter tx : device.getTransmitters() ) {
- Receiver rx = tx.getReceiver();
- if( rx != null ) peerRxList.add(rx);
- }
- // 自分Rx ← 相手Tx
- List<Transmitter> peerTxList = new ArrayList<Transmitter>();
- List<Receiver> rxList = device.getReceivers();
- if( ! rxList.isEmpty() ) {
- for( MidiDeviceModel m : deviceModel.getDeviceTreeModel() ) {
- if( m == deviceModel ) continue; // 「自分Rx ← 自分Tx」は重複するのでスキップ
- List<Transmitter> peerSourceTxList = m.getMidiDevice().getTransmitters();
- for( Transmitter tx : peerSourceTxList ) {
- for( Receiver rx : rxList ) if( tx.getReceiver() == rx ) peerTxList.add(tx);
- }
- }
- }
+ // 自分Tx → 相手Rx
+ List<Receiver> peerRxList = device.getTransmitters().stream()
+ .map(tx -> tx.getReceiver())
+ .filter(Objects::nonNull)
+ .collect(Collectors.toList());
+ // 自分Rx ← 相手Tx
+ List<Receiver> myRxList = device.getReceivers(); // 基本的に 0件 or 1件
+ List<Transmitter> peerTxList = deviceModel.getDeviceTreeModel().stream()
+ .filter(peer -> peer != deviceModel)
+ .flatMap(peer -> peer.getMidiDevice().getTransmitters().stream().filter(
+ peerTx -> myRxList.stream().anyMatch(myRx -> myRx == peerTx.getReceiver())
+ ))
+ .collect(Collectors.toList());
device.close(); // 一旦閉じる
try {
device.open(); // 再び開くことでマイクロ秒位置がリセットされる
- //
// 接続を復元
- // 自分Tx → 相手Rx
+ // 自分Tx → 相手Rx (例外キャッチのためあえてラムダ式にしていない)
for( Receiver peerRx : peerRxList ) openTransmitter().setReceiver(peerRx);
- // 自分Rx ← 相手Tx
- if( ! rxList.isEmpty() ) {
- for( Transmitter peerTx : peerTxList ) peerTx.setReceiver(rxList.get(0));
- }
+ // 自分Rx ← 相手Tx
+ if( ! myRxList.isEmpty() ) peerTxList.forEach(peerTx -> peerTx.setReceiver(myRxList.get(0)));
} catch( MidiUnavailableException e ) {
e.printStackTrace();
}
import java.awt.event.ActionEvent;
import java.nio.charset.Charset;
+import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
* 列の列挙型
*/
public enum Column {
- /** MIDIシーケンスの番号 */
- NUMBER("No.", Integer.class, 20),
+ NUMBER("#", Integer.class, 20),
/** 再生ボタン */
PLAY("Play/Stop", String.class, 60) {
@Override
public boolean isCellEditable() { return true; }
@Override
public Object getValueOf(SequenceTrackListTableModel sequenceModel) {
- String name = sequenceModel.toString();
- return name == null ? "" : name;
+ return sequenceModel.toString();
}
},
/** 文字コード */
},
/** タイミング分割形式 */
DIVISION_TYPE("DivType", String.class, 50) {
+ private Map<Float,String> labels;
+ {
+ Map<Float,String> m = new HashMap<Float,String>();
+ m.put(Sequence.PPQ, "PPQ");
+ m.put(Sequence.SMPTE_24, "SMPTE_24");
+ m.put(Sequence.SMPTE_25, "SMPTE_25");
+ m.put(Sequence.SMPTE_30, "SMPTE_30");
+ m.put(Sequence.SMPTE_30DROP, "SMPTE_30DROP");
+ labels = Collections.unmodifiableMap(m);
+ }
@Override
public Object getValueOf(SequenceTrackListTableModel sequenceModel) {
- float dt = sequenceModel.getSequence().getDivisionType();
- String dtLabel = divisionTypeLabels.get(dt);
- return dtLabel == null ? "[Unknown]" : dtLabel;
- }
- };
- /** タイミング分割形式に対応するラベル文字列 */
- private static final Map<Float,String> divisionTypeLabels = new HashMap<Float,String>() {
- {
- put(Sequence.PPQ, "PPQ");
- put(Sequence.SMPTE_24, "SMPTE_24");
- put(Sequence.SMPTE_25, "SMPTE_25");
- put(Sequence.SMPTE_30, "SMPTE_30");
- put(Sequence.SMPTE_30DROP, "SMPTE_30DROP");
+ String label = labels.get(sequenceModel.getSequence().getDivisionType());
+ return label == null ? "[Unknown]" : label;
}
};
String title;
switch(Column.values()[column]) {
case FILENAME:
// ファイル名の変更
- sequenceModelList.get(row).setFilename((String)val);
+ sequenceModelList.get(row).setFilename(val.toString());
fireTableCellUpdated(row, column);
break;
case NAME:
// シーケンス名の設定または変更
- if( sequenceModelList.get(row).setName((String)val) )
+ if( sequenceModelList.get(row).setName(val.toString()) )
fireTableCellUpdated(row, Column.MODIFIED.ordinal());
fireTableCellUpdated(row, column);
break;
* 列の列挙型
*/
public enum Column {
- /** トラック番号 */
- TRACK_NUMBER("No.", Integer.class, 20),
- /** イベント数 */
+ TRACK_NUMBER("#", Integer.class, 20),
EVENTS("Events", Integer.class, 40),
- /** Mute */
MUTE("Mute", Boolean.class, 30),
- /** Solo */
SOLO("Solo", Boolean.class, 30),
- /** 録音するMIDIチャンネル */
RECORD_CHANNEL("RecCh", String.class, 40),
- /** MIDIチャンネル */
CHANNEL("Ch", String.class, 30),
- /** トラック名 */
TRACK_NAME("Track name", String.class, 100);
String title;
Class<?> columnClass;
/**
* MIDIファイル名
*/
- private String filename = "";
+ private String filename;
/**
* テキスト部分の文字コード(タイトル、歌詞など)
*/