From: Paul Brossier Date: Tue, 2 Jan 2024 20:16:22 +0000 (+0100) Subject: [ci] add pip install to readthedocs.yaml X-Git-Url: https://git.aubio.org/?p=aubio.git;a=commitdiff_plain;h=HEAD;hp=b1ed6ce4dd48b7328ecba01c73010a5ea7d12c1c [ci] add pip install to readthedocs.yaml --- diff --git a/.appveyor.yml b/.appveyor.yml index 0c21b176..e1550061 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -15,14 +15,6 @@ environment: PYTHON_VERSION: 2.7.x PYTHON_ARCH: 64 - - PYTHONDIR: C:\Python35 - PYTHON_VERSION: 3.5.x - PYTHON_ARCH: 32 - - - PYTHONDIR: C:\Python35-x64 - PYTHON_VERSION: 3.5.x - PYTHON_ARCH: 64 - - PYTHONDIR: C:\Python36 PYTHON_VERSION: 3.6.x PYTHON_ARCH: 32 @@ -39,6 +31,14 @@ environment: PYTHON_VERSION: 3.7.x PYTHON_ARCH: 64 + - PYTHONDIR: C:\Python38 + PYTHON_VERSION: 3.8.x + PYTHON_ARCH: 32 + + - PYTHONDIR: C:\Python38-x64 + PYTHON_VERSION: 3.8.x + PYTHON_ARCH: 64 + install: - ECHO "Installed SDKs:" - ps: "ls \"C:/Program Files/Microsoft SDKs/Windows\"" diff --git a/.circleci/config.yml b/.circleci/config.yml index eb007381..b2594c7e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,8 +1,12 @@ +# python circleci 2.1 configuration +# see https://circleci.com/docs/2.1/language-python/ +version: 2.1 + 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 + sudo apt-get -y install make sox pkg-config libavcodec-dev libavformat-dev libswresample-dev libavutil-dev libsndfile1-dev libsamplerate-dev librubberband-dev libvorbis-dev libflac-dev pip-install: &pip-install name: Install pip dependencies @@ -36,11 +40,10 @@ uninstall-wheel: &uninstall-wheel pip show -f aubio pip uninstall --verbose --yes aubio -version: 2 jobs: - build-27: + build-36: docker: - - image: circleci/python:2.7 + - image: cimg/python:3.6 steps: - checkout - run: *apt-install @@ -52,9 +55,9 @@ jobs: - store_artifacts: path: dist/ - build-36: + build-310: docker: - - image: circleci/python:3.6 + - image: cimg/python:3.10 steps: - checkout - run: *apt-install @@ -66,9 +69,22 @@ jobs: - store_artifacts: path: dist/ - build-37: + build-310-nodeps: + docker: + - image: cimg/python:3.10 + steps: + - checkout + - run: *pip-install + - run: *build-wheel + - run: *install-wheel + - run: *test-pytest-nosounds + - run: *uninstall-wheel + - store_artifacts: + path: dist/ + + build-312: docker: - - image: circleci/python:3.7 + - image: cimg/python:3.12 steps: - checkout - run: *apt-install @@ -80,9 +96,9 @@ jobs: - store_artifacts: path: dist/ - build-37-nodeps: + build-312-nodeps: docker: - - image: circleci/python:3.7 + - image: cimg/python:3.12 steps: - checkout - run: *pip-install @@ -98,7 +114,8 @@ workflows: test-wheel: jobs: - - build-27 - build-36 - - build-37 - - build-37-nodeps + - build-310 + - build-310-nodeps + - build-312 + - build-312-nodeps diff --git a/.gitignore b/.gitignore index 21bb1cce..e17a67da 100644 --- a/.gitignore +++ b/.gitignore @@ -12,8 +12,8 @@ python/lib/aubio/_aubio.*.so RE:examples/[a-z]* # ignore compiled test programs -RE:tests/src/test-[a-z-_]*$ -RE:tests/cpp/test-[a-z-_]*$ +RE:tests/src/test-[a-z_-]*$ +RE:tests/cpp/test-[a-z_-]*$ # only sgml manpages count doc/*.1 diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 00000000..85253fbc --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,16 @@ +# see https://docs.readthedocs.io/en/stable/config-file/ +version: 2 + +build: + os: ubuntu-22.04 + tools: + python: "3.12" + +python: + install: + - requirements: requirements.txt + - method: pip + path: . + +sphinx: + configuration: doc/conf.py diff --git a/.travis.yml b/.travis.yml index 788e2064..30b81e58 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,55 +2,63 @@ language: python matrix: include: - - python: 3.6 + - name: "Linux (Python 3.8)" + python: 3.8 os: linux - compiler: gcc - - python: 3.5 + distro: bionic + - name: "Linux (Python 3.6)" + python: 3.6 os: linux - compiler: gcc env: WAFOPTS="--build-type=debug" - - python: 2.7 + - name: "Linux (Python 2.7, nodeps)" + python: 2.7 os: linux - compiler: gcc - - python: "pypy3.5" + distro: trusty + env: WAFOPTS="--nodeps" + - name: "Linux (Python pypy3.5)" + language: python + python: "pypy3.5" os: linux - compiler: gcc - env: CFLAGS="-Os" WAFOPTS="--disable-avcodec" - - python: 3.6 + - name: "Linux (double, fftw3)" + python: 3.8 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" - - python: 2.7 + - name: "Linux (default, dpkg-buildflags)" os: linux - compiler: gcc env: CFLAGS="`dpkg-buildflags --get CFLAGS`" LDFLAGS="`dpkg-buildflags --get LDFLAGS`" - - language: C + + - name: "macOS (xcode11)" + language: shell os: osx - compiler: clang - - language: C + osx_image: xcode11 + - name: "macOS (xcode12, lib only)" + language: shell os: osx - compiler: clang - env: CFLAGS="-Os" HAVE_AUBIO_DOUBLE=1 WAFOPTS="--disable-accelerate" - - language: C + osx_image: xcode12 + script: + - make test_lib_only_clean + - name: "macOS (xcode10, noopt, nodeps)" + language: shell os: osx - compiler: clang - env: WAFOPTS="--enable-fat --disable-avcodec --disable-sndfile --disable-samplerate" - - language: C + osx_image: xcode10 + env: CFLAGS="-Os" HAVE_AUBIO_DOUBLE=1 WAFOPTS="--disable-accelerate --nodeps" + - name: "iOS" + language: shell os: osx - compiler: clang - env: WAFOPTS="--with-target-platform=ios --disable-avcodec --disable-sndfile --disable-samplerate" AUBIO_NOTESTS=1 - - language: C + env: WAFOPTS="--with-target-platform=ios --nodeps" AUBIO_NOTESTS=1 + - name: "iOSSimulator" + language: shell os: osx - compiler: clang - env: WAFOPTS="--with-target-platform=iosimulator --disable-avcodec --disable-sndfile --disable-samplerate" AUBIO_NOTESTS=1 + env: WAFOPTS="--with-target-platform=iosimulator --nodeps" AUBIO_NOTESTS=1 -# use trusty -dist: trusty -sudo: required +# - name: "Windows (Python 3.8.0, lib only)" +# language: shell +# os: windows +# before_install: +# - choco install python --version 3.8.0 +# - choco install make +# - python -m pip install --upgrade pip +# env: PATH=/c/Python38:/c/Python38/Scripts:$PATH AUBIO_NOTESTS=1 addons: apt: @@ -58,13 +66,16 @@ addons: - bzip2 - libavcodec-dev - libavformat-dev - - libavresample-dev + - libswresample-dev - libavutil-dev - libsndfile1-dev - libsamplerate-dev + - libvorbis-dev + - libflac-dev - libjack-dev - libasound2-dev - libfftw3-dev + - librubberband-dev - sox - lcov homebrew: @@ -72,22 +83,15 @@ addons: - sox - ffmpeg - libsndfile + - libvorbis + - flac + - libsamplerate + - rubberband - lcov update: true -before_install: - - | - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then - 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 + - make getwaf deps_python - which pip - pip --version - pip install coverage diff --git a/Makefile b/Makefile index a21c0d63..97d9d19a 100644 --- a/Makefile +++ b/Makefile @@ -183,7 +183,7 @@ create_test_sounds: -$(SOX) -r 22050 -b 16 -n "$(TESTSOUNDS)/22050Hz_5s_brownnoise.wav" synth 5 brownnoise vol 0.9 -$(SOX) -r 32000 -b 16 -n "$(TESTSOUNDS)/32000Hz_127f_sine440.wav" synth 127s sine 440 vol 0.9 -$(SOX) -r 8000 -b 16 -n "$(TESTSOUNDS)/8000Hz_30s_silence.wav" trim 0 30 - -$(SOX) -r 48000 -b 32 -n "$(TESTSOUNDS)/48000Hz_60s_sweep.wav" synth 60 sine 100-20000 vol 0.9 + -$(SOX) -r 48000 -b 16 -n "$(TESTSOUNDS)/48000Hz_60s_sweep.wav" synth 60 sine 100-20000 vol 0.9 -$(SOX) -r 44100 -b 16 -n "$(TESTSOUNDS)/44100Hz_44100f_sine441.wav" synth 44100s sine 441 vol 0.9 -$(SOX) -r 44100 -b 16 -n "$(TESTSOUNDS)/44100Hz_100f_sine441.wav" synth 100s sine 441 vol 0.9 diff --git a/README.md b/README.md index 45db9116..c3cbabbf 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,15 @@ aubio ===== -[![Travis build status](https://travis-ci.org/aubio/aubio.svg?branch=master)](https://travis-ci.org/aubio/aubio "Travis build status") +[![CircleCI build status](https://circleci.com/gh/aubio/aubio.svg?style=shield)](https://circleci.com/gh/aubio/aubio "CircleCI build status") +[![Azure Pipelines](https://dev.azure.com/aubio/aubio/_apis/build/status/aubio.aubio)](https://dev.azure.com/aubio/aubio "Azure 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") -[![Commits since last release](https://img.shields.io/github/commits-since/aubio/aubio/latest.svg)](https://github.com/aubio/aubio "Commits since last release") +[![Pypi Downloads](https://img.shields.io/pypi/dm/aubio.svg?label=Pypi%20downloads)](https://pypi.org/project/aubio/) +[![Conda Downloads](https://img.shields.io/conda/dn/conda-forge/aubio.svg?label=Conda%20downloads)](https://anaconda.org/conda-forge/aubio) [![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) +[![Commits since last release](https://img.shields.io/github/commits-since/aubio/aubio/latest.svg)](https://github.com/aubio/aubio "Commits since last release") aubio is a library to label music and sounds. It listens to audio signals and attempts to detect events. For instance, when a drum is hit, at which frequency diff --git a/VERSION b/VERSION index 8fa7caab..5a03d28d 100644 --- a/VERSION +++ b/VERSION @@ -1,7 +1,7 @@ AUBIO_MAJOR_VERSION=0 AUBIO_MINOR_VERSION=5 AUBIO_PATCH_VERSION=0 -AUBIO_VERSION_STATUS='~alpha' +AUBIO_VERSION_STATUS='-alpha' LIBAUBIO_LT_CUR=5 LIBAUBIO_LT_REV=4 LIBAUBIO_LT_AGE=8 diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 277140ca..974dffa2 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -3,7 +3,35 @@ jobs: - job: linux pool: - vmImage: 'ubuntu-16.04' + vmImage: 'ubuntu-latest' + steps: + - script: | + sudo apt update + sudo apt install libsndfile-dev libavformat-dev librubberband-dev libvorbis-dev libflac-dev + displayName: 'deps' + - script: | + make + displayName: 'make' + env: + CFLAGS: -Werror + +- job: linux_double + pool: + vmImage: 'ubuntu-latest' + steps: + - script: | + sudo apt update + sudo apt install libfftw3-dev libsndfile-dev libavformat-dev librubberband-dev libvorbis-dev libflac-dev + displayName: 'deps' + - script: | + HAVE_AUBIO_DOUBLE=1 WAFOPTS="--enable-fftw3" make + displayName: 'make' + env: + CFLAGS: -Werror + +- job: linux_nodeps + pool: + vmImage: 'ubuntu-latest' steps: - script: | make @@ -13,7 +41,7 @@ jobs: - job: windows pool: - vmImage: 'vs2017-win2016' + vmImage: 'windows-latest' steps: - script: | make @@ -24,12 +52,12 @@ jobs: - job: macos pool: - vmImage: 'macos-10.13' + vmImage: 'macos-latest' steps: - script: | brew update brew install pkg-config gnupg - brew install sox ffmpeg libsndfile lcov + brew install sox ffmpeg libsndfile libvorbis flac lcov displayName: 'brew install' - script: | make diff --git a/doc/develop.rst b/doc/develop.rst index fa27db02..3e88987e 100644 --- a/doc/develop.rst +++ b/doc/develop.rst @@ -92,7 +92,7 @@ First, define a few variables and allocate some memory. .. literalinclude:: ../tests/src/io/test-source.c :language: C - :lines: 22-24, 30-32, 34 + :lines: 24-26, 30, 32-34 .. note:: With ``samplerate = 0``, ``aubio_source`` will be created with the file's @@ -102,13 +102,13 @@ Now for the processing loop: .. literalinclude:: ../tests/src/io/test-source.c :language: C - :lines: 40-44 + :lines: 41-45 At the end of the processing loop, memory is deallocated: .. literalinclude:: ../tests/src/io/test-source.c :language: C - :lines: 55-56 + :lines: 55-58 See the complete example: :download:`test-source.c <../tests/src/io/test-source.c>`. @@ -126,13 +126,13 @@ The processing loop could now look like: .. literalinclude:: ../tests/src/spectral/test-phasevoc.c :language: C - :lines: 20-37 + :lines: 27-44 Time to clean up the previously allocated memory: .. literalinclude:: ../tests/src/spectral/test-phasevoc.c :language: C - :lines: 39-44 + :lines: 47-50 See the complete example: :download:`test-phasevoc.c <../tests/src/spectral/test-phasevoc.c>`. diff --git a/doc/installing.rst b/doc/installing.rst index 2c104125..2044c0b9 100644 --- a/doc/installing.rst +++ b/doc/installing.rst @@ -67,8 +67,7 @@ Cheat sheet - :ref:`check the list of optional dependencies `:: # debian / ubuntu - dpkg -l libavcodec-dev libavutil-dev libavformat-dev \ - libswresample-dev libavresample-dev \ + dpkg -l libavcodec-dev libavutil-dev libavformat-dev libswresample-dev \ libsamplerate-dev libsndfile-dev \ txt2man doxygen diff --git a/doc/requirements.rst b/doc/requirements.rst index fcad2d1a..905855f5 100644 --- a/doc/requirements.rst +++ b/doc/requirements.rst @@ -31,6 +31,8 @@ unusual location. If ``pkg-config`` is not found in ``PATH``, the configure step will succeed, but none of the external libraries will be used. +To build aubio with no external libraries, use the ``--nodeps`` build option. + Media libraries --------------- @@ -48,7 +50,7 @@ from all formats supported by `libav * libavcodec * libavformat * libavutil -* libavresample +* libswresample To enable this option, configure with ``--enable-avcodec``. The build will then failed if the required libraries are not found. To disable this option, diff --git a/examples/jackio.c b/examples/jackio.c index f781fb16..7a677312 100644 --- a/examples/jackio.c +++ b/examples/jackio.c @@ -123,10 +123,10 @@ new_aubio_jack (uint_t hop_size, uint_t ichan, uint_t ochan, for (i = 0; i < ochan + omidichan; i++) { if (i < ochan) { jack_port_type = JACK_DEFAULT_AUDIO_TYPE; - AUBIO_SPRINTF (name, "out_%d", i + 1); + AUBIO_SNPRINTF (name, sizeof (name), "out_%d", i + 1); } else { jack_port_type = JACK_DEFAULT_MIDI_TYPE; - AUBIO_SPRINTF (name, "midi_out_%d", i - ochan + 1); + AUBIO_SNPRINTF (name, sizeof (name), "midi_out_%d", i - ochan + 1); } if ((jack_setup->oports[i] = jack_port_register (jack_setup->client, name, @@ -140,10 +140,10 @@ new_aubio_jack (uint_t hop_size, uint_t ichan, uint_t ochan, for (i = 0; i < ichan + imidichan; i++) { if (i < ichan) { jack_port_type = JACK_DEFAULT_AUDIO_TYPE; - AUBIO_SPRINTF (name, "in_%d", i + 1); + AUBIO_SNPRINTF (name, sizeof (name), "in_%d", i + 1); } else { jack_port_type = JACK_DEFAULT_MIDI_TYPE; - AUBIO_SPRINTF (name, "midi_in_%d", i - ichan + 1); + AUBIO_SNPRINTF (name, sizeof (name), "midi_in_%d", i - ichan + 1); } if ((jack_setup->iports[i] = jack_port_register (jack_setup->client, name, diff --git a/python/demos/demo_pitch.py b/python/demos/demo_pitch.py index 555a30e6..b85d2a0d 100755 --- a/python/demos/demo_pitch.py +++ b/python/demos/demo_pitch.py @@ -97,7 +97,7 @@ ax3.plot(times, confidences) # draw a line at tolerance ax3.plot(times, [tolerance]*len(confidences)) ax3.axis( xmin = times[0], xmax = times[-1]) -ax3.set_ylabel('condidence') +ax3.set_ylabel('confidence') set_xlabels_sample2time(ax3, times[-1], samplerate) plt.show() #plt.savefig(os.path.basename(filename) + '.svg') diff --git a/python/demos/demo_pitchshift.py b/python/demos/demo_pitchshift.py new file mode 100755 index 00000000..ad78794a --- /dev/null +++ b/python/demos/demo_pitchshift.py @@ -0,0 +1,52 @@ +#! /usr/bin/env python + +import sys +import aubio + +if __name__ == '__main__': + if len(sys.argv) < 3: + print('usage: %s [transpose] [samplerate] [hop_size] [mode]' % sys.argv[0]) + print('available modes: default, crispness:0, crispness:1, ... crispness:6') + sys.exit(1) + if len(sys.argv) > 3: transpose = float(sys.argv[3]) + else: transpose = 12. + if len(sys.argv) > 4: samplerate = int(sys.argv[4]) + else: samplerate = 0 + if len(sys.argv) > 5: hop_size = int(sys.argv[5]) + else: hop_size = 64 + if len(sys.argv) > 6: mode = sys.argv[6] + else: mode = "default" + + source_read = aubio.source(sys.argv[1], samplerate, hop_size) + if samplerate == 0: samplerate = source_read.samplerate + sink_out = aubio.sink(sys.argv[2], samplerate) + + pitchshifter = aubio.pitchshift(mode, 1., hop_size, samplerate) + if transpose: pitchshifter.set_transpose(transpose) + + total_frames, read = 0, hop_size + transpose_range = 23.9 + while read: + vec, read = source_read() + # transpose the samples + out = pitchshifter(vec) + # position in the file (between 0. and 1.) + percent_read = total_frames / float(source_read.duration) + # variable transpose rate (in semitones) + transpose = 2 * transpose_range * percent_read - transpose_range + # set transpose rate + pitchshifter.set_transpose(transpose) + # print the transposition + #print pitchshifter.get_transpose() + # write the output + sink_out(out, read) + total_frames += read + + # end of file, print some results + outstr = "wrote %.2fs" % (total_frames / float(samplerate)) + outstr += " (%d frames in" % total_frames + outstr += " %d blocks" % (total_frames // source_read.hop_size) + outstr += " at %dHz)" % source_read.samplerate + outstr += " from " + source_read.uri + outstr += " to " + sink_out.uri + print(outstr) diff --git a/python/ext/ufuncs.c b/python/ext/ufuncs.c index d373d725..e5641342 100644 --- a/python/ext/ufuncs.c +++ b/python/ext/ufuncs.c @@ -3,8 +3,8 @@ typedef smpl_t (*aubio_unary_func_t)(smpl_t input); -static void aubio_PyUFunc_d_d(char **args, npy_intp *dimensions, - npy_intp* steps, void* data) +static void aubio_PyUFunc_d_d(char **args, const npy_intp *dimensions, + const npy_intp* steps, void* data) { npy_intp i; npy_intp n = dimensions[0]; @@ -22,8 +22,8 @@ static void aubio_PyUFunc_d_d(char **args, npy_intp *dimensions, } } -static void aubio_PyUFunc_f_f_As_d_d(char **args, npy_intp *dimensions, - npy_intp* steps, void* data) +static void aubio_PyUFunc_f_f_As_d_d(char **args, const npy_intp *dimensions, + const npy_intp* steps, void* data) { npy_intp i; npy_intp n = dimensions[0]; diff --git a/python/lib/gen_code.py b/python/lib/gen_code.py index 3db270b4..be7fec65 100644 --- a/python/lib/gen_code.py +++ b/python/lib/gen_code.py @@ -20,6 +20,7 @@ aubiodefvalue = { 'ratio': '0.5', 'method': '"default"', 'uri': '"none"', + 'transpose': '0.', } member_types = { @@ -83,6 +84,7 @@ objoutsize = { 'tempo': '1', 'filterbank': 'self->n_filters', 'tss': 'self->buf_size', + 'pitchshift': 'self->hop_size', 'dct': 'self->size', } @@ -96,6 +98,7 @@ objinputsize = { 'tempo': 'self->hop_size', 'wavetable': 'self->hop_size', 'tss': 'self->buf_size / 2 + 1', + 'pitchshift': 'self->hop_size', } def get_name(proto): @@ -290,6 +293,8 @@ Py_{shortname}_new (PyTypeObject * pytype, PyObject * args, PyObject * kwds) return self.check_valid_uint(p) if p['type'] == 'char_t*': return self.check_valid_char(p) + if p['type'] == 'smpl_t': + return self.check_valid_smpl(p) else: print ("ERROR, no idea how to check %s for validity" % p['type']) @@ -314,6 +319,15 @@ Py_{shortname}_new (PyTypeObject * pytype, PyObject * args, PyObject * kwds) }} """.format(defval = aubiodefvalue[name], name = name) + def check_valid_smpl(self, p): + name = p['name'] + return """ + self->{name} = {defval}; + if ({name} != 0.) {{ + self->{name} = {name}; + }} +""".format(defval = aubiodefvalue[name], name = name) + def gen_init(self): out = """ // init {shortname} diff --git a/python/lib/gen_external.py b/python/lib/gen_external.py index e5f0a02d..4ef75270 100644 --- a/python/lib/gen_external.py +++ b/python/lib/gen_external.py @@ -46,6 +46,7 @@ default_skip_objects = [ #'sampler', 'audio_unit', 'spectral_whitening', + 'timestretch', # TODO fix parsing of uint_t *read in _do ] diff --git a/python/lib/moresetuptools.py b/python/lib/moresetuptools.py index 299d1f9f..0c704315 100644 --- a/python/lib/moresetuptools.py +++ b/python/lib/moresetuptools.py @@ -76,9 +76,10 @@ def add_local_macros(ext, usedouble = False): def add_external_deps(ext, usedouble = False): # loof for additional packages print("Info: looking for *optional* additional packages") - packages = ['libavcodec', 'libavformat', 'libavutil', - 'libswresample', 'libavresample', + packages = ['libavcodec', 'libavformat', 'libavutil', 'libswresample', + 'jack', 'sndfile', + 'rubberband', #'fftw3f', ] # samplerate only works with float @@ -89,17 +90,15 @@ def add_external_deps(ext, usedouble = False): add_packages(packages, ext=ext) if 'avcodec' in ext.libraries \ and 'avformat' in ext.libraries \ - and 'avutil' in ext.libraries: - if 'swresample' in ext.libraries: - ext.define_macros += [('HAVE_SWRESAMPLE', 1)] - elif 'avresample' in ext.libraries: - ext.define_macros += [('HAVE_AVRESAMPLE', 1)] - if 'swresample' in ext.libraries or 'avresample' in ext.libraries: - ext.define_macros += [('HAVE_LIBAV', 1)] + and 'avutil' in ext.libraries \ + and 'swresample' in ext.libraries: + ext.define_macros += [('HAVE_LIBAV', 1)] if 'sndfile' in ext.libraries: ext.define_macros += [('HAVE_SNDFILE', 1)] if 'samplerate' in ext.libraries: ext.define_macros += [('HAVE_SAMPLERATE', 1)] + if 'rubberband' in ext.libraries: + ext.define_macros += [('HAVE_RUBBERBAND', 1)] if 'fftw3f' in ext.libraries: ext.define_macros += [('HAVE_FFTW3F', 1)] ext.define_macros += [('HAVE_FFTW3', 1)] diff --git a/python/tests/test_pitchshift.py b/python/tests/test_pitchshift.py new file mode 100755 index 00000000..4d3dd3f4 --- /dev/null +++ b/python/tests/test_pitchshift.py @@ -0,0 +1,104 @@ +#! /usr/bin/env python + +from numpy.testing import TestCase +from _tools import parametrize, skipTest +import numpy as np +import aubio + +class aubio_pitchshift(TestCase): + + def setUp(self): + try: + self.o = aubio.pitchshift(hop_size = 128) + except RuntimeError as e: + self.skipTest("creating aubio.pitchshift {}".format(e)) + + def test_default_creation(self): + self.assertEqual(self.o.get_pitchscale(), 1) + self.assertEqual(self.o.get_transpose(), 0) + + def test_on_zeros(self): + test_length = self.o.hop_size * 100 + read = 0 + # test on zeros + vec = aubio.fvec(self.o.hop_size) + transpose_range = 24 + while read < test_length: + # transpose the samples + out = self.o(vec) + self.assertTrue((out == 0).all()) + # position in the file (between 0. and 1.) + percent_read = read / float(test_length) + # variable transpose rate (in semitones) + transpose = 2 * transpose_range * percent_read - transpose_range + # set transpose rate + self.o.set_transpose(transpose) + read += len(vec) + + def test_on_ones(self): + test_length = self.o.hop_size * 100 + read = 0 + # test on zeros + vec = aubio.fvec(self.o.hop_size) + 1 + transpose_range = 1.24 + while read < test_length: + # transpose the samples + out = self.o(vec) + # position in the file (between 0. and 1.) + percent_read = read / float(test_length) + # variable transpose rate (in semitones) + transpose = 2 * transpose_range * percent_read - transpose_range + # set transpose rate + self.o.set_transpose(transpose) + read += len(vec) + + def test_transpose_too_high(self): + with self.assertRaises(ValueError): + self.o.set_transpose(24.3) + + def test_transpose_too_low(self): + with self.assertRaises(ValueError): + self.o.set_transpose(-24.3) + +class aubio_pitchshift_wrong_params(TestCase): + + def test_wrong_transpose(self): + with self.assertRaises(RuntimeError): + aubio.pitchshift("default", -123) + +class Test_aubio_pitchshift_testruns(object): + + run_args = ['mode', 'pitchscale', 'hop_size', 'samplerate'] + run_values = [ + ("default", 1.2, 128, 44100), + ("crispness:0", 0.43, 64, 8000), + ("crispness:3", 0.53, 256, 8000), + ("crispness:3", 1.53, 512, 8000), + ("crispness:6", 2.3, 4096, 192000), + ] + + @parametrize(run_args, run_values) + def test_run_with_params(self, mode, pitchscale, hop_size, samplerate): + try: + self.o = aubio.pitchshift(mode, pitchscale, hop_size, samplerate) + except RuntimeError as e: + skipTest("failed creating pitchshift ({})".format(e)) + test_length = self.o.hop_size * 50 + read = 0 + # test on random + vec = np.random.rand(self.o.hop_size).astype(aubio.float_type) + transpose_range = self.o.get_transpose() + while read < test_length: + # transpose the samples + out = self.o(vec) + # position in the file (between 0. and 1.) + percent_read = read / float(test_length) + # variable transpose rate (in semitones) + transpose = transpose_range - 2 * transpose_range * percent_read + # set transpose rate + self.o.set_transpose(transpose) + read += len(vec) + +if __name__ == '__main__': + from _tools import run_module_suite + run_module_suite() diff --git a/scripts/build_android b/scripts/build_android index e1e790cd..37cfd35e 100755 --- a/scripts/build_android +++ b/scripts/build_android @@ -1,4 +1,4 @@ -#! /bin/bash +#! /usr/bin/env bash set -e set -x diff --git a/scripts/build_apple_frameworks b/scripts/build_apple_frameworks index 9cb66610..17c8bc85 100755 --- a/scripts/build_apple_frameworks +++ b/scripts/build_apple_frameworks @@ -14,7 +14,7 @@ mkdir -p "$OUTPUTDIR" #VERSION+=+$(git log --pretty=format:"%h" -1) CFLAGS="-Werror -Os" -WAFCONF="--disable-sndfile --disable-avcodec --disable-samplerate --enable-fat" # --disable-memcpy --disable-accelerate" +WAFCONF="--nodeps --enable-fat" # --disable-memcpy --disable-accelerate" export VERSION @@ -45,19 +45,6 @@ function create_framework() { popd } -function create_framework_fat() { - rm -rf $AUBIO_TMPDIR/framework-$3 - mkdir -p $AUBIO_TMPDIR/framework-$3/$1-$2.$3_framework/$1.framework - cp -pr COPYING README.md $AUBIO_TMPDIR/framework-$3/$1-$2.$3_framework - pushd $AUBIO_TMPDIR/framework-$3 - cp -pr $AUBIO_TMPDIR/framework-ios/$1-$2.ios_framework/$1.framework/Headers $1-$2.$3_framework/$1.framework - cp -pr $AUBIO_TMPDIR/framework-ios/$1-$2.ios_framework/$1.framework/Modules $1-$2.$3_framework/$1.framework - lipo $AUBIO_TMPDIR/framework-ios/$1-$2.ios_framework/$1.framework/$1 \ - $AUBIO_TMPDIR/framework-iosimulator/$1-$2.iosimulator_framework/$1.framework/$1 \ - -output $1-$2.$3_framework/$1.framework/$1 -create - popd -} - function create_framework_zip() { # create zip pushd $AUBIO_TMPDIR/framework-$3 @@ -72,7 +59,7 @@ set -e #./waf dist --verbose -for PLATFORM in darwin ios iosimulator +for PLATFORM in darwin ios iosimulator watchos watchsimulator do rm -rf $AUBIO_TMPDIR/dist-$PLATFORM WAF_OPTIONS="--verbose --destdir $AUBIO_TMPDIR/dist-$PLATFORM --with-target-platform $PLATFORM $WAFCONF" @@ -84,19 +71,18 @@ do create_framework $PACKAGE $VERSION $PLATFORM if [ $PLATFORM == 'darwin' ] then - # on darwin, build a .tar.bz2 of /usr and a .zip of aubio.framework + # on darwin, build a .tar.bz2 of /usr create_tarballs $PACKAGE $VERSION $PLATFORM - create_framework_zip $PACKAGE $VERSION $PLATFORM fi + + # build a .zip of aubio.framework + create_framework_zip $PACKAGE $VERSION $PLATFORM + + # clean up ./waf uninstall $WAF_OPTIONS done -# after both ios and iosimulator have been built -PLATFORM=iosuniversal -create_framework_fat $PACKAGE $VERSION $PLATFORM -create_framework_zip $PACKAGE $VERSION $PLATFORM - ./waf clean ./waf distclean diff --git a/scripts/build_emscripten b/scripts/build_emscripten index dd24cbcd..87d2f64a 100755 --- a/scripts/build_emscripten +++ b/scripts/build_emscripten @@ -1,4 +1,4 @@ -#! /bin/bash +#! /usr/bin/env bash function checkprog() { type $1 >/dev/null 2>&1 || { echo >&2 "$1 required but not found, aborting."; exit 1; } diff --git a/scripts/build_mingw b/scripts/build_mingw index b0a0d5a4..f68a0ef8 100755 --- a/scripts/build_mingw +++ b/scripts/build_mingw @@ -1,4 +1,4 @@ -#! /bin/bash +#! /usr/bin/env bash # This script cross compiles aubio for windows using mingw, four times: # diff --git a/scripts/get_waf.sh b/scripts/get_waf.sh index 5fdcea64..535b7c8f 100755 --- a/scripts/get_waf.sh +++ b/scripts/get_waf.sh @@ -1,12 +1,12 @@ -#! /bin/bash +#! /usr/bin/env bash set -e #set -x -WAFVERSION=2.0.17 +WAFVERSION=2.0.26 WAFTARBALL=waf-$WAFVERSION.tar.bz2 WAFURL=https://waf.io/$WAFTARBALL -WAFUPSTREAMKEY=https://gitlab.com/ita1024/waf/raw/master/utils/pubkey.asc +WAFUPSTREAMKEY=https://gitlab.com/ita1024/waf/-/raw/waf-$WAFVERSION/utils/pubkey.asc WAFBUILDDIR=`mktemp -d` @@ -48,7 +48,7 @@ fetchwaf buildwaf popd -cp -prv $WAFBUILDDIR/waf-$WAFVERSION/waf $PWD +cp -prv $WAFBUILDDIR/waf-$WAFVERSION/waf "$PWD" chmod +x waf cleanup diff --git a/src/aubio.h b/src/aubio.h index 73deb24c..3c9216d0 100644 --- a/src/aubio.h +++ b/src/aubio.h @@ -220,6 +220,8 @@ extern "C" #include "pitch/pitchfcomb.h" #include "pitch/pitchspecacf.h" #include "tempo/beattracking.h" +#include "effects/pitchshift.h" +#include "effects/timestretch.h" #include "utils/scale.h" #include "utils/hist.h" #endif diff --git a/src/aubio_priv.h b/src/aubio_priv.h index 5795f1e3..df56ca53 100644 --- a/src/aubio_priv.h +++ b/src/aubio_priv.h @@ -236,20 +236,31 @@ uint_t aubio_log(sint_t level, const char_t *fmt, ...); #define AUBIO_ERR(...) aubio_log(AUBIO_LOG_ERR, "AUBIO ERROR: " __VA_ARGS__) #define AUBIO_INF(...) aubio_log(AUBIO_LOG_INF, "AUBIO INFO: " __VA_ARGS__) #define AUBIO_MSG(...) aubio_log(AUBIO_LOG_MSG, __VA_ARGS__) -#define AUBIO_DBG(...) aubio_log(AUBIO_LOG_DBG, __VA_ARGS__) +#define _AUBIO_DBG(...) aubio_log(AUBIO_LOG_DBG, __VA_ARGS__) #define AUBIO_WRN(...) aubio_log(AUBIO_LOG_WRN, "AUBIO WARNING: " __VA_ARGS__) #else #define AUBIO_ERR(format, args...) aubio_log(AUBIO_LOG_ERR, "AUBIO ERROR: " format , ##args) #define AUBIO_INF(format, args...) aubio_log(AUBIO_LOG_INF, "AUBIO INFO: " format , ##args) #define AUBIO_MSG(format, args...) aubio_log(AUBIO_LOG_MSG, format , ##args) -#define AUBIO_DBG(format, args...) aubio_log(AUBIO_LOG_DBG, format , ##args) +#define _AUBIO_DBG(format, args...) aubio_log(AUBIO_LOG_DBG, format , ##args) #define AUBIO_WRN(format, args...) aubio_log(AUBIO_LOG_WRN, "AUBIO WARNING: " format, ##args) #endif +#ifdef DEBUG +#define AUBIO_DBG _AUBIO_DBG +#else +// disable debug output +#ifdef HAVE_C99_VARARGS_MACROS +#define AUBIO_DBG(...) {} +#else +#define AUBIO_DBG(format, args...) {} +#endif +#endif + #define AUBIO_ERROR AUBIO_ERR #define AUBIO_QUIT(_s) exit(_s) -#define AUBIO_SPRINTF sprintf +#define AUBIO_SNPRINTF snprintf #define AUBIO_MAX_SAMPLERATE (192000*8) #define AUBIO_MAX_CHANNELS 1024 @@ -330,7 +341,7 @@ uint_t aubio_log(sint_t level, const char_t *fmt, ...); #define isnan _isnan #endif -#if !defined(_MSC_VER) +#if !defined(_WIN32) #define AUBIO_STRERROR(errno,buf,len) strerror_r(errno, buf, len) #else #define AUBIO_STRERROR(errno,buf,len) strerror_s(buf, len, errno) diff --git a/src/effects/pitchshift.h b/src/effects/pitchshift.h new file mode 100644 index 00000000..808ac602 --- /dev/null +++ b/src/effects/pitchshift.h @@ -0,0 +1,127 @@ +/* + Copyright (C) 2016 Paul Brossier + + This file is part of aubio. + + aubio is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + aubio is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with aubio. If not, see . + +*/ + +#ifndef AUBIO_PITCHSHIFT_H +#define AUBIO_PITCHSHIFT_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** \file + + Pitch shifting object + + ::aubio_pitchshift_t can be used to transpose a stream of blocks of frames. + + \example effects/test-pitchshift.c + +*/ + +/** pitch shifting object */ +typedef struct _aubio_pitchshift_t aubio_pitchshift_t; + +/** execute pitch shifting on an input signal frame + + \param o pitch shifting object as returned by new_aubio_pitchshift() + \param in input signal of size [hop_size] + \param out output pitch candidates of size [1] + +*/ +void aubio_pitchshift_do (aubio_pitchshift_t * o, const fvec_t * in, + fvec_t * out); + +/** deletion of the pitch shifting object + + \param o pitch shifting object as returned by new_aubio_pitchshift() + +*/ +void del_aubio_pitchshift (aubio_pitchshift_t * o); + +/** creation of the pitch shifting object + + \param method set pitch shifting algorithm ("default") + \param transpose initial pitch transposition + \param hop_size step size between two consecutive analysis instant + \param samplerate sampling rate of the signal + + \return newly created ::aubio_pitchshift_t + +*/ +aubio_pitchshift_t *new_aubio_pitchshift (const char_t * method, + smpl_t transpose, uint_t hop_size, uint_t samplerate); + +/** get the latency of the pitch shifting object, in samples + + \param o pitch shifting object as returned by ::new_aubio_pitchshift() + + \return latency of the pitch shifting object in samples + +*/ +uint_t aubio_pitchshift_get_latency (aubio_pitchshift_t * o); + +/** set the pitch scale of the pitch shifting object + + \param o pitch shifting object as returned by new_aubio_pitchshift() + \param pitchscale new pitch scale of the pitch shifting object + + pitchscale is a frequency ratio. It should be in the range [0.25, 4]. + + \return 0 if successfull, non-zero otherwise + +*/ +uint_t aubio_pitchshift_set_pitchscale (aubio_pitchshift_t * o, + smpl_t pitchscale); + +/** get the pitchscale of the pitch shifting object + + \param o pitch shifting object as returned by ::new_aubio_pitchshift() + + \return pitchscale of the pitch shifting object + +*/ +smpl_t aubio_pitchshift_get_pitchscale (aubio_pitchshift_t * o); + +/** set the transposition of the pitch shifting object, in semitones + + \param o pitch shifting object as returned by new_aubio_pitchshift() + \param transpose new pitch transposition of the pitch shifting object, + expressed in semitones (should be in the range [-24;+24]) + + \return 0 if successfull, non-zero otherwise + +*/ +uint_t aubio_pitchshift_set_transpose (aubio_pitchshift_t * o, + smpl_t transpose); + +/** get the transposition of the pitch shifting object, in semitones + + \param o pitch shifting object as returned by ::new_aubio_pitchshift() + + \return transposition of the pitch shifting object, in semitones + +*/ +smpl_t aubio_pitchshift_get_transpose (aubio_pitchshift_t * o); + +#ifdef __cplusplus +} +#endif + +#endif /* AUBIO_PITCHSHIFT_H */ diff --git a/src/effects/pitchshift_dummy.c b/src/effects/pitchshift_dummy.c new file mode 100644 index 00000000..ed16ca00 --- /dev/null +++ b/src/effects/pitchshift_dummy.c @@ -0,0 +1,73 @@ +/* + Copyright (C) 2016 Paul Brossier + + This file is part of aubio. + + aubio is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + aubio is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with aubio. If not, see . + +*/ + +#include "aubio_priv.h" + +#ifndef HAVE_RUBBERBAND + +#include "fvec.h" +#include "effects/pitchshift.h" + +// TODO fallback pitch shifting implementation + +struct _aubio_pitchshift_t +{ + void *dummy; +}; + +void aubio_pitchshift_do (aubio_pitchshift_t * o UNUSED, const fvec_t * in UNUSED, + fvec_t * out UNUSED) { +} + +void del_aubio_pitchshift (aubio_pitchshift_t * o UNUSED) { +} + +aubio_pitchshift_t *new_aubio_pitchshift (const char_t * method UNUSED, + smpl_t pitchscale UNUSED, uint_t hop_size UNUSED, uint_t samplerate UNUSED) +{ + AUBIO_ERR ("aubio was not compiled with rubberband\n"); + return NULL; +} + +uint_t aubio_pitchshift_set_pitchscale (aubio_pitchshift_t * o UNUSED, smpl_t pitchscale UNUSED) +{ + return AUBIO_FAIL; +} + +smpl_t aubio_pitchshift_get_pitchscale (aubio_pitchshift_t * o UNUSED) +{ + return 1.; +} + +uint_t aubio_pitchshift_set_transpose (aubio_pitchshift_t * o UNUSED, smpl_t transpose UNUSED) { + return AUBIO_FAIL; +} + +smpl_t aubio_pitchshift_get_transpose (aubio_pitchshift_t * o UNUSED) { + return 0.; +} + +uint_t aubio_pitchshift_get_latency (aubio_pitchshift_t * o UNUSED) { + return 0.; +} + +// end of dummy implementation + +#endif /* HAVE_RUBBERBAND */ diff --git a/src/effects/pitchshift_rubberband.c b/src/effects/pitchshift_rubberband.c new file mode 100644 index 00000000..1211dca8 --- /dev/null +++ b/src/effects/pitchshift_rubberband.c @@ -0,0 +1,162 @@ +/* + Copyright (C) 2016 Paul Brossier + + This file is part of aubio. + + aubio is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + aubio is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with aubio. If not, see . + +*/ + +#include "aubio_priv.h" + +#ifdef HAVE_RUBBERBAND + +#include "fvec.h" +#include "effects/pitchshift.h" + +#include + +/** generic pitch shifting structure */ +struct _aubio_pitchshift_t +{ + uint_t samplerate; /**< samplerate */ + uint_t hopsize; /**< hop size */ + smpl_t pitchscale; /**< pitch scale */ + + RubberBandState rb; + RubberBandOptions rboptions; +}; + +extern RubberBandOptions aubio_get_rubberband_opts(const char_t *mode); + +aubio_pitchshift_t * +new_aubio_pitchshift (const char_t * mode, + smpl_t transpose, uint_t hopsize, uint_t samplerate) +{ + aubio_pitchshift_t *p = AUBIO_NEW (aubio_pitchshift_t); + p->samplerate = samplerate; + p->hopsize = hopsize; + p->pitchscale = 1.; + p->rb = NULL; + if ((sint_t)hopsize <= 0) { + AUBIO_ERR("pitchshift: hop_size should be >= 0, got %d\n", hopsize); + goto beach; + } + if ((sint_t)samplerate <= 0) { + AUBIO_ERR("pitchshift: samplerate should be >= 0, got %d\n", samplerate); + goto beach; + } + + p->rboptions = aubio_get_rubberband_opts(mode); + if (p->rboptions < 0) { + AUBIO_ERR("pitchshift: unknown pitch shifting method %s\n", mode); + goto beach; + } + + //AUBIO_MSG("pitchshift: using pitch shifting method %s\n", mode); + + p->rb = rubberband_new(samplerate, 1, p->rboptions, 1., p->pitchscale); + rubberband_set_max_process_size(p->rb, p->hopsize); + //rubberband_set_debug_level(p->rb, 10); + + if (aubio_pitchshift_set_transpose(p, transpose)) goto beach; + +#if 1 + // warm up rubber band + unsigned int latency = MAX(p->hopsize, rubberband_get_latency(p->rb)); + int available = rubberband_available(p->rb); + fvec_t *zeros = new_fvec(p->hopsize); + while (available <= (int)latency) { + rubberband_process(p->rb, + (const float* const*)&(zeros->data), p->hopsize, 0); + available = rubberband_available(p->rb); + } + del_fvec(zeros); +#endif + + return p; + +beach: + del_aubio_pitchshift(p); + return NULL; +} + +void +del_aubio_pitchshift (aubio_pitchshift_t * p) +{ + if (p->rb) { + rubberband_delete(p->rb); + } + AUBIO_FREE (p); +} + +uint_t aubio_pitchshift_get_latency (aubio_pitchshift_t * p) { + return rubberband_get_latency(p->rb); +} + +uint_t +aubio_pitchshift_set_pitchscale (aubio_pitchshift_t * p, smpl_t pitchscale) +{ + if (pitchscale >= 0.25 && pitchscale <= 4.) { + p->pitchscale = pitchscale; + rubberband_set_pitch_scale(p->rb, p->pitchscale); + return AUBIO_OK; + } else { + AUBIO_ERR("pitchshift: could not set pitchscale to '%f'," + " should be in the range [0.25, 4.].\n", pitchscale); + return AUBIO_FAIL; + } +} + +smpl_t +aubio_pitchshift_get_pitchscale (aubio_pitchshift_t * p) +{ + return p->pitchscale; +} + +uint_t +aubio_pitchshift_set_transpose(aubio_pitchshift_t * p, smpl_t transpose) +{ + if (transpose >= -24. && transpose <= 24.) { + smpl_t pitchscale = POW(2., transpose / 12.); + return aubio_pitchshift_set_pitchscale(p, pitchscale); + } else { + AUBIO_ERR("pitchshift: could not set transpose to '%f'," + " should be in the range [-24; 24.].\n", transpose); + return AUBIO_FAIL; + } +} + +smpl_t +aubio_pitchshift_get_transpose(aubio_pitchshift_t * p) +{ + return 12. * LOG(p->pitchscale) / LOG(2.0); +} + +void +aubio_pitchshift_do (aubio_pitchshift_t * p, const fvec_t * in, fvec_t * out) +{ + // third parameter is always 0 since we are never expecting a final frame + rubberband_process(p->rb, (const float* const*)&(in->data), p->hopsize, 0); + if (rubberband_available(p->rb) >= (int)p->hopsize) { + rubberband_retrieve(p->rb, (float* const*)&(out->data), p->hopsize); + } else { + AUBIO_WRN("pitchshift: catching up with zeros" + ", only %d available, needed: %d, current pitchscale: %f\n", + rubberband_available(p->rb), p->hopsize, p->pitchscale); + fvec_zeros(out); + } +} + +#endif diff --git a/src/effects/rubberband_utils.c b/src/effects/rubberband_utils.c new file mode 100644 index 00000000..6e904c41 --- /dev/null +++ b/src/effects/rubberband_utils.c @@ -0,0 +1,150 @@ + + +#include "aubio_priv.h" + +#ifdef HAVE_RUBBERBAND + +#include + +// check rubberband is 1.8.1, warn if 1.3 +#if !((RUBBERBAND_API_MAJOR_VERSION >= 2) && \ + (RUBBERBAND_API_MINOR_VERSION >= 5)) +#warning RubberBandOptionDetectorSoft not available, \ + please upgrade rubberband to version 1.8.1 or higher +#define RubberBandOptionDetectorSoft 0x00000000 +#endif + +#include +#include +#include + +char_t** aubio_split_str(const char_t* str, const char_t sep) { + char_t** result = 0; + uint_t count = 0; + char_t input[PATH_MAX]; + char_t* in_ptr = input; + char_t* last_sep = 0; + char_t delim[2]; delim[0] = sep; delim[1] = 0; + + strncpy(input, str, PATH_MAX); + input[PATH_MAX - 1] = '\0'; + + // count number of elements + while (*in_ptr) { + if (sep == *in_ptr) { + count++; + last_sep = in_ptr; + } + in_ptr++; + } + // add space for trailing token. + count += last_sep < (input + strlen(input) - 1); + count++; + + result = AUBIO_ARRAY(char_t*, count); + if (result) { + uint_t idx = 0; + char_t* params = strtok(input, delim); + while (params) { + // make sure we don't got in the wild + if (idx >= count) + break; + *(result + idx++) = strdup(params); + params = strtok(0, delim); + } + // add null string at the end if needed + if (idx < count - 1) + *(result + idx) = 0; + } + return result; +} + +RubberBandOptions aubio_get_rubberband_opts(const char_t *mode) +{ + RubberBandOptions rboptions = RubberBandOptionProcessRealTime; + + if ( strcmp(mode,"crispness:0") == 0 ) { + rboptions |= RubberBandOptionTransientsSmooth; + rboptions |= RubberBandOptionWindowLong; + rboptions |= RubberBandOptionPhaseIndependent; + } else if ( strcmp(mode, "crispness:1") == 0 ) { + rboptions |= RubberBandOptionDetectorSoft; + rboptions |= RubberBandOptionTransientsSmooth; + rboptions |= RubberBandOptionWindowLong; + rboptions |= RubberBandOptionPhaseIndependent; + } else if ( strcmp(mode, "crispness:2") == 0 ) { + rboptions |= RubberBandOptionTransientsSmooth; + rboptions |= RubberBandOptionPhaseIndependent; + } else if ( strcmp(mode, "crispness:3") == 0 ) { + rboptions |= RubberBandOptionTransientsSmooth; + } else if ( strcmp(mode, "crispness:4") == 0 ) { + // same as "default" + } else if ( strcmp(mode, "crispness:5") == 0 ) { + rboptions |= RubberBandOptionTransientsCrisp; + } else if ( strcmp(mode, "crispness:6") == 0 ) { + rboptions |= RubberBandOptionTransientsCrisp; + rboptions |= RubberBandOptionWindowShort; + rboptions |= RubberBandOptionPhaseIndependent; + } else if ( strcmp(mode, "default") == 0 ) { + // nothing to do + } else { + // attempt to parse a list of options, separated with ',' + char_t **params = aubio_split_str(mode, ':'); + uint_t i = 0; + if (!params || !params[0]) { + // memory failure occurred or empty string was passed + AUBIO_ERR("rubberband_utils: failed parsing options\n"); + rboptions = -1; + } + while (*(params + i) != NULL) { + if ( strcmp(params[i], "ProcessOffline" ) == 0 ) { + rboptions = RubberBandOptionProcessOffline; + // TODO: add wrapper to rb study(smpl_t *input, uint_t write) + AUBIO_ERR("rubberband_utils: RubberBandOptionProcessOffline is not available\n"); + rboptions = -1; + } + else if ( strcmp(params[i], "ProcessRealTime" ) == 0 ) rboptions |= RubberBandOptionProcessRealTime; + else if ( strcmp(params[i], "StretchElastic" ) == 0 ) rboptions |= RubberBandOptionStretchElastic; + else if ( strcmp(params[i], "StretchPrecise" ) == 0 ) rboptions |= RubberBandOptionStretchPrecise; + else if ( strcmp(params[i], "TransientsCrisp" ) == 0 ) rboptions |= RubberBandOptionTransientsCrisp; + else if ( strcmp(params[i], "TransientsMixed" ) == 0 ) rboptions |= RubberBandOptionTransientsMixed; + else if ( strcmp(params[i], "TransientsSmooth" ) == 0 ) rboptions |= RubberBandOptionTransientsSmooth; + else if ( strcmp(params[i], "DetectorCompound" ) == 0 ) rboptions |= RubberBandOptionDetectorCompound; + else if ( strcmp(params[i], "DetectorPercussive" ) == 0 ) rboptions |= RubberBandOptionDetectorPercussive; + else if ( strcmp(params[i], "DetectorSoft" ) == 0 ) rboptions |= RubberBandOptionDetectorSoft; + else if ( strcmp(params[i], "PhaseLaminar" ) == 0 ) rboptions |= RubberBandOptionPhaseLaminar; + else if ( strcmp(params[i], "PhaseIndependent" ) == 0 ) rboptions |= RubberBandOptionPhaseIndependent; + else if ( strcmp(params[i], "ThreadingAuto" ) == 0 ) rboptions |= RubberBandOptionThreadingAuto; + else if ( strcmp(params[i], "ThreadingNever" ) == 0 ) rboptions |= RubberBandOptionThreadingNever; + else if ( strcmp(params[i], "ThreadingAlways" ) == 0 ) rboptions |= RubberBandOptionThreadingAlways; + else if ( strcmp(params[i], "WindowStandard" ) == 0 ) rboptions |= RubberBandOptionWindowStandard; + else if ( strcmp(params[i], "WindowShort" ) == 0 ) rboptions |= RubberBandOptionWindowShort; + else if ( strcmp(params[i], "WindowLong" ) == 0 ) rboptions |= RubberBandOptionWindowLong; + else if ( strcmp(params[i], "SmoothingOff" ) == 0 ) rboptions |= RubberBandOptionSmoothingOff; + else if ( strcmp(params[i], "SmoothingOn" ) == 0 ) rboptions |= RubberBandOptionSmoothingOn; + else if ( strcmp(params[i], "FormantShifted" ) == 0 ) rboptions |= RubberBandOptionFormantShifted; + else if ( strcmp(params[i], "FormantPreserved" ) == 0 ) rboptions |= RubberBandOptionFormantPreserved; + else if ( strcmp(params[i], "PitchHighSpeed" ) == 0 ) rboptions |= RubberBandOptionPitchHighSpeed; + else if ( strcmp(params[i], "PitchHighQuality" ) == 0 ) rboptions |= RubberBandOptionPitchHighQuality; + else if ( strcmp(params[i], "PitchHighConsistency" ) == 0 ) rboptions |= RubberBandOptionPitchHighConsistency; + else if ( strcmp(params[i], "ChannelsApart" ) == 0 ) rboptions |= RubberBandOptionChannelsApart; + else if ( strcmp(params[i], "ChannelsTogether" ) == 0 ) rboptions |= RubberBandOptionChannelsTogether; + else { + AUBIO_ERR("rubberband_utils: did not understand option '%s', should be one of: " + "StretchElastic|StretchPrecise, TransientsCrisp|TransientsMixed|TransientsSmooth, " + "DetectorCompound|DetectorPercussive|DetectorSoft, PhaseLaminar|PhaseIndependent, " + "ThreadingAuto|ThreadingNever|ThreadingAlways, WindowStandard|WindowLong|WindowShort, " + "SmoothingOn|SmoothingOff, FormantShifted|FormantPreserved, " + "PitchHighSpeed|PitchHighQuality|PitchHighConsistency, ChannelsApart|ChannelsTogether\n" + , params[i]); + rboptions = -1; + } + AUBIO_FREE(params[i]); + i++; + } + AUBIO_FREE(params); + } + return rboptions; +} + +#endif diff --git a/src/effects/timestretch.h b/src/effects/timestretch.h new file mode 100644 index 00000000..b1454d01 --- /dev/null +++ b/src/effects/timestretch.h @@ -0,0 +1,198 @@ +/* + Copyright (C) 2016 Paul Brossier + + This file is part of aubio. + + aubio is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + aubio is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with aubio. If not, see . + +*/ + +#ifndef AUBIO_TIMESTRETCH_H +#define AUBIO_TIMESTRETCH_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** \file + + time stretching object + + ::aubio_timestretch_t can be used to open a source file, read samples from + it, time-stretch them, and write out the modified samples. + + The time-stretching factor can be changed at any time using + aubio_timestretch_set_stretch(). + + A transposition can also be applied and changed at any time with + aubio_timestretch_set_transpose(). + + \example effects/test-timestretch.c + +*/ + +/** time stretching object */ +typedef struct _aubio_timestretch_t aubio_timestretch_t; + +/** execute time stretching on an input signal frame + + \param o time stretching object as returned by new_aubio_timestretch() + \param out timestretched output of size [hop_size] + \param read number of frames actually wrote out + +*/ +void aubio_timestretch_do (aubio_timestretch_t * o, fvec_t * out, + uint_t * read); + +/** deletion of the time stretching object + + \param o time stretching object as returned by new_aubio_timestretch() + +*/ +void del_aubio_timestretch (aubio_timestretch_t * o); + +/** creation of the time stretching object + + \param method time stretching algorithm ("default") + \param stretch initial time stretching factor + \param hop_size block size at which the frames should be produced + \param samplerate sampling rate of the signal + + \return newly created ::aubio_timestretch_t + +*/ +aubio_timestretch_t *new_aubio_timestretch (const char_t * method, + smpl_t stretch, uint_t hop_size, uint_t samplerate); + +/** push length samples from in to time stretching object + + \param o time stretching object as returned by ::new_aubio_timestretch() + \param in input vector of new samples to push to time stretching object + \param length number of new samples to push from input vector + + \return number of currently available samples + + */ +sint_t aubio_timestretch_push(aubio_timestretch_t * o, fvec_t *in, + uint_t length); + +/** get number of currently available samples from time stretching object + + \param o time stretching object as returned by ::new_aubio_timestretch() + + \return number of currently available samples + + */ +sint_t aubio_timestretch_get_available(aubio_timestretch_t * o); + +/** get the latency of the time stretching object, in samples + + \param o time stretching object as returned by ::new_aubio_timestretch() + + \return latency of the time stretching object in samples + +*/ +uint_t aubio_timestretch_get_latency (aubio_timestretch_t * o); + +/** get the samplerate of the time stretching object + + Call after new_aubio_timestretch() was called with 0 to match the original + samplerate of the input file. + + \param o time stretching object as returned by new_aubio_timestretch() + + \return samplerate of the time stretching object + + */ +uint_t aubio_timestretch_get_samplerate (aubio_timestretch_t * o); + +/** set the stretching ratio of the time stretching object + + \param o time stretching object as returned by new_aubio_timestretch() + \param stretch new time stretching ratio of the time stretching object + (should be in the range [0.025; 10.]) + + \return 0 if successfull, non-zero otherwise + +*/ +uint_t aubio_timestretch_set_stretch (aubio_timestretch_t * o, smpl_t stretch); + +/** get the transposition of the time stretching object, in semitones + + \param o time stretching object as returned by ::new_aubio_timestretch() + + \return time stretching ratio of the time stretching object, in the range + [0.025; 10.] + +*/ +smpl_t aubio_timestretch_get_stretch (aubio_timestretch_t * o); + +/** set the pitch scale of the time stretching object + + \param o time stretching object as returned by new_aubio_timestretch() + \param pitchscale new pitch scale of the time stretching object + + pitchscale is a frequency ratio. It should be in the range [0.25, 4]. + + \return 0 if successfull, non-zero otherwise + +*/ +uint_t aubio_timestretch_set_pitchscale (aubio_timestretch_t * o, + smpl_t pitchscale); + +/** get the pitchscale of the time stretching object + + \param o time stretching object as returned by ::new_aubio_timestretch() + + \return pitchscale of the time stretching object + +*/ +smpl_t aubio_timestretch_get_pitchscale (aubio_timestretch_t * o); + +/** set the transposition of the time stretching object, in semitones + + \param o time stretching object as returned by new_aubio_timestretch() + + \param transpose new pitch transposition of the time stretching object, + expressed in semitones (should be in the range [-24;+24]) + + \return 0 if successfull, non-zero otherwise + +*/ +uint_t aubio_timestretch_set_transpose (aubio_timestretch_t * o, + smpl_t transpose); + +/** get the transposition of the time stretching object, in semitones + + \param o time stretching object as returned by ::new_aubio_timestretch() + + \return transposition of the time stretching object, in semitones + +*/ +smpl_t aubio_timestretch_get_transpose (aubio_timestretch_t * o); + +/** reset the time stretching object + + \param o time stretching object as returned by ::new_aubio_timestretch() + + \return 0 on success, non-zero otherwise + +*/ +uint_t aubio_timestretch_reset(aubio_timestretch_t * o); + +#ifdef __cplusplus +} +#endif + +#endif /* AUBIO_TIMESTRETCH_H */ diff --git a/src/effects/timestretch_dummy.c b/src/effects/timestretch_dummy.c new file mode 100644 index 00000000..adb80554 --- /dev/null +++ b/src/effects/timestretch_dummy.c @@ -0,0 +1,102 @@ +/* + Copyright (C) 2016 Paul Brossier + + This file is part of aubio. + + aubio is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + aubio is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with aubio. If not, see . + +*/ + +#include "aubio_priv.h" + +#ifndef HAVE_RUBBERBAND + +#include "fvec.h" +#include "effects/timestretch.h" + +// TODO fallback time stretching implementation + +struct _aubio_timestretch_t +{ + void *dummy; +}; + +void +aubio_timestretch_do (aubio_timestretch_t * o UNUSED, fvec_t * out UNUSED, + uint_t * read UNUSED) +{ +} + +void del_aubio_timestretch (aubio_timestretch_t * o UNUSED) { +} + +aubio_timestretch_t * +new_aubio_timestretch (const char_t * method UNUSED, + smpl_t pitchscale UNUSED, uint_t hop_size UNUSED, uint_t samplerate UNUSED) +{ + AUBIO_ERR ("timestretch: aubio was not compiled with rubberband\n"); + return NULL; +} + +uint_t aubio_timestretch_set_stretch (aubio_timestretch_t * o UNUSED, smpl_t stretch UNUSED) +{ + return AUBIO_FAIL; +} + +smpl_t aubio_timestretch_get_stretch (aubio_timestretch_t * o UNUSED) +{ + return 1.; +} + +uint_t aubio_timestretch_set_pitchscale (aubio_timestretch_t * o UNUSED, smpl_t pitchscale UNUSED) +{ + return AUBIO_FAIL; +} + +uint_t aubio_timestretch_get_samplerate (aubio_timestretch_t * o UNUSED) { + return 0; +} + +smpl_t aubio_timestretch_get_pitchscale (aubio_timestretch_t * o UNUSED) +{ + return 1.; +} + +uint_t aubio_timestretch_set_transpose (aubio_timestretch_t * o UNUSED, smpl_t transpose UNUSED) { + return AUBIO_FAIL; +} + +smpl_t aubio_timestretch_get_transpose (aubio_timestretch_t * o UNUSED) { + return 0.; +} + +uint_t aubio_timestretch_get_latency (aubio_timestretch_t * o UNUSED) { + return 0.; +} + +uint_t aubio_timestretch_reset(aubio_timestretch_t *o UNUSED) { + return AUBIO_FAIL; +} + +sint_t aubio_timestretch_push(aubio_timestretch_t * o UNUSED, fvec_t * in + UNUSED, uint_t length UNUSED) { + return AUBIO_FAIL; +} + +sint_t aubio_timestretch_get_available(aubio_timestretch_t * o UNUSED) { + return AUBIO_FAIL; +} +// end of dummy implementation + +#endif /* HAVE_RUBBERBAND */ diff --git a/src/effects/timestretch_rubberband.c b/src/effects/timestretch_rubberband.c new file mode 100644 index 00000000..d2144906 --- /dev/null +++ b/src/effects/timestretch_rubberband.c @@ -0,0 +1,261 @@ +/* + Copyright (C) 2016 Paul Brossier + + This file is part of aubio. + + aubio is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + aubio is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with aubio. If not, see . + +*/ + +#include "aubio_priv.h" + +#ifdef HAVE_RUBBERBAND + +#include "fvec.h" +#include "fmat.h" +#include "io/source.h" +#include "effects/timestretch.h" + +#include + +#define MIN_STRETCH_RATIO 0.025 +#define MAX_STRETCH_RATIO 40. + +#define HAVE_THREADS 1 +#if 0 +#undef HAVE_THREADS +#endif + +#ifdef HAVE_THREADS +#include +#endif + +/** generic time stretching structure */ +struct _aubio_timestretch_t +{ + uint_t samplerate; /**< samplerate */ + uint_t hopsize; /**< hop size */ + smpl_t stretchratio; /**< time ratio */ + smpl_t pitchscale; /**< pitch scale */ + + RubberBandState rb; + RubberBandOptions rboptions; +}; + +extern RubberBandOptions aubio_get_rubberband_opts(const char_t *mode); + +//static void aubio_timestretch_warmup (aubio_timestretch_t * p); + +aubio_timestretch_t * +new_aubio_timestretch (const char_t * mode, smpl_t stretchratio, uint_t hopsize, + uint_t samplerate) +{ + aubio_timestretch_t *p = AUBIO_NEW (aubio_timestretch_t); + p->hopsize = hopsize; + p->pitchscale = 1.; + + if ((sint_t)hopsize <= 0) { + AUBIO_ERR("timestretch: hopsize should be > 0, got %d\n", hopsize); + goto beach; + } + + if ((sint_t)samplerate <= 0) { + AUBIO_ERR("timestretch: samplerate should be > 0, got %d\n", samplerate); + goto beach; + } + + if (stretchratio <= MAX_STRETCH_RATIO && stretchratio >= MIN_STRETCH_RATIO) { + p->stretchratio = stretchratio; + } else { + AUBIO_ERR("timestretch: stretchratio should be in the range [%.3f, %.3f], got %f\n", + MIN_STRETCH_RATIO, MAX_STRETCH_RATIO, stretchratio); + goto beach; + } + + p->rboptions = aubio_get_rubberband_opts(mode); + if (p->rboptions < 0) { + AUBIO_ERR("timestretch: unknown time stretching method %s\n", mode); + goto beach; + } + + p->rb = rubberband_new(samplerate, 1, p->rboptions, p->stretchratio, p->pitchscale); + if (!p->rb) goto beach; + + p->samplerate = samplerate; + + //aubio_timestretch_warmup(p); + + return p; + +beach: + del_aubio_timestretch(p); + return NULL; +} + +#if 0 +static void +aubio_timestretch_warmup (aubio_timestretch_t * p) +{ + // warm up rubber band + //AUBIO_WRN("timestretch: warming-up\n"); + unsigned int latency = MAX(p->hopsize, rubberband_get_latency(p->rb)); + fvec_t *input = new_fvec(p->hopsize); + while (aubio_timestretch_push(p, input, input->length) < (int)latency) { + //sint_t available = aubio_timestretch_get_available(p); + //AUBIO_WRN("timestretch: warmup got %d, latency: %d\n", available, latency); + } + del_fvec(input); +} +#endif + +void +del_aubio_timestretch (aubio_timestretch_t * p) +{ + if (p->rb) { + rubberband_delete(p->rb); + } + AUBIO_FREE (p); +} + +uint_t +aubio_timestretch_get_samplerate (aubio_timestretch_t * p) +{ + return p->samplerate; +} + +uint_t aubio_timestretch_get_latency (aubio_timestretch_t * p) { + return rubberband_get_latency(p->rb); +} + +uint_t +aubio_timestretch_set_stretch (aubio_timestretch_t * p, smpl_t stretch) +{ + if (!p->rb) { + AUBIO_ERR("timestretch: could not set stretch ratio," + " rubberband not created\n"); + return AUBIO_FAIL; + } + if (stretch >= MIN_STRETCH_RATIO && stretch <= MAX_STRETCH_RATIO) { + p->stretchratio = stretch; + rubberband_set_time_ratio(p->rb, 1./p->stretchratio); + return AUBIO_OK; + } else { + AUBIO_ERR("timestretch: could not set stretch ratio to '%f'," + " should be in the range [%.2f, %.2f].\n", stretch, + MIN_STRETCH_RATIO, MAX_STRETCH_RATIO); + return AUBIO_FAIL; + } +} + +smpl_t +aubio_timestretch_get_stretch (aubio_timestretch_t * p) +{ + return p->stretchratio; +} + +uint_t +aubio_timestretch_set_pitchscale (aubio_timestretch_t * p, smpl_t pitchscale) +{ + if (!p->rb) { + AUBIO_ERR("timestretch: could not set pitch scale," + " rubberband not created\n"); + return AUBIO_FAIL; + } + if (pitchscale >= 0.0625 && pitchscale <= 4.) { + p->pitchscale = pitchscale; + rubberband_set_pitch_scale(p->rb, p->pitchscale); + return AUBIO_OK; + } else { + AUBIO_ERR("timestretch: could not set pitchscale to '%f'," + " should be in the range [0.0625, 4.].\n", pitchscale); + return AUBIO_FAIL; + } +} + +smpl_t +aubio_timestretch_get_pitchscale (aubio_timestretch_t * p) +{ + return p->pitchscale; +} + +uint_t +aubio_timestretch_set_transpose(aubio_timestretch_t * p, smpl_t transpose) +{ + if (transpose >= -24. && transpose <= 24.) { + smpl_t pitchscale = POW(2., transpose / 12.); + return aubio_timestretch_set_pitchscale(p, pitchscale); + } else { + AUBIO_ERR("timestretch: could not set transpose to '%f'," + " should be in the range [-24; 24].\n", transpose); + return AUBIO_FAIL; + } +} + +smpl_t +aubio_timestretch_get_transpose(aubio_timestretch_t * p) +{ + return 12. * LOG(p->pitchscale) / LOG(2.0); +} + +sint_t +aubio_timestretch_push(aubio_timestretch_t *p, fvec_t *input, uint_t length) +{ + // push new samples to rubberband, return available + int available; + int eof = (input->length != length) ? 1 : 0; + rubberband_process(p->rb, (const float* const*)&(input->data), length, eof); + available = rubberband_available(p->rb); + //AUBIO_WRN("timestretch: processed %d, %d available, eof: %d\n", + // length, available, eof); + return available; +} + +sint_t +aubio_timestretch_get_available(aubio_timestretch_t *p) { + return rubberband_available(p->rb); +} + +void +aubio_timestretch_do(aubio_timestretch_t * p, fvec_t * out, uint_t * read) +{ + // now retrieve the samples and write them into out->data + int available = rubberband_available(p->rb); + if (available >= (int)out->length) { + rubberband_retrieve(p->rb, (float* const*)&(out->data), out->length); + *read = out->length; + } else if (available > 0) { + // this occurs each time the end of file is reached + //AUBIO_WRN("timestretch: short read\n"); + rubberband_retrieve(p->rb, (float* const*)&(out->data), available); + fvec_t zeros; zeros.length = out->length - available; zeros.data = out->data + available; + fvec_zeros(&zeros); + *read = available; + } else { + // this may occur if the previous was a short read available == hopsize + fvec_zeros(out); + *read = 0; + } +} + +uint_t +aubio_timestretch_reset(aubio_timestretch_t *p) +{ + uint_t err = AUBIO_OK; + if (p->rb) { + rubberband_reset(p->rb); + } + return err; +} + +#endif diff --git a/src/io/sink.c b/src/io/sink.c index 364a2c39..5c9561e8 100644 --- a/src/io/sink.c +++ b/src/io/sink.c @@ -53,8 +53,86 @@ struct _aubio_sink_t { del_aubio_sink_t s_del; }; +extern uint_t aubio_str_path_has_extension(const char_t *filename, + const char_t *pattern); + +#ifdef HAVE_VORBISENC +typedef struct _aubio_sink_vorbis_t aubio_sink_vorbis_t; +extern aubio_sink_vorbis_t * new_aubio_sink_vorbis(const char_t *uri, + uint_t samplerate); +extern void del_aubio_sink_vorbis (aubio_sink_vorbis_t *s); +extern uint_t aubio_sink_vorbis_open(aubio_sink_vorbis_t *s); +extern uint_t aubio_sink_vorbis_close(aubio_sink_vorbis_t *s); +extern uint_t aubio_sink_vorbis_preset_channels(aubio_sink_vorbis_t *s, + uint_t channels); +extern uint_t aubio_sink_vorbis_preset_samplerate(aubio_sink_vorbis_t *s, + uint_t samplerate); +extern uint_t aubio_sink_vorbis_get_channels(aubio_sink_vorbis_t *s); +extern uint_t aubio_sink_vorbis_get_samplerate(aubio_sink_vorbis_t *s); +extern void aubio_sink_vorbis_do(aubio_sink_vorbis_t *s, fvec_t* + write_data, uint_t write); +extern void aubio_sink_vorbis_do_multi(aubio_sink_vorbis_t *s, fmat_t* + write_data, uint_t write); +#endif /* HAVE_VORBISENC */ + +#ifdef HAVE_FLAC +typedef struct _aubio_sink_flac_t aubio_sink_flac_t; +extern aubio_sink_flac_t * new_aubio_sink_flac(const char_t *uri, + uint_t samplerate); +extern void del_aubio_sink_flac (aubio_sink_flac_t *s); +extern uint_t aubio_sink_flac_open(aubio_sink_flac_t *s); +extern uint_t aubio_sink_flac_close(aubio_sink_flac_t *s); +extern uint_t aubio_sink_flac_preset_channels(aubio_sink_flac_t *s, + uint_t channels); +extern uint_t aubio_sink_flac_preset_samplerate(aubio_sink_flac_t *s, + uint_t samplerate); +extern uint_t aubio_sink_flac_get_channels(aubio_sink_flac_t *s); +extern uint_t aubio_sink_flac_get_samplerate(aubio_sink_flac_t *s); +extern void aubio_sink_flac_do(aubio_sink_flac_t *s, fvec_t* + write_data, uint_t write); +extern void aubio_sink_flac_do_multi(aubio_sink_flac_t *s, fmat_t* + write_data, uint_t write); +#endif /* HAVE_FLAC */ + aubio_sink_t * new_aubio_sink(const char_t * uri, uint_t samplerate) { aubio_sink_t * s = AUBIO_NEW(aubio_sink_t); + +#ifdef HAVE_VORBISENC + // check if this uri could be for us + if (aubio_str_path_has_extension(uri, "ogg")) { + s->sink = (void *)new_aubio_sink_vorbis(uri, samplerate); + if (s->sink) { + s->s_do = (aubio_sink_do_t)(aubio_sink_vorbis_do); + s->s_do_multi = (aubio_sink_do_multi_t)(aubio_sink_vorbis_do_multi); + s->s_preset_samplerate = (aubio_sink_preset_samplerate_t)(aubio_sink_vorbis_preset_samplerate); + s->s_preset_channels = (aubio_sink_preset_channels_t)(aubio_sink_vorbis_preset_channels); + s->s_get_samplerate = (aubio_sink_get_samplerate_t)(aubio_sink_vorbis_get_samplerate); + s->s_get_channels = (aubio_sink_get_channels_t)(aubio_sink_vorbis_get_channels); + s->s_close = (aubio_sink_close_t)(aubio_sink_vorbis_close); + s->s_del = (del_aubio_sink_t)(del_aubio_sink_vorbis); + return s; + } + } +#endif /* HAVE_VORBISENC */ + +#ifdef HAVE_FLAC + // check if this uri could be for us + if (aubio_str_path_has_extension(uri, "flac")) { + s->sink = (void *)new_aubio_sink_flac(uri, samplerate); + if (s->sink) { + s->s_do = (aubio_sink_do_t)(aubio_sink_flac_do); + s->s_do_multi = (aubio_sink_do_multi_t)(aubio_sink_flac_do_multi); + s->s_preset_samplerate = (aubio_sink_preset_samplerate_t)(aubio_sink_flac_preset_samplerate); + s->s_preset_channels = (aubio_sink_preset_channels_t)(aubio_sink_flac_preset_channels); + s->s_get_samplerate = (aubio_sink_get_samplerate_t)(aubio_sink_flac_get_samplerate); + s->s_get_channels = (aubio_sink_get_channels_t)(aubio_sink_flac_get_channels); + s->s_close = (aubio_sink_close_t)(aubio_sink_flac_close); + s->s_del = (del_aubio_sink_t)(del_aubio_sink_flac); + return s; + } + } +#endif /* HAVE_FLAC */ + #ifdef HAVE_SINK_APPLE_AUDIO s->sink = (void *)new_aubio_sink_apple_audio(uri, samplerate); if (s->sink) { @@ -99,7 +177,9 @@ aubio_sink_t * new_aubio_sink(const char_t * uri, uint_t samplerate) { #endif /* HAVE_WAVWRITE */ #if !defined(HAVE_WAVWRITE) && \ !defined(HAVE_SNDFILE) && \ - !defined(HAVE_SINK_APPLE_AUDIO) + !defined(HAVE_SINK_APPLE_AUDIO) && \ + !defined(HAVE_VORBISENC) && \ + !defined(HAVE_FLAC) AUBIO_ERROR("sink: failed creating '%s' at %dHz (no sink built-in)\n", uri, samplerate); #endif del_aubio_sink(s); diff --git a/src/io/sink_apple_audio.c b/src/io/sink_apple_audio.c index c58a52c6..5792a4ff 100644 --- a/src/io/sink_apple_audio.c +++ b/src/io/sink_apple_audio.c @@ -38,6 +38,10 @@ char_t *getPrintableOSStatusError(char_t *str, OSStatus error); uint_t aubio_sink_apple_audio_open(aubio_sink_apple_audio_t *s); +uint_t aubio_str_extension_matches(const char_t *ext, + const char_t *pattern); +const char_t *aubio_str_get_extension(const char_t *filename); + #define MAX_SIZE 4096 // the maximum number of frames that can be written at a time void aubio_sink_apple_audio_write(aubio_sink_apple_audio_t *s, uint_t write); @@ -52,6 +56,7 @@ struct _aubio_sink_apple_audio_t { AudioBufferList bufferList; ExtAudioFileRef audioFile; bool async; + AudioFileTypeID fileType; }; aubio_sink_apple_audio_t * new_aubio_sink_apple_audio(const char_t * uri, uint_t samplerate) { @@ -70,6 +75,8 @@ aubio_sink_apple_audio_t * new_aubio_sink_apple_audio(const char_t * uri, uint_t s->samplerate = 0; s->channels = 0; + aubio_sink_apple_audio_preset_format(s, aubio_str_get_extension(uri)); + // zero samplerate given. do not open yet if ((sint_t)samplerate == 0) { return s; @@ -120,6 +127,74 @@ uint_t aubio_sink_apple_audio_preset_channels(aubio_sink_apple_audio_t *s, uint_ return AUBIO_OK; } +uint_t aubio_sink_apple_audio_preset_format(aubio_sink_apple_audio_t *s, + const char_t *fmt) +{ + if (aubio_str_extension_matches(fmt, "wav")) { + s->fileType = kAudioFileWAVEType; + } else if (aubio_str_extension_matches(fmt, "m4a") + || aubio_str_extension_matches(fmt, "mp4") ) { + // use alac for "mp4" and "m4a" + s->fileType = kAudioFileM4AType; + } else if (aubio_str_extension_matches(fmt, "aac") ) { + // only use lossy codec for "aac" + s->fileType = kAudioFileMPEG4Type; + } else if (aubio_str_extension_matches(fmt, "aiff") ) { + // only use lossy codec for "aac" + s->fileType = kAudioFileAIFFType; + } else { + s->fileType = kAudioFileWAVEType; + if (fmt && strnlen(fmt, PATH_MAX)) { + AUBIO_WRN("sink_apple_audio: could not guess format for %s," + " using default (wav)\n", s->path); + return AUBIO_FAIL; + } + } + return AUBIO_OK; +} + +static void aubio_sink_apple_audio_set_client_format(aubio_sink_apple_audio_t* s, + AudioStreamBasicDescription *clientFormat) +{ + memset(clientFormat, 0, sizeof(AudioStreamBasicDescription)); + // always set samplerate and channels first + clientFormat->mSampleRate = (Float64)(s->samplerate); + clientFormat->mChannelsPerFrame = s->channels; + + switch (s->fileType) { + case kAudioFileM4AType: + clientFormat->mFormatID = kAudioFormatAppleLossless; + break; + case kAudioFileMPEG4Type: + clientFormat->mFormatID = kAudioFormatMPEG4AAC; + clientFormat->mFormatFlags = kMPEG4Object_AAC_Main; + clientFormat->mFormatFlags |= kAppleLosslessFormatFlag_16BitSourceData; + clientFormat->mFramesPerPacket = 1024; + break; + case kAudioFileWAVEType: + clientFormat->mFormatID = kAudioFormatLinearPCM; + clientFormat->mFormatFlags = kAudioFormatFlagIsSignedInteger; + clientFormat->mFormatFlags |= kAudioFormatFlagIsPacked; + clientFormat->mBitsPerChannel = sizeof(short) * 8; + clientFormat->mFramesPerPacket = 1; + clientFormat->mBytesPerFrame = clientFormat->mBitsPerChannel * clientFormat->mChannelsPerFrame / 8; + clientFormat->mBytesPerPacket = clientFormat->mFramesPerPacket * clientFormat->mBytesPerFrame; + break; + case kAudioFileAIFFType: + clientFormat->mFormatID = kAudioFormatLinearPCM; + clientFormat->mFormatFlags = kAudioFormatFlagIsSignedInteger; + clientFormat->mFormatFlags |= kAudioFormatFlagIsPacked; + clientFormat->mFormatFlags |= kAudioFormatFlagIsBigEndian; + clientFormat->mBitsPerChannel = sizeof(short) * 8; + clientFormat->mFramesPerPacket = 1; + clientFormat->mBytesPerFrame = clientFormat->mBitsPerChannel * clientFormat->mChannelsPerFrame / 8; + clientFormat->mBytesPerPacket = clientFormat->mFramesPerPacket * clientFormat->mBytesPerFrame; + break; + default: + break; + } +} + uint_t aubio_sink_apple_audio_get_samplerate(const aubio_sink_apple_audio_t *s) { return s->samplerate; @@ -134,19 +209,6 @@ uint_t aubio_sink_apple_audio_open(aubio_sink_apple_audio_t *s) { if (s->samplerate == 0 || s->channels == 0) return AUBIO_FAIL; - AudioStreamBasicDescription clientFormat; - memset(&clientFormat, 0, sizeof(AudioStreamBasicDescription)); - clientFormat.mFormatID = kAudioFormatLinearPCM; - clientFormat.mSampleRate = (Float64)(s->samplerate); - clientFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked; - clientFormat.mChannelsPerFrame = s->channels; - clientFormat.mBitsPerChannel = sizeof(short) * 8; - clientFormat.mFramesPerPacket = 1; - clientFormat.mBytesPerFrame = clientFormat.mBitsPerChannel * clientFormat.mChannelsPerFrame / 8; - clientFormat.mBytesPerPacket = clientFormat.mFramesPerPacket * clientFormat.mBytesPerFrame; - clientFormat.mReserved = 0; - - AudioFileTypeID fileType = kAudioFileWAVEType; CFURLRef fileURL = createURLFromPath(s->path); bool overwrite = true; @@ -161,8 +223,13 @@ uint_t aubio_sink_apple_audio_open(aubio_sink_apple_audio_t *s) { inputFormat.mFramesPerPacket = 1; inputFormat.mBytesPerFrame = inputFormat.mBitsPerChannel * inputFormat.mChannelsPerFrame / 8; inputFormat.mBytesPerPacket = inputFormat.mFramesPerPacket * inputFormat.mBytesPerFrame; + + // get the in-file format + AudioStreamBasicDescription clientFormat; + aubio_sink_apple_audio_set_client_format(s, &clientFormat); + OSStatus err = noErr; - err = ExtAudioFileCreateWithURL(fileURL, fileType, &clientFormat, NULL, + err = ExtAudioFileCreateWithURL(fileURL, s->fileType, &clientFormat, NULL, overwrite ? kAudioFileFlags_EraseFile : 0, &s->audioFile); CFRelease(fileURL); if (err) { @@ -173,6 +240,20 @@ uint_t aubio_sink_apple_audio_open(aubio_sink_apple_audio_t *s) { goto beach; } +#if defined(kAppleSoftwareAudioCodecManufacturer) + // on iOS, set software based encoding before setting clientDataFormat + UInt32 codecManf = kAppleSoftwareAudioCodecManufacturer; + err = ExtAudioFileSetProperty(s->audioFile, + kExtAudioFileProperty_CodecManufacturer, + sizeof(UInt32), &codecManf); + if (err) { + char_t errorstr[20]; + AUBIO_ERR("sink_apple_audio: error when trying to set sofware codec on %s " + "(%s)\n", s->path, getPrintableOSStatusError(errorstr, err)); + goto beach; + } +#endif + err = ExtAudioFileSetProperty(s->audioFile, kExtAudioFileProperty_ClientDataFormat, sizeof(AudioStreamBasicDescription), &inputFormat); @@ -237,9 +318,9 @@ void aubio_sink_apple_audio_write(aubio_sink_apple_audio_t *s, uint_t write) { if (err) { char_t errorstr[20]; if (err == kExtAudioFileError_AsyncWriteBufferOverflow) { - sprintf(errorstr,"buffer overflow"); + snprintf(errorstr, sizeof (errorstr), "buffer overflow"); } else if (err == kExtAudioFileError_AsyncWriteTooLarge) { - sprintf(errorstr,"write too large"); + snprintf(errorstr, sizeof (errorstr), "write too large"); } else { // unknown error getPrintableOSStatusError(errorstr, err); diff --git a/src/io/sink_apple_audio.h b/src/io/sink_apple_audio.h index 14d6a8aa..1e228bcf 100644 --- a/src/io/sink_apple_audio.h +++ b/src/io/sink_apple_audio.h @@ -96,6 +96,29 @@ uint_t aubio_sink_apple_audio_preset_channels(aubio_sink_apple_audio_t *s, uint_ /** + preset sink format + + \param s sink, created with ::new_aubio_sink_apple_audio + \param fmt format of the file to create + + \return 0 on success, 1 on error + + Preset the format of the sink. Supported format strings: + - "wav": WAVE, 16 bit (default) + - "aiff": AIFF, 16 bit + - "m4a" or "mp4": Apple Audio Lossless Codec (ALAC) + - "aac": Audio Advanced Codec, lossy + + Full list of supported encoding format is available in Table 1-2 of + `Multimedia Programming Guide + `_. + + */ +uint_t aubio_sink_apple_audio_preset_format(aubio_sink_apple_audio_t *s, + const char_t *fmt); + +/** + get samplerate of sink object \param s sink object, created with ::new_aubio_sink_apple_audio diff --git a/src/io/sink_flac.c b/src/io/sink_flac.c new file mode 100644 index 00000000..04144793 --- /dev/null +++ b/src/io/sink_flac.c @@ -0,0 +1,367 @@ +/* + Copyright (C) 2018 Paul Brossier + + This file is part of aubio. + + aubio is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + aubio is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with aubio. If not, see . + +*/ + +/* + This file is largely inspired by `examples/c/encode/file/main.c` in the + flac source package (versions 1.3.2 and later) available online at + https://xiph.org/flac/ +*/ + +#include "aubio_priv.h" + +#ifdef HAVE_FLAC + +#include "fmat.h" +#include "io/ioutils.h" + +#include +#include + +#define MAX_WRITE_SIZE 4096 + +// swap host to little endian +#if defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) +#define HTOLES(x) SWAPS(x) +#elif defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) +#define HTOLES(x) x +#else +#ifdef HAVE_WIN_HACKS +#define HTOLES(x) x +#else +#define HTOLES(x) SWAPS(htons(x)) +#endif +#endif + +// convert to short, taking care of endianness +#define FLOAT_TO_SHORT(x) (HTOLES((FLAC__int32)(x * 32768))) + +struct _aubio_sink_flac_t { + uint_t samplerate; + uint_t channels; + char_t *path; + + FILE *fid; // file id + FLAC__StreamEncoder* encoder; + FLAC__int32 *buffer; + FLAC__StreamMetadata **metadata; +}; + +typedef struct _aubio_sink_flac_t aubio_sink_flac_t; + +uint_t aubio_sink_flac_preset_channels(aubio_sink_flac_t *s, + uint_t channels); +uint_t aubio_sink_flac_preset_samplerate(aubio_sink_flac_t *s, + uint_t samplerate); +uint_t aubio_sink_flac_open(aubio_sink_flac_t *s); +uint_t aubio_sink_flac_close (aubio_sink_flac_t *s); +void del_aubio_sink_flac (aubio_sink_flac_t *s); + +#if 0 +static void aubio_sink_flac_callback(const FLAC__StreamEncoder* encoder, + FLAC__uint64 bytes_written, FLAC__uint64 samples_written, + unsigned frames_writtten, unsigned total_frames_estimate, + void *client_data); +#endif + +aubio_sink_flac_t * new_aubio_sink_flac (const char_t *uri, + uint_t samplerate) +{ + aubio_sink_flac_t * s = AUBIO_NEW(aubio_sink_flac_t); + + if (!uri) { + AUBIO_ERROR("sink_flac: Aborted opening null path\n"); + goto failure; + } + + s->path = AUBIO_ARRAY(char_t, strnlen(uri, PATH_MAX) + 1); + strncpy(s->path, uri, strnlen(uri, PATH_MAX) + 1); + s->path[strnlen(uri, PATH_MAX)] = '\0'; + + s->channels = 0; + s->samplerate = 0; + + if ((sint_t)samplerate == 0) + return s; + + aubio_sink_flac_preset_samplerate(s, samplerate); + s->channels = 1; + + if (aubio_sink_flac_open(s) != AUBIO_OK) + goto failure; + + return s; + +failure: + del_aubio_sink_flac(s); + return NULL; +} + +void del_aubio_sink_flac (aubio_sink_flac_t *s) +{ + if (s->fid) + aubio_sink_flac_close(s); + if (s->buffer) + AUBIO_FREE(s->buffer); + if (s->path) + AUBIO_FREE(s->path); + AUBIO_FREE(s); +} + +uint_t aubio_sink_flac_open(aubio_sink_flac_t *s) +{ + uint_t ret = AUBIO_FAIL; + FLAC__bool ok = true; + FLAC__StreamEncoderInitStatus init_status; + FLAC__StreamMetadata_VorbisComment_Entry entry; + const unsigned comp_level = 5; + const unsigned bps = 16; + + if (s->samplerate == 0 || s->channels == 0) return AUBIO_FAIL; + + s->buffer = AUBIO_ARRAY(FLAC__int32, s->channels * MAX_WRITE_SIZE); + if (!s->buffer) { + AUBIO_ERR("sink_flac: failed allocating buffer for %s\n", s->path); + return AUBIO_FAIL; + } + + s->fid = fopen((const char *)s->path, "wb"); + if (!s->fid) { + AUBIO_STRERR("sink_flac: Failed opening %s (%s)\n", s->path, errorstr); + return AUBIO_FAIL; + } + + if((s->encoder = FLAC__stream_encoder_new()) == NULL) { + AUBIO_ERR("sink_flac: failed allocating encoder for %s\n", s->path); + goto failure; + } + ok &= FLAC__stream_encoder_set_verify(s->encoder, true); + ok &= FLAC__stream_encoder_set_compression_level(s->encoder, comp_level); + ok &= FLAC__stream_encoder_set_channels(s->encoder, s->channels); + ok &= FLAC__stream_encoder_set_bits_per_sample(s->encoder, bps); + ok &= FLAC__stream_encoder_set_sample_rate(s->encoder, s->samplerate); + // the total number of samples can not be estimated (streaming) + // it will be set by the encoder in FLAC__stream_encoder_finish + //ok &= FLAC__stream_encoder_set_total_samples_estimate(s->encoder, 0); + + if (!ok) { + AUBIO_ERR("sink_flac: failed setting metadata for %s\n", s->path); + goto failure; + } + + s->metadata = AUBIO_ARRAY(FLAC__StreamMetadata*, 2); + if (!s->metadata) { + AUBIO_ERR("sink_flac: failed allocating memory for %s\n", s->path); + goto failure; + } + + s->metadata[0] = FLAC__metadata_object_new(FLAC__METADATA_TYPE_VORBIS_COMMENT); + if (!s->metadata[0]) { + AUBIO_ERR("sink_flac: failed allocating vorbis comment %s\n", s->path); + goto failure; + } + + s->metadata[1] = FLAC__metadata_object_new(FLAC__METADATA_TYPE_PADDING); + if (!s->metadata[1]) { + AUBIO_ERR("sink_flac: failed allocating vorbis comment %s\n", s->path); + goto failure; + } + + ok = FLAC__metadata_object_vorbiscomment_entry_from_name_value_pair(&entry, + "encoder", "aubio"); + ok &= FLAC__metadata_object_vorbiscomment_append_comment(s->metadata[0], + entry, false); + if (!ok) { + AUBIO_ERR("sink_flac: failed setting metadata for %s\n", s->path); + goto failure; + } + + // padding length + s->metadata[1]->length = 1234; + if (!FLAC__stream_encoder_set_metadata(s->encoder, s->metadata, 2)) { + AUBIO_ERR("sink_flac: failed setting metadata for %s\n", s->path); + goto failure; + } + + // initialize encoder + init_status = FLAC__stream_encoder_init_file(s->encoder, s->path, + NULL, NULL); + //aubio_sink_flac_callback, s); + if (init_status == FLAC__STREAM_ENCODER_INIT_STATUS_INVALID_SAMPLE_RATE) { + AUBIO_ERR("sink_flac: failed initilizing encoder for %s" + " (invalid samplerate %d)\n", s->path, s->samplerate); + goto failure; + } else if (init_status == + FLAC__STREAM_ENCODER_INIT_STATUS_INVALID_NUMBER_OF_CHANNELS) { + AUBIO_ERR("sink_flac: failed initilizing encoder for %s" + " (invalid number of channel %d)\n", s->path, s->channels); + goto failure; + } else if (init_status != FLAC__STREAM_ENCODER_INIT_STATUS_OK) { + AUBIO_ERR("sink_flac: failed initilizing encoder for %s (%d)\n", + s->path, (int)init_status); + goto failure; + } + + // mark success + ret = AUBIO_OK; + +failure: + + return ret; +} + +uint_t aubio_sink_flac_preset_samplerate(aubio_sink_flac_t *s, + uint_t samplerate) +{ + if (aubio_io_validate_samplerate("sink_flac", s->path, samplerate)) + return AUBIO_FAIL; + s->samplerate = samplerate; + if (s->samplerate != 0 && s->channels != 0) + return aubio_sink_flac_open(s); + return AUBIO_OK; +} + +uint_t aubio_sink_flac_preset_channels(aubio_sink_flac_t *s, + uint_t channels) +{ + if (aubio_io_validate_channels("sink_flac", s->path, channels)) { + return AUBIO_FAIL; + } + s->channels = channels; + // automatically open when both samplerate and channels have been set + if (s->samplerate != 0 && s->channels != 0) { + return aubio_sink_flac_open(s); + } + return AUBIO_OK; +} + +uint_t aubio_sink_flac_get_samplerate(const aubio_sink_flac_t *s) +{ + return s->samplerate; +} + +uint_t aubio_sink_flac_get_channels(const aubio_sink_flac_t *s) +{ + return s->channels; +} + +static void aubio_sink_write_frames(aubio_sink_flac_t *s, uint_t length) +{ + // send to encoder + if (!FLAC__stream_encoder_process_interleaved(s->encoder, + (const FLAC__int32*)s->buffer, length)) { + FLAC__StreamEncoderState state = + FLAC__stream_encoder_get_state(s->encoder); + AUBIO_WRN("sink_flac: error writing to %s (%s)\n", + s->path, FLAC__StreamEncoderStateString[state]); + } +} + +void aubio_sink_flac_do(aubio_sink_flac_t *s, fvec_t *write_data, + uint_t write) +{ + uint_t c, v; + uint_t length = aubio_sink_validate_input_length("sink_flac", s->path, + MAX_WRITE_SIZE, write_data->length, write); + // fill buffer + if (!write) { + return; + } else { + for (c = 0; c < s->channels; c++) { + for (v = 0; v < length; v++) { + s->buffer[v * s->channels + c] = FLOAT_TO_SHORT(write_data->data[v]); + } + } + } + // send to encoder + aubio_sink_write_frames(s, length); +} + +void aubio_sink_flac_do_multi(aubio_sink_flac_t *s, fmat_t *write_data, + uint_t write) +{ + uint_t c, v; + uint_t channels = aubio_sink_validate_input_channels("sink_flac", s->path, + s->channels, write_data->height); + uint_t length = aubio_sink_validate_input_length("sink_flac", s->path, + MAX_WRITE_SIZE, write_data->length, write); + // fill buffer + if (!write) { + return; + } else { + for (c = 0; c < channels; c++) { + for (v = 0; v < length; v++) { + s->buffer[v * s->channels + c] = FLOAT_TO_SHORT(write_data->data[c][v]); + } + } + } + // send to encoder + aubio_sink_write_frames(s, length); +} + +uint_t aubio_sink_flac_close (aubio_sink_flac_t *s) +{ + uint_t ret = AUBIO_OK; + + if (!s->fid) return AUBIO_FAIL; + + if (s->encoder) { + // mark the end of stream + if (!FLAC__stream_encoder_finish(s->encoder)) { + FLAC__StreamEncoderState state = + FLAC__stream_encoder_get_state(s->encoder); + AUBIO_ERR("sink_flac: Error closing encoder for %s (%s)\n", + s->path, FLAC__StreamEncoderStateString[state]); + ret &= AUBIO_FAIL; + } + + FLAC__stream_encoder_delete(s->encoder); + } + + if (s->metadata) { + // clean up metadata after stream finished + if (s->metadata[0]) + FLAC__metadata_object_delete(s->metadata[0]); + if (s->metadata[1]) + FLAC__metadata_object_delete(s->metadata[1]); + AUBIO_FREE(s->metadata); + } + + if (s->fid && fclose(s->fid)) { + AUBIO_STRERR("sink_flac: Error closing file %s (%s)\n", s->path, errorstr); + ret &= AUBIO_FAIL; + } + s->fid = NULL; + + return ret; +} + +#if 0 +static void aubio_sink_flac_callback(const FLAC__StreamEncoder* encoder UNUSED, + FLAC__uint64 bytes_written, FLAC__uint64 samples_written, + unsigned frames_written, unsigned total_frames_estimate, + void *client_data UNUSED) +{ + AUBIO_WRN("sink_flac: %d bytes_written, %d samples_written," + " %d/%d frames writen\n", + bytes_written, samples_written, frames_written, total_frames_estimate); +} +#endif + +#endif /* HAVE_FLAC */ diff --git a/src/io/sink_sndfile.c b/src/io/sink_sndfile.c index 35e2215c..5b168a4b 100644 --- a/src/io/sink_sndfile.c +++ b/src/io/sink_sndfile.c @@ -48,10 +48,15 @@ struct _aubio_sink_sndfile_t { SNDFILE *handle; uint_t scratch_size; smpl_t *scratch_data; + int format; }; uint_t aubio_sink_sndfile_open(aubio_sink_sndfile_t *s); +uint_t aubio_str_extension_matches(const char_t *ext, + const char_t *pattern); +const char_t *aubio_str_get_extension(const char_t *filename); + aubio_sink_sndfile_t * new_aubio_sink_sndfile(const char_t * path, uint_t samplerate) { aubio_sink_sndfile_t * s = AUBIO_NEW(aubio_sink_sndfile_t); s->max_size = MAX_SIZE; @@ -67,6 +72,8 @@ aubio_sink_sndfile_t * new_aubio_sink_sndfile(const char_t * path, uint_t sample s->samplerate = 0; s->channels = 0; + aubio_sink_sndfile_preset_format(s, aubio_str_get_extension(path)); + // zero samplerate given. do not open yet if ((sint_t)samplerate == 0) { return s; @@ -115,6 +122,30 @@ uint_t aubio_sink_sndfile_preset_channels(aubio_sink_sndfile_t *s, uint_t channe return AUBIO_OK; } +uint_t aubio_sink_sndfile_preset_format(aubio_sink_sndfile_t *s, + const char_t *fmt) +{ + if (aubio_str_extension_matches(fmt, "wav")) { + s->format = SF_FORMAT_WAV | SF_FORMAT_PCM_16; + } else if (aubio_str_extension_matches(fmt, "aiff")) { + s->format = SF_FORMAT_AIFF | SF_FORMAT_PCM_16; + } else if (aubio_str_extension_matches(fmt, "flac")) { + s->format = SF_FORMAT_FLAC | SF_FORMAT_PCM_16; + } else if (aubio_str_extension_matches(fmt, "ogg")) { + s->format = SF_FORMAT_OGG | SF_FORMAT_VORBIS; + } else if (atoi(fmt) > 0x010000) { + s->format = atoi(fmt); + } else { + s->format = SF_FORMAT_WAV | SF_FORMAT_PCM_16; + if (fmt && strnlen(fmt, PATH_MAX)) { + AUBIO_WRN("sink_sndfile: could not guess format %s for %s," + " using default (wav)\n", fmt, s->path); + return AUBIO_FAIL; + } + } + return AUBIO_OK; +} + uint_t aubio_sink_sndfile_get_samplerate(const aubio_sink_sndfile_t *s) { return s->samplerate; @@ -131,7 +162,7 @@ uint_t aubio_sink_sndfile_open(aubio_sink_sndfile_t *s) { AUBIO_MEMSET(&sfinfo, 0, sizeof (sfinfo)); sfinfo.samplerate = s->samplerate; sfinfo.channels = s->channels; - sfinfo.format = SF_FORMAT_WAV | SF_FORMAT_PCM_16; + sfinfo.format = s->format; /* try creating the file */ s->handle = sf_open (s->path, SFM_WRITE, &sfinfo); diff --git a/src/io/sink_sndfile.h b/src/io/sink_sndfile.h index 083c4ca2..d12ff55c 100644 --- a/src/io/sink_sndfile.h +++ b/src/io/sink_sndfile.h @@ -95,6 +95,39 @@ uint_t aubio_sink_sndfile_preset_channels(aubio_sink_sndfile_t *s, uint_t channe /** + preset sink format + + \param s sink, created with ::new_aubio_sink_sndfile + \param fmt format of the file to create + + \return 0 on success, 1 on error + + Preset the format of the sink. Supported format strings: + - "wav": 16 bit (default) + - "aiff": aiff, 16 bit + - "flac": flac, 16 bit + - "ogg": ogg vorbis stream + + Alternatively, any sndfile format can be set by passing the corresponding + integer as a string: + + \code{.c} + char_t fmt[10]; + snprintf(fmt, sizeof(fmt), "%d", SF_FORMAT_FLAC | SF_FORMAT_PCM_24); + aubio_sink_sndfile_preset_format(s, fmt); + \endcode + + The file should have been created using a samplerate of 0. + + This function should be called before aubio_sink_sndfile_preset_samplerate() + and aubio_sink_sndfile_preset_channels(). + +*/ +uint_t aubio_sink_sndfile_preset_format(aubio_sink_sndfile_t *s, + const char_t* fmt); + +/** + get samplerate of sink object \param s sink object, created with ::new_aubio_sink_sndfile diff --git a/src/io/sink_vorbis.c b/src/io/sink_vorbis.c new file mode 100644 index 00000000..00f3e1c4 --- /dev/null +++ b/src/io/sink_vorbis.c @@ -0,0 +1,314 @@ +/* + Copyright (C) 2018 Paul Brossier + + This file is part of aubio. + + aubio is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + aubio is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with aubio. If not, see . + +*/ + +/* + This file is largely inspired by `examples/encoder_example.c` in the + libvorbis source package (versions 1.3.5 and later) available online at + https://xiph.org/vorbis/ +*/ + +#include "aubio_priv.h" + +#ifdef HAVE_VORBISENC + +#include "fmat.h" +#include "io/ioutils.h" + +#include +#include // strerror +#include // errno +#include // time + +#define MAX_SIZE 4096 + +struct _aubio_sink_vorbis_t { + FILE *fid; // file id + ogg_stream_state os; // stream + ogg_page og; // page + ogg_packet op; // data packet + vorbis_info vi; // vorbis bitstream settings + vorbis_comment vc; // user comment + vorbis_dsp_state vd; // working state + vorbis_block vb; // working space + + uint_t samplerate; + uint_t channels; + char_t *path; +}; + +typedef struct _aubio_sink_vorbis_t aubio_sink_vorbis_t; + +uint_t aubio_sink_vorbis_preset_channels(aubio_sink_vorbis_t *s, + uint_t channels); +uint_t aubio_sink_vorbis_preset_samplerate(aubio_sink_vorbis_t *s, + uint_t samplerate); +uint_t aubio_sink_vorbis_open(aubio_sink_vorbis_t *s); +uint_t aubio_sink_vorbis_close (aubio_sink_vorbis_t *s); +void del_aubio_sink_vorbis (aubio_sink_vorbis_t *s); + +static uint_t aubio_sink_vorbis_write_page(aubio_sink_vorbis_t *s); + +aubio_sink_vorbis_t * new_aubio_sink_vorbis (const char_t *uri, + uint_t samplerate) +{ + aubio_sink_vorbis_t * s = AUBIO_NEW(aubio_sink_vorbis_t); + + if (!uri) { + AUBIO_ERROR("sink_vorbis: Aborted opening null path\n"); + goto failure; + } + + s->path = AUBIO_ARRAY(char_t, strnlen(uri, PATH_MAX) + 1); + strncpy(s->path, uri, strnlen(uri, PATH_MAX) + 1); + s->path[strnlen(uri, PATH_MAX)] = '\0'; + + s->channels = 0; + + if ((sint_t)samplerate == 0) + return s; + + aubio_sink_vorbis_preset_samplerate(s, samplerate); + s->channels = 1; + + if (aubio_sink_vorbis_open(s) != AUBIO_OK) + goto failure; + + return s; + +failure: + del_aubio_sink_vorbis(s); + return NULL; +} + +void del_aubio_sink_vorbis (aubio_sink_vorbis_t *s) +{ + if (s->fid) aubio_sink_vorbis_close(s); + // clean up + ogg_stream_clear(&s->os); + vorbis_block_clear(&s->vb); + vorbis_dsp_clear(&s->vd); + vorbis_comment_clear(&s->vc); + vorbis_info_clear(&s->vi); + + if (s->path) AUBIO_FREE(s->path); + AUBIO_FREE(s); +} + +uint_t aubio_sink_vorbis_open(aubio_sink_vorbis_t *s) +{ + float quality_mode = .9; + + if (s->samplerate == 0 || s->channels == 0) return AUBIO_FAIL; + + s->fid = fopen((const char *)s->path, "wb"); + if (!s->fid) { + AUBIO_STRERR("sink_vorbis: Error opening file \'%s\' (%s)\n", + s->path, errorstr); + return AUBIO_FAIL; + } + + vorbis_info_init(&s->vi); + if (vorbis_encode_init_vbr(&s->vi, s->channels, s->samplerate, quality_mode)) + { + AUBIO_ERR("sink_vorbis: vorbis_encode_init_vbr failed\n"); + return AUBIO_FAIL; + } + + // add comment + vorbis_comment_init(&s->vc); + vorbis_comment_add_tag(&s->vc, "ENCODER", "aubio"); + + // initalise analysis and block + vorbis_analysis_init(&s->vd, &s->vi); + vorbis_block_init(&s->vd, &s->vb); + + // pick randome serial number + srand(time(NULL)); + ogg_stream_init(&s->os, rand()); + + // write header + { + ogg_packet header; + ogg_packet header_comm; + ogg_packet header_code; + + vorbis_analysis_headerout(&s->vd, &s->vc, &header, &header_comm, + &header_code); + + ogg_stream_packetin(&s->os, &header); + ogg_stream_packetin(&s->os, &header_comm); + ogg_stream_packetin(&s->os, &header_code); + + // make sure audio data will start on a new page + while (1) + { + if (!ogg_stream_flush(&s->os, &s->og)) break; + if (aubio_sink_vorbis_write_page(s)) return AUBIO_FAIL; + } + } + + return AUBIO_OK; +} + +uint_t aubio_sink_vorbis_preset_samplerate(aubio_sink_vorbis_t *s, + uint_t samplerate) +{ + if (aubio_io_validate_samplerate("sink_vorbis", s->path, samplerate)) + return AUBIO_FAIL; + s->samplerate = samplerate; + if (/* s->samplerate != 0 && */ s->channels != 0) + return aubio_sink_vorbis_open(s); + return AUBIO_OK; +} + +uint_t aubio_sink_vorbis_preset_channels(aubio_sink_vorbis_t *s, + uint_t channels) +{ + if (aubio_io_validate_channels("sink_vorbis", s->path, channels)) { + return AUBIO_FAIL; + } + s->channels = channels; + // automatically open when both samplerate and channels have been set + if (s->samplerate != 0 /* && s->channels != 0 */) { + return aubio_sink_vorbis_open(s); + } + return AUBIO_OK; +} + +uint_t aubio_sink_vorbis_get_samplerate(const aubio_sink_vorbis_t *s) +{ + return s->samplerate; +} + +uint_t aubio_sink_vorbis_get_channels(const aubio_sink_vorbis_t *s) +{ + return s->channels; +} + +static +uint_t aubio_sink_vorbis_write_page(aubio_sink_vorbis_t *s) { + int result; + size_t wrote; + wrote = fwrite(s->og.header, 1, s->og.header_len, s->fid); + result = (wrote == (unsigned)s->og.header_len); + wrote = fwrite(s->og.body, 1, s->og.body_len, s->fid); + result &= (wrote == (unsigned)s->og.body_len); + if (result == 0) { + AUBIO_STRERR("sink_vorbis: failed writing \'%s\' to disk (%s)\n", + s->path, errorstr); + return AUBIO_FAIL; + } + return AUBIO_OK; +} + +static +void aubio_sink_vorbis_write(aubio_sink_vorbis_t *s) +{ + // pre-analysis + while (vorbis_analysis_blockout(&s->vd, &s->vb) == 1) { + + vorbis_analysis(&s->vb, NULL); + vorbis_bitrate_addblock(&s->vb); + + while (vorbis_bitrate_flushpacket(&s->vd, &s->op)) + { + ogg_stream_packetin(&s->os, &s->op); + + while (1) { + if (!ogg_stream_pageout (&s->os, &s->og)) break; + aubio_sink_vorbis_write_page(s); + if (ogg_page_eos(&s->og)) break; + } + } + } +} + +void aubio_sink_vorbis_do(aubio_sink_vorbis_t *s, fvec_t *write_data, + uint_t write) +{ + uint_t c, v; + uint_t length = aubio_sink_validate_input_length("sink_vorbis", s->path, + MAX_SIZE, write_data->length, write); + float **buffer = vorbis_analysis_buffer(&s->vd, (long)length); + // fill buffer + if (!write) { + return; + } else if (!buffer) { + AUBIO_WRN("sink_vorbis: failed fetching buffer of size %d\n", write); + return; + } else { + for (c = 0; c < s->channels; c++) { + for (v = 0; v < length; v++) { + buffer[c][v] = write_data->data[v]; + } + } + // tell vorbis how many frames were written + vorbis_analysis_wrote(&s->vd, (long)length); + } + // write to file + aubio_sink_vorbis_write(s); +} + +void aubio_sink_vorbis_do_multi(aubio_sink_vorbis_t *s, fmat_t *write_data, + uint_t write) +{ + uint_t c, v; + uint_t channels = aubio_sink_validate_input_channels("sink_vorbis", s->path, + s->channels, write_data->height); + uint_t length = aubio_sink_validate_input_length("sink_vorbis", s->path, + MAX_SIZE, write_data->length, write); + float **buffer = vorbis_analysis_buffer(&s->vd, (long)length); + // fill buffer + if (!write) { + return; + } else if (!buffer) { + AUBIO_WRN("sink_vorbis: failed fetching buffer of size %d\n", write); + return; + } else { + for (c = 0; c < channels; c++) { + for (v = 0; v < length; v++) { + buffer[c][v] = write_data->data[c][v]; + } + } + // tell vorbis how many frames were written + vorbis_analysis_wrote(&s->vd, (long)length); + } + + aubio_sink_vorbis_write(s); +} + +uint_t aubio_sink_vorbis_close (aubio_sink_vorbis_t *s) +{ + if (!s->fid) return AUBIO_FAIL; + //mark the end of stream + vorbis_analysis_wrote(&s->vd, 0); + + aubio_sink_vorbis_write(s); + + if (s->fid && fclose(s->fid)) { + AUBIO_STRERR("sink_vorbis: Error closing file \'%s\' (%s)\n", + s->path, errorstr); + return AUBIO_FAIL; + } + s->fid = NULL; + return AUBIO_OK; +} + +#endif /* HAVE_VORBISENC */ diff --git a/src/io/source_avcodec.c b/src/io/source_avcodec.c index 5b25d85c..71ea8b5f 100644 --- a/src/io/source_avcodec.c +++ b/src/io/source_avcodec.c @@ -24,11 +24,7 @@ #include #include -#if defined(HAVE_SWRESAMPLE) #include -#elif defined(HAVE_AVRESAMPLE) -#include -#endif #include // determine whether we use libavformat from ffmpeg or from libav @@ -56,6 +52,12 @@ #define av_packet_unref av_free_packet #endif +#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(57,28,100) +//#warning "libavutil < 57.28.100 is deprecated" +#else +#define LIBAVUTIL_HAS_CH_LAYOUT +#endif + #include "aubio_priv.h" #include "fvec.h" #include "fmat.h" @@ -68,6 +70,10 @@ #define AUBIO_AVCODEC_MAX_BUFFER_SIZE AV_INPUT_BUFFER_MIN_SIZE #endif +#if LIBAVCODEC_VERSION_MAJOR >= 59 +#define FF_API_LAVF_AVCTX 1 +#endif + struct _aubio_source_avcodec_t { uint_t hop_size; uint_t samplerate; @@ -82,12 +88,12 @@ struct _aubio_source_avcodec_t { AVFormatContext *avFormatCtx; AVCodecContext *avCodecCtx; AVFrame *avFrame; +#if FF_API_INIT_PACKET + AVPacket *avPacket; +#else AVPacket avPacket; -#ifdef HAVE_AVRESAMPLE - AVAudioResampleContext *avr; -#elif defined(HAVE_SWRESAMPLE) - SwrContext *avr; #endif + SwrContext *avr; smpl_t *output; uint_t read_samples; uint_t read_index; @@ -122,11 +128,15 @@ aubio_source_avcodec_t * new_aubio_source_avcodec(const char_t * path, AVFormatContext *avFormatCtx = NULL; AVCodecContext *avCodecCtx = NULL; AVFrame *avFrame = NULL; +#if FF_API_INIT_PACKET + AVPacket *avPacket = NULL; +#endif sint_t selected_stream = -1; #if FF_API_LAVF_AVCTX AVCodecParameters *codecpar; #endif - AVCodec *codec; + + const AVCodec *codec; uint_t i; int err; if (path == NULL) { @@ -259,7 +269,11 @@ aubio_source_avcodec_t * new_aubio_source_avcodec(const char_t * path, /* get input specs */ s->input_samplerate = avCodecCtx->sample_rate; +#ifdef LIBAVUTIL_HAS_CH_LAYOUT + s->input_channels = avCodecCtx->ch_layout.nb_channels; +#else s->input_channels = avCodecCtx->channels; +#endif //AUBIO_DBG("input_samplerate: %d\n", s->input_samplerate); //AUBIO_DBG("input_channels: %d\n", s->input_channels); @@ -277,7 +291,16 @@ aubio_source_avcodec_t * new_aubio_source_avcodec(const char_t * path, avFrame = av_frame_alloc(); if (!avFrame) { AUBIO_ERR("source_avcodec: Could not allocate frame for (%s)\n", s->path); + goto beach; + } + +#if FF_API_INIT_PACKET + avPacket = av_packet_alloc(); + if (!avPacket) { + AUBIO_ERR("source_avcodec: Could not allocate packet for (%s)\n", s->path); + goto beach; } +#endif /* allocate output for avr */ s->output = (smpl_t *)av_malloc(AUBIO_AVCODEC_MAX_BUFFER_SIZE @@ -289,6 +312,9 @@ aubio_source_avcodec_t * new_aubio_source_avcodec(const char_t * path, s->avFormatCtx = avFormatCtx; s->avCodecCtx = avCodecCtx; s->avFrame = avFrame; +#if FF_API_INIT_PACKET + s->avPacket = avPacket; +#endif aubio_source_avcodec_reset_resampler(s); @@ -312,16 +338,22 @@ void aubio_source_avcodec_reset_resampler(aubio_source_avcodec_t * s) // create or reset resampler to/from mono/multi-channel if ( s->avr == NULL ) { int err; + SwrContext *avr = swr_alloc(); +#ifdef LIBAVUTIL_HAS_CH_LAYOUT + AVChannelLayout input_layout; + AVChannelLayout output_layout; + av_channel_layout_default(&input_layout, s->input_channels); + av_channel_layout_default(&output_layout, s->input_channels); + + av_opt_set_chlayout(avr, "in_channel_layout", &input_layout, 0); + av_opt_set_chlayout(avr, "out_channel_layout", &output_layout, 0); +#else int64_t input_layout = av_get_default_channel_layout(s->input_channels); int64_t output_layout = av_get_default_channel_layout(s->input_channels); -#ifdef HAVE_AVRESAMPLE - AVAudioResampleContext *avr = avresample_alloc_context(); -#elif defined(HAVE_SWRESAMPLE) - SwrContext *avr = swr_alloc(); -#endif /* HAVE_AVRESAMPLE || HAVE_SWRESAMPLE */ av_opt_set_int(avr, "in_channel_layout", input_layout, 0); av_opt_set_int(avr, "out_channel_layout", output_layout, 0); +#endif av_opt_set_int(avr, "in_sample_rate", s->input_samplerate, 0); av_opt_set_int(avr, "out_sample_rate", s->samplerate, 0); av_opt_set_int(avr, "in_sample_fmt", s->avCodecCtx->sample_fmt, 0); @@ -332,11 +364,7 @@ void aubio_source_avcodec_reset_resampler(aubio_source_avcodec_t * s) #endif // TODO: use planar? //av_opt_set_int(avr, "out_sample_fmt", AV_SAMPLE_FMT_FLTP, 0); -#ifdef HAVE_AVRESAMPLE - if ( ( err = avresample_open(avr) ) < 0) -#elif defined(HAVE_SWRESAMPLE) if ( ( err = swr_init(avr) ) < 0) -#endif /* HAVE_AVRESAMPLE || HAVE_SWRESAMPLE */ { char errorstr[256]; av_strerror (err, errorstr, sizeof(errorstr)); @@ -354,36 +382,34 @@ void aubio_source_avcodec_readframe(aubio_source_avcodec_t *s, AVFormatContext *avFormatCtx = s->avFormatCtx; AVCodecContext *avCodecCtx = s->avCodecCtx; AVFrame *avFrame = s->avFrame; - AVPacket avPacket = s->avPacket; -#ifdef HAVE_AVRESAMPLE - AVAudioResampleContext *avr = s->avr; -#elif defined(HAVE_SWRESAMPLE) +#if FF_API_INIT_PACKET + AVPacket *avPacket = s->avPacket; +#else + AVPacket *avPacket = &s->avPacket; +#endif SwrContext *avr = s->avr; -#endif /* HAVE_AVRESAMPLE || HAVE_SWRESAMPLE */ int got_frame = 0; -#ifdef HAVE_AVRESAMPLE - int in_linesize = 0; - int in_samples = avFrame->nb_samples; - int out_linesize = 0; - int max_out_samples = AUBIO_AVCODEC_MAX_BUFFER_SIZE; - int out_samples = 0; -#elif defined(HAVE_SWRESAMPLE) int in_samples = avFrame->nb_samples; +#ifdef LIBAVUTIL_HAS_CH_LAYOUT + int max_out_samples = AUBIO_AVCODEC_MAX_BUFFER_SIZE / avCodecCtx->ch_layout.nb_channels; +#else int max_out_samples = AUBIO_AVCODEC_MAX_BUFFER_SIZE / avCodecCtx->channels; +#endif int out_samples = 0; -#endif /* HAVE_AVRESAMPLE || HAVE_SWRESAMPLE */ smpl_t *output = s->output; #ifndef FF_API_LAVF_AVCTX int len = 0; #else int ret = 0; #endif - av_init_packet (&avPacket); +#ifndef FF_API_INIT_PACKET + av_init_packet (avPacket); +#endif *read_samples = 0; do { - int err = av_read_frame (avFormatCtx, &avPacket); + int err = av_read_frame (avFormatCtx, avPacket); if (err == AVERROR_EOF) { s->eof = 1; goto beach; @@ -396,10 +422,10 @@ void aubio_source_avcodec_readframe(aubio_source_avcodec_t *s, s->eof = 1; goto beach; } - } while (avPacket.stream_index != s->selected_stream); + } while (avPacket->stream_index != s->selected_stream); #if FF_API_LAVF_AVCTX - ret = avcodec_send_packet(avCodecCtx, &avPacket); + ret = avcodec_send_packet(avCodecCtx, avPacket); if (ret < 0 && ret != AVERROR_EOF) { AUBIO_ERR("source_avcodec: error when sending packet for %s\n", s->path); goto beach; @@ -422,7 +448,7 @@ void aubio_source_avcodec_readframe(aubio_source_avcodec_t *s, } } #else - len = avcodec_decode_audio4(avCodecCtx, avFrame, &got_frame, &avPacket); + len = avcodec_decode_audio4(avCodecCtx, avFrame, &got_frame, avPacket); if (len < 0) { AUBIO_ERR("source_avcodec: error while decoding %s\n", s->path); @@ -436,33 +462,27 @@ void aubio_source_avcodec_readframe(aubio_source_avcodec_t *s, } #if LIBAVUTIL_VERSION_MAJOR > 52 - if (avFrame->channels != (sint_t)s->input_channels) { +#ifdef LIBAVUTIL_HAS_CH_LAYOUT + int frame_channels = avFrame->ch_layout.nb_channels; +#else + int frame_channels = avFrame->channels; +#endif + if (frame_channels != (sint_t)s->input_channels) { AUBIO_WRN ("source_avcodec: trying to read from %d channel(s)," "but configured for %d; is '%s' corrupt?\n", - avFrame->channels, s->input_channels, s->path); + frame_channels, s->input_channels, s->path); goto beach; } #else #warning "avutil < 53 is deprecated, crashes might occur on corrupt files" #endif -#ifdef HAVE_AVRESAMPLE - in_linesize = 0; - av_samples_get_buffer_size(&in_linesize, avCodecCtx->channels, - avFrame->nb_samples, avCodecCtx->sample_fmt, 1); in_samples = avFrame->nb_samples; - out_linesize = 0; max_out_samples = AUBIO_AVCODEC_MAX_BUFFER_SIZE; - out_samples = avresample_convert ( avr, - (uint8_t **)&output, out_linesize, max_out_samples, - (uint8_t **)avFrame->data, in_linesize, in_samples); -#elif defined(HAVE_SWRESAMPLE) - in_samples = avFrame->nb_samples; - max_out_samples = AUBIO_AVCODEC_MAX_BUFFER_SIZE / avCodecCtx->channels; + if (frame_channels > 0) max_out_samples /= frame_channels; out_samples = swr_convert( avr, (uint8_t **)&output, max_out_samples, (const uint8_t **)avFrame->data, in_samples); -#endif /* HAVE_AVRESAMPLE || HAVE_SWRESAMPLE */ if (out_samples < 0) { AUBIO_WRN("source_avcodec: error while resampling %s (%d)\n", s->path, out_samples); @@ -472,7 +492,7 @@ void aubio_source_avcodec_readframe(aubio_source_avcodec_t *s, *read_samples = out_samples; beach: - av_packet_unref(&avPacket); + av_packet_unref(avPacket); } void aubio_source_avcodec_do(aubio_source_avcodec_t * s, fvec_t * read_data, @@ -596,14 +616,8 @@ uint_t aubio_source_avcodec_seek (aubio_source_avcodec_t * s, uint_t pos) { s->eof = 0; s->read_index = 0; s->read_samples = 0; -#ifdef HAVE_AVRESAMPLE - // reset the AVAudioResampleContext - avresample_close(s->avr); - avresample_open(s->avr); -#elif defined(HAVE_SWRESAMPLE) swr_close(s->avr); swr_init(s->avr); -#endif return ret; } @@ -617,13 +631,8 @@ uint_t aubio_source_avcodec_get_duration (aubio_source_avcodec_t * s) { uint_t aubio_source_avcodec_close(aubio_source_avcodec_t * s) { if (s->avr != NULL) { -#ifdef HAVE_AVRESAMPLE - avresample_close( s->avr ); - av_free ( s->avr ); -#elif defined(HAVE_SWRESAMPLE) swr_close ( s->avr ); swr_free ( &s->avr ); -#endif } s->avr = NULL; if (s->avCodecCtx != NULL) { @@ -638,7 +647,13 @@ uint_t aubio_source_avcodec_close(aubio_source_avcodec_t * s) { avformat_close_input(&s->avFormatCtx); s->avFormatCtx = NULL; } +#if FF_API_INIT_PACKET + if (s->avPacket) { + av_packet_unref(s->avPacket); + } +#else av_packet_unref(&s->avPacket); +#endif return AUBIO_OK; } @@ -653,6 +668,12 @@ void del_aubio_source_avcodec(aubio_source_avcodec_t * s){ av_frame_free( &(s->avFrame) ); } s->avFrame = NULL; +#if FF_API_INIT_PACKET + if (s->avPacket != NULL) { + av_packet_free( &(s->avPacket) ); + } + s->avPacket = NULL; +#endif if (s->path) { AUBIO_FREE(s->path); } diff --git a/src/io/utils_apple_audio.c b/src/io/utils_apple_audio.c index a05d65d3..29d884b6 100644 --- a/src/io/utils_apple_audio.c +++ b/src/io/utils_apple_audio.c @@ -52,7 +52,7 @@ char_t *getPrintableOSStatusError(char_t *str, OSStatus error) str[6] = '\0'; } else // no, format it as an integer - sprintf(str, "%d", (int)error); + snprintf(str, 10, "%d", (int)error); return str; } diff --git a/src/musicutils.h b/src/musicutils.h index a6594951..af222e5b 100644 --- a/src/musicutils.h +++ b/src/musicutils.h @@ -240,7 +240,7 @@ smpl_t aubio_db_spl (const fvec_t * v); \param v vector to get level from \param threshold threshold in dB SPL - \return 0 if level is under the given threshold, 1 otherwise + \return 1 if level is under the given threshold, 0 otherwise */ uint_t aubio_silence_detection (const fvec_t * v, smpl_t threshold); diff --git a/src/utils/strutils.c b/src/utils/strutils.c new file mode 100644 index 00000000..8b695ff4 --- /dev/null +++ b/src/utils/strutils.c @@ -0,0 +1,47 @@ +/* + Copyright (C) 2018 Paul Brossier + + This file is part of aubio. + + aubio is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + aubio is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with aubio. If not, see . + +*/ + +#include "aubio_priv.h" + +#ifdef HAVE_WIN_HACKS +#define strncasecmp _strnicmp +#endif + +const char_t *aubio_str_get_extension(const char_t *filename) +{ + // find last occurence of dot character + const char_t *ext; + if (!filename) return NULL; + ext = strrchr(filename, '.'); + if (!ext || ext == filename) return ""; + else return ext + 1; +} + +uint_t aubio_str_extension_matches(const char_t *ext, const char_t *pattern) +{ + return ext && pattern && (strncasecmp(ext, pattern, PATH_MAX) == 0); +} + +uint_t aubio_str_path_has_extension(const char_t *filename, + const char_t *pattern) +{ + const char_t *ext = aubio_str_get_extension(filename); + return aubio_str_extension_matches(ext, pattern); +} diff --git a/src/wscript_build b/src/wscript_build index 895c191f..3130c65e 100644 --- a/src/wscript_build +++ b/src/wscript_build @@ -6,11 +6,13 @@ uselib += ['FFTW3', 'FFTW3F'] uselib += ['INTEL_IPP'] uselib += ['SAMPLERATE'] uselib += ['SNDFILE'] +uselib += ['RUBBERBAND'] uselib += ['AVCODEC'] uselib += ['AVFORMAT'] uselib += ['SWRESAMPLE'] -uselib += ['AVRESAMPLE'] uselib += ['AVUTIL'] +uselib += ['VORBISENC'] +uselib += ['FLAC'] uselib += ['BLAS'] source = ctx.path.ant_glob('*.c **/*.c') diff --git a/tests/src/effects/test-pitchshift.c b/tests/src/effects/test-pitchshift.c new file mode 100644 index 00000000..64bce30f --- /dev/null +++ b/tests/src/effects/test-pitchshift.c @@ -0,0 +1,110 @@ +#define AUBIO_UNSTABLE 1 +#include +#include "utils_tests.h" + +int test_wrong_params(void); + +int main (int argc, char **argv) +{ + sint_t err = 0; + + if (argc < 3) { + PRINT_ERR("not enough arguments, running tests\n"); + err = test_wrong_params(); + PRINT_MSG("usage: %s [transpose] ", argv[0]); + PRINT_MSG("[mode] [hop_size] [samplerate]\n"); + PRINT_MSG(" with [transpose] a number of semi tones in the range " + " [-24, 24], and [mode] in 'default', 'crispness:0'," + " 'crispness:1', ... 'crispness:6'\n"); + return err; + } + +#ifdef HAVE_RUBBERBAND + uint_t samplerate = 0; + uint_t hop_size = 64; + smpl_t transpose = 0.; + uint_t n_frames = 0, read = 0; + + char_t *source_path = argv[1]; + char_t *sink_path = argv[2]; + char_t *mode = "default"; + + transpose = 0.; + + if ( argc >= 4 ) transpose = atof(argv[3]); + if ( argc >= 5 ) mode = argv[4]; + if ( argc >= 6 ) hop_size = atoi(argv[5]); + if ( argc >= 7 ) samplerate = atoi(argv[6]); + + fvec_t *vec = new_fvec(hop_size); + fvec_t *out = new_fvec(hop_size); + if (!vec) { err = 1; goto beach_fvec; } + + aubio_source_t *i = new_aubio_source(source_path, samplerate, hop_size); + if (!i) { err = 1; goto beach_source; } + + if (samplerate == 0 ) samplerate = aubio_source_get_samplerate(i); + + aubio_sink_t *o = new_aubio_sink(sink_path, samplerate); + if (!o) { err = 1; goto beach_sink; } + + aubio_pitchshift_t *ps = + new_aubio_pitchshift(mode, transpose, hop_size, samplerate); + if (!ps) { err = 1; goto beach_pitchshift; } + + do { + aubio_source_do(i, vec, &read); + //aubio_pitchshift_set_transpose(ps, tranpose); + aubio_pitchshift_do(ps, vec, out); + aubio_sink_do(o, out, read); + n_frames += read; + } while ( read == hop_size ); + + PRINT_MSG("read %d frames at %dHz (%d blocks) from %s\n", + n_frames, samplerate, n_frames / hop_size, + source_path); + PRINT_MSG("wrote to %s with hop_size: %d, samplerate: %d, latency %d\n", + sink_path, hop_size, samplerate, aubio_pitchshift_get_latency(ps)); + + del_aubio_pitchshift(ps); +beach_pitchshift: + del_aubio_sink(o); +beach_sink: + del_aubio_source(i); +beach_source: + del_fvec(vec); + del_fvec(out); +beach_fvec: + aubio_cleanup(); +#else + err = 0; + PRINT_ERR("aubio was not compiled with rubberband\n"); +#endif + return err; +} + +int test_wrong_params(void) +{ + const char_t *mode = "default"; + smpl_t transpose = 0.; + uint_t hop_size = 256; + uint_t samplerate = 44100; + + if (new_aubio_pitchshift("??", transpose, hop_size, samplerate)) return 1; + if (new_aubio_pitchshift(mode, 28., hop_size, samplerate)) return 1; + if (new_aubio_pitchshift(mode, transpose, 0, samplerate)) return 1; + if (new_aubio_pitchshift(mode, transpose, hop_size, 0)) return 1; + + aubio_pitchshift_t *p = new_aubio_pitchshift(mode, transpose, + hop_size, samplerate); +#ifdef HAVE_RUBBERBAND + if (!p) return 1; + if (!aubio_pitchshift_set_pitchscale(p, 0.1)) return 1; + if (!aubio_pitchshift_set_transpose(p, -30)) return 1; + del_aubio_pitchshift(p); +#else + if (p) return 1; +#endif + + return run_on_default_source_and_sink(main); +} diff --git a/tests/src/effects/test-timestretch.c b/tests/src/effects/test-timestretch.c new file mode 100644 index 00000000..8ea48c28 --- /dev/null +++ b/tests/src/effects/test-timestretch.c @@ -0,0 +1,153 @@ +#define AUBIO_UNSTABLE 1 +#include +#include "utils_tests.h" + +int test_wrong_params(void); + +int main (int argc, char **argv) +{ + sint_t err = 0; + + if (argc < 3 || argc >= 9) { + PRINT_ERR("wrong number of arguments, running tests\n"); + err = test_wrong_params(); + PRINT_MSG("usage: %s [transpose] [mode] [hop_size] [samplerate]\n", argv[0]); + PRINT_MSG(" with a time stretching ratio in the range [0.025, 10.]\n"); + PRINT_MSG(" [transpose] a number of semi tones in the range [-24, 24]\n"); + PRINT_MSG(" and [mode] in 'default', 'crispness:0', ..., 'crispness:6'\n"); + return err; + } + +#ifdef HAVE_RUBBERBAND + uint_t samplerate = 0; // using source samplerate + uint_t hop_size = 64; + smpl_t transpose = 0.; + smpl_t stretch = 1.; + uint_t n_frames = 0, read = 0; + uint_t eof = 0, source_read = 0; + + char_t *source_path = argv[1]; + char_t *sink_path = argv[2]; + char_t *mode = "default"; + + if ( argc >= 4 ) stretch = atof(argv[3]); + if ( argc >= 5 ) transpose = atof(argv[4]); + if ( argc >= 6 ) mode = argv[5]; + if ( argc >= 7 ) hop_size = atoi(argv[6]); + if ( argc >= 8 ) samplerate = atoi(argv[7]); + + uint_t source_hopsize = 2048; + aubio_source_t *s = new_aubio_source(source_path, samplerate, source_hopsize); + if (!s) { err = 1; goto beach_source; } + if (samplerate == 0) samplerate = aubio_source_get_samplerate(s); + + fvec_t *in = new_fvec(source_hopsize); + fvec_t *out = new_fvec(hop_size); + if (!out || !in) { err = 1; goto beach_fvec; } + + aubio_timestretch_t *ps = new_aubio_timestretch(mode, stretch, hop_size, + samplerate); + if (!ps) { err = 1; goto beach_timestretch; } + //if (samplerate == 0 ) samplerate = aubio_timestretch_get_samplerate(ps); + + aubio_sink_t *o = new_aubio_sink(sink_path, samplerate); + if (!o) { err = 1; goto beach_sink; } + + if (transpose != 0) aubio_timestretch_set_transpose(ps, transpose); + + do { + //aubio_timestretch_set_stretch(ps, stretch); + //aubio_timestretch_set_transpose(ps, transpose); + + while (aubio_timestretch_get_available(ps) < (sint_t)hop_size && !eof) { + aubio_source_do(s, in, &source_read); + aubio_timestretch_push(ps, in, source_read); + if (source_read < in->length) eof = 1; + } +#if 0 + if (n_frames == hop_size * 200) { + PRINT_MSG("sampler: setting stretch gave %d\n", + aubio_timestretch_set_stretch(ps, 2.) ); + PRINT_MSG("sampler: getting stretch gave %f\n", + aubio_timestretch_get_stretch(ps) ); + PRINT_MSG("sampler: setting transpose gave %d\n", + aubio_timestretch_set_transpose(ps, 12.) ); + PRINT_MSG("sampler: getting transpose gave %f\n", + aubio_timestretch_get_transpose(ps) ); + } +#endif + aubio_timestretch_do(ps, out, &read); + aubio_sink_do(o, out, read); + n_frames += read; + } while ( read == hop_size ); + + PRINT_MSG("wrote %d frames at %dHz (%d blocks) from %s written to %s\n", + n_frames, samplerate, n_frames / hop_size, + source_path, sink_path); + + del_aubio_sink(o); +beach_sink: + del_aubio_timestretch(ps); +beach_timestretch: + del_fvec(out); + del_fvec(in); +beach_fvec: + del_aubio_source(s); +beach_source: +#else + err = 0; + PRINT_ERR("aubio was not compiled with rubberband\n"); +#endif + return err; +} + +int test_wrong_params(void) +{ + const char_t *mode = "default"; + smpl_t stretch = 1.; + uint_t hop_size = 256; + uint_t samplerate = 44100; + + if (new_aubio_timestretch("ProcessOffline:?:", stretch, hop_size, samplerate)) return 1; + if (new_aubio_timestretch("", stretch, hop_size, samplerate)) return 1; + if (new_aubio_timestretch(mode, 41., hop_size, samplerate)) return 1; + if (new_aubio_timestretch(mode, stretch, 0, samplerate)) return 1; + if (new_aubio_timestretch(mode, stretch, hop_size, 0)) return 1; + + aubio_timestretch_t *p = new_aubio_timestretch(mode, stretch, hop_size, + samplerate); +#ifdef HAVE_RUBBERBAND + if (!p) return 1; + + if (aubio_timestretch_get_latency(p) == 0) return 1; + + if (aubio_timestretch_get_samplerate(p) != samplerate) return 1; + + aubio_timestretch_reset(p); + + if (aubio_timestretch_get_transpose(p) != 0) return 1; + if (aubio_timestretch_set_transpose(p, 2.)) return 1; + if (fabs(aubio_timestretch_get_transpose(p) - 2.) >= 1e-6) return 1; + if (!aubio_timestretch_set_transpose(p, 200.)) return 1; + if (!aubio_timestretch_set_transpose(p, -200.)) return 1; + if (aubio_timestretch_set_transpose(p, 0.)) return 1; + + if (aubio_timestretch_get_pitchscale(p) != 1) return 1; + if (aubio_timestretch_set_pitchscale(p, 2.)) return 1; + if (fabs(aubio_timestretch_get_pitchscale(p) - 2.) >= 1e-6) return 1; + if (!aubio_timestretch_set_pitchscale(p, 0.)) return 1; + if (!aubio_timestretch_set_pitchscale(p, 6.)) return 1; + + if (aubio_timestretch_get_stretch(p) != stretch) return 1; + if (aubio_timestretch_set_stretch(p, 2.)) return 1; + if (fabs(aubio_timestretch_get_stretch(p) - 2.) >= 1e-6) return 1; + if (!aubio_timestretch_set_stretch(p, 0.)) return 1; + if (!aubio_timestretch_set_stretch(p, 41.)) return 1; + + del_aubio_timestretch(p); +#else + if (p) return 1; +#endif + + return run_on_default_source_and_sink(main); +} diff --git a/tests/src/io/test-sink_flac.c b/tests/src/io/test-sink_flac.c new file mode 100644 index 00000000..acc84287 --- /dev/null +++ b/tests/src/io/test-sink_flac.c @@ -0,0 +1,47 @@ +#define AUBIO_UNSTABLE 1 +#include +#include "utils_tests.h" + +#define aubio_sink_custom "flac" + +#ifdef HAVE_FLAC +// functions not exposed in the headers, declared here +typedef struct _aubio_sink_flac_t aubio_sink_flac_t; +extern aubio_sink_flac_t * new_aubio_sink_flac(const char_t *uri, + uint_t samplerate); +extern void del_aubio_sink_flac (aubio_sink_flac_t *s); +extern uint_t aubio_sink_flac_open(aubio_sink_flac_t *s); +extern uint_t aubio_sink_flac_close(aubio_sink_flac_t *s); +extern uint_t aubio_sink_flac_preset_channels(aubio_sink_flac_t *s, + uint_t channels); +extern uint_t aubio_sink_flac_preset_samplerate(aubio_sink_flac_t *s, + uint_t samplerate); +extern void aubio_sink_flac_do(aubio_sink_flac_t *s, fvec_t* write_data, + uint_t write); +extern void aubio_sink_flac_do_multi(aubio_sink_flac_t *s, + fmat_t *write_data, uint_t write); +extern uint_t aubio_sink_flac_get_channels(aubio_sink_flac_t *s); +extern uint_t aubio_sink_flac_get_samplerate(aubio_sink_flac_t *s); + +#define HAVE_AUBIO_SINK_CUSTOM +#define aubio_sink_custom_t aubio_sink_flac_t +#define new_aubio_sink_custom new_aubio_sink_flac +#define del_aubio_sink_custom del_aubio_sink_flac +#define aubio_sink_custom_do aubio_sink_flac_do +#define aubio_sink_custom_do_multi aubio_sink_flac_do_multi +#define aubio_sink_custom_close aubio_sink_flac_close +#define aubio_sink_custom_preset_samplerate aubio_sink_flac_preset_samplerate +#define aubio_sink_custom_preset_channels aubio_sink_flac_preset_channels +#define aubio_sink_custom_get_samplerate aubio_sink_flac_get_samplerate +#define aubio_sink_custom_get_channels aubio_sink_flac_get_channels +#endif /* HAVE_FLAC */ + +#include "base-sink_custom.h" + +// this file uses the unstable aubio api, please use aubio_sink instead +// see src/io/sink.h and tests/src/sink/test-sink.c + +int main (int argc, char **argv) +{ + return base_main(argc, argv); +} diff --git a/tests/src/io/test-sink_vorbis.c b/tests/src/io/test-sink_vorbis.c new file mode 100644 index 00000000..fe6bb2b0 --- /dev/null +++ b/tests/src/io/test-sink_vorbis.c @@ -0,0 +1,47 @@ +#define AUBIO_UNSTABLE 1 +#include +#include "utils_tests.h" + +#define aubio_sink_custom "vorbis" + +#ifdef HAVE_VORBISENC +// functions not exposed in the headers, declared here +typedef struct _aubio_sink_vorbis_t aubio_sink_vorbis_t; +extern aubio_sink_vorbis_t * new_aubio_sink_vorbis(const char_t *uri, + uint_t samplerate); +extern void del_aubio_sink_vorbis (aubio_sink_vorbis_t *s); +extern uint_t aubio_sink_vorbis_open(aubio_sink_vorbis_t *s); +extern uint_t aubio_sink_vorbis_close(aubio_sink_vorbis_t *s); +extern uint_t aubio_sink_vorbis_preset_channels(aubio_sink_vorbis_t *s, + uint_t channels); +extern uint_t aubio_sink_vorbis_preset_samplerate(aubio_sink_vorbis_t *s, + uint_t samplerate); +extern void aubio_sink_vorbis_do(aubio_sink_vorbis_t *s, fvec_t *write_data, + uint_t write); +extern void aubio_sink_vorbis_do_multi(aubio_sink_vorbis_t *s, + fmat_t *write_data, uint_t write); +extern uint_t aubio_sink_vorbis_get_channels(aubio_sink_vorbis_t *s); +extern uint_t aubio_sink_vorbis_get_samplerate(aubio_sink_vorbis_t *s); + +#define HAVE_AUBIO_SINK_CUSTOM +#define aubio_sink_custom_t aubio_sink_vorbis_t +#define new_aubio_sink_custom new_aubio_sink_vorbis +#define del_aubio_sink_custom del_aubio_sink_vorbis +#define aubio_sink_custom_do aubio_sink_vorbis_do +#define aubio_sink_custom_do_multi aubio_sink_vorbis_do_multi +#define aubio_sink_custom_close aubio_sink_vorbis_close +#define aubio_sink_custom_preset_samplerate aubio_sink_vorbis_preset_samplerate +#define aubio_sink_custom_preset_channels aubio_sink_vorbis_preset_channels +#define aubio_sink_custom_get_samplerate aubio_sink_vorbis_get_samplerate +#define aubio_sink_custom_get_channels aubio_sink_vorbis_get_channels +#endif /* HAVE_VORBISENC */ + +#include "base-sink_custom.h" + +// this file uses the unstable aubio api, please use aubio_sink instead +// see src/io/sink.h and tests/src/sink/test-sink.c + +int main (int argc, char **argv) +{ + return base_main(argc, argv); +} diff --git a/tests/src/spectral/test-dct.c b/tests/src/spectral/test-dct.c index 3ea31098..8c6e89be 100644 --- a/tests/src/spectral/test-dct.c +++ b/tests/src/spectral/test-dct.c @@ -1,5 +1,6 @@ #include #include "aubio.h" +#include "aubio_priv.h" #include "utils_tests.h" int main (void) @@ -32,7 +33,7 @@ int main (void) aubio_dct_do (dct, in, dctout); aubio_dct_rdo (dct, dctout, out); for (j = 0; j < in->length; j++) { - return_code += (fabsf(in->data[j] - out->data[j]) > 10.e-4); + return_code += (ABS(in->data[j] - out->data[j]) > 10.e-4); } } diff --git a/wscript b/wscript index d6523ce0..8ea98f37 100644 --- a/wscript +++ b/wscript @@ -11,6 +11,7 @@ # For more info about waf, see http://code.google.com/p/waf/ . import sys +import subprocess APPNAME = 'aubio' @@ -45,6 +46,12 @@ def options(ctx): help = 'whether to compile with (--build-type=release)' \ ' or without (--build-type=debug)' \ ' compiler opimizations [default: release]') + ctx.add_option('--debug', action = 'store_const', + dest = 'build_type', const = 'debug', + help = 'build in debug mode (see --build-type)') + ctx.add_option('--nodeps', action = 'store_const', + dest = 'nodeps', const = 'debug', + help = 'build with no external dependencies') add_option_enable_disable(ctx, 'fftw3f', default = False, help_str = 'compile with fftw3f instead of ooura (recommended)', help_disable_str = 'do not compile with fftw3f') @@ -66,9 +73,18 @@ def options(ctx): add_option_enable_disable(ctx, 'avcodec', default = None, help_str = 'compile with libavcodec (auto)', help_disable_str = 'disable libavcodec') + add_option_enable_disable(ctx, 'vorbis', default = None, + help_str = 'compile with libvorbis (auto)', + help_disable_str = 'disable libvorbis') + add_option_enable_disable(ctx, 'flac', default = None, + help_str = 'compile with libFLAC (auto)', + help_disable_str = 'disable libflac') add_option_enable_disable(ctx, 'samplerate', default = None, help_str = 'compile with samplerate (auto)', help_disable_str = 'disable samplerate') + add_option_enable_disable(ctx, 'rubberband', default = None, + help_str = 'compile with rubberband (auto)', + help_disable_str = 'disable rubberband') add_option_enable_disable(ctx, 'memcpy', default = True, help_str = 'use memcpy hacks (default)', help_disable_str = 'do not use memcpy hacks') @@ -123,6 +139,30 @@ def configure(ctx): if ctx.options.target_platform: target_platform = ctx.options.target_platform + if ctx.options.nodeps: + external_deps = [ + 'sndfile', + 'samplerate', + 'jack', + 'rubberband', + 'avcodec', + 'blas', + 'fftw3', + 'fftw3f', + 'flac', + 'vorbis', + ] + for d in external_deps: + if not hasattr(ctx.options, 'enable_' + d): + raise ctx.errors.ConfigurationError ('--enable-%s missing from options' % d) + if getattr(ctx.options, 'enable_' + d) == True: + msg = 'Option --nodeps can not be used along with --enable-%s' % d + raise ctx.errors.ConfigurationError (msg) + elif getattr(ctx.options, 'enable_' + d) is None: + msg = 'Option --nodeps used but automatic detection with --enable-%s' % d + ctx.msg('Warning', msg) + setattr(ctx.options, 'enable_' + d, False) + from waflib import Options if target_platform=='emscripten': @@ -195,13 +235,13 @@ def configure(ctx): ctx.env['cshlib_PATTERN'] = 'lib%s.dll' if target_platform == 'darwin' and ctx.options.enable_fat: - ctx.env.CFLAGS += ['-arch', 'i386', '-arch', 'x86_64'] - ctx.env.LINKFLAGS += ['-arch', 'i386', '-arch', 'x86_64'] + ctx.env.CFLAGS += ['-arch', 'arm64', '-arch', 'x86_64'] + ctx.env.LINKFLAGS += ['-arch', 'arm64', '-arch', 'x86_64'] MINSDKVER="10.4" ctx.env.CFLAGS += [ '-mmacosx-version-min=' + MINSDKVER ] ctx.env.LINKFLAGS += [ '-mmacosx-version-min=' + MINSDKVER ] - if target_platform in [ 'darwin', 'ios', 'iosimulator']: + if target_platform in [ 'darwin', 'ios', 'iosimulator' ]: if (ctx.options.enable_apple_audio != False): ctx.env.FRAMEWORK += ['CoreFoundation', 'AudioToolbox'] ctx.define('HAVE_SOURCE_APPLE_AUDIO', 1) @@ -218,16 +258,28 @@ def configure(ctx): ctx.msg('Checking for Accelerate framework', 'no (disabled)', color = 'YELLOW') - if target_platform in [ 'ios', 'iosimulator' ]: + if target_platform in [ 'ios', 'iosimulator', 'watchos', 'watchsimulator' ]: MINSDKVER="6.1" + xcodeslct_output = subprocess.check_output (['xcode-select', '--print-path']) + XCODEPATH = xcodeslct_output.decode(sys.stdout.encoding).strip() + if target_platform == 'ios': + SDKNAME = "iPhoneOS" + elif target_platform == 'iosimulator': + SDKNAME = "iPhoneSimulator" + elif target_platform == 'watchos': + SDKNAME = "WatchOS" + elif target_platform == 'watchsimulator': + SDKNAME = "WatchSimulator" + else: + raise ctx.errors.ConfigurationError ("Error: unknown target platform '" + + target_platform + "'") + DEVROOT = "%(XCODEPATH)s/Platforms/%(SDKNAME)s.platform/Developer" % locals() + SDKROOT = "%(DEVROOT)s/SDKs/%(SDKNAME)s.sdk" % locals() ctx.env.CFLAGS += ['-std=c99'] - if (ctx.options.enable_apple_audio != False): + if ctx.options.enable_apple_audio != False and target_platform.startswith ('ios'): ctx.define('HAVE_AUDIO_UNIT', 1) #ctx.env.FRAMEWORK += ['CoreFoundation', 'AudioToolbox'] if target_platform == 'ios': - DEVROOT = "/Applications/Xcode.app/Contents" - DEVROOT += "/Developer/Platforms/iPhoneOS.platform/Developer" - SDKROOT = "%(DEVROOT)s/SDKs/iPhoneOS.sdk" % locals() ctx.env.CFLAGS += [ '-fembed-bitcode' ] ctx.env.CFLAGS += [ '-arch', 'arm64' ] ctx.env.CFLAGS += [ '-arch', 'armv7' ] @@ -237,16 +289,27 @@ def configure(ctx): ctx.env.LINKFLAGS += ['-arch', 'armv7s'] ctx.env.CFLAGS += [ '-miphoneos-version-min=' + MINSDKVER ] ctx.env.LINKFLAGS += [ '-miphoneos-version-min=' + MINSDKVER ] - else: - DEVROOT = "/Applications/Xcode.app/Contents" - DEVROOT += "/Developer/Platforms/iPhoneSimulator.platform/Developer" - SDKROOT = "%(DEVROOT)s/SDKs/iPhoneSimulator.sdk" % locals() - ctx.env.CFLAGS += [ '-arch', 'i386' ] + elif target_platform == 'iosimulator': ctx.env.CFLAGS += [ '-arch', 'x86_64' ] - ctx.env.LINKFLAGS += ['-arch', 'i386'] + ctx.env.CFLAGS += [ '-arch', 'arm64' ] ctx.env.LINKFLAGS += ['-arch', 'x86_64'] + ctx.env.LINKFLAGS += ['-arch', 'arm64'] ctx.env.CFLAGS += [ '-mios-simulator-version-min=' + MINSDKVER ] ctx.env.LINKFLAGS += [ '-mios-simulator-version-min=' + MINSDKVER ] + elif target_platform == 'watchos': + ctx.env.CFLAGS += [ '-arch', 'armv7' ] + ctx.env.CFLAGS += [ '-arch', 'armv7s' ] + ctx.env.LINKFLAGS += ['-arch', 'armv7'] + ctx.env.LINKFLAGS += ['-arch', 'armv7s'] + ctx.env.CFLAGS += [ '-mwatchos-version-min=' + MINSDKVER ] + ctx.env.LINKFLAGS += [ '-mwatchos-version-min=' + MINSDKVER ] + elif target_platform == 'watchsimulator': + ctx.env.CFLAGS += [ '-arch', 'x86_64' ] + ctx.env.CFLAGS += [ '-arch', 'arm64' ] + ctx.env.LINKFLAGS += ['-arch', 'x86_64'] + ctx.env.LINKFLAGS += ['-arch', 'arm64'] + ctx.env.CFLAGS += [ '-mwatchsimulator-version-min=' + MINSDKVER ] + ctx.env.LINKFLAGS += [ '-mwatchsimulator-version-min=' + MINSDKVER ] ctx.env.CFLAGS += [ '-isysroot' , SDKROOT] ctx.env.LINKFLAGS += [ '-isysroot' , SDKROOT] @@ -381,6 +444,12 @@ def configure(ctx): args = '--cflags --libs samplerate >= 0.0.15', mandatory = ctx.options.enable_samplerate) + # check for librubberband + if (ctx.options.enable_rubberband != False): + ctx.check_cfg(package = 'rubberband', atleast_version = '1.3', + args = '--cflags --libs', + mandatory = ctx.options.enable_rubberband) + # check for jack if (ctx.options.enable_jack != False): ctx.check_cfg(package = 'jack', @@ -405,11 +474,6 @@ def configure(ctx): args = '--cflags --libs libswresample >= 1.2.0', uselib_store = 'SWRESAMPLE', mandatory = False) - if 'HAVE_SWRESAMPLE' not in ctx.env: - ctx.check_cfg(package = 'libavresample', - args = '--cflags --libs libavresample >= 1.0.1', - uselib_store = 'AVRESAMPLE', - mandatory = False) msg_check = 'Checking for all libav libraries' if 'HAVE_AVCODEC' not in ctx.env: @@ -418,18 +482,27 @@ def configure(ctx): ctx.msg(msg_check, 'not found (missing avformat)', color = 'YELLOW') elif 'HAVE_AVUTIL' not in ctx.env: ctx.msg(msg_check, 'not found (missing avutil)', color = 'YELLOW') - elif 'HAVE_SWRESAMPLE' not in ctx.env \ - and 'HAVE_AVRESAMPLE' not in ctx.env: - resample_missing = 'not found (avresample or swresample required)' + elif 'HAVE_SWRESAMPLE' not in ctx.env : + resample_missing = 'not found (missing swresample)' ctx.msg(msg_check, resample_missing, color = 'YELLOW') else: ctx.msg(msg_check, 'yes') - if 'HAVE_SWRESAMPLE' in ctx.env: - ctx.define('HAVE_SWRESAMPLE', 1) - elif 'HAVE_AVRESAMPLE' in ctx.env: - ctx.define('HAVE_AVRESAMPLE', 1) ctx.define('HAVE_LIBAV', 1) + # check for vorbisenc + if (ctx.options.enable_vorbis != False): + ctx.check_cfg(package = 'vorbisenc vorbis ogg', + args = '--cflags --libs', + uselib_store = 'VORBISENC', + mandatory = ctx.options.enable_vorbis) + + # check for flac + if (ctx.options.enable_flac != False): + ctx.check_cfg(package = 'flac', + args = '--cflags --libs', + uselib_store = 'FLAC', + mandatory = ctx.options.enable_flac) + if (ctx.options.enable_wavread != False): ctx.define('HAVE_WAVREAD', 1) ctx.msg('Checking if using source_wavread', @@ -502,7 +575,7 @@ def build(bld): bld.recurse('src') # add sub directories - if bld.env['DEST_OS'] not in ['ios', 'iosimulator', 'android']: + if bld.env['DEST_OS'] not in ['ios', 'iosimulator', 'watchos', 'watchsimulator', 'android']: if bld.env['DEST_OS']=='emscripten' and not bld.options.testcmd: bld.options.testcmd = 'node %s' if bld.options.enable_examples: