view src/org/qmul/eecs/c4dm/sia/midi/MidiParser.java @ 69:94e6592eb106

modified to take into account that sia:Datapoints now have sia:vector properties which then have sia:dimVals, rather than sia:Datapoints having their own sia:dimVal properties
author stevenh
date Fri, 02 Aug 2013 11:13:10 +0100
parents fa9030705e93
children 39106212a3c6
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.SiaMain;
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.query.Dataset;
import com.hp.hpl.jena.query.ReadWrite;
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.tdb.TDBFactory;
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";

	// The input midi file
	private static final String midiFileName = "/Volumes/USB_DISK/portable/ewerts/Cantata_16_no_5-mids/score.mid";

	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");
		
		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);

		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");
						double microsecs = MidiUtils.tick2microsecond(sequence, tick, tempoCache);
						double secs = microsecs/1000000;
						System.out.println(" microsecs = " + secs);
						
						DimensionValue timeDimVal = new DimensionValue();
						timeDimVal.setDimension(TIME_DIMENSION);
						timeDimVal.setValue(secs);
						
						DimensionValue pitchDimVal = new DimensionValue();
						pitchDimVal.setDimension(PITCH_DIMENSION);
						pitchDimVal.setValue(data1);
						
						DimensionValue channelDimVal = new DimensionValue();
						channelDimVal.setDimension(CHANNEL_DIMENSION);
						channelDimVal.setValue(trackDimensionMap.get(trackIdx));

						Datapoint datapoint = new Datapoint();
						Vector<DimensionValue> dimVals = new Vector<DimensionValue>();
						dimVals.add(timeDimVal);
						dimVals.add(pitchDimVal);
						dimVals.add(channelDimVal);
						
						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());
						Resource channelDimValBnode = ontModel.createResource(AnonId.create());

						ontModel.add(datapointBnode, siaDimValProperty, timeDimValBnode);
						ontModel.addLiteral(timeDimValBnode, siaDimensionProperty, TIME_DIMENSION);
						ontModel.addLiteral(timeDimValBnode, siaValueProperty, secs);

						ontModel.add(datapointBnode, siaDimValProperty, pitchDimValBnode);
						ontModel.addLiteral(pitchDimValBnode, siaDimensionProperty, PITCH_DIMENSION);
						ontModel.addLiteral(pitchDimValBnode, siaValueProperty, data1);

						ontModel.add(datapointBnode, siaDimValProperty, channelDimValBnode);
						ontModel.addLiteral(channelDimValBnode, siaDimensionProperty, CHANNEL_DIMENSION);
						ontModel.addLiteral(channelDimValBnode, siaValueProperty, trackDimensionMap.get(trackIdx).intValue());

					}
					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);
				}
			}
		}

		System.out.println("done");
	    	    
		// Print out what we've got now
		System.out.println("------------------");
		StmtIterator stmtIterator = ontModel.listStatements();
		printStmts(stmtIterator);

		// TODO 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());
		
		// Obtain a dataset context
		Dataset dataset = TDBFactory.assembleDataset(SiaMain.assemblerFile);
        dataset.begin(ReadWrite.WRITE) ;
        try {
        	dataset.replaceNamedModel(SiaMain.graph, ontModel);
        	dataset.commit();
        	System.out.println("dataset.commit() done");
        } finally {
        	dataset.end();
        	System.out.println("dataset.end() done");
        }
		dataset.close();
    	System.out.println("dataset.close() done");

    	System.out.println("max tick: " + maxTick);
		System.out.println("Number of Datapoints (n) = " + datapoints.size());
		
	}

	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();
	}
}