From ec1ce52d8bd4dbae8c210e92ece430b129442e0e Mon Sep 17 00:00:00 2001 From: Paul Brossier Date: Mon, 19 Oct 2009 15:09:21 +0200 Subject: [PATCH] added crazy generator --- interfaces/python/gen_pyobject.py | 308 ++++++++++++++++++++++++++++++ interfaces/python/generator.py | 119 ++++++++++++ 2 files changed, 427 insertions(+) create mode 100644 interfaces/python/gen_pyobject.py create mode 100644 interfaces/python/generator.py diff --git a/interfaces/python/gen_pyobject.py b/interfaces/python/gen_pyobject.py new file mode 100644 index 00000000..0125d729 --- /dev/null +++ b/interfaces/python/gen_pyobject.py @@ -0,0 +1,308 @@ +#! /usr/bin/python + +""" This madness of code is used to generate the C code of the python interface +to aubio. Don't try this at home. + +The list of typedefs and functions is obtained from the command line 'cpp +aubio.h'. This list is then used to parse all the functions about this object. + +I hear the ones asking "why not use swig, or cython, or something like that?" + +The requirements for this extension are the following: + + - aubio vectors can be viewed as numpy arrays, and vice versa + - aubio 'object' should be python classes, not just a bunch of functions + +I haven't met any python interface generator that can meet both these +requirements. If you know of one, please let me know, it will spare me +maintaining this bizarre file. +""" + +# TODO +# do function: for now, only the following pattern is supported: +# void aubio__do (aubio_foo_t * o, +# [input1_t * input, [output1_t * output, ..., output3_t * output]]); +# There is no way of knowing that output1 is actually input2. In the future, +# const could be used for the inputs in the C prototypes. + +# the important bits: the size of the output for each objects. this data should +# move into the C library at some point. +defaultsizes = { + 'resampler': ('input->length * self->ratio', 'input->channels'), + 'onsetdetection': ('1', 'self->channels'), + 'onset': ('1', 'self->channels'), + 'pitchyin': ('1', 'in->channels'), + 'pitchyinfft': ('1', 'in->channels'), + 'pitchschmitt': ('1', 'in->channels'), + 'pitchmcomb': ('1', 'self->channels'), + 'pitchfcomb': ('1', 'self->channels'), + 'pitch': ('1', 'self->channels'), + 'tss': ('self->hop_s', 'self->channels'), + 'mfcc': ('self->n_coeffs', 'in->channels'), + 'beattracking': ('self->winlen', 'self->channels'), + 'tempo': ('self->buf_size', 'self->channels'), + 'peakpicker': ('1', 'self->channels'), +} + +# default value for variables +aubioinitvalue = { + 'uint_t': 0, + 'smpl_t': 0, + 'lsmp_t': 0., + 'char_t*': 'NULL', + } + +aubiodefvalue = { + # we have some clean up to do + 'win_s': 'Py_default_vector_length', + 'bufsize': 'Py_default_vector_length', + 'buf_size': 'Py_default_vector_length', + 'winlen': 'Py_default_vector_length', + # and here too + 'hop_s': 'Py_default_vector_length / 2', + 'hopsize': 'Py_default_vector_length / 2', + 'hop_size': 'Py_default_vector_length / 2', + # these should be alright + 'channels': 'Py_default_vector_channels', + 'samplerate': 'Py_aubio_default_samplerate', + # now for the non obvious ones + 'n_filters': '40', + 'n_coeffs': '13', + 'nelems': '10', + 'flow': '0.', + 'fhig': '1.', + 'ilow': '0.', + 'ihig': '1.', + 'thrs': '0.5', + 'ratio': '0.5', + 'threshold': '0.5', + 'mode': '"default"', + 'onset_mode': '"default"', + 'type': '0', + } + +# aubio to python +aubio2pytypes = { + 'uint_t': 'I', + 'smpl_t': 'I', + 'lsmp_t': 'I', + 'fvec_t': 'O', + 'cvec_t': 'O', + 'char_t*': 's', +} + +# aubio to pyaubio +aubio2pyaubio = { + 'fvec_t': 'Py_fvec', + 'cvec_t': 'Py_cvec', +} + +# array to aubio +aubiovecfrompyobj = { + 'fvec_t': 'PyAubio_ArrayToFvec', + 'cvec_t': 'PyAubio_ArrayToCvec', +} + +# aubio to array +aubiovectopyobj = { + 'fvec_t': 'PyAubio_FvecToArray', + 'cvec_t': 'PyAubio_CvecToArray', +} + +def get_newparams(newfunc): + newparams = [[p.split()[0], p.split()[-1]] + for p in newfunc.split('(')[1].split(')')[0].split(',')] + # make char_t a pointer + return map(lambda x: [x[0].replace('char_t', 'char_t*'), x[1]], newparams) + +def gen_new_init(newfunc, name): + newparams = get_newparams(newfunc) + # self->param1, self->param2, self->param3 + selfparams = ', self->'.join([p[1] for p in newparams]) + # "param1", "param2", "param3" + paramnames = ", ".join(["\""+p[1]+"\"" for p in newparams]) + pyparams = "".join(map(lambda p: aubio2pytypes[p[0]], newparams)) + paramrefs = ", ".join(["&" + p[1] for p in newparams]) + s = """\ +// WARNING: this file is generated, DO NOT EDIT +#include "aubiowraphell.h" + +typedef struct +{ + PyObject_HEAD + aubio_%(name)s_t * o; +""" % locals() + for ptype, pname in newparams: + s += """\ + %(ptype)s %(pname)s; +""" % locals() + s += """\ +} Py_%(name)s; + +static char Py_%(name)s_doc[] = "%(name)s object"; + +static PyObject * +Py_%(name)s_new (PyTypeObject * pytype, PyObject * args, PyObject * kwds) +{ +""" % locals() + for ptype, pname in newparams: + defval = aubioinitvalue[ptype] + s += """\ + %(ptype)s %(pname)s = %(defval)s; +""" % locals() + s += """\ + Py_%(name)s *self; + static char *kwlist[] = { %(paramnames)s, NULL }; + + if (!PyArg_ParseTupleAndKeywords (args, kwds, "|%(pyparams)s", kwlist, + %(paramrefs)s)) { + return NULL; + } + + self = (Py_%(name)s *) pytype->tp_alloc (pytype, 0); + + if (self == NULL) { + return NULL; + } +""" % locals() + # TODO add parameters default values + for ptype, pname in newparams: + defval = aubiodefvalue[pname] + s += """\ + + self->%(pname)s = %(defval)s; + if (%(pname)s > 0) { + self->%(pname)s = %(pname)s; + } else if (%(pname)s < 0) { + PyErr_SetString (PyExc_ValueError, + "can not use negative window size"); + return NULL; + } +""" % locals() + s += """\ + + return (PyObject *) self; +} + +AUBIO_INIT(%(name)s, self->%(selfparams)s) + +AUBIO_DEL(%(name)s) + +""" % locals() + return s + +def gen_do(dofunc, name): + funcname = dofunc.split()[1].split('(')[0] + doparams = [p.split() for p in dofunc.split('(')[1].split(')')[0].split(',')] + # make sure the first parameter is the object + assert doparams[0][0] == "aubio_"+name+"_t", \ + "method is not in 'aubio__t" + # and remove it + doparams = doparams[1:] + # guess the input/output params, assuming we have less than 3 + assert len(doparams) > 0, \ + "no parameters for function do in object %s" % name + #assert (len(doparams) <= 2), \ + # "more than 3 parameters for do in object %s" % name + + # build strings for inputs, assuming there is only one input + inputparams = [doparams[0]] + # build the parsing string for PyArg_ParseTuple + pytypes = "".join([aubio2pytypes[p[0]] for p in doparams[0:1]]) + inputdefs = "\n ".join(["PyObject * " + p[-1] + "_obj;" for p in inputparams]) + inputvecs = "\n ".join(map(lambda p: \ + aubio2pyaubio[p[0]]+" * " + p[-1] + ";", inputparams)) + parseinput = "" + for p in inputparams: + inputvec = p[-1] + inputdef = p[-1] + "_obj" + converter = aubiovecfrompyobj[p[0]] + parseinput += """%(inputvec)s = %(converter)s (%(inputdef)s); + + if (%(inputvec)s == NULL) { + return NULL; + }""" % locals() + # build the string for the input objects references + inputrefs = ", ".join(["&" + p[-1] + "_obj" for p in inputparams]) + # end of inputs strings + + # build strings for outputs + outputparams = doparams[1:] + if len(doparams) > 1: + #assert len(outputparams) == 1, \ + # "too many output parameters" + outputvecs = "\n ".join([aubio2pyaubio[p[0]]+" * " + p[-1] + ";" for p in outputparams]) + outputcreate = """\ +AUBIO_NEW_VEC(%(name)s, %(pytype)s, %(length)s, %(channels)s) + %(name)s->o = new_%(autype)s (%(length)s, %(channels)s);""" % \ + {'name': p[-1], 'pytype': aubio2pyaubio[p[0]], 'autype': p[0][:-2], + 'length': defaultsizes[name][0], 'channels': defaultsizes[name][1]} + returnval = "(PyObject *)" + aubiovectopyobj[p[0]] + " (" + p[-1] + ")" + else: + # no output + outputvecs = "" + outputcreate = "" + #returnval = "Py_None"; + returnval = "(PyObject *)" + aubiovectopyobj[p[0]] + " (" + p[-1] + ")" + # end of output strings + + # build the parameters for the _do() call + doparams_string = "self->o, " + ", ".join([p[-1]+"->o" for p in doparams]) + + # put it all together + s = """\ +static PyObject * +Py_%(name)s_do(Py_%(name)s * self, PyObject * args) +{ + %(inputdefs)s + %(inputvecs)s + %(outputvecs)s + + if (!PyArg_ParseTuple (args, "%(pytypes)s", %(inputrefs)s)) { + return NULL; + } + + %(parseinput)s + + %(outputcreate)s + + /* compute _do function */ + %(funcname)s (%(doparams_string)s); + + return %(returnval)s; +} +""" % locals() + return s + +def gen_members(new_method, name): + newparams = get_newparams(new_method) + s = """ +AUBIO_MEMBERS_START(%(name)s)""" % locals() + for param in newparams: + s += """ + {"%(pname)s", T_INT, offsetof (Py_%(name)s, %(pname)s), READONLY, ""},""" \ + % { 'pname': param[1], 'ptype': param[0], 'name': name} + s += """ +AUBIO_MEMBERS_STOP(%(name)s) + +""" % locals() + return s + +def gen_methods(get_methods, set_methods, name): + # TODO add methods + s = """\ +static PyMethodDef Py_%(name)s_methods[] = { +""" % locals() + # TODO add PyMethodDefs + s += """\ + {NULL} /* sentinel */ +}; +""" % locals() + return s + +def gen_finish(name): + s = """\ + +AUBIO_TYPEOBJECT(%(name)s, "aubio.%(name)s") +""" % locals() + return s diff --git a/interfaces/python/generator.py b/interfaces/python/generator.py new file mode 100644 index 00000000..11c55d68 --- /dev/null +++ b/interfaces/python/generator.py @@ -0,0 +1,119 @@ +#! /usr/bin/python + +""" This file generates a c file from a list of cpp prototypes. """ + +import os, sys + +skip_objects = ['fft', 'pvoc', 'filter', 'filterbank', 'biquad'] + +cpp_output = [l.strip() for l in os.popen('cpp -I ../../build/default/src ../../src/aubio.h').readlines()] + +cpp_output = filter(lambda y: len(y) > 1, cpp_output) +cpp_output = filter(lambda y: not y.startswith('#'), cpp_output) + +i = 1 +while 1: + if i >= len(cpp_output): break + if cpp_output[i-1].endswith(',') or cpp_output[i-1].endswith('{') or cpp_output[i].startswith('}'): + cpp_output[i] = cpp_output[i-1] + ' ' + cpp_output[i] + cpp_output.pop(i-1) + else: + i += 1 + +typedefs = filter(lambda y: y.startswith ('typedef struct _aubio'), cpp_output) + +objects = [a.split()[3][:-1] for a in typedefs] + +print "-- INFO: %d objects in total" % len(objects) + +for object in objects: + lint = 0 + + if object[-2:] == '_t': + object_name = object[:-2] + else: + object_name = object + print "-- WARNING: %s does not end in _t" % object + + if object_name[:len('aubio_')] != 'aubio_': + print "-- WARNING: %s does not start n aubio_" % object + + print "-- INFO: looking at", object_name + object_methods = filter(lambda x: object in x, cpp_output) + object_methods = [a.strip() for a in object_methods] + object_methods = filter(lambda x: not x.startswith('typedef'), object_methods) + #for method in object_methods: + # print method + + new_methods = filter(lambda x: 'new_'+object_name in x, object_methods) + if len(new_methods) > 1: + print "-- WARNING: more than one new method for", object_name + for method in new_methods: + print method + elif len(new_methods) < 1: + print "-- WARNING: no new method for", object_name + elif 0: + for method in new_methods: + print method + + del_methods = filter(lambda x: 'del_'+object_name in x, object_methods) + if len(del_methods) > 1: + print "-- WARNING: more than one del method for", object_name + for method in del_methods: + print method + elif len(del_methods) < 1: + print "-- WARNING: no del method for", object_name + + do_methods = filter(lambda x: object_name+'_do' in x, object_methods) + if len(do_methods) > 1: + pass + #print "-- WARNING: more than one do method for", object_name + #for method in do_methods: + # print method + elif len(do_methods) < 1: + print "-- WARNING: no do method for", object_name + elif 0: + for method in do_methods: + print method + + # check do methods return void + for method in do_methods: + if (method.split()[0] != 'void'): + print "-- ERROR: _do method does not return void:", method + + get_methods = filter(lambda x: object_name+'_get_' in x, object_methods) + + set_methods = filter(lambda x: object_name+'_set_' in x, object_methods) + for method in set_methods: + if (method.split()[0] != 'uint_t'): + print "-- ERROR: _set method does not return uint_t:", method + + other_methods = filter(lambda x: x not in new_methods, object_methods) + other_methods = filter(lambda x: x not in del_methods, other_methods) + other_methods = filter(lambda x: x not in do_methods, other_methods) + other_methods = filter(lambda x: x not in get_methods, other_methods) + other_methods = filter(lambda x: x not in set_methods, other_methods) + + if len(other_methods) > 0: + print "-- WARNING: some methods for", object_name, "were unidentified" + for method in other_methods: + print method + + # generate object + if not os.path.isdir('generated'): os.mkdir('generated') + from gen_pyobject import * + short_name = object_name[len('aubio_'):] + if short_name in skip_objects: + print "-- INFO: skipping object", short_name + continue + if 1: #try: + s = gen_new_init(new_methods[0], short_name) + s += gen_do(do_methods[0], short_name) + s += gen_members(new_methods[0], short_name) + s += gen_methods(get_methods, set_methods, short_name) + s += gen_finish(short_name) + fd = open('generated/gen-'+short_name+'.c', 'w') + fd.write(s) + #except Exception, e: + # print "-- ERROR:", type(e), str(e), "in", short_name + # continue -- 2.26.2