Showing the changes to python (2 of 4 files). Show all
Unified · Split
| @@ -0,0 +1,408 @@ | ||
| 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 aubio | |
| 15 | + | |
| 16 | +def aubio_parser(): | |
| 17 | + epilog = 'use "%(prog)s <command> --help" for more info about each command' | |
| 18 | + parser = argparse.ArgumentParser(epilog=epilog) | |
| 19 | + parser.add_argument('-V', '--version', help="show version", | |
| 20 | + action="store_true", dest="show_version") | |
| 21 | + | |
| 22 | + subparsers = parser.add_subparsers(title='commands', dest='command', | |
| 23 | + metavar="") | |
| 24 | + | |
| 25 | + # onset subcommand | |
| 26 | + subparser = subparsers.add_parser('onset', | |
| 27 | + help='estimate time of onsets (beginning of sound event)', | |
| 28 | + formatter_class = argparse.ArgumentDefaultsHelpFormatter) | |
| 29 | + parser_add_input(subparser) | |
| 30 | + parser_add_buf_hop_size(subparser) | |
| 31 | + helpstr = "onset novelty function" | |
| 32 | + helpstr += " <default|energy|hfc|complex|phase|specdiff|kl|mkl|specflux>" | |
| 33 | + parser_add_method(subparser, helpstr=helpstr) | |
| 34 | + parser_add_threshold(subparser) | |
| 35 | + parser_add_silence(subparser) | |
| 36 | + parser_add_minioi(subparser) | |
| 37 | + parser_add_time_format(subparser) | |
| 38 | + parser_add_verbose_help(subparser) | |
| 39 | + subparser.set_defaults(process=process_onset) | |
| 40 | + | |
| 41 | + # pitch subcommand | |
| 42 | + subparser = subparsers.add_parser('pitch', | |
| 43 | + help='estimate fundamental frequency (monophonic)') | |
| 44 | + parser_add_input(subparser) | |
| 45 | + parser_add_buf_hop_size(subparser, buf_size=2048) | |
| 46 | + helpstr = "pitch detection method <default|yinfft|yin|mcomb|fcomb|schmitt>" | |
| 47 | + parser_add_method(subparser, helpstr=helpstr) | |
| 48 | + parser_add_threshold(subparser) | |
| 49 | + parser_add_silence(subparser) | |
| 50 | + parser_add_time_format(subparser) | |
| 51 | + parser_add_verbose_help(subparser) | |
| 52 | + subparser.set_defaults(process=process_pitch) | |
| 53 | + | |
| 54 | + # beat subcommand | |
| 55 | + subparser = subparsers.add_parser('beat', | |
| 56 | + help='estimate location of beats') | |
| 57 | + parser_add_input(subparser) | |
| 58 | + parser_add_buf_hop_size(subparser, buf_size=1024, hop_size=512) | |
| 59 | + parser_add_time_format(subparser) | |
| 60 | + parser_add_verbose_help(subparser) | |
| 61 | + subparser.set_defaults(process=process_beat) | |
| 62 | + | |
| 63 | + # tempo subcommand | |
| 64 | + subparser = subparsers.add_parser('tempo', | |
| 65 | + help='estimate overall tempo in bpm') | |
| 66 | + parser_add_input(subparser) | |
| 67 | + parser_add_buf_hop_size(subparser, buf_size=1024, hop_size=512) | |
| 68 | + parser_add_time_format(subparser) | |
| 69 | + parser_add_verbose_help(subparser) | |
| 70 | + subparser.set_defaults(process=process_tempo) | |
| 71 | + | |
| 72 | + # notes subcommand | |
| 73 | + subparser = subparsers.add_parser('notes', | |
| 74 | + help='estimate midi-like notes (monophonic)') | |
| 75 | + parser_add_input(subparser) | |
| 76 | + parser_add_buf_hop_size(subparser) | |
| 77 | + parser_add_time_format(subparser) | |
| 78 | + parser_add_verbose_help(subparser) | |
| 79 | + subparser.set_defaults(process=process_notes) | |
| 80 | + | |
| 81 | + # mfcc subcommand | |
| 82 | + subparser = subparsers.add_parser('mfcc', | |
| 83 | + help='extract Mel-Frequency Cepstrum Coefficients') | |
| 84 | + parser_add_input(subparser) | |
| 85 | + parser_add_buf_hop_size(subparser) | |
| 86 | + parser_add_time_format(subparser) | |
| 87 | + parser_add_verbose_help(subparser) | |
| 88 | + subparser.set_defaults(process=process_mfcc) | |
| 89 | + | |
| 90 | + # melbands subcommand | |
| 91 | + subparser = subparsers.add_parser('melbands', | |
| 92 | + help='extract energies in Mel-frequency bands') | |
| 93 | + parser_add_input(subparser) | |
| 94 | + parser_add_buf_hop_size(subparser) | |
| 95 | + parser_add_time_format(subparser) | |
| 96 | + parser_add_verbose_help(subparser) | |
| 97 | + subparser.set_defaults(process=process_melbands) | |
| 98 | + | |
| 99 | + return parser | |
| 100 | + | |
| 101 | +def parser_add_input(parser): | |
| 102 | + parser.add_argument("source_uri", default=None, nargs='?', | |
| 103 | + help="input sound file to analyse", metavar = "<source_uri>") | |
| 104 | + parser.add_argument("-i", "--input", dest = "source_uri2", | |
| 105 | + help="input sound file to analyse", metavar = "<source_uri>") | |
| 106 | + parser.add_argument("-r", "--samplerate", | |
| 107 | + metavar = "<freq>", type=int, | |
| 108 | + action="store", dest="samplerate", default=0, | |
| 109 | + help="samplerate at which the file should be represented") | |
| 110 | + | |
| 111 | +def parser_add_verbose_help(parser): | |
| 112 | + parser.add_argument("-v","--verbose", | |
| 113 | + action="count", dest="verbose", default=1, | |
| 114 | + help="make lots of noise [default]") | |
| 115 | + parser.add_argument("-q","--quiet", | |
| 116 | + action="store_const", dest="verbose", const=0, | |
| 117 | + help="be quiet") | |
| 118 | + | |
| 119 | +def parser_add_buf_hop_size(parser, buf_size=512, hop_size=256): | |
| 120 | + parser.add_argument("-B","--bufsize", | |
| 121 | + action="store", dest="buf_size", default=buf_size, | |
| 122 | + metavar = "<size>", type=int, | |
| 123 | + help="buffer size [default=%d]" % buf_size) | |
| 124 | + parser.add_argument("-H","--hopsize", | |
| 125 | + metavar = "<size>", type=int, | |
| 126 | + action="store", dest="hop_size", default=hop_size, | |
| 127 | + help="overlap size [default=%d]" % hop_size) | |
| 128 | + | |
| 129 | +def parser_add_method(parser, method='default', helpstr='method'): | |
| 130 | + parser.add_argument("-m","--method", | |
| 131 | + metavar = "<method>", type=str, | |
| 132 | + action="store", dest="method", default=method, | |
| 133 | + help="%s [default=%s]" % (helpstr, method)) | |
| 134 | + | |
| 135 | +def parser_add_threshold(parser, default=None): | |
| 136 | + parser.add_argument("-t","--threshold", | |
| 137 | + metavar = "<threshold>", type=float, | |
| 138 | + action="store", dest="threshold", default=default, | |
| 139 | + help="threshold [default=%s]" % default) | |
| 140 | + | |
| 141 | +def parser_add_silence(parser): | |
| 142 | + parser.add_argument("-s", "--silence", | |
| 143 | + metavar = "<value>", type=float, | |
| 144 | + action="store", dest="silence", default=-70, | |
| 145 | + help="silence threshold") | |
| 146 | + | |
| 147 | +def parser_add_minioi(parser): | |
| 148 | + parser.add_argument("-M", "--minioi", | |
| 149 | + metavar = "<value>", type=str, | |
| 150 | + action="store", dest="minioi", default="12ms", | |
| 151 | + help="minimum Inter-Onset Interval") | |
| 152 | + | |
| 153 | +def parser_add_time_format(parser): | |
| 154 | + helpstr = "select time values output format (samples, ms, seconds)" | |
| 155 | + helpstr += " [default=seconds]" | |
| 156 | + parser.add_argument("-T", "--time-format", | |
| 157 | + metavar='format', | |
| 158 | + dest="time_format", | |
| 159 | + default=None, | |
| 160 | + help=helpstr) | |
| 161 | + | |
| 162 | +# some utilities | |
| 163 | + | |
| 164 | +def samples2seconds(n_frames, samplerate): | |
| 165 | + return "%f\t" % (n_frames / float(samplerate)) | |
| 166 | + | |
| 167 | +def samples2milliseconds(n_frames, samplerate): | |
| 168 | + return "%f\t" % (1000. * n_frames / float(samplerate)) | |
| 169 | + | |
| 170 | +def samples2samples(n_frames, samplerate): | |
| 171 | + return "%d\t" % n_frames | |
| 172 | + | |
| 173 | +def timefunc(mode): | |
| 174 | + if mode is None or mode == 'seconds' or mode == 's': | |
| 175 | + return samples2seconds | |
| 176 | + elif mode == 'ms' or mode == 'milliseconds': | |
| 177 | + return samples2milliseconds | |
| 178 | + elif mode == 'samples': | |
| 179 | + return samples2samples | |
| 180 | + else: | |
| 181 | + raise ValueError('invalid time format %s' % mode) | |
| 182 | + | |
| 183 | +# definition of processing classes | |
| 184 | + | |
| 185 | +class default_process(object): | |
| 186 | + def __init__(self, args): | |
| 187 | + if 'time_format' in args: | |
| 188 | + self.time2string = timefunc(args.time_format) | |
| 189 | + if args.verbose > 2 and hasattr(self, 'options'): | |
| 190 | + name = type(self).__name__.split('_')[1] | |
| 191 | + optstr = ' '.join(['running', name, 'with options', repr(self.options), '\n']) | |
| 192 | + sys.stderr.write(optstr) | |
| 193 | + def flush(self, n_frames, samplerate): | |
| 194 | + # optionally called at the end of process | |
| 195 | + pass | |
| 196 | + | |
| 197 | + def parse_options(self, args, valid_opts): | |
| 198 | + # get any valid options found in a dictionnary of arguments | |
| 199 | + options = {k :v for k,v in vars(args).items() if k in valid_opts} | |
| 200 | + self.options = options | |
| 201 | + | |
| 202 | + def remap_pvoc_options(self, options): | |
| 203 | + # FIXME: we need to remap buf_size to win_s, hop_size to hop_s | |
| 204 | + # adjust python/ext/py-phasevoc.c to understand buf_size/hop_size | |
| 205 | + if 'buf_size' in options: | |
| 206 | + options['win_s'] = options['buf_size'] | |
| 207 | + del options['buf_size'] | |
| 208 | + if 'hop_size' in options: | |
| 209 | + options['hop_s'] = options['hop_size'] | |
| 210 | + del options['hop_size'] | |
| 211 | + self.options = options | |
| 212 | + | |
| 213 | +class process_onset(default_process): | |
| 214 | + valid_opts = ['method', 'hop_size', 'buf_size', 'samplerate'] | |
| 215 | + def __init__(self, args): | |
| 216 | + self.parse_options(args, self.valid_opts) | |
| 217 | + self.onset = aubio.onset(**self.options) | |
| 218 | + if args.threshold is not None: | |
| 219 | + self.onset.set_threshold(args.threshold) | |
| 220 | + if args.minioi: | |
| 221 | + if args.minioi.endswith('ms'): | |
| 222 | + self.onset.set_minioi_ms(float(args.minioi[:-2])) | |
| 223 | + elif args.minioi.endswith('s'): | |
| 224 | + self.onset.set_minioi_s(float(args.minioi[:-1])) | |
| 225 | + else: | |
| 226 | + self.onset.set_minioi(int(args.minioi)) | |
| 227 | + if args.silence: | |
| 228 | + self.onset.set_silence(args.silence) | |
| 229 | + super(process_onset, self).__init__(args) | |
| 230 | + def __call__(self, block): | |
| 231 | + return self.onset(block) | |
| 232 | + def repr_res(self, res, frames_read, samplerate): | |
| 233 | + if res[0] != 0: | |
| 234 | + outstr = self.time2string(self.onset.get_last(), samplerate) | |
| 235 | + sys.stdout.write(outstr + '\n') | |
| 236 | + | |
| 237 | +class process_pitch(default_process): | |
| 238 | + valid_opts = ['method', 'hop_size', 'buf_size', 'samplerate'] | |
| 239 | + def __init__(self, args): | |
| 240 | + self.parse_options(args, self.valid_opts) | |
| 241 | + self.pitch = aubio.pitch(**self.options) | |
| 242 | + if args.threshold is not None: | |
| 243 | + self.pitch.set_tolerance(args.threshold) | |
| 244 | + if args.silence is not None: | |
| 245 | + self.pitch.set_silence(args.silence) | |
| 246 | + super(process_pitch, self).__init__(args) | |
| 247 | + def __call__(self, block): | |
| 248 | + return self.pitch(block) | |
| 249 | + def repr_res(self, res, frames_read, samplerate): | |
| 250 | + fmt_out = self.time2string(frames_read, samplerate) | |
| 251 | + sys.stdout.write(fmt_out + "%.6f\n" % res[0]) | |
| 252 | + | |
| 253 | +class process_beat(default_process): | |
| 254 | + valid_opts = ['method', 'hop_size', 'buf_size', 'samplerate'] | |
| 255 | + def __init__(self, args): | |
| 256 | + self.parse_options(args, self.valid_opts) | |
| 257 | + self.tempo = aubio.tempo(**self.options) | |
| 258 | + super(process_beat, self).__init__(args) | |
| 259 | + def __call__(self, block): | |
| 260 | + return self.tempo(block) | |
| 261 | + def repr_res(self, res, frames_read, samplerate): | |
| 262 | + if res[0] != 0: | |
| 263 | + outstr = self.time2string(self.tempo.get_last(), samplerate) | |
| 264 | + sys.stdout.write(outstr + '\n') | |
| 265 | + | |
| 266 | +class process_tempo(process_beat): | |
| 267 | + def __init__(self, args): | |
| 268 | + super(process_tempo, self).__init__(args) | |
| 269 | + self.beat_locations = [] | |
| 270 | + def repr_res(self, res, frames_read, samplerate): | |
| 271 | + if res[0] != 0: | |
| 272 | + self.beat_locations.append(self.tempo.get_last_s()) | |
| 273 | + def flush(self, frames_read, samplerate): | |
| 274 | + import numpy as np | |
| 275 | + if len(self.beat_locations) < 2: | |
| 276 | + outstr = "unknown bpm" | |
| 277 | + else: | |
| 278 | + bpms = 60./ np.diff(self.beat_locations) | |
| 279 | + median_bpm = np.mean(bpms) | |
| 280 | + if len(self.beat_locations) < 10: | |
| 281 | + outstr = "%.2f bpm (uncertain)" % median_bpm | |
| 282 | + else: | |
| 283 | + outstr = "%.2f bpm" % median_bpm | |
| 284 | + sys.stdout.write(outstr + '\n') | |
| 285 | + | |
| 286 | +class process_notes(default_process): | |
| 287 | + valid_opts = ['method', 'hop_size', 'buf_size', 'samplerate'] | |
| 288 | + def __init__(self, args): | |
| 289 | + self.parse_options(args, self.valid_opts) | |
| 290 | + self.notes = aubio.notes(**self.options) | |
| 291 | + super(process_notes, self).__init__(args) | |
| 292 | + def __call__(self, block): | |
| 293 | + return self.notes(block) | |
| 294 | + def repr_res(self, res, frames_read, samplerate): | |
| 295 | + if res[2] != 0: # note off | |
| 296 | + fmt_out = self.time2string(frames_read, samplerate) | |
| 297 | + sys.stdout.write(fmt_out + '\n') | |
| 298 | + if res[0] != 0: # note on | |
| 299 | + lastmidi = res[0] | |
| 300 | + fmt_out = "%f\t" % lastmidi | |
| 301 | + fmt_out += self.time2string(frames_read, samplerate) | |
| 302 | + sys.stdout.write(fmt_out) # + '\t') | |
| 303 | + def flush(self, frames_read, samplerate): | |
| 304 | + eof = self.time2string(frames_read, samplerate) | |
| 305 | + sys.stdout.write(eof + '\n') | |
| 306 | + | |
| 307 | +class process_mfcc(default_process): | |
| 308 | + def __init__(self, args): | |
| 309 | + valid_opts1 = ['hop_size', 'buf_size'] | |
| 310 | + self.parse_options(args, valid_opts1) | |
| 311 | + self.remap_pvoc_options(self.options) | |
| 312 | + self.pv = aubio.pvoc(**self.options) | |
| 313 | + | |
| 314 | + valid_opts2 = ['buf_size', 'n_filters', 'n_coeffs', 'samplerate'] | |
| 315 | + self.parse_options(args, valid_opts2) | |
| 316 | + self.mfcc = aubio.mfcc(**self.options) | |
| 317 | + | |
| 318 | + # remember all options | |
| 319 | + self.parse_options(args, list(set(valid_opts1 + valid_opts2))) | |
| 320 | + | |
| 321 | + super(process_mfcc, self).__init__(args) | |
| 322 | + | |
| 323 | + def __call__(self, block): | |
| 324 | + fftgrain = self.pv(block) | |
| 325 | + return self.mfcc(fftgrain) | |
| 326 | + def repr_res(self, res, frames_read, samplerate): | |
| 327 | + fmt_out = self.time2string(frames_read, samplerate) | |
| 328 | + fmt_out += ' '.join(["% 9.7f" % f for f in res.tolist()]) | |
| 329 | + sys.stdout.write(fmt_out + '\n') | |
| 330 | + | |
| 331 | +class process_melbands(default_process): | |
| 332 | + def __init__(self, args): | |
| 333 | + self.args = args | |
| 334 | + valid_opts = ['hop_size', 'buf_size'] | |
| 335 | + self.parse_options(args, valid_opts) | |
| 336 | + self.remap_pvoc_options(self.options) | |
| 337 | + self.pv = aubio.pvoc(**self.options) | |
| 338 | + | |
| 339 | + valid_opts = ['buf_size', 'n_filters'] | |
| 340 | + self.parse_options(args, valid_opts) | |
| 341 | + self.remap_pvoc_options(self.options) | |
| 342 | + self.filterbank = aubio.filterbank(**self.options) | |
| 343 | + self.filterbank.set_mel_coeffs_slaney(args.samplerate) | |
| 344 | + | |
| 345 | + super(process_melbands, self).__init__(args) | |
| 346 | + def __call__(self, block): | |
| 347 | + fftgrain = self.pv(block) | |
| 348 | + return self.filterbank(fftgrain) | |
| 349 | + def repr_res(self, res, frames_read, samplerate): | |
| 350 | + fmt_out = self.time2string(frames_read, samplerate) | |
| 351 | + fmt_out += ' '.join(["% 9.7f" % f for f in res.tolist()]) | |
| 352 | + sys.stdout.write(fmt_out + '\n') | |
| 353 | + | |
| 354 | +def main(): | |
| 355 | + parser = aubio_parser() | |
| 356 | + args = parser.parse_args() | |
| 357 | + if 'show_version' in args and args.show_version: | |
| 358 | + sys.stdout.write('aubio version ' + aubio.version + '\n') | |
| 359 | + sys.exit(0) | |
| 360 | + elif 'verbose' in args and args.verbose > 3: | |
| 361 | + sys.stderr.write('aubio version ' + aubio.version + '\n') | |
| 362 | + if 'command' not in args or args.command is None: | |
| 363 | + # no command given, print help and return 1 | |
| 364 | + parser.print_help() | |
| 365 | + sys.exit(1) | |
| 366 | + elif not args.source_uri and not args.source_uri2: | |
| 367 | + sys.stderr.write("Error: a source is required\n") | |
| 368 | + parser.print_help() | |
| 369 | + sys.exit(1) | |
| 370 | + elif args.source_uri2 is not None: | |
| 371 | + args.source_uri = args.source_uri2 | |
| 372 | + try: | |
| 373 | + # open source_uri | |
| 374 | + with aubio.source(args.source_uri, hop_size=args.hop_size, | |
| 375 | + samplerate=args.samplerate) as a_source: | |
| 376 | + # always update args.samplerate to native samplerate, in case | |
| 377 | + # source was opened with args.samplerate=0 | |
| 378 | + args.samplerate = a_source.samplerate | |
| 379 | + # create the processor for this subcommand | |
| 380 | + processor = args.process(args) | |
| 381 | + frames_read = 0 | |
| 382 | + while True: | |
| 383 | + # read new block from source | |
| 384 | + block, read = a_source() | |
| 385 | + # execute processor on this block | |
| 386 | + res = processor(block) | |
| 387 | + # print results for this block | |
| 388 | + if args.verbose > 0: | |
| 389 | + processor.repr_res(res, frames_read, a_source.samplerate) | |
| 390 | + # increment total number of frames read | |
| 391 | + frames_read += read | |
| 392 | + # exit loop at end of file | |
| 393 | + if read < a_source.hop_size: break | |
| 394 | + # flush the processor if needed | |
| 395 | + processor.flush(frames_read, a_source.samplerate) | |
| 396 | + if args.verbose > 1: | |
| 397 | + fmt_string = "read {:.2f}s" | |
| 398 | + fmt_string += " ({:d} samples in {:d} blocks of {:d})" | |
| 399 | + fmt_string += " from {:s} at {:d}Hz\n" | |
| 400 | + sys.stderr.write(fmt_string.format( | |
| 401 | + frames_read/float(a_source.samplerate), | |
| 402 | + frames_read, | |
| 403 | + frames_read // a_source.hop_size + 1, | |
| 404 | + a_source.hop_size, | |
| 405 | + a_source.uri, | |
| 406 | + a_source.samplerate)) | |
| 407 | + except KeyboardInterrupt as e: | |
| 408 | + sys.exit(1) | |
| @@ -6,11 +6,10 @@ · 5 unmodified lines | ||
| 6 | 6 | |
| 7 | 7 | import sys |
| 8 | 8 | |
| 9 | −usage = "usage: %s [options] -i soundfile" % sys.argv[0] | |
| 10 | −usage += "\n help: %s -h" % sys.argv[0] | |
| 11 | − | |
| 12 | 9 | def parse_args(): |
| 13 | 10 | from optparse import OptionParser |
| 11 | + usage = "usage: %s [options] -i soundfile" % sys.argv[0] | |
| 12 | + usage += "\n help: %s -h" % sys.argv[0] | |
| 14 | 13 | parser = OptionParser(usage=usage) |
| 15 | 14 | parser.add_option("-i", "--input", action = "store", dest = "source_file", |
| 16 | 15 | help="input sound file to analyse", metavar = "<source_file>") |
| @@ -133,7 +132,6 @@ def parse_args(): · 116 unmodified lines | ||
| 133 | 132 | help="be quiet") |
| 134 | 133 | (options, args) = parser.parse_args() |
| 135 | 134 | if not options.source_file: |
| 136 | − import os.path | |
| 137 | 135 | if len(args) == 1: |
| 138 | 136 | options.source_file = args[0] |
| 139 | 137 | else: |
| @@ -141,7 +139,7 @@ def parse_args(): · 1 unmodified line | ||
| 141 | 139 | sys.exit(1) |
| 142 | 140 | return options, args |
| 143 | 141 | |
| 144 | −if __name__ == '__main__': | |
| 142 | +def main(): | |
| 145 | 143 | options, args = parse_args() |
| 146 | 144 | |
| 147 | 145 | hopsize = options.hopsize |
| @@ -149,7 +147,7 @@ if __name__ == '__main__': · 1 unmodified line | ||
| 149 | 147 | samplerate = options.samplerate |
| 150 | 148 | source_file = options.source_file |
| 151 | 149 | |
| 152 | − from aubio import onset, tempo, source, sink | |
| 150 | + from aubio import onset, tempo, source | |
| 153 | 151 | |
| 154 | 152 | s = source(source_file, samplerate, hopsize) |
| 155 | 153 | if samplerate == 0: samplerate = s.get_samplerate() |