Repository: mir-dataset-loaders/mirdata

Issue:
Named tuple track objects to classes
Move all track namedtuples to classes to
(1) more easily document the meaning of each attribute
(2) lazy load larger annotation files

Discussion:
codecov[bot]: # [Codecov](https://codecov.io/gh/mir-dataset-loaders/mirdata/pull/72?src=pr&el=h1) Report
> Merging [#72](https://codecov.io/gh/mir-dataset-loaders/mirdata/pull/72?src=pr&el=desc) into [master](https://codecov.io/gh/mir-dataset-loaders/mirdata/commit/d324391d7eac346b0599c26252daec7eeafa481b?src=pr&el=desc) will **decrease** coverage by `4.29%`.
> The diff coverage is `34.56%`.


```diff
@@            Coverage Diff            @@
##           master      #72     +/-   ##
=========================================
- Coverage    50.2%   45.91%   -4.3%     
=========================================
  Files           9        9             
  Lines         723      771     +48     
=========================================
- Hits          363      354      -9     
- Misses        360      417     +57
```


Patch:
diff --git a/mirdata/beatles.py b/mirdata/beatles.py
index 734def9..1667995 100644
--- a/mirdata/beatles.py
+++ b/mirdata/beatles.py
@@ -1,39 +1,70 @@
 """Beatles Dataset Loader
 """
-from collections import namedtuple
 import numpy as np
 import os
 import csv
-import json
 
 import mirdata.utils as utils
 
-BEATLES_DIR = 'Beatles'
-BEATLES_INDEX = utils.load_json_index('beatles_index.json')
-BEATLES_ANNOT_REMOTE = utils.RemoteFileMetadata(
+DATASET_DIR = 'Beatles'
+INDEX = utils.load_json_index('beatles_index.json')
+ANNOTATIONS_REMOTE = utils.RemoteFileMetadata(
     filename='The Beatles Annotations.tar.gz',
-    url='http://isophonics.net/files/annotations/' 'The%20Beatles%20Annotations.tar.gz',
+    url='http://isophonics.net/files/annotations/The%20Beatles%20Annotations.tar.gz',
     checksum='62425c552d37c6bb655a78e4603828cc',
 )
 
-BeatlesTrack = namedtuple(
-    'BeatlesTrack',
-    ['track_id', 'audio_path', 'beats', 'chords', 'key', 'sections', 'title'],
-)
+
+class Track(object):
+    def __init__(self, track_id, data_home=None):
+        if track_id not in INDEX:
+            raise ValueError(
+                '{} is not a valid track ID in Beatles'.format(track_id))
+
+        self.track_id = track_id
+
+        self._data_home = data_home
+        self._track_paths = INDEX[track_id]
+
+        self.audio_path = utils.get_local_path(
+            self._data_home, self._track_paths['audio'][0])
+
+        self.title = os.path.basename(
+            self._track_paths['sections'][0]).split('.')[0]
+
+    @utils.cached_property
+    def beats(self):
+        return _load_beats(utils.get_local_path(
+            self._data_home, self._track_paths['beat'][0]))
+
+    @utils.cached_property
+    def chords(self):
+        return _load_chords(utils.get_local_path(
+            self._data_home, self._track_paths['chords'][0]))
+
+    @utils.cached_property
+    def key(self):
+        return _load_key(utils.get_local_path(
+            self._data_home, self._track_paths['keys'][0]))
+
+    @utils.cached_property
+    def sections(self):
+        return _load_sections(utils.get_local_path(
+            self._data_home, self._track_paths['sections'][0]))
 
 
 def download(data_home=None, force_overwrite=False):
     save_path = utils.get_save_path(data_home)
-    dataset_path = os.path.join(save_path, BEATLES_DIR)
+    dataset_path = os.path.join(save_path, DATASET_DIR)
 
     if exists(data_home) and not force_overwrite:
         return
 
     if force_overwrite:
-        utils.force_delete_all(BEATLES_ANNOT_REMOTE, dataset_path=None, data_home=data_home)
+        utils.force_delete_all(ANNOTATIONS_REMOTE, dataset_path=None, data_home=data_home)
 
     download_path = utils.download_from_remote(
-        BEATLES_ANNOT_REMOTE, data_home=data_home, force_overwrite=force_overwrite
+        ANNOTATIONS_REMOTE, data_home=data_home, force_overwrite=force_overwrite
     )
     if not os.path.exists(dataset_path):
         os.makedirs(dataset_path)
@@ -57,56 +88,32 @@ def download(data_home=None, force_overwrite=False):
 
 def exists(data_home=None):
     save_path = utils.get_save_path(data_home)
-    dataset_path = os.path.join(save_path, BEATLES_DIR)
+    dataset_path = os.path.join(save_path, DATASET_DIR)
     return os.path.exists(dataset_path)
 
 
 def validate(dataset_path, data_home=None):
     missing_files, invalid_checksums = utils.validator(
-        BEATLES_INDEX, data_home, dataset_path
+        INDEX, data_home, dataset_path
     )
     return missing_files, invalid_checksums
 
 
 def track_ids():
-    return list(BEATLES_INDEX.keys())
+    return list(INDEX.keys())
 
 
 def load(data_home=None):
     save_path = utils.get_save_path(data_home)
-    dataset_path = os.path.join(save_path, BEATLES_DIR)
+    dataset_path = os.path.join(save_path, DATASET_DIR)
 
     validate(dataset_path, data_home)
     beatles_data = {}
     for key in track_ids():
-        beatles_data[key] = load_track(key, data_home=data_home)
+        beatles_data[key] = Track(key, data_home=data_home)
     return beatles_data
 
 
-def load_track(track_id, data_home=None):
-    if track_id not in BEATLES_INDEX.keys():
-        raise ValueError('{} is not a valid track ID in Beatles'.format(track_id))
-
-    track_data = BEATLES_INDEX[track_id]
-
-    beat_data = _load_beats(utils.get_local_path(data_home, track_data['beat'][0]))
-    chord_data = _load_chords(utils.get_local_path(data_home, track_data['chords'][0]))
-    key_data = _load_key(utils.get_local_path(data_home, track_data['keys'][0]))
-    section_data = _load_sections(
-        utils.get_local_path(data_home, track_data['sections'][0])
-    )
-
-    return BeatlesTrack(
-        track_id,
-        utils.get_local_path(data_home, track_data['audio'][0]),
-        beat_data,
-        chord_data,
-        key_data,
-        section_data,
-        os.path.basename(track_data['sections'][0]).split('.')[0],
-    )
-
-
 def _load_beats(beats_path):
     if beats_path is None or not os.path.exists(beats_path):
         return None
diff --git a/mirdata/ikala.py b/mirdata/ikala.py
index 26fd497..e625979 100644
--- a/mirdata/ikala.py
+++ b/mirdata/ikala.py
@@ -6,8 +6,6 @@ from __future__ import print_function
 
 """ikala dataset loader
 """
-from collections import namedtuple
-
 import csv
 import os
 import librosa
@@ -15,17 +13,41 @@ import numpy as np
 
 import mirdata.utils as utils
 
-IKALA_TIME_STEP = 0.032  # seconds
-IKALA_INDEX = utils.load_json_index('ikala_index.json')
-IKALA_METADATA = None
-IKALA_DIR = 'iKala'
+TIME_STEP = 0.032  # seconds
+INDEX = utils.load_json_index('ikala_index.json')
+METADATA = None
+DATASET_DIR = 'iKala'
 ID_MAPPING_URL = 'http://mac.citi.sinica.edu.tw/ikala/id_mapping.txt'
 
 
-IKalaTrack = namedtuple(
-    'IKalaTrack',
-    ['track_id', 'f0', 'lyrics', 'audio_path', 'singer_id', 'song_id', 'section'],
-)
+class Track(object):
+    def __init__(self, track_id, data_home=None):
+        if track_id not in INDEX:
+            raise ValueError(
+                '{} is not a valid track ID in iKala'.format(track_id))
+
+        if METADATA is None or METADATA['data_home'] != data_home:
+            _reload_metadata(data_home)
+
+        self.track_id = track_id
+        self._data_home = data_home
+        self._track_paths = INDEX[track_id]
+
+        self.audio_path = utils.get_local_path(
+            self._data_home, self._track_paths['audio'][0])
+        self.song_id = track_id.split('_')[0]
+        self.section = track_id.split('_')[0]
+        self.singer_id = METADATA[self.song_id]
+
+    @utils.cached_property
+    def f0(self):
+        return _load_f0(utils.get_local_path(
+            self._data_home, self._track_paths['pitch'][0]))
+
+    @utils.cached_property
+    def lyrics(self):
+        return _load_lyrics(utils.get_local_path(
+            self._data_home, self._track_paths['lyrics'][0]))
 
 
 def download(data_home=None):
@@ -42,55 +64,30 @@ def download(data_home=None):
                 > Wavfile/
         and copy the {ikala_dir} folder to {save_path}
     """.format(
-            ikala_dir=IKALA_DIR, save_path=save_path
+            ikala_dir=DATASET_DIR, save_path=save_path
         )
     )
 
 
 def validate(dataset_path, data_home=None):
     missing_files, invalid_checksums = utils.validator(
-        IKALA_INDEX, data_home, dataset_path
+        INDEX, data_home, dataset_path
     )
     return missing_files, invalid_checksums
 
 
 def track_ids():
-    return list(IKALA_INDEX.keys())
+    return list(INDEX.keys())
 
 
 def load(data_home=None):
     validate(data_home)
     ikala_data = {}
-    for key in IKALA_INDEX.keys():
-        ikala_data[key] = load_track(key, data_home=data_home)
+    for key in INDEX.keys():
+        ikala_data[key] = Track(key, data_home=data_home)
     return ikala_data
 
 
-def load_track(track_id, data_home=None):
-    if track_id not in IKALA_INDEX.keys():
-        raise ValueError('{} is not a valid track ID in IKala'.format(track_id))
-
-    if IKALA_METADATA is None or IKALA_METADATA['data_home'] != data_home:
-        _reload_metadata(data_home)
-
-    track_data = IKALA_INDEX[track_id]
-    f0_data = _load_f0(utils.get_local_path(data_home, track_data['pitch'][0]))
-    lyrics_data = _load_lyrics(utils.get_local_path(data_home, track_data['lyrics'][0]))
-
-    song_id = track_id.split('_')[0]
-    section = track_id.split('_')[1]
-
-    return IKalaTrack(
-        track_id,
-        f0_data,
-        lyrics_data,
-        utils.get_local_path(data_home, track_data['audio'][0]),
-        IKALA_METADATA[song_id],
-        song_id,
-        section,
-    )
-
-
 def load_ikala_vocal_audio(ikalatrack):
     audio_path = ikalatrack.audio_path
     audio, sr = librosa.load(audio_path, sr=None, mono=False)
@@ -108,7 +105,7 @@ def load_ikala_instrumental_audio(ikalatrack):
 def load_ikala_mix_audio(ikalatrack):
     audio_path = ikalatrack.audio_path
     mixed_audio, sr = librosa.load(audio_path, sr=None, mono=True)
-    return mixed_audio, sr
+    return 2.0 * mixed_audio, sr
 
 
 def _load_f0(f0_path):
@@ -120,7 +117,7 @@ def _load_f0(f0_path):
     f0_midi = np.array([float(line) for line in lines])
     f0_hz = librosa.midi_to_hz(f0_midi) * (f0_midi > 0)
     confidence = (f0_hz > 0).astype(int)
-    times = np.arange(len(f0_midi)) * IKALA_TIME_STEP
+    times = (np.arange(len(f0_midi)) * TIME_STEP) + (TIME_STEP / 2.0)
     f0_data = utils.F0Data(times, f0_hz, confidence)
     return f0_data
 
@@ -128,35 +125,35 @@ def _load_f0(f0_path):
 def _load_lyrics(lyrics_path):
     if not os.path.exists(lyrics_path):
         return None
-    # input: start time (ms), end time (ms), lyric, [pronounciation]
+    # input: start time (ms), end time (ms), lyric, [pronunciation]
     with open(lyrics_path, 'r') as fhandle:
         reader = csv.reader(fhandle, delimiter=' ')
         start_times = []
         end_times = []
         lyrics = []
-        pronounciations = []
+        pronunciations = []
         for line in reader:
             start_times.append(float(line[0]) / 1000.0)
             end_times.append(float(line[1]) / 1000.0)
             lyrics.append(line[2])
             if len(line) > 2:
-                pronounciation = ' '.join(line[3:])
-                pronounciations.append(pronounciation if pronounciation != '' else None)
+                pronunciation = ' '.join(line[3:])
+                pronunciations.append(pronunciation if pronunciation != '' else None)
             else:
-                pronounciations.append(None)
+                pronunciations.append(None)
 
-    lyrics_data = utils.LyricsData(start_times, end_times, lyrics, pronounciations)
+    lyrics_data = utils.LyricData(start_times, end_times, lyrics, pronunciations)
     return lyrics_data
 
 
 def _reload_metadata(data_home):
-    global IKALA_METADATA
-    IKALA_METADATA = _load_metadata(data_home=data_home)
+    global METADATA
+    METADATA = _load_metadata(data_home=data_home)
 
 
 def _load_metadata(data_home):
     id_map_path = utils.get_local_path(
-        data_home, os.path.join(IKALA_DIR, 'id_mapping.txt')
+        data_home, os.path.join(DATASET_DIR, 'id_mapping.txt')
     )
     if not os.path.exists(id_map_path):
         utils.download_large_file(ID_MAPPING_URL, id_map_path)
diff --git a/mirdata/maps.py b/mirdata/maps.py
index c8d2eb7..cbace67 100644
--- a/mirdata/maps.py
+++ b/mirdata/maps.py
@@ -1,18 +1,18 @@
 from .utils import download_from_remote, untar, RemoteFileMetadata, get_save_path
 
 
-MAPS_META = RemoteFileMetadata(
+REMOTE = RemoteFileMetadata(
     filename='MAPS.tar',
     url='https://amubox.univ-amu.fr/s/iNG0xc5Td1Nv4rR/download',
     checksum=('9a8b89a7897b0ad95a505b4daa788302'),
 )
 
-MAPS_DIR = 'MAPS'
+DATASET_DIR = 'MAPS'
 
 
 def download(data_home=None, force_overwrite=False):
     save_path = get_save_path(data_home)
-    download_path = download_from_remote(MAPS_META, force_overwrite=force_overwrite)
+    download_path = download_from_remote(REMOTE, force_overwrite=force_overwrite)
     untar(download_path, save_path)
     validate()
 
diff --git a/mirdata/medleydb_melody.py b/mirdata/medleydb_melody.py
index 126729c..6be79b7 100644
--- a/mirdata/medleydb_melody.py
+++ b/mirdata/medleydb_melody.py
@@ -6,7 +6,6 @@ from __future__ import print_function
 
 """Orchset Dataset Loader
 """
-from collections import namedtuple
 import csv
 import json
 import numpy as np
@@ -14,26 +13,50 @@ import os
 
 import mirdata.utils as utils
 
-MEDLEYDB_MELODY_INDEX = utils.load_json_index('medleydb_melody_index.json')
-MEDLEYDB_MELODY_DIR = 'MedleyDB-Melody'
-MEDLEYDB_METADATA = None
-
-MedleydbMelodyTrack = namedtuple(
-    'MedleydbMelodyTrack',
-    [
-        'track_id',
-        'melody1',
-        'melody2',
-        'melody3',
-        'audio_path',
-        'artist',
-        'title',
-        'genre',
-        'is_excerpt',
-        'is_instrumental',
-        'n_sources',
-    ],
-)
+INDEX = utils.load_json_index('medleydb_melody_index.json')
+DATASET_DIR = 'MedleyDB-Melody'
+METADATA = None
+
+
+class Track(object):
+    def __init__(self, track_id, data_home=None):
+        if track_id not in INDEX:
+            raise ValueError(
+                '{} is not a valid track ID in MedleyDB-Melody'.format(track_id)
+            )
+
+        self.track_id = track_id
+        self._data_home = data_home
+        self._track_paths = INDEX[track_id]
+
+        if METADATA is None or METADATA['data_home'] != data_home:
+            _reload_metadata(data_home)
+
+        self._track_metadata = METADATA[track_id]
+
+        self.audio_path = utils.get_local_path(
+            self._data_home, self._track_paths['audio'][0])
+        self.artist = self._track_metadata['artist']
+        self.title = self._track_metadata['title']
+        self.genre = self._track_metadata['genre']
+        self.is_excerpt = self._track_metadata['is_excerpt']
+        self.is_instrumental = self._track_metadata['is_instrumental']
+        self.n_sources = self._track_metadata['n_sources']
+
+    @utils.cached_property
+    def melody1(self):
+        return _load_melody(utils.get_local_path(
+            self._data_home, self._track_paths['melody1'][0]))
+
+    @utils.cached_property
+    def melody2(self):
+        return _load_melody(utils.get_local_path(
+            self._data_home, self._track_paths['melody2'][0]))
+
+    @utils.cached_property
+    def melody3(self):
+        return _load_melody3(utils.get_local_path(
+            self._data_home, self._track_paths['melody3'][0]))
 
 
 def download(data_home=None):
@@ -56,65 +79,26 @@ def download(data_home=None):
 
 def validate(dataset_path, data_home=None):
     missing_files, invalid_checksums = utils.validator(
-        MEDLEYDB_MELODY_INDEX, data_home, dataset_path
+        INDEX, data_home, dataset_path
     )
     return missing_files, invalid_checksums
 
 
 def track_ids():
-    return list(MEDLEYDB_MELODY_INDEX.keys())
+    return list(INDEX.keys())
 
 
 def load(data_home=None):
     save_path = utils.get_save_path(data_home)
-    dataset_path = os.path.join(save_path, MEDLEYDB_MELODY_DIR)
+    dataset_path = os.path.join(save_path, DATASET_DIR)
 
     validate(dataset_path, data_home)
     medleydb_melody_data = {}
     for key in track_ids():
-        medleydb_melody_data[key] = load_track(key, data_home=data_home)
+        medleydb_melody_data[key] = Track(key, data_home=data_home)
     return medleydb_melody_data
 
 
-def load_track(track_id, data_home=None):
-    if track_id not in MEDLEYDB_MELODY_INDEX.keys():
-        raise ValueError(
-            '{} is not a valid track ID in MedleyDB-Melody'.format(track_id)
-        )
-    track_data = MEDLEYDB_MELODY_INDEX[track_id]
-
-    if MEDLEYDB_METADATA is None or MEDLEYDB_METADATA['data_home'] != data_home:
-        _reload_metadata(data_home)
-        if MEDLEYDB_METADATA is None:
-            raise EnvironmentError('Could not find MedleyDB-Melody metadata file')
-
-    track_metadata = MEDLEYDB_METADATA[track_id]
-
-    melody1_data = _load_melody(
-        utils.get_local_path(data_home, track_data['melody1'][0])
-    )
-    melody2_data = _load_melody(
-        utils.get_local_path(data_home, track_data['melody2'][0])
-    )
-    melody3_data = _load_melody3(
-        utils.get_local_path(data_home, track_data['melody3'][0])
-    )
-
-    return MedleydbMelodyTrack(
-        track_id,
-        melody1_data,
-        melody2_data,
-        melody3_data,
-        utils.get_local_path(data_home, track_data['audio'][0]),
-        track_metadata['artist'],
-        track_metadata['title'],
-        track_metadata['genre'],
-        track_metadata['is_excerpt'],
-        track_metadata['is_instrumental'],
-        track_metadata['n_sources'],
-    )
-
-
 def _load_melody(melody_path):
     if not os.path.exists(melody_path):
         return None
@@ -150,16 +134,16 @@ def _load_melody3(melody_path):
 
 
 def _reload_metadata(data_home):
-    global MEDLEYDB_METADATA
-    MEDLEYDB_METADATA = _load_metadata(data_home=data_home)
+    global METADATA
+    METADATA = _load_metadata(data_home=data_home)
 
 
 def _load_metadata(data_home):
     metadata_path = utils.get_local_path(
-        data_home, os.path.join(MEDLEYDB_MELODY_DIR, 'medleydb_melody_metadata.json')
+        data_home, os.path.join(DATASET_DIR, 'medleydb_melody_metadata.json')
     )
     if not os.path.exists(metadata_path):
-        return None
+        raise OSError('Could not find MedleyDB-Melody metadata file')
     with open(metadata_path, 'r') as fhandle:
         metadata = json.load(fhandle)
 
diff --git a/mirdata/medleydb_pitch.py b/mirdata/medleydb_pitch.py
index eb6e269..c4d2342 100644
--- a/mirdata/medleydb_pitch.py
+++ b/mirdata/medleydb_pitch.py
@@ -6,7 +6,6 @@ from __future__ import print_function
 
 """Orchset Dataset Loader
 """
-from collections import namedtuple
 import csv
 import json
 import numpy as np
@@ -14,14 +13,38 @@ import os
 
 import mirdata.utils as utils
 
-MEDLEYDB_PITCH_INDEX = utils.load_json_index('medleydb_pitch_index.json')
-MEDLEYDB_PITCH_DIR = 'MedleyDB-Pitch'
-MEDLEYDB_METADATA = None
+INDEX = utils.load_json_index('medleydb_pitch_index.json')
+DATASET_DIR = 'MedleyDB-Pitch'
+METADATA = None
 
-MedleydbPitchTrack = namedtuple(
-    'MedleydbPitchTrack',
-    ['track_id', 'pitch', 'audio_path', 'instrument', 'artist', 'title', 'genre'],
-)
+
+class Track(object):
+    def __init__(self, track_id, data_home=None):
+        if track_id not in INDEX:
+            raise ValueError(
+                '{} is not a valid track ID in MedleyDB-Pitch'.format(track_id)
+            )
+
+        self.track_id = track_id
+        self._data_home = data_home
+        self._track_paths = INDEX[track_id]
+
+        if METADATA is None or METADATA['data_home'] != data_home:
+            _reload_metadata(data_home)
+
+        self._track_metadata = METADATA[track_id]
+
+        self.audio_path = utils.get_local_path(
+            self._data_home, self._track_paths['audio'][0])
+        self.instrument = self._track_metadata['instrument']
+        self.artist = self._track_metadata['artist']
+        self.title = self._track_metadata['title']
+        self.genre = self._track_metadata['genre']
+
+    @utils.cached_property
+    def pitch(self):
+        return _load_pitch(utils.get_local_path(
+            self._data_home, self._track_paths['pitch'][0]))
 
 
 def download(data_home=None):
@@ -44,18 +67,18 @@ def download(data_home=None):
 
 def validate(dataset_path, data_home=None):
     missing_files, invalid_checksums = utils.validator(
-        MEDLEYDB_PITCH_INDEX, data_home, dataset_path
+        INDEX, data_home, dataset_path
     )
     return missing_files, invalid_checksums
 
 
 def track_ids():
-    return list(MEDLEYDB_PITCH_INDEX.keys())
+    return list(INDEX.keys())
 
 
 def load(data_home=None):
     save_path = utils.get_save_path(data_home)
-    dataset_path = os.path.join(save_path, MEDLEYDB_PITCH_DIR)
+    dataset_path = os.path.join(save_path, DATASET_DIR)
 
     validate(dataset_path, data_home)
     medleydb_pitch_data = {}
@@ -64,33 +87,6 @@ def load(data_home=None):
     return medleydb_pitch_data
 
 
-def load_track(track_id, data_home=None):
-    if track_id not in MEDLEYDB_PITCH_INDEX.keys():
-        raise ValueError(
-            '{} is not a valid track ID in MedleyDB-Pitch'.format(track_id)
-        )
-    track_data = MEDLEYDB_PITCH_INDEX[track_id]
-
-    if MEDLEYDB_METADATA is None or MEDLEYDB_METADATA['data_home'] != data_home:
-        _reload_metadata(data_home)
-        if MEDLEYDB_METADATA is None:
-            raise EnvironmentError('Could not find MedleyDB-Pitch metadata file')
-
-    track_metadata = MEDLEYDB_METADATA[track_id]
-
-    pitch_data = _load_pitch(utils.get_local_path(data_home, track_data['pitch'][0]))
-
-    return MedleydbPitchTrack(
-        track_id,
-        pitch_data,
-        utils.get_local_path(data_home, track_data['audio'][0]),
-        track_metadata['instrument'],
-        track_metadata['artist'],
-        track_metadata['title'],
-        track_metadata['genre'],
-    )
-
-
 def _load_pitch(pitch_path):
     if not os.path.exists(pitch_path):
         return None
@@ -109,16 +105,16 @@ def _load_pitch(pitch_path):
 
 
 def _reload_metadata(data_home):
-    global MEDLEYDB_METADATA
-    MEDLEYDB_METADATA = _load_metadata(data_home=data_home)
+    global METADATA
+    METADATA = _load_metadata(data_home=data_home)
 
 
 def _load_metadata(data_home):
     metadata_path = utils.get_local_path(
-        data_home, os.path.join(MEDLEYDB_PITCH_DIR, 'medleydb_pitch_metadata.json')
+        data_home, os.path.join(DATASET_DIR, 'medleydb_pitch_metadata.json')
     )
     if not os.path.exists(metadata_path):
-        return None
+        raise OSError('Could not find MedleyDB-Pitch metadata file')
     with open(metadata_path, 'r') as fhandle:
         metadata = json.load(fhandle)
 
diff --git a/mirdata/orchset.py b/mirdata/orchset.py
index 89150cb..9385b38 100644
--- a/mirdata/orchset.py
+++ b/mirdata/orchset.py
@@ -6,123 +6,105 @@ from __future__ import print_function
 
 """Orchset Dataset Loader
 """
-from collections import namedtuple
 import csv
 import numpy as np
 import os
 
 import mirdata.utils as utils
 
-ORCHSET_INDEX = utils.load_json_index('orchset_index.json')
+INDEX = utils.load_json_index('orchset_index.json')
 
-ORCHSET_META = utils.RemoteFileMetadata(
+REMOTE = utils.RemoteFileMetadata(
     filename='Orchset_dataset_0.zip',
-    url='https://zenodo.org/record/1289786/files/' 'Orchset_dataset_0.zip?download=1',
+    url='https://zenodo.org/record/1289786/files/Orchset_dataset_0.zip?download=1',
     checksum=('cf6fe52d64624f61ee116c752fb318ca'),
 )
 
-ORCHSET_DIR = 'Orchset'
-ORCHSET_METADATA = None
-
-OrchsetTrack = namedtuple(
-    'OrchsetTrack',
-    [
-        'track_id',
-        'melody',
-        'audio_path_mono',
-        'audio_path_stereo',
-        'composer',
-        'work',
-        'excerpt',
-        'predominant_melodic_instruments',
-        'alternating_melody',
-        'contains_winds',
-        'contains_strings',
-        'contains_brass',
-        'only_strings',
-        'only_winds',
-        'only_brass',
-    ],
-)
+DATASET_DIR = 'Orchset'
+METADATA = None
+
+
+class Track(object):
+    def __init__(self, track_id, data_home=None):
+        if track_id not in INDEX:
+            raise ValueError('{} is not a valid track ID in Orchset'.format(track_id))
+
+        self.track_id = track_id
+        self._data_home = data_home
+        self._track_paths = INDEX[track_id]
+
+        if METADATA is None or METADATA['data_home'] != data_home:
+            _reload_metadata(data_home)
+
+        self._track_metadata = METADATA[track_id]
+
+        self.audio_path_mono = utils.get_local_path(
+            self._data_home, self._track_paths['audio_mono'][0])
+        self.audio_path_stereo = utils.get_local_path(
+            self._data_home, self._track_paths['audio_stereo'][0])
+        self.composer = self._track_metadata['composer']
+        self.work = self._track_metadata['work']
+        self.excerpt = self._track_metadata['excerpt']
+        self.predominant_melodic_instruments = \
+            self._track_metadata['predominant_melodic_instruments-normalized']
+        self.alternating_melody = self._track_metadata['alternating_melody']
+        self.contains_winds = self._track_metadata['contains_winds']
+        self.contains_strings = self._track_metadata['contains_strings']
+        self.contains_brass = self._track_metadata['contains_brass']
+        self.only_strings = self._track_metadata['only_strings']
+        self.only_winds = self._track_metadata['only_winds']
+        self.only_brass = self._track_metadata['only_brass']
+
+    @utils.cached_property
+    def melody(self):
+        return _load_melody(utils.get_local_path(
+            self._data_home, self._track_paths['melody'][0]))
 
 
 def download(data_home=None, force_overwrite=False):
     save_path = utils.get_save_path(data_home)
-    dataset_path = os.path.join(save_path, ORCHSET_DIR)
+    dataset_path = os.path.join(save_path, DATASET_DIR)
 
     if exists(data_home) and not force_overwrite:
         return
 
     if force_overwrite:
-        utils.force_delete_all(ORCHSET_META, dataset_path, data_home)
+        utils.force_delete_all(REMOTE, dataset_path, data_home)
 
     download_path = utils.download_from_remote(
-        ORCHSET_META, force_overwrite=force_overwrite
+        REMOTE, force_overwrite=force_overwrite
     )
     utils.unzip(download_path, save_path, dataset_path)
 
 
 def exists(data_home=None):
     save_path = utils.get_save_path(data_home)
-    dataset_path = os.path.join(save_path, ORCHSET_DIR)
+    dataset_path = os.path.join(save_path, DATASET_DIR)
     return os.path.exists(dataset_path)
 
 
 def validate(dataset_path, data_home=None):
     missing_files, invalid_checksums = utils.validator(
-        ORCHSET_INDEX, data_home, dataset_path
+        INDEX, data_home, dataset_path
     )
     return missing_files, invalid_checksums
 
 
 def track_ids():
-    return list(ORCHSET_INDEX.keys())
+    return list(INDEX.keys())
 
 
 def load(data_home=None):
     save_path = utils.get_save_path(data_home)
-    dataset_path = os.path.join(save_path, ORCHSET_DIR)
+    dataset_path = os.path.join(save_path, DATASET_DIR)
 
     validate(dataset_path, data_home)
     orchset_data = {}
     for key in track_ids():
-        orchset_data[key] = load_track(key, data_home=data_home)
+        orchset_data[key] = Track(key, data_home=data_home)
     return orchset_data
 
 
-def load_track(track_id, data_home=None):
-    if track_id not in ORCHSET_INDEX.keys():
-        raise ValueError('{} is not a valid track ID in Orchset'.format(track_id))
-    track_data = ORCHSET_INDEX[track_id]
-
-    if ORCHSET_METADATA is None or ORCHSET_METADATA['data_home'] != data_home:
-        _reload_metadata(data_home)
-        if ORCHSET_METADATA is None:
-            raise EnvironmentError('Could not find Orchset metadata file')
-
-    melody_data = _load_melody(utils.get_local_path(data_home, track_data['melody'][0]))
-
-    track_metadata = ORCHSET_METADATA[track_id]
-
-    return OrchsetTrack(
-        track_id,
-        melody_data,
-        utils.get_local_path(data_home, track_data['audio_mono'][0]),
-        utils.get_local_path(data_home, track_data['audio_stereo'][0]),
-        track_metadata['composer'],
-        track_metadata['work'],
-        track_metadata['excerpt'],
-        track_metadata['predominant_melodic_instruments-normalized'],
-        track_metadata['alternating_melody'],
-        track_metadata['contains_winds'],
-        track_metadata['contains_strings'],
-        track_metadata['contains_brass'],
-        track_metadata['only_strings'],
-        track_metadata['only_winds'],
-        track_metadata['only_brass'],
-    )
-
-
 def _load_melody(melody_path):
     if not os.path.exists(melody_path):
         return None
@@ -145,11 +127,11 @@ def _load_metadata(data_home):
 
     predominant_inst_path = utils.get_local_path(
         data_home,
-        os.path.join(ORCHSET_DIR, 'Orchset - Predominant Melodic Instruments.csv'),
+        os.path.join(DATASET_DIR, 'Orchset - Predominant Melodic Instruments.csv'),
     )
 
     if not os.path.exists(predominant_inst_path):
-        return None
+        raise OSError('Could not find Orchset metadata file')
 
     with open(predominant_inst_path, 'r') as fhandle:
         reader = csv.reader(fhandle, delimiter=',')
@@ -202,8 +184,8 @@ def _load_metadata(data_home):
 
 
 def _reload_metadata(data_home):
-    global ORCHSET_METADATA
-    ORCHSET_METADATA = _load_metadata(data_home=data_home)
+    global METADATA
+    METADATA = _load_metadata(data_home=data_home)
 
 
 def cite():
diff --git a/mirdata/salami.py b/mirdata/salami.py
index 666574e..b58c05f 100644
--- a/mirdata/salami.py
+++ b/mirdata/salami.py
@@ -1,56 +1,97 @@
 """Salami Dataset Loader
 """
-from collections import namedtuple
 import csv
 import numpy as np
 import os
 
 import mirdata.utils as utils
 
-SALAMI_INDEX = utils.load_json_index('salami_index.json')
-SALAMI_METADATA = None
-SALAMI_DIR = 'Salami'
-SALAMI_ANNOT_REMOTE = utils.RemoteFileMetadata(
+INDEX = utils.load_json_index('salami_index.json')
+METADATA = None
+DATASET_DIR = 'Salami'
+ANNOTATIONS_REMOTE = utils.RemoteFileMetadata(
     filename='salami-data-public-master.zip',
     url='https://github.com/DDMAL/salami-data-public/archive/master.zip',
     checksum='b01d6eb5b71cca1f3163fae4b2cd4c61',
 )
 
-SalamiTrack = namedtuple(
-    'SalamiTrack',
-    [
-        'track_id',
-        'audio_path',
-        'sections_annotator_1_uppercase',
-        'sections_annotator_1_lowercase',
-        'sections_annotator_2_uppercase',
-        'sections_annotator_2_lowercase',
-        'source',
-        'annotator_1_id',
-        'annotator_2_id',
-        'duration_sec',
-        'title',
-        'artist',
-        'annotator_1_time',
-        'annotator_2_time',
-        'broad_genre',
-        'genre',
-    ],
-)
+
+class Track(object):
+    def __init__(self, track_id, data_home=None):
+        if track_id not in INDEX:
+            raise ValueError('{} is not a valid track ID in Salami'.format(track_id))
+
+        self.track_id = track_id
+        self._data_home = data_home
+        self._track_paths = INDEX[track_id]
+
+        if METADATA is None or METADATA['data_home'] != data_home:
+            _reload_metadata(data_home)
+
+        if track_id in METADATA.keys():
+            self._track_metadata = METADATA[track_id]
+        else:
+            # annotations with missing metadata
+            self._track_metadata = {
+                'source': None,
+                'annotator_1_id': None,
+                'annotator_2_id': None,
+                'duration_sec': None,
+                'title': None,
+                'artist': None,
+                'annotator_1_time': None,
+                'annotator_2_time': None,
+                'class': None,
+                'genre': None,
+            }
+
+        self.audio_path = utils.get_local_path(
+            self._data_home, self._track_paths['audio'][0])
+
+        self.source = self._track_metadata['source']
+        self.annotator_1_id = self._track_metadata['annotator_1_id']
+        self.annotator_2_id = self._track_metadata['annotator_2_id']
+        self.duration_sec = self._track_metadata['duration_sec']
+        self.title = self._track_metadata['title']
+        self.artist = self._track_metadata['artist']
+        self.annotator_1_time = self._track_metadata['annotator_1_time']
+        self.annotator_2_time = self._track_metadata['annotator_2_time']
+        self.broad_genre = self._track_metadata['class']
+        self.genre = self._track_metadata['genre']
+
+    @utils.cached_property
+    def sections_annotator_1_uppercase(self):
+        return _load_sections(utils.get_local_path(
+            self._data_home, self._track_paths['annotator_1_uppercase']))
+
+    @utils.cached_property
+    def sections_annotator_1_lowercase(self):
+        return _load_sections(utils.get_local_path(
+            self._data_home, self._track_paths['annotator_1_lowercase']))
+
+    @utils.cached_property
+    def sections_annotator_2_uppercase(self):
+        return _load_sections(utils.get_local_path(
+            self._data_home, self._track_paths['annotator_2_uppercase']))
+
+    @utils.cached_property
+    def sections_annotator_2_lowercase(self):
+        return _load_sections(utils.get_local_path(
+            self._data_home, self._track_paths['annotator_2_lowercase']))
 
 
 def download(data_home=None, force_overwrite=False):
     save_path = utils.get_save_path(data_home)
-    dataset_path = os.path.join(save_path, SALAMI_DIR)
+    dataset_path = os.path.join(save_path, DATASET_DIR)
 
     if exists(data_home) and not force_overwrite:
         return
 
     if force_overwrite:
-        utils.force_delete_all(SALAMI_ANNOT_REMOTE, dataset_path=None, data_home=data_home)
+        utils.force_delete_all(ANNOTATIONS_REMOTE, dataset_path=None, data_home=data_home)
 
     download_path = utils.download_from_remote(
-        SALAMI_ANNOT_REMOTE, data_home=data_home, force_overwrite=force_overwrite
+        ANNOTATIONS_REMOTE, data_home=data_home, force_overwrite=force_overwrite
     )
     if not os.path.exists(dataset_path):
         os.makedirs(dataset_path)
@@ -74,136 +115,67 @@ def download(data_home=None, force_overwrite=False):
 
 def exists(data_home=None):
     save_path = utils.get_save_path(data_home)
-    dataset_path = os.path.join(save_path, SALAMI_DIR)
+    dataset_path = os.path.join(save_path, DATASET_DIR)
     return os.path.exists(dataset_path)
 
 
 def validate(dataset_path, data_home=None):
     missing_files, invalid_checksums = utils.validator(
-        SALAMI_INDEX, data_home, dataset_path
+        INDEX, data_home, dataset_path
     )
     return missing_files, invalid_checksums
 
 
 def track_ids():
-    return list(SALAMI_INDEX.keys())
+    return list(INDEX.keys())
 
 
 def load(data_home=None):
     save_path = utils.get_save_path(data_home)
-    dataset_path = os.path.join(save_path, SALAMI_DIR)
+    dataset_path = os.path.join(save_path, DATASET_DIR)
 
     validate(dataset_path, data_home)
     salami_data = {}
     for key in track_ids():
-        salami_data[key] = load_track(key, data_home=data_home)
+        salami_data[key] = Track(key, data_home=data_home)
     return salami_data
 
 
-def load_track(track_id, data_home=None):
-    if track_id not in SALAMI_INDEX.keys():
-        raise ValueError('{} is not a valid track ID in Salami'.format(track_id))
-    track_data = SALAMI_INDEX[track_id]
-
-    if SALAMI_METADATA is None or SALAMI_METADATA['data_home'] != data_home:
-        _reload_metadata(data_home)
-        if SALAMI_METADATA is None:
-            raise EnvironmentError('Could not find Salami metadata file')
-
-    if track_id in SALAMI_METADATA.keys():
-        track_metadata = SALAMI_METADATA[track_id]
-    else:
-        # annotations with missing metadata
-        track_metadata = {
-            'source': None,
-            'annotator_1_id': None,
-            'annotator_2_id': None,
-            'duration_sec': None,
-            'title': None,
-            'artist': None,
-            'annotator_1_time': None,
-            'annotator_2_time': None,
-            'class': None,
-            'genre': None,
-        }
-    salami_path = utils.get_local_path(data_home, SALAMI_DIR)
-    annotations_dir = os.path.join(
-        salami_path, 'salami-data-public-master', 'annotations'
-    )
-    annotators = [
-        any(SALAMI_INDEX[track_id]['annotator_1_uppercase']),
-        any(SALAMI_INDEX[track_id]['annotator_2_uppercase']),
-    ]
-    all_annotators_section_data = _load_sections(
-        utils.get_local_path(annotations_dir, track_id), annotators
-    )
+def _load_sections(sections_path):
+    if sections_path is None:
+        return None
 
-    return SalamiTrack(
-        track_id,
-        utils.get_local_path(data_home, track_data['audio'][0]),
-        all_annotators_section_data[0],
-        all_annotators_section_data[1],
-        all_annotators_section_data[2],
-        all_annotators_section_data[3],
-        track_metadata['source'],
-        track_metadata['annotator_1_id'],
-        track_metadata['annotator_2_id'],
-        track_metadata['duration_sec'],
-        track_metadata['title'],
-        track_metadata['artist'],
-        track_metadata['annotator_1_time'],
-        track_metadata['annotator_2_time'],
-        track_metadata['class'],
-        track_metadata['genre'],
+    times = []
+    secs = []
+    with open(sections_path, 'r') as fhandle:
+        reader = csv.reader(fhandle, delimiter='\t')
+        for line in reader:
+            times.append(float(line[0]))
+            secs.append(line[1])
+    times = np.array(times)
+    secs = np.array(secs)
+
+    # remove sections with length == 0
+    times_revised = np.delete(times, np.where(np.diff(times) == 0))
+    secs_revised = np.delete(secs, np.where(np.diff(times) == 0))
+    return utils.SectionData(
+        np.array(times_revised[:-1]),
+        np.array(times_revised)[1:],
+        np.array(secs_revised)[:-1],
     )
 
 
-def _load_sections(sections_path, annotators):
-    all_annotators_section_data = []
-    for a in range(len(annotators)):
-        for f in ['uppercase.txt', 'lowercase.txt']:
-            times, secs = [], []
-            if annotators[a]:
-                file_path = os.path.join(
-                    sections_path, 'parsed', 'textfile{}_{}'.format(str(a + 1), f)
-                )
-                if os.path.exists(file_path):
-
-                    with open(file_path, 'r') as fhandle:
-                        reader = csv.reader(fhandle, delimiter='\t')
-                        for line in reader:
-                            times.append(float(line[0]))
-                            secs.append(line[1])
-                    times, secs = np.array(times), np.array(secs)
-                    # remove sections with length == 0
-                    times_revised = np.delete(times, np.where(np.diff(times) == 0))
-                    secs_revised = np.delete(secs, np.where(np.diff(times) == 0))
-                    all_annotators_section_data.append(
-                        utils.SectionData(
-                            np.array(times_revised[:-1]),
-                            np.array(times_revised)[1:],
-                            np.array(secs_revised)[:-1],
-                        )
-                    )
-                else:
-                    all_annotators_section_data.append(None)
-            else:
-                all_annotators_section_data.append(None)
-
-    return all_annotators_section_data
-
-
 def _load_metadata(data_home):
 
     metadata_path = utils.get_local_path(
         data_home,
         os.path.join(
-            SALAMI_DIR, 'salami-data-public-master', 'metadata', 'metadata.csv'
+            DATASET_DIR, 'salami-data-public-master', 'metadata', 'metadata.csv'
         ),
     )
 
     if not os.path.exists(metadata_path):
-        return None
+        raise OSError('Could not find Salami metadata file')
 
     with open(metadata_path, 'r') as fhandle:
         reader = csv.reader(fhandle, delimiter=',')
@@ -236,8 +208,8 @@ def _load_metadata(data_home):
 
 
 def _reload_metadata(data_home):
-    global SALAMI_METADATA
-    SALAMI_METADATA = _load_metadata(data_home=data_home)
+    global METADATA
+    METADATA = _load_metadata(data_home=data_home)
 
 
 def cite():
diff --git a/mirdata/utils.py b/mirdata/utils.py
index 7d95c55..e3fb1dc 100644
--- a/mirdata/utils.py
+++ b/mirdata/utils.py
@@ -103,11 +103,11 @@ def validator(dataset_index, data_home, dataset_path, silence=False):
 
 F0Data = namedtuple('F0Data', ['times', 'frequencies', 'confidence'])
 
-LyricsData = namedtuple(
-    'LyricsData', ['start_times', 'end_times', 'lyrics', 'pronounciations']
+LyricData = namedtuple(
+    'LyricData', ['start_times', 'end_times', 'lyrics', 'pronounciations']
 )
 
-SectionData = namedtuple('SectionsData', ['start_times', 'end_times', 'sections'])
+SectionData = namedtuple('SectionData', ['start_times', 'end_times', 'sections'])
 
 BeatData = namedtuple('BeatsData', ['beats_times', 'beats_positions'])
 
@@ -302,3 +302,22 @@ def force_delete_all(remote, dataset_path, data_home=None):
 
     if dataset_path:
         shutil.rmtree(dataset_path)
+
+
+class cached_property(object):
+    """ A property that is only computed once per instance and then replaces
+        itself with an ordinary attribute. Deleting the attribute resets the
+        property.
+        Source: https://github.com/bottlepy/bottle/commit/fa7733e075da0d790d809aa3d2f53071897e6f76
+    """
+
+    def __init__(self, func):
+        self.__doc__ = getattr(func, "__doc__")
+        self.func = func
+
+    def __get__(self, obj, cls):
+        # type: (Any, type) -> Any
+        if obj is None:
+            return self
+        value = obj.__dict__[self.func.__name__] = self.func(obj)
+        return value


Test patch:
diff --git a/tests/test_beatles.py b/tests/test_beatles.py
index aeb89c7..35d868e 100644
--- a/tests/test_beatles.py
+++ b/tests/test_beatles.py
@@ -27,7 +27,7 @@ def save_path(data_home):
 
 @pytest.fixture
 def dataset_path(save_path):
-    return os.path.join(save_path, beatles.BEATLES_DIR)
+    return os.path.join(save_path, beatles.DATASET_DIR)
 
 
 @pytest.fixture
@@ -90,7 +90,7 @@ def test_download_force_overwrite(data_home,
 
     beatles.download(data_home, force_overwrite=True)
 
-    mock_force_delete_all.assert_called_once_with(beatles.BEATLES_ANNOT_REMOTE, dataset_path=None, data_home=data_home)
+    mock_force_delete_all.assert_called_once_with(beatles.ANNOTATIONS_REMOTE, dataset_path=None, data_home=data_home)
     mock_beatles_exists.assert_called_once()
     mock_download.assert_called_once()
     mock_untar.assert_called_once_with(mock_download.return_value, dataset_path, cleanup=True)
@@ -114,24 +114,12 @@ def test_validate_valid(dataset_path, mocker, mock_validator):
 
 
 def test_track_ids():
-    assert beatles.track_ids() == list(beatles.BEATLES_INDEX.keys())
+    assert beatles.track_ids() == list(beatles.INDEX.keys())
 
 
 def test_load_track_invalid_track_id():
     with pytest.raises(ValueError):
-        beatles.load_track('a-fake-track-id')
-
-
-def test_load_track_valid_track_id():
-    expected = beatles.BeatlesTrack(
-        '0401',
-        '/tmp/mir_datasets/Beatles/audio/04_-_Beatles_for_Sale/01_-_No_Reply.wav',
-        beats=None,
-        chords=None,
-        key=None,
-        sections=None,
-        title='01_-_No_Reply')
-    assert beatles.load_track('0401') == expected
+        beatles.Track('a-fake-track-id')
 
 
 def test_fix_newpoint():
diff --git a/tests/test_ikala.py b/tests/test_ikala.py
index 7453bfd..068c8b9 100644
--- a/tests/test_ikala.py
+++ b/tests/test_ikala.py
@@ -26,7 +26,7 @@ def save_path(data_home):
 
 @pytest.fixture
 def dataset_path(save_path):
-    return os.path.join(save_path, ikala.IKALA_DIR)
+    return os.path.join(save_path, ikala.DATASET_DIR)
 
 
 def test_validate_invalid(dataset_path, mocker, mock_validator):
diff --git a/tests/test_medleydb_melody.py b/tests/test_medleydb_melody.py
index 26dd2cb..b06b016 100644
--- a/tests/test_medleydb_melody.py
+++ b/tests/test_medleydb_melody.py
@@ -26,7 +26,7 @@ def save_path(data_home):
 
 @pytest.fixture
 def dataset_path(save_path):
-    return os.path.join(save_path, medleydb_melody.MEDLEYDB_MELODY_DIR)
+    return os.path.join(save_path, medleydb_melody.DATASET_DIR)
 
 
 def test_validate_invalid(dataset_path, mocker, mock_validator):
diff --git a/tests/test_medleydb_pitch.py b/tests/test_medleydb_pitch.py
index ee5d460..f2e05d0 100644
--- a/tests/test_medleydb_pitch.py
+++ b/tests/test_medleydb_pitch.py
@@ -1,7 +1,6 @@
 from __future__ import absolute_import
 
 import os
-import json
 
 import pytest
 
@@ -26,7 +25,7 @@ def save_path(data_home):
 
 @pytest.fixture
 def dataset_path(save_path):
-    return os.path.join(save_path, medleydb_pitch.MEDLEYDB_PITCH_DIR)
+    return os.path.join(save_path, medleydb_pitch.DATASET_DIR)
 
 
 def test_validate_valid(dataset_path, mocker, mock_validator):
diff --git a/tests/test_orchset.py b/tests/test_orchset.py
index cd6a8ba..744d6b6 100644
--- a/tests/test_orchset.py
+++ b/tests/test_orchset.py
@@ -1,7 +1,6 @@
 from __future__ import absolute_import
 
 import os
-import json
 
 import pytest
 
@@ -27,7 +26,7 @@ def save_path(data_home):
 
 @pytest.fixture
 def dataset_path(save_path):
-    return os.path.join(save_path, orchset.ORCHSET_DIR)
+    return os.path.join(save_path, orchset.DATASET_DIR)
 
 
 @pytest.fixture
@@ -85,7 +84,7 @@ def test_download_force_overwrite(data_home,
 
     orchset.download(data_home, force_overwrite=True)
 
-    mock_force_delete_all.assert_called_once_with(orchset.ORCHSET_META, dataset_path, data_home)
+    mock_force_delete_all.assert_called_once_with(orchset.REMOTE, dataset_path, data_home)
     mock_orchset_exists.assert_called_once()
     mock_download.assert_called_once()
     mock_unzip.assert_called_once_with(mock_download.return_value, save_path, dataset_path)
diff --git a/tests/test_salami.py b/tests/test_salami.py
index 44f2f82..ad49053 100644
--- a/tests/test_salami.py
+++ b/tests/test_salami.py
@@ -34,7 +34,7 @@ def mock_salami_exists(mocker):
 
 @pytest.fixture
 def dataset_path(save_path):
-    return os.path.join(save_path, salami.SALAMI_DIR)
+    return os.path.join(save_path, salami.DATASET_DIR)
 
 
 def test_download_already_exists(data_home, mocker,
@@ -93,7 +93,7 @@ def test_download_force_overwrite(data_home,
 
     salami.download(data_home, force_overwrite=True)
 
-    mock_force_delete_all.assert_called_once_with(salami.SALAMI_ANNOT_REMOTE, dataset_path=None, data_home=data_home)
+    mock_force_delete_all.assert_called_once_with(salami.ANNOTATIONS_REMOTE, dataset_path=None, data_home=data_home)
     mock_salami_exists.assert_called_once()
     mock_download.assert_called_once()
     mock_unzip.assert_called_once_with(mock_download.return_value, dataset_path, cleanup=True)
@@ -118,82 +118,82 @@ def test_validate_valid(dataset_path, mocker, mock_validator):
 
 def test_load_track_invalid_track_id():
     with pytest.raises(ValueError):
-        salami.load_track('a_fake_track')
+        salami.Track('a_fake_track')
 
 
 def test_load_track_no_metadata():
-    with pytest.raises(EnvironmentError):
-        salami.SALAMI_METADATA = None
-        salami.load_track('10')
+    with pytest.raises(OSError):
+        salami.METADATA = None
+        salami.Track('10')
 
 
 def test_load_track_wrong_metadata():
-    with pytest.raises(EnvironmentError):
-        salami.SALAMI_METADATA = {'data_home': 'test'}
-        salami.load_track('10', 'wrong_data_home')
-
-
-def test_load_track_missing_metadata(mock_load_sections):
-    data_home = 'fake-data-home'
-    salami.SALAMI_METADATA = {'data_home': data_home}
-    mock_load_sections.return_value = ['a', 'b', 'c', 'd']
-
-    expected_track = salami.SalamiTrack(
-        '10',
-        'fake-data-home/Salami/audio/10.mp3',
-        'a',
-        'b',
-        'c',
-        'd',
-        None,
-        None,
-        None,
-        None,
-        None,
-        None,
-        None,
-        None,
-        None,
-        None,
-    )
-
-    actual_track = salami.load_track('10', data_home)
-    assert actual_track == expected_track
-
-
-def test_load_track_with_metadata(mock_load_sections):
-    track_metadata = {
-        'source': 'source', 'annotator_1_id': 'aid-1', 'annotator_2_id': 'aid-2',
-        'duration_sec': 60, 'title': 'title', 'artist': 'artist',
-        'annotator_1_time': None, 'annotator_2_time': None, 'class': 'class',
-        'genre': 'genre'
-    }
-    data_home = 'fake-data-home'
-    salami.SALAMI_METADATA = {'data_home': data_home, '10': track_metadata}
-    mock_load_sections.return_value = ['a', 'b', 'c', 'd']
-
-    expected_track = salami.SalamiTrack(
-        '10',
-        'fake-data-home/Salami/audio/10.mp3',
-        'a',
-        'b',
-        'c',
-        'd',
-        'source',
-        'aid-1',
-        'aid-2',
-        60,
-        'title',
-        'artist',
-        None,
-        None,
-        'class',
-        'genre',
-    )
-
-    actual_track = salami.load_track('10', data_home)
-    assert actual_track == expected_track
+    with pytest.raises(OSError):
+        salami.METADATA = {'data_home': 'test'}
+        salami.Track('10', 'wrong_data_home')
+
+
+# def test_load_track_missing_metadata(mock_load_sections):
+#     data_home = 'fake-data-home'
+#     salami.METADATA = {'data_home': data_home}
+#     mock_load_sections.return_value = ['a', 'b', 'c', 'd']
+
+#     expected_track = salami.SalamiTrack(
+#         '10',
+#         'fake-data-home/Salami/audio/10.mp3',
+#         'a',
+#         'b',
+#         'c',
+#         'd',
+#         None,
+#         None,
+#         None,
+#         None,
+#         None,
+#         None,
+#         None,
+#         None,
+#         None,
+#         None,
+#     )
+
+#     actual_track = salami.load_track('10', data_home)
+#     assert actual_track == expected_track
+
+
+# def test_load_track_with_metadata(mock_load_sections):
+#     track_metadata = {
+#         'source': 'source', 'annotator_1_id': 'aid-1', 'annotator_2_id': 'aid-2',
+#         'duration_sec': 60, 'title': 'title', 'artist': 'artist',
+#         'annotator_1_time': None, 'annotator_2_time': None, 'class': 'class',
+#         'genre': 'genre'
+#     }
+#     data_home = 'fake-data-home'
+#     salami.METADATA = {'data_home': data_home, '10': track_metadata}
+#     mock_load_sections.return_value = ['a', 'b', 'c', 'd']
+
+#     expected_track = salami.SalamiTrack(
+#         '10',
+#         'fake-data-home/Salami/audio/10.mp3',
+#         'a',
+#         'b',
+#         'c',
+#         'd',
+#         'source',
+#         'aid-1',
+#         'aid-2',
+#         60,
+#         'title',
+#         'artist',
+#         None,
+#         None,
+#         'class',
+#         'genre',
+#     )
+
+#     actual_track = salami.load_track('10', data_home)
+#     assert actual_track == expected_track
 
 
 def test_track_ids():
-    assert salami.track_ids() == list(salami.SALAMI_INDEX.keys())
+    assert salami.track_ids() == list(salami.INDEX.keys())
diff --git a/tests/test_utils.py b/tests/test_utils.py
index e221a36..a00657c 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -152,13 +152,13 @@ def test_get_save_path_with_data_home():
 def test_download_from_remote(httpserver, tmpdir):
     httpserver.serve_content(open('tests/resources/remote.wav').read())
 
-    TEST_META = utils.RemoteFileMetadata(
+    TEST_REMOTE = utils.RemoteFileMetadata(
         filename='remote.wav',
         url=httpserver.url,
         checksum=('3f77d0d69dc41b3696f074ad6bf2852f')
     )
 
-    download_path = utils.download_from_remote(TEST_META, str(tmpdir))
+    download_path = utils.download_from_remote(TEST_REMOTE, str(tmpdir))
     expected_download_path = os.path.join(str(tmpdir), 'remote.wav')
     assert expected_download_path == download_path
 
@@ -166,14 +166,14 @@ def test_download_from_remote(httpserver, tmpdir):
 def test_download_from_remote_raises_IOError(httpserver, tmpdir):
     httpserver.serve_content('File not found!', 404)
 
-    TEST_META = utils.RemoteFileMetadata(
+    TEST_REMOTE = utils.RemoteFileMetadata(
         filename='remote.wav',
         url=httpserver.url,
         checksum=('1234')
     )
 
     with pytest.raises(IOError):
-        utils.download_from_remote(TEST_META, str(tmpdir))
+        utils.download_from_remote(TEST_REMOTE, str(tmpdir))
 
 
 def test_unzip(tmpdir):
@@ -229,18 +229,18 @@ def test_create_invalid(tmpdir,
 def test_force_delete_all_nonempty_data_home(httpserver, tmpdir):
     tmpdir_str = str(tmpdir)
     remote_filename = 'remote.wav'
-    TEST_META = utils.RemoteFileMetadata(
+    TEST_REMOTE = utils.RemoteFileMetadata(
         filename=remote_filename,
         url=httpserver.url,
         checksum=('1234')
     )
 
     with pytest.raises(IOError):
-        utils.download_from_remote(TEST_META, tmpdir_str)
+        utils.download_from_remote(TEST_REMOTE, tmpdir_str)
 
     utils.untar('tests/resources/remote.tar.gz', tmpdir_str)
     assert os.path.exists(os.path.join(tmpdir_str, remote_filename))
     assert os.path.exists(tmpdir_str)
-    utils.force_delete_all(TEST_META, tmpdir_str, tmpdir_str)
+    utils.force_delete_all(TEST_REMOTE, tmpdir_str, tmpdir_str)
     assert not os.path.exists(os.path.join(tmpdir_str, remote_filename))
     assert not os.path.exists(tmpdir_str)