|
Java example source code file (RealTimeSequencer.java)
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< 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 |
Copyright 1998-2024 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.