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