Tuesday, June 21, 2011

Specifying a network using the Swig/Python bindings


The implicit patching model of Marsyas allows the specification of networks at run-time as recursively nested trees. Most of the readers of this blog are familiar with the cumbersome C++ syntax of using a combination of create and addMarSystem. When this run-time mechanism is coupled with a language with reasonable syntax for nested lists such as Python it is easy to use a much more concise syntax. In this post I show how by writing a short simple function this syntax can be enabled in Python. As a further bonus it is easy to serialize the network specification. First let's see how it looks. This example shows a simple filterbank that will take as input a mono sound file, filter it with two different filters and write a stereo sound file where each channel corresponds to the output of each filter. Readers not familiar with the basic composite topologies such as Series and Fanout should read the corresponding sections in the user manual.

net = ["Series/net",
        ["SoundFileSource/src",
        ["Fanout/fanout",
             ["Filter/f1", "Filter/f2"]],
        "Gain/gain",
        "SoundFileSink/dest"]]


It is easy to convert this representation (basically a nested list of strings) into various forms. For example using the json module it is straightforward to convert it to json.

print json.dumps(net)

To get the actual network we need to write a function that converts this representation
to the actual Marsyas commands to create the network. Then we can do something like:

msys = create(net)
print msys.toStringShort()
# typically after creating the network you would 
# update a couple of controls and then start ticking 

Writing such a function in C++ is not trivial but thanks to a little bit of functional magic it is relatively straightforward to write in Python. Here is the create function in Python:

# create a MarSystem from a recursive list specification
def create(net):
  composite = msm.create("Gain/id") # will be overwritten
  if (len(net) == 2):
    composite = msm.create(net[0])
    msyslist = map(create,net[1])
    msyslist = map(composite.addMarSystem,msyslist)
  else:
    composite = msm.create(net)
  return composite

For those unfamiliar with functional programming the map function applies the function specified in it's first argument to each item of the list specified in its second argument and returns a list with the results of each function application. The if statement differentiates composites from basic MarSystems. Basic MarSystems are simply created. For Composites first the composite MarSystem is created, then all of its children are created (recursively by calling the create function as they can also be Composites) and then added to the parent MarSystem.

I hope this post motivates you to try out the Python bindings of Marsyas as well as explore the wonderful ideas of functional programming. Running code can be found under the src/marsyas_python directory of the svn trunk.