Stevebook - [ blog:calling_haskell_from_a_pure_data_external ]

Calling Haskell from a Pure Data external

Screenshot of the Pd external object featuring Haskell function calls.

I've been playing lately with an idea for a Pure Data external that involves a certain amount of structured data manipulation, and started to wonder if it might be easier to use a higher level language than C. I don't really want to make this a massive external with huge crazy dependencies though, so I started looking at very small Lisps that I could embed or that compile to C. I had a certain amount of success with Gambit, but I started looking into ML and Haskell just for fun.

I looked at some small embeddable options in this arena that I'd previously found, such as EmbeddedML. I also found some nice and small Scheme-ish things like TinyScheme and this amazing 300-line lisp interpreter in C. Finally though I decided that it would be nice to have something a little more full-featured.

This led me to figure out how to call Haskell code from C, and, thanks to a certain blog post, it turns out not to be too difficult. I managed to make a pretty minimal project that generates a compiled Pd external calling out to some Haskell functions. I should note that this is far from a *complete* way to use Haskell in Pd. There was a talk on a way to write whole extensions in Haskell at the Pd 2007 convention, which can be found here. In my case though I really just wanted to pass some data off to Haskell for transformation and then retrieve the results, and I think this simple solution will do the trick. The final code is presented here. Above is screenshot showing the output after clicking the bang.

The code is short enough to list here.

Test.hs:

{-# LANGUAGE ForeignFunctionInterface #-}
module Test where

import Foreign.C.Types
import Foreign.C.String

hsfun :: CInt -> IO CInt
hsfun x = do
    putStrLn "Hello World"
    return (42 + x)
    
hstest :: CString -> IO CString
hstest x = do
    str <- peekCString x
    newCString ( "Hello" ++ str )

foreign export ccall
    hsfun :: CInt -> IO CInt

foreign export ccall
    hstest :: CString -> IO CString

test.c:

#include <stdlib.h>

#include <HsFFI.h>
#include "Test_stub.h"

#include <m_pd.h>

static t_class *Test_class;

typedef struct _Test
{
    t_object x_obj;
} t_Test;

static void *Test_new(t_symbol *s, int argc, t_atom *argv)
{
    t_Test *t = (t_Test*)pd_new(Test_class);
    return t;
}
  
static void Test_free(t_Test *x)
{
}

static void Test_bang()
{
    post("Test_bang(): %ld", hsfun(4));
    char *s = hstest("?");
    post("Test_bang(): %s", s);
    free(s);
}

void Test_setup()
{
    static char *argv[] = { "Test.so", 0 }, **argv_ = argv;
    static int argc = 1;
    hs_init(&argc, &argv_);
    
    Test_class = class_new(gensym("Test"), (t_newmethod)Test_new, 
			 (t_method)Test_free, sizeof(t_Test), 0, A_GIMME, 0);
    class_addbang(Test_class, Test_bang);
}

Makefile:

all: Test.pd_linux

Test_stub.h: Test.hs

ghc -O2 –make -no-hs-main -optl '-shared' -o Test.so Test.hs

Test.so: Test_stub.h test.c
	ghc -I../../../pd/src -O2 --make -no-hs-main -optl -shared -optc -DMODULE=Test -o Test.so Test.hs test.c

Test.pd_linux: Test.so
	cp Test.so Test.pd_linux

.PHONY: clean
clean:
	-@rm -vf *.o *.hi Test_stub.h Test_stub.c Test.pd_linux Test.so

test.pd:

#N canvas 5 49 450 300 10;
#X obj 43 65 Test;
#X obj 43 43 bng 15 250 50 0 empty empty empty 17 7 0 10 -262144 -1 -1;
#X connect 1 0 0 0;