├── .github └── workflows │ ├── test_conda.yml │ └── test_pip.yml ├── .gitignore ├── LICENCE ├── README.md ├── data └── DCS_LI_QuartetB_Take03_S1_LRX_excerpt.wav ├── demo_libf0.ipynb ├── docs ├── .nojekyll ├── Makefile ├── build │ └── html │ │ ├── .buildinfo │ │ ├── .doctrees │ │ ├── environment.pickle │ │ ├── getting_started.doctree │ │ ├── index.doctree │ │ ├── index_pyin.doctree │ │ ├── index_salience.doctree │ │ ├── index_swipe.doctree │ │ ├── index_swipe_slim.doctree │ │ ├── index_utils.doctree │ │ └── index_yin.doctree │ │ ├── _modules │ │ ├── index.html │ │ └── libf0 │ │ │ ├── pyin.html │ │ │ ├── salience.html │ │ │ ├── swipe.html │ │ │ ├── swipe_slim.html │ │ │ ├── utils.html │ │ │ └── yin.html │ │ ├── _sources │ │ ├── getting_started.rst.txt │ │ ├── index.rst.txt │ │ ├── index_pyin.rst.txt │ │ ├── index_salience.rst.txt │ │ ├── index_swipe.rst.txt │ │ ├── index_swipe_slim.rst.txt │ │ ├── index_utils.rst.txt │ │ └── index_yin.rst.txt │ │ ├── _static │ │ ├── _sphinx_javascript_frameworks_compat.js │ │ ├── basic.css │ │ ├── css │ │ │ ├── badge_only.css │ │ │ ├── fonts │ │ │ │ ├── Roboto-Slab-Bold.woff │ │ │ │ ├── Roboto-Slab-Bold.woff2 │ │ │ │ ├── Roboto-Slab-Regular.woff │ │ │ │ ├── Roboto-Slab-Regular.woff2 │ │ │ │ ├── fontawesome-webfont.eot │ │ │ │ ├── fontawesome-webfont.svg │ │ │ │ ├── fontawesome-webfont.ttf │ │ │ │ ├── fontawesome-webfont.woff │ │ │ │ ├── fontawesome-webfont.woff2 │ │ │ │ ├── lato-bold-italic.woff │ │ │ │ ├── lato-bold-italic.woff2 │ │ │ │ ├── lato-bold.woff │ │ │ │ ├── lato-bold.woff2 │ │ │ │ ├── lato-normal-italic.woff │ │ │ │ ├── lato-normal-italic.woff2 │ │ │ │ ├── lato-normal.woff │ │ │ │ └── lato-normal.woff2 │ │ │ └── theme.css │ │ ├── doctools.js │ │ ├── documentation_options.js │ │ ├── file.png │ │ ├── fonts │ │ │ ├── FontAwesome.otf │ │ │ ├── Lato │ │ │ │ ├── lato-bold.eot │ │ │ │ ├── lato-bold.ttf │ │ │ │ ├── lato-bold.woff │ │ │ │ ├── lato-bold.woff2 │ │ │ │ ├── lato-bolditalic.eot │ │ │ │ ├── lato-bolditalic.ttf │ │ │ │ ├── lato-bolditalic.woff │ │ │ │ ├── lato-bolditalic.woff2 │ │ │ │ ├── lato-italic.eot │ │ │ │ ├── lato-italic.ttf │ │ │ │ ├── lato-italic.woff │ │ │ │ ├── lato-italic.woff2 │ │ │ │ ├── lato-regular.eot │ │ │ │ ├── lato-regular.ttf │ │ │ │ ├── lato-regular.woff │ │ │ │ └── lato-regular.woff2 │ │ │ ├── Roboto-Slab-Bold.woff │ │ │ ├── Roboto-Slab-Bold.woff2 │ │ │ ├── Roboto-Slab-Light.woff │ │ │ ├── Roboto-Slab-Light.woff2 │ │ │ ├── Roboto-Slab-Regular.woff │ │ │ ├── Roboto-Slab-Regular.woff2 │ │ │ ├── Roboto-Slab-Thin.woff │ │ │ ├── Roboto-Slab-Thin.woff2 │ │ │ ├── RobotoSlab │ │ │ │ ├── roboto-slab-v7-bold.eot │ │ │ │ ├── roboto-slab-v7-bold.ttf │ │ │ │ ├── roboto-slab-v7-bold.woff │ │ │ │ ├── roboto-slab-v7-bold.woff2 │ │ │ │ ├── roboto-slab-v7-regular.eot │ │ │ │ ├── roboto-slab-v7-regular.ttf │ │ │ │ ├── roboto-slab-v7-regular.woff │ │ │ │ └── roboto-slab-v7-regular.woff2 │ │ │ ├── fontawesome-webfont.eot │ │ │ ├── fontawesome-webfont.svg │ │ │ ├── fontawesome-webfont.ttf │ │ │ ├── fontawesome-webfont.woff │ │ │ ├── fontawesome-webfont.woff2 │ │ │ ├── lato-bold-italic.woff │ │ │ ├── lato-bold-italic.woff2 │ │ │ ├── lato-bold.woff │ │ │ ├── lato-bold.woff2 │ │ │ ├── lato-normal-italic.woff │ │ │ ├── lato-normal-italic.woff2 │ │ │ ├── lato-normal.woff │ │ │ └── lato-normal.woff2 │ │ ├── jquery-3.5.1.js │ │ ├── jquery-3.6.0.js │ │ ├── jquery.js │ │ ├── js │ │ │ ├── badge_only.js │ │ │ ├── html5shiv-printshiv.min.js │ │ │ ├── html5shiv.min.js │ │ │ ├── modernizr.min.js │ │ │ └── theme.js │ │ ├── language_data.js │ │ ├── libf0.png │ │ ├── minus.png │ │ ├── plus.png │ │ ├── pygments.css │ │ ├── searchtools.js │ │ ├── sphinx_highlight.js │ │ ├── underscore-1.12.0.js │ │ ├── underscore-1.13.1.js │ │ └── underscore.js │ │ ├── genindex.html │ │ ├── getting_started.html │ │ ├── index.html │ │ ├── index_pyin.html │ │ ├── index_salience.html │ │ ├── index_swipe.html │ │ ├── index_swipe_slim.html │ │ ├── index_utils.html │ │ ├── index_yin.html │ │ ├── objects.inv │ │ ├── py-modindex.html │ │ ├── search.html │ │ └── searchindex.js ├── index.html ├── make.bat └── source │ ├── _static │ └── libf0.png │ ├── conf.py │ ├── getting_started.rst │ ├── index.rst │ ├── index_pyin.rst │ ├── index_salience.rst │ ├── index_swipe.rst │ ├── index_swipe_slim.rst │ ├── index_utils.rst │ └── index_yin.rst ├── libf0 ├── __init__.py ├── pyin.py ├── salience.py ├── swipe.py ├── swipe_slim.py ├── utils.py └── yin.py ├── setup.py └── tests ├── __init__.py ├── __pycache__ └── conftest.cpython-38-pytest-7.1.2.pyc └── test_algorithms.py /.github/workflows/test_conda.yml: -------------------------------------------------------------------------------- 1 | name: build conda 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | build-linux: 11 | runs-on: ubuntu-latest 12 | strategy: 13 | max-parallel: 5 14 | 15 | steps: 16 | - uses: actions/checkout@v2 17 | - name: Set up Python 3.8 18 | uses: actions/setup-python@v2 19 | with: 20 | python-version: 3.8 21 | - name: Add conda to system path 22 | run: | 23 | # $CONDA is an environment variable pointing to the root of the miniconda directory 24 | echo $CONDA/bin >> $GITHUB_PATH 25 | - name: Install dependencies 26 | run: | 27 | conda install -c conda-forge libsndfile python=3.8 28 | python -m pip install -e .['tests'] 29 | - name: Lint with flake8 30 | run: | 31 | conda install flake8 32 | # stop the build if there are Python syntax errors or undefined names 33 | flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics 34 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide 35 | flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics 36 | - name: Test with pytest 37 | run: | 38 | pytest 39 | -------------------------------------------------------------------------------- /.github/workflows/test_pip.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a variety of Python versions 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions 3 | 4 | name: build 5 | 6 | on: 7 | push: 8 | branches: [ main ] 9 | pull_request: 10 | branches: [ main ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | strategy: 17 | fail-fast: false 18 | matrix: 19 | python-version: [3.7, 3.8, 3.9] 20 | 21 | steps: 22 | - uses: actions/checkout@v2 23 | - name: Set up Python ${{ matrix.python-version }} 24 | uses: actions/setup-python@v2 25 | with: 26 | python-version: ${{ matrix.python-version }} 27 | - name: Install libnsdfile 28 | run: | 29 | sudo apt update 30 | sudo apt install libsndfile1-dev libsndfile1 31 | - name: Install dependencies 32 | run: | 33 | python -m pip install --upgrade pip 34 | python -m pip install flake8 35 | python -m pip install -e .['tests'] 36 | python --version 37 | pip --version 38 | python -m pip list 39 | - name: Lint with flake8 40 | run: | 41 | # stop the build if there are Python syntax errors or undefined names 42 | flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics 43 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide 44 | flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics 45 | - name: Test with pytest 46 | run: | 47 | pytest 48 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | .DS_Store 3 | .idea 4 | 5 | # ignore files created when preparing package for PyPi 6 | build/ 7 | dist/ 8 | *.egg-info/ 9 | 10 | # ignore cache files 11 | .ipynb_checkpoints 12 | .pytest_cache 13 | 14 | # do not ignore build of docs 15 | !docs/build/ -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Sebastian Rosenzweig, Simon Schwär, Meinard Müller, International Audio Laboratories Erlangen, Germany. 4 | We thank the German Research Foundation (DFG) for various research grants that 5 | allow us for conducting fundamental research in music processing. 6 | The International Audio Laboratories Erlangen are a joint institution of the 7 | Friedrich-Alexander-Universität Erlangen-Nürnberg (FAU) and Fraunhofer 8 | Institute for Integrated Circuits IIS. 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a copy of 11 | this software and associated documentation files (the "Software"), to deal in 12 | the Software without restriction, including without limitation the rights to 13 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 14 | the Software, and to permit persons to whom the Software is furnished to do so, 15 | subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be included in all 18 | copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 22 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 23 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 24 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 25 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Python Package using Conda](https://github.com/groupmm/libf0/actions/workflows/test_conda.yml/badge.svg)](https://github.com/groupmm/libf0/actions/workflows/test_conda.yml) 2 | [![Python package](https://github.com/groupmm/libf0/actions/workflows/test_pip.yml/badge.svg)](https://github.com/groupmm/libf0/actions/workflows/test_pip.yml) 3 | 4 | 5 | # libf0 6 | 7 | This repository contains a Python package called libf0 which provides open-source implementations for four popular model-based F0-estimation approaches, YIN (Cheveigné & Kawahara, 2002), pYIN (Mauch & Dixon, 2014), an approach inspired by Melodia (Salamon & Gómez, 2012), and SWIPE (Camacho & Harris, 2008). 8 | 9 | If you use the libf0 in your research, please consider the following references. 10 | 11 | ## References 12 | 13 | Sebastian Rosenzweig, Simon Schwär, and Meinard Müller. 14 | [libf0: A Python Library for Fundamental Frequency Estimation.](https://archives.ismir.net/ismir2022/latebreaking/000003.pdf) 15 | In Late Breaking Demos of the International Society for Music Information Retrieval Conference (ISMIR), Bengaluru, India, 2022. 16 | 17 | Alain de Cheveigné and Hideki Kawahara. 18 | YIN, a fundamental frequency estimator for speech and music. Journal of the Acoustical Society of America (JASA), 111(4):1917–1930, 2002. 19 | 20 | Matthias Mauch and Simon Dixon. 21 | pYIN: A fundamental frequency estimator using probabilistic threshold distributions. In IEEE International Conference on Acoustics, Speech and Signal Processing (ICASSP), pages 659–663, Florence, Italy, 2014. 22 | 23 | Justin Salamon and Emilia Gómez. 24 | Melody extraction from polyphonic music signals using pitch contour characteristics. IEEE Transactions on Audio, Speech, and Language Processing, 20(6): 25 | 1759–1770, 2012. 26 | 27 | Arturo Camacho and John G. Harris. 28 | A sawtooth waveform inspired pitch estimator for speech and music. The Journal of the Acoustical Society of America, 124(3):1638–1652, 2008. 29 | 30 | Meinard Müller. Fundamentals of Music Processing – Using Python and Jupyter Notebooks. Springer Verlag, 2nd edition, 2021. ISBN 978-3-030-69807-2. doi: 10.1007/978-3-030-69808-9. 31 | 32 | 33 | ## Installing 34 | 35 | If you just want to try our example notebook, you can run it using Binder directly in your browser: [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/groupmm/libf0/HEAD) 36 | 37 | To install the libf0 locally, you can use the Python package manager pip: 38 | 39 | ``` 40 | pip install libf0 41 | ``` 42 | 43 | We recommend to do this inside a conda or virtual environment (requiring at least Python 3.7). 44 | If you want to run the example notebook locally, you **must** first install libf0 to resolve all dependencies. Then, you can clone this repository using 45 | 46 | ``` 47 | git clone https://github.com/groupmm/libf0.git 48 | ``` 49 | install Jupyter using 50 | 51 | ``` 52 | pip install jupyter 53 | ``` 54 | 55 | and then start the notebook server via 56 | 57 | ``` 58 | jupyter notebook 59 | ``` 60 | 61 | 62 | ## Documentation 63 | There is also an API documentation for libf0: 64 | 65 | https://groupmm.github.io/libf0 66 | 67 | ## Contributing 68 | 69 | We are happy for suggestions and contributions. We would be grateful for either directly contacting us via email (meinard.mueller@audiolabs-erlangen.de) or for creating an issue in our Github repository. Please do not submit a pull request without prior consultation with us. 70 | 71 | ## Tests 72 | 73 | We provide automated tests for each algorithm. To execute the test script, you will need to install extra requirements for testing: 74 | 75 | ``` 76 | pip install 'libf0[tests]' 77 | pytest tests 78 | ``` 79 | 80 | ## Licence 81 | 82 | The code for this toolbox is published under an MIT licence. 83 | 84 | ## Acknowledgements 85 | 86 | This work was supported by the German Research Foundation (MU 2686/13-1, SCHE 280/20-1). We thank Edgar Suárez and Vojtěch Pešek for helping with the implementations. Furthermore, we thank Fatemeh Eftekhar and Maryam Pirmoradi for testing the toolbox. The International Audio Laboratories Erlangen are a joint institution of the Friedrich-Alexander-Universität Erlangen-Nürnberg (FAU) and Fraunhofer Institute for Integrated Circuits IIS. 87 | -------------------------------------------------------------------------------- /data/DCS_LI_QuartetB_Take03_S1_LRX_excerpt.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/groupmm/libf0/0baf532b506e2489fd7abebcf229a3c7d9cb98b7/data/DCS_LI_QuartetB_Take03_S1_LRX_excerpt.wav -------------------------------------------------------------------------------- /demo_libf0.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "feeb7b4a", 6 | "metadata": {}, 7 | "source": [ 8 | "# libf0 - A Python Library for F0-Estimation in Music Recordings" 9 | ] 10 | }, 11 | { 12 | "cell_type": "code", 13 | "execution_count": null, 14 | "id": "8a81c6dc", 15 | "metadata": {}, 16 | "outputs": [], 17 | "source": [ 18 | "import numpy as np\n", 19 | "import librosa\n", 20 | "from scipy.interpolate import interp1d\n", 21 | "\n", 22 | "import IPython.display as ipd\n", 23 | "import matplotlib.pyplot as plt\n", 24 | "\n", 25 | "import libf0" 26 | ] 27 | }, 28 | { 29 | "cell_type": "code", 30 | "execution_count": null, 31 | "id": "82a6a26e-db62-41f0-9e8c-c718b1359752", 32 | "metadata": {}, 33 | "outputs": [], 34 | "source": [ 35 | "# Plot function\n", 36 | "def plot_f0_trajectory(Y_LF, t, f, f0, t_f0, figsize=(8.5, 3.4), xlim=(0, 11.5), ylim=(2000, 6000)):\n", 37 | " \"\"\"\n", 38 | " Plot a calculated f0 trajectory on the corresponding spectrogram\n", 39 | " \n", 40 | " Parameters\n", 41 | " ----------\n", 42 | " Y_LF : np.ndarray\n", 43 | " log-frequency spectrogram\n", 44 | " t : np.ndarray\n", 45 | " time axis of the spectrogram\n", 46 | " f : np.ndarray\n", 47 | " log-frequency axis of the spectrogram in cents\n", 48 | " f0 : np.ndarray\n", 49 | " f0 trajectory in cents\n", 50 | " t_f0 : np.ndarray\n", 51 | " time points of the f0 trajectory frames\n", 52 | " figsize : tuple\n", 53 | " figure size\n", 54 | " xlim : tuple\n", 55 | " x-limits\n", 56 | " ylim : tuple\n", 57 | " y-limits\n", 58 | " \"\"\"\n", 59 | " plt.figure(figsize=figsize)\n", 60 | "\n", 61 | " plt.imshow(Y_LF, cmap='gray_r', aspect='auto', origin='lower', extent=[t[0], t[-1], f[0], f[-1]])\n", 62 | " plt.plot(t_f0, f0, linestyle='', marker='.', markersize=5, color=[192/256, 0, 0])\n", 63 | "\n", 64 | " plt.xlim(xlim)\n", 65 | " plt.ylim(ylim)\n", 66 | "\n", 67 | " plt.gca().tick_params(axis='both', which='major', labelsize=10)\n", 68 | " plt.gca().tick_params(axis='both', which='minor', labelsize=10)\n", 69 | " \n", 70 | " plt.xlabel(\"Time (seconds)\", fontsize=12)\n", 71 | " plt.ylabel(\"Log-Frequency (cents)\", fontsize=12)\n", 72 | " \n", 73 | " cbar = plt.colorbar()\n", 74 | " cbar.ax.get_yaxis().labelpad = 15\n", 75 | " cbar.ax.set_ylabel('Log-Magnitude', rotation=270)\n", 76 | "\n", 77 | " plt.tight_layout()\n", 78 | " plt.show()" 79 | ] 80 | }, 81 | { 82 | "cell_type": "code", 83 | "execution_count": null, 84 | "id": "8f646465", 85 | "metadata": {}, 86 | "outputs": [], 87 | "source": [ 88 | "# load demo audio (a throat microphone recording of a soprano singer)\n", 89 | "fn_wav = \"./data/DCS_LI_QuartetB_Take03_S1_LRX_excerpt.wav\"\n", 90 | "x, Fs = librosa.load(fn_wav, sr=22050)\n", 91 | "ipd.display(ipd.Audio(x, rate=Fs, normalize=True)) # audio playback\n", 92 | "\n", 93 | "# shared parameters\n", 94 | "N = 2048 # window size in samples\n", 95 | "H = 256 # hop size in samples\n", 96 | "zero_pad = 2048 # zero-padding for STFT (only for visualization)\n", 97 | "F_min = 55.0 # minimum frequency of interest in Hz\n", 98 | "F_max = 1760.0 # maximum frequency of interest in Hz\n", 99 | "R = 10 # resolution of F0-estimations in cents\n", 100 | "\n", 101 | "# calculate magnitude spectrogram of input signal for visualization\n", 102 | "X = librosa.stft(x, n_fft=N+zero_pad, hop_length=H, win_length=N, window='hann', pad_mode='constant', center=True)\n", 103 | "Y = np.abs(X)\n", 104 | "F_coef_lin = librosa.fft_frequencies(sr=Fs, n_fft=N+zero_pad)\n", 105 | "T_coef = librosa.frames_to_time(np.arange(X.shape[1]), sr=Fs, hop_length=H)\n", 106 | "\n", 107 | "# interpolate magnitude spectrogram to a logarithmic frequency axis \n", 108 | "B = np.floor((1200 / R) * np.log2(F_max / F_min) + 0.5)\n", 109 | "F_coef_log_cents = np.arange(0, B) * R \n", 110 | "F_coef_log_hz = 2 ** (F_coef_log_cents / 1200) * F_min\n", 111 | "compute_Y_interpol = interp1d(F_coef_lin, Y, kind='cubic', axis=0)\n", 112 | "Y_LF = compute_Y_interpol(F_coef_log_hz)\n", 113 | "Y_LF[Y_LF < 0] = 0 # discard negative values after interpolation\n", 114 | "\n", 115 | "# use log-magnitude for visualizations\n", 116 | "Y_LF = np.log(1 + Y_LF)\n", 117 | "\n", 118 | "plot_f0_trajectory(Y_LF, T_coef, F_coef_log_cents, [], [])" 119 | ] 120 | }, 121 | { 122 | "cell_type": "markdown", 123 | "id": "a2d8f9fe", 124 | "metadata": {}, 125 | "source": [ 126 | "### YIN\n", 127 | "\n", 128 | "For algorithmic details, see:\n", 129 | "\n", 130 | "Alain de Cheveigné and Hideki Kawahara. YIN, a fundamental frequency estimator for speech and music. Journal of the Acoustical Society of America (JASA), 111(4):1917–1930, 2002." 131 | ] 132 | }, 133 | { 134 | "cell_type": "code", 135 | "execution_count": null, 136 | "id": "a9a165be", 137 | "metadata": {}, 138 | "outputs": [], 139 | "source": [ 140 | "# YIN parameters\n", 141 | "threshold = 0.15\n", 142 | "\n", 143 | "# run YIN algorithm\n", 144 | "f0_yin, t_yin, ap_yin = libf0.yin(x, Fs=Fs, N=N, H=H, F_min=F_min, F_max=F_max, threshold=threshold, verbose=True)\n", 145 | "\n", 146 | "# convert trajectory to cent scale\n", 147 | "f0_yin_cents = libf0.hz_to_cents(f0_yin, F_min)\n", 148 | "\n", 149 | "# plot the filtered result\n", 150 | "plot_f0_trajectory(Y_LF, T_coef, F_coef_log_cents, f0_yin_cents, t_yin)\n", 151 | "\n", 152 | "# sonify the filtered result (left: sonification, right: original audio)\n", 153 | "x_son_yin = libf0.sonify_trajectory_with_sinusoid(f0_yin, t_yin, len(x), Fs=Fs)\n", 154 | "ipd.display(ipd.Audio(np.vstack((x_son_yin.reshape(1, -1), x.reshape(1, -1))), rate=Fs, normalize=True))" 155 | ] 156 | }, 157 | { 158 | "cell_type": "markdown", 159 | "id": "f27baf76", 160 | "metadata": {}, 161 | "source": [ 162 | "### pYIN\n", 163 | "\n", 164 | "For algorithmic details, see:\n", 165 | "\n", 166 | "Matthias Mauch and Simon Dixon. pYIN: A fundamental frequency estimator using probabilistic threshold distributions. In IEEE International Conference on Acoustics, Speech and Signal Processing (ICASSP), pages 659–663, Florence, Italy, 2014." 167 | ] 168 | }, 169 | { 170 | "cell_type": "code", 171 | "execution_count": null, 172 | "id": "9986dfe1", 173 | "metadata": {}, 174 | "outputs": [], 175 | "source": [ 176 | "# set parameters\n", 177 | "thresholds = np.arange(0.01, 1, 0.01)\n", 178 | "R = 10 # bin resolution in cents\n", 179 | "\n", 180 | "# run pYIN algorithm\n", 181 | "f0_pyin, t_pyin, conf_pyin = libf0.pyin(x, Fs=Fs, N=N, H=H, F_min=F_min, F_max=F_max, R=R, thresholds=thresholds)\n", 182 | "\n", 183 | "# convert trajectory to cent scale\n", 184 | "f0_pyin_cents = libf0.hz_to_cents(f0_pyin, F_min)\n", 185 | "\n", 186 | "# plot the filtered result\n", 187 | "plot_f0_trajectory(Y_LF, T_coef, F_coef_log_cents, f0_pyin_cents, t_pyin)\n", 188 | "\n", 189 | "# sonify the filtered result (left: sonification, right: original audio)\n", 190 | "x_son_pyin = libf0.sonify_trajectory_with_sinusoid(f0_pyin, t_pyin, len(x), Fs=Fs)\n", 191 | "ipd.display(ipd.Audio(np.vstack((x_son_pyin.reshape(1, -1), x.reshape(1, -1))), rate=Fs, normalize=True))" 192 | ] 193 | }, 194 | { 195 | "cell_type": "markdown", 196 | "id": "5218adb5-862b-4315-a85d-375dc69cc0e1", 197 | "metadata": {}, 198 | "source": [ 199 | "### Salience Algorithm\n", 200 | "\n", 201 | "For algorithmic details, see:\n", 202 | "\n", 203 | "Justin Salamon and Emilia Gómez. Melody extraction from polyphonic music signals using pitch contour characteristics. IEEE Transactions on Audio, Speech, and Language Processing, 20(6): 1759–1770, 2012.\n", 204 | "\n", 205 | "Meinard Müller. Fundamentals of Music Processing – Using Python and Jupyter Notebooks. Springer\n", 206 | "Verlag, 2nd edition, 2021. ISBN 978-3-030-69807-2. doi: 10.1007/978-3-030-69808-9." 207 | ] 208 | }, 209 | { 210 | "cell_type": "code", 211 | "execution_count": null, 212 | "id": "b0f0edcb-1e8a-47e1-a40d-9a28dae69879", 213 | "metadata": {}, 214 | "outputs": [], 215 | "source": [ 216 | "# set parameters\n", 217 | "num_harm = 10 # number of harmonics for the summation\n", 218 | "freq_smoothing = 11 # length of the smoothing filter\n", 219 | "\n", 220 | "# run the salience algorithm\n", 221 | "f0_sal, t_sal, conf_sal = libf0.salience(x, Fs=Fs, N=N, H=H, F_min=F_min, F_max=F_max, R=R, num_harm=num_harm, freq_smooth_len=freq_smoothing)\n", 222 | "\n", 223 | "# convert trajectory to cent scale\n", 224 | "f0_sal_cents = libf0.hz_to_cents(f0_sal, F_min)\n", 225 | "\n", 226 | "# plot the result\n", 227 | "plot_f0_trajectory(Y_LF, T_coef, F_coef_log_cents, f0_sal_cents, t_sal)\n", 228 | "\n", 229 | "# sonify the result (left: sonification, right: original audio)\n", 230 | "x_son_sal = libf0.sonify_trajectory_with_sinusoid(f0_sal, t_sal, len(x), Fs=Fs)\n", 231 | "ipd.display(ipd.Audio(np.vstack((x_son_sal.reshape(1, -1), x.reshape(1, -1))), rate=Fs, normalize=True))" 232 | ] 233 | }, 234 | { 235 | "cell_type": "markdown", 236 | "id": "0eabab3f", 237 | "metadata": {}, 238 | "source": [ 239 | "### SWIPE\n", 240 | "\n", 241 | "For algorithmic details, see:\n", 242 | "\n", 243 | "Arturo Camacho and John G. Harris. A sawtooth waveform inspired pitch estimator for speech and music. The Journal of the Acoustical Society of America, 124(3):1638–1652, 2008." 244 | ] 245 | }, 246 | { 247 | "cell_type": "code", 248 | "execution_count": null, 249 | "id": "ee7a8ff4", 250 | "metadata": {}, 251 | "outputs": [], 252 | "source": [ 253 | "# set parameters\n", 254 | "threshold = 0.5 # confidence threshold between 0 and 1\n", 255 | "\n", 256 | "# run the SWIPE algorithm\n", 257 | "f0_swipe, t_swipe, conf_swipe = libf0.swipe(x, Fs, H, F_min, F_max, strength_threshold=threshold)\n", 258 | "\n", 259 | "# convert trajectory to cent scale\n", 260 | "f0_swipe_cents = libf0.hz_to_cents(f0_swipe, F_min)\n", 261 | "\n", 262 | "# plot the result\n", 263 | "plot_f0_trajectory(Y_LF, T_coef, F_coef_log_cents, f0_swipe_cents, t_swipe)\n", 264 | "\n", 265 | "# sonify the result (left: sonification, right: original audio)\n", 266 | "x_son_swipe = libf0.sonify_trajectory_with_sinusoid(f0_swipe, t_swipe, len(x), Fs=Fs)\n", 267 | "ipd.display(ipd.Audio(np.vstack((x_son_swipe.reshape(1, -1), x.reshape(1, -1))), rate=Fs, normalize=True))" 268 | ] 269 | }, 270 | { 271 | "cell_type": "markdown", 272 | "id": "08b2121c", 273 | "metadata": {}, 274 | "source": [ 275 | "### SWIPE (slim)\n", 276 | "\n", 277 | "A more efficient and didactic implementation of the SWIPE algorithm." 278 | ] 279 | }, 280 | { 281 | "cell_type": "code", 282 | "execution_count": null, 283 | "id": "bee19066", 284 | "metadata": {}, 285 | "outputs": [], 286 | "source": [ 287 | "# set parameters\n", 288 | "threshold = 0.5 # confidence threshold between 0 and 1\n", 289 | "\n", 290 | "# run the SWIPE algorithm\n", 291 | "f0_swipes, t_swipes, conf_swipes = libf0.swipe_slim(x, Fs, H, F_min, F_max, strength_threshold=threshold) # a simplified implementation\n", 292 | "\n", 293 | "# convert trajectory to cent scale\n", 294 | "f0_swipes_cents = libf0.hz_to_cents(f0_swipes, F_min)\n", 295 | "\n", 296 | "# plot the result\n", 297 | "plot_f0_trajectory(Y_LF, T_coef, F_coef_log_cents, f0_swipes_cents, t_swipe)\n", 298 | "\n", 299 | "# sonify the result (left: sonification, right: original audio)\n", 300 | "x_son_swipes = libf0.sonify_trajectory_with_sinusoid(f0_swipes, t_swipes, len(x), Fs=Fs)\n", 301 | "ipd.display(ipd.Audio(np.vstack((x_son_swipes.reshape(1, -1), x.reshape(1, -1))), rate=Fs, normalize=True))" 302 | ] 303 | }, 304 | { 305 | "cell_type": "code", 306 | "execution_count": null, 307 | "id": "b271aa6d", 308 | "metadata": {}, 309 | "outputs": [], 310 | "source": [] 311 | } 312 | ], 313 | "metadata": { 314 | "kernelspec": { 315 | "display_name": "Python 3", 316 | "language": "python", 317 | "name": "python3" 318 | }, 319 | "language_info": { 320 | "codemirror_mode": { 321 | "name": "ipython", 322 | "version": 3 323 | }, 324 | "file_extension": ".py", 325 | "mimetype": "text/x-python", 326 | "name": "python", 327 | "nbconvert_exporter": "python", 328 | "pygments_lexer": "ipython3", 329 | "version": "3.8.10" 330 | } 331 | }, 332 | "nbformat": 4, 333 | "nbformat_minor": 5 334 | } 335 | -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/groupmm/libf0/0baf532b506e2489fd7abebcf229a3c7d9cb98b7/docs/.nojekyll -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = source 9 | BUILDDIR = build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/build/html/.buildinfo: -------------------------------------------------------------------------------- 1 | # Sphinx build info version 1 2 | # This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. 3 | config: 8c91fd5622b39f1fe68671e5fb0fa0c2 4 | tags: 645f666f9bcd5a90fca523b33c5a78b7 5 | -------------------------------------------------------------------------------- /docs/build/html/.doctrees/environment.pickle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/groupmm/libf0/0baf532b506e2489fd7abebcf229a3c7d9cb98b7/docs/build/html/.doctrees/environment.pickle -------------------------------------------------------------------------------- /docs/build/html/.doctrees/getting_started.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/groupmm/libf0/0baf532b506e2489fd7abebcf229a3c7d9cb98b7/docs/build/html/.doctrees/getting_started.doctree -------------------------------------------------------------------------------- /docs/build/html/.doctrees/index.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/groupmm/libf0/0baf532b506e2489fd7abebcf229a3c7d9cb98b7/docs/build/html/.doctrees/index.doctree -------------------------------------------------------------------------------- /docs/build/html/.doctrees/index_pyin.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/groupmm/libf0/0baf532b506e2489fd7abebcf229a3c7d9cb98b7/docs/build/html/.doctrees/index_pyin.doctree -------------------------------------------------------------------------------- /docs/build/html/.doctrees/index_salience.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/groupmm/libf0/0baf532b506e2489fd7abebcf229a3c7d9cb98b7/docs/build/html/.doctrees/index_salience.doctree -------------------------------------------------------------------------------- /docs/build/html/.doctrees/index_swipe.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/groupmm/libf0/0baf532b506e2489fd7abebcf229a3c7d9cb98b7/docs/build/html/.doctrees/index_swipe.doctree -------------------------------------------------------------------------------- /docs/build/html/.doctrees/index_swipe_slim.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/groupmm/libf0/0baf532b506e2489fd7abebcf229a3c7d9cb98b7/docs/build/html/.doctrees/index_swipe_slim.doctree -------------------------------------------------------------------------------- /docs/build/html/.doctrees/index_utils.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/groupmm/libf0/0baf532b506e2489fd7abebcf229a3c7d9cb98b7/docs/build/html/.doctrees/index_utils.doctree -------------------------------------------------------------------------------- /docs/build/html/.doctrees/index_yin.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/groupmm/libf0/0baf532b506e2489fd7abebcf229a3c7d9cb98b7/docs/build/html/.doctrees/index_yin.doctree -------------------------------------------------------------------------------- /docs/build/html/_modules/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Overview: module code — libf0 1.0.2 documentation 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 59 | 60 |
64 | 65 |
66 |
67 |
68 |
    69 |
  • 70 | 71 |
  • 72 |
  • 73 |
