view MidiPlayer.java @ 41:75354c638975

Fix for Windows users: the GUI was previously covered by task bar. Now offers a full screen option which covers the task bar. Also a good option for less technical users as program will stay in full screen until closed - less chance for error.
author Carl Bussey <c.bussey@se10.qmul.ac.uk>
date Wed, 05 Jun 2013 19:22:31 +0100
parents 796b3e3e053f
children
line wrap: on
line source
/*=============================================================================
 * File:       MidiPlayer.java
 * Author:     Marcus Pearce <m.pearce@gold.ac.uk>
 * Created:    <2007-02-14 12:13:56 marcusp>
 * Time-stamp: <2011-11-15 16:52:06 marcusp>
 *=============================================================================
 */

/* 
 * Based on: 
 *   http://www.jsresources.org/examples/SimpleMidiPlayer.html
 *   http://www.jsresources.org/examples/DumpSequence.html
 */ 

import java.io.File;
import java.io.IOException;
import java.util.ArrayList; 

import javax.sound.midi.MidiSystem;
import javax.sound.midi.MidiDevice; 
import javax.sound.midi.InvalidMidiDataException;
import javax.sound.midi.MidiUnavailableException;
import javax.sound.midi.MetaEventListener;
import javax.sound.midi.Sequence;
import javax.sound.midi.Sequencer;
import javax.sound.midi.Synthesizer;
import javax.sound.midi.MidiChannel;
import javax.sound.midi.Receiver;
import javax.sound.midi.Transmitter;
import javax.sound.midi.Track;
import javax.sound.midi.MidiEvent;
import javax.sound.midi.MidiMessage;
import javax.sound.midi.ShortMessage;
import javax.sound.midi.MetaMessage;
import javax.sound.midi.SysexMessage;
import javax.sound.midi.MetaMessage;
import java.util.Arrays;
import java.util.Iterator;


public class MidiPlayer { 

    /* variables */ 
    private Sequencer   sequencer = null;
    private Synthesizer synthesizer = null;
    private Sequence sequence = null;
    private ArrayList<Long> onsets, offsets = null;
    private ArrayList<Integer> pitches, velocities = null;
    private File midiFile;
    private boolean defaultMD = false;
    private int midiDevice;
    private boolean debug;

    /* accessors */ 
    public Sequencer getSequencer() { return sequencer; }
    public boolean usingDefault() { return defaultMD; }

    /* Constructors */
    public MidiPlayer(String path, int deviceNumber, boolean d) {
        if(deviceNumber == -1) {
            defaultMD = true;
            midiDevice = 0;
        }
        else{
            midiDevice = deviceNumber;
        }
        midiFile = new File(path);
        debug = d;
        setup();
    }
    
    private void setup(){
        // Get sequence
        try { sequence = MidiSystem.getSequence(midiFile); }
        catch (InvalidMidiDataException e) {
            e.printStackTrace();
            System.exit(1);
        }
        catch (IOException e) {
            e.printStackTrace();
            System.exit(1);
        }
        //sequencer.setTempoInBPM(bpm); 
        // Workaround bug in JDK 
//         sequencer.addMetaEventListener(new MetaEventListener() {
//                 public void meta(MetaMessage event) {
//                     if (event.getType() == 47) {
//                         sequencer.close();
//                         if (synthesizer != null) {
//                             synthesizer.close();
//                         }
//                     }
//                 }
//             });
        // Set up MIDI output for sequence
        try {
            MidiDevice.Info msinfo[] = MidiSystem.getMidiDeviceInfo(); 
            for(int i = 0; i < msinfo.length; i++) { 
		MidiDevice.Info m = msinfo[i];
                 System.out.println("Name: " + m.getName() + 
                                    "; Vendor: " + m.getVendor() + 
                                    "; Version: " + m.getVersion()); 
            }
		MidiDevice synth = MidiSystem.getMidiDevice(msinfo[midiDevice]);
		synth.open();
		// Get sequencer  
        	try { sequencer = MidiSystem.getSequencer(defaultMD); }
        	catch (MidiUnavailableException e) {
            		e.printStackTrace();
           		System.exit(1);
        	}
            // synthesizer = MidiSystem.getSynthesizer();

		try { sequencer.open(); }////
       		catch (MidiUnavailableException e) {
            		e.printStackTrace();
            		System.exit(1);
        	}
        	// Assign sequence to sequencer 
        	try { sequencer.setSequence(sequence); }
        	catch (InvalidMidiDataException e) {
            		e.printStackTrace();
            		System.exit(1);
        	}////

            // Change the patch 
            //             MidiChannel channels[] = synthesizer.getChannels(); 
            //             for (int i = 0; i < channels.length; i++) 
            //                 channels[i].programChange(65);
		
            Receiver synthReceiver = synth.getReceiver();
            Transmitter	seqTransmitter = sequencer.getTransmitter();
            seqTransmitter.setReceiver(synthReceiver);

        }
        catch (MidiUnavailableException e) {
            e.printStackTrace();
        }

        // compute data from the MIDI file 

	/*
        onsets = computeOnsets(); 
        offsets = computeOffsets(); 
        pitches = computePitches(); 
	*/
	registerEvents();

	if (debug) {
	    String divisionType; 
	    if (sequence.getDivisionType() == sequence.PPQ)
		divisionType = "ppq"; 
	    else 
		divisionType = "smpte"; 
	    System.out.println("division type = " + divisionType + 
			       "; resolution = " + sequence.getResolution()); 
	}
    }

