0N/A/*
6321N/A * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved.
0N/A * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0N/A *
0N/A * This code is free software; you can redistribute it and/or modify it
0N/A * under the terms of the GNU General Public License version 2 only, as
2362N/A * published by the Free Software Foundation. Oracle designates this
0N/A * particular file as subject to the "Classpath" exception as provided
2362N/A * by Oracle in the LICENSE file that accompanied this code.
0N/A *
0N/A * This code is distributed in the hope that it will be useful, but WITHOUT
0N/A * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0N/A * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0N/A * version 2 for more details (a copy is included in the LICENSE file that
0N/A * accompanied this code).
0N/A *
0N/A * You should have received a copy of the GNU General Public License version
0N/A * 2 along with this work; if not, write to the Free Software Foundation,
0N/A * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0N/A *
2362N/A * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
2362N/A * or visit www.oracle.com if you need additional information or have any
2362N/A * questions.
0N/A */
0N/A
0N/Apackage com.sun.media.sound;
0N/A
0N/Aimport java.io.IOException;
0N/Aimport java.io.InputStream;
0N/A
0N/Aimport java.util.ArrayList;
0N/Aimport java.util.List;
6321N/Aimport java.util.Map;
6321N/Aimport java.util.WeakHashMap;
0N/A
0N/Aimport javax.sound.midi.*;
0N/A
0N/A
0N/A/**
0N/A * A Real Time Sequencer
0N/A *
0N/A * @author Florian Bomers
0N/A */
0N/A
0N/A/* TODO:
0N/A * - rename PlayThread to PlayEngine (because isn't a thread)
0N/A */
6321N/Afinal class RealTimeSequencer extends AbstractMidiDevice
6321N/A implements Sequencer, AutoConnectSequencer {
0N/A
0N/A // STATIC VARIABLES
0N/A
0N/A /** debugging flags */
0N/A private final static boolean DEBUG_PUMP = false;
0N/A private final static boolean DEBUG_PUMP_ALL = false;
0N/A
0N/A /**
0N/A * Event Dispatcher thread. Should be using a shared event
0N/A * dispatcher instance with a factory in EventDispatcher
0N/A */
6321N/A private static final Map<ThreadGroup, EventDispatcher> dispatchers =
6321N/A new WeakHashMap<>();
0N/A
0N/A /**
0N/A * All RealTimeSequencers share this info object.
0N/A */
0N/A static final RealTimeSequencerInfo info = new RealTimeSequencerInfo();
0N/A
0N/A
6321N/A private static final Sequencer.SyncMode[] masterSyncModes = { Sequencer.SyncMode.INTERNAL_CLOCK };
6321N/A private static final Sequencer.SyncMode[] slaveSyncModes = { Sequencer.SyncMode.NO_SYNC };
0N/A
6321N/A private static final Sequencer.SyncMode masterSyncMode = Sequencer.SyncMode.INTERNAL_CLOCK;
6321N/A private static final Sequencer.SyncMode slaveSyncMode = Sequencer.SyncMode.NO_SYNC;
0N/A
0N/A
0N/A /**
0N/A * Sequence on which this sequencer is operating.
0N/A */
0N/A private Sequence sequence = null;
0N/A
0N/A // caches
0N/A
0N/A /**
0N/A * Same for setTempoInMPQ...
0N/A * -1 means not set.
0N/A */
0N/A private double cacheTempoMPQ = -1;
0N/A
0N/A
0N/A /**
0N/A * cache value for tempo factor until sequence is set
0N/A * -1 means not set.
0N/A */
0N/A private float cacheTempoFactor = -1;
0N/A
0N/A
0N/A /** if a particular track is muted */
0N/A private boolean[] trackMuted = null;
0N/A /** if a particular track is solo */
0N/A private boolean[] trackSolo = null;
0N/A
0N/A /** tempo cache for getMicrosecondPosition */
6321N/A private final MidiUtils.TempoCache tempoCache = new MidiUtils.TempoCache();
0N/A
0N/A /**
0N/A * True if the sequence is running.
0N/A */
0N/A private boolean running = false;
0N/A
0N/A
0N/A /** the thread for pushing out the MIDI messages */
0N/A private PlayThread playThread;
0N/A
0N/A
0N/A /**
0N/A * True if we are recording
0N/A */
0N/A private boolean recording = false;
0N/A
0N/A
0N/A /**
0N/A * List of tracks to which we're recording
0N/A */
6321N/A private final List recordingTracks = new ArrayList();
0N/A
0N/A
0N/A private long loopStart = 0;
0N/A private long loopEnd = -1;
0N/A private int loopCount = 0;
0N/A
0N/A
0N/A /**
0N/A * Meta event listeners
0N/A */
6321N/A private final ArrayList metaEventListeners = new ArrayList();
0N/A
0N/A
0N/A /**
0N/A * Control change listeners
0N/A */
6321N/A private final ArrayList controllerEventListeners = new ArrayList();
0N/A
0N/A
0N/A /** automatic connection support */
0N/A private boolean autoConnect = false;
0N/A
0N/A /** if we need to autoconnect at next open */
0N/A private boolean doAutoConnectAtNextOpen = false;
0N/A
0N/A /** the receiver that this device is auto-connected to */
0N/A Receiver autoConnectedReceiver = null;
0N/A
0N/A
0N/A /* ****************************** CONSTRUCTOR ****************************** */
0N/A
6321N/A RealTimeSequencer() throws MidiUnavailableException {
0N/A super(info);
0N/A
0N/A if (Printer.trace) Printer.trace(">> RealTimeSequencer CONSTRUCTOR");
0N/A if (Printer.trace) Printer.trace("<< RealTimeSequencer CONSTRUCTOR completed");
0N/A }
0N/A
0N/A
0N/A /* ****************************** SEQUENCER METHODS ******************** */
0N/A
0N/A public synchronized void setSequence(Sequence sequence)
0N/A throws InvalidMidiDataException {
0N/A
0N/A if (Printer.trace) Printer.trace(">> RealTimeSequencer: setSequence(" + sequence +")");
0N/A
0N/A if (sequence != this.sequence) {
0N/A if (this.sequence != null && sequence == null) {
0N/A setCaches();
0N/A stop();
0N/A // initialize some non-cached values
0N/A trackMuted = null;
0N/A trackSolo = null;
0N/A loopStart = 0;
0N/A loopEnd = -1;
0N/A loopCount = 0;
0N/A if (getDataPump() != null) {
0N/A getDataPump().setTickPos(0);
0N/A getDataPump().resetLoopCount();
0N/A }
0N/A }
0N/A
0N/A if (playThread != null) {
0N/A playThread.setSequence(sequence);
0N/A }
0N/A
0N/A // store this sequence (do not copy - we want to give the possibility
0N/A // of modifying the sequence at runtime)
0N/A this.sequence = sequence;
0N/A
0N/A if (sequence != null) {
0N/A tempoCache.refresh(sequence);
0N/A // rewind to the beginning
0N/A setTickPosition(0);
0N/A // propagate caches
0N/A propagateCaches();
0N/A }
0N/A }
0N/A else if (sequence != null) {
0N/A tempoCache.refresh(sequence);
0N/A if (playThread != null) {
0N/A playThread.setSequence(sequence);
0N/A }
0N/A }
0N/A
0N/A if (Printer.trace) Printer.trace("<< RealTimeSequencer: setSequence(" + sequence +") completed");
0N/A }
0N/A
0N/A
0N/A public synchronized void setSequence(InputStream stream) throws IOException, InvalidMidiDataException {
0N/A
0N/A if (Printer.trace) Printer.trace(">> RealTimeSequencer: setSequence(" + stream +")");
0N/A
0N/A if (stream == null) {
0N/A setSequence((Sequence) null);
0N/A return;
0N/A }
0N/A
0N/A Sequence seq = MidiSystem.getSequence(stream); // can throw IOException, InvalidMidiDataException
0N/A
0N/A setSequence(seq);
0N/A
0N/A if (Printer.trace) Printer.trace("<< RealTimeSequencer: setSequence(" + stream +") completed");
0N/A
0N/A }
0N/A
0N/A
0N/A public Sequence getSequence() {
0N/A return sequence;
0N/A }
0N/A
0N/A
0N/A public synchronized void start() {
0N/A if (Printer.trace) Printer.trace(">> RealTimeSequencer: start()");
0N/A
0N/A // sequencer not open: throw an exception
0N/A if (!isOpen()) {
0N/A throw new IllegalStateException("sequencer not open");
0N/A }
0N/A
0N/A // sequence not available: throw an exception
0N/A if (sequence == null) {
0N/A throw new IllegalStateException("sequence not set");
0N/A }
0N/A
0N/A // already running: return quietly
0N/A if (running == true) {
0N/A return;
0N/A }
0N/A
0N/A // start playback
0N/A implStart();
0N/A
0N/A if (Printer.trace) Printer.trace("<< RealTimeSequencer: start() completed");
0N/A }
0N/A
0N/A
0N/A public synchronized void stop() {
0N/A if (Printer.trace) Printer.trace(">> RealTimeSequencer: stop()");
0N/A
0N/A if (!isOpen()) {
0N/A throw new IllegalStateException("sequencer not open");
0N/A }
0N/A stopRecording();
0N/A
0N/A // not running; just return
0N/A if (running == false) {
0N/A if (Printer.trace) Printer.trace("<< RealTimeSequencer: stop() not running!");
0N/A return;
0N/A }
0N/A
0N/A // stop playback
0N/A implStop();
0N/A
0N/A if (Printer.trace) Printer.trace("<< RealTimeSequencer: stop() completed");
0N/A }
0N/A
0N/A
0N/A public boolean isRunning() {
0N/A return running;
0N/A }
0N/A
0N/A
0N/A public void startRecording() {
0N/A if (!isOpen()) {
0N/A throw new IllegalStateException("Sequencer not open");
0N/A }
0N/A
0N/A start();
0N/A recording = true;
0N/A }
0N/A
0N/A
0N/A public void stopRecording() {
0N/A if (!isOpen()) {
0N/A throw new IllegalStateException("Sequencer not open");
0N/A }
0N/A recording = false;
0N/A }
0N/A
0N/A
0N/A public boolean isRecording() {
0N/A return recording;
0N/A }
0N/A
0N/A
0N/A public void recordEnable(Track track, int channel) {
0N/A if (!findTrack(track)) {
0N/A throw new IllegalArgumentException("Track does not exist in the current sequence");
0N/A }
0N/A
0N/A synchronized(recordingTracks) {
0N/A RecordingTrack rc = RecordingTrack.get(recordingTracks, track);
0N/A if (rc != null) {
0N/A rc.channel = channel;
0N/A } else {
0N/A recordingTracks.add(new RecordingTrack(track, channel));
0N/A }
0N/A }
0N/A
0N/A }
0N/A
0N/A
0N/A public void recordDisable(Track track) {
0N/A synchronized(recordingTracks) {
0N/A RecordingTrack rc = RecordingTrack.get(recordingTracks, track);
0N/A if (rc != null) {
0N/A recordingTracks.remove(rc);
0N/A }
0N/A }
0N/A
0N/A }
0N/A
0N/A
0N/A private boolean findTrack(Track track) {
0N/A boolean found = false;
0N/A if (sequence != null) {
0N/A Track[] tracks = sequence.getTracks();
0N/A for (int i = 0; i < tracks.length; i++) {
0N/A if (track == tracks[i]) {
0N/A found = true;
0N/A break;
0N/A }
0N/A }
0N/A }
0N/A return found;
0N/A }
0N/A
0N/A
0N/A public float getTempoInBPM() {
0N/A if (Printer.trace) Printer.trace(">> RealTimeSequencer: getTempoInBPM() ");
0N/A
0N/A return (float) MidiUtils.convertTempo(getTempoInMPQ());
0N/A }
0N/A
0N/A
0N/A public void setTempoInBPM(float bpm) {
0N/A if (Printer.trace) Printer.trace(">> RealTimeSequencer: setTempoInBPM() ");
0N/A if (bpm <= 0) {
0N/A // should throw IllegalArgumentException
0N/A bpm = 1.0f;
0N/A }
0N/A
0N/A setTempoInMPQ((float) MidiUtils.convertTempo((double) bpm));
0N/A }
0N/A
0N/A
0N/A public float getTempoInMPQ() {
0N/A if (Printer.trace) Printer.trace(">> RealTimeSequencer: getTempoInMPQ() ");
0N/A
0N/A if (needCaching()) {
0N/A // if the sequencer is closed, return cached value
0N/A if (cacheTempoMPQ != -1) {
0N/A return (float) cacheTempoMPQ;
0N/A }
0N/A // if sequence is set, return current tempo
0N/A if (sequence != null) {
0N/A return tempoCache.getTempoMPQAt(getTickPosition());
0N/A }
0N/A
0N/A // last resort: return a standard tempo: 120bpm
0N/A return (float) MidiUtils.DEFAULT_TEMPO_MPQ;
0N/A }
0N/A return (float)getDataPump().getTempoMPQ();
0N/A }
0N/A
0N/A
0N/A public void setTempoInMPQ(float mpq) {
0N/A if (mpq <= 0) {
0N/A // should throw IllegalArgumentException
0N/A mpq = 1.0f;
0N/A }
0N/A
0N/A if (Printer.trace) Printer.trace(">> RealTimeSequencer: setTempoInMPQ() ");
0N/A
0N/A if (needCaching()) {
0N/A // cache the value
0N/A cacheTempoMPQ = mpq;
0N/A } else {
0N/A // set the native tempo in MPQ
0N/A getDataPump().setTempoMPQ(mpq);
0N/A
0N/A // reset the tempoInBPM and tempoInMPQ values so we won't use them again
0N/A cacheTempoMPQ = -1;
0N/A }
0N/A }
0N/A
0N/A
0N/A public void setTempoFactor(float factor) {
0N/A if (factor <= 0) {
0N/A // should throw IllegalArgumentException
0N/A return;
0N/A }
0N/A
0N/A if (Printer.trace) Printer.trace(">> RealTimeSequencer: setTempoFactor() ");
0N/A
0N/A if (needCaching()) {
0N/A cacheTempoFactor = factor;
0N/A } else {
0N/A getDataPump().setTempoFactor(factor);
0N/A // don't need cache anymore
0N/A cacheTempoFactor = -1;
0N/A }
0N/A }
0N/A
0N/A
0N/A public float getTempoFactor() {
0N/A if (Printer.trace) Printer.trace(">> RealTimeSequencer: getTempoFactor() ");
0N/A
0N/A if (needCaching()) {
0N/A if (cacheTempoFactor != -1) {
0N/A return cacheTempoFactor;
0N/A }
0N/A return 1.0f;
0N/A }
0N/A return getDataPump().getTempoFactor();
0N/A }
0N/A
0N/A
0N/A public long getTickLength() {
0N/A if (Printer.trace) Printer.trace(">> RealTimeSequencer: getTickLength() ");
0N/A
0N/A if (sequence == null) {
0N/A return 0;
0N/A }
0N/A
0N/A return sequence.getTickLength();
0N/A }
0N/A
0N/A
0N/A public synchronized long getTickPosition() {
0N/A if (Printer.trace) Printer.trace(">> RealTimeSequencer: getTickPosition() ");
0N/A
0N/A if (getDataPump() == null || sequence == null) {
0N/A return 0;
0N/A }
0N/A
0N/A return getDataPump().getTickPos();
0N/A }
0N/A
0N/A
0N/A public synchronized void setTickPosition(long tick) {
0N/A if (tick < 0) {
0N/A // should throw IllegalArgumentException
0N/A return;
0N/A }
0N/A
0N/A if (Printer.trace) Printer.trace(">> RealTimeSequencer: setTickPosition("+tick+") ");
0N/A
0N/A if (getDataPump() == null) {
0N/A if (tick != 0) {
0N/A // throw new InvalidStateException("cannot set position in closed state");
0N/A }
0N/A }
0N/A else if (sequence == null) {
0N/A if (tick != 0) {
0N/A // throw new InvalidStateException("cannot set position if sequence is not set");
0N/A }
0N/A } else {
0N/A getDataPump().setTickPos(tick);
0N/A }
0N/A }
0N/A
0N/A
0N/A public long getMicrosecondLength() {
0N/A if (Printer.trace) Printer.trace(">> RealTimeSequencer: getMicrosecondLength() ");
0N/A
0N/A if (sequence == null) {
0N/A return 0;
0N/A }
0N/A
0N/A return sequence.getMicrosecondLength();
0N/A }
0N/A
0N/A
0N/A public long getMicrosecondPosition() {
0N/A if (Printer.trace) Printer.trace(">> RealTimeSequencer: getMicrosecondPosition() ");
0N/A
0N/A if (getDataPump() == null || sequence == null) {
0N/A return 0;
0N/A }
0N/A synchronized (tempoCache) {
0N/A return MidiUtils.tick2microsecond(sequence, getDataPump().getTickPos(), tempoCache);
0N/A }
0N/A }
0N/A
0N/A
0N/A public void setMicrosecondPosition(long microseconds) {
0N/A if (microseconds < 0) {
0N/A // should throw IllegalArgumentException
0N/A return;
0N/A }
0N/A
0N/A if (Printer.trace) Printer.trace(">> RealTimeSequencer: setMicrosecondPosition("+microseconds+") ");
0N/A
0N/A if (getDataPump() == null) {
0N/A if (microseconds != 0) {
0N/A // throw new InvalidStateException("cannot set position in closed state");
0N/A }
0N/A }
0N/A else if (sequence == null) {
0N/A if (microseconds != 0) {
0N/A // throw new InvalidStateException("cannot set position if sequence is not set");
0N/A }
0N/A } else {
0N/A synchronized(tempoCache) {
0N/A setTickPosition(MidiUtils.microsecond2tick(sequence, microseconds, tempoCache));
0N/A }
0N/A }
0N/A }
0N/A
0N/A
0N/A public void setMasterSyncMode(Sequencer.SyncMode sync) {
0N/A // not supported
0N/A }
0N/A
0N/A
0N/A public Sequencer.SyncMode getMasterSyncMode() {
0N/A return masterSyncMode;
0N/A }
0N/A
0N/A
0N/A public Sequencer.SyncMode[] getMasterSyncModes() {
0N/A Sequencer.SyncMode[] returnedModes = new Sequencer.SyncMode[masterSyncModes.length];
0N/A System.arraycopy(masterSyncModes, 0, returnedModes, 0, masterSyncModes.length);
0N/A return returnedModes;
0N/A }
0N/A
0N/A
0N/A public void setSlaveSyncMode(Sequencer.SyncMode sync) {
0N/A // not supported
0N/A }
0N/A
0N/A
0N/A public Sequencer.SyncMode getSlaveSyncMode() {
0N/A return slaveSyncMode;
0N/A }
0N/A
0N/A
0N/A public Sequencer.SyncMode[] getSlaveSyncModes() {
0N/A Sequencer.SyncMode[] returnedModes = new Sequencer.SyncMode[slaveSyncModes.length];
0N/A System.arraycopy(slaveSyncModes, 0, returnedModes, 0, slaveSyncModes.length);
0N/A return returnedModes;
0N/A }
0N/A
6321N/A int getTrackCount() {
0N/A Sequence seq = getSequence();
0N/A if (seq != null) {
0N/A // $$fb wish there was a nicer way to get the number of tracks...
0N/A return sequence.getTracks().length;
0N/A }
0N/A return 0;
0N/A }
0N/A
0N/A
0N/A
0N/A public synchronized void setTrackMute(int track, boolean mute) {
0N/A int trackCount = getTrackCount();
0N/A if (track < 0 || track >= getTrackCount()) return;
0N/A trackMuted = ensureBoolArraySize(trackMuted, trackCount);
0N/A trackMuted[track] = mute;
0N/A if (getDataPump() != null) {
0N/A getDataPump().muteSoloChanged();
0N/A }
0N/A }
0N/A
0N/A
0N/A public synchronized boolean getTrackMute(int track) {
0N/A if (track < 0 || track >= getTrackCount()) return false;
0N/A if (trackMuted == null || trackMuted.length <= track) return false;
0N/A return trackMuted[track];
0N/A }
0N/A
0N/A
0N/A public synchronized void setTrackSolo(int track, boolean solo) {
0N/A int trackCount = getTrackCount();
0N/A if (track < 0 || track >= getTrackCount()) return;
0N/A trackSolo = ensureBoolArraySize(trackSolo, trackCount);
0N/A trackSolo[track] = solo;
0N/A if (getDataPump() != null) {
0N/A getDataPump().muteSoloChanged();
0N/A }
0N/A }
0N/A
0N/A
0N/A public synchronized boolean getTrackSolo(int track) {
0N/A if (track < 0 || track >= getTrackCount()) return false;
0N/A if (trackSolo == null || trackSolo.length <= track) return false;
0N/A return trackSolo[track];
0N/A }
0N/A
0N/A
0N/A public boolean addMetaEventListener(MetaEventListener listener) {
0N/A synchronized(metaEventListeners) {
0N/A if (! metaEventListeners.contains(listener)) {
0N/A
0N/A metaEventListeners.add(listener);
0N/A }
0N/A return true;
0N/A }
0N/A }
0N/A
0N/A
0N/A public void removeMetaEventListener(MetaEventListener listener) {
0N/A synchronized(metaEventListeners) {
0N/A int index = metaEventListeners.indexOf(listener);
0N/A if (index >= 0) {
0N/A metaEventListeners.remove(index);
0N/A }
0N/A }
0N/A }
0N/A
0N/A
0N/A public int[] addControllerEventListener(ControllerEventListener listener, int[] controllers) {
0N/A synchronized(controllerEventListeners) {
0N/A
0N/A // first find the listener. if we have one, add the controllers
0N/A // if not, create a new element for it.
0N/A ControllerListElement cve = null;
0N/A boolean flag = false;
0N/A for(int i=0; i < controllerEventListeners.size(); i++) {
0N/A
0N/A cve = (ControllerListElement) controllerEventListeners.get(i);
0N/A
0N/A if (cve.listener.equals(listener)) {
0N/A cve.addControllers(controllers);
0N/A flag = true;
0N/A break;
0N/A }
0N/A }
0N/A if (!flag) {
0N/A cve = new ControllerListElement(listener, controllers);
0N/A controllerEventListeners.add(cve);
0N/A }
0N/A
0N/A // and return all the controllers this listener is interested in
0N/A return cve.getControllers();
0N/A }
0N/A }
0N/A
0N/A
0N/A public int[] removeControllerEventListener(ControllerEventListener listener, int[] controllers) {
0N/A synchronized(controllerEventListeners) {
0N/A ControllerListElement cve = null;
0N/A boolean flag = false;
0N/A for (int i=0; i < controllerEventListeners.size(); i++) {
0N/A cve = (ControllerListElement) controllerEventListeners.get(i);
0N/A if (cve.listener.equals(listener)) {
0N/A cve.removeControllers(controllers);
0N/A flag = true;
0N/A break;
0N/A }
0N/A }
0N/A if (!flag) {
0N/A return new int[0];
0N/A }
0N/A if (controllers == null) {
0N/A int index = controllerEventListeners.indexOf(cve);
0N/A if (index >= 0) {
0N/A controllerEventListeners.remove(index);
0N/A }
0N/A return new int[0];
0N/A }
0N/A return cve.getControllers();
0N/A }
0N/A }
0N/A
0N/A
0N/A ////////////////// LOOPING (added in 1.5) ///////////////////////
0N/A
0N/A public void setLoopStartPoint(long tick) {
0N/A if ((tick > getTickLength())
0N/A || ((loopEnd != -1) && (tick > loopEnd))
0N/A || (tick < 0)) {
0N/A throw new IllegalArgumentException("invalid loop start point: "+tick);
0N/A }
0N/A loopStart = tick;
0N/A }
0N/A
0N/A public long getLoopStartPoint() {
0N/A return loopStart;
0N/A }
0N/A
0N/A public void setLoopEndPoint(long tick) {
0N/A if ((tick > getTickLength())
0N/A || ((loopStart > tick) && (tick != -1))
0N/A || (tick < -1)) {
0N/A throw new IllegalArgumentException("invalid loop end point: "+tick);
0N/A }
0N/A loopEnd = tick;
0N/A }
0N/A
0N/A public long getLoopEndPoint() {
0N/A return loopEnd;
0N/A }
0N/A
0N/A public void setLoopCount(int count) {
0N/A if (count != LOOP_CONTINUOUSLY
0N/A && count < 0) {
0N/A throw new IllegalArgumentException("illegal value for loop count: "+count);
0N/A }
0N/A loopCount = count;
0N/A if (getDataPump() != null) {
0N/A getDataPump().resetLoopCount();
0N/A }
0N/A }
0N/A
0N/A public int getLoopCount() {
0N/A return loopCount;
0N/A }
0N/A
0N/A
0N/A /* *********************************** play control ************************* */
0N/A
0N/A /*
0N/A */
0N/A protected void implOpen() throws MidiUnavailableException {
0N/A if (Printer.trace) Printer.trace(">> RealTimeSequencer: implOpen()");
0N/A
0N/A //openInternalSynth();
0N/A
0N/A // create PlayThread
0N/A playThread = new PlayThread();
0N/A
0N/A //id = nOpen();
0N/A //if (id == 0) {
0N/A // throw new MidiUnavailableException("unable to open sequencer");
0N/A //}
0N/A if (sequence != null) {
0N/A playThread.setSequence(sequence);
0N/A }
0N/A
0N/A // propagate caches
0N/A propagateCaches();
0N/A
0N/A if (doAutoConnectAtNextOpen) {
0N/A doAutoConnect();
0N/A }
0N/A if (Printer.trace) Printer.trace("<< RealTimeSequencer: implOpen() succeeded");
0N/A }
0N/A
0N/A private void doAutoConnect() {
0N/A if (Printer.trace) Printer.trace(">> RealTimeSequencer: doAutoConnect()");
0N/A Receiver rec = null;
0N/A // first try to connect to the default synthesizer
0N/A // IMPORTANT: this code needs to be synch'ed with
3631N/A // MidiSystem.getSequencer(boolean), because the same
0N/A // algorithm needs to be used!
0N/A try {
0N/A Synthesizer synth = MidiSystem.getSynthesizer();
0N/A if (synth instanceof ReferenceCountingDevice) {
0N/A rec = ((ReferenceCountingDevice) synth).getReceiverReferenceCounting();
0N/A } else {
3631N/A synth.open();
3631N/A try {
3631N/A rec = synth.getReceiver();
3631N/A } finally {
3631N/A // make sure that the synth is properly closed
3631N/A if (rec == null) {
3631N/A synth.close();
3631N/A }
3631N/A }
0N/A }
0N/A } catch (Exception e) {
0N/A // something went wrong with synth
0N/A }
0N/A if (rec == null) {
0N/A // then try to connect to the default Receiver
0N/A try {
0N/A rec = MidiSystem.getReceiver();
0N/A } catch (Exception e) {
0N/A // something went wrong. Nothing to do then!
0N/A }
0N/A }
0N/A if (rec != null) {
0N/A autoConnectedReceiver = rec;
0N/A try {
0N/A getTransmitter().setReceiver(rec);
0N/A } catch (Exception e) {}
0N/A }
0N/A if (Printer.trace) Printer.trace("<< RealTimeSequencer: doAutoConnect() succeeded");
0N/A }
0N/A
0N/A private synchronized void propagateCaches() {
0N/A // only set caches if open and sequence is set
0N/A if (sequence != null && isOpen()) {
0N/A if (cacheTempoFactor != -1) {
0N/A setTempoFactor(cacheTempoFactor);
0N/A }
0N/A if (cacheTempoMPQ == -1) {
0N/A setTempoInMPQ((new MidiUtils.TempoCache(sequence)).getTempoMPQAt(getTickPosition()));
0N/A } else {
0N/A setTempoInMPQ((float) cacheTempoMPQ);
0N/A }
0N/A }
0N/A }
0N/A
0N/A /** populate the caches with the current values */
0N/A private synchronized void setCaches() {
0N/A cacheTempoFactor = getTempoFactor();
0N/A cacheTempoMPQ = getTempoInMPQ();
0N/A }
0N/A
0N/A
0N/A
0N/A protected synchronized void implClose() {
0N/A if (Printer.trace) Printer.trace(">> RealTimeSequencer: implClose() ");
0N/A
0N/A if (playThread == null) {
0N/A if (Printer.err) Printer.err("RealTimeSequencer.implClose() called, but playThread not instanciated!");
0N/A } else {
0N/A // Interrupt playback loop.
0N/A playThread.close();
0N/A playThread = null;
0N/A }
0N/A
0N/A super.implClose();
0N/A
0N/A sequence = null;
0N/A running = false;
0N/A cacheTempoMPQ = -1;
0N/A cacheTempoFactor = -1;
0N/A trackMuted = null;
0N/A trackSolo = null;
0N/A loopStart = 0;
0N/A loopEnd = -1;
0N/A loopCount = 0;
0N/A
0N/A /** if this sequencer is set to autoconnect, need to
0N/A * re-establish the connection at next open!
0N/A */
0N/A doAutoConnectAtNextOpen = autoConnect;
0N/A
0N/A if (autoConnectedReceiver != null) {
0N/A try {
0N/A autoConnectedReceiver.close();
0N/A } catch (Exception e) {}
0N/A autoConnectedReceiver = null;
0N/A }
0N/A
0N/A if (Printer.trace) Printer.trace("<< RealTimeSequencer: implClose() completed");
0N/A }
0N/A
6321N/A void implStart() {
0N/A if (Printer.trace) Printer.trace(">> RealTimeSequencer: implStart()");
0N/A
0N/A if (playThread == null) {
0N/A if (Printer.err) Printer.err("RealTimeSequencer.implStart() called, but playThread not instanciated!");
0N/A return;
0N/A }
0N/A
0N/A tempoCache.refresh(sequence);
0N/A if (!running) {
0N/A running = true;
0N/A playThread.start();
0N/A }
0N/A if (Printer.trace) Printer.trace("<< RealTimeSequencer: implStart() completed");
0N/A }
0N/A
0N/A
6321N/A void implStop() {
0N/A if (Printer.trace) Printer.trace(">> RealTimeSequencer: implStop()");
0N/A
0N/A if (playThread == null) {
0N/A if (Printer.err) Printer.err("RealTimeSequencer.implStop() called, but playThread not instanciated!");
0N/A return;
0N/A }
0N/A
0N/A recording = false;
0N/A if (running) {
0N/A running = false;
0N/A playThread.stop();
0N/A }
0N/A if (Printer.trace) Printer.trace("<< RealTimeSequencer: implStop() completed");
0N/A }
0N/A
6321N/A private static EventDispatcher getEventDispatcher() {
6321N/A // create and start the global event thread
6321N/A //TODO need a way to stop this thread when the engine is done
6321N/A final ThreadGroup tg = Thread.currentThread().getThreadGroup();
6321N/A synchronized (dispatchers) {
6321N/A EventDispatcher eventDispatcher = dispatchers.get(tg);
6321N/A if (eventDispatcher == null) {
6321N/A eventDispatcher = new EventDispatcher();
6321N/A dispatchers.put(tg, eventDispatcher);
6321N/A eventDispatcher.start();
6321N/A }
6321N/A return eventDispatcher;
6321N/A }
6321N/A }
0N/A
0N/A /**
0N/A * Send midi player events.
0N/A * must not be synchronized on "this"
0N/A */
6321N/A void sendMetaEvents(MidiMessage message) {
0N/A if (metaEventListeners.size() == 0) return;
0N/A
0N/A //if (Printer.debug) Printer.debug("sending a meta event");
6321N/A getEventDispatcher().sendAudioEvents(message, metaEventListeners);
0N/A }
0N/A
0N/A /**
0N/A * Send midi player events.
0N/A */
6321N/A void sendControllerEvents(MidiMessage message) {
0N/A int size = controllerEventListeners.size();
0N/A if (size == 0) return;
0N/A
0N/A //if (Printer.debug) Printer.debug("sending a controller event");
0N/A
0N/A if (! (message instanceof ShortMessage)) {
0N/A if (Printer.debug) Printer.debug("sendControllerEvents: message is NOT instanceof ShortMessage!");
0N/A return;
0N/A }
0N/A ShortMessage msg = (ShortMessage) message;
0N/A int controller = msg.getData1();
0N/A List sendToListeners = new ArrayList();
0N/A for (int i = 0; i < size; i++) {
0N/A ControllerListElement cve = (ControllerListElement) controllerEventListeners.get(i);
0N/A for(int j = 0; j < cve.controllers.length; j++) {
0N/A if (cve.controllers[j] == controller) {
0N/A sendToListeners.add(cve.listener);
0N/A break;
0N/A }
0N/A }
0N/A }
6321N/A getEventDispatcher().sendAudioEvents(message, sendToListeners);
0N/A }
0N/A
0N/A
0N/A
0N/A private boolean needCaching() {
0N/A return !isOpen() || (sequence == null) || (playThread == null);
0N/A }
0N/A
0N/A /**
0N/A * return the data pump instance, owned by play thread
0N/A * if playthread is null, return null.
0N/A * This method is guaranteed to return non-null if
0N/A * needCaching returns false
0N/A */
0N/A private DataPump getDataPump() {
0N/A if (playThread != null) {
0N/A return playThread.getDataPump();
0N/A }
0N/A return null;
0N/A }
0N/A
0N/A private MidiUtils.TempoCache getTempoCache() {
0N/A return tempoCache;
0N/A }
0N/A
0N/A private static boolean[] ensureBoolArraySize(boolean[] array, int desiredSize) {
0N/A if (array == null) {
0N/A return new boolean[desiredSize];
0N/A }
0N/A if (array.length < desiredSize) {
0N/A boolean[] newArray = new boolean[desiredSize];
0N/A System.arraycopy(array, 0, newArray, 0, array.length);
0N/A return newArray;
0N/A }
0N/A return array;
0N/A }
0N/A
0N/A
0N/A // OVERRIDES OF ABSTRACT MIDI DEVICE METHODS
0N/A
0N/A protected boolean hasReceivers() {
0N/A return true;
0N/A }
0N/A
0N/A // for recording
0N/A protected Receiver createReceiver() throws MidiUnavailableException {
0N/A return new SequencerReceiver();
0N/A }
0N/A
0N/A
0N/A protected boolean hasTransmitters() {
0N/A return true;
0N/A }
0N/A
0N/A
0N/A protected Transmitter createTransmitter() throws MidiUnavailableException {
0N/A return new SequencerTransmitter();
0N/A }
0N/A
0N/A
0N/A // interface AutoConnectSequencer
0N/A public void setAutoConnect(Receiver autoConnectedReceiver) {
0N/A this.autoConnect = (autoConnectedReceiver != null);
0N/A this.autoConnectedReceiver = autoConnectedReceiver;
0N/A }
0N/A
0N/A
0N/A
0N/A // INNER CLASSES
0N/A
0N/A /**
0N/A * An own class to distinguish the class name from
0N/A * the transmitter of other devices
0N/A */
0N/A private class SequencerTransmitter extends BasicTransmitter {
0N/A private SequencerTransmitter() {
0N/A super();
0N/A }
0N/A }
0N/A
0N/A
6321N/A final class SequencerReceiver extends AbstractReceiver {
0N/A
5893N/A void implSend(MidiMessage message, long timeStamp) {
0N/A if (recording) {
0N/A long tickPos = 0;
0N/A
0N/A // convert timeStamp to ticks
0N/A if (timeStamp < 0) {
0N/A tickPos = getTickPosition();
0N/A } else {
0N/A synchronized(tempoCache) {
0N/A tickPos = MidiUtils.microsecond2tick(sequence, timeStamp, tempoCache);
0N/A }
0N/A }
0N/A
0N/A // and record to the first matching Track
0N/A Track track = null;
0N/A // do not record real-time events
0N/A // see 5048381: NullPointerException when saving a MIDI sequence
0N/A if (message.getLength() > 1) {
0N/A if (message instanceof ShortMessage) {
0N/A ShortMessage sm = (ShortMessage) message;
0N/A // all real-time messages have 0xF in the high nibble of the status byte
0N/A if ((sm.getStatus() & 0xF0) != 0xF0) {
0N/A track = RecordingTrack.get(recordingTracks, sm.getChannel());
0N/A }
0N/A } else {
0N/A // $$jb: where to record meta, sysex events?
0N/A // $$fb: the first recording track
0N/A track = RecordingTrack.get(recordingTracks, -1);
0N/A }
0N/A if (track != null) {
0N/A // create a copy of this message
0N/A if (message instanceof ShortMessage) {
0N/A message = new FastShortMessage((ShortMessage) message);
0N/A } else {
0N/A message = (MidiMessage) message.clone();
0N/A }
0N/A
0N/A // create new MidiEvent
0N/A MidiEvent me = new MidiEvent(message, tickPos);
0N/A track.add(me);
0N/A }
0N/A }
0N/A }
0N/A }
0N/A }
0N/A
0N/A
0N/A private static class RealTimeSequencerInfo extends MidiDevice.Info {
0N/A
0N/A private static final String name = "Real Time Sequencer";
2866N/A private static final String vendor = "Oracle Corporation";
0N/A private static final String description = "Software sequencer";
0N/A private static final String version = "Version 1.0";
0N/A
0N/A private RealTimeSequencerInfo() {
0N/A super(name, vendor, description, version);
0N/A }
0N/A } // class Info
0N/A
0N/A
0N/A private class ControllerListElement {
0N/A
0N/A // $$jb: using an array for controllers b/c its
0N/A // easier to deal with than turning all the
0N/A // ints into objects to use a Vector
0N/A int [] controllers;
6321N/A final ControllerEventListener listener;
0N/A
0N/A private ControllerListElement(ControllerEventListener listener, int[] controllers) {
0N/A
0N/A this.listener = listener;
0N/A if (controllers == null) {
0N/A controllers = new int[128];
0N/A for (int i = 0; i < 128; i++) {
0N/A controllers[i] = i;
0N/A }
0N/A }
0N/A this.controllers = controllers;
0N/A }
0N/A
0N/A private void addControllers(int[] c) {
0N/A
0N/A if (c==null) {
0N/A controllers = new int[128];
0N/A for (int i = 0; i < 128; i++) {
0N/A controllers[i] = i;
0N/A }
0N/A return;
0N/A }
0N/A int temp[] = new int[ controllers.length + c.length ];
0N/A int elements;
0N/A
0N/A // first add what we have
0N/A for(int i=0; i<controllers.length; i++) {
0N/A temp[i] = controllers[i];
0N/A }
0N/A elements = controllers.length;
0N/A // now add the new controllers only if we don't already have them
0N/A for(int i=0; i<c.length; i++) {
0N/A boolean flag = false;
0N/A
0N/A for(int j=0; j<controllers.length; j++) {
0N/A if (c[i] == controllers[j]) {
0N/A flag = true;
0N/A break;
0N/A }
0N/A }
0N/A if (!flag) {
0N/A temp[elements++] = c[i];
0N/A }
0N/A }
0N/A // now keep only the elements we need
0N/A int newc[] = new int[ elements ];
0N/A for(int i=0; i<elements; i++){
0N/A newc[i] = temp[i];
0N/A }
0N/A controllers = newc;
0N/A }
0N/A
0N/A private void removeControllers(int[] c) {
0N/A
0N/A if (c==null) {
0N/A controllers = new int[0];
0N/A } else {
0N/A int temp[] = new int[ controllers.length ];
0N/A int elements = 0;
0N/A
0N/A
0N/A for(int i=0; i<controllers.length; i++){
0N/A boolean flag = false;
0N/A for(int j=0; j<c.length; j++) {
0N/A if (controllers[i] == c[j]) {
0N/A flag = true;
0N/A break;
0N/A }
0N/A }
0N/A if (!flag){
0N/A temp[elements++] = controllers[i];
0N/A }
0N/A }
0N/A // now keep only the elements remaining
0N/A int newc[] = new int[ elements ];
0N/A for(int i=0; i<elements; i++) {
0N/A newc[i] = temp[i];
0N/A }
0N/A controllers = newc;
0N/A
0N/A }
0N/A }
0N/A
0N/A private int[] getControllers() {
0N/A
0N/A // return a copy of our array of controllers,
0N/A // so others can't mess with it
0N/A if (controllers == null) {
0N/A return null;
0N/A }
0N/A
0N/A int c[] = new int[controllers.length];
0N/A
0N/A for(int i=0; i<controllers.length; i++){
0N/A c[i] = controllers[i];
0N/A }
0N/A return c;
0N/A }
0N/A
0N/A } // class ControllerListElement
0N/A
0N/A
0N/A static class RecordingTrack {
0N/A
6321N/A private final Track track;
0N/A private int channel;
0N/A
0N/A RecordingTrack(Track track, int channel) {
0N/A this.track = track;
0N/A this.channel = channel;
0N/A }
0N/A
0N/A static RecordingTrack get(List recordingTracks, Track track) {
0N/A
0N/A synchronized(recordingTracks) {
0N/A int size = recordingTracks.size();
0N/A
0N/A for (int i = 0; i < size; i++) {
0N/A RecordingTrack current = (RecordingTrack)recordingTracks.get(i);
0N/A if (current.track == track) {
0N/A return current;
0N/A }
0N/A }
0N/A }
0N/A return null;
0N/A }
0N/A
0N/A static Track get(List recordingTracks, int channel) {
0N/A
0N/A synchronized(recordingTracks) {
0N/A int size = recordingTracks.size();
0N/A for (int i = 0; i < size; i++) {
0N/A RecordingTrack current = (RecordingTrack)recordingTracks.get(i);
0N/A if ((current.channel == channel) || (current.channel == -1)) {
0N/A return current.track;
0N/A }
0N/A }
0N/A }
0N/A return null;
0N/A
0N/A }
0N/A }
0N/A
0N/A
6321N/A final class PlayThread implements Runnable {
0N/A private Thread thread;
6321N/A private final Object lock = new Object();
0N/A
0N/A /** true if playback is interrupted (in close) */
0N/A boolean interrupted = false;
0N/A boolean isPumping = false;
0N/A
6321N/A private final DataPump dataPump = new DataPump();
0N/A
0N/A
0N/A PlayThread() {
0N/A // nearly MAX_PRIORITY
0N/A int priority = Thread.NORM_PRIORITY
0N/A + ((Thread.MAX_PRIORITY - Thread.NORM_PRIORITY) * 3) / 4;
0N/A thread = JSSecurityManager.createThread(this,
0N/A "Java Sound Sequencer", // name
0N/A false, // daemon
0N/A priority, // priority
0N/A true); // doStart
0N/A }
0N/A
0N/A DataPump getDataPump() {
0N/A return dataPump;
0N/A }
0N/A
0N/A synchronized void setSequence(Sequence seq) {
0N/A dataPump.setSequence(seq);
0N/A }
0N/A
0N/A
0N/A /** start thread and pump. Requires up-to-date tempoCache */
0N/A synchronized void start() {
0N/A // mark the sequencer running
0N/A running = true;
0N/A
0N/A if (!dataPump.hasCachedTempo()) {
0N/A long tickPos = getTickPosition();
0N/A dataPump.setTempoMPQ(tempoCache.getTempoMPQAt(tickPos));
0N/A }
0N/A dataPump.checkPointMillis = 0; // means restarted
0N/A dataPump.clearNoteOnCache();
0N/A dataPump.needReindex = true;
0N/A
0N/A dataPump.resetLoopCount();
0N/A
0N/A // notify the thread
0N/A synchronized(lock) {
0N/A lock.notifyAll();
0N/A }
0N/A
0N/A if (Printer.debug) Printer.debug(" ->Started MIDI play thread");
0N/A
0N/A }
0N/A
0N/A // waits until stopped
0N/A synchronized void stop() {
0N/A playThreadImplStop();
0N/A long t = System.nanoTime() / 1000000l;
0N/A while (isPumping) {
0N/A synchronized(lock) {
0N/A try {
0N/A lock.wait(2000);
0N/A } catch (InterruptedException ie) {
0N/A // ignore
0N/A }
0N/A }
0N/A // don't wait for more than 2 seconds
0N/A if ((System.nanoTime()/1000000l) - t > 1900) {
0N/A if (Printer.err) Printer.err("Waited more than 2 seconds in RealTimeSequencer.PlayThread.stop()!");
0N/A //break;
0N/A }
0N/A }
0N/A }
0N/A
0N/A void playThreadImplStop() {
0N/A // mark the sequencer running
0N/A running = false;
0N/A synchronized(lock) {
0N/A lock.notifyAll();
0N/A }
0N/A }
0N/A
0N/A void close() {
0N/A Thread oldThread = null;
0N/A synchronized (this) {
0N/A // dispose of thread
0N/A interrupted = true;
0N/A oldThread = thread;
0N/A thread = null;
0N/A }
0N/A if (oldThread != null) {
0N/A // wake up the thread if it's in wait()
0N/A synchronized(lock) {
0N/A lock.notifyAll();
0N/A }
0N/A }
0N/A // wait for the thread to terminate itself,
0N/A // but max. 2 seconds. Must not be synchronized!
0N/A if (oldThread != null) {
0N/A try {
0N/A oldThread.join(2000);
0N/A } catch (InterruptedException ie) {}
0N/A }
0N/A }
0N/A
0N/A
0N/A /**
0N/A * Main process loop driving the media flow.
0N/A *
0N/A * Make sure to NOT synchronize on RealTimeSequencer
0N/A * anywhere here (even implicit). That is a sure deadlock!
0N/A */
0N/A public void run() {
0N/A
0N/A while (!interrupted) {
0N/A boolean EOM = false;
0N/A boolean wasRunning = running;
0N/A isPumping = !interrupted && running;
0N/A while (!EOM && !interrupted && running) {
0N/A EOM = dataPump.pump();
0N/A
0N/A try {
0N/A Thread.sleep(1);
0N/A } catch (InterruptedException ie) {
0N/A // ignore
0N/A }
0N/A }
0N/A if (Printer.debug) {
0N/A Printer.debug("Exited main pump loop because: ");
0N/A if (EOM) Printer.debug(" -> EOM is reached");
0N/A if (!running) Printer.debug(" -> running was set to false");
0N/A if (interrupted) Printer.debug(" -> interrupted was set to true");
0N/A }
0N/A
0N/A playThreadImplStop();
0N/A if (wasRunning) {
0N/A dataPump.notesOff(true);
0N/A }
0N/A if (EOM) {
0N/A dataPump.setTickPos(sequence.getTickLength());
0N/A
0N/A // send EOT event (mis-used for end of media)
0N/A MetaMessage message = new MetaMessage();
0N/A try{
0N/A message.setMessage(MidiUtils.META_END_OF_TRACK_TYPE, new byte[0], 0);
0N/A } catch(InvalidMidiDataException e1) {}
0N/A sendMetaEvents(message);
0N/A }
0N/A synchronized (lock) {
0N/A isPumping = false;
0N/A // wake up a waiting stop() method
0N/A lock.notifyAll();
0N/A while (!running && !interrupted) {
0N/A try {
0N/A lock.wait();
0N/A } catch (Exception ex) {}
0N/A }
0N/A }
0N/A } // end of while(!EOM && !interrupted && running)
0N/A if (Printer.debug) Printer.debug("end of play thread");
0N/A }
0N/A }
0N/A
0N/A
0N/A /**
0N/A * class that does the actual dispatching of events,
0N/A * used to be in native in MMAPI
0N/A */
0N/A private class DataPump {
0N/A private float currTempo; // MPQ tempo
0N/A private float tempoFactor; // 1.0 is default
0N/A private float inverseTempoFactor;// = 1.0 / tempoFactor
0N/A private long ignoreTempoEventAt; // ignore next META tempo during playback at this tick pos only
0N/A private int resolution;
0N/A private float divisionType;
0N/A private long checkPointMillis; // microseconds at checkoint
0N/A private long checkPointTick; // ticks at checkpoint
0N/A private int[] noteOnCache; // bit-mask of notes that are currently on
0N/A private Track[] tracks;
0N/A private boolean[] trackDisabled; // if true, do not play this track
0N/A private int[] trackReadPos; // read index per track
0N/A private long lastTick;
0N/A private boolean needReindex = false;
0N/A private int currLoopCounter = 0;
0N/A
0N/A //private sun.misc.Perf perf = sun.misc.Perf.getPerf();
0N/A //private long perfFreq = perf.highResFrequency();
0N/A
0N/A
0N/A DataPump() {
0N/A init();
0N/A }
0N/A
0N/A synchronized void init() {
0N/A ignoreTempoEventAt = -1;
0N/A tempoFactor = 1.0f;
0N/A inverseTempoFactor = 1.0f;
0N/A noteOnCache = new int[128];
0N/A tracks = null;
0N/A trackDisabled = null;
0N/A }
0N/A
0N/A synchronized void setTickPos(long tickPos) {
0N/A long oldLastTick = tickPos;
0N/A lastTick = tickPos;
0N/A if (running) {
0N/A notesOff(false);
0N/A }
0N/A if (running || tickPos > 0) {
0N/A // will also reindex
0N/A chaseEvents(oldLastTick, tickPos);
0N/A } else {
0N/A needReindex = true;
0N/A }
0N/A if (!hasCachedTempo()) {
0N/A setTempoMPQ(getTempoCache().getTempoMPQAt(lastTick, currTempo));
0N/A // treat this as if it is a real time tempo change
0N/A ignoreTempoEventAt = -1;
0N/A }
0N/A // trigger re-configuration
0N/A checkPointMillis = 0;
0N/A }
0N/A
0N/A long getTickPos() {
0N/A return lastTick;
0N/A }
0N/A
0N/A // hasCachedTempo is only valid if it is the current position
0N/A boolean hasCachedTempo() {
0N/A if (ignoreTempoEventAt != lastTick) {
0N/A ignoreTempoEventAt = -1;
0N/A }
0N/A return ignoreTempoEventAt >= 0;
0N/A }
0N/A
0N/A // this method is also used internally in the pump!
0N/A synchronized void setTempoMPQ(float tempoMPQ) {
0N/A if (tempoMPQ > 0 && tempoMPQ != currTempo) {
0N/A ignoreTempoEventAt = lastTick;
0N/A this.currTempo = tempoMPQ;
0N/A // re-calculate check point
0N/A checkPointMillis = 0;
0N/A }
0N/A }
0N/A
0N/A float getTempoMPQ() {
0N/A return currTempo;
0N/A }
0N/A
0N/A synchronized void setTempoFactor(float factor) {
0N/A if (factor > 0 && factor != this.tempoFactor) {
0N/A tempoFactor = factor;
0N/A inverseTempoFactor = 1.0f / factor;
0N/A // re-calculate check point
0N/A checkPointMillis = 0;
0N/A }
0N/A }
0N/A
0N/A float getTempoFactor() {
0N/A return tempoFactor;
0N/A }
0N/A
0N/A synchronized void muteSoloChanged() {
0N/A boolean[] newDisabled = makeDisabledArray();
0N/A if (running) {
0N/A applyDisabledTracks(trackDisabled, newDisabled);
0N/A }
0N/A trackDisabled = newDisabled;
0N/A }
0N/A
0N/A
0N/A
0N/A synchronized void setSequence(Sequence seq) {
0N/A if (seq == null) {
0N/A init();
0N/A return;
0N/A }
0N/A tracks = seq.getTracks();
0N/A muteSoloChanged();
0N/A resolution = seq.getResolution();
0N/A divisionType = seq.getDivisionType();
0N/A trackReadPos = new int[tracks.length];
0N/A // trigger re-initialization
0N/A checkPointMillis = 0;
0N/A needReindex = true;
0N/A }
0N/A
0N/A synchronized void resetLoopCount() {
0N/A currLoopCounter = loopCount;
0N/A }
0N/A
0N/A void clearNoteOnCache() {
0N/A for (int i = 0; i < 128; i++) {
0N/A noteOnCache[i] = 0;
0N/A }
0N/A }
0N/A
0N/A void notesOff(boolean doControllers) {
0N/A int done = 0;
0N/A for (int ch=0; ch<16; ch++) {
0N/A int channelMask = (1<<ch);
0N/A for (int i=0; i<128; i++) {
0N/A if ((noteOnCache[i] & channelMask) != 0) {
0N/A noteOnCache[i] ^= channelMask;
0N/A // send note on with velocity 0
0N/A getTransmitterList().sendMessage((ShortMessage.NOTE_ON | ch) | (i<<8), -1);
0N/A done++;
0N/A }
0N/A }
0N/A /* all notes off */
0N/A getTransmitterList().sendMessage((ShortMessage.CONTROL_CHANGE | ch) | (123<<8), -1);
0N/A /* sustain off */
0N/A getTransmitterList().sendMessage((ShortMessage.CONTROL_CHANGE | ch) | (64<<8), -1);
0N/A if (doControllers) {
0N/A /* reset all controllers */
0N/A getTransmitterList().sendMessage((ShortMessage.CONTROL_CHANGE | ch) | (121<<8), -1);
0N/A done++;
0N/A }
0N/A }
0N/A if (DEBUG_PUMP) Printer.println(" noteOff: sent "+done+" messages.");
0N/A }
0N/A
0N/A
0N/A private boolean[] makeDisabledArray() {
0N/A if (tracks == null) {
0N/A return null;
0N/A }
0N/A boolean[] newTrackDisabled = new boolean[tracks.length];
0N/A boolean[] solo;
0N/A boolean[] mute;
0N/A synchronized(RealTimeSequencer.this) {
0N/A mute = trackMuted;
0N/A solo = trackSolo;
0N/A }
0N/A // if one track is solo, then only play solo
0N/A boolean hasSolo = false;
0N/A if (solo != null) {
0N/A for (int i = 0; i < solo.length; i++) {
0N/A if (solo[i]) {
0N/A hasSolo = true;
0N/A break;
0N/A }
0N/A }
0N/A }
0N/A if (hasSolo) {
0N/A // only the channels with solo play, regardless of mute
0N/A for (int i = 0; i < newTrackDisabled.length; i++) {
0N/A newTrackDisabled[i] = (i >= solo.length) || (!solo[i]);
0N/A }
0N/A } else {
0N/A // mute the selected channels
0N/A for (int i = 0; i < newTrackDisabled.length; i++) {
0N/A newTrackDisabled[i] = (mute != null) && (i < mute.length) && (mute[i]);
0N/A }
0N/A }
0N/A return newTrackDisabled;
0N/A }
0N/A
0N/A /**
0N/A * chase all events from beginning of Track
0N/A * and send note off for those events that are active
0N/A * in noteOnCache array.
0N/A * It is possible, of course, to catch notes from other tracks,
0N/A * but better than more complicated logic to detect
0N/A * which notes are really from this track
0N/A */
0N/A private void sendNoteOffIfOn(Track track, long endTick) {
0N/A int size = track.size();
0N/A int done = 0;
0N/A try {
0N/A for (int i = 0; i < size; i++) {
0N/A MidiEvent event = track.get(i);
0N/A if (event.getTick() > endTick) break;
0N/A MidiMessage msg = event.getMessage();
0N/A int status = msg.getStatus();
0N/A int len = msg.getLength();
0N/A if (len == 3 && ((status & 0xF0) == ShortMessage.NOTE_ON)) {
0N/A int note = -1;
0N/A if (msg instanceof ShortMessage) {
0N/A ShortMessage smsg = (ShortMessage) msg;
0N/A if (smsg.getData2() > 0) {
0N/A // only consider Note On with velocity > 0
0N/A note = smsg.getData1();
0N/A }
0N/A } else {
0N/A byte[] data = msg.getMessage();
0N/A if ((data[2] & 0x7F) > 0) {
0N/A // only consider Note On with velocity > 0
0N/A note = data[1] & 0x7F;
0N/A }
0N/A }
0N/A if (note >= 0) {
0N/A int bit = 1<<(status & 0x0F);
0N/A if ((noteOnCache[note] & bit) != 0) {
0N/A // the bit is set. Send Note Off
0N/A getTransmitterList().sendMessage(status | (note<<8), -1);
0N/A // clear the bit
0N/A noteOnCache[note] &= (0xFFFF ^ bit);
0N/A done++;
0N/A }
0N/A }
0N/A }
0N/A }
0N/A } catch (ArrayIndexOutOfBoundsException aioobe) {
0N/A // this happens when messages are removed
0N/A // from the track while this method executes
0N/A }
0N/A if (DEBUG_PUMP) Printer.println(" sendNoteOffIfOn: sent "+done+" messages.");
0N/A }
0N/A
0N/A
0N/A /**
0N/A * Runtime application of mute/solo:
0N/A * if a track is muted that was previously playing, send
0N/A * note off events for all currently playing notes
0N/A */
0N/A private void applyDisabledTracks(boolean[] oldDisabled, boolean[] newDisabled) {
0N/A byte[][] tempArray = null;
0N/A synchronized(RealTimeSequencer.this) {
0N/A for (int i = 0; i < newDisabled.length; i++) {
0N/A if (((oldDisabled == null)
0N/A || (i >= oldDisabled.length)
0N/A || !oldDisabled[i])
0N/A && newDisabled[i]) {
0N/A // case that a track gets muted: need to
0N/A // send appropriate note off events to prevent
0N/A // hanging notes
0N/A
0N/A if (tracks.length > i) {
0N/A sendNoteOffIfOn(tracks[i], lastTick);
0N/A }
0N/A }
0N/A else if ((oldDisabled != null)
0N/A && (i < oldDisabled.length)
0N/A && oldDisabled[i]
0N/A && !newDisabled[i]) {
0N/A // case that a track was muted and is now unmuted
0N/A // need to chase events and re-index this track
0N/A if (tempArray == null) {
0N/A tempArray = new byte[128][16];
0N/A }
0N/A chaseTrackEvents(i, 0, lastTick, true, tempArray);
0N/A }
0N/A }
0N/A }
0N/A }
0N/A
0N/A /** go through all events from startTick to endTick
0N/A * chase the controller state and program change state
0N/A * and then set the end-states at once.
0N/A *
0N/A * needs to be called in synchronized state
0N/A * @param tempArray an byte[128][16] to hold controller messages
0N/A */
0N/A private void chaseTrackEvents(int trackNum,
0N/A long startTick,
0N/A long endTick,
0N/A boolean doReindex,
0N/A byte[][] tempArray) {
0N/A if (startTick > endTick) {
0N/A // start from the beginning
0N/A startTick = 0;
0N/A }
0N/A byte[] progs = new byte[16];
0N/A // init temp array with impossible values
0N/A for (int ch = 0; ch < 16; ch++) {
0N/A progs[ch] = -1;
0N/A for (int co = 0; co < 128; co++) {
0N/A tempArray[co][ch] = -1;
0N/A }
0N/A }
0N/A Track track = tracks[trackNum];
0N/A int size = track.size();
0N/A try {
0N/A for (int i = 0; i < size; i++) {
0N/A MidiEvent event = track.get(i);
0N/A if (event.getTick() >= endTick) {
0N/A if (doReindex && (trackNum < trackReadPos.length)) {
0N/A trackReadPos[trackNum] = (i > 0)?(i-1):0;
0N/A if (DEBUG_PUMP) Printer.println(" chaseEvents: setting trackReadPos["+trackNum+"] = "+trackReadPos[trackNum]);
0N/A }
0N/A break;
0N/A }
0N/A MidiMessage msg = event.getMessage();
0N/A int status = msg.getStatus();
0N/A int len = msg.getLength();
0N/A if (len == 3 && ((status & 0xF0) == ShortMessage.CONTROL_CHANGE)) {
0N/A if (msg instanceof ShortMessage) {
0N/A ShortMessage smsg = (ShortMessage) msg;
0N/A tempArray[smsg.getData1() & 0x7F][status & 0x0F] = (byte) smsg.getData2();
0N/A } else {
0N/A byte[] data = msg.getMessage();
0N/A tempArray[data[1] & 0x7F][status & 0x0F] = data[2];
0N/A }
0N/A }
0N/A if (len == 2 && ((status & 0xF0) == ShortMessage.PROGRAM_CHANGE)) {
0N/A if (msg instanceof ShortMessage) {
0N/A ShortMessage smsg = (ShortMessage) msg;
0N/A progs[status & 0x0F] = (byte) smsg.getData1();
0N/A } else {
0N/A byte[] data = msg.getMessage();
0N/A progs[status & 0x0F] = data[1];
0N/A }
0N/A }
0N/A }
0N/A } catch (ArrayIndexOutOfBoundsException aioobe) {
0N/A // this happens when messages are removed
0N/A // from the track while this method executes
0N/A }
0N/A int numControllersSent = 0;
0N/A // now send out the aggregated controllers and program changes
0N/A for (int ch = 0; ch < 16; ch++) {
0N/A for (int co = 0; co < 128; co++) {
0N/A byte controllerValue = tempArray[co][ch];
0N/A if (controllerValue >= 0) {
0N/A int packedMsg = (ShortMessage.CONTROL_CHANGE | ch) | (co<<8) | (controllerValue<<16);
0N/A getTransmitterList().sendMessage(packedMsg, -1);
0N/A numControllersSent++;
0N/A }
0N/A }
0N/A // send program change *after* controllers, to
0N/A // correctly initialize banks
0N/A if (progs[ch] >= 0) {
0N/A getTransmitterList().sendMessage((ShortMessage.PROGRAM_CHANGE | ch) | (progs[ch]<<8), -1);
0N/A }
0N/A if (progs[ch] >= 0 || startTick == 0 || endTick == 0) {
0N/A // reset pitch bend on this channel (E0 00 40)
0N/A getTransmitterList().sendMessage((ShortMessage.PITCH_BEND | ch) | (0x40 << 16), -1);
0N/A // reset sustain pedal on this channel
0N/A getTransmitterList().sendMessage((ShortMessage.CONTROL_CHANGE | ch) | (64 << 8), -1);
0N/A }
0N/A }
0N/A if (DEBUG_PUMP) Printer.println(" chaseTrackEvents track "+trackNum+": sent "+numControllersSent+" controllers.");
0N/A }
0N/A
0N/A
0N/A /** chase controllers and program for all tracks */
0N/A synchronized void chaseEvents(long startTick, long endTick) {
0N/A if (DEBUG_PUMP) Printer.println(">> chaseEvents from tick "+startTick+".."+(endTick-1));
0N/A byte[][] tempArray = new byte[128][16];
0N/A for (int t = 0; t < tracks.length; t++) {
0N/A if ((trackDisabled == null)
0N/A || (trackDisabled.length <= t)
0N/A || (!trackDisabled[t])) {
0N/A // if track is not disabled, chase the events for it
0N/A chaseTrackEvents(t, startTick, endTick, true, tempArray);
0N/A }
0N/A }
0N/A if (DEBUG_PUMP) Printer.println("<< chaseEvents");
0N/A }
0N/A
0N/A
0N/A // playback related methods (pumping)
0N/A
0N/A private long getCurrentTimeMillis() {
0N/A return System.nanoTime() / 1000000l;
0N/A //return perf.highResCounter() * 1000 / perfFreq;
0N/A }
0N/A
0N/A private long millis2tick(long millis) {
0N/A if (divisionType != Sequence.PPQ) {
0N/A double dTick = ((((double) millis) * tempoFactor)
0N/A * ((double) divisionType)
0N/A * ((double) resolution))
0N/A / ((double) 1000);
0N/A return (long) dTick;
0N/A }
0N/A return MidiUtils.microsec2ticks(millis * 1000,
0N/A currTempo * inverseTempoFactor,
0N/A resolution);
0N/A }
0N/A
0N/A private long tick2millis(long tick) {
0N/A if (divisionType != Sequence.PPQ) {
0N/A double dMillis = ((((double) tick) * 1000) /
0N/A (tempoFactor * ((double) divisionType) * ((double) resolution)));
0N/A return (long) dMillis;
0N/A }
0N/A return MidiUtils.ticks2microsec(tick,
0N/A currTempo * inverseTempoFactor,
0N/A resolution) / 1000;
0N/A }
0N/A
0N/A private void ReindexTrack(int trackNum, long tick) {
0N/A if (trackNum < trackReadPos.length && trackNum < tracks.length) {
0N/A trackReadPos[trackNum] = MidiUtils.tick2index(tracks[trackNum], tick);
0N/A if (DEBUG_PUMP) Printer.println(" reindexTrack: setting trackReadPos["+trackNum+"] = "+trackReadPos[trackNum]);
0N/A }
0N/A }
0N/A
0N/A /* returns if changes are pending */
0N/A private boolean dispatchMessage(int trackNum, MidiEvent event) {
0N/A boolean changesPending = false;
0N/A MidiMessage message = event.getMessage();
0N/A int msgStatus = message.getStatus();
0N/A int msgLen = message.getLength();
0N/A if (msgStatus == MetaMessage.META && msgLen >= 2) {
0N/A // a meta message. Do not send it to the device.
0N/A // 0xFF with length=1 is a MIDI realtime message
0N/A // which shouldn't be in a Sequence, but we play it
0N/A // nonetheless.
0N/A
0N/A // see if this is a tempo message. Only on track 0.
0N/A if (trackNum == 0) {
0N/A int newTempo = MidiUtils.getTempoMPQ(message);
0N/A if (newTempo > 0) {
0N/A if (event.getTick() != ignoreTempoEventAt) {
0N/A setTempoMPQ(newTempo); // sets ignoreTempoEventAt!
0N/A changesPending = true;
0N/A }
0N/A // next loop, do not ignore anymore tempo events.
0N/A ignoreTempoEventAt = -1;
0N/A }
0N/A }
0N/A // send to listeners
0N/A sendMetaEvents(message);
0N/A
0N/A } else {
0N/A // not meta, send to device
0N/A getTransmitterList().sendMessage(message, -1);
0N/A
0N/A switch (msgStatus & 0xF0) {
0N/A case ShortMessage.NOTE_OFF: {
0N/A // note off - clear the bit in the noteOnCache array
0N/A int note = ((ShortMessage) message).getData1() & 0x7F;
0N/A noteOnCache[note] &= (0xFFFF ^ (1<<(msgStatus & 0x0F)));
0N/A break;
0N/A }
0N/A
0N/A case ShortMessage.NOTE_ON: {
0N/A // note on
0N/A ShortMessage smsg = (ShortMessage) message;
0N/A int note = smsg.getData1() & 0x7F;
0N/A int vel = smsg.getData2() & 0x7F;
0N/A if (vel > 0) {
0N/A // if velocity > 0 set the bit in the noteOnCache array
0N/A noteOnCache[note] |= 1<<(msgStatus & 0x0F);
0N/A } else {
0N/A // if velocity = 0 clear the bit in the noteOnCache array
0N/A noteOnCache[note] &= (0xFFFF ^ (1<<(msgStatus & 0x0F)));
0N/A }
0N/A break;
0N/A }
0N/A
0N/A case ShortMessage.CONTROL_CHANGE:
0N/A // if controller message, send controller listeners
0N/A sendControllerEvents(message);
0N/A break;
0N/A
0N/A }
0N/A }
0N/A return changesPending;
0N/A }
0N/A
0N/A
0N/A /** the main pump method
0N/A * @return true if end of sequence is reached
0N/A */
0N/A synchronized boolean pump() {
0N/A long currMillis;
0N/A long targetTick = lastTick;
0N/A MidiEvent currEvent;
0N/A boolean changesPending = false;
0N/A boolean doLoop = false;
0N/A boolean EOM = false;
0N/A
0N/A currMillis = getCurrentTimeMillis();
0N/A int finishedTracks = 0;
0N/A do {
0N/A changesPending = false;
0N/A
0N/A // need to re-find indexes in tracks?
0N/A if (needReindex) {
0N/A if (DEBUG_PUMP) Printer.println("Need to re-index at "+currMillis+" millis. TargetTick="+targetTick);
0N/A if (trackReadPos.length < tracks.length) {
0N/A trackReadPos = new int[tracks.length];
0N/A }
0N/A for (int t = 0; t < tracks.length; t++) {
0N/A ReindexTrack(t, targetTick);
0N/A if (DEBUG_PUMP_ALL) Printer.println(" Setting trackReadPos["+t+"]="+trackReadPos[t]);
0N/A }
0N/A needReindex = false;
0N/A checkPointMillis = 0;
0N/A }
0N/A
0N/A // get target tick from current time in millis
0N/A if (checkPointMillis == 0) {
0N/A // new check point
0N/A currMillis = getCurrentTimeMillis();
0N/A checkPointMillis = currMillis;
0N/A targetTick = lastTick;
0N/A checkPointTick = targetTick;
0N/A if (DEBUG_PUMP) Printer.println("New checkpoint to "+currMillis+" millis. "
0N/A +"TargetTick="+targetTick
0N/A +" new tempo="+MidiUtils.convertTempo(currTempo)+"bpm");
0N/A } else {
0N/A // calculate current tick based on current time in milliseconds
0N/A targetTick = checkPointTick + millis2tick(currMillis - checkPointMillis);
0N/A if (DEBUG_PUMP_ALL) Printer.println("targetTick = "+targetTick+" at "+currMillis+" millis");
0N/A if ((loopEnd != -1)
0N/A && ((loopCount > 0 && currLoopCounter > 0)
0N/A || (loopCount == LOOP_CONTINUOUSLY))) {
0N/A if (lastTick <= loopEnd && targetTick >= loopEnd) {
0N/A // need to loop!
0N/A // only play until loop end
0N/A targetTick = loopEnd - 1;
0N/A doLoop = true;
0N/A if (DEBUG_PUMP) Printer.println("set doLoop to true. lastTick="+lastTick
0N/A +" targetTick="+targetTick
0N/A +" loopEnd="+loopEnd
0N/A +" jumping to loopStart="+loopStart
0N/A +" new currLoopCounter="+currLoopCounter);
0N/A if (DEBUG_PUMP) Printer.println(" currMillis="+currMillis
0N/A +" checkPointMillis="+checkPointMillis
0N/A +" checkPointTick="+checkPointTick);
0N/A
0N/A }
0N/A }
0N/A lastTick = targetTick;
0N/A }
0N/A
0N/A finishedTracks = 0;
0N/A
0N/A for (int t = 0; t < tracks.length; t++) {
0N/A try {
0N/A boolean disabled = trackDisabled[t];
0N/A Track thisTrack = tracks[t];
0N/A int readPos = trackReadPos[t];
0N/A int size = thisTrack.size();
0N/A // play all events that are due until targetTick
0N/A while (!changesPending && (readPos < size)
0N/A && (currEvent = thisTrack.get(readPos)).getTick() <= targetTick) {
0N/A
0N/A if ((readPos == size -1) && MidiUtils.isMetaEndOfTrack(currEvent.getMessage())) {
0N/A // do not send out this message. Finished with this track
0N/A readPos = size;
0N/A break;
0N/A }
0N/A // TODO: some kind of heuristics if the MIDI messages have changed
0N/A // significantly (i.e. deleted or inserted a bunch of messages)
0N/A // since last time. Would need to set needReindex = true then
0N/A readPos++;
0N/A // only play this event if the track is enabled,
0N/A // or if it is a tempo message on track 0
0N/A // Note: cannot put this check outside
0N/A // this inner loop in order to detect end of file
0N/A if (!disabled ||
0N/A ((t == 0) && (MidiUtils.isMetaTempo(currEvent.getMessage())))) {
0N/A changesPending = dispatchMessage(t, currEvent);
0N/A }
0N/A }
0N/A if (readPos >= size) {
0N/A finishedTracks++;
0N/A }
0N/A if (DEBUG_PUMP_ALL) {
0N/A System.out.print(" pumped track "+t+" ("+size+" events) "
0N/A +" from index: "+trackReadPos[t]
0N/A +" to "+(readPos-1));
0N/A System.out.print(" -> ticks: ");
0N/A if (trackReadPos[t] < size) {
0N/A System.out.print(""+(thisTrack.get(trackReadPos[t]).getTick()));
0N/A } else {
0N/A System.out.print("EOT");
0N/A }
0N/A System.out.print(" to ");
0N/A if (readPos < size) {
0N/A System.out.print(""+(thisTrack.get(readPos-1).getTick()));
0N/A } else {
0N/A System.out.print("EOT");
0N/A }
0N/A System.out.println();
0N/A }
0N/A trackReadPos[t] = readPos;
0N/A } catch(Exception e) {
0N/A if (Printer.debug) Printer.debug("Exception in Sequencer pump!");
0N/A if (Printer.debug) e.printStackTrace();
0N/A if (e instanceof ArrayIndexOutOfBoundsException) {
0N/A needReindex = true;
0N/A changesPending = true;
0N/A }
0N/A }
0N/A if (changesPending) {
0N/A break;
0N/A }
0N/A }
0N/A EOM = (finishedTracks == tracks.length);
0N/A if (doLoop
0N/A || ( ((loopCount > 0 && currLoopCounter > 0)
0N/A || (loopCount == LOOP_CONTINUOUSLY))
0N/A && !changesPending
0N/A && (loopEnd == -1)
0N/A && EOM)) {
0N/A
0N/A long oldCheckPointMillis = checkPointMillis;
0N/A long loopEndTick = loopEnd;
0N/A if (loopEndTick == -1) {
0N/A loopEndTick = lastTick;
0N/A }
0N/A
0N/A // need to loop back!
0N/A if (loopCount != LOOP_CONTINUOUSLY) {
0N/A currLoopCounter--;
0N/A }
0N/A if (DEBUG_PUMP) Printer.println("Execute loop: lastTick="+lastTick
0N/A +" loopEnd="+loopEnd
0N/A +" jumping to loopStart="+loopStart
0N/A +" new currLoopCounter="+currLoopCounter);
0N/A setTickPos(loopStart);
0N/A // now patch the checkPointMillis so that
0N/A // it points to the exact beginning of when the loop was finished
0N/A
0N/A // $$fb TODO: although this is mathematically correct (i.e. the loop position
0N/A // is correct, and doesn't drift away with several repetition,
0N/A // there is a slight lag when looping back, probably caused
0N/A // by the chasing.
0N/A
0N/A checkPointMillis = oldCheckPointMillis + tick2millis(loopEndTick - checkPointTick);
0N/A checkPointTick = loopStart;
0N/A if (DEBUG_PUMP) Printer.println(" Setting currMillis="+currMillis
0N/A +" new checkPointMillis="+checkPointMillis
0N/A +" new checkPointTick="+checkPointTick);
0N/A // no need for reindexing, is done in setTickPos
0N/A needReindex = false;
0N/A changesPending = false;
0N/A // reset doLoop flag
0N/A doLoop = false;
0N/A EOM = false;
0N/A }
0N/A } while (changesPending);
0N/A
0N/A return EOM;
0N/A }
0N/A
0N/A } // class DataPump
0N/A
0N/A}