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