/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package midi;

import data.Beat;
import data.NoteData;
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JTextPane;
import javax.swing.text.BadLocationException;
import javax.swing.text.StyledDocument;
import jm.music.data.Part;
import jm.music.data.Phrase;
import jm.music.data.Score;
import motif.PolyphonMotifDetection;

/**
 *
 * @author Tom
 */
public class MidiQuantizer {
    private ArrayList<ArrayList<NoteData>> onSets = new ArrayList<>(); //ArrayList mit ArrayLists der OnSets pro Phrase
    private ArrayList<ArrayList<Beat>> scoreInBeats = new ArrayList<>(); //die Noten unterteilt in Takte
    private ArrayList<Integer> PartsPhrases = new ArrayList<>();
    private ArrayList<Integer> Instruments = new ArrayList<>(); //Liste der verwendeten Instrumente
    private int ppqn = -1; //Pulse per quater note
    private double tempo = -1.0;
    private int numerator = 4;
    private int denominator = 4;
    private static double BEATLENGTH = 4.0; //Laenge eines Taktes 4.0 = 4/4-Takt
    private static int PAUSE = 50; //alle Pause kleiner als PAUSE werden ignoriert
    private static int ONSETS = 0, /*MARKERONE = 1, MARKERTWO = 2, MARKERTHREE = 3, MARKERFOUR = 4,*/ DURATION = 5,
                       PITCH = 6, NOTE = 7, /*MAPPINGONE = 8, MAPPINGTWO = 9, MAPPINGTHREE = 10, MAPPINFOUR = 11,*/ QUANTIZEDDURATION = 12;
    private double longestPhrase = 0.0; //Dauer der laengsten Phrase
    private JTextPane textPane = null;
    private String key = "C major";

    /**
     * Konstruktor
     */
    public MidiQuantizer() {
    }
    
    /**
     * Kontruktor
     * @param textPane TextPane fuer Log-Ausgaben
     */
    public MidiQuantizer(JTextPane textPane) {
        this.textPane = textPane;
    }
    
    /**
    * berechnet die Daten fuer die Markings/Mappings
    * @param scr Score, der bearbeitet wird (MIDI-File)
    */
   public ArrayList<ArrayList<Beat>> getQuantizedScoreInBeats(Score scr){
      if (scr != null){
        numerator = scr.getNumerator();
        denominator = scr.getDenominator();
        BEATLENGTH = (Double.valueOf(numerator)/Double.valueOf(denominator))*4; //voller Takt zB.: 4/4, 2/4, 3/4, ...

        setLongestPhrase(scr);
        setStartTimes(scr);
        //roundStartTimes();
        //roundDurations();
        //setMarkingOne();
        //setMarkingTwo();
        //setMarkingThree();
        //setMarkingFour();
        //setMappingOne();
        //setMappingTwo();
        setQuantizedNoteDurations();
        setSameBeatCount();
        setKey();

        if (textPane != null){
            //Ausgabe der einzelnen Werte
            out("OnSets ---------------------");
            writeToTextPane("OnSets ---------------------\n");
            printDataInBeats(ONSETS);
            /*out("\nmarkingOne -----------------");
            writeToTextPane("\nmarkingOne -----------------\n");
            printDataInBeats(MARKERONE);
            out("\nmarkingTwo------------------");
            writeToTextPane("\nmarkingTwo------------------\n");
            printDataInBeats(MARKERTWO);
            out("\nmarkedOne-------------------");
            writeToTextPane("\nmarkedOne-------------------\n");
            printDataInBeats(MAPPINGONE);
            out("\nmarkedTwo-------------------");
            writeToTextPane("\nmarkedTwo-------------------\n");
            printDataInBeats(MAPPINGTWO);*/
            out("\nNotQuantizedNoteDurations---");
            writeToTextPane("\nNotQuantizedNoteDurations---\n");
            getNotQuantizedNoteDurations();
            out("\nDuration----------------");
            writeToTextPane("\nDuration----------------\n");
            printDataInBeats(DURATION);
            out("\nPitches-----------------");
            writeToTextPane("\nPitches-----------------\n");
            printDataInBeats(PITCH);
            out("\nNotesInBeats----------------");
            writeToTextPane("\nNotesInBeats----------------\n");
            printNotesInBeats();
            out("\nNotesInBeatsAlternate-------");
            writeToTextPane("\nNotesInBeatsAlternate-------\n");
            printNotesInBeatsAlternate();
            /*out("\nBeatCountPerPhrase----------");
            writeToTextPane("\nBeatCountPerPhrase----------\n");
            printBeatCountPerPhrase();*/
            out("");
            writeToTextPane("\n");
        }
        return scoreInBeats;
      }
      return null;
   }
   
