Michael Gogins
https://github.com/gogins
http://michaelgogins.tumblr.com
The CXX opcodes provide a means for Csound users to embed C++ source code in Csound orchestra code, and for Csound then to compile, load, link, and run this C++ code during the course of the Csound performance.
This could of course be done outside of Csound, e.g. by writing plugin opcodes. However, experience shows that bringing the C++ code and build commands into Csound provides a considerably more efficient composing environment.
The cxx_compile
opcode compiles C++ source, embedded in a Csound
orchestra, into a dynamic link library, and executes its entry point at init
time.
The cxx_invoke
opcode implements an opcode-like invocable interface to be
created and called during the Csound performance, either at init time, or
at k-rate. Commonly, this is used to implement new Csound opcodes directly in
C++ from the Csound orchestra. It is also used to generate scores or control
channel values at the beginning of, or during, the performance.
These opcodes do not embed the C++ compiler, but rather use the operating system and an installed C++ toolchain to execute a C++ compilation. The resulting dynamic link libraries are then loaded by Csound, and symbols in them can be invoked by Csound.
cxx_compile
- Compile C++ source code into a dynamic link library, and
execute its entry point at Csound init time.
The cxx_compile
opcode uses an installed C++ toolchain to compile C++ source
code that is embedded in the orchestra at Csound's init time. The code is
compiled to a dynamic link library that is then linked and loaded. A specified
entry point is then called. This function can do anything that C++ code can do
and has access to the running instance of Csound.
i_result cxx_compile S_entry_point, S_source_code, S_compiler_command [, S_dynamic_link_libraries]
S_entry_point - A valid C identifier, unique in the Csound performance,
for an entry point function that must be defined in the source code. This
function must have the signature extern "C" int (*)(CSOUND *csound)
. This
function has full access to the running instance of Csound via the Csound API
members of the CSOUND structure, as well as to all symbols in all other loaded
dynamic link libraries.
S_source_code - C++ source code. Can be a multi-line string literal
enclosed in {{
and }}
. Please note, this string is a "heredoc" and, thus,
any \
characters in it must be escaped, e.g. one must write \\n
not \n
for a newline character. The source code represents one translation unit, but
it can be as large as needed; in practice, this is not a limitation.
S_compiler_command - Standard gcc/clang compiler command, as would be passed
on the terminal command line. Can be a multi-line string literal enclosed in
{{
and }}
. If the -v
option is present, additional diagnostics are
enabled for the cxx_compile
and cxx_invoke
opcodes. Link libraries and
linker options should also be specified normally. The compiler name must come
first, this enables these opcodes to be used with different compilers. The
source code filename and the output filename must not be specified.
i_result - 0 if the code has been compiled and executed succesfully; non-0 if there is an error. Toolchain diagnostics are printed to stderr.
S_dynamic_link_libraries - A space-delimited list of dynamic link libraries
upon which the compiled code depends, and which therefore must be preloaded.
These libraries must be complete filenames (e.g., not -lmylib
, but
libMylib.so
), searched for in the standard locations, or can be given as
complete filepaths to be loaded unconditionally.
The module is compiled and executed at Csound's initialization time, which
comes after csoundStart
has been called. If the compilation is done in the
orchestra header, i.e. in instr 0
, the execution occurs during Csound's
init pass for instr 0
. If the compilation is done from a regular Csound
instrument, the execution occurs during Csound's init pass for that particular
instrument instance.
Non-standard include directories and compiler options may be used, but must be
defined in S_compiler_command
.
Dynamic link libraries on which the module depends may also be used, and may be specified in the normal way.
The source code is saved to a unique temporary file and then compiled, loaded, linked, and executed.
PLEASE NOTE: Some shared libraries use the symbol __dso_handle
, but
this is not always defined in the compiler's startup code. To work around this,
manually define it in your C++ code like this:
void* __dso_handle = (void *)&__dso_handle;
The module must define a uniquely named C function, which is the entry point
to the module, in the same way that the main
function is the entry point to
a C program, with the following signature:
extern "C" int(*)(CSOUND *csound);
Once the cxx_compile
opcode has preloaded any dependent libraries, and then
compiled, linked, and loaded the module, Csound immediately calls the entry
point function in that module.
The entry point function may call any Csound API functions that are members of
the CSOUND
struct, define classes and structs, call any public symbol in any
loaded dynamic link library, or indeed do anything at all that can be done
with C++ code.
For example, the module may use an external shared library to assist with
algorithmic composition, then translate the generated score to a Csound score,
then call csound->InputMessage
to schedule the score for immediate
performance.
However, one of the most significant uses of cxx_compile
is to compile C++
code into classes that can perform the work of Csound opcodes. This is
done by implementing the CxxInvokable
interface. See cxx_invoke
for how
this works and how to use it.
cxx_invoke
- creates an instance of a class that implements the
CxxInvokable
interface that has been defined previously using
cxx_compile
, and invokes that instance at i-time, k-time, or both.
Creates an instance of a CxxInvokable
that has been defined previously
using cxx_compile
, and invokes that instance at i-time, k-time, or both.
This can be used to implement any type of Csound opcode. It can also be used
for other purposes, e.g. simply as a way to call some function in the
CxxInvokable
module.
[m_output_1,...] cxx_invoke S_cxx_invokable, i_thread, [, m_input_1,...]
S_cxx_invokable - A name unique in the Csound performance for a factory
function CxxInvokable *(*)
that creates and returns a new object
implementing the following pure abstract interface:
/**
* Defines the pure abstract interface, implemented by CXX modules, to be
* called by Csound using the `cxx_invoke` opcode.
*/
struct CxxInvokable {
virtual ~CxxInvokable() {};
/**
* Called once at init time. The inputs are the same as the
* parameters passed to the `cxx_invoke` opcode. The outputs become
* the values returned from the `cxx_invoke` opcode. Performs the
* same work as `iopadr` in a standard Csound opcode definition. The
* `opds` argument can be used to find many things about the invoking
* opcode, its enclosing instrument, and the running instance of Csound.
*/
virtual int init(CSOUND *csound, OPDS *opds, MYFLT **outputs, MYFLT **inputs) = 0;
/**
* Called once every kperiod. The inputs are the same as the
* parameters passed to the `cxx_invoke` opcode. The outputs become
* the values returned from the `cxx_invoke` opcode. Performs the
* same work as `kopadr` in a standard Csound opcode definition.
*/
virtual int kontrol(CSOUND *csound, MYFLT **outputs, MYFLT **inputs) = 0;
/**
* Called by Csound when the Csound instrument that contains this
* instance of the `CxxInvokable` is turned off.
*/
virtual int noteoff(CSOUND *csound) = 0;
};
i_thread - The "thread" on which this CxxInvokable
will run:
- 1 = The
CxxInvokable::init
method is called, but not theCxxInvokable::kontrol
method. - 2 = The
CxxInvokable::init
function is not called, but theCxxInvokable::kontrol
method is called once for every kperiod during the lifetime of the instrument. - 3 = The
CxxInvokable::init
method is called once at the init pass for the instrument, and theCxxInvokable::kontrol
method is then called once every kperiod during the lifetime of the instrument.
[m_input_i,...] - 0 or more Csound variables, of any type, size, shape, or
rate, as defined in entry1.c.
These are actually the input arguments provided by the Csound runtime to cxx_invoke
.
[m_output_1,...] - From 0 to 40 Csound variables, of any type, size,
shape, or rate. These are actually the output arguments provided by the Csound
runtime for cxx_invoke
.
The S_cxx_invokeable symbol is looked up in the loaded dynamic link library,
and a new instance of the CxxInvokable
class is created. cxx_invoke
then
calls the CxxInvokable::init
method with the input and output arguments, and
any output values computed by the CxxInvokable
are returned in the elements
of the outputs argument.
Because of the variable numbers and types of arguments, it is virtually
impossible for cxx_invoke
to perform type checking at compile time. The user
must therefore take care to defie the correct numbers, types, shapes, and
rates for these parameters and return values.
If the thread
parameter is 2 or 3, the CxxInvokable::kontrol
method is
called once per kperiod during the lifetime of the opcode. Any output values
computed by the CxxInvokable
must be returned in elements of the outputs
argument.
When the Csound instrument that has created the cxx_invoke
opcode is
turned off, Csound calls the CxxInvokable::noteoff
method. At that
time, the CxxInvokable
should release any system resources or memory
that it has acquired.
The CxxInvokable
instance is then deleted by the cxx_invoke
opcode.
An opcode written in C++ for the CXX opcodes should run at the same speed as the same code running as a statically compiled plugin opcode, which is usually about 2 to 3 times faster than the same algorithm implemented in the Csound orchestra language.
The cxx_example.csd
file uses the cxx_compile
opcode to compile a reverb
opcode and instrument and a score generator. For the sake of clarity, although
all of this code could be implemented in one module, the following separate
modules are
defined:
-
A reverb opcode, written in C++, which is then wrapped in a Csound instrument definition.
-
A score generating function, written in C++.
The Csound orchestra in this piece uses the signal flow graph opcodes to connect the guitar instrument to the output instrument, where reverb is applied.
cxx_os
- Returns two strings, the first identifying the operating system
targeted by the compiler, and the second listing the compiler macros that
were defined by the compiler.
The cxx_os
opcode returns two strings, the first identifying the operating
system targeted by the compiler, and the second listing the compiler macros
that are defined by the compiler. This can be used for conditionally executing
different code in the Csound orchestra depending on the operating system.
That, in turn, can be used to write orchestras that work without modification
on different operating systems.
S_os, S_macros cxx_os
The cxx_hello.csd
file uses the cxx_os
opcode to print the operating
system name and a list of compiler macros that identify the operating system.
The operating system name in turn is used to execute an appropriate compiler command for that operating system.
cxx_raise
- Immediately raises the specified operating system signal.
The cxx_raise
opcode immediately raises an operating system signal,
identified by the string form of the usual macro constant:
"SIGTERM" - Termination request, sent to the program. This can be used to force Csound to exit, when otherwise it would hang.
"SIGSEGV" - Invalid memory access (segmentation fault).
"SIGINT" - External interrupt, usually initiated by the user. This can be used when debugging, to force Csound to break execution.
"SIGILL" - Invalid program image, such as invalid instruction.
"SIGABRT" - Abnormal termination condition, as is e.g. initiated by abort().
"SIGFPE" - Erroneous arithmetic operation, such as divide by zero,
This overrides any existing signal handlers or atexit callbacks set by Csound.
cxx_raise S_signal_name
-
Install the C++ toolchain of your preference.
-
Either download a binary release of these opcodes for your system, or compile them from source code using:
cmake . make
-
Copy the
cxx_opcodes.so
file to yourOPCODE6DIR6
directory, or load it with Csound's--opcode-lib="./cxx_opcodes.so"
option. -
Test by executing
csound cxx_example.csd
.
Michael Gogins
https://github.com/gogins
http://michaelgogins.tumblr.com