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

import data.Beat;
import data.MotifData;
import data.MultiBeat;
import data.MultiMotifData;
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;

/**
 *
 * @author Tom
 */
public class PolyphonMotifDetection {
   ArrayList<ArrayList<Beat>> scoreInBeats = null;
   private JTextPane textPane = null;
   private int numerator = 4;
   private int denominator = 4;
   private String key = "C major";
   private ArrayList<ArrayList<ArrayList<Integer>>> alRepetitions = new ArrayList<>(); //alle Parts, alle SpaltenIndices, jedes Taktmatches
   private ArrayList<ArrayList<ArrayList<Integer>>> alPossibleKrebs = new ArrayList<>(); //alle Parts, alle SpaltenIndices, jedes alternativen Krebs Matches
   private ArrayList<ArrayList<Integer>> alMatches = new ArrayList<>(); //doppelte ArrayList mit den Spalten-Indices der Matches pro Takt pro Part
   private ArrayList<ArrayList<Integer>> alKrebs = new ArrayList<>(); //doppelte ArrayList mit den Spalten-Indices der Krebse pro Takt pro Part
   private ArrayList<ArrayList<ArrayList<MotifData>>> alMotifData = new ArrayList<>(); //ArrayList mit allen Motifen und deren Anzahl an Vorkommnissen pro Part
   private ArrayList<ArrayList<ArrayList<MultiMotifData>>> alMultiMotifData = new ArrayList<>(); //ArrayList mit allen Motifen und deren Anzahl an Vorkommnissen pro Part
   
   /**
    * Konstruktor
    * @param numerator Numerator
    * @param denominator Denominator
    * @param key Tonart
    */
   public PolyphonMotifDetection(int numerator, int denominator, String key) {
       this.numerator = numerator;
       this.denominator = denominator;
       this.key = key;
   }
   
   /**
    * Konstruktor mit TextPane
    * Log-Ausgaben werden in die TextPane geschrieben
    * @param numerator Numerator
    * @param denominator Denominator
    * @param key Tonart
    * @param textPane TextPane fuer die Logausgaben
    */
   public PolyphonMotifDetection(int numerator, int denominator, String key, JTextPane textPane) {
       this.numerator = numerator;
       this.denominator = denominator;
       this.key = key;
       this.textPane = textPane;
   }
   
   /**
    * detektiert monophone und polyphone Motife
    * der Form Wiederholung, Sequenz, Krebs und Spiegel
    * die Motiflaenge entspricht genau der Laenge eines Takts
    * 
    * die erkannten Motife werden getrennt in ArrayLists abgelegt und koennen
    * mittels getMonophonMotifs bzw. getPolyphonMotifs ausgelesen werden
    * @param scoreInBeats der quantisierte Score unterteilt in Takte
    */
   public void detectMotifs(ArrayList<ArrayList<Beat>> scoreInBeats){
       this.scoreInBeats = scoreInBeats;
       int PartCount = getPartCount();
       int from = -1, to = 0;
       
       //fuer jeden Part
       for (int i=0; i<PartCount; i++){
           //fuer jede Phrase
           for (int j=0; j<scoreInBeats.size(); j++){
               if (scoreInBeats.get(j).size() > 0 && scoreInBeats.get(j).get(0).getPart() == i){
                   if (from == -1){
                       from = j;
                   }
                   to = j;
               }
           }
           int maxBeatLength = getMaxBeatLength();
           //alle Takte auf Strings mappen
           String[][] sBeats = mapBeatToString(from, to, maxBeatLength);
           //Indices aller Takte in Liste geben;
           //diese werden bei jedem rekursivem Durchlauf weniger,
           //da alle Takte, die gleich sind wie das Template (Matches)
           //bzw. zumindest das Template selbst geloescht werden
           ArrayList<Integer> indicesToDo = new ArrayList<>();
           for (int x=0; x<maxBeatLength;x++){
               indicesToDo.add(x);
           }
           //finde Motife
           getDifferentMotifs(sBeats, indicesToDo, true);
           
           indicesToDo = new ArrayList<>();
           for (int x=0; x<maxBeatLength;x++){
               indicesToDo.add(x);
           }
           //finde alternative Krebse (bisher wurden nur Krebse gefunden, die nur aus einer Tonlaenge pro Takt bestehen)
           //bisher nur zB.: VVVV -> VVVV, HH -> HH, usw.
           //aber zB koennte HVV und VVH ein Krebs sein
           getAlternateKrebs(sBeats, indicesToDo, true);
           
           //Motife des Part hinzufuegen
           //auch wenn keine Motife gefunden leeren Eintrag hinzufuegen,
           //um den Part darzustellen
           alRepetitions.add(alMatches);
           alPossibleKrebs.add(alKrebs);
           
           //Neuinitialisierung
           alMatches = new ArrayList<>();
           alKrebs = new ArrayList<>();
           from = -1;
           to = 0;
       }
       //Ausgabe der Parts mit den aehnlichen Takten
       if (textPane != null){
            outline("Parts & Matches--------------\n");
            writeToTextPane("Parts & Matches--------------\n\n");
            printMatches();
            outline("Possible Odd Krebs--------------\n");
            writeToTextPane("Possible Odd Krebs--------------\n\n");
            printPossibleKrebsMatches();
       }
       
       //aus den vorgefilterten Takten die Motive suchen
       getMotifs();
       getMotifsFromOddKrebs();
       
       //Ausgabe der Parts mit gefundenen Motiven
       if (textPane != null){
            outline("Monophon Motifs--------------\n");
            writeToTextPane("Monophon Motifs--------------\n\n");
            printMotifs();
            outline("Polyphon Motifs--------------\n");
            writeToTextPane("Polyphon Motifs--------------\n\n");
            printMultiMotifs();
       }
   }
   
   /**
    * liefert eine Arrayliste der monophonen Motife
    * unterteilt in Part->Motif->Motifart (Rep, Seq, Krebs, Spiegel)
    * @return Arrayliste der monophonen Motife
    */
   public ArrayList<ArrayList<ArrayList<MotifData>>> getMonophonMotifs(){
       return alMotifData;
   }
   
   /**
    * liefert eine Arrayliste der polyphonen Motife
    * unterteilt in Part->Motif->Motifart (Rep, Seq, Krebs, Spiegel)
    * @return Arrayliste der polyphonen Motife
    */
   public ArrayList<ArrayList<ArrayList<MultiMotifData>>> getPolyphonMotifs(){
       return alMultiMotifData;
   }
   
   /**
    * mappt jede Note eines Taktes auf einen String
    * (fuer spaetere (String-)Vergleiche von Takten)
    * 
    * 4.0   = G = Ganze
    * 2.0   = H = Halbe
    * 1.0   = V = Viertel
    * 0.5   = A = Achtel
    * 0.25  = S = Sechtzehntel
    * 0.125 = Z = Zweiundreissigstel
    * 
    * @param from ID der ersten Phrase des Parts
    * @param to ID der letzten Phrase des Parts
    * @param maxBeatLength Anzahl der maximalen Takte im Score
    * @return zweidimensionales StringArray (Zeilen: Phrasen, Spalten: Takte der Phrase)
    */
   private String[][] mapBeatToString(int from, int to, int maxBeatLength) {
                                    //Anzahl Phrasen //Anzahl Takte
      String[][] sBeats = new String[to-from+1][maxBeatLength];
      
      for (int i=0; i<sBeats.length; i++){
          for (int j=0; j<sBeats[i].length; j++){
              sBeats[i][j] = "";
          }
      }
      
      if (scoreInBeats != null && to < scoreInBeats.size() && from >= 0){
        for (int phrase=from; phrase <= to; phrase++){
           for (int beat=0; beat < scoreInBeats.get(phrase).size(); beat++){
              //maximal 32 Zweiundreissigstel pro Takt
              for (NoteData sd : scoreInBeats.get(phrase).get(beat).getBeat()){
                 double d = sd.getQuantizedDuration();
                 if (Double.compare(d, 4.0) == 0 && sd.getPitch() == Integer.MIN_VALUE){ //Takt besteht nur aus einer Pause
                    sBeats[phrase-from][beat] += "R";
                 } else {
                    sBeats[phrase-from][beat] += getStringFromDuration(d);
                 }
              }
           }
        }
      }
      return sBeats;
   }
   