   /**
    * liefert den Numerator des Scores
    * @return Numerator
    */
   public int getNumerator(){
       return numerator;
   }
   
   /**
    * liefert den Denominator des Scores
    * @return Denominator
    */
   public int getDenominator(){
       return denominator;
   }
   
   /**
    * liefert die Tonart des Scores
    * @return die Tonart
    */
   public String getKey(){
       return key;
   }
   
   /**
    * bestimmt die Tonart des Scores
    * Schaetzung mit Hilfe des Krumhansl-Schmuckler-Algorithmus
    */
   private void setKey(){
       KeyFinder kf = new KeyFinder(getNoteDurationsForKeyFinding());
       key = kf.getKey();
   }
   
   /**
    * liefert ein Array mit den summierten Notenlaengen jeder Note (C-B)
    * @return Array mit summierter Laenge pro Note
    */
   private double[] getNoteDurationsForKeyFinding(){
        double[] noteDurations = {0,0,0,0,0,0,0,0,0,0,0,0};
        
        for (ArrayList<Beat> alb : scoreInBeats){
            for (Beat b : alb){
                for (NoteData nd : b.getBeat()){
                    switch (nd.getNote())
                    {
                        case "C":
                            noteDurations[0] += nd.getQuantizedDuration();
                            break;
                        case "C#":
                            noteDurations[1] += nd.getQuantizedDuration();
                            break;
                        case "D":
                            noteDurations[2] += nd.getQuantizedDuration();
                            break;
                        case "Eb":
                            noteDurations[3] += nd.getQuantizedDuration();
                            break;
                        case "E":
                            noteDurations[4] += nd.getQuantizedDuration();
                            break;
                        case "F":
                            noteDurations[5] += nd.getQuantizedDuration();
                            break;
                        case "F#":
                            noteDurations[6] += nd.getQuantizedDuration();
                            break;
                        case "G":
                            noteDurations[7] += nd.getQuantizedDuration();
                            break;
                        case "Ab":
                            noteDurations[8] += nd.getQuantizedDuration();
                            break;
                        case "A":
                            noteDurations[9] += nd.getQuantizedDuration();
                            break;
                        case "Bb":
                            noteDurations[10] += nd.getQuantizedDuration();
                            break;
                        case "B":
                            noteDurations[11] += nd.getQuantizedDuration();
                            break;
                    }
                }
            }
        }
        return noteDurations;
   }
   
    /**
     * setzt die Laenge der laengsten Phrase
     * @param scr Score bzw. MIDI-File
     */
    private void setLongestPhrase(Score scr) {
        for (Part p : scr.getPartArray()) {
          if (p.getChannel() != 9) { //keine Percussions
            for (Phrase ph : p.getPhraseArray()) {
                if (ph.getEndTime() > longestPhrase){
                    longestPhrase = ph.getEndTime();
                }
            }
          }
        }
    }
    
   /**
    * berechnet die Startzeit jeder Note des uebergebenen Scores
    * und fuegt eine Pause am Anfang und Ende jeder Phrase ein,
    * um alle Phrasen auf die gleiche Laenge zu bringen
    * @param scr Score
    */
   private void setStartTimes(Score scr){
      double startTime;
      double lastDuration = 0.0;
      
      ppqn = scr.getPPQN();
      tempo = scr.getTempo();
      for (Part p : scr.getPartArray()) {
         if (p.getChannel() != 9) { //keine Percussions
            int i = 0;
            //Instrument speichern
            Instruments.add(p.getInstrument());
            for (Phrase ph : p.getPhraseArray()) {
               PartsPhrases.add(i);
               i++;
               ArrayList<NoteData> al = new ArrayList<>();
               startTime = ph.getStartTime();
               /* bevor die erste Note der Phrase gespielt wird,
                * kann es sein, dass davor eine sehr lange Pause ist.
                * Aus diesem Grund wird eine Note (eigentlich Pause: Integer.MIN_VALUE)
                * mit einer Dauer der 'startTime' erstellt, um alle Phrasen gleich auszurichten.
                */
               if (startTime > PAUSE) {
                   al.add(new NoteData(0.0, startTime, Integer.MIN_VALUE));
               }
               for (jm.music.data.Note n : ph.getNoteArray()) {
                  if (n.getDuration() != 0.0){ //ignoriere Noten der Laenge 0.0
                    startTime += n.getDuration();
                    //ignoriere sehr kurze Pausen
                    if (!(n.getPitch() == Integer.MIN_VALUE && n.getDuration() <= PAUSE)){
                       al.add(new NoteData(startTime, n.getDuration(), n.getPitch()));
                       lastDuration = n.getDuration();
                    }
                  }
               }
               /*
                * um die Laenge aller Phrasen gleich zu machen
                * wird am Ende der Phrase gegebenenfalls noch eine
                * Pause in der Laenge der erforderlichen Restdauer hinzugefuegt
                */
               if (startTime + lastDuration < longestPhrase) {
                   double start = startTime + lastDuration;
                   double duration = longestPhrase - start;
                   al.add(new NoteData(start, duration, Integer.MIN_VALUE));
               }
               onSets.add(al);
            }
         }
      }
   }
   
