1 | (async () => {
|
2 | const isCI = ["8081", "8082"].includes(location.port) && location.search.includes("ci=true");
|
3 | const url = "/dist/csound.js";
|
4 | const { Csound } = await import(url);
|
5 |
|
6 | const helloWorld = `
|
7 | <CsoundSynthesizer>
|
8 | <CsOptions>
|
9 | -odac
|
10 | </CsOptions>
|
11 | <CsInstruments>
|
12 | instr 1
|
13 | prints "Hello World!\\n"
|
14 | endin
|
15 | </CsInstruments>
|
16 | <CsScore>
|
17 | i 1 0 0
|
18 | </CsScore>
|
19 | </CsoundSynthesizer>
|
20 | `;
|
21 |
|
22 | const shortTone = `
|
23 | <CsoundSynthesizer>
|
24 | <CsOptions>
|
25 | -odac
|
26 | </CsOptions>
|
27 | <CsInstruments>
|
28 |
|
29 | chnset(1, "test1")
|
30 | chnset(2, "test2")
|
31 |
|
32 | instr 1
|
33 | out poscil(0dbfs/3, 440) * linen:a(1, .01, p3, .01)
|
34 | endin
|
35 | </CsInstruments>
|
36 | <CsScore>
|
37 | i 1 0 2
|
38 | </CsScore>
|
39 | </CsoundSynthesizer>
|
40 | `;
|
41 |
|
42 | const shortTone2 = `
|
43 | <CsoundSynthesizer>
|
44 | <CsOptions>
|
45 | -odac
|
46 | </CsOptions>
|
47 | <CsInstruments>
|
48 | 0dbfs = 1
|
49 |
|
50 | chnset(440, "freq")
|
51 |
|
52 | instr 1
|
53 | out poscil(0dbfs/3, chnget:k("freq")) * linen:a(1, .01, p3, .01)
|
54 | endin
|
55 | </CsInstruments>
|
56 | <CsScore>
|
57 | i 1 0 1
|
58 | </CsScore>
|
59 | </CsoundSynthesizer>
|
60 | `;
|
61 |
|
62 | const stringChannelTest = `
|
63 | <CsoundSynthesizer>
|
64 | <CsOptions>
|
65 | -odac
|
66 | </CsOptions>
|
67 | <CsInstruments>
|
68 | 0dbfs = 1
|
69 |
|
70 | instr 1
|
71 | chnset("test0", "strChannel")
|
72 | turnoff
|
73 | endin
|
74 |
|
75 | </CsInstruments>
|
76 | <CsScore>
|
77 | i 1 0 2
|
78 | e 2 0
|
79 | </CsScore>
|
80 | </CsoundSynthesizer>
|
81 | `;
|
82 |
|
83 | const pluginTest = `
|
84 | <CsoundSynthesizer>
|
85 | <CsOptions>
|
86 | -odac
|
87 | </CsOptions>
|
88 | <CsInstruments>
|
89 | 0dbfs=1
|
90 | instr 1
|
91 | i1 = 2
|
92 | i2 = 2
|
93 | i3 mult i1, i2
|
94 | print i3
|
95 | endin
|
96 | instr 2
|
97 | k1 = 2
|
98 | k2 = 2
|
99 | k3 mult k1, k2
|
100 | printk2 k3
|
101 | endin
|
102 | instr 3
|
103 | a1 oscili 0dbfs, 440
|
104 | a2 oscili 0dbfs, 356
|
105 | a3 mult a1, a2
|
106 | out a3
|
107 | endin
|
108 | </CsInstruments>
|
109 | <CsScore>
|
110 | i1 0 0
|
111 | i2 0 1
|
112 | i3 0 2
|
113 | e 0 0
|
114 | </CsScore>
|
115 | </CsoundSynthesizer>
|
116 | `;
|
117 |
|
118 | const cxxPluginTest = `
|
119 | <CsoundSynthesizer>
|
120 | <CsOptions>
|
121 | -odac
|
122 | </CsOptions>
|
123 | <CsInstruments>
|
124 | 0dbfs=1
|
125 | instr 1
|
126 | kcone_lengths[] fillarray 0.0316, 0.051, .3, 0.2
|
127 | kradii_in[] fillarray 0.0055, 0.00635, 0.0075, 0.0075
|
128 | kradii_out[] fillarray 0.0055, 0.0075, 0.0075, 0.0275
|
129 | kcurve_type[] fillarray 1, 1, 1, 2
|
130 | kLength linseg 0.2, 2, 0.3
|
131 | kPick_Pos = 1.0
|
132 | kEndReflection init 1.0
|
133 | kEndReflection = 1.0
|
134 | kDensity = 1.0
|
135 | kComputeVisco = 0
|
136 | aImpulse mpulse .5, .1
|
137 | aFeedback, aSound resontube 0.005*aImpulse, kLength, kcone_lengths, kradii_in, kradii_out, kcurve_type, kEndReflection, kDensity, kPick_Pos, kComputeVisco
|
138 | out aSound
|
139 | endin
|
140 | </CsInstruments>
|
141 | <CsScore>
|
142 | i1 0 2
|
143 | </CsScore>
|
144 | </CsoundSynthesizer>
|
145 | `;
|
146 |
|
147 | const ftableTest = `
|
148 | <CsoundSynthesizer>
|
149 | <CsOptions>
|
150 | -odac
|
151 | </CsOptions>
|
152 | <CsInstruments>
|
153 | instr 1
|
154 | prints "Hello Fibonnaci!\\n"
|
155 | prints "Table length %d\\n", tableng:i(1)
|
156 | endin
|
157 | </CsInstruments>
|
158 | <CsScore>
|
159 | f 1 0 8 -2 0 1 1 2 3 5 8 13
|
160 | i 1 0 -1
|
161 | </CsScore>
|
162 | </CsoundSynthesizer>
|
163 | `;
|
164 |
|
165 | const samplesTest = `
|
166 | <CsoundSynthesizer>
|
167 | <CsOptions>
|
168 | -odac
|
169 | </CsOptions>
|
170 | <CsInstruments>
|
171 | sr = 44100
|
172 | ksmps = 32
|
173 | nchnls = 1
|
174 | 0dbfs = 1
|
175 |
|
176 | instr 1
|
177 | Ssample = "tiny_test_sample.wav"
|
178 | aRead[] diskin Ssample, 1, 0, 0
|
179 | out aRead[0], aRead[0]
|
180 | endin
|
181 |
|
182 | instr 2
|
183 | aSig monitor
|
184 | fout "monitor_out.wav", 4, aSig
|
185 | endin
|
186 |
|
187 | </CsInstruments>
|
188 | <CsScore>
|
189 | i 2 0 0.3
|
190 | i 1 0 0.1
|
191 | i 1 + .
|
192 | i 1 + .
|
193 | e
|
194 | </CsScore>
|
195 | </CsoundSynthesizer>
|
196 | `;
|
197 |
|
198 | mocha.setup({ ui: "bdd", timeout: 10000 }).fullTrace();
|
199 |
|
200 | if (isCI) {
|
201 | MochaWebdriverClient.install(mocha);
|
202 | }
|
203 |
|
204 | const csoundVariations = [
|
205 | { useWorker: false, useSPN: false, name: "SINGLE THREAD, AW" },
|
206 | { useWorker: false, useSPN: true, name: "SINGLE THREAD, SPN" },
|
207 | { useWorker: true, useSAB: true, name: "WORKER, AW, SAB" },
|
208 | { useWorker: true, useSAB: false, name: "WORKER, AW, Messageport" },
|
209 | { useWorker: true, useSAB: false, useSPN: true, name: "WORKER, SPN, MessagePort" },
|
210 | ];
|
211 |
|
212 | csoundVariations.forEach((test) => {
|
213 | describe(`@csound/browser : ${test.name}`, async function () {
|
214 | this.timeout(10000);
|
215 | it("can be started", async function () {
|
216 | const cs = await Csound(test);
|
217 | console.log(`Csound version: ${cs.name}`);
|
218 | const startReturn = await cs.start();
|
219 | assert.equal(startReturn, 0);
|
220 | await cs.stop();
|
221 | cs.terminateInstance && (await cs.terminateInstance());
|
222 | });
|
223 |
|
224 | it("has expected methods", async function () {
|
225 | const cs = await Csound(test);
|
226 | assert.property(cs, "getAudioContext", "has .getAudioContext() method");
|
227 | assert.property(cs, "start", "has .start() method");
|
228 | assert.property(cs, "stop", "has .stop() method");
|
229 | assert.property(cs, "pause", "has .pause() method");
|
230 | await cs.stop();
|
231 | await cs.terminateInstance();
|
232 | });
|
233 |
|
234 | it("can use run using just compileOrc", async function () {
|
235 | const cs = await Csound(test);
|
236 | await cs.compileOrc(`
|
237 | ksmps=64
|
238 | instr 1
|
239 | out oscili(.25, 110)
|
240 | endin
|
241 | schedule(1,0,1)
|
242 | `);
|
243 | const startReturn = await cs.start();
|
244 | assert.equal(startReturn, 0);
|
245 | await cs.stop();
|
246 | await cs.terminateInstance();
|
247 | });
|
248 |
|
249 | it("can play tone and get channel values", async function () {
|
250 | const cs = await Csound(test);
|
251 | const compileReturn = await cs.compileCsdText(shortTone);
|
252 | assert.equal(compileReturn, 0);
|
253 | const startReturn = await cs.start();
|
254 |
|
255 | assert.equal(startReturn, 0);
|
256 | assert.equal(1, await cs.getControlChannel("test1"));
|
257 | assert.equal(2, await cs.getControlChannel("test2"));
|
258 | await cs.stop();
|
259 | await cs.terminateInstance();
|
260 | });
|
261 |
|
262 | it("can play tone and send channel values", async function () {
|
263 | const cs = await Csound(test);
|
264 | const compileReturn = await cs.compileCsdText(shortTone2);
|
265 | assert.equal(compileReturn, 0);
|
266 | const startReturn = await cs.start();
|
267 | assert.equal(startReturn, 0);
|
268 | await cs.setControlChannel("freq", 880);
|
269 | assert.equal(880, await cs.getControlChannel("freq"));
|
270 | await cs.stop();
|
271 | await cs.terminateInstance();
|
272 | });
|
273 |
|
274 | it("can send and receive string channel values", async function () {
|
275 | const cs = await Csound(test);
|
276 | const compileReturn = await cs.compileCsdText(stringChannelTest);
|
277 | assert.equal(compileReturn, 0);
|
278 | const startReturn = await cs.start();
|
279 | assert.equal(startReturn, 0);
|
280 | assert.equal("test0", await cs.getStringChannel("strChannel"));
|
281 | await cs.setStringChannel("strChannel", "test1");
|
282 | assert.equal("test1", await cs.getStringChannel("strChannel"));
|
283 | await cs.stop();
|
284 | await cs.terminateInstance();
|
285 | });
|
286 |
|
287 | it("can load and run plugins", async function () {
|
288 | const testWithPlugin = Object.assign(
|
289 | {
|
290 | withPlugins: ["./plugin_example.wasm"],
|
291 | },
|
292 | test,
|
293 | );
|
294 | const cs = await Csound(testWithPlugin);
|
295 | assert.equal(0, await cs.compileCsdText(pluginTest));
|
296 | await cs.start();
|
297 | await cs.stop();
|
298 | await cs.terminateInstance();
|
299 | });
|
300 |
|
301 | it("can load and run c++ plugins", async function () {
|
302 | const testWithPlugin = Object.assign(
|
303 | {
|
304 | withPlugins: ["./plugin_example_cxx.wasm"],
|
305 | },
|
306 | test,
|
307 | );
|
308 | const cs = await Csound(testWithPlugin);
|
309 |
|
310 | assert.equal(0, await cs.compileCsdText(cxxPluginTest));
|
311 | await cs.start();
|
312 | await cs.stop();
|
313 | await cs.terminateInstance();
|
314 | });
|
315 |
|
316 | it("emits public events in realtime performance", async function () {
|
317 | if (test.name !== "WORKER, AW, SAB") {
|
318 | const eventPlaySpy = sinon.spy();
|
319 | const eventPauseSpy = sinon.spy();
|
320 | const eventStopSpy = sinon.spy();
|
321 | const eventOnAudioNodeCreatedSpy = sinon.spy();
|
322 |
|
323 | const csoundObj = await Csound(test);
|
324 |
|
325 | csoundObj.on("play", eventPlaySpy);
|
326 | csoundObj.on("pause", eventPauseSpy);
|
327 | csoundObj.on("stop", eventStopSpy);
|
328 | csoundObj.on("onAudioNodeCreated", eventOnAudioNodeCreatedSpy);
|
329 |
|
330 | await csoundObj.setOption("-odac");
|
331 | await csoundObj.compileCsdText(shortTone);
|
332 | await csoundObj.start();
|
333 | await csoundObj.pause();
|
334 | await csoundObj.resume();
|
335 | await csoundObj.stop();
|
336 |
|
337 | assert(eventPlaySpy.calledTwice, 'The "play" event was emitted twice');
|
338 | assert(eventPauseSpy.calledOnce, 'The "pause" event was emitted once');
|
339 | assert(eventStopSpy.calledOnce, 'The "stop" event was emitted once');
|
340 | assert(
|
341 | eventOnAudioNodeCreatedSpy.calledOnce,
|
342 | 'The "onAudioNodeCreated" event was emitted once',
|
343 | );
|
344 | assert(
|
345 | eventOnAudioNodeCreatedSpy.calledWith(sinon.match.instanceOf(AudioNode)),
|
346 | 'The argument provided to the callback of "onAudioNodeCreated" was an AudioNode',
|
347 | );
|
348 | await csoundObj.terminateInstance();
|
349 | }
|
350 | });
|
351 |
|
352 | it("can read and write ftables in realtime", async function () {
|
353 | const csoundObj = await Csound(test);
|
354 | await csoundObj.setOption("-odac");
|
355 | await csoundObj.compileCsdText(ftableTest);
|
356 | await csoundObj.start();
|
357 |
|
358 |
|
359 | assert.equal(8, await csoundObj.tableLength(1), "The length of the table counts as 8");
|
360 | assert.equal(0, await csoundObj.tableGet(1, 0, "The first index is 0"));
|
361 | assert.equal(1, await csoundObj.tableGet(1, 1, "The second index is 1"));
|
362 | assert.equal(1, await csoundObj.tableGet(1, 2, "The third index is 2"));
|
363 | assert.equal(2, await csoundObj.tableGet(1, 3, "The fourth index is 3"));
|
364 |
|
365 | await csoundObj.tableSet(1, 0, 123);
|
366 | await csoundObj.tableSet(1, 1, 666);
|
367 |
|
368 | assert.equal(123, await csoundObj.tableGet(1, 0, "The first index was modified to 123"));
|
369 | assert.equal(666, await csoundObj.tableGet(1, 1, "The second index was modified to 666"));
|
370 |
|
371 | await csoundObj.stop();
|
372 | await csoundObj.terminateInstance();
|
373 | });
|
374 |
|
375 | it("can read and write arraybuffers to/from ftables in realtime", async function () {
|
376 | const csoundObj = await Csound(test);
|
377 | await csoundObj.setOption("-odac");
|
378 | await csoundObj.compileCsdText(ftableTest);
|
379 | await csoundObj.start();
|
380 |
|
381 | const tableLength = await csoundObj.tableLength(1);
|
382 |
|
383 |
|
384 |
|
385 | const float64array = new Float64Array(tableLength);
|
386 |
|
387 |
|
388 | float64array.set([1, 1.1, 1.01, 1.001]);
|
389 |
|
390 |
|
391 | await csoundObj.tableCopyIn(1, float64array);
|
392 |
|
393 |
|
394 | assert.equal(
|
395 | float64array[0],
|
396 | await csoundObj.tableGet(1, 0),
|
397 | "The first index from table1 matches the first index of the copied array",
|
398 | );
|
399 | assert.equal(
|
400 | float64array[1],
|
401 | await csoundObj.tableGet(1, 1),
|
402 | "The second index from table1 matches the second index of the copied array",
|
403 | );
|
404 | assert.equal(
|
405 | float64array[2],
|
406 | await csoundObj.tableGet(1, 2),
|
407 | "The third index from table1 matches the third index of the copied array",
|
408 | );
|
409 | assert.equal(
|
410 | float64array[3],
|
411 | await csoundObj.tableGet(1, 3),
|
412 | "The fourth index from table1 matches the fourth index of the copied array",
|
413 | );
|
414 |
|
415 | const csoundTableOneFloat64 = await csoundObj.tableCopyOut(1);
|
416 |
|
417 | const csoundTableOneArray = Array.from(csoundTableOneFloat64);
|
418 | assert.deepEqual(
|
419 | csoundTableOneArray,
|
420 | [1, 1.1, 1.01, 1.001, 0, 0, 0, 0],
|
421 | "The current csound table matches the 4 numbers we copied into it followed by 4 empty values (0)",
|
422 | );
|
423 | await csoundObj.stop();
|
424 | await csoundObj.terminateInstance();
|
425 | });
|
426 |
|
427 | it("can stop() and reset() without start()", async function () {
|
428 | const csoundObj = await Csound(test);
|
429 | await csoundObj.stop();
|
430 | await csoundObj.reset();
|
431 | await csoundObj.start();
|
432 | await csoundObj.stop();
|
433 | await csoundObj.terminateInstance();
|
434 | });
|
435 |
|
436 | it("can start() -> stop() -> reset() and start again", async function () {
|
437 | const csoundObj = await Csound(test);
|
438 | await csoundObj.compileCsdText(helloWorld);
|
439 | await csoundObj.start();
|
440 | await csoundObj.stop();
|
441 | await csoundObj.reset();
|
442 | await csoundObj.compileCsdText(helloWorld);
|
443 | await csoundObj.start();
|
444 | await csoundObj.stop();
|
445 | await csoundObj.terminateInstance();
|
446 | });
|
447 |
|
448 | it("can play a sample, write a sample and read the output file", async function () {
|
449 | const csoundObj = await Csound(test);
|
450 | const response = await fetch("tiny_test_sample.wav");
|
451 | const testSampleArrayBuffer = await response.arrayBuffer();
|
452 | const testSample = new Uint8Array(testSampleArrayBuffer);
|
453 | await csoundObj.fs.writeFile("tiny_test_sample.wav", testSample);
|
454 |
|
455 |
|
456 | let endResolver;
|
457 | const waitUntilEnd = new Promise((resolve) => {
|
458 | endResolver = resolve;
|
459 | });
|
460 | csoundObj.on("realtimePerformanceEnded", endResolver);
|
461 |
|
462 | assert.include(
|
463 | await csoundObj.fs.readdir("/"),
|
464 | "tiny_test_sample.wav",
|
465 | "The sample was written into the root dir",
|
466 | );
|
467 |
|
468 | assert.equal(0, await csoundObj.compileCsdText(samplesTest), "The test string is valid");
|
469 | assert.equal(
|
470 | 0,
|
471 | await csoundObj.start(),
|
472 | "Csounds starts normally, indicating the sample was found",
|
473 | );
|
474 |
|
475 | await waitUntilEnd;
|
476 | assert.include(
|
477 | await csoundObj.fs.readdir("/"),
|
478 | "monitor_out.wav",
|
479 | "The sample which csound wrote with fout, is accessible after the end of performance",
|
480 | );
|
481 | await csoundObj.terminateInstance();
|
482 | });
|
483 |
|
484 | it("can play a csd from a nested filesystem directory, with code requiring a sample", async function () {
|
485 | const csoundObj = await Csound(test);
|
486 | const response = await fetch("/tiny_test_sample.wav");
|
487 | const testSampleArrayBuffer = await response.arrayBuffer();
|
488 | const testSample = new Uint8Array(testSampleArrayBuffer);
|
489 |
|
490 |
|
491 | const csdPath = "/anycsd.csd";
|
492 | await csoundObj.fs.mkdir("/somedir");
|
493 | await csoundObj.fs.writeFile("tiny_test_sample.wav", testSample);
|
494 | await csoundObj.fs.writeFile(csdPath, samplesTest);
|
495 |
|
496 |
|
497 | let endResolver;
|
498 | const waitUntilEnd = new Promise((resolve) => {
|
499 | endResolver = resolve;
|
500 | });
|
501 | csoundObj.on("realtimePerformanceEnded", endResolver);
|
502 |
|
503 | assert.include(
|
504 | await csoundObj.fs.readdir("/"),
|
505 | "tiny_test_sample.wav",
|
506 | "The sample was written into the root dir",
|
507 | );
|
508 |
|
509 | assert.equal(0, await csoundObj.compileCsd(csdPath), "The test Csd is valid");
|
510 | assert.equal(
|
511 | 0,
|
512 | await csoundObj.start(),
|
513 | "Csounds starts normally, indicating the sample was found",
|
514 | );
|
515 |
|
516 | await waitUntilEnd;
|
517 | assert.include(
|
518 | await csoundObj.fs.readdir("/"),
|
519 | "monitor_out.wav",
|
520 | "The sample which csound wrote with fout, is accessible after the end of performance",
|
521 | );
|
522 | await csoundObj.terminateInstance();
|
523 | });
|
524 | });
|
525 | });
|
526 |
|
527 | const triggerEvent = "ontouchstart" in document.documentElement ? "touchend" : "click";
|
528 | document.querySelector("#all_tests").addEventListener(triggerEvent, async function () {
|
529 | mocha.fullTrace(true);
|
530 | mocha.checkLeaks(false);
|
531 | mocha.cleanReferencesAfterRun(true);
|
532 | mocha.run();
|
533 | });
|
534 | if (isCI) {
|
535 | mocha.cleanReferencesAfterRun(true);
|
536 | mocha.run();
|
537 | }
|
538 | })();
|