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: }