74 |
75 |
76 |
77 |
78 | 79 |

All modules for which code is available

80 | 87 | 88 |
89 |
90 | 104 |
105 |
106 |
107 |
108 | 113 | 114 | 115 | -------------------------------------------------------------------------------- /docs/build/html/_sources/getting_started.rst.txt: -------------------------------------------------------------------------------- 1 | Getting Started 2 | =============== 3 | 4 | Installation 5 | ------------ 6 | 7 | With Python >= 3.6, you can install ``libf0`` using the Python package manager pip: 8 | 9 | .. code-block:: bash 10 | 11 | pip install libf0 12 | 13 | Contributing 14 | ------------ 15 | 16 | For development, testing, or generating the API documentation, clone the git repository and install: 17 | 18 | .. code-block:: bash 19 | 20 | git clone https://github.com/groupmm/libf0.git 21 | cd libf0 22 | pip install -e .[dev,tests,docs] 23 | -------------------------------------------------------------------------------- /docs/build/html/_sources/index.rst.txt: -------------------------------------------------------------------------------- 1 | .. libf0 documentation master file, created by 2 | sphinx-quickstart on Thu May 27 14:48:45 2021. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | libf0 7 | ================================== 8 | ``libf0`` is a Python toolbox for fundamental frequency (F0) estimation in music recordings. 9 | 10 | If you use this toolbox, please cite: 11 | 12 | .. [#] Sebastian Rosenzweig, Simon Schwär, and Meinard Müller. libf0: A Python Library for Fundamental Frequency Estimation. In Late Breaking Demos of the International Society for Music Information Retrieval Conference (ISMIR), Bengaluru, India, 2022. 13 | 14 | 15 | To reference the original algorithms implemented in this toolbox, please cite: 16 | 17 | .. [#] Alain De Cheveigné and Hideki Kawahara: YIN, a fundamental frequency estimator for speech and music, The Journal of the Acoustical Society of America 111.4 (2002): 1917-1930. 18 | .. [#] Matthias Mauch and Simon Dixon: PYIN: A fundamental frequency estimator using probabilistic threshold distributions, IEEE International Conference on Acoustics, Speech and Signal Processing (ICASSP) (2014): 659-663. 19 | .. [#] Justin Salamon and Emilia Gómez: Melody Extraction From Polyphonic Music Signals Using Pitch Contour Characteristics, IEEE Transactions on Audio, Speech, and Language Processing, vol. 20, no. 6, pp. 1759–1770, 2012. 20 | .. [#] Arturo Camacho and John G. Harris: A sawtooth waveform inspired pitch estimator for speech and music. The Journal of the Acoustical Society of America, vol. 124, no. 3, pp. 1638–1652, 2008 21 | .. [#] Meinard Müller. Fundamentals of Music Processing – Using Python and Jupyter Notebooks. Springer Verlag, 2nd edition, 2021. ISBN 978-3-030-69807-2. doi: 10.1007/978-3-030-69808-9. 22 | 23 | 24 | .. toctree:: 25 | :hidden: 26 | 27 | getting_started 28 | 29 | 30 | 31 | .. toctree:: 32 | :caption: API Documentation 33 | :maxdepth: 1 34 | :hidden: 35 | 36 | index_yin 37 | index_pyin 38 | index_salience 39 | index_swipe 40 | index_swipe_slim 41 | index_utils 42 | -------------------------------------------------------------------------------- /docs/build/html/_sources/index_pyin.rst.txt: -------------------------------------------------------------------------------- 1 | pyin 2 | ============== 3 | 4 | .. automodule:: libf0.pyin 5 | :members: 6 | :undoc-members: 7 | -------------------------------------------------------------------------------- /docs/build/html/_sources/index_salience.rst.txt: -------------------------------------------------------------------------------- 1 | salience 2 | ============== 3 | 4 | .. automodule:: libf0.salience 5 | :members: 6 | :undoc-members: 7 | -------------------------------------------------------------------------------- /docs/build/html/_sources/index_swipe.rst.txt: -------------------------------------------------------------------------------- 1 | swipe 2 | ============== 3 | 4 | .. automodule:: libf0.swipe 5 | :members: 6 | :undoc-members: 7 | -------------------------------------------------------------------------------- /docs/build/html/_sources/index_swipe_slim.rst.txt: -------------------------------------------------------------------------------- 1 | swipe_slim 2 | ============== 3 | 4 | .. automodule:: libf0.swipe_slim 5 | :members: 6 | :undoc-members: 7 | -------------------------------------------------------------------------------- /docs/build/html/_sources/index_utils.rst.txt: -------------------------------------------------------------------------------- 1 | utils 2 | ===== 3 | 4 | .. automodule:: libf0.utils 5 | :members: 6 | :undoc-members: 7 | -------------------------------------------------------------------------------- /docs/build/html/_sources/index_yin.rst.txt: -------------------------------------------------------------------------------- 1 | yin 2 | ============== 3 | 4 | .. automodule:: libf0.yin 5 | :members: 6 | :undoc-members: 7 | -------------------------------------------------------------------------------- /docs/build/html/_static/_sphinx_javascript_frameworks_compat.js: -------------------------------------------------------------------------------- 1 | /* 2 | * _sphinx_javascript_frameworks_compat.js 3 | * ~~~~~~~~~~ 4 | * 5 | * Compatability shim for jQuery and underscores.js. 6 | * 7 | * WILL BE REMOVED IN Sphinx 6.0 8 | * xref RemovedInSphinx60Warning 9 | * 10 | */ 11 | 12 | /** 13 | * select a different prefix for underscore 14 | */ 15 | $u = _.noConflict(); 16 | 17 | 18 | /** 19 | * small helper function to urldecode strings 20 | * 21 | * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent#Decoding_query_parameters_from_a_URL 22 | */ 23 | jQuery.urldecode = function(x) { 24 | if (!x) { 25 | return x 26 | } 27 | return decodeURIComponent(x.replace(/\+/g, ' ')); 28 | }; 29 | 30 | /** 31 | * small helper function to urlencode strings 32 | */ 33 | jQuery.urlencode = encodeURIComponent; 34 | 35 | /** 36 | * This function returns the parsed url parameters of the 37 | * current request. Multiple values per key are supported, 38 | * it will always return arrays of strings for the value parts. 39 | */ 40 | jQuery.getQueryParameters = function(s) { 41 | if (typeof s === 'undefined') 42 | s = document.location.search; 43 | var parts = s.substr(s.indexOf('?') + 1).split('&'); 44 | var result = {}; 45 | for (var i = 0; i < parts.length; i++) { 46 | var tmp = parts[i].split('=', 2); 47 | var key = jQuery.urldecode(tmp[0]); 48 | var value = jQuery.urldecode(tmp[1]); 49 | if (key in result) 50 | result[key].push(value); 51 | else 52 | result[key] = [value]; 53 | } 54 | return result; 55 | }; 56 | 57 | /** 58 | * highlight a given string on a jquery object by wrapping it in 59 | * span elements with the given class name. 60 | */ 61 | jQuery.fn.highlightText = function(text, className) { 62 | function highlight(node, addItems) { 63 | if (node.nodeType === 3) { 64 | var val = node.nodeValue; 65 | var pos = val.toLowerCase().indexOf(text); 66 | if (pos >= 0 && 67 | !jQuery(node.parentNode).hasClass(className) && 68 | !jQuery(node.parentNode).hasClass("nohighlight")) { 69 | var span; 70 | var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg"); 71 | if (isInSVG) { 72 | span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); 73 | } else { 74 | span = document.createElement("span"); 75 | span.className = className; 76 | } 77 | span.appendChild(document.createTextNode(val.substr(pos, text.length))); 78 | node.parentNode.insertBefore(span, node.parentNode.insertBefore( 79 | document.createTextNode(val.substr(pos + text.length)), 80 | node.nextSibling)); 81 | node.nodeValue = val.substr(0, pos); 82 | if (isInSVG) { 83 | var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); 84 | var bbox = node.parentElement.getBBox(); 85 | rect.x.baseVal.value = bbox.x; 86 | rect.y.baseVal.value = bbox.y; 87 | rect.width.baseVal.value = bbox.width; 88 | rect.height.baseVal.value = bbox.height; 89 | rect.setAttribute('class', className); 90 | addItems.push({ 91 | "parent": node.parentNode, 92 | "target": rect}); 93 | } 94 | } 95 | } 96 | else if (!jQuery(node).is("button, select, textarea")) { 97 | jQuery.each(node.childNodes, function() { 98 | highlight(this, addItems); 99 | }); 100 | } 101 | } 102 | var addItems = []; 103 | var result = this.each(function() { 104 | highlight(this, addItems); 105 | }); 106 | for (var i = 0; i < addItems.length; ++i) { 107 | jQuery(addItems[i].parent).before(addItems[i].target); 108 | } 109 | return result; 110 | }; 111 | 112 | /* 113 | * backward compatibility for jQuery.browser 114 | * This will be supported until firefox bug is fixed. 115 | */ 116 | if (!jQuery.browser) { 117 | jQuery.uaMatch = function(ua) { 118 | ua = ua.toLowerCase(); 119 | 120 | var match = /(chrome)[ \/]([\w.]+)/.exec(ua) || 121 | /(webkit)[ \/]([\w.]+)/.exec(ua) || 122 | /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || 123 | /(msie) ([\w.]+)/.exec(ua) || 124 | ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || 125 | []; 126 | 127 | return { 128 | browser: match[ 1 ] || "", 129 | version: match[ 2 ] || "0" 130 | }; 131 | }; 132 | jQuery.browser = {}; 133 | jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true; 134 | } 135 | -------------------------------------------------------------------------------- /docs/build/html/_static/css/badge_only.css: -------------------------------------------------------------------------------- 1 | .clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:FontAwesome;font-style:normal;font-weight:400;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#FontAwesome) format("svg")}.fa:before{font-family:FontAwesome;font-style:normal;font-weight:400;line-height:1}.fa:before,a .fa{text-decoration:inherit}.fa:before,a .fa,li .fa{display:inline-block}li .fa-large:before{width:1.875em}ul.fas{list-style-type:none;margin-left:2em;text-indent:-.8em}ul.fas li .fa{width:.8em}ul.fas li .fa-large:before{vertical-align:baseline}.fa-book:before,.icon-book:before{content:"\f02d"}.fa-caret-down:before,.icon-caret-down:before{content:"\f0d7"}.fa-caret-up:before,.icon-caret-up:before{content:"\f0d8"}.fa-caret-left:before,.icon-caret-left:before{content:"\f0d9"}.fa-caret-right:before,.icon-caret-right:before{content:"\f0da"}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60}.rst-versions .rst-current-version:after{clear:both;content:"";display:block}.rst-versions .rst-current-version .fa{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}} -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/Roboto-Slab-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/groupmm/libf0/0baf532b506e2489fd7abebcf229a3c7d9cb98b7/docs/build/html/_static/css/fonts/Roboto-Slab-Bold.woff -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/Roboto-Slab-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/groupmm/libf0/0baf532b506e2489fd7abebcf229a3c7d9cb98b7/docs/build/html/_static/css/fonts/Roboto-Slab-Bold.woff2 -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/Roboto-Slab-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/groupmm/libf0/0baf532b506e2489fd7abebcf229a3c7d9cb98b7/docs/build/html/_static/css/fonts/Roboto-Slab-Regular.woff -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/Roboto-Slab-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/groupmm/libf0/0baf532b506e2489fd7abebcf229a3c7d9cb98b7/docs/build/html/_static/css/fonts/Roboto-Slab-Regular.woff2 -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/groupmm/libf0/0baf532b506e2489fd7abebcf229a3c7d9cb98b7/docs/build/html/_static/css/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/groupmm/libf0/0baf532b506e2489fd7abebcf229a3c7d9cb98b7/docs/build/html/_static/css/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/groupmm/libf0/0baf532b506e2489fd7abebcf229a3c7d9cb98b7/docs/build/html/_static/css/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/groupmm/libf0/0baf532b506e2489fd7abebcf229a3c7d9cb98b7/docs/build/html/_static/css/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/lato-bold-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/groupmm/libf0/0baf532b506e2489fd7abebcf229a3c7d9cb98b7/docs/build/html/_static/css/fonts/lato-bold-italic.woff -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/lato-bold-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/groupmm/libf0/0baf532b506e2489fd7abebcf229a3c7d9cb98b7/docs/build/html/_static/css/fonts/lato-bold-italic.woff2 -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/lato-bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/groupmm/libf0/0baf532b506e2489fd7abebcf229a3c7d9cb98b7/docs/build/html/_static/css/fonts/lato-bold.woff -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/lato-bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/groupmm/libf0/0baf532b506e2489fd7abebcf229a3c7d9cb98b7/docs/build/html/_static/css/fonts/lato-bold.woff2 -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/lato-normal-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/groupmm/libf0/0baf532b506e2489fd7abebcf229a3c7d9cb98b7/docs/build/html/_static/css/fonts/lato-normal-italic.woff -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/lato-normal-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/groupmm/libf0/0baf532b506e2489fd7abebcf229a3c7d9cb98b7/docs/build/html/_static/css/fonts/lato-normal-italic.woff2 -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/lato-normal.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/groupmm/libf0/0baf532b506e2489fd7abebcf229a3c7d9cb98b7/docs/build/html/_static/css/fonts/lato-normal.woff -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/lato-normal.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/groupmm/libf0/0baf532b506e2489fd7abebcf229a3c7d9cb98b7/docs/build/html/_static/css/fonts/lato-normal.woff2 -------------------------------------------------------------------------------- /docs/build/html/_static/doctools.js: -------------------------------------------------------------------------------- 1 | /* 2 | * doctools.js 3 | * ~~~~~~~~~~~ 4 | * 5 | * Base JavaScript utilities for all Sphinx HTML documentation. 6 | * 7 | * :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS. 8 | * :license: BSD, see LICENSE for details. 9 | * 10 | */ 11 | "use strict"; 12 | 13 | const BLACKLISTED_KEY_CONTROL_ELEMENTS = new Set([ 14 | "TEXTAREA", 15 | "INPUT", 16 | "SELECT", 17 | "BUTTON", 18 | ]); 19 | 20 | const _ready = (callback) => { 21 | if (document.readyState !== "loading") { 22 | callback(); 23 | } else { 24 | document.addEventListener("DOMContentLoaded", callback); 25 | } 26 | }; 27 | 28 | /** 29 | * Small JavaScript module for the documentation. 30 | */ 31 | const Documentation = { 32 | init: () => { 33 | Documentation.initDomainIndexTable(); 34 | Documentation.initOnKeyListeners(); 35 | }, 36 | 37 | /** 38 | * i18n support 39 | */ 40 | TRANSLATIONS: {}, 41 | PLURAL_EXPR: (n) => (n === 1 ? 0 : 1), 42 | LOCALE: "unknown", 43 | 44 | // gettext and ngettext don't access this so that the functions 45 | // can safely bound to a different name (_ = Documentation.gettext) 46 | gettext: (string) => { 47 | const translated = Documentation.TRANSLATIONS[string]; 48 | switch (typeof translated) { 49 | case "undefined": 50 | return string; // no translation 51 | case "string": 52 | return translated; // translation exists 53 | default: 54 | return translated[0]; // (singular, plural) translation tuple exists 55 | } 56 | }, 57 | 58 | ngettext: (singular, plural, n) => { 59 | const translated = Documentation.TRANSLATIONS[singular]; 60 | if (typeof translated !== "undefined") 61 | return translated[Documentation.PLURAL_EXPR(n)]; 62 | return n === 1 ? singular : plural; 63 | }, 64 | 65 | addTranslations: (catalog) => { 66 | Object.assign(Documentation.TRANSLATIONS, catalog.messages); 67 | Documentation.PLURAL_EXPR = new Function( 68 | "n", 69 | `return (${catalog.plural_expr})` 70 | ); 71 | Documentation.LOCALE = catalog.locale; 72 | }, 73 | 74 | /** 75 | * helper function to focus on search bar 76 | */ 77 | focusSearchBar: () => { 78 | document.querySelectorAll("input[name=q]")[0]?.focus(); 79 | }, 80 | 81 | /** 82 | * Initialise the domain index toggle buttons 83 | */ 84 | initDomainIndexTable: () => { 85 | const toggler = (el) => { 86 | const idNumber = el.id.substr(7); 87 | const toggledRows = document.querySelectorAll(`tr.cg-${idNumber}`); 88 | if (el.src.substr(-9) === "minus.png") { 89 | el.src = `${el.src.substr(0, el.src.length - 9)}plus.png`; 90 | toggledRows.forEach((el) => (el.style.display = "none")); 91 | } else { 92 | el.src = `${el.src.substr(0, el.src.length - 8)}minus.png`; 93 | toggledRows.forEach((el) => (el.style.display = "")); 94 | } 95 | }; 96 | 97 | const togglerElements = document.querySelectorAll("img.toggler"); 98 | togglerElements.forEach((el) => 99 | el.addEventListener("click", (event) => toggler(event.currentTarget)) 100 | ); 101 | togglerElements.forEach((el) => (el.style.display = "")); 102 | if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) togglerElements.forEach(toggler); 103 | }, 104 | 105 | initOnKeyListeners: () => { 106 | // only install a listener if it is really needed 107 | if ( 108 | !DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS && 109 | !DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS 110 | ) 111 | return; 112 | 113 | document.addEventListener("keydown", (event) => { 114 | // bail for input elements 115 | if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; 116 | // bail with special keys 117 | if (event.altKey || event.ctrlKey || event.metaKey) return; 118 | 119 | if (!event.shiftKey) { 120 | switch (event.key) { 121 | case "ArrowLeft": 122 | if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; 123 | 124 | const prevLink = document.querySelector('link[rel="prev"]'); 125 | if (prevLink && prevLink.href) { 126 | window.location.href = prevLink.href; 127 | event.preventDefault(); 128 | } 129 | break; 130 | case "ArrowRight": 131 | if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; 132 | 133 | const nextLink = document.querySelector('link[rel="next"]'); 134 | if (nextLink && nextLink.href) { 135 | window.location.href = nextLink.href; 136 | event.preventDefault(); 137 | } 138 | break; 139 | } 140 | } 141 | 142 | // some keyboard layouts may need Shift to get / 143 | switch (event.key) { 144 | case "/": 145 | if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) break; 146 | Documentation.focusSearchBar(); 147 | event.preventDefault(); 148 | } 149 | }); 150 | }, 151 | }; 152 | 153 | // quick alias for translations 154 | const _ = Documentation.gettext; 155 | 156 | _ready(Documentation.init); 157 | -------------------------------------------------------------------------------- /docs/build/html/_static/documentation_options.js: -------------------------------------------------------------------------------- 1 | var DOCUMENTATION_OPTIONS = { 2 | URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'), 3 | VERSION: '1.0.2', 4 | LANGUAGE: 'en', 5 | COLLAPSE_INDEX: false, 6 | BUILDER: 'html', 7 | FILE_SUFFIX: '.html', 8 | LINK_SUFFIX: '.html', 9 | HAS_SOURCE: true, 10 | SOURCELINK_SUFFIX: '.txt', 11 | NAVIGATION_WITH_KEYS: false, 12 | SHOW_SEARCH_SUMMARY: true, 13 | ENABLE_SEARCH_SHORTCUTS: true, 14 | }; -------------------------------------------------------------------------------- /docs/build/html/_static/file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/groupmm/libf0/0baf532b506e2489fd7abebcf229a3c7d9cb98b7/docs/build/html/_static/file.png -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/groupmm/libf0/0baf532b506e2489fd7abebcf229a3c7d9cb98b7/docs/build/html/_static/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/Lato/lato-bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/groupmm/libf0/0baf532b506e2489fd7abebcf229a3c7d9cb98b7/docs/build/html/_static/fonts/Lato/lato-bold.eot -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/Lato/lato-bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/groupmm/libf0/0baf532b506e2489fd7abebcf229a3c7d9cb98b7/docs/build/html/_static/fonts/Lato/lato-bold.ttf -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/Lato/lato-bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/groupmm/libf0/0baf532b506e2489fd7abebcf229a3c7d9cb98b7/docs/build/html/_static/fonts/Lato/lato-bold.woff -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/Lato/lato-bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/groupmm/libf0/0baf532b506e2489fd7abebcf229a3c7d9cb98b7/docs/build/html/_static/fonts/Lato/lato-bold.woff2 -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/Lato/lato-bolditalic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/groupmm/libf0/0baf532b506e2489fd7abebcf229a3c7d9cb98b7/docs/build/html/_static/fonts/Lato/lato-bolditalic.eot -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/Lato/lato-bolditalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/groupmm/libf0/0baf532b506e2489fd7abebcf229a3c7d9cb98b7/docs/build/html/_static/fonts/Lato/lato-bolditalic.ttf -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/Lato/lato-bolditalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/groupmm/libf0/0baf532b506e2489fd7abebcf229a3c7d9cb98b7/docs/build/html/_static/fonts/Lato/lato-bolditalic.woff -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/Lato/lato-bolditalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/groupmm/libf0/0baf532b506e2489fd7abebcf229a3c7d9cb98b7/docs/build/html/_static/fonts/Lato/lato-bolditalic.woff2 -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/Lato/lato-italic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/groupmm/libf0/0baf532b506e2489fd7abebcf229a3c7d9cb98b7/docs/build/html/_static/fonts/Lato/lato-italic.eot -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/Lato/lato-italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/groupmm/libf0/0baf532b506e2489fd7abebcf229a3c7d9cb98b7/docs/build/html/_static/fonts/Lato/lato-italic.ttf -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/Lato/lato-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/groupmm/libf0/0baf532b506e2489fd7abebcf229a3c7d9cb98b7/docs/build/html/_static/fonts/Lato/lato-italic.woff -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/Lato/lato-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/groupmm/libf0/0baf532b506e2489fd7abebcf229a3c7d9cb98b7/docs/build/html/_static/fonts/Lato/lato-italic.woff2 -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/Lato/lato-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/groupmm/libf0/0baf532b506e2489fd7abebcf229a3c7d9cb98b7/docs/build/html/_static/fonts/Lato/lato-regular.eot -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/Lato/lato-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/groupmm/libf0/0baf532b506e2489fd7abebcf229a3c7d9cb98b7/docs/build/html/_static/fonts/Lato/lato-regular.ttf -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/Lato/lato-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/groupmm/libf0/0baf532b506e2489fd7abebcf229a3c7d9cb98b7/docs/build/html/_static/fonts/Lato/lato-regular.woff -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/Lato/lato-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/groupmm/libf0/0baf532b506e2489fd7abebcf229a3c7d9cb98b7/docs/build/html/_static/fonts/Lato/lato-regular.woff2 -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/Roboto-Slab-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/groupmm/libf0/0baf532b506e2489fd7abebcf229a3c7d9cb98b7/docs/build/html/_static/fonts/Roboto-Slab-Bold.woff -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/Roboto-Slab-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/groupmm/libf0/0baf532b506e2489fd7abebcf229a3c7d9cb98b7/docs/build/html/_static/fonts/Roboto-Slab-Bold.woff2 -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/Roboto-Slab-Light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/groupmm/libf0/0baf532b506e2489fd7abebcf229a3c7d9cb98b7/docs/build/html/_static/fonts/Roboto-Slab-Light.woff -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/Roboto-Slab-Light.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/groupmm/libf0/0baf532b506e2489fd7abebcf229a3c7d9cb98b7/docs/build/html/_static/fonts/Roboto-Slab-Light.woff2 -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/Roboto-Slab-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/groupmm/libf0/0baf532b506e2489fd7abebcf229a3c7d9cb98b7/docs/build/html/_static/fonts/Roboto-Slab-Regular.woff -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/Roboto-Slab-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/groupmm/libf0/0baf532b506e2489fd7abebcf229a3c7d9cb98b7/docs/build/html/_static/fonts/Roboto-Slab-Regular.woff2 -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/Roboto-Slab-Thin.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/groupmm/libf0/0baf532b506e2489fd7abebcf229a3c7d9cb98b7/docs/build/html/_static/fonts/Roboto-Slab-Thin.woff -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/Roboto-Slab-Thin.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/groupmm/libf0/0baf532b506e2489fd7abebcf229a3c7d9cb98b7/docs/build/html/_static/fonts/Roboto-Slab-Thin.woff2 -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/groupmm/libf0/0baf532b506e2489fd7abebcf229a3c7d9cb98b7/docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/groupmm/libf0/0baf532b506e2489fd7abebcf229a3c7d9cb98b7/docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/groupmm/libf0/0baf532b506e2489fd7abebcf229a3c7d9cb98b7/docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/groupmm/libf0/0baf532b506e2489fd7abebcf229a3c7d9cb98b7/docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/groupmm/libf0/0baf532b506e2489fd7abebcf229a3c7d9cb98b7/docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/groupmm/libf0/0baf532b506e2489fd7abebcf229a3c7d9cb98b7/docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/groupmm/libf0/0baf532b506e2489fd7abebcf229a3c7d9cb98b7/docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/groupmm/libf0/0baf532b506e2489fd7abebcf229a3c7d9cb98b7/docs/build/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/groupmm/libf0/0baf532b506e2489fd7abebcf229a3c7d9cb98b7/docs/build/html/_static/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/groupmm/libf0/0baf532b506e2489fd7abebcf229a3c7d9cb98b7/docs/build/html/_static/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/groupmm/libf0/0baf532b506e2489fd7abebcf229a3c7d9cb98b7/docs/build/html/_static/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/groupmm/libf0/0baf532b506e2489fd7abebcf229a3c7d9cb98b7/docs/build/html/_static/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/lato-bold-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/groupmm/libf0/0baf532b506e2489fd7abebcf229a3c7d9cb98b7/docs/build/html/_static/fonts/lato-bold-italic.woff -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/lato-bold-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/groupmm/libf0/0baf532b506e2489fd7abebcf229a3c7d9cb98b7/docs/build/html/_static/fonts/lato-bold-italic.woff2 -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/lato-bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/groupmm/libf0/0baf532b506e2489fd7abebcf229a3c7d9cb98b7/docs/build/html/_static/fonts/lato-bold.woff -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/lato-bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/groupmm/libf0/0baf532b506e2489fd7abebcf229a3c7d9cb98b7/docs/build/html/_static/fonts/lato-bold.woff2 -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/lato-normal-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/groupmm/libf0/0baf532b506e2489fd7abebcf229a3c7d9cb98b7/docs/build/html/_static/fonts/lato-normal-italic.woff -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/lato-normal-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/groupmm/libf0/0baf532b506e2489fd7abebcf229a3c7d9cb98b7/docs/build/html/_static/fonts/lato-normal-italic.woff2 -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/lato-normal.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/groupmm/libf0/0baf532b506e2489fd7abebcf229a3c7d9cb98b7/docs/build/html/_static/fonts/lato-normal.woff -------------------------------------------------------------------------------- /docs/build/html/_static/fonts/lato-normal.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/groupmm/libf0/0baf532b506e2489fd7abebcf229a3c7d9cb98b7/docs/build/html/_static/fonts/lato-normal.woff2 -------------------------------------------------------------------------------- /docs/build/html/_static/js/badge_only.js: -------------------------------------------------------------------------------- 1 | !function(e){var t={};function r(n){if(t[n])return t[n].exports;var o=t[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports}r.m=e,r.c=t,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)r.d(n,o,function(t){return e[t]}.bind(null,o));return n},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=4)}({4:function(e,t,r){}}); -------------------------------------------------------------------------------- /docs/build/html/_static/js/html5shiv-printshiv.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @preserve HTML5 Shiv 3.7.3-pre | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed 3 | */ 4 | !function(a,b){function c(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function d(){var a=y.elements;return"string"==typeof a?a.split(" "):a}function e(a,b){var c=y.elements;"string"!=typeof c&&(c=c.join(" ")),"string"!=typeof a&&(a=a.join(" ")),y.elements=c+" "+a,j(b)}function f(a){var b=x[a[v]];return b||(b={},w++,a[v]=w,x[w]=b),b}function g(a,c,d){if(c||(c=b),q)return c.createElement(a);d||(d=f(c));var e;return e=d.cache[a]?d.cache[a].cloneNode():u.test(a)?(d.cache[a]=d.createElem(a)).cloneNode():d.createElem(a),!e.canHaveChildren||t.test(a)||e.tagUrn?e:d.frag.appendChild(e)}function h(a,c){if(a||(a=b),q)return a.createDocumentFragment();c=c||f(a);for(var e=c.frag.cloneNode(),g=0,h=d(),i=h.length;i>g;g++)e.createElement(h[g]);return e}function i(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return y.shivMethods?g(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+d().join().replace(/[\w\-:]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(y,b.frag)}function j(a){a||(a=b);var d=f(a);return!y.shivCSS||p||d.hasCSS||(d.hasCSS=!!c(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),q||i(a,d),a}function k(a){for(var b,c=a.getElementsByTagName("*"),e=c.length,f=RegExp("^(?:"+d().join("|")+")$","i"),g=[];e--;)b=c[e],f.test(b.nodeName)&&g.push(b.applyElement(l(b)));return g}function l(a){for(var b,c=a.attributes,d=c.length,e=a.ownerDocument.createElement(A+":"+a.nodeName);d--;)b=c[d],b.specified&&e.setAttribute(b.nodeName,b.nodeValue);return e.style.cssText=a.style.cssText,e}function m(a){for(var b,c=a.split("{"),e=c.length,f=RegExp("(^|[\\s,>+~])("+d().join("|")+")(?=[[\\s,>+~#.:]|$)","gi"),g="$1"+A+"\\:$2";e--;)b=c[e]=c[e].split("}"),b[b.length-1]=b[b.length-1].replace(f,g),c[e]=b.join("}");return c.join("{")}function n(a){for(var b=a.length;b--;)a[b].removeNode()}function o(a){function b(){clearTimeout(g._removeSheetTimer),d&&d.removeNode(!0),d=null}var d,e,g=f(a),h=a.namespaces,i=a.parentWindow;return!B||a.printShived?a:("undefined"==typeof h[A]&&h.add(A),i.attachEvent("onbeforeprint",function(){b();for(var f,g,h,i=a.styleSheets,j=[],l=i.length,n=Array(l);l--;)n[l]=i[l];for(;h=n.pop();)if(!h.disabled&&z.test(h.media)){try{f=h.imports,g=f.length}catch(o){g=0}for(l=0;g>l;l++)n.push(f[l]);try{j.push(h.cssText)}catch(o){}}j=m(j.reverse().join("")),e=k(a),d=c(a,j)}),i.attachEvent("onafterprint",function(){n(e),clearTimeout(g._removeSheetTimer),g._removeSheetTimer=setTimeout(b,500)}),a.printShived=!0,a)}var p,q,r="3.7.3",s=a.html5||{},t=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,u=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,v="_html5shiv",w=0,x={};!function(){try{var a=b.createElement("a");a.innerHTML="",p="hidden"in a,q=1==a.childNodes.length||function(){b.createElement("a");var a=b.createDocumentFragment();return"undefined"==typeof a.cloneNode||"undefined"==typeof a.createDocumentFragment||"undefined"==typeof a.createElement}()}catch(c){p=!0,q=!0}}();var y={elements:s.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video",version:r,shivCSS:s.shivCSS!==!1,supportsUnknownElements:q,shivMethods:s.shivMethods!==!1,type:"default",shivDocument:j,createElement:g,createDocumentFragment:h,addElements:e};a.html5=y,j(b);var z=/^$|\b(?:all|print)\b/,A="html5shiv",B=!q&&function(){var c=b.documentElement;return!("undefined"==typeof b.namespaces||"undefined"==typeof b.parentWindow||"undefined"==typeof c.applyElement||"undefined"==typeof c.removeNode||"undefined"==typeof a.attachEvent)}();y.type+=" print",y.shivPrint=o,o(b),"object"==typeof module&&module.exports&&(module.exports=y)}("undefined"!=typeof window?window:this,document); -------------------------------------------------------------------------------- /docs/build/html/_static/js/html5shiv.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @preserve HTML5 Shiv 3.7.3 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed 3 | */ 4 | !function(a,b){function c(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function d(){var a=t.elements;return"string"==typeof a?a.split(" "):a}function e(a,b){var c=t.elements;"string"!=typeof c&&(c=c.join(" ")),"string"!=typeof a&&(a=a.join(" ")),t.elements=c+" "+a,j(b)}function f(a){var b=s[a[q]];return b||(b={},r++,a[q]=r,s[r]=b),b}function g(a,c,d){if(c||(c=b),l)return c.createElement(a);d||(d=f(c));var e;return e=d.cache[a]?d.cache[a].cloneNode():p.test(a)?(d.cache[a]=d.createElem(a)).cloneNode():d.createElem(a),!e.canHaveChildren||o.test(a)||e.tagUrn?e:d.frag.appendChild(e)}function h(a,c){if(a||(a=b),l)return a.createDocumentFragment();c=c||f(a);for(var e=c.frag.cloneNode(),g=0,h=d(),i=h.length;i>g;g++)e.createElement(h[g]);return e}function i(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return t.shivMethods?g(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+d().join().replace(/[\w\-:]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(t,b.frag)}function j(a){a||(a=b);var d=f(a);return!t.shivCSS||k||d.hasCSS||(d.hasCSS=!!c(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),l||i(a,d),a}var k,l,m="3.7.3-pre",n=a.html5||{},o=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,p=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,q="_html5shiv",r=0,s={};!function(){try{var a=b.createElement("a");a.innerHTML="",k="hidden"in a,l=1==a.childNodes.length||function(){b.createElement("a");var a=b.createDocumentFragment();return"undefined"==typeof a.cloneNode||"undefined"==typeof a.createDocumentFragment||"undefined"==typeof a.createElement}()}catch(c){k=!0,l=!0}}();var t={elements:n.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video",version:m,shivCSS:n.shivCSS!==!1,supportsUnknownElements:l,shivMethods:n.shivMethods!==!1,type:"default",shivDocument:j,createElement:g,createDocumentFragment:h,addElements:e};a.html5=t,j(b),"object"==typeof module&&module.exports&&(module.exports=t)}("undefined"!=typeof window?window:this,document); -------------------------------------------------------------------------------- /docs/build/html/_static/js/modernizr.min.js: -------------------------------------------------------------------------------- 1 | /* Modernizr 2.6.2 (Custom Build) | MIT & BSD 2 | * Build: http://modernizr.com/download/#-fontface-backgroundsize-borderimage-borderradius-boxshadow-flexbox-hsla-multiplebgs-opacity-rgba-textshadow-cssanimations-csscolumns-generatedcontent-cssgradients-cssreflections-csstransforms-csstransforms3d-csstransitions-applicationcache-canvas-canvastext-draganddrop-hashchange-history-audio-video-indexeddb-input-inputtypes-localstorage-postmessage-sessionstorage-websockets-websqldatabase-webworkers-geolocation-inlinesvg-smil-svg-svgclippaths-touch-webgl-shiv-mq-cssclasses-addtest-prefixed-teststyles-testprop-testallprops-hasevent-prefixes-domprefixes-load 3 | */ 4 | ;window.Modernizr=function(a,b,c){function D(a){j.cssText=a}function E(a,b){return D(n.join(a+";")+(b||""))}function F(a,b){return typeof a===b}function G(a,b){return!!~(""+a).indexOf(b)}function H(a,b){for(var d in a){var e=a[d];if(!G(e,"-")&&j[e]!==c)return b=="pfx"?e:!0}return!1}function I(a,b,d){for(var e in a){var f=b[a[e]];if(f!==c)return d===!1?a[e]:F(f,"function")?f.bind(d||b):f}return!1}function J(a,b,c){var d=a.charAt(0).toUpperCase()+a.slice(1),e=(a+" "+p.join(d+" ")+d).split(" ");return F(b,"string")||F(b,"undefined")?H(e,b):(e=(a+" "+q.join(d+" ")+d).split(" "),I(e,b,c))}function K(){e.input=function(c){for(var d=0,e=c.length;d',a,""].join(""),l.id=h,(m?l:n).innerHTML+=f,n.appendChild(l),m||(n.style.background="",n.style.overflow="hidden",k=g.style.overflow,g.style.overflow="hidden",g.appendChild(n)),i=c(l,a),m?l.parentNode.removeChild(l):(n.parentNode.removeChild(n),g.style.overflow=k),!!i},z=function(b){var c=a.matchMedia||a.msMatchMedia;if(c)return c(b).matches;var d;return y("@media "+b+" { #"+h+" { position: absolute; } }",function(b){d=(a.getComputedStyle?getComputedStyle(b,null):b.currentStyle)["position"]=="absolute"}),d},A=function(){function d(d,e){e=e||b.createElement(a[d]||"div"),d="on"+d;var f=d in e;return f||(e.setAttribute||(e=b.createElement("div")),e.setAttribute&&e.removeAttribute&&(e.setAttribute(d,""),f=F(e[d],"function"),F(e[d],"undefined")||(e[d]=c),e.removeAttribute(d))),e=null,f}var a={select:"input",change:"input",submit:"form",reset:"form",error:"img",load:"img",abort:"img"};return d}(),B={}.hasOwnProperty,C;!F(B,"undefined")&&!F(B.call,"undefined")?C=function(a,b){return B.call(a,b)}:C=function(a,b){return b in a&&F(a.constructor.prototype[b],"undefined")},Function.prototype.bind||(Function.prototype.bind=function(b){var c=this;if(typeof c!="function")throw new TypeError;var d=w.call(arguments,1),e=function(){if(this instanceof e){var a=function(){};a.prototype=c.prototype;var f=new a,g=c.apply(f,d.concat(w.call(arguments)));return Object(g)===g?g:f}return c.apply(b,d.concat(w.call(arguments)))};return e}),s.flexbox=function(){return J("flexWrap")},s.canvas=function(){var a=b.createElement("canvas");return!!a.getContext&&!!a.getContext("2d")},s.canvastext=function(){return!!e.canvas&&!!F(b.createElement("canvas").getContext("2d").fillText,"function")},s.webgl=function(){return!!a.WebGLRenderingContext},s.touch=function(){var c;return"ontouchstart"in a||a.DocumentTouch&&b instanceof DocumentTouch?c=!0:y(["@media (",n.join("touch-enabled),("),h,")","{#modernizr{top:9px;position:absolute}}"].join(""),function(a){c=a.offsetTop===9}),c},s.geolocation=function(){return"geolocation"in navigator},s.postmessage=function(){return!!a.postMessage},s.websqldatabase=function(){return!!a.openDatabase},s.indexedDB=function(){return!!J("indexedDB",a)},s.hashchange=function(){return A("hashchange",a)&&(b.documentMode===c||b.documentMode>7)},s.history=function(){return!!a.history&&!!history.pushState},s.draganddrop=function(){var a=b.createElement("div");return"draggable"in a||"ondragstart"in a&&"ondrop"in a},s.websockets=function(){return"WebSocket"in a||"MozWebSocket"in a},s.rgba=function(){return D("background-color:rgba(150,255,150,.5)"),G(j.backgroundColor,"rgba")},s.hsla=function(){return D("background-color:hsla(120,40%,100%,.5)"),G(j.backgroundColor,"rgba")||G(j.backgroundColor,"hsla")},s.multiplebgs=function(){return D("background:url(https://),url(https://),red url(https://)"),/(url\s*\(.*?){3}/.test(j.background)},s.backgroundsize=function(){return J("backgroundSize")},s.borderimage=function(){return J("borderImage")},s.borderradius=function(){return J("borderRadius")},s.boxshadow=function(){return J("boxShadow")},s.textshadow=function(){return b.createElement("div").style.textShadow===""},s.opacity=function(){return E("opacity:.55"),/^0.55$/.test(j.opacity)},s.cssanimations=function(){return J("animationName")},s.csscolumns=function(){return J("columnCount")},s.cssgradients=function(){var a="background-image:",b="gradient(linear,left top,right bottom,from(#9f9),to(white));",c="linear-gradient(left top,#9f9, white);";return D((a+"-webkit- ".split(" ").join(b+a)+n.join(c+a)).slice(0,-a.length)),G(j.backgroundImage,"gradient")},s.cssreflections=function(){return J("boxReflect")},s.csstransforms=function(){return!!J("transform")},s.csstransforms3d=function(){var a=!!J("perspective");return a&&"webkitPerspective"in g.style&&y("@media (transform-3d),(-webkit-transform-3d){#modernizr{left:9px;position:absolute;height:3px;}}",function(b,c){a=b.offsetLeft===9&&b.offsetHeight===3}),a},s.csstransitions=function(){return J("transition")},s.fontface=function(){var a;return y('@font-face {font-family:"font";src:url("https://")}',function(c,d){var e=b.getElementById("smodernizr"),f=e.sheet||e.styleSheet,g=f?f.cssRules&&f.cssRules[0]?f.cssRules[0].cssText:f.cssText||"":"";a=/src/i.test(g)&&g.indexOf(d.split(" ")[0])===0}),a},s.generatedcontent=function(){var a;return y(["#",h,"{font:0/0 a}#",h,':after{content:"',l,'";visibility:hidden;font:3px/1 a}'].join(""),function(b){a=b.offsetHeight>=3}),a},s.video=function(){var a=b.createElement("video"),c=!1;try{if(c=!!a.canPlayType)c=new Boolean(c),c.ogg=a.canPlayType('video/ogg; codecs="theora"').replace(/^no$/,""),c.h264=a.canPlayType('video/mp4; codecs="avc1.42E01E"').replace(/^no$/,""),c.webm=a.canPlayType('video/webm; codecs="vp8, vorbis"').replace(/^no$/,"")}catch(d){}return c},s.audio=function(){var a=b.createElement("audio"),c=!1;try{if(c=!!a.canPlayType)c=new Boolean(c),c.ogg=a.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/,""),c.mp3=a.canPlayType("audio/mpeg;").replace(/^no$/,""),c.wav=a.canPlayType('audio/wav; codecs="1"').replace(/^no$/,""),c.m4a=(a.canPlayType("audio/x-m4a;")||a.canPlayType("audio/aac;")).replace(/^no$/,"")}catch(d){}return c},s.localstorage=function(){try{return localStorage.setItem(h,h),localStorage.removeItem(h),!0}catch(a){return!1}},s.sessionstorage=function(){try{return sessionStorage.setItem(h,h),sessionStorage.removeItem(h),!0}catch(a){return!1}},s.webworkers=function(){return!!a.Worker},s.applicationcache=function(){return!!a.applicationCache},s.svg=function(){return!!b.createElementNS&&!!b.createElementNS(r.svg,"svg").createSVGRect},s.inlinesvg=function(){var a=b.createElement("div");return a.innerHTML="",(a.firstChild&&a.firstChild.namespaceURI)==r.svg},s.smil=function(){return!!b.createElementNS&&/SVGAnimate/.test(m.call(b.createElementNS(r.svg,"animate")))},s.svgclippaths=function(){return!!b.createElementNS&&/SVGClipPath/.test(m.call(b.createElementNS(r.svg,"clipPath")))};for(var L in s)C(s,L)&&(x=L.toLowerCase(),e[x]=s[L](),v.push((e[x]?"":"no-")+x));return e.input||K(),e.addTest=function(a,b){if(typeof a=="object")for(var d in a)C(a,d)&&e.addTest(d,a[d]);else{a=a.toLowerCase();if(e[a]!==c)return e;b=typeof b=="function"?b():b,typeof f!="undefined"&&f&&(g.className+=" "+(b?"":"no-")+a),e[a]=b}return e},D(""),i=k=null,function(a,b){function k(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function l(){var a=r.elements;return typeof a=="string"?a.split(" "):a}function m(a){var b=i[a[g]];return b||(b={},h++,a[g]=h,i[h]=b),b}function n(a,c,f){c||(c=b);if(j)return c.createElement(a);f||(f=m(c));var g;return f.cache[a]?g=f.cache[a].cloneNode():e.test(a)?g=(f.cache[a]=f.createElem(a)).cloneNode():g=f.createElem(a),g.canHaveChildren&&!d.test(a)?f.frag.appendChild(g):g}function o(a,c){a||(a=b);if(j)return a.createDocumentFragment();c=c||m(a);var d=c.frag.cloneNode(),e=0,f=l(),g=f.length;for(;e",f="hidden"in a,j=a.childNodes.length==1||function(){b.createElement("a");var a=b.createDocumentFragment();return typeof a.cloneNode=="undefined"||typeof a.createDocumentFragment=="undefined"||typeof a.createElement=="undefined"}()}catch(c){f=!0,j=!0}})();var r={elements:c.elements||"abbr article aside audio bdi canvas data datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video",shivCSS:c.shivCSS!==!1,supportsUnknownElements:j,shivMethods:c.shivMethods!==!1,type:"default",shivDocument:q,createElement:n,createDocumentFragment:o};a.html5=r,q(b)}(this,b),e._version=d,e._prefixes=n,e._domPrefixes=q,e._cssomPrefixes=p,e.mq=z,e.hasEvent=A,e.testProp=function(a){return H([a])},e.testAllProps=J,e.testStyles=y,e.prefixed=function(a,b,c){return b?J(a,b,c):J(a,"pfx")},g.className=g.className.replace(/(^|\s)no-js(\s|$)/,"$1$2")+(f?" js "+v.join(" "):""),e}(this,this.document),function(a,b,c){function d(a){return"[object Function]"==o.call(a)}function e(a){return"string"==typeof a}function f(){}function g(a){return!a||"loaded"==a||"complete"==a||"uninitialized"==a}function h(){var a=p.shift();q=1,a?a.t?m(function(){("c"==a.t?B.injectCss:B.injectJs)(a.s,0,a.a,a.x,a.e,1)},0):(a(),h()):q=0}function i(a,c,d,e,f,i,j){function k(b){if(!o&&g(l.readyState)&&(u.r=o=1,!q&&h(),l.onload=l.onreadystatechange=null,b)){"img"!=a&&m(function(){t.removeChild(l)},50);for(var d in y[c])y[c].hasOwnProperty(d)&&y[c][d].onload()}}var j=j||B.errorTimeout,l=b.createElement(a),o=0,r=0,u={t:d,s:c,e:f,a:i,x:j};1===y[c]&&(r=1,y[c]=[]),"object"==a?l.data=c:(l.src=c,l.type=a),l.width=l.height="0",l.onerror=l.onload=l.onreadystatechange=function(){k.call(this,r)},p.splice(e,0,u),"img"!=a&&(r||2===y[c]?(t.insertBefore(l,s?null:n),m(k,j)):y[c].push(l))}function j(a,b,c,d,f){return q=0,b=b||"j",e(a)?i("c"==b?v:u,a,b,this.i++,c,d,f):(p.splice(this.i++,0,a),1==p.length&&h()),this}function k(){var a=B;return a.loader={load:j,i:0},a}var l=b.documentElement,m=a.setTimeout,n=b.getElementsByTagName("script")[0],o={}.toString,p=[],q=0,r="MozAppearance"in l.style,s=r&&!!b.createRange().compareNode,t=s?l:n.parentNode,l=a.opera&&"[object Opera]"==o.call(a.opera),l=!!b.attachEvent&&!l,u=r?"object":l?"script":"img",v=l?"script":u,w=Array.isArray||function(a){return"[object Array]"==o.call(a)},x=[],y={},z={timeout:function(a,b){return b.length&&(a.timeout=b[0]),a}},A,B;B=function(a){function b(a){var a=a.split("!"),b=x.length,c=a.pop(),d=a.length,c={url:c,origUrl:c,prefixes:a},e,f,g;for(f=0;f"),n("table.docutils.footnote").wrap("
"),n("table.docutils.citation").wrap("
"),n(".wy-menu-vertical ul").not(".simple").siblings("a").each((function(){var t=n(this);expand=n(''),expand.on("click",(function(n){return e.toggleCurrent(t),n.stopPropagation(),!1})),t.prepend(expand)}))},reset:function(){var n=encodeURI(window.location.hash)||"#";try{var e=$(".wy-menu-vertical"),t=e.find('[href="'+n+'"]');if(0===t.length){var i=$('.document [id="'+n.substring(1)+'"]').closest("div.section");0===(t=e.find('[href="#'+i.attr("id")+'"]')).length&&(t=e.find('[href="#"]'))}if(t.length>0){$(".wy-menu-vertical .current").removeClass("current").attr("aria-expanded","false"),t.addClass("current").attr("aria-expanded","true"),t.closest("li.toctree-l1").parent().addClass("current").attr("aria-expanded","true");for(let n=1;n<=10;n++)t.closest("li.toctree-l"+n).addClass("current").attr("aria-expanded","true");t[0].scrollIntoView()}}catch(n){console.log("Error expanding nav for anchor",n)}},onScroll:function(){this.winScroll=!1;var n=this.win.scrollTop(),e=n+this.winHeight,t=this.navBar.scrollTop()+(n-this.winPosition);n<0||e>this.docHeight||(this.navBar.scrollTop(t),this.winPosition=n)},onResize:function(){this.winResize=!1,this.winHeight=this.win.height(),this.docHeight=$(document).height()},hashChange:function(){this.linkScroll=!0,this.win.one("hashchange",(function(){this.linkScroll=!1}))},toggleCurrent:function(n){var e=n.closest("li");e.siblings("li.current").removeClass("current").attr("aria-expanded","false"),e.siblings().find("li.current").removeClass("current").attr("aria-expanded","false");var t=e.find("> ul li");t.length&&(t.removeClass("current").attr("aria-expanded","false"),e.toggleClass("current").attr("aria-expanded",(function(n,e){return"true"==e?"false":"true"})))}},"undefined"!=typeof window&&(window.SphinxRtdTheme={Navigation:n.exports.ThemeNav,StickyNav:n.exports.ThemeNav}),function(){for(var n=0,e=["ms","moz","webkit","o"],t=0;t0 63 | var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1 64 | var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1 65 | var s_v = "^(" + C + ")?" + v; // vowel in stem 66 | 67 | this.stemWord = function (w) { 68 | var stem; 69 | var suffix; 70 | var firstch; 71 | var origword = w; 72 | 73 | if (w.length < 3) 74 | return w; 75 | 76 | var re; 77 | var re2; 78 | var re3; 79 | var re4; 80 | 81 | firstch = w.substr(0,1); 82 | if (firstch == "y") 83 | w = firstch.toUpperCase() + w.substr(1); 84 | 85 | // Step 1a 86 | re = /^(.+?)(ss|i)es$/; 87 | re2 = /^(.+?)([^s])s$/; 88 | 89 | if (re.test(w)) 90 | w = w.replace(re,"$1$2"); 91 | else if (re2.test(w)) 92 | w = w.replace(re2,"$1$2"); 93 | 94 | // Step 1b 95 | re = /^(.+?)eed$/; 96 | re2 = /^(.+?)(ed|ing)$/; 97 | if (re.test(w)) { 98 | var fp = re.exec(w); 99 | re = new RegExp(mgr0); 100 | if (re.test(fp[1])) { 101 | re = /.$/; 102 | w = w.replace(re,""); 103 | } 104 | } 105 | else if (re2.test(w)) { 106 | var fp = re2.exec(w); 107 | stem = fp[1]; 108 | re2 = new RegExp(s_v); 109 | if (re2.test(stem)) { 110 | w = stem; 111 | re2 = /(at|bl|iz)$/; 112 | re3 = new RegExp("([^aeiouylsz])\\1$"); 113 | re4 = new RegExp("^" + C + v + "[^aeiouwxy]$"); 114 | if (re2.test(w)) 115 | w = w + "e"; 116 | else if (re3.test(w)) { 117 | re = /.$/; 118 | w = w.replace(re,""); 119 | } 120 | else if (re4.test(w)) 121 | w = w + "e"; 122 | } 123 | } 124 | 125 | // Step 1c 126 | re = /^(.+?)y$/; 127 | if (re.test(w)) { 128 | var fp = re.exec(w); 129 | stem = fp[1]; 130 | re = new RegExp(s_v); 131 | if (re.test(stem)) 132 | w = stem + "i"; 133 | } 134 | 135 | // Step 2 136 | re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/; 137 | if (re.test(w)) { 138 | var fp = re.exec(w); 139 | stem = fp[1]; 140 | suffix = fp[2]; 141 | re = new RegExp(mgr0); 142 | if (re.test(stem)) 143 | w = stem + step2list[suffix]; 144 | } 145 | 146 | // Step 3 147 | re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/; 148 | if (re.test(w)) { 149 | var fp = re.exec(w); 150 | stem = fp[1]; 151 | suffix = fp[2]; 152 | re = new RegExp(mgr0); 153 | if (re.test(stem)) 154 | w = stem + step3list[suffix]; 155 | } 156 | 157 | // Step 4 158 | re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/; 159 | re2 = /^(.+?)(s|t)(ion)$/; 160 | if (re.test(w)) { 161 | var fp = re.exec(w); 162 | stem = fp[1]; 163 | re = new RegExp(mgr1); 164 | if (re.test(stem)) 165 | w = stem; 166 | } 167 | else if (re2.test(w)) { 168 | var fp = re2.exec(w); 169 | stem = fp[1] + fp[2]; 170 | re2 = new RegExp(mgr1); 171 | if (re2.test(stem)) 172 | w = stem; 173 | } 174 | 175 | // Step 5 176 | re = /^(.+?)e$/; 177 | if (re.test(w)) { 178 | var fp = re.exec(w); 179 | stem = fp[1]; 180 | re = new RegExp(mgr1); 181 | re2 = new RegExp(meq1); 182 | re3 = new RegExp("^" + C + v + "[^aeiouwxy]$"); 183 | if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) 184 | w = stem; 185 | } 186 | re = /ll$/; 187 | re2 = new RegExp(mgr1); 188 | if (re.test(w) && re2.test(w)) { 189 | re = /.$/; 190 | w = w.replace(re,""); 191 | } 192 | 193 | // and turn initial Y back to y 194 | if (firstch == "y") 195 | w = firstch.toLowerCase() + w.substr(1); 196 | return w; 197 | } 198 | } 199 | 200 | -------------------------------------------------------------------------------- /docs/build/html/_static/libf0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/groupmm/libf0/0baf532b506e2489fd7abebcf229a3c7d9cb98b7/docs/build/html/_static/libf0.png -------------------------------------------------------------------------------- /docs/build/html/_static/minus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/groupmm/libf0/0baf532b506e2489fd7abebcf229a3c7d9cb98b7/docs/build/html/_static/minus.png -------------------------------------------------------------------------------- /docs/build/html/_static/plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/groupmm/libf0/0baf532b506e2489fd7abebcf229a3c7d9cb98b7/docs/build/html/_static/plus.png -------------------------------------------------------------------------------- /docs/build/html/_static/pygments.css: -------------------------------------------------------------------------------- 1 | pre { line-height: 125%; } 2 | td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } 3 | span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } 4 | td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } 5 | span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } 6 | .highlight .hll { background-color: #ffffcc } 7 | .highlight { background: #eeffcc; } 8 | .highlight .c { color: #408090; font-style: italic } /* Comment */ 9 | .highlight .err { border: 1px solid #FF0000 } /* Error */ 10 | .highlight .k { color: #007020; font-weight: bold } /* Keyword */ 11 | .highlight .o { color: #666666 } /* Operator */ 12 | .highlight .ch { color: #408090; font-style: italic } /* Comment.Hashbang */ 13 | .highlight .cm { color: #408090; font-style: italic } /* Comment.Multiline */ 14 | .highlight .cp { color: #007020 } /* Comment.Preproc */ 15 | .highlight .cpf { color: #408090; font-style: italic } /* Comment.PreprocFile */ 16 | .highlight .c1 { color: #408090; font-style: italic } /* Comment.Single */ 17 | .highlight .cs { color: #408090; background-color: #fff0f0 } /* Comment.Special */ 18 | .highlight .gd { color: #A00000 } /* Generic.Deleted */ 19 | .highlight .ge { font-style: italic } /* Generic.Emph */ 20 | .highlight .gr { color: #FF0000 } /* Generic.Error */ 21 | .highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ 22 | .highlight .gi { color: #00A000 } /* Generic.Inserted */ 23 | .highlight .go { color: #333333 } /* Generic.Output */ 24 | .highlight .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */ 25 | .highlight .gs { font-weight: bold } /* Generic.Strong */ 26 | .highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ 27 | .highlight .gt { color: #0044DD } /* Generic.Traceback */ 28 | .highlight .kc { color: #007020; font-weight: bold } /* Keyword.Constant */ 29 | .highlight .kd { color: #007020; font-weight: bold } /* Keyword.Declaration */ 30 | .highlight .kn { color: #007020; font-weight: bold } /* Keyword.Namespace */ 31 | .highlight .kp { color: #007020 } /* Keyword.Pseudo */ 32 | .highlight .kr { color: #007020; font-weight: bold } /* Keyword.Reserved */ 33 | .highlight .kt { color: #902000 } /* Keyword.Type */ 34 | .highlight .m { color: #208050 } /* Literal.Number */ 35 | .highlight .s { color: #4070a0 } /* Literal.String */ 36 | .highlight .na { color: #4070a0 } /* Name.Attribute */ 37 | .highlight .nb { color: #007020 } /* Name.Builtin */ 38 | .highlight .nc { color: #0e84b5; font-weight: bold } /* Name.Class */ 39 | .highlight .no { color: #60add5 } /* Name.Constant */ 40 | .highlight .nd { color: #555555; font-weight: bold } /* Name.Decorator */ 41 | .highlight .ni { color: #d55537; font-weight: bold } /* Name.Entity */ 42 | .highlight .ne { color: #007020 } /* Name.Exception */ 43 | .highlight .nf { color: #06287e } /* Name.Function */ 44 | .highlight .nl { color: #002070; font-weight: bold } /* Name.Label */ 45 | .highlight .nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */ 46 | .highlight .nt { color: #062873; font-weight: bold } /* Name.Tag */ 47 | .highlight .nv { color: #bb60d5 } /* Name.Variable */ 48 | .highlight .ow { color: #007020; font-weight: bold } /* Operator.Word */ 49 | .highlight .w { color: #bbbbbb } /* Text.Whitespace */ 50 | .highlight .mb { color: #208050 } /* Literal.Number.Bin */ 51 | .highlight .mf { color: #208050 } /* Literal.Number.Float */ 52 | .highlight .mh { color: #208050 } /* Literal.Number.Hex */ 53 | .highlight .mi { color: #208050 } /* Literal.Number.Integer */ 54 | .highlight .mo { color: #208050 } /* Literal.Number.Oct */ 55 | .highlight .sa { color: #4070a0 } /* Literal.String.Affix */ 56 | .highlight .sb { color: #4070a0 } /* Literal.String.Backtick */ 57 | .highlight .sc { color: #4070a0 } /* Literal.String.Char */ 58 | .highlight .dl { color: #4070a0 } /* Literal.String.Delimiter */ 59 | .highlight .sd { color: #4070a0; font-style: italic } /* Literal.String.Doc */ 60 | .highlight .s2 { color: #4070a0 } /* Literal.String.Double */ 61 | .highlight .se { color: #4070a0; font-weight: bold } /* Literal.String.Escape */ 62 | .highlight .sh { color: #4070a0 } /* Literal.String.Heredoc */ 63 | .highlight .si { color: #70a0d0; font-style: italic } /* Literal.String.Interpol */ 64 | .highlight .sx { color: #c65d09 } /* Literal.String.Other */ 65 | .highlight .sr { color: #235388 } /* Literal.String.Regex */ 66 | .highlight .s1 { color: #4070a0 } /* Literal.String.Single */ 67 | .highlight .ss { color: #517918 } /* Literal.String.Symbol */ 68 | .highlight .bp { color: #007020 } /* Name.Builtin.Pseudo */ 69 | .highlight .fm { color: #06287e } /* Name.Function.Magic */ 70 | .highlight .vc { color: #bb60d5 } /* Name.Variable.Class */ 71 | .highlight .vg { color: #bb60d5 } /* Name.Variable.Global */ 72 | .highlight .vi { color: #bb60d5 } /* Name.Variable.Instance */ 73 | .highlight .vm { color: #bb60d5 } /* Name.Variable.Magic */ 74 | .highlight .il { color: #208050 } /* Literal.Number.Integer.Long */ -------------------------------------------------------------------------------- /docs/build/html/_static/sphinx_highlight.js: -------------------------------------------------------------------------------- 1 | /* Highlighting utilities for Sphinx HTML documentation. */ 2 | "use strict"; 3 | 4 | const SPHINX_HIGHLIGHT_ENABLED = true 5 | 6 | /** 7 | * highlight a given string on a node by wrapping it in 8 | * span elements with the given class name. 9 | */ 10 | const _highlight = (node, addItems, text, className) => { 11 | if (node.nodeType === Node.TEXT_NODE) { 12 | const val = node.nodeValue; 13 | const parent = node.parentNode; 14 | const pos = val.toLowerCase().indexOf(text); 15 | if ( 16 | pos >= 0 && 17 | !parent.classList.contains(className) && 18 | !parent.classList.contains("nohighlight") 19 | ) { 20 | let span; 21 | 22 | const closestNode = parent.closest("body, svg, foreignObject"); 23 | const isInSVG = closestNode && closestNode.matches("svg"); 24 | if (isInSVG) { 25 | span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); 26 | } else { 27 | span = document.createElement("span"); 28 | span.classList.add(className); 29 | } 30 | 31 | span.appendChild(document.createTextNode(val.substr(pos, text.length))); 32 | parent.insertBefore( 33 | span, 34 | parent.insertBefore( 35 | document.createTextNode(val.substr(pos + text.length)), 36 | node.nextSibling 37 | ) 38 | ); 39 | node.nodeValue = val.substr(0, pos); 40 | 41 | if (isInSVG) { 42 | const rect = document.createElementNS( 43 | "http://www.w3.org/2000/svg", 44 | "rect" 45 | ); 46 | const bbox = parent.getBBox(); 47 | rect.x.baseVal.value = bbox.x; 48 | rect.y.baseVal.value = bbox.y; 49 | rect.width.baseVal.value = bbox.width; 50 | rect.height.baseVal.value = bbox.height; 51 | rect.setAttribute("class", className); 52 | addItems.push({ parent: parent, target: rect }); 53 | } 54 | } 55 | } else if (node.matches && !node.matches("button, select, textarea")) { 56 | node.childNodes.forEach((el) => _highlight(el, addItems, text, className)); 57 | } 58 | }; 59 | const _highlightText = (thisNode, text, className) => { 60 | let addItems = []; 61 | _highlight(thisNode, addItems, text, className); 62 | addItems.forEach((obj) => 63 | obj.parent.insertAdjacentElement("beforebegin", obj.target) 64 | ); 65 | }; 66 | 67 | /** 68 | * Small JavaScript module for the documentation. 69 | */ 70 | const SphinxHighlight = { 71 | 72 | /** 73 | * highlight the search words provided in localstorage in the text 74 | */ 75 | highlightSearchWords: () => { 76 | if (!SPHINX_HIGHLIGHT_ENABLED) return; // bail if no highlight 77 | 78 | // get and clear terms from localstorage 79 | const url = new URL(window.location); 80 | const highlight = 81 | localStorage.getItem("sphinx_highlight_terms") 82 | || url.searchParams.get("highlight") 83 | || ""; 84 | localStorage.removeItem("sphinx_highlight_terms") 85 | url.searchParams.delete("highlight"); 86 | window.history.replaceState({}, "", url); 87 | 88 | // get individual terms from highlight string 89 | const terms = highlight.toLowerCase().split(/\s+/).filter(x => x); 90 | if (terms.length === 0) return; // nothing to do 91 | 92 | // There should never be more than one element matching "div.body" 93 | const divBody = document.querySelectorAll("div.body"); 94 | const body = divBody.length ? divBody[0] : document.querySelector("body"); 95 | window.setTimeout(() => { 96 | terms.forEach((term) => _highlightText(body, term, "highlighted")); 97 | }, 10); 98 | 99 | const searchBox = document.getElementById("searchbox"); 100 | if (searchBox === null) return; 101 | searchBox.appendChild( 102 | document 103 | .createRange() 104 | .createContextualFragment( 105 | '" 109 | ) 110 | ); 111 | }, 112 | 113 | /** 114 | * helper function to hide the search marks again 115 | */ 116 | hideSearchWords: () => { 117 | document 118 | .querySelectorAll("#searchbox .highlight-link") 119 | .forEach((el) => el.remove()); 120 | document 121 | .querySelectorAll("span.highlighted") 122 | .forEach((el) => el.classList.remove("highlighted")); 123 | localStorage.removeItem("sphinx_highlight_terms") 124 | }, 125 | 126 | initEscapeListener: () => { 127 | // only install a listener if it is really needed 128 | if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) return; 129 | 130 | document.addEventListener("keydown", (event) => { 131 | // bail for input elements 132 | if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; 133 | // bail with special keys 134 | if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey) return; 135 | if (DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS && (event.key === "Escape")) { 136 | SphinxHighlight.hideSearchWords(); 137 | event.preventDefault(); 138 | } 139 | }); 140 | }, 141 | }; 142 | 143 | _ready(SphinxHighlight.highlightSearchWords); 144 | _ready(SphinxHighlight.initEscapeListener); 145 | -------------------------------------------------------------------------------- /docs/build/html/getting_started.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Getting Started — libf0 1.0.2 documentation 8 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 66 | 67 |
71 | 72 |
73 |
74 |
75 | 82 |
83 |
84 |
85 |
86 | 87 |
88 |

