├── .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 | [](https://github.com/meinardmueller/synctoolbox/actions/workflows/test_conda.yml)
2 | [](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: [](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 |
26 |
52 |
53 |
54 |
55 |
56 | SyncToolbox
57 |
58 |
59 |
60 |
61 |
62 |
63 | »
64 | Overview: module code
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
All modules for which code is available
74 |
85 |
86 |
87 |
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 | '' +
152 | '' +
153 | Documentation.gettext("Hide Search Matches") +
154 | "
"
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 |
29 |
65 |
66 |
67 |
68 |
69 | SyncToolbox
70 |
71 |
72 |
73 |
74 |
75 |
76 | »
77 | Feature Extraction (synctoolbox.feature)
78 |
79 | View page source
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
92 |
93 |
94 |
95 |
96 |
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 |
28 |
54 |
55 |
56 |
57 |
58 | SyncToolbox
59 |
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 |
106 |
107 |
108 |
109 |
110 |
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 |
29 |
55 |
56 |
57 |
58 |
59 | SyncToolbox
60 |
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 |
82 |
83 |
144 |
145 |
146 |
147 |
148 |
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 |
29 |
55 |
56 |
57 |
58 |
59 | SyncToolbox
60 |
61 |
62 |
63 |
64 |
65 |
66 | »
67 | Search
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 | Please activate JavaScript to enable the search functionality.
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
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 |
--------------------------------------------------------------------------------