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