├── .github └── workflows │ ├── test_conda.yml │ └── test_pip.yml ├── .gitignore ├── LICENCE ├── README.md ├── apt.txt ├── data_csv ├── Chopin_Op010-03-Measures1-8_MIDI.csv ├── Schubert_D911-01_HU33.csv ├── Schubert_D911-01_SC06.csv ├── Schubert_D911-03_HU33.csv └── Schubert_D911-03_SC06.csv ├── data_music ├── Chopin_Op010-03-Measures1-8_Igoshina.wav ├── Chopin_Op010-03-Measures1-8_MIDI.mid ├── Chopin_Op010-03-Measures1-8_Varsi.wav ├── Schubert_D911-01_HU33.wav ├── Schubert_D911-01_SC06.wav ├── Schubert_D911-03_HU33.wav └── Schubert_D911-03_SC06.wav ├── docs ├── .nojekyll ├── Makefile ├── build │ ├── doctrees │ │ ├── dtw.doctree │ │ ├── environment.pickle │ │ ├── feature │ │ │ ├── chroma.doctree │ │ │ ├── csv_tools.doctree │ │ │ ├── dlnco.doctree │ │ │ ├── index.doctree │ │ │ └── pitch_onset.doctree │ │ ├── genindex.doctree │ │ ├── index.doctree │ │ └── py-modindex.doctree │ └── html │ │ ├── .buildinfo │ │ ├── _modules │ │ ├── index.html │ │ └── synctoolbox │ │ │ ├── dtw │ │ │ ├── core.html │ │ │ └── mrmsdtw.html │ │ │ └── feature │ │ │ ├── chroma.html │ │ │ ├── csv_tools.html │ │ │ ├── dlnco.html │ │ │ └── pitch_onset.html │ │ ├── _sources │ │ ├── dtw.rst.txt │ │ ├── feature │ │ │ ├── chroma.rst.txt │ │ │ ├── csv_tools.rst.txt │ │ │ ├── dlnco.rst.txt │ │ │ ├── index.rst.txt │ │ │ └── pitch_onset.rst.txt │ │ ├── genindex.rst.txt │ │ ├── index.rst.txt │ │ └── py-modindex.rst.txt │ │ ├── _static │ │ ├── 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 │ │ ├── jquery.js │ │ ├── js │ │ │ ├── badge_only.js │ │ │ ├── html5shiv-printshiv.min.js │ │ │ ├── html5shiv.min.js │ │ │ └── theme.js │ │ ├── language_data.js │ │ ├── logo_synctoolbox.png │ │ ├── minus.png │ │ ├── plus.png │ │ ├── pygments.css │ │ ├── searchtools.js │ │ └── underscore.js │ │ ├── dtw.html │ │ ├── feature │ │ ├── chroma.html │ │ ├── csv_tools.html │ │ ├── dlnco.html │ │ ├── filterbank.html │ │ ├── index.html │ │ ├── novelty.html │ │ ├── pitch.html │ │ ├── pitch_onset.html │ │ └── utils.html │ │ ├── genindex.html │ │ ├── index.html │ │ ├── objects.inv │ │ ├── py-modindex.html │ │ ├── search.html │ │ └── searchindex.js ├── index.html ├── make.bat └── source │ ├── _static │ └── logo_synctoolbox.png │ ├── conf.py │ ├── dtw.rst │ ├── feature │ ├── chroma.rst │ ├── csv_tools.rst │ ├── dlnco.rst │ ├── filterbank.rst │ ├── index.rst │ ├── novelty.rst │ ├── pitch.rst │ ├── pitch_onset.rst │ └── utils.rst │ ├── genindex.rst │ ├── index.rst │ └── py-modindex.rst ├── environment.yml ├── ismir2021lbd_using_the_synctoolbox_for_an_experiment.ipynb ├── pyproject.toml ├── sync_audio_audio_full.ipynb ├── sync_audio_audio_simple.ipynb ├── sync_audio_score_full.ipynb ├── synctoolbox ├── __init__.py ├── dtw │ ├── __init__.py │ ├── anchor.py │ ├── core.py │ ├── cost.py │ ├── mrmsdtw.py │ ├── utils.py │ └── visualization.py └── feature │ ├── __init__.py │ ├── chroma.py │ ├── csv_tools.py │ ├── dlnco.py │ ├── filterbank.py │ ├── novelty.py │ ├── pitch.py │ ├── pitch_onset.py │ └── utils.py └── tests ├── data ├── f_CENS_1.npy ├── f_CENS_2.npy ├── f_DLNCO_1.npy ├── f_DLNCO_2.npy ├── f_chroma_1.npy ├── f_chroma_2.npy ├── f_chroma_quantized_1.npy ├── f_chroma_quantized_2.npy ├── f_filtfilt.npy ├── f_onset.npy ├── f_pitch_1.npy ├── f_pitch_2.npy ├── f_pitch_ann.npy ├── f_pitch_onset_1.pickle ├── f_pitch_onset_2.pickle ├── f_pitch_onset_ann.pickle ├── fb.pickle ├── peaks.npy ├── thresh.npy ├── wp.npy ├── wp_high_res.npy └── wp_simple.npy ├── test_dtw.py ├── test_features.py └── utils.py /.github/workflows/test_conda.yml: -------------------------------------------------------------------------------- 1 | name: Python Package using Conda 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | build-linux: 13 | runs-on: ubuntu-latest 14 | strategy: 15 | max-parallel: 5 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Set up Python 3.11 20 | uses: actions/setup-python@v2 21 | with: 22 | python-version: 3.11 23 | - name: Add conda to system path 24 | run: | 25 | # $CONDA is an environment variable pointing to the root of the miniconda directory 26 | echo $CONDA/bin >> $GITHUB_PATH 27 | - name: Install dependencies 28 | run: | 29 | conda install -c conda-forge libsndfile python=3.11 30 | python -m pip install .['test'] 31 | - name: Lint with flake8 32 | run: | 33 | conda install flake8 34 | # stop the build if there are Python syntax errors or undefined names 35 | flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics 36 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide 37 | flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics 38 | - name: Test with pytest 39 | run: | 40 | pytest 41 | -------------------------------------------------------------------------------- /.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: Python package 5 | 6 | on: 7 | push: 8 | branches: 9 | - master 10 | pull_request: 11 | branches: 12 | - master 13 | 14 | jobs: 15 | build: 16 | 17 | runs-on: ubuntu-latest 18 | strategy: 19 | fail-fast: false 20 | matrix: 21 | python-version: 22 | - 3.8 23 | - 3.9 24 | - '3.10' 25 | - 3.11 26 | 27 | steps: 28 | - uses: actions/checkout@v2 29 | - name: Set up Python ${{ matrix.python-version }} 30 | uses: actions/setup-python@v2 31 | with: 32 | python-version: ${{ matrix.python-version }} 33 | - name: Install libnsdfile 34 | run: | 35 | sudo apt update 36 | sudo apt install libsndfile1-dev libsndfile1 37 | - name: Install dependencies 38 | run: | 39 | python -m pip install --upgrade pip 40 | python -m pip install flake8 41 | python -m pip install .['test'] 42 | python --version 43 | pip --version 44 | python -m pip list 45 | - name: Lint with flake8 46 | run: | 47 | # stop the build if there are Python syntax errors or undefined names 48 | flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics 49 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide 50 | flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics 51 | - name: Test with pytest 52 | run: | 53 | pytest 54 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | .idea/ 3 | .ipynb_checkpoints/ 4 | build/ 5 | dist/ 6 | *.egg-info/ 7 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Meinard Müller, Yigitcan Özer, Michael Krause, Thomas Prätzlich, 4 | Jonathan Driedger, International Audio Laboratories Erlangen, Germany. 5 | We thank the German Research Foundation (DFG) for various research grants that 6 | allow us for conducting fundamental research in music processing. 7 | The International Audio Laboratories Erlangen are a joint institution of the 8 | Friedrich-Alexander-Universität Erlangen-Nürnberg (FAU) and Fraunhofer 9 | Institute for Integrated Circuits IIS. 10 | 11 | Permission is hereby granted, free of charge, to any person obtaining a copy of 12 | this software and associated documentation files (the "Software"), to deal in 13 | the Software without restriction, including without limitation the rights to 14 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 15 | the Software, and to permit persons to whom the Software is furnished to do so, 16 | subject to the following conditions: 17 | 18 | The above copyright notice and this permission notice shall be included in all 19 | copies or substantial portions of the Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 23 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 24 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 25 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 26 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Python Package using Conda](https://github.com/meinardmueller/synctoolbox/actions/workflows/test_conda.yml/badge.svg)](https://github.com/meinardmueller/synctoolbox/actions/workflows/test_conda.yml) 2 | [![Python package](https://github.com/meinardmueller/synctoolbox/actions/workflows/test_pip.yml/badge.svg)](https://github.com/meinardmueller/synctoolbox/actions/workflows/test_pip.yml) 3 | 4 | 5 | # Sync Toolbox 6 | 7 | This repository contains a Python package called Sync Toolbox, which provides open-source reference implementations for full-fledged music synchronization pipelines and yields state-of-the-art alignment results for a wide range of Western music. 8 | 9 | Using suitable feature representations and cost measures, the toolbox's core technology is based on dynamic time warping (DTW), which brings the feature sequences into temporal correspondence. To account for efficiency, robustness and, accuracy, our toolbox integrates and combines techniques such as [multiscale DTW (MsDTW)](https://www.audiolabs-erlangen.de/fau/professor/mueller/publications/2006_MuellerMattesKurth_MultiscaleAudioSynchronization_ISMIR.pdf), [memory-restricted MsDTW (MrMsDTW)](https://www.audiolabs-erlangen.de/fau/professor/mueller/publications/2016_PraetzlichDriedgerMueller_MrMsDTW_ICASSP.pdf), and [high-resolution music synchronization](https://www.audiolabs-erlangen.de/fau/professor/mueller/publications/2009_EwertMuellerGrosche_HighResAudioSync_ICASSP.pdf). 10 | 11 | If you use the Sync Toolbox in your research, please consider the following references. 12 | 13 | ## References 14 | 15 | Meinard Müller, Yigitcan Özer, Michael Krause, Thomas Prätzlich, and Jonathan Driedger. [Sync Toolbox: A Python Package for Efficient, Robust, and Accurate Music Synchronization.](https://joss.theoj.org/papers/10.21105/joss.03434) Journal of Open Source Software (JOSS), 6(64), 2021. 16 | 17 | Meinard Müller, Henning Mattes, and Frank Kurth. 18 | [An Efficient Multiscale Approach to Audio Synchronization](https://www.audiolabs-erlangen.de/fau/professor/mueller/publications/2006_MuellerMattesKurth_MultiscaleAudioSynchronization_ISMIR.pdf). 19 | In Proceedings of the International Society for Music Information Retrieval Conference (ISMIR): 192–197, 2006. 20 | 21 | Sebastian Ewert, Meinard Müller, and Peter Grosche. 22 | [High Resolution Audio Synchronization Using Chroma Onset Features](https://www.audiolabs-erlangen.de/fau/professor/mueller/publications/2009_EwertMuellerGrosche_HighResAudioSync_ICASSP.pdf). 23 | In Proceedings of IEEE International Conference on Acoustics, Speech, and Signal Processing (ICASSP): 1869–1872, 2009. 24 | 25 | Thomas Prätzlich, Jonathan Driedger, and Meinard Müller 26 | [Memory-Restricted Multiscale Dynamic Time Warping](https://www.audiolabs-erlangen.de/fau/professor/mueller/publications/2016_PraetzlichDriedgerMueller_MrMsDTW_ICASSP.pdf). 27 | In Proceedings of the IEEE International Conference on Acoustics, Speech, and Signal Processing (ICASSP): 569–573, 2016. 28 | 29 | ## Installing 30 | 31 | If you just want to try our example notebooks, you can run them using Binder directly in your browser: [![Binder](https://mybinder.org/badge_logo.svg)](https://notebooks.gesis.org/binder/v2/gh/meinardmueller/synctoolbox/master) 32 | 33 | To install the Sync Toolbox locally, you can use the Python package manager pip: 34 | 35 | ``` 36 | pip install synctoolbox 37 | ``` 38 | 39 | We recommend to do this inside a conda or virtual environment (requiring at least Python 3.7). As an alternative, you may also create the environment ``synctoolbox`` as provided by the file ``environment.yml`` (which includes the synctoolbox package as well as the jupyter package to run the demo files): 40 | 41 | ``` 42 | conda env create -f environment.yml 43 | ``` 44 | 45 | **Note:** On some systems, you may see errors related with ``soundfile`` when calling some functions or executing our example notebooks. ``soundfile`` is a dependency of ``librosa``, which is used by the Sync Toolbox. In case of errors, you may have to install libsndfile using your package manager, e.g., ``sudo apt install libsndfile1``. Alternatively, you may create a conda environment, install ``librosa`` using conda and then install the Sync Toolbox with the pip command from above. See [here](https://github.com/librosa/librosa#hints-for-the-installation) for further information if you are experiencing these issues. 46 | 47 | 48 | If you want to run the example notebooks locally, you **must** first install the Sync Toolbox to resolve all dependencies. Then, you can clone this repository using 49 | 50 | ``` 51 | git clone https://github.com/meinardmueller/synctoolbox.git 52 | ``` 53 | install Jupyter using 54 | 55 | ``` 56 | pip install jupyter 57 | ``` 58 | 59 | and then start the notebook server via 60 | 61 | ``` 62 | jupyter notebook 63 | ``` 64 | 65 | Finally, HTML exports of the example notebooks are provided under "[Releases](https://github.com/meinardmueller/synctoolbox/releases)". 66 | 67 | 68 | ## Usage 69 | 70 | Fully worked examples for using the sync toolbox are provided in the accompanying Jupyter notebooks. In ``sync_audio_audio_simple.ipynb``, we show how to use the toolbox to synchronize two recordings of the same piece of music using standard chroma features. We also compare runtimes for standard DTW and MrMsDTW. In ``sync_audio_audio_full.ipynb``, we expand this example and demonstrate how to build a full synchronization pipeline that yields state-of-the-art results. Finally, ``sync_audio_score_full.ipynb`` shows a similar pipeline for synchronizing a music recording with the corresponding score. 71 | 72 | There is also an API documentation for the Sync Toolbox: 73 | 74 | https://meinardmueller.github.io/synctoolbox 75 | 76 | ## Contributing 77 | 78 | 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. 79 | 80 | ## Tests 81 | 82 | We provide automated tests for each feature and different variants of MrMsDTW. These ensure that the outputs match the ground truth matrices provided in the **tests/data** folder. 83 | 84 | To execute the test script, you will need to install extra requirements for testing: 85 | 86 | ``` 87 | pip install 'synctoolbox[tests]' 88 | pytest tests 89 | ``` 90 | 91 | ## Licence 92 | 93 | The code for this toolbox is published under an MIT licence. This does not apply to the data files. Schubert songs are taken from the [Schubert Winterreise Dataset](https://zenodo.org/record/4122060). The Chopin prelude example files are taken from the [FMP notebooks](https://www.audiolabs-erlangen.de/resources/MIR/FMP/C0/C0.html). 94 | 95 | ## Acknowledgements 96 | 97 | The synctoolbox package builds on results, material, and insights that have been obtained in close collaboration with different people. We would like to express our gratitude to former and current students, collaborators, and colleagues who have influenced and supported us in creating this package, including Vlora Arifi-Müller, Michael Clausen, Sebastian Ewert, Christian Fremerey, and Frank Kurth. The main authors of Sync Toolbox are associated with the International Audio Laboratories Erlangen, which are a joint institution of the Friedrich-Alexander-Universität Erlangen-Nürnberg (FAU) and Fraunhofer Institute for Integrated Circuits IIS. We also thank the German Research Foundation (DFG) for various research grants that allowed us for conducting fundamental research in music processing (in particular, MU 2686/7-2, DFG-MU 2686/14-1). 98 | -------------------------------------------------------------------------------- /apt.txt: -------------------------------------------------------------------------------- 1 | libsndfile1 2 | rubberband-cli 3 | -------------------------------------------------------------------------------- /data_csv/Chopin_Op010-03-Measures1-8_MIDI.csv: -------------------------------------------------------------------------------- 1 | Start;Duration;Pitch;Velocity;Instrument 2 | 0.4;0.575;59;38;piano 3 | 1.4200000000000002;1.1199999999999999;56;21;piano 4 | 1.4200000000000002;0.7283333333333328;64;48;piano 5 | 1.4433333333333334;1.1483333333333334;40;34;piano 6 | 2.105;0.5016666666666669;47;19;piano 7 | 2.111666666666667;0.8174999999999999;59;26;piano 8 | 2.5333333333333337;0.43500000000000005;63;46;piano 9 | 2.543333333333333;0.035000000000000586;56;25;piano 10 | 2.9283333333333337;0.3799999999999999;64;63;piano 11 | 2.9325;0.030833333333333268;59;34;piano 12 | 2.935;0.476666666666667;47;25;piano 13 | 3.33;0.41333333333333355;66;52;piano 14 | 3.3350000000000004;0.6483333333333325;63;29;piano 15 | 3.345;0.8383333333333334;57;31;piano 16 | 3.373333333333333;1.1900000000000004;35;33;piano 17 | 3.766666666666667;0.8500000000000001;59;26;piano 18 | 3.775;0.8566666666666669;47;16;piano 19 | 4.1866666666666665;0.04166666666666696;57;21;piano 20 | 4.62;0.15833333333333321;59;20;piano 21 | 4.635000000000002;0.35499999999999865;47;14;piano 22 | 5.08;0.26000000000000156;57;23;piano 23 | 5.111666666666667;0.7183333333333328;35;20;piano 24 | 5.628333333333334;0.23666666666666636;68;60;piano 25 | 5.6416666666666675;0.5149999999999997;59;21;piano 26 | 5.6433333333333335;0.6816666666666666;47;17;piano 27 | 6.053333333333334;0.4616666666666678;68;61;piano 28 | 6.075;0.5666666666666682;63;41;piano 29 | 6.083333333333334;0.5899999999999999;57;17;piano 30 | 6.45;0.3033333333333337;66;60;piano 31 | 6.468333333333334;0.3550000000000004;59;33;piano 32 | 6.485;0.5200000000000005;47;24;piano 33 | 6.94;0.5099999999999998;68;54;piano 34 | 6.9433333333333325;0.8450000000000015;56;37;piano 35 | 6.945;0.9950000000000001;64;27;piano 36 | 6.951666666666667;1.2333333333333334;40;36;piano 37 | 7.5233333333333325;0.7600000000000007;47;22;piano 38 | 7.528333333333333;0.7800000000000002;59;33;piano 39 | 7.925000000000002;0.6933333333333325;56;26;piano 40 | 8.311666666666667;0.25333333333333385;59;31;piano 41 | 8.316666666666666;0.5400000000000009;47;13;piano 42 | 8.68;0.33000000000000007;56;26;piano 43 | 8.71;0.6666666666666661;40;38;piano 44 | 9.213333333333336;0.21499999999999986;69;54;piano 45 | 9.243333333333334;0.47666666666666657;47;23;piano 46 | 9.258333333333336;0.6166666666666636;64;19;piano 47 | 9.596666666666668;0.4066666666666663;69;69;piano 48 | 9.645;0.48666666666666814;59;23;piano 49 | 9.971666666666668;0.293333333333333;68;78;piano 50 | 10.006666666666668;0.2616666666666667;64;38;piano 51 | 10.016666666666667;0.47000000000000064;47;23;piano 52 | 10.45;0.48166666666666735;73;74;piano 53 | 10.476666666666668;0.6666666666666661;63;39;piano 54 | 10.483333333333334;0.7466666666666661;35;26;piano 55 | 10.965;0.42333333333333556;69;20;piano 56 | 10.973333333333334;0.5499999999999972;47;18;piano 57 | 11.44;0.6100000000000012;59;23;piano 58 | 11.815;0.451666666666668;71;73;piano 59 | 11.845;0.42666666666666764;63;29;piano 60 | 11.851666666666668;0.553333333333331;47;24;piano 61 | 12.206666666666667;0.43333333333333357;69;58;piano 62 | 12.241666666666667;0.41000000000000014;59;22;piano 63 | 12.243333333333334;0.5466666666666651;40;40;piano 64 | 12.605;0.41000000000000014;68;54;piano 65 | 12.625;0.5450000000000017;64;38;piano 66 | 12.63;0.658333333333335;47;29;piano 67 | 13.0;0.41499999999999915;63;54;piano 68 | 13.010000000000002;0.6016666666666666;56;30;piano 69 | 13.35;0.2850000000000019;64;61;piano 70 | 13.361666666666668;0.4066666666666663;59;33;piano 71 | 13.385000000000002;0.38333333333333286;47;26;piano 72 | 13.72;0.3966666666666665;61;32;piano 73 | 13.72;0.35499999999999865;66;39;piano 74 | 13.731666666666667;0.7200000000000006;57;29;piano 75 | 13.738333333333335;0.7483333333333331;35;29;piano 76 | 14.101666666666668;0.4383333333333308;63;38;piano 77 | 14.108333333333334;0.6433333333333326;47;21;piano 78 | 14.1225;0.7374999999999989;59;26;piano 79 | 14.463333333333335;0.559999999999997;61;33;piano 80 | 14.48;0.6716666666666669;57;24;piano 81 | 14.856666666666667;0.34333333333333194;63;25;piano 82 | 14.859166666666667;0.4291666666666689;47;21;piano 83 | 14.87;0.6416666666666675;59;21;piano 84 | 15.31;0.2350000000000012;61;24;piano 85 | 15.318333333333335;0.27833333333333243;57;19;piano 86 | 15.343333333333335;0.7416666666666654;35;23;piano 87 | 15.86;0.245000000000001;68;51;piano 88 | 15.883333333333335;0.22499999999999964;47;19;piano 89 | 15.886666666666667;0.5566666666666684;63;33;piano 90 | 15.891666666666667;0.6333333333333346;59;8;piano 91 | 16.291666666666668;0.36666666666666714;68;61;piano 92 | 16.32;0.44333333333333513;57;21;piano 93 | 16.32166666666667;0.6383333333333319;61;28;piano 94 | 16.698333333333334;0.29666666666666686;66;44;piano 95 | 16.725;0.2766666666666673;59;20;piano 96 | 16.735;0.5183333333333344;63;24;piano 97 | 16.758333333333333;0.6766666666666694;47;17;piano 98 | 17.26666666666667;0.41999999999999815;56;20;piano 99 | 17.274166666666666;0.720833333333335;64;42;piano 100 | 17.28;0.7349999999999994;40;18;piano 101 | 17.833333333333336;0.40166666666666373;59;14;piano 102 | 17.835833333333333;0.575833333333339;47;11;piano 103 | 18.225;0.5124999999999993;56;22;piano 104 | 18.631666666666668;0.168333333333333;47;19;piano 105 | 18.633333333333333;0.634999999999998;59;15;piano 106 | 19.245;0.42000000000000526;68;32;piano 107 | 19.26833333333333;0.44833333333334124;62;16;piano 108 | 19.28;0.5899999999999999;40;32;piano 109 | 19.68;0.4066666666666734;69;47;piano 110 | 19.716666666666672;0.43333333333332646;64;31;piano 111 | 19.718333333333334;0.490000000000002;52;31;piano 112 | 20.028333333333336;0.37999999999999545;66;57;piano 113 | 20.061666666666667;0.38166666666666416;62;32;piano 114 | 20.345;0.3583333333333343;68;69;piano 115 | 20.381666666666668;0.3350000000000044;52;38;piano 116 | 20.39;0.4733333333333327;64;26;piano 117 | 20.676666666666673;0.3583333333333272;69;63;piano 118 | 20.688333333333336;0.40166666666666373;61;38;piano 119 | 20.703333333333333;0.495000000000001;45;34;piano 120 | 21.03333333333333;0.3616666666666717;71;78;piano 121 | 21.071666666666673;0.351666666666663;52;44;piano 122 | 21.073333333333334;0.4799999999999969;64;24;piano 123 | 21.356666666666673;0.35666666666665847;68;78;piano 124 | 21.383333333333336;0.3549999999999933;61;27;piano 125 | 21.68166666666667;0.22000000000000242;69;73;piano 126 | 21.70333333333333;0.21166666666667666;64;48;piano 127 | 21.70583333333333;0.442499999999999;52;45;piano 128 | 22.07333333333333;0.39166666666666927;73;66;piano 129 | 22.095;0.6516666666666708;61;38;piano 130 | 22.11333333333333;0.6533333333333395;45;37;piano 131 | 22.565;0.47166666666666757;54;31;piano 132 | 22.57333333333333;0.7366666666666681;64;42;piano 133 | 22.93666666666667;0.5700000000000003;66;62;piano 134 | 22.958333333333336;0.668333333333333;61;38;piano 135 | 23.313333333333336;0.654999999999994;54;29;piano 136 | 23.313333333333336;0.006666666666664156;64;41;piano 137 | 23.64333333333333;0.600000000000005;47;34;piano 138 | 23.645833333333336;0.6141666666666659;59;40;piano 139 | 23.965833333333336;0.3308333333333344;64;51;piano 140 | 23.975;0.5233333333333299;54;34;piano 141 | 24.23833333333333;0.38166666666667126;70;49;piano 142 | 24.270000000000003;0.42333333333332845;68;67;piano 143 | 24.290000000000006;0.6316666666666642;59;49;piano 144 | 24.625;0.36000000000000654;66;65;piano 145 | 24.64833333333333;0.39833333333334053;54;40;piano 146 | 24.665000000000006;0.6933333333333245;64;39;piano 147 | 24.95166666666667;0.6649999999999991;49;43;piano 148 | 24.96166666666667;0.7699999999999996;58;33;piano 149 | 25.35;0.41833333333332945;54;29;piano 150 | 25.361666666666668;0.08666666666666245;64;44;piano 151 | 25.65833333333333;0.3733333333333384;70;44;piano 152 | 25.71833333333333;0.3916666666666764;68;61;piano 153 | 25.735000000000007;0.01333333333332476;58;28;piano 154 | 26.07166666666667;0.28500000000000014;66;69;piano 155 | 26.09666666666667;0.5033333333333303;54;38;piano 156 | 26.105;0.5750000000000028;64;46;piano 157 | 26.59166666666667;0.3233333333333377;71;67;piano 158 | 26.60333333333333;0.7983333333333391;57;33;piano 159 | 26.60333333333333;0.6683333333333366;63;41;piano 160 | 26.63666666666667;1.086666666666666;47;28;piano 161 | 27.130000000000006;0.8699999999999974;59;38;piano 162 | 27.138333333333332;0.8700000000000045;54;24;piano 163 | 27.543333333333337;0.6266666666666652;57;27;piano 164 | 28.00333333333333;0.08166666666667055;59;24;piano 165 | 28.01166666666667;0.11333333333333329;54;14;piano 166 | -------------------------------------------------------------------------------- /data_csv/Schubert_D911-01_HU33.csv: -------------------------------------------------------------------------------- 1 | start;measure 2 | 1.258390023;1 3 | 3.291836735;2 4 | 5.338775510;3 5 | 7.297959184;4 6 | 9.389092971;5 7 | 11.245532880;6 8 | 13.392562358;7 9 | 15.787755102;8 10 | 17.763265306;9 11 | 19.849569161;10 12 | 21.953492063;11 13 | 24.026077098;12 14 | 26.032222222;13 15 | 28.052426304;14 16 | 30.142721088;15 17 | 32.253287982;16 18 | 34.313809524;17 19 | 36.418321995;18 20 | 38.416326531;19 21 | 40.561836735;20 22 | 42.582766440;21 23 | 44.810022676;22 24 | 47.020408163;23 25 | 48.999115646;24 26 | 50.995079365;25 27 | 53.172970522;26 28 | 55.245238095;27 29 | 57.321043084;28 30 | 59.373401361;29 31 | 61.474195011;30 32 | 63.632063492;31 33 | 65.763265306;32 34 | 68.087165533;33 35 | 70.099160998;34 36 | 72.175260771;35 37 | 74.280204082;36 38 | 76.424875283;37 39 | 78.448979592;38 40 | 80.625918367;39 41 | 83.021111111;40 42 | 84.996621315;41 43 | 87.082925170;42 44 | 89.186848072;43 45 | 91.259433107;44 46 | 93.265578231;45 47 | 95.285782313;46 48 | 97.376077097;47 49 | 99.486643991;48 50 | 101.547165533;49 51 | 103.651678004;50 52 | 105.649682540;51 53 | 107.795192744;52 54 | 109.816122449;53 55 | 112.043378685;54 56 | 114.253764172;55 57 | 116.232471655;56 58 | 118.228435374;57 59 | 120.406326531;58 60 | 122.478594104;59 61 | 124.554399093;60 62 | 126.606757370;61 63 | 128.707551020;62 64 | 130.865419501;63 65 | 132.996621315;64 66 | 135.320521542;65 67 | 137.332517007;66 68 | 139.408616780;67 69 | 141.513560091;68 70 | 143.658231292;69 71 | 145.682335601;70 72 | 147.859274376;71 73 | 150.155804989;72 74 | 152.294580499;73 75 | 154.471746032;74 76 | 156.499319728;75 77 | 158.710907029;76 78 | 160.750680272;77 79 | 162.861814059;78 80 | 164.896938775;79 81 | 167.063083900;80 82 | 169.099841270;81 83 | 171.297165533;82 84 | 173.364852608;83 85 | 175.442653061;84 86 | 177.543854875;85 87 | 179.804784580;86 88 | 181.968367347;87 89 | 183.990816326;88 90 | 185.901337868;89 91 | 188.066009070;90 92 | 190.155804989;91 93 | 192.212925170;92 94 | 194.397528345;93 95 | 196.590566893;94 96 | 198.763197279;95 97 | 200.952403628;96 98 | 203.558163265;97 99 | 205.755510204;98 100 | 207.885056689;99 101 | 210.153015873;100 102 | 212.408866213;101 103 | 214.498662131;102 104 | 217.159886621;103 105 | 220.114988662;104 106 | 222.743560091;105 107 | 225.323492063;106 108 | 227.895895692;107 109 | 230.457346939;108 110 | 232.807913832;109 111 | 235.361088435;110 112 | 237.763968254;111 113 | 240.257233560;112 114 | 242.678253968;113 115 | 245.253446712;114 116 | 247.690498866;115 117 | 249.990793651;116 118 | 252.294580499;117 119 | 254.792539682;118 120 | 257.600702948;119 121 | 259.858911565;120 122 | 262.018526077;121 123 | 264.422811791;122 124 | 266.727233560;123 125 | 268.932879819;124 126 | 271.494580499;125 127 | 273.824920635;126 128 | 276.080340136;127 129 | 278.455328798;128 130 | 281.078253968;129 131 | 284.088344671;130 132 | 288.229274376;131 133 | 290.552675737;132 134 | 292.784376417;133 135 | 295.137551020;134 136 | 297.670226757;135 137 | 300.605079365;136 138 | 304.033356009;137 139 | 305.682335601;137.500 140 | -------------------------------------------------------------------------------- /data_csv/Schubert_D911-01_SC06.csv: -------------------------------------------------------------------------------- 1 | start;measure 2 | 0.238662132;1 3 | 2.489886621;2 4 | 4.850453515;3 5 | 7.216938776;4 6 | 9.641746032;5 7 | 11.815850340;6 8 | 14.150340136;7 9 | 16.395487528;8 10 | 18.538639456;9 11 | 20.781337868;10 12 | 23.090770975;11 13 | 25.354172336;12 14 | 27.529115646;13 15 | 29.782040816;14 16 | 31.949501134;15 17 | 34.355283447;16 18 | 36.512199546;17 19 | 38.866734694;18 20 | 41.150725624;19 21 | 43.398095238;20 22 | 45.666417234;21 23 | 48.042086168;22 24 | 50.495986395;23 25 | 53.007687075;24 26 | 55.447913832;25 27 | 57.922176871;26 28 | 60.291587302;27 29 | 62.600997732;28 30 | 64.778888889;29 31 | 67.105668934;30 32 | 69.544217687;31 33 | 71.958639456;32 34 | 74.071655329;33 35 | 76.228412698;34 36 | 78.620136054;35 37 | 80.933151927;36 38 | 83.405396825;37 39 | 85.604104308;38 40 | 87.849206349;39 41 | 90.198956916;40 42 | 92.348888889;41 43 | 94.646213152;42 44 | 96.904557823;43 45 | 99.140272109;44 46 | 101.345215420;45 47 | 103.613446712;46 48 | 105.912743764;47 49 | 108.348503401;48 50 | 110.676213152;49 51 | 112.939160998;50 52 | 115.191655329;51 53 | 117.411700680;52 54 | 119.693605442;53 55 | 121.962811791;54 56 | 124.373083900;55 57 | 126.778548753;56 58 | 129.218412698;57 59 | 131.610702948;58 60 | 133.914149660;59 61 | 136.185034014;60 62 | 138.389183673;61 63 | 140.561995465;62 64 | 142.814739229;63 65 | 145.099954649;64 66 | 147.245102041;65 67 | 149.408798186;66 68 | 151.614693878;67 69 | 153.838730159;68 70 | 156.258684807;69 71 | 158.348480726;70 72 | 160.555283447;71 73 | 162.798911565;72 74 | 164.967619048;73 75 | 167.190680272;74 76 | 169.347981859;75 77 | 171.532108844;76 78 | 173.738480726;77 79 | 175.934557823;78 80 | 178.117210884;79 81 | 180.522675737;80 82 | 182.682993197;81 83 | 185.053356009;82 84 | 187.291451247;83 85 | 189.561768707;84 86 | 191.868140590;85 87 | 194.223310658;86 88 | 196.579365079;87 89 | 199.002698413;88 90 | 201.564058957;89 91 | 203.952471655;90 92 | 206.296145125;91 93 | 208.631292517;92 94 | 210.857891156;93 95 | 213.216507937;94 96 | 215.560476190;95 97 | 217.923015873;96 98 | 220.237278912;97 99 | 222.533015873;98 100 | 224.863628118;99 101 | 227.227346939;100 102 | 229.721972789;101 103 | 232.016099773;102 104 | 234.709886621;103 105 | 237.399909297;104 106 | 239.741179138;105 107 | 242.218956916;106 108 | 244.554013605;107 109 | 246.985419501;108 110 | 249.254784580;109 111 | 251.751473923;110 112 | 254.177233560;111 113 | 256.638548753;112 114 | 259.065034014;113 115 | 261.547482993;114 116 | 264.025986395;115 117 | 266.517823129;116 118 | 268.842040816;117 119 | 271.719909297;118 120 | 274.464331066;119 121 | 277.002448980;120 122 | 279.510204082;121 123 | 282.054671202;122 124 | 284.498775510;123 125 | 286.948707483;124 126 | 289.597551020;125 127 | 292.176780045;126 128 | 294.620022676;127 129 | 297.134149660;128 130 | 299.665124717;129 131 | 302.424920635;130 132 | 305.783582766;131 133 | 308.347210884;132 134 | 310.773560091;133 135 | 313.181814059;134 136 | 315.656235828;135 137 | 318.142040816;136 138 | 322.060770975;137 139 | 324.905215420;137.500 140 | -------------------------------------------------------------------------------- /data_csv/Schubert_D911-03_HU33.csv: -------------------------------------------------------------------------------- 1 | start;measure 2 | 0.243809524;0.750 3 | 0.993764172;1 4 | 3.524489796;2 5 | 5.988843537;3 6 | 8.560430839;4 7 | 11.163219955;5 8 | 13.677188209;6 9 | 16.168866213;7 10 | 18.939909297;8 11 | 21.745487528;9 12 | 24.427392290;10 13 | 27.155215420;11 14 | 29.732947846;12 15 | 32.209569161;13 16 | 34.646530612;14 17 | 37.251678005;15 18 | 39.764557823;16 19 | 42.658027211;17 20 | 45.164240363;18 21 | 47.631156463;19 22 | 50.031473923;20 23 | 52.741201814;21 24 | 55.603560091;22 25 | 58.503696145;23 26 | 61.603174603;24 27 | 64.515759637;25 28 | 67.423696145;26 29 | 70.171292517;27 30 | 73.795555556;28 31 | 76.578956916;29 32 | 79.560476190;30 33 | 82.298820862;31 34 | 84.865147392;32 35 | 87.401927438;33 36 | 89.837142857;34 37 | 92.259750567;35 38 | 94.686235828;36 39 | 97.247823129;37 40 | 99.684172336;38 41 | 102.298435374;39 42 | 104.778435374;40 43 | 107.153378685;41 44 | 109.460884354;42 45 | 111.814126984;43 46 | 114.182199546;44 47 | 116.518616780;45 48 | 119.025578231;46 49 | 121.612290249;47 50 | 124.308049887;48 51 | 127.549750567;49 52 | 129.849070295;50 53 | 132.287233560;51 54 | 134.677460317;52 55 | 137.141065760;53 56 | 139.717505669;54 57 | 142.885918367;55 58 | 144.817052154;55.500 59 | -------------------------------------------------------------------------------- /data_csv/Schubert_D911-03_SC06.csv: -------------------------------------------------------------------------------- 1 | start;measure 2 | 0.657142857;0.750 3 | 1.261224490;1 4 | 3.811995465;2 5 | 6.478208617;3 6 | 8.902448980;4 7 | 11.442630385;5 8 | 13.918027211;6 9 | 16.367346939;7 10 | 19.041972789;8 11 | 21.505918367;9 12 | 24.183877551;10 13 | 26.639727891;11 14 | 29.238730159;12 15 | 31.820408163;13 16 | 34.384172336;14 17 | 36.804965986;15 18 | 39.299637188;16 19 | 41.861814059;17 20 | 44.206099773;18 21 | 46.573197279;19 22 | 48.831360544;20 23 | 51.138775510;21 24 | 53.457142857;22 25 | 55.865306122;23 26 | 58.434920635;24 27 | 60.853038549;25 28 | 63.231043084;26 29 | 65.575510204;27 30 | 68.260861678;28 31 | 70.538775510;29 32 | 73.001224490;30 33 | 75.459365079;31 34 | 77.864625850;32 35 | 80.251609977;33 36 | 82.706598639;34 37 | 84.950113379;35 38 | 87.241882086;36 39 | 89.579591837;37 40 | 92.055782313;38 41 | 94.629183673;39 42 | 97.412426304;40 43 | 99.912448980;41 44 | 102.319365079;42 45 | 104.697687075;43 46 | 107.237800454;44 47 | 109.576643991;45 48 | 111.832653061;46 49 | 114.229682540;47 50 | 116.653061224;48 51 | 119.360204082;49 52 | 121.739160998;50 53 | 124.376077098;51 54 | 126.755102041;52 55 | 129.375510204;53 56 | 132.086190476;54 57 | 135.142857143;55 58 | 136.930612245;55.500 59 | -------------------------------------------------------------------------------- /data_music/Chopin_Op010-03-Measures1-8_Igoshina.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meinardmueller/synctoolbox/cf4c471f232d510849b77491720dd19f8441340c/data_music/Chopin_Op010-03-Measures1-8_Igoshina.wav -------------------------------------------------------------------------------- /data_music/Chopin_Op010-03-Measures1-8_MIDI.mid: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meinardmueller/synctoolbox/cf4c471f232d510849b77491720dd19f8441340c/data_music/Chopin_Op010-03-Measures1-8_MIDI.mid -------------------------------------------------------------------------------- /data_music/Chopin_Op010-03-Measures1-8_Varsi.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meinardmueller/synctoolbox/cf4c471f232d510849b77491720dd19f8441340c/data_music/Chopin_Op010-03-Measures1-8_Varsi.wav -------------------------------------------------------------------------------- /data_music/Schubert_D911-01_HU33.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meinardmueller/synctoolbox/cf4c471f232d510849b77491720dd19f8441340c/data_music/Schubert_D911-01_HU33.wav -------------------------------------------------------------------------------- /data_music/Schubert_D911-01_SC06.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meinardmueller/synctoolbox/cf4c471f232d510849b77491720dd19f8441340c/data_music/Schubert_D911-01_SC06.wav -------------------------------------------------------------------------------- /data_music/Schubert_D911-03_HU33.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meinardmueller/synctoolbox/cf4c471f232d510849b77491720dd19f8441340c/data_music/Schubert_D911-03_HU33.wav -------------------------------------------------------------------------------- /data_music/Schubert_D911-03_SC06.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meinardmueller/synctoolbox/cf4c471f232d510849b77491720dd19f8441340c/data_music/Schubert_D911-03_SC06.wav -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meinardmueller/synctoolbox/cf4c471f232d510849b77491720dd19f8441340c/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/doctrees/dtw.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meinardmueller/synctoolbox/cf4c471f232d510849b77491720dd19f8441340c/docs/build/doctrees/dtw.doctree -------------------------------------------------------------------------------- /docs/build/doctrees/environment.pickle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meinardmueller/synctoolbox/cf4c471f232d510849b77491720dd19f8441340c/docs/build/doctrees/environment.pickle -------------------------------------------------------------------------------- /docs/build/doctrees/feature/chroma.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meinardmueller/synctoolbox/cf4c471f232d510849b77491720dd19f8441340c/docs/build/doctrees/feature/chroma.doctree -------------------------------------------------------------------------------- /docs/build/doctrees/feature/csv_tools.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meinardmueller/synctoolbox/cf4c471f232d510849b77491720dd19f8441340c/docs/build/doctrees/feature/csv_tools.doctree -------------------------------------------------------------------------------- /docs/build/doctrees/feature/dlnco.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meinardmueller/synctoolbox/cf4c471f232d510849b77491720dd19f8441340c/docs/build/doctrees/feature/dlnco.doctree -------------------------------------------------------------------------------- /docs/build/doctrees/feature/index.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meinardmueller/synctoolbox/cf4c471f232d510849b77491720dd19f8441340c/docs/build/doctrees/feature/index.doctree -------------------------------------------------------------------------------- /docs/build/doctrees/feature/pitch_onset.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meinardmueller/synctoolbox/cf4c471f232d510849b77491720dd19f8441340c/docs/build/doctrees/feature/pitch_onset.doctree -------------------------------------------------------------------------------- /docs/build/doctrees/genindex.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meinardmueller/synctoolbox/cf4c471f232d510849b77491720dd19f8441340c/docs/build/doctrees/genindex.doctree -------------------------------------------------------------------------------- /docs/build/doctrees/index.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meinardmueller/synctoolbox/cf4c471f232d510849b77491720dd19f8441340c/docs/build/doctrees/index.doctree -------------------------------------------------------------------------------- /docs/build/doctrees/py-modindex.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meinardmueller/synctoolbox/cf4c471f232d510849b77491720dd19f8441340c/docs/build/doctrees/py-modindex.doctree -------------------------------------------------------------------------------- /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: 4e87c9bba5dd46298c4e0b72e6a34be2 4 | tags: 645f666f9bcd5a90fca523b33c5a78b7 5 | -------------------------------------------------------------------------------- /docs/build/html/_modules/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Overview: module code — SyncToolbox 1.0.0 documentation 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 53 | 54 |
58 | 59 |
60 |
61 |
62 |
    63 |
  • »
  • 64 |
  • Overview: module code
  • 65 |
  • 66 |
  • 67 |
