source: interfaces/python/gen_pyobject.py @ abbd910

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

gen_pyobject.py: clean up, output a float instead of a vector with a single element

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