   /**
    * liefert die Anzahl der maximalen Beats einer Phrase
    * @return Anzahl der Beats, die jede Phrase haben sollte
    */
   private int getMaxBeatCount(){
       int max = 0;
       for (ArrayList<Beat> alb : scoreInBeats){
           if (max < alb.size()){
               max = alb.size();
           }
       }
       return max;
   }
   
   /**
    * haengt an Phrasen, die weniger Takte haben als die Phrase mit den maximalen Takten
    * weitere Takte mit Pausen an, um in jeder Phrase die gleiche Anzahl an Takten zu
    * gewaehrleisten
    */
   private void setSameBeatCount(){
       int beatCount = getMaxBeatCount();
       double startTime = 0.0;
       for (ArrayList<Beat> alb : scoreInBeats){
           if (alb.size() > 0){
               //Startzeit fuer neuen Takt
               startTime = alb.get(alb.size()-1).getBeat().get(alb.get(alb.size()-1).getNoteCount()-1).getStartTime() //Startzeit der letzen Note des letzten Takts der Phrase
                       + alb.get(alb.size()-1).getBeat().get(alb.get(alb.size()-1).getNoteCount()-1).getDuration(); //Dauer der letzten Note des letzten Takts der Phrase
           }
           //fuelle fehlende Takte mit Pausen auf
           for (int i = alb.size(); i < beatCount; i++){
               Beat b = new Beat();
               NoteData nd = new NoteData(startTime, BEATLENGTH*ppqn, Integer.MIN_VALUE);
               nd.setQuantizedDuration(BEATLENGTH);
               startTime += BEATLENGTH*ppqn; //Startzeit um einen Takt weiter setzen
               b.addNote(nd);
               alb.add(b);
           }
       }
   }
   
   /**
    * only for debug purpose
    */
   private void getNotQuantizedNoteDurations(){
      for (ArrayList<NoteData> a : onSets){
         double Sum = 0.0;         
         for (int i=0; i<a.size(); i++){
            double d = a.get(i).getDuration()/ppqn;
            if (i==0){
                d = (a.get(i).getStartTime()+a.get(i).getDuration())/ppqn;
            }
            System.out.print(d +" " +a.get(i).getNote() +" ");
            writeToTextPane(d +" " +a.get(i).getNote() +" ");
            Sum += d;
         }
         out(" Length: " +Sum);
         writeToTextPane(" Length: " +Sum +"\n");
      }
   }
   
   /**
    * berechnet die quantisierten Notendauern
    * und unterteilt die Noten in Takte
    *
    * 0.125 ... zweiundreissigstel
    * 0.25  ... sechtzehntel
    * 0.5   ... achtel
    * 1.0   ... viertel
    * 2.0   ... halbe
    * 4.0   ... ganze
    */
   private void setQuantizedNoteDurations(){
      int Part = -1, Phrase, x = 0;
      for (ArrayList<NoteData> a : onSets){
         ArrayList<Beat> alb = new ArrayList<>();
         Beat b = new Beat();
         
         //Part- und Phrasennummer setzen
         Phrase = PartsPhrases.get(x);
         if (Phrase == 0){
             Part++;
         }
         b.setPart(Part);
         b.setPhrase(Phrase);
         b.setInstrument(Instruments.get(Part)); //setzt das Instrument des Parts
         x++;
         
         for (int i=0; i<a.size(); i++){
            double d = a.get(i).getDuration()/ppqn;
            //trenne Noten in Takte
            if (d > BEATLENGTH){ //lange Note oder Pause
               b = splitNoteInBeats(alb, b, a.get(i), Part, Phrase);
            } else { //Note passt in einen Takt
               d = getNoteQuantizedDuration(d); //Note quantisieren
               if (b.getBeatSum() + d <= BEATLENGTH){ //quantisierte Note passt in aktuellen Takt
                  NoteData nd = a.get(i).Clone();
                  nd.setDuration(d*ppqn); //Dauer anpassen
                  nd.setQuantizedDuration(d);
                  b.addNote(nd);
               } else {
                  alb.add(b); //alten Takt speichern
                  b = new Beat(); //neuen Takt beginnen
                  NoteData nd = a.get(i).Clone();
                  nd.setDuration(d*ppqn); //Dauer anpassen
                  nd.setQuantizedDuration(d);
                  b.setPart(Part);
                  b.setPhrase(Phrase);
                  b.setInstrument(Instruments.get(Part)); //setzt das Instrument des Parts
                  b.addNote(nd);
               }
            }
         }
         //falls ein leerer Takt aus splitNoteBeats zurueck kommt
         //und es keine weiteren Noten gibt, speichere diesen Beat nicht mehr
         if (b.getNoteCount() != 0){
            alb.add(b);
         }
         scoreInBeats.add(alb);
      }
   }
   