Getting Started

89 |
90 |

Installation

91 |

With Python >= 3.6, you can install libf0 using the Python package manager pip:

92 |
pip install libf0
 93 | 
94 |
95 |
96 |
97 |

Contributing

98 |

For development, testing, or generating the API documentation, clone the git repository and install:

99 |
git clone https://github.com/groupmm/libf0.git
100 | cd libf0
101 | pip install -e .[dev,tests,docs]
102 | 
103 |
104 |
105 |
106 | 107 | 108 |
109 |
110 |
114 | 115 |
116 | 117 |
118 |

© Copyright 2022, Sebastian Rosenzweig, Simon Schwär, Meinard Müller.

119 |
120 | 121 | Built with Sphinx using a 122 | theme 123 | provided by Read the Docs. 124 | 125 | 126 |
127 |
128 |
129 |
130 |
131 | 136 | 137 | 138 | -------------------------------------------------------------------------------- /docs/build/html/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | libf0 — libf0 1.0.2 documentation 8 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 | 61 | 62 |
66 | 67 |
68 |
69 |
70 | 77 |
78 |
79 |
80 |
81 | 82 |
83 |

libf0

84 |

libf0 is a Python toolbox for fundamental frequency (F0) estimation in music recordings.

85 |

If you use this toolbox, please cite:

