PyVST: another ctypes-based Python VST wrapper

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.

Buy Me a Coffee!
Other Amount:
Your Email Address:

8 thoughts on “PyVST: another ctypes-based Python VST wrapper

  1. Hello Mattheiu,
    We appear to be working on very similar stuff. I’m currently trying to implement a python VST wrapper similar to the jVSTwRapper project, where the plugin DLL is extended by python allowing the user to create plugins using python. Were you planning on moving in this direction with your wrapper or are you making this wrapper as a host to analyse plugin processing?

    Please get back to me if you’re interested in collaboration or further discussions.
    Regards,
    B

    1. Hi,

      I’ve seen your work before, and it seem promising 🙂 I do not intend to expose Python to a VST developer at the moment. If I try this way, I’ll surely use your work 😉

  2. Hi Matthieu,

    I am also interested in running C++-based VST plugins in python and ended up onto your page. I found your github and realized that is a windows project. Have you seen that stuff run on a Mac? I might try to adapt your code otherwise. Or do you know of other VST hosts in python that would help me get started? Thanks for this great piece of code.

    1. Hi Tee,
      I didn’t have a Mac or a 64 bits Windows at the time I wrote, so for the moment, it is tested on Windows 32bits. Feel free to fork and add OS X support (also I guess something like pyAU would be more relevant on this platform 😉 ). At that time, I didn’t know of any similqr project.

  3. Awesome! Thanks Matthieu. I’ll see what I can do with the Mac port of your stuff, and will try pyAU as well. Looks all very interesting. Bonne continuation…

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.