1 /* 2 * Copyright (c) 2003, 2007, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.sun.media.sound; 27 28 import java.io.ByteArrayOutputStream; 29 import java.io.ByteArrayInputStream; 30 import java.io.DataOutputStream; 31 import java.io.IOException; 32 import java.io.InputStream; 33 34 import java.util.ArrayList; 35 import java.util.List; 36 37 import javax.sound.midi.*; 38 39 40 /** 41 * A Real Time Sequencer 42 * 43 * @author Florian Bomers 44 */ 45 46 /* TODO: 47 * - rename PlayThread to PlayEngine (because isn't a thread) 48 */ 49 class RealTimeSequencer extends AbstractMidiDevice implements Sequencer, AutoConnectSequencer { 50 51 // STATIC VARIABLES 52 53 /** debugging flags */ 54 private final static boolean DEBUG_PUMP = false; 55 private final static boolean DEBUG_PUMP_ALL = false; 56 57 /** 58 * Event Dispatcher thread. Should be using a shared event 59 * dispatcher instance with a factory in EventDispatcher 60 */ 61 private static final EventDispatcher eventDispatcher; 62 63 /** 64 * All RealTimeSequencers share this info object. 65 */ 66 static final RealTimeSequencerInfo info = new RealTimeSequencerInfo(); 67 68 69 private static Sequencer.SyncMode[] masterSyncModes = { Sequencer.SyncMode.INTERNAL_CLOCK }; 70 private static Sequencer.SyncMode[] slaveSyncModes = { Sequencer.SyncMode.NO_SYNC }; 71 72 private static Sequencer.SyncMode masterSyncMode = Sequencer.SyncMode.INTERNAL_CLOCK; 73 private static Sequencer.SyncMode slaveSyncMode = Sequencer.SyncMode.NO_SYNC; 74 75 76 /** 77 * Sequence on which this sequencer is operating. 78 */ 79 private Sequence sequence = null; 80 81 // caches 82 83 /** 84 * Same for setTempoInMPQ... 85 * -1 means not set. 86 */ 87 private double cacheTempoMPQ = -1; 88 89 90 /** 91 * cache value for tempo factor until sequence is set 92 * -1 means not set. 93 */ 94 private float cacheTempoFactor = -1; 95 96 97 /** if a particular track is muted */ 98 private boolean[] trackMuted = null; 99 /** if a particular track is solo */ 100 private boolean[] trackSolo = null; 101 102 /** tempo cache for getMicrosecondPosition */ 103 private MidiUtils.TempoCache tempoCache = new MidiUtils.TempoCache(); 104 105 /** 106 * True if the sequence is running. 107 */ 108 private boolean running = false; 109 110 111 /** the thread for pushing out the MIDI messages */ 112 private PlayThread playThread; 113 114 115 /** 116 * True if we are recording 117 */ 118 private boolean recording = false; 119 120 121 /** 122 * List of tracks to which we're recording 123 */ 124 private List recordingTracks = new ArrayList(); 125 126 127 private long loopStart = 0; 128 private long loopEnd = -1; 129 private int loopCount = 0; 130 131 132 /** 133 * Meta event listeners 134 */ 135 private ArrayList metaEventListeners = new ArrayList(); 136 137 138 /** 139 * Control change listeners 140 */ 141 private ArrayList controllerEventListeners = new ArrayList(); 142 143 144 /** automatic connection support */ 145 private boolean autoConnect = false; 146 147 /** if we need to autoconnect at next open */ 148 private boolean doAutoConnectAtNextOpen = false; 149 150 /** the receiver that this device is auto-connected to */ 151 Receiver autoConnectedReceiver = null; 152 153 154 static { 155 // create and start the global event thread 156 eventDispatcher = new EventDispatcher(); 157 eventDispatcher.start(); 158 } 159 160 161 /* ****************************** CONSTRUCTOR ****************************** */ 162 163 protected RealTimeSequencer() throws MidiUnavailableException { 164 super(info); 165 166 if (Printer.trace) Printer.trace(">> RealTimeSequencer CONSTRUCTOR"); 167 if (Printer.trace) Printer.trace("<< RealTimeSequencer CONSTRUCTOR completed"); 168 } 169 170 171 /* ****************************** SEQUENCER METHODS ******************** */ 172 173 public synchronized void setSequence(Sequence sequence) 174 throws InvalidMidiDataException { 175 176 if (Printer.trace) Printer.trace(">> RealTimeSequencer: setSequence(" + sequence +")"); 177 178 if (sequence != this.sequence) { 179 if (this.sequence != null && sequence == null) { 180 setCaches(); 181 stop(); 182 // initialize some non-cached values 183 trackMuted = null; 184 trackSolo = null; 185 loopStart = 0; 186 loopEnd = -1; 187 loopCount = 0; 188 if (getDataPump() != null) { 189 getDataPump().setTickPos(0); 190 getDataPump().resetLoopCount(); 191 } 192 } 193 194 if (playThread != null) { 195 playThread.setSequence(sequence); 196 } 197 198 // store this sequence (do not copy - we want to give the possibility 199 // of modifying the sequence at runtime) 200 this.sequence = sequence; 201 202 if (sequence != null) { 203 tempoCache.refresh(sequence); 204 // rewind to the beginning 205 setTickPosition(0); 206 // propagate caches 207 propagateCaches(); 208 } 209 } 210 else if (sequence != null) { 211 tempoCache.refresh(sequence); 212 if (playThread != null) { 213 playThread.setSequence(sequence); 214 } 215 } 216 217 if (Printer.trace) Printer.trace("<< RealTimeSequencer: setSequence(" + sequence +") completed"); 218 } 219 220 221 public synchronized void setSequence(InputStream stream) throws IOException, InvalidMidiDataException { 222 223 if (Printer.trace) Printer.trace(">> RealTimeSequencer: setSequence(" + stream +")"); 224 225 if (stream == null) { 226 setSequence((Sequence) null); 227 return; 228 } 229 230 Sequence seq = MidiSystem.getSequence(stream); // can throw IOException, InvalidMidiDataException 231 232 setSequence(seq); 233 234 if (Printer.trace) Printer.trace("<< RealTimeSequencer: setSequence(" + stream +") completed"); 235 236 } 237 238 239 public Sequence getSequence() { 240 return sequence; 241 } 242 243 244 public synchronized void start() { 245 if (Printer.trace) Printer.trace(">> RealTimeSequencer: start()"); 246 247 // sequencer not open: throw an exception 248 if (!isOpen()) { 249 throw new IllegalStateException("sequencer not open"); 250 } 251 252 // sequence not available: throw an exception 253 if (sequence == null) { 254 throw new IllegalStateException("sequence not set"); 255 } 256 257 // already running: return quietly 258 if (running == true) { 259 return; 260 } 261 262 // start playback 263 implStart(); 264 265 if (Printer.trace) Printer.trace("<< RealTimeSequencer: start() completed"); 266 } 267 268 269 public synchronized void stop() { 270 if (Printer.trace) Printer.trace(">> RealTimeSequencer: stop()"); 271 272 if (!isOpen()) { 273 throw new IllegalStateException("sequencer not open"); 274 } 275 stopRecording(); 276 277 // not running; just return 278 if (running == false) { 279 if (Printer.trace) Printer.trace("<< RealTimeSequencer: stop() not running!"); 280 return; 281 } 282 283 // stop playback 284 implStop(); 285 286 if (Printer.trace) Printer.trace("<< RealTimeSequencer: stop() completed"); 287 } 288 289 290 public boolean isRunning() { 291 return running; 292 } 293 294 295 public void startRecording() { 296 if (!isOpen()) { 297 throw new IllegalStateException("Sequencer not open"); 298 } 299 300 start(); 301 recording = true; 302 } 303 304 305 public void stopRecording() { 306 if (!isOpen()) { 307 throw new IllegalStateException("Sequencer not open"); 308 } 309 recording = false; 310 } 311 312 313 public boolean isRecording() { 314 return recording; 315 } 316 317 318 public void recordEnable(Track track, int channel) { 319 if (!findTrack(track)) { 320 throw new IllegalArgumentException("Track does not exist in the current sequence"); 321 } 322 323 synchronized(recordingTracks) { 324 RecordingTrack rc = RecordingTrack.get(recordingTracks, track); 325 if (rc != null) { 326 rc.channel = channel; 327 } else { 328 recordingTracks.add(new RecordingTrack(track, channel)); 329 } 330 } 331 332 } 333 334 335 public void recordDisable(Track track) { 336 synchronized(recordingTracks) { 337 RecordingTrack rc = RecordingTrack.get(recordingTracks, track); 338 if (rc != null) { 339 recordingTracks.remove(rc); 340 } 341 } 342 343 } 344 345 346 private boolean findTrack(Track track) { 347 boolean found = false; 348 if (sequence != null) { 349 Track[] tracks = sequence.getTracks(); 350 for (int i = 0; i < tracks.length; i++) { 351 if (track == tracks[i]) { 352 found = true; 353 break; 354 } 355 } 356 } 357 return found; 358 } 359 360 361 public float getTempoInBPM() { 362 if (Printer.trace) Printer.trace(">> RealTimeSequencer: getTempoInBPM() "); 363 364 return (float) MidiUtils.convertTempo(getTempoInMPQ()); 365 } 366 367 368 public void setTempoInBPM(float bpm) { 369 if (Printer.trace) Printer.trace(">> RealTimeSequencer: setTempoInBPM() "); 370 if (bpm <= 0) { 371 // should throw IllegalArgumentException 372 bpm = 1.0f; 373 } 374 375 setTempoInMPQ((float) MidiUtils.convertTempo((double) bpm)); 376 } 377 378 379 public float getTempoInMPQ() { 380 if (Printer.trace) Printer.trace(">> RealTimeSequencer: getTempoInMPQ() "); 381 382 if (needCaching()) { 383 // if the sequencer is closed, return cached value 384 if (cacheTempoMPQ != -1) { 385 return (float) cacheTempoMPQ; 386 } 387 // if sequence is set, return current tempo 388 if (sequence != null) { 389 return tempoCache.getTempoMPQAt(getTickPosition()); 390 } 391 392 // last resort: return a standard tempo: 120bpm 393 return (float) MidiUtils.DEFAULT_TEMPO_MPQ; 394 } 395 return (float)getDataPump().getTempoMPQ(); 396 } 397 398 399 public void setTempoInMPQ(float mpq) { 400 if (mpq <= 0) { 401 // should throw IllegalArgumentException 402 mpq = 1.0f; 403 } 404 405 if (Printer.trace) Printer.trace(">> RealTimeSequencer: setTempoInMPQ() "); 406 407 if (needCaching()) { 408 // cache the value 409 cacheTempoMPQ = mpq; 410 } else { 411 // set the native tempo in MPQ 412 getDataPump().setTempoMPQ(mpq); 413 414 // reset the tempoInBPM and tempoInMPQ values so we won't use them again 415 cacheTempoMPQ = -1; 416 } 417 } 418 419 420 public void setTempoFactor(float factor) { 421 if (factor <= 0) { 422 // should throw IllegalArgumentException 423 return; 424 } 425 426 if (Printer.trace) Printer.trace(">> RealTimeSequencer: setTempoFactor() "); 427 428 if (needCaching()) { 429 cacheTempoFactor = factor; 430 } else { 431 getDataPump().setTempoFactor(factor); 432 // don't need cache anymore 433 cacheTempoFactor = -1; 434 } 435 } 436 437 438 public float getTempoFactor() { 439 if (Printer.trace) Printer.trace(">> RealTimeSequencer: getTempoFactor() "); 440 441 if (needCaching()) { 442 if (cacheTempoFactor != -1) { 443 return cacheTempoFactor; 444 } 445 return 1.0f; 446 } 447 return getDataPump().getTempoFactor(); 448 } 449 450 451 public long getTickLength() { 452 if (Printer.trace) Printer.trace(">> RealTimeSequencer: getTickLength() "); 453 454 if (sequence == null) { 455 return 0; 456 } 457 458 return sequence.getTickLength(); 459 } 460 461 462 public synchronized long getTickPosition() { 463 if (Printer.trace) Printer.trace(">> RealTimeSequencer: getTickPosition() "); 464 465 if (getDataPump() == null || sequence == null) { 466 return 0; 467 } 468 469 return getDataPump().getTickPos(); 470 } 471 472 473 public synchronized void setTickPosition(long tick) { 474 if (tick < 0) { 475 // should throw IllegalArgumentException 476 return; 477 } 478 479 if (Printer.trace) Printer.trace(">> RealTimeSequencer: setTickPosition("+tick+") "); 480 481 if (getDataPump() == null) { 482 if (tick != 0) { 483 // throw new InvalidStateException("cannot set position in closed state"); 484 } 485 } 486 else if (sequence == null) { 487 if (tick != 0) { 488 // throw new InvalidStateException("cannot set position if sequence is not set"); 489 } 490 } else { 491 getDataPump().setTickPos(tick); 492 } 493 } 494 495 496 public long getMicrosecondLength() { 497 if (Printer.trace) Printer.trace(">> RealTimeSequencer: getMicrosecondLength() "); 498 499 if (sequence == null) { 500 return 0; 501 } 502 503 return sequence.getMicrosecondLength(); 504 } 505 506 507 public long getMicrosecondPosition() { 508 if (Printer.trace) Printer.trace(">> RealTimeSequencer: getMicrosecondPosition() "); 509 510 if (getDataPump() == null || sequence == null) { 511 return 0; 512 } 513 synchronized (tempoCache) { 514 return MidiUtils.tick2microsecond(sequence, getDataPump().getTickPos(), tempoCache); 515 } 516 } 517 518 519 public void setMicrosecondPosition(long microseconds) { 520 if (microseconds < 0) { 521 // should throw IllegalArgumentException 522 return; 523 } 524 525 if (Printer.trace) Printer.trace(">> RealTimeSequencer: setMicrosecondPosition("+microseconds+") "); 526 527 if (getDataPump() == null) { 528 if (microseconds != 0) { 529 // throw new InvalidStateException("cannot set position in closed state"); 530 } 531 } 532 else if (sequence == null) { 533 if (microseconds != 0) { 534 // throw new InvalidStateException("cannot set position if sequence is not set"); 535 } 536 } else { 537 synchronized(tempoCache) { 538 setTickPosition(MidiUtils.microsecond2tick(sequence, microseconds, tempoCache)); 539 } 540 } 541 } 542 543 544 public void setMasterSyncMode(Sequencer.SyncMode sync) { 545 // not supported 546 } 547 548 549 public Sequencer.SyncMode getMasterSyncMode() { 550 return masterSyncMode; 551 } 552 553 554 public Sequencer.SyncMode[] getMasterSyncModes() { 555 Sequencer.SyncMode[] returnedModes = new Sequencer.SyncMode[masterSyncModes.length]; 556 System.arraycopy(masterSyncModes, 0, returnedModes, 0, masterSyncModes.length); 557 return returnedModes; 558 } 559 560 561 public void setSlaveSyncMode(Sequencer.SyncMode sync) { 562 // not supported 563 } 564 565 566 public Sequencer.SyncMode getSlaveSyncMode() { 567 return slaveSyncMode; 568 } 569 570 571 public Sequencer.SyncMode[] getSlaveSyncModes() { 572 Sequencer.SyncMode[] returnedModes = new Sequencer.SyncMode[slaveSyncModes.length]; 573 System.arraycopy(slaveSyncModes, 0, returnedModes, 0, slaveSyncModes.length); 574 return returnedModes; 575 } 576 577 protected int getTrackCount() { 578 Sequence seq = getSequence(); 579 if (seq != null) { 580 // $$fb wish there was a nicer way to get the number of tracks... 581 return sequence.getTracks().length; 582 } 583 return 0; 584 } 585 586 587 588 public synchronized void setTrackMute(int track, boolean mute) { 589 int trackCount = getTrackCount(); 590 if (track < 0 || track >= getTrackCount()) return; 591 trackMuted = ensureBoolArraySize(trackMuted, trackCount); 592 trackMuted[track] = mute; 593 if (getDataPump() != null) { 594 getDataPump().muteSoloChanged(); 595 } 596 } 597 598 599 public synchronized boolean getTrackMute(int track) { 600 if (track < 0 || track >= getTrackCount()) return false; 601 if (trackMuted == null || trackMuted.length <= track) return false; 602 return trackMuted[track]; 603 } 604 605 606 public synchronized void setTrackSolo(int track, boolean solo) { 607 int trackCount = getTrackCount(); 608 if (track < 0 || track >= getTrackCount()) return; 609 trackSolo = ensureBoolArraySize(trackSolo, trackCount); 610 trackSolo[track] = solo; 611 if (getDataPump() != null) { 612 getDataPump().muteSoloChanged(); 613 } 614 } 615 616 617 public synchronized boolean getTrackSolo(int track) { 618 if (track < 0 || track >= getTrackCount()) return false; 619 if (trackSolo == null || trackSolo.length <= track) return false; 620 return trackSolo[track]; 621 } 622 623 624 public boolean addMetaEventListener(MetaEventListener listener) { 625 synchronized(metaEventListeners) { 626 if (! metaEventListeners.contains(listener)) { 627 628 metaEventListeners.add(listener); 629 } 630 return true; 631 } 632 } 633 634 635 public void removeMetaEventListener(MetaEventListener listener) { 636 synchronized(metaEventListeners) { 637 int index = metaEventListeners.indexOf(listener); 638 if (index >= 0) { 639 metaEventListeners.remove(index); 640 } 641 } 642 } 643 644 645 public int[] addControllerEventListener(ControllerEventListener listener, int[] controllers) { 646 synchronized(controllerEventListeners) { 647 648 // first find the listener. if we have one, add the controllers 649 // if not, create a new element for it. 650 ControllerListElement cve = null; 651 boolean flag = false; 652 for(int i=0; i < controllerEventListeners.size(); i++) { 653 654 cve = (ControllerListElement) controllerEventListeners.get(i); 655 656 if (cve.listener.equals(listener)) { 657 cve.addControllers(controllers); 658 flag = true; 659 break; 660 } 661 } 662 if (!flag) { 663 cve = new ControllerListElement(listener, controllers); 664 controllerEventListeners.add(cve); 665 } 666 667 // and return all the controllers this listener is interested in 668 return cve.getControllers(); 669 } 670 } 671 672 673 public int[] removeControllerEventListener(ControllerEventListener listener, int[] controllers) { 674 synchronized(controllerEventListeners) { 675 ControllerListElement cve = null; 676 boolean flag = false; 677 for (int i=0; i < controllerEventListeners.size(); i++) { 678 cve = (ControllerListElement) controllerEventListeners.get(i); 679 if (cve.listener.equals(listener)) { 680 cve.removeControllers(controllers); 681 flag = true; 682 break; 683 } 684 } 685 if (!flag) { 686 return new int[0]; 687 } 688 if (controllers == null) { 689 int index = controllerEventListeners.indexOf(cve); 690 if (index >= 0) { 691 controllerEventListeners.remove(index); 692 } 693 return new int[0]; 694 } 695 return cve.getControllers(); 696 } 697 } 698 699 700 ////////////////// LOOPING (added in 1.5) /////////////////////// 701 702 public void setLoopStartPoint(long tick) { 703 if ((tick > getTickLength()) 704 || ((loopEnd != -1) && (tick > loopEnd)) 705 || (tick < 0)) { 706 throw new IllegalArgumentException("invalid loop start point: "+tick); 707 } 708 loopStart = tick; 709 } 710 711 public long getLoopStartPoint() { 712 return loopStart; 713 } 714 715 public void setLoopEndPoint(long tick) { 716 if ((tick > getTickLength()) 717 || ((loopStart > tick) && (tick != -1)) 718 || (tick < -1)) { 719 throw new IllegalArgumentException("invalid loop end point: "+tick); 720 } 721 loopEnd = tick; 722 } 723 724 public long getLoopEndPoint() { 725 return loopEnd; 726 } 727 728 public void setLoopCount(int count) { 729 if (count != LOOP_CONTINUOUSLY 730 && count < 0) { 731 throw new IllegalArgumentException("illegal value for loop count: "+count); 732 } 733 loopCount = count; 734 if (getDataPump() != null) { 735 getDataPump().resetLoopCount(); 736 } 737 } 738 739 public int getLoopCount() { 740 return loopCount; 741 } 742 743 744 /* *********************************** play control ************************* */ 745 746 /* 747 */ 748 protected void implOpen() throws MidiUnavailableException { 749 if (Printer.trace) Printer.trace(">> RealTimeSequencer: implOpen()"); 750 751 //openInternalSynth(); 752 753 // create PlayThread 754 playThread = new PlayThread(); 755 756 //id = nOpen(); 757 //if (id == 0) { 758 // throw new MidiUnavailableException("unable to open sequencer"); 759 //} 760 if (sequence != null) { 761 playThread.setSequence(sequence); 762 } 763 764 // propagate caches 765 propagateCaches(); 766 767 if (doAutoConnectAtNextOpen) { 768 doAutoConnect(); 769 } 770 if (Printer.trace) Printer.trace("<< RealTimeSequencer: implOpen() succeeded"); 771 } 772 773 private void doAutoConnect() { 774 if (Printer.trace) Printer.trace(">> RealTimeSequencer: doAutoConnect()"); 775 Receiver rec = null; 776 // first try to connect to the default synthesizer 777 // IMPORTANT: this code needs to be synch'ed with 778 // MidiSystem.getSequencer(boolean), because the same 779 // algorithm needs to be used! 780 try { 781 Synthesizer synth = MidiSystem.getSynthesizer(); 782 if (synth instanceof ReferenceCountingDevice) { 783 rec = ((ReferenceCountingDevice) synth).getReceiverReferenceCounting(); 784 } else { 785 synth.open(); 786 try { 787 rec = synth.getReceiver(); 788 } finally { 789 // make sure that the synth is properly closed 790 if (rec == null) { 791 synth.close(); 792 } 793 } 794 } 795 } catch (Exception e) { 796 // something went wrong with synth 797 } 798 if (rec == null) { 799 // then try to connect to the default Receiver 800 try { 801 rec = MidiSystem.getReceiver(); 802 } catch (Exception e) { 803 // something went wrong. Nothing to do then! 804 } 805 } 806 if (rec != null) { 807 autoConnectedReceiver = rec; 808 try { 809 getTransmitter().setReceiver(rec); 810 } catch (Exception e) {} 811 } 812 if (Printer.trace) Printer.trace("<< RealTimeSequencer: doAutoConnect() succeeded"); 813 } 814 815 private synchronized void propagateCaches() { 816 // only set caches if open and sequence is set 817 if (sequence != null && isOpen()) { 818 if (cacheTempoFactor != -1) { 819 setTempoFactor(cacheTempoFactor); 820 } 821 if (cacheTempoMPQ == -1) { 822 setTempoInMPQ((new MidiUtils.TempoCache(sequence)).getTempoMPQAt(getTickPosition())); 823 } else { 824 setTempoInMPQ((float) cacheTempoMPQ); 825 } 826 } 827 } 828 829 /** populate the caches with the current values */ 830 private synchronized void setCaches() { 831 cacheTempoFactor = getTempoFactor(); 832 cacheTempoMPQ = getTempoInMPQ(); 833 } 834 835 836 837 protected synchronized void implClose() { 838 if (Printer.trace) Printer.trace(">> RealTimeSequencer: implClose() "); 839 840 if (playThread == null) { 841 if (Printer.err) Printer.err("RealTimeSequencer.implClose() called, but playThread not instanciated!"); 842 } else { 843 // Interrupt playback loop. 844 playThread.close(); 845 playThread = null; 846 } 847 848 super.implClose(); 849 850 sequence = null; 851 running = false; 852 cacheTempoMPQ = -1; 853 cacheTempoFactor = -1; 854 trackMuted = null; 855 trackSolo = null; 856 loopStart = 0; 857 loopEnd = -1; 858 loopCount = 0; 859 860 /** if this sequencer is set to autoconnect, need to 861 * re-establish the connection at next open! 862 */ 863 doAutoConnectAtNextOpen = autoConnect; 864 865 if (autoConnectedReceiver != null) { 866 try { 867 autoConnectedReceiver.close(); 868 } catch (Exception e) {} 869 autoConnectedReceiver = null; 870 } 871 872 if (Printer.trace) Printer.trace("<< RealTimeSequencer: implClose() completed"); 873 } 874 875 protected void implStart() { 876 if (Printer.trace) Printer.trace(">> RealTimeSequencer: implStart()"); 877 878 if (playThread == null) { 879 if (Printer.err) Printer.err("RealTimeSequencer.implStart() called, but playThread not instanciated!"); 880 return; 881 } 882 883 tempoCache.refresh(sequence); 884 if (!running) { 885 running = true; 886 playThread.start(); 887 } 888 if (Printer.trace) Printer.trace("<< RealTimeSequencer: implStart() completed"); 889 } 890 891 892 protected void implStop() { 893 if (Printer.trace) Printer.trace(">> RealTimeSequencer: implStop()"); 894 895 if (playThread == null) { 896 if (Printer.err) Printer.err("RealTimeSequencer.implStop() called, but playThread not instanciated!"); 897 return; 898 } 899 900 recording = false; 901 if (running) { 902 running = false; 903 playThread.stop(); 904 } 905 if (Printer.trace) Printer.trace("<< RealTimeSequencer: implStop() completed"); 906 } 907 908 909 /** 910 * Send midi player events. 911 * must not be synchronized on "this" 912 */ 913 protected void sendMetaEvents(MidiMessage message) { 914 if (metaEventListeners.size() == 0) return; 915 916 //if (Printer.debug) Printer.debug("sending a meta event"); 917 eventDispatcher.sendAudioEvents(message, metaEventListeners); 918 } 919 920 /** 921 * Send midi player events. 922 */ 923 protected void sendControllerEvents(MidiMessage message) { 924 int size = controllerEventListeners.size(); 925 if (size == 0) return; 926 927 //if (Printer.debug) Printer.debug("sending a controller event"); 928 929 if (! (message instanceof ShortMessage)) { 930 if (Printer.debug) Printer.debug("sendControllerEvents: message is NOT instanceof ShortMessage!"); 931 return; 932 } 933 ShortMessage msg = (ShortMessage) message; 934 int controller = msg.getData1(); 935 List sendToListeners = new ArrayList(); 936 for (int i = 0; i < size; i++) { 937 ControllerListElement cve = (ControllerListElement) controllerEventListeners.get(i); 938 for(int j = 0; j < cve.controllers.length; j++) { 939 if (cve.controllers[j] == controller) { 940 sendToListeners.add(cve.listener); 941 break; 942 } 943 } 944 } 945 eventDispatcher.sendAudioEvents(message, sendToListeners); 946 } 947 948 949 950 private boolean needCaching() { 951 return !isOpen() || (sequence == null) || (playThread == null); 952 } 953 954 /** 955 * return the data pump instance, owned by play thread 956 * if playthread is null, return null. 957 * This method is guaranteed to return non-null if 958 * needCaching returns false 959 */ 960 private DataPump getDataPump() { 961 if (playThread != null) { 962 return playThread.getDataPump(); 963 } 964 return null; 965 } 966 967 private MidiUtils.TempoCache getTempoCache() { 968 return tempoCache; 969 } 970 971 private static boolean[] ensureBoolArraySize(boolean[] array, int desiredSize) { 972 if (array == null) { 973 return new boolean[desiredSize]; 974 } 975 if (array.length < desiredSize) { 976 boolean[] newArray = new boolean[desiredSize]; 977 System.arraycopy(array, 0, newArray, 0, array.length); 978 return newArray; 979 } 980 return array; 981 } 982 983 984 // OVERRIDES OF ABSTRACT MIDI DEVICE METHODS 985 986 protected boolean hasReceivers() { 987 return true; 988 } 989 990 // for recording 991 protected Receiver createReceiver() throws MidiUnavailableException { 992 return new SequencerReceiver(); 993 } 994 995 996 protected boolean hasTransmitters() { 997 return true; 998 } 999 1000 1001 protected Transmitter createTransmitter() throws MidiUnavailableException { 1002 return new SequencerTransmitter(); 1003 } 1004 1005 1006 // interface AutoConnectSequencer 1007 public void setAutoConnect(Receiver autoConnectedReceiver) { 1008 this.autoConnect = (autoConnectedReceiver != null); 1009 this.autoConnectedReceiver = autoConnectedReceiver; 1010 } 1011 1012 1013 1014 // INNER CLASSES 1015 1016 /** 1017 * An own class to distinguish the class name from 1018 * the transmitter of other devices 1019 */ 1020 private class SequencerTransmitter extends BasicTransmitter { 1021 private SequencerTransmitter() { 1022 super(); 1023 } 1024 } 1025 1026 1027 class SequencerReceiver extends AbstractReceiver { 1028 1029 protected void implSend(MidiMessage message, long timeStamp) { 1030 if (recording) { 1031 long tickPos = 0; 1032 1033 // convert timeStamp to ticks 1034 if (timeStamp < 0) { 1035 tickPos = getTickPosition(); 1036 } else { 1037 synchronized(tempoCache) { 1038 tickPos = MidiUtils.microsecond2tick(sequence, timeStamp, tempoCache); 1039 } 1040 } 1041 1042 // and record to the first matching Track 1043 Track track = null; 1044 // do not record real-time events 1045 // see 5048381: NullPointerException when saving a MIDI sequence 1046 if (message.getLength() > 1) { 1047 if (message instanceof ShortMessage) { 1048 ShortMessage sm = (ShortMessage) message; 1049 // all real-time messages have 0xF in the high nibble of the status byte 1050 if ((sm.getStatus() & 0xF0) != 0xF0) { 1051 track = RecordingTrack.get(recordingTracks, sm.getChannel()); 1052 } 1053 } else { 1054 // $$jb: where to record meta, sysex events? 1055 // $$fb: the first recording track 1056 track = RecordingTrack.get(recordingTracks, -1); 1057 } 1058 if (track != null) { 1059 // create a copy of this message 1060 if (message instanceof ShortMessage) { 1061 message = new FastShortMessage((ShortMessage) message); 1062 } else { 1063 message = (MidiMessage) message.clone(); 1064 } 1065 1066 // create new MidiEvent 1067 MidiEvent me = new MidiEvent(message, tickPos); 1068 track.add(me); 1069 } 1070 } 1071 } 1072 } 1073 } 1074 1075 1076 private static class RealTimeSequencerInfo extends MidiDevice.Info { 1077 1078 private static final String name = "Real Time Sequencer"; 1079 private static final String vendor = "Oracle Corporation"; 1080 private static final String description = "Software sequencer"; 1081 private static final String version = "Version 1.0"; 1082 1083 private RealTimeSequencerInfo() { 1084 super(name, vendor, description, version); 1085 } 1086 } // class Info 1087 1088 1089 private class ControllerListElement { 1090 1091 // $$jb: using an array for controllers b/c its 1092 // easier to deal with than turning all the 1093 // ints into objects to use a Vector 1094 int [] controllers; 1095 ControllerEventListener listener; 1096 1097 private ControllerListElement(ControllerEventListener listener, int[] controllers) { 1098 1099 this.listener = listener; 1100 if (controllers == null) { 1101 controllers = new int[128]; 1102 for (int i = 0; i < 128; i++) { 1103 controllers[i] = i; 1104 } 1105 } 1106 this.controllers = controllers; 1107 } 1108 1109 private void addControllers(int[] c) { 1110 1111 if (c==null) { 1112 controllers = new int[128]; 1113 for (int i = 0; i < 128; i++) { 1114 controllers[i] = i; 1115 } 1116 return; 1117 } 1118 int temp[] = new int[ controllers.length + c.length ]; 1119 int elements; 1120 1121 // first add what we have 1122 for(int i=0; i<controllers.length; i++) { 1123 temp[i] = controllers[i]; 1124 } 1125 elements = controllers.length; 1126 // now add the new controllers only if we don't already have them 1127 for(int i=0; i<c.length; i++) { 1128 boolean flag = false; 1129 1130 for(int j=0; j<controllers.length; j++) { 1131 if (c[i] == controllers[j]) { 1132 flag = true; 1133 break; 1134 } 1135 } 1136 if (!flag) { 1137 temp[elements++] = c[i]; 1138 } 1139 } 1140 // now keep only the elements we need 1141 int newc[] = new int[ elements ]; 1142 for(int i=0; i<elements; i++){ 1143 newc[i] = temp[i]; 1144 } 1145 controllers = newc; 1146 } 1147 1148 private void removeControllers(int[] c) { 1149 1150 if (c==null) { 1151 controllers = new int[0]; 1152 } else { 1153 int temp[] = new int[ controllers.length ]; 1154 int elements = 0; 1155 1156 1157 for(int i=0; i<controllers.length; i++){ 1158 boolean flag = false; 1159 for(int j=0; j<c.length; j++) { 1160 if (controllers[i] == c[j]) { 1161 flag = true; 1162 break; 1163 } 1164 } 1165 if (!flag){ 1166 temp[elements++] = controllers[i]; 1167 } 1168 } 1169 // now keep only the elements remaining 1170 int newc[] = new int[ elements ]; 1171 for(int i=0; i<elements; i++) { 1172 newc[i] = temp[i]; 1173 } 1174 controllers = newc; 1175 1176 } 1177 } 1178 1179 private int[] getControllers() { 1180 1181 // return a copy of our array of controllers, 1182 // so others can't mess with it 1183 if (controllers == null) { 1184 return null; 1185 } 1186 1187 int c[] = new int[controllers.length]; 1188 1189 for(int i=0; i<controllers.length; i++){ 1190 c[i] = controllers[i]; 1191 } 1192 return c; 1193 } 1194 1195 } // class ControllerListElement 1196 1197 1198 static class RecordingTrack { 1199 1200 private Track track; 1201 private int channel; 1202 1203 RecordingTrack(Track track, int channel) { 1204 this.track = track; 1205 this.channel = channel; 1206 } 1207 1208 static RecordingTrack get(List recordingTracks, Track track) { 1209 1210 synchronized(recordingTracks) { 1211 int size = recordingTracks.size(); 1212 1213 for (int i = 0; i < size; i++) { 1214 RecordingTrack current = (RecordingTrack)recordingTracks.get(i); 1215 if (current.track == track) { 1216 return current; 1217 } 1218 } 1219 } 1220 return null; 1221 } 1222 1223 static Track get(List recordingTracks, int channel) { 1224 1225 synchronized(recordingTracks) { 1226 int size = recordingTracks.size(); 1227 for (int i = 0; i < size; i++) { 1228 RecordingTrack current = (RecordingTrack)recordingTracks.get(i); 1229 if ((current.channel == channel) || (current.channel == -1)) { 1230 return current.track; 1231 } 1232 } 1233 } 1234 return null; 1235 1236 } 1237 } 1238 1239 1240 class PlayThread implements Runnable { 1241 private Thread thread; 1242 private Object lock = new Object(); 1243 1244 /** true if playback is interrupted (in close) */ 1245 boolean interrupted = false; 1246 boolean isPumping = false; 1247 1248 private DataPump dataPump = new DataPump(); 1249 1250 1251 PlayThread() { 1252 // nearly MAX_PRIORITY 1253 int priority = Thread.NORM_PRIORITY 1254 + ((Thread.MAX_PRIORITY - Thread.NORM_PRIORITY) * 3) / 4; 1255 thread = JSSecurityManager.createThread(this, 1256 "Java Sound Sequencer", // name 1257 false, // daemon 1258 priority, // priority 1259 true); // doStart 1260 } 1261 1262 DataPump getDataPump() { 1263 return dataPump; 1264 } 1265 1266 synchronized void setSequence(Sequence seq) { 1267 dataPump.setSequence(seq); 1268 } 1269 1270 1271 /** start thread and pump. Requires up-to-date tempoCache */ 1272 synchronized void start() { 1273 // mark the sequencer running 1274 running = true; 1275 1276 if (!dataPump.hasCachedTempo()) { 1277 long tickPos = getTickPosition(); 1278 dataPump.setTempoMPQ(tempoCache.getTempoMPQAt(tickPos)); 1279 } 1280 dataPump.checkPointMillis = 0; // means restarted 1281 dataPump.clearNoteOnCache(); 1282 dataPump.needReindex = true; 1283 1284 dataPump.resetLoopCount(); 1285 1286 // notify the thread 1287 synchronized(lock) { 1288 lock.notifyAll(); 1289 } 1290 1291 if (Printer.debug) Printer.debug(" ->Started MIDI play thread"); 1292 1293 } 1294 1295 // waits until stopped 1296 synchronized void stop() { 1297 playThreadImplStop(); 1298 long t = System.nanoTime() / 1000000l; 1299 while (isPumping) { 1300 synchronized(lock) { 1301 try { 1302 lock.wait(2000); 1303 } catch (InterruptedException ie) { 1304 // ignore 1305 } 1306 } 1307 // don't wait for more than 2 seconds 1308 if ((System.nanoTime()/1000000l) - t > 1900) { 1309 if (Printer.err) Printer.err("Waited more than 2 seconds in RealTimeSequencer.PlayThread.stop()!"); 1310 //break; 1311 } 1312 } 1313 } 1314 1315 void playThreadImplStop() { 1316 // mark the sequencer running 1317 running = false; 1318 synchronized(lock) { 1319 lock.notifyAll(); 1320 } 1321 } 1322 1323 void close() { 1324 Thread oldThread = null; 1325 synchronized (this) { 1326 // dispose of thread 1327 interrupted = true; 1328 oldThread = thread; 1329 thread = null; 1330 } 1331 if (oldThread != null) { 1332 // wake up the thread if it's in wait() 1333 synchronized(lock) { 1334 lock.notifyAll(); 1335 } 1336 } 1337 // wait for the thread to terminate itself, 1338 // but max. 2 seconds. Must not be synchronized! 1339 if (oldThread != null) { 1340 try { 1341 oldThread.join(2000); 1342 } catch (InterruptedException ie) {} 1343 } 1344 } 1345 1346 1347 /** 1348 * Main process loop driving the media flow. 1349 * 1350 * Make sure to NOT synchronize on RealTimeSequencer 1351 * anywhere here (even implicit). That is a sure deadlock! 1352 */ 1353 public void run() { 1354 1355 while (!interrupted) { 1356 boolean EOM = false; 1357 boolean wasRunning = running; 1358 isPumping = !interrupted && running; 1359 while (!EOM && !interrupted && running) { 1360 EOM = dataPump.pump(); 1361 1362 try { 1363 Thread.sleep(1); 1364 } catch (InterruptedException ie) { 1365 // ignore 1366 } 1367 } 1368 if (Printer.debug) { 1369 Printer.debug("Exited main pump loop because: "); 1370 if (EOM) Printer.debug(" -> EOM is reached"); 1371 if (!running) Printer.debug(" -> running was set to false"); 1372 if (interrupted) Printer.debug(" -> interrupted was set to true"); 1373 } 1374 1375 playThreadImplStop(); 1376 if (wasRunning) { 1377 dataPump.notesOff(true); 1378 } 1379 if (EOM) { 1380 dataPump.setTickPos(sequence.getTickLength()); 1381 1382 // send EOT event (mis-used for end of media) 1383 MetaMessage message = new MetaMessage(); 1384 try{ 1385 message.setMessage(MidiUtils.META_END_OF_TRACK_TYPE, new byte[0], 0); 1386 } catch(InvalidMidiDataException e1) {} 1387 sendMetaEvents(message); 1388 } 1389 synchronized (lock) { 1390 isPumping = false; 1391 // wake up a waiting stop() method 1392 lock.notifyAll(); 1393 while (!running && !interrupted) { 1394 try { 1395 lock.wait(); 1396 } catch (Exception ex) {} 1397 } 1398 } 1399 } // end of while(!EOM && !interrupted && running) 1400 if (Printer.debug) Printer.debug("end of play thread"); 1401 } 1402 } 1403 1404 1405 /** 1406 * class that does the actual dispatching of events, 1407 * used to be in native in MMAPI 1408 */ 1409 private class DataPump { 1410 private float currTempo; // MPQ tempo 1411 private float tempoFactor; // 1.0 is default 1412 private float inverseTempoFactor;// = 1.0 / tempoFactor 1413 private long ignoreTempoEventAt; // ignore next META tempo during playback at this tick pos only 1414 private int resolution; 1415 private float divisionType; 1416 private long checkPointMillis; // microseconds at checkoint 1417 private long checkPointTick; // ticks at checkpoint 1418 private int[] noteOnCache; // bit-mask of notes that are currently on 1419 private Track[] tracks; 1420 private boolean[] trackDisabled; // if true, do not play this track 1421 private int[] trackReadPos; // read index per track 1422 private long lastTick; 1423 private boolean needReindex = false; 1424 private int currLoopCounter = 0; 1425 1426 //private sun.misc.Perf perf = sun.misc.Perf.getPerf(); 1427 //private long perfFreq = perf.highResFrequency(); 1428 1429 1430 DataPump() { 1431 init(); 1432 } 1433 1434 synchronized void init() { 1435 ignoreTempoEventAt = -1; 1436 tempoFactor = 1.0f; 1437 inverseTempoFactor = 1.0f; 1438 noteOnCache = new int[128]; 1439 tracks = null; 1440 trackDisabled = null; 1441 } 1442 1443 synchronized void setTickPos(long tickPos) { 1444 long oldLastTick = tickPos; 1445 lastTick = tickPos; 1446 if (running) { 1447 notesOff(false); 1448 } 1449 if (running || tickPos > 0) { 1450 // will also reindex 1451 chaseEvents(oldLastTick, tickPos); 1452 } else { 1453 needReindex = true; 1454 } 1455 if (!hasCachedTempo()) { 1456 setTempoMPQ(getTempoCache().getTempoMPQAt(lastTick, currTempo)); 1457 // treat this as if it is a real time tempo change 1458 ignoreTempoEventAt = -1; 1459 } 1460 // trigger re-configuration 1461 checkPointMillis = 0; 1462 } 1463 1464 long getTickPos() { 1465 return lastTick; 1466 } 1467 1468 // hasCachedTempo is only valid if it is the current position 1469 boolean hasCachedTempo() { 1470 if (ignoreTempoEventAt != lastTick) { 1471 ignoreTempoEventAt = -1; 1472 } 1473 return ignoreTempoEventAt >= 0; 1474 } 1475 1476 // this method is also used internally in the pump! 1477 synchronized void setTempoMPQ(float tempoMPQ) { 1478 if (tempoMPQ > 0 && tempoMPQ != currTempo) { 1479 ignoreTempoEventAt = lastTick; 1480 this.currTempo = tempoMPQ; 1481 // re-calculate check point 1482 checkPointMillis = 0; 1483 } 1484 } 1485 1486 float getTempoMPQ() { 1487 return currTempo; 1488 } 1489 1490 synchronized void setTempoFactor(float factor) { 1491 if (factor > 0 && factor != this.tempoFactor) { 1492 tempoFactor = factor; 1493 inverseTempoFactor = 1.0f / factor; 1494 // re-calculate check point 1495 checkPointMillis = 0; 1496 } 1497 } 1498 1499 float getTempoFactor() { 1500 return tempoFactor; 1501 } 1502 1503 synchronized void muteSoloChanged() { 1504 boolean[] newDisabled = makeDisabledArray(); 1505 if (running) { 1506 applyDisabledTracks(trackDisabled, newDisabled); 1507 } 1508 trackDisabled = newDisabled; 1509 } 1510 1511 1512 1513 synchronized void setSequence(Sequence seq) { 1514 if (seq == null) { 1515 init(); 1516 return; 1517 } 1518 tracks = seq.getTracks(); 1519 muteSoloChanged(); 1520 resolution = seq.getResolution(); 1521 divisionType = seq.getDivisionType(); 1522 trackReadPos = new int[tracks.length]; 1523 // trigger re-initialization 1524 checkPointMillis = 0; 1525 needReindex = true; 1526 } 1527 1528 synchronized void resetLoopCount() { 1529 currLoopCounter = loopCount; 1530 } 1531 1532 void clearNoteOnCache() { 1533 for (int i = 0; i < 128; i++) { 1534 noteOnCache[i] = 0; 1535 } 1536 } 1537 1538 void notesOff(boolean doControllers) { 1539 int done = 0; 1540 for (int ch=0; ch<16; ch++) { 1541 int channelMask = (1<<ch); 1542 for (int i=0; i<128; i++) { 1543 if ((noteOnCache[i] & channelMask) != 0) { 1544 noteOnCache[i] ^= channelMask; 1545 // send note on with velocity 0 1546 getTransmitterList().sendMessage((ShortMessage.NOTE_ON | ch) | (i<<8), -1); 1547 done++; 1548 } 1549 } 1550 /* all notes off */ 1551 getTransmitterList().sendMessage((ShortMessage.CONTROL_CHANGE | ch) | (123<<8), -1); 1552 /* sustain off */ 1553 getTransmitterList().sendMessage((ShortMessage.CONTROL_CHANGE | ch) | (64<<8), -1); 1554 if (doControllers) { 1555 /* reset all controllers */ 1556 getTransmitterList().sendMessage((ShortMessage.CONTROL_CHANGE | ch) | (121<<8), -1); 1557 done++; 1558 } 1559 } 1560 if (DEBUG_PUMP) Printer.println(" noteOff: sent "+done+" messages."); 1561 } 1562 1563 1564 private boolean[] makeDisabledArray() { 1565 if (tracks == null) { 1566 return null; 1567 } 1568 boolean[] newTrackDisabled = new boolean[tracks.length]; 1569 boolean[] solo; 1570 boolean[] mute; 1571 synchronized(RealTimeSequencer.this) { 1572 mute = trackMuted; 1573 solo = trackSolo; 1574 } 1575 // if one track is solo, then only play solo 1576 boolean hasSolo = false; 1577 if (solo != null) { 1578 for (int i = 0; i < solo.length; i++) { 1579 if (solo[i]) { 1580 hasSolo = true; 1581 break; 1582 } 1583 } 1584 } 1585 if (hasSolo) { 1586 // only the channels with solo play, regardless of mute 1587 for (int i = 0; i < newTrackDisabled.length; i++) { 1588 newTrackDisabled[i] = (i >= solo.length) || (!solo[i]); 1589 } 1590 } else { 1591 // mute the selected channels 1592 for (int i = 0; i < newTrackDisabled.length; i++) { 1593 newTrackDisabled[i] = (mute != null) && (i < mute.length) && (mute[i]); 1594 } 1595 } 1596 return newTrackDisabled; 1597 } 1598 1599 /** 1600 * chase all events from beginning of Track 1601 * and send note off for those events that are active 1602 * in noteOnCache array. 1603 * It is possible, of course, to catch notes from other tracks, 1604 * but better than more complicated logic to detect 1605 * which notes are really from this track 1606 */ 1607 private void sendNoteOffIfOn(Track track, long endTick) { 1608 int size = track.size(); 1609 int done = 0; 1610 try { 1611 for (int i = 0; i < size; i++) { 1612 MidiEvent event = track.get(i); 1613 if (event.getTick() > endTick) break; 1614 MidiMessage msg = event.getMessage(); 1615 int status = msg.getStatus(); 1616 int len = msg.getLength(); 1617 if (len == 3 && ((status & 0xF0) == ShortMessage.NOTE_ON)) { 1618 int note = -1; 1619 if (msg instanceof ShortMessage) { 1620 ShortMessage smsg = (ShortMessage) msg; 1621 if (smsg.getData2() > 0) { 1622 // only consider Note On with velocity > 0 1623 note = smsg.getData1(); 1624 } 1625 } else { 1626 byte[] data = msg.getMessage(); 1627 if ((data[2] & 0x7F) > 0) { 1628 // only consider Note On with velocity > 0 1629 note = data[1] & 0x7F; 1630 } 1631 } 1632 if (note >= 0) { 1633 int bit = 1<<(status & 0x0F); 1634 if ((noteOnCache[note] & bit) != 0) { 1635 // the bit is set. Send Note Off 1636 getTransmitterList().sendMessage(status | (note<<8), -1); 1637 // clear the bit 1638 noteOnCache[note] &= (0xFFFF ^ bit); 1639 done++; 1640 } 1641 } 1642 } 1643 } 1644 } catch (ArrayIndexOutOfBoundsException aioobe) { 1645 // this happens when messages are removed 1646 // from the track while this method executes 1647 } 1648 if (DEBUG_PUMP) Printer.println(" sendNoteOffIfOn: sent "+done+" messages."); 1649 } 1650 1651 1652 /** 1653 * Runtime application of mute/solo: 1654 * if a track is muted that was previously playing, send 1655 * note off events for all currently playing notes 1656 */ 1657 private void applyDisabledTracks(boolean[] oldDisabled, boolean[] newDisabled) { 1658 byte[][] tempArray = null; 1659 synchronized(RealTimeSequencer.this) { 1660 for (int i = 0; i < newDisabled.length; i++) { 1661 if (((oldDisabled == null) 1662 || (i >= oldDisabled.length) 1663 || !oldDisabled[i]) 1664 && newDisabled[i]) { 1665 // case that a track gets muted: need to 1666 // send appropriate note off events to prevent 1667 // hanging notes 1668 1669 if (tracks.length > i) { 1670 sendNoteOffIfOn(tracks[i], lastTick); 1671 } 1672 } 1673 else if ((oldDisabled != null) 1674 && (i < oldDisabled.length) 1675 && oldDisabled[i] 1676 && !newDisabled[i]) { 1677 // case that a track was muted and is now unmuted 1678 // need to chase events and re-index this track 1679 if (tempArray == null) { 1680 tempArray = new byte[128][16]; 1681 } 1682 chaseTrackEvents(i, 0, lastTick, true, tempArray); 1683 } 1684 } 1685 } 1686 } 1687 1688 /** go through all events from startTick to endTick 1689 * chase the controller state and program change state 1690 * and then set the end-states at once. 1691 * 1692 * needs to be called in synchronized state 1693 * @param tempArray an byte[128][16] to hold controller messages 1694 */ 1695 private void chaseTrackEvents(int trackNum, 1696 long startTick, 1697 long endTick, 1698 boolean doReindex, 1699 byte[][] tempArray) { 1700 if (startTick > endTick) { 1701 // start from the beginning 1702 startTick = 0; 1703 } 1704 byte[] progs = new byte[16]; 1705 // init temp array with impossible values 1706 for (int ch = 0; ch < 16; ch++) { 1707 progs[ch] = -1; 1708 for (int co = 0; co < 128; co++) { 1709 tempArray[co][ch] = -1; 1710 } 1711 } 1712 Track track = tracks[trackNum]; 1713 int size = track.size(); 1714 try { 1715 for (int i = 0; i < size; i++) { 1716 MidiEvent event = track.get(i); 1717 if (event.getTick() >= endTick) { 1718 if (doReindex && (trackNum < trackReadPos.length)) { 1719 trackReadPos[trackNum] = (i > 0)?(i-1):0; 1720 if (DEBUG_PUMP) Printer.println(" chaseEvents: setting trackReadPos["+trackNum+"] = "+trackReadPos[trackNum]); 1721 } 1722 break; 1723 } 1724 MidiMessage msg = event.getMessage(); 1725 int status = msg.getStatus(); 1726 int len = msg.getLength(); 1727 if (len == 3 && ((status & 0xF0) == ShortMessage.CONTROL_CHANGE)) { 1728 if (msg instanceof ShortMessage) { 1729 ShortMessage smsg = (ShortMessage) msg; 1730 tempArray[smsg.getData1() & 0x7F][status & 0x0F] = (byte) smsg.getData2(); 1731 } else { 1732 byte[] data = msg.getMessage(); 1733 tempArray[data[1] & 0x7F][status & 0x0F] = data[2]; 1734 } 1735 } 1736 if (len == 2 && ((status & 0xF0) == ShortMessage.PROGRAM_CHANGE)) { 1737 if (msg instanceof ShortMessage) { 1738 ShortMessage smsg = (ShortMessage) msg; 1739 progs[status & 0x0F] = (byte) smsg.getData1(); 1740 } else { 1741 byte[] data = msg.getMessage(); 1742 progs[status & 0x0F] = data[1]; 1743 } 1744 } 1745 } 1746 } catch (ArrayIndexOutOfBoundsException aioobe) { 1747 // this happens when messages are removed 1748 // from the track while this method executes 1749 } 1750 int numControllersSent = 0; 1751 // now send out the aggregated controllers and program changes 1752 for (int ch = 0; ch < 16; ch++) { 1753 for (int co = 0; co < 128; co++) { 1754 byte controllerValue = tempArray[co][ch]; 1755 if (controllerValue >= 0) { 1756 int packedMsg = (ShortMessage.CONTROL_CHANGE | ch) | (co<<8) | (controllerValue<<16); 1757 getTransmitterList().sendMessage(packedMsg, -1); 1758 numControllersSent++; 1759 } 1760 } 1761 // send program change *after* controllers, to 1762 // correctly initialize banks 1763 if (progs[ch] >= 0) { 1764 getTransmitterList().sendMessage((ShortMessage.PROGRAM_CHANGE | ch) | (progs[ch]<<8), -1); 1765 } 1766 if (progs[ch] >= 0 || startTick == 0 || endTick == 0) { 1767 // reset pitch bend on this channel (E0 00 40) 1768 getTransmitterList().sendMessage((ShortMessage.PITCH_BEND | ch) | (0x40 << 16), -1); 1769 // reset sustain pedal on this channel 1770 getTransmitterList().sendMessage((ShortMessage.CONTROL_CHANGE | ch) | (64 << 8), -1); 1771 } 1772 } 1773 if (DEBUG_PUMP) Printer.println(" chaseTrackEvents track "+trackNum+": sent "+numControllersSent+" controllers."); 1774 } 1775 1776 1777 /** chase controllers and program for all tracks */ 1778 synchronized void chaseEvents(long startTick, long endTick) { 1779 if (DEBUG_PUMP) Printer.println(">> chaseEvents from tick "+startTick+".."+(endTick-1)); 1780 byte[][] tempArray = new byte[128][16]; 1781 for (int t = 0; t < tracks.length; t++) { 1782 if ((trackDisabled == null) 1783 || (trackDisabled.length <= t) 1784 || (!trackDisabled[t])) { 1785 // if track is not disabled, chase the events for it 1786 chaseTrackEvents(t, startTick, endTick, true, tempArray); 1787 } 1788 } 1789 if (DEBUG_PUMP) Printer.println("<< chaseEvents"); 1790 } 1791 1792 1793 // playback related methods (pumping) 1794 1795 private long getCurrentTimeMillis() { 1796 return System.nanoTime() / 1000000l; 1797 //return perf.highResCounter() * 1000 / perfFreq; 1798 } 1799 1800 private long millis2tick(long millis) { 1801 if (divisionType != Sequence.PPQ) { 1802 double dTick = ((((double) millis) * tempoFactor) 1803 * ((double) divisionType) 1804 * ((double) resolution)) 1805 / ((double) 1000); 1806 return (long) dTick; 1807 } 1808 return MidiUtils.microsec2ticks(millis * 1000, 1809 currTempo * inverseTempoFactor, 1810 resolution); 1811 } 1812 1813 private long tick2millis(long tick) { 1814 if (divisionType != Sequence.PPQ) { 1815 double dMillis = ((((double) tick) * 1000) / 1816 (tempoFactor * ((double) divisionType) * ((double) resolution))); 1817 return (long) dMillis; 1818 } 1819 return MidiUtils.ticks2microsec(tick, 1820 currTempo * inverseTempoFactor, 1821 resolution) / 1000; 1822 } 1823 1824 private void ReindexTrack(int trackNum, long tick) { 1825 if (trackNum < trackReadPos.length && trackNum < tracks.length) { 1826 trackReadPos[trackNum] = MidiUtils.tick2index(tracks[trackNum], tick); 1827 if (DEBUG_PUMP) Printer.println(" reindexTrack: setting trackReadPos["+trackNum+"] = "+trackReadPos[trackNum]); 1828 } 1829 } 1830 1831 /* returns if changes are pending */ 1832 private boolean dispatchMessage(int trackNum, MidiEvent event) { 1833 boolean changesPending = false; 1834 MidiMessage message = event.getMessage(); 1835 int msgStatus = message.getStatus(); 1836 int msgLen = message.getLength(); 1837 if (msgStatus == MetaMessage.META && msgLen >= 2) { 1838 // a meta message. Do not send it to the device. 1839 // 0xFF with length=1 is a MIDI realtime message 1840 // which shouldn't be in a Sequence, but we play it 1841 // nonetheless. 1842 1843 // see if this is a tempo message. Only on track 0. 1844 if (trackNum == 0) { 1845 int newTempo = MidiUtils.getTempoMPQ(message); 1846 if (newTempo > 0) { 1847 if (event.getTick() != ignoreTempoEventAt) { 1848 setTempoMPQ(newTempo); // sets ignoreTempoEventAt! 1849 changesPending = true; 1850 } 1851 // next loop, do not ignore anymore tempo events. 1852 ignoreTempoEventAt = -1; 1853 } 1854 } 1855 // send to listeners 1856 sendMetaEvents(message); 1857 1858 } else { 1859 // not meta, send to device 1860 getTransmitterList().sendMessage(message, -1); 1861 1862 switch (msgStatus & 0xF0) { 1863 case ShortMessage.NOTE_OFF: { 1864 // note off - clear the bit in the noteOnCache array 1865 int note = ((ShortMessage) message).getData1() & 0x7F; 1866 noteOnCache[note] &= (0xFFFF ^ (1<<(msgStatus & 0x0F))); 1867 break; 1868 } 1869 1870 case ShortMessage.NOTE_ON: { 1871 // note on 1872 ShortMessage smsg = (ShortMessage) message; 1873 int note = smsg.getData1() & 0x7F; 1874 int vel = smsg.getData2() & 0x7F; 1875 if (vel > 0) { 1876 // if velocity > 0 set the bit in the noteOnCache array 1877 noteOnCache[note] |= 1<<(msgStatus & 0x0F); 1878 } else { 1879 // if velocity = 0 clear the bit in the noteOnCache array 1880 noteOnCache[note] &= (0xFFFF ^ (1<<(msgStatus & 0x0F))); 1881 } 1882 break; 1883 } 1884 1885 case ShortMessage.CONTROL_CHANGE: 1886 // if controller message, send controller listeners 1887 sendControllerEvents(message); 1888 break; 1889 1890 } 1891 } 1892 return changesPending; 1893 } 1894 1895 1896 /** the main pump method 1897 * @return true if end of sequence is reached 1898 */ 1899 synchronized boolean pump() { 1900 long currMillis; 1901 long targetTick = lastTick; 1902 MidiEvent currEvent; 1903 boolean changesPending = false; 1904 boolean doLoop = false; 1905 boolean EOM = false; 1906 1907 currMillis = getCurrentTimeMillis(); 1908 int finishedTracks = 0; 1909 do { 1910 changesPending = false; 1911 1912 // need to re-find indexes in tracks? 1913 if (needReindex) { 1914 if (DEBUG_PUMP) Printer.println("Need to re-index at "+currMillis+" millis. TargetTick="+targetTick); 1915 if (trackReadPos.length < tracks.length) { 1916 trackReadPos = new int[tracks.length]; 1917 } 1918 for (int t = 0; t < tracks.length; t++) { 1919 ReindexTrack(t, targetTick); 1920 if (DEBUG_PUMP_ALL) Printer.println(" Setting trackReadPos["+t+"]="+trackReadPos[t]); 1921 } 1922 needReindex = false; 1923 checkPointMillis = 0; 1924 } 1925 1926 // get target tick from current time in millis 1927 if (checkPointMillis == 0) { 1928 // new check point 1929 currMillis = getCurrentTimeMillis(); 1930 checkPointMillis = currMillis; 1931 targetTick = lastTick; 1932 checkPointTick = targetTick; 1933 if (DEBUG_PUMP) Printer.println("New checkpoint to "+currMillis+" millis. " 1934 +"TargetTick="+targetTick 1935 +" new tempo="+MidiUtils.convertTempo(currTempo)+"bpm"); 1936 } else { 1937 // calculate current tick based on current time in milliseconds 1938 targetTick = checkPointTick + millis2tick(currMillis - checkPointMillis); 1939 if (DEBUG_PUMP_ALL) Printer.println("targetTick = "+targetTick+" at "+currMillis+" millis"); 1940 if ((loopEnd != -1) 1941 && ((loopCount > 0 && currLoopCounter > 0) 1942 || (loopCount == LOOP_CONTINUOUSLY))) { 1943 if (lastTick <= loopEnd && targetTick >= loopEnd) { 1944 // need to loop! 1945 // only play until loop end 1946 targetTick = loopEnd - 1; 1947 doLoop = true; 1948 if (DEBUG_PUMP) Printer.println("set doLoop to true. lastTick="+lastTick 1949 +" targetTick="+targetTick 1950 +" loopEnd="+loopEnd 1951 +" jumping to loopStart="+loopStart 1952 +" new currLoopCounter="+currLoopCounter); 1953 if (DEBUG_PUMP) Printer.println(" currMillis="+currMillis 1954 +" checkPointMillis="+checkPointMillis 1955 +" checkPointTick="+checkPointTick); 1956 1957 } 1958 } 1959 lastTick = targetTick; 1960 } 1961 1962 finishedTracks = 0; 1963 1964 for (int t = 0; t < tracks.length; t++) { 1965 try { 1966 boolean disabled = trackDisabled[t]; 1967 Track thisTrack = tracks[t]; 1968 int readPos = trackReadPos[t]; 1969 int size = thisTrack.size(); 1970 // play all events that are due until targetTick 1971 while (!changesPending && (readPos < size) 1972 && (currEvent = thisTrack.get(readPos)).getTick() <= targetTick) { 1973 1974 if ((readPos == size -1) && MidiUtils.isMetaEndOfTrack(currEvent.getMessage())) { 1975 // do not send out this message. Finished with this track 1976 readPos = size; 1977 break; 1978 } 1979 // TODO: some kind of heuristics if the MIDI messages have changed 1980 // significantly (i.e. deleted or inserted a bunch of messages) 1981 // since last time. Would need to set needReindex = true then 1982 readPos++; 1983 // only play this event if the track is enabled, 1984 // or if it is a tempo message on track 0 1985 // Note: cannot put this check outside 1986 // this inner loop in order to detect end of file 1987 if (!disabled || 1988 ((t == 0) && (MidiUtils.isMetaTempo(currEvent.getMessage())))) { 1989 changesPending = dispatchMessage(t, currEvent); 1990 } 1991 } 1992 if (readPos >= size) { 1993 finishedTracks++; 1994 } 1995 if (DEBUG_PUMP_ALL) { 1996 System.out.print(" pumped track "+t+" ("+size+" events) " 1997 +" from index: "+trackReadPos[t] 1998 +" to "+(readPos-1)); 1999 System.out.print(" -> ticks: "); 2000 if (trackReadPos[t] < size) { 2001 System.out.print(""+(thisTrack.get(trackReadPos[t]).getTick())); 2002 } else { 2003 System.out.print("EOT"); 2004 } 2005 System.out.print(" to "); 2006 if (readPos < size) { 2007 System.out.print(""+(thisTrack.get(readPos-1).getTick())); 2008 } else { 2009 System.out.print("EOT"); 2010 } 2011 System.out.println(); 2012 } 2013 trackReadPos[t] = readPos; 2014 } catch(Exception e) { 2015 if (Printer.debug) Printer.debug("Exception in Sequencer pump!"); 2016 if (Printer.debug) e.printStackTrace(); 2017 if (e instanceof ArrayIndexOutOfBoundsException) { 2018 needReindex = true; 2019 changesPending = true; 2020 } 2021 } 2022 if (changesPending) { 2023 break; 2024 } 2025 } 2026 EOM = (finishedTracks == tracks.length); 2027 if (doLoop 2028 || ( ((loopCount > 0 && currLoopCounter > 0) 2029 || (loopCount == LOOP_CONTINUOUSLY)) 2030 && !changesPending 2031 && (loopEnd == -1) 2032 && EOM)) { 2033 2034 long oldCheckPointMillis = checkPointMillis; 2035 long loopEndTick = loopEnd; 2036 if (loopEndTick == -1) { 2037 loopEndTick = lastTick; 2038 } 2039 2040 // need to loop back! 2041 if (loopCount != LOOP_CONTINUOUSLY) { 2042 currLoopCounter--; 2043 } 2044 if (DEBUG_PUMP) Printer.println("Execute loop: lastTick="+lastTick 2045 +" loopEnd="+loopEnd 2046 +" jumping to loopStart="+loopStart 2047 +" new currLoopCounter="+currLoopCounter); 2048 setTickPos(loopStart); 2049 // now patch the checkPointMillis so that 2050 // it points to the exact beginning of when the loop was finished 2051 2052 // $$fb TODO: although this is mathematically correct (i.e. the loop position 2053 // is correct, and doesn't drift away with several repetition, 2054 // there is a slight lag when looping back, probably caused 2055 // by the chasing. 2056 2057 checkPointMillis = oldCheckPointMillis + tick2millis(loopEndTick - checkPointTick); 2058 checkPointTick = loopStart; 2059 if (DEBUG_PUMP) Printer.println(" Setting currMillis="+currMillis 2060 +" new checkPointMillis="+checkPointMillis 2061 +" new checkPointTick="+checkPointTick); 2062 // no need for reindexing, is done in setTickPos 2063 needReindex = false; 2064 changesPending = false; 2065 // reset doLoop flag 2066 doLoop = false; 2067 EOM = false; 2068 } 2069 } while (changesPending); 2070 2071 return EOM; 2072 } 2073 2074 } // class DataPump 2075 2076 }