1 /*
2  * Copyright (c) 2003, 2019, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
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     /**
65      * Event Dispatcher thread. Should be using a shared event
66      * dispatcher instance with a factory in EventDispatcher
67      */
68     private static final Map<ThreadGroup, EventDispatcher> dispatchers =
69             new WeakHashMap<>();
70 
71     /**
72      * All RealTimeSequencers share this info object.
73      */
74     static final MidiDevice.Info info = new RealTimeSequencerInfo();
75 
76 
77     private static final Sequencer.SyncMode[] masterSyncModes = { Sequencer.SyncMode.INTERNAL_CLOCK };
78     private static final Sequencer.SyncMode[] slaveSyncModes  = { Sequencer.SyncMode.NO_SYNC };
79 
80     private static final Sequencer.SyncMode masterSyncMode    = Sequencer.SyncMode.INTERNAL_CLOCK;
81     private static final Sequencer.SyncMode slaveSyncMode     = Sequencer.SyncMode.NO_SYNC;
82 
83     /**
84      * Sequence on which this sequencer is operating.
85      */
86     private Sequence sequence = null;
87 
88     // caches
89 
90     /**
91      * Same for setTempoInMPQ...
92      * -1 means not set.
93      */
94     private double cacheTempoMPQ = -1;
95 
96     /**
97      * cache value for tempo factor until sequence is set
98      * -1 means not set.
99      */
100     private float cacheTempoFactor = -1;
101 
102     /** if a particular track is muted */
103     private boolean[] trackMuted = null;
104     /** if a particular track is solo */
105     private boolean[] trackSolo = null;
106 
107     /** tempo cache for getMicrosecondPosition */
108     private final MidiUtils.TempoCache tempoCache = new MidiUtils.TempoCache();
109 
110     /**
111      * True if the sequence is running.
112      */
113     private volatile boolean running;
114 
115     /**
116      * the thread for pushing out the MIDI messages.
117      */
118     private PlayThread playThread;
119 
120     /**
121      * True if we are recording.
122      */
123     private volatile boolean recording;
124 
125     /**
126      * List of tracks to which we're recording.
127      */
128     private final List<RecordingTrack> recordingTracks = new ArrayList<>();
129 
130     private long loopStart = 0;
131     private long loopEnd = -1;
132     private int loopCount = 0;
133 
134     /**
135      * Meta event listeners.
136      */
137     private final ArrayList<Object> metaEventListeners = new ArrayList<>();
138 
139     /**
140      * Control change listeners.
141      */
142     private final ArrayList<ControllerListElement> controllerEventListeners = new ArrayList<>();
143 
144     /**
145      * automatic connection support.
146      */
147     private boolean autoConnect = false;
148 
149     /**
150      * if we need to autoconnect at next open.
151      */
152     private boolean doAutoConnectAtNextOpen = false;
153 
154     /**
155      * the receiver that this device is auto-connected to.
156      */
157     Receiver autoConnectedReceiver = null;
158 
159 
160     /* ****************************** CONSTRUCTOR ****************************** */
161 
RealTimeSequencer()162     RealTimeSequencer(){
163         super(info);
164     }
165 
166     /* ****************************** SEQUENCER METHODS ******************** */
167 
168     @Override
setSequence(Sequence sequence)169     public synchronized void setSequence(Sequence sequence)
170         throws InvalidMidiDataException {
171         if (sequence != this.sequence) {
172             if (this.sequence != null && sequence == null) {
173                 setCaches();
174                 stop();
175                 // initialize some non-cached values
176                 trackMuted = null;
177                 trackSolo = null;
178                 loopStart = 0;
179                 loopEnd = -1;
180                 loopCount = 0;
181                 if (getDataPump() != null) {
182                     getDataPump().setTickPos(0);
183                     getDataPump().resetLoopCount();
184                 }
185             }
186 
187             if (playThread != null) {
188                 playThread.setSequence(sequence);
189             }
190 
191             // store this sequence (do not copy - we want to give the possibility
192             // of modifying the sequence at runtime)
193             this.sequence = sequence;
194 
195             if (sequence != null) {
196                 tempoCache.refresh(sequence);
197                 // rewind to the beginning
198                 setTickPosition(0);
199                 // propagate caches
200                 propagateCaches();
201             }
202         }
203         else if (sequence != null) {
204             tempoCache.refresh(sequence);
205             if (playThread != null) {
206                 playThread.setSequence(sequence);
207             }
208         }
209     }
210 
211     @Override
setSequence(InputStream stream)212     public synchronized void setSequence(InputStream stream) throws IOException, InvalidMidiDataException {
213         if (stream == null) {
214             setSequence((Sequence) null);
215             return;
216         }
217 
218         Sequence seq = MidiSystem.getSequence(stream); // can throw IOException, InvalidMidiDataException
219 
220         setSequence(seq);
221     }
222 
223     @Override
getSequence()224     public Sequence getSequence() {
225         return sequence;
226     }
227 
228     @Override
start()229     public synchronized void start() {
230         // sequencer not open: throw an exception
231         if (!isOpen()) {
232             throw new IllegalStateException("sequencer not open");
233         }
234 
235         // sequence not available: throw an exception
236         if (sequence == null) {
237             throw new IllegalStateException("sequence not set");
238         }
239 
240         // already running: return quietly
241         if (running == true) {
242             return;
243         }
244 
245         // start playback
246         implStart();
247     }
248 
249     @Override
stop()250     public synchronized void stop() {
251         if (!isOpen()) {
252             throw new IllegalStateException("sequencer not open");
253         }
254         stopRecording();
255 
256         // not running; just return
257         if (running == false) {
258             return;
259         }
260 
261         // stop playback
262         implStop();
263     }
264 
265     @Override
isRunning()266     public boolean isRunning() {
267         return running;
268     }
269 
270     @Override
startRecording()271     public void startRecording() {
272         if (!isOpen()) {
273             throw new IllegalStateException("Sequencer not open");
274         }
275 
276         start();
277         recording = true;
278     }
279 
280     @Override
stopRecording()281     public void stopRecording() {
282         if (!isOpen()) {
283             throw new IllegalStateException("Sequencer not open");
284         }
285         recording = false;
286     }
287 
288     @Override
isRecording()289     public boolean isRecording() {
290         return recording;
291     }
292 
293     @Override
recordEnable(Track track, int channel)294     public void recordEnable(Track track, int channel) {
295         if (!findTrack(track)) {
296             throw new IllegalArgumentException("Track does not exist in the current sequence");
297         }
298 
299         synchronized(recordingTracks) {
300             RecordingTrack rc = RecordingTrack.get(recordingTracks, track);
301             if (rc != null) {
302                 rc.channel = channel;
303             } else {
304                 recordingTracks.add(new RecordingTrack(track, channel));
305             }
306         }
307 
308     }
309 
310     @Override
recordDisable(Track track)311     public void recordDisable(Track track) {
312         synchronized(recordingTracks) {
313             RecordingTrack rc = RecordingTrack.get(recordingTracks, track);
314             if (rc != null) {
315                 recordingTracks.remove(rc);
316             }
317         }
318 
319     }
320 
findTrack(Track track)321     private boolean findTrack(Track track) {
322         boolean found = false;
323         if (sequence != null) {
324             Track[] tracks = sequence.getTracks();
325             for (int i = 0; i < tracks.length; i++) {
326                 if (track == tracks[i]) {
327                     found = true;
328                     break;
329                 }
330             }
331         }
332         return found;
333     }
334 
335     @Override
getTempoInBPM()336     public float getTempoInBPM() {
337         return (float) MidiUtils.convertTempo(getTempoInMPQ());
338     }
339 
340     @Override
setTempoInBPM(float bpm)341     public void setTempoInBPM(float bpm) {
342         if (bpm <= 0) {
343             // should throw IllegalArgumentException
344             bpm = 1.0f;
345         }
346 
347         setTempoInMPQ((float) MidiUtils.convertTempo((double) bpm));
348     }
349 
350     @Override
getTempoInMPQ()351     public float getTempoInMPQ() {
352         if (needCaching()) {
353             // if the sequencer is closed, return cached value
354             if (cacheTempoMPQ != -1) {
355                 return (float) cacheTempoMPQ;
356             }
357             // if sequence is set, return current tempo
358             if (sequence != null) {
359                 return tempoCache.getTempoMPQAt(getTickPosition());
360             }
361 
362             // last resort: return a standard tempo: 120bpm
363             return (float) MidiUtils.DEFAULT_TEMPO_MPQ;
364         }
365         return getDataPump().getTempoMPQ();
366     }
367 
368     @Override
setTempoInMPQ(float mpq)369     public void setTempoInMPQ(float mpq) {
370         if (mpq <= 0) {
371             // should throw IllegalArgumentException
372             mpq = 1.0f;
373         }
374         if (needCaching()) {
375             // cache the value
376             cacheTempoMPQ = mpq;
377         } else {
378             // set the native tempo in MPQ
379             getDataPump().setTempoMPQ(mpq);
380 
381             // reset the tempoInBPM and tempoInMPQ values so we won't use them again
382             cacheTempoMPQ = -1;
383         }
384     }
385 
386     @Override
setTempoFactor(float factor)387     public void setTempoFactor(float factor) {
388         if (factor <= 0) {
389             // should throw IllegalArgumentException
390             return;
391         }
392         if (needCaching()) {
393             cacheTempoFactor = factor;
394         } else {
395             getDataPump().setTempoFactor(factor);
396             // don't need cache anymore
397             cacheTempoFactor = -1;
398         }
399     }
400 
401     @Override
getTempoFactor()402     public float getTempoFactor() {
403         if (needCaching()) {
404             if (cacheTempoFactor != -1) {
405                 return cacheTempoFactor;
406             }
407             return 1.0f;
408         }
409         return getDataPump().getTempoFactor();
410     }
411 
412     @Override
getTickLength()413     public long getTickLength() {
414         if (sequence == null) {
415             return 0;
416         }
417 
418         return sequence.getTickLength();
419     }
420 
421     @Override
getTickPosition()422     public synchronized long getTickPosition() {
423         if (getDataPump() == null || sequence == null) {
424             return 0;
425         }
426 
427         return getDataPump().getTickPos();
428     }
429 
430     @Override
setTickPosition(long tick)431     public synchronized void setTickPosition(long tick) {
432         if (tick < 0) {
433             // should throw IllegalArgumentException
434             return;
435         }
436         if (getDataPump() == null) {
437             if (tick != 0) {
438                 // throw new InvalidStateException("cannot set position in closed state");
439             }
440         }
441         else if (sequence == null) {
442             if (tick != 0) {
443                 // throw new InvalidStateException("cannot set position if sequence is not set");
444             }
445         } else {
446             getDataPump().setTickPos(tick);
447         }
448     }
449 
450     @Override
getMicrosecondLength()451     public long getMicrosecondLength() {
452         if (sequence == null) {
453             return 0;
454         }
455 
456         return sequence.getMicrosecondLength();
457     }
458 
459     @Override
getMicrosecondPosition()460     public long getMicrosecondPosition() {
461         if (getDataPump() == null || sequence == null) {
462             return 0;
463         }
464         synchronized (tempoCache) {
465             return MidiUtils.tick2microsecond(sequence, getDataPump().getTickPos(), tempoCache);
466         }
467     }
468 
469     @Override
setMicrosecondPosition(long microseconds)470     public void setMicrosecondPosition(long microseconds) {
471         if (microseconds < 0) {
472             // should throw IllegalArgumentException
473             return;
474         }
475         if (getDataPump() == null) {
476             if (microseconds != 0) {
477                 // throw new InvalidStateException("cannot set position in closed state");
478             }
479         }
480         else if (sequence == null) {
481             if (microseconds != 0) {
482                 // throw new InvalidStateException("cannot set position if sequence is not set");
483             }
484         } else {
485             synchronized(tempoCache) {
486                 setTickPosition(MidiUtils.microsecond2tick(sequence, microseconds, tempoCache));
487             }
488         }
489     }
490 
491     @Override
setMasterSyncMode(Sequencer.SyncMode sync)492     public void setMasterSyncMode(Sequencer.SyncMode sync) {
493         // not supported
494     }
495 
496     @Override
getMasterSyncMode()497     public Sequencer.SyncMode getMasterSyncMode() {
498         return masterSyncMode;
499     }
500 
501     @Override
getMasterSyncModes()502     public Sequencer.SyncMode[] getMasterSyncModes() {
503         Sequencer.SyncMode[] returnedModes = new Sequencer.SyncMode[masterSyncModes.length];
504         System.arraycopy(masterSyncModes, 0, returnedModes, 0, masterSyncModes.length);
505         return returnedModes;
506     }
507 
508     @Override
setSlaveSyncMode(Sequencer.SyncMode sync)509     public void setSlaveSyncMode(Sequencer.SyncMode sync) {
510         // not supported
511     }
512 
513     @Override
getSlaveSyncMode()514     public Sequencer.SyncMode getSlaveSyncMode() {
515         return slaveSyncMode;
516     }
517 
518     @Override
getSlaveSyncModes()519     public Sequencer.SyncMode[] getSlaveSyncModes() {
520         Sequencer.SyncMode[] returnedModes = new Sequencer.SyncMode[slaveSyncModes.length];
521         System.arraycopy(slaveSyncModes, 0, returnedModes, 0, slaveSyncModes.length);
522         return returnedModes;
523     }
524 
getTrackCount()525     int getTrackCount() {
526         Sequence seq = getSequence();
527         if (seq != null) {
528             // $$fb wish there was a nicer way to get the number of tracks...
529             return sequence.getTracks().length;
530         }
531         return 0;
532     }
533 
534     @Override
setTrackMute(int track, boolean mute)535     public synchronized void setTrackMute(int track, boolean mute) {
536         int trackCount = getTrackCount();
537         if (track < 0 || track >= getTrackCount()) return;
538         trackMuted = ensureBoolArraySize(trackMuted, trackCount);
539         trackMuted[track] = mute;
540         if (getDataPump() != null) {
541             getDataPump().muteSoloChanged();
542         }
543     }
544 
545     @Override
getTrackMute(int track)546     public synchronized boolean getTrackMute(int track) {
547         if (track < 0 || track >= getTrackCount()) return false;
548         if (trackMuted == null || trackMuted.length <= track) return false;
549         return trackMuted[track];
550     }
551 
552     @Override
setTrackSolo(int track, boolean solo)553     public synchronized void setTrackSolo(int track, boolean solo) {
554         int trackCount = getTrackCount();
555         if (track < 0 || track >= getTrackCount()) return;
556         trackSolo = ensureBoolArraySize(trackSolo, trackCount);
557         trackSolo[track] = solo;
558         if (getDataPump() != null) {
559             getDataPump().muteSoloChanged();
560         }
561     }
562 
563     @Override
getTrackSolo(int track)564     public synchronized boolean getTrackSolo(int track) {
565         if (track < 0 || track >= getTrackCount()) return false;
566         if (trackSolo == null || trackSolo.length <= track) return false;
567         return trackSolo[track];
568     }
569 
570     @Override
addMetaEventListener(MetaEventListener listener)571     public boolean addMetaEventListener(MetaEventListener listener) {
572         synchronized(metaEventListeners) {
573             if (! metaEventListeners.contains(listener)) {
574 
575                 metaEventListeners.add(listener);
576             }
577             return true;
578         }
579     }
580 
581     @Override
removeMetaEventListener(MetaEventListener listener)582     public void removeMetaEventListener(MetaEventListener listener) {
583         synchronized(metaEventListeners) {
584             int index = metaEventListeners.indexOf(listener);
585             if (index >= 0) {
586                 metaEventListeners.remove(index);
587             }
588         }
589     }
590 
591     @Override
addControllerEventListener(ControllerEventListener listener, int[] controllers)592     public int[] addControllerEventListener(ControllerEventListener listener, int[] controllers) {
593         synchronized(controllerEventListeners) {
594 
595             // first find the listener.  if we have one, add the controllers
596             // if not, create a new element for it.
597             ControllerListElement cve = null;
598             boolean flag = false;
599             for(int i=0; i < controllerEventListeners.size(); i++) {
600 
601                 cve = controllerEventListeners.get(i);
602 
603                 if (cve.listener.equals(listener)) {
604                     cve.addControllers(controllers);
605                     flag = true;
606                     break;
607                 }
608             }
609             if (!flag) {
610                 cve = new ControllerListElement(listener, controllers);
611                 controllerEventListeners.add(cve);
612             }
613 
614             // and return all the controllers this listener is interested in
615             return cve.getControllers();
616         }
617     }
618 
619     @Override
removeControllerEventListener(ControllerEventListener listener, int[] controllers)620     public int[] removeControllerEventListener(ControllerEventListener listener, int[] controllers) {
621         synchronized(controllerEventListeners) {
622             ControllerListElement cve = null;
623             boolean flag = false;
624             for (int i=0; i < controllerEventListeners.size(); i++) {
625                 cve = controllerEventListeners.get(i);
626                 if (cve.listener.equals(listener)) {
627                     cve.removeControllers(controllers);
628                     flag = true;
629                     break;
630                 }
631             }
632             if (!flag) {
633                 return new int[0];
634             }
635             if (controllers == null) {
636                 int index = controllerEventListeners.indexOf(cve);
637                 if (index >= 0) {
638                     controllerEventListeners.remove(index);
639                 }
640                 return new int[0];
641             }
642             return cve.getControllers();
643         }
644     }
645 
646     ////////////////// LOOPING (added in 1.5) ///////////////////////
647 
648     @Override
setLoopStartPoint(long tick)649     public void setLoopStartPoint(long tick) {
650         if ((tick > getTickLength())
651             || ((loopEnd != -1) && (tick > loopEnd))
652             || (tick < 0)) {
653             throw new IllegalArgumentException("invalid loop start point: "+tick);
654         }
655         loopStart = tick;
656     }
657 
658     @Override
getLoopStartPoint()659     public long getLoopStartPoint() {
660         return loopStart;
661     }
662 
663     @Override
setLoopEndPoint(long tick)664     public void setLoopEndPoint(long tick) {
665         if ((tick > getTickLength())
666             || ((loopStart > tick) && (tick != -1))
667             || (tick < -1)) {
668             throw new IllegalArgumentException("invalid loop end point: "+tick);
669         }
670         loopEnd = tick;
671     }
672 
673     @Override
getLoopEndPoint()674     public long getLoopEndPoint() {
675         return loopEnd;
676     }
677 
678     @Override
setLoopCount(int count)679     public void setLoopCount(int count) {
680         if (count != LOOP_CONTINUOUSLY
681             && count < 0) {
682             throw new IllegalArgumentException("illegal value for loop count: "+count);
683         }
684         loopCount = count;
685         if (getDataPump() != null) {
686             getDataPump().resetLoopCount();
687         }
688     }
689 
690     @Override
getLoopCount()691     public int getLoopCount() {
692         return loopCount;
693     }
694 
695     /* *********************************** play control ************************* */
696 
697     @Override
implOpen()698     protected void implOpen() throws MidiUnavailableException {
699         //openInternalSynth();
700 
701         // create PlayThread
702         playThread = new PlayThread();
703 
704         //id = nOpen();
705         //if (id == 0) {
706         //    throw new MidiUnavailableException("unable to open sequencer");
707         //}
708         if (sequence != null) {
709             playThread.setSequence(sequence);
710         }
711 
712         // propagate caches
713         propagateCaches();
714 
715         if (doAutoConnectAtNextOpen) {
716             doAutoConnect();
717         }
718     }
719 
doAutoConnect()720     private void doAutoConnect() {
721         Receiver rec = null;
722         // first try to connect to the default synthesizer
723         // IMPORTANT: this code needs to be synch'ed with
724         //            MidiSystem.getSequencer(boolean), because the same
725         //            algorithm needs to be used!
726         try {
727             Synthesizer synth = MidiSystem.getSynthesizer();
728             if (synth instanceof ReferenceCountingDevice) {
729                 rec = ((ReferenceCountingDevice) synth).getReceiverReferenceCounting();
730             } else {
731                 synth.open();
732                 try {
733                     rec = synth.getReceiver();
734                 } finally {
735                     // make sure that the synth is properly closed
736                     if (rec == null) {
737                         synth.close();
738                     }
739                 }
740             }
741         } catch (Exception e) {
742             // something went wrong with synth
743         }
744         if (rec == null) {
745             // then try to connect to the default Receiver
746             try {
747                 rec = MidiSystem.getReceiver();
748             } catch (Exception e) {
749                 // something went wrong. Nothing to do then!
750             }
751         }
752         if (rec != null) {
753             autoConnectedReceiver = rec;
754             try {
755                 getTransmitter().setReceiver(rec);
756             } catch (Exception e) {}
757         }
758     }
759 
propagateCaches()760     private synchronized void propagateCaches() {
761         // only set caches if open and sequence is set
762         if (sequence != null && isOpen()) {
763             if (cacheTempoFactor != -1) {
764                 setTempoFactor(cacheTempoFactor);
765             }
766             if (cacheTempoMPQ == -1) {
767                 setTempoInMPQ((new MidiUtils.TempoCache(sequence)).getTempoMPQAt(getTickPosition()));
768             } else {
769                 setTempoInMPQ((float) cacheTempoMPQ);
770             }
771         }
772     }
773 
774     /**
775      * populate the caches with the current values.
776      */
setCaches()777     private synchronized void setCaches() {
778         cacheTempoFactor = getTempoFactor();
779         cacheTempoMPQ = getTempoInMPQ();
780     }
781 
782     @Override
implClose()783     protected synchronized void implClose() {
784         if (playThread == null) {
785             if (Printer.err) Printer.err("RealTimeSequencer.implClose() called, but playThread not instanciated!");
786         } else {
787             // Interrupt playback loop.
788             playThread.close();
789             playThread = null;
790         }
791 
792         super.implClose();
793 
794         sequence = null;
795         running = false;
796         cacheTempoMPQ = -1;
797         cacheTempoFactor = -1;
798         trackMuted = null;
799         trackSolo = null;
800         loopStart = 0;
801         loopEnd = -1;
802         loopCount = 0;
803 
804         /** if this sequencer is set to autoconnect, need to
805          * re-establish the connection at next open!
806          */
807         doAutoConnectAtNextOpen = autoConnect;
808 
809         if (autoConnectedReceiver != null) {
810             try {
811                 autoConnectedReceiver.close();
812             } catch (Exception e) {}
813             autoConnectedReceiver = null;
814         }
815     }
816 
implStart()817     void implStart() {
818         if (playThread == null) {
819             if (Printer.err) Printer.err("RealTimeSequencer.implStart() called, but playThread not instanciated!");
820             return;
821         }
822 
823         tempoCache.refresh(sequence);
824         if (!running) {
825             running  = true;
826             playThread.start();
827         }
828     }
829 
implStop()830     void implStop() {
831         if (playThread == null) {
832             if (Printer.err) Printer.err("RealTimeSequencer.implStop() called, but playThread not instanciated!");
833             return;
834         }
835 
836         recording = false;
837         if (running) {
838             running = false;
839             playThread.stop();
840         }
841     }
842 
getEventDispatcher()843     private static EventDispatcher getEventDispatcher() {
844         // create and start the global event thread
845         //TODO  need a way to stop this thread when the engine is done
846         final ThreadGroup tg = Thread.currentThread().getThreadGroup();
847         synchronized (dispatchers) {
848             EventDispatcher eventDispatcher = dispatchers.get(tg);
849             if (eventDispatcher == null) {
850                 eventDispatcher = new EventDispatcher();
851                 dispatchers.put(tg, eventDispatcher);
852                 eventDispatcher.start();
853             }
854             return eventDispatcher;
855         }
856     }
857 
858     /**
859      * Send midi player events.
860      * must not be synchronized on "this"
861      */
sendMetaEvents(MidiMessage message)862     void sendMetaEvents(MidiMessage message) {
863         if (metaEventListeners.size() == 0) return;
864 
865         getEventDispatcher().sendAudioEvents(message, metaEventListeners);
866     }
867 
868     /**
869      * Send midi player events.
870      */
sendControllerEvents(MidiMessage message)871     void sendControllerEvents(MidiMessage message) {
872         int size = controllerEventListeners.size();
873         if (size == 0) return;
874 
875         if (! (message instanceof ShortMessage)) {
876             return;
877         }
878         ShortMessage msg = (ShortMessage) message;
879         int controller = msg.getData1();
880         List<Object> sendToListeners = new ArrayList<>();
881         for (int i = 0; i < size; i++) {
882             ControllerListElement cve = controllerEventListeners.get(i);
883             for(int j = 0; j < cve.controllers.length; j++) {
884                 if (cve.controllers[j] == controller) {
885                     sendToListeners.add(cve.listener);
886                     break;
887                 }
888             }
889         }
890         getEventDispatcher().sendAudioEvents(message, sendToListeners);
891     }
892 
needCaching()893     private boolean needCaching() {
894         return !isOpen() || (sequence == null) || (playThread == null);
895     }
896 
897     /**
898      * return the data pump instance, owned by play thread
899      * if playthread is null, return null.
900      * This method is guaranteed to return non-null if
901      * needCaching returns false
902      */
getDataPump()903     private DataPump getDataPump() {
904         if (playThread != null) {
905             return playThread.getDataPump();
906         }
907         return null;
908     }
909 
getTempoCache()910     private MidiUtils.TempoCache getTempoCache() {
911         return tempoCache;
912     }
913 
ensureBoolArraySize(boolean[] array, int desiredSize)914     private static boolean[] ensureBoolArraySize(boolean[] array, int desiredSize) {
915         if (array == null) {
916             return new boolean[desiredSize];
917         }
918         if (array.length < desiredSize) {
919             boolean[] newArray = new boolean[desiredSize];
920             System.arraycopy(array, 0, newArray, 0, array.length);
921             return newArray;
922         }
923         return array;
924     }
925 
926     // OVERRIDES OF ABSTRACT MIDI DEVICE METHODS
927 
928     @Override
hasReceivers()929     protected boolean hasReceivers() {
930         return true;
931     }
932 
933     // for recording
934     @Override
createReceiver()935     protected Receiver createReceiver() throws MidiUnavailableException {
936         return new SequencerReceiver();
937     }
938 
939     @Override
hasTransmitters()940     protected boolean hasTransmitters() {
941         return true;
942     }
943 
944     @Override
createTransmitter()945     protected Transmitter createTransmitter() throws MidiUnavailableException {
946         return new SequencerTransmitter();
947     }
948 
949     // interface AutoConnectSequencer
950     @Override
setAutoConnect(Receiver autoConnectedReceiver)951     public void setAutoConnect(Receiver autoConnectedReceiver) {
952         this.autoConnect = (autoConnectedReceiver != null);
953         this.autoConnectedReceiver = autoConnectedReceiver;
954     }
955 
956     /**
957      * An own class to distinguish the class name from
958      * the transmitter of other devices.
959      */
960     private class SequencerTransmitter extends BasicTransmitter {
SequencerTransmitter()961         private SequencerTransmitter() {
962             super();
963         }
964     }
965 
966     final class SequencerReceiver extends AbstractReceiver {
967 
968         @Override
implSend(MidiMessage message, long timeStamp)969         void implSend(MidiMessage message, long timeStamp) {
970             if (recording) {
971                 long tickPos = 0;
972 
973                 // convert timeStamp to ticks
974                 if (timeStamp < 0) {
975                     tickPos = getTickPosition();
976                 } else {
977                     synchronized(tempoCache) {
978                         tickPos = MidiUtils.microsecond2tick(sequence, timeStamp, tempoCache);
979                     }
980                 }
981 
982                 // and record to the first matching Track
983                 Track track = null;
984                 // do not record real-time events
985                 // see 5048381: NullPointerException when saving a MIDI sequence
986                 if (message.getLength() > 1) {
987                     if (message instanceof ShortMessage) {
988                         ShortMessage sm = (ShortMessage) message;
989                         // all real-time messages have 0xF in the high nibble of the status byte
990                         if ((sm.getStatus() & 0xF0) != 0xF0) {
991                             track = RecordingTrack.get(recordingTracks, sm.getChannel());
992                         }
993                     } else {
994                         // $$jb: where to record meta, sysex events?
995                         // $$fb: the first recording track
996                         track = RecordingTrack.get(recordingTracks, -1);
997                     }
998                     if (track != null) {
999                         // create a copy of this message
1000                         if (message instanceof ShortMessage) {
1001                             message = new FastShortMessage((ShortMessage) message);
1002                         } else {
1003                             message = (MidiMessage) message.clone();
1004                         }
1005 
1006                         // create new MidiEvent
1007                         MidiEvent me = new MidiEvent(message, tickPos);
1008                         track.add(me);
1009                     }
1010                 }
1011             }
1012         }
1013     }
1014 
1015     private static class RealTimeSequencerInfo extends MidiDevice.Info {
1016 
1017         private static final String name = "Real Time Sequencer";
1018         private static final String vendor = "Oracle Corporation";
1019         private static final String description = "Software sequencer";
1020         private static final String version = "Version 1.0";
1021 
RealTimeSequencerInfo()1022         RealTimeSequencerInfo() {
1023             super(name, vendor, description, version);
1024         }
1025     } // class Info
1026 
1027     private class ControllerListElement {
1028 
1029         // $$jb: using an array for controllers b/c its
1030         //       easier to deal with than turning all the
1031         //       ints into objects to use a Vector
1032         int []  controllers;
1033         final ControllerEventListener listener;
1034 
ControllerListElement(ControllerEventListener listener, int[] controllers)1035         private ControllerListElement(ControllerEventListener listener, int[] controllers) {
1036 
1037             this.listener = listener;
1038             if (controllers == null) {
1039                 controllers = new int[128];
1040                 for (int i = 0; i < 128; i++) {
1041                     controllers[i] = i;
1042                 }
1043             }
1044             this.controllers = controllers;
1045         }
1046 
addControllers(int[] c)1047         private void addControllers(int[] c) {
1048 
1049             if (c==null) {
1050                 controllers = new int[128];
1051                 for (int i = 0; i < 128; i++) {
1052                     controllers[i] = i;
1053                 }
1054                 return;
1055             }
1056             int[] temp = new int[ controllers.length + c.length ];
1057             int elements;
1058 
1059             // first add what we have
1060             for(int i=0; i<controllers.length; i++) {
1061                 temp[i] = controllers[i];
1062             }
1063             elements = controllers.length;
1064             // now add the new controllers only if we don't already have them
1065             for(int i=0; i<c.length; i++) {
1066                 boolean flag = false;
1067 
1068                 for(int j=0; j<controllers.length; j++) {
1069                     if (c[i] == controllers[j]) {
1070                         flag = true;
1071                         break;
1072                     }
1073                 }
1074                 if (!flag) {
1075                     temp[elements++] = c[i];
1076                 }
1077             }
1078             // now keep only the elements we need
1079             int[] newc = new int[ elements ];
1080             for(int i=0; i<elements; i++){
1081                 newc[i] = temp[i];
1082             }
1083             controllers = newc;
1084         }
1085 
removeControllers(int[] c)1086         private void removeControllers(int[] c) {
1087 
1088             if (c==null) {
1089                 controllers = new int[0];
1090             } else {
1091                 int[] temp = new int[ controllers.length ];
1092                 int elements = 0;
1093 
1094 
1095                 for(int i=0; i<controllers.length; i++){
1096                     boolean flag = false;
1097                     for(int j=0; j<c.length; j++) {
1098                         if (controllers[i] == c[j]) {
1099                             flag = true;
1100                             break;
1101                         }
1102                     }
1103                     if (!flag){
1104                         temp[elements++] = controllers[i];
1105                     }
1106                 }
1107                 // now keep only the elements remaining
1108                 int[] newc = new int[ elements ];
1109                 for(int i=0; i<elements; i++) {
1110                     newc[i] = temp[i];
1111                 }
1112                 controllers = newc;
1113 
1114             }
1115         }
1116 
getControllers()1117         private int[] getControllers() {
1118 
1119             // return a copy of our array of controllers,
1120             // so others can't mess with it
1121             if (controllers == null) {
1122                 return null;
1123             }
1124 
1125             int[] c = new int[controllers.length];
1126 
1127             for(int i=0; i<controllers.length; i++){
1128                 c[i] = controllers[i];
1129             }
1130             return c;
1131         }
1132 
1133     } // class ControllerListElement
1134 
1135     static class RecordingTrack {
1136 
1137         private final Track track;
1138         private int channel;
1139 
RecordingTrack(Track track, int channel)1140         RecordingTrack(Track track, int channel) {
1141             this.track = track;
1142             this.channel = channel;
1143         }
1144 
get(List<RecordingTrack> recordingTracks, Track track)1145         static RecordingTrack get(List<RecordingTrack> recordingTracks, Track track) {
1146 
1147             synchronized(recordingTracks) {
1148                 int size = recordingTracks.size();
1149 
1150                 for (int i = 0; i < size; i++) {
1151                     RecordingTrack current = recordingTracks.get(i);
1152                     if (current.track == track) {
1153                         return current;
1154                     }
1155                 }
1156             }
1157             return null;
1158         }
1159 
get(List<RecordingTrack> recordingTracks, int channel)1160         static Track get(List<RecordingTrack> recordingTracks, int channel) {
1161 
1162             synchronized(recordingTracks) {
1163                 int size = recordingTracks.size();
1164                 for (int i = 0; i < size; i++) {
1165                     RecordingTrack current = recordingTracks.get(i);
1166                     if ((current.channel == channel) || (current.channel == -1)) {
1167                         return current.track;
1168                     }
1169                 }
1170             }
1171             return null;
1172 
1173         }
1174     }
1175 
1176     final class PlayThread implements Runnable {
1177         private Thread thread;
1178         private final Object lock = new Object();
1179 
1180         /** true if playback is interrupted (in close) */
1181         boolean interrupted = false;
1182         boolean isPumping = false;
1183 
1184         private final DataPump dataPump = new DataPump();
1185 
1186 
PlayThread()1187         PlayThread() {
1188             // nearly MAX_PRIORITY
1189             int priority = Thread.NORM_PRIORITY
1190                 + ((Thread.MAX_PRIORITY - Thread.NORM_PRIORITY) * 3) / 4;
1191             thread = JSSecurityManager.createThread(this,
1192                                                     "Java Sound Sequencer", // name
1193                                                     false,                  // daemon
1194                                                     priority,               // priority
1195                                                     true);                  // doStart
1196         }
1197 
getDataPump()1198         DataPump getDataPump() {
1199             return dataPump;
1200         }
1201 
setSequence(Sequence seq)1202         synchronized void setSequence(Sequence seq) {
1203             dataPump.setSequence(seq);
1204         }
1205 
1206 
1207         /** start thread and pump. Requires up-to-date tempoCache */
start()1208         synchronized void start() {
1209             // mark the sequencer running
1210             running = true;
1211 
1212             if (!dataPump.hasCachedTempo()) {
1213                 long tickPos = getTickPosition();
1214                 dataPump.setTempoMPQ(tempoCache.getTempoMPQAt(tickPos));
1215             }
1216             dataPump.checkPointMillis = 0; // means restarted
1217             dataPump.clearNoteOnCache();
1218             dataPump.needReindex = true;
1219 
1220             dataPump.resetLoopCount();
1221 
1222             // notify the thread
1223             synchronized(lock) {
1224                 lock.notifyAll();
1225             }
1226         }
1227 
1228         // waits until stopped
stop()1229         synchronized void stop() {
1230             playThreadImplStop();
1231             long t = System.nanoTime() / 1000000l;
1232             while (isPumping) {
1233                 synchronized(lock) {
1234                     try {
1235                         lock.wait(2000);
1236                     } catch (InterruptedException ie) {
1237                         // ignore
1238                     }
1239                 }
1240                 // don't wait for more than 2 seconds
1241                 if ((System.nanoTime()/1000000l) - t > 1900) {
1242                     if (Printer.err) Printer.err("Waited more than 2 seconds in RealTimeSequencer.PlayThread.stop()!");
1243                     //break;
1244                 }
1245             }
1246         }
1247 
playThreadImplStop()1248         void playThreadImplStop() {
1249             // mark the sequencer running
1250             running = false;
1251             synchronized(lock) {
1252                 lock.notifyAll();
1253             }
1254         }
1255 
close()1256         void close() {
1257             Thread oldThread = null;
1258             synchronized (this) {
1259                 // dispose of thread
1260                 interrupted = true;
1261                 oldThread = thread;
1262                 thread = null;
1263             }
1264             if (oldThread != null) {
1265                 // wake up the thread if it's in wait()
1266                 synchronized(lock) {
1267                     lock.notifyAll();
1268                 }
1269             }
1270             // wait for the thread to terminate itself,
1271             // but max. 2 seconds. Must not be synchronized!
1272             if (oldThread != null) {
1273                 try {
1274                     oldThread.join(2000);
1275                 } catch (InterruptedException ie) {}
1276             }
1277         }
1278 
1279         /**
1280          * Main process loop driving the media flow.
1281          *
1282          * Make sure to NOT synchronize on RealTimeSequencer
1283          * anywhere here (even implicit). That is a sure deadlock!
1284          */
1285         @Override
run()1286         public void run() {
1287 
1288             while (!interrupted) {
1289                 boolean EOM = false;
1290                 boolean wasRunning = running;
1291                 isPumping = !interrupted && running;
1292                 while (!EOM && !interrupted && running) {
1293                     EOM = dataPump.pump();
1294 
1295                     try {
1296                         Thread.sleep(1);
1297                     } catch (InterruptedException ie) {
1298                         // ignore
1299                     }
1300                 }
1301 
1302                 playThreadImplStop();
1303                 if (wasRunning) {
1304                     dataPump.notesOff(true);
1305                 }
1306                 if (EOM) {
1307                     dataPump.setTickPos(sequence.getTickLength());
1308 
1309                     // send EOT event (mis-used for end of media)
1310                     MetaMessage message = new MetaMessage();
1311                     try{
1312                         message.setMessage(MidiUtils.META_END_OF_TRACK_TYPE, new byte[0], 0);
1313                     } catch(InvalidMidiDataException e1) {}
1314                     sendMetaEvents(message);
1315                 }
1316                 synchronized (lock) {
1317                     isPumping = false;
1318                     // wake up a waiting stop() method
1319                     lock.notifyAll();
1320                     while (!running && !interrupted) {
1321                         try {
1322                             lock.wait();
1323                         } catch (Exception ex) {}
1324                     }
1325                 }
1326             } // end of while(!EOM && !interrupted && running)
1327         }
1328     }
1329 
1330     /**
1331      * class that does the actual dispatching of events,
1332      * used to be in native in MMAPI.
1333      */
1334     private class DataPump {
1335         private float currTempo;         // MPQ tempo
1336         private float tempoFactor;       // 1.0 is default
1337         private float inverseTempoFactor;// = 1.0 / tempoFactor
1338         private long ignoreTempoEventAt; // ignore next META tempo during playback at this tick pos only
1339         private int resolution;
1340         private float divisionType;
1341         private long checkPointMillis;   // microseconds at checkoint
1342         private long checkPointTick;     // ticks at checkpoint
1343         private int[] noteOnCache;       // bit-mask of notes that are currently on
1344         private Track[] tracks;
1345         private boolean[] trackDisabled; // if true, do not play this track
1346         private int[] trackReadPos;      // read index per track
1347         private long lastTick;
1348         private boolean needReindex = false;
1349         private int currLoopCounter = 0;
1350 
1351         //private sun.misc.Perf perf = sun.misc.Perf.getPerf();
1352         //private long perfFreq = perf.highResFrequency();
1353 
DataPump()1354         DataPump() {
1355             init();
1356         }
1357 
init()1358         synchronized void init() {
1359             ignoreTempoEventAt = -1;
1360             tempoFactor = 1.0f;
1361             inverseTempoFactor = 1.0f;
1362             noteOnCache = new int[128];
1363             tracks = null;
1364             trackDisabled = null;
1365         }
1366 
setTickPos(long tickPos)1367         synchronized void setTickPos(long tickPos) {
1368             long oldLastTick = tickPos;
1369             lastTick = tickPos;
1370             if (running) {
1371                 notesOff(false);
1372             }
1373             if (running || tickPos > 0) {
1374                 // will also reindex
1375                 chaseEvents(oldLastTick, tickPos);
1376             } else {
1377                 needReindex = true;
1378             }
1379             if (!hasCachedTempo()) {
1380                 setTempoMPQ(getTempoCache().getTempoMPQAt(lastTick, currTempo));
1381                 // treat this as if it is a real time tempo change
1382                 ignoreTempoEventAt = -1;
1383             }
1384             // trigger re-configuration
1385             checkPointMillis = 0;
1386         }
1387 
getTickPos()1388         long getTickPos() {
1389             return lastTick;
1390         }
1391 
1392         // hasCachedTempo is only valid if it is the current position
hasCachedTempo()1393         boolean hasCachedTempo() {
1394             if (ignoreTempoEventAt != lastTick) {
1395                 ignoreTempoEventAt = -1;
1396             }
1397             return ignoreTempoEventAt >= 0;
1398         }
1399 
1400         // this method is also used internally in the pump!
setTempoMPQ(float tempoMPQ)1401         synchronized void setTempoMPQ(float tempoMPQ) {
1402             if (tempoMPQ > 0 && tempoMPQ != currTempo) {
1403                 ignoreTempoEventAt = lastTick;
1404                 this.currTempo = tempoMPQ;
1405                 // re-calculate check point
1406                 checkPointMillis = 0;
1407             }
1408         }
1409 
getTempoMPQ()1410         float getTempoMPQ() {
1411             return currTempo;
1412         }
1413 
setTempoFactor(float factor)1414         synchronized void setTempoFactor(float factor) {
1415             if (factor > 0 && factor != this.tempoFactor) {
1416                 tempoFactor = factor;
1417                 inverseTempoFactor = 1.0f / factor;
1418                 // re-calculate check point
1419                 checkPointMillis = 0;
1420             }
1421         }
1422 
getTempoFactor()1423         float getTempoFactor() {
1424             return tempoFactor;
1425         }
1426 
muteSoloChanged()1427         synchronized void muteSoloChanged() {
1428             boolean[] newDisabled = makeDisabledArray();
1429             if (running) {
1430                 applyDisabledTracks(trackDisabled, newDisabled);
1431             }
1432             trackDisabled = newDisabled;
1433         }
1434 
setSequence(Sequence seq)1435         synchronized void setSequence(Sequence seq) {
1436             if (seq == null) {
1437                 init();
1438                 return;
1439             }
1440             tracks = seq.getTracks();
1441             muteSoloChanged();
1442             resolution = seq.getResolution();
1443             divisionType = seq.getDivisionType();
1444             trackReadPos = new int[tracks.length];
1445             // trigger re-initialization
1446             checkPointMillis = 0;
1447             needReindex = true;
1448         }
1449 
resetLoopCount()1450         synchronized void resetLoopCount() {
1451             currLoopCounter = loopCount;
1452         }
1453 
clearNoteOnCache()1454         void clearNoteOnCache() {
1455             for (int i = 0; i < 128; i++) {
1456                 noteOnCache[i] = 0;
1457             }
1458         }
1459 
notesOff(boolean doControllers)1460         void notesOff(boolean doControllers) {
1461             int done = 0;
1462             for (int ch=0; ch<16; ch++) {
1463                 int channelMask = (1<<ch);
1464                 for (int i=0; i<128; i++) {
1465                     if ((noteOnCache[i] & channelMask) != 0) {
1466                         noteOnCache[i] ^= channelMask;
1467                         // send note on with velocity 0
1468                         getTransmitterList().sendMessage((ShortMessage.NOTE_ON | ch) | (i<<8), -1);
1469                         done++;
1470                     }
1471                 }
1472                 /* all notes off */
1473                 getTransmitterList().sendMessage((ShortMessage.CONTROL_CHANGE | ch) | (123<<8), -1);
1474                 /* sustain off */
1475                 getTransmitterList().sendMessage((ShortMessage.CONTROL_CHANGE | ch) | (64<<8), -1);
1476                 if (doControllers) {
1477                     /* reset all controllers */
1478                     getTransmitterList().sendMessage((ShortMessage.CONTROL_CHANGE | ch) | (121<<8), -1);
1479                     done++;
1480                 }
1481             }
1482         }
1483 
makeDisabledArray()1484         private boolean[] makeDisabledArray() {
1485             if (tracks == null) {
1486                 return null;
1487             }
1488             boolean[] newTrackDisabled = new boolean[tracks.length];
1489             boolean[] solo;
1490             boolean[] mute;
1491             synchronized(RealTimeSequencer.this) {
1492                 mute = trackMuted;
1493                 solo = trackSolo;
1494             }
1495             // if one track is solo, then only play solo
1496             boolean hasSolo = false;
1497             if (solo != null) {
1498                 for (int i = 0; i < solo.length; i++) {
1499                     if (solo[i]) {
1500                         hasSolo = true;
1501                         break;
1502                     }
1503                 }
1504             }
1505             if (hasSolo) {
1506                 // only the channels with solo play, regardless of mute
1507                 for (int i = 0; i < newTrackDisabled.length; i++) {
1508                     newTrackDisabled[i] = (i >= solo.length) || (!solo[i]);
1509                 }
1510             } else {
1511                 // mute the selected channels
1512                 for (int i = 0; i < newTrackDisabled.length; i++) {
1513                     newTrackDisabled[i] = (mute != null) && (i < mute.length) && (mute[i]);
1514                 }
1515             }
1516             return newTrackDisabled;
1517         }
1518 
1519         /**
1520          * chase all events from beginning of Track
1521          * and send note off for those events that are active
1522          * in noteOnCache array.
1523          * It is possible, of course, to catch notes from other tracks,
1524          * but better than more complicated logic to detect
1525          * which notes are really from this track
1526          */
sendNoteOffIfOn(Track track, long endTick)1527         private void sendNoteOffIfOn(Track track, long endTick) {
1528             int size = track.size();
1529             int done = 0;
1530             try {
1531                 for (int i = 0; i < size; i++) {
1532                     MidiEvent event = track.get(i);
1533                     if (event.getTick() > endTick) break;
1534                     MidiMessage msg = event.getMessage();
1535                     int status = msg.getStatus();
1536                     int len = msg.getLength();
1537                     if (len == 3 && ((status & 0xF0) == ShortMessage.NOTE_ON)) {
1538                         int note = -1;
1539                         if (msg instanceof ShortMessage) {
1540                             ShortMessage smsg = (ShortMessage) msg;
1541                             if (smsg.getData2() > 0) {
1542                                 // only consider Note On with velocity > 0
1543                                 note = smsg.getData1();
1544                             }
1545                         } else {
1546                             byte[] data = msg.getMessage();
1547                             if ((data[2] & 0x7F) > 0) {
1548                                 // only consider Note On with velocity > 0
1549                                 note = data[1] & 0x7F;
1550                             }
1551                         }
1552                         if (note >= 0) {
1553                             int bit = 1<<(status & 0x0F);
1554                             if ((noteOnCache[note] & bit) != 0) {
1555                                 // the bit is set. Send Note Off
1556                                 getTransmitterList().sendMessage(status | (note<<8), -1);
1557                                 // clear the bit
1558                                 noteOnCache[note] &= (0xFFFF ^ bit);
1559                                 done++;
1560                             }
1561                         }
1562                     }
1563                 }
1564             } catch (ArrayIndexOutOfBoundsException aioobe) {
1565                 // this happens when messages are removed
1566                 // from the track while this method executes
1567             }
1568         }
1569 
1570         /**
1571          * Runtime application of mute/solo:
1572          * if a track is muted that was previously playing, send
1573          *    note off events for all currently playing notes.
1574          */
applyDisabledTracks(boolean[] oldDisabled, boolean[] newDisabled)1575         private void applyDisabledTracks(boolean[] oldDisabled, boolean[] newDisabled) {
1576             byte[][] tempArray = null;
1577             synchronized(RealTimeSequencer.this) {
1578                 for (int i = 0; i < newDisabled.length; i++) {
1579                     if (((oldDisabled == null)
1580                          || (i >= oldDisabled.length)
1581                          || !oldDisabled[i])
1582                         && newDisabled[i]) {
1583                         // case that a track gets muted: need to
1584                         // send appropriate note off events to prevent
1585                         // hanging notes
1586 
1587                         if (tracks.length > i) {
1588                             sendNoteOffIfOn(tracks[i], lastTick);
1589                         }
1590                     }
1591                     else if ((oldDisabled != null)
1592                              && (i < oldDisabled.length)
1593                              && oldDisabled[i]
1594                              && !newDisabled[i]) {
1595                         // case that a track was muted and is now unmuted
1596                         // need to chase events and re-index this track
1597                         if (tempArray == null) {
1598                             tempArray = new byte[128][16];
1599                         }
1600                         chaseTrackEvents(i, 0, lastTick, true, tempArray);
1601                     }
1602                 }
1603             }
1604         }
1605 
1606         /** go through all events from startTick to endTick
1607          * chase the controller state and program change state
1608          * and then set the end-states at once.
1609          *
1610          * needs to be called in synchronized state
1611          * @param tempArray an byte[128][16] to hold controller messages
1612          */
chaseTrackEvents(int trackNum, long startTick, long endTick, boolean doReindex, byte[][] tempArray)1613         private void chaseTrackEvents(int trackNum,
1614                                       long startTick,
1615                                       long endTick,
1616                                       boolean doReindex,
1617                                       byte[][] tempArray) {
1618             if (startTick > endTick) {
1619                 // start from the beginning
1620                 startTick = 0;
1621             }
1622             byte[] progs = new byte[16];
1623             // init temp array with impossible values
1624             for (int ch = 0; ch < 16; ch++) {
1625                 progs[ch] = -1;
1626                 for (int co = 0; co < 128; co++) {
1627                     tempArray[co][ch] = -1;
1628                 }
1629             }
1630             Track track = tracks[trackNum];
1631             int size = track.size();
1632             try {
1633                 for (int i = 0; i < size; i++) {
1634                     MidiEvent event = track.get(i);
1635                     if (event.getTick() >= endTick) {
1636                         if (doReindex && (trackNum < trackReadPos.length)) {
1637                             trackReadPos[trackNum] = (i > 0)?(i-1):0;
1638                         }
1639                         break;
1640                     }
1641                     MidiMessage msg = event.getMessage();
1642                     int status = msg.getStatus();
1643                     int len = msg.getLength();
1644                     if (len == 3 && ((status & 0xF0) == ShortMessage.CONTROL_CHANGE)) {
1645                         if (msg instanceof ShortMessage) {
1646                             ShortMessage smsg = (ShortMessage) msg;
1647                             tempArray[smsg.getData1() & 0x7F][status & 0x0F] = (byte) smsg.getData2();
1648                         } else {
1649                             byte[] data = msg.getMessage();
1650                             tempArray[data[1] & 0x7F][status & 0x0F] = data[2];
1651                         }
1652                     }
1653                     if (len == 2 && ((status & 0xF0) == ShortMessage.PROGRAM_CHANGE)) {
1654                         if (msg instanceof ShortMessage) {
1655                             ShortMessage smsg = (ShortMessage) msg;
1656                             progs[status & 0x0F] = (byte) smsg.getData1();
1657                         } else {
1658                             byte[] data = msg.getMessage();
1659                             progs[status & 0x0F] = data[1];
1660                         }
1661                     }
1662                 }
1663             } catch (ArrayIndexOutOfBoundsException aioobe) {
1664                 // this happens when messages are removed
1665                 // from the track while this method executes
1666             }
1667             int numControllersSent = 0;
1668             // now send out the aggregated controllers and program changes
1669             for (int ch = 0; ch < 16; ch++) {
1670                 for (int co = 0; co < 128; co++) {
1671                     byte controllerValue = tempArray[co][ch];
1672                     if (controllerValue >= 0) {
1673                         int packedMsg = (ShortMessage.CONTROL_CHANGE | ch) | (co<<8) | (controllerValue<<16);
1674                         getTransmitterList().sendMessage(packedMsg, -1);
1675                         numControllersSent++;
1676                     }
1677                 }
1678                 // send program change *after* controllers, to
1679                 // correctly initialize banks
1680                 if (progs[ch] >= 0) {
1681                     getTransmitterList().sendMessage((ShortMessage.PROGRAM_CHANGE | ch) | (progs[ch]<<8), -1);
1682                 }
1683                 if (progs[ch] >= 0 || startTick == 0 || endTick == 0) {
1684                     // reset pitch bend on this channel (E0 00 40)
1685                     getTransmitterList().sendMessage((ShortMessage.PITCH_BEND | ch) | (0x40 << 16), -1);
1686                     // reset sustain pedal on this channel
1687                     getTransmitterList().sendMessage((ShortMessage.CONTROL_CHANGE | ch) | (64 << 8), -1);
1688                 }
1689             }
1690         }
1691 
1692         /**
1693          * chase controllers and program for all tracks.
1694          */
chaseEvents(long startTick, long endTick)1695         synchronized void chaseEvents(long startTick, long endTick) {
1696             byte[][] tempArray = new byte[128][16];
1697             for (int t = 0; t < tracks.length; t++) {
1698                 if ((trackDisabled == null)
1699                     || (trackDisabled.length <= t)
1700                     || (!trackDisabled[t])) {
1701                     // if track is not disabled, chase the events for it
1702                     chaseTrackEvents(t, startTick, endTick, true, tempArray);
1703                 }
1704             }
1705         }
1706 
1707         // playback related methods (pumping)
1708 
getCurrentTimeMillis()1709         private long getCurrentTimeMillis() {
1710             return System.nanoTime() / 1000000l;
1711             //return perf.highResCounter() * 1000 / perfFreq;
1712         }
1713 
millis2tick(long millis)1714         private long millis2tick(long millis) {
1715             if (divisionType != Sequence.PPQ) {
1716                 double dTick = ((((double) millis) * tempoFactor)
1717                                 * ((double) divisionType)
1718                                 * ((double) resolution))
1719                     / ((double) 1000);
1720                 return (long) dTick;
1721             }
1722             return MidiUtils.microsec2ticks(millis * 1000,
1723                                             currTempo * inverseTempoFactor,
1724                                             resolution);
1725         }
1726 
tick2millis(long tick)1727         private long tick2millis(long tick) {
1728             if (divisionType != Sequence.PPQ) {
1729                 double dMillis = ((((double) tick) * 1000) /
1730                                   (tempoFactor * ((double) divisionType) * ((double) resolution)));
1731                 return (long) dMillis;
1732             }
1733             return MidiUtils.ticks2microsec(tick,
1734                                             currTempo * inverseTempoFactor,
1735                                             resolution) / 1000;
1736         }
1737 
ReindexTrack(int trackNum, long tick)1738         private void ReindexTrack(int trackNum, long tick) {
1739             if (trackNum < trackReadPos.length && trackNum < tracks.length) {
1740                 trackReadPos[trackNum] = MidiUtils.tick2index(tracks[trackNum], tick);
1741             }
1742         }
1743 
1744         /* returns if changes are pending */
dispatchMessage(int trackNum, MidiEvent event)1745         private boolean dispatchMessage(int trackNum, MidiEvent event) {
1746             boolean changesPending = false;
1747             MidiMessage message = event.getMessage();
1748             int msgStatus = message.getStatus();
1749             int msgLen = message.getLength();
1750             if (msgStatus == MetaMessage.META && msgLen >= 2) {
1751                 // a meta message. Do not send it to the device.
1752                 // 0xFF with length=1 is a MIDI realtime message
1753                 // which shouldn't be in a Sequence, but we play it
1754                 // nonetheless.
1755 
1756                 // see if this is a tempo message. Only on track 0.
1757                 if (trackNum == 0) {
1758                     int newTempo = MidiUtils.getTempoMPQ(message);
1759                     if (newTempo > 0) {
1760                         if (event.getTick() != ignoreTempoEventAt) {
1761                             setTempoMPQ(newTempo); // sets ignoreTempoEventAt!
1762                             changesPending = true;
1763                         }
1764                         // next loop, do not ignore anymore tempo events.
1765                         ignoreTempoEventAt = -1;
1766                     }
1767                 }
1768                 // send to listeners
1769                 sendMetaEvents(message);
1770 
1771             } else {
1772                 // not meta, send to device
1773                 getTransmitterList().sendMessage(message, -1);
1774 
1775                 switch (msgStatus & 0xF0) {
1776                 case ShortMessage.NOTE_OFF: {
1777                     // note off - clear the bit in the noteOnCache array
1778                     int note = ((ShortMessage) message).getData1() & 0x7F;
1779                     noteOnCache[note] &= (0xFFFF ^ (1<<(msgStatus & 0x0F)));
1780                     break;
1781                 }
1782 
1783                 case ShortMessage.NOTE_ON: {
1784                     // note on
1785                     ShortMessage smsg = (ShortMessage) message;
1786                     int note = smsg.getData1() & 0x7F;
1787                     int vel = smsg.getData2() & 0x7F;
1788                     if (vel > 0) {
1789                         // if velocity > 0 set the bit in the noteOnCache array
1790                         noteOnCache[note] |= 1<<(msgStatus & 0x0F);
1791                     } else {
1792                         // if velocity = 0 clear the bit in the noteOnCache array
1793                         noteOnCache[note] &= (0xFFFF ^ (1<<(msgStatus & 0x0F)));
1794                     }
1795                     break;
1796                 }
1797 
1798                 case ShortMessage.CONTROL_CHANGE:
1799                     // if controller message, send controller listeners
1800                     sendControllerEvents(message);
1801                     break;
1802 
1803                 }
1804             }
1805             return changesPending;
1806         }
1807 
1808         /** the main pump method
1809          * @return true if end of sequence is reached
1810          */
pump()1811         synchronized boolean pump() {
1812             long currMillis;
1813             long targetTick = lastTick;
1814             MidiEvent currEvent;
1815             boolean changesPending = false;
1816             boolean doLoop = false;
1817             boolean EOM = false;
1818 
1819             currMillis = getCurrentTimeMillis();
1820             int finishedTracks = 0;
1821             do {
1822                 changesPending = false;
1823 
1824                 // need to re-find indexes in tracks?
1825                 if (needReindex) {
1826                     if (trackReadPos.length < tracks.length) {
1827                         trackReadPos = new int[tracks.length];
1828                     }
1829                     for (int t = 0; t < tracks.length; t++) {
1830                         ReindexTrack(t, targetTick);
1831                     }
1832                     needReindex = false;
1833                     checkPointMillis = 0;
1834                 }
1835 
1836                 // get target tick from current time in millis
1837                 if (checkPointMillis == 0) {
1838                     // new check point
1839                     currMillis = getCurrentTimeMillis();
1840                     checkPointMillis = currMillis;
1841                     targetTick = lastTick;
1842                     checkPointTick = targetTick;
1843                 } else {
1844                     // calculate current tick based on current time in milliseconds
1845                     targetTick = checkPointTick + millis2tick(currMillis - checkPointMillis);
1846                     if ((loopEnd != -1)
1847                         && ((loopCount > 0 && currLoopCounter > 0)
1848                             || (loopCount == LOOP_CONTINUOUSLY))) {
1849                         if (lastTick <= loopEnd && targetTick >= loopEnd) {
1850                             // need to loop!
1851                             // only play until loop end
1852                             targetTick = loopEnd - 1;
1853                             doLoop = true;
1854                         }
1855                     }
1856                     lastTick = targetTick;
1857                 }
1858 
1859                 finishedTracks = 0;
1860 
1861                 for (int t = 0; t < tracks.length; t++) {
1862                     try {
1863                         boolean disabled = trackDisabled[t];
1864                         Track thisTrack = tracks[t];
1865                         int readPos = trackReadPos[t];
1866                         int size = thisTrack.size();
1867                         // play all events that are due until targetTick
1868                         while (!changesPending && (readPos < size)
1869                                && (currEvent = thisTrack.get(readPos)).getTick() <= targetTick) {
1870 
1871                             if ((readPos == size -1) &&  MidiUtils.isMetaEndOfTrack(currEvent.getMessage())) {
1872                                 // do not send out this message. Finished with this track
1873                                 readPos = size;
1874                                 break;
1875                             }
1876                             // TODO: some kind of heuristics if the MIDI messages have changed
1877                             // significantly (i.e. deleted or inserted a bunch of messages)
1878                             // since last time. Would need to set needReindex = true then
1879                             readPos++;
1880                             // only play this event if the track is enabled,
1881                             // or if it is a tempo message on track 0
1882                             // Note: cannot put this check outside
1883                             //       this inner loop in order to detect end of file
1884                             if (!disabled ||
1885                                 ((t == 0) && (MidiUtils.isMetaTempo(currEvent.getMessage())))) {
1886                                 changesPending = dispatchMessage(t, currEvent);
1887                             }
1888                         }
1889                         if (readPos >= size) {
1890                             finishedTracks++;
1891                         }
1892                         trackReadPos[t] = readPos;
1893                     } catch(Exception e) {
1894                         if (Printer.err) e.printStackTrace();
1895                         if (e instanceof ArrayIndexOutOfBoundsException) {
1896                             needReindex = true;
1897                             changesPending = true;
1898                         }
1899                     }
1900                     if (changesPending) {
1901                         break;
1902                     }
1903                 }
1904                 EOM = (finishedTracks == tracks.length);
1905                 if (doLoop
1906                     || ( ((loopCount > 0 && currLoopCounter > 0)
1907                           || (loopCount == LOOP_CONTINUOUSLY))
1908                          && !changesPending
1909                          && (loopEnd == -1)
1910                          && EOM)) {
1911 
1912                     long oldCheckPointMillis = checkPointMillis;
1913                     long loopEndTick = loopEnd;
1914                     if (loopEndTick == -1) {
1915                         loopEndTick = lastTick;
1916                     }
1917 
1918                     // need to loop back!
1919                     if (loopCount != LOOP_CONTINUOUSLY) {
1920                         currLoopCounter--;
1921                     }
1922                     setTickPos(loopStart);
1923                     // now patch the checkPointMillis so that
1924                     // it points to the exact beginning of when the loop was finished
1925 
1926                     // $$fb TODO: although this is mathematically correct (i.e. the loop position
1927                     //            is correct, and doesn't drift away with several repetition,
1928                     //            there is a slight lag when looping back, probably caused
1929                     //            by the chasing.
1930 
1931                     checkPointMillis = oldCheckPointMillis + tick2millis(loopEndTick - checkPointTick);
1932                     checkPointTick = loopStart;
1933                     // no need for reindexing, is done in setTickPos
1934                     needReindex = false;
1935                     changesPending = false;
1936                     // reset doLoop flag
1937                     doLoop = false;
1938                     EOM = false;
1939                 }
1940             } while (changesPending);
1941 
1942             return EOM;
1943         }
1944     } // class DataPump
1945 }
1946