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