def convert_abc(root_dir, sub_dir, full_file_path): """Converts an abc 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: tunes, exceptions = abc_parser.parse_abc_tunebook( tf.gfile.GFile(full_file_path, 'rb').read()) except abc_parser.ABCParseError as e: tf.logging.warning( 'Could not parse ABC file %s. It will be skipped. Error was: %s', full_file_path, e) return None for exception in exceptions: tf.logging.warning( 'Could not parse tune in ABC file %s. It will be skipped. Error was: ' '%s', full_file_path, exception) sequences = [] for idx, tune in tunes.items(): tune.collection_name = os.path.basename(root_dir) tune.filename = os.path.join(sub_dir, os.path.basename(full_file_path)) tune.id = generate_note_sequence_id( '{}_{}'.format(tune.filename, idx), tune.collection_name, 'abc') sequences.append(tune) tf.logging.info('Converted ABC file %s.', full_file_path) return sequences
def testSlashDuration(self): tunes, exceptions = abc_parser.parse_abc_tunebook("""X:1 Q:1/4=120 L:1/4 T:Test CC/C//C///C//// """) self.assertLen(tunes, 1) self.assertEmpty(exceptions) expected_ns1 = testing_lib.parse_test_proto( music_pb2.NoteSequence, """ ticks_per_quarter: 220 source_info: { source_type: SCORE_BASED encoding_type: ABC parser: MAGENTA_ABC } reference_number: 1 sequence_metadata { title: "Test" } tempos { qpm: 120 } notes { pitch: 60 velocity: 90 start_time: 0.0 end_time: 0.5 } notes { pitch: 60 velocity: 90 start_time: 0.5 end_time: 0.75 } notes { pitch: 60 velocity: 90 start_time: 0.75 end_time: 0.875 } notes { pitch: 60 velocity: 90 start_time: 0.875 end_time: 0.9375 } notes { pitch: 60 velocity: 90 start_time: 0.9375 end_time: 0.96875 } total_time: 0.96875 """) self.assertProtoEquals(expected_ns1, tunes[1])
def testParseTempos(self): # Examples from http://abcnotation.com/wiki/abc:standard:v2.1#qtempo tunes, exceptions = abc_parser.parse_abc_tunebook(""" X:1 L:1/4 Q:60 X:2 L:1/4 Q:C=100 X:3 Q:1/2=120 X:4 Q:1/4 3/8 1/4 3/8=40 X:5 Q:5/4=40 X:6 Q: "Allegro" 1/4=120 X:7 Q: 1/4=120 "Allegro" X:8 Q: 3/8=50 "Slowly" X:9 Q:"Andante" X:10 Q:100 % define tempo using deprecated syntax % deprecated tempo syntax depends on unit note length. if it is % not defined, it is derived from the current meter. M:2/4 % define meter after tempo to verify that is supported. X:11 Q:100 % define tempo using deprecated syntax % deprecated tempo syntax depends on unit note length. L:1/4 % define note length after tempo to verify that is supported. """) self.assertLen(tunes, 11) self.assertEmpty(exceptions) self.assertEqual(60, tunes[1].tempos[0].qpm) self.assertEqual(100, tunes[2].tempos[0].qpm) self.assertEqual(240, tunes[3].tempos[0].qpm) self.assertEqual(200, tunes[4].tempos[0].qpm) self.assertEqual(200, tunes[5].tempos[0].qpm) self.assertEqual(120, tunes[6].tempos[0].qpm) self.assertEqual(120, tunes[7].tempos[0].qpm) self.assertEqual(75, tunes[8].tempos[0].qpm) self.assertEmpty(tunes[9].tempos, 0) self.assertEqual(25, tunes[10].tempos[0].qpm) self.assertEqual(100, tunes[11].tempos[0].qpm)
def testChords(self): tunes, exceptions = abc_parser.parse_abc_tunebook(""" X:1 Q:1/4=120 L:1/4 T:Test [CEG]""") self.assertEmpty(tunes) self.assertLen(exceptions, 1) self.assertIsInstance(exceptions[0], abc_parser.ChordError)
def testInvalidCharacter(self): tunes, exceptions = abc_parser.parse_abc_tunebook(""" X:1 Q:1/4=120 L:1/4 T:Test invalid notes!""") self.assertEmpty(tunes) self.assertLen(exceptions, 1) self.assertIsInstance(exceptions[0], abc_parser.InvalidCharacterError)
def testTuplet(self): tunes, exceptions = abc_parser.parse_abc_tunebook(""" X:1 Q:1/4=120 L:1/4 T:Test (3abc """) self.assertEmpty(tunes) self.assertLen(exceptions, 1) self.assertIsInstance(exceptions[0], abc_parser.TupletError)
def testTie(self): tunes, exceptions = abc_parser.parse_abc_tunebook(""" X:1 Q:1/4=120 L:1/4 T:Test abc-|cba c4-c4 C.-C """) self.assertLen(tunes, 1) self.assertEmpty(exceptions) self.assertLen(tunes[1].notes, 10)
def testSlur(self): tunes, exceptions = abc_parser.parse_abc_tunebook(""" X:1 Q:1/4=120 L:1/4 T:Test (ABC) ( a b c ) (c (d e f) g a) """) self.assertLen(tunes, 1) self.assertEmpty(exceptions) self.assertLen(tunes[1].notes, 12)
def testDecorations(self): tunes, exceptions = abc_parser.parse_abc_tunebook(""" X:1 Q:1/4=120 L:1/4 T:Test .a~bHcLdMeOfPgSATbucvd """) self.assertLen(tunes, 1) self.assertEmpty(exceptions) self.assertLen(tunes[1].notes, 11)
def testParseOctaves(self): tunes, exceptions = abc_parser.parse_abc_tunebook("""X:1 T:Test CC,',C,C'c """) self.assertLen(tunes, 1) self.assertEmpty(exceptions) expected_ns1 = testing_lib.parse_test_proto( music_pb2.NoteSequence, """ ticks_per_quarter: 220 source_info: { source_type: SCORE_BASED encoding_type: ABC parser: MAGENTA_ABC } reference_number: 1 sequence_metadata { title: "Test" } notes { pitch: 60 velocity: 90 end_time: 0.25 } notes { pitch: 48 velocity: 90 start_time: 0.25 end_time: 0.5 } notes { pitch: 48 velocity: 90 start_time: 0.5 end_time: 0.75 } notes { pitch: 72 velocity: 90 start_time: 0.75 end_time: 1.0 } notes { pitch: 72 velocity: 90 start_time: 1.0 end_time: 1.25 } total_time: 1.25 """) self.assertProtoEquals(expected_ns1, tunes[1])
def testChordAnnotations(self): tunes, exceptions = abc_parser.parse_abc_tunebook(""" X:1 Q:1/4=120 L:1/4 T:Test "G"G % verify that an empty annotation doesn't cause problems. ""D """) self.assertLen(tunes, 1) self.assertEmpty(exceptions) expected_ns1 = testing_lib.parse_test_proto( music_pb2.NoteSequence, """ ticks_per_quarter: 220 source_info: { source_type: SCORE_BASED encoding_type: ABC parser: MAGENTA_ABC } reference_number: 1 sequence_metadata { title: "Test" } tempos { qpm: 120 } notes { pitch: 67 velocity: 90 end_time: 0.5 } notes { pitch: 62 velocity: 90 start_time: 0.5 end_time: 1.0 } text_annotations { text: "G" annotation_type: CHORD_SYMBOL } text_annotations { time: 0.5 } total_time: 1.0 """) self.assertProtoEquals(expected_ns1, tunes[1])
def testLineContinuation(self): tunes, exceptions = abc_parser.parse_abc_tunebook(r""" X:1 Q:1/4=120 L:1/4 T:Test abc \ cba| abc\ cba| abc cba| cdef|\ \ cedf:| """) self.assertLen(tunes, 1) self.assertEmpty(exceptions) self.assertLen(tunes[1].notes, 26)
def testOneSidedRepeat(self): tunes, exceptions = abc_parser.parse_abc_tunebook(""" X:1 Q:1/4=120 L:1/4 T:Test Bcd :| Bcd """) self.assertLen(tunes, 1) self.assertEmpty(exceptions) expected_ns1 = testing_lib.parse_test_proto( music_pb2.NoteSequence, """ ticks_per_quarter: 220 source_info: { source_type: SCORE_BASED encoding_type: ABC parser: MAGENTA_ABC } reference_number: 1 sequence_metadata { title: "Test" } tempos { qpm: 120 } notes { pitch: 71 velocity: 90 start_time: 0.0 end_time: 0.5 } notes { pitch: 72 velocity: 90 start_time: 0.5 end_time: 1.0 } notes { pitch: 74 velocity: 90 start_time: 1.0 end_time: 1.5 } notes { pitch: 71 velocity: 90 start_time: 1.5 end_time: 2.0 } notes { pitch: 72 velocity: 90 start_time: 2.0 end_time: 2.5 } notes { pitch: 74 velocity: 90 start_time: 2.5 end_time: 3.0 } section_annotations { time: 0 section_id: 0 } section_annotations { time: 1.5 section_id: 1 } section_groups { sections { section_id: 0 } num_times: 2 } section_groups { sections { section_id: 1 } num_times: 1 } total_time: 3.0 """) self.assertProtoEquals(expected_ns1, tunes[1])
def testRepeats(self): # Several equivalent versions of the same tune. tunes, exceptions = abc_parser.parse_abc_tunebook(""" X:1 Q:1/4=120 L:1/4 T:Test Bcd ::[]|[]:: Bcd ::| X:2 Q:1/4=120 L:1/4 T:Test Bcd :::: Bcd ::| X:3 Q:1/4=120 L:1/4 T:Test |::Bcd ::|:: Bcd ::| % This version contains mismatched repeat symbols. X:4 Q:1/4=120 L:1/4 T:Test |::Bcd ::|: Bcd ::| % This version is missing a repeat symbol at the end. X:5 Q:1/4=120 L:1/4 T:Test |:: Bcd ::|: Bcd | % Ambiguous repeat that should go to the last repeat symbol. X:6 Q:1/4=120 L:1/4 T:Test |:: Bcd ::| Bcd :| % Ambiguous repeat that should go to the last double bar. X:7 Q:1/4=120 L:1/4 T:Test |:: Bcd ::| Bcd || Bcd :| % Ambiguous repeat that should go to the last double bar. X:8 Q:1/4=120 L:1/4 T:Test || Bcd ::| Bcd || Bcd :| % Ensure double bar doesn't confuse declared repeat. X:9 Q:1/4=120 L:1/4 T:Test |:: B || cd ::| Bcd || |: Bcd :| % Mismatched repeat at the very beginning. X:10 Q:1/4=120 L:1/4 T:Test :| Bcd |:: Bcd ::| """) self.assertLen(tunes, 7) self.assertLen(exceptions, 3) self.assertIsInstance(exceptions[0], abc_parser.RepeatParseError) self.assertIsInstance(exceptions[1], abc_parser.RepeatParseError) self.assertIsInstance(exceptions[2], abc_parser.RepeatParseError) expected_ns1 = testing_lib.parse_test_proto( music_pb2.NoteSequence, """ ticks_per_quarter: 220 source_info: { source_type: SCORE_BASED encoding_type: ABC parser: MAGENTA_ABC } reference_number: 1 sequence_metadata { title: "Test" } tempos { qpm: 120 } notes { pitch: 71 velocity: 90 start_time: 0.0 end_time: 0.5 } notes { pitch: 72 velocity: 90 start_time: 0.5 end_time: 1.0 } notes { pitch: 74 velocity: 90 start_time: 1.0 end_time: 1.5 } notes { pitch: 71 velocity: 90 start_time: 1.5 end_time: 2.0 } notes { pitch: 72 velocity: 90 start_time: 2.0 end_time: 2.5 } notes { pitch: 74 velocity: 90 start_time: 2.5 end_time: 3.0 } section_annotations { time: 0 section_id: 0 } section_annotations { time: 1.5 section_id: 1 } section_groups { sections { section_id: 0 } num_times: 3 } section_groups { sections { section_id: 1 } num_times: 3 } total_time: 3.0 """) self.assertProtoEquals(expected_ns1, tunes[1]) # Other versions are identical except for the reference number. expected_ns2 = copy.deepcopy(expected_ns1) expected_ns2.reference_number = 2 self.assertProtoEquals(expected_ns2, tunes[2]) expected_ns3 = copy.deepcopy(expected_ns1) expected_ns3.reference_number = 3 self.assertProtoEquals(expected_ns3, tunes[3]) # Also identical, except the last section is played only twice. expected_ns6 = copy.deepcopy(expected_ns1) expected_ns6.reference_number = 6 expected_ns6.section_groups[-1].num_times = 2 self.assertProtoEquals(expected_ns6, tunes[6]) expected_ns7 = testing_lib.parse_test_proto( music_pb2.NoteSequence, """ ticks_per_quarter: 220 source_info: { source_type: SCORE_BASED encoding_type: ABC parser: MAGENTA_ABC } reference_number: 7 sequence_metadata { title: "Test" } tempos { qpm: 120 } notes { pitch: 71 velocity: 90 start_time: 0.0 end_time: 0.5 } notes { pitch: 72 velocity: 90 start_time: 0.5 end_time: 1.0 } notes { pitch: 74 velocity: 90 start_time: 1.0 end_time: 1.5 } notes { pitch: 71 velocity: 90 start_time: 1.5 end_time: 2.0 } notes { pitch: 72 velocity: 90 start_time: 2.0 end_time: 2.5 } notes { pitch: 74 velocity: 90 start_time: 2.5 end_time: 3.0 } notes { pitch: 71 velocity: 90 start_time: 3.0 end_time: 3.5 } notes { pitch: 72 velocity: 90 start_time: 3.5 end_time: 4.0 } notes { pitch: 74 velocity: 90 start_time: 4.0 end_time: 4.5 } section_annotations { time: 0 section_id: 0 } section_annotations { time: 1.5 section_id: 1 } section_annotations { time: 3.0 section_id: 2 } section_groups { sections { section_id: 0 } num_times: 3 } section_groups { sections { section_id: 1 } num_times: 1 } section_groups { sections { section_id: 2 } num_times: 2 } total_time: 4.5 """) self.assertProtoEquals(expected_ns7, tunes[7]) expected_ns8 = copy.deepcopy(expected_ns7) expected_ns8.reference_number = 8 self.assertProtoEquals(expected_ns8, tunes[8]) expected_ns9 = copy.deepcopy(expected_ns7) expected_ns9.reference_number = 9 self.assertProtoEquals(expected_ns9, tunes[9])
def testParseBrokenRhythm(self): # These tunes should be equivalent. tunes, exceptions = abc_parser.parse_abc_tunebook(""" X:1 Q:1/4=120 L:1/4 M:3/4 T:Test B>cd B<cd X:2 Q:1/4=120 L:1/4 M:3/4 T:Test B3/2c/2d B/2c3/2d X:3 Q:1/4=120 L:1/4 M:3/4 T:Test B3/c/d B/c3/d """) self.assertLen(tunes, 3) self.assertEmpty(exceptions) expected_ns1 = testing_lib.parse_test_proto( music_pb2.NoteSequence, """ ticks_per_quarter: 220 source_info: { source_type: SCORE_BASED encoding_type: ABC parser: MAGENTA_ABC } reference_number: 1 sequence_metadata { title: "Test" } time_signatures { numerator: 3 denominator: 4 } tempos { qpm: 120 } notes { pitch: 71 velocity: 90 start_time: 0.0 end_time: 0.75 } notes { pitch: 72 velocity: 90 start_time: 0.75 end_time: 1.0 } notes { pitch: 74 velocity: 90 start_time: 1.0 end_time: 1.5 } notes { pitch: 71 velocity: 90 start_time: 1.5 end_time: 1.75 } notes { pitch: 72 velocity: 90 start_time: 1.75 end_time: 2.5 } notes { pitch: 74 velocity: 90 start_time: 2.5 end_time: 3.0 } total_time: 3.0 """) self.assertProtoEquals(expected_ns1, tunes[1]) expected_ns2 = copy.deepcopy(expected_ns1) expected_ns2.reference_number = 2 self.assertProtoEquals(expected_ns2, tunes[2]) expected_ns2.reference_number = 3 self.assertProtoEquals(expected_ns2, tunes[3])
def testNoteAccidentalsPerBar(self): tunes, exceptions = abc_parser.parse_abc_tunebook(""" X:1 Q:1/4=120 L:1/4 T:Test GF^GGg|Gg """) self.assertLen(tunes, 1) self.assertEmpty(exceptions) expected_ns1 = testing_lib.parse_test_proto( music_pb2.NoteSequence, """ ticks_per_quarter: 220 source_info: { source_type: SCORE_BASED encoding_type: ABC parser: MAGENTA_ABC } reference_number: 1 sequence_metadata { title: "Test" } tempos { qpm: 120 } notes { pitch: 67 velocity: 90 start_time: 0.0 end_time: 0.5 } notes { pitch: 65 velocity: 90 start_time: 0.5 end_time: 1.0 } notes { pitch: 68 velocity: 90 start_time: 1.0 end_time: 1.5 } notes { pitch: 68 velocity: 90 start_time: 1.5 end_time: 2.0 } notes { pitch: 80 velocity: 90 start_time: 2.0 end_time: 2.5 } notes { pitch: 67 velocity: 90 start_time: 2.5 end_time: 3.0 } notes { pitch: 79 velocity: 90 start_time: 3.0 end_time: 3.5 } total_time: 3.5 """) self.assertProtoEquals(expected_ns1, tunes[1])