Simple Analysis of MIDI files

In this example we'll look at how to read in a MIDI file (created by jMusic or some other program) and perform a basic statistical analysis on it. We'll check the pitch and rhythm value range, the score length, and elements of the melodic contour. From this starting point you should be able to extend this example to check other aspects of the music you're interested in.

Here is an example of the output from this program:

========================================================
STATS after 2 files. Last score called allemande a.mid
========================================================
Lowest note is 60
Highest note is 79
----------------------------------------
Shortest note is 0.5
Longest note is 3.8046875
----------------------------------------
Smallest score contains 26 notes
Largest score contains 44 notes
----------------------------------------
Upward movements were 24
Downward movements were 44
----------------------------------------

Click here to view the source file.

Let's have a closer look.

import jm.JMC;
import jm.music.data.*;
import jm.music.tools.*;
import jm.util.*;
import java.util.Vector;
import java.awt.event.*;
import java.awt.*;
import java.util.Enumeration;

Lots of stuff is imported in this class. the jm classes as usual, as well the vector and Enumeration classes for unpacking the jMusic score structure so we can then analyse the notes information, and the awt class is required because we do some simple GUI moves to make the selecting of MIDI files easier.

public final class SimpleAnalysis extends Frame implements JMC{
static int highNote = 0;
static int lowNote = 127;
static double longestRhythm = 0.0;
static double shortestRhythm = 1000.0;
static int shortLength = 1000000;
static int longLength = 0;
static int ascending = 0;
static int descending = 0;
static int prev = 60;
static Score s = new Score();
static int scoreCount = 0;

The class is declared. Notice that is extends the Frame class, this is used for the dialogue box from which MIDI files are selected. We don't actually build a window that we see. We declare variables for each of the attributes we want to collect data on.

	public static void main(String[] args){
new SimpleAnalysis();
}
The main() method is very short, it simply calls the constructor.


	public SimpleAnalysis() {
FileDialog fd;
Frame f = new Frame();
The constructor takes no arguments. A dialog box for reading files is declared as well as an instance of the frame class which it requires.


         for(;;) {               
//open a MIDI file
fd = new FileDialog(f,"Open MIDI file or choose cancel to finish.",
FileDialog.LOAD);
fd.show();
//break out when user presses cancel
if(fd.getFile() == null) break;

Read.midi(s, fd.getDirectory()+fd.getFile());
s.setTitle(fd.getFile());

The for loop goes forever - well until the Cancel button on the file dialog causes a null file to be returned at which point we break out of the loop. The last two lines of this segment place a jMusic score into 's' by reading an converting the selected MIDI file. The score's title is set to the the name of the file. This is used later in printing the analysis to identify the file.

   		//keep track of the number of scores analysed    
scoreCount++;
//reset melody length
int tempLength = 0;
// iterate through each note
//find the highest and lowest notes
Enumeration enum = s.getPartList().elements();
while(enum.hasMoreElements()){
Part nextPt = (Part)enum.nextElement();
Enumeration enum2 = nextPt.getPhraseList().elements();
while(enum2.hasMoreElements()){
Phrase nextPhr = (Phrase)enum2.nextElement();
Enumeration enum3 = nextPhr.getNoteList().elements();
while(enum3.hasMoreElements()){
Note nextNote = (Note)enum3.nextElement();
int pitch = nextNote.getPitch();
//check range
pitchRange(pitch);
double rv = nextNote.getRhythmValue();
//check rhythmic values
rhythmRange(rv);
//check melody length
tempLength++;
//check direction
upOrDown(pitch);
}
}
}
//update length extremes
musicLength(tempLength);

OK, there's lot's of work done in this code segment. basically we read every note in every phrase in every part of the score and update the analysis data after each. The Enumeration interface is used to help us with this task. It often looks complicated at first but is only so because of the number of layers we need to peel back from the score structure to get to the notes. The Enumeration gets the vector (a list) of the parts, phrases, notes (as the case may be) and loops through the list for each element while there are more elements. The use of Vectors and Enumeration helps us cope with the fact that there any be any number of notes, in any number of phrases, in any number of parts.

A series of methods (that we'll get to later), are called which update variables such as the highest and longest notes. You should be able to add more of your own methods to check other aspects of the notes which interest you.

	System.out.println("=========================");
System.out.println("STATS after "+scoreCount+" files."+
"Last score called "+s.getTitle());
System.out.println("=========================");
System.out.println("Lowest note is "+lowNote);
System.out.println("Highest note is "+highNote);
System.out.println("----------------------------------------");
System.out.println("Shortest note is "+shortestRhythm);
System.out.println("Longest note is "+longestRhythm);
System.out.println("----------------------------------------");
System.out.println("Smallest score contains " + shortLength + " notes");
System.out.println("Largest score contains " + longLength + " notes");
System.out.println("----------------------------------------");
System.out.println("Upward movements were " + ascending);
System.out.println("Downward movements were " + descending);
System.out.println("----------------------------------------");

After collecting the analysis information this section of the code prints it to the screen. This is done after each MIDI file is read. the data is accumulative, that is for example, the highest note after three MIDI files is the highest note in any of the three MIDI files.

	public void pitchRange(int pitch) {
//check the range of the MIDI file
if(pitch < lowNote) lowNote = pitch;
if(pitch > highNote) highNote = pitch;
}

This method does a simple check to see if the current note's pitch is higher or lower than any previous pitch has been, and if so updates the relevant variable.

 	public void rhythmRange(double rv) {
//check the range of the MIDI file
if(rv < shortestRhythm) shortestRhythm = rv;
if(rv > longestRhythm) longestRhythm = rv;
}

This method similarly checks the longest and shortest rhythmicValues.

	public void musicLength(int temp) {
if(temp < shortLength) shortLength = temp;
if(temp > longLength) longLength = temp;
}

This method checks the number of notes in a score and updates the records of the longest and shortest values.

	public void upOrDown(int pitch) {
if(pitch < prev) descending++;
if(pitch > prev) ascending++;
prev = pitch;
}
}

This method checks weather a pitch is higher or lower than the previous one, then updates the variables keeping stats on that info. (This is a little dodgy because it doesn't take into account part boundaries, but in large scores that is statistically negligible).

More jMusic Analysis Information

Check out Adam Kirby's analysis classes in the documentation for another readily availible source of jMusic analysis support. See also the PhraseAnalysis application.

We encourage you to write you own analysis methods, then email them to us so we can post them for the whole jMusic community to use.

To do histograms of a score:

Use the following code segment after importing jm.util.View :

View.histogram(myScore);