Merge branch 'aybe-patch-2' of feature/vcpkg_docs
authorPaul Brossier <piem@piem.org>
Sat, 11 May 2019 10:04:27 +0000 (12:04 +0200)
committerPaul Brossier <piem@piem.org>
Sat, 11 May 2019 10:04:27 +0000 (12:04 +0200)
205 files changed:
.appveyor.yml
.circleci/config.yml [new file with mode: 0644]
.gitignore
.landscape.yml [deleted file]
.travis.yml
ChangeLog
MANIFEST.in
Makefile
README.md
VERSION
azure-pipelines.yml [new file with mode: 0644]
circle.yml [deleted file]
doc/about.rst
doc/aubio.txt
doc/aubiocut.txt
doc/aubiomfcc.txt
doc/aubionotes.txt
doc/aubiopitch.txt
doc/conf.py
doc/debian_packages.rst
doc/develop.rst
doc/index.rst
doc/installing.rst
doc/py_analysis.rst [new file with mode: 0644]
doc/py_datatypes.rst [new file with mode: 0644]
doc/py_examples.rst [new file with mode: 0644]
doc/py_io.rst [new file with mode: 0644]
doc/py_spectral.rst [new file with mode: 0644]
doc/py_synth.rst [new file with mode: 0644]
doc/py_temporal.rst [new file with mode: 0644]
doc/py_utils.rst [new file with mode: 0644]
doc/python.rst [new file with mode: 0644]
doc/python_module.rst
doc/requirements.rst
doc/statuslinks.rst
examples/aubiomfcc.c
examples/aubionotes.c
examples/aubioonset.c
examples/aubiopitch.c
examples/aubioquiet.c
examples/aubiotrack.c
examples/parse_args.h
examples/utils.c
examples/utils.h
nose2.cfg [deleted file]
python/README.md
python/__init__.py [deleted file]
python/demos/demo_bpm_extract.py
python/demos/demo_filter.py
python/demos/demo_filterbank.py
python/demos/demo_pitch_sinusoid.py
python/demos/demo_source_simple.py
python/demos/demo_wav2midi.py
python/ext/aubio-docstrings.h [new file with mode: 0644]
python/ext/aubio-types.h
python/ext/aubiomodule.c
python/ext/py-cvec.c
python/ext/py-fft.c
python/ext/py-filter.c
python/ext/py-filterbank.c
python/ext/py-musicutils.c
python/ext/py-musicutils.h
python/ext/py-phasevoc.c
python/ext/py-sink.c
python/ext/py-source.c
python/ext/ufuncs.c
python/lib/__init__.py [deleted file]
python/lib/aubio/__init__.py
python/lib/aubio/cmd.py
python/lib/aubio/cut.py
python/lib/aubio/midiconv.py
python/lib/aubio/slicing.py
python/lib/gen_code.py
python/lib/gen_external.py
python/lib/moresetuptools.py
python/tests/__init__.py [deleted file]
python/tests/_tools.py [new file with mode: 0644]
python/tests/run_all_tests [deleted file]
python/tests/test_aubio.py
python/tests/test_aubio_cmd.py
python/tests/test_aubio_cut.py
python/tests/test_cvec.py
python/tests/test_dct.py [new file with mode: 0755]
python/tests/test_fft.py
python/tests/test_filter.py
python/tests/test_filterbank.py
python/tests/test_filterbank_mel.py
python/tests/test_fvec.py
python/tests/test_fvec_shift.py [new file with mode: 0644]
python/tests/test_hztomel.py [new file with mode: 0755]
python/tests/test_mathutils.py
python/tests/test_mfcc.py
python/tests/test_midi2note.py
python/tests/test_musicutils.py
python/tests/test_note2midi.py
python/tests/test_notes.py
python/tests/test_onset.py
python/tests/test_phasevoc.py
python/tests/test_pitch.py
python/tests/test_sink.py
python/tests/test_slicing.py
python/tests/test_source.py
python/tests/test_source_channels.py [new file with mode: 0755]
python/tests/test_specdesc.py
python/tests/test_tempo.py [new file with mode: 0755]
python/tests/test_zero_crossing_rate.py
python/tests/utils.py
requirements.txt
scripts/build_apple_frameworks
scripts/get_waf.sh
setup.py
src/aubio.h
src/aubio_priv.h
src/fmat.c
src/fvec.c
src/io/ioutils.c
src/io/ioutils.h
src/io/sink.c
src/io/sink_apple_audio.c
src/io/sink_sndfile.c
src/io/sink_wavwrite.c
src/io/source.c
src/io/source.h
src/io/source_apple_audio.c
src/io/source_avcodec.c
src/io/source_sndfile.c
src/io/source_wavread.c
src/io/utils_apple_audio.c
src/mathutils.c
src/mathutils.h
src/musicutils.c [new file with mode: 0644]
src/musicutils.h
src/notes/notes.c
src/notes/notes.h
src/onset/onset.c
src/pitch/pitchmcomb.c
src/pitch/pitchspecacf.c
src/pitch/pitchyin.c
src/pitch/pitchyinfast.c
src/pitch/pitchyinfft.c
src/spectral/awhitening.c
src/spectral/dct.c [new file with mode: 0644]
src/spectral/dct.h [new file with mode: 0644]
src/spectral/dct_accelerate.c [new file with mode: 0644]
src/spectral/dct_fftw.c [new file with mode: 0644]
src/spectral/dct_ipp.c [new file with mode: 0644]
src/spectral/dct_ooura.c [new file with mode: 0644]
src/spectral/dct_plain.c [new file with mode: 0644]
src/spectral/fft.c
src/spectral/filterbank.c
src/spectral/filterbank.h
src/spectral/filterbank_mel.c
src/spectral/filterbank_mel.h
src/spectral/mfcc.c
src/spectral/mfcc.h
src/spectral/phasevoc.c
src/spectral/phasevoc.h
src/spectral/specdesc.c
src/synth/wavetable.c
src/synth/wavetable.h
src/tempo/tempo.c
src/utils/hist.c
tests/create_tests_source.py [new file with mode: 0755]
tests/src/io/base-sink_custom.h [new file with mode: 0644]
tests/src/io/base-source_custom.h [new file with mode: 0644]
tests/src/io/test-sink-multi.c [deleted file]
tests/src/io/test-sink.c
tests/src/io/test-sink_apple_audio-multi.c [deleted file]
tests/src/io/test-sink_apple_audio.c
tests/src/io/test-sink_sndfile-multi.c [deleted file]
tests/src/io/test-sink_sndfile.c
tests/src/io/test-sink_wavwrite-multi.c [deleted file]
tests/src/io/test-sink_wavwrite.c
tests/src/io/test-source.c
tests/src/io/test-source_apple_audio.c
tests/src/io/test-source_avcodec.c
tests/src/io/test-source_multi.c [deleted file]
tests/src/io/test-source_seek.c [deleted file]
tests/src/io/test-source_sndfile.c
tests/src/io/test-source_wavread.c
tests/src/notes/test-notes.c [new file with mode: 0644]
tests/src/onset/test-onset.c
tests/src/pitch/test-pitch.c
tests/src/spectral/test-awhitening.c
tests/src/spectral/test-dct.c [new file with mode: 0644]
tests/src/spectral/test-filterbank.c
tests/src/spectral/test-mfcc.c
tests/src/spectral/test-phasevoc.c
tests/src/spectral/test-tss.c
tests/src/synth/test-sampler.c
tests/src/synth/test-wavetable.c
tests/src/tempo/test-tempo.c
tests/src/temporal/test-filter.c
tests/src/test-cvec.c
tests/src/test-delnull.c [deleted file]
tests/src/test-fmat.c
tests/src/test-fvec.c
tests/src/test-lvec.c
tests/src/test-mathutils-window.c
tests/src/test-mathutils.c
tests/src/test-vecutils.c [new file with mode: 0644]
tests/src/utils/test-hist.c
tests/utils_tests.h
tests/wscript_build
wscript

index a39cd42..0c21b17 100644 (file)
@@ -7,60 +7,72 @@ environment:
 
     # pre-installed python version, see:
     # http://www.appveyor.com/docs/installed-software#python
-    - PYTHONDIR: "C:\\Python27"
-      PYTHON_VERSION: "2.7.x"
-      PYTHON_ARCH: "32"
+    - PYTHONDIR: C:\Python27
+      PYTHON_VERSION: 2.7.x
+      PYTHON_ARCH: 32
 
-    - PYTHONDIR: "C:\\Python27-x64"
-      PYTHON_VERSION: "2.7.x"
-      PYTHON_ARCH: "64"
+    - PYTHONDIR: C:\Python27-x64
+      PYTHON_VERSION: 2.7.x
+      PYTHON_ARCH: 64
 
-    - PYTHONDIR: "C:\\Python34"
-      PYTHON_VERSION: "3.4.x"
-      PYTHON_ARCH: "32"
+    - PYTHONDIR: C:\Python35
+      PYTHON_VERSION: 3.5.x
+      PYTHON_ARCH: 32
 
-    - PYTHONDIR: "C:\\Python34-x64"
-      PYTHON_VERSION: "3.4.x"
-      PYTHON_ARCH: "64"
+    - PYTHONDIR: C:\Python35-x64
+      PYTHON_VERSION: 3.5.x
+      PYTHON_ARCH: 64
 
-    - PYTHONDIR: "C:\\Python35"
-      PYTHON_VERSION: "3.5.x"
-      PYTHON_ARCH: "32"
+    - PYTHONDIR: C:\Python36
+      PYTHON_VERSION: 3.6.x
+      PYTHON_ARCH: 32
 
-    - PYTHONDIR: "C:\\Python35-x64"
-      PYTHON_VERSION: "3.5.x"
-      PYTHON_ARCH: "64"
-      # add path required to run preprocessor step
-      PATH_EXTRAS: "c:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\bin"
+    - PYTHONDIR: C:\Python36-x64
+      PYTHON_VERSION: 3.6.x
+      PYTHON_ARCH: 64
 
-install:
+    - PYTHONDIR: C:\Python37
+      PYTHON_VERSION: 3.7.x
+      PYTHON_ARCH: 32
+
+    - PYTHONDIR: C:\Python37-x64
+      PYTHON_VERSION: 3.7.x
+      PYTHON_ARCH: 64
 
+install:
   - ECHO "Installed SDKs:"
   - ps: "ls \"C:/Program Files/Microsoft SDKs/Windows\""
 
+  - "SET PATH=%PYTHONDIR%;%PYTHONDIR%\\Scripts;%PATH%"
+
   # Check that we have the expected version and architecture for Python
-  - "%PYTHONDIR%\\python.exe --version"
-  - "%PYTHONDIR%\\python.exe -c \"import struct; print(struct.calcsize('P') * 8)\""
+  - "python --version"
+  - "python -c \"import struct; print(struct.calcsize('P') * 8)\""
 
-  # We need wheel installed to build wheels
-  - "%PYTHONDIR%\\python.exe -m pip install wheel"
+  - "python -m pip install --disable-pip-version-check --user --upgrade pip"
+  - "python -m pip install --upgrade setuptools"
 
-  - "SET PATH=%PATH_EXTRAS%;%PYTHONDIR%;%PYTHONDIR%\\Scripts;%PATH%"
+  # We need wheel installed to build wheels
+  - "python -m pip install wheel"
 
-  - "pip install --disable-pip-version-check --user --upgrade pip"
-  - "pip install --upgrade setuptools"
+  - "pip install -r requirements.txt"
 
 before_build:
   - "bash scripts/get_waf.sh"
 
 build_script:
-  # build python module without using libaubio
-  - "pip install -r requirements.txt"
-  - "python setup.py build"
-  - "pip install ."
+  # also build libaubio with waf
+  - python waf configure build install --verbose --msvc_version="msvc 14.0"
+  # clean before building python package
+  - python waf distclean
+  # build, upload and install wheel (inspired by numpy's appveyor)
+  - ps: |
+      pip wheel -v -v -v --wheel-dir=dist .
+      ls dist -r | Foreach-Object {
+          Push-AppveyorArtifact $_.FullName
+          pip install $_.FullName
+      }
+
+test_script:
   - "python python\\demos\\demo_create_test_sounds.py"
-  - "nose2 --verbose"
-  # clean up
-  - "python waf distclean"
-  # build libaubio
-  - python waf configure build --verbose --msvc_version="msvc 14.0"
+  - "pytest --verbose"
diff --git a/.circleci/config.yml b/.circleci/config.yml
new file mode 100644 (file)
index 0000000..eb00738
--- /dev/null
@@ -0,0 +1,104 @@
+apt-run: &apt-install
+  name: Install apt packages
+  command: |
+    sudo apt-get update
+    sudo apt-get -y install make sox pkg-config libavcodec-dev libavformat-dev libavresample-dev libavutil-dev libsndfile1-dev libsamplerate-dev
+
+pip-install: &pip-install
+  name: Install pip dependencies
+  command: |
+    pip install --user -r requirements.txt
+
+build-wheel: &build-wheel
+  name: Build python wheel
+  command: |
+    pip wheel -v -v -v --wheel-dir=dist .
+
+install-wheel: &install-wheel
+  name: Install python wheel
+  command: |
+    pip install --user dist/aubio*.whl
+
+test-pytest: &test-pytest
+  name: Test python wheel
+  command: |
+    make create_test_sounds
+    PATH=/home/circleci/.local/bin:$PATH pytest -v
+
+test-pytest-nosounds: &test-pytest-nosounds
+  name: Test python wheel
+  command: |
+    PATH=/home/circleci/.local/bin:$PATH pytest -v
+
+uninstall-wheel: &uninstall-wheel
+  name: Uninstall python wheel
+  command: |
+    pip show -f aubio
+    pip uninstall --verbose --yes aubio
+
+version: 2
+jobs:
+  build-27:
+    docker:
+      - image: circleci/python:2.7
+    steps:
+      - checkout
+      - run: *apt-install
+      - run: *pip-install
+      - run: *build-wheel
+      - run: *install-wheel
+      - run: *test-pytest
+      - run: *uninstall-wheel
+      - store_artifacts:
+          path: dist/
+
+  build-36:
+    docker:
+      - image: circleci/python:3.6
+    steps:
+      - checkout
+      - run: *apt-install
+      - run: *pip-install
+      - run: *build-wheel
+      - run: *install-wheel
+      - run: *test-pytest
+      - run: *uninstall-wheel
+      - store_artifacts:
+          path: dist/
+
+  build-37:
+    docker:
+      - image: circleci/python:3.7
+    steps:
+      - checkout
+      - run: *apt-install
+      - run: *pip-install
+      - run: *build-wheel
+      - run: *install-wheel
+      - run: *test-pytest
+      - run: *uninstall-wheel
+      - store_artifacts:
+          path: dist/
+
+  build-37-nodeps:
+    docker:
+      - image: circleci/python:3.7
+    steps:
+      - checkout
+      - run: *pip-install
+      - run: *build-wheel
+      - run: *install-wheel
+      - run: *test-pytest-nosounds
+      - run: *uninstall-wheel
+      - store_artifacts:
+          path: dist/
+
+workflows:
+  version: 2
+
+  test-wheel:
+    jobs:
+      - build-27
+      - build-36
+      - build-37
+      - build-37-nodeps
index 1611727..21bb1cc 100644 (file)
@@ -5,6 +5,8 @@
 # gcov generated files
 *.gcno
 *.gcda
+python/lib/aubio/_aubio.*.so
+.coverage
 
 # ignore compiled examples
 RE:examples/[a-z]*
@@ -43,7 +45,10 @@ pip-delete-this-directory.txt
 aubio-*.tar.bz2
 aubio-*.zip
 dist/*.tar.gz
+dist/*.whl
 
 # test sounds
 python/tests/sounds
 aubio.egg-info
+.eggs
+.cache
diff --git a/.landscape.yml b/.landscape.yml
deleted file mode 100644 (file)
index d180360..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-strictness: medium
-test-warnings: true
-python-targets:
-  - 2
-  - 3
index c1fb2c4..ec4cc9a 100644 (file)
@@ -2,24 +2,25 @@ language: python
 
 matrix:
   include:
-    - python: 3.5
+    - python: 3.6
       os: linux
       compiler: gcc
-    - python: 3.4
+    - python: 3.5
       os: linux
       compiler: gcc
+      env: WAFOPTS="--build-type=debug"
     - python: 2.7
       os: linux
       compiler: gcc
-    - language: C
-      os: osx
-      osx_image: xcode8
-      compiler: clang
-    - python: 3.5
+    - python: "pypy3.5"
       os: linux
       compiler: gcc
-      env: CFLAGS="-Os" WAFOPTS="--disable-samplerate --disable-sndfile"
-    - python: 3.4
+      env: CFLAGS="-Os" WAFOPTS="--disable-avcodec"
+    - python: 3.6
+      os: linux
+      compiler: gcc
+      env: CFLAGS="-Os" WAFOPTS="--disable-samplerate"
+    - python: 3.5
       os: linux
       compiler: gcc
       env: HAVE_AUBIO_DOUBLE=1 CFLAGS="-O3" WAFOPTS="--enable-fftw3"
@@ -29,37 +30,21 @@ matrix:
       env: CFLAGS="`dpkg-buildflags --get CFLAGS`" LDFLAGS="`dpkg-buildflags --get LDFLAGS`"
     - language: C
       os: osx
-      osx_image: xcode8
       compiler: clang
-      env: CFLAGS="-Os" HAVE_AUBIO_DOUBLE=1 WAFOPTS="--disable-accelerate"
     - language: C
       os: osx
-      osx_image: xcode8
       compiler: clang
-      env: WAFOPTS="--enable-fat --disable-avcodec --disable-sndfile"
-    - language: C
-      os: osx
-      osx_image: xcode8
-      compiler: clang
-      env: WAFOPTS="--with-target-platform=ios --disable-avcodec --disable-sndfile" AUBIO_NOTESTS=1
-    - language: C
-      os: osx
-      osx_image: xcode8
-      compiler: clang
-      env: WAFOPTS="--with-target-platform=iosimulator --disable-avcodec --disable-sndfile" AUBIO_NOTESTS=1
+      env: CFLAGS="-Os" HAVE_AUBIO_DOUBLE=1 WAFOPTS="--disable-accelerate"
     - language: C
       os: osx
-      osx_image: xcode8.2
       compiler: clang
       env: WAFOPTS="--enable-fat --disable-avcodec --disable-sndfile"
     - language: C
       os: osx
-      osx_image: xcode8.2
       compiler: clang
       env: WAFOPTS="--with-target-platform=ios --disable-avcodec --disable-sndfile" AUBIO_NOTESTS=1
     - language: C
       os: osx
-      osx_image: xcode8.2
       compiler: clang
       env: WAFOPTS="--with-target-platform=iosimulator --disable-avcodec --disable-sndfile" AUBIO_NOTESTS=1
 
@@ -82,25 +67,30 @@ addons:
     - libfftw3-dev
     - sox
     - lcov
+  homebrew:
+    packages:
+    - sox
+    - ffmpeg
+    - libsndfile
+    - lcov
+    #update: true
 
 before_install:
    - |
      if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
-       brew update
-       brew install sox
-       brew install ffmpeg
-       brew install libsndfile
-       brew install lcov
        export PATH="$HOME/Library/Python/2.7/bin/:$PATH"
      fi;
 
 install:
+  - |
+    if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
+      alias pip=pip2
+    fi;
   - travis_retry pip install --upgrade pip
   - travis_retry make getwaf expandwaf deps_python
   - which pip
   - pip --version
-  - pip install python-coveralls
-  - gem install coveralls-lcov
+  - pip install coverage
 
 script:
   - make create_test_sounds
@@ -115,10 +105,6 @@ script:
 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
index eadfda4..24b0af0 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,246 @@
+2018-12-19 Paul Brossier <piem@aubio.org>
+
+       [ Overview ]
+
+       * VERSION: bump to 0.4.9
+       * library: improve stability, fixing potential crashes and memory leaks on
+       invalid arguments; improve library messages and reporting of system errors
+       * tests/: major clean-up, check return codes, increase code coverage
+       * python/tests/: switch to pytest (closes gh-163), check emitted warnings
+       * python/: add pages to manual with brief descriptions of classes
+
+       [ Fixes ]
+
+       * security: improve arguments validation in new_aubio_filterbank (prevent
+       possible null-pointer dereference on invalid n_filters, CVE-2018-19801),
+       new_aubio-tempo (prevent possible buffer overflow, CVE-2018-19800), and
+       new_aubio_onset (prevent null-pointer dereference, CVE-2018-19802). Thanks
+       to Guoxiang Niu (@niugx), from the EaglEye Team for reporting these issues.
+       * tempo: fix delay_ms methods
+       * filterbank: fix aubio_filterbank_get_power (thanks to @romanbsd who
+       also noticed this issue)
+       * dct: creation fail on negative sizes or invalid accelerate radix,
+       fix typo in error and warning messages, prevent possible memory leak
+       * pitch: prevent null pointer dereference in yinfast, comment out unused
+       functions in mcomb and yin, prevent possible leak in specacf
+       * mfcc: always use dct module, strengthen input validation, change
+       get_{scale,power} to return smpl_t
+       * specdesc: improve error message
+       * notes: prevent null pointer dereference
+       * hist: add validation for size argument, prevent possible leak
+       * awhitening: use shortest length available (closes gh-216)
+       * io: add macros to display system errors, add helpers to validate input
+       arguments of source and sink methods, always clean-up after failure
+       * source: validate input sizes to prevent invalid reads
+       * apple_audio: use native format conversions in source and sink, prevent
+       possible apple_audio crash on empty string, get_duration returns 0 on failure
+       * ffmpeg/avcodec: prevent deprecation warnings, read after close, and skipped
+       samples warnings, improve warning messages, only show a warning when
+       swr_convert failed, prevent possible memory leak when closing swr context
+       * wavwrite: copy to all channels if needed, check fseek and fwrite return
+       values, call fflush in open to return failure on full disk-system
+       * source_sndfile: fix reading sizes when resampling, set error message when
+       reading after close
+       * aubio_priv.h: include blas first (see gh-225), add STRERROR macros
+
+       [ Python ]
+
+       * documentation: add pages to manual, add minimal docstrings for fft,
+       digital_filter, and generated objects, improve specdesc documentation
+       * filterbank: add get_norm/power documentation
+       * source: take a copy of the last frame before resizing it, raise an
+       exception when read failed, fix compilation warning
+       * fixes: remove unneeded check convert with PyFloat_FromDouble or
+       PyFloat_FromDouble, check if sink, digital_filter, were created before
+       deleting
+
+       [ Tests ]
+
+       * python/tests/: switch to pytest (slightly slower than nose2 but better at
+       capturing warnings and parametrization), improve coding style and coverage.
+       Tests should now be run with `pytest`.
+       * tests/: Each test program in C must now return 0, otherwise the test will
+       fail. Examples have been modified to run themselves on a test audio file,
+       but can still be run with arguments. Tests for `source` and `sink` have been
+       factorised, and some code cleaning. A python script is used to create a
+       test sound file. Tested on linux, macos, and windows, improvements to
+       test-mfcc (closes gh-219).
+
+       [ Build system ]
+
+       * waf: upgrade to 2.0.14, check the return code of each test program,
+       update rules to build manual and api documentation into build/, check
+       for errno.h
+       * osx: use -Os in scripts/build_apple_frameworks
+       * Makefile: improve coverage reports
+       * appveyor, travis, circleci: switch to pytest, set one travis config to use
+       sndfile only
+       * travis: add py3.6, drop py3.4, use py3.5 to test debug mode
+       * azure: add basic configuration
+
+2018-11-21 Paul Brossier <piem@aubio.org>
+
+       [ Overview ]
+
+       * VERSION: bump to 0.4.8
+       * notes: new option release_drop (gh-203)
+       * spectral: new parameters added to filterbank and mfcc (gh-206)
+       * python: start documenting module (gh-73, debian #480018), improve build for
+       win-amd64 (gh-154, gh-199, gh-208)
+       * fixes: prevent crash when using fft sizes unsupported by vDSP (gh-207),
+       prevent saturation when down-mixing a multi-channel source (avcodec/ffmpeg)
+
+       [ Fixes ]
+
+       * avcodec: prevent saturation when down-mixing a multi-channel source, emit
+       a warning if compiling against avutil < 53 (gh-137), wrap long lines
+       * examples/: avoid hiding global and unreachable code
+       * fft: limit to r*2*n sizes, with r in [1, 3, 5, 15] (vDSP only) (gh-207)
+       * fft: fix reconstruction for odd sizes (fftw only)
+       * pvoc: add missing implementations for aubio_pvoc_get_hop/win
+       * mathutils: increase ln(2) precision of in freqtomidi/miditofreq
+       * wavetable: stop sets playing to 0, add dummy implementation for _load
+
+       [ New features ]
+
+       * src/musicutils.h: new aubio_meltohz, aubio_hztomel, with _htk versions
+       * src/spectral/filterbank.h: new set_mel_coeffs, set_mel_coeffs_htk,
+       set_power, and set_norm methods, improved set_triangle_bands
+       * src/spectral/mfcc.h: new set_scale, set_power, set_norm, set_mel_coeffs,
+       set_mel_coeffs_htk, set_mel_coeffs_slaney
+       * src/mathutils.h: new fvec_mul
+       * src/notes: new option release_drop to prevent missing note-offs (gh-203)
+
+       [ Python module ]
+
+       * fix: rounding to nearest integer in midi2note and freq2note
+       * general: supports code generation of setters with none or multiple
+       parameters
+       * documentation: add docstrings do fvec, cvec, source, sink, pvoc, frequency
+       conversion and level detection routines (gh-73, debian #480018)
+       * slicing: improve and document slice_source_at_stamps
+       * module: new note2freq function, recover error log when raising exceptions
+       on failed set_ methods, prevent cyclic import, coding style improvements
+       * demos: improve coding style, fix bpm_extract arguments
+       * MANIFEST.in: exclude *.pyc, improve patterns
+
+       [ Documentation ]
+
+       * doc/: use sphinx autodoc to load docstrings from aubio module, reorganize
+       python module documentation, add a note about double precision, use https
+       when possible
+       * src/spectral/: update Auditory Toolbox url, update copyright year
+
+       [ Tools ]
+
+       * aubionotes: add --release-drop option
+       * aubio: add --release-drop and --silence options to `aubio notes`,
+       workaround for -V to really show version (py2)
+       * aubiocut: add option --create-first to always create first slice
+
+       [ Tests ]
+
+       * tests/, python/tests: add tests for new methods, check source channel
+       down-mix, improve coverage
+
+       [ Build system ]
+
+       * Makefile: disable docs when measuring coverage, add branch coverage
+       option, add coverage_zero_counters target, improve html report
+       * waf: update to 2.0.12, improve wscript style, prevent shipping some
+       generated files
+       * python: always show compiler warnings when pre-processing headers,
+       workaround to fix code generation for win-amd64 (gh-154, gh-199, gh-208).
+       * continuous integration: add azure pipelines, update and improve
+       configurations for appveyor, circleci, and travis.
+
+2018-09-22 Paul Brossier <piem@aubio.org>
+
+       [ Overview ]
+
+       * VERSION: bump to 0.4.7
+       * src/spectral/dct.h: add dct type II object with optimised versions
+       * src/io/, src/notes/, src/pitch: prevent crashes on corrupted files
+       * examples/: fix jack midi output, improve messages when jack disabled
+       * python/: add dct support, minor bug fixes tests and demos
+       * wscript: improve support for BLAS/ATLAS
+
+       [ Library fixes ]
+
+       * src/pitch/pitchyinfft.c: fix out of bound read when samplerate > 50kHz
+       thanks to @fCorleone (closes #189, CVE-2018-14523, debian #904906)
+       * src/notes/notes.c: bail out if pitch creation failed (see #188)
+       * src/io/source_wavread.c:
+        - also exit if samplerate is negative (closes #188, CVE-2018-14522,
+        debian #904907)
+        - add some input validation (closes #148 and #158, CVE-2017-17054,
+        debian #883355)
+       * src/io/source_avcodec.c:
+        - give up if resampling context failed opening (see #137, closes #187,
+        CVE-2018-14521, debian #904908)
+        - give up reading file if number of channel changes during stream (closes
+        #137, CVE-2017-17554, debian #884237)
+        - make sure libavutil > 52 before checking avFrame->channels (see #137)
+        - fix build with ffmpeg 4.0, thanks to @jcowgill (closes #168, #173)
+        - avoid deprecated call for ffmpeg >= 4.0
+       * src/onset/onset.c: add dummy default parameters for wphase (closes #150)
+
+       [ Tools ]
+
+       * examples/parse_args.h: hide jack options if not available, improve error
+       message (closes #182)
+       * examples/utils.h: process_block returns void
+       * examples/utils.c: fix examples failing to send more than one JACK midi
+       event per frame, thanks to @cyclopsian (closes #201)
+
+       [ New features ]
+
+       * src/spectral/dct.h: add dct type II object with implementation factory
+       * src/spectral/dct_plain.c: add plain dct implementation
+       * src/spectral/dct_ooura.c: add ooura implementation
+       * src/spectral/dct_fftw.c: add fftw implementation
+       * src/spectral/dct_ipp.c: add ipp version
+       * src/spectral/dct_accelerate.c: add vdsp/accelerate dct
+       * tests/src/spectral/test-dct.c: check reconstruction works
+       * src/spectral/mfcc.c: use new dct to compute mfcc
+
+       [ Library internals ]
+
+       * src/aubio_priv.h: avoid hard-coded undefs, split BLAS and ATLAS support,
+       add vdsp scalar add and multiply
+
+       [ Build system ]
+
+       * wscript:
+        - add options to disable examples and tests
+        - detect includes for openblas/libblas/atlas
+       * scripts/get_waf.sh: bump to 2.0.11, verify signature if gpg available
+       * python/lib/gen_external.py: pass '-x c' to emcc only
+
+       [ Python ]
+
+       * python/lib/gen_code.py: add support for rdo methods
+       * python/tests/test_dct.py: add tests for new dct
+       * python/demos/demo_pitch_sinusoid.py: use // to yield an integer, fixing
+       demo on py3, thanks to @ancorcruz (closes #176)
+       * python/ext/py-musicutils.*: add shift(fvec) and ishift(fvec)
+       * python/tests/test_fvec_shift.py: add tests for shift() and ishift()
+       * python/lib/aubio/cmd.py: fix typo in comment
+
+       [ Documentation ]
+
+       * README.md, doc/statuslinks.rst: use latest for commits-since
+       * examples/parse_args.h: add yinfast to pitch algorithms
+       * doc/requirements.rst: add some blas documentation
+       * doc/requirements.rst: split media/optimisation libraries
+       * doc/develop.rst: fixed spelling error, thanks to Jon Williams (closes #161)
+       * doc/aubio{pitch,notes}.txt: add yinfast to list of pitch methods
+
+       [ Continuous integration ]
+
+       * .travis.yml: remove xcode8.2 builds, group osx, add alias pip=pip2
+       * .appveyor.yml: upgrade pip first, always use python -m pip
+
 2017-10-02 Paul Brossier <piem@aubio.org>
 
        [ Overview ]
index 745c180..85edbdf 100644 (file)
@@ -1,26 +1,20 @@
 include AUTHORS COPYING README.md VERSION ChangeLog
 include python/README.md
 include this_version.py
+include waf_gensyms.py
+include waf
+recursive-include waflib *.py
 include Makefile wscript */wscript_build
-include waf waflib/* waflib/*/*
-exclude waflib/__pycache__/*
 include aubio.pc.in
-include nose2.cfg
 include requirements.txt
 include src/*.c src/*.h
 include src/*/*.c src/*/*.h
 include examples/*.c examples/*.h
-include tests/*.h tests/*/*.c tests/*/*/*.c
+recursive-include tests *.h *.c *.py
 include python/ext/*.h
-include python/__init__.py
-include python/lib/__init__.py
-include python/lib/moresetuptools.py
-include python/lib/gen_external.py
-include python/lib/gen_code.py
-include python/tests/run_all_tests
-include python/tests/*.py
+recursive-include python *.py
+include python/README.md
 include python/tests/eval_pitch
-include python/demos/*.py
 include python/tests/*.expected
 include doc/*.txt doc/*.rst doc/*.cfg doc/Makefile doc/make.bat doc/conf.py
 exclude doc/full.cfg
index f8af602..a21c0d6 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -35,13 +35,15 @@ INCLUDEDIR?=$(PREFIX)/include
 DATAROOTDIR?=$(PREFIX)/share
 MANDIR?=$(DATAROOTDIR)/man
 
-# default nose2 command
-NOSE2?=nose2 -N 4 --verbose
+# default python test command
+PYTEST?=pytest --verbose
 
 SOX=sox
 
 TESTSOUNDS := python/tests/sounds
 
+LCOVOPTS += --rc lcov_branch_coverage=1
+
 all: build
 
 checkwaf:
@@ -138,9 +140,7 @@ 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
-       # run with nose2, multiple processes
-       $(NOSE2)
+       $(PYTEST)
 
 clean_python:
        ./setup.py clean
@@ -234,26 +234,45 @@ test_python_only_clean: test_python_only \
        uninstall_python \
        check_clean_python
 
+coverage_cycle: coverage_zero_counters coverage_report
+
+coverage_zero_counters:
+       lcov --zerocounters --directory .
+
 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
+       # capture coverage after running c tests
+       lcov $(LCOVOPTS) --capture --no-external --directory . \
+               --output-file build/coverage_lib.info
+       # build and test python
        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
-
+       # run tests, with python coverage
+       coverage run `which pytest`
+       # capture coverage again
+       lcov $(LCOVOPTS) --capture --no-external --directory . \
+               --output-file build/coverage_python.info
+       # merge both coverage info files
+       lcov $(LCOVOPTS) -a build/coverage_python.info -a build/coverage_lib.info \
+               --output-file build/coverage.info
+       # remove tests
+       lcov $(LCOVOPTS) --remove build/coverage.info '*/ooura_fft8g*' \
+               --output-file build/coverage_lib.info
+
+# make sure we don't build the doc, which builds a temporary python module
+coverage_report: export WAFOPTS += --disable-docs
 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/.*"
+       # generate report with lcov's genhtml
+       genhtml build/coverage_lib.info --output-directory build/coverage_c \
+               --branch-coverage --highlight --legend
+       # generate python report with coverage python package
        coverage report
-       coverage html
+       coverage html -d build/coverage_python
+       # show links to generated reports
+       for i in $$(ls build/coverage_*/index.html); do echo file://$(PWD)/$$i; done
 
 sphinx: configure
        $(WAFCMD) sphinx $(WAFOPTS)
