Module note_seq.abc_parser_test

Tests for abc_parser.

Expand source code
# Copyright 2021 The Magenta Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Tests for abc_parser."""

import copy
import os.path

from absl.testing import absltest
from note_seq import abc_parser
from note_seq import midi_io
from note_seq import sequences_lib
from note_seq import testing_lib
from note_seq.protobuf import music_pb2


class AbcParserTest(testing_lib.ProtoTestCase):

  def compare_accidentals(self, expected, accidentals):
    self.assertCountEqual(expected, accidentals.values())

  def compare_proto_list(self, expected, test):
    self.assertEqual(len(expected), len(test))
    for e, t in zip(expected, test):
      self.assertProtoEquals(e, t)

  def compare_to_abc2midi_and_metadata(
      self, midi_path, expected_metadata, expected_expanded_metadata, test):
    """Compare parsing results to the abc2midi "reference" implementation."""
    # Compare section annotations and groups before expanding.
    self.compare_proto_list(expected_metadata.section_annotations,
                            test.section_annotations)
    self.compare_proto_list(expected_metadata.section_groups,
                            test.section_groups)

    expanded_test = sequences_lib.expand_section_groups(test)

    abc2midi = midi_io.midi_file_to_sequence_proto(
        os.path.join(testing_lib.get_testdata_dir(), midi_path))

    # abc2midi adds a 1-tick delay to the start of every note, but we don't.
    tick_length = ((1 / (abc2midi.tempos[0].qpm / 60)) /
                   abc2midi.ticks_per_quarter)

    for note in abc2midi.notes:
      # For now, don't compare velocities.
      note.velocity = 90
      note.start_time -= tick_length

    self.compare_proto_list(abc2midi.notes, expanded_test.notes)

    self.assertEqual(abc2midi.total_time, expanded_test.total_time)

    self.compare_proto_list(abc2midi.time_signatures,
                            expanded_test.time_signatures)

    # We've checked the notes and time signatures, now compare the rest of the
    # proto to the expected proto.
    expanded_test_copy = copy.deepcopy(expanded_test)
    del expanded_test_copy.notes[:]
    expanded_test_copy.ClearField('total_time')
    del expanded_test_copy.time_signatures[:]

    self.assertProtoEquals(expected_expanded_metadata, expanded_test_copy)

  def testParseKeyBasic(self):
    # Most examples taken from
    # http://abcnotation.com/wiki/abc:standard:v2.1#kkey
    accidentals, proto_key, proto_mode = abc_parser.ABCTune.parse_key('C major')
    self.compare_accidentals([0, 0, 0, 0, 0, 0, 0], accidentals)
    self.assertEqual(music_pb2.NoteSequence.KeySignature.C, proto_key)
    self.assertEqual(music_pb2.NoteSequence.KeySignature.MAJOR, proto_mode)

    accidentals, proto_key, proto_mode = abc_parser.ABCTune.parse_key('A minor')
    self.compare_accidentals([0, 0, 0, 0, 0, 0, 0], accidentals)
    self.assertEqual(music_pb2.NoteSequence.KeySignature.A, proto_key)
    self.assertEqual(music_pb2.NoteSequence.KeySignature.MINOR, proto_mode)

    accidentals, proto_key, proto_mode = abc_parser.ABCTune.parse_key(
        'C ionian')
    self.compare_accidentals([0, 0, 0, 0, 0, 0, 0], accidentals)
    self.assertEqual(music_pb2.NoteSequence.KeySignature.C, proto_key)
    self.assertEqual(music_pb2.NoteSequence.KeySignature.MAJOR, proto_mode)

    accidentals, proto_key, proto_mode = abc_parser.ABCTune.parse_key(
        'A aeolian')
    self.compare_accidentals([0, 0, 0, 0, 0, 0, 0], accidentals)
    self.assertEqual(music_pb2.NoteSequence.KeySignature.A, proto_key)
    self.assertEqual(music_pb2.NoteSequence.KeySignature.MINOR, proto_mode)

    accidentals, proto_key, proto_mode = abc_parser.ABCTune.parse_key(
        'G Mixolydian')
    self.compare_accidentals([0, 0, 0, 0, 0, 0, 0], accidentals)
    self.assertEqual(music_pb2.NoteSequence.KeySignature.G, proto_key)
    self.assertEqual(music_pb2.NoteSequence.KeySignature.MIXOLYDIAN,
                     proto_mode)

    accidentals, proto_key, proto_mode = abc_parser.ABCTune.parse_key(
        'D dorian')
    self.compare_accidentals([0, 0, 0, 0, 0, 0, 0], accidentals)
    self.assertEqual(music_pb2.NoteSequence.KeySignature.D, proto_key)
    self.assertEqual(music_pb2.NoteSequence.KeySignature.DORIAN,
                     proto_mode)

    accidentals, proto_key, proto_mode = abc_parser.ABCTune.parse_key(
        'E phrygian')
    self.compare_accidentals([0, 0, 0, 0, 0, 0, 0], accidentals)
    self.assertEqual(music_pb2.NoteSequence.KeySignature.E, proto_key)
    self.assertEqual(music_pb2.NoteSequence.KeySignature.PHRYGIAN,
                     proto_mode)

    accidentals, proto_key, proto_mode = abc_parser.ABCTune.parse_key(
        'F Lydian')
    self.compare_accidentals([0, 0, 0, 0, 0, 0, 0], accidentals)
    self.assertEqual(music_pb2.NoteSequence.KeySignature.F, proto_key)
    self.assertEqual(music_pb2.NoteSequence.KeySignature.LYDIAN,
                     proto_mode)

    accidentals, proto_key, proto_mode = abc_parser.ABCTune.parse_key(
        'B Locrian')
    self.compare_accidentals([0, 0, 0, 0, 0, 0, 0], accidentals)
    self.assertEqual(music_pb2.NoteSequence.KeySignature.B, proto_key)
    self.assertEqual(music_pb2.NoteSequence.KeySignature.LOCRIAN,
                     proto_mode)

    accidentals, proto_key, proto_mode = abc_parser.ABCTune.parse_key(
        'F# mixolydian')
    self.compare_accidentals([1, 0, 1, 1, 0, 1, 1], accidentals)
    self.assertEqual(music_pb2.NoteSequence.KeySignature.F_SHARP, proto_key)
    self.assertEqual(music_pb2.NoteSequence.KeySignature.MIXOLYDIAN,
                     proto_mode)

    accidentals, proto_key, proto_mode = abc_parser.ABCTune.parse_key(
        'F#Mix')
    self.compare_accidentals([1, 0, 1, 1, 0, 1, 1], accidentals)
    self.assertEqual(music_pb2.NoteSequence.KeySignature.F_SHARP, proto_key)
    self.assertEqual(music_pb2.NoteSequence.KeySignature.MIXOLYDIAN,
                     proto_mode)

    accidentals, proto_key, proto_mode = abc_parser.ABCTune.parse_key(
        'F#MIX')
    self.compare_accidentals([1, 0, 1, 1, 0, 1, 1], accidentals)
    self.assertEqual(music_pb2.NoteSequence.KeySignature.F_SHARP, proto_key)
    self.assertEqual(music_pb2.NoteSequence.KeySignature.MIXOLYDIAN,
                     proto_mode)

    accidentals, proto_key, proto_mode = abc_parser.ABCTune.parse_key(
        'Fm')
    self.compare_accidentals([-1, -1, 0, -1, -1, 0, 0], accidentals)
    self.assertEqual(music_pb2.NoteSequence.KeySignature.F, proto_key)
    self.assertEqual(music_pb2.NoteSequence.KeySignature.MINOR, proto_mode)

  def testParseKeyExplicit(self):
    # Most examples taken from
    # http://abcnotation.com/wiki/abc:standard:v2.1#kkey
    accidentals, proto_key, proto_mode = abc_parser.ABCTune.parse_key(
        'D exp _b _e ^f')
    self.compare_accidentals([0, -1, 0, 0, -1, 1, 0], accidentals)
    self.assertEqual(music_pb2.NoteSequence.KeySignature.D, proto_key)
    self.assertEqual(music_pb2.NoteSequence.KeySignature.MAJOR, proto_mode)

  def testParseKeyAccidentals(self):
    # Most examples taken from
    # http://abcnotation.com/wiki/abc:standard:v2.1#kkey
    accidentals, proto_key, proto_mode = abc_parser.ABCTune.parse_key(
        'D Phr ^f')
    self.compare_accidentals([0, -1, 0, 0, -1, 1, 0], accidentals)
    self.assertEqual(music_pb2.NoteSequence.KeySignature.D, proto_key)
    self.assertEqual(music_pb2.NoteSequence.KeySignature.PHRYGIAN,
                     proto_mode)

    accidentals, proto_key, proto_mode = abc_parser.ABCTune.parse_key(
        'D maj =c')
    self.compare_accidentals([0, 0, 0, 0, 0, 1, 0], accidentals)
    self.assertEqual(music_pb2.NoteSequence.KeySignature.D, proto_key)
    self.assertEqual(music_pb2.NoteSequence.KeySignature.MAJOR, proto_mode)

    accidentals, proto_key, proto_mode = abc_parser.ABCTune.parse_key(
        'D =c')
    self.compare_accidentals([0, 0, 0, 0, 0, 1, 0], accidentals)
    self.assertEqual(music_pb2.NoteSequence.KeySignature.D, proto_key)
    self.assertEqual(music_pb2.NoteSequence.KeySignature.MAJOR, proto_mode)

  def testParseEnglishAbc(self):
    tunes, exceptions = abc_parser.parse_abc_tunebook_file(
        os.path.join(testing_lib.get_testdata_dir(), 'english.abc'))
    self.assertLen(tunes, 1)
    self.assertLen(exceptions, 2)
    self.assertIsInstance(exceptions[0], abc_parser.VariantEndingError)
    self.assertIsInstance(exceptions[1], abc_parser.PartError)

    expected_metadata1 = 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: "Dusty Miller, The; Binny's Jig"
          artist: "Trad."
          composers: "Trad."
        }
        key_signatures {
          key: G
        }
        section_annotations {
          time: 0.0
          section_id: 0
        }
        section_annotations {
          time: 6.0
          section_id: 1
        }
        section_annotations {
          time: 12.0
          section_id: 2
        }
        section_groups {
          sections {
            section_id: 0
          }
          num_times: 2
        }
        section_groups {
          sections {
            section_id: 1
          }
          num_times: 2
        }
        section_groups {
          sections {
            section_id: 2
          }
          num_times: 2
        }
        """)
    expected_expanded_metadata1 = 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: "Dusty Miller, The; Binny's Jig"
          artist: "Trad."
          composers: "Trad."
        }
        key_signatures {
          key: G
        }
        section_annotations {
          time: 0.0
          section_id: 0
        }
        section_annotations {
          time: 6.0
          section_id: 0
        }
        section_annotations {
          time: 12.0
          section_id: 1
        }
        section_annotations {
          time: 18.0
          section_id: 1
        }
        section_annotations {
          time: 24.0
          section_id: 2
        }
        section_annotations {
          time: 30.0
          section_id: 2
        }
        """)
    self.compare_to_abc2midi_and_metadata(
        'english1.mid', expected_metadata1,
        expected_expanded_metadata1, tunes[1])

    # TODO(fjord): re-enable once we support variant endings.
    # expected_ns2_metadata = 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: 2
    #     sequence_metadata {
    #       title: "Old Sir Simon the King"
    #       artist: "Trad."
    #       composers: "Trad."
    #     }
    #     key_signatures {
    #       key: G
    #     }
    #     """)
    # self.compare_to_abc2midi_and_metadata(
    #     'english2.mid', expected_ns2_metadata, tunes[1])

    # TODO(fjord): re-enable once we support parts.
    # expected_ns3_metadata = 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: 3
    #     sequence_metadata {
    #       title: "William and Nancy; New Mown Hay; Legacy, The"
    #       artist: "Trad."
    #       composers: "Trad."
    #     }
    #     key_signatures {
    #       key: G
    #     }
    #     """)
    # # TODO(fjord): verify chord annotations
    # del tunes[3].text_annotations[:]
    # self.compare_to_abc2midi_and_metadata(
    #     'english3.mid', expected_ns3_metadata, tunes[3])

  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 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 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 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 testMultiVoice(self):
    tunes, exceptions = abc_parser.parse_abc_tunebook_file(
        os.path.join(testing_lib.get_testdata_dir(),
                     'zocharti_loch.abc'))
    self.assertEmpty(tunes)
    self.assertLen(exceptions, 1)
    self.assertIsInstance(exceptions[0], abc_parser.MultiVoiceError)

  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 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 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 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 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 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])

  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 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 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 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 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)

