Build System Overview
Contents
Build System Overview#
FluCoMa uses CMake to handle the build process. If you just want to build the existing FluCoMa objects, then you don’t really need to understand the gory details: build instructions are available in the README.md for each Host:
Likewise, if you just want to use Algorithm implementations on their own, and don’t need FluCoMa to produce Max, SC, PD plugins (or a CLI executable), then all you need to do is use the header files directly, providing you have a way of getting the needed dependencies and are equipped with a C++ toolchain (see below).
If you want to develop a new Client then you’ll need to know a bit more. Here we’ll cover the steps involved in adding a new Client, and shed some light on how our repos hang together, how dependenices are managed, and so forth.
Environment Overview#
FluCoMa is developed using C++17, and objects in the official distribution need to run on Mac OS, Windows and desktop Linux-flavours (we typically test on Ubuntu). We don’t use compiler-specific extenstions, in the hope that the code can be as portable as possible.
To use / build FluCoMa code, then you will need
a C++17 toolchain for your operating system
CMake (>= 3.18)
git
if want to build doccumentation, then you will need a python environment >= 3.8 as well (requirements are here)
Warning
Even though we use C++17 as a language dialect, in order to maintain backwards support for older operating systems, we only use a subset of the standard library. Most notably, we do not use std::filesystem.
Currently, we aim to be able to support as far back as MacOS 10.9 (because we want to be able to support Max 7 for as long as possible). Obviously this committment can not last forever: each change in archiecture or OS APIs makes this harder.
Repositories#
Each Host that FluCoMa supports has a git repository, linked to in the list at the top of this pages. These repos contain the wrapper code that generates viable plugins for that platform, as well as the specific CMake code for that host. Each of these repos depends on flucoma-core and, if you want to build the documentation as well, on flucoma-docs.
To build for a given host, you minimally need to git clone the repo for that host. By default it will automatically pull in flucoma-core and optionaly flucoma-docs (if the CMake cache variable DOCS=ON) to <your build folder>/_deps, as well as all the other dependencies.
However, if you’re developing, then you probably want to have your own clones of core and docs to work on. To to this, git clone these repos separately, and then tell CMake for the host in question where to find these repos.
Note
The current way we manage our branches is to use main as a stable branch with the current release, and to do all new development on a branch called dev. So you will want to checkout dev for each flucoma repo you are working on.
Step by step:
Clone
flucoma-coregit clone https://github.com/flucoma/flucoma-core.gitClone
flucoma-docsgit clone https://github.com/flucoma/flucoma-docs.gitClone your host repo
git clone https://github.com/flucoma/flucoma-max.gitand / or
git clone https://github.com/flucoma/flucoma-pd.gitand / or
git clone https://github.com/flucoma/flucoma-sc.git
and / or
git clone https://github.com/flucoma/flucoma-cli.gitFor each repo you’ve cloned, checkout the
devbranch:cd <repo directory [core | docs | host]> git checkout dev
Go to your host repo, and configure with CMake
cd <host repo directory> mkdir build && cd build
When we configure, we will let
CMake’sFetchContentknow that we already have thecoreanddocsrepos.cmake -DFETCHCONTENT_SOURCE_DIR_FLUCOMA-CORE=<path to where you cloned flucoma-core> -DFETCHCONTENT_SOURCE_DIR_FLUCOMA-DOCS=<<path to where you cloned flucoma-docs> -DDOC=ON ..That’s a bit of a mouthful. You may wish to consider either using CMake presets if you’re using CMake >= 3.19, or making a .cmake file with these variables in, that can be passed as a set of inital cache values. e.g
# a file called flucoma-vars.cmake, or whatever you want set(FETCHCONTENT_SOURCE_DIR_FLUCOMA-CORE, "<path to where you cloned flucoma-core>") set(FETCHCONTENT_SOURCE_DIR_FLUCOMA-DOCS, "<path to where you cloned flucoma-docs>") set(DOCS, ON)
Then the
cmakeincovation simplifies tocmake -C<path your .cmake file> ..Try building an object to check that stuff is working. From your
buildfolder. For Max / PD:cmake --build . --target fluid.gain_tildeFor SC
cmake --build . --target FluidGain
Note
The structure of having host repos depend on core and docs makes life easier for users who just want to build for their preffered host, but it’s a bit onerous when developing.
One could automate a good deal of this with a further CMake project that pulls everything in, sets branches etc.
Dependencies#
If you’re using CMake to build, then the dependencies should be pulled in automatically (using CMake’s FetchContent feature). If you’re wanting to use Algorithms stand-alone, then you will need to satisfy these dependencies by hand. You can find out what they are by looking in the CMakeLists.txt file at the top level of the flucoma-core repo, and looking for FetchContent_Declare blocks. For example, here is the block for the Eigen C++ library that a lot of the algorithms depend on:
FetchContent_Declare(
Eigen
GIT_SHALLOW TRUE
GIT_REPOSITORY https://gitlab.com/libeigen/eigen.git
GIT_PROGRESS TRUE
GIT_BRANCH "3.4"
GIT_TAG "3.4.0"
)
Adding a New Client: QuickStart#
Assuming you’ve got yourself set up ok. Adding a new Client starts with making a new .hpp C++ header file in a subdirectory of flucoma-core/inlude/clients. If your Client deals with streaming data (i.e. audio or streams of lists / control signals) then, by convention, it goes in flucoma-core/inlude/clients/rt. Otherwise, for offline processors, it goes in flucoma-core/inlude/clients/nrt.
For the purposes of demonstration, let’s copy the real-time GainClient.hpp and add a new FluCoMa object called Gain2. In Max / PD, this will produce an object called fluid.gain2~. In SuperCollider, it will make a plugin called FluidGain2.
Copy
flucoma-core/inlude/clients/rt/GainClient.hppcd include/clients/rt cp GainClient.hpp GainClient2.hpp
Then, to add the new object to the build system, edit
flucoma-core/FlucomaClients.cmake, by adding the following lineadd_client(Gain2 clients/rt/GainClient2.hpp CLASS RTGainClient)
This allows the
CMaketo take care of some otherwise tedious work of generating an appropriate.cppfile and compilation target for your hostThen go back to the
builddirectory for your host, and see if it buildscmake --build . --target Gain2
All being well, the build will succeed and your object will appear somewhere, varying depending on the host
Max:
flucoma-max/externalsPD:
flucoma-pd/pd_objectsSC:
flucoma-sc/release-packaging/PluginsCLI:
flucoma-cli/bin
Note
This diversity of output locations is a mess, we know. Even worse, it’s writing into the source tree, which is generally bad practice.
The tricky thing has been wanting to arranging matters so that newly compiled plugins appear in a structure that is viable for the host in question, i.e.
for Max, in a package-like folder
for PD, a flat directory that contains both externals and help files
for SC, a folder hierarchy that follows convention
Running the cmake install target makes all this happen anyway, but then decouples one’s freshly minted plugin from it’s generated location, which then makes debugging harder.
Anyway, it needs sorting out. For now, be aware that
For Max,
flucoma-maxcan (for now) be placed directly in packages (or sym-linked) and will workFor SC, the
flucoma-sc/release-packagingcan be sym-linked into your extensions folder, and will workFor PD, the need to have externals and help files together makes everything awful
So, nearly done. The final step is to see if your new object works. In Max / PD, it should be immediately available, providing the environment can see it (see the above note). We should be able to test it by opening the existing fluid.gain~ / FluidGain help file and changing the object name.
SuperCollider Language Class#
In SC, you will want an sclang class file for the object. Let’s copy and amend flucoma-sc/release-packaging/Classes/FluidGain.sc:
Copy
cd flucoma-sc/release-packaging/Classes/ cp FluidGain.sc FluidGain2.sc
Edit. Open FluidGain2.sc in your favourite editor (which might be the SC IDE for these purposes), and just change the class name
FluidGain2 : UGen { *ar { arg in = 0, gain=1.0; ^this.multiNew('audio', in.asAudioRateInput(this), gain) } }
Recompile the class library, amend the
FluidGainhelpfile, start the server and see if it works!
Next Steps: Your Own Object#
Obviously this object isn’t very exciting. For more detail on the manouveres involved in making your own Client, you can look at the walk-throughs of some existing Clients
[
PitchClient](../developing clients/pitch.md) shows a client that takes audio in, does an STFT and produces control-rate descriptors[
KDTree](../developing clients/kdtree.md) shows a ‘data’ client that usesFluidDataSetas a source for running an algorithm.
Of course, as part of making your own Client, presumably you have an Algorithm that it runs. You have much more latitude over the form of this than with Clients. By convention, it will be in a header file that lives in flucoma-core/include/algorithms/public (if it is intended to be part of the distribution)
Debugging#
Because clients need to be run in the context of a host environment (until we make a mock environment, somehow), debugging involves attaching a debugger to the host environment itself. The exact mechanics of this vary by the host and operating system, but
for SuperCollider, it’s easiest launch the server from the SC IDE and then attach your debugger to the running instance of
scsynthlikewise, for PD. On Mac OS, note that you need to attach to the
pdexecutable that lives inside the.apppackage (because the gui and pd istelf are separate processes)for Max, attaching to Max.app should work (or Max.exe on Windows). Note that, on Mac, if you use the XCode generator from cmake, we make the resulting project set up to use
/Applications/Max.appas the execution / debug target for all the objects, so that just pressing the ‘play’ button a client’s target should build it and launch Max with the debugger attached. Which is nice.
We (well, weefuzzy) would heartily reccomend working almost exclusively with Debug builds whilst developing, so that assert conditions can be tested and caught. If your object really needs some optimisation to be able to work at all, then we have a cmake build configuration called Test that will keep assertions on but apply some optimiation. However, checking Release builds for surprises is very important too.
Sanitizers#
We would also wholeheartedly endorse debugging with santizers like asan (address sanitizer) and ubsan (undefined behaviour sanitizer) whereever possible, because they can make catching and diagnosing bugs much, much quicker. Unfortunately, using these in the context of a host environment isn’t straightforward, because the libraries need to be injected into the host’s running environment to work (otherwise it just crashes when it tries to load your object).
The basic formula for injecting, say, asan on Mac OS is to execute
"export DYLD_INSERT_LIBRARIES=<location of your asan lib>;" <progam>
The location of the asan library depends on your toolchain and thus XCode version on Mac OS. It’s probably something like
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/<VERSION>/lib/darwin/libclang_rt.asan_osx_dynamic.dylib
where <VERSION> is some version number for the clang in your toolchain. This might work to save burrowing through folders
find /Applications/Xcode.app/Contents/Developer/Toolchains/ -name \*asan_osx\*.dylib
In SuperCollider, you can enable this in the IDE like this
~realserverprogam = Server.program
Server.program = "export DYLD_INSERT_LIBRARIES=<terrifying string from above>;" + Server.program
This sets the Server.program to run with the asan library injected. We set the variable ~realserverprogram so you can switch back if needed.
For Max, it doesn’t appear possible to inject asan into Max 8 because of something to do with the bundled libchromium. However, it does work for Max 7, which you will need to invoke from the terminal.
PD can, likewise, be invoked from the terminal.