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