/*
*
      
_______
                       
_____
   
_____ _____
  

*
     
|__
   
__|
                     
|
  
__ \ / ____|__ \
 

*
        
| | __ _ _ __ ___
  
______| || | (___ | |__) |
*
        
| |/ _` | '__/ __|/ _ \/ __| |
  
| |\___ \|___/
 

*
        
| | (_| | |
  
\__ \ (_) \__ \ |__| |____) | |
     

*
        
|_|\__,_|_|
  
|___/\___/|___/_____/|_____/|_|
     

*
                                                         

* -------------------------------------------------------------
*
* TarsosDSP is developed by Joren Six at IPEM, University Ghent
*
  

* -------------------------------------------------------------
*
*
  
Info:
 
http://0110.be/tag/TarsosDSP
*
  
Github:
 
https://github.com/JorenSix/TarsosDSP
*
  
Releases:
 
http://0110.be/releases/TarsosDSP/
*
  

*
  
TarsosDSP includes modified source code by various authors,
*
  
for credits and info, see README.
*
 

*/

package be.tarsos.dsp.example.catify;

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

import javax.sound.midi.InvalidMidiDataException;
import javax.sound.midi.MidiEvent;
import javax.sound.midi.MidiSystem;
import javax.sound.midi.Sequence;

public class MidiParser {
	
private Sequence seq;
	
private int currentTrack;
	
private ArrayList<Integer> nextMessageOf;
	
private List<MidiNoteInfo> temporaryMidiNotes;
	
private List<MidiNoteInfo> midiNotes;
	

	
public MidiParser(File midiFile) throws InvalidMidiDataException, IOException{
		
seq = MidiSystem.getSequence(midiFile);
		

	
}
	

	
public List<MidiNoteInfo> generateNoteInfo(){
		
temporaryMidiNotes = new ArrayList<MidiNoteInfo>();
		
midiNotes = new ArrayList<MidiNoteInfo>();
		
nextMessageOf = new ArrayList<Integer>();
		
currentTrack = 0;
		
convertMidi2RealTime(seq);
		
return midiNotes;
	
}
	

	
private
  
void
convertMidi2RealTime(Sequence seq) {
		
double currentTempo = 500000;
		
int tickOfTempoChange = 0;
		
double msb4 = 0;

		
for (int track = 0; track < seq.getTracks().length; track++){
			
nextMessageOf.add(0);
		
}


		
MidiEvent nextEvent;
		
while ((nextEvent = getNextEvent()) != null) {
			
int tick = (int) nextEvent.getTick();
			
if (noteIsOff(nextEvent)) {
				
double time = (msb4 + (((currentTempo / seq.getResolution()) / 1000) * (tick - tickOfTempoChange)));
				
int stop = (int) (time + 0.5);
				
int midiNote = ((int) nextEvent.getMessage().getMessage()[1] & 0xFF);
					

				
MidiNoteInfo midiNoteInfo = null;
				
for(MidiNoteInfo s : temporaryMidiNotes){
					
if(s.getMidiNote()==midiNote){
						
midiNoteInfo = s;
					
}
				
}
				
if(midiNoteInfo==null){
					
System.err.println("Note off without note on...");
				
}else{
					
temporaryMidiNotes.remove(midiNoteInfo);
					
midiNoteInfo.setStop(stop);
					
midiNotes.add(midiNoteInfo);
					

				
}
			
} else if (noteIsOn(nextEvent)) {
				
double time = (msb4 + (((currentTempo / seq.getResolution()) / 1000) * (tick - tickOfTempoChange)));
				
int start = (int) (time + 0.5);
				
int midiNote = ((int) nextEvent.getMessage().getMessage()[1] & 0xFF);
				
int velocity = ((int) nextEvent.getMessage().getMessage()[2] & 0xFF);
				

				
//System.out.println("track=" + currentTrack + " tick=" + tick + " time=" + start
  
+ "ms " + " note " + midiNote + " on" + " velocity " + velocity);

				
temporaryMidiNotes.add(new MidiNoteInfo(start,midiNote, velocity));
			
} else if (changeTemp(nextEvent)) {
				
String a = (Integer.toHexString((int) nextEvent.getMessage()
						
.getMessage()[3] & 0xFF));
				
String b = (Integer.toHexString((int) nextEvent.getMessage()
						
.getMessage()[4] & 0xFF));
				
String c = (Integer.toHexString((int) nextEvent.getMessage()
						
.getMessage()[5] & 0xFF));
				
if (a.length() == 1)
					
a = ("0" + a);
				
if (b.length() == 1)
					
b = ("0" + b);
				
if (c.length() == 1)
					
c = ("0" + c);
				
String whole = a + b + c;
				
int newTempo = Integer.parseInt(whole, 16);
				
double newTime = (currentTempo / seq.getResolution())
						
* (tick - tickOfTempoChange);
				
msb4 += (newTime / 1000);
				
tickOfTempoChange = tick;
				
currentTempo = newTempo;
			
}
		
}
	
}

	
private MidiEvent getNextEvent() {
		
ArrayList<MidiEvent> nextEvent = new ArrayList<MidiEvent>();
		
ArrayList<Integer> trackOfNextEvent = new ArrayList<Integer>();
		
for (int track = 0; track < seq.getTracks().length; track++) {
			
if (seq.getTracks()[track].size() - 1 > (nextMessageOf.get(track))) {
				
nextEvent.add(seq.getTracks()[track].get(nextMessageOf
						
.get
(track)));
				
trackOfNextEvent.add(track);
			
}
		
}
		
if (nextEvent.size() == 0)
			
return null;
		
int closestMessage = 0;
		
int smallestTick = (int) nextEvent.get(0).getTick();
		
for (int trialMessage = 1; trialMessage < nextEvent.size(); trialMessage++) {
			
if ((int) nextEvent.get(trialMessage).getTick() < smallestTick) {
				
smallestTick = (int) nextEvent.get(trialMessage).getTick();
				
closestMessage = trialMessage;
			
}
		
}
		
currentTrack = trackOfNextEvent.get(closestMessage);
		
nextMessageOf.set(currentTrack, (nextMessageOf.get(currentTrack) + 1));
		
return nextEvent.get(closestMessage);
	
}

	
private static boolean noteIsOff(MidiEvent event) {
		
if (Integer.toString((int) event.getMessage().getStatus(), 16)
				
.toUpperCase().charAt(0) == '8'
				
|| (noteIsOn(event) && event.getMessage().getLength() >= 3 && ((int) event
						
.getMessage
().getMessage()[2] & 0xFF) == 0))
			
return true;
		
return false;
	
}

	
private static boolean noteIsOn(MidiEvent event) {
		
if (Integer.toString(event.getMessage().getStatus(), 16).toUpperCase()
				
.charAt(0) == '9')
			
return true;
		
return false;
	
}

	
private static boolean changeTemp(MidiEvent event) {
		
if ((int) Integer.valueOf(
				
("" + Integer
						
.toString
((int) event.getMessage().getStatus(), 16)
						
.toUpperCase().charAt(0)), 16) == 15
				
&& (int) Integer.valueOf(
						
("" + ((String) (Integer.toString((int) event
								
.getMessage
().getStatus(), 16).toUpperCase()))
								
.charAt(1)), 16) == 15
				
&& Integer
						
.toString
((int) event.getMessage().getMessage()[1], 16)
						
.toUpperCase().length() == 2
				
&& Integer
						
.toString
((int) event.getMessage().getMessage()[1], 16)
						
.toUpperCase().equals("51")
				
&& Integer
						
.toString
((int) event.getMessage().getMessage()[2], 16)
						
.toUpperCase().equals("3"))
			
return true;
		
return false;
	
}
}