Mercurial > hg > semantic-sia
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 } |