Module Functions and Variables
While the code to create a module’s controls is automatically generated, it is up to the programmer to write the code that makes the module actually do something useful. The following functions are available to the module programmer.
User Variables & Functions
This section of the Source Code is where programmers can define any member variables and custom functions for their class. For example, if you want to store the current volume in a variable, you might write:
private int volume;
If you wanted to create your own function to set the current volume, you might write:
private void SetVolume(double newVolume)
{
this.volume = newVolume;
}
public void Initialize()
This function gets called when a module is first created. It is very useful for setting up default values, as well as initializing effects algorithms, oscillators, filters, etc.
If we have a knob called masterVolume, we might write the following Initialize code to read the start position of the knob:
public void Initialize()
{
// add your own code here
This.volume = masterVolume.GetValue(); // This gets the current value of the knob
}
public void Destroy()
This function is called when a module is deleted. Typically, it is not necessary to write any code in the Destroy()
function, because Java will automatically free any member variables. However, for more complex modules, it might be desirable to end timers, close network connections, close files, etc.
public boolean Notify( VoltageComponent component, ModuleNotifications notification, double doubleValue, long longValue, int x, int y, Object object )
This important function is used to receive notifications from Voltage when:
Controls are altered
Mouse events occur
Cables are connected or disconnected from jacks
Canvas controls are about to paint, or are finished painting
A GUI Update Timer has fired
It is likely that many additional notifications will be added to Voltage Modular over time.
A typical use case would be to receive a notice when a knob is turned:
public boolean Notify( VoltageComponent component, ModuleNotifications notification, double doubleValue, long longValue, int x, int y, Object object )
{
// add your own code here
switch(notification)
{
case Knob_Changed:
{
if (component == masterVolume) // If the Master Volume knob is turned..
this.volume = doubleValue; // ..then store its new value in our volume variable.
}
}
}
public void ProcessSample()
This function gets called once for every sample that gets generated by Voltage. Typically, this function will read input signals from input jacks, process the audio through a DSP algorithm, and write output signals to output jacks.
All audio generated by Voltage is fixed at 48 kHz, regardless of the sample rate of the host application. As a result, you can build fixed sample-rate DSP algorithms that do not need to adjust for sample rate changes.
It is always a good idea to write intelligent and efficient code in your ProcessSample()
algorithm. If a module is not connected to any outputs, for example, it may be possible to skip sections of CPU-intensive DSP processing. You will always want to write a value of 0 to any outputs, even if you’re skipping DSP processing, so that values do not get “stuck” on a cable when a module’s inputs are disconnected.
A typical example of a ProcessSample()
function might look like this:
public void ProcessSample()
{
// add your own code here
double inputSignal = inputJack.GetValue();
// Adjust the input signal by our volume value
double outputSignal = inputSignal * this.volume;
outputJack.SetValue(outputSignal)
}
public String GetTooltipText( VoltageComponent component )
Voltage will automatically generate a tooltip whenever a knob or slider is adjusted. If a knob has a range of 0 to 1.0, that exact value will be shown in the tooltip. However, if the “Display Values In Percent” flag is set for a knob or slider, then the value will be multiplied by 100 and displayed with a percent sign, so a value of 1.0 will be displayed as 100%.
In some cases, you may wish to show a different value. For example, you may wish to show a value with particular units. Or, for example, filter cutoff frequency knobs work best when they operate logarithmically instead of linearly. In these cases, you can add code to the GetTooltipText()
function to return a custom tooltip for a specific control.
A typical example of a GetTooltipText()
function might look like this:
public String GetTooltipText( VoltageComponent component )
{
if ( component == null )
return super.GetTooltipText( component, bChanging );
// add your own code here
If (component == frequencyKnob) // If the Frequency Knob is about to show a tooltip..
{
return String.format("%.1f Hz", frequencyKnob.GetValue()); // Return tooltip with unit “Hz”
}
return super.GetTooltipText( component );
}
public void EditComponentValue(VoltageComponent component, double newValue)
A Voltage user can choose to type in an exact value on a knob and slider instead of just moving the control. If a knob has a range of 0 to 1.0, then the user can type in a value between 0 and 1.0, and that value will be set in the knob. However, if the “Display Values In Percent” flag is set for a knob or slider, then the value types in will be divided by 100. So, for example, if the user types in 100, the value will be set to 1.0.
As in GetTooltipText, certain controls may display tooltip values that do not line up directly with the minimum and maximum values of the control. For example, a filter knob may have a min of 0 and a max of 1.0, but may display values from 20 to 20000, with a logarithmic response. In these cases, if a user types in a frequency that they want to set the knob to, e.g. 10000, this function can be used to map the value that is typed in to the correct linear mapping. For example, the value 10000 may map to a linear value of .20 on a knob.
In this case, your code must change the newValue variable to a correct linear mapping between the Min and Max values of the control.
public byte[] GetStateInformation()
public void SetStateInformation(byte[] stateInfo)
When saving a preset, Voltage will store the position of every knob and slider in your module, the state of all toggle buttons, and the state of all cables that are plugged into any of the module’s jacks. These values will be restored when the user loads the preset. For many modules, this means that the entire state of a module will be automatically saved and reloaded.
However, many more complex modules have have controls that affect internal values. For example, there might be buttons for transposing an octave setting up or down, or for setting a MIDI channel, or for loading a sample. In these cases, storing the knob, slider, and toggle button positions won’t be enough to restore the state of a module.
In these cases, additional module information, such as the selected octave, MIDI channel, file path, etc., can be saved and restored using the GetStateInformation()
and SetStateInformation()
functions. GetStateInformation()
returns a byte array that can contain any sort of information that you want to store, in any format. SetStateInformation()
receives that same byte array back and must decode it.
We recommend using the java.util.Properties class to easily store various properties for your module, and the java.io.ByteArrayOutputStream and java.io.ByteArrayInputStream classes to convert the Properties object into a byte array, and later convert the byte array back to a Properties object. However, you can use any method you’d like to store and restore the data. For example, a String can be turned into a byte array as follows:
String fileName = “sound.wav;
byte[] byteArray = fileName.getBytes(StandardCharsets.UTF_8);
The byte array can be turned back into a string as follows:
String decodedFileName = new String(byteArray, StandardCharsets.UTF_8);
In addition, the java.io.ByteArrayOutputStream and java.io.ByteArrayInputStream classes can be used to turn any serializable class into a byte stream, so you could easily create your own serializable class to store module data.
Note: any Java imports that you wish to add to the project, such as
import java.io.ByteArrayOutputStream;
import java.io.ByteArrayInputStream;
can be placed in the “User Imports” section of the Source Code.
public void OnUndoRedo( String undoType, double newValue, Object optionalObject )
Voltage will automatically create Undo and Redo events whenever a knob, slider, or toggle button is changed. However, more complex modules may have situations where custom undo operations are required. For example, using buttons to change a MIDI channel, change a transpose setting, or load a sample should generate custom undo events. Fortunately, this is very easy to accomplish using the CreateUndoNode()
functions that are built in to every module. There are two of these functions:
public native void CreateUndoNode(String undoType, String description, double previousValue, double newValue);
public native void CreateUndoNode(String undoType, String description, Object previousValue, Object newValue);
The first function stores the previous value and the new value as a double. The second function allows you to store the previous value and new value as a generic Object. You can pass in any object, such as a String.
When incrementing a MIDI channel, you might call:
CreateUndoNode(“changeMidiChannel”, “Change Midi Channel”, currentChannel, currentChannel + 1);
That’s all you need to do. The first string, “changeMidiChannel”, identifies the undo operation. This label will be passed in to OnUndoRedo()
as the String undoType. The second string, “Change Midi Channel”, is the description of the undo event, and is shown to the user if they hover over Voltage Modular’s undo or redo buttons. Finally, currentChannel and currentChannel+1 are the “before” and “after” values. The “before” value will be passed in to OnUndoRedo()
when a user hits Undo. The “after” value will be passed in to OnUndoRedo()
when a user hits Redo.
Here is a typical example of using this function:
public void OnUndoRedo( String undoType, double newValue, Object optionalObject )
{
// add your own code here
if (undoType == “changeMidiChannel)
currentChannel = newValue;
}