changeset 37:cb7f70e10a0c

New
author stevenh
date Tue, 26 Feb 2013 13:30:46 +0000
parents 029968a13f8b
children 1f09e7ded739
files src/org/qmul/eecs/c4dm/sia/midi/MidiParser.java
diffstat 1 files changed, 462 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/org/qmul/eecs/c4dm/sia/midi/MidiParser.java	Tue Feb 26 13:30:46 2013 +0000
@@ -0,0 +1,462 @@
+package org.qmul.eecs.c4dm.sia.midi;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Vector;
+
+import javax.sound.midi.InvalidMidiDataException;
+import javax.sound.midi.MetaMessage;
+import javax.sound.midi.MidiEvent;
+import javax.sound.midi.MidiMessage;
+import javax.sound.midi.MidiSystem;
+import javax.sound.midi.Sequence;
+import javax.sound.midi.ShortMessage;
+import javax.sound.midi.SysexMessage;
+import javax.sound.midi.Track;
+
+import org.qmul.eecs.c4dm.sia.model.Datapoint;
+import org.qmul.eecs.c4dm.sia.model.DimensionValue;
+import org.qmul.eecs.c4dm.sia.rdf.Namespaces;
+
+import com.hp.hpl.jena.ontology.OntClass;
+import com.hp.hpl.jena.ontology.OntModel;
+import com.hp.hpl.jena.ontology.OntResource;
+import com.hp.hpl.jena.rdf.model.AnonId;
+import com.hp.hpl.jena.rdf.model.ModelFactory;
+import com.hp.hpl.jena.rdf.model.NodeIterator;
+import com.hp.hpl.jena.rdf.model.Property;
+import com.hp.hpl.jena.rdf.model.RDFNode;
+import com.hp.hpl.jena.rdf.model.Resource;
+import com.hp.hpl.jena.rdf.model.Statement;
+import com.hp.hpl.jena.rdf.model.StmtIterator;
+import com.hp.hpl.jena.util.iterator.ExtendedIterator;
+import com.hp.hpl.jena.vocabulary.RDF;
+import com.sun.media.sound.MidiUtils;
+
+public class MidiParser {
+	
+	public static final int TIME_DIMENSION = 1;
+	public static final int PITCH_DIMENSION = 2;
+	public static final int CHANNEL_DIMENSION = 3;
+
+	// The ontology loaded as dataset
+	private static final String ontology = "file:src/rdf/siaDatapointOntology.n3";
+
+	// The final output file
+	private static final String finalModelFileName = "src/rdf/midiModel";
+
+	public static void main(String[] args)
+	{
+		// First create a Jena ontology model
+		OntModel ontModel = ModelFactory
+				.createOntologyModel(); // OntModelSpec.OWL_MEM
+
+		// Then read the data from the file into the ontology model
+		ontModel.read(ontology, "N3");
+		
+		// Set the value of SIA_NS_URI as the namespace for 'sia' found in
+		// the ontology. This must be done before we use any of the sia.model classes.
+		Namespaces.SIA_NS_URI = ontModel.getNsPrefixURI("sia");
+		OntClass datapointClass = ontModel.getOntClass(Datapoint.RESOURCE_URI);
+		Resource datapointResource = ontModel.getOntResource(datapointClass);
+		Property siaDimValProperty = ontModel.createProperty(DimensionValue.PROPERTY_URI);
+		Property siaDimensionProperty = ontModel.createProperty(DimensionValue.DIMENSION_URI);
+		Property siaValueProperty = ontModel.createProperty(DimensionValue.VALUE_URI);
+
+		String midiFileName = "/Volumes/USB DISK/portable/ewerts/Cantata_16_no_5-mids/score.mid";
+
+		File midiFile1 = new File(midiFileName);
+	
+		Sequence sequence = null;
+		try {
+			sequence = MidiSystem.getSequence(midiFile1);
+		} catch (InvalidMidiDataException e) {
+			e.printStackTrace();
+			System.exit(1);
+		} catch (IOException e) {
+			e.printStackTrace();
+			System.exit(1);
+		}
+		
+		MidiUtils.TempoCache tempoCache = new MidiUtils.TempoCache(sequence);
+
+		Track[] tracks = sequence.getTracks();
+		int numTracks = tracks.length;
+		float divisionType = sequence.getDivisionType();
+	    List<Datapoint> datapoints = new ArrayList<Datapoint>();
+		
+		HashMap<Integer, Integer> trackDimensionMap = getTrackToDimensionIndexMap(sequence);
+		
+		if (trackDimensionMap.isEmpty())
+		{
+			try {
+				throw new Exception("Couldn't find any track to dimension mappings");
+			} catch (Exception e) {
+				e.printStackTrace();
+				System.exit(1);
+			}
+		}
+		
+		long maxTick = 0;
+				
+		for (int trackIdx = 0; trackIdx < numTracks; trackIdx++)
+		{
+			System.out.println("Track " + trackIdx + ":");
+			int numEvents = tracks[trackIdx].size();
+			for (int eventIdx = 0; eventIdx < numEvents; eventIdx++)
+			{
+				MidiEvent event = tracks[trackIdx].get(eventIdx);
+				long tick = event.getTick();
+				MidiMessage midiMessage = event.getMessage();
+				
+				int midiMessageLength = midiMessage.getLength();
+				byte[] messageBytes = midiMessage.getMessage();
+				int status = midiMessage.getStatus();
+				System.out.println("tick = " + tick + " midiMessageLength = " + midiMessageLength + " status byte = " + status);
+
+				int s = status & 0x80;
+				if (s == 0x80)
+				{
+					System.out.println("STATUS MESSAGE (s = " + s + ")");
+				}
+				else
+				{
+					System.out.println("not a status message (s = " + s + ")");
+				}
+					
+				// Determine the type of this message (short, sysex or meta)
+				if (midiMessage instanceof ShortMessage)
+				{
+					System.out.print("ShortMessage ");
+										
+					// Determine which command is being issued
+					ShortMessage shortMessage = (ShortMessage)midiMessage;
+					int messageCommand = shortMessage.getCommand();
+					int channel = shortMessage.getChannel();
+					int data1 = shortMessage.getData1();
+					int data2 = shortMessage.getData2();
+
+					if (messageCommand == ShortMessage.ACTIVE_SENSING)
+					{
+						System.out.print("ignoring ACTIVE_SENSING");
+					}
+					else if (messageCommand == ShortMessage.CHANNEL_PRESSURE)
+					{
+						System.out.print("ignoring CHANNEL_PRESSURE");
+					}
+					else if (messageCommand == ShortMessage.CONTINUE)
+					{
+						System.out.print("ignoring CONTINUE");
+					}
+					else if (messageCommand == ShortMessage.CONTROL_CHANGE)
+					{
+						System.out.print("ignoring CONTROL_CHANGE");
+					}
+					else if (messageCommand == ShortMessage.END_OF_EXCLUSIVE)
+					{
+						System.out.print("ignoring END_OF_EXCLUSIVE");
+					}
+					else if (messageCommand == ShortMessage.MIDI_TIME_CODE)
+					{
+						System.out.print("ignoring MIDI_TIME_CODE");
+					}
+					else if (messageCommand == ShortMessage.NOTE_OFF)
+					{
+						System.out.println("NOTE_OFF");
+						long microsecs = MidiUtils.tick2microsecond(sequence, tick, tempoCache);
+						System.out.println(" microsecs = " + microsecs);
+					}
+					else if (messageCommand == ShortMessage.NOTE_ON)
+					{
+						if (tick > maxTick)
+						{
+							maxTick = tick;
+						}
+						System.out.println("NOTE_ON");
+						long microsecs = MidiUtils.tick2microsecond(sequence, tick, tempoCache);
+						System.out.println(" microsecs = " + microsecs);
+						
+						DimensionValue timeDimVal = new DimensionValue();
+						timeDimVal.setDimension(TIME_DIMENSION);
+						timeDimVal.setValue(tick);
+						
+						DimensionValue dimVal = new DimensionValue();
+						dimVal.setDimension(trackDimensionMap.get(trackIdx));
+						dimVal.setValue(data1);
+						
+						Datapoint datapoint = new Datapoint();
+						Vector<DimensionValue> dimVals = new Vector<DimensionValue>();
+						dimVals.add(timeDimVal);
+						dimVals.add(dimVal);
+						
+						datapoint.setDimensionValues(dimVals);
+						datapoints.add(datapoint);
+						
+						// RDF
+						Resource datapointBnode = ontModel.createResource(AnonId.create());
+						ontModel.add(datapointBnode, RDF.type, datapointResource);
+
+						Resource timeDimValBnode = ontModel.createResource(AnonId.create());
+						Resource pitchDimValBnode = ontModel.createResource(AnonId.create());
+
+						ontModel.add(datapointBnode, siaDimValProperty, timeDimValBnode);
+						ontModel.addLiteral(timeDimValBnode, siaDimensionProperty, TIME_DIMENSION);
+						ontModel.addLiteral(timeDimValBnode, siaValueProperty, tick);
+
+						ontModel.add(datapointBnode, siaDimValProperty, pitchDimValBnode);
+						ontModel.addLiteral(pitchDimValBnode, siaDimensionProperty, trackDimensionMap.get(trackIdx).intValue());
+						ontModel.addLiteral(pitchDimValBnode, siaValueProperty, data1);
+					}
+					else if (messageCommand == ShortMessage.PITCH_BEND)
+					{
+						System.out.print("ignoring PITCH_BEND");
+					}
+					else if (messageCommand == ShortMessage.POLY_PRESSURE)
+					{
+						System.out.print("ignoring POLY_PRESSURE");
+					}
+					else if (messageCommand == ShortMessage.PROGRAM_CHANGE)
+					{
+						System.out.print("ignoring PROGRAM_CHANGE");
+					}
+					else if (messageCommand == ShortMessage.SONG_POSITION_POINTER)
+					{
+						System.out.print("ignoring SONG_POSITION_POINTER");
+					}
+					else
+					{
+						System.out.print("unrecognised midi message command (" + messageCommand + ")");
+					}
+					System.out.print(", channel " + channel + ", data1 = [" + data1 + "], data2 = [" + data2 + "]");
+					System.out.println();
+				}
+				else if (midiMessage instanceof MetaMessage)
+				{
+					System.out.println("MetaMessage");
+					
+					MetaMessage metaMessage = (MetaMessage)midiMessage;
+					byte[] metaMessageData = metaMessage.getData();
+					int metaMessageLength = metaMessage.getLength();
+					int metaMessageType = metaMessage.getType();
+					System.out.println("metaMessageType = " + metaMessageType + ", metaMessageLength = " + metaMessageLength);
+					
+					// Determine message type
+					if (metaMessageType == 81)
+					{
+						if (divisionType == Sequence.PPQ)
+						{
+							// Do nothing - we've dealt with PPQ tempo data elsewhere
+						}
+						else
+						{
+							try {
+								throw new Exception("Not yet implemented SMPTE tempo metadata");
+							} catch (Exception e) {
+								e.printStackTrace();
+								System.exit(1);
+							}
+						}
+					}
+
+					for (int dataIdx = 0; dataIdx < metaMessageData.length; dataIdx++)
+					{
+						System.out.println("\tmetaMessageData[" + dataIdx + "] = " + (metaMessageType == 81 ? metaMessageData[dataIdx] : (char)metaMessageData[dataIdx]));
+					}
+
+				}
+				else if (midiMessage instanceof SysexMessage)
+				{
+					// We can safely ignore these messages
+					System.out.println("ignoring SysexMessage");
+				}
+				else
+				{
+					System.out.println("Unknown MidiMessage type (" + midiMessage.getClass().toString() + ")");
+				}
+				
+				for (int byteIdx = 0; byteIdx < midiMessageLength; byteIdx++)
+				{
+					byte messageByte = messageBytes[byteIdx];
+					System.out.println("\tbyte[" + byteIdx + "] = " + messageByte);
+				}
+			}
+		}
+
+		// Data integrity start - set any 'missing' dimensions to zero value
+		// Note the highest dimension for later use
+		Collection<Integer> values = trackDimensionMap.values();
+		Iterator<Integer> valuesIter = values.iterator();
+		int maxDimension = 0;
+		while (valuesIter.hasNext())
+		{
+			Integer value = valuesIter.next();
+			if (value > maxDimension)
+			{
+				maxDimension = value;
+			}
+		}
+
+		// Iterate over all datapoints
+		ExtendedIterator<? extends OntResource> datapointIter = datapointClass.listInstances();
+		OntResource datapointIndividual;
+		OntModel ontModelTemp = ModelFactory
+				.createOntologyModel(); // OntModelSpec.OWL_MEM
+
+		while (datapointIter.hasNext())
+		{
+			datapointIndividual = datapointIter.next();
+		
+			// Find all Dimension Values for this datapoint
+			NodeIterator dimValIter = datapointIndividual.listPropertyValues(siaDimValProperty);
+			
+			// Create a hashmap of dimensions for this datapoint
+			HashMap<Integer, Boolean> dimensions = new HashMap<>();
+			while (dimValIter.hasNext())
+			{
+				RDFNode dimVal = dimValIter.next();
+				NodeIterator dims = ontModel.listObjectsOfProperty(dimVal.asResource(), siaDimensionProperty);
+				while (dims.hasNext())
+				{
+					int dim = dims.next().asLiteral().getInt();
+					dimensions.put(dim, true);
+					System.out.println(dim);
+				}
+			}
+			
+			for (int i = 2; i <= maxDimension; i++)
+			{
+				if (!dimensions.containsKey(i))
+				{
+					Resource zeroValDimValBnode = ontModel.createResource(AnonId.create());
+
+					Resource dpResource = datapointIndividual.asResource();
+					ontModelTemp.add(dpResource, siaDimValProperty, zeroValDimValBnode);
+					ontModelTemp.addLiteral(zeroValDimValBnode, siaDimensionProperty, i);
+					ontModelTemp.addLiteral(zeroValDimValBnode, siaValueProperty, 0);					
+				}
+			}
+			
+		}
+		ontModel.add(ontModelTemp);
+		// Data integrity end
+
+		System.out.println("done");
+	    	    
+		// Print out what we've got now
+		System.out.println("------------------");
+		StmtIterator stmtIterator = ontModel.listStatements();
+		printStmts(stmtIterator);
+
+		// Write rdf to file
+		File outFileRdf = new File(finalModelFileName + ".rdf");
+		File outFileN3 = new File(finalModelFileName + ".n3");
+		FileOutputStream outFileOutputStreamRdf = null;
+		FileOutputStream outFileOutputStreamN3 = null;
+
+		// RDF/XML version
+		try {
+			outFileOutputStreamRdf = new FileOutputStream(outFileRdf);
+			ontModel.writeAll(outFileOutputStreamRdf, "RDF/XML", null);
+		} catch (FileNotFoundException e) {
+			System.out.println("Unable to write to file: "
+					+ outFileRdf.getAbsolutePath());
+			e.printStackTrace();
+			System.exit(1);
+		}
+
+		try {
+			outFileOutputStreamRdf.close();
+		} catch (IOException e1) {
+			e1.printStackTrace();
+			System.exit(1);
+		}
+
+		// N3 version
+		try {
+			outFileOutputStreamN3 = new FileOutputStream(outFileN3);
+			ontModel.writeAll(outFileOutputStreamN3, "N3", null);
+		} catch (FileNotFoundException e) {
+			System.out.println("Unable to write to file: "
+					+ outFileN3.getAbsolutePath());
+			e.printStackTrace();
+			System.exit(1);
+		}
+
+		try {
+			outFileOutputStreamN3.close();
+		} catch (IOException e1) {
+			e1.printStackTrace();
+			System.exit(1);
+		}
+
+		System.out.println("Model written to files: "
+				+ outFileRdf.getAbsolutePath() + " and " + outFileN3.getAbsolutePath());
+		
+		System.out.println("max tick: " + maxTick);
+		
+	}
+
+	private static HashMap<Integer, Integer> getTrackToDimensionIndexMap(Sequence sequence) {
+		
+		int numTracks = sequence.getTracks().length;
+		int siaDatapointDimension = 2; // Dimension 1 is reserved for time
+
+		HashMap<Integer, Integer> trackDimensionMap = new HashMap<Integer, Integer>();
+		Track[] tracks = sequence.getTracks();
+		
+		for (int trackIdx = 0; trackIdx < numTracks; trackIdx++)
+		{
+			boolean trackIsAudible = false;
+			int numEvents = tracks[trackIdx].size();
+
+			for (int eventIdx = 0; eventIdx < numEvents; eventIdx++)
+			{
+				MidiEvent event = tracks[trackIdx].get(eventIdx);
+				MidiMessage midiMessage = event.getMessage();
+				
+				// Determine the type of this message (short, sysex or meta)
+				if (midiMessage instanceof ShortMessage)
+				{
+					// Determine which command is being issued
+					ShortMessage shortMessage = (ShortMessage)midiMessage;
+					int messageCommand = shortMessage.getCommand();
+
+					if (messageCommand == ShortMessage.NOTE_ON)
+					{
+						trackIsAudible = true;
+						break;
+					}
+				}
+			}
+
+			if (trackIsAudible)
+			{
+				trackDimensionMap.put(trackIdx, siaDatapointDimension);
+				siaDatapointDimension++;
+			}
+		}
+
+		return trackDimensionMap;
+	}
+
+	private static void printStmts(StmtIterator iter) {
+		Statement statement;
+
+		while (iter.hasNext()) {
+			statement = iter.nextStatement();
+			System.out.println(" | <" + statement.getSubject() + "> | <"
+					+ statement.getPredicate() + "> | <"
+					+ statement.getObject() + "> | ");
+		}
+
+		// And an empty line to make it pretty
+		System.out.println();
+	}
+}