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