-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathMidiTester.java
More file actions
233 lines (200 loc) · 11 KB
/
MidiTester.java
File metadata and controls
233 lines (200 loc) · 11 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
import javax.sound.midi.*;
import java.io.File;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.TreeMap;
/* Class as a tester for JMidi objects. This will eventually become
a MIDI parser which will create a file of a certain format for
data analysis. Currently, this will transcribe the information
in a given MIDI file into a human readable output (and not necessarily
the best output for data analysis...yet).
*/
public class MidiTester {
// Variables that represent the MIDI value for various types of MIDI messages.
// Depending on the type of MIDI file, some choose to format a NOTE_OFF message
// using the NOTE_OFF tag or simply a NOTE_ON message with 0 velocity.
public static final int NOTE_ON = 0x90;
public static final int NOTE_OFF = 0x80;
public static final int MIDI_CONTROL_CHANGE = 0xB0; // Header for control change messages
public static final int PROGRAM_CHANGE = 0xC0; // Header for program (instrument/channel) change messages
// Variables that represent MIDI values for various meta-messages.
public static final int TIME_SIGNATURE = 0x58;
public static final int KEY_SIGNATURE = 0x59;
public static final int TRACK_NAME = 0x03;
public static final int RANDOM_TEXT = 0x01;
public static final int SET_TEMPO = 0x51;
public static final int END_OF_TRACK = 0x2F;
public static final int MIDI_PORT_MESSAGE = 0x21;
// Relative file path of the MIDI file that you want to analyze.
public static final String FILE_NAME = ".\\MIDI_Files\\Fur_Elise.mid";
public static void main(String[] args) throws Exception {
// PrintStream out = new PrintStream(new FileOutputStream("output.txt"), true);
// System.setOut(out);
// Creates a Java Sequence for us to manipulate the MIDI file and
// a LinkedList to store the notes when determining when they are turned off.
Sequence sequence = MidiSystem.getSequence(new File(FILE_NAME));
LinkedList<JMidiNote> notesPlayed = new LinkedList<>();
TreeMap<Long, JMidiNoteCluster> noteClusters = new TreeMap<>();
// Goes through each track
int trackNumber = 0;
for (Track track : sequence.getTracks()) {
trackNumber++;
System.out.println("Track " + trackNumber + ": size = " + track.size());
System.out.println();
// Goes through each MIDI message in the track
for (int i = 0; i < track.size(); i++) {
MidiEvent event = track.get(i);
MidiMessage message = event.getMessage();
if (message instanceof ShortMessage) {
ShortMessage sm = (ShortMessage) message;
// Is it a note (on) message?
if (sm.getCommand() == NOTE_ON && sm.getData2() != 0) {
// Creates the JMidi object and adds it to the LinkedList.
JMidiNote midiNote = new JMidiNote(event.getTick(), sm.getChannel(), sm.getData2(),
sm.getData1(), sequence.getResolution());
notesPlayed.add(midiNote);
}
// Is it a note OFF message?
else if(sm.getData2() == 0){
// Creates specialized note-off JMidiNote
JMidiNote noteOff = new JMidiNote(event.getTick(),sm.getChannel(), sm.getData1(), sequence.getResolution());
// Goes through all the notes already played
for(JMidiNote possibleNote : notesPlayed){
if(possibleNote.isOn && noteOff.isEndNoteOfThisNote(possibleNote)){
// found it! Note hasn't been turned off and is the same as the note-off message
// Sets tickStop and note length.
possibleNote.setTickStop(noteOff.getTickStart());
possibleNote.setUpNoteLength();
possibleNote.isOn = false; // turns off the note
if(!noteClusters.containsKey(possibleNote.getTickStart())){
// new note cluster!
JMidiNoteCluster noteCluster = new JMidiNoteCluster(possibleNote.getTickStart(),
possibleNote);
noteClusters.put(possibleNote.getTickStart(),noteCluster);
}
else{
// old note cluster
JMidiNoteCluster noteCluster = noteClusters.get(possibleNote.getTickStart());
noteCluster.cluster.add(possibleNote);
noteClusters.put(possibleNote.getTickStart(), noteCluster);
}
//System.out.println(possibleNote); // prints it in place (might be changed later)
notesPlayed.remove(possibleNote);
break; // stop the search
}
}
}
// Is it a MIDI Program Change Message?
else if (sm.getCommand() == PROGRAM_CHANGE) {
System.out.print("Select Channel Mode: ");
JMidiNote.setUpChannelLookup(); // Sets up the channel lookup map if needed
System.out.print(JMidiNote.CHANNEL_LOOKUP.get(sm.getData1()));
System.out.println();
}
// Is it a MIDI Control Change Message?
else if (sm.getCommand() == MIDI_CONTROL_CHANGE) {
JMidiControl.initMessageSet(); // Sets up the control message map if needed
JMidiControl control = new JMidiControl(sm.getData1(), sm.getData2());
System.out.print(control);
System.out.println();
System.out.println();
}
} else {
// Most likely, the message is a MIDI MetaMessage
if (message instanceof MetaMessage) {
MetaMessage metaMessage = (MetaMessage) message;
int messageType = metaMessage.getType();
// Is it a time signature message?
if (messageType == TIME_SIGNATURE) {
byte[] timeInfo = metaMessage.getData();
JMidiTimeSign time = new JMidiTimeSign(timeInfo);
System.out.println("Tick #: " + event.getTick()+ time);
}
// Is it a key signature message?
else if (messageType == KEY_SIGNATURE) {
JMidiKeySign.initList(); // Initialize key signature list if needed
byte[] keyInfo = metaMessage.getData();
JMidiKeySign.JKeySignature keySign = JMidiKeySign.findKey(keyInfo);
System.out.println(keySign);
}
// Is it a tempo message?
else if (messageType == SET_TEMPO) {
byte[] tempo = metaMessage.getData();
JMidiTempo midiTempo = new JMidiTempo(event.getTick(), sequence.getResolution(), tempo);
System.out.println(midiTempo);
}
// End of Track message?
else if (messageType == END_OF_TRACK) {
JMidiControl mes = new JMidiControl();
System.out.println(mes);
System.out.println();
}
// MIDI port message?
else if (messageType == MIDI_PORT_MESSAGE) {
System.out.println("MIDI Port: " + (metaMessage.getData()[0] + 1));
}
// Random text/track name?
else if (messageType == TRACK_NAME || messageType == RANDOM_TEXT) {
// NEVER USED IN SAMPLE BUT THIS IS WHAT WE WOULD DO
System.out.println("TRACK NAME:");
byte[] trackInfo = metaMessage.getData();
for (byte info : trackInfo)
System.out.print((char) info);
System.out.println();
}
// No idea...
else
System.out.print("Type of MetaMessage: " + metaMessage.getType());
System.out.println();
}
}
}
int i = 0;
for(long tickStart : noteClusters.keySet()){
i++;
JMidiNoteCluster cluster = noteClusters.get(tickStart);
// format: starting tick, note, dynamic, ratio to quarter note
System.out.print(tickStart + "," + "[");
for(JMidiNote note : cluster.cluster){
System.out.print(note.channelNumber + ",");
}
System.out.print("],");
System.out.print("[");
for(JMidiNote note : cluster.cluster){
System.out.print(note + ",");
}
System.out.print("],");
System.out.print("[");
for(JMidiNote note : cluster.cluster){
System.out.print(note.getDynamic() + ",");
}
System.out.print("],");
System.out.print("[");
for(JMidiNote note : cluster.cluster){
System.out.print(note.getNoteInTermsOfQuarter() + ",");
}
System.out.println("]");
}
}
}
public static class JMidiNoteCluster implements Comparable<JMidiNoteCluster> {
public long startingTick;
public ArrayList<JMidiNote> cluster;
public JMidiNoteCluster(long start, JMidiNote firstNote){
startingTick = start;
cluster = new ArrayList<>();
cluster.add(firstNote);
}
@Override
public int compareTo(JMidiNoteCluster o) {
if(this.startingTick < o.startingTick)
return -1;
else if(this.startingTick > o.startingTick)
return 1;
else
return 0;
}
}
}