86 |
87 |
1
88 |

Sebastian Rosenzweig, Simon Schwär, and Meinard Müller. libf0: A Python Library for Fundamental Frequency Estimation. In Late Breaking Demos of the International Society for Music Information Retrieval Conference (ISMIR), Bengaluru, India, 2022.

89 |
90 |
91 |

To reference the original algorithms implemented in this toolbox, please cite:

92 |
93 |
2
94 |

Alain De Cheveigné and Hideki Kawahara: YIN, a fundamental frequency estimator for speech and music, The Journal of the Acoustical Society of America 111.4 (2002): 1917-1930.

95 |
96 |
3
97 |

Matthias Mauch and Simon Dixon: PYIN: A fundamental frequency estimator using probabilistic threshold distributions, IEEE International Conference on Acoustics, Speech and Signal Processing (ICASSP) (2014): 659-663.

98 |
99 |
4
100 |

Justin Salamon and Emilia Gómez: Melody Extraction From Polyphonic Music Signals Using Pitch Contour Characteristics, IEEE Transactions on Audio, Speech, and Language Processing, vol. 20, no. 6, pp. 1759–1770, 2012.

101 |
102 |
5
103 |

Arturo Camacho and John G. Harris: A sawtooth waveform inspired pitch estimator for speech and music. The Journal of the Acoustical Society of America, vol. 124, no. 3, pp. 1638–1652, 2008