    /* number of microseconds per MIDI tick */ 
    private double microsecondsPerTick() { 
        double seqTickLength = (float)sequence.getTickLength(); 
        double seqMicrosecondLength = (float)sequence.getMicrosecondLength(); 
        double microsecondsPerTick = seqMicrosecondLength / seqTickLength; 
        //System.out.println("seqTickLength = " + seqTickLength); 
        //System.out.println("seqMicrosecondLength = " + seqMicrosecondLength); 
        //System.out.println("microsecondsPerTick = " + microsecondsPerTick); 
        return microsecondsPerTick; 
    }
    
    private long ticksToMicroseconds(long tick) { 
        double microsecondsPerTick = microsecondsPerTick(); 
        double seconds = (double)tick * microsecondsPerTick; 
        
        return (long)(10 * Math.floor(seconds * 0.1));
    }

    /* compute a list of note onset times (microseconds) */ 
    private ArrayList computeOnsets() {
        ArrayList ons = new ArrayList(); 
        Track[]	tracks = sequence.getTracks();
        
        for (int nTrack = 0; nTrack < tracks.length; nTrack++) {
            Track track = tracks[nTrack];
            for (int nEvent = 0; nEvent < track.size(); nEvent++) {
                MidiEvent event = track.get(nEvent);
                MidiMessage message = event.getMessage();
                if (message instanceof ShortMessage && 
                    ((ShortMessage)message).getCommand() == ShortMessage.NOTE_ON) {
//                               System.out.println("onset in ticks = " + event.getTick()+ 
//                                                  "; onset in microseconds = " + 
//                                                  ticksToMicroseconds(event.getTick()));
                              //if the event does not have a velocity of zero (i.e. switching a note off)
                              if(message.getMessage()[2] != 0) {
                                 ons.add(ticksToMicroseconds(event.getTick())); //THEN we can add the note
                              }
                }
            }
        } 
        return ons; 
    }

    /* compute a list of note offset times (microseconds) */ 
    private ArrayList computeOffsets() {
        ArrayList offs = new ArrayList(); 
        Track[]	tracks = sequence.getTracks();
        
        for (int nTrack = 0; nTrack < tracks.length; nTrack++) {
            Track track = tracks[nTrack];
            for (int nEvent = 0; nEvent < track.size(); nEvent++) {
                MidiEvent event = track.get(nEvent);
                MidiMessage message = event.getMessage();
                if (message instanceof ShortMessage && 
                    ((ShortMessage)message).getCommand() == ShortMessage.NOTE_OFF) {
                    //System.out.println("offset in ticks = " + event.getTick()+ 
                    //                   "; microsecondsPerTick = " + microsecondsPerTick +
                    //                   "; offset in microseconds = " + 
                    //                   (float)event.getTick() * microsecondsPerTick);
                    offs.add(ticksToMicroseconds(event.getTick()));
                }
                //if we have not found a note off,
                else if (message instanceof ShortMessage 
                        && ((ShortMessage)message).getCommand() == ShortMessage.NOTE_ON
                        && message.getMessage()[2] == 0) { //but it is a note on with a velocity of zero
                        offs.add(ticksToMicroseconds(event.getTick())); //add it as an off signal
                }
                
            }
        }
        return offs; 
    }

