Usage ===== Installation ------------ Faust-ctypes can be installed from PIP .. code-block:: shell $ pip install faust-ctypes or one can get the sources from GitLab .. code-block:: shell $ git clone https://gitlab.com/adud2/faust-ctypes Compiling Faust DSP Code Into a DLL ----------------------------------- Let's start with a trivial Faust program ``tests/dsp/minimal.dsp`` .. literalinclude:: ../../tests/dsp/minimal.dsp To compile it to a DLL, we must use a special architecture file (see `Faust Documentation on architecture files`_ for more details) called ``dllarch.c``. It is provided with the package. One must also ensure C and Faust compiler are installed. One can then use Faust compiler to generate C code : .. code-block:: shell $ faust -lang c -A -a dllarch.c This C code can then be compiled into a DLL by any C compiler. For instance, on the git repository .. code-block:: shell $ faust -lang c -A faust_ctypes -a dllarch.c tests/dsp/minimal.dsp > tests/dsp/minimal.c $ gcc -fPIC -shared tests/dsp/minimal.c -o tests/dsp/minimal.so Or using the provided Makefile .. code-block:: shell $ make tests/dsp/minimal.so Loading a DLL from Python ------------------------- We can now load this file as a DLL using CTypes >>> import ctypes >>> dll = ctypes.CDLL("../tests/dsp/minimal.so") >>> dll Yet, a raw DLL is not very convenient to use. For instance, one has to handle function signatures by hand. One must also handle memory and initiate C objects. The wrapper is made to automate this repetitive work. >>> from faust_ctypes.wrapper import Faust >>> dll = ctypes.CDLL("../tests/dsp/minimal.so") >>> dsp = Faust(dll) >>> dsp or even simpler >>> from faust_ctypes.wrapper import Faust >>> dsp = Faust("../tests/dsp/minimal.so") >>> dsp The initialization of the ``Faust`` object creates and bind three components together : * The Processor object, used to handle computations, accessible from the attribute ``proc`` * The UserInterface object, used to access the user interface elements declared in the Faust program accessible from the attribute ``ui`` * The Metadata objet, a storage for metadatas gathered from source and from computations, accessible from the attribute ``meta`` Processing Data --------------- The data processor of a DSP is stored in the `proc` attribute of the wrapper. .. testsetup:: process from faust_ctypes.wrapper import Faust dsp = Faust("../tests/dsp/minimal.so") The highest-level way of processing signals through a DSP is the following .. doctest:: process >>> inp = dsp.proc.gen_io(10) >>> dsp.proc.compute(inp) array([[0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]], dtype=...) We first create a signal of 10 samples of dimension 1 (``minimal`` has one input), and then we pass it through the minimal processor using the ``compute`` method, which returns a signal of 10 samples of dimension 1 (``minimal`` has one output). One can also create an array from numpy-compatible objects using the ``from_obj`` method. .. doctest:: process >>> inp = dsp.proc.from_obj(range(10)) >>> dsp.proc.compute(inp) array([[0., 1., 2., 3., 4., 5., 6., 7., 8., 9.]], dtype=...) In both case, ``compute`` checks that ``inp`` is a :ref:`DSP-compatible input `, creates the output array, do the computations and then returns the output array. One can also provide an output array if it has been created otherwise. ``compute`` will not create the array, but instead check that it is a :ref:`DSP-compatible output `. To reprocess with different data, one can modify the input array and call directly the ``process`` method. .. doctest:: process >>> inp = dsp.proc.from_obj(range(10)) >>> out = dsp.proc.compute(inp) >>> out array([[0., 1., 2., 3., 4., 5., 6., 7., 8., 9.]], dtype=...) >>> inp[0][1] = 0 >>> dsp.proc.process(10) >>> out array([[0., 0., 2., 3., 4., 5., 6., 7., 8., 9.]], dtype=...) There is a special kind of DSP with 0 input, the synthesizers. For instance, the trivial Faust program .. literalinclude :: ../../tests/dsp/minisynth.dsp is a synthesizer with 0 input and 1 output. >>> from faust_ctypes.wrapper import Faust >>> dsp = Faust("../tests/dsp/minisynth.so") >>> dsp.proc.compute(10) array([[0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]], dtype=...) Interacting with the User Interface ----------------------------------- .. note:: the building of the interface has been mostly taken from Marc Joliet's FaustPy_ code In order to control the DSP, Faut provides a set of primitives to build a user interface with sliders, buttons, checkboxes, etc… organized into horizontal, vertical or tab groups (see `Faust Documentation`_ for more detail). These interface elements are supposed to variate slowly (compared to the sampling frequency). Thus, during one call of data processing the UI elements are considered as constants. Yet one can change them between two subsequent calls. The interface object, stored in the `ui` attribute of the wrapper, handles this. .. note:: only active widgets (checkboxes, sliders, numerical entries) has been implemented. Displays and Bargraphs are ignored for the moment Consider the following less-tivial Faust code : .. literalinclude:: ../../tests/dsp/simpleui.dsp It implements a simple sound controller: the input signal is amplified by the gain and the resulting signal is outputted iff the gate is opened. As default, the gain is set to 0 (not 0 dB, proper 0) and the gate is closed. So whatever the input is, the output will be 0. >>> from faust_ctypes.wrapper import Faust >>> dsp = Faust("../tests/dsp/simpleui.so") >>> inp = dsp.proc.from_obj(range(5)) >>> dsp.proc.compute(inp) array([[0., 0., 0., 0., 0.]], dtype=...) .. testsetup:: simpleui from faust_ctypes.wrapper import Faust dsp = Faust("../tests/dsp/simpleui.so") inp = dsp.proc.from_obj(range(5)) In order to change this, we must turn on the gain and set the gate up. In faust_ctypes (as in FaustPy), all groups are stored as "boxes" with the user interface elements that they contain as attributes, down to basic UI elements, such as buttons and sliders, called "parameters". The attribute name of the boxes are their label prefixed with `b_`, whereas the attribute name of the parameters are their label prefixed with `p_`. The Parameter interacting with the gate is then stored in `b_simpleui.p_gate` (recall that Faust creates by default a vertical group labelled with the DSP name and containing all the UI elements of the upper level). .. doctest:: simpleui >>> dsp.ui.b_simpleui.p_gate Each object has a `zone` attribute, which is a pointer to the values of the UI elements of the DSP. So, to set the gain to `.5` and open the gate, one must write .. doctest:: simpleui >>> dsp.ui.b_simpleui.p_gate.zone = 1 >>> dsp.ui.b_simpleui.p_gain.zone = .5 >>> dsp.proc.compute(inp) array([[0. , 0.5, 1. , 1.5, 2. ]], dtype=...) And the signal now passes through the processor. Accessing Metadata ------------------ The last element of the wrapper is a dictionnary containing all the metadata gathered by the compiler on the source code. With the previous example : .. doctest:: simpleui >>> dsp.meta.data {...:...} .. todo:: check if gathers metadata well .. todo:: adapt tests from FaustPy .. _`Faust Documentation`: https://faustdoc.grame.fr/manual/syntax/#user-interface-primitives-and-configuration .. _`Faust Documentation on architecture files`: https://faustdoc.grame.fr/manual/architectures/ .. _FaustPy: https://github.com/marcecj/faust_python