   /**
    * teilt lange Noten bzw. Pausen in eigene Takte auf und gibt die Restdauer zurueck
    * @param b aktueller Takt
    * @param nd Note
    * @param Part der aktuelle Part
    * @param Phrase die aktuelle Phrase
    * @return neuer Takt oder aktueller Takt mit Restdauer der Note
    */
   private Beat splitNoteInBeats(ArrayList<Beat> alb ,Beat b, NoteData nd, int Part, int Phrase){
      double duration = nd.getDuration()/ppqn;
      double newStartTime = nd.getStartTime();
      
      while (duration >= BEATLENGTH){ //solange die Dauer der Note groesser als die Taktlaenge
         if (BEATLENGTH - b.getBeatSum() != 0){
            double fit = getNoteQuantizedDuration(BEATLENGTH - b.getBeatSum()); //quantisierter Restplatz in aktuellem Takt
            NoteData n = nd.Clone();
            n.setDuration(fit*ppqn); //Dauer anpassen
            n.setStartTime(newStartTime); //Starttime anpassen
            newStartTime += n.getDuration(); //Starttime aktualisieren
            n.setQuantizedDuration(fit);
            b.addNote(n); //quantisierte Note zum aktuellen Takt hinzufuegen
            duration -= fit; //Restdauer aktualisieren
            alb.add(b); //Takt speichern
            b = new Beat(); //neuen Takt anlegen
            b.setPart(Part);
            b.setPhrase(Phrase);
            b.setInstrument(Instruments.get(Part)); //setzt das Instrument des Parts
         } else { //Takt ist voll
            alb.add(b); //Takt speichern
            b = new Beat(); //neuen Takt anlegen
            b.setPart(Part);
            b.setPhrase(Phrase);
            b.setInstrument(Instruments.get(Part)); //setzt das Instrument des Parts
         }
      }
      //Restdauer uebernehmen
      if (duration > 0.0 && !(duration < PAUSE && nd.getPitch() == Integer.MIN_VALUE)){
        NoteData n = nd.Clone();
        n.setDuration(duration*ppqn); //Dauer anpassen
        n.setStartTime(newStartTime); //Starttime anpassen
        n.setQuantizedDuration(getNoteQuantizedDuration(duration));
        b.addNote(n); //quantisierte Note hinzufuegen
      }
      return b; //neuer Takt oder aktueller Takt mit Restdauer der Note
   }
   
   /**
    * gibt die quantisierte Notendauer zurueck
    * 4.0   ... Ganze
    * 2.0   ... Halbe
    * 1.0   ... Viertel
    * 0.5   ... Achtel
    * 0.25  ... Sechtzehntel
    * 0.125 ... Zweiundreissigstel
    * @param duration Dauer der Note
    * @return quantisierte Notendauer
    */
    private double getNoteQuantizedDuration(double duration) {
        double whole = BEATLENGTH;

        /**
         1       = 32/32
         3/4     = 24/32
         1/2     = 16/32
         3/8     = 12/32
         1/4     =  8/32
         3/16    =  6/32
         1/8     =  4/32 =    8/64
         triplet         = 5.33/64
         1/16    =  2/32 =    4/64
         1/32    =  1/32 =    2/64
         **/ 

        if      (duration >= 28*whole/32) //Ganze
            return 4;
        //else if (duration >= 20*whole/32) //Halbe gepunktet
        //    return 3;
        else if (duration >= 14*whole/32) //Halbe
            return 2;
        //else if (duration >= 10*whole/32) //Viertel gepunktet
        //    return 1.5;
        else if (duration >=  7*whole/32) //Viertel
            return 1;
        //else if (duration >=  5*whole/32) //Achtel gepunktet
        //    return 0.75;
        else if (duration >=  6*whole/64) //Achtel
            return 0.5;
        else if (duration >=  5*whole/64) //Trippel
            return 0.33;
        else if (duration >=  3*whole/64) //Sechtzehntel
            return 0.25;
        else //Zweiundreissigstel
            return 0.125;
    }
   
