package org.herac.tuxguitar.player.base; import java.util.ArrayList; import java.util.Iterator; import java.util.List; 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.TGNote; import org.herac.tuxguitar.song.models.TGString; import org.herac.tuxguitar.song.models.TGTrack; import org.herac.tuxguitar.util.TGLock; public class MidiPlayer{ private static final int MAX_CHANNELS = 16; public static final int MAX_VOLUME = 10; private static final int TIMER_DELAY = 10; private TGSongManager songManager; private MidiSequencer sequencer; private MidiTransmitter outputTransmitter; private MidiOutputPort outputPort; private MidiPlayerMode mode; private String sequencerKey; private String outputPortKey; private List outputPortProviders; private List sequencerProviders; private int volume; private boolean running; private boolean paused; private boolean changeTickPosition; private boolean metronomeEnabled; private int metronomeTrack; private int infoTrack; private boolean anySolo; protected long tickLength; protected long tickPosition; protected boolean starting; protected TGLock lock = new TGLock(); public MidiPlayer() { this.lock = new TGLock(); this.volume = MAX_VOLUME; } /** * Inicia el Secuenciador y Sintetizador * @throws MidiUnavailableException */ public void init(TGSongManager songManager) { this.songManager = songManager; this.outputPortProviders = new ArrayList(); this.sequencerProviders = new ArrayList(); this.getSequencer(); this.getMode(); this.reset(); } /** * Retorna una lista de instrumentos */ public MidiInstrument[] getInstruments(){ return MidiInstrument.INSTRUMENT_LIST; } /** * Retorna una lista de instrumentos */ public MidiPercussion[] getPercussions(){ return MidiPercussion.PERCUSSION_LIST; } /** * Resetea los valores */ public void reset(){ this.stop(); this.lock.lock(); this.tickPosition = TGDuration.QUARTER_TIME; this.setChangeTickPosition(false); this.lock.unlock(); } /** * Cierra el Secuenciador y Sintetizador * @throws MidiUnavailableException */ public void close(){ try { this.closeSequencer(); this.closeOutputPort(); } catch (MidiPlayerException e) { e.printStackTrace(); } } /** * 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) { e.printStackTrace(); } } /** * Para la reproduccion * @throws MidiUnavailableException */ public void stop() { this.stop(false); } public void pause(){ this.stop(true); } /** * Inicia la reproduccion * @throws MidiPlayerException * @throws MidiUnavailableException */ public synchronized void play() throws MidiPlayerException{ try { this.setStarting(true); this.stop(); this.lock.lock(); this.checkOutput(); this.systemReset(); this.addSecuence(); 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(); setStarting(false); 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()); } } }catch (Throwable throwable) { setStarting(false); reset(); throwable.printStackTrace(); }finally{ MidiPlayer.this.lock.unlock(); } } }).start(); }catch (Throwable throwable) { this.setStarting(false); this.reset(); throw new MidiPlayerException(throwable.getMessage(),throwable); }finally{ this.lock.unlock(); } } protected void finish(){ try { if(this.getMode().isLoop()){ this.setStarting(true); this.reset(); this.getMode().notifyLoop(); this.play(); return; } this.reset(); } catch (MidiPlayerException e) { e.printStackTrace(); } } public void checkOutput() throws Throwable { if( getOutputPort() != null ){ getOutputPort().check(); } } public int getVolume() { return this.volume; } public void setVolume(int volume) { this.volume = volume; if (this.isRunning()) { this.updateControllers(); } } protected boolean isStarting() { return this.starting; } protected void setStarting(boolean starting) { this.starting = starting; } /** * Asigna el valor a running */ public void setRunning(boolean running) { this.running = running; } /** * Retorna True si esta reproduciendo */ public boolean isRunning() { try { return (this.running || this.getSequencer().isRunning() || this.isStarting()); } catch (MidiPlayerException e) { e.printStackTrace(); } return false; } public boolean isPaused() { return this.paused; } public void setPaused(boolean paused) { this.paused = paused; } /** * Retorna True si hay cambios en la posicion */ protected boolean isChangeTickPosition() { return this.changeTickPosition; } /** * Asigna los cambios de la posicion */ private void setChangeTickPosition(boolean changeTickPosition) { this.changeTickPosition = changeTickPosition; } /** * Indica la posicion del secuenciador * @throws MidiUnavailableException */ public void setTickPosition(long position) { this.tickPosition = position; this.setChangeTickPosition(true); } /** * Retorna el tick de la nota que esta reproduciendo */ public long getTickPosition() { return this.tickPosition; } protected void changeTickPosition(){ try{ if(isRunning()){ this.getSequencer().setTickPosition(this.tickPosition); } setChangeTickPosition(false); } catch (MidiPlayerException e) { e.printStackTrace(); } } public void systemReset(){ try { this.getOutputTransmitter().sendSystemReset(); } catch (MidiPlayerException e) { e.printStackTrace(); } } /** * Agrega la Secuencia * @throws MidiUnavailableException */ public void addSecuence() { try{ MidiSequenceParser parser = new MidiSequenceParser(this.songManager,MidiSequenceParser.DEFAULT_PLAY_FLAGS,getMode().getCurrentPercent(),0); MidiSequenceHandler sequence = getSequencer().createSequence(this.songManager.getSong().countTracks() + 2); parser.parse(sequence); this.infoTrack = sequence.getInfoTrack(); this.metronomeTrack = sequence.getMetronomeTrack(); } catch (MidiPlayerException e) { e.printStackTrace(); } } 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) { e.printStackTrace(); } } public void updatePrograms() { try{ Iterator it = this.songManager.getSong().getTracks(); while(it.hasNext()){ TGTrack track = (TGTrack)it.next(); 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) { e.printStackTrace(); } } public void updateControllers() { this.anySolo = false; boolean percusionUpdated = false; Iterator it = this.songManager.getSong().getTracks(); while(it.hasNext()){ TGTrack track = (TGTrack)it.next(); this.updateController(track); this.anySolo = ((!this.anySolo)?track.isSolo():this.anySolo); percusionUpdated = (percusionUpdated || track.isPercussionTrack()); } if(!percusionUpdated && 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 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) { e.printStackTrace(); } } 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) { e.printStackTrace(); } } private void afterUpdate(){ try{ getSequencer().setSolo(this.infoTrack,this.anySolo); getSequencer().setSolo(this.metronomeTrack,(isMetronomeEnabled() && this.anySolo)); }catch (MidiPlayerException e) { e.printStackTrace(); } } public boolean isMetronomeEnabled() { return this.metronomeEnabled; } 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) { e.printStackTrace(); } } public void playBeat(final TGTrack track,final List 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 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) { throwable.printStackTrace(); } } public MidiPlayerMode getMode(){ if(this.mode == null){ this.mode = new MidiPlayerMode(); } return this.mode; } public MidiTransmitter getOutputTransmitter(){ if (this.outputTransmitter == null) { this.outputTransmitter = new MidiTransmitter(); } return this.outputTransmitter; } /** * Retorna el Puerto Midi */ public MidiOutputPort getOutputPort(){ return this.outputPort; } /** * Retorna el Sequenciador */ public MidiSequencer getSequencer(){ if (this.sequencer == null) { this.sequencer = new MidiSequencerEmpty(); } return this.sequencer; } 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 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 void openOutputPort(String key) { this.openOutputPort(key, false); } public void openOutputPort(String key, boolean tryFirst) { this.outputPortKey = key; this.openOutputPort(listOutputPorts(),tryFirst); } public void openOutputPort(List 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){ throwable.printStackTrace(); } } 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){ throwable.printStackTrace(); } } public void openSequencer(List 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 List listOutputPorts() { List ports = new ArrayList(); Iterator it = this.outputPortProviders.iterator(); while(it.hasNext()){ try{ MidiOutputPortProvider provider = (MidiOutputPortProvider)it.next(); ports.addAll(provider.listPorts()); }catch(Throwable throwable){ throwable.printStackTrace(); } } return ports; } public List listSequencers(){ List sequencers = new ArrayList(); Iterator it = this.sequencerProviders.iterator(); while(it.hasNext()){ try{ MidiSequencerProvider provider = (MidiSequencerProvider)it.next(); sequencers.addAll(provider.listSequencers()); }catch(Throwable throwable){ throwable.printStackTrace(); } } return sequencers; } 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); } } 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){ throwable.printStackTrace(); } } public boolean isSequencerOpen(String key){ if(key != null){ String currentKey = getSequencer().getKey(); if(currentKey == null){ return false; } return currentKey.equals(key); } return false; } 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 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); } 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); } public void removeOutputPortProvider(MidiOutputPortProvider provider) throws MidiPlayerException { this.outputPortProviders.remove(provider); MidiOutputPort current = getOutputPort(); if( current != null ){ Iterator it = provider.listPorts().iterator(); while(it.hasNext()){ MidiOutputPort port = (MidiOutputPort)it.next(); 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){ Iterator it = provider.listSequencers().iterator(); while(it.hasNext()){ MidiSequencer sequencer = (MidiSequencer)it.next(); if(current.getKey().equals(sequencer.getKey())){ closeSequencer(); break; } } } } }