├── .coveragerc ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── docs ├── Makefile ├── _templates │ └── class.rst ├── changes.rst ├── conf.py ├── examples.rst ├── examples │ ├── example_beat.py │ ├── example_beat_output.jams │ ├── example_chord.jams │ ├── example_chord_import.py │ └── example_eval.py ├── index.rst ├── jams.rst ├── jams_structure.rst ├── namespace.rst ├── namespace_structure.rst ├── namespaces │ ├── beat.rst │ ├── chord.rst │ ├── key.rst │ ├── lyrics.rst │ ├── misc.rst │ ├── mood.rst │ ├── onset.rst │ ├── pattern.rst │ ├── pitch.rst │ ├── segment.rst │ ├── tag.rst │ └── tempo.rst ├── quickstart.rst └── requirements.txt ├── jams ├── __init__.py ├── core.py ├── display.py ├── eval.py ├── exceptions.py ├── nsconvert.py ├── schema.py ├── schemata │ ├── jams_schema.json │ ├── namespaces │ │ ├── beat │ │ │ ├── beat.json │ │ │ └── position.json │ │ ├── chord │ │ │ ├── chord.json │ │ │ ├── chord.re │ │ │ ├── harte.json │ │ │ ├── harte.re │ │ │ ├── roman.json │ │ │ └── roman.re │ │ ├── key │ │ │ └── key_mode.json │ │ ├── lyrics │ │ │ ├── bow.json │ │ │ └── lyrics.json │ │ ├── misc │ │ │ ├── blob.json │ │ │ ├── scaper.json │ │ │ └── vector.json │ │ ├── mood │ │ │ └── thayer.json │ │ ├── onset │ │ │ └── onset.json │ │ ├── pattern │ │ │ └── jku.json │ │ ├── pitch │ │ │ ├── class.json │ │ │ ├── contour_hz.json │ │ │ ├── hz.json │ │ │ ├── midi.json │ │ │ ├── note_hz.json │ │ │ └── note_midi.json │ │ ├── segment │ │ │ ├── multi.json │ │ │ ├── open.json │ │ │ ├── salami_function.json │ │ │ ├── salami_lower.json │ │ │ ├── salami_upper.json │ │ │ └── tut.json │ │ ├── tag │ │ │ ├── cal10k.json │ │ │ ├── cal500.json │ │ │ ├── gtzan.json │ │ │ ├── medleydb_instruments.json │ │ │ ├── msd_tagtraum_cd1.json │ │ │ ├── msd_tagtraum_cd2.json │ │ │ ├── open.json │ │ │ ├── tag_audioset.json │ │ │ ├── tag_audioset_genre.json │ │ │ ├── tag_audioset_instruments.json │ │ │ ├── tag_fma_genre.json │ │ │ ├── tag_fma_subgenre.json │ │ │ └── tag_urbansound.json │ │ └── tempo │ │ │ └── tempo.json │ └── validate.py ├── sonify.py ├── util.py └── version.py ├── pyproject.toml ├── scripts ├── jams_to_lab.py └── jams_to_mirex_pattern.py ├── setup.py └── tests ├── fixtures ├── invalid.jams ├── pattern_data.jams ├── schema │ └── testing_uppercase.json ├── transcription_est.jams ├── transcription_ref.jams ├── valid.jams └── valid.jamz ├── test_convert.py ├── test_display.py ├── test_eval.py ├── test_jams.py ├── test_ns.py ├── test_schema.py ├── test_sonify.py └── test_util.py /.coveragerc: -------------------------------------------------------------------------------- 1 | [report] 2 | omit = 3 | */python?.?/* 4 | */site-packages/nose/* 5 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: JSON Annotated Music Specification for Reproducible MIR Research 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | install: 13 | runs-on: ubuntu-latest 14 | strategy: 15 | matrix: 16 | python-version: 17 | - "3.9" 18 | - "3.10" 19 | - "3.11" 20 | - "3.12" 21 | timeout-minutes: 5 22 | steps: 23 | - uses: actions/checkout@v4 24 | - name: Setup Python ${{ matrix.python-version }} 25 | uses: actions/setup-python@v5 26 | with: 27 | python-version: ${{ matrix.python-version }} 28 | - name: Install jams 29 | run: pip install -e .[display,tests] 30 | - name: Run tests 31 | run: | 32 | pytest -v --cov-report term-missing --cov jams 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[co] 2 | 3 | # Packages 4 | *.egg 5 | *.egg-info 6 | dist 7 | build 8 | eggs 9 | parts 10 | bin 11 | var 12 | sdist 13 | develop-eggs 14 | .installed.cfg 15 | lib 16 | share 17 | pyvenv.cfg 18 | 19 | 20 | # Installer logs 21 | pip-log.txt 22 | 23 | # Unit test / coverage reports 24 | .coverage 25 | .tox 26 | 27 | #Translations 28 | *.mo 29 | 30 | #Mr Developer 31 | .mr.developer.cfg 32 | 33 | # OS generated files # 34 | .DS_Store 35 | .DS_Store? 36 | ._* 37 | .Spotlight-V100 38 | .Trashes 39 | ehthumbs.db 40 | Thumbs.db 41 | 42 | # Vim 43 | *.swp 44 | 45 | # Sphinx 46 | docs/_* 47 | docs/generated 48 | .idea/.name 49 | .idea/encodings.xml 50 | .idea/jams.iml 51 | .idea/misc.xml 52 | .idea/modules.xml 53 | .idea/vcs.xml 54 | .idea/workspace.xml 55 | .idea/scopes/scope_settings.xml 56 | .idea/inspectionProfiles/profiles_settings.xml 57 | .idea/inspectionProfiles/Project_Default.xml 58 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | 2 | Contributing code 3 | ================= 4 | 5 | How to contribute 6 | ----------------- 7 | 8 | The preferred way to contribute to jams is to fork the 9 | [main repository](http://github.com/marl/jams/) on 10 | GitHub: 11 | 12 | 1. Fork the [project repository](http://github.com/marl/jams): 13 | click on the 'Fork' button near the top of the page. This creates 14 | a copy of the code under your account on the GitHub server. 15 | 16 | 2. Clone this copy to your local disk: 17 | 18 | $ git clone git@github.com:YourLogin/jams.git 19 | $ cd jams 20 | 21 | 3. Create a branch to hold your changes: 22 | 23 | $ git checkout -b my-feature 24 | 25 | and start making changes. Never work in the ``master`` branch! 26 | 27 | 4. Work on this copy on your computer using Git to do the version 28 | control. When you're done editing, do: 29 | 30 | $ git add modified_files 31 | $ git commit 32 | 33 | to record your changes in Git, then push them to GitHub with: 34 | 35 | $ git push -u origin my-feature 36 | 37 | Finally, go to the web page of the your fork of the jams repo, 38 | and click 'Pull request' to send your changes to the maintainers for 39 | review. This will send an email to the committers. 40 | 41 | (If any of the above seems like magic to you, then look up the 42 | [Git documentation](http://git-scm.com/documentation) on the web.) 43 | 44 | It is recommended to check that your contribution complies with the 45 | following rules before submitting a pull request: 46 | 47 | - All public methods should have informative docstrings with sample 48 | usage presented. 49 | 50 | You can also check for common programming errors with the following 51 | tools: 52 | 53 | - Code with good unittest coverage (at least 80%), check with: 54 | 55 | $ pip install nose coverage 56 | $ nosetests --with-coverage --cover-package=jams -w jams/tests/ 57 | 58 | - No pyflakes warnings, check with: 59 | 60 | $ pip install pyflakes 61 | $ pyflakes path/to/module.py 62 | 63 | - No PEP8 warnings, check with: 64 | 65 | $ pip install pep8 66 | $ pep8 path/to/module.py 67 | 68 | - AutoPEP8 can help you fix some of the easy redundant errors: 69 | 70 | $ pip install autopep8 71 | $ autopep8 path/to/pep8.py 72 | 73 | 74 | Documentation 75 | ------------- 76 | 77 | You can edit the documentation using any text editor and then generate 78 | the HTML output by typing ``make html`` from the docs/ directory. 79 | The resulting HTML files will be placed in _build/html/ and are viewable 80 | in a web browser. See the README file in the doc/ directory for more information. 81 | 82 | For building the documentation, you will need 83 | [sphinx](http://sphinx.pocoo.org/), 84 | [matplotlib](http://matplotlib.sourceforge.net/), and [scikit-learn](http://scikit-learn.org/). 85 | 86 | 87 | Note 88 | ---- 89 | This document was gleefully borrowed from [scikit-learn](http://scikit-learn.org/). 90 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | ISC License 2 | 3 | Copyright (c) 2015, MARL 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted, provided that the above 7 | copyright notice and this permission notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | jams 2 | ==== 3 | [![PyPI](https://img.shields.io/pypi/v/jams.svg)](https://pypi.python.org/pypi/jams) 4 | [![License](https://img.shields.io/pypi/l/jams.svg)](https://github.com/marl/jams/blob/master/LICENSE.md) 5 | [![Build Status](https://travis-ci.org/marl/jams.svg?branch=master)](https://travis-ci.org/marl/jams) 6 | [![Coverage Status](https://coveralls.io/repos/marl/jams/badge.svg?branch=master)](https://coveralls.io/r/marl/jams?branch=master) 7 | [![Dependency Status](https://dependencyci.com/github/marl/jams/badge)](https://dependencyci.com/github/marl/jams) 8 | 9 | A JSON Annotated Music Specification for Reproducible MIR Research. 10 | 11 | Please, refer to [documentation](http://jams.readthedocs.io/en/stable/) for a comprehensive 12 | description of JAMS. 13 | 14 | What 15 | ---- 16 | JAMS is a JSON-based music annotation format. 17 | 18 | We provide: 19 | * A formal JSON schema for generic annotations 20 | * The ability to store multiple annotations per file 21 | * Schema definitions for a wide range of annotation types (beats, chords, segments, tags, etc.) 22 | * Error detection and validation for annotations 23 | * A translation layer to interface with [mir eval](https://craffel.github.io/mir_eval) 24 | for evaluating annotations 25 | 26 | Why 27 | ---- 28 | Music annotations are traditionally provided as plain-text files employing 29 | simple formatting schema (comma or tab separated) when possible. However, as 30 | the field of MIR has continued to evolve, such annotations have become 31 | increasingly complex, and more often custom conventions are employed to 32 | represent this information. And, as a result, these custom conventions can be 33 | unwieldy and non-trivial to parse and use. 34 | 35 | Therefore, JAMS provides a simple, structured, and sustainable approach to 36 | representing rich information in a human-readable, language agnostic format. 37 | Importantly, JAMS supports the following use-cases: 38 | * multiple types annotations 39 | * multiple annotations for a given task 40 | * rich file level and annotation level metadata 41 | 42 | How 43 | ---- 44 | This library is offered as a proof-of-concept, demonstrating the promise of a 45 | JSON-based schema to meet the needs of the MIR community. To install, clone the 46 | repository into a working directory and proceed thusly. 47 | 48 | The full documentation can be found [here](http://jams.readthedocs.io/en/stable/). 49 | 50 | Who 51 | ---- 52 | To date, the initial JAMS effort has evolved out of internal needs at MARL@NYU, 53 | with some great feedback from our friends at LabROSA. 54 | 55 | If you want to get involved, do let us know! 56 | 57 | Details 58 | ------- 59 | JAMS is proposed in the following publication: 60 | 61 | [1] Eric J. Humphrey, Justin Salamon, Oriol Nieto, Jon Forsyth, Rachel M. Bittner, 62 | and Juan P. Bello, "[JAMS: A JSON Annotated Music Specification for Reproducible 63 | MIR Research](http://marl.smusic.nyu.edu/papers/humphrey_jams_ismir2014.pdf)", 64 | Proceedings of the 15th International Conference on Music Information Retrieval, 65 | 2014. 66 | 67 | The JAMS schema and data representation used in the API were overhauled significantly between versions 0.1 (initial proposal) and 0.2 (overhauled), see the following technical report for details: 68 | 69 | [2] B. McFee, E. J. Humphrey, O. Nieto, J. Salamon, R. Bittner, J. Forsyth, J. P. Bello, "[Pump Up The JAMS: V0.2 And Beyond](http://www.justinsalamon.com/uploads/4/3/9/4/4394963/mcfee_jams_ismir_lbd2015.pdf)", Technical report, October 2015. 70 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " devhelp to make HTML files and a Devhelp project" 34 | @echo " epub to make an epub" 35 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 36 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 37 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 38 | @echo " text to make text files" 39 | @echo " man to make manual pages" 40 | @echo " texinfo to make Texinfo files" 41 | @echo " info to make Texinfo files and run them through makeinfo" 42 | @echo " gettext to make PO message catalogs" 43 | @echo " changes to make an overview of all changed/added/deprecated items" 44 | @echo " xml to make Docutils-native XML files" 45 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 46 | @echo " linkcheck to check all external links for integrity" 47 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 48 | 49 | clean: 50 | rm -rf $(BUILDDIR)/* 51 | 52 | html: 53 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 54 | @echo 55 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 56 | 57 | dirhtml: 58 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 59 | @echo 60 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 61 | 62 | singlehtml: 63 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 64 | @echo 65 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 66 | 67 | pickle: 68 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 69 | @echo 70 | @echo "Build finished; now you can process the pickle files." 71 | 72 | json: 73 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 74 | @echo 75 | @echo "Build finished; now you can process the JSON files." 76 | 77 | htmlhelp: 78 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 79 | @echo 80 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 81 | ".hhp project file in $(BUILDDIR)/htmlhelp." 82 | 83 | qthelp: 84 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 85 | @echo 86 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 87 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 88 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/jams.qhcp" 89 | @echo "To view the help file:" 90 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/jams.qhc" 91 | 92 | devhelp: 93 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 94 | @echo 95 | @echo "Build finished." 96 | @echo "To view the help file:" 97 | @echo "# mkdir -p $$HOME/.local/share/devhelp/jams" 98 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/jams" 99 | @echo "# devhelp" 100 | 101 | epub: 102 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 103 | @echo 104 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 105 | 106 | latex: 107 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 108 | @echo 109 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 110 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 111 | "(use \`make latexpdf' here to do that automatically)." 112 | 113 | latexpdf: 114 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 115 | @echo "Running LaTeX files through pdflatex..." 116 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 117 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 118 | 119 | latexpdfja: 120 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 121 | @echo "Running LaTeX files through platex and dvipdfmx..." 122 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 123 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 124 | 125 | text: 126 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 127 | @echo 128 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 129 | 130 | man: 131 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 132 | @echo 133 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 134 | 135 | texinfo: 136 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 137 | @echo 138 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 139 | @echo "Run \`make' in that directory to run these through makeinfo" \ 140 | "(use \`make info' here to do that automatically)." 141 | 142 | info: 143 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 144 | @echo "Running Texinfo files through makeinfo..." 145 | make -C $(BUILDDIR)/texinfo info 146 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 147 | 148 | gettext: 149 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 150 | @echo 151 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 152 | 153 | changes: 154 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 155 | @echo 156 | @echo "The overview file is in $(BUILDDIR)/changes." 157 | 158 | linkcheck: 159 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 160 | @echo 161 | @echo "Link check complete; look for any errors in the above output " \ 162 | "or in $(BUILDDIR)/linkcheck/output.txt." 163 | 164 | doctest: 165 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 166 | @echo "Testing of doctests in the sources finished, look at the " \ 167 | "results in $(BUILDDIR)/doctest/output.txt." 168 | 169 | xml: 170 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 171 | @echo 172 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 173 | 174 | pseudoxml: 175 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 176 | @echo 177 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 178 | -------------------------------------------------------------------------------- /docs/_templates/class.rst: -------------------------------------------------------------------------------- 1 | {{ fullname }} 2 | {{ underline }} 3 | 4 | .. currentmodule:: {{ module }} 5 | 6 | .. autoclass:: {{ objname }} 7 | :members: 8 | :no-inherited-members: 9 | :show-inheritance: 10 | 11 | .. automethod:: __init__ 12 | 13 | {% block attributes %} 14 | {% if attributes %} 15 | .. rubric:: Attributes 16 | 17 | .. autosummary:: 18 | {% for item in attributes %} 19 | ~{{ name }}.{{ item }} 20 | {%- endfor %} 21 | {% endif %} 22 | {% endblock %} 23 | 24 | {% block methods %} 25 | .. rubric:: Methods 26 | 27 | .. autosummary:: 28 | {% for item in methods %} 29 | ~{{ name }}.{{ item }} 30 | {%- endfor %} 31 | {% endblock %} 32 | 33 | -------------------------------------------------------------------------------- /docs/changes.rst: -------------------------------------------------------------------------------- 1 | Changes 2 | ======= 3 | 4 | v0.3.4 5 | ------ 6 | 7 | - Added support for jsonschema version 3.0 (`PR #202 8 | `_) 9 | 10 | - Added converter script `jams_to_mirex_pattern.py` (`PR #194 11 | `_) 12 | 13 | v0.3.3 14 | ------ 15 | 16 | - Pinned jsonschema dependency to version 2.6 (`PR #200 17 | `_) 18 | 19 | v0.3.2 20 | ------ 21 | 22 | - Added schemata for ``tag_urbansound`` and ``scaper`` (`PR #191 `_) 23 | - Fixed a timing bug in ``Annotation.slice`` (`PR #189 `_) 24 | - Added ``display`` mapping for ``tag_open`` namespaces (`PR #188 `_) 25 | - Updated `sortedcontainers` dependency to avoid deprecations (`PR #187 `_) 26 | 27 | v0.3.1 28 | ------ 29 | 30 | - Improved documentation (`PR #176 `_) 31 | - Added ``Annotation.to_samples`` (`PR #173 `_) 32 | - Added schemata for FMA genre tags (`PR #172 `_) 33 | - Accelerated validation (`PR #170 `_) 34 | - Added schemata for AudioSet tags (`PR #168 `_) 35 | - Added ``jams.list_namespaces()`` (`PR #166 `_) 36 | 37 | v0.3.0 38 | ------ 39 | 40 | - Removed the `JamsFrame` class and replaced the underlying observation storage data 41 | structure (`PR #149 `_). 42 | 43 | - `import_lab` now returns only an `Annotation` and does not construct a `JAMS` object 44 | (`PR #154 `_) 45 | 46 | - Accelerated pitch contour sonification 47 | (`PR #155 `_) 48 | 49 | - Migrated all tests from nosetest to py.test 50 | (`PR #157 `_) 51 | 52 | - Improved `repr()` and added HTML rendering for JAMS objects in notebooks 53 | (`PR #158 `_) 54 | 55 | - Fixed a JSON serialization bug with numpy datatypes 56 | (`PR #160 `_) 57 | 58 | v0.2.3 59 | ------ 60 | 61 | - Deprecated the `JamsFrame` class 62 | (`PR #153 `_): 63 | 64 | - Moved `JamsFrame.to_interval_values()` to `Annotation.to_interval_values()` 65 | 66 | - Any code that uses `pandas.DataFrame` methods on `Annotation.data` will cease to work 67 | starting in 0.3.0. 68 | 69 | - Forward compatibility with 0.3.0 70 | (`PR #153 `_): 71 | 72 | - Added the `jams.Observation` type 73 | 74 | - Added iteration support to `Annotation` objects 75 | 76 | - added type safety check in regexp search (`PR #146 `_). 77 | - added support for `pandas=0.20` (`PR #150 `_). 78 | 79 | v0.2.2 80 | ------ 81 | - added ``__contains__`` method to ``JObject`` 82 | (`PR #139 `_). 83 | - Implemented ``JAMS.trim()`` method 84 | (`PR #136 `_). 85 | - Updates to the SALAMI tag namespaces 86 | (`PR #134 `_). 87 | - added `infer_duration` flag to ``import_lab`` 88 | (`PR #125 `_). 89 | - namespace conversion validates input 90 | (`PR #123 `_). 91 | - Refactored the ``pitch`` namespaces 92 | (`PR #121 `_). 93 | - Fancy indexing for annotation arrays 94 | (`PR #120 `_). 95 | - ``jams.schema.values`` function to access enumerated types 96 | (`PR #119 `_). 97 | - ``jams.display`` submodule 98 | (`PR #115 `_). 99 | - support for `mir_eval >= 0.3` 100 | (`PR #106 `_). 101 | - Automatic conversion between namespaces 102 | (`PR #105 `_). 103 | - Fixed a type error in ``jams_to_lab`` 104 | (`PR #94 `_). 105 | - ``jams.sonify`` module for sonification 106 | (`PR #91 `_). 107 | 108 | v0.2.1 109 | ------ 110 | New features 111 | - ``eval`` support for hierarchical segmentation via the ``multi_segment`` namespace 112 | (`PR #79 `_). 113 | - Local namespace management 114 | (`PR #75 `_). 115 | - Python 3.5 support 116 | (`PR #73 `_). 117 | - ``jams.search()`` now allows matching objects by equality 118 | (`PR #71 `_). 119 | - ``multi_segment`` namespace for multi-level structural segmentations. 120 | (`PR #69 `_). 121 | - ``vector`` namespace for numerical vector data 122 | (`PR #64 `_). 123 | - ``blob`` namespace for unstructured, time-keyed observation data 124 | (`PR #63 `_). 125 | - ``tag_msd_tagtraum_cd1`` and ``tag_msd_tagtraum_cd2`` namespaces for genre tags 126 | (`PR #83 `_). 127 | 128 | Schema changes 129 | - ``Annotation`` objects now have ``time`` and ``duration`` fields which encode the 130 | interval over which the annotation is valid. 131 | (`PR #67 `_). 132 | 133 | Bug fixes 134 | - Appending data to ``Annotation`` or ``JamsFrame`` objects now fails if ``time`` or ``duration`` are 135 | ill-specified. 136 | (`PR #87 `_). 137 | 138 | -------------------------------------------------------------------------------- /docs/examples.rst: -------------------------------------------------------------------------------- 1 | ************* 2 | Example usage 3 | ************* 4 | 5 | Storing annotations 6 | =================== 7 | 8 | This section demonstrates a complete use-case of JAMS for storing estimated annotations. 9 | The example uses `librosa `_ to estimate global tempo 10 | and beat timings. 11 | 12 | example_beat.py 13 | --------------- 14 | 15 | The following script loads the librosa example audio clip, estimates the track duration, 16 | tempo, and beat timings, and constructs a JAMS object to store the estimations. 17 | 18 | .. literalinclude:: examples/example_beat.py 19 | :linenos: 20 | 21 | example_beat_output.jams 22 | ------------------------ 23 | The above script generates the following JAMS object. 24 | 25 | .. literalinclude:: examples/example_beat_output.jams 26 | :linenos: 27 | :language: javascript 28 | 29 | Evaluating annotations 30 | ====================== 31 | 32 | The following script illustrates how to evaluate one JAMS annotation object against another using the 33 | built-in `eval` submodule to wrap `mir_eval `_. 34 | 35 | Given two jams files, say, `reference.jams` and `estimate.jams`, the script first loads them as objects 36 | (``j_ref`` and ``j_est``, respectively). It then uses the `JAMS.search` method to locate all 37 | annotations of namespace ``"beat"``. If no matching annotations are found, an empty list is returned. 38 | 39 | In this example, we are assuming that each JAMS file contains only a 40 | single annotation of interest, so the first result is taken by indexing the results at 0. (In general, you 41 | may want to use `annotation_metadata` to select a specific annotation from the JAMS object, if multiple are 42 | present.) 43 | 44 | Finally, the two annotations are compared by calling `jams.eval.beat`, which returns an ordered 45 | dictionary of evaluation metrics for the annotations in question. 46 | 47 | example_eval.py 48 | --------------- 49 | 50 | .. literalinclude:: examples/example_eval.py 51 | :linenos: 52 | 53 | 54 | Data conversion 55 | =============== 56 | 57 | JAMS provides some basic functionality to help convert from flat file formats (e.g., CSV or LAB). 58 | 59 | example_chord_import.py 60 | ----------------------- 61 | 62 | .. literalinclude:: examples/example_chord_import.py 63 | :linenos: 64 | 65 | chord_output.jams 66 | ----------------- 67 | 68 | Calling the above script on `01_-_I_Saw_Her_Standing_There.lab 69 | `_ 70 | from `IsoPhonics `_ should produce the following JAMS object: 71 | 72 | .. literalinclude:: examples/example_chord.jams 73 | :linenos: 74 | :language: javascript 75 | 76 | 77 | More examples 78 | ============= 79 | In general, converting a dataset to JAMS format will require a bit more work to ensure that value fields 80 | conform to the specified namespace schema, but the import script above should serve as a simple starting 81 | point. 82 | 83 | For further reference, a separate repository `jams-data `_ has been 84 | created to house conversion scripts for publicly available datasets. 85 | Note that development of converters is a work in progress, so proceed with caution! 86 | -------------------------------------------------------------------------------- /docs/examples/example_beat.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import librosa 4 | import jams 5 | 6 | 7 | def beat_track(infile, outfile): 8 | 9 | # Load the audio file 10 | y, sr = librosa.load(infile) 11 | 12 | # Compute the track duration 13 | track_duration = librosa.get_duration(y=y, sr=sr) 14 | 15 | # Extract tempo and beat estimates 16 | tempo, beat_frames = librosa.beat.beat_track(y=y, sr=sr) 17 | 18 | # Convert beat frames to time 19 | beat_times = librosa.frames_to_time(beat_frames, sr=sr) 20 | 21 | # Construct a new JAMS object and annotation records 22 | jam = jams.JAMS() 23 | 24 | # Store the track duration 25 | jam.file_metadata.duration = track_duration 26 | 27 | beat_a = jams.Annotation(namespace='beat') 28 | beat_a.annotation_metadata = jams.AnnotationMetadata(data_source='librosa beat tracker') 29 | 30 | # Add beat timings to the annotation record. 31 | # The beat namespace does not require value or confidence fields, 32 | # so we can leave those blank. 33 | for t in beat_times: 34 | beat_a.append(time=t, duration=0.0) 35 | 36 | # Store the new annotation in the jam 37 | jam.annotations.append(beat_a) 38 | 39 | # Add tempo estimation to the annotation. 40 | tempo_a = jams.Annotation(namespace='tempo', time=0, duration=track_duration) 41 | tempo_a.annotation_metadata = jams.AnnotationMetadata(data_source='librosa tempo estimator') 42 | 43 | # The tempo estimate is global, so it should start at time=0 and cover the full 44 | # track duration. 45 | # If we had a likelihood score on the estimation, it could be stored in 46 | # `confidence`. Since we have no competing estimates, we'll set it to 1.0. 47 | tempo_a.append(time=0.0, 48 | duration=track_duration, 49 | value=tempo, 50 | confidence=1.0) 51 | 52 | # Store the new annotation in the jam 53 | jam.annotations.append(tempo_a) 54 | 55 | # Save to disk 56 | jam.save(outfile) 57 | 58 | 59 | if __name__ == '__main__': 60 | 61 | infile = librosa.util.example_audio_file() 62 | beat_track(infile, 'output.jams') 63 | -------------------------------------------------------------------------------- /docs/examples/example_chord_import.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import jams 4 | import sys 5 | 6 | 7 | def import_chord_jams(infile, outfile): 8 | 9 | # import_lab returns a new jams object, 10 | # and a handle to the newly created annotation 11 | chords = jams.util.import_lab('chord', infile) 12 | 13 | # Infer the track duration from the end of the last annotation 14 | duration = max([obs.time + obs.duration for obs in chords]) 15 | 16 | chords.time = 0 17 | chords.duration = duration 18 | 19 | # Create a jams object 20 | jam = jams.JAMS() 21 | jam.file_metadata.duration = duration 22 | jam.annotations.append(chords) 23 | 24 | # save to disk 25 | jam.save(outfile) 26 | 27 | 28 | if __name__ == '__main__': 29 | 30 | infile, outfile = sys.argv[1:] 31 | import_chord_jams(infile, outfile) 32 | -------------------------------------------------------------------------------- /docs/examples/example_eval.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import sys 4 | import jams 5 | 6 | from pprint import pprint 7 | 8 | def compare_beats(f_ref, f_est): 9 | 10 | # f_ref contains the reference annotations 11 | j_ref = jams.load(f_ref) 12 | 13 | # f_est contains the estimated annotations 14 | j_est = jams.load(f_est) 15 | 16 | # Get the first reference beats 17 | beat_ref = j_ref.search(namespace='beat')[0] 18 | beat_est = j_est.search(namespace='beat')[0] 19 | 20 | # Get the scores 21 | return jams.eval.beat(beat_ref, beat_est) 22 | 23 | 24 | if __name__ == '__main__': 25 | 26 | f_ref, f_est = sys.argv[1:] 27 | scores = compare_beats(f_ref, f_est) 28 | 29 | # Print them out 30 | pprint(dict(scores)) 31 | 32 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. jams documentation master file, created by 2 | sphinx-quickstart on Mon Dec 8 10:34:40 2014. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | #### 7 | JAMS 8 | #### 9 | A JSON Annotated Music Specification for Reproducible MIR Research. 10 | 11 | JAMS provides: 12 | * A formal JSON schema for generic annotations 13 | * The ability to store multiple annotations per file 14 | * Schema definitions for a wide range of annotation types (beats, chords, segments, tags, etc.) 15 | * Error detection and validation 16 | * A translation layer to interface with `mir_eval `_ for evaluating annotations 17 | 18 | For the most recent information, please refer to `JAMS on github `_. 19 | 20 | .. toctree:: 21 | :maxdepth: 2 22 | 23 | quickstart 24 | jams_structure 25 | namespace_structure 26 | examples 27 | 28 | ************* 29 | API reference 30 | ************* 31 | .. toctree:: 32 | :maxdepth: 2 33 | 34 | jams 35 | namespace 36 | 37 | ********** 38 | Contribute 39 | ********** 40 | - `Issue Tracker `_ 41 | - `Source Code `_ 42 | 43 | 44 | ********* 45 | Changelog 46 | ********* 47 | .. toctree:: 48 | :maxdepth: 2 49 | 50 | changes 51 | 52 | * :ref:`genindex` 53 | 54 | -------------------------------------------------------------------------------- /docs/jams.rst: -------------------------------------------------------------------------------- 1 | .. _jams: 2 | 3 | API Reference 4 | ============= 5 | 6 | .. automodule:: jams.core 7 | .. automodule:: jams.schema 8 | .. automodule:: jams.display 9 | .. automodule:: jams.sonify 10 | .. automodule:: jams.eval 11 | .. automodule:: jams.nsconvert 12 | .. automodule:: jams.util 13 | -------------------------------------------------------------------------------- /docs/jams_structure.rst: -------------------------------------------------------------------------------- 1 | ************** 2 | JAMS Structure 3 | ************** 4 | 5 | This section describes the anatomy of JAMS objects. 6 | 7 | JAMS 8 | ==== 9 | 10 | A JAMS object consists of three basic properties: 11 | * ``file_metadata``, which describes the audio file to which these annotations are attached; 12 | * ``annotations``, a list of Annotation_ objects (described below); and 13 | * ``sandbox``, an unrestricted place to store any additional data. 14 | 15 | 16 | FileMetadata 17 | ============ 18 | The ``file_metadata`` field contains the following properties: 19 | * ``identifiers``: an unstructured ``sandbox``-type object for storing identifier mappings, e.g., MusicBrainz 20 | ID; 21 | * ``artist``, ``title``, ``release`` : meta-data strings for the track in question; 22 | * ``duration`` : non-negative number describing the length (in seconds) of the track; and 23 | * ``jams_version`` : string describing the JAMS version for this file. 24 | 25 | .. _Annotation: 26 | 27 | Annotation 28 | ========== 29 | 30 | Each annotation object contains the following properties: 31 | * ``namespace`` : a string describing the type of this annotation; 32 | * ``data`` : a list of *observations*, each containing: 33 | * ``time`` : non-negative number denoting the time of the observation (in seconds) 34 | * ``duration`` : non-negative number denoting the duration of the observation (in seconds) 35 | * ``value`` : actual annotation (e.g., chord, segment label) 36 | * ``confidence`` : certainty of the annotation 37 | * ``annotation_metadata`` : see Annotation_Metadata_; and 38 | * ``sandbox`` : additional unstructured storage space for this annotation. 39 | * ``time`` : optional non-negative number indicating the beginning point at which this annotation is valid 40 | * ``duration`` : optional non-negative number indicating the duration of the valid portion of this 41 | annotation. 42 | 43 | The permissible contents of the ``value`` and ``confidence`` fields are defined by the ``namespace``. 44 | 45 | The interpretation of the ``time`` and ``duration`` observation fields are as follows: 46 | 47 | - if ``duration > 0``, the observation covers the half-open time interval ``[time, time + duration)``. 48 | - if ``duration == 0``, the observation covers the closed interval ``[time, time]``, that is, the single time instant. 49 | 50 | The first case is the most widely used, and the half-open interval convention eliminates ambiguity of interval membership at the boundaries of adjacent intervals. 51 | 52 | The second case is primarily useful for instantaneous measurements (e.g., beat events) or uniformly sampled values of a temporally continuous signal (e.g., fundamental frequency 53 | curve). 54 | 55 | .. note:: The ``time`` and ``duration`` fields of ``annotation`` are considered optional. If left blank, 56 | the annotation should be assumed to be valid for the entirety of the track. 57 | 58 | 59 | Annotation_Metadata 60 | =================== 61 | The meta-data associated with each annotation describes the process by which the annotation was generated. 62 | The ``annotation_metadata`` property has the following fields: 63 | 64 | * ``corpus``: a string describing a corpus to which this annotation belongs; 65 | * ``version`` : string or number, the version of this annotation; 66 | * ``curator`` : a structured object containing contact information (``name`` and ``email``) for the curator of this data; 67 | * ``annotator`` : a ``sandbox`` object to describe the individual annotator --- which can be a person or a program --- that generated this annotation; 68 | * ``annotation_tools``, ``annotation_rules``, ``validation``: strings to describe the process by which 69 | annotations were collected and pre-processed; and 70 | * ``data_source`` : string describing the type of annotator, e.g., "program", "expert human", 71 | "crowdsource". 72 | 73 | 74 | -------------------------------------------------------------------------------- /docs/namespace.rst: -------------------------------------------------------------------------------- 1 | .. _namespace: 2 | 3 | Namespace definitions 4 | ===================== 5 | 6 | 7 | .. include:: namespaces/beat.rst 8 | 9 | .. include:: namespaces/chord.rst 10 | 11 | .. include:: namespaces/key.rst 12 | 13 | .. include:: namespaces/lyrics.rst 14 | 15 | .. include:: namespaces/mood.rst 16 | 17 | .. include:: namespaces/onset.rst 18 | 19 | .. include:: namespaces/pattern.rst 20 | 21 | .. include:: namespaces/pitch.rst 22 | 23 | .. include:: namespaces/segment.rst 24 | 25 | .. include:: namespaces/tag.rst 26 | 27 | .. include:: namespaces/tempo.rst 28 | 29 | .. include:: namespaces/misc.rst 30 | 31 | -------------------------------------------------------------------------------- /docs/namespace_structure.rst: -------------------------------------------------------------------------------- 1 | *************** 2 | Task namespaces 3 | *************** 4 | In JAMS v0.2.0, the concept of task `namespaces` was introduced. Broadly speaking, a `namespace` 5 | defines the syntax (and some semantics) of a particular type of annotation. 6 | 7 | For example, the `chord` namespace requires that all observed `value` fields are valid strings within a 8 | pre-defined grammar. Similarly, the `tempo` namespace requires that `value` fields be non-negative numbers, 9 | and the `confidence` fields lie within the range `[0, 1]`. 10 | 11 | JAMS ships with 26 pre-defined namespaces, covering a variety of common music informatics tasks. This 12 | collection should not be assumed to be complete, however, and more namespaces may be added in subsequent 13 | versions. Please refer to :ref:`namespace` for a comprehensive description of the existing namespaces. 14 | 15 | 16 | Namespace specification format 17 | ============================== 18 | 19 | In this section, we'll demonstrate how to define a task namespace, using `tempo` as our running example. 20 | Namespaces are defined by JSON objects that contain partial `JSON schema `_ 21 | specifications for the `value` and `confidence` fields of the :ref:`Annotation`, as well as additional meta-data to 22 | describe the namespace and encoding. 23 | 24 | `tempo.json` is reproduced here: 25 | 26 | .. code-block:: json 27 | :linenos: 28 | 29 | {"tempo": 30 | { 31 | "value": { 32 | "type": "number", 33 | "minimum": 0 34 | }, 35 | "confidence": { 36 | "type": "number", 37 | "minimum": 0, 38 | "maximum": 1.0 39 | }, 40 | "dense": false, 41 | "description": "Tempo measurements, in beats per minute (BPM)" 42 | } 43 | } 44 | 45 | The key `"tempo"` at line 1 is the string with which this namespace will be identified in JAMS objects by the 46 | annotation's `namespace` field. This string must be a unique identifier. 47 | 48 | Lines 3--6 specify the valid contents of the `value` field for tempo annotations. In this case, values must 49 | be numeric and non-negative. Any valid JSON schema definition can be substituted here, allowing for 50 | structured observation objects. (See :ref:`pattern_jku ` for an example of this.) 51 | 52 | Similarly, lines 7--11 specify valid contents of the `confidence` field. Most namespaces do not enforce 53 | specific constraints on confidence, so this block is optional. In the case of `tempo`, confidence must be a 54 | numeric value in the range `[0, 1]`. 55 | 56 | Line 12 `dense` is a boolean which specifies whether the annotation should be densely encoded during 57 | serialization or not. There is functionally no difference between dense and sparse encoding, 58 | but dense coding is more space-efficient for high-frequency observations such as melody contours. 59 | 60 | Finally, line 13 contains a brief description of the namespace and corresponding task. 61 | 62 | 63 | Local namespaces 64 | ================ 65 | 66 | The JAMS namespace management architecture is modular and extensible, so it is relatively straightforward 67 | to create a new namespace schema and add it to JAMS at run-time: 68 | 69 | >>> jams.schema.add_namespace('/path/to/my/new/namespace.json') 70 | 71 | Beginning with JAMS 0.2.1, a custom schema directory can be provided by setting the 72 | ``JAMS_SCHEMA_DIR`` environment variable prior to importing ``jams``. This allows local 73 | customizations to be added automatically at run-time without having to manually add each 74 | schema file individually. 75 | -------------------------------------------------------------------------------- /docs/namespaces/beat.rst: -------------------------------------------------------------------------------- 1 | Beat 2 | ---- 3 | 4 | beat 5 | ~~~~ 6 | Beat event markers with optional metrical position. 7 | 8 | ===== ======== ================== ========== 9 | time duration value confidence 10 | ===== ======== ================== ========== 11 | [sec] [sec] [number] or [null] -- 12 | ===== ======== ================== ========== 13 | 14 | Each observation corresponds to a single beat event. 15 | 16 | The ``value`` field can be a number (positive or negative, integer or floating point), 17 | indicating the metrical position within the bar of the observed beat. 18 | 19 | If no metrical position is provided for the annotation, the ``value`` field will be 20 | ``null``. 21 | 22 | *Example* 23 | 24 | ===== ======== ===== ========== 25 | time duration value confidence 26 | ===== ======== ===== ========== 27 | 0.500 0.000 1 null 28 | 1.000 0.000 2 null 29 | 1.500 0.000 3 null 30 | 2.000 0.000 4 null 31 | 2.500 0.000 1 null 32 | ===== ======== ===== ========== 33 | 34 | .. note:: 35 | ``duration`` is typically zero for beat events, but this is not enforced. 36 | 37 | ``confidence`` is an unconstrained field for beat annotations, and may contain 38 | arbitrary data. 39 | 40 | 41 | beat_position 42 | ~~~~~~~~~~~~~ 43 | Beat events with time signature information. 44 | 45 | +-------+----------+--------------+------------+ 46 | | time | duration | value | confidence | 47 | +=======+==========+==============+============+ 48 | | [sec] | [sec] | - position | -- | 49 | | | | - measure | | 50 | | | | - num_beats | | 51 | | | | - beat_units | | 52 | +-------+----------+--------------+------------+ 53 | 54 | Each observation corresponds to a single beat event. 55 | 56 | The ``value`` field is a structure containing the following fields: 57 | 58 | - ``position`` : the position of the beat within the measure. Can be any number greater 59 | than or equal to 1. 60 | - ``measure`` : the index of the measure containing this beat. Can be any non-negative 61 | integer. 62 | - ``num_beats`` : the number of beats per measure : can be any strictly positive 63 | integer. 64 | - ``beat_units`` : the note value for beats in this measure. Must be one of: 65 | ``1, 2, 4, 8, 16, 32, 64, 128, 256``. 66 | 67 | All fields are required for each observation. 68 | 69 | *Example* 70 | 71 | +-------+----------+-----------------+------------+ 72 | | time | duration | value | confidence | 73 | +=======+==========+=================+============+ 74 | | 0.500 | 0.000 | - position: 1 | null | 75 | | | | - measure: 0 | | 76 | | | | - num_beats: 4 | | 77 | | | | - beat_units: 4 | | 78 | +-------+----------+-----------------+------------+ 79 | | 1.000 | 0.000 | - position: 2 | null | 80 | | | | - measure: 0 | | 81 | | | | - num_beats: 4 | | 82 | | | | - beat_units: 4 | | 83 | +-------+----------+-----------------+------------+ 84 | | 1.500 | 0.000 | - position: 3 | null | 85 | | | | - measure: 0 | | 86 | | | | - num_beats: 4 | | 87 | | | | - beat_units: 4 | | 88 | +-------+----------+-----------------+------------+ 89 | | 2.000 | 0.000 | - position: 4 | null | 90 | | | | - measure: 0 | | 91 | | | | - num_beats: 4 | | 92 | | | | - beat_units: 4 | | 93 | +-------+----------+-----------------+------------+ 94 | | 2.500 | 0.000 | - position: 1 | null | 95 | | | | - measure: 1 | | 96 | | | | - num_beats: 4 | | 97 | | | | - beat_units: 4 | | 98 | +-------+----------+-----------------+------------+ 99 | 100 | .. note:: 101 | ``duration`` is typically zero for beat events, but this is not enforced. 102 | 103 | ``confidence`` is an unconstrained field for beat annotations, and may contain 104 | arbitrary data. 105 | 106 | ``position`` should lie in the range ``[1, beat_units]``, but the upper bound is not 107 | enforced at the schema level. 108 | -------------------------------------------------------------------------------- /docs/namespaces/chord.rst: -------------------------------------------------------------------------------- 1 | Chord 2 | ----- 3 | 4 | chord 5 | ~~~~~ 6 | Chord annotations described by an extended version of the grammar defined by Harte, et al. [1]_ 7 | 8 | ===== ======== ====== ========== 9 | time duration value confidence 10 | ===== ======== ====== ========== 11 | [sec] [sec] string -- 12 | ===== ======== ====== ========== 13 | 14 | .. [1] Harte, Christopher, Mark B. Sandler, Samer A. Abdallah, and Emilia Gómez. 15 | "Symbolic Representation of Musical Chords: A Proposed Syntax for Text Annotations." 16 | In ISMIR, vol. 5, pp. 66-71. 2005. 17 | 18 | This namespace is similar to `chord_harte`, with the following modifications: 19 | 20 | * Sharps and flats may not be mixed in a note symbol. For instance, `A#b#` is legal in `chord_harte` but 21 | not in `chord`. `A###` is legal in both. 22 | * The following quality values have been added: 23 | - *sus2*, *1*, *5* 24 | - *aug7* 25 | - *11*, *maj11*, *min11* 26 | - *13*, *maj13*, *min13* 27 | 28 | *Example* 29 | 30 | ===== ======== ============= ========== 31 | time duration value confidence 32 | ===== ======== ============= ========== 33 | 0.000 1.000 ``N`` null 34 | 0.000 1.000 ``Bb:5`` null 35 | 0.000 1.000 ``E:(*5)`` null 36 | 0.000 1.000 ``E#:min9/9`` null 37 | 0.000 1.000 ``G##:maj6`` null 38 | 0.000 1.000 ``D:13/6`` null 39 | 0.000 1.000 ``A:sus2`` null 40 | ===== ======== ============= ========== 41 | 42 | .. note:: 43 | ``confidence`` is an unconstrained field, and may contain arbitrary data. 44 | 45 | 46 | chord_harte 47 | ~~~~~~~~~~~ 48 | Chord annotations described according to the grammar defined by Harte, et al. [1]_ 49 | 50 | ===== ======== ====== ========== 51 | time duration value confidence 52 | ===== ======== ====== ========== 53 | [sec] [sec] string -- 54 | ===== ======== ====== ========== 55 | 56 | Each observed value is a text representation of a chord annotation. 57 | 58 | * ``N`` specifies a *no chord* observation 59 | * Notes are annotated in the usual way: ``A-G`` followed by optional sharps (``#``) and flats (``b``) 60 | * Chord qualities are denoted by abbreviated strings: 61 | - *maj*, *min*, *dim*, *aug* 62 | - *maj7*, *min7*, *7*, *dim7*, *hdim7*, *minmaj7* 63 | - *maj6*, *min6* 64 | - *9*, *maj9*, *min9* 65 | - *sus4* 66 | * Inversions are specified by a slash (``/``) followed by the interval number, e.g., ``G/3``. 67 | * Extensions are denoted in parentheses, e.g., ``G(b11,13)``. 68 | Suppressed notes are indicated with an asterisk, e.g., ``G(*3)`` 69 | 70 | A complete description of the chord grammar is provided in [1]_, table 1. 71 | 72 | *Example* 73 | 74 | ===== ======== ============= ========== 75 | time duration value confidence 76 | ===== ======== ============= ========== 77 | 0.000 1.000 ``N`` null 78 | 0.000 1.000 ``Bb`` null 79 | 0.000 1.000 ``E:(*5)`` null 80 | 0.000 1.000 ``E#:min9/9`` null 81 | 0.000 1.000 ``G#b:maj6`` null 82 | 0.000 1.000 ``D/6`` null 83 | 0.000 1.000 ``A:sus4`` null 84 | ===== ======== ============= ========== 85 | 86 | 87 | .. note:: 88 | ``confidence`` is an unconstrained field, and may contain arbitrary data. 89 | 90 | 91 | chord_roman 92 | ~~~~~~~~~~~ 93 | Chord annotations in roman numeral format, as described by [2]_. 94 | 95 | +-------+----------+------------+------------+ 96 | | time | duration | value | confidence | 97 | +=======+==========+============+============+ 98 | | [sec] | [sec] | - tonic | -- | 99 | | | | - chord | | 100 | +-------+----------+------------+------------+ 101 | 102 | The ``value`` field is a structure containing the following fields: 103 | 104 | - ``tonic`` : (string) the tonic note of the chord, e.g., ``A`` or ``Gb``. 105 | - ``chord`` : (string) the scale degree of the chord in roman numerals (1--7), along with 106 | inversions, extensions, and qualities. 107 | 108 | - Scale degrees are encoded with optional leading sharps and flats, e.g., ``V``, ``bV`` or 109 | ``#VII``. Upper-case numerals indicate major, lower-case numeral indicate minor. 110 | 111 | - Qualities are encoded as one of the following symbols: 112 | 113 | - ``o`` : diminished (triad) 114 | - ``+`` : augmented (triad) 115 | - ``s`` : suspension 116 | - ``d`` : dominant (seventh) 117 | - ``h`` : half-diminished (seventh) 118 | - ``x`` : fully-diminished (seventh) 119 | - Inversions are encoded by arabic numerals, e.g., ``V6`` for a first-inversion triad, ``V64`` 120 | for second inversion. 121 | 122 | - Applied chords are encoded by a ``/`` followed by a roman numeral encoding of the scale degree, 123 | e.g., ``V7/IV``. 124 | 125 | .. [2] http://theory.esm.rochester.edu/rock_corpus/harmonic_analyses.html 126 | 127 | *Example* 128 | +-------+----------+--------------+------------+ 129 | | time | duration | value | confidence | 130 | +=======+==========+==============+============+ 131 | | 0.000 | 0.500 | - tonic: C | -- | 132 | | | | - chord: I6 | | 133 | +-------+----------+--------------+------------+ 134 | | 0.500 | 0.500 | - tonic: C | -- | 135 | | | | - chord: bIV | | 136 | +-------+----------+--------------+------------+ 137 | | 1.000 | 0.500 | - tonic: C | -- | 138 | | | | - chord: Vh7 | | 139 | +-------+----------+--------------+------------+ 140 | 141 | .. note:: 142 | The grammar defined in [2]_ has been constrained to support only the quality symbols listed 143 | above. 144 | 145 | ``confidence`` is an unconstrained field, and may contain arbitrary data. 146 | 147 | -------------------------------------------------------------------------------- /docs/namespaces/key.rst: -------------------------------------------------------------------------------- 1 | Key 2 | --- 3 | 4 | key_mode 5 | ~~~~~~~~ 6 | Key and optional mode (major/minor or Greek modes) 7 | 8 | ===== ======== ====== ========== 9 | time duration value confidence 10 | ===== ======== ====== ========== 11 | [sec] [sec] string -- 12 | ===== ======== ====== ========== 13 | 14 | The ``value`` field is a string matching one of the three following patterns: 15 | 16 | * ``N`` : no key 17 | * ``Ab, A, A#, Bb, ... G#`` : tonic note, upper case 18 | * ``tonic:MODE`` where ``tonic`` is as described above, and ``MODE`` is one of: ``major, minor, ionian, dorian, 19 | phrygian, lydian, mixolydian, aeolian, locrian``. 20 | 21 | *Example* 22 | 23 | ===== ======== ========= ========== 24 | time duration value confidence 25 | ===== ======== ========= ========== 26 | 0.000 30.0 C:minor null 27 | 30.0 5.00 N null 28 | 35.0 15.0 C#:dorian null 29 | 50.0 10.0 Eb null 30 | 60.0 10.0 A:lydian null 31 | ===== ======== ========= ========== 32 | 33 | 34 | .. note:: 35 | 36 | ``confidence`` is an unconstrained field, and may contain arbitrary data. 37 | 38 | -------------------------------------------------------------------------------- /docs/namespaces/lyrics.rst: -------------------------------------------------------------------------------- 1 | Lyrics 2 | ------ 3 | 4 | lyrics 5 | ~~~~~~ 6 | Time-aligned lyrical annotations. 7 | 8 | ===== ======== ====== ========== 9 | time duration value confidence 10 | ===== ======== ====== ========== 11 | [sec] [sec] string -- 12 | ===== ======== ====== ========== 13 | 14 | The required ``value`` field can contain arbitrary text data, e.g., lyrics. 15 | 16 | *Example* 17 | 18 | ===== ======== ======================== ========== 19 | time duration value confidence 20 | ===== ======== ======================== ========== 21 | 0.500 4.000 "Row row row your boat" null 22 | 4.500 2.000 "gently down the stream" null 23 | 7.000 1.000 "merrily" null 24 | 8.000 1.000 "merrily" null 25 | 9.000 1.000 "merrily" null 26 | 10.00 1.000 "merrily" null 27 | ===== ======== ======================== ========== 28 | 29 | .. note:: 30 | ``confidence`` is an unconstrained field, and may contain arbitrary data. 31 | 32 | lyrics_bow 33 | ~~~~~~~~~~ 34 | Time-aligned bag-of-words or bag-of-ngrams. 35 | 36 | ===== ======== ====== ========== 37 | time duration value confidence 38 | ===== ======== ====== ========== 39 | [sec] [sec] array -- 40 | ===== ======== ====== ========== 41 | 42 | The required ``value`` field is an array, where each element is an array of ``[term, count]``. 43 | The ``term`` here may be either a string (for simple bag-of-words) or an array of strings (for bag-of-ngrams). 44 | 45 | *Example* 46 | 47 | +-------+----------+------------------------+------------+ 48 | | time | duration | value | confidence | 49 | +=======+==========+========================+============+ 50 | | 0.000 | 30.00 | * ['row', 3] | null | 51 | | | | * [ ['row', 'row'], 2] | | 52 | | | | * ['your', 1] | | 53 | | | | * ['boat', 1] | | 54 | +-------+----------+------------------------+------------+ 55 | 56 | .. note:: 57 | ``confidence`` is an unconstrained field, and may contain arbitrary data. 58 | -------------------------------------------------------------------------------- /docs/namespaces/misc.rst: -------------------------------------------------------------------------------- 1 | Miscellaneous 2 | ------------- 3 | 4 | Vector 5 | ~~~~~~ 6 | 7 | Numerical vector data. This is useful for generic regression problems where the output is 8 | a vector of numbers. 9 | 10 | ===== ======== ================== ========== 11 | time duration value confidence 12 | ===== ======== ================== ========== 13 | [sec] [sec] [array of numbers] -- 14 | ===== ======== ================== ========== 15 | 16 | Each observation value must be an array of at least one number. Different observations 17 | may have different length arrays, so it is up to the user to verify that arrays have the 18 | desired length. 19 | 20 | 21 | Blob 22 | ~~~~ 23 | 24 | Arbitrary data blobs. 25 | 26 | ===== ======== ===== ========== 27 | time duration value confidence 28 | ===== ======== ===== ========== 29 | [sec] [sec] -- -- 30 | ===== ======== ===== ========== 31 | 32 | This namespace can be used to encode arbitrary data. The value and confidence fields have no schema 33 | constraints, and may contain any structured (but serializable) data. This can be useful for storing complex 34 | output data that does not fit any particular task schema, such as regression targets or geolocation data. 35 | 36 | It is strongly advised that the AnnotationMetadata for blobs be as explicit as possible. 37 | 38 | 39 | Scaper 40 | ~~~~~~ 41 | 42 | Structured representation for soundscapes synthesized by the Scaper_ package. 43 | 44 | ===== ======== ================ ========== 45 | time duration value confidence 46 | ===== ======== ================ ========== 47 | [sec] [sec] - label -- 48 | - source_file 49 | - source_time 50 | - event_time 51 | - event_duration 52 | - snr 53 | - time_stretch 54 | - pitch_shift 55 | - role 56 | ===== ======== ================ ========== 57 | 58 | Each ``value`` field contains a dictionary with the following keys: 59 | 60 | * ``label``: a string indicating the label of the sound source 61 | * ``source_file``: a full path to the original sound source (on disk) 62 | * ``source_time``: a non-negative number indicating the time offset within ``source_file`` of the sound 63 | * ``event_time``: the start time of the event in the synthesized soundscape 64 | * ``event_duration``: a strictly positive number indicating the duration of the event 65 | * ``snr``: the signal-to-noise ratio (in LUFS) of the sound compared to the background 66 | * ``time_stetch``: (optional) a strictly positive number indicating the amount of time-stretch applied to 67 | the source 68 | * ``pitch_shift``: (optional) the amount of pitch-shift applied to the source 69 | * ``role``: one of ``background`` or ``foreground`` 70 | 71 | .. _Scaper: https://scaper.readthedocs.io/en/latest/ 72 | -------------------------------------------------------------------------------- /docs/namespaces/mood.rst: -------------------------------------------------------------------------------- 1 | Mood 2 | ---- 3 | 4 | mood_thayer 5 | ~~~~~~~~~~~ 6 | 7 | Time-varying emotion measurements as ordered pairs of ``(valence, arousal)`` 8 | 9 | ===== ======== ================== ========== 10 | time duration value confidence 11 | ===== ======== ================== ========== 12 | [sec] [sec] (valence, arousal) -- 13 | ===== ======== ================== ========== 14 | 15 | The ``value`` field is an ordered pair of numbers measuring the ``valence`` and 16 | ``arousal`` positions in the Thayer mood model [3]_. 17 | 18 | .. [3] Thayer, Robert E. The biopsychology of mood and arousal. 19 | Oxford University Press, 1989. 20 | 21 | *Example* 22 | 23 | ===== ======== =========== ========== 24 | time duration value confidence 25 | ===== ======== =========== ========== 26 | 0.500 0.250 (-0.5, 1.0) null 27 | 0.750 0.250 (-0.3, 0.6) null 28 | 1.000 0.750 (-0.1, 0.1) null 29 | 1.750 0.500 (0.3, -0.5) null 30 | ===== ======== =========== ========== 31 | 32 | 33 | .. note:: 34 | ``confidence`` is an unconstrained field, and may contain arbitrary data. 35 | -------------------------------------------------------------------------------- /docs/namespaces/onset.rst: -------------------------------------------------------------------------------- 1 | Onset 2 | ----- 3 | 4 | onset 5 | ~~~~~ 6 | Note onset event markers. 7 | 8 | ===== ======== ===== ========== 9 | time duration value confidence 10 | ===== ======== ===== ========== 11 | [sec] [sec] -- -- 12 | ===== ======== ===== ========== 13 | 14 | This namespace can be used to encode timing of arbitrary instantaneous events. 15 | Most commonly, this is applied to note onsets. 16 | 17 | *Example* 18 | 19 | ===== ======== ===== ========== 20 | time duration value confidence 21 | ===== ======== ===== ========== 22 | 0.500 0.000 null null 23 | 1.000 0.000 null null 24 | 1.500 0.000 null null 25 | 2.000 0.000 null null 26 | ===== ======== ===== ========== 27 | 28 | .. note:: 29 | ``duration`` is typically zero for instantaneous events, but this is not enforced. 30 | 31 | ``value`` and ``confidence`` fields are unconstrained, and may contain arbitrary data. 32 | -------------------------------------------------------------------------------- /docs/namespaces/pattern.rst: -------------------------------------------------------------------------------- 1 | Pattern 2 | ------- 3 | 4 | .. _patternjku: 5 | 6 | pattern_jku 7 | ~~~~~~~~~~~ 8 | 9 | Each note of the pattern contains ``(pattern_id, midi_pitch, occurrence_id, morph_pitch, 10 | staff)``, following the format described in [4]_. 11 | 12 | +-------+----------+------------------+------------+ 13 | | time | duration | value | confidence | 14 | +=======+==========+==================+============+ 15 | | [sec] | [sec] | - pattern_id | -- | 16 | | | | - midi_pitch | | 17 | | | | - occurrence_id | | 18 | | | | - morph_pitch | | 19 | | | | - staff | | 20 | +-------+----------+------------------+------------+ 21 | 22 | .. [4] Collins T., Discovery of Repeated Themes & Sections, Music Information Retrieval 23 | Evalaluation eXchange (MIReX), 2013 (Accessed on July 7th 2015). Available `here 24 | `_. 25 | 26 | Each ``value`` field contains a dictionary with the following keys: 27 | 28 | * ``pattern_id``: The integer that identifies the current pattern, \ 29 | starting from 1. 30 | * ``midi_pitch``: The float representing the midi pitch. 31 | * ``occurrence_id``: The integer that identifies the current occurrence, \ 32 | starting from 1. 33 | * ``morph_pitch``: The float representing the morphological pitch. 34 | * ``staff``: The integer representing the staff where the current note of the \ 35 | patter is found, starting from 0. 36 | 37 | 38 | *Example* 39 | 40 | +-------+----------+--------------------+------------+ 41 | | time | duration | value | confidence | 42 | +=======+==========+====================+============+ 43 | | 62.86 | 0.09 | - pattern_id: 1 | null | 44 | | | | - midi_pitch: 50 | | 45 | | | | - occurrence_id: 1 | | 46 | | | | - morph_pitch: 54 | | 47 | | | | - staff: 1 | | 48 | +-------+----------+--------------------+------------+ 49 | | 62.86 | 0.36 | - pattern_id: 1 | null | 50 | | | | - midi_pitch: 77 | | 51 | | | | - occurrence_id: 1 | | 52 | | | | - morph_pitch: 70 | | 53 | | | | - staff: 0 | | 54 | +-------+----------+--------------------+------------+ 55 | | 36.34 | 0.09 | - pattern_id: 1 | null | 56 | | | | - midi_pitch: 71 | | 57 | | | | - occurrence_id: 2 | | 58 | | | | - morph_pitch: 66 | | 59 | | | | - staff: 0 | | 60 | +-------+----------+--------------------+------------+ 61 | | 36.43 | 0.36 | - pattern_id: 1 | null | 62 | | | | - midi_pitch: 69 | | 63 | | | | - occurrence_id: 2 | | 64 | | | | - morph_pitch: 65 | | 65 | | | | - staff: 0 | | 66 | +-------+----------+--------------------+------------+ 67 | 68 | -------------------------------------------------------------------------------- /docs/namespaces/pitch.rst: -------------------------------------------------------------------------------- 1 | Pitch 2 | ----- 3 | 4 | pitch_contour 5 | ~~~~~~~~~~~~~ 6 | Pitch contours in the format ``(index, frequency, voicing)``. 7 | 8 | +-------+----------+---------------+------------+ 9 | | time | duration | value | confidence | 10 | +=======+==========+===============+============+ 11 | | [sec] | [--] | - index | -- | 12 | | | | - frequency | | 13 | | | | - voiced | | 14 | +-------+----------+---------------+------------+ 15 | 16 | Each ``value`` field is a structure containing a contour ``index`` (an integer indicating which contour the observation belongs to), a ``frequency`` value in Hz, and a boolean indicating if the values is ``voiced``. The ``confidence`` field is unconstrained. 17 | 18 | 19 | *Example* 20 | 21 | +--------+----------+--------------------+------------+ 22 | | time | duration | value | confidence | 23 | +========+==========+====================+============+ 24 | | 0.0000 | 0.0000 | - index: 0 | null | 25 | | | | - frequency: 442.1 | | 26 | | | | - voiced: True | | 27 | +--------+----------+--------------------+------------+ 28 | | 0.0058 | 0.0000 | - index: 0 | null | 29 | | | | - frequency: 457.8 | | 30 | | | | - voiced: False | | 31 | +--------+----------+--------------------+------------+ 32 | | 2.5490 | 0.0000 | - index: 1 | null | 33 | | | | - frequency: 89.4 | | 34 | | | | - voiced: True | | 35 | +--------+----------+--------------------+------------+ 36 | | 2.5548 | 0.0000 | - index: 1 | null | 37 | | | | - frequency: 90.0 | | 38 | | | | - voiced: True | | 39 | +--------+----------+--------------------+------------+ 40 | 41 | 42 | note_hz 43 | ~~~~~~~ 44 | Note events with (non-negative) frequencies measured in Hz. 45 | 46 | +-------+----------+---------------+------------+ 47 | | time | duration | value | confidence | 48 | +=======+==========+===============+============+ 49 | | [sec] | [sec] | - number | -- | 50 | +-------+----------+---------------+------------+ 51 | 52 | Each ``value`` field gives the frequency of the note in Hz. 53 | 54 | *Example* 55 | 56 | +-------+----------+---------------+------------+ 57 | | time | duration | value | confidence | 58 | +=======+==========+===============+============+ 59 | | 12.34 | 0.287 | 189.9 | null | 60 | +-------+----------+---------------+------------+ 61 | | 2.896 | 3.000 | 74.0 | null | 62 | +-------+----------+---------------+------------+ 63 | | 10.12 | 0.5. | 440.0 | null | 64 | +-------+----------+---------------+------------+ 65 | 66 | 67 | note_midi 68 | ~~~~~~~~~ 69 | Note events with pitches measured in (fractional) MIDI note numbers. 70 | 71 | +-------+----------+---------------+------------+ 72 | | time | duration | value | confidence | 73 | +=======+==========+===============+============+ 74 | | [sec] | [sec] | - number | -- | 75 | +-------+----------+---------------+------------+ 76 | 77 | Each ``value`` field gives the pitch of the note in MIDI note numbers. 78 | 79 | *Example* 80 | 81 | +-------+----------+---------------+------------+ 82 | | time | duration | value | confidence | 83 | +=======+==========+===============+============+ 84 | | 12.34 | 0.287 | 52.0 | null | 85 | +-------+----------+---------------+------------+ 86 | | 2.896 | 3.000 | 20.7 | null | 87 | +-------+----------+---------------+------------+ 88 | | 10.12 | 0.5. | 42.0 | null | 89 | +-------+----------+---------------+------------+ 90 | 91 | 92 | pitch_class 93 | ~~~~~~~~~~~ 94 | Pitch measurements in ``(tonic, pitch class)`` format. 95 | 96 | +-------+----------+---------------+------------+ 97 | | time | duration | value | confidence | 98 | +=======+==========+===============+============+ 99 | | [sec] | [sec] | - tonic | -- | 100 | | | | - pitch class | | 101 | +-------+----------+---------------+------------+ 102 | 103 | Each ``value`` field is a structure containing a ``tonic`` (note string, e.g., ``"A#"`` or 104 | ``"D"``) 105 | and a pitch class ``pitch`` as an integer scale degree. The ``confidence`` field is unconstrained. 106 | 107 | 108 | *Example* 109 | 110 | +-------+----------+------------------+------------+ 111 | | time | duration | value | confidence | 112 | +=======+==========+==================+============+ 113 | | 0.000 | 30.0 | - tonic: ``C`` | null | 114 | | | | - pitch: 0 | | 115 | +-------+----------+------------------+------------+ 116 | | 0.000 | 30.0 | - tonic: ``C`` | null | 117 | | | | - pitch: 4 | | 118 | +-------+----------+------------------+------------+ 119 | | 0.000 | 30.0 | - tonic: ``C`` | null | 120 | | | | - pitch: 7 | | 121 | +-------+----------+------------------+------------+ 122 | | 30.00 | 35.0 | - tonic: ``G`` | null | 123 | | | | - pitch: 0 | | 124 | +-------+----------+------------------+------------+ 125 | 126 | 127 | pitch_hz 128 | ~~~~~~~~ 129 | .. warning:: Deprecated, use ``pitch_contour``. 130 | 131 | Pitch measurements in Hertz (Hz). Pitch (a subjective sensation) is represented 132 | as fundamental frequency (a physical quantity), a.k.a. "f0". 133 | 134 | +-------+----------+---------------+------------+ 135 | | time | duration | value | confidence | 136 | +=======+==========+===============+============+ 137 | | [sec] | -- | - number | -- | 138 | +-------+----------+---------------+------------+ 139 | 140 | The ``time`` field represents the instantaneous time in which the pitch f0 was 141 | estimated. By convention, this (usually) represents the center time of the 142 | analysis frame. Note that this is different from pitch_midi and pitch_class, 143 | where ``time`` represents the onset time. As a consequence, the ``duration`` 144 | field is undefined and should be ignored. The ``value`` field is a number 145 | representing the f0 in Hz. By convention, values that are equal to or less than 146 | zero are used to represent silence (no pitch). Some algorithms (e.g. melody 147 | extraction algorithms that adhere to the MIREX convention) use negative f0 148 | values to represent the algorithm's pitch estimate for frames where it thinks 149 | there is no active pitch (e.g. no melody), to allow the independent evaluation 150 | of pitch activation detection (a.k.a. "voicing detection") and pitch frequency 151 | estimation. The ``confidence`` field is unconstrained. 152 | 153 | *Example* 154 | 155 | +-------+----------+---------------+------------+ 156 | | time | duration | value | confidence | 157 | +=======+==========+===============+============+ 158 | | 0.000 | 0.000 | 300.00 | null | 159 | +-------+----------+---------------+------------+ 160 | | 0.010 | 0.000 | 305.00 | null | 161 | +-------+----------+---------------+------------+ 162 | | 0.020 | 0.000 | 310.00 | null | 163 | +-------+----------+---------------+------------+ 164 | | 0.030 | 0.000 | 0.00 | null | 165 | +-------+----------+---------------+------------+ 166 | | 0.040 | 0.000 | -280.00 | null | 167 | +-------+----------+---------------+------------+ 168 | | 0.050 | 0.000 | -290.00 | null | 169 | +-------+----------+---------------+------------+ 170 | 171 | 172 | pitch_midi 173 | ~~~~~~~~~~ 174 | .. warning:: Deprecated, use ``note_midi`` or ``pitch_contour``. 175 | 176 | Pitch measurements in (fractional) MIDI note number notation. 177 | 178 | ===== ======== ====== ========== 179 | time duration value confidence 180 | ===== ======== ====== ========== 181 | [sec] [sec] number -- 182 | ===== ======== ====== ========== 183 | 184 | The ``value`` field is a number representing the pitch in MIDI notation. 185 | Numbers can be negative (for notes below ``C-1``) or fractional. 186 | 187 | *Example* 188 | 189 | ===== ======== ===== ========== 190 | time duration value confidence 191 | ===== ======== ===== ========== 192 | 0.000 30.000 24 null 193 | 0.000 30.000 43.02 null 194 | 15.00 45.000 26 null 195 | ===== ======== ===== ========== 196 | 197 | -------------------------------------------------------------------------------- /docs/namespaces/segment.rst: -------------------------------------------------------------------------------- 1 | Segment 2 | ------- 3 | 4 | segment_open 5 | ~~~~~~~~~~~~ 6 | Structural segmentation with an open vocabulary of segment labels. 7 | 8 | ===== ======== ================== ========== 9 | time duration value confidence 10 | ===== ======== ================== ========== 11 | [sec] [sec] string -- 12 | ===== ======== ================== ========== 13 | 14 | The ``value`` field contains string descriptors for each segment, e.g., "verse" or 15 | "bridge". 16 | 17 | *Example* 18 | ===== ======== ==================== ========== 19 | time duration value confidence 20 | ===== ======== ==================== ========== 21 | 0.000 20.000 intro null 22 | 20.00 30.000 verse null 23 | 30.00 50.000 refrain null 24 | 50.00 70.000 verse (alternate) null 25 | ===== ======== ==================== ========== 26 | 27 | 28 | segment_salami_function 29 | ~~~~~~~~~~~~~~~~~~~~~~~ 30 | Segment annotations with functional labels from the SALAMI guidelines. 31 | 32 | ===== ======== ================== ========== 33 | time duration value confidence 34 | ===== ======== ================== ========== 35 | [sec] [sec] string -- 36 | ===== ======== ================== ========== 37 | 38 | The ``value`` field must be one of the allowable strings in the SALAMI function 39 | vocabulary_. 40 | 41 | .. _vocabulary: https://github.com/DDMAL/salami-data-public/blob/master/funct_vocab_dictionary.txt 42 | 43 | *Example* 44 | ===== ======== ==================== ========== 45 | time duration value confidence 46 | ===== ======== ==================== ========== 47 | 0.000 20.000 applause null 48 | 20.00 30.000 count-in null 49 | 30.00 50.000 introduction null 50 | 50.00 70.000 verse null 51 | ===== ======== ==================== ========== 52 | 53 | 54 | segment_salami_upper 55 | ~~~~~~~~~~~~~~~~~~~~ 56 | Segment annotations with SALAMI's upper-case (large) label format. 57 | 58 | ===== ======== ================== ========== 59 | time duration value confidence 60 | ===== ======== ================== ========== 61 | [sec] [sec] string -- 62 | ===== ======== ================== ========== 63 | 64 | The ``value`` field must be a string of the following format: 65 | 66 | - "silence" or "Silence" 67 | - One or more upper-case letters, followed by zero or more apostrophes 68 | 69 | *Example* 70 | ===== ======== ==================== ========== 71 | time duration value confidence 72 | ===== ======== ==================== ========== 73 | 0.000 20.000 silence null 74 | 20.00 30.000 A null 75 | 30.00 50.000 B null 76 | 50.00 70.000 A' null 77 | ===== ======== ==================== ========== 78 | 79 | 80 | segment_salami_lower 81 | ~~~~~~~~~~~~~~~~~~~~ 82 | Segment annotations with SALAMI's lower-case (small) label format. 83 | 84 | ===== ======== ================== ========== 85 | time duration value confidence 86 | ===== ======== ================== ========== 87 | [sec] [sec] string -- 88 | ===== ======== ================== ========== 89 | 90 | The ``value`` field must be a string of the following format: 91 | 92 | - "silence" or "Silence" 93 | - One or more lower-case letters, followed by zero or more apostrophes 94 | 95 | *Example* 96 | ===== ======== ==================== ========== 97 | time duration value confidence 98 | ===== ======== ==================== ========== 99 | 0.000 20.000 silence null 100 | 20.00 30.000 a null 101 | 30.00 50.000 b null 102 | 50.00 70.000 a' null 103 | ===== ======== ==================== ========== 104 | 105 | segment_tut 106 | ~~~~~~~~~~~ 107 | Segment annotations using the TUT_ vocabulary. 108 | 109 | ===== ======== ================== ========== 110 | time duration value confidence 111 | ===== ======== ================== ========== 112 | [sec] [sec] string -- 113 | ===== ======== ================== ========== 114 | 115 | .. _TUT: http://www.cs.tut.fi/sgn/arg/paulus/structure.html 116 | 117 | The ``value`` field is a string describing the function of the segment. 118 | 119 | *Example* 120 | ===== ======== ==================== ========== 121 | time duration value confidence 122 | ===== ======== ==================== ========== 123 | 0.000 20.000 Intro null 124 | 20.00 30.000 Verse null 125 | 30.00 50.000 bridge null 126 | 50.00 70.000 RefrainA null 127 | ===== ======== ==================== ========== 128 | 129 | 130 | multi_segment 131 | ~~~~~~~~~~~~~ 132 | Multi-level structural segmentations. 133 | 134 | ===== ======== ================== ========== 135 | time duration value confidence 136 | ===== ======== ================== ========== 137 | [sec] [sec] * label : string -- 138 | * level : int >= 0 139 | ===== ======== ================== ========== 140 | 141 | In a multi-level segmentation, the track is partitioned many times --- 142 | possibly recursively --- which results in a collection of segmentations of varying degrees 143 | of specificity. In the ``multi_segment`` namespace, all of the resulting segments are 144 | collected together, and the ``level`` field is used to encode the segment's corresponding 145 | partition. 146 | 147 | Level values must be non-negative, and ordered by increasing specificity. For example, 148 | ``level==0`` may correspond to a single segment spanning the entire track, and each 149 | subsequent level value corresponds to a more refined segmentation. 150 | 151 | *Example* 152 | ===== ======== ================== ========== 153 | time duration value confidence 154 | ===== ======== ================== ========== 155 | 0.000 60.000 * label : A null 156 | * level : 0 157 | 0.000 30.000 * label : B null 158 | * level : 1 159 | 30.00 60.000 * label : C null 160 | * level : 1 161 | 0.000 15.000 * label : a null 162 | * level : 2 163 | 15.00 30.000 * label : b null 164 | * level : 2 165 | 30.00 45.000 * label : a null 166 | * level : 2 167 | 45.00 60.000 * label : c null 168 | * level : 2 169 | ===== ======== ================== ========== 170 | 171 | 172 | -------------------------------------------------------------------------------- /docs/namespaces/tempo.rst: -------------------------------------------------------------------------------- 1 | Tempo 2 | ----- 3 | 4 | tempo 5 | ~~~~~ 6 | Tempo measurements in beats per minute (BPM). 7 | 8 | ===== ======== ====== ========== 9 | time duration value confidence 10 | ===== ======== ====== ========== 11 | [sec] [sec] number number 12 | ===== ======== ====== ========== 13 | 14 | The ``value`` field is a non-negative number (floating point), indicated the tempo measurement. 15 | The ``confidence`` field is a number in the range ``[0, 1]``, following the format used by MIREX [5]_. 16 | 17 | .. [5] http://www.music-ir.org/mirex/wiki/2014:Audio_Tempo_Estimation 18 | 19 | *Example* 20 | 21 | ===== ======== ====== ========== 22 | time duration value confidence 23 | ===== ======== ====== ========== 24 | 0.00 60.00 180.0 0.8 25 | 0.00 60.00 90.0 0.2 26 | ===== ======== ====== ========== 27 | 28 | 29 | .. note:: 30 | MIREX requires that tempo measurements come in pairs, and that the confidence values sum to 1. 31 | This is not enforced at the schema level. 32 | 33 | -------------------------------------------------------------------------------- /docs/quickstart.rst: -------------------------------------------------------------------------------- 1 | *************** 2 | Getting started 3 | *************** 4 | 5 | Creating a JAMS data structure from scratch 6 | =========================================== 7 | First, create the top-level JAMS container: 8 | 9 | >>> import jams 10 | >>> jam = jams.JAMS() 11 | 12 | A track in JAMS must have a duration (in seconds). For this example, we'll make up a fake number, but in 13 | reality, you would compute the track duration from the source audio. 14 | 15 | >>> jam.file_metadata.duration = 8.0 16 | 17 | Now we can create a beat annotation: 18 | 19 | >>> ann = jams.Annotation(namespace='beat', time=0, duration=jam.file_metadata.duration) 20 | >>> ann.append(time=0.33, duration=0.0, confidence=1, value=1) 21 | 22 | Then, we'll update the annotation's metadata by directly setting its fields: 23 | 24 | >>> ann.annotation_metadata = jams.AnnotationMetadata(data_source='Well paid students') 25 | >>> ann.annotation_metadata.curator = jams.Curator(name='Rincewind', 26 | ... email='rincewind@unseen.edu') 27 | 28 | Add our new annotation to the jam: 29 | 30 | >>> jam.annotations.append(ann) 31 | 32 | We can update the annotation at any time, and add a new observation: 33 | 34 | >>> ann.append(time=0.66, duration=0.0, confidence=1, value=1) 35 | 36 | 37 | Once you've added all your data, you can serialize the annotation to a string: 38 | 39 | >>> jam.dumps(indent=2) 40 | { 41 | "sandbox": {}, 42 | "annotations": [ 43 | { 44 | "data": [ 45 | { 46 | "duration": 0.0, 47 | "confidence": 1.0, 48 | "value": 1.0, 49 | "time": 0.33 50 | }, 51 | { 52 | "duration": 0.0, 53 | "confidence": 1.0, 54 | "value": 1.0, 55 | "time": 0.66 56 | } 57 | ], 58 | "annotation_metadata": { 59 | "annotation_tools": "", 60 | "curator": { 61 | "name": "Rincewind", 62 | "email": "rincewind@unseen.edu" 63 | }, 64 | "annotator": {}, 65 | "version": "", 66 | "corpus": "", 67 | "annotation_rules": "", 68 | "validation": "", 69 | "data_source": "Well paid students" 70 | }, 71 | "namespace": "beat", 72 | "sandbox": {} 73 | } 74 | ], 75 | "file_metadata": { 76 | "jams_version": "0.2.0", 77 | "title": "", 78 | "identifiers": {}, 79 | "release": "", 80 | "duration": 8.0, 81 | "artist": "" 82 | } 83 | } 84 | 85 | Or save to a file using the built-in save function: 86 | 87 | >>> jam.save("these_are_still_my.jams") 88 | 89 | 90 | Reading a JAMS file 91 | =================== 92 | Assuming you already have a JAMS file on-disk, say at 'these_are_also_my.jams', 93 | you can easily read it back into memory: 94 | 95 | >>> another_jam = jams.load('these_are_also_my.jams') 96 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | six 2 | numpydoc>=0.5 3 | -------------------------------------------------------------------------------- /jams/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Top-level module for JAMS""" 3 | 4 | import os 5 | import sys 6 | from importlib import resources 7 | from itertools import chain 8 | 9 | # Import the necessary modules 10 | from .exceptions import * 11 | from . import util 12 | from . import schema 13 | from . import eval 14 | from . import sonify 15 | from .version import version as __version__ 16 | 17 | from .core import * 18 | from .nsconvert import convert 19 | from .schema import list_namespaces 20 | 21 | 22 | # Populate the namespace mapping 23 | if sys.version_info < (3, 10): 24 | from pkg_resources import resource_filename 25 | 26 | for _ in util.find_with_extension(resource_filename(__name__, schema.NS_SCHEMA_DIR), 'json'): 27 | schema.add_namespace(_) 28 | 29 | else: 30 | for ns in chain(*map(lambda p: p.rglob('*.json'), resources.files('jams.schemata.namespaces').iterdir())): 31 | schema.add_namespace(ns) 32 | 33 | # Populate local namespaces 34 | if 'JAMS_SCHEMA_DIR' in os.environ: 35 | for ns in util.find_with_extension(os.environ['JAMS_SCHEMA_DIR'], 'json'): 36 | schema.add_namespace(ns) 37 | -------------------------------------------------------------------------------- /jams/display.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | r''' 3 | Display 4 | ------- 5 | 6 | .. autosummary:: 7 | :toctree: generated/ 8 | 9 | display 10 | display_multi 11 | ''' 12 | 13 | from collections import OrderedDict 14 | 15 | import json 16 | import re 17 | import six 18 | 19 | import numpy as np 20 | 21 | import matplotlib.pyplot as plt 22 | from matplotlib.offsetbox import AnchoredText 23 | 24 | import mir_eval.display 25 | 26 | from .eval import hierarchy_flatten 27 | from .exceptions import NamespaceError, ParameterError 28 | from .eval import coerce_annotation 29 | from .nsconvert import can_convert 30 | 31 | 32 | def pprint_jobject(obj, **kwargs): 33 | '''Pretty-print a jobject. 34 | 35 | Parameters 36 | ---------- 37 | obj : jams.JObject 38 | 39 | kwargs 40 | additional parameters to `json.dumps` 41 | 42 | Returns 43 | ------- 44 | string 45 | A simplified display of `obj` contents. 46 | ''' 47 | 48 | obj_simple = {k: v for k, v in six.iteritems(obj.__json__) if v} 49 | 50 | string = json.dumps(obj_simple, **kwargs) 51 | 52 | # Suppress braces and quotes 53 | string = re.sub(r'[{}"]', '', string) 54 | 55 | # Kill trailing commas 56 | string = re.sub(r',\n', '\n', string) 57 | 58 | # Kill blank lines 59 | string = re.sub(r'^\s*$', '', string) 60 | 61 | return string 62 | 63 | 64 | def intervals(annotation, **kwargs): 65 | '''Plotting wrapper for labeled intervals''' 66 | times, labels = annotation.to_interval_values() 67 | 68 | return mir_eval.display.labeled_intervals(times, labels, **kwargs) 69 | 70 | 71 | def hierarchy(annotation, **kwargs): 72 | '''Plotting wrapper for hierarchical segmentations''' 73 | htimes, hlabels = hierarchy_flatten(annotation) 74 | 75 | htimes = [np.asarray(_) for _ in htimes] 76 | return mir_eval.display.hierarchy(htimes, hlabels, **kwargs) 77 | 78 | 79 | def pitch_contour(annotation, **kwargs): 80 | '''Plotting wrapper for pitch contours''' 81 | ax = kwargs.pop('ax', None) 82 | 83 | # If the annotation is empty, we need to construct a new axes 84 | ax = mir_eval.display.__get_axes(ax=ax)[0] 85 | 86 | times, values = annotation.to_interval_values() 87 | 88 | indices = np.unique([v['index'] for v in values]) 89 | 90 | for idx in indices: 91 | rows = [i for (i, v) in enumerate(values) if v['index'] == idx] 92 | freqs = np.asarray([values[r]['frequency'] for r in rows]) 93 | unvoiced = ~np.asarray([values[r]['voiced'] for r in rows]) 94 | freqs[unvoiced] *= -1 95 | 96 | ax = mir_eval.display.pitch(times[rows, 0], freqs, unvoiced=True, 97 | ax=ax, 98 | **kwargs) 99 | return ax 100 | 101 | 102 | def event(annotation, **kwargs): 103 | '''Plotting wrapper for events''' 104 | 105 | times, values = annotation.to_interval_values() 106 | 107 | if any(values): 108 | labels = values 109 | else: 110 | labels = None 111 | 112 | return mir_eval.display.events(times, labels=labels, **kwargs) 113 | 114 | 115 | def beat_position(annotation, **kwargs): 116 | '''Plotting wrapper for beat-position data''' 117 | 118 | times, values = annotation.to_interval_values() 119 | 120 | labels = [_['position'] for _ in values] 121 | 122 | # TODO: plot time signature, measure number 123 | return mir_eval.display.events(times, labels=labels, **kwargs) 124 | 125 | 126 | def piano_roll(annotation, **kwargs): 127 | '''Plotting wrapper for piano rolls''' 128 | times, midi = annotation.to_interval_values() 129 | 130 | return mir_eval.display.piano_roll(times, midi=midi, **kwargs) 131 | 132 | 133 | VIZ_MAPPING = OrderedDict() 134 | 135 | VIZ_MAPPING['segment_open'] = intervals 136 | VIZ_MAPPING['chord'] = intervals 137 | VIZ_MAPPING['multi_segment'] = hierarchy 138 | VIZ_MAPPING['pitch_contour'] = pitch_contour 139 | VIZ_MAPPING['beat_position'] = beat_position 140 | VIZ_MAPPING['beat'] = event 141 | VIZ_MAPPING['onset'] = event 142 | VIZ_MAPPING['note_midi'] = piano_roll 143 | VIZ_MAPPING['tag_open'] = intervals 144 | 145 | 146 | def display(annotation, meta=True, **kwargs): 147 | '''Visualize a jams annotation through mir_eval 148 | 149 | Parameters 150 | ---------- 151 | annotation : jams.Annotation 152 | The annotation to display 153 | 154 | meta : bool 155 | If `True`, include annotation metadata in the figure 156 | 157 | kwargs 158 | Additional keyword arguments to mir_eval.display functions 159 | 160 | Returns 161 | ------- 162 | ax 163 | Axis handles for the new display 164 | 165 | Raises 166 | ------ 167 | NamespaceError 168 | If the annotation cannot be visualized 169 | ''' 170 | 171 | for namespace, func in six.iteritems(VIZ_MAPPING): 172 | try: 173 | ann = coerce_annotation(annotation, namespace) 174 | 175 | axes = func(ann, **kwargs) 176 | 177 | # Title should correspond to original namespace, not the coerced version 178 | axes.set_title(annotation.namespace) 179 | if meta: 180 | description = pprint_jobject(annotation.annotation_metadata, indent=2) 181 | 182 | anchored_box = AnchoredText(description.strip('\n'), 183 | loc=2, 184 | frameon=True, 185 | bbox_to_anchor=(1.02, 1.0), 186 | bbox_transform=axes.transAxes, 187 | borderpad=0.0) 188 | axes.add_artist(anchored_box) 189 | 190 | axes.figure.subplots_adjust(right=0.8) 191 | 192 | return axes 193 | except NamespaceError: 194 | pass 195 | 196 | raise NamespaceError('Unable to visualize annotation of namespace="{:s}"' 197 | .format(annotation.namespace)) 198 | 199 | 200 | def display_multi(annotations, fig_kw=None, meta=True, **kwargs): 201 | '''Display multiple annotations with shared axes 202 | 203 | Parameters 204 | ---------- 205 | annotations : jams.AnnotationArray 206 | A collection of annotations to display 207 | 208 | fig_kw : dict 209 | Keyword arguments to `plt.figure` 210 | 211 | meta : bool 212 | If `True`, display annotation metadata for each annotation 213 | 214 | kwargs 215 | Additional keyword arguments to the `mir_eval.display` routines 216 | 217 | Returns 218 | ------- 219 | fig 220 | The created figure 221 | axs 222 | List of subplot axes corresponding to each displayed annotation 223 | ''' 224 | if fig_kw is None: 225 | fig_kw = dict() 226 | 227 | fig_kw.setdefault('sharex', True) 228 | fig_kw.setdefault('squeeze', True) 229 | 230 | # Filter down to coercable annotations first 231 | display_annotations = [] 232 | for ann in annotations: 233 | for namespace in VIZ_MAPPING: 234 | if can_convert(ann, namespace): 235 | display_annotations.append(ann) 236 | break 237 | 238 | # If there are no displayable annotations, fail here 239 | if not len(display_annotations): 240 | raise ParameterError('No displayable annotations found') 241 | 242 | fig, axs = plt.subplots(nrows=len(display_annotations), ncols=1, **fig_kw) 243 | 244 | # MPL is stupid when making singleton subplots. 245 | # We catch this and make it always iterable. 246 | if len(display_annotations) == 1: 247 | axs = [axs] 248 | 249 | for ann, ax in zip(display_annotations, axs): 250 | kwargs['ax'] = ax 251 | display(ann, meta=meta, **kwargs) 252 | 253 | return fig, axs 254 | -------------------------------------------------------------------------------- /jams/exceptions.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- encoding: utf-8 -*- 3 | '''Exception classes for JAMS''' 4 | 5 | 6 | class JamsError(Exception): 7 | '''The root JAMS exception class''' 8 | pass 9 | 10 | 11 | class SchemaError(JamsError): 12 | '''Exceptions relating to schema validation''' 13 | pass 14 | 15 | 16 | class NamespaceError(JamsError): 17 | '''Exceptions relating to task namespaces''' 18 | pass 19 | 20 | 21 | class ParameterError(JamsError): 22 | '''Exceptions relating to function and method parameters''' 23 | pass 24 | -------------------------------------------------------------------------------- /jams/nsconvert.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- encoding: utf-8 -*- 3 | # CREATED:2016-02-16 15:40:04 by Brian McFee 4 | r''' 5 | Namespace conversion 6 | -------------------- 7 | 8 | .. autosummary:: 9 | :toctree: generated 10 | 11 | convert 12 | ''' 13 | 14 | import numpy as np 15 | 16 | from copy import deepcopy 17 | from collections import defaultdict 18 | 19 | from .exceptions import NamespaceError 20 | 21 | 22 | # The structure that handles all conversion mappings 23 | __CONVERSION__ = defaultdict(defaultdict) 24 | 25 | __all__ = ['convert', 'can_convert'] 26 | 27 | 28 | def _conversion(target, source): 29 | '''A decorator to register namespace conversions. 30 | 31 | Usage 32 | ----- 33 | >>> @conversion('tag_open', 'tag_.*') 34 | ... def tag_to_open(annotation): 35 | ... annotation.namespace = 'tag_open' 36 | ... return annotation 37 | ''' 38 | 39 | def register(func): 40 | '''This decorator registers func as mapping source to target''' 41 | __CONVERSION__[target][source] = func 42 | return func 43 | 44 | return register 45 | 46 | 47 | def convert(annotation, target_namespace): 48 | '''Convert a given annotation to the target namespace. 49 | 50 | Parameters 51 | ---------- 52 | annotation : jams.Annotation 53 | An annotation object 54 | 55 | target_namespace : str 56 | The target namespace 57 | 58 | Returns 59 | ------- 60 | mapped_annotation : jams.Annotation 61 | if `annotation` already belongs to `target_namespace`, then 62 | it is returned directly. 63 | 64 | otherwise, `annotation` is copied and automatically converted 65 | to the target namespace. 66 | 67 | Raises 68 | ------ 69 | SchemaError 70 | if the input annotation fails to validate 71 | 72 | NamespaceError 73 | if no conversion is possible 74 | 75 | Examples 76 | -------- 77 | Convert frequency measurements in Hz to MIDI 78 | 79 | >>> ann_midi = jams.convert(ann_hz, 'note_midi') 80 | 81 | And back to Hz 82 | 83 | >>> ann_hz2 = jams.convert(ann_midi, 'note_hz') 84 | ''' 85 | 86 | # First, validate the input. If this fails, we can't auto-convert. 87 | annotation.validate(strict=True) 88 | 89 | # If we're already in the target namespace, do nothing 90 | if annotation.namespace == target_namespace: 91 | return annotation 92 | 93 | if target_namespace in __CONVERSION__: 94 | # Otherwise, make a copy to mangle 95 | annotation = deepcopy(annotation) 96 | 97 | # Look for a way to map this namespace to the target 98 | for source in __CONVERSION__[target_namespace]: 99 | if annotation.search(namespace=source): 100 | return __CONVERSION__[target_namespace][source](annotation) 101 | 102 | # No conversion possible 103 | raise NamespaceError('Unable to convert annotation from namespace=' 104 | '"{0}" to "{1}"'.format(annotation.namespace, 105 | target_namespace)) 106 | 107 | 108 | def can_convert(annotation, target_namespace): 109 | '''Test if an annotation can be mapped to a target namespace 110 | 111 | Parameters 112 | ---------- 113 | annotation : jams.Annotation 114 | An annotation object 115 | 116 | target_namespace : str 117 | The target namespace 118 | 119 | Returns 120 | ------- 121 | True 122 | if `annotation` can be automatically converted to 123 | `target_namespace` 124 | 125 | False 126 | otherwise 127 | ''' 128 | 129 | # If we're already in the target namespace, do nothing 130 | if annotation.namespace == target_namespace: 131 | return True 132 | 133 | if target_namespace in __CONVERSION__: 134 | # Look for a way to map this namespace to the target 135 | for source in __CONVERSION__[target_namespace]: 136 | if annotation.search(namespace=source): 137 | return True 138 | return False 139 | 140 | 141 | @_conversion('pitch_contour', 'pitch_hz') 142 | def pitch_hz_to_contour(annotation): 143 | '''Convert a pitch_hz annotation to a contour''' 144 | annotation.namespace = 'pitch_contour' 145 | data = annotation.pop_data() 146 | 147 | for obs in data: 148 | annotation.append(time=obs.time, duration=obs.duration, 149 | confidence=obs.confidence, 150 | value=dict(index=0, 151 | frequency=np.abs(obs.value), 152 | voiced=obs.value > 0)) 153 | return annotation 154 | 155 | 156 | @_conversion('pitch_contour', 'pitch_midi') 157 | def pitch_midi_to_contour(annotation): 158 | '''Convert a pitch_hz annotation to a contour''' 159 | annotation = pitch_midi_to_hz(annotation) 160 | return pitch_hz_to_contour(annotation) 161 | 162 | 163 | @_conversion('note_hz', 'note_midi') 164 | def note_midi_to_hz(annotation): 165 | '''Convert a pitch_midi annotation to pitch_hz''' 166 | 167 | annotation.namespace = 'note_hz' 168 | data = annotation.pop_data() 169 | 170 | for obs in data: 171 | annotation.append(time=obs.time, duration=obs.duration, 172 | confidence=obs.confidence, 173 | value=440 * (2.0**((obs.value - 69.0)/12.0))) 174 | 175 | return annotation 176 | 177 | 178 | @_conversion('note_midi', 'note_hz') 179 | def note_hz_to_midi(annotation): 180 | '''Convert a pitch_hz annotation to pitch_midi''' 181 | 182 | annotation.namespace = 'note_midi' 183 | 184 | data = annotation.pop_data() 185 | 186 | for obs in data: 187 | annotation.append(time=obs.time, duration=obs.duration, 188 | confidence=obs.confidence, 189 | value=12 * (np.log2(obs.value) - np.log2(440.0)) + 69) 190 | 191 | return annotation 192 | 193 | 194 | @_conversion('pitch_hz', 'pitch_midi') 195 | def pitch_midi_to_hz(annotation): 196 | '''Convert a pitch_midi annotation to pitch_hz''' 197 | 198 | annotation.namespace = 'pitch_hz' 199 | 200 | data = annotation.pop_data() 201 | 202 | for obs in data: 203 | annotation.append(time=obs.time, duration=obs.duration, 204 | confidence=obs.confidence, 205 | value=440 * (2.0**((obs.value - 69.0)/12.0))) 206 | 207 | return annotation 208 | 209 | 210 | @_conversion('pitch_midi', 'pitch_hz') 211 | def pitch_hz_to_midi(annotation): 212 | '''Convert a pitch_hz annotation to pitch_midi''' 213 | 214 | annotation.namespace = 'pitch_midi' 215 | 216 | data = annotation.pop_data() 217 | 218 | for obs in data: 219 | annotation.append(time=obs.time, duration=obs.duration, 220 | confidence=obs.confidence, 221 | value=12 * (np.log2(obs.value) - np.log2(440.0)) + 69) 222 | return annotation 223 | 224 | 225 | @_conversion('segment_open', 'segment_.*') 226 | def segment_to_open(annotation): 227 | '''Convert any segmentation to open label space''' 228 | 229 | annotation.namespace = 'segment_open' 230 | return annotation 231 | 232 | 233 | @_conversion('tag_open', 'tag_.*') 234 | def tag_to_open(annotation): 235 | '''Convert any tag annotation to open label space''' 236 | 237 | annotation.namespace = 'tag_open' 238 | return annotation 239 | 240 | 241 | @_conversion('tag_open', 'scaper') 242 | def scaper_to_tag(annotation): 243 | '''Convert scaper annotations to tag_open''' 244 | 245 | annotation.namespace = 'tag_open' 246 | 247 | data = annotation.pop_data() 248 | for obs in data: 249 | annotation.append(time=obs.time, duration=obs.duration, 250 | confidence=obs.confidence, value=obs.value['label']) 251 | 252 | return annotation 253 | 254 | 255 | @_conversion('beat', 'beat_position') 256 | def beat_position(annotation): 257 | '''Convert beat_position to beat''' 258 | 259 | annotation.namespace = 'beat' 260 | data = annotation.pop_data() 261 | for obs in data: 262 | annotation.append(time=obs.time, duration=obs.duration, 263 | confidence=obs.confidence, 264 | value=obs.value['position']) 265 | 266 | return annotation 267 | 268 | 269 | @_conversion('chord', 'chord_harte') 270 | def chordh_to_chord(annotation): 271 | '''Convert Harte annotation to chord''' 272 | 273 | annotation.namespace = 'chord' 274 | return annotation 275 | -------------------------------------------------------------------------------- /jams/schema.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- encoding: utf-8 -*- 3 | r''' 4 | Namespace management 5 | -------------------- 6 | 7 | .. autosummary:: 8 | :toctree: generated/ 9 | 10 | add_namespace 11 | namespace 12 | namespace_array 13 | is_dense 14 | values 15 | get_dtypes 16 | list_namespaces 17 | ''' 18 | 19 | from __future__ import print_function 20 | 21 | import json 22 | import os 23 | import copy 24 | 25 | import numpy as np 26 | import jsonschema 27 | 28 | from .exceptions import NamespaceError, JamsError 29 | 30 | __all__ = ['add_namespace', 'namespace', 'is_dense', 'values', 'get_dtypes', 'VALIDATOR'] 31 | 32 | __NAMESPACE__ = dict() 33 | 34 | 35 | def add_namespace(filename): 36 | '''Add a namespace definition to our working set. 37 | 38 | Namespace files consist of partial JSON schemas defining the behavior 39 | of the `value` and `confidence` fields of an Annotation. 40 | 41 | Parameters 42 | ---------- 43 | filename : str 44 | Path to json file defining the namespace object 45 | ''' 46 | with open(filename, mode='r') as fileobj: 47 | __NAMESPACE__.update(json.load(fileobj)) 48 | 49 | 50 | def namespace(ns_key): 51 | '''Construct a validation schema for a given namespace. 52 | 53 | Parameters 54 | ---------- 55 | ns_key : str 56 | Namespace key identifier (eg, 'beat' or 'segment_tut') 57 | 58 | Returns 59 | ------- 60 | schema : dict 61 | JSON schema of `namespace` 62 | ''' 63 | 64 | if ns_key not in __NAMESPACE__: 65 | raise NamespaceError('Unknown namespace: {:s}'.format(ns_key)) 66 | 67 | sch = copy.deepcopy(JAMS_SCHEMA['definitions']['SparseObservation']) 68 | 69 | for key in ['value', 'confidence']: 70 | try: 71 | sch['properties'][key] = __NAMESPACE__[ns_key][key] 72 | except KeyError: 73 | pass 74 | 75 | return sch 76 | 77 | 78 | def namespace_array(ns_key): 79 | '''Construct a validation schema for arrays of a given namespace. 80 | 81 | Parameters 82 | ---------- 83 | ns_key : str 84 | Namespace key identifier 85 | 86 | Returns 87 | ------- 88 | schema : dict 89 | JSON schema of `namespace` observation arrays 90 | ''' 91 | 92 | obs_sch = namespace(ns_key) 93 | obs_sch['title'] = 'Observation' 94 | 95 | sch = copy.deepcopy(JAMS_SCHEMA['definitions']['SparseObservationList']) 96 | sch['items'] = obs_sch 97 | return sch 98 | 99 | 100 | def is_dense(ns_key): 101 | '''Determine whether a namespace has dense formatting. 102 | 103 | Parameters 104 | ---------- 105 | ns_key : str 106 | Namespace key identifier 107 | 108 | Returns 109 | ------- 110 | dense : bool 111 | True if `ns_key` has a dense packing 112 | False otherwise. 113 | ''' 114 | 115 | if ns_key not in __NAMESPACE__: 116 | raise NamespaceError('Unknown namespace: {:s}'.format(ns_key)) 117 | 118 | return __NAMESPACE__[ns_key]['dense'] 119 | 120 | 121 | def values(ns_key): 122 | '''Return the allowed values for an enumerated namespace. 123 | 124 | Parameters 125 | ---------- 126 | ns_key : str 127 | Namespace key identifier 128 | 129 | Returns 130 | ------- 131 | values : list 132 | 133 | Raises 134 | ------ 135 | NamespaceError 136 | If `ns_key` is not found, or does not have enumerated values 137 | 138 | Examples 139 | -------- 140 | >>> jams.schema.values('tag_gtzan') 141 | ['blues', 'classical', 'country', 'disco', 'hip-hop', 'jazz', 142 | 'metal', 'pop', 'reggae', 'rock'] 143 | ''' 144 | 145 | if ns_key not in __NAMESPACE__: 146 | raise NamespaceError('Unknown namespace: {:s}'.format(ns_key)) 147 | 148 | if 'enum' not in __NAMESPACE__[ns_key]['value']: 149 | raise NamespaceError('Namespace {:s} is not enumerated'.format(ns_key)) 150 | 151 | return copy.copy(__NAMESPACE__[ns_key]['value']['enum']) 152 | 153 | 154 | def get_dtypes(ns_key): 155 | '''Get the dtypes associated with the value and confidence fields 156 | for a given namespace. 157 | 158 | Parameters 159 | ---------- 160 | ns_key : str 161 | The namespace key in question 162 | 163 | Returns 164 | ------- 165 | value_dtype, confidence_dtype : numpy.dtype 166 | Type identifiers for value and confidence fields. 167 | ''' 168 | 169 | # First, get the schema 170 | if ns_key not in __NAMESPACE__: 171 | raise NamespaceError('Unknown namespace: {:s}'.format(ns_key)) 172 | 173 | value_dtype = __get_dtype(__NAMESPACE__[ns_key].get('value', {})) 174 | confidence_dtype = __get_dtype(__NAMESPACE__[ns_key].get('confidence', {})) 175 | 176 | return value_dtype, confidence_dtype 177 | 178 | 179 | def list_namespaces(): 180 | '''Print out a listing of available namespaces''' 181 | print('{:30s}\t{:40s}'.format('NAME', 'DESCRIPTION')) 182 | print('-' * 78) 183 | for sch in sorted(__NAMESPACE__): 184 | desc = __NAMESPACE__[sch]['description'] 185 | desc = (desc[:44] + '..') if len(desc) > 46 else desc 186 | print('{:30s}\t{:40s}'.format(sch, desc)) 187 | 188 | 189 | # Mapping of js primitives to numpy types 190 | __TYPE_MAP__ = dict(integer=np.int_, 191 | boolean=np.bool_, 192 | number=np.float64, 193 | object=np.object_, 194 | array=np.object_, 195 | string=np.object_, 196 | null=np.float64) 197 | 198 | 199 | def __get_dtype(typespec): 200 | '''Get the dtype associated with a jsonschema type definition 201 | 202 | Parameters 203 | ---------- 204 | typespec : dict 205 | The schema definition 206 | 207 | Returns 208 | ------- 209 | dtype : numpy.dtype 210 | The associated dtype 211 | ''' 212 | 213 | if 'type' in typespec: 214 | return __TYPE_MAP__.get(typespec['type'], np.object_) 215 | 216 | elif 'enum' in typespec: 217 | # Enums map to objects 218 | return np.object_ 219 | 220 | elif 'oneOf' in typespec: 221 | # Recurse 222 | types = [__get_dtype(v) for v in typespec['oneOf']] 223 | 224 | # If they're not all equal, return object 225 | if all([t == types[0] for t in types]): 226 | return types[0] 227 | 228 | return np.object_ 229 | 230 | 231 | def __load_jams_schema(): 232 | '''Load the schema file from the package.''' 233 | abs_schema_dir = os.path.join(os.path.dirname(__file__), SCHEMA_DIR) 234 | schema_file = os.path.join(abs_schema_dir, 'jams_schema.json') 235 | with open(schema_file, mode='r') as fdesc: 236 | jams_schema = json.load(fdesc) 237 | 238 | if jams_schema is None: 239 | raise JamsError('Unable to load JAMS schema') 240 | 241 | return jams_schema 242 | 243 | 244 | # Populate the schemata 245 | SCHEMA_DIR = 'schemata' 246 | NS_SCHEMA_DIR = os.path.join(SCHEMA_DIR, 'namespaces') 247 | 248 | JAMS_SCHEMA = __load_jams_schema() 249 | VALIDATOR = jsonschema.Draft4Validator(JAMS_SCHEMA) 250 | -------------------------------------------------------------------------------- /jams/schemata/jams_schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | 4 | "definitions": { 5 | 6 | "FileMetadata": { 7 | "type": "object", 8 | "title": "File Metadata", 9 | "properties": { 10 | "identifiers": { 11 | "type": "object", 12 | "title": "Namespace Identifiers", 13 | "description": "Sandbox for file ID information (e.g. echonest ID, musicbrainz ID, etc.)." 14 | }, 15 | "artist": { "type": "string"}, 16 | "title": { "type": "string"}, 17 | "release": { "type": "string"}, 18 | "duration": { "type": "number", "minimum": 0.0}, 19 | "jams_version" : { "type": "string", "pattern": "[0-9].[0-9].[0-9]"} 20 | } 21 | }, 22 | 23 | "Curator": { 24 | "type": "object", 25 | "title": "Curator", 26 | "description": "Curator contact information: name and email", 27 | "properties": { 28 | "name": { "type": "string"}, 29 | "email": { "type": "string", "format": "email" } 30 | }, 31 | "required": ["name", "email"] 32 | }, 33 | 34 | "AnnotationMetadata": { 35 | "type": "object", 36 | "title": "Annotation metadata", 37 | "description": "All metadata required to describe an annotation", 38 | "properties": { 39 | "corpus": { "type": "string" }, 40 | "version": { "type": ["string", "number"] }, 41 | "curator": { "$ref": "#/definitions/Curator" }, 42 | "annotator": { 43 | "type": "object", 44 | "title": "Annotator", 45 | "description": "Sandbox for information about the annotator." 46 | }, 47 | "annotation_tools": { "type": "string"}, 48 | "annotation_rules": { "type": "string"}, 49 | "validation": { "type": "string"}, 50 | "data_source": { "type": "string"} 51 | } 52 | }, 53 | 54 | "SparseObservation": { 55 | "type": "object", 56 | "title": "SparseObservation", 57 | "description": "An observation that spans an interval of time", 58 | "properties": { 59 | "value": { }, 60 | "confidence": { }, 61 | "time": {"type": "number", "minimum": 0.0}, 62 | "duration": {"type": "number", "minimum": 0.0} 63 | }, 64 | "required": ["value", "time", "duration", "confidence"] 65 | }, 66 | 67 | "DenseObservation": { 68 | "type": "object", 69 | "title": "DenseObservation", 70 | "description": "A dense series of observations", 71 | "properties": { 72 | "value": {"type": "array"}, 73 | "confidence": {"type": "array" }, 74 | "time": {"type": "array", "items": { "type": "number", "minimum": 0.0 }}, 75 | "duration": {"type": "array", "items": { "type": "number", "minimum": 0.0 }} 76 | }, 77 | "required": ["value", "time", "duration", "confidence"] 78 | }, 79 | 80 | "SparseObservationList": { 81 | "type": "array", 82 | "title": "Sparse Observation List", 83 | "id": "#sparse-observation-list", 84 | "description": "A list of sparse observation objects", 85 | "items": { "$ref": "#/definitions/SparseObservation" } 86 | }, 87 | 88 | "Annotation": { 89 | "id": "#annotation", 90 | "type": "object", 91 | "title": "Annotation", 92 | "description": "An annotation of a single observation, e.g. tags", 93 | "properties": { 94 | "annotation_metadata": { "$ref": "#/definitions/AnnotationMetadata" }, 95 | "data": { 96 | "title": "Data", 97 | "description": "An array of observations", 98 | "oneOf": [ { "$ref": "#/definitions/DenseObservation" }, 99 | { "$ref": "#/definitions/SparseObservationList" } ] 100 | }, 101 | "namespace": {"type": "string"}, 102 | "sandbox": { "type": "object"}, 103 | "time": { 104 | "oneOf": [ {"type": "number", "minimum": 0.0}, 105 | {"type": "null"} ] 106 | }, 107 | "duration": { 108 | "oneOf": [ {"type": "number", "minimum": 0.0}, 109 | {"type": "null"} ] 110 | } 111 | }, 112 | "required": ["annotation_metadata", "data", "namespace"] 113 | } 114 | 115 | }, 116 | 117 | "type": "object", 118 | "title": "JAMS file", 119 | "description": "JSON Annotated Music Specification", 120 | "properties": { 121 | 122 | "file_metadata": { "$ref": "#/definitions/FileMetadata" }, 123 | 124 | "annotations": { 125 | "type": "array", 126 | "title": "Annotations", 127 | "description": "Array of annotations", 128 | "items": { "$ref": "#/definitions/Annotation" } 129 | }, 130 | 131 | "sandbox": { "type": "object" } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /jams/schemata/namespaces/beat/beat.json: -------------------------------------------------------------------------------- 1 | {"beat": { 2 | "value": { 3 | "oneOf": [ 4 | { 5 | "type": "number" 6 | }, 7 | { 8 | "type": "null" 9 | } 10 | ] 11 | }, 12 | "dense": false, 13 | "description": "Beat event markers with optional metrical position" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /jams/schemata/namespaces/beat/position.json: -------------------------------------------------------------------------------- 1 | {"beat_position": { 2 | "value": { 3 | "type": "object", 4 | "properties": { 5 | "position": {"type": "number", "minimum": 1}, 6 | "measure": {"type": "integer", "minimum": 0}, 7 | "num_beats": { "type": "integer", "minimum": 1}, 8 | "beat_units": { "enum": [1, 2, 4, 8, 16, 32, 64, 128, 256]} 9 | }, 10 | "required": ["position", "measure", "num_beats", "beat_units"] 11 | }, 12 | "dense": false, 13 | "description": "Tuples of (position, measure, beats per measure, beat units)" 14 | } 15 | } 16 | 17 | -------------------------------------------------------------------------------- /jams/schemata/namespaces/chord/chord.json: -------------------------------------------------------------------------------- 1 | {"chord": 2 | { 3 | "value": { 4 | "type": "string", 5 | "pattern": "^((N|X)|(([A-G](b*|#*))((:(maj|min|dim|aug|1|5|sus2|sus4|maj6|min6|7|maj7|min7|dim7|hdim7|minmaj7|aug7|9|maj9|min9|11|maj11|min11|13|maj13|min13)(\\((\\*?((b*|#*)([1-9]|1[0-3]?))(,\\*?((b*|#*)([1-9]|1[0-3]?)))*)\\))?)|(:\\((\\*?((b*|#*)([1-9]|1[0-3]?))(,\\*?((b*|#*)([1-9]|1[0-3]?)))*)\\)))?((/((b*|#*)([1-9]|1[0-3]?)))?)?))$" 6 | }, 7 | "dense": false, 8 | "description": "Expanded chord set. Includes: X, sus2, 1, 5, and 13" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /jams/schemata/namespaces/chord/chord.re: -------------------------------------------------------------------------------- 1 | ^((N|X)|(([A-G](b*|#*))((:(maj|min|dim|aug|1|5|sus2|sus4|maj6|min6|7|maj7|min7|dim7|hdim7|minmaj7|aug7|9|maj9|min9|11|maj11|min11|13|maj13|min13)(\((\*?((b*|#*)([1-9]|1[0-3]?))(,\*?((b*|#*)([1-9]|1[0-3]?)))*)\))?)|(:\((\*?((b*|#*)([1-9]|1[0-3]?))(,\*?((b*|#*)([1-9]|1[0-3]?)))*)\)))?((/((b*|#*)([1-9]|1[0-3]?)))?)?))$ 2 | -------------------------------------------------------------------------------- /jams/schemata/namespaces/chord/harte.json: -------------------------------------------------------------------------------- 1 | {"chord_harte": 2 | { 3 | "value": { 4 | "type": "string", 5 | "pattern": "^((N)|(([A-G][b#]*)((:(maj|min|dim|aug|maj7|min7|7|dim7|hdim7|minmaj7|maj6|min6|9|maj9|min9|sus4)(\\((\\*?([b#]*([1-9]|1[0-3]?))(,\\*?([b#]*([1-9]|1[0-3]?)))*)\\))?)|(:\\((\\*?([b#]*([1-9]|1[0-3]?))(,\\*?([b#]*([1-9]|1[0-3]?)))*)\\)))?((/([b#]*([1-9]|1[0-3]?)))?)?))$" 6 | }, 7 | "dense": false, 8 | "description": "Chord annotations following Harte et al., 2005." 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /jams/schemata/namespaces/chord/harte.re: -------------------------------------------------------------------------------- 1 | ^((N)|(([A-G][b#]*)((:(maj|min|dim|aug|maj7|min7|7|dim7|hdim7|minmaj7|maj6|min6|9|maj9|min9|sus4)(\((\*?([b#]*([1-9]|1[0-3]?))(,\*?([b#]*([1-9]|1[0-3]?)))*)\))?)|(:\((\*?([b#]*([1-9]|1[0-3]?))(,\*?([b#]*([1-9]|1[0-3]?)))*)\)))?((/([b#]*([1-9]|1[0-3]?)))?)?))$ 2 | -------------------------------------------------------------------------------- /jams/schemata/namespaces/chord/roman.json: -------------------------------------------------------------------------------- 1 | {"chord_roman": 2 | { 3 | "value": { 4 | "type": "object", 5 | "properties": { 6 | "tonic": { "type": "string", 7 | "pattern": "^[A-G][b#]?$" }, 8 | "chord": { 9 | "type": "string", 10 | "pattern": "^([b#]?(i|I|ii|II|iii|III|iv|IV|v|V|vi|VI|vii|VII))[osdhx+]?[0-9]?[0-9]?(/([b#]?(i|I|ii|II|iii|III|iv|IV|v|V|vi|VI|vii|VII)))?$" 11 | } 12 | }, 13 | "required": ["tonic", "chord"] 14 | }, 15 | "dense": false, 16 | "description": "Roman numeral chords: (tonic, chord)" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /jams/schemata/namespaces/chord/roman.re: -------------------------------------------------------------------------------- 1 | ^([b#]?(i|I|ii|II|iii|III|iv|IV|v|V|vi|VI|vii|VII))[osdhx+]?[0-9]?[0-9]?(/([b#]?(i|I|ii|II|iii|III|iv|IV|v|V|vi|VI|vii|VII)))?$ -------------------------------------------------------------------------------- /jams/schemata/namespaces/key/key_mode.json: -------------------------------------------------------------------------------- 1 | {"key_mode": 2 | { 3 | "value": { 4 | "type": "string", 5 | "pattern": 6 | "^N|([A-G][b#]?)(:(major|minor|ionian|dorian|phrygian|lydian|mixolydian|aeolian|locrian))?$" 7 | }, 8 | "dense": false, 9 | "description": "Key and optional mode (major/minor or Greek modes)" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /jams/schemata/namespaces/lyrics/bow.json: -------------------------------------------------------------------------------- 1 | {"lyrics_bow": 2 | { 3 | "value": { 4 | "type": "array", 5 | "items": { 6 | "type": "array", 7 | "items": [ 8 | {"oneOf": [ 9 | {"type": "string"}, 10 | {"type": "array", 11 | "items": {"type": "string"}} 12 | ] 13 | }, 14 | {"type": "number", "minimum": 0} 15 | ] 16 | } 17 | }, 18 | "dense": false, 19 | "description": "Bag of words or n-grams" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /jams/schemata/namespaces/lyrics/lyrics.json: -------------------------------------------------------------------------------- 1 | {"lyrics": 2 | { 3 | "value": { "type": "string" }, 4 | "dense": false, 5 | "description": "Open strings for lyrics annotations" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /jams/schemata/namespaces/misc/blob.json: -------------------------------------------------------------------------------- 1 | {"blob": 2 | { 3 | "dense": false, 4 | "description": "Binary object (blob)" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /jams/schemata/namespaces/misc/scaper.json: -------------------------------------------------------------------------------- 1 | {"scaper": 2 | { 3 | "value": { 4 | "type": "object", 5 | "properties": { 6 | "label": { 7 | "type": "string" 8 | }, 9 | "source_file": { 10 | "type": "string" 11 | }, 12 | "source_time": { 13 | "type": "number", "minimum": 0 14 | }, 15 | "event_time": { 16 | "type": "number", "minimum": 0 17 | }, 18 | "event_duration": { 19 | "type": "number", "minimum": 0, "exclusiveMinimum": true 20 | }, 21 | "snr": { 22 | "type": "number" 23 | }, 24 | "time_stretch": { 25 | "type": ["number", "null"], "minimum": 0, "exclusiveMinimum": true 26 | }, 27 | "pitch_shift": { 28 | "type": ["number", "null"] 29 | }, 30 | "role": { 31 | "enum": ["background", "foreground"] 32 | } 33 | } 34 | }, 35 | "dense": false, 36 | "description": "Sound event following the scaper format." 37 | } 38 | } -------------------------------------------------------------------------------- /jams/schemata/namespaces/misc/vector.json: -------------------------------------------------------------------------------- 1 | {"vector": 2 | { 3 | "value": { 4 | "type": "array", 5 | "items": {"type": "number"}, 6 | "minItems": 1 7 | }, 8 | "dense": false, 9 | "description": "Numerical vector annotations" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /jams/schemata/namespaces/mood/thayer.json: -------------------------------------------------------------------------------- 1 | {"mood_thayer": 2 | { 3 | "value": { 4 | "type": "array", 5 | "items": {"type": "number"}, 6 | "minItems": 2, 7 | "maxItems": 2 8 | }, 9 | "dense": false, 10 | "description": "Thayer mood model: (valence, arousal)" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /jams/schemata/namespaces/onset/onset.json: -------------------------------------------------------------------------------- 1 | {"onset": 2 | { 3 | "value": { }, 4 | "dense": false, 5 | "description": "Onset event markers" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /jams/schemata/namespaces/pattern/jku.json: -------------------------------------------------------------------------------- 1 | {"pattern_jku": 2 | { 3 | "value": { 4 | "type": "object", 5 | "properties": { 6 | "midi_pitch": {"type": "number"}, 7 | "morph_pitch": {"type": "number"}, 8 | "staff": {"type": "number"}, 9 | "pattern_id": { 10 | "type": "number", 11 | "minimum": 1 12 | }, 13 | "occurrence_id": { 14 | "type": "number", 15 | "minimum": 1 16 | } 17 | }, 18 | "required": ["midi_pitch", "morph_pitch", "staff", "pattern_id", "occurrence_id"] 19 | }, 20 | "dense": false, 21 | "description": "Pattern (MIDI pitch), including staff height, voice number, occurrence id, and pattern id" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /jams/schemata/namespaces/pitch/class.json: -------------------------------------------------------------------------------- 1 | {"pitch_class": 2 | { 3 | "value": { 4 | "type": "object", 5 | "properties": { 6 | "tonic": { "type": "string", "pattern": "^[A-G][b#]?$" }, 7 | "pitch": { "type": "integer" } 8 | }, 9 | "required": ["tonic", "pitch"] 10 | }, 11 | "dense": true, 12 | "description": "Pitch class in (tonic, pitch class) format" 13 | } 14 | } 15 | 16 | -------------------------------------------------------------------------------- /jams/schemata/namespaces/pitch/contour_hz.json: -------------------------------------------------------------------------------- 1 | {"pitch_contour": 2 | { 3 | "value": { "type": "object" }, 4 | "dense": true, 5 | "properties": { 6 | "index": {"type": "integer"}, 7 | "frequency": {"type": "number", "minimum": 0}, 8 | "voiced": {"type": "boolean"} 9 | }, 10 | "description": "Pitch contours: (index, frequency, voicing)" 11 | } 12 | } 13 | 14 | -------------------------------------------------------------------------------- /jams/schemata/namespaces/pitch/hz.json: -------------------------------------------------------------------------------- 1 | {"pitch_hz": 2 | { 3 | "value": { "type": "number" }, 4 | "dense": true, 5 | "description": "[DEPRECATED] Pitch in Hz" 6 | } 7 | } 8 | 9 | -------------------------------------------------------------------------------- /jams/schemata/namespaces/pitch/midi.json: -------------------------------------------------------------------------------- 1 | {"pitch_midi": 2 | { 3 | "value": { "type": "number" }, 4 | "dense": true, 5 | "description": "[DEPRECATED] Pitch in (fractional) MIDI note numbers" 6 | } 7 | } 8 | 9 | -------------------------------------------------------------------------------- /jams/schemata/namespaces/pitch/note_hz.json: -------------------------------------------------------------------------------- 1 | {"note_hz": 2 | { 3 | "value": { "type": "number", "minimum": 0}, 4 | "dense": false, 5 | "description": "Note pitches in Hz" 6 | } 7 | } 8 | 9 | -------------------------------------------------------------------------------- /jams/schemata/namespaces/pitch/note_midi.json: -------------------------------------------------------------------------------- 1 | {"note_midi": 2 | { 3 | "value": { "type": "number" }, 4 | "dense": false, 5 | "description": "Note pitches in (fractional) MIDI note numbers" 6 | } 7 | } 8 | 9 | -------------------------------------------------------------------------------- /jams/schemata/namespaces/segment/multi.json: -------------------------------------------------------------------------------- 1 | {"multi_segment": 2 | { 3 | "value": { 4 | "type": "object", 5 | "properties": { 6 | "label": {"type": "string" }, 7 | "level": {"type": "integer", "minimum": 0} 8 | }, 9 | "required": ["label", "level"] 10 | }, 11 | "dense": false, 12 | "description": "Multi-level segmentation: (label, level)" 13 | } 14 | } 15 | 16 | -------------------------------------------------------------------------------- /jams/schemata/namespaces/segment/open.json: -------------------------------------------------------------------------------- 1 | {"segment_open": 2 | { 3 | "value": { "type": "string" }, 4 | "dense": false, 5 | "description": "Open vocabulary segment labels" 6 | } 7 | } 8 | 9 | -------------------------------------------------------------------------------- /jams/schemata/namespaces/segment/salami_function.json: -------------------------------------------------------------------------------- 1 | {"segment_salami_function": 2 | { 3 | "value": { 4 | "enum": [ 5 | "applause", 6 | "backing", 7 | "bridge", 8 | "break", 9 | "build", 10 | "call_and_response", 11 | "chorus", 12 | "coda", 13 | "contrasting_middle", 14 | "count-in", 15 | "crowd_sounds", 16 | "da_capo", 17 | "development", 18 | "end", 19 | "exposition", 20 | "fadeout", 21 | "fade-out", 22 | "head", 23 | "groove", 24 | "gypsy", 25 | "instrumental", 26 | "interlude", 27 | "intro", 28 | "introduction", 29 | "main theme", 30 | "no_function", 31 | "ostinato", 32 | "outro", 33 | "pre-chorus", 34 | "pre-verse", 35 | "pick-up", 36 | "post-cadential", 37 | "post-chorus", 38 | "post-verse", 39 | "recapitulation", 40 | "ritornello", 41 | "secondary theme", 42 | "silence", 43 | "solo", 44 | "spoken", 45 | "stage_sounds", 46 | "theme", 47 | "third theme", 48 | "trans", 49 | "transition", 50 | "variation", 51 | "verse", 52 | "voice", 53 | "voice_male", 54 | "voice_female" 55 | ] 56 | }, 57 | "dense": false, 58 | "description": "SALAMI functional label segmentation" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /jams/schemata/namespaces/segment/salami_lower.json: -------------------------------------------------------------------------------- 1 | {"segment_salami_lower": 2 | { 3 | "value": { 4 | "type": "string", 5 | "pattern": "^[a-z][a-z]?'*$|^[Ss]ilence$" 6 | }, 7 | "dense": false, 8 | "description": "SALAMI lowercase segmentations" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /jams/schemata/namespaces/segment/salami_upper.json: -------------------------------------------------------------------------------- 1 | {"segment_salami_upper": 2 | { 3 | "value": { 4 | "type": "string", 5 | "pattern": "^[A-Z]'*$|^[Ss]ilence$" 6 | }, 7 | "dense": false, 8 | "description": "SALAMI uppercase segmentations" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /jams/schemata/namespaces/segment/tut.json: -------------------------------------------------------------------------------- 1 | {"segment_tut": 2 | { 3 | "value": { 4 | "enum": [ 5 | "Break", 6 | "Bridge", 7 | "BridgeA", 8 | "BridgeB", 9 | "BridgeC", 10 | "Close", 11 | "Closing", 12 | "GuitarS", 13 | "Intro", 14 | "MR", 15 | "Out", 16 | "Outro", 17 | "OutroS", 18 | "Outro_", 19 | "Refrain", 20 | "Refrain+Coda", 21 | "RefrainA", 22 | "RefrainB", 23 | "RefrainL", 24 | "RefrainO", 25 | "RefrainS", 26 | "Si", 27 | "Solo(ver+ref)", 28 | "Verse", 29 | "VerseA", 30 | "VerseAS", 31 | "VerseB", 32 | "VerseC", 33 | "VerseH", 34 | "VerseHS", 35 | "VerseO", 36 | "VerseS", 37 | "break", 38 | "break(intro)", 39 | "breakS", 40 | "bridge", 41 | "bridgeA", 42 | "bridgeAS", 43 | "bridgeB", 44 | "bridgeBS", 45 | "bridgeH", 46 | "bridgeS", 47 | "improI", 48 | "improV", 49 | "interlude", 50 | "interludeA", 51 | "interludeB", 52 | "intro", 53 | "introB", 54 | "introH", 55 | "long_connector", 56 | "outro", 57 | "outroA", 58 | "outroB", 59 | "refrain", 60 | "refrainA", 61 | "refrainB", 62 | "refrainS", 63 | "short_connector", 64 | "silence", 65 | "verse", 66 | "verseA", 67 | "verseB", 68 | "verseBS", 69 | "verseC", 70 | "verseH", 71 | "verseHS", 72 | "verseI", 73 | "verseS", 74 | "verseV" 75 | ] 76 | }, 77 | "dense": false, 78 | "description": "TUT Beatles segment labels" 79 | } 80 | } 81 | 82 | -------------------------------------------------------------------------------- /jams/schemata/namespaces/tag/cal500.json: -------------------------------------------------------------------------------- 1 | { 2 | "tag_cal500": { 3 | "dense": false, 4 | "description": "CAL500 tags", 5 | "value": { 6 | "enum": [ 7 | "Emotion-Angry_/_Aggressive", 8 | "NOT-Emotion-Angry_/_Aggressive", 9 | "Emotion-Arousing_/_Awakening", 10 | "NOT-Emotion-Arousing_/_Awakening", 11 | "Emotion-Bizarre_/_Weird", 12 | "NOT-Emotion-Bizarre_/_Weird", 13 | "Emotion-Calming_/_Soothing", 14 | "NOT-Emotion-Calming_/_Soothing", 15 | "Emotion-Carefree_/_Lighthearted", 16 | "NOT-Emotion-Carefree_/_Lighthearted", 17 | "Emotion-Cheerful_/_Festive", 18 | "NOT-Emotion-Cheerful_/_Festive", 19 | "Emotion-Emotional_/_Passionate", 20 | "NOT-Emotion-Emotional_/_Passionate", 21 | "Emotion-Exciting_/_Thrilling", 22 | "NOT-Emotion-Exciting_/_Thrilling", 23 | "Emotion-Happy", 24 | "NOT-Emotion-Happy", 25 | "Emotion-Laid-back_/_Mellow", 26 | "NOT-Emotion-Laid-back_/_Mellow", 27 | "Emotion-Light_/_Playful", 28 | "NOT-Emotion-Light_/_Playful", 29 | "Emotion-Loving_/_Romantic", 30 | "NOT-Emotion-Loving_/_Romantic", 31 | "Emotion-Pleasant_/_Comfortable", 32 | "NOT-Emotion-Pleasant_/_Comfortable", 33 | "Emotion-Positive_/_Optimistic", 34 | "NOT-Emotion-Positive_/_Optimistic", 35 | "Emotion-Powerful_/_Strong", 36 | "NOT-Emotion-Powerful_/_Strong", 37 | "Emotion-Sad", 38 | "NOT-Emotion-Sad", 39 | "Emotion-Tender_/_Soft", 40 | "NOT-Emotion-Tender_/_Soft", 41 | "Emotion-Touching_/_Loving", 42 | "NOT-Emotion-Touching_/_Loving", 43 | "Genre--_Alternative", 44 | "Genre--_Alternative_Folk", 45 | "Genre--_Bebop", 46 | "Genre--_Brit_Pop", 47 | "Genre--_Classic_Rock", 48 | "Genre--_Contemporary_Blues", 49 | "Genre--_Contemporary_R&B", 50 | "Genre--_Cool_Jazz", 51 | "Genre--_Country_Blues", 52 | "Genre--_Dance_Pop", 53 | "Genre--_Electric_Blues", 54 | "Genre--_Funk", 55 | "Genre--_Gospel", 56 | "Genre--_Metal/Hard_Rock", 57 | "Genre--_Punk", 58 | "Genre--_Roots_Rock", 59 | "Genre--_Singer_/_Songwriter", 60 | "Genre--_Soft_Rock", 61 | "Genre--_Soul", 62 | "Genre--_Swing", 63 | "Genre-Bluegrass", 64 | "Genre-Blues", 65 | "Genre-Country", 66 | "Genre-Electronica", 67 | "Genre-Folk", 68 | "Genre-Hip_Hop/Rap", 69 | "Genre-Jazz", 70 | "Genre-Pop", 71 | "Genre-R&B", 72 | "Genre-Rock", 73 | "Genre-World", 74 | "Instrument_-_Acoustic_Guitar", 75 | "Instrument_-_Ambient_Sounds", 76 | "Instrument_-_Backing_vocals", 77 | "Instrument_-_Bass", 78 | "Instrument_-_Drum_Machine", 79 | "Instrument_-_Drum_Set", 80 | "Instrument_-_Electric_Guitar_(clean)", 81 | "Instrument_-_Electric_Guitar_(distorted)", 82 | "Instrument_-_Female_Lead_Vocals", 83 | "Instrument_-_Hand_Drums", 84 | "Instrument_-_Harmonica", 85 | "Instrument_-_Horn_Section", 86 | "Instrument_-_Male_Lead_Vocals", 87 | "Instrument_-_Organ", 88 | "Instrument_-_Piano", 89 | "Instrument_-_Samples", 90 | "Instrument_-_Saxophone", 91 | "Instrument_-_Sequencer", 92 | "Instrument_-_String_Ensemble", 93 | "Instrument_-_Synthesizer", 94 | "Instrument_-_Tambourine", 95 | "Instrument_-_Trombone", 96 | "Instrument_-_Trumpet", 97 | "Instrument_-_Violin/Fiddle", 98 | "Song-Catchy/Memorable", 99 | "NOT-Song-Catchy/Memorable", 100 | "Song-Changing_Energy_Level", 101 | "NOT-Song-Changing_Energy_Level", 102 | "Song-Fast_Tempo", 103 | "NOT-Song-Fast_Tempo", 104 | "Song-Heavy_Beat", 105 | "NOT-Song-Heavy_Beat", 106 | "Song-High_Energy", 107 | "NOT-Song-High_Energy", 108 | "Song-Like", 109 | "NOT-Song-Like", 110 | "Song-Positive_Feelings", 111 | "NOT-Song-Positive_Feelings", 112 | "Song-Quality", 113 | "NOT-Song-Quality", 114 | "Song-Recommend", 115 | "NOT-Song-Recommend", 116 | "Song-Recorded", 117 | "NOT-Song-Recorded", 118 | "Song-Texture_Acoustic", 119 | "Song-Texture_Electric", 120 | "Song-Texture_Synthesized", 121 | "Song-Tonality", 122 | "NOT-Song-Tonality", 123 | "Song-Very_Danceable", 124 | "NOT-Song-Very_Danceable", 125 | "Usage-At_a_party", 126 | "Usage-At_work", 127 | "Usage-Cleaning_the_house", 128 | "Usage-Driving", 129 | "Usage-Exercising", 130 | "Usage-Getting_ready_to_go_out", 131 | "Usage-Going_to_sleep", 132 | "Usage-Hanging_with_friends", 133 | "Usage-Intensely_Listening", 134 | "Usage-Reading", 135 | "Usage-Romancing", 136 | "Usage-Sleeping", 137 | "Usage-Studying", 138 | "Usage-Waking_up", 139 | "Usage-With_the_family", 140 | "Vocals-Aggressive", 141 | "Vocals-Altered_with_Effects", 142 | "Vocals-Breathy", 143 | "Vocals-Call_&_Response", 144 | "Vocals-Duet", 145 | "Vocals-Emotional", 146 | "Vocals-Falsetto", 147 | "Vocals-Gravelly", 148 | "Vocals-High-pitched", 149 | "Vocals-Low-pitched", 150 | "Vocals-Monotone", 151 | "Vocals-Rapping", 152 | "Vocals-Screaming", 153 | "Vocals-Spoken", 154 | "Vocals-Strong", 155 | "Vocals-Vocal_Harmonies", 156 | "Genre-Best--_Alternative", 157 | "Genre-Best--_Classic_Rock", 158 | "Genre-Best--_Metal/Hard_Rock", 159 | "Genre-Best--_Punk", 160 | "Genre-Best--_Soft_Rock", 161 | "Genre-Best--_Soul", 162 | "Genre-Best-Blues", 163 | "Genre-Best-Country", 164 | "Genre-Best-Electronica", 165 | "Genre-Best-Folk", 166 | "Genre-Best-Hip_Hop/Rap", 167 | "Genre-Best-Jazz", 168 | "Genre-Best-Pop", 169 | "Genre-Best-R&B", 170 | "Genre-Best-Rock", 171 | "Genre-Best-World", 172 | "Instrument_-_Acoustic_Guitar-Solo", 173 | "Instrument_-_Electric_Guitar_(clean)-Solo", 174 | "Instrument_-_Electric_Guitar_(distorted)-Solo", 175 | "Instrument_-_Female_Lead_Vocals-Solo", 176 | "Instrument_-_Harmonica-Solo", 177 | "Instrument_-_Male_Lead_Vocals-Solo", 178 | "Instrument_-_Piano-Solo", 179 | "Instrument_-_Saxophone-Solo", 180 | "Instrument_-_Trumpet-Solo" 181 | ] 182 | } 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /jams/schemata/namespaces/tag/gtzan.json: -------------------------------------------------------------------------------- 1 | {"tag_gtzan": 2 | { 3 | "value": { 4 | "enum": [ 5 | "blues", 6 | "classical", 7 | "country", 8 | "disco", 9 | "hip-hop", 10 | "jazz", 11 | "metal", 12 | "pop", 13 | "reggae", 14 | "rock" 15 | ] 16 | }, 17 | "dense": false, 18 | "description": "GTZAN 10-class genre annotation" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /jams/schemata/namespaces/tag/medleydb_instruments.json: -------------------------------------------------------------------------------- 1 | {"tag_medleydb_instruments": 2 | { 3 | "value": { 4 | "enum": [ 5 | "accordion", 6 | "acoustic guitar", 7 | "alto saxophone", 8 | "auxiliary percussion", 9 | "bamboo flute", 10 | "banjo", 11 | "baritone saxophone", 12 | "bass clarinet", 13 | "bass drum", 14 | "bassoon", 15 | "bongo", 16 | "brass section", 17 | "cello", 18 | "cello section", 19 | "chimes", 20 | "claps", 21 | "clarinet", 22 | "clarinet section", 23 | "clean electric guitar", 24 | "cymbal", 25 | "darbuka", 26 | "distorted electric guitar", 27 | "dizi", 28 | "double bass", 29 | "doumbek", 30 | "drum machine", 31 | "drum set", 32 | "electric bass", 33 | "electric piano", 34 | "erhu", 35 | "female singer", 36 | "flute", 37 | "flute section", 38 | "french horn", 39 | "french horn section", 40 | "fx/processed sound", 41 | "glockenspiel", 42 | "gong", 43 | "gu", 44 | "guzheng", 45 | "harmonica", 46 | "harp", 47 | "horn section", 48 | "kick drum", 49 | "lap steel guitar", 50 | "liuqin", 51 | "male rapper", 52 | "male singer", 53 | "male speaker", 54 | "mandolin", 55 | "melodica", 56 | "oboe", 57 | "oud", 58 | "piano", 59 | "piccolo", 60 | "sampler", 61 | "scratches", 62 | "shaker", 63 | "snare drum", 64 | "soprano saxophone", 65 | "string section", 66 | "synthesizer", 67 | "tabla", 68 | "tack piano", 69 | "tambourine", 70 | "tenor saxophone", 71 | "timpani", 72 | "toms", 73 | "trombone", 74 | "trombone section", 75 | "trumpet", 76 | "trumpet section", 77 | "tuba", 78 | "vibraphone", 79 | "viola", 80 | "viola section", 81 | "violin", 82 | "violin section", 83 | "vocalists", 84 | "yangqin", 85 | "zhongruan" 86 | ] 87 | }, 88 | "dense": false, 89 | "description": "MedleyDB instrument source annotations" 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /jams/schemata/namespaces/tag/msd_tagtraum_cd1.json: -------------------------------------------------------------------------------- 1 | {"tag_msd_tagtraum_cd1": 2 | { 3 | "value": { 4 | "enum": [ 5 | "reggae", 6 | "pop/rock", 7 | "rnb", 8 | "jazz", 9 | "vocal", 10 | "new age", 11 | "latin", 12 | "rap", 13 | "country", 14 | "international", 15 | "blues", 16 | "electronic", 17 | "folk" 18 | ] 19 | }, 20 | "confidence": { 21 | "oneOf": [ {"type": "number", "minimum": 0.0, "maximum": 1.0}, 22 | {"type": "null"} ] 23 | }, 24 | "dense": false, 25 | "description": "MSD tagtraum cd1 13-class genre annotation" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /jams/schemata/namespaces/tag/msd_tagtraum_cd2.json: -------------------------------------------------------------------------------- 1 | {"tag_msd_tagtraum_cd2": 2 | { 3 | "value": { 4 | "enum": [ 5 | "reggae", 6 | "latin", 7 | "metal", 8 | "rnb", 9 | "jazz", 10 | "punk", 11 | "pop", 12 | "new age", 13 | "country", 14 | "rap", 15 | "rock", 16 | "world", 17 | "blues", 18 | "electronic", 19 | "folk" 20 | ] 21 | }, 22 | "confidence": { 23 | "oneOf": [ {"type": "number", "minimum": 0.0, "maximum": 1.0}, 24 | {"type": "null"} ] 25 | }, 26 | "dense": false, 27 | "description": "MSD tagtraum cd2 15-class genre annotation" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /jams/schemata/namespaces/tag/open.json: -------------------------------------------------------------------------------- 1 | {"tag_open": 2 | { 3 | "value": { "type": "string" }, 4 | "dense": false, 5 | "description": "Open tag vocabularies allow all strings" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /jams/schemata/namespaces/tag/tag_audioset_genre.json: -------------------------------------------------------------------------------- 1 | { 2 | "tag_audioset_genre": { 3 | "description": "AudioSet musical genres", 4 | "value": { 5 | "enum": [ 6 | "A capella", 7 | "Afrobeat", 8 | "Ambient music", 9 | "Beatboxing", 10 | "Bluegrass", 11 | "Blues", 12 | "Carnatic music", 13 | "Chant", 14 | "Christian music", 15 | "Classical music", 16 | "Country", 17 | "Cumbia", 18 | "Disco", 19 | "Drone music", 20 | "Drum and bass", 21 | "Dub", 22 | "Dubstep", 23 | "Electro", 24 | "Electronic dance music", 25 | "Electronic music", 26 | "Electronica", 27 | "Flamenco", 28 | "Folk music", 29 | "Funk", 30 | "Funk carioca", 31 | "Gospel music", 32 | "Grime music", 33 | "Grunge", 34 | "Heavy metal", 35 | "Hip hop music", 36 | "House music", 37 | "Independent music", 38 | "Jazz", 39 | "Kuduro", 40 | "Kwaito", 41 | "Mantra", 42 | "Middle Eastern music", 43 | "Music for children", 44 | "Music of Africa", 45 | "Music of Asia", 46 | "Music of Bollywood", 47 | "Music of Latin America", 48 | "New-age music", 49 | "Noise music", 50 | "Oldschool jungle", 51 | "Opera", 52 | "Pop music", 53 | "Progressive rock", 54 | "Psychedelic rock", 55 | "Punk rock", 56 | "Reggae", 57 | "Rhythm and blues", 58 | "Rock and roll", 59 | "Rock music", 60 | "Salsa music", 61 | "Ska", 62 | "Soca music", 63 | "Soul music", 64 | "Swing music", 65 | "Techno", 66 | "Traditional music", 67 | "Trance music", 68 | "Trap music", 69 | "UK garage", 70 | "Vocal music" 71 | ] 72 | }, 73 | "dense": false 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /jams/schemata/namespaces/tag/tag_audioset_instruments.json: -------------------------------------------------------------------------------- 1 | { 2 | "tag_audioset_instruments": { 3 | "dense": false, 4 | "value": { 5 | "enum": [ 6 | "Accordion", 7 | "Acoustic guitar", 8 | "Alto saxophone", 9 | "Bagpipes", 10 | "Banjo", 11 | "Bass (instrument role)", 12 | "Bass drum", 13 | "Bass guitar", 14 | "Bassoon", 15 | "Bell", 16 | "Bicycle bell", 17 | "Bowed string instrument", 18 | "Brass instrument", 19 | "Bugle", 20 | "Cello", 21 | "Change ringing (campanology)", 22 | "Chime", 23 | "Choir", 24 | "Church bell", 25 | "Clarinet", 26 | "Clavinet", 27 | "Cornet", 28 | "Cowbell", 29 | "Crash cymbal", 30 | "Cymbal", 31 | "Didgeridoo", 32 | "Double bass", 33 | "Drum", 34 | "Drum kit", 35 | "Drum machine", 36 | "Drum roll", 37 | "Electric guitar", 38 | "Electric piano", 39 | "Electronic organ", 40 | "Flute", 41 | "French horn", 42 | "Glockenspiel", 43 | "Gong", 44 | "Guitar", 45 | "Hammond organ", 46 | "Harmonica", 47 | "Harp", 48 | "Harpsichord", 49 | "Hi-hat", 50 | "Jingle bell", 51 | "Keyboard (musical)", 52 | "Mallet percussion", 53 | "Mandolin", 54 | "Maraca", 55 | "Marimba, xylophone", 56 | "Mellotron", 57 | "Musical ensemble", 58 | "Oboe", 59 | "Orchestra", 60 | "Organ", 61 | "Percussion", 62 | "Piano", 63 | "Pizzicato", 64 | "Plucked string instrument", 65 | "Rattle (instrument)", 66 | "Rhodes piano", 67 | "Rimshot", 68 | "Sampler", 69 | "Saxophone", 70 | "Scratching (performance technique)", 71 | "Shofar", 72 | "Singing bowl", 73 | "Sitar", 74 | "Snare drum", 75 | "Soprano saxophone", 76 | "Steel guitar, slide guitar", 77 | "Steelpan", 78 | "String section", 79 | "Strum", 80 | "Synthesizer", 81 | "Tabla", 82 | "Tambourine", 83 | "Tapping (guitar technique)", 84 | "Theremin", 85 | "Timpani", 86 | "Trombone", 87 | "Trumpet", 88 | "Tubular bells", 89 | "Tuning fork", 90 | "Ukulele", 91 | "Vibraphone", 92 | "Violin, fiddle", 93 | "Wind chime", 94 | "Wind instrument, woodwind instrument", 95 | "Wood block", 96 | "Zither" 97 | ] 98 | }, 99 | "description": "AudioSet musical instruments" 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /jams/schemata/namespaces/tag/tag_fma_genre.json: -------------------------------------------------------------------------------- 1 | { 2 | "tag_fma_genre": { 3 | "description": "Free Music Archive genres", 4 | "value": { 5 | "enum": [ 6 | "Blues", 7 | "Classical", 8 | "Country", 9 | "Easy Listening", 10 | "Electronic", 11 | "Experimental", 12 | "Folk", 13 | "Hip-Hop", 14 | "Instrumental", 15 | "International", 16 | "Jazz", 17 | "Old-Time / Historic", 18 | "Pop", 19 | "Rock", 20 | "Soul-RnB", 21 | "Spoken" 22 | ] 23 | }, 24 | "dense": false 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /jams/schemata/namespaces/tag/tag_fma_subgenre.json: -------------------------------------------------------------------------------- 1 | { 2 | "tag_fma_subgenre": { 3 | "description": "Free Music Archive genres and sub-genres", 4 | "value": { 5 | "enum": [ 6 | "20th Century Classical", 7 | "Abstract Hip-Hop", 8 | "African", 9 | "Afrobeat", 10 | "Alternative Hip-Hop", 11 | "Ambient", 12 | "Ambient Electronic", 13 | "Americana", 14 | "Asia-Far East", 15 | "Audio Collage", 16 | "Avant-Garde", 17 | "Balkan", 18 | "Banter", 19 | "Be-Bop", 20 | "Big Band/Swing", 21 | "Bigbeat", 22 | "Black-Metal", 23 | "Bluegrass", 24 | "Blues", 25 | "Bollywood", 26 | "Brazilian", 27 | "Breakbeat", 28 | "Breakcore - Hard", 29 | "British Folk", 30 | "Celtic", 31 | "Chamber Music", 32 | "Chill-out", 33 | "Chip Music", 34 | "Chiptune", 35 | "Choral Music", 36 | "Christmas", 37 | "Classical", 38 | "Comedy", 39 | "Compilation", 40 | "Composed Music", 41 | "Contemporary Classical", 42 | "Country", 43 | "Country & Western", 44 | "Cumbia", 45 | "Dance", 46 | "Death-Metal", 47 | "Deep Funk", 48 | "Disco", 49 | "Downtempo", 50 | "Drone", 51 | "Drum & Bass", 52 | "Dubstep", 53 | "Easy Listening", 54 | "Easy Listening: Vocal", 55 | "Electroacoustic", 56 | "Electronic", 57 | "Electro-Punk", 58 | "Europe", 59 | "Experimental", 60 | "Experimental Pop", 61 | "Fado", 62 | "Field Recordings", 63 | "Flamenco", 64 | "Folk", 65 | "Freak-Folk", 66 | "Free-Folk", 67 | "Free-Jazz", 68 | "French", 69 | "Funk", 70 | "Garage", 71 | "Glitch", 72 | "Gospel", 73 | "Goth", 74 | "Grindcore", 75 | "Hardcore", 76 | "Hip-Hop", 77 | "Hip-Hop Beats", 78 | "Holiday", 79 | "House", 80 | "IDM", 81 | "Improv", 82 | "Indian", 83 | "Indie-Rock", 84 | "Industrial", 85 | "Instrumental", 86 | "International", 87 | "Interview", 88 | "Jazz", 89 | "Jazz: Out", 90 | "Jazz: Vocal", 91 | "Jungle", 92 | "Kid-Friendly", 93 | "Klezmer", 94 | "Krautrock", 95 | "Latin", 96 | "Latin America", 97 | "Lo-Fi", 98 | "Loud-Rock", 99 | "Lounge", 100 | "Metal", 101 | "Middle East", 102 | "Minimal Electronic", 103 | "Minimalism", 104 | "Modern Jazz", 105 | "Musical Theater", 106 | "Musique Concrete", 107 | "Nerdcore", 108 | "New Age", 109 | "New Wave", 110 | "N. Indian Traditional", 111 | "Noise", 112 | "Noise-Rock", 113 | "North African", 114 | "Novelty", 115 | "No Wave", 116 | "Nu-Jazz", 117 | "Old-Time / Historic", 118 | "Opera", 119 | "Pacific", 120 | "Poetry", 121 | "Polka", 122 | "Pop", 123 | "Post-Punk", 124 | "Post-Rock", 125 | "Power-Pop", 126 | "Progressive", 127 | "Psych-Folk", 128 | "Psych-Rock", 129 | "Punk", 130 | "Radio", 131 | "Radio Art", 132 | "Radio Theater", 133 | "Rap", 134 | "Reggae - Dancehall", 135 | "Reggae - Dub", 136 | "Rock", 137 | "Rockabilly", 138 | "Rock Opera", 139 | "Romany (Gypsy)", 140 | "Salsa", 141 | "Shoegaze", 142 | "Singer-Songwriter", 143 | "Skweee", 144 | "Sludge", 145 | "Soul-RnB", 146 | "Sound Art", 147 | "Sound Collage", 148 | "Sound Effects", 149 | "Sound Poetry", 150 | "Soundtrack", 151 | "South Indian Traditional", 152 | "Space-Rock", 153 | "Spanish", 154 | "Spoken", 155 | "Spoken Weird", 156 | "Spoken Word", 157 | "Surf", 158 | "Symphony", 159 | "Synth Pop", 160 | "Talk Radio", 161 | "Tango", 162 | "Techno", 163 | "Thrash", 164 | "Trip-Hop", 165 | "Turkish", 166 | "Unclassifiable", 167 | "Western Swing", 168 | "Wonky" 169 | ] 170 | }, 171 | "dense": false 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /jams/schemata/namespaces/tag/tag_urbansound.json: -------------------------------------------------------------------------------- 1 | { 2 | "tag_urbansound": { 3 | "description": "Urban sound classes as per UrbanSound, UrbanSound8K and URBAN-SED datasets", 4 | "value": { 5 | "enum": [ 6 | "air_conditioner", 7 | "car_horn", 8 | "children_playing", 9 | "dog_bark", 10 | "drilling", 11 | "engine_idling", 12 | "gun_shot", 13 | "jackhammer", 14 | "siren", 15 | "street_music" 16 | ] 17 | }, 18 | "dense": false 19 | } 20 | } -------------------------------------------------------------------------------- /jams/schemata/namespaces/tempo/tempo.json: -------------------------------------------------------------------------------- 1 | {"tempo": 2 | { 3 | "value": { 4 | "type": "number", 5 | "minimum": 0 6 | }, 7 | "confidence": { 8 | "type": "number", 9 | "minimum": 0, 10 | "maximum": 1.0 11 | }, 12 | "dense": false, 13 | "description": "Tempo measurements, in beats per minute (BPM)" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /jams/schemata/validate.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | '''Validator script for jams files''' 3 | 4 | import argparse 5 | import sys 6 | import json 7 | import jsonschema 8 | 9 | 10 | def process_arguments(args): 11 | '''Argument parser''' 12 | parser = argparse.ArgumentParser(description='JAMS schema validator') 13 | 14 | parser.add_argument('schema_file', 15 | action='store', 16 | help='path to the schema file') 17 | parser.add_argument('jams_files', 18 | action='store', 19 | nargs='+', 20 | help='path to one or more JAMS files') 21 | 22 | return vars(parser.parse_args(args)) 23 | 24 | 25 | def load_json(filename): 26 | '''Load a json file''' 27 | with open(filename, 'r') as fdesc: 28 | return json.load(fdesc) 29 | 30 | 31 | def validate(schema_file=None, jams_files=None): 32 | '''Validate a jams file against a schema''' 33 | 34 | schema = load_json(schema_file) 35 | 36 | for jams_file in jams_files: 37 | try: 38 | jams = load_json(jams_file) 39 | jsonschema.validate(jams, schema) 40 | print '{:s} was successfully validated'.format(jams_file) 41 | except jsonschema.ValidationError as exc: 42 | print '{:s} was NOT successfully validated'.format(jams_file) 43 | 44 | print exc 45 | 46 | 47 | if __name__ == '__main__': 48 | validate(**process_arguments(sys.argv[1:])) 49 | -------------------------------------------------------------------------------- /jams/sonify.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # CREATED:2015-12-12 18:20:37 by Brian McFee 3 | r''' 4 | Sonification 5 | ------------ 6 | 7 | .. autosummary:: 8 | :toctree: generated/ 9 | 10 | sonify 11 | ''' 12 | 13 | from itertools import product 14 | from collections import OrderedDict, defaultdict 15 | import six 16 | import numpy as np 17 | import mir_eval.sonify 18 | from mir_eval.util import filter_kwargs 19 | from .eval import coerce_annotation, hierarchy_flatten 20 | from .exceptions import NamespaceError 21 | 22 | __all__ = ['sonify'] 23 | 24 | 25 | def mkclick(freq, sr=22050, duration=0.1): 26 | '''Generate a click sample. 27 | 28 | This replicates functionality from mir_eval.sonify.clicks, 29 | but exposes the target frequency and duration. 30 | ''' 31 | 32 | times = np.arange(int(sr * duration)) 33 | click = np.sin(2 * np.pi * times * freq / float(sr)) 34 | click *= np.exp(- times / (1e-2 * sr)) 35 | 36 | return click 37 | 38 | 39 | def clicks(annotation, sr=22050, length=None, **kwargs): 40 | '''Sonify events with clicks. 41 | 42 | This uses mir_eval.sonify.clicks, and is appropriate for instantaneous 43 | events such as beats or segment boundaries. 44 | ''' 45 | 46 | interval, _ = annotation.to_interval_values() 47 | 48 | return filter_kwargs(mir_eval.sonify.clicks, interval[:, 0], 49 | fs=sr, length=length, **kwargs) 50 | 51 | 52 | def downbeat(annotation, sr=22050, length=None, **kwargs): 53 | '''Sonify beats and downbeats together. 54 | ''' 55 | 56 | beat_click = mkclick(440 * 2, sr=sr) 57 | downbeat_click = mkclick(440 * 3, sr=sr) 58 | 59 | intervals, values = annotation.to_interval_values() 60 | 61 | beats, downbeats = [], [] 62 | 63 | for time, value in zip(intervals[:, 0], values): 64 | if value['position'] == 1: 65 | downbeats.append(time) 66 | else: 67 | beats.append(time) 68 | 69 | if length is None: 70 | length = int(sr * np.max(intervals)) + len(beat_click) + 1 71 | 72 | y = filter_kwargs(mir_eval.sonify.clicks, 73 | np.asarray(beats), 74 | fs=sr, length=length, click=beat_click) 75 | 76 | y += filter_kwargs(mir_eval.sonify.clicks, 77 | np.asarray(downbeats), 78 | fs=sr, length=length, click=downbeat_click) 79 | 80 | return y 81 | 82 | 83 | def multi_segment(annotation, sr=22050, length=None, **kwargs): 84 | '''Sonify multi-level segmentations''' 85 | 86 | # Pentatonic scale, because why not 87 | PENT = [1, 32./27, 4./3, 3./2, 16./9] 88 | DURATION = 0.1 89 | 90 | h_int, _ = hierarchy_flatten(annotation) 91 | 92 | if length is None: 93 | length = int(sr * (max(np.max(_) for _ in h_int) + 1. / DURATION) + 1) 94 | 95 | y = 0.0 96 | for ints, (oc, scale) in zip(h_int, product(range(3, 3 + len(h_int)), 97 | PENT)): 98 | click = mkclick(440.0 * scale * oc, sr=sr, duration=DURATION) 99 | y = y + filter_kwargs(mir_eval.sonify.clicks, 100 | np.unique(ints), 101 | fs=sr, length=length, 102 | click=click) 103 | return y 104 | 105 | 106 | def chord(annotation, sr=22050, length=None, **kwargs): 107 | '''Sonify chords 108 | 109 | This uses mir_eval.sonify.chords. 110 | ''' 111 | 112 | intervals, chords = annotation.to_interval_values() 113 | 114 | return filter_kwargs(mir_eval.sonify.chords, 115 | chords, intervals, 116 | fs=sr, length=length, 117 | **kwargs) 118 | 119 | 120 | def pitch_contour(annotation, sr=22050, length=None, **kwargs): 121 | '''Sonify pitch contours. 122 | 123 | This uses mir_eval.sonify.pitch_contour, and should only be applied 124 | to pitch annotations using the pitch_contour namespace. 125 | 126 | Each contour is sonified independently, and the resulting waveforms 127 | are summed together. 128 | ''' 129 | 130 | # Map contours to lists of observations 131 | 132 | times = defaultdict(list) 133 | freqs = defaultdict(list) 134 | 135 | for obs in annotation: 136 | times[obs.value['index']].append(obs.time) 137 | freqs[obs.value['index']].append(obs.value['frequency'] * 138 | (-1)**(~obs.value['voiced'])) 139 | 140 | y_out = 0.0 141 | for ix in times: 142 | y_out = y_out + filter_kwargs(mir_eval.sonify.pitch_contour, 143 | np.asarray(times[ix]), 144 | np.asarray(freqs[ix]), 145 | fs=sr, length=length, 146 | **kwargs) 147 | if length is None: 148 | length = len(y_out) 149 | 150 | return y_out 151 | 152 | 153 | def piano_roll(annotation, sr=22050, length=None, **kwargs): 154 | '''Sonify a piano-roll 155 | 156 | This uses mir_eval.sonify.time_frequency, and is appropriate 157 | for sparse transcription data, e.g., annotations in the `note_midi` 158 | namespace. 159 | ''' 160 | 161 | intervals, pitches = annotation.to_interval_values() 162 | 163 | # Construct the pitchogram 164 | pitch_map = {f: idx for idx, f in enumerate(np.unique(pitches))} 165 | 166 | gram = np.zeros((len(pitch_map), len(intervals))) 167 | 168 | for col, f in enumerate(pitches): 169 | gram[pitch_map[f], col] = 1 170 | 171 | return filter_kwargs(mir_eval.sonify.time_frequency, 172 | gram, np.asarray(pitches), np.asarray(intervals), 173 | sr, length=length, **kwargs) 174 | 175 | 176 | SONIFY_MAPPING = OrderedDict() 177 | SONIFY_MAPPING['beat_position'] = downbeat 178 | SONIFY_MAPPING['beat'] = clicks 179 | SONIFY_MAPPING['multi_segment'] = multi_segment 180 | SONIFY_MAPPING['segment_open'] = clicks 181 | SONIFY_MAPPING['onset'] = clicks 182 | SONIFY_MAPPING['chord'] = chord 183 | SONIFY_MAPPING['note_hz'] = piano_roll 184 | SONIFY_MAPPING['pitch_contour'] = pitch_contour 185 | 186 | 187 | def sonify(annotation, sr=22050, duration=None, **kwargs): 188 | '''Sonify a jams annotation through mir_eval 189 | 190 | Parameters 191 | ---------- 192 | annotation : jams.Annotation 193 | The annotation to sonify 194 | 195 | sr = : positive number 196 | The sampling rate of the output waveform 197 | 198 | duration : float (optional) 199 | Optional length (in seconds) of the output waveform 200 | 201 | kwargs 202 | Additional keyword arguments to mir_eval.sonify functions 203 | 204 | Returns 205 | ------- 206 | y_sonified : np.ndarray 207 | The waveform of the sonified annotation 208 | 209 | Raises 210 | ------ 211 | NamespaceError 212 | If the annotation has an un-sonifiable namespace 213 | ''' 214 | 215 | length = None 216 | 217 | if duration is None: 218 | duration = annotation.duration 219 | 220 | if duration is not None: 221 | length = int(duration * sr) 222 | 223 | # If the annotation can be directly sonified, try that first 224 | if annotation.namespace in SONIFY_MAPPING: 225 | ann = coerce_annotation(annotation, annotation.namespace) 226 | return SONIFY_MAPPING[annotation.namespace](ann, 227 | sr=sr, 228 | length=length, 229 | **kwargs) 230 | 231 | for namespace, func in six.iteritems(SONIFY_MAPPING): 232 | try: 233 | ann = coerce_annotation(annotation, namespace) 234 | return func(ann, sr=sr, length=length, **kwargs) 235 | except NamespaceError: 236 | pass 237 | 238 | raise NamespaceError('Unable to sonify annotation of namespace="{:s}"' 239 | .format(annotation.namespace)) 240 | -------------------------------------------------------------------------------- /jams/util.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- encoding: utf-8 -*- 3 | r""" 4 | Utility functions 5 | ----------------- 6 | 7 | .. autosummary:: 8 | :toctree: generated/ 9 | 10 | import_lab 11 | expand_filepaths 12 | smkdirs 13 | filebase 14 | find_with_extension 15 | """ 16 | 17 | import os 18 | import glob 19 | import pandas as pd 20 | 21 | from . import core 22 | 23 | 24 | def import_lab(namespace, filename, infer_duration=True, **parse_options): 25 | r'''Load a .lab file as an Annotation object. 26 | 27 | .lab files are assumed to have the following format: 28 | 29 | ``TIME_START\tTIME_END\tANNOTATION`` 30 | 31 | By default, .lab files are assumed to have columns separated by one 32 | or more white-space characters, and have no header or index column 33 | information. 34 | 35 | If the .lab file contains only two columns, then an empty duration 36 | field is inferred. 37 | 38 | If the .lab file contains more than three columns, each row's 39 | annotation value is assigned the contents of last non-empty column. 40 | 41 | 42 | Parameters 43 | ---------- 44 | namespace : str 45 | The namespace for the new annotation 46 | 47 | filename : str 48 | Path to the .lab file 49 | 50 | infer_duration : bool 51 | If `True`, interval durations are inferred from `(start, end)` columns, 52 | or difference between successive times. 53 | 54 | If `False`, interval durations are assumed to be explicitly coded as 55 | `(start, duration)` columns. If only one time column is given, then 56 | durations are set to 0. 57 | 58 | For instantaneous event annotations (e.g., beats or onsets), this 59 | should be set to `False`. 60 | 61 | parse_options : additional keyword arguments 62 | Passed to ``pandas.DataFrame.read_csv`` 63 | 64 | Returns 65 | ------- 66 | annotation : Annotation 67 | The newly constructed annotation object 68 | 69 | See Also 70 | -------- 71 | pandas.DataFrame.read_csv 72 | ''' 73 | 74 | # Create a new annotation object 75 | annotation = core.Annotation(namespace) 76 | 77 | parse_options.setdefault('sep', r'\s+') 78 | parse_options.setdefault('engine', 'python') 79 | parse_options.setdefault('header', None) 80 | parse_options.setdefault('index_col', False) 81 | 82 | # This is a hack to handle potentially ragged .lab data 83 | parse_options.setdefault('names', range(20)) 84 | 85 | data = pd.read_csv(filename, **parse_options) 86 | 87 | # Drop all-nan columns 88 | data = data.dropna(how='all', axis=1) 89 | 90 | # Do we need to add a duration column? 91 | # This only applies to event annotations 92 | if len(data.columns) == 2: 93 | # Insert a column of zeros after the timing 94 | data.insert(1, 'duration', 0) 95 | if infer_duration: 96 | data['duration'][:-1] = data.loc[:, 0].diff()[1:].values 97 | 98 | else: 99 | # Convert from time to duration 100 | if infer_duration: 101 | data.loc[:, 1] -= data[0] 102 | 103 | for row in data.itertuples(): 104 | time, duration = row[1:3] 105 | 106 | value = [x for x in row[3:] if x is not None][-1] 107 | 108 | annotation.append(time=time, 109 | duration=duration, 110 | confidence=1.0, 111 | value=value) 112 | 113 | return annotation 114 | 115 | 116 | def expand_filepaths(base_dir, rel_paths): 117 | """Expand a list of relative paths to a give base directory. 118 | 119 | Parameters 120 | ---------- 121 | base_dir : str 122 | The target base directory 123 | 124 | rel_paths : list (or list-like) 125 | Collection of relative path strings 126 | 127 | Returns 128 | ------- 129 | expanded_paths : list 130 | `rel_paths` rooted at `base_dir` 131 | 132 | Examples 133 | -------- 134 | >>> jams.util.expand_filepaths('/data', ['audio', 'beat', 'seglab']) 135 | ['/data/audio', '/data/beat', '/data/seglab'] 136 | 137 | """ 138 | return [os.path.join(base_dir, os.path.normpath(rp)) for rp in rel_paths] 139 | 140 | 141 | def smkdirs(dpath, mode=0o777): 142 | """Safely make a full directory path if it doesn't exist. 143 | 144 | Parameters 145 | ---------- 146 | dpath : str 147 | Path of directory/directories to create 148 | 149 | mode : int [default=0777] 150 | Permissions for the new directories 151 | 152 | See also 153 | -------- 154 | os.makedirs 155 | """ 156 | if not os.path.exists(dpath): 157 | os.makedirs(dpath, mode=mode) 158 | 159 | 160 | def filebase(filepath): 161 | """Return the extension-less basename of a file path. 162 | 163 | Parameters 164 | ---------- 165 | filepath : str 166 | Path to a file 167 | 168 | Returns 169 | ------- 170 | base : str 171 | The name of the file, with directory and extension removed 172 | 173 | Examples 174 | -------- 175 | >>> jams.util.filebase('my_song.mp3') 176 | 'my_song' 177 | 178 | """ 179 | return os.path.splitext(os.path.basename(filepath))[0] 180 | 181 | 182 | def find_with_extension(in_dir, ext, depth=3, sort=True): 183 | """Naive depth-search into a directory for files with a given extension. 184 | 185 | Parameters 186 | ---------- 187 | in_dir : str 188 | Path to search. 189 | ext : str 190 | File extension to match. 191 | depth : int 192 | Depth of directories to search. 193 | sort : bool 194 | Sort the list alphabetically 195 | 196 | Returns 197 | ------- 198 | matched : list 199 | Collection of matching file paths. 200 | 201 | Examples 202 | -------- 203 | >>> jams.util.find_with_extension('Audio', 'wav') 204 | ['Audio/LizNelson_Rainfall/LizNelson_Rainfall_MIX.wav', 205 | 'Audio/LizNelson_Rainfall/LizNelson_Rainfall_RAW/LizNelson_Rainfall_RAW_01_01.wav', 206 | 'Audio/LizNelson_Rainfall/LizNelson_Rainfall_RAW/LizNelson_Rainfall_RAW_02_01.wav', 207 | ... 208 | 'Audio/Phoenix_ScotchMorris/Phoenix_ScotchMorris_STEMS/Phoenix_ScotchMorris_STEM_02.wav', 209 | 'Audio/Phoenix_ScotchMorris/Phoenix_ScotchMorris_STEMS/Phoenix_ScotchMorris_STEM_03.wav', 210 | 'Audio/Phoenix_ScotchMorris/Phoenix_ScotchMorris_STEMS/Phoenix_ScotchMorris_STEM_04.wav'] 211 | 212 | """ 213 | assert depth >= 1 214 | ext = ext.strip(os.extsep) 215 | match = list() 216 | for n in range(1, depth+1): 217 | wildcard = os.path.sep.join(["*"]*n) 218 | search_path = os.path.join(in_dir, os.extsep.join([wildcard, ext])) 219 | match += glob.glob(search_path) 220 | 221 | if sort: 222 | match.sort() 223 | return match 224 | -------------------------------------------------------------------------------- /jams/version.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """Version info""" 4 | 5 | short_version = '0.3' 6 | version = '0.3.5a' 7 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools >= 61.0"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [tool.pytest.ini_options] 6 | addopts = [ 7 | "-v", 8 | "--cov-report=term-missing", 9 | "--cov=jams", 10 | ] 11 | testpaths = [ 12 | "tests" 13 | ] -------------------------------------------------------------------------------- /scripts/jams_to_lab.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | '''Convert a jams file into one or more lab files.''' 3 | 4 | import argparse 5 | import collections 6 | import sys 7 | import os 8 | import json 9 | import pandas as pd 10 | 11 | import jams 12 | 13 | 14 | def get_output_name(output_prefix, namespace, index): 15 | '''Get the output name (prefix) 16 | 17 | Parameters 18 | ---------- 19 | output_prefix : str 20 | The path prefix of the target filename 21 | 22 | namespace : str 23 | The namespace of the annotation in question 24 | 25 | index : int 26 | The index number of this annotation within the namespace 27 | 28 | Returns 29 | ------- 30 | output_name : str 31 | "output_prefix__namespace__index" 32 | ''' 33 | return '{:s}__{:s}__{:02d}'.format(output_prefix, namespace, index) 34 | 35 | 36 | def get_comments(jam, ann): 37 | '''Get the metadata from a jam and an annotation, combined as a string. 38 | 39 | Parameters 40 | ---------- 41 | jam : JAMS 42 | The jams object 43 | 44 | ann : Annotation 45 | An annotation object 46 | 47 | Returns 48 | ------- 49 | comments : str 50 | The jam.file_metadata and ann.annotation_metadata, combined and serialized 51 | ''' 52 | jam_comments = jam.file_metadata.__json__ 53 | ann_comments = ann.annotation_metadata.__json__ 54 | return json.dumps({'metadata': jam_comments, 55 | 'annotation metadata': ann_comments}, 56 | indent=2) 57 | 58 | 59 | def lab_dump(ann, comment, filename, sep, comment_char): 60 | '''Save an annotation as a lab/csv. 61 | 62 | Parameters 63 | ---------- 64 | ann : Annotation 65 | The annotation object 66 | 67 | comment : str 68 | The comment string header 69 | 70 | filename : str 71 | The output filename 72 | 73 | sep : str 74 | The separator string for output 75 | 76 | comment_char : str 77 | The character used to denote comments 78 | ''' 79 | 80 | intervals, values = ann.to_interval_values() 81 | 82 | frame = pd.DataFrame(columns=['Time', 'End Time', 'Label'], 83 | data={'Time': intervals[:, 0], 84 | 'End Time': intervals[:, 1], 85 | 'Label': values}) 86 | 87 | with open(filename, 'w') as fdesc: 88 | for line in comment.split('\n'): 89 | fdesc.write('{:s} {:s}\n'.format(comment_char, line)) 90 | 91 | frame.to_csv(path_or_buf=fdesc, index=False, sep=sep) 92 | 93 | 94 | def convert_jams(jams_file, output_prefix, csv=False, comment_char='#', namespaces=None): 95 | '''Convert jams to labs. 96 | 97 | Parameters 98 | ---------- 99 | jams_file : str 100 | The path on disk to the jams file in question 101 | 102 | output_prefix : str 103 | The file path prefix of the outputs 104 | 105 | csv : bool 106 | Whether to output in csv (True) or lab (False) format 107 | 108 | comment_char : str 109 | The character used to denote comments 110 | 111 | namespaces : list-like 112 | The set of namespace patterns to match for output 113 | ''' 114 | 115 | if namespaces is None: 116 | raise ValueError('No namespaces provided. Try ".*" for all namespaces.') 117 | 118 | jam = jams.load(jams_file) 119 | 120 | # Get all the annotations 121 | # Filter down to the unique ones 122 | # For each annotation 123 | # generate the comment string 124 | # generate the output filename 125 | # dump to csv 126 | 127 | # Make a counter object for each namespace type 128 | counter = collections.Counter() 129 | 130 | annotations = [] 131 | 132 | for query in namespaces: 133 | annotations.extend(jam.search(namespace=query)) 134 | 135 | if csv: 136 | suffix = 'csv' 137 | sep = ',' 138 | else: 139 | suffix = 'lab' 140 | sep = '\t' 141 | 142 | for ann in annotations: 143 | index = counter[ann.namespace] 144 | counter[ann.namespace] += 1 145 | filename = os.path.extsep.join([get_output_name(output_prefix, 146 | ann.namespace, 147 | index), 148 | suffix]) 149 | 150 | comment = get_comments(jam, ann) 151 | 152 | # Dump to disk 153 | lab_dump(ann, comment, filename, sep, comment_char) 154 | 155 | 156 | def parse_arguments(args): 157 | '''Parse arguments from the command line''' 158 | parser = argparse.ArgumentParser(description='Convert JAMS to .lab files') 159 | 160 | parser.add_argument('-c', 161 | '--comma-separated', 162 | dest='csv', 163 | action='store_true', 164 | default=False, 165 | help='Output in .csv instead of .lab') 166 | 167 | parser.add_argument('--comment', dest='comment_char', type=str, default='#', 168 | help='Comment character') 169 | 170 | parser.add_argument('-n', 171 | '--namespace', 172 | dest='namespaces', 173 | nargs='+', 174 | default=['.*'], 175 | help='One or more namespaces to output. Default is all.') 176 | 177 | parser.add_argument('jams_file', 178 | help='Path to the input jams file') 179 | 180 | parser.add_argument('output_prefix', help='Prefix for output files') 181 | 182 | return vars(parser.parse_args(args)) 183 | 184 | 185 | if __name__ == '__main__': 186 | 187 | convert_jams(**parse_arguments(sys.argv[1:])) 188 | -------------------------------------------------------------------------------- /scripts/jams_to_mirex_pattern.py: -------------------------------------------------------------------------------- 1 | 2 | #!/usr/bin/env python 3 | '''Convert JAMS to MIREX pattern discovery format 4 | 5 | Example usage: 6 | 7 | ./jams_to_mirex_pattern.py /path/to/file.jams outputname 8 | ''' 9 | 10 | import json 11 | import sys 12 | from argparse import ArgumentParser 13 | import jams 14 | 15 | def parse_arguments(args): 16 | 17 | parser = ArgumentParser(description='Parse JAMS annotations into the MIREX Pattern Discovery task format') 18 | 19 | parser.add_argument('infile', type=str, help='Input JAMS file') 20 | parser.add_argument('output_prefix', type=str, 21 | help='Prefix for output lab files') 22 | 23 | return vars(parser.parse_args(args)) 24 | 25 | def run(infile='', output_prefix='annotation'): 26 | with open(infile, 'r') as fdesc: 27 | x = json.load(fdesc) 28 | 29 | for elem in x["annotations"]: 30 | # looping through the annotators 31 | 32 | metadata = elem["annotation_metadata"] 33 | anno = metadata["annotator"] 34 | d = elem["data"] 35 | 36 | with open('{1}_{0}.txt'.format(anno, output_prefix), 'w') as text_file: 37 | text_file.write("Pattern1" + "\n") 38 | text_file.write("Occ1" + "\n") 39 | 40 | pcount = 1 41 | ocount = 1 42 | 43 | initpid = d[0]["value"]["pattern_id"] 44 | initocc = d[0]["value"]["occurrence_id"] 45 | 46 | pastpid = initpid 47 | pastoid = initocc 48 | 49 | for y in d: 50 | # lopping through the events given an annotator 51 | time = y["time"] 52 | 53 | pid = y["value"]["pattern_id"] 54 | 55 | if pid != pastpid: 56 | pcount += 1 57 | ocount = 1 58 | text_file.write("Pattern"+str(pcount)+"\n") 59 | text_file.write("Occ"+str(ocount)+"\n") 60 | 61 | midip = y["value"]["midi_pitch"] 62 | occid = y["value"]["occurrence_id"] 63 | 64 | if occid != pastoid: 65 | ocount += 1 66 | text_file.write("Occ"+str(ocount)+"\n") 67 | 68 | pastpid = pid 69 | pastoid = occid 70 | 71 | text_file.write(str(time)+", "+str(midip)+"\n") 72 | 73 | 74 | 75 | if __name__ == '__main__': 76 | params = parse_arguments(sys.argv[1:]) 77 | 78 | run(**params) 79 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | import importlib.util 4 | import importlib.machinery 5 | 6 | 7 | def load_source(modname, filename): 8 | loader = importlib.machinery.SourceFileLoader(modname, filename) 9 | spec = importlib.util.spec_from_file_location(modname, filename, loader=loader) 10 | module = importlib.util.module_from_spec(spec) 11 | loader.exec_module(module) 12 | return module 13 | 14 | 15 | version = load_source('jams.version', 'jams/version.py') 16 | 17 | setup( 18 | name='jams', 19 | version=version.version, 20 | description='A JSON Annotated Music Specification for Reproducible MIR Research', 21 | author='JAMS development crew', 22 | url='http://github.com/marl/jams', 23 | download_url='http://github.com/marl/jams/releases', 24 | packages=find_packages(), 25 | package_data={'': ['schemata/*.json', 26 | 'schemata/namespaces/*.json', 27 | 'schemata/namespaces/*/*.json']}, 28 | long_description='A JSON Annotated Music Specification for Reproducible MIR Research', 29 | classifiers=[ 30 | "License :: OSI Approved :: ISC License (ISCL)", 31 | "Programming Language :: Python", 32 | "Development Status :: 3 - Alpha", 33 | "Intended Audience :: Developers", 34 | "Topic :: Multimedia :: Sound/Audio :: Analysis", 35 | "Programming Language :: Python :: 3", 36 | "Programming Language :: Python :: 3.9", 37 | "Programming Language :: Python :: 3.10", 38 | "Programming Language :: Python :: 3.11", 39 | "Programming Language :: Python :: 3.12", 40 | "Programming Language :: Python :: 3.13", 41 | ], 42 | python_requires=">=3.9", 43 | keywords='audio music json', 44 | license='ISC', 45 | install_requires=[ 46 | 'pandas', 47 | 'sortedcontainers>=2.0.0', 48 | 'jsonschema>=3.0.0', 49 | 'numpy>=1.8.0', 50 | 'six', 51 | 'decorator', 52 | 'mir_eval>=0.8.2' 53 | ], 54 | extras_require={ 55 | 'display': ['matplotlib>=1.5.0'], 56 | 'tests': ['pytest ~= 8.0', 'pytest-cov', 'matplotlib>=3'], 57 | }, 58 | scripts=['scripts/jams_to_lab.py'] 59 | ) 60 | -------------------------------------------------------------------------------- /tests/fixtures/invalid.jams: -------------------------------------------------------------------------------- 1 | { 2 | "sandbox": { 3 | "content_path": "SMC_001.wav" 4 | }, 5 | "annotations": [ 6 | { 7 | "data": [ 8 | { 9 | "duration": 0.0, 10 | "confidence": NaN, 11 | "value": 1.0, 12 | "time": -20 13 | }, 14 | { 15 | "duration": 0.0, 16 | "confidence": NaN, 17 | "value": 1.0, 18 | "time": 1.9346 19 | }, 20 | { 21 | "duration": 0.0, 22 | "confidence": NaN, 23 | "value": 1.0, 24 | "time": 3.1265 25 | }, 26 | { 27 | "duration": 0.0, 28 | "confidence": NaN, 29 | "value": 1.0, 30 | "time": 4.31 31 | }, 32 | { 33 | "duration": 0.0, 34 | "confidence": NaN, 35 | "value": 1.0, 36 | "time": 5.55 37 | }, 38 | { 39 | "duration": 0.0, 40 | "confidence": NaN, 41 | "value": 1.0, 42 | "time": 6.9 43 | }, 44 | { 45 | "duration": 0.0, 46 | "confidence": NaN, 47 | "value": 1.0, 48 | "time": 8.06 49 | }, 50 | { 51 | "duration": 0.0, 52 | "confidence": NaN, 53 | "value": 1.0, 54 | "time": 9.25 55 | }, 56 | { 57 | "duration": 0.0, 58 | "confidence": NaN, 59 | "value": 1.0, 60 | "time": 10.52 61 | }, 62 | { 63 | "duration": 0.0, 64 | "confidence": NaN, 65 | "value": 1.0, 66 | "time": 11.844 67 | }, 68 | { 69 | "duration": 0.0, 70 | "confidence": NaN, 71 | "value": 1.0, 72 | "time": 13.11 73 | }, 74 | { 75 | "duration": 0.0, 76 | "confidence": NaN, 77 | "value": 1.0, 78 | "time": 14.3 79 | }, 80 | { 81 | "duration": 0.0, 82 | "confidence": NaN, 83 | "value": 1.0, 84 | "time": 15.62 85 | }, 86 | { 87 | "duration": 0.0, 88 | "confidence": NaN, 89 | "value": 1.0, 90 | "time": 16.83 91 | }, 92 | { 93 | "duration": 0.0, 94 | "confidence": NaN, 95 | "value": 1.0, 96 | "time": 18.04 97 | }, 98 | { 99 | "duration": 0.0, 100 | "confidence": NaN, 101 | "value": 1.0, 102 | "time": 19.42 103 | }, 104 | { 105 | "duration": 0.0, 106 | "confidence": NaN, 107 | "value": 1.0, 108 | "time": 20.669 109 | }, 110 | { 111 | "duration": 0.0, 112 | "confidence": NaN, 113 | "value": 1.0, 114 | "time": 21.97 115 | }, 116 | { 117 | "duration": 0.0, 118 | "confidence": NaN, 119 | "value": 1.0, 120 | "time": 23.14 121 | }, 122 | { 123 | "duration": 0.0, 124 | "confidence": NaN, 125 | "value": 1.0, 126 | "time": 24.4 127 | }, 128 | { 129 | "duration": 0.0, 130 | "confidence": NaN, 131 | "value": 1.0, 132 | "time": 25.6 133 | }, 134 | { 135 | "duration": 0.0, 136 | "confidence": NaN, 137 | "value": 1.0, 138 | "time": 26.955 139 | }, 140 | { 141 | "duration": 0.0, 142 | "confidence": NaN, 143 | "value": 1.0, 144 | "time": 28.18 145 | }, 146 | { 147 | "duration": 0.0, 148 | "confidence": NaN, 149 | "value": 1.0, 150 | "time": 29.47 151 | }, 152 | { 153 | "duration": 0.0, 154 | "confidence": NaN, 155 | "value": 1.0, 156 | "time": 30.63 157 | }, 158 | { 159 | "duration": 0.0, 160 | "confidence": NaN, 161 | "value": 1.0, 162 | "time": 31.88 163 | }, 164 | { 165 | "duration": 0.0, 166 | "confidence": NaN, 167 | "value": 1.0, 168 | "time": 33.26 169 | }, 170 | { 171 | "duration": 0.0, 172 | "confidence": NaN, 173 | "value": 1.0, 174 | "time": 34.53 175 | }, 176 | { 177 | "duration": 0.0, 178 | "confidence": NaN, 179 | "value": 1.0, 180 | "time": 35.65 181 | }, 182 | { 183 | "duration": 0.0, 184 | "confidence": NaN, 185 | "value": 1.0, 186 | "time": 36.86 187 | }, 188 | { 189 | "duration": 0.0, 190 | "confidence": NaN, 191 | "value": 1.0, 192 | "time": 38.106 193 | }, 194 | { 195 | "duration": 0.0, 196 | "confidence": NaN, 197 | "value": 1.0, 198 | "time": 39.33 199 | } 200 | ], 201 | "annotation_metadata": { 202 | "corpus": "SMC_MIREX", 203 | "curator": { 204 | "name": "Matthew Davies", 205 | "email": "mdavies@inescporto.pt" 206 | }, 207 | "annotator": { 208 | "id": "a" 209 | } 210 | }, 211 | "namespace": "beat", 212 | "sandbox": { 213 | "metrical_interpretation": "2_1_1" 214 | } 215 | }, 216 | { 217 | "data": [ 218 | { 219 | "duration": 40.0, 220 | "confidence": null, 221 | "value": "slow tempo", 222 | "time": 0.0 223 | }, 224 | { 225 | "duration": 40.0, 226 | "confidence": null, 227 | "value": "(poor sound quality)", 228 | "time": 0.0 229 | } 230 | ], 231 | "annotation_metadata": { 232 | "corpus": "SMC_MIREX", 233 | "curator": { 234 | "name": "Matthew Davies", 235 | "email": "mdavies@inescporto.pt" 236 | }, 237 | "annotator": { 238 | "confidence": 1, 239 | "id": "a" 240 | } 241 | }, 242 | "namespace": "tag_open", 243 | "sandbox": {} 244 | } 245 | ], 246 | "file_metadata": { 247 | "duration": 40.0, 248 | "identifiers": {}, 249 | "jams_version": "0.1.0", 250 | "title": "SMC_001" 251 | } 252 | } 253 | -------------------------------------------------------------------------------- /tests/fixtures/schema/testing_uppercase.json: -------------------------------------------------------------------------------- 1 | {"testing_tag_upper": 2 | { 3 | "value": { "type": "string", "pattern": "^[A-Z]*$"}, 4 | "dense": false, 5 | "description": "(testing) Upper-case strings only" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tests/fixtures/transcription_est.jams: -------------------------------------------------------------------------------- 1 | { 2 | "sandbox": {}, 3 | "annotations": [ 4 | { 5 | "namespace": "pitch_hz", 6 | "sandbox": {}, 7 | "time": 0, 8 | "duration": 0.65000000000000002, 9 | "annotation_metadata": { 10 | "annotation_tools": "", 11 | "curator": { 12 | "name": "Justin Salamon", 13 | "email": "justin.salamon@nyu.edu" 14 | }, 15 | "annotator": {}, 16 | "version": "", 17 | "corpus": "", 18 | "annotation_rules": "", 19 | "validation": "", 20 | "data_source": "Manually generated for test purposes" 21 | }, 22 | "data": { 23 | "duration": [ 24 | 0.17, 25 | 0.05, 26 | 0.1, 27 | 0.05, 28 | 0.09 29 | ], 30 | "confidence": [ 31 | 1.0, 32 | 1.0, 33 | 1.0, 34 | 1.0, 35 | 1.0 36 | ], 37 | "value": [ 38 | 225.0, 39 | 246.94200000000001, 40 | 500.0, 41 | 293.66500000000002, 42 | 293.66500000000002 43 | ], 44 | "time": [ 45 | 0.12, 46 | 0.3, 47 | 0.5, 48 | 0.55, 49 | 0.56 50 | ] 51 | } 52 | } 53 | ], 54 | "file_metadata": { 55 | "jams_version": "0.2.1", 56 | "title": "est00", 57 | "identifiers": {}, 58 | "release": "", 59 | "duration": 0.65000000000000002, 60 | "artist": "" 61 | } 62 | } -------------------------------------------------------------------------------- /tests/fixtures/transcription_ref.jams: -------------------------------------------------------------------------------- 1 | { 2 | "sandbox": {}, 3 | "annotations": [ 4 | { 5 | "namespace": "pitch_hz", 6 | "sandbox": {}, 7 | "time": 0, 8 | "duration": 0.65000000000000002, 9 | "annotation_metadata": { 10 | "annotation_tools": "", 11 | "curator": { 12 | "name": "Justin Salamon", 13 | "email": "justin.salamon@nyu.edu" 14 | }, 15 | "annotator": {}, 16 | "version": "", 17 | "corpus": "", 18 | "annotation_rules": "", 19 | "validation": "", 20 | "data_source": "Manually generated for test purposes" 21 | }, 22 | "data": { 23 | "duration": [ 24 | 0.2, 25 | 0.1, 26 | 0.1, 27 | 0.1 28 | ], 29 | "confidence": [ 30 | 1.0, 31 | 1.0, 32 | 1.0, 33 | 1.0 34 | ], 35 | "value": [ 36 | 220.0, 37 | 246.94200000000001, 38 | 277.18299999999999, 39 | 293.66500000000002 40 | ], 41 | "time": [ 42 | 0.1, 43 | 0.3, 44 | 0.5, 45 | 0.55 46 | ] 47 | } 48 | } 49 | ], 50 | "file_metadata": { 51 | "jams_version": "0.2.1", 52 | "title": "ref00", 53 | "identifiers": {}, 54 | "release": "", 55 | "duration": 0.65000000000000002, 56 | "artist": "" 57 | } 58 | } -------------------------------------------------------------------------------- /tests/fixtures/valid.jams: -------------------------------------------------------------------------------- 1 | { 2 | "sandbox": { 3 | "content_path": "SMC_001.wav" 4 | }, 5 | "annotations": [ 6 | { 7 | "data": [ 8 | { 9 | "duration": 0.0, 10 | "confidence": NaN, 11 | "value": 1.0, 12 | "time": 0.6775 13 | }, 14 | { 15 | "duration": 0.0, 16 | "confidence": NaN, 17 | "value": 1.0, 18 | "time": 1.9346 19 | }, 20 | { 21 | "duration": 0.0, 22 | "confidence": NaN, 23 | "value": 1.0, 24 | "time": 3.1265 25 | }, 26 | { 27 | "duration": 0.0, 28 | "confidence": NaN, 29 | "value": 1.0, 30 | "time": 4.31 31 | }, 32 | { 33 | "duration": 0.0, 34 | "confidence": NaN, 35 | "value": 1.0, 36 | "time": 5.55 37 | }, 38 | { 39 | "duration": 0.0, 40 | "confidence": NaN, 41 | "value": 1.0, 42 | "time": 6.9 43 | }, 44 | { 45 | "duration": 0.0, 46 | "confidence": NaN, 47 | "value": 1.0, 48 | "time": 8.06 49 | }, 50 | { 51 | "duration": 0.0, 52 | "confidence": NaN, 53 | "value": 1.0, 54 | "time": 9.25 55 | }, 56 | { 57 | "duration": 0.0, 58 | "confidence": NaN, 59 | "value": 1.0, 60 | "time": 10.52 61 | }, 62 | { 63 | "duration": 0.0, 64 | "confidence": NaN, 65 | "value": 1.0, 66 | "time": 11.844 67 | }, 68 | { 69 | "duration": 0.0, 70 | "confidence": NaN, 71 | "value": 1.0, 72 | "time": 13.11 73 | }, 74 | { 75 | "duration": 0.0, 76 | "confidence": NaN, 77 | "value": 1.0, 78 | "time": 14.3 79 | }, 80 | { 81 | "duration": 0.0, 82 | "confidence": NaN, 83 | "value": 1.0, 84 | "time": 15.62 85 | }, 86 | { 87 | "duration": 0.0, 88 | "confidence": NaN, 89 | "value": 1.0, 90 | "time": 16.83 91 | }, 92 | { 93 | "duration": 0.0, 94 | "confidence": NaN, 95 | "value": 1.0, 96 | "time": 18.04 97 | }, 98 | { 99 | "duration": 0.0, 100 | "confidence": NaN, 101 | "value": 1.0, 102 | "time": 19.42 103 | }, 104 | { 105 | "duration": 0.0, 106 | "confidence": NaN, 107 | "value": 1.0, 108 | "time": 20.669 109 | }, 110 | { 111 | "duration": 0.0, 112 | "confidence": NaN, 113 | "value": 1.0, 114 | "time": 21.97 115 | }, 116 | { 117 | "duration": 0.0, 118 | "confidence": NaN, 119 | "value": 1.0, 120 | "time": 23.14 121 | }, 122 | { 123 | "duration": 0.0, 124 | "confidence": NaN, 125 | "value": 1.0, 126 | "time": 24.4 127 | }, 128 | { 129 | "duration": 0.0, 130 | "confidence": NaN, 131 | "value": 1.0, 132 | "time": 25.6 133 | }, 134 | { 135 | "duration": 0.0, 136 | "confidence": NaN, 137 | "value": 1.0, 138 | "time": 26.955 139 | }, 140 | { 141 | "duration": 0.0, 142 | "confidence": NaN, 143 | "value": 1.0, 144 | "time": 28.18 145 | }, 146 | { 147 | "duration": 0.0, 148 | "confidence": NaN, 149 | "value": 1.0, 150 | "time": 29.47 151 | }, 152 | { 153 | "duration": 0.0, 154 | "confidence": NaN, 155 | "value": 1.0, 156 | "time": 30.63 157 | }, 158 | { 159 | "duration": 0.0, 160 | "confidence": NaN, 161 | "value": 1.0, 162 | "time": 31.88 163 | }, 164 | { 165 | "duration": 0.0, 166 | "confidence": NaN, 167 | "value": 1.0, 168 | "time": 33.26 169 | }, 170 | { 171 | "duration": 0.0, 172 | "confidence": NaN, 173 | "value": 1.0, 174 | "time": 34.53 175 | }, 176 | { 177 | "duration": 0.0, 178 | "confidence": NaN, 179 | "value": 1.0, 180 | "time": 35.65 181 | }, 182 | { 183 | "duration": 0.0, 184 | "confidence": NaN, 185 | "value": 1.0, 186 | "time": 36.86 187 | }, 188 | { 189 | "duration": 0.0, 190 | "confidence": NaN, 191 | "value": 1.0, 192 | "time": 38.106 193 | }, 194 | { 195 | "duration": 0.0, 196 | "confidence": NaN, 197 | "value": 1.0, 198 | "time": 39.33 199 | } 200 | ], 201 | "annotation_metadata": { 202 | "corpus": "SMC_MIREX", 203 | "curator": { 204 | "name": "Matthew Davies", 205 | "email": "mdavies@inescporto.pt" 206 | }, 207 | "annotator": { 208 | "id": "a" 209 | } 210 | }, 211 | "namespace": "beat", 212 | "sandbox": { 213 | "metrical_interpretation": "2_1_1" 214 | } 215 | }, 216 | { 217 | "data": [ 218 | { 219 | "duration": 40.0, 220 | "confidence": null, 221 | "value": "slow tempo", 222 | "time": 0.0 223 | }, 224 | { 225 | "duration": 40.0, 226 | "confidence": null, 227 | "value": "(poor sound quality)", 228 | "time": 0.0 229 | } 230 | ], 231 | "annotation_metadata": { 232 | "corpus": "SMC_MIREX", 233 | "curator": { 234 | "name": "Matthew Davies", 235 | "email": "mdavies@inescporto.pt" 236 | }, 237 | "annotator": { 238 | "confidence": 1, 239 | "id": "a" 240 | } 241 | }, 242 | "namespace": "tag_open", 243 | "sandbox": {} 244 | } 245 | ], 246 | "file_metadata": { 247 | "duration": 40.0, 248 | "identifiers": {}, 249 | "jams_version": "0.1.0", 250 | "title": "SMC_001" 251 | } 252 | } -------------------------------------------------------------------------------- /tests/fixtures/valid.jamz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marl/jams/91e0d57c9a2806c4f3a6746f84851cb3a12760d1/tests/fixtures/valid.jamz -------------------------------------------------------------------------------- /tests/test_display.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- encoding: utf-8 -*- 3 | 4 | import numpy as np 5 | 6 | import matplotlib 7 | matplotlib.use('Agg') 8 | import matplotlib.pyplot as plt 9 | 10 | import pytest 11 | import jams 12 | import jams.display 13 | 14 | from jams import NamespaceError 15 | 16 | 17 | # A simple run-without-fail test for plotting 18 | @pytest.mark.parametrize('namespace', 19 | ['segment_open', 'chord', 'multi_segment', 20 | 'pitch_contour', 'beat_position', 'beat', 21 | 'onset', 'note_midi', 'tag_open']) 22 | @pytest.mark.parametrize('meta', [False, True]) 23 | def test_display(namespace, meta): 24 | 25 | ann = jams.Annotation(namespace=namespace) 26 | jams.display.display(ann, meta=meta) 27 | 28 | @pytest.mark.parametrize('namespace', ['tempo']) 29 | @pytest.mark.parametrize('meta', [False, True]) 30 | def test_display_exception(namespace, meta): 31 | with pytest.raises(NamespaceError): 32 | ann = jams.Annotation(namespace=namespace) 33 | jams.display.display(ann, meta=meta) 34 | 35 | 36 | def test_display_multi(): 37 | 38 | jam = jams.JAMS() 39 | jam.annotations.append(jams.Annotation(namespace='beat')) 40 | jams.display.display_multi(jam.annotations) 41 | 42 | 43 | def test_display_multi_multi(): 44 | 45 | jam = jams.JAMS() 46 | jam.annotations.append(jams.Annotation(namespace='beat')) 47 | jam.annotations.append(jams.Annotation(namespace='chord')) 48 | 49 | jams.display.display_multi(jam.annotations) 50 | 51 | 52 | def test_display_pitch_contour(): 53 | 54 | ann = jams.Annotation(namespace='pitch_hz', duration=5) 55 | 56 | values = np.arange(100, 200) 57 | times = np.linspace(0, 2, num=len(values)) 58 | 59 | for t, v in zip(times, values): 60 | ann.append(time=t, value=v, duration=0) 61 | 62 | jams.display.display(ann) 63 | 64 | 65 | def test_display_labeled_events(): 66 | 67 | times = np.arange(40) 68 | values = times % 4 69 | 70 | ann = jams.Annotation(namespace='beat', duration=60) 71 | 72 | for t, v in zip(times, values): 73 | ann.append(time=t, value=v, duration=0) 74 | 75 | jams.display.display(ann) 76 | 77 | 78 | def test_display_multi_fail(): 79 | anns = jams.AnnotationArray() 80 | 81 | with pytest.raises(jams.ParameterError): 82 | jams.display.display_multi(anns) 83 | -------------------------------------------------------------------------------- /tests/test_schema.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # CREATED:2015-07-15 10:21:30 by Brian McFee 3 | '''Namespace management tests''' 4 | 5 | from six.moves import reload_module 6 | 7 | import pytest 8 | import os 9 | 10 | from jams import NamespaceError 11 | import jams 12 | 13 | 14 | @pytest.mark.parametrize('ns_key', ['pitch_hz', 'beat']) 15 | def test_schema_namespace(ns_key): 16 | # Get the schema 17 | schema = jams.schema.namespace(ns_key) 18 | # Make sure it has the correct properties 19 | valid_keys = set(['time', 'duration', 'value', 'confidence']) 20 | for key in schema['properties']: 21 | assert key in valid_keys 22 | for key in ['time', 'duration']: 23 | assert key in schema['properties'] 24 | 25 | @pytest.mark.parametrize('ns_key', ['DNE']) 26 | def test_schema_namespace_exception(ns_key): 27 | with pytest.raises(NamespaceError): 28 | jams.schema.namespace(ns_key) 29 | 30 | 31 | 32 | @pytest.mark.parametrize('ns, dense', [('pitch_hz', True), ('beat', False)]) 33 | def test_schema_is_dense(ns, dense): 34 | assert dense == jams.schema.is_dense(ns) 35 | 36 | @pytest.mark.parametrize('ns', ['DNE']) 37 | def test_schema_is_dense_exception(ns): 38 | with pytest.raises(NamespaceError): 39 | jams.schema.is_dense(ns) 40 | 41 | 42 | @pytest.fixture 43 | def local_namespace(): 44 | 45 | os.environ['JAMS_SCHEMA_DIR'] = os.path.join('tests', 'fixtures', 'schema') 46 | reload_module(jams) 47 | 48 | # This one should pass 49 | yield 'testing_tag_upper', True 50 | 51 | # Cleanup 52 | del os.environ['JAMS_SCHEMA_DIR'] 53 | reload_module(jams) 54 | 55 | 56 | def test_schema_local(local_namespace): 57 | 58 | ns_key, exists = local_namespace 59 | 60 | # Get the schema 61 | if exists: 62 | schema = jams.schema.namespace(ns_key) 63 | 64 | # Make sure it has the correct properties 65 | valid_keys = set(['time', 'duration', 'value', 'confidence']) 66 | for key in schema['properties']: 67 | assert key in valid_keys 68 | 69 | for key in ['time', 'duration']: 70 | assert key in schema['properties'] 71 | else: 72 | with pytest.raises(NamespaceError): 73 | schema = jams.schema.namespace(ns_key) 74 | 75 | 76 | def test_schema_values_pass(): 77 | 78 | values = jams.schema.values('tag_gtzan') 79 | 80 | assert values == ['blues', 'classical', 'country', 81 | 'disco', 'hip-hop', 'jazz', 'metal', 82 | 'pop', 'reggae', 'rock'] 83 | 84 | 85 | def test_schema_values_missing(): 86 | with pytest.raises(NamespaceError): 87 | jams.schema.values('imaginary namespace') 88 | 89 | 90 | def test_schema_values_notenum(): 91 | with pytest.raises(NamespaceError): 92 | jams.schema.values('chord_harte') 93 | 94 | 95 | def test_schema_dtypes(): 96 | 97 | for n in jams.schema.__NAMESPACE__: 98 | jams.schema.get_dtypes(n) 99 | 100 | 101 | def test_schema_dtypes_badns(): 102 | with pytest.raises(NamespaceError): 103 | jams.schema.get_dtypes('unknown namespace') 104 | 105 | 106 | def test_list_namespaces(): 107 | jams.schema.list_namespaces() 108 | -------------------------------------------------------------------------------- /tests/test_sonify.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # CREATED:2016-02-11 12:07:58 by Brian McFee 3 | """Sonification tests""" 4 | 5 | import numpy as np 6 | import pytest 7 | from test_eval import create_hierarchy 8 | 9 | import jams 10 | 11 | 12 | def test_no_sonify(): 13 | 14 | ann = jams.Annotation(namespace='vector') 15 | with pytest.raises(jams.NamespaceError): 16 | jams.sonify.sonify(ann) 17 | 18 | 19 | def test_bad_sonify(): 20 | ann = jams.Annotation(namespace='chord') 21 | ann.append(time=0, duration=1, value='not a chord') 22 | 23 | with pytest.raises(jams.SchemaError): 24 | jams.sonify.sonify(ann) 25 | 26 | 27 | @pytest.mark.parametrize('ns', ['segment_open', 'chord']) 28 | @pytest.mark.parametrize('sr', [8000, 11025]) 29 | @pytest.mark.parametrize('duration', [None, 5.0, 1.0]) 30 | def test_duration(ns, sr, duration): 31 | 32 | ann = jams.Annotation(namespace=ns) 33 | ann.append(time=3, duration=1, value='C') 34 | 35 | y = jams.sonify.sonify(ann, sr=sr, duration=duration) 36 | 37 | if duration is not None: 38 | assert len(y) == int(sr * duration) 39 | 40 | 41 | def test_note_hz(): 42 | ann = jams.Annotation(namespace='note_hz') 43 | ann.append(time=0, duration=1, value=261.0) 44 | y = jams.sonify.sonify(ann, sr=8000, duration=2.0) 45 | 46 | assert len(y) == 8000 * 2 47 | 48 | 49 | def test_note_hz_nolength(): 50 | ann = jams.Annotation(namespace='note_hz') 51 | ann.append(time=0, duration=1, value=261.0) 52 | y = jams.sonify.sonify(ann, sr=8000) 53 | 54 | assert len(y) == 8000 * 1 55 | assert np.any(y) 56 | 57 | 58 | def test_note_midi(): 59 | ann = jams.Annotation(namespace='note_midi') 60 | ann.append(time=0, duration=1, value=60) 61 | y = jams.sonify.sonify(ann, sr=8000, duration=2.0) 62 | 63 | assert len(y) == 8000 * 2 64 | 65 | 66 | @pytest.fixture(scope='module') 67 | def ann_contour(): 68 | ann = jams.Annotation(namespace='pitch_contour') 69 | 70 | duration = 5.0 71 | fs = 0.01 72 | # Generate a contour with deep vibrato and no voicing from 3s-4s 73 | times = np.linspace(0, duration, num=int(duration / fs)) 74 | rate = 5 75 | vibrato = 220 + 20 * np.sin(2 * np.pi * times * rate) 76 | 77 | for t, v in zip(times, vibrato): 78 | ann.append(time=t, duration=fs, value={'frequency': v, 79 | 'index': 0, 80 | 'voiced': (t < 3 or t > 4)}) 81 | 82 | return ann 83 | 84 | 85 | @pytest.mark.parametrize('duration', [None, 5.0, 10.0]) 86 | @pytest.mark.parametrize('sr', [8000]) 87 | def test_contour(ann_contour, duration, sr): 88 | y = jams.sonify.sonify(ann_contour, sr=sr, duration=duration) 89 | if duration is not None: 90 | assert len(y) == sr * duration 91 | 92 | 93 | @pytest.mark.parametrize('namespace', ['chord', 'chord_harte']) 94 | @pytest.mark.parametrize('sr', [8000]) 95 | @pytest.mark.parametrize('duration', [2.0]) 96 | @pytest.mark.parametrize('value', ['C:maj/5']) 97 | def test_chord(namespace, sr, duration, value): 98 | 99 | ann = jams.Annotation(namespace=namespace) 100 | ann.append(time=0.5, duration=1.0, value=value) 101 | y = jams.sonify.sonify(ann, sr=sr, duration=duration) 102 | 103 | assert len(y) == sr * duration 104 | 105 | 106 | @pytest.mark.parametrize('namespace, value', 107 | [('beat', 1), 108 | ('segment_open', 'C'), 109 | ('onset', 1)]) 110 | @pytest.mark.parametrize('sr', [8000]) 111 | @pytest.mark.parametrize('duration', [2.0]) 112 | def test_event(namespace, sr, duration, value): 113 | 114 | ann = jams.Annotation(namespace=namespace) 115 | ann.append(time=0.5, duration=0, value=value) 116 | y = jams.sonify.sonify(ann, sr=sr, duration=duration) 117 | assert len(y) == sr * duration 118 | 119 | 120 | @pytest.fixture(scope='module') 121 | def beat_pos_ann(): 122 | ann = jams.Annotation(namespace='beat_position') 123 | 124 | for i, t in enumerate(np.arange(0, 10, 0.25)): 125 | ann.append(time=t, duration=0, 126 | value=dict(position=1 + i % 4, 127 | measure=1 + i // 4, 128 | num_beats=4, 129 | beat_units=4)) 130 | return ann 131 | 132 | 133 | @pytest.mark.parametrize('sr', [8000]) 134 | @pytest.mark.parametrize('duration', [None, 5, 15]) 135 | def test_beat_position(beat_pos_ann, sr, duration): 136 | 137 | yout = jams.sonify.sonify(beat_pos_ann, sr=sr, duration=duration) 138 | if duration is not None: 139 | assert len(yout) == duration * sr 140 | 141 | 142 | @pytest.fixture(scope='module') 143 | def ann_hier(): 144 | return create_hierarchy(values=['AB', 'abac', 'xxyyxxzz'], duration=30) 145 | 146 | 147 | @pytest.mark.parametrize('sr', [8000]) 148 | @pytest.mark.parametrize('duration', [None, 15, 30]) 149 | def test_multi_segment(ann_hier, sr, duration): 150 | y = jams.sonify.sonify(ann_hier, sr=sr, duration=duration) 151 | if duration: 152 | assert len(y) == duration * sr 153 | -------------------------------------------------------------------------------- /tests/test_util.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # CHANGED:2015-03-05 17:53:32 by Brian McFee 3 | """Test the util module""" 4 | import sys 5 | import tempfile 6 | import os 7 | import pytest 8 | import numpy as np 9 | 10 | from jams import core, util 11 | 12 | 13 | import six 14 | 15 | 16 | def srand(seed=628318530): 17 | np.random.seed(seed) 18 | pass 19 | 20 | 21 | @pytest.mark.parametrize('ns, lab, ints, y, infer_duration', 22 | [('beat', 23 | "1.0 1\n3.0 2", 24 | np.array([[1.0, 3.0], [3.0, 3.0]]), 25 | [1, 2], 26 | True), 27 | ('beat', 28 | "1.0 1\n3.0 2", 29 | np.array([[1.0, 1.0], [3.0, 3.0]]), 30 | [1, 2], 31 | False), 32 | ('chord_harte', 33 | "1.0 2.0 a\n2.0 4.0 b", 34 | np.array([[1.0, 2.0], [2.0, 4.0]]), 35 | ['a', 'b'], 36 | True), 37 | ('chord', 38 | "1.0 1.0 c\n2.0 2.0 d", 39 | np.array([[1.0, 2.0], [2.0, 4.0]]), 40 | ['c', 'd'], 41 | False)]) 42 | def test_import_lab(ns, lab, ints, y, infer_duration): 43 | ann = util.import_lab(ns, six.StringIO(lab), 44 | infer_duration=infer_duration) 45 | 46 | assert len(ints) == len(ann.data) 47 | assert len(y) == len(ann.data) 48 | 49 | for yi, ival, obs in zip(y, ints, ann): 50 | assert obs.time == ival[0] 51 | assert obs.duration == ival[1] - ival[0] 52 | assert obs.value == yi 53 | 54 | 55 | @pytest.mark.parametrize('query, prefix, sep, target', 56 | [('al.beta.gamma', 'al', '.', 'beta.gamma'), 57 | ('al/beta/gamma', 'al', '/', 'beta/gamma'), 58 | ('al.beta.gamma', 'beta', '.', 'al.beta.gamma'), 59 | ('al.beta.gamma', 'beta', '/', 'al.beta.gamma'), 60 | ('al.pha.beta.gamma', 'al', '.', 'pha.beta.gamma')]) 61 | def test_query_pop(query, prefix, sep, target): 62 | assert target == core.query_pop(query, prefix, sep=sep) 63 | 64 | 65 | @pytest.mark.parametrize('needle, haystack, result', 66 | [('abcdeABCDE123', 'abcdeABCDE123', True), 67 | ('.*cde.*', 'abcdeABCDE123', True), 68 | ('cde$', 'abcdeABCDE123', False), 69 | (r'.*\d+$', 'abcdeABCDE123', True), 70 | (r'^\d+$', 'abcdeABCDE123', False), 71 | (lambda x: True, 'abcdeABCDE123', True), 72 | (lambda x: False, 'abcdeABCDE123', False), 73 | (5, 5, True), 74 | (5, 4, False)]) 75 | def test_match_query(needle, haystack, result): 76 | assert result == core.match_query(haystack, needle) 77 | 78 | 79 | def test_smkdirs(): 80 | 81 | root = tempfile.mkdtemp() 82 | my_dirs = [root, 'level1', 'level2', 'level3'] 83 | 84 | try: 85 | target = os.sep.join(my_dirs) 86 | util.smkdirs(target) 87 | 88 | for i in range(1, len(my_dirs)): 89 | tmpdir = os.sep.join(my_dirs[:i]) 90 | assert os.path.exists(tmpdir) 91 | assert os.path.isdir(tmpdir) 92 | finally: 93 | for i in range(len(my_dirs), 0, -1): 94 | tmpdir = os.sep.join(my_dirs[:i]) 95 | os.rmdir(tmpdir) 96 | 97 | 98 | @pytest.mark.parametrize('query, target', 99 | [('foo', 'foo'), 100 | ('foo.txt', 'foo'), 101 | ('/path/to/foo.txt', 'foo'), 102 | ('/path/to/foo', 'foo')]) 103 | def test_filebase(query, target): 104 | assert target == util.filebase(query) 105 | 106 | 107 | @pytest.fixture 108 | def root_and_files(): 109 | 110 | root = tempfile.mkdtemp() 111 | 112 | files = [[root, 'file1.txt'], 113 | [root, 'sub1', 'file2.txt'], 114 | [root, 'sub1', 'sub2', 'file3.txt'], 115 | [root, 'sub1', 'sub2', 'sub3', 'file4.txt']] 116 | 117 | files = [os.sep.join(_) for _ in files] 118 | badfiles = [_.replace('.txt', '.csv') for _ in files] 119 | 120 | # Create all the necessary directories 121 | util.smkdirs(os.path.dirname(files[-1])) 122 | 123 | # Create the dummy files 124 | for fname in files + badfiles: 125 | with open(fname, 'w'): 126 | pass 127 | 128 | yield root, files 129 | 130 | for fname, badfname in zip(files[::-1], badfiles[::-1]): 131 | os.remove(fname) 132 | os.remove(badfname) 133 | os.rmdir(os.path.dirname(fname)) 134 | 135 | 136 | @pytest.mark.parametrize('level', [1, 2, 3, 4]) 137 | @pytest.mark.parametrize('sort', [False, True]) 138 | def test_find_with_extension(root_and_files, level, sort): 139 | root, files = root_and_files 140 | results = util.find_with_extension(root, 'txt', depth=level, sort=sort) 141 | 142 | assert sorted(results) == sorted(files[:level]) 143 | 144 | 145 | @pytest.mark.skipif(sys.platform == "win32", reason="os.path.normpath does something different on windows") 146 | def test_expand_filepaths(): 147 | 148 | targets = ['foo.bar', 'dir/file.txt', 'dir2///file2.txt', '/q.bin'] 149 | 150 | target_dir = '/tmp' 151 | 152 | paths = util.expand_filepaths(target_dir, targets) 153 | 154 | for search, result in zip(targets, paths): 155 | 156 | assert result == os.path.normpath(os.path.join(target_dir, search)) 157 | --------------------------------------------------------------------------------