    /**
     * Kurzform fuer System.out.println
     * @param s Zeichenkette
     */
   private void out(String s){
      System.out.println(s);
   }
   
   /**
    * wenn ein JTextPane gesetzt wurde wird der uebergebene String
    * in der TextPane angehaengt
    * @param s die Zeichenkette
    */
   private void writeToTextPane(String s){
       if (textPane != null) {
          StyledDocument doc = textPane.getStyledDocument();
           try {
               doc.insertString(doc.getLength(), s, null);
           } catch (BadLocationException ex) {
               Logger.getLogger(PolyphonMotifDetection.class.getName()).log(Level.SEVERE, null, ex);
           }
       }
   }

   /**
    * gibt alle Noten in Takten getrennt aus
    * schreibt die Daten in die Console (Debug)
    * bzw. in einen JTextPane, falls dieser gesetzt wurde
    */
   private void printNotesInBeats() {
      double Sum = 0.0;
      double Beatlength = 0.0;
      
      for (ArrayList<Beat> alb : scoreInBeats){
         for (Beat b : alb){
            System.out.print("| "); //Takttrennzeichen
            writeToTextPane("| ");
            for (NoteData sd : b.getBeat()){
               Beatlength += sd.getQuantizedDuration();
               System.out.print(sd.getQuantizedDuration() +" ");
               writeToTextPane(sd.getQuantizedDuration() +" ");
               Sum += sd.getQuantizedDuration();
            }
            System.out.print("(" +Beatlength +")");
            writeToTextPane("(" +Beatlength +")");
            Beatlength = 0.0;
         }
         out(" Length: " +Sum); //Zeilenumbruch
         writeToTextPane(" Length: " +Sum +"\n");
         Sum = 0.0;
      }
   }
   
   /**
    * gibt alle Noten gemapped auf einzelne Zeichen 
    * und getrennt in Takte aus. Die Laenge eines Taktes
    * wird zur besseren Lesbarkeit an den Takt mit den
    * meisten Noten angepasst
    * schreibt die Daten in die Console (Debug)
    * bzw. in einen JTextPane, falls dieser gesetzt wurde
    * 
    * 4.0   = G = Ganze
    * 2.0   = H = Halbe
    * 1.0   = V = Viertel
    * 0.5   = A = Achtel
    * 0.25  = S = Sechtzehntel
    * 0.125 = Z = Zweiundreissigstel
    */
   private void printNotesInBeatsAlternate() {
      int oldPart = 0;
      
      for (ArrayList<Beat> alb : scoreInBeats){
         int i=0;
         if (oldPart != alb.get(0).getPart()){
             oldPart = alb.get(0).getPart();
             out("");
             writeToTextPane("\n");
         }
         for (Beat b : alb){
            System.out.print("|"); //Takttrennzeichen
            writeToTextPane("|");
            //maximal 32 Zweiundreissigstel pro Takt
            int spaces = getMaxBeatNoteCount(i) - b.getNoteCount();
            for (NoteData sd : b.getBeat()){
               double d = sd.getQuantizedDuration();
               switch (d+""){
                   case "4.0":
                       System.out.print("G");
                       writeToTextPane("G");
                       break;
                   case "2.0":
                       System.out.print("H");
                       writeToTextPane("H");
                       break;
                   case "1.0":
                       System.out.print("V");
                       writeToTextPane("V");
                       break;
                   case "0.5":
                       System.out.print("A");
                       writeToTextPane("A");
                       break;
                   case "0.33":
                       System.out.print("T");
                       writeToTextPane("T");
                       break;
                   case "0.25":
                       System.out.print("S");
                       writeToTextPane("S");
                       break;
                   case "0.125":
                       System.out.print("Z");
                       writeToTextPane("Z");
                       break;
               }
            }
            for (int x = 0; x < spaces; x++){
                System.out.print(" ");
                writeToTextPane(" ");
            }
            i++;
         }
         out("");
         writeToTextPane("\n");
      }
   }
   
   /**
    * liefert die Anzahl der meisten Noten aller Phrasen im aktuellen Takt
    * @param beat Index des Beats in der ArrayList
    * @return maximale Anzahl an Noten
    */
   private int getMaxBeatNoteCount(int beat){
       int maxNoteCount = 1;
       for (ArrayList<Beat> alb : scoreInBeats){
           if (alb.size() > beat){
              if (alb.get(beat).getNoteCount() > maxNoteCount){
                  maxNoteCount = alb.get(beat).getNoteCount();
              }
           }
       }
       return maxNoteCount;
   }
   
