alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

Java example source code file (RealTimeSequencer.java)

This example Java source code file (RealTimeSequencer.java) is included in the alvinalexander.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Learn more about this Java project at its project page.

Java - Java tags/keywords

arraylist, audio, controllerlistelement, datapump, debug_pump, eom, eventdispatcher, exception, illegalstateexception, midi, midievent, midimessage, realtimesequencer, recordingtrack, shortmessage, sound, track, util

The RealTimeSequencer.java Java example source code

/*
 * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package com.sun.media.sound;

import java.io.IOException;
import java.io.InputStream;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;

import javax.sound.midi.*;


/**
 * A Real Time Sequencer
 *
 * @author Florian Bomers
 */

/* TODO:
 * - rename PlayThread to PlayEngine (because isn't a thread)
 */
final class RealTimeSequencer extends AbstractMidiDevice
        implements Sequencer, AutoConnectSequencer {

    // STATIC VARIABLES

    /** debugging flags */
    private final static boolean DEBUG_PUMP = false;
    private final static boolean DEBUG_PUMP_ALL = false;

    /**
     * Event Dispatcher thread. Should be using a shared event
     * dispatcher instance with a factory in EventDispatcher
     */
    private static final Map<ThreadGroup, EventDispatcher> dispatchers =
            new WeakHashMap<>();

    /**
     * All RealTimeSequencers share this info object.
     */
    static final RealTimeSequencerInfo info = new RealTimeSequencerInfo();


    private static final Sequencer.SyncMode[] masterSyncModes = { Sequencer.SyncMode.INTERNAL_CLOCK };
    private static final Sequencer.SyncMode[] slaveSyncModes  = { Sequencer.SyncMode.NO_SYNC };

    private static final Sequencer.SyncMode masterSyncMode    = Sequencer.SyncMode.INTERNAL_CLOCK;
    private static final Sequencer.SyncMode slaveSyncMode     = Sequencer.SyncMode.NO_SYNC;


    /**
     * Sequence on which this sequencer is operating.
     */
    private Sequence sequence = null;

    // caches

    /**
     * Same for setTempoInMPQ...
     * -1 means not set.
     */
    private double cacheTempoMPQ = -1;


    /**
     * cache value for tempo factor until sequence is set
     * -1 means not set.
     */
    private float cacheTempoFactor = -1;


    /** if a particular track is muted */
    private boolean[] trackMuted = null;
    /** if a particular track is solo */
    private boolean[] trackSolo = null;

    /** tempo cache for getMicrosecondPosition */
    private final MidiUtils.TempoCache tempoCache = new MidiUtils.TempoCache();

    /**
     * True if the sequence is running.
     */
    private boolean running = false;


    /** the thread for pushing out the MIDI messages */
    private PlayThread playThread;


    /**
     * True if we are recording
     */
    private boolean recording = false;


    /**
     * List of tracks to which we're recording
     */
    private final List recordingTracks = new ArrayList();


    private long loopStart = 0;
    private long loopEnd = -1;
    private int loopCount = 0;


    /**
     * Meta event listeners
     */
    private final ArrayList metaEventListeners = new ArrayList();


    /**
     * Control change listeners
     */
    private final ArrayList controllerEventListeners = new ArrayList();


    /** automatic connection support */
    private boolean autoConnect = false;

    /** if we need to autoconnect at next open */
    private boolean doAutoConnectAtNextOpen = false;

    /** the receiver that this device is auto-connected to */
    Receiver autoConnectedReceiver = null;


    /* ****************************** CONSTRUCTOR ****************************** */

    RealTimeSequencer() throws MidiUnavailableException {
        super(info);

        if (Printer.trace) Printer.trace(">> RealTimeSequencer CONSTRUCTOR");
        if (Printer.trace) Printer.trace("<< RealTimeSequencer CONSTRUCTOR completed");
    }


    /* ****************************** SEQUENCER METHODS ******************** */

    public synchronized void setSequence(Sequence sequence)
        throws InvalidMidiDataException {

        if (Printer.trace) Printer.trace(">> RealTimeSequencer: setSequence(" + sequence +")");

        if (sequence != this.sequence) {
            if (this.sequence != null && sequence == null) {
                setCaches();
                stop();
                // initialize some non-cached values
                trackMuted = null;
                trackSolo = null;
                loopStart = 0;
                loopEnd = -1;
                loopCount = 0;
                if (getDataPump() != null) {
                    getDataPump().setTickPos(0);
                    getDataPump().resetLoopCount();
                }
            }

            if (playThread != null) {
                playThread.setSequence(sequence);
            }

            // store this sequence (do not copy - we want to give the possibility
            // of modifying the sequence at runtime)
            this.sequence = sequence;

            if (sequence != null) {
                tempoCache.refresh(sequence);
                // rewind to the beginning
                setTickPosition(0);
                // propagate caches
                propagateCaches();
            }
        }
        else if (sequence != null) {
            tempoCache.refresh(sequence);
            if (playThread != null) {
                playThread.setSequence(sequence);
            }
        }

        if (Printer.trace) Printer.trace("<< RealTimeSequencer: setSequence(" + sequence +") completed");
    }


    public synchronized void setSequence(InputStream stream) throws IOException, InvalidMidiDataException {

        if (Printer.trace) Printer.trace(">> RealTimeSequencer: setSequence(" + stream +")");

        if (stream == null) {
            setSequence((Sequence) null);
            return;
        }

        Sequence seq = MidiSystem.getSequence(stream); // can throw IOException, InvalidMidiDataException

        setSequence(seq);

        if (Printer.trace) Printer.trace("<< RealTimeSequencer: setSequence(" + stream +") completed");

    }


    public Sequence getSequence() {
        return sequence;
    }


    public synchronized void start() {
        if (Printer.trace) Printer.trace(">> RealTimeSequencer: start()");

        // sequencer not open: throw an exception
        if (!isOpen()) {
            throw new IllegalStateException("sequencer not open");
        }

        // sequence not available: throw an exception
        if (sequence == null) {
            throw new IllegalStateException("sequence not set");
        }

        // already running: return quietly
        if (running == true) {
            return;
        }

        // start playback
        implStart();

        if (Printer.trace) Printer.trace("<< RealTimeSequencer: start() completed");
    }


    public synchronized void stop() {
        if (Printer.trace) Printer.trace(">> RealTimeSequencer: stop()");

        if (!isOpen()) {
            throw new IllegalStateException("sequencer not open");
        }
        stopRecording();

        // not running; just return
        if (running == false) {
            if (Printer.trace) Printer.trace("<< RealTimeSequencer: stop() not running!");
            return;
        }

        // stop playback
        implStop();

        if (Printer.trace) Printer.trace("<< RealTimeSequencer: stop() completed");
    }


    public boolean isRunning() {
        return running;
    }


    public void startRecording() {
        if (!isOpen()) {
            throw new IllegalStateException("Sequencer not open");
        }

        start();
        recording = true;
    }


    public void stopRecording() {
        if (!isOpen()) {
            throw new IllegalStateException("Sequencer not open");
        }
        recording = false;
    }


    public boolean isRecording() {
        return recording;
    }


    public void recordEnable(Track track, int channel) {
        if (!findTrack(track)) {
            throw new IllegalArgumentException("Track does not exist in the current sequence");
        }

        synchronized(recordingTracks) {
            RecordingTrack rc = RecordingTrack.get(recordingTracks, track);
            if (rc != null) {
                rc.channel = channel;
            } else {
                recordingTracks.add(new RecordingTrack(track, channel));
            }
        }

    }


    public void recordDisable(Track track) {
        synchronized(recordingTracks) {
            RecordingTrack rc = RecordingTrack.get(recordingTracks, track);
            if (rc != null) {
                recordingTracks.remove(rc);
            }
        }

    }


    private boolean findTrack(Track track) {
        boolean found = false;
        if (sequence != null) {
            Track[] tracks = sequence.getTracks();
            for (int i = 0; i < tracks.length; i++) {
                if (track == tracks[i]) {
                    found = true;
                    break;
                }
            }
        }
        return found;
    }


    public float getTempoInBPM() {
        if (Printer.trace) Printer.trace(">> RealTimeSequencer: getTempoInBPM() ");

        return (float) MidiUtils.convertTempo(getTempoInMPQ());
    }


    public void setTempoInBPM(float bpm) {
        if (Printer.trace) Printer.trace(">> RealTimeSequencer: setTempoInBPM() ");
        if (bpm <= 0) {
            // should throw IllegalArgumentException
            bpm = 1.0f;
        }

        setTempoInMPQ((float) MidiUtils.convertTempo((double) bpm));
    }


    public float getTempoInMPQ() {
        if (Printer.trace) Printer.trace(">> RealTimeSequencer: getTempoInMPQ() ");

        if (needCaching()) {
            // if the sequencer is closed, return cached value
            if (cacheTempoMPQ != -1) {
                return (float) cacheTempoMPQ;
            }
            // if sequence is set, return current tempo
            if (sequence != null) {
                return tempoCache.getTempoMPQAt(getTickPosition());
            }

            // last resort: return a standard tempo: 120bpm
            return (float) MidiUtils.DEFAULT_TEMPO_MPQ;
        }
        return (float)getDataPump().getTempoMPQ();
    }


    public void setTempoInMPQ(float mpq) {
        if (mpq <= 0) {
            // should throw IllegalArgumentException
            mpq = 1.0f;
        }

        if (Printer.trace) Printer.trace(">> RealTimeSequencer: setTempoInMPQ() ");

        if (needCaching()) {
            // cache the value
            cacheTempoMPQ = mpq;
        } else {
            // set the native tempo in MPQ
            getDataPump().setTempoMPQ(mpq);

            // reset the tempoInBPM and tempoInMPQ values so we won't use them again
            cacheTempoMPQ = -1;
        }
    }


    public void setTempoFactor(float factor) {
        if (factor <= 0) {
            // should throw IllegalArgumentException
            return;
        }

        if (Printer.trace) Printer.trace(">> RealTimeSequencer: setTempoFactor() ");

        if (needCaching()) {
            cacheTempoFactor = factor;
        } else {
            getDataPump().setTempoFactor(factor);
            // don't need cache anymore
            cacheTempoFactor = -1;
        }
    }


    public float getTempoFactor() {
        if (Printer.trace) Printer.trace(">> RealTimeSequencer: getTempoFactor() ");

        if (needCaching()) {
            if (cacheTempoFactor != -1) {
                return cacheTempoFactor;
            }
            return 1.0f;
        }
        return getDataPump().getTempoFactor();
    }


    public long getTickLength() {
        if (Printer.trace) Printer.trace(">> RealTimeSequencer: getTickLength() ");

        if (sequence == null) {
            return 0;
        }

        return sequence.getTickLength();
    }


    public synchronized long getTickPosition() {
        if (Printer.trace) Printer.trace(">> RealTimeSequencer: getTickPosition() ");

        if (getDataPump() == null || sequence == null) {
            return 0;
        }

        return getDataPump().getTickPos();
    }


    public synchronized void setTickPosition(long tick) {
        if (tick < 0) {
            // should throw IllegalArgumentException
            return;
        }

        if (Printer.trace) Printer.trace(">> RealTimeSequencer: setTickPosition("+tick+") ");

        if (getDataPump() == null) {
            if (tick != 0) {
                // throw new InvalidStateException("cannot set position in closed state");
            }
        }
        else if (sequence == null) {
            if (tick != 0) {
                // throw new InvalidStateException("cannot set position if sequence is not set");
            }
        } else {
            getDataPump().setTickPos(tick);
        }
    }


    public long getMicrosecondLength() {
        if (Printer.trace) Printer.trace(">> RealTimeSequencer: getMicrosecondLength() ");

        if (sequence == null) {
            return 0;
        }

        return sequence.getMicrosecondLength();
    }


    public long getMicrosecondPosition() {
        if (Printer.trace) Printer.trace(">> RealTimeSequencer: getMicrosecondPosition() ");

        if (getDataPump() == null || sequence == null) {
            return 0;
        }
        synchronized (tempoCache) {
            return MidiUtils.tick2microsecond(sequence, getDataPump().getTickPos(), tempoCache);
        }
    }


    public void setMicrosecondPosition(long microseconds) {
        if (microseconds < 0) {
            // should throw IllegalArgumentException
            return;
        }

        if (Printer.trace) Printer.trace(">> RealTimeSequencer: setMicrosecondPosition("+microseconds+") ");

        if (getDataPump() == null) {
            if (microseconds != 0) {
                // throw new InvalidStateException("cannot set position in closed state");
            }
        }
        else if (sequence == null) {
            if (microseconds != 0) {
                // throw new InvalidStateException("cannot set position if sequence is not set");
            }
        } else {
            synchronized(tempoCache) {
                setTickPosition(MidiUtils.microsecond2tick(sequence, microseconds, tempoCache));
            }
        }
    }


    public void setMasterSyncMode(Sequencer.SyncMode sync) {
        // not supported
    }


    public Sequencer.SyncMode getMasterSyncMode() {
        return masterSyncMode;
    }


    public Sequencer.SyncMode[] getMasterSyncModes() {
        Sequencer.SyncMode[] returnedModes = new Sequencer.SyncMode[masterSyncModes.length];
        System.arraycopy(masterSyncModes, 0, returnedModes, 0, masterSyncModes.length);
        return returnedModes;
    }


    public void setSlaveSyncMode(Sequencer.SyncMode sync) {
        // not supported
    }


    public Sequencer.SyncMode getSlaveSyncMode() {
        return slaveSyncMode;
    }


    public Sequencer.SyncMode[] getSlaveSyncModes() {
        Sequencer.SyncMode[] returnedModes = new Sequencer.SyncMode[slaveSyncModes.length];
        System.arraycopy(slaveSyncModes, 0, returnedModes, 0, slaveSyncModes.length);
        return returnedModes;
    }

    int getTrackCount() {
        Sequence seq = getSequence();
        if (seq != null) {
            // $$fb wish there was a nicer way to get the number of tracks...
            return sequence.getTracks().length;
        }
        return 0;
    }



    public synchronized void setTrackMute(int track, boolean mute) {
        int trackCount = getTrackCount();
        if (track < 0 || track >= getTrackCount()) return;
        trackMuted = ensureBoolArraySize(trackMuted, trackCount);
        trackMuted[track] = mute;
        if (getDataPump() != null) {
            getDataPump().muteSoloChanged();
        }
    }


    public synchronized boolean getTrackMute(int track) {
        if (track < 0 || track >= getTrackCount()) return false;
        if (trackMuted == null || trackMuted.length <= track) return false;
        return trackMuted[track];
    }


    public synchronized void setTrackSolo(int track, boolean solo) {
        int trackCount = getTrackCount();
        if (track < 0 || track >= getTrackCount()) return;
        trackSolo = ensureBoolArraySize(trackSolo, trackCount);
        trackSolo[track] = solo;
        if (getDataPump() != null) {
            getDataPump().muteSoloChanged();
        }
    }


    public synchronized boolean getTrackSolo(int track) {
        if (track < 0 || track >= getTrackCount()) return false;
        if (trackSolo == null || trackSolo.length <= track) return false;
        return trackSolo[track];
    }


    public boolean addMetaEventListener(MetaEventListener listener) {
        synchronized(metaEventListeners) {
            if (! metaEventListeners.contains(listener)) {

                metaEventListeners.add(listener);
            }
            return true;
        }
    }


    public void removeMetaEventListener(MetaEventListener listener) {
        synchronized(metaEventListeners) {
            int index = metaEventListeners.indexOf(listener);
            if (index >= 0) {
                metaEventListeners.remove(index);
            }
        }
    }


    public int[] addControllerEventListener(ControllerEventListener listener, int[] controllers) {
        synchronized(controllerEventListeners) {

            // first find the listener.  if we have one, add the controllers
            // if not, create a new element for it.
            ControllerListElement cve = null;
            boolean flag = false;
            for(int i=0; i < controllerEventListeners.size(); i++) {

                cve = (ControllerListElement) controllerEventListeners.get(i);

                if (cve.listener.equals(listener)) {
                    cve.addControllers(controllers);
                    flag = true;
                    break;
                }
            }
            if (!flag) {
                cve = new ControllerListElement(listener, controllers);
                controllerEventListeners.add(cve);
            }

            // and return all the controllers this listener is interested in
            return cve.getControllers();
        }
    }


    public int[] removeControllerEventListener(ControllerEventListener listener, int[] controllers) {
        synchronized(controllerEventListeners) {
            ControllerListElement cve = null;
            boolean flag = false;
            for (int i=0; i < controllerEventListeners.size(); i++) {
                cve = (ControllerListElement) controllerEventListeners.get(i);
                if (cve.listener.equals(listener)) {
                    cve.removeControllers(controllers);
                    flag = true;
                    break;
                }
            }
            if (!flag) {
                return new int[0];
            }
            if (controllers == null) {
                int index = controllerEventListeners.indexOf(cve);
                if (index >= 0) {
                    controllerEventListeners.remove(index);
                }
                return new int[0];
            }
            return cve.getControllers();
        }
    }


    ////////////////// LOOPING (added in 1.5) ///////////////////////

    public void setLoopStartPoint(long tick) {
        if ((tick > getTickLength())
            || ((loopEnd != -1) && (tick > loopEnd))
            || (tick < 0)) {
            throw new IllegalArgumentException("invalid loop start point: "+tick);
        }
        loopStart = tick;
    }

    public long getLoopStartPoint() {
        return loopStart;
    }

    public void setLoopEndPoint(long tick) {
        if ((tick > getTickLength())
            || ((loopStart > tick) && (tick != -1))
            || (tick < -1)) {
            throw new IllegalArgumentException("invalid loop end point: "+tick);
        }
        loopEnd = tick;
    }

    public long getLoopEndPoint() {
        return loopEnd;
    }

    public void setLoopCount(int count) {
        if (count != LOOP_CONTINUOUSLY
            && count < 0) {
            throw new IllegalArgumentException("illegal value for loop count: "+count);
        }
        loopCount = count;
        if (getDataPump() != null) {
            getDataPump().resetLoopCount();
        }
    }

    public int getLoopCount() {
        return loopCount;
    }


    /* *********************************** play control ************************* */

    /*
     */
    protected void implOpen() throws MidiUnavailableException {
        if (Printer.trace) Printer.trace(">> RealTimeSequencer: implOpen()");

        //openInternalSynth();

        // create PlayThread
        playThread = new PlayThread();

        //id = nOpen();
        //if (id == 0) {
        //    throw new MidiUnavailableException("unable to open sequencer");
        //}
        if (sequence != null) {
            playThread.setSequence(sequence);
        }

        // propagate caches
        propagateCaches();

        if (doAutoConnectAtNextOpen) {
            doAutoConnect();
        }
        if (Printer.trace) Printer.trace("<< RealTimeSequencer: implOpen() succeeded");
    }

    private void doAutoConnect() {
        if (Printer.trace) Printer.trace(">> RealTimeSequencer: doAutoConnect()");
        Receiver rec = null;
        // first try to connect to the default synthesizer
        // IMPORTANT: this code needs to be synch'ed with
        //            MidiSystem.getSequencer(boolean), because the same
        //            algorithm needs to be used!
        try {
            Synthesizer synth = MidiSystem.getSynthesizer();
            if (synth instanceof ReferenceCountingDevice) {
                rec = ((ReferenceCountingDevice) synth).getReceiverReferenceCounting();
            } else {
                synth.open();
                try {
                    rec = synth.getReceiver();
                } finally {
                    // make sure that the synth is properly closed
                    if (rec == null) {
                        synth.close();
                    }
                }
            }
        } catch (Exception e) {
            // something went wrong with synth
        }
        if (rec == null) {
            // then try to connect to the default Receiver
            try {
                rec = MidiSystem.getReceiver();
            } catch (Exception e) {
                // something went wrong. Nothing to do then!
            }
        }
        if (rec != null) {
            autoConnectedReceiver = rec;
            try {
                getTransmitter().setReceiver(rec);
            } catch (Exception e) {}
        }
        if (Printer.trace) Printer.trace("<< RealTimeSequencer: doAutoConnect() succeeded");
    }

    private synchronized void propagateCaches() {
        // only set caches if open and sequence is set
        if (sequence != null && isOpen()) {
            if (cacheTempoFactor != -1) {
                setTempoFactor(cacheTempoFactor);
            }
            if (cacheTempoMPQ == -1) {
                setTempoInMPQ((new MidiUtils.TempoCache(sequence)).getTempoMPQAt(getTickPosition()));
            } else {
                setTempoInMPQ((float) cacheTempoMPQ);
            }
        }
    }

    /** populate the caches with the current values */
    private synchronized void setCaches() {
        cacheTempoFactor = getTempoFactor();
        cacheTempoMPQ = getTempoInMPQ();
    }



    protected synchronized void implClose() {
        if (Printer.trace) Printer.trace(">> RealTimeSequencer: implClose() ");

        if (playThread == null) {
            if (Printer.err) Printer.err("RealTimeSequencer.implClose() called, but playThread not instanciated!");
        } else {
            // Interrupt playback loop.
            playThread.close();
            playThread = null;
        }

        super.implClose();

        sequence = null;
        running = false;
        cacheTempoMPQ = -1;
        cacheTempoFactor = -1;
        trackMuted = null;
        trackSolo = null;
        loopStart = 0;
        loopEnd = -1;
        loopCount = 0;

        /** if this sequencer is set to autoconnect, need to
         * re-establish the connection at next open!
         */
        doAutoConnectAtNextOpen = autoConnect;

        if (autoConnectedReceiver != null) {
            try {
                autoConnectedReceiver.close();
            } catch (Exception e) {}
            autoConnectedReceiver = null;
        }

        if (Printer.trace) Printer.trace("<< RealTimeSequencer: implClose() completed");
    }

    void implStart() {
        if (Printer.trace) Printer.trace(">> RealTimeSequencer: implStart()");

        if (playThread == null) {
            if (Printer.err) Printer.err("RealTimeSequencer.implStart() called, but playThread not instanciated!");
            return;
        }

        tempoCache.refresh(sequence);
        if (!running) {
            running  = true;
            playThread.start();
        }
        if (Printer.trace) Printer.trace("<< RealTimeSequencer: implStart() completed");
    }


    void implStop() {
        if (Printer.trace) Printer.trace(">> RealTimeSequencer: implStop()");

        if (playThread == null) {
            if (Printer.err) Printer.err("RealTimeSequencer.implStop() called, but playThread not instanciated!");
            return;
        }

        recording = false;
        if (running) {
            running = false;
            playThread.stop();
        }
        if (Printer.trace) Printer.trace("<< RealTimeSequencer: implStop() completed");
    }

    private static EventDispatcher getEventDispatcher() {
        // create and start the global event thread
        //TODO  need a way to stop this thread when the engine is done
        final ThreadGroup tg = Thread.currentThread().getThreadGroup();
        synchronized (dispatchers) {
            EventDispatcher eventDispatcher = dispatchers.get(tg);
            if (eventDispatcher == null) {
                eventDispatcher = new EventDispatcher();
                dispatchers.put(tg, eventDispatcher);
                eventDispatcher.start();
            }
            return eventDispatcher;
        }
    }

    /**
     * Send midi player events.
     * must not be synchronized on "this"
     */
    void sendMetaEvents(MidiMessage message) {
        if (metaEventListeners.size() == 0) return;

        //if (Printer.debug) Printer.debug("sending a meta event");
        getEventDispatcher().sendAudioEvents(message, metaEventListeners);
    }

    /**
     * Send midi player events.
     */
    void sendControllerEvents(MidiMessage message) {
        int size = controllerEventListeners.size();
        if (size == 0) return;

        //if (Printer.debug) Printer.debug("sending a controller event");

        if (! (message instanceof ShortMessage)) {
            if (Printer.debug) Printer.debug("sendControllerEvents: message is NOT instanceof ShortMessage!");
            return;
        }
        ShortMessage msg = (ShortMessage) message;
        int controller = msg.getData1();
        List sendToListeners = new ArrayList();
        for (int i = 0; i < size; i++) {
            ControllerListElement cve = (ControllerListElement) controllerEventListeners.get(i);
            for(int j = 0; j < cve.controllers.length; j++) {
                if (cve.controllers[j] == controller) {
                    sendToListeners.add(cve.listener);
                    break;
                }
            }
        }
        getEventDispatcher().sendAudioEvents(message, sendToListeners);
    }



    private boolean needCaching() {
        return !isOpen() || (sequence == null) || (playThread == null);
    }

    /**
     * return the data pump instance, owned by play thread
     * if playthread is null, return null.
     * This method is guaranteed to return non-null if
     * needCaching returns false
     */
    private DataPump getDataPump() {
        if (playThread != null) {
            return playThread.getDataPump();
        }
        return null;
    }

    private MidiUtils.TempoCache getTempoCache() {
        return tempoCache;
    }

    private static boolean[] ensureBoolArraySize(boolean[] array, int desiredSize) {
        if (array == null) {
            return new boolean[desiredSize];
        }
        if (array.length < desiredSize) {
            boolean[] newArray = new boolean[desiredSize];
            System.arraycopy(array, 0, newArray, 0, array.length);
            return newArray;
        }
        return array;
    }


    // OVERRIDES OF ABSTRACT MIDI DEVICE METHODS

    protected boolean hasReceivers() {
        return true;
    }

    // for recording
    protected Receiver createReceiver() throws MidiUnavailableException {
        return new SequencerReceiver();
    }


    protected boolean hasTransmitters() {
        return true;
    }


    protected Transmitter createTransmitter() throws MidiUnavailableException {
        return new SequencerTransmitter();
    }


    // interface AutoConnectSequencer
    public void setAutoConnect(Receiver autoConnectedReceiver) {
        this.autoConnect = (autoConnectedReceiver != null);
        this.autoConnectedReceiver = autoConnectedReceiver;
    }



    // INNER CLASSES

    /**
     * An own class to distinguish the class name from
     * the transmitter of other devices
     */
    private class SequencerTransmitter extends BasicTransmitter {
        private SequencerTransmitter() {
            super();
        }
    }


    final class SequencerReceiver extends AbstractReceiver {

        void implSend(MidiMessage message, long timeStamp) {
            if (recording) {
                long tickPos = 0;

                // convert timeStamp to ticks
                if (timeStamp < 0) {
                    tickPos = getTickPosition();
                } else {
                    synchronized(tempoCache) {
                        tickPos = MidiUtils.microsecond2tick(sequence, timeStamp, tempoCache);
                    }
                }

                // and record to the first matching Track
                Track track = null;
                // do not record real-time events
                // see 5048381: NullPointerException when saving a MIDI sequence
                if (message.getLength() > 1) {
                    if (message instanceof ShortMessage) {
                        ShortMessage sm = (ShortMessage) message;
                        // all real-time messages have 0xF in the high nibble of the status byte
                        if ((sm.getStatus() & 0xF0) != 0xF0) {
                            track = RecordingTrack.get(recordingTracks, sm.getChannel());
                        }
                    } else {
                        // $$jb: where to record meta, sysex events?
                        // $$fb: the first recording track
                        track = RecordingTrack.get(recordingTracks, -1);
                    }
                    if (track != null) {
                        // create a copy of this message
                        if (message instanceof ShortMessage) {
                            message = new FastShortMessage((ShortMessage) message);
                        } else {
                            message = (MidiMessage) message.clone();
                        }

                        // create new MidiEvent
                        MidiEvent me = new MidiEvent(message, tickPos);
                        track.add(me);
                    }
                }
            }
        }
    }


    private static class RealTimeSequencerInfo extends MidiDevice.Info {

        private static final String name = "Real Time Sequencer";
        private static final String vendor = "Oracle Corporation";
        private static final String description = "Software sequencer";
        private static final String version = "Version 1.0";

        private RealTimeSequencerInfo() {
            super(name, vendor, description, version);
        }
    } // class Info


    private class ControllerListElement {

        // $$jb: using an array for controllers b/c its
        //       easier to deal with than turning all the
        //       ints into objects to use a Vector
        int []  controllers;
        final ControllerEventListener listener;

        private ControllerListElement(ControllerEventListener listener, int[] controllers) {

            this.listener = listener;
            if (controllers == null) {
                controllers = new int[128];
                for (int i = 0; i < 128; i++) {
                    controllers[i] = i;
                }
            }
            this.controllers = controllers;
        }

        private void addControllers(int[] c) {

            if (c==null) {
                controllers = new int[128];
                for (int i = 0; i < 128; i++) {
                    controllers[i] = i;
                }
                return;
            }
            int temp[] = new int[ controllers.length + c.length ];
            int elements;

            // first add what we have
            for(int i=0; i<controllers.length; i++) {
                temp[i] = controllers[i];
            }
            elements = controllers.length;
            // now add the new controllers only if we don't already have them
            for(int i=0; i<c.length; i++) {
                boolean flag = false;

                for(int j=0; j<controllers.length; j++) {
                    if (c[i] == controllers[j]) {
                        flag = true;
                        break;
                    }
                }
                if (!flag) {
                    temp[elements++] = c[i];
                }
            }
            // now keep only the elements we need
            int newc[] = new int[ elements ];
            for(int i=0; i<elements; i++){
                newc[i] = temp[i];
            }
            controllers = newc;
        }

        private void removeControllers(int[] c) {

            if (c==null) {
                controllers = new int[0];
            } else {
                int temp[] = new int[ controllers.length ];
                int elements = 0;


                for(int i=0; i<controllers.length; i++){
                    boolean flag = false;
                    for(int j=0; j<c.length; j++) {
                        if (controllers[i] == c[j]) {
                            flag = true;
                            break;
                        }
                    }
                    if (!flag){
                        temp[elements++] = controllers[i];
                    }
                }
                // now keep only the elements remaining
                int newc[] = new int[ elements ];
                for(int i=0; i<elements; i++) {
                    newc[i] = temp[i];
                }
                controllers = newc;

            }
        }

        private int[] getControllers() {

            // return a copy of our array of controllers,
            // so others can't mess with it
            if (controllers == null) {
                return null;
            }

            int c[] = new int[controllers.length];

            for(int i=0; i<controllers.length; i++){
                c[i] = controllers[i];
            }
            return c;
        }

    } // class ControllerListElement


    static class RecordingTrack {

        private final Track track;
        private int channel;

        RecordingTrack(Track track, int channel) {
            this.track = track;
            this.channel = channel;
        }

        static RecordingTrack get(List recordingTracks, Track track) {

            synchronized(recordingTracks) {
                int size = recordingTracks.size();

                for (int i = 0; i < size; i++) {
                    RecordingTrack current = (RecordingTrack)recordingTracks.get(i);
                    if (current.track == track) {
                        return current;
                    }
                }
            }
            return null;
        }

        static Track get(List recordingTracks, int channel) {

            synchronized(recordingTracks) {
                int size = recordingTracks.size();
                for (int i = 0; i < size; i++) {
                    RecordingTrack current = (RecordingTrack)recordingTracks.get(i);
                    if ((current.channel == channel) || (current.channel == -1)) {
                        return current.track;
                    }
                }
            }
            return null;

        }
    }


    final class PlayThread implements Runnable {
        private Thread thread;
        private final Object lock = new Object();

        /** true if playback is interrupted (in close) */
        boolean interrupted = false;
        boolean isPumping = false;

        private final DataPump dataPump = new DataPump();


        PlayThread() {
            // nearly MAX_PRIORITY
            int priority = Thread.NORM_PRIORITY
                + ((Thread.MAX_PRIORITY - Thread.NORM_PRIORITY) * 3) / 4;
            thread = JSSecurityManager.createThread(this,
                                                    "Java Sound Sequencer", // name
                                                    false,                  // daemon
                                                    priority,               // priority
                                                    true);                  // doStart
        }

        DataPump getDataPump() {
            return dataPump;
        }

        synchronized void setSequence(Sequence seq) {
            dataPump.setSequence(seq);
        }


        /** start thread and pump. Requires up-to-date tempoCache */
        synchronized void start() {
            // mark the sequencer running
            running = true;

            if (!dataPump.hasCachedTempo()) {
                long tickPos = getTickPosition();
                dataPump.setTempoMPQ(tempoCache.getTempoMPQAt(tickPos));
            }
            dataPump.checkPointMillis = 0; // means restarted
            dataPump.clearNoteOnCache();
            dataPump.needReindex = true;

            dataPump.resetLoopCount();

            // notify the thread
            synchronized(lock) {
                lock.notifyAll();
            }

            if (Printer.debug) Printer.debug(" ->Started MIDI play thread");

        }

        // waits until stopped
        synchronized void stop() {
            playThreadImplStop();
            long t = System.nanoTime() / 1000000l;
            while (isPumping) {
                synchronized(lock) {
                    try {
                        lock.wait(2000);
                    } catch (InterruptedException ie) {
                        // ignore
                    }
                }
                // don't wait for more than 2 seconds
                if ((System.nanoTime()/1000000l) - t > 1900) {
                    if (Printer.err) Printer.err("Waited more than 2 seconds in RealTimeSequencer.PlayThread.stop()!");
                    //break;
                }
            }
        }

        void playThreadImplStop() {
            // mark the sequencer running
            running = false;
            synchronized(lock) {
                lock.notifyAll();
            }
        }

        void close() {
            Thread oldThread = null;
            synchronized (this) {
                // dispose of thread
                interrupted = true;
                oldThread = thread;
                thread = null;
            }
            if (oldThread != null) {
                // wake up the thread if it's in wait()
                synchronized(lock) {
                    lock.notifyAll();
                }
            }
            // wait for the thread to terminate itself,
            // but max. 2 seconds. Must not be synchronized!
            if (oldThread != null) {
                try {
                    oldThread.join(2000);
                } catch (InterruptedException ie) {}
            }
        }


        /**
         * Main process loop driving the media flow.
         *
         * Make sure to NOT synchronize on RealTimeSequencer
         * anywhere here (even implicit). That is a sure deadlock!
         */
        public void run() {

            while (!interrupted) {
                boolean EOM = false;
                boolean wasRunning = running;
                isPumping = !interrupted && running;
                while (!EOM && !interrupted && running) {
                    EOM = dataPump.pump();

                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException ie) {
                        // ignore
                    }
                }
                if (Printer.debug) {
                    Printer.debug("Exited main pump loop because: ");
                    if (EOM) Printer.debug(" -> EOM is reached");
                    if (!running) Printer.debug(" -> running was set to false");
                    if (interrupted) Printer.debug(" -> interrupted was set to true");
                }

                playThreadImplStop();
                if (wasRunning) {
                    dataPump.notesOff(true);
                }
                if (EOM) {
                    dataPump.setTickPos(sequence.getTickLength());

                    // send EOT event (mis-used for end of media)
                    MetaMessage message = new MetaMessage();
                    try{
                        message.setMessage(MidiUtils.META_END_OF_TRACK_TYPE, new byte[0], 0);
                    } catch(InvalidMidiDataException e1) {}
                    sendMetaEvents(message);
                }
                synchronized (lock) {
                    isPumping = false;
                    // wake up a waiting stop() method
                    lock.notifyAll();
                    while (!running && !interrupted) {
                        try {
                            lock.wait();
                        } catch (Exception ex) {}
                    }
                }
            } // end of while(!EOM && !interrupted && running)
            if (Printer.debug) Printer.debug("end of play thread");
        }
    }


    /**
     * class that does the actual dispatching of events,
     * used to be in native in MMAPI
     */
    private class DataPump {
        private float currTempo;         // MPQ tempo
        private float tempoFactor;       // 1.0 is default
        private float inverseTempoFactor;// = 1.0 / tempoFactor
        private long ignoreTempoEventAt; // ignore next META tempo during playback at this tick pos only
        private int resolution;
        private float divisionType;
        private long checkPointMillis;   // microseconds at checkoint
        private long checkPointTick;     // ticks at checkpoint
        private int[] noteOnCache;       // bit-mask of notes that are currently on
        private Track[] tracks;
        private boolean[] trackDisabled; // if true, do not play this track
        private int[] trackReadPos;      // read index per track
        private long lastTick;
        private boolean needReindex = false;
        private int currLoopCounter = 0;

        //private sun.misc.Perf perf = sun.misc.Perf.getPerf();
        //private long perfFreq = perf.highResFrequency();


        DataPump() {
            init();
        }

        synchronized void init() {
            ignoreTempoEventAt = -1;
            tempoFactor = 1.0f;
            inverseTempoFactor = 1.0f;
            noteOnCache = new int[128];
            tracks = null;
            trackDisabled = null;
        }

        synchronized void setTickPos(long tickPos) {
            long oldLastTick = tickPos;
            lastTick = tickPos;
            if (running) {
                notesOff(false);
            }
            if (running || tickPos > 0) {
                // will also reindex
                chaseEvents(oldLastTick, tickPos);
            } else {
                needReindex = true;
            }
            if (!hasCachedTempo()) {
                setTempoMPQ(getTempoCache().getTempoMPQAt(lastTick, currTempo));
                // treat this as if it is a real time tempo change
                ignoreTempoEventAt = -1;
            }
            // trigger re-configuration
            checkPointMillis = 0;
        }

        long getTickPos() {
            return lastTick;
        }

        // hasCachedTempo is only valid if it is the current position
        boolean hasCachedTempo() {
            if (ignoreTempoEventAt != lastTick) {
                ignoreTempoEventAt = -1;
            }
            return ignoreTempoEventAt >= 0;
        }

        // this method is also used internally in the pump!
        synchronized void setTempoMPQ(float tempoMPQ) {
            if (tempoMPQ > 0 && tempoMPQ != currTempo) {
                ignoreTempoEventAt = lastTick;
                this.currTempo = tempoMPQ;
                // re-calculate check point
                checkPointMillis = 0;
            }
        }

        float getTempoMPQ() {
            return currTempo;
        }

        synchronized void setTempoFactor(float factor) {
            if (factor > 0 && factor != this.tempoFactor) {
                tempoFactor = factor;
                inverseTempoFactor = 1.0f / factor;
                // re-calculate check point
                checkPointMillis = 0;
            }
        }

        float getTempoFactor() {
            return tempoFactor;
        }

        synchronized void muteSoloChanged() {
            boolean[] newDisabled = makeDisabledArray();
            if (running) {
                applyDisabledTracks(trackDisabled, newDisabled);
            }
            trackDisabled = newDisabled;
        }



        synchronized void setSequence(Sequence seq) {
            if (seq == null) {
                init();
                return;
            }
            tracks = seq.getTracks();
            muteSoloChanged();
            resolution = seq.getResolution();
            divisionType = seq.getDivisionType();
            trackReadPos = new int[tracks.length];
            // trigger re-initialization
            checkPointMillis = 0;
            needReindex = true;
        }

        synchronized void resetLoopCount() {
            currLoopCounter = loopCount;
        }

        void clearNoteOnCache() {
            for (int i = 0; i < 128; i++) {
                noteOnCache[i] = 0;
            }
        }

        void notesOff(boolean doControllers) {
            int done = 0;
            for (int ch=0; ch<16; ch++) {
                int channelMask = (1< chaseEvents from tick "+startTick+".."+(endTick-1));
            byte[][] tempArray = new byte[128][16];
            for (int t = 0; t < tracks.length; t++) {
                if ((trackDisabled == null)
                    || (trackDisabled.length <= t)
                    || (!trackDisabled[t])) {
                    // if track is not disabled, chase the events for it
                    chaseTrackEvents(t, startTick, endTick, true, tempArray);
                }
            }
            if (DEBUG_PUMP) Printer.println("<< chaseEvents");
        }


        // playback related methods (pumping)

        private long getCurrentTimeMillis() {
            return System.nanoTime() / 1000000l;
            //return perf.highResCounter() * 1000 / perfFreq;
        }

        private long millis2tick(long millis) {
            if (divisionType != Sequence.PPQ) {
                double dTick = ((((double) millis) * tempoFactor)
                                * ((double) divisionType)
                                * ((double) resolution))
                    / ((double) 1000);
                return (long) dTick;
            }
            return MidiUtils.microsec2ticks(millis * 1000,
                                            currTempo * inverseTempoFactor,
                                            resolution);
        }

        private long tick2millis(long tick) {
            if (divisionType != Sequence.PPQ) {
                double dMillis = ((((double) tick) * 1000) /
                                  (tempoFactor * ((double) divisionType) * ((double) resolution)));
                return (long) dMillis;
            }
            return MidiUtils.ticks2microsec(tick,
                                            currTempo * inverseTempoFactor,
                                            resolution) / 1000;
        }

        private void ReindexTrack(int trackNum, long tick) {
            if (trackNum < trackReadPos.length && trackNum < tracks.length) {
                trackReadPos[trackNum] = MidiUtils.tick2index(tracks[trackNum], tick);
                if (DEBUG_PUMP) Printer.println("  reindexTrack: setting trackReadPos["+trackNum+"] = "+trackReadPos[trackNum]);
            }
        }

        /* returns if changes are pending */
        private boolean dispatchMessage(int trackNum, MidiEvent event) {
            boolean changesPending = false;
            MidiMessage message = event.getMessage();
            int msgStatus = message.getStatus();
            int msgLen = message.getLength();
            if (msgStatus == MetaMessage.META && msgLen >= 2) {
                // a meta message. Do not send it to the device.
                // 0xFF with length=1 is a MIDI realtime message
                // which shouldn't be in a Sequence, but we play it
                // nonetheless.

                // see if this is a tempo message. Only on track 0.
                if (trackNum == 0) {
                    int newTempo = MidiUtils.getTempoMPQ(message);
                    if (newTempo > 0) {
                        if (event.getTick() != ignoreTempoEventAt) {
                            setTempoMPQ(newTempo); // sets ignoreTempoEventAt!
                            changesPending = true;
                        }
                        // next loop, do not ignore anymore tempo events.
                        ignoreTempoEventAt = -1;
                    }
                }
                // send to listeners
                sendMetaEvents(message);

            } else {
                // not meta, send to device
                getTransmitterList().sendMessage(message, -1);

                switch (msgStatus & 0xF0) {
                case ShortMessage.NOTE_OFF: {
                    // note off - clear the bit in the noteOnCache array
                    int note = ((ShortMessage) message).getData1() & 0x7F;
                    noteOnCache[note] &= (0xFFFF ^ (1<<(msgStatus & 0x0F)));
                    break;
                }

                case ShortMessage.NOTE_ON: {
                    // note on
                    ShortMessage smsg = (ShortMessage) message;
                    int note = smsg.getData1() & 0x7F;
                    int vel = smsg.getData2() & 0x7F;
                    if (vel > 0) {
                        // if velocity > 0 set the bit in the noteOnCache array
                        noteOnCache[note] |= 1<<(msgStatus & 0x0F);
                    } else {
                        // if velocity = 0 clear the bit in the noteOnCache array
                        noteOnCache[note] &= (0xFFFF ^ (1<<(msgStatus & 0x0F)));
                    }
                    break;
                }

                case ShortMessage.CONTROL_CHANGE:
                    // if controller message, send controller listeners
                    sendControllerEvents(message);
                    break;

                }
            }
            return changesPending;
        }


        /** the main pump method
         * @return true if end of sequence is reached
         */
        synchronized boolean pump() {
            long currMillis;
            long targetTick = lastTick;
            MidiEvent currEvent;
            boolean changesPending = false;
            boolean doLoop = false;
            boolean EOM = false;

            currMillis = getCurrentTimeMillis();
            int finishedTracks = 0;
            do {
                changesPending = false;

                // need to re-find indexes in tracks?
                if (needReindex) {
                    if (DEBUG_PUMP) Printer.println("Need to re-index at "+currMillis+" millis. TargetTick="+targetTick);
                    if (trackReadPos.length < tracks.length) {
                        trackReadPos = new int[tracks.length];
                    }
                    for (int t = 0; t < tracks.length; t++) {
                        ReindexTrack(t, targetTick);
                        if (DEBUG_PUMP_ALL) Printer.println("  Setting trackReadPos["+t+"]="+trackReadPos[t]);
                    }
                    needReindex = false;
                    checkPointMillis = 0;
                }

                // get target tick from current time in millis
                if (checkPointMillis == 0) {
                    // new check point
                    currMillis = getCurrentTimeMillis();
                    checkPointMillis = currMillis;
                    targetTick = lastTick;
                    checkPointTick = targetTick;
                    if (DEBUG_PUMP) Printer.println("New checkpoint to "+currMillis+" millis. "
                                                       +"TargetTick="+targetTick
                                                       +" new tempo="+MidiUtils.convertTempo(currTempo)+"bpm");
                } else {
                    // calculate current tick based on current time in milliseconds
                    targetTick = checkPointTick + millis2tick(currMillis - checkPointMillis);
                    if (DEBUG_PUMP_ALL) Printer.println("targetTick = "+targetTick+" at "+currMillis+" millis");
                    if ((loopEnd != -1)
                        && ((loopCount > 0 && currLoopCounter > 0)
                            || (loopCount == LOOP_CONTINUOUSLY))) {
                        if (lastTick <= loopEnd && targetTick >= loopEnd) {
                            // need to loop!
                            // only play until loop end
                            targetTick = loopEnd - 1;
                            doLoop = true;
                            if (DEBUG_PUMP) Printer.println("set doLoop to true. lastTick="+lastTick
                                                               +"  targetTick="+targetTick
                                                               +"  loopEnd="+loopEnd
                                                               +"  jumping to loopStart="+loopStart
                                                               +"  new currLoopCounter="+currLoopCounter);
                            if (DEBUG_PUMP) Printer.println("  currMillis="+currMillis
                                                               +"  checkPointMillis="+checkPointMillis
                                                               +"  checkPointTick="+checkPointTick);

                        }
                    }
                    lastTick = targetTick;
                }

                finishedTracks = 0;

                for (int t = 0; t < tracks.length; t++) {
                    try {
                        boolean disabled = trackDisabled[t];
                        Track thisTrack = tracks[t];
                        int readPos = trackReadPos[t];
                        int size = thisTrack.size();
                        // play all events that are due until targetTick
                        while (!changesPending && (readPos < size)
                               && (currEvent = thisTrack.get(readPos)).getTick() <= targetTick) {

                            if ((readPos == size -1) &&  MidiUtils.isMetaEndOfTrack(currEvent.getMessage())) {
                                // do not send out this message. Finished with this track
                                readPos = size;
                                break;
                            }
                            // TODO: some kind of heuristics if the MIDI messages have changed
                            // significantly (i.e. deleted or inserted a bunch of messages)
                            // since last time. Would need to set needReindex = true then
                            readPos++;
                            // only play this event if the track is enabled,
                            // or if it is a tempo message on track 0
                            // Note: cannot put this check outside
                            //       this inner loop in order to detect end of file
                            if (!disabled ||
                                ((t == 0) && (MidiUtils.isMetaTempo(currEvent.getMessage())))) {
                                changesPending = dispatchMessage(t, currEvent);
                            }
                        }
                        if (readPos >= size) {
                            finishedTracks++;
                        }
                        if (DEBUG_PUMP_ALL) {
                            System.out.print(" pumped track "+t+" ("+size+" events) "
                                             +" from index: "+trackReadPos[t]
                                             +" to "+(readPos-1));
                            System.out.print(" -> ticks: ");
                            if (trackReadPos[t] < size) {
                                System.out.print(""+(thisTrack.get(trackReadPos[t]).getTick()));
                            } else {
                                System.out.print("EOT");
                            }
                            System.out.print(" to ");
                            if (readPos < size) {
                                System.out.print(""+(thisTrack.get(readPos-1).getTick()));
                            } else {
                                System.out.print("EOT");
                            }
                            System.out.println();
                        }
                        trackReadPos[t] = readPos;
                    } catch(Exception e) {
                        if (Printer.debug) Printer.debug("Exception in Sequencer pump!");
                        if (Printer.debug) e.printStackTrace();
                        if (e instanceof ArrayIndexOutOfBoundsException) {
                            needReindex = true;
                            changesPending = true;
                        }
                    }
                    if (changesPending) {
                        break;
                    }
                }
                EOM = (finishedTracks == tracks.length);
                if (doLoop
                    || ( ((loopCount > 0 && currLoopCounter > 0)
                          || (loopCount == LOOP_CONTINUOUSLY))
                         && !changesPending
                         && (loopEnd == -1)
                         && EOM)) {

                    long oldCheckPointMillis = checkPointMillis;
                    long loopEndTick = loopEnd;
                    if (loopEndTick == -1) {
                        loopEndTick = lastTick;
                    }

                    // need to loop back!
                    if (loopCount != LOOP_CONTINUOUSLY) {
                        currLoopCounter--;
                    }
                    if (DEBUG_PUMP) Printer.println("Execute loop: lastTick="+lastTick
                                                       +"  loopEnd="+loopEnd
                                                       +"  jumping to loopStart="+loopStart
                                                       +"  new currLoopCounter="+currLoopCounter);
                    setTickPos(loopStart);
                    // now patch the checkPointMillis so that
                    // it points to the exact beginning of when the loop was finished

                    // $$fb TODO: although this is mathematically correct (i.e. the loop position
                    //            is correct, and doesn't drift away with several repetition,
                    //            there is a slight lag when looping back, probably caused
                    //            by the chasing.

                    checkPointMillis = oldCheckPointMillis + tick2millis(loopEndTick - checkPointTick);
                    checkPointTick = loopStart;
                    if (DEBUG_PUMP) Printer.println("  Setting currMillis="+currMillis
                                                       +"  new checkPointMillis="+checkPointMillis
                                                       +"  new checkPointTick="+checkPointTick);
                    // no need for reindexing, is done in setTickPos
                    needReindex = false;
                    changesPending = false;
                    // reset doLoop flag
                    doLoop = false;
                    EOM = false;
                }
            } while (changesPending);

            return EOM;
        }

    } // class DataPump

}

Other Java examples (source code examples)

Here is a short list of links related to this Java RealTimeSequencer.java source code file:

... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.