gogins / csound-extended

Extensions for Csound including algorithmic composition, Android app, and WebAssembly.
GNU Lesser General Public License v2.1
40 stars 1 forks source link

CsoundWebAudio examples not working properly #126

Closed gogins closed 4 years ago

gogins commented 4 years ago
gogins commented 4 years ago

If I break at Start and then resume, the AudioWorklet works, though it's buzzy and noisy. If I don't break, it doesn't work. That suggests Start returns before things are actually working.

gogins commented 4 years ago

Trying to reorder connecting and processing in AudioWorklet.

gogins commented 4 years ago

Things to look at:

gogins commented 4 years ago

This https://developers.google.com/web/updates/2018/06/audio-worklet-design-pattern provides context for these issues. This, frankly, is a real mess. The basic recommendation is to run CsoundEmbind in a Worker not in the Processor, and use the Processor only for synchronously and efficiently transferring audio and control data using shared memory rather than a MessagePort. It's not clear to me that this would provide any substantial advantage over the ScriptProcessorNode.

However see this: https://github.com/hlolli/csound-wasm. It follows the whole pattern above and exposes most of the "C" Csound API. Not sure about C++.

Actually, now that it's working with ALSA, it's clear that the AudioWorklet is indeed significantly more performant then ScriptProcessorNode.

gogins commented 4 years ago

OK, things are getting clearer. I will need to make at least Csound.CompileCsdText, Csound.Stop, and Csound.Cleanup behave as though they are synchronous so that the sequencing always works.

I will not make the module loading be synchronous because the page might freeze.

The logic in csound_loader.js is very tricky. It was wrong. I think I have mostly fixed it.

CsoundAudioNode is now rendering. But it is still buzzy and choppy.

gogins commented 4 years ago

Profiler total times over several seconds of Xanadu:

This is hasty, but the implication is that the choppiness is not down to Csound or even to Embind, but to the AudioWorklet infrastructure. As suggested by the worklet design pattern, this may be down to using MessagePort instead of shared memory.

That being so, the only advantage I see to AudioWorklet at this time is that ScriptProcessorNode is deprecated. After reviewing the documents, and after my experience with other implementations of Csound using AudioWorklet which are all just as choppy as mine, I am going to get AudioWorklet to work with calls that need to be serialized being made synchronous, and let ScriptProcessorNode be preferred to AudioWorklet.

gogins commented 4 years ago

Try:

gogins commented 4 years ago

See: https://stackoverflow.com/questions/26150232/resolve-javascript-promise-outside-function-scope.

gogins commented 4 years ago

Finally got Csound.CompileCsdText to act as though it is synchronous. This was not easy to understand. First the event handler for the Play button has to be async. Then the CsoundAudioNode.CompileCsdText has to be async with a Promise that assigns its resolve callback to CsoundAudioNode.resolveCompileCsdText.

I'm not sure how to make this consistent with the other JavaScript interfaces to Csound.

gogins commented 4 years ago

Using emscripten_console_log does not help.

gogins commented 4 years ago

Regarding the double run to build... first run:

/usr/bin/cmake -E cmake_progress_start /home/mkg/csound-extended/WebAssembly/cmask/CMakeFiles 0
rm: cannot remove 'CMakeCache.txt': No such file or directory
Packaging some resources...
python: can't open file '/tools/file_packager.py': [Errno 2] No such file or directory
Configuring csound-static...
configure: cmake -DCMAKE_VERBOSE_MAKEFILE=1 -DBUILD_PLUGINS_DIR=plugins -DUSE_COMPILER_OPTIMIZATIONS=0 -DWASM=1 -DINIT_STATIC_MODULES=1 -DUSE_DOUBLE=NO -DBUILD_MULTI_CORE=0 -DBUILD_JACK_OPCODES=0 -DEMSCRIPTEN=1 -DCMAKE_TOOLCHAIN_FILE=/cmake/Modules/Platform/Emscripten.cmake -DCMAKE_MODULE_PATH=/cmake -DCMAKE_BUILD_TYPE=Release -GUnix Makefiles -DHAVE_BIG_ENDIAN=0 -DCMAKE_16BIT_TYPE=unsigned short -DHAVE_STRTOD_L=0 -DBUILD_STATIC_LIBRARY=YES -DHAVE_ATOMIC_BUILTIN=0 -DHAVE_SPRINTF_L=NO -DUSE_GETTEXT=NO -DLIBSNDFILE_LIBRARY=../deps/libsndfile.a -DSNDFILE_H_PATH=../deps/libsndfile-1.0.25/src ../../dependencies/csound -DCMAKE_CROSSCOMPILING_EMULATOR="/home/mkg/emsdk/node/12.9.1_64bit/bin/node"
CMake Error at /usr/share/cmake-3.16/Modules/CMakeDetermineSystem.cmake:99 (message):
  Could not find toolchain file: /cmake/Modules/Platform/Emscripten.cmake
