EFFECTS / MIDI

VFX Script

VFX Script is used in Patcher to manipulate MIDI notes and automation parameters using Python - a powerful programming language. In short - it allows real-time transformation of incoming Note and Automation data, using any logic Python supports. The processed data can then be routed to connected Patcher modules or used to trigger other actions.

VFX Scripts can turn simple Patcher setups into advanced audio applications by introducing sequential logic and memory. This means the output can depend on previous states, not just the current input - enabling dynamic and responsive behavior.

Programming skills are not required to use VFX Script, thanks to a growing selection of pre-made presets. User-created presets are shared in the VFX Scripting forum.

NOTE: VFX Script must be connected to Automation or Event (note) targets on plugins to do work. It modifies this data as an input and passes it onto the connected plugin as an output.

Video

Tabs

VFX Script has four tabs:

  • User interface - Controls created in the script will be displayed here. Embedded surfaces will appear here after being called in the script.
  • Script - The Python code is written here. For script examples, see the final section of this page.
    • Compile - Press this to run the script. Any errors are displayed at the bottom after compiling.
    • Restart - Restarts the script without resetting the User interface.
    • Options - Use the arrow menu to embed a custom Control Surface preset. The preset must be called in the script to make it visible.
  • Log - Displays text when the print() function is used. Previous text can be erased with the backspace key.
  • API reference - Lists Python functions and classes specific to VFX Script. The ScriptDialog class (contains functions for adding controls to the User interface) is documented in the Piano Roll Scripting API.

Connecting VFX Script

Add VFX Script from the Plugin database, Plugin Picker or Plugin Menu into Patcher. By default there is a MIDI connection enabled, but inputs and outputs can be added in the script as needed. After defining them, Right-click the plugin icon in Patcher to activate and link to instrument or effects targets in Patcher.

Errors

After compiling, you may receive an error pointing out a problem in your script. It will contain the type, description and line the error occurs in. The following example shows a syntax error caused by a missing bracket in line 22.

Example: SyntaxError (expected '(' (, line 22))

As there are many types of possible errors, you can ask for help in the VFX Scripting forum or check the Python website for resources. It is recommended to work through an introductory programming course before getting into scripting.

Example Scripts

Printing to Log

Prints a message to the Log when compiled.

print("Hello World!")

Adding inputs and outputs

Creates an input knob to the User interface and passes its value to an output. Activate inputs and outputs in Patcher by Right-clicking on VFX Script.

import flvfx as vfx

# Adds an output controller 

vfx.addOutputController('knob_out', 0)
    
# Event handler function.
def onTick():

  # Sets the output to match the value of the input.
  vfx.setOutputController('knob_out', vfx.context.form.getInputValue('knob_in') * 100)
    
# Creates a dialog to the User interface.
def createDialog():

  form = vfx.ScriptDialog('', '')
  form.addInputKnob('knob_in', 0, 0, 0.01) # Defines a knob called 'knob_in'.
  return form

Embedding a surface

Displays an embedded Control Surface in the User interface. Before compiling this script, click the arrow (Options) in the Script tab and embed a surface preset.

import flvfx as vfx

# Creates a dialog and adds the surface preset to the User interface.    
def createDialog():

  form = vfx.ScriptDialog('','') # Defining a dialog called 'form'.
  form.addInputSurface('') # Embedding the Control Surface.
  return form 

NOTE: Surface presets are saved to .../Documents/Image-Line/Presets/Plugin presets/Effects/Control Surface.

Accessing controls in a surface preset

Connects a button in a surface preset to an action. When the button is pressed, a message will be printed to the Log. Before compiling this script, click the arrow (Options) in the Script tab and embed a surface preset containing at least one control. The name of the control will be referenced in the script.

import flvfx as vfx

# Event handler function.
def onTick():

  # Define global variables for btn and counter.
  global btn
  global counter
  
  # Set btn to the input value of a surface control called 'btn'. 
  btn = vfx.context.form.getInputValue('btn')
  
  # Conditional structure to check if btn was pressed to display a message in the Log.
  # The counter ensures only one message per button press gets printed. 
  if btn == 1 and counter == 0:
    print("Pressed!")
    counter = 1  
  if btn == 0:
    counter = 0

# Creates a dialog and adds the surface preset to the User interface.
def createDialog():  
  form = vfx.ScriptDialog('','')
  form.addInputSurface('')
  return form

NOTE: Surface presets are saved to .../Documents/Image-Line/Presets/Plugin presets/Effects/Control Surface.

Tutorial Scripts

The following simple scripts include many comments describing the function of the various parts of the code, and can be used as a general guide for how scripts should be constructed. These scripts are also inlcuded among the presets of VFX Script.