   /**
    * gibt die gewuenschten Daten der Noten aus
    * schreibt die Daten in die Console (Debug) bzw. in einen JTextPane,
    * falls dieser gesetzt wurde
    * @param marker gibt an, welche Daten ausgegeben werden sollen
    * 
    *  0 ... Starttime
    *  1 ... MarkingOne
    *  2 ... markingTwo
    *  3 ... markingThree
    *  4 ... markinFour
    *  5 ... Duration
    *  6 ... Pitch (int)
    *  7 ... Note (Pitch as String)
    *  8 ... MappingOne
    *  9 ... MappingTwo
    * 10 ... MappingThree
    * 11 ... MappingFour
    */
   private void printArrayList(int marker){
      for (ArrayList<NoteData> a : onSets){
         for (NoteData d : a){
            switch (marker) {
                case 0:
                    System.out.print(d.getStartTime() +" ");
                    writeToTextPane(d.getStartTime() +" ");
                    break;
                /*case 1:
                    System.out.print(d.getMarkingOne() +" ");
                    writeToTextPane(d.getMarkingOne() +" ");
                    break;
                case 2:
                    System.out.print(d.getMarkingTwo() +" ");
                    writeToTextPane(d.getMarkingTwo() +" ");
                    break;
                case 3:
                    System.out.print(d.getMarkingThree() +" ");
                    writeToTextPane(d.getMarkingThree() +" ");
                    break;
                case 4:
                    System.out.print(d.getMarkingFour() +" ");
                    writeToTextPane(d.getMarkingFour() +" ");
                    break;*/  
                case 5:
                    System.out.print(d.getDuration() +" ");
                    writeToTextPane(d.getDuration() +" ");
                    break;
                case 6:
                    System.out.print(d.getPitch() +" ");
                    writeToTextPane(d.getPitch() +" ");
                    break;
                case 7:
                    System.out.print(d.getNote() +" ");
                    writeToTextPane(d.getNote() +" ");
                    break;
                /*case 8:
                    System.out.print(d.getMappingOne() +" ");
                    writeToTextPane(d.getMappingOne() +" ");
                    break;
                case 9:
                    System.out.print(d.getMappingTwo() +" ");
                    writeToTextPane(d.getMappingTwo() +" ");
                    break;
                case 10:
                    System.out.print(d.getMappingThree() +" ");
                    writeToTextPane(d.getMappingThree() +" ");
                    break;
                case 11:
                    System.out.print(d.getMappingFour() +" ");
                    writeToTextPane(d.getMappingFour() +" ");
                    break;*/
            }
         }
         System.out.println();
      }
   }
   
   /**
    * gibt die gewuenschten Daten der Noten getrennt in Takte aus
    * schreibt die Daten in die Console (Debug) bzw. in einen JTextPane,
    * falls dieser gesetzt wurde
    * @param marker gibt an, welche Daten ausgegeben werden sollen
    * 
    *  0 ... Starttime
    *  1 ... MarkingOne
    *  2 ... MarkingTwo
    *  3 ... MarkingThree
    *  4 ... MarkinFour
    *  5 ... Duration
    *  6 ... Pitch (int)
    *  7 ... Note (Pitch as String)
    *  8 ... MappingOne
    *  9 ... MappingTwo
    * 10 ... MappingThree
    * 11 ... MappingFour
    * 12 ... Quantized Duration
    */
   private void printDataInBeats(int marker) {     
      for (ArrayList<Beat> alb : scoreInBeats){
         for (Beat b : alb){
            System.out.print("| "); //Takttrennzeichen
            writeToTextPane("| ");
            for (NoteData d : b.getBeat()){
                switch (marker) {
                    case 0:
                        System.out.print(d.getStartTime() +" ");
                        writeToTextPane(d.getStartTime() +" ");
                        break;
                    /*case 1:
                        System.out.print(d.getMarkingOne() +" ");
                        writeToTextPane(d.getMarkingOne() +" ");
                        break;
                    case 2:
                        System.out.print(d.getMarkingTwo() +" ");
                        writeToTextPane(d.getMarkingTwo() +" ");
                        break;
                    case 3:
                        System.out.print(d.getMarkingThree() +" ");
                        writeToTextPane(d.getMarkingThree() +" ");
                        break;
                    case 4:
                        System.out.print(d.getMarkingFour() +" ");
                        writeToTextPane(d.getMarkingFour() +" ");
                        break;*/  
                    case 5:
                        System.out.print(d.getDuration() +" ");
                        writeToTextPane(d.getDuration() +" ");
                        break;
                    case 6:
                        if (d.getPitch() == Integer.MIN_VALUE){
                            System.out.print("REST ");
                            writeToTextPane("REST ");
                        } else {
                            System.out.print(d.getPitch() +" ");
                            writeToTextPane(d.getPitch() +" ");
                        }
                        break;
                    case 7:
                        System.out.print(d.getNote() +" ");
                        writeToTextPane(d.getNote() +" ");
                        break;
                    /*case 8:
                        System.out.print(d.getMappingOne() +" ");
                        writeToTextPane(d.getMappingOne() +" ");
                        break;
                    case 9:
                        System.out.print(d.getMappingTwo() +" ");
                        writeToTextPane(d.getMappingTwo() +" ");
                        break;
                    case 10:
                        System.out.print(d.getMappingThree() +" ");
                        writeToTextPane(d.getMappingThree() +" ");
                        break;
                    case 11:
                        System.out.print(d.getMappingFour() +" ");
                        writeToTextPane(d.getMappingFour() +" ");
                        break;*/
                    case 12:
                        System.out.print(d.getQuantizedDuration() +" ");
                        writeToTextPane(d.getQuantizedDuration() +" ");
                        break;
                }
            }
         }
         out(""); //Zeilenumbruch
         writeToTextPane("\n");
      }
   }
   
