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