def test_empty_archive(self): with tempfile.NamedTemporaryFile(suffix='.mxl') as temp_file: z = zipfile.ZipFile(temp_file.name, 'w') z.close() with self.assertRaises(musicxml_reader.MusicXMLConversionError): musicxml_reader.musicxml_file_to_sequence_proto(temp_file.name)
def test_unpitched_notes(self): with self.assertRaises(musicxml_parser.UnpitchedNoteError): musicxml_parser.MusicXMLDocument(os.path.join( testing_lib.get_testdata_dir(), 'unpitched.xml')) with self.assertRaises(musicxml_reader.MusicXMLConversionError): musicxml_reader.musicxml_file_to_sequence_proto(os.path.join( testing_lib.get_testdata_dir(), 'unpitched.xml'))
def test_empty_archive(self): with tempfile.NamedTemporaryFile(suffix='.mxl') as temp_file: z = zipfile.ZipFile(temp_file.name, 'w') z.close() with self.assertRaises(musicxml_reader.MusicXMLConversionError): musicxml_reader.musicxml_file_to_sequence_proto( temp_file.name)
def test_unpitched_notes(self): with self.assertRaises(musicxml_parser.UnpitchedNoteException): musicxml_parser.MusicXMLDocument(os.path.join( tf.resource_loader.get_data_files_path(), 'testdata/unpitched.xml')) with self.assertRaises(musicxml_reader.MusicXMLConversionError): musicxml_reader.musicxml_file_to_sequence_proto(os.path.join( tf.resource_loader.get_data_files_path(), 'testdata/unpitched.xml'))
def testcompressedmxlunicodefilename(self): """Test an MXL file containing a unicode filename within its zip archive.""" unicode_filename = os.path.join( testing_lib.get_testdata_dir(), 'unicode_filename.mxl') sequence = musicxml_reader.musicxml_file_to_sequence_proto(unicode_filename) self.assertLen(sequence.notes, 8)
def test_empty_doc(self): """Verify that an empty doc can be parsed.""" xml = r"""<?xml version="1.0" encoding="UTF-8" standalone="no"?> <!DOCTYPE score-partwise PUBLIC "-//Recordare//DTD MusicXML 3.0 Partwise//EN" "http://www.musicxml.org/dtds/partwise.dtd"> <score-partwise version="3.0"> </score-partwise> """ with tempfile.NamedTemporaryFile() as temp_file: temp_file.write(xml) temp_file.flush() ns = musicxml_reader.musicxml_file_to_sequence_proto( temp_file.name) expected_ns = common_testing_lib.parse_test_proto( music_pb2.NoteSequence, """ ticks_per_quarter: 220 source_info: { source_type: SCORE_BASED encoding_type: MUSIC_XML parser: MAGENTA_MUSIC_XML } key_signatures { key: C time: 0 } tempos { qpm: 120.0 } total_time: 0.0 """) self.assertProtoEquals(expected_ns, ns)
def _xml_to_seq_proto(self, full_xml_path, seq_collection_name): """Converts an individual .xml file to a NoteSequence proto. Args: full_xml_path: the full path to the file to convert. collection: name of collection to which to save. Returns: NoteSequence proto or None if the file could not be converted. """ if (full_xml_path.lower().endswith('.xml') or full_xml_path.lower().endswith('.mxl')): try: sequence = musicxml_reader.musicxml_file_to_sequence_proto( full_xml_path) except musicxml_reader.MusicXMLConversionError as e: print( 'INFO: Could not parse MusicXML file {}. It will be skipped. \ Error was: {}'.format(full_xml_path, e)) return None sequence.collection_name = seq_collection_name sequence.filename = os.path.basename(full_xml_path) sequence.id = os.path.basename(full_xml_path) return sequence else: print('INFO: Unable to find a converter for file {}'.format( full_xml_path))
def convert_musicxml(root_dir, sub_dir, full_file_path): """Converts a musicxml file to a sequence proto. Args: root_dir: A string specifying the root directory for the files being converted. sub_dir: The directory being converted currently. full_file_path: the full path to the file to convert. Returns: Either a NoteSequence proto or None if the file could not be converted. """ try: sequence = musicxml_reader.musicxml_file_to_sequence_proto(full_file_path) except musicxml_reader.MusicXMLConversionError as e: tf.logging.warning( 'Could not parse MusicXML file %s. It will be skipped. Error was: %s', full_file_path, e) return None sequence.collection_name = os.path.basename(root_dir) sequence.filename = os.path.join(sub_dir, os.path.basename(full_file_path)) sequence.id = note_sequence_io.generate_note_sequence_id( sequence.filename, sequence.collection_name, 'musicxml') tf.logging.info('Converted MusicXML file %s.', full_file_path) return sequence
def test_chord_symbols(self): ns = musicxml_reader.musicxml_file_to_sequence_proto( self.chord_symbols_filename) chord_symbols = [(annotation.time, annotation.text) for annotation in ns.text_annotations if annotation.annotation_type == CHORD_SYMBOL] chord_symbols = list(sorted(chord_symbols, key=operator.itemgetter(0))) expected_beats_and_chords = [ (0.0, 'N.C.'), (4.0, 'Cmaj7'), (12.0, 'F6(add9)'), (16.0, 'F#dim7/A'), (20.0, 'Bm7b5'), (24.0, 'E7(#9)'), (28.0, 'A7(add9)(no3)'), (32.0, 'Bbsus2'), (36.0, 'Am(maj7)'), (38.0, 'D13'), (40.0, 'E5'), (44.0, 'Caug') ] # Adjust for 120 QPM. expected_times_and_chords = [(beat / 2.0, chord) for beat, chord in expected_beats_and_chords] self.assertEqual(expected_times_and_chords, chord_symbols)
def convert_musicxml(root_dir, sub_dir, full_file_path): """Converts a musicxml file to a sequence proto. Args: root_dir: A string specifying the root directory for the files being converted. sub_dir: The directory being converted currently. full_file_path: the full path to the file to convert. Returns: Either a NoteSequence proto or None if the file could not be converted. """ try: sequence = musicxml_reader.musicxml_file_to_sequence_proto( full_file_path) except musicxml_reader.MusicXMLConversionError as e: tf.logging.warning( 'Could not parse MusicXML file %s. It will be skipped. Error was: %s', full_file_path, e) return None sequence.collection_name = os.path.basename(root_dir) sequence.filename = os.path.join(sub_dir, os.path.basename(full_file_path)) sequence.id = note_sequence_io.generate_note_sequence_id( sequence.filename, sequence.collection_name, 'musicxml') tf.logging.info('Converted MusicXML file %s.', full_file_path) return sequence
def testcompressedmxlunicodefilename(self): """Test an MXL file containing a unicode filename within its zip archive.""" unicode_filename = os.path.join( tf.resource_loader.get_data_files_path(), 'testdata/unicode_filename.mxl') sequence = musicxml_reader.musicxml_file_to_sequence_proto(unicode_filename) self.assertEqual(len(sequence.notes), 8)
def convert_directory(root_dir, sub_dir, sequence_writer, recursive=False): """Converts MusicXMLs to NoteSequences and writes to `sequence_writer`. MusicXML files found in the specified directory specified by the combination of `root_dir` and `sub_dir` and converted to NoteSequence protos with the basename of `root_dir` as the collection_name, and the relative path to the MusicXML file from `root_dir` as the filename. If `recursive` is true, recursively converts any subdirectories of the specified directory. Args: root_dir: A string specifying a root directory. sub_dir: A string specifying a path to a directory under `root_dir` in which to convert MusicXML contents. sequence_writer: A NoteSequenceRecordWriter to write the resulting NoteSequence protos to. recursive: A boolean specifying whether or not recursively convert MusicXMLs contained in subdirectories of the specified directory. Returns: The number of NoteSequence protos written as an integer. """ dir_to_convert = os.path.join(root_dir, sub_dir) tf.logging.info("Converting MusicXML files in '%s'.", dir_to_convert) files_in_dir = tf.gfile.ListDirectory(os.path.join(dir_to_convert)) recurse_sub_dirs = [] sequences_written = 0 sequences_skipped = 0 for file_in_dir in files_in_dir: full_file_path = os.path.join(dir_to_convert, file_in_dir) if tf.gfile.IsDirectory(full_file_path): if recursive: recurse_sub_dirs.append(os.path.join(sub_dir, file_in_dir)) continue try: sequence = musicxml_reader.musicxml_file_to_sequence_proto( full_file_path) except musicxml_reader.MusicXMLConversionError as e: tf.logging.warning( 'Could not parse MusicXML file %s. It will be skipped. Error was: %s', full_file_path, e) sequences_skipped += 1 continue sequence.collection_name = os.path.basename(root_dir) sequence.filename = os.path.join(sub_dir, file_in_dir) sequence.id = note_sequence_io.generate_note_sequence_id( sequence.filename, sequence.collection_name, 'musicxml') sequence_writer.write(sequence) sequences_written += 1 tf.logging.info("Converted %d MusicXML files in '%s'.", sequences_written, dir_to_convert) tf.logging.info('Could not parse %d MusicXML files.', sequences_skipped) for recurse_sub_dir in recurse_sub_dirs: sequences_written += convert_directory(root_dir, recurse_sub_dir, sequence_writer, recursive) return sequences_written
def test_transposed_keysig(self): xml = br"""<?xml version="1.0" encoding="UTF-8" standalone="no"?> <!DOCTYPE score-partwise PUBLIC "-//Recordare//DTD MusicXML 3.0 Partwise//EN" "http://www.musicxml.org/dtds/partwise.dtd"> <score-partwise version="3.0"> <part-list> <score-part id="P1"> <part-name/> </score-part> </part-list> <part id="P1"> <measure number="1"> <attributes> <divisions>4</divisions> <key> <fifths>-3</fifths> <mode>major</mode> </key> <time> <beats>4</beats> <beat-type>4</beat-type> </time> <clef> <sign>G</sign> <line>2</line> </clef> <transpose> <diatonic>-5</diatonic> <chromatic>-9</chromatic> </transpose> </attributes> <note> <pitch> <step>G</step> <octave>4</octave> </pitch> <duration>2</duration> <voice>1</voice> <type>quarter</type> </note> </measure> </part> </score-partwise> """ with tempfile.NamedTemporaryFile() as temp_file: temp_file.write(xml) temp_file.flush() musicxml_parser.MusicXMLDocument(temp_file.name) sequence = musicxml_reader.musicxml_file_to_sequence_proto(temp_file.name) self.assertLen(sequence.key_signatures, 1) self.assertEqual(music_pb2.NoteSequence.KeySignature.G_FLAT, sequence.key_signatures[0].key)
def test_transposed_keysig(self): xml = br"""<?xml version="1.0" encoding="UTF-8" standalone="no"?> <!DOCTYPE score-partwise PUBLIC "-//Recordare//DTD MusicXML 3.0 Partwise//EN" "http://www.musicxml.org/dtds/partwise.dtd"> <score-partwise version="3.0"> <part-list> <score-part id="P1"> <part-name/> </score-part> </part-list> <part id="P1"> <measure number="1"> <attributes> <divisions>4</divisions> <key> <fifths>-3</fifths> <mode>major</mode> </key> <time> <beats>4</beats> <beat-type>4</beat-type> </time> <clef> <sign>G</sign> <line>2</line> </clef> <transpose> <diatonic>-5</diatonic> <chromatic>-9</chromatic> </transpose> </attributes> <note> <pitch> <step>G</step> <octave>4</octave> </pitch> <duration>2</duration> <voice>1</voice> <type>quarter</type> </note> </measure> </part> </score-partwise> """ with tempfile.NamedTemporaryFile() as temp_file: temp_file.write(xml) temp_file.flush() musicxml_parser.MusicXMLDocument(temp_file.name) sequence = musicxml_reader.musicxml_file_to_sequence_proto(temp_file.name) self.assertEqual(1, len(sequence.key_signatures)) self.assertEqual(music_pb2.NoteSequence.KeySignature.G_FLAT, sequence.key_signatures[0].key)
def read_from_xml(xml_file): """Reads an xml file into a prettyMIDI object. Args: xml_file : str path to a musicxml file Returns: a prettyMIDI object """ xml_note_sequence = musicxml_reader.musicxml_file_to_sequence_proto( xml_file) return magenta.music.sequence_proto_to_pretty_midi(xml_note_sequence)
def test_atonal_transposition(self): """Test that transposition works when changing instrument transposition. This can occur within a single part in a score where the score has no key signature / is atonal. Examples include changing from a non-transposing instrument to a transposing one (ex. Flute to Bb Clarinet) or vice versa, or changing among transposing instruments (ex. Bb Clarinet to Eb Alto Saxophone). """ ns = musicxml_reader.musicxml_file_to_sequence_proto( self.atonal_transposition_filename) expected_ns = common_testing_lib.parse_test_proto( music_pb2.NoteSequence, """ ticks_per_quarter: 220 time_signatures: { numerator: 4 denominator: 4 } tempos: { qpm: 120 } key_signatures: { } part_infos { part: 0 name: "Flute" } source_info: { source_type: SCORE_BASED encoding_type: MUSIC_XML parser: MAGENTA_MUSIC_XML } total_time: 4.0 """) expected_pitches = [72, 74, 76, 77, 79, 77, 76, 74] time = 0 for pitch in expected_pitches: note = expected_ns.notes.add() note.pitch = pitch note.start_time = time time += .5 note.end_time = time note.velocity = 64 note.numerator = 1 note.denominator = 4 note.voice = 1 self.maxDiff = None self.assertProtoEquals(expected_ns, ns)
def test_incomplete_measures(self): """Test that incomplete measures have the correct time signature. This can occur in pickup bars or incomplete measures. For example, if the time signature in the MusicXML is 4/4, but the measure only contains one quarter note, Magenta expects this pickup measure to have a time signature of 1/4. """ ns = musicxml_reader.musicxml_file_to_sequence_proto( self.time_signature_filename) # One time signature per measure self.assertEqual(len(ns.time_signatures), 10) self.assertEqual(len(ns.key_signatures), 1) self.assertEqual(len(ns.notes), 112)
def testFluteScale(self): """Verify properties of the flute scale.""" ns = musicxml_reader.musicxml_file_to_sequence_proto( self.flute_scale_filename) expected_ns = common_testing_lib.parse_test_proto( music_pb2.NoteSequence, """ ticks_per_quarter: 220 time_signatures: { numerator: 4 denominator: 4 } tempos: { qpm: 120 } key_signatures: { key: F } source_info: { source_type: SCORE_BASED encoding_type: MUSIC_XML parser: MAGENTA_MUSIC_XML } part_infos { part: 0 name: "Flute" } total_time: 4.0 """) expected_pitches = [65, 67, 69, 70, 72, 74, 76, 77] time = 0 for pitch in expected_pitches: note = expected_ns.notes.add() note.part = 0 note.voice = 1 note.pitch = pitch note.start_time = time time += .5 note.end_time = time note.velocity = 64 note.numerator = 1 note.denominator = 4 self.assertProtoEquals(expected_ns, ns)
def test_whole_measure_rest_forward(self): """Test that a whole measure rest can be encoded using <forward>. A whole measure rest is usually encoded as a <note> with a duration equal to that of a whole measure. An alternative encoding is to use the <forward> element to advance the time cursor to a duration equal to that of a whole measure. This implies a whole measure rest when there are no <note> elements in this measure. """ ns = musicxml_reader.musicxml_file_to_sequence_proto( self.whole_measure_rest_forward_filename) expected_ns = testing_lib.parse_test_proto( music_pb2.NoteSequence, """ ticks_per_quarter: 220 time_signatures { numerator: 4 denominator: 4 } time_signatures { time: 6.0 numerator: 2 denominator: 4 } key_signatures { } tempos { qpm: 120 } notes { pitch: 72 velocity: 64 end_time: 2.0 numerator: 1 denominator: 1 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 4.0 end_time: 6.0 numerator: 1 denominator: 1 voice: 1 } notes { pitch: 60 velocity: 64 start_time: 6.0 end_time: 7.0 numerator: 1 denominator: 2 voice: 1 } notes { pitch: 60 velocity: 64 start_time: 8.0 end_time: 9.0 numerator: 1 denominator: 2 voice: 1 } total_time: 9.0 part_infos { name: "Flute" } source_info { source_type: SCORE_BASED encoding_type: MUSIC_XML parser: MAGENTA_MUSIC_XML } """) self.assertProtoEquals(expected_ns, ns)
def test_whole_measure_rest_forward(self): """Test that a whole measure rest can be encoded using <forward>. A whole measure rest is usually encoded as a <note> with a duration equal to that of a whole measure. An alternative encoding is to use the <forward> element to advance the time cursor to a duration equal to that of a whole measure. This implies a whole measure rest when there are no <note> elements in this measure. """ ns = musicxml_reader.musicxml_file_to_sequence_proto( self.whole_measure_rest_forward_filename) expected_ns = common_testing_lib.parse_test_proto( music_pb2.NoteSequence, """ ticks_per_quarter: 220 time_signatures { numerator: 4 denominator: 4 } time_signatures { time: 6.0 numerator: 2 denominator: 4 } key_signatures { } tempos { qpm: 120 } notes { pitch: 72 velocity: 64 end_time: 2.0 numerator: 1 denominator: 1 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 4.0 end_time: 6.0 numerator: 1 denominator: 1 voice: 1 } notes { pitch: 60 velocity: 64 start_time: 6.0 end_time: 7.0 numerator: 1 denominator: 2 voice: 1 } notes { pitch: 60 velocity: 64 start_time: 8.0 end_time: 9.0 numerator: 1 denominator: 2 voice: 1 } total_time: 9.0 part_infos { name: "Flute" } source_info { source_type: SCORE_BASED encoding_type: MUSIC_XML parser: MAGENTA_MUSIC_XML } """) self.assertProtoEquals(expected_ns, ns)
def test_meter(self): """Test that meters are encoded properly. Musical meters are expressed as a ratio of beats to divisions. The MusicXML parser uses this ratio in lowest terms for timing purposes. However, the meters should be in the actual terms when appearing in a NoteSequence. """ ns = musicxml_reader.musicxml_file_to_sequence_proto( self.meter_test_filename) expected_ns = testing_lib.parse_test_proto( music_pb2.NoteSequence, """ ticks_per_quarter: 220 time_signatures { numerator: 1 denominator: 4 } time_signatures { time: 0.5 numerator: 2 denominator: 4 } time_signatures { time: 1.5 numerator: 3 denominator: 4 } time_signatures { time: 3.0 numerator: 4 denominator: 4 } time_signatures { time: 5.0 numerator: 5 denominator: 4 } time_signatures { time: 7.5 numerator: 6 denominator: 4 } time_signatures { time: 10.5 numerator: 7 denominator: 4 } time_signatures { time: 14.0 numerator: 1 denominator: 8 } time_signatures { time: 14.25 numerator: 2 denominator: 8 } time_signatures { time: 14.75 numerator: 3 denominator: 8 } time_signatures { time: 15.5 numerator: 4 denominator: 8 } time_signatures { time: 16.5 numerator: 5 denominator: 8 } time_signatures { time: 17.75 numerator: 6 denominator: 8 } time_signatures { time: 19.25 numerator: 7 denominator: 8 } time_signatures { time: 21.0 numerator: 8 denominator: 8 } time_signatures { time: 23.0 numerator: 9 denominator: 8 } time_signatures { time: 25.25 numerator: 10 denominator: 8 } time_signatures { time: 27.75 numerator: 11 denominator: 8 } time_signatures { time: 30.5 numerator: 12 denominator: 8 } time_signatures { time: 33.5 numerator: 2 denominator: 2 } time_signatures { time: 35.5 numerator: 3 denominator: 2 } time_signatures { time: 38.5 numerator: 4 denominator: 2 } time_signatures { time: 42.5 numerator: 4 denominator: 4 } time_signatures { time: 44.5 numerator: 2 denominator: 2 } key_signatures { } tempos { qpm: 120 } notes { pitch: 72 velocity: 64 end_time: 0.5 numerator: 1 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 0.5 end_time: 1.5 numerator: 1 denominator: 2 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 1.5 end_time: 3.0 numerator: 3 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 3.0 end_time: 5.0 numerator: 1 denominator: 1 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 5.0 end_time: 6.5 numerator: 3 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 6.5 end_time: 7.5 numerator: 1 denominator: 2 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 7.5 end_time: 9.0 numerator: 3 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 9.0 end_time: 10.5 numerator: 3 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 10.5 end_time: 12.0 numerator: 3 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 12.0 end_time: 13.5 numerator: 3 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 13.5 end_time: 14.0 numerator: 1 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 14.0 end_time: 14.25 numerator: 1 denominator: 8 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 14.25 end_time: 14.75 numerator: 1 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 14.75 end_time: 15.5 numerator: 3 denominator: 8 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 15.5 end_time: 16.0 numerator: 1 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 16.0 end_time: 16.5 numerator: 1 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 16.5 end_time: 17.0 numerator: 1 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 17.0 end_time: 17.5 numerator: 1 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 17.5 end_time: 17.75 numerator: 1 denominator: 8 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 17.75 end_time: 18.5 numerator: 3 denominator: 8 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 18.5 end_time: 19.25 numerator: 3 denominator: 8 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 19.25 end_time: 20.0 numerator: 3 denominator: 8 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 20.0 end_time: 20.5 numerator: 1 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 20.5 end_time: 21.0 numerator: 1 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 21.0 end_time: 21.75 numerator: 3 denominator: 8 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 21.75 end_time: 22.5 numerator: 3 denominator: 8 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 22.5 end_time: 23.0 numerator: 1 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 23.0 end_time: 24.5 numerator: 3 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 24.5 end_time: 25.25 numerator: 3 denominator: 8 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 25.25 end_time: 26.75 numerator: 3 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 26.75 end_time: 27.25 numerator: 1 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 27.25 end_time: 27.75 numerator: 1 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 27.75 end_time: 29.25 numerator: 3 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 29.25 end_time: 30.0 numerator: 3 denominator: 8 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 30.0 end_time: 30.5 numerator: 1 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 30.5 end_time: 32.0 numerator: 3 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 32.0 end_time: 33.5 numerator: 3 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 33.5 end_time: 34.5 numerator: 1 denominator: 2 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 34.5 end_time: 35.5 numerator: 1 denominator: 2 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 35.5 end_time: 36.5 numerator: 1 denominator: 2 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 36.5 end_time: 37.5 numerator: 1 denominator: 2 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 37.5 end_time: 38.5 numerator: 1 denominator: 2 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 38.5 end_time: 40.5 numerator: 1 denominator: 1 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 40.5 end_time: 42.5 numerator: 1 denominator: 1 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 42.5 end_time: 44.5 numerator: 1 denominator: 1 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 44.5 end_time: 46.5 numerator: 1 denominator: 1 voice: 1 } total_time: 46.5 part_infos { name: "Flute" } source_info { source_type: SCORE_BASED encoding_type: MUSIC_XML parser: MAGENTA_MUSIC_XML } """) self.assertProtoEquals(expected_ns, ns)
def test_st_anne(self): """Verify properties of the St. Anne file. The file contains 2 parts and 4 voices. """ ns = musicxml_reader.musicxml_file_to_sequence_proto( self.st_anne_filename) expected_ns = common_testing_lib.parse_test_proto( music_pb2.NoteSequence, """ ticks_per_quarter: 220 time_signatures { numerator: 1 denominator: 4 } time_signatures { time: 0.5 numerator: 4 denominator: 4 } time_signatures { time: 2.5 numerator: 4 denominator: 4 } time_signatures { time: 4.5 numerator: 4 denominator: 4 } time_signatures { time: 6.5 numerator: 3 denominator: 4 } time_signatures { time: 8.0 numerator: 1 denominator: 4 } time_signatures { time: 8.5 numerator: 4 denominator: 4 } time_signatures { time: 10.5 numerator: 4 denominator: 4 } time_signatures { time: 12.5 numerator: 4 denominator: 4 } time_signatures { time: 14.5 numerator: 3 denominator: 4 } tempos: { qpm: 120 } key_signatures: { key: C } source_info: { source_type: SCORE_BASED encoding_type: MUSIC_XML parser: MAGENTA_MUSIC_XML } part_infos { part: 0 name: "Harpsichord" } part_infos { part: 1 name: "Piano" } total_time: 16.0 """) pitches_0_1 = [ (67, .5), (64, .5), (69, .5), (67, .5), (72, .5), (72, .5), (71, .5), (72, .5), (67, .5), (72, .5), (67, .5), (69, .5), (66, .5), (67, 1.5), (71, .5), (72, .5), (69, .5), (74, .5), (71, .5), (72, .5), (69, .5), (71, .5), (67, .5), (69, .5), (72, .5), (74, .5), (71, .5), (72, 1.5), ] pitches_0_2 = [ (60, .5), (60, .5), (60, .5), (60, .5), (64, .5), (62, .5), (62, .5), (64, .5), (64, .5), (64, .5), (64, .5), (64, .5), (62, .5), (62, 1.5), (62, .5), (64, .5), (60, .5), (65, .5), (62, .5), (64, .75), (62, .25), (59, .5), (60, .5), (65, .5), (64, .5), (62, .5), (62, .5), (64, 1.5), ] pitches_1_1 = [ (52, .5), (55, .5), (57, .5), (60, .5), (60, .5), (57, .5), (55, .5), (55, .5), (60, .5), (60, .5), (59, .5), (57, .5), (57, .5), (59, 1.5), (55, .5), (55, .5), (57, .5), (57, .5), (55, .5), (55, .5), (57, .5), (56, .5), (55, .5), (53, .5), (55, .5), (57, .5), (55, .5), (55, 1.5), ] pitches_1_2 = [ (48, .5), (48, .5), (53, .5), (52, .5), (57, .5), (53, .5), (55, .5), (48, .5), (48, .5), (45, .5), (52, .5), (48, .5), (50, .5), (43, 1.5), (55, .5), (48, .5), (53, .5), (50, .5), (55, .5), (48, .5), (53, .5), (52, .5), (52, .5), (50, .5), (48, .5), (53, .5), (55, .5), (48, 1.5), ] part_voice_instrument_program_pitches = [ (0, 1, 1, 7, pitches_0_1), (0, 2, 1, 7, pitches_0_2), (1, 1, 2, 1, pitches_1_1), (1, 2, 2, 1, pitches_1_2), ] for part, voice, instrument, program, pitches in ( part_voice_instrument_program_pitches): time = 0 for pitch, duration in pitches: note = expected_ns.notes.add() note.part = part note.voice = voice note.pitch = pitch note.start_time = time time += duration note.end_time = time note.velocity = 64 note.instrument = instrument note.program = program if duration == .5: note.numerator = 1 note.denominator = 4 if duration == .25: note.numerator = 1 note.denominator = 8 if duration == .75: note.numerator = 3 note.denominator = 8 if duration == 1.5: note.numerator = 3 note.denominator = 4 expected_ns.notes.sort( key=lambda note: (note.part, note.voice, note.start_time)) ns.notes.sort( key=lambda note: (note.part, note.voice, note.start_time)) self.assertProtoEquals(expected_ns, ns)
def test_unmetered_music(self): """Test that time signatures are inserted for music without time signatures. MusicXML does not require the use of time signatures. Music without time signatures occur in medieval chant, cadenzas, and contemporary music. """ ns = musicxml_reader.musicxml_file_to_sequence_proto( self.unmetered_filename) expected_ns = common_testing_lib.parse_test_proto( music_pb2.NoteSequence, """ ticks_per_quarter: 220 time_signatures: { numerator: 11 denominator: 8 } tempos: { qpm: 120 } key_signatures: { } notes { pitch: 72 velocity: 64 end_time: 0.5 numerator: 1 denominator: 4 voice: 1 } notes { pitch: 74 velocity: 64 start_time: 0.5 end_time: 0.75 numerator: 1 denominator: 8 voice: 1 } notes { pitch: 76 velocity: 64 start_time: 0.75 end_time: 1.25 numerator: 1 denominator: 4 voice: 1 } notes { pitch: 77 velocity: 64 start_time: 1.25 end_time: 1.75 numerator: 1 denominator: 4 voice: 1 } notes { pitch: 79 velocity: 64 start_time: 1.75 end_time: 2.75 numerator: 1 denominator: 2 voice: 1 } part_infos { name: "Flute" } source_info: { source_type: SCORE_BASED encoding_type: MUSIC_XML parser: MAGENTA_MUSIC_XML } total_time: 2.75 """) self.maxDiff = None self.assertProtoEquals(expected_ns, ns)
def test_meter(self): """Test that meters are encoded properly. Musical meters are expressed as a ratio of beats to divisions. The MusicXML parser uses this ratio in lowest terms for timing purposes. However, the meters should be in the actual terms when appearing in a NoteSequence. """ ns = musicxml_reader.musicxml_file_to_sequence_proto( self.meter_test_filename) expected_ns = common_testing_lib.parse_test_proto( music_pb2.NoteSequence, """ ticks_per_quarter: 220 time_signatures { numerator: 1 denominator: 4 } time_signatures { time: 0.5 numerator: 2 denominator: 4 } time_signatures { time: 1.5 numerator: 3 denominator: 4 } time_signatures { time: 3.0 numerator: 4 denominator: 4 } time_signatures { time: 5.0 numerator: 5 denominator: 4 } time_signatures { time: 7.5 numerator: 6 denominator: 4 } time_signatures { time: 10.5 numerator: 7 denominator: 4 } time_signatures { time: 14.0 numerator: 1 denominator: 8 } time_signatures { time: 14.25 numerator: 2 denominator: 8 } time_signatures { time: 14.75 numerator: 3 denominator: 8 } time_signatures { time: 15.5 numerator: 4 denominator: 8 } time_signatures { time: 16.5 numerator: 5 denominator: 8 } time_signatures { time: 17.75 numerator: 6 denominator: 8 } time_signatures { time: 19.25 numerator: 7 denominator: 8 } time_signatures { time: 21.0 numerator: 8 denominator: 8 } time_signatures { time: 23.0 numerator: 9 denominator: 8 } time_signatures { time: 25.25 numerator: 10 denominator: 8 } time_signatures { time: 27.75 numerator: 11 denominator: 8 } time_signatures { time: 30.5 numerator: 12 denominator: 8 } time_signatures { time: 33.5 numerator: 2 denominator: 2 } time_signatures { time: 35.5 numerator: 3 denominator: 2 } time_signatures { time: 38.5 numerator: 4 denominator: 2 } time_signatures { time: 42.5 numerator: 4 denominator: 4 } time_signatures { time: 44.5 numerator: 2 denominator: 2 } key_signatures { } tempos { qpm: 120 } notes { pitch: 72 velocity: 64 end_time: 0.5 numerator: 1 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 0.5 end_time: 1.5 numerator: 1 denominator: 2 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 1.5 end_time: 3.0 numerator: 3 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 3.0 end_time: 5.0 numerator: 1 denominator: 1 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 5.0 end_time: 6.5 numerator: 3 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 6.5 end_time: 7.5 numerator: 1 denominator: 2 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 7.5 end_time: 9.0 numerator: 3 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 9.0 end_time: 10.5 numerator: 3 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 10.5 end_time: 12.0 numerator: 3 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 12.0 end_time: 13.5 numerator: 3 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 13.5 end_time: 14.0 numerator: 1 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 14.0 end_time: 14.25 numerator: 1 denominator: 8 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 14.25 end_time: 14.75 numerator: 1 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 14.75 end_time: 15.5 numerator: 3 denominator: 8 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 15.5 end_time: 16.0 numerator: 1 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 16.0 end_time: 16.5 numerator: 1 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 16.5 end_time: 17.0 numerator: 1 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 17.0 end_time: 17.5 numerator: 1 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 17.5 end_time: 17.75 numerator: 1 denominator: 8 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 17.75 end_time: 18.5 numerator: 3 denominator: 8 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 18.5 end_time: 19.25 numerator: 3 denominator: 8 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 19.25 end_time: 20.0 numerator: 3 denominator: 8 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 20.0 end_time: 20.5 numerator: 1 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 20.5 end_time: 21.0 numerator: 1 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 21.0 end_time: 21.75 numerator: 3 denominator: 8 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 21.75 end_time: 22.5 numerator: 3 denominator: 8 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 22.5 end_time: 23.0 numerator: 1 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 23.0 end_time: 24.5 numerator: 3 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 24.5 end_time: 25.25 numerator: 3 denominator: 8 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 25.25 end_time: 26.75 numerator: 3 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 26.75 end_time: 27.25 numerator: 1 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 27.25 end_time: 27.75 numerator: 1 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 27.75 end_time: 29.25 numerator: 3 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 29.25 end_time: 30.0 numerator: 3 denominator: 8 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 30.0 end_time: 30.5 numerator: 1 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 30.5 end_time: 32.0 numerator: 3 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 32.0 end_time: 33.5 numerator: 3 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 33.5 end_time: 34.5 numerator: 1 denominator: 2 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 34.5 end_time: 35.5 numerator: 1 denominator: 2 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 35.5 end_time: 36.5 numerator: 1 denominator: 2 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 36.5 end_time: 37.5 numerator: 1 denominator: 2 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 37.5 end_time: 38.5 numerator: 1 denominator: 2 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 38.5 end_time: 40.5 numerator: 1 denominator: 1 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 40.5 end_time: 42.5 numerator: 1 denominator: 1 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 42.5 end_time: 44.5 numerator: 1 denominator: 1 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 44.5 end_time: 46.5 numerator: 1 denominator: 1 voice: 1 } total_time: 46.5 part_infos { name: "Flute" } source_info { source_type: SCORE_BASED encoding_type: MUSIC_XML parser: MAGENTA_MUSIC_XML } """) self.assertProtoEquals(expected_ns, ns)