Dear all,
I am developing some software that can deal with MIDI files and do some fun stuff with them, but I am running into some strange trouble when analysing the MidiEvents.
I want to list all the MidiEvents occurring in a particular Track, with their tickposition, status, and precise byte-message. The tick position works fine, but I am having troubles with the status bytes.
Many of the MidiEvents are not recognised by Java and are neither NoteOn nor NoteOff events, although I think they should be. Is it me having implemented something wrong, or are these MIDI-files with a really strange format?
The following is an excerpt of the code:
public class MidiEventStorage
{
static Sequencer midiSequencer;
public static void main(String[] args)
{
Sequence mySequence = loadMidiSequence();
RedBlackTree<MyMidiEvent> midiEventTree = midiSequenceToTree(mySequence);
midiSequencer.start();
midiEventTree.printTree();
/*----*/pauseProg();/*--------------------------------------*/
midiSequencer.stop();
midiSequencer.close();
}// End main
private static Sequence loadMidiSequence()
{
Sequence midiSequence;
File myFile = new File(System.getProperty("user.dir"));
JFileChooser fileChooser = new JFileChooser(myFile);
if (fileChooser.showOpenDialog(null) == JFileChooser.APPROVE_OPTION)
{
if ( fileChooser.getSelectedFile().exists())
{
try
{
midiSequencer = MidiSystem.getSequencer();
midiSequencer.open();
midiSequence = MidiSystem.getSequence(fileChooser.getSelectedFile());
midiSequencer.setSequence(midiSequence);
return midiSequence;
}
catch (MidiUnavailableException e) {}
catch (InvalidMidiDataException e) {}
catch (IOException e) {}
}
}
return null;
}
public static RedBlackTree<MyMidiEvent> midiSequenceToTree(Sequence midiSequence)
{
RedBlackTree<MyMidiEvent> midiEventTree = new RedBlackTree<MyMidiEvent>();
Track [] midiTracks = midiSequence.getTracks();
Track currentTrack;
int size;
for (int ii = 0; ii < midiTracks.length; ii++)
{
currentTrack = midiTracks[ii];
size = currentTrack.size();
for (int jj = 0; jj < size; jj++)
{
midiEventTree.insert( new MyMidiEvent(currentTrack.get(jj), ii) );
}
}
return midiEventTree;
}
public static void pauseProg()
{
System.out.print("Press [ENTER] to continue...");
try
{
System.in.read();
System.in.skip(System.in.available());
}
catch(Exception e){e.printStackTrace();}
}
}
import javax.sound.midi.MidiEvent;
import javax.sound.midi.MidiMessage;
import javax.sound.midi.ShortMessage;
public class MyMidiEvent extends MidiEvent implements Comparable<MyMidiEvent>
{
private int trackNumber;
public MyMidiEvent(MidiEvent event, int track)
{
super(event.getMessage(),event.getTick());
trackNumber = track;
}
public MidiEvent toMidiEvent()
{
return new MidiEvent(getMessage(), getTick());
}
public String toString()
{
return "@ tick " + getTick() + ": " + getMessageType() + " (" + getByteMessage() + "), track " + trackNumber;
}
private static String format(byte b)
{
String binaryString = Integer.toBinaryString(b & 0xFF);
while(binaryString.length() < 8) binaryString = "0" + binaryString;
return binaryString;
}
public int getTrack() {return trackNumber;}
public String getMessageType()
{
int messageStatus = (int) (this.getMessage().getStatus() & 0xFF);
// Note: a NoteOff message has either a NOTE_OFF message status
// or a NoteOn message status with argument (volume) 0. This
// method does not yet cover for the latter case!
if (messageStatus == ShortMessage.ACTIVE_SENSING)
return messageStatus + "-" + "ActiveSensing";
if (messageStatus == ShortMessage.CHANNEL_PRESSURE)
return messageStatus + "-" + "ChannelPressure";
if (messageStatus == ShortMessage.CONTINUE)
return messageStatus + "-" + "Continue";
if (messageStatus == ShortMessage.CONTROL_CHANGE)
return messageStatus + "-" + "ControlChange";
if (messageStatus == ShortMessage.END_OF_EXCLUSIVE)
return messageStatus + "-" + "EndOfExclusive";
if (messageStatus == ShortMessage.MIDI_TIME_CODE)
return messageStatus + "-" + "MidiTimeCode";
if (messageStatus == ShortMessage.NOTE_OFF)
return messageStatus + "-" + "NoteOff";
if (messageStatus == ShortMessage.NOTE_ON)
return messageStatus + "-" + "NoteOn";
if (messageStatus == ShortMessage.PITCH_BEND)
return messageStatus + "-" + "PitchBend";
if (messageStatus == ShortMessage.POLY_PRESSURE)
return messageStatus + "-" + "PolyphonicPressure";
if (messageStatus == ShortMessage.PROGRAM_CHANGE)
return messageStatus + "-" + "ProgramChange";
if (messageStatus == ShortMessage.SONG_POSITION_POINTER)
return messageStatus + "-" + "SongPositionPointer";
if (messageStatus == ShortMessage.SONG_SELECT)
return messageStatus + "-" + "SongSelect";
if (messageStatus == ShortMessage.START)
return messageStatus + "-" + "Start";
if (messageStatus == ShortMessage.STOP)
return messageStatus + "-" + "Stop";
if (messageStatus == ShortMessage.SYSTEM_RESET)
return messageStatus + "-" + "SystemReset";
if (messageStatus == ShortMessage.TIMING_CLOCK)
return messageStatus + "-" + "TimingClock";
if (messageStatus == ShortMessage.TUNE_REQUEST)
return messageStatus + "-" + "TuneRequest";
return messageStatus + "-" + "Unknown";
}
public String getByteMessage()
{
byte [] byteArray = this.getMessage().getMessage();
String bitRepresentation = "";
for (int ii = 0; ii < byteArray.length; ii++)
{
bitRepresentation = bitRepresentation + " " + format(byteArray[ii]) ;
}
return bitRepresentation;
}
//******************************************
// Interface Methods (from Comparable interface)
//******************************************
final int BEFORE = -1;
final int EQUAL = 0;
final int AFTER = 1;
public int compareTo(MyMidiEvent event)
{
// compare tick position
if (this.getTick() > event.getTick())
return AFTER;
else if (this.getTick() < event.getTick())
return BEFORE;
// compare track number
else if (this.trackNumber > event.trackNumber)
return AFTER;
else if (this.trackNumber < event.trackNumber)
return BEFORE;
else
return EQUAL;
}
}
Please assume that I have a class RedBlackTree that works fine, and which contains a method printTree() that executes myMidiEvent.toString() at all the nodes it contains.
The System output that I tend to get generally looks like this:
@ tick 576: 153-Unknown ( 10011001 00101010 01011010), track 3
@ tick 636: 153-Unknown ( 10011001 00101010 00000000), track 3
@ tick 664: 149-Unknown ( 10010101 00100110 00000000), track 1
@ tick 768: 144-NoteOn ( 10010000 01000011 01000000), track 0
@ tick 768: 153-Unknown ( 10011001 00111000 01000000), track 3
@ tick 812: 128-NoteOff ( 10000000 01000011 00000000), track 0
@ tick 816: 144-NoteOn ( 10010000 01000010 01001010), track 0
@ tick 828: 153-Unknown ( 10011001 00111000 00000000), track 3
@ tick 860: 128-NoteOff ( 10000000 01000010 00000000), track 0
@ tick 864: 144-NoteOn ( 10010000 01000001 01011110), track 0
@ tick 864: 149-Unknown ( 10010101 00101010 01000000), track 1
@ tick 908: 128-NoteOff ( 10000000 01000001 00000000), track 0
@ tick 912: 144-NoteOn ( 10010000 01000000 01110010), track 0
@ tick 956: 128-NoteOff ( 10000000 01000000 00000000), track 0
@ tick 956: 149-Unknown ( 10010101 00101010 00000000), track 1
@ tick 960: 153-Unknown ( 10011001 00111000 00010000), track 3
So some events are recognised, but many are not. Can someone tell me why not? What is going on in the brains of Java?
Best,
Jelle