Repository: mir-dataset-loaders/mirdata

Issue:
f0s_to_jams is not compliant with pitch_contour namespace
The function `jams_utils.f0s_to_jams` produces a `jam` object whose `Annotation` is marked as `pitch_contour`. However, this `Annotation` is not compliant with the official `pitch_contour` namespace of JAMS. The consequence is that orchset Tracks cannot be exported to JAMS. 

Minimal non-working example:

```python
from mirdata import orchset

X = orchset.load()
keys = list(X.keys())
key = keys[0]
jam = X[key].to_jams()
jam.validate()
```

Result:
```
---------------------------------------------------------------------------
ValidationError                           Traceback (most recent call last)
~/miniconda3/lib/python3.7/site-packages/jams/core.py in validate(self, strict)
   1807         try:
-> 1808             schema.VALIDATOR.validate(self.__json_light__, schema.JAMS_SCHEMA)
   1809 

~/miniconda3/lib/python3.7/site-packages/jsonschema/validators.py in validate(self, *args, **kwargs)
    352             for error in self.iter_errors(*args, **kwargs):
--> 353                 raise error
    354 

ValidationError: None is not of type 'number'

Failed validating 'type' in schema['properties']['file_metadata']['properties']['duration']:
    {'minimum': 0.0, 'type': 'number'}

On instance['file_metadata']['duration']:
    None

During handling of the above exception, another exception occurred:

SchemaError                               Traceback (most recent call last)
<ipython-input-2-dbd09b7b0fbe> in <module>
----> 1 jam.validate()

~/miniconda3/lib/python3.7/site-packages/jams/core.py in validate(self, strict)
   1821         except jsonschema.ValidationError as invalid:
   1822             if strict:
-> 1823                 raise SchemaError(str(invalid))
   1824             else:
   1825                 warnings.warn(str(invalid))

SchemaError: None is not of type 'number'

Failed validating 'type' in schema['properties']['file_metadata']['properties']['duration']:
    {'minimum': 0.0, 'type': 'number'}

On instance['file_metadata']['duration']:
    None
```

It appears that the values in the Annotation of `f0s_to_jams` are of type `float`, as they describe the frequency in Hertz.
However, `pitch_contour` values are supposed to be triplets: `(index, frequency, voicing)`.
Note that voicing is boolean ... this is pre-Bittner and Bosch ISMIR 2019 ;) 



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


```diff
@@            Coverage Diff             @@
##           master     #212      +/-   ##
==========================================
- Coverage   95.97%   95.43%   -0.55%     
==========================================
  Files          19       19              
  Lines        1938     1949      +11     
==========================================
  Hits         1860     1860              
- Misses         78       89      +11
```

rabitt: A caveat - the big change I pushed results in a less complete jams object in the `to_jams` function. Unfortunately, without substantially changing the converter, this is not trivial and I propose we leave this issue for the future.

Patch:
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index f81bf23..504f016 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -14,6 +14,8 @@ Finally, run tox with `tox`.  All tests should pass!
 
 ## Contributing a new dataset loader
 
+**IMPORTANT:** when starting your pull request lease use the **new_loader.md template**, it will simplify the reviewing process and also help you make a complete PR. You can do that by adding `&template=new_loader.md` at the end of the url when your creating the PR (e.g. `...mir-dataset-loaders/mirdata/compare?expand=1` will become `...mir-dataset-loaders/mirdata/compare?expand=1&template=new_loader.md`.
+
 
 ### Dataset checklist
 
@@ -31,7 +33,7 @@ If your dataset **is not fully downloadable** there are two extra steps you shou
 1. Contacting the mirdata organizers by opening an issue or PR so we can discuss how to proceed with the closed dataset.
 2. Show that the version used to create the checksum is the "canonical" one, either by getting the version from the dataset creator, or by verifying equivalence with several other copies of the dataset.
 
-To reduce friction, we will make commits on top of contributors pull requests by default unless they use the `please-do-not-edit` flag. Please use the **new_loader.md** template for your pull request by adding &template=new_loader.md at the end of the url when your creating the PR (e.g. `...mir-dataset-loaders/mirdata/compare?expand=1` will become `...mir-dataset-loaders/mirdata/compare?expand=1&template=new_loader.md`.
+To reduce friction, we will make commits on top of contributors pull requests by default unless they use the `please-do-not-edit` flag. 
 
 ### Dataset description:
 
diff --git a/mirdata/beatles.py b/mirdata/beatles.py
index 16f9a93..20f30f0 100644
--- a/mirdata/beatles.py
+++ b/mirdata/beatles.py
@@ -104,7 +104,11 @@ class Track(track.Track):
             section_data=[(self.sections, None)],
             chord_data=[(self.chords, None)],
             key_data=[(self.key, None)],
-            metadata={'artist': 'The Beatles', 'title': self.title},
+            metadata={
+                'artist': 'The Beatles',
+                'title': self.title,
+                'duration': librosa.get_duration(self.audio[0], self.audio[1]),
+            },
         )
 
 
diff --git a/mirdata/gtzan_genre.py b/mirdata/gtzan_genre.py
index e45aaac..8df3c44 100644
--- a/mirdata/gtzan_genre.py
+++ b/mirdata/gtzan_genre.py
@@ -28,7 +28,6 @@ DATASET_REMOTE = download_utils.RemoteFileMetadata(
     destination_dir="gtzan_genre",
 )
 
-
 DATA = utils.LargeData("gtzan_genre_index.json")
 
 
@@ -70,12 +69,21 @@ class Track(track.Track):
         return load_audio(self.audio_path, sample_rate=22050)
 
     def to_jams(self):
-        """(Not Implemented) Jams: the track's data in jams format"""
-        raise NotImplementedError
+        """Jams: the track's data in jams format"""
+        return jams_utils.jams_converter(
+            tags_gtzan_data=[(self.genre, 'gtzan-genre')],
+            metadata={
+                'title': "Unknown track",
+                'artist': "Unknown artist",
+                'release': "Unknown album",
+                'duration': 30.0,
+                'curator': 'George Tzanetakis',
+            },
+        )
 
 
 def load_audio(audio_path, sample_rate=22050):
-    """Load a GTzan audio file.
+    """Load a GTZAN audio file.
 
     Args:
         audio_path (str): path to audio file
diff --git a/mirdata/ikala.py b/mirdata/ikala.py
index 3f7c4c0..faf8dec 100644
--- a/mirdata/ikala.py
+++ b/mirdata/ikala.py
@@ -140,6 +140,7 @@ class Track(track.Track):
                 'singer_id': self.singer_id,
                 'track_id': self.track_id,
                 'song_id': self.song_id,
+                'duration': librosa.get_duration(self.mix_audio[0], self.mix_audio[1]),
             },
         )
 
diff --git a/mirdata/jams_utils.py b/mirdata/jams_utils.py
index 162ce3a..d806b1b 100644
--- a/mirdata/jams_utils.py
+++ b/mirdata/jams_utils.py
@@ -15,6 +15,7 @@ def jams_converter(
     multi_section_data=None,
     key_data=None,
     lyrics_data=None,
+    tags_gtzan_data=None,
     metadata=None,
 ):
 
