/*
* Created on Jul 3, 2005
*
* Copyright (c) 2005 Peter Johan Salomonsen (http://www.petersalomonsen.com)
*
* http://www.frinika.com
*
* This file is part of Frinika.
*
* Frinika is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Frinika 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 for more details.
*
* You should have received a copy of the GNU General Public License
* along with Frinika; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package com.frinika.sequencer;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.ArrayList;
import java.util.Vector;
import javax.sound.midi.ControllerEventListener;
import javax.sound.midi.InvalidMidiDataException;
import javax.sound.midi.MetaEventListener;
import javax.sound.midi.MidiDevice;
import javax.sound.midi.MidiMessage;
import javax.sound.midi.MidiUnavailableException;
import javax.sound.midi.Receiver;
import javax.sound.midi.Sequence;
import javax.sound.midi.Sequencer;
import javax.sound.midi.ShortMessage;
import javax.sound.midi.SysexMessage;
import javax.sound.midi.Track;
import javax.sound.midi.Transmitter;
import com.frinika.project.FrinikaAudioSystem;
import com.frinika.sequencer.gui.RecordingDialog;
import com.frinika.sequencer.midi.MidiMessageListener;
import com.frinika.sequencer.midi.MonitorReceiver;
import com.frinika.sequencer.model.AudioLane;
import com.frinika.sequencer.model.ChannelEvent;
import com.frinika.sequencer.model.ControllerEvent;
import com.frinika.sequencer.model.MidiLane;
import com.frinika.sequencer.model.MidiPart;
import com.frinika.sequencer.model.MidiPlayOptions;
import com.frinika.sequencer.model.MultiEvent;
import com.frinika.sequencer.model.NoteEvent;
import com.frinika.sequencer.model.Part;
import com.frinika.sequencer.model.PitchBendEvent;
import com.frinika.sequencer.model.RecordableLane;
import com.frinika.synth.SynthRack;
import com.frinika.sequencer.model.tempo.TempoList;
/**
* The purpose of the Frinika sequencer implementation is to solve
* the following issues of the current implementation in Sun J2SE5.0:
* - Smooth looping
* - Don't skip notes / events on the first tick when starting to play
* - Able to insert / remove events from the sequence while playing (not same as
* recording)
* - Better abilities for song position monitoring
* NOTE: The Frinika sequencer is not a complete implementation of the Java sequencer. Its
* primary goals is to solve the needs of Frinika, and the implementation subset
* is thereafter.
*
* To be able to insert and remove events while playing the sequence has to be reorganized
* from a vector, to a hashtable structure. In the default sequencer pointers to the next
* midi event to be played is based on the vector index. When inserting or removing notes
* before the current index the sequencer pointer will break. By using a hashtable with
* keyword tick and value set to an array of events occuring at that tick, one is able to
* lookup notes to be played for a given tick position - thus not vulnerable to changes in
* the ordering of events.
*
* The skipping of notes on the start tick, is caused by a bug in the binary search routine
* of the default sequencer. This binary search is searching for the first event to play
* after the given starttick. By using the mentioned hashtable above, this is not an issue
* anymore.
*
* Issues with smooth looping is suspected though not verified, to be caused by chasing of
* controller values when resetting the loop. The symptom is that the longer out in the song
* you are, the loop gap is longer and longer.
*
* @author Peter Johan Salomonsen
*
* TODO PJL repair notification for recording.
*/
public class FrinikaSequencer implements Sequencer {
private FrinikaSequence sequence;
private FrinikaSequencerPlayer player = new FrinikaSequencerPlayer(this);
private int loopCount;
private long loopStartPoint;
private long loopEndPoint;
private float bpm = 100;
private boolean recording;
private final List<Transmitter> transmitters = new ArrayList<Transmitter>();
private final List<Receiver> receivers = new ArrayList<Receiver>();
private final List<SongPositionListener> songPositionListeners = new ArrayList<SongPositionListener>();
private final ArrayList<SequencerListener> sequencerListeners = new ArrayList<SequencerListener>();
private final Collection<MidiMessageListener> midiMessageListeners = new HashSet<MidiMessageListener>(); // Jens
/**
* List of Midi Out Devices mapped to the transmitters
*/
private final HashMap<MidiDevice,Transmitter> midiOutDeviceTransmitters = new HashMap<MidiDevice,Transmitter>();
private final List<MidiDevice> midiOutDevices = new ArrayList<MidiDevice>();
/**
* Songposition listeners that requires to be notified on each tick
*/
private final ArrayList<SongPositionListener> intenseSongPositionListeners = new ArrayList<SongPositionListener>();
/**
* Tempochange listeners
*/
private final ArrayList<TempoChangeListener> tempoChangeListeners = new ArrayList<TempoChangeListener>();
/**
* MidiLanes that are armed for recording
*/
private final HashSet<RecordableLane> recordingLanes = new HashSet<RecordableLane>();
private Vector<Vector<MultiEvent>> recordingTakes = new Vector<Vector<MultiEvent>>();
// The current recording take - will be added to recordingTake when recording is stopped, or in case of a loop - and provided that there are multievents in the take
private Vector<MultiEvent> currentRecordingTake = new Vector<MultiEvent>();
private RecordingDialog recordingTakeDialog = null;
/**
* Solo / mute
*/
private final HashSet<FrinikaTrackWrapper> soloFrinikaTrackWrappers = new HashSet<FrinikaTrackWrapper>();
//private final HashSet<FrinikaTrackWrapper> muteFrinikaTrackWrappers = new HashSet<FrinikaTrackWrapper>(); // Jens
//private final HashSet<FrinikaTrackWrapper> loopedFrinikaTrackWrappers = new HashSet<FrinikaTrackWrapper>(); // Jens
private final HashMap<FrinikaTrackWrapper, MidiPlayOptions> frinikaTrackWrappersMidiPlayOptions = new HashMap<FrinikaTrackWrapper, MidiPlayOptions>(); // Jens
// The default transmitter
private Transmitter transmitter;
HashMap<Integer,NoteEvent> pendingNoteEvents = new HashMap<Integer,NoteEvent>();
int lastLoopCount;
// The default receiver - with MultiEvent support
private Receiver receiver = new MonitorReceiver(getMidiMessageListeners(), new Receiver() // Jens
{
void addEventToRecordingTracks(ChannelEvent event)
{
currentRecordingTake.add(event);
}
public void send(MidiMessage message, long timeStamp) {
long tick = player.getRealTimeTickPosition();
// I think this should filter out all the stuff we are not interested in
// I've moved this to MonitorReceiver PJL
// if (message.getStatus() >= ShortMessage.MIDI_TIME_CODE) return;
// TODO insert diversion of midi event in here ... midiInFocus ?
// I think this should be done in the MidiInDeviceManager ?
for(RecordableLane rlane : recordingLanes)
{
if (!(rlane instanceof MidiLane)) continue;
MidiLane lane=(MidiLane)rlane;
MidiDevice dev=lane.getMidiDevice();
// Do not pass on this MIDI event to the device if this lane is muted
MidiPlayOptions po = lane.getPlayOptions();
if ( po.muted ) continue;
// If a device is not assigned ignore
if (dev == null) continue;
if(message instanceof ShortMessage)
{
ShortMessage shm = (ShortMessage) message;
try {
ShortMessage throughShm;
int cmd = shm.getCommand();
int chn = lane.getMidiChannel();
int dat1 = shm.getData1();
int dat2 = shm.getData2();
throughShm = new ShortMessage();
// apply some of the MidiPlayOptions (on direct monitor output, so it sounds like recorded and played back)
if ((cmd == ShortMessage.NOTE_ON) || (cmd == ShortMessage.NOTE_OFF)) {
dat1 = FrinikaSequencerPlayer.applyPlayOptionsNote(po, dat1);
dat2 = FrinikaSequencerPlayer.applyPlayOptionsVelocity(po, dat2);
throughShm.setMessage(cmd, chn, dat1, dat2);
dev.getReceiver().send(throughShm,-1); // midi-through of events on recording-armed tracks
} else {
throughShm.setMessage(cmd, chn, dat1, dat2);
dev.getReceiver().send(throughShm,-1); // midi-through of events on recording-armed tracks
System.out.println(shm.getData1() + " " + shm.getData2() + " " + throughShm.getData1() + " " + throughShm.getData2());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
if (true) return; // try out new recording scheme
if(recording)
{
if(player.getLoopCount()>lastLoopCount)
{
newRecordingTake();
lastLoopCount = player.getLoopCount();
}
// for(Transmitter trans : transmitters)
// trans.getReceiver().send(message,-1);
if(message instanceof ShortMessage)
{
ShortMessage shm = (ShortMessage)message;
if(shm.getCommand() == ShortMessage.NOTE_ON || shm.getCommand() == ShortMessage.NOTE_OFF)
{
//Note off
if(shm.getCommand() == ShortMessage.NOTE_OFF || shm.getData2()==0)
{
// Generate a note event
NoteEvent noteEvent = pendingNoteEvents.get(shm.getChannel() << 8 | shm.getData1());
if (noteEvent != null) {
noteEvent.setDuration(tick-noteEvent.getStartTick());
pendingNoteEvents.remove(shm.getChannel() << 8 | shm.getData1());
addEventToRecordingTracks(noteEvent);
}
}
else
{
//Note on
pendingNoteEvents.put(shm.getChannel() << 8 | shm.getData1(),
new NoteEvent((FrinikaTrackWrapper)null,tick,shm.getData1(),shm.getData2(),shm.getChannel(),0));
}
}
else if(shm.getCommand() == ShortMessage.CONTROL_CHANGE)
{
addEventToRecordingTracks(new ControllerEvent((FrinikaTrackWrapper)null,tick,shm.getData1(),shm.getData2()));
}
else if(shm.getCommand() == ShortMessage.PITCH_BEND)
{
addEventToRecordingTracks(new PitchBendEvent((FrinikaTrackWrapper)null,tick,((shm.getData1()) | (shm.getData2() << 7)) & 0x7fff));
}
}
/**
// The old recording code
long tick = player.getRealTimeTickPosition();
for(Transmitter trans : transmitters)
trans.getReceiver().send(message,-1);
sequence.getFrinikaTrackWrappers().get(0).add(
new MidiEvent(message,tick));
*/
}
}
public void close() {
}
});
public FrinikaSequencer()
{
receivers.add(receiver);
}
/**
* PJL added for Recording Manager
*
* @return
*/
public long getRealTimeTickPosition() {
return player.getRealTimeTickPosition();
}
public void setLoopEndPointInBeats(double d) {
long ticks = (long) (d * sequence.getResolution());
setLoopEndPoint(ticks);
}
public void setLoopStartPointInBeats(double d) {
long ticks = (long) (d * sequence.getResolution());
setLoopStartPoint(ticks);
}
public void setSequence(Sequence sequence) throws InvalidMidiDataException {
this.sequence = (FrinikaSequence)sequence;
}
public void setSequence(InputStream stream) throws IOException, InvalidMidiDataException {
// TODO Auto-generated method stub
}
public Sequence getSequence() {
return sequence;
}
public void start() {
player.setLatencyCompensationInMillis(FrinikaAudioSystem.getAudioServer().getOutputLatencyMillis());
for(SequencerListener listener : sequencerListeners)
listener.beforeStart();
player.start();
for(SequencerListener listener : sequencerListeners)
listener.start();
}
public void stop() {
if(recording)
{
newRecordingTake();
recording = false;
}
player.stop();
// Concurrent modification alert
ArrayList<SequencerListener> list = (ArrayList)sequencerListeners.clone();
for(SequencerListener listener : list)
listener.stop();
}
public boolean isRunning() {
return player.isRunning();
}
/**
* Add a new take for recording
*
*/
final void newRecordingTake()
{
if(currentRecordingTake.size()>0)
{
recordingTakes.add(currentRecordingTake);
if(recordingTakeDialog!=null)
{
recordingTakeDialog.notifyNewTake(recordingTakes.size()-1);
// If this is a loop then show the dialog
if(recordingTakes.size()>1)
recordingTakeDialog.setVisible(true);
}
}
currentRecordingTake = new Vector<MultiEvent>();
}
/**
* Get number of takes from the last recording
* @return
*/
public int getNumberOfTakes()
{
return recordingTakes.size();
}
/**
* Deploy one of the takes from the last recording to recording lanes
* @param takeNo - the take to deploy
*/
public void deployTake(int[] takeNumbers)
{
Vector<MidiPart> parts = new Vector<MidiPart>();
for(RecordableLane lane : recordingLanes)
{
Part part;
if (lane instanceof MidiLane) {
part = new MidiPart((MidiLane)lane);
parts.add((MidiPart)part);
}
if (lane instanceof AudioLane) {
try {
throw new Exception(" CAN NOT DEPLOY AUDIO YET");
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
for(Integer takeNo : takeNumbers)
{
for(MultiEvent event : recordingTakes.get(takeNo))
{
for(MidiPart part : parts)
{
try {
part.add((MultiEvent)event.clone());
} catch (CloneNotSupportedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
for(MidiPart part : parts)
part.setBoundsFromEvents();
recordingTakes.clear();
if(recordingTakeDialog!=null)
{
recordingTakeDialog.dispose();
recordingTakeDialog = null;
}
}
/**
* Return the MultiEvents of a recording take
* @param takeNo
* @return
*/
public Vector<MultiEvent> getRecordingTake(int takeNo)
{
return recordingTakes.get(takeNo);
}
public void startRecording() {
pendingNoteEvents.clear();
lastLoopCount = 0;
recording = true;
// player.setLatencyCompensationInMillis(FrinikaAudioSystem.getOutputLatencyMillis());
start();
}
public void stopRecording() {
stop();
}
public boolean isRecording() {
return recording;
}
/**
* @deprecated Use recordEnable on MidiLane instead
*/
public void recordEnable(Track track, int channel) {
// TODO Auto-generated method stub
}
public void recordEnable(RecordableLane lane) {
recordingLanes.add(lane);
}
/**
* @deprecated Use recordEnable on MidiLane instead
*/
public void recordDisable(Track track) {
// TODO Auto-generated method stub
}
public boolean isRecording(RecordableLane lane) {
return recordingLanes.contains(lane);
}
public void recordDisable(RecordableLane lane) {
recordingLanes.remove(lane);
}
public float getTempoInBPM() {
return bpm;
}
public void setTempoInBPM(float bpm) {
this.bpm = bpm;
notifyTempoChangeListeners();
}
public float getTempoInMPQ() {
// TODO Auto-generated method stub
return 0;
}
public void setTempoInMPQ(float mpq) {
// TODO Auto-generated method stub
}
public void setTempoFactor(float factor) {
// TODO Auto-generated method stub
}
public float getTempoFactor() {
// TODO Auto-generated method stub
return 0;
}
public long getTickLength() {
// TODO Auto-generated method stub
return 0;
}
public long getTicksLooped() {
return player.getTicksLooped();
}
public long getTickPosition() {
return player.getTickPosition();
}
public void setTickPosition(long tick) {
player.setTickPosition(tick);
}
public long getMicrosecondLength() {
// TODO Auto-generated method stub
return 0;
}
public long getMicrosecondPosition() {
return player.getMicroSecondPosition();
}
public void setMicrosecondPosition(long microseconds) {
// TODO Auto-generated method stub
}
public void setMasterSyncMode(SyncMode sync) {
// TODO Auto-generated method stub
}
public SyncMode getMasterSyncMode() {
// TODO Auto-generated method stub
return null;
}
public SyncMode[] getMasterSyncModes() {
// TODO Auto-generated method stub
return null;
}
public void setSlaveSyncMode(SyncMode sync) {
// TODO Auto-generated method stub
}
public SyncMode getSlaveSyncMode() {
// TODO Auto-generated method stub
return null;
}
public SyncMode[] getSlaveSyncModes() {
// TODO Auto-generated method stub
return null;
}
public void setTrackMute(int track, boolean mute) {
// TODO Auto-generated method stub
}
public boolean getTrackMute(int track) {
// TODO Auto-generated method stub
return false;
}
public void setTrackSolo(int track, boolean solo) {
// TODO Auto-generated method stub
}
public boolean getTrackSolo(int track) {
// TODO Auto-generated method stub
return false;
}
public boolean addMetaEventListener(MetaEventListener listener) {
// TODO Auto-generated method stub
return false;
}
public void removeMetaEventListener(MetaEventListener listener) {
// TODO Auto-generated method stub
}
public int[] addControllerEventListener(ControllerEventListener listener, int[] controllers) {
// TODO Auto-generated method stub
return null;
}
public int[] removeControllerEventListener(ControllerEventListener listener, int[] controllers) {
// TODO Auto-generated method stub
return null;
}
public void setLoopStartPoint(long tick) {
this.loopStartPoint = tick;
// notify GUI stuff. TODO is this the right way to do this or do we need another type of listener ?
notifySongPositionListeners(player.getTickPosition());
}
public long getLoopStartPoint() {
return loopStartPoint;
}
public void setLoopEndPoint(long tick) {
this.loopEndPoint = tick;
// See setLoopStart
notifySongPositionListeners(player.getTickPosition());
}
public long getLoopEndPoint() {
return loopEndPoint;
}
public void setLoopCount(int count) {
this.loopCount = count;
}
public int getLoopCount() {
return loopCount;
}
public Info getDeviceInfo() {
// TODO Auto-generated method stub
return null;
}
public void open() throws MidiUnavailableException {
// TODO Auto-generated method stub
}
public void close() {
stop();
}
public boolean isOpen() {
// TODO Auto-generated method stub
return false;
}
public int getMaxReceivers() {
// TODO Auto-generated method stub
return 0;
}
public int getMaxTransmitters() {
// TODO Auto-generated method stub
return 0;
}
public Receiver getReceiver() throws MidiUnavailableException {
return receiver;
}
public List<Receiver> getReceivers() {
return receivers;
}
public Transmitter getTransmitter() throws MidiUnavailableException {
return transmitter;
}
public List<Transmitter> getTransmitters() {
return transmitters;
}
/**
* Register a MidiOutDevice to this sequencer. It's also added to the list of transmitters.
* @param midiDevice
* @throws MidiUnavailableException
*/
public void addMidiOutDevice(final MidiDevice midiDevice) throws MidiUnavailableException
{
Transmitter transmitter = new Transmitter() {
Receiver receiver = midiDevice.getReceiver();
public void setReceiver(Receiver receiver) {
this.receiver = receiver;
}
public Receiver getReceiver() {
return receiver;
}
public void close() {
}
};
midiOutDeviceTransmitters.put(midiDevice,transmitter);
midiOutDevices.add(midiDevice);
transmitters.add(transmitter);
}
/**
* Deregister a midi out device from this sequencer. Also removed from the list of transmitters
* @param midiDevice
*/
public void removeMidiOutDevice(MidiDevice midiDevice)
{
transmitters.remove(midiOutDeviceTransmitters.get(midiDevice));
midiOutDeviceTransmitters.remove(midiDevice);
midiOutDevices.remove(midiDevice);
}
public List<MidiDevice> listMidiOutDevices()
{
return midiOutDevices;
}
public void addSequencerListener(SequencerListener sequencerListener)
{
sequencerListeners.add(sequencerListener);
}
public void removeSequencerListener(SequencerListener sequencerListener)
{
sequencerListeners.remove(sequencerListener);
}
/**
* Add a song position listener to the sequencer.
* See the SongPositionListener javadoc.
* @param songPositionListener
*/
public void addSongPositionListener(SongPositionListener songPositionListener)
{
if(songPositionListener.requiresNotificationOnEachTick())
intenseSongPositionListeners.add(songPositionListener);
else
songPositionListeners.add(songPositionListener);
}
/**
* Remove a song position listener from the sequencer
* @param songPositionListener
*/
public void removeSongPositionListener(SongPositionListener songPositionListener)
{
if(songPositionListener.requiresNotificationOnEachTick())
intenseSongPositionListeners.remove(songPositionListener);
else
songPositionListeners.remove(songPositionListener);
}
final public void notifySongPositionListeners() {
notifySongPositionListeners(getTickPosition());
}
//PJL
/**
* Add a tempo change listener to the sequencer.
* This is notified when we play a tempo change event.
* @param listener
*/
public void addTempoChangeListener(TempoChangeListener listener)
{
tempoChangeListeners.add(listener);
}
/**
* Remove a tempo change position listener from the sequencer
* @param listener
*/
public void removeTempoChangeListener(TempoChangeListener listener)
{
tempoChangeListeners.remove(listener);
}
final void notifyTempoChangeListeners() {
for(TempoChangeListener listener : tempoChangeListeners)
listener.notifyTempoChange(bpm);
}
/**
* Notify songPositionListeners that requires to be notified on each tick
* @param tick
*/
final void notifyIntenseSongPositionListeners(long tick)
{
for(SongPositionListener listener : intenseSongPositionListeners)
listener.notifyTickPosition(tick);
}
final void notifyAllSongPositionListeners(long tick)
{
notifyIntenseSongPositionListeners(tick);
notifySongPositionListeners(tick);
}
final void notifySongPositionListeners(long tick)
{
for(SongPositionListener listener : songPositionListeners)
listener.notifyTickPosition(tick);
}
public Collection<FrinikaTrackWrapper> getSoloFrinikaTrackWrappers() {
return soloFrinikaTrackWrappers;
}
// Jens: now via getPlayOptions().mute
/*Collection<FrinikaTrackWrapper> getMuteFrinikaTrackWrappers() {
return muteFrinikaTrackWrappers;
}
public void setMute(MidiLane lane, boolean mute)
{
if(mute)
muteFrinikaTrackWrappers.add(lane.getTrack());
else
muteFrinikaTrackWrappers.remove(lane.getTrack());
}*/
public void setSolo(MidiLane lane, boolean solo)
{
if(solo)
soloFrinikaTrackWrappers.add(lane.getTrack());
else
soloFrinikaTrackWrappers.remove(lane.getTrack());
}
public void setRecordingTakeDialog(RecordingDialog dialog) {
this.recordingTakeDialog = dialog;
}
/**
* Send noteOff and reset all controllers to zero
*
*/
public void panic()
{
player.notesOff(true);
byte []data={(byte) 0xF0,0x43,0x10,0x4C,0x00,0x00,0x7F,0x00,(byte) 0xF7};
SysexMessage mess=new SysexMessage();
try {
mess.setMessage(data,data.length);
} catch (InvalidMidiDataException e1) {
e1.printStackTrace();
}
for (MidiDevice midiDev : listMidiOutDevices()) {
if (midiDev instanceof SynthRack) {
// TODO ?
} else {
try {
midiDev.getReceiver().send(mess, -1);
} catch (MidiUnavailableException e1) {
e1.printStackTrace();
}
}
}
}
// Jens: now via getPlayOptions().mute
/*public boolean isMute(RecordableLane lane) {
return muteFrinikaTrackWrappers.contains(((MidiLane)lane).getTrack());
}*/
public boolean isSolo(RecordableLane lane) {
return soloFrinikaTrackWrappers.contains(((MidiLane)lane).getTrack());
}
public MidiPlayOptions getPlayOptions(FrinikaTrackWrapper track) {
MidiPlayOptions opt = frinikaTrackWrappersMidiPlayOptions.get(track);
if(opt == null) return new MidiPlayOptions(); // if opt null, return a default one
return opt;
}
public void setPlayOptions(FrinikaTrackWrapper track, MidiPlayOptions opt) {
frinikaTrackWrappersMidiPlayOptions.put(track, opt);
}
public void addMidiMessageListener(MidiMessageListener l) {
getMidiMessageListeners().add(l);
}
public void removeMidiMessageListener(MidiMessageListener l) {
getMidiMessageListeners().remove(l);
}
/**
* Manually send a midi message using the channel/device settings of a FrinikaTrackWrapper
* This is also used by wavexport
* @param msg
* @param trackWrapper
* @throws InvalidMidiDataException
* @throws MidiUnavailableException
*/
public void sendMidiMessage(MidiMessage msg,FrinikaTrackWrapper trackWrapper) throws InvalidMidiDataException, MidiUnavailableException
{
player.sendMidiMessage(msg,trackWrapper);
}
/**
* Set whether to play in realtime or if rendering (e.g. export wav)
* @param realtime
*/
public void setRealtime(boolean realtime)
{
player.setRealtime(realtime);
}
/**
* Returns whether to play in realtime or if rendering (e.g. export wav)
*/
public boolean getRealtime()
{
return player.getRealtime();
}
/**
* If the player is not in realtime mode you can manually trigger the next tick here
*
*/
public void nonRealtimeNextTick()
{
if(!player.getRealtime())
player.timerEvent();
}
/**
* set the tempolist
*
* @param tl
*/
public void setTempoList(TempoList tl) {
player.setTempoList(tl);
}
/**
*
* set priority of the player
*
*/
public void setPlayerPriority(int prio) {
player.priorityRequested=prio;
}
public Collection<MidiMessageListener> getMidiMessageListeners() {
return midiMessageListeners;
}
}