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