1 package camidion.chordhelper.mididevice;
3 import java.awt.event.ActionEvent;
4 import java.awt.event.ActionListener;
5 import java.util.HashMap;
8 import javax.sound.midi.InvalidMidiDataException;
9 import javax.sound.midi.Sequence;
10 import javax.sound.midi.Sequencer;
11 import javax.swing.AbstractAction;
12 import javax.swing.Action;
13 import javax.swing.BoundedRangeModel;
14 import javax.swing.ComboBoxModel;
15 import javax.swing.DefaultBoundedRangeModel;
16 import javax.swing.DefaultComboBoxModel;
17 import javax.swing.Icon;
18 import javax.swing.event.ChangeEvent;
19 import javax.swing.event.ChangeListener;
20 import javax.swing.event.EventListenerList;
21 import javax.swing.event.ListDataEvent;
22 import javax.swing.event.ListDataListener;
24 import camidion.chordhelper.ButtonIcon;
25 import camidion.chordhelper.midieditor.SequenceTickIndex;
26 import camidion.chordhelper.midieditor.SequenceTrackListTableModel;
31 public class MidiSequencerModel extends MidiTransceiverListModel
32 implements BoundedRangeModel
36 * @param sequencer シーケンサーMIDIデバイス
37 * @param deviceModelList 親のMIDIデバイスモデルリスト
39 public MidiSequencerModel(Sequencer sequencer, MidiTransceiverListModelList deviceModelList) {
40 super(sequencer, deviceModelList);
43 * このシーケンサーの再生スピード調整モデル
45 public BoundedRangeModel speedSliderModel = new DefaultBoundedRangeModel(0, 0, -7, 7) {{
47 new ChangeListener() {
49 public void stateChanged(ChangeEvent e) {
51 getSequencer().setTempoFactor((float)(
52 val == 0 ? 1.0 : Math.pow( 2.0, ((double)val)/12.0 )
62 public Sequencer getSequencer() { return (Sequencer)device; }
66 public StartStopAction startStopAction = new StartStopAction();
70 class StartStopAction extends AbstractAction {
71 private Map<Boolean,Icon> iconMap = new HashMap<Boolean,Icon>() {
73 put(Boolean.FALSE, new ButtonIcon(ButtonIcon.PLAY_ICON));
74 put(Boolean.TRUE, new ButtonIcon(ButtonIcon.PAUSE_ICON));
80 "Start/Stop recording or playing - 録音または再生の開始/停止"
85 public void actionPerformed(ActionEvent event) {
86 if(timeRangeUpdater.isRunning()) stop(); else start();
90 * @param isRunning 開始されていたらtrue
92 public void setRunning(boolean isRunning) {
93 putValue(LARGE_ICON_KEY, iconMap.get(isRunning));
94 putValue(SELECTED_KEY, isRunning);
98 * シーケンサに合わせてミリ秒位置を更新するタイマー
100 private javax.swing.Timer timeRangeUpdater = new javax.swing.Timer(
102 new ActionListener(){
104 public void actionPerformed(ActionEvent e) {
105 if( valueIsAdjusting || ! getSequencer().isRunning() ) {
106 // 手動で移動中の場合や、シーケンサが止まっている場合は、
116 * このモデルのMIDIシーケンサを開始します。
118 * <p>録音するMIDIチャンネルがMIDIエディタで指定されている場合、
119 * 録音スタート時のタイムスタンプが正しく0になるよう、
120 * 各MIDIデバイスのタイムスタンプをすべてリセットします。
123 public void start() {
124 Sequencer sequencer = getSequencer();
125 if( ! sequencer.isOpen() || sequencer.getSequence() == null ) {
126 timeRangeUpdater.stop();
127 startStopAction.setRunning(false);
130 startStopAction.setRunning(true);
131 timeRangeUpdater.start();
132 SequenceTrackListTableModel sequenceTableModel = getSequenceTrackListTableModel();
133 if( sequenceTableModel != null && sequenceTableModel.hasRecordChannel() ) {
134 for(MidiTransceiverListModel m : deviceModelList) m.resetMicrosecondPosition();
136 sequencer.startRecording();
145 * このモデルのMIDIシーケンサを停止します。
148 Sequencer sequencer = getSequencer();
149 if(sequencer.isOpen()) sequencer.stop();
150 timeRangeUpdater.stop();
151 startStopAction.setRunning(false);
155 * {@link Sequencer#getMicrosecondLength()} と同じです。
156 * @return マイクロ秒単位でのシーケンスの長さ
158 public long getMicrosecondLength() {
160 // Sequencer.getMicrosecondLength() returns NEGATIVE value
161 // when over 0x7FFFFFFF microseconds (== 35.7913941166666... minutes),
162 // should be corrected when negative
164 long usLength = getSequencer().getMicrosecondLength();
165 return usLength < 0 ? 0x100000000L + usLength : usLength ;
168 public int getMaximum() { return (int)(getMicrosecondLength()/1000L); }
170 public void setMaximum(int newMaximum) {}
172 public int getMinimum() { return 0; }
174 public void setMinimum(int newMinimum) {}
176 public int getExtent() { return 0; }
178 public void setExtent(int newExtent) {}
180 * {@link Sequencer#getMicrosecondPosition()} と同じです。
181 * @return マイクロ秒単位での現在の位置
183 public long getMicrosecondPosition() {
184 long usPosition = getSequencer().getMicrosecondPosition();
185 return usPosition < 0 ? 0x100000000L + usPosition : usPosition ;
188 public int getValue() { return (int)(getMicrosecondPosition()/1000L); }
190 public void setValue(int newValue) {
191 getSequencer().setMicrosecondPosition(1000L * (long)newValue);
197 private boolean valueIsAdjusting = false;
199 public boolean getValueIsAdjusting() { return valueIsAdjusting; }
201 public void setValueIsAdjusting(boolean valueIsAdjusting) {
202 this.valueIsAdjusting = valueIsAdjusting;
205 public void setRangeProperties(int value, int extent, int min, int max, boolean valueIsAdjusting) {
206 getSequencer().setMicrosecondPosition(1000L * (long)value);
207 setValueIsAdjusting(valueIsAdjusting);
213 protected EventListenerList listenerList = new EventListenerList();
216 * <p>このシーケンサーの再生時間位置変更通知を受けるリスナーを追加します。
220 public void addChangeListener(ChangeListener listener) {
221 listenerList.add(ChangeListener.class, listener);
225 * <p>このシーケンサーの再生時間位置変更通知を受けるリスナーを除去します。
229 public void removeChangeListener(ChangeListener listener) {
230 listenerList.remove(ChangeListener.class, listener);
233 * 秒位置が変わったことをリスナーに通知します。
234 * <p>登録中のすべての {@link ChangeListener} について
235 * {@link ChangeListener#stateChanged(ChangeEvent)}
236 * を呼び出すことによって状態の変化を通知します。
239 public void fireStateChanged() {
240 Object[] listeners = listenerList.getListenerList();
241 for (int i = listeners.length-2; i>=0; i-=2) {
242 if (listeners[i]==ChangeListener.class) {
243 ((ChangeListener)listeners[i+1]).stateChanged(new ChangeEvent(this));
250 private SequenceTrackListTableModel sequenceTableModel = null;
252 * このシーケンサーに現在ロードされているシーケンスのMIDIトラックリストテーブルモデルを返します。
253 * @return MIDIトラックリストテーブルモデル(何もロードされていなければnull)
255 public SequenceTrackListTableModel getSequenceTrackListTableModel() {
256 return sequenceTableModel;
259 * MIDIトラックリストテーブルモデルを
260 * このシーケンサーモデルにセットします。
261 * @param sequenceTableModel MIDIトラックリストテーブルモデル
264 public boolean setSequenceTrackListTableModel(SequenceTrackListTableModel sequenceTableModel) {
265 // javax.sound.midi:Sequencer.setSequence() のドキュメントにある
266 // 「このメソッドは、Sequencer が閉じている場合でも呼び出すことができます。 」
267 // という記述は、null をセットする場合には当てはまらない。
268 // 連鎖的に stop() が呼ばれるために IllegalStateException sequencer not open が出る。
269 // この現象を回避するため、あらかじめチェックしてから setSequence() を呼び出している。
271 if( sequenceTableModel != null || getSequencer().isOpen() ) {
272 Sequence sequence = null;
273 if( sequenceTableModel != null )
274 sequence = sequenceTableModel.getSequence();
276 getSequencer().setSequence(sequence);
277 } catch ( InvalidMidiDataException e ) {
282 if( this.sequenceTableModel != null ) {
283 this.sequenceTableModel.fireTableDataChanged();
285 if( sequenceTableModel != null ) {
286 sequenceTableModel.fireTableDataChanged();
288 this.sequenceTableModel = sequenceTableModel;
295 * @param measureOffset 何小節進めるか(戻したいときは負数を指定)
297 private void moveMeasure(int measureOffset) {
298 if( measureOffset == 0 || sequenceTableModel == null )
300 SequenceTickIndex seqIndex = sequenceTableModel.getSequenceTickIndex();
301 Sequencer sequencer = getSequencer();
302 int measurePosition = seqIndex.tickToMeasure(sequencer.getTickPosition());
303 long newTickPosition = seqIndex.measureToTick(measurePosition + measureOffset);
304 if( newTickPosition < 0 ) {
309 long tickLength = sequencer.getTickLength();
310 if( newTickPosition > tickLength ) {
312 newTickPosition = tickLength - 1;
315 sequencer.setTickPosition(newTickPosition);
321 public Action moveBackwardAction = new AbstractAction() {
323 putValue(SHORT_DESCRIPTION, "Move backward 1 measure - 1小節戻る");
324 putValue(LARGE_ICON_KEY, new ButtonIcon(ButtonIcon.BACKWARD_ICON));
327 public void actionPerformed(ActionEvent event) { moveMeasure(-1); }
332 public Action moveForwardAction = new AbstractAction() {
334 putValue(SHORT_DESCRIPTION, "Move forward 1 measure - 1小節進む");
335 putValue(LARGE_ICON_KEY, new ButtonIcon(ButtonIcon.FORWARD_ICON));
338 public void actionPerformed(ActionEvent event) { moveMeasure(1); }
341 * マスター同期モードのコンボボックスモデル
343 public ComboBoxModel<Sequencer.SyncMode> masterSyncModeModel =
344 new DefaultComboBoxModel<Sequencer.SyncMode>(getSequencer().getMasterSyncModes()) {{
345 addListDataListener(new ListDataListener() {
347 public void intervalAdded(ListDataEvent e) { }
349 public void intervalRemoved(ListDataEvent e) { }
351 public void contentsChanged(ListDataEvent e) {
352 getSequencer().setMasterSyncMode((Sequencer.SyncMode)getSelectedItem());
357 * スレーブ同期モードのコンボボックスモデル
359 public ComboBoxModel<Sequencer.SyncMode> slaveSyncModeModel =
360 new DefaultComboBoxModel<Sequencer.SyncMode>(getSequencer().getSlaveSyncModes()) {{
361 addListDataListener(new ListDataListener() {
363 public void intervalAdded(ListDataEvent e) { }
365 public void intervalRemoved(ListDataEvent e) { }
367 public void contentsChanged(ListDataEvent e) {
368 getSequencer().setSlaveSyncMode((Sequencer.SyncMode)getSelectedItem());