OSDN Git Service

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