@@ -41,6 +42,9 @@ def jams_converter(
         A list of tuples of (KeyData, str), where str describes the annotation.
     lyrics_data (list or None):
         A list of tuples of (LyricData, str), where str describes the annotation.
+    tags_gtzan_data (list or None):
+        A list of tuples of (str, str), where the first srt is the tag and the second
+        is a descriptor of the annotation.
     metadata (dict or None):
         A dictionary containing the track metadata.
 
@@ -59,12 +63,13 @@ def jams_converter(
                 setattr(jam.file_metadata, key, metadata[key])
             else:
                 setattr(jam.sandbox, key, metadata[key])
+
     # beats
     if beat_data is not None:
-        if type(beat_data) != list:
+        if not isinstance(beat_data, list):
             raise TypeError('beat_data should be a list of tuples')
         for beats in beat_data:
-            if type(beats) != tuple:
+            if not isinstance(beats, tuple):
                 raise TypeError(
                     'beat_data should be a list of tuples, '
                     + 'but contains a {} element'.format(type(beats))
@@ -73,10 +78,10 @@ def jams_converter(
 
     # sections
     if section_data is not None:
-        if type(section_data) != list:
+        if not isinstance(section_data, list):
             raise TypeError('section_data should be a list of tuples')
         for sections in section_data:
-            if type(sections) != tuple:
+            if not isinstance(sections, tuple):
                 raise TypeError(
                     'section_data should be a list of tuples, '
                     + 'but contains a {} element'.format(type(sections))
@@ -85,15 +90,17 @@ def jams_converter(
 
     # multi-sections (sections with multiple levels)
     if multi_section_data is not None:
-        if type(multi_section_data) != list:
+        if not isinstance(multi_section_data, list):
             raise TypeError('multi_section_data should be a list of tuples')
         for sections in multi_section_data:
-            if type(sections) != tuple:
+            if not isinstance(sections, tuple):
                 raise TypeError(
                     'multi_section_data should be a list of tuples, '
                     + 'but contains a {} element'.format(type(sections))
                 )
-            if (type(sections[0]) != list) or (type(sections[0][0]) != tuple):
+            if not (
+                isinstance(sections[0], list) and isinstance(sections[0][0], tuple)
+            ):
                 raise TypeError(
                     'tuples in multi_section_data should contain a '
                     + 'list of tuples, indicating annotations in the different '
@@ -104,10 +111,10 @@ def jams_converter(
 
     # chords
     if chord_data is not None:
-        if type(chord_data) != list:
+        if not isinstance(chord_data, list):
             raise TypeError('chord_data should be a list of tuples')
         for chords in chord_data:
-            if type(chords) != tuple:
+            if not isinstance(chords, tuple):
                 raise TypeError(
                     'chord_data should be a list of tuples, '
                     + 'but contains a {} element'.format(type(chords))
@@ -116,10 +123,10 @@ def jams_converter(
 
     # notes
     if note_data is not None:
-        if type(note_data) != list:
+        if not isinstance(note_data, list):
             raise TypeError('note_data should be a list of tuples')
         for notes in note_data:
-            if type(notes) != tuple:
+            if not isinstance(notes, tuple):
                 raise TypeError(
                     'note_data should be a list of tuples, '
                     + 'but contains a {} element'.format(type(notes))
@@ -128,10 +135,10 @@ def jams_converter(
 
     # keys
     if key_data is not None:
-        if type(key_data) != list:
+        if not isinstance(key_data, list):
             raise TypeError('key_data should be a list of tuples')
         for keys in key_data:
-            if type(keys) != tuple:
+            if not isinstance(keys, tuple):
                 raise TypeError(
                     'key_data should be a list of tuples, '
                     + 'but contains a {} element'.format(type(keys))
@@ -140,10 +147,10 @@ def jams_converter(
 
     # f0
     if f0_data is not None:
-        if type(f0_data) != list:
+        if not isinstance(f0_data, list):
             raise TypeError('f0_data should be a list of tuples')
         for f0s in f0_data:
-            if type(f0s) != tuple:
+            if not isinstance(f0s, tuple):
                 raise TypeError(
                     'f0_data should be a list of tuples, '
                     + 'but contains a {} element'.format(type(f0s))
@@ -152,16 +159,28 @@ def jams_converter(
 
     # lyrics
     if lyrics_data is not None:
-        if type(lyrics_data) != list:
+        if not isinstance(lyrics_data, list):
             raise TypeError('lyrics_data should be a list of tuples')
         for lyrics in lyrics_data:
-            if type(lyrics) != tuple:
+            if not isinstance(lyrics, tuple):
                 raise TypeError(
                     'lyrics_data should be a list of tuples, '
                     + 'but contains a {} element'.format(type(lyrics))
                 )
             jam.annotations.append(lyrics_to_jams(lyrics))
 
+    # tags
+    if tags_gtzan_data is not None:
+        if not isinstance(tags_gtzan_data, list):
+            raise TypeError('tags_gtzan_data should be a list of tuples')
+        for tag in tags_gtzan_data:
+            if not isinstance(tag, tuple):
+                raise TypeError(
+                    'tags_gtzan_data should be a list of tuples, '
+                    + 'but contains a {} element'.format(type(tag))
+                )
+            jam.annotations.append(tag_gtzan_to_jams(tag))
+
     return jam
 
 
@@ -183,7 +202,7 @@ def beats_to_jams(beats):
     jannot_beat = jams.Annotation(namespace='beat')
     jannot_beat.annotation_metadata = jams.AnnotationMetadata(data_source='mirdata')
     if beats[0] is not None:
-        if type(beats[0]) != utils.BeatData:
+        if not isinstance(beats[0], utils.BeatData):
             raise TypeError('Type should be BeatData.')
         for t, p in zip(beats[0].beat_times, beats[0].beat_positions):
             jannot_beat.append(time=t, duration=0.0, value=p)
@@ -209,7 +228,7 @@ def sections_to_jams(sections):
     jannot_seg = jams.Annotation(namespace='segment_open')
     jannot_seg.annotation_metadata = jams.AnnotationMetadata(data_source='mirdata')
     if sections[0] is not None:
-        if type(sections[0]) != utils.SectionData:
+        if not isinstance(sections[0], utils.SectionData):
             raise TypeError('Type should be SectionData.')
         for inter, seg in zip(sections[0].intervals, sections[0].labels):
             jannot_seg.append(time=inter[0], duration=inter[1] - inter[0], value=seg)
@@ -235,7 +254,7 @@ def chords_to_jams(chords):
     jannot_chord = jams.Annotation(namespace='chord')
     jannot_chord.annotation_metadata = jams.AnnotationMetadata(data_source='mirdata')
     if chords[0] is not None:
-        if type(chords[0]) != utils.ChordData:
+        if not isinstance(chords[0], utils.ChordData):
             raise TypeError('Type should be ChordData.')
         for beg, end, ch in zip(
             chords[0].intervals[:, 0], chords[0].intervals[:, 1], chords[0].labels
@@ -263,7 +282,7 @@ def notes_to_jams(notes):
     jannot_note = jams.Annotation(namespace='note_hz')
     jannot_note.annotation_metadata = jams.AnnotationMetadata(data_source='mirdata')
     if notes[0] is not None:
-        if type(notes[0]) != utils.NoteData:
+        if not isinstance(notes[0], utils.NoteData):
             raise TypeError('Type should be NoteData.')
         for beg, end, n in zip(
             notes[0].intervals[:, 0], notes[0].intervals[:, 1], notes[0].notes
@@ -291,7 +310,7 @@ def keys_to_jams(keys):
     jannot_key = jams.Annotation(namespace='key_mode')
     jannot_key.annotation_metadata = jams.AnnotationMetadata(data_source='mirdata')
     if keys[0] is not None:
-        if type(keys[0]) != utils.KeyData:
+        if not isinstance(keys[0], utils.KeyData):
             raise TypeError('Type should be KeyData.')
         for beg, end, key in zip(keys[0].start_times, keys[0].end_times, keys[0].keys):
             jannot_key.append(time=beg, duration=end - beg, value=key)
@@ -323,7 +342,7 @@ def multi_sections_to_jams(multi_sections):
     )
     for sections in multi_sections[0]:
         if sections[0] is not None:
-            if type(sections[0]) != utils.SectionData:
+            if not isinstance(sections[0], utils.SectionData):
                 raise TypeError('Type should be SectionData.')
             for inter, seg in zip(sections[0].intervals, sections[0].labels):
                 jannot_multi.append(
@@ -351,10 +370,15 @@ def f0s_to_jams(f0s):
     jannot_f0 = jams.Annotation(namespace='pitch_contour')
     jannot_f0.annotation_metadata = jams.AnnotationMetadata(data_source='mirdata')
     if f0s[0] is not None:
-        if type(f0s[0]) != utils.F0Data:
+        if not isinstance(f0s[0], utils.F0Data):
             raise TypeError('Type should be F0Data.')
         for t, f, c in zip(f0s[0].times, f0s[0].frequencies, f0s[0].confidence):
-            jannot_f0.append(time=t, duration=0.0, value=f, confidence=c)
+            jannot_f0.append(
+                time=t,
+                duration=0.0,
+                value={'index': 0, 'frequency': f, 'voiced': f > 0},
+                confidence=c,
+            )
     if f0s[1] is not None:
         jannot_f0.sandbox = jams.Sandbox(name=f0s[1])
     return jannot_f0
@@ -377,7 +401,7 @@ def lyrics_to_jams(lyrics):
     jannot_lyric = jams.Annotation(namespace='lyrics')
     jannot_lyric.annotation_metadata = jams.AnnotationMetadata(data_source='mirdata')
     if lyrics[0] is not None:
-        if type(lyrics[0]) != utils.LyricData:
+        if not isinstance(lyrics[0], utils.LyricData):
             raise TypeError('Type should be LyricData.')
         for beg, end, lyric in zip(
             lyrics[0].start_times, lyrics[0].end_times, lyrics[0].lyrics
@@ -386,3 +410,30 @@ def lyrics_to_jams(lyrics):
     if lyrics[1] is not None:
         jannot_lyric.sandbox = jams.Sandbox(name=lyrics[1])
     return jannot_lyric
+
+
+def tag_gtzan_to_jams(tags):
+    '''
+    Convert tag-gtzan annotations into jams format.
+
+    Parameters
+    ----------
+    tags: tuple
+        A tuple in the format (str, str), where the first str is the tag
+        and the second describes the annotation.
+
+    Returns
+    -------
+    jannot_tag_gtzan: JAM tag_gtzan annotation object.
+    '''
+    jannot_tag_gtzan = jams.Annotation(namespace='tag_gtzan')
+    jannot_tag_gtzan.annotation_metadata = jams.AnnotationMetadata(
+        data_source='mirdata'
+    )
+    if tags[0] is not None:
+        if not isinstance(tags[0], str):
+            raise TypeError('Type should be str.')
+        jannot_tag_gtzan.append(time=0.0, duration=0.0, value=tags[0])
+    if tags[1] is not None:
+        jannot_tag_gtzan.sandbox = jams.Sandbox(name=tags[1])
+    return jannot_tag_gtzan
diff --git a/mirdata/medley_solos_db.py b/mirdata/medley_solos_db.py
index afb7be3..52cc956 100644
--- a/mirdata/medley_solos_db.py
+++ b/mirdata/medley_solos_db.py
@@ -135,7 +135,9 @@ class Track(track.Track):
 
     def to_jams(self):
         """Jams: the track's data in jams format"""
-        return jams_utils.jams_converter(metadata=self._track_metadata)
+        metadata = {'duration': librosa.get_duration(self.audio[0], self.audio[1])}
+        metadata.update(self._track_metadata)
+        return jams_utils.jams_converter(metadata=metadata)
 
 
 def load_audio(audio_path):
diff --git a/mirdata/medleydb_melody.py b/mirdata/medleydb_melody.py
index 14ac888..73b22e7 100644
--- a/mirdata/medleydb_melody.py
+++ b/mirdata/medleydb_melody.py
@@ -133,9 +133,11 @@ class Track(track.Track):
     def to_jams(self):
         """Jams: the track's data in jams format"""
         # jams does not support multipitch, so we skip melody3
+        metadata = {k: v for k, v in self._track_metadata.items() if v is not None}
+        metadata['duration'] = librosa.get_duration(self.audio[0], self.audio[1])
         return jams_utils.jams_converter(
             f0_data=[(self.melody1, 'melody1'), (self.melody2, 'melody2')],
-            metadata=self._track_metadata,
+            metadata=metadata,
         )
 
 
diff --git a/mirdata/medleydb_pitch.py b/mirdata/medleydb_pitch.py
index 1761859..6fc3c5a 100644
--- a/mirdata/medleydb_pitch.py
+++ b/mirdata/medleydb_pitch.py
@@ -106,8 +106,10 @@ class Track(track.Track):
 
     def to_jams(self):
         """Jams: the track's data in jams format"""
+        metadata = {k: v for k, v in self._track_metadata.items() if v is not None}
+        metadata['duration'] = librosa.get_duration(self.audio[0], self.audio[1])
         return jams_utils.jams_converter(
-            f0_data=[(self.pitch, None)], metadata=self._track_metadata
+            f0_data=[(self.pitch, 'annotated pitch')], metadata=metadata
         )
 
 
diff --git a/mirdata/orchset.py b/mirdata/orchset.py
index 96a4323..5a78453 100644
--- a/mirdata/orchset.py
+++ b/mirdata/orchset.py
@@ -193,8 +193,12 @@ class Track(track.Track):
 
     def to_jams(self):
         """Jams: the track's data in jams format"""
+        metadata = {k: v for k, v in self._track_metadata.items() if v is not None}
+        metadata['duration'] = librosa.get_duration(
+            self.audio_mono[0], self.audio_mono[1]
+        )
         return jams_utils.jams_converter(
-            f0_data=[(self.melody, None)], metadata=self._track_metadata
+            f0_data=[(self.melody, 'annotated melody')], metadata=metadata
         )
 
 
diff --git a/mirdata/rwc_classical.py b/mirdata/rwc_classical.py
index 4a4c8db..1aea7c1 100644
--- a/mirdata/rwc_classical.py
+++ b/mirdata/rwc_classical.py
@@ -175,10 +175,12 @@ class Track(track.Track):
 
     def to_jams(self):
         """Jams: the track's data in jams format"""
+        metadata = {k: v for k, v in self._track_metadata.items() if v is not None}
+        metadata['duration'] = librosa.get_duration(self.audio[0], self.audio[1])
         return jams_utils.jams_converter(
             beat_data=[(self.beats, None)],
             section_data=[(self.sections, None)],
-            metadata=self._track_metadata,
+            metadata=metadata,
         )
 
 
diff --git a/mirdata/rwc_jazz.py b/mirdata/rwc_jazz.py
index 359b24e..a862979 100644
--- a/mirdata/rwc_jazz.py
+++ b/mirdata/rwc_jazz.py
@@ -32,6 +32,7 @@ eighth-note feel, music with a sixteenth-note feel, and Latin jazz music.
 For more details, please visit: https://staff.aist.go.jp/m.goto/RWC-MDB/rwc-mdb-j.html
 """
 import csv
+import librosa
 import logging
 import os
 
@@ -198,10 +199,12 @@ class Track(track.Track):
 
     def to_jams(self):
         """Jams: the track's data in jams format"""
+        metadata = {k: v for k, v in self._track_metadata.items() if v is not None}
+        metadata['duration'] = librosa.get_duration(self.audio[0], self.audio[1])
         return jams_utils.jams_converter(
             beat_data=[(self.beats, None)],
             section_data=[(self.sections, None)],
-            metadata=self._track_metadata,
+            metadata=metadata,
         )
 
 
diff --git a/mirdata/rwc_popular.py b/mirdata/rwc_popular.py
index eb8bb96..e63d871 100644
--- a/mirdata/rwc_popular.py
+++ b/mirdata/rwc_popular.py
@@ -10,6 +10,7 @@ the 1990s.
 For more details, please visit: https://staff.aist.go.jp/m.goto/RWC-MDB/rwc-mdb-p.html
 """
 import csv
+import librosa
 import logging
 import numpy as np
 import os
@@ -217,11 +218,13 @@ class Track(track.Track):
 
     def to_jams(self):
         """Jams: the track's data in jams format"""
+        metadata = {k: v for k, v in self._track_metadata.items() if v is not None}
+        metadata['duration'] = librosa.get_duration(self.audio[0], self.audio[1])
         return jams_utils.jams_converter(
             beat_data=[(self.beats, None)],
             section_data=[(self.sections, None)],
             chord_data=[(self.chords, None)],
-            metadata=self._track_metadata,
+            metadata=metadata,
         )
 
 
diff --git a/mirdata/salami.py b/mirdata/salami.py
index 5cdb74f..020abc8 100644
--- a/mirdata/salami.py
+++ b/mirdata/salami.py
@@ -194,6 +194,8 @@ class Track(track.Track):
 
     def to_jams(self):
         """Jams: the track's data in jams format"""
+        metadata = {k: v for k, v in self._track_metadata.items() if v is not None}
+        metadata['duration'] = librosa.get_duration(self.audio[0], self.audio[1])
         return jams_utils.jams_converter(
             multi_section_data=[
                 (
@@ -211,7 +213,7 @@ class Track(track.Track):
                     'annotator_2',
                 ),
             ],
-            metadata=self._track_metadata,
+            metadata=metadata,
         )
 
 
diff --git a/mirdata/tinysol.py b/mirdata/tinysol.py
index 95616f3..1842902 100644
--- a/mirdata/tinysol.py
+++ b/mirdata/tinysol.py
@@ -190,7 +190,9 @@ class Track(track.Track):
 
     def to_jams(self):
         """Jams: the track's data in jams format"""
-        return jams_utils.jams_converter(metadata=self._track_metadata)  # TODO PR #185
+        metadata = {k: v for k, v in self._track_metadata.items() if v is not None}
+        metadata['duration'] = librosa.get_duration(self.audio[0], self.audio[1])
+        return jams_utils.jams_converter(metadata=metadata)  # TODO PR #185
 
 
 def load_audio(audio_path):


Test patch:
diff --git a/tests/test_beatles.py b/tests/test_beatles.py
index f4d50de..778218c 100644
--- a/tests/test_beatles.py
+++ b/tests/test_beatles.py
@@ -13,15 +13,15 @@ def test_track():
 
     expected_attributes = {
         'audio_path': 'tests/resources/mir_datasets/Beatles/'
-            + 'audio/01_-_Please_Please_Me/11_-_Do_You_Want_To_Know_A_Secret.wav',
+        + 'audio/01_-_Please_Please_Me/11_-_Do_You_Want_To_Know_A_Secret.wav',
         'beats_path': 'tests/resources/mir_datasets/Beatles/'
-            + 'annotations/beat/The Beatles/01_-_Please_Please_Me/11_-_Do_You_Want_To_Know_A_Secret.txt',
+        + 'annotations/beat/The Beatles/01_-_Please_Please_Me/11_-_Do_You_Want_To_Know_A_Secret.txt',
         'chords_path': 'tests/resources/mir_datasets/Beatles/'
-            + 'annotations/chordlab/The Beatles/01_-_Please_Please_Me/11_-_Do_You_Want_To_Know_A_Secret.lab',
+        + 'annotations/chordlab/The Beatles/01_-_Please_Please_Me/11_-_Do_You_Want_To_Know_A_Secret.lab',
         'keys_path': 'tests/resources/mir_datasets/Beatles/'
-            + 'annotations/keylab/The Beatles/01_-_Please_Please_Me/11_-_Do_You_Want_To_Know_A_Secret.lab',
+        + 'annotations/keylab/The Beatles/01_-_Please_Please_Me/11_-_Do_You_Want_To_Know_A_Secret.lab',
         'sections_path': 'tests/resources/mir_datasets/Beatles/'
-            + 'annotations/seglab/The Beatles/01_-_Please_Please_Me/11_-_Do_You_Want_To_Know_A_Secret.lab',
+        + 'annotations/seglab/The Beatles/01_-_Please_Please_Me/11_-_Do_You_Want_To_Know_A_Secret.lab',
         'title': '11_-_Do_You_Want_To_Know_A_Secret',
         'track_id': '0111',
     }
@@ -30,7 +30,7 @@ def test_track():
         'beats': utils.BeatData,
         'chords': utils.ChordData,
         'key': utils.KeyData,
-        'sections': utils.SectionData
+        'sections': utils.SectionData,
     }
 
     run_track_tests(track, expected_attributes, expected_property_types)
diff --git a/tests/test_dali.py b/tests/test_dali.py
index ab7c2eb..506ba30 100644
--- a/tests/test_dali.py
+++ b/tests/test_dali.py
@@ -15,10 +15,10 @@ def test_track():
     expected_attributes = {
         'album': 'Mezmerize',
         'annotation_path': 'tests/resources/mir_datasets/DALI/'
-            + 'annotations/4b196e6c99574dd49ad00d56e132712b.gz',
+        + 'annotations/4b196e6c99574dd49ad00d56e132712b.gz',
         'artist': 'System Of A Down',
         'audio_path': 'tests/resources/mir_datasets/DALI/'
-            + 'audio/4b196e6c99574dd49ad00d56e132712b.mp3',
+        + 'audio/4b196e6c99574dd49ad00d56e132712b.mp3',
         'audio_url': 'zUzd9KyIDrM',
         'dataset_version': 1,
         'ground_truth': False,
@@ -36,7 +36,7 @@ def test_track():
         'words': utils.LyricData,
         'lines': utils.LyricData,
         'paragraphs': utils.LyricData,
-        'annotation_object': DALI.Annotations
+        'annotation_object': DALI.Annotations,
     }
 
     run_track_tests(track, expected_attributes, expected_property_types)
diff --git a/tests/test_gtzan_genre.py b/tests/test_gtzan_genre.py
index a448963..be9eeea 100644
--- a/tests/test_gtzan_genre.py
+++ b/tests/test_gtzan_genre.py
@@ -23,7 +23,7 @@ def test_track():
     expected_attributes = {
         'genre': "country",
         'audio_path': "tests/resources/mir_datasets/GTZAN-Genre/"
-            + "gtzan_genre/genres/country/country.00000.wav",
+        + "gtzan_genre/genres/country/country.00000.wav",
         'track_id': "country.00000",
     }
     run_track_tests(track, expected_attributes, {})
@@ -31,3 +31,15 @@ def test_track():
     audio, sr = track.audio
     assert sr == 22050
     assert audio.shape == (663300,)
+
+
+def test_to_jams():
+    default_trackid = "country.00000"
+    track = gtzan_genre.Track(default_trackid, data_home=TEST_DATA_HOME)
+    jam = track.to_jams()
+
+    # Validate GTZAN schema
+    assert jam.validate()
+
+    # Test the that the genre parser of mirdata is correct
+    assert jam.annotations["tag_gtzan"][0].data[0].value == "country"
diff --git a/tests/test_guitarset.py b/tests/test_guitarset.py
index 2969d9d..cd6b83d 100644
--- a/tests/test_guitarset.py
+++ b/tests/test_guitarset.py
@@ -18,15 +18,15 @@ def test_track():
     expected_attributes = {
         'track_id': '03_BN3-119-G_solo',
         'audio_hex_cln_path': 'tests/resources/mir_datasets/GuitarSet/'
-            + 'audio_hex-pickup_debleeded/03_BN3-119-G_solo_hex_cln.wav',
+        + 'audio_hex-pickup_debleeded/03_BN3-119-G_solo_hex_cln.wav',
         'audio_hex_path': 'tests/resources/mir_datasets/GuitarSet/'
-            + 'audio_hex-pickup_original/03_BN3-119-G_solo_hex.wav',
+        + 'audio_hex-pickup_original/03_BN3-119-G_solo_hex.wav',
         'audio_mic_path': 'tests/resources/mir_datasets/GuitarSet/'
-            + 'audio_mono-mic/03_BN3-119-G_solo_mic.wav',
+        + 'audio_mono-mic/03_BN3-119-G_solo_mic.wav',
         'audio_mix_path': 'tests/resources/mir_datasets/GuitarSet/'
-            + 'audio_mono-pickup_mix/03_BN3-119-G_solo_mix.wav',
+        + 'audio_mono-pickup_mix/03_BN3-119-G_solo_mix.wav',
         'jams_path': 'tests/resources/mir_datasets/GuitarSet/'
-            + 'annotation/03_BN3-119-G_solo.jams',
+        + 'annotation/03_BN3-119-G_solo.jams',
         'player_id': '03',
         'tempo': 119,
         'mode': 'solo',
diff --git a/tests/test_ikala.py b/tests/test_ikala.py
index bb76360..71e5c37 100644
--- a/tests/test_ikala.py
+++ b/tests/test_ikala.py
@@ -14,7 +14,7 @@ def test_track():
     expected_attributes = {
         'track_id': '10161_chorus',
         'audio_path': 'tests/resources/mir_datasets/iKala/'
-            + 'Wavfile/10161_chorus.wav',
+        + 'Wavfile/10161_chorus.wav',
         'song_id': '10161',
         'section': 'chorus',
         'singer_id': '1',
@@ -22,10 +22,7 @@ def test_track():
         'lyrics_path': 'tests/resources/mir_datasets/iKala/Lyrics/10161_chorus.lab',
     }
 
-    expected_property_types = {
-        'f0': utils.F0Data,
-        'lyrics': utils.LyricData,
-    }
+    expected_property_types = {'f0': utils.F0Data, 'lyrics': utils.LyricData}
 
     assert track._track_paths == {
         'audio': ['Wavfile/10161_chorus.wav', '278ae003cb0d323e99b9a643c0f2eeda'],
@@ -69,7 +66,10 @@ def test_to_jams():
     f0s = jam.search(namespace='pitch_contour')[0]['data']
     assert [f0.time for f0 in f0s] == [0.016, 0.048]
     assert [f0.duration for f0 in f0s] == [0.0, 0.0]
-    assert [f0.value for f0 in f0s] == [0.0, 260.946404518887]
+    assert [f0.value for f0 in f0s] == [
+        {'frequency': 0.0, 'index': 0, 'voiced': False},
+        {'frequency': 260.946404518887, 'index': 0, 'voiced': True},
+    ]
     assert [f0.confidence for f0 in f0s] == [0.0, 1.0]
 
 
diff --git a/tests/test_jams_utils.py b/tests/test_jams_utils.py
index 08f9832..21c4a57 100644
--- a/tests/test_jams_utils.py
+++ b/tests/test_jams_utils.py
@@ -21,7 +21,6 @@ def get_jam_data(jam, namespace, annot_numb):
 
 
 def test_beats():
-
     beat_data_1 = [(utils.BeatData(np.array([0.2, 0.3]), np.array([1, 2])), None)]
     beat_data_2 = [(utils.BeatData(np.array([0.5, 0.7]), np.array([2, 3])), 'beats_2')]
     beat_data_3 = [
@@ -202,7 +201,7 @@ def test_notes():
             utils.NoteData(
                 np.array([[0.0, 0.5, 1.0], [0.5, 1.0, 1.5]]).T,
                 np.array([1108.731, 1108.731, 1108.731]),
-                np.array([1, 1, 1])
+                np.array([1, 1, 1]),
             ),
             None,
         )
@@ -212,7 +211,7 @@ def test_notes():
             utils.NoteData(
                 np.array([[0.0, 0.8, 1.0], [0.5, 1.0, 1.5]]).T,
                 np.array([1108.731, 1108.731, 1108.731]),
-                np.array([1, 1, 1])
+                np.array([1, 1, 1]),
             ),
             'notes_2',
         )
@@ -222,7 +221,7 @@ def test_notes():
             utils.NoteData(
                 np.array([[0.0, 0.5, 1.0], [0.5, 1.0, 1.5]]).T,
                 np.array([1108.731, 1108.731, 1108.731]),
-                np.array([1, 1, 1])
+                np.array([1, 1, 1]),
             ),
             'notes_1',
         ),
@@ -230,16 +229,16 @@ def test_notes():
             utils.NoteData(
                 np.array([[0.0, 0.7, 1.0], [0.7, 1.0, 1.5]]).T,
                 np.array([1108.731, 1108.731, 1108.731]),
-                np.array([1, 1, 1])
+                np.array([1, 1, 1]),
             ),
             'notes_2',
         ),
     ]
     note_data_4 = (
         utils.NoteData(
-            np.array([[0.0, 0.5, 1.0], [0.5, 1.0, 1.5]]).T, 
+            np.array([[0.0, 0.5, 1.0], [0.5, 1.0, 1.5]]).T,
             np.array([1108.731, 1108.731, 1108.731]),
-            np.array([1, 1, 1])
+            np.array([1, 1, 1]),
         ),
         None,
     )
@@ -248,7 +247,7 @@ def test_notes():
             utils.NoteData(
                 np.array([[0.0, 0.5, 1.0], [0.5, 1.0, 1.5]]).T,
                 np.array([1108.731, 1108.731, 1108.731]),
-                np.array([1, 1, 1])
+                np.array([1, 1, 1]),
             ),
             None,
         ],
@@ -256,7 +255,7 @@ def test_notes():
             utils.NoteData(
                 np.array([[0.0, 0.8, 1.0], [0.5, 1.0, 1.5]]).T,
                 np.array([1108.731, 1108.731, 1108.731]),
-                np.array([1, 1, 1])
+                np.array([1, 1, 1]),
             ),
             'notes_2',
         ),
@@ -805,7 +804,10 @@ def test_f0s():
     time, duration, value, confidence = get_jam_data(jam_1, 'pitch_contour', 0)
     assert time == [0.016, 0.048]
     assert duration == [0.0, 0.0]
-    assert value == [0.0, 260.9]
+    assert value == [
+        {'frequency': 0.0, 'index': 0, 'voiced': False},
+        {'frequency': 260.9, 'index': 0, 'voiced': True},
+    ]
     assert confidence == [0.0, 1.0]
 
     assert jam_2.annotations[0]['sandbox']['name'] == 'f0s_1'
@@ -813,13 +815,19 @@ def test_f0s():
     time, duration, value, confidence = get_jam_data(jam_3, 'pitch_contour', 0)
     assert time == [0.016, 0.048]
     assert duration == [0.0, 0.0]
-    assert value == [0.0, 260.9]
+    assert value == [
+        {'frequency': 0.0, 'index': 0, 'voiced': False},
+        {'frequency': 260.9, 'index': 0, 'voiced': True},
+    ]
     assert confidence == [0.0, 1.0]
 
     time, duration, value, confidence = get_jam_data(jam_3, 'pitch_contour', 1)
     assert time == [0.003, 0.012]
     assert duration == [0.0, 0.0]
-    assert value == [0.0, 230.5]
+    assert value == [
+        {'frequency': 0.0, 'index': 0, 'voiced': False},
+        {'frequency': 230.5, 'index': 0, 'voiced': True},
+    ]
     assert confidence == [0.0, 1.0]
 
     time, duration, value, confidence = get_jam_data(jam_6, 'pitch_contour', 0)
@@ -963,6 +971,25 @@ def test_lyrics():
         jams_utils.jams_converter(lyrics_data=lyrics_data_7)
 
 
+def test_tags():
+    tag_data1 = [('blues', 'I am a description')]
+    tag_data2 = [('disco', 'tag 1'), ('rock', 'tag 2')]
+    tag_data3 = [('invalid', 'asdf')]
+    tag_data4 = ('jazz', 'wrong format')
+    tag_data5 = ['wrong format too']
+    jam1 = jams_utils.jams_converter(tags_gtzan_data=tag_data1, metadata={'duration': 10.0})
+    assert jam1.validate()
+    jam2 = jams_utils.jams_converter(tags_gtzan_data=tag_data2, metadata={'duration': 10.0})
+    assert jam2.validate()
+    jam3 = jams_utils.jams_converter(tags_gtzan_data=tag_data3, metadata={'duration': 10.0})
+    with pytest.raises(jams.SchemaError):
+        assert jam3.validate()
+    with pytest.raises(TypeError):
+        jam4 = jams_utils.jams_converter(tags_gtzan_data=tag_data4)
+    with pytest.raises(TypeError):
+        jam5 = jams_utils.jams_converter(tags_gtzan_data=tag_data5)
+
+
 def test_metadata():
     metadata_1 = {
         'duration': 1.5,
diff --git a/tests/test_loaders.py b/tests/test_loaders.py
index 4d96819..464222c 100644
--- a/tests/test_loaders.py
+++ b/tests/test_loaders.py
@@ -12,6 +12,17 @@ import mirdata.track as track
 from tests.test_utils import DEFAULT_DATA_HOME
 
 DATASETS = [importlib.import_module("mirdata.{}".format(d)) for d in mirdata.__all__]
+CUSTOM_TEST_TRACKS = {
+    'beatles': '0111',
+    'guitarset': '03_BN3-119-G_solo',
+    'medley_solos_db': 'd07b1fc0-567d-52c2-fef4-239f31c9d40e',
+    'medleydb_melody': 'MusicDelta_Beethoven',
+    'rwc_classical': 'RM-C003',
+    'rwc_jazz': 'RM-J004',
+    'rwc_popular': 'RM-P001',
+    'salami': '2',
+    'tinysol': 'Fl-ord-C4-mf-N-T14d',
+}
 
 
 def test_cite():
@@ -56,21 +67,38 @@ def test_load_and_trackids():
 
 
 def test_track():
+    jams_temporary_exceptions = ['dali']
+    data_home_dir = 'tests/resources/mir_datasets'
+
     for dataset in DATASETS:
-        print(str(dataset))
-        trackid = dataset.track_ids()[0]
+        dataset_name = dataset.__name__.split('.')[1]
+        print(dataset_name)
+
+        if dataset_name in CUSTOM_TEST_TRACKS:
+            trackid = CUSTOM_TEST_TRACKS[dataset_name]
+        else:
+            trackid = dataset.track_ids()[0]
 
-        # test data home None
         track_default = dataset.Track(trackid)
         assert track_default._data_home == os.path.join(
-            DEFAULT_DATA_HOME, dataset.DATASET_DIR)
+            DEFAULT_DATA_HOME, dataset.DATASET_DIR
+        )
+
+        # test data home specified
+        data_home = os.path.join(data_home_dir, dataset.DATASET_DIR)
+        track_test = dataset.Track(trackid, data_home=data_home)
+
+        assert isinstance(track_test, track.Track)
 
-        assert isinstance(track_default, track.Track)
+        assert hasattr(track_test, 'to_jams')
 
-        assert hasattr(track_default, 'to_jams')
+        if dataset_name not in jams_temporary_exceptions:
+            # Validate json schema
+            jam = track_test.to_jams()
+            assert jam.validate()
 
         # will fail if something goes wrong with __repr__
-        print(track_default)
+        print(track_test)
 
         with pytest.raises(ValueError):
             dataset.Track('~faketrackid~?!')
diff --git a/tests/test_medley_solos_db.py b/tests/test_medley_solos_db.py
index 799511f..1f5f100 100644
--- a/tests/test_medley_solos_db.py
+++ b/tests/test_medley_solos_db.py
@@ -12,11 +12,11 @@ def test_track():
     expected_attributes = {
         'track_id': 'd07b1fc0-567d-52c2-fef4-239f31c9d40e',
         'audio_path': 'tests/resources/mir_datasets/Medley-solos-DB/'
-            + 'audio/Medley-solos-DB_validation-3_d07b1fc0-567d-52c2-fef4-239f31c9d40e.wav',
+        + 'audio/Medley-solos-DB_validation-3_d07b1fc0-567d-52c2-fef4-239f31c9d40e.wav',
         'instrument': 'flute',
         'instrument_id': 3,
         'song_id': 210,
-        'subset': 'validation'
+        'subset': 'validation',
     }
 
     expected_property_types = {}
diff --git a/tests/test_medleydb_melody.py b/tests/test_medleydb_melody.py
index f928be2..1ddf87b 100644
--- a/tests/test_medleydb_melody.py
+++ b/tests/test_medleydb_melody.py
@@ -14,13 +14,13 @@ def test_track():
     expected_attributes = {
         'track_id': 'MusicDelta_Beethoven',
         'audio_path': 'tests/resources/mir_datasets/'
-            + 'MedleyDB-Melody/audio/MusicDelta_Beethoven_MIX.wav',
+        + 'MedleyDB-Melody/audio/MusicDelta_Beethoven_MIX.wav',
         'melody1_path': 'tests/resources/mir_datasets/'
-            + 'MedleyDB-Melody/melody1/MusicDelta_Beethoven_MELODY1.csv',
+        + 'MedleyDB-Melody/melody1/MusicDelta_Beethoven_MELODY1.csv',
         'melody2_path': 'tests/resources/mir_datasets/'
-            + 'MedleyDB-Melody/melody2/MusicDelta_Beethoven_MELODY2.csv',
+        + 'MedleyDB-Melody/melody2/MusicDelta_Beethoven_MELODY2.csv',
         'melody3_path': 'tests/resources/mir_datasets/'
-            + 'MedleyDB-Melody/melody3/MusicDelta_Beethoven_MELODY3.csv',
+        + 'MedleyDB-Melody/melody3/MusicDelta_Beethoven_MELODY3.csv',
         'artist': 'MusicDelta',
         'title': 'Beethoven',
         'genre': 'Classical',
@@ -32,7 +32,7 @@ def test_track():
     expected_property_types = {
         'melody1': utils.F0Data,
         'melody2': utils.F0Data,
-        'melody3': utils.MultipitchData
+        'melody3': utils.MultipitchData,
     }
 
     run_track_tests(track, expected_attributes, expected_property_types)
@@ -51,7 +51,10 @@ def test_to_jams():
     f0s = jam.search(namespace='pitch_contour')[1]['data']
     assert [f0.time for f0 in f0s] == [0.046439909297052155, 0.052244897959183675]
     assert [f0.duration for f0 in f0s] == [0.0, 0.0]
-    assert [f0.value for f0 in f0s] == [0.0, 965.992]
+    assert [f0.value for f0 in f0s] == [
+        {'frequency': 0.0, 'index': 0, 'voiced': False},
+        {'frequency': 965.992, 'index': 0, 'voiced': True},
+    ]
     assert [f0.confidence for f0 in f0s] == [0.0, 1.0]
 
     assert jam['file_metadata']['title'] == 'Beethoven'
diff --git a/tests/test_medleydb_pitch.py b/tests/test_medleydb_pitch.py
index 95455bd..f06c82e 100644
--- a/tests/test_medleydb_pitch.py
+++ b/tests/test_medleydb_pitch.py
@@ -14,18 +14,16 @@ def test_track():
     expected_attributes = {
         'track_id': 'AClassicEducation_NightOwl_STEM_08',
         'audio_path': 'tests/resources/mir_datasets/'
-            + 'MedleyDB-Pitch/audio/AClassicEducation_NightOwl_STEM_08.wav',
+        + 'MedleyDB-Pitch/audio/AClassicEducation_NightOwl_STEM_08.wav',
         'pitch_path': 'tests/resources/mir_datasets/'
-            + 'MedleyDB-Pitch/pitch/AClassicEducation_NightOwl_STEM_08.csv',
+        + 'MedleyDB-Pitch/pitch/AClassicEducation_NightOwl_STEM_08.csv',
         'instrument': 'male singer',
         'artist': 'AClassicEducation',
         'title': 'NightOwl',
-        'genre': 'Singer/Songwriter'
+        'genre': 'Singer/Songwriter',
     }
 
-    expected_property_types = {
-        'pitch': utils.F0Data,
-    }
+    expected_property_types = {'pitch': utils.F0Data}
 
     run_track_tests(track, expected_attributes, expected_property_types)
 
@@ -45,7 +43,10 @@ def test_to_jams():
     f0s = jam.search(namespace='pitch_contour')[0]['data']
     assert [f0.time for f0 in f0s] == [0.06965986394557823, 0.07546485260770976]
     assert [f0.duration for f0 in f0s] == [0.0, 0.0]
-    assert [f0.value for f0 in f0s] == [0.0, 191.877]
+    assert [f0.value for f0 in f0s] == [
+        {'frequency': 0.0, 'index': 0, 'voiced': False},
+        {'frequency': 191.877, 'index': 0, 'voiced': True},
+    ]
     assert [f0.confidence for f0 in f0s] == [0.0, 1.0]
 
     assert jam['file_metadata']['title'] == 'NightOwl'
diff --git a/tests/test_orchset.py b/tests/test_orchset.py
index 59bde29..d76c26f 100644
--- a/tests/test_orchset.py
+++ b/tests/test_orchset.py
@@ -14,11 +14,11 @@ def test_track():
     expected_attributes = {
         'track_id': 'Beethoven-S3-I-ex1',
         'audio_path_mono': 'tests/resources/mir_datasets/Orchset/'
-            + 'audio/mono/Beethoven-S3-I-ex1.wav',
+        + 'audio/mono/Beethoven-S3-I-ex1.wav',
         'audio_path_stereo': 'tests/resources/mir_datasets/Orchset/'
-            + 'audio/stereo/Beethoven-S3-I-ex1.wav',
+        + 'audio/stereo/Beethoven-S3-I-ex1.wav',
         'melody_path': 'tests/resources/mir_datasets/Orchset/'
-            + 'GT/Beethoven-S3-I-ex1.mel',
+        + 'GT/Beethoven-S3-I-ex1.mel',
         'composer': 'Beethoven',
         'work': 'S3-I',
         'excerpt': '1',
@@ -32,9 +32,7 @@ def test_track():
         'only_brass': False,
     }
 
-    expected_property_types = {
-        'melody': utils.F0Data
-    }
+    expected_property_types = {'melody': utils.F0Data}
 
     run_track_tests(track, expected_attributes, expected_property_types)
 
@@ -56,7 +54,11 @@ def test_to_jams():
     f0s = jam.search(namespace='pitch_contour')[0]['data']
     assert [f0.time for f0 in f0s] == [0.0, 0.08, 0.09]
     assert [f0.duration for f0 in f0s] == [0.0, 0.0, 0.0]
-    assert [f0.value for f0 in f0s] == [0.0, 0.0, 622.254]
+    assert [f0.value for f0 in f0s] == [
+        {'frequency': 0.0, 'index': 0, 'voiced': False},
+        {'frequency': 0.0, 'index': 0, 'voiced': False},
+        {'frequency': 622.254, 'index': 0, 'voiced': True},
+    ]
     assert [f0.confidence for f0 in f0s] == [0.0, 0.0, 1.0]
 
     assert jam['sandbox']['alternating_melody'] == True
diff --git a/tests/test_rwc_classical.py b/tests/test_rwc_classical.py
index a0a6ab3..73ec00d 100644
--- a/tests/test_rwc_classical.py
+++ b/tests/test_rwc_classical.py
@@ -14,11 +14,11 @@ def test_track():
     expected_attributes = {
         'track_id': 'RM-C003',
         'audio_path': 'tests/resources/mir_datasets/RWC-Classical/'
-            + 'audio/rwc-c-m01/3.wav',
+        + 'audio/rwc-c-m01/3.wav',
         'sections_path': 'tests/resources/mir_datasets/RWC-Classical/'
-            + 'annotations/AIST.RWC-MDB-C-2001.CHORUS/RM-C003.CHORUS.TXT',
+        + 'annotations/AIST.RWC-MDB-C-2001.CHORUS/RM-C003.CHORUS.TXT',
         'beats_path': 'tests/resources/mir_datasets/RWC-Classical/'
-            + 'annotations/AIST.RWC-MDB-C-2001.BEAT/RM-C003.BEAT.TXT',
+        + 'annotations/AIST.RWC-MDB-C-2001.BEAT/RM-C003.BEAT.TXT',
         'piece_number': 'No. 3',
         'suffix': 'M01',
         'track_number': 'Tr. 03',
@@ -29,10 +29,7 @@ def test_track():
         'category': 'Symphony',
     }
 
-    expected_property_types = {
-        'beats': utils.BeatData,
-        'sections': utils.SectionData
-    }
+    expected_property_types = {'beats': utils.BeatData, 'sections': utils.SectionData}
 
     run_track_tests(track, expected_attributes, expected_property_types)
 
diff --git a/tests/test_rwc_jazz.py b/tests/test_rwc_jazz.py
index d5b65c2..d535f9c 100644
--- a/tests/test_rwc_jazz.py
+++ b/tests/test_rwc_jazz.py
@@ -13,11 +13,11 @@ def test_track():
     expected_attributes = {
         'track_id': 'RM-J004',
         'audio_path': 'tests/resources/mir_datasets/RWC-Jazz/'
-            + 'audio/rwc-j-m01/4.wav',
+        + 'audio/rwc-j-m01/4.wav',
         'sections_path': 'tests/resources/mir_datasets/RWC-Jazz/'
-            + 'annotations/AIST.RWC-MDB-J-2001.CHORUS/RM-J004.CHORUS.TXT',
+        + 'annotations/AIST.RWC-MDB-J-2001.CHORUS/RM-J004.CHORUS.TXT',
         'beats_path': 'tests/resources/mir_datasets/RWC-Jazz/'
-            + 'annotations/AIST.RWC-MDB-J-2001.BEAT/RM-J004.BEAT.TXT',
+        + 'annotations/AIST.RWC-MDB-J-2001.BEAT/RM-J004.BEAT.TXT',
         'piece_number': 'No. 4',
         'suffix': 'M01',
         'track_number': 'Tr. 04',
@@ -28,10 +28,7 @@ def test_track():
         'instruments': 'Pf',
     }
 
-    expected_property_types = {
-        'beats': utils.BeatData,
-        'sections': utils.SectionData
-    }
+    expected_property_types = {'beats': utils.BeatData, 'sections': utils.SectionData}
 
     run_track_tests(track, expected_attributes, expected_property_types)
 
diff --git a/tests/test_rwc_popular.py b/tests/test_rwc_popular.py
index 923a327..f1bd0d2 100644
--- a/tests/test_rwc_popular.py
+++ b/tests/test_rwc_popular.py
@@ -15,15 +15,15 @@ def test_track():
     expected_attributes = {
         'track_id': 'RM-P001',
         'audio_path': 'tests/resources/mir_datasets/RWC-Popular/'
-            + 'audio/rwc-p-m01/1.wav',
+        + 'audio/rwc-p-m01/1.wav',
         'sections_path': 'tests/resources/mir_datasets/RWC-Popular/'
-            + 'annotations/AIST.RWC-MDB-P-2001.CHORUS/RM-P001.CHORUS.TXT',
+        + 'annotations/AIST.RWC-MDB-P-2001.CHORUS/RM-P001.CHORUS.TXT',
         'beats_path': 'tests/resources/mir_datasets/RWC-Popular/'
-            + 'annotations/AIST.RWC-MDB-P-2001.BEAT/RM-P001.BEAT.TXT',
+        + 'annotations/AIST.RWC-MDB-P-2001.BEAT/RM-P001.BEAT.TXT',
         'chords_path': 'tests/resources/mir_datasets/RWC-Popular/'
-            + 'annotations/AIST.RWC-MDB-P-2001.CHORD/RWC_Pop_Chords/N001-M01-T01.lab',
+        + 'annotations/AIST.RWC-MDB-P-2001.CHORD/RWC_Pop_Chords/N001-M01-T01.lab',
         'voca_inst_path': 'tests/resources/mir_datasets/RWC-Popular/'
-            + 'annotations/AIST.RWC-MDB-P-2001.VOCA_INST/RM-P001.VOCA_INST.TXT',
+        + 'annotations/AIST.RWC-MDB-P-2001.VOCA_INST/RM-P001.VOCA_INST.TXT',
         'piece_number': 'No. 1',
         'suffix': 'M01',
         'track_number': 'Tr. 01',
diff --git a/tests/test_salami.py b/tests/test_salami.py
index 093e3f4..a867ea4 100644
--- a/tests/test_salami.py
+++ b/tests/test_salami.py
@@ -13,16 +13,15 @@ def test_track():
 
     expected_attributes = {
         'track_id': '2',
-        'audio_path': 'tests/resources/mir_datasets/Salami/'
-            + 'audio/2.mp3',
+        'audio_path': 'tests/resources/mir_datasets/Salami/' + 'audio/2.mp3',
         'sections_annotator1_uppercase_path': 'tests/resources/mir_datasets/Salami/'
-            + 'salami-data-public-hierarchy-corrections/annotations/2/parsed/textfile1_uppercase.txt',
+        + 'salami-data-public-hierarchy-corrections/annotations/2/parsed/textfile1_uppercase.txt',
         'sections_annotator1_lowercase_path': 'tests/resources/mir_datasets/Salami/'
-            + 'salami-data-public-hierarchy-corrections/annotations/2/parsed/textfile1_lowercase.txt',
+        + 'salami-data-public-hierarchy-corrections/annotations/2/parsed/textfile1_lowercase.txt',
         'sections_annotator2_uppercase_path': 'tests/resources/mir_datasets/Salami/'
-            + 'salami-data-public-hierarchy-corrections/annotations/2/parsed/textfile2_uppercase.txt',
+        + 'salami-data-public-hierarchy-corrections/annotations/2/parsed/textfile2_uppercase.txt',
         'sections_annotator2_lowercase_path': 'tests/resources/mir_datasets/Salami/'
-            + 'salami-data-public-hierarchy-corrections/annotations/2/parsed/textfile2_lowercase.txt',
+        + 'salami-data-public-hierarchy-corrections/annotations/2/parsed/textfile2_lowercase.txt',
         'source': 'Codaich',
         'annotator_1_id': '5',
         'annotator_2_id': '8',
diff --git a/tests/test_tinysol.py b/tests/test_tinysol.py
index 224c914..da6fb40 100644
--- a/tests/test_tinysol.py
+++ b/tests/test_tinysol.py
@@ -14,7 +14,7 @@ def test_track():
     expected_attributes = {
         'track_id': 'Fl-ord-C4-mf-N-T14d',
         'audio_path': 'tests/resources/mir_datasets/TinySOL/'
-            + 'audio/Winds/Flute/ordinario/Fl-ord-C4-mf-N-T14d.wav',
+        + 'audio/Winds/Flute/ordinario/Fl-ord-C4-mf-N-T14d.wav',
         'dynamics': 'mf',
         'fold': 0,
         'family': 'Winds',
diff --git a/tests/test_track.py b/tests/test_track.py
index 7e7996f..b15104e 100644
--- a/tests/test_track.py
+++ b/tests/test_track.py
@@ -12,7 +12,6 @@ else:
 
 
 def test_track_repr():
-
     class TestTrack(track.Track):
         def __init__(self):
             self.a = 'asdf'
diff --git a/tests/test_utils.py b/tests/test_utils.py
index dd282a5..d990c1b 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -52,9 +52,9 @@ def get_attributes_and_properties(class_instance):
         else:
             raise ValueError("Unknown type {}".format(attr))
 
-    non_attributes = list(itertools.chain.from_iterable(
-        [properties, cached_properties, functions]
-    ))
+    non_attributes = list(
+        itertools.chain.from_iterable([properties, cached_properties, functions])
+    )
     for val in dir(class_instance):
         if val.startswith('_'):
             continue