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