Call Stack (most recent call first):
  CMakeLists.txt:6 (project)

CMake Error: CMake was unable to find a build program corresponding to "Unix Makefiles".  CMAKE_MAKE_PROGRAM is not set.  You probably need to select a different build tool.
CMake Error: CMAKE_C_COMPILER not set, after EnableLanguage
CMake Error: CMAKE_CXX_COMPILER not set, after EnableLanguage
-- Configuring incomplete, errors occurred!
Making csound-static...
make: make csound-static -j6
make: *** No rule to make target 'csound-static'.  Stop.

Second run:

Packaging some resources...
Remember to build the main file with  -s FORCE_FILESYSTEM=1  so that it includes support for loading this file package
Configuring csound-static...
configure: cmake -DCMAKE_VERBOSE_MAKEFILE=1 -DBUILD_PLUGINS_DIR=plugins -DUSE_COMPILER_OPTIMIZATIONS=0 -DWASM=1 -DINIT_STATIC_MODULES=1 -DUSE_DOUBLE=NO -DBUILD_MULTI_CORE=0 -DBUILD_JACK_OPCODES=0 -DEMSCRIPTEN=1 -DCMAKE_TOOLCHAIN_FILE=/home/mkg/emsdk/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake -DCMAKE_MODULE_PATH=/home/mkg/emsdk/upstream/emscripten/cmake -DCMAKE_BUILD_TYPE=Release -GUnix Makefiles -DHAVE_BIG_ENDIAN=0 -DCMAKE_16BIT_TYPE=unsigned short -DHAVE_STRTOD_L=0 -DBUILD_STATIC_LIBRARY=YES -DHAVE_ATOMIC_BUILTIN=0 -DHAVE_SPRINTF_L=NO -DUSE_GETTEXT=NO -DLIBSNDFILE_LIBRARY=../deps/libsndfile.a -DSNDFILE_H_PATH=../deps/libsndfile-1.0.25/src ../../dependencies/csound -DCMAKE_CROSSCOMPILING_EMULATOR="/home/mkg/emsdk/node/12.9.1_64bit/bin/node"

and configuration then succeeds with many warnings.

gogins commented 4 years ago

Looks like EMSCRIPTEN_ROOT is undefined or empty on the first run. Fixed by setting up the Emscripten SDK environment in a more consistent manner.

gogins commented 4 years ago

Configure the build to ignore or fix certain warnings:

gogins commented 4 years ago

I will try an actual callback for the Csound messages. Emscripten supports this with a C API. See:

https://stackoverflow.com/questions/20421002/passing-a-c-function-to-a-javascript-function-in-emscripten https://emscripten.org/docs/porting/connecting_cpp_and_javascript/Interacting-with-code.html#interacting-with-code-call-function-pointers-from-c

gogins commented 4 years ago

An actual callback is easy with emscripten::val as a parameter in the CsoundEmbind::SetMessageCallback function.

But again, it works only on the first run. No, now it works on all runs. I used csoundSetDefaultMessageCallback instead of csoundSetMessageCallback.

Trying to pass the actual text message now from Csound::Embind....

