python/lib/gen_pyobject.py: fix shortname of methods with multiple _
[aubio.git] / python / lib / gen_pyobject.py
1 #! /usr/bin/python
2
3 """ This madness of code is used to generate the C code of the python interface
4 to aubio. Don't try this at home.
5
6 The list of typedefs and functions is obtained from the command line 'cpp
7 aubio.h'. This list is then used to parse all the functions about this object.
8
9 I hear the ones asking "why not use swig, or cython, or something like that?"
10
11 The 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
16 I haven't met any python interface generator that can meet both these
17 requirements. If you know of one, please let me know, it will spare me
18 maintaining this bizarre file.
19 """
20
21 param_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
33 def write_msg(*args):
34   pass
35   # uncomment out for debugging
36   #print args
37
38 def 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
58 def 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
67 def 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
74 def 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
81 def 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.
87 defaultsizes = {
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
106 aubioinitvalue = {
107     'uint_t': 0,
108     'smpl_t': 0,
109     'lsmp_t': 0.,
110     'char_t*': 'NULL',
111     }
112
113 aubiodefvalue = {
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
135 aubio2pytypes = {
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
145 aubiovecfrompyobj = {
146     'fvec_t*': 'PyAubio_ArrayToCFvec',
147     'cvec_t*': 'PyAubio_ArrayToCCvec',
148     'uint_t': '(uint_t)PyInt_AsLong',
149 }
150
151 # aubio to python
152 aubiovectopyobj = {
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
160 def 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
177 typedef 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
191 static char Py_%(name)s_doc[] = "%(name)s object";
192
193 static PyObject *
194 Py_%(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
262 AUBIO_INIT(%(name)s %(selfparams)s)
263
264 AUBIO_DEL(%(name)s)
265
266 """ % locals()
267     return s
268
269 def 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
316 def 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 += "  Py_RETURN_NONE"
346   # end of output strings
347   return outputvecs, outputcreate, returnval
348
349 def 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 */
393 static PyObject * 
394 Py_%(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
414 def gen_members(new_method, name):
415     newparams = get_params_types_names(new_method)
416     s = """
417 AUBIO_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 += """
434 AUBIO_MEMBERS_STOP(%(name)s)
435
436 """ % locals()
437     return s
438
439
440 def 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 += """
462 static PyObject *
463 Py%(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   Py_RETURN_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('aubio_'+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('aubio_'+name+'_')[-1]
497         method_defs += """\
498   {"%(shortname)s", (PyCFunction) Py%(method_name)s,
499     METH_NOARGS, ""},
500 """ % locals()
501         s += """
502 static PyObject *
503 Py%(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 += """
512 static PyMethodDef Py_%(name)s_methods[] = {
513 """ % locals() 
514     s += method_defs 
515     s += """\
516   {NULL} /* sentinel */
517 };
518 """ % locals() 
519     return s
520
521 def gen_finish(name):
522     s = """\
523
524 AUBIO_TYPEOBJECT(%(name)s, "aubio.%(name)s")
525 """ % locals()
526     return s