68 |
69 |
70 | 88 | 102 |
103 |
104 |
105 |
106 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /docs/build/html/_sources/dtw.rst.txt: -------------------------------------------------------------------------------- 1 | Dynamic Time Warping (synctoolbox.dtw) 2 | ====================================== 3 | 4 | .. automodule:: synctoolbox.dtw.core 5 | :members: 6 | :undoc-members: 7 | 8 | .. automodule:: synctoolbox.dtw.mrmsdtw 9 | :members: 10 | :undoc-members: 11 | 12 | 13 | -------------------------------------------------------------------------------- /docs/build/html/_sources/feature/chroma.rst.txt: -------------------------------------------------------------------------------- 1 | Chroma Features (synctoolbox.feature.chroma) 2 | ============================================ 3 | 4 | .. automodule:: synctoolbox.feature.chroma 5 | :members: 6 | :undoc-members: -------------------------------------------------------------------------------- /docs/build/html/_sources/feature/csv_tools.rst.txt: -------------------------------------------------------------------------------- 1 | CSV Tools (synctoolbox.feature.csv_tools) 2 | ========================================= 3 | 4 | .. automodule:: synctoolbox.feature.csv_tools 5 | :members: 6 | :undoc-members: -------------------------------------------------------------------------------- /docs/build/html/_sources/feature/dlnco.rst.txt: -------------------------------------------------------------------------------- 1 | DLNCO Features (synctoolbox.feature.dlnco) 2 | ========================================== 3 | 4 | .. automodule:: synctoolbox.feature.dlnco 5 | :members: 6 | :undoc-members: -------------------------------------------------------------------------------- /docs/build/html/_sources/feature/index.rst.txt: -------------------------------------------------------------------------------- 1 | Feature Extraction (synctoolbox.feature) 2 | ======================================== 3 | 4 | .. toctree:: 5 | :caption: Features 6 | :maxdepth: 1 7 | :hidden: 8 | 9 | chroma 10 | filterbank 11 | pitch 12 | pitch_onset 13 | dlnco 14 | novelty 15 | csv_tools 16 | utils -------------------------------------------------------------------------------- /docs/build/html/_sources/feature/pitch_onset.rst.txt: -------------------------------------------------------------------------------- 1 | Pitch Onset Features (synctoolbox.feature.pitch_onset) 2 | ====================================================== 3 | 4 | .. automodule:: synctoolbox.feature.pitch_onset 5 | :members: 6 | :undoc-members: -------------------------------------------------------------------------------- /docs/build/html/_sources/genindex.rst.txt: -------------------------------------------------------------------------------- 1 | Index 2 | ===== -------------------------------------------------------------------------------- /docs/build/html/_sources/index.rst.txt: -------------------------------------------------------------------------------- 1 | .. SyncToolbox documentation master file, created by 2 | sphinx-quickstart on Tue May 4 08:49:22 2021. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | 7 | Sync Toolbox: A Python Package for Efficient, Robust, and Accurate Music Synchronization 8 | ======================================================================================== 9 | **Sync Toolbox** is a Python package, which comprises all components of a music synchronization 10 | pipeline that is robust, efficient, and accurate. 11 | 12 | The toolbox's core technology is based on dynamic time warping (DTW). Using suitable feature representations 13 | and cost measures, DTW brings the feature sequences into temporal correspondence. To account for efficiency, 14 | robustness, and accuracy, **Sync Toolbox** uses a combination of 15 | `multiscale DTW (MsDTW) `__, 16 | `memory-restricted MsDTW (MrMsDTW) `__, 17 | and `high-resolution music synchronization `__. 18 | 19 | Despite a slight overlap with the well-known software packages in the MIR field (e.g., librosa and linmdtw), 20 | our **Sync Toolbox** is the first to provide an open-source Python package for offline music synchronization 21 | that produces state-of-the-art alignment results regarding efficiency and accuracy. 22 | 23 | 24 | 25 | Sync Toolbox API Documentation 26 | ------------------------------ 27 | 28 | The source code for the package Sync Toolbox is hosted at GitHub: 29 | 30 | https://github.com/meinardmueller/synctoolbox 31 | 32 | In particular, please note the provided Readme and the example notebooks. 33 | 34 | If you use SyncToolbox in a scholarly work, please consider citing the Sync Toolbox article. [#]_ 35 | 36 | 37 | .. [#] Müller et al., (2021). Sync Toolbox: A Python Package for Efficient, Robust, and Accurate Music Synchronization. Journal of Open Source Software, 6(64), 3434, https://doi.org/10.21105/joss.03434 38 | 39 | .. toctree:: 40 | :caption: API Documentation 41 | :maxdepth: 2 42 | :hidden: 43 | 44 | dtw 45 | feature/index 46 | 47 | 48 | .. toctree:: 49 | :caption: Reference 50 | :maxdepth: 1 51 | :hidden: 52 | 53 | genindex 54 | py-modindex 55 | -------------------------------------------------------------------------------- /docs/build/html/_sources/py-modindex.rst.txt: -------------------------------------------------------------------------------- 1 | Module Index 2 | ============ 3 | -------------------------------------------------------------------------------- /docs/build/html/_static/css/badge_only.css: -------------------------------------------------------------------------------- 1 | .fa:before{-webkit-font-smoothing:antialiased}.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/meinardmueller/synctoolbox/cf4c471f232d510849b77491720dd19f8441340c/docs/build/html/_static/css/fonts/Roboto-Slab-Bold.woff -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/Roboto-Slab-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meinardmueller/synctoolbox/cf4c471f232d510849b77491720dd19f8441340c/docs/build/html/_static/css/fonts/Roboto-Slab-Bold.woff2 -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/Roboto-Slab-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meinardmueller/synctoolbox/cf4c471f232d510849b77491720dd19f8441340c/docs/build/html/_static/css/fonts/Roboto-Slab-Regular.woff -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/Roboto-Slab-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meinardmueller/synctoolbox/cf4c471f232d510849b77491720dd19f8441340c/docs/build/html/_static/css/fonts/Roboto-Slab-Regular.woff2 -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meinardmueller/synctoolbox/cf4c471f232d510849b77491720dd19f8441340c/docs/build/html/_static/css/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meinardmueller/synctoolbox/cf4c471f232d510849b77491720dd19f8441340c/docs/build/html/_static/css/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meinardmueller/synctoolbox/cf4c471f232d510849b77491720dd19f8441340c/docs/build/html/_static/css/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meinardmueller/synctoolbox/cf4c471f232d510849b77491720dd19f8441340c/docs/build/html/_static/css/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/lato-bold-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meinardmueller/synctoolbox/cf4c471f232d510849b77491720dd19f8441340c/docs/build/html/_static/css/fonts/lato-bold-italic.woff -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/lato-bold-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meinardmueller/synctoolbox/cf4c471f232d510849b77491720dd19f8441340c/docs/build/html/_static/css/fonts/lato-bold-italic.woff2 -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/lato-bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meinardmueller/synctoolbox/cf4c471f232d510849b77491720dd19f8441340c/docs/build/html/_static/css/fonts/lato-bold.woff -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/lato-bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meinardmueller/synctoolbox/cf4c471f232d510849b77491720dd19f8441340c/docs/build/html/_static/css/fonts/lato-bold.woff2 -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/lato-normal-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meinardmueller/synctoolbox/cf4c471f232d510849b77491720dd19f8441340c/docs/build/html/_static/css/fonts/lato-normal-italic.woff -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/lato-normal-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meinardmueller/synctoolbox/cf4c471f232d510849b77491720dd19f8441340c/docs/build/html/_static/css/fonts/lato-normal-italic.woff2 -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/lato-normal.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meinardmueller/synctoolbox/cf4c471f232d510849b77491720dd19f8441340c/docs/build/html/_static/css/fonts/lato-normal.woff -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/lato-normal.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meinardmueller/synctoolbox/cf4c471f232d510849b77491720dd19f8441340c/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 _ready = (callback) => { 14 | if (document.readyState !== "loading") { 15 | callback(); 16 | } else { 17 | document.addEventListener("DOMContentLoaded", callback); 18 | } 19 | }; 20 | 21 | /** 22 | * highlight a given string on a node by wrapping it in 23 | * span elements with the given class name. 24 | */ 25 | const _highlight = (node, addItems, text, className) => { 26 | if (node.nodeType === Node.TEXT_NODE) { 27 | const val = node.nodeValue; 28 | const parent = node.parentNode; 29 | const pos = val.toLowerCase().indexOf(text); 30 | if ( 31 | pos >= 0 && 32 | !parent.classList.contains(className) && 33 | !parent.classList.contains("nohighlight") 34 | ) { 35 | let span; 36 | 37 | const closestNode = parent.closest("body, svg, foreignObject"); 38 | const isInSVG = closestNode && closestNode.matches("svg"); 39 | if (isInSVG) { 40 | span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); 41 | } else { 42 | span = document.createElement("span"); 43 | span.classList.add(className); 44 | } 45 | 46 | span.appendChild(document.createTextNode(val.substr(pos, text.length))); 47 | parent.insertBefore( 48 | span, 49 | parent.insertBefore( 50 | document.createTextNode(val.substr(pos + text.length)), 51 | node.nextSibling 52 | ) 53 | ); 54 | node.nodeValue = val.substr(0, pos); 55 | 56 | if (isInSVG) { 57 | const rect = document.createElementNS( 58 | "http://www.w3.org/2000/svg", 59 | "rect" 60 | ); 61 | const bbox = parent.getBBox(); 62 | rect.x.baseVal.value = bbox.x; 63 | rect.y.baseVal.value = bbox.y; 64 | rect.width.baseVal.value = bbox.width; 65 | rect.height.baseVal.value = bbox.height; 66 | rect.setAttribute("class", className); 67 | addItems.push({ parent: parent, target: rect }); 68 | } 69 | } 70 | } else if (node.matches && !node.matches("button, select, textarea")) { 71 | node.childNodes.forEach((el) => _highlight(el, addItems, text, className)); 72 | } 73 | }; 74 | const _highlightText = (thisNode, text, className) => { 75 | let addItems = []; 76 | _highlight(thisNode, addItems, text, className); 77 | addItems.forEach((obj) => 78 | obj.parent.insertAdjacentElement("beforebegin", obj.target) 79 | ); 80 | }; 81 | 82 | /** 83 | * Small JavaScript module for the documentation. 84 | */ 85 | const Documentation = { 86 | init: () => { 87 | Documentation.highlightSearchWords(); 88 | Documentation.initDomainIndexTable(); 89 | Documentation.initOnKeyListeners(); 90 | }, 91 | 92 | /** 93 | * i18n support 94 | */ 95 | TRANSLATIONS: {}, 96 | PLURAL_EXPR: (n) => (n === 1 ? 0 : 1), 97 | LOCALE: "unknown", 98 | 99 | // gettext and ngettext don't access this so that the functions 100 | // can safely bound to a different name (_ = Documentation.gettext) 101 | gettext: (string) => { 102 | const translated = Documentation.TRANSLATIONS[string]; 103 | switch (typeof translated) { 104 | case "undefined": 105 | return string; // no translation 106 | case "string": 107 | return translated; // translation exists 108 | default: 109 | return translated[0]; // (singular, plural) translation tuple exists 110 | } 111 | }, 112 | 113 | ngettext: (singular, plural, n) => { 114 | const translated = Documentation.TRANSLATIONS[singular]; 115 | if (typeof translated !== "undefined") 116 | return translated[Documentation.PLURAL_EXPR(n)]; 117 | return n === 1 ? singular : plural; 118 | }, 119 | 120 | addTranslations: (catalog) => { 121 | Object.assign(Documentation.TRANSLATIONS, catalog.messages); 122 | Documentation.PLURAL_EXPR = new Function( 123 | "n", 124 | `return (${catalog.plural_expr})` 125 | ); 126 | Documentation.LOCALE = catalog.locale; 127 | }, 128 | 129 | /** 130 | * highlight the search words provided in the url in the text 131 | */ 132 | highlightSearchWords: () => { 133 | const highlight = 134 | new URLSearchParams(window.location.search).get("highlight") || ""; 135 | const terms = highlight.toLowerCase().split(/\s+/).filter(x => x); 136 | if (terms.length === 0) return; // nothing to do 137 | 138 | // There should never be more than one element matching "div.body" 139 | const divBody = document.querySelectorAll("div.body"); 140 | const body = divBody.length ? divBody[0] : document.querySelector("body"); 141 | window.setTimeout(() => { 142 | terms.forEach((term) => _highlightText(body, term, "highlighted")); 143 | }, 10); 144 | 145 | const searchBox = document.getElementById("searchbox"); 146 | if (searchBox === null) return; 147 | searchBox.appendChild( 148 | document 149 | .createRange() 150 | .createContextualFragment( 151 | '" 155 | ) 156 | ); 157 | }, 158 | 159 | /** 160 | * helper function to hide the search marks again 161 | */ 162 | hideSearchWords: () => { 163 | document 164 | .querySelectorAll("#searchbox .highlight-link") 165 | .forEach((el) => el.remove()); 166 | document 167 | .querySelectorAll("span.highlighted") 168 | .forEach((el) => el.classList.remove("highlighted")); 169 | const url = new URL(window.location); 170 | url.searchParams.delete("highlight"); 171 | window.history.replaceState({}, "", url); 172 | }, 173 | 174 | /** 175 | * helper function to focus on search bar 176 | */ 177 | focusSearchBar: () => { 178 | document.querySelectorAll("input[name=q]")[0]?.focus(); 179 | }, 180 | 181 | /** 182 | * Initialise the domain index toggle buttons 183 | */ 184 | initDomainIndexTable: () => { 185 | const toggler = (el) => { 186 | const idNumber = el.id.substr(7); 187 | const toggledRows = document.querySelectorAll(`tr.cg-${idNumber}`); 188 | if (el.src.substr(-9) === "minus.png") { 189 | el.src = `${el.src.substr(0, el.src.length - 9)}plus.png`; 190 | toggledRows.forEach((el) => (el.style.display = "none")); 191 | } else { 192 | el.src = `${el.src.substr(0, el.src.length - 8)}minus.png`; 193 | toggledRows.forEach((el) => (el.style.display = "")); 194 | } 195 | }; 196 | 197 | const togglerElements = document.querySelectorAll("img.toggler"); 198 | togglerElements.forEach((el) => 199 | el.addEventListener("click", (event) => toggler(event.currentTarget)) 200 | ); 201 | togglerElements.forEach((el) => (el.style.display = "")); 202 | if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) togglerElements.forEach(toggler); 203 | }, 204 | 205 | initOnKeyListeners: () => { 206 | // only install a listener if it is really needed 207 | if ( 208 | !DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS && 209 | !DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS 210 | ) 211 | return; 212 | 213 | const blacklistedElements = new Set([ 214 | "TEXTAREA", 215 | "INPUT", 216 | "SELECT", 217 | "BUTTON", 218 | ]); 219 | document.addEventListener("keydown", (event) => { 220 | if (blacklistedElements.has(document.activeElement.tagName)) return; // bail for input elements 221 | if (event.altKey || event.ctrlKey || event.metaKey) return; // bail with special keys 222 | 223 | if (!event.shiftKey) { 224 | switch (event.key) { 225 | case "ArrowLeft": 226 | if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; 227 | 228 | const prevLink = document.querySelector('link[rel="prev"]'); 229 | if (prevLink && prevLink.href) { 230 | window.location.href = prevLink.href; 231 | event.preventDefault(); 232 | } 233 | break; 234 | case "ArrowRight": 235 | if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; 236 | 237 | const nextLink = document.querySelector('link[rel="next"]'); 238 | if (nextLink && nextLink.href) { 239 | window.location.href = nextLink.href; 240 | event.preventDefault(); 241 | } 242 | break; 243 | case "Escape": 244 | if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) break; 245 | Documentation.hideSearchWords(); 246 | event.preventDefault(); 247 | } 248 | } 249 | 250 | // some keyboard layouts may need Shift to get / 251 | switch (event.key) { 252 | case "/": 253 | if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) break; 254 | Documentation.focusSearchBar(); 255 | event.preventDefault(); 256 | } 257 | }); 258 | }, 259 | }; 260 | 261 | // quick alias for translations 262 | const _ = Documentation.gettext; 263 | 264 | _ready(Documentation.init); 265 | -------------------------------------------------------------------------------- /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.0', 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/meinardmueller/synctoolbox/cf4c471f232d510849b77491720dd19f8441340c/docs/build/html/_static/file.png -------------------------------------------------------------------------------- /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/theme.js: -------------------------------------------------------------------------------- 1 | !function(n){var e={};function t(i){if(e[i])return e[i].exports;var o=e[i]={i:i,l:!1,exports:{}};return n[i].call(o.exports,o,o.exports,t),o.l=!0,o.exports}t.m=n,t.c=e,t.d=function(n,e,i){t.o(n,e)||Object.defineProperty(n,e,{enumerable:!0,get:i})},t.r=function(n){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},t.t=function(n,e){if(1&e&&(n=t(n)),8&e)return n;if(4&e&&"object"==typeof n&&n&&n.__esModule)return n;var i=Object.create(null);if(t.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:n}),2&e&&"string"!=typeof n)for(var o in n)t.d(i,o,function(e){return n[e]}.bind(null,o));return i},t.n=function(n){var e=n&&n.__esModule?function(){return n.default}:function(){return n};return t.d(e,"a",e),e},t.o=function(n,e){return Object.prototype.hasOwnProperty.call(n,e)},t.p="",t(t.s=0)}([function(n,e,t){t(1),n.exports=t(3)},function(n,e,t){(function(){var e="undefined"!=typeof window?window.jQuery:t(2);n.exports.ThemeNav={navBar:null,win:null,winScroll:!1,winResize:!1,linkScroll:!1,winPosition:0,winHeight:null,docHeight:null,isRunning:!1,enable:function(n){var t=this;void 0===n&&(n=!0),t.isRunning||(t.isRunning=!0,e((function(e){t.init(e),t.reset(),t.win.on("hashchange",t.reset),n&&t.win.on("scroll",(function(){t.linkScroll||t.winScroll||(t.winScroll=!0,requestAnimationFrame((function(){t.onScroll()})))})),t.win.on("resize",(function(){t.winResize||(t.winResize=!0,requestAnimationFrame((function(){t.onResize()})))})),t.onResize()})))},enableSticky:function(){this.enable(!0)},init:function(n){n(document);var e=this;this.navBar=n("div.wy-side-scroll:first"),this.win=n(window),n(document).on("click","[data-toggle='wy-nav-top']",(function(){n("[data-toggle='wy-nav-shift']").toggleClass("shift"),n("[data-toggle='rst-versions']").toggleClass("shift")})).on("click",".wy-menu-vertical .current ul li a",(function(){var t=n(this);n("[data-toggle='wy-nav-shift']").removeClass("shift"),n("[data-toggle='rst-versions']").toggleClass("shift"),e.toggleCurrent(t),e.hashChange()})).on("click","[data-toggle='rst-current-version']",(function(){n("[data-toggle='rst-versions']").toggleClass("shift-up")})),n("table.docutils:not(.field-list,.footnote,.citation)").wrap("
"),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/logo_synctoolbox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meinardmueller/synctoolbox/cf4c471f232d510849b77491720dd19f8441340c/docs/build/html/_static/logo_synctoolbox.png -------------------------------------------------------------------------------- /docs/build/html/_static/minus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meinardmueller/synctoolbox/cf4c471f232d510849b77491720dd19f8441340c/docs/build/html/_static/minus.png -------------------------------------------------------------------------------- /docs/build/html/_static/plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meinardmueller/synctoolbox/cf4c471f232d510849b77491720dd19f8441340c/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/feature/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Feature Extraction (synctoolbox.feature) — SyncToolbox 1.0.0 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 |
    76 |
  • »
  • 77 |
  • Feature Extraction (synctoolbox.feature)
  • 78 |
  • 79 | View page source 80 |
  • 81 |
