< prev index next >

src/java.desktop/share/classes/com/sun/media/sound/RealTimeSequencer.java

Print this page




  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 }
< prev index next >