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 @@
11 #! /usr/bin/env python
2+# -*- coding: utf8 -*-
3+
4+"""
5+aubio
6+=====
7+
8+Provides a number of classes and functions for music and audio signal
9+analysis.
10+
11+How to use the documentation
12+----------------------------
13+
14+Documentation of the python module is available as docstrings provided
15+within the code, and a reference guide available online from `the
16+aubio homepage <https://aubio.org/documentation>`_.
17+
18+The docstrings examples are written assuming `aubio` and `numpy` have been
19+imported with:
20+
21+>>> import aubio
22+>>> import numpy as np
23+"""
224
325 import numpy
4−from ._aubio import *
26+from ._aubio import __version__ as version
527 from ._aubio import float_type
28+from ._aubio import *
629 from .midiconv import *
730 from .slicing import *
831
32+
933 class 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'))
1158
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):
1375 if isinstance(input_arg, int):
1476 if input_arg == 0:
1577 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')
1779 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+
6+This file was written by Paul Brossier <piem@aubio.org> and is released under
7+the GNU/GPL v3.
8+
9+Note: this script is mostly about parsing command line arguments. For more
10+readable code examples, check out the `python/demos` folder."""
11+
12+import sys
13+import argparse
14+import warnings
15+import aubio
16+
17+def 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+
41+def 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+
47+def 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+
64+def 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+
79+def 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+
89+def 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+
99+def 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+
111+def 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+
121+def 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+
131+def 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+
142+def 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+
159+class 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+
268+def samples2seconds(n_frames, samplerate):
269+ return "%f\t" % (n_frames / float(samplerate))
270+
271+def samples2milliseconds(n_frames, samplerate):
272+ return "%f\t" % (1000. * n_frames / float(samplerate))
273+
274+def samples2samples(n_frames, _samplerate):
275+ return "%d\t" % n_frames
276+
277+def 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+
289+class 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+
318+class 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+
342+class 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+
360+class 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+
373+class 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+
393+class 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+
418+class 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+
442+class 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+
465+class 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+
498+class 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+
521+def _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+
547+def 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+
7+import sys
8+from aubio.cmd import AubioArgumentParser, _cut_slice
9+
10+def 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+
93+def _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+
136+def 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 @@
11 # -*- coding: utf-8 -*-
22 """ utilities to convert midi note number to and from note names """
33
4−__all__ = ['note2midi', 'midi2note', 'freq2note']
5−
64 import sys
5+from ._aubio import freqtomidi, miditofreq
6+
7+__all__ = ['note2midi', 'midi2note', 'freq2note', 'note2freq']
8+
79 py3 = sys.version_info[0] == 3
810 if py3:
911 str_instances = str
@@ -12,18 +14,65 @@ else: · 2 unmodified lines
1214 str_instances = (str, unicode)
1315 int_instances = (int, long)
1416
17+
1518 def note2midi(note):
16− " convert note name to midi note number, e.g. [C-1, G9] -> [0, 127] "
17− _valid_notenames = {'C': 0, 'D': 2, 'E': 4, 'F': 5, 'G': 7, 'A': 9, 'B': 11}
18− _valid_modifiers = {None: 0, u'♮': 0, '#': +1, u'♯': +1, u'\udd2a': +2,
19− 'b': -1, u'♭': -1, u'\ufffd': -2}
19+ """Convert note name to midi note number.
20+
21+ Input string `note` should be composed of one note root
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+ }
2068 _valid_octaves = range(-1, 10)
2169 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)))
2372 if len(note) not in range(2, 5):
24− raise ValueError("string of 2 to 4 characters expected, got %d (%s)" \
25− % (len(note), note))
26− notename, modifier, octave = [None]*3
73+ msg = "string of 2 to 4 characters expected, got {:d} ({:s})"
74+ raise ValueError(msg.format(len(note), note))
75+ notename, modifier, octave = [None] * 3
2776
2877 if len(note) == 4:
2978 notename, modifier, octave_sign, octave = note
@@ -46,21 +95,97 @@ def note2midi(note): · 16 unmodified lines
4695 if octave not in _valid_octaves:
4796 raise ValueError("%s is not a valid octave" % octave)
4897
49− midi = 12 + octave * 12 + _valid_notenames[notename] + _valid_modifiers[modifier]
98+ midi = (octave + 1) * 12 + _valid_notenames[notename] \
99+ + _valid_modifiers[modifier]
50100 if midi > 127:
51101 raise ValueError("%s is outside of the range C-2 to G8" % note)
52102 return midi
53103
104+
54105 def 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+ """
56136 if not isinstance(midi, int_instances):
57137 raise TypeError("an integer is required, got %s" % midi)
58138 if midi not in range(0, 128):
59− raise ValueError("an integer between 0 and 127 is excepted, got %d" % midi)
60− _valid_notenames = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B']
139+ msg = "an integer between 0 and 127 is excepted, got {:d}"
140+ raise ValueError(msg.format(midi))
141+ _valid_notenames = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#',
142+ 'A', 'A#', 'B']
61143 return _valid_notenames[midi % 12] + str(int(midi / 12) - 1)
62144
145+
63146 def freq2note(freq):
64− " convert frequency in Hz to nearest note name, e.g. [0, 22050.] -> [C-1, G9] "
65− from aubio import freqtomidi
66− return midi2note(int(freqtomidi(freq)))
147+ """Convert frequency in Hz to nearest note name.
148+
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+
170+def 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
55
66 _max_timestamp = 1e120
77
8+
89 def slice_source_at_stamps(source_file, timestamps, timestamps_end=None,
9− output_dir=None, samplerate=0, hopsize=256):
10− """ slice a sound file at given timestamps """
10+ output_dir=None, samplerate=0, hopsize=256,
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+ """
1175
12− if timestamps is None or len(timestamps) == 0:
76+ if not timestamps:
1377 raise ValueError("no timestamps given")
1478
15− if timestamps[0] != 0:
79+ if timestamps[0] != 0 and create_first:
1680 timestamps = [0] + timestamps
1781 if timestamps_end is not None:
1882 timestamps_end = [timestamps[1] - 1] + timestamps_end
1983
2084 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):
2288 raise ValueError("len(timestamps_end) != len(timestamps)")
2389 else:
2490 timestamps_end = [t - 1 for t in timestamps[1:]] + [_max_timestamp]
2591
2692 regions = list(zip(timestamps, timestamps_end))
27− #print regions
2893
2994 source_base_name, _ = os.path.splitext(os.path.basename(source_file))
3095 if output_dir is not None:
@@ -32,8 +97,8 @@ def slice_source_at_stamps(source_file, timestamps, timestamps_end=None, · 1 unmodified line
3297 os.makedirs(output_dir)
3398 source_base_name = os.path.join(output_dir, source_base_name)
3499
35− def new_sink_name(source_base_name, timestamp, samplerate):
36− """ create a sink based on a timestamp in samples, converted in seconds """
100+ def _new_sink_name(source_base_name, timestamp, samplerate):
101+ # create name based on a timestamp in samples, converted in seconds
37102 timestamp_seconds = timestamp / float(samplerate)
38103 return source_base_name + "_%011.6f" % timestamp_seconds + '.wav'
39104
@@ -48,16 +113,17 @@ def slice_source_at_stamps(source_file, timestamps, timestamps_end=None, · 8 unmodified lines
48113 # get hopsize new samples from source
49114 vec, read = _source.do_multi()
50115 # if the total number of frames read will exceed the next region start
51− if len(regions) and total_frames + read >= regions[0][0]:
52− #print "getting", regions[0], "at", total_frames
116+ while regions and total_frames + read >= regions[0][0]:
53117 # get next region
54118 start_stamp, end_stamp = regions.pop(0)
55119 # 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)
57122 # create its sink
58123 _sink = sink(new_sink_path, samplerate, _source.channels)
59124 # 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}
61127 # append the dictionary to the current list of slices
62128 slices.append(new_slice)
63129
@@ -69,18 +135,19 @@ def slice_source_at_stamps(source_file, timestamps, timestamps_end=None, · 5 unmodified lines
69135 start = max(start_stamp - total_frames, 0)
70136 # number of samples yet to written be until end of region
71137 remaining = end_stamp - total_frames + 1
72− #print current_slice, remaining, start
73138 # not enough frames remaining, time to split
74139 if remaining < read:
75140 if remaining > start:
76141 # write remaining samples from current region
77142 _sink.do_multi(vec[:, start:remaining], remaining - start)
78− #print "closing region", "remaining", remaining
79143 # close this file
80144 _sink.close()
81145 elif read > start:
82146 # write all the samples
83147 _sink.do_multi(vec[:, start:read], read - start)
84148 total_frames += read
149+ # remove old slices
150+ slices = list(filter(lambda s: s['end_stamp'] > total_frames,
151+ slices))
85152 if read < hopsize:
86153 break
python/lib/gen_code.py+65−23
@@ -2,6 +2,7 @@ aubiodefvalue = { · 1 unmodified line
22 # we have some clean up to do
33 'buf_size': 'Py_default_vector_length',
44 'win_s': 'Py_default_vector_length',
5+ 'size': 'Py_default_vector_length',
56 # and here too
67 'hop_size': 'Py_default_vector_length / 2',
78 'hop_s': 'Py_default_vector_length / 2',
@@ -84,6 +85,7 @@ objoutsize = { · 76 unmodified lines
8485 'filterbank': 'self->n_filters',
8586 'tss': 'self->buf_size',
8687 'pitchshift': 'self->hop_size',
88+ 'dct': 'self->size',
8789 }
8890
8991 objinputsize = {
@@ -179,6 +181,10 @@ class MappedObject(object): · 89 unmodified lines
179181 self.do_inputs = [get_params_types_names(self.do_proto)[1]]
180182 self.do_outputs = get_params_types_names(self.do_proto)[2:]
181183 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
182188 self.struct_outputs = ";\n ".join(struct_output_str)
183189
184190 #print ("input_params: ", map(split_type, get_input_params(self.do_proto)))
@@ -186,17 +192,26 @@ class MappedObject(object): · 1 unmodified line
186192
187193 def gen_code(self):
188194 out = ""
189− out += self.gen_struct()
190− out += self.gen_doc()
191− out += self.gen_new()
192− out += self.gen_init()
193− out += self.gen_del()
194− out += self.gen_do()
195− out += self.gen_memberdef()
196− out += self.gen_set()
197− out += self.gen_get()
198− out += self.gen_methodef()
199− out += self.gen_typeobject()
195+ try:
196+ out += self.gen_struct()
197+ out += self.gen_doc()
198+ out += self.gen_new()
199+ out += self.gen_init()
200+ out += self.gen_del()
201+ out += self.gen_do()
202+ if len(self.prototypes['rdo']):
203+ self.do_proto = self.prototypes['rdo'][0]
204+ self.do_inputs = [get_params_types_names(self.do_proto)[1]]
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
200215 return out
201216
202217 def gen_struct(self):
@@ -380,12 +395,12 @@ Py_{shortname}_del (Py_{shortname} * self, PyObject * unused) · 177 unmodified lines
380395 """.format(del_fn = del_fn)
381396 return out
382397
383− def gen_do(self):
398+ def gen_do(self, method = 'do'):
384399 out = """
385400 // do {shortname}
386401 static PyObject*
387−Py_{shortname}_do (Py_{shortname} * self, PyObject * args)
388−{{""".format(**self.__dict__)
402+Pyaubio_{shortname}_{method} (Py_{shortname} * self, PyObject * args)
403+{{""".format(method = method, **self.__dict__)
389404 input_params = self.do_inputs
390405 output_params = self.do_outputs
391406 #print input_params
@@ -461,30 +476,51 @@ Py_{shortname}_do (Py_{shortname} * self, PyObject * args) · 69 unmodified lines
461476 // {shortname} setters
462477 """.format(**self.__dict__)
463478 for set_param in self.prototypes['set']:
464− params = get_params_types_names(set_param)[1]
465− paramtype = params['type']
479+ params = get_params_types_names(set_param)[1:]
480+ param = self.shortname.split('_set_')[-1]
481+ paramdecls = "".join(["""
482+ {0} {1};""".format(p['type'], p['name']) for p in params])
466483 method_name = get_name(set_param)
467484 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])
469490 out += """
470491 static PyObject *
471492 Pyaubio_{shortname}_set_{param} (Py_{shortname} *self, PyObject *args)
472493 {{
473494 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 += """
475500
476− if (!PyArg_ParseTuple (args, "{pyparamtype}", &{param})) {{
501+ if (!PyArg_ParseTuple (args, "{pyparamtypes}", {refs})) {{
477502 return NULL;
478503 }}
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});
480508
481509 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+ }}
483518 return NULL;
484519 }}
485520 Py_RETURN_NONE;
486521 }}
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__)
488524 return out
489525
490526 def gen_get(self):
@@ -525,6 +561,12 @@ static PyMethodDef Py_{shortname}_methods[] = {{""".format(**self.__dict__) · 34 unmodified lines
525561 out += """
526562 {{"{shortname}", (PyCFunction) Py{name},
527563 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)
528570 out += """
529571 {NULL} /* sentinel */
530572 };
@@ -550,7 +592,7 @@ PyTypeObject Py_{shortname}Type = {{ · 19 unmodified lines
550592 0,
551593 0,
552594 0,
553− (ternaryfunc)Py_{shortname}_do,
595+ (ternaryfunc)Pyaubio_{shortname}_do,
554596 0,
555597 0,
556598 0,
python/lib/gen_external.py+180−89
@@ -1,5 +1,10 @@
11 import distutils.ccompiler
2−import sys, os, subprocess, glob
2+import sys
3+import os
4+import subprocess
5+import glob
6+from distutils.sysconfig import customize_compiler
7+from gen_code import MappedObject
38
49 header = os.path.join('src', 'aubio.h')
510 output_path = os.path.join('python', 'gen')
@@ -8,43 +13,44 @@ source_header = """// this file is generated! do not modify · 2 unmodified lines
813 #include "aubio-types.h"
914 """
1015
11−skip_objects = [
12− # already in ext/
13− 'fft',
14− 'pvoc',
15− 'filter',
16− 'filterbank',
17− #'resampler',
18− # AUBIO_UNSTABLE
19− 'hist',
20− 'parameter',
21− 'scale',
22− 'beattracking',
23− 'resampler',
24− 'peakpicker',
25− 'pitchfcomb',
26− 'pitchmcomb',
27− 'pitchschmitt',
28− 'pitchspecacf',
29− 'pitchyin',
30− 'pitchyinfft',
31− 'sink',
32− 'sink_apple_audio',
33− 'sink_sndfile',
34− 'sink_wavwrite',
35− #'mfcc',
36− 'source',
37− 'source_apple_audio',
38− 'source_sndfile',
39− 'source_avcodec',
40− 'source_wavread',
41− #'sampler',
42− 'audio_unit',
43− ]
16+default_skip_objects = [
17+ # already in ext/
18+ 'fft',
19+ 'pvoc',
20+ 'filter',
21+ 'filterbank',
22+ # AUBIO_UNSTABLE
23+ 'hist',
24+ 'parameter',
25+ 'scale',
26+ 'beattracking',
27+ 'resampler',
28+ 'peakpicker',
29+ 'pitchfcomb',
30+ 'pitchmcomb',
31+ 'pitchschmitt',
32+ 'pitchspecacf',
33+ 'pitchyin',
34+ 'pitchyinfft',
35+ 'pitchyinfast',
36+ 'sink',
37+ 'sink_apple_audio',
38+ 'sink_sndfile',
39+ 'sink_wavwrite',
40+ #'mfcc',
41+ 'source',
42+ 'source_apple_audio',
43+ 'source_sndfile',
44+ 'source_avcodec',
45+ 'source_wavread',
46+ #'sampler',
47+ 'audio_unit',
48+ 'spectral_whitening',
49+]
50+
4451
4552 def get_preprocessor():
4653 # findout which compiler to use
47− from distutils.sysconfig import customize_compiler
4854 compiler_name = distutils.ccompiler.get_default_compiler()
4955 compiler = distutils.ccompiler.new_compiler(compiler=compiler_name)
5056 try:
@@ -59,26 +65,50 @@ def get_preprocessor(): · 8 unmodified lines
5965 print("Warning: failed initializing compiler ({:s})".format(repr(e)))
6066
6167 cpp_cmd = None
62− if hasattr(compiler, 'preprocessor'): # for unixccompiler
68+ if hasattr(compiler, 'preprocessor'): # for unixccompiler
6369 cpp_cmd = compiler.preprocessor
64− elif hasattr(compiler, 'compiler'): # for ccompiler
70+ elif hasattr(compiler, 'compiler'): # for ccompiler
6571 cpp_cmd = compiler.compiler.split()
6672 cpp_cmd += ['-E']
67− elif hasattr(compiler, 'cc'): # for msvccompiler
73+ elif hasattr(compiler, 'cc'): # for msvccompiler
6874 cpp_cmd = compiler.cc.split()
6975 cpp_cmd += ['-E']
7076
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+
7189 if not cpp_cmd:
7290 print("Warning: could not guess preprocessor, using env's CC")
7391 cpp_cmd = os.environ.get('CC', 'cc').split()
7492 cpp_cmd += ['-E']
75−
93+ if 'emcc' in cpp_cmd:
94+ cpp_cmd += ['-x', 'c'] # emcc defaults to c++, force C language
7695 return cpp_cmd
7796
78−def get_cpp_objects(header=header):
97+
98+def 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+
105+def get_cpp_output(header=header, usedouble=False):
106+ ''' find and run a C pre-processor on aubio.h '''
79107 cpp_cmd = get_preprocessor()
80108
81109 macros = [('AUBIO_UNSTABLE', 1)]
110+ if usedouble:
111+ macros += [('HAVE_AUBIO_DOUBLE', 1)]
82112
83113 if not os.path.isfile(header):
84114 raise Exception("could not find include file " + header)
@@ -89,56 +119,115 @@ def get_cpp_objects(header=header): · 4 unmodified lines
89119
90120 print("Running command: {:s}".format(" ".join(cpp_cmd)))
91121 proc = subprocess.Popen(cpp_cmd,
92− stderr=subprocess.PIPE,
93− stdout=subprocess.PIPE)
122+ stderr=subprocess.PIPE,
123+ stdout=subprocess.PIPE)
94124 assert proc, 'Proc was none'
95125 cpp_output = proc.stdout.read()
96126 err_output = proc.stderr.read()
127+ if err_output:
128+ print("Warning: preprocessor produced errors or warnings:\n%s" \
129+ % err_output.decode('utf8'))
97130 if not cpp_output:
98− raise Exception("preprocessor output is empty:\n%s" % err_output)
99− elif err_output:
100− print ("Warning: preprocessor produced warnings:\n%s" % err_output)
131+ raise_msg = "preprocessor output is empty! Running command " \
132+ + "\"%s\" failed" % " ".join(cpp_cmd)
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)
101138 if not isinstance(cpp_output, list):
102139 cpp_output = [l.strip() for l in cpp_output.decode('utf8').split('\n')]
103140
104− cpp_output = filter(lambda y: len(y) > 1, cpp_output)
141+ return cpp_output
142+
143+def filter_cpp_output(cpp_raw_output):
144+ ''' prepare cpp-output for parsing '''
145+ cpp_output = filter(lambda y: len(y) > 1, cpp_raw_output)
105146 cpp_output = list(filter(lambda y: not y.startswith('#'), cpp_output))
106147
107148 i = 1
108149 while 1:
109− if i >= len(cpp_output): break
110− if cpp_output[i-1].endswith(',') or cpp_output[i-1].endswith('{') or cpp_output[i].startswith('}'):
111− cpp_output[i] = cpp_output[i-1] + ' ' + cpp_output[i]
112− cpp_output.pop(i-1)
150+ if i >= len(cpp_output):
151+ break
152+ if ('{' in cpp_output[i - 1]) and ('}' not in cpp_output[i - 1]) or (';' not in cpp_output[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)
113158 else:
114159 i += 1
115160
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
117166
167+ return cpp_output
168+
169+
170+def 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)
118174 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+
179+def 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))
119197
120− return cpp_output, cpp_objects
198+ return res
121199
122200
123−def analyze_cpp_output(cpp_objects, cpp_output):
201+def 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+ '''
124208 lib = {}
125209
126210 for o in cpp_objects:
127− if o[:6] != 'aubio_':
128− continue
129− shortname = o[6:-2]
130− if shortname in skip_objects:
131− continue
132− lib[shortname] = {'struct': [], 'new': [], 'del': [], 'do': [], 'get': [], 'set': [], 'other': []}
211+ shortname = o
212+ if o[:6] == 'aubio_':
213+ shortname = o[6:-2] # without aubio_ prefix and _t suffix
214+
215+ lib[shortname] = {'struct': [], 'new': [], 'del': [], 'do': [], 'rdo': [], 'get': [], 'set': [], 'other': []}
133216 lib[shortname]['longname'] = o
134217 lib[shortname]['shortname'] = shortname
135− for fn in cpp_output:
136− if o[:-1] in fn:
137− #print "found", o[:-1], "in", fn
218+
219+ fullshortname = o[:-2] # name without _t suffix
220+
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
138225 if 'typedef struct ' in fn:
139226 lib[shortname]['struct'].append(fn)
140227 elif '_do' in fn:
141228 lib[shortname]['do'].append(fn)
229+ elif '_rdo' in fn:
230+ lib[shortname]['rdo'].append(fn)
142231 elif 'new_' in fn:
143232 lib[shortname]['new'].append(fn)
144233 elif 'del_' in fn:
@@ -148,52 +237,52 @@ def analyze_cpp_output(cpp_objects, cpp_output): · 3 unmodified lines
148237 elif '_set_' in fn:
149238 lib[shortname]['set'].append(fn)
150239 else:
151− #print "no idea what to do about", fn
240+ # print "no idea what to do about", fn
152241 lib[shortname]['other'].append(fn)
153242 return lib
154243
155−def print_cpp_output_results(lib, cpp_output):
156− for fn in cpp_output:
244+
245+def print_c_declarations_results(lib, c_declarations):
246+ for fn in c_declarations:
157247 found = 0
158248 for o in lib:
159249 for family in lib[o]:
160250 if fn in lib[o][family]:
161251 found = 1
162252 if found == 0:
163− print ("missing", fn)
253+ print("missing", fn)
164254
165255 for o in lib:
166256 for family in lib[o]:
167257 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]))
169259 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]))
171261 else:
172− print ( "{:15s} {:10s} {:s}".format(o, family, lib[o][family] ) )
262+ print("{:15s} {:10s} {:s}".format(o, family, lib[o][family]))
173263
174264
175265 def generate_external(header=header, output_path=output_path, usedouble=False, overwrite=True):
176− if not os.path.isdir(output_path): os.mkdir(output_path)
177− elif not overwrite: return glob.glob(os.path.join(output_path, '*.c'))
266+ if not os.path.isdir(output_path):
267+ os.mkdir(output_path)
268+ elif not overwrite:
269+ return sorted(glob.glob(os.path.join(output_path, '*.c')))
178270
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)
180273
181− lib = analyze_cpp_output(cpp_objects, cpp_output)
182− # print_cpp_output_results(lib, cpp_output)
274+ lib = generate_lib_from_c_declarations(cpp_objects, c_declarations)
275+ # print_c_declarations_results(lib, c_declarations)
183276
184277 sources_list = []
185− try:
186− from .gen_code import MappedObject
187− except (SystemError, ValueError):
188− from gen_code import MappedObject
189278 for o in lib:
190279 out = source_header
191− mapped = MappedObject(lib[o], usedouble = usedouble)
280+ mapped = MappedObject(lib[o], usedouble=usedouble)
192281 out += mapped.gen_code()
193282 output_file = os.path.join(output_path, 'gen-%s.c' % o)
194283 with open(output_file, 'w') as f:
195284 f.write(out)
196− print ("wrote %s" % output_file )
285+ print("wrote %s" % output_file)
197286 sources_list.append(output_file)
198287
199288 out = source_header
@@ -205,23 +294,23 @@ int generated_types_ready (void) · 5 unmodified lines
205294 {{
206295 return ({pycheck_types});
207296 }}
208−""".format(pycheck_types = check_types)
297+""".format(pycheck_types=check_types)
209298
210299 add_types = "".join(["""
211300 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])
213302 out += """
214303
215304 void add_generated_objects ( PyObject *m )
216305 {{
217306 {add_types}
218307 }}
219−""".format(add_types = add_types)
308+""".format(add_types=add_types)
220309
221310 output_file = os.path.join(output_path, 'aubio-generated.c')
222311 with open(output_file, 'w') as f:
223312 f.write(out)
224− print ("wrote %s" % output_file )
313+ print("wrote %s" % output_file)
225314 sources_list.append(output_file)
226315
227316 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
239328 {objlist}
240329 int generated_objects ( void );
241330 void add_generated_objects( PyObject *m );
242−""".format(objlist = objlist)
331+""".format(objlist=objlist)
243332
244333 output_file = os.path.join(output_path, 'aubio-generated.h')
245334 with open(output_file, 'w') as f:
246335 f.write(out)
247− print ("wrote %s" % output_file )
336+ print("wrote %s" % output_file)
248337 # no need to add header to list of sources
249338
250− return sources_list
339+ return sorted(sources_list)
251340
252341 if __name__ == '__main__':
253− if len(sys.argv) > 1: header = sys.argv[1]
254− if len(sys.argv) > 2: output_path = sys.argv[2]
342+ if len(sys.argv) > 1:
343+ header = sys.argv[1]
344+ if len(sys.argv) > 2:
345+ output_path = sys.argv[2]
255346 generate_external(header, output_path)
python/lib/moresetuptools.py+78−26
@@ -2,7 +2,9 @@ · 1 unmodified line
22 #
33 import sys, os, glob, subprocess
44 import distutils, distutils.command.clean, distutils.dir_util
5−from .gen_external import generate_external, header, output_path
5+from gen_external import generate_external, header, output_path
6+
7+from this_version import get_aubio_version
68
79 # inspired from https://gist.github.com/abergmeier/9488990
810 def add_packages(packages, ext=None, **kw):
@@ -21,6 +23,7 @@ def add_packages(packages, ext=None, **kw): · 12 unmodified lines
2123 }
2224
2325 for package in packages:
26+ print("checking for {:s}".format(package))
2427 cmd = ['pkg-config', '--libs', '--cflags', package]
2528 try:
2629 tokens = subprocess.check_output(cmd)
@@ -54,13 +57,14 @@ def add_local_aubio_lib(ext): · 27 unmodified lines
5457
5558 def add_local_aubio_sources(ext):
5659 """ build aubio inside python module instead of linking against libaubio """
57− print("Warning: libaubio was not built with waf, adding src/")
58− # create an empty header, macros will be passed on the command line
59− fake_config_header = os.path.join('python', 'ext', 'config.h')
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'))
60+ print("Info: libaubio was not installed or built locally with waf, adding src/")
61+ aubio_sources = sorted(glob.glob(os.path.join('src', '**.c')))
62+ aubio_sources += sorted(glob.glob(os.path.join('src', '*', '**.c')))
6363 ext.sources += aubio_sources
64+
65+def add_local_macros(ext, usedouble = False):
66+ if usedouble:
67+ ext.define_macros += [('HAVE_AUBIO_DOUBLE', 1)]
6468 # define macros (waf puts them in build/src/config.h)
6569 for define_macro in ['HAVE_STDLIB_H', 'HAVE_STDIO_H',
6670 'HAVE_MATH_H', 'HAVE_STRING_H',
@@ -69,22 +73,31 @@ def add_local_aubio_sources(ext): · 2 unmodified lines
6973 'HAVE_MEMCPY_HACKS']:
7074 ext.define_macros += [(define_macro, 1)]
7175
76+def add_external_deps(ext, usedouble = False):
7277 # loof for additional packages
7378 print("Info: looking for *optional* additional packages")
74− packages = ['libavcodec', 'libavformat', 'libavutil', 'libavresample',
79+ packages = ['libavcodec', 'libavformat', 'libavutil',
80+ 'libswresample', 'libavresample',
7581 'jack',
76− 'sndfile', 'samplerate',
82+ 'sndfile',
7783 'rubberband',
7884 #'fftw3f',
7985 ]
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")
8091 add_packages(packages, ext=ext)
8192 if 'avcodec' in ext.libraries \
8293 and 'avformat' in ext.libraries \
83− and 'avutil' in ext.libraries \
84− and 'avresample' in ext.libraries:
85− ext.define_macros += [('HAVE_LIBAV', 1)]
86− if 'jack' in ext.libraries:
87− ext.define_macros += [('HAVE_JACK', 1)]
94+ and 'avutil' in ext.libraries:
95+ if 'swresample' in ext.libraries:
96+ ext.define_macros += [('HAVE_SWRESAMPLE', 1)]
97+ elif 'avresample' in ext.libraries:
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)]
88101 if 'sndfile' in ext.libraries:
89102 ext.define_macros += [('HAVE_SNDFILE', 1)]
90103 if 'samplerate' in ext.libraries:
@@ -107,39 +120,78 @@ def add_local_aubio_sources(ext): · 16 unmodified lines
107120
108121 ext.define_macros += [('HAVE_WAVWRITE', 1)]
109122 ext.define_macros += [('HAVE_WAVREAD', 1)]
110− # TODO:
111− # add cblas
123+
124+ # TODO: add cblas
112125 if 0:
113126 ext.libraries += ['cblas']
114127 ext.define_macros += [('HAVE_ATLAS_CBLAS_H', 1)]
115128
116129 def add_system_aubio(ext):
117130 # 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)
119133 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+
138+def 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)]
121143
122144 class CleanGenerated(distutils.command.clean.clean):
123145 def run(self):
124− distutils.dir_util.remove_tree(output_path)
125− distutils.command.clean.clean.run(self)
146+ if os.path.isdir(output_path):
147+ distutils.dir_util.remove_tree(output_path)
148+
149+from distutils.command.build_ext import build_ext as _build_ext
150+class build_ext(_build_ext):
126151
127−class GenerateCommand(distutils.cmd.Command):
128− description = 'generate gen/gen-*.c files from ../src/aubio.h'
129− user_options = [
152+ user_options = _build_ext.user_options + [
130153 # The format is (long option, short option, description).
131154 ('enable-double', None, 'use HAVE_AUBIO_DOUBLE=1 (default: 0)'),
132155 ]
133156
134157 def initialize_options(self):
158+ _build_ext.initialize_options(self)
135159 self.enable_double = False
136160
137161 def finalize_options(self):
162+ _build_ext.finalize_options(self)
138163 if self.enable_double:
139164 self.announce(
140165 'will generate code for aubio compiled with HAVE_AUBIO_DOUBLE=1',
141166 level=distutils.log.INFO)
142167
143− def run(self):
144− self.announce( 'Generating code', level=distutils.log.INFO)
145− generated_object_files = generate_external(header, output_path, usedouble=self.enable_double)
168+ def build_extension(self, extension):
169+ if self.enable_double or 'HAVE_AUBIO_DOUBLE' in os.environ:
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