Showing the changes to python/lib (9 of 230 files). Show all
Unified · Split
Empty file deleted.
| @@ -1,18 +1,85 @@ | |||
| 1 | #! /usr/bin/env python | 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 | import numpy | 25 | import numpy |
| 4 | from ._aubio import * | 26 | from ._aubio import __version__ as version |
| 5 | from ._aubio import float_type | 27 | from ._aubio import float_type |
| 28 | from ._aubio import * | ||
| 6 | from .midiconv import * | 29 | from .midiconv import * |
| 7 | from .slicing import * | 30 | from .slicing import * |
| 8 | 31 | ||
| 32 | |||
| 9 | class fvec(numpy.ndarray): | 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 | if isinstance(input_arg, int): | 75 | if isinstance(input_arg, int): |
| 14 | if input_arg == 0: | 76 | if input_arg == 0: |
| 15 | raise ValueError("vector length of 1 or more expected") | 77 | raise ValueError("vector length of 1 or more expected") |
| 16 | return numpy.zeros(input_arg, dtype=float_type, **kwargs) | 78 | return numpy.zeros(input_arg, dtype=float_type, order='C') |
| 17 | else: | 79 | else: |
| 18 | return numpy.array(input_arg, dtype=float_type, **kwargs) | 80 | np_input = numpy.array(input_arg, dtype=float_type, order='C') |
| 81 | if len(np_input.shape) != 1: | ||
| 82 | raise ValueError("input_arg should have shape (n,)") | ||
| 83 | if np_input.shape[0] == 0: | ||
| 84 | raise ValueError("vector length of 1 or more expected") | ||
| 85 | return np_input | ||
| @@ -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 | # -*- coding: utf-8 -*- | 1 | # -*- coding: utf-8 -*- |
| 2 | """ utilities to convert midi note number to and from note names """ | 2 | """ utilities to convert midi note number to and from note names """ |
| 3 | 3 | ||
| 4 | __all__ = ['note2midi', 'midi2note', 'freq2note'] | ||
| 5 | |||
| 6 | import sys | 4 | import sys |
| 5 | from ._aubio import freqtomidi, miditofreq | ||
| 6 | |||
| 7 | __all__ = ['note2midi', 'midi2note', 'freq2note', 'note2freq'] | ||
| 8 | |||
| 7 | py3 = sys.version_info[0] == 3 | 9 | py3 = sys.version_info[0] == 3 |
| 8 | if py3: | 10 | if py3: |
| 9 | str_instances = str | 11 | str_instances = str |
| @@ -12,18 +14,65 @@ else: · 2 unmodified lines | |||
| 12 | str_instances = (str, unicode) | 14 | str_instances = (str, unicode) |
| 13 | int_instances = (int, long) | 15 | int_instances = (int, long) |
| 14 | 16 | ||
| 17 | |||
| 15 | def note2midi(note): | 18 | def note2midi(note): |
| 16 | " convert note name to midi note number, e.g. [C-1, G9] -> [0, 127] " | 19 | """Convert note name to midi note number. |
| 17 | _valid_notenames = {'C': 0, 'D': 2, 'E': 4, 'F': 5, 'G': 7, 'A': 9, 'B': 11} | 20 | |
| 18 | _valid_modifiers = {None: 0, u'♮': 0, '#': +1, u'♯': +1, u'\udd2a': +2, | 21 | Input string `note` should be composed of one note root |
| 19 | 'b': -1, u'♭': -1, u'\ufffd': -2} | 22 | and one octave, with optionally one modifier in between. |
| 23 | |||
| 24 | List of valid components: | ||
| 25 | |||
| 26 | - note roots: `C`, `D`, `E`, `F`, `G`, `A`, `B`, | ||
| 27 | - modifiers: `b`, `#`, as well as unicode characters | ||
| 28 | `𝄫`, `♭`, `♮`, `♯` and `𝄪`, | ||
| 29 | - octave numbers: `-1` -> `11`. | ||
| 30 | |||
| 31 | Parameters | ||
| 32 | ---------- | ||
| 33 | note : str | ||
| 34 | note name | ||
| 35 | |||
| 36 | Returns | ||
| 37 | ------- | ||
| 38 | int | ||
| 39 | corresponding midi note number | ||
| 40 | |||
| 41 | Examples | ||
| 42 | -------- | ||
| 43 | >>> aubio.note2midi('C#4') | ||
| 44 | 61 | ||
| 45 | >>> aubio.note2midi('B♭5') | ||
| 46 | 82 | ||
| 47 | |||
| 48 | Raises | ||
| 49 | ------ | ||
| 50 | TypeError | ||
| 51 | If `note` was not a string. | ||
| 52 | ValueError | ||
| 53 | If an error was found while converting `note`. | ||
| 54 | |||
| 55 | See Also | ||
| 56 | -------- | ||
| 57 | midi2note, freqtomidi, miditofreq | ||
| 58 | """ | ||
| 59 | _valid_notenames = {'C': 0, 'D': 2, 'E': 4, 'F': 5, 'G': 7, | ||
| 60 | 'A': 9, 'B': 11} | ||
| 61 | _valid_modifiers = { | ||
| 62 | u'𝄫': -2, # double flat | ||
| 63 | u'♭': -1, 'b': -1, '\u266d': -1, # simple flat | ||
| 64 | u'♮': 0, '\u266e': 0, None: 0, # natural | ||
| 65 | '#': +1, u'♯': +1, '\u266f': +1, # sharp | ||
| 66 | u'𝄪': +2, # double sharp | ||
| 67 | } | ||
| 20 | _valid_octaves = range(-1, 10) | 68 | _valid_octaves = range(-1, 10) |
| 21 | if not isinstance(note, str_instances): | 69 | if not isinstance(note, str_instances): |
| 22 | raise TypeError("a string is required, got %s (%s)" % (note, str(type(note)))) | 70 | msg = "a string is required, got {:s} ({:s})" |
| 71 | raise TypeError(msg.format(str(type(note)), repr(note))) | ||
| 23 | if len(note) not in range(2, 5): | 72 | if len(note) not in range(2, 5): |
| 24 | raise ValueError("string of 2 to 4 characters expected, got %d (%s)" \ | 73 | msg = "string of 2 to 4 characters expected, got {:d} ({:s})" |
| 25 | % (len(note), note)) | 74 | raise ValueError(msg.format(len(note), note)) |
| 26 | notename, modifier, octave = [None]*3 | 75 | notename, modifier, octave = [None] * 3 |
| 27 | 76 | ||
| 28 | if len(note) == 4: | 77 | if len(note) == 4: |
| 29 | notename, modifier, octave_sign, octave = note | 78 | notename, modifier, octave_sign, octave = note |
| @@ -46,21 +95,97 @@ def note2midi(note): · 16 unmodified lines | |||
| 46 | if octave not in _valid_octaves: | 95 | if octave not in _valid_octaves: |
| 47 | raise ValueError("%s is not a valid octave" % octave) | 96 | raise ValueError("%s is not a valid octave" % octave) |
| 48 | 97 | ||
| 49 | midi = 12 + octave * 12 + _valid_notenames[notename] + _valid_modifiers[modifier] | 98 | midi = (octave + 1) * 12 + _valid_notenames[notename] \ |
| 99 | + _valid_modifiers[modifier] | ||
| 50 | if midi > 127: | 100 | if midi > 127: |
| 51 | raise ValueError("%s is outside of the range C-2 to G8" % note) | 101 | raise ValueError("%s is outside of the range C-2 to G8" % note) |
| 52 | return midi | 102 | return midi |
| 53 | 103 | ||
| 104 | |||
| 54 | def midi2note(midi): | 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 | if not isinstance(midi, int_instances): | 136 | if not isinstance(midi, int_instances): |
| 57 | raise TypeError("an integer is required, got %s" % midi) | 137 | raise TypeError("an integer is required, got %s" % midi) |
| 58 | if midi not in range(0, 128): | 138 | if midi not in range(0, 128): |
| 59 | raise ValueError("an integer between 0 and 127 is excepted, got %d" % midi) | 139 | msg = "an integer between 0 and 127 is excepted, got {:d}" |
| 60 | _valid_notenames = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'] | 140 | raise ValueError(msg.format(midi)) |
| 141 | _valid_notenames = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', | ||
| 142 | 'A', 'A#', 'B'] | ||
| 61 | return _valid_notenames[midi % 12] + str(int(midi / 12) - 1) | 143 | return _valid_notenames[midi % 12] + str(int(midi / 12) - 1) |
| 62 | 144 | ||
| 145 | |||
| 63 | def freq2note(freq): | 146 | def freq2note(freq): |
| 64 | " convert frequency in Hz to nearest note name, e.g. [0, 22050.] -> [C-1, G9] " | 147 | """Convert frequency in Hz to nearest note name. |
| 65 | from aubio import freqtomidi | 148 | |
| 66 | return midi2note(int(freqtomidi(freq))) | 149 | Parameters |
| 150 | ---------- | ||
| 151 | freq : float [0, 23000[ | ||
| 152 | input frequency, in Hz | ||
| 153 | |||
| 154 | Returns | ||
| 155 | ------- | ||
| 156 | str | ||
| 157 | name of the nearest note | ||
| 158 | |||
| 159 | Example | ||
| 160 | ------- | ||
| 161 | >>> aubio.freq2note(440) | ||
| 162 | 'A4' | ||
| 163 | >>> aubio.freq2note(220.1) | ||
| 164 | 'A3' | ||
| 165 | """ | ||
| 166 | nearest_note = int(freqtomidi(freq) + .5) | ||
| 167 | return midi2note(nearest_note) | ||
| 168 | |||
| 169 | |||
| 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 | _max_timestamp = 1e120 | 6 | _max_timestamp = 1e120 |
| 7 | 7 | ||
| 8 | |||
| 8 | def slice_source_at_stamps(source_file, timestamps, timestamps_end=None, | 9 | def slice_source_at_stamps(source_file, timestamps, timestamps_end=None, |
| 9 | output_dir=None, samplerate=0, hopsize=256): | 10 | output_dir=None, samplerate=0, hopsize=256, |
| 10 | """ slice a sound file at given timestamps """ | 11 | create_first=False): |
| 12 | """Slice a sound file at given timestamps. | ||
| 13 | |||
| 14 | This function reads `source_file` and creates slices, new smaller | ||
| 15 | files each starting at `t` in `timestamps`, a list of integer | ||
| 16 | corresponding to time locations in `source_file`, in samples. | ||
| 17 | |||
| 18 | If `timestamps_end` is unspecified, the slices will end at | ||
| 19 | `timestamps_end[n] = timestamps[n+1]-1`, or the end of file. | ||
| 20 | Otherwise, `timestamps_end` should be a list with the same length | ||
| 21 | as `timestamps` containing the locations of the end of each slice. | ||
| 22 | |||
| 23 | If `output_dir` is unspecified, the new slices will be written in | ||
| 24 | the current directory. If `output_dir` is a string, new slices | ||
| 25 | will be written in `output_dir`, after creating the directory if | ||
| 26 | required. | ||
| 27 | |||
| 28 | The default `samplerate` is 0, meaning the original sampling rate | ||
| 29 | of `source_file` will be used. When using a sampling rate | ||
| 30 | different to the one of the original files, `timestamps` and | ||
| 31 | `timestamps_end` should be expressed in the re-sampled signal. | ||
| 32 | |||
| 33 | The `hopsize` parameter simply tells :class:`source` to use this | ||
| 34 | hopsize and does not change the output slices. | ||
| 35 | |||
| 36 | If `create_first` is True and `timestamps` does not start with `0`, the | ||
| 37 | first slice from `0` to `timestamps[0] - 1` will be automatically added. | ||
| 38 | |||
| 39 | Parameters | ||
| 40 | ---------- | ||
| 41 | source_file : str | ||
| 42 | path of the resource to slice | ||
| 43 | timestamps : :obj:`list` of :obj:`int` | ||
| 44 | time stamps at which to slice, in samples | ||
| 45 | timestamps_end : :obj:`list` of :obj:`int` (optional) | ||
| 46 | time stamps at which to end the slices | ||
| 47 | output_dir : str (optional) | ||
| 48 | output directory to write the slices to | ||
| 49 | samplerate : int (optional) | ||
| 50 | samplerate to read the file at | ||
| 51 | hopsize : int (optional) | ||
| 52 | number of samples read from source per iteration | ||
| 53 | create_first : bool (optional) | ||
| 54 | always create the slice at the start of the file | ||
| 55 | |||
| 56 | Examples | ||
| 57 | -------- | ||
| 58 | Create two slices: the first slice starts at the beginning of the | ||
| 59 | input file `loop.wav` and lasts exactly one second, starting at | ||
| 60 | sample `0` and ending at sample `44099`; the second slice starts | ||
| 61 | at sample `44100` and lasts until the end of the input file: | ||
| 62 | |||
| 63 | >>> aubio.slice_source_at_stamps('loop.wav', [0, 44100]) | ||
| 64 | |||
| 65 | Create one slice, from 1 second to 2 seconds: | ||
| 66 | |||
| 67 | >>> aubio.slice_source_at_stamps('loop.wav', [44100], [44100 * 2 - 1]) | ||
| 68 | |||
| 69 | Notes | ||
| 70 | ----- | ||
| 71 | Slices may be overlapping. If `timestamps_end` is `1` element | ||
| 72 | shorter than `timestamps`, the last slice will end at the end of | ||
| 73 | the file. | ||
| 74 | """ | ||
| 11 | 75 | ||
| 12 | if timestamps is None or len(timestamps) == 0: | 76 | if not timestamps: |
| 13 | raise ValueError("no timestamps given") | 77 | raise ValueError("no timestamps given") |
| 14 | 78 | ||
| 15 | if timestamps[0] != 0: | 79 | if timestamps[0] != 0 and create_first: |
| 16 | timestamps = [0] + timestamps | 80 | timestamps = [0] + timestamps |
| 17 | if timestamps_end is not None: | 81 | if timestamps_end is not None: |
| 18 | timestamps_end = [timestamps[1] - 1] + timestamps_end | 82 | timestamps_end = [timestamps[1] - 1] + timestamps_end |
| 19 | 83 | ||
| 20 | if timestamps_end is not None: | 84 | if timestamps_end is not None: |
| 21 | if len(timestamps_end) != len(timestamps): | 85 | if len(timestamps_end) == len(timestamps) - 1: |
| 86 | timestamps_end = timestamps_end + [_max_timestamp] | ||
| 87 | elif len(timestamps_end) != len(timestamps): | ||
| 22 | raise ValueError("len(timestamps_end) != len(timestamps)") | 88 | raise ValueError("len(timestamps_end) != len(timestamps)") |
| 23 | else: | 89 | else: |
| 24 | timestamps_end = [t - 1 for t in timestamps[1:]] + [_max_timestamp] | 90 | timestamps_end = [t - 1 for t in timestamps[1:]] + [_max_timestamp] |
| 25 | 91 | ||
| 26 | regions = list(zip(timestamps, timestamps_end)) | 92 | regions = list(zip(timestamps, timestamps_end)) |
| 27 | #print regions | ||
| 28 | 93 | ||
| 29 | source_base_name, _ = os.path.splitext(os.path.basename(source_file)) | 94 | source_base_name, _ = os.path.splitext(os.path.basename(source_file)) |
| 30 | if output_dir is not None: | 95 | if output_dir is not None: |
| @@ -32,8 +97,8 @@ def slice_source_at_stamps(source_file, timestamps, timestamps_end=None, · 1 unmodified line | |||
| 32 | os.makedirs(output_dir) | 97 | os.makedirs(output_dir) |
| 33 | source_base_name = os.path.join(output_dir, source_base_name) | 98 | source_base_name = os.path.join(output_dir, source_base_name) |
| 34 | 99 | ||
| 35 | def new_sink_name(source_base_name, timestamp, samplerate): | 100 | def _new_sink_name(source_base_name, timestamp, samplerate): |
| 36 | """ create a sink based on a timestamp in samples, converted in seconds """ | 101 | # create name based on a timestamp in samples, converted in seconds |
| 37 | timestamp_seconds = timestamp / float(samplerate) | 102 | timestamp_seconds = timestamp / float(samplerate) |
| 38 | return source_base_name + "_%011.6f" % timestamp_seconds + '.wav' | 103 | return source_base_name + "_%011.6f" % timestamp_seconds + '.wav' |
| 39 | 104 | ||
| @@ -48,16 +113,17 @@ def slice_source_at_stamps(source_file, timestamps, timestamps_end=None, · 8 unmodified lines | |||
| 48 | # get hopsize new samples from source | 113 | # get hopsize new samples from source |
| 49 | vec, read = _source.do_multi() | 114 | vec, read = _source.do_multi() |
| 50 | # if the total number of frames read will exceed the next region start | 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]: | 116 | while regions and total_frames + read >= regions[0][0]: |
| 52 | #print "getting", regions[0], "at", total_frames | ||
| 53 | # get next region | 117 | # get next region |
| 54 | start_stamp, end_stamp = regions.pop(0) | 118 | start_stamp, end_stamp = regions.pop(0) |
| 55 | # create a name for the sink | 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 | # create its sink | 122 | # create its sink |
| 58 | _sink = sink(new_sink_path, samplerate, _source.channels) | 123 | _sink = sink(new_sink_path, samplerate, _source.channels) |
| 59 | # create a dictionary containing all this | 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 | # append the dictionary to the current list of slices | 127 | # append the dictionary to the current list of slices |
| 62 | slices.append(new_slice) | 128 | slices.append(new_slice) |
| 63 | 129 | ||
| @@ -69,18 +135,19 @@ def slice_source_at_stamps(source_file, timestamps, timestamps_end=None, · 5 unmodified lines | |||
| 69 | start = max(start_stamp - total_frames, 0) | 135 | start = max(start_stamp - total_frames, 0) |
| 70 | # number of samples yet to written be until end of region | 136 | # number of samples yet to written be until end of region |
| 71 | remaining = end_stamp - total_frames + 1 | 137 | remaining = end_stamp - total_frames + 1 |
| 72 | #print current_slice, remaining, start | ||
| 73 | # not enough frames remaining, time to split | 138 | # not enough frames remaining, time to split |
| 74 | if remaining < read: | 139 | if remaining < read: |
| 75 | if remaining > start: | 140 | if remaining > start: |
| 76 | # write remaining samples from current region | 141 | # write remaining samples from current region |
| 77 | _sink.do_multi(vec[:, start:remaining], remaining - start) | 142 | _sink.do_multi(vec[:, start:remaining], remaining - start) |
| 78 | #print "closing region", "remaining", remaining | ||
| 79 | # close this file | 143 | # close this file |
| 80 | _sink.close() | 144 | _sink.close() |
| 81 | elif read > start: | 145 | elif read > start: |
| 82 | # write all the samples | 146 | # write all the samples |
| 83 | _sink.do_multi(vec[:, start:read], read - start) | 147 | _sink.do_multi(vec[:, start:read], read - start) |
| 84 | total_frames += read | 148 | total_frames += read |
| 149 | # remove old slices | ||
| 150 | slices = list(filter(lambda s: s['end_stamp'] > total_frames, | ||
| 151 | slices)) | ||
| 85 | if read < hopsize: | 152 | if read < hopsize: |
| 86 | break | 153 | break |
| @@ -2,6 +2,7 @@ aubiodefvalue = { · 1 unmodified line | |||
| 2 | # we have some clean up to do | 2 | # we have some clean up to do |
| 3 | 'buf_size': 'Py_default_vector_length', | 3 | 'buf_size': 'Py_default_vector_length', |
| 4 | 'win_s': 'Py_default_vector_length', | 4 | 'win_s': 'Py_default_vector_length', |
| 5 | 'size': 'Py_default_vector_length', | ||
| 5 | # and here too | 6 | # and here too |
| 6 | 'hop_size': 'Py_default_vector_length / 2', | 7 | 'hop_size': 'Py_default_vector_length / 2', |
| 7 | 'hop_s': 'Py_default_vector_length / 2', | 8 | 'hop_s': 'Py_default_vector_length / 2', |
| @@ -84,6 +85,7 @@ objoutsize = { · 76 unmodified lines | |||
| 84 | 'filterbank': 'self->n_filters', | 85 | 'filterbank': 'self->n_filters', |
| 85 | 'tss': 'self->buf_size', | 86 | 'tss': 'self->buf_size', |
| 86 | 'pitchshift': 'self->hop_size', | 87 | 'pitchshift': 'self->hop_size', |
| 88 | 'dct': 'self->size', | ||
| 87 | } | 89 | } |
| 88 | 90 | ||
| 89 | objinputsize = { | 91 | objinputsize = { |
| @@ -179,6 +181,10 @@ class MappedObject(object): · 89 unmodified lines | |||
| 179 | self.do_inputs = [get_params_types_names(self.do_proto)[1]] | 181 | self.do_inputs = [get_params_types_names(self.do_proto)[1]] |
| 180 | self.do_outputs = get_params_types_names(self.do_proto)[2:] | 182 | self.do_outputs = get_params_types_names(self.do_proto)[2:] |
| 181 | struct_output_str = ["PyObject *{0[name]}; {1} c_{0[name]}".format(i, i['type'][:-1]) for i in self.do_outputs] | 183 | struct_output_str = ["PyObject *{0[name]}; {1} c_{0[name]}".format(i, i['type'][:-1]) for i in self.do_outputs] |
| 184 | if len(self.prototypes['rdo']): | ||
| 185 | rdo_outputs = get_params_types_names(prototypes['rdo'][0])[2:] | ||
| 186 | struct_output_str += ["PyObject *{0[name]}; {1} c_{0[name]}".format(i, i['type'][:-1]) for i in rdo_outputs] | ||
| 187 | self.outputs += rdo_outputs | ||
| 182 | self.struct_outputs = ";\n ".join(struct_output_str) | 188 | self.struct_outputs = ";\n ".join(struct_output_str) |
| 183 | 189 | ||
| 184 | #print ("input_params: ", map(split_type, get_input_params(self.do_proto))) | 190 | #print ("input_params: ", map(split_type, get_input_params(self.do_proto))) |
| @@ -186,17 +192,26 @@ class MappedObject(object): · 1 unmodified line | |||
| 186 | 192 | ||
| 187 | def gen_code(self): | 193 | def gen_code(self): |
| 188 | out = "" | 194 | out = "" |
| 189 | out += self.gen_struct() | 195 | try: |
| 190 | out += self.gen_doc() | 196 | out += self.gen_struct() |
| 191 | out += self.gen_new() | 197 | out += self.gen_doc() |
| 192 | out += self.gen_init() | 198 | out += self.gen_new() |
| 193 | out += self.gen_del() | 199 | out += self.gen_init() |
| 194 | out += self.gen_do() | 200 | out += self.gen_del() |
| 195 | out += self.gen_memberdef() | 201 | out += self.gen_do() |
| 196 | out += self.gen_set() | 202 | if len(self.prototypes['rdo']): |
| 197 | out += self.gen_get() | 203 | self.do_proto = self.prototypes['rdo'][0] |
| 198 | out += self.gen_methodef() | 204 | self.do_inputs = [get_params_types_names(self.do_proto)[1]] |
| 199 | out += self.gen_typeobject() | 205 | self.do_outputs = get_params_types_names(self.do_proto)[2:] |
| 206 | out += self.gen_do(method='rdo') | ||
| 207 | out += self.gen_memberdef() | ||
| 208 | out += self.gen_set() | ||
| 209 | out += self.gen_get() | ||
| 210 | out += self.gen_methodef() | ||
| 211 | out += self.gen_typeobject() | ||
| 212 | except Exception as e: | ||
| 213 | print ("Failed generating code for", self.shortname) | ||
| 214 | raise | ||
| 200 | return out | 215 | return out |
| 201 | 216 | ||
| 202 | def gen_struct(self): | 217 | def gen_struct(self): |
| @@ -380,12 +395,12 @@ Py_{shortname}_del (Py_{shortname} * self, PyObject * unused) · 177 unmodified lines | |||
| 380 | """.format(del_fn = del_fn) | 395 | """.format(del_fn = del_fn) |
| 381 | return out | 396 | return out |
| 382 | 397 | ||
| 383 | def gen_do(self): | 398 | def gen_do(self, method = 'do'): |
| 384 | out = """ | 399 | out = """ |
| 385 | // do {shortname} | 400 | // do {shortname} |
| 386 | static PyObject* | 401 | static PyObject* |
| 387 | Py_{shortname}_do (Py_{shortname} * self, PyObject * args) | 402 | Pyaubio_{shortname}_{method} (Py_{shortname} * self, PyObject * args) |
| 388 | {{""".format(**self.__dict__) | 403 | {{""".format(method = method, **self.__dict__) |
| 389 | input_params = self.do_inputs | 404 | input_params = self.do_inputs |
| 390 | output_params = self.do_outputs | 405 | output_params = self.do_outputs |
| 391 | #print input_params | 406 | #print input_params |
| @@ -461,30 +476,51 @@ Py_{shortname}_do (Py_{shortname} * self, PyObject * args) · 69 unmodified lines | |||
| 461 | // {shortname} setters | 476 | // {shortname} setters |
| 462 | """.format(**self.__dict__) | 477 | """.format(**self.__dict__) |
| 463 | for set_param in self.prototypes['set']: | 478 | for set_param in self.prototypes['set']: |
| 464 | params = get_params_types_names(set_param)[1] | 479 | params = get_params_types_names(set_param)[1:] |
| 465 | paramtype = params['type'] | 480 | param = self.shortname.split('_set_')[-1] |
| 481 | paramdecls = "".join([""" | ||
| 482 | {0} {1};""".format(p['type'], p['name']) for p in params]) | ||
| 466 | method_name = get_name(set_param) | 483 | method_name = get_name(set_param) |
| 467 | param = method_name.split('aubio_'+self.shortname+'_set_')[-1] | 484 | param = method_name.split('aubio_'+self.shortname+'_set_')[-1] |
| 468 | pyparamtype = pyargparse_chars[paramtype] | 485 | refs = ", ".join(["&%s" % p['name'] for p in params]) |
| 486 | paramlist = ", ".join(["%s" % p['name'] for p in params]) | ||
| 487 | if len(params): | ||
| 488 | paramlist = "," + paramlist | ||
| 489 | pyparamtypes = ''.join([pyargparse_chars[p['type']] for p in params]) | ||
| 469 | out += """ | 490 | out += """ |
| 470 | static PyObject * | 491 | static PyObject * |
| 471 | Pyaubio_{shortname}_set_{param} (Py_{shortname} *self, PyObject *args) | 492 | Pyaubio_{shortname}_set_{param} (Py_{shortname} *self, PyObject *args) |
| 472 | {{ | 493 | {{ |
| 473 | uint_t err = 0; | 494 | uint_t err = 0; |
| 474 | {paramtype} {param}; | 495 | {paramdecls} |
| 496 | """.format(param = param, paramdecls = paramdecls, **self.__dict__) | ||
| 497 | |||
| 498 | if len(refs) and len(pyparamtypes): | ||
| 499 | out += """ | ||
| 475 | 500 | ||
| 476 | if (!PyArg_ParseTuple (args, "{pyparamtype}", &{param})) {{ | 501 | if (!PyArg_ParseTuple (args, "{pyparamtypes}", {refs})) {{ |
| 477 | return NULL; | 502 | return NULL; |
| 478 | }} | 503 | }} |
| 479 | err = aubio_{shortname}_set_{param} (self->o, {param}); | 504 | """.format(pyparamtypes = pyparamtypes, refs = refs) |
| 505 | |||
| 506 | out += """ | ||
| 507 | err = aubio_{shortname}_set_{param} (self->o {paramlist}); | ||
| 480 | 508 | ||
| 481 | if (err > 0) {{ | 509 | if (err > 0) {{ |
| 482 | PyErr_SetString (PyExc_ValueError, "error running aubio_{shortname}_set_{param}"); | 510 | if (PyErr_Occurred() == NULL) {{ |
| 511 | PyErr_SetString (PyExc_ValueError, "error running aubio_{shortname}_set_{param}"); | ||
| 512 | }} else {{ | ||
| 513 | // change the RuntimeError into ValueError | ||
| 514 | PyObject *type, *value, *traceback; | ||
| 515 | PyErr_Fetch(&type, &value, &traceback); | ||
| 516 | PyErr_Restore(PyExc_ValueError, value, traceback); | ||
| 517 | }} | ||
| 483 | return NULL; | 518 | return NULL; |
| 484 | }} | 519 | }} |
| 485 | Py_RETURN_NONE; | 520 | Py_RETURN_NONE; |
| 486 | }} | 521 | }} |
| 487 | """.format(param = param, paramtype = paramtype, pyparamtype = pyparamtype, **self.__dict__) | 522 | """.format(param = param, refs = refs, paramdecls = paramdecls, |
| 523 | pyparamtypes = pyparamtypes, paramlist = paramlist, **self.__dict__) | ||
| 488 | return out | 524 | return out |
| 489 | 525 | ||
| 490 | def gen_get(self): | 526 | def gen_get(self): |
| @@ -525,6 +561,12 @@ static PyMethodDef Py_{shortname}_methods[] = {{""".format(**self.__dict__) · 34 unmodified lines | |||
| 525 | out += """ | 561 | out += """ |
| 526 | {{"{shortname}", (PyCFunction) Py{name}, | 562 | {{"{shortname}", (PyCFunction) Py{name}, |
| 527 | METH_NOARGS, ""}},""".format(name = name, shortname = shortname) | 563 | METH_NOARGS, ""}},""".format(name = name, shortname = shortname) |
| 564 | for m in self.prototypes['rdo']: | ||
| 565 | name = get_name(m) | ||
| 566 | shortname = name.replace('aubio_%s_' % self.shortname, '') | ||
| 567 | out += """ | ||
| 568 | {{"{shortname}", (PyCFunction) Py{name}, | ||
| 569 | METH_VARARGS, ""}},""".format(name = name, shortname = shortname) | ||
| 528 | out += """ | 570 | out += """ |
| 529 | {NULL} /* sentinel */ | 571 | {NULL} /* sentinel */ |
| 530 | }; | 572 | }; |
| @@ -550,7 +592,7 @@ PyTypeObject Py_{shortname}Type = {{ · 19 unmodified lines | |||
| 550 | 0, | 592 | 0, |
| 551 | 0, | 593 | 0, |
| 552 | 0, | 594 | 0, |
| 553 | (ternaryfunc)Py_{shortname}_do, | 595 | (ternaryfunc)Pyaubio_{shortname}_do, |
| 554 | 0, | 596 | 0, |
| 555 | 0, | 597 | 0, |
| 556 | 0, | 598 | 0, |
| @@ -1,5 +1,10 @@ | |||
| 1 | import distutils.ccompiler | 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 | header = os.path.join('src', 'aubio.h') | 9 | header = os.path.join('src', 'aubio.h') |
| 5 | output_path = os.path.join('python', 'gen') | 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 | #include "aubio-types.h" | 13 | #include "aubio-types.h" |
| 9 | """ | 14 | """ |
| 10 | 15 | ||
| 11 | skip_objects = [ | 16 | default_skip_objects = [ |
| 12 | # already in ext/ | 17 | # already in ext/ |
| 13 | 'fft', | 18 | 'fft', |
| 14 | 'pvoc', | 19 | 'pvoc', |
| 15 | 'filter', | 20 | 'filter', |
| 16 | 'filterbank', | 21 | 'filterbank', |
| 17 | #'resampler', | 22 | # AUBIO_UNSTABLE |
| 18 | # AUBIO_UNSTABLE | 23 | 'hist', |
| 19 | 'hist', | 24 | 'parameter', |
| 20 | 'parameter', | 25 | 'scale', |
| 21 | 'scale', | 26 | 'beattracking', |
| 22 | 'beattracking', | 27 | 'resampler', |
| 23 | 'resampler', | 28 | 'peakpicker', |
| 24 | 'peakpicker', | 29 | 'pitchfcomb', |
| 25 | 'pitchfcomb', | 30 | 'pitchmcomb', |
| 26 | 'pitchmcomb', | 31 | 'pitchschmitt', |
| 27 | 'pitchschmitt', | 32 | 'pitchspecacf', |
| 28 | 'pitchspecacf', | 33 | 'pitchyin', |
| 29 | 'pitchyin', | 34 | 'pitchyinfft', |
| 30 | 'pitchyinfft', | 35 | 'pitchyinfast', |
| 31 | 'sink', | 36 | 'sink', |
| 32 | 'sink_apple_audio', | 37 | 'sink_apple_audio', |
| 33 | 'sink_sndfile', | 38 | 'sink_sndfile', |
| 34 | 'sink_wavwrite', | 39 | 'sink_wavwrite', |
| 35 | #'mfcc', | 40 | #'mfcc', |
| 36 | 'source', | 41 | 'source', |
| 37 | 'source_apple_audio', | 42 | 'source_apple_audio', |
| 38 | 'source_sndfile', | 43 | 'source_sndfile', |
| 39 | 'source_avcodec', | 44 | 'source_avcodec', |
| 40 | 'source_wavread', | 45 | 'source_wavread', |
| 41 | #'sampler', | 46 | #'sampler', |
| 42 | 'audio_unit', | 47 | 'audio_unit', |
| 43 | ] | 48 | 'spectral_whitening', |
| 49 | ] | ||
| 50 | |||
| 44 | 51 | ||
| 45 | def get_preprocessor(): | 52 | def get_preprocessor(): |
| 46 | # findout which compiler to use | 53 | # findout which compiler to use |
| 47 | from distutils.sysconfig import customize_compiler | ||
| 48 | compiler_name = distutils.ccompiler.get_default_compiler() | 54 | compiler_name = distutils.ccompiler.get_default_compiler() |
| 49 | compiler = distutils.ccompiler.new_compiler(compiler=compiler_name) | 55 | compiler = distutils.ccompiler.new_compiler(compiler=compiler_name) |
| 50 | try: | 56 | try: |
| @@ -59,26 +65,50 @@ def get_preprocessor(): · 8 unmodified lines | |||
| 59 | print("Warning: failed initializing compiler ({:s})".format(repr(e))) | 65 | print("Warning: failed initializing compiler ({:s})".format(repr(e))) |
| 60 | 66 | ||
| 61 | cpp_cmd = None | 67 | cpp_cmd = None |
| 62 | if hasattr(compiler, 'preprocessor'): # for unixccompiler | 68 | if hasattr(compiler, 'preprocessor'): # for unixccompiler |
| 63 | cpp_cmd = compiler.preprocessor | 69 | cpp_cmd = compiler.preprocessor |
| 64 | elif hasattr(compiler, 'compiler'): # for ccompiler | 70 | elif hasattr(compiler, 'compiler'): # for ccompiler |
| 65 | cpp_cmd = compiler.compiler.split() | 71 | cpp_cmd = compiler.compiler.split() |
| 66 | cpp_cmd += ['-E'] | 72 | cpp_cmd += ['-E'] |
| 67 | elif hasattr(compiler, 'cc'): # for msvccompiler | 73 | elif hasattr(compiler, 'cc'): # for msvccompiler |
| 68 | cpp_cmd = compiler.cc.split() | 74 | cpp_cmd = compiler.cc.split() |
| 69 | cpp_cmd += ['-E'] | 75 | cpp_cmd += ['-E'] |
| 70 | 76 | ||
| 77 | # On win-amd64 (py3.x), the default compiler is cross-compiling, from x86 | ||
| 78 | # to amd64 with %WIN_SDK_ROOT%\x86_amd64\cl.exe, but using this binary as a | ||
| 79 | # pre-processor generates no output, so we use %WIN_SDK_ROOT%\cl.exe | ||
| 80 | # instead. | ||
| 81 | if len(cpp_cmd) > 1 and 'cl.exe' in cpp_cmd[-2]: | ||
| 82 | plat = os.path.basename(os.path.dirname(cpp_cmd[-2])) | ||
| 83 | if plat == 'x86_amd64': | ||
| 84 | print('workaround on win64 to avoid empty pre-processor output') | ||
| 85 | cpp_cmd[-2] = cpp_cmd[-2].replace('x86_amd64', '') | ||
| 86 | elif True in ['amd64' in f for f in cpp_cmd]: | ||
| 87 | print('warning: not using workaround for', cpp_cmd[0], plat) | ||
| 88 | |||
| 71 | if not cpp_cmd: | 89 | if not cpp_cmd: |
| 72 | print("Warning: could not guess preprocessor, using env's CC") | 90 | print("Warning: could not guess preprocessor, using env's CC") |
| 73 | cpp_cmd = os.environ.get('CC', 'cc').split() | 91 | cpp_cmd = os.environ.get('CC', 'cc').split() |
| 74 | cpp_cmd += ['-E'] | 92 | cpp_cmd += ['-E'] |
| 75 | 93 | if 'emcc' in cpp_cmd: | |
| 94 | cpp_cmd += ['-x', 'c'] # emcc defaults to c++, force C language | ||
| 76 | return cpp_cmd | 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 | cpp_cmd = get_preprocessor() | 107 | cpp_cmd = get_preprocessor() |
| 80 | 108 | ||
| 81 | macros = [('AUBIO_UNSTABLE', 1)] | 109 | macros = [('AUBIO_UNSTABLE', 1)] |
| 110 | if usedouble: | ||
| 111 | macros += [('HAVE_AUBIO_DOUBLE', 1)] | ||
| 82 | 112 | ||
| 83 | if not os.path.isfile(header): | 113 | if not os.path.isfile(header): |
| 84 | raise Exception("could not find include file " + header) | 114 | raise Exception("could not find include file " + header) |
| @@ -89,56 +119,115 @@ def get_cpp_objects(header=header): · 4 unmodified lines | |||
| 89 | 119 | ||
| 90 | print("Running command: {:s}".format(" ".join(cpp_cmd))) | 120 | print("Running command: {:s}".format(" ".join(cpp_cmd))) |
| 91 | proc = subprocess.Popen(cpp_cmd, | 121 | proc = subprocess.Popen(cpp_cmd, |
| 92 | stderr=subprocess.PIPE, | 122 | stderr=subprocess.PIPE, |
| 93 | stdout=subprocess.PIPE) | 123 | stdout=subprocess.PIPE) |
| 94 | assert proc, 'Proc was none' | 124 | assert proc, 'Proc was none' |
| 95 | cpp_output = proc.stdout.read() | 125 | cpp_output = proc.stdout.read() |
| 96 | err_output = proc.stderr.read() | 126 | err_output = proc.stderr.read() |
| 127 | if err_output: | ||
| 128 | print("Warning: preprocessor produced errors or warnings:\n%s" \ | ||
| 129 | % err_output.decode('utf8')) | ||
| 97 | if not cpp_output: | 130 | if not cpp_output: |
| 98 | raise Exception("preprocessor output is empty:\n%s" % err_output) | 131 | raise_msg = "preprocessor output is empty! Running command " \ |
| 99 | elif err_output: | 132 | + "\"%s\" failed" % " ".join(cpp_cmd) |
| 100 | print ("Warning: preprocessor produced warnings:\n%s" % err_output) | 133 | if err_output: |
| 134 | raise_msg += " with stderr: \"%s\"" % err_output.decode('utf8') | ||
| 135 | else: | ||
| 136 | raise_msg += " with no stdout or stderr" | ||
| 137 | raise Exception(raise_msg) | ||
| 101 | if not isinstance(cpp_output, list): | 138 | if not isinstance(cpp_output, list): |
| 102 | cpp_output = [l.strip() for l in cpp_output.decode('utf8').split('\n')] | 139 | cpp_output = [l.strip() for l in cpp_output.decode('utf8').split('\n')] |
| 103 | 140 | ||
| 104 | cpp_output = filter(lambda y: len(y) > 1, cpp_output) | 141 | return cpp_output |
| 142 | |||
| 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 | cpp_output = list(filter(lambda y: not y.startswith('#'), cpp_output)) | 146 | cpp_output = list(filter(lambda y: not y.startswith('#'), cpp_output)) |
| 106 | 147 | ||
| 107 | i = 1 | 148 | i = 1 |
| 108 | while 1: | 149 | while 1: |
| 109 | if i >= len(cpp_output): break | 150 | if i >= len(cpp_output): |
| 110 | if cpp_output[i-1].endswith(',') or cpp_output[i-1].endswith('{') or cpp_output[i].startswith('}'): | 151 | break |
| 111 | cpp_output[i] = cpp_output[i-1] + ' ' + cpp_output[i] | 152 | if ('{' in cpp_output[i - 1]) and ('}' not in cpp_output[i - 1]) or (';' not in cpp_output[i - 1]): |
| 112 | cpp_output.pop(i-1) | 153 | cpp_output[i] = cpp_output[i - 1] + ' ' + cpp_output[i] |
| 154 | cpp_output.pop(i - 1) | ||
| 155 | elif ('}' in cpp_output[i]): | ||
| 156 | cpp_output[i] = cpp_output[i - 1] + ' ' + cpp_output[i] | ||
| 157 | cpp_output.pop(i - 1) | ||
| 113 | else: | 158 | else: |
| 114 | i += 1 | 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 | cpp_objects = [a.split()[3][:-1] for a in typedefs] | 174 | cpp_objects = [a.split()[3][:-1] for a in typedefs] |
| 175 | cpp_objects_filtered = filter(lambda y: not y[6:-2] in skip_objects, cpp_objects) | ||
| 176 | return cpp_objects_filtered | ||
| 177 | |||
| 178 | |||
| 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 | lib = {} | 208 | lib = {} |
| 125 | 209 | ||
| 126 | for o in cpp_objects: | 210 | for o in cpp_objects: |
| 127 | if o[:6] != 'aubio_': | 211 | shortname = o |
| 128 | continue | 212 | if o[:6] == 'aubio_': |
| 129 | shortname = o[6:-2] | 213 | shortname = o[6:-2] # without aubio_ prefix and _t suffix |
| 130 | if shortname in skip_objects: | 214 | |
| 131 | continue | 215 | lib[shortname] = {'struct': [], 'new': [], 'del': [], 'do': [], 'rdo': [], 'get': [], 'set': [], 'other': []} |
| 132 | lib[shortname] = {'struct': [], 'new': [], 'del': [], 'do': [], 'get': [], 'set': [], 'other': []} | ||
| 133 | lib[shortname]['longname'] = o | 216 | lib[shortname]['longname'] = o |
| 134 | lib[shortname]['shortname'] = shortname | 217 | lib[shortname]['shortname'] = shortname |
| 135 | for fn in cpp_output: | 218 | |
| 136 | if o[:-1] in fn: | 219 | fullshortname = o[:-2] # name without _t suffix |
| 137 | #print "found", o[:-1], "in", fn | 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 | if 'typedef struct ' in fn: | 225 | if 'typedef struct ' in fn: |
| 139 | lib[shortname]['struct'].append(fn) | 226 | lib[shortname]['struct'].append(fn) |
| 140 | elif '_do' in fn: | 227 | elif '_do' in fn: |
| 141 | lib[shortname]['do'].append(fn) | 228 | lib[shortname]['do'].append(fn) |
| 229 | elif '_rdo' in fn: | ||
| 230 | lib[shortname]['rdo'].append(fn) | ||
| 142 | elif 'new_' in fn: | 231 | elif 'new_' in fn: |
| 143 | lib[shortname]['new'].append(fn) | 232 | lib[shortname]['new'].append(fn) |
| 144 | elif 'del_' in fn: | 233 | elif 'del_' in fn: |
| @@ -148,52 +237,52 @@ def analyze_cpp_output(cpp_objects, cpp_output): · 3 unmodified lines | |||
| 148 | elif '_set_' in fn: | 237 | elif '_set_' in fn: |
| 149 | lib[shortname]['set'].append(fn) | 238 | lib[shortname]['set'].append(fn) |
| 150 | else: | 239 | else: |
| 151 | #print "no idea what to do about", fn | 240 | # print "no idea what to do about", fn |
| 152 | lib[shortname]['other'].append(fn) | 241 | lib[shortname]['other'].append(fn) |
| 153 | return lib | 242 | return lib |
| 154 | 243 | ||
| 155 | def print_cpp_output_results(lib, cpp_output): | 244 | |
| 156 | for fn in cpp_output: | 245 | def print_c_declarations_results(lib, c_declarations): |
| 246 | for fn in c_declarations: | ||
| 157 | found = 0 | 247 | found = 0 |
| 158 | for o in lib: | 248 | for o in lib: |
| 159 | for family in lib[o]: | 249 | for family in lib[o]: |
| 160 | if fn in lib[o][family]: | 250 | if fn in lib[o][family]: |
| 161 | found = 1 | 251 | found = 1 |
| 162 | if found == 0: | 252 | if found == 0: |
| 163 | print ("missing", fn) | 253 | print("missing", fn) |
| 164 | 254 | ||
| 165 | for o in lib: | 255 | for o in lib: |
| 166 | for family in lib[o]: | 256 | for family in lib[o]: |
| 167 | if type(lib[o][family]) == str: | 257 | if type(lib[o][family]) == str: |
| 168 | print ( "{:15s} {:10s} {:s}".format(o, family, lib[o][family] ) ) | 258 | print("{:15s} {:10s} {:s}".format(o, family, lib[o][family])) |
| 169 | elif len(lib[o][family]) == 1: | 259 | elif len(lib[o][family]) == 1: |
| 170 | print ( "{:15s} {:10s} {:s}".format(o, family, lib[o][family][0] ) ) | 260 | print("{:15s} {:10s} {:s}".format(o, family, lib[o][family][0])) |
| 171 | else: | 261 | else: |
| 172 | print ( "{:15s} {:10s} {:s}".format(o, family, lib[o][family] ) ) | 262 | print("{:15s} {:10s} {:s}".format(o, family, lib[o][family])) |
| 173 | 263 | ||
| 174 | 264 | ||
| 175 | def generate_external(header=header, output_path=output_path, usedouble=False, overwrite=True): | 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) | 266 | if not os.path.isdir(output_path): |
| 177 | elif not overwrite: return glob.glob(os.path.join(output_path, '*.c')) | 267 | os.mkdir(output_path) |
| 268 | elif not overwrite: | ||
| 269 | return sorted(glob.glob(os.path.join(output_path, '*.c'))) | ||
| 178 | 270 | ||
| 179 | cpp_output, cpp_objects = get_cpp_objects(header) | 271 | c_declarations = get_c_declarations(header, usedouble=usedouble) |
| 272 | cpp_objects = get_cpp_objects_from_c_declarations(c_declarations) | ||
| 180 | 273 | ||
| 181 | lib = analyze_cpp_output(cpp_objects, cpp_output) | 274 | lib = generate_lib_from_c_declarations(cpp_objects, c_declarations) |
| 182 | # print_cpp_output_results(lib, cpp_output) | 275 | # print_c_declarations_results(lib, c_declarations) |
| 183 | 276 | ||
| 184 | sources_list = [] | 277 | sources_list = [] |
| 185 | try: | ||
| 186 | from .gen_code import MappedObject | ||
| 187 | except (SystemError, ValueError): | ||
| 188 | from gen_code import MappedObject | ||
| 189 | for o in lib: | 278 | for o in lib: |
| 190 | out = source_header | 279 | out = source_header |
| 191 | mapped = MappedObject(lib[o], usedouble = usedouble) | 280 | mapped = MappedObject(lib[o], usedouble=usedouble) |
| 192 | out += mapped.gen_code() | 281 | out += mapped.gen_code() |
| 193 | output_file = os.path.join(output_path, 'gen-%s.c' % o) | 282 | output_file = os.path.join(output_path, 'gen-%s.c' % o) |
| 194 | with open(output_file, 'w') as f: | 283 | with open(output_file, 'w') as f: |
| 195 | f.write(out) | 284 | f.write(out) |
| 196 | print ("wrote %s" % output_file ) | 285 | print("wrote %s" % output_file) |
| 197 | sources_list.append(output_file) | 286 | sources_list.append(output_file) |
| 198 | 287 | ||
| 199 | out = source_header | 288 | out = source_header |
| @@ -205,23 +294,23 @@ int generated_types_ready (void) · 5 unmodified lines | |||
| 205 | {{ | 294 | {{ |
| 206 | return ({pycheck_types}); | 295 | return ({pycheck_types}); |
| 207 | }} | 296 | }} |
| 208 | """.format(pycheck_types = check_types) | 297 | """.format(pycheck_types=check_types) |
| 209 | 298 | ||
| 210 | add_types = "".join([""" | 299 | add_types = "".join([""" |
| 211 | Py_INCREF (&Py_{name}Type); | 300 | Py_INCREF (&Py_{name}Type); |
| 212 | PyModule_AddObject(m, "{name}", (PyObject *) & Py_{name}Type);""".format(name = o) for o in lib]) | 301 | PyModule_AddObject(m, "{name}", (PyObject *) & Py_{name}Type);""".format(name=o) for o in lib]) |
| 213 | out += """ | 302 | out += """ |
| 214 | 303 | ||
| 215 | void add_generated_objects ( PyObject *m ) | 304 | void add_generated_objects ( PyObject *m ) |
| 216 | {{ | 305 | {{ |
| 217 | {add_types} | 306 | {add_types} |
| 218 | }} | 307 | }} |
| 219 | """.format(add_types = add_types) | 308 | """.format(add_types=add_types) |
| 220 | 309 | ||
| 221 | output_file = os.path.join(output_path, 'aubio-generated.c') | 310 | output_file = os.path.join(output_path, 'aubio-generated.c') |
| 222 | with open(output_file, 'w') as f: | 311 | with open(output_file, 'w') as f: |
| 223 | f.write(out) | 312 | f.write(out) |
| 224 | print ("wrote %s" % output_file ) | 313 | print("wrote %s" % output_file) |
| 225 | sources_list.append(output_file) | 314 | sources_list.append(output_file) |
| 226 | 315 | ||
| 227 | objlist = "".join(["extern PyTypeObject Py_%sType;\n" % p for p in lib]) | 316 | objlist = "".join(["extern PyTypeObject Py_%sType;\n" % p for p in lib]) |
| @@ -239,17 +328,19 @@ void add_generated_objects ( PyObject *m ) · 11 unmodified lines | |||
| 239 | {objlist} | 328 | {objlist} |
| 240 | int generated_objects ( void ); | 329 | int generated_objects ( void ); |
| 241 | void add_generated_objects( PyObject *m ); | 330 | void add_generated_objects( PyObject *m ); |
| 242 | """.format(objlist = objlist) | 331 | """.format(objlist=objlist) |
| 243 | 332 | ||
| 244 | output_file = os.path.join(output_path, 'aubio-generated.h') | 333 | output_file = os.path.join(output_path, 'aubio-generated.h') |
| 245 | with open(output_file, 'w') as f: | 334 | with open(output_file, 'w') as f: |
| 246 | f.write(out) | 335 | f.write(out) |
| 247 | print ("wrote %s" % output_file ) | 336 | print("wrote %s" % output_file) |
| 248 | # no need to add header to list of sources | 337 | # no need to add header to list of sources |
| 249 | 338 | ||
| 250 | return sources_list | 339 | return sorted(sources_list) |
| 251 | 340 | ||
| 252 | if __name__ == '__main__': | 341 | if __name__ == '__main__': |
| 253 | if len(sys.argv) > 1: header = sys.argv[1] | 342 | if len(sys.argv) > 1: |
| 254 | if len(sys.argv) > 2: output_path = sys.argv[2] | 343 | header = sys.argv[1] |
| 344 | if len(sys.argv) > 2: | ||
| 345 | output_path = sys.argv[2] | ||
| 255 | generate_external(header, output_path) | 346 | generate_external(header, output_path) |
| @@ -2,7 +2,9 @@ · 1 unmodified line | |||
| 2 | # | 2 | # |
| 3 | import sys, os, glob, subprocess | 3 | import sys, os, glob, subprocess |
| 4 | import distutils, distutils.command.clean, distutils.dir_util | 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 | # inspired from https://gist.github.com/abergmeier/9488990 | 9 | # inspired from https://gist.github.com/abergmeier/9488990 |
| 8 | def add_packages(packages, ext=None, **kw): | 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 | for package in packages: | 25 | for package in packages: |
| 26 | print("checking for {:s}".format(package)) | ||
| 24 | cmd = ['pkg-config', '--libs', '--cflags', package] | 27 | cmd = ['pkg-config', '--libs', '--cflags', package] |
| 25 | try: | 28 | try: |
| 26 | tokens = subprocess.check_output(cmd) | 29 | tokens = subprocess.check_output(cmd) |
| @@ -54,13 +57,14 @@ def add_local_aubio_lib(ext): · 27 unmodified lines | |||
| 54 | 57 | ||
| 55 | def add_local_aubio_sources(ext): | 58 | def add_local_aubio_sources(ext): |
| 56 | """ build aubio inside python module instead of linking against libaubio """ | 59 | """ build aubio inside python module instead of linking against libaubio """ |
| 57 | print("Warning: libaubio was not built with waf, adding src/") | 60 | print("Info: libaubio was not installed or built locally with waf, adding src/") |
| 58 | # create an empty header, macros will be passed on the command line | 61 | aubio_sources = sorted(glob.glob(os.path.join('src', '**.c'))) |
| 59 | fake_config_header = os.path.join('python', 'ext', 'config.h') | 62 | aubio_sources += sorted(glob.glob(os.path.join('src', '*', '**.c'))) |
| 60 | distutils.file_util.write_file(fake_config_header, "") | ||
| 61 | aubio_sources = glob.glob(os.path.join('src', '**.c')) | ||
| 62 | aubio_sources += glob.glob(os.path.join('src', '*', '**.c')) | ||
| 63 | ext.sources += aubio_sources | 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 | # define macros (waf puts them in build/src/config.h) | 68 | # define macros (waf puts them in build/src/config.h) |
| 65 | for define_macro in ['HAVE_STDLIB_H', 'HAVE_STDIO_H', | 69 | for define_macro in ['HAVE_STDLIB_H', 'HAVE_STDIO_H', |
| 66 | 'HAVE_MATH_H', 'HAVE_STRING_H', | 70 | 'HAVE_MATH_H', 'HAVE_STRING_H', |
| @@ -69,22 +73,31 @@ def add_local_aubio_sources(ext): · 2 unmodified lines | |||
| 69 | 'HAVE_MEMCPY_HACKS']: | 73 | 'HAVE_MEMCPY_HACKS']: |
| 70 | ext.define_macros += [(define_macro, 1)] | 74 | ext.define_macros += [(define_macro, 1)] |
| 71 | 75 | ||
| 76 | def add_external_deps(ext, usedouble = False): | ||
| 72 | # loof for additional packages | 77 | # loof for additional packages |
| 73 | print("Info: looking for *optional* additional packages") | 78 | print("Info: looking for *optional* additional packages") |
| 74 | packages = ['libavcodec', 'libavformat', 'libavutil', 'libavresample', | 79 | packages = ['libavcodec', 'libavformat', 'libavutil', |
| 80 | 'libswresample', 'libavresample', | ||
| 75 | 'jack', | 81 | 'jack', |
| 76 | 'sndfile', 'samplerate', | 82 | 'sndfile', |
| 77 | 'rubberband', | 83 | 'rubberband', |
| 78 | #'fftw3f', | 84 | #'fftw3f', |
| 79 | ] | 85 | ] |
| 86 | # samplerate only works with float | ||
| 87 | if usedouble is False: | ||
| 88 | packages += ['samplerate'] | ||
| 89 | else: | ||
| 90 | print("Info: not adding libsamplerate in double precision mode") | ||
| 80 | add_packages(packages, ext=ext) | 91 | add_packages(packages, ext=ext) |
| 81 | if 'avcodec' in ext.libraries \ | 92 | if 'avcodec' in ext.libraries \ |
| 82 | and 'avformat' in ext.libraries \ | 93 | and 'avformat' in ext.libraries \ |
| 83 | and 'avutil' in ext.libraries \ | 94 | and 'avutil' in ext.libraries: |
| 84 | and 'avresample' in ext.libraries: | 95 | if 'swresample' in ext.libraries: |
| 85 | ext.define_macros += [('HAVE_LIBAV', 1)] | 96 | ext.define_macros += [('HAVE_SWRESAMPLE', 1)] |
| 86 | if 'jack' in ext.libraries: | 97 | elif 'avresample' in ext.libraries: |
| 87 | ext.define_macros += [('HAVE_JACK', 1)] | 98 | ext.define_macros += [('HAVE_AVRESAMPLE', 1)] |
| 99 | if 'swresample' in ext.libraries or 'avresample' in ext.libraries: | ||
| 100 | ext.define_macros += [('HAVE_LIBAV', 1)] | ||
| 88 | if 'sndfile' in ext.libraries: | 101 | if 'sndfile' in ext.libraries: |
| 89 | ext.define_macros += [('HAVE_SNDFILE', 1)] | 102 | ext.define_macros += [('HAVE_SNDFILE', 1)] |
| 90 | if 'samplerate' in ext.libraries: | 103 | if 'samplerate' in ext.libraries: |
| @@ -107,39 +120,78 @@ def add_local_aubio_sources(ext): · 16 unmodified lines | |||
| 107 | 120 | ||
| 108 | ext.define_macros += [('HAVE_WAVWRITE', 1)] | 121 | ext.define_macros += [('HAVE_WAVWRITE', 1)] |
| 109 | ext.define_macros += [('HAVE_WAVREAD', 1)] | 122 | ext.define_macros += [('HAVE_WAVREAD', 1)] |
| 110 | # TODO: | 123 | |
| 111 | # add cblas | 124 | # TODO: add cblas |
| 112 | if 0: | 125 | if 0: |
| 113 | ext.libraries += ['cblas'] | 126 | ext.libraries += ['cblas'] |
| 114 | ext.define_macros += [('HAVE_ATLAS_CBLAS_H', 1)] | 127 | ext.define_macros += [('HAVE_ATLAS_CBLAS_H', 1)] |
| 115 | 128 | ||
| 116 | def add_system_aubio(ext): | 129 | def add_system_aubio(ext): |
| 117 | # use pkg-config to find aubio's location | 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 | if 'aubio' not in ext.libraries: | 133 | if 'aubio' not in ext.libraries: |
| 120 | print("Error: libaubio not found") | 134 | print("Info: aubio " + aubio_version + " was not found by pkg-config") |
| 135 | else: | ||
| 136 | print("Info: using system aubio " + aubio_version + " found in " + ' '.join(ext.library_dirs)) | ||
| 137 | |||
| 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 | class CleanGenerated(distutils.command.clean.clean): | 144 | class CleanGenerated(distutils.command.clean.clean): |
| 123 | def run(self): | 145 | def run(self): |
| 124 | distutils.dir_util.remove_tree(output_path) | 146 | if os.path.isdir(output_path): |
| 125 | distutils.command.clean.clean.run(self) | 147 | distutils.dir_util.remove_tree(output_path) |
| 148 | |||
| 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): | 152 | user_options = _build_ext.user_options + [ |
| 128 | description = 'generate gen/gen-*.c files from ../src/aubio.h' | ||
| 129 | user_options = [ | ||
| 130 | # The format is (long option, short option, description). | 153 | # The format is (long option, short option, description). |
| 131 | ('enable-double', None, 'use HAVE_AUBIO_DOUBLE=1 (default: 0)'), | 154 | ('enable-double', None, 'use HAVE_AUBIO_DOUBLE=1 (default: 0)'), |
| 132 | ] | 155 | ] |
| 133 | 156 | ||
| 134 | def initialize_options(self): | 157 | def initialize_options(self): |
| 158 | _build_ext.initialize_options(self) | ||
| 135 | self.enable_double = False | 159 | self.enable_double = False |
| 136 | 160 | ||
| 137 | def finalize_options(self): | 161 | def finalize_options(self): |
| 162 | _build_ext.finalize_options(self) | ||
| 138 | if self.enable_double: | 163 | if self.enable_double: |
| 139 | self.announce( | 164 | self.announce( |
| 140 | 'will generate code for aubio compiled with HAVE_AUBIO_DOUBLE=1', | 165 | 'will generate code for aubio compiled with HAVE_AUBIO_DOUBLE=1', |
| 141 | level=distutils.log.INFO) | 166 | level=distutils.log.INFO) |
| 142 | 167 | ||
| 143 | def run(self): | 168 | def build_extension(self, extension): |
| 144 | self.announce( 'Generating code', level=distutils.log.INFO) | 169 | if self.enable_double or 'HAVE_AUBIO_DOUBLE' in os.environ: |
| 145 | generated_object_files = generate_external(header, output_path, usedouble=self.enable_double) | 170 | enable_double = True |
| 171 | else: | ||
| 172 | enable_double = False | ||
| 173 | # seack for aubio headers and lib in PKG_CONFIG_PATH | ||
| 174 | add_system_aubio(extension) | ||
| 175 | # the lib was not installed on this system | ||
| 176 | if 'aubio' not in extension.libraries: | ||
| 177 | # use local src/aubio.h | ||
| 178 | if os.path.isfile(os.path.join('src', 'aubio.h')): | ||
| 179 | add_local_aubio_header(extension) | ||
| 180 | add_local_macros(extension, usedouble=enable_double) | ||
| 181 | # look for a local waf build | ||
| 182 | if os.path.isfile(os.path.join('build','src', 'fvec.c.1.o')): | ||
| 183 | add_local_aubio_lib(extension) | ||
| 184 | else: | ||
| 185 | # check for external dependencies | ||
| 186 | add_external_deps(extension, usedouble=enable_double) | ||
| 187 | # force adding libav on windows | ||
| 188 | if os.name == 'nt' and ('WITH_LIBAV' in os.environ \ | ||
| 189 | or 'CONDA_PREFIX' in os.environ): | ||
| 190 | add_libav_on_win(extension) | ||
| 191 | # add libaubio sources and look for optional deps with pkg-config | ||
| 192 | add_local_aubio_sources(extension) | ||
| 193 | # generate files python/gen/*.c, python/gen/aubio-generated.h | ||
| 194 | extension.include_dirs += [ output_path ] | ||
| 195 | extension.sources += generate_external(header, output_path, overwrite = False, | ||
| 196 | usedouble=enable_double) | ||
| 197 | return _build_ext.build_extension(self, extension) | ||