source: python/lib/aubio/cmd.py @ 8a3acad

feature/autosinkfeature/constantqfeature/pitchshiftfeature/pydocstringsfeature/timestretch
Last change on this file since 8a3acad was 8a3acad, checked in by Paul Brossier <piem@piem.org>, 2 years ago

python/lib/aubio/cmd.py: add help subcommand

  • Property mode set to 100644
File size: 19.0 KB
Line 
1#! /usr/bin/env python
2# -*- coding: utf-8 -*-
3
4"""aubio command line tool
5
6This file was written by Paul Brossier <piem@aubio.org> and is released under
7the GNU/GPL v3.
8
9Note: this script is mostly about parsing command line arguments. For more
10readable code examples, check out the `python/demos` folder."""
11
12import sys
13import argparse
14import aubio
15
16def 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    parser_add_subcommand_help(subparsers)
26
27    parser_add_subcommand_onset(subparsers)
28    parser_add_subcommand_pitch(subparsers)
29    parser_add_subcommand_beat(subparsers)
30    parser_add_subcommand_tempo(subparsers)
31    parser_add_subcommand_notes(subparsers)
32    parser_add_subcommand_mfcc(subparsers)
33    parser_add_subcommand_melbands(subparsers)
34    parser_add_subcommand_quiet(subparsers)
35
36    return parser
37
38def parser_add_subcommand_help(subparsers):
39    # global help subcommand
40    subparsers.add_parser('help',
41            help='show help message',
42            formatter_class = argparse.ArgumentDefaultsHelpFormatter)
43
44def parser_add_subcommand_onset(subparsers):
45    # onset subcommand
46    subparser = subparsers.add_parser('onset',
47            help='estimate time of onsets (beginning of sound event)',
48            formatter_class = argparse.ArgumentDefaultsHelpFormatter)
49    parser_add_input(subparser)
50    parser_add_buf_hop_size(subparser)
51    helpstr = "onset novelty function"
52    helpstr += " <default|energy|hfc|complex|phase|specdiff|kl|mkl|specflux>"
53    parser_add_method(subparser, helpstr=helpstr)
54    parser_add_threshold(subparser)
55    parser_add_silence(subparser)
56    parser_add_minioi(subparser)
57    parser_add_time_format(subparser)
58    parser_add_verbose_help(subparser)
59    subparser.set_defaults(process=process_onset)
60
61def parser_add_subcommand_pitch(subparsers):
62    # pitch subcommand
63    subparser = subparsers.add_parser('pitch',
64            help='estimate fundamental frequency (monophonic)')
65    parser_add_input(subparser)
66    parser_add_buf_hop_size(subparser, buf_size=2048)
67    helpstr = "pitch detection method <default|yinfft|yin|mcomb|fcomb|schmitt>"
68    parser_add_method(subparser, helpstr=helpstr)
69    parser_add_threshold(subparser)
70    parser_add_pitch_unit(subparser)
71    parser_add_silence(subparser)
72    parser_add_time_format(subparser)
73    parser_add_verbose_help(subparser)
74    subparser.set_defaults(process=process_pitch)
75
76def parser_add_subcommand_beat(subparsers):
77    # beat subcommand
78    subparser = subparsers.add_parser('beat',
79            help='estimate location of beats')
80    parser_add_input(subparser)
81    parser_add_buf_hop_size(subparser, buf_size=1024, hop_size=512)
82    parser_add_time_format(subparser)
83    parser_add_verbose_help(subparser)
84    subparser.set_defaults(process=process_beat)
85
86def parser_add_subcommand_tempo(subparsers):
87    # tempo subcommand
88    subparser = subparsers.add_parser('tempo',
89            help='estimate overall tempo in bpm')
90    parser_add_input(subparser)
91    parser_add_buf_hop_size(subparser, buf_size=1024, hop_size=512)
92    parser_add_time_format(subparser)
93    parser_add_verbose_help(subparser)
94    subparser.set_defaults(process=process_tempo)
95
96def parser_add_subcommand_notes(subparsers):
97    # notes subcommand
98    subparser = subparsers.add_parser('notes',
99            help='estimate midi-like notes (monophonic)')
100    parser_add_input(subparser)
101    parser_add_buf_hop_size(subparser)
102    parser_add_time_format(subparser)
103    parser_add_verbose_help(subparser)
104    subparser.set_defaults(process=process_notes)
105
106def parser_add_subcommand_mfcc(subparsers):
107    # mfcc subcommand
108    subparser = subparsers.add_parser('mfcc',
109            help='extract Mel-Frequency Cepstrum Coefficients')
110    parser_add_input(subparser)
111    parser_add_buf_hop_size(subparser)
112    parser_add_time_format(subparser)
113    parser_add_verbose_help(subparser)
114    subparser.set_defaults(process=process_mfcc)
115
116def parser_add_subcommand_melbands(subparsers):
117    # melbands subcommand
118    subparser = subparsers.add_parser('melbands',
119            help='extract energies in Mel-frequency bands')
120    parser_add_input(subparser)
121    parser_add_buf_hop_size(subparser)
122    parser_add_time_format(subparser)
123    parser_add_verbose_help(subparser)
124    subparser.set_defaults(process=process_melbands)
125
126def parser_add_subcommand_quiet(subparsers):
127    # quiet subcommand
128    subparser = subparsers.add_parser('quiet',
129            help='extract timestamps of quiet and loud regions')
130    parser_add_input(subparser)
131    parser_add_hop_size(subparser)
132    parser_add_silence(subparser)
133    parser_add_time_format(subparser)
134    parser_add_verbose_help(subparser)
135    subparser.set_defaults(process=process_quiet)
136
137def parser_add_input(parser):
138    parser.add_argument("source_uri", default=None, nargs='?',
139            help="input sound file to analyse", metavar = "<source_uri>")
140    parser.add_argument("-i", "--input", dest = "source_uri2",
141            help="input sound file to analyse", metavar = "<source_uri>")
142    parser.add_argument("-r", "--samplerate",
143            metavar = "<freq>", type=int,
144            action="store", dest="samplerate", default=0,
145            help="samplerate at which the file should be represented")
146
147def parser_add_verbose_help(parser):
148    parser.add_argument("-v","--verbose",
149            action="count", dest="verbose", default=1,
150            help="make lots of noise [default]")
151    parser.add_argument("-q","--quiet",
152            action="store_const", dest="verbose", const=0,
153            help="be quiet")
154
155def parser_add_buf_hop_size(parser, buf_size=512, hop_size=256):
156    parser_add_buf_size(parser, buf_size=buf_size)
157    parser_add_hop_size(parser, hop_size=hop_size)
158
159def parser_add_buf_size(parser, buf_size=512):
160    parser.add_argument("-B","--bufsize",
161            action="store", dest="buf_size", default=buf_size,
162            metavar = "<size>", type=int,
163            help="buffer size [default=%d]" % buf_size)
164
165def parser_add_hop_size(parser, hop_size=256):
166    parser.add_argument("-H","--hopsize",
167            metavar = "<size>", type=int,
168            action="store", dest="hop_size", default=hop_size,
169            help="overlap size [default=%d]" % hop_size)
170
171def parser_add_method(parser, method='default', helpstr='method'):
172    parser.add_argument("-m","--method",
173            metavar = "<method>", type=str,
174            action="store", dest="method", default=method,
175            help="%s [default=%s]" % (helpstr, method))
176
177def parser_add_threshold(parser, default=None):
178    parser.add_argument("-t","--threshold",
179            metavar = "<threshold>", type=float,
180            action="store", dest="threshold", default=default,
181            help="threshold [default=%s]" % default)
182
183def parser_add_silence(parser):
184    parser.add_argument("-s", "--silence",
185            metavar = "<value>", type=float,
186            action="store", dest="silence", default=-70,
187            help="silence threshold")
188
189def parser_add_minioi(parser):
190    parser.add_argument("-M", "--minioi",
191            metavar = "<value>", type=str,
192            action="store", dest="minioi", default="12ms",
193            help="minimum Inter-Onset Interval")
194
195def parser_add_pitch_unit(parser, default="Hz"):
196    help_str = "frequency unit, should be one of Hz, midi, bin, cent"
197    help_str += " [default=%s]" % default
198    parser.add_argument("-u", "--pitch-unit",
199            metavar = "<value>", type=str,
200            action="store", dest="pitch_unit", default=default,
201            help=help_str)
202
203def parser_add_time_format(parser):
204    helpstr = "select time values output format (samples, ms, seconds)"
205    helpstr += " [default=seconds]"
206    parser.add_argument("-T", "--time-format",
207             metavar='format',
208             dest="time_format",
209             default=None,
210             help=helpstr)
211
212# some utilities
213
214def samples2seconds(n_frames, samplerate):
215    return "%f\t" % (n_frames / float(samplerate))
216
217def samples2milliseconds(n_frames, samplerate):
218    return "%f\t" % (1000. * n_frames / float(samplerate))
219
220def samples2samples(n_frames, _samplerate):
221    return "%d\t" % n_frames
222
223def timefunc(mode):
224    if mode is None or mode == 'seconds' or mode == 's':
225        return samples2seconds
226    elif mode == 'ms' or mode == 'milliseconds':
227        return samples2milliseconds
228    elif mode == 'samples':
229        return samples2samples
230    else:
231        raise ValueError("invalid time format '%s'" % mode)
232
233# definition of processing classes
234
235class default_process(object):
236    def __init__(self, args):
237        if 'time_format' in args:
238            self.time2string = timefunc(args.time_format)
239        if args.verbose > 2 and hasattr(self, 'options'):
240            name = type(self).__name__.split('_')[1]
241            optstr = ' '.join(['running', name, 'with options', repr(self.options), '\n'])
242            sys.stderr.write(optstr)
243    def flush(self, frames_read, samplerate):
244        # optionally called at the end of process
245        pass
246
247    def parse_options(self, args, valid_opts):
248        # get any valid options found in a dictionnary of arguments
249        options = {k :v for k,v in vars(args).items() if k in valid_opts}
250        self.options = options
251
252    def remap_pvoc_options(self, options):
253        # FIXME: we need to remap buf_size to win_s, hop_size to hop_s
254        # adjust python/ext/py-phasevoc.c to understand buf_size/hop_size
255        if 'buf_size' in options:
256            options['win_s'] = options['buf_size']
257            del options['buf_size']
258        if 'hop_size' in options:
259            options['hop_s'] = options['hop_size']
260            del options['hop_size']
261        self.options = options
262
263class process_onset(default_process):
264    valid_opts = ['method', 'hop_size', 'buf_size', 'samplerate']
265    def __init__(self, args):
266        self.parse_options(args, self.valid_opts)
267        self.onset = aubio.onset(**self.options)
268        if args.threshold is not None:
269            self.onset.set_threshold(args.threshold)
270        if args.minioi:
271            if args.minioi.endswith('ms'):
272                self.onset.set_minioi_ms(float(args.minioi[:-2]))
273            elif args.minioi.endswith('s'):
274                self.onset.set_minioi_s(float(args.minioi[:-1]))
275            else:
276                self.onset.set_minioi(int(args.minioi))
277        if args.silence:
278            self.onset.set_silence(args.silence)
279        super(process_onset, self).__init__(args)
280    def __call__(self, block):
281        return self.onset(block)
282    def repr_res(self, res, _frames_read, samplerate):
283        if res[0] != 0:
284            outstr = self.time2string(self.onset.get_last(), samplerate)
285            sys.stdout.write(outstr + '\n')
286
287class process_pitch(default_process):
288    valid_opts = ['method', 'hop_size', 'buf_size', 'samplerate']
289    def __init__(self, args):
290        self.parse_options(args, self.valid_opts)
291        self.pitch = aubio.pitch(**self.options)
292        if args.pitch_unit is not None:
293            self.pitch.set_unit(args.pitch_unit)
294        if args.threshold is not None:
295            self.pitch.set_tolerance(args.threshold)
296        if args.silence is not None:
297            self.pitch.set_silence(args.silence)
298        super(process_pitch, self).__init__(args)
299    def __call__(self, block):
300        return self.pitch(block)
301    def repr_res(self, res, frames_read, samplerate):
302        fmt_out = self.time2string(frames_read, samplerate)
303        sys.stdout.write(fmt_out + "%.6f\n" % res[0])
304
305class process_beat(default_process):
306    valid_opts = ['method', 'hop_size', 'buf_size', 'samplerate']
307    def __init__(self, args):
308        self.parse_options(args, self.valid_opts)
309        self.tempo = aubio.tempo(**self.options)
310        super(process_beat, self).__init__(args)
311    def __call__(self, block):
312        return self.tempo(block)
313    def repr_res(self, res, _frames_read, samplerate):
314        if res[0] != 0:
315            outstr = self.time2string(self.tempo.get_last(), samplerate)
316            sys.stdout.write(outstr + '\n')
317
318class process_tempo(process_beat):
319    def __init__(self, args):
320        super(process_tempo, self).__init__(args)
321        self.beat_locations = []
322    def repr_res(self, res, _frames_read, samplerate):
323        if res[0] != 0:
324            self.beat_locations.append(self.tempo.get_last_s())
325    def flush(self, frames_read, samplerate):
326        import numpy as np
327        if len(self.beat_locations) < 2:
328            outstr = "unknown bpm"
329        else:
330            bpms = 60./ np.diff(self.beat_locations)
331            median_bpm = np.mean(bpms)
332            if len(self.beat_locations) < 10:
333                outstr = "%.2f bpm (uncertain)" % median_bpm
334            else:
335                outstr = "%.2f bpm" % median_bpm
336        sys.stdout.write(outstr + '\n')
337
338class process_notes(default_process):
339    valid_opts = ['method', 'hop_size', 'buf_size', 'samplerate']
340    def __init__(self, args):
341        self.parse_options(args, self.valid_opts)
342        self.notes = aubio.notes(**self.options)
343        super(process_notes, self).__init__(args)
344    def __call__(self, block):
345        return self.notes(block)
346    def repr_res(self, res, frames_read, samplerate):
347        if res[2] != 0: # note off
348            fmt_out = self.time2string(frames_read, samplerate)
349            sys.stdout.write(fmt_out + '\n')
350        if res[0] != 0: # note on
351            lastmidi = res[0]
352            fmt_out = "%f\t" % lastmidi
353            fmt_out += self.time2string(frames_read, samplerate)
354            sys.stdout.write(fmt_out) # + '\t')
355    def flush(self, frames_read, samplerate):
356        eof = self.time2string(frames_read, samplerate)
357        sys.stdout.write(eof + '\n')
358
359class process_mfcc(default_process):
360    def __init__(self, args):
361        valid_opts1 = ['hop_size', 'buf_size']
362        self.parse_options(args, valid_opts1)
363        self.remap_pvoc_options(self.options)
364        self.pv = aubio.pvoc(**self.options)
365
366        valid_opts2 = ['buf_size', 'n_filters', 'n_coeffs', 'samplerate']
367        self.parse_options(args, valid_opts2)
368        self.mfcc = aubio.mfcc(**self.options)
369
370        # remember all options
371        self.parse_options(args, list(set(valid_opts1 + valid_opts2)))
372
373        super(process_mfcc, self).__init__(args)
374
375    def __call__(self, block):
376        fftgrain = self.pv(block)
377        return self.mfcc(fftgrain)
378    def repr_res(self, res, frames_read, samplerate):
379        fmt_out = self.time2string(frames_read, samplerate)
380        fmt_out += ' '.join(["% 9.7f" % f for f in res.tolist()])
381        sys.stdout.write(fmt_out + '\n')
382
383class process_melbands(default_process):
384    def __init__(self, args):
385        self.args = args
386        valid_opts = ['hop_size', 'buf_size']
387        self.parse_options(args, valid_opts)
388        self.remap_pvoc_options(self.options)
389        self.pv = aubio.pvoc(**self.options)
390
391        valid_opts = ['buf_size', 'n_filters']
392        self.parse_options(args, valid_opts)
393        self.remap_pvoc_options(self.options)
394        self.filterbank = aubio.filterbank(**self.options)
395        self.filterbank.set_mel_coeffs_slaney(args.samplerate)
396
397        super(process_melbands, self).__init__(args)
398    def __call__(self, block):
399        fftgrain = self.pv(block)
400        return self.filterbank(fftgrain)
401    def repr_res(self, res, frames_read, samplerate):
402        fmt_out = self.time2string(frames_read, samplerate)
403        fmt_out += ' '.join(["% 9.7f" % f for f in res.tolist()])
404        sys.stdout.write(fmt_out + '\n')
405
406class process_quiet(default_process):
407    def __init__(self, args):
408        self.args = args
409        valid_opts = ['hop_size', 'silence']
410        self.parse_options(args, valid_opts)
411        self.wassilence = 1
412
413        if args.silence is not None:
414            self.silence = args.silence
415        super(process_quiet, self).__init__(args)
416
417    def __call__(self, block):
418        if aubio.silence_detection(block, self.silence) == 1:
419            if self.wassilence != 1:
420                self.wassilence = 1
421                return 2 # newly found silence
422            return 1 # silence again
423        else:
424            if self.wassilence != 0:
425                self.wassilence = 0
426                return -1 # newly found noise
427            return 0 # noise again
428
429    def repr_res(self, res, frames_read, samplerate):
430        fmt_out = None
431        if res == -1:
432            fmt_out = "NOISY: "
433        if res == 2:
434            fmt_out = "QUIET: "
435        if fmt_out is not None:
436            fmt_out += self.time2string(frames_read, samplerate)
437            sys.stdout.write(fmt_out + '\n')
438
439def main():
440    parser = aubio_parser()
441    args = parser.parse_args()
442    if 'show_version' in args and args.show_version:
443        sys.stdout.write('aubio version ' + aubio.version + '\n')
444        sys.exit(0)
445    elif 'verbose' in args and args.verbose > 3:
446        sys.stderr.write('aubio version ' + aubio.version + '\n')
447    if 'command' not in args or args.command is None or args.command in ['help']:
448        # no command given, print help and return 1
449        parser.print_help()
450        if args.command and args.command in ['help']:
451            sys.exit(0)
452        else:
453            sys.exit(1)
454    elif not args.source_uri and not args.source_uri2:
455        sys.stderr.write("Error: a source is required\n")
456        parser.print_help()
457        sys.exit(1)
458    elif args.source_uri2 is not None:
459        args.source_uri = args.source_uri2
460    try:
461        # open source_uri
462        with aubio.source(args.source_uri, hop_size=args.hop_size,
463                samplerate=args.samplerate) as a_source:
464            # always update args.samplerate to native samplerate, in case
465            # source was opened with args.samplerate=0
466            args.samplerate = a_source.samplerate
467            # create the processor for this subcommand
468            processor = args.process(args)
469            frames_read = 0
470            while True:
471                # read new block from source
472                block, read = a_source()
473                # execute processor on this block
474                res = processor(block)
475                # print results for this block
476                if args.verbose > 0:
477                    processor.repr_res(res, frames_read, a_source.samplerate)
478                # increment total number of frames read
479                frames_read += read
480                # exit loop at end of file
481                if read < a_source.hop_size: break
482            # flush the processor if needed
483            processor.flush(frames_read, a_source.samplerate)
484            if args.verbose > 1:
485                fmt_string = "read {:.2f}s"
486                fmt_string += " ({:d} samples in {:d} blocks of {:d})"
487                fmt_string += " from {:s} at {:d}Hz\n"
488                sys.stderr.write(fmt_string.format(
489                        frames_read/float(a_source.samplerate),
490                        frames_read,
491                        frames_read // a_source.hop_size + 1,
492                        a_source.hop_size,
493                        a_source.uri,
494                        a_source.samplerate))
495    except KeyboardInterrupt:
496        sys.exit(1)
Note: See TracBrowser for help on using the repository browser.