if __name__ == '__main__':
  absltest.main()

Classes

class AbcParserTest (*args, **kwargs)

Adds assertProtoEquals from tf.test.TestCase.

Create an instance of the class that will use the named test method when executed. Raises a ValueError if the instance does not have a method with the specified name.

Expand source code
class AbcParserTest(testing_lib.ProtoTestCase):

  def compare_accidentals(self, expected, accidentals):
    self.assertCountEqual(expected, accidentals.values())

  def compare_proto_list(self, expected, test):
    self.assertEqual(len(expected), len(test))
    for e, t in zip(expected, test):
      self.assertProtoEquals(e, t)

  def compare_to_abc2midi_and_metadata(
      self, midi_path, expected_metadata, expected_expanded_metadata, test):
    """Compare parsing results to the abc2midi "reference" implementation."""
    # Compare section annotations and groups before expanding.
    self.compare_proto_list(expected_metadata.section_annotations,
                            test.section_annotations)
    self.compare_proto_list(expected_metadata.section_groups,
                            test.section_groups)

    expanded_test = sequences_lib.expand_section_groups(test)

    abc2midi = midi_io.midi_file_to_sequence_proto(
        os.path.join(testing_lib.get_testdata_dir(), midi_path))

    # abc2midi adds a 1-tick delay to the start of every note, but we don't.
    tick_length = ((1 / (abc2midi.tempos[0].qpm / 60)) /
                   abc2midi.ticks_per_quarter)

    for note in abc2midi.notes:
      # For now, don't compare velocities.
      note.velocity = 90
      note.start_time -= tick_length

    self.compare_proto_list(abc2midi.notes, expanded_test.notes)

    self.assertEqual(abc2midi.total_time, expanded_test.total_time)

    self.compare_proto_list(abc2midi.time_signatures,
                            expanded_test.time_signatures)

    # We've checked the notes and time signatures, now compare the rest of the
    # proto to the expected proto.
    expanded_test_copy = copy.deepcopy(expanded_test)
    del expanded_test_copy.notes[:]
    expanded_test_copy.ClearField('total_time')
    del expanded_test_copy.time_signatures[:]

    self.assertProtoEquals(expected_expanded_metadata, expanded_test_copy)

  def testParseKeyBasic(self):
    # Most examples taken from
    # http://abcnotation.com/wiki/abc:standard:v2.1#kkey
    accidentals, proto_key, proto_mode = abc_parser.ABCTune.parse_key('C major')
    self.compare_accidentals([0, 0, 0, 0, 0, 0, 0], accidentals)
    self.assertEqual(music_pb2.NoteSequence.KeySignature.C, proto_key)
    self.assertEqual(music_pb2.NoteSequence.KeySignature.MAJOR, proto_mode)

    accidentals, proto_key, proto_mode = abc_parser.ABCTune.parse_key('A minor')
    self.compare_accidentals([0, 0, 0, 0, 0, 0, 0], accidentals)
    self.assertEqual(music_pb2.NoteSequence.KeySignature.A, proto_key)
    self.assertEqual(music_pb2.NoteSequence.KeySignature.MINOR, proto_mode)

    accidentals, proto_key, proto_mode = abc_parser.ABCTune.parse_key(
        'C ionian')
    self.compare_accidentals([0, 0, 0, 0, 0, 0, 0], accidentals)
    self.assertEqual(music_pb2.NoteSequence.KeySignature.C, proto_key)
    self.assertEqual(music_pb2.NoteSequence.KeySignature.MAJOR, proto_mode)

    accidentals, proto_key, proto_mode = abc_parser.ABCTune.parse_key(
        'A aeolian')
    self.compare_accidentals([0, 0, 0, 0, 0, 0, 0], accidentals)
    self.assertEqual(music_pb2.NoteSequence.KeySignature.A, proto_key)
    self.assertEqual(music_pb2.NoteSequence.KeySignature.MINOR, proto_mode)

    accidentals, proto_key, proto_mode = abc_parser.ABCTune.parse_key(
        'G Mixolydian')
    self.compare_accidentals([0, 0, 0, 0, 0, 0, 0], accidentals)
    self.assertEqual(music_pb2.NoteSequence.KeySignature.G, proto_key)
    self.assertEqual(music_pb2.NoteSequence.KeySignature.MIXOLYDIAN,
                     proto_mode)

    accidentals, proto_key, proto_mode = abc_parser.ABCTune.parse_key(
        'D dorian')
    self.compare_accidentals([0, 0, 0, 0, 0, 0, 0], accidentals)
    self.assertEqual(music_pb2.NoteSequence.KeySignature.D, proto_key)
    self.assertEqual(music_pb2.NoteSequence.KeySignature.DORIAN,
                     proto_mode)

    accidentals, proto_key, proto_mode = abc_parser.ABCTune.parse_key(
        'E phrygian')
    self.compare_accidentals([0, 0, 0, 0, 0, 0, 0], accidentals)
    self.assertEqual(music_pb2.NoteSequence.KeySignature.E, proto_key)
    self.assertEqual(music_pb2.NoteSequence.KeySignature.PHRYGIAN,
                     proto_mode)

    accidentals, proto_key, proto_mode = abc_parser.ABCTune.parse_key(
        'F Lydian')
    self.compare_accidentals([0, 0, 0, 0, 0, 0, 0], accidentals)
    self.assertEqual(music_pb2.NoteSequence.KeySignature.F, proto_key)
    self.assertEqual(music_pb2.NoteSequence.KeySignature.LYDIAN,
                     proto_mode)

    accidentals, proto_key, proto_mode = abc_parser.ABCTune.parse_key(
        'B Locrian')
    self.compare_accidentals([0, 0, 0, 0, 0, 0, 0], accidentals)
    self.assertEqual(music_pb2.NoteSequence.KeySignature.B, proto_key)
    self.assertEqual(music_pb2.NoteSequence.KeySignature.LOCRIAN,
                     proto_mode)

    accidentals, proto_key, proto_mode = abc_parser.ABCTune.parse_key(
        'F# mixolydian')
    self.compare_accidentals([1, 0, 1, 1, 0, 1, 1], accidentals)
    self.assertEqual(music_pb2.NoteSequence.KeySignature.F_SHARP, proto_key)
    self.assertEqual(music_pb2.NoteSequence.KeySignature.MIXOLYDIAN,
                     proto_mode)

    accidentals, proto_key, proto_mode = abc_parser.ABCTune.parse_key(
        'F#Mix')
    self.compare_accidentals([1, 0, 1, 1, 0, 1, 1], accidentals)
    self.assertEqual(music_pb2.NoteSequence.KeySignature.F_SHARP, proto_key)
    self.assertEqual(music_pb2.NoteSequence.KeySignature.MIXOLYDIAN,
                     proto_mode)

    accidentals, proto_key, proto_mode = abc_parser.ABCTune.parse_key(
        'F#MIX')
    self.compare_accidentals([1, 0, 1, 1, 0, 1, 1], accidentals)
    self.assertEqual(music_pb2.NoteSequence.KeySignature.F_SHARP, proto_key)
    self.assertEqual(music_pb2.NoteSequence.KeySignature.MIXOLYDIAN,
                     proto_mode)

    accidentals, proto_key, proto_mode = abc_parser.ABCTune.parse_key(
        'Fm')
    self.compare_accidentals([-1, -1, 0, -1, -1, 0, 0], accidentals)
    self.assertEqual(music_pb2.NoteSequence.KeySignature.F, proto_key)
    self.assertEqual(music_pb2.NoteSequence.KeySignature.MINOR, proto_mode)

  def testParseKeyExplicit(self):
    # Most examples taken from
    # http://abcnotation.com/wiki/abc:standard:v2.1#kkey
    accidentals, proto_key, proto_mode = abc_parser.ABCTune.parse_key(
        'D exp _b _e ^f')
    self.compare_accidentals([0, -1, 0, 0, -1, 1, 0], accidentals)
    self.assertEqual(music_pb2.NoteSequence.KeySignature.D, proto_key)
    self.assertEqual(music_pb2.NoteSequence.KeySignature.MAJOR, proto_mode)

  def testParseKeyAccidentals(self):
    # Most examples taken from
    # http://abcnotation.com/wiki/abc:standard:v2.1#kkey
    accidentals, proto_key, proto_mode = abc_parser.ABCTune.parse_key(
        'D Phr ^f')
    self.compare_accidentals([0, -1, 0, 0, -1, 1, 0], accidentals)
    self.assertEqual(music_pb2.NoteSequence.KeySignature.D, proto_key)
    self.assertEqual(music_pb2.NoteSequence.KeySignature.PHRYGIAN,
                     proto_mode)

    accidentals, proto_key, proto_mode = abc_parser.ABCTune.parse_key(
        'D maj =c')
    self.compare_accidentals([0, 0, 0, 0, 0, 1, 0], accidentals)
    self.assertEqual(music_pb2.NoteSequence.KeySignature.D, proto_key)
    self.assertEqual(music_pb2.NoteSequence.KeySignature.MAJOR, proto_mode)

    accidentals, proto_key, proto_mode = abc_parser.ABCTune.parse_key(
        'D =c')
    self.compare_accidentals([0, 0, 0, 0, 0, 1, 0], accidentals)
    self.assertEqual(music_pb2.NoteSequence.KeySignature.D, proto_key)
    self.assertEqual(music_pb2.NoteSequence.KeySignature.MAJOR, proto_mode)

  def testParseEnglishAbc(self):
    tunes, exceptions = abc_parser.parse_abc_tunebook_file(
        os.path.join(testing_lib.get_testdata_dir(), 'english.abc'))
    self.assertLen(tunes, 1)
    self.assertLen(exceptions, 2)
    self.assertIsInstance(exceptions[0], abc_parser.VariantEndingError)
    self.assertIsInstance(exceptions[1], abc_parser.PartError)

    expected_metadata1 = 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: "Dusty Miller, The; Binny's Jig"
          artist: "Trad."
          composers: "Trad."
        }
        key_signatures {
          key: G
        }
        section_annotations {
          time: 0.0
          section_id: 0
        }
        section_annotations {
          time: 6.0
          section_id: 1
        }
        section_annotations {
          time: 12.0
          section_id: 2
        }
        section_groups {
          sections {
            section_id: 0
          }
          num_times: 2
        }
        section_groups {
          sections {
            section_id: 1
          }
          num_times: 2
        }
        section_groups {
          sections {
            section_id: 2
          }
          num_times: 2
        }
        """)
    expected_expanded_metadata1 = 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: "Dusty Miller, The; Binny's Jig"
          artist: "Trad."
          composers: "Trad."
        }
        key_signatures {
          key: G
        }
        section_annotations {
          time: 0.0
          section_id: 0
        }
        section_annotations {
          time: 6.0
          section_id: 0
        }
        section_annotations {
          time: 12.0
          section_id: 1
        }
        section_annotations {
          time: 18.0
          section_id: 1
        }
        section_annotations {
          time: 24.0
          section_id: 2
        }
        section_annotations {
          time: 30.0
          section_id: 2
        }
        """)
    self.compare_to_abc2midi_and_metadata(
        'english1.mid', expected_metadata1,
        expected_expanded_metadata1, tunes[1])

    # TODO(fjord): re-enable once we support variant endings.
    # expected_ns2_metadata = 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: 2
    #     sequence_metadata {
    #       title: "Old Sir Simon the King"
    #       artist: "Trad."
    #       composers: "Trad."
    #     }
    #     key_signatures {
    #       key: G
    #     }
    #     """)
    # self.compare_to_abc2midi_and_metadata(
    #     'english2.mid', expected_ns2_metadata, tunes[1])

    # TODO(fjord): re-enable once we support parts.
    # expected_ns3_metadata = 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: 3
    #     sequence_metadata {
    #       title: "William and Nancy; New Mown Hay; Legacy, The"
    #       artist: "Trad."
    #       composers: "Trad."
    #     }
    #     key_signatures {
    #       key: G
    #     }
    #     """)
    # # TODO(fjord): verify chord annotations
    # del tunes[3].text_annotations[:]
    # self.compare_to_abc2midi_and_metadata(
    #     'english3.mid', expected_ns3_metadata, tunes[3])

  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 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 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 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 testMultiVoice(self):
    tunes, exceptions = abc_parser.parse_abc_tunebook_file(
        os.path.join(testing_lib.get_testdata_dir(),
                     'zocharti_loch.abc'))
    self.assertEmpty(tunes)
    self.assertLen(exceptions, 1)
    self.assertIsInstance(exceptions[0], abc_parser.MultiVoiceError)

  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 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 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 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 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 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])

  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 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 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 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 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)

