source: python/lib/gen_pyobject.py @ ade36e6

feature/autosinkfeature/cnnfeature/cnn_orgfeature/constantqfeature/crepefeature/crepe_orgfeature/pitchshiftfeature/pydocstringsfeature/timestretchfix/ffmpeg5pitchshiftsamplertimestretchyinfft+
Last change on this file since ade36e6 was ade36e6, checked in by Paul Brossier <piem@piem.org>, 8 years ago

python/lib/gen_pyobject.py: prepare for python3 (see #33)

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