6321N/A * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved. 0N/A * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 0N/A * This code is free software; you can redistribute it and/or modify it 0N/A * under the terms of the GNU General Public License version 2 only, as 2362N/A * published by the Free Software Foundation. Oracle designates this 0N/A * particular file as subject to the "Classpath" exception as provided 2362N/A * by Oracle in the LICENSE file that accompanied this code. 0N/A * This code is distributed in the hope that it will be useful, but WITHOUT 0N/A * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 0N/A * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 0N/A * version 2 for more details (a copy is included in the LICENSE file that 0N/A * accompanied this code). 0N/A * You should have received a copy of the GNU General Public License version 0N/A * 2 along with this work; if not, write to the Free Software Foundation, 0N/A * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 2362N/A * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 2362N/A * or visit www.oracle.com if you need additional information or have any 0N/A * A Real Time Sequencer 0N/A * @author Florian Bomers 0N/A * - rename PlayThread to PlayEngine (because isn't a thread) 0N/A /** debugging flags */ 0N/A * Event Dispatcher thread. Should be using a shared event 0N/A * dispatcher instance with a factory in EventDispatcher 0N/A * All RealTimeSequencers share this info object. 0N/A * Sequence on which this sequencer is operating. 0N/A * Same for setTempoInMPQ... 0N/A * cache value for tempo factor until sequence is set 0N/A /** if a particular track is muted */ 0N/A /** if a particular track is solo */ 0N/A /** tempo cache for getMicrosecondPosition */ 0N/A * True if the sequence is running. 0N/A /** the thread for pushing out the MIDI messages */ 0N/A * True if we are recording 0N/A * List of tracks to which we're recording 0N/A * Meta event listeners 0N/A * Control change listeners 0N/A /** automatic connection support */ 0N/A /** if we need to autoconnect at next open */ 0N/A /** the receiver that this device is auto-connected to */ 0N/A /* ****************************** CONSTRUCTOR ****************************** */ 0N/A /* ****************************** SEQUENCER METHODS ******************** */ 0N/A // initialize some non-cached values 0N/A // store this sequence (do not copy - we want to give the possibility 0N/A // of modifying the sequence at runtime) 0N/A // rewind to the beginning 0N/A // sequencer not open: throw an exception 0N/A // sequence not available: throw an exception 0N/A // already running: return quietly 0N/A // not running; just return 0N/A // should throw IllegalArgumentException 0N/A // if the sequencer is closed, return cached value 0N/A // if sequence is set, return current tempo 0N/A // last resort: return a standard tempo: 120bpm 0N/A // should throw IllegalArgumentException 0N/A // set the native tempo in MPQ 0N/A // reset the tempoInBPM and tempoInMPQ values so we won't use them again 0N/A // should throw IllegalArgumentException 0N/A // don't need cache anymore 0N/A // should throw IllegalArgumentException 0N/A // throw new InvalidStateException("cannot set position in closed state"); 0N/A // throw new InvalidStateException("cannot set position if sequence is not set"); 0N/A // should throw IllegalArgumentException 0N/A // throw new InvalidStateException("cannot set position in closed state"); 0N/A // throw new InvalidStateException("cannot set position if sequence is not set"); 0N/A // $$fb wish there was a nicer way to get the number of tracks... 0N/A // first find the listener. if we have one, add the controllers 0N/A // if not, create a new element for it. 0N/A // and return all the controllers this listener is interested in 0N/A ////////////////// LOOPING (added in 1.5) /////////////////////// 0N/A /* *********************************** play control ************************* */ 0N/A //openInternalSynth(); 0N/A // create PlayThread 0N/A // throw new MidiUnavailableException("unable to open sequencer"); 0N/A // first try to connect to the default synthesizer 0N/A // IMPORTANT: this code needs to be synch'ed with 3631N/A // MidiSystem.getSequencer(boolean), because the same 0N/A // algorithm needs to be used! 3631N/A // make sure that the synth is properly closed 0N/A // something went wrong with synth 0N/A // then try to connect to the default Receiver 0N/A // something went wrong. Nothing to do then! 0N/A // only set caches if open and sequence is set 0N/A /** populate the caches with the current values */ 0N/A // Interrupt playback loop. 0N/A /** if this sequencer is set to autoconnect, need to 0N/A * re-establish the connection at next open! 6321N/A // create and start the global event thread 6321N/A //TODO need a way to stop this thread when the engine is done 0N/A * Send midi player events. 0N/A * must not be synchronized on "this" 0N/A //if (Printer.debug) Printer.debug("sending a meta event"); 0N/A * Send midi player events. 0N/A //if (Printer.debug) Printer.debug("sending a controller event"); 0N/A * return the data pump instance, owned by play thread 0N/A * if playthread is null, return null. 0N/A * This method is guaranteed to return non-null if 0N/A * needCaching returns false 0N/A // OVERRIDES OF ABSTRACT MIDI DEVICE METHODS 0N/A // interface AutoConnectSequencer 0N/A * An own class to distinguish the class name from 0N/A * the transmitter of other devices 0N/A // convert timeStamp to ticks 0N/A // and record to the first matching Track 0N/A // do not record real-time events 0N/A // see 5048381: NullPointerException when saving a MIDI sequence 0N/A // all real-time messages have 0xF in the high nibble of the status byte 0N/A // $$jb: where to record meta, sysex events? 0N/A // $$fb: the first recording track 0N/A // create a copy of this message 0N/A // create new MidiEvent 0N/A // $$jb: using an array for controllers b/c its 0N/A // easier to deal with than turning all the 0N/A // ints into objects to use a Vector 0N/A for (
int i =
0; i <
128; i++) {
0N/A for (
int i =
0; i <
128; i++) {
0N/A // first add what we have 0N/A // now add the new controllers only if we don't already have them 0N/A // now keep only the elements we need 0N/A // now keep only the elements remaining 0N/A // return a copy of our array of controllers, 0N/A // so others can't mess with it 0N/A }
// class ControllerListElement 0N/A /** true if playback is interrupted (in close) */ 0N/A // nearly MAX_PRIORITY 0N/A "Java Sound Sequencer",
// name 0N/A /** start thread and pump. Requires up-to-date tempoCache */ 0N/A // mark the sequencer running 0N/A // notify the thread 0N/A // waits until stopped 0N/A // don't wait for more than 2 seconds 0N/A // mark the sequencer running 0N/A synchronized (
this) {
0N/A // dispose of thread 0N/A // wake up the thread if it's in wait() 0N/A // wait for the thread to terminate itself, 0N/A // but max. 2 seconds. Must not be synchronized! 0N/A * Main process loop driving the media flow. 0N/A * Make sure to NOT synchronize on RealTimeSequencer 0N/A * anywhere here (even implicit). That is a sure deadlock! 0N/A // send EOT event (mis-used for end of media) 0N/A // wake up a waiting stop() method 0N/A }
// end of while(!EOM && !interrupted && running) 0N/A * class that does the actual dispatching of events, 0N/A * used to be in native in MMAPI 0N/A //private sun.misc.Perf perf = sun.misc.Perf.getPerf(); 0N/A //private long perfFreq = perf.highResFrequency(); 0N/A // will also reindex 0N/A // treat this as if it is a real time tempo change 0N/A // trigger re-configuration 0N/A // hasCachedTempo is only valid if it is the current position 0N/A // this method is also used internally in the pump! 0N/A // re-calculate check point 0N/A // re-calculate check point 0N/A // trigger re-initialization 0N/A for (
int i =
0; i <
128; i++) {
0N/A for (
int i=
0; i<
128; i++) {
0N/A // send note on with velocity 0 0N/A /* reset all controllers */ 0N/A // if one track is solo, then only play solo 0N/A // only the channels with solo play, regardless of mute 0N/A // mute the selected channels 0N/A * chase all events from beginning of Track 0N/A * and send note off for those events that are active 0N/A * in noteOnCache array. 0N/A * It is possible, of course, to catch notes from other tracks, 0N/A * but better than more complicated logic to detect 0N/A * which notes are really from this track 0N/A // only consider Note On with velocity > 0 0N/A // only consider Note On with velocity > 0 0N/A // the bit is set. Send Note Off 0N/A // this happens when messages are removed 0N/A // from the track while this method executes 0N/A * if a track is muted that was previously playing, send 0N/A * note off events for all currently playing notes 0N/A // case that a track gets muted: need to 0N/A // send appropriate note off events to prevent 0N/A // case that a track was muted and is now unmuted 0N/A // need to chase events and re-index this track 0N/A /** go through all events from startTick to endTick 0N/A * chase the controller state and program change state 0N/A * and then set the end-states at once. 0N/A * needs to be called in synchronized state 0N/A * @param tempArray an byte[128][16] to hold controller messages 0N/A // start from the beginning 0N/A // init temp array with impossible values 0N/A // this happens when messages are removed 0N/A // from the track while this method executes 0N/A // now send out the aggregated controllers and program changes 0N/A // send program change *after* controllers, to 0N/A // correctly initialize banks 0N/A // reset pitch bend on this channel (E0 00 40) 0N/A // reset sustain pedal on this channel 0N/A /** chase controllers and program for all tracks */ 0N/A // if track is not disabled, chase the events for it 0N/A // playback related methods (pumping) 0N/A //return perf.highResCounter() * 1000 / perfFreq; 0N/A /* returns if changes are pending */ 0N/A // a meta message. Do not send it to the device. 0N/A // 0xFF with length=1 is a MIDI realtime message 0N/A // which shouldn't be in a Sequence, but we play it 0N/A // see if this is a tempo message. Only on track 0. 0N/A // next loop, do not ignore anymore tempo events. 0N/A // send to listeners 0N/A // not meta, send to device 0N/A // note off - clear the bit in the noteOnCache array 0N/A // if velocity > 0 set the bit in the noteOnCache array 0N/A // if velocity = 0 clear the bit in the noteOnCache array 0N/A // if controller message, send controller listeners 0N/A /** the main pump method 0N/A * @return true if end of sequence is reached 0N/A // need to re-find indexes in tracks? 0N/A // get target tick from current time in millis 0N/A // calculate current tick based on current time in milliseconds 0N/A // only play until loop end 0N/A // play all events that are due until targetTick 0N/A // do not send out this message. Finished with this track 0N/A // TODO: some kind of heuristics if the MIDI messages have changed 0N/A // significantly (i.e. deleted or inserted a bunch of messages) 0N/A // since last time. Would need to set needReindex = true then 0N/A // only play this event if the track is enabled, 0N/A // or if it is a tempo message on track 0 0N/A // Note: cannot put this check outside 0N/A // this inner loop in order to detect end of file 0N/A // need to loop back! 0N/A // now patch the checkPointMillis so that 0N/A // it points to the exact beginning of when the loop was finished 0N/A // $$fb TODO: although this is mathematically correct (i.e. the loop position 0N/A // is correct, and doesn't drift away with several repetition, 0N/A // there is a slight lag when looping back, probably caused 0N/A // no need for reindexing, is done in setTickPos 0N/A // reset doLoop flag