code: /*
   * Computoser is a music-composition algorithm and a website to present the results
   * Copyright (C) 2012-2014  Bozhidar Bozhanov
   *
   * Computoser is free software: you can redistribute it and/or modify
   * it under the terms of the GNU Affero General Public License as
   * published by the Free Software Foundation, either version 3 of the
   * License, or (at your option) any later version.
   *
   * Computoser is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   * GNU Affero General Public License for more details.
   *
   * You should have received a copy of the GNU Affero General Public License
   * along with Computoser.  If not, see <http://www.gnu.org/licenses/>.
   */
  
  package com.music.tools;
  
  import java.io.File;
  import java.io.FileReader;
  import java.io.Reader;
  import java.text.DecimalFormat;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.List;
  import java.util.Set;
  
  import org.xml.sax.Attributes;
  import org.xml.sax.InputSource;
  import org.xml.sax.SAXException;
  import org.xml.sax.XMLReader;
  import org.xml.sax.helpers.DefaultHandler;
  import org.xml.sax.helpers.XMLReaderFactory;
  
  import com.google.common.collect.Sets;
  
  public class SongDBAnalyzer {
  
      private static final DecimalFormat df = new DecimalFormat("#.##");
  
      public static void main(String[] args) throws Exception {
          File dir = new File("C:\\workspace\\music\\analysis\\db");
          File[] files = dir.listFiles();
  
          XMLReader xr = XMLReaderFactory.createXMLReader();
          NoteContentHandler handler = new NoteContentHandler();
          xr.setContentHandler(handler);
          for (File file : files) {
              try (Reader reader = new FileReader(file)) {
                  try {
                      xr.parse(new InputSource(reader));
                      handler.getNotes().add(null);
                  } catch (Exception ex) {
                      System.out.println(file.getName());
                      throw ex;
                  }
              }
          }
          List<NoteElement> noteList = handler.getNotes();
  
          int[] degreeValue = new int[] {0,0,0,0,0,0,0};
          int[] degreeBeforePause = new int[] {0,0,0,0,0,0,0};
          int[] lengths = new int[1000];
          int[] unisonLengths = new int[1000];
          int[] intervals = new int[] {0,0,0,0,0,0,0,0,0,0,0,0,0};
          int[] firstInMeasure = new int[] {0,0,0,0,0,0,0};
          int[] lastInMeasure = new int[] {0,0,0,0,0,0,0};
          int[][] lengthsPerDegree = new int[7][1000];
          int[][] nextNotes = new int[7][7];
          int measures = 0;
          int notes = 0;
          int stable = 0;
          int unstable = 0;
          NoteElement previousNote = null;
          int currentNoteCount = 0;
          List<Integer> noteCounts = new ArrayList<Integer>();
          for (NoteElement note : noteList) {
              if (note == null) {
                  previousNote = null;
                  noteCounts.add(currentNoteCount);
                  currentNoteCount = 0;
                  continue;
              } else {
                  currentNoteCount++;
              }
  
              if (note.isRest() && previousNote != null) {
                  degreeBeforePause[previousNote.getScaleDegree()-1]++;
              }
              if (!note.isRest() && !note.isFlat() && !note.isSharp()) {
                  notes++;
                  degreeValue[note.getScaleDegree()-1]++;
                  lengths[(int) (note.getNoteLength() * 100)]++;
                  lengthsPerDegree[note.getScaleDegree()-1][(int)(note.getNoteLength() * 100)]++;
                  if (previousNote != null) {
                      nextNotes[previousNote.getScaleDegree()-1][note.getScaleDegree()-1]++;
                      int currentNormalized = note.getScaleDegree() + note.getOctave() * 7;
                      int previousNormalized = previousNote.getScaleDegree() + previousNote.getOctave() * 7;
                      int interval = currentNormalized - previousNormalized;
                      int idx = Math.abs(interval);
                      if (intervals.length > idx) {
                          intervals[idx]++;
                      }
                      if (interval == 0) {
                          unisonLengths[(int) (note.getNoteLength() * 100)]++;
                      }
                  }
  
                  if (previousNote == null || previousNote.getStartMeasure() != note.getStartMeasure()) {
                      if (previousNote != null) {
                          lastInMeasure[previousNote.getScaleDegree() - 1]++;
                      }
                      firstInMeasure[note.getScaleDegree() - 1] ++;
                      measures++;
                  }
                  if (note.getScaleDegree() == 1 || note.getScaleDegree() == 3 || note.getScaleDegree() == 5) {
                      stable ++;
                  } else {
                      unstable ++;
                  }
                  previousNote = note;
              }
          }
  
          System.out.println("Degrees: " + Arrays.toString(percentages(degreeValue)));
          System.out.println("Degrees before pause: " + Arrays.toString(percentages(degreeBeforePause)));
          System.out.println("Intervals: " + Arrays.toString(percentages(intervals)));
          System.out.println("First note in measure: " + Arrays.toString(percentages(firstInMeasure)));
          System.out.println("Last note in measure: " + Arrays.toString(percentages(lastInMeasure)));
          System.out.println("Lengths: " + Arrays.toString(percentages(lengths, true, true)));
          System.out.println("Unison lengths: " + Arrays.toString(percentages(unisonLengths, true, true)));
          System.out.println("Stable:Unstable = " + stable + ":" + unstable);
          System.out.println("Notes/measure = " + (1d*notes/measures));
          for (int i = 0; i < 7; i++) {
              System.out.println("Lengths for degree " + (i+1));
              double degreeTotal = 0;
              for (int length = 0; length < lengthsPerDegree[i].length; length++) {
                  degreeTotal += lengthsPerDegree[i][length];
              }
              for (int length = 0; length < lengthsPerDegree[i].length; length++) {
                  if (lengthsPerDegree[i][length] > 0) {
                      System.out.print(length + "=" + df.format((lengthsPerDegree[i][length]) / degreeTotal * 100));
                      System.out.print("; ");
                  }
              }
              System.out.println();
              System.out.println();
          }
          System.out.println("-----------------------");
          System.out.println();
  
          for (int i = 0; i < 7; i++) {
              System.out.println("Next notes for degree " + (i+1));
              double degreeTotal = 0;
              for (int k = 0; k < nextNotes[i].length; k++) {
                  degreeTotal += nextNotes[i][k];
              }
              for (int k = 0; k < nextNotes[i].length; k++) {
                  System.out.print((k+1) + "=" + df.format((nextNotes[i][k]) / degreeTotal * 100));
                  System.out.print("; ");
              }
              System.out.println();
              System.out.println();
          }
  
          double sum = 0;
          int noteCountSize = 0;
          for (int count : noteCounts) {
              sum += count;
              if (count != 0) {
                  noteCountSize ++;
              }
          }
          System.out.println(noteCounts);
          System.out.println("Average notes per piece: " + df.format(sum / noteCountSize));
      }
  
      public static String[] percentages(int[] array) {
          return percentages(array, false, false);
      }
      public static String[] percentages(int[] array, boolean excludeZero, boolean includeIdx) {
          double total = 0;
          List<String> result = new ArrayList<>();
          for (int val : array) {
              total += val;
          }
          for (int i = 0; i < array.length; i++) {
              double value = array[i] / total * 100;
              if (excludeZero && value == 0) {
                  continue;
              }
              String prefix = "";
              if (includeIdx) {
                  prefix += i + "=";
              }
              result.add(prefix + df.format(value));
          }
          return result.toArray(new String[result.size()]);
      }
  
      public static class NoteContentHandler extends DefaultHandler {
          private List<NoteElement> notes = new ArrayList<>();
          private NoteElement currentNote;
          private Set<String> noteProperties = Sets.newHashSet("start_beat_abs", "start_measure", "start_beat",
                  "note_length", "scale_degree", "octave", "isRest");
          private String currentProperty;
          private StringBuilder currentPropertyValue = new StringBuilder();
  
          @Override
          public void startElement(String uri, String localName, String qName, Attributes attributes)
                  throws SAXException {
              if (localName.equals("note")) {
                  currentNote = new NoteElement();
              }
              if (noteProperties.contains(localName)) {
                  currentProperty = localName;
              }
  
          }
  
          @Override
          public void characters(char[] ch, int start, int length) throws SAXException {
              if (currentProperty != null && currentNote != null) {
                  currentPropertyValue.append(Arrays.copyOfRange(ch, start, start+length));
              }
          }
  
          @Override
          public void endElement(String uri, String localName, String qName) throws SAXException {
              if (localName.equals("note") && currentNote != null) {
                  notes.add(currentNote);
                  currentNote = null;
              }
              if (noteProperties.contains(localName) && currentNote != null) {
                  //System.out.println(currentProperty + " : " + currentPropertyValue.toString().trim());
  
                  if (currentProperty.equals("start_beat_abs")) {
                      currentNote.setStartBeatAbs(Double.parseDouble(currentPropertyValue.toString().trim()));
                  }
                  if (currentProperty.equals("start_measure")) {
                      currentNote.setStartMeasure(Double.parseDouble(currentPropertyValue.toString().trim()));
                  }
                  if (currentProperty.equals("start_beat")) {
                      currentNote.setStartBeat(Double.parseDouble(currentPropertyValue.toString().trim()));
                  }
                  if (currentProperty.equals("note_length")) {
                      currentNote.setNoteLength(Double.parseDouble(currentPropertyValue.toString().trim()));
                  }
                  if (currentProperty.equals("scale_degree")) {
                      String value = currentPropertyValue.toString().trim();
                      if (value.equalsIgnoreCase("rest")) {
                          currentNote.setScaleDegree(-1);
                      } else {
                          if (value.endsWith("f")) {
                              currentNote.setFlat(true);
                              value = value.substring(0, value.length() - 1);
                          } else if (value.endsWith("s")) {
                              currentNote.setSharp(true);
                              value = value.substring(0, value.length() - 1);
                          }
                          currentNote.setScaleDegree(Integer.parseInt(value));
                      }
                  }
                  if (currentProperty.equals("octave")) {
                      currentNote.setOctave(Integer.parseInt(currentPropertyValue.toString().trim()));
                  }
                  if (currentProperty.equals("isRest")) {
                      currentNote.setRest(currentPropertyValue.toString().trim().equals("1") ? true : false);
                  }
                  currentPropertyValue = new StringBuilder();
                  currentProperty = null;
              }
          }
  
          public List<NoteElement> getNotes() {
              return notes;
          }
      }
  }