Source code for mirdata.datasets.maestro

"""MAESTRO Dataset Loader

.. admonition:: Dataset Info
    :class: dropdown

    MAESTRO (MIDI and Audio Edited for Synchronous TRacks and Organization) is a
    dataset composed of over 200 hours of virtuosic piano performances captured
    with fine alignment (~3 ms) between note labels and audio waveforms.

    The dataset is created and released by Google's Magenta team.

    The dataset contains over 200 hours of paired audio and MIDI recordings from
    ten years of International Piano-e-Competition. The MIDI data includes key
    strike velocities and sustain/sostenuto/una corda pedal positions. Audio and
    MIDI files are aligned with ∼3 ms accuracy and sliced to individual musical
    pieces, which are annotated with composer, title, and year of performance.
    Uncompressed audio is of CD quality or higher (44.1–48 kHz 16-bit PCM stereo).

    A train/validation/test split configuration is also proposed, so that the same
    composition, even if performed by multiple contestants, does not appear in
    multiple subsets. Repertoire is mostly classical, including composers from the
    17th to early 20th century.

    The dataset is made available by Google LLC under a Creative Commons
    Attribution Non-Commercial Share-Alike 4.0 (CC BY-NC-SA 4.0) license.

    This loader supports MAESTRO version 2.

    For more details, please visit: https://magenta.tensorflow.org/datasets/maestro

"""

import json
import logging
import os
from typing import BinaryIO, Optional, Tuple

from deprecated.sphinx import deprecated
import librosa
import numpy as np
import pretty_midi
from smart_open import open

from mirdata import core, download_utils, io, jams_utils


BIBTEX = """@inproceedings{
  hawthorne2018enabling,
  title={Enabling Factorized Piano Music Modeling and Generation with the {MAESTRO} Dataset},
  author={Curtis Hawthorne and Andriy Stasyuk and Adam Roberts and Ian Simon and Cheng-Zhi Anna Huang and Sander Dieleman and Erich Elsen and Jesse Engel and Douglas Eck},
  booktitle={International Conference on Learning Representations},
  year={2019},
  url={https://openreview.net/forum?id=r1lYRjC9F7},
}
"""

INDEXES = {
    "default": "2.0.0",
    "test": "2.0.0",
    "2.0.0": core.Index(filename="maestro_index_2.0.0.json"),
}

REMOTES = {
    "all": download_utils.RemoteFileMetadata(
        filename="maestro-v2.0.0.zip",
        url="https://storage.googleapis.com/magentadata/datasets/maestro/v2.0.0/maestro-v2.0.0.zip",
        checksum="7a6c23536ebcf3f50b1f00ac253886a7",
        unpack_directories=["maestro-v2.0.0"],
    ),
    "midi": download_utils.RemoteFileMetadata(
        filename="maestro-v2.0.0-midi.zip",
        url="https://storage.googleapis.com/magentadata/datasets/maestro/v2.0.0/maestro-v2.0.0-midi.zip",
        checksum="8a45cc678a8b23cd7bad048b1e9034c5",
        unpack_directories=["maestro-v2.0.0"],
    ),
    "metadata": download_utils.RemoteFileMetadata(
        filename="maestro-v2.0.0.json",
        url=(
            "https://storage.googleapis.com/magentadata/datasets/maestro/v2.0.0/maestro-v2.0.0.json"
        ),
        checksum="576172af1cdc4efddcf0be7d260d48f7",
    ),
}

LICENSE_INFO = (
    "Creative Commons Attribution Non-Commercial Share-Alike 4.0 (CC BY-NC-SA 4.0)."
)


