source: python/lib/aubio/cmd.py @ 70762c5

feature/autosinkfeature/cnnfeature/cnn_orgfeature/constantqfeature/crepefeature/crepe_orgfeature/pitchshiftfeature/pydocstringsfeature/timestretchfix/ffmpeg5
Last change on this file since 70762c5 was 70762c5, checked in by Paul Brossier <piem@piem.org>, 7 years ago

python/lib/aubio/cmd.py: add some doc, prefix unused variables with _

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