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