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