Ancestors

  • ProtoTestCase
  • absl.testing.absltest.TestCase
  • absl.third_party.unittest3_backport.case.TestCase
  • unittest.case.TestCase

Methods

def compare_accidentals(self, expected, accidentals)
Expand source code
def compare_accidentals(self, expected, accidentals):
  self.assertCountEqual(expected, accidentals.values())
def compare_proto_list(self, expected, test)
Expand source code
def compare_proto_list(self, expected, test):
  self.assertEqual(len(expected), len(test))
  for e, t in zip(expected, test):
    self.assertProtoEquals(e, t)
def compare_to_abc2midi_and_metadata(self, midi_path, expected_metadata, expected_expanded_metadata, test)

Compare parsing results to the abc2midi "reference" implementation.

Expand source code
def compare_to_abc2midi_and_metadata(
    self, midi_path, expected_metadata, expected_expanded_metadata, test):
  """Compare parsing results to the abc2midi "reference" implementation."""
  # Compare section annotations and groups before expanding.
  self.compare_proto_list(expected_metadata.section_annotations,
                          test.section_annotations)
  self.compare_proto_list(expected_metadata.section_groups,
                          test.section_groups)

  expanded_test = sequences_lib.expand_section_groups(test)

  abc2midi = midi_io.midi_file_to_sequence_proto(
      os.path.join(testing_lib.get_testdata_dir(), midi_path))

  # abc2midi adds a 1-tick delay to the start of every note, but we don't.
  tick_length = ((1 / (abc2midi.tempos[0].qpm / 60)) /
                 abc2midi.ticks_per_quarter)

  for note in abc2midi.notes:
    # For now, don't compare velocities.
    note.velocity = 90
    note.start_time -= tick_length

  self.compare_proto_list(abc2midi.notes, expanded_test.notes)

  self.assertEqual(abc2midi.total_time, expanded_test.total_time)

  self.compare_proto_list(abc2midi.time_signatures,
                          expanded_test.time_signatures)

  # We've checked the notes and time signatures, now compare the rest of the
  # proto to the expected proto.
  expanded_test_copy = copy.deepcopy(expanded_test)
  del expanded_test_copy.notes[:]
  expanded_test_copy.ClearField('total_time')
  del expanded_test_copy.time_signatures[:]

  self.assertProtoEquals(expected_expanded_metadata, expanded_test_copy)