    /* compute a list of note pitches */ 
    private ArrayList computePitches() {
        ArrayList pit = new ArrayList(); 
        Track[]	tracks = sequence.getTracks();
        
        for (int nTrack = 0; nTrack < tracks.length; nTrack++) {
            Track track = tracks[nTrack];
            for (int nEvent = 0; nEvent < track.size(); nEvent++) {
                MidiEvent event = track.get(nEvent);
                MidiMessage message = event.getMessage();
                if (message instanceof ShortMessage && 
                    ((ShortMessage)message).getCommand() == ShortMessage.NOTE_ON)
                    pit.add(((ShortMessage)message).getData1()); 
            }
        } 
        return pit; 
    }

    /*
     * Simultaneously construct lists of event onsets, offsets and pitches
     */
    private void registerEvents() {
	onsets = new ArrayList<Long>();
	offsets = new ArrayList<Long>();
        pitches = new ArrayList<Integer>(); 
        velocities = new ArrayList<Integer>(); 

	Track[] tracks = sequence.getTracks();

	// Iterate over MIDI tracks
	for (int i = 0; i < tracks.length; i++) {
	    Track track = tracks[i];
	    // Iterate over track events
	    for (int j = 0; j < track.size(); j++) {
		registerEvent(i, j);
	    }
	}

	if (debug) {
	    System.out.println("\nRegistered events...");
	    Iterator<Long> oi = onsets.iterator();
	    Iterator<Long> fi = offsets.iterator();
	    Iterator<Integer> pi = pitches.iterator();
	    Iterator<Integer> vi = velocities.iterator();
 
	    int pos = 1;
	    while (oi.hasNext() && fi.hasNext() && pi.hasNext() && vi.hasNext()) {
		System.out.println("Event " + pos + ": onset " + oi.next() + ", offset " + fi.next()
				   + ", pitch " + pi.next() + ", velocity " + vi.next());
		pos++;
	    }

	    if (oi.hasNext() || fi.hasNext() || pi.hasNext() || vi.hasNext()) {
		System.out.println("Warning: event lists not equal length.");
	    }
	}
    }

    /*
     * Add the given event to the onset, offset and pitch lists.
     */
    private void registerEvent(int trackIndex, int eventIndex) {

	Track track = sequence.getTracks()[trackIndex];
	MidiEvent event = track.get(eventIndex);
	MidiMessage message = event.getMessage();

	// Register NOTE_ON events
	if (message instanceof ShortMessage) {
	    ShortMessage shortMsg = (ShortMessage) message;
	    int velocity = shortMsg.getData2();
	    
	    if (shortMsg.getCommand() == ShortMessage.NOTE_ON && velocity != 0) {
		
		long onset = ticksToMicroseconds(event.getTick());
		int pitch = shortMsg.getData1();
		long offset = getOffset(trackIndex, eventIndex, onset, pitch);
		
		registerEvent(onset, offset, pitch, velocity);
	    }
	}
    }

    /*
     * Add the event details to the onset, offset and pitch lists,
     * use onset to determine the list position.
     */
    private void registerEvent(long onset, long offset, int pitch, int velocity) {
	
	int index = 0;
	boolean inserted = false;

	while (index < onsets.size()) {

	    if (onsets.get(index) > onset) 
		break;
	    else 
		index++;
	}

	onsets.add(index, onset);
	offsets.add(index, offset);
	pitches.add(index, pitch);
	velocities.add(index, pitch);
    }



