diff test/perf-test.js @ 117:2e8aacb7f883

Move some things around (have not yet updated builds)
author Chris Cannam <c.cannam@qmul.ac.uk>
date Tue, 08 Nov 2016 12:02:57 +0000
parents
children a91b4defa581
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/perf-test.js	Tue Nov 08 12:02:57 2016 +0000
@@ -0,0 +1,222 @@
+"use strict";
+
+var exampleModule = VampExamplePluginsModule();
+
+// It is possible to declare both parameters and return values as
+// "string", in which case Emscripten will take care of
+// conversions. But it's not clear how one would manage memory for
+// newly-constructed returned C strings -- the returned pointer from
+// piperRequestJson would appear (?) to be thrown away by the
+// Emscripten string converter if we declare it as returning a string,
+// so we have no opportunity to pass it to piperFreeJson, which
+// suggests this would leak memory if the string isn't static. Not
+// wholly sure though. Anyway, passing and returning pointers (as
+// numbers) means we can manage the Emscripten heap memory however we
+// want in our request wrapper function below.
+
+var piperRequestJson = exampleModule.cwrap(
+    'piperRequestJson', 'number', ['number']
+);
+
+var piperProcessRaw = exampleModule.cwrap(
+    "piperProcessRaw", "number", ["number", "number", "number", "number"]
+);
+
+var piperFreeJson = exampleModule.cwrap(
+    'piperFreeJson', 'void', ['number']
+);
+
+function note(blah) {
+    document.getElementById("test-result").innerHTML += blah + "<br>";
+}
+
+function comment(blah) {
+    note("<br><i>" + blah + "</i>");
+}
+
+function processRaw(request) {
+    
+    const nChannels = request.processInput.inputBuffers.length;
+    const nFrames = request.processInput.inputBuffers[0].length;
+
+    const buffersPtr = exampleModule._malloc(nChannels * 4);
+    const buffers = new Uint32Array(
+        exampleModule.HEAPU8.buffer, buffersPtr, nChannels);
+
+    for (let i = 0; i < nChannels; ++i) {
+        const framesPtr = exampleModule._malloc(nFrames * 4);
+        const frames = new Float32Array(
+            exampleModule.HEAPU8.buffer, framesPtr, nFrames);
+        frames.set(request.processInput.inputBuffers[i]);
+        buffers[i] = framesPtr;
+    }
+    
+    const responseJson = piperProcessRaw(
+        request.handle,
+        buffersPtr,
+        request.processInput.timestamp.s,
+        request.processInput.timestamp.n);
+    
+    for (let i = 0; i < nChannels; ++i) {
+        exampleModule._free(buffers[i]);
+    }
+    exampleModule._free(buffersPtr);
+
+    const responseJstr = exampleModule.Pointer_stringify(responseJson);
+    const response = JSON.parse(responseJstr);
+    
+    piperFreeJson(responseJson);
+    
+    return response;
+}
+
+function makeTimestamp(seconds) {
+    if (seconds >= 0.0) {
+        return {
+            s: Math.floor(seconds),
+            n: Math.floor((seconds - Math.floor(seconds)) * 1e9 + 0.5)
+        };
+    } else {
+        const { s, n } = makeTimestamp(-seconds);
+        return { s: -s, n: -n };
+    }
+}
+
+function frame2timestamp(frame, rate) {
+    return makeTimestamp(frame / rate);
+}
+
+function request(jsonStr) {
+    note("Request JSON = " + jsonStr);
+    var m = exampleModule;
+    // Inspection reveals that intArrayFromString converts the string
+    // from utf16 to utf8, which is what we want (though the docs
+    // don't mention this). Note the *Cstr values are Emscripten heap
+    // pointers
+    var inCstr = m.allocate(m.intArrayFromString(jsonStr), 'i8', m.ALLOC_NORMAL);
+    var outCstr = piperRequestJson(inCstr);
+    m._free(inCstr);
+    var result = m.Pointer_stringify(outCstr);
+    piperFreeJson(outCstr);
+    note("Returned JSON = " + result);
+    return result;
+}
+
+function myFromBase64(b64) {
+    while (b64.length % 4 > 0) { b64 += "="; }
+    let conv = new Float32Array(toByteArray(b64).buffer);
+    return conv;
+}
+
+function convertWireFeature(wfeature) {
+    let out = {};
+    if (wfeature.timestamp != null) {
+        out.timestamp = wfeature.timestamp;
+    }
+    if (wfeature.duration != null) {
+        out.duration = wfeature.duration;
+    }
+    if (wfeature.label != null) {
+        out.label = wfeature.label;
+    }
+    const vv = wfeature.featureValues;
+    if (vv != null) {
+        if (typeof vv === "string") {
+            out.featureValues = myFromBase64(vv);
+        } else {
+            out.featureValues = new Float32Array(vv);
+        }
+    }
+    return out;
+}
+
+function convertWireFeatureList(wfeatures) {
+    return wfeatures.map(convertWireFeature);
+}
+
+function responseToFeatureSet(response) {
+    const features = new Map();
+    const processResponse = response.result;
+    const wireFeatures = processResponse.features;
+    Object.keys(wireFeatures).forEach(key => {
+        return features.set(key, convertWireFeatureList(wireFeatures[key]));
+    });
+    return features;
+}
+
+function test() {
+
+    const rate = 44100;
+    
+    comment("Loading zero crossings plugin...");
+    let result = request('{"method":"load","params": {"key":"vamp-example-plugins:zerocrossing","inputSampleRate":' + rate + ',"adapterFlags":["AdaptAllSafe"]}}');
+
+    const blockSize = 1024;
+
+    result = request('{"method":"configure","params":{"handle":1,"configuration":{"blockSize": ' + blockSize + ', "channelCount": 1, "stepSize": ' + blockSize + '}}}');
+
+    const nblocks = 1000;
+
+    const makeBlock = (n => { 
+        return {
+            timestamp : frame2timestamp(n * blockSize, rate),
+            inputBuffers : [
+                new Float32Array(Array.from(Array(blockSize).keys(),
+                                            n => n / blockSize))
+            ],
+        }
+    });
+    
+    const blocks = Array.from(Array(nblocks).keys(), makeBlock);
+    
+    comment("Now processing " + nblocks + " blocks of 1024 samples each...");
+
+    let total = 0;
+    
+    let start = (new Date()).getTime();
+    comment("Start at " + start);
+    
+    for (let i = 0; i < nblocks; ++i) {
+	result = processRaw({
+	    "handle": 1,
+	    "processInput": blocks[i]
+	});
+        let features = responseToFeatureSet(result);
+        let count = features.get("counts")[0].featureValues[0];
+        total += count;
+    }
+
+    let finish = (new Date()).getTime();
+    comment("Finish at " + finish + " for a time of " + (finish - start) + " ms");
+
+    comment("Total = " + total);
+
+    comment("Again...");
+
+    total = 0;
+    
+    start = (new Date()).getTime();
+    comment("Start at " + start);
+    
+    for (let i = 0; i < nblocks; ++i) {
+	result = processRaw({
+	    "handle": 1,
+	    "processInput": blocks[i]
+	});
+        let features = responseToFeatureSet(result);
+        let count = features.get("counts")[0].featureValues[0];
+        total += count;
+    }
+
+    finish = (new Date()).getTime();
+    comment("Finish at " + finish + " for a time of " + (finish - start) + " ms");
+    
+    comment("Total = " + total);
+    
+    comment("Cleaning up the plugin and getting any remaining features...");
+    result = request('{"method":"finish","params":{"handle":1}}');
+}
+
+window.onload = function() {
+    test();
+}