index b3ca16f..45db911 100644 (file)
--- a/README.md
+++ b/README.md
@@ -3,8 +3,7 @@ aubio
 
 [![Travis build status](https://travis-ci.org/aubio/aubio.svg?branch=master)](https://travis-ci.org/aubio/aubio "Travis build status")
 [![Appveyor build status](https://img.shields.io/appveyor/ci/piem/aubio/master.svg)](https://ci.appveyor.com/project/piem/aubio "Appveyor build status")
-[![Landscape code health](https://landscape.io/github/aubio/aubio/master/landscape.svg?style=flat)](https://landscape.io/github/aubio/aubio/master "Landscape code health")
-[![Commits since last release](https://img.shields.io/github/commits-since/aubio/aubio/0.4.6.svg)](https://github.com/aubio/aubio "Commits since last release")
+[![Commits since last release](https://img.shields.io/github/commits-since/aubio/aubio/latest.svg)](https://github.com/aubio/aubio "Commits since last release")
 
 [![Documentation](https://readthedocs.org/projects/aubio/badge/?version=latest)](http://aubio.readthedocs.io/en/latest/?badge=latest "Latest documentation")
 [![DOI](https://zenodo.org/badge/396389.svg)](https://zenodo.org/badge/latestdoi/396389)
diff --git a/VERSION b/VERSION
index 768e642..8fa7caa 100644 (file)
--- a/VERSION
+++ b/VERSION
@@ -1,7 +1,7 @@
 AUBIO_MAJOR_VERSION=0
-AUBIO_MINOR_VERSION=4
-AUBIO_PATCH_VERSION=7
+AUBIO_MINOR_VERSION=5
+AUBIO_PATCH_VERSION=0
 AUBIO_VERSION_STATUS='~alpha'
 LIBAUBIO_LT_CUR=5
-LIBAUBIO_LT_REV=3
-LIBAUBIO_LT_AGE=7
+LIBAUBIO_LT_REV=4
+LIBAUBIO_LT_AGE=8
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
new file mode 100644 (file)
index 0000000..277140c
--- /dev/null
@@ -0,0 +1,38 @@
+#  configuration file for azure continuous integration
+jobs:
+
+- job: linux
+  pool:
+    vmImage: 'ubuntu-16.04'
+  steps:
+  - script: |
+      make
+    displayName: 'make'
+    env:
+      CFLAGS: -Werror
+
+- job: windows
+  pool:
+    vmImage: 'vs2017-win2016'
+  steps:
+  - script: |
+      make
+    displayName: 'make'
+    env:
+      # fail on error
+      CFLAGS: /WX
+
+- job: macos
+  pool:
+    vmImage: 'macos-10.13'
+  steps:
+  - script: |
+      brew update
+      brew install pkg-config gnupg
+      brew install sox ffmpeg libsndfile lcov
+    displayName: 'brew install'
+  - script: |
+      make
+    displayName: 'make'
+    env:
+      CFLAGS: -Werror
diff --git a/circle.yml b/circle.yml
deleted file mode 100644 (file)
index 1d8d29c..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-dependencies:
-  pre:
-    - sudo apt-get update; sudo apt-get install make sox pkg-config libavcodec-dev libavformat-dev libavresample-dev libavutil-dev libsndfile1-dev libsamplerate-dev
-
-test:
-  pre:
-    - make create_test_sounds
-  override:
-    - nose2 -v
index b4996ea..65083f8 100644 (file)
@@ -59,7 +59,7 @@ Copyright © 2003-2017 Paul Brossier <piem@aubio.org>
 License
 -------
 
-aubio is a `free <http://www.debian.org/intro/free>`_ and `open source
+aubio is a `free <https://www.debian.org/intro/free>`_ and `open source
 <http://www.opensource.org/docs/definition.php>`_ software; **you** can
 redistribute it and/or modify it under the terms of the `GNU
 <https://www.gnu.org/>`_ `General Public License
index bee9eb1..cd1138f 100644 (file)
@@ -98,7 +98,13 @@ TEMPO
 
 NOTES
 
-  The "note" command accepts all common options and no additional options.
+  The following additional options can be used with the "notes" subcommand.
+
+  -s <value>, --silence <value>  silence threshold, in dB (default: -70)
+
+  -d <value>, --release-drop <value>  release drop level, in dB. If the level
+  drops more than this amount since the last note started, the note will be
+  turned off (default: 10).
 
 MFCC
 
index 8023460..c578133 100644 (file)
@@ -50,6 +50,8 @@ OPTIONS
   --cut-until-nslices n  How many extra slices should be added at the end of
   each slice (default 0).
 
+  --create-first  Alway create first slice.
+
   -h, --help  Print a short help message and exit.
 
   -v, --verbose  Be verbose.
index 245e352..7ef93f7 100644 (file)
@@ -51,7 +51,7 @@ REFERENCES
   according to Malcolm Slaney's Auditory Toolbox, available at the following
   url:
 
-  http://cobweb.ecn.purdue.edu/~malcolm/interval/1998-010/ (see file mfcc.m)
+  https://engineering.purdue.edu/~malcolm/interval/1998-010/ (see file mfcc.m)
 
 SEE ALSO
 
index 0355040..3701056 100644 (file)
@@ -6,7 +6,7 @@ SYNOPSIS
   aubionotes source
   aubionotes [[-i] source]
              [-r rate] [-B win] [-H hop]
-             [-O method] [-t thres]
+             [-O method] [-t thres] [-d drop]
              [-p method] [-u unit] [-l thres]
              [-T time-format]
              [-s sil]
@@ -68,6 +68,10 @@ OPTIONS
   will not be detected. A value of -20.0 would eliminate most onsets but the
   loudest ones. A value of -90.0 would select all onsets. Defaults to -90.0.
 
+  -d, --release-drop  Set the release drop threshold, in dB. If the level drops
+  more than this amount since the last note started, the note will be turned
+  off. Defaults to 10.
+
   -T, --timeformat format  Set time format (samples, ms, seconds). Defaults to
   seconds.
 
@@ -87,7 +91,8 @@ ONSET METHODS
 
 PITCH METHODS
 
-  Available methods: default, schmitt, fcomb, mcomb, specacf, yin, yinfft.
+  Available methods: default, schmitt, fcomb, mcomb, specacf, yin, yinfft,
+  yinfast.
 
   See aubiopitch(1) for details about these methods.
 
index 1313975..a521ce1 100644 (file)
@@ -120,6 +120,12 @@ PITCH METHODS
   Chapter 3, Pitch Analysis, PhD thesis, Centre for Digital music, Queen Mary
   University of London, London, UK, 2006.
 
+  yinfast  YIN algorithm (accelerated)
+
+  An optimised implementation of the YIN algorithm, yielding results identical
+  to the original YIN algorithm, while reducing its computational cost from
+  O(n^2) to O(n log(n)).
+
 SEE ALSO
 
   aubioonset(1),
index 0746971..7d491b0 100644 (file)
@@ -29,7 +29,14 @@ from this_version import get_aubio_version
 
 # Add any Sphinx extension module names here, as strings. They can be extensions
 # coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
-extensions = ['sphinx.ext.viewcode', 'sphinx.ext.autodoc']
+extensions = ['sphinx.ext.viewcode', 'sphinx.ext.autodoc',
+        'sphinx.ext.napoleon', 'sphinx.ext.intersphinx']
+
+autodoc_member_order = 'groupwise'
+
+intersphinx_mapping = {
+        'numpy': ('https://docs.scipy.org/doc/numpy/', None),
+        }
 
 # Add any paths that contain templates here, relative to this directory.
 templates_path = ['_templates']
@@ -45,7 +52,7 @@ master_doc = 'index'
 
 # General information about the project.
 project = u'aubio'
-copyright = u'2016, Paul Brossier'
+copyright = u'2018, Paul Brossier'
 
 # The version info for the project you're documenting, acts as replacement for
 # |version| and |release|, also used in various other places throughout the
index 6680f1a..9bc7ab7 100644 (file)
@@ -9,7 +9,7 @@ For the latest version of the packages, see
 https://anonscm.debian.org/cgit/collab-maint/aubio.git/. Use
 ``git-buildpackage`` to build from the git repository. For instance:
 
-.. code-block:: bash
+.. code-block:: console
 
     $ git clone git://anonscm.debian.org/collab-maint/aubio.git
     $ cd aubio
index f127ca9..fa27db0 100644 (file)
@@ -1,7 +1,7 @@
 .. _develop:
 
-Developping with aubio
-======================
+Developing with aubio
+=====================
 
 Here is a brief overview of the C library.
 
index 4fc80de..5581f73 100644 (file)
@@ -70,6 +70,7 @@ Content
 
    installing
    python_module
+   python
    cli
    develop
    about
index 69c5d78..2c10412 100644 (file)
@@ -31,7 +31,7 @@ Cheat sheet
     ./waf build
     sudo ./waf install
 
-- :ref:`install python-aubio from source <python>`::
+- :ref:`install python-aubio from source <python-install>`::
 
     # from git
     pip install git+https://git.aubio.org/aubio/aubio/
@@ -45,7 +45,7 @@ Cheat sheet
     cd aubio
     pip install -v .
 
-- :ref:`install python-aubio from a pre-compiled binary <python>`::
+- :ref:`install python-aubio from a pre-compiled binary <python-install>`::
 
       # conda [osx, linux, win]
       conda install -c conda-forge aubio
diff --git a/doc/py_analysis.rst b/doc/py_analysis.rst
new file mode 100644 (file)
index 0000000..47d7ab1
--- /dev/null
@@ -0,0 +1,15 @@
+.. currentmodule:: aubio
+.. default-domain:: py
+
+Analysis
+--------
+
+.. members of generated classes are not shown
+
+.. autoclass:: onset
+
+.. autoclass:: pitch
+
+.. autoclass:: tempo
+
+.. autoclass:: notes
diff --git a/doc/py_datatypes.rst b/doc/py_datatypes.rst
new file mode 100644 (file)
index 0000000..21fad69
--- /dev/null
@@ -0,0 +1,43 @@
+.. default-domain:: py
+.. currentmodule:: aubio
+
+Data-types
+----------
+
+This section contains the documentation for :data:`float_type`,
+:class:`fvec`, and :class:`cvec`.
+
+.. defined in rst only
+
+.. data:: float_type
+
+    A string constant describing the floating-point representation used in
+    :class:`fvec`, :class:`cvec`, and elsewhere in this module.
+
+    Defaults to `"float32"`.
+
+    If `aubio` was built specifically with the option `--enable-double`, this
+    string will be defined to `"float64"`. See :ref:`py-doubleprecision` in
+    :ref:`python-install` for more details on building aubio in double
+    precision mode.
+
+    .. rubric:: Examples
+
+    >>> aubio.float_type
+    'float32'
+    >>> numpy.zeros(10).dtype
+    'float64'
+    >>> aubio.fvec(10).dtype
+    'float32'
+    >>> np.arange(10, dtype=aubio.float_type).dtype
+    'float32'
+
+.. defined in `python/lib/aubio/__init__.py`
+
+.. autoclass:: fvec
+  :members:
+
+.. defined in `python/ext/py-cvec.h`
+
+.. autoclass:: cvec
+  :members:
diff --git a/doc/py_examples.rst b/doc/py_examples.rst
new file mode 100644 (file)
index 0000000..cd20901
--- /dev/null
@@ -0,0 +1,42 @@
+.. default-domain:: py
+.. currentmodule:: aubio
+
+Examples
+--------
+
+Below is a short selection of examples using the aubio module.
+
+Read a sound file
+.................
+
+Here is a simple script, :download:`demo_source_simple.py
+<../python/demos/demo_source_simple.py>` that reads all the samples from a
+media file using :class:`source`:
+
+.. literalinclude:: ../python/demos/demo_source_simple.py
+   :language: python
+
+Filter a sound file
+...................
+
+Here is another example, :download:`demo_filter.py
+<../python/demos/demo_filter.py>`, which applies a filter to a sound file
+and writes the filtered signal in another file:
+
+* read audio samples from a file with :class:`source`
+
+* filter them using an `A-weighting <https://en.wikipedia.org/wiki/A-weighting>`_
+  filter using :class:`digital_filter`
+
+* write the filtered samples to a new file with :class:`sink`.
+
+.. literalinclude:: ../python/demos/demo_filter.py
+   :language: python
+
+More examples
+.............
+
+For more examples showing how to use other components of the module, see
+the `python demos folder`_.
+
+.. _python demos folder: https://github.com/aubio/aubio/blob/master/python/demos
diff --git a/doc/py_io.rst b/doc/py_io.rst
new file mode 100644 (file)
index 0000000..6be251f
--- /dev/null
@@ -0,0 +1,118 @@
+.. currentmodule:: aubio
+.. default-domain:: py
+
+Input/Output
+------------
+
+This section contains the documentation for two classes:
+:class:`source`, to read audio samples from files, and :class:`sink`,
+to write audio samples to disk.
+
+.. defined in `python/ext`
+
+..
+   Note: __call__ docstrings of objects defined in C must be written
+   specifically in RST, since there is no known way to add them to
+   their C implementation.
+
+..
+   TODO: remove special-members documentation
+
+.. defined in py-source.c
+
+.. autoclass:: source
+  :members:
+  :special-members: __enter__
+  :no-special-members:
+
+  .. function:: __call__()
+
+    Read at most `hop_size` new samples from self, return them in
+    a tuple with the number of samples actually read.
+
+    The returned tuple contains:
+
+    - a vector of shape `(hop_size,)`, filled with the `read` next
+      samples available, zero-padded if `read < hop_size`
+    - `read`, an integer indicating the number of samples read
+
+    If opened with more than one channel, the frames will be
+    down-mixed to produce the new samples.
+
+    :returns: A tuple of one array of samples and one integer.
+    :rtype: (array, int)
+
+    .. seealso:: :meth:`__next__`
+
+    .. rubric:: Example
+
+    >>> src = aubio.source('stereo.wav')
+    >>> while True:
+    ...     samples, read = src()
+    ...     if read < src.hop_size:
+    ...         break
+
+  .. function:: __next__()
+
+    Read at most `hop_size` new frames from self, return them in
+    an array.
+
+    If source was opened with one channel, next(self) returns
+    an array of shape `(read,)`, where `read` is the actual
+    number of frames read (`0 <= read <= hop_size`).
+
+    If `source` was opened with more then one channel, the
+    returned arrays will be of shape `(channels, read)`, where
+    `read` is the actual number of frames read (`0 <= read <=
+    hop_size`).
+
+    :return: A tuple of one array of frames and one integer.
+    :rtype: (array, int)
+
+    .. seealso:: :meth:`__call__`
+
+    .. rubric:: Example
+
+    >>> for frames in aubio.source('song.flac')
+    ...     print(samples.shape)
+
+  .. function:: __iter__()
+
+    Implement iter(self).
+
+    .. seealso:: :meth:`__next__`
+
+  .. function:: __enter__()
+
+    Implement context manager interface. The file will be opened
+    upon entering the context. See `with` statement.
+
+    .. rubric:: Example
+
+    >>> with aubio.source('loop.ogg') as src:
+    ...     src.uri, src.samplerate, src.channels
+
+  .. function:: __exit__()
+
+    Implement context manager interface. The file will be closed
+    before exiting the context. See `with` statement.
+
+    .. seealso:: :meth:`__enter__`
+
+.. py-sink.c
+   TODO: remove special-members documentation
+
+.. autoclass:: aubio.sink
+  :members:
+
+  .. function:: __call__(vec, length)
+
+    Write `length` samples from `vec`.
+
+    :param array vec: input vector to write from
+    :param int length: number of samples to write
+    :example:
+
+    >>> with aubio.sink('foo.wav') as snk:
+    ...     snk(aubio.fvec(1025), 1025)
+
diff --git a/doc/py_spectral.rst b/doc/py_spectral.rst
new file mode 100644 (file)
index 0000000..1697041
--- /dev/null
@@ -0,0 +1,34 @@
+.. currentmodule:: aubio
+.. default-domain:: py
+
+.. members of generated classes are not yet documented
+
+Spectral features
+-----------------
+
+This section contains the documentation for:
+
+- :class:`dct`
+- :class:`fft`
+- :class:`filterbank`
+- :class:`mfcc`
+- :class:`pvoc`
+- :class:`specdesc`
+- :class:`tss`
+
+.. autoclass:: dct
+
+.. autoclass:: fft
+  :members:
+
+.. autoclass:: filterbank
+  :members:
+
+.. autoclass:: mfcc
+
+.. autoclass:: pvoc
+  :members:
+
+.. autoclass:: specdesc
+
+.. autoclass:: tss
diff --git a/doc/py_synth.rst b/doc/py_synth.rst
new file mode 100644 (file)
index 0000000..d7afb24
--- /dev/null
@@ -0,0 +1,9 @@
+.. currentmodule:: aubio
+.. default-domain:: py
+
+Synthesis
+---------
+
+.. autoclass:: sampler
+
+.. autoclass:: wavetable
diff --git a/doc/py_temporal.rst b/doc/py_temporal.rst
new file mode 100644 (file)
index 0000000..9a0bb39
--- /dev/null
@@ -0,0 +1,8 @@
+.. currentmodule:: aubio
+.. default-domain:: py
+
+Digital filters
+---------------
+
+.. autoclass:: digital_filter
+  :members:
diff --git a/doc/py_utils.rst b/doc/py_utils.rst
new file mode 100644 (file)
index 0000000..94debf8
--- /dev/null
@@ -0,0 +1,84 @@
+.. default-domain:: py
+.. currentmodule:: aubio
+
+Utilities
+---------
+
+This section documents various helper functions included in the aubio library.
+
+Note name conversion
+....................
+
+.. midiconv.py
+
+.. autofunction:: note2midi
+
+.. autofunction:: midi2note
+
+.. autofunction:: freq2note
+
+.. autofunction:: note2freq
+
+Frequency conversion
+....................
+
+.. python/ext/ufuncs.c
+
+.. autofunction:: freqtomidi
+
+.. autofunction:: miditofreq
+
+.. python/ext/py-musicutils.h
+
+.. autofunction:: meltohz
+
+.. autofunction:: hztomel
+
+.. python/ext/aubiomodule.c
+
+.. autofunction:: bintomidi
+.. autofunction:: miditobin
+.. autofunction:: bintofreq
+.. autofunction:: freqtobin
+
+Audio file slicing
+..................
+
+.. slicing.py
+
+.. autofunction:: slice_source_at_stamps
+
+Windowing
+.........
+
+.. python/ext/py-musicutils.h
+
+.. autofunction:: window
+
+Audio level detection
+.....................
+
+.. python/ext/py-musicutils.h
+
+.. autofunction:: level_lin
+.. autofunction:: db_spl
+.. autofunction:: silence_detection
+.. autofunction:: level_detection
+
+Vector utilities
+................
+
+.. python/ext/aubiomodule.c
+
+.. autofunction:: alpha_norm
+.. autofunction:: zero_crossing_rate
+.. autofunction:: min_removal
+
+.. python/ext/py-musicutils.h
+
+.. autofunction:: shift
+.. autofunction:: ishift
+
+.. python/ext/ufuncs.c
+
+.. autofunction:: unwrap2pi
diff --git a/doc/python.rst b/doc/python.rst
new file mode 100644 (file)
index 0000000..cc91244
--- /dev/null
@@ -0,0 +1,59 @@
+.. make sure our default-domain is python here
+.. default-domain:: py
+
+.. set current module
+.. currentmodule:: aubio
+
+..
+   we follow numpy type docstrings, see:
+   https://numpydoc.readthedocs.io/en/latest/format.html#docstring-standard
+..
+   note: we do not import aubio's docstring, which will be displayed from an
+   interpreter.
+
+.. .. automodule:: aubio
+
+
+.. _python:
+
+Python documentation
+====================
+
+This module provides a number of classes and functions for the analysis of
+music and audio signals.
+
+Contents
+--------
+
+.. toctree::
+   :maxdepth: 1
+
+   py_datatypes
+   py_io
+   py_temporal
+   py_spectral
+   py_analysis
+   py_synth
+   py_utils
+   py_examples
+
+Introduction
+------------
+
+This document provides a reference guide. For documentation on how to
+install aubio, see :ref:`python-install`.
+
+Examples included in this guide and within the code are written assuming
+both `aubio` and `numpy`_ have been imported:
+
+.. code-block:: python
+
+    >>> import aubio
+    >>> import numpy as np
+
+`Changed in 0.4.8` :  Prior to this version, almost no documentation was
+provided with the python module. This version adds documentation for some
+classes, including :class:`fvec`, :class:`cvec`, :class:`source`, and
+:class:`sink`.
+
+.. _numpy: https://www.numpy.org
index ed834f5..cd04f18 100644 (file)
-.. _python:
+.. _python-install:
 
-Python module
-=============
+Installing aubio for Python
+===========================
 
-The aubio extension for Python is available for Python 2.7 and Python 3.
+aubio is available as a package for Python 2.7 and Python 3. The aubio
+extension is written C using the `Python/C`_ and the `Numpy/C`_ APIs.
+
+.. _Python/C: https://docs.python.org/c-api/index.html
+.. _Numpy/C: https://docs.scipy.org/doc/numpy/reference/c-api.html
+
+For general documentation on how to install Python packages, see `Installing
+Packages`_.
 
 Installing aubio with pip
 -------------------------
 
-aubio can now be installed using ``pip``:
+aubio can be installed from `PyPI`_ using ``pip``:
 
-.. code-block:: bash
+.. code-block:: console
 
     $ pip install aubio
 
-Building the module
--------------------
+See also `Installing from PyPI`_ for general documentation.
 
-From ``aubio`` source directory, run the following:
+.. note::
 
-.. code-block:: bash
+  aubio is currently a `source only`_ package, so you will need a compiler to
+  install it from `PyPI`_. See also `Installing aubio with conda`_ for
+  pre-compiled binaries.
 
-    $ ./setup.py clean
-    $ ./setup.py build
-    $ sudo ./setup.py install
+.. _PyPI: https://pypi.python.org/pypi/aubio
+.. _Installing Packages: https://packaging.python.org/tutorials/installing-packages/
+.. _Installing from PyPI: https://packaging.python.org/tutorials/installing-packages/#installing-from-pypi
+.. _source only: https://packaging.python.org/tutorials/installing-packages/#source-distributions-vs-wheels
+
+Installing aubio with conda
+---------------------------
 
-Using aubio in python
----------------------
+`Conda packages`_ are available through the `conda-forge`_ channel for Linux,
+macOS, and Windows:
 
-Once you have python-aubio installed, you should be able to run ``python -c
-"import aubio; print(aubio.version)"``.
+.. code-block:: console
 
-A simple example
-................
+    $ conda config --add channels conda-forge
+    $ conda install -c conda-forge aubio
 
-Here is a :download:`simple script <../python/demos/demo_source_simple.py>`
-that reads all the samples from a media file:
+.. _Conda packages: https://anaconda.org/conda-forge/aubio
+.. _conda-forge: https://conda-forge.org/
 
-.. literalinclude:: ../python/demos/demo_source_simple.py
-   :language: python
+.. _py-doubleprecision:
 
-Filtering an input sound file
-.............................
+Double precision
+----------------
 
-Here is a more complete example, :download:`demo_filter.py
-<../python/demos/demo_filter.py>`. This files executes the following:
+This module can be compiled in double-precision mode, in which case the
+default type for floating-point samples will be 64-bit. The default is
+single precision mode (32-bit, recommended).
 
-* read an input media file (``aubio.source``)
+To build the aubio module with double precision, use the option
+`--enable-double` of the `build_ext` subcommand:
+
+.. code:: bash
+
+    $ ./setup.py clean
+    $ ./setup.py build_ext --enable-double
+    $ pip install -v .
 
-* filter it using an `A-weighting <https://en.wikipedia.org/wiki/A-weighting>`_
-  filter (``aubio.digital_filter``)
+**Note**: If linking against `libaubio`, make sure the library was also
+compiled in :ref:`doubleprecision` mode.
 
-* write result to a new file (``aubio.sink``)
 
-.. literalinclude:: ../python/demos/demo_filter.py
-   :language: python
+Checking your installation
+--------------------------
 
-More demos
-..........
+Once the python module is installed, its version can be checked with:
+
+.. code-block:: console
+
+    $ python -c "import aubio; print(aubio.version, aubio.float_type)"
+
+The command line `aubio` is also installed:
+
+.. code-block:: console
+
+    $ aubio -h
 
-Check out the `python demos folder`_ for more examples.
 
 Python tests
 ------------
 
-A number of `python tests`_ are provided. To run them, use
-``python/tests/run_all_tests``.
+A number of Python tests are provided in the `python/tests`_ folder. To run
+them, install `pytest`_ and run it from the aubio source directory:
+
+.. code-block:: console
 
-.. _python demos folder: https://github.com/aubio/aubio/blob/master/python/demos
-.. _demo_filter.py: https://github.com/aubio/aubio/blob/master/python/demos/demo_filter.py
-.. _python tests: https://github.com/aubio/aubio/blob/master/python/tests
+    $ pip install pytest
+    $ git clone https://git.aubio.org/aubio/aubio
+    $ cd aubio
+    $ pytest
 
+.. _python/tests: https://github.com/aubio/aubio/blob/master/python/tests
+.. _pytest: https://pytest.org
index 76d3d67..fcad2d1 100644 (file)
@@ -31,6 +31,9 @@ unusual location.
     If ``pkg-config`` is not found in ``PATH``, the configure step will
     succeed, but none of the external libraries will be used.
 
+Media libraries
+---------------
+
 libav
 .....
 
@@ -78,6 +81,9 @@ To enable this option, configure with ``--enable-samplerate``. The build will
 then fail if the required library is not found. To disable this option,
 configure with ``--disable-samplerate``
 
+Optimisation libraries
+----------------------
+
 libfftw3
 ........
 
@@ -92,6 +98,100 @@ To enable this option, configure with ``--enable-fftw3``. The build will
 then fail if the required library is not found. To disable this option,
 configure with ``--disable-fftw3``
 
+blas
+....
+
+On macOs/iOS, `blas
+<https://en.wikipedia.org/wiki/Basic_Linear_Algebra_Subprograms>`_ are made
+available through the Accelerate framework.
+
+On Linux, they can be enabled with ``--enable-blas``.  On Debian (etch),
+`atlas`_, `openblas`_, and `libblas`_ have been successfully tested.
+
+When enabled, ``waf`` will check for the current blas configuration by running
+``pkg-config --libs blas``. Depending of the library path returned by
+``pkg-config``, different headers will be searched for.
+
+.. note::
+
+    On Debian systems, `multiple versions of BLAS and LAPACK
+    <https://wiki.debian.org/DebianScience/LinearAlgebraLibraries>`_ can be
+    installed. To configure which libblas is being used:
+
+    .. code-block:: console
+
+      $ sudo update-alternatives --config libblas.so
+
+..
+  Expected pkg-config output for each alternative:
+    /usr/lib/atlas-base/atlas/libblas.so
+    -L/usr/lib/atlas-base/atlas -lblas
+    /usr/lib/openblas-base/libblas.so
+    -L/usr/lib/openblas-base -lblas
+    /usr/lib/libblas/libblas.so
+    -lblas
+
+atlas
+.....
+
+`ATLAS BLAS APIs <http://math-atlas.sourceforge.net/>`_ will be used the path
+returned by ``pkg-config --libs blas`` contains ``atlas``.
+
+..
+  ``<atlas/cblas.h>`` will be included.
+
+Example:
+
+.. code-block:: console
+
+  $ pkg-config --libs blas
+  -L/usr/lib/atlas-base/atlas -lblas
+  $ ./waf configure --enable-atlas
+  [...]
+  Checking for 'blas'                      : yes
+  Checking for header atlas/cblas.h        : yes
+
+openblas
+........
+
+`OpenBlas libraries <https://www.openblas.net/>`_ will be used when the output
+of ``pkg-config --libs blas`` contains 'openblas',
+
+..
+  ``<openblas/cblas.h>`` will be included.
+
+Example:
+
+.. code-block:: console
+
+  $ pkg-config --libs blas
+  -L/usr/lib/openblas-base -lblas
+  $ ./waf configure --enable-atlas
+  [...]
+  Checking for 'blas'                      : yes
+  Checking for header openblas/cblas.h     : yes
+
+libblas
+.......
+
+`Netlib's libblas (LAPACK) <https://www.netlib.org/lapack/>`_ will be used if
+no specific library path is specified by ``pkg-config``
+
+..
+  ``<cblas.h>`` will be included.
+
+Example:
+
+.. code-block:: console
+
+  $ pkg-config --libs blas
+  -lblas
+  $ ./waf configure --enable-atlas
+  [...]
+  Checking for 'blas'                      : yes
+  Checking for header cblas.h              : yes
+
+
 Platform notes
 --------------
 
@@ -197,12 +297,43 @@ Here is an example of a custom command:
                 --manpagesdir=/opt/share/man  \
                 uninstall clean distclean dist distcheck
 
+.. _doubleprecision:
+
 Double precision
 ................
 
+The datatype used to store real numbers in aubio is named `smpl_t`. By default,
+`smpl_t` is defined as `float`, a `single-precision format
+<https://en.wikipedia.org/wiki/Single-precision_floating-point_format>`_
+(32-bit).  Some algorithms require a floating point representation with a
+higher precision, for instance to prevent arithmetic underflow in recursive
+filters.  In aubio, these special samples are named `lsmp_t` and defined as
+`double` by default (64-bit).
+
+Sometimes it may be useful to compile aubio in `double-precision`, for instance
+to reproduce numerical results obtained with 64-bit routines. In this case,
+`smpl_t` will be defined as `double`.
+
+The following table shows how `smpl_t` and `lsmp_t` are defined in single- and
+double-precision modes:
+
+.. list-table:: Single and double-precision modes
+   :align: center
+
+   * -
+     - single
+     - double
+   * - `smpl_t`
+     - ``float``
+     - ``double``
+   * - `lsmp_t`
+     - ``double``
+     - ``long double``
+
 To compile aubio in double precision mode, configure with ``--enable-double``.
 
-To compile aubio in single precision mode, use ``--disable-double`` (default).
+To compile in single-precision mode (default), use ``--disable-double`` (or
+simply none of these two options).
 
 Disabling the tests
 ...................
index 4bb540e..b9e6719 100644 (file)
@@ -9,15 +9,11 @@ Current status
    :target: https://ci.appveyor.com/project/piem/aubio/
    :alt: Appveyor build status
 
-.. image:: https://landscape.io/github/aubio/aubio/master/landscape.svg?style=flat
-   :target: https://landscape.io/github/aubio/aubio/master
-   :alt: Landscape code health
-
 .. image:: https://readthedocs.org/projects/aubio/badge/?version=latest
    :target: https://aubio.readthedocs.io/en/latest/?badge=latest
    :alt: Documentation status
 
-.. image:: https://img.shields.io/github/commits-since/aubio/aubio/0.4.6.svg?maxAge=2592000
+.. image:: https://img.shields.io/github/commits-since/aubio/aubio/latest.svg
    :target: https://github.com/aubio/aubio
    :alt: Commits since last release
 
index 0942ade..f333d63 100644 (file)
@@ -68,7 +68,7 @@ int main(int argc, char **argv) {
     goto beach;
   }
 
-  examples_common_process((aubio_process_func_t)process_block, process_print);
+  examples_common_process(process_block, process_print);
 
   del_aubio_pvoc (pv);
   del_cvec (fftgrain);
index f74063a..0969774 100644 (file)
@@ -21,6 +21,7 @@
 #include "utils.h"
 #define PROG_HAS_PITCH 1
 #define PROG_HAS_ONSET 1
+#define PROG_HAS_NOTES 1
 #define PROG_HAS_SILENCE 1
 #define PROG_HAS_JACK 1
 // TODO add PROG_HAS_OUTPUT
@@ -82,8 +83,14 @@ int main(int argc, char **argv) {
           silence_threshold);
     }
   }
+  if (release_drop != 10.) {
+    if (aubio_notes_set_release_drop (notes, release_drop) != 0) {
+      errmsg ("failed setting notes release drop to %.2f\n",
+          release_drop);
+    }
+  }
 
-  examples_common_process((aubio_process_func_t)process_block, process_print);
+  examples_common_process(process_block, process_print);
 
   // send a last note off if required
   if (lastmidi) {
index 18b9a61..ca3496b 100644 (file)
@@ -86,7 +86,7 @@ int main(int argc, char **argv) {
   aubio_wavetable_set_freq ( wavetable, 2450.);
   //aubio_sampler_load (sampler, "/archives/sounds/woodblock.aiff");
 
-  examples_common_process((aubio_process_func_t)process_block, process_print);
+  examples_common_process(process_block, process_print);
 
   // send a last note off
   if (usejack) {
index 2d7a012..3a3d37e 100644 (file)
@@ -79,7 +79,7 @@ int main(int argc, char **argv) {
   wavetable = new_aubio_wavetable (samplerate, hop_size);
   aubio_wavetable_play ( wavetable );
 
-  examples_common_process((aubio_process_func_t)process_block,process_print);
+  examples_common_process(process_block, process_print);
 
   del_aubio_pitch (o);
   del_aubio_wavetable (wavetable);
index f62e1ed..1d7c6d8 100644 (file)
@@ -55,7 +55,7 @@ int main(int argc, char **argv) {
   verbmsg ("using source: %s at %dHz\n", source_uri, samplerate);
   verbmsg ("buffer_size: %d, ", buffer_size);
   verbmsg ("hop_size: %d\n", hop_size);
-  examples_common_process((aubio_process_func_t)process_block,process_print);
+  examples_common_process(process_block, process_print);
   examples_common_del();
   return 0;
 }
index d4a1de7..32e8b62 100644 (file)
@@ -87,7 +87,7 @@ int main(int argc, char **argv) {
   aubio_wavetable_set_freq ( wavetable, 2450.);
   //aubio_sampler_load (sampler, "/archives/sounds/woodblock.aiff");
 
-  examples_common_process((aubio_process_func_t)process_block,process_print);
+  examples_common_process(process_block, process_print);
 
   // send a last note off
   if (usejack) {
index f46629f..f8c33d2 100644 (file)
@@ -47,6 +47,7 @@ extern uint_t time_format;
 extern char_t * tempo_method;
 // more general stuff
 extern smpl_t silence_threshold;
+extern smpl_t release_drop;
 extern uint_t mix_input;
 // midi tap
 extern smpl_t miditap_note;
@@ -64,8 +65,8 @@ int parse_args (int argc, char **argv);
 // internal stuff
 extern int blocks;
 
-extern fvec_t *ibuf;
-extern fvec_t *obuf;
+extern fvec_t *input_buffer;
+extern fvec_t *output_buffer;
 
 const char *prog_name;
 
@@ -97,7 +98,7 @@ void usage (FILE * stream, int exit_code)
 #endif /* PROG_HAS_ONSET */
 #ifdef PROG_HAS_PITCH
       "       -p      --pitch            select pitch detection algorithm\n"
-      "                 <default|yinfft|yin|mcomb|fcomb|schmitt>; default=yinfft\n"
+      "                 <default|yinfft|yinfast|yin|mcomb|fcomb|schmitt>; default=yinfft\n"
       "       -u      --pitch-unit       select pitch output unit\n"
       "                 <default|freq|hertz|Hz|midi|cent|bin>; default=freq\n"
       "       -l      --pitch-tolerance  select pitch tolerance\n"
@@ -107,6 +108,10 @@ void usage (FILE * stream, int exit_code)
       "       -s      --silence          select silence threshold\n"
       "                 a value in dB, for instance -70, or -100; default=-90\n"
 #endif /* PROG_HAS_SILENCE */
+#ifdef PROG_HAS_NOTES
+      "       -d      --release-drop     select release drop threshold\n"
+      "                 a positive value in dB; default=10\n"
+#endif
       "       -T      --time-format      select time values output format\n"
       "                 (samples, ms, seconds) default=seconds\n"
 #ifdef PROG_HAS_OUTPUT
@@ -115,13 +120,13 @@ void usage (FILE * stream, int exit_code)
       "       -f      --force-overwrite  overwrite output file if needed\n"
       "                 do not fail if output file already exists\n"
 #endif /* PROG_HAS_OUTPUT */
-#ifdef PROG_HAS_JACK
+#if defined(PROG_HAS_JACK) && defined(HAVE_JACK)
       "       -j      --jack             use Jack\n"
 #if defined(PROG_HAS_ONSET) && !defined(PROG_HAS_PITCH)
       "       -N      --miditap-note     MIDI note; default=69.\n"
       "       -V      --miditap-velo     MIDI velocity; default=65.\n"
 #endif /* defined(PROG_HAS_ONSET) && !defined(PROG_HAS_PITCH) */
-#endif /* PROG_HAS_JACK */
+#endif /* defined(PROG_HAS_JACK) && defined(HAVE_JACK) */
       "       -v      --verbose          be verbose\n"
       "       -h      --help             display this message\n"
       );
@@ -157,6 +162,9 @@ parse_args (int argc, char **argv)
 #ifdef PROG_HAS_SILENCE
     "s:"
 #endif /* PROG_HAS_SILENCE */
+#ifdef PROG_HAS_NOTES
+    "d:"
+#endif /* PROG_HAS_SILENCE */
 #ifdef PROG_HAS_OUTPUT
     "mf"
 #endif /* PROG_HAS_OUTPUT */
@@ -192,6 +200,9 @@ parse_args (int argc, char **argv)
 #ifdef PROG_HAS_SILENCE
     {"silence",               1, NULL, 's'},
 #endif /* PROG_HAS_SILENCE */
+#ifdef PROG_HAS_NOTES
+    {"release-drop",          1, NULL, 'd'},
+#endif /* PROG_HAS_NOTES */
     {"time-format",           1, NULL, 'T'},
 #ifdef PROG_HAS_OUTPUT
     {"mix-input",             0, NULL, 'm'},
@@ -200,11 +211,11 @@ parse_args (int argc, char **argv)
     {NULL,                    0, NULL, 0}
   };
 #endif /* HAVE_GETOPT_H */
-  prog_name = argv[0];
+  // better safe than sorry
   if (argc < 1) {
     usage (stderr, 1);
-    return -1;
   }
+  prog_name = argv[0];
 #ifdef HAVE_GETOPT_H
   do {
     next_option = getopt_long (argc, argv, options, long_options, NULL);
@@ -274,6 +285,9 @@ parse_args (int argc, char **argv)
       case 's':                /* silence threshold */
         silence_threshold = (smpl_t) atof (optarg);
         break;
+      case 'd':                /* release-drop threshold */
+        release_drop = (smpl_t) atof (optarg);
+        break;
       case 'm':                /* mix_input flag */
         mix_input = 1;
         break;
@@ -313,7 +327,8 @@ parse_args (int argc, char **argv)
     usejack = 1;
 #else
     errmsg("Error: no arguments given (and no available audio input)\n");
-    usage ( stderr, 1 );
+    errmsg("       consider recompiling with jack support (--enable-jack)\n");
+    exit ( 1 );
 #endif /* HAVE_JACK */
 #else
     errmsg("Error: no arguments given\n");
index 39adef9..c8e44a3 100644 (file)
@@ -54,6 +54,7 @@ uint_t time_format = 0; // for "seconds", 1 for "ms", 2 for "samples"
 char_t * tempo_method = "default";
 // more general stuff
 smpl_t silence_threshold = -90.;
+smpl_t release_drop = 10.;
 uint_t mix_input = 0;
 
 uint_t force_overwrite = 0;
@@ -62,8 +63,8 @@ uint_t force_overwrite = 0;
 // internal memory stuff
 aubio_source_t *this_source = NULL;
 aubio_sink_t *this_sink = NULL;
-fvec_t *ibuf;
-fvec_t *obuf;
+fvec_t *input_buffer;
+fvec_t *output_buffer;
 
 smpl_t miditap_note = 69.;
 smpl_t miditap_velo = 65.;
@@ -75,8 +76,12 @@ extern void usage (FILE * stream, int exit_code);
 extern int parse_args (int argc, char **argv);
 
 #if HAVE_JACK
+#define MAX_MIDI_EVENTS 128
+#define MAX_MIDI_EVENT_SIZE 3
 aubio_jack_t *jack_setup;
 jack_midi_event_t ev;
+jack_midi_data_t midi_data[MAX_MIDI_EVENTS * MAX_MIDI_EVENT_SIZE];
+size_t midi_event_count = 0;
 #endif /* HAVE_JACK */
 
 void examples_common_init (int argc, char **argv);
@@ -121,18 +126,15 @@ void examples_common_init (int argc, char **argv)
     source_uri = "jack";
 #endif /* HAVE_JACK */
   }
-  ibuf = new_fvec (hop_size);
-  obuf = new_fvec (hop_size);
+  input_buffer = new_fvec (hop_size);
+  output_buffer = new_fvec (hop_size);
 
 }
 
 void examples_common_del (void)
 {
-#ifdef HAVE_JACK
-  if (ev.buffer) free(ev.buffer);
-#endif
-  del_fvec (ibuf);
-  del_fvec (obuf);
+  del_fvec (input_buffer);
+  del_fvec (output_buffer);
   aubio_cleanup ();
   fflush(stderr);
   fflush(stdout);
@@ -146,8 +148,7 @@ void examples_common_process (aubio_process_func_t process_func,
   if (usejack) {
 
 #ifdef HAVE_JACK
-    ev.size = 3;
-    ev.buffer = malloc (3 * sizeof (jack_midi_data_t));
+    ev.size = MAX_MIDI_EVENT_SIZE;
     ev.time = 0; // send it now
     debug ("Jack activation ...\n");
     aubio_jack_activate (jack_setup, process_func);
@@ -165,14 +166,14 @@ void examples_common_process (aubio_process_func_t process_func,
     blocks = 0;
 
     do {
-      aubio_source_do (this_source, ibuf, &read);
-      process_func (ibuf, obuf);
+      aubio_source_do (this_source, input_buffer, &read);
+      process_func (input_buffer, output_buffer);
       // print to console if verbose or no output given
       if (verbose || sink_uri == NULL) {
         print();
       }
       if (this_sink) {
-        aubio_sink_do (this_sink, obuf, hop_size);
+        aubio_sink_do (this_sink, output_buffer, hop_size);
       }
       blocks++;
       total_read += read;
@@ -183,7 +184,8 @@ void examples_common_process (aubio_process_func_t process_func,
         total_read, blocks, hop_size, source_uri, samplerate);
 
     del_aubio_source (this_source);
-    del_aubio_sink   (this_sink);
+    if (this_sink)
+      del_aubio_sink   (this_sink);
 
   }
 }
@@ -193,6 +195,10 @@ send_noteon (smpl_t pitch, smpl_t velo)
 {
 #ifdef HAVE_JACK
   if (usejack) {
+    ev.buffer = midi_data + midi_event_count++ * MAX_MIDI_EVENT_SIZE;
+    if (midi_event_count >= MAX_MIDI_EVENTS) {
+      midi_event_count = 0;
+    }
     ev.buffer[2] = velo;
     ev.buffer[1] = pitch;
     if (velo == 0) {
index e911bef..5cc9ef9 100644 (file)
@@ -66,7 +66,7 @@ typedef void (aubio_print_func_t) (void);
 void send_noteon (smpl_t pitch, smpl_t velo);
 
 /** common process function */
-typedef int (*aubio_process_func_t) (fvec_t * input, fvec_t * output);
+typedef void (*aubio_process_func_t) (fvec_t * input, fvec_t * output);
 
 void process_block (fvec_t *ibuf, fvec_t *obuf);
 void process_print (void);
diff --git a/nose2.cfg b/nose2.cfg
deleted file mode 100644 (file)
index d1be6d8..0000000
--- a/nose2.cfg
+++ /dev/null
@@ -1,6 +0,0 @@
-[unittest]
-start-dir = python/tests/
-plugins = nose2.plugins.mp
-
-[multiprocess]
-always-on = false
index dbb8ff3..0367ada 100644 (file)
-Python aubio module
-===================
+aubio
+=====
 
-This module wraps the aubio library for Python using the numpy module.
+aubio is a collection of tools for music and audio analysis.
 
-Using the Python aubio module
------------------------------
+This package integrates the aubio library with [NumPy] to provide a set of
+efficient tools to process and analyse audio signals, including:
 
-After installing python-aubio, you will be able to import the aubio module:
+- read audio from any media file, including videos and remote streams
+- high quality phase vocoder, spectral filterbanks, and linear filters
+- Mel-Frequency Cepstrum Coefficients and standard spectral descriptors
+- detection of note attacks (onset)
+- pitch tracking (fundamental frequency estimation)
+- beat detection and tempo tracking
 
-    $ python
-    [...]
-    >>> import aubio
-    >>> help(aubio.miditofreq)
+aubio works with both Python 2 and Python 3.
 
-Finding some inspiration
-------------------------
+Links
+-----
 
-Some examples are available in the `python/demos` directory. These scripts are
-small programs written in python and using python-aubio.
+- [module documentation][doc_python]
+- [installation instructions][doc_python_install]
+- [aubio manual][manual]
+- [aubio homepage][homepage]
+- [issue tracker][bugtracker]
 
-For instance, `demo_source.py` reads a media file.
+Demos
+-----
 
-    $ ./python/demos/demo_source.py /path/to/sound/sample.wav
+Some examples are available in the [`python/demos` folder][demos_dir]. Each
+script is a command line program which accepts one ore more argument.
 
-and `demo_timestretch_online.py` stretches the original file into a new one:
+**Notes**: installing additional modules is required to run some of the demos.
 
-    $ ./python/demo/demo_timestretch_online.py loop.wav stretched_loop.wav 0.92`
+### Analysis
 
-Note: you might need to install additional modules to run some of the demos.
-Some demos use [matplotlib](http://matplotlib.org/) to draw plots, others use
-[PySoundCard](https://github.com/bastibe/PySoundCard) to play and record
-sounds.
+- `demo_source.py` uses aubio to read audio samples from media files
+- `demo_onset_plot.py` detects attacks in a sound file and plots the results
+  using [matplotlib]
+- `demo_pitch.py` looks for fundamental frequency in a sound file and plots the
+  results using [matplotlib]
+- `demo_spectrogram.py`, `demo_specdesc.py`, `demo_mfcc.py` for spectral
+  analysis.
 
-Testing the Python module
--------------------------
+### Real-time
 
-Python tests are in `python/tests` and use the [nose2 python package][nose2].
+- `demo_pyaudio.py` and `demo_tapthebeat.py` use [pyaudio]
+- `demo_pysoundcard_play.py`, `demo_pysoundcard.py` use [PySoundCard]
+- `demo_alsa.py` uses [pyalsaaudio]
 
-To run the all the python tests, use the script:
+### Others
 
-    $ ./python/tests/run_all_tests
+- `demo_timestretch.py` can change the duration of an input file and write the
+  new sound to disk,
+- `demo_wav2midi.py` detects the notes in a file and uses [mido] to write the
+  results into a MIDI file
 
-Each test script can also be called one at a time. For instance:
+### Example
 
-    $ ./python/tests/test_note2midi.py -v
+Use `demo_timestretch_online.py` to slow down `loop.wav`, write the results in
+`stretched_loop.wav`:
 
-[nose2]: https://github.com/nose-devs/nose2
+    $ python demo_timestretch_online.py loop.wav stretched_loop.wav 0.92
 
-Install in a virtualenv
------------------------
-
-You should be able to install python-aubio directly from the top source
-directory of aubio.
-
-First, create a virtualenv to hold the required python module:
-
-    $ virtualenv pyaubio
-    $ source pyaubio/bin/activate
-
-Now install and build the python extension using:
-
-    $ pip install .
-
-Install requirements
---------------------
-
-Before compiling this module, you must have compiled libaubio.
-
-A simple way to do this is with pip:
-
-    $ pip install -r requirements.txt
-
-For more information about how this module works, please refer to the [Python/C
-API Reference Manual] (http://docs.python.org/c-api/index.html) and the
-[Numpy/C API Reference](http://docs.scipy.org/doc/numpy/reference/c-api.html).
-
-Compiling python aubio
-----------------------
-
-To build the aubio Python module, run the following command from the top source
-directory of aubio:
-
-    $ ./setup.py build
-
-Note: if libaubio was previously built using waf, the script will use it.
-Otherwise, the entire library will be built inside the python extension.
-
-To find out more about `setup.py` options:
-
-    $ ./setup.py --help
-
-Installing
+Built with
 ----------
 
-To install the Python module:
-
-    $ ./setup.py install
-
-Alternatively, you may want to use the Python module without installing it by
-setting your PYTHONPATH, for instance as follows:
-
-    $ export PYTHONPATH=$PYTHONPATH:$PWD/`ls -rtd build/lib.* | head -1`:$PWD/tests
-
+The core of aubio is written in C for portability and speed. In addition to
+[NumPy], aubio can be optionally built to use one or more of the following
+libraries:
+
+- media file reading:
+
+    - [ffmpeg] / [avcodec] to decode and read audio from almost any format,
+    - [libsndfile] to read audio from uncompressed sound files,
+    - [libsamplerate] to re-sample audio signals,
+    - [CoreAudio] to read all media formats supported by macOS, iOS, and tvOS.
+
+- hardware acceleration:
+
+    - [Atlas] and [Blas], for accelerated vector and matrix computations,
+    - [fftw3], to compute fast Fourier Transforms of any size,
+    - [Accelerate] for accelerated FFT and matrix computations (macOS/iOS),
+    - [Intel IPP], accelerated vector computation and FFT implementation.
+
+[ffmpeg]: https://ffmpeg.org
+[avcodec]: https://libav.org
+[libsndfile]: http://www.mega-nerd.com/libsndfile/
+[libsamplerate]: http://www.mega-nerd.com/SRC/
+[CoreAudio]: https://developer.apple.com/reference/coreaudio
+[Atlas]: http://math-atlas.sourceforge.net/
+[Blas]: https://en.wikipedia.org/wiki/Basic_Linear_Algebra_Subprograms
+[fftw3]: http://fftw.org
+[Accelerate]: https://developer.apple.com/reference/accelerate
+[Intel IPP]: https://software.intel.com/en-us/intel-ipp
+
+[demos_dir]:https://github.com/aubio/aubio/tree/master/python/demos
+[pyaudio]:https://people.csail.mit.edu/hubert/pyaudio/
+[PySoundCard]:https://github.com/bastibe/PySoundCard
+[pyalsaaudio]:https://larsimmisch.github.io/pyalsaaudio/
+[mido]:https://mido.readthedocs.io
+
+[manual]: https://aubio.org/manual/latest/
+[doc_python]: https://aubio.org/manual/latest/python.html
+[doc_python_install]: https://aubio.org/manual/latest/python_module.html
+[homepage]: https://aubio.org
+[NumPy]: https://www.numpy.org
+[bugtracker]: https://github.com/aubio/aubio/issues
+[matplotlib]:https://matplotlib.org/
diff --git a/python/__init__.py b/python/__init__.py
deleted file mode 100644 (file)
index e69de29..0000000
index ab0ceab..b04ebea 100755 (executable)
@@ -22,7 +22,7 @@ def get_file_bpm(path, params=None):
         elif params.mode in ['default']:
             pass
         else:
-            print("unknown mode {:s}".format(params.mode))
+            raise ValueError("unknown mode {:s}".format(params.mode))
     # manual settings
     if 'samplerate' in params:
         samplerate = params.samplerate
@@ -69,9 +69,9 @@ if __name__ == '__main__':
     parser = argparse.ArgumentParser()
     parser.add_argument('-m', '--mode',
             help="mode [default|fast|super-fast]",
-            dest="mode")
+            dest="mode", default='default')
     parser.add_argument('sources',
-            nargs='*',
+            nargs='+',
             help="input_files")
     args = parser.parse_args()
     for f in args.sources:
index 10226ce..ab3df6d 100755 (executable)
@@ -1,36 +1,53 @@
 #! /usr/bin/env python
 
+import sys
+import os.path
+import aubio
 
-def apply_filter(path):
-    from aubio import source, sink, digital_filter
-    from os.path import basename, splitext
 
+def apply_filter(path, target):
     # open input file, get its samplerate
-    s = source(path)
+    s = aubio.source(path)
     samplerate = s.samplerate
 
     # create an A-weighting filter
-    f = digital_filter(7)
+    f = aubio.digital_filter(7)
     f.set_a_weighting(samplerate)
-    # alternatively, apply another filter
 
     # create output file
-    o = sink("filtered_" + splitext(basename(path))[0] + ".wav", samplerate)
+    o = aubio.sink(target, samplerate)
 
     total_frames = 0
     while True:
+        # read from source
         samples, read = s()
+        # filter samples
         filtered_samples = f(samples)
+        # write to sink
         o(filtered_samples, read)
+        # count frames read
         total_frames += read
-        if read < s.hop_size: break
+        # end of file reached
+        if read < s.hop_size:
+            break
 
+    # print some info
     duration = total_frames / float(samplerate)
-    print ("read {:s}".format(s.uri))
-    print ("applied A-weighting filtered ({:d} Hz)".format(samplerate))
-    print ("wrote {:s} ({:.2f} s)".format(o.uri, duration))
+    input_str = "input: {:s} ({:.2f} s, {:d} Hz)"
+    output_str = "output: {:s}, A-weighting filtered ({:d} frames total)"
+    print(input_str.format(s.uri, duration, samplerate))
+    print(output_str.format(o.uri, total_frames))
 
 if __name__ == '__main__':
-    import sys
-    for f in sys.argv[1:]:
-        apply_filter(f)
+    usage = "{:s} <input_file> [output_file]".format(sys.argv[0])
+    if not 1 < len(sys.argv) < 4:
+        print(usage)
+        sys.exit(1)
+    if len(sys.argv) < 3:
+        input_path = sys.argv[1]
+        basename = os.path.splitext(os.path.basename(input_path))[0] + ".wav"
+        output_path = "filtered_" + basename
+    else:
+        input_path, output_path = sys.argv[1:]
+    # run function
+    apply_filter(input_path, output_path)
index 54aff57..ec29d11 100755 (executable)
@@ -1,30 +1,44 @@
 #! /usr/bin/env python
 
-from aubio import filterbank, fvec
-from pylab import loglog, show, xlim, ylim, xlabel, ylabel, title
-from numpy import vstack, arange
+"""Create a filterbank from a list of frequencies.
 
-win_s = 2048
+This demo uses `aubio.filterbank.set_triangle_bands` to build a set of
+triangular filters from a list of frequencies.
+
+The filterbank coefficients are then modified before being displayed."""
+
+import aubio
+import numpy as np
+import matplotlib.pyplot as plt
+
+# sampling rate and size of the fft
 samplerate = 48000
+win_s = 2048
 
+# define a list of custom frequency
 freq_list = [60, 80, 200, 400, 800, 1600, 3200, 6400, 12800, 24000]
+# number of filters to create
 n_filters = len(freq_list) - 2
 
-f = filterbank(n_filters, win_s)
-freqs = fvec(freq_list)
+# create a new filterbank
+f = aubio.filterbank(n_filters, win_s)
+freqs = aubio.fvec(freq_list)
 f.set_triangle_bands(freqs, samplerate)
 
+# get the coefficients from the filterbank
 coeffs = f.get_coeffs()
-coeffs[4] *= 5.
-
+# apply a gain to fifth band
+coeffs[4] *= 6.
+# load the modified coeffs into the filterbank
 f.set_coeffs(coeffs)
 
-times = vstack([arange(win_s // 2 + 1) * samplerate / win_s] * n_filters)
-title('Bank of filters built using a simple list of boundaries\nThe middle band has been amplified by 2.')
-loglog(times.T, f.get_coeffs().T, '.-')
-xlim([50, samplerate/2])
-ylim([1.0e-6, 2.0e-2])
-xlabel('log frequency (Hz)')
-ylabel('log amplitude')
-
-show()
+# display the band gains in a loglog plot
+freqs = np.vstack([np.arange(win_s // 2 + 1) * samplerate / win_s] * n_filters)
+plt.title('filterbank built from a list of frequencies\n'
+          'The 5th band has been amplified by a factor 6.')
+plt.loglog(freqs.T, f.get_coeffs().T, '.-')
+plt.xlim([50, samplerate/2])
+plt.ylim([1.0e-6, 2.0e-2])
+plt.xlabel('log frequency (Hz)')
+plt.ylabel('log amplitude')
+plt.show()
index 629f327..b7ef7b4 100755 (executable)
@@ -37,7 +37,7 @@ freqs[ pointer : pointer + partition ] = 1480
 
 pointer += partition
 pointer += partition
-freqs[ pointer : pointer + partition ] = 400 + 5 * np.random.random(sin_length/8)
+freqs[ pointer : pointer + partition ] = 400 + 5 * np.random.random(sin_length//8)
 
 a = build_sinusoid(sin_length, freqs, samplerate)
 
index 46bd8be..f0577f8 100755 (executable)
@@ -1,16 +1,20 @@
 #! /usr/bin/env python
-import sys, aubio
+
+"""A simple example using aubio.source."""
+
+import sys
+import aubio
 
 samplerate = 0  # use original source samplerate
-hop_size = 256 # number of frames to read in one block
-s = aubio.source(sys.argv[1], samplerate, hop_size)
+hop_size = 256  # number of frames to read in one block
+src = aubio.source(sys.argv[1], samplerate, hop_size)
 total_frames = 0
 
-while True: # reading loop
-    samples, read = s()
-    total_frames += read
-    if read < hop_size: break # end of file reached
+while True:
+    samples, read = src()  # read hop_size new samples from source
+    total_frames += read   # increment total number of frames
+    if read < hop_size:    # end of file reached
+        break
 
 fmt_string = "read {:d} frames at {:d}Hz from {:s}"
-print (fmt_string.format(total_frames, s.samplerate, sys.argv[1]))
-
+print(fmt_string.format(total_frames, src.samplerate, src.uri))
index 48c2d0a..8caa3ae 100755 (executable)
@@ -63,7 +63,7 @@ while True:
         delta = frames2tick(total_frames) - last_time
         if new_note[2] > 0:
             track.append(Message('note_off', note=int(new_note[2]),
-                velocity=127, time=0)
+                velocity=127, time=delta)
                 )
         track.append(Message('note_on',
             note=int(new_note[0]),
diff --git a/python/ext/aubio-docstrings.h b/python/ext/aubio-docstrings.h
new file mode 100644 (file)
index 0000000..2929ee1
--- /dev/null
@@ -0,0 +1,143 @@
+#define PYAUBIO_dct_doc \
+    "dct(size=1024)\n"\
+    "\n"\
+    "Compute Discrete Fourier Transorms of Type-II.\n"\
+    "\n"\
+    "Parameters\n"\
+    "----------\n"\
+    "size : int\n"\
+    "    size of the DCT to compute\n"\
+    "\n"\
+    "Example\n"\
+    "-------\n"\
+    ">>> d = aubio.dct(16)\n"\
+    ">>> d.size\n"\
+    "16\n"\
+    ">>> x = aubio.fvec(np.ones(d.size))\n"\
+    ">>> d(x)\n"\
+    "array([4., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],\n"\
+    "      dtype=float32)\n"\
+    ">>> d.rdo(d(x))\n"\
+    "array([1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.],\n"\
+    "      dtype=float32)\n"\
+    "\n"\
+    "References\n"\
+    "----------\n"\
+    "`DCT-II in Discrete Cosine Transform\n"\
+    "<https://en.wikipedia.org/wiki/Discrete_cosine_transform#DCT-II>`_\n"\
+    "on Wikipedia.\n"
+
+#define PYAUBIO_mfcc_doc \
+    "mfcc(buf_size=1024, n_filters=40, n_coeffs=13, samplerate=44100)\n"\
+    "\n"\
+    "Compute Mel Frequency Cepstrum Coefficients (MFCC).\n"\
+    "\n"\
+    "`mfcc` creates a callable which takes a `cvec` as input.\n"\
+    "\n"\
+    "If `n_filters = 40`, the filterbank will be initialized with\n"\
+    ":meth:`filterbank.set_mel_coeffs_slaney`. Otherwise, if `n_filters`\n"\
+    "is greater than `0`, it will be initialized with\n"\
+    ":meth:`filterbank.set_mel_coeffs` using `fmin = 0`,\n"\
+    "`fmax = samplerate/`.\n"\
+    "\n"\
+    "Example\n"\
+    "-------\n"\
+    ">>> buf_size = 2048; n_filters = 128; n_coeffs = 13; samplerate = 44100\n"\
+    ">>> mf = aubio.mfcc(buf_size, n_filters, n_coeffs, samplerate)\n"\
+    ">>> fftgrain = aubio.cvec(buf_size)\n"\
+    ">>> mf(fftgrain).shape\n"\
+    "(13,)\n"\
+    ""
+
+#define PYAUBIO_notes_doc \
+    "notes(method=\"default\", buf_size=1024, hop_size=512, samplerate=44100)\n"\
+    "\n"\
+    "Note detection\n"
+
+#define PYAUBIO_onset_doc \
+    "onset(method=\"default\", buf_size=1024, hop_size=512, samplerate=44100)\n"\
+    "\n"\
+    "Onset detection object. `method` should be one of method supported by\n"\
+    ":class:`specdesc`.\n"
+
+#define PYAUBIO_pitch_doc \
+    "pitch(method=\"default\", buf_size=1024, hop_size=512, samplerate=44100)\n"\
+    "\n"\
+    "Pitch detection.\n"\
+    "\n"\
+    "Supported methods: `yinfft`, `yin`, `yinfast`, `fcomb`, `mcomb`,\n"\
+    "`schmitt`, `specacf`, `default` (`yinfft`).\n"
+
+#define PYAUBIO_sampler_doc \
+    "sampler(hop_size=512, samplerate=44100)\n"\
+    "\n"\
+    "Sampler.\n"
+
+#define PYAUBIO_specdesc_doc \
+    "specdesc(method=\"default\", buf_size=1024)\n"\
+    "\n"\
+    "Spectral description functions. Creates a callable that takes a\n"\
+    ":class:`cvec` as input, typically created by :class:`pvoc` for\n"\
+    "overlap and windowing, and returns a single float.\n"\
+    "\n"\
+    "`method` can be any of the values listed below. If `default` is used\n"\
+    "the `hfc` function will be selected.\n"\
+    "\n"\
+    "Onset novelty functions:\n"\
+    "\n"\
+    "- `energy`: local energy,\n"\
+    "- `hfc`: high frequency content,\n"\
+    "- `complex`: complex domain,\n"\
+    "- `phase`: phase-based method,\n"\
+    "- `wphase`: weighted phase deviation,\n"\
+    "- `specdiff`: spectral difference,\n"\
+    "- `kl`: Kullback-Liebler,\n"\
+    "- `mkl`: modified Kullback-Liebler,\n"\
+    "- `specflux`: spectral flux.\n"\
+    "\n"\
+    "Spectral shape functions:\n"\
+    "\n"\
+    "- `centroid`: spectral centroid (barycenter of the norm vector),\n"\
+    "- `spread`: variance around centroid,\n"\
+    "- `skewness`: third order moment,\n"\
+    "- `kurtosis`: a measure of the flatness of the spectrum,\n"\
+    "- `slope`: decreasing rate of the amplitude,\n"\
+    "- `decrease`: perceptual based measurement of the decreasing rate,\n"\
+    "- `rolloff`: 95th energy percentile.\n"\
+    "\n"\
+    "Parameters\n"\
+    "----------\n"\
+    "method : str\n"\
+    "    Onset novelty or spectral shape function.\n"\
+    "buf_size : int\n"\
+    "    Length of the input frame.\n"\
+    "\n"\
+    "Example\n"\
+    "-------\n"\
+    ">>> win_s = 1024; hop_s = win_s // 2\n"\
+    ">>> pv = aubio.pvoc(win_s, hop_s)\n"\
+    ">>> sd = aubio.specdesc(\"mkl\", win_s)\n"\
+    ">>> sd(pv(aubio.fvec(hop_s))).shape\n"\
+    "(1,)\n"\
+    "\n"\
+    "References\n"\
+    "----------\n"\
+    "`Detailed description "\
+    "<https://aubio.org/doc/latest/specdesc_8h.html#details>`_ in\n"\
+    "`aubio API documentation <https://aubio.org/doc/latest/index.html>`_.\n"\
+    ""
+
+#define PYAUBIO_tempo_doc \
+    "tempo(method=\"default\", buf_size=1024, hop_size=512, samplerate=44100)\n"\
+    "\n"\
+    "Tempo detection and beat tracking.\n"
+
+#define PYAUBIO_tss_doc \
+    "tss(buf_size=1024, hop_size=512)\n"\
+    "\n"\
+    "Transient/Steady-state separation.\n"
+
+#define PYAUBIO_wavetable_doc \
+    "wavetable(samplerate=44100, hop_size=512)\n"\
+    "\n"\
+    "Wavetable synthesis.\n"
index 26f8b1d..4458ecc 100644 (file)
@@ -1,6 +1,7 @@
 #include <Python.h>
 #include <structmember.h>
 
+#include "aubio-docstrings.h"
 #include "aubio-generated.h"
 
 #define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
index 76ed9c9..d4ed033 100644 (file)
 static char aubio_module_doc[] = "Python module for the aubio library";
 
 static char Py_alpha_norm_doc[] = ""
-"alpha_norm(fvec, integer) -> float\n"
+"alpha_norm(vec, alpha)\n"
 "\n"
-"Compute alpha normalisation factor on vector, given alpha\n"
+"Compute `alpha` normalisation factor of vector `vec`.\n"
+"\n"
+"Parameters\n"
+"----------\n"
+"vec : fvec\n"
+"   input vector\n"
+"alpha : float\n"
+"   norm factor\n"
+"\n"
+"Returns\n"
+"-------\n"
+"float\n"
+"   p-norm of the input vector, where `p=alpha`\n"
 "\n"
 "Example\n"
 "-------\n"
 "\n"
-">>> b = alpha_norm(a, 9)";
+">>> a = aubio.fvec(np.arange(10)); alpha = 2\n"
+">>> aubio.alpha_norm(a, alpha), (sum(a**alpha)/len(a))**(1./alpha)\n"
+"(5.338539123535156, 5.338539126015656)\n"
+"\n"
+"Note\n"
+"----\n"
+"Computed as:\n"
+"\n"
+".. math::\n"
+"  l_{\\alpha} = \n"
+"       \\|\\frac{\\sum_{n=0}^{N-1}{{x_n}^{\\alpha}}}{N}\\|^{1/\\alpha}\n"
+"";
 
 static char Py_bintomidi_doc[] = ""
-"bintomidi(float, samplerate = integer, fftsize = integer) -> float\n"
+"bintomidi(fftbin, samplerate, fftsize)\n"
+"\n"
+"Convert FFT bin to frequency in midi note, given the sampling rate\n"
+"and the size of the FFT.\n"
 "\n"
-"Convert bin (float) to midi (float), given the sampling rate and the FFT size\n"
+"Parameters\n"
+"----------\n"
+"fftbin : float\n"
+"   input frequency bin\n"
+"samplerate : float\n"
+"   sampling rate of the signal\n"
+"fftsize : float\n"
+"   size of the FFT\n"
+"\n"
+"Returns\n"
+"-------\n"
+"float\n"
+"   Frequency converted to midi note.\n"
 "\n"
 "Example\n"
 "-------\n"
 "\n"
-">>> midi = bintomidi(float, samplerate = 44100, fftsize = 1024)";
+">>> aubio.bintomidi(10, 44100, 1024)\n"
+"68.62871551513672\n"
+"";
 
 static char Py_miditobin_doc[] = ""
-"miditobin(float, samplerate = integer, fftsize = integer) -> float\n"
+"miditobin(midi, samplerate, fftsize)\n"
 "\n"
-"Convert midi (float) to bin (float), given the sampling rate and the FFT size\n"
+"Convert frequency in midi note to FFT bin, given the sampling rate\n"
+"and the size of the FFT.\n"
 "\n"
-"Example\n"
+"Parameters\n"
+"----------\n"
+"midi : float\n"
+"   input frequency, in midi note\n"
+"samplerate : float\n"
+"   sampling rate of the signal\n"
+"fftsize : float\n"
+"   size of the FFT\n"
+"\n"
+"Returns\n"
 "-------\n"
+"float\n"
+"   Frequency converted to FFT bin.\n"
+"\n"
+"Examples\n"
+"--------\n"
 "\n"
-">>> bin = miditobin(midi, samplerate = 44100, fftsize = 1024)";
+">>> aubio.miditobin(69, 44100, 1024)\n"
+"10.216779708862305\n"
+">>> aubio.miditobin(75.08, 32000, 512)\n"
+"10.002175331115723\n"
+"";
 
 static char Py_bintofreq_doc[] = ""
-"bintofreq(float, samplerate = integer, fftsize = integer) -> float\n"
+"bintofreq(fftbin, samplerate, fftsize)\n"
+"\n"
+"Convert FFT bin to frequency in Hz, given the sampling rate\n"
+"and the size of the FFT.\n"
 "\n"
-"Convert bin number (float) in frequency (Hz), given the sampling rate and the FFT size\n"
+"Parameters\n"
+"----------\n"
+"fftbin : float\n"
+"   input frequency bin\n"
+"samplerate : float\n"
+"   sampling rate of the signal\n"
+"fftsize : float\n"
+"   size of the FFT\n"
+"\n"
+"Returns\n"
+"-------\n"
+"float\n"
+"   Frequency converted to Hz.\n"
 "\n"
 "Example\n"
 "-------\n"
 "\n"
-">>> freq = bintofreq(bin, samplerate = 44100, fftsize = 1024)";
+">>> aubio.bintofreq(10, 44100, 1024)\n"
+"430.6640625\n"
+"";
 
 static char Py_freqtobin_doc[] = ""
-"freqtobin(float, samplerate = integer, fftsize = integer) -> float\n"
+"freqtobin(freq, samplerate, fftsize)\n"
 "\n"
-"Convert frequency (Hz) in bin number (float), given the sampling rate and the FFT size\n"
+"Convert frequency in Hz to FFT bin, given the sampling rate\n"
+"and the size of the FFT.\n"
 "\n"
-"Example\n"
+"Parameters\n"
+"----------\n"
+"midi : float\n"
+"   input frequency, in midi note\n"
+"samplerate : float\n"
+"   sampling rate of the signal\n"
+"fftsize : float\n"
+"   size of the FFT\n"
+"\n"
+"Returns\n"
 "-------\n"
+"float\n"
+"   Frequency converted to FFT bin.\n"
+"\n"
+"Examples\n"
+"--------\n"
 "\n"
-">>> bin = freqtobin(freq, samplerate = 44100, fftsize = 1024)";
+">>> aubio.freqtobin(440, 44100, 1024)\n"
+"10.216779708862305\n"
+"";
 
 static char Py_zero_crossing_rate_doc[] = ""
-"zero_crossing_rate(fvec) -> float\n"
+"zero_crossing_rate(vec)\n"
+"\n"
+"Compute zero-crossing rate of `vec`.\n"
 "\n"
-"Compute Zero crossing rate of a vector\n"
+"Parameters\n"
+"----------\n"
+"vec : fvec\n"
+"   input vector\n"
+"\n"
+"Returns\n"
+"-------\n"
+"float\n"
+"   Zero-crossing rate.\n"
 "\n"
 "Example\n"
 "-------\n"
 "\n"
-">>> z = zero_crossing_rate(a)";
+">>> a = np.linspace(-1., 1., 1000, dtype=aubio.float_type)\n"
+">>> aubio.zero_crossing_rate(a), 1/1000\n"
+"(0.0010000000474974513, 0.001)\n"
+"";
 
 static char Py_min_removal_doc[] = ""
-"min_removal(fvec) -> float\n"
+"min_removal(vec)\n"
+"\n"
+"Remove the minimum value of a vector to each of its element.\n"
 "\n"
-"Remove the minimum value of a vector, in-place modification\n"
+"Modifies the input vector in-place and returns a reference to it.\n"
+"\n"
+"Parameters\n"
+"----------\n"
+"vec : fvec\n"
+"   input vector\n"
+"\n"
+"Returns\n"
+"-------\n"
+"fvec\n"
+"   modified input vector\n"
 "\n"
 "Example\n"
 "-------\n"
 "\n"
-">>> min_removal(a)";
+">>> aubio.min_removal(aubio.fvec(np.arange(1,4)))\n"
+"array([0., 1., 2.], dtype=" AUBIO_NPY_SMPL_STR ")\n"
+"";
 
 extern void add_ufuncs ( PyObject *m );
 extern int generated_types_ready(void);
@@ -103,7 +223,7 @@ Py_alpha_norm (PyObject * self, PyObject * args)
   }
 
   // compute the function
-  result = Py_BuildValue (AUBIO_NPY_SMPL_CHR, fvec_alpha_norm (&vec, alpha));
+  result = PyFloat_FromDouble(fvec_alpha_norm (&vec, alpha));
   if (result == NULL) {
     return NULL;
   }
@@ -117,7 +237,9 @@ Py_bintomidi (PyObject * self, PyObject * args)
   smpl_t input, samplerate, fftsize;
   smpl_t output;
 
-  if (!PyArg_ParseTuple (args, "|" AUBIO_NPY_SMPL_CHR AUBIO_NPY_SMPL_CHR AUBIO_NPY_SMPL_CHR , &input, &samplerate, &fftsize)) {
+  if (!PyArg_ParseTuple (args,
+        "" AUBIO_NPY_SMPL_CHR AUBIO_NPY_SMPL_CHR AUBIO_NPY_SMPL_CHR,
+        &input, &samplerate, &fftsize)) {
     return NULL;
   }
 
@@ -132,7 +254,9 @@ Py_miditobin (PyObject * self, PyObject * args)
   smpl_t input, samplerate, fftsize;
   smpl_t output;
 
-  if (!PyArg_ParseTuple (args, "|" AUBIO_NPY_SMPL_CHR AUBIO_NPY_SMPL_CHR AUBIO_NPY_SMPL_CHR , &input, &samplerate, &fftsize)) {
+  if (!PyArg_ParseTuple (args,
+        "" AUBIO_NPY_SMPL_CHR AUBIO_NPY_SMPL_CHR AUBIO_NPY_SMPL_CHR,
+        &input, &samplerate, &fftsize)) {
     return NULL;
   }
 
@@ -147,7 +271,9 @@ Py_bintofreq (PyObject * self, PyObject * args)
   smpl_t input, samplerate, fftsize;
   smpl_t output;
 
-  if (!PyArg_ParseTuple (args, "|" AUBIO_NPY_SMPL_CHR AUBIO_NPY_SMPL_CHR AUBIO_NPY_SMPL_CHR, &input, &samplerate, &fftsize)) {
+  if (!PyArg_ParseTuple (args,
+        "" AUBIO_NPY_SMPL_CHR AUBIO_NPY_SMPL_CHR AUBIO_NPY_SMPL_CHR,
+        &input, &samplerate, &fftsize)) {
     return NULL;
   }
 
@@ -162,7 +288,9 @@ Py_freqtobin (PyObject * self, PyObject * args)
   smpl_t input, samplerate, fftsize;
   smpl_t output;
 
-  if (!PyArg_ParseTuple (args, "|" AUBIO_NPY_SMPL_CHR AUBIO_NPY_SMPL_CHR AUBIO_NPY_SMPL_CHR, &input, &samplerate, &fftsize)) {
+  if (!PyArg_ParseTuple (args,
+        "" AUBIO_NPY_SMPL_CHR AUBIO_NPY_SMPL_CHR AUBIO_NPY_SMPL_CHR,
+        &input, &samplerate, &fftsize)) {
     return NULL;
   }
 
@@ -191,7 +319,7 @@ Py_zero_crossing_rate (PyObject * self, PyObject * args)
   }
 
   // compute the function
-  result = Py_BuildValue (AUBIO_NPY_SMPL_CHR, aubio_zero_crossing_rate (&vec));
+  result = PyFloat_FromDouble(aubio_zero_crossing_rate (&vec));
   if (result == NULL) {
     return NULL;
   }
@@ -242,6 +370,12 @@ static PyMethodDef aubio_methods[] = {
   {"silence_detection", Py_aubio_silence_detection, METH_VARARGS, Py_aubio_silence_detection_doc},
   {"level_detection", Py_aubio_level_detection, METH_VARARGS, Py_aubio_level_detection_doc},
   {"window", Py_aubio_window, METH_VARARGS, Py_aubio_window_doc},
+  {"shift", Py_aubio_shift, METH_VARARGS, Py_aubio_shift_doc},
+  {"ishift", Py_aubio_ishift, METH_VARARGS, Py_aubio_ishift_doc},
+  {"hztomel", Py_aubio_hztomel, METH_VARARGS|METH_KEYWORDS, Py_aubio_hztomel_doc},
+  {"meltohz", Py_aubio_meltohz, METH_VARARGS|METH_KEYWORDS, Py_aubio_meltohz_doc},
+  {"hztomel_htk", Py_aubio_hztomel_htk, METH_VARARGS, Py_aubio_hztomel_htk_doc},
+  {"meltohz_htk", Py_aubio_meltohz_htk, METH_VARARGS, Py_aubio_meltohz_htk_doc},
   {NULL, NULL, 0, NULL} /* Sentinel */
 };
 
index f584fe7..1c9316a 100644 (file)
@@ -19,7 +19,37 @@ typedef struct
   uint_t length;
 } Py_cvec;
 
-static char Py_cvec_doc[] = "cvec object";
+static char Py_cvec_doc[] = ""
+"cvec(size)\n"
+"\n"
+"A container holding spectral data.\n"
+"\n"
+"Create one `cvec` to store the spectral information of a window\n"
+"of `size` points. The data will be stored  in two vectors,\n"
+":attr:`phas` and :attr:`norm`, each of shape (:attr:`length`,),\n"
+"with `length = size // 2 + 1`.\n"
+"\n"
+"Parameters\n"
+"----------\n"
+"size: int\n"
+"   Size of spectrum to create.\n"
+"\n"
+"Examples\n"
+"--------\n"
+">>> c = aubio.cvec(1024)\n"
+">>> c\n"
+"aubio cvec of 513 elements\n"
+">>> c.length\n"
+"513\n"
+">>> c.norm.dtype, c.phas.dtype\n"
+"(dtype('float32'), dtype('float32'))\n"
+">>> c.norm.shape, c.phas.shape\n"
+"((513,), (513,))\n"
+"\n"
+"See Also\n"
+"--------\n"
+"fvec, fft, pvoc\n"
+"";
 
 
 PyObject *
@@ -106,7 +136,7 @@ Py_cvec_repr (Py_cvec * self, PyObject * unused)
     goto fail;
   }
 
-  args = Py_BuildValue ("I", self->length);
+  args = PyLong_FromLong(self->length);
   if (args == NULL) {
     goto fail;
   }
@@ -182,7 +212,7 @@ Py_cvec_set_phas (Py_cvec * vec, PyObject *input, void * closure)
 static PyMemberDef Py_cvec_members[] = {
   // TODO remove READONLY flag and define getter/setter
   {"length", T_INT, offsetof (Py_cvec, length), READONLY,
-      "length attribute"},
+      "int: Length of `norm` and `phas` vectors."},
   {NULL}                        /* Sentinel */
 };
 
@@ -191,11 +221,11 @@ static PyMethodDef Py_cvec_methods[] = {
 };
 
 static PyGetSetDef Py_cvec_getseters[] = {
-  {"norm", (getter)Py_cvec_get_norm, (setter)Py_cvec_set_norm, 
-      "Numpy vector of shape (length,) containing the magnitude",
+  {"norm", (getter)Py_cvec_get_norm, (setter)Py_cvec_set_norm,
+      "numpy.ndarray: Vector of shape `(length,)` containing the magnitude.",
       NULL},
-  {"phas", (getter)Py_cvec_get_phas, (setter)Py_cvec_set_phas, 
-      "Numpy vector of shape (length,) containing the phase",
+  {"phas", (getter)Py_cvec_get_phas, (setter)Py_cvec_set_phas,
+      "numpy.ndarray: Vector of shape `(length,)` containing the phase.",
       NULL},
   {NULL} /* sentinel */
 };
index 8763632..a08af4e 100644 (file)
@@ -1,6 +1,24 @@
 #include "aubio-types.h"
 
-static char Py_fft_doc[] = "fft object";
+static char Py_fft_doc[] = ""
+"fft(size=1024)\n"
+"\n"
+"Compute Fast Fourier Transorms.\n"
+"\n"
+"Parameters\n"
+"----------\n"
+"size : int\n"
+"    size of the FFT to compute\n"
+"\n"
+"Example\n"
+"-------\n"
+">>> x = aubio.fvec(512)\n"
+">>> f = aubio.fft(512)\n"
+">>> c = f(x); c\n"
+"aubio cvec of 257 elements\n"
+">>> x2 = f.rdo(c); x2.shape\n"
+"(512,)\n"
+"";
 
 typedef struct
 {
index df78e47..861f8cd 100644 (file)
@@ -10,7 +10,58 @@ typedef struct
   fvec_t c_out;
 } Py_filter;
 
-static char Py_filter_doc[] = "filter object";
+static char Py_filter_doc[] = ""
+"digital_filter(order=7)\n"
+"\n"
+"Create a digital filter.\n"
+"";
+
+static char Py_filter_set_c_weighting_doc[] = ""
+"set_c_weighting(samplerate)\n"
+"\n"
+"Set filter coefficients to C-weighting.\n"
+"\n"
+"`samplerate` should be one of 8000, 11025, 16000, 22050, 24000, 32000,\n"
+"44100, 48000, 88200, 96000, or 192000. `order` of the filter should be 5.\n"
+"\n"
+"Parameters\n"
+"----------\n"
+"samplerate : int\n"
+"    Sampling-rate of the input signal, in Hz.\n"
+"";
+
+static char Py_filter_set_a_weighting_doc[] = ""
+"set_a_weighting(samplerate)\n"
+"\n"
+"Set filter coefficients to A-weighting.\n"
+"\n"
+"`samplerate` should be one of 8000, 11025, 16000, 22050, 24000, 32000,\n"
+"44100, 48000, 88200, 96000, or 192000. `order` of the filter should be 7.\n"
+"\n"
+"Parameters\n"
+"----------\n"
+"samplerate : int\n"
+"    Sampling-rate of the input signal.\n"
+"";
+
+static char Py_filter_set_biquad_doc[] = ""
+"set_biquad(b0, b1, b2, a1, a2)\n"
+"\n"
+"Set biquad coefficients. `order` of the filter should be 3.\n"
+"\n"
+"Parameters\n"
+"----------\n"
+"b0 : float\n"
+"    Forward filter coefficient.\n"
+"b1 : float\n"
+"    Forward filter coefficient.\n"
+"b2 : float\n"
+"    Forward filter coefficient.\n"
+"a1 : float\n"
+"    Feedback filter coefficient.\n"
+"a2 : float\n"
+"    Feedback filter coefficient.\n"
+"";
 
 static PyObject *
 Py_filter_new (PyTypeObject * type, PyObject * args, PyObject * kwds)
@@ -58,7 +109,8 @@ static void
 Py_filter_del (Py_filter * self)
 {
   Py_XDECREF(self->out);
-  del_aubio_filter (self->o);
+  if (self->o)
+    del_aubio_filter (self->o);
   Py_TYPE(self)->tp_free ((PyObject *) self);
 }
 
@@ -156,11 +208,11 @@ static PyMemberDef Py_filter_members[] = {
 
 static PyMethodDef Py_filter_methods[] = {
   {"set_c_weighting", (PyCFunction) Py_filter_set_c_weighting, METH_VARARGS,
-      "set filter coefficients to C-weighting"},
+      Py_filter_set_c_weighting_doc},
   {"set_a_weighting", (PyCFunction) Py_filter_set_a_weighting, METH_VARARGS,
-      "set filter coefficients to A-weighting"},
+      Py_filter_set_a_weighting_doc},
   {"set_biquad", (PyCFunction) Py_filter_set_biquad, METH_VARARGS,
-      "set b0, b1, b2, a1, a2 biquad coefficients"},
+      Py_filter_set_biquad_doc},
   {NULL}
 };
 
index a4e0ea6..0bd00d0 100644 (file)
@@ -1,6 +1,188 @@
 #include "aubio-types.h"
 
-static char Py_filterbank_doc[] = "filterbank object";
+static char Py_filterbank_doc[] = ""
+"filterbank(n_filters=40, win_s=1024)\n"
+"\n"
+"Create a bank of spectral filters. Each instance is a callable\n"
+"that holds a matrix of coefficients.\n"
+"\n"
+"See also :meth:`set_mel_coeffs`, :meth:`set_mel_coeffs_htk`,\n"
+":meth:`set_mel_coeffs_slaney`, :meth:`set_triangle_bands`, and\n"
+":meth:`set_coeffs`.\n"
+"\n"
+"Parameters\n"
+"----------\n"
+"n_filters : int\n"
+"    Number of filters to create.\n"
+"win_s : int\n"
+"    Size of the input spectrum to process.\n"
+"\n"
+"Examples\n"
+"--------\n"
+">>> f = aubio.filterbank(128, 1024)\n"
+">>> f.set_mel_coeffs(44100, 0, 10000)\n"
+">>> c = aubio.cvec(1024)\n"
+">>> f(c).shape\n"
+"(128, )\n"
+"";
+
+static char Py_filterbank_set_triangle_bands_doc[] =""
+"set_triangle_bands(freqs, samplerate)\n"
+"\n"
+"Set triangular bands. The coefficients will be set to triangular\n"
+"overlapping windows using the boundaries specified by `freqs`.\n"
+"\n"
+"`freqs` should contain `n_filters + 2` frequencies in Hz, ordered\n"
+"by value, from smallest to largest. The first element should be greater\n"
+"or equal to zero; the last element should be smaller or equal to\n"
+"`samplerate / 2`.\n"
+"\n"
+"Parameters\n"
+"----------\n"
+"freqs: fvec\n"
+"    List of frequencies, in Hz.\n"
+"samplerate : float\n"
+"    Sampling-rate of the expected input.\n"
+"\n"
+"Example\n"
+"-------\n"
+">>> fb = aubio.filterbank(n_filters=100, win_s=2048)\n"
+">>> samplerate = 44100; freqs = np.linspace(0, 20200, 102)\n"
+">>> fb.set_triangle_bands(aubio.fvec(freqs), samplerate)\n"
+"";
+
+static char Py_filterbank_set_mel_coeffs_slaney_doc[] = ""
+"set_mel_coeffs_slaney(samplerate)\n"
+"\n"
+"Set coefficients of filterbank to match Slaney's Auditory Toolbox.\n"
+"\n"
+"The filter coefficients will be set as in Malcolm Slaney's\n"
+"implementation. The filterbank should have been created with\n"
+"`n_filters = 40`.\n"
+"\n"
+"This is approximately equivalent to using :meth:`set_mel_coeffs` with\n"
+"`fmin = 400./3., fmax = 6853.84`.\n"
+"\n"
+"Parameters\n"
+"----------\n"
+"samplerate : float\n"
+"    Sampling-rate of the expected input.\n"
+"\n"
+"References\n"
+"----------\n"
+"\n"
+"Malcolm Slaney, `Auditory Toolbox Version 2, Technical Report #1998-010\n"
+"<https://engineering.purdue.edu/~malcolm/interval/1998-010/>`_\n"
+"";
+
+static char Py_filterbank_set_mel_coeffs_doc[] = ""
+"set_mel_coeffs(samplerate, fmin, fmax)\n"
+"\n"
+"Set coefficients of filterbank to linearly spaced mel scale.\n"
+"\n"
+"Parameters\n"
+"----------\n"
+"samplerate : float\n"
+"    Sampling-rate of the expected input.\n"
+"fmin : float\n"
+"    Lower frequency boundary of the first filter.\n"
+"fmax : float\n"
+"    Upper frequency boundary of the last filter.\n"
+"\n"
+"See also\n"
+"--------\n"
+"hztomel\n"
+"";
+
+static char Py_filterbank_set_mel_coeffs_htk_doc[] = ""
+"set_mel_coeffs_htk(samplerate, fmin, fmax)\n"
+"\n"
+"Set coefficients of the filters to be linearly spaced in the HTK mel scale.\n"
+"\n"
+"Parameters\n"
+"----------\n"
+"samplerate : float\n"
+"    Sampling-rate of the expected input.\n"
+"fmin : float\n"
+"    Lower frequency boundary of the first filter.\n"
+"fmax : float\n"
+"    Upper frequency boundary of the last filter.\n"
+"\n"
+"See also\n"
+"--------\n"
+"hztomel with `htk=True`\n"
+"";
+
+static char Py_filterbank_get_coeffs_doc[] = ""
+"get_coeffs()\n"
+"\n"
+"Get coefficients matrix of filterbank.\n"
+"\n"
+"Returns\n"
+"-------\n"
+"array_like\n"
+"    Array of shape (n_filters, win_s/2+1) containing the coefficients.\n"
+"";
+
+static char Py_filterbank_set_coeffs_doc[] = ""
+"set_coeffs(coeffs)\n"
+"\n"
+"Set coefficients of filterbank.\n"
+"\n"
+"Parameters\n"
+"----------\n"
+"coeffs : fmat\n"
+"    Array of shape (n_filters, win_s/2+1) containing the coefficients.\n"
+"";
+
+static char Py_filterbank_set_power_doc[] = ""
+"set_power(power)\n"
+"\n"
+"Set power applied to input spectrum of filterbank.\n"
+"\n"
+"Parameters\n"
+"----------\n"
+"power : float\n"
+"    Power to raise input spectrum to before computing the filters.\n"
+"";
+
+static char Py_filterbank_get_power_doc[] = ""
+"get_power()\n"
+"\n"
+"Get power applied to filterbank.\n"
+"\n"
+"Returns\n"
+"-------\n"
+"float\n"
+"    Power parameter.\n"
+"";
+
+static char Py_filterbank_set_norm_doc[] = ""
+"set_norm(norm)\n"
+"\n"
+"Set norm parameter. If set to `0`, the filters will not be normalized.\n"
+"If set to `1`, the filters will be normalized to one. Default to `1`.\n"
+"\n"
+"This function should be called *before* :meth:`set_triangle_bands`,\n"
+":meth:`set_mel_coeffs`, :meth:`set_mel_coeffs_htk`, or\n"
+":meth:`set_mel_coeffs_slaney`.\n"
+"\n"
+"Parameters\n"
+"----------\n"
+"norm : int\n"
+"   `0` to disable, `1` to enable\n"
+"";
+
+static char Py_filterbank_get_norm_doc[] = ""
+"get_norm()\n"
+"\n"
+"Get norm parameter of filterbank.\n"
+"\n"
+"Returns\n"
+"-------\n"
+"float\n"
+"    Norm parameter.\n"
+"";
 
 typedef struct
 {
@@ -94,7 +276,7 @@ Py_filterbank_do(Py_filterbank * self, PyObject * args)
 
   if (self->vec.length != self->win_s / 2 + 1) {
     PyErr_Format(PyExc_ValueError,
-                 "input cvec has length %d, but fft expects length %d",
+                 "input cvec has length %d, but filterbank expects length %d",
                  self->vec.length, self->win_s / 2 + 1);
     return NULL;
   }
@@ -122,8 +304,8 @@ Py_filterbank_set_triangle_bands (Py_filterbank * self, PyObject *args)
   uint_t err = 0;
 
   PyObject *input;
-  uint_t samplerate;
-  if (!PyArg_ParseTuple (args, "OI", &input, &samplerate)) {
+  smpl_t samplerate;
+  if (!PyArg_ParseTuple (args, "O" AUBIO_NPY_SMPL_CHR, &input, &samplerate)) {
     return NULL;
   }
 
@@ -138,8 +320,14 @@ Py_filterbank_set_triangle_bands (Py_filterbank * self, PyObject *args)
   err = aubio_filterbank_set_triangle_bands (self->o,
       &(self->freqs), samplerate);
   if (err > 0) {
-    PyErr_SetString (PyExc_ValueError,
-        "error when setting filter to A-weighting");
+    if (PyErr_Occurred() == NULL) {
+      PyErr_SetString (PyExc_ValueError, "error running set_triangle_bands");
+    } else {
+      // change the RuntimeError into ValueError
+      PyObject *type, *value, *traceback;
+      PyErr_Fetch(&type, &value, &traceback);
+      PyErr_Restore(PyExc_ValueError, value, traceback);
+    }
     return NULL;
   }
   Py_RETURN_NONE;
@@ -150,15 +338,79 @@ Py_filterbank_set_mel_coeffs_slaney (Py_filterbank * self, PyObject *args)
 {
   uint_t err = 0;
 
-  uint_t samplerate;
-  if (!PyArg_ParseTuple (args, "I", &samplerate)) {
+  smpl_t samplerate;
+  if (!PyArg_ParseTuple (args, AUBIO_NPY_SMPL_CHR, &samplerate)) {
     return NULL;
   }
 
   err = aubio_filterbank_set_mel_coeffs_slaney (self->o, samplerate);
   if (err > 0) {
-    PyErr_SetString (PyExc_ValueError,
-        "error when setting filter to A-weighting");
+    if (PyErr_Occurred() == NULL) {
+      PyErr_SetString (PyExc_ValueError, "error running set_mel_coeffs_slaney");
+    } else {
+      // change the RuntimeError into ValueError
+      PyObject *type, *value, *traceback;
+      PyErr_Fetch(&type, &value, &traceback);
+      PyErr_Restore(PyExc_ValueError, value, traceback);
+    }
+    return NULL;
+  }
+  Py_RETURN_NONE;
+}
+
+static PyObject *
+Py_filterbank_set_mel_coeffs (Py_filterbank * self, PyObject *args)
+{
+  uint_t err = 0;
+
+  smpl_t samplerate;
+  smpl_t freq_min;
+  smpl_t freq_max;
+  if (!PyArg_ParseTuple (args, AUBIO_NPY_SMPL_CHR AUBIO_NPY_SMPL_CHR
+        AUBIO_NPY_SMPL_CHR, &samplerate, &freq_min, &freq_max)) {
+    return NULL;
+  }
+
+  err = aubio_filterbank_set_mel_coeffs (self->o, samplerate,
+      freq_min, freq_max);
+  if (err > 0) {
+    if (PyErr_Occurred() == NULL) {
+      PyErr_SetString (PyExc_ValueError, "error running set_mel_coeffs");
+    } else {
+      // change the RuntimeError into ValueError
+      PyObject *type, *value, *traceback;
+      PyErr_Fetch(&type, &value, &traceback);
+      PyErr_Restore(PyExc_ValueError, value, traceback);
+    }
+    return NULL;
+  }
+  Py_RETURN_NONE;
+}
+
+static PyObject *
+Py_filterbank_set_mel_coeffs_htk (Py_filterbank * self, PyObject *args)
+{
+  uint_t err = 0;
+
+  smpl_t samplerate;
+  smpl_t freq_min;
+  smpl_t freq_max;
+  if (!PyArg_ParseTuple (args, AUBIO_NPY_SMPL_CHR AUBIO_NPY_SMPL_CHR
+        AUBIO_NPY_SMPL_CHR, &samplerate, &freq_min, &freq_max)) {
+    return NULL;
+  }
+
+  err = aubio_filterbank_set_mel_coeffs_htk (self->o, samplerate,
+      freq_min, freq_max);
+  if (err > 0) {
+    if (PyErr_Occurred() == NULL) {
+      PyErr_SetString (PyExc_ValueError, "error running set_mel_coeffs_htk");
+    } else {
+      // change the RuntimeError into ValueError
+      PyObject *type, *value, *traceback;
+      PyErr_Fetch(&type, &value, &traceback);
+      PyErr_Restore(PyExc_ValueError, value, traceback);
+    }
     return NULL;
   }
   Py_RETURN_NONE;
@@ -195,15 +447,87 @@ Py_filterbank_get_coeffs (Py_filterbank * self, PyObject *unused)
       aubio_filterbank_get_coeffs (self->o) );
 }
 
+static PyObject *
+Py_filterbank_set_power(Py_filterbank *self, PyObject *args)
+{
+  smpl_t power;
+
+  if (!PyArg_ParseTuple (args, AUBIO_NPY_SMPL_CHR, &power)) {
+    return NULL;
+  }
+  if(aubio_filterbank_set_power (self->o, power)) {
+    if (PyErr_Occurred() == NULL) {
+      PyErr_SetString (PyExc_ValueError,
+          "error running filterbank.set_power");
+    } else {
+      // change the RuntimeError into ValueError
+      PyObject *type, *value, *traceback;
+      PyErr_Fetch(&type, &value, &traceback);
+      PyErr_Restore(PyExc_ValueError, value, traceback);
+    }
+    return NULL;
+  }
+  Py_RETURN_NONE;
+}
+
+static PyObject *
+Py_filterbank_get_power (Py_filterbank * self, PyObject *unused)
+{
+  smpl_t power = aubio_filterbank_get_power(self->o);
+  return (PyObject *)PyFloat_FromDouble (power);
+}
+
+static PyObject *
+Py_filterbank_set_norm(Py_filterbank *self, PyObject *args)
+{
+  smpl_t norm;
+
+  if (!PyArg_ParseTuple (args, AUBIO_NPY_SMPL_CHR, &norm)) {
+    return NULL;
+  }
+  if(aubio_filterbank_set_norm (self->o, norm)) {
+    if (PyErr_Occurred() == NULL) {
+      PyErr_SetString (PyExc_ValueError,
+          "error running filterbank.set_power");
+    } else {
+      // change the RuntimeError into ValueError
+      PyObject *type, *value, *traceback;
+      PyErr_Fetch(&type, &value, &traceback);
+      PyErr_Restore(PyExc_ValueError, value, traceback);
+    }
+    return NULL;
+  }
+  Py_RETURN_NONE;
+}
+
+static PyObject *
+Py_filterbank_get_norm (Py_filterbank * self, PyObject *unused)
+{
+  smpl_t norm = aubio_filterbank_get_norm(self->o);
+  return (PyObject *)PyFloat_FromDouble (norm);
+}
+
 static PyMethodDef Py_filterbank_methods[] = {
   {"set_triangle_bands", (PyCFunction) Py_filterbank_set_triangle_bands,
-    METH_VARARGS, "set coefficients of filterbanks"},
+    METH_VARARGS, Py_filterbank_set_triangle_bands_doc},
   {"set_mel_coeffs_slaney", (PyCFunction) Py_filterbank_set_mel_coeffs_slaney,
-    METH_VARARGS, "set coefficients of filterbank as in Auditory Toolbox"},
+    METH_VARARGS, Py_filterbank_set_mel_coeffs_slaney_doc},
+  {"set_mel_coeffs", (PyCFunction) Py_filterbank_set_mel_coeffs,
+    METH_VARARGS, Py_filterbank_set_mel_coeffs_doc},
+  {"set_mel_coeffs_htk", (PyCFunction) Py_filterbank_set_mel_coeffs_htk,
+    METH_VARARGS, Py_filterbank_set_mel_coeffs_htk_doc},
   {"get_coeffs", (PyCFunction) Py_filterbank_get_coeffs,
-    METH_NOARGS, "get coefficients of filterbank"},
+    METH_NOARGS, Py_filterbank_get_coeffs_doc},
   {"set_coeffs", (PyCFunction) Py_filterbank_set_coeffs,
-    METH_VARARGS, "set coefficients of filterbank"},
+    METH_VARARGS, Py_filterbank_set_coeffs_doc},
+  {"set_power", (PyCFunction) Py_filterbank_set_power,
+    METH_VARARGS, Py_filterbank_set_power_doc},
+  {"get_power", (PyCFunction) Py_filterbank_get_power,
+    METH_NOARGS, Py_filterbank_get_power_doc},
+  {"set_norm", (PyCFunction) Py_filterbank_set_norm,
+    METH_VARARGS, Py_filterbank_set_norm_doc},
+  {"get_norm", (PyCFunction) Py_filterbank_get_norm,
+    METH_NOARGS, Py_filterbank_get_norm_doc},
   {NULL}
 };
 
index 3078e07..0827ea2 100644 (file)
@@ -39,7 +39,7 @@ Py_aubio_level_lin(PyObject *self, PyObject *args)
     return NULL;
   }
 
-  level_lin = Py_BuildValue(AUBIO_NPY_SMPL_CHR, aubio_level_lin(&vec));
+  level_lin = PyFloat_FromDouble(aubio_level_lin(&vec));
   if (level_lin == NULL) {
     PyErr_SetString (PyExc_ValueError, "failed computing level_lin");
     return NULL;
@@ -67,7 +67,7 @@ Py_aubio_db_spl(PyObject *self, PyObject *args)
     return NULL;
   }
 
-  db_spl = Py_BuildValue(AUBIO_NPY_SMPL_CHR, aubio_db_spl(&vec));
+  db_spl = PyFloat_FromDouble(aubio_db_spl(&vec));
   if (db_spl == NULL) {
     PyErr_SetString (PyExc_ValueError, "failed computing db_spl");
     return NULL;
@@ -96,7 +96,7 @@ Py_aubio_silence_detection(PyObject *self, PyObject *args)
     return NULL;
   }
 
-  silence_detection = Py_BuildValue("I", aubio_silence_detection(&vec, threshold));
+  silence_detection = PyLong_FromLong(aubio_silence_detection(&vec, threshold));
   if (silence_detection == NULL) {
     PyErr_SetString (PyExc_ValueError, "failed computing silence_detection");
     return NULL;
@@ -125,7 +125,7 @@ Py_aubio_level_detection(PyObject *self, PyObject *args)
     return NULL;
   }
 
-  level_detection = Py_BuildValue(AUBIO_NPY_SMPL_CHR, aubio_level_detection(&vec, threshold));
+  level_detection = PyFloat_FromDouble(aubio_level_detection(&vec, threshold));
   if (level_detection == NULL) {
     PyErr_SetString (PyExc_ValueError, "failed computing level_detection");
     return NULL;
@@ -133,3 +133,105 @@ Py_aubio_level_detection(PyObject *self, PyObject *args)
 
   return level_detection;
 }
+
+PyObject *
+Py_aubio_shift(PyObject *self, PyObject *args)
+{
+  PyObject *input;
+  fvec_t vec;
+
+  if (!PyArg_ParseTuple (args, "O:shift", &input)) {
+    return NULL;
+  }
+
+  if (input == NULL) {
+    return NULL;
+  }
+
+  if (!PyAubio_ArrayToCFvec(input, &vec)) {
+    return NULL;
+  }
+
+  fvec_shift(&vec);
+
+  //Py_RETURN_NONE;
+  return (PyObject *) PyAubio_CFvecToArray(&vec);
+}
+
+PyObject *
+Py_aubio_ishift(PyObject *self, PyObject *args)
+{
+  PyObject *input;
+  fvec_t vec;
+
+  if (!PyArg_ParseTuple (args, "O:shift", &input)) {
+    return NULL;
+  }
+
+  if (input == NULL) {
+    return NULL;
+  }
+
+  if (!PyAubio_ArrayToCFvec(input, &vec)) {
+    return NULL;
+  }
+
+  fvec_ishift(&vec);
+
+  //Py_RETURN_NONE;
+  return (PyObject *) PyAubio_CFvecToArray(&vec);
+}
+
+PyObject*
+Py_aubio_hztomel(PyObject *self, PyObject *args, PyObject *kwds)
+{
+  smpl_t v;
+  PyObject *htk = NULL;
+  static char *kwlist[] = {"f", "htk", NULL};
+  if (!PyArg_ParseTupleAndKeywords(args, kwds, AUBIO_NPY_SMPL_CHR "|O",
+        kwlist, &v, &htk))
+  {
+    return NULL;
+  }
+  if (htk != NULL && PyObject_IsTrue(htk) == 1)
+    return PyFloat_FromDouble(aubio_hztomel_htk(v));
+  else
+    return PyFloat_FromDouble(aubio_hztomel(v));
+}
+
+PyObject*
+Py_aubio_meltohz(PyObject *self, PyObject *args, PyObject *kwds)
+{
+  smpl_t v;
+  PyObject *htk = NULL;
+  static char *kwlist[] = {"m", "htk", NULL};
+  if (!PyArg_ParseTupleAndKeywords(args, kwds, AUBIO_NPY_SMPL_CHR "|O",
+        kwlist, &v, &htk))
+  {
+    return NULL;
+  }
+  if (htk != NULL && PyObject_IsTrue(htk) == 1)
+    return PyFloat_FromDouble(aubio_meltohz_htk(v));
+  else
+    return PyFloat_FromDouble(aubio_meltohz(v));
+}
+
+PyObject*
+Py_aubio_hztomel_htk(PyObject *self, PyObject *args)
+{
+  smpl_t v;
+  if (!PyArg_ParseTuple(args, AUBIO_NPY_SMPL_CHR, &v)) {
+    return NULL;
+  }
+  return PyFloat_FromDouble(aubio_hztomel_htk(v));
+}
+
+PyObject*
+Py_aubio_meltohz_htk(PyObject *self, PyObject *args)
+{
+  smpl_t v;
+  if (!PyArg_ParseTuple(args, AUBIO_NPY_SMPL_CHR, &v)) {
+    return NULL;
+  }
+  return PyFloat_FromDouble(aubio_meltohz_htk(v));
+}
index 54ee230..27698d1 100644 (file)
 #define PY_AUBIO_MUSICUTILS_H
 
 static char Py_aubio_window_doc[] = ""
-"window(string, integer) -> fvec\n"
+"window(window_type, size)\n"
 "\n"
-"Create a window\n"
+"Create a window of length `size`. `window_type` should be one\n"
+"of the following:\n"
 "\n"
-"Example\n"
+"- `default` (same as `hanningz`).\n"
+"- `ones`\n"
+"- `rectangle`\n"
+"- `hamming`\n"
+"- `hanning`\n"
+"- `hanningz` [1]_\n"
+"- `blackman`\n"
+"- `blackman_harris`\n"
+"- `gaussian`\n"
+"- `welch`\n"
+"- `parzen`\n"
+"\n"
+"Parameters\n"
+"----------\n"
+"window_type : str\n"
+"   Type of window.\n"
+"size : int\n"
+"   Length of window.\n"
+"\n"
+"Returns\n"
 "-------\n"
+"fvec\n"
+"   Array of shape `(length,)` containing the new window.\n"
+"\n"
+"See Also\n"
+"--------\n"
+"pvoc, fft\n"
+"\n"
+"Examples\n"
+"--------\n"
+"Compute a zero-phase Hann window on `1024` points:\n"
 "\n"
-">>> window('hanningz', 1024)\n"
+">>> aubio.window('hanningz', 1024)\n"
 "array([  0.00000000e+00,   9.41753387e-06,   3.76403332e-05, ...,\n"
-"         8.46982002e-05,   3.76403332e-05,   9.41753387e-06], dtype=float32)";
+"         8.46982002e-05,   3.76403332e-05,   9.41753387e-06], dtype=float32)\n"
+"\n"
+"Plot different window types with `matplotlib <https://matplotlib.org/>`_:\n"
+"\n"
+">>> import matplotlib.pyplot as plt\n"
+">>> modes = ['default', 'ones', 'rectangle', 'hamming', 'hanning',\n"
+"...          'hanningz', 'blackman', 'blackman_harris', 'gaussian',\n"
+"...          'welch', 'parzen']; n = 2048\n"
+">>> for m in modes: plt.plot(aubio.window(m, n), label=m)\n"
+"...\n"
+">>> plt.legend(); plt.show()\n"
+"\n"
+"Note\n"
+"----\n"
+"The following examples contain the equivalent source code to compute\n"
+"each type of window with `NumPy <https://numpy.org>`_:\n"
+"\n"
+">>> n = 1024; x = np.arange(n, dtype=aubio.float_type)\n"
+">>> ones = np.ones(n).astype(aubio.float_type)\n"
+">>> rectangle = 0.5 * ones\n"
+">>> hanning = 0.5 - 0.5 * np.cos(2 * np.pi * x / n)\n"
+">>> hanningz = 0.5 * (1 - np.cos(2 * np.pi * x / n))\n"
+">>> hamming = 0.54 - 0.46 * np.cos(2.*np.pi * x / (n - 1))\n"
+">>> blackman = 0.42 \\\n"
+"...          - 0.50 * np.cos(2 * np.pi * x / (n - 1)) \\\n"
+"...          + 0.08 * np.cos(4 * np.pi * x / (n - 1))\n"
+">>> blackman_harris = 0.35875 \\\n"
+"...       - 0.48829 * np.cos(2 * np.pi * x / (n - 1)) \\\n"
+"...       + 0.14128 * np.cos(4 * np.pi * x / (n - 1)) \\\n"
+"...       + 0.01168 * np.cos(6 * np.pi * x / (n - 1))\n"
+">>> gaussian = np.exp( - 0.5 * ((x - 0.5 * (n - 1)) \\\n"
+"...                            / (0.25 * (n - 1)) )**2 )\n"
+">>> welch = 1 - ((2 * x - n) / (n + 1))**2\n"
+">>> parzen = 1 - np.abs((2 * x - n) / (n + 1))\n"
+">>> default = hanningz\n"
+"References\n"
+"----------\n"
+#if 0
+"`Window function <https://en.wikipedia.org/wiki/Window_function>`_ on\n"
+"Wikipedia.\n"
+"\n"
+#endif
+".. [1] Amalia de Götzen, Nicolas Bernardini, and Daniel Arfib. Traditional\n"
+"   (?) implementations of a phase vocoder: the tricks of the trade.\n"
+"   In *Proceedings of the International Conference on Digital Audio\n"
+"   Effects* (DAFx-00), pages 37–44, University of Verona, Italy, 2000.\n"
+"   (`online version <"
+"https://www.cs.princeton.edu/courses/archive/spr09/cos325/Bernardini.pdf"
+">`_).\n"
+"";
 
 PyObject * Py_aubio_window(PyObject *self, PyObject *args);
 
 static char Py_aubio_level_lin_doc[] = ""
-"level_lin(fvec) -> fvec\n"
+"level_lin(x)\n"
 "\n"
-"Compute sound level on a linear scale.\n"
+"Compute sound pressure level of `x`, on a linear scale.\n"
 "\n"
-"This gives the average of the square amplitudes.\n"
+"Parameters\n"
+"----------\n"
+"x : fvec\n"
+"   input vector\n"
+"\n"
+"Returns\n"
+"-------\n"
+"float\n"
+"   Linear level of `x`.\n"
 "\n"
 "Example\n"
 "-------\n"
 "\n"
-">>> level_Lin(numpy.ones(1024))\n"
-"1.0";
+">>> aubio.level_lin(aubio.fvec(numpy.ones(1024)))\n"
+"1.0\n"
+"\n"
+"Note\n"
+"----\n"
+"Computed as the average of the squared amplitudes:\n"
+"\n"
+".. math:: L = \\frac {\\sum_{n=0}^{N-1} {x_n}^2} {N}\n"
+"\n"
+"See Also\n"
+"--------\n"
+"db_spl, silence_detection, level_detection\n"
+"";
 
 PyObject * Py_aubio_level_lin(PyObject *self, PyObject *args);
 
 static char Py_aubio_db_spl_doc[] = ""
-"Compute sound pressure level (SPL) in dB\n"
+"db_spl(x)\n"
 "\n"
-"This quantity is often wrongly called 'loudness'.\n"
+"Compute Sound Pressure Level (SPL) of `x`, in dB.\n"
 "\n"
-"This gives ten times the log10 of the average of the square amplitudes.\n"
+"Parameters\n"
+"----------\n"
+"x : fvec\n"
+"   input vector\n"
+"\n"
+"Returns\n"
+"-------\n"
+"float\n"
+"   Level of `x`, in dB SPL.\n"
 "\n"
 "Example\n"
 "-------\n"
 "\n"
-">>> db_spl(numpy.ones(1024))\n"
-"1.0";
+">>> aubio.db_spl(aubio.fvec(np.ones(1024)))\n"
+"1.0\n"
+">>> aubio.db_spl(0.7*aubio.fvec(np.ones(32)))\n"
+"-3.098040819168091\n"
+"\n"
+"Note\n"
+"----\n"
+"Computed as `log10` of :py:func:`level_lin`:\n"
+"\n"
+".. math::\n"
+"\n"
+"   {SPL}_{dB} = log10{\\frac {\\sum_{n=0}^{N-1}{x_n}^2} {N}}\n"
+"\n"
+"This quantity is often incorrectly called 'loudness'.\n"
+"\n"
+"See Also\n"
+"--------\n"
+"level_lin, silence_detection, level_detection\n"
+"";
 
 PyObject * Py_aubio_db_spl(PyObject *self, PyObject *args);
 
 static char Py_aubio_silence_detection_doc[] = ""
-"Check if buffer level in dB SPL is under a given threshold\n"
+"silence_detection(vec, level)\n"
 "\n"
-"Return 0 if level is under the given threshold, 1 otherwise.\n"
+"Check if level of `vec`, in dB SPL, is under a given threshold.\n"
 "\n"
-"Example\n"
+"Parameters\n"
+"----------\n"
+"vec : fvec\n"
+"   input vector\n"
+"level : float\n"
+"   level threshold, in dB SPL\n"
+"\n"
+"Returns\n"
 "-------\n"
+"int\n"
+"   `1` if level of `vec`, in dB SPL, is under `level`,\n"
+"   `0` otherwise.\n"
 "\n"
-">>> import numpy\n"""
-">>> silence_detection(numpy.ones(1024, dtype=\"float32\"), -80)\n"
-"0";
+"Examples\n"
+"--------\n"
+"\n"
+">>> aubio.silence_detection(aubio.fvec(32), -100.)\n"
+"1\n"
+">>> aubio.silence_detection(aubio.fvec(np.ones(32)), 0.)\n"
+"0\n"
+"\n"
+"See Also\n"
+"--------\n"
+"level_detection, db_spl, level_lin\n"
+"";
 
 PyObject * Py_aubio_silence_detection(PyObject *self, PyObject *args);
 
 static char Py_aubio_level_detection_doc[] = ""
-"Get buffer level in dB SPL if over a given threshold, 1. otherwise.\n"
+"level_detection(vec, level)\n"
+"\n"
+"Check if `vec` is above threshold `level`, in dB SPL.\n"
+"\n"
+"Parameters\n"
+"----------\n"
+"vec : fvec\n"
+"   input vector\n"
+"level : float\n"
+"   level threshold, in dB SPL\n"
+"\n"
+"Returns\n"
+"-------\n"
+"float\n"
+"   `1.0` if level of `vec` in dB SPL is under `level`,\n"
+"   `db_spl(vec)` otherwise.\n"
 "\n"
 "Example\n"
 "-------\n"
 "\n"
-">>> import numpy\n"""
-">>> level_detection(0.7*numpy.ones(1024, dtype=\"float32\"), -80)\n"
-"0";
+">>> aubio.level_detection(0.7*aubio.fvec(np.ones(1024)), -3.)\n"
+"1.0\n"
+">>> aubio.level_detection(0.7*aubio.fvec(np.ones(1024)), -4.)\n"
+"-3.0980708599090576\n"
+"\n"
+"See Also\n"
+"--------\n"
+"silence_detection, db_spl, level_lin\n"
+"";
 
 PyObject * Py_aubio_level_detection(PyObject *self, PyObject *args);
 
+static char Py_aubio_shift_doc[] = ""
+"shift(vec)\n"
+"\n"
+"Swap left and right partitions of a vector, in-place.\n"
+"\n"
+"Parameters\n"
+"----------\n"
+"vec : fvec\n"
+"   input vector to shift\n"
+"\n"
+"Returns\n"
+"-------\n"
+"fvec\n"
+"   The swapped vector.\n"
+"\n"
+"Notes\n"
+"-----\n"
+"The input vector is also modified.\n"
+"\n"
+"For a vector of length N, the partition is split at index N - N//2.\n"
+"\n"
+"Example\n"
+"-------\n"
+"\n"
+">>> aubio.shift(aubio.fvec(np.arange(3)))\n"
+"array([2., 0., 1.], dtype=" AUBIO_NPY_SMPL_STR ")\n"
+"\n"
+"See Also\n"
+"--------\n"
+"ishift\n"
+"";
+PyObject * Py_aubio_shift(PyObject *self, PyObject *args);
+
+static char Py_aubio_ishift_doc[] = ""
+"ishift(vec)\n"
+"\n"
+"Swap right and left partitions of a vector, in-place.\n"
+"\n"
+"Parameters\n"
+"----------\n"
+"vec : fvec\n"
+"   input vector to shift\n"
+"\n"
+"Returns\n"
+"-------\n"
+"fvec\n"
+"   The swapped vector.\n"
+"\n"
+"Notes\n"
+"-----\n"
+"The input vector is also modified.\n"
+"\n"
+"Unlike with :py:func:`shift`, the partition is split at index N//2.\n"
+"\n"
+"Example\n"
+"-------\n"
+"\n"
+">>> aubio.ishift(aubio.fvec(np.arange(3)))\n"
+"array([1., 2., 0.], dtype=" AUBIO_NPY_SMPL_STR ")\n"
+"\n"
+"See Also\n"
+"--------\n"
+"shift\n"
+"";
+PyObject * Py_aubio_ishift(PyObject *self, PyObject *args);
+
+static char Py_aubio_hztomel_doc[] = ""
+"hztomel(f, htk=False)\n"
+"\n"
+"Convert a scalar from frequency to mel scale.\n"
+"\n"
+"Parameters\n"
+"----------\n"
+"m : float\n"
+"   input frequency, in Hz\n"
+"htk : bool\n"
+"   if `True`, use Htk mel scale instead of Slaney.\n"
+"\n"
+"Returns\n"
+"-------\n"
+"float\n"
+"   output mel\n"
+"\n"
+"See Also\n"
+"--------\n"
+"meltohz\n"
+"";
+PyObject * Py_aubio_hztomel(PyObject *self, PyObject *args);
+
+static char Py_aubio_meltohz_doc[] = ""
+"meltohz(m, htk=False)\n"
+"\n"
+"Convert a scalar from mel scale to frequency.\n"
+"\n"
+"Parameters\n"
+"----------\n"
+"m : float\n"
+"   input mel\n"
+"htk : bool\n"
+"   if `True`, use Htk mel scale instead of Slaney.\n"
+"\n"
+"Returns\n"
+"-------\n"
+"float\n"
+"   output frequency, in Hz\n"
+"\n"
+"See Also\n"
+"--------\n"
+"hztomel\n"
+"";
+PyObject * Py_aubio_meltohz(PyObject *self, PyObject *args);
+
+static char Py_aubio_hztomel_htk_doc[] = ""
+"hztomel_htk(m)\n"
+"\n"
+"Same as `hztomel(m, htk=True)`\n"
+"\n"
+"See Also\n"
+"--------\n"
+"hztomel\n"
+"";
+PyObject * Py_aubio_hztomel_htk(PyObject *self, PyObject *args);
+
+static char Py_aubio_meltohz_htk_doc[] = ""
+"meltohz_htk(m)\n"
+"\n"
+"Same as `meltohz(m, htk=True)`\n"
+"\n"
+"See Also\n"
+"--------\n"
+"meltohz\n"
+"";
+PyObject * Py_aubio_meltohz_htk(PyObject *self, PyObject *args);
+
 #endif /* PY_AUBIO_MUSICUTILS_H */
index e53fdf9..4d36fb3 100644 (file)
@@ -1,6 +1,58 @@
 #include "aubio-types.h"
 
-static char Py_pvoc_doc[] = "pvoc object";
+static char Py_pvoc_doc[] = ""
+"pvoc(win_s=512, hop_s=256)\n"
+"\n"
+"Phase vocoder.\n"
+"\n"
+"`pvoc` creates callable object implements a phase vocoder [1]_,\n"
+"using the tricks detailed in [2]_.\n"
+"\n"
+"The call function takes one input of type `fvec` and of size\n"
+"`hop_s`, and returns a `cvec` of length `win_s//2+1`.\n"
+"\n"
+"Parameters\n"
+"----------\n"
+"win_s : int\n"
+"  number of channels in the phase-vocoder.\n"
+"hop_s : int\n"
+"  number of samples expected between each call\n"
+"\n"
+"Examples\n"
+"--------\n"
+">>> x = aubio.fvec(256)\n"
+">>> pv = aubio.pvoc(512, 256)\n"
+">>> pv(x)\n"
+"aubio cvec of 257 elements\n"
+"\n"
+"Default values for hop_s and win_s are provided:\n"
+"\n"
+">>> pv = aubio.pvoc()\n"
+">>> pv.win_s, pv.hop_s\n"
+"512, 256\n"
+"\n"
+"A `cvec` can be resynthesised using `rdo()`:\n"
+"\n"
+">>> pv = aubio.pvoc(512, 256)\n"
+">>> y = aubio.cvec(512)\n"
+">>> x_reconstructed = pv.rdo(y)\n"
+">>> x_reconstructed.shape\n"
+"(256,)\n"
+"\n"
+"References\n"
+"----------\n"
+".. [1] James A. Moorer. The use of the phase vocoder in computer music\n"
+"   applications. `Journal of the Audio Engineering Society`,\n"
+"   26(1/2):42–45, 1978.\n"
+".. [2] Amalia de Götzen, Nicolas Bernardini, and Daniel Arfib. Traditional\n"
+"   (?) implementations of a phase vocoder: the tricks of the trade.\n"
+"   In `Proceedings of the International Conference on Digital Audio\n"
+"   Effects` (DAFx-00), pages 37–44, University of Verona, Italy, 2000.\n"
+"   (`online version <"
+"https://www.cs.princeton.edu/courses/archive/spr09/cos325/Bernardini.pdf"
+">`_).\n"
+"";
+
 
 typedef struct
 {
@@ -38,10 +90,6 @@ Py_pvoc_new (PyTypeObject * type, PyObject * args, PyObject * kwds)
   self->win_s = Py_default_vector_length;
   self->hop_s = Py_default_vector_length/2;
 
-  if (self == NULL) {
-    return NULL;
-  }
-
   if (win_s > 0) {
     self->win_s = win_s;
   } else if (win_s < 0) {
@@ -121,9 +169,11 @@ Py_pvoc_do(Py_pvoc * self, PyObject * args)
 
 static PyMemberDef Py_pvoc_members[] = {
   {"win_s", T_INT, offsetof (Py_pvoc, win_s), READONLY,
-    "size of the window"},
+    "int: Size of phase vocoder analysis windows, in samples.\n"
+    ""},
   {"hop_s", T_INT, offsetof (Py_pvoc, hop_s), READONLY,
-    "size of the hop"},
+    "int: Interval between two analysis, in samples.\n"
+    ""},
   { NULL } // sentinel
 };
 
@@ -175,8 +225,47 @@ Pyaubio_pvoc_set_window (Py_pvoc *self, PyObject *args)
 
 static PyMethodDef Py_pvoc_methods[] = {
   {"rdo", (PyCFunction) Py_pvoc_rdo, METH_VARARGS,
-    "synthesis of spectral grain"},
-  {"set_window", (PyCFunction) Pyaubio_pvoc_set_window, METH_VARARGS, ""},
+    "rdo(fftgrain)\n"
+    "\n"
+    "Read a new spectral grain and resynthesise the next `hop_s`\n"
+    "output samples.\n"
+    "\n"
+    "Parameters\n"
+    "----------\n"
+    "fftgrain : cvec\n"
+    "    new input `cvec` to synthesize from, should be of size `win_s/2+1`\n"
+    "\n"
+    "Returns\n"
+    "-------\n"
+    "fvec\n"
+    "    re-synthesised output of shape `(hop_s,)`\n"
+    "\n"
+    "Example\n"
+    "-------\n"
+    ">>> pv = aubio.pvoc(2048, 512)\n"
+    ">>> out = pv.rdo(aubio.cvec(2048))\n"
+    ">>> out.shape\n"
+    "(512,)\n"
+    ""},
+  {"set_window", (PyCFunction) Pyaubio_pvoc_set_window, METH_VARARGS,
+    "set_window(window_type)\n"
+    "\n"
+    "Set window function\n"
+    "\n"
+    "Parameters\n"
+    "----------\n"
+    "window_type : str\n"
+    "    the window type to use for this phase vocoder\n"
+    "\n"
+    "Raises\n"
+    "------\n"
+    "ValueError\n"
+    "    If an unknown window type was given.\n"
+    "\n"
+    "See Also\n"
+    "--------\n"
+    "window : create a window.\n"
+    ""},
   {NULL}
 };
 
index 3362abb..4fc514f 100644 (file)
@@ -12,53 +12,78 @@ typedef struct
 } Py_sink;
 
 static char Py_sink_doc[] = ""
-"  __new__(path, samplerate = 44100, channels = 1)\n"
+"sink(path, samplerate=44100, channels=1)\n"
 "\n"
-"      Create a new sink, opening the given path for writing.\n"
+"Write audio samples to file.\n"
 "\n"
-"      Examples\n"
-"      --------\n"
+"Parameters\n"
+"----------\n"
+"path : str\n"
+"   Pathname of the file to be opened for writing.\n"
+"samplerate : int\n"
+"   Sampling rate of the file, in Hz.\n"
+"channels : int\n"
+"   Number of channels to create the file with.\n"
 "\n"
-"      Create a new sink at 44100Hz, mono:\n"
+"Examples\n"
+"--------\n"
 "\n"
-"      >>> sink('/tmp/t.wav')\n"
+"Create a new sink at 44100Hz, mono:\n"
 "\n"
-"      Create a new sink at 8000Hz, mono:\n"
+">>> snk = aubio.sink('out.wav')\n"
 "\n"
-"      >>> sink('/tmp/t.wav', samplerate = 8000)\n"
+"Create a new sink at 32000Hz, stereo, write 100 samples into it:\n"
 "\n"
-"      Create a new sink at 32000Hz, stereo:\n"
+">>> snk = aubio.sink('out.wav', samplerate=16000, channels=3)\n"
+">>> snk(aubio.fvec(100), 100)\n"
 "\n"
-"      >>> sink('/tmp/t.wav', samplerate = 32000, channels = 2)\n"
+"Open a new sink at 48000Hz, stereo, write `1234` samples into it:\n"
 "\n"
-"      Create a new sink at 32000Hz, 5 channels:\n"
+">>> with aubio.sink('out.wav', samplerate=48000, channels=2) as src:\n"
+"...     snk(aubio.fvec(1024), 1024)\n"
+"...     snk(aubio.fvec(210), 210)\n"
+"...\n"
 "\n"
-"      >>> sink('/tmp/t.wav', channels = 5, samplerate = 32000)\n"
-"\n"
-"  __call__(vec, write)\n"
-"      x(vec,write) <==> x.do(vec, write)\n"
-"\n"
-"      Write vector to sink.\n"
-"\n"
-"      See also\n"
-"      --------\n"
-"      aubio.sink.do\n"
+"See also\n"
+"--------\n"
+"source: read audio samples from a file.\n"
 "\n";
 
 static char Py_sink_do_doc[] = ""
-"x.do(vec, write) <==> x(vec, write)\n"
+"do(vec, write)\n"
+"\n"
+"Write a single channel vector to sink.\n"
 "\n"
-"write monophonic vector to sink";
+"Parameters\n"
+"----------\n"
+"vec : fvec\n"
+"   input vector `(n,)` where `n >= 0`.\n"
+"write : int\n"
+"   Number of samples to write.\n"
+"";
 
 static char Py_sink_do_multi_doc[] = ""
-"x.do_multi(mat, write)\n"
+"do_multi(mat, write)\n"
 "\n"
-"write polyphonic vector to sink";
+"Write a matrix containing vectors from multiple channels to sink.\n"
+"\n"
+"Parameters\n"
+"----------\n"
+"mat : numpy.ndarray\n"
+"   input matrix of shape `(channels, n)`, where `n >= 0`.\n"
+"write : int\n"
+"   Number of frames to write.\n"
+"";
 
 static char Py_sink_close_doc[] = ""
-"x.close()\n"
+"close()\n"
+"\n"
+"Close this sink now.\n"
 "\n"
-"close this sink now";
+"By default, the sink will be closed before being deleted.\n"
+"Explicitely closing a sink can be useful to control the number\n"
+"of files simultaneously opened.\n"
+"";
 
 static PyObject *
 Py_sink_new (PyTypeObject * pytype, PyObject * args, PyObject * kwds)
@@ -125,8 +150,10 @@ Py_sink_init (Py_sink * self, PyObject * args, PyObject * kwds)
 static void
 Py_sink_del (Py_sink *self, PyObject *unused)
 {
-  del_aubio_sink(self->o);
-  free(self->mwrite_data.data);
+  if (self->o) {
+    del_aubio_sink(self->o);
+    free(self->mwrite_data.data);
+  }
   if (self->uri) {
     free(self->uri);
   }
@@ -188,11 +215,11 @@ Py_sink_do_multi(Py_sink * self, PyObject * args)
 
 static PyMemberDef Py_sink_members[] = {
   {"uri", T_STRING, offsetof (Py_sink, uri), READONLY,
-    "path at which the sink was created"},
+    "str (read-only): Path at which the sink was created."},
   {"samplerate", T_INT, offsetof (Py_sink, samplerate), READONLY,
-    "samplerate at which the sink was created"},
+    "int (read-only): Samplerate at which the sink was created."},
   {"channels", T_INT, offsetof (Py_sink, channels), READONLY,
-    "number of channels with which the sink was created"},
+    "int (read-only): Number of channels with which the sink was created."},
   { NULL } // sentinel
 };
 
index 219bdd4..7e9d48b 100644 (file)
@@ -16,68 +16,316 @@ typedef struct
 } Py_source;
 
 static char Py_source_doc[] = ""
-"   __new__(path, samplerate = 0, hop_size = 512, channels = 1)\n"
+"source(path, samplerate=0, hop_size=512, channels=0)\n"
 "\n"
-"       Create a new source, opening the given path for reading.\n"
+"Read audio samples from a media file.\n"
 "\n"
-"       Examples\n"
-"       --------\n"
+"`source` open the file specified in `path` and creates a callable\n"
+"returning `hop_size` new audio samples at each invocation.\n"
 "\n"
-"       Create a new source, using the original samplerate, with hop_size = 512:\n"
+"If `samplerate=0` (default), the original sampling rate of `path`\n"
+"will be used. Otherwise, the output audio samples will be\n"
+"resampled at the desired sampling-rate.\n"
 "\n"
-"       >>> source('/tmp/t.wav')\n"
+"If `channels=0` (default), the original number of channels\n"
+"in `path` will be used. Otherwise, the output audio samples\n"
+"will be down-mixed or up-mixed to the desired number of\n"
+"channels.\n"
 "\n"
-"       Create a new source, resampling the original to 8000Hz:\n"
+"If `path` is a URL, a remote connection will be attempted to\n"
+"open the resource and stream data from it.\n"
 "\n"
-"       >>> source('/tmp/t.wav', samplerate = 8000)\n"
+"The parameter `hop_size` determines how many samples should be\n"
+"read at each consecutive calls.\n"
 "\n"
-"       Create a new source, resampling it at 32000Hz, hop_size = 32:\n"
+"Parameters\n"
+"----------\n"
+"path : str\n"
+"   pathname (or URL) of the file to be opened for reading\n"
+"samplerate : int, optional\n"
+"   sampling rate of the file\n"
+"hop_size : int, optional\n"
+"   number of samples to be read per iteration\n"
+"channels : int, optional\n"
+"   number of channels of the file\n"
 "\n"
-"       >>> source('/tmp/t.wav', samplerate = 32000, hop_size = 32)\n"
+"Examples\n"
+"--------\n"
+"By default, when only `path` is given, the file will be opened\n"
+"with its original sampling rate and channel:\n"
 "\n"
-"       Create a new source, using its original samplerate:\n"
+">>> src = aubio.source('stereo.wav')\n"
+">>> src.uri, src.samplerate, src.channels, src.duration\n"
+"('stereo.wav', 48000, 2, 86833)\n"
 "\n"
-"       >>> source('/tmp/t.wav', samplerate = 0)\n"
+"A typical loop to read all samples from a local file could\n"
+"look like this:\n"
 "\n"
-"   __call__()\n"
-"       vec, read = x() <==> vec, read = x.do()\n"
+">>> src = aubio.source('stereo.wav')\n"
+">>> total_read = 0\n"
+">>> while True:\n"
+"...     samples, read = src()\n"
+"...     # do something with samples\n"
+"...     total_read += read\n"
+"...     if read < src.hop_size:\n"
+"...         break\n"
+"...\n"
 "\n"
-"       Read vector from source.\n"
+"In a more Pythonic way, it can also look like this:\n"
 "\n"
-"       See also\n"
-"       --------\n"
-"       aubio.source.do\n"
-"\n";
+">>> total_read = 0\n"
+">>> with aubio.source('stereo.wav') as src:\n"
+"...     for frames in src:\n"
+"...         total_read += samples.shape[-1]\n"
+"...\n"
+"\n"
+".. rubric:: Basic interface\n"
+"\n"
+"`source` is a **callable**; its :meth:`__call__` method\n"
+"returns a tuple containing:\n"
+"\n"
+"- a vector of shape `(hop_size,)`, filled with the `read` next\n"
+"  samples available, zero-padded if `read < hop_size`\n"
+"- `read`, an integer indicating the number of samples read\n"
+"\n"
+"To read the first `hop_size` samples from the source, simply call\n"
+"the instance itself, with no argument:\n"
+"\n"
+">>> src = aubio.source('song.ogg')\n"
+">>> samples, read = src()\n"
+">>> samples.shape, read, src.hop_size\n"
+"((512,), 512, 512)\n"
+"\n"
+"The first call returned the slice of samples `[0 : hop_size]`.\n"
+"The next call will return samples `[hop_size: 2*hop_size]`.\n"
+"\n"
+"After several invocations of :meth:`__call__`, when reaching the end\n"
+"of the opened stream, `read` might become less than `hop_size`:\n"
+"\n"
+">>> samples, read = src()\n"
+">>> samples.shape, read\n"
+"((512,), 354)\n"
+"\n"
+"The end of the vector `samples` is filled with zeros.\n"
+"\n"
+"After the end of the stream, `read` will be `0` since no more\n"
+"samples are available:\n"
+"\n"
+">>> samples, read = src()\n"
+">>> samples.shape, read\n"
+"((512,), 0)\n"
+"\n"
+"**Note**: when the source has more than one channels, they\n"
+"are be down-mixed to mono when invoking :meth:`__call__`.\n"
+"To read from each individual channel, see :meth:`__next__`.\n"
+"\n"
+".. rubric:: ``for`` statements\n"
+"\n"
+"The `source` objects are **iterables**. This allows using them\n"
+"directly in a ``for`` loop, which calls :meth:`__next__` until\n"
+"the end of the stream is reached:\n"
+"\n"
+">>> src = aubio.source('stereo.wav')\n"
+">>> for frames in src:\n"
+">>>     print (frames.shape)\n"
+"...\n"
+"(2, 512)\n"
+"(2, 512)\n"
+"(2, 230)\n"
+"\n"
+"**Note**: When `next(self)` is called on a source with multiple\n"
+"channels, an array of shape `(channels, read)` is returned,\n"
+"unlike with :meth:`__call__` which always returns the down-mixed\n"
+"channels.\n"
+"\n"
+"If the file is opened with a single channel, `next(self)` returns\n"
+"an array of shape `(read,)`:\n"
+"\n"
+">>> src = aubio.source('stereo.wav', channels=1)\n"
+">>> next(src).shape\n"
+"(512,)\n"
+"\n"
+".. rubric:: ``with`` statements\n"
+"\n"
+"The `source` objects are **context managers**, which allows using\n"
+"them in ``with`` statements:\n"
+"\n"
+">>> with aubio.source('audiotrack.wav') as source:\n"
+"...     n_frames=0\n"
+"...     for samples in source:\n"
+"...         n_frames += len(samples)\n"
+"...     print('read', n_frames, 'samples in', samples.shape[0], 'channels',\n"
+"...         'from file \"%%s\"' %% source.uri)\n"
+"...\n"
+"read 239334 samples in 2 channels from file \"audiotrack.wav\"\n"
+"\n"
+"The file will be closed before exiting the statement.\n"
+"\n"
+"See also the methods implementing the context manager,\n"
+":meth:`__enter__` and :meth:`__exit__`.\n"
+"\n"
+".. rubric:: Seeking and closing\n"
+"\n"
+"At any time, :meth:`seek` can be used to move to any position in\n"
+"the file. For instance, to rewind to the start of the stream:\n"
+"\n"
+">>> src.seek(0)\n"
+"\n"
+"The opened file will be automatically closed when the object falls\n"
+"out of scope and is scheduled for garbage collection.\n"
+"\n"
+"In some cases, it is useful to manually :meth:`close` a given source,\n"
+"for instance to limit the number of simultaneously opened files:\n"
+"\n"
+">>> src.close()\n"
+"\n"
+".. rubric:: Input formats\n"
+"\n"
+"Depending on how aubio was compiled, :class:`source` may or may not\n"
+"open certain **files format**. Below are some examples that assume\n"
+"support for compressed files and remote urls was compiled in:\n"
+"\n"
+"- open a local file using its original sampling rate and channels,\n"
+"  and with the default hop size:\n"
+"\n"
+">>> s = aubio.source('sample.wav')\n"
+">>> s.uri, s.samplerate, s.channels, s.hop_size\n"
+"('sample.wav', 44100, 2, 512)\n"
+"\n"
+"- open a local compressed audio file, resampling to 32000Hz if needed:\n"
+"\n"
+">>> s = aubio.source('song.mp3', samplerate=32000)\n"
+">>> s.uri, s.samplerate, s.channels, s.hop_size\n"
+"('song.mp3', 32000, 2, 512)\n"
+"\n"
+"- open a local video file, down-mixing and resampling it to 16kHz:\n"
+"\n"
+">>> s = aubio.source('movie.mp4', samplerate=16000, channels=1)\n"
+">>> s.uri, s.samplerate, s.channels, s.hop_size\n"
+"('movie.mp4', 16000, 1, 512)\n"
+"\n"
+"- open a remote resource, with hop_size = 1024:\n"
+"\n"
+">>> s = aubio.source('https://aubio.org/drum.ogg', hop_size=1024)\n"
+">>> s.uri, s.samplerate, s.channels, s.hop_size\n"
+"('https://aubio.org/drum.ogg', 48000, 2, 1024)\n"
+"\n"
+"See Also\n"
+"--------\n"
+"sink: write audio samples to a file.\n"
+"";
 
 static char Py_source_get_samplerate_doc[] = ""
-"x.get_samplerate() -> source samplerate\n"
+"get_samplerate()\n"
+"\n"
+"Get sampling rate of source.\n"
 "\n"
-"Get samplerate of source.";
+"Returns\n"
+"-------\n"
+"int\n"
+"    Sampling rate, in Hz.\n"
+"";
 
 static char Py_source_get_channels_doc[] = ""
-"x.get_channels() -> number of channels\n"
+"get_channels()\n"
 "\n"
-"Get number of channels in source.";
+"Get number of channels in source.\n"
+"\n"
+"Returns\n"
+"-------\n"
+"int\n"
+"    Number of channels.\n"
+"";
 
 static char Py_source_do_doc[] = ""
-"vec, read = x.do() <==> vec, read = x()\n"
+"source.do()\n"
+"\n"
+"Read vector of audio samples.\n"
 "\n"
-"Read monophonic vector from source.";
+"If the audio stream in the source has more than one channel,\n"
+"the channels will be down-mixed.\n"
+"\n"
+"Returns\n"
+"-------\n"
+"samples : numpy.ndarray\n"
+"    `fvec` of size `hop_size` containing the new samples.\n"
+"read : int\n"
+"    Number of samples read from the source, equals to `hop_size`\n"
+"    before the end-of-file is reached, less when it is reached,\n"
+"    and `0` after.\n"
+"\n"
+"See Also\n"
+"--------\n"
+"do_multi\n"
+"\n"
+"Examples\n"
+"--------\n"
+">>> src = aubio.source('sample.wav', hop_size=1024)\n"
+">>> src.do()\n"
+"(array([-0.00123001, -0.00036685,  0.00097106, ..., -0.2031033 ,\n"
+"       -0.2025854 , -0.20221856], dtype=" AUBIO_NPY_SMPL_STR "), 1024)\n"
+"";
 
 static char Py_source_do_multi_doc[] = ""
-"mat, read = x.do_multi()\n"
+"do_multi()\n"
+"\n"
+"Read multiple channels of audio samples.\n"
 "\n"
-"Read polyphonic vector from source.";
+"If the source was opened with the same number of channels\n"
+"found in the stream, each channel will be read individually.\n"
+"\n"
+"If the source was opened with less channels than the number\n"
+"of channels in the stream, only the first channels will be read.\n"
+"\n"
+"If the source was opened with more channels than the number\n"
+"of channel in the original stream, the first channels will\n"
+"be duplicated on the additional output channel.\n"
+"\n"
+"Returns\n"
+"-------\n"
+"samples : numpy.ndarray\n"
+"    NumPy array of shape `(hop_size, channels)` containing the new\n"
+"    audio samples.\n"
+"read : int\n"
+"    Number of samples read from the source, equals to `hop_size`\n"
+"    before the end-of-file is reached, less when it is reached,\n"
+"    and `0` after.\n"
+"\n"
+"See Also\n"
+"--------\n"
+"do\n"
+"\n"
+"Examples\n"
+"--------\n"
+">>> src = aubio.source('sample.wav')\n"
+">>> src.do_multi()\n"
+"(array([[ 0.00668335,  0.0067749 ,  0.00714111, ..., -0.05737305,\n"
+"        -0.05856323, -0.06018066],\n"
+"       [-0.00842285, -0.0072937 , -0.00576782, ..., -0.09405518,\n"
+"        -0.09558105, -0.09725952]], dtype=" AUBIO_NPY_SMPL_STR "), 512)\n"
+"";
 
 static char Py_source_close_doc[] = ""
-"x.close()\n"
+"close()\n"
+"\n"
+"Close this source now.\n"
 "\n"
-"Close this source now.";
+".. note:: Closing twice a source will **not** raise any exception.\n"
+"";
 
 static char Py_source_seek_doc[] = ""
-"x.seek(position)\n"
+"seek(position)\n"
+"\n"
+"Seek to position in file.\n"
+"\n"
+"If the source was not opened with its original sampling-rate,\n"
+"`position` corresponds to the position in the re-sampled file.\n"
 "\n"
-"Seek to resampled frame position.";
+"Parameters\n"
+"----------\n"
+"position : str\n"
+"   position to seek to, in samples\n"
+"";
 
 static PyObject *
 Py_source_new (PyTypeObject * pytype, PyObject * args, PyObject * kwds)
@@ -188,6 +436,10 @@ Py_source_do(Py_source * self, PyObject * args)
   /* compute _do function */
   aubio_source_do (self->o, &(self->c_read_to), &read);
 
+  if (PyErr_Occurred() != NULL) {
+    return NULL;
+  }
+
   outputs = PyTuple_New(2);
   PyTuple_SetItem( outputs, 0, self->read_to );
   PyTuple_SetItem( outputs, 1, (PyObject *)PyLong_FromLong(read));
@@ -209,6 +461,10 @@ Py_source_do_multi(Py_source * self, PyObject * args)
   /* compute _do function */
   aubio_source_do_multi (self->o, &(self->c_mread_to), &read);
 
+  if (PyErr_Occurred() != NULL) {
+    return NULL;
+  }
+
   outputs = PyTuple_New(2);
   PyTuple_SetItem( outputs, 0, self->mread_to);
   PyTuple_SetItem( outputs, 1, (PyObject *)PyLong_FromLong(read));
@@ -217,15 +473,29 @@ Py_source_do_multi(Py_source * self, PyObject * args)
 
 static PyMemberDef Py_source_members[] = {
   {"uri", T_STRING, offsetof (Py_source, uri), READONLY,
-    "path at which the source was created"},
+    "str (read-only): pathname or URL"},
   {"samplerate", T_INT, offsetof (Py_source, samplerate), READONLY,
-    "samplerate at which the source is viewed"},
+    "int (read-only): sampling rate"},
   {"channels", T_INT, offsetof (Py_source, channels), READONLY,
-    "number of channels found in the source"},
+    "int (read-only): number of channels"},
   {"hop_size", T_INT, offsetof (Py_source, hop_size), READONLY,
-    "number of consecutive frames that will be read at each do or do_multi call"},
+    "int (read-only): number of samples read per iteration"},
   {"duration", T_INT, offsetof (Py_source, duration), READONLY,
-    "total number of frames in the source (estimated)"},
+    "int (read-only): total number of frames in the source\n"
+    "\n"
+    "Can be estimated, for instance if the opened stream is\n"
+    "a compressed media or a remote resource.\n"
+    "\n"
+    "Example\n"
+    "-------\n"
+    ">>> n = 0\n"
+    ">>> src = aubio.source('track1.mp3')\n"
+    ">>> for samples in src:\n"
+    "...     n += samples.shape[-1]\n"
+    "...\n"
+    ">>> n, src.duration\n"
+    "(9638784, 9616561)\n"
+    ""},
   { NULL } // sentinel
 };
 
@@ -311,7 +581,10 @@ static PyObject* Pyaubio_source_iter_next(Py_source *self) {
       return vec;
     } else if (PyLong_AsLong(size) > 0) {
       // short read, return a shorter array
-      PyArrayObject *shortread = (PyArrayObject*)PyTuple_GetItem(done, 0);
+      PyObject *vec = PyTuple_GetItem(done, 0);
+      // take a copy to prevent resizing internal arrays
+      PyArrayObject *shortread = (PyArrayObject*)PyArray_FROM_OTF(vec,
+          NPY_NOTYPE, NPY_ARRAY_ENSURECOPY);
       PyArray_Dims newdims;
       PyObject *reshaped;
       newdims.len = PyArray_NDIM(shortread);
@@ -324,6 +597,7 @@ static PyObject* Pyaubio_source_iter_next(Py_source *self) {
       }
       reshaped = PyArray_Newshape(shortread, &newdims, NPY_CORDER);
       Py_DECREF(shortread);
+      Py_DECREF(vec);
       return reshaped;
     } else {
       PyErr_SetNone(PyExc_StopIteration);
index 8a4e917..d373d72 100644 (file)
@@ -58,7 +58,22 @@ static char Py_aubio_unary_types[] = {
   //NPY_OBJECT, NPY_OBJECT,
 };
 
-static char Py_unwrap2pi_doc[] = "map angle to unit circle [-pi, pi[";
+// Note: these docstrings should *not* include the function signatures
+
+static char Py_unwrap2pi_doc[] = ""
+"\n"
+"Map angle to unit circle :math:`[-\\pi, \\pi[`.\n"
+"\n"
+"Parameters\n"
+"----------\n"
+"x : numpy.ndarray\n"
+"   input array\n"
+"\n"
+"Returns\n"
+"-------\n"
+"numpy.ndarray\n"
+"   values clamped to the unit circle :math:`[-\\pi, \\pi[`\n"
+"";
 
 static void* Py_unwrap2pi_data[] = {
   (void *)aubio_unwrap2pi,
@@ -67,14 +82,40 @@ static void* Py_unwrap2pi_data[] = {
   //(void *)unwrap2pio,
 };
 
-static char Py_freqtomidi_doc[] = "convert frequency to midi";
+static char Py_freqtomidi_doc[] = ""
+"\n"
+"Convert frequency `[0; 23000[` to midi `[0; 128[`.\n"
+"\n"
+"Parameters\n"
+"----------\n"
+"x : numpy.ndarray\n"
+"    Array of frequencies, in Hz.\n"
+"\n"
+"Returns\n"
+"-------\n"
+"numpy.ndarray\n"
+"    Converted frequencies, in midi note.\n"
+"";
 
 static void* Py_freqtomidi_data[] = {
   (void *)aubio_freqtomidi,
   (void *)aubio_freqtomidi,
 };
 
-static char Py_miditofreq_doc[] = "convert midi to frequency";
+static char Py_miditofreq_doc[] = ""
+"\n"
+"Convert midi `[0; 128[` to frequency `[0, 23000]`.\n"
+"\n"
+"Parameters\n"
+"----------\n"
+"x : numpy.ndarray\n"
+"    Array of frequencies, in midi note.\n"
+"\n"
+"Returns\n"
+"-------\n"
+"numpy.ndarray\n"
+"    Converted frequencies, in Hz\n"
+"";
 
 static void* Py_miditofreq_data[] = {
   (void *)aubio_miditofreq,
diff --git a/python/lib/__init__.py b/python/lib/__init__.py
deleted file mode 100644 (file)
index e69de29..0000000
index e7b891c..ebe0fa8 100644 (file)
@@ -1,4 +1,26 @@
 #! /usr/bin/env python
+# -*- coding: utf8 -*-
+
+"""
+aubio
+=====
+
+Provides a number of classes and functions for music and audio signal
+analysis.
+
+How to use the documentation
+----------------------------
+
+Documentation of the python module is available as docstrings provided
+within the code, and a reference guide available online from `the
+aubio homepage <https://aubio.org/documentation>`_.
+
+The docstrings examples are written assuming `aubio` and `numpy` have been
+imported with:
+
+>>> import aubio
+>>> import numpy as np
+"""
 
 import numpy
 from ._aubio import __version__ as version
@@ -7,13 +29,57 @@ from ._aubio import *
 from .midiconv import *
 from .slicing import *
 
+
 class fvec(numpy.ndarray):
-    """a numpy vector holding audio samples"""
+    """fvec(input_arg=1024)
+    A vector holding float samples.
 
-    def __new__(cls, input_arg=1024, **kwargs):
+    If `input_arg` is an `int`, a 1-dimensional vector of length `input_arg`
+    will be created and filled with zeros. Otherwise, if `input_arg` is an
+    `array_like` object, it will be converted to a 1-dimensional vector of
+    type :data:`float_type`.
+
+    Parameters
+    ----------
+    input_arg : `int` or `array_like`
+        Can be a positive integer, or any object that can be converted to
+        a numpy array with :func:`numpy.array`.
+
+    Examples
+    --------
+    >>> aubio.fvec(10)
+    array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0.], dtype=float32)
+    >>> aubio.fvec([0,1,2])
+    array([0., 1., 2.], dtype=float32)
+    >>> a = np.arange(10); type(a), type(aubio.fvec(a))
+    (<class 'numpy.ndarray'>, <class 'numpy.ndarray'>)
+    >>> a.dtype, aubio.fvec(a).dtype
+    (dtype('int64'), dtype('float32'))
+
+    Notes
+    -----
+
+    In the Python world, `fvec` is simply a subclass of
+    :class:`numpy.ndarray`. In practice, any 1-dimensional `numpy.ndarray` of
+    `dtype` :data:`float_type` may be passed to methods accepting
+    `fvec` as parameter. For instance, `sink()` or `pvoc()`.
+
+    See Also
+    --------
+    cvec : a container holding spectral data
+    numpy.ndarray : parent class of :class:`fvec`
+    numpy.zeros : create a numpy array filled with zeros
+    numpy.array : create a numpy array from an existing object
+    """
+    def __new__(cls, input_arg=1024):
         if isinstance(input_arg, int):
             if input_arg == 0:
                 raise ValueError("vector length of 1 or more expected")
-            return numpy.zeros(input_arg, dtype=float_type, **kwargs)
+            return numpy.zeros(input_arg, dtype=float_type, order='C')
         else:
-            return numpy.array(input_arg, dtype=float_type, **kwargs)
+            np_input = numpy.array(input_arg, dtype=float_type, order='C')
+            if len(np_input.shape) != 1:
+                raise ValueError("input_arg should have shape (n,)")
+            if np_input.shape[0] == 0:
+                raise ValueError("vector length of 1 or more expected")
+            return np_input
index 292c64c..05780ea 100644 (file)
@@ -11,6 +11,7 @@ readable code examples, check out the `python/demos` folder."""
 
 import sys
 import argparse
+import warnings
 import aubio
 
 def aubio_parser():
@@ -101,6 +102,8 @@ def parser_add_subcommand_notes(subparsers):
             help='estimate midi-like notes (monophonic)')
     subparser.add_input()
     subparser.add_buf_hop_size()
+    subparser.add_silence()
+    subparser.add_release_drop()
     subparser.add_time_format()
     subparser.add_verbose_help()
     subparser.set_defaults(process=process_notes)
@@ -137,7 +140,7 @@ def parser_add_subcommand_quiet(subparsers):
     subparser.set_defaults(process=process_quiet)
 
 def parser_add_subcommand_cut(subparsers):
-    # quiet subcommand
+    # cut subcommand
     subparser = subparsers.add_parser('cut',
             help='slice at timestamps')
     subparser.add_input()
@@ -166,10 +169,10 @@ class AubioArgumentParser(argparse.ArgumentParser):
                 help="samplerate at which the file should be represented")
 
     def add_verbose_help(self):
-        self.add_argument("-v","--verbose",
+        self.add_argument("-v", "--verbose",
                 action="count", dest="verbose", default=1,
                 help="make lots of noise [default]")
-        self.add_argument("-q","--quiet",
+        self.add_argument("-q", "--quiet",
                 action="store_const", dest="verbose", const=0,
                 help="be quiet")
 
@@ -178,25 +181,25 @@ class AubioArgumentParser(argparse.ArgumentParser):
         self.add_hop_size(hop_size=hop_size)
 
     def add_buf_size(self, buf_size=512):
-        self.add_argument("-B","--bufsize",
+        self.add_argument("-B", "--bufsize",
                 action="store", dest="buf_size", default=buf_size,
                 metavar = "<size>", type=int,
                 help="buffer size [default=%d]" % buf_size)
 
     def add_hop_size(self, hop_size=256):
-        self.add_argument("-H","--hopsize",
+        self.add_argument("-H", "--hopsize",
                 metavar = "<size>", 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",
+        self.add_argument("-m", "--method",
                 metavar = "<method>", 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",
+        self.add_argument("-t", "--threshold",
                 metavar = "<threshold>", type=float,
                 action="store", dest="threshold", default=default,
                 help="threshold [default=%s]" % default)
@@ -207,6 +210,12 @@ class AubioArgumentParser(argparse.ArgumentParser):
                 action="store", dest="silence", default=-70,
                 help="silence threshold")
 
+    def add_release_drop(self):
+        self.add_argument("-d", "--release-drop",
+                metavar = "<value>", type=float,
+                action="store", dest="release_drop", default=10,
+                help="release drop threshold")
+
     def add_minioi(self, default="12ms"):
         self.add_argument("-M", "--minioi",
                 metavar = "<value>", type=str,
@@ -231,14 +240,16 @@ class AubioArgumentParser(argparse.ArgumentParser):
                  help=helpstr)
 
     def add_slicer_options(self):
-        self.add_argument("-o","--output", type = str,
+        self.add_argument("-o", "--output", type = str,
                 metavar = "<outputdir>",
                 action="store", dest="output_directory", default=None,
-                help="specify path where slices of the original file should be created")
+                help="specify path where slices of the original file should"
+                " be created")
         self.add_argument("--cut-until-nsamples", type = int,
                 metavar = "<samples>",
                 action = "store", dest = "cut_until_nsamples", default = None,
-                help="how many extra samples should be added at the end of each slice")
+                help="how many extra samples should be added at the end of"
+                " each slice")
         self.add_argument("--cut-every-nslices", type = int,
                 metavar = "<samples>",
                 action = "store", dest = "cut_every_nslices", default = None,
@@ -246,7 +257,11 @@ class AubioArgumentParser(argparse.ArgumentParser):
         self.add_argument("--cut-until-nslices", type = int,
                 metavar = "<slices>",
                 action = "store", dest = "cut_until_nslices", default = None,
-                help="how many extra slices should be added at the end of each slice")
+                help="how many extra slices should be added at the end of"
+                " each slice")
+        self.add_argument("--create-first",
+                action = "store_true", dest = "create_first", default = False,
+                help="always include first slice")
 
 # some utilities
 
@@ -277,7 +292,8 @@ class default_process(object):
             self.time2string = timefunc(args.time_format)
         if args.verbose > 2 and hasattr(self, 'options'):
             name = type(self).__name__.split('_')[1]
-            optstr = ' '.join(['running', name, 'with options', repr(self.options), '\n'])
+            optstr = ' '.join(['running', name, 'with options',
+                repr(self.options), '\n'])
             sys.stderr.write(optstr)
     def flush(self, frames_read, samplerate):
         # optionally called at the end of process
@@ -285,7 +301,7 @@ class default_process(object):
 
     def parse_options(self, args, valid_opts):
         # get any valid options found in a dictionnary of arguments
-        options = {k :v for k,v in vars(args).items() if k in valid_opts}
+        options = {k: v for k, v in vars(args).items() if k in valid_opts}
         self.options = options
 
     def remap_pvoc_options(self, options):
@@ -366,7 +382,7 @@ class process_tempo(process_beat):
         if len(self.beat_locations) < 2:
             outstr = "unknown bpm"
         else:
-            bpms = 60./ np.diff(self.beat_locations)
+            bpms = 60. / np.diff(self.beat_locations)
             median_bpm = np.mean(bpms)
             if len(self.beat_locations) < 10:
                 outstr = "%.2f bpm (uncertain)" % median_bpm
@@ -379,18 +395,22 @@ class process_notes(default_process):
     def __init__(self, args):
         self.parse_options(args, self.valid_opts)
         self.notes = aubio.notes(**self.options)
+        if args.silence is not None:
+            self.notes.set_silence(args.silence)
+        if args.release_drop is not None:
+            self.notes.set_release_drop(args.release_drop)
         super(process_notes, self).__init__(args)
     def __call__(self, block):
         return self.notes(block)
     def repr_res(self, res, frames_read, samplerate):
-        if res[2] != 0: # note off
+        if res[2] != 0:  # note off
             fmt_out = self.time2string(frames_read, samplerate)
             sys.stdout.write(fmt_out + '\n')
-        if res[0] != 0: # note on
+        if res[0] != 0:  # note on
             lastmidi = res[0]
             fmt_out = "%f\t" % lastmidi
             fmt_out += self.time2string(frames_read, samplerate)
-            sys.stdout.write(fmt_out) # + '\t')
+            sys.stdout.write(fmt_out)  # + '\t')
     def flush(self, frames_read, samplerate):
         eof = self.time2string(frames_read, samplerate)
         sys.stdout.write(eof + '\n')
@@ -457,13 +477,13 @@ class process_quiet(default_process):
         if aubio.silence_detection(block, self.silence) == 1:
             if self.wassilence != 1:
                 self.wassilence = 1
-                return 2 # newly found silence
-            return 1 # silence again
+                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
+                return -1  # newly found noise
+            return 0       # noise again
 
     def repr_res(self, res, frames_read, samplerate):
         fmt_out = None
@@ -483,29 +503,74 @@ class process_cut(process_onset):
 
     def __call__(self, block):
         ret = super(process_cut, self).__call__(block)
-        if ret: self.slices.append(self.onset.get_last())
+        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}
+        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}
+                     {'duration': duration, 'samplerate': samplerate}
         info = "created %d slices from " % len(self.slices)
         info += base_info
         sys.stderr.write(info)
 
+def _cut_slice(options, timestamps):
+    # cutting pass
+    nstamps = len(timestamps)
+    if nstamps > 0:
+        # generate output files
+        timestamps_end = None
+        if options.cut_every_nslices:
+            timestamps = timestamps[::options.cut_every_nslices]
+            nstamps = len(timestamps)
+        if options.cut_until_nslices and options.cut_until_nsamples:
+            msg = "using cut_until_nslices, but cut_until_nsamples is set"
+            warnings.warn(msg)
+        if options.cut_until_nsamples:
+            lag = options.cut_until_nsamples
+            timestamps_end = [t + lag for t in timestamps[1:]]
+            timestamps_end += [1e120]
+        if options.cut_until_nslices:
+            slice_lag = options.cut_until_nslices
+            timestamps_end = [t for t in timestamps[1 + slice_lag:]]
+            timestamps_end += [1e120] * (options.cut_until_nslices + 1)
+        aubio.slice_source_at_stamps(options.source_uri,
+                timestamps, timestamps_end = timestamps_end,
+                output_dir = options.output_directory,
+                samplerate = options.samplerate,
+                create_first = options.create_first)
+
 def main():
     parser = aubio_parser()
-    args = parser.parse_args()
+    if sys.version_info[0] != 3:
+        # on py2, create a dummy ArgumentParser to workaround the
+        # optional subcommand issue. See https://bugs.python.org/issue9253
+        # This ensures that:
+        #  - version string is shown when only '-V' is passed
+        #  - help is printed if  '-V' is passed with any other argument
+        #  - any other argument get forwarded to the real parser
+        parser_root = argparse.ArgumentParser(add_help=False)
+        parser_root.add_argument('-V', '--version', help="show version",
+                action="store_true", dest="show_version")
+        args, extras = parser_root.parse_known_args()
+        if not args.show_version:  # no -V, forward to parser
+            args = parser.parse_args(extras, namespace=args)
+        elif len(extras) != 0:     # -V with other arguments, print help
+            parser.print_help()
+            sys.exit(1)
+    else:  # in py3, we can simply use parser directly
+        args = parser.parse_args()
     if 'show_version' in args and args.show_version:
         sys.stdout.write('aubio version ' + aubio.version + '\n')
         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 or args.command in ['help']:
+    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()
         if args.command and args.command in ['help']:
@@ -539,7 +604,8 @@ def main():
                 # increment total number of frames read
                 frames_read += read
                 # exit loop at end of file
-                if read < a_source.hop_size: break
+                if read < a_source.hop_size:
+                    break
             # flush the processor if needed
             processor.flush(frames_read, a_source.samplerate)
             if args.verbose > 1:
@@ -547,7 +613,7 @@ def main():
                 fmt_string += " ({:d} samples in {:d} blocks of {:d})"
                 fmt_string += " from {:s} at {:d}Hz\n"
                 sys.stderr.write(fmt_string.format(
-                        frames_read/float(a_source.samplerate),
+                        frames_read / float(a_source.samplerate),
                         frames_read,
                         frames_read // a_source.hop_size + 1,
                         a_source.hop_size,
index c01615c..a31e38d 100644 (file)
@@ -5,83 +5,83 @@
 """
 
 import sys
-from aubio.cmd import AubioArgumentParser
+from aubio.cmd import AubioArgumentParser, _cut_slice
 
 def aubio_cut_parser():
     parser = AubioArgumentParser()
     parser.add_input()
-    parser.add_argument("-O","--onset-method",
+    parser.add_argument("-O", "--onset-method",
             action="store", dest="onset_method", default='default',
             metavar = "<onset_method>",
             help="onset detection method [default=default] \
                     complexdomain|hfc|phase|specdiff|energy|kl|mkl")
     # cutting methods
-    parser.add_argument("-b","--beat",
+    parser.add_argument("-b", "--beat",
             action="store_true", dest="beat", default=False,
             help="slice at beat locations")
     """
-    parser.add_argument("-S","--silencecut",
+    parser.add_argument("-S", "--silencecut",
             action="store_true", dest="silencecut", default=False,
             help="use silence locations")
-    parser.add_argument("-s","--silence",
+    parser.add_argument("-s", "--silence",
             metavar = "<value>",
             action="store", dest="silence", default=-70,
             help="silence threshold [default=-70]")
             """
     # algorithm parameters
     parser.add_buf_hop_size()
-    parser.add_argument("-t","--threshold", "--onset-threshold",
+    parser.add_argument("-t", "--threshold", "--onset-threshold",
             metavar = "<threshold>", type=float,
             action="store", dest="threshold", default=0.3,
             help="onset peak picking threshold [default=0.3]")
-    parser.add_argument("-c","--cut",
+    parser.add_argument("-c", "--cut",
             action="store_true", dest="cut", default=False,
             help="cut input sound file at detected labels")
     parser.add_minioi()
 
     """
-    parser.add_argument("-D","--delay",
+    parser.add_argument("-D", "--delay",
             action = "store", dest = "delay", type = float,
             metavar = "<seconds>", default=0,
             help="number of seconds to take back [default=system]\
                     default system delay is 3*hopsize/samplerate")
-    parser.add_argument("-C","--dcthreshold",
+    parser.add_argument("-C", "--dcthreshold",
             metavar = "<value>",
             action="store", dest="dcthreshold", default=1.,
             help="onset peak picking DC component [default=1.]")
-    parser.add_argument("-L","--localmin",
+    parser.add_argument("-L", "--localmin",
             action="store_true", dest="localmin", default=False,
             help="use local minima after peak detection")
-    parser.add_argument("-d","--derivate",
+    parser.add_argument("-d", "--derivate",
             action="store_true", dest="derivate", default=False,
             help="derivate onset detection function")
-    parser.add_argument("-z","--zerocross",
+    parser.add_argument("-z", "--zerocross",
             metavar = "<value>",
             action="store", dest="zerothres", default=0.008,
             help="zero-crossing threshold for slicing [default=0.00008]")
     # plotting functions
-    parser.add_argument("-p","--plot",
+    parser.add_argument("-p", "--plot",
             action="store_true", dest="plot", default=False,
             help="draw plot")
-    parser.add_argument("-x","--xsize",
+    parser.add_argument("-x", "--xsize",
             metavar = "<size>",
             action="store", dest="xsize", default=1.,
             type=float, help="define xsize for plot")
-    parser.add_argument("-y","--ysize",
+    parser.add_argument("-y", "--ysize",
             metavar = "<size>",
             action="store", dest="ysize", default=1.,
             type=float, help="define ysize for plot")
-    parser.add_argument("-f","--function",
+    parser.add_argument("-f", "--function",
             action="store_true", dest="func", default=False,
             help="print detection function")
-    parser.add_argument("-n","--no-onsets",
+    parser.add_argument("-n", "--no-onsets",
             action="store_true", dest="nplot", default=False,
             help="do not plot detected onsets")
-    parser.add_argument("-O","--outplot",
+    parser.add_argument("-O", "--outplot",
             metavar = "<output_image>",
             action="store", dest="outplot", default=None,
             help="save plot to output.{ps,png}")
-    parser.add_argument("-F","--spectrogram",
+    parser.add_argument("-F", "--spectrogram",
             action="store_true", dest="spectro", default=False,
             help="add spectrogram to the plot")
     """
@@ -101,13 +101,15 @@ def _cut_analyze(options):
 
     s = source(source_uri, samplerate, hopsize)
     if samplerate == 0:
-        samplerate = s.get_samplerate()
+        samplerate = s.samplerate
         options.samplerate = samplerate
 
     if options.beat:
-        o = tempo(options.onset_method, bufsize, hopsize, samplerate=samplerate)
+        o = tempo(options.onset_method, bufsize, hopsize,
+                samplerate=samplerate)
     else:
-        o = onset(options.onset_method, bufsize, hopsize, samplerate=samplerate)
+        o = onset(options.onset_method, bufsize, hopsize,
+                samplerate=samplerate)
         if options.minioi:
             if options.minioi.endswith('ms'):
                 o.set_minioi_ms(int(options.minioi[:-2]))
@@ -122,36 +124,15 @@ def _cut_analyze(options):
     while True:
         samples, read = s()
         if o(samples):
-            timestamps.append (o.get_last())
-            if options.verbose: print ("%.4f" % o.get_last_s())
+            timestamps.append(o.get_last())
+            if options.verbose:
+                print("%.4f" % o.get_last_s())
         total_frames += read
-        if read < hopsize: break
+        if read < hopsize:
+            break
     del s
     return timestamps, total_frames
 
-def _cut_slice(options, timestamps):
-    # cutting pass
-    nstamps = len(timestamps)
-    if nstamps > 0:
-        # generate output files
-        from aubio.slicing import slice_source_at_stamps
-        timestamps_end = None
-        if options.cut_every_nslices:
-            timestamps = timestamps[::options.cut_every_nslices]
-            nstamps = len(timestamps)
-        if options.cut_until_nslices and options.cut_until_nsamples:
-            print ("warning: using cut_until_nslices, but cut_until_nsamples is set")
-        if options.cut_until_nsamples:
-            timestamps_end = [t + options.cut_until_nsamples for t in timestamps[1:]]
-            timestamps_end += [ 1e120 ]
-        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(options.source_uri,
-                timestamps, timestamps_end = timestamps_end,
-                output_dir = options.output_directory,
-                samplerate = options.samplerate)
-
 def main():
     parser = aubio_cut_parser()
     options = parser.parse_args()
@@ -166,7 +147,7 @@ def main():
     timestamps, total_frames = _cut_analyze(options)
 
     # print some info
-    duration = float (total_frames) / float(options.samplerate)
+    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}
index c79523f..99b3c0b 100644 (file)
@@ -1,9 +1,11 @@
 # -*- coding: utf-8 -*-
 """ utilities to convert midi note number to and from note names """
 
-__all__ = ['note2midi', 'midi2note', 'freq2note']
-
 import sys
+from ._aubio import freqtomidi, miditofreq
+
+__all__ = ['note2midi', 'midi2note', 'freq2note', 'note2freq']
+
 py3 = sys.version_info[0] == 3
 if py3:
     str_instances = str
@@ -12,23 +14,65 @@ else:
     str_instances = (str, unicode)
     int_instances = (int, long)
 
+
 def note2midi(note):
-    " convert note name to midi note number, e.g. [C-1, G9] -> [0, 127] "
-    _valid_notenames = {'C': 0, 'D': 2, 'E': 4, 'F': 5, 'G': 7, 'A': 9, 'B': 11}
+    """Convert note name to midi note number.
+
+    Input string `note` should be composed of one note root
+    and one octave, with optionally one modifier in between.
+
+    List of valid components:
+
+    - note roots: `C`, `D`, `E`, `F`, `G`, `A`, `B`,
+    - modifiers: `b`, `#`, as well as unicode characters
+      `𝄫`, `♭`, `♮`, `♯` and `𝄪`,
+    - octave numbers: `-1` -> `11`.
+
+    Parameters
+    ----------
+    note : str
+        note name
+
+    Returns
+    -------
+    int
+        corresponding midi note number
+
+    Examples
+    --------
+    >>> aubio.note2midi('C#4')
+    61
+    >>> aubio.note2midi('B♭5')
+    82
+
+    Raises
+    ------
+    TypeError
+        If `note` was not a string.
+    ValueError
+        If an error was found while converting `note`.
+
+    See Also
+    --------
+    midi2note, freqtomidi, miditofreq
+    """
+    _valid_notenames = {'C': 0, 'D': 2, 'E': 4, 'F': 5, 'G': 7,
+                        'A': 9, 'B': 11}
     _valid_modifiers = {
-            u'𝄫': -2,                        # double flat
-            u'♭': -1, 'b': -1, '\u266d': -1, # simple flat
-            u'♮': 0, '\u266e': 0, None: 0,   # natural
-            '#': +1, u'♯': +1, '\u266f': +1, # sharp
-            u'𝄪': +2,                        # double sharp
+            u'𝄫': -2,                         # double flat
+            u'♭': -1, 'b': -1, '\u266d': -1,  # simple flat
+            u'♮': 0, '\u266e': 0, None: 0,    # natural
+            '#': +1, u'♯': +1, '\u266f': +1,  # sharp
+            u'𝄪': +2,                         # double sharp
             }
     _valid_octaves = range(-1, 10)
     if not isinstance(note, str_instances):
-        raise TypeError("a string is required, got %s (%s)" % (note, str(type(note))))
+        msg = "a string is required, got {:s} ({:s})"
+        raise TypeError(msg.format(str(type(note)), repr(note)))
     if len(note) not in range(2, 5):
-        raise ValueError("string of 2 to 4 characters expected, got %d (%s)" \
-                         % (len(note), note))
-    notename, modifier, octave = [None]*3
+        msg = "string of 2 to 4 characters expected, got {:d} ({:s})"
+        raise ValueError(msg.format(len(note), note))
+    notename, modifier, octave = [None] * 3
 
     if len(note) == 4:
         notename, modifier, octave_sign, octave = note
@@ -51,21 +95,97 @@ def note2midi(note):
     if octave not in _valid_octaves:
         raise ValueError("%s is not a valid octave" % octave)
 
-    midi = 12 + octave * 12 + _valid_notenames[notename] + _valid_modifiers[modifier]
+    midi = (octave + 1) * 12 + _valid_notenames[notename] \
+                             + _valid_modifiers[modifier]
     if midi > 127:
         raise ValueError("%s is outside of the range C-2 to G8" % note)
     return midi
 
+
 def midi2note(midi):
-    " convert midi note number to note name, e.g. [0, 127] -> [C-1, G9] "
+    """Convert midi note number to note name.
+
+    Parameters
+    ----------
+    midi : int [0, 128]
+        input midi note number
+
+    Returns
+    -------
+    str
+        note name
+
+    Examples
+    --------
+    >>> aubio.midi2note(70)
+    'A#4'
+    >>> aubio.midi2note(59)
+    'B3'
+
+    Raises
+    ------
+    TypeError
+        If `midi` was not an integer.
+    ValueError
+        If `midi` is out of the range `[0, 128]`.
+
+    See Also
+    --------
+    note2midi, miditofreq, freqtomidi
+    """
     if not isinstance(midi, int_instances):
         raise TypeError("an integer is required, got %s" % midi)
     if midi not in range(0, 128):
-        raise ValueError("an integer between 0 and 127 is excepted, got %d" % midi)
-    _valid_notenames = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B']
+        msg = "an integer between 0 and 127 is excepted, got {:d}"
+        raise ValueError(msg.format(midi))
+    _valid_notenames = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#',
+                        'A', 'A#', 'B']
     return _valid_notenames[midi % 12] + str(int(midi / 12) - 1)
 
+
 def freq2note(freq):
-    " convert frequency in Hz to nearest note name, e.g. [0, 22050.] -> [C-1, G9] "
-    from aubio import freqtomidi
-    return midi2note(int(freqtomidi(freq)))
+    """Convert frequency in Hz to nearest note name.
+
+    Parameters
+    ----------
+    freq : float [0, 23000[
+        input frequency, in Hz
+
+    Returns
+    -------
+    str
+        name of the nearest note
+
+    Example
+    -------
+    >>> aubio.freq2note(440)
+    'A4'
+    >>> aubio.freq2note(220.1)
+    'A3'
+    """
+    nearest_note = int(freqtomidi(freq) + .5)
+    return midi2note(nearest_note)
+
+
+def note2freq(note):
+    """Convert note name to corresponding frequency, in Hz.
+
+    Parameters
+    ----------
+    note : str
+        input note name
+
+    Returns
+    -------
+    freq : float [0, 23000[
+        frequency, in Hz
+
+    Example
+    -------
+    >>> aubio.note2freq('A4')
+    440
+    >>> aubio.note2freq('A3')
+    220.1
+    """
+    midi = note2midi(note)
+    return miditofreq(midi)
index fa9d2e3..4d9964c 100644 (file)
@@ -5,26 +5,91 @@ from aubio import source, sink
 
 _max_timestamp = 1e120
 
+
 def slice_source_at_stamps(source_file, timestamps, timestamps_end=None,
-                           output_dir=None, samplerate=0, hopsize=256):
-    """ slice a sound file at given timestamps """
+                           output_dir=None, samplerate=0, hopsize=256,
+                           create_first=False):
+    """Slice a sound file at given timestamps.
+
+    This function reads `source_file` and creates slices, new smaller
+    files each starting at `t` in `timestamps`, a list of integer
+    corresponding to time locations in `source_file`, in samples.
+
+    If `timestamps_end` is unspecified, the slices will end at
+    `timestamps_end[n] = timestamps[n+1]-1`, or the end of file.
+    Otherwise, `timestamps_end` should be a list with the same length
+    as `timestamps` containing the locations of the end of each slice.
+
+    If `output_dir` is unspecified, the new slices will be written in
+    the current directory. If `output_dir` is a string, new slices
+    will be written in `output_dir`, after creating the directory if
+    required.
+
+    The default `samplerate` is 0, meaning the original sampling rate
+    of `source_file` will be used. When using a sampling rate
+    different to the one of the original files, `timestamps` and
+    `timestamps_end` should be expressed in the re-sampled signal.
+
+    The `hopsize` parameter simply tells :class:`source` to use this
+    hopsize and does not change the output slices.
+
+    If `create_first` is True and `timestamps` does not start with `0`, the
+    first slice from `0` to `timestamps[0] - 1` will be automatically added.
+
+    Parameters
+    ----------
+    source_file : str
+        path of the resource to slice
+    timestamps : :obj:`list` of :obj:`int`
+        time stamps at which to slice, in samples
+    timestamps_end : :obj:`list` of :obj:`int` (optional)
+        time stamps at which to end the slices
+    output_dir : str (optional)
+        output directory to write the slices to
+    samplerate : int (optional)
+        samplerate to read the file at
+    hopsize : int (optional)
+        number of samples read from source per iteration
+    create_first : bool (optional)
+        always create the slice at the start of the file
+
+    Examples
+    --------
+    Create two slices: the first slice starts at the beginning of the
+    input file `loop.wav` and lasts exactly one second, starting at
+    sample `0` and ending at sample `44099`; the second slice starts
+    at sample `44100` and lasts until the end of the input file:
+
+    >>> aubio.slice_source_at_stamps('loop.wav', [0, 44100])
+
+    Create one slice, from 1 second to 2 seconds:
+
+    >>> aubio.slice_source_at_stamps('loop.wav', [44100], [44100 * 2 - 1])
+
+    Notes
+    -----
+    Slices may be overlapping. If `timestamps_end` is `1` element
+    shorter than `timestamps`, the last slice will end at the end of
+    the file.
+    """
 
-    if timestamps is None or len(timestamps) == 0:
+    if not timestamps:
         raise ValueError("no timestamps given")
 
-    if timestamps[0] != 0:
+    if timestamps[0] != 0 and create_first:
         timestamps = [0] + timestamps
         if timestamps_end is not None:
             timestamps_end = [timestamps[1] - 1] + timestamps_end
 
     if timestamps_end is not None:
-        if len(timestamps_end) != len(timestamps):
+        if len(timestamps_end) == len(timestamps) - 1:
+            timestamps_end = timestamps_end + [_max_timestamp]
+        elif len(timestamps_end) != len(timestamps):
             raise ValueError("len(timestamps_end) != len(timestamps)")
     else:
         timestamps_end = [t - 1 for t in timestamps[1:]] + [_max_timestamp]
 
     regions = list(zip(timestamps, timestamps_end))
-    #print regions
 
     source_base_name, _ = os.path.splitext(os.path.basename(source_file))
     if output_dir is not None:
@@ -32,8 +97,8 @@ def slice_source_at_stamps(source_file, timestamps, timestamps_end=None,
             os.makedirs(output_dir)
         source_base_name = os.path.join(output_dir, source_base_name)
 
-    def new_sink_name(source_base_name, timestamp, samplerate):
-        """ create a sink based on a timestamp in samples, converted in seconds """
+    def _new_sink_name(source_base_name, timestamp, samplerate):
+        # create name based on a timestamp in samples, converted in seconds
         timestamp_seconds = timestamp / float(samplerate)
         return source_base_name + "_%011.6f" % timestamp_seconds + '.wav'
 
@@ -48,16 +113,17 @@ def slice_source_at_stamps(source_file, timestamps, timestamps_end=None,
         # get hopsize new samples from source
         vec, read = _source.do_multi()
         # if the total number of frames read will exceed the next region start
-        if len(regions) and total_frames + read >= regions[0][0]:
-            #print "getting", regions[0], "at", total_frames
+        while regions and total_frames + read >= regions[0][0]:
             # get next region
             start_stamp, end_stamp = regions.pop(0)
             # create a name for the sink
-            new_sink_path = new_sink_name(source_base_name, start_stamp, samplerate)
+            new_sink_path = _new_sink_name(source_base_name, start_stamp,
+                                           samplerate)
             # create its sink
             _sink = sink(new_sink_path, samplerate, _source.channels)
             # create a dictionary containing all this
-            new_slice = {'start_stamp': start_stamp, 'end_stamp': end_stamp, 'sink': _sink}
+            new_slice = {'start_stamp': start_stamp, 'end_stamp': end_stamp,
+                         'sink': _sink}
             # append the dictionary to the current list of slices
             slices.append(new_slice)
 
@@ -69,18 +135,19 @@ def slice_source_at_stamps(source_file, timestamps, timestamps_end=None,
             start = max(start_stamp - total_frames, 0)
             # number of samples yet to written be until end of region
             remaining = end_stamp - total_frames + 1
-            #print current_slice, remaining, start
             # not enough frames remaining, time to split
             if remaining < read:
                 if remaining > start:
                     # write remaining samples from current region
                     _sink.do_multi(vec[:, start:remaining], remaining - start)
-                    #print "closing region", "remaining", remaining
                     # close this file
                     _sink.close()
             elif read > start:
                 # write all the samples
                 _sink.do_multi(vec[:, start:read], read - start)
         total_frames += read
+        # remove old slices
+        slices = list(filter(lambda s: s['end_stamp'] > total_frames,
+                             slices))
         if read < hopsize:
             break
index d391197..b48f9a0 100644 (file)
@@ -2,6 +2,7 @@ aubiodefvalue = {
     # we have some clean up to do
     'buf_size': 'Py_default_vector_length',
     'win_s': 'Py_default_vector_length',
+    'size': 'Py_default_vector_length',
     # and here too
     'hop_size': 'Py_default_vector_length / 2',
     'hop_s': 'Py_default_vector_length / 2',
@@ -82,6 +83,7 @@ objoutsize = {
         'tempo': '1',
         'filterbank': 'self->n_filters',
         'tss': 'self->buf_size',
+        'dct': 'self->size',
         }
 
 objinputsize = {
@@ -176,6 +178,10 @@ class MappedObject(object):
         self.do_inputs = [get_params_types_names(self.do_proto)[1]]
         self.do_outputs = get_params_types_names(self.do_proto)[2:]
         struct_output_str = ["PyObject *{0[name]}; {1} c_{0[name]}".format(i, i['type'][:-1]) for i in self.do_outputs]
+        if len(self.prototypes['rdo']):
+            rdo_outputs = get_params_types_names(prototypes['rdo'][0])[2:]
+            struct_output_str += ["PyObject *{0[name]}; {1} c_{0[name]}".format(i, i['type'][:-1]) for i in rdo_outputs]
+            self.outputs += rdo_outputs
         self.struct_outputs = ";\n    ".join(struct_output_str)
 
         #print ("input_params: ", map(split_type, get_input_params(self.do_proto)))
@@ -190,6 +196,11 @@ class MappedObject(object):
             out += self.gen_init()
             out += self.gen_del()
             out += self.gen_do()
+            if len(self.prototypes['rdo']):
+                self.do_proto = self.prototypes['rdo'][0]
+                self.do_inputs = [get_params_types_names(self.do_proto)[1]]
+                self.do_outputs = get_params_types_names(self.do_proto)[2:]
+                out += self.gen_do(method='rdo')
             out += self.gen_memberdef()
             out += self.gen_set()
             out += self.gen_get()
@@ -220,11 +231,21 @@ typedef struct{{
         return out.format(do_inputs_list = do_inputs_list, **self.__dict__)
 
     def gen_doc(self):
+        sig = []
+        for p in self.input_params:
+            name = p['name']
+            defval = aubiodefvalue[name].replace('"','\\\"')
+            sig.append("{name}={defval}".format(defval=defval, name=name))
         out = """
-// TODO: add documentation
-static char Py_{shortname}_doc[] = \"undefined\";
+#ifndef PYAUBIO_{shortname}_doc
+#define PYAUBIO_{shortname}_doc "{shortname}({sig})"
+#endif /* PYAUBIO_{shortname}_doc */
+
+static char Py_{shortname}_doc[] = ""
+PYAUBIO_{shortname}_doc
+"";
 """
-        return out.format(**self.__dict__)
+        return out.format(sig=', '.join(sig), **self.__dict__)
 
     def gen_new(self):
         out = """
@@ -370,12 +391,12 @@ Py_{shortname}_del  (Py_{shortname} * self, PyObject * unused)
 """.format(del_fn = del_fn)
         return out
 
-    def gen_do(self):
+    def gen_do(self, method = 'do'):
         out = """
 // do {shortname}
 static PyObject*
-Py_{shortname}_do  (Py_{shortname} * self, PyObject * args)
-{{""".format(**self.__dict__)
+Pyaubio_{shortname}_{method}  (Py_{shortname} * self, PyObject * args)
+{{""".format(method = method, **self.__dict__)
         input_params = self.do_inputs
         output_params = self.do_outputs
         #print input_params
@@ -451,30 +472,51 @@ Py_{shortname}_do  (Py_{shortname} * self, PyObject * args)
 // {shortname} setters
 """.format(**self.__dict__)
         for set_param in self.prototypes['set']:
-            params = get_params_types_names(set_param)[1]
-            paramtype = params['type']
+            params = get_params_types_names(set_param)[1:]
+            param = self.shortname.split('_set_')[-1]
+            paramdecls = "".join(["""
+   {0} {1};""".format(p['type'], p['name']) for p in params])
             method_name = get_name(set_param)
             param = method_name.split('aubio_'+self.shortname+'_set_')[-1]
-            pyparamtype = pyargparse_chars[paramtype]
+            refs = ", ".join(["&%s" % p['name'] for p in params])
+            paramlist = ", ".join(["%s" % p['name'] for p in params])
+            if len(params):
+                paramlist = "," + paramlist
+            pyparamtypes = ''.join([pyargparse_chars[p['type']] for p in params])
             out += """
 static PyObject *
 Pyaubio_{shortname}_set_{param} (Py_{shortname} *self, PyObject *args)
 {{
   uint_t err = 0;
-  {paramtype} {param};
+  {paramdecls}
+""".format(param = param, paramdecls = paramdecls, **self.__dict__)
+
+            if len(refs) and len(pyparamtypes):
+                out += """
 
-  if (!PyArg_ParseTuple (args, "{pyparamtype}", &{param})) {{
+  if (!PyArg_ParseTuple (args, "{pyparamtypes}", {refs})) {{
     return NULL;
   }}
-  err = aubio_{shortname}_set_{param} (self->o, {param});
+""".format(pyparamtypes = pyparamtypes, refs = refs)
+
+            out += """
+  err = aubio_{shortname}_set_{param} (self->o {paramlist});
 
   if (err > 0) {{
-    PyErr_SetString (PyExc_ValueError, "error running aubio_{shortname}_set_{param}");
+    if (PyErr_Occurred() == NULL) {{
+      PyErr_SetString (PyExc_ValueError, "error running aubio_{shortname}_set_{param}");
+    }} else {{
+      // change the RuntimeError into ValueError
+      PyObject *type, *value, *traceback;
+      PyErr_Fetch(&type, &value, &traceback);
+      PyErr_Restore(PyExc_ValueError, value, traceback);
+    }}
     return NULL;
   }}
   Py_RETURN_NONE;
 }}
-""".format(param = param, paramtype = paramtype, pyparamtype = pyparamtype, **self.__dict__)
+""".format(param = param, refs = refs, paramdecls = paramdecls,
+        pyparamtypes = pyparamtypes, paramlist = paramlist, **self.__dict__)
         return out
 
     def gen_get(self):
@@ -515,6 +557,12 @@ static PyMethodDef Py_{shortname}_methods[] = {{""".format(**self.__dict__)
             out += """
   {{"{shortname}", (PyCFunction) Py{name},
     METH_NOARGS, ""}},""".format(name = name, shortname = shortname)
+        for m in self.prototypes['rdo']:
+            name = get_name(m)
+            shortname = name.replace('aubio_%s_' % self.shortname, '')
+            out += """
+  {{"{shortname}", (PyCFunction) Py{name},
+    METH_VARARGS, ""}},""".format(name = name, shortname = shortname)
         out += """
   {NULL} /* sentinel */
 };
@@ -540,7 +588,7 @@ PyTypeObject Py_{shortname}Type = {{
   0,
   0,
   0,
-  (ternaryfunc)Py_{shortname}_do,
+  (ternaryfunc)Pyaubio_{shortname}_do,
   0,
   0,
   0,
index 8095cb6..e5f0a02 100644 (file)
@@ -3,6 +3,8 @@ import sys
 import os
 import subprocess
 import glob
+from distutils.sysconfig import customize_compiler
+from gen_code import MappedObject
 
 header = os.path.join('src', 'aubio.h')
 output_path = os.path.join('python', 'gen')
@@ -49,7 +51,6 @@ default_skip_objects = [
 
 def get_preprocessor():
     # findout which compiler to use
-    from distutils.sysconfig import customize_compiler
     compiler_name = distutils.ccompiler.get_default_compiler()
     compiler = distutils.ccompiler.new_compiler(compiler=compiler_name)
     try:
@@ -73,17 +74,36 @@ def get_preprocessor():
         cpp_cmd = compiler.cc.split()
         cpp_cmd += ['-E']
 
+    # On win-amd64 (py3.x), the default compiler is cross-compiling, from x86
+    # to amd64 with %WIN_SDK_ROOT%\x86_amd64\cl.exe, but using this binary as a
+    # pre-processor generates no output, so we use %WIN_SDK_ROOT%\cl.exe
+    # instead.
+    if len(cpp_cmd) > 1 and 'cl.exe' in cpp_cmd[-2]:
+        plat = os.path.basename(os.path.dirname(cpp_cmd[-2]))
+        if plat == 'x86_amd64':
+            print('workaround on win64 to avoid empty pre-processor output')
+            cpp_cmd[-2] = cpp_cmd[-2].replace('x86_amd64', '')
+        elif True in ['amd64' in f for f in cpp_cmd]:
+            print('warning: not using workaround for', cpp_cmd[0], plat)
+
     if not cpp_cmd:
         print("Warning: could not guess preprocessor, using env's CC")
         cpp_cmd = os.environ.get('CC', 'cc').split()
         cpp_cmd += ['-E']
-    cpp_cmd += ['-x', 'c']  # force C language (emcc defaults to c++)
+    if 'emcc' in cpp_cmd:
+        cpp_cmd += ['-x', 'c'] # emcc defaults to c++, force C language
     return cpp_cmd
 
 
 def get_c_declarations(header=header, usedouble=False):
     ''' return a dense and preprocessed  string of all c declarations implied by aubio.h
     '''
+    cpp_output = get_cpp_output(header=header, usedouble=usedouble)
+    return filter_cpp_output (cpp_output)
+
+
+def get_cpp_output(header=header, usedouble=False):
+    ''' find and run a C pre-processor on aubio.h '''
     cpp_cmd = get_preprocessor()
 
     macros = [('AUBIO_UNSTABLE', 1)]
@@ -100,18 +120,30 @@ def get_c_declarations(header=header, usedouble=False):
     print("Running command: {:s}".format(" ".join(cpp_cmd)))
     proc = subprocess.Popen(cpp_cmd,
                             stderr=subprocess.PIPE,
-                            stdout=subprocess.PIPE)
+                            stdout=subprocess.PIPE,
+                            universal_newlines=True)
     assert proc, 'Proc was none'
     cpp_output = proc.stdout.read()
     err_output = proc.stderr.read()
+    if err_output:
+        print("Warning: preprocessor produced errors or warnings:\n%s" \
+                % err_output)
     if not cpp_output:
-        raise Exception("preprocessor output is empty:\n%s" % err_output)
-    elif err_output:
-        print("Warning: preprocessor produced warnings:\n%s" % err_output)
+        raise_msg = "preprocessor output is empty! Running command " \
+                + "\"%s\" failed" % " ".join(cpp_cmd)
+        if err_output:
+            raise_msg += " with stderr: \"%s\"" % err_output
+        else:
+            raise_msg += " with no stdout or stderr"
+        raise Exception(raise_msg)
     if not isinstance(cpp_output, list):
-        cpp_output = [l.strip() for l in cpp_output.decode('utf8').split('\n')]
+        cpp_output = [l.strip() for l in cpp_output.split('\n')]
 
-    cpp_output = filter(lambda y: len(y) > 1, cpp_output)
+    return cpp_output
+
+def filter_cpp_output(cpp_raw_output):
+    ''' prepare cpp-output for parsing '''
+    cpp_output = filter(lambda y: len(y) > 1, cpp_raw_output)
     cpp_output = list(filter(lambda y: not y.startswith('#'), cpp_output))
 
     i = 1
@@ -181,7 +213,7 @@ def generate_lib_from_c_declarations(cpp_objects, c_declarations):
         if o[:6] == 'aubio_':
             shortname = o[6:-2]  # without aubio_ prefix and _t suffix
 
-        lib[shortname] = {'struct': [], 'new': [], 'del': [], 'do': [], 'get': [], 'set': [], 'other': []}
+        lib[shortname] = {'struct': [], 'new': [], 'del': [], 'do': [], 'rdo': [], 'get': [], 'set': [], 'other': []}
         lib[shortname]['longname'] = o
         lib[shortname]['shortname'] = shortname
 
@@ -195,6 +227,8 @@ def generate_lib_from_c_declarations(cpp_objects, c_declarations):
                     lib[shortname]['struct'].append(fn)
                 elif '_do' in fn:
                     lib[shortname]['do'].append(fn)
+                elif '_rdo' in fn:
+                    lib[shortname]['rdo'].append(fn)
                 elif 'new_' in fn:
                     lib[shortname]['new'].append(fn)
                 elif 'del_' in fn:
@@ -242,10 +276,6 @@ def generate_external(header=header, output_path=output_path, usedouble=False, o
     # print_c_declarations_results(lib, c_declarations)
 
     sources_list = []
-    try:
-        from .gen_code import MappedObject
-    except (SystemError, ValueError):
-        from gen_code import MappedObject
     for o in lib:
         out = source_header
         mapped = MappedObject(lib[o], usedouble=usedouble)
index 2f621a7..299d1f9 100644 (file)
@@ -2,7 +2,7 @@
 #
 import sys, os, glob, subprocess
 import distutils, distutils.command.clean, distutils.dir_util
-from .gen_external import generate_external, header, output_path
+from gen_external import generate_external, header, output_path
 
 from this_version import get_aubio_version
 
@@ -68,7 +68,7 @@ def add_local_macros(ext, usedouble = False):
     # define macros (waf puts them in build/src/config.h)
     for define_macro in ['HAVE_STDLIB_H', 'HAVE_STDIO_H',
                          'HAVE_MATH_H', 'HAVE_STRING_H',
-                         'HAVE_C99_VARARGS_MACROS',
+                         'HAVE_ERRNO_H', 'HAVE_C99_VARARGS_MACROS',
                          'HAVE_LIMITS_H', 'HAVE_STDARG_H',
                          'HAVE_MEMCPY_HACKS']:
         ext.define_macros += [(define_macro, 1)]
diff --git a/python/tests/__init__.py b/python/tests/__init__.py
deleted file mode 100644 (file)
index 8b13789..0000000
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/python/tests/_tools.py b/python/tests/_tools.py
new file mode 100644 (file)
index 0000000..4121656
--- /dev/null
@@ -0,0 +1,41 @@
+"""
+This file imports test methods from different testing modules, in this
+order:
+
+    - try importing 'pytest'
+    - if it fails, fallback to 'numpy.testing'
+
+Nose2 support was removed because of lacking assertWarns on py2.7.
+
+"""
+
+import sys
+
+_has_pytest = False
+
+# check if we have pytest
+try:
+    import pytest
+    parametrize = pytest.mark.parametrize
+    assert_raises = pytest.raises
+    assert_warns = pytest.warns
+    skipTest = pytest.skip
+    _has_pytest = True
+    def run_module_suite():
+        import sys, pytest
+        pytest.main(sys.argv)
+except:
+    pass
+
+# otherwise fallback on numpy.testing
+if not _has_pytest:
+    from numpy.testing import dec, assert_raises, assert_warns
+    from numpy.testing import SkipTest
+    parametrize = dec.parametrize
+    def skipTest(msg):
+        raise SkipTest(msg)
+    from numpy.testing import run_module_suite
+
+# always use numpy's assert_equal
+import numpy
+assert_equal = numpy.testing.assert_equal
diff --git a/python/tests/run_all_tests b/python/tests/run_all_tests
deleted file mode 100755 (executable)
index bc6bb8c..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-#! /usr/bin/env python
-
-if __name__ == '__main__':
-    import nose2.main
-    nose2.discover()
index 98a6115..5768cac 100755 (executable)
@@ -1,6 +1,5 @@
 #! /usr/bin/env python
 
-from unittest import main
 from numpy.testing import TestCase
 
 class aubiomodule_test_case(TestCase):
@@ -15,5 +14,5 @@ class aubiomodule_test_case(TestCase):
         self.assertEqual('0', aubio.version[0])
 
 if __name__ == '__main__':
+    from unittest import main
     main()
-
index 6e834d1..471ac85 100755 (executable)
@@ -1,8 +1,7 @@
 #! /usr/bin/env python
 
-import aubio.cmd
-from nose2 import main
 from numpy.testing import TestCase
+import aubio.cmd
 
 class aubio_cmd(TestCase):
 
@@ -19,13 +18,17 @@ class aubio_cmd(TestCase):
 class aubio_cmd_utils(TestCase):
 
     def test_samples2seconds(self):
-        self.assertEqual(aubio.cmd.samples2seconds(3200, 32000), "0.100000\t")
+        self.assertEqual(aubio.cmd.samples2seconds(3200, 32000),
+                "0.100000\t")
 
     def test_samples2milliseconds(self):
-        self.assertEqual(aubio.cmd.samples2milliseconds(3200, 32000), "100.000000\t")
+        self.assertEqual(aubio.cmd.samples2milliseconds(3200, 32000),
+                "100.000000\t")
 
     def test_samples2samples(self):
-        self.assertEqual(aubio.cmd.samples2samples(3200, 32000), "3200\t")
+        self.assertEqual(aubio.cmd.samples2samples(3200, 32000),
+                "3200\t")
 
 if __name__ == '__main__':
+    from unittest import main
     main()
index cbbfe05..01ad2c6 100755 (executable)
@@ -1,7 +1,6 @@
 #! /usr/bin/env python
 
 import aubio.cut
-from nose2 import main
 from numpy.testing import TestCase
 
 class aubio_cut(TestCase):
@@ -13,4 +12,5 @@ class aubio_cut(TestCase):
         assert self.a_parser.parse_args(['-v']).verbose
 
 if __name__ == '__main__':
+    from unittest import main
     main()
index 5632784..73ee654 100755 (executable)
@@ -1,6 +1,5 @@
 #! /usr/bin/env python
 
-from unittest import main
 import numpy as np
 from numpy.testing import TestCase, assert_equal
 from aubio import cvec, fvec, float_type
@@ -141,4 +140,5 @@ class aubio_cvec_wrong_norm_input(TestCase):
             a.norm = np.zeros((512//2+1, 2), dtype = float_type)
 
 if __name__ == '__main__':
+    from unittest import main
     main()
diff --git a/python/tests/test_dct.py b/python/tests/test_dct.py
new file mode 100755 (executable)
index 0000000..c9b2ba7
--- /dev/null
@@ -0,0 +1,72 @@
+#! /usr/bin/env python
+
+
+import numpy as np
+from numpy.testing import TestCase, assert_almost_equal
+import aubio
+
+precomputed_arange = [ 9.89949512, -6.44232273,  0., -0.67345482, 0.,
+        -0.20090288, 0., -0.05070186]
+
+precomputed_some_ones = [ 4.28539848,  0.2469689,  -0.14625292, -0.58121818,
+        -0.83483052, -0.75921834, -0.35168475,  0.24087936,
+        0.78539824, 1.06532764,  0.97632152,  0.57164496, 0.03688532,
+        -0.39446154, -0.54619485, -0.37771079]
+
+class aubio_dct(TestCase):
+
+    def test_init(self):
+        """ test that aubio.dct() is created with expected size """
+        a_dct = aubio.dct()
+        self.assertEqual(a_dct.size, 1024)
+
+    def test_arange(self):
+        """ test that dct(arange(8)) is computed correctly
+
+        >>> from scipy.fftpack import dct
+        >>> a_in = np.arange(8).astype(aubio.float_type)
+        >>> precomputed = dct(a_in, norm='ortho')
+        """
+        N = len(precomputed_arange)
+        a_dct = aubio.dct(8)
+        a_in = np.arange(8).astype(aubio.float_type)
+        a_expected = aubio.fvec(precomputed_arange)
+        assert_almost_equal(a_dct(a_in), a_expected, decimal=5)
+
+    def test_some_ones(self):
+        """ test that dct(somevector) is computed correctly """
+        a_dct = aubio.dct(16)
+        a_in = np.ones(16).astype(aubio.float_type)
+        a_in[1] = 0
+        a_in[3] = np.pi
+        a_expected = aubio.fvec(precomputed_some_ones)
+        assert_almost_equal(a_dct(a_in), a_expected, decimal=6)
+
+    def test_reconstruction(self):
+        """ test that some_ones vector can be recontructed """
+        a_dct = aubio.dct(16)
+        a_in = np.ones(16).astype(aubio.float_type)
+        a_in[1] = 0
+        a_in[3] = np.pi
+        a_dct_in = a_dct(a_in)
+        a_dct_reconstructed = a_dct.rdo(a_dct_in)
+        assert_almost_equal(a_dct_reconstructed, a_in, decimal=6)
+
+    def test_negative_size(self):
+        """ test that creation fails with a negative size """
+        with self.assertRaises(ValueError):
+            aubio.dct(-1)
+
+    def test_wrong_size(self):
+        """ test that creation fails with a non power-of-two size """
+        # supports for non 2** fft sizes only when compiled with fftw3
+        size = 13
+        try:
+            with self.assertRaises(RuntimeError):
+                aubio.dct(size)
+        except AssertionError:
+            self.skipTest('creating aubio.dct with size %d did not fail' % size)
+
+if __name__ == '__main__':
+    from unittest import main
+    main()
index a8f82b9..abe95d3 100755 (executable)
@@ -1,6 +1,5 @@
 #! /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
@@ -142,6 +141,37 @@ class aubio_fft_test_case(TestCase):
         assert_almost_equal ( r[0], impulse, decimal = 6)
         assert_almost_equal ( r[1:], 0)
 
+class aubio_fft_odd_sizes(TestCase):
+
+    def test_reconstruct_with_odd_size(self):
+        win_s = 29
+        self.recontruct(win_s, 'odd sizes not supported')
+
+    def test_reconstruct_with_radix15(self):
+        win_s = 2 ** 4 * 15
+        self.recontruct(win_s, 'radix 15 supported')
+
+    def test_reconstruct_with_radix5(self):
+        win_s = 2 ** 4 * 5
+        self.recontruct(win_s, 'radix 5 supported')
+
+    def test_reconstruct_with_radix3(self):
+        win_s = 2 ** 4 * 3
+        self.recontruct(win_s, 'radix 3 supported')
+
+    def recontruct(self, win_s, skipMessage):
+        try:
+            f = fft(win_s)
+        except RuntimeError:
+            self.skipTest(skipMessage)
+        input_signal = fvec(win_s)
+        input_signal[win_s//2] = 1
+        c = f(input_signal)
+        output_signal = f.rdo(c)
+        assert_almost_equal(input_signal, output_signal)
+
+class aubio_fft_wrong_params(TestCase):
+
     def test_large_input_timegrain(self):
         win_s = 1024
         f = fft(win_s)
@@ -170,26 +200,16 @@ class aubio_fft_test_case(TestCase):
         with self.assertRaises(ValueError):
             f.rdo(s)
 
-class aubio_fft_wrong_params(TestCase):
-
     def test_wrong_buf_size(self):
         win_s = -1
         with self.assertRaises(ValueError):
             fft(win_s)
 
-    def test_buf_size_not_power_of_two(self):
-        # when compiled with fftw3, aubio supports non power of two fft sizes
-        win_s = 320
-        try:
-            with self.assertRaises(RuntimeError):
-                fft(win_s)
-        except AssertionError:
-            self.skipTest('creating aubio.fft with size %d did not fail' % win_s)
-
     def test_buf_size_too_small(self):
         win_s = 1
         with self.assertRaises(RuntimeError):
             fft(win_s)
 
 if __name__ == '__main__':
+    from unittest import main
     main()
index 6a7a0de..576ae11 100755 (executable)
@@ -1,9 +1,8 @@
 #! /usr/bin/env python
 
-from unittest import main
 from numpy.testing import TestCase, assert_equal, assert_almost_equal
 from aubio import fvec, digital_filter
-from .utils import array_from_text_file
+from utils import array_from_text_file
 
 class aubio_filter_test_case(TestCase):
 
@@ -77,6 +76,16 @@ class aubio_filter_test_case(TestCase):
         with self.assertRaises(ValueError):
             f.set_biquad(0., 0., 0, 0., 0.)
 
+    def test_all_available_presets(self):
+        f = digital_filter(7)
+        for sr in [8000, 11025, 16000, 22050, 24000, 32000,
+                44100, 48000, 88200, 96000, 192000]:
+            f.set_a_weighting(sr)
+        f = digital_filter(5)
+        for sr in [8000, 11025, 16000, 22050, 24000, 32000,
+                44100, 48000, 88200, 96000, 192000]:
+            f.set_c_weighting(sr)
+
 class aubio_filter_wrong_params(TestCase):
 
     def test_negative_order(self):
@@ -84,4 +93,5 @@ class aubio_filter_wrong_params(TestCase):
             digital_filter(-1)
 
 if __name__ == '__main__':
+    from unittest import main
     main()
index 8808ba8..67a8558 100755 (executable)
@@ -4,7 +4,7 @@ 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
+from utils import array_from_text_file
 
 class aubio_filterbank_test_case(TestCase):
 
@@ -87,5 +87,5 @@ class aubio_filterbank_wrong_values(TestCase):
             f(cvec(256))
 
 if __name__ == '__main__':
-    import nose2
-    nose2.main()
+    from unittest import main
+    main()
index a45dbf0..0771264 100755 (executable)
@@ -2,11 +2,9 @@
 
 import numpy as np
 from numpy.testing import TestCase, assert_equal, assert_almost_equal
+from _tools import assert_warns
 
-from aubio import cvec, filterbank, float_type
-
-import warnings
-warnings.filterwarnings('ignore', category=UserWarning, append=True)
+from aubio import fvec, cvec, filterbank, float_type
 
 class aubio_filterbank_mel_test_case(TestCase):
 
@@ -47,6 +45,127 @@ class aubio_filterbank_mel_test_case(TestCase):
                 [ 0.02070313, 0.02138672, 0.02127604, 0.02135417,
                     0.02133301, 0.02133301, 0.02133311, 0.02133334, 0.02133345])
 
+    def test_triangle_freqs_with_zeros(self):
+        """make sure set_triangle_bands works when list starts with 0"""
+        freq_list = [0, 40, 80]
+        freqs = np.array(freq_list, dtype = float_type)
+        f = filterbank(len(freqs)-2, 1024)
+        f.set_triangle_bands(freqs, 48000)
+        assert_equal ( f(cvec(1024)), 0)
+        self.assertIsInstance(f.get_coeffs(), np.ndarray)
+
+    def test_triangle_freqs_with_wrong_negative(self):
+        """make sure set_triangle_bands fails when list contains a negative"""
+        freq_list = [-10, 0, 80]
+        f = filterbank(len(freq_list)-2, 1024)
+        with self.assertRaises(ValueError):
+            f.set_triangle_bands(fvec(freq_list), 48000)
+
+    def test_triangle_freqs_with_wrong_ordering(self):
+        """make sure set_triangle_bands fails when list not ordered"""
+        freq_list = [0, 80, 40]
+        f = filterbank(len(freq_list)-2, 1024)
+        with self.assertRaises(ValueError):
+            f.set_triangle_bands(fvec(freq_list), 48000)
+
+    def test_triangle_freqs_with_large_freq(self):
+        """make sure set_triangle_bands warns when freq > nyquist"""
+        samplerate = 22050
+        freq_list = [0, samplerate//4, samplerate // 2 + 1]
+        f = filterbank(len(freq_list)-2, 1024)
+        with assert_warns(UserWarning):
+            f.set_triangle_bands(fvec(freq_list), samplerate)
+
+    def test_triangle_freqs_with_not_enough_filters(self):
+        """make sure set_triangle_bands warns when not enough filters"""
+        samplerate = 22050
+        freq_list = [0, 100, 1000, 4000, 8000, 10000]
+        f = filterbank(len(freq_list)-3, 1024)
+        with assert_warns(UserWarning):
+            f.set_triangle_bands(fvec(freq_list), samplerate)
+
+    def test_triangle_freqs_with_too_many_filters(self):
+        """make sure set_triangle_bands warns when too many filters"""
+        samplerate = 22050
+        freq_list = [0, 100, 1000, 4000, 8000, 10000]
+        f = filterbank(len(freq_list)-1, 1024)
+        with assert_warns(UserWarning):
+            f.set_triangle_bands(fvec(freq_list), samplerate)
+
+    def test_triangle_freqs_with_double_value(self):
+        """make sure set_triangle_bands works with 2 duplicate freqs"""
+        samplerate = 22050
+        freq_list = [0, 100, 1000, 4000, 4000, 4000, 10000]
+        f = filterbank(len(freq_list)-2, 1024)
+        with assert_warns(UserWarning):
+            f.set_triangle_bands(fvec(freq_list), samplerate)
+
+    def test_triangle_freqs_with_triple(self):
+        """make sure set_triangle_bands works with 3 duplicate freqs"""
+        samplerate = 22050
+        freq_list = [0, 100, 1000, 4000, 4000, 4000, 10000]
+        f = filterbank(len(freq_list)-2, 1024)
+        with assert_warns(UserWarning):
+            f.set_triangle_bands(fvec(freq_list), samplerate)
+
+
+    def test_triangle_freqs_without_norm(self):
+        """make sure set_triangle_bands works without """
+        samplerate = 22050
+        freq_list = fvec([0, 100, 1000, 10000])
+        f = filterbank(len(freq_list) - 2, 1024)
+        f.set_norm(0)
+        f.set_triangle_bands(freq_list, samplerate)
+        expected = f.get_coeffs()
+        f.set_norm(1)
+        f.set_triangle_bands(fvec(freq_list), samplerate)
+        assert_almost_equal(f.get_coeffs().T,
+                expected.T * 2. / (freq_list[2:] - freq_list[:-2]))
+
+    def test_triangle_freqs_wrong_norm(self):
+        f = filterbank(10, 1024)
+        with self.assertRaises(ValueError):
+            f.set_norm(-1)
+
+    def test_triangle_freqs_with_power(self):
+        f = filterbank(9, 1024)
+        freqs = fvec([40, 80, 200, 400, 800, 1600, 3200, 6400, 12800, 15000,
+            24000])
+        f.set_power(2)
+        f.set_triangle_bands(freqs, 48000)
+        spec = cvec(1024)
+        spec.norm[:] = .1
+        expected = fvec([0.02070313, 0.02138672, 0.02127604, 0.02135417,
+            0.02133301, 0.02133301, 0.02133311, 0.02133334, 0.02133345])
+        expected /= 100.
+        assert_almost_equal(f(spec), expected)
+
+    def test_mel_coeffs(self):
+        f = filterbank(40, 1024)
+        f.set_mel_coeffs(44100, 0, 44100 / 2)
+
+    def test_zero_fmax(self):
+        f = filterbank(40, 1024)
+        f.set_mel_coeffs(44100, 0, 0)
+
+    def test_wrong_mel_coeffs(self):
+        f = filterbank(40, 1024)
+        with self.assertRaises(ValueError):
+            f.set_mel_coeffs_slaney(0)
+        with self.assertRaises(ValueError):
+            f.set_mel_coeffs(44100, 0, -44100 / 2)
+        with self.assertRaises(ValueError):
+            f.set_mel_coeffs(44100, -0.1, 44100 / 2)
+        with self.assertRaises(ValueError):
+            f.set_mel_coeffs(-44100, 0.1, 44100 / 2)
+        with self.assertRaises(ValueError):
+            f.set_mel_coeffs_htk(-1, 0, 0)
+
+    def test_mel_coeffs_htk(self):
+        f = filterbank(40, 1024)
+        f.set_mel_coeffs_htk(44100, 0, 44100 / 2)
+
+
 if __name__ == '__main__':
-    import nose2
-    nose2.main()
+    from unittest import main
+    main()
index 4e50f0f..765c9fe 100755 (executable)
@@ -1,6 +1,5 @@
 #! /usr/bin/env python
 
-from unittest import main
 import numpy as np
 from numpy.testing import TestCase, assert_equal, assert_almost_equal
 from aubio import fvec, zero_crossing_rate, alpha_norm, min_removal
@@ -60,6 +59,14 @@ class aubio_fvec_wrong_values(TestCase):
         self.assertRaises(IndexError, a.__getitem__, 3)
         self.assertRaises(IndexError, a.__getitem__, 2)
 
+    def test_wrong_dimensions(self):
+        a = np.array([[[1, 2], [3, 4]]], dtype=float_type)
+        self.assertRaises(ValueError, fvec, a)
+
+    def test_wrong_size(self):
+        a = np.ndarray([0,], dtype=float_type)
+        self.assertRaises(ValueError, fvec, a)
+
 class aubio_wrong_fvec_input(TestCase):
     """ uses min_removal to test PyAubio_IsValidVector """
 
@@ -140,4 +147,5 @@ class aubio_fvec_test_memory(TestCase):
         del c
 
 if __name__ == '__main__':
+    from unittest import main
     main()
diff --git a/python/tests/test_fvec_shift.py b/python/tests/test_fvec_shift.py
new file mode 100644 (file)
index 0000000..c7a0315
--- /dev/null
@@ -0,0 +1,35 @@
+#! /usr/bin/env python
+
+import numpy as np
+from numpy.testing import TestCase, assert_equal
+import aubio
+
+class aubio_shift_test_case(TestCase):
+
+    def run_shift_ishift(self, n):
+        ramp = np.arange(n, dtype=aubio.float_type)
+        # construct expected output
+        # even length: [5. 6. 7. 8. 9. 0. 1. 2. 3. 4.]
+        # odd length: [4. 5. 6. 0. 1. 2. 3.]
+        half = n - n//2
+        expected = np.concatenate([np.arange(half, n), np.arange(half)])
+        # shift in place, returns modified copy
+        assert_equal(aubio.shift(ramp), expected)
+        # check input was changed as expected
+        assert_equal(ramp, expected)
+        # construct expected output
+        expected = np.arange(n)
+        # revert shift in place, returns modifed copy
+        assert_equal(aubio.ishift(ramp), expected)
+        # check input was shifted back
+        assert_equal(ramp, expected)
+
+    def test_can_shift_fvec(self):
+        self.run_shift_ishift(10)
+
+    def test_can_shift_fvec_odd(self):
+        self.run_shift_ishift(7)
+
+if __name__ == '__main__':
+    from unittest import main
+    main()
diff --git a/python/tests/test_hztomel.py b/python/tests/test_hztomel.py
new file mode 100755 (executable)
index 0000000..fcd8fa1
--- /dev/null
@@ -0,0 +1,112 @@
+#! /usr/bin/env python
+
+from unittest import main
+from numpy.testing import TestCase
+from numpy.testing import assert_equal, assert_almost_equal
+from _tools import assert_warns
+import numpy as np
+import aubio
+
+from aubio import hztomel, meltohz
+from aubio import hztomel_htk, meltohz_htk
+
+
+class aubio_hztomel_test_case(TestCase):
+
+    def test_hztomel(self):
+        assert_equal(hztomel(0.), 0.)
+        assert_almost_equal(hztomel(400. / 3.), 2., decimal=5)
+        assert_almost_equal(hztomel(1000. / 3), 5.)
+        assert_equal(hztomel(200.), 3.)
+        assert_almost_equal(hztomel(1000.), 15)
+        assert_almost_equal(hztomel(6400), 42)
+        assert_almost_equal(hztomel(40960), 69)
+
+        for m in np.linspace(0, 1000, 100):
+            assert_almost_equal(hztomel(meltohz(m)) - m, 0, decimal=3)
+
+    def test_meltohz(self):
+        assert_equal(meltohz(0.), 0.)
+        assert_almost_equal(meltohz(2), 400. / 3., decimal=4)
+        assert_equal(meltohz(3.), 200.)
+        assert_almost_equal(meltohz(5), 1000. / 3., decimal=4)
+        assert_almost_equal(meltohz(15), 1000., decimal=4)
+        assert_almost_equal(meltohz(42), 6400., decimal=2)
+        assert_almost_equal(meltohz(69), 40960., decimal=1)
+
+        for f in np.linspace(0, 20000, 1000):
+            assert_almost_equal(meltohz(hztomel(f)) - f, 0, decimal=1)
+
+    def test_meltohz_negative(self):
+        with assert_warns(UserWarning):
+            assert_equal(meltohz(-1), 0)
+
+    def test_hztomel_negative(self):
+        with assert_warns(UserWarning):
+            assert_equal(hztomel(-1), 0)
+
+
+class aubio_hztomel_htk_test_case(TestCase):
+
+    def test_meltohz(self):
+        assert_equal(meltohz(0, htk=True), 0)
+        assert_almost_equal(meltohz(2595, htk=True), 6300., decimal=1)
+
+    def test_hztomel(self):
+        assert_equal(hztomel(0, htk=True), 0)
+        assert_almost_equal(hztomel(3428.7, htk=True), 2000., decimal=1)
+        assert_almost_equal(hztomel(6300, htk=True), 2595., decimal=1)
+
+    def test_meltohz_negative(self):
+        with assert_warns(UserWarning):
+            assert_equal(meltohz(-1, htk=True), 0)
+        assert_almost_equal(meltohz(2000, htk=True), 3428.7, decimal=1)
+        assert_almost_equal(meltohz(1000, htk=True), 1000., decimal=1)
+
+    def test_hztomel_negative(self):
+        with assert_warns(UserWarning):
+            assert_equal(meltohz(-1, htk=True), 0)
+        with assert_warns(UserWarning):
+            assert_equal(hztomel(-1, htk=True), 0)
+        assert_almost_equal(hztomel(1000, htk=True), 1000., decimal=1)
+
+    def test_hztomel_htk(self):
+        for f in np.linspace(0, 20000, 1000):
+            assert_almost_equal(meltohz_htk(hztomel_htk(f)) - f, 0, decimal=1)
+        for f in np.linspace(0, 20000, 1000):
+            assert_almost_equal(hztomel_htk(meltohz_htk(f)) - f, 0, decimal=1)
+
+
+class aubio_hztomel_wrong_values(TestCase):
+    """ more tests to cover all branches """
+
+    def test_hztomel_wrong_values(self):
+        with self.assertRaises(TypeError):
+            hztomel('s')
+
+    def test_meltohz_wrong_values(self):
+        with self.assertRaises(TypeError):
+            meltohz(bytes('ad'))
+
+    def test_meltohz_no_arg(self):
+        with self.assertRaises(TypeError):
+            meltohz()
+
+    def test_meltohz_htk_no_arg(self):
+        with self.assertRaises(TypeError):
+            meltohz_htk()
+
+    def test_hztomel_htk_wrong_values(self):
+        with self.assertRaises(TypeError):
+            hztomel_htk('0')
+
+    def test_hztomel_htk_false(self):
+        assert hztomel(120, htk=False) == hztomel(120)
+
+    def test_meltohz_htk_false(self):
+        assert meltohz(12, htk=False) == meltohz(12)
+
+
+if __name__ == '__main__':
+    from unittest import main
+    main()
index f68fb11..ee68966 100755 (executable)
@@ -1,6 +1,5 @@
 #! /usr/bin/env python
 
-from unittest import main
 from numpy.testing import TestCase, assert_equal
 from numpy import array, arange, isnan, isinf
 from aubio import bintomidi, miditobin, freqtobin, bintofreq, freqtomidi, miditofreq
@@ -101,4 +100,5 @@ class aubio_mathutils(TestCase):
         assert_equal ( array(b) < 0, False )
 
 if __name__ == '__main__':
+    from unittest import main
     main()
index e7f3b18..fed7eb8 100755 (executable)
@@ -1,7 +1,6 @@
 #! /usr/bin/env python
 
-from nose2 import main
-from nose2.tools import params
+from _tools import parametrize, assert_raises
 from numpy import random, count_nonzero
 from numpy.testing import TestCase
 from aubio import mfcc, cvec, float_type
@@ -15,28 +14,21 @@ samplerate = 44100
 new_params = ['buf_size', 'n_filters', 'n_coeffs', 'samplerate']
 new_deflts = [1024, 40, 13, 44100]
 
-class aubio_mfcc(TestCase):
+class Test_aubio_mfcc(object):
 
-    def setUp(self):
-        self.o = mfcc()
+    members_args = 'name'
 
-    def test_default_creation(self):
-        pass
-
-    def test_delete(self):
-        del self.o
-
-    @params(*new_params)
+    @parametrize(members_args, new_params)
     def test_read_only_member(self, name):
-        o = self.o
-        with self.assertRaises((TypeError, AttributeError)):
+        o = mfcc()
+        with assert_raises((TypeError, AttributeError)):
             setattr(o, name, 0)
 
-    @params(*zip(new_params, new_deflts))
+    @parametrize('name, expected', zip(new_params, new_deflts))
     def test_default_param(self, name, expected):
         """ test mfcc.{:s} = {:d} """.format(name, expected)
-        o = self.o
-        self.assertEqual( getattr(o, name), expected)
+        o = mfcc()
+        assert getattr(o, name) == expected
 
 class aubio_mfcc_wrong_params(TestCase):
 
@@ -82,9 +74,9 @@ class aubio_mfcc_compute(TestCase):
         #print coeffs
 
 
-class aubio_mfcc_all_parameters(TestCase):
+class Test_aubio_mfcc_all_parameters(object):
 
-    @params(
+    run_values = [
             (2048, 40, 13, 44100),
             (1024, 40, 13, 44100),
             (512, 40, 13, 44100),
@@ -100,7 +92,10 @@ class aubio_mfcc_all_parameters(TestCase):
             #(1024, 30, 20, 44100),
             (1024, 40, 40, 44100),
             (1024, 40, 3, 44100),
-            )
+            ]
+    run_args = ['buf_size', 'n_filters', 'n_coeffs', 'samplerate']
+
+    @parametrize(run_args, run_values)
     def test_run_with_params(self, buf_size, n_filters, n_coeffs, samplerate):
         " check mfcc can run with reasonable parameters "
         o = mfcc(buf_size, n_filters, n_coeffs, samplerate)
@@ -110,5 +105,43 @@ class aubio_mfcc_all_parameters(TestCase):
             o(spec)
         #print coeffs
 
+
+class aubio_mfcc_fb_params(TestCase):
+
+    def test_set_scale(self):
+        buf_size, n_filters, n_coeffs, samplerate = 512, 20, 10, 16000
+        m = mfcc(buf_size, n_filters, n_coeffs, samplerate)
+        m.set_scale(10.5)
+        assert m.get_scale() == 10.5
+        m(cvec(buf_size))
+
+    def test_set_power(self):
+        buf_size, n_filters, n_coeffs, samplerate = 512, 20, 10, 16000
+        m = mfcc(buf_size, n_filters, n_coeffs, samplerate)
+        m.set_power(2.5)
+        assert m.get_power() == 2.5
+        m(cvec(buf_size))
+
+    def test_set_mel_coeffs(self):
+        buf_size, n_filters, n_coeffs, samplerate = 512, 20, 10, 16000
+        m = mfcc(buf_size, n_filters, n_coeffs, samplerate)
+        m.set_mel_coeffs(0., samplerate/2.)
+        m(cvec(buf_size))
+
+    def test_set_mel_coeffs_htk(self):
+        buf_size, n_filters, n_coeffs, samplerate = 512, 20, 10, 16000
+        m = mfcc(buf_size, n_filters, n_coeffs, samplerate)
+        m.set_mel_coeffs_htk(0., samplerate/2.)
+        m(cvec(buf_size))
+
+    def test_set_mel_coeffs_slaney(self):
+        buf_size, n_filters, n_coeffs, samplerate = 512, 40, 10, 16000
+        m = mfcc(buf_size, n_filters, n_coeffs, samplerate)
+        m.set_mel_coeffs_slaney()
+        m(cvec(buf_size))
+        assert m.get_power() == 1
+        assert m.get_scale() == 1
+
 if __name__ == '__main__':
-    main()
+    from _tools import run_module_suite
+    run_module_suite()
index 056738e..5451c58 100755 (executable)
@@ -2,8 +2,7 @@
 # -*- coding: utf-8 -*-
 
 from aubio import midi2note
-from nose2.tools import params
-import unittest
+from _tools import parametrize, assert_raises
 
 list_of_known_midis = (
         ( 0, 'C-1' ),
@@ -15,31 +14,31 @@ list_of_known_midis = (
         ( 127, 'G9' ),
         )
 
-class midi2note_good_values(unittest.TestCase):
+class Test_midi2note_good_values(object):
 
-    @params(*list_of_known_midis)
+    @parametrize('midi, note', list_of_known_midis)
     def test_midi2note_known_values(self, midi, note):
         " known values are correctly converted "
-        self.assertEqual ( midi2note(midi), note )
+        assert midi2note(midi) == (note)
 
-class midi2note_wrong_values(unittest.TestCase):
+class Test_midi2note_wrong_values(object):
 
     def test_midi2note_negative_value(self):
         " fails when passed a negative value "
-        self.assertRaises(ValueError, midi2note, -2)
+        assert_raises(ValueError, midi2note, -2)
 
     def test_midi2note_large(self):
         " fails when passed a value greater than 127 "
-        self.assertRaises(ValueError, midi2note, 128)
+        assert_raises(ValueError, midi2note, 128)
 
     def test_midi2note_floating_value(self):
         " fails when passed a floating point "
-        self.assertRaises(TypeError, midi2note, 69.2)
+        assert_raises(TypeError, midi2note, 69.2)
 
     def test_midi2note_character_value(self):
         " fails when passed a value that can not be transformed to integer "
-        self.assertRaises(TypeError, midi2note, "a")
+        assert_raises(TypeError, midi2note, "a")
 
 if __name__ == '__main__':
-    import nose2
-    nose2.main()
+    from _tools import run_module_suite
+    run_module_suite()
index dd54abb..eaa774f 100755 (executable)
@@ -1,9 +1,8 @@
 #! /usr/bin/env python
 
-from unittest import main
 import numpy as np
 from numpy.testing import TestCase
-from numpy.testing.utils import assert_equal, assert_almost_equal
+from numpy.testing import assert_equal, assert_almost_equal
 from aubio import window, level_lin, db_spl, silence_detection, level_detection
 from aubio import fvec, float_type
 
@@ -85,4 +84,5 @@ class aubio_level_detection(TestCase):
         assert level_detection(ones(1024, dtype = float_type), -70) == 0
 
 if __name__ == '__main__':
+    from unittest import main
     main()
index 261b025..0608195 100755 (executable)
@@ -3,9 +3,9 @@
 
 from __future__ import unicode_literals
 
-from aubio import note2midi, freq2note
-from nose2.tools import params
-import unittest
+from aubio import note2midi, freq2note, note2freq, float_type
+from numpy.testing import TestCase
+from _tools import parametrize, assert_raises, skipTest
 
 list_of_known_notes = (
         ( 'C-1', 0 ),
@@ -44,29 +44,32 @@ list_of_unknown_notes = (
         ( '2' ),
         )
 
-class note2midi_good_values(unittest.TestCase):
+class Test_note2midi_good_values(object):
 
-    @params(*list_of_known_notes)
+    @parametrize('note, midi', list_of_known_notes)
     def test_note2midi_known_values(self, note, midi):
         " known values are correctly converted "
-        self.assertEqual ( note2midi(note), midi )
+        assert note2midi(note) == midi
 
-    @params(*list_of_known_notes_with_unicode_issues)
+    @parametrize('note, midi', list_of_known_notes_with_unicode_issues)
     def test_note2midi_known_values_with_unicode_issues(self, note, midi):
-        " known values are correctly converted, unless decoding is expected to fail"
+        " difficult values are correctly converted unless expected failure "
         try:
-            self.assertEqual ( note2midi(note), midi )
+            assert note2midi(note) == midi
         except UnicodeEncodeError as e:
+            # platforms with decoding failures include:
+            # - osx: python <= 2.7.10
+            # - win: python <= 2.7.12
             import sys
-            strfmt = "len(u'\\U0001D12A') != 1, excpected decoding failure | {:s} | {:s} {:s}"
-            strres = strfmt.format(e, sys.platform, sys.version)
-            # happens with: darwin 2.7.10, windows 2.7.12
+            strmsg = "len(u'\\U0001D12A') != 1, expected decoding failure"
+            strmsg += " | upgrade to Python 3 to fix"
+            strmsg += " | {:s} | {:s} {:s}"
             if len('\U0001D12A') != 1 and sys.version[0] == '2':
-                self.skipTest(strres + " | upgrade to Python 3 to fix")
+                skipTest(strmsg.format(repr(e), sys.platform, sys.version))
             else:
                 raise
 
-class note2midi_wrong_values(unittest.TestCase):
+class note2midi_wrong_values(TestCase):
 
     def test_note2midi_missing_octave(self):
         " fails when passed only one character"
@@ -104,17 +107,36 @@ class note2midi_wrong_values(unittest.TestCase):
         " fails when passed a note with a note name longer than expected"
         self.assertRaises(ValueError, note2midi, 'CB+-3')
 
-    @params(*list_of_unknown_notes)
+class Test_note2midi_unknown_values(object):
+
+    @parametrize('note', list_of_unknown_notes)
     def test_note2midi_unknown_values(self, note):
         " unknown values throw out an error "
-        self.assertRaises(ValueError, note2midi, note)
+        assert_raises(ValueError, note2midi, note)
 
-class freq2note_simple_test(unittest.TestCase):
+class freq2note_simple_test(TestCase):
 
-    def test_freq2note(self):
+    def test_freq2note_above(self):
         " make sure freq2note(441) == A4 "
         self.assertEqual("A4", freq2note(441))
 
+    def test_freq2note_under(self):
+        " make sure freq2note(439) == A4 "
+        self.assertEqual("A4", freq2note(439))
+
+class note2freq_simple_test(TestCase):
+
+    def test_note2freq(self):
+        " make sure note2freq('A3') == 220"
+        self.assertEqual(220, note2freq("A3"))
+
+    def test_note2freq_under(self):
+        " make sure note2freq(A4) == 440"
+        if float_type == 'float32':
+            self.assertEqual(440, note2freq("A4"))
+        else:
+            self.assertLess(abs(note2freq("A4")-440), 1.e-12)
+
 if __name__ == '__main__':
-    import nose2
-    nose2.main()
+    from _tools import run_module_suite
+    run_module_suite()
index 44aa3e7..a95d010 100755 (executable)
@@ -1,10 +1,14 @@
 #! /usr/bin/env python
 
-from unittest import main
 from numpy.testing import TestCase, assert_equal, assert_almost_equal
-from aubio import notes
+from aubio import notes, source
+import numpy as np
+from utils import list_all_sounds
+
+list_of_sounds = list_all_sounds('sounds')
 
 AUBIO_DEFAULT_NOTES_SILENCE = -70.
+AUBIO_DEFAULT_NOTES_RELEASE_DROP = 10.
 AUBIO_DEFAULT_NOTES_MINIOI_MS = 30.
 
 class aubio_notes_default(TestCase):
@@ -38,14 +42,22 @@ class aubio_notes_params(TestCase):
         self.o.set_silence(val)
         assert_equal (self.o.get_silence(), val)
 
-from .utils import list_all_sounds
-list_of_sounds = list_all_sounds('sounds')
+    def test_get_release_drop(self):
+        assert_equal (self.o.get_release_drop(), AUBIO_DEFAULT_NOTES_RELEASE_DROP)
+
+    def test_set_release_drop(self):
+        val = 50
+        self.o.set_release_drop(val)
+        assert_equal (self.o.get_release_drop(), val)
+
+    def test_set_release_drop_wrong(self):
+        val = -10
+        with self.assertRaises(ValueError):
+            self.o.set_release_drop(val)
 
 class aubio_notes_sinewave(TestCase):
 
     def analyze_file(self, filepath, samplerate=0):
-        from aubio import source
-        import numpy as np
         win_s = 512 # fft size
         hop_s = 256 # hop size
 
@@ -78,4 +90,5 @@ class aubio_notes_sinewave(TestCase):
                 assert_equal (results[0][1], [69, 123, -1])
 
 if __name__ == '__main__':
+    from unittest import main
     main()
index 8b5f7ba..08edbee 100755 (executable)
@@ -1,8 +1,7 @@
 #! /usr/bin/env python
 
-from unittest import main
 from numpy.testing import TestCase, assert_equal, assert_almost_equal
-from aubio import onset
+from aubio import onset, fvec
 
 class aubio_onset_default(TestCase):
 
@@ -83,5 +82,38 @@ class aubio_onset_32000(aubio_onset_params):
 class aubio_onset_8000(aubio_onset_params):
     samplerate = 8000
 
+class aubio_onset_coverate(TestCase):
+    # extra tests to execute the C routines and improve coverage
+
+    def test_all_methods(self):
+        for method in ['default', 'energy', 'hfc', 'complexdomain', 'complex',
+                'phase', 'wphase', 'mkl', 'kl', 'specflux', 'specdiff',
+                'old_default']:
+            o = onset(method=method, buf_size=512, hop_size=256)
+            o(fvec(256))
+
+    def test_get_methods(self):
+        o = onset(method='default', buf_size=512, hop_size=256)
+
+        assert o.get_silence() == -70
+        o.set_silence(-20)
+        assert_almost_equal(o.get_silence(), -20)
+
+        assert o.get_compression() == 1
+        o.set_compression(.99)
+        assert_almost_equal(o.get_compression(), .99)
+
+        assert o.get_awhitening() == 0
+        o.set_awhitening(1)
+        assert o.get_awhitening() == 1
+
+        o.get_last()
+        o.get_last_ms()
+        o.get_last_s()
+        o.get_descriptor()
+        o.get_thresholded_descriptor()
+
+
 if __name__ == '__main__':
+    from unittest import main
     main()
index 957d3b1..cf3b7ac 100755 (executable)
@@ -1,9 +1,8 @@
 #! /usr/bin/env python
 
 from numpy.testing import TestCase, assert_equal, assert_array_less
+from _tools import parametrize
 from aubio import fvec, cvec, pvoc, float_type
-from nose2 import main
-from nose2.tools import params
 import numpy as np
 
 if float_type == 'float32':
@@ -18,7 +17,7 @@ def create_sine(hop_s, freq, samplerate):
 def create_noise(hop_s):
     return np.random.rand(hop_s).astype(float_type) * 2. - 1.
 
-class aubio_pvoc_test_case(TestCase):
+class Test_aubio_pvoc_test_case(object):
     """ pvoc object test case """
 
     def test_members_automatic_sizes_default(self):
@@ -56,7 +55,17 @@ class aubio_pvoc_test_case(TestCase):
                         + 'This is expected when using fftw3 on powerpc.')
             assert_equal ( r, 0.)
 
-    @params(
+    def test_no_overlap(self):
+        win_s, hop_s = 1024, 1024
+        f = pvoc (win_s, hop_s)
+        t = fvec (hop_s)
+        for _ in range(4):
+            s = f(t)
+            r = f.rdo(s)
+            assert_equal ( t, 0.)
+
+    resynth_noise_args = "hop_s, ratio"
+    resynth_noise_values = [
             ( 256, 8),
             ( 256, 4),
             ( 256, 2),
@@ -78,13 +87,16 @@ class aubio_pvoc_test_case(TestCase):
             (8192, 8),
             (8192, 4),
             (8192, 2),
-            )
+            ]
+
+    @parametrize(resynth_noise_args, resynth_noise_values)
     def test_resynth_steps_noise(self, hop_s, ratio):
         """ check the resynthesis of a random signal is correct """
         sigin = create_noise(hop_s)
         self.reconstruction(sigin, hop_s, ratio)
 
-    @params(
+    resynth_sine_args = "samplerate, hop_s, ratio, freq"
+    resynth_sine_values = [
             (44100,  256, 8,   441),
             (44100,  256, 4,  1203),
             (44100,  256, 2,  3045),
@@ -99,7 +111,9 @@ class aubio_pvoc_test_case(TestCase):
             (22050,  256, 8,   445),
             (96000, 1024, 8, 47000),
             (96000, 1024, 8,    20),
-            )
+            ]
+
+    @parametrize(resynth_sine_args, resynth_sine_values)
     def test_resynth_steps_sine(self, samplerate, hop_s, ratio, freq):
         """ check the resynthesis of a sine is correct """
         sigin = create_sine(hop_s, freq, samplerate)
@@ -190,5 +204,5 @@ class aubio_pvoc_wrong_params(TestCase):
             self.skipTest('creating aubio.pvoc with size %d did not fail' % win_s)
 
 if __name__ == '__main__':
+    from unittest import main
     main()
-
index 749c37b..6305532 100755 (executable)
@@ -1,7 +1,6 @@
 #! /usr/bin/env python
 
-from unittest import TestCase, main
-from numpy.testing import assert_equal
+from numpy.testing import TestCase, assert_equal
 from numpy import sin, arange, mean, median, isnan, pi
 from aubio import fvec, pitch, freqtomidi, float_type
 
@@ -116,10 +115,11 @@ def create_test (algo, mode):
 
 for algo in pitch_algorithms:
     for mode in signal_modes:
-        test_method = create_test (algo, mode)
-        test_method.__name__ = 'test_pitch_%s_%d_%d_%dHz_sin_%.0f' % ( algo,
+        _test_method = create_test (algo, mode)
+        _test_method.__name__ = 'test_pitch_%s_%d_%d_%dHz_sin_%.0f' % ( algo,
                 mode[0], mode[1], mode[2], mode[3] )
-        setattr (aubio_pitch_Sinusoid, test_method.__name__, test_method)
+        setattr (aubio_pitch_Sinusoid, _test_method.__name__, _test_method)
 
 if __name__ == '__main__':
+    from unittest import main
     main()
index 795032b..9516fe4 100755 (executable)
@@ -1,13 +1,10 @@
 #! /usr/bin/env python
 
-from nose2 import main
-from nose2.tools import params
 from numpy.testing import TestCase
 from aubio import fvec, source, sink
-from .utils import list_all_sounds, get_tmp_sink_path, del_tmp_sink_path
-
-import warnings
-warnings.filterwarnings('ignore', category=UserWarning, append=True)
+from utils import list_all_sounds, get_tmp_sink_path, del_tmp_sink_path
+from utils import parse_file_samplerate
+from _tools import parametrize, skipTest, assert_raises, assert_warns
 
 list_of_sounds = list_all_sounds('sounds')
 samplerates = [0, 44100, 8000, 32000]
@@ -23,30 +20,26 @@ for soundfile in list_of_sounds:
         for samplerate in samplerates:
             all_params.append((hop_size, samplerate, soundfile))
 
-class aubio_sink_test_case(TestCase):
-
-    def setUp(self):
-        if not len(list_of_sounds):
-            self.skipTest('add some sound files in \'python/tests/sounds\'')
+class Test_aubio_sink(object):
 
     def test_wrong_filename(self):
-        with self.assertRaises(RuntimeError):
+        with assert_raises(RuntimeError):
             sink('')
 
     def test_wrong_samplerate(self):
-        with self.assertRaises(RuntimeError):
+        with assert_raises(RuntimeError):
             sink(get_tmp_sink_path(), -1)
 
     def test_wrong_samplerate_too_large(self):
-        with self.assertRaises(RuntimeError):
+        with assert_raises(RuntimeError):
             sink(get_tmp_sink_path(), 1536001, 2)
 
     def test_wrong_channels(self):
-        with self.assertRaises(RuntimeError):
+        with assert_raises(RuntimeError):
             sink(get_tmp_sink_path(), 44100, -1)
 
     def test_wrong_channels_too_large(self):
-        with self.assertRaises(RuntimeError):
+        with assert_raises(RuntimeError):
             sink(get_tmp_sink_path(), 44100, 202020)
 
     def test_many_sinks(self):
@@ -66,13 +59,19 @@ class aubio_sink_test_case(TestCase):
             g.close()
         shutil.rmtree(tmpdir)
 
-    @params(*all_params)
+    @parametrize('hop_size, samplerate, path', all_params)
     def test_read_and_write(self, hop_size, samplerate, path):
-
+        orig_samplerate = parse_file_samplerate(soundfile)
         try:
-            f = source(path, samplerate, hop_size)
+            if orig_samplerate is not None and orig_samplerate < samplerate:
+                # upsampling should emit a warning
+                with assert_warns(UserWarning):
+                    f = source(soundfile, samplerate, hop_size)
+            else:
+                f = source(soundfile, samplerate, hop_size)
         except RuntimeError as e:
-            self.skipTest('failed opening with hop_s = {:d}, samplerate = {:d} ({:s})'.format(hop_size, samplerate, str(e)))
+            err_msg = '{:s} (hop_s = {:d}, samplerate = {:d})'
+            skipTest(err_msg.format(str(e), hop_size, samplerate))
         if samplerate == 0: samplerate = f.samplerate
         sink_path = get_tmp_sink_path()
         g = sink(sink_path, samplerate)
@@ -84,12 +83,19 @@ class aubio_sink_test_case(TestCase):
             if read < f.hop_size: break
         del_tmp_sink_path(sink_path)
 
-    @params(*all_params)
+    @parametrize('hop_size, samplerate, path', all_params)
     def test_read_and_write_multi(self, hop_size, samplerate, path):
+        orig_samplerate = parse_file_samplerate(soundfile)
         try:
-            f = source(path, samplerate, hop_size)
+            if orig_samplerate is not None and orig_samplerate < samplerate:
+                # upsampling should emit a warning
+                with assert_warns(UserWarning):
+                    f = source(soundfile, samplerate, hop_size)
+            else:
+                f = source(soundfile, samplerate, hop_size)
         except RuntimeError as e:
-            self.skipTest('failed opening with hop_s = {:d}, samplerate = {:d} ({:s})'.format(hop_size, samplerate, str(e)))
+            err_msg = '{:s} (hop_s = {:d}, samplerate = {:d})'
+            skipTest(err_msg.format(str(e), hop_size, samplerate))
         if samplerate == 0: samplerate = f.samplerate
         sink_path = get_tmp_sink_path()
         g = sink(sink_path, samplerate, channels = f.channels)
@@ -125,4 +131,5 @@ class aubio_sink_test_case(TestCase):
                 g(vec, 128)
 
 if __name__ == '__main__':
-    main()
+    from _tools import run_module_suite
+    run_module_suite()
index c96ba52..18391e9 100755 (executable)
@@ -1,10 +1,9 @@
 #! /usr/bin/env python
 
-from unittest import main
 from numpy.testing import TestCase, assert_equal
 from aubio import slice_source_at_stamps
-from .utils import count_files_in_directory, get_default_test_sound
-from .utils import count_samples_in_directory, count_samples_in_file
+from utils import count_files_in_directory, get_default_test_sound
+from utils import count_samples_in_directory, count_samples_in_file
 
 import tempfile
 import shutil
@@ -23,19 +22,27 @@ class aubio_slicing_test_case(TestCase):
 
     def test_slice_start_only_no_zero(self):
         regions_start = [i*1000 for i in range(1, n_slices)]
-        slice_source_at_stamps(self.source_file, regions_start, output_dir = self.output_dir)
+        slice_source_at_stamps(self.source_file, regions_start,
+                output_dir = self.output_dir, create_first=True)
 
     def test_slice_start_beyond_end(self):
         regions_start = [i*1000 for i in range(1, n_slices)]
         regions_start += [count_samples_in_file(self.source_file) + 1000]
-        slice_source_at_stamps(self.source_file, regions_start, output_dir = self.output_dir)
+        slice_source_at_stamps(self.source_file, regions_start,
+                output_dir = self.output_dir, create_first=True)
 
     def test_slice_start_every_blocksize(self):
         hopsize = 200
-        regions_start = [i*hopsize for i in range(1, n_slices)]
+        regions_start = [i*hopsize for i in range(0, n_slices)]
         slice_source_at_stamps(self.source_file, regions_start, output_dir = self.output_dir,
                 hopsize = 200)
 
+    def test_slice_start_every_half_blocksize(self):
+        hopsize = 200
+        regions_start = [i*hopsize//2 for i in range(0, n_slices)]
+        slice_source_at_stamps(self.source_file, regions_start,
+                output_dir = self.output_dir, hopsize = 200)
+
     def tearDown(self):
         original_samples = count_samples_in_file(self.source_file)
         written_samples = count_samples_in_directory(self.output_dir)
@@ -91,6