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