History

Merge branch 'master' into feature/pitchshift

Paul Brossier committed 7 years ago

Commit 633400d2 parents 5b46bc3 f19db54230 files · +12450 −4607Browse files

Showing the changes to python/lib (9 of 230 files). Show all

Unified · Split

9 files
  1. python/lib/__init__.py +0 −0
  2. python/lib/aubio/__init__.py +72 −5
  3. python/lib/aubio/cmd.py +623 −0
  4. python/lib/aubio/cut.py +163 −0
  5. python/lib/aubio/midiconv.py +142 −17
  6. python/lib/aubio/slicing.py +81 −14
  7. python/lib/gen_code.py +65 −23
  8. python/lib/gen_external.py +180 −89
  9. python/lib/moresetuptools.py +78 −26
python/lib/__init__.py+0−0

Empty file deleted.

python/lib/aubio/__init__.py+72−5
@@ -1,18 +1,85 @@
1#! /usr/bin/env python1#! /usr/bin/env python
2# -*- coding: utf8 -*-
3
4"""
5aubio
6=====
7
8Provides a number of classes and functions for music and audio signal
9analysis.
10
11How to use the documentation
12----------------------------
13
14Documentation of the python module is available as docstrings provided
15within the code, and a reference guide available online from `the
16aubio homepage <https://aubio.org/documentation>`_.
17
18The docstrings examples are written assuming `aubio` and `numpy` have been
19imported with:
20
21>>> import aubio
22>>> import numpy as np
23"""
2 24
3import numpy25import numpy
4from ._aubio import *26from ._aubio import __version__ as version
5from ._aubio import float_type27from ._aubio import float_type
28from ._aubio import *
6from .midiconv import *29from .midiconv import *
7from .slicing import *30from .slicing import *
8 31
32
9class fvec(numpy.ndarray):33class fvec(numpy.ndarray):
10 """a numpy vector holding audio samples"""34 """fvec(input_arg=1024)
35 A vector holding float samples.
36
37 If `input_arg` is an `int`, a 1-dimensional vector of length `input_arg`
38 will be created and filled with zeros. Otherwise, if `input_arg` is an
39 `array_like` object, it will be converted to a 1-dimensional vector of
40 type :data:`float_type`.
41
42 Parameters
43 ----------
44 input_arg : `int` or `array_like`
45 Can be a positive integer, or any object that can be converted to
46 a numpy array with :func:`numpy.array`.
47
48 Examples
49 --------
50 >>> aubio.fvec(10)
51 array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0.], dtype=float32)
52 >>> aubio.fvec([0,1,2])
53 array([0., 1., 2.], dtype=float32)
54 >>> a = np.arange(10); type(a), type(aubio.fvec(a))
55 (<class 'numpy.ndarray'>, <class 'numpy.ndarray'>)
56 >>> a.dtype, aubio.fvec(a).dtype
57 (dtype('int64'), dtype('float32'))
11 58
12 def __new__(cls, input_arg=1024, **kwargs):59 Notes
60 -----
61
62 In the Python world, `fvec` is simply a subclass of
63 :class:`numpy.ndarray`. In practice, any 1-dimensional `numpy.ndarray` of
64 `dtype` :data:`float_type` may be passed to methods accepting
65 `fvec` as parameter. For instance, `sink()` or `pvoc()`.
66
67 See Also
68 --------
69 cvec : a container holding spectral data
70 numpy.ndarray : parent class of :class:`fvec`
71 numpy.zeros : create a numpy array filled with zeros
72 numpy.array : create a numpy array from an existing object
73 """
74 def __new__(cls, input_arg=1024):
13 if isinstance(input_arg, int):75 if isinstance(input_arg, int):
14 if input_arg == 0:76 if input_arg == 0:
15 raise ValueError("vector length of 1 or more expected")77 raise ValueError("vector length of 1 or more expected")
16 return numpy.zeros(input_arg, dtype=float_type, **kwargs)78 return numpy.zeros(input_arg, dtype=float_type, order='C')
17 else:79 else:
18 return numpy.array(input_arg, dtype=float_type, **kwargs)80 np_input = numpy.array(input_arg, dtype=float_type, order='C')
81 if len(np_input.shape) != 1:
82 raise ValueError("input_arg should have shape (n,)")
83 if np_input.shape[0] == 0:
84 raise ValueError("vector length of 1 or more expected")
85 return np_input
python/lib/aubio/cmd.py+623−0
@@ -0,0 +1,623 @@
1#! /usr/bin/env python
2# -*- coding: utf-8 -*-
3
4"""aubio command line tool
5
6This file was written by Paul Brossier <piem@aubio.org> and is released under
7the GNU/GPL v3.
8
9Note: this script is mostly about parsing command line arguments. For more
10readable code examples, check out the `python/demos` folder."""
11
12import sys
13import argparse
14import warnings
15import aubio
16
17def aubio_parser():
18 epilog = 'use "%(prog)s <command> --help" for more info about each command'
19 parser = argparse.ArgumentParser(epilog=epilog)
20 parser.add_argument('-V', '--version', help="show version",
21 action="store_true", dest="show_version")
22
23 subparsers = parser.add_subparsers(title='commands', dest='command',
24 parser_class= AubioArgumentParser,
25 metavar="")
26
27 parser_add_subcommand_help(subparsers)
28
29 parser_add_subcommand_onset(subparsers)
30 parser_add_subcommand_pitch(subparsers)
31 parser_add_subcommand_beat(subparsers)
32 parser_add_subcommand_tempo(subparsers)
33 parser_add_subcommand_notes(subparsers)
34 parser_add_subcommand_mfcc(subparsers)
35 parser_add_subcommand_melbands(subparsers)
36 parser_add_subcommand_quiet(subparsers)
37 parser_add_subcommand_cut(subparsers)
38
39 return parser
40
41def parser_add_subcommand_help(subparsers):
42 # global help subcommand
43 subparsers.add_parser('help',
44 help='show help message',
45 formatter_class = argparse.ArgumentDefaultsHelpFormatter)
46
47def parser_add_subcommand_onset(subparsers):
48 # onset subcommand
49 subparser = subparsers.add_parser('onset',
50 help='estimate time of onsets (beginning of sound event)',
51 formatter_class = argparse.ArgumentDefaultsHelpFormatter)
52 subparser.add_input()
53 subparser.add_buf_hop_size()
54 helpstr = "onset novelty function"
55 helpstr += " <default|energy|hfc|complex|phase|specdiff|kl|mkl|specflux>"
56 subparser.add_method(helpstr=helpstr)
57 subparser.add_threshold()
58 subparser.add_silence()
59 subparser.add_minioi()
60 subparser.add_time_format()
61 subparser.add_verbose_help()
62 subparser.set_defaults(process=process_onset)
63
64def parser_add_subcommand_pitch(subparsers):
65 # pitch subcommand
66 subparser = subparsers.add_parser('pitch',
67 help='estimate fundamental frequency (monophonic)')
68 subparser.add_input()
69 subparser.add_buf_hop_size(buf_size=2048)
70 helpstr = "pitch detection method <default|yinfft|yin|mcomb|fcomb|schmitt>"
71 subparser.add_method(helpstr=helpstr)
72 subparser.add_threshold()
73 subparser.add_pitch_unit()
74 subparser.add_silence()
75 subparser.add_time_format()
76 subparser.add_verbose_help()
77 subparser.set_defaults(process=process_pitch)
78
79def parser_add_subcommand_beat(subparsers):
80 # beat subcommand
81 subparser = subparsers.add_parser('beat',
82 help='estimate location of beats')
83 subparser.add_input()
84 subparser.add_buf_hop_size(buf_size=1024, hop_size=512)
85 subparser.add_time_format()
86 subparser.add_verbose_help()
87 subparser.set_defaults(process=process_beat)
88
89def parser_add_subcommand_tempo(subparsers):
90 # tempo subcommand
91 subparser = subparsers.add_parser('tempo',
92 help='estimate overall tempo in bpm')
93 subparser.add_input()
94 subparser.add_buf_hop_size(buf_size=1024, hop_size=512)
95 subparser.add_time_format()
96 subparser.add_verbose_help()
97 subparser.set_defaults(process=process_tempo)
98
99def parser_add_subcommand_notes(subparsers):
100 # notes subcommand
101 subparser = subparsers.add_parser('notes',
102 help='estimate midi-like notes (monophonic)')
103 subparser.add_input()
104 subparser.add_buf_hop_size()
105 subparser.add_silence()
106 subparser.add_release_drop()
107 subparser.add_time_format()
108 subparser.add_verbose_help()
109 subparser.set_defaults(process=process_notes)
110
111def parser_add_subcommand_mfcc(subparsers):
112 # mfcc subcommand
113 subparser = subparsers.add_parser('mfcc',
114 help='extract Mel-Frequency Cepstrum Coefficients')
115 subparser.add_input()
116 subparser.add_buf_hop_size()
117 subparser.add_time_format()
118 subparser.add_verbose_help()
119 subparser.set_defaults(process=process_mfcc)
120
121def parser_add_subcommand_melbands(subparsers):
122 # melbands subcommand
123 subparser = subparsers.add_parser('melbands',
124 help='extract energies in Mel-frequency bands')
125 subparser.add_input()
126 subparser.add_buf_hop_size()
127 subparser.add_time_format()
128 subparser.add_verbose_help()
129 subparser.set_defaults(process=process_melbands)
130
131def parser_add_subcommand_quiet(subparsers):
132 # quiet subcommand
133 subparser = subparsers.add_parser('quiet',
134 help='extract timestamps of quiet and loud regions')
135 subparser.add_input()
136 subparser.add_hop_size()
137 subparser.add_silence()
138 subparser.add_time_format()
139 subparser.add_verbose_help()
140 subparser.set_defaults(process=process_quiet)
141
142def parser_add_subcommand_cut(subparsers):
143 # cut subcommand
144 subparser = subparsers.add_parser('cut',
145 help='slice at timestamps')
146 subparser.add_input()
147 helpstr = "onset novelty function"
148 helpstr += " <default|energy|hfc|complex|phase|specdiff|kl|mkl|specflux>"
149 subparser.add_method(helpstr=helpstr)
150 subparser.add_buf_hop_size()
151 subparser.add_silence()
152 subparser.add_threshold(default=0.3)
153 subparser.add_minioi()
154 subparser.add_slicer_options()
155 subparser.add_time_format()
156 subparser.add_verbose_help()
157 subparser.set_defaults(process=process_cut)
158
159class AubioArgumentParser(argparse.ArgumentParser):
160
161 def add_input(self):
162 self.add_argument("source_uri", default=None, nargs='?',
163 help="input sound file to analyse", metavar = "<source_uri>")
164 self.add_argument("-i", "--input", dest = "source_uri2",
165 help="input sound file to analyse", metavar = "<source_uri>")
166 self.add_argument("-r", "--samplerate",
167 metavar = "<freq>", type=int,
168 action="store", dest="samplerate", default=0,
169 help="samplerate at which the file should be represented")
170
171 def add_verbose_help(self):
172 self.add_argument("-v", "--verbose",
173 action="count", dest="verbose", default=1,
174 help="make lots of noise [default]")
175 self.add_argument("-q", "--quiet",
176 action="store_const", dest="verbose", const=0,
177 help="be quiet")
178
179 def add_buf_hop_size(self, buf_size=512, hop_size=256):
180 self.add_buf_size(buf_size=buf_size)
181 self.add_hop_size(hop_size=hop_size)
182
183 def add_buf_size(self, buf_size=512):
184 self.add_argument("-B", "--bufsize",
185 action="store", dest="buf_size", default=buf_size,
186 metavar = "<size>", type=int,
187 help="buffer size [default=%d]" % buf_size)
188
189 def add_hop_size(self, hop_size=256):
190 self.add_argument("-H", "--hopsize",
191 metavar = "<size>", type=int,
192 action="store", dest="hop_size", default=hop_size,
193 help="overlap size [default=%d]" % hop_size)
194
195 def add_method(self, method='default', helpstr='method'):
196 self.add_argument("-m", "--method",
197 metavar = "<method>", type=str,
198 action="store", dest="method", default=method,
199 help="%s [default=%s]" % (helpstr, method))
200
201 def add_threshold(self, default=None):
202 self.add_argument("-t", "--threshold",
203 metavar = "<threshold>", type=float,
204 action="store", dest="threshold", default=default,
205 help="threshold [default=%s]" % default)
206
207 def add_silence(self):
208 self.add_argument("-s", "--silence",
209 metavar = "<value>", type=float,
210 action="store", dest="silence", default=-70,
211 help="silence threshold")
212
213 def add_release_drop(self):
214 self.add_argument("-d", "--release-drop",
215 metavar = "<value>", type=float,
216 action="store", dest="release_drop", default=10,
217 help="release drop threshold")
218
219 def add_minioi(self, default="12ms"):
220 self.add_argument("-M", "--minioi",
221 metavar = "<value>", type=str,
222 action="store", dest="minioi", default=default,
223 help="minimum Inter-Onset Interval [default=%s]" % default)
224
225 def add_pitch_unit(self, default="Hz"):
226 help_str = "frequency unit, should be one of Hz, midi, bin, cent"
227 help_str += " [default=%s]" % default
228 self.add_argument("-u", "--pitch-unit",
229 metavar = "<value>", type=str,
230 action="store", dest="pitch_unit", default=default,
231 help=help_str)
232
233 def add_time_format(self):
234 helpstr = "select time values output format (samples, ms, seconds)"
235 helpstr += " [default=seconds]"
236 self.add_argument("-T", "--time-format",
237 metavar='format',
238 dest="time_format",
239 default=None,
240 help=helpstr)
241
242 def add_slicer_options(self):
243 self.add_argument("-o", "--output", type = str,
244 metavar = "<outputdir>",
245 action="store", dest="output_directory", default=None,
246 help="specify path where slices of the original file should"
247 " be created")
248 self.add_argument("--cut-until-nsamples", type = int,
249 metavar = "<samples>",
250 action = "store", dest = "cut_until_nsamples", default = None,
251 help="how many extra samples should be added at the end of"
252 " each slice")
253 self.add_argument("--cut-every-nslices", type = int,
254 metavar = "<samples>",
255 action = "store", dest = "cut_every_nslices", default = None,
256 help="how many slices should be groupped together at each cut")
257 self.add_argument("--cut-until-nslices", type = int,
258 metavar = "<slices>",
259 action = "store", dest = "cut_until_nslices", default = None,
260 help="how many extra slices should be added at the end of"
261 " each slice")
262 self.add_argument("--create-first",
263 action = "store_true", dest = "create_first", default = False,
264 help="always include first slice")
265
266# some utilities
267
268def samples2seconds(n_frames, samplerate):
269 return "%f\t" % (n_frames / float(samplerate))
270
271def samples2milliseconds(n_frames, samplerate):
272 return "%f\t" % (1000. * n_frames / float(samplerate))
273
274def samples2samples(n_frames, _samplerate):
275 return "%d\t" % n_frames
276
277def timefunc(mode):
278 if mode is None or mode == 'seconds' or mode == 's':
279 return samples2seconds
280 elif mode == 'ms' or mode == 'milliseconds':
281 return samples2milliseconds
282 elif mode == 'samples':
283 return samples2samples
284 else:
285 raise ValueError("invalid time format '%s'" % mode)
286
287# definition of processing classes
288
289class default_process(object):
290 def __init__(self, args):
291 if 'time_format' in args:
292 self.time2string = timefunc(args.time_format)
293 if args.verbose > 2 and hasattr(self, 'options'):
294 name = type(self).__name__.split('_')[1]
295 optstr = ' '.join(['running', name, 'with options',
296 repr(self.options), '\n'])
297 sys.stderr.write(optstr)
298 def flush(self, frames_read, samplerate):
299 # optionally called at the end of process
300 pass
301
302 def parse_options(self, args, valid_opts):
303 # get any valid options found in a dictionnary of arguments
304 options = {k: v for k, v in vars(args).items() if k in valid_opts}
305 self.options = options
306
307 def remap_pvoc_options(self, options):
308 # FIXME: we need to remap buf_size to win_s, hop_size to hop_s
309 # adjust python/ext/py-phasevoc.c to understand buf_size/hop_size
310 if 'buf_size' in options:
311 options['win_s'] = options['buf_size']
312 del options['buf_size']
313 if 'hop_size' in options:
314 options['hop_s'] = options['hop_size']
315 del options['hop_size']
316 self.options = options
317
318class process_onset(default_process):
319 valid_opts = ['method', 'hop_size', 'buf_size', 'samplerate']
320 def __init__(self, args):
321 self.parse_options(args, self.valid_opts)
322 self.onset = aubio.onset(**self.options)
323 if args.threshold is not None:
324 self.onset.set_threshold(args.threshold)
325 if args.minioi:
326 if args.minioi.endswith('ms'):
327 self.onset.set_minioi_ms(float(args.minioi[:-2]))
328 elif args.minioi.endswith('s'):
329 self.onset.set_minioi_s(float(args.minioi[:-1]))
330 else:
331 self.onset.set_minioi(int(args.minioi))
332 if args.silence:
333 self.onset.set_silence(args.silence)
334 super(process_onset, self).__init__(args)
335 def __call__(self, block):
336 return self.onset(block)
337 def repr_res(self, res, _frames_read, samplerate):
338 if res[0] != 0:
339 outstr = self.time2string(self.onset.get_last(), samplerate)
340 sys.stdout.write(outstr + '\n')
341
342class process_pitch(default_process):
343 valid_opts = ['method', 'hop_size', 'buf_size', 'samplerate']
344 def __init__(self, args):
345 self.parse_options(args, self.valid_opts)
346 self.pitch = aubio.pitch(**self.options)
347 if args.pitch_unit is not None:
348 self.pitch.set_unit(args.pitch_unit)
349 if args.threshold is not None:
350 self.pitch.set_tolerance(args.threshold)
351 if args.silence is not None:
352 self.pitch.set_silence(args.silence)
353 super(process_pitch, self).__init__(args)
354 def __call__(self, block):
355 return self.pitch(block)
356 def repr_res(self, res, frames_read, samplerate):
357 fmt_out = self.time2string(frames_read, samplerate)
358 sys.stdout.write(fmt_out + "%.6f\n" % res[0])
359
360class process_beat(default_process):
361 valid_opts = ['method', 'hop_size', 'buf_size', 'samplerate']
362 def __init__(self, args):
363 self.parse_options(args, self.valid_opts)
364 self.tempo = aubio.tempo(**self.options)
365 super(process_beat, self).__init__(args)
366 def __call__(self, block):
367 return self.tempo(block)
368 def repr_res(self, res, _frames_read, samplerate):
369 if res[0] != 0:
370 outstr = self.time2string(self.tempo.get_last(), samplerate)
371 sys.stdout.write(outstr + '\n')
372
373class process_tempo(process_beat):
374 def __init__(self, args):
375 super(process_tempo, self).__init__(args)
376 self.beat_locations = []
377 def repr_res(self, res, _frames_read, samplerate):
378 if res[0] != 0:
379 self.beat_locations.append(self.tempo.get_last_s())
380 def flush(self, frames_read, samplerate):
381 import numpy as np
382 if len(self.beat_locations) < 2:
383 outstr = "unknown bpm"
384 else:
385 bpms = 60. / np.diff(self.beat_locations)
386 median_bpm = np.mean(bpms)
387 if len(self.beat_locations) < 10:
388 outstr = "%.2f bpm (uncertain)" % median_bpm
389 else:
390 outstr = "%.2f bpm" % median_bpm
391 sys.stdout.write(outstr + '\n')
392
393class process_notes(default_process):
394 valid_opts = ['method', 'hop_size', 'buf_size', 'samplerate']
395 def __init__(self, args):
396 self.parse_options(args, self.valid_opts)
397 self.notes = aubio.notes(**self.options)
398 if args.silence is not None:
399 self.notes.set_silence(args.silence)
400 if args.release_drop is not None:
401 self.notes.set_release_drop(args.release_drop)
402 super(process_notes, self).__init__(args)
403 def __call__(self, block):
404 return self.notes(block)
405 def repr_res(self, res, frames_read, samplerate):
406 if res[2] != 0: # note off
407 fmt_out = self.time2string(frames_read, samplerate)
408 sys.stdout.write(fmt_out + '\n')
409 if res[0] != 0: # note on
410 lastmidi = res[0]
411 fmt_out = "%f\t" % lastmidi
412 fmt_out += self.time2string(frames_read, samplerate)
413 sys.stdout.write(fmt_out) # + '\t')
414 def flush(self, frames_read, samplerate):
415 eof = self.time2string(frames_read, samplerate)
416 sys.stdout.write(eof + '\n')
417
418class process_mfcc(default_process):
419 def __init__(self, args):
420 valid_opts1 = ['hop_size', 'buf_size']
421 self.parse_options(args, valid_opts1)
422 self.remap_pvoc_options(self.options)
423 self.pv = aubio.pvoc(**self.options)
424
425 valid_opts2 = ['buf_size', 'n_filters', 'n_coeffs', 'samplerate']
426 self.parse_options(args, valid_opts2)
427 self.mfcc = aubio.mfcc(**self.options)
428
429 # remember all options
430 self.parse_options(args, list(set(valid_opts1 + valid_opts2)))
431
432 super(process_mfcc, self).__init__(args)
433
434 def __call__(self, block):
435 fftgrain = self.pv(block)
436 return self.mfcc(fftgrain)
437 def repr_res(self, res, frames_read, samplerate):
438 fmt_out = self.time2string(frames_read, samplerate)
439 fmt_out += ' '.join(["% 9.7f" % f for f in res.tolist()])
440 sys.stdout.write(fmt_out + '\n')
441
442class process_melbands(default_process):
443 def __init__(self, args):
444 self.args = args
445 valid_opts = ['hop_size', 'buf_size']
446 self.parse_options(args, valid_opts)
447 self.remap_pvoc_options(self.options)
448 self.pv = aubio.pvoc(**self.options)
449
450 valid_opts = ['buf_size', 'n_filters']
451 self.parse_options(args, valid_opts)
452 self.remap_pvoc_options(self.options)
453 self.filterbank = aubio.filterbank(**self.options)
454 self.filterbank.set_mel_coeffs_slaney(args.samplerate)
455
456 super(process_melbands, self).__init__(args)
457 def __call__(self, block):
458 fftgrain = self.pv(block)
459 return self.filterbank(fftgrain)
460 def repr_res(self, res, frames_read, samplerate):
461 fmt_out = self.time2string(frames_read, samplerate)
462 fmt_out += ' '.join(["% 9.7f" % f for f in res.tolist()])
463 sys.stdout.write(fmt_out + '\n')
464
465class process_quiet(default_process):
466 def __init__(self, args):
467 self.args = args
468 valid_opts = ['hop_size', 'silence']
469 self.parse_options(args, valid_opts)
470 self.wassilence = 1
471
472 if args.silence is not None:
473 self.silence = args.silence
474 super(process_quiet, self).__init__(args)
475
476 def __call__(self, block):
477 if aubio.silence_detection(block, self.silence) == 1:
478 if self.wassilence != 1:
479 self.wassilence = 1
480 return 2 # newly found silence
481 return 1 # silence again
482 else:
483 if self.wassilence != 0:
484 self.wassilence = 0
485 return -1 # newly found noise
486 return 0 # noise again
487
488 def repr_res(self, res, frames_read, samplerate):
489 fmt_out = None
490 if res == -1:
491 fmt_out = "NOISY: "
492 if res == 2:
493 fmt_out = "QUIET: "
494 if fmt_out is not None:
495 fmt_out += self.time2string(frames_read, samplerate)
496 sys.stdout.write(fmt_out + '\n')
497
498class process_cut(process_onset):
499 def __init__(self, args):
500 super(process_cut, self).__init__(args)
501 self.slices = []
502 self.options = args
503
504 def __call__(self, block):
505 ret = super(process_cut, self).__call__(block)
506 if ret:
507 self.slices.append(self.onset.get_last())
508 return ret
509
510 def flush(self, frames_read, samplerate):
511 _cut_slice(self.options, self.slices)
512 duration = float(frames_read) / float(samplerate)
513 base_info = '%(source_file)s' % \
514 {'source_file': self.options.source_uri}
515 base_info += ' (total %(duration).2fs at %(samplerate)dHz)\n' % \
516 {'duration': duration, 'samplerate': samplerate}
517 info = "created %d slices from " % len(self.slices)
518 info += base_info
519 sys.stderr.write(info)
520
521def _cut_slice(options, timestamps):
522 # cutting pass
523 nstamps = len(timestamps)
524 if nstamps > 0:
525 # generate output files
526 timestamps_end = None
527 if options.cut_every_nslices:
528 timestamps = timestamps[::options.cut_every_nslices]
529 nstamps = len(timestamps)
530 if options.cut_until_nslices and options.cut_until_nsamples:
531 msg = "using cut_until_nslices, but cut_until_nsamples is set"
532 warnings.warn(msg)
533 if options.cut_until_nsamples:
534 lag = options.cut_until_nsamples
535 timestamps_end = [t + lag for t in timestamps[1:]]
536 timestamps_end += [1e120]
537 if options.cut_until_nslices:
538 slice_lag = options.cut_until_nslices
539 timestamps_end = [t for t in timestamps[1 + slice_lag:]]
540 timestamps_end += [1e120] * (options.cut_until_nslices + 1)
541 aubio.slice_source_at_stamps(options.source_uri,
542 timestamps, timestamps_end = timestamps_end,
543 output_dir = options.output_directory,
544 samplerate = options.samplerate,
545 create_first = options.create_first)
546
547def main():
548 parser = aubio_parser()
549 if sys.version_info[0] != 3:
550 # on py2, create a dummy ArgumentParser to workaround the
551 # optional subcommand issue. See https://bugs.python.org/issue9253
552 # This ensures that:
553 # - version string is shown when only '-V' is passed
554 # - help is printed if '-V' is passed with any other argument
555 # - any other argument get forwarded to the real parser
556 parser_root = argparse.ArgumentParser(add_help=False)
557 parser_root.add_argument('-V', '--version', help="show version",
558 action="store_true", dest="show_version")
559 args, extras = parser_root.parse_known_args()
560 if not args.show_version: # no -V, forward to parser
561 args = parser.parse_args(extras, namespace=args)
562 elif len(extras) != 0: # -V with other arguments, print help
563 parser.print_help()
564 sys.exit(1)
565 else: # in py3, we can simply use parser directly
566 args = parser.parse_args()
567 if 'show_version' in args and args.show_version:
568 sys.stdout.write('aubio version ' + aubio.version + '\n')
569 sys.exit(0)
570 elif 'verbose' in args and args.verbose > 3:
571 sys.stderr.write('aubio version ' + aubio.version + '\n')
572 if 'command' not in args or args.command is None \
573 or args.command in ['help']:
574 # no command given, print help and return 1
575 parser.print_help()
576 if args.command and args.command in ['help']:
577 sys.exit(0)
578 else:
579 sys.exit(1)
580 elif not args.source_uri and not args.source_uri2:
581 sys.stderr.write("Error: a source is required\n")
582 parser.print_help()
583 sys.exit(1)
584 elif args.source_uri2 is not None:
585 args.source_uri = args.source_uri2
586 try:
587 # open source_uri
588 with aubio.source(args.source_uri, hop_size=args.hop_size,
589 samplerate=args.samplerate) as a_source:
590 # always update args.samplerate to native samplerate, in case
591 # source was opened with args.samplerate=0
592 args.samplerate = a_source.samplerate
593 # create the processor for this subcommand
594 processor = args.process(args)
595 frames_read = 0
596 while True:
597 # read new block from source
598 block, read = a_source()
599 # execute processor on this block
600 res = processor(block)
601 # print results for this block
602 if args.verbose > 0:
603 processor.repr_res(res, frames_read, a_source.samplerate)
604 # increment total number of frames read
605 frames_read += read
606 # exit loop at end of file
607 if read < a_source.hop_size:
608 break
609 # flush the processor if needed
610 processor.flush(frames_read, a_source.samplerate)
611 if args.verbose > 1:
612 fmt_string = "read {:.2f}s"
613 fmt_string += " ({:d} samples in {:d} blocks of {:d})"
614 fmt_string += " from {:s} at {:d}Hz\n"
615 sys.stderr.write(fmt_string.format(
616 frames_read / float(a_source.samplerate),
617 frames_read,
618 frames_read // a_source.hop_size + 1,
619 a_source.hop_size,
620 a_source.uri,
621 a_source.samplerate))
622 except KeyboardInterrupt:
623 sys.exit(1)
python/lib/aubio/cut.py+163−0
@@ -0,0 +1,163 @@
1#! /usr/bin/env python
2
3""" this file was written by Paul Brossier
4 it is released under the GNU/GPL license.
5"""
6
7import sys
8from aubio.cmd import AubioArgumentParser, _cut_slice
9
10def aubio_cut_parser():
11 parser = AubioArgumentParser()
12 parser.add_input()
13 parser.add_argument("-O", "--onset-method",
14 action="store", dest="onset_method", default='default',
15 metavar = "<onset_method>",
16 help="onset detection method [default=default] \
17 complexdomain|hfc|phase|specdiff|energy|kl|mkl")
18 # cutting methods
19 parser.add_argument("-b", "--beat",
20 action="store_true", dest="beat", default=False,
21 help="slice at beat locations")
22 """
23 parser.add_argument("-S", "--silencecut",
24 action="store_true", dest="silencecut", default=False,
25 help="use silence locations")
26 parser.add_argument("-s", "--silence",
27 metavar = "<value>",
28 action="store", dest="silence", default=-70,
29 help="silence threshold [default=-70]")
30 """
31 # algorithm parameters
32 parser.add_buf_hop_size()
33 parser.add_argument("-t", "--threshold", "--onset-threshold",
34 metavar = "<threshold>", type=float,
35 action="store", dest="threshold", default=0.3,
36 help="onset peak picking threshold [default=0.3]")
37 parser.add_argument("-c", "--cut",
38 action="store_true", dest="cut", default=False,
39 help="cut input sound file at detected labels")
40 parser.add_minioi()
41
42 """
43 parser.add_argument("-D", "--delay",
44 action = "store", dest = "delay", type = float,
45 metavar = "<seconds>", default=0,
46 help="number of seconds to take back [default=system]\
47 default system delay is 3*hopsize/samplerate")
48 parser.add_argument("-C", "--dcthreshold",
49 metavar = "<value>",
50 action="store", dest="dcthreshold", default=1.,
51 help="onset peak picking DC component [default=1.]")
52 parser.add_argument("-L", "--localmin",
53 action="store_true", dest="localmin", default=False,
54 help="use local minima after peak detection")
55 parser.add_argument("-d", "--derivate",
56 action="store_true", dest="derivate", default=False,
57 help="derivate onset detection function")
58 parser.add_argument("-z", "--zerocross",
59 metavar = "<value>",
60 action="store", dest="zerothres", default=0.008,
61 help="zero-crossing threshold for slicing [default=0.00008]")
62 # plotting functions
63 parser.add_argument("-p", "--plot",
64 action="store_true", dest="plot", default=False,
65 help="draw plot")
66 parser.add_argument("-x", "--xsize",
67 metavar = "<size>",
68 action="store", dest="xsize", default=1.,
69 type=float, help="define xsize for plot")
70 parser.add_argument("-y", "--ysize",
71 metavar = "<size>",
72 action="store", dest="ysize", default=1.,
73 type=float, help="define ysize for plot")
74 parser.add_argument("-f", "--function",
75 action="store_true", dest="func", default=False,
76 help="print detection function")
77 parser.add_argument("-n", "--no-onsets",
78 action="store_true", dest="nplot", default=False,
79 help="do not plot detected onsets")
80 parser.add_argument("-O", "--outplot",
81 metavar = "<output_image>",
82 action="store", dest="outplot", default=None,
83 help="save plot to output.{ps,png}")
84 parser.add_argument("-F", "--spectrogram",
85 action="store_true", dest="spectro", default=False,
86 help="add spectrogram to the plot")
87 """
88 parser.add_slicer_options()
89 parser.add_verbose_help()
90 return parser
91
92
93def _cut_analyze(options):
94 hopsize = options.hop_size
95 bufsize = options.buf_size
96 samplerate = options.samplerate
97 source_uri = options.source_uri
98
99 # analyze pass
100 from aubio import onset, tempo, source
101
102 s = source(source_uri, samplerate, hopsize)
103 if samplerate == 0:
104 samplerate = s.samplerate
105 options.samplerate = samplerate
106
107 if options.beat:
108 o = tempo(options.onset_method, bufsize, hopsize,
109 samplerate=samplerate)
110 else:
111 o = onset(options.onset_method, bufsize, hopsize,
112 samplerate=samplerate)
113 if options.minioi:
114 if options.minioi.endswith('ms'):
115 o.set_minioi_ms(int(options.minioi[:-2]))
116 elif options.minioi.endswith('s'):
117 o.set_minioi_s(int(options.minioi[:-1]))
118 else:
119 o.set_minioi(int(options.minioi))
120 o.set_threshold(options.threshold)
121
122 timestamps = []
123 total_frames = 0
124 while True:
125 samples, read = s()
126 if o(samples):
127 timestamps.append(o.get_last())
128 if options.verbose:
129 print("%.4f" % o.get_last_s())
130 total_frames += read
131 if read < hopsize:
132 break
133 del s
134 return timestamps, total_frames
135
136def main():
137 parser = aubio_cut_parser()
138 options = parser.parse_args()
139 if not options.source_uri and not options.source_uri2:
140 sys.stderr.write("Error: no file name given\n")
141 parser.print_help()
142 sys.exit(1)
143 elif options.source_uri2 is not None:
144 options.source_uri = options.source_uri2
145
146 # analysis
147 timestamps, total_frames = _cut_analyze(options)
148
149 # print some info
150 duration = float(total_frames) / float(options.samplerate)
151 base_info = '%(source_uri)s' % {'source_uri': options.source_uri}
152 base_info += ' (total %(duration).2fs at %(samplerate)dHz)\n' % \
153 {'duration': duration, 'samplerate': options.samplerate}
154
155 info = "found %d timestamps in " % len(timestamps)
156 info += base_info
157 sys.stderr.write(info)
158
159 if options.cut:
160 _cut_slice(options, timestamps)
161 info = "created %d slices from " % len(timestamps)
162 info += base_info
163 sys.stderr.write(info)
python/lib/aubio/midiconv.py+142−17
@@ -1,9 +1,11 @@
1# -*- coding: utf-8 -*-1# -*- coding: utf-8 -*-
2""" utilities to convert midi note number to and from note names """2""" utilities to convert midi note number to and from note names """
3 3
4__all__ = ['note2midi', 'midi2note', 'freq2note']
5
6import sys4import sys
5from ._aubio import freqtomidi, miditofreq
6
7__all__ = ['note2midi', 'midi2note', 'freq2note', 'note2freq']
8
7py3 = sys.version_info[0] == 39py3 = sys.version_info[0] == 3
8if py3:10if py3:
9 str_instances = str11 str_instances = str
@@ -12,18 +14,65 @@ else: · 2 unmodified lines
12 str_instances = (str, unicode)14 str_instances = (str, unicode)
13 int_instances = (int, long)15 int_instances = (int, long)
14 16
17
15def note2midi(note):18def note2midi(note):
16 " convert note name to midi note number, e.g. [C-1, G9] -> [0, 127] "19 """Convert note name to midi note number.
17 _valid_notenames = {'C': 0, 'D': 2, 'E': 4, 'F': 5, 'G': 7, 'A': 9, 'B': 11}20
18 _valid_modifiers = {None: 0, u'♮': 0, '#': +1, u'♯': +1, u'\udd2a': +2,21 Input string `note` should be composed of one note root
19 'b': -1, u'♭': -1, u'\ufffd': -2}22 and one octave, with optionally one modifier in between.
23
24 List of valid components:
25
26 - note roots: `C`, `D`, `E`, `F`, `G`, `A`, `B`,
27 - modifiers: `b`, `#`, as well as unicode characters
28 `𝄫`, `♭`, `♮`, `♯` and `𝄪`,
29 - octave numbers: `-1` -> `11`.
30
31 Parameters
32 ----------
33 note : str
34 note name
35
36 Returns
37 -------
38 int
39 corresponding midi note number
40
41 Examples
42 --------
43 >>> aubio.note2midi('C#4')
44 61
45 >>> aubio.note2midi('B♭5')
46 82
47
48 Raises
49 ------
50 TypeError
51 If `note` was not a string.
52 ValueError
53 If an error was found while converting `note`.
54
55 See Also
56 --------
57 midi2note, freqtomidi, miditofreq
58 """
59 _valid_notenames = {'C': 0, 'D': 2, 'E': 4, 'F': 5, 'G': 7,
60 'A': 9, 'B': 11}
61 _valid_modifiers = {
62 u'𝄫': -2, # double flat
63 u'♭': -1, 'b': -1, '\u266d': -1, # simple flat
64 u'♮': 0, '\u266e': 0, None: 0, # natural
65 '#': +1, u'♯': +1, '\u266f': +1, # sharp
66 u'𝄪': +2, # double sharp
67 }
20 _valid_octaves = range(-1, 10)68 _valid_octaves = range(-1, 10)
21 if not isinstance(note, str_instances):69 if not isinstance(note, str_instances):
22 raise TypeError("a string is required, got %s (%s)" % (note, str(type(note))))70 msg = "a string is required, got {:s} ({:s})"
71 raise TypeError(msg.format(str(type(note)), repr(note)))
23 if len(note) not in range(2, 5):72 if len(note) not in range(2, 5):
24 raise ValueError("string of 2 to 4 characters expected, got %d (%s)" \73 msg = "string of 2 to 4 characters expected, got {:d} ({:s})"
25 % (len(note), note))74 raise ValueError(msg.format(len(note), note))
26 notename, modifier, octave = [None]*375 notename, modifier, octave = [None] * 3
27 76
28 if len(note) == 4:77 if len(note) == 4:
29 notename, modifier, octave_sign, octave = note78 notename, modifier, octave_sign, octave = note
@@ -46,21 +95,97 @@ def note2midi(note): · 16 unmodified lines
46 if octave not in _valid_octaves:95 if octave not in _valid_octaves:
47 raise ValueError("%s is not a valid octave" % octave)96 raise ValueError("%s is not a valid octave" % octave)
48 97
49 midi = 12 + octave * 12 + _valid_notenames[notename] + _valid_modifiers[modifier]98 midi = (octave + 1) * 12 + _valid_notenames[notename] \
99 + _valid_modifiers[modifier]
50 if midi > 127:100 if midi > 127:
51 raise ValueError("%s is outside of the range C-2 to G8" % note)101 raise ValueError("%s is outside of the range C-2 to G8" % note)
52 return midi102 return midi
53 103
104
54def midi2note(midi):105def midi2note(midi):
55 " convert midi note number to note name, e.g. [0, 127] -> [C-1, G9] "106 """Convert midi note number to note name.
107
108 Parameters
109 ----------
110 midi : int [0, 128]
111 input midi note number
112
113 Returns
114 -------
115 str
116 note name
117
118 Examples
119 --------
120 >>> aubio.midi2note(70)
121 'A#4'
122 >>> aubio.midi2note(59)
123 'B3'
124
125 Raises
126 ------
127 TypeError
128 If `midi` was not an integer.
129 ValueError
130 If `midi` is out of the range `[0, 128]`.
131
132 See Also
133 --------
134 note2midi, miditofreq, freqtomidi
135 """
56 if not isinstance(midi, int_instances):136 if not isinstance(midi, int_instances):
57 raise TypeError("an integer is required, got %s" % midi)137 raise TypeError("an integer is required, got %s" % midi)
58 if midi not in range(0, 128):138 if midi not in range(0, 128):
59 raise ValueError("an integer between 0 and 127 is excepted, got %d" % midi)139 msg = "an integer between 0 and 127 is excepted, got {:d}"
60 _valid_notenames = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B']140 raise ValueError(msg.format(midi))
141 _valid_notenames = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#',
142 'A', 'A#', 'B']
61 return _valid_notenames[midi % 12] + str(int(midi / 12) - 1)143 return _valid_notenames[midi % 12] + str(int(midi / 12) - 1)
62 144
145
63def freq2note(freq):146def freq2note(freq):
64 " convert frequency in Hz to nearest note name, e.g. [0, 22050.] -> [C-1, G9] "147 """Convert frequency in Hz to nearest note name.
65 from aubio import freqtomidi148
66 return midi2note(int(freqtomidi(freq)))149 Parameters
150 ----------
151 freq : float [0, 23000[
152 input frequency, in Hz
153
154 Returns
155 -------
156 str
157 name of the nearest note
158
159 Example
160 -------
161 >>> aubio.freq2note(440)
162 'A4'
163 >>> aubio.freq2note(220.1)
164 'A3'
165 """
166 nearest_note = int(freqtomidi(freq) + .5)
167 return midi2note(nearest_note)
168
169
170def note2freq(note):
171 """Convert note name to corresponding frequency, in Hz.
172
173 Parameters
174 ----------
175 note : str
176 input note name
177
178 Returns
179 -------
180 freq : float [0, 23000[
181 frequency, in Hz
182
183 Example
184 -------
185 >>> aubio.note2freq('A4')
186 440
187 >>> aubio.note2freq('A3')
188 220.1
189 """
190 midi = note2midi(note)
191 return miditofreq(midi)
python/lib/aubio/slicing.py+81−14
@@ -5,26 +5,91 @@ from aubio import source, sink · 4 unmodified lines
5 5
6_max_timestamp = 1e1206_max_timestamp = 1e120
7 7
8
8def slice_source_at_stamps(source_file, timestamps, timestamps_end=None,9def slice_source_at_stamps(source_file, timestamps, timestamps_end=None,
9 output_dir=None, samplerate=0, hopsize=256):10 output_dir=None, samplerate=0, hopsize=256,
10 """ slice a sound file at given timestamps """11 create_first=False):
12 """Slice a sound file at given timestamps.
13
14 This function reads `source_file` and creates slices, new smaller
15 files each starting at `t` in `timestamps`, a list of integer
16 corresponding to time locations in `source_file`, in samples.
17
18 If `timestamps_end` is unspecified, the slices will end at
19 `timestamps_end[n] = timestamps[n+1]-1`, or the end of file.
20 Otherwise, `timestamps_end` should be a list with the same length
21 as `timestamps` containing the locations of the end of each slice.
22
23 If `output_dir` is unspecified, the new slices will be written in
24 the current directory. If `output_dir` is a string, new slices
25 will be written in `output_dir`, after creating the directory if
26 required.
27
28 The default `samplerate` is 0, meaning the original sampling rate
29 of `source_file` will be used. When using a sampling rate
30 different to the one of the original files, `timestamps` and
31 `timestamps_end` should be expressed in the re-sampled signal.
32
33 The `hopsize` parameter simply tells :class:`source` to use this
34 hopsize and does not change the output slices.
35
36 If `create_first` is True and `timestamps` does not start with `0`, the
37 first slice from `0` to `timestamps[0] - 1` will be automatically added.
38
39 Parameters
40 ----------
41 source_file : str
42 path of the resource to slice
43 timestamps : :obj:`list` of :obj:`int`
44 time stamps at which to slice, in samples
45 timestamps_end : :obj:`list` of :obj:`int` (optional)
46 time stamps at which to end the slices
47 output_dir : str (optional)
48 output directory to write the slices to
49 samplerate : int (optional)
50 samplerate to read the file at
51 hopsize : int (optional)
52 number of samples read from source per iteration
53 create_first : bool (optional)
54 always create the slice at the start of the file
55
56 Examples
57 --------
58 Create two slices: the first slice starts at the beginning of the
59 input file `loop.wav` and lasts exactly one second, starting at
60 sample `0` and ending at sample `44099`; the second slice starts
61 at sample `44100` and lasts until the end of the input file:
62
63 >>> aubio.slice_source_at_stamps('loop.wav', [0, 44100])
64
65 Create one slice, from 1 second to 2 seconds:
66
67 >>> aubio.slice_source_at_stamps('loop.wav', [44100], [44100 * 2 - 1])
68
69 Notes
70 -----
71 Slices may be overlapping. If `timestamps_end` is `1` element
72 shorter than `timestamps`, the last slice will end at the end of
73 the file.
74 """
11 75
12 if timestamps is None or len(timestamps) == 0:76 if not timestamps:
13 raise ValueError("no timestamps given")77 raise ValueError("no timestamps given")
14 78
15 if timestamps[0] != 0:79 if timestamps[0] != 0 and create_first:
16 timestamps = [0] + timestamps80 timestamps = [0] + timestamps
17 if timestamps_end is not None:81 if timestamps_end is not None:
18 timestamps_end = [timestamps[1] - 1] + timestamps_end82 timestamps_end = [timestamps[1] - 1] + timestamps_end
19 83
20 if timestamps_end is not None:84 if timestamps_end is not None:
21 if len(timestamps_end) != len(timestamps):85 if len(timestamps_end) == len(timestamps) - 1:
86 timestamps_end = timestamps_end + [_max_timestamp]
87 elif len(timestamps_end) != len(timestamps):
22 raise ValueError("len(timestamps_end) != len(timestamps)")88 raise ValueError("len(timestamps_end) != len(timestamps)")
23 else:89 else:
24 timestamps_end = [t - 1 for t in timestamps[1:]] + [_max_timestamp]90 timestamps_end = [t - 1 for t in timestamps[1:]] + [_max_timestamp]
25 91
26 regions = list(zip(timestamps, timestamps_end))92 regions = list(zip(timestamps, timestamps_end))
27 #print regions
28 93
29 source_base_name, _ = os.path.splitext(os.path.basename(source_file))94 source_base_name, _ = os.path.splitext(os.path.basename(source_file))
30 if output_dir is not None:95 if output_dir is not None:
@@ -32,8 +97,8 @@ def slice_source_at_stamps(source_file, timestamps, timestamps_end=None, · 1 unmodified line
32 os.makedirs(output_dir)97 os.makedirs(output_dir)
33 source_base_name = os.path.join(output_dir, source_base_name)98 source_base_name = os.path.join(output_dir, source_base_name)
34 99
35 def new_sink_name(source_base_name, timestamp, samplerate):100 def _new_sink_name(source_base_name, timestamp, samplerate):
36 """ create a sink based on a timestamp in samples, converted in seconds """101 # create name based on a timestamp in samples, converted in seconds
37 timestamp_seconds = timestamp / float(samplerate)102 timestamp_seconds = timestamp / float(samplerate)
38 return source_base_name + "_%011.6f" % timestamp_seconds + '.wav'103 return source_base_name + "_%011.6f" % timestamp_seconds + '.wav'
39 104
@@ -48,16 +113,17 @@ def slice_source_at_stamps(source_file, timestamps, timestamps_end=None, · 8 unmodified lines
48 # get hopsize new samples from source113 # get hopsize new samples from source
49 vec, read = _source.do_multi()114 vec, read = _source.do_multi()
50 # if the total number of frames read will exceed the next region start115 # if the total number of frames read will exceed the next region start
51 if len(regions) and total_frames + read >= regions[0][0]:116 while regions and total_frames + read >= regions[0][0]:
52 #print "getting", regions[0], "at", total_frames
53 # get next region117 # get next region
54 start_stamp, end_stamp = regions.pop(0)118 start_stamp, end_stamp = regions.pop(0)
55 # create a name for the sink119 # create a name for the sink
56 new_sink_path = new_sink_name(source_base_name, start_stamp, samplerate)120 new_sink_path = _new_sink_name(source_base_name, start_stamp,
121 samplerate)
57 # create its sink122 # create its sink
58 _sink = sink(new_sink_path, samplerate, _source.channels)123 _sink = sink(new_sink_path, samplerate, _source.channels)
59 # create a dictionary containing all this124 # create a dictionary containing all this
60 new_slice = {'start_stamp': start_stamp, 'end_stamp': end_stamp, 'sink': _sink}125 new_slice = {'start_stamp': start_stamp, 'end_stamp': end_stamp,
126 'sink': _sink}
61 # append the dictionary to the current list of slices127 # append the dictionary to the current list of slices
62 slices.append(new_slice)128 slices.append(new_slice)
63 129
@@ -69,18 +135,19 @@ def slice_source_at_stamps(source_file, timestamps, timestamps_end=None, · 5 unmodified lines
69 start = max(start_stamp - total_frames, 0)135 start = max(start_stamp - total_frames, 0)
70 # number of samples yet to written be until end of region136 # number of samples yet to written be until end of region
71 remaining = end_stamp - total_frames + 1137 remaining = end_stamp - total_frames + 1
72 #print current_slice, remaining, start
73 # not enough frames remaining, time to split138 # not enough frames remaining, time to split
74 if remaining < read:139 if remaining < read:
75 if remaining > start:140 if remaining > start:
76 # write remaining samples from current region141 # write remaining samples from current region
77 _sink.do_multi(vec[:, start:remaining], remaining - start)142 _sink.do_multi(vec[:, start:remaining], remaining - start)
78 #print "closing region", "remaining", remaining
79 # close this file143 # close this file
80 _sink.close()144 _sink.close()
81 elif read > start:145 elif read > start:
82 # write all the samples146 # write all the samples
83 _sink.do_multi(vec[:, start:read], read - start)147 _sink.do_multi(vec[:, start:read], read - start)
84 total_frames += read148 total_frames += read
149 # remove old slices
150 slices = list(filter(lambda s: s['end_stamp'] > total_frames,
151 slices))
85 if read < hopsize:152 if read < hopsize:
86 break153 break
python/lib/gen_code.py+65−23
@@ -2,6 +2,7 @@ aubiodefvalue = { · 1 unmodified line
2 # we have some clean up to do2 # we have some clean up to do
3 'buf_size': 'Py_default_vector_length',3 'buf_size': 'Py_default_vector_length',
4 'win_s': 'Py_default_vector_length',4 'win_s': 'Py_default_vector_length',
5 'size': 'Py_default_vector_length',
5 # and here too6 # and here too
6 'hop_size': 'Py_default_vector_length / 2',7 'hop_size': 'Py_default_vector_length / 2',
7 'hop_s': 'Py_default_vector_length / 2',8 'hop_s': 'Py_default_vector_length / 2',
@@ -84,6 +85,7 @@ objoutsize = { · 76 unmodified lines
84 'filterbank': 'self->n_filters',85 'filterbank': 'self->n_filters',
85 'tss': 'self->buf_size',86 'tss': 'self->buf_size',
86 'pitchshift': 'self->hop_size',87 'pitchshift': 'self->hop_size',
88 'dct': 'self->size',
87 }89 }
88 90
89objinputsize = {91objinputsize = {
@@ -179,6 +181,10 @@ class MappedObject(object): · 89 unmodified lines
179 self.do_inputs = [get_params_types_names(self.do_proto)[1]]181 self.do_inputs = [get_params_types_names(self.do_proto)[1]]
180 self.do_outputs = get_params_types_names(self.do_proto)[2:]182 self.do_outputs = get_params_types_names(self.do_proto)[2:]
181 struct_output_str = ["PyObject *{0[name]}; {1} c_{0[name]}".format(i, i['type'][:-1]) for i in self.do_outputs]183 struct_output_str = ["PyObject *{0[name]}; {1} c_{0[name]}".format(i, i['type'][:-1]) for i in self.do_outputs]
184 if len(self.prototypes['rdo']):
185 rdo_outputs = get_params_types_names(prototypes['rdo'][0])[2:]
186 struct_output_str += ["PyObject *{0[name]}; {1} c_{0[name]}".format(i, i['type'][:-1]) for i in rdo_outputs]
187 self.outputs += rdo_outputs
182 self.struct_outputs = ";\n ".join(struct_output_str)188 self.struct_outputs = ";\n ".join(struct_output_str)
183 189
184 #print ("input_params: ", map(split_type, get_input_params(self.do_proto)))190 #print ("input_params: ", map(split_type, get_input_params(self.do_proto)))
@@ -186,17 +192,26 @@ class MappedObject(object): · 1 unmodified line
186 192
187 def gen_code(self):193 def gen_code(self):
188 out = ""194 out = ""
189 out += self.gen_struct()195 try:
190 out += self.gen_doc()196 out += self.gen_struct()
191 out += self.gen_new()197 out += self.gen_doc()
192 out += self.gen_init()198 out += self.gen_new()
193 out += self.gen_del()199 out += self.gen_init()
194 out += self.gen_do()200 out += self.gen_del()
195 out += self.gen_memberdef()201 out += self.gen_do()
196 out += self.gen_set()202 if len(self.prototypes['rdo']):
197 out += self.gen_get()203 self.do_proto = self.prototypes['rdo'][0]
198 out += self.gen_methodef()204 self.do_inputs = [get_params_types_names(self.do_proto)[1]]
199 out += self.gen_typeobject()205 self.do_outputs = get_params_types_names(self.do_proto)[2:]
206 out += self.gen_do(method='rdo')
207 out += self.gen_memberdef()
208 out += self.gen_set()
209 out += self.gen_get()
210 out += self.gen_methodef()
211 out += self.gen_typeobject()
212 except Exception as e:
213 print ("Failed generating code for", self.shortname)
214 raise
200 return out215 return out
201 216
202 def gen_struct(self):217 def gen_struct(self):
@@ -380,12 +395,12 @@ Py_{shortname}_del (Py_{shortname} * self, PyObject * unused) · 177 unmodified lines
380""".format(del_fn = del_fn)395""".format(del_fn = del_fn)
381 return out396 return out
382 397
383 def gen_do(self):398 def gen_do(self, method = 'do'):
384 out = """399 out = """
385// do {shortname}400// do {shortname}
386static PyObject*401static PyObject*
387Py_{shortname}_do (Py_{shortname} * self, PyObject * args)402Pyaubio_{shortname}_{method} (Py_{shortname} * self, PyObject * args)
388{{""".format(**self.__dict__)403{{""".format(method = method, **self.__dict__)
389 input_params = self.do_inputs404 input_params = self.do_inputs
390 output_params = self.do_outputs405 output_params = self.do_outputs
391 #print input_params406 #print input_params
@@ -461,30 +476,51 @@ Py_{shortname}_do (Py_{shortname} * self, PyObject * args) · 69 unmodified lines
461// {shortname} setters476// {shortname} setters
462""".format(**self.__dict__)477""".format(**self.__dict__)
463 for set_param in self.prototypes['set']:478 for set_param in self.prototypes['set']:
464 params = get_params_types_names(set_param)[1]479 params = get_params_types_names(set_param)[1:]
465 paramtype = params['type']480 param = self.shortname.split('_set_')[-1]
481 paramdecls = "".join(["""
482 {0} {1};""".format(p['type'], p['name']) for p in params])
466 method_name = get_name(set_param)483 method_name = get_name(set_param)
467 param = method_name.split('aubio_'+self.shortname+'_set_')[-1]484 param = method_name.split('aubio_'+self.shortname+'_set_')[-1]
468 pyparamtype = pyargparse_chars[paramtype]485 refs = ", ".join(["&%s" % p['name'] for p in params])
486 paramlist = ", ".join(["%s" % p['name'] for p in params])
487 if len(params):
488 paramlist = "," + paramlist
489 pyparamtypes = ''.join([pyargparse_chars[p['type']] for p in params])
469 out += """490 out += """
470static PyObject *491static PyObject *
471Pyaubio_{shortname}_set_{param} (Py_{shortname} *self, PyObject *args)492Pyaubio_{shortname}_set_{param} (Py_{shortname} *self, PyObject *args)
472{{493{{
473 uint_t err = 0;494 uint_t err = 0;
474 {paramtype} {param};495 {paramdecls}
496""".format(param = param, paramdecls = paramdecls, **self.__dict__)
497
498 if len(refs) and len(pyparamtypes):
499 out += """
475 500
476 if (!PyArg_ParseTuple (args, "{pyparamtype}", &{param})) {{501 if (!PyArg_ParseTuple (args, "{pyparamtypes}", {refs})) {{
477 return NULL;502 return NULL;
478 }}503 }}
479 err = aubio_{shortname}_set_{param} (self->o, {param});504""".format(pyparamtypes = pyparamtypes, refs = refs)
505
506 out += """
507 err = aubio_{shortname}_set_{param} (self->o {paramlist});
480 508
481 if (err > 0) {{509 if (err > 0) {{
482 PyErr_SetString (PyExc_ValueError, "error running aubio_{shortname}_set_{param}");510 if (PyErr_Occurred() == NULL) {{
511 PyErr_SetString (PyExc_ValueError, "error running aubio_{shortname}_set_{param}");
512 }} else {{
513 // change the RuntimeError into ValueError
514 PyObject *type, *value, *traceback;
515 PyErr_Fetch(&type, &value, &traceback);
516 PyErr_Restore(PyExc_ValueError, value, traceback);
517 }}
483 return NULL;518 return NULL;
484 }}519 }}
485 Py_RETURN_NONE;520 Py_RETURN_NONE;
486}}521}}
487""".format(param = param, paramtype = paramtype, pyparamtype = pyparamtype, **self.__dict__)522""".format(param = param, refs = refs, paramdecls = paramdecls,
523 pyparamtypes = pyparamtypes, paramlist = paramlist, **self.__dict__)
488 return out524 return out
489 525
490 def gen_get(self):526 def gen_get(self):
@@ -525,6 +561,12 @@ static PyMethodDef Py_{shortname}_methods[] = {{""".format(**self.__dict__) · 34 unmodified lines
525 out += """561 out += """
526 {{"{shortname}", (PyCFunction) Py{name},562 {{"{shortname}", (PyCFunction) Py{name},
527 METH_NOARGS, ""}},""".format(name = name, shortname = shortname)563 METH_NOARGS, ""}},""".format(name = name, shortname = shortname)
564 for m in self.prototypes['rdo']:
565 name = get_name(m)
566 shortname = name.replace('aubio_%s_' % self.shortname, '')
567 out += """
568 {{"{shortname}", (PyCFunction) Py{name},
569 METH_VARARGS, ""}},""".format(name = name, shortname = shortname)
528 out += """570 out += """
529 {NULL} /* sentinel */571 {NULL} /* sentinel */
530};572};
@@ -550,7 +592,7 @@ PyTypeObject Py_{shortname}Type = {{ · 19 unmodified lines
550 0,592 0,
551 0,593 0,
552 0,594 0,
553 (ternaryfunc)Py_{shortname}_do,595 (ternaryfunc)Pyaubio_{shortname}_do,
554 0,596 0,
555 0,597 0,
556 0,598 0,
python/lib/gen_external.py+180−89
@@ -1,5 +1,10 @@
1import distutils.ccompiler1import distutils.ccompiler
2import sys, os, subprocess, glob2import sys
3import os
4import subprocess
5import glob
6from distutils.sysconfig import customize_compiler
7from gen_code import MappedObject
3 8
4header = os.path.join('src', 'aubio.h')9header = os.path.join('src', 'aubio.h')
5output_path = os.path.join('python', 'gen')10output_path = os.path.join('python', 'gen')
@@ -8,43 +13,44 @@ source_header = """// this file is generated! do not modify · 2 unmodified lines
8#include "aubio-types.h"13#include "aubio-types.h"
9"""14"""
10 15
11skip_objects = [16default_skip_objects = [
12 # already in ext/17 # already in ext/
13 'fft',18 'fft',
14 'pvoc',19 'pvoc',
15 'filter',20 'filter',
16 'filterbank',21 'filterbank',
17 #'resampler',22 # AUBIO_UNSTABLE
18 # AUBIO_UNSTABLE23 'hist',
19 'hist',24 'parameter',
20 'parameter',25 'scale',
21 'scale',26 'beattracking',
22 'beattracking',27 'resampler',
23 'resampler',28 'peakpicker',
24 'peakpicker',29 'pitchfcomb',
25 'pitchfcomb',30 'pitchmcomb',
26 'pitchmcomb',31 'pitchschmitt',
27 'pitchschmitt',32 'pitchspecacf',
28 'pitchspecacf',33 'pitchyin',
29 'pitchyin',34 'pitchyinfft',
30 'pitchyinfft',35 'pitchyinfast',
31 'sink',36 'sink',
32 'sink_apple_audio',37 'sink_apple_audio',
33 'sink_sndfile',38 'sink_sndfile',
34 'sink_wavwrite',39 'sink_wavwrite',
35 #'mfcc',40 #'mfcc',
36 'source',41 'source',
37 'source_apple_audio',42 'source_apple_audio',
38 'source_sndfile',43 'source_sndfile',
39 'source_avcodec',44 'source_avcodec',
40 'source_wavread',45 'source_wavread',
41 #'sampler',46 #'sampler',
42 'audio_unit',47 'audio_unit',
43 ]48 'spectral_whitening',
49]
50
44 51
45def get_preprocessor():52def get_preprocessor():
46 # findout which compiler to use53 # findout which compiler to use
47 from distutils.sysconfig import customize_compiler
48 compiler_name = distutils.ccompiler.get_default_compiler()54 compiler_name = distutils.ccompiler.get_default_compiler()
49 compiler = distutils.ccompiler.new_compiler(compiler=compiler_name)55 compiler = distutils.ccompiler.new_compiler(compiler=compiler_name)
50 try:56 try:
@@ -59,26 +65,50 @@ def get_preprocessor(): · 8 unmodified lines
59 print("Warning: failed initializing compiler ({:s})".format(repr(e)))65 print("Warning: failed initializing compiler ({:s})".format(repr(e)))
60 66
61 cpp_cmd = None67 cpp_cmd = None
62 if hasattr(compiler, 'preprocessor'): # for unixccompiler68 if hasattr(compiler, 'preprocessor'): # for unixccompiler
63 cpp_cmd = compiler.preprocessor69 cpp_cmd = compiler.preprocessor
64 elif hasattr(compiler, 'compiler'): # for ccompiler70 elif hasattr(compiler, 'compiler'): # for ccompiler
65 cpp_cmd = compiler.compiler.split()71 cpp_cmd = compiler.compiler.split()
66 cpp_cmd += ['-E']72 cpp_cmd += ['-E']
67 elif hasattr(compiler, 'cc'): # for msvccompiler73 elif hasattr(compiler, 'cc'): # for msvccompiler
68 cpp_cmd = compiler.cc.split()74 cpp_cmd = compiler.cc.split()
69 cpp_cmd += ['-E']75 cpp_cmd += ['-E']
70 76
77 # On win-amd64 (py3.x), the default compiler is cross-compiling, from x86
78 # to amd64 with %WIN_SDK_ROOT%\x86_amd64\cl.exe, but using this binary as a
79 # pre-processor generates no output, so we use %WIN_SDK_ROOT%\cl.exe
80 # instead.
81 if len(cpp_cmd) > 1 and 'cl.exe' in cpp_cmd[-2]:
82 plat = os.path.basename(os.path.dirname(cpp_cmd[-2]))
83 if plat == 'x86_amd64':
84 print('workaround on win64 to avoid empty pre-processor output')
85 cpp_cmd[-2] = cpp_cmd[-2].replace('x86_amd64', '')
86 elif True in ['amd64' in f for f in cpp_cmd]:
87 print('warning: not using workaround for', cpp_cmd[0], plat)
88
71 if not cpp_cmd:89 if not cpp_cmd:
72 print("Warning: could not guess preprocessor, using env's CC")90 print("Warning: could not guess preprocessor, using env's CC")
73 cpp_cmd = os.environ.get('CC', 'cc').split()91 cpp_cmd = os.environ.get('CC', 'cc').split()
74 cpp_cmd += ['-E']92 cpp_cmd += ['-E']
75 93 if 'emcc' in cpp_cmd:
94 cpp_cmd += ['-x', 'c'] # emcc defaults to c++, force C language
76 return cpp_cmd95 return cpp_cmd
77 96
78def get_cpp_objects(header=header):97
98def get_c_declarations(header=header, usedouble=False):
99 ''' return a dense and preprocessed string of all c declarations implied by aubio.h
100 '''
101 cpp_output = get_cpp_output(header=header, usedouble=usedouble)
102 return filter_cpp_output (cpp_output)
103
104
105def get_cpp_output(header=header, usedouble=False):
106 ''' find and run a C pre-processor on aubio.h '''
79 cpp_cmd = get_preprocessor()107 cpp_cmd = get_preprocessor()
80 108
81 macros = [('AUBIO_UNSTABLE', 1)]109 macros = [('AUBIO_UNSTABLE', 1)]
110 if usedouble:
111 macros += [('HAVE_AUBIO_DOUBLE', 1)]
82 112
83 if not os.path.isfile(header):113 if not os.path.isfile(header):
84 raise Exception("could not find include file " + header)114 raise Exception("could not find include file " + header)
@@ -89,56 +119,115 @@ def get_cpp_objects(header=header): · 4 unmodified lines
89 119
90 print("Running command: {:s}".format(" ".join(cpp_cmd)))120 print("Running command: {:s}".format(" ".join(cpp_cmd)))
91 proc = subprocess.Popen(cpp_cmd,121 proc = subprocess.Popen(cpp_cmd,
92 stderr=subprocess.PIPE,122 stderr=subprocess.PIPE,
93 stdout=subprocess.PIPE)123 stdout=subprocess.PIPE)
94 assert proc, 'Proc was none'124 assert proc, 'Proc was none'
95 cpp_output = proc.stdout.read()125 cpp_output = proc.stdout.read()
96 err_output = proc.stderr.read()126 err_output = proc.stderr.read()
127 if err_output:
128 print("Warning: preprocessor produced errors or warnings:\n%s" \
129 % err_output.decode('utf8'))
97 if not cpp_output:130 if not cpp_output:
98 raise Exception("preprocessor output is empty:\n%s" % err_output)131 raise_msg = "preprocessor output is empty! Running command " \
99 elif err_output:132 + "\"%s\" failed" % " ".join(cpp_cmd)
100 print ("Warning: preprocessor produced warnings:\n%s" % err_output)133 if err_output:
134 raise_msg += " with stderr: \"%s\"" % err_output.decode('utf8')
135 else:
136 raise_msg += " with no stdout or stderr"
137 raise Exception(raise_msg)
101 if not isinstance(cpp_output, list):138 if not isinstance(cpp_output, list):
102 cpp_output = [l.strip() for l in cpp_output.decode('utf8').split('\n')]139 cpp_output = [l.strip() for l in cpp_output.decode('utf8').split('\n')]
103 140
104 cpp_output = filter(lambda y: len(y) > 1, cpp_output)141 return cpp_output
142
143def filter_cpp_output(cpp_raw_output):
144 ''' prepare cpp-output for parsing '''
145 cpp_output = filter(lambda y: len(y) > 1, cpp_raw_output)
105 cpp_output = list(filter(lambda y: not y.startswith('#'), cpp_output))146 cpp_output = list(filter(lambda y: not y.startswith('#'), cpp_output))
106 147
107 i = 1148 i = 1
108 while 1:149 while 1:
109 if i >= len(cpp_output): break150 if i >= len(cpp_output):
110 if cpp_output[i-1].endswith(',') or cpp_output[i-1].endswith('{') or cpp_output[i].startswith('}'):151 break
111 cpp_output[i] = cpp_output[i-1] + ' ' + cpp_output[i]152 if ('{' in cpp_output[i - 1]) and ('}' not in cpp_output[i - 1]) or (';' not in cpp_output[i - 1]):
112 cpp_output.pop(i-1)153 cpp_output[i] = cpp_output[i - 1] + ' ' + cpp_output[i]
154 cpp_output.pop(i - 1)
155 elif ('}' in cpp_output[i]):
156 cpp_output[i] = cpp_output[i - 1] + ' ' + cpp_output[i]
157 cpp_output.pop(i - 1)
113 else:158 else:
114 i += 1159 i += 1
115 160
116 typedefs = filter(lambda y: y.startswith ('typedef struct _aubio'), cpp_output)161 # clean pointer notations
162 tmp = []
163 for l in cpp_output:
164 tmp += [l.replace(' *', ' * ')]
165 cpp_output = tmp
117 166
167 return cpp_output
168
169
170def get_cpp_objects_from_c_declarations(c_declarations, skip_objects=None):
171 if skip_objects is None:
172 skip_objects = default_skip_objects
173 typedefs = filter(lambda y: y.startswith('typedef struct _aubio'), c_declarations)
118 cpp_objects = [a.split()[3][:-1] for a in typedefs]174 cpp_objects = [a.split()[3][:-1] for a in typedefs]
175 cpp_objects_filtered = filter(lambda y: not y[6:-2] in skip_objects, cpp_objects)
176 return cpp_objects_filtered
177
178
179def get_all_func_names_from_lib(lib):
180 ''' return flat string of all function used in lib
181 '''
182 res = []
183 for _, v in lib.items():
184 if isinstance(v, dict):
185 res += get_all_func_names_from_lib(v)
186 elif isinstance(v, list):
187 for elem in v:
188 e = elem.split('(')
189 if len(e) < 2:
190 continue # not a function
191 fname_part = e[0].strip().split(' ')
192 fname = fname_part[-1]
193 if fname:
194 res += [fname]
195 else:
196 raise NameError('gen_lib : weird function: ' + str(e))
119 197
120 return cpp_output, cpp_objects198 return res
121 199
122 200
123def analyze_cpp_output(cpp_objects, cpp_output):201def generate_lib_from_c_declarations(cpp_objects, c_declarations):
202 ''' returns a lib from given cpp_object names
203
204 a lib is a dict grouping functions by family (onset,pitch...)
205 each eement is itself a dict of functions grouped by puposes as :
206 struct, new, del, do, get, set and other
207 '''
124 lib = {}208 lib = {}
125 209
126 for o in cpp_objects:210 for o in cpp_objects:
127 if o[:6] != 'aubio_':211 shortname = o
128 continue212 if o[:6] == 'aubio_':
129 shortname = o[6:-2]213 shortname = o[6:-2] # without aubio_ prefix and _t suffix
130 if shortname in skip_objects:214
131 continue215 lib[shortname] = {'struct': [], 'new': [], 'del': [], 'do': [], 'rdo': [], 'get': [], 'set': [], 'other': []}
132 lib[shortname] = {'struct': [], 'new': [], 'del': [], 'do': [], 'get': [], 'set': [], 'other': []}
133 lib[shortname]['longname'] = o216 lib[shortname]['longname'] = o
134 lib[shortname]['shortname'] = shortname217 lib[shortname]['shortname'] = shortname
135 for fn in cpp_output:218
136 if o[:-1] in fn:219 fullshortname = o[:-2] # name without _t suffix
137 #print "found", o[:-1], "in", fn220
221 for fn in c_declarations:
222 func_name = fn.split('(')[0].strip().split(' ')[-1]
223 if func_name.startswith(fullshortname + '_') or func_name.endswith(fullshortname):
224 # print "found", shortname, "in", fn
138 if 'typedef struct ' in fn:225 if 'typedef struct ' in fn:
139 lib[shortname]['struct'].append(fn)226 lib[shortname]['struct'].append(fn)
140 elif '_do' in fn:227 elif '_do' in fn:
141 lib[shortname]['do'].append(fn)228 lib[shortname]['do'].append(fn)
229 elif '_rdo' in fn:
230 lib[shortname]['rdo'].append(fn)
142 elif 'new_' in fn:231 elif 'new_' in fn:
143 lib[shortname]['new'].append(fn)232 lib[shortname]['new'].append(fn)
144 elif 'del_' in fn:233 elif 'del_' in fn:
@@ -148,52 +237,52 @@ def analyze_cpp_output(cpp_objects, cpp_output): · 3 unmodified lines
148 elif '_set_' in fn:237 elif '_set_' in fn:
149 lib[shortname]['set'].append(fn)238 lib[shortname]['set'].append(fn)
150 else:239 else:
151 #print "no idea what to do about", fn240 # print "no idea what to do about", fn
152 lib[shortname]['other'].append(fn)241 lib[shortname]['other'].append(fn)
153 return lib242 return lib
154 243
155def print_cpp_output_results(lib, cpp_output):244
156 for fn in cpp_output:245def print_c_declarations_results(lib, c_declarations):
246 for fn in c_declarations:
157 found = 0247 found = 0
158 for o in lib:248 for o in lib:
159 for family in lib[o]:249 for family in lib[o]:
160 if fn in lib[o][family]:250 if fn in lib[o][family]:
161 found = 1251 found = 1
162 if found == 0:252 if found == 0:
163 print ("missing", fn)253 print("missing", fn)
164 254
165 for o in lib:255 for o in lib:
166 for family in lib[o]:256 for family in lib[o]:
167 if type(lib[o][family]) == str:257 if type(lib[o][family]) == str:
168 print ( "{:15s} {:10s} {:s}".format(o, family, lib[o][family] ) )258 print("{:15s} {:10s} {:s}".format(o, family, lib[o][family]))
169 elif len(lib[o][family]) == 1:259 elif len(lib[o][family]) == 1:
170 print ( "{:15s} {:10s} {:s}".format(o, family, lib[o][family][0] ) )260 print("{:15s} {:10s} {:s}".format(o, family, lib[o][family][0]))
171 else:261 else:
172 print ( "{:15s} {:10s} {:s}".format(o, family, lib[o][family] ) )262 print("{:15s} {:10s} {:s}".format(o, family, lib[o][family]))
173 263
174 264
175def generate_external(header=header, output_path=output_path, usedouble=False, overwrite=True):265def generate_external(header=header, output_path=output_path, usedouble=False, overwrite=True):
176 if not os.path.isdir(output_path): os.mkdir(output_path)266 if not os.path.isdir(output_path):
177 elif not overwrite: return glob.glob(os.path.join(output_path, '*.c'))267 os.mkdir(output_path)
268 elif not overwrite:
269 return sorted(glob.glob(os.path.join(output_path, '*.c')))
178 270
179 cpp_output, cpp_objects = get_cpp_objects(header)271 c_declarations = get_c_declarations(header, usedouble=usedouble)
272 cpp_objects = get_cpp_objects_from_c_declarations(c_declarations)
180 273
181 lib = analyze_cpp_output(cpp_objects, cpp_output)274 lib = generate_lib_from_c_declarations(cpp_objects, c_declarations)
182 # print_cpp_output_results(lib, cpp_output)275 # print_c_declarations_results(lib, c_declarations)
183 276
184 sources_list = []277 sources_list = []
185 try:
186 from .gen_code import MappedObject
187 except (SystemError, ValueError):
188 from gen_code import MappedObject
189 for o in lib:278 for o in lib:
190 out = source_header279 out = source_header
191 mapped = MappedObject(lib[o], usedouble = usedouble)280 mapped = MappedObject(lib[o], usedouble=usedouble)
192 out += mapped.gen_code()281 out += mapped.gen_code()
193 output_file = os.path.join(output_path, 'gen-%s.c' % o)282 output_file = os.path.join(output_path, 'gen-%s.c' % o)
194 with open(output_file, 'w') as f:283 with open(output_file, 'w') as f:
195 f.write(out)284 f.write(out)
196 print ("wrote %s" % output_file )285 print("wrote %s" % output_file)
197 sources_list.append(output_file)286 sources_list.append(output_file)
198 287
199 out = source_header288 out = source_header
@@ -205,23 +294,23 @@ int generated_types_ready (void) · 5 unmodified lines
205{{294{{
206 return ({pycheck_types});295 return ({pycheck_types});
207}}296}}
208""".format(pycheck_types = check_types)297""".format(pycheck_types=check_types)
209 298
210 add_types = "".join(["""299 add_types = "".join(["""
211 Py_INCREF (&Py_{name}Type);300 Py_INCREF (&Py_{name}Type);
212 PyModule_AddObject(m, "{name}", (PyObject *) & Py_{name}Type);""".format(name = o) for o in lib])301 PyModule_AddObject(m, "{name}", (PyObject *) & Py_{name}Type);""".format(name=o) for o in lib])
213 out += """302 out += """
214 303
215void add_generated_objects ( PyObject *m )304void add_generated_objects ( PyObject *m )
216{{305{{
217{add_types}306{add_types}
218}}307}}
219""".format(add_types = add_types)308""".format(add_types=add_types)
220 309
221 output_file = os.path.join(output_path, 'aubio-generated.c')310 output_file = os.path.join(output_path, 'aubio-generated.c')
222 with open(output_file, 'w') as f:311 with open(output_file, 'w') as f:
223 f.write(out)312 f.write(out)
224 print ("wrote %s" % output_file )313 print("wrote %s" % output_file)
225 sources_list.append(output_file)314 sources_list.append(output_file)
226 315
227 objlist = "".join(["extern PyTypeObject Py_%sType;\n" % p for p in lib])316 objlist = "".join(["extern PyTypeObject Py_%sType;\n" % p for p in lib])
@@ -239,17 +328,19 @@ void add_generated_objects ( PyObject *m ) · 11 unmodified lines
239{objlist}328{objlist}
240int generated_objects ( void );329int generated_objects ( void );
241void add_generated_objects( PyObject *m );330void add_generated_objects( PyObject *m );
242""".format(objlist = objlist)331""".format(objlist=objlist)
243 332
244 output_file = os.path.join(output_path, 'aubio-generated.h')333 output_file = os.path.join(output_path, 'aubio-generated.h')
245 with open(output_file, 'w') as f:334 with open(output_file, 'w') as f:
246 f.write(out)335 f.write(out)
247 print ("wrote %s" % output_file )336 print("wrote %s" % output_file)
248 # no need to add header to list of sources337 # no need to add header to list of sources
249 338
250 return sources_list339 return sorted(sources_list)
251 340
252if __name__ == '__main__':341if __name__ == '__main__':
253 if len(sys.argv) > 1: header = sys.argv[1]342 if len(sys.argv) > 1:
254 if len(sys.argv) > 2: output_path = sys.argv[2]343 header = sys.argv[1]
344 if len(sys.argv) > 2:
345 output_path = sys.argv[2]
255 generate_external(header, output_path)346 generate_external(header, output_path)
python/lib/moresetuptools.py+78−26
@@ -2,7 +2,9 @@ · 1 unmodified line
2#2#
3import sys, os, glob, subprocess3import sys, os, glob, subprocess
4import distutils, distutils.command.clean, distutils.dir_util4import distutils, distutils.command.clean, distutils.dir_util
5from .gen_external import generate_external, header, output_path5from gen_external import generate_external, header, output_path
6
7from this_version import get_aubio_version
6 8
7# inspired from https://gist.github.com/abergmeier/94889909# inspired from https://gist.github.com/abergmeier/9488990
8def add_packages(packages, ext=None, **kw):10def add_packages(packages, ext=None, **kw):
@@ -21,6 +23,7 @@ def add_packages(packages, ext=None, **kw): · 12 unmodified lines
21 }23 }
22 24
23 for package in packages:25 for package in packages:
26 print("checking for {:s}".format(package))
24 cmd = ['pkg-config', '--libs', '--cflags', package]27 cmd = ['pkg-config', '--libs', '--cflags', package]
25 try:28 try:
26 tokens = subprocess.check_output(cmd)29 tokens = subprocess.check_output(cmd)
@@ -54,13 +57,14 @@ def add_local_aubio_lib(ext): · 27 unmodified lines
54 57
55def add_local_aubio_sources(ext):58def add_local_aubio_sources(ext):
56 """ build aubio inside python module instead of linking against libaubio """59 """ build aubio inside python module instead of linking against libaubio """
57 print("Warning: libaubio was not built with waf, adding src/")60 print("Info: libaubio was not installed or built locally with waf, adding src/")
58 # create an empty header, macros will be passed on the command line61 aubio_sources = sorted(glob.glob(os.path.join('src', '**.c')))
59 fake_config_header = os.path.join('python', 'ext', 'config.h')62 aubio_sources += sorted(glob.glob(os.path.join('src', '*', '**.c')))
60 distutils.file_util.write_file(fake_config_header, "")
61 aubio_sources = glob.glob(os.path.join('src', '**.c'))
62 aubio_sources += glob.glob(os.path.join('src', '*', '**.c'))
63 ext.sources += aubio_sources63 ext.sources += aubio_sources
64
65def add_local_macros(ext, usedouble = False):
66 if usedouble:
67 ext.define_macros += [('HAVE_AUBIO_DOUBLE', 1)]
64 # define macros (waf puts them in build/src/config.h)68 # define macros (waf puts them in build/src/config.h)
65 for define_macro in ['HAVE_STDLIB_H', 'HAVE_STDIO_H',69 for define_macro in ['HAVE_STDLIB_H', 'HAVE_STDIO_H',
66 'HAVE_MATH_H', 'HAVE_STRING_H',70 'HAVE_MATH_H', 'HAVE_STRING_H',
@@ -69,22 +73,31 @@ def add_local_aubio_sources(ext): · 2 unmodified lines
69 'HAVE_MEMCPY_HACKS']:73 'HAVE_MEMCPY_HACKS']:
70 ext.define_macros += [(define_macro, 1)]74 ext.define_macros += [(define_macro, 1)]
71 75
76def add_external_deps(ext, usedouble = False):
72 # loof for additional packages77 # loof for additional packages
73 print("Info: looking for *optional* additional packages")78 print("Info: looking for *optional* additional packages")
74 packages = ['libavcodec', 'libavformat', 'libavutil', 'libavresample',79 packages = ['libavcodec', 'libavformat', 'libavutil',
80 'libswresample', 'libavresample',
75 'jack',81 'jack',
76 'sndfile', 'samplerate',82 'sndfile',
77 'rubberband',83 'rubberband',
78 #'fftw3f',84 #'fftw3f',
79 ]85 ]
86 # samplerate only works with float
87 if usedouble is False:
88 packages += ['samplerate']
89 else:
90 print("Info: not adding libsamplerate in double precision mode")
80 add_packages(packages, ext=ext)91 add_packages(packages, ext=ext)
81 if 'avcodec' in ext.libraries \92 if 'avcodec' in ext.libraries \
82 and 'avformat' in ext.libraries \93 and 'avformat' in ext.libraries \
83 and 'avutil' in ext.libraries \94 and 'avutil' in ext.libraries:
84 and 'avresample' in ext.libraries:95 if 'swresample' in ext.libraries:
85 ext.define_macros += [('HAVE_LIBAV', 1)]96 ext.define_macros += [('HAVE_SWRESAMPLE', 1)]
86 if 'jack' in ext.libraries:97 elif 'avresample' in ext.libraries:
87 ext.define_macros += [('HAVE_JACK', 1)]98 ext.define_macros += [('HAVE_AVRESAMPLE', 1)]
99 if 'swresample' in ext.libraries or 'avresample' in ext.libraries:
100 ext.define_macros += [('HAVE_LIBAV', 1)]
88 if 'sndfile' in ext.libraries:101 if 'sndfile' in ext.libraries:
89 ext.define_macros += [('HAVE_SNDFILE', 1)]102 ext.define_macros += [('HAVE_SNDFILE', 1)]
90 if 'samplerate' in ext.libraries:103 if 'samplerate' in ext.libraries:
@@ -107,39 +120,78 @@ def add_local_aubio_sources(ext): · 16 unmodified lines
107 120
108 ext.define_macros += [('HAVE_WAVWRITE', 1)]121 ext.define_macros += [('HAVE_WAVWRITE', 1)]
109 ext.define_macros += [('HAVE_WAVREAD', 1)]122 ext.define_macros += [('HAVE_WAVREAD', 1)]
110 # TODO:123
111 # add cblas124 # TODO: add cblas
112 if 0:125 if 0:
113 ext.libraries += ['cblas']126 ext.libraries += ['cblas']
114 ext.define_macros += [('HAVE_ATLAS_CBLAS_H', 1)]127 ext.define_macros += [('HAVE_ATLAS_CBLAS_H', 1)]
115 128
116def add_system_aubio(ext):129def add_system_aubio(ext):
117 # use pkg-config to find aubio's location130 # use pkg-config to find aubio's location
118 add_packages(['aubio'], ext)131 aubio_version = get_aubio_version()
132 add_packages(['aubio = ' + aubio_version], ext)
119 if 'aubio' not in ext.libraries:133 if 'aubio' not in ext.libraries:
120 print("Error: libaubio not found")134 print("Info: aubio " + aubio_version + " was not found by pkg-config")
135 else:
136 print("Info: using system aubio " + aubio_version + " found in " + ' '.join(ext.library_dirs))
137
138def add_libav_on_win(ext):
139 """ no pkg-config on windows, simply assume these libs are available """
140 ext.libraries += ['avformat', 'avutil', 'avcodec', 'swresample']
141 for define_macro in ['HAVE_LIBAV', 'HAVE_SWRESAMPLE']:
142 ext.define_macros += [(define_macro, 1)]
121 143
122class CleanGenerated(distutils.command.clean.clean):144class CleanGenerated(distutils.command.clean.clean):
123 def run(self):145 def run(self):
124 distutils.dir_util.remove_tree(output_path)146 if os.path.isdir(output_path):
125 distutils.command.clean.clean.run(self)147 distutils.dir_util.remove_tree(output_path)
148
149from distutils.command.build_ext import build_ext as _build_ext
150class build_ext(_build_ext):
126 151
127class GenerateCommand(distutils.cmd.Command):152 user_options = _build_ext.user_options + [
128 description = 'generate gen/gen-*.c files from ../src/aubio.h'
129 user_options = [
130 # The format is (long option, short option, description).153 # The format is (long option, short option, description).
131 ('enable-double', None, 'use HAVE_AUBIO_DOUBLE=1 (default: 0)'),154 ('enable-double', None, 'use HAVE_AUBIO_DOUBLE=1 (default: 0)'),
132 ]155 ]
133 156
134 def initialize_options(self):157 def initialize_options(self):
158 _build_ext.initialize_options(self)
135 self.enable_double = False159 self.enable_double = False
136 160
137 def finalize_options(self):161 def finalize_options(self):
162 _build_ext.finalize_options(self)
138 if self.enable_double:163 if self.enable_double:
139 self.announce(164 self.announce(
140 'will generate code for aubio compiled with HAVE_AUBIO_DOUBLE=1',165 'will generate code for aubio compiled with HAVE_AUBIO_DOUBLE=1',
141 level=distutils.log.INFO)166 level=distutils.log.INFO)
142 167
143 def run(self):168 def build_extension(self, extension):
144 self.announce( 'Generating code', level=distutils.log.INFO)169 if self.enable_double or 'HAVE_AUBIO_DOUBLE' in os.environ:
145 generated_object_files = generate_external(header, output_path, usedouble=self.enable_double)170 enable_double = True
171 else:
172 enable_double = False
173 # seack for aubio headers and lib in PKG_CONFIG_PATH
174 add_system_aubio(extension)
175 # the lib was not installed on this system
176 if 'aubio' not in extension.libraries:
177 # use local src/aubio.h
178 if os.path.isfile(os.path.join('src', 'aubio.h')):
179 add_local_aubio_header(extension)
180 add_local_macros(extension, usedouble=enable_double)
181 # look for a local waf build
182 if os.path.isfile(os.path.join('build','src', 'fvec.c.1.o')):
183 add_local_aubio_lib(extension)
184 else:
185 # check for external dependencies
186 add_external_deps(extension, usedouble=enable_double)
187 # force adding libav on windows
188 if os.name == 'nt' and ('WITH_LIBAV' in os.environ \
189 or 'CONDA_PREFIX' in os.environ):
190 add_libav_on_win(extension)
191 # add libaubio sources and look for optional deps with pkg-config
192 add_local_aubio_sources(extension)
193 # generate files python/gen/*.c, python/gen/aubio-generated.h
194 extension.include_dirs += [ output_path ]
195 extension.sources += generate_external(header, output_path, overwrite = False,
196 usedouble=enable_double)
197 return _build_ext.build_extension(self, extension)

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t