Tutorial 1 - simple note bypass

"""
1. Bypass Script - Passes notes unaffected. This is done by creating a new voice
(i.e. a note) for each incoming voice, and copying the properties of the
incoming voice to the new one.
"""
import flvfx as vfx # VFX Script API, for FL <-> Python communication

# Create a modified voice class, which inherits from the vfx.Voice class, and
# add any needed properties. In this example we create a parentVoice property
# so that the new voice can keep tabs on the incoming voice that spawned it.
class ModifiedVoice(vfx.Voice):
  parentVoice = None

# onTriggerVoice(incomingVoice) is called whenever there is a note event
# happens, with incomingVoice (a vfx.Voice) being that event. In this example
# we create a new voice based on the incoming voice and trigger it. Note that
# incoming voices can't be triggred in the script.
def onTriggerVoice(incomingVoice):
  v = ModifiedVoice(incomingVoice) # Create voice, copy values from incomingVoice
  # v.note = frequency as MIDI note number
  # v.finePitch = note offset (fractional note number)
  # v.pan = stereo pan [-1,1]
  # v.velocity = note velocity/loudness [0,1]
  v.parentVoice = incomingVoice # Keep track of the parent voice that spawned new voice
  v.trigger() # Trigger the new voice

# onTick is called for every tick of the clock. In this example, we go through
# the active voices and for each, copy the properties of the parent voice. This
# done so that any variation in pitch (e.g. from slide notes) is properly conveyed
# to the new voice.
def onTick():
  # vfx.context.tick = current clock time, in ticks
  # vfx.context.PPQ = ticks per quarter note
  for v in vfx.context.voices: # For all active voices...
    v.copyFrom(v.parentVoice) # Copy properties of parent voice

# onReleaseVoice(incomingVoice) is called whenever an incoming voice gets
# released (i.e. ends). In this example, we go through the active voices, and
# if any have a the incoming voice as a parent, we release that voice.
def onReleaseVoice(incomingVoice):
  for v in vfx.context.voices: # For all active voices...
    if v.parentVoice == incomingVoice: # If its parent is the incoming voice...
      v.release() # End the voice/note

# The createDialog() call is for creating the UI for controlling the script.
# In this example there is only some text displayed, but this would be where
# controls would be defined.
def createDialog():
  form = vfx.ScriptDialog('', 'Bypass script - passes notes unaffected.')
  return form

Tutorial 2 - voice modification

"""
2. Voice Modification Script - Takes incoming voices and modifies them, providing
controls to change the pitch, pan and velocity of the voice.
"""
import flvfx as vfx # VFX Script API, for FL <-> Python communication

# In this case, we want to add some extra properties to the modified note class.
# We want to keep track of the pitch offset, the modified pan, and the modified
# velocity values. We need to keep track of them so that they can be applied both
# at trigger time, and also during onTick() updates.
class ModifiedVoice(vfx.Voice):
  parentVoice = None  # Original incoming voice
  noteOffset = 0  # Pitch offset, in semitones
  modifiedPan = 0  # Midified pan: [-1, 1]
  modifiedVelocity = 0.8  # Modified velocity: [-1, 1]

# When an incoming voice is triggered, we need to make a new voice that is based
# on the incoming one, and then set the values of
def onTriggerVoice(incomingVoice):
  v = ModifiedVoice(incomingVoice)
  # Get pitch offset, pan, and velocity from the UI inputs:
  v.noteOffset = vfx.context.form.getInputValue('Pitch Offset')
  v.modifiedPan = vfx.context.form.getInputValue('Pan')
  v.modifiedVelocity = vfx.context.form.getInputValue('Velocity')
  # Apply the pitch offset, pan, and velocity to the voice:
  v.note += v.noteOffset
  v.pan = v.modifiedPan
  v.velocity = v.modifiedVelocity
  v.parentVoice = incomingVoice # Keep track of the parent voice that spawned new voice
  v.trigger() # Trigger the new voice

# We need to copy the values from the parent voice every tick, in order for note
# slides to work. And once the voice properties have been copied from the parent
# voice, we need to re-apply the pitch offset, pan, and velocity, using the values
# that were stored in the modified voice class.
def onTick():
  for v in vfx.context.voices:  # For all active voices:
    v.copyFrom(v.parentVoice)  # Copy properties of original voice
    v.note += v.noteOffset  # Re-apply pitch offset
    v.pan = v.modifiedPan  # Re-apply pan
    v.velocity = v.modifiedVelocity  # Re-apply velocity

