diff --git a/Android/CsoundAndroid/jni/version.h b/Android/CsoundAndroid/jni/version.h index 4bb236b34e0..2849006befe 100644 --- a/Android/CsoundAndroid/jni/version.h +++ b/Android/CsoundAndroid/jni/version.h @@ -40,7 +40,7 @@ #define CS_PACKAGE_VERSION VERSION #define CS_VERSION (6) #define CS_SUBVER (18) -#define CS_PATCHLEVEL (0) +#define CS_PATCHLEVEL (1) #define CS_APIVERSION 4 /* should be increased anytime a new version diff --git a/InOut/libsnd.c b/InOut/libsnd.c index fd8213f0958..e7f115bf294 100644 --- a/InOut/libsnd.c +++ b/InOut/libsnd.c @@ -841,9 +841,9 @@ void sfopenout(CSOUND *csound) /* init for sound out */ #ifdef SNDFILE_MP3 // VL: setting bitrate to constant improves quality if(O->filetyp == TYP_MPEG) { - csound->Message(csound, "Setting MP3 bitrate to %s\n", O->mp3_mode ? "variable" : "constant" );; + csound->Message(csound, "Setting MP3 bitrate to %s\n", csound->mp3_mode ? "variable" : "constant" );; sf_command(STA(outfile), SFC_SET_BITRATE_MODE, - &(O->mp3_mode), sizeof(int)); + &(csound->mp3_mode), sizeof(int)); } #endif diff --git a/Top/argdecode.c b/Top/argdecode.c index c9d76a7f7e1..05f5283461d 100644 --- a/Top/argdecode.c +++ b/Top/argdecode.c @@ -1257,7 +1257,7 @@ static int decode_long(CSOUND *csound, char *s, int argc, char **argv) } else if (!(strcmp(s, "vbr"))) { #ifdef SNDFILE_MP3 - O->mp3_mode = SF_BITRATE_MODE_VARIABLE; + csound->mp3_mode = SF_BITRATE_MODE_VARIABLE; #endif return 1; } diff --git a/Top/csound.c b/Top/csound.c index 3188e178218..e4fd0969ae4 100644 --- a/Top/csound.c +++ b/Top/csound.c @@ -1019,7 +1019,8 @@ static const CSOUND cenviron_ = { NULL, /* op */ 0, /* mode */ NULL, /* opcodedir */ - NULL /* score_srt */ + NULL, /* score_srt */ + 0 /* mp3 mode */ }; void csound_aops_init_tables(CSOUND *cs); diff --git a/include/csoundCore.h b/include/csoundCore.h index f2988cbf83c..6bdc9fc9c2a 100644 --- a/include/csoundCore.h +++ b/include/csoundCore.h @@ -270,7 +270,6 @@ typedef struct CORFIL { int echo; MYFLT limiter; float sr_default, kr_default; - int mp3_mode; } OPARMS; typedef struct arglst { @@ -1839,6 +1838,7 @@ typedef struct _message_queue_t_ { int mode; char *opcodedir; char *score_srt; + int mp3_mode; /*struct CSOUND_ **self;*/ /**@}*/ #endif /* __BUILDING_LIBCSOUND */ diff --git a/installer/macosx/release-build.sh b/installer/macosx/release-build.sh index e23290e9628..59b6ee50139 100644 --- a/installer/macosx/release-build.sh +++ b/installer/macosx/release-build.sh @@ -213,7 +213,7 @@ install_name_tool -id libcsnd6.6.0.dylib $FRAMEWORK64_DIR/Versions/6.0/libcsnd6 install_name_tool -change $DEPS_BASE/lib/liblo.7.dylib @loader_path/../../../../libs/liblo.7.dylib $FRAMEWORK64_DIR/Resources/Opcodes64/libosc.dylib install_name_tool -change $DEPS_BASE/lib/libportaudio.2.dylib @loader_path/../../../../libs/libportaudio.2.dylib $FRAMEWORK64_DIR/Resources/Opcodes64/librtpa.dylib #install_name_tool -change $DEPS_BASE/lib/libfluidsynth.1.dylib @loader_path/../../../../libs/libfluidsynth.1.dylib $FRAMEWORK64_DIR/Resources/Opcodes64/libfluidOpcodes.dylib -install_name_tool -change libportmidi.dylib @loader_path/../../../../libs/libportmidi.dylib $FRAMEWORK64_DIR/Resources/Opcodes64/libpmidi.dylib +install_name_tool -change @rpath/libportmidi.2.dylib @loader_path/../../../../libs/libportmidi.dylib $FRAMEWORK64_DIR/Resources/Opcodes64/libpmidi.dylib echo "...setting permissions..." diff --git a/wasm/browser/package.json b/wasm/browser/package.json index 082ac9aca17..adb4cf5f82d 100644 --- a/wasm/browser/package.json +++ b/wasm/browser/package.json @@ -1,6 +1,6 @@ { "name": "@csound/browser", - "version": "6.18.0-beta1", + "version": "6.18.2", "module": "./dist/csound.js", "typings": "index.d.ts", "type": "module", @@ -57,7 +57,7 @@ "@babel/core": "^7.16.7", "@babel/plugin-proposal-object-rest-spread": "^7.16.7", "@babel/plugin-transform-destructuring": "^7.16.7", - "@csound/wasm-bin": "6.18.0-beta1", + "@csound/wasm-bin": "6.18.2", "browser-or-node": "^2.0.0", "chai": "^4.3.4", "cross-env": "^7.0.3", diff --git a/wasm/browser/src/index.js b/wasm/browser/src/index.js index 5e266ed4624..9f28f5e9ed7 100644 --- a/wasm/browser/src/index.js +++ b/wasm/browser/src/index.js @@ -9,11 +9,14 @@ import SingleThreadAudioWorkletMainThread from "./mains/worklet.singlethread.mai import { logIndex as log } from "./logger"; import { areWorkletsSupported, + isSafari, isSabSupported, isScriptProcessorNodeSupported, WebkitAudioContext, } from "./utils"; +unmuteIosAudio(); + const wasmDataURI = goog.require("binary.wasm"); /** @@ -33,8 +36,6 @@ const Csound = async function ({ useSAB = true, useSPN = false, } = {}) { - unmuteIosAudio(); - const audioContextIsProvided = audioContext && WebkitAudioContext() && audioContext instanceof WebkitAudioContext(); @@ -42,6 +43,11 @@ const Csound = async function ({ // default to creating an audio context for SingleThread audioContext = audioContext || new (WebkitAudioContext())({ latencyHint: "interactive" }); } + + if (isSafari()) { + audioContext.resume(); + } + const workletSupport = areWorkletsSupported(); const spnSupport = isScriptProcessorNodeSupported(); diff --git a/wasm/browser/src/mains/worklet.singlethread.main.js b/wasm/browser/src/mains/worklet.singlethread.main.js index 3a784ae515b..63ffae899fb 100644 --- a/wasm/browser/src/mains/worklet.singlethread.main.js +++ b/wasm/browser/src/mains/worklet.singlethread.main.js @@ -120,6 +120,10 @@ class SingleThreadAudioWorkletMainThread { break; } case "renderStarted": { + if (this.eventPromises.isWaitingToStart()) { + log("Start promise resolved")(); + this.eventPromises.releaseStartPromise(); + } this.publicEvents.triggerRenderStarted(this); break; } @@ -230,8 +234,14 @@ class SingleThreadAudioWorkletMainThread { case "csoundStart": { const csoundStart = async function () { this.eventPromises.createStartPromise(); + const isRequestingInput = await this.workletProxy.isRequestingInput(); + if (isRequestingInput) { + this.exportApi.enableAudioInput(); + } + const startResult = await proxyCallback({ csound: csoundInstance }); - if (await this.exportApi._isRequestingRtMidiInput(csoundInstance)) { + const isRequestingMidi = await this.exportApi._isRequestingRtMidiInput(csoundInstance); + if (isRequestingMidi) { requestMidi({ onMidiMessage: this.handleMidiInput.bind(this), }); diff --git a/wasm/browser/src/utils.js b/wasm/browser/src/utils.js index 690656a5993..24fb93cc82c 100644 --- a/wasm/browser/src/utils.js +++ b/wasm/browser/src/utils.js @@ -13,6 +13,9 @@ export const isIos = () => /iPhone|iPad|iPod/.test(navigator.userAgent); const isFirefox = () => navigator.userAgent.toLowerCase().includes("firefox"); +export const isSafari = () => + typeof navigator.vendor === "string" && navigator.vendor.includes("Apple"); + export const isSabSupported = () => !isFirefox() && typeof window.Atomics !== "undefined" && @@ -52,26 +55,28 @@ export const stopableStates = new Set([ "renderStarted", ]); -export const makeProxyCallback = (proxyPort, csoundInstance, apiK, playState) => async ( - ...arguments_ -) => { - if (!playState || !stopableStates.has(playState)) { - const modifiedFs = {}; // getModifiedPersistentStorage(); - Object.values(modifiedFs).length > 0 && - (await proxyPort.callUncloned("syncWorkerFs", [csoundInstance, modifiedFs])); - } - return await proxyPort.callUncloned(apiK, [csoundInstance, ...arguments_]); -}; +export const makeProxyCallback = + (proxyPort, csoundInstance, apiK, playState) => + async (...arguments_) => { + if (!playState || !stopableStates.has(playState)) { + const modifiedFs = {}; // getModifiedPersistentStorage(); + Object.values(modifiedFs).length > 0 && + (await proxyPort.callUncloned("syncWorkerFs", [csoundInstance, modifiedFs])); + } + return await proxyPort.callUncloned(apiK, [csoundInstance, ...arguments_]); + }; -export const makeSingleThreadCallback = (csoundInstance, apiCallback) => async (...arguments_) => { - return await apiCallback.apply({}, [csoundInstance, ...arguments_]); -}; +export const makeSingleThreadCallback = + (csoundInstance, apiCallback) => + async (...arguments_) => { + return await apiCallback.apply({}, [csoundInstance, ...arguments_]); + }; export const fetchPlugins = async (withPlugins) => { return await Promise.all( withPlugins.map(async (url) => { const response = await fetch(url); return response.arrayBuffer(); - }) + }), ); }; diff --git a/wasm/browser/src/utils/request-midi.js b/wasm/browser/src/utils/request-midi.js index ac6631de4f0..6a1e426cbfb 100644 --- a/wasm/browser/src/utils/request-midi.js +++ b/wasm/browser/src/utils/request-midi.js @@ -1,31 +1,28 @@ import { logMidiRequest as log } from "../logger"; -const connectedMidiDevices = new Set(); - export async function requestMidi({ onMidiMessage /** function(number,number,number):void */ }) { - log("requesting for web-midi connection"); + log("requesting for web-midi connection")(); + if (navigator && navigator.requestMIDIAccess) { try { const midiDevices = await navigator.requestMIDIAccess(); + if (midiDevices.inputs) { /** @type {Iterator} * @supress {JSC_WRONG_ARGUMENT_COUNT} */ const midiInputs = midiDevices.inputs.values(); for (let input = midiInputs.next(); input && !input.done; input = midiInputs.next()) { - log(`Connecting midi-input: ${input.value.name || "unkown"}`); - if (!connectedMidiDevices.has(input.value.name || "unkown")) { - input.value.onmidimessage = onMidiMessage; - connectedMidiDevices.add(input.value.name || "unkown"); - } + log(`Connecting midi-input: ${input.value.name || "unkown"}`)(); + input.value.onmidimessage = onMidiMessage; } } else { - log("no midi-device detected"); + log("no midi-device detected")(); } } catch (error) { - log("error while connecting web-midi: " + error); + log("error while connecting web-midi: " + error)(); } } else { - log("no web-midi support found, midi-input will not work!"); + log("no web-midi support found, midi-input will not work!")(); } } diff --git a/wasm/browser/src/workers/common.utils.js b/wasm/browser/src/workers/common.utils.js index c90d2097e22..0f8be760d73 100644 --- a/wasm/browser/src/workers/common.utils.js +++ b/wasm/browser/src/workers/common.utils.js @@ -53,3 +53,26 @@ export const instantiateAudioPacket = (numberChannels, numberFrames) => { } return channels; }; + +export const renderFunction = + ({ libraryCsound, workerMessagePort, wasi }) => + async ({ csound }) => { + const kr = libraryCsound.csoundGetKr(csound); + let lastResult = 0; + let cnt = 0; + + while ( + (workerMessagePort.vanillaWorkerState === "renderStarted" || + workerMessagePort.workerState === "renderStarted") && + lastResult === 0 + ) { + lastResult = libraryCsound.csoundPerformKsmps(csound); + cnt += 1; + + if (typeof setTimeout === "function" && lastResult === 0 && cnt % kr === 0) { + // this is immediately executed, but allows events to be picked up + await new Promise((resolve) => setTimeout(resolve, 0)); + } + } + workerMessagePort.broadcastPlayState("renderEnded"); + }; diff --git a/wasm/browser/src/workers/vanilla.worker.js b/wasm/browser/src/workers/vanilla.worker.js index 8363999cc76..2d7a446e198 100644 --- a/wasm/browser/src/workers/vanilla.worker.js +++ b/wasm/browser/src/workers/vanilla.worker.js @@ -4,7 +4,7 @@ import MessagePortState from "../utils/message-port-state"; // import { initFS, getWorkerFs, syncWorkerFs } from "../filesystem/worker-fs"; import { logVANWorker as log } from "../logger"; import { RING_BUFFER_SIZE } from "../constants.js"; -import { handleCsoundStart, instantiateAudioPacket } from "./common.utils"; +import { handleCsoundStart, instantiateAudioPacket, renderFunction } from "./common.utils"; import libcsoundFactory from "../libcsound"; import loadWasm from "../module"; import { clearArray } from "../utils/clear-array"; @@ -226,25 +226,6 @@ const initRtMidiEventPort = ({ rtmidiPort }) => { return rtmidiPort; }; -const renderFunction = - ({ libraryCsound, workerMessagePort, wasi }) => - async ({ csound }) => { - const ksmps = libraryCsound.csoundGetKsmps(csound); - let lastResult = 0; - let cnt = 0; - - while (workerMessagePort.vanillaWorkerState === "renderStarted" && lastResult === 0) { - lastResult = libraryCsound.csoundPerformKsmps(csound); - cnt += 1; - - if (lastResult === 0 && cnt % ksmps === 0) { - // this is immediately executed, but allows events to be picked up - await new Promise((resolve) => setTimeout(resolve, 0)); - } - } - workerMessagePort.broadcastPlayState("renderEnded"); - }; - const initialize = async ({ audioInputPort, inputChannelCount, diff --git a/wasm/browser/src/workers/worklet.singlethread.worker.js b/wasm/browser/src/workers/worklet.singlethread.worker.js index a2d31e4ff2b..08f2211e41c 100644 --- a/wasm/browser/src/workers/worklet.singlethread.worker.js +++ b/wasm/browser/src/workers/worklet.singlethread.worker.js @@ -29,6 +29,7 @@ import loadWasm from "../module"; import { assoc, pipe } from "rambda/dist/rambda.esm.js"; import { clearArray } from "../utils/clear-array"; import { logSinglethreadWorkletWorker as log } from "../logger"; +import { renderFunction } from "./common.utils"; let libraryCsound; let combined; @@ -102,6 +103,7 @@ class WorkletSinglethreadWorker extends AudioWorkletProcessor { this.csound = libraryCsound.csoundCreate(0); this.result = 0; this.running = false; + this.isRendering = false; this.started = false; this.resetCsound(false); @@ -183,7 +185,7 @@ class WorkletSinglethreadWorker extends AudioWorkletProcessor { } process(inputs, outputs) { - if (this.isPaused || !this.csoundOutputBuffer || !this.running) { + if (!this.isRendering && (this.isPaused || !this.csoundOutputBuffer || !this.running)) { const output = outputs[0]; const bufferLength = output[0].length; for (let index = 0; index < bufferLength; index++) { @@ -308,6 +310,12 @@ class WorkletSinglethreadWorker extends AudioWorkletProcessor { return true; } + async isRequestingInput() { + const cs = this.csound; + const inputName = libraryCsound.csoundGetInputName(cs) || ""; + return inputName.includes("adc"); + } + async start() { let returnValueValue = -1; if (!this.started) { @@ -321,20 +329,43 @@ class WorkletSinglethreadWorker extends AudioWorkletProcessor { this.zerodBFS = libraryCsound.csoundGet0dBFS(cs); - this.csoundOutputBuffer = new Float64Array( - this.wasm.wasi.memory.buffer, - libraryCsound.csoundGetSpout(cs), - ksmps * this.nchnls, - ); - this.csoundInputBuffer = new Float64Array( - this.wasm.wasi.memory.buffer, - libraryCsound.csoundGetSpin(cs), - ksmps * this.nchnls_i, - ); returnValueValue = libraryCsound.csoundStart(cs); - log("csoundStart called with {} return val", returnValueValue)(); - this.started = true; - this.needsStartNotification = true; + + if (returnValueValue !== 0) { + return returnValueValue; + } + + const outputName = libraryCsound.csoundGetOutputName(cs) || "test.wav"; + const isExpectingRealtimeOutput = outputName.includes("dac"); + + if (isExpectingRealtimeOutput) { + this.csoundOutputBuffer = new Float64Array( + this.wasm.wasi.memory.buffer, + libraryCsound.csoundGetSpout(cs), + ksmps * this.nchnls, + ); + this.csoundInputBuffer = new Float64Array( + this.wasm.wasi.memory.buffer, + libraryCsound.csoundGetSpin(cs), + ksmps * this.nchnls_i, + ); + + log("csoundStart called with {} return val", returnValueValue)(); + this.started = true; + this.needsStartNotification = true; + } else { + const renderer = renderFunction({ + libraryCsound, + workerMessagePort: this.workerMessagePort, + wasi: this.wasi, + }); + this.isRendering = true; + this.workerMessagePort.broadcastPlayState("renderStarted"); + renderer({ csound: cs }).then(() => { + this.isRendering = false; + }); + return 0; + } } else { log("worklet was asked to start but it already has!")(); } diff --git a/wasm/browser/yarn.lock b/wasm/browser/yarn.lock index a226bb5ad35..73c97a46bd8 100644 --- a/wasm/browser/yarn.lock +++ b/wasm/browser/yarn.lock @@ -237,10 +237,10 @@ "@babel/helper-validator-identifier" "^7.16.7" to-fast-properties "^2.0.0" -"@csound/wasm-bin@6.18.0-beta1": - version "6.18.0-beta1" - resolved "https://registry.yarnpkg.com/@csound/wasm-bin/-/wasm-bin-6.18.0-beta1.tgz#3553756cff4a8109199ceeeeb1e653d4a66c2166" - integrity sha512-9Pws7rCY97hWq2oX28CwSP7jB8oi4I5sf0xh0wJEZIillQqe4MAVcRVi+NJfFe8XvpCJD6iCkZRtx2u5TfPTeg== +"@csound/wasm-bin@6.18.2": + version "6.18.2" + resolved "https://registry.yarnpkg.com/@csound/wasm-bin/-/wasm-bin-6.18.2.tgz#7c17f39d440ad14ed612e5ef554523ad474f17bf" + integrity sha512-HHiKdgjycNrcGYL0lv8AohNCszOdi2LvMcDAkMH/MhbLSPfD2omTQ6aERt1sv9npUGuVzdhCQr4OiuekRu7B5w== "@eslint/eslintrc@^1.2.1": version "1.2.1" diff --git a/wasm/package.json b/wasm/package.json index f113b0420fc..8a981585472 100644 --- a/wasm/package.json +++ b/wasm/package.json @@ -1,6 +1,6 @@ { "name": "@csound/wasm-bin", - "version": "6.18.0-beta1", + "version": "6.18.2", "description": "A package containing csound wasm binaries which can be bundled into other csound packages 📂", "main": "./lib/csound.wasm", "files": ["src", "lib"],