/* * Copyright (c) 2003, 2011, 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.ByteArrayOutputStream;import java.io.ByteArrayInputStream;import java.io.DataOutputStream;import java.io.IOException;import java.io.InputStream; import java.util.ArrayList;import java.util.List; import javax.sound.midi.*; /** * A Real Time Sequencer * * @author Florian Bomers */ /* TODO: * - rename PlayThread to PlayEngine (because isn't a thread) */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 EventDispatcher eventDispatcher; /** * All RealTimeSequencers share this info object. */ static final RealTimeSequencerInfo info = new RealTimeSequencerInfo(); private static Sequencer.SyncMode[] masterSyncModes = { Sequencer.SyncMode.INTERNAL_CLOCK }; private static Sequencer.SyncMode[] slaveSyncModes = { Sequencer.SyncMode.NO_SYNC }; private static Sequencer.SyncMode masterSyncMode = Sequencer.SyncMode.INTERNAL_CLOCK; private static 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 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 List recordingTracks = new ArrayList(); private long loopStart = 0; private long loopEnd = -1; private int loopCount = 0; /** * Meta event listeners */ private ArrayList metaEventListeners = new ArrayList(); /** * Control change listeners */ private 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; static { // create and start the global event thread eventDispatcher = new EventDispatcher(); eventDispatcher.start(); } /* ****************************** CONSTRUCTOR ****************************** */ protected 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;Lines 1–500 of 2076. Load more
/* * Copyright (c) 2003, 2011, 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.ByteArrayOutputStream; import java.io.ByteArrayInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.List; import javax.sound.midi.*; /** * A Real Time Sequencer * * @author Florian Bomers */ /* TODO: * - rename PlayThread to PlayEngine (because isn't a thread) */ 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 EventDispatcher eventDispatcher; /** * All RealTimeSequencers share this info object. */ static final RealTimeSequencerInfo info = new RealTimeSequencerInfo(); private static Sequencer.SyncMode[] masterSyncModes = { Sequencer.SyncMode.INTERNAL_CLOCK }; private static Sequencer.SyncMode[] slaveSyncModes = { Sequencer.SyncMode.NO_SYNC }; private static Sequencer.SyncMode masterSyncMode = Sequencer.SyncMode.INTERNAL_CLOCK; private static 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 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 List recordingTracks = new ArrayList(); private long loopStart = 0; private long loopEnd = -1; private int loopCount = 0; /** * Meta event listeners */ private ArrayList metaEventListeners = new ArrayList(); /** * Control change listeners */ private 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; static { // create and start the global event thread eventDispatcher = new EventDispatcher(); eventDispatcher.start(); } /* ****************************** CONSTRUCTOR ****************************** */ protected 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;