c@117
|
1 "use strict";
|
c@117
|
2
|
c@125
|
3 var extractorName = "VampExamplePlugins";
|
c@125
|
4 var pluginKey = "vamp-example-plugins:zerocrossing";
|
cannam@175
|
5 var extractor = require("../examples/vamp-example-plugins/" + extractorName + "_w.umd");
|
c@117
|
6 var base64 = require("./base64");
|
c@117
|
7
|
cannam@175
|
8 extractor().then(function (extractorModule) {
|
c@117
|
9
|
cannam@175
|
10 // It is possible to declare both parameters and return values as
|
cannam@175
|
11 // "string", in which case Emscripten will take care of
|
cannam@175
|
12 // conversions. But it's not clear how one would manage memory for
|
cannam@175
|
13 // newly-constructed returned C strings -- the returned pointer from
|
cannam@175
|
14 // piperRequestJson would appear (?) to be thrown away by the
|
cannam@175
|
15 // Emscripten string converter if we declare it as returning a string,
|
cannam@175
|
16 // so we have no opportunity to pass it to piperFreeJson, which
|
cannam@175
|
17 // suggests this would leak memory if the string isn't static. Not
|
cannam@175
|
18 // wholly sure though. Anyway, passing and returning pointers (as
|
cannam@175
|
19 // numbers) means we can manage the Emscripten heap memory however we
|
cannam@175
|
20 // want in our request wrapper function below.
|
c@117
|
21
|
cannam@175
|
22 var piperRequestJson = extractorModule.cwrap(
|
cannam@175
|
23 'piperRequestJson', 'number', ['number']
|
cannam@175
|
24 );
|
c@117
|
25
|
cannam@175
|
26 var piperProcessRaw = extractorModule.cwrap(
|
cannam@175
|
27 "piperProcessRaw", "number", ["number", "number", "number", "number"]
|
cannam@175
|
28 );
|
c@117
|
29
|
cannam@175
|
30 var piperFreeJson = extractorModule.cwrap(
|
cannam@175
|
31 'piperFreeJson', 'void', ['number']
|
cannam@175
|
32 );
|
c@117
|
33
|
cannam@175
|
34 function note(blah) {
|
cannam@175
|
35 console.log(blah);
|
cannam@175
|
36 }
|
c@117
|
37
|
cannam@175
|
38 function comment(blah) {
|
cannam@175
|
39 console.log(blah);
|
cannam@175
|
40 }
|
c@117
|
41
|
cannam@175
|
42 function processRaw(request) {
|
cannam@175
|
43
|
cannam@175
|
44 const nChannels = request.processInput.inputBuffers.length;
|
cannam@175
|
45 const nFrames = request.processInput.inputBuffers[0].length;
|
c@117
|
46
|
cannam@175
|
47 const buffersPtr = extractorModule._malloc(nChannels * 4);
|
cannam@175
|
48 const buffers = new Uint32Array(
|
cannam@175
|
49 extractorModule.HEAPU8.buffer, buffersPtr, nChannels);
|
cannam@175
|
50
|
cannam@175
|
51 for (let i = 0; i < nChannels; ++i) {
|
cannam@175
|
52 const framesPtr = extractorModule._malloc(nFrames * 4);
|
cannam@175
|
53 const frames = new Float32Array(
|
cannam@175
|
54 extractorModule.HEAPU8.buffer, framesPtr, nFrames);
|
cannam@175
|
55 frames.set(request.processInput.inputBuffers[i]);
|
cannam@175
|
56 buffers[i] = framesPtr;
|
cannam@175
|
57 }
|
cannam@175
|
58
|
cannam@175
|
59 const responseJson = piperProcessRaw(
|
cannam@175
|
60 request.handle,
|
cannam@175
|
61 buffersPtr,
|
cannam@175
|
62 request.processInput.timestamp.s,
|
cannam@175
|
63 request.processInput.timestamp.n);
|
cannam@175
|
64
|
cannam@175
|
65 for (let i = 0; i < nChannels; ++i) {
|
cannam@175
|
66 extractorModule._free(buffers[i]);
|
cannam@175
|
67 }
|
cannam@175
|
68 extractorModule._free(buffersPtr);
|
cannam@175
|
69
|
cannam@176
|
70 const responseJstr = extractorModule.UTF8ToString(responseJson);
|
cannam@175
|
71 const response = JSON.parse(responseJstr);
|
cannam@175
|
72
|
cannam@175
|
73 piperFreeJson(responseJson);
|
cannam@175
|
74
|
cannam@175
|
75 return response;
|
c@117
|
76 }
|
c@117
|
77
|
cannam@175
|
78 function makeTimestamp(seconds) {
|
cannam@175
|
79 if (seconds >= 0.0) {
|
cannam@175
|
80 return {
|
cannam@175
|
81 s: Math.floor(seconds),
|
cannam@175
|
82 n: Math.floor((seconds - Math.floor(seconds)) * 1e9 + 0.5)
|
cannam@175
|
83 };
|
c@117
|
84 } else {
|
cannam@175
|
85 const { s, n } = makeTimestamp(-seconds);
|
cannam@175
|
86 return { s: -s, n: -n };
|
c@117
|
87 }
|
c@117
|
88 }
|
c@117
|
89
|
cannam@175
|
90 function frame2timestamp(frame, rate) {
|
cannam@175
|
91 return makeTimestamp(frame / rate);
|
c@117
|
92 }
|
c@117
|
93
|
cannam@175
|
94 function request(jsonStr) {
|
cannam@175
|
95 note("Request JSON = " + jsonStr);
|
cannam@175
|
96 var m = extractorModule;
|
cannam@175
|
97 // Inspection reveals that intArrayFromString converts the string
|
cannam@175
|
98 // from utf16 to utf8, which is what we want (though the docs
|
cannam@175
|
99 // don't mention this). Note the *Cstr values are Emscripten heap
|
cannam@175
|
100 // pointers
|
cannam@175
|
101 var inCstr = m.allocate(m.intArrayFromString(jsonStr), 'i8', m.ALLOC_NORMAL);
|
cannam@175
|
102 var outCstr = piperRequestJson(inCstr);
|
cannam@175
|
103 m._free(inCstr);
|
cannam@176
|
104 var result = m.UTF8ToString(outCstr);
|
cannam@175
|
105 piperFreeJson(outCstr);
|
cannam@175
|
106 note("Returned JSON = " + result);
|
cannam@175
|
107 return result;
|
c@117
|
108 }
|
c@117
|
109
|
cannam@175
|
110 function myFromBase64(b64) {
|
cannam@175
|
111 while (b64.length % 4 > 0) { b64 += "="; }
|
cannam@175
|
112 let conv = new Float32Array(base64.toByteArray(b64).buffer);
|
cannam@175
|
113 return conv;
|
cannam@175
|
114 }
|
c@117
|
115
|
cannam@175
|
116 function convertWireFeature(wfeature) {
|
cannam@175
|
117 let out = {};
|
cannam@175
|
118 if (wfeature.timestamp != null) {
|
cannam@175
|
119 out.timestamp = wfeature.timestamp;
|
cannam@175
|
120 }
|
cannam@175
|
121 if (wfeature.duration != null) {
|
cannam@175
|
122 out.duration = wfeature.duration;
|
cannam@175
|
123 }
|
cannam@175
|
124 if (wfeature.label != null) {
|
cannam@175
|
125 out.label = wfeature.label;
|
cannam@175
|
126 }
|
cannam@175
|
127 const vv = wfeature.featureValues;
|
cannam@175
|
128 if (vv != null) {
|
cannam@175
|
129 if (typeof vv === "string") {
|
cannam@175
|
130 out.featureValues = myFromBase64(vv);
|
cannam@175
|
131 } else {
|
cannam@175
|
132 out.featureValues = new Float32Array(vv);
|
cannam@175
|
133 }
|
cannam@175
|
134 }
|
cannam@175
|
135 return out;
|
cannam@175
|
136 }
|
c@117
|
137
|
cannam@175
|
138 function convertWireFeatureList(wfeatures) {
|
cannam@175
|
139 return wfeatures.map(convertWireFeature);
|
cannam@175
|
140 }
|
cannam@175
|
141
|
cannam@175
|
142 function responseToFeatureSet(response) {
|
cannam@175
|
143 const features = new Map();
|
cannam@175
|
144 const processResponse = response.result;
|
cannam@175
|
145 const wireFeatures = processResponse.features;
|
cannam@175
|
146 Object.keys(wireFeatures).forEach(key => {
|
cannam@175
|
147 return features.set(key, convertWireFeatureList(wireFeatures[key]));
|
cannam@175
|
148 });
|
cannam@175
|
149 return features;
|
cannam@175
|
150 }
|
cannam@175
|
151
|
cannam@175
|
152 function test(extractorModule) {
|
cannam@175
|
153
|
cannam@175
|
154 const rate = 44100;
|
cannam@175
|
155
|
cannam@175
|
156 comment("Loading zero crossings plugin...");
|
cannam@175
|
157 let result = request('{"method":"load","params": {"key":"' + pluginKey + '","inputSampleRate":' + rate + ',"adapterFlags":["AdaptAllSafe"]}}');
|
cannam@175
|
158
|
cannam@175
|
159 const blockSize = 1024;
|
cannam@175
|
160
|
cannam@175
|
161 result = request('{"method":"configure","params":{"handle":1,"configuration":{"framing": { "blockSize": ' + blockSize + ', "stepSize": ' + blockSize + '}, "channelCount": 1 }}}');
|
cannam@175
|
162
|
cannam@175
|
163 const nblocks = 1000;
|
cannam@175
|
164
|
cannam@175
|
165 const makeBlock = (n => {
|
cannam@175
|
166 return {
|
cannam@175
|
167 timestamp : frame2timestamp(n * blockSize, rate),
|
cannam@175
|
168 inputBuffers : [
|
cannam@175
|
169 new Float32Array(Array.from(Array(blockSize).keys(),
|
cannam@175
|
170 n => n / blockSize))
|
cannam@175
|
171 ],
|
cannam@175
|
172 }
|
cannam@175
|
173 });
|
cannam@175
|
174
|
cannam@175
|
175 const blocks = Array.from(Array(nblocks).keys(), makeBlock);
|
cannam@175
|
176
|
cannam@175
|
177 comment("Now processing " + nblocks + " blocks of 1024 samples each...");
|
cannam@175
|
178
|
cannam@175
|
179 let total = 0;
|
cannam@175
|
180
|
cannam@175
|
181 let start = (new Date()).getTime();
|
cannam@175
|
182 comment("Start at " + start);
|
cannam@175
|
183
|
cannam@175
|
184 for (let i = 0; i < nblocks; ++i) {
|
cannam@175
|
185 result = processRaw({
|
cannam@175
|
186 "handle": 1,
|
cannam@175
|
187 "processInput": blocks[i]
|
cannam@175
|
188 });
|
cannam@175
|
189 let features = responseToFeatureSet(result);
|
cannam@175
|
190 let count = features.get("counts")[0].featureValues[0];
|
cannam@175
|
191 total += count;
|
cannam@175
|
192 }
|
cannam@175
|
193
|
cannam@175
|
194 let finish = (new Date()).getTime();
|
cannam@175
|
195 comment("Finish at " + finish + " for a time of " + (finish - start) + " ms");
|
cannam@175
|
196
|
cannam@175
|
197 comment("Total = " + total);
|
cannam@175
|
198
|
cannam@175
|
199 comment("Again...");
|
cannam@175
|
200
|
cannam@175
|
201 total = 0;
|
cannam@175
|
202
|
cannam@175
|
203 start = (new Date()).getTime();
|
cannam@175
|
204 comment("Start at " + start);
|
cannam@175
|
205
|
cannam@175
|
206 for (let i = 0; i < nblocks; ++i) {
|
cannam@175
|
207 result = processRaw({
|
cannam@175
|
208 "handle": 1,
|
cannam@175
|
209 "processInput": blocks[i]
|
cannam@175
|
210 });
|
cannam@175
|
211 let features = responseToFeatureSet(result);
|
cannam@175
|
212 let count = features.get("counts")[0].featureValues[0];
|
cannam@175
|
213 total += count;
|
cannam@175
|
214 }
|
cannam@175
|
215
|
cannam@175
|
216 finish = (new Date()).getTime();
|
cannam@175
|
217 comment("Finish at " + finish + " for a time of " + (finish - start) + " ms");
|
cannam@175
|
218
|
cannam@175
|
219 comment("Total = " + total);
|
cannam@175
|
220
|
cannam@175
|
221 comment("Cleaning up the plugin and getting any remaining features...");
|
cannam@175
|
222 result = request('{"method":"finish","params":{"handle":1}}');
|
cannam@175
|
223 }
|
cannam@175
|
224
|
cannam@175
|
225 test();
|
cannam@175
|
226 });
|