82 |
83 |
84 |
85 |
86 | 87 |
88 |

Feature Extraction (synctoolbox.feature)

89 |
90 |
91 |
92 | 93 | 94 |
95 |
96 |
100 | 101 |
102 | 103 |
104 |

© Copyright 2021, Meinard Müller, Yigitcan Özer, Michael Krause, Thomas Prätzlich and Jonathan Driedger.

105 |
106 | 107 | Built with Sphinx using a 108 | theme 109 | provided by Read the Docs. 110 | 111 | 112 |
113 |
114 |
115 |
116 |
117 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /docs/build/html/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Sync Toolbox: A Python Package for Efficient, Robust, and Accurate Music Synchronization — SyncToolbox 1.0.0 documentation 8 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 55 | 56 |
60 | 61 |
62 |
63 |
64 |
    65 |
  • »
  • 66 |
  • Sync Toolbox: A Python Package for Efficient, Robust, and Accurate Music Synchronization
  • 67 |
  • 68 | View page source 69 |
  • 70 |
71 |
72 |
73 |
74 |
75 | 76 |
77 |

Sync Toolbox: A Python Package for Efficient, Robust, and Accurate Music Synchronization

78 |

Sync Toolbox is a Python package, which comprises all components of a music synchronization 79 | pipeline that is robust, efficient, and accurate.

80 |

The toolbox’s core technology is based on dynamic time warping (DTW). Using suitable feature representations 81 | and cost measures, DTW brings the feature sequences into temporal correspondence. To account for efficiency, 82 | robustness, and accuracy, Sync Toolbox uses a combination of 83 | multiscale DTW (MsDTW), 84 | memory-restricted MsDTW (MrMsDTW), 85 | and high-resolution music synchronization.

86 |

Despite a slight overlap with the well-known software packages in the MIR field (e.g., librosa and linmdtw), 87 | our Sync Toolbox is the first to provide an open-source Python package for offline music synchronization 88 | that produces state-of-the-art alignment results regarding efficiency and accuracy.

89 |
90 |

Sync Toolbox API Documentation

91 |

