comparison src/org/qmul/eecs/c4dm/sia/midi/MidiParser.java @ 37:cb7f70e10a0c

New
author stevenh
date Tue, 26 Feb 2013 13:30:46 +0000
parents
children fa9030705e93
comparison
equal deleted inserted replaced
36:029968a13f8b 37:cb7f70e10a0c
1 package org.qmul.eecs.c4dm.sia.midi;
2
3 import java.io.File;
4 import java.io.FileNotFoundException;
5 import java.io.FileOutputStream;
6 import java.io.IOException;
7 import java.util.ArrayList;
8 import java.util.Collection;
9 import java.util.HashMap;
10 import java.util.Iterator;
11 import java.util.List;
12 import java.util.Vector;
13
14 import javax.sound.midi.InvalidMidiDataException;
15 import javax.sound.midi.MetaMessage;
16 import javax.sound.midi.MidiEvent;
17 import javax.sound.midi.MidiMessage;
18 import javax.sound.midi.MidiSystem;
19 import javax.sound.midi.Sequence;
20 import javax.sound.midi.ShortMessage;
21 import javax.sound.midi.SysexMessage;
22 import javax.sound.midi.Track;
23
24 import org.qmul.eecs.c4dm.sia.model.Datapoint;
25 import org.qmul.eecs.c4dm.sia.model.DimensionValue;
26 import org.qmul.eecs.c4dm.sia.rdf.Namespaces;
27
28 import com.hp.hpl.jena.ontology.OntClass;
29 import com.hp.hpl.jena.ontology.OntModel;
30 import com.hp.hpl.jena.ontology.OntResource;
31 import com.hp.hpl.jena.rdf.model.AnonId;
32 import com.hp.hpl.jena.rdf.model.ModelFactory;
33 import com.hp.hpl.jena.rdf.model.NodeIterator;
34 import com.hp.hpl.jena.rdf.model.Property;
35 import com.hp.hpl.jena.rdf.model.RDFNode;
36 import com.hp.hpl.jena.rdf.model.Resource;
37 import com.hp.hpl.jena.rdf.model.Statement;
38 import com.hp.hpl.jena.rdf.model.StmtIterator;
39 import com.hp.hpl.jena.util.iterator.ExtendedIterator;
40 import com.hp.hpl.jena.vocabulary.RDF;
41 import com.sun.media.sound.MidiUtils;
42
43 public class MidiParser {
44
45 public static final int TIME_DIMENSION = 1;
46 public static final int PITCH_DIMENSION = 2;
47 public static final int CHANNEL_DIMENSION = 3;
48
49 // The ontology loaded as dataset
50 private static final String ontology = "file:src/rdf/siaDatapointOntology.n3";
51
52 // The final output file
53 private static final String finalModelFileName = "src/rdf/midiModel";
54
55 public static void main(String[] args)
56 {
57 // First create a Jena ontology model
58 OntModel ontModel = ModelFactory
59 .createOntologyModel(); // OntModelSpec.OWL_MEM
60
61 // Then read the data from the file into the ontology model
62 ontModel.read(ontology, "N3");
63
64 // Set the value of SIA_NS_URI as the namespace for 'sia' found in
65 // the ontology. This must be done before we use any of the sia.model classes.
66 Namespaces.SIA_NS_URI = ontModel.getNsPrefixURI("sia");
67 OntClass datapointClass = ontModel.getOntClass(Datapoint.RESOURCE_URI);
68 Resource datapointResource = ontModel.getOntResource(datapointClass);
69 Property siaDimValProperty = ontModel.createProperty(DimensionValue.PROPERTY_URI);
70 Property siaDimensionProperty = ontModel.createProperty(DimensionValue.DIMENSION_URI);
71 Property siaValueProperty = ontModel.createProperty(DimensionValue.VALUE_URI);
72
73 String midiFileName = "/Volumes/USB DISK/portable/ewerts/Cantata_16_no_5-mids/score.mid";
74
75 File midiFile1 = new File(midiFileName);
76
77 Sequence sequence = null;
78 try {
79 sequence = MidiSystem.getSequence(midiFile1);
80 } catch (InvalidMidiDataException e) {
81 e.printStackTrace();
82 System.exit(1);
83 } catch (IOException e) {
84 e.printStackTrace();
85 System.exit(1);
86 }
87
88 MidiUtils.TempoCache tempoCache = new MidiUtils.TempoCache(sequence);
89
90 Track[] tracks = sequence.getTracks();
91 int numTracks = tracks.length;
92 float divisionType = sequence.getDivisionType();
93 List<Datapoint> datapoints = new ArrayList<Datapoint>();
94
95 HashMap<Integer, Integer> trackDimensionMap = getTrackToDimensionIndexMap(sequence);
96
97 if (trackDimensionMap.isEmpty())
98 {
99 try {
100 throw new Exception("Couldn't find any track to dimension mappings");
101 } catch (Exception e) {
102 e.printStackTrace();
103 System.exit(1);
104 }
105 }
106
107 long maxTick = 0;
108
109 for (int trackIdx = 0; trackIdx < numTracks; trackIdx++)
110 {
111 System.out.println("Track " + trackIdx + ":");
112 int numEvents = tracks[trackIdx].size();
113 for (int eventIdx = 0; eventIdx < numEvents; eventIdx++)
114 {
115 MidiEvent event = tracks[trackIdx].get(eventIdx);
116 long tick = event.getTick();
117 MidiMessage midiMessage = event.getMessage();
118
119 int midiMessageLength = midiMessage.getLength();
120 byte[] messageBytes = midiMessage.getMessage();
121 int status = midiMessage.getStatus();
122 System.out.println("tick = " + tick + " midiMessageLength = " + midiMessageLength + " status byte = " + status);
123
124 int s = status & 0x80;
125 if (s == 0x80)
126 {
127 System.out.println("STATUS MESSAGE (s = " + s + ")");
128 }
129 else
130 {
131 System.out.println("not a status message (s = " + s + ")");
132 }
133
134 // Determine the type of this message (short, sysex or meta)
135 if (midiMessage instanceof ShortMessage)
136 {
137 System.out.print("ShortMessage ");
138
139 // Determine which command is being issued
140 ShortMessage shortMessage = (ShortMessage)midiMessage;
141 int messageCommand = shortMessage.getCommand();
142 int channel = shortMessage.getChannel();
143 int data1 = shortMessage.getData1();
144 int data2 = shortMessage.getData2();
145
146 if (messageCommand == ShortMessage.ACTIVE_SENSING)
147 {
148 System.out.print("ignoring ACTIVE_SENSING");
149 }
150 else if (messageCommand == ShortMessage.CHANNEL_PRESSURE)
151 {
152 System.out.print("ignoring CHANNEL_PRESSURE");
153 }
154 else if (messageCommand == ShortMessage.CONTINUE)
155 {
156 System.out.print("ignoring CONTINUE");
157 }
158 else if (messageCommand == ShortMessage.CONTROL_CHANGE)
159 {
160 System.out.print("ignoring CONTROL_CHANGE");
161 }
162 else if (messageCommand == ShortMessage.END_OF_EXCLUSIVE)
163 {
164 System.out.print("ignoring END_OF_EXCLUSIVE");
165 }
166 else if (messageCommand == ShortMessage.MIDI_TIME_CODE)
167 {
168 System.out.print("ignoring MIDI_TIME_CODE");
169 }
170 else if (messageCommand == ShortMessage.NOTE_OFF)
171 {
172 System.out.println("NOTE_OFF");
173 long microsecs = MidiUtils.tick2microsecond(sequence, tick, tempoCache);
174 System.out.println(" microsecs = " + microsecs);
175 }
176 else if (messageCommand == ShortMessage.NOTE_ON)
177 {
178 if (tick > maxTick)
179 {
180 maxTick = tick;
181 }
182 System.out.println("NOTE_ON");
183 long microsecs = MidiUtils.tick2microsecond(sequence, tick, tempoCache);
184 System.out.println(" microsecs = " + microsecs);
185
186 DimensionValue timeDimVal = new DimensionValue();
187 timeDimVal.setDimension(TIME_DIMENSION);
188 timeDimVal.setValue(tick);
189
190 DimensionValue dimVal = new DimensionValue();
191 dimVal.setDimension(trackDimensionMap.get(trackIdx));
192 dimVal.setValue(data1);
193
194 Datapoint datapoint = new Datapoint();
195 Vector<DimensionValue> dimVals = new Vector<DimensionValue>();
196 dimVals.add(timeDimVal);
197 dimVals.add(dimVal);
198
199 datapoint.setDimensionValues(dimVals);
200 datapoints.add(datapoint);
201
202 // RDF
203 Resource datapointBnode = ontModel.createResource(AnonId.create());
204 ontModel.add(datapointBnode, RDF.type, datapointResource);
205
206 Resource timeDimValBnode = ontModel.createResource(AnonId.create());
207 Resource pitchDimValBnode = ontModel.createResource(AnonId.create());
208
209 ontModel.add(datapointBnode, siaDimValProperty, timeDimValBnode);
210 ontModel.addLiteral(timeDimValBnode, siaDimensionProperty, TIME_DIMENSION);
211 ontModel.addLiteral(timeDimValBnode, siaValueProperty, tick);
212
213 ontModel.add(datapointBnode, siaDimValProperty, pitchDimValBnode);
214 ontModel.addLiteral(pitchDimValBnode, siaDimensionProperty, trackDimensionMap.get(trackIdx).intValue());
215 ontModel.addLiteral(pitchDimValBnode, siaValueProperty, data1);
216 }
217 else if (messageCommand == ShortMessage.PITCH_BEND)
218 {
219 System.out.print("ignoring PITCH_BEND");
220 }
221 else if (messageCommand == ShortMessage.POLY_PRESSURE)
222 {
223 System.out.print("ignoring POLY_PRESSURE");
224 }
225 else if (messageCommand == ShortMessage.PROGRAM_CHANGE)
226 {
227 System.out.print("ignoring PROGRAM_CHANGE");
228 }
229 else if (messageCommand == ShortMessage.SONG_POSITION_POINTER)
230 {
231 System.out.print("ignoring SONG_POSITION_POINTER");
232 }
233 else
234 {
235 System.out.print("unrecognised midi message command (" + messageCommand + ")");
236 }
237 System.out.print(", channel " + channel + ", data1 = [" + data1 + "], data2 = [" + data2 + "]");
238 System.out.println();
239 }
240 else if (midiMessage instanceof MetaMessage)
241 {
242 System.out.println("MetaMessage");
243
244 MetaMessage metaMessage = (MetaMessage)midiMessage;
245 byte[] metaMessageData = metaMessage.getData();
246 int metaMessageLength = metaMessage.getLength();
247 int metaMessageType = metaMessage.getType();
248 System.out.println("metaMessageType = " + metaMessageType + ", metaMessageLength = " + metaMessageLength);
249
250 // Determine message type
251 if (metaMessageType == 81)
252 {
253 if (divisionType == Sequence.PPQ)
254 {
255 // Do nothing - we've dealt with PPQ tempo data elsewhere
256 }
257 else
258 {
259 try {
260 throw new Exception("Not yet implemented SMPTE tempo metadata");
261 } catch (Exception e) {
262 e.printStackTrace();
263 System.exit(1);
264 }
265 }
266 }
267
268 for (int dataIdx = 0; dataIdx < metaMessageData.length; dataIdx++)
269 {
270 System.out.println("\tmetaMessageData[" + dataIdx + "] = " + (metaMessageType == 81 ? metaMessageData[dataIdx] : (char)metaMessageData[dataIdx]));
271 }
272
273 }
274 else if (midiMessage instanceof SysexMessage)
275 {
276 // We can safely ignore these messages
277 System.out.println("ignoring SysexMessage");
278 }
279 else
280 {
281 System.out.println("Unknown MidiMessage type (" + midiMessage.getClass().toString() + ")");
282 }
283
284 for (int byteIdx = 0; byteIdx < midiMessageLength; byteIdx++)
285 {
286 byte messageByte = messageBytes[byteIdx];
287 System.out.println("\tbyte[" + byteIdx + "] = " + messageByte);
288 }
289 }
290 }
291
292 // Data integrity start - set any 'missing' dimensions to zero value
293 // Note the highest dimension for later use
294 Collection<Integer> values = trackDimensionMap.values();
295 Iterator<Integer> valuesIter = values.iterator();
296 int maxDimension = 0;
297 while (valuesIter.hasNext())
298 {
299 Integer value = valuesIter.next();
300 if (value > maxDimension)
301 {
302 maxDimension = value;
303 }
304 }
305
306 // Iterate over all datapoints
307 ExtendedIterator<? extends OntResource> datapointIter = datapointClass.listInstances();
308 OntResource datapointIndividual;
309 OntModel ontModelTemp = ModelFactory
310 .createOntologyModel(); // OntModelSpec.OWL_MEM
311
312 while (datapointIter.hasNext())
313 {
314 datapointIndividual = datapointIter.next();
315
316 // Find all Dimension Values for this datapoint
317 NodeIterator dimValIter = datapointIndividual.listPropertyValues(siaDimValProperty);
318
319 // Create a hashmap of dimensions for this datapoint
320 HashMap<Integer, Boolean> dimensions = new HashMap<>();
321 while (dimValIter.hasNext())
322 {
323 RDFNode dimVal = dimValIter.next();
324 NodeIterator dims = ontModel.listObjectsOfProperty(dimVal.asResource(), siaDimensionProperty);
325 while (dims.hasNext())
326 {
327 int dim = dims.next().asLiteral().getInt();
328 dimensions.put(dim, true);
329 System.out.println(dim);
330 }
331 }
332
333 for (int i = 2; i <= maxDimension; i++)
334 {
335 if (!dimensions.containsKey(i))
336 {
337 Resource zeroValDimValBnode = ontModel.createResource(AnonId.create());
338
339 Resource dpResource = datapointIndividual.asResource();
340 ontModelTemp.add(dpResource, siaDimValProperty, zeroValDimValBnode);
341 ontModelTemp.addLiteral(zeroValDimValBnode, siaDimensionProperty, i);
342 ontModelTemp.addLiteral(zeroValDimValBnode, siaValueProperty, 0);
343 }
344 }
345
346 }
347 ontModel.add(ontModelTemp);
348 // Data integrity end
349
350 System.out.println("done");
351
352 // Print out what we've got now
353 System.out.println("------------------");
354 StmtIterator stmtIterator = ontModel.listStatements();
355 printStmts(stmtIterator);
356
357 // Write rdf to file
358 File outFileRdf = new File(finalModelFileName + ".rdf");
359 File outFileN3 = new File(finalModelFileName + ".n3");
360 FileOutputStream outFileOutputStreamRdf = null;
361 FileOutputStream outFileOutputStreamN3 = null;
362
363 // RDF/XML version
364 try {
365 outFileOutputStreamRdf = new FileOutputStream(outFileRdf);
366 ontModel.writeAll(outFileOutputStreamRdf, "RDF/XML", null);
367 } catch (FileNotFoundException e) {
368 System.out.println("Unable to write to file: "
369 + outFileRdf.getAbsolutePath());
370 e.printStackTrace();
371 System.exit(1);
372 }
373
374 try {
375 outFileOutputStreamRdf.close();
376 } catch (IOException e1) {
377 e1.printStackTrace();
378 System.exit(1);
379 }
380
381 // N3 version
382 try {
383 outFileOutputStreamN3 = new FileOutputStream(outFileN3);
384 ontModel.writeAll(outFileOutputStreamN3, "N3", null);
385 } catch (FileNotFoundException e) {
386 System.out.println("Unable to write to file: "
387 + outFileN3.getAbsolutePath());
388 e.printStackTrace();
389 System.exit(1);
390 }
391
392 try {
393 outFileOutputStreamN3.close();
394 } catch (IOException e1) {
395 e1.printStackTrace();
396 System.exit(1);
397 }
398
399 System.out.println("Model written to files: "
400 + outFileRdf.getAbsolutePath() + " and " + outFileN3.getAbsolutePath());
401
402 System.out.println("max tick: " + maxTick);
403
404 }
405
406 private static HashMap<Integer, Integer> getTrackToDimensionIndexMap(Sequence sequence) {
407
408 int numTracks = sequence.getTracks().length;
409 int siaDatapointDimension = 2; // Dimension 1 is reserved for time
410
411 HashMap<Integer, Integer> trackDimensionMap = new HashMap<Integer, Integer>();
412 Track[] tracks = sequence.getTracks();
413
414 for (int trackIdx = 0; trackIdx < numTracks; trackIdx++)
415 {
416 boolean trackIsAudible = false;
417 int numEvents = tracks[trackIdx].size();
418
419 for (int eventIdx = 0; eventIdx < numEvents; eventIdx++)
420 {
421 MidiEvent event = tracks[trackIdx].get(eventIdx);
422 MidiMessage midiMessage = event.getMessage();
423
424 // Determine the type of this message (short, sysex or meta)
425 if (midiMessage instanceof ShortMessage)
426 {
427 // Determine which command is being issued
428 ShortMessage shortMessage = (ShortMessage)midiMessage;
429 int messageCommand = shortMessage.getCommand();
430
431 if (messageCommand == ShortMessage.NOTE_ON)
432 {
433 trackIsAudible = true;
434 break;
435 }
436 }
437 }
438
439 if (trackIsAudible)
440 {
441 trackDimensionMap.put(trackIdx, siaDatapointDimension);
442 siaDatapointDimension++;
443 }
444 }
445
446 return trackDimensionMap;
447 }
448
449 private static void printStmts(StmtIterator iter) {
450 Statement statement;
451
452 while (iter.hasNext()) {
453 statement = iter.nextStatement();
454 System.out.println(" | <" + statement.getSubject() + "> | <"
455 + statement.getPredicate() + "> | <"
456 + statement.getObject() + "> | ");
457 }
458
459 // And an empty line to make it pretty
460 System.out.println();
461 }
462 }