source: python/lib/gen_pyobject.py @ 906c0a2

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

lib/gen_pyobject.py: fix size checks

Signed-off-by: Paul Brossier <piem@piem.org>

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