The source code for the package Sync Toolbox is hosted at GitHub:

92 |

https://github.com/meinardmueller/synctoolbox

93 |

In particular, please note the provided Readme and the example notebooks.

94 |

If you use SyncToolbox in a scholarly work, please consider citing the Sync Toolbox article. 1

95 |
96 |
1
97 |

Müller et al., (2021). Sync Toolbox: A Python Package for Efficient, Robust, and Accurate Music Synchronization. Journal of Open Source Software, 6(64), 3434, https://doi.org/10.21105/joss.03434

98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 | 107 | 108 |
109 |
110 |
113 | 114 |
115 | 116 |
117 |

© Copyright 2021, Meinard Müller, Yigitcan Özer, Michael Krause, Thomas Prätzlich and Jonathan Driedger.

118 |
119 | 120 | Built with Sphinx using a 121 | theme 122 | provided by Read the Docs. 123 | 124 | 125 |
126 |
127 |
128 |
129 |
130 | 135 | 136 | 137 | -------------------------------------------------------------------------------- /docs/build/html/objects.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meinardmueller/synctoolbox/cf4c471f232d510849b77491720dd19f8441340c/docs/build/html/objects.inv -------------------------------------------------------------------------------- /docs/build/html/py-modindex.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Python Module Index — SyncToolbox 1.0.0 documentation 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 | 56 | 57 |
61 | 62 |
63 |
64 |
65 |
    66 |
  • »
  • 67 |
  • Python Module Index
  • 68 |
  • 69 |
  • 70 |
71 |
72 |
73 |
74 |
75 | 76 | 77 |

Python Module Index

78 | 79 |
80 | s 81 |
82 | 83 | 84 | 85 | 87 | 88 | 90 | 93 | 94 | 95 | 98 | 99 | 100 | 103 | 104 | 105 | 108 | 109 | 110 | 113 | 114 | 115 | 118 | 119 | 120 | 123 | 124 | 125 | 128 | 129 | 130 | 133 | 134 | 135 | 138 | 139 | 140 | 143 |
 
86 | s
91 | synctoolbox 92 |
    96 | synctoolbox.dtw.core 97 |
    101 | synctoolbox.dtw.mrmsdtw 102 |
    106 | synctoolbox.feature.chroma 107 |
    111 | synctoolbox.feature.csv_tools 112 |
    116 | synctoolbox.feature.dlnco 117 |
    121 | synctoolbox.feature.filterbank 122 |
    126 | synctoolbox.feature.novelty 127 |
    131 | synctoolbox.feature.pitch 132 |
    136 | synctoolbox.feature.pitch_onset 137 |
    141 | synctoolbox.feature.utils 142 |
144 | 145 | 146 |
147 |
148 |
149 | 150 |
151 | 152 |
153 |

© Copyright 2021, Meinard Müller, Yigitcan Özer, Michael Krause, Thomas Prätzlich and Jonathan Driedger.

154 |
155 | 156 | Built with Sphinx using a 157 | theme 158 | provided by Read the Docs. 159 | 160 | 161 |
162 |
163 |
164 |
165 |
166 | 171 | 172 | 173 | -------------------------------------------------------------------------------- /docs/build/html/search.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Search — SyncToolbox 1.0.0 documentation 7 | 8 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 | 56 | 57 |
61 | 62 |
63 |
64 |
65 |
    66 |
  • »
  • 67 |
  • Search
  • 68 |
  • 69 |
  • 70 |
71 |
72 |
73 |
74 |
75 | 76 | 83 | 84 | 85 |
86 | 87 |
88 | 89 |
90 |
91 |
92 | 93 |
94 | 95 |
96 |

© Copyright 2021, Meinard Müller, Yigitcan Özer, Michael Krause, Thomas Prätzlich and Jonathan Driedger.