   /**
    * gibt die Anzahl der Takte pro Phrase aus
    * schreibt die Daten in die Console (Debug) bzw. in einen JTextPane,
    * falls dieser gesetzt wurde
    */
   private void printBeatCountPerPhrase(){
       int i = 0;
       for (ArrayList<Beat> alb : scoreInBeats){
           out("Phrase " +i +": " +alb.size());
           writeToTextPane("Phrase " +i +": " +alb.size() +"\n");
           i++;
       }
   }
   
   /**
    * Marking1 uses the onset information to mark
    * repetitions : are weighted with the weight 1 the
    * events which ioi (inter-onset) with the event
    * before has been repeated at least one time
    * consecutively. The notion of repetition is not
    * absolute for performed music, so a threshold is
    * considered.
    * 
    * For instance :
    * the onset sequence (in ms) :
    *           (0 10 50 90 110)
    * is marked (?  0  0  1  0)
    * 
    * Source: A causal algorithm for beat-tracking - Benoit Meudic
    */
   /*private void setMarkingOne(){
      for (ArrayList<NoteData> ald : onSets) {
         if (ald.size() > 0){
            ald.get(0).setMarkingOne(Double.MIN_VALUE);
         }
         for (int i=1; i<ald.size(); i++){
            ald.get(i).setMarkingOne(ald.get(i).getStartTime()-ald.get(i-1).getStartTime());
         }
      }
   }*/
   
   /**
    * Marking2 also uses the onset information to mark
    * long inter-onsets : are marked with the weight x
    * the events which ioi whith the next event is
    * greater than the x consecutive past ioi. There
    * again, a threshold is considered.
    * 
    * For instance :
    * the onset sequence (in ms) :
    *           (0 10 50 100 110)
    * is marked (0  1  2   0  ?)
    * 
    * Source: A causal algorithm for beat-tracking - Benoit Meudic
    */
   /*private void setMarkingTwo(){      
      for (ArrayList<NoteData> ald : onSets) {
         for (int i=0; i<ald.size()-1; i++){
            ald.get(i).setMarkingTwo(ald.get(i).getMarkingOne()+ald.get(i+1).getMarkingOne());
         }
         ald.get(ald.size()-1).setMarkingTwo(Double.MIN_VALUE);
      }
   }*/
   
   /**
    * Marking3 uses the pitch information to mark the
    * note densities : are marked with the weight 1 the
    * events which have more than one note. Notes with
    * slight onset differences are considered as being
    * part of the same event.
    * 
    * For instance :
    * the sequence ((C) (C E G) (D) (G B D) (C))
    * is marked    ( 0     1     0     1     0 )
    * 
    * Source: A causal algorithm for beat-tracking - Benoit Meudic
    */
   /*private void setMarkingThree(){
      
   }*/
   
   /**
    * Marking4 uses the onset and duration features to
    * mark coverings : are marked with the weight x the
    * events which occur at the end of the duration of a
    * previous event which covers x>1 events.
    * 
    * For instance :
    * the sequence (onset, duration) :
    *           ((0,90) (10,40) (50,40) (90,20) (110,?))
    * is marked (  0       0       0       2        0 )
    * 
    * Source: A causal algorithm for beat-tracking - Benoit Meudic
    */
   /*private void setMarkingFour(){
      
   }*/
   
   /**
    * setzt die Gewichtung fuer MarkerOne
    */
   /*private void setMappingOne(){
      for (ArrayList<NoteData> a : onSets){
         if (a.size() > 0){
             a.get(0).setMappingOne(0);
         }
         for (int i=1; i<a.size(); i++){
            if (a.get(i).getMarkingOne() == a.get(i-1).getMarkingOne()){
               a.get(i).setMappingOne(1);
            } else {
               a.get(i).setMappingOne(0);
            }
         }
      }
   }*/
   
