Generally, when working with the Vult language, there isn’t a strict rule on how to integrate the generated code into your project. However, there are some patterns that I’ve found to be particularly useful, depending on the nature of your project. Let me walk you through a few of these strategies.
NOTE: I have not compiled all the code shown here, so there may be typos
When crafting an audio plugin, there’s typically a primary processing function responsible for rendering the audio. The frequency of its invocation depends on the framework, occurring either per sample or for an entire block (comprising many samples). This function handles audio data, and requires high sample rates, often exceeding 44100 samples per second.
In contrast, adjustments to knobs or parameters in your graphical user interface (GUI) can be processed at a lower sample rate. When a parameter changes, it might be necessary to trigger a set of calculations that don’t require repetition with every audio sample. Take, for instance, a filter where coefficients can be recalculated at a lower sample rate, either when a parameter changes or at the completion of each audio block.
A good way of organizing your Vult code for this use case is the following:
On the C++ side, the code would look something like this:
For a more complete example, you can take a look at the template for VCV Rack plugins in the following repository https://github.com/vult-dsp/RackPlayground
At present, the Vult language lacks a built-in mechanism for crafting polyphonic instruments. Nonetheless, it’s straightforward to achieve by taking the generated code and creating multiple instances. In VCV Rack, I employ the following pattern.
Some optimizations can enhance performance; for instance, deactivating voices. If the call to Processor_setParameters
is resource-intensive (perhaps involving coefficient computation) it’s feasible to copy the result from one instance to others, as illustrated in the following code:"
I leverage the Vult language to write code for my non-analog Eurorack modules, which you can explore at https://www.vult-dsp.com/hardware
Essentially, all my projects with microcontrollers incorporate some underlying Vult code. The way of integrating it differs sligthly depending on the platform.
For some platforms the Vult language has a very convenient way of integrating the code in the form of templates. One of such templates is for the Teensy Audio library. You can find an example on how to use it here: https://github.com/modlfo/teensy-vult-example
When dealing with Arduino (and compatible) boards that don’t involve audio processing, my preferred approach is to expose certain common Arduino functions to the Vult side through ’external’ functions. Here’s an example illustrating how to read buttons and knobs while generating analog output.
In this example, the external function calls like digitalRead
are replaced by the stub functions stub_digitalRead
. In order for this to compile nicely, we need to provide such functions. Since Arduino compiles C++ code, we just need to put somewhere in the sketch the definitions. Then we can simply declare, initialize and call the Vult code in the sketch.
Alternatively, you can get data out of your processor
by using functions or accessing the fields directly.
Some of the applications I work on involve audio processing. In such cases, the code closely resembles the ‘Audio Plugin’ pattern, where the task involves rendering a buffer of samples. The specifics vary from platform to platform. But usually you have something like this on the C/C++ side.