# Release voices when their corresponding parent voice gets released.
def onReleaseVoice(incomingVoice):
  for v in vfx.context.voices:  # For all active voices...
    if v.parentVoice == incomingVoice:  # If its parent is the incoming voice...
      v.release()  # End the voice/note

# In this case there will be controls on the UI for controlling the pitch offset,
# the pan, and the velocity. UI controls are created in the createDialog() call,
# and the controls are then accessed using vfx.context.form.getInputValue().
def createDialog():
  form = vfx.ScriptDialog('', 'Add note offset, set pan and velocity values.')
  form.addInputKnobInt('Pitch Offset',0,-12,12,hint='Pitch offset in semitones')
  form.addInputKnob('Pan',0,-1,1,hint='Voice Pan')
  form.addInputKnob('Velocity',0.8,0,1,hint='Voice Velocity')
  # UI types that are available:
  # form.addInputKnob(name, default, min, max, hint)
  # form.addInputKnobInt(name, default, min, max, hint)
  # form.addInputCheckbox(name, default, hint)
  # form.addInputCombo(name, option_list, default_index, hint)
  # form.addInputText(name, default)
  return form

Tutorial 3 - voice generation

"""
3. Voice Creation Script - Generates voices at a steady rate. The time between
generations, the note number used, the pan and velocity, and the duration of the
voice can be controlled.
"""
import flvfx as vfx # VFX Script API, for FL <-> Python communication

# Since we are generating voices in this case (rather than modifying voices), we
# don't need onTriggerVoice() and onReleaseVoice() calls. Most of the work is
# done during the onTick() call, where we'll use vfx.context.ticks and
# vfx.context.PPQ to see what the clock time is, and if a voice should be
# generated on that tick.
def onTick():
  # Get parameters from UI controls:
  note_number = vfx.context.form.getInputValue('Note Number')
  note_period = pow(2,vfx.context.form.getInputValue('Period')-1)
  note_length = vfx.context.form.getInputValue('Length')
  note_pan = vfx.context.form.getInputValue('Pan')
  note_velocity = vfx.context.form.getInputValue('Velocity')
  # Set period between voice generations (PPQ = ticks/quarter note)
  stepTicks = int(note_period*vfx.context.PPQ/2) # Time between notes in ticks
  if vfx.context.isPlaying and vfx.context.ticks % stepTicks == 1:
    v = vfx.Voice()  # Create new voice
    v.note = note_number  # Set pitch via note number
    v.length = int(note_length*stepTicks) # Set note length in ticks
    v.pan = note_pan  # Set voice pan
    v.velocity = note_velocity  # Set voice velocity
    v.trigger()  # Trigger the voice

# Note that since we are generating voices and setting their length at trigger
# time, we don't need to manually release them; they will be release automatically
# at v.length ticks after triggering.

# We create the needed UI controls in the createDialog() call:
def createDialog():
  form = vfx.ScriptDialog('', 'Generate a steady pulse of notes.')
  form.addInputKnobInt('Note Number',60,0,131,hint='Note number in semitones')
  form.addInputKnobInt('Period',1,1,4,hint='Time between notes in 1/8th notes')
  form.addInputKnob('Length',0.5,0,1,hint='Note length (% of period)')
  form.addInputKnob('Pan',0,-1,1,hint='Voice Pan')
  form.addInputKnob('Velocity',0.8,0,1,hint='Voice Velocity')
  return form

Tutorial 4 - control signal generation

"""
4. Control Signal Generation Script - Creates a classic sinusoidal LFO that can
be used to modulate plugin parameters. In order to generate control signals,
"Output Controllers" must be created in the script.
"""
import flvfx as vfx # VFX Script API, for FL <-> Python communication
import math # Extra Python library for math functions

# Here we'll define some variables that will need to be accessed by multiple
# different function calls within the script. In this case, the phase of the LFO
# must persist from one onTick() call to another, and the list of options for
# how the clock is synced will be used by both onTick() and createDialog():
phase = 0  # Current phase of the LFO (updated during onTick())
clock_options = ['Free','Synced']  # Clock sync control options

