Voltage is a modular synthesizer in the truest sense. A Voltage module is a stand-alone object, capable of producing any sound a programmer can dream up. Every module in Voltage is a self-contained unit, with its own processing code, artwork, and resources. Its capabilities are practically limitless - from filters to oscillators, from complex effects to MIDI arpeggiators, from mixers to sequencers, the sky's the limit.
Voltage Modules are written in Java, and all code is Java 8 SE compliant. All of the capabilities of the Java language are at your disposal, including multithreading, encryption, networking capabilities, and more. Voltage is powered by a highly optimized “hot spot” virtual machine that compiles the Java functions directly to machine language, so modules run at essentially the same speed as compiled native code.
The Initialize() function gets called once, when a module is first created. Any default values can be set up in this function. For example, filters and oscillators can be configured, effect algorithms can be set to their default values, lists can be populated, etc.
For every sample that is generated by Voltage Modular, a module’s ProcessSample() function is called. This is the most important function of any module, as it does the core work of the module. Typically, the primary operation of a module involves reading one or more input signals from input jacks, processing these signals, and outputting the processed values to output jacks. Of course, some modules, such as oscilloscopes, may not have output jacks, and other modules, such as simple oscillators, may not have input jacks.
While controls such as knobs, sliders, jacks, buttons, etc., are drawn natively by the Voltage application, they are created by calls from the Java code, and notifications are sent to the Java module when controls are modified. By processing these notifications, controls such as gain knobs, frequency sliders, etc. can alter the sound of a module. The Notify() function gets called whenever controls are modified. In addition, notifications can be sent for mouse events, connecting and disconnecting cables, GUI updating, and other events.
Initialize(), ProcessSample() and Notify() are the critical functions necessary to build almost any module. However, there are additional functions that you can use to save and restore custom settings, format custom tooltip text, handle custom undo/redo commands, and more. With many simple modules it may not be necessary to write code for these additional functions, but advanced modules will find that these extra functions will allow you to customize your modules in many useful ways.
All samples within Voltage module are 64-bit floating-point doubles at 48Khz. Audio coming into and going out of Voltage will be seamlessly resampled to match the sample rate of the host application with a high-quality resampling algorithm.
Floating-point denormals are deactivated (flushed-to-zero) on the CPU level by Voltage, so your DSP code does not need to worry about correcting for denormals.
Voltage modules are designed to process a nominal input and output range of +/- 5.0 “volts.” Incoming audio is mapped from (-1.0, 1.0) to (-5.0, 5.0) and outgoing audio is mapped from (-5.0, 5.0) to (-1.0, 1.0) when it is returned to the sound device or to your DAW. Modules can certainly output higher or lower voltages - there is no limit - but some modules, such as filters, are likely to clip when signals above (-5.0, 5.0) are received.
In this way, audio and control signals can be used completely interchangeably. LFOs and oscillators output (-5.0, 5.0) and audio signals coming in into Voltage have the same range. You can use audio to modulate values or run an LFO through a filter to create an unusual wave shape. There should be no distinction whatsoever between audio signals and control signals, and your modules should function with any signals.
Because multiple individual modules can be processed simultaneously in a multithreaded system, they can read samples and output samples before or after the modules around them have read or output the next sample. Module interactions with cables always need to be one sample behind, so that inputs are all the previously calculated values, and the outputs are calculated as inputs for the next round of samples. In summary, each cable used introduces one sample of latency.