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