source: interfaces/python/gen_pyobject.py @ 75be651

feature/autosinkfeature/constantqfeature/pitchshiftfeature/pydocstringsfeature/timestretchpitchshiftsamplertimestretchyinfft+
Last change on this file since 75be651 was 75be651, checked in by Paul Brossier <piem@piem.org>, 10 years ago

interfaces/python/gen_pyobject.py: fix aubio2types

  • Property mode set to 100644
File size: 13.3 KB
Line 
1#! /usr/bin/python
2
3""" This madness of code is used to generate the C code of the python interface
4to aubio. Don't try this at home.
5
6The list of typedefs and functions is obtained from the command line 'cpp
7aubio.h'. This list is then used to parse all the functions about this object.
8
9I hear the ones asking "why not use swig, or cython, or something like that?"
10
11The requirements for this extension are the following:
12
13    - aubio vectors can be viewed as numpy arrays, and vice versa
14    - aubio 'object' should be python classes, not just a bunch of functions
15
16I haven't met any python interface generator that can meet both these
17requirements. If you know of one, please let me know, it will spare me
18maintaining this bizarre file.
19"""
20
21# TODO
22# do function: for now, only the following pattern is supported:
23# void aubio_<foo>_do (aubio_foo_t * o,
24#       [input1_t * input, [output1_t * output, ..., output3_t * output]]);
25# There is no way of knowing that output1 is actually input2. In the future,
26# const could be used for the inputs in the C prototypes.
27
28def split_type(arg):
29    """ arg = 'foo *name'
30        return ['foo*', 'name'] """
31    l = arg.split()
32    # ['foo', '*name'] -> ['foo*', 'name']
33    if l[-1].startswith('*'):
34        return [l[0]+'*', l[1][1:]]
35    # ['foo', '*', 'name'] -> ['foo*', 'name']
36    if len(l) == 3:
37        return [l[0]+l[1], l[2]]
38    else:
39        return l
40
41def get_params(proto):
42    """ get the list of parameters from a function prototype
43    example: proto = "int main (int argc, char ** argv)"
44    returns: ['int argc', 'char ** argv']
45    """
46    import re
47    paramregex = re.compile('[\(, ](\w+ \*?\*? ?\w+)[, \)]')
48    return paramregex.findall(proto)
49
50def get_params_types_names(proto):
51    """ get the list of parameters from a function prototype
52    example: proto = "int main (int argc, char ** argv)"
53    returns: [['int', 'argc'], ['char **','argv']]
54    """
55    return map(split_type, get_params(proto)) 
56
57def get_return_type(proto):
58    import re
59    paramregex = re.compile('(\w+ ?\*?).*')
60    outputs = paramregex.findall(proto)
61    assert len(outputs) == 1
62    return outputs[0].replace(' ', '')
63
64def get_name(proto):
65    name = proto.split()[1].split('(')[0]
66    return name.replace('*','')
67
68# the important bits: the size of the output for each objects. this data should
69# move into the C library at some point.
70defaultsizes = {
71    'resampler':    ('input->length * self->ratio', 'input->channels'),
72    'specdesc':     ('1', 'fftgrain->channels'),
73    'onset':        ('1', 'self->channels'),
74    'pitchyin':     ('1', 'in->channels'),
75    'pitchyinfft':  ('1', 'in->channels'),
76    'pitchschmitt': ('1', 'in->channels'),
77    'pitchmcomb':   ('1', 'self->channels'),
78    'pitchfcomb':   ('1', 'self->channels'),
79    'pitch':        ('1', 'self->channels'),
80    'tss':          ('self->hop_size', 'self->channels'),
81    'mfcc':         ('self->n_coeffs', 'in->channels'),
82    'beattracking': ('self->hop_size', 'self->channels'),
83    'tempo':        ('1', 'self->channels'),
84    'peakpicker':   ('1', 'self->channels'),
85}
86
87# default value for variables
88aubioinitvalue = {
89    'uint_t': 0,
90    'smpl_t': 0,
91    'lsmp_t': 0.,
92    'char_t*': 'NULL',
93    }
94
95aubiodefvalue = {
96    # we have some clean up to do
97    'buf_size': 'Py_default_vector_length', 
98    # and here too
99    'hop_size': 'Py_default_vector_length / 2', 
100    # these should be alright
101    'channels': 'Py_default_vector_channels', 
102    'samplerate': 'Py_aubio_default_samplerate', 
103    # now for the non obvious ones
104    'n_filters': '40', 
105    'n_coeffs': '13', 
106    'nelems': '10',
107    'flow': '0.', 
108    'fhig': '1.', 
109    'ilow': '0.', 
110    'ihig': '1.', 
111    'thrs': '0.5',
112    'ratio': '0.5',
113    'method': '"default"',
114    }
115
116# aubio to python
117aubio2pytypes = {
118    'uint_t': 'I',
119    'smpl_t': 'f',
120    'lsmp_t': 'd',
121    'fvec_t*': 'O',
122    'cvec_t*': 'O',
123    'char_t*': 's',
124}
125
126# aubio to pyaubio
127aubio2pyaubio = {
128    'fvec_t*': 'Py_fvec',
129    'cvec_t*': 'Py_cvec',
130}
131
132# array to aubio
133aubiovecfrompyobj = {
134    'fvec_t*': 'PyAubio_ArrayToFvec',
135    'cvec_t*': 'PyAubio_ArrayToCvec',
136}
137
138# aubio to array
139aubiovectopyobj = {
140    'fvec_t*': 'PyAubio_FvecToArray',
141    'cvec_t*': 'PyAubio_CvecToArray',
142}
143
144aubiovectopyobj_new = {
145    'fvec_t*': 'PyAubio_CFvecToArray',
146    'cvec_t*': 'PyAubio_CCvecToArray',
147    'smpl_t': 'PyFloat_FromDouble',
148}
149
150def gen_new_init(newfunc, name):
151    newparams = get_params_types_names(newfunc)
152    # self->param1, self->param2, self->param3
153    selfparams = ', self->'.join([p[1] for p in newparams])
154    # "param1", "param2", "param3"
155    paramnames = ", ".join(["\""+p[1]+"\"" for p in newparams])
156    pyparams = "".join(map(lambda p: aubio2pytypes[p[0]], newparams))
157    paramrefs = ", ".join(["&" + p[1] for p in newparams])
158    s = """\
159// WARNING: this file is generated, DO NOT EDIT
160
161// WARNING: if you haven't read the first line yet, please do so
162#include "aubiowraphell.h"
163
164typedef struct
165{
166  PyObject_HEAD
167  aubio_%(name)s_t * o;
168""" % locals()
169    for ptype, pname in newparams:
170        s += """\
171  %(ptype)s %(pname)s;
172""" % locals()
173    s += """\
174} Py_%(name)s;
175
176static char Py_%(name)s_doc[] = "%(name)s object";
177
178static PyObject *
179Py_%(name)s_new (PyTypeObject * pytype, PyObject * args, PyObject * kwds)
180{
181""" % locals()
182    for ptype, pname in newparams:
183        initval = aubioinitvalue[ptype]
184        s += """\
185  %(ptype)s %(pname)s = %(initval)s;
186""" % locals()
187    # now the actual PyArg_Parse
188    s += """\
189  Py_%(name)s *self;
190  static char *kwlist[] = { %(paramnames)s, NULL };
191
192  if (!PyArg_ParseTupleAndKeywords (args, kwds, "|%(pyparams)s", kwlist,
193          %(paramrefs)s)) {
194    return NULL;
195  }
196
197  self = (Py_%(name)s *) pytype->tp_alloc (pytype, 0);
198
199  if (self == NULL) {
200    return NULL;
201  }
202""" % locals()
203    for ptype, pname in newparams:
204        defval = aubiodefvalue[pname]
205        if ptype == 'char_t*':
206            s += """\
207
208  self->%(pname)s = %(defval)s;
209  if (%(pname)s != NULL) {
210    self->%(pname)s = %(pname)s;
211  }
212""" % locals()
213        elif ptype == 'uint_t':
214            s += """\
215
216  self->%(pname)s = %(defval)s;
217  if (%(pname)s > 0) {
218    self->%(pname)s = %(pname)s;
219  } else if (%(pname)s < 0) {
220    PyErr_SetString (PyExc_ValueError,
221        "can not use negative value for %(pname)s");
222    return NULL;
223  }
224""" % locals()
225        elif ptype == 'smpl_t':
226            s += """\
227
228  self->%(pname)s = %(defval)s;
229  if (%(pname)s != %(defval)s) {
230    self->%(pname)s = %(pname)s;
231  }
232""" % locals()
233        else:
234            print "ERROR, unknown type of parameter %s %s" % (ptype, pname)
235    s += """\
236
237  return (PyObject *) self;
238}
239
240AUBIO_INIT(%(name)s, self->%(selfparams)s)
241
242AUBIO_DEL(%(name)s)
243
244""" % locals()
245    return s
246
247def gen_do(dofunc, name):
248    funcname = dofunc.split()[1].split('(')[0]
249    doparams = get_params_types_names(dofunc) 
250    # make sure the first parameter is the object
251    assert doparams[0][0] == "aubio_"+name+"_t*", \
252        "method is not in 'aubio_<name>_t"
253    # and remove it
254    doparams = doparams[1:]
255    # guess the input/output params, assuming we have less than 3
256    assert len(doparams) > 0, \
257        "no parameters for function do in object %s" % name
258    #assert (len(doparams) <= 2), \
259    #    "more than 3 parameters for do in object %s" % name
260
261    # build strings for inputs, assuming there is only one input
262    inputparams = [doparams[0]]
263    # build the parsing string for PyArg_ParseTuple
264    pytypes = "".join([aubio2pytypes[p[0]] for p in doparams[0:1]])
265    inputdefs = "\n  ".join(["PyObject * " + p[-1] + "_obj;" for p in inputparams])
266    inputvecs = "\n  ".join(map(lambda p: \
267                aubio2pyaubio[p[0]]+" * " + p[-1] + ";", inputparams))
268    parseinput = ""
269    for p in inputparams:
270        inputvec = p[-1]
271        inputdef = p[-1] + "_obj"
272        converter = aubiovecfrompyobj[p[0]]
273        parseinput += """%(inputvec)s = %(converter)s (%(inputdef)s);
274
275  if (%(inputvec)s == NULL) {
276    return NULL;
277  }""" % locals()
278    # build the string for the input objects references
279    inputrefs = ", ".join(["&" + p[-1] + "_obj" for p in inputparams])
280    # end of inputs strings
281
282    # build strings for outputs
283    outputparams = doparams[1:]
284    if len(outputparams) >= 1:
285        #assert len(outputparams) == 1, \
286        #    "too many output parameters"
287        outputvecs = "\n  ".join([aubio2pyaubio[p[0]]+" * " + p[-1] + ";" for p in outputparams])
288        outputcreate = "\n  ".join(["""\
289AUBIO_NEW_VEC(%(name)s, %(pytype)s, %(length)s, %(channels)s)
290  %(name)s->o = new_%(autype)s (%(length)s, %(channels)s);""" % \
291    {'name': p[-1], 'pytype': aubio2pyaubio[p[0]], 'autype': p[0][:-3],
292        'length': defaultsizes[name][0], 'channels': defaultsizes[name][1]} \
293        for p in outputparams]) 
294        if len(outputparams) > 1:
295            returnval = "PyObject *outputs = PyList_New(0);\n"
296            for p in outputparams:
297                returnval += "  PyList_Append( outputs, (PyObject *)" + aubiovectopyobj[p[0]] + " (" + p[-1] + ")" +");\n"
298            returnval += "  return outputs;"
299        else:
300            returnval = "return (PyObject *)" + aubiovectopyobj[p[0]] + " (" + p[-1] + ")"
301    else:
302        # no output
303        outputvecs = ""
304        outputcreate = ""
305        #returnval = "Py_None";
306        returnval = "return (PyObject *)" + aubiovectopyobj[p[0]] + " (" + p[-1] + ")"
307    # end of output strings
308
309    # build the parameters for the  _do() call
310    doparams_string = "self->o, " + ", ".join([p[-1]+"->o" for p in doparams])
311
312    # put it all together
313    s = """\
314static PyObject *
315Py_%(name)s_do(Py_%(name)s * self, PyObject * args)
316{
317  %(inputdefs)s
318  %(inputvecs)s
319  %(outputvecs)s
320
321  if (!PyArg_ParseTuple (args, "%(pytypes)s", %(inputrefs)s)) {
322    return NULL;
323  }
324
325  %(parseinput)s
326 
327  %(outputcreate)s
328
329  /* compute _do function */
330  %(funcname)s (%(doparams_string)s);
331
332  %(returnval)s;
333}
334""" % locals()
335    return s
336
337def gen_members(new_method, name):
338    newparams = get_params_types_names(new_method)
339    s = """
340AUBIO_MEMBERS_START(%(name)s)""" % locals()
341    for param in newparams:
342        if param[0] == 'char_t*':
343            s += """
344  {"%(pname)s", T_STRING, offsetof (Py_%(name)s, %(pname)s), READONLY, ""},""" \
345        % { 'pname': param[1], 'ptype': param[0], 'name': name}
346        elif param[0] == 'uint_t':
347            s += """
348  {"%(pname)s", T_INT, offsetof (Py_%(name)s, %(pname)s), READONLY, ""},""" \
349        % { 'pname': param[1], 'ptype': param[0], 'name': name}
350        elif param[0] == 'smpl_t':
351            s += """
352  {"%(pname)s", T_FLOAT, offsetof (Py_%(name)s, %(pname)s), READONLY, ""},""" \
353        % { 'pname': param[1], 'ptype': param[0], 'name': name}
354        else:
355            print "-- ERROR, unknown member type ", param
356    s += """
357AUBIO_MEMBERS_STOP(%(name)s)
358
359""" % locals()
360    return s
361
362
363def gen_methods(get_methods, set_methods, name):
364    s = ""
365    method_defs = ""
366    for method in set_methods:
367        method_name = get_name(method)
368        params = get_params_types_names(method)
369        out_type = get_return_type(method)
370        assert params[0][0] == "aubio_"+name+"_t*", \
371            "get method is not in 'aubio_<name>_t"
372        print method
373        print params[1:]
374        setter_args = "self->o, " +",".join([p[1] for p in params[1:]])
375        parse_args = ""
376        for p in params[1:]:
377            parse_args += p[0] + " " + p[1] + ";\n"
378        argmap = "".join([aubio2pytypes[p[0]] for p in params[1:]])
379        arglist = ", ".join(["&"+p[1] for p in params[1:]])
380        parse_args += """
381  if (!PyArg_ParseTuple (args, "%(argmap)s", %(arglist)s)) {
382    return NULL;
383  } """ % locals()
384        s += """
385static PyObject *
386Py%(funcname)s (Py_%(objname)s *self, PyObject *args)
387{
388  uint_t err = 0;
389
390  %(parse_args)s
391
392  err = %(funcname)s (%(setter_args)s);
393
394  if (err > 0) {
395    PyErr_SetString (PyExc_ValueError,
396        "error running %(funcname)s");
397    return NULL;
398  }
399  return Py_None;
400}
401""" % {'funcname': method_name, 'objname': name, 
402        'out_type': out_type, 'setter_args': setter_args, 'parse_args': parse_args }
403        shortname = method_name.split(name+'_')[-1]
404        method_defs += """\
405  {"%(shortname)s", (PyCFunction) Py%(method_name)s,
406    METH_VARARGS, ""},
407""" % locals()
408
409    for method in get_methods:
410        method_name = get_name(method)
411        params = get_params_types_names(method)
412        out_type = get_return_type(method)
413        assert params[0][0] == "aubio_"+name+"_t*", \
414            "get method is not in 'aubio_<name>_t %s" % params[0][0]
415        assert len(params) == 1, \
416            "get method has more than one parameter %s" % params
417        getter_args = "self->o" 
418        returnval = "(PyObject *)" + aubiovectopyobj_new[out_type] + " (tmp)"
419        shortname = method_name.split(name+'_')[-1]
420        method_defs += """\
421  {"%(shortname)s", (PyCFunction) Py%(method_name)s,
422    METH_NOARGS, ""},
423""" % locals()
424        s += """
425static PyObject *
426Py%(funcname)s (Py_%(objname)s *self, PyObject *unused)
427{
428  %(out_type)s tmp = %(funcname)s (%(getter_args)s);
429  return %(returnval)s;
430}
431""" % {'funcname': method_name, 'objname': name, 
432        'out_type': out_type, 'getter_args': getter_args, 'returnval': returnval }
433
434    s += """
435static PyMethodDef Py_%(name)s_methods[] = {
436""" % locals() 
437    s += method_defs
438    s += """\
439  {NULL} /* sentinel */
440};
441""" % locals() 
442    return s
443
444def gen_finish(name):
445    s = """\
446
447AUBIO_TYPEOBJECT(%(name)s, "aubio.%(name)s")
448""" % locals()
449    return s
Note: See TracBrowser for help on using the repository browser.