   /**
    * liefert zu einer quantisierten Notendauer das zugehoerige Zeichen
    * @param d quantisierte Notendauer (4.0, 2.0, 1.0, 0.5, ...)
    * @return gemappted Zeichen
    */
   private String getStringFromDuration(double d){
       switch (d+""){
        case "4.0":
            return "G";
        case "2.0":
            return "H";
        case "1.0":
            return "V";
        case "0.5":
            return "A";
        case "0.33":
            return "T";
        case "0.25":
            return "S";
        case "0.125":
            return "Z";
        default:
            return "";
        }
   }
   
   /**
    * nur zu Debugging-Zwecken
    * gibt das StringArray geteilt in Takte aus
    */
   private void printBeats(String[][] sB){
       for (int i=0; i<sB.length; i++){
          out("|");
          writeToTextPane("|");
          for (int j=0; j<sB[i].length; j++){
              out(sB[i][j] +"|");
              writeToTextPane(sB[i][j] +"|");
          }
          out("\n");
          writeToTextPane("\n");
       }
       out("\n");
       writeToTextPane("\n");
   }
   
   /**
     * gibt die Anzahl maximalen Takte des aktuellen Scores zurueck
     * (diese sollten nach der Quantisierung bei jeder Phrase gleich sein)
     * @return Anzahl der maximalen Takte
     */
   private int getMaxBeatLength(){
       int max = 0;
       for (ArrayList<Beat> alb : scoreInBeats){
           if (alb.size() > max){
               max = alb.size();
           }
       }
       return max;
   }
   
   /**
    * liefert die maximale Notenanzahl eines Takts eines Part
    * @param Beat der gesuchte Takt
    * @param Part der aktuelle Part
    * @return maximale Anzahl der Noten im Takt des Parts
    */
   private int getMaxNoteCountFromBeatInPhrase(int Beat, int Part){
       int max = 0;
       for (ArrayList<Beat> alb : scoreInBeats){
           if (alb.size() > Beat && alb.get(Beat).getPart() == Part){
               if (max < alb.get(Beat).getNoteCount()){
                   max = alb.get(Beat).getNoteCount();
               }
           }
       }
       return max;
   }
   
   /**
     * gibt die Anzahl der Phrasen des aktuellen Scores zurueck
     * @return Anzahl der Phrasen
     */
    private int getPhraseCount(){
        if (scoreInBeats != null){
            return scoreInBeats.size();
        } else {
            return 0;
        }
    }
   
   /**
     * gibt die Anzahl der Parts des aktuellen Scores zurueck
     * @return Anzahl der Parts
     */
    private int getPartCount(){
        if (scoreInBeats != null && scoreInBeats.size() > 0){
            if (scoreInBeats.get(scoreInBeats.size()-1).size() > 0){
                return scoreInBeats.get(scoreInBeats.size()-1).get(0).getPart()+1;
            }
        }
        return 0;
    }
   
   /**
     * Kurzform fuer System.out.print
     * @param s Zeichenkette
     */
   private void out(String s){
      System.out.print(s);
   }
   