def testChordAnnotations(self)
Expand source code
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 testChords(self)
Expand source code
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 testDecorations(self)
Expand source code
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 testInvalidCharacter(self)
Expand source code
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 testLineContinuation(self)
Expand source code
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 testMultiVoice(self)
Expand source code
def testMultiVoice(self):
  tunes, exceptions = abc_parser.parse_abc_tunebook_file(
      os.path.join(testing_lib.get_testdata_dir(),
                   'zocharti_loch.abc'))
  self.assertEmpty(tunes)
  self.assertLen(exceptions, 1)
  self.assertIsInstance(exceptions[0], abc_parser.MultiVoiceError)
def testNoteAccidentalsPerBar(self)
Expand source code
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])
def testOneSidedRepeat(self)
Expand source code
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 testParseBrokenRhythm(self)
Expand source code
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 testParseEnglishAbc(self)
Expand source code
def testParseEnglishAbc(self):
  tunes, exceptions = abc_parser.parse_abc_tunebook_file(
      os.path.join(testing_lib.get_testdata_dir(), 'english.abc'))
  self.assertLen(tunes, 1)
  self.assertLen(exceptions, 2)
  self.assertIsInstance(exceptions[0], abc_parser.VariantEndingError)
  self.assertIsInstance(exceptions[1], abc_parser.PartError)

  expected_metadata1 = 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: "Dusty Miller, The; Binny's Jig"
        artist: "Trad."
        composers: "Trad."
      }
      key_signatures {
        key: G
      }
      section_annotations {
        time: 0.0
        section_id: 0
      }
      section_annotations {
        time: 6.0
        section_id: 1
      }
      section_annotations {
        time: 12.0
        section_id: 2
      }
      section_groups {
        sections {
          section_id: 0
        }
        num_times: 2
      }
      section_groups {
        sections {
          section_id: 1
        }
        num_times: 2
      }
      section_groups {
        sections {
          section_id: 2
        }
        num_times: 2
      }
      """)
  expected_expanded_metadata1 = 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: "Dusty Miller, The; Binny's Jig"
        artist: "Trad."
        composers: "Trad."
      }
      key_signatures {
        key: G
      }
      section_annotations {
        time: 0.0
        section_id: 0
      }
      section_annotations {
        time: 6.0
        section_id: 0
      }
      section_annotations {
        time: 12.0
        section_id: 1
      }
      section_annotations {
        time: 18.0
        section_id: 1
      }
      section_annotations {
        time: 24.0
        section_id: 2
      }
      section_annotations {
        time: 30.0
        section_id: 2
      }
      """)
  self.compare_to_abc2midi_and_metadata(
      'english1.mid', expected_metadata1,
      expected_expanded_metadata1, tunes[1])

  # TODO(fjord): re-enable once we support variant endings.
  # expected_ns2_metadata = 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: 2
  #     sequence_metadata {
  #       title: "Old Sir Simon the King"
  #       artist: "Trad."
  #       composers: "Trad."
  #     }
  #     key_signatures {
  #       key: G
  #     }
  #     """)
  # self.compare_to_abc2midi_and_metadata(
  #     'english2.mid', expected_ns2_metadata, tunes[1])

  # TODO(fjord): re-enable once we support parts.
  # expected_ns3_metadata = 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: 3
  #     sequence_metadata {
  #       title: "William and Nancy; New Mown Hay; Legacy, The"
  #       artist: "Trad."
  #       composers: "Trad."
  #     }
  #     key_signatures {
  #       key: G
  #     }
  #     """)
  # # TODO(fjord): verify chord annotations
  # del tunes[3].text_annotations[:]
  # self.compare_to_abc2midi_and_metadata(
  #     'english3.mid', expected_ns3_metadata, tunes[3])
