view host/host.java @ 31:d8ff8c5ad52a

Add host program, working like a minimal version of vamp-simple-host
author Chris Cannam
date Thu, 22 Nov 2012 14:31:23 +0000
parents
children c9515589be7d
line wrap: on
line source

import java.util.ArrayList;
import java.util.TreeMap;
import java.util.Map;
import java.util.List;
import java.lang.RuntimeException;

import org.vamp_plugins.PluginLoader;
import org.vamp_plugins.Plugin;
import org.vamp_plugins.ParameterDescriptor;
import org.vamp_plugins.OutputDescriptor;
import org.vamp_plugins.Feature;
import org.vamp_plugins.RealTime;

import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.UnsupportedAudioFileException;

import java.io.File;
import java.io.IOException;

public class host
{
    private static void printFeatures(RealTime frameTime, Integer output,
				      Map<Integer, ArrayList<Feature>> features)
    {
	if (!features.containsKey(output)) return;

	for (Feature f : features.get(output)) {
	    if (f.hasTimestamp) {
		System.out.print(f.timestamp);
	    } else {
		System.out.print(frameTime);
	    }
	    if (f.hasDuration) {
		System.out.print("," + f.duration);
	    }
	    System.out.print(":");
	    for (float v : f.values) {
		System.out.print(" " + v);
	    }
	    System.out.print(" " + f.label);
	    System.out.println("");
	}
    }

    private static void usage() {
	System.err.println("Usage: host pluginlibrary:plugin:output file.wav");
    }

    private static int readBlock(AudioFormat format, AudioInputStream stream,
				 float[][] buffers)
	throws java.io.IOException
    {
	// 16-bit LE signed PCM only
	int channels = format.getChannels();
	byte[] raw = new byte[buffers[0].length * channels * 2];
	int read = stream.read(raw);
	if (read < 0) return read;
	int frames = read / (channels * 2);
	for (int i = 0; i < frames; ++i) {
	    for (int c = 0; c < channels; ++c) {
		int ix = i * channels + c;
		int ival = (raw[ix*2] & 0xff) | (raw[ix*2 + 1] << 8);
		float fval = ival / 32768.0f;
		buffers[c][i] = fval;
	    }
	}
	return frames;
    }

    public static void main(String[] args)
    {
	if (args.length < 2) {
	    usage();
	    return;
	}

	PluginLoader loader = PluginLoader.getInstance();

	String key = args[0];
	String filename = args[1];

	String[] keyparts = key.split(":");
	if (keyparts.length < 3) {
	    usage();
	    return;
	}
	
	String pluginKey = keyparts[0] + ":" + keyparts[1];
	String outputKey = keyparts[2];

	try {
	    File f = new File(filename);
	    AudioInputStream stream = AudioSystem.getAudioInputStream(f);
	    AudioFormat format = stream.getFormat();

	    if (format.getSampleSizeInBits() != 16 ||
		format.getEncoding() != AudioFormat.Encoding.PCM_SIGNED ||
		format.isBigEndian()) {
		System.err.println("Sorry, only 16-bit signed little-endian PCM files supported");
		return;
	    }

	    float rate = format.getFrameRate();
	    int channels = format.getChannels();
	    int bytesPerFrame = format.getFrameSize();
	    int blockSize = 1024; // frames

	    Plugin p = loader.loadPlugin
		(pluginKey, rate, PluginLoader.AdapterFlags.ADAPT_ALL);

	    OutputDescriptor[] outputs = p.getOutputDescriptors();
	    int outputNumber = -1;
	    for (int i = 0; i < outputs.length; ++i) {
		if (outputs[i].identifier.equals(outputKey)) outputNumber = i;
	    }
	    if (outputNumber < 0) {
		System.err.println("Plugin lacks output id: " + outputKey);
		System.err.print("Outputs are:");
		for (int i = 0; i < outputs.length; ++i) {
		    System.err.print(" " + outputs[i].identifier);
		}
		System.err.println("");
		return;
	    }
	    
	    boolean b = p.initialise(channels, blockSize, blockSize);
	    if (!b) {
		System.err.println("Plugin initialise failed");
		return;
	    }
	    
	    float[][] buffers = new float[channels][blockSize];

	    boolean done = false;
	    boolean incomplete = false;
	    int block = 0;

	    while (!done) {

		for (int c = 0; c < channels; ++c) {
		    for (int i = 0; i < blockSize; ++i) {
			buffers[c][i] = 0.0f;
		    }
		}

		int read = readBlock(format, stream, buffers);

		if (read < 0) {
		    done = true;
		} else {

		    if (incomplete) {
			// An incomplete block is only OK if it's the
			// last one -- so if the previous block was
			// incomplete, we have trouble
			System.err.println("Audio file read incomplete! Short buffer detected at " + block * blockSize);
			return;
		    }
		    
		    incomplete = (read < buffers[0].length);

		    RealTime timestamp = RealTime.frame2RealTime
			(block * blockSize, (int)(rate + 0.5));

		    TreeMap<Integer, ArrayList<Feature>>
			features = p.process(buffers, timestamp);

		    printFeatures(timestamp, outputNumber, features);

		    timestamp.dispose();
		}
		
		++block;
	    }

	    TreeMap<Integer, ArrayList<Feature>>
		features = p.getRemainingFeatures();

	    RealTime timestamp = RealTime.frame2RealTime
		(block * blockSize, (int)(rate + 0.5));
	    printFeatures(timestamp, outputNumber, features);
	    timestamp.dispose();

	    p.dispose();

	} catch (java.io.IOException e) {
	    System.err.println("Failed to read audio file: " + e.getMessage());

	} catch (javax.sound.sampled.UnsupportedAudioFileException e) {
	    System.err.println("Unsupported audio file format: " + e.getMessage());

	} catch (PluginLoader.LoadFailedException e) {
	    System.err.println("Plugin load failed (unknown plugin?): key is " +
			       key);
	}
    }
}