UNPKG

@doc.e.dub/csound-browser

Version:

[![npm (scoped with tag)](https://shields.shivering-isles.com/npm/v/@csound/browser/latest)](https://www.npmjs.com/package/@csound/browser) [![GitHub Workflow Status](https://shields.shivering-isles.com/github/workflow/status/csound/csound/csound_wasm)](h

531 lines (471 loc) 15.9 kB
(async () => { const isCI = location.port === "8081" && location.search.includes("ci=true"); const url = isCI ? "/csound.esm.js" : "/csound.dev.esm.js"; const { Csound } = await import(url); const helloWorld = ` <CsoundSynthesizer> <CsOptions> -odac </CsOptions> <CsInstruments> instr 1 prints "Hello World!\n" endin </CsInstruments> <CsScore> i 1 0 0 </CsScore> </CsoundSynthesizer> `; const shortTone = ` <CsoundSynthesizer> <CsOptions> -odac </CsOptions> <CsInstruments> 0dbfs = 1 chnset(1, "test1") chnset(2, "test2") instr 1 out poscil(0dbfs/3, 440) * linen:a(1, .01, p3, .01) endin </CsInstruments> <CsScore> i 1 0 2 </CsScore> </CsoundSynthesizer> `; const shortTone2 = ` <CsoundSynthesizer> <CsOptions> -odac </CsOptions> <CsInstruments> 0dbfs = 1 chnset(440, "freq") instr 1 out poscil(0dbfs/3, chnget:k("freq")) * linen:a(1, .01, p3, .01) endin </CsInstruments> <CsScore> i 1 0 1 </CsScore> </CsoundSynthesizer> `; const stringChannelTest = ` <CsoundSynthesizer> <CsOptions> -odac </CsOptions> <CsInstruments> 0dbfs = 1 chnset("test0", "strChannel") </CsInstruments> <CsScore> i 1 0 2 </CsScore> </CsoundSynthesizer> `; const pluginTest = ` <CsoundSynthesizer> <CsOptions> -odac </CsOptions> <CsInstruments> 0dbfs=1 instr 1 i1 = 2 i2 = 2 i3 mult i1, i2 print i3 endin instr 2 k1 = 2 k2 = 2 k3 mult k1, k2 printk2 k3 endin instr 3 a1 oscili 0dbfs, 440 a2 oscili 0dbfs, 356 a3 mult a1, a2 out a3 endin </CsInstruments> <CsScore> i1 0 0 i2 0 1 i3 0 2 e 0 0 </CsScore> </CsoundSynthesizer> `; const cxxPluginTest = ` <CsoundSynthesizer> <CsOptions> -odac </CsOptions> <CsInstruments> 0dbfs=1 instr 1 kcone_lengths[] fillarray 0.0316, 0.051, .3, 0.2 kradii_in[] fillarray 0.0055, 0.00635, 0.0075, 0.0075 kradii_out[] fillarray 0.0055, 0.0075, 0.0075, 0.0275 kcurve_type[] fillarray 1, 1, 1, 2 kLength linseg 0.2, 2, 0.3 kPick_Pos = 1.0 kEndReflection init 1.0 kEndReflection = 1.0 kDensity = 1.0 kComputeVisco = 0 aImpulse mpulse .5, .1 aFeedback, aSound resontube 0.005*aImpulse, kLength, kcone_lengths, kradii_in, kradii_out, kcurve_type, kEndReflection, kDensity, kPick_Pos, kComputeVisco out aSound endin </CsInstruments> <CsScore> i1 0 2 </CsScore> </CsoundSynthesizer> `; const ftableTest = ` <CsoundSynthesizer> <CsOptions> -odac </CsOptions> <CsInstruments> instr 1 prints "Hello Fibonnaci!\\n" prints "Table length %d\\n", tableng:i(1) endin </CsInstruments> <CsScore> f 1 0 8 -2 0 1 1 2 3 5 8 13 i 1 0 -1 </CsScore> </CsoundSynthesizer> `; const samplesTest = ` <CsoundSynthesizer> <CsOptions> -odac </CsOptions> <CsInstruments> sr = 44100 ksmps = 32 nchnls = 1 0dbfs = 1 instr 1 Ssample = "tiny_test_sample.wav" aRead[] diskin Ssample, 1, 0, 0 out aRead[0], aRead[0] endin instr 2 aSig monitor fout "monitor_out.wav", 4, aSig endin </CsInstruments> <CsScore> i 2 0 1 i 1 0 0.1 i 1 + . i 1 + . e </CsScore> </CsoundSynthesizer> `; mocha.setup({ ui: "bdd", timeout: 10000 }).fullTrace(); if (isCI) { MochaWebdriverClient.install(mocha); } const csoundVariations = [ { useWorker: false, useSPN: false, name: "SINGLE THREAD, AW" }, { useWorker: false, useSPN: true, name: "SINGLE THREAD, SPN" }, { useWorker: true, useSAB: true, name: "WORKER, AW, SAB" }, { useWorker: true, useSAB: false, name: "WORKER, AW, Messageport" }, { useWorker: true, useSAB: false, useSPN: true, name: "WORKER, SPN, MessagePort" }, ]; csoundVariations.forEach((test) => { describe(`@csound/browser : ${test.name}`, async function () { this.timeout(10000); it("can be started", async function () { const cs = await Csound(test); console.log(`Csound version: ${cs.name}`); const startReturn = await cs.start(); assert.equal(startReturn, 0); await cs.stop(); cs.terminateInstance && (await cs.terminateInstance()); }); it("has expected methods", async function () { const cs = await Csound(test); assert.property(cs, "getAudioContext", "has .getAudioContext() method"); assert.property(cs, "start", "has .start() method"); assert.property(cs, "stop", "has .stop() method"); assert.property(cs, "pause", "has .pause() method"); await cs.stop(); await cs.terminateInstance(); }); it("can use run using just compileOrc", async function () { const cs = await Csound(test); await cs.compileOrc(` ksmps=64 instr 1 out oscili(.25, 110) endin schedule(1,0,1) `); const startReturn = await cs.start(); assert.equal(startReturn, 0); await cs.stop(); await cs.terminateInstance(); }); it("can play tone and get channel values", async function () { const cs = await Csound(test); const compileReturn = await cs.compileCsdText(shortTone); assert.equal(compileReturn, 0); const startReturn = await cs.start(); assert.equal(startReturn, 0); assert.equal(1, await cs.getControlChannel("test1")); assert.equal(2, await cs.getControlChannel("test2")); await cs.stop(); await cs.terminateInstance(); }); it("can play tone and send channel values", async function () { const cs = await Csound(test); const compileReturn = await cs.compileCsdText(shortTone2); assert.equal(compileReturn, 0); const startReturn = await cs.start(); assert.equal(startReturn, 0); await cs.setControlChannel("freq", 880); assert.equal(880, await cs.getControlChannel("freq")); await cs.stop(); await cs.terminateInstance(); }); it("can send and receive string channel values", async function () { const cs = await Csound(test); const compileReturn = await cs.compileCsdText(stringChannelTest); assert.equal(compileReturn, 0); const startReturn = await cs.start(); assert.equal(startReturn, 0); assert.equal("test0", await cs.getStringChannel("strChannel")); await cs.setStringChannel("strChannel", "test1"); assert.equal("test1", await cs.getStringChannel("strChannel")); await cs.stop(); await cs.terminateInstance(); }); it("can load and run plugins", async function () { const testWithPlugin = Object.assign( { withPlugins: ["./plugin_example.wasm"], }, test, ); const cs = await Csound(testWithPlugin); assert.equal(0, await cs.compileCsdText(pluginTest)); await cs.start(); await cs.stop(); await cs.terminateInstance(); }); it("can load and run c++ plugins", async function () { const testWithPlugin = Object.assign( { withPlugins: ["./plugin_example_cxx.wasm"], }, test, ); const cs = await Csound(testWithPlugin); assert.equal(0, await cs.compileCsdText(cxxPluginTest)); await cs.start(); await cs.stop(); await cs.terminateInstance(); }); it("emits public events in realtime performance", async function () { const eventPlaySpy = sinon.spy(); const eventPauseSpy = sinon.spy(); const eventStopSpy = sinon.spy(); const eventOnAudioNodeCreatedSpy = sinon.spy(); const csoundObj = await Csound(test); csoundObj.on("play", eventPlaySpy); csoundObj.on("pause", eventPauseSpy); csoundObj.on("stop", eventStopSpy); csoundObj.on("onAudioNodeCreated", eventOnAudioNodeCreatedSpy); await csoundObj.setOption("-odac"); await csoundObj.compileCsdText(shortTone); await csoundObj.start(); await csoundObj.pause(); await csoundObj.resume(); await csoundObj.stop(); assert(eventPlaySpy.calledTwice, 'The "play" event was emitted twice'); assert(eventPauseSpy.calledOnce, 'The "pause" event was emitted once'); assert(eventStopSpy.calledOnce, 'The "stop" event was emitted once'); assert( eventOnAudioNodeCreatedSpy.calledOnce, 'The "onAudioNodeCreated" event was emitted once', ); assert( eventOnAudioNodeCreatedSpy.calledWith(sinon.match.instanceOf(AudioNode)), 'The argument provided to the callback of "onAudioNodeCreated" was an AudioNode', ); await csoundObj.terminateInstance(); }); it("can read and write ftables in realtime", async function () { const csoundObj = await Csound(test); await csoundObj.setOption("-odac"); await csoundObj.compileCsdText(ftableTest); await csoundObj.start(); // assert few indicies assert.equal(8, await csoundObj.tableLength(1), "The length of the table counts as 8"); assert.equal(0, await csoundObj.tableGet(1, 0, "The first index is 0")); assert.equal(1, await csoundObj.tableGet(1, 1, "The second index is 1")); assert.equal(1, await csoundObj.tableGet(1, 2, "The third index is 2")); assert.equal(2, await csoundObj.tableGet(1, 3, "The fourth index is 3")); await csoundObj.tableSet(1, 0, 123); await csoundObj.tableSet(1, 1, 666); assert.equal(123, await csoundObj.tableGet(1, 0, "The first index was modified to 123")); assert.equal(666, await csoundObj.tableGet(1, 1, "The second index was modified to 666")); await csoundObj.stop(); await csoundObj.terminateInstance(); }); it("can read and write arraybuffers to/from ftables in realtime", async function () { const csoundObj = await Csound(test); await csoundObj.setOption("-odac"); await csoundObj.compileCsdText(ftableTest); await csoundObj.start(); const tableLength = await csoundObj.tableLength(1); // we initialize a float64 typed array // using the length of the original csound table const float64array = new Float64Array(tableLength); // we then fill the arrays with test values float64array.set([1, 1.1, 1.01, 1.001]); // then we copy the the array from js into csound's runtime onto table 1 await csoundObj.tableCopyIn(1, float64array); // assert that the values got delivered assert.equal( float64array[0], await csoundObj.tableGet(1, 0), "The first index from table1 matches the first index of the copied array", ); assert.equal( float64array[1], await csoundObj.tableGet(1, 1), "The second index from table1 matches the second index of the copied array", ); assert.equal( float64array[2], await csoundObj.tableGet(1, 2), "The third index from table1 matches the third index of the copied array", ); assert.equal( float64array[3], await csoundObj.tableGet(1, 3), "The fourth index from table1 matches the fourth index of the copied array", ); const csoundTableOneFloat64 = await csoundObj.tableCopyOut(1); // we convert it to normal Array for readability const csoundTableOneArray = Array.from(csoundTableOneFloat64); assert.deepEqual( csoundTableOneArray, [1, 1.1, 1.01, 1.001, 0, 0, 0, 0], "The current csound table matches the 4 numbers we copied into it followed by 4 empty values (0)", ); await csoundObj.stop(); await csoundObj.terminateInstance(); }); it("can stop() and reset() without start()", async function () { const csoundObj = await Csound(test); await csoundObj.stop(); await csoundObj.reset(); await csoundObj.start(); await csoundObj.stop(); await csoundObj.terminateInstance(); }); it("can start() -> stop() -> reset() and start again", async function () { const csoundObj = await Csound(test); await csoundObj.compileCsdText(helloWorld); await csoundObj.start(); await csoundObj.stop(); await csoundObj.reset(); await csoundObj.compileCsdText(helloWorld); await csoundObj.start(); await csoundObj.stop(); await csoundObj.terminateInstance(); }); it("can play a sample, write a sample and read the output file", async function () { const csoundObj = await Csound(test); const response = await fetch("/tiny_test_sample.wav"); const testSampleArrayBuffer = await response.arrayBuffer(); const testSample = new Uint8Array(testSampleArrayBuffer); csoundObj.fs.writeFileSync("tiny_test_sample.wav", testSample); // allow the example to play until the end let endResolver; const waitUntilEnd = new Promise((resolve) => { endResolver = resolve; }); csoundObj.on("realtimePerformanceEnded", endResolver); assert.include( csoundObj.fs.readdirSync("/"), "tiny_test_sample.wav", "The sample was written into the root dir", ); assert.equal(0, await csoundObj.compileCsdText(samplesTest), "The test string is valid"); assert.equal( 0, await csoundObj.start(), "Csounds starts normally, indicating the sample was found", ); await waitUntilEnd; assert.include( csoundObj.fs.readdirSync("/"), "monitor_out.wav", "The sample which csound wrote with fout, is accessible after the end of performance", ); await csoundObj.terminateInstance(); }); it("can play a csd from a nested filesystem directory, with code requiring a sample", async function () { const csoundObj = await Csound(test); const response = await fetch("/tiny_test_sample.wav"); const testSampleArrayBuffer = await response.arrayBuffer(); const testSample = new Uint8Array(testSampleArrayBuffer); csoundObj.fs.writeFileSync("tiny_test_sample.wav", testSample); // Writing the csd to disk const csdPath = "/somedir/anycsd.csd"; csoundObj.fs.mkdirpSync("/somedir"); csoundObj.fs.writeFileSync(csdPath, samplesTest); // allow the example to play until the end let endResolver; const waitUntilEnd = new Promise((resolve) => { endResolver = resolve; }); csoundObj.on("realtimePerformanceEnded", endResolver); assert.include( csoundObj.fs.readdirSync("/"), "tiny_test_sample.wav", "The sample was written into the root dir", ); assert.equal(0, await csoundObj.compileCsd(csdPath), "The test Csd is valid"); assert.equal( 0, await csoundObj.start(), "Csounds starts normally, indicating the sample was found", ); await waitUntilEnd; assert.include( csoundObj.fs.readdirSync("/"), "monitor_out.wav", "The sample which csound wrote with fout, is accessible after the end of performance", ); await csoundObj.terminateInstance(); }); }); }); const triggerEvent = "ontouchstart" in document.documentElement ? "touchend" : "click"; document.querySelector("#all_tests").addEventListener(triggerEvent, async function () { mocha.fullTrace(true); mocha.checkLeaks(false); // worker + spn defenitely leaks mocha.cleanReferencesAfterRun(true); mocha.run(); }); if (isCI) { mocha.run(); } })();