def testParseKeyAccidentals(self)
Expand source code
def testParseKeyAccidentals(self):
  # Most examples taken from
  # http://abcnotation.com/wiki/abc:standard:v2.1#kkey
  accidentals, proto_key, proto_mode = abc_parser.ABCTune.parse_key(
      'D Phr ^f')
  self.compare_accidentals([0, -1, 0, 0, -1, 1, 0], accidentals)
  self.assertEqual(music_pb2.NoteSequence.KeySignature.D, proto_key)
  self.assertEqual(music_pb2.NoteSequence.KeySignature.PHRYGIAN,
                   proto_mode)

  accidentals, proto_key, proto_mode = abc_parser.ABCTune.parse_key(
      'D maj =c')
  self.compare_accidentals([0, 0, 0, 0, 0, 1, 0], accidentals)
  self.assertEqual(music_pb2.NoteSequence.KeySignature.D, proto_key)
  self.assertEqual(music_pb2.NoteSequence.KeySignature.MAJOR, proto_mode)

  accidentals, proto_key, proto_mode = abc_parser.ABCTune.parse_key(
      'D =c')
  self.compare_accidentals([0, 0, 0, 0, 0, 1, 0], accidentals)
  self.assertEqual(music_pb2.NoteSequence.KeySignature.D, proto_key)
  self.assertEqual(music_pb2.NoteSequence.KeySignature.MAJOR, proto_mode)