   /**
    * setzt die Gewichtung fuer MarkerTwo
    */
   /*private void setMappingTwo(){
      int ZeroCount = 1;
      boolean wasBigger = false;
      
      for (ArrayList<NoteData> a : onSets){
         if (a.size() > 0){
             a.get(0).setMappingTwo(0);
         }
         for (int i=1; i<a.size(); i++){
            if (a.get(i).getMarkingTwo() > a.get(i-1).getMarkingTwo()){
               a.get(i).setMappingTwo(ZeroCount);
               ZeroCount++;
               wasBigger = true;
            } else {
               if (wasBigger) {
                  ZeroCount = 0;
                  wasBigger = false;
               }
               ZeroCount++;
               a.get(i).setMappingTwo(0);
            }
         }
         ZeroCount=1;
      }
   }*/
   
   /** In Midi Files, time is measured in pulses.  Notes that have
    * pulse times that are close together (like within 10 pulses)
    * will sound like they're the same chord.  We want to draw
    * these notes as a single chord, it makes the sheet music much
    * easier to read.  We don't want to draw notes that are close
    * together as two separate chords.
    *
    * The SymbolSpacing class only aligns notes that have exactly the same
    * start times.  Notes with slightly different start times will
    * appear in separate vertical columns.  This isn't what we want.
    * We want to align notes with approximately the same start times.
    * So, this function is used to assign the same starttime for notes
    * that are close together (timewise).
    */
    /*private void roundStartTimes() {
        // Get all the starttimes in all tracks, in sorted order
        ArrayList<Double> starttimes = new ArrayList<>();
        for (ArrayList<NoteData> al : onSets){
            for (NoteData sd : al){
                starttimes.add(sd.getStartTime());
            }
        }
        Collections.sort(starttimes);

        // Notes within interval apart will be combined.
        double interval = ppqn * tempo / 1000;

        // If two starttimes are within interval millisec, make them the same
        for (int i = 0; i < starttimes.size() - 1; i++) {
                if (starttimes.get(i+1) - starttimes.get(i) <= interval) {
                        starttimes.set(i+1, starttimes.get(i));
                }
        }

        // Adjust the note starttimes, so that it matches one of the starttimes values
        for (ArrayList<NoteData> al : onSets){
            int i = 0;
            for (NoteData sd : al){
               while (i < starttimes.size() && sd.getStartTime() - interval > starttimes.get(i)) {
                  i++;
               }
               if (sd.getStartTime() > starttimes.get(i) && sd.getStartTime() - starttimes.get(i) <= interval) {
                  sd.setStartTime(starttimes.get(i));
               }
            }
        }
    }*/
   
    /** We want note durations to span up to the next note in general.
    * The sheet music looks nicer that way.  In contrast, sheet music
    * with lots of 16th/32nd notes separated by small rests doesn't
    * look as nice.  Having nice looking sheet music is more important
    * than faithfully representing the Midi File data.
    *
    * Therefore, this function rounds the duration of MidiNotes up to
    * the next note where possible.
    */
    /*private void roundDurations() {
        for (ArrayList<NoteData> al : onSets){
            NoteData prevNote = null;
            for (int i=0; i < al.size()-1; i++){
                NoteData note1 = al.get(i);
                if (prevNote == null){
                    prevNote = note1;
                }
                NoteData note2 = note1;
                // Get the next note that has a different start time
                for (int j=i+1; j < al.size(); j++){
                    note2 = al.get(j);
                    if (note1.getStartTime() < note2.getStartTime()){
                        break;
                    }
                }
                double maxduration = note2.getStartTime() - note1.getStartTime();
                double dur = 0.0;
                if (ppqn <= maxduration)
                        dur = ppqn;
                else if (ppqn/2 <= maxduration)
                        dur = ppqn/2;
                else if (ppqn/3 <= maxduration)
                        dur = ppqn/3;
                else if (ppqn/4 <= maxduration)
                        dur = ppqn/4;


                if (dur < note1.getDuration()) {
                    dur = note1.getDuration();
                }

                // Special case: If the previous note's duration
                // matches this note's duration, we can make a notepair.
                // So don't expand the duration in that case.
                if ((prevNote.getStartTime() + prevNote.getDuration() == note1.getStartTime()) && (prevNote.getDuration() == note1.getDuration())) {
                    dur = note1.getDuration();
                }
                note1.setDuration(dur);
                if (al.get(i+1).getStartTime() != note1.getStartTime()) {
                    prevNote = note1;
                }
            }
        }
    }*/
}