gogins commented 4 years ago
Uncaught TypeError: Illegal invocation
    at shim (http://localhost:5103/CsoundAudioProcessor.js:20248:13)
    at __emval_call (http://localhost:5103/CsoundAudioProcessor.js:8851:23)
    at wasm-function[210]:0x7f03f

Is this caused by an invalid function, or by an invalid parameter, or both? The message parameter is OK, the function object is invalid. I will try variations on the function object.

Works now. Workaround: create global var for the MessagePort that will be in scope of the callback shim. But this limits the instances of Csound on a page to 1.

gogins commented 4 years ago

I have looked at the design pattern HeapAudioBuffer and inspected the current AudioBuffer in the debugger. I do not understand the motivation for the HeapAudioBuffer unless it is simply to skip range checking or to adapt to changes in buffer shape (not an issue here).

Wait, I do get it. Elementwise access to the buffers is thus pushed down into C code compiled to WASM. However, this should not be a problem for my code because spin and spout are obtained before process is ever called, and these are just Float32Array allocated from the WASM heap like the buffers in the HeapAudioBuffer.

In other words, instead of pushing heap buffers down into C code before calling process, I am pulling heap buffers up from C code before calling process. Should be a wash.

gogins commented 4 years ago

Regarding the separate Worker pattern, I do not see any reason to do this unless it isolates the audio processing from interruption by other things happening in the browser. But that may be the motivation. I think I will look again at @hlolli's code again and see if it uses this pattern. If so, it confers no significant advantage.

gogins commented 4 years ago

Where is this coming from? audio buffered in 256 sample-frame blocks. Maybe this is an issue. I would have thought this would be 128.

This message comes from Csound, and probably should not be an issue, because we are directly using spin and spout. However, I have set this size to 128.

gogins commented 4 years ago

Closing the processor's MessagePort when performance begins does not reduce audio problems.

gogins commented 4 years ago

Profiled again. Auto-scrolling the message window is a performance load. I will try not using a message callback at all.

This does not help.

gogins commented 4 years ago

Nothing learned from the profile, as it is not easy to interpret the data.

I tried Faust's online Faust compiler and although it's not as bad as our stuff, it is still bad. There is always a repetitive glitch.

Looking through this, and I will see if I can find a more current meeting: https://www.w3.org/2019/09/16-audio-minutes.html

I will look through issues here, both V1 and V2: https://github.com/WebAudio/web-audio-api.

gogins commented 4 years ago

This issue https://github.com/WebAudio/web-audio-api/issues/2008 discusses the shared memory design pattern, and implies that garbage collection in the rendering task is the cause of my woes. The expert says "never ever create objects [he means JS objects] on this rendering thread."

Because Csound itself creates objects during rendering, this may not be possible.

It is clear that at least as of July 2019, the official WebAudio spec for AudioWorklet was not complete.

I will try my player.html in Firefox. Oops, doesn't run at all, can't create AudioWorklet.

gogins commented 4 years ago

The experts expect that the worker will listen on the MessagePort just for a start message, then use a SharedArrayBuffer to manage all further communications with the worklet.

gogins commented 4 years ago

csound_loader.js (embedded in player.html doesn't work on Android, as it presupposes the WebAssembly code has been loaded. I can try just loading these files, that should work, but better if they're not required. Can .js files be loaded programmatically? Yes, but only asynchronously.

gogins commented 4 years ago

CsoundAudioNode just started working perfectly, but I am not sure why. I had assumed it would not work and changed several things at the same time. Check:

Indeed, I am now using ALSA without PulseAudio.

The sampleRate mismatch is a red herring. The fix is simply using just ALSA instead of PulseAudio.

The implication also is that for Csound, it is not necessary to used the shared memory IPC mechanism and the MessagePort appears to be working fine.

gogins commented 4 years ago

If Csound completes its performance, it will not restart.

Changed the order of calls. Did not help.

Changed AudioWorkletProcessor.process to always return true unless buffer shapes don't match. That fixed the restart problem. Returning false takes the node out of the signal flow graph, i.e. permanently turns off Csound.

gogins commented 4 years ago

I can't run csound.node in NW.js without PulseAudio. I can run Csound on the command line or in WebAssembly without PulseAudio. This probably is the reason: https://github.com/nwjs/nw.js/issues/3573.

I changed /etc/pulse/daemon.conf to use float samples and a sampling rate of 48000 Hz. This has enabled NW.js to run Csound just fine with PulseAudio. But with PulseAudio, CsoundAudioNode is still choppy and buzzy, though I think not as much as before.