def testParseKeyBasic(self)
Expand source code
def testParseKeyBasic(self):
  # Most examples taken from
  # http://abcnotation.com/wiki/abc:standard:v2.1#kkey
  accidentals, proto_key, proto_mode = abc_parser.ABCTune.parse_key('C major')
  self.compare_accidentals([0, 0, 0, 0, 0, 0, 0], accidentals)
  self.assertEqual(music_pb2.NoteSequence.KeySignature.C, proto_key)
  self.assertEqual(music_pb2.NoteSequence.KeySignature.MAJOR, proto_mode)

  accidentals, proto_key, proto_mode = abc_parser.ABCTune.parse_key('A minor')
  self.compare_accidentals([0, 0, 0, 0, 0, 0, 0], accidentals)
  self.assertEqual(music_pb2.NoteSequence.KeySignature.A, proto_key)
  self.assertEqual(music_pb2.NoteSequence.KeySignature.MINOR, proto_mode)

  accidentals, proto_key, proto_mode = abc_parser.ABCTune.parse_key(
      'C ionian')
  self.compare_accidentals([0, 0, 0, 0, 0, 0, 0], accidentals)
  self.assertEqual(music_pb2.NoteSequence.KeySignature.C, proto_key)
  self.assertEqual(music_pb2.NoteSequence.KeySignature.MAJOR, proto_mode)

  accidentals, proto_key, proto_mode = abc_parser.ABCTune.parse_key(
      'A aeolian')
  self.compare_accidentals([0, 0, 0, 0, 0, 0, 0], accidentals)
  self.assertEqual(music_pb2.NoteSequence.KeySignature.A, proto_key)
  self.assertEqual(music_pb2.NoteSequence.KeySignature.MINOR, proto_mode)

  accidentals, proto_key, proto_mode = abc_parser.ABCTune.parse_key(
      'G Mixolydian')
  self.compare_accidentals([0, 0, 0, 0, 0, 0, 0], accidentals)
  self.assertEqual(music_pb2.NoteSequence.KeySignature.G, proto_key)
  self.assertEqual(music_pb2.NoteSequence.KeySignature.MIXOLYDIAN,
                   proto_mode)

  accidentals, proto_key, proto_mode = abc_parser.ABCTune.parse_key(
      'D dorian')
  self.compare_accidentals([0, 0, 0, 0, 0, 0, 0], accidentals)
  self.assertEqual(music_pb2.NoteSequence.KeySignature.D, proto_key)
  self.assertEqual(music_pb2.NoteSequence.KeySignature.DORIAN,
                   proto_mode)

  accidentals, proto_key, proto_mode = abc_parser.ABCTune.parse_key(
      'E phrygian')
  self.compare_accidentals([0, 0, 0, 0, 0, 0, 0], accidentals)
  self.assertEqual(music_pb2.NoteSequence.KeySignature.E, proto_key)
  self.assertEqual(music_pb2.NoteSequence.KeySignature.PHRYGIAN,
                   proto_mode)

  accidentals, proto_key, proto_mode = abc_parser.ABCTune.parse_key(
      'F Lydian')
  self.compare_accidentals([0, 0, 0, 0, 0, 0, 0], accidentals)
  self.assertEqual(music_pb2.NoteSequence.KeySignature.F, proto_key)
  self.assertEqual(music_pb2.NoteSequence.KeySignature.LYDIAN,
                   proto_mode)

  accidentals, proto_key, proto_mode = abc_parser.ABCTune.parse_key(
      'B Locrian')
  self.compare_accidentals([0, 0, 0, 0, 0, 0, 0], accidentals)
  self.assertEqual(music_pb2.NoteSequence.KeySignature.B, proto_key)
  self.assertEqual(music_pb2.NoteSequence.KeySignature.LOCRIAN,
                   proto_mode)

  accidentals, proto_key, proto_mode = abc_parser.ABCTune.parse_key(
      'F# mixolydian')
  self.compare_accidentals([1, 0, 1, 1, 0, 1, 1], accidentals)
  self.assertEqual(music_pb2.NoteSequence.KeySignature.F_SHARP, proto_key)
  self.assertEqual(music_pb2.NoteSequence.KeySignature.MIXOLYDIAN,
                   proto_mode)

  accidentals, proto_key, proto_mode = abc_parser.ABCTune.parse_key(
      'F#Mix')
  self.compare_accidentals([1, 0, 1, 1, 0, 1, 1], accidentals)
  self.assertEqual(music_pb2.NoteSequence.KeySignature.F_SHARP, proto_key)
  self.assertEqual(music_pb2.NoteSequence.KeySignature.MIXOLYDIAN,
                   proto_mode)

  accidentals, proto_key, proto_mode = abc_parser.ABCTune.parse_key(
      'F#MIX')
  self.compare_accidentals([1, 0, 1, 1, 0, 1, 1], accidentals)
  self.assertEqual(music_pb2.NoteSequence.KeySignature.F_SHARP, proto_key)
  self.assertEqual(music_pb2.NoteSequence.KeySignature.MIXOLYDIAN,
                   proto_mode)

  accidentals, proto_key, proto_mode = abc_parser.ABCTune.parse_key(
      'Fm')
  self.compare_accidentals([-1, -1, 0, -1, -1, 0, 0], accidentals)
  self.assertEqual(music_pb2.NoteSequence.KeySignature.F, proto_key)
  self.assertEqual(music_pb2.NoteSequence.KeySignature.MINOR, proto_mode)
def testParseKeyExplicit(self)
Expand source code
def testParseKeyExplicit(self):
  # Most examples taken from
  # http://abcnotation.com/wiki/abc:standard:v2.1#kkey
  accidentals, proto_key, proto_mode = abc_parser.ABCTune.parse_key(
      'D exp _b _e ^f')
  self.compare_accidentals([0, -1, 0, 0, -1, 1, 0], accidentals)
  self.assertEqual(music_pb2.NoteSequence.KeySignature.D, proto_key)
  self.assertEqual(music_pb2.NoteSequence.KeySignature.MAJOR, proto_mode)
def testParseOctaves(self)
Expand source code
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 testParseTempos(self)
Expand source code
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 testRepeats(self)
Expand source code
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 testSlashDuration(self)
Expand source code
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 testSlur(self)
Expand source code
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 testTie(self)
Expand source code
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 testTuplet(self)
Expand source code
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)

Inherited members