From: Paul Brossier Date: Sat, 30 Sep 2017 18:22:13 +0000 (+0200) Subject: Merge branch 'master' into PR/simplify_emscripten X-Git-Tag: 0.4.6~9^2~13 X-Git-Url: https://git.aubio.org/?p=aubio.git;a=commitdiff_plain;h=23facacbd4fc1fee9c867190ae831a379eed4a72;hp=9fabad58e523935b4614f4f3754b63b2f3a04c5c Merge branch 'master' into PR/simplify_emscripten --- diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 00000000..66ded4e3 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,3 @@ +[run] +branch = True +source = aubio diff --git a/.travis.yml b/.travis.yml index 73aef644..c1fb2c49 100644 --- a/.travis.yml +++ b/.travis.yml @@ -81,6 +81,7 @@ addons: - libasound2-dev - libfftw3-dev - sox + - lcov before_install: - | @@ -89,6 +90,7 @@ before_install: brew install sox brew install ffmpeg brew install libsndfile + brew install lcov export PATH="$HOME/Library/Python/2.7/bin/:$PATH" fi; @@ -97,17 +99,30 @@ install: - travis_retry make getwaf expandwaf deps_python - which pip - pip --version + - pip install python-coveralls + - gem install coveralls-lcov script: - make create_test_sounds - | if [[ -z "$AUBIO_NOTESTS" ]]; then make test_lib_python_clean - make test_python_only_clean + make coverage else make test_lib_only_clean fi; +after_success: + - | + if [[ -z "$AUBIO_NOTESTS" ]]; then + # upload lcov coverage to coveralls.io + coveralls-lcov build/coverage.info + # upload python coverage + #coveralls + # upload to codecov + bash <(curl -s https://codecov.io/bash) + fi + notifications: irc: channels: diff --git a/Makefile b/Makefile index 7e9226e6..f8af602e 100644 --- a/Makefile +++ b/Makefile @@ -35,6 +35,9 @@ INCLUDEDIR?=$(PREFIX)/include DATAROOTDIR?=$(PREFIX)/share MANDIR?=$(DATAROOTDIR)/man +# default nose2 command +NOSE2?=nose2 -N 4 --verbose + SOX=sox TESTSOUNDS := python/tests/sounds @@ -135,9 +138,9 @@ test_python: export LD_LIBRARY_PATH=$(DESTDIR)/$(LIBDIR) test_python: export PYTHONPATH=$(PYDESTDIR)/$(LIBDIR) test_python: local_dylib # run test with installed package - ./python/tests/run_all_tests --verbose - # also run with nose, multiple processes - nose2 -N 4 + # ./python/tests/run_all_tests --verbose + # run with nose2, multiple processes + $(NOSE2) clean_python: ./setup.py clean @@ -231,6 +234,27 @@ test_python_only_clean: test_python_only \ uninstall_python \ check_clean_python +coverage: export CFLAGS=--coverage +coverage: export LDFLAGS=--coverage +coverage: export PYTHONPATH=$(PWD)/python/lib +coverage: export LD_LIBRARY_PATH=$(PWD)/build/src +coverage: force_uninstall_python deps_python \ + clean_python clean distclean build local_dylib + lcov --capture --no-external --directory . --output-file build/coverage_lib.info + pip install -v -e . + coverage run `which nose2` + lcov --capture --no-external --directory . --output-file build/coverage_python.info + lcov -a build/coverage_python.info -a build/coverage_lib.info -o build/coverage.info + +coverage_report: coverage + genhtml build/coverage.info --output-directory lcov_html + mkdir -p gcovr_html/ + gcovr -r . --html --html-details \ + --output gcovr_html/index.html \ + --exclude ".*tests/.*" --exclude ".*examples/.*" + coverage report + coverage html + sphinx: configure $(WAFCMD) sphinx $(WAFOPTS) diff --git a/aubio.pc.in b/aubio.pc.in index 301a1b5a..16a892f5 100644 --- a/aubio.pc.in +++ b/aubio.pc.in @@ -7,4 +7,4 @@ Name: aubio Description: a library for audio labelling Version: @VERSION@ Libs: -L${libdir} -laubio -Cflags: -I${includedir} +Cflags: -I${includedir} diff --git a/python/demos/demo_bpm_extract.py b/python/demos/demo_bpm_extract.py index ba7fbad7..ab0ceab8 100755 --- a/python/demos/demo_bpm_extract.py +++ b/python/demos/demo_bpm_extract.py @@ -3,26 +3,33 @@ from aubio import source, tempo from numpy import median, diff -def get_file_bpm(path, params = None): +def get_file_bpm(path, params=None): """ Calculate the beats per minute (bpm) of a given file. path: path to the file param: dictionary of parameters """ if params is None: params = {} - try: - win_s = params['win_s'] - samplerate = params['samplerate'] - hop_s = params['hop_s'] - except KeyError: - """ - # super fast - samplerate, win_s, hop_s = 4000, 128, 64 - # fast - samplerate, win_s, hop_s = 8000, 512, 128 - """ - # default: - samplerate, win_s, hop_s = 44100, 1024, 512 + # default: + samplerate, win_s, hop_s = 44100, 1024, 512 + if 'mode' in params: + if params.mode in ['super-fast']: + # super fast + samplerate, win_s, hop_s = 4000, 128, 64 + elif params.mode in ['fast']: + # fast + samplerate, win_s, hop_s = 8000, 512, 128 + elif params.mode in ['default']: + pass + else: + print("unknown mode {:s}".format(params.mode)) + # manual settings + if 'samplerate' in params: + samplerate = params.samplerate + if 'win_s' in params: + win_s = params.win_s + if 'hop_s' in params: + hop_s = params.hop_s s = source(path, samplerate, hop_s) samplerate = s.samplerate @@ -44,19 +51,29 @@ def get_file_bpm(path, params = None): if read < hop_s: break - # Convert to periods and to bpm - if len(beats) > 1: - if len(beats) < 4: - print("few beats found in {:s}".format(path)) - bpms = 60./diff(beats) - b = median(bpms) - else: - b = 0 - print("not enough beats found in {:s}".format(path)) - return b + def beats_to_bpm(beats, path): + # if enough beats are found, convert to periods then to bpm + if len(beats) > 1: + if len(beats) < 4: + print("few beats found in {:s}".format(path)) + bpms = 60./diff(beats) + return median(bpms) + else: + print("not enough beats found in {:s}".format(path)) + return 0 + + return beats_to_bpm(beats, path) if __name__ == '__main__': - import sys - for f in sys.argv[1:]: - bpm = get_file_bpm(f) + import argparse + parser = argparse.ArgumentParser() + parser.add_argument('-m', '--mode', + help="mode [default|fast|super-fast]", + dest="mode") + parser.add_argument('sources', + nargs='*', + help="input_files") + args = parser.parse_args() + for f in args.sources: + bpm = get_file_bpm(f, params = args) print("{:6s} {:s}".format("{:2f}".format(bpm), f)) diff --git a/python/demos/demo_tapthebeat.py b/python/demos/demo_tapthebeat.py index fb3117c7..0483379d 100755 --- a/python/demos/demo_tapthebeat.py +++ b/python/demos/demo_tapthebeat.py @@ -44,7 +44,7 @@ a_tempo = aubio.tempo("default", win_s, hop_s, samplerate) click = 0.7 * np.sin(2. * np.pi * np.arange(hop_s) / hop_s * samplerate / 3000.) # pyaudio callback -def pyaudio_callback(in_data, frame_count, time_info, status): +def pyaudio_callback(_in_data, _frame_count, _time_info, _status): samples, read = a_source() is_beat = a_tempo(samples) if is_beat: diff --git a/python/lib/aubio/cmd.py b/python/lib/aubio/cmd.py index 7d9d0c90..292c64c9 100644 --- a/python/lib/aubio/cmd.py +++ b/python/lib/aubio/cmd.py @@ -20,153 +20,233 @@ def aubio_parser(): action="store_true", dest="show_version") subparsers = parser.add_subparsers(title='commands', dest='command', + parser_class= AubioArgumentParser, metavar="") + parser_add_subcommand_help(subparsers) + + parser_add_subcommand_onset(subparsers) + parser_add_subcommand_pitch(subparsers) + parser_add_subcommand_beat(subparsers) + parser_add_subcommand_tempo(subparsers) + parser_add_subcommand_notes(subparsers) + parser_add_subcommand_mfcc(subparsers) + parser_add_subcommand_melbands(subparsers) + parser_add_subcommand_quiet(subparsers) + parser_add_subcommand_cut(subparsers) + + return parser + +def parser_add_subcommand_help(subparsers): + # global help subcommand + subparsers.add_parser('help', + help='show help message', + formatter_class = argparse.ArgumentDefaultsHelpFormatter) + +def parser_add_subcommand_onset(subparsers): # onset subcommand subparser = subparsers.add_parser('onset', help='estimate time of onsets (beginning of sound event)', formatter_class = argparse.ArgumentDefaultsHelpFormatter) - parser_add_input(subparser) - parser_add_buf_hop_size(subparser) + subparser.add_input() + subparser.add_buf_hop_size() helpstr = "onset novelty function" helpstr += " " - parser_add_method(subparser, helpstr=helpstr) - parser_add_threshold(subparser) - parser_add_silence(subparser) - parser_add_minioi(subparser) - parser_add_time_format(subparser) - parser_add_verbose_help(subparser) + subparser.add_method(helpstr=helpstr) + subparser.add_threshold() + subparser.add_silence() + subparser.add_minioi() + subparser.add_time_format() + subparser.add_verbose_help() subparser.set_defaults(process=process_onset) +def parser_add_subcommand_pitch(subparsers): # pitch subcommand subparser = subparsers.add_parser('pitch', help='estimate fundamental frequency (monophonic)') - parser_add_input(subparser) - parser_add_buf_hop_size(subparser, buf_size=2048) + subparser.add_input() + subparser.add_buf_hop_size(buf_size=2048) helpstr = "pitch detection method " - parser_add_method(subparser, helpstr=helpstr) - parser_add_threshold(subparser) - parser_add_pitch_unit(subparser) - parser_add_silence(subparser) - parser_add_time_format(subparser) - parser_add_verbose_help(subparser) + subparser.add_method(helpstr=helpstr) + subparser.add_threshold() + subparser.add_pitch_unit() + subparser.add_silence() + subparser.add_time_format() + subparser.add_verbose_help() subparser.set_defaults(process=process_pitch) +def parser_add_subcommand_beat(subparsers): # beat subcommand subparser = subparsers.add_parser('beat', help='estimate location of beats') - parser_add_input(subparser) - parser_add_buf_hop_size(subparser, buf_size=1024, hop_size=512) - parser_add_time_format(subparser) - parser_add_verbose_help(subparser) + subparser.add_input() + subparser.add_buf_hop_size(buf_size=1024, hop_size=512) + subparser.add_time_format() + subparser.add_verbose_help() subparser.set_defaults(process=process_beat) +def parser_add_subcommand_tempo(subparsers): # tempo subcommand subparser = subparsers.add_parser('tempo', help='estimate overall tempo in bpm') - parser_add_input(subparser) - parser_add_buf_hop_size(subparser, buf_size=1024, hop_size=512) - parser_add_time_format(subparser) - parser_add_verbose_help(subparser) + subparser.add_input() + subparser.add_buf_hop_size(buf_size=1024, hop_size=512) + subparser.add_time_format() + subparser.add_verbose_help() subparser.set_defaults(process=process_tempo) +def parser_add_subcommand_notes(subparsers): # notes subcommand subparser = subparsers.add_parser('notes', help='estimate midi-like notes (monophonic)') - parser_add_input(subparser) - parser_add_buf_hop_size(subparser) - parser_add_time_format(subparser) - parser_add_verbose_help(subparser) + subparser.add_input() + subparser.add_buf_hop_size() + subparser.add_time_format() + subparser.add_verbose_help() subparser.set_defaults(process=process_notes) +def parser_add_subcommand_mfcc(subparsers): # mfcc subcommand subparser = subparsers.add_parser('mfcc', help='extract Mel-Frequency Cepstrum Coefficients') - parser_add_input(subparser) - parser_add_buf_hop_size(subparser) - parser_add_time_format(subparser) - parser_add_verbose_help(subparser) + subparser.add_input() + subparser.add_buf_hop_size() + subparser.add_time_format() + subparser.add_verbose_help() subparser.set_defaults(process=process_mfcc) +def parser_add_subcommand_melbands(subparsers): # melbands subcommand subparser = subparsers.add_parser('melbands', help='extract energies in Mel-frequency bands') - parser_add_input(subparser) - parser_add_buf_hop_size(subparser) - parser_add_time_format(subparser) - parser_add_verbose_help(subparser) + subparser.add_input() + subparser.add_buf_hop_size() + subparser.add_time_format() + subparser.add_verbose_help() subparser.set_defaults(process=process_melbands) - return parser - -def parser_add_input(parser): - parser.add_argument("source_uri", default=None, nargs='?', - help="input sound file to analyse", metavar = "") - parser.add_argument("-i", "--input", dest = "source_uri2", - help="input sound file to analyse", metavar = "") - parser.add_argument("-r", "--samplerate", - metavar = "", type=int, - action="store", dest="samplerate", default=0, - help="samplerate at which the file should be represented") - -def parser_add_verbose_help(parser): - parser.add_argument("-v","--verbose", - action="count", dest="verbose", default=1, - help="make lots of noise [default]") - parser.add_argument("-q","--quiet", - action="store_const", dest="verbose", const=0, - help="be quiet") - -def parser_add_buf_hop_size(parser, buf_size=512, hop_size=256): - parser.add_argument("-B","--bufsize", - action="store", dest="buf_size", default=buf_size, - metavar = "", type=int, - help="buffer size [default=%d]" % buf_size) - parser.add_argument("-H","--hopsize", - metavar = "", type=int, - action="store", dest="hop_size", default=hop_size, - help="overlap size [default=%d]" % hop_size) - -def parser_add_method(parser, method='default', helpstr='method'): - parser.add_argument("-m","--method", - metavar = "", type=str, - action="store", dest="method", default=method, - help="%s [default=%s]" % (helpstr, method)) - -def parser_add_threshold(parser, default=None): - parser.add_argument("-t","--threshold", - metavar = "", type=float, - action="store", dest="threshold", default=default, - help="threshold [default=%s]" % default) - -def parser_add_silence(parser): - parser.add_argument("-s", "--silence", - metavar = "", type=float, - action="store", dest="silence", default=-70, - help="silence threshold") - -def parser_add_minioi(parser): - parser.add_argument("-M", "--minioi", - metavar = "", type=str, - action="store", dest="minioi", default="12ms", - help="minimum Inter-Onset Interval") - -def parser_add_pitch_unit(parser, default="Hz"): - help_str = "frequency unit, should be one of Hz, midi, bin, cent" - help_str += " [default=%s]" % default - parser.add_argument("-u", "--pitch-unit", - metavar = "", type=str, - action="store", dest="pitch_unit", default=default, - help=help_str) - -def parser_add_time_format(parser): - helpstr = "select time values output format (samples, ms, seconds)" - helpstr += " [default=seconds]" - parser.add_argument("-T", "--time-format", - metavar='format', - dest="time_format", - default=None, - help=helpstr) +def parser_add_subcommand_quiet(subparsers): + # quiet subcommand + subparser = subparsers.add_parser('quiet', + help='extract timestamps of quiet and loud regions') + subparser.add_input() + subparser.add_hop_size() + subparser.add_silence() + subparser.add_time_format() + subparser.add_verbose_help() + subparser.set_defaults(process=process_quiet) + +def parser_add_subcommand_cut(subparsers): + # quiet subcommand + subparser = subparsers.add_parser('cut', + help='slice at timestamps') + subparser.add_input() + helpstr = "onset novelty function" + helpstr += " " + subparser.add_method(helpstr=helpstr) + subparser.add_buf_hop_size() + subparser.add_silence() + subparser.add_threshold(default=0.3) + subparser.add_minioi() + subparser.add_slicer_options() + subparser.add_time_format() + subparser.add_verbose_help() + subparser.set_defaults(process=process_cut) + +class AubioArgumentParser(argparse.ArgumentParser): + + def add_input(self): + self.add_argument("source_uri", default=None, nargs='?', + help="input sound file to analyse", metavar = "") + self.add_argument("-i", "--input", dest = "source_uri2", + help="input sound file to analyse", metavar = "") + self.add_argument("-r", "--samplerate", + metavar = "", type=int, + action="store", dest="samplerate", default=0, + help="samplerate at which the file should be represented") + + def add_verbose_help(self): + self.add_argument("-v","--verbose", + action="count", dest="verbose", default=1, + help="make lots of noise [default]") + self.add_argument("-q","--quiet", + action="store_const", dest="verbose", const=0, + help="be quiet") + + def add_buf_hop_size(self, buf_size=512, hop_size=256): + self.add_buf_size(buf_size=buf_size) + self.add_hop_size(hop_size=hop_size) + + def add_buf_size(self, buf_size=512): + self.add_argument("-B","--bufsize", + action="store", dest="buf_size", default=buf_size, + metavar = "", type=int, + help="buffer size [default=%d]" % buf_size) + + def add_hop_size(self, hop_size=256): + self.add_argument("-H","--hopsize", + metavar = "", type=int, + action="store", dest="hop_size", default=hop_size, + help="overlap size [default=%d]" % hop_size) + + def add_method(self, method='default', helpstr='method'): + self.add_argument("-m","--method", + metavar = "", type=str, + action="store", dest="method", default=method, + help="%s [default=%s]" % (helpstr, method)) + + def add_threshold(self, default=None): + self.add_argument("-t","--threshold", + metavar = "", type=float, + action="store", dest="threshold", default=default, + help="threshold [default=%s]" % default) + + def add_silence(self): + self.add_argument("-s", "--silence", + metavar = "", type=float, + action="store", dest="silence", default=-70, + help="silence threshold") + + def add_minioi(self, default="12ms"): + self.add_argument("-M", "--minioi", + metavar = "", type=str, + action="store", dest="minioi", default=default, + help="minimum Inter-Onset Interval [default=%s]" % default) + + def add_pitch_unit(self, default="Hz"): + help_str = "frequency unit, should be one of Hz, midi, bin, cent" + help_str += " [default=%s]" % default + self.add_argument("-u", "--pitch-unit", + metavar = "", type=str, + action="store", dest="pitch_unit", default=default, + help=help_str) + + def add_time_format(self): + helpstr = "select time values output format (samples, ms, seconds)" + helpstr += " [default=seconds]" + self.add_argument("-T", "--time-format", + metavar='format', + dest="time_format", + default=None, + help=helpstr) + + def add_slicer_options(self): + self.add_argument("-o","--output", type = str, + metavar = "", + action="store", dest="output_directory", default=None, + help="specify path where slices of the original file should be created") + self.add_argument("--cut-until-nsamples", type = int, + metavar = "", + action = "store", dest = "cut_until_nsamples", default = None, + help="how many extra samples should be added at the end of each slice") + self.add_argument("--cut-every-nslices", type = int, + metavar = "", + action = "store", dest = "cut_every_nslices", default = None, + help="how many slices should be groupped together at each cut") + self.add_argument("--cut-until-nslices", type = int, + metavar = "", + action = "store", dest = "cut_until_nslices", default = None, + help="how many extra slices should be added at the end of each slice") # some utilities @@ -176,7 +256,7 @@ def samples2seconds(n_frames, samplerate): def samples2milliseconds(n_frames, samplerate): return "%f\t" % (1000. * n_frames / float(samplerate)) -def samples2samples(n_frames, samplerate): +def samples2samples(n_frames, _samplerate): return "%d\t" % n_frames def timefunc(mode): @@ -199,7 +279,7 @@ class default_process(object): name = type(self).__name__.split('_')[1] optstr = ' '.join(['running', name, 'with options', repr(self.options), '\n']) sys.stderr.write(optstr) - def flush(self, n_frames, samplerate): + def flush(self, frames_read, samplerate): # optionally called at the end of process pass @@ -238,7 +318,7 @@ class process_onset(default_process): super(process_onset, self).__init__(args) def __call__(self, block): return self.onset(block) - def repr_res(self, res, frames_read, samplerate): + def repr_res(self, res, _frames_read, samplerate): if res[0] != 0: outstr = self.time2string(self.onset.get_last(), samplerate) sys.stdout.write(outstr + '\n') @@ -269,7 +349,7 @@ class process_beat(default_process): super(process_beat, self).__init__(args) def __call__(self, block): return self.tempo(block) - def repr_res(self, res, frames_read, samplerate): + def repr_res(self, res, _frames_read, samplerate): if res[0] != 0: outstr = self.time2string(self.tempo.get_last(), samplerate) sys.stdout.write(outstr + '\n') @@ -278,7 +358,7 @@ class process_tempo(process_beat): def __init__(self, args): super(process_tempo, self).__init__(args) self.beat_locations = [] - def repr_res(self, res, frames_read, samplerate): + def repr_res(self, res, _frames_read, samplerate): if res[0] != 0: self.beat_locations.append(self.tempo.get_last_s()) def flush(self, frames_read, samplerate): @@ -362,6 +442,61 @@ class process_melbands(default_process): fmt_out += ' '.join(["% 9.7f" % f for f in res.tolist()]) sys.stdout.write(fmt_out + '\n') +class process_quiet(default_process): + def __init__(self, args): + self.args = args + valid_opts = ['hop_size', 'silence'] + self.parse_options(args, valid_opts) + self.wassilence = 1 + + if args.silence is not None: + self.silence = args.silence + super(process_quiet, self).__init__(args) + + def __call__(self, block): + if aubio.silence_detection(block, self.silence) == 1: + if self.wassilence != 1: + self.wassilence = 1 + return 2 # newly found silence + return 1 # silence again + else: + if self.wassilence != 0: + self.wassilence = 0 + return -1 # newly found noise + return 0 # noise again + + def repr_res(self, res, frames_read, samplerate): + fmt_out = None + if res == -1: + fmt_out = "NOISY: " + if res == 2: + fmt_out = "QUIET: " + if fmt_out is not None: + fmt_out += self.time2string(frames_read, samplerate) + sys.stdout.write(fmt_out + '\n') + +class process_cut(process_onset): + def __init__(self, args): + super(process_cut, self).__init__(args) + self.slices = [] + self.options = args + + def __call__(self, block): + ret = super(process_cut, self).__call__(block) + if ret: self.slices.append(self.onset.get_last()) + return ret + + def flush(self, frames_read, samplerate): + from aubio.cut import _cut_slice + _cut_slice(self.options, self.slices) + duration = float (frames_read) / float(samplerate) + base_info = '%(source_file)s' % {'source_file': self.options.source_uri} + base_info += ' (total %(duration).2fs at %(samplerate)dHz)\n' % \ + {'duration': duration, 'samplerate': samplerate} + info = "created %d slices from " % len(self.slices) + info += base_info + sys.stderr.write(info) + def main(): parser = aubio_parser() args = parser.parse_args() @@ -370,10 +505,13 @@ def main(): sys.exit(0) elif 'verbose' in args and args.verbose > 3: sys.stderr.write('aubio version ' + aubio.version + '\n') - if 'command' not in args or args.command is None: + if 'command' not in args or args.command is None or args.command in ['help']: # no command given, print help and return 1 parser.print_help() - sys.exit(1) + if args.command and args.command in ['help']: + sys.exit(0) + else: + sys.exit(1) elif not args.source_uri and not args.source_uri2: sys.stderr.write("Error: a source is required\n") parser.print_help() diff --git a/python/lib/aubio/cut.py b/python/lib/aubio/cut.py index d82f7542..c01615cf 100644 --- a/python/lib/aubio/cut.py +++ b/python/lib/aubio/cut.py @@ -5,16 +5,11 @@ """ import sys -import argparse - -def parse_args(): - usage = "usage: %s [options] -i soundfile" % sys.argv[0] - usage += "\n help: %s -h" % sys.argv[0] - parser = argparse.ArgumentParser() - parser.add_argument("source_file", default=None, nargs='?', - help="input sound file to analyse", metavar = "") - parser.add_argument("-i", "--input", action = "store", dest = "source_file2", - help="input sound file to analyse", metavar = "") +from aubio.cmd import AubioArgumentParser + +def aubio_cut_parser(): + parser = AubioArgumentParser() + parser.add_input() parser.add_argument("-O","--onset-method", action="store", dest="onset_method", default='default', metavar = "", @@ -23,7 +18,7 @@ def parse_args(): # cutting methods parser.add_argument("-b","--beat", action="store_true", dest="beat", default=False, - help="use beat locations") + help="slice at beat locations") """ parser.add_argument("-S","--silencecut", action="store_true", dest="silencecut", default=False, @@ -34,32 +29,15 @@ def parse_args(): help="silence threshold [default=-70]") """ # algorithm parameters - parser.add_argument("-r", "--samplerate", - metavar = "", type=int, - action="store", dest="samplerate", default=0, - help="samplerate at which the file should be represented") - parser.add_argument("-B","--bufsize", - action="store", dest="bufsize", default=512, - metavar = "", type=int, - help="buffer size [default=512]") - parser.add_argument("-H","--hopsize", - metavar = "", type=int, - action="store", dest="hopsize", default=256, - help="overlap size [default=256]") - parser.add_argument("-t","--onset-threshold", - metavar = "", type=float, + parser.add_buf_hop_size() + parser.add_argument("-t","--threshold", "--onset-threshold", + metavar = "", type=float, action="store", dest="threshold", default=0.3, help="onset peak picking threshold [default=0.3]") parser.add_argument("-c","--cut", action="store_true", dest="cut", default=False, - help="cut input sound file at detected labels \ - best used with option -L") - - # minioi - parser.add_argument("-M","--minioi", - metavar = "", type=str, - action="store", dest="minioi", default="12ms", - help="minimum inter onset interval [default=12ms]") + help="cut input sound file at detected labels") + parser.add_minioi() """ parser.add_argument("-D","--delay", @@ -81,9 +59,7 @@ def parse_args(): metavar = "", action="store", dest="zerothres", default=0.008, help="zero-crossing threshold for slicing [default=0.00008]") - """ # plotting functions - """ parser.add_argument("-p","--plot", action="store_true", dest="plot", default=False, help="draw plot") @@ -109,51 +85,24 @@ def parse_args(): action="store_true", dest="spectro", default=False, help="add spectrogram to the plot") """ - parser.add_argument("-o","--output", type = str, - metavar = "", - action="store", dest="output_directory", default=None, - help="specify path where slices of the original file should be created") - parser.add_argument("--cut-until-nsamples", type = int, - metavar = "", - action = "store", dest = "cut_until_nsamples", default = None, - help="how many extra samples should be added at the end of each slice") - parser.add_argument("--cut-every-nslices", type = int, - metavar = "", - action = "store", dest = "cut_every_nslices", default = None, - help="how many slices should be groupped together at each cut") - parser.add_argument("--cut-until-nslices", type = int, - metavar = "", - action = "store", dest = "cut_until_nslices", default = None, - help="how many extra slices should be added at the end of each slice") - - parser.add_argument("-v","--verbose", - action="store_true", dest="verbose", default=True, - help="make lots of noise [default]") - parser.add_argument("-q","--quiet", - action="store_false", dest="verbose", default=True, - help="be quiet") - args = parser.parse_args() - if not args.source_file and not args.source_file2: - sys.stderr.write("Error: no file name given\n") - parser.print_help() - sys.exit(1) - elif args.source_file2 is not None: - args.source_file = args.source_file2 - return args + parser.add_slicer_options() + parser.add_verbose_help() + return parser -def main(): - options = parse_args() - source_file = options.source_file - hopsize = options.hopsize - bufsize = options.bufsize +def _cut_analyze(options): + hopsize = options.hop_size + bufsize = options.buf_size samplerate = options.samplerate - source_file = options.source_file + source_uri = options.source_uri + # analyze pass from aubio import onset, tempo, source - s = source(source_file, samplerate, hopsize) - if samplerate == 0: samplerate = s.get_samplerate() + s = source(source_uri, samplerate, hopsize) + if samplerate == 0: + samplerate = s.get_samplerate() + options.samplerate = samplerate if options.beat: o = tempo(options.onset_method, bufsize, hopsize, samplerate=samplerate) @@ -170,7 +119,6 @@ def main(): timestamps = [] total_frames = 0 - # analyze pass while True: samples, read = s() if o(samples): @@ -179,15 +127,12 @@ def main(): total_frames += read if read < hopsize: break del s - # print some info - nstamps = len(timestamps) - duration = float (total_frames) / float(samplerate) - info = 'found %(nstamps)d timestamps in %(source_file)s' % locals() - info += ' (total %(duration).2fs at %(samplerate)dHz)\n' % locals() - sys.stderr.write(info) + return timestamps, total_frames +def _cut_slice(options, timestamps): # cutting pass - if options.cut and nstamps > 0: + nstamps = len(timestamps) + if nstamps > 0: # generate output files from aubio.slicing import slice_source_at_stamps timestamps_end = None @@ -202,12 +147,36 @@ def main(): if options.cut_until_nslices: timestamps_end = [t for t in timestamps[1 + options.cut_until_nslices:]] timestamps_end += [ 1e120 ] * (options.cut_until_nslices + 1) - slice_source_at_stamps(source_file, timestamps, timestamps_end = timestamps_end, + slice_source_at_stamps(options.source_uri, + timestamps, timestamps_end = timestamps_end, output_dir = options.output_directory, - samplerate = samplerate) + samplerate = options.samplerate) + +def main(): + parser = aubio_cut_parser() + options = parser.parse_args() + if not options.source_uri and not options.source_uri2: + sys.stderr.write("Error: no file name given\n") + parser.print_help() + sys.exit(1) + elif options.source_uri2 is not None: + options.source_uri = options.source_uri2 + + # analysis + timestamps, total_frames = _cut_analyze(options) + + # print some info + duration = float (total_frames) / float(options.samplerate) + base_info = '%(source_uri)s' % {'source_uri': options.source_uri} + base_info += ' (total %(duration).2fs at %(samplerate)dHz)\n' % \ + {'duration': duration, 'samplerate': options.samplerate} + + info = "found %d timestamps in " % len(timestamps) + info += base_info + sys.stderr.write(info) - # print some info - duration = float (total_frames) / float(samplerate) - info = 'created %(nstamps)d slices from %(source_file)s' % locals() - info += ' (total %(duration).2fs at %(samplerate)dHz)\n' % locals() + if options.cut: + _cut_slice(options, timestamps) + info = "created %d slices from " % len(timestamps) + info += base_info sys.stderr.write(info) diff --git a/python/tests/test_aubio_cmd.py b/python/tests/test_aubio_cmd.py new file mode 100755 index 00000000..6e834d15 --- /dev/null +++ b/python/tests/test_aubio_cmd.py @@ -0,0 +1,31 @@ +#! /usr/bin/env python + +import aubio.cmd +from nose2 import main +from numpy.testing import TestCase + +class aubio_cmd(TestCase): + + def setUp(self): + self.a_parser = aubio.cmd.aubio_parser() + + def test_default_creation(self): + try: + assert self.a_parser.parse_args(['-V']).show_version + except SystemExit: + url = 'https://bugs.python.org/issue9253' + self.skipTest('subcommand became optional in py3, see %s' % url) + +class aubio_cmd_utils(TestCase): + + def test_samples2seconds(self): + self.assertEqual(aubio.cmd.samples2seconds(3200, 32000), "0.100000\t") + + def test_samples2milliseconds(self): + self.assertEqual(aubio.cmd.samples2milliseconds(3200, 32000), "100.000000\t") + + def test_samples2samples(self): + self.assertEqual(aubio.cmd.samples2samples(3200, 32000), "3200\t") + +if __name__ == '__main__': + main() diff --git a/python/tests/test_aubio_cut.py b/python/tests/test_aubio_cut.py new file mode 100755 index 00000000..cbbfe05f --- /dev/null +++ b/python/tests/test_aubio_cut.py @@ -0,0 +1,16 @@ +#! /usr/bin/env python + +import aubio.cut +from nose2 import main +from numpy.testing import TestCase + +class aubio_cut(TestCase): + + def setUp(self): + self.a_parser = aubio.cut.aubio_cut_parser() + + def test_default_creation(self): + assert self.a_parser.parse_args(['-v']).verbose + +if __name__ == '__main__': + main() diff --git a/python/tests/test_filterbank.py b/python/tests/test_filterbank.py index 3245008b..8808ba80 100755 --- a/python/tests/test_filterbank.py +++ b/python/tests/test_filterbank.py @@ -1,9 +1,8 @@ #! /usr/bin/env python -from unittest import main -from numpy.testing import TestCase -from numpy.testing import assert_equal, assert_almost_equal import numpy as np +from numpy.testing import TestCase, assert_equal, assert_almost_equal + from aubio import cvec, filterbank, float_type from .utils import array_from_text_file @@ -62,6 +61,13 @@ class aubio_filterbank_test_case(TestCase): f.set_mel_coeffs_slaney(16000) assert_almost_equal ( expected, f.get_coeffs() ) + def test_mfcc_coeffs_get_coeffs(self): + f = filterbank(40, 512) + coeffs = f.get_coeffs() + self.assertIsInstance(coeffs, np.ndarray) + assert_equal (coeffs, 0) + assert_equal (np.shape(coeffs), (40, 512 / 2 + 1)) + class aubio_filterbank_wrong_values(TestCase): def test_negative_window(self): @@ -81,4 +87,5 @@ class aubio_filterbank_wrong_values(TestCase): f(cvec(256)) if __name__ == '__main__': - main() + import nose2 + nose2.main() diff --git a/python/tests/test_filterbank_mel.py b/python/tests/test_filterbank_mel.py index 1ce38e9c..a45dbf07 100755 --- a/python/tests/test_filterbank_mel.py +++ b/python/tests/test_filterbank_mel.py @@ -1,44 +1,46 @@ #! /usr/bin/env python -from unittest import main -from numpy.testing import TestCase -from numpy.testing import assert_equal, assert_almost_equal -from numpy import array, shape +import numpy as np +from numpy.testing import TestCase, assert_equal, assert_almost_equal + from aubio import cvec, filterbank, float_type +import warnings +warnings.filterwarnings('ignore', category=UserWarning, append=True) + class aubio_filterbank_mel_test_case(TestCase): def test_slaney(self): f = filterbank(40, 512) f.set_mel_coeffs_slaney(16000) a = f.get_coeffs() - assert_equal(shape (a), (40, 512/2 + 1) ) + assert_equal(np.shape (a), (40, 512/2 + 1) ) def test_other_slaney(self): f = filterbank(40, 512*2) f.set_mel_coeffs_slaney(44100) - _ = f.get_coeffs() + self.assertIsInstance(f.get_coeffs(), np.ndarray) #print "sum is", sum(sum(a)) for win_s in [256, 512, 1024, 2048, 4096]: f = filterbank(40, win_s) f.set_mel_coeffs_slaney(32000) - _ = f.get_coeffs() #print "sum is", sum(sum(a)) + self.assertIsInstance(f.get_coeffs(), np.ndarray) def test_triangle_freqs_zeros(self): f = filterbank(9, 1024) freq_list = [40, 80, 200, 400, 800, 1600, 3200, 6400, 12800, 15000, 24000] - freqs = array(freq_list, dtype = float_type) + freqs = np.array(freq_list, dtype = float_type) f.set_triangle_bands(freqs, 48000) - _ = f.get_coeffs().T assert_equal ( f(cvec(1024)), 0) + self.assertIsInstance(f.get_coeffs(), np.ndarray) def test_triangle_freqs_ones(self): f = filterbank(9, 1024) freq_list = [40, 80, 200, 400, 800, 1600, 3200, 6400, 12800, 15000, 24000] - freqs = array(freq_list, dtype = float_type) + freqs = np.array(freq_list, dtype = float_type) f.set_triangle_bands(freqs, 48000) - _ = f.get_coeffs().T + self.assertIsInstance(f.get_coeffs(), np.ndarray) spec = cvec(1024) spec.norm[:] = 1 assert_almost_equal ( f(spec), @@ -46,4 +48,5 @@ class aubio_filterbank_mel_test_case(TestCase): 0.02133301, 0.02133301, 0.02133311, 0.02133334, 0.02133345]) if __name__ == '__main__': - main() + import nose2 + nose2.main() diff --git a/scripts/get_waf.sh b/scripts/get_waf.sh index a6122f4a..d880db6b 100755 --- a/scripts/get_waf.sh +++ b/scripts/get_waf.sh @@ -3,7 +3,7 @@ set -e set -x -WAFURL=https://waf.io/waf-1.9.12 +WAFURL=https://waf.io/waf-2.0.1 ( which wget > /dev/null && wget -qO waf $WAFURL ) || ( which curl > /dev/null && curl $WAFURL > waf ) diff --git a/src/io/source_avcodec.c b/src/io/source_avcodec.c index c6fee2f9..ccdce807 100644 --- a/src/io/source_avcodec.c +++ b/src/io/source_avcodec.c @@ -313,15 +313,15 @@ void aubio_source_avcodec_reset_resampler(aubio_source_avcodec_t * s, uint_t mul // TODO: use planar? //av_opt_set_int(avr, "out_sample_fmt", AV_SAMPLE_FMT_FLTP, 0); #ifdef HAVE_AVRESAMPLE - if ( ( err = avresample_open(avr) ) < 0) { + if ( ( err = avresample_open(avr) ) < 0) #elif defined(HAVE_SWRESAMPLE) - if ( ( err = swr_init(avr) ) < 0) { + if ( ( err = swr_init(avr) ) < 0) #endif /* HAVE_AVRESAMPLE || HAVE_SWRESAMPLE */ + { char errorstr[256]; av_strerror (err, errorstr, sizeof(errorstr)); - AUBIO_ERR("source_avcodec: Could not open AVAudioResampleContext for %s (%s)\n", + AUBIO_ERR("source_avcodec: Could not open resampling context for %s (%s)\n", s->path, errorstr); - //goto beach; return; } s->avr = avr; diff --git a/src/spectral/mfcc.c b/src/spectral/mfcc.c index 101deb82..f54799d3 100644 --- a/src/spectral/mfcc.c +++ b/src/spectral/mfcc.c @@ -36,7 +36,7 @@ struct _aubio_mfcc_t { uint_t win_s; /** grain length */ uint_t samplerate; /** sample rate (needed?) */ - uint_t n_filters; /** number of *filters */ + uint_t n_filters; /** number of filters */ uint_t n_coefs; /** number of coefficients (<= n_filters/2 +1) */ aubio_filterbank_t *fb; /** filter bank */ fvec_t *in_dct; /** input buffer for dct * [fb->n_filters] */