In a previous post, I’ve tried to use Qt for the editor window of a VST plugin. The thing is, I want to do more than just play with a GUI, I also want to see what is done to an audio stream by a plugin.
To do so, I’ve decided to expose the VST interface to Python. There are some implementation I’ve heard of, but they are based on Cython or other wrapping tools. Ctypes has the advantage of not needing a compilation step. There are also every functionality needed, as callback creation (plugins use a callback to ask the host some stuffs), and Python provides the additional mathematical tools to display what the plugin does. It may not be perfect, but it will be enough for a starter.
Wrapping the VST effect class
Wrapping a VST class is not an easy task. The plugin is accessed by a C structure with pointer functions for the main functionalities: processing an audio flow (with floats or doubles), setting and getting parameters, and a general function for setting and getting information. Additionaly, when instantiating a plugin, a callback must be given. This ctypes callback will have to be stored inside the wrapper so that it stays valid through the plugin lifetime.
So the C structure is created as a class that inherits from ctypes.Structure. Then I have to populate a class that will call the correct function or return the appropriate element inside this structure.
class AEffect(Structure): _fields_ = [ ('magic', c_int), ('dispatcher', c_void_p), ('process', c_void_p), ('setParameter', c_void_p), ('getParameter', c_void_p), (...) ] audiomaster_callback = CFUNCTYPE(c_void_p, POINTER(AEffect), c_int, c_int, c_long, c_void_p, c_float)
class VSTPlugin(object): def __init__(self, filename, audio_callback = basic_callback): """ Constructor Parameters: filename is the name of the plugin to load audio_callback is the Python function to call (optional) """ self.__lib = CDLL(filename) self.__callback = audiomaster_callback(audio_callback) try: self.__lib.VSTPluginMain.argtypes = [audiomaster_callback, ] self.__lib.VSTPluginMain.restype = POINTER(AEffect) self.__effect = self.__lib.VSTPluginMain(self.__callback).contents except AttributeError: self.__lib.main.argtypes = [audiomaster_callback, ] self.__lib.main.restype = POINTER(AEffect) self.__effect = self.__lib.main(self.__callback).contents
The main VST function must be called through a dispatch function available in the structure. I will only show of them:
Rewritting the minihost sample
The minihost sample prints some details of the VST plugin and then displays them. Here, I’ll do the same, but I’ve processed a sine-sweep signal and then display the result. If the 64bits processing is available, I will use it.
Here are some info that are displayed prior to the processing for the Big Tick NastyShaper plugin:
Plugin name: Vendor name: Product name: numPrograms = 16 numParams = 11 numInputs = 2 numOutputs = 2 Program 000: Nasty Shaper (...) Program 015: Nasty Shaper Param 000: Pre-Gain [0.00 dB] (normalized = 0.500000) Param 001: Post-Gain [0.00 dB] (normalized = 0.500000) Param 002: WS1 [25.00 %] (normalized = 0.250000) Param 003: WS2 [35.00 %] (normalized = 0.350000) Param 004: WS3 [45.00 %] (normalized = 0.450000) Param 005: WS4 [55.00 %] (normalized = 0.550000) Param 006: WS5 [20.00 %] (normalized = 0.200000) Param 007: WS6 [60.00 %] (normalized = 0.600000) Param 008: WS7 [30.00 %] (normalized = 0.300000) Param 009: WS8 [40.00 %] (normalized = 0.400000) Param 010: Oversample [OFF ] (normalized = 0.000000) Testing with floats (32bits)
Here is a graphical result. On the first row, I display the original input, on the second row is the associated output.
To be continued
The whole 2.4 standard is not yet wrapped, far from it. There is still much to do to be able to use this wrapper class for every plugin (how do I load an impulse for a convolution reverb for instance), but it can still help analyze how a lot of plugins change the audio signal.
Some plugins are not working yet (mainly because every input and output must be connected), but I’m working on it 😉
The code is available on Launchpad.