1 /*
   2  * Copyright (c) 1999, 2013, 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 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     public final List<Receiver> getReceivers() {
 288         List<Receiver> recs;
 289         synchronized (traRecLock) {
 290             if (receiverList == null) {
 291                 recs = Collections.unmodifiableList(new ArrayList<Receiver>(0));
 292             } else {
 293                 recs = Collections.unmodifiableList
 294                     ((List<Receiver>) (receiverList.clone()));
 295             }
 296         }
 297         return recs;
 298     }
 299 
 300 
 301     /**
 302      * This implementation uses createTransmitter, which may throw an exception.
 303      * If a transmitter is returned in createTransmitter, it is added to the internal
 304      * TransmitterList
 305      */
 306     public final Transmitter getTransmitter() throws MidiUnavailableException {
 307         Transmitter transmitter;
 308         synchronized (traRecLock) {
 309             transmitter = createTransmitter(); // may throw MidiUnavailableException
 310             getTransmitterList().add(transmitter);
 311         }
 312         return transmitter;
 313     }
 314 
 315 
 316     public final List<Transmitter> getTransmitters() {
 317         List<Transmitter> tras;
 318         synchronized (traRecLock) {
 319             if (transmitterList == null
 320                 || transmitterList.transmitters.size() == 0) {
 321                 tras = Collections.unmodifiableList(new ArrayList<Transmitter>(0));
 322             } else {
 323                 tras = Collections.unmodifiableList((List<Transmitter>) (transmitterList.transmitters.clone()));
 324             }
 325         }
 326         return tras;
 327     }
 328 
 329 
 330     // HELPER METHODS
 331 
 332     final long getId() {
 333         return id;
 334     }
 335 
 336 
 337     // REFERENCE COUNTING
 338 
 339     /** Retrieve a Receiver and open the device implicitly.
 340         This method is called by MidiSystem.getReceiver().
 341      */
 342     public final Receiver getReceiverReferenceCounting()
 343             throws MidiUnavailableException {
 344         /* Keep this order of commands! If getReceiver() throws an exception,
 345            openInternal() should not be called!
 346         */
 347         Receiver receiver;
 348         synchronized (traRecLock) {
 349             receiver = getReceiver();
 350             AbstractMidiDevice.this.openInternal(receiver);
 351         }
 352         return receiver;
 353     }
 354 
 355 
 356     /** Retrieve a Transmitter and open the device implicitly.
 357         This method is called by MidiSystem.getTransmitter().
 358      */
 359     public final Transmitter getTransmitterReferenceCounting()
 360             throws MidiUnavailableException {
 361         /* Keep this order of commands! If getTransmitter() throws an exception,
 362            openInternal() should not be called!
 363         */
 364         Transmitter transmitter;
 365         synchronized (traRecLock) {
 366             transmitter = getTransmitter();
 367             AbstractMidiDevice.this.openInternal(transmitter);
 368         }
 369         return transmitter;
 370     }
 371 
 372 
 373     /** Return the list of objects that have opened the device implicitely.
 374      */
 375     private synchronized List getOpenKeepingObjects() {
 376         if (openKeepingObjects == null) {
 377             openKeepingObjects = new ArrayList();
 378         }
 379         return openKeepingObjects;
 380     }
 381 
 382 
 383 
 384     // RECEIVER HANDLING METHODS
 385 
 386 
 387     /** Return the internal list of Receivers, possibly creating it first.
 388      */
 389     private List<Receiver> getReceiverList() {
 390         synchronized (traRecLock) {
 391             if (receiverList == null) {
 392                 receiverList = new ArrayList<Receiver>();
 393             }
 394         }
 395         return receiverList;
 396     }
 397 
 398 
 399     /** Returns if this device supports Receivers.
 400         Subclasses that use Receivers should override this method to
 401         return true. They also should override createReceiver().
 402 
 403         @return true, if the device supports Receivers, false otherwise.
 404     */
 405     protected boolean hasReceivers() {
 406         return false;
 407     }
 408 
 409 
 410     /** Create a Receiver object.
 411         throwing an exception here means that Receivers aren't enabled.
 412         Subclasses that use Receivers should override this method with
 413         one that returns objects implementing Receiver.
 414         Classes overriding this method should also override hasReceivers()
 415         to return true.
 416     */
 417     protected Receiver createReceiver() throws MidiUnavailableException {
 418         throw new MidiUnavailableException("MIDI IN receiver not available");
 419     }
 420 
 421 
 422 
 423     // TRANSMITTER HANDLING
 424 
 425     /** Return the internal list of Transmitters, possibly creating it first.
 426      */
 427     final TransmitterList getTransmitterList() {
 428         synchronized (traRecLock) {
 429             if (transmitterList == null) {
 430                 transmitterList = new TransmitterList();
 431             }
 432         }
 433         return transmitterList;
 434     }
 435 
 436 
 437     /** Returns if this device supports Transmitters.
 438         Subclasses that use Transmitters should override this method to
 439         return true. They also should override createTransmitter().
 440 
 441         @return true, if the device supports Transmitters, false otherwise.
 442     */
 443     protected boolean hasTransmitters() {
 444         return false;
 445     }
 446 
 447 
 448     /** Create a Transmitter object.
 449         throwing an exception here means that Transmitters aren't enabled.
 450         Subclasses that use Transmitters should override this method with
 451         one that returns objects implementing Transmitters.
 452         Classes overriding this method should also override hasTransmitters()
 453         to return true.
 454     */
 455     protected Transmitter createTransmitter() throws MidiUnavailableException {
 456         throw new MidiUnavailableException("MIDI OUT transmitter not available");
 457     }
 458 
 459     // ABSTRACT METHODS
 460 
 461     protected abstract void implOpen() throws MidiUnavailableException;
 462 
 463 
 464     /**
 465      * close this device if discarded by the garbage collector
 466      */
 467     protected final void finalize() {
 468         close();
 469     }
 470 
 471     // INNER CLASSES
 472 
 473     /** Base class for Receivers.
 474         Subclasses that use Receivers must use this base class, since it
 475         contains magic necessary to manage implicit closing the device.
 476         This is necessary for Receivers retrieved via MidiSystem.getReceiver()
 477         (which opens the device implicitely).
 478      */
 479     abstract class AbstractReceiver implements MidiDeviceReceiver {
 480         private boolean open = true;
 481 
 482 
 483         /** Deliver a MidiMessage.
 484             This method contains magic related to the closed state of a
 485             Receiver. Therefore, subclasses should not override this method.
 486             Instead, they should implement implSend().
 487         */
 488         @Override
 489         public final synchronized void send(final MidiMessage message,
 490                                             final long timeStamp) {
 491             if (!open) {
 492                 throw new IllegalStateException("Receiver is not open");
 493             }
 494             implSend(message, timeStamp);
 495         }
 496 
 497         abstract void implSend(MidiMessage message, long timeStamp);
 498 
 499         /** Close the Receiver.
 500          * Here, the call to the magic method closeInternal() takes place.
 501          * Therefore, subclasses that override this method must call
 502          * 'super.close()'.
 503          */
 504         @Override
 505         public final void close() {
 506             open = false;
 507             synchronized (AbstractMidiDevice.this.traRecLock) {
 508                 AbstractMidiDevice.this.getReceiverList().remove(this);
 509             }
 510             AbstractMidiDevice.this.closeInternal(this);
 511         }
 512 
 513         @Override
 514         public final MidiDevice getMidiDevice() {
 515             return AbstractMidiDevice.this;
 516         }
 517 
 518         final boolean isOpen() {
 519             return open;
 520         }
 521 
 522         //$$fb is that a good idea?
 523         //protected void finalize() {
 524         //    close();
 525         //}
 526 
 527     } // class AbstractReceiver
 528 
 529 
 530     /**
 531      * Transmitter base class.
 532      * This class especially makes sure the device is closed if it
 533      * has been opened implicitly by a call to MidiSystem.getTransmitter().
 534      * The logic of doing so is actually in closeInternal().
 535      *
 536      * Also, it has some optimizations regarding sending to the Receivers,
 537      * for known Receivers, and managing itself in the TransmitterList.
 538      */
 539     class BasicTransmitter implements MidiDeviceTransmitter {
 540 
 541         private Receiver receiver = null;
 542         TransmitterList tlist = null;
 543 
 544         protected BasicTransmitter() {
 545         }
 546 
 547         private void setTransmitterList(TransmitterList tlist) {
 548             this.tlist = tlist;
 549         }
 550 
 551         public final void setReceiver(Receiver receiver) {
 552             if (tlist != null && this.receiver != receiver) {
 553                 if (Printer.debug) Printer.debug("Transmitter "+toString()+": set receiver "+receiver);
 554                 tlist.receiverChanged(this, this.receiver, receiver);
 555                 this.receiver = receiver;
 556             }
 557         }
 558 
 559         public final Receiver getReceiver() {
 560             return receiver;
 561         }
 562 
 563 
 564         /** Close the Transmitter.
 565          * Here, the call to the magic method closeInternal() takes place.
 566          * Therefore, subclasses that override this method must call
 567          * 'super.close()'.
 568          */
 569         public final void close() {
 570             AbstractMidiDevice.this.closeInternal(this);
 571             if (tlist != null) {
 572                 tlist.receiverChanged(this, this.receiver, null);
 573                 tlist.remove(this);
 574                 tlist = null;
 575             }
 576         }
 577 
 578         public final MidiDevice getMidiDevice() {
 579             return AbstractMidiDevice.this;
 580         }
 581 
 582     } // class BasicTransmitter
 583 
 584 
 585     /**
 586      * a class to manage a list of transmitters
 587      */
 588     final class TransmitterList {
 589 
 590         private final ArrayList<Transmitter> transmitters = new ArrayList<Transmitter>();
 591         private MidiOutDevice.MidiOutReceiver midiOutReceiver;
 592 
 593         // how many transmitters must be present for optimized
 594         // handling
 595         private int optimizedReceiverCount = 0;
 596 
 597 
 598         private void add(Transmitter t) {
 599             synchronized(transmitters) {
 600                 transmitters.add(t);
 601             }
 602             if (t instanceof BasicTransmitter) {
 603                 ((BasicTransmitter) t).setTransmitterList(this);
 604             }
 605             if (Printer.debug) Printer.debug("--added transmitter "+t);
 606         }
 607 
 608         private void remove(Transmitter t) {
 609             synchronized(transmitters) {
 610                 int index = transmitters.indexOf(t);
 611                 if (index >= 0) {
 612                     transmitters.remove(index);
 613                     if (Printer.debug) Printer.debug("--removed transmitter "+t);
 614                 }
 615             }
 616         }
 617 
 618         private void receiverChanged(BasicTransmitter t,
 619                                      Receiver oldR,
 620                                      Receiver newR) {
 621             synchronized(transmitters) {
 622                 // some optimization
 623                 if (midiOutReceiver == oldR) {
 624                     midiOutReceiver = null;
 625                 }
 626                 if (newR != null) {
 627                     if ((newR instanceof MidiOutDevice.MidiOutReceiver)
 628                         && (midiOutReceiver == null)) {
 629                         midiOutReceiver = ((MidiOutDevice.MidiOutReceiver) newR);
 630                     }
 631                 }
 632                 optimizedReceiverCount =
 633                       ((midiOutReceiver!=null)?1:0);
 634             }
 635             // more potential for optimization here
 636         }
 637 
 638 
 639         /** closes all transmitters and empties the list */
 640         void close() {
 641             synchronized (transmitters) {
 642                 for(int i = 0; i < transmitters.size(); i++) {
 643                     transmitters.get(i).close();
 644                 }
 645                 transmitters.clear();
 646             }
 647             if (Printer.trace) Printer.trace("TransmitterList.close() succeeded");
 648         }
 649 
 650 
 651 
 652         /**
 653         * Send this message to all receivers
 654         * status = packedMessage & 0xFF
 655         * data1 = (packedMessage & 0xFF00) >> 8;
 656         * data1 = (packedMessage & 0xFF0000) >> 16;
 657         */
 658         void sendMessage(int packedMessage, long timeStamp) {
 659             try {
 660                 synchronized(transmitters) {
 661                     int size = transmitters.size();
 662                     if (optimizedReceiverCount == size) {
 663                         if (midiOutReceiver != null) {
 664                             if (TRACE_TRANSMITTER) Printer.println("Sending packed message to MidiOutReceiver");
 665                             midiOutReceiver.sendPackedMidiMessage(packedMessage, timeStamp);
 666                         }
 667                     } else {
 668                         if (TRACE_TRANSMITTER) Printer.println("Sending packed message to "+size+" transmitter's receivers");
 669                         for (int i = 0; i < size; i++) {
 670                             Receiver receiver = ((Transmitter)transmitters.get(i)).getReceiver();
 671                             if (receiver != null) {
 672                                 if (optimizedReceiverCount > 0) {
 673                                     if (receiver instanceof MidiOutDevice.MidiOutReceiver) {
 674                                         ((MidiOutDevice.MidiOutReceiver) receiver).sendPackedMidiMessage(packedMessage, timeStamp);
 675                                     } else {
 676                                         receiver.send(new FastShortMessage(packedMessage), timeStamp);
 677                                     }
 678                                 } else {
 679                                     receiver.send(new FastShortMessage(packedMessage), timeStamp);
 680                                 }
 681                             }
 682                         }
 683                     }
 684                 }
 685             } catch (InvalidMidiDataException e) {
 686                 // this happens when invalid data comes over the wire. Ignore it.
 687             }
 688         }
 689 
 690         void sendMessage(byte[] data, long timeStamp) {
 691             try {
 692                 synchronized(transmitters) {
 693                     int size = transmitters.size();
 694                     if (TRACE_TRANSMITTER) Printer.println("Sending long message to "+size+" transmitter's receivers");
 695                     for (int i = 0; i < size; i++) {
 696                         Receiver receiver = ((Transmitter)transmitters.get(i)).getReceiver();
 697                         if (receiver != null) {
 698                             //$$fb 2002-04-02: SysexMessages are mutable, so
 699                             // an application could change the contents of this object,
 700                             // or try to use the object later. So we can't get around object creation
 701                             // But the array need not be unique for each FastSysexMessage object,
 702                             // because it cannot be modified.
 703                             receiver.send(new FastSysexMessage(data), timeStamp);
 704                         }
 705                     }
 706                 }
 707             } catch (InvalidMidiDataException e) {
 708                 // this happens when invalid data comes over the wire. Ignore it.
 709                 return;
 710             }
 711         }
 712 
 713 
 714         /**
 715         * Send this message to all transmitters
 716         */
 717         void sendMessage(MidiMessage message, long timeStamp) {
 718             if (message instanceof FastShortMessage) {
 719                 sendMessage(((FastShortMessage) message).getPackedMsg(), timeStamp);
 720                 return;
 721             }
 722             synchronized(transmitters) {
 723                 int size = transmitters.size();
 724                 if (optimizedReceiverCount == size) {
 725                     if (midiOutReceiver != null) {
 726                         if (TRACE_TRANSMITTER) Printer.println("Sending MIDI message to MidiOutReceiver");
 727                         midiOutReceiver.send(message, timeStamp);
 728                     }
 729                 } else {
 730                     if (TRACE_TRANSMITTER) Printer.println("Sending MIDI message to "+size+" transmitter's receivers");
 731                     for (int i = 0; i < size; i++) {
 732                         Receiver receiver = ((Transmitter)transmitters.get(i)).getReceiver();
 733                         if (receiver != null) {
 734                             //$$fb 2002-04-02: ShortMessages are mutable, so
 735                             // an application could change the contents of this object,
 736                             // or try to use the object later.
 737                             // We violate this spec here, to avoid costly (and gc-intensive)
 738                             // object creation for potentially hundred of messages per second.
 739                             // The spec should be changed to allow Immutable MidiMessages
 740                             // (i.e. throws InvalidStateException or so in setMessage)
 741                             receiver.send(message, timeStamp);
 742                         }
 743                     }
 744                 }
 745             }
 746         }
 747 
 748 
 749     } // TransmitterList
 750 
 751 }