4.4 CsoundObj: A Platform-Specific API
4.4.1 The Architecture of Csound
The following will describe the high-level architecture of Csound. I will begin by discussing libcsound, which contains the core of Csound itself. Next I will discuss how Csound provides developer extensibility in two ways: firstly, by development of plugins, and secondly, by client use of libcsound and its API. I will discuss how the public API of libcsound is used by both plugins and library clients. These aspects of Csound’s architecture form the base upon which the CsoundObj library was developed.
Core Library: libcsound
Csound employs a layered architecture that isolates portable code from plat- form-specific code. At its core is libcsound, a portable library that has two main library dependencies: libsndfile and pthreads. Beyond these two libraries, Csound requires either libraries available on POSIX-compliant systems (i.e., Linux, BSD, OSX) or Windows standard libraries. For tools, Csound requires Flex and Bison, as well as a C99-compliant C-language compiler. Csound uses the CMake build tool to generate build files for other build tools (e.g., GNU Make, XCode project files). Csound’s dependencies are shown in Figure 4.8.
libcsound contains the essence of Csound. The library includes Csound’s language compilers, core audio engine, and built-in set of opcodes. Any feature of Csound that has a third-party dependency outside of the standard C library, libsndfile, and pthreads, is handled externally to libcsound. These features may be supplied either through plugins or by host applications that use libcsound.
At this level of architecture, libcsound is only a library. One can not execute libcsound. In this state it is only of use to developers who would
Required • Tools
– CMake.
– Build System (Make, XCode, Ninja). – C Compiler (GCC, Clang, MSVC). – Flex.
– Bison. • Libraries
– Pthreads (non-MSVC platforms). – libsndfile.
Optional • Libraries
– gettext (for internationalisation).
Figure 4.8: Csound Dependencies
build applications or their own libraries based on Csound. Developers using libcsound will use the public Csound API, located in two places: the csound.h header file and the CSOUND struct itself. csound.h lists public function proto- types that host applications can use to embed Csound into their application. These functions include operations such as creating and running a Csound engine, as well as communicating with an engine via Csound’s channel system.
The functions that make up the API in csound.h are also available as function pointers in the CSOUND data structure. This allows plugins to have access to the same functions as in csound.h, just by dereferencing the function
pointer from the CSOUND data structure. The reason for using function pointers for plugins is that one can compile plugins using just the Csound- provided development headers, without requiring linking to libcsound itself. Requiring plugins to link to libcsound was not a problem for Unix-style operating systems (i.e., Linux and OSX) but did cause problems on Windows.
Client Applications and Libraries
Csound started its life as a single, monolithic command-line program. In Csound 5, the core of Csound was isolated into libcsound and a public API was developed to allow building programs using Csound as a library. It was at this time that the command-line version of Csound moved from a monolithic application to become a smaller executable that itself linked to libcsound.
By having a standard core library and well-defined public API, developers had a clear way to embed Csound into their applications. Consequently, users of the library also all shared the same implementation. If one wanted to modify Csound for their own use, they could contribute a change and all users of the library would benefit.
Additionally, while Csound provides many features, the libcsound API design is such that it does not try to do everything itself. Rather, libcsound tries to provide all of the necessary lower-level functionality that would enable client applications to build what they need themselves. In this light, developers can use libcsound and extend the capabilities of Csound using their own application code.
libcsound is not only the basis for applications, but also other libraries. Csound’s standard distributions include cross-language wrappers (also known as Adapters [70]) in C++, Python, and Java. These language-specific classes
and libraries wrap libcsound’s API functions and data types and offer another API that is appropriate for their target languages. Adjustments for languages include presenting a class-based API as well as mapping data types from one language to another. For example, users may pass String objects to the Java API for Csound and the wrapper translates these to C’s char* type when further calling a wrapped libcsound API function.
For C++, Csound provides a csound.hpp header file that offers an object- oriented, class-based view of libcsound’s API. For Python and Java, an intermediary library, libcsnd6, is written in C++ to provide both an object- oriented API, but also additional glue classes that help to make certain aspects of Csound usage more idiomatic for the target language (e.g., expose a class for wrapping access to a-rate variables, rather than passing a pointer to the data to the host). SWIG (Simplified Wrapper and Interface Generator) — a wrapper generator program — is then used to analyze the libcsnd6 API and automatically create all of the wrapper code that bridges the target language and the native code.7
The relationship of applications and libraries to libcsound is shown in Figure 4.9.
Plugins
In addition to client extension, one can create plugin libraries that are loaded by libcsound. When a Csound engine starts, it will first load any plugins found
7The design and usage of SWIG is described in [29]. The application and documentation
is available at [170]. As discussed in [171], SWIG is sometimes compared with interface compilers such as CORBA [83] and COM [124], but SWIG does not use an Interface Description Language (IDL), does not generate stubs, does not define protocols, and does not to define component models.
Figure 4.9: Relationship of libcsound to other libraries and applications in the directory path defined in the OPCODE6DIR64 environment variable, as well as any libraries explicitly given to the Csound program as an argument using the –opcode-lib= flag.8
When Csound first finds a library, it attempts to load it. If the library does successfully load, it means that its dependencies have been found and successfully satisfied. Upon a successful load, three functions are sought out in the library: csoundModuleCreate(), csoundModuleInit(), and csoundMo-
8The environment variable used depends upon the version of Csound used. Csound can be
compiled to use 32-bit or 64-bit floating point precision for its processing. These correspond to float or double numeric types in C. For Csound 5,OPCODEDIR and OPCODEDIR64 were used for float and double versions of Csound. This allowed users to have both versions of Csound installed on the same system and to load plugins from separate locations. For Csound 6, the variable names were changed toOPCODE6DIR and OPCODE6DIR64. This was to allow having both versions of both Csound 5 and Csound 6 installed on the same system. In Csound 6, the standard version distributed for desktop users is the 64-bit doubles version, soOPCODE6DIR64 is used here.
duleDestroy(). These three functions are called at various points in the Csound engine life-cycle.
Each of the above functions takes in a single argument: a pointer to a CSOUND engine. Csound plugins use the Csound API through the function pointers provided as part of the CSOUND data structure. As noted earlier, these are the same API functions that are in the public Csound API in csound.h. Consequently, anything that a plugin is capable of doing – such as registering opcodes, audio drivers, MIDI drivers, and graphics drawing functions – a host application can do as well.
However, the opposite is not true: plugins can not extend Csound in all of the same ways as a host application. Plugins can not – or at least, should not – alter the flow of control of the engine. However, host programs can and are expected to operate the Csound engine however they would like. In this way, plugins are meant to add features to known extension points, while hosts may do that as well as add completely new abstractions and features on top of the Csound engine.
Discussion
The architecture of Csound is centered on the core libcsound library. Client applications build upon and use the Csound engine, and may also provide their own extensions to Csound. Plugins are loaded by Csound itself and are designed to only provide extensions.
Figure 4.10 illustrates the relationship between Csound, clients, and plugins. For plugins, they provide features that are available to all clients of libcsound. All features in Plugin X and Plugin Y are available to Client A and Client B. For clients, the features they provide are limited to their own
Figure 4.10: Csound, clients, and plugins
application. Any Csound extensions that Client A provides are not available to Client B, and vice versa.
The two methods of developer extension in Csound both employ the same Csound API. Their roles within the architecture of the system dictate when to develop a plugin and when to develop a feature within a client application itself. The ecosystem of Csound prior to the work in this chapter has largely been developed with the assumption that both extension mechanisms would be available. However, as will be explored below, not all platforms support plugin loading and features traditionally provided as plugins would have to be managed in a new way.