UNPKG

@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

644 lines (561 loc) 20.9 kB
(async () => { const isCI = ["8081", "8082"].includes(location.port) && location.search.includes("ci=true"); const url = "/dist/csound.js"; // isCI ? "/csound.esm.js" : "/csound.dev.esm.js"; const resp = await import(url); // console.log("RESP", resp); const { Csound } = resp; 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> 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 instr 1 chnset("test0", "strChannel") turnoff endin </CsInstruments> <CsScore> i 1 0 2 e 2 0 </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 0.3 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.compileCSD(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.compileCSD(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.compileCSD(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.compileCSD(pluginTest)); await cs.start(); await cs.stop(); await cs.terminateInstance(); }); // DISABLED due to cxx plugin using source not updated for CS7 // 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.compileCSD(cxxPluginTest)); // await cs.start(); // await cs.stop(); // await cs.terminateInstance(); // }); it("emits public events in realtime performance", async function () { if (test.name !== "WORKER, AW, SAB") { 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.compileCSD(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.compileCSD(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.compileCSD(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.compileCSD(helloWorld); await csoundObj.start(); await csoundObj.stop(); await csoundObj.reset(); await csoundObj.compileCSD(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); await csoundObj.fs.writeFile("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( await csoundObj.fs.readdir("/"), "tiny_test_sample.wav", "The sample was written into the root dir", ); assert.equal(0, await csoundObj.compileCSD(samplesTest), "The test string is valid"); assert.equal( 0, await csoundObj.start(), "Csounds starts normally, indicating the sample was found", ); await waitUntilEnd; assert.include( await csoundObj.fs.readdir("/"), "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); // Writing the csd to disk const csdPath = "/anycsd.csd"; await csoundObj.fs.mkdir("/somedir"); await csoundObj.fs.writeFile("tiny_test_sample.wav", testSample); await csoundObj.fs.writeFile(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( await csoundObj.fs.readdir("/"), "tiny_test_sample.wav", "The sample was written into the root dir", ); assert.equal(0, await csoundObj.compileCSD(csdPath, 0), "The test Csd is valid"); assert.equal( 0, await csoundObj.start(), "Csounds starts normally, indicating the sample was found", ); await waitUntilEnd; assert.include( await csoundObj.fs.readdir("/"), "monitor_out.wav", "The sample which csound wrote with fout, is accessible after the end of performance", ); await csoundObj.terminateInstance(); }); it("can use fs.stat() to get file information", 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); // Write a test file await csoundObj.fs.writeFile("test_file.txt", new TextEncoder().encode("Hello World")); await csoundObj.fs.writeFile("tiny_test_sample.wav", testSample); // Test stat on existing file const fileStat = await csoundObj.fs.stat("test_file.txt"); assert.isObject(fileStat, "stat returns an object for existing file"); assert.property(fileStat, "size", "stat object has size property"); assert.property(fileStat, "isFile", "stat object has isFile property"); assert.property(fileStat, "isDirectory", "stat object has isDirectory property"); assert.equal(fileStat.size, 11, "file size matches expected length"); assert.isTrue(fileStat.isFile, "isFile returns true for file"); assert.isFalse(fileStat.isDirectory, "isDirectory returns false for file"); // Test stat on binary file const binaryStat = await csoundObj.fs.stat("tiny_test_sample.wav"); assert.isObject(binaryStat, "stat returns an object for binary file"); assert.equal(binaryStat.size, testSample.length, "binary file size matches"); // Test stat on non-existing file const nonExistentStat = await csoundObj.fs.stat("non_existent_file.txt"); assert.isUndefined(nonExistentStat, "stat returns undefined for non-existent file"); await csoundObj.terminateInstance(); }); it("can use fs.pathExists() to check file existence", async function () { const csoundObj = await Csound(test); // Test non-existing file const nonExistentExists = await csoundObj.fs.pathExists("non_existent_file.txt"); assert.isFalse(nonExistentExists, "pathExists returns false for non-existent file"); // Write a test file await csoundObj.fs.writeFile("test_file.txt", new TextEncoder().encode("Hello World")); // Test existing file const existingFileExists = await csoundObj.fs.pathExists("test_file.txt"); assert.isTrue(existingFileExists, "pathExists returns true for existing file"); // Test with directory await csoundObj.fs.mkdir("test_directory"); const directoryExists = await csoundObj.fs.pathExists("test_directory"); assert.isTrue(directoryExists, "pathExists returns true for existing directory"); // Test with nested path await csoundObj.fs.writeFile( "test_directory/nested_file.txt", new TextEncoder().encode("Nested"), ); const nestedFileExists = await csoundObj.fs.pathExists("test_directory/nested_file.txt"); assert.isTrue(nestedFileExists, "pathExists returns true for nested file"); await csoundObj.terminateInstance(); }); it("can use fs.stat() and fs.pathExists() together for file operations", async function () { const csoundObj = await Csound(test); const fileName = "combined_test_file.txt"; const fileContent = "This is a test file for combined operations"; // Initially file should not exist assert.isFalse(await csoundObj.fs.pathExists(fileName), "File does not exist initially"); assert.isUndefined( await csoundObj.fs.stat(fileName), "stat returns undefined for non-existent file", ); // Write file await csoundObj.fs.writeFile(fileName, new TextEncoder().encode(fileContent)); // Now file should exist assert.isTrue(await csoundObj.fs.pathExists(fileName), "File exists after writing"); const stat = await csoundObj.fs.stat(fileName); assert.isObject(stat, "stat returns object after file creation"); assert.equal(stat.size, fileContent.length, "file size matches content length"); assert.isTrue(stat.isFile, "stat correctly identifies file"); assert.isFalse(stat.isDirectory, "stat correctly identifies not directory"); // Remove file await csoundObj.fs.unlink(fileName); // File should no longer exist assert.isFalse( await csoundObj.fs.pathExists(fileName), "File does not exist after unlinking", ); assert.isUndefined( await csoundObj.fs.stat(fileName), "stat returns undefined after unlinking", ); 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.cleanReferencesAfterRun(true); mocha.run(); } })();