package org.herac.tuxguitar.player.base; import java.util.ArrayList; import java.util.List; import javax.sound.midi.MidiUnavailableException; import org.apache.log4j.Logger; import org.herac.tuxguitar.song.managers.TGSongManager; import org.herac.tuxguitar.song.models.TGChannel; import org.herac.tuxguitar.song.models.TGDuration; import org.herac.tuxguitar.song.models.TGMeasureHeader; import org.herac.tuxguitar.song.models.TGNote; import org.herac.tuxguitar.song.models.TGString; import org.herac.tuxguitar.song.models.TGTrack; import org.herac.tuxguitar.util.TGLock; public class MidiPlayer { /** The Logger for this class. */ public static final transient Logger LOG = Logger .getLogger(MidiPlayer.class); private static final int MAX_CHANNELS = 16; public static final int MAX_VOLUME = 10; private static final int TIMER_DELAY = 10; private boolean anySolo; private boolean changeTickPosition; private int infoTrack; private List<MidiPlayerListener> listeners; protected TGLock lock = new TGLock(); private int loopEHeader; private int loopSHeader; private long loopSPosition; private boolean metronomeEnabled; private int metronomeTrack; private MidiPlayerMode mode; private MidiOutputPort outputPort; private String outputPortKey; private List<MidiOutputPortProvider> outputPortProviders; private MidiTransmitter outputTransmitter; private boolean paused; private boolean running; private MidiSequencer sequencer; private String sequencerKey; private List<MidiSequencerProvider> sequencerProviders; private TGSongManager songManager; protected boolean starting; protected long tickLength; protected long tickPosition; private int volume = MAX_VOLUME; public void addListener(MidiPlayerListener listener) { if (!this.listeners.contains(listener)) { this.listeners.add(listener); } } public void addOutputPortProvider(MidiOutputPortProvider provider) throws MidiPlayerException { this.addOutputPortProvider(provider, false); } public void addOutputPortProvider(MidiOutputPortProvider provider, boolean tryFirst) throws MidiPlayerException { this.outputPortProviders.add(provider); this.openOutputPort(provider.listPorts(), tryFirst); } /** * Agrega la Secuencia * * */ public void addSequence() { try { MidiSequenceParser parser = new MidiSequenceParser(this.songManager, MidiSequenceParser.DEFAULT_PLAY_FLAGS, getMode().getCurrentPercent(), 0); MidiSequenceHandler sequence = getSequencer().createSequence( this.songManager.getSong().countTracks() + 2); parser.setSHeader(getLoopSHeader()); parser.setEHeader(getLoopEHeader()); parser.parse(sequence); this.infoTrack = parser.getInfoTrack(); this.metronomeTrack = parser.getMetronomeTrack(); } catch (MidiPlayerException e) { LOG.error(e); } } public void addSequencerProvider(MidiSequencerProvider provider) throws MidiPlayerException { this.addSequencerProvider(provider, false); } public void addSequencerProvider(MidiSequencerProvider provider, boolean tryFirst) throws MidiPlayerException { this.sequencerProviders.add(provider); this.openSequencer(provider.listSequencers(), tryFirst); } private void afterUpdate() { try { getSequencer().setSolo(this.infoTrack, this.anySolo); getSequencer().setSolo(this.metronomeTrack, (isMetronomeEnabled() && this.anySolo)); } catch (MidiPlayerException e) { LOG.error(e); } } protected void changeTickPosition() { try { if (isRunning()) { if (this.tickPosition < this.getLoopSPosition()) { this.tickPosition = this.getLoopSPosition(); } this.getSequencer().setTickPosition(this.tickPosition); } setChangeTickPosition(false); } catch (MidiPlayerException e) { LOG.error(e); } } public void checkDevices() throws Throwable { this.getSequencer().check(); if (this.getOutputPort() != null) { this.getOutputPort().check(); } } /** * Cierra el Secuenciador y Sintetizador * * */ public void close() { try { this.closeSequencer(); this.closeOutputPort(); } catch (MidiPlayerException e) { LOG.error(e); } } public void closeOutputPort() { try { if (this.isRunning()) { this.stop(); } this.lock.lock(); if (this.outputPort != null) { this.getOutputTransmitter().removeReceiver(this.outputPort.getKey()); this.outputPort.close(); this.outputPort = null; } this.lock.unlock(); } catch (Throwable throwable) { LOG.error(throwable); } } public void closeSequencer() throws MidiPlayerException { try { if (this.isRunning()) { this.stop(); } this.lock.lock(); if (this.sequencer != null) { this.sequencer.close(); this.sequencer = null; } this.lock.unlock(); } catch (Throwable throwable) { throw new MidiPlayerException(throwable.getMessage(), throwable); } } protected void finish() { try { if (this.getMode().isLoop()) { this.setStarting(true); this.reset(); this.getMode().notifyLoop(); this.notifyLoop(); this.play(); return; } this.reset(); } catch (MidiPlayerException e) { LOG.error(e); } } /** * Retorna una lista de instrumentos */ public MidiInstrument[] getInstruments() { return MidiInstrument.INSTRUMENT_LIST; } public int getLoopEHeader() { return this.loopEHeader; } public int getLoopSHeader() { return this.loopSHeader; } public long getLoopSPosition() { return this.loopSPosition; } public MidiPlayerMode getMode() { if (this.mode == null) { this.mode = new MidiPlayerMode(); } return this.mode; } /** * Retorna el Puerto Midi */ public MidiOutputPort getOutputPort() { return this.outputPort; } public MidiTransmitter getOutputTransmitter() { if (this.outputTransmitter == null) { this.outputTransmitter = new MidiTransmitter(); } return this.outputTransmitter; } /** * Retorna una lista de instrumentos */ public MidiPercussion[] getPercussions() { return MidiPercussion.PERCUSSION_LIST; } /** * Retorna el Sequenciador */ public MidiSequencer getSequencer() { if (this.sequencer == null) { this.sequencer = new MidiSequencerEmpty(); } return this.sequencer; } /** * Retorna el tick de la nota que esta reproduciendo */ public long getTickPosition() { return this.tickPosition; } public int getVolume() { return this.volume; } /** * Inicia el Secuenciador y Sintetizador * * */ public void init(TGSongManager songManager) { this.songManager = songManager; this.outputPortProviders = new ArrayList<MidiOutputPortProvider>(); this.sequencerProviders = new ArrayList<MidiSequencerProvider>(); this.listeners = new ArrayList<MidiPlayerListener>(); this.getSequencer(); this.getMode(); this.reset(); } /** * Retorna True si hay cambios en la posicion */ protected boolean isChangeTickPosition() { return this.changeTickPosition; } public boolean isMetronomeEnabled() { return this.metronomeEnabled; } public boolean isOutputPortOpen(String key) { if (key != null && getOutputPort() != null) { String currentKey = getOutputPort().getKey(); if (currentKey == null) { return false; } return currentKey.equals(key); } return false; } public boolean isPaused() { return this.paused; } /** * Retorna True si esta reproduciendo */ public boolean isRunning() { try { return (this.running || this.getSequencer().isRunning() || this .isStarting()); } catch (MidiPlayerException e) { LOG.error(e); } return false; } public boolean isSequencerOpen(String key) { if (key != null) { String currentKey = getSequencer().getKey(); if (currentKey == null) { return false; } return currentKey.equals(key); } return false; } protected boolean isStarting() { return this.starting; } public List<MidiOutputPort> listOutputPorts() { List<MidiOutputPort> ports = new ArrayList<MidiOutputPort>(); for (final MidiOutputPortProvider provider : this.outputPortProviders) { try { ports.addAll(provider.listPorts()); } catch (Throwable throwable) { LOG.error(throwable); } } return ports; } public List<MidiSequencer> listSequencers() { List<MidiSequencer> sequencers = new ArrayList<MidiSequencer>(); for (final MidiSequencerProvider provider : this.sequencerProviders) { try { sequencers.addAll(provider.listSequencers()); } catch (Throwable throwable) { LOG.error(throwable); } } return sequencers; } public boolean loadOutputPort(MidiOutputPort port) { try { this.closeOutputPort(); this.outputPort = port; this.outputPort.open(); this.getOutputTransmitter().addReceiver(this.outputPort.getKey(), this.outputPort.getReceiver()); } catch (Throwable throwable) { this.outputPort = null; return false; } return true; } public boolean loadSequencer(MidiSequencer sequencer) { try { this.closeSequencer(); this.sequencer = sequencer; this.sequencer.open(); this.sequencer.setTransmitter(getOutputTransmitter()); } catch (Throwable throwable) { this.sequencer = null; return false; } return true; } public void notifyLoop() { for (final MidiPlayerListener listener : this.listeners) { listener.notifyLoop(); } } public void notifyStarted() { for (final MidiPlayerListener listener : this.listeners) { listener.notifyStarted(); } } public void notifyStopped() { for (final MidiPlayerListener listener : this.listeners) { listener.notifyStopped(); } } public void openOutputPort(List<MidiOutputPort> ports, boolean tryFirst) { try { if (this.outputPortKey != null && !this.isOutputPortOpen(this.outputPortKey)) { this.closeOutputPort(); for (int i = 0; i < ports.size(); i++) { MidiOutputPort port = (MidiOutputPort) ports.get(i); if (port.getKey().equals(this.outputPortKey)) { if (this.loadOutputPort(port)) { return; } } } } if (getOutputPort() == null && !ports.isEmpty() && tryFirst) { this.loadOutputPort((MidiOutputPort) ports.get(0)); } } catch (Throwable throwable) { LOG.error(throwable); } } public void openOutputPort(String key) { this.openOutputPort(key, false); } public void openOutputPort(String key, boolean tryFirst) { this.outputPortKey = key; this.openOutputPort(listOutputPorts(), tryFirst); } public void openSequencer(List<MidiSequencer> sequencers, boolean tryFirst) throws MidiPlayerException { try { if (this.sequencerKey != null && !this.isSequencerOpen(this.sequencerKey)) { this.closeSequencer(); for (int i = 0; i < sequencers.size(); i++) { MidiSequencer sequencer = (MidiSequencer) sequencers.get(i); if (sequencer.getKey().equals(this.sequencerKey)) { if (this.loadSequencer(sequencer)) { return; } } } } if (getSequencer() instanceof MidiSequencerEmpty && !sequencers.isEmpty() && tryFirst) { this.loadSequencer((MidiSequencer) sequencers.get(0)); } } catch (Throwable throwable) { throw new MidiPlayerException(throwable.getMessage(), throwable); } } public void openSequencer(String key) { this.openSequencer(key, false); } public void openSequencer(String key, boolean tryFirst) { try { this.sequencerKey = key; this.openSequencer(listSequencers(), tryFirst); } catch (Throwable throwable) { LOG.error(throwable); } } public void pause() { this.stop(true); } /** * Inicia la reproduccion * * @throws MidiPlayerException * @throws MidiUnavailableException */ public synchronized void play() throws MidiPlayerException { try { final boolean notifyStarted = (!this.isRunning()); this.setStarting(true); this.stop(); this.lock.lock(); this.checkDevices(); this.updateLoop(true); this.systemReset(); this.addSequence(); this.updatePrograms(); this.updateControllers(); this.updateDefaultControllers(); this.setMetronomeEnabled(isMetronomeEnabled()); this.changeTickPosition(); this.setRunning(true); this.getSequencer().start(); new Thread(new Runnable() { public synchronized void run() { try { MidiPlayer.this.lock.lock(); MidiPlayer.this.setStarting(false); if (notifyStarted) { MidiPlayer.this.notifyStarted(); } MidiPlayer.this.tickLength = getSequencer().getTickLength(); MidiPlayer.this.tickPosition = getSequencer().getTickPosition(); Object sequencerLock = new Object(); while (getSequencer().isRunning() && isRunning()) { synchronized (sequencerLock) { if (isChangeTickPosition()) { changeTickPosition(); } MidiPlayer.this.tickPosition = getSequencer().getTickPosition(); sequencerLock.wait(TIMER_DELAY); } } // FINISH if (isRunning()) { if (MidiPlayer.this.tickPosition >= (MidiPlayer.this.tickLength - (TGDuration.QUARTER_TIME / 2))) { finish(); } else { stop(isPaused()); } } if (!isRunning()) { MidiPlayer.this.notifyStopped(); } } catch (Throwable throwable) { setStarting(false); reset(); LOG.error(throwable); } finally { MidiPlayer.this.lock.unlock(); } } }).start(); } catch (Throwable throwable) { this.setStarting(false); this.reset(); throw new MidiPlayerException(throwable.getMessage(), throwable); } finally { this.lock.unlock(); } } public void playBeat(int channel, int program, int volume, int balance, int chorus, int reverb, int phaser, int tremolo, int[][] beat) { playBeat(channel, program, volume, balance, chorus, reverb, phaser, tremolo, beat, 500, 0); } public void playBeat(int channel, int program, int volume, int balance, int chorus, int reverb, int phaser, int tremolo, int[][] beat, long duration, int interval) { try { getOutputTransmitter().sendProgramChange(channel, program); getOutputTransmitter().sendControlChange(channel, MidiControllers.VOLUME, volume); getOutputTransmitter().sendControlChange(channel, MidiControllers.BALANCE, balance); getOutputTransmitter().sendControlChange(channel, MidiControllers.CHORUS, chorus); getOutputTransmitter().sendControlChange(channel, MidiControllers.REVERB, reverb); getOutputTransmitter().sendControlChange(channel, MidiControllers.PHASER, phaser); getOutputTransmitter().sendControlChange(channel, MidiControllers.TREMOLO, tremolo); for (int i = 0; i < beat.length; i++) { getOutputTransmitter().sendNoteOn(channel, beat[i][0], beat[i][1]); if (interval > 0) { Thread.sleep(interval); } } Thread.sleep(duration); for (int i = 0; i < beat.length; i++) { getOutputTransmitter().sendNoteOff(channel, beat[i][0], beat[i][1]); } } catch (Throwable throwable) { LOG.error(throwable); } } public void playBeat(final TGTrack track, final List<TGNote> notes) { int channel = track.getChannel().getChannel(); int program = track.getChannel().getInstrument(); int volume = (int) ((this.getVolume() / 10.00) * track.getChannel() .getVolume()); int balance = track.getChannel().getBalance(); int chorus = track.getChannel().getChorus(); int reverb = track.getChannel().getReverb(); int phaser = track.getChannel().getPhaser(); int tremolo = track.getChannel().getTremolo(); int size = notes.size(); int[][] beat = new int[size][2]; for (int i = 0; i < size; i++) { TGNote note = (TGNote) notes.get(i); beat[i][0] = track.getOffset() + (note.getValue() + ((TGString) track.getStrings().get( note.getString() - 1)).getValue()); beat[i][1] = note.getVelocity(); } playBeat(channel, program, volume, balance, chorus, reverb, phaser, tremolo, beat); } public void removeListener(MidiPlayerListener listener) { if (this.listeners.contains(listener)) { this.listeners.remove(listener); } } public void removeOutputPortProvider(MidiOutputPortProvider provider) throws MidiPlayerException { this.outputPortProviders.remove(provider); MidiOutputPort current = getOutputPort(); if (current != null) { for (final MidiOutputPort port : provider.listPorts()) { if (port.getKey().equals(current.getKey())) { closeOutputPort(); break; } } } } public void removeSequencerProvider(MidiSequencerProvider provider) throws MidiPlayerException { this.sequencerProviders.remove(provider); MidiSequencer current = getSequencer(); if (!(current instanceof MidiSequencerEmpty) && current != null) { for (final MidiSequencer sequencer : provider.listSequencers()) { if (current.getKey().equals(sequencer.getKey())) { closeSequencer(); break; } } } } /** * Resetea los valores */ public void reset() { this.stop(); this.lock.lock(); this.tickPosition = TGDuration.QUARTER_TIME; this.setChangeTickPosition(false); this.lock.unlock(); } /** * Asigna los cambios de la posicion */ private void setChangeTickPosition(boolean changeTickPosition) { this.changeTickPosition = changeTickPosition; } public void setMetronomeEnabled(boolean metronomeEnabled) { try { this.metronomeEnabled = metronomeEnabled; this.getSequencer().setMute(this.metronomeTrack, !isMetronomeEnabled()); this.getSequencer().setSolo(this.metronomeTrack, (isMetronomeEnabled() && this.anySolo)); } catch (MidiPlayerException e) { LOG.error(e); } } public void setPaused(boolean paused) { this.paused = paused; } /** * Asigna el valor a running */ public void setRunning(boolean running) { this.running = running; } protected void setStarting(boolean starting) { this.starting = starting; } /** * Indica la posicion del secuenciador * * @throws MidiUnavailableException */ public void setTickPosition(long position) { this.tickPosition = position; this.setChangeTickPosition(true); } public void setVolume(int volume) { this.volume = volume; if (this.isRunning()) { this.updateControllers(); } } /** * Para la reproduccion * * @throws MidiUnavailableException */ public void stop() { this.stop(false); } /** * Para la reproduccion * * @throws MidiUnavailableException */ public void stop(boolean paused) { try { this.setPaused(paused); if (this.isRunning()) { this.getSequencer().stop(); } this.setRunning(false); } catch (MidiPlayerException e) { LOG.error(e); } } public void systemReset() { try { this.getOutputTransmitter().sendSystemReset(); } catch (MidiPlayerException e) { LOG.error(e); } } private void updateController(int channel, int volume, int balance, int chorus, int reverb, int phaser, int tremolo, int expression) { try { getOutputTransmitter().sendControlChange(channel, MidiControllers.VOLUME, volume); getOutputTransmitter().sendControlChange(channel, MidiControllers.BALANCE, balance); getOutputTransmitter().sendControlChange(channel, MidiControllers.CHORUS, chorus); getOutputTransmitter().sendControlChange(channel, MidiControllers.REVERB, reverb); getOutputTransmitter().sendControlChange(channel, MidiControllers.PHASER, phaser); getOutputTransmitter().sendControlChange(channel, MidiControllers.TREMOLO, tremolo); getOutputTransmitter().sendControlChange(channel, MidiControllers.EXPRESSION, expression); } catch (MidiPlayerException e) { LOG.error(e); } } private void updateController(TGTrack track) { try { int volume = (int) ((this.getVolume() / 10.00) * track.getChannel() .getVolume()); int balance = track.getChannel().getBalance(); int chorus = track.getChannel().getChorus(); int reverb = track.getChannel().getReverb(); int phaser = track.getChannel().getPhaser(); int tremolo = track.getChannel().getTremolo(); updateController(track.getChannel().getChannel(), volume, balance, chorus, reverb, phaser, tremolo, 127); if (track.getChannel().getChannel() != track.getChannel() .getEffectChannel()) { updateController(track.getChannel().getEffectChannel(), volume, balance, chorus, reverb, phaser, tremolo, 127); } getSequencer().setMute(track.getNumber(), track.isMute()); getSequencer().setSolo(track.getNumber(), track.isSolo()); } catch (MidiPlayerException e) { LOG.error(e); } } public void updateControllers() { this.anySolo = false; boolean percussionUpdated = false; for (final TGTrack track : this.songManager.getSong().getTracks()) { this.updateController(track); this.anySolo = ((!this.anySolo) ? track.isSolo() : this.anySolo); percussionUpdated = (percussionUpdated || track.isPercussionTrack()); } if (!percussionUpdated && isMetronomeEnabled()) { int volume = (int) ((this.getVolume() / 10.00) * TGChannel.DEFAULT_VOLUME); int balance = TGChannel.DEFAULT_BALANCE; int chorus = TGChannel.DEFAULT_CHORUS; int reverb = TGChannel.DEFAULT_REVERB; int phaser = TGChannel.DEFAULT_PHASER; int tremolo = TGChannel.DEFAULT_TREMOLO; updateController(9, volume, balance, chorus, reverb, phaser, tremolo, 127); } this.afterUpdate(); } private void updateDefaultControllers() { try { for (int channel = 0; channel < MAX_CHANNELS; channel++) { getOutputTransmitter().sendControlChange(channel, MidiControllers.RPN_MSB, 0); getOutputTransmitter().sendControlChange(channel, MidiControllers.RPN_LSB, 0); getOutputTransmitter().sendControlChange(channel, MidiControllers.DATA_ENTRY_MSB, 12); getOutputTransmitter().sendControlChange(channel, MidiControllers.DATA_ENTRY_LSB, 0); } } catch (MidiPlayerException e) { LOG.error(e); } } public void updateLoop(boolean force) { if (force || !this.isRunning()) { this.loopSHeader = -1; this.loopEHeader = -1; this.loopSPosition = TGDuration.QUARTER_TIME; if (getMode().isLoop()) { int hCount = this.songManager.getSong().countMeasureHeaders(); this.loopSHeader = (getMode().getLoopSHeader() <= hCount ? getMode() .getLoopSHeader() : -1); this.loopEHeader = (getMode().getLoopEHeader() <= hCount ? getMode() .getLoopEHeader() : -1); if (this.loopSHeader > 0 && this.loopSHeader <= hCount) { TGMeasureHeader header = this.songManager .getMeasureHeader(this.loopSHeader); if (header != null) { this.loopSPosition = header.getStart(); } } } } } public void updatePrograms() { try { for (final TGTrack track : this.songManager.getSong().getTracks()) { getOutputTransmitter() .sendProgramChange(track.getChannel().getChannel(), track.getChannel().getInstrument()); if (track.getChannel().getChannel() != track.getChannel() .getEffectChannel()) { getOutputTransmitter().sendProgramChange( track.getChannel().getEffectChannel(), track.getChannel().getInstrument()); } } } catch (MidiPlayerException e) { LOG.error(e); } } }