1 /*
   2  * Copyright (c) 2003, 2016, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.media.sound;
  27 
  28 import java.io.IOException;
  29 import java.io.InputStream;
  30 
  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             }
 187 
 188             if (playThread != null) {
 189                 playThread.setSequence(sequence);
 190             }
 191 
 192             // store this sequence (do not copy - we want to give the possibility
 193             // of modifying the sequence at runtime)
 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) {
 762             doAutoConnect();
 763         }
 764         if (Printer.trace) Printer.trace("<< RealTimeSequencer: implOpen() succeeded");
 765     }
 766 
 767     private void doAutoConnect() {
 768         if (Printer.trace) Printer.trace(">> RealTimeSequencer: doAutoConnect()");
 769         Receiver rec = null;
 770         // first try to connect to the default synthesizer
 771         // IMPORTANT: this code needs to be synch'ed with
 772         //            MidiSystem.getSequencer(boolean), because the same
 773         //            algorithm needs to be used!
 774         try {
 775             Synthesizer synth = MidiSystem.getSynthesizer();
 776             if (synth instanceof ReferenceCountingDevice) {
 777                 rec = ((ReferenceCountingDevice) synth).getReceiverReferenceCounting();
 778             } else {
 779                 synth.open();
 780                 try {
 781                     rec = synth.getReceiver();
 782                 } finally {
 783                     // make sure that the synth is properly closed
 784                     if (rec == null) {
 785                         synth.close();
 786                     }
 787                 }
 788             }
 789         } catch (Exception e) {
 790             // something went wrong with synth
 791         }
 792         if (rec == null) {
 793             // then try to connect to the default Receiver
 794             try {
 795                 rec = MidiSystem.getReceiver();
 796             } catch (Exception e) {
 797                 // something went wrong. Nothing to do then!
 798             }
 799         }
 800         if (rec != null) {
 801             autoConnectedReceiver = rec;
 802             try {
 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;
 851         loopEnd = -1;
 852         loopCount = 0;
 853 
 854         /** if this sequencer is set to autoconnect, need to
 855          * re-establish the connection at next open!
 856          */
 857         doAutoConnectAtNextOpen = autoConnect;
 858 
 859         if (autoConnectedReceiver != null) {
 860             try {
 861                 autoConnectedReceiver.close();
 862             } catch (Exception e) {}
 863             autoConnectedReceiver = null;
 864         }
 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();
 906         synchronized (dispatchers) {
 907             EventDispatcher eventDispatcher = dispatchers.get(tg);
 908             if (eventDispatcher == null) {
 909                 eventDispatcher = new EventDispatcher();
 910                 dispatchers.put(tg, eventDispatcher);
 911                 eventDispatcher.start();
 912             }
 913             return eventDispatcher;
 914         }
 915     }
 916 
 917     /**
 918      * Send midi player events.
 919      * must not be synchronized on "this"
 920      */
 921     void sendMetaEvents(MidiMessage message) {
 922         if (metaEventListeners.size() == 0) return;
 923 
 924         //if (Printer.debug) Printer.debug("sending a meta event");
 925         getEventDispatcher().sendAudioEvents(message, metaEventListeners);
 926     }
 927 
 928     /**
 929      * Send midi player events.
 930      */
 931     void sendControllerEvents(MidiMessage message) {
 932         int size = controllerEventListeners.size();
 933         if (size == 0) return;
 934 
 935         //if (Printer.debug) Printer.debug("sending a controller event");
 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;
1057                         // all real-time messages have 0xF in the high nibble of the status byte
1058                         if ((sm.getStatus() & 0xF0) != 0xF0) {
1059                             track = RecordingTrack.get(recordingTracks, sm.getChannel());
1060                         }
1061                     } else {
1062                         // $$jb: where to record meta, sysex events?
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 
1117         private void addControllers(int[] c) {
1118 
1119             if (c==null) {
1120                 controllers = new int[128];
1121                 for (int i = 0; i < 128; i++) {
1122                     controllers[i] = i;
1123                 }
1124                 return;
1125             }
1126             int temp[] = new int[ controllers.length + c.length ];
1127             int elements;
1128 
1129             // first add what we have
1130             for(int i=0; i<controllers.length; i++) {
1131                 temp[i] = controllers[i];
1132             }
1133             elements = controllers.length;
1134             // now add the new controllers only if we don't already have them
1135             for(int i=0; i<c.length; i++) {
1136                 boolean flag = false;
1137 
1138                 for(int j=0; j<controllers.length; j++) {
1139                     if (c[i] == controllers[j]) {
1140                         flag = true;
1141                         break;
1142                     }
1143                 }
1144                 if (!flag) {
1145                     temp[elements++] = c[i];
1146                 }
1147             }
1148             // now keep only the elements we need
1149             int newc[] = new int[ elements ];
1150             for(int i=0; i<elements; i++){
1151                 newc[i] = temp[i];
1152             }
1153             controllers = newc;
1154         }
1155 
1156         private void removeControllers(int[] c) {
1157 
1158             if (c==null) {
1159                 controllers = new int[0];
1160             } else {
1161                 int temp[] = new int[ controllers.length ];
1162                 int elements = 0;
1163 
1164 
1165                 for(int i=0; i<controllers.length; i++){
1166                     boolean flag = false;
1167                     for(int j=0; j<c.length; j++) {
1168                         if (controllers[i] == c[j]) {
1169                             flag = true;
1170                             break;
1171                         }
1172                     }
1173                     if (!flag){
1174                         temp[elements++] = controllers[i];
1175                     }
1176                 }
1177                 // now keep only the elements remaining
1178                 int newc[] = new int[ elements ];
1179                 for(int i=0; i<elements; i++) {
1180                     newc[i] = temp[i];
1181                 }
1182                 controllers = newc;
1183 
1184             }
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                     }
1226                 }
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
1268         }
1269 
1270         DataPump getDataPump() {
1271             return dataPump;
1272         }
1273 
1274         synchronized void setSequence(Sequence seq) {
1275             dataPump.setSequence(seq);
1276         }
1277 
1278 
1279         /** start thread and pump. Requires up-to-date tempoCache */
1280         synchronized void start() {
1281             // mark the sequencer running
1282             running = true;
1283 
1284             if (!dataPump.hasCachedTempo()) {
1285                 long tickPos = getTickPosition();
1286                 dataPump.setTempoMPQ(tempoCache.getTempoMPQAt(tickPos));
1287             }
1288             dataPump.checkPointMillis = 0; // means restarted
1289             dataPump.clearNoteOnCache();
1290             dataPump.needReindex = true;
1291 
1292             dataPump.resetLoopCount();
1293 
1294             // notify the thread
1295             synchronized(lock) {
1296                 lock.notifyAll();
1297             }
1298 
1299             if (Printer.debug) Printer.debug(" ->Started MIDI play thread");
1300 
1301         }
1302 
1303         // waits until stopped
1304         synchronized void stop() {
1305             playThreadImplStop();
1306             long t = System.nanoTime() / 1000000l;
1307             while (isPumping) {
1308                 synchronized(lock) {
1309                     try {
1310                         lock.wait(2000);
1311                     } catch (InterruptedException ie) {
1312                         // ignore
1313                     }
1314                 }
1315                 // don't wait for more than 2 seconds
1316                 if ((System.nanoTime()/1000000l) - t > 1900) {
1317                     if (Printer.err) Printer.err("Waited more than 2 seconds in RealTimeSequencer.PlayThread.stop()!");
1318                     //break;
1319                 }
1320             }
1321         }
1322 
1323         void playThreadImplStop() {
1324             // mark the sequencer running
1325             running = false;
1326             synchronized(lock) {
1327                 lock.notifyAll();
1328             }
1329         }
1330 
1331         void close() {
1332             Thread oldThread = null;
1333             synchronized (this) {
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");
1381                 }
1382 
1383                 playThreadImplStop();
1384                 if (wasRunning) {
1385                     dataPump.notesOff(true);
1386                 }
1387                 if (EOM) {
1388                     dataPump.setTickPos(sequence.getTickLength());
1389 
1390                     // send EOT event (mis-used for end of media)
1391                     MetaMessage message = new MetaMessage();
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) {
1458                 // will also reindex
1459                 chaseEvents(oldLastTick, tickPos);
1460             } else {
1461                 needReindex = true;
1462             }
1463             if (!hasCachedTempo()) {
1464                 setTempoMPQ(getTempoCache().getTempoMPQAt(lastTick, currTempo));
1465                 // treat this as if it is a real time tempo change
1466                 ignoreTempoEventAt = -1;
1467             }
1468             // trigger re-configuration
1469             checkPointMillis = 0;
1470         }
1471 
1472         long getTickPos() {
1473             return lastTick;
1474         }
1475 
1476         // hasCachedTempo is only valid if it is the current position
1477         boolean hasCachedTempo() {
1478             if (ignoreTempoEventAt != lastTick) {
1479                 ignoreTempoEventAt = -1;
1480             }
1481             return ignoreTempoEventAt >= 0;
1482         }
1483 
1484         // this method is also used internally in the pump!
1485         synchronized void setTempoMPQ(float tempoMPQ) {
1486             if (tempoMPQ > 0 && tempoMPQ != currTempo) {
1487                 ignoreTempoEventAt = lastTick;
1488                 this.currTempo = tempoMPQ;
1489                 // re-calculate check point
1490                 checkPointMillis = 0;
1491             }
1492         }
1493 
1494         float getTempoMPQ() {
1495             return currTempo;
1496         }
1497 
1498         synchronized void setTempoFactor(float factor) {
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() {
1541             for (int i = 0; i < 128; i++) {
1542                 noteOnCache[i] = 0;
1543             }
1544         }
1545 
1546         void notesOff(boolean doControllers) {
1547             int done = 0;
1548             for (int ch=0; ch<16; ch++) {
1549                 int channelMask = (1<<ch);
1550                 for (int i=0; i<128; i++) {
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                 }
1592             }
1593             if (hasSolo) {
1594                 // only the channels with solo play, regardless of mute
1595                 for (int i = 0; i < newTrackDisabled.length; i++) {
1596                     newTrackDisabled[i] = (i >= solo.length) || (!solo[i]);
1597                 }
1598             } else {
1599                 // mute the selected channels
1600                 for (int i = 0; i < newTrackDisabled.length; i++) {
1601                     newTrackDisabled[i] = (mute != null) && (i < mute.length) && (mute[i]);
1602                 }
1603             }
1604             return newTrackDisabled;
1605         }
1606 
1607         /**
1608          * chase all events from beginning of Track
1609          * and send note off for those events that are active
1610          * in noteOnCache array.
1611          * It is possible, of course, to catch notes from other tracks,
1612          * but better than more complicated logic to detect
1613          * which notes are really from this track
1614          */
1615         private void sendNoteOffIfOn(Track track, long endTick) {
1616             int size = track.size();
1617             int done = 0;
1618             try {
1619                 for (int i = 0; i < size; i++) {
1620                     MidiEvent event = track.get(i);
1621                     if (event.getTick() > endTick) break;
1622                     MidiMessage msg = event.getMessage();
1623                     int status = msg.getStatus();
1624                     int len = msg.getLength();
1625                     if (len == 3 && ((status & 0xF0) == ShortMessage.NOTE_ON)) {
1626                         int note = -1;
1627                         if (msg instanceof ShortMessage) {
1628                             ShortMessage smsg = (ShortMessage) msg;
1629                             if (smsg.getData2() > 0) {
1630                                 // only consider Note On with velocity > 0
1631                                 note = smsg.getData1();
1632                             }
1633                         } else {
1634                             byte[] data = msg.getMessage();
1635                             if ((data[2] & 0x7F) > 0) {
1636                                 // only consider Note On with velocity > 0
1637                                 note = data[1] & 0x7F;
1638                             }
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]
1684                              && !newDisabled[i]) {
1685                         // case that a track was muted and is now unmuted
1686                         // need to chase events and re-index this track
1687                         if (tempArray == null) {
1688                             tempArray = new byte[128][16];
1689                         }
1690                         chaseTrackEvents(i, 0, lastTick, true, tempArray);
1691                     }
1692                 }
1693             }
1694         }
1695 
1696         /** go through all events from startTick to endTick
1697          * chase the controller state and program change state
1698          * and then set the end-states at once.
1699          *
1700          * needs to be called in synchronized state
1701          * @param tempArray an byte[128][16] to hold controller messages
1702          */
1703         private void chaseTrackEvents(int trackNum,
1704                                       long startTick,
1705                                       long endTick,
1706                                       boolean doReindex,
1707                                       byte[][] tempArray) {
1708             if (startTick > endTick) {
1709                 // start from the beginning
1710                 startTick = 0;
1711             }
1712             byte[] progs = new byte[16];
1713             // init temp array with impossible values
1714             for (int ch = 0; ch < 16; ch++) {
1715                 progs[ch] = -1;
1716                 for (int co = 0; co < 128; co++) {
1717                     tempArray[co][ch] = -1;
1718                 }
1719             }
1720             Track track = tracks[trackNum];
1721             int size = track.size();
1722             try {
1723                 for (int i = 0; i < size; i++) {
1724                     MidiEvent event = track.get(i);
1725                     if (event.getTick() >= endTick) {
1726                         if (doReindex && (trackNum < trackReadPos.length)) {
1727                             trackReadPos[trackNum] = (i > 0)?(i-1):0;
1728                             if (DEBUG_PUMP) Printer.println("  chaseEvents: setting trackReadPos["+trackNum+"] = "+trackReadPos[trackNum]);
1729                         }
1730                         break;
1731                     }
1732                     MidiMessage msg = event.getMessage();
1733                     int status = msg.getStatus();
1734                     int len = msg.getLength();
1735                     if (len == 3 && ((status & 0xF0) == ShortMessage.CONTROL_CHANGE)) {
1736                         if (msg instanceof ShortMessage) {
1737                             ShortMessage smsg = (ShortMessage) msg;
1738                             tempArray[smsg.getData1() & 0x7F][status & 0x0F] = (byte) smsg.getData2();
1739                         } else {
1740                             byte[] data = msg.getMessage();
1741                             tempArray[data[1] & 0x7F][status & 0x0F] = data[2];
1742                         }
1743                     }
1744                     if (len == 2 && ((status & 0xF0) == ShortMessage.PROGRAM_CHANGE)) {
1745                         if (msg instanceof ShortMessage) {
1746                             ShortMessage smsg = (ShortMessage) msg;
1747                             progs[status & 0x0F] = (byte) smsg.getData1();
1748                         } else {
1749                             byte[] data = msg.getMessage();
1750                             progs[status & 0x0F] = data[1];
1751                         }
1752                     }
1753                 }
1754             } catch (ArrayIndexOutOfBoundsException aioobe) {
1755                 // this happens when messages are removed
1756                 // from the track while this method executes
1757             }
1758             int numControllersSent = 0;
1759             // now send out the aggregated controllers and program changes
1760             for (int ch = 0; ch < 16; ch++) {
1761                 for (int co = 0; co < 128; co++) {
1762                     byte controllerValue = tempArray[co][ch];
1763                     if (controllerValue >= 0) {
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 
1821         private long tick2millis(long tick) {
1822             if (divisionType != Sequence.PPQ) {
1823                 double dMillis = ((((double) tick) * 1000) /
1824                                   (tempoFactor * ((double) divisionType) * ((double) resolution)));
1825                 return (long) dMillis;
1826             }
1827             return MidiUtils.ticks2microsec(tick,
1828                                             currTempo * inverseTempoFactor,
1829                                             resolution) / 1000;
1830         }
1831 
1832         private void ReindexTrack(int trackNum, long tick) {
1833             if (trackNum < trackReadPos.length && trackNum < tracks.length) {
1834                 trackReadPos[trackNum] = MidiUtils.tick2index(tracks[trackNum], tick);
1835                 if (DEBUG_PUMP) Printer.println("  reindexTrack: setting trackReadPos["+trackNum+"] = "+trackReadPos[trackNum]);
1836             }
1837         }
1838 
1839         /* returns if changes are pending */
1840         private boolean dispatchMessage(int trackNum, MidiEvent event) {
1841             boolean changesPending = false;
1842             MidiMessage message = event.getMessage();
1843             int msgStatus = message.getStatus();
1844             int msgLen = message.getLength();
1845             if (msgStatus == MetaMessage.META && msgLen >= 2) {
1846                 // a meta message. Do not send it to the device.
1847                 // 0xFF with length=1 is a MIDI realtime message
1848                 // which shouldn't be in a Sequence, but we play it
1849                 // nonetheless.
1850 
1851                 // see if this is a tempo message. Only on track 0.
1852                 if (trackNum == 0) {
1853                     int newTempo = MidiUtils.getTempoMPQ(message);
1854                     if (newTempo > 0) {
1855                         if (event.getTick() != ignoreTempoEventAt) {
1856                             setTempoMPQ(newTempo); // sets ignoreTempoEventAt!
1857                             changesPending = true;
1858                         }
1859                         // next loop, do not ignore anymore tempo events.
1860                         ignoreTempoEventAt = -1;
1861                     }
1862                 }
1863                 // send to listeners
1864                 sendMetaEvents(message);
1865 
1866             } else {
1867                 // not meta, send to device
1868                 getTransmitterList().sendMessage(message, -1);
1869 
1870                 switch (msgStatus & 0xF0) {
1871                 case ShortMessage.NOTE_OFF: {
1872                     // note off - clear the bit in the noteOnCache array
1873                     int note = ((ShortMessage) message).getData1() & 0x7F;
1874                     noteOnCache[note] &= (0xFFFF ^ (1<<(msgStatus & 0x0F)));
1875                     break;
1876                 }
1877 
1878                 case ShortMessage.NOTE_ON: {
1879                     // note on
1880                     ShortMessage smsg = (ShortMessage) message;
1881                     int note = smsg.getData1() & 0x7F;
1882                     int vel = smsg.getData2() & 0x7F;
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) {
1924                         trackReadPos = new int[tracks.length];
1925                     }
1926                     for (int t = 0; t < tracks.length; t++) {
1927                         ReindexTrack(t, targetTick);
1928                         if (DEBUG_PUMP_ALL) Printer.println("  Setting trackReadPos["+t+"]="+trackReadPos[t]);
1929                     }
1930                     needReindex = false;
1931                     checkPointMillis = 0;
1932                 }
1933 
1934                 // get target tick from current time in millis
1935                 if (checkPointMillis == 0) {
1936                     // new check point
1937                     currMillis = getCurrentTimeMillis();
1938                     checkPointMillis = currMillis;
1939                     targetTick = lastTick;
1940                     checkPointTick = targetTick;
1941                     if (DEBUG_PUMP) Printer.println("New checkpoint to "+currMillis+" millis. "
1942                                                        +"TargetTick="+targetTick
1943                                                        +" new tempo="+MidiUtils.convertTempo(currTempo)+"bpm");
1944                 } else {
1945                     // calculate current tick based on current time in milliseconds
1946                     targetTick = checkPointTick + millis2tick(currMillis - checkPointMillis);
1947                     if (DEBUG_PUMP_ALL) Printer.println("targetTick = "+targetTick+" at "+currMillis+" millis");
1948                     if ((loopEnd != -1)
1949                         && ((loopCount > 0 && currLoopCounter > 0)
1950                             || (loopCount == LOOP_CONTINUOUSLY))) {
1951                         if (lastTick <= loopEnd && targetTick >= loopEnd) {
1952                             // need to loop!
1953                             // only play until loop end
1954                             targetTick = loopEnd - 1;
1955                             doLoop = true;
1956                             if (DEBUG_PUMP) Printer.println("set doLoop to true. lastTick="+lastTick
1957                                                                +"  targetTick="+targetTick
1958                                                                +"  loopEnd="+loopEnd
1959                                                                +"  jumping to loopStart="+loopStart
1960                                                                +"  new currLoopCounter="+currLoopCounter);
1961                             if (DEBUG_PUMP) Printer.println("  currMillis="+currMillis
1962                                                                +"  checkPointMillis="+checkPointMillis
1963                                                                +"  checkPointTick="+checkPointTick);
1964 
1965                         }
1966                     }
1967                     lastTick = targetTick;
1968                 }
1969 
1970                 finishedTracks = 0;
1971 
1972                 for (int t = 0; t < tracks.length; t++) {
1973                     try {
1974                         boolean disabled = trackDisabled[t];
1975                         Track thisTrack = tracks[t];
1976                         int readPos = trackReadPos[t];
1977                         int size = thisTrack.size();
1978                         // play all events that are due until targetTick
1979                         while (!changesPending && (readPos < size)
1980                                && (currEvent = thisTrack.get(readPos)).getTick() <= targetTick) {
1981 
1982                             if ((readPos == size -1) &&  MidiUtils.isMetaEndOfTrack(currEvent.getMessage())) {
1983                                 // do not send out this message. Finished with this track
1984                                 readPos = size;
1985                                 break;
1986                             }
1987                             // TODO: some kind of heuristics if the MIDI messages have changed
1988                             // significantly (i.e. deleted or inserted a bunch of messages)
1989                             // since last time. Would need to set needReindex = true then
1990                             readPos++;
1991                             // only play this event if the track is enabled,
1992                             // or if it is a tempo message on track 0
1993                             // Note: cannot put this check outside
1994                             //       this inner loop in order to detect end of file
1995                             if (!disabled ||
1996                                 ((t == 0) && (MidiUtils.isMetaTempo(currEvent.getMessage())))) {
1997                                 changesPending = dispatchMessage(t, currEvent);
1998                             }
1999                         }
2000                         if (readPos >= size) {
2001                             finishedTracks++;
2002                         }
2003                         if (DEBUG_PUMP_ALL) {
2004                             System.out.print(" pumped track "+t+" ("+size+" events) "
2005                                              +" from index: "+trackReadPos[t]
2006                                              +" to "+(readPos-1));
2007                             System.out.print(" -> ticks: ");
2008                             if (trackReadPos[t] < size) {
2009                                 System.out.print(""+(thisTrack.get(trackReadPos[t]).getTick()));
2010                             } else {
2011                                 System.out.print("EOT");
2012                             }
2013                             System.out.print(" to ");
2014                             if (readPos < size) {
2015                                 System.out.print(""+(thisTrack.get(readPos-1).getTick()));
2016                             } else {
2017                                 System.out.print("EOT");
2018                             }
2019                             System.out.println();
2020                         }
2021                         trackReadPos[t] = readPos;
2022                     } catch(Exception e) {
2023                         if (Printer.debug) Printer.debug("Exception in Sequencer pump!");
2024                         if (Printer.debug) e.printStackTrace();
2025                         if (e instanceof ArrayIndexOutOfBoundsException) {
2026                             needReindex = true;
2027                             changesPending = true;
2028                         }
2029                     }
2030                     if (changesPending) {
2031                         break;
2032                     }
2033                 }
2034                 EOM = (finishedTracks == tracks.length);
2035                 if (doLoop
2036                     || ( ((loopCount > 0 && currLoopCounter > 0)
2037                           || (loopCount == LOOP_CONTINUOUSLY))
2038                          && !changesPending
2039                          && (loopEnd == -1)
2040                          && EOM)) {
2041 
2042                     long oldCheckPointMillis = checkPointMillis;
2043                     long loopEndTick = loopEnd;
2044                     if (loopEndTick == -1) {
2045                         loopEndTick = lastTick;
2046                     }
2047 
2048                     // need to loop back!
2049                     if (loopCount != LOOP_CONTINUOUSLY) {
2050                         currLoopCounter--;
2051                     }
2052                     if (DEBUG_PUMP) Printer.println("Execute loop: lastTick="+lastTick
2053                                                        +"  loopEnd="+loopEnd
2054                                                        +"  jumping to loopStart="+loopStart
2055                                                        +"  new currLoopCounter="+currLoopCounter);
2056                     setTickPos(loopStart);
2057                     // now patch the checkPointMillis so that
2058                     // it points to the exact beginning of when the loop was finished
2059 
2060                     // $$fb TODO: although this is mathematically correct (i.e. the loop position
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 }