104 |
105 |
6
106 |

Meinard Müller. Fundamentals of Music Processing – Using Python and Jupyter Notebooks. Springer Verlag, 2nd edition, 2021. ISBN 978-3-030-69807-2. doi: 10.1007/978-3-030-69808-9.

107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 | 115 | 116 |
117 |
118 |
121 | 122 |
123 | 124 |
125 |

© Copyright 2022, Sebastian Rosenzweig, Simon Schwär, Meinard Müller.

126 |
127 | 128 | Built with Sphinx using a 129 | theme 130 | provided by Read the Docs. 131 | 132 | 133 |
134 |
135 |
136 |
137 |
138 | 143 | 144 | 145 | -------------------------------------------------------------------------------- /docs/build/html/index_swipe_slim.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | swipe_slim — libf0 1.0.2 documentation 8 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 67 | 68 |
72 | 73 |
74 |
75 |
76 | 83 |
84 |
85 |
86 |
87 | 88 |
89 |

swipe_slim

90 |
91 |
Description: libf0 SWIPE slim implementation
92 |
Contributors: Sebastian Rosenzweig, Simon Schwär, Meinard Müller
93 |
License: The MIT license, https://opensource.org/licenses/MIT
94 |
This file is part of libf0.
95 |
96 |
97 |
98 | libf0.swipe_slim.swipe_slim(x, Fs=22050, H=256, F_min=55.0, F_max=1760.0, R=10, strength_threshold=0)[source]
99 |

Slim and didactical implementation of a sawtooth waveform inspired pitch estimator (SWIPE). 100 | This version uses a log-frequency spectrogram instead of ERB filters. Furthermore, it is implemented more 101 | efficiently. See swipe() for the original implementation.

102 |
103 |
1
104 |

A. Camacho and J. G. Harris, 105 | “A sawtooth waveform inspired pitch estimator for speech and music.” 106 | The Journal of the Acoustical Society of America, vol. 124, no. 3, pp. 1638–1652, Sep. 2008

107 |
108 |
109 |
110 |
Parameters
111 |
    112 |
  • x (ndarray) – Audio signal

  • 113 |
  • Fs (int) – Sampling rate

  • 114 |
  • H (int) – Hop size

  • 115 |
  • F_min (float or int) – Minimal frequency

  • 116 |
  • F_max (float or int) – Maximal frequency

  • 117 |
  • R (float) – resolution of the pitch candidate bins in cents (default = 10)

  • 118 |
  • strength_threshold (float) – confidence threshold [0, 1] for the pitch detection (default value = 0)

  • 119 |
120 |
121 |
Returns
122 |

    123 |
  • f0 (ndarray) – Estimated F0-trajectory

  • 124 |
  • t (ndarray) – Time axis

  • 125 |
  • conf (ndarray) – Confidence / Pitch Strength

  • 126 |
127 |

128 |
129 |
130 |
131 | 132 |
133 |
134 | libf0.swipe_slim.compute_kernel(f, F_coef_log_hz)[source]
135 |

Compute a SWIPE’ kernel.

136 |
137 |
Parameters
138 |
    139 |
  • f (float) – Frequency in Hz

  • 140 |
  • F_coef_log_hz – Logarithmic frequency axis in Hz

  • 141 |
142 |
143 |
Returns
144 |

k – Kernel

145 |
146 |
Return type
147 |

ndarray

148 |
149 |
150 |
151 | 152 |
153 |
154 | libf0.swipe_slim.prime_and_one(upto=1000000)[source]
155 |

Returns a set of prime numbers, adapted from http://rebrained.com/?p=458

156 |
157 |
Parameters
158 |

upto (int) – Find prime numbers up to this number

159 |
160 |
Return type
161 |

A set of prime numbers including 1 & 2

162 |
163 |
164 |
165 | 166 |
167 | 168 | 169 |
170 |
171 |
175 | 176 |
177 | 178 |
179 |

© Copyright 2022, Sebastian Rosenzweig, Simon Schwär, Meinard Müller.

180 |
181 | 182 | Built with Sphinx using a 183 | theme 184 | provided by Read the Docs. 185 | 186 | 187 |
188 |
189 |
190 |
191 |
192 | 197 | 198 | 199 | -------------------------------------------------------------------------------- /docs/build/html/index_utils.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | utils — libf0 1.0.2 documentation 8 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 | 66 | 67 |
71 | 72 |
73 |
74 |
75 | 82 |
83 |
84 |
85 |
86 | 87 |
88 |

utils

89 |
90 |
Description: libf0 utility functions
91 |
Contributors: Sebastian Rosenzweig, Simon Schwär, Meinard Müller
92 |
License: The MIT license, https://opensource.org/licenses/MIT
93 |
This file is part of libf0.
94 |
95 |
96 |
97 | libf0.utils.sonify_trajectory_with_sinusoid(f0, t, audio_len, confidence=None, Fs=22050, smooth_len=11)[source]
98 |

Sonification of trajectory with sinusoidal. Adapted from FMP notebook: C8/C8S2_FundFreqTracking.ipynb

99 |
100 |
Parameters
101 |
    102 |
  • f0 (ndarray) – F0-trajectory

  • 103 |
  • t (ndarray) – Time axis

  • 104 |
  • audio_len (int) – Desired audio length in samples

  • 105 |
  • confidence (None or ndarray) – Confidence values for amplitude control

  • 106 |
  • Fs (int) – Sampling rate

  • 107 |
  • smooth_len (int) – Smoothing filter length to avoid clicks in the sonification

  • 108 |
109 |
110 |
Returns
111 |

x_soni – Sonified F0-trajectory

112 |
113 |
Return type
114 |

ndarray

115 |
116 |
117 |
118 | 119 |
120 |
121 | libf0.utils.hz_to_cents(F, F_ref=55.0)[source]
122 |

Converts frequency in Hz to cents.

123 |
124 |
Parameters
125 |
    126 |
  • F (float or ndarray) – Frequency value in Hz

  • 127 |
  • F_ref (float) – Reference frequency in Hz (Default value = 55.0)

  • 128 |
129 |
130 |
Returns
131 |

F_cents – Frequency in cents

132 |
133 |
Return type
134 |

float or ndarray

135 |
136 |
137 |
138 | 139 |
140 |
141 | libf0.utils.cents_to_hz(F_cents, F_ref=55.0)[source]
142 |

Converts frequency in cents to Hz.

143 |
144 |
Parameters
145 |
    146 |
  • F_cents (float or ndarray) – Frequency in cents

  • 147 |
  • F_ref (float) – Reference frequency in Hz (Default value = 55.0)

  • 148 |
149 |
150 |
Returns
151 |

F – Frequency in Hz

152 |
153 |
Return type
154 |

float or ndarray

155 |
156 |
157 |
158 | 159 |
160 | 161 | 162 |
163 |
164 |
167 | 168 |
169 | 170 |
171 |

© Copyright 2022, Sebastian Rosenzweig, Simon Schwär, Meinard Müller.

172 |
173 | 174 | Built with Sphinx using a 175 | theme 176 | provided by Read the Docs. 177 | 178 | 179 |
180 |
181 |
182 |
183 |
184 | 189 | 190 | 191 | -------------------------------------------------------------------------------- /docs/build/html/objects.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/groupmm/libf0/0baf532b506e2489fd7abebcf229a3c7d9cb98b7/docs/build/html/objects.inv -------------------------------------------------------------------------------- /docs/build/html/py-modindex.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Python Module Index — libf0 1.0.2 documentation 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 62 | 63 |
67 | 68 |
69 |
70 |
71 |
    72 |
  • 73 | 74 |
  • 75 |
  • 76 |
77 |
78 |
79 |
80 |
81 | 82 | 83 |

Python Module Index

84 | 85 |
86 | l 87 |
88 | 89 | 90 | 91 | 93 | 94 | 96 | 99 | 100 | 101 | 104 | 105 | 106 | 109 | 110 | 111 | 114 | 115 | 116 | 119 | 120 | 121 | 124 | 125 | 126 | 129 |
 
92 | l
97 | libf0 98 |
    102 | libf0.pyin 103 |
    107 | libf0.salience 108 |
    112 | libf0.swipe 113 |
    117 | libf0.swipe_slim 118 |
    122 | libf0.utils 123 |
    127 | libf0.yin 128 |
130 | 131 | 132 |
133 |
134 |
135 | 136 |
137 | 138 |
139 |

© Copyright 2022, Sebastian Rosenzweig, Simon Schwär, Meinard Müller.

140 |
141 | 142 | Built with Sphinx using a 143 | theme 144 | provided by Read the Docs. 145 | 146 | 147 |
148 |
149 |
150 |
151 |
152 | 157 | 158 | 159 | -------------------------------------------------------------------------------- /docs/build/html/search.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Search — libf0 1.0.2 documentation 7 | 8 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 62 | 63 |
67 | 68 |
69 |
70 |
71 |
    72 |
  • 73 | 74 |
  • 75 |
  • 76 |
77 |
78 |
79 |
80 |
81 | 82 | 89 | 90 | 91 |
92 | 93 |
94 | 95 |
96 |
97 |
98 | 99 |
100 | 101 |
102 |

© Copyright 2022, Sebastian Rosenzweig, Simon Schwär, Meinard Müller.

