source: interfaces/python/gen_pyobject.py @ 0b2892f

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

gen_pyobject.py: improve, also parse io/source

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