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(;;) { fd = new FileDialog(f,"Open MIDI file or choose cancel to finish.", FileDialog.LOAD); fd.show(); 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.
scoreCount++; int tempLength = 0; 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(); pitchRange(pitch); double rv = nextNote.getRhythmValue(); rhythmRange(rv); tempLength++; upOrDown(pitch); } } } 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) { 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) { 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).
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);
|