   /**
     * Kurzform fuer System.out.println
     * @param s Zeichenkette
     */
   private void outline(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);
           }
       }
   }

   /**
    * liefert alle Wiederholungen von bestimmten Taktmustern
    * @param sBeats ein Array mit den Takten in Strings codiert (pro Phrase eine Zeile, pro Spalte ein Takt)
    * @param indicesToDo IDs der Takte, welche noch nicht als Muster dran waren bzw. noch nicht als Match vorkamen
    * @param FirstTime gibt an, ob es der erste Aufruf ist (restlichen Aufrufe sind rekursiv)
    */
   private void getDifferentMotifs(String[][] sBeats, ArrayList<Integer> indicesToDo, boolean FirstTime) {             
      if (indicesToDo.size() > 1){ //letzter Eintrag hat keine Vergleichstakte mehr
          if (FirstTime) { //Takte, die nur aus Pausen bestehen muessen nur im ersten Durchlauf geloescht werden
            //besteht eine Spalte nur aus Pausen => wieder auf false setzen und ignorieren
            ArrayList<Integer> Rest = new ArrayList<>();
            for (int j : indicesToDo){ //nur mehr verbleibende Indices verwenden
                boolean OnlyRest = true;
                for (int i=0; i<sBeats.length; i++){          
                    if (!sBeats[i][j].equalsIgnoreCase("R")){
                        OnlyRest = false;
                    }
                }
                if (OnlyRest){
                    Rest.add(j);
                }
            }

            //alle Takte, die nur aus Pausen bestehen ignorieren
            for (int x : Rest){
                if (indicesToDo.contains(x)){
                    int keyLocation = indicesToDo.indexOf(x);
                    indicesToDo.remove(keyLocation);
                }
            }
          }
          
          //boolsches Array zum speichern der gefundenen Elemente
          boolean[][] bBeats = new boolean[sBeats.length][sBeats[0].length];
          //Initialisierung mit "false"
          for (int i=0; i<bBeats.length; i++){
              for (int j : indicesToDo){ //nur mehr verbleibende Indices verwenden
                  bBeats[i][j] = false;
              }
          }

          //nach Matches suchen
          if (indicesToDo.size() > 0){ //es kann sein, dass in der ArrayList "done" schon alle verbleibenden Indices waren und keine mehr vorhanden sind
             for (int i=0; i<sBeats.length; i++){
                String template = sBeats[i][indicesToDo.get(0)]; //immer ersten verbleibenden Takt als Muster nehmen
                for (int j : indicesToDo){ //nur mehr verbleibende Indices verwenden
                    if (sBeats[i][j].equalsIgnoreCase("R") //Takt besteht nur aus einer Pause (ist daher fuer das Motiv nicht relevant)
                        || sBeats[i][j].equalsIgnoreCase(template)){ //oder Match gefunden
                        bBeats[i][j] = true;
                    }
                }
             }
          }
          
          //Matches auswerten (Spaltenweise; Spalten mit nur "true" sind moegliche Wiederholungen
          ArrayList<Integer> tempMatches = new ArrayList<>();
          for (int j : indicesToDo){ //nur mehr verbleibende Indices verwenden
              boolean bMatch = true;
              for (int i=0; i<bBeats.length; i++){          
                  if (bBeats[i][j] == false){
                      bMatch = false;
                      break; //kein Match => aufhoeren in dieser Spalte
                  }
              }
              if (bMatch){ //alle auf true => moeglicher Match
                  tempMatches.add(j);
              }
          }
          //alle gefundenen Matches aus den Indices zum suchen streichen
          for (int x : tempMatches){
              if (indicesToDo.contains(x)){
                  int keyLocation = indicesToDo.indexOf(x);
                  indicesToDo.remove(keyLocation);
              }
          }
          //wenn Size = 1, dann kein Match, sondern nur mit sich selbst gleich
          if (tempMatches.size() > 1){
              alMatches.add(tempMatches);
          }
          
          //rekursiver Aufruf der Funktion mit kleinerem StringArray
          getDifferentMotifs(sBeats, indicesToDo, false);
      }
   }
   
   /**
    * liefert alle Wiederholungen von Taktmustern mit den gleichen Notenlaengen
    * @param sBeats ein Array mit den Takten in Strings codiert (pro Phrase eine Zeile, pro Spalte ein Takt)
    * @param indicesToDo IDs der Takte, welche noch nicht als Muster dran waren bzw. noch nicht als Match vorkamen
    * @param FirstTime gibt an, ob es der erste Aufruf ist (restlichen Aufrufe sind rekursiv)
    */
   private void getAlternateKrebs(String[][] sBeats, ArrayList<Integer> indicesToDo, boolean FirstTime) {             
      if (indicesToDo.size() > 1){ //letzter Eintrag hat keine Vergleichstakte mehr
          //muss nur beim ersten Aufruf ausgefuehrt werden
          if (FirstTime){
            //alle Matches, die nur aus Takten mit gleichen Notenlaengen bestehen ignorieren,
            //da diese schon bei den anderen Motiven abgearbeitet wurden
            ArrayList<Integer> done = new ArrayList<>();
            for (int j : indicesToDo){
                boolean OnlySame = true;
                for (int i=0; i<sBeats.length; i++){
                    if (!(sBeats[i][j].equalsIgnoreCase("R") || sBeats[i][j].equalsIgnoreCase("G") || sBeats[i][j].equalsIgnoreCase("HH") || 
                            sBeats[i][j].equalsIgnoreCase("VVVV") || sBeats[i][j].equalsIgnoreCase("AAAAAAAA") || sBeats[i][j].equalsIgnoreCase("TTTTTTTTTTTT") ||
                            sBeats[i][j].equalsIgnoreCase("SSSSSSSSSSSSSSSS") || sBeats[i][j].equalsIgnoreCase("ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"))){
                        OnlySame = false;
                    }
                }
                if (OnlySame){
                    done.add(j);
                }
             }
             //alle Vorkommnisse loeschen
             for (int x : done){
                if (indicesToDo.contains(x)){
                    int keyLocation = indicesToDo.indexOf(x);
                    indicesToDo.remove(keyLocation);
                }
             }
          }
          
          //boolsches Array zum speichern der gefundenen Elemente
          boolean[][] bBeats = new boolean[sBeats.length][sBeats[0].length];
          //Initialisierung mit "false"
          for (int i=0; i<bBeats.length; i++){
              for (int j : indicesToDo){ //nur mehr verbleibende Indices verwenden
                  bBeats[i][j] = false;
              }
          }

          //nach Matches suchen
          if (indicesToDo.size() > 0){ //es kann sein, dass in der ArrayList "done" schon alle verbleibenden Indices waren und keine mehr vorhanden sind
             ArrayList<Integer> doubleEntry = new ArrayList<>();
             for (int i=0; i<sBeats.length; i++){
                String template = sBeats[i][indicesToDo.get(0)]; //immer ersten verbleibenden Takt als Muster nehmen
                String reverseTemplate = new StringBuffer(template).reverse().toString(); //Vorlage umdrehen um moegliche Krebse zu finden (HVV -> VVH)

                for (int j : indicesToDo){ //nur mehr verbleibende Indices verwenden
                   if (sBeats[i][j].equalsIgnoreCase(reverseTemplate)){ //Match gefunden
                       bBeats[i][j] = true;
                   }
                   if (i == 0 && sBeats[i][j].equalsIgnoreCase(template)){ //Takt ist gleich wie die Vorlage (wird nur das erste Mal geprueft)
                       if (isSameAsTemplate(sBeats,indicesToDo.get(0), j)){
                          doubleEntry.add(j);
                       }
                   }
                }
             }
             
             //Takte, die gleich wie die Vorlage sind koennen ignoriert werden
             for (int x : doubleEntry){
                if (indicesToDo.contains(x)){
                    int keyLocation = indicesToDo.indexOf(x);
                    indicesToDo.remove(keyLocation);
                }
             }
          }
          
          //Matches auswerten (Spaltenweise; Spalten mit nur "true" sind moegliche Krebse
          ArrayList<Integer> tempMatches = new ArrayList<>();
          for (int j : indicesToDo){ //nur mehr verbleibende Indices verwenden
              boolean bMatch = true;
              for (int i=0; i<bBeats.length; i++){          
                  if (bBeats[i][j] == false){
                      bMatch = false;
                      break; //kein Match => aufhoeren in dieser Spalte
                  }
              }
              if (bMatch){ //alle auf true => moeglicher Match
                  tempMatches.add(j);
              }
          }
          
          //alle gefundenen Matches aus den Indices zum suchen streichen
          for (int x : tempMatches){
              if (indicesToDo.contains(x)){
                  int keyLocation = indicesToDo.indexOf(x);
                  indicesToDo.remove(keyLocation);
              }
          }
          
          //wenn Size = 1, dann kein Match, sondern nur mit sich selbst gleich
          if (tempMatches.size() > 1){
            alKrebs.add(tempMatches);
          }
          
          //rekursiver Aufruf der Funktion mit kleinerem StringArray
          getAlternateKrebs(sBeats, indicesToDo, false);
      }
   }

   /**
    * gibt an, ob beide Takte gleich sind
    * @param sBeats Array mit den Takten als Strings
    * @param templateId ID im Array der Vorlage
    * @param compareId ID im Array des Vergleichstakts
    * @return true, wenn beide Takte gleich, sonst false
    */
   private boolean isSameAsTemplate(String[][] sBeats, int templateId, int compareId){
       for (int i=0; i<sBeats.length; i++){
           if (!sBeats[i][templateId].equalsIgnoreCase(sBeats[i][compareId])){
               return false;
           }
       }
       return true;
   }
   
   /**
    * fuer Debugging-/Logging-Zwecke
    * gibt die Indices der moeglichen gefundenen
    * Motife mit Wiederholungen pro Part aus
    */
    private void printMatches() {
        int Part = 0;
        int Motif = 0;
        
        if (scoreInBeats != null){            
            String[][] sBeats = mapBeatToString(0, getPhraseCount()-1, getMaxBeatLength());
            
            for (ArrayList<ArrayList<Integer>> alali : alRepetitions){
                //gib den Part aus
                outline("Part " +Part);
                writeToTextPane("Part " +Part +"\n");
                for (ArrayList<Beat> alb : scoreInBeats){
                    if (alb.size() > 0 && alb.get(0).getPart() == Part){
                        out("|");
                        writeToTextPane("|");
                        int beatId = 0;
                        for (Beat b : alb){
                            int spaces = getMaxNoteCountFromBeatInPhrase(beatId, Part) - b.getNoteCount();
                            for (NoteData nd : b.getBeat()){
                                if (Double.compare(nd.getQuantizedDuration(), 4.0) == 0 && nd.getPitch() == Integer.MIN_VALUE){ //Takt besteht nur aus einer Pause
                                    out("R");
                                    writeToTextPane("R");
                                } else {
                                    String s = getStringFromDuration(nd.getQuantizedDuration());
                                    out(s);
                                    writeToTextPane(s);
                                }
                            }
                            for (int x = 0; x < spaces; x++){
                                System.out.print(" ");
                                writeToTextPane(" ");
                            }
                            out("|");
                            writeToTextPane("|");
                            beatId++;
                        }
                        outline("");
                        writeToTextPane("\n");
                    }
                }

                //gib die gefundenen Motive aus
                for (ArrayList<Integer> ali : alali){
                    boolean first = true;
                    out("Motif: " +Motif +" |");
                    writeToTextPane("Motif: " +Motif  +" |");
                    for (int i : ali){
                        if (first){
                            String s = getBeat(sBeats, Part, i);
                            out(s +"|");
                            writeToTextPane(s +"|");
                            first = false;
                        }
                        out(" " +i);
                        writeToTextPane(" " +i);
                    }
                    outline("");
                    writeToTextPane("\n");
                    Motif++;
                }
                outline("");
                writeToTextPane("\n");
                Part++;
            }
        }
    }
    
    /**
    * fuer Debugging-/Logging-Zwecke
    * gibt die Indices der moeglichen gefundenen
    * Krebse pro Part aus
    */
    private void printPossibleKrebsMatches() {
        int Part = 0;
        int Motif = 0;
        
        if (scoreInBeats != null){            
            String[][] sBeats = mapBeatToString(0, getPhraseCount()-1, getMaxBeatLength());
            
            for (ArrayList<ArrayList<Integer>> alali : alPossibleKrebs){
                //gib den Part aus
                outline("Part " +Part);
                writeToTextPane("Part " +Part +"\n");
                for (ArrayList<Beat> alb : scoreInBeats){
                    if (alb.size() > 0 && alb.get(0).getPart() == Part){
                        out("|");
                        writeToTextPane("|");
                        int beatId = 0;
                        for (Beat b : alb){
                            int spaces = getMaxNoteCountFromBeatInPhrase(beatId, Part) - b.getNoteCount();
                            for (NoteData nd : b.getBeat()){
                                if (Double.compare(nd.getQuantizedDuration(), 4.0) == 0 && nd.getPitch() == Integer.MIN_VALUE){ //Takt besteht nur aus einer Pause
                                    out("R");
                                    writeToTextPane("R");
                                } else {
                                    String s = getStringFromDuration(nd.getQuantizedDuration());
                                    out(s);
                                    writeToTextPane(s);
                                }
                            }
                            for (int x = 0; x < spaces; x++){
                                System.out.print(" ");
                                writeToTextPane(" ");
                            }
                            out("|");
                            writeToTextPane("|");
                            beatId++;
                        }
                        outline("");
                        writeToTextPane("\n");
                    }
                }

                //gib die gefundenen Motive aus
                for (ArrayList<Integer> ali : alali){
                    boolean first = true;
                    out("Motif: " +Motif +" |");
                    writeToTextPane("Motif: " +Motif  +" |");
                    for (int i : ali){
                        if (first){
                            String s = getBeat(sBeats, Part, i);
                            out(s +"|");
                            writeToTextPane(s +"|");
                            first = false;
                        }
                        out(" " +i);
                        writeToTextPane(" " +i);
                    }
                    outline("");
                    writeToTextPane("\n");
                    Motif++;
                }
                outline("");
                writeToTextPane("\n");
                Part++;
            }
        }
    }
    
    /**
     * liefet einen bestimmten Takt eines bestimmten Parts als String
     * @param sBeats Array mit Takten als Strings
     * @param Part der gewuenschte Part
     * @param Beat der gewuenschte Takt
     * @return der gesuchte Takt als String
     */
    private String getBeat(String[][] sBeats, int Part, int Beat){
        for (int i=0; i<scoreInBeats.size(); i++){
            if (scoreInBeats.get(i).size() > 0 && scoreInBeats.get(i).get(0).getPart() == Part){
                return sBeats[i][Beat];
            }
        }
        return "";
    }
    
    /**
     * liefert einen bestimmten Takt eines Parts als Beat
     * @param Part der gesuchte Part
     * @param Beat der gesuchte Takt
     * @return der gesuchte Takt mit quantisierten Noten
     */
    private Beat getBeat(int Part, int Beat){
        for (int i=0; i<scoreInBeats.size(); i++){
            if (scoreInBeats.get(i).size() > 0 && scoreInBeats.get(i).get(0).getPart() == Part){
                return scoreInBeats.get(i).get(Beat);
            }
        }
        return null;
    }
    
    /**
     * liefert einen bestimmten Takt eines Parts mit mehreren Phrasen
     * als (innerhalb einer Note absteigen sortierten) Beat
     * zB.:
     * 60 65 62    60 76 63
     * 58 67 63 => 59 65 62
     * 59 64 60    58 64 60
     * @param Part der gesuchte Part
     * @param Beat der gesuchte Takt
     * @return der gesuchte Takt mit quantisierten sortierten Noten
     */
    private MultiBeat getMultiBeat(int Part, int Beat){
        int maxNoteCount = 0;
        int PhraseCount = 0;
        //derselbe Takt eines Parts kann in verschiedenen Phrasen verschieden lang sein
        //manchmal besteht der Takt einer Phrase nur aus einer Pause, da er zur Zeit nicht benoetigt wird
        //daher wird zuerst die maximale Notenanzahl des Takt bestimmt (Spalten im Array)
        //und die Anzahl der Phrasen (Zeilen im Array)
        for (int i=0; i<scoreInBeats.size(); i++){
            if (scoreInBeats.get(i).size() > 0 && scoreInBeats.get(i).get(0).getPart() == Part){
                PhraseCount++;
                if (scoreInBeats.get(i).get(Beat).getNoteCount() > maxNoteCount){
                    maxNoteCount = scoreInBeats.get(i).get(Beat).getNoteCount();
                }
            }
        }
        //die Noten der Takte werden in das zweidimensionale Array uebertragen
        //und anschliessend pro Note absteigend sortiert
        MultiBeat mb = new MultiBeat(new NoteData[PhraseCount][maxNoteCount]);
        int PhraseID = -1;
        for (int i=0; i<scoreInBeats.size(); i++){
            if (scoreInBeats.get(i).size() > 0 && scoreInBeats.get(i).get(0).getPart() == Part){
                PhraseID++;
                Beat b = scoreInBeats.get(i).get(Beat);
                mb.setInstrument(b.getInstrument());
                for (int j=0; j < b.getNoteCount(); j++){
                    mb.setBeat(PhraseID, j, b.getNote(j));
                }
            }
        }
        mb.sort();
        
        return mb;
    }

    /**
     * sucht nach Motiven in der vorgefilterten ArrayList (in alRepetitions)
     * und speichert diese getrennt nach Part und Typ ab
     * (monophone in alMotifData bzw. polyphone in alMultiMotifData)
     */
    private void getMotifs(){
        boolean multiPhrase = false;
        ArrayList<Beat> alb = new ArrayList<>();
        ArrayList<MultiBeat> albm = new ArrayList<>();
        //alle Parts
        for (int Part=0; Part<alRepetitions.size(); Part++){
            //alle Motive des Parts
            if (hasMultiplePhrases(Part)){
                multiPhrase = true;
            }
            ArrayList<ArrayList<MotifData>> alMotif = new ArrayList<>();
            ArrayList<ArrayList<MultiMotifData>> alMultiMotif = new ArrayList<>();
            for (int Motif=0; Motif<alRepetitions.get(Part).size(); Motif++){
                //alle Spalten-IDs der Takte mit dem aktuellen Motiv
                for (int ID=0; ID<alRepetitions.get(Part).get(Motif).size(); ID++){
                    if (multiPhrase){
                        MultiBeat mb = getMultiBeat(Part, alRepetitions.get(Part).get(Motif).get(ID));
                        albm.add(mb);
                        //out(mb.toString() +"| ");
                        //writeToTextPane(mb.toString() +"| ");
                    } else {
                        Beat b = getBeat(Part, alRepetitions.get(Part).get(Motif).get(ID));
                        alb.add(b);
                        //out(b.toString() +"| ");
                        //writeToTextPane(b.toString() +"| ");
                    }
                }
                //outline("");
                //writeToTextPane("\n");
                
                ArrayList<Integer> beatsToDo = new ArrayList<>();
                if (multiPhrase){
                    for (int i=0; i<albm.size(); i++){
                        beatsToDo.add(i);
                    }
                } else {
                    for (int i=0; i<alb.size(); i++){
                        beatsToDo.add(i);
                    }
                }
                
                //Wiederholung, Sequenz, Krebs, Spiegel
                if (multiPhrase){
                    ArrayList<MultiMotifData> almmd = new ArrayList<>(); //ArrayList mit Motifdaten
                    getDifferentMultiMotifsFromPart(almmd, albm, beatsToDo);
                    alMultiMotif.add(almmd); //pro Part eine eigene ArrayList
                    albm = new ArrayList<>();
                } else {
                    ArrayList<MotifData> almd = new ArrayList<>(); //ArrayList mit Motifdaten
                    getDifferentMotifsFromPart(almd, alb, beatsToDo);
                    alMotif.add(almd); //pro Part eine eigene ArrayList
                    //neu anlegen fuer naechstes Motif
                    alb = new ArrayList<>();
                }
            }
            if (multiPhrase) {
                alMultiMotifData.add(alMultiMotif);
            } else {
                alMotifData.add(alMotif);
            }
            //outline("");
            //writeToTextPane("\n");
            multiPhrase = false;
        }
    }
    
    /**
     * gibt an, ob es sich bei beiden Takten um exakt die gleichen Noten handelt
     * @param template Taktvorlage
     * @param compare Vergleichstakt
     * @return true wenn gleich, false wenn verschieden
     */
    private boolean isRepetition(Beat template, Beat compare){
        for (int i=0; i<template.getNoteCount();i++){
            if (template.getNote(i).getPitch() != compare.getNote(i).getPitch()){
                return false;
            }
        }
        return true;
    }
    
    /**
     * gibt an, ob es sich beim Vergleichstakt um eine Sequenz handelt
     * @param template Taktvorlage
     * @param compare Vergleichstakt
     * @return true wenn gleicher transponierter Takt, false wenn verschieden
     */
    private boolean isSequenz(Beat template, Beat compare){
        for (int i=0; i<template.getNoteCount()-1;i++){
            if ((template.getNote(i).getPitch() - template.getNote(i+1).getPitch()) != (compare.getNote(i).getPitch() - compare.getNote(i+1).getPitch())){
                return false;
            }
        }
        return true;
    }
    
    /**
     * gibt an, ob es sich beim Vergleichstakt um einen Krebs handelt
     * @param template Taktvorlage
     * @param compare Vergleichstakt
     * @return true wenn Takt in umgekehrter Reihenfolge, false wenn nicht
     */
    private boolean isKrebs(Beat template, Beat compare){
        int Count = template.getNoteCount() - 1;
        for (int i=0; i<template.getNoteCount();i++){
            if (template.getNote(i).getPitch() != compare.getNote(Count).getPitch()){
                return false;
            }
            Count--;
        }
        return true;
    }
    
    /**
     * gibt an, ob es sich beim Vergleichstakt um einen Spiegel handelt
     * @param template Taktvorlage
     * @param compare Vergleichstakt
     * @return true wenn Takt gespiegelt ist, false wenn nicht
     */
    private boolean isSpiegel(Beat template, Beat compare){
        for (int i=0; i<template.getNoteCount()-1;i++){
            //Vergleichstakt muss gleich grosse Intervallsaenderung haben nur mit verkehrtem Vorzeichen
            if ((template.getNote(i).getPitch() - template.getNote(i+1).getPitch()) != -(compare.getNote(i).getPitch() - compare.getNote(i+1).getPitch())){
                return false;
            }
        }
        return true;
    }
    
    /**
     * sucht nach Motiven innerhalb eines Parts
     * gesucht werden Wiederholung, Sequenz, Krebs und Spiegel
     * @param almd ArrayList mit den Motifdaten
     * @param alb ArrayList mit den Takten des aktuellen Motivs
     * @param beatsToDo ArrayList mit den IDs der Takte, die noch nicht abgearbeitet wurden
     */
    private void getDifferentMotifsFromPart(ArrayList<MotifData> almd, ArrayList<Beat> alb, ArrayList<Integer> beatsToDo) {
        if (alb.size() > 0 && beatsToDo.size() > 0){            
            Beat template = alb.get(beatsToDo.get(0)); //ersten Takt als Vorlage nehmen
            beatsToDo.remove(0); //ersten Takt aus Liste loeschen, da als Template
            //vergleiche Template mit jedem verbleibenden Takt
            ArrayList<Beat> aRep = new ArrayList<>(), aSeq = new ArrayList<>(),
                    aKrebs = new ArrayList<>(), aSpiegel = new ArrayList<>();
            for (int i=0; i<beatsToDo.size(); i++){
                int ID = beatsToDo.get(i);
                //ist der Takt eine exakte Wiederholung der Vorlage?
                if (isRepetition(template, alb.get(ID))){
                    beatsToDo.remove(i); //Wiederholung aus Bearbeitungliste entfernen, da nicht mehr relevant
                    aRep.add(alb.get(ID));
                    i--; //i wieder um 1 verringern, da beatsToDo kleiner geworden ist
                }
                /* ist der Takt eine Sequenz der Vorlage?
                 * eine exakte Wiederholung des Basistakts transponiert
                 * auf eine andere Tonstufe
                 */
                else if (isSequenz(template, alb.get(ID))){
                    beatsToDo.remove(i); //Sequenz aus Bearbeitungliste entfernen, da nicht mehr relevant
                    aSeq.add(alb.get(ID));
                    i--; //i wieder um 1 verringern, da beatsToDo kleiner geworden ist
                }
                //rueckwaerts verlaufender Takt im Vergleich zum Basistakt
                else if (isKrebs(template, alb.get(ID))){
                    beatsToDo.remove(i);
                    aKrebs.add(alb.get(ID));
                    i--; //i wieder um 1 verringern, da beatsToDo kleiner geworden ist
                }
                /*
                 * Spiegelung des Basistaktes
                 * zB bei einem 4/4-Takt mit 4 Vierteln
                 * werden die ersten beiden Noten an den Schluss und
                 * die letzten beiden Noten am Anfang gespielt
                 * die Spiegelachse befindet sich in der Mitte des Takts
                 */
                else if (isSpiegel(template, alb.get(ID))){
                    beatsToDo.remove(i);
                    aSpiegel.add(alb.get(ID));
                    i--; //i wieder um 1 verringern, da beatsToDo kleiner geworden ist
                }
            }
            //pruefen, ob ein Motiv gefunden wurde
            //falls ja, speichern der Vorlage als erstes Element
            if (aRep.size() > 0){
                aRep.add(0, template);
                almd.add(new MotifData(aRep, "Rep", numerator, denominator, key));
            }
            if (aSeq.size() > 0){
                aSeq.add(0, template);
                almd.add(new MotifData(aSeq, "Seq", numerator, denominator, key));
            }
            if (aKrebs.size() > 0){
                aKrebs.add(0, template);
                almd.add(new MotifData(aKrebs, "Krebs", numerator, denominator, key));
            }
            if (aSpiegel.size() > 0){
                aSpiegel.add(0, template);
                almd.add(new MotifData(aSpiegel, "Spiegel", numerator, denominator, key));
            }
            //rekursiver Aufruf mit der restlichen Bearbeitungsliste
            getDifferentMotifsFromPart(almd, alb, beatsToDo);
        }
    }
    
    /**
     * sucht nach polyphonen Motiven innerhalb eines Parts mit mehreren Phrasen
     * gesucht werden Wiederholung, Sequenz, Krebs und Spiegel
     * @param almd ArrayList mit den Motifdaten
     * @param albm ArrayList mit den Takten des aktuellen Motivs
     * @param beatsToDo ArrayList mit den IDs der Takte, die noch nicht abgearbeitet wurden
     */
    private void getDifferentMultiMotifsFromPart(ArrayList<MultiMotifData> almd, ArrayList<MultiBeat> albm, ArrayList<Integer> beatsToDo) {
        if (albm.size() > 0 && beatsToDo.size() > 0){            
            MultiBeat template = albm.get(beatsToDo.get(0)); //ersten Takt als Vorlage nehmen
            beatsToDo.remove(0); //ersten Takt aus Liste loeschen, da als Template
            //vergleiche Template mit jedem verbleibenden Takt
            ArrayList<MultiBeat> aRep = new ArrayList<>(), aSeq = new ArrayList<>(),
                    aKrebs = new ArrayList<>(), aSpiegel = new ArrayList<>();
            for (int i=0; i<beatsToDo.size(); i++){
                int ID = beatsToDo.get(i);
                //ist der Takt eine exakte Wiederholung der Vorlage?
                if (isMultiRepetition(template, albm.get(ID))){
                    beatsToDo.remove(i); //Wiederholung aus Bearbeitungliste entfernen, da nicht mehr relevant
                    aRep.add(albm.get(ID));
                    i--; //i wieder um 1 verringern, da beatsToDo kleiner geworden ist
                }
                /* ist der Takt eine Sequenz der Vorlage?
                 * eine exakte Wiederholung des Basistakts transponiert
                 * auf eine andere Tonstufe
                 */
                else if (isMultiSequenz(template, albm.get(ID))){
                    beatsToDo.remove(i); //Sequenz aus Bearbeitungliste entfernen, da nicht mehr relevant
                    aSeq.add(albm.get(ID));
                    i--; //i wieder um 1 verringern, da beatsToDo kleiner geworden ist
                }
                //rueckwaerts verlaufender Takt im Vergleich zum Basistakt
                else if (isMultiKrebs(template, albm.get(ID))){
                    beatsToDo.remove(i);
                    aKrebs.add(albm.get(ID));
                    i--; //i wieder um 1 verringern, da beatsToDo kleiner geworden ist
                }
                /*
                 * Spiegelung des Basistaktes
                 * zB bei einem 4/4-Takt mit 4 Vierteln
                 * werden die ersten beiden Noten an den Schluss und
                 * die letzten beiden Noten am Anfang gespielt
                 * die Spiegelachse befindet sich in der Mitte des Takts
                 */
                else if (isMultiSpiegel(template, albm.get(ID))){
                    beatsToDo.remove(i);
                    aSpiegel.add(albm.get(ID));
                    i--; //i wieder um 1 verringern, da beatsToDo kleiner geworden ist
                }
            }
            //pruefen, ob ein Motiv gefunden wurde
            //falls ja, speichern der Vorlage als erstes Element
            if (aRep.size() > 0){
                aRep.add(0, template);
                almd.add(new MultiMotifData(aRep, "Rep", numerator, denominator, key));
            }
            if (aSeq.size() > 0){
                aSeq.add(0, template);
                almd.add(new MultiMotifData(aSeq, "Seq", numerator, denominator, key));
            }
            if (aKrebs.size() > 0){
                aKrebs.add(0, template);
                almd.add(new MultiMotifData(aKrebs, "Krebs", numerator, denominator, key));
            }
            if (aSpiegel.size() > 0){
                aSpiegel.add(0, template);
                almd.add(new MultiMotifData(aSpiegel, "Spiegel", numerator, denominator, key));
            }
            //rekursiver Aufruf mit der restlichen Bearbeitungsliste
            getDifferentMultiMotifsFromPart(almd, albm, beatsToDo);
        }
    }

    /**
     * gibt an, ob es sich bei beiden Takten um exakt die gleichen Noten handelt
     * @param template Taktvorlage
     * @param compare Vergleichstakt
     * @return true wenn gleich, false wenn verschieden
     */
    private boolean isMultiRepetition(MultiBeat template, MultiBeat compare){
        NoteData[][] temp = template.getBeat();
        NoteData[][] comp = compare.getBeat();
        
        for (int i=0; i<temp.length; i++){
            /* ungueltigen Indexzugriff verhindern
             * es ist moeglich, dass ein potenzieller Match markiert wurde,
             * da Takte bestehend aus Pausen als Match angesehen werden.
             * Dadurch kann es zu unterschiedlichen Taktlaengen kommen.
             * Beispiel:
             * HVV   HVV
             * VVVV  R
             * 
             * Hier hatte man in Zeile 2 einen Error auf Grund der verschiedenen Taktlaenge.
             * Es kann sofort false zurueckgegeben werden, da es sich sicher nicht um eine
             * Wiederholung handelt.
             */
            if (temp[i].length != comp[i].length){
                return false;
            }
            for (int j=0; j<temp[i].length; j++){
                if (temp[i][j].getPitch() != comp[i][j].getPitch()){
                    return false;
                }
            }
        }
        return true;
    }
    
    /**
     * gibt an, ob es sich beim Vergleichstakt um eine Sequenz handelt
     * @param template Taktvorlage
     * @param compare Vergleichstakt
     * @return true wenn gleicher transponierter Takt, false wenn verschieden
     */
    private boolean isMultiSequenz(MultiBeat template, MultiBeat compare){
        NoteData[][] temp = template.getBeat();
        NoteData[][] comp = compare.getBeat();
        boolean bOnlyRest = true;
        
        //wenn Takt nur aus einer MultiNote besteht und diese nur aus Pausen besteht, nicht als Sequenz nehmen
        if (compare.getNoteCount() == 1){
            for (int i=0; i<temp.length; i++){
                if (temp[i].length != comp[i].length){
                    return false;
                }
                for (int j=0; j<temp[i].length; j++){
                    if (comp[i][j].getPitch() != Integer.MIN_VALUE){
                        bOnlyRest = false;
                    }
                }
            }
            if (bOnlyRest){
                return false;
            }
        }
        
        //horizontale Intervalle pruefen
        for (int i=0; i<temp.length; i++){
            /* ungueltigen Indexzugriff verhindern
             * es ist moeglich, dass ein potenzieller Match markiert wurde,
             * da Takte bestehend aus Pausen als Match angesehen werden.
             * Dadurch kann es zu unterschiedlichen Taktlaengen kommen.
             * Beispiel:
             * HVV   HVV
             * VVVV  R
             * 
             * Hier hatte man in Zeile 2 einen Error auf Grund der verschiedenen Taktlaenge.
             * Es kann sofort false zurueckgegeben werden, da es sich sicher nicht um eine
             * Sequenz handelt.
             */
            if (temp[i].length != comp[i].length){
                return false;
            }
            for (int j=0; j<temp[i].length-1; j++){
                if ((temp[i][j].getPitch() - temp[i][j+1].getPitch()) != (comp[i][j].getPitch() - comp[i][j+1].getPitch())){
                    return false;
                }
            }
        }
        //vertikale Intervalle pruefen
        if (temp.length > 0){
            for (int i=0; i<temp[0].length; i++){
                for (int j=0; j<temp.length-1; j++){
                    if ((temp[j][i].getPitch() - temp[j+1][i].getPitch()) != (comp[j][i].getPitch() - comp[j+1][i].getPitch())){
                        return false;
                    }
                }
            }
        }
        return true;
    }
    
    /**
     * gibt an, ob es sich beim Vergleichstakt um einen Krebs handelt
     * @param template Taktvorlage
     * @param compare Vergleichstakt
     * @return true wenn Takt in umgekehrter Reihenfolge, false wenn nicht
     */
    private boolean isMultiKrebs(MultiBeat template, MultiBeat compare){
        NoteData[][] temp = template.getBeat();
        NoteData[][] comp = compare.getBeat();
        
        if (temp.length > 0){
            int Count = temp[0].length - 1;
            
            for (int i=0; i<temp.length; i++){
                /* ungueltigen Indexzugriff verhindern
                 * es ist moeglich, dass ein potenzieller Match markiert wurde,
                 * da Takte bestehend aus Pausen als Match angesehen werden.
                 * Dadurch kann es zu unterschiedlichen Taktlaengen kommen.
                 * Beispiel:
                 * HVV   HVV
                 * VVVV  R
                 * 
                 * Hier hatte man in Zeile 2 einen Error auf Grund der verschiedenen Taktlaenge.
                 * Es kann sofort false zurueckgegeben werden, da es sich sicher nicht um einen
                 * Krebs handelt.
                 */
                if (temp[i].length != comp[i].length){
                    return false;
                }
                for (int j=0; j<temp[i].length; j++){
                    if (temp[i][j].getPitch() != comp[i][Count].getPitch()){
                        return false;
                    }
                    Count--;
                }
                Count = temp[i].length - 1;
            }
        }
        return true;
    }
    
    /**
     * gibt an, ob es sich beim Vergleichstakt um einen Spiegel handelt
     * @param template Taktvorlage
     * @param compare Vergleichstakt
     * @return true wenn Takt gespiegelt ist, false wenn nicht
     */
    private boolean isMultiSpiegel(MultiBeat template, MultiBeat compare){
        NoteData[][] temp = template.getBeat();
        NoteData[][] comp = compare.getBeat();
        
        boolean bOnlyRest = true;
        
        //wenn Takt nur aus einer MultiNote besteht und diese nur aus Pausen besteht, nicht als Spiegel nehmen
        if (compare.getNoteCount() == 1){
            for (int i=0; i<comp.length; i++){
                for (int j=0; j<comp[i].length; j++){
                    if (comp[i][j].getPitch() != Integer.MIN_VALUE){
                        bOnlyRest = false;
                    }
                }
            }
            if (bOnlyRest){
                return false;
            }
        }
        
        //horizontale Intervalle pruefen
        for (int i=0; i<temp.length; i++){
            /* ungueltigen Indexzugriff verhindern
             * es ist moeglich, dass ein potenzieller Match markiert wurde,
             * da Takte bestehend aus Pausen als Match angesehen werden.
             * Dadurch kann es zu unterschiedlichen Taktlaengen kommen.
             * Beispiel:
             * HVV   HVV
             * VVVV  R
             * 
             * Hier hatte man in Zeile 2 einen Error auf Grund der verschiedenen Taktlaenge.
             * Es kann sofort false zurueckgegeben werden, da es sich sicher nicht um einen
             * Spiegel handelt.
             */
            if (temp[i].length != comp[i].length){
                return false;
            }
            for (int j=0; j<temp[i].length-1; j++){
                if ((temp[i][j].getPitch() - temp[i][j+1].getPitch()) != -(comp[i][j].getPitch() - comp[i][j+1].getPitch())){
                    return false;
                }
            }
        }
        //vertikale Intervalle pruefen
        if (temp.length > 0){
            for (int i=0; i<temp[0].length; i++){
                for (int j=0; j<temp.length-1; j++){
                    if ((temp[j][i].getPitch() - temp[j+1][i].getPitch()) != (comp[j][i].getPitch() - comp[j+1][i].getPitch())){
                        return false;
                    }
                }
            }
        }
        return true;
    }
    
    /**
     * sucht nach Krebs-Motiven in der vorgefilterten ArrayList (in alPossibleKrebs)
     * und speichert diese getrennt nach Part und Typ ab
     * (monophone in alMotifData bzw. polyphone in alMultiMotifData)
     */
    private void getMotifsFromOddKrebs(){
        boolean multiPhrase = false;
        ArrayList<Beat> alb = new ArrayList<>();
        ArrayList<MultiBeat> albm = new ArrayList<>();
        //alle Parts
        for (int Part=0; Part<alPossibleKrebs.size(); Part++){
            //alle Motive des Parts
            if (hasMultiplePhrases(Part)){
                multiPhrase = true;
            }
            ArrayList<ArrayList<MotifData>> alMotif = new ArrayList<>();
            ArrayList<ArrayList<MultiMotifData>> alMultiMotif = new ArrayList<>();
            for (int Motif=0; Motif<alPossibleKrebs.get(Part).size(); Motif++){
                //alle Spalten-IDs der Takte mit dem aktuellen Motiv
                for (int ID=0; ID<alPossibleKrebs.get(Part).get(Motif).size(); ID++){
                    if (multiPhrase){
                        MultiBeat mb = getMultiBeat(Part, alPossibleKrebs.get(Part).get(Motif).get(ID));
                        albm.add(mb);
                    } else {
                        Beat b = getBeat(Part, alPossibleKrebs.get(Part).get(Motif).get(ID));
                        alb.add(b);
                    }
                }
                
                ArrayList<Integer> beatsToDo = new ArrayList<>();
                if (multiPhrase){
                    for (int i=0; i<albm.size(); i++){
                        beatsToDo.add(i);
                    }
                } else {
                    for (int i=0; i<alb.size(); i++){
                        beatsToDo.add(i);
                    }
                }
                
                //ungerade Krebse (zB.: HVV -> VVH)
                if (multiPhrase){
                    ArrayList<MultiMotifData> almmd = new ArrayList<>(); //ArrayList mit Motifdaten
                    getMultiOddKrebs(almmd, albm, beatsToDo);
                    alMultiMotif.add(almmd); //pro Part eine eigene ArrayList
                    albm = new ArrayList<>();
                } else {
                    ArrayList<MotifData> almd = new ArrayList<>(); //ArrayList mit Motifdaten
                    getOddKrebs(almd, alb, beatsToDo);
                    alMotif.add(almd); //pro Part eine eigene ArrayList
                    //neu anlegen fuer naechstes Motif
                    alb = new ArrayList<>();
                }
            }
            if (multiPhrase) {
                alMultiMotifData.add(alMultiMotif);
            } else {
                alMotifData.add(alMotif);
            }
            //outline("");
            //writeToTextPane("\n");
            multiPhrase = false;
        }
    }
    
    /**
     * sucht nach Motiven innerhalb eines Parts
     * gesucht werden nur Krebse in der Form zB.: HVV -> VVH, ...
     * @param almd ArrayList mit den Motifdaten
     * @param alb ArrayList mit den Takten des aktuellen Motivs
     * @param beatsToDo ArrayList mit den IDs der Takte, die noch nicht abgearbeitet wurden
     */
    private void getOddKrebs(ArrayList<MotifData> almd, ArrayList<Beat> alb, ArrayList<Integer> beatsToDo){
        if (alb.size() > 0 && beatsToDo.size() > 0){            
            Beat template = alb.get(beatsToDo.get(0)); //ersten Takt als Vorlage nehmen
            beatsToDo.remove(0); //ersten Takt aus Liste loeschen, da als Template
            //vergleiche Template mit jedem verbleibenden Takt
            ArrayList<Beat> aKrebs = new ArrayList<>();
            for (int i=0; i<beatsToDo.size(); i++){
                int ID = beatsToDo.get(i);
                //rueckwaerts verlaufender Takt im Vergleich zum Basistakt
                if (isKrebs(template, alb.get(ID))){
                    beatsToDo.remove(i);
                    aKrebs.add(alb.get(ID));
                    i--; //i wieder um 1 verringern, da beatsToDo kleiner geworden ist
                }
            }
            //pruefen, ob ein Motiv gefunden wurde
            //falls ja, speichern der Vorlage als erstes Element
            if (aKrebs.size() > 0){
                aKrebs.add(0, template);
                almd.add(new MotifData(aKrebs, "Krebs", numerator, denominator, key));
            }
            //rekursiver Aufruf mit der restlichen Bearbeitungsliste
            getOddKrebs(almd, alb, beatsToDo);
        }
    }
    
    /**
     * sucht nach polyphonen Motiven innerhalb eines Parts mit mehreren Phrasen
     * gesucht werden nur polyphone Krebse in der Form zB.: VVH -> HVV
     * @param almd ArrayList mit den Motifdaten
     * @param albm ArrayList mit den Takten des aktuellen Motivs
     * @param beatsToDo ArrayList mit den IDs der Takte, die noch nicht abgearbeitet wurden
     */
    private void getMultiOddKrebs(ArrayList<MultiMotifData> almd, ArrayList<MultiBeat> albm, ArrayList<Integer> beatsToDo){
        if (albm.size() > 0 && beatsToDo.size() > 0){            
            MultiBeat template = albm.get(beatsToDo.get(0)); //ersten Takt als Vorlage nehmen
            beatsToDo.remove(0); //ersten Takt aus Liste loeschen, da als Template
            //vergleiche Template mit jedem verbleibenden Takt
            ArrayList<MultiBeat> aKrebs = new ArrayList<>();
            for (int i=0; i<beatsToDo.size(); i++){
                int ID = beatsToDo.get(i);
                //rueckwaerts verlaufender Takt im Vergleich zum Basistakt
                if (isMultiKrebs(template, albm.get(ID))){
                    beatsToDo.remove(i);
                    aKrebs.add(albm.get(ID));
                    i--; //i wieder um 1 verringern, da beatsToDo kleiner geworden ist
                }
            }
            //pruefen, ob ein Motiv gefunden wurde
            //falls ja, speichern der Vorlage als erstes Element
            if (aKrebs.size() > 0){
                aKrebs.add(0, template);
                almd.add(new MultiMotifData(aKrebs, "Krebs", numerator, denominator, key));
            }
            //rekursiver Aufruf mit der restlichen Bearbeitungsliste
            getMultiOddKrebs(almd, albm, beatsToDo);
        }
    }

    /**
    * gibt an, ob der Part aus mehreren Phrasen besteht
    * @param Part der zu untersuchende Part
    * @return true, wenn mehrere Phrasen im Part, sonst false
    */
   private boolean hasMultiplePhrases(int Part){
       int Count = 0;
       for (ArrayList<Beat> ald : scoreInBeats){
           if (ald.size() > 0 && ald.get(0).getPart() == Part){
               Count++;
           }
       }
       if (Count > 1){
           return true;
       } else {
           return false;
       }
   }
    
    /**
     * gibt alle Motiftypen pro Part aus
     */
    private void printMotifs() {
        //jeder Part
        for (ArrayList<ArrayList<MotifData>> alalmd : alMotifData){
            //jedes Motif
            for (ArrayList<MotifData> almd : alalmd){
                //jede Motifart (Wiederholung, Sequenz, Krebs, Spiegel)
                for (MotifData md : almd){
                    outline(md.toString());
                    writeToTextPane(md.toString() +"\n");
                }
            }
            outline("");
            writeToTextPane("\n");
        }
    }
    
    /**
     * gibt alle MultiMotiftypen pro Part aus
     */
    private void printMultiMotifs() {
        //jeder Part
        for (ArrayList<ArrayList<MultiMotifData>> alalmd : alMultiMotifData){
            //jedes Motif
            for (ArrayList<MultiMotifData> almd : alalmd){
                //jede Motifart (Wiederholung, Sequenz, Krebs, Spiegel)
                for (MultiMotifData md : almd){
                    outline(md.toString());
                    writeToTextPane(md.toString() +"\n");
                }
            }
            outline("");
            writeToTextPane("\n");
        }
    }
    
    /**
     * liefert den Pitch fuer die Note ohne Vorzeichen (ohne # bzw. b)
     * @param B das Referenz-B
     * @param Pitch der aktuelle Pitch
     * @return der veraenderte Pitch
     */
    /*private int getNormalNote(int B, int Pitch){
        int low = B, high = Pitch;
        boolean lower = false;
        if (B > Pitch){
            low = Pitch;
            high = B;
            lower = true;
        }
        
        for (int i=low; i<high; i++){
            if (Note.getNote(i).equalsIgnoreCase("C#") || Note.getNote(i).equalsIgnoreCase("Eb") || 
                    Note.getNote(i).equalsIgnoreCase("F#") || Note.getNote(i).equalsIgnoreCase("Ab") || 
                    Note.getNote(i).equalsIgnoreCase("Bb")){
                if (lower){
                    Pitch++;
                } else {
                    Pitch--;
                }
            }
        }
        return Pitch;
    }*/
    
    /**
     * gibt eine Liste mit Bs als Spiegelachsen zurueck
     * @param low niedrigste Note im Takt
     * @param high hoechste Note im Takt
     * @return Liste mit Bs, welche als Spiegelachsen verwendet werden
     */
    /*private ArrayList<Integer> getReferenceB(NoteData low, NoteData high){
        if (low == null && high == null){
            return null;
        } else {
            int[] allBs = {11,23,35,47,59,71,83,95,107,119}; //Array mit allen moeglichen Bs
            ArrayList<Integer> Bs = new ArrayList<>(); //Liste der dazwischen liegenden Bs
            
            //alle zwischen low und high liegenden Bs speichern
            for (int i=0; i<allBs.length; i++){
                if (allBs[i] >= low.getPitch() && allBs[i] <= high.getPitch()){
                    Bs.add(i);
                }
            }
            //wenn kein dazwischen liegendes B gefunden
            //darunter und darueber liegendes B speichern
            if (Bs.isEmpty()){
                int lower = -1;
                int higher = -1;
                for (int i=0; i<allBs.length; i++){
                    if (allBs[i] <= low.getPitch()){
                        lower = i;
                    }
                    if (allBs[i] >= high.getPitch()){
                        higher = i;
                        break; //schleife kann abgebrochen werden, da naechstgroesseres B gefunden
                    }
                }
                if (lower != -1){
                    Bs.add(lower);
                }
                if (higher != -1){
                    Bs.add(higher);
                }
            }
            
            ArrayList<Integer> ret = new ArrayList<>();
            if (Bs.size() == 1){
                //zwischen low und high befindet sich genau ein B (bzw. H)
                ret.add(allBs[Bs.get(0)]);
                return ret;
            } else if (Bs.size() == 2){
                //zwischen low und high befindet sich ein darueber und ein darunter liegendes B
                ret.add(allBs[Bs.get(0)]);
                ret.add(allBs[Bs.get(1)]);
                return ret;
            } else { //mehr als 2 Bs
                if (Bs.size() % 2 == 0){ //gerade Anzahl an Bs
                    //bei gerader Anzahl groesser 2 werden die beiden mittleren B als Spiegelachsen verwendet
                    int x = Bs.size()/2;
                    ret.add(allBs[Bs.get(x)]);
                    ret.add(allBs[Bs.get(x+1)]);
                    return ret;
                } else { //ungerade Anzahl an Bs
                    //bei einer ungeraden Anzahl groesser 1 wird das mittlere B als Spiegelachse verwendet
                    String s = Math.ceil(Bs.size()/2) +"";
                    int x = Integer.valueOf(s.substring(0, s.length()-2)); // .0 wegschneiden
                    ret.add(allBs[x]);
                    return ret;
                }
            }
        }
    }*/
}