[docs] class Track(core.Track): """MAESTRO Track class Args: track_id (str): track id of the track Attributes: audio_path (str): Path to the track's audio file canonical_composer (str): Composer of the piece, standardized on a single spelling for a given name. canonical_title (str): Title of the piece. Not guaranteed to be standardized to a single representation. duration (float): Duration in seconds, based on the MIDI file. midi_path (str): Path to the track's MIDI file split (str): Suggested train/validation/test split. track_id (str): track id year (int): Year of performance. Cached Property: midi (pretty_midi.PrettyMIDI): object containing MIDI annotations notes (NoteData): annotated piano notes """ def __init__(self, track_id, data_home, dataset_name, index, metadata): super().__init__(track_id, data_home, dataset_name, index, metadata) self.midi_path = self.get_path("midi") self.audio_path = self.get_path("audio") @property def canonical_composer(self): return self._track_metadata.get("canonical_composer") @property def canonical_title(self): return self._track_metadata.get("canonical_title") @property def split(self): return self._track_metadata.get("split") @property def year(self): return self._track_metadata.get("year") @property def duration(self): return self._track_metadata.get("duration") @core.cached_property def midi(self) -> Optional[pretty_midi.PrettyMIDI]: return io.load_midi(self.midi_path) @core.cached_property def notes(self): logging.warning( "The default unit for maestro pitch and velocity values have" + " changed in mirdata >0.3.3 from hz/confidence to midi/velocity" ) return io.load_notes_from_midi(self.midi_path, self.midi) @property def audio(self) -> Optional[Tuple[np.ndarray, float]]: """The track's audio Returns: * np.ndarray - audio signal * float - sample rate """ return load_audio(self.audio_path)
[docs] def to_jams(self): """Get the track's data in jams format Returns: jams.JAMS: the track's data in jams format """ return jams_utils.jams_converter( audio_path=self.audio_path, note_data=[(self.notes, None)], metadata=self._track_metadata, )
[docs] @io.coerce_to_bytes_io def load_audio(fhandle: BinaryIO) -> Tuple[np.ndarray, float]: """Load a MAESTRO audio file. Args: fhandle (str or file-like): File-like object or path to audio file Returns: * np.ndarray - the mono audio signal * float - The sample rate of the audio file """ return librosa.load(fhandle, sr=None, mono=True)
[docs] @core.docstring_inherit(core.Dataset) class Dataset(core.Dataset): """ The maestro dataset """ def __init__(self, data_home=None, version="default"): super().__init__( data_home, version, name="maestro", track_class=Track, bibtex=BIBTEX, indexes=INDEXES, remotes=REMOTES, license_info=LICENSE_INFO, ) @core.cached_property def _metadata(self): metadata_path = os.path.join(self.data_home, "maestro-v2.0.0.json") try: with open(metadata_path, "r") as fhandle: raw_metadata = json.load(fhandle) except FileNotFoundError: raise FileNotFoundError("Metadata not found. Did you run .download()?") metadata = {} for mdata in raw_metadata: track_id = mdata["midi_filename"].split(".")[0] metadata[track_id] = mdata return metadata
[docs] @deprecated(reason="Use mirdata.datasets.maestro.load_audio", version="0.3.4") def load_audio(self, *args, **kwargs): return load_audio(*args, **kwargs)
[docs] @deprecated(reason="Use mirdata.io.load_midi", version="0.3.4") def load_midi(self, *args, **kwargs): return io.load_midi(*args, **kwargs)
[docs] @deprecated(reason="Use mirdata.io.load_notes_from_midi", version="0.3.4") def load_notes(self, *args, **kwargs): return io.load_notes_from_midi(*args, **kwargs)
[docs] def download(self, partial_download=None, force_overwrite=False, cleanup=False): """Download the dataset Args: partial_download (list or None): A list of keys of remotes to partially download. If None, all data is downloaded force_overwrite (bool): If True, existing files are overwritten by the downloaded files. cleanup (bool): Whether to delete any zip/tar files after extracting. Raises: ValueError: if invalid keys are passed to partial_download IOError: if a downloaded file's checksum is different from expected """ # in MAESTRO "metadata" is contained in "midi" is contained in "all" if partial_download is None or "all" in partial_download: partial_download = ["all"] elif "midi" in partial_download: partial_download = ["midi"] download_utils.downloader( self.data_home, remotes=self.remotes, index=self._index_data, partial_download=partial_download, force_overwrite=force_overwrite, cleanup=cleanup, )