view 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
line wrap: on
line source
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();
	}
}