103 |
104 | 105 | Built with Sphinx using a 106 | theme 107 | provided by Read the Docs. 108 | 109 | 110 |
111 |
112 |
113 |
114 |
115 | 120 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=source 11 | set BUILDDIR=build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/source/_static/libf0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/groupmm/libf0/0baf532b506e2489fd7abebcf229a3c7d9cb98b7/docs/source/_static/libf0.png -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | # 13 | import os 14 | import re 15 | import sys 16 | 17 | DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..')) 18 | assert os.path.exists(os.path.join(DIR, 'libf0')) 19 | sys.path.insert(0, DIR) 20 | 21 | # -- Project information ----------------------------------------------------- 22 | 23 | project = 'libf0' 24 | copyright = '2022, Sebastian Rosenzweig, Simon Schwär, Meinard Müller' 25 | author = 'Sebastian Rosenzweig, Simon Schwär, Meinard Müller' 26 | 27 | #reading version from local setup file 28 | setup_fn = next(fn for fn in ['setup.py'] if os.path.exists(os.path.join(DIR, fn))) 29 | with open(os.path.join(DIR, setup_fn), 'r') as stream: 30 | setup_content = stream.read() 31 | version_match = re.search("version='(.*?)'", setup_content) 32 | assert version_match is not None 33 | libtsm_version = version_match.group(1) 34 | 35 | version = libtsm_version 36 | release = libtsm_version 37 | 38 | # -- General configuration --------------------------------------------------- 39 | 40 | # Add any Sphinx extension module names here, as strings. They can be 41 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 42 | # ones. 43 | extensions = [ 44 | 'sphinx.ext.autodoc', # documentation based on docstrings 45 | 'sphinx.ext.napoleon', # for having google/numpy style docstrings 46 | 'sphinx.ext.viewcode', # link source code 47 | 'sphinx.ext.intersphinx', 48 | 'sphinx.ext.autosummary', 49 | 'sphinx.ext.extlinks' 50 | ] 51 | 52 | # Add any paths that contain templates here, relative to this directory. 53 | templates_path = ['_templates'] 54 | 55 | # List of patterns, relative to source directory, that match files and 56 | # directories to ignore when looking for source files. 57 | # This pattern also affects html_static_path and html_extra_path. 58 | exclude_patterns = [] 59 | 60 | # The name of the Pygments (syntax highlighting) style to use. 61 | pygments_style = 'sphinx' 62 | 63 | autodoc_member_order = 'bysource' # avoid sorting functions alpha 64 | 65 | # -- Options for HTML output ------------------------------------------------- 66 | 67 | # The theme to use for HTML and HTML Help pages. See the documentation for 68 | # a list of builtin themes. 69 | # 70 | import sphinx_rtd_theme # noqa 71 | 72 | html_theme = "sphinx_rtd_theme" 73 | html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] 74 | 75 | # Add any paths that contain custom static files (such as style sheets) here, 76 | # relative to this directory. They are copied after the builtin static files, 77 | # so a file named "default.css" will overwrite the builtin "default.css". 78 | html_static_path = ['_static'] 79 | 80 | html_use_index = True 81 | html_use_modindex = True 82 | 83 | html_logo = os.path.join(html_static_path[0], 'libf0.png') 84 | 85 | html_theme_options = {'logo_only': True} 86 | -------------------------------------------------------------------------------- /docs/source/getting_started.rst: -------------------------------------------------------------------------------- 1 | Getting Started 2 | =============== 3 | 4 | Installation 5 | ------------ 6 | 7 | With Python >= 3.6, you can install ``libf0`` using the Python package manager pip: 8 | 9 | .. code-block:: bash 10 | 11 | pip install libf0 12 | 13 | Contributing 14 | ------------ 15 | 16 | For development, testing, or generating the API documentation, clone the git repository and install: 17 | 18 | .. code-block:: bash 19 | 20 | git clone https://github.com/groupmm/libf0.git 21 | cd libf0 22 | pip install -e .[dev,tests,docs] 23 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | .. libf0 documentation master file, created by 2 | sphinx-quickstart on Thu May 27 14:48:45 2021. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | libf0 7 | ================================== 8 | ``libf0`` is a Python toolbox for fundamental frequency (F0) estimation in music recordings. 9 | 10 | If you use this toolbox, please cite: 11 | 12 | .. [#] Sebastian Rosenzweig, Simon Schwär, and Meinard Müller. libf0: A Python Library for Fundamental Frequency Estimation. In Late Breaking Demos of the International Society for Music Information Retrieval Conference (ISMIR), Bengaluru, India, 2022. 13 | 14 | 15 | To reference the original algorithms implemented in this toolbox, please cite: 16 | 17 | .. [#] Alain De Cheveigné and Hideki Kawahara: YIN, a fundamental frequency estimator for speech and music, The Journal of the Acoustical Society of America 111.4 (2002): 1917-1930. 18 | .. [#] Matthias Mauch and Simon Dixon: PYIN: A fundamental frequency estimator using probabilistic threshold distributions, IEEE International Conference on Acoustics, Speech and Signal Processing (ICASSP) (2014): 659-663. 19 | .. [#] Justin Salamon and Emilia Gómez: Melody Extraction From Polyphonic Music Signals Using Pitch Contour Characteristics, IEEE Transactions on Audio, Speech, and Language Processing, vol. 20, no. 6, pp. 1759–1770, 2012. 20 | .. [#] Arturo Camacho and John G. Harris: A sawtooth waveform inspired pitch estimator for speech and music. The Journal of the Acoustical Society of America, vol. 124, no. 3, pp. 1638–1652, 2008 21 | .. [#] Meinard Müller. Fundamentals of Music Processing – Using Python and Jupyter Notebooks. Springer Verlag, 2nd edition, 2021. ISBN 978-3-030-69807-2. doi: 10.1007/978-3-030-69808-9. 22 | 23 | 24 | .. toctree:: 25 | :hidden: 26 | 27 | getting_started 28 | 29 | 30 | 31 | .. toctree:: 32 | :caption: API Documentation 33 | :maxdepth: 1 34 | :hidden: 35 | 36 | index_yin 37 | index_pyin 38 | index_salience 39 | index_swipe 40 | index_swipe_slim 41 | index_utils 42 | -------------------------------------------------------------------------------- /docs/source/index_pyin.rst: -------------------------------------------------------------------------------- 1 | pyin 2 | ============== 3 | 4 | .. automodule:: libf0.pyin 5 | :members: 6 | :undoc-members: 7 | -------------------------------------------------------------------------------- /docs/source/index_salience.rst: -------------------------------------------------------------------------------- 1 | salience 2 | ============== 3 | 4 | .. automodule:: libf0.salience 5 | :members: 6 | :undoc-members: 7 | -------------------------------------------------------------------------------- /docs/source/index_swipe.rst: -------------------------------------------------------------------------------- 1 | swipe 2 | ============== 3 | 4 | .. automodule:: libf0.swipe 5 | :members: 6 | :undoc-members: 7 | -------------------------------------------------------------------------------- /docs/source/index_swipe_slim.rst: -------------------------------------------------------------------------------- 1 | swipe_slim 2 | ============== 3 | 4 | .. automodule:: libf0.swipe_slim 5 | :members: 6 | :undoc-members: 7 | -------------------------------------------------------------------------------- /docs/source/index_utils.rst: -------------------------------------------------------------------------------- 1 | utils 2 | ===== 3 | 4 | .. automodule:: libf0.utils 5 | :members: 6 | :undoc-members: 7 | -------------------------------------------------------------------------------- /docs/source/index_yin.rst: -------------------------------------------------------------------------------- 1 | yin 2 | ============== 3 | 4 | .. automodule:: libf0.yin 5 | :members: 6 | :undoc-members: 7 | -------------------------------------------------------------------------------- /libf0/__init__.py: -------------------------------------------------------------------------------- 1 | from .salience import * 2 | from .yin import * 3 | from .pyin import * 4 | from .swipe import * 5 | from .swipe_slim import * 6 | from .utils import * 7 | -------------------------------------------------------------------------------- /libf0/salience.py: -------------------------------------------------------------------------------- 1 | """ 2 | | Description: libf0 salience-based F0 estimation implementation 3 | | Author: Sebastian Rosenzweig, Simon Schwär, Meinard Müller 4 | | License: The MIT license, https://opensource.org/licenses/MIT 5 | | This file is part of libf0. 6 | """ 7 | import numpy as np 8 | from librosa import stft 9 | from scipy import ndimage, linalg 10 | from numba import njit 11 | 12 | 13 | def salience(x, Fs=22050, N=2048, H=256, F_min=55.0, F_max=1760.0, R=10.0, num_harm=10, freq_smooth_len=11, 14 | alpha=0.9, gamma=0.0, constraint_region=None, tol=5, score_low=0.01, score_high=1.0): 15 | """ 16 | Implementation of a salience-based F0-estimation algorithm using pitch contours, inspired by Melodia. 17 | 18 | .. [#] Justin Salamon and Emilia Gómez, 19 | "Melody Extraction From Polyphonic Music Signals Using Pitch Contour Characteristics." 20 | IEEE Transactions on Audio, Speech, and Language Processing, vol. 20, no. 6, pp. 1759–1770, Aug. 2012. 21 | 22 | Parameters 23 | ---------- 24 | x : ndarray 25 | Audio signal 26 | Fs : int 27 | Sampling rate 28 | N : int 29 | Window size 30 | H : int 31 | Hop size 32 | F_min : float or int 33 | Minimal frequency 34 | F_max : float or int 35 | Maximal frequency 36 | R : int 37 | Frequency resolution given in cents 38 | num_harm : int 39 | Number of harmonics (Default value = 10) 40 | freq_smooth_len : int 41 | Filter length for vertical smoothing (Default value = 11) 42 | alpha : float 43 | Weighting parameter for harmonics (Default value = 0.9) 44 | gamma : float 45 | Logarithmic compression factor (Default value = 0.0) 46 | constraint_region : None or ndarray 47 | Constraint regions, row-format: (t_start_sec, t_end_sec, f_start_hz, f_end,hz) 48 | (Default value = None) 49 | tol : int 50 | Tolerance parameter for transition matrix (Default value = 5) 51 | score_low : float 52 | Score (low) for transition matrix (Default value = 0.01) 53 | score_high : float 54 | Score (high) for transition matrix (Default value = 1.0) 55 | 56 | Returns 57 | ------- 58 | f0 : ndarray 59 | Estimated F0-trajectory 60 | T_coef: ndarray 61 | Time axis 62 | sal: ndarray 63 | Salience value of estimated F0 64 | 65 | See also 66 | -------- 67 | [FMP] Notebook: C8/C8S2_SalienceRepresentation.ipynb 68 | """ 69 | 70 | # compute salience representation via instantaneous frequency and harmonic summation 71 | Z, F_coef_hertz = compute_salience_rep(x, Fs, N=N, H=H, F_min=F_min, F_max=F_max, R=R, 72 | num_harm=num_harm, freq_smooth_len=freq_smooth_len, 73 | alpha=alpha, gamma=gamma) 74 | 75 | # compute trajectory via dynamic programming 76 | T_coef = (np.arange(Z.shape[1]) * H) / Fs 77 | index_CR = compute_trajectory_cr(Z, T_coef, F_coef_hertz, constraint_region, 78 | tol=tol, score_low=score_low, score_high=score_high) 79 | 80 | traj = F_coef_hertz[index_CR] 81 | traj[index_CR == -1] = 0 82 | 83 | # compute salience value 84 | Z_max = np.max(Z, axis=0) 85 | Z_norm = np.divide(Z, np.ones((Z.shape[0], 1)) * Z_max) 86 | sal = Z_norm[index_CR, np.arange(Z.shape[1])] 87 | sal[traj == 0] = 0 88 | 89 | return traj, T_coef, sal 90 | 91 | 92 | def compute_salience_rep(x, Fs, N, H, F_min, F_max, R, num_harm, freq_smooth_len, alpha, gamma): 93 | """ 94 | Compute salience representation [FMP, Eq. (8.56)] 95 | 96 | Parameters 97 | ---------- 98 | x : ndarray 99 | Audio signal 100 | Fs : int 101 | Sampling rate 102 | N : int 103 | Window size 104 | H : int 105 | Hop size 106 | F_min : float or int 107 | Minimal frequency 108 | F_max : float or int 109 | Maximal frequency 110 | R : int 111 | Frequency resolution given in cents 112 | num_harm : int 113 | Number of harmonics 114 | freq_smooth_len : int 115 | Filter length for vertical smoothing 116 | alpha : float 117 | Weighting parameter for harmonics 118 | gamma : float 119 | Logarithmic compression factor 120 | 121 | Returns 122 | ------- 123 | Z : ndarray 124 | Salience representation 125 | F_coef_hertz : ndarray 126 | Frequency axis in Hz 127 | 128 | See also 129 | -------- 130 | [FMP] Notebook: C8/C8S2_SalienceRepresentation.ipynb 131 | """ 132 | 133 | X = stft(x, n_fft=N, hop_length=H, win_length=N, pad_mode='constant') 134 | Y_LF_IF_bin, F_coef_hertz = compute_y_lf_if_bin_eff(X, Fs, N, H, F_min, F_max, R) 135 | 136 | # smoothing 137 | Y_LF_IF_bin = ndimage.convolve1d(Y_LF_IF_bin, np.hanning(freq_smooth_len), axis=0, mode='constant') 138 | 139 | Z = compute_salience_from_logfreq_spec(Y_LF_IF_bin, R, n_harmonics=num_harm, alpha=alpha, beta=1, gamma=gamma) 140 | return Z, F_coef_hertz 141 | 142 | 143 | def compute_y_lf_if_bin_eff(X, Fs, N, H, F_min, F_max, R): 144 | """ 145 | Binned Log-frequency Spectrogram with variable frequency resolution based on instantaneous frequency, 146 | more efficient implementation than FMP 147 | 148 | Parameters 149 | ---------- 150 | X : ndarray 151 | Complex spectrogram 152 | Fs : int 153 | Sampling rate in Hz 154 | N : int 155 | Window size 156 | H : int 157 | Hop size 158 | F_min : float or int 159 | Minimal frequency 160 | F_max : float or int 161 | Maximal frequency 162 | R : int 163 | Frequency resolution given in cents 164 | 165 | Returns 166 | ------- 167 | Y_LF_IF_bin : ndarray 168 | Binned log-frequency spectrogram using instantaneous frequency (shape: [freq, time]) 169 | F_coef_hertz : ndarray 170 | Frequency axis in Hz 171 | """ 172 | 173 | # calculate number of bins on log frequency axis 174 | B = frequency_to_bin_index(F_max, R, F_min) + 1 175 | 176 | # center frequencies of the final bins 177 | F_coef_hertz = F_min * np.power(2, (np.arange(0, B) * R / 1200)) 178 | 179 | # calculate heterodyned phase increment (hpi) 180 | k = np.arange(X.shape[0]).reshape(-1, 1) 181 | omega = 2 * np.pi * k / N # center frequency for each bin in rad 182 | hpi = (np.angle(X[:, 1:]) - np.angle(X[:, 0:-1])) - omega * H 183 | 184 | # reduce hpi to -pi:pi range 185 | # this is much faster than using the modulo function below, but gives the same result 186 | # hpi = np.mod(hpi + np.pi, 2 * np.pi) - np.pi 187 | hpi = hpi - 2 * np.pi * (np.around((hpi / (2 * np.pi)) + 1) - 1) 188 | 189 | # calculate instantaneous frequencies in Hz 190 | inst_f = (omega + hpi / H) * Fs / (2 * np.pi) 191 | # repeat the first time frame to match dimensions of X 192 | inst_f = np.hstack((np.copy(inst_f[:, 0]).reshape(-1, 1), inst_f)) 193 | 194 | # mask frequencies that are not relevant 195 | mask = np.logical_and(inst_f >= F_min, inst_f < F_max) 196 | inst_f *= mask 197 | # set 0 to nan, so it does stay at nan in the bin assignment calculation 198 | inst_f[np.where(inst_f == 0)] = np.nan 199 | 200 | # find which inst_f values belong to which bin 201 | bin_assignment = frequency_to_bin_index(inst_f, R, F_min) 202 | # we map the discarded values to an extra bin that we remove before returning the binned spectrogram 203 | bin_assignment[np.where(np.isnan(inst_f))] = B 204 | 205 | # perform binning on power spectrogram for each time frame separately 206 | Y = np.abs(X) ** 2 207 | Y_LF_IF_bin = np.zeros((B+1, Y.shape[1])) 208 | for t in range(Y.shape[1]): 209 | np.add.at(Y_LF_IF_bin[:, t], bin_assignment[:, t], Y[:, t]) 210 | 211 | return Y_LF_IF_bin[:B, :], F_coef_hertz 212 | 213 | 214 | def compute_salience_from_logfreq_spec(lf_spec, R, n_harmonics, alpha, beta, gamma, harmonic_win_len=11): 215 | """ 216 | Compute salience representation using harmonic summation following [1] 217 | 218 | [1] J. Salamon and E. Gomez, 219 | "Melody Extraction From Polyphonic Music Signals Using Pitch Contour Characteristics." 220 | IEEE Transactions on Audio, Speech, and Language Processing, vol. 20, no. 6, pp. 1759–1770, Aug. 2012. 221 | 222 | Parameters 223 | ---------- 224 | lf_spec : ndarray 225 | (F, T) log-spectrogram 226 | R : int 227 | Frequency resolution given in cents 228 | n_harmonics : int 229 | Number of harmonics 230 | alpha : float 231 | Weighting parameter for harmonics 232 | beta : float 233 | Compression parameter for spectrogram magnitudes 234 | gamma : float 235 | Magnitude threshold 236 | harmonic_win_len : int 237 | Length of a frequency weighting window in bins 238 | 239 | Returns 240 | ------- 241 | Z : ndarray 242 | (F, T) salience representation of the input spectrogram 243 | """ 244 | 245 | # magnitude thresholding and compression 246 | eps = np.finfo(np.float32).eps 247 | threshold_mask = (20 * np.log10(lf_spec/np.max(lf_spec) + eps)) < gamma 248 | lf_spec = lf_spec**beta * threshold_mask 249 | 250 | # compute window 251 | max_diff_bins = harmonic_win_len // 2 252 | window = np.cos(np.linspace(-1, 1, 2*max_diff_bins+1)*np.pi/2)**2 # cosine^2 window 253 | 254 | # compute indices of harmonics 255 | harmonics = np.round(np.log2(np.arange(1, n_harmonics + 1)) * 1200 / R).astype(int) 256 | weighting_vec = np.zeros((lf_spec.shape[0] + max_diff_bins)) 257 | 258 | # compute weights 259 | for idx, h in enumerate(harmonics): 260 | if h+harmonic_win_len > len(weighting_vec): 261 | break # we reached the maximum length available 262 | weighting_vec[h:h+harmonic_win_len] += window * alpha**idx 263 | 264 | # correlate lf_spec with the weighting vector on the frequency axis 265 | Z = ndimage.correlate1d(lf_spec, weighting_vec[:], 266 | axis=0, mode='constant', cval=0, origin=-len(weighting_vec)//2 + max_diff_bins) 267 | 268 | # magnitude thresholding and compression 269 | threshold_mask = (20 * np.log10(Z / np.max(Z) + eps)) < gamma 270 | Z = Z ** beta * threshold_mask 271 | 272 | return Z 273 | 274 | 275 | def define_transition_matrix(B, tol=0, score_low=0.01, score_high=1.0): 276 | """ 277 | Generate transition matrix for dynamic programming 278 | 279 | Parameters 280 | ---------- 281 | B : int 282 | Number of bins 283 | tol : int 284 | Tolerance parameter for transition matrix (Default value = 0) 285 | score_low : float 286 | Score (low) for transition matrix (Default value = 0.01) 287 | score_high : float 288 | Score (high) for transition matrix (Default value = 1.0) 289 | 290 | Returns 291 | ------- 292 | T : ndarray 293 | (B, B) Transition matrix 294 | 295 | See also 296 | -------- 297 | [FMP] Notebook: C8/C8S2_FundFreqTracking.ipynb 298 | """ 299 | 300 | col = np.ones((B,)) * score_low 301 | col[0:tol+1] = np.ones((tol+1, )) * score_high 302 | T = linalg.toeplitz(col) 303 | return T 304 | 305 | 306 | @njit 307 | def compute_trajectory_dp(Z, T): 308 | """ 309 | Trajectory tracking using dynamic programming 310 | 311 | Parameters 312 | ---------- 313 | Z : ndarray 314 | Salience representation 315 | T : ndarray 316 | Transisition matrix 317 | 318 | Returns 319 | ------- 320 | eta_DP : ndarray 321 | Trajectory indices 322 | 323 | See also 324 | -------- 325 | [FMP] Notebook: C8/C8S2_FundFreqTracking.ipynb 326 | """ 327 | 328 | B, N = Z.shape 329 | eps_machine = np.finfo(np.float32).eps 330 | Z_log = np.log(Z + eps_machine) 331 | T_log = np.log(T + eps_machine) 332 | 333 | E = np.zeros((B, N)) 334 | D = np.zeros((B, N)) 335 | D[:, 0] = Z_log[:, 0] 336 | 337 | for n in np.arange(1, N): 338 | for b in np.arange(0, B): 339 | D[b, n] = np.max(T_log[b, :] + D[:, n-1]) + Z_log[b, n] 340 | E[b, n-1] = np.argmax(T_log[b, :] + D[:, n-1]) 341 | 342 | # backtracking 343 | eta_DP = np.zeros(N) 344 | eta_DP[N-1] = int(np.argmax(D[:, N-1])) 345 | 346 | for n in np.arange(N-2, -1, -1): 347 | eta_DP[n] = E[int(eta_DP[n+1]), n] 348 | 349 | return eta_DP.astype(np.int64) 350 | 351 | 352 | def compute_trajectory_cr(Z, T_coef, F_coef_hertz, constraint_region=None, 353 | tol=5, score_low=0.01, score_high=1.0): 354 | """ 355 | Trajectory tracking with constraint regions 356 | Notebook: C8/C8S2_FundFreqTracking.ipynb 357 | 358 | Parameters 359 | ---------- 360 | Z : ndarray 361 | Salience representation 362 | T_coef : ndarray 363 | Time axis 364 | F_coef_hertz : ndarray 365 | Frequency axis in Hz 366 | constraint_region : ndarray or None 367 | Constraint regions, row-format: (t_start_sec, t_end_sec, f_start_hz, f_end_hz) 368 | (Default value = None) 369 | tol : int 370 | Tolerance parameter for transition matrix (Default value = 5) 371 | score_low : float 372 | Score (low) for transition matrix (Default value = 0.01) 373 | score_high : float 374 | Score (high) for transition matrix (Default value = 1.0) 375 | 376 | Returns 377 | ------- 378 | eta : ndarray 379 | Trajectory indices, unvoiced frames are indicated with -1 380 | 381 | See also 382 | -------- 383 | [FMP] Notebook: C8/C8S2_FundFreqTracking.ipynb 384 | """ 385 | 386 | # do tracking within every constraint region 387 | if constraint_region is not None: 388 | # initialize contour, unvoiced frames are indicated with -1 389 | eta = np.full(len(T_coef), -1) 390 | 391 | for row_idx in range(constraint_region.shape[0]): 392 | t_start = constraint_region[row_idx, 0] # sec 393 | t_end = constraint_region[row_idx, 1] # sec 394 | f_start = constraint_region[row_idx, 2] # Hz 395 | f_end = constraint_region[row_idx, 3] # Hz 396 | 397 | # convert start/end values to indices 398 | t_start_idx = np.argmin(np.abs(T_coef - t_start)) 399 | t_end_idx = np.argmin(np.abs(T_coef - t_end)) 400 | f_start_idx = np.argmin(np.abs(F_coef_hertz - f_start)) 401 | f_end_idx = np.argmin(np.abs(F_coef_hertz - f_end)) 402 | 403 | # track in salience part 404 | cur_Z = Z[f_start_idx:f_end_idx+1, t_start_idx:t_end_idx+1] 405 | T = define_transition_matrix(cur_Z.shape[0], tol=tol, 406 | score_low=score_low, score_high=score_high) 407 | cur_eta = compute_trajectory_dp(cur_Z, T) 408 | 409 | # fill contour 410 | eta[t_start_idx:t_end_idx+1] = f_start_idx + cur_eta 411 | else: 412 | T = define_transition_matrix(Z.shape[0], tol=tol, score_low=score_low, score_high=score_high) 413 | eta = compute_trajectory_dp(Z, T) 414 | 415 | return eta 416 | 417 | 418 | def frequency_to_bin_index(F, R, F_ref): 419 | """ 420 | Binning function with variable frequency resolution 421 | Note: Indexing starts with 0 (opposed to [FMP, Eq. (8.49)]) 422 | 423 | Parameters 424 | ---------- 425 | F : float or ndarray 426 | Frequency in Hz 427 | R : float 428 | Frequency resolution in cents (Default value = 10.0) 429 | F_ref : float 430 | Reference frequency in Hz (Default value = 55.0) 431 | 432 | Returns 433 | ------- 434 | bin_index (int): Index for bin (starting with index 0) 435 | 436 | See also 437 | -------- 438 | [FMP] Notebook: C8/C8S2_SalienceRepresentation.ipynb 439 | """ 440 | bin_index = np.floor((1200 / R) * np.log2(F / F_ref) + 0.5).astype(np.int64) 441 | return bin_index 442 | -------------------------------------------------------------------------------- /libf0/swipe.py: -------------------------------------------------------------------------------- 1 | """ 2 | | Description: libf0 SWIPE implementation 3 | | Contributors: Sebastian Rosenzweig, Vojtěch Pešek, Simon Schwär, Meinard Müller 4 | | License: The MIT license, https://opensource.org/licenses/MIT 5 | | This file is part of libf0. 6 | """ 7 | from scipy import interpolate 8 | import numpy as np 9 | import librosa 10 | 11 | 12 | def swipe(x, Fs=22050, H=256, F_min=55.0, F_max=1760.0, dlog2p=1 / 96, derbs=0.1, strength_threshold=0): 13 | """ 14 | Implementation of a sawtooth waveform inspired pitch estimator (SWIPE). 15 | This version of the algorithm follows the original implementation, see `swipe_slim` for a more efficient 16 | alternative. 17 | 18 | .. [#] Arturo Camacho and John G. Harris, 19 | "A sawtooth waveform inspired pitch estimator for speech and music." 20 | The Journal of the Acoustical Society of America, vol. 124, no. 3, pp. 1638–1652, Sep. 2008 21 | 22 | Parameters 23 | ---------- 24 | x : ndarray 25 | Audio signal 26 | Fs : int 27 | Sampling rate 28 | H : int 29 | Hop size 30 | F_min : float or int 31 | Minimal frequency 32 | F_max : float or int 33 | Maximal frequency 34 | dlog2p : float 35 | resolution of the pitch candidate bins in octaves (default value = 1/96 -> 96 bins per octave) 36 | derbs : float 37 | resolution of the ERB bands (default value = 0.1) 38 | strength_threshold : float 39 | confidence threshold [0, 1] for the pitch detection (default value = 0) 40 | 41 | Returns 42 | ------- 43 | f0 : ndarray 44 | Estimated F0-trajectory 45 | t : ndarray 46 | Time axis 47 | strength : ndarray 48 | Confidence/Pitch Strength 49 | """ 50 | 51 | t = np.arange(0, len(x), H) / Fs # Times 52 | 53 | # Compute pitch candidates 54 | pc = 2 ** np.arange(np.log2(F_min), np.log2(F_max), dlog2p) 55 | 56 | # Pitch strength matrix 57 | S = np.zeros((len(pc), len(t))) 58 | 59 | # Determine P2-WSs [max, min] 60 | log_ws_max = np.ceil(np.log2((8 / F_min) * Fs)) 61 | log_ws_min = np.floor(np.log2((8 / F_max) * Fs)) 62 | 63 | # P2-WSs - window sizes in samples 64 | ws = 2 ** np.arange(log_ws_max, log_ws_min - 1, -1, dtype=np.int32) 65 | # print(f'window sizes in samples: {ws}') 66 | 67 | # Determine window sizes used by each pitch candidate 68 | log2pc = np.arange(np.log2(F_min), np.log2(F_max), dlog2p) 69 | d = log2pc - np.log2(np.divide(8 * Fs, ws[0])) 70 | 71 | # Create ERBs spaced frequencies (in Hertz) 72 | fERBs = erbs2hz(np.arange(hz2erbs(pc[0] / 4), hz2erbs(Fs / 2), derbs)) 73 | 74 | for i in range(0, len(ws)): 75 | N = ws[i] 76 | H = int(N / 2) 77 | 78 | x_zero_padded = np.concatenate([x, np.zeros(N)]) 79 | 80 | X = librosa.stft(x_zero_padded, n_fft=N, hop_length=H, pad_mode='constant', center=True) 81 | ti = librosa.frames_to_time(np.arange(0, X.shape[1]), sr=Fs, hop_length=H, n_fft=N) 82 | f = librosa.fft_frequencies(sr=Fs, n_fft=N) 83 | 84 | ti = np.insert(ti, 0, 0) 85 | ti = np.delete(ti, -1) 86 | 87 | spectrum = np.abs(X) 88 | magnitude = resample_ferbs(spectrum, f, fERBs) 89 | loudness = np.sqrt(magnitude) 90 | 91 | # Select candidates that use this window size 92 | # First window 93 | if i == 0: 94 | j = np.argwhere(d < 1).flatten() 95 | k = np.argwhere(d[j] > 0).flatten() 96 | # Last Window 97 | elif i == len(ws) - 1: 98 | j = np.argwhere(d - i > -1).flatten() 99 | k = np.argwhere(d[j] - i < 0).flatten() 100 | else: 101 | j = np.argwhere(np.abs(d - i) < 1).flatten() 102 | k = np.arange(0, len(j)) 103 | 104 | pc_to_compute = pc[j] 105 | 106 | pitch_strength = pitch_strength_all_candidates(fERBs, loudness, pc_to_compute) 107 | 108 | resampled_pitch_strength = resample_time(pitch_strength, t, ti) 109 | 110 | lambda_ = d[j[k]] - i 111 | mu = np.ones(len(j)) 112 | mu[k] = 1 - np.abs(lambda_) 113 | 114 | S[j, :] = S[j, :] + np.multiply( 115 | np.ones(resampled_pitch_strength.shape) * mu.reshape((mu.shape[0], 1)), 116 | resampled_pitch_strength 117 | ) 118 | 119 | # Fine-tune the pitch using parabolic interpolation 120 | pitches, strength = parabolic_int(S, strength_threshold, pc) 121 | 122 | pitches[np.where(np.isnan(pitches))] = 0 # avoid NaN output 123 | 124 | return pitches, t, strength 125 | 126 | 127 | def nyquist(Fs): 128 | """Nyquist Frequency""" 129 | return Fs / 2 130 | 131 | 132 | def F_coef(k, N, Fs): 133 | """Physical frequency of STFT coefficients""" 134 | return (k * Fs) / N 135 | 136 | 137 | def T_coef(m, H, Fs): 138 | """Physical time of STFT coefficients""" 139 | return m * H / Fs 140 | 141 | 142 | def stft_with_f_t(y, N, H, Fs): 143 | """STFT wrapper""" 144 | x = librosa.stft(y, int(N), int(H), pad_mode='constant', center=True) 145 | f = F_coef(np.arange(0, x.shape[0]), N, Fs) 146 | t = T_coef(np.arange(0, x.shape[1]), H, Fs) 147 | 148 | return x, f, t 149 | 150 | 151 | def hz2erbs(hz): 152 | """Convert Hz to ERB scale""" 153 | return 21.4 * np.log10(1 + hz / 229) 154 | 155 | 156 | def erbs2hz(erbs): 157 | """Convert ERB to Hz""" 158 | return (10 ** np.divide(erbs, 21.4) - 1) * 229 159 | 160 | 161 | def pitch_strength_all_candidates(ferbs, loudness, pitch_candidates): 162 | """Compute pitch strength for all pitch candidates""" 163 | # Normalize loudness 164 | normalization_loudness = np.full_like(loudness, np.sqrt(np.sum(loudness * loudness, axis=0))) 165 | with np.errstate(divide='ignore', invalid='ignore'): 166 | loudness = loudness / normalization_loudness 167 | 168 | # Create pitch salience matrix 169 | S = np.zeros((len(pitch_candidates), loudness.shape[1])) 170 | 171 | for j in range(0, len(pitch_candidates)): 172 | S[j, :] = pitch_strength_one(ferbs, loudness, pitch_candidates[j]) 173 | return S 174 | 175 | 176 | def pitch_strength_one(erbs_frequencies, normalized_loudness, pitch_candidate): 177 | """Compute pitch strength for one pitch candidate""" 178 | number_of_harmonics = np.floor(erbs_frequencies[-1] / pitch_candidate - 0.75).astype(np.int32) 179 | k = np.zeros(erbs_frequencies.shape) 180 | 181 | # f_prime / f 182 | q = erbs_frequencies / pitch_candidate 183 | 184 | for i in np.concatenate(([1], primes(number_of_harmonics))): 185 | a = np.abs(q - i) 186 | p = a < 0.25 187 | k[p] = np.cos(np.dot(2 * np.pi, q[p])) 188 | v = np.logical_and(0.25 < a, a < 0.75) 189 | k[v] = k[v] + np.cos(np.dot(2 * np.pi, q[v])) / 2 190 | 191 | # Apply envelope 192 | k = np.multiply(k, np.sqrt(1.0 / erbs_frequencies)) 193 | 194 | # K+-normalize kernel 195 | k = k / np.linalg.norm(k[k > 0]) 196 | 197 | # Compute pitch strength 198 | S = np.dot(k, normalized_loudness) 199 | return S 200 | 201 | 202 | def resample_ferbs(spectrum, f, ferbs): 203 | """Resample to ERB scale""" 204 | magnitude = np.zeros((len(ferbs), spectrum.shape[1])) 205 | 206 | for t in range(spectrum.shape[1]): 207 | spl = interpolate.splrep(f, spectrum[:, t]) 208 | interpolate.splev(ferbs, spl) 209 | 210 | magnitude[:, t] = interpolate.splev(ferbs, spl) 211 | 212 | return np.maximum(magnitude, 0) 213 | 214 | 215 | def resample_time(pitch_strength, resampled_time, ti): 216 | """Resample time axis""" 217 | if pitch_strength.shape[1] > 0: 218 | pitch_strength = interpolate_one_candidate(pitch_strength, ti, resampled_time) 219 | else: 220 | pitch_strength = np.kron(np.ones((len(pitch_strength), len(resampled_time))), np.NaN) 221 | return pitch_strength 222 | 223 | 224 | def interpolate_one_candidate(pitch_strength, ti, resampled_time): 225 | """Interpolate time axis""" 226 | pitch_strength_interpolated = np.zeros((pitch_strength.shape[0], len(resampled_time))) 227 | 228 | for s in range(pitch_strength.shape[0]): 229 | t_i = interpolate.interp1d(ti, pitch_strength[s, :], 'linear', bounds_error=True) 230 | pitch_strength_interpolated[s, :] = t_i(resampled_time) 231 | 232 | return pitch_strength_interpolated 233 | 234 | 235 | def parabolic_int(pitch_strength, strength_threshold, pc): 236 | """Parabolic interpolation between pitch candidates using pitch strength""" 237 | p = np.full((pitch_strength.shape[1],), np.NaN) 238 | s = np.full((pitch_strength.shape[1],), np.NaN) 239 | 240 | for j in range(pitch_strength.shape[1]): 241 | i = np.argmax(pitch_strength[:, j]) 242 | s[j] = pitch_strength[i, j] 243 | 244 | if s[j] < strength_threshold: 245 | continue 246 | 247 | if i == 0: 248 | p[j] = pc[0] 249 | elif i == len(pc) - 1: 250 | p[j] = pc[0] 251 | else: 252 | I = np.arange(i - 1, i + 2) 253 | tc = 1 / pc[I] 254 | ntc = np.dot((tc / tc[1] - 1), 2 * np.pi) 255 | if np.any(np.isnan(pitch_strength[I, j])): 256 | s[j] = np.nan 257 | p[j] = np.nan 258 | else: 259 | c = np.polyfit(ntc, pitch_strength[I, j], 2) 260 | ftc = 1 / 2 ** np.arange(np.log2(pc[I[0]]), np.log2(pc[I[2]]), 1 / 12 / 64) 261 | nftc = np.dot((ftc / tc[1] - 1), 2 * np.pi) 262 | poly = np.polyval(c, nftc) 263 | k = np.argmax(poly) 264 | s[j] = poly[k] 265 | p[j] = 2 ** (np.log2(pc[I[0]]) + k / 12 / 64) 266 | return p, s 267 | 268 | 269 | def primes(n): 270 | """Returns a set of n prime numbers""" 271 | small_primes = np.array([2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 272 | 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 273 | 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 274 | 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 275 | 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 276 | 509, 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617, 619, 277 | 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 743, 278 | 751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, 839, 853, 857, 859, 863, 279 | 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, 953, 967, 971, 977, 983, 991, 997]) 280 | 281 | b = small_primes <= n 282 | return small_primes[b] 283 | -------------------------------------------------------------------------------- /libf0/swipe_slim.py: -------------------------------------------------------------------------------- 1 | """ 2 | | Description: libf0 SWIPE slim implementation 3 | | Contributors: Sebastian Rosenzweig, Simon Schwär, Meinard Müller 4 | | License: The MIT license, https://opensource.org/licenses/MIT 5 | | This file is part of libf0. 6 | """ 7 | import numpy as np 8 | import librosa 9 | from .yin import parabolic_interpolation 10 | from scipy.interpolate import interp1d 11 | 12 | 13 | def swipe_slim(x, Fs=22050, H=256, F_min=55.0, F_max=1760.0, R=10, strength_threshold=0): 14 | """ 15 | Slim and didactical implementation of a sawtooth waveform inspired pitch estimator (SWIPE). 16 | This version uses a log-frequency spectrogram instead of ERB filters. Furthermore, it is implemented more 17 | efficiently. See `swipe()` for the original implementation. 18 | 19 | .. [#] A. Camacho and J. G. Harris, 20 | "A sawtooth waveform inspired pitch estimator for speech and music." 21 | The Journal of the Acoustical Society of America, vol. 124, no. 3, pp. 1638–1652, Sep. 2008 22 | 23 | Parameters 24 | ---------- 25 | x : ndarray 26 | Audio signal 27 | Fs : int 28 | Sampling rate 29 | H : int 30 | Hop size 31 | F_min : float or int 32 | Minimal frequency 33 | F_max : float or int 34 | Maximal frequency 35 | R : float 36 | resolution of the pitch candidate bins in cents (default = 10) 37 | strength_threshold : float 38 | confidence threshold [0, 1] for the pitch detection (default value = 0) 39 | 40 | Returns 41 | ------- 42 | f0 : ndarray 43 | Estimated F0-trajectory 44 | t : ndarray 45 | Time axis 46 | conf : ndarray 47 | Confidence / Pitch Strength 48 | """ 49 | 50 | # compute time and frequency axis 51 | t = np.arange(0, len(x), H) / Fs # time axis 52 | F_coef_log = np.arange(0, np.log2(Fs/2/F_min), R/1200) 53 | F_coef_log_hz = F_min * 2 ** F_coef_log # pitch candidates 54 | 55 | # pre-compute kernels, one kernel for each pitch candidate in range [F_min : F_max] 56 | F_min_idx = np.argmin(np.abs(F_coef_log_hz - F_min)) 57 | F_max_idx = np.argmin(np.abs(F_coef_log_hz - F_max)) 58 | B = F_max_idx - F_min_idx # Number of pitch candidates 59 | kernels = np.zeros((B, len(F_coef_log_hz))) 60 | for i, f in enumerate(F_coef_log_hz[F_min_idx:F_max_idx]): 61 | kernels[i, :] = compute_kernel(f, F_coef_log_hz) 62 | 63 | # determine optimal window length for each candidate 64 | L_opt = np.log2(Fs * 8 / np.array([F_min, F_max])) # exponents for optimal window sizes 2^L, see paper Section II.G 65 | L_rnd = np.arange(np.round(L_opt[1]), np.round(L_opt[0])+1).astype(np.int32) # range of rounded exponents 66 | N_pow2 = 2 ** L_rnd # Compute rounded power-2 windows sizes 67 | # Quantization error between optimal window size (see paper Section II.G) and rounded power-2 windows size 68 | # Using only the largest N here, since errors for other N can be derived from err by subtracting exponent (cyclic) 69 | err = np.abs(np.log2(8 * Fs / F_coef_log_hz[F_min_idx:F_max_idx]) - np.log2(np.max(N_pow2))) 70 | 71 | S = np.zeros((B, len(t))) # "pitch-strength" matrix 72 | 73 | # loop through all window sizes 74 | for octave, N in enumerate(N_pow2): 75 | # Compute STFT 76 | x_pad = np.pad(x, (0, N)) # to avoid problems during time axis interpolation 77 | H = N // 2 78 | X = librosa.stft(x_pad, n_fft=N, hop_length=H, win_length=N, window='hann', pad_mode='constant', center=True) 79 | Y = np.abs(X) 80 | T_coef_lin_s = np.arange(0, X.shape[1]) * H / Fs 81 | F_coef_lin_hz = np.arange(N // 2 + 1) * Fs / N 82 | 83 | # Resample to log-frequency axis 84 | compute_Y_log = interp1d(F_coef_lin_hz, Y, kind='cubic', axis=0) 85 | Y_log = compute_Y_log(F_coef_log_hz) 86 | 87 | # Normalize magnitudes 88 | Y_log /= np.sqrt(np.sum(Y_log ** 2, axis=0)) + np.finfo(float).eps 89 | 90 | # Correlate kernels with log-spectrum for pitch candidates where N is optimal 91 | S_N = np.matmul(kernels, Y_log) 92 | 93 | # Resample time axis 94 | compute_S_N_res = interp1d(T_coef_lin_s, S_N, kind='linear', axis=1) 95 | S_N_res = compute_S_N_res(t) 96 | 97 | # Weight pitch strength according to quantization error 98 | candidates = (err > octave - 1) & (err < octave + 1) # consider pitches +/- 1 octave from current window 99 | mu = 1 - np.abs(err[candidates] - octave) 100 | 101 | S[candidates, :] += np.multiply(mu.reshape(-1, 1), S_N_res[candidates, :]) 102 | 103 | # Obtain pitch estimates and corresponding confidence 104 | max_indices = np.argmax(S, axis=0) 105 | conf = np.max(S, axis=0) 106 | 107 | # Parabolic Interpolation of pitch estimates for refinement 108 | time_idx = np.arange(S.shape[1]) 109 | indeces_shift, _ = parabolic_interpolation(S[max_indices-1, time_idx], 110 | S[max_indices, time_idx], 111 | S[max_indices+1, time_idx]) 112 | compute_f0_log = interp1d(np.arange(len(F_coef_log)), F_coef_log, kind='linear') 113 | f0_hz = F_min * 2 ** compute_f0_log(max_indices+indeces_shift) 114 | 115 | # Thresholding 116 | f0_hz[conf < strength_threshold] = 0 # discard estimates where confidence is low 117 | 118 | return f0_hz, t, conf 119 | 120 | 121 | def compute_kernel(f, F_coef_log_hz): 122 | """ 123 | Compute a SWIPE' kernel. 124 | 125 | Parameters 126 | ---------- 127 | f : float 128 | Frequency in Hz 129 | F_coef_log_hz : 130 | Logarithmic frequency axis in Hz 131 | 132 | Returns 133 | ------- 134 | k : ndarray 135 | Kernel 136 | """ 137 | k = np.zeros(len(F_coef_log_hz)) 138 | n_harmonics = np.floor(F_coef_log_hz[-1] / f).astype(np.int32) 139 | prime_numbers = prime_and_one(100)[:n_harmonics] # only consider prime harmonics for kernel peaks 140 | 141 | ratio = F_coef_log_hz / f 142 | 143 | # loop through all prime harmonics 144 | for p in prime_numbers: 145 | a = np.abs(ratio - p) # normalized distance between harmonic and current pitch candidate 146 | main_peak_bins = a < 0.25 147 | k[main_peak_bins] = np.cos(np.dot(np.array(2 * np.pi).reshape(-1, 1), 148 | ratio[main_peak_bins].reshape(1, -1))).flatten() 149 | valley_bins = np.logical_and(0.25 < a, a < 0.75) 150 | k[valley_bins] += np.cos(np.dot(np.array(2 * np.pi).reshape(-1, 1), 151 | ratio[valley_bins].reshape(1, -1))).flatten() / 2 152 | 153 | # Apply decay 154 | k = np.multiply(k, np.sqrt(1.0 / F_coef_log_hz)) 155 | 156 | # K+-normalize kernel 157 | k = k / np.linalg.norm(k[k > 0]) 158 | 159 | return k 160 | 161 | 162 | def prime_and_one(upto=1000000): 163 | """ 164 | Returns a set of prime numbers, adapted from http://rebrained.com/?p=458 165 | 166 | Parameters 167 | ---------- 168 | upto : int 169 | Find prime numbers up to this number 170 | 171 | Returns 172 | ------- 173 | A set of prime numbers including 1 & 2 174 | """ 175 | primes = np.arange(3, upto+1, 2) 176 | isprime = np.ones((upto-1)//2, dtype=np.bool8) 177 | for factor in primes[:int(np.sqrt(upto))//2]: 178 | if isprime[(factor-2)//2]: 179 | isprime[(factor*3-2)//2::factor] = 0 180 | return np.concatenate((np.array([1, 2]), primes[isprime])) 181 | -------------------------------------------------------------------------------- /libf0/utils.py: -------------------------------------------------------------------------------- 1 | """ 2 | | Description: libf0 utility functions 3 | | Contributors: Sebastian Rosenzweig, Simon Schwär, Meinard Müller 4 | | License: The MIT license, https://opensource.org/licenses/MIT 5 | | This file is part of libf0. 6 | """ 7 | import numpy as np 8 | 9 | 10 | def sonify_trajectory_with_sinusoid(f0, t, audio_len, confidence=None, Fs=22050, smooth_len=11): 11 | """ 12 | Sonification of trajectory with sinusoidal. Adapted from FMP notebook: C8/C8S2_FundFreqTracking.ipynb 13 | 14 | Parameters 15 | ---------- 16 | f0 : ndarray 17 | F0-trajectory 18 | t : ndarray 19 | Time axis 20 | audio_len : int 21 | Desired audio length in samples 22 | confidence : None or ndarray 23 | Confidence values for amplitude control 24 | Fs : int 25 | Sampling rate 26 | smooth_len : int 27 | Smoothing filter length to avoid clicks in the sonification 28 | 29 | Returns 30 | ------- 31 | x_soni : ndarray 32 | Sonified F0-trajectory 33 | """ 34 | if confidence is None: 35 | confidence = np.ones_like(f0) 36 | 37 | # initialize 38 | x_soni = np.zeros(audio_len) 39 | amplitude_mod = np.zeros(audio_len) 40 | 41 | # Computation of hop size 42 | sine_len = int(t[1] * Fs) 43 | 44 | t = np.arange(0, sine_len) / Fs 45 | phase = 0 46 | 47 | # loop over all F0 values, ensure continuous phase 48 | for idx in np.arange(0, len(f0)): 49 | cur_f = f0[idx] 50 | cur_amp = confidence[idx] 51 | 52 | if cur_f == 0: 53 | phase = 0 54 | continue 55 | 56 | cur_soni = np.sin(2*np.pi*(cur_f*t+phase)) 57 | diff = np.maximum(0, (idx+1)*sine_len - len(x_soni)) 58 | if diff > 0: 59 | x_soni[idx * sine_len:(idx + 1) * sine_len - diff] = cur_soni[:-diff] 60 | amplitude_mod[idx * sine_len:(idx + 1) * sine_len - diff] = cur_amp 61 | else: 62 | x_soni[idx*sine_len:(idx+1)*sine_len-diff] = cur_soni 63 | amplitude_mod[idx*sine_len:(idx+1)*sine_len-diff] = cur_amp 64 | 65 | phase += cur_f * sine_len / Fs 66 | phase -= 2 * np.round(phase/2) 67 | 68 | # filter amplitudes to avoid transients 69 | amplitude_mod = np.convolve(amplitude_mod, np.hanning(smooth_len)/np.sum(np.hanning(smooth_len)), 'same') 70 | x_soni = x_soni * amplitude_mod 71 | return x_soni 72 | 73 | 74 | def hz_to_cents(F, F_ref=55.0): 75 | """ 76 | Converts frequency in Hz to cents. 77 | 78 | Parameters 79 | ---------- 80 | F : float or ndarray 81 | Frequency value in Hz 82 | F_ref : float 83 | Reference frequency in Hz (Default value = 55.0) 84 | Returns 85 | ------- 86 | F_cents : float or ndarray 87 | Frequency in cents 88 | """ 89 | 90 | # Avoid division by 0 91 | F_temp = np.array(F).astype(float) 92 | F_temp[F_temp == 0] = np.nan 93 | 94 | F_cents = 1200 * np.log2(F_temp / F_ref) 95 | 96 | return F_cents 97 | 98 | 99 | def cents_to_hz(F_cents, F_ref=55.0): 100 | """ 101 | Converts frequency in cents to Hz. 102 | 103 | Parameters 104 | ---------- 105 | F_cents : float or ndarray 106 | Frequency in cents 107 | F_ref : float 108 | Reference frequency in Hz (Default value = 55.0) 109 | Returns 110 | ------- 111 | F : float or ndarray 112 | Frequency in Hz 113 | """ 114 | F = F_ref * 2 ** (F_cents / 1200) 115 | 116 | # Avoid NaN output 117 | F = np.nan_to_num(F, copy=False, nan=0) 118 | 119 | return F 120 | -------------------------------------------------------------------------------- /libf0/yin.py: -------------------------------------------------------------------------------- 1 | """ 2 | | Description: libf0 YIN implementation 3 | | Contributors: Sebastian Rosenzweig, Simon Schwär, Edgar Suárez, Meinard Müller 4 | | License: The MIT license, https://opensource.org/licenses/MIT 5 | | This file is part of libf0. 6 | """ 7 | import numpy as np 8 | from numba import njit 9 | 10 | 11 | def yin(x, Fs=22050, N=2048, H=256, F_min=55.0, F_max=1760.0, threshold=0.15, verbose=False): 12 | """ 13 | Implementation of the YIN algorithm. 14 | 15 | .. [#] Alain De Cheveigné and Hideki Kawahara. 16 | "YIN, a fundamental frequency estimator for speech and music." 17 | The Journal of the Acoustical Society of America 111.4 (2002): 1917-1930. 18 | 19 | Parameters 20 | ---------- 21 | x : ndarray [shape=(L, )], real - valued 22 | Audio signal 23 | Fs : int 24 | Sampling frequency 25 | N : int 26 | Window size 27 | H : int 28 | Hop size 29 | F_min : float 30 | Minimal frequency 31 | F_max : float 32 | Maximal frequency 33 | threshold : float 34 | Threshold for cumulative mean normalized difference function 35 | verbose : bool 36 | Switch to activate/deactivate status bar 37 | 38 | Returns 39 | ------- 40 | f0 : ndarray 41 | Estimated F0-trajectory 42 | t : ndarray 43 | Time axis 44 | ap: ndarray 45 | Aperiodicity (indicator for voicing: the lower, the more reliable the estimate) 46 | """ 47 | 48 | if F_min > F_max: 49 | raise Exception("F_min must be smaller than F_max!") 50 | 51 | if F_min < Fs/N: 52 | raise Exception(f"The condition (F_min >= Fs/N) was not met. With Fs = {Fs}, N = {N} and F_min = {F_min} you have the following options: \n1) Set F_min >= {np.ceil(Fs/N)} Hz. \n2) Set N >= {np.ceil(Fs/F_min).astype(int)}. \n3) Set Fs <= {np.floor(F_min * N)} Hz.") 53 | 54 | x_pad = np.concatenate((np.zeros(N//2), x, np.zeros(N//2))) # Add zeros for centered estimates 55 | M = int(np.floor((len(x_pad) - N) / H)) + 1 # Compute number of estimates that will be generated 56 | f0 = np.zeros(M) # Estimated fundamental frequencies (0 for unspecified frames) 57 | t = np.arange(M)*H/Fs # Time axis 58 | ap = np.zeros(M) # Aperiodicity 59 | 60 | lag_min = max(int(np.ceil(Fs / F_max)), 1) # lag of maximal frequency in samples 61 | lag_max = int(np.ceil(Fs / F_min)) # lag of minimal frequency in samples 62 | 63 | for m in range(M): 64 | if verbose: 65 | print(f"YIN Progress: {np.ceil(100*m/M).astype(int)}%", end='\r') 66 | # Take a frame from input signal 67 | frame = x_pad[m*H:m*H + N] 68 | 69 | # Cumulative Mean Normalized Difference Function 70 | cmndf = cumulative_mean_normalized_difference_function(frame, lag_max) 71 | 72 | # Absolute Thresholding 73 | lag_est = absolute_thresholding(cmndf, threshold, lag_min, lag_max, parabolic_interp=True) 74 | 75 | # Refine estimate by constraining search to vicinity of best local estimate (default: +/- 25 cents) 76 | tol_cents = 25 77 | lag_min_local = int(np.round(Fs / ((Fs / lag_est) * 2 ** (tol_cents/1200)))) 78 | if lag_min_local < lag_min: 79 | lag_min_local = lag_min 80 | lag_max_local = int(np.round(Fs / ((Fs / lag_est) * 2 ** (-tol_cents/1200)))) 81 | if lag_max_local > lag_max: 82 | lag_max_local = lag_max 83 | lag_new = absolute_thresholding(cmndf, threshold=np.inf, lag_min=lag_min_local, lag_max=lag_max_local, 84 | parabolic_interp=True) 85 | 86 | # Compute Fundamental Frequency Estimate 87 | f0[m] = Fs / lag_new 88 | 89 | # Compute Aperiodicity 90 | ap[m] = aperiodicity(frame, lag_new) 91 | 92 | return f0, t, ap 93 | 94 | 95 | @njit 96 | def cumulative_mean_normalized_difference_function(frame, lag_max): 97 | """ 98 | Computes Cumulative Mean Normalized Difference Function (CMNDF). 99 | 100 | Parameters 101 | ---------- 102 | frame : ndarray 103 | Audio frame 104 | lag_max : int 105 | Maximum expected lag in the CMNDF 106 | 107 | Returns 108 | ------- 109 | cmndf : ndarray 110 | Cumulative Mean Normalized Difference Function 111 | """ 112 | 113 | cmndf = np.zeros(lag_max+1) # Initialize CMNDF 114 | cmndf[0] = 1 115 | diff_mean = 0 116 | 117 | for tau in range(1, lag_max+1): 118 | # Difference function 119 | diff = np.sum((frame[0:-tau] - frame[0 + tau:]) ** 2) 120 | # Iterative mean of the difference function 121 | diff_mean = diff_mean*(tau-1)/tau + diff/tau 122 | 123 | cmndf[tau] = diff / (diff_mean + np.finfo(np.float64).eps) 124 | 125 | return cmndf 126 | 127 | 128 | def absolute_thresholding(cmndf, threshold, lag_min, lag_max, parabolic_interp=True): 129 | """ 130 | Absolute thresholding: 131 | Set an absolute threshold and choose the smallest value of tau that gives a minimum of d' deeper than that 132 | threshold. If none is found, the global minimum is chosen instead. 133 | 134 | Parameters 135 | ---------- 136 | cmndf : ndarray 137 | Cumulative Mean Normalized Difference Function 138 | threshold : float 139 | Threshold 140 | lag_min : float 141 | Minimal lag 142 | lag_max : float 143 | Maximal lag 144 | parabolic_interp : bool 145 | Switch to activate/deactivate parabolic interpolation 146 | 147 | Returns 148 | ------- 149 | 150 | """ 151 | 152 | # take shortcut if search range only allows for one possible lag 153 | if lag_min == lag_max: 154 | return lag_min 155 | 156 | # find local minima below absolute threshold in interval [lag_min:lag_max] 157 | local_min_idxs = (np.argwhere((cmndf[1:-1] < cmndf[0:-2]) & (cmndf[1:-1] < cmndf[2:]))).flatten() + 1 158 | below_thr_idxs = np.argwhere(cmndf[lag_min:lag_max] < threshold).flatten() + lag_min 159 | # numba compatible intersection of indices sets 160 | min_idxs = np.unique(np.array([i for i in local_min_idxs for j in below_thr_idxs if i == j])) 161 | 162 | # if no local minima below threshold are found, return global minimum 163 | if not min_idxs.size: 164 | return np.argmin(cmndf[lag_min:lag_max]) + lag_min 165 | 166 | # find first local minimum 167 | lag = np.min(min_idxs) # choose first local minimum 168 | 169 | # Optional: Parabolic Interpolation of local minima 170 | if parabolic_interp: 171 | lag_corr, cmndf[lag] = parabolic_interpolation(cmndf[lag-1], cmndf[lag], cmndf[lag+1]) 172 | lag += lag_corr 173 | 174 | return lag 175 | 176 | 177 | @njit 178 | def parabolic_interpolation(y1, y2, y3): 179 | """ 180 | Parabolic interpolation of an extremal value given three samples with equal spacing on the x-axis. 181 | The middle value y2 is assumed to be the extremal sample of the three. 182 | 183 | Parameters 184 | ---------- 185 | y1: f(x1) 186 | y2: f(x2) 187 | y3: f(x3) 188 | 189 | Returns 190 | ------- 191 | x_interp: Interpolated x-value (relative to x3-x2) 192 | y_interp: Interpolated y-value, f(x_interp) 193 | """ 194 | 195 | a = np.finfo(np.float64).eps + (y1 + y3 - 2 * y2) / 2 196 | b = (y3 - y1) / 2 197 | x_interp = -b / (2 * a) 198 | y_interp = y2 - (b ** 2) / (4 * a) 199 | 200 | return x_interp, y_interp 201 | 202 | 203 | def aperiodicity(frame, lag_est): 204 | """ 205 | Compute aperiodicity of given frame (serves as indicator for reliability or voicing detection). 206 | 207 | Parameters 208 | ---------- 209 | frame : ndarray 210 | Frame 211 | lag_est : float 212 | Estimated lag 213 | 214 | Returns 215 | ------- 216 | ap: float 217 | Aperiodicity (the lower, the more reliable the estimate) 218 | """ 219 | 220 | lag_int = int(np.floor(lag_est)) # uncorrected period estimate 221 | frac = lag_est - lag_int # residual 222 | 223 | # Pad frame to insure constant size 224 | frame_pad = np.concatenate((frame, np.flip(frame))) # mirror padding 225 | 226 | # Shift frame by estimated period 227 | if frac == 0: 228 | frame_shift = frame_pad[lag_int:lag_int+len(frame)] 229 | else: 230 | # linear interpolation between adjacent shifts 231 | frame_shift = (1 - frac) * frame_pad[lag_int:lag_int+len(frame)] + \ 232 | frac * frame_pad[lag_int+1:lag_int+1+len(frame)] 233 | 234 | pwr = (np.mean(frame ** 2) + np.mean(frame_shift ** 2)) / 2 # average power over fixed and shifted frame 235 | res = np.mean((frame - frame_shift) ** 2) / 2 # residual power 236 | ap = res / (pwr + np.finfo(np.float64).eps) 237 | 238 | return ap 239 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | 4 | with open('README.md', 'r') as fdesc: 5 | long_description = fdesc.read() 6 | 7 | setup( 8 | name='libf0', 9 | version='1.0.2', 10 | description='A Python Library for Fundamental Frequency Estimation in Music Recordings', 11 | author='Sebastian Rosenzweig, Simon Schwär, and Meinard Müller', 12 | author_email='meinard.mueller@audiolabs-erlangen.de', 13 | url='https://github.com/groupmm/libf0', 14 | download_url='https://github.com/groupmm/libf0', 15 | packages=find_packages(), 16 | long_description=long_description, 17 | long_description_content_type='text/markdown', 18 | classifiers=[ 19 | "License :: OSI Approved :: MIT License", 20 | "Programming Language :: Python", 21 | "Intended Audience :: Developers", 22 | "Topic :: Multimedia :: Sound/Audio :: Analysis", 23 | "Programming Language :: Python :: 3", 24 | ], 25 | keywords=['audio', 'music', 'f0', 'pitch', 'yin', 'pyin', 'melodia', 'swipe'], 26 | license='MIT', 27 | install_requires=[ 28 | 'librosa >= 0.8.0, < 1.0.0', 29 | 'numba >= 0.51.0, < 1.0.0', 30 | 'numpy >= 1.17.0, < 2.0.0', 31 | 'scipy >= 1.3.0, < 2.0.0'], 32 | python_requires='>=3.6', 33 | extras_require={ 34 | 'dev': ['ipython >= 7.8.0, < 8.0.0', 35 | 'jupyter == 1.0.*', 36 | 'matplotlib >= 3.1.0, < 4.0.0', 37 | 'nbstripout == 0.4.*', 38 | 'pysoundfile >= 0.9.0, < 1.0.0'], 39 | 'tests': ['pytest == 6.2.*'], 40 | 'docs': ['sphinx == 4.0.*', 41 | 'sphinx-rtd-theme == 0.5.*'], 42 | } 43 | ) 44 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/groupmm/libf0/0baf532b506e2489fd7abebcf229a3c7d9cb98b7/tests/__init__.py -------------------------------------------------------------------------------- /tests/__pycache__/conftest.cpython-38-pytest-7.1.2.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/groupmm/libf0/0baf532b506e2489fd7abebcf229a3c7d9cb98b7/tests/__pycache__/conftest.cpython-38-pytest-7.1.2.pyc -------------------------------------------------------------------------------- /tests/test_algorithms.py: -------------------------------------------------------------------------------- 1 | """ 2 | | Description: Tests for functions of libf0 3 | | Contributors: Sebastian Rosenzweig, Meinard Müller 4 | | License: The MIT license, https://opensource.org/licenses/MIT 5 | | This file is part of libf0. 6 | """ 7 | 8 | import numpy as np 9 | from scipy.signal import chirp 10 | from scipy.interpolate import interp1d 11 | import libf0 12 | 13 | 14 | # Generate test audio 1: Sinusoid 15 | Fs = 22050 # Sampling rate 16 | dur_sec = 5 # Duration of generated audio 17 | T_audio = np.arange(0, dur_sec*Fs) / Fs # Audio time axis 18 | F_sine = 440 # Frequency of sinusoid 19 | x1 = np.sin(2*np.pi*F_sine*T_audio) # Sinusoidal signal 20 | 21 | # Generate test audio 2: Sweep 22 | F_start_chirp = 220.0 23 | F_end_chirp = 640.0 24 | x2 = chirp(T_audio, F_start_chirp, dur_sec, F_end_chirp, method='logarithmic') 25 | 26 | F0_chirp = F_start_chirp * (F_end_chirp / F_start_chirp) ** (T_audio / dur_sec) # Ground truth F0 27 | chirp_func = interp1d(T_audio, F0_chirp, kind='cubic') # interpolation function to make outputs comparable 28 | 29 | # Further (global) parameters 30 | F_min = 55.0 # Minimum frequency 31 | F_max = 1760.0 # Maximum frequency 32 | N = 2048 # Window size 33 | H = 256 # Hop size 34 | atol_sine = 10 # absolute test tolerance for sine signal in cents, tolerance for chirp varies depending on algorithm 35 | 36 | 37 | def test_hz_to_cents(): 38 | F_cents = libf0.hz_to_cents(440, F_ref=55.0) 39 | assert F_cents == 3600 40 | 41 | 42 | def test_cents_to_hz(): 43 | F_hz = libf0.cents_to_hz(3600, F_ref=55.0) 44 | assert F_hz == 440 45 | 46 | 47 | def test_yin(): 48 | threshold = 0.15 49 | f0_yin_x1, t_yin_x1, conf_yin_x1 = libf0.yin(x1, Fs=Fs, N=N, H=H, F_min=F_min, F_max=F_max, threshold=threshold) 50 | f0_yin_x2, t_yin_x2, conf_yin_x2 = libf0.yin(x2, Fs=Fs, N=N, H=H, F_min=F_min, F_max=F_max, threshold=threshold) 51 | 52 | assert np.allclose(libf0.hz_to_cents(f0_yin_x1), libf0.hz_to_cents(F_sine), rtol=0, atol=atol_sine) 53 | assert np.allclose(libf0.hz_to_cents(f0_yin_x2), libf0.hz_to_cents(chirp_func(t_yin_x2)), rtol=0, atol=50) 54 | 55 | 56 | def test_pyin(): 57 | R = 10 58 | thrs = np.arange(0.01, 1, 0.01) 59 | betas = [1, 18] 60 | abs_min_prob = 0.01 61 | voicing_prob = 0.5 62 | f0_pyin_x1, t_pyin_x1, conf_pyin_x1 = libf0.pyin(x1, Fs=Fs, N=N, H=H, F_min=F_min, F_max=F_max, R=R, 63 | thresholds=thrs, beta_params=betas, 64 | absolute_min_prob=abs_min_prob, voicing_prob=voicing_prob) 65 | f0_pyin_x2, t_pyin_x2, conf_pyin_x2 = libf0.pyin(x2, Fs=Fs, N=N, H=H, F_min=F_min, F_max=F_max, R=R, 66 | thresholds=thrs, beta_params=betas, 67 | absolute_min_prob=abs_min_prob, voicing_prob=voicing_prob) 68 | 69 | # exclude first frame, since it is always 0 70 | assert np.allclose(libf0.hz_to_cents(f0_pyin_x1[1:]), libf0.hz_to_cents(F_sine), rtol=0, atol=atol_sine) 71 | assert np.allclose(libf0.hz_to_cents(f0_pyin_x2[1:]), libf0.hz_to_cents(chirp_func(t_pyin_x2[1:])), rtol=0, atol=20) 72 | 73 | 74 | def test_salience(): 75 | R = 10 76 | n_harm = 10 77 | freq_smooth_len = 11 78 | alpha = 0.9 79 | gamma = 0.0 80 | tol = 5 81 | score_low = 0.01 82 | score_high = 1.0 83 | f0_sal_x1, t_sal_x1, conf_sal_x1 = libf0.salience(x1, Fs=Fs, N=N, H=H, F_min=F_min, F_max=F_max, R=R, 84 | num_harm=n_harm, freq_smooth_len=freq_smooth_len, alpha=alpha, 85 | gamma=gamma, constraint_region=None, tol=tol, 86 | score_low=score_low, score_high=score_high) 87 | 88 | f0_sal_x2, t_sal_x2, conf_sal_x2 = libf0.salience(x2, Fs=Fs, N=N, H=H, F_min=F_min, F_max=F_max, R=R, 89 | num_harm=n_harm, freq_smooth_len=freq_smooth_len, alpha=alpha, 90 | gamma=gamma, constraint_region=None, tol=tol, 91 | score_low=score_low, score_high=score_high) 92 | 93 | assert np.allclose(libf0.hz_to_cents(f0_sal_x1), libf0.hz_to_cents(F_sine), rtol=0, atol=atol_sine) 94 | assert np.allclose(libf0.hz_to_cents(f0_sal_x2), libf0.hz_to_cents(chirp_func(t_sal_x2)), rtol=0, atol=20) 95 | 96 | 97 | def test_swipe(): 98 | dlog2p = 1 / 96 99 | derbs = 0.1 100 | strength_threshold = 0 101 | f0_swipe_x1, t_swipe_x1, conf_swipe_x1 = libf0.swipe(x1, Fs=Fs, H=H, F_min=F_min, F_max=F_max, dlog2p=dlog2p, 102 | derbs=derbs, strength_threshold=strength_threshold) 103 | f0_swipe_x2, t_swipe_x2, conf_swipe_x2 = libf0.swipe(x2, Fs=Fs, H=H, F_min=F_min, F_max=F_max, dlog2p=dlog2p, 104 | derbs=derbs, strength_threshold=strength_threshold) 105 | 106 | # exclude first an last frame due to artifacts at signal edges 107 | assert np.allclose(libf0.hz_to_cents(f0_swipe_x1[1:-1]), libf0.hz_to_cents(F_sine), rtol=0, atol=atol_sine) 108 | assert np.allclose(libf0.hz_to_cents(f0_swipe_x2[1:-1]), libf0.hz_to_cents(chirp_func(t_swipe_x2[1:-1])), rtol=0, 109 | atol=50) 110 | 111 | 112 | def test_swipe_slim(): 113 | R = 10 114 | strength_threshold = 0 115 | f0_swipes_x1, t_swipes_x1, conf_swipes_x1 = libf0.swipe_slim(x1, Fs=Fs, H=H, F_min=F_min, F_max=F_max, R=R, 116 | strength_threshold=strength_threshold) 117 | f0_swipes_x2, t_swipes_x2, conf_swipes_x2 = libf0.swipe_slim(x2, Fs=Fs, H=H, F_min=F_min, F_max=F_max, R=R, 118 | strength_threshold=strength_threshold) 119 | 120 | # exclude first an last frame due to artifacts at signal edges 121 | assert np.allclose(libf0.hz_to_cents(f0_swipes_x1[1:-1]), libf0.hz_to_cents(F_sine), rtol=0, atol=atol_sine) 122 | assert np.allclose(libf0.hz_to_cents(f0_swipes_x2[1:-1]), libf0.hz_to_cents(chirp_func(t_swipes_x2[1:-1])), rtol=0, 123 | atol=20) 124 | --------------------------------------------------------------------------------