diff --git a/.github/workflows/plugins_build_7.yml b/.github/workflows/plugins_build_7.yml index 4d2ca45..b86907e 100644 --- a/.github/workflows/plugins_build_7.yml +++ b/.github/workflows/plugins_build_7.yml @@ -20,6 +20,7 @@ on: env: CSOUND_VERSION: 7.0.0-beta.18 + FAUST_VERSION: 2.88.0 VCPKG_BINARY_SOURCES: "clear;files,${{ github.workspace }}/vcpkg-cache,readwrite" jobs: @@ -71,7 +72,7 @@ jobs: libsndfile1-dev libasound2-dev libportaudio2 libportmidi-dev \ libeigen3-dev libfluidsynth-dev libpng-dev \ libwebsockets-dev libuv1-dev libhdf5-dev \ - ocl-icd-opencl-dev \ + ocl-icd-opencl-dev libtinfo-dev \ flex bison - name: Cache Csound install @@ -94,16 +95,45 @@ jobs: cmake --build "$RUNNER_TEMP/csound-build" --parallel "$(nproc)" cmake --install "$RUNNER_TEMP/csound-build" + - name: Cache Faust + id: cache-faust + uses: actions/cache@v4 + with: + path: ~/faust + key: faust-${{ runner.os }}-${{ runner.arch }}-${{ env.FAUST_VERSION }} + + - name: Install Faust + if: steps.cache-faust.outputs.cache-hit != 'true' + run: | + mkdir -p "$HOME/faust" + curl -sL -o "$RUNNER_TEMP/libfaust.zip" \ + "https://github.com/grame-cncm/faust/releases/download/$FAUST_VERSION/libfaust-ubuntu-x86_64.zip" + curl -sL -o "$RUNNER_TEMP/faust-src.tar.gz" \ + "https://github.com/grame-cncm/faust/releases/download/$FAUST_VERSION/faust-$FAUST_VERSION.tar.gz" + unzip -q "$RUNNER_TEMP/libfaust.zip" -d "$HOME/faust" + tar xzf "$RUNNER_TEMP/faust-src.tar.gz" -C "$RUNNER_TEMP" \ + "faust-$FAUST_VERSION/architecture" "faust-$FAUST_VERSION/libraries" + mv "$RUNNER_TEMP/faust-$FAUST_VERSION/architecture" "$HOME/faust/include" + mv "$RUNNER_TEMP/faust-$FAUST_VERSION/libraries" "$HOME/faust/stdlib" + rm -f "$RUNNER_TEMP/libfaust.zip" "$RUNNER_TEMP/faust-src.tar.gz" + - name: Configure plugin build run: | echo "CSOUND_INCLUDE_DIR_HINT=$HOME/csound-install/include/csound" >> "$GITHUB_ENV" echo "CSOUND_LIBRARY_DIR_HINT=$HOME/csound-install/lib" >> "$GITHUB_ENV" + echo "FAUST_INCLUDE_DIR_HINT=$HOME/faust/include" >> "$GITHUB_ENV" + echo "FAUST_LIB_DIR_HINT=$HOME/faust/lib" >> "$GITHUB_ENV" + echo "FAUST_STDLIB_DIR_HINT=$HOME/faust/stdlib" >> "$GITHUB_ENV" - name: Build plugins run: | cmake -S 7.x -B build -DCMAKE_BUILD_TYPE=Release \ + -DFAUST_STATIC=ON \ -DCSOUND_INCLUDE_DIR_HINT="$CSOUND_INCLUDE_DIR_HINT" \ - -DCSOUND_LIBRARY_DIR_HINT="$CSOUND_LIBRARY_DIR_HINT" + -DCSOUND_LIBRARY_DIR_HINT="$CSOUND_LIBRARY_DIR_HINT" \ + -DFAUST_INCLUDE_DIR_HINT="$FAUST_INCLUDE_DIR_HINT" \ + -DFAUST_LIB_DIR_HINT="$FAUST_LIB_DIR_HINT" \ + -DFAUST_STDLIB_DIR_HINT="$FAUST_STDLIB_DIR_HINT" cmake --build build --parallel "$(nproc)" - name: Stage plugins @@ -118,8 +148,11 @@ jobs: build/src/py/*.so \ build/src/OpenCL/*.so \ build/src/stk/*.so package/ + cp build/src/faustcsound/*.so package/ mkdir -p package/stk/rawwaves cp 7.x/src/stk/rawwaves/*.raw package/stk/rawwaves/ + mkdir -p package/faust + cp -R "$HOME/faust/stdlib/." package/faust/ - name: Upload plugins uses: actions/upload-artifact@v4 @@ -192,6 +225,33 @@ jobs: cmake --build "$RUNNER_TEMP/csound-build" --parallel "$(sysctl -n hw.ncpu)" cmake --install "$RUNNER_TEMP/csound-build" + - name: Cache Faust + id: cache-faust + uses: actions/cache@v4 + with: + path: ~/faust + key: faust-${{ runner.os }}-${{ runner.arch }}-${{ env.FAUST_VERSION }} + + - name: Install Faust + if: steps.cache-faust.outputs.cache-hit != 'true' + run: | + if [ "$(uname -m)" = "arm64" ]; then + asset="Faust-$FAUST_VERSION-arm64.dmg" + else + asset="Faust-$FAUST_VERSION-x64.dmg" + fi + curl -sL -o "$RUNNER_TEMP/faust.dmg" \ + "https://github.com/grame-cncm/faust/releases/download/$FAUST_VERSION/$asset" + mkdir -p "$HOME/faust" "$RUNNER_TEMP/faust_dmg" + hdiutil attach "$RUNNER_TEMP/faust.dmg" \ + -mountpoint "$RUNNER_TEMP/faust_dmg" -nobrowse -quiet + root=$(dirname "$(find "$RUNNER_TEMP/faust_dmg" -maxdepth 2 -type d -name include | head -1)") + cp -R "$root/include" "$HOME/faust/include" + cp -R "$root/lib" "$HOME/faust/lib" + cp -R "$root/share/faust" "$HOME/faust/stdlib" + hdiutil detach "$RUNNER_TEMP/faust_dmg" -quiet + rm -f "$RUNNER_TEMP/faust.dmg" + - name: Configure plugin build run: | framework="$HOME/csound-install/Frameworks/CsoundLib64.framework" @@ -202,14 +262,21 @@ jobs: echo "CSOUND_INCLUDE_DIR_HINT=$headers" >> "$GITHUB_ENV" echo "CSOUND_FRAMEWORK_DIR_HINT=$HOME/csound-install/Frameworks" >> "$GITHUB_ENV" echo "CSOUND_LIBRARY_DIR_HINT=$HOME/csound-install/Frameworks" >> "$GITHUB_ENV" + echo "FAUST_INCLUDE_DIR_HINT=$HOME/faust/include" >> "$GITHUB_ENV" + echo "FAUST_LIB_DIR_HINT=$HOME/faust/lib" >> "$GITHUB_ENV" + echo "FAUST_STDLIB_DIR_HINT=$HOME/faust/stdlib" >> "$GITHUB_ENV" - name: Build plugins run: | cmake -S 7.x -B build -DCMAKE_BUILD_TYPE=Release \ -DUSE_VCPKG=ON \ + -DFAUST_STATIC=ON \ -DCSOUND_INCLUDE_DIR_HINT="$CSOUND_INCLUDE_DIR_HINT" \ -DCSOUND_FRAMEWORK_DIR_HINT="$CSOUND_FRAMEWORK_DIR_HINT" \ - -DCSOUND_LIBRARY_DIR_HINT="$CSOUND_LIBRARY_DIR_HINT" + -DCSOUND_LIBRARY_DIR_HINT="$CSOUND_LIBRARY_DIR_HINT" \ + -DFAUST_INCLUDE_DIR_HINT="$FAUST_INCLUDE_DIR_HINT" \ + -DFAUST_LIB_DIR_HINT="$FAUST_LIB_DIR_HINT" \ + -DFAUST_STDLIB_DIR_HINT="$FAUST_STDLIB_DIR_HINT" cmake --build build --parallel "$(sysctl -n hw.ncpu)" - name: Stage plugins @@ -224,8 +291,11 @@ jobs: build/src/py/*.dylib \ build/src/OpenCL/*.dylib \ build/src/stk/*.dylib package/ + cp build/src/faustcsound/*.dylib package/ mkdir -p package/stk/rawwaves cp 7.x/src/stk/rawwaves/*.raw package/stk/rawwaves/ + mkdir -p package/faust + cp -R "$HOME/faust/stdlib/." package/faust/ - name: Upload plugins uses: actions/upload-artifact@v4 @@ -294,6 +364,23 @@ jobs: cmake --build csound-build --config Release --parallel cmake --install csound-build --config Release + - name: Cache Faust + id: cache-faust + uses: actions/cache@v4 + with: + path: ~/faust + key: faust-${{ runner.os }}-${{ runner.arch }}-${{ env.FAUST_VERSION }} + + - name: Install Faust + if: steps.cache-faust.outputs.cache-hit != 'true' + run: | + $faustDir = Join-Path $env:USERPROFILE 'faust' + New-Item -ItemType Directory -Force -Path $faustDir | Out-Null + curl.exe -sL -o "$env:RUNNER_TEMP\faust.exe" ` + "https://github.com/grame-cncm/faust/releases/download/$env:FAUST_VERSION/Faust-$env:FAUST_VERSION-win64.exe" + & 'C:\Program Files\7-Zip\7z.exe' x "$env:RUNNER_TEMP\faust.exe" "-o$faustDir" -y | Out-Null + Remove-Item "$env:RUNNER_TEMP\faust.exe" + - name: Configure plugin build run: | $install = Join-Path $env:RUNNER_TEMP 'csound-install' @@ -305,13 +392,21 @@ jobs: if (-not $lib) { throw "csound64.lib not found under $install" } "CSOUND_INCLUDE_DIR_HINT=$($header.DirectoryName)" >> $env:GITHUB_ENV "CSOUND_LIBRARY_DIR_HINT=$($lib.DirectoryName)" >> $env:GITHUB_ENV + $faustDir = Join-Path $env:USERPROFILE 'faust' + "FAUST_INCLUDE_DIR_HINT=$faustDir\include" >> $env:GITHUB_ENV + "FAUST_LIB_DIR_HINT=$faustDir\lib" >> $env:GITHUB_ENV + "FAUST_STDLIB_DIR_HINT=$faustDir\share\faust" >> $env:GITHUB_ENV - name: Build plugins run: | cmake -S 7.x -B build -A x64 ` -DUSE_VCPKG=ON ` + -DFAUST_STATIC=ON ` -DCSOUND_INCLUDE_DIR_HINT="$env:CSOUND_INCLUDE_DIR_HINT" ` - -DCSOUND_LIBRARY_DIR_HINT="$env:CSOUND_LIBRARY_DIR_HINT" + -DCSOUND_LIBRARY_DIR_HINT="$env:CSOUND_LIBRARY_DIR_HINT" ` + -DFAUST_INCLUDE_DIR_HINT="$env:FAUST_INCLUDE_DIR_HINT" ` + -DFAUST_LIB_DIR_HINT="$env:FAUST_LIB_DIR_HINT" ` + -DFAUST_STDLIB_DIR_HINT="$env:FAUST_STDLIB_DIR_HINT" cmake --build build --config Release --parallel - name: Stage plugins @@ -328,6 +423,9 @@ jobs: $rawwaves = Join-Path $package 'stk/rawwaves' New-Item -ItemType Directory -Force -Path $rawwaves | Out-Null Copy-Item '7.x\src\stk\rawwaves\*.raw' -Destination $rawwaves + $faustOut = Join-Path $package 'faust' + New-Item -ItemType Directory -Force -Path $faustOut | Out-Null + Copy-Item "$env:USERPROFILE\faust\share\faust\*" -Destination $faustOut -Recurse - name: Upload plugins uses: actions/upload-artifact@v4 diff --git a/7.x/CMakeLists.txt b/7.x/CMakeLists.txt index ed75642..91b2f6a 100644 --- a/7.x/CMakeLists.txt +++ b/7.x/CMakeLists.txt @@ -314,3 +314,4 @@ add_subdirectory(./src/p5glove) add_subdirectory(./src/CUDA) add_subdirectory(./src/OpenCL) add_subdirectory(./src/stk) +add_subdirectory(./src/faustcsound) diff --git a/7.x/cmake/Modules/FindFAUST.cmake b/7.x/cmake/Modules/FindFAUST.cmake index d1d329e..a14dc99 100644 --- a/7.x/cmake/Modules/FindFAUST.cmake +++ b/7.x/cmake/Modules/FindFAUST.cmake @@ -1,6 +1,11 @@ # Find Faust2 include(FindPackageHandleStandardArgs) +# When ON, link against the self-contained static library that bundles LLVM +# (libfaustwithllvm on Linux/macOS, libfaustwithllvm.lib on Windows). This +# produces a plugin with no runtime dependency on libfaust. +option(FAUST_STATIC "Link against a static libfaust that bundles LLVM" OFF) + find_path(FAUST_INCLUDE_DIR faust/dsp/llvm-dsp.h HINTS C:/Program Files/Faust/include/ @@ -10,49 +15,91 @@ find_path(FAUST_INCLUDE_DIR faust/dsp/llvm-dsp.h "${FAUST_INCLUDE_DIR_HINT}" ) -find_library(FAUST_LIBRARY - NAMES - libfaust.so - libfaust.dylib - faust.dll - faust - libfaust +if(FAUST_STATIC) + find_library(FAUST_LIBRARY + NAMES + libfaustwithllvm.lib + libfaustwithllvm.a + faustwithllvm + HINTS + "${FAUST_LIB_DIR_HINT}" + ) +else() + find_library(FAUST_LIBRARY + NAMES + libfaust.so + libfaust.dylib + faust.dll + faust + libfaust + HINTS + "${FAUST_LIB_DIR_HINT}" + ) +endif() + +# Locate the Faust standard library (containing stdfaust.lib). It is shipped +# as share/faust in the binary packages and as libraries/ in the source tree. +find_path(FAUST_STDLIB_DIR stdfaust.lib HINTS - "${FAUST_LIB_DIR_HINT}" + "${FAUST_STDLIB_DIR_HINT}" + "${FAUST_LIB_DIR_HINT}/../share/faust" + "${FAUST_INCLUDE_DIR_HINT}/../share/faust" + "${FAUST_LIB_DIR_HINT}/../libraries" + "${FAUST_INCLUDE_DIR_HINT}/../libraries" + "${FAUST_INCLUDE_DIR_HINT}/../share/faust" ) find_package_handle_standard_args(FAUST FAUST_INCLUDE_DIR FAUST_LIBRARY) if(FAUST_FOUND) - set(FAUST_LIBRARIES ${FAUST_LIBRARY}) set(FAUST_INCLUDE_DIRS ${FAUST_INCLUDE_DIR}) - if("${FAUST_LIBRARY}" MATCHES ".*\\.a") - # This is a static build of faust, hence - # we have to add all the LLVM flags... + if(FAUST_STATIC) + # libfaustwithllvm bundles LLVM, so only platform libraries are needed. + if(WIN32) + set(FAUST_LIBRARIES ${FAUST_LIBRARY} + ws2_32 crypt32 dbghelp ole32 shell32 psapi bcrypt + version uuid user32 advapi32 ntdll) + elseif(APPLE) + set(FAUST_LIBRARIES ${FAUST_LIBRARY} z) + else() + set(_faust_syslibs dl pthread z) + find_library(FAUST_TINFO_LIBRARY NAMES tinfo ncurses) + if(FAUST_TINFO_LIBRARY) + list(APPEND _faust_syslibs ${FAUST_TINFO_LIBRARY}) + endif() + set(FAUST_LIBRARIES ${FAUST_LIBRARY} ${_faust_syslibs}) + endif() + else() + set(FAUST_LIBRARIES ${FAUST_LIBRARY}) - find_program(LLVM_CONFIG llvm-config - HINTS - /usr/bin - /usr/local/bin - /usr/local/opt/llvm/bin - ) + if("${FAUST_LIBRARY}" MATCHES ".*\\.a") + # This is a static build of faust without LLVM, hence + # we have to add all the LLVM flags... - if(NOT LLVM_CONFIG) - message(WARNING "Using a static Faust library requires LLVM tooling to be present in the path.") - UNSET(FAUST_FOUND) - else() - exec_program(${LLVM_CONFIG} ARGS --includedir OUTPUT_VARIABLE LLVM_DIR) - exec_program(${LLVM_CONFIG} ARGS --libs OUTPUT_VARIABLE LLVM_LIBS) - exec_program(${LLVM_CONFIG} ARGS --version OUTPUT_VARIABLE LLVM_VERSION) - exec_program(${LLVM_CONFIG} ARGS --ldflags OUTPUT_VARIABLE LLVM_LDFLAGS) + find_program(LLVM_CONFIG llvm-config + HINTS + /usr/bin + /usr/local/bin + /usr/local/opt/llvm/bin + ) - set(LLVM_VERSION LLVM_${LLVM_VERSION_MAJOR}${LLVM_VERSION_MINOR}) + if(NOT LLVM_CONFIG) + message(WARNING "Using a static Faust library requires LLVM tooling to be present in the path.") + UNSET(FAUST_FOUND) + else() + exec_program(${LLVM_CONFIG} ARGS --includedir OUTPUT_VARIABLE LLVM_DIR) + exec_program(${LLVM_CONFIG} ARGS --libs OUTPUT_VARIABLE LLVM_LIBS) + exec_program(${LLVM_CONFIG} ARGS --version OUTPUT_VARIABLE LLVM_VERSION) + exec_program(${LLVM_CONFIG} ARGS --ldflags OUTPUT_VARIABLE LLVM_LDFLAGS) - find_package(OpenSSL REQUIRED) - set(FAUST_LIBRARIES ${FAUST_LIBRARIES} dl ${OPENSSL_LIBRARIES} ncurses z ${LLVM_LDFLAGS} ${LLVM_LIBS} ) - endif() - endif() + set(LLVM_VERSION LLVM_${LLVM_VERSION_MAJOR}${LLVM_VERSION_MINOR}) + + find_package(OpenSSL REQUIRED) + set(FAUST_LIBRARIES ${FAUST_LIBRARIES} dl ${OPENSSL_LIBRARIES} ncurses z ${LLVM_LDFLAGS} ${LLVM_LIBS} ) + endif() + endif() + endif() else() set(FAUST_LIBRARIES) set(FAUST_INCLUDE_DIRS) diff --git a/7.x/src/faustcsound/CMakeLists.txt b/7.x/src/faustcsound/CMakeLists.txt new file mode 100644 index 0000000..70b8e07 --- /dev/null +++ b/7.x/src/faustcsound/CMakeLists.txt @@ -0,0 +1,57 @@ +# Build faustcsound opcodes +option(BUILD_FAUST_OPCODES "Build the Faust opcodes" ON) + +find_package(FAUST) +check_deps(BUILD_FAUST_OPCODES FAUST_FOUND) + +if(BUILD_FAUST_OPCODES AND FAUST_FOUND) + message(STATUS "Faust library: ${FAUST_LIBRARY}") + + make_plugin(faustcsound faustgen.cpp) + + target_include_directories(faustcsound PRIVATE + ${CSOUND_INCLUDE_DIRS} + ${FAUST_INCLUDE_DIR}) + target_compile_definitions(faustcsound PRIVATE "-DFAUSTFLOAT=cs_float") + + if(PTHREAD_LIBRARY) + target_compile_definitions(faustcsound PRIVATE "-DHAVE_PTHREAD") + endif() + + target_link_libraries(faustcsound + ${FAUST_LIBRARIES} + ${PTHREAD_LIBRARY} + ${CMAKE_DL_LIBS}) + + if(APPLE) + set_target_properties(faustcsound PROPERTIES + INSTALL_RPATH_USE_LINK_PATH TRUE + ) + endif() + + if(FAUST_STATIC) + # libfaustwithllvm bundles LLVM; without this the plugin would export + # the hundreds of thousands of LLVM/Faust symbols from the archive. + if(APPLE) + set_target_properties(faustcsound PROPERTIES LINK_FLAGS + "-Wl,-exported_symbols_list,${CMAKE_CURRENT_SOURCE_DIR}/faustcsound.exports") + elseif(UNIX) + set_target_properties(faustcsound PROPERTIES LINK_FLAGS + "-Wl,--exclude-libs,ALL") + endif() + endif() + + # Install the Faust standard library next to the plugin and point the + # plugin at it, so programs using import("stdfaust.lib") work without a + # separate Faust installation. + if(FAUST_STDLIB_DIR) + message(STATUS "Faust standard library: ${FAUST_STDLIB_DIR}") + install(DIRECTORY "${FAUST_STDLIB_DIR}/" + DESTINATION "${PLUGIN_INSTALL_DIR}/faust") + target_compile_definitions(faustcsound PRIVATE + "DEFAULT_FAUST_LIB_PATH=\"${PLUGIN_INSTALL_DIR}/faust\"") + else() + message(WARNING "Faust standard library (stdfaust.lib) not found; " + "set FAUST_STDLIB_DIR_HINT to enable import(\"stdfaust.lib\")") + endif() +endif() diff --git a/7.x/src/faustcsound/README.md b/7.x/src/faustcsound/README.md new file mode 100644 index 0000000..aef59eb --- /dev/null +++ b/7.x/src/faustcsound/README.md @@ -0,0 +1,72 @@ +Faust opcodes +======= + +These opcodes embed the [Faust](https://faust.grame.fr/) compiler and DSP +runtime into Csound: + +* `faustgen` - compiles and runs a single Faust program +* `faustcompile` - compiles a Faust program, returning a handle +* `faustaudio` - creates a DSP instance from a compiled program +* `faustdsp` - creates a DSP instance (no audio outputs) from a compiled program +* `faustplay` - runs an existing DSP instance with explicit inputs/outputs +* `faustctl` - sets the value of a control of a DSP instance + +Building +-------- + +To build the Faust opcodes you need the Faust library (headers and +`libfaust`), which can be downloaded as a binary package from + or built from source +(which also requires libcrypto and LLVM). + +When using the pre-built binary packages the following CMake variables +are important: + +* `FAUST_INCLUDE_DIR_HINT` - the `include` directory of the package + (contains `faust/dsp/llvm-dsp.h`). +* `FAUST_LIB_DIR_HINT` - the `lib` directory of the package. +* `FAUST_STDLIB_DIR_HINT` - the directory containing `stdfaust.lib` + (`share/faust` in the binary packages, `libraries` in the source tree). +* `FAUST_STATIC` - when `ON`, link against the self-contained + `libfaustwithllvm` archive, which bundles LLVM so the plugin has no + runtime dependency on `libfaust`. This produces a much larger plugin. + +For example, if Faust 2.88.0 is placed in the top-level user directory: + +``` +cmake .. -DFAUST_INCLUDE_DIR_HINT=$HOME/faust-2.88.0/include \ + -DFAUST_LIB_DIR_HINT=$HOME/faust-2.88.0/lib \ + -DFAUST_STDLIB_DIR_HINT=$HOME/faust-2.88.0/share/faust \ + -DFAUST_STATIC=ON +``` + +Without `FAUST_STATIC` the plugin is linked to `libfaust` dynamically and +`libfaust` must be locatable at runtime (the build sets an rpath to the +library directory by default). With `FAUST_STATIC=ON` the LLVM/Faust +symbols are linked in and hidden, so only the opcode entry points are +exported. + +The Faust standard library is installed next to the plugin (in a `faust` +subdirectory of the plugin directory) and the plugin looks for it there, +so `import("stdfaust.lib")` works without a separate Faust installation. +Set `FAUST_LIB_PATH` to override this, e.g. + +``` +export FAUST_LIB_PATH=$HOME/faust-2.88.0/share/faust +``` + +Faust programs that use the standard library (e.g. `os.osc`) need +`import("stdfaust.lib");`. + +Notes on the Csound 7 port +-------------------------- + +* `MYFLT` was replaced by `cs_float` and `FAUSTFLOAT` is defined to + `cs_float`, so the plugin always matches the Csound sample type. +* Opcode cleanup uses the `OENTRY` `deinit` field instead of the removed + `RegisterDeinitCallback`; `faustcompile` still uses a reset callback so + that compiled factories outlive the compiling instrument. +* `CreateThread2` was removed from the Csound API, so the `istacksize` + argument of `faustcompile` is now accepted but ignored. `faustgen` + compiles synchronously on the calling (init) thread, and asynchronous + `faustcompile` uses the default thread stack size. diff --git a/7.x/src/faustcsound/faustcsound.exports b/7.x/src/faustcsound/faustcsound.exports new file mode 100644 index 0000000..663cc21 --- /dev/null +++ b/7.x/src/faustcsound/faustcsound.exports @@ -0,0 +1,4 @@ +_csoundModuleCreate +_csoundModuleInit +_csoundModuleDestroy +_csoundModuleInfo diff --git a/7.x/src/faustcsound/faustgen.cpp b/7.x/src/faustcsound/faustgen.cpp new file mode 100644 index 0000000..7d85066 --- /dev/null +++ b/7.x/src/faustcsound/faustgen.cpp @@ -0,0 +1,1121 @@ +/* faustgen.cpp + + Copyright (c) Victor Lazzarini, 2013 + + This file is part of Csound. + + The Csound Library is free software; you can redistribute it + and/or modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + Csound is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with Csound; if not, write to the Free Software + Foundation, Inc., 31 Milk Street, #960789, Boston, MA, 02196, USA + + Faust Csound opcodes + + These four opcodes allow Faust to be embedded in Csound code: + + faustcompile: compiles a Faust program + faustaudio: creates a DSP instance from a compiled Faust program + (any number of instances from a single compiled program are + allowed) + faustctl: sets the value of a given control of a Faust DSP instance + + faustgen: compiles and creates a single DSP instance from a Faust program + (convenient for one-off Faust programs) + +*/ +#include "csdl.h" +#ifndef FAUSTFLOAT +#define FAUSTFLOAT cs_float +#endif +#include "faust/dsp/llvm-dsp.h" +#include "faust/gui/UI.h" +#include +#include +#ifdef _WIN32 +#include +#else +#include +#include +#endif + +#define MAXARG 40 + +static int dir_exists(const char *p) { +#ifdef _WIN32 + DWORD a = GetFileAttributesA(p); + return (a != INVALID_FILE_ATTRIBUTES && (a & FILE_ATTRIBUTE_DIRECTORY)); +#else + struct stat st; + return (stat(p, &st) == 0 && S_ISDIR(st.st_mode)); +#endif +} + +static void get_plugin_dir(char *out, size_t n) { + out[0] = '\0'; +#ifdef _WIN32 + HMODULE hm = NULL; + GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | + GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + (LPCSTR)&get_plugin_dir, &hm); + GetModuleFileNameA(hm, out, (DWORD)n); +#else + Dl_info info; + if (dladdr((void *)&get_plugin_dir, &info) && info.dli_fname) + snprintf(out, n, "%s", info.dli_fname); +#endif + char *slash = strrchr(out, '/'); +#ifdef _WIN32 + char *bslash = strrchr(out, '\\'); + if (!slash || (bslash && bslash > slash)) + slash = bslash; +#endif + if (slash) + *slash = '\0'; +} + +static void set_faust_lib_path(const char *path) { +#ifdef _WIN32 + _putenv_s("FAUST_LIB_PATH", path); +#else + setenv("FAUST_LIB_PATH", path, 1); +#endif +} + +/* Locate the Faust standard library directory so that programs using + import("stdfaust.lib") work without a separate Faust installation. A + user-provided FAUST_LIB_PATH always takes precedence; otherwise we look + for a "faust" directory next to the plugin, then fall back to the path + configured at build time. */ +static void setup_faust_path(CSOUND *csound) { + const char *env = getenv("FAUST_LIB_PATH"); + if (env != NULL && env[0] != '\0') + return; + char dir[4096], cand[4096]; + get_plugin_dir(dir, sizeof(dir)); + if (dir[0] != '\0') { + snprintf(cand, sizeof(cand), "%s/faust", dir); + if (dir_exists(cand)) { + set_faust_lib_path(cand); + return; + } + } +#ifdef DEFAULT_FAUST_LIB_PATH + if (dir_exists(DEFAULT_FAUST_LIB_PATH)) { + set_faust_lib_path(DEFAULT_FAUST_LIB_PATH); + return; + } +#endif + (void)csound; +} + +/** + * Faust controls class for Csound + * + **/ +class controls : public UI { + + struct ctl { + FAUSTFLOAT *zone; + char label[65]; + cs_float min, max; + ctl *nxt; + } anchor; + + void addctl(const char *label, FAUSTFLOAT *zone, FAUSTFLOAT min, + FAUSTFLOAT max) { + ctl *pctl = &anchor; + while (pctl->nxt) + pctl = pctl->nxt; + pctl->nxt = new ctl; + pctl = pctl->nxt; + strncpy(pctl->label, label, 63); + pctl->label[63] = '\n'; pctl->label[64] = '\0'; + pctl->zone = zone; + pctl->min = min; + pctl->max = max; + pctl->nxt = NULL; + } + +public: + controls() { + anchor.nxt = NULL; + anchor.label[0] = '\0'; + } + ~controls() { + ctl *pctl = &anchor, *tmp; + pctl = pctl->nxt; + while (pctl) { + tmp = pctl; + pctl = pctl->nxt; + delete tmp; + } + } + + virtual void openTabBox(const char *label){}; + virtual void openHorizontalBox(const char *label){}; + virtual void openVerticalBox(const char *label){}; + virtual void closeBox(){}; + + virtual void addSoundfile(const char *label, const char *filename, + Soundfile **sf_zone){}; + + virtual void addButton(const char *label, FAUSTFLOAT *zone) { + addctl(label, zone, 0, 0); + } + virtual void addCheckButton(const char *label, FAUSTFLOAT *zone) { + addctl(label, zone, 0, 0); + } + virtual void addVerticalSlider(const char *label, FAUSTFLOAT *zone, + FAUSTFLOAT init, FAUSTFLOAT min, + FAUSTFLOAT max, FAUSTFLOAT step) { + addctl(label, zone, min, max); + } + virtual void addHorizontalSlider(const char *label, FAUSTFLOAT *zone, + FAUSTFLOAT init, FAUSTFLOAT min, + FAUSTFLOAT max, FAUSTFLOAT step) { + addctl(label, zone, min, max); + } + virtual void addNumEntry(const char *label, FAUSTFLOAT *zone, FAUSTFLOAT init, + FAUSTFLOAT min, FAUSTFLOAT max, FAUSTFLOAT step) { + addctl(label, zone, min, max); + } + virtual void addHorizontalBargraph(const char *label, FAUSTFLOAT *zone, + FAUSTFLOAT min, FAUSTFLOAT max){}; + virtual void addVerticalBargraph(const char *label, FAUSTFLOAT *zone, + FAUSTFLOAT min, FAUSTFLOAT max){}; + + FAUSTFLOAT *getZone(char *label) { + ctl *pctl = &anchor; + pctl = pctl->nxt; + while (pctl) { + if (strcmp(pctl->label, label) == 0) + break; + pctl = pctl->nxt; + } + if (pctl) + return pctl->zone; + else + return NULL; + } + cs_float getMax(char *label) { + ctl *pctl = &anchor; + pctl = pctl->nxt; + while (pctl) { + if (strcmp(pctl->label, label) == 0) + break; + pctl = pctl->nxt; + } + if (pctl) + return pctl->max; + else + return 0; + } + cs_float getMin(char *label) { + ctl *pctl = &anchor; + pctl = pctl->nxt; + while (pctl) { + if (strcmp(pctl->label, label) == 0) + break; + pctl = pctl->nxt; + } + if (pctl) + return pctl->min; + else + return 0; + } +}; + +/** + * Faust object handle + * + **/ +struct faustobj { + void *obj; + controls *ctls; + faustobj *nxt; + uint64_t cnt; +}; + +/** + * Faust compile opcode + + usage: + ihandle faustcompile Scode, Sargs[,iasync, istacksize] + + ihandle - handle to compiled code + Scode - Faust program + Sargs - Faust compiler args + istacksize - compiler stack size in megabytes (default 1MB). NOTE: this + argument is accepted for backwards compatibility but is currently ignored, + as the Csound 7 threading API no longer supports setting the stack size. + iasync - async operation (1 = on, 0 = off) default to 1 +**/ +struct faustcompile { + OPDS h; + cs_float *hptr; + STRINGDAT *code; + STRINGDAT *args; + cs_float *async; + cs_float *stacksize; + cs_float *extra; + llvm_dsp_factory *factory; + void *thread; + void *lock; +}; + +char **parse_cmd(CSOUND *csound, char *str, int32_t *argc) { + char **argv; + int32_t i = 0, n = 0, end = strlen(str); + while (str[i] == ' ') + i++; + if (str[i] != '\0') + *argc = 1; + while (str[i] != '\0') { + if (str[i] == ' ') { + while (str[++i] == ' ') + ; + if (str[i] == '\0') + break; + (*argc)++; + } + i++; + } + argv = (char **)csound->Calloc(csound, sizeof(char *) * (*argc)); + i = 0; + while (str[i] == ' ') + i++; + for (n = 0; n < *argc && i < end; n++) { + argv[n] = &(str[i]); + while (str[++i] != ' ' && i < end) + ; + if (i >= end) + break; + str[i] = '\0'; + while (str[++i] == ' ' && i < end) + ; + } + return argv; +} + +int32_t delete_faustcompile(CSOUND *csound, void *p) { + + faustcompile *pp = ((faustcompile *)p); + faustobj *fobj, *prv, **pfobj; + if (pp->thread) + csound->JoinThread(pp->thread); + pfobj = (faustobj **)csound->QueryGlobalVariable(csound, "::factory"); + if(pfobj != NULL) { + fobj = *pfobj; + prv = fobj; + while (fobj != NULL) { + if (fobj->obj == (void *)pp->factory) { + prv->nxt = fobj->nxt; + break; + } + prv = fobj; + fobj = fobj->nxt; + } + if (fobj != NULL) { + if (*pfobj == fobj) + *pfobj = fobj->nxt; + deleteDSPFactory(pp->factory); + csound->Free(csound, fobj); + } + } + csound->DestroyMutex(pp->lock); + return OK; +} + +struct hdata { + faustcompile *p; + CSOUND *csound; +}; + +void *init_faustcompile_thread(void *pp) { + + faustcompile *p = ((hdata *)pp)->p; + faustobj **pffactory, *ffactory; + CSOUND *csound = ((hdata *)pp)->csound; + llvm_dsp_factory *factory; + int32_t argc = 0; + std::string err_msg; + char *cmd = (char *) csound->Calloc(csound, p->args->size + 9); + char *ccode = csound->Strdup(csound, p->code->data); + char *extra; + cs_float test = *p->extra; + strcpy(cmd, p->args->data); +#ifdef USE_DOUBLE + strcat(cmd, " -double"); +#endif + const char **argv = (const char **) parse_cmd(csound, cmd, &argc); + const char *varname = "::factory"; + + if(test) + extra = ((STRINGDAT *) p->extra)->data; + else + extra = (char *) ""; + + // Need to protect this + csound->LockMutex((void *) p->lock); + factory = createDSPFactoryFromString("faustop", (const char *) ccode, + argc, argv, extra, err_msg, 3); + csound->UnlockMutex((void *) p->lock); + + if (factory == NULL) { + csound->Message(csound, Str("\nFaust compilation problem:\nline %s\n"), + err_msg.c_str()); + *(p->hptr) = FL(-2.0); // error code. + csound->Free(csound, argv); + csound->Free(csound, cmd); + csound->Free(csound, ccode); + csound->Free(csound, pp); + return NULL; + } + + pffactory = (faustobj **)csound->QueryGlobalVariable(csound, varname); + if (pffactory == NULL) { + csound->CreateGlobalVariable(csound, varname, sizeof(faustobj *)); + pffactory = (faustobj **)csound->QueryGlobalVariable(csound, varname); + ffactory = (faustobj *)csound->Calloc(csound, sizeof(faustobj)); + ffactory->obj = factory; + ffactory->nxt = NULL; + ffactory->cnt = 0ul; + *pffactory = ffactory; + } else { + ffactory = *pffactory; + while (ffactory->nxt) { + ffactory = ffactory->nxt; + } + ffactory->nxt = (faustobj *) csound->Calloc(csound, sizeof(faustobj)); + ffactory->nxt->cnt = ffactory->cnt+1ul; + ffactory = ffactory->nxt; + ffactory->obj = factory; + } + p->factory = factory; + if(p->hptr) + *p->hptr = FL(ffactory->cnt); + csound->Free(csound, argv); + csound->Free(csound, cmd); + csound->Free(csound, ccode); + csound->Free(csound, pp); + + return NULL; +} + +static uintptr_t init_faustcompile_thread_runner(void *pp) { + init_faustcompile_thread(pp); + return 0; +} + +int32_t init_faustcompile(CSOUND *csound, faustcompile *p) { + hdata *data = (hdata *)csound->Malloc(csound, sizeof(hdata)); + data->csound = csound; + data->p = p; + *p->hptr = -1.0; + p->lock = csound->Create_Mutex(0); + p->thread = NULL; + + if(!(int)*p->async) { + /* synchronous compilation, run on the calling thread (large stack) */ + init_faustcompile_thread(data); + } else { + p->thread = csound->CreateThread(init_faustcompile_thread_runner, data); + } + /* Factory cleanup is deferred to reset, so that instruments using the + compiled factory are not affected when the compiling instrument ends. */ + csound->RegisterResetCallback(csound, p, delete_faustcompile); + return OK; +} + +struct faustdsp { + OPDS h; + cs_float *ohptr; + cs_float *code; /* faust compiled code handle */ + llvm_dsp *engine; /* faust DSP */ + llvm_dsp_factory *factory; /* DSP factory */ +}; + +/* deinit function + delete faust dsp objects +*/ +int32_t delete_faustdsp(CSOUND *csound, void *p) { + faustdsp *pp = (faustdsp *)p; + faustobj *fobj, *prv, **pfobj; + pfobj = (faustobj **)csound->QueryGlobalVariable(csound, "::dsp"); + if (pfobj != NULL) { + fobj = *pfobj; + prv = fobj; + while (fobj != NULL) { + if (fobj->obj == (void *)pp->engine) { + prv->nxt = fobj->nxt; + break; + } + prv = fobj; + fobj = fobj->nxt; + } + if (fobj != NULL) { + if (*pfobj == fobj) + *pfobj = fobj->nxt; + csound->Free(csound, fobj); + delete pp->engine; + } else + csound->Warning(csound, Str("could not find DSP %p for deletion"), + pp->engine); + } + if (pp->factory) + deleteDSPFactory(pp->factory); + + return OK; +} + +int32_t init_faustdsp(CSOUND *csound, faustdsp *p) { + int32_t factory; + faustobj *fobj, **fobjp, **pfdsp, *fdsp; + llvm_dsp *dsp; + controls *ctls = new controls(); + const char *varname = "::dsp"; + int timout = 0; + while (*((cs_float *)p->code) == -1.0) { + csound->Sleep(1); + timout++; + if(timout > 1000) { + return csound->InitError( + csound, "%s", Str("Faust code was not ready. Try compiling it \n" + "in a separate instrument prior to running it here\n")); + } + } + + factory = (int32_t)*((cs_float *)p->code); + + if (factory == -2) + return csound->InitError( + csound, "%s", Str("Faust code did not compile properly.\n" + "Check above messages for Faust compiler errors\n")); + + fobjp = (faustobj **)csound->QueryGlobalVariable(csound, "::factory"); + if (fobjp == NULL) + return csound->InitError(csound, "%s", Str("no factory available\n")); + fobj = *fobjp; + while ((int32_t)fobj->cnt != factory) { + fobj = fobj->nxt; + if (fobj == NULL) + return csound->InitError(csound, Str("factory not found %d\n"), + (int32_t)factory); + } + + dsp = ((llvm_dsp_factory *)fobj->obj)->createDSPInstance(); + if (dsp == NULL) + return csound->InitError(csound, "%s", Str("Faust instantiation problem\n")); + + dsp->buildUserInterface(ctls); + pfdsp = (faustobj **)csound->QueryGlobalVariable(csound, varname); + if (pfdsp == NULL) { + csound->CreateGlobalVariable(csound, varname, sizeof(faustobj *)); + pfdsp = (faustobj **)csound->QueryGlobalVariable(csound, varname); + fdsp = (faustobj *)csound->Calloc(csound, sizeof(faustobj)); + fdsp->obj = dsp; + fdsp->ctls = ctls; + fdsp->nxt = NULL; + fdsp->cnt = 0; + *pfdsp = fdsp; + } else { + fdsp = *pfdsp; + if (fdsp != NULL) { + while (fdsp->nxt) { + fdsp = fdsp->nxt; + } + fdsp->nxt = (faustobj *)csound->Calloc(csound, sizeof(faustobj)); + fdsp->nxt->cnt = fdsp->cnt + 1; + fdsp = fdsp->nxt; + fdsp->obj = dsp; + fdsp->ctls = ctls; + } else { + fdsp = (faustobj *)csound->Calloc(csound, sizeof(faustobj)); + fdsp->obj = dsp; + fdsp->ctls = ctls; + fdsp->nxt = NULL; + fdsp->cnt = 0; + *pfdsp = fdsp; + } + } + + p->factory = NULL; // this opcode does not own the factory + p->engine = (llvm_dsp *)fdsp->obj; + p->engine->init((int)csound->GetEngineSr(csound)); + *p->ohptr = (cs_float)fdsp->cnt; + return OK; + +} + +struct faustplay { + OPDS h; + cs_float *outs[MAXARG]; /* outputs */ + cs_float *inst; + cs_float *ins[VARGMAX]; /* inputs */ + llvm_dsp *engine; /* faust DSP */ + llvm_dsp_factory *factory; /* DSP factory */ + AUXCH memin; + AUXCH memout; +}; + + +int32_t init_faustplay(CSOUND *csound, faustplay *p) { + faustobj *fobj, **fobjp; + const OPARMS *parms = csound->GetOParms(csound); + int32_t instance = (int32_t)*p->inst; + + fobjp = (faustobj **)csound->QueryGlobalVariable(csound, "::dsp"); + if (fobjp == NULL) + return csound->InitError(csound, "%s", Str("no dsp instances available\n")); + fobj = *fobjp; + + while ((int32_t)fobj->cnt != instance) { + fobj = fobj->nxt; + if (fobj == NULL) + return csound->InitError(csound, Str("dsp instance not found %d\n"), + (int32_t)*p->inst); + } + + p->engine = (llvm_dsp *)fobj->obj; + p->engine->init((int)csound->GetEngineSr(csound)); + + if (p->engine->getNumInputs() != p->INOCOUNT - 1) { + delete p->engine; + return csound->InitError(csound, "%s", Str("wrong number of input args\n")); + } + if (p->engine->getNumOutputs() != p->OUTOCOUNT) { + delete p->engine; + return csound->InitError(csound, "%s", Str("wrong number of output args\n")); + } + + /* memory for sampAccurate offsets */ + if (parms->sampleAccurate) { + size_t size; + size = p->engine->getNumInputs() * sizeof(cs_float *); + if (p->memin.auxp == NULL || p->memin.size < size) + csound->AuxAlloc(csound, size, &p->memin); + size = p->engine->getNumOutputs() * sizeof(cs_float *); + if (p->memout.auxp == NULL || p->memout.size < size) + csound->AuxAlloc(csound, size, &p->memout); + } + + return OK; +} + +int32_t perf_faustplay(CSOUND *csound, faustplay *p) { + int32_t nsmps = CS_KSMPS, i; + uint32_t offset = p->h.insdshead->ksmps_offset; + uint32_t early = p->h.insdshead->ksmps_no_end; + cs_float **in_tmp = (cs_float **)p->memin.auxp; + cs_float **out_tmp = (cs_float **)p->memout.auxp; + + if (UNLIKELY(early)) { + for (i = 0; i < p->OUTOCOUNT - 1; i++) + memset(p->outs[i], '\0', nsmps * sizeof(cs_float)); + nsmps -= early; + } + if (UNLIKELY(offset)) { + /* offset pointers, save current pos */ + for (i = 0; i < p->OUTOCOUNT; i++) { + memset(p->outs[i], '\0', nsmps * sizeof(cs_float)); + out_tmp[i] = p->outs[i]; + p->outs[i] = &(p->outs[i][offset]); + } + for (i = 0; i < p->INOCOUNT - 1; i++) { + in_tmp[i] = p->ins[i]; + p->ins[i] = &(p->ins[i][offset]); + } + nsmps -= offset; + } + + p->engine->compute(nsmps, p->ins, p->outs); + + if (UNLIKELY(offset)) { + /* restore pos */ + for (i = 0; i < p->OUTOCOUNT; i++) + p->outs[i] = out_tmp[i]; + for (i = 0; i < p->INOCOUNT - 1; i++) + p->ins[i] = in_tmp[i]; + } + return OK; +} + + +/** + * faustgen and faustaudio opcodes + + usage: + ihandle[,asig1,...] faustgen Scode[,ain1,...] + ihandle[,asig1,...] faustaudio ifactory,[,ain1,...] + + Scode - Faust program + ifactory - handle pointing to compiled code from faustcompile + asig1 ... - audio outputs from Faust program + ain1 ... - audio inputs to Faust program + + ihandle - handle identifying this Faust DSP instance + +**/ +struct faustgen { + OPDS h; + cs_float *ohptr; + cs_float *outs[MAXARG]; /* outputs */ + STRINGDAT *code; /* faust code as string */ + cs_float *ins[VARGMAX]; /* inputs */ + llvm_dsp *engine; /* faust DSP */ + llvm_dsp_factory *factory; /* DSP factory */ + controls *ctls; + AUXCH memin; + AUXCH memout; +}; + +struct hdata2 { + faustgen *p; + CSOUND *csound; +}; + + +/* deinit function + delete faust objects +*/ +int32_t delete_faustgen(CSOUND *csound, void *p) { + faustgen *pp = (faustgen *)p; + faustobj *fobj, *prv, **pfobj; + if((pfobj = (faustobj **)csound->QueryGlobalVariable(csound, "::dsp")) + != NULL) { + fobj = *pfobj; + prv = fobj; + while (fobj != NULL) { + if (fobj->obj == (void *)pp->engine) { + prv->nxt = fobj->nxt; + break; + } + prv = fobj; + fobj = fobj->nxt; + } + if (fobj != NULL) { + if (*pfobj == fobj) + *pfobj = fobj->nxt; + csound->Free(csound, fobj); + delete pp->ctls; + delete pp->engine; + } + } + if (pp->factory) + deleteDSPFactory(pp->factory); + + return OK; +} + + +int32_t init_faustaudio(CSOUND *csound, faustgen *p) { + int32_t factory; + const OPARMS *parms = csound->GetOParms(csound); + faustobj *fobj, **fobjp, **pfdsp, *fdsp; + llvm_dsp *dsp; + controls *ctls = new controls(); + const char *varname = "::dsp"; + int timout = 0; + while (*((cs_float *)p->code) == -1.0) { + csound->Sleep(1); + timout++; + if(timout > 1000) { + return csound->InitError( + csound, "%s", Str("Faust code was not ready. Try compiling it \n" + "in a separate instrument prior to running it here\n")); + } + } + + + factory = (int32_t)*((cs_float *)p->code); + + if (factory == -2) + return csound->InitError( + csound, "%s", Str("Faust code did not compile properly.\n" + "Check above messages for Faust compiler errors\n")); + + fobjp = (faustobj **)csound->QueryGlobalVariable(csound, "::factory"); + if (fobjp == NULL) + return csound->InitError(csound, "%s", Str("no factory available\n")); + fobj = *fobjp; + while ((int32_t)fobj->cnt != factory) { + fobj = fobj->nxt; + if (fobj == NULL) + return csound->InitError(csound, Str("factory not found %d\n"), + (int32_t)factory); + } + + dsp = ((llvm_dsp_factory *)fobj->obj)->createDSPInstance(); + if (dsp == NULL) + return csound->InitError(csound, "%s", Str("Faust instantiation problem\n")); + + dsp->buildUserInterface(ctls); + pfdsp = (faustobj **)csound->QueryGlobalVariable(csound, varname); + if (pfdsp == NULL) { + csound->CreateGlobalVariable(csound, varname, sizeof(faustobj *)); + pfdsp = (faustobj **)csound->QueryGlobalVariable(csound, varname); + fdsp = (faustobj *)csound->Calloc(csound, sizeof(faustobj)); + fdsp->obj = dsp; + fdsp->ctls = ctls; + fdsp->nxt = NULL; + fdsp->cnt = 0; + *pfdsp = fdsp; + } else { + fdsp = *pfdsp; + if (fdsp != NULL) { + while (fdsp->nxt) { + fdsp = fdsp->nxt; + } + fdsp->nxt = (faustobj *)csound->Calloc(csound, sizeof(faustobj)); + fdsp->nxt->cnt = fdsp->cnt + 1; + fdsp = fdsp->nxt; + fdsp->obj = dsp; + fdsp->ctls = ctls; + } else { + fdsp = (faustobj *)csound->Calloc(csound, sizeof(faustobj)); + fdsp->obj = dsp; + fdsp->ctls = ctls; + fdsp->nxt = NULL; + fdsp->cnt = 0; + *pfdsp = fdsp; + } + } + + p->factory = NULL; // this opcode does not own the factory + p->engine = (llvm_dsp *)fdsp->obj; + p->engine->init((int)csound->GetEngineSr(csound)); + + if (p->engine->getNumInputs() != p->INOCOUNT - 1) { + delete p->engine; + p->engine = NULL; + return csound->InitError(csound, "%s", Str("wrong number of input args\n")); + } + if (p->engine->getNumOutputs() != p->OUTOCOUNT - 1) { + delete p->engine; + p->engine = NULL; + return csound->InitError(csound, "%s", Str("wrong number of output args\n")); + } + + /* memory for sampAccurate offsets */ + if (parms->sampleAccurate) { + size_t size; + size = p->engine->getNumInputs() * sizeof(cs_float *); + if (p->memin.auxp == NULL || p->memin.size < size) + csound->AuxAlloc(csound, size, &p->memin); + size = p->engine->getNumOutputs() * sizeof(cs_float *); + if (p->memout.auxp == NULL || p->memout.size < size) + csound->AuxAlloc(csound, size, &p->memout); + } + + p->ctls = ctls; + *p->ohptr = (cs_float)fdsp->cnt; + return OK; +} + +void *init_faustgen_thread(void *pp) { + CSOUND *csound = ((hdata2 *)pp)->csound; + faustgen *p = ((hdata2 *)pp)->p; + const OPARMS *parms = csound->GetOParms(csound); + std::string err_msg; +#ifdef USE_DOUBLE + int32_t argc = 4; + const char *argv[] = {"-vec", "-lv", " 1", "-double"}; +#else + int32_t argc = 3; + const char *argv[] = {"-vec", "-lv", " 1"}; +#endif + faustobj **pfdsp, *fdsp; + llvm_dsp *dsp; + controls *ctls = new controls(); + const char *varname = "::dsp"; + p->engine = NULL; + p->factory = createDSPFactoryFromString( + "faustop", (const char *)p->code->data, argc, argv, "", err_msg, 3); + if (p->factory == NULL) { + csound->InitError(csound, Str("Faust compilation problem: %s\n"), + err_msg.c_str()); + csound->Free(csound, pp); + return NULL; + } + + dsp = p->factory->createDSPInstance(); + if (dsp == NULL) { + csound->InitError(csound, "%s", + Str("Faust instantiation problem\n")); + csound->Free(csound, pp); + return NULL; + } + + dsp->buildUserInterface(ctls); + + pfdsp = (faustobj **)csound->QueryGlobalVariable(csound, varname); + if (pfdsp == NULL || *pfdsp == NULL) { + csound->CreateGlobalVariable(csound, varname, sizeof(faustobj *)); + pfdsp = (faustobj **)csound->QueryGlobalVariable(csound, varname); + fdsp = (faustobj *)csound->Calloc(csound, sizeof(faustobj)); + fdsp->obj = dsp; + fdsp->ctls = ctls; + fdsp->nxt = NULL; + fdsp->cnt = 0; + *pfdsp = fdsp; + } else { + fdsp = *pfdsp; + while (fdsp->nxt) { + fdsp = fdsp->nxt; + } + fdsp->nxt = (faustobj *)csound->Calloc(csound, sizeof(faustobj)); + fdsp->nxt->cnt = fdsp->cnt++; + fdsp = fdsp->nxt; + fdsp->obj = dsp; + fdsp->ctls = ctls; + } + + p->engine = dsp; + dsp->init((int)csound->GetEngineSr(csound)); + if (p->engine->getNumInputs() != p->INOCOUNT - 1) { + csound->InitError(csound, "%s", Str("wrong number of input args\n")); + delete p->engine; + deleteDSPFactory(p->factory); + p->factory = NULL; + p->engine = NULL; + csound->Free(csound, pp); + return NULL; + } + if (p->engine->getNumOutputs() != p->OUTOCOUNT - 1) { + csound->InitError(csound, + Str("wrong number of output args: need %d had %d\n"), + p->engine->getNumOutputs(), + p->OUTOCOUNT - 1); + delete p->engine; + deleteDSPFactory(p->factory); + csound->Free(csound, pp); + p->engine = NULL; + p->factory = NULL; + return NULL; + } + + /* memory for sampAccurate offsets */ + if (parms->sampleAccurate) { + size_t size; + size = p->engine->getNumInputs() * sizeof(cs_float *); + if (p->memin.auxp == NULL || p->memin.size < size) + csound->AuxAlloc(csound, size, &p->memin); + size = p->engine->getNumOutputs() * sizeof(cs_float *); + if (p->memout.auxp == NULL || p->memout.size < size) + csound->AuxAlloc(csound, size, &p->memout); + } + p->ctls = ctls; + *p->ohptr = (cs_float)fdsp->cnt; + csound->Free(csound, pp); + return NULL; +} + +int32_t init_faustgen(CSOUND *csound, faustgen *p) { + hdata2 *data = (hdata2 *)csound->Malloc(csound, sizeof(hdata2)); + data->csound = csound; + data->p = p; + /* Compile synchronously on the calling thread, which has a large stack; + the original code also joined the worker thread immediately. */ + init_faustgen_thread(data); + return OK; +} + +int32_t perf_faust(CSOUND *csound, faustgen *p) { + int32_t nsmps = CS_KSMPS, i; + uint32_t offset = p->h.insdshead->ksmps_offset; + uint32_t early = p->h.insdshead->ksmps_no_end; + cs_float **in_tmp = (cs_float **)p->memin.auxp; + cs_float **out_tmp = (cs_float **)p->memout.auxp; + + if (UNLIKELY(early)) { + for (i = 0; i < p->OUTOCOUNT - 1; i++) + memset(p->outs[i], '\0', nsmps * sizeof(cs_float)); + nsmps -= early; + } + if (UNLIKELY(offset)) { + /* offset pointers, save current pos */ + for (i = 0; i < p->OUTOCOUNT - 1; i++) { + memset(p->outs[i], '\0', nsmps * sizeof(cs_float)); + out_tmp[i] = p->outs[i]; + p->outs[i] = &(p->outs[i][offset]); + } + for (i = 0; i < p->INOCOUNT - 1; i++) { + in_tmp[i] = p->ins[i]; + p->ins[i] = &(p->ins[i][offset]); + } + nsmps -= offset; + } + + p->engine->compute(nsmps, p->ins, p->outs); + + if (UNLIKELY(offset)) { + /* restore pos -- could be memcpy? */ + for (i = 0; i < p->OUTOCOUNT - 1; i++) + p->outs[i] = out_tmp[i]; + for (i = 0; i < p->INOCOUNT - 1; i++) + p->ins[i] = in_tmp[i]; + } + return OK; +} + +#define MAXPARAM 128 + +/** + * faustctl opcode + + usage: + faustctl idsp, Slabel, kval[, Slabel1, kval1 ...] + + idsp - handle from an existing Faust DSP instance + Slabel - name of control (in Faust program) + kval - value to be sent to control + +**/ +struct faustctl { + OPDS h; + cs_float *inst; + STRINGDAT *label; + cs_float *val; + cs_float *extraparam[MAXPARAM]; + FAUSTFLOAT *zone; + cs_float min, max; + cs_float minextra[MAXPARAM/2], maxextra[MAXPARAM/2]; + FAUSTFLOAT *zonextra[MAXPARAM/2]; +}; + +int32_t init_faustctl(CSOUND *csound, faustctl *p) { + + faustobj *fobj, **fobjp; + int32_t instance = (int32_t)*p->inst; + + /* checks that extra parameter count is even */ + if((p->INOCOUNT - 3)%2) + return csound->InitError(csound, "unbalanced parameter count \n"); + + fobjp = (faustobj **)csound->QueryGlobalVariable(csound, "::dsp"); + if (fobjp == NULL) + return csound->InitError(csound, "%s", Str("no dsp instances available\n")); + fobj = *fobjp; + + while ((int32_t)fobj->cnt != instance) { + fobj = fobj->nxt; + if (fobj == NULL) + return csound->InitError(csound, Str("dsp instance not found %d\n"), + (int32_t)*p->inst); + } + + p->zone = fobj->ctls->getZone(p->label->data); + if (p->zone == NULL) + return csound->InitError(csound, Str("dsp control %s not found\n"), + p->label->data); + p->max = fobj->ctls->getMax(p->label->data); + p->min = fobj->ctls->getMin(p->label->data); + { + cs_float val = *p->val; + if (p->min != p->max) + val = val < p->min ? p->min : (val > p->max ? p->max : val); + *p->zone = val; + } + + + /* implementation of extra optional parameters */ + for(int n = 0; n < p->INOCOUNT - 3; n+=2) { + char *name = ((STRINGDAT *)p->extraparam[n])->data; + p->zonextra[n/2] = fobj->ctls->getZone(name); + if (p->zonextra[n/2] == NULL) + return csound->InitError(csound, Str("dsp control %s not found\n"), + name); + p->maxextra[n/2] = fobj->ctls->getMax(name); + p->minextra[n/2] = fobj->ctls->getMin(name); + { + cs_float val = *(p->extraparam[n+1]); + cs_float min = p->minextra[n/2]; + cs_float max = p->maxextra[n/2]; + if (min != max) + val = val < min ? min : (val > max ? max : val); + *(p->zonextra[n/2]) = val; + } + } + + return OK; +} + +int32_t perf_faustctl(CSOUND *csound, faustctl *p) { + cs_float val = *p->val; + if (p->min != p->max) + val = val < p->min ? p->min : (val > p->max ? p->max : val); + *p->zone = val; + + /* implementation of extra optional parameters */ + for(int n = 0; n < p->INOCOUNT - 3; n+=2) { + cs_float val = *(p->extraparam[n+1]); + cs_float min = p->minextra[n/2]; + cs_float max = p->maxextra[n/2]; + if (min != max) + val = val < min ? min : (val > max ? max : val); + *(p->zonextra[n/2]) = val; + } + return OK; +} + +#define S(x) sizeof(x) + +static OENTRY localops[] = { + {(char *)"faustgen", S(faustgen), 0, + (char *)"immmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm", (char *)"SM", + (SUBR)init_faustgen, (SUBR)perf_faust, (SUBR)delete_faustgen, NULL, 0}, + {(char *)"faustcompile", S(faustcompile), 0, (char *)"i", (char *)"SSppo", + (SUBR)init_faustcompile, NULL, NULL, NULL, 0}, + {(char *)"faustaudio", S(faustgen), 0, + (char *)"immmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm", (char *)"iM", + (SUBR)init_faustaudio, (SUBR)perf_faust, (SUBR)delete_faustgen, NULL, 0}, + {(char *)"faustdsp", S(faustdsp), 0, + (char *)"i", (char *)"i", + (SUBR)init_faustdsp, NULL, (SUBR)delete_faustdsp, NULL, 0}, + {(char *)"faustplay", S(faustplay), 0, + (char *)"mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm", (char *)"iM", + (SUBR)init_faustplay, (SUBR)perf_faustplay, NULL, NULL, 0}, + {(char *)"faustctl.i", S(faustgen), 0, (char *)"", (char *)"iSiN", + (SUBR)init_faustctl, NULL, NULL, NULL, 0}, + {(char *)"faustctl.k ", S(faustgen), 0, (char *)"", (char *)"iSkN", + (SUBR)init_faustctl, (SUBR)perf_faustctl, NULL, NULL, 0}, + {NULL, 0, 0, NULL, NULL, NULL, NULL, NULL, NULL, 0}}; + +PUBLIC int32_t csoundModuleCreate(CSOUND *csound) { + (void)csound; + return 0; +} + +PUBLIC int32_t csoundModuleInit(CSOUND *csound) { + int32_t err = 0; + OENTRY *ep = (OENTRY *)&(localops[0]); + setup_faust_path(csound); + while (ep->opname != NULL) { + err |= csound->AppendOpcode(csound, + ep->opname, ep->dsblksiz, ep->flags, + ep->outypes, ep->intypes, + (int32_t (*)(CSOUND *, void *))ep->init, + (int32_t (*)(CSOUND *, void *))ep->perf, + (int32_t (*)(CSOUND *, void *))ep->deinit); + ep++; + } + return err; +} + +PUBLIC int32_t csoundModuleDestroy(CSOUND *csound) { + (void)csound; + return 0; +} + +PUBLIC int32_t csoundModuleInfo(void) { + return CSOUND_MODULE_INFO; +}