    /*
     * Find the matching NOTE_OFF event *after* the given NOTE_ON event
     * (the <onsetIndex>th event in track <trackNum>)
     */
    private long getOffset(int trackNum, int onsetIndex, long onsetTime, int pitch) {
	
	long offset = -1;	
	Track track = sequence.getTracks()[trackNum];

	// Iterate through remaining events looking for NOTE_OFF
	for (int k = onsetIndex + 1; k < track.size(); k++) {
	    MidiEvent event = track.get(k);
	    MidiMessage message = event.getMessage();
	    
	    if (message instanceof ShortMessage) {
		ShortMessage shortMsg = (ShortMessage) message;
		int command = shortMsg.getCommand();

		// Check NOTE_OFF message
		if (command == ShortMessage.NOTE_OFF
		    || (command == ShortMessage.NOTE_ON && shortMsg.getData2() == 0)) {
		    int pitch2 = shortMsg.getData1();
		    // If pitches are identical then offset found
		    if (pitch == pitch2) {
			offset = ticksToMicroseconds(event.getTick());
			break;
		    }   
		}
	    }
	}

	if (offset < 0) {
	    System.out.println("No NOTE_OFF found for track " + trackNum
			       + ", onset " + onsetTime + ", pitch " + pitch);
	}

	return offset;
    }



    /* return a list of note onset times (milliseconds) */ 
    public ArrayList getOnsets() { return onsets; } 

    /* return a list of note offset times (milliseconds) */ 
    public ArrayList getOffsets() { return offsets; } 

    /* return a list of note pitches */ 
    public ArrayList getPitches() { return pitches; } 
    
    /* return a list of note durations (microseconds) */ 
    public ArrayList getDurations() { 
        Object[] ons = onsets.toArray(); 
        Object[] offs = offsets.toArray(); 
        
        ArrayList durations = new ArrayList(); 
        
        for(int i = 0; i < ons.length; i++) { 
            durations.add(((Long)offs[i]).longValue() - 
                          ((Long)ons[i]).longValue());
        } 
        return durations; 
    } 

    /* return a list of inter-onset intervals (microseconds) */ 
    public ArrayList getInterOnsetIntervals() { 
        Object[] ons = onsets.toArray(); 

        ArrayList iois = new ArrayList(); 
        long firstIOI = 0; 
        iois.add(firstIOI); // IOI of first note is zero 
        
        for(int i = 1; i < ons.length; i++) { 
            iois.add(((Long)ons[i]).longValue() - 
                     ((Long)ons[i-1]).longValue());
        } 
        return iois; 
    } 

    /* return the length of a rest preceding a note */ 
    public ArrayList getDeltaST() { 
        Object[] durs = getDurations().toArray(); 
        Object[] iois = getInterOnsetIntervals().toArray(); 
        
        ArrayList deltaST = new ArrayList(); 
        long firstDeltaST = 0; 
        deltaST.add(firstDeltaST); // deltaST of first note is zero 

        for(int i = 1; i < durs.length; i++) { 
            deltaST.add(((Long)durs[i-1]).longValue() - 
                        ((Long)iois[i]).longValue());
        }
        return deltaST; 
    } 
    
    /* 
     * Return the tatum of the midi file, i.e. the shortest
     * note duration or positive rest.
     */ 
    public long getTatum() {
    

        Object[] durs = getDurations().toArray(); 
        Object[] rests = getDeltaST().toArray(); 
        long tatum = -1; 


        for(int i = 0; i < durs.length; i++) { 
            long dur = ((Long)durs[i]).longValue(); 
            long rest = ((Long)rests[i]).longValue(); 
            long min = dur; 
            if (rest > 0)
                min = Math.min(dur, rest);
            if (debug)
		System.out.println("Dur: " + dur + "    Rest: " + rest);
            if (tatum < 0)
                tatum = dur; 
            else if (min < tatum) 
                tatum = min; 
        }
        //tatum = sequence.getResolution()*1000;
        return tatum; 
    }

    /* return length of sequence in microseconds */ 
    public long getLength() { 
        return sequence.getMicrosecondLength(); 
    } 

    /* play the midi file */ 
    public void play() { 
        //System.out.println("MidiPlayer.play: run sequencer.start().");
        sequencer.start();
    } 

    public void stop() { 
        if (synthesizer != null) {
            synthesizer.close();
        }
        if (sequencer != null) {
            sequencer.close();
        }
        sequencer = null;
        synthesizer = null;
    }
}