Showing the changes to python/lib (9 of 230 files). Show all
Unified · Split
Empty file deleted.
| @@ -1,18 +1,85 @@ | ||
| 1 | 1 | #! /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 | +""" | |
| 2 | 24 | |
| 3 | 25 | import numpy |
| 4 | −from ._aubio import * | |
| 26 | +from ._aubio import __version__ as version | |
| 5 | 27 | from ._aubio import float_type |
| 28 | +from ._aubio import * | |
| 6 | 29 | from .midiconv import * |
| 7 | 30 | from .slicing import * |
| 8 | 31 | |
| 32 | + | |
| 9 | 33 | 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')) | |
| 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 | 75 | if isinstance(input_arg, int): |
| 14 | 76 | if input_arg == 0: |
| 15 | 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 | 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 | |
| @@ -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) | |
| @@ -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) | |
| @@ -1,9 +1,11 @@ | ||
| 1 | 1 | # -*- coding: utf-8 -*- |
| 2 | 2 | """ utilities to convert midi note number to and from note names """ |
| 3 | 3 | |
| 4 | −__all__ = ['note2midi', 'midi2note', 'freq2note'] | |
| 5 | − | |
| 6 | 4 | import sys |
| 5 | +from ._aubio import freqtomidi, miditofreq | |
| 6 | + | |
| 7 | +__all__ = ['note2midi', 'midi2note', 'freq2note', 'note2freq'] | |
| 8 | + | |
| 7 | 9 | py3 = sys.version_info[0] == 3 |
| 8 | 10 | if py3: |
| 9 | 11 | str_instances = str |
| @@ -12,18 +14,65 @@ else: · 2 unmodified lines | ||
| 12 | 14 | str_instances = (str, unicode) |
| 13 | 15 | int_instances = (int, long) |
| 14 | 16 | |
| 17 | + | |
| 15 | 18 | 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 | + } | |
| 20 | 68 | _valid_octaves = range(-1, 10) |
| 21 | 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 | 72 | 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 | |
| 27 | 76 | |
| 28 | 77 | if len(note) == 4: |
| 29 | 78 | notename, modifier, octave_sign, octave = note |
| @@ -46,21 +95,97 @@ def note2midi(note): · 16 unmodified lines | ||
| 46 | 95 | if octave not in _valid_octaves: |
| 47 | 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 | 100 | if midi > 127: |
| 51 | 101 | raise ValueError("%s is outside of the range C-2 to G8" % note) |
| 52 | 102 | return midi |
| 53 | 103 | |
| 104 | + | |
| 54 | 105 | 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 | + """ | |
| 56 | 136 | if not isinstance(midi, int_instances): |
| 57 | 137 | raise TypeError("an integer is required, got %s" % midi) |
| 58 | 138 | 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'] | |
| 61 | 143 | return _valid_notenames[midi % 12] + str(int(midi / 12) - 1) |
| 62 | 144 | |
| 145 | + | |
| 63 | 146 | 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) | |
| @@ -5,26 +5,91 @@ from aubio import source, sink · 4 unmodified lines | ||
| 5 | 5 | |
| 6 | 6 | _max_timestamp = 1e120 |
| 7 | 7 | |
| 8 | + | |
| 8 | 9 | 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 | + """ | |
| 11 | 75 | |
| 12 | − if timestamps is None or len(timestamps) == 0: | |
| 76 | + if not timestamps: | |
| 13 | 77 | raise ValueError("no timestamps given") |
| 14 | 78 | |
| 15 | − if timestamps[0] != 0: | |
| 79 | + if timestamps[0] != 0 and create_first: | |
| 16 | 80 | timestamps = [0] + timestamps |
| 17 | 81 | if timestamps_end is not None: |
| 18 | 82 | timestamps_end = [timestamps[1] - 1] + timestamps_end |
| 19 | 83 | |
| 20 | 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 | 88 | raise ValueError("len(timestamps_end) != len(timestamps)") |
| 23 | 89 | else: |
| 24 | 90 | timestamps_end = [t - 1 for t in timestamps[1:]] + [_max_timestamp] |
| 25 | 91 | |
| 26 | 92 | regions = list(zip(timestamps, timestamps_end)) |
| 27 | − #print regions | |
| 28 | 93 | |
| 29 | 94 | source_base_name, _ = os.path.splitext(os.path.basename(source_file)) |
| 30 | 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 | 97 | os.makedirs(output_dir) |
| 33 | 98 | source_base_name = os.path.join(output_dir, source_base_name) |
| 34 | 99 | |
| 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 | |
| 37 | 102 | timestamp_seconds = timestamp / float(samplerate) |
| 38 | 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 | 113 | # get hopsize new samples from source |
| 49 | 114 | vec, read = _source.do_multi() |
| 50 | 115 | # 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]: | |
| 53 | 117 | # get next region |
| 54 | 118 | start_stamp, end_stamp = regions.pop(0) |
| 55 | 119 | # 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 | 122 | # create its sink |
| 58 | 123 | _sink = sink(new_sink_path, samplerate, _source.channels) |
| 59 | 124 | # 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 | 127 | # append the dictionary to the current list of slices |
| 62 | 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 | 135 | start = max(start_stamp - total_frames, 0) |
| 70 | 136 | # number of samples yet to written be until end of region |
| 71 | 137 | remaining = end_stamp - total_frames + 1 |
| 72 | − #print current_slice, remaining, start | |
| 73 | 138 | # not enough frames remaining, time to split |
| 74 | 139 | if remaining < read: |
| 75 | 140 | if remaining > start: |
| 76 | 141 | # write remaining samples from current region |
| 77 | 142 | _sink.do_multi(vec[:, start:remaining], remaining - start) |
| 78 | − #print "closing region", "remaining", remaining | |
| 79 | 143 | # close this file |
| 80 | 144 | _sink.close() |
| 81 | 145 | elif read > start: |
| 82 | 146 | # write all the samples |
| 83 | 147 | _sink.do_multi(vec[:, start:read], read - start) |
| 84 | 148 | total_frames += read |
| 149 | + # remove old slices | |
| 150 | + slices = list(filter(lambda s: s['end_stamp'] > total_frames, | |
| 151 | + slices)) | |
| 85 | 152 | if read < hopsize: |
| 86 | 153 | break |
| @@ -2,6 +2,7 @@ aubiodefvalue = { · 1 unmodified line | ||
| 2 | 2 | # we have some clean up to do |
| 3 | 3 | 'buf_size': 'Py_default_vector_length', |
| 4 | 4 | 'win_s': 'Py_default_vector_length', |
| 5 | + 'size': 'Py_default_vector_length', | |
| 5 | 6 | # and here too |
| 6 | 7 | 'hop_size': 'Py_default_vector_length / 2', |
| 7 | 8 | 'hop_s': 'Py_default_vector_length / 2', |
| @@ -84,6 +85,7 @@ objoutsize = { · 76 unmodified lines | ||
| 84 | 85 | 'filterbank': 'self->n_filters', |
| 85 | 86 | 'tss': 'self->buf_size', |
| 86 | 87 | 'pitchshift': 'self->hop_size', |
| 88 | + 'dct': 'self->size', | |
| 87 | 89 | } |
| 88 | 90 | |
| 89 | 91 | objinputsize = { |
| @@ -179,6 +181,10 @@ class MappedObject(object): · 89 unmodified lines | ||
| 179 | 181 | self.do_inputs = [get_params_types_names(self.do_proto)[1]] |
| 180 | 182 | self.do_outputs = get_params_types_names(self.do_proto)[2:] |
| 181 | 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 | 188 | self.struct_outputs = ";\n ".join(struct_output_str) |
| 183 | 189 | |
| 184 | 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 | 193 | def gen_code(self): |
| 188 | 194 | 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 | |
| 200 | 215 | return out |
| 201 | 216 | |
| 202 | 217 | def gen_struct(self): |
| @@ -380,12 +395,12 @@ Py_{shortname}_del (Py_{shortname} * self, PyObject * unused) · 177 unmodified lines | ||
| 380 | 395 | """.format(del_fn = del_fn) |
| 381 | 396 | return out |
| 382 | 397 | |
| 383 | − def gen_do(self): | |
| 398 | + def gen_do(self, method = 'do'): | |
| 384 | 399 | out = """ |
| 385 | 400 | // do {shortname} |
| 386 | 401 | 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__) | |
| 389 | 404 | input_params = self.do_inputs |
| 390 | 405 | output_params = self.do_outputs |
| 391 | 406 | #print input_params |
| @@ -461,30 +476,51 @@ Py_{shortname}_do (Py_{shortname} * self, PyObject * args) · 69 unmodified lines | ||
| 461 | 476 | // {shortname} setters |
| 462 | 477 | """.format(**self.__dict__) |
| 463 | 478 | 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]) | |
| 466 | 483 | method_name = get_name(set_param) |
| 467 | 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 | 490 | out += """ |
| 470 | 491 | static PyObject * |
| 471 | 492 | Pyaubio_{shortname}_set_{param} (Py_{shortname} *self, PyObject *args) |
| 472 | 493 | {{ |
| 473 | 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 | 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 | 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 | 518 | return NULL; |
| 484 | 519 | }} |
| 485 | 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 | 524 | return out |
| 489 | 525 | |
| 490 | 526 | def gen_get(self): |
| @@ -525,6 +561,12 @@ static PyMethodDef Py_{shortname}_methods[] = {{""".format(**self.__dict__) · 34 unmodified lines | ||
| 525 | 561 | out += """ |
| 526 | 562 | {{"{shortname}", (PyCFunction) Py{name}, |
| 527 | 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 | 570 | out += """ |
| 529 | 571 | {NULL} /* sentinel */ |
| 530 | 572 | }; |
| @@ -550,7 +592,7 @@ PyTypeObject Py_{shortname}Type = {{ · 19 unmodified lines | ||
| 550 | 592 | 0, |
| 551 | 593 | 0, |
| 552 | 594 | 0, |
| 553 | − (ternaryfunc)Py_{shortname}_do, | |
| 595 | + (ternaryfunc)Pyaubio_{shortname}_do, | |
| 554 | 596 | 0, |
| 555 | 597 | 0, |
| 556 | 598 | 0, |
| @@ -1,5 +1,10 @@ | ||
| 1 | 1 | 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 | |
| 3 | 8 | |
| 4 | 9 | header = os.path.join('src', 'aubio.h') |
| 5 | 10 | output_path = os.path.join('python', 'gen') |
| @@ -8,43 +13,44 @@ source_header = """// this file is generated! do not modify · 2 unmodified lines | ||
| 8 | 13 | #include "aubio-types.h" |
| 9 | 14 | """ |
| 10 | 15 | |
| 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 | + | |
| 44 | 51 | |
| 45 | 52 | def get_preprocessor(): |
| 46 | 53 | # findout which compiler to use |
| 47 | − from distutils.sysconfig import customize_compiler | |
| 48 | 54 | compiler_name = distutils.ccompiler.get_default_compiler() |
| 49 | 55 | compiler = distutils.ccompiler.new_compiler(compiler=compiler_name) |
| 50 | 56 | try: |
| @@ -59,26 +65,50 @@ def get_preprocessor(): · 8 unmodified lines | ||
| 59 | 65 | print("Warning: failed initializing compiler ({:s})".format(repr(e))) |
| 60 | 66 | |
| 61 | 67 | cpp_cmd = None |
| 62 | − if hasattr(compiler, 'preprocessor'): # for unixccompiler | |
| 68 | + if hasattr(compiler, 'preprocessor'): # for unixccompiler | |
| 63 | 69 | cpp_cmd = compiler.preprocessor |
| 64 | − elif hasattr(compiler, 'compiler'): # for ccompiler | |
| 70 | + elif hasattr(compiler, 'compiler'): # for ccompiler | |
| 65 | 71 | cpp_cmd = compiler.compiler.split() |
| 66 | 72 | cpp_cmd += ['-E'] |
| 67 | − elif hasattr(compiler, 'cc'): # for msvccompiler | |
| 73 | + elif hasattr(compiler, 'cc'): # for msvccompiler | |
| 68 | 74 | cpp_cmd = compiler.cc.split() |
| 69 | 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 | 89 | if not cpp_cmd: |
| 72 | 90 | print("Warning: could not guess preprocessor, using env's CC") |
| 73 | 91 | cpp_cmd = os.environ.get('CC', 'cc').split() |
| 74 | 92 | cpp_cmd += ['-E'] |
| 75 | − | |
| 93 | + if 'emcc' in cpp_cmd: | |
| 94 | + cpp_cmd += ['-x', 'c'] # emcc defaults to c++, force C language | |
| 76 | 95 | return cpp_cmd |
| 77 | 96 | |
| 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 ''' | |
| 79 | 107 | cpp_cmd = get_preprocessor() |
| 80 | 108 | |
| 81 | 109 | macros = [('AUBIO_UNSTABLE', 1)] |
| 110 | + if usedouble: | |
| 111 | + macros += [('HAVE_AUBIO_DOUBLE', 1)] | |
| 82 | 112 | |
| 83 | 113 | if not os.path.isfile(header): |
| 84 | 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 | 120 | print("Running command: {:s}".format(" ".join(cpp_cmd))) |
| 91 | 121 | proc = subprocess.Popen(cpp_cmd, |
| 92 | − stderr=subprocess.PIPE, | |
| 93 | − stdout=subprocess.PIPE) | |
| 122 | + stderr=subprocess.PIPE, | |
| 123 | + stdout=subprocess.PIPE) | |
| 94 | 124 | assert proc, 'Proc was none' |
| 95 | 125 | cpp_output = proc.stdout.read() |
| 96 | 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 | 130 | 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) | |
| 101 | 138 | if not isinstance(cpp_output, list): |
| 102 | 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 | + | |
| 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) | |
| 105 | 146 | cpp_output = list(filter(lambda y: not y.startswith('#'), cpp_output)) |
| 106 | 147 | |
| 107 | 148 | i = 1 |
| 108 | 149 | 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) | |
| 113 | 158 | else: |
| 114 | 159 | 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 | + | |
| 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) | |
| 118 | 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 | + | |
| 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)) | |
| 119 | 197 | |
| 120 | − return cpp_output, cpp_objects | |
| 198 | + return res | |
| 121 | 199 | |
| 122 | 200 | |
| 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 | + ''' | |
| 124 | 208 | lib = {} |
| 125 | 209 | |
| 126 | 210 | 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': []} | |
| 133 | 216 | lib[shortname]['longname'] = o |
| 134 | 217 | 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 | |
| 138 | 225 | if 'typedef struct ' in fn: |
| 139 | 226 | lib[shortname]['struct'].append(fn) |
| 140 | 227 | elif '_do' in fn: |
| 141 | 228 | lib[shortname]['do'].append(fn) |
| 229 | + elif '_rdo' in fn: | |
| 230 | + lib[shortname]['rdo'].append(fn) | |
| 142 | 231 | elif 'new_' in fn: |
| 143 | 232 | lib[shortname]['new'].append(fn) |
| 144 | 233 | elif 'del_' in fn: |
| @@ -148,52 +237,52 @@ def analyze_cpp_output(cpp_objects, cpp_output): · 3 unmodified lines | ||
| 148 | 237 | elif '_set_' in fn: |
| 149 | 238 | lib[shortname]['set'].append(fn) |
| 150 | 239 | else: |
| 151 | − #print "no idea what to do about", fn | |
| 240 | + # print "no idea what to do about", fn | |
| 152 | 241 | lib[shortname]['other'].append(fn) |
| 153 | 242 | return lib |
| 154 | 243 | |
| 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: | |
| 157 | 247 | found = 0 |
| 158 | 248 | for o in lib: |
| 159 | 249 | for family in lib[o]: |
| 160 | 250 | if fn in lib[o][family]: |
| 161 | 251 | found = 1 |
| 162 | 252 | if found == 0: |
| 163 | − print ("missing", fn) | |
| 253 | + print("missing", fn) | |
| 164 | 254 | |
| 165 | 255 | for o in lib: |
| 166 | 256 | for family in lib[o]: |
| 167 | 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 | 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 | 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 | |
| 175 | 265 | 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'))) | |
| 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) | |
| 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) | |
| 183 | 276 | |
| 184 | 277 | sources_list = [] |
| 185 | − try: | |
| 186 | − from .gen_code import MappedObject | |
| 187 | − except (SystemError, ValueError): | |
| 188 | − from gen_code import MappedObject | |
| 189 | 278 | for o in lib: |
| 190 | 279 | out = source_header |
| 191 | − mapped = MappedObject(lib[o], usedouble = usedouble) | |
| 280 | + mapped = MappedObject(lib[o], usedouble=usedouble) | |
| 192 | 281 | out += mapped.gen_code() |
| 193 | 282 | output_file = os.path.join(output_path, 'gen-%s.c' % o) |
| 194 | 283 | with open(output_file, 'w') as f: |
| 195 | 284 | f.write(out) |
| 196 | − print ("wrote %s" % output_file ) | |
| 285 | + print("wrote %s" % output_file) | |
| 197 | 286 | sources_list.append(output_file) |
| 198 | 287 | |
| 199 | 288 | out = source_header |
| @@ -205,23 +294,23 @@ int generated_types_ready (void) · 5 unmodified lines | ||
| 205 | 294 | {{ |
| 206 | 295 | return ({pycheck_types}); |
| 207 | 296 | }} |
| 208 | −""".format(pycheck_types = check_types) | |
| 297 | +""".format(pycheck_types=check_types) | |
| 209 | 298 | |
| 210 | 299 | add_types = "".join([""" |
| 211 | 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 | 302 | out += """ |
| 214 | 303 | |
| 215 | 304 | void add_generated_objects ( PyObject *m ) |
| 216 | 305 | {{ |
| 217 | 306 | {add_types} |
| 218 | 307 | }} |
| 219 | −""".format(add_types = add_types) | |
| 308 | +""".format(add_types=add_types) | |
| 220 | 309 | |
| 221 | 310 | output_file = os.path.join(output_path, 'aubio-generated.c') |
| 222 | 311 | with open(output_file, 'w') as f: |
| 223 | 312 | f.write(out) |
| 224 | − print ("wrote %s" % output_file ) | |
| 313 | + print("wrote %s" % output_file) | |
| 225 | 314 | sources_list.append(output_file) |
| 226 | 315 | |
| 227 | 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 | 328 | {objlist} |
| 240 | 329 | int generated_objects ( void ); |
| 241 | 330 | void add_generated_objects( PyObject *m ); |
| 242 | −""".format(objlist = objlist) | |
| 331 | +""".format(objlist=objlist) | |
| 243 | 332 | |
| 244 | 333 | output_file = os.path.join(output_path, 'aubio-generated.h') |
| 245 | 334 | with open(output_file, 'w') as f: |
| 246 | 335 | f.write(out) |
| 247 | − print ("wrote %s" % output_file ) | |
| 336 | + print("wrote %s" % output_file) | |
| 248 | 337 | # no need to add header to list of sources |
| 249 | 338 | |
| 250 | − return sources_list | |
| 339 | + return sorted(sources_list) | |
| 251 | 340 | |
| 252 | 341 | 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] | |
| 255 | 346 | generate_external(header, output_path) |
| @@ -2,7 +2,9 @@ · 1 unmodified line | ||
| 2 | 2 | # |
| 3 | 3 | import sys, os, glob, subprocess |
| 4 | 4 | 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 | |
| 6 | 8 | |
| 7 | 9 | # inspired from https://gist.github.com/abergmeier/9488990 |
| 8 | 10 | def 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 | 25 | for package in packages: |
| 26 | + print("checking for {:s}".format(package)) | |
| 24 | 27 | cmd = ['pkg-config', '--libs', '--cflags', package] |
| 25 | 28 | try: |
| 26 | 29 | tokens = subprocess.check_output(cmd) |
| @@ -54,13 +57,14 @@ def add_local_aubio_lib(ext): · 27 unmodified lines | ||
| 54 | 57 | |
| 55 | 58 | def add_local_aubio_sources(ext): |
| 56 | 59 | """ 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'))) | |
| 63 | 63 | ext.sources += aubio_sources |
| 64 | + | |
| 65 | +def add_local_macros(ext, usedouble = False): | |
| 66 | + if usedouble: | |
| 67 | + ext.define_macros += [('HAVE_AUBIO_DOUBLE', 1)] | |
| 64 | 68 | # define macros (waf puts them in build/src/config.h) |
| 65 | 69 | for define_macro in ['HAVE_STDLIB_H', 'HAVE_STDIO_H', |
| 66 | 70 | 'HAVE_MATH_H', 'HAVE_STRING_H', |
| @@ -69,22 +73,31 @@ def add_local_aubio_sources(ext): · 2 unmodified lines | ||
| 69 | 73 | 'HAVE_MEMCPY_HACKS']: |
| 70 | 74 | ext.define_macros += [(define_macro, 1)] |
| 71 | 75 | |
| 76 | +def add_external_deps(ext, usedouble = False): | |
| 72 | 77 | # loof for additional packages |
| 73 | 78 | print("Info: looking for *optional* additional packages") |
| 74 | − packages = ['libavcodec', 'libavformat', 'libavutil', 'libavresample', | |
| 79 | + packages = ['libavcodec', 'libavformat', 'libavutil', | |
| 80 | + 'libswresample', 'libavresample', | |
| 75 | 81 | 'jack', |
| 76 | − 'sndfile', 'samplerate', | |
| 82 | + 'sndfile', | |
| 77 | 83 | 'rubberband', |
| 78 | 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 | 91 | add_packages(packages, ext=ext) |
| 81 | 92 | if 'avcodec' in ext.libraries \ |
| 82 | 93 | 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)] | |
| 88 | 101 | if 'sndfile' in ext.libraries: |
| 89 | 102 | ext.define_macros += [('HAVE_SNDFILE', 1)] |
| 90 | 103 | if 'samplerate' in ext.libraries: |
| @@ -107,39 +120,78 @@ def add_local_aubio_sources(ext): · 16 unmodified lines | ||
| 107 | 120 | |
| 108 | 121 | ext.define_macros += [('HAVE_WAVWRITE', 1)] |
| 109 | 122 | ext.define_macros += [('HAVE_WAVREAD', 1)] |
| 110 | − # TODO: | |
| 111 | − # add cblas | |
| 123 | + | |
| 124 | + # TODO: add cblas | |
| 112 | 125 | if 0: |
| 113 | 126 | ext.libraries += ['cblas'] |
| 114 | 127 | ext.define_macros += [('HAVE_ATLAS_CBLAS_H', 1)] |
| 115 | 128 | |
| 116 | 129 | def add_system_aubio(ext): |
| 117 | 130 | # 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 | 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 | + | |
| 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)] | |
| 121 | 143 | |
| 122 | 144 | class CleanGenerated(distutils.command.clean.clean): |
| 123 | 145 | 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): | |
| 126 | 151 | |
| 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 + [ | |
| 130 | 153 | # The format is (long option, short option, description). |
| 131 | 154 | ('enable-double', None, 'use HAVE_AUBIO_DOUBLE=1 (default: 0)'), |
| 132 | 155 | ] |
| 133 | 156 | |
| 134 | 157 | def initialize_options(self): |
| 158 | + _build_ext.initialize_options(self) | |
| 135 | 159 | self.enable_double = False |
| 136 | 160 | |
| 137 | 161 | def finalize_options(self): |
| 162 | + _build_ext.finalize_options(self) | |
| 138 | 163 | if self.enable_double: |
| 139 | 164 | self.announce( |
| 140 | 165 | 'will generate code for aubio compiled with HAVE_AUBIO_DOUBLE=1', |
| 141 | 166 | level=distutils.log.INFO) |
| 142 | 167 | |
| 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) | |