# We are only dealing with a control signal in this script, so we do not need
# onTriggerVoice() or onReleaseVoice() calls. We need an onTick() call so that 
# we can update the control signal every tick and set the output value.
def onTick():
  global phase

  # Grab the current control values from the UI elements
  clock_option = clock_options[vfx.context.form.getInputValue('Speed: Clock')]
  amp_scale = vfx.context.form.getInputValue('Amplitude: Amplitude')
  amp_offset = vfx.context.form.getInputValue('Amplitude: Offset')

  # Determine the current phase of the LFO. Note that we give the user the option
  # have the phase synced to the clock, or to let it run free. If synced, then
  # we get the clock time in ticks and derive the phase from that. If free, then
  # we increment the phase from where it was last tick (which is why we need
  # phase to be a global variable that persists between calls)
  phase_increment = 2*math.pi*vfx.context.form.getInputValue('Speed: Speed')/vfx.context.PPQ
  if clock_option == 'Synced':
    phase = vfx.context.ticks*phase_increment  # Set phase based on clock
  elif clock_option == 'Free':
    phase += phase_increment  # Increment phase from its previous value

  # Create the sinusoidal LFO using math.sin() and current phase
  LFO = 0.5*math.sin(phase) + 0.5
  # Apply amplitude scaling to the LFO
  LFO = amp_scale*(LFO-0.5) + 0.5
  # Apply a prescribed offset to the LFO
  LFO += amp_offset*0.5*(1.0-amp_scale)
  # Set the output controller with the current LFO value
  vfx.setOutputController('LFO',LFO)

# In addition to the desired UI controls, we will also create an output controller
# using the vfx.addOutputController(name, default_value) call. Output controllers
# are used to output control signals for controlling/modulating parameters in other
# plugins in Patcher. Also note that we are grouping the UI controls using the
# form.addGroup() and form.endGroup() functions. Input groups are given a name, 
# and that name is then referenced when accessing the UI elements elsewhere in the
# script ('GroupName: InputName').
def createDialog():
  form = vfx.ScriptDialog('', 'Sinusoidal LFO.')
  form.addGroup('Speed') # Begin UI group, give it a name
  form.addInputKnob('Speed',0.5,0,1,hint='LFO speed: cycles / quarter note')
  form.addInputCombo('Clock',clock_options,0,hint='Sync to clock or run free')
  form.endGroup() # End current UI group
  form.addGroup('Amplitude')
  form.addInputKnob('Amplitude',1,0,1,hint='LFO amplitude')
  form.addInputKnob('Offset',0,-1,1,hint='LFO offset')
  form.endGroup()
  vfx.addOutputController('LFO', 0) # Create an output controller for the control signal
  return form

API Reference

Object Members Notes
# User comments Start comment lines with #.
vfx
addOutputController(name, default) Defines an output controller (automation) pin. e.g., ('LFO', 0.0).
setOutputController(name, value) Sets the value of a defined output controller.
vfx.context
voices list: Active vfx.Voice objects triggered by this script.
ticks int: Current host clock time in PPQ ticks (read-only).
PPQ int: Project's Pulses Per Quarter note (read-only).
isPlaying bool: True if FL Studio transport is playing (read-only).
vfx.Voice
Voice() Constructor: Creates a new, empty voice.
Voice(existingVoice) Constructor: Creates new voice, copies properties from existingVoice.
(class instance) note float: MIDI note number (e.g., 60.0 = C4). Fractional for microtuning.
finePitch float: Fine pitch offset in fractional note numbers.
velocity float: Note velocity (0.0 to 1.0).
pan float: Stereo panning (-1.0 Left, 0.0 Center, 1.0 Right).
length int: Duration in ticks. If set, auto-releases after this time.
output int: Voice output for the event (0-based). Default 0.
fcut float: Mod X parameter (-1.0 to 1.0).
fres float: Mod Y parameter (-1.0 to 1.0).
trigger() Sends Note On for this voice.
release() Sends Note Off for this voice.
copyFrom(otherVoice) Copies all properties from otherVoice. Used for slide note handling.
vfx.ScriptDialog
ScriptDialog(title, description) Constructor: Creates the UI dialog.
(class instance) addInputKnob(name, def, min, max, hint) Adds a float knob.
addInputKnobInt(name, def, min, max, hint) Adds an integer knob.
addInputCheckbox(name, def, hint) Adds a checkbox.
addInputCombo(name, opts, def_idx, hint) Adds a dropdown menu. "opts" is a list of strings.
addInputText(name, def_text, hint) Adds a text input field.
addInputSurface() Uses the embedded Control Surface as a UI.
addGroup(name) Starts a named UI group.
endGroup() Ends the current UI group.
(via vfx.context) getInputValue(name) Gets value from UI control. Use 'GroupName: ControlName' for grouped controls.
setNormalizedValue(name, value) Sets value (normalized to 0-1) of a UI control.
Event Handlers
createDialog() Defines script UI. Must return a vfx.ScriptDialog instance.
onTick() Called every tick for continuous processing and updates.
onTriggerVoice(incomingVoice) Called whenever an incoming voice (i.e. note) is triggered. incomingVoice is a vfx.Voice object.
onReleaseVoice(incomingVoice) Called whenever an incoming voice is released. incomingVoice is the original vfx.Voice that triggered.

Plugin Credits: Ville Krumlinde.