added crazy generator
authorPaul Brossier <piem@piem.org>
Mon, 19 Oct 2009 13:09:21 +0000 (15:09 +0200)
committerPaul Brossier <piem@piem.org>
Mon, 19 Oct 2009 13:09:21 +0000 (15:09 +0200)
interfaces/python/gen_pyobject.py [new file with mode: 0644]
interfaces/python/generator.py [new file with mode: 0644]

diff --git a/interfaces/python/gen_pyobject.py b/interfaces/python/gen_pyobject.py
new file mode 100644 (file)
index 0000000..0125d72
--- /dev/null
@@ -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_<foo>_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_<name>_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 (file)
index 0000000..11c55d6
--- /dev/null
@@ -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