OSDN Git Service

・MIDIデバイスダイアログに時間リセットボタン追加
[midichordhelper/MIDIChordHelper.git] / src / camidion / chordhelper / mididevice / MidiSequencerModel.java
1 package camidion.chordhelper.mididevice;
2
3 import java.awt.event.ActionEvent;
4 import java.awt.event.ActionListener;
5 import java.util.HashMap;
6 import java.util.Map;
7
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.ListDataEvent;
21 import javax.swing.event.ListDataListener;
22
23 import camidion.chordhelper.ButtonIcon;
24 import camidion.chordhelper.midieditor.SequenceTickIndex;
25 import camidion.chordhelper.midieditor.SequenceTrackListTableModel;
26 import camidion.chordhelper.midieditor.SequencerSpeedSlider;
27
28 /**
29  * MIDIシーケンサモデル
30  */
31 public class MidiSequencerModel extends MidiTransceiverListModel implements BoundedRangeModel {
32         /**
33          * MIDIシーケンサモデルを構築します。
34          * @param sequencer シーケンサーMIDIデバイス
35          * @param deviceModelList 親のMIDIデバイスモデルリスト
36          */
37         public MidiSequencerModel(Sequencer sequencer, MidiTransceiverListModelList deviceModelList) {
38                 super(sequencer, deviceModelList);
39         }
40         /**
41          * このシーケンサーの再生スピード調整モデル
42          */
43         public BoundedRangeModel speedSliderModel = new DefaultBoundedRangeModel(0, 0, -7, 7) {{
44                 addChangeListener(new ChangeListener() {
45                         @Override
46                         public void stateChanged(ChangeEvent e) {
47                                 getSequencer().setTempoFactor(SequencerSpeedSlider.tempoFactorOf(getValue()));
48                         }
49                 });
50         }};
51         /**
52          * MIDIシーケンサを返します。
53          * @return MIDIシーケンサ
54          */
55         public Sequencer getSequencer() { return (Sequencer)device; }
56         /**
57          * 開始終了アクション
58          */
59         public StartStopAction startStopAction = new StartStopAction();
60         /**
61          * 開始終了アクション
62          */
63         class StartStopAction extends AbstractAction {
64                 private Map<Boolean,Icon> iconMap = new HashMap<Boolean,Icon>() {
65                         {
66                                 put(Boolean.FALSE, new ButtonIcon(ButtonIcon.PLAY_ICON));
67                                 put(Boolean.TRUE, new ButtonIcon(ButtonIcon.PAUSE_ICON));
68                         }
69                 };
70                 {
71                         putValue(
72                                 SHORT_DESCRIPTION,
73                                 "Start/Stop recording or playing - 録音または再生の開始/停止"
74                         );
75                         setRunning(false);
76                 }
77                 @Override
78                 public void actionPerformed(ActionEvent event) {
79                         if(timeRangeUpdater.isRunning()) stop(); else start();
80                 }
81                 /**
82                  * 開始されているかどうかを設定します。
83                  * @param isRunning 開始されていたらtrue
84                  */
85                 private void setRunning(boolean isRunning) {
86                         putValue(LARGE_ICON_KEY, iconMap.get(isRunning));
87                         putValue(SELECTED_KEY, isRunning);
88                 }
89         }
90         /**
91          * シーケンサに合わせてミリ秒位置を更新するタイマー
92          */
93         private javax.swing.Timer timeRangeUpdater = new javax.swing.Timer( 20,
94                 new ActionListener(){
95                         @Override
96                         public void actionPerformed(ActionEvent e) {
97                                 if( valueIsAdjusting || ! getSequencer().isRunning() ) {
98                                         // 手動で移動中の場合や、シーケンサが止まっている場合は、
99                                         // タイマーによる更新は不要
100                                         return;
101                                 }
102                                 // リスナーに読み込みを促す
103                                 fireStateChanged();
104                         }
105                 }
106         );
107         /**
108          * このモデルのMIDIシーケンサを開始します。
109          *
110          * <p>録音するMIDIチャンネルがMIDIエディタで指定されている場合、
111          * 録音スタート時のタイムスタンプが正しく0になるよう、
112          * 各MIDIデバイスのタイムスタンプをすべてリセットします。
113          * </p>
114          */
115         public void start() {
116                 Sequencer sequencer = getSequencer();
117                 if( ! sequencer.isOpen() || sequencer.getSequence() == null ) {
118                         timeRangeUpdater.stop();
119                         startStopAction.setRunning(false);
120                         return;
121                 }
122                 startStopAction.setRunning(true);
123                 timeRangeUpdater.start();
124                 SequenceTrackListTableModel sequenceTableModel = getSequenceTrackListTableModel();
125                 if( sequenceTableModel != null && sequenceTableModel.hasRecordChannel() ) {
126                         deviceModelList.resetMicrosecondPosition();
127                         System.gc();
128                         sequencer.startRecording();
129                 }
130                 else {
131                         System.gc();
132                         sequencer.start();
133                 }
134                 fireStateChanged();
135         }
136         /**
137          * このモデルのMIDIシーケンサを停止します。
138          */
139         public void stop() {
140                 Sequencer sequencer = getSequencer();
141                 if(sequencer.isOpen()) sequencer.stop();
142                 timeRangeUpdater.stop();
143                 startStopAction.setRunning(false);
144                 fireStateChanged();
145         }
146         /**
147          * {@link Sequencer#getMicrosecondLength()} と同じです。
148          * @return マイクロ秒単位でのシーケンスの長さ
149          */
150         public long getMicrosecondLength() {
151                 //
152                 // Sequencer.getMicrosecondLength() returns NEGATIVE value
153                 //  when over 0x7FFFFFFF microseconds (== 35.7913941166666... minutes),
154                 //  should be corrected when negative
155                 //
156                 long usLength = getSequencer().getMicrosecondLength();
157                 return usLength < 0 ? 0x100000000L + usLength : usLength ;
158         }
159         @Override
160         public int getMaximum() { return (int)(getMicrosecondLength()/1000L); }
161         @Override
162         public void setMaximum(int newMaximum) {}
163         @Override
164         public int getMinimum() { return 0; }
165         @Override
166         public void setMinimum(int newMinimum) {}
167         @Override
168         public int getExtent() { return 0; }
169         @Override
170         public void setExtent(int newExtent) {}
171         /**
172          * {@link Sequencer#getMicrosecondPosition()} と同じです。
173          * @return マイクロ秒単位での現在の位置
174          */
175         public long getMicrosecondPosition() {
176                 long usPosition = getSequencer().getMicrosecondPosition();
177                 return usPosition < 0 ? 0x100000000L + usPosition : usPosition ;
178         }
179         @Override
180         public int getValue() { return (int)(getMicrosecondPosition()/1000L); }
181         @Override
182         public void setValue(int newValue) {
183                 getSequencer().setMicrosecondPosition(1000L * (long)newValue);
184                 fireStateChanged();
185         }
186         /**
187          * 値調整中のときtrue
188          */
189         private boolean valueIsAdjusting = false;
190         @Override
191         public boolean getValueIsAdjusting() { return valueIsAdjusting; }
192         @Override
193         public void setValueIsAdjusting(boolean valueIsAdjusting) {
194                 this.valueIsAdjusting = valueIsAdjusting;
195         }
196         @Override
197         public void setRangeProperties(int value, int extent, int min, int max, boolean valueIsAdjusting) {
198                 getSequencer().setMicrosecondPosition(1000L * (long)value);
199                 setValueIsAdjusting(valueIsAdjusting);
200                 fireStateChanged();
201         }
202         /**
203          * {@inheritDoc}
204          * <p>このシーケンサーの再生時間位置または再生対象ファイルが変更されたときに
205          * 通知を受けるリスナーを追加します。
206          * </p>
207          */
208         @Override
209         public void addChangeListener(ChangeListener listener) {
210                 listenerList.add(ChangeListener.class, listener);
211         }
212         /**
213          * {@inheritDoc}
214          * <p>このシーケンサーの再生時間位置または再生対象ファイルが変更されたときに
215          * 通知を受けるリスナーを除去します。
216          * </p>
217          */
218         @Override
219         public void removeChangeListener(ChangeListener listener) {
220                 listenerList.remove(ChangeListener.class, listener);
221         }
222         /**
223          * 秒位置が変わったことをリスナーに通知します。
224          * <p>登録中のすべての {@link ChangeListener} について
225          * {@link ChangeListener#stateChanged(ChangeEvent)}
226          * を呼び出すことによって状態の変化を通知します。
227          * </p>
228          */
229         public void fireStateChanged() {
230                 Object[] listeners = listenerList.getListenerList();
231                 for (int i = listeners.length-2; i>=0; i-=2) {
232                         if (listeners[i]==ChangeListener.class) {
233                                 ((ChangeListener)listeners[i+1]).stateChanged(new ChangeEvent(this));
234                         }
235                 }
236         }
237         /**
238          * MIDIトラックリストテーブルモデル
239          */
240         private SequenceTrackListTableModel sequenceTableModel = null;
241         /**
242          * このシーケンサーに現在ロードされているシーケンスのMIDIトラックリストテーブルモデルを返します。
243          * @return MIDIトラックリストテーブルモデル(何もロードされていなければnull)
244          */
245         public SequenceTrackListTableModel getSequenceTrackListTableModel() {
246                 return sequenceTableModel;
247         }
248         /**
249          * MIDIトラックリストテーブルモデルをこのシーケンサーモデルにセットします。
250          * nullを指定してアンセットすることもできます。
251          * @param sequenceTableModel MIDIトラックリストテーブルモデル
252          * @throws InvalidMidiDataException {@link Sequencer#setSequence(Sequence)} を参照
253          */
254         public void setSequenceTrackListTableModel(SequenceTrackListTableModel sequenceTableModel)
255                 throws InvalidMidiDataException
256         {
257                 // javax.sound.midi:Sequencer.setSequence() のドキュメントにある
258                 // 「このメソッドは、Sequencer が閉じている場合でも呼び出すことができます。 」
259                 // という記述は、null をセットする場合には当てはまらない。
260                 // 連鎖的に stop() が呼ばれるために IllegalStateException sequencer not open が出る。
261                 // この現象を回避するため、あらかじめチェックしてから setSequence() を呼び出している。
262                 //
263                 if( sequenceTableModel != null || getSequencer().isOpen() ) {
264                         getSequencer().setSequence(sequenceTableModel == null ? null : sequenceTableModel.getSequence());
265                 }
266                 if( this.sequenceTableModel != null ) this.sequenceTableModel.fireTableDataChanged();
267                 if( sequenceTableModel != null ) sequenceTableModel.fireTableDataChanged();
268                 this.sequenceTableModel = sequenceTableModel;
269                 fireStateChanged();
270         }
271         /**
272          * 小節単位で位置を移動します。
273          * @param measureOffset 何小節進めるか(戻したいときは負数を指定)
274          */
275         private void moveMeasure(int measureOffset) {
276                 if( measureOffset == 0 || sequenceTableModel == null ) return;
277                 SequenceTickIndex seqIndex = sequenceTableModel.getSequenceTickIndex();
278                 Sequencer sequencer = getSequencer();
279                 int measurePosition = seqIndex.tickToMeasure(sequencer.getTickPosition());
280                 long newTickPosition = seqIndex.measureToTick(measurePosition + measureOffset);
281                 if( newTickPosition < 0 ) {
282                         // 下限
283                         newTickPosition = 0;
284                 }
285                 else {
286                         long tickLength = sequencer.getTickLength();
287                         if( newTickPosition > tickLength ) {
288                                 // 上限
289                                 newTickPosition = tickLength - 1;
290                         }
291                 }
292                 sequencer.setTickPosition(newTickPosition);
293                 fireStateChanged();
294         }
295         /**
296          * 1小節戻るアクション
297          */
298         public Action moveBackwardAction = new AbstractAction() {
299                 {
300                         putValue(SHORT_DESCRIPTION, "Move backward 1 measure - 1小節戻る");
301                         putValue(LARGE_ICON_KEY, new ButtonIcon(ButtonIcon.BACKWARD_ICON));
302                 }
303                 @Override
304                 public void actionPerformed(ActionEvent event) { moveMeasure(-1); }
305         };
306         /**
307          *1小節進むアクション
308          */
309         public Action moveForwardAction = new AbstractAction() {
310                 {
311                         putValue(SHORT_DESCRIPTION, "Move forward 1 measure - 1小節進む");
312                         putValue(LARGE_ICON_KEY, new ButtonIcon(ButtonIcon.FORWARD_ICON));
313                 }
314                 @Override
315                 public void actionPerformed(ActionEvent event) { moveMeasure(1); }
316         };
317         /**
318          * マスター同期モードのコンボボックスモデル
319          */
320         public ComboBoxModel<Sequencer.SyncMode> masterSyncModeModel =
321                 new DefaultComboBoxModel<Sequencer.SyncMode>(getSequencer().getMasterSyncModes()) {{
322                         addListDataListener(new ListDataListener() {
323                                 @Override
324                                 public void intervalAdded(ListDataEvent e) { }
325                                 @Override
326                                 public void intervalRemoved(ListDataEvent e) { }
327                                 @Override
328                                 public void contentsChanged(ListDataEvent e) {
329                                         getSequencer().setMasterSyncMode((Sequencer.SyncMode)getSelectedItem());
330                                 }
331                         });
332                 }};
333         /**
334          * スレーブ同期モードのコンボボックスモデル
335          */
336         public ComboBoxModel<Sequencer.SyncMode> slaveSyncModeModel =
337                 new DefaultComboBoxModel<Sequencer.SyncMode>(getSequencer().getSlaveSyncModes()) {{
338                         addListDataListener(new ListDataListener() {
339                                 @Override
340                                 public void intervalAdded(ListDataEvent e) { }
341                                 @Override
342                                 public void intervalRemoved(ListDataEvent e) { }
343                                 @Override
344                                 public void contentsChanged(ListDataEvent e) {
345                                         getSequencer().setSlaveSyncMode((Sequencer.SyncMode)getSelectedItem());
346                                 }
347                         });
348                 }};
349 }