source: python/gen_pyobject.py @ ad5203c

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

python/: improve build

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