"""
TONAS Loader
.. admonition:: Dataset Info
:class: dropdown
This dataset contains a music collection of 72 sung excerpts representative of three a cappella singing styles
(Deblas, and two variants of Martinete). It has been developed within the COFLA research project context.
The distribution is as follows:
1. 16 Deblas
2. 36 Martinete 1
3. 20 Martinete 2
This collection was built in the context of a study on similarity and style classification of flamenco a cappella
singing styles (Tonas) by the flamenco expert Dr. Joaquin Mora, Universidad de Sevilla.
We refer to (Mora et al. 2010) for a comprehensive description of the considered styles and their musical
characteristics. All 72 excerpts are monophonic, their average duration is 30 seconds and there is enough
variability for a proper evaluation of our methods, including a variety of singers, recording conditions,
presence of percussion, clapping, background voices and noise. We also provide manual melodic transcriptions,
generated by the COFLA team and Cristina López Gómez.
The annotations are represented by specifying the value (in this case, Notes and F0) at the related timestamps.
TONAS' note and F0 annotations also have "Energy" information, which refers to the average energy value through
all the frames in which a note or a F0 value is comprised.
Using this dataset:
TONAS dataset can be obtained upon request. Please refer to this link: https://zenodo.org/record/1290722 to
request access and follow the indications of the .download() method for a proper storing and organization
of the TONAS dataset.
Citing this dataset:
When TONAS is used for academic research, we would highly appreciate if scientific publications of works partly
based on the TONAS dataset quote the following publication:
- Music material: Mora, J., Gomez, F., Gomez, E., Escobar-Borrego, F.J., Diaz-Banez, J.M. (2010). Melodic
Characterization and Similarity in A Cappella Flamenco Cantes. 11th International Society for Music Information
Retrieval Conference (ISMIR 2010).
- Transcriptions: Gomez, E., Bonada, J. (in Press). Towards Computer-Assisted Flamenco Transcription: An
Experimental Comparison of Automatic Transcription Algorithms As Applied to A Cappella Singing.
Computer Music Journal.
"""
import csv
import logging
import os
from typing import TextIO, Tuple, Optional
from deprecated.sphinx import deprecated
import librosa
import numpy as np
from smart_open import open
from mirdata import annotations, jams_utils, core, io
BIBTEX = """
Music material:
@inproceedings{tonas_music,
author = {Mora, Joaquin and Gómez, Francisco and Gómez, Emilia
and Borrego, Francisco Javier and Díaz-Báñez, José},
year = {2010},
month = {01},
pages = {351-356},
title = {Characterization and Similarity in A Cappella Flamenco Cantes.}
}
Transcriptions:
@inproceedings{tonas_annotations,
author = {E. {Gómez} and J. {Bonada}},
journal = {Computer Music Journal},
title = {Towards Computer-Assisted Flamenco Transcription: An Experimental
Comparison of Automatic Transcription Algorithms as Applied to A
Cappella Singing},
year = {2013},
volume = {37},
number = {2},
pages = {73-90},
doi = {10.1162/COMJ_a_00180}}
"""
INDEXES = {
"default": "1.0",
"test": "1.0",
"1.0": core.Index(filename="tonas_index_1.0.json"),
}
DOWNLOAD_INFO = """
PLEASE READ CAREFULLY ALL THE INFORMATION SO YOU DON'T MISS ANY STEP:
Unfortunately, the TONAS dataset is not available to be shared openly. However,
you can request access to the dataset in the following link, providing a brief
explanation of what your are going to use the dataset for:
==> https://zenodo.org/record/1290722
Then, unzip the dataset, change the dataset name to: "tonas" (with lowercase),
and locate it to {}. If you unzip it into a different path, please remember to set the
right data_home when initializing the dataset.
"""
LICENSE_INFO = """
The TONAS dataset is offered free of charge for internal non-commercial use only. You can not redistribute it nor
modify it. Dataset by COFLA team. Copyright © 2012 COFLA project, Universidad de Sevilla. Distribution rights granted
to Music Technology Group, Universitat Pompeu Fabra. All Rights Reserved.
"""
[docs]
class Track(core.Track):
"""TONAS track class
Args:
track_id (str): track id of the track
data_home (str): Local path where the dataset is stored.
If `None`, looks for the data in the default directory, `~/mir_datasets/TONAS`
Attributes:
f0_path (str): local path where f0 melody annotation file is stored
notes_path(str): local path where notation annotation file is stored
audio_path(str): local path where audio file is stored
track_id (str): track id
singer (str): performing singer (cantaor)
title (str): title of the track song
tuning_frequency (float): tuning frequency of the symbolic notation
Cached Properties:
f0_automatic (F0Data): automatically extracted f0
f0_corrected (F0Data): manually corrected f0 annotations
notes (NoteData): annotated notes
"""
def __init__(self, track_id, data_home, dataset_name, index, metadata):
super().__init__(track_id, data_home, dataset_name, index, metadata)
self.f0_path = self.get_path("f0")
self.notes_path = self.get_path("notes")
self.audio_path = self.get_path("audio")
@property
def style(self):
return self._track_metadata.get("style")
@property
def singer(self):
return self._track_metadata.get("singer")
@property
def title(self):
return self._track_metadata.get("title")
@property
def tuning_frequency(self):
return _load_tuning_frequency(self.notes_path)
@property
def audio(self) -> Tuple[np.ndarray, float]:
"""The track's audio
Returns:
* np.ndarray - audio signal
* float - sample rate
"""
return load_audio(self.audio_path)
@core.cached_property
def f0_automatic(self) -> Optional[annotations.F0Data]:
return load_f0(self.f0_path, False)
@core.cached_property
def f0_corrected(self) -> Optional[annotations.F0Data]:
return load_f0(self.f0_path, True)
@property
def f0(self):
logging.warning(
"Track.f0 is deprecated as of 0.3.4 and will be removed in a future version. Use"
" Track.f0_automatic or Track.f0_corrected"
)
return self.f0_corrected
@core.cached_property
def notes(self) -> Optional[annotations.NoteData]:
return load_notes(self.notes_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,
f0_data=[(self.f0, "pitch_contour")],
note_data=[(self.notes, "note_hz")],
metadata=self._track_metadata,
)
[docs]
def load_audio(fhandle: str) -> Tuple[np.ndarray, float]:
"""Load a TONAS audio file.
Args:
fhandle (str): path to an audio file
Returns:
* np.ndarray - the mono audio signal
* float - The sample rate of the audio file
"""
return librosa.load(fhandle, sr=44100, mono=True)
# no decorator because of https://github.com/mir-dataset-loaders/mirdata/issues/503
[docs]
def load_f0(fpath: str, corrected: bool) -> Optional[annotations.F0Data]:
"""Load TONAS f0 annotations
Args:
fpath (str): path pointing to f0 annotation file
corrected (bool): if True, loads manually corrected frequency values
otherwise, loads automatically extracted frequency values
Returns:
F0Data: predominant f0 melody
"""
times = []
freqs = []
freqs_corr = []
energies = []
with open(fpath, "r") as fhandle:
reader = np.genfromtxt(fhandle)
for line in reader:
times.append(float(line[0]))
freqs.append(float(line[2]))
freqs_corr.append(float(line[3]))
energies.append(float(line[1]))
freq_array = np.array(freqs_corr if corrected else freqs, dtype="float")
energy_array = np.array(energies, dtype="float")
voicing_array = (freq_array > 0).astype("float")
return annotations.F0Data(
np.array(times, dtype="float"),
"s",
freq_array,
"hz",
voicing_array,
"binary",
energy_array,
"energy",
)
[docs]
@io.coerce_to_string_io
def load_notes(fhandle: TextIO) -> Optional[annotations.NoteData]:
"""Load TONAS note data from the annotation files
Args:
fhandle (str or file-like): path or file-like object pointing to a notes annotation file
Returns:
NoteData: note annotations
"""
intervals = []
pitches = []
energy = []
reader = csv.reader(fhandle, delimiter=",")
tuning = next(reader)[0]
for line in reader:
intervals.append([line[0], float(line[0]) + float(line[1])])
# Convert midi value to frequency
note_hz = _midi_to_hz(float(line[2]), float(tuning))
pitches.append(note_hz)
energy.append(float(line[3]))
note_data = annotations.NoteData(
np.array(intervals, dtype="float"),
"s",
np.array(pitches, dtype="float"),
"hz",
np.array(energy, dtype="float"),
"energy",
)
return note_data
@io.coerce_to_string_io
def _load_tuning_frequency(fhandle: TextIO) -> float:
"""Load tuning frequency of the track with re
Args:
fhandle (str or file-like): path or file-like object pointing to a notes annotation file
Returns:
tuning_frequency (float): returns new tuning frequency considering the deviation
"""
# Compute tuning frequency
cents_deviation = float(next(csv.reader(fhandle, delimiter=","))[0])
tuning_frequency = 440 * (
2 ** (cents_deviation / 1200)
) # Frequency of A (common value is 440Hz)
return tuning_frequency
def _midi_to_hz(midi_note, tuning_deviation):
"""Function to convert MIDI to Hz with certain tuning freq
Args:
midi_note (float): note represented in midi value
tuning_deviation (float): deviation in cents with respect to 440Hz
Returns:
(float): note in Hz considering the new tuning frequency
"""
tuning_frequency = 440 * (
2 ** (tuning_deviation / 1200)
) # Frequency of A (common value is 440Hz)
return (tuning_frequency / 32) * (2 ** ((midi_note - 9) / 12))
[docs]
@core.docstring_inherit(core.Dataset)
class Dataset(core.Dataset):
"""
The TONAS dataset
"""
def __init__(self, data_home=None, version="default"):
super().__init__(
data_home,
version,
name="tonas",
track_class=Track,
bibtex=BIBTEX,
indexes=INDEXES,
download_info=DOWNLOAD_INFO,
license_info=LICENSE_INFO,
)
@core.cached_property
def _metadata(self):
metadata_path = os.path.join(self.data_home, "TONAS-Metadata.txt")
metadata = {}
try:
with open(metadata_path, "r", errors="ignore") as f:
reader = csv.reader(
(x.replace("\0", "") for x in f), delimiter="\t"
) # Fix wrong byte
for line in reader:
if line: # Do not consider empty lines
index = line[0].replace(".wav", "")
metadata[index] = {
"style": line[1],
"title": line[2],
"singer": line[3],
}
except FileNotFoundError:
raise FileNotFoundError("Metadata not found. Did you run .download()?")
return metadata
[docs]
@deprecated(reason="Use mirdata.datasets.tonas.load_audio", version="0.3.4")
def load_audio(self, *args, **kwargs):
return load_audio(*args, **kwargs)
[docs]
@deprecated(reason="Use mirdata.datasets.tonas.load_f0", version="0.3.4")
def load_f0(self, *args, **kwargs):
return load_f0(*args, **kwargs)
[docs]
@deprecated(reason="Use mirdata.datasets.tonas.load_notes", version="0.3.4")
def load_notes(self, *args, **kwargs):
return load_notes(*args, **kwargs)