/*
*
      
_______
                       
_____
   
_____ _____
  

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

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

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

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

*
                                                         

* -------------------------------------------------------------
*
* 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.
*
 

*/

/*
	
Copyright (C) 2001, 2006 by Simon Dixon

	
This program 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.

	
This program 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 this program (the file gpl.txt); if not, download it from
	
http://www.gnu.org/licenses/gpl.txt
 
or write to the
	
Free Software Foundation, Inc.,
	
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/


package be.tarsos.dsp.beatroot;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.ListIterator;



// Adapted from eventList::readMatchFile in beatroot/src/eventMidi.cpp

// Reads in a Prolog score+performance (.match) file; returns it as an eventList
// Lines in the match file can be of the form:
//
		
hammer_bounce-PlayedNote.
//
		
info(Attribute, Value).
//
		
insertion-PlayedNote.
//
		
ornament(Anchor)-PlayedNote.
//
		
ScoreNote-deletion.
//
		
ScoreNote-PlayedNote.
//
		
ScoreNote-trailing_score_note.
//
		
trailing_played_note-PlayedNote.
//
		
trill(Anchor)-PlayedNote.
// where ScoreNote is of the form
//
		
snote(Anchor,[NoteName,Modifier],Octave,Bar:Beat,Offset,Duration,
//
				
BeatNumber,DurationInBeats,ScoreAttributesList)
//
		
e.g. snote(n1,[b,b],5,1:1,0,3/16,0,0.75,[s])
// and PlayedNote is of the form
//
		
note(Number,[NoteName,Modifier],Octave,Onset,Offset,AdjOffset,Velocity)
//
		
e.g. note(1,[a,#],5,5054,6362,6768,53)

class WormFileParseException extends RuntimeException {

	
static final long serialVersionUID = 0;
	
public WormFileParseException(String s) {
		
super(s);
	
} // constructor

} // class WormFileParseException

class MatchFileParseException extends RuntimeException {

	
static final long serialVersionUID = 0;
	
public MatchFileParseException(String s) {
		
super(s);
	
} // constructor

} // class MatchFileParseException

class BTFileParseException extends RuntimeException {

	
static final long serialVersionUID = 0;
	
public BTFileParseException(String s) {
		
super(s);
	
} // constructor

} // class BTFileParseException


// Process the strings which label extra features of notes in match files.
// We assume no more than 32 distinct labels in a file.
class Flags {

	
String[] labels = new String[32];
	
int size = 0;
	

	
int getFlag(String s) {
		
if ((s == null) || s.equals(""))
			
return 0;
		
//int val = 1;
		
for (int i = 0; i < size; i++)
			
if (s.equals(labels[i]))
				
return 1 << i;
		
if (size == 32)
	
{
			
System.err.println("Overflow: Too many flags: " + s);
			
size--;
		
}
		
labels[size] = s;
		
return 1 << size++;
	
} // getFlag()

	
String getLabel(int i) {
		
if (i >= size)
			
return "ERROR: Unknown flag";
		
return labels[i];
	
} // getLabel()

} // class Flags


// A score/match/midi file is represented as an EventList object,
//
  
which contains pointers to the head and tail links, and some
//
  
class-wide parameters. Parameters are class-wide, as it is
//
  
assumed that the Worm has only one input file at a time.
public class EventList {

	
public LinkedList<Event> l;

	
protected static boolean timingCorrection = false;
	
protected static double timingDisplacement = 0;
	
protected static int clockUnits = 480;
	
protected static int clockRate = 500000;
	
protected static double metricalLevel = 0;
	
public static final double UNKNOWN = Double.NaN;
	
protected static boolean noMelody = false;
	
protected static boolean onlyMelody = false;
	
protected static Flags flags = new Flags();

	
public EventList() {
		
l = new LinkedList<Event>();
	
} // constructor

	
public EventList(EventList e) {
		
this();
		
ListIterator<Event> it = e.listIterator();
		
while (it.hasNext())
			
add(it.next());
	
} // constructor

	
public EventList(Event[] e) {
		
this();
		
for (int i=0; i < e.length; i++)
			
add(e[i]);
	
} // constructor

	
public void add(Event e) {
		
l.add(e);
	
} // add()

	
public void add(EventList ev) {
		
l.addAll(ev.l);
	
} // add()

	
public void insert(Event newEvent, boolean uniqueTimes) {
		
ListIterator<Event> li = l.listIterator();
		
while (li.hasNext()) {
			
int sgn = newEvent.compareTo(li.next());
			
if (sgn < 0) {
				
li.previous();
				
break;
			
} else if (uniqueTimes && (sgn == 0)) {
				
li.remove();
				
break;
			
}
		
}
		
li.add(newEvent);
	
} // insert()

	
public ListIterator<Event> listIterator() {
		
return l.listIterator();
	
} // listIterator()

	
public Iterator<Event> iterator() {
		
return l.iterator();
	
} // iterator()

	
public int size() {
		
return l.size();
	
} // size()

	
public Event[] toArray() {
		
return toArray(0);
	
} // toArray()

	
public double[] toOnsetArray() {
		
double[] d = new double[l.size()];
		
int i = 0;
		
for (Iterator<Event> it = l.iterator(); it.hasNext(); i++)
			
d[i] = it.next().keyDown;
		
return d;
	
} // toOnsetArray()

	
public Event[] toArray(int match) {
		
int count = 0;
		
for (Event e : l)
			
if ((match == 0) || (e.midiCommand == match))
				
count++;
		
Event[] a = new Event[count];
		
int i = 0;
		
for (Event e : l)
			
if ((match == 0) || (e.midiCommand == match))
				
a[i++] = e;
		
return a;
	
} // toArray()

	
public void writeBinary(String fileName) {
		
try {
			
ObjectOutputStream oos = new ObjectOutputStream(
										
new FileOutputStream(fileName));
			
oos.writeObject(this);
			
oos.close();
		
} catch (IOException e) {
			
System.err.println(e);
		
}
	
} // writeBinary()

	
public static EventList readBinary(String fileName) {
		
try {
			
ObjectInputStream ois = new ObjectInputStream(
										
new FileInputStream(fileName));
			
EventList e = (EventList) ois.readObject();
			
ois.close();
			
return e;
		
} catch (IOException e) {
			
System.err.println(e);
			
return null;
		
} catch (ClassNotFoundException e) {
			
System.err.println(e);
			
return null;
		
}
	
} // readBinary()

	
/*
	
public void writeMIDI(String fileName) {
		
writeMIDI(fileName, null);
	
} // writeMIDI()

	
public void writeMIDI(String fileName, EventList pedal) {
		
try {
			
MidiSystem.write(toMIDI(pedal), 1, new File(fileName));
		
} catch (Exception e) {
			
System.err.println("Error: Unable to write MIDI file " + fileName);
			
e.printStackTrace();
		
}
	
} // writeMIDI()

	
public Sequence toMIDI(EventList pedal) throws InvalidMidiDataException {
		
final int midiTempo = 1000000;
		
Sequence s = new Sequence(Sequence.PPQ, 1000);
		
Track[] tr = new Track[16];
		
tr[0] = s.createTrack();
		
MetaMessage mm = new MetaMessage();
		
byte[] b = new byte[3];
		
b[0] = (byte)((midiTempo >> 16) & 0xFF);
		
b[1] = (byte)((midiTempo >> 8) & 0xFF);
		
b[2] = (byte)(midiTempo & 0xFF);
		
mm.setMessage(0x51, b, 3);
		
tr[0].add(new MidiEvent(mm, 0L));
		
for (Event e : l) {// from match or beatTrack file
			
if (e.midiCommand == 0) // skip beatTrack file
				
break;
			
if (tr[e.midiTrack] == null)
				
tr[e.midiTrack] = s.createTrack();
			
//switch (e.midiCommand)
 

			
//case ShortMessage.NOTE_ON:
			
//case ShortMessage.POLY_PRESSURE:
			
//case ShortMessage.CONTROL_CHANGE:
			
//case ShortMessage.PROGRAM_CHANGE:
			
//case ShortMessage.CHANNEL_PRESSURE:
			
//case ShortMessage.PITCH_BEND:
			
ShortMessage sm = new ShortMessage();
			
sm.setMessage(e.midiCommand, e.midiChannel,
							
e.midiPitch, e.midiVelocity);
			
tr[e.midiTrack].add(new MidiEvent(sm,
						
(long)Math.round(1000 * e.keyDown)));
			
if (e.midiCommand == ShortMessage.NOTE_ON) {
				
sm = new ShortMessage();
				
sm.setMessage(ShortMessage.NOTE_OFF, e.midiChannel, e.midiPitch, 0);
				
tr[e.midiTrack].add(new MidiEvent(sm, (long)Math.round(1000 * e.keyUp)));
			
}
		
}
		
if (pedal != null) { // from MIDI file
	
//
		
if (t.size() > 0) // otherwise beatTrack files leave an empty trk
	
//
			
t = s.createTrack();
			
for (Event e : pedal.l) {
				
if (tr[e.midiTrack] == null)
					
tr[e.midiTrack] = s.createTrack();
				
ShortMessage sm = new ShortMessage();
				
sm.setMessage(e.midiCommand, e.midiChannel,
 

								
e.midiPitch, e.midiVelocity);
				
tr[e.midiTrack].add(new MidiEvent(sm,
						
(long)Math.round(1000 * e.keyDown)));
				
if (e.midiCommand == ShortMessage.NOTE_ON) {
					
sm = new ShortMessage();
					
sm.setMessage(ShortMessage.NOTE_OFF, e.midiChannel,
									
e.midiPitch,e.midiVelocity);
					
tr[e.midiTrack].add(new MidiEvent(sm,
							
(long)Math.round(1000 * e.keyUp)));
				
}
				
//catch (InvalidMidiDataException exception) {}
			
}
		
}
		
return s;
	
} // toMIDI()

	
public static EventList readMidiFile(String fileName) {
		
return readMidiFile(fileName, 0);
	
} // readMidiFile()

	
public static EventList readMidiFile(String fileName, int skipTrackFlag) {
		
EventList list = new EventList();
		
Sequence s;
		
try {
			
s = MidiSystem.getSequence(new File(fileName));
		
} catch (Exception e) {
			
e.printStackTrace();
			
return list;
		
}
		
double midiTempo = 500000;
		
double tempoFactor = midiTempo / s.getResolution() / 1000000.0;
		
// System.err.println(tempoFactor);
		
Event[][] noteOns = new Event[128][16];
		
Track[] tracks = s.getTracks();
		
for (int t = 0; t < tracks.length; t++, skipTrackFlag >>= 1) {
			
if ((skipTrackFlag & 1) == 1)
				
continue;
			
for (int e = 0; e < tracks[t].size(); e++) {
				
MidiEvent me = tracks[t].get(e);
				
MidiMessage mm = me.getMessage();
				
double time = me.getTick() * tempoFactor;
				
byte[] mesg = mm.getMessage();
				
int channel = mesg[0] & 0x0F;
				
int command = mesg[0] & 0xF0;
				
if (command == ShortMessage.NOTE_ON) {
					
int pitch = mesg[1] & 0x7F;
					
int velocity = mesg[2] & 0x7F;
					
if (noteOns[pitch][channel] != null) {
						
if (velocity == 0) { // NOTE_OFF in disguise :(
							
noteOns[pitch][channel].keyUp = time;
							
noteOns[pitch][channel].pedalUp = time;
							
noteOns[pitch][channel] = null;
						
} else
 							
System.err.println("Double note on: n=" + pitch +
									
" c=" + channel +
									
" t1=" + noteOns[pitch][channel] +
									
" t2=" + time);
					
} else {
						
Event n = new Event(time, 0, 0, pitch, velocity, -1, -1,
										
0, ShortMessage.NOTE_ON, channel, t);
						
noteOns[pitch][channel] = n;
						
list.add(n);
					
}
				
} else if (command == ShortMessage.NOTE_OFF) {
					
int pitch = mesg[1] & 0x7F;
					
noteOns[pitch][channel].keyUp = time;
					
noteOns[pitch][channel].pedalUp = time;
					
noteOns[pitch][channel] = null;
				
} else if (command == 0xF0) {
					
if ((channel == 0x0F) && (mesg[1] == 0x51)) {
						
midiTempo = (mesg[5] & 0xFF) |
									
((mesg[4] & 0xFF) << 8) |
									
((mesg[3] & 0xFF) << 16);
						
tempoFactor = midiTempo / s.getResolution() / 1000000.0;
					
// System.err.println("Info: Tempo change: " + midiTempo +
					
//
						
"
  
tf=" + tempoFactor);
					
}
				
} else if (mesg.length > 3) {
					
System.err.println("midi message too long: " + mesg.length);
					
System.err.println("\tFirst byte: " + mesg[0]);
				
} else {
					
int b0 = mesg[0] & 0xFF;
					
int b1 = -1;
					
int b2 = -1;
					
if (mesg.length > 1)
						
b1 = mesg[1] & 0xFF;
					
if (mesg.length > 2)
						
b2 = mesg[2] & 0xFF;
					
list.add(new Event(time, time, -1, b1, b2, -1, -1, 0,
										
b0 & 0xF0, b0 & 0x0F, t));
				
}
			
}
		
}
		
for (int pitch = 0; pitch < 128; pitch++)
			
for (int channel = 0; channel < 16; channel++)
				
if (noteOns[pitch][channel] != null)
					
System.err.println("Missing note off: n=" +
 

							
noteOns[pitch][channel].midiPitch + " t=" +
							
noteOns[pitch][channel].keyDown);
		
return list;
	
} // readMidiFile()
*/

	
public void print() {
		
for (Iterator<Event> i = l.iterator(); i.hasNext(); )
			
i.next().print(flags);
	
} // print()

	
public static void setTimingCorrection(double corr) {
		
timingCorrection = corr >= 0;
		
timingDisplacement = corr;
	
} // setTimingCorrection()

	

	

	

	

} // class EventList