10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26 package com.sun.media.sound;
27
28 import java.io.IOException;
29 import java.io.InputStream;
30
31 import java.util.ArrayList;
32 import java.util.List;
33 import java.util.Map;
34 import java.util.WeakHashMap;
35
36 import javax.sound.midi.*;
37
38
39 /**
40 * A Real Time Sequencer
41 *
42 * @author Florian Bomers
43 */
44
45 /* TODO:
46 * - rename PlayThread to PlayEngine (because isn't a thread)
47 */
48 final class RealTimeSequencer extends AbstractMidiDevice
49 implements Sequencer, AutoConnectSequencer {
50
51 // STATIC VARIABLES
52
53 /** debugging flags */
54 private static final boolean DEBUG_PUMP = false;
55 private static final boolean DEBUG_PUMP_ALL = false;
56
57 /**
58 * Event Dispatcher thread. Should be using a shared event
59 * dispatcher instance with a factory in EventDispatcher
60 */
61 private static final Map<ThreadGroup, EventDispatcher> dispatchers =
62 new WeakHashMap<>();
63
64 /**
65 * All RealTimeSequencers share this info object.
66 */
67 static final MidiDevice.Info info = new RealTimeSequencerInfo();
68
69
70 private static final Sequencer.SyncMode[] masterSyncModes = { Sequencer.SyncMode.INTERNAL_CLOCK };
71 private static final Sequencer.SyncMode[] slaveSyncModes = { Sequencer.SyncMode.NO_SYNC };
72
73 private static final Sequencer.SyncMode masterSyncMode = Sequencer.SyncMode.INTERNAL_CLOCK;
74 private static final Sequencer.SyncMode slaveSyncMode = Sequencer.SyncMode.NO_SYNC;
75
76
77 /**
78 * Sequence on which this sequencer is operating.
79 */
80 private Sequence sequence = null;
81
82 // caches
83
84 /**
85 * Same for setTempoInMPQ...
86 * -1 means not set.
87 */
88 private double cacheTempoMPQ = -1;
89
90
91 /**
92 * cache value for tempo factor until sequence is set
93 * -1 means not set.
94 */
95 private float cacheTempoFactor = -1;
96
97
98 /** if a particular track is muted */
99 private boolean[] trackMuted = null;
100 /** if a particular track is solo */
101 private boolean[] trackSolo = null;
102
103 /** tempo cache for getMicrosecondPosition */
104 private final MidiUtils.TempoCache tempoCache = new MidiUtils.TempoCache();
105
106 /**
107 * True if the sequence is running.
108 */
109 private volatile boolean running;
110
111
112 /** the thread for pushing out the MIDI messages */
113 private PlayThread playThread;
114
115
116 /**
117 * True if we are recording
118 */
119 private volatile boolean recording;
120
121
122 /**
123 * List of tracks to which we're recording
124 */
125 private final List<RecordingTrack> recordingTracks = new ArrayList<>();
126
127
128 private long loopStart = 0;
129 private long loopEnd = -1;
130 private int loopCount = 0;
131
132
133 /**
134 * Meta event listeners
135 */
136 private final ArrayList<Object> metaEventListeners = new ArrayList<>();
137
138
139 /**
140 * Control change listeners
141 */
142 private final ArrayList<ControllerListElement> controllerEventListeners = new ArrayList<>();
143
144
145 /** automatic connection support */
146 private boolean autoConnect = false;
147
148 /** if we need to autoconnect at next open */
149 private boolean doAutoConnectAtNextOpen = false;
150
151 /** the receiver that this device is auto-connected to */
152 Receiver autoConnectedReceiver = null;
153
154
155 /* ****************************** CONSTRUCTOR ****************************** */
156
157 RealTimeSequencer(){
158 super(info);
159
160 if (Printer.trace) Printer.trace(">> RealTimeSequencer CONSTRUCTOR");
161 if (Printer.trace) Printer.trace("<< RealTimeSequencer CONSTRUCTOR completed");
162 }
163
164
165 /* ****************************** SEQUENCER METHODS ******************** */
166
167 public synchronized void setSequence(Sequence sequence)
168 throws InvalidMidiDataException {
169
170 if (Printer.trace) Printer.trace(">> RealTimeSequencer: setSequence(" + sequence +")");
171
172 if (sequence != this.sequence) {
173 if (this.sequence != null && sequence == null) {
174 setCaches();
175 stop();
176 // initialize some non-cached values
177 trackMuted = null;
178 trackSolo = null;
179 loopStart = 0;
180 loopEnd = -1;
181 loopCount = 0;
182 if (getDataPump() != null) {
183 getDataPump().setTickPos(0);
184 getDataPump().resetLoopCount();
185 }
186 }
194 this.sequence = sequence;
195
196 if (sequence != null) {
197 tempoCache.refresh(sequence);
198 // rewind to the beginning
199 setTickPosition(0);
200 // propagate caches
201 propagateCaches();
202 }
203 }
204 else if (sequence != null) {
205 tempoCache.refresh(sequence);
206 if (playThread != null) {
207 playThread.setSequence(sequence);
208 }
209 }
210
211 if (Printer.trace) Printer.trace("<< RealTimeSequencer: setSequence(" + sequence +") completed");
212 }
213
214
215 public synchronized void setSequence(InputStream stream) throws IOException, InvalidMidiDataException {
216
217 if (Printer.trace) Printer.trace(">> RealTimeSequencer: setSequence(" + stream +")");
218
219 if (stream == null) {
220 setSequence((Sequence) null);
221 return;
222 }
223
224 Sequence seq = MidiSystem.getSequence(stream); // can throw IOException, InvalidMidiDataException
225
226 setSequence(seq);
227
228 if (Printer.trace) Printer.trace("<< RealTimeSequencer: setSequence(" + stream +") completed");
229
230 }
231
232
233 public Sequence getSequence() {
234 return sequence;
235 }
236
237
238 public synchronized void start() {
239 if (Printer.trace) Printer.trace(">> RealTimeSequencer: start()");
240
241 // sequencer not open: throw an exception
242 if (!isOpen()) {
243 throw new IllegalStateException("sequencer not open");
244 }
245
246 // sequence not available: throw an exception
247 if (sequence == null) {
248 throw new IllegalStateException("sequence not set");
249 }
250
251 // already running: return quietly
252 if (running == true) {
253 return;
254 }
255
256 // start playback
257 implStart();
258
259 if (Printer.trace) Printer.trace("<< RealTimeSequencer: start() completed");
260 }
261
262
263 public synchronized void stop() {
264 if (Printer.trace) Printer.trace(">> RealTimeSequencer: stop()");
265
266 if (!isOpen()) {
267 throw new IllegalStateException("sequencer not open");
268 }
269 stopRecording();
270
271 // not running; just return
272 if (running == false) {
273 if (Printer.trace) Printer.trace("<< RealTimeSequencer: stop() not running!");
274 return;
275 }
276
277 // stop playback
278 implStop();
279
280 if (Printer.trace) Printer.trace("<< RealTimeSequencer: stop() completed");
281 }
282
283
284 public boolean isRunning() {
285 return running;
286 }
287
288
289 public void startRecording() {
290 if (!isOpen()) {
291 throw new IllegalStateException("Sequencer not open");
292 }
293
294 start();
295 recording = true;
296 }
297
298
299 public void stopRecording() {
300 if (!isOpen()) {
301 throw new IllegalStateException("Sequencer not open");
302 }
303 recording = false;
304 }
305
306
307 public boolean isRecording() {
308 return recording;
309 }
310
311
312 public void recordEnable(Track track, int channel) {
313 if (!findTrack(track)) {
314 throw new IllegalArgumentException("Track does not exist in the current sequence");
315 }
316
317 synchronized(recordingTracks) {
318 RecordingTrack rc = RecordingTrack.get(recordingTracks, track);
319 if (rc != null) {
320 rc.channel = channel;
321 } else {
322 recordingTracks.add(new RecordingTrack(track, channel));
323 }
324 }
325
326 }
327
328
329 public void recordDisable(Track track) {
330 synchronized(recordingTracks) {
331 RecordingTrack rc = RecordingTrack.get(recordingTracks, track);
332 if (rc != null) {
333 recordingTracks.remove(rc);
334 }
335 }
336
337 }
338
339
340 private boolean findTrack(Track track) {
341 boolean found = false;
342 if (sequence != null) {
343 Track[] tracks = sequence.getTracks();
344 for (int i = 0; i < tracks.length; i++) {
345 if (track == tracks[i]) {
346 found = true;
347 break;
348 }
349 }
350 }
351 return found;
352 }
353
354
355 public float getTempoInBPM() {
356 if (Printer.trace) Printer.trace(">> RealTimeSequencer: getTempoInBPM() ");
357
358 return (float) MidiUtils.convertTempo(getTempoInMPQ());
359 }
360
361
362 public void setTempoInBPM(float bpm) {
363 if (Printer.trace) Printer.trace(">> RealTimeSequencer: setTempoInBPM() ");
364 if (bpm <= 0) {
365 // should throw IllegalArgumentException
366 bpm = 1.0f;
367 }
368
369 setTempoInMPQ((float) MidiUtils.convertTempo((double) bpm));
370 }
371
372
373 public float getTempoInMPQ() {
374 if (Printer.trace) Printer.trace(">> RealTimeSequencer: getTempoInMPQ() ");
375
376 if (needCaching()) {
377 // if the sequencer is closed, return cached value
378 if (cacheTempoMPQ != -1) {
379 return (float) cacheTempoMPQ;
380 }
381 // if sequence is set, return current tempo
382 if (sequence != null) {
383 return tempoCache.getTempoMPQAt(getTickPosition());
384 }
385
386 // last resort: return a standard tempo: 120bpm
387 return (float) MidiUtils.DEFAULT_TEMPO_MPQ;
388 }
389 return getDataPump().getTempoMPQ();
390 }
391
392
393 public void setTempoInMPQ(float mpq) {
394 if (mpq <= 0) {
395 // should throw IllegalArgumentException
396 mpq = 1.0f;
397 }
398
399 if (Printer.trace) Printer.trace(">> RealTimeSequencer: setTempoInMPQ() ");
400
401 if (needCaching()) {
402 // cache the value
403 cacheTempoMPQ = mpq;
404 } else {
405 // set the native tempo in MPQ
406 getDataPump().setTempoMPQ(mpq);
407
408 // reset the tempoInBPM and tempoInMPQ values so we won't use them again
409 cacheTempoMPQ = -1;
410 }
411 }
412
413
414 public void setTempoFactor(float factor) {
415 if (factor <= 0) {
416 // should throw IllegalArgumentException
417 return;
418 }
419
420 if (Printer.trace) Printer.trace(">> RealTimeSequencer: setTempoFactor() ");
421
422 if (needCaching()) {
423 cacheTempoFactor = factor;
424 } else {
425 getDataPump().setTempoFactor(factor);
426 // don't need cache anymore
427 cacheTempoFactor = -1;
428 }
429 }
430
431
432 public float getTempoFactor() {
433 if (Printer.trace) Printer.trace(">> RealTimeSequencer: getTempoFactor() ");
434
435 if (needCaching()) {
436 if (cacheTempoFactor != -1) {
437 return cacheTempoFactor;
438 }
439 return 1.0f;
440 }
441 return getDataPump().getTempoFactor();
442 }
443
444
445 public long getTickLength() {
446 if (Printer.trace) Printer.trace(">> RealTimeSequencer: getTickLength() ");
447
448 if (sequence == null) {
449 return 0;
450 }
451
452 return sequence.getTickLength();
453 }
454
455
456 public synchronized long getTickPosition() {
457 if (Printer.trace) Printer.trace(">> RealTimeSequencer: getTickPosition() ");
458
459 if (getDataPump() == null || sequence == null) {
460 return 0;
461 }
462
463 return getDataPump().getTickPos();
464 }
465
466
467 public synchronized void setTickPosition(long tick) {
468 if (tick < 0) {
469 // should throw IllegalArgumentException
470 return;
471 }
472
473 if (Printer.trace) Printer.trace(">> RealTimeSequencer: setTickPosition("+tick+") ");
474
475 if (getDataPump() == null) {
476 if (tick != 0) {
477 // throw new InvalidStateException("cannot set position in closed state");
478 }
479 }
480 else if (sequence == null) {
481 if (tick != 0) {
482 // throw new InvalidStateException("cannot set position if sequence is not set");
483 }
484 } else {
485 getDataPump().setTickPos(tick);
486 }
487 }
488
489
490 public long getMicrosecondLength() {
491 if (Printer.trace) Printer.trace(">> RealTimeSequencer: getMicrosecondLength() ");
492
493 if (sequence == null) {
494 return 0;
495 }
496
497 return sequence.getMicrosecondLength();
498 }
499
500
501 public long getMicrosecondPosition() {
502 if (Printer.trace) Printer.trace(">> RealTimeSequencer: getMicrosecondPosition() ");
503
504 if (getDataPump() == null || sequence == null) {
505 return 0;
506 }
507 synchronized (tempoCache) {
508 return MidiUtils.tick2microsecond(sequence, getDataPump().getTickPos(), tempoCache);
509 }
510 }
511
512
513 public void setMicrosecondPosition(long microseconds) {
514 if (microseconds < 0) {
515 // should throw IllegalArgumentException
516 return;
517 }
518
519 if (Printer.trace) Printer.trace(">> RealTimeSequencer: setMicrosecondPosition("+microseconds+") ");
520
521 if (getDataPump() == null) {
522 if (microseconds != 0) {
523 // throw new InvalidStateException("cannot set position in closed state");
524 }
525 }
526 else if (sequence == null) {
527 if (microseconds != 0) {
528 // throw new InvalidStateException("cannot set position if sequence is not set");
529 }
530 } else {
531 synchronized(tempoCache) {
532 setTickPosition(MidiUtils.microsecond2tick(sequence, microseconds, tempoCache));
533 }
534 }
535 }
536
537
538 public void setMasterSyncMode(Sequencer.SyncMode sync) {
539 // not supported
540 }
541
542
543 public Sequencer.SyncMode getMasterSyncMode() {
544 return masterSyncMode;
545 }
546
547
548 public Sequencer.SyncMode[] getMasterSyncModes() {
549 Sequencer.SyncMode[] returnedModes = new Sequencer.SyncMode[masterSyncModes.length];
550 System.arraycopy(masterSyncModes, 0, returnedModes, 0, masterSyncModes.length);
551 return returnedModes;
552 }
553
554
555 public void setSlaveSyncMode(Sequencer.SyncMode sync) {
556 // not supported
557 }
558
559
560 public Sequencer.SyncMode getSlaveSyncMode() {
561 return slaveSyncMode;
562 }
563
564
565 public Sequencer.SyncMode[] getSlaveSyncModes() {
566 Sequencer.SyncMode[] returnedModes = new Sequencer.SyncMode[slaveSyncModes.length];
567 System.arraycopy(slaveSyncModes, 0, returnedModes, 0, slaveSyncModes.length);
568 return returnedModes;
569 }
570
571 int getTrackCount() {
572 Sequence seq = getSequence();
573 if (seq != null) {
574 // $$fb wish there was a nicer way to get the number of tracks...
575 return sequence.getTracks().length;
576 }
577 return 0;
578 }
579
580
581
582 public synchronized void setTrackMute(int track, boolean mute) {
583 int trackCount = getTrackCount();
584 if (track < 0 || track >= getTrackCount()) return;
585 trackMuted = ensureBoolArraySize(trackMuted, trackCount);
586 trackMuted[track] = mute;
587 if (getDataPump() != null) {
588 getDataPump().muteSoloChanged();
589 }
590 }
591
592
593 public synchronized boolean getTrackMute(int track) {
594 if (track < 0 || track >= getTrackCount()) return false;
595 if (trackMuted == null || trackMuted.length <= track) return false;
596 return trackMuted[track];
597 }
598
599
600 public synchronized void setTrackSolo(int track, boolean solo) {
601 int trackCount = getTrackCount();
602 if (track < 0 || track >= getTrackCount()) return;
603 trackSolo = ensureBoolArraySize(trackSolo, trackCount);
604 trackSolo[track] = solo;
605 if (getDataPump() != null) {
606 getDataPump().muteSoloChanged();
607 }
608 }
609
610
611 public synchronized boolean getTrackSolo(int track) {
612 if (track < 0 || track >= getTrackCount()) return false;
613 if (trackSolo == null || trackSolo.length <= track) return false;
614 return trackSolo[track];
615 }
616
617
618 public boolean addMetaEventListener(MetaEventListener listener) {
619 synchronized(metaEventListeners) {
620 if (! metaEventListeners.contains(listener)) {
621
622 metaEventListeners.add(listener);
623 }
624 return true;
625 }
626 }
627
628
629 public void removeMetaEventListener(MetaEventListener listener) {
630 synchronized(metaEventListeners) {
631 int index = metaEventListeners.indexOf(listener);
632 if (index >= 0) {
633 metaEventListeners.remove(index);
634 }
635 }
636 }
637
638
639 public int[] addControllerEventListener(ControllerEventListener listener, int[] controllers) {
640 synchronized(controllerEventListeners) {
641
642 // first find the listener. if we have one, add the controllers
643 // if not, create a new element for it.
644 ControllerListElement cve = null;
645 boolean flag = false;
646 for(int i=0; i < controllerEventListeners.size(); i++) {
647
648 cve = controllerEventListeners.get(i);
649
650 if (cve.listener.equals(listener)) {
651 cve.addControllers(controllers);
652 flag = true;
653 break;
654 }
655 }
656 if (!flag) {
657 cve = new ControllerListElement(listener, controllers);
658 controllerEventListeners.add(cve);
659 }
660
661 // and return all the controllers this listener is interested in
662 return cve.getControllers();
663 }
664 }
665
666
667 public int[] removeControllerEventListener(ControllerEventListener listener, int[] controllers) {
668 synchronized(controllerEventListeners) {
669 ControllerListElement cve = null;
670 boolean flag = false;
671 for (int i=0; i < controllerEventListeners.size(); i++) {
672 cve = controllerEventListeners.get(i);
673 if (cve.listener.equals(listener)) {
674 cve.removeControllers(controllers);
675 flag = true;
676 break;
677 }
678 }
679 if (!flag) {
680 return new int[0];
681 }
682 if (controllers == null) {
683 int index = controllerEventListeners.indexOf(cve);
684 if (index >= 0) {
685 controllerEventListeners.remove(index);
686 }
687 return new int[0];
688 }
689 return cve.getControllers();
690 }
691 }
692
693
694 ////////////////// LOOPING (added in 1.5) ///////////////////////
695
696 public void setLoopStartPoint(long tick) {
697 if ((tick > getTickLength())
698 || ((loopEnd != -1) && (tick > loopEnd))
699 || (tick < 0)) {
700 throw new IllegalArgumentException("invalid loop start point: "+tick);
701 }
702 loopStart = tick;
703 }
704
705 public long getLoopStartPoint() {
706 return loopStart;
707 }
708
709 public void setLoopEndPoint(long tick) {
710 if ((tick > getTickLength())
711 || ((loopStart > tick) && (tick != -1))
712 || (tick < -1)) {
713 throw new IllegalArgumentException("invalid loop end point: "+tick);
714 }
715 loopEnd = tick;
716 }
717
718 public long getLoopEndPoint() {
719 return loopEnd;
720 }
721
722 public void setLoopCount(int count) {
723 if (count != LOOP_CONTINUOUSLY
724 && count < 0) {
725 throw new IllegalArgumentException("illegal value for loop count: "+count);
726 }
727 loopCount = count;
728 if (getDataPump() != null) {
729 getDataPump().resetLoopCount();
730 }
731 }
732
733 public int getLoopCount() {
734 return loopCount;
735 }
736
737
738 /* *********************************** play control ************************* */
739
740 /*
741 */
742 protected void implOpen() throws MidiUnavailableException {
743 if (Printer.trace) Printer.trace(">> RealTimeSequencer: implOpen()");
744
745 //openInternalSynth();
746
747 // create PlayThread
748 playThread = new PlayThread();
749
750 //id = nOpen();
751 //if (id == 0) {
752 // throw new MidiUnavailableException("unable to open sequencer");
753 //}
754 if (sequence != null) {
755 playThread.setSequence(sequence);
756 }
757
758 // propagate caches
759 propagateCaches();
760
761 if (doAutoConnectAtNextOpen) {
803 getTransmitter().setReceiver(rec);
804 } catch (Exception e) {}
805 }
806 if (Printer.trace) Printer.trace("<< RealTimeSequencer: doAutoConnect() succeeded");
807 }
808
809 private synchronized void propagateCaches() {
810 // only set caches if open and sequence is set
811 if (sequence != null && isOpen()) {
812 if (cacheTempoFactor != -1) {
813 setTempoFactor(cacheTempoFactor);
814 }
815 if (cacheTempoMPQ == -1) {
816 setTempoInMPQ((new MidiUtils.TempoCache(sequence)).getTempoMPQAt(getTickPosition()));
817 } else {
818 setTempoInMPQ((float) cacheTempoMPQ);
819 }
820 }
821 }
822
823 /** populate the caches with the current values */
824 private synchronized void setCaches() {
825 cacheTempoFactor = getTempoFactor();
826 cacheTempoMPQ = getTempoInMPQ();
827 }
828
829
830
831 protected synchronized void implClose() {
832 if (Printer.trace) Printer.trace(">> RealTimeSequencer: implClose() ");
833
834 if (playThread == null) {
835 if (Printer.err) Printer.err("RealTimeSequencer.implClose() called, but playThread not instanciated!");
836 } else {
837 // Interrupt playback loop.
838 playThread.close();
839 playThread = null;
840 }
841
842 super.implClose();
843
844 sequence = null;
845 running = false;
846 cacheTempoMPQ = -1;
847 cacheTempoFactor = -1;
848 trackMuted = null;
849 trackSolo = null;
850 loopStart = 0;
865
866 if (Printer.trace) Printer.trace("<< RealTimeSequencer: implClose() completed");
867 }
868
869 void implStart() {
870 if (Printer.trace) Printer.trace(">> RealTimeSequencer: implStart()");
871
872 if (playThread == null) {
873 if (Printer.err) Printer.err("RealTimeSequencer.implStart() called, but playThread not instanciated!");
874 return;
875 }
876
877 tempoCache.refresh(sequence);
878 if (!running) {
879 running = true;
880 playThread.start();
881 }
882 if (Printer.trace) Printer.trace("<< RealTimeSequencer: implStart() completed");
883 }
884
885
886 void implStop() {
887 if (Printer.trace) Printer.trace(">> RealTimeSequencer: implStop()");
888
889 if (playThread == null) {
890 if (Printer.err) Printer.err("RealTimeSequencer.implStop() called, but playThread not instanciated!");
891 return;
892 }
893
894 recording = false;
895 if (running) {
896 running = false;
897 playThread.stop();
898 }
899 if (Printer.trace) Printer.trace("<< RealTimeSequencer: implStop() completed");
900 }
901
902 private static EventDispatcher getEventDispatcher() {
903 // create and start the global event thread
904 //TODO need a way to stop this thread when the engine is done
905 final ThreadGroup tg = Thread.currentThread().getThreadGroup();
936
937 if (! (message instanceof ShortMessage)) {
938 if (Printer.debug) Printer.debug("sendControllerEvents: message is NOT instanceof ShortMessage!");
939 return;
940 }
941 ShortMessage msg = (ShortMessage) message;
942 int controller = msg.getData1();
943 List<Object> sendToListeners = new ArrayList<>();
944 for (int i = 0; i < size; i++) {
945 ControllerListElement cve = controllerEventListeners.get(i);
946 for(int j = 0; j < cve.controllers.length; j++) {
947 if (cve.controllers[j] == controller) {
948 sendToListeners.add(cve.listener);
949 break;
950 }
951 }
952 }
953 getEventDispatcher().sendAudioEvents(message, sendToListeners);
954 }
955
956
957
958 private boolean needCaching() {
959 return !isOpen() || (sequence == null) || (playThread == null);
960 }
961
962 /**
963 * return the data pump instance, owned by play thread
964 * if playthread is null, return null.
965 * This method is guaranteed to return non-null if
966 * needCaching returns false
967 */
968 private DataPump getDataPump() {
969 if (playThread != null) {
970 return playThread.getDataPump();
971 }
972 return null;
973 }
974
975 private MidiUtils.TempoCache getTempoCache() {
976 return tempoCache;
977 }
978
979 private static boolean[] ensureBoolArraySize(boolean[] array, int desiredSize) {
980 if (array == null) {
981 return new boolean[desiredSize];
982 }
983 if (array.length < desiredSize) {
984 boolean[] newArray = new boolean[desiredSize];
985 System.arraycopy(array, 0, newArray, 0, array.length);
986 return newArray;
987 }
988 return array;
989 }
990
991
992 // OVERRIDES OF ABSTRACT MIDI DEVICE METHODS
993
994 protected boolean hasReceivers() {
995 return true;
996 }
997
998 // for recording
999 protected Receiver createReceiver() throws MidiUnavailableException {
1000 return new SequencerReceiver();
1001 }
1002
1003
1004 protected boolean hasTransmitters() {
1005 return true;
1006 }
1007
1008
1009 protected Transmitter createTransmitter() throws MidiUnavailableException {
1010 return new SequencerTransmitter();
1011 }
1012
1013
1014 // interface AutoConnectSequencer
1015 public void setAutoConnect(Receiver autoConnectedReceiver) {
1016 this.autoConnect = (autoConnectedReceiver != null);
1017 this.autoConnectedReceiver = autoConnectedReceiver;
1018 }
1019
1020
1021
1022 // INNER CLASSES
1023
1024 /**
1025 * An own class to distinguish the class name from
1026 * the transmitter of other devices
1027 */
1028 private class SequencerTransmitter extends BasicTransmitter {
1029 private SequencerTransmitter() {
1030 super();
1031 }
1032 }
1033
1034
1035 final class SequencerReceiver extends AbstractReceiver {
1036
1037 void implSend(MidiMessage message, long timeStamp) {
1038 if (recording) {
1039 long tickPos = 0;
1040
1041 // convert timeStamp to ticks
1042 if (timeStamp < 0) {
1043 tickPos = getTickPosition();
1044 } else {
1045 synchronized(tempoCache) {
1046 tickPos = MidiUtils.microsecond2tick(sequence, timeStamp, tempoCache);
1047 }
1048 }
1049
1050 // and record to the first matching Track
1051 Track track = null;
1052 // do not record real-time events
1053 // see 5048381: NullPointerException when saving a MIDI sequence
1054 if (message.getLength() > 1) {
1055 if (message instanceof ShortMessage) {
1056 ShortMessage sm = (ShortMessage) message;
1063 // $$fb: the first recording track
1064 track = RecordingTrack.get(recordingTracks, -1);
1065 }
1066 if (track != null) {
1067 // create a copy of this message
1068 if (message instanceof ShortMessage) {
1069 message = new FastShortMessage((ShortMessage) message);
1070 } else {
1071 message = (MidiMessage) message.clone();
1072 }
1073
1074 // create new MidiEvent
1075 MidiEvent me = new MidiEvent(message, tickPos);
1076 track.add(me);
1077 }
1078 }
1079 }
1080 }
1081 }
1082
1083
1084 private static class RealTimeSequencerInfo extends MidiDevice.Info {
1085
1086 private static final String name = "Real Time Sequencer";
1087 private static final String vendor = "Oracle Corporation";
1088 private static final String description = "Software sequencer";
1089 private static final String version = "Version 1.0";
1090
1091 RealTimeSequencerInfo() {
1092 super(name, vendor, description, version);
1093 }
1094 } // class Info
1095
1096
1097 private class ControllerListElement {
1098
1099 // $$jb: using an array for controllers b/c its
1100 // easier to deal with than turning all the
1101 // ints into objects to use a Vector
1102 int [] controllers;
1103 final ControllerEventListener listener;
1104
1105 private ControllerListElement(ControllerEventListener listener, int[] controllers) {
1106
1107 this.listener = listener;
1108 if (controllers == null) {
1109 controllers = new int[128];
1110 for (int i = 0; i < 128; i++) {
1111 controllers[i] = i;
1112 }
1113 }
1114 this.controllers = controllers;
1115 }
1116
1185 }
1186
1187 private int[] getControllers() {
1188
1189 // return a copy of our array of controllers,
1190 // so others can't mess with it
1191 if (controllers == null) {
1192 return null;
1193 }
1194
1195 int c[] = new int[controllers.length];
1196
1197 for(int i=0; i<controllers.length; i++){
1198 c[i] = controllers[i];
1199 }
1200 return c;
1201 }
1202
1203 } // class ControllerListElement
1204
1205
1206 static class RecordingTrack {
1207
1208 private final Track track;
1209 private int channel;
1210
1211 RecordingTrack(Track track, int channel) {
1212 this.track = track;
1213 this.channel = channel;
1214 }
1215
1216 static RecordingTrack get(List<RecordingTrack> recordingTracks, Track track) {
1217
1218 synchronized(recordingTracks) {
1219 int size = recordingTracks.size();
1220
1221 for (int i = 0; i < size; i++) {
1222 RecordingTrack current = recordingTracks.get(i);
1223 if (current.track == track) {
1224 return current;
1225 }
1227 }
1228 return null;
1229 }
1230
1231 static Track get(List<RecordingTrack> recordingTracks, int channel) {
1232
1233 synchronized(recordingTracks) {
1234 int size = recordingTracks.size();
1235 for (int i = 0; i < size; i++) {
1236 RecordingTrack current = recordingTracks.get(i);
1237 if ((current.channel == channel) || (current.channel == -1)) {
1238 return current.track;
1239 }
1240 }
1241 }
1242 return null;
1243
1244 }
1245 }
1246
1247
1248 final class PlayThread implements Runnable {
1249 private Thread thread;
1250 private final Object lock = new Object();
1251
1252 /** true if playback is interrupted (in close) */
1253 boolean interrupted = false;
1254 boolean isPumping = false;
1255
1256 private final DataPump dataPump = new DataPump();
1257
1258
1259 PlayThread() {
1260 // nearly MAX_PRIORITY
1261 int priority = Thread.NORM_PRIORITY
1262 + ((Thread.MAX_PRIORITY - Thread.NORM_PRIORITY) * 3) / 4;
1263 thread = JSSecurityManager.createThread(this,
1264 "Java Sound Sequencer", // name
1265 false, // daemon
1266 priority, // priority
1267 true); // doStart
1334 // dispose of thread
1335 interrupted = true;
1336 oldThread = thread;
1337 thread = null;
1338 }
1339 if (oldThread != null) {
1340 // wake up the thread if it's in wait()
1341 synchronized(lock) {
1342 lock.notifyAll();
1343 }
1344 }
1345 // wait for the thread to terminate itself,
1346 // but max. 2 seconds. Must not be synchronized!
1347 if (oldThread != null) {
1348 try {
1349 oldThread.join(2000);
1350 } catch (InterruptedException ie) {}
1351 }
1352 }
1353
1354
1355 /**
1356 * Main process loop driving the media flow.
1357 *
1358 * Make sure to NOT synchronize on RealTimeSequencer
1359 * anywhere here (even implicit). That is a sure deadlock!
1360 */
1361 public void run() {
1362
1363 while (!interrupted) {
1364 boolean EOM = false;
1365 boolean wasRunning = running;
1366 isPumping = !interrupted && running;
1367 while (!EOM && !interrupted && running) {
1368 EOM = dataPump.pump();
1369
1370 try {
1371 Thread.sleep(1);
1372 } catch (InterruptedException ie) {
1373 // ignore
1374 }
1375 }
1376 if (Printer.debug) {
1377 Printer.debug("Exited main pump loop because: ");
1378 if (EOM) Printer.debug(" -> EOM is reached");
1379 if (!running) Printer.debug(" -> running was set to false");
1380 if (interrupted) Printer.debug(" -> interrupted was set to true");
1392 try{
1393 message.setMessage(MidiUtils.META_END_OF_TRACK_TYPE, new byte[0], 0);
1394 } catch(InvalidMidiDataException e1) {}
1395 sendMetaEvents(message);
1396 }
1397 synchronized (lock) {
1398 isPumping = false;
1399 // wake up a waiting stop() method
1400 lock.notifyAll();
1401 while (!running && !interrupted) {
1402 try {
1403 lock.wait();
1404 } catch (Exception ex) {}
1405 }
1406 }
1407 } // end of while(!EOM && !interrupted && running)
1408 if (Printer.debug) Printer.debug("end of play thread");
1409 }
1410 }
1411
1412
1413 /**
1414 * class that does the actual dispatching of events,
1415 * used to be in native in MMAPI
1416 */
1417 private class DataPump {
1418 private float currTempo; // MPQ tempo
1419 private float tempoFactor; // 1.0 is default
1420 private float inverseTempoFactor;// = 1.0 / tempoFactor
1421 private long ignoreTempoEventAt; // ignore next META tempo during playback at this tick pos only
1422 private int resolution;
1423 private float divisionType;
1424 private long checkPointMillis; // microseconds at checkoint
1425 private long checkPointTick; // ticks at checkpoint
1426 private int[] noteOnCache; // bit-mask of notes that are currently on
1427 private Track[] tracks;
1428 private boolean[] trackDisabled; // if true, do not play this track
1429 private int[] trackReadPos; // read index per track
1430 private long lastTick;
1431 private boolean needReindex = false;
1432 private int currLoopCounter = 0;
1433
1434 //private sun.misc.Perf perf = sun.misc.Perf.getPerf();
1435 //private long perfFreq = perf.highResFrequency();
1436
1437
1438 DataPump() {
1439 init();
1440 }
1441
1442 synchronized void init() {
1443 ignoreTempoEventAt = -1;
1444 tempoFactor = 1.0f;
1445 inverseTempoFactor = 1.0f;
1446 noteOnCache = new int[128];
1447 tracks = null;
1448 trackDisabled = null;
1449 }
1450
1451 synchronized void setTickPos(long tickPos) {
1452 long oldLastTick = tickPos;
1453 lastTick = tickPos;
1454 if (running) {
1455 notesOff(false);
1456 }
1457 if (running || tickPos > 0) {
1499 if (factor > 0 && factor != this.tempoFactor) {
1500 tempoFactor = factor;
1501 inverseTempoFactor = 1.0f / factor;
1502 // re-calculate check point
1503 checkPointMillis = 0;
1504 }
1505 }
1506
1507 float getTempoFactor() {
1508 return tempoFactor;
1509 }
1510
1511 synchronized void muteSoloChanged() {
1512 boolean[] newDisabled = makeDisabledArray();
1513 if (running) {
1514 applyDisabledTracks(trackDisabled, newDisabled);
1515 }
1516 trackDisabled = newDisabled;
1517 }
1518
1519
1520
1521 synchronized void setSequence(Sequence seq) {
1522 if (seq == null) {
1523 init();
1524 return;
1525 }
1526 tracks = seq.getTracks();
1527 muteSoloChanged();
1528 resolution = seq.getResolution();
1529 divisionType = seq.getDivisionType();
1530 trackReadPos = new int[tracks.length];
1531 // trigger re-initialization
1532 checkPointMillis = 0;
1533 needReindex = true;
1534 }
1535
1536 synchronized void resetLoopCount() {
1537 currLoopCounter = loopCount;
1538 }
1539
1540 void clearNoteOnCache() {
1551 if ((noteOnCache[i] & channelMask) != 0) {
1552 noteOnCache[i] ^= channelMask;
1553 // send note on with velocity 0
1554 getTransmitterList().sendMessage((ShortMessage.NOTE_ON | ch) | (i<<8), -1);
1555 done++;
1556 }
1557 }
1558 /* all notes off */
1559 getTransmitterList().sendMessage((ShortMessage.CONTROL_CHANGE | ch) | (123<<8), -1);
1560 /* sustain off */
1561 getTransmitterList().sendMessage((ShortMessage.CONTROL_CHANGE | ch) | (64<<8), -1);
1562 if (doControllers) {
1563 /* reset all controllers */
1564 getTransmitterList().sendMessage((ShortMessage.CONTROL_CHANGE | ch) | (121<<8), -1);
1565 done++;
1566 }
1567 }
1568 if (DEBUG_PUMP) Printer.println(" noteOff: sent "+done+" messages.");
1569 }
1570
1571
1572 private boolean[] makeDisabledArray() {
1573 if (tracks == null) {
1574 return null;
1575 }
1576 boolean[] newTrackDisabled = new boolean[tracks.length];
1577 boolean[] solo;
1578 boolean[] mute;
1579 synchronized(RealTimeSequencer.this) {
1580 mute = trackMuted;
1581 solo = trackSolo;
1582 }
1583 // if one track is solo, then only play solo
1584 boolean hasSolo = false;
1585 if (solo != null) {
1586 for (int i = 0; i < solo.length; i++) {
1587 if (solo[i]) {
1588 hasSolo = true;
1589 break;
1590 }
1591 }
1639 }
1640 if (note >= 0) {
1641 int bit = 1<<(status & 0x0F);
1642 if ((noteOnCache[note] & bit) != 0) {
1643 // the bit is set. Send Note Off
1644 getTransmitterList().sendMessage(status | (note<<8), -1);
1645 // clear the bit
1646 noteOnCache[note] &= (0xFFFF ^ bit);
1647 done++;
1648 }
1649 }
1650 }
1651 }
1652 } catch (ArrayIndexOutOfBoundsException aioobe) {
1653 // this happens when messages are removed
1654 // from the track while this method executes
1655 }
1656 if (DEBUG_PUMP) Printer.println(" sendNoteOffIfOn: sent "+done+" messages.");
1657 }
1658
1659
1660 /**
1661 * Runtime application of mute/solo:
1662 * if a track is muted that was previously playing, send
1663 * note off events for all currently playing notes
1664 */
1665 private void applyDisabledTracks(boolean[] oldDisabled, boolean[] newDisabled) {
1666 byte[][] tempArray = null;
1667 synchronized(RealTimeSequencer.this) {
1668 for (int i = 0; i < newDisabled.length; i++) {
1669 if (((oldDisabled == null)
1670 || (i >= oldDisabled.length)
1671 || !oldDisabled[i])
1672 && newDisabled[i]) {
1673 // case that a track gets muted: need to
1674 // send appropriate note off events to prevent
1675 // hanging notes
1676
1677 if (tracks.length > i) {
1678 sendNoteOffIfOn(tracks[i], lastTick);
1679 }
1680 }
1681 else if ((oldDisabled != null)
1682 && (i < oldDisabled.length)
1683 && oldDisabled[i]
1764 int packedMsg = (ShortMessage.CONTROL_CHANGE | ch) | (co<<8) | (controllerValue<<16);
1765 getTransmitterList().sendMessage(packedMsg, -1);
1766 numControllersSent++;
1767 }
1768 }
1769 // send program change *after* controllers, to
1770 // correctly initialize banks
1771 if (progs[ch] >= 0) {
1772 getTransmitterList().sendMessage((ShortMessage.PROGRAM_CHANGE | ch) | (progs[ch]<<8), -1);
1773 }
1774 if (progs[ch] >= 0 || startTick == 0 || endTick == 0) {
1775 // reset pitch bend on this channel (E0 00 40)
1776 getTransmitterList().sendMessage((ShortMessage.PITCH_BEND | ch) | (0x40 << 16), -1);
1777 // reset sustain pedal on this channel
1778 getTransmitterList().sendMessage((ShortMessage.CONTROL_CHANGE | ch) | (64 << 8), -1);
1779 }
1780 }
1781 if (DEBUG_PUMP) Printer.println(" chaseTrackEvents track "+trackNum+": sent "+numControllersSent+" controllers.");
1782 }
1783
1784
1785 /** chase controllers and program for all tracks */
1786 synchronized void chaseEvents(long startTick, long endTick) {
1787 if (DEBUG_PUMP) Printer.println(">> chaseEvents from tick "+startTick+".."+(endTick-1));
1788 byte[][] tempArray = new byte[128][16];
1789 for (int t = 0; t < tracks.length; t++) {
1790 if ((trackDisabled == null)
1791 || (trackDisabled.length <= t)
1792 || (!trackDisabled[t])) {
1793 // if track is not disabled, chase the events for it
1794 chaseTrackEvents(t, startTick, endTick, true, tempArray);
1795 }
1796 }
1797 if (DEBUG_PUMP) Printer.println("<< chaseEvents");
1798 }
1799
1800
1801 // playback related methods (pumping)
1802
1803 private long getCurrentTimeMillis() {
1804 return System.nanoTime() / 1000000l;
1805 //return perf.highResCounter() * 1000 / perfFreq;
1806 }
1807
1808 private long millis2tick(long millis) {
1809 if (divisionType != Sequence.PPQ) {
1810 double dTick = ((((double) millis) * tempoFactor)
1811 * ((double) divisionType)
1812 * ((double) resolution))
1813 / ((double) 1000);
1814 return (long) dTick;
1815 }
1816 return MidiUtils.microsec2ticks(millis * 1000,
1817 currTempo * inverseTempoFactor,
1818 resolution);
1819 }
1820
1883 if (vel > 0) {
1884 // if velocity > 0 set the bit in the noteOnCache array
1885 noteOnCache[note] |= 1<<(msgStatus & 0x0F);
1886 } else {
1887 // if velocity = 0 clear the bit in the noteOnCache array
1888 noteOnCache[note] &= (0xFFFF ^ (1<<(msgStatus & 0x0F)));
1889 }
1890 break;
1891 }
1892
1893 case ShortMessage.CONTROL_CHANGE:
1894 // if controller message, send controller listeners
1895 sendControllerEvents(message);
1896 break;
1897
1898 }
1899 }
1900 return changesPending;
1901 }
1902
1903
1904 /** the main pump method
1905 * @return true if end of sequence is reached
1906 */
1907 synchronized boolean pump() {
1908 long currMillis;
1909 long targetTick = lastTick;
1910 MidiEvent currEvent;
1911 boolean changesPending = false;
1912 boolean doLoop = false;
1913 boolean EOM = false;
1914
1915 currMillis = getCurrentTimeMillis();
1916 int finishedTracks = 0;
1917 do {
1918 changesPending = false;
1919
1920 // need to re-find indexes in tracks?
1921 if (needReindex) {
1922 if (DEBUG_PUMP) Printer.println("Need to re-index at "+currMillis+" millis. TargetTick="+targetTick);
1923 if (trackReadPos.length < tracks.length) {
2061 // is correct, and doesn't drift away with several repetition,
2062 // there is a slight lag when looping back, probably caused
2063 // by the chasing.
2064
2065 checkPointMillis = oldCheckPointMillis + tick2millis(loopEndTick - checkPointTick);
2066 checkPointTick = loopStart;
2067 if (DEBUG_PUMP) Printer.println(" Setting currMillis="+currMillis
2068 +" new checkPointMillis="+checkPointMillis
2069 +" new checkPointTick="+checkPointTick);
2070 // no need for reindexing, is done in setTickPos
2071 needReindex = false;
2072 changesPending = false;
2073 // reset doLoop flag
2074 doLoop = false;
2075 EOM = false;
2076 }
2077 } while (changesPending);
2078
2079 return EOM;
2080 }
2081
2082 } // class DataPump
2083
2084 }
|
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26 package com.sun.media.sound;
27
28 import java.io.IOException;
29 import java.io.InputStream;
30 import java.util.ArrayList;
31 import java.util.List;
32 import java.util.Map;
33 import java.util.WeakHashMap;
34
35 import javax.sound.midi.ControllerEventListener;
36 import javax.sound.midi.InvalidMidiDataException;
37 import javax.sound.midi.MetaEventListener;
38 import javax.sound.midi.MetaMessage;
39 import javax.sound.midi.MidiDevice;
40 import javax.sound.midi.MidiEvent;
41 import javax.sound.midi.MidiMessage;
42 import javax.sound.midi.MidiSystem;
43 import javax.sound.midi.MidiUnavailableException;
44 import javax.sound.midi.Receiver;
45 import javax.sound.midi.Sequence;
46 import javax.sound.midi.Sequencer;
47 import javax.sound.midi.ShortMessage;
48 import javax.sound.midi.Synthesizer;
49 import javax.sound.midi.Track;
50 import javax.sound.midi.Transmitter;
51
52 /**
53 * A Real Time Sequencer
54 *
55 * @author Florian Bomers
56 */
57
58 /* TODO:
59 * - rename PlayThread to PlayEngine (because isn't a thread)
60 */
61 final class RealTimeSequencer extends AbstractMidiDevice
62 implements Sequencer, AutoConnectSequencer {
63
64 /** debugging flags */
65 private static final boolean DEBUG_PUMP = false;
66 private static final boolean DEBUG_PUMP_ALL = false;
67
68 /**
69 * Event Dispatcher thread. Should be using a shared event
70 * dispatcher instance with a factory in EventDispatcher
71 */
72 private static final Map<ThreadGroup, EventDispatcher> dispatchers =
73 new WeakHashMap<>();
74
75 /**
76 * All RealTimeSequencers share this info object.
77 */
78 static final MidiDevice.Info info = new RealTimeSequencerInfo();
79
80
81 private static final Sequencer.SyncMode[] masterSyncModes = { Sequencer.SyncMode.INTERNAL_CLOCK };
82 private static final Sequencer.SyncMode[] slaveSyncModes = { Sequencer.SyncMode.NO_SYNC };
83
84 private static final Sequencer.SyncMode masterSyncMode = Sequencer.SyncMode.INTERNAL_CLOCK;
85 private static final Sequencer.SyncMode slaveSyncMode = Sequencer.SyncMode.NO_SYNC;
86
87 /**
88 * Sequence on which this sequencer is operating.
89 */
90 private Sequence sequence = null;
91
92 // caches
93
94 /**
95 * Same for setTempoInMPQ...
96 * -1 means not set.
97 */
98 private double cacheTempoMPQ = -1;
99
100 /**
101 * cache value for tempo factor until sequence is set
102 * -1 means not set.
103 */
104 private float cacheTempoFactor = -1;
105
106 /** if a particular track is muted */
107 private boolean[] trackMuted = null;
108 /** if a particular track is solo */
109 private boolean[] trackSolo = null;
110
111 /** tempo cache for getMicrosecondPosition */
112 private final MidiUtils.TempoCache tempoCache = new MidiUtils.TempoCache();
113
114 /**
115 * True if the sequence is running.
116 */
117 private volatile boolean running;
118
119 /**
120 * the thread for pushing out the MIDI messages.
121 */
122 private PlayThread playThread;
123
124 /**
125 * True if we are recording.
126 */
127 private volatile boolean recording;
128
129 /**
130 * List of tracks to which we're recording.
131 */
132 private final List<RecordingTrack> recordingTracks = new ArrayList<>();
133
134 private long loopStart = 0;
135 private long loopEnd = -1;
136 private int loopCount = 0;
137
138 /**
139 * Meta event listeners.
140 */
141 private final ArrayList<Object> metaEventListeners = new ArrayList<>();
142
143 /**
144 * Control change listeners.
145 */
146 private final ArrayList<ControllerListElement> controllerEventListeners = new ArrayList<>();
147
148 /**
149 * automatic connection support.
150 */
151 private boolean autoConnect = false;
152
153 /**
154 * if we need to autoconnect at next open.
155 */
156 private boolean doAutoConnectAtNextOpen = false;
157
158 /**
159 * the receiver that this device is auto-connected to.
160 */
161 Receiver autoConnectedReceiver = null;
162
163
164 /* ****************************** CONSTRUCTOR ****************************** */
165
166 RealTimeSequencer(){
167 super(info);
168
169 if (Printer.trace) Printer.trace(">> RealTimeSequencer CONSTRUCTOR");
170 if (Printer.trace) Printer.trace("<< RealTimeSequencer CONSTRUCTOR completed");
171 }
172
173 /* ****************************** SEQUENCER METHODS ******************** */
174
175 @Override
176 public synchronized void setSequence(Sequence sequence)
177 throws InvalidMidiDataException {
178
179 if (Printer.trace) Printer.trace(">> RealTimeSequencer: setSequence(" + sequence +")");
180
181 if (sequence != this.sequence) {
182 if (this.sequence != null && sequence == null) {
183 setCaches();
184 stop();
185 // initialize some non-cached values
186 trackMuted = null;
187 trackSolo = null;
188 loopStart = 0;
189 loopEnd = -1;
190 loopCount = 0;
191 if (getDataPump() != null) {
192 getDataPump().setTickPos(0);
193 getDataPump().resetLoopCount();
194 }
195 }
203 this.sequence = sequence;
204
205 if (sequence != null) {
206 tempoCache.refresh(sequence);
207 // rewind to the beginning
208 setTickPosition(0);
209 // propagate caches
210 propagateCaches();
211 }
212 }
213 else if (sequence != null) {
214 tempoCache.refresh(sequence);
215 if (playThread != null) {
216 playThread.setSequence(sequence);
217 }
218 }
219
220 if (Printer.trace) Printer.trace("<< RealTimeSequencer: setSequence(" + sequence +") completed");
221 }
222
223 @Override
224 public synchronized void setSequence(InputStream stream) throws IOException, InvalidMidiDataException {
225
226 if (Printer.trace) Printer.trace(">> RealTimeSequencer: setSequence(" + stream +")");
227
228 if (stream == null) {
229 setSequence((Sequence) null);
230 return;
231 }
232
233 Sequence seq = MidiSystem.getSequence(stream); // can throw IOException, InvalidMidiDataException
234
235 setSequence(seq);
236
237 if (Printer.trace) Printer.trace("<< RealTimeSequencer: setSequence(" + stream +") completed");
238
239 }
240
241 @Override
242 public Sequence getSequence() {
243 return sequence;
244 }
245
246 @Override
247 public synchronized void start() {
248 if (Printer.trace) Printer.trace(">> RealTimeSequencer: start()");
249
250 // sequencer not open: throw an exception
251 if (!isOpen()) {
252 throw new IllegalStateException("sequencer not open");
253 }
254
255 // sequence not available: throw an exception
256 if (sequence == null) {
257 throw new IllegalStateException("sequence not set");
258 }
259
260 // already running: return quietly
261 if (running == true) {
262 return;
263 }
264
265 // start playback
266 implStart();
267
268 if (Printer.trace) Printer.trace("<< RealTimeSequencer: start() completed");
269 }
270
271 @Override
272 public synchronized void stop() {
273 if (Printer.trace) Printer.trace(">> RealTimeSequencer: stop()");
274
275 if (!isOpen()) {
276 throw new IllegalStateException("sequencer not open");
277 }
278 stopRecording();
279
280 // not running; just return
281 if (running == false) {
282 if (Printer.trace) Printer.trace("<< RealTimeSequencer: stop() not running!");
283 return;
284 }
285
286 // stop playback
287 implStop();
288
289 if (Printer.trace) Printer.trace("<< RealTimeSequencer: stop() completed");
290 }
291
292 @Override
293 public boolean isRunning() {
294 return running;
295 }
296
297 @Override
298 public void startRecording() {
299 if (!isOpen()) {
300 throw new IllegalStateException("Sequencer not open");
301 }
302
303 start();
304 recording = true;
305 }
306
307 @Override
308 public void stopRecording() {
309 if (!isOpen()) {
310 throw new IllegalStateException("Sequencer not open");
311 }
312 recording = false;
313 }
314
315 @Override
316 public boolean isRecording() {
317 return recording;
318 }
319
320 @Override
321 public void recordEnable(Track track, int channel) {
322 if (!findTrack(track)) {
323 throw new IllegalArgumentException("Track does not exist in the current sequence");
324 }
325
326 synchronized(recordingTracks) {
327 RecordingTrack rc = RecordingTrack.get(recordingTracks, track);
328 if (rc != null) {
329 rc.channel = channel;
330 } else {
331 recordingTracks.add(new RecordingTrack(track, channel));
332 }
333 }
334
335 }
336
337 @Override
338 public void recordDisable(Track track) {
339 synchronized(recordingTracks) {
340 RecordingTrack rc = RecordingTrack.get(recordingTracks, track);
341 if (rc != null) {
342 recordingTracks.remove(rc);
343 }
344 }
345
346 }
347
348 private boolean findTrack(Track track) {
349 boolean found = false;
350 if (sequence != null) {
351 Track[] tracks = sequence.getTracks();
352 for (int i = 0; i < tracks.length; i++) {
353 if (track == tracks[i]) {
354 found = true;
355 break;
356 }
357 }
358 }
359 return found;
360 }
361
362 @Override
363 public float getTempoInBPM() {
364 if (Printer.trace) Printer.trace(">> RealTimeSequencer: getTempoInBPM() ");
365
366 return (float) MidiUtils.convertTempo(getTempoInMPQ());
367 }
368
369 @Override
370 public void setTempoInBPM(float bpm) {
371 if (Printer.trace) Printer.trace(">> RealTimeSequencer: setTempoInBPM() ");
372 if (bpm <= 0) {
373 // should throw IllegalArgumentException
374 bpm = 1.0f;
375 }
376
377 setTempoInMPQ((float) MidiUtils.convertTempo((double) bpm));
378 }
379
380 @Override
381 public float getTempoInMPQ() {
382 if (Printer.trace) Printer.trace(">> RealTimeSequencer: getTempoInMPQ() ");
383
384 if (needCaching()) {
385 // if the sequencer is closed, return cached value
386 if (cacheTempoMPQ != -1) {
387 return (float) cacheTempoMPQ;
388 }
389 // if sequence is set, return current tempo
390 if (sequence != null) {
391 return tempoCache.getTempoMPQAt(getTickPosition());
392 }
393
394 // last resort: return a standard tempo: 120bpm
395 return (float) MidiUtils.DEFAULT_TEMPO_MPQ;
396 }
397 return getDataPump().getTempoMPQ();
398 }
399
400 @Override
401 public void setTempoInMPQ(float mpq) {
402 if (mpq <= 0) {
403 // should throw IllegalArgumentException
404 mpq = 1.0f;
405 }
406
407 if (Printer.trace) Printer.trace(">> RealTimeSequencer: setTempoInMPQ() ");
408
409 if (needCaching()) {
410 // cache the value
411 cacheTempoMPQ = mpq;
412 } else {
413 // set the native tempo in MPQ
414 getDataPump().setTempoMPQ(mpq);
415
416 // reset the tempoInBPM and tempoInMPQ values so we won't use them again
417 cacheTempoMPQ = -1;
418 }
419 }
420
421 @Override
422 public void setTempoFactor(float factor) {
423 if (factor <= 0) {
424 // should throw IllegalArgumentException
425 return;
426 }
427
428 if (Printer.trace) Printer.trace(">> RealTimeSequencer: setTempoFactor() ");
429
430 if (needCaching()) {
431 cacheTempoFactor = factor;
432 } else {
433 getDataPump().setTempoFactor(factor);
434 // don't need cache anymore
435 cacheTempoFactor = -1;
436 }
437 }
438
439 @Override
440 public float getTempoFactor() {
441 if (Printer.trace) Printer.trace(">> RealTimeSequencer: getTempoFactor() ");
442
443 if (needCaching()) {
444 if (cacheTempoFactor != -1) {
445 return cacheTempoFactor;
446 }
447 return 1.0f;
448 }
449 return getDataPump().getTempoFactor();
450 }
451
452 @Override
453 public long getTickLength() {
454 if (Printer.trace) Printer.trace(">> RealTimeSequencer: getTickLength() ");
455
456 if (sequence == null) {
457 return 0;
458 }
459
460 return sequence.getTickLength();
461 }
462
463 @Override
464 public synchronized long getTickPosition() {
465 if (Printer.trace) Printer.trace(">> RealTimeSequencer: getTickPosition() ");
466
467 if (getDataPump() == null || sequence == null) {
468 return 0;
469 }
470
471 return getDataPump().getTickPos();
472 }
473
474 @Override
475 public synchronized void setTickPosition(long tick) {
476 if (tick < 0) {
477 // should throw IllegalArgumentException
478 return;
479 }
480
481 if (Printer.trace) Printer.trace(">> RealTimeSequencer: setTickPosition("+tick+") ");
482
483 if (getDataPump() == null) {
484 if (tick != 0) {
485 // throw new InvalidStateException("cannot set position in closed state");
486 }
487 }
488 else if (sequence == null) {
489 if (tick != 0) {
490 // throw new InvalidStateException("cannot set position if sequence is not set");
491 }
492 } else {
493 getDataPump().setTickPos(tick);
494 }
495 }
496
497 @Override
498 public long getMicrosecondLength() {
499 if (Printer.trace) Printer.trace(">> RealTimeSequencer: getMicrosecondLength() ");
500
501 if (sequence == null) {
502 return 0;
503 }
504
505 return sequence.getMicrosecondLength();
506 }
507
508 @Override
509 public long getMicrosecondPosition() {
510 if (Printer.trace) Printer.trace(">> RealTimeSequencer: getMicrosecondPosition() ");
511
512 if (getDataPump() == null || sequence == null) {
513 return 0;
514 }
515 synchronized (tempoCache) {
516 return MidiUtils.tick2microsecond(sequence, getDataPump().getTickPos(), tempoCache);
517 }
518 }
519
520 @Override
521 public void setMicrosecondPosition(long microseconds) {
522 if (microseconds < 0) {
523 // should throw IllegalArgumentException
524 return;
525 }
526
527 if (Printer.trace) Printer.trace(">> RealTimeSequencer: setMicrosecondPosition("+microseconds+") ");
528
529 if (getDataPump() == null) {
530 if (microseconds != 0) {
531 // throw new InvalidStateException("cannot set position in closed state");
532 }
533 }
534 else if (sequence == null) {
535 if (microseconds != 0) {
536 // throw new InvalidStateException("cannot set position if sequence is not set");
537 }
538 } else {
539 synchronized(tempoCache) {
540 setTickPosition(MidiUtils.microsecond2tick(sequence, microseconds, tempoCache));
541 }
542 }
543 }
544
545 @Override
546 public void setMasterSyncMode(Sequencer.SyncMode sync) {
547 // not supported
548 }
549
550 @Override
551 public Sequencer.SyncMode getMasterSyncMode() {
552 return masterSyncMode;
553 }
554
555 @Override
556 public Sequencer.SyncMode[] getMasterSyncModes() {
557 Sequencer.SyncMode[] returnedModes = new Sequencer.SyncMode[masterSyncModes.length];
558 System.arraycopy(masterSyncModes, 0, returnedModes, 0, masterSyncModes.length);
559 return returnedModes;
560 }
561
562 @Override
563 public void setSlaveSyncMode(Sequencer.SyncMode sync) {
564 // not supported
565 }
566
567 @Override
568 public Sequencer.SyncMode getSlaveSyncMode() {
569 return slaveSyncMode;
570 }
571
572 @Override
573 public Sequencer.SyncMode[] getSlaveSyncModes() {
574 Sequencer.SyncMode[] returnedModes = new Sequencer.SyncMode[slaveSyncModes.length];
575 System.arraycopy(slaveSyncModes, 0, returnedModes, 0, slaveSyncModes.length);
576 return returnedModes;
577 }
578
579 int getTrackCount() {
580 Sequence seq = getSequence();
581 if (seq != null) {
582 // $$fb wish there was a nicer way to get the number of tracks...
583 return sequence.getTracks().length;
584 }
585 return 0;
586 }
587
588 @Override
589 public synchronized void setTrackMute(int track, boolean mute) {
590 int trackCount = getTrackCount();
591 if (track < 0 || track >= getTrackCount()) return;
592 trackMuted = ensureBoolArraySize(trackMuted, trackCount);
593 trackMuted[track] = mute;
594 if (getDataPump() != null) {
595 getDataPump().muteSoloChanged();
596 }
597 }
598
599 @Override
600 public synchronized boolean getTrackMute(int track) {
601 if (track < 0 || track >= getTrackCount()) return false;
602 if (trackMuted == null || trackMuted.length <= track) return false;
603 return trackMuted[track];
604 }
605
606 @Override
607 public synchronized void setTrackSolo(int track, boolean solo) {
608 int trackCount = getTrackCount();
609 if (track < 0 || track >= getTrackCount()) return;
610 trackSolo = ensureBoolArraySize(trackSolo, trackCount);
611 trackSolo[track] = solo;
612 if (getDataPump() != null) {
613 getDataPump().muteSoloChanged();
614 }
615 }
616
617 @Override
618 public synchronized boolean getTrackSolo(int track) {
619 if (track < 0 || track >= getTrackCount()) return false;
620 if (trackSolo == null || trackSolo.length <= track) return false;
621 return trackSolo[track];
622 }
623
624 @Override
625 public boolean addMetaEventListener(MetaEventListener listener) {
626 synchronized(metaEventListeners) {
627 if (! metaEventListeners.contains(listener)) {
628
629 metaEventListeners.add(listener);
630 }
631 return true;
632 }
633 }
634
635 @Override
636 public void removeMetaEventListener(MetaEventListener listener) {
637 synchronized(metaEventListeners) {
638 int index = metaEventListeners.indexOf(listener);
639 if (index >= 0) {
640 metaEventListeners.remove(index);
641 }
642 }
643 }
644
645 @Override
646 public int[] addControllerEventListener(ControllerEventListener listener, int[] controllers) {
647 synchronized(controllerEventListeners) {
648
649 // first find the listener. if we have one, add the controllers
650 // if not, create a new element for it.
651 ControllerListElement cve = null;
652 boolean flag = false;
653 for(int i=0; i < controllerEventListeners.size(); i++) {
654
655 cve = controllerEventListeners.get(i);
656
657 if (cve.listener.equals(listener)) {
658 cve.addControllers(controllers);
659 flag = true;
660 break;
661 }
662 }
663 if (!flag) {
664 cve = new ControllerListElement(listener, controllers);
665 controllerEventListeners.add(cve);
666 }
667
668 // and return all the controllers this listener is interested in
669 return cve.getControllers();
670 }
671 }
672
673 @Override
674 public int[] removeControllerEventListener(ControllerEventListener listener, int[] controllers) {
675 synchronized(controllerEventListeners) {
676 ControllerListElement cve = null;
677 boolean flag = false;
678 for (int i=0; i < controllerEventListeners.size(); i++) {
679 cve = controllerEventListeners.get(i);
680 if (cve.listener.equals(listener)) {
681 cve.removeControllers(controllers);
682 flag = true;
683 break;
684 }
685 }
686 if (!flag) {
687 return new int[0];
688 }
689 if (controllers == null) {
690 int index = controllerEventListeners.indexOf(cve);
691 if (index >= 0) {
692 controllerEventListeners.remove(index);
693 }
694 return new int[0];
695 }
696 return cve.getControllers();
697 }
698 }
699
700 ////////////////// LOOPING (added in 1.5) ///////////////////////
701
702 @Override
703 public void setLoopStartPoint(long tick) {
704 if ((tick > getTickLength())
705 || ((loopEnd != -1) && (tick > loopEnd))
706 || (tick < 0)) {
707 throw new IllegalArgumentException("invalid loop start point: "+tick);
708 }
709 loopStart = tick;
710 }
711
712 @Override
713 public long getLoopStartPoint() {
714 return loopStart;
715 }
716
717 @Override
718 public void setLoopEndPoint(long tick) {
719 if ((tick > getTickLength())
720 || ((loopStart > tick) && (tick != -1))
721 || (tick < -1)) {
722 throw new IllegalArgumentException("invalid loop end point: "+tick);
723 }
724 loopEnd = tick;
725 }
726
727 @Override
728 public long getLoopEndPoint() {
729 return loopEnd;
730 }
731
732 @Override
733 public void setLoopCount(int count) {
734 if (count != LOOP_CONTINUOUSLY
735 && count < 0) {
736 throw new IllegalArgumentException("illegal value for loop count: "+count);
737 }
738 loopCount = count;
739 if (getDataPump() != null) {
740 getDataPump().resetLoopCount();
741 }
742 }
743
744 @Override
745 public int getLoopCount() {
746 return loopCount;
747 }
748
749 /* *********************************** play control ************************* */
750
751 @Override
752 protected void implOpen() throws MidiUnavailableException {
753 if (Printer.trace) Printer.trace(">> RealTimeSequencer: implOpen()");
754
755 //openInternalSynth();
756
757 // create PlayThread
758 playThread = new PlayThread();
759
760 //id = nOpen();
761 //if (id == 0) {
762 // throw new MidiUnavailableException("unable to open sequencer");
763 //}
764 if (sequence != null) {
765 playThread.setSequence(sequence);
766 }
767
768 // propagate caches
769 propagateCaches();
770
771 if (doAutoConnectAtNextOpen) {
813 getTransmitter().setReceiver(rec);
814 } catch (Exception e) {}
815 }
816 if (Printer.trace) Printer.trace("<< RealTimeSequencer: doAutoConnect() succeeded");
817 }
818
819 private synchronized void propagateCaches() {
820 // only set caches if open and sequence is set
821 if (sequence != null && isOpen()) {
822 if (cacheTempoFactor != -1) {
823 setTempoFactor(cacheTempoFactor);
824 }
825 if (cacheTempoMPQ == -1) {
826 setTempoInMPQ((new MidiUtils.TempoCache(sequence)).getTempoMPQAt(getTickPosition()));
827 } else {
828 setTempoInMPQ((float) cacheTempoMPQ);
829 }
830 }
831 }
832
833 /**
834 * populate the caches with the current values.
835 */
836 private synchronized void setCaches() {
837 cacheTempoFactor = getTempoFactor();
838 cacheTempoMPQ = getTempoInMPQ();
839 }
840
841 @Override
842 protected synchronized void implClose() {
843 if (Printer.trace) Printer.trace(">> RealTimeSequencer: implClose() ");
844
845 if (playThread == null) {
846 if (Printer.err) Printer.err("RealTimeSequencer.implClose() called, but playThread not instanciated!");
847 } else {
848 // Interrupt playback loop.
849 playThread.close();
850 playThread = null;
851 }
852
853 super.implClose();
854
855 sequence = null;
856 running = false;
857 cacheTempoMPQ = -1;
858 cacheTempoFactor = -1;
859 trackMuted = null;
860 trackSolo = null;
861 loopStart = 0;
876
877 if (Printer.trace) Printer.trace("<< RealTimeSequencer: implClose() completed");
878 }
879
880 void implStart() {
881 if (Printer.trace) Printer.trace(">> RealTimeSequencer: implStart()");
882
883 if (playThread == null) {
884 if (Printer.err) Printer.err("RealTimeSequencer.implStart() called, but playThread not instanciated!");
885 return;
886 }
887
888 tempoCache.refresh(sequence);
889 if (!running) {
890 running = true;
891 playThread.start();
892 }
893 if (Printer.trace) Printer.trace("<< RealTimeSequencer: implStart() completed");
894 }
895
896 void implStop() {
897 if (Printer.trace) Printer.trace(">> RealTimeSequencer: implStop()");
898
899 if (playThread == null) {
900 if (Printer.err) Printer.err("RealTimeSequencer.implStop() called, but playThread not instanciated!");
901 return;
902 }
903
904 recording = false;
905 if (running) {
906 running = false;
907 playThread.stop();
908 }
909 if (Printer.trace) Printer.trace("<< RealTimeSequencer: implStop() completed");
910 }
911
912 private static EventDispatcher getEventDispatcher() {
913 // create and start the global event thread
914 //TODO need a way to stop this thread when the engine is done
915 final ThreadGroup tg = Thread.currentThread().getThreadGroup();
946
947 if (! (message instanceof ShortMessage)) {
948 if (Printer.debug) Printer.debug("sendControllerEvents: message is NOT instanceof ShortMessage!");
949 return;
950 }
951 ShortMessage msg = (ShortMessage) message;
952 int controller = msg.getData1();
953 List<Object> sendToListeners = new ArrayList<>();
954 for (int i = 0; i < size; i++) {
955 ControllerListElement cve = controllerEventListeners.get(i);
956 for(int j = 0; j < cve.controllers.length; j++) {
957 if (cve.controllers[j] == controller) {
958 sendToListeners.add(cve.listener);
959 break;
960 }
961 }
962 }
963 getEventDispatcher().sendAudioEvents(message, sendToListeners);
964 }
965
966 private boolean needCaching() {
967 return !isOpen() || (sequence == null) || (playThread == null);
968 }
969
970 /**
971 * return the data pump instance, owned by play thread
972 * if playthread is null, return null.
973 * This method is guaranteed to return non-null if
974 * needCaching returns false
975 */
976 private DataPump getDataPump() {
977 if (playThread != null) {
978 return playThread.getDataPump();
979 }
980 return null;
981 }
982
983 private MidiUtils.TempoCache getTempoCache() {
984 return tempoCache;
985 }
986
987 private static boolean[] ensureBoolArraySize(boolean[] array, int desiredSize) {
988 if (array == null) {
989 return new boolean[desiredSize];
990 }
991 if (array.length < desiredSize) {
992 boolean[] newArray = new boolean[desiredSize];
993 System.arraycopy(array, 0, newArray, 0, array.length);
994 return newArray;
995 }
996 return array;
997 }
998
999 // OVERRIDES OF ABSTRACT MIDI DEVICE METHODS
1000
1001 @Override
1002 protected boolean hasReceivers() {
1003 return true;
1004 }
1005
1006 // for recording
1007 @Override
1008 protected Receiver createReceiver() throws MidiUnavailableException {
1009 return new SequencerReceiver();
1010 }
1011
1012 @Override
1013 protected boolean hasTransmitters() {
1014 return true;
1015 }
1016
1017 @Override
1018 protected Transmitter createTransmitter() throws MidiUnavailableException {
1019 return new SequencerTransmitter();
1020 }
1021
1022 // interface AutoConnectSequencer
1023 @Override
1024 public void setAutoConnect(Receiver autoConnectedReceiver) {
1025 this.autoConnect = (autoConnectedReceiver != null);
1026 this.autoConnectedReceiver = autoConnectedReceiver;
1027 }
1028
1029 /**
1030 * An own class to distinguish the class name from
1031 * the transmitter of other devices.
1032 */
1033 private class SequencerTransmitter extends BasicTransmitter {
1034 private SequencerTransmitter() {
1035 super();
1036 }
1037 }
1038
1039 final class SequencerReceiver extends AbstractReceiver {
1040
1041 @Override
1042 void implSend(MidiMessage message, long timeStamp) {
1043 if (recording) {
1044 long tickPos = 0;
1045
1046 // convert timeStamp to ticks
1047 if (timeStamp < 0) {
1048 tickPos = getTickPosition();
1049 } else {
1050 synchronized(tempoCache) {
1051 tickPos = MidiUtils.microsecond2tick(sequence, timeStamp, tempoCache);
1052 }
1053 }
1054
1055 // and record to the first matching Track
1056 Track track = null;
1057 // do not record real-time events
1058 // see 5048381: NullPointerException when saving a MIDI sequence
1059 if (message.getLength() > 1) {
1060 if (message instanceof ShortMessage) {
1061 ShortMessage sm = (ShortMessage) message;
1068 // $$fb: the first recording track
1069 track = RecordingTrack.get(recordingTracks, -1);
1070 }
1071 if (track != null) {
1072 // create a copy of this message
1073 if (message instanceof ShortMessage) {
1074 message = new FastShortMessage((ShortMessage) message);
1075 } else {
1076 message = (MidiMessage) message.clone();
1077 }
1078
1079 // create new MidiEvent
1080 MidiEvent me = new MidiEvent(message, tickPos);
1081 track.add(me);
1082 }
1083 }
1084 }
1085 }
1086 }
1087
1088 private static class RealTimeSequencerInfo extends MidiDevice.Info {
1089
1090 private static final String name = "Real Time Sequencer";
1091 private static final String vendor = "Oracle Corporation";
1092 private static final String description = "Software sequencer";
1093 private static final String version = "Version 1.0";
1094
1095 RealTimeSequencerInfo() {
1096 super(name, vendor, description, version);
1097 }
1098 } // class Info
1099
1100 private class ControllerListElement {
1101
1102 // $$jb: using an array for controllers b/c its
1103 // easier to deal with than turning all the
1104 // ints into objects to use a Vector
1105 int [] controllers;
1106 final ControllerEventListener listener;
1107
1108 private ControllerListElement(ControllerEventListener listener, int[] controllers) {
1109
1110 this.listener = listener;
1111 if (controllers == null) {
1112 controllers = new int[128];
1113 for (int i = 0; i < 128; i++) {
1114 controllers[i] = i;
1115 }
1116 }
1117 this.controllers = controllers;
1118 }
1119
1188 }
1189
1190 private int[] getControllers() {
1191
1192 // return a copy of our array of controllers,
1193 // so others can't mess with it
1194 if (controllers == null) {
1195 return null;
1196 }
1197
1198 int c[] = new int[controllers.length];
1199
1200 for(int i=0; i<controllers.length; i++){
1201 c[i] = controllers[i];
1202 }
1203 return c;
1204 }
1205
1206 } // class ControllerListElement
1207
1208 static class RecordingTrack {
1209
1210 private final Track track;
1211 private int channel;
1212
1213 RecordingTrack(Track track, int channel) {
1214 this.track = track;
1215 this.channel = channel;
1216 }
1217
1218 static RecordingTrack get(List<RecordingTrack> recordingTracks, Track track) {
1219
1220 synchronized(recordingTracks) {
1221 int size = recordingTracks.size();
1222
1223 for (int i = 0; i < size; i++) {
1224 RecordingTrack current = recordingTracks.get(i);
1225 if (current.track == track) {
1226 return current;
1227 }
1229 }
1230 return null;
1231 }
1232
1233 static Track get(List<RecordingTrack> recordingTracks, int channel) {
1234
1235 synchronized(recordingTracks) {
1236 int size = recordingTracks.size();
1237 for (int i = 0; i < size; i++) {
1238 RecordingTrack current = recordingTracks.get(i);
1239 if ((current.channel == channel) || (current.channel == -1)) {
1240 return current.track;
1241 }
1242 }
1243 }
1244 return null;
1245
1246 }
1247 }
1248
1249 final class PlayThread implements Runnable {
1250 private Thread thread;
1251 private final Object lock = new Object();
1252
1253 /** true if playback is interrupted (in close) */
1254 boolean interrupted = false;
1255 boolean isPumping = false;
1256
1257 private final DataPump dataPump = new DataPump();
1258
1259
1260 PlayThread() {
1261 // nearly MAX_PRIORITY
1262 int priority = Thread.NORM_PRIORITY
1263 + ((Thread.MAX_PRIORITY - Thread.NORM_PRIORITY) * 3) / 4;
1264 thread = JSSecurityManager.createThread(this,
1265 "Java Sound Sequencer", // name
1266 false, // daemon
1267 priority, // priority
1268 true); // doStart
1335 // dispose of thread
1336 interrupted = true;
1337 oldThread = thread;
1338 thread = null;
1339 }
1340 if (oldThread != null) {
1341 // wake up the thread if it's in wait()
1342 synchronized(lock) {
1343 lock.notifyAll();
1344 }
1345 }
1346 // wait for the thread to terminate itself,
1347 // but max. 2 seconds. Must not be synchronized!
1348 if (oldThread != null) {
1349 try {
1350 oldThread.join(2000);
1351 } catch (InterruptedException ie) {}
1352 }
1353 }
1354
1355 /**
1356 * Main process loop driving the media flow.
1357 *
1358 * Make sure to NOT synchronize on RealTimeSequencer
1359 * anywhere here (even implicit). That is a sure deadlock!
1360 */
1361 @Override
1362 public void run() {
1363
1364 while (!interrupted) {
1365 boolean EOM = false;
1366 boolean wasRunning = running;
1367 isPumping = !interrupted && running;
1368 while (!EOM && !interrupted && running) {
1369 EOM = dataPump.pump();
1370
1371 try {
1372 Thread.sleep(1);
1373 } catch (InterruptedException ie) {
1374 // ignore
1375 }
1376 }
1377 if (Printer.debug) {
1378 Printer.debug("Exited main pump loop because: ");
1379 if (EOM) Printer.debug(" -> EOM is reached");
1380 if (!running) Printer.debug(" -> running was set to false");
1381 if (interrupted) Printer.debug(" -> interrupted was set to true");
1393 try{
1394 message.setMessage(MidiUtils.META_END_OF_TRACK_TYPE, new byte[0], 0);
1395 } catch(InvalidMidiDataException e1) {}
1396 sendMetaEvents(message);
1397 }
1398 synchronized (lock) {
1399 isPumping = false;
1400 // wake up a waiting stop() method
1401 lock.notifyAll();
1402 while (!running && !interrupted) {
1403 try {
1404 lock.wait();
1405 } catch (Exception ex) {}
1406 }
1407 }
1408 } // end of while(!EOM && !interrupted && running)
1409 if (Printer.debug) Printer.debug("end of play thread");
1410 }
1411 }
1412
1413 /**
1414 * class that does the actual dispatching of events,
1415 * used to be in native in MMAPI.
1416 */
1417 private class DataPump {
1418 private float currTempo; // MPQ tempo
1419 private float tempoFactor; // 1.0 is default
1420 private float inverseTempoFactor;// = 1.0 / tempoFactor
1421 private long ignoreTempoEventAt; // ignore next META tempo during playback at this tick pos only
1422 private int resolution;
1423 private float divisionType;
1424 private long checkPointMillis; // microseconds at checkoint
1425 private long checkPointTick; // ticks at checkpoint
1426 private int[] noteOnCache; // bit-mask of notes that are currently on
1427 private Track[] tracks;
1428 private boolean[] trackDisabled; // if true, do not play this track
1429 private int[] trackReadPos; // read index per track
1430 private long lastTick;
1431 private boolean needReindex = false;
1432 private int currLoopCounter = 0;
1433
1434 //private sun.misc.Perf perf = sun.misc.Perf.getPerf();
1435 //private long perfFreq = perf.highResFrequency();
1436
1437 DataPump() {
1438 init();
1439 }
1440
1441 synchronized void init() {
1442 ignoreTempoEventAt = -1;
1443 tempoFactor = 1.0f;
1444 inverseTempoFactor = 1.0f;
1445 noteOnCache = new int[128];
1446 tracks = null;
1447 trackDisabled = null;
1448 }
1449
1450 synchronized void setTickPos(long tickPos) {
1451 long oldLastTick = tickPos;
1452 lastTick = tickPos;
1453 if (running) {
1454 notesOff(false);
1455 }
1456 if (running || tickPos > 0) {
1498 if (factor > 0 && factor != this.tempoFactor) {
1499 tempoFactor = factor;
1500 inverseTempoFactor = 1.0f / factor;
1501 // re-calculate check point
1502 checkPointMillis = 0;
1503 }
1504 }
1505
1506 float getTempoFactor() {
1507 return tempoFactor;
1508 }
1509
1510 synchronized void muteSoloChanged() {
1511 boolean[] newDisabled = makeDisabledArray();
1512 if (running) {
1513 applyDisabledTracks(trackDisabled, newDisabled);
1514 }
1515 trackDisabled = newDisabled;
1516 }
1517
1518 synchronized void setSequence(Sequence seq) {
1519 if (seq == null) {
1520 init();
1521 return;
1522 }
1523 tracks = seq.getTracks();
1524 muteSoloChanged();
1525 resolution = seq.getResolution();
1526 divisionType = seq.getDivisionType();
1527 trackReadPos = new int[tracks.length];
1528 // trigger re-initialization
1529 checkPointMillis = 0;
1530 needReindex = true;
1531 }
1532
1533 synchronized void resetLoopCount() {
1534 currLoopCounter = loopCount;
1535 }
1536
1537 void clearNoteOnCache() {
1548 if ((noteOnCache[i] & channelMask) != 0) {
1549 noteOnCache[i] ^= channelMask;
1550 // send note on with velocity 0
1551 getTransmitterList().sendMessage((ShortMessage.NOTE_ON | ch) | (i<<8), -1);
1552 done++;
1553 }
1554 }
1555 /* all notes off */
1556 getTransmitterList().sendMessage((ShortMessage.CONTROL_CHANGE | ch) | (123<<8), -1);
1557 /* sustain off */
1558 getTransmitterList().sendMessage((ShortMessage.CONTROL_CHANGE | ch) | (64<<8), -1);
1559 if (doControllers) {
1560 /* reset all controllers */
1561 getTransmitterList().sendMessage((ShortMessage.CONTROL_CHANGE | ch) | (121<<8), -1);
1562 done++;
1563 }
1564 }
1565 if (DEBUG_PUMP) Printer.println(" noteOff: sent "+done+" messages.");
1566 }
1567
1568 private boolean[] makeDisabledArray() {
1569 if (tracks == null) {
1570 return null;
1571 }
1572 boolean[] newTrackDisabled = new boolean[tracks.length];
1573 boolean[] solo;
1574 boolean[] mute;
1575 synchronized(RealTimeSequencer.this) {
1576 mute = trackMuted;
1577 solo = trackSolo;
1578 }
1579 // if one track is solo, then only play solo
1580 boolean hasSolo = false;
1581 if (solo != null) {
1582 for (int i = 0; i < solo.length; i++) {
1583 if (solo[i]) {
1584 hasSolo = true;
1585 break;
1586 }
1587 }
1635 }
1636 if (note >= 0) {
1637 int bit = 1<<(status & 0x0F);
1638 if ((noteOnCache[note] & bit) != 0) {
1639 // the bit is set. Send Note Off
1640 getTransmitterList().sendMessage(status | (note<<8), -1);
1641 // clear the bit
1642 noteOnCache[note] &= (0xFFFF ^ bit);
1643 done++;
1644 }
1645 }
1646 }
1647 }
1648 } catch (ArrayIndexOutOfBoundsException aioobe) {
1649 // this happens when messages are removed
1650 // from the track while this method executes
1651 }
1652 if (DEBUG_PUMP) Printer.println(" sendNoteOffIfOn: sent "+done+" messages.");
1653 }
1654
1655 /**
1656 * Runtime application of mute/solo:
1657 * if a track is muted that was previously playing, send
1658 * note off events for all currently playing notes.
1659 */
1660 private void applyDisabledTracks(boolean[] oldDisabled, boolean[] newDisabled) {
1661 byte[][] tempArray = null;
1662 synchronized(RealTimeSequencer.this) {
1663 for (int i = 0; i < newDisabled.length; i++) {
1664 if (((oldDisabled == null)
1665 || (i >= oldDisabled.length)
1666 || !oldDisabled[i])
1667 && newDisabled[i]) {
1668 // case that a track gets muted: need to
1669 // send appropriate note off events to prevent
1670 // hanging notes
1671
1672 if (tracks.length > i) {
1673 sendNoteOffIfOn(tracks[i], lastTick);
1674 }
1675 }
1676 else if ((oldDisabled != null)
1677 && (i < oldDisabled.length)
1678 && oldDisabled[i]
1759 int packedMsg = (ShortMessage.CONTROL_CHANGE | ch) | (co<<8) | (controllerValue<<16);
1760 getTransmitterList().sendMessage(packedMsg, -1);
1761 numControllersSent++;
1762 }
1763 }
1764 // send program change *after* controllers, to
1765 // correctly initialize banks
1766 if (progs[ch] >= 0) {
1767 getTransmitterList().sendMessage((ShortMessage.PROGRAM_CHANGE | ch) | (progs[ch]<<8), -1);
1768 }
1769 if (progs[ch] >= 0 || startTick == 0 || endTick == 0) {
1770 // reset pitch bend on this channel (E0 00 40)
1771 getTransmitterList().sendMessage((ShortMessage.PITCH_BEND | ch) | (0x40 << 16), -1);
1772 // reset sustain pedal on this channel
1773 getTransmitterList().sendMessage((ShortMessage.CONTROL_CHANGE | ch) | (64 << 8), -1);
1774 }
1775 }
1776 if (DEBUG_PUMP) Printer.println(" chaseTrackEvents track "+trackNum+": sent "+numControllersSent+" controllers.");
1777 }
1778
1779 /**
1780 * chase controllers and program for all tracks.
1781 */
1782 synchronized void chaseEvents(long startTick, long endTick) {
1783 if (DEBUG_PUMP) Printer.println(">> chaseEvents from tick "+startTick+".."+(endTick-1));
1784 byte[][] tempArray = new byte[128][16];
1785 for (int t = 0; t < tracks.length; t++) {
1786 if ((trackDisabled == null)
1787 || (trackDisabled.length <= t)
1788 || (!trackDisabled[t])) {
1789 // if track is not disabled, chase the events for it
1790 chaseTrackEvents(t, startTick, endTick, true, tempArray);
1791 }
1792 }
1793 if (DEBUG_PUMP) Printer.println("<< chaseEvents");
1794 }
1795
1796 // playback related methods (pumping)
1797
1798 private long getCurrentTimeMillis() {
1799 return System.nanoTime() / 1000000l;
1800 //return perf.highResCounter() * 1000 / perfFreq;
1801 }
1802
1803 private long millis2tick(long millis) {
1804 if (divisionType != Sequence.PPQ) {
1805 double dTick = ((((double) millis) * tempoFactor)
1806 * ((double) divisionType)
1807 * ((double) resolution))
1808 / ((double) 1000);
1809 return (long) dTick;
1810 }
1811 return MidiUtils.microsec2ticks(millis * 1000,
1812 currTempo * inverseTempoFactor,
1813 resolution);
1814 }
1815
1878 if (vel > 0) {
1879 // if velocity > 0 set the bit in the noteOnCache array
1880 noteOnCache[note] |= 1<<(msgStatus & 0x0F);
1881 } else {
1882 // if velocity = 0 clear the bit in the noteOnCache array
1883 noteOnCache[note] &= (0xFFFF ^ (1<<(msgStatus & 0x0F)));
1884 }
1885 break;
1886 }
1887
1888 case ShortMessage.CONTROL_CHANGE:
1889 // if controller message, send controller listeners
1890 sendControllerEvents(message);
1891 break;
1892
1893 }
1894 }
1895 return changesPending;
1896 }
1897
1898 /** the main pump method
1899 * @return true if end of sequence is reached
1900 */
1901 synchronized boolean pump() {
1902 long currMillis;
1903 long targetTick = lastTick;
1904 MidiEvent currEvent;
1905 boolean changesPending = false;
1906 boolean doLoop = false;
1907 boolean EOM = false;
1908
1909 currMillis = getCurrentTimeMillis();
1910 int finishedTracks = 0;
1911 do {
1912 changesPending = false;
1913
1914 // need to re-find indexes in tracks?
1915 if (needReindex) {
1916 if (DEBUG_PUMP) Printer.println("Need to re-index at "+currMillis+" millis. TargetTick="+targetTick);
1917 if (trackReadPos.length < tracks.length) {
2055 // is correct, and doesn't drift away with several repetition,
2056 // there is a slight lag when looping back, probably caused
2057 // by the chasing.
2058
2059 checkPointMillis = oldCheckPointMillis + tick2millis(loopEndTick - checkPointTick);
2060 checkPointTick = loopStart;
2061 if (DEBUG_PUMP) Printer.println(" Setting currMillis="+currMillis
2062 +" new checkPointMillis="+checkPointMillis
2063 +" new checkPointTick="+checkPointTick);
2064 // no need for reindexing, is done in setTickPos
2065 needReindex = false;
2066 changesPending = false;
2067 // reset doLoop flag
2068 doLoop = false;
2069 EOM = false;
2070 }
2071 } while (changesPending);
2072
2073 return EOM;
2074 }
2075 } // class DataPump
2076 }
|