Save This Page
Home » openjdk-7 » com.sun.media » sound » [javadoc | source]
    1   /*
    2    * Copyright (c) 2003, 2011, 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   }

Save This Page
Home » openjdk-7 » com.sun.media » sound » [javadoc | source]