1 /*
   2  * Copyright (c) 1999, 2016, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.media.sound;
  27 
  28 import java.util.ArrayList;
  29 import java.util.Collections;
  30 import java.util.List;
  31 
  32 import javax.sound.midi.InvalidMidiDataException;
  33 import javax.sound.midi.MidiDevice;
  34 import javax.sound.midi.MidiDeviceReceiver;
  35 import javax.sound.midi.MidiDeviceTransmitter;
  36 import javax.sound.midi.MidiMessage;
  37 import javax.sound.midi.MidiUnavailableException;
  38 import javax.sound.midi.Receiver;
  39 import javax.sound.midi.Transmitter;
  40 
  41 
  42 /**
  43  * Abstract AbstractMidiDevice class representing functionality shared by
  44  * MidiInDevice and MidiOutDevice objects.
  45  *
  46  * @author David Rivas
  47  * @author Kara Kytle
  48  * @author Matthias Pfisterer
  49  * @author Florian Bomers
  50  */
  51 abstract class AbstractMidiDevice implements MidiDevice, ReferenceCountingDevice {
  52 
  53     private static final boolean TRACE_TRANSMITTER = false;
  54 
  55     private ArrayList<Receiver> receiverList;
  56 
  57     private TransmitterList transmitterList;
  58 
  59     // lock to protect receiverList and transmitterList
  60     // from simultaneous creation and destruction
  61     // reduces possibility of deadlock, compared to
  62     // synchronizing to the class instance
  63     private final Object traRecLock = new Object();
  64 
  65     // DEVICE ATTRIBUTES
  66 
  67     private final MidiDevice.Info info;
  68 
  69     // DEVICE STATE
  70 
  71     private volatile boolean open;
  72     private int openRefCount;
  73 
  74     /** List of Receivers and Transmitters that opened the device implicitely.
  75      */
  76     private List<Object> openKeepingObjects;
  77 
  78     /**
  79      * This is the device handle returned from native code.
  80      */
  81     protected volatile long id;
  82 
  83     /**
  84      * Constructs an AbstractMidiDevice with the specified info object.
  85      * @param info the description of the device
  86      */
  87     /*
  88      * The initial mode and only supported mode default to OMNI_ON_POLY.
  89      */
  90     protected AbstractMidiDevice(MidiDevice.Info info) {
  91 
  92         if(Printer.trace) Printer.trace(">> AbstractMidiDevice CONSTRUCTOR");
  93 
  94         this.info = info;
  95         openRefCount = 0;
  96 
  97         if(Printer.trace) Printer.trace("<< AbstractMidiDevice CONSTRUCTOR completed");
  98     }
  99 
 100     // MIDI DEVICE METHODS
 101 
 102     @Override
 103     public final MidiDevice.Info getDeviceInfo() {
 104         return info;
 105     }
 106 
 107     /** Open the device from an application program.
 108      * Setting the open reference count to -1 here prevents Transmitters and Receivers that
 109      * opened the device implicitly from closing it. The only way to close the device after
 110      * this call is a call to close().
 111      */
 112     @Override
 113     public final void open() throws MidiUnavailableException {
 114         if (Printer.trace) Printer.trace("> AbstractMidiDevice: open()");
 115         synchronized(this) {
 116             openRefCount = -1;
 117             doOpen();
 118         }
 119         if (Printer.trace) Printer.trace("< AbstractMidiDevice: open() completed");
 120     }
 121 
 122     /** Open the device implicitly.
 123      * This method is intended to be used by AbstractReceiver
 124      * and BasicTransmitter. Actually, it is called by getReceiverReferenceCounting() and
 125      * getTransmitterReferenceCounting(). These, in turn, are called by MidiSytem on calls to
 126      * getReceiver() and getTransmitter(). The former methods should pass the Receiver or
 127      * Transmitter just created as the object parameter to this method. Storing references to
 128      * these objects is necessary to be able to decide later (when it comes to closing) if
 129      * R/T's are ones that opened the device implicitly.
 130      *
 131      * @object The Receiver or Transmitter instance that triggered this implicit open.
 132      */
 133     private void openInternal(Object object) throws MidiUnavailableException {
 134         if (Printer.trace) Printer.trace("> AbstractMidiDevice: openInternal()");
 135         synchronized(this) {
 136             if (openRefCount != -1) {
 137                 openRefCount++;
 138                 getOpenKeepingObjects().add(object);
 139             }
 140             // double calls to doOpens() will be catched by the open flag.
 141             doOpen();
 142         }
 143         if (Printer.trace) Printer.trace("< AbstractMidiDevice: openInternal() completed");
 144     }
 145 
 146     private void doOpen() throws MidiUnavailableException {
 147         if (Printer.trace) Printer.trace("> AbstractMidiDevice: doOpen()");
 148         synchronized(this) {
 149             if (! isOpen()) {
 150                 implOpen();
 151                 open = true;
 152             }
 153         }
 154         if (Printer.trace) Printer.trace("< AbstractMidiDevice: doOpen() completed");
 155     }
 156 
 157     @Override
 158     public final void close() {
 159         if (Printer.trace) Printer.trace("> AbstractMidiDevice: close()");
 160         synchronized (this) {
 161             doClose();
 162             openRefCount = 0;
 163         }
 164         if (Printer.trace) Printer.trace("< AbstractMidiDevice: close() completed");
 165     }
 166 
 167     /** Close the device for an object that implicitely opened it.
 168      * This method is intended to be used by Transmitter.close() and Receiver.close().
 169      * Those methods should pass this for the object parameter. Since Transmitters or Receivers
 170      * do not know if their device has been opened implicitely because of them, they call this
 171      * method in any case. This method now is able to seperate Receivers/Transmitters that opened
 172      * the device implicitely from those that didn't by looking up the R/T in the
 173      * openKeepingObjects list. Only if the R/T is contained there, the reference count is
 174      * reduced.
 175      *
 176      * @param object The object that might have been opening the device implicitely (for now,
 177      * this may be a Transmitter or receiver).
 178      */
 179     public final void closeInternal(Object object) {
 180         if (Printer.trace) Printer.trace("> AbstractMidiDevice: closeInternal()");
 181         synchronized(this) {
 182             if (getOpenKeepingObjects().remove(object)) {
 183                 if (openRefCount > 0) {
 184                     openRefCount--;
 185                     if (openRefCount == 0) {
 186                         doClose();
 187                     }
 188                 }
 189             }
 190         }
 191         if (Printer.trace) Printer.trace("< AbstractMidiDevice: closeInternal() completed");
 192     }
 193 
 194     public final void doClose() {
 195         if (Printer.trace) Printer.trace("> AbstractMidiDevice: doClose()");
 196         synchronized(this) {
 197             if (isOpen()) {
 198                 implClose();
 199                 open = false;
 200             }
 201         }
 202         if (Printer.trace) Printer.trace("< AbstractMidiDevice: doClose() completed");
 203     }
 204 
 205     @Override
 206     public final boolean isOpen() {
 207         return open;
 208     }
 209 
 210     protected void implClose() {
 211         synchronized (traRecLock) {
 212             if (receiverList != null) {
 213                 // close all receivers
 214                 for(int i = 0; i < receiverList.size(); i++) {
 215                     receiverList.get(i).close();
 216                 }
 217                 receiverList.clear();
 218             }
 219             if (transmitterList != null) {
 220                 // close all transmitters
 221                 transmitterList.close();
 222             }
 223         }
 224     }
 225 
 226     /**
 227      * This implementation always returns -1.
 228      * Devices that actually provide this should over-ride
 229      * this method.
 230      */
 231     @Override
 232     public long getMicrosecondPosition() {
 233         return -1;
 234     }
 235 
 236     /** Return the maximum number of Receivers supported by this device.
 237         Depending on the return value of hasReceivers(), this method returns either 0 or -1.
 238         Subclasses should rather override hasReceivers() than override this method.
 239      */
 240     @Override
 241     public final int getMaxReceivers() {
 242         if (hasReceivers()) {
 243             return -1;
 244         } else {
 245             return 0;
 246         }
 247     }
 248 
 249     /** Return the maximum number of Transmitters supported by this device.
 250         Depending on the return value of hasTransmitters(), this method returns either 0 or -1.
 251         Subclasses should override hasTransmitters().
 252      */
 253     @Override
 254     public final int getMaxTransmitters() {
 255         if (hasTransmitters()) {
 256             return -1;
 257         } else {
 258             return 0;
 259         }
 260     }
 261 
 262     /** Retrieve a Receiver for this device.
 263         This method returns the value returned by createReceiver(), if it doesn't throw
 264         an exception. Subclasses should rather override createReceiver() than override
 265         this method.
 266         If createReceiver returns a Receiver, it is added to the internal list
 267         of Receivers (see getReceiversList)
 268      */
 269     @Override
 270     public final Receiver getReceiver() throws MidiUnavailableException {
 271         Receiver receiver;
 272         synchronized (traRecLock) {
 273             receiver = createReceiver(); // may throw MidiUnavailableException
 274             getReceiverList().add(receiver);
 275         }
 276         return receiver;
 277     }
 278 
 279     @Override
 280     @SuppressWarnings("unchecked") // Cast of result of clone
 281     public final List<Receiver> getReceivers() {
 282         List<Receiver> recs;
 283         synchronized (traRecLock) {
 284             if (receiverList == null) {
 285                 recs = Collections.unmodifiableList(new ArrayList<Receiver>(0));
 286             } else {
 287                 recs = Collections.unmodifiableList
 288                     ((List<Receiver>) (receiverList.clone()));
 289             }
 290         }
 291         return recs;
 292     }
 293 
 294     /**
 295      * This implementation uses createTransmitter, which may throw an exception.
 296      * If a transmitter is returned in createTransmitter, it is added to the internal
 297      * TransmitterList
 298      */
 299     @Override
 300     public final Transmitter getTransmitter() throws MidiUnavailableException {
 301         Transmitter transmitter;
 302         synchronized (traRecLock) {
 303             transmitter = createTransmitter(); // may throw MidiUnavailableException
 304             getTransmitterList().add(transmitter);
 305         }
 306         return transmitter;
 307     }
 308 
 309     @Override
 310     @SuppressWarnings("unchecked") // Cast of result of clone
 311     public final List<Transmitter> getTransmitters() {
 312         List<Transmitter> tras;
 313         synchronized (traRecLock) {
 314             if (transmitterList == null
 315                 || transmitterList.transmitters.size() == 0) {
 316                 tras = Collections.unmodifiableList(new ArrayList<Transmitter>(0));
 317             } else {
 318                 tras = Collections.unmodifiableList((List<Transmitter>) (transmitterList.transmitters.clone()));
 319             }
 320         }
 321         return tras;
 322     }
 323 
 324     final long getId() {
 325         return id;
 326     }
 327 
 328     // REFERENCE COUNTING
 329 
 330     /** Retrieve a Receiver and open the device implicitly.
 331         This method is called by MidiSystem.getReceiver().
 332      */
 333     @Override
 334     public final Receiver getReceiverReferenceCounting()
 335             throws MidiUnavailableException {
 336         /* Keep this order of commands! If getReceiver() throws an exception,
 337            openInternal() should not be called!
 338         */
 339         Receiver receiver;
 340         synchronized (traRecLock) {
 341             receiver = getReceiver();
 342             AbstractMidiDevice.this.openInternal(receiver);
 343         }
 344         return receiver;
 345     }
 346 
 347     /** Retrieve a Transmitter and open the device implicitly.
 348         This method is called by MidiSystem.getTransmitter().
 349      */
 350     @Override
 351     public final Transmitter getTransmitterReferenceCounting()
 352             throws MidiUnavailableException {
 353         /* Keep this order of commands! If getTransmitter() throws an exception,
 354            openInternal() should not be called!
 355         */
 356         Transmitter transmitter;
 357         synchronized (traRecLock) {
 358             transmitter = getTransmitter();
 359             AbstractMidiDevice.this.openInternal(transmitter);
 360         }
 361         return transmitter;
 362     }
 363 
 364     /** Return the list of objects that have opened the device implicitely.
 365      */
 366     private synchronized List<Object> getOpenKeepingObjects() {
 367         if (openKeepingObjects == null) {
 368             openKeepingObjects = new ArrayList<>();
 369         }
 370         return openKeepingObjects;
 371     }
 372 
 373     // RECEIVER HANDLING METHODS
 374 
 375     /** Return the internal list of Receivers, possibly creating it first.
 376      */
 377     private List<Receiver> getReceiverList() {
 378         synchronized (traRecLock) {
 379             if (receiverList == null) {
 380                 receiverList = new ArrayList<>();
 381             }
 382         }
 383         return receiverList;
 384     }
 385 
 386     /** Returns if this device supports Receivers.
 387         Subclasses that use Receivers should override this method to
 388         return true. They also should override createReceiver().
 389 
 390         @return true, if the device supports Receivers, false otherwise.
 391     */
 392     protected boolean hasReceivers() {
 393         return false;
 394     }
 395 
 396     /** Create a Receiver object.
 397         throwing an exception here means that Receivers aren't enabled.
 398         Subclasses that use Receivers should override this method with
 399         one that returns objects implementing Receiver.
 400         Classes overriding this method should also override hasReceivers()
 401         to return true.
 402     */
 403     protected Receiver createReceiver() throws MidiUnavailableException {
 404         throw new MidiUnavailableException("MIDI IN receiver not available");
 405     }
 406 
 407     // TRANSMITTER HANDLING
 408 
 409     /** Return the internal list of Transmitters, possibly creating it first.
 410      */
 411     final TransmitterList getTransmitterList() {
 412         synchronized (traRecLock) {
 413             if (transmitterList == null) {
 414                 transmitterList = new TransmitterList();
 415             }
 416         }
 417         return transmitterList;
 418     }
 419 
 420     /** Returns if this device supports Transmitters.
 421         Subclasses that use Transmitters should override this method to
 422         return true. They also should override createTransmitter().
 423 
 424         @return true, if the device supports Transmitters, false otherwise.
 425     */
 426     protected boolean hasTransmitters() {
 427         return false;
 428     }
 429 
 430     /** Create a Transmitter object.
 431         throwing an exception here means that Transmitters aren't enabled.
 432         Subclasses that use Transmitters should override this method with
 433         one that returns objects implementing Transmitters.
 434         Classes overriding this method should also override hasTransmitters()
 435         to return true.
 436     */
 437     protected Transmitter createTransmitter() throws MidiUnavailableException {
 438         throw new MidiUnavailableException("MIDI OUT transmitter not available");
 439     }
 440 
 441     protected abstract void implOpen() throws MidiUnavailableException;
 442 
 443     /**
 444      * close this device if discarded by the garbage collector.
 445      */
 446     @Override
 447     protected final void finalize() {
 448         close();
 449     }
 450 
 451     /** Base class for Receivers.
 452         Subclasses that use Receivers must use this base class, since it
 453         contains magic necessary to manage implicit closing the device.
 454         This is necessary for Receivers retrieved via MidiSystem.getReceiver()
 455         (which opens the device implicitely).
 456      */
 457     abstract class AbstractReceiver implements MidiDeviceReceiver {
 458         private volatile boolean open = true;
 459 
 460 
 461         /** Deliver a MidiMessage.
 462             This method contains magic related to the closed state of a
 463             Receiver. Therefore, subclasses should not override this method.
 464             Instead, they should implement implSend().
 465         */
 466         @Override
 467         public final synchronized void send(final MidiMessage message,
 468                                             final long timeStamp) {
 469             if (!open) {
 470                 throw new IllegalStateException("Receiver is not open");
 471             }
 472             implSend(message, timeStamp);
 473         }
 474 
 475         abstract void implSend(MidiMessage message, long timeStamp);
 476 
 477         /** Close the Receiver.
 478          * Here, the call to the magic method closeInternal() takes place.
 479          * Therefore, subclasses that override this method must call
 480          * 'super.close()'.
 481          */
 482         @Override
 483         public final void close() {
 484             open = false;
 485             synchronized (AbstractMidiDevice.this.traRecLock) {
 486                 AbstractMidiDevice.this.getReceiverList().remove(this);
 487             }
 488             AbstractMidiDevice.this.closeInternal(this);
 489         }
 490 
 491         @Override
 492         public final MidiDevice getMidiDevice() {
 493             return AbstractMidiDevice.this;
 494         }
 495 
 496         final boolean isOpen() {
 497             return open;
 498         }
 499 
 500         //$$fb is that a good idea?
 501         //protected void finalize() {
 502         //    close();
 503         //}
 504 
 505     } // class AbstractReceiver
 506 
 507 
 508     /**
 509      * Transmitter base class.
 510      * This class especially makes sure the device is closed if it
 511      * has been opened implicitly by a call to MidiSystem.getTransmitter().
 512      * The logic of doing so is actually in closeInternal().
 513      *
 514      * Also, it has some optimizations regarding sending to the Receivers,
 515      * for known Receivers, and managing itself in the TransmitterList.
 516      */
 517     class BasicTransmitter implements MidiDeviceTransmitter {
 518 
 519         private Receiver receiver = null;
 520         TransmitterList tlist = null;
 521 
 522         protected BasicTransmitter() {
 523         }
 524 
 525         private void setTransmitterList(TransmitterList tlist) {
 526             this.tlist = tlist;
 527         }
 528 
 529         @Override
 530         public final void setReceiver(Receiver receiver) {
 531             if (tlist != null && this.receiver != receiver) {
 532                 if (Printer.debug) Printer.debug("Transmitter "+toString()+": set receiver "+receiver);
 533                 tlist.receiverChanged(this, this.receiver, receiver);
 534                 this.receiver = receiver;
 535             }
 536         }
 537 
 538         @Override
 539         public final Receiver getReceiver() {
 540             return receiver;
 541         }
 542 
 543         /** Close the Transmitter.
 544          * Here, the call to the magic method closeInternal() takes place.
 545          * Therefore, subclasses that override this method must call
 546          * 'super.close()'.
 547          */
 548         @Override
 549         public final void close() {
 550             AbstractMidiDevice.this.closeInternal(this);
 551             if (tlist != null) {
 552                 tlist.receiverChanged(this, this.receiver, null);
 553                 tlist.remove(this);
 554                 tlist = null;
 555             }
 556         }
 557 
 558         @Override
 559         public final MidiDevice getMidiDevice() {
 560             return AbstractMidiDevice.this;
 561         }
 562 
 563     } // class BasicTransmitter
 564 
 565     /**
 566      * a class to manage a list of transmitters.
 567      */
 568     final class TransmitterList {
 569 
 570         private final ArrayList<Transmitter> transmitters = new ArrayList<>();
 571         private MidiOutDevice.MidiOutReceiver midiOutReceiver;
 572 
 573         // how many transmitters must be present for optimized
 574         // handling
 575         private int optimizedReceiverCount = 0;
 576 
 577 
 578         private void add(Transmitter t) {
 579             synchronized(transmitters) {
 580                 transmitters.add(t);
 581             }
 582             if (t instanceof BasicTransmitter) {
 583                 ((BasicTransmitter) t).setTransmitterList(this);
 584             }
 585             if (Printer.debug) Printer.debug("--added transmitter "+t);
 586         }
 587 
 588         private void remove(Transmitter t) {
 589             synchronized(transmitters) {
 590                 int index = transmitters.indexOf(t);
 591                 if (index >= 0) {
 592                     transmitters.remove(index);
 593                     if (Printer.debug) Printer.debug("--removed transmitter "+t);
 594                 }
 595             }
 596         }
 597 
 598         private void receiverChanged(BasicTransmitter t,
 599                                      Receiver oldR,
 600                                      Receiver newR) {
 601             synchronized(transmitters) {
 602                 // some optimization
 603                 if (midiOutReceiver == oldR) {
 604                     midiOutReceiver = null;
 605                 }
 606                 if (newR != null) {
 607                     if ((newR instanceof MidiOutDevice.MidiOutReceiver)
 608                         && (midiOutReceiver == null)) {
 609                         midiOutReceiver = ((MidiOutDevice.MidiOutReceiver) newR);
 610                     }
 611                 }
 612                 optimizedReceiverCount =
 613                       ((midiOutReceiver!=null)?1:0);
 614             }
 615             // more potential for optimization here
 616         }
 617 
 618 
 619         /** closes all transmitters and empties the list */
 620         void close() {
 621             synchronized (transmitters) {
 622                 for(int i = 0; i < transmitters.size(); i++) {
 623                     transmitters.get(i).close();
 624                 }
 625                 transmitters.clear();
 626             }
 627             if (Printer.trace) Printer.trace("TransmitterList.close() succeeded");
 628         }
 629 
 630 
 631 
 632         /**
 633         * Send this message to all receivers
 634         * status = packedMessage & 0xFF
 635         * data1 = (packedMessage & 0xFF00) >> 8;
 636         * data1 = (packedMessage & 0xFF0000) >> 16;
 637         */
 638         void sendMessage(int packedMessage, long timeStamp) {
 639             try {
 640                 synchronized(transmitters) {
 641                     int size = transmitters.size();
 642                     if (optimizedReceiverCount == size) {
 643                         if (midiOutReceiver != null) {
 644                             if (TRACE_TRANSMITTER) Printer.println("Sending packed message to MidiOutReceiver");
 645                             midiOutReceiver.sendPackedMidiMessage(packedMessage, timeStamp);
 646                         }
 647                     } else {
 648                         if (TRACE_TRANSMITTER) Printer.println("Sending packed message to "+size+" transmitter's receivers");
 649                         for (int i = 0; i < size; i++) {
 650                             Receiver receiver = transmitters.get(i).getReceiver();
 651                             if (receiver != null) {
 652                                 if (optimizedReceiverCount > 0) {
 653                                     if (receiver instanceof MidiOutDevice.MidiOutReceiver) {
 654                                         ((MidiOutDevice.MidiOutReceiver) receiver).sendPackedMidiMessage(packedMessage, timeStamp);
 655                                     } else {
 656                                         receiver.send(new FastShortMessage(packedMessage), timeStamp);
 657                                     }
 658                                 } else {
 659                                     receiver.send(new FastShortMessage(packedMessage), timeStamp);
 660                                 }
 661                             }
 662                         }
 663                     }
 664                 }
 665             } catch (InvalidMidiDataException e) {
 666                 // this happens when invalid data comes over the wire. Ignore it.
 667             }
 668         }
 669 
 670         void sendMessage(byte[] data, long timeStamp) {
 671             try {
 672                 synchronized(transmitters) {
 673                     int size = transmitters.size();
 674                     if (TRACE_TRANSMITTER) Printer.println("Sending long message to "+size+" transmitter's receivers");
 675                     for (int i = 0; i < size; i++) {
 676                         Receiver receiver = transmitters.get(i).getReceiver();
 677                         if (receiver != null) {
 678                             //$$fb 2002-04-02: SysexMessages are mutable, so
 679                             // an application could change the contents of this object,
 680                             // or try to use the object later. So we can't get around object creation
 681                             // But the array need not be unique for each FastSysexMessage object,
 682                             // because it cannot be modified.
 683                             receiver.send(new FastSysexMessage(data), timeStamp);
 684                         }
 685                     }
 686                 }
 687             } catch (InvalidMidiDataException e) {
 688                 // this happens when invalid data comes over the wire. Ignore it.
 689                 return;
 690             }
 691         }
 692 
 693         /**
 694         * Send this message to all transmitters.
 695         */
 696         void sendMessage(MidiMessage message, long timeStamp) {
 697             if (message instanceof FastShortMessage) {
 698                 sendMessage(((FastShortMessage) message).getPackedMsg(), timeStamp);
 699                 return;
 700             }
 701             synchronized(transmitters) {
 702                 int size = transmitters.size();
 703                 if (optimizedReceiverCount == size) {
 704                     if (midiOutReceiver != null) {
 705                         if (TRACE_TRANSMITTER) Printer.println("Sending MIDI message to MidiOutReceiver");
 706                         midiOutReceiver.send(message, timeStamp);
 707                     }
 708                 } else {
 709                     if (TRACE_TRANSMITTER) Printer.println("Sending MIDI message to "+size+" transmitter's receivers");
 710                     for (int i = 0; i < size; i++) {
 711                         Receiver receiver = transmitters.get(i).getReceiver();
 712                         if (receiver != null) {
 713                             //$$fb 2002-04-02: ShortMessages are mutable, so
 714                             // an application could change the contents of this object,
 715                             // or try to use the object later.
 716                             // We violate this spec here, to avoid costly (and gc-intensive)
 717                             // object creation for potentially hundred of messages per second.
 718                             // The spec should be changed to allow Immutable MidiMessages
 719                             // (i.e. throws InvalidStateException or so in setMessage)
 720                             receiver.send(message, timeStamp);
 721                         }
 722                     }
 723                 }
 724             }
 725         }
 726     } // TransmitterList
 727 }