97 |
98 | 99 | Built with Sphinx using a 100 | theme 101 | provided by Read the Docs. 102 | 103 | 104 |
105 |
106 |
107 |
108 |
109 | 114 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | -------------------------------------------------------------------------------- /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/logo_synctoolbox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meinardmueller/synctoolbox/cf4c471f232d510849b77491720dd19f8441340c/docs/source/_static/logo_synctoolbox.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 | SYNCTOOLBOX_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..')) 18 | assert os.path.exists(os.path.join(SYNCTOOLBOX_DIR, 'synctoolbox')) 19 | sys.path.insert(0, SYNCTOOLBOX_DIR) 20 | 21 | # -- Project information ----------------------------------------------------- 22 | 23 | project = 'SyncToolbox' 24 | copyright = '2021, Meinard Müller, Yigitcan Özer, Michael Krause, Thomas Prätzlich and Jonathan Driedger' 25 | author = 'Meinard Müller, Yigitcan Özer, Michael Krause, Thomas Prätzlich and Jonathan Driedger' 26 | 27 | # The full version, including alpha/beta/rc tags 28 | release = '1.0.0' 29 | 30 | 31 | # -- General configuration --------------------------------------------------- 32 | 33 | # Add any Sphinx extension module names here, as strings. They can be 34 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 35 | # ones. 36 | extensions = [ 37 | 'sphinx.ext.autodoc', # documentation based on docstrings 38 | 'sphinx.ext.napoleon', # for having google/numpy style docstrings 39 | 'sphinx.ext.viewcode', # link source code 40 | 'sphinx.ext.intersphinx', 41 | 'sphinx.ext.autosummary', 42 | 'sphinx.ext.extlinks' 43 | ] 44 | autodoc_preserve_defaults = True 45 | 46 | # Add any paths that contain templates here, relative to this directory. 47 | templates_path = ['_templates'] 48 | 49 | 50 | # List of patterns, relative to source directory, that match files and 51 | # directories to ignore when looking for source files. 52 | # This pattern also affects html_static_path and html_extra_path. 53 | exclude_patterns = [] 54 | 55 | # The name of the Pygments (syntax highlighting) style to use. 56 | pygments_style = 'sphinx' 57 | 58 | 59 | # -- Options for HTML output ------------------------------------------------- 60 | 61 | # The theme to use for HTML and HTML Help pages. See the documentation for 62 | # a list of builtin themes. 63 | # 64 | # html_theme = 'alabaster' 65 | import sphinx_rtd_theme # noqa 66 | 67 | html_theme = "sphinx_rtd_theme" 68 | html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] 69 | 70 | # Add any paths that contain custom static files (such as style sheets) here, 71 | # relative to this directory. They are copied after the builtin static files, 72 | # so a file named "default.css" will overwrite the builtin "default.css". 73 | html_static_path = ['_static'] 74 | html_use_index = True 75 | html_use_modindex = True 76 | 77 | html_logo = os.path.join(html_static_path[0], 'logo_synctoolbox.png') 78 | 79 | html_theme_options = {'logo_only': True} 80 | 81 | napoleon_custom_sections = [('Returns', 'params_style'), ('Parameters', 'params_style')] 82 | 83 | 84 | extlinks = {'fmpbook': ('https://www.audiolabs-erlangen.de/fau/professor/mueller/bookFMP', 'FMP'), 85 | 'fmpnotebook': ('https://www.audiolabs-erlangen.de/resources/MIR/FMP/%s.html', '%s.ipynb')} 86 | 87 | 88 | def link_notebook(app, what, name, obj, options, lines): 89 | for i, line in enumerate(lines): 90 | if 'Notebook:' in line: 91 | match = re.search('Notebook: (.*?)\.ipynb', line) 92 | if match: 93 | link = match.group(1) 94 | lines[i] = lines[i].replace(f'{link}.ipynb', f':fmpnotebook:`{link}`') 95 | 96 | 97 | def remove_module_docstring(app, what, name, obj, options, lines): 98 | if what == 'module': 99 | del lines[:] 100 | 101 | 102 | def setup(app): 103 | app.connect('autodoc-process-docstring', link_notebook) 104 | app.connect('autodoc-process-docstring', remove_module_docstring) 105 | -------------------------------------------------------------------------------- /docs/source/dtw.rst: -------------------------------------------------------------------------------- 1 | Dynamic Time Warping (synctoolbox.dtw) 2 | ====================================== 3 | 4 | .. automodule:: synctoolbox.dtw.core 5 | :members: 6 | :undoc-members: 7 | 8 | .. automodule:: synctoolbox.dtw.mrmsdtw 9 | :members: 10 | :undoc-members: 11 | 12 | 13 | -------------------------------------------------------------------------------- /docs/source/feature/chroma.rst: -------------------------------------------------------------------------------- 1 | Chroma Features (synctoolbox.feature.chroma) 2 | ============================================ 3 | 4 | .. automodule:: synctoolbox.feature.chroma 5 | :members: 6 | :undoc-members: -------------------------------------------------------------------------------- /docs/source/feature/csv_tools.rst: -------------------------------------------------------------------------------- 1 | CSV Tools (synctoolbox.feature.csv_tools) 2 | ========================================= 3 | 4 | .. automodule:: synctoolbox.feature.csv_tools 5 | :members: 6 | :undoc-members: -------------------------------------------------------------------------------- /docs/source/feature/dlnco.rst: -------------------------------------------------------------------------------- 1 | DLNCO Features (synctoolbox.feature.dlnco) 2 | ========================================== 3 | 4 | .. automodule:: synctoolbox.feature.dlnco 5 | :members: 6 | :undoc-members: -------------------------------------------------------------------------------- /docs/source/feature/filterbank.rst: -------------------------------------------------------------------------------- 1 | Filterbank Generation (synctoolbox.feature.filterbank) 2 | ====================================================== 3 | 4 | .. automodule:: synctoolbox.feature.filterbank 5 | :members: 6 | :undoc-members: -------------------------------------------------------------------------------- /docs/source/feature/index.rst: -------------------------------------------------------------------------------- 1 | Feature Extraction (synctoolbox.feature) 2 | ======================================== 3 | 4 | .. toctree:: 5 | :caption: Features 6 | :maxdepth: 1 7 | :hidden: 8 | 9 | chroma 10 | filterbank 11 | pitch 12 | pitch_onset 13 | dlnco 14 | novelty 15 | csv_tools 16 | utils -------------------------------------------------------------------------------- /docs/source/feature/novelty.rst: -------------------------------------------------------------------------------- 1 | Spectral Flux (synctoolbox.feature.novelty) 2 | =========================================== 3 | 4 | .. automodule:: synctoolbox.feature.novelty 5 | :members: 6 | :undoc-members: -------------------------------------------------------------------------------- /docs/source/feature/pitch.rst: -------------------------------------------------------------------------------- 1 | Pitch Features (synctoolbox.feature.pitch) 2 | ========================================== 3 | 4 | .. automodule:: synctoolbox.feature.pitch 5 | :members: 6 | :undoc-members: -------------------------------------------------------------------------------- /docs/source/feature/pitch_onset.rst: -------------------------------------------------------------------------------- 1 | Pitch Onset Features (synctoolbox.feature.pitch_onset) 2 | ====================================================== 3 | 4 | .. automodule:: synctoolbox.feature.pitch_onset 5 | :members: 6 | :undoc-members: -------------------------------------------------------------------------------- /docs/source/feature/utils.rst: -------------------------------------------------------------------------------- 1 | Utils (synctoolbox.feature.utils) 2 | ================================= 3 | 4 | .. automodule:: synctoolbox.feature.utils 5 | :members: 6 | :undoc-members: -------------------------------------------------------------------------------- /docs/source/genindex.rst: -------------------------------------------------------------------------------- 1 | Index 2 | ===== -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | .. SyncToolbox documentation master file, created by 2 | sphinx-quickstart on Tue May 4 08:49:22 2021. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | 7 | Sync Toolbox: A Python Package for Efficient, Robust, and Accurate Music Synchronization 8 | ======================================================================================== 9 | **Sync Toolbox** is a Python package, which comprises all components of a music synchronization 10 | pipeline that is robust, efficient, and accurate. 11 | 12 | The toolbox's core technology is based on dynamic time warping (DTW). Using suitable feature representations 13 | and cost measures, DTW brings the feature sequences into temporal correspondence. To account for efficiency, 14 | robustness, and accuracy, **Sync Toolbox** uses a combination of 15 | `multiscale DTW (MsDTW) `__, 16 | `memory-restricted MsDTW (MrMsDTW) `__, 17 | and `high-resolution music synchronization `__. 18 | 19 | Despite a slight overlap with the well-known software packages in the MIR field (e.g., librosa and linmdtw), 20 | our **Sync Toolbox** is the first to provide an open-source Python package for offline music synchronization 21 | that produces state-of-the-art alignment results regarding efficiency and accuracy. 22 | 23 | 24 | 25 | Sync Toolbox API Documentation 26 | ------------------------------ 27 | 28 | The source code for the package Sync Toolbox is hosted at GitHub: 29 | 30 | https://github.com/meinardmueller/synctoolbox 31 | 32 | In particular, please note the provided Readme and the example notebooks. 33 | 34 | If you use SyncToolbox in a scholarly work, please consider citing the Sync Toolbox article. [#]_ 35 | 36 | 37 | .. [#] Müller et al., (2021). Sync Toolbox: A Python Package for Efficient, Robust, and Accurate Music Synchronization. Journal of Open Source Software, 6(64), 3434, https://doi.org/10.21105/joss.03434 38 | 39 | .. toctree:: 40 | :caption: API Documentation 41 | :maxdepth: 2 42 | :hidden: 43 | 44 | dtw 45 | feature/index 46 | 47 | 48 | .. toctree:: 49 | :caption: Reference 50 | :maxdepth: 1 51 | :hidden: 52 | 53 | genindex 54 | py-modindex 55 | -------------------------------------------------------------------------------- /docs/source/py-modindex.rst: -------------------------------------------------------------------------------- 1 | Module Index 2 | ============ 3 | -------------------------------------------------------------------------------- /environment.yml: -------------------------------------------------------------------------------- 1 | name: synctoolbox 2 | 3 | channels: 4 | - defaults 5 | - conda-forge 6 | 7 | dependencies: 8 | - python>=3.8.0, <3.13.0 9 | - ipython 10 | - jupyter 11 | 12 | - pip: 13 | - synctoolbox==1.2.* 14 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["flit"] 3 | build-backend = "flit.buildapi" 4 | 5 | [project] 6 | name = "synctoolbox" 7 | version = "1.4.0" 8 | description = "Python Package for Efficient, Robust, and Accurate Music Synchronization (Sync Toolbox)" 9 | readme = "README.md" 10 | requires-python = ">=3.8, <3.13.0" 11 | license = {text = "MIT"} 12 | authors = [ 13 | {name = "Meinard Müller", email = "meinard.mueller@audiolabs-erlangen.de"}, 14 | {name = "Yigitcan Özer"}, 15 | {name = "Michael Krause"}, 16 | {name = "Thomas Prätzlich"}, 17 | {name = "Jonathan Driedger"} 18 | ] 19 | classifiers = [ 20 | "License :: OSI Approved :: MIT License", 21 | "Programming Language :: Python", 22 | "Intended Audience :: Developers", 23 | "Topic :: Multimedia :: Sound/Audio :: Analysis", 24 | "Programming Language :: Python :: 3", 25 | ] 26 | dependencies = [ 27 | "librosa >= 0.8.0, < 1.0.0", 28 | "matplotlib >= 3.1.0, < 4.0.0", 29 | "music21 >= 5.7.0, < 6.0.0", 30 | "numba >= 0.58.1, < 0.60.0", 31 | "numpy >= 1.17.0, < 2.0.0", 32 | "pandas >= 1.0.0, < 2.0.0", 33 | "pretty_midi >= 0.2.0, < 1.0.0", 34 | "soundfile >= 0.9.0, < 1.0.0", 35 | "scipy >= 1.7.0, < 2.0.0", 36 | "libfmp >= 1.2.0, < 2.0.0" 37 | ] 38 | 39 | [project.optional-dependencies] 40 | develop = [ 41 | "flit >= 3.10.0", 42 | "ipython >= 8.10.0", 43 | "jupyter", 44 | "nbstripout" 45 | ] 46 | test = ["pytest == 6.2.*"] 47 | doc = [ 48 | "sphinx == 4.0.*", 49 | "sphinx_rtd_theme == 0.5.*" 50 | ] 51 | 52 | [project.urls] 53 | Homepage = "https://github.com/meinardmueller/synctoolbox" 54 | Repository = "https://github.com/meinardmueller/synctoolbox" 55 | Download = "https://github.com/meinardmueller/synctoolbox" 56 | Documentation = "https://meinardmueller.github.io/synctoolbox/build/html/index.html" 57 | -------------------------------------------------------------------------------- /sync_audio_audio_simple.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": { 6 | "pycharm": { 7 | "name": "#%% md\n" 8 | } 9 | }, 10 | "source": [ 11 | "# Demo: Audio-audio synchronization with chroma features and MrMsDTW\n", 12 | "\n", 13 | "In this notebook, we'll show a minimal example for the use of the SyncToolbox for music synchronization. We will take two recordings of the same musical piece (the first song of Franz Schubert's \"Winterreise\"), compute chroma representations of both recordings and align them using classical dynamic time warping (DTW) and multi-resolution multi-scale DTW (MrMsDTW). We will also compare the runtimes of the two algorithms.\n", 14 | "\n", 15 | "For an explanation of chroma features and DTW, see [1]." 16 | ] 17 | }, 18 | { 19 | "cell_type": "code", 20 | "execution_count": null, 21 | "metadata": { 22 | "pycharm": { 23 | "name": "#%%\n" 24 | } 25 | }, 26 | "outputs": [], 27 | "source": [ 28 | "# Loading some modules and defining some constants used later\n", 29 | "import time\n", 30 | "import librosa.display\n", 31 | "import matplotlib.pyplot as plt\n", 32 | "import IPython.display as ipd\n", 33 | "from libfmp.b.b_plot import plot_signal, plot_chromagram\n", 34 | "from libfmp.c3.c3s2_dtw_plot import plot_matrix_with_points\n", 35 | "\n", 36 | "from synctoolbox.dtw.core import compute_warping_path\n", 37 | "from synctoolbox.dtw.cost import cosine_distance\n", 38 | "from synctoolbox.dtw.mrmsdtw import sync_via_mrmsdtw\n", 39 | "%matplotlib inline\n", 40 | "\n", 41 | "Fs = 22050\n", 42 | "N = 2048\n", 43 | "H = 1024\n", 44 | "feature_rate = int(22050 / H)\n", 45 | "\n", 46 | "figsize = (9, 3)" 47 | ] 48 | }, 49 | { 50 | "cell_type": "markdown", 51 | "metadata": {}, 52 | "source": [ 53 | "## Loading two recordings of the same piece\n", 54 | "\n", 55 | "Here, we take recordings of the song \"Gute Nacht\" by Franz Schubert from his song cycle \"Winterreise\" in two performances (versions). The first version is by Gerhard Hüsch and Hanns-Udo Müller from 1933. The second version is by Randall Scarlata and Jeremy Denk from 2006.\n", 56 | "\n", 57 | "### Version 1" 58 | ] 59 | }, 60 | { 61 | "cell_type": "code", 62 | "execution_count": null, 63 | "metadata": { 64 | "pycharm": { 65 | "name": "#%%\n" 66 | } 67 | }, 68 | "outputs": [], 69 | "source": [ 70 | "audio_1, _ = librosa.load('data_music/Schubert_D911-01_HU33.wav', sr=Fs)\n", 71 | "\n", 72 | "plot_signal(audio_1, Fs=Fs, ylabel='Amplitude', title='Version 1', figsize=figsize)\n", 73 | "ipd.display(ipd.Audio(audio_1, rate=Fs))" 74 | ] 75 | }, 76 | { 77 | "cell_type": "markdown", 78 | "metadata": {}, 79 | "source": [ 80 | "### Version 2" 81 | ] 82 | }, 83 | { 84 | "cell_type": "code", 85 | "execution_count": null, 86 | "metadata": { 87 | "pycharm": { 88 | "name": "#%%\n" 89 | } 90 | }, 91 | "outputs": [], 92 | "source": [ 93 | "audio_2, _ = librosa.load('data_music/Schubert_D911-01_SC06.wav', Fs)\n", 94 | "\n", 95 | "plot_signal(audio_2, Fs=Fs, ylabel='Amplitude', title='Version 2', figsize=figsize)\n", 96 | "ipd.display(ipd.Audio(audio_2, rate=Fs))" 97 | ] 98 | }, 99 | { 100 | "cell_type": "markdown", 101 | "metadata": {}, 102 | "source": [ 103 | "## Obtaining chroma representations of the recordings using librosa\n", 104 | "\n", 105 | "For most Western classical and pop music, chroma features are highly useful for aligning different versions of the same piece. Here, we use librosa to calculate two very basic chroma representations, derived from STFTs. The plots illustrate the chroma representations of the first 30 seconds of each version." 106 | ] 107 | }, 108 | { 109 | "cell_type": "code", 110 | "execution_count": null, 111 | "metadata": { 112 | "pycharm": { 113 | "name": "#%%\n" 114 | } 115 | }, 116 | "outputs": [], 117 | "source": [ 118 | "chroma_1 = librosa.feature.chroma_stft(y=audio_1, sr=Fs, n_fft=N, hop_length=H, norm=2.0)\n", 119 | "plot_chromagram(chroma_1[:, :30 * feature_rate], Fs=feature_rate, title='Chroma representation for version 1', figsize=figsize)\n", 120 | "plt.show()\n", 121 | "\n", 122 | "chroma_2 = librosa.feature.chroma_stft(y=audio_2, sr=Fs, n_fft=N, hop_length=H, norm=2.0)\n", 123 | "plot_chromagram(chroma_2[:, :30 * feature_rate], Fs=feature_rate, title='Chroma representation for version 2', figsize=figsize)\n", 124 | "plt.show()" 125 | ] 126 | }, 127 | { 128 | "cell_type": "markdown", 129 | "metadata": {}, 130 | "source": [ 131 | "## Aligning chroma representations using full DTW\n", 132 | "\n", 133 | "The chroma feature sequences in the last cell can be used for time warping. As both versions last around five minutes, an alignment can still be computed in reasonable time using classical, full DTW. In the next cell we use the SyncToolbox implementation of DTW to do this. Each feature sequence consists of around 7000 frames, meaning that the matrices computed during full DTW become quite huge - around 14 million entries each! - leading to high memory consumption." 134 | ] 135 | }, 136 | { 137 | "cell_type": "code", 138 | "execution_count": null, 139 | "metadata": { 140 | "pycharm": { 141 | "name": "#%%\n" 142 | } 143 | }, 144 | "outputs": [], 145 | "source": [ 146 | "C = cosine_distance(chroma_1, chroma_2)\n", 147 | "_, _, wp_full = compute_warping_path(C=C)\n", 148 | "# Equivalently, full DTW may be computed using librosa via:\n", 149 | "# _, wp_librosa = librosa.sequence.dtw(C=C)\n", 150 | "\n", 151 | "plot_matrix_with_points(C, wp_full.T, linestyle='-', marker='', aspect='equal',\n", 152 | " title='Cost matrix and warping path computed using full DTW',\n", 153 | " xlabel='Version 2 (frames)', ylabel='Version 1 (frames)', figsize=(9, 5))\n", 154 | "plt.show()" 155 | ] 156 | }, 157 | { 158 | "cell_type": "markdown", 159 | "metadata": {}, 160 | "source": [ 161 | "## Aligning chroma representations using SyncToolbox (MrMsDTW)\n", 162 | "\n", 163 | "We now compute an alignment between the two versions using MrMsDTW. This algorithm has a much lower memory footprint and will also be faster on long feature sequences. For more information, see [2]." 164 | ] 165 | }, 166 | { 167 | "cell_type": "code", 168 | "execution_count": null, 169 | "metadata": { 170 | "pycharm": { 171 | "name": "#%%\n" 172 | } 173 | }, 174 | "outputs": [], 175 | "source": [ 176 | "_ = sync_via_mrmsdtw(f_chroma1=chroma_1,\n", 177 | " f_chroma2=chroma_2,\n", 178 | " input_feature_rate=feature_rate,\n", 179 | " verbose=True)" 180 | ] 181 | }, 182 | { 183 | "cell_type": "markdown", 184 | "metadata": {}, 185 | "source": [ 186 | "## Runtime comparison\n", 187 | "\n", 188 | "We now compare the runtime of both algorithms. During their first call, they may create function caches etc. So, after running the previous cells, we can now test their raw performance." 189 | ] 190 | }, 191 | { 192 | "cell_type": "code", 193 | "execution_count": null, 194 | "metadata": { 195 | "pycharm": { 196 | "name": "#%%\n" 197 | } 198 | }, 199 | "outputs": [], 200 | "source": [ 201 | "start_time = time.time()\n", 202 | "C = cosine_distance(chroma_1, chroma_2)\n", 203 | "compute_warping_path(C=C)\n", 204 | "end_time = time.time()\n", 205 | "print(f'Full DTW took {end_time - start_time}s')\n", 206 | "\n", 207 | "start_time = time.time()\n", 208 | "sync_via_mrmsdtw(f_chroma1=chroma_1,\n", 209 | " f_chroma2=chroma_2,\n", 210 | " input_feature_rate=feature_rate,\n", 211 | " verbose=False)\n", 212 | "end_time = time.time()\n", 213 | "print(f'MrMsDTW took {end_time - start_time}s')\n" 214 | ] 215 | }, 216 | { 217 | "cell_type": "markdown", 218 | "metadata": {}, 219 | "source": [ 220 | "## References\n", 221 | "\n", 222 | "[1] Meinard Müller: Fundamentals of Music Processing – Audio, Analysis, Algorithms, Applications, ISBN: 978-3-319-21944-8, Springer, 2015.\n", 223 | "\n", 224 | "[2] Thomas Prätzlich, Jonathan Driedger, and Meinard Müller: Memory-Restricted Multiscale Dynamic Time Warping,\n", 225 | "In Proceedings of the IEEE International Conference on Acoustics, Speech, and Signal Processing (ICASSP): 569–573, 2016." 226 | ] 227 | } 228 | ], 229 | "metadata": { 230 | "kernelspec": { 231 | "display_name": "Python 3 (ipykernel)", 232 | "language": "python", 233 | "name": "python3" 234 | }, 235 | "language_info": { 236 | "codemirror_mode": { 237 | "name": "ipython", 238 | "version": 3 239 | }, 240 | "file_extension": ".py", 241 | "mimetype": "text/x-python", 242 | "name": "python", 243 | "nbconvert_exporter": "python", 244 | "pygments_lexer": "ipython3", 245 | "version": "3.11.5" 246 | } 247 | }, 248 | "nbformat": 4, 249 | "nbformat_minor": 4 250 | } 251 | -------------------------------------------------------------------------------- /synctoolbox/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meinardmueller/synctoolbox/cf4c471f232d510849b77491720dd19f8441340c/synctoolbox/__init__.py -------------------------------------------------------------------------------- /synctoolbox/dtw/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meinardmueller/synctoolbox/cf4c471f232d510849b77491720dd19f8441340c/synctoolbox/dtw/__init__.py -------------------------------------------------------------------------------- /synctoolbox/dtw/anchor.py: -------------------------------------------------------------------------------- 1 | from numba import jit 2 | import numpy as np 3 | from typing import Tuple 4 | 5 | 6 | def project_alignment_on_a_new_feature_rate(alignment: np.ndarray, 7 | feature_rate_old: int, 8 | feature_rate_new: int, 9 | cost_matrix_size_old: tuple = (), 10 | cost_matrix_size_new: tuple = ()) -> np.ndarray: 11 | """Projects an alignment computed for a cost matrix on a certain 12 | feature resolution on a cost matrix having a different feature 13 | resolution. 14 | 15 | Parameters 16 | ---------- 17 | alignment : np.ndarray [shape=(2, N)] 18 | Alignment matrix 19 | 20 | feature_rate_old : int 21 | Feature rate of the old cost matrix 22 | 23 | feature_rate_new : int 24 | Feature rate of the new cost matrix 25 | 26 | cost_matrix_size_old : tuple 27 | Size of the old cost matrix. Possibly needed to deal with border cases 28 | 29 | cost_matrix_size_new : tuple 30 | Size of the new cost matrix. Possibly needed to deal with border cases 31 | 32 | Returns 33 | ------- 34 | np.ndarray [shape=(2, N)] 35 | Anchor sequence for the new cost matrix 36 | """ 37 | # Project the alignment on the new feature rate 38 | fac = feature_rate_new / feature_rate_old 39 | anchors = np.round(alignment * fac) + 1 40 | 41 | # In case the sizes of the cost matrices are given explicitly and the 42 | # alignment specifies to align the first and last elements, handle this case 43 | # separately since this might cause problems in the general projection 44 | # procedure. 45 | if cost_matrix_size_old is not None and cost_matrix_size_new is not None: 46 | if np.array_equal(alignment[:, 0], np.array([0, 0])): 47 | anchors[:, 0] = np.array([1, 1]) 48 | 49 | if np.array_equal(alignment[:, -1], np.array(cost_matrix_size_old) - 1): 50 | anchors[:, -1] = np.array(cost_matrix_size_new) 51 | 52 | return anchors - 1 53 | 54 | 55 | def derive_anchors_from_projected_alignment(projected_alignment: np.ndarray, 56 | threshold: int) -> np.ndarray: 57 | """Derive anchors from a projected alignment such that the area of the rectangle 58 | defined by two subsequent anchors a1 and a2 is below a given threshold. 59 | 60 | Parameters 61 | ---------- 62 | projected_alignment : np.ndarray [shape=(2, N)] 63 | Projected alignment array 64 | 65 | threshold : int 66 | Maximum area of the constraint rectangle 67 | 68 | Returns 69 | ------- 70 | anchors_res : np.ndarray [shape=(2, M)] 71 | Resulting anchor sequence 72 | """ 73 | L = projected_alignment.shape[1] 74 | 75 | a1 = np.array(projected_alignment[:, 0], copy=True).reshape(-1, 1) 76 | a2 = np.array(projected_alignment[:, -1], copy=True).reshape(-1, 1) 77 | 78 | if __compute_area(a1, a2) <= threshold: 79 | anchors_res = np.concatenate([a1, a2], axis=1) 80 | elif L > 2: 81 | center = int(np.floor(L/2 + 1)) 82 | 83 | a1 = np.array(projected_alignment[:, 0], copy=True).reshape(-1, 1) 84 | a2 = np.array(projected_alignment[:, center - 1], copy=True).reshape(-1, 1) 85 | a3 = np.array(projected_alignment[:, -1], copy=True).reshape(-1, 1) 86 | 87 | if __compute_area(a1, a2) > threshold: 88 | anchors_1 = derive_anchors_from_projected_alignment(projected_alignment[:, 0:center], threshold) 89 | else: 90 | anchors_1 = np.concatenate([a1, a2], axis=1) 91 | 92 | if __compute_area(a2, a3) > threshold: 93 | anchors_2 = derive_anchors_from_projected_alignment(projected_alignment[:, center - 1:], threshold) 94 | else: 95 | anchors_2 = np.concatenate([a2, a3], axis=1) 96 | 97 | anchors_res = np.concatenate([anchors_1, anchors_2[:, 1:]], axis=1) 98 | else: 99 | if __compute_area(a1, a2) > threshold: 100 | print('Only two anchor points are given which do not fulfill the constraint.') 101 | anchors_res = np.concatenate([a1, a2], axis=1) 102 | 103 | return anchors_res 104 | 105 | 106 | def derive_neighboring_anchors(warping_path: np.ndarray, 107 | anchor_indices: np.ndarray) -> Tuple[np.ndarray, np.ndarray]: 108 | """Compute anchor points in the neighborhood of previous anchor points. 109 | 110 | Parameters 111 | ---------- 112 | warping_path : np.ndarray [shape=(2, N)] 113 | Warping path 114 | 115 | anchor_indices : np.ndarray 116 | Indices corresponding to the anchor points in the ``warping_path`` 117 | 118 | Returns 119 | ------- 120 | neighboring_anchors : np.ndarray [shape=(2, N-1)] 121 | Sequence of neighboring anchors 122 | 123 | neighboring_anchor_indices : np.ndarray 124 | Indices into ``warping path`` corresponding to ``neighboring_anchors`` 125 | """ 126 | L = anchor_indices.shape[0] 127 | neighboring_anchor_indices = np.zeros(L-1, dtype=int) 128 | neighboring_anchors = np.zeros((2, L-1), dtype=int) 129 | 130 | for k in range(1, L): 131 | i1 = anchor_indices[k-1] 132 | i2 = anchor_indices[k] 133 | 134 | neighboring_anchor_indices[k-1] = i1 + np.floor((i2 - i1) / 2) 135 | neighboring_anchors[:, k-1] = warping_path[:, neighboring_anchor_indices[k - 1]] 136 | 137 | return neighboring_anchors, neighboring_anchor_indices 138 | 139 | 140 | @jit(nopython=True) 141 | def __compute_area(a: tuple, 142 | b: tuple): 143 | """Computes the area between two points, given as tuples""" 144 | return (b[0] - a[0] + 1) * (b[1] - a[1] + 1) 145 | 146 | 147 | 148 | -------------------------------------------------------------------------------- /synctoolbox/dtw/core.py: -------------------------------------------------------------------------------- 1 | import librosa 2 | from numba import jit 3 | import numpy as np 4 | 5 | 6 | @jit(nopython=True, cache=True) 7 | def __C_to_DE(C: np.ndarray = None, 8 | dn: np.ndarray = np.array([1, 1, 0], np.int64), 9 | dm: np.ndarray = np.array([1, 0, 1], np.int64), 10 | dw: np.ndarray = np.array([1.0, 1.0, 1.0], np.float64), 11 | sub_sequence: bool = False) -> (np.ndarray, np.ndarray): 12 | """This function computes the accumulated cost matrix D and the step index 13 | matrix E. 14 | 15 | Parameters 16 | ---------- 17 | C : np.ndarray (np.float32 / np.float64) [shape=(N, M)] 18 | Cost matrix 19 | 20 | dn : np.ndarray (np.int64) [shape=(1, S)] 21 | Integer array defining valid steps (N direction of C), default: [1, 1, 0] 22 | 23 | dm : np.ndarray (np.int64) [shape=(1, S)] 24 | Integer array defining valid steps (M direction of C), default: [1, 0, 1] 25 | 26 | dw : np.ndarray (np.float64) [shape=(1, S)] 27 | Double array defining the weight of the each step, default: [1.0, 1.0, 1.0] 28 | 29 | sub_sequence : bool 30 | Set `True` for SubSequence DTW, default: False 31 | 32 | Returns 33 | ------- 34 | D : np.ndarray (np.float64) [shape=(N, M)] 35 | Accumulated cost matrix of type double 36 | 37 | E : np.ndarray (np.int64) [shape=(N, M)] 38 | Step index matrix. 39 | E[n, m] holds the index of the step take to determine the value of D[n, m]. 40 | If E[n, m] is zero, no valid step was possible. 41 | NaNs in the cost matrix are preserved, invalid fields in the cost matrix are NaNs. 42 | """ 43 | if C is None: 44 | raise ValueError('C must be a 2D numpy array.') 45 | 46 | N, M = C.shape 47 | S = dn.size 48 | 49 | if S != dm.size or S != dw.size: 50 | raise ValueError('The parameters dn,dm, and dw must be of equal length.') 51 | 52 | # calc bounding box size of steps 53 | sbbn = np.max(dn) 54 | sbbm = np.max(dm) 55 | 56 | # initialize E 57 | E = np.zeros((N, M), np.int64) - 1 58 | 59 | # initialize extended D matrix 60 | D = np.ones((sbbn + N, sbbm + M), np.float64) * np.inf 61 | 62 | if sub_sequence: 63 | for m in range(M): 64 | D[sbbn, sbbm + m] = C[0, m] 65 | else: 66 | D[sbbn, sbbm] = C[0, 0] 67 | 68 | # accumulate 69 | for m in range(sbbm, M + sbbm): 70 | for n in range(sbbn, N + sbbn): 71 | for s in range(S): 72 | cost = D[n - dn[s], m - dm[s]] + C[n - sbbn, m - sbbm] * dw[s] 73 | if cost < D[n, m]: 74 | D[n, m] = cost 75 | E[n - sbbn, m - sbbm] = s 76 | 77 | D = D[sbbn: N + sbbn, sbbm: M + sbbm] 78 | 79 | return D, E 80 | 81 | 82 | @jit(nopython=True, cache=True) 83 | def __E_to_warping_path(E: np.ndarray, 84 | dn: np.ndarray = np.array([1, 1, 0], np.int64), 85 | dm: np.ndarray = np.array([1, 0, 1], np.int64), 86 | sub_sequence: bool = False, 87 | end_index: int = -1) -> np.ndarray: 88 | """This function computes a warping path based on the provided matrix E 89 | and the allowed steps. 90 | 91 | Parameters 92 | ---------- 93 | E : np.ndarray (np.int64) [shape=(N, M)] 94 | Step index matrix 95 | 96 | dn : np.ndarray (np.int64) [shape=(1, S)] 97 | Integer array defining valid steps (N direction of C), default: [1, 1, 0] 98 | 99 | dm : np.ndarray (np.int64) [shape=(1, S)] 100 | Integer array defining valid steps (M direction of C), default: [1, 0, 1] 101 | 102 | sub_sequence : bool 103 | Set `True` for SubSequence DTW, default: False 104 | 105 | end_index : int 106 | In case of SubSequence DTW 107 | 108 | Returns 109 | ------- 110 | warping_path : np.ndarray (np.int64) [shape=(2, M)] 111 | Resulting optimal warping path 112 | """ 113 | N, M = E.shape 114 | 115 | if not sub_sequence and end_index == -1: 116 | end_index = M - 1 117 | 118 | m = end_index 119 | n = N - 1 120 | 121 | warping_path = np.zeros((2, n + m + 1)) 122 | 123 | index = 0 124 | 125 | def _loop(m, n, index): 126 | warping_path[:, index] = np.array([n, m]) 127 | step_index = E[n, m] 128 | m -= dm[step_index] 129 | n -= dn[step_index] 130 | index += 1 131 | return m, n, index 132 | 133 | if sub_sequence: 134 | while n > 0: 135 | m, n, index = _loop(m, n, index) 136 | else: 137 | while m > 0 or n > 0: 138 | m, n, index = _loop(m, n, index) 139 | 140 | warping_path[:, index] = np.array([n, m]) 141 | warping_path = warping_path[:, index::-1] 142 | 143 | return warping_path 144 | 145 | 146 | def compute_warping_path(C: np.ndarray, 147 | step_sizes: np.ndarray = np.array([[1, 0], [0, 1], [1, 1]], np.int64), 148 | step_weights: np.ndarray = np.array([1.0, 1.0, 1.0], np.float64), 149 | implementation: str = 'synctoolbox'): 150 | """Applies DTW on cost matrix C. 151 | 152 | Parameters 153 | ---------- 154 | C : np.ndarray (np.float32 / np.float64) [shape=(N, M)] 155 | Cost matrix 156 | 157 | step_sizes : np.ndarray (np.int64) [shape=(2, S)] 158 | Array of step sizes 159 | 160 | step_weights : np.ndarray (np.float64) [shape=(2, S)] 161 | Array of step weights 162 | 163 | implementation: str 164 | Choose among ``synctoolbox`` and ``librosa``. (default: ``synctoolbox``) 165 | 166 | Returns 167 | ------- 168 | D : np.ndarray (np.float64) [shape=(N, M)] 169 | Accumulated cost matrix 170 | 171 | E : np.ndarray (np.int64) [shape=(N, M)] 172 | Step index matrix 173 | 174 | wp : np.ndarray (np.int64) [shape=(2, M)] 175 | Warping path 176 | """ 177 | if implementation == 'librosa': 178 | D, wp, E = librosa.sequence.dtw(C=C, 179 | step_sizes_sigma=step_sizes, 180 | weights_add=np.array([0, 0, 0]), 181 | weights_mul=step_weights, 182 | return_steps=True, 183 | subseq=False) 184 | wp = wp[::-1].T 185 | 186 | elif implementation == 'synctoolbox': 187 | dn = step_sizes[:, 0] 188 | dm = step_sizes[:, 1] 189 | 190 | D, E = __C_to_DE(C, 191 | dn=dn, 192 | dm=dm, 193 | dw=step_weights, 194 | sub_sequence=False) 195 | 196 | wp = __E_to_warping_path(E=E, 197 | dn=dn, 198 | dm=dm, 199 | sub_sequence=False) 200 | 201 | else: 202 | raise NotImplementedError(f'No implementation found called {implementation}') 203 | 204 | if np.abs(C[0,0] - 1.29213666) < 0.000001: 205 | pass 206 | print(wp) 207 | 208 | if (wp < 0).any(): 209 | print(f"Shape of current warping path: {wp.shape}") # Debugging output 210 | neg_indices = np.where(wp < 0) # Get row and column indices 211 | neg_values = wp[neg_indices] # Get corresponding negative values 212 | 213 | # Convert indices to a list of tuples (row, col) 214 | neg_indices_list = list(zip(neg_indices[0], neg_indices[1])) 215 | 216 | raise ValueError( 217 | f'Warping path must be non-negative. ' 218 | f'Found negative values at indices {neg_indices_list}: {neg_values.tolist()}. ' 219 | f'\nConsider using another set of step sizes.' 220 | ) 221 | 222 | if (np.diff(wp) < 0).any(): 223 | raise ValueError('Warping path must be monotonically increasing. ' 224 | 'Consider using another set of step sizes.') 225 | 226 | return D, E, wp 227 | 228 | -------------------------------------------------------------------------------- /synctoolbox/dtw/cost.py: -------------------------------------------------------------------------------- 1 | from numba import jit 2 | import numpy as np 3 | from sklearn.metrics.pairwise import euclidean_distances 4 | 5 | 6 | #@jit(nopython=True) 7 | def cosine_distance(f1, f2, cos_meas_max=2.0, cos_meas_min=1.0): 8 | """For all pairs of vectors f1' and f2' in f1 and f2, computes 1 - (f1.f2), 9 | where '.' is the dot product, and rescales the results to lie in the 10 | range [cos_meas_min, cos_meas_max]. 11 | Corresponds to regular cosine distance if f1' and f2' are normalized and 12 | cos_meas_min==0.0 and cos_meas_max==1.0.""" 13 | return (1 - f1.T @ f2) * (cos_meas_max - cos_meas_min) + cos_meas_min 14 | 15 | 16 | 17 | #@jit(nopython=True) 18 | def euclidean_distance(f1, f2, l2_meas_max=1.0, l2_meas_min=0.0): 19 | """Computes euclidean distances between the vectors in f1 and f2, and 20 | rescales the results to lie in the range [cos_meas_min, cos_meas_max].""" 21 | 22 | S1 = euclidean_distances(f1.T, f2.T) 23 | 24 | return S1 * (l2_meas_max - l2_meas_min) + l2_meas_min 25 | 26 | 27 | def compute_high_res_cost_matrix(f_chroma1: np.ndarray, 28 | f_chroma2: np.ndarray, 29 | f_onset1: np.ndarray, 30 | f_onset2: np.ndarray, 31 | weights: np.ndarray = np.array([1.0, 1.0]), 32 | cos_meas_min: float = 1.0, 33 | cos_meas_max: float = 2.0, 34 | l2_meas_min: float = 0.0, 35 | l2_meas_max: float = 1.0): 36 | """Computes cost matrix of two sequences using two feature matrices 37 | for each sequence. Cosine distance is used for the chroma sequences and 38 | euclidean distance is used for the DLNCO sequences. 39 | 40 | Parameters 41 | ---------- 42 | f_chroma1 : np.ndarray [shape=(12, N)] 43 | Chroma feature matrix of the first sequence (assumed to be normalized). 44 | 45 | f_chroma2 : np.ndarray [shape=(12, M)] 46 | Chroma feature matrix of the second sequence (assumed to be normalized). 47 | 48 | f_onset1 : np.ndarray [shape=(12, N)] 49 | DLNCO feature matrix of the first sequence 50 | 51 | f_onset2 : np.ndarray [shape=(12, M)] 52 | DLNCO feature matrix of the second sequence 53 | 54 | weights : np.ndarray [shape=[2,]] 55 | Weights array for the high-resolution cost computation. 56 | weights[0] * cosine_distance + weights[1] * euclidean_distance 57 | 58 | cos_meas_min : float 59 | Cosine distances are shifted to be at least ``cos_meas_min`` 60 | 61 | cos_meas_max : float 62 | Cosine distances are scaled to be at most ``cos_meas_max`` 63 | 64 | l2_meas_min : float 65 | Euclidean distances are shifted to be at least ``l2_meas_min`` 66 | 67 | l2_meas_max : float 68 | Euclidean distances are scaled to be at most ``l2_meas_max`` 69 | 70 | Returns 71 | ------- 72 | C: np.ndarray [shape=(N, M)] 73 | Cost matrix 74 | """ 75 | cos_dis = cosine_distance(f_chroma1, f_chroma2, cos_meas_min=cos_meas_min, cos_meas_max=cos_meas_max) 76 | euc_dis = euclidean_distance(f_onset1, f_onset2, l2_meas_min=l2_meas_min, l2_meas_max=l2_meas_max) 77 | 78 | return weights[0] * cos_dis + weights[1] * euc_dis 79 | 80 | -------------------------------------------------------------------------------- /synctoolbox/dtw/visualization.py: -------------------------------------------------------------------------------- 1 | import matplotlib 2 | import matplotlib.cm 3 | import matplotlib.patches 4 | import matplotlib.pyplot as plt 5 | import numpy as np 6 | from typing import Tuple, List 7 | 8 | 9 | def sync_visualize_step1(cost_matrices: List, 10 | num_rows: int, 11 | num_cols: int, 12 | anchors: np.ndarray, 13 | wp: np.ndarray) -> Tuple[plt.Figure, plt.Axes]: 14 | 15 | fig, ax = plt.subplots(1, 1, dpi=72) 16 | ax = __visualize_cost_matrices(ax, cost_matrices) 17 | __visualize_constraint_rectangles(anchors[[1, 0], :], 18 | edgecolor='firebrick') 19 | 20 | __visualize_path_in_matrix(ax=ax, 21 | wp=wp, 22 | axisX=np.arange(0, num_rows), 23 | axisY=np.arange(0, num_cols), 24 | path_color='firebrick') 25 | 26 | return fig, ax 27 | 28 | 29 | def sync_visualize_step2(ax: plt.Axes, 30 | cost_matrices: list, 31 | wp_step2: np.ndarray, 32 | wp_step1: np.ndarray, 33 | num_rows_step1: int, 34 | num_cols_step1: int, 35 | anchors_step1: np.ndarray, 36 | neighboring_anchors: np.ndarray, 37 | plot_title: str = ""): 38 | 39 | offset_x = neighboring_anchors[0, 0] - 1 40 | offset_y = neighboring_anchors[1, 0] - 1 41 | ax = __visualize_cost_matrices(ax=ax, 42 | cost_matrices=cost_matrices, 43 | offset_x=offset_x, 44 | offset_y=offset_y) 45 | 46 | __visualize_constraint_rectangles(anchors_step1[[1, 0], :], 47 | edgecolor='firebrick') 48 | 49 | __visualize_path_in_matrix(ax=ax, 50 | wp=wp_step1, 51 | axisX=np.arange(0, num_rows_step1), 52 | axisY=np.arange(0, num_cols_step1), 53 | path_color='firebrick') 54 | 55 | __visualize_constraint_rectangles(neighboring_anchors[[1, 0], :] - 1, 56 | edgecolor='orangered', 57 | linestyle='--') 58 | 59 | __visualize_path_in_matrix(ax=ax, 60 | wp=wp_step2, 61 | axisX=np.arange(0, num_rows_step1), 62 | axisY=np.arange(0, num_cols_step1), 63 | path_color='orangered') 64 | 65 | ax.set_title(plot_title) 66 | ax.set_ylabel("Version 1 (frames)") 67 | ax.set_xlabel("Version 2 (frames)") 68 | 69 | ax = plt.gca() # get the current axes 70 | pcm = None 71 | for pcm in ax.get_children(): 72 | if isinstance(pcm, matplotlib.cm.ScalarMappable): 73 | break 74 | plt.colorbar(pcm, ax=ax) 75 | plt.tight_layout() 76 | plt.show() 77 | 78 | 79 | def __size_dtw_matrices(dtw_matrices: List) -> Tuple[List[np.ndarray], List[np.ndarray]]: 80 | """Gives information about the dimensionality of a DTW matrix 81 | given in form of a list matrix 82 | 83 | Parameters 84 | ---------- 85 | dtw_matrices: list 86 | The DTW matrix (cost matrix or accumulated cost matrix) given in form a list. 87 | 88 | Returns 89 | ------- 90 | axisX_list: list 91 | A list containing a horizontal axis for each of the sub matrices 92 | which specifies the horizontal position of the respective submatrix 93 | in the overall cost matrix. 94 | 95 | axis_y_list: list 96 | A list containing a vertical axis for each of the 97 | sub matrices which specifies the vertical position of the 98 | respective submatrix in the overall cost matrix. 99 | 100 | """ 101 | num_matrices = len(dtw_matrices) 102 | size_list = [dtw_mat.shape for dtw_mat in dtw_matrices] 103 | 104 | axis_x_list = list() 105 | axis_y_list = list() 106 | 107 | x_acc = 0 108 | y_acc = 0 109 | 110 | for i in range(num_matrices): 111 | curr_size_list = size_list[i] 112 | axis_x_list.append(np.arange(x_acc, x_acc + curr_size_list[0])) 113 | axis_y_list.append(np.arange(y_acc, y_acc + curr_size_list[1])) 114 | x_acc += curr_size_list[0] - 1 115 | y_acc += curr_size_list[1] - 1 116 | 117 | return axis_x_list, axis_y_list 118 | 119 | 120 | def __visualize_cost_matrices(ax: plt.Axes, 121 | cost_matrices: list = None, 122 | offset_x: float = 0.0, 123 | offset_y: float = 0.0) -> plt.Axes: 124 | """Visualizes cost matrices 125 | 126 | Parameters 127 | ---------- 128 | ax : axes 129 | The Axes instance to plot on 130 | 131 | cost_matrices : list 132 | List of DTW cost matrices. 133 | 134 | offset_x : float 135 | Offset on the x axis. 136 | 137 | offset_y : float 138 | Offset on the y axis. 139 | 140 | Returns 141 | ------- 142 | ax: axes 143 | The Axes instance to plot on 144 | 145 | """ 146 | x_ax, y_ax = __size_dtw_matrices(dtw_matrices=cost_matrices) 147 | 148 | for i, cur_cost in enumerate(cost_matrices[::-1]): 149 | curr_x_ax = x_ax[i] + offset_x 150 | curr_y_ax = y_ax[i] + offset_y 151 | cur_cost = cost_matrices[i] 152 | ax.imshow(cur_cost, cmap='gray_r', aspect='auto', origin='lower', 153 | extent=[curr_y_ax[0], curr_y_ax[-1], curr_x_ax[0], curr_x_ax[-1]]) 154 | 155 | return ax 156 | 157 | 158 | def __visualize_path_in_matrix(ax, 159 | wp: np.ndarray = None, 160 | axisX: np.ndarray = None, 161 | axisY: np.ndarray = None, 162 | path_color: str = 'r'): 163 | """Plots a warping path on top of a given matrix. The matrix is 164 | usually an accumulated cost matrix. 165 | 166 | Parameters 167 | ---------- 168 | ax : axes 169 | The Axes instance to plot on 170 | 171 | wp : np.ndarray 172 | Warping path 173 | 174 | axisX : np.ndarray 175 | Array of X axis 176 | 177 | axisY : np.ndarray 178 | Array of Y axis 179 | 180 | path_color : str 181 | Color of the warping path to be plotted. (default: r) 182 | """ 183 | assert axisX is not None and isinstance(axisX, np.ndarray), 'axisX must be a numpy array!' 184 | assert axisY is not None and isinstance(axisY, np.ndarray), 'axisY must be a numpy array!' 185 | 186 | wp = wp.astype(int) 187 | 188 | ax.plot(axisY[wp[1, :]], axisX[wp[0, :]], '-k', linewidth=5) 189 | ax.plot(axisY[wp[1, :]], axisX[wp[0, :]], color=path_color, linewidth=3) 190 | 191 | 192 | def __visualize_constraint_rectangles(anchors: np.ndarray, 193 | linestyle: str = '-', 194 | edgecolor: str = 'royalblue', 195 | linewidth: float = 1.0): 196 | 197 | for k in range(anchors.shape[1]-1): 198 | a1 = anchors[:, k] 199 | a2 = anchors[:, k + 1] 200 | 201 | # a rectangle is defined by [x y width height] 202 | x = a1[0] 203 | y = a1[1] 204 | w = a2[0] - a1[0] + np.finfo(float).eps 205 | h = a2[1] - a1[1] + np.finfo(float).eps 206 | 207 | rect = matplotlib.patches.Rectangle((x, y), w, h, 208 | linewidth=linewidth, 209 | edgecolor=edgecolor, 210 | linestyle=linestyle, 211 | facecolor='none') 212 | 213 | plt.gca().add_patch(rect) 214 | 215 | 216 | 217 | -------------------------------------------------------------------------------- /synctoolbox/feature/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meinardmueller/synctoolbox/cf4c471f232d510849b77491720dd19f8441340c/synctoolbox/feature/__init__.py -------------------------------------------------------------------------------- /synctoolbox/feature/chroma.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from typing import Tuple 3 | 4 | from synctoolbox.feature.utils import smooth_downsample_feature, normalize_feature 5 | 6 | 7 | def pitch_to_CENS(f_pitch: np.ndarray, 8 | input_feature_rate: float, 9 | win_len_smooth: int = 0, 10 | downsamp_smooth: int = 1, 11 | quant_steps: np.ndarray = np.array([40, 20, 10, 5]) / 100, 12 | quant_weights: np.ndarray = np.array([1, 1, 1, 1]) / 4, 13 | norm_thresh: float = 0.001, 14 | midi_min: int = 21, 15 | midi_max: int = 108, 16 | ) -> Tuple[np.ndarray, float]: 17 | """Generate CENS features from pitch features (CENS: Chroma Energy Normalized Statistics). 18 | 19 | The following is computed: 20 | 21 | * Energy for each chroma band 22 | 23 | * l1-normalization of the chroma vectors 24 | 25 | * Local statistics: 26 | 27 | + Component-wise quantization of the normalized chroma vectors 28 | 29 | + Smoothing and downsampling of the feature sequence 30 | 31 | + l2-normalization of the resulting vectors 32 | 33 | Individual steps of this procedure can be computed with the remaining functions in this module. 34 | 35 | Parameters 36 | ---------- 37 | f_pitch : np.ndarray [shape=(128, N)] 38 | MIDI pitch-based feature representation, obtained e.g. through ``audio_to_pitch_features``. 39 | 40 | input_feature_rate: float 41 | Feature rate of the input pitch features ``f_pitch`` 42 | 43 | win_len_smooth : int 44 | Smoothing window length, default: no smoothing 45 | 46 | downsamp_smooth : int 47 | Downsampling factor, default: no downsampling 48 | 49 | quant_steps : np.ndarray 50 | After l1-normalization, all entries are quantized into bins defined by these boundaries. 51 | The default values correspond to the standard definition of CENS features. 52 | 53 | quant_weights : np.ndarray 54 | The individual quantization bins can be given weights. Default is equal weight for all bins. 55 | 56 | norm_thresh : float 57 | For l1-normalization, chroma entries below this threshold are considered 58 | as noise and set to 0. 59 | For l2-normalization, chroma vectors with norm below this threshold 60 | are replaced with uniform vectors. 61 | 62 | midi_min : int 63 | Minimum MIDI pitch index to consider (default: 21) 64 | 65 | midi_max : int 66 | Maximum MIDI pitch index to consider (default: 108) 67 | 68 | Returns 69 | ------- 70 | f_CENS: np.ndarray 71 | CENS (Chroma Energy Normalized Statistics) features 72 | 73 | CENS_feature_rate: float 74 | Feature rate of the CENS features 75 | """ 76 | # Pitch to chroma features 77 | f_chroma = pitch_to_chroma(f_pitch=f_pitch, 78 | midi_min=midi_min, 79 | midi_max=midi_max) 80 | 81 | # Quantize chroma features 82 | f_chroma_quantized = quantize_chroma(f_chroma=f_chroma, 83 | quant_steps=quant_steps, 84 | quant_weights=quant_weights, 85 | norm_thresh=norm_thresh) 86 | 87 | # Temporal smoothing and downsampling 88 | f_CENS, CENS_feature_rate = quantized_chroma_to_CENS(f_chroma_quantized, 89 | win_len_smooth, 90 | downsamp_smooth, 91 | input_feature_rate, 92 | norm_thresh) 93 | 94 | return f_CENS, CENS_feature_rate 95 | 96 | 97 | def quantized_chroma_to_CENS(f_chroma_quantized: np.ndarray, 98 | win_len_smooth: int, 99 | downsamp_smooth: int, 100 | input_feature_rate: float, 101 | norm_thresh: float = 0.001): 102 | """Smooths, downsamples, and normalizes a chroma sequence obtained e.g. through ``quantize_chroma``. 103 | 104 | Parameters 105 | ---------- 106 | f_chroma_quantized: np.ndarray [shape=(12, N)] 107 | Quantized chroma representation 108 | 109 | win_len_smooth : int 110 | Smoothing window length. Setting this to 0 applies no smoothing. 111 | 112 | downsamp_smooth : int 113 | Downsampling factor. Setting this to 1 applies no downsampling. 114 | 115 | input_feature_rate: float 116 | Feature rate of ``f_chroma_quantized`` 117 | 118 | norm_thresh : float 119 | For the final l2-normalization, chroma vectors with norm below this threshold 120 | are replaced with uniform vectors. 121 | 122 | Returns 123 | ------- 124 | f_CENS: np.ndarray 125 | CENS (Chroma Energy Normalized Statistics) features 126 | 127 | CENS_feature_rate: float 128 | Feature rate of the CENS features 129 | """ 130 | # Temporal smoothing and downsampling 131 | f_chroma_energy_stat, CENSfeature_rate = smooth_downsample_feature(f_feature=f_chroma_quantized, 132 | win_len_smooth=win_len_smooth, 133 | downsamp_smooth=downsamp_smooth, 134 | input_feature_rate=input_feature_rate) 135 | 136 | # Last step: normalize each vector with its L2 norm 137 | f_CENS = normalize_feature(feature=f_chroma_energy_stat, norm_ord=2, threshold=norm_thresh) 138 | 139 | return f_CENS, CENSfeature_rate 140 | 141 | 142 | def quantize_chroma(f_chroma, 143 | quant_steps: np.ndarray = np.array([40, 20, 10, 5]) / 100, 144 | quant_weights: np.ndarray = np.array([1, 1, 1, 1]) / 4, 145 | norm_thresh: float = 0.001) -> np.ndarray: 146 | """Computes thresholded l1-normalization of the chroma vectors and then applies 147 | component-wise quantization of the normalized chroma vectors. 148 | 149 | Parameters 150 | ---------- 151 | f_chroma: np.ndarray [shape=(12, N)] 152 | Chroma representation 153 | 154 | quant_steps : np.ndarray 155 | After l1-normalization, all entries are quantized into bins defined by these boundaries. 156 | The default values correspond to the standard definition of CENS features. 157 | 158 | quant_weights : np.ndarray 159 | The individual quantization bins can be given weights. Default is equal weight for all bins. 160 | 161 | norm_thresh : float 162 | For l1-normalization, chroma entries below this threshold are considered 163 | as noise and set to 0. 164 | 165 | Returns 166 | ------- 167 | f_chroma_quantized: np.ndarray [shape=(12, N)] 168 | Quantized chroma representation 169 | """ 170 | f_chroma_energy_distr = np.zeros((12, f_chroma.shape[1])) 171 | 172 | # Thresholded l1-normalization 173 | for k in range(f_chroma.shape[1]): 174 | if np.sum(f_chroma[:, k] > norm_thresh) > 0: 175 | seg_energy_square = np.sum(f_chroma[:, k]) 176 | f_chroma_energy_distr[:, k] = f_chroma[:, k] / seg_energy_square 177 | 178 | # component-wise quantization of the normalized chroma vectors 179 | f_chroma_quantized = np.zeros((12, f_chroma.shape[1])) 180 | for n in range(quant_steps.size): 181 | f_chroma_quantized += (f_chroma_energy_distr > quant_steps[n]) * quant_weights[n] 182 | 183 | return f_chroma_quantized 184 | 185 | 186 | def pitch_to_chroma(f_pitch: np.ndarray, 187 | midi_min: int = 21, 188 | midi_max: int = 108) -> np.ndarray: 189 | """Aggregate pitch-based features into chroma bands. 190 | 191 | Parameters 192 | ---------- 193 | f_pitch : np.ndarray [shape=(128, N)] 194 | MIDI pitch-based feature representation, obtained e.g. through 195 | ``audio_to_pitch_features``. 196 | 197 | midi_min : int 198 | Minimum MIDI pitch index to consider (default: 21) 199 | 200 | midi_max : int 201 | Maximum MIDI pitch index to consider (default: 108) 202 | 203 | Returns 204 | ------- 205 | f_chroma: np.ndarray [shape=(12, N)] 206 | Rows of 'f_pitch' between ``midi_min`` and ``midi_max``, 207 | aggregated into chroma bands. 208 | """ 209 | f_chroma = np.zeros((12, f_pitch.shape[1])) 210 | for p in range(midi_min, midi_max + 1): 211 | chroma = np.mod(p, 12) 212 | f_chroma[chroma, :] += f_pitch[p, :] 213 | return f_chroma 214 | -------------------------------------------------------------------------------- /synctoolbox/feature/dlnco.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | import numpy as np 3 | from libfmp.b import MultiplePlotsWithColorbar, plot_chromagram, plot_matrix 4 | 5 | 6 | def pitch_onset_features_to_DLNCO(f_peaks: dict, 7 | feature_sequence_length: int, 8 | feature_rate: int = 50, 9 | midi_min: int = 21, 10 | midi_max: int = 108, 11 | log_compression_gamma: float = 10000.0, 12 | chroma_norm_ord: int = 2, 13 | LN_maxfilterlength_seconds: float = 0.8, 14 | LN_maxfilterthresh: float = 0.1, 15 | DLNCO_filtercoef: np.ndarray = np.sqrt(1 / np.arange(1, 11)), 16 | visualize=False) -> np.ndarray: 17 | """Computes decaying locally adaptive normalized chroma onset (DLNCO) features from 18 | a dictionary of peaks obtained e.g. by ``audio_to_pitch_onset_features``. 19 | 20 | Parameters 21 | ---------- 22 | f_peaks : dict 23 | A dictionary of onset peaks 24 | 25 | * Each key corresponds to the midi pitch number 26 | 27 | * Each value f_peaks[midi_pitch] is an array of doubles of size 2xN: 28 | 29 | + First row give the positions of the peaks in milliseconds. 30 | 31 | + Second row contains the corresponding magnitudes of the peaks. 32 | 33 | feature_sequence_length : int 34 | Desired length of the resulting feature sequence. This should be at least as long as the 35 | position of the last peak in ``f_peaks``, but can be longer. 36 | 37 | feature_rate : int 38 | Desired features per second in the output representation 39 | 40 | midi_min : int 41 | Minimum MIDI pitch index (default: 21) 42 | 43 | midi_max : int 44 | Maximum MIDI pitch index (default: 108) 45 | 46 | log_compression_gamma : float 47 | Gamma factor of the log compression applied to peak magnitudes. 48 | 49 | chroma_norm_ord : int 50 | Order of the norm used for chroma onset vectors. 51 | 52 | LN_maxfilterlength_seconds : float 53 | Length of the maximum filter applied for determining local norm of chroma onsets in seconds. 54 | 55 | LN_maxfilterthresh : float 56 | Minimum threshold for normalizing chroma onsets using local norm. 57 | 58 | DLNCO_filtercoef : np.ndarray 59 | Sequence of decay coefficients applied on normalized chroma onsets. 60 | 61 | visualize : bool 62 | Set `True` to visualize chroma onset features (Default: False) 63 | 64 | Returns 65 | ------- 66 | f_DLNCO : np.array [shape=(d_dlnco, N_dlnco)] 67 | Decaying Locally adaptively Normalized Chroma Onset features 68 | """ 69 | f_CO = np.zeros((feature_sequence_length, 12)) 70 | 71 | for midi_pitch in range(midi_min, midi_max + 1): 72 | if midi_pitch not in f_peaks: 73 | continue 74 | time_peaks = f_peaks[midi_pitch][0, :] / 1000 # Now given in seconds 75 | val_peaks = np.log(f_peaks[midi_pitch][1, :] * log_compression_gamma + 1) 76 | ind_chroma = np.mod(midi_pitch, 12) 77 | for k in range(time_peaks.size): 78 | indTime = __matlab_round(time_peaks[k] * feature_rate) # Usage of "round" accounts 79 | # "center window convention" 80 | 81 | f_CO[indTime, ind_chroma] += val_peaks[k] 82 | 83 | # No two ways to normalize F_CO: simply columnwise (f_N) or via local 84 | # normalizing curve (f_LN) 85 | f_N = np.zeros(feature_sequence_length) 86 | 87 | for k in range(feature_sequence_length): 88 | f_N[k] = np.linalg.norm(f_CO[k, :], chroma_norm_ord) 89 | 90 | f_LN = np.array(f_N, copy=True) 91 | f_left = np.array(f_N, copy=True) 92 | f_right = np.array(f_N, copy=True) 93 | LN_maxfilterlength_frames = int(LN_maxfilterlength_seconds * feature_rate) 94 | if LN_maxfilterlength_frames % 2 == 1: 95 | LN_maxfilterlength_frames -= 1 96 | shift = int(np.floor((LN_maxfilterlength_frames) / 2)) 97 | 98 | # TODO improve with scipy.ndimage.maximum_filter 99 | for s in range(shift): 100 | f_left = np.roll(f_left, 1, axis=0) 101 | f_left[0] = 0 102 | f_right = np.roll(f_right, -1, axis=0) 103 | f_right[-1] = 0 104 | f_LN = np.max([f_left, f_LN, f_right], axis=0) 105 | 106 | f_LN = np.maximum(f_LN, LN_maxfilterthresh) 107 | 108 | # Compute f_NC0 (normalizing f_C0 using f_N) 109 | # f_NCO = np.zeros((feature_sequence_length, 12)) 110 | 111 | # Compute f_LNC0 (normalizing f_C0 using f_LN) 112 | f_LNCO = np.zeros((feature_sequence_length, 12)) 113 | for k in range(feature_sequence_length): 114 | # f_NCO[k, :] = f_CO[k, :] / (f_N[k]) #+ eps) 115 | f_LNCO[k, :] = f_CO[k, :] / f_LN[k] 116 | 117 | # Compute f_DLNCO 118 | f_DLNCO = np.zeros((feature_sequence_length, 12)) 119 | 120 | num_coef = DLNCO_filtercoef.size 121 | for p_idx in range(12): 122 | v_shift = np.array(f_LNCO[:, p_idx], copy=True) 123 | v_help = np.zeros((feature_sequence_length, num_coef)) 124 | 125 | for n in range(num_coef): 126 | v_help[:, n] = DLNCO_filtercoef[n] * v_shift 127 | v_shift = np.roll(v_shift, 1) 128 | v_shift[0] = 0 129 | 130 | f_DLNCO[:, p_idx] = np.max(v_help, axis=1) 131 | 132 | # visualization 133 | if visualize: 134 | plot_chromagram(X=f_CO.T, title='CO', colorbar=True, Fs=feature_rate, colorbar_aspect=50, figsize=(9, 3)) 135 | __visualize_LN_features(f_N, f_LN, feature_sequence_length, feature_rate) 136 | plot_chromagram(X=f_LNCO.T, title='LNCO', colorbar=True, Fs=feature_rate, colorbar_aspect=50, figsize=(9, 3)) 137 | plot_chromagram(X=f_DLNCO.T, title='DLNCO', colorbar=True, Fs=feature_rate, colorbar_aspect=50, figsize=(9, 3)) 138 | 139 | f_DLNCO = f_DLNCO.T 140 | 141 | return f_DLNCO 142 | 143 | 144 | def __visualize_LN_features(f_N: np.ndarray, 145 | f_LN: np.ndarray, 146 | num_feature: int, 147 | res: int, 148 | ax: plt.Axes = None): 149 | tight_layouten = ax is None 150 | if ax is None: 151 | fig, ax = plt.subplots(1, 1, figsize=(9, 3), dpi=72) 152 | 153 | t = np.arange(0, num_feature) / res 154 | ax.plot(t, f_N) 155 | 156 | if t[-1] > t[0]: 157 | ax.set_xlim([t[0], t[-1]]) 158 | 159 | ax.plot(t, f_LN, 'r') 160 | ax.set_title('Local Norm of CO') 161 | ax.set_xlabel('Time (seconds)') 162 | ax.set_ylabel('Norm') 163 | if tight_layouten: 164 | plt.tight_layout() 165 | 166 | 167 | def __matlab_round(x: float = None) -> int: 168 | """Workaround to cope the rounding differences between MATLAB and python""" 169 | if x - np.floor(x) < 0.5: 170 | return int(np.floor(x)) 171 | else: 172 | return int(np.ceil(x)) 173 | -------------------------------------------------------------------------------- /synctoolbox/feature/filterbank.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from scipy import signal 3 | 4 | FILTERBANK_SETTINGS = [ 5 | { 6 | 'fs': 22050, 7 | 'midi_min': 96, 8 | 'midi_max': 120 9 | }, 10 | { 11 | 'fs': 4410, 12 | 'midi_min': 60, 13 | 'midi_max': 95 14 | }, 15 | { 16 | 'fs': 882, 17 | 'midi_min': 21, 18 | 'midi_max': 59 19 | } 20 | ] 21 | 22 | FS_PITCH = [22050, 4410, 882] 23 | 24 | 25 | def generate_filterbank(semitone_offset_cents: int = 0, 26 | Q: float = 25.0, 27 | stop: float = 2.0, 28 | Rp: float = 1.0, 29 | Rs: float = 50.0) -> dict: 30 | """Generate a multi-rate filterbank corresponding to different MIDI pitches. 31 | Used to create the pitch features in ``audio_to_pitch_features`` and the pitch 32 | onset features in ``audio_to_pitch_onset_features``.""" 33 | pass_rel = 1 / (2 * Q) 34 | stop_rel = pass_rel * stop 35 | shifted_midi_freq = __get_shifted_midi_frequencies(semitone_offset_cents) 36 | h = dict() 37 | for setting in FILTERBANK_SETTINGS: 38 | nyq = setting['fs'] / 2 39 | for midi_pitch in range(setting['midi_min'], setting['midi_max'] + 1): 40 | h[midi_pitch] = dict() 41 | pitch = shifted_midi_freq[midi_pitch] 42 | Wp = np.array([pitch - pass_rel * pitch, pitch + pass_rel * pitch], np.float64) / nyq 43 | Ws = np.array([pitch - stop_rel * pitch, pitch + stop_rel * pitch], np.float64) / nyq 44 | n, Wn = signal.ellipord(wp=Wp, ws=Ws, gpass=Rp, gstop=Rs) 45 | h[midi_pitch] = signal.ellip(N=n, rp=Rp, rs=Rs, Wn=Wn, output='sos', btype='bandpass') 46 | 47 | return h 48 | 49 | 50 | def __get_shifted_midi_frequencies(semitone_offset_cents: float) -> np.ndarray: 51 | """Returns MIDI center frequencies, shifted by the given offset. 52 | 53 | Parameters 54 | ---------- 55 | semitone_offset_cents : float 56 | Offset in cents 57 | 58 | Returns 59 | ------- 60 | np.ndarray : Shifted MIDI center frequencies 61 | """ 62 | return 2 ** ((np.arange(128) - 69 + semitone_offset_cents / 100) / 12) * 440.0 63 | 64 | 65 | def generate_list_of_downsampled_audio(f_audio: np.ndarray) -> list: 66 | """Generates a multi resolution list of raw audio using downsampling 67 | 68 | Parameters 69 | ---------- 70 | f_audio: np.ndarray 71 | Input audio array (mono) 72 | 73 | Returns 74 | ------- 75 | wav_ds: list 76 | - wav_ds[0]: Same as ``f_audio`` 77 | - wav_ds[1]: ``f_audio`` downsampled by the factor of 5, using a Kaiser window 78 | - wav_ds[2]: ``f_audio`` downsampled by the factor of 25, using a Kaiser window 79 | """ 80 | wav_ds = list() 81 | wav_ds.append(f_audio) 82 | kaiser_win = __design_kaiser_win(up=1, down=5) 83 | wav_ds.append(signal.resample_poly(x=f_audio, up=1, down=5, axis=0, window=kaiser_win)) 84 | wav_ds.append(signal.resample_poly(x=wav_ds[1], up=1, down=5, axis=0, window=kaiser_win)) 85 | 86 | return wav_ds 87 | 88 | 89 | def __design_kaiser_win(up: int, down: int, bta=5.0) -> np.ndarray: 90 | """This function is a workaround to have the same Kaiser window as in the 91 | resample() function in MATLAB.""" 92 | max_rate = max(up, down) 93 | f_c = 1. / max_rate # cutoff of FIR filter (rel. to Nyquist) 94 | half_len = 100 * max_rate # reasonable cutoff for our sinc-like function 95 | h = signal.firwin(2 * half_len + 1, f_c, window=('kaiser', bta)) 96 | return h 97 | 98 | 99 | def get_fs_index(midi_pitch: int) -> int: 100 | """Get the index of the filterbank used for `midi_pitch`""" 101 | if 21 <= midi_pitch <= 59: 102 | return 2 103 | elif 60 <= midi_pitch <= 95: 104 | return 1 105 | elif 96 <= midi_pitch <= 120: 106 | return 0 107 | else: 108 | raise ValueError('Invalid MIDI pitch {midi_pitch}! Choose between 21 <= midi_pitch <= 120.') -------------------------------------------------------------------------------- /synctoolbox/feature/novelty.py: -------------------------------------------------------------------------------- 1 | import librosa 2 | from libfmp.c6 import compute_local_average 3 | import numpy as np 4 | 5 | 6 | def spectral_flux(f_audio: np.ndarray, 7 | Fs: int = 22050, 8 | feature_rate: int = 50, 9 | gamma: float = 10, 10 | M_sec: float = 0.1) -> np.ndarray: 11 | """Generates the spectral-based novelty curve given an audio array. 12 | 13 | This function is based on the FMP notebook on "Spectral-Based Novelty": 14 | https://www.audiolabs-erlangen.de/resources/MIR/FMP/C6/C6S1_NoveltySpectral.html 15 | 16 | Parameters 17 | ---------- 18 | f_audio : np.ndarray 19 | One dimensional f_audio array (mono) 20 | 21 | Fs : float 22 | Sampling rate of ``f_audio`` (in Hz) 23 | 24 | feature_rate : int 25 | Features per second 26 | 27 | gamma : float 28 | Log compression factor 29 | 30 | M_sec: float 31 | Determines size (2M+1) in samples of centric window used for local average 32 | 33 | filter_coeff: np.ndarray 34 | Sequence of decay coefficients applied on normalized chroma onsets. 35 | 36 | Returns 37 | ------- 38 | sf : np.ndarray [shape=(N, )] 39 | Enhanced novelty curve with the subtraction of a local averagenad a temporal decay 40 | """ 41 | 42 | window_size = int(Fs / feature_rate * 2) 43 | hop_size = int(window_size / 2) 44 | 45 | X = librosa.stft(f_audio, 46 | n_fft=window_size, 47 | hop_length=hop_size, 48 | win_length=window_size, 49 | window='hann') 50 | 51 | Y = np.log(1 + gamma * np.abs(X)) 52 | Y_diff = np.diff(Y, n=1) 53 | 54 | # Half wave rectification 55 | Y_diff[Y_diff < 0] = 0 56 | 57 | # Novelty curve 58 | nov = np.sum(Y_diff, axis=0) 59 | 60 | # Compute local average 61 | M = int(np.ceil(M_sec * Fs / hop_size)) 62 | local_average = compute_local_average(nov, M) 63 | 64 | # Subtract the local average from the novelty curve 65 | nov_norm = nov - local_average 66 | nov_norm[nov_norm < 0] = 0 67 | nov_norm = nov_norm / max(nov_norm) 68 | return nov_norm 69 | 70 | 71 | def add_decay(nov_norm: np.ndarray, 72 | filter_coeff: np.ndarray = np.sqrt(1 / np.arange(1, 11))): 73 | # Add a temporal decay to the novelty curve. 74 | v_shift = np.array(nov_norm, copy=True) 75 | v_help = np.zeros((nov_norm.shape[0], 10)) 76 | 77 | for n in range(len(filter_coeff)): 78 | v_help[:, n] = filter_coeff[n] * v_shift 79 | v_shift = np.roll(v_shift, 1) 80 | v_shift[0] = 0 81 | 82 | sf = np.max(v_help, axis=1) 83 | return sf 84 | -------------------------------------------------------------------------------- /synctoolbox/feature/pitch.py: -------------------------------------------------------------------------------- 1 | from libfmp.b import plot_matrix 2 | import numpy as np 3 | from numba import jit 4 | import matplotlib.pyplot as plt 5 | from scipy import signal 6 | 7 | from synctoolbox.feature.filterbank import FS_PITCH, generate_list_of_downsampled_audio, get_fs_index,\ 8 | generate_filterbank 9 | 10 | PITCH_NAME_LABELS = [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 11 | 'C0 ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 12 | 'C1 ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 13 | 'C2 ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 14 | 'C3 ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 15 | 'C4 ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 16 | 'C5 ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 17 | 'C6 ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 18 | 'C7 ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 19 | 'C8 ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 20 | 'C9 '] 21 | 22 | 23 | def audio_to_pitch_features(f_audio: np.ndarray, 24 | Fs: float = 22050, 25 | feature_rate: int = 50, 26 | midi_min: int = 21, 27 | midi_max: int = 108, 28 | tuning_offset: int = 0, 29 | verbose: bool = False, 30 | visualization_title: str = "Pitch features", 31 | visualization_log_gamma: float = 100.0) -> np.ndarray: 32 | """Computes pitch-based features via an IIR filterbank aggregated as STMSP 33 | (short-time mean-square power). The signal is decomposed into subbands that 34 | correspond to MIDI pitches between midi_min and midi_max. 35 | In the output array, each row corresponds to one MIDI pitch. Per convention, 36 | the output has size 128xN. Only the rows between ``midi_min`` and ``midi_max`` 37 | are filled, the rest contains zeros. 38 | 39 | Parameters 40 | ---------- 41 | f_audio : np.ndarray 42 | One dimensional audio array (mono) 43 | 44 | Fs : float 45 | Sampling rate of ``f_audio`` (in Hz) 46 | 47 | feature_rate: int 48 | Features per second 49 | 50 | midi_min : int 51 | Minimum MIDI index (indices below ``midi_min`` are filled with zero in the output) 52 | 53 | midi_max : int 54 | Maximum MIDI index (indices above ``midi_max`` are filled with zero in the output) 55 | 56 | tuning_offset : int 57 | Tuning offset used to shift the filterbank (in cents) 58 | 59 | verbose : bool 60 | Set `True` to activate the visualization of features 61 | 62 | visualization_title : str 63 | Title for the visualization plot. Only relevant if ``verbose`` is True 64 | 65 | visualization_log_gamma : float 66 | Log compression gamma parameter for visualization. (relevant only if ``verbose`` is True. 67 | 68 | Returns 69 | ------- 70 | f_pitch : np.ndarray [shape=(128, N)] 71 | Matrix containing the extracted pitch-based features 72 | """ 73 | if verbose: 74 | print("Generating filterbank...") 75 | h = generate_filterbank(semitone_offset_cents=tuning_offset) 76 | 77 | if verbose: 78 | print("Downsampling signal...") 79 | wav_ds = generate_list_of_downsampled_audio(f_audio) 80 | 81 | # Compute features for all pitches 82 | wav_size = f_audio.size 83 | win_len_STMSP = Fs / feature_rate * 2 84 | step_size = int(win_len_STMSP / 2) 85 | group_delay = np.round(win_len_STMSP / 2) 86 | 87 | # Group delay is adjusted 88 | seg_wav_start = np.concatenate([np.ones(1), np.arange(1, wav_size+1, step_size)]).astype(np.float64) 89 | seg_wav_stop = np.minimum(seg_wav_start + win_len_STMSP, wav_size) 90 | seg_wav_stop[0] = np.minimum(group_delay, wav_size) 91 | seg_wav_num = seg_wav_start.size 92 | f_pitch = np.zeros((128, seg_wav_num)) 93 | 94 | if verbose: 95 | print("Processing midi pitches", midi_min, "to", midi_max) 96 | for midi_pitch in range(midi_min, midi_max + 1): 97 | if verbose and midi_pitch % 10 == 0: 98 | print(midi_pitch, end="") 99 | else: 100 | print(".", end="") 101 | index = get_fs_index(midi_pitch) 102 | f_filtfilt = signal.sosfiltfilt(x=wav_ds[index], sos=h[midi_pitch]) 103 | f_square = f_filtfilt ** 2 104 | 105 | start = np.floor(seg_wav_start / Fs * FS_PITCH[index]).astype(int) # floor due to indexing 106 | stop = np.floor(seg_wav_stop / Fs * FS_PITCH[index]).astype(int) 107 | factor = Fs / FS_PITCH[index] 108 | __window_and_sum(f_pitch, f_square, midi_pitch, seg_wav_num, start, stop, factor) 109 | 110 | if verbose: 111 | print("") 112 | __visualize_pitch(f_pitch, 113 | feature_rate=feature_rate, 114 | plot_title=visualization_title, 115 | log_comp_gamma=visualization_log_gamma) 116 | plt.show() 117 | 118 | return f_pitch 119 | 120 | 121 | @jit(nopython=True) 122 | def __window_and_sum(f_pitch, f_square, midi_pitch, seg_wav_num, start, stop, factor): 123 | for k in range(seg_wav_num): # TODO this is extremely inefficient, can we use better numpy indexing to improve this? np.convolve? 124 | f_pitch[midi_pitch, k] = np.sum(f_square[start[k]:stop[k]]) * factor 125 | 126 | 127 | def __visualize_pitch(f_pitch: np.ndarray, 128 | midi_min: int = 21, 129 | midi_max: int = 108, 130 | feature_rate: float = 0, 131 | use_pitch_name_labels: bool = False, 132 | plot_title: str = "Pitch features", 133 | log_comp_gamma: float = 10): 134 | f_image = f_pitch[midi_min:midi_max + 1, :] 135 | if log_comp_gamma != 0: 136 | f_image = np.log(1 + log_comp_gamma * f_image) 137 | 138 | plot_title += f' log-compressed with $\gamma={log_comp_gamma}$' 139 | 140 | fig, ax, im = plot_matrix(X=f_image, extent=[0, f_pitch.shape[1]/feature_rate, midi_min, midi_max+1], 141 | title=plot_title, ylabel='MIDI Pitch', xlabel="Time (seconds)", figsize=(9, 9), 142 | colorbar_aspect=50) 143 | 144 | pitchscale = np.arange(midi_min, midi_max + 1) 145 | 146 | ax[0].set_yticks(pitchscale[::2]) 147 | if use_pitch_name_labels: 148 | ax[0].set_yticks(np.arange(midi_min, midi_max + 1)) 149 | ax[0].set_yticklabels(PITCH_NAME_LABELS[midi_min-1:midi_max], fontsize=12) 150 | ax[0].set_ylabel("Pitch") 151 | else: 152 | ax[0].set_yticks(pitchscale[::2]) 153 | ax[0].set_yticklabels(pitchscale[::2], fontsize=10) 154 | 155 | -------------------------------------------------------------------------------- /synctoolbox/feature/utils.py: -------------------------------------------------------------------------------- 1 | from libfmp.c3 import compute_freq_distribution, tuning_similarity 2 | from numba import jit 3 | import numpy as np 4 | from scipy import signal 5 | from typing import Tuple 6 | 7 | 8 | def smooth_downsample_feature(f_feature: np.ndarray, 9 | input_feature_rate: float, 10 | win_len_smooth: int = 0, 11 | downsamp_smooth: int = 1) -> Tuple[np.ndarray, float]: 12 | """Temporal smoothing and downsampling of a feature sequence 13 | 14 | Parameters 15 | ---------- 16 | f_feature : np.ndarray 17 | Input feature sequence, size dxN 18 | 19 | input_feature_rate : float 20 | Input feature rate in Hz 21 | 22 | win_len_smooth : int 23 | Smoothing window length. For 0, no smoothing is applied. 24 | 25 | downsamp_smooth : int 26 | Downsampling factor. For 1, no downsampling is applied. 27 | 28 | Returns 29 | ------- 30 | f_feature_stat : np.ndarray 31 | Downsampled & smoothed feature. 32 | 33 | new_feature_rate : float 34 | New feature rate after downsampling 35 | """ 36 | if win_len_smooth != 0 or downsamp_smooth != 1: 37 | # hack to get the same results as on MATLAB 38 | stat_window = np.hanning(win_len_smooth+2)[1:-1] 39 | stat_window /= np.sum(stat_window) 40 | 41 | # upfirdn filters and downsamples each column of f_stat_help 42 | f_feature_stat = signal.upfirdn(h=stat_window, x=f_feature, up=1, down=downsamp_smooth) 43 | seg_num = f_feature.shape[1] 44 | stat_num = int(np.ceil(seg_num / downsamp_smooth)) 45 | cut = int(np.floor((win_len_smooth - 1) / (2 * downsamp_smooth))) 46 | f_feature_stat = f_feature_stat[:, cut: stat_num + cut] 47 | else: 48 | f_feature_stat = f_feature 49 | 50 | new_feature_rate = input_feature_rate / downsamp_smooth 51 | 52 | return f_feature_stat, new_feature_rate 53 | 54 | 55 | @jit(nopython=True) 56 | def normalize_feature(feature: np.ndarray, 57 | norm_ord: int, 58 | threshold: float) -> np.ndarray: 59 | """Normalizes a feature sequence according to the l^norm_ord norm. 60 | 61 | Parameters 62 | ---------- 63 | feature : np.ndarray 64 | Input feature sequence of size d x N 65 | d: dimensionality of feature vectors 66 | N: number of feature vectors (time in frames) 67 | 68 | norm_ord : int 69 | Norm degree 70 | 71 | threshold : float 72 | If the norm falls below threshold for a feature vector, then the 73 | normalized feature vector is set to be the normalized unit vector. 74 | 75 | Returns 76 | ------- 77 | f_normalized : np.ndarray 78 | Normalized feature sequence 79 | """ 80 | # TODO rewrite in vectorized fashion 81 | d, N = feature.shape 82 | f_normalized = np.zeros((d, N)) 83 | 84 | # normalize the vectors according to the l^norm_ord norm 85 | unit_vec = np.ones(d) 86 | unit_vec = unit_vec / np.linalg.norm(unit_vec, norm_ord) 87 | 88 | for k in range(N): 89 | cur_norm = np.linalg.norm(feature[:, k], norm_ord) 90 | 91 | if cur_norm < threshold: 92 | f_normalized[:, k] = unit_vec 93 | else: 94 | f_normalized[:, k] = feature[:, k] / cur_norm 95 | 96 | return f_normalized 97 | 98 | 99 | def estimate_tuning(x: np.ndarray, 100 | Fs: float, 101 | N: int = 16384, 102 | gamma: float = 100, 103 | local: bool = True, 104 | filt: bool = True, 105 | filt_len: int = 101) -> int: 106 | """Compute tuning deviation in cents for an audio signal. Convenience wrapper around 107 | 'compute_freq_distribution' and 'tuning_similarity' from libfmp. 108 | 109 | Parameters 110 | ---------- 111 | x : np.ndarray 112 | Input signal 113 | 114 | Fs : float 115 | Sampling rate 116 | 117 | N : int 118 | Window size 119 | 120 | gamma : float 121 | Constant for logarithmic compression 122 | 123 | local : bool 124 | If `True`, computes STFT and averages; otherwise computes global DFT 125 | 126 | filt : bool 127 | If `True`, applies local frequency averaging and by rectification 128 | 129 | filt_len : int 130 | Filter length for local frequency averaging (length given in cents) 131 | 132 | Returns 133 | ------- 134 | tuning : int 135 | Estimated tuning deviation for ``x`` (in cents) 136 | """ 137 | # TODO supply N in seconds and compute window size in frames via Fs 138 | v, _ = compute_freq_distribution(x, Fs, N, gamma, local, filt, filt_len) 139 | _, _, _, tuning, _ = tuning_similarity(v) 140 | return tuning 141 | 142 | 143 | def shift_chroma_vectors(chroma: np.ndarray, 144 | chroma_shift: int) -> np.ndarray: 145 | """Shift chroma representation by the given number of semitones. 146 | Format is assumed to be 12xN 147 | 148 | Parameters 149 | ---------- 150 | chroma: np.ndarray [shape=(12, N)] 151 | Chroma representation 152 | 153 | chroma_shift: int 154 | Chroma shift 155 | 156 | Returns 157 | ------- 158 | shifted_chroma: np.ndarray 159 | Shifted chroma representation 160 | """ 161 | shifted_chroma = np.roll(chroma, chroma_shift, axis=0) 162 | return shifted_chroma 163 | -------------------------------------------------------------------------------- /tests/data/f_CENS_1.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meinardmueller/synctoolbox/cf4c471f232d510849b77491720dd19f8441340c/tests/data/f_CENS_1.npy -------------------------------------------------------------------------------- /tests/data/f_CENS_2.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meinardmueller/synctoolbox/cf4c471f232d510849b77491720dd19f8441340c/tests/data/f_CENS_2.npy -------------------------------------------------------------------------------- /tests/data/f_DLNCO_1.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meinardmueller/synctoolbox/cf4c471f232d510849b77491720dd19f8441340c/tests/data/f_DLNCO_1.npy -------------------------------------------------------------------------------- /tests/data/f_DLNCO_2.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meinardmueller/synctoolbox/cf4c471f232d510849b77491720dd19f8441340c/tests/data/f_DLNCO_2.npy -------------------------------------------------------------------------------- /tests/data/f_chroma_1.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meinardmueller/synctoolbox/cf4c471f232d510849b77491720dd19f8441340c/tests/data/f_chroma_1.npy -------------------------------------------------------------------------------- /tests/data/f_chroma_2.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meinardmueller/synctoolbox/cf4c471f232d510849b77491720dd19f8441340c/tests/data/f_chroma_2.npy -------------------------------------------------------------------------------- /tests/data/f_chroma_quantized_1.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meinardmueller/synctoolbox/cf4c471f232d510849b77491720dd19f8441340c/tests/data/f_chroma_quantized_1.npy -------------------------------------------------------------------------------- /tests/data/f_chroma_quantized_2.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meinardmueller/synctoolbox/cf4c471f232d510849b77491720dd19f8441340c/tests/data/f_chroma_quantized_2.npy -------------------------------------------------------------------------------- /tests/data/f_filtfilt.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meinardmueller/synctoolbox/cf4c471f232d510849b77491720dd19f8441340c/tests/data/f_filtfilt.npy -------------------------------------------------------------------------------- /tests/data/f_onset.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meinardmueller/synctoolbox/cf4c471f232d510849b77491720dd19f8441340c/tests/data/f_onset.npy -------------------------------------------------------------------------------- /tests/data/f_pitch_1.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meinardmueller/synctoolbox/cf4c471f232d510849b77491720dd19f8441340c/tests/data/f_pitch_1.npy -------------------------------------------------------------------------------- /tests/data/f_pitch_2.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meinardmueller/synctoolbox/cf4c471f232d510849b77491720dd19f8441340c/tests/data/f_pitch_2.npy -------------------------------------------------------------------------------- /tests/data/f_pitch_ann.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meinardmueller/synctoolbox/cf4c471f232d510849b77491720dd19f8441340c/tests/data/f_pitch_ann.npy -------------------------------------------------------------------------------- /tests/data/f_pitch_onset_1.pickle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meinardmueller/synctoolbox/cf4c471f232d510849b77491720dd19f8441340c/tests/data/f_pitch_onset_1.pickle -------------------------------------------------------------------------------- /tests/data/f_pitch_onset_2.pickle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meinardmueller/synctoolbox/cf4c471f232d510849b77491720dd19f8441340c/tests/data/f_pitch_onset_2.pickle -------------------------------------------------------------------------------- /tests/data/f_pitch_onset_ann.pickle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meinardmueller/synctoolbox/cf4c471f232d510849b77491720dd19f8441340c/tests/data/f_pitch_onset_ann.pickle -------------------------------------------------------------------------------- /tests/data/fb.pickle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meinardmueller/synctoolbox/cf4c471f232d510849b77491720dd19f8441340c/tests/data/fb.pickle -------------------------------------------------------------------------------- /tests/data/peaks.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meinardmueller/synctoolbox/cf4c471f232d510849b77491720dd19f8441340c/tests/data/peaks.npy -------------------------------------------------------------------------------- /tests/data/thresh.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meinardmueller/synctoolbox/cf4c471f232d510849b77491720dd19f8441340c/tests/data/thresh.npy -------------------------------------------------------------------------------- /tests/data/wp.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meinardmueller/synctoolbox/cf4c471f232d510849b77491720dd19f8441340c/tests/data/wp.npy -------------------------------------------------------------------------------- /tests/data/wp_high_res.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meinardmueller/synctoolbox/cf4c471f232d510849b77491720dd19f8441340c/tests/data/wp_high_res.npy -------------------------------------------------------------------------------- /tests/data/wp_simple.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meinardmueller/synctoolbox/cf4c471f232d510849b77491720dd19f8441340c/tests/data/wp_simple.npy -------------------------------------------------------------------------------- /tests/test_dtw.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from synctoolbox.dtw.core import compute_warping_path 4 | from synctoolbox.dtw.cost import cosine_distance 5 | from synctoolbox.dtw.mrmsdtw import sync_via_mrmsdtw 6 | from synctoolbox.dtw.utils import compute_optimal_chroma_shift, shift_chroma_vectors 7 | 8 | 9 | def test_optimal_chroma_shift(): 10 | f_CENS_1_gt = np.load('tests/data/f_CENS_1.npy') 11 | f_CENS_2_gt = np.load('tests/data/f_CENS_2.npy') 12 | opt_chroma_shift = compute_optimal_chroma_shift(f_CENS_1_gt, f_CENS_2_gt) 13 | 14 | assert opt_chroma_shift == 11 15 | 16 | 17 | def test_dtw(): 18 | f_chroma_1_gt = np.load('tests/data/f_chroma_1.npy') 19 | f_chroma_2_gt = np.load('tests/data/f_chroma_2.npy') 20 | C_cosine = cosine_distance(f_chroma_1_gt, f_chroma_2_gt) 21 | 22 | wp_gt = np.load('tests/data/wp.npy') 23 | 24 | _, _, wp = compute_warping_path(C_cosine) 25 | 26 | assert np.array_equal(wp_gt, wp) 27 | 28 | 29 | def test_simple_mrmsdtw(): 30 | f_chroma_quantized_1_gt = np.load('tests/data/f_chroma_quantized_1.npy') 31 | f_chroma_quantized_2_gt = np.load('tests/data/f_chroma_quantized_2.npy') 32 | wp_gt = np.load('tests/data/wp_simple.npy') 33 | 34 | f_chroma_quantized_2_gt = shift_chroma_vectors(f_chroma_quantized_2_gt, 11) 35 | 36 | wp = sync_via_mrmsdtw(f_chroma1=f_chroma_quantized_1_gt, 37 | f_chroma2=f_chroma_quantized_2_gt, 38 | input_feature_rate=50, 39 | step_weights=np.array([1.5, 1.5, 2.0]), 40 | threshold_rec=10 ** 6, 41 | verbose=False) 42 | 43 | assert np.array_equal(wp_gt, wp) 44 | 45 | 46 | def test_high_res_mrmsdtw(): 47 | f_chroma_quantized_1_gt = np.load('tests/data/f_chroma_quantized_1.npy') 48 | f_chroma_quantized_2_gt = np.load('tests/data/f_chroma_quantized_2.npy') 49 | f_DLNCO_1_gt = np.load('tests/data/f_DLNCO_1.npy') 50 | f_DLNCO_2_gt = np.load('tests/data/f_DLNCO_2.npy') 51 | wp_gt = np.load('tests/data/wp_high_res.npy') 52 | 53 | f_chroma_quantized_2_gt = shift_chroma_vectors(f_chroma_quantized_2_gt, 11) 54 | f_DLNCO_2_gt = shift_chroma_vectors(f_DLNCO_2_gt, 11) 55 | 56 | wp = sync_via_mrmsdtw(f_chroma1=f_chroma_quantized_1_gt, 57 | f_onset1=f_DLNCO_1_gt, 58 | f_chroma2=f_chroma_quantized_2_gt, 59 | f_onset2=f_DLNCO_2_gt, 60 | input_feature_rate=50, 61 | step_weights=np.array([1.5, 1.5, 2.0]), 62 | threshold_rec=10 ** 6, 63 | verbose=False) 64 | 65 | assert np.array_equal(wp_gt, wp) 66 | -------------------------------------------------------------------------------- /tests/test_features.py: -------------------------------------------------------------------------------- 1 | import librosa 2 | import numpy as np 3 | 4 | from synctoolbox.feature.chroma import pitch_to_chroma, quantize_chroma, quantized_chroma_to_CENS 5 | from synctoolbox.feature.csv_tools import read_csv_to_df, df_to_pitch_features, df_to_pitch_onset_features 6 | from synctoolbox.feature.dlnco import pitch_onset_features_to_DLNCO 7 | from synctoolbox.feature.filterbank import generate_filterbank 8 | from synctoolbox.feature.pitch import audio_to_pitch_features 9 | from synctoolbox.feature.pitch_onset import audio_to_pitch_onset_features, __find_peaks 10 | from synctoolbox.feature.utils import estimate_tuning 11 | from utils import dict_allclose, filterbank_equal, load_dict 12 | 13 | 14 | def test_tuning(): 15 | import libfmp.c2 # TODO: This should be removed after the new release of libfmp 16 | audio_1, _ = librosa.load('data_music/Schubert_D911-03_HU33.wav', sr=22050) 17 | audio_2, _ = librosa.load('data_music/Schubert_D911-03_SC06.wav', sr=22050) 18 | tuning_offset_1 = estimate_tuning(audio_1, 22050) 19 | tuning_offset_2 = estimate_tuning(audio_2, 22050) 20 | 21 | assert tuning_offset_1 == 7 22 | assert tuning_offset_2 == 1 23 | 24 | 25 | def test_chroma_features(): 26 | f_pitch_1_gt = np.load('tests/data/f_pitch_1.npy') 27 | f_pitch_2_gt = np.load('tests/data/f_pitch_2.npy') 28 | f_chroma_1_gt = np.load('tests/data/f_chroma_1.npy') 29 | f_chroma_2_gt = np.load('tests/data/f_chroma_2.npy') 30 | 31 | f_chroma_1 = pitch_to_chroma(f_pitch=f_pitch_1_gt) 32 | f_chroma_2 = pitch_to_chroma(f_pitch=f_pitch_2_gt) 33 | 34 | assert np.allclose(f_chroma_1, f_chroma_1_gt, atol=1e-5) 35 | assert np.allclose(f_chroma_2, f_chroma_2_gt, atol=1e-5) 36 | 37 | 38 | def test_quantized_chroma_features(): 39 | f_chroma_1_gt = np.load('tests/data/f_chroma_1.npy') 40 | f_chroma_2_gt = np.load('tests/data/f_chroma_2.npy') 41 | f_chroma_quantized_1_gt = np.load('tests/data/f_chroma_quantized_1.npy') 42 | f_chroma_quantized_2_gt = np.load('tests/data/f_chroma_quantized_2.npy') 43 | 44 | f_chroma_quantized_1 = quantize_chroma(f_chroma=f_chroma_1_gt) 45 | f_chroma_quantized_2 = quantize_chroma(f_chroma=f_chroma_2_gt) 46 | 47 | assert np.allclose(f_chroma_quantized_1_gt, f_chroma_quantized_1, atol=1e-5) 48 | assert np.allclose(f_chroma_quantized_2_gt, f_chroma_quantized_2, atol=1e-5) 49 | 50 | 51 | def test_CENS_features(): 52 | f_chroma_quantized_1_gt = np.load('tests/data/f_chroma_quantized_1.npy') 53 | f_chroma_quantized_2_gt = np.load('tests/data/f_chroma_quantized_2.npy') 54 | f_CENS_1_gt = np.load('tests/data/f_CENS_1.npy') 55 | f_CENS_2_gt = np.load('tests/data/f_CENS_2.npy') 56 | 57 | f_cens_1 = quantized_chroma_to_CENS(f_chroma_quantized_1_gt, 201, 50, 50)[0] 58 | f_cens_2 = quantized_chroma_to_CENS(f_chroma_quantized_2_gt, 201, 50, 50)[0] 59 | 60 | assert np.allclose(f_cens_1, f_CENS_1_gt, atol=1e-5) 61 | assert np.allclose(f_cens_2, f_CENS_2_gt, atol=1e-5) 62 | 63 | 64 | def test_filterbank(): 65 | fb_gt = load_dict('tests/data/fb.pickle') 66 | fb = generate_filterbank(semitone_offset_cents=7) 67 | filterbank_equal(fb, fb_gt) 68 | 69 | 70 | def test_peak_search(): 71 | f_onset_gt = np.load('tests/data/f_onset.npy') 72 | peaks_gt = np.load('tests/data/peaks.npy') 73 | thresh_gt = np.load('tests/data/thresh.npy') 74 | time_peaks = __find_peaks(W=f_onset_gt, dir=1, abs_thresh=thresh_gt) 75 | assert np.array_equal(peaks_gt, time_peaks) 76 | 77 | 78 | def test_pitch_features(): 79 | audio_1, _ = librosa.load('data_music/Schubert_D911-03_HU33.wav', sr=22050) 80 | audio_2, _ = librosa.load('data_music/Schubert_D911-03_SC06.wav', sr=22050) 81 | f_pitch_1_gt = np.load('tests/data/f_pitch_1.npy') 82 | f_pitch_2_gt = np.load('tests/data/f_pitch_2.npy') 83 | 84 | f_pitch_1 = audio_to_pitch_features(f_audio=audio_1, 85 | Fs=22050, 86 | tuning_offset=1, 87 | feature_rate=50, 88 | verbose=False) 89 | 90 | f_pitch_2 = audio_to_pitch_features(f_audio=audio_2, 91 | Fs=22050, 92 | tuning_offset=7, 93 | feature_rate=50, 94 | verbose=False) 95 | 96 | assert np.allclose(f_pitch_1, f_pitch_1_gt, atol=1e-5) 97 | assert np.allclose(f_pitch_2, f_pitch_2_gt, atol=1e-5) 98 | 99 | 100 | def test_pitch_onset_features(): 101 | audio_1, _ = librosa.load('data_music/Schubert_D911-03_HU33.wav', sr=22050) 102 | audio_2, _ = librosa.load('data_music/Schubert_D911-03_SC06.wav', sr=22050) 103 | f_pitch_onset_1_gt = load_dict('tests/data/f_pitch_onset_1.pickle') 104 | f_pitch_onset_2_gt = load_dict('tests/data/f_pitch_onset_2.pickle') 105 | 106 | f_pitch_onset_1 = audio_to_pitch_onset_features(f_audio=audio_1, 107 | Fs=22050, 108 | tuning_offset=1, 109 | verbose=False) 110 | 111 | f_pitch_onset_2 = audio_to_pitch_onset_features(f_audio=audio_2, 112 | Fs=22050, 113 | tuning_offset=7, 114 | verbose=False) 115 | 116 | dict_allclose(f_pitch_onset_1, f_pitch_onset_1_gt, atol=1e-5) 117 | dict_allclose(f_pitch_onset_2, f_pitch_onset_2_gt, atol=1e-5) 118 | 119 | 120 | def test_DLNCO_features(): 121 | f_pitch_onset_1_gt = load_dict('tests/data/f_pitch_onset_1.pickle') 122 | f_pitch_onset_2_gt = load_dict('tests/data/f_pitch_onset_2.pickle') 123 | f_DLNCO_1_gt = np.load('tests/data/f_DLNCO_1.npy') 124 | f_DLNCO_2_gt = np.load('tests/data/f_DLNCO_2.npy') 125 | 126 | f_DLNCO_1 = pitch_onset_features_to_DLNCO(f_peaks=f_pitch_onset_1_gt, 127 | feature_rate=50, 128 | feature_sequence_length=7518, 129 | visualize=False) 130 | 131 | f_DLNCO_2 = pitch_onset_features_to_DLNCO(f_peaks=f_pitch_onset_2_gt, 132 | feature_rate=50, 133 | feature_sequence_length=6860, 134 | visualize=False) 135 | 136 | assert np.allclose(f_DLNCO_1, f_DLNCO_1_gt, atol=1e-5) 137 | assert np.allclose(f_DLNCO_2, f_DLNCO_2_gt, atol=1e-5) 138 | 139 | 140 | def test_df_to_pitch_features(): 141 | df_annotation = read_csv_to_df('data_csv/Chopin_Op010-03-Measures1-8_MIDI.csv', csv_delimiter=';') 142 | f_pitch_ann_gt = np.load('tests/data/f_pitch_ann.npy') 143 | 144 | f_pitch_ann = df_to_pitch_features(df_annotation, feature_rate=50) 145 | 146 | assert np.allclose(f_pitch_ann, f_pitch_ann_gt, atol=1e-5) 147 | 148 | 149 | def test_df_to_pitch_onset_features(): 150 | df_annotation = read_csv_to_df('data_csv/Chopin_Op010-03-Measures1-8_MIDI.csv', csv_delimiter=';') 151 | f_pitch_onset_ann_gt = load_dict('tests/data/f_pitch_onset_ann.pickle') 152 | 153 | f_pitch_onset_ann = df_to_pitch_onset_features(df_annotation) 154 | 155 | dict_allclose(f_pitch_onset_ann_gt, f_pitch_onset_ann, atol=1e-5) 156 | 157 | 158 | 159 | -------------------------------------------------------------------------------- /tests/utils.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pickle 3 | 4 | 5 | def load_dict(filepath): 6 | """Loads dictionary from a .pickle file. 7 | 8 | Parameters 9 | ---------- 10 | filepath : str 11 | Filepath to the .pickle file 12 | 13 | Returns 14 | ------- 15 | Dictionary 16 | """ 17 | with open(filepath, 'rb') as byte_stream: 18 | return pickle.load(byte_stream) 19 | 20 | 21 | def dict_allclose(dict1, dict2, atol=1e-5, rtol=1e-5): 22 | """Checks whether the entries of two dictionaries are close to each other.""" 23 | assert dict1.keys() == dict2.keys(), 'The midi indices of two feature arrays are not identical.' 24 | for k in dict1: 25 | assert dict1[k].shape == dict2[k].shape, f'Features have different shapes for the midi index {k}.' \ 26 | f'The first feature array f_1[{k}] has {dict1[k].shape} elements;' \ 27 | f'The second feature array f_2[{k}] has {dict2[k].shape} elements:' 28 | diff = np.abs(dict1[k] - dict2[k]) 29 | idx = diff > atol + rtol * np.abs(dict2[k]) 30 | assert np.allclose(dict1[k].astype(np.float64), 31 | dict2[k].astype(np.float64), atol=atol, rtol=rtol), \ 32 | f'The values in the feature arrays don\'t match for the midi index {k},'\ 33 | f'for {idx.size} elements.' \ 34 | f'diff: {diff[idx]}' \ 35 | f'where: {np.where(idx)[0]}' 36 | 37 | 38 | def filterbank_equal(fb, fb_gt, atol=1e-5): 39 | """Checks whether the entries of two filterbanks are equal to each other.""" 40 | assert fb.keys() == fb_gt.keys(), 'The MIDI indices of two filterbanks are not identical.' 41 | 42 | for k in fb: 43 | assert np.allclose(fb[k], fb_gt[k], atol=atol) 44 | --------------------------------------------------------------------------------