Module note_seq.musicxml_parser_test
Test to ensure correct import of MusicXML.
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.
"""Test to ensure correct import of MusicXML."""
import collections
import operator
import os.path
import tempfile
import zipfile
from absl.testing import absltest
from note_seq import musicxml_parser
from note_seq import musicxml_reader
from note_seq import testing_lib
from note_seq.protobuf import music_pb2
# Shortcut to CHORD_SYMBOL annotation type.
CHORD_SYMBOL = music_pb2.NoteSequence.TextAnnotation.CHORD_SYMBOL
class MusicXMLParserTest(testing_lib.ProtoTestCase):
"""Class to test the MusicXML parser use cases.
self.flute_scale_filename contains an F-major scale of 8 quarter notes each
self.clarinet_scale_filename contains a F-major scale of 8 quarter notes
each appearing as written pitch. This means the key is written as
G-major but sounds as F-major. The MIDI pitch numbers must be transposed
to be input into Magenta
self.band_score_filename contains a number of instruments in written
pitch. The score has two time signatures (6/8 and 2/4) and two sounding
keys (Bb-major and Eb major). The file also contains chords and
multiple voices (see Oboe part in measure 57), as well as dynamics,
articulations, slurs, ties, hairpins, grace notes, tempo changes,
and multiple barline types (double, repeat)
self.compressed_filename contains the same content as
self.flute_scale_filename, but compressed in MXL format
self.rhythm_durations_filename contains a variety of rhythms (long, short,
dotted, tuplet, and dotted tuplet) to test the computation of rhythmic
ratios.
self.atonal_transposition_filename contains a change of instrument
from a non-transposing (Flute) to transposing (Bb Clarinet) in a score
with no key / atonal. This ensures that transposition works properly when
no key signature is found (Issue #355)
self.st_anne_filename contains a 4-voice piece written in two parts.
self.whole_measure_rest_forward_filename contains 4 measures:
Measures 1 and 2 contain whole note rests in 4/4. The first is a <note>,
the second uses a <forward>. The durations must match.
Measures 3 and 4 contain whole note rests in 2/4. The first is a <note>,
the second uses a <forward>. The durations must match.
(Issue #674).
self.meter_test_filename contains a different meter in each measure:
- 1/4 through 7/4 inclusive
- 1/8 through 12/8 inclusive
- 2/2 through 4/2 inclusive
- Common time and Cut time meters
"""
def setUp(self):
self.maxDiff = None # pylint:disable=invalid-name
self.steps_per_quarter = 4
self.flute_scale_filename = os.path.join(
testing_lib.get_testdata_dir(), 'flute_scale.xml')
self.clarinet_scale_filename = os.path.join(
testing_lib.get_testdata_dir(), 'clarinet_scale.xml')
self.band_score_filename = os.path.join(
testing_lib.get_testdata_dir(), 'el_capitan.xml')
self.compressed_filename = os.path.join(
testing_lib.get_testdata_dir(), 'flute_scale.mxl')
self.multiple_rootfile_compressed_filename = os.path.join(
testing_lib.get_testdata_dir(), 'flute_scale_with_png.mxl')
self.rhythm_durations_filename = os.path.join(
testing_lib.get_testdata_dir(), 'rhythm_durations.xml')
self.st_anne_filename = os.path.join(
testing_lib.get_testdata_dir(), 'st_anne.xml')
self.atonal_transposition_filename = os.path.join(
testing_lib.get_testdata_dir(), 'atonal_transposition_change.xml')
self.chord_symbols_filename = os.path.join(
testing_lib.get_testdata_dir(), 'chord_symbols.xml')
self.time_signature_filename = os.path.join(
testing_lib.get_testdata_dir(), 'st_anne.xml')
self.unmetered_filename = os.path.join(
testing_lib.get_testdata_dir(), 'unmetered_example.xml')
self.alternating_meter_filename = os.path.join(
testing_lib.get_testdata_dir(), 'alternating_meter.xml')
self.mid_measure_meter_filename = os.path.join(
testing_lib.get_testdata_dir(), 'mid_measure_time_signature.xml')
self.whole_measure_rest_forward_filename = os.path.join(
testing_lib.get_testdata_dir(), 'whole_measure_rest_forward.xml')
self.meter_test_filename = os.path.join(
testing_lib.get_testdata_dir(), 'meter_test.xml')
super().setUp()
def check_musicxml_and_sequence(self, musicxml, sequence_proto):
"""Compares MusicXMLDocument object against a sequence proto.
Args:
musicxml: A MusicXMLDocument object.
sequence_proto: A NoteSequence proto.
"""
# Test time signature changes.
self.assertEqual(len(musicxml.get_time_signatures()),
len(sequence_proto.time_signatures))
for musicxml_time, sequence_time in zip(musicxml.get_time_signatures(),
sequence_proto.time_signatures):
self.assertEqual(musicxml_time.numerator, sequence_time.numerator)
self.assertEqual(musicxml_time.denominator, sequence_time.denominator)
self.assertAlmostEqual(musicxml_time.time_position, sequence_time.time)
# Test key signature changes.
self.assertEqual(len(musicxml.get_key_signatures()),
len(sequence_proto.key_signatures))
for musicxml_key, sequence_key in zip(musicxml.get_key_signatures(),
sequence_proto.key_signatures):
if musicxml_key.mode == 'major':
mode = 0
elif musicxml_key.mode == 'minor':
mode = 1
# The Key enum in music.proto does NOT follow MIDI / MusicXML specs
# Convert from MIDI / MusicXML key to music.proto key
music_proto_keys = [11, 6, 1, 8, 3, 10, 5, 0, 7, 2, 9, 4, 11, 6, 1]
key = music_proto_keys[musicxml_key.key + 7]
self.assertEqual(key, sequence_key.key)
self.assertEqual(mode, sequence_key.mode)
self.assertAlmostEqual(musicxml_key.time_position, sequence_key.time)
# Test tempos.
musicxml_tempos = musicxml.get_tempos()
self.assertEqual(len(musicxml_tempos),
len(sequence_proto.tempos))
for musicxml_tempo, sequence_tempo in zip(
musicxml_tempos, sequence_proto.tempos):
self.assertAlmostEqual(musicxml_tempo.qpm, sequence_tempo.qpm)
self.assertAlmostEqual(musicxml_tempo.time_position,
sequence_tempo.time)
# Test parts/instruments.
seq_parts = collections.defaultdict(list)
for seq_note in sequence_proto.notes:
seq_parts[seq_note.part].append(seq_note)
self.assertEqual(len(musicxml.parts), len(seq_parts))
for musicxml_part, seq_part_id in zip(
musicxml.parts, sorted(seq_parts.keys())):
seq_instrument_notes = seq_parts[seq_part_id]
musicxml_notes = []
for musicxml_measure in musicxml_part.measures:
for musicxml_note in musicxml_measure.notes:
if not musicxml_note.is_rest:
musicxml_notes.append(musicxml_note)
self.assertEqual(len(musicxml_notes), len(seq_instrument_notes))
for musicxml_note, sequence_note in zip(musicxml_notes,
seq_instrument_notes):
self.assertEqual(musicxml_note.pitch[1], sequence_note.pitch)
self.assertEqual(musicxml_note.velocity, sequence_note.velocity)
self.assertAlmostEqual(musicxml_note.note_duration.time_position,
sequence_note.start_time)
self.assertAlmostEqual(musicxml_note.note_duration.time_position
+ musicxml_note.note_duration.seconds,
sequence_note.end_time)
# Check that the duration specified in the MusicXML and the
# duration float match to within +/- 1 (delta = 1)
# Delta is used because duration in MusicXML is always an integer
# For example, a 3:2 half note might have a durationfloat of 341.333
# but would have the 1/3 distributed in the MusicXML as
# 341.0, 341.0, 342.0.
# Check that (3 * 341.333) = (341 + 341 + 342) is true by checking
# that 341.0 and 342.0 are +/- 1 of 341.333
self.assertAlmostEqual(
musicxml_note.note_duration.duration,
musicxml_note.state.divisions * 4
* musicxml_note.note_duration.duration_float(),
delta=1)
def check_musicxml_to_sequence(self, filename):
"""Test the translation from MusicXML to Sequence proto."""
source_musicxml = musicxml_parser.MusicXMLDocument(filename)
sequence_proto = musicxml_reader.musicxml_to_sequence_proto(source_musicxml)
self.check_musicxml_and_sequence(source_musicxml, sequence_proto)
def check_fmajor_scale(self, filename, part_name):
"""Verify MusicXML scale file.
Verify that it contains the correct pitches (sounding pitch) and durations.
Args:
filename: file to test.
part_name: name of the part the sequence is expected to contain.
"""
expected_ns = testing_lib.parse_test_proto(
music_pb2.NoteSequence,
"""
ticks_per_quarter: 220
source_info: {
source_type: SCORE_BASED
encoding_type: MUSIC_XML
parser: MAGENTA_MUSIC_XML
}
key_signatures {
key: F
time: 0
}
time_signatures {
numerator: 4
denominator: 4
}
tempos {
qpm: 120.0
}
total_time: 4.0
""")
part_info = expected_ns.part_infos.add()
part_info.name = part_name
expected_pitches = [65, 67, 69, 70, 72, 74, 76, 77]
time = 0
for pitch in expected_pitches:
note = expected_ns.notes.add()
note.part = 0
note.voice = 1
note.pitch = pitch
note.start_time = time
time += .5
note.end_time = time
note.velocity = 64
note.numerator = 1
note.denominator = 4
# Convert MusicXML to NoteSequence
source_musicxml = musicxml_parser.MusicXMLDocument(filename)
sequence_proto = musicxml_reader.musicxml_to_sequence_proto(source_musicxml)
# Check equality
self.assertProtoEquals(expected_ns, sequence_proto)
def testsimplemusicxmltosequence(self):
"""Test the simple flute scale MusicXML file."""
self.check_musicxml_to_sequence(self.flute_scale_filename)
self.check_fmajor_scale(self.flute_scale_filename, 'Flute')
def testcomplexmusicxmltosequence(self):
"""Test the complex band score MusicXML file."""
self.check_musicxml_to_sequence(self.band_score_filename)
def testtransposedxmltosequence(self):
"""Test the translation from transposed MusicXML to Sequence proto.
Compare a transposed MusicXML file (clarinet) to an identical untransposed
sequence (flute).
"""
untransposed_musicxml = musicxml_parser.MusicXMLDocument(
self.flute_scale_filename)
transposed_musicxml = musicxml_parser.MusicXMLDocument(
self.clarinet_scale_filename)
untransposed_proto = musicxml_reader.musicxml_to_sequence_proto(
untransposed_musicxml)
self.check_musicxml_and_sequence(transposed_musicxml, untransposed_proto)
self.check_fmajor_scale(self.clarinet_scale_filename, 'Clarinet in Bb')
def testcompressedmxlunicodefilename(self):
"""Test an MXL file containing a unicode filename within its zip archive."""
unicode_filename = os.path.join(
testing_lib.get_testdata_dir(), 'unicode_filename.mxl')
sequence = musicxml_reader.musicxml_file_to_sequence_proto(unicode_filename)
self.assertLen(sequence.notes, 8)
def testcompressedxmltosequence(self):
"""Test the translation from compressed MusicXML to Sequence proto.
Compare a compressed MusicXML file to an identical uncompressed sequence.
"""
uncompressed_musicxml = musicxml_parser.MusicXMLDocument(
self.flute_scale_filename)
compressed_musicxml = musicxml_parser.MusicXMLDocument(
self.compressed_filename)
uncompressed_proto = musicxml_reader.musicxml_to_sequence_proto(
uncompressed_musicxml)
self.check_musicxml_and_sequence(compressed_musicxml, uncompressed_proto)
self.check_fmajor_scale(self.flute_scale_filename, 'Flute')
def testmultiplecompressedxmltosequence(self):
"""Test the translation from compressed MusicXML with multiple rootfiles.
The example MXL file contains a MusicXML file of the Flute F Major scale,
as well as the PNG rendering of the score contained within the single MXL
file.
"""
uncompressed_musicxml = musicxml_parser.MusicXMLDocument(
self.flute_scale_filename)
compressed_musicxml = musicxml_parser.MusicXMLDocument(
self.multiple_rootfile_compressed_filename)
uncompressed_proto = musicxml_reader.musicxml_to_sequence_proto(
uncompressed_musicxml)
self.check_musicxml_and_sequence(compressed_musicxml, uncompressed_proto)
self.check_fmajor_scale(self.flute_scale_filename, 'Flute')
def testrhythmdurationsxmltosequence(self):
"""Test the rhythm durations MusicXML file."""
self.check_musicxml_to_sequence(self.rhythm_durations_filename)
def testFluteScale(self):
"""Verify properties of the flute scale."""
ns = musicxml_reader.musicxml_file_to_sequence_proto(
self.flute_scale_filename)
expected_ns = testing_lib.parse_test_proto(
music_pb2.NoteSequence,
"""
ticks_per_quarter: 220
time_signatures: {
numerator: 4
denominator: 4
}
tempos: {
qpm: 120
}
key_signatures: {
key: F
}
source_info: {
source_type: SCORE_BASED
encoding_type: MUSIC_XML
parser: MAGENTA_MUSIC_XML
}
part_infos {
part: 0
name: "Flute"
}
total_time: 4.0
""")
expected_pitches = [65, 67, 69, 70, 72, 74, 76, 77]
time = 0
for pitch in expected_pitches:
note = expected_ns.notes.add()
note.part = 0
note.voice = 1
note.pitch = pitch
note.start_time = time
time += .5
note.end_time = time
note.velocity = 64
note.numerator = 1
note.denominator = 4
self.assertProtoEquals(expected_ns, ns)
def test_atonal_transposition(self):
"""Test that transposition works when changing instrument transposition.
This can occur within a single part in a score where the score
has no key signature / is atonal. Examples include changing from a
non-transposing instrument to a transposing one (ex. Flute to Bb Clarinet)
or vice versa, or changing among transposing instruments (ex. Bb Clarinet
to Eb Alto Saxophone).
"""
ns = musicxml_reader.musicxml_file_to_sequence_proto(
self.atonal_transposition_filename)
expected_ns = testing_lib.parse_test_proto(
music_pb2.NoteSequence,
"""
ticks_per_quarter: 220
time_signatures: {
numerator: 4
denominator: 4
}
tempos: {
qpm: 120
}
key_signatures: {
}
part_infos {
part: 0
name: "Flute"
}
source_info: {
source_type: SCORE_BASED
encoding_type: MUSIC_XML
parser: MAGENTA_MUSIC_XML
}
total_time: 4.0
""")
expected_pitches = [72, 74, 76, 77, 79, 77, 76, 74]
time = 0
for pitch in expected_pitches:
note = expected_ns.notes.add()
note.pitch = pitch
note.start_time = time
time += .5
note.end_time = time
note.velocity = 64
note.numerator = 1
note.denominator = 4
note.voice = 1
self.maxDiff = None
self.assertProtoEquals(expected_ns, ns)
def test_incomplete_measures(self):
"""Test that incomplete measures have the correct time signature.
This can occur in pickup bars or incomplete measures. For example,
if the time signature in the MusicXML is 4/4, but the measure only
contains one quarter note, Magenta expects this pickup measure to have
a time signature of 1/4.
"""
ns = musicxml_reader.musicxml_file_to_sequence_proto(
self.time_signature_filename)
# One time signature per measure
self.assertLen(ns.time_signatures, 6)
self.assertLen(ns.key_signatures, 1)
self.assertLen(ns.notes, 112)
def test_unmetered_music(self):
"""Test that time signatures are inserted for music without time signatures.
MusicXML does not require the use of time signatures. Music without
time signatures occur in medieval chant, cadenzas, and contemporary music.
"""
ns = musicxml_reader.musicxml_file_to_sequence_proto(
self.unmetered_filename)
expected_ns = testing_lib.parse_test_proto(
music_pb2.NoteSequence,
"""
ticks_per_quarter: 220
time_signatures: {
numerator: 11
denominator: 8
}
tempos: {
qpm: 120
}
key_signatures: {
}
notes {
pitch: 72
velocity: 64
end_time: 0.5
numerator: 1
denominator: 4
voice: 1
}
notes {
pitch: 74
velocity: 64
start_time: 0.5
end_time: 0.75
numerator: 1
denominator: 8
voice: 1
}
notes {
pitch: 76
velocity: 64
start_time: 0.75
end_time: 1.25
numerator: 1
denominator: 4
voice: 1
}
notes {
pitch: 77
velocity: 64
start_time: 1.25
end_time: 1.75
numerator: 1
denominator: 4
voice: 1
}
notes {
pitch: 79
velocity: 64
start_time: 1.75
end_time: 2.75
numerator: 1
denominator: 2
voice: 1
}
part_infos {
name: "Flute"
}
source_info: {
source_type: SCORE_BASED
encoding_type: MUSIC_XML
parser: MAGENTA_MUSIC_XML
}
total_time: 2.75
""")
self.maxDiff = None
self.assertProtoEquals(expected_ns, ns)
def test_st_anne(self):
"""Verify properties of the St. Anne file.
The file contains 2 parts and 4 voices.
"""
ns = musicxml_reader.musicxml_file_to_sequence_proto(
self.st_anne_filename)
expected_ns = testing_lib.parse_test_proto(
music_pb2.NoteSequence,
"""
ticks_per_quarter: 220
time_signatures {
numerator: 1
denominator: 4
}
time_signatures {
time: 0.5
numerator: 4
denominator: 4
}
time_signatures {
time: 6.5
numerator: 3
denominator: 4
}
time_signatures {
time: 8.0
numerator: 1
denominator: 4
}
time_signatures {
time: 8.5
numerator: 4
denominator: 4
}
time_signatures {
time: 14.5
numerator: 3
denominator: 4
}
tempos: {
qpm: 120
}
key_signatures: {
key: C
}
source_info: {
source_type: SCORE_BASED
encoding_type: MUSIC_XML
parser: MAGENTA_MUSIC_XML
}
part_infos {
part: 0
name: "Harpsichord"
}
part_infos {
part: 1
name: "Piano"
}
total_time: 16.0
""")
pitches_0_1 = [
(67, .5),
(64, .5),
(69, .5),
(67, .5),
(72, .5),
(72, .5),
(71, .5),
(72, .5),
(67, .5),
(72, .5),
(67, .5),
(69, .5),
(66, .5),
(67, 1.5),
(71, .5),
(72, .5),
(69, .5),
(74, .5),
(71, .5),
(72, .5),
(69, .5),
(71, .5),
(67, .5),
(69, .5),
(72, .5),
(74, .5),
(71, .5),
(72, 1.5),
]
pitches_0_2 = [
(60, .5),
(60, .5),
(60, .5),
(60, .5),
(64, .5),
(62, .5),
(62, .5),
(64, .5),
(64, .5),
(64, .5),
(64, .5),
(64, .5),
(62, .5),
(62, 1.5),
(62, .5),
(64, .5),
(60, .5),
(65, .5),
(62, .5),
(64, .75),
(62, .25),
(59, .5),
(60, .5),
(65, .5),
(64, .5),
(62, .5),
(62, .5),
(64, 1.5),
]
pitches_1_1 = [
(52, .5),
(55, .5),
(57, .5),
(60, .5),
(60, .5),
(57, .5),
(55, .5),
(55, .5),
(60, .5),
(60, .5),
(59, .5),
(57, .5),
(57, .5),
(59, 1.5),
(55, .5),
(55, .5),
(57, .5),
(57, .5),
(55, .5),
(55, .5),
(57, .5),
(56, .5),
(55, .5),
(53, .5),
(55, .5),
(57, .5),
(55, .5),
(55, 1.5),
]
pitches_1_2 = [
(48, .5),
(48, .5),
(53, .5),
(52, .5),
(57, .5),
(53, .5),
(55, .5),
(48, .5),
(48, .5),
(45, .5),
(52, .5),
(48, .5),
(50, .5),
(43, 1.5),
(55, .5),
(48, .5),
(53, .5),
(50, .5),
(55, .5),
(48, .5),
(53, .5),
(52, .5),
(52, .5),
(50, .5),
(48, .5),
(53, .5),
(55, .5),
(48, 1.5),
]
part_voice_instrument_program_pitches = [
(0, 1, 1, 7, pitches_0_1),
(0, 2, 1, 7, pitches_0_2),
(1, 1, 2, 1, pitches_1_1),
(1, 2, 2, 1, pitches_1_2),
]
for part, voice, instrument, program, pitches in (
part_voice_instrument_program_pitches):
time = 0
for pitch, duration in pitches:
note = expected_ns.notes.add()
note.part = part
note.voice = voice
note.pitch = pitch
note.start_time = time
time += duration
note.end_time = time
note.velocity = 64
note.instrument = instrument
note.program = program
if duration == .5:
note.numerator = 1
note.denominator = 4
if duration == .25:
note.numerator = 1
note.denominator = 8
if duration == .75:
note.numerator = 3
note.denominator = 8
if duration == 1.5:
note.numerator = 3
note.denominator = 4
expected_ns.notes.sort(
key=lambda note: (note.part, note.voice, note.start_time))
ns.notes.sort(
key=lambda note: (note.part, note.voice, note.start_time))
self.assertProtoEquals(expected_ns, ns)
def test_empty_part_name(self):
"""Verify that a part with an empty name can be parsed."""
xml = br"""<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE score-partwise PUBLIC
"-//Recordare//DTD MusicXML 3.0 Partwise//EN"
"http://www.musicxml.org/dtds/partwise.dtd">
<score-partwise version="3.0">
<part-list>
<score-part id="P1">
<part-name/>
</score-part>
</part-list>
<part id="P1">
</part>
</score-partwise>
"""
with tempfile.NamedTemporaryFile() as temp_file:
temp_file.write(xml)
temp_file.flush()
ns = musicxml_reader.musicxml_file_to_sequence_proto(
temp_file.name)
expected_ns = testing_lib.parse_test_proto(
music_pb2.NoteSequence,
"""
ticks_per_quarter: 220
source_info: {
source_type: SCORE_BASED
encoding_type: MUSIC_XML
parser: MAGENTA_MUSIC_XML
}
key_signatures {
key: C
time: 0
}
tempos {
qpm: 120.0
}
part_infos {
part: 0
}
total_time: 0.0
""")
self.assertProtoEquals(expected_ns, ns)
def test_empty_part_list(self):
"""Verify that a part without a corresponding score-part can be parsed."""
xml = br"""<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE score-partwise PUBLIC
"-//Recordare//DTD MusicXML 3.0 Partwise//EN"
"http://www.musicxml.org/dtds/partwise.dtd">
<score-partwise version="3.0">
<part id="P1">
</part>
</score-partwise>
"""
with tempfile.NamedTemporaryFile() as temp_file:
temp_file.write(xml)
temp_file.flush()
ns = musicxml_reader.musicxml_file_to_sequence_proto(
temp_file.name)
expected_ns = testing_lib.parse_test_proto(
music_pb2.NoteSequence,
"""
ticks_per_quarter: 220
source_info: {
source_type: SCORE_BASED
encoding_type: MUSIC_XML
parser: MAGENTA_MUSIC_XML
}
key_signatures {
key: C
time: 0
}
tempos {
qpm: 120.0
}
part_infos {
part: 0
}
total_time: 0.0
""")
self.assertProtoEquals(expected_ns, ns)
def test_empty_doc(self):
"""Verify that an empty doc can be parsed."""
xml = br"""<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE score-partwise PUBLIC
"-//Recordare//DTD MusicXML 3.0 Partwise//EN"
"http://www.musicxml.org/dtds/partwise.dtd">
<score-partwise version="3.0">
</score-partwise>
"""
with tempfile.NamedTemporaryFile() as temp_file:
temp_file.write(xml)
temp_file.flush()
ns = musicxml_reader.musicxml_file_to_sequence_proto(
temp_file.name)
expected_ns = testing_lib.parse_test_proto(
music_pb2.NoteSequence,
"""
ticks_per_quarter: 220
source_info: {
source_type: SCORE_BASED
encoding_type: MUSIC_XML
parser: MAGENTA_MUSIC_XML
}
key_signatures {
key: C
time: 0
}
tempos {
qpm: 120.0
}
total_time: 0.0
""")
self.assertProtoEquals(expected_ns, ns)
def test_chord_symbols(self):
ns = musicxml_reader.musicxml_file_to_sequence_proto(
self.chord_symbols_filename)
chord_symbols = [(annotation.time, annotation.text)
for annotation in ns.text_annotations
if annotation.annotation_type == CHORD_SYMBOL]
chord_symbols = list(sorted(chord_symbols, key=operator.itemgetter(0)))
expected_beats_and_chords = [
(0.0, 'N.C.'),
(4.0, 'Cmaj7'),
(12.0, 'F6(add9)'),
(16.0, 'F#dim7/A'),
(20.0, 'Bm7b5'),
(24.0, 'E7(#9)'),
(28.0, 'A7(add9)(no3)'),
(32.0, 'Bbsus2'),
(36.0, 'Am(maj7)'),
(38.0, 'D13'),
(40.0, 'E5'),
(44.0, 'Caug')
]
# Adjust for 120 QPM.
expected_times_and_chords = [(beat / 2.0, chord)
for beat, chord in expected_beats_and_chords]
self.assertEqual(expected_times_and_chords, chord_symbols)
def test_alternating_meter(self):
with self.assertRaises(musicxml_parser.AlternatingTimeSignatureError):
musicxml_parser.MusicXMLDocument(self.alternating_meter_filename)
def test_mid_measure_meter_change(self):
with self.assertRaises(musicxml_parser.MultipleTimeSignatureError):
musicxml_parser.MusicXMLDocument(self.mid_measure_meter_filename)
def test_unpitched_notes(self):
with self.assertRaises(musicxml_parser.UnpitchedNoteError):
musicxml_parser.MusicXMLDocument(os.path.join(
testing_lib.get_testdata_dir(), 'unpitched.xml'))
with self.assertRaises(musicxml_reader.MusicXMLConversionError):
musicxml_reader.musicxml_file_to_sequence_proto(os.path.join(
testing_lib.get_testdata_dir(), 'unpitched.xml'))
def test_empty_archive(self):
with tempfile.NamedTemporaryFile(suffix='.mxl') as temp_file:
z = zipfile.ZipFile(temp_file.name, 'w')
z.close()
with self.assertRaises(musicxml_reader.MusicXMLConversionError):
musicxml_reader.musicxml_file_to_sequence_proto(
temp_file.name)
def test_whole_measure_rest_forward(self):
"""Test that a whole measure rest can be encoded using <forward>.
A whole measure rest is usually encoded as a <note> with a duration
equal to that of a whole measure. An alternative encoding is to
use the <forward> element to advance the time cursor to a duration
equal to that of a whole measure. This implies a whole measure rest
when there are no <note> elements in this measure.
"""
ns = musicxml_reader.musicxml_file_to_sequence_proto(
self.whole_measure_rest_forward_filename)
expected_ns = testing_lib.parse_test_proto(
music_pb2.NoteSequence,
"""
ticks_per_quarter: 220
time_signatures {
numerator: 4
denominator: 4
}
time_signatures {
time: 6.0
numerator: 2
denominator: 4
}
key_signatures {
}
tempos {
qpm: 120
}
notes {
pitch: 72
velocity: 64
end_time: 2.0
numerator: 1
denominator: 1
voice: 1
}
notes {
pitch: 72
velocity: 64
start_time: 4.0
end_time: 6.0
numerator: 1
denominator: 1
voice: 1
}
notes {
pitch: 60
velocity: 64
start_time: 6.0
end_time: 7.0
numerator: 1
denominator: 2
voice: 1
}
notes {
pitch: 60
velocity: 64
start_time: 8.0
end_time: 9.0
numerator: 1
denominator: 2
voice: 1
}
total_time: 9.0
part_infos {
name: "Flute"
}
source_info {
source_type: SCORE_BASED
encoding_type: MUSIC_XML
parser: MAGENTA_MUSIC_XML
}
""")
self.assertProtoEquals(expected_ns, ns)
def test_meter(self):
"""Test that meters are encoded properly.
Musical meters are expressed as a ratio of beats to divisions.
The MusicXML parser uses this ratio in lowest terms for timing
purposes. However, the meters should be in the actual terms
when appearing in a NoteSequence.
"""
ns = musicxml_reader.musicxml_file_to_sequence_proto(
self.meter_test_filename)
expected_ns = testing_lib.parse_test_proto(
music_pb2.NoteSequence,
"""
ticks_per_quarter: 220
time_signatures {
numerator: 1
denominator: 4
}
time_signatures {
time: 0.5
numerator: 2
denominator: 4
}
time_signatures {
time: 1.5
numerator: 3
denominator: 4
}
time_signatures {
time: 3.0
numerator: 4
denominator: 4
}
time_signatures {
time: 5.0
numerator: 5
denominator: 4
}
time_signatures {
time: 7.5
numerator: 6
denominator: 4
}
time_signatures {
time: 10.5
numerator: 7
denominator: 4
}
time_signatures {
time: 14.0
numerator: 1
denominator: 8
}
time_signatures {
time: 14.25
numerator: 2
denominator: 8
}
time_signatures {
time: 14.75
numerator: 3
denominator: 8
}
time_signatures {
time: 15.5
numerator: 4
denominator: 8
}
time_signatures {
time: 16.5
numerator: 5
denominator: 8
}
time_signatures {
time: 17.75
numerator: 6
denominator: 8
}
time_signatures {
time: 19.25
numerator: 7
denominator: 8
}
time_signatures {
time: 21.0
numerator: 8
denominator: 8
}
time_signatures {
time: 23.0
numerator: 9
denominator: 8
}
time_signatures {
time: 25.25
numerator: 10
denominator: 8
}
time_signatures {
time: 27.75
numerator: 11
denominator: 8
}
time_signatures {
time: 30.5
numerator: 12
denominator: 8
}
time_signatures {
time: 33.5
numerator: 2
denominator: 2
}
time_signatures {
time: 35.5
numerator: 3
denominator: 2
}
time_signatures {
time: 38.5
numerator: 4
denominator: 2
}
time_signatures {
time: 42.5
numerator: 4
denominator: 4
}
time_signatures {
time: 44.5
numerator: 2
denominator: 2
}
key_signatures {
}
tempos {
qpm: 120
}
notes {
pitch: 72
velocity: 64
end_time: 0.5
numerator: 1
denominator: 4
voice: 1
}
notes {
pitch: 72
velocity: 64
start_time: 0.5
end_time: 1.5
numerator: 1
denominator: 2
voice: 1
}
notes {
pitch: 72
velocity: 64
start_time: 1.5
end_time: 3.0
numerator: 3
denominator: 4
voice: 1
}
notes {
pitch: 72
velocity: 64
start_time: 3.0
end_time: 5.0
numerator: 1
denominator: 1
voice: 1
}
notes {
pitch: 72
velocity: 64
start_time: 5.0
end_time: 6.5
numerator: 3
denominator: 4
voice: 1
}
notes {
pitch: 72
velocity: 64
start_time: 6.5
end_time: 7.5
numerator: 1
denominator: 2
voice: 1
}
notes {
pitch: 72
velocity: 64
start_time: 7.5
end_time: 9.0
numerator: 3
denominator: 4
voice: 1
}
notes {
pitch: 72
velocity: 64
start_time: 9.0
end_time: 10.5
numerator: 3
denominator: 4
voice: 1
}
notes {
pitch: 72
velocity: 64
start_time: 10.5
end_time: 12.0
numerator: 3
denominator: 4
voice: 1
}
notes {
pitch: 72
velocity: 64
start_time: 12.0
end_time: 13.5
numerator: 3
denominator: 4
voice: 1
}
notes {
pitch: 72
velocity: 64
start_time: 13.5
end_time: 14.0
numerator: 1
denominator: 4
voice: 1
}
notes {
pitch: 72
velocity: 64
start_time: 14.0
end_time: 14.25
numerator: 1
denominator: 8
voice: 1
}
notes {
pitch: 72
velocity: 64
start_time: 14.25
end_time: 14.75
numerator: 1
denominator: 4
voice: 1
}
notes {
pitch: 72
velocity: 64
start_time: 14.75
end_time: 15.5
numerator: 3
denominator: 8
voice: 1
}
notes {
pitch: 72
velocity: 64
start_time: 15.5
end_time: 16.0
numerator: 1
denominator: 4
voice: 1
}
notes {
pitch: 72
velocity: 64
start_time: 16.0
end_time: 16.5
numerator: 1
denominator: 4
voice: 1
}
notes {
pitch: 72
velocity: 64
start_time: 16.5
end_time: 17.0
numerator: 1
denominator: 4
voice: 1
}
notes {
pitch: 72
velocity: 64
start_time: 17.0
end_time: 17.5
numerator: 1
denominator: 4
voice: 1
}
notes {
pitch: 72
velocity: 64
start_time: 17.5
end_time: 17.75
numerator: 1
denominator: 8
voice: 1
}
notes {
pitch: 72
velocity: 64
start_time: 17.75
end_time: 18.5
numerator: 3
denominator: 8
voice: 1
}
notes {
pitch: 72
velocity: 64
start_time: 18.5
end_time: 19.25
numerator: 3
denominator: 8
voice: 1
}
notes {
pitch: 72
velocity: 64
start_time: 19.25
end_time: 20.0
numerator: 3
denominator: 8
voice: 1
}
notes {
pitch: 72
velocity: 64
start_time: 20.0
end_time: 20.5
numerator: 1
denominator: 4
voice: 1
}
notes {
pitch: 72
velocity: 64
start_time: 20.5
end_time: 21.0
numerator: 1
denominator: 4
voice: 1
}
notes {
pitch: 72
velocity: 64
start_time: 21.0
end_time: 21.75
numerator: 3
denominator: 8
voice: 1
}
notes {
pitch: 72
velocity: 64
start_time: 21.75
end_time: 22.5
numerator: 3
denominator: 8
voice: 1
}
notes {
pitch: 72
velocity: 64
start_time: 22.5
end_time: 23.0
numerator: 1
denominator: 4
voice: 1
}
notes {
pitch: 72
velocity: 64
start_time: 23.0
end_time: 24.5
numerator: 3
denominator: 4
voice: 1
}
notes {
pitch: 72
velocity: 64
start_time: 24.5
end_time: 25.25
numerator: 3
denominator: 8
voice: 1
}
notes {
pitch: 72
velocity: 64
start_time: 25.25
end_time: 26.75
numerator: 3
denominator: 4
voice: 1
}
notes {
pitch: 72
velocity: 64
start_time: 26.75
end_time: 27.25
numerator: 1
denominator: 4
voice: 1
}
notes {
pitch: 72
velocity: 64
start_time: 27.25
end_time: 27.75
numerator: 1
denominator: 4
voice: 1
}
notes {
pitch: 72
velocity: 64
start_time: 27.75
end_time: 29.25
numerator: 3
denominator: 4
voice: 1
}
notes {
pitch: 72
velocity: 64
start_time: 29.25
end_time: 30.0
numerator: 3
denominator: 8
voice: 1
}
notes {
pitch: 72
velocity: 64
start_time: 30.0
end_time: 30.5
numerator: 1
denominator: 4
voice: 1
}
notes {
pitch: 72
velocity: 64
start_time: 30.5
end_time: 32.0
numerator: 3
denominator: 4
voice: 1
}
notes {
pitch: 72
velocity: 64
start_time: 32.0
end_time: 33.5
numerator: 3
denominator: 4
voice: 1
}
notes {
pitch: 72
velocity: 64
start_time: 33.5
end_time: 34.5
numerator: 1
denominator: 2
voice: 1
}
notes {
pitch: 72
velocity: 64
start_time: 34.5
end_time: 35.5
numerator: 1
denominator: 2
voice: 1
}
notes {
pitch: 72
velocity: 64
start_time: 35.5
end_time: 36.5
numerator: 1
denominator: 2
voice: 1
}
notes {
pitch: 72
velocity: 64
start_time: 36.5
end_time: 37.5
numerator: 1
denominator: 2
voice: 1
}
notes {
pitch: 72
velocity: 64
start_time: 37.5
end_time: 38.5
numerator: 1
denominator: 2
voice: 1
}
notes {
pitch: 72
velocity: 64
start_time: 38.5
end_time: 40.5
numerator: 1
denominator: 1
voice: 1
}
notes {
pitch: 72
velocity: 64
start_time: 40.5
end_time: 42.5
numerator: 1
denominator: 1
voice: 1
}
notes {
pitch: 72
velocity: 64
start_time: 42.5
end_time: 44.5
numerator: 1
denominator: 1
voice: 1
}
notes {
pitch: 72
velocity: 64
start_time: 44.5
end_time: 46.5
numerator: 1
denominator: 1
voice: 1
}
total_time: 46.5
part_infos {
name: "Flute"
}
source_info {
source_type: SCORE_BASED
encoding_type: MUSIC_XML
parser: MAGENTA_MUSIC_XML
}
""")
self.assertProtoEquals(expected_ns, ns)
def test_key_missing_fifths(self):
xml = br"""<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE score-partwise PUBLIC
"-//Recordare//DTD MusicXML 3.0 Partwise//EN"
"http://www.musicxml.org/dtds/partwise.dtd">
<score-partwise version="3.0">
<part-list>
<score-part id="P1">
<part-name/>
</score-part>
</part-list>
<part id="P1">
<measure number="1">
<attributes>
<divisions>2</divisions>
<key>
<!-- missing fifths element. -->
</key>
<time>
<beats>4</beats>
<beat-type>4</beat-type>
</time>
</attributes>
<note>
<pitch>
<step>G</step>
<octave>4</octave>
</pitch>
<duration>2</duration>
<voice>1</voice>
<type>quarter</type>
</note>
</measure>
</part>
</score-partwise>
"""
with tempfile.NamedTemporaryFile() as temp_file:
temp_file.write(xml)
temp_file.flush()
with self.assertRaises(musicxml_parser.KeyParseError):
musicxml_parser.MusicXMLDocument(temp_file.name)
def test_harmony_missing_degree(self):
xml = br"""<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE score-partwise PUBLIC
"-//Recordare//DTD MusicXML 3.0 Partwise//EN"
"http://www.musicxml.org/dtds/partwise.dtd">
<score-partwise version="3.0">
<part-list>
<score-part id="P1">
<part-name/>
</score-part>
</part-list>
<part id="P1">
<measure number="1">
<attributes>
<divisions>2</divisions>
<time>
<beats>4</beats>
<beat-type>4</beat-type>
</time>
</attributes>
<note>
<pitch>
<step>G</step>
<octave>4</octave>
</pitch>
<duration>2</duration>
<voice>1</voice>
<type>quarter</type>
</note>
<harmony>
<degree>
<!-- missing degree-value text -->
<degree-value></degree-value>
</degree>
</harmony>
</measure>
</part>
</score-partwise>
"""
with tempfile.NamedTemporaryFile() as temp_file:
temp_file.write(xml)
temp_file.flush()
with self.assertRaises(musicxml_parser.ChordSymbolParseError):
musicxml_parser.MusicXMLDocument(temp_file.name)
def test_transposed_keysig(self):
xml = br"""<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE score-partwise PUBLIC
"-//Recordare//DTD MusicXML 3.0 Partwise//EN"
"http://www.musicxml.org/dtds/partwise.dtd">
<score-partwise version="3.0">
<part-list>
<score-part id="P1">
<part-name/>
</score-part>
</part-list>
<part id="P1">
<measure number="1">
<attributes>
<divisions>4</divisions>
<key>
<fifths>-3</fifths>
<mode>major</mode>
</key>
<time>
<beats>4</beats>
<beat-type>4</beat-type>
</time>
<clef>
<sign>G</sign>
<line>2</line>
</clef>
<transpose>
<diatonic>-5</diatonic>
<chromatic>-9</chromatic>
</transpose>
</attributes>
<note>
<pitch>
<step>G</step>
<octave>4</octave>
</pitch>
<duration>2</duration>
<voice>1</voice>
<type>quarter</type>
</note>
</measure>
</part>
</score-partwise>
"""
with tempfile.NamedTemporaryFile() as temp_file:
temp_file.write(xml)
temp_file.flush()
musicxml_parser.MusicXMLDocument(temp_file.name)
sequence = musicxml_reader.musicxml_file_to_sequence_proto(temp_file.name)
self.assertLen(sequence.key_signatures, 1)
self.assertEqual(music_pb2.NoteSequence.KeySignature.G_FLAT,
sequence.key_signatures[0].key)
def test_beats_composite(self):
xml = br"""<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE score-partwise PUBLIC
"-//Recordare//DTD MusicXML 3.0 Partwise//EN"
"http://www.musicxml.org/dtds/partwise.dtd">
<score-partwise version="3.0">
<part-list>
<score-part id="P1">
<part-name/>
</score-part>
</part-list>
<part id="P1">
<measure number="1">
<attributes>
<divisions>2</divisions>
<time>
<beats>4+5</beats>
<beat-type>4</beat-type>
</time>
</attributes>
<note>
<pitch>
<step>G</step>
<octave>4</octave>
</pitch>
<duration>2</duration>
<voice>1</voice>
<type>quarter</type>
</note>
</measure>
</part>
</score-partwise>
"""
with tempfile.NamedTemporaryFile() as temp_file:
temp_file.write(xml)
temp_file.flush()
with self.assertRaises(musicxml_parser.TimeSignatureParseError):
musicxml_parser.MusicXMLDocument(temp_file.name)
def test_invalid_note_type(self):
xml = br"""<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE score-partwise PUBLIC
"-//Recordare//DTD MusicXML 3.0 Partwise//EN"
"http://www.musicxml.org/dtds/partwise.dtd">
<score-partwise version="3.0">
<part-list>
<score-part id="P1">
<part-name/>
</score-part>
</part-list>
<part id="P1">
<measure number="1">
<attributes>
<divisions>2</divisions>
<time>
<beats>4</beats>
<beat-type>4</beat-type>
</time>
</attributes>
<note>
<pitch>
<step>G</step>
<octave>4</octave>
</pitch>
<duration>2</duration>
<voice>1</voice>
<type>blarg</type>
</note>
</measure>
</part>
</score-partwise>
"""
with tempfile.NamedTemporaryFile() as temp_file:
temp_file.write(xml)
temp_file.flush()
with self.assertRaises(musicxml_parser.InvalidNoteDurationTypeError):
musicxml_parser.MusicXMLDocument(temp_file.name)
if __name__ == '__main__':
absltest.main()
Classes
class MusicXMLParserTest (*args, **kwargs)
-
Class to test the MusicXML parser use cases.
self.flute_scale_filename contains an F-major scale of 8 quarter notes each
self.clarinet_scale_filename contains a F-major scale of 8 quarter notes each appearing as written pitch. This means the key is written as G-major but sounds as F-major. The MIDI pitch numbers must be transposed to be input into Magenta
self.band_score_filename contains a number of instruments in written pitch. The score has two time signatures (6/8 and 2/4) and two sounding keys (Bb-major and Eb major). The file also contains chords and multiple voices (see Oboe part in measure 57), as well as dynamics, articulations, slurs, ties, hairpins, grace notes, tempo changes, and multiple barline types (double, repeat)
self.compressed_filename contains the same content as self.flute_scale_filename, but compressed in MXL format
self.rhythm_durations_filename contains a variety of rhythms (long, short, dotted, tuplet, and dotted tuplet) to test the computation of rhythmic ratios.
self.atonal_transposition_filename contains a change of instrument from a non-transposing (Flute) to transposing (Bb Clarinet) in a score with no key / atonal. This ensures that transposition works properly when no key signature is found (Issue #355)
self.st_anne_filename contains a 4-voice piece written in two parts.
self.whole_measure_rest_forward_filename contains 4 measures: Measures 1 and 2 contain whole note rests in 4/4. The first is a
, the second uses a . The durations must match. Measures 3 and 4 contain whole note rests in 2/4. The first is a , the second uses a . The durations must match. (Issue #674). self.meter_test_filename contains a different meter in each measure: - 1/4 through 7/4 inclusive - 1/8 through 12/8 inclusive - 2/2 through 4/2 inclusive - Common time and Cut time meters
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 MusicXMLParserTest(testing_lib.ProtoTestCase): """Class to test the MusicXML parser use cases. self.flute_scale_filename contains an F-major scale of 8 quarter notes each self.clarinet_scale_filename contains a F-major scale of 8 quarter notes each appearing as written pitch. This means the key is written as G-major but sounds as F-major. The MIDI pitch numbers must be transposed to be input into Magenta self.band_score_filename contains a number of instruments in written pitch. The score has two time signatures (6/8 and 2/4) and two sounding keys (Bb-major and Eb major). The file also contains chords and multiple voices (see Oboe part in measure 57), as well as dynamics, articulations, slurs, ties, hairpins, grace notes, tempo changes, and multiple barline types (double, repeat) self.compressed_filename contains the same content as self.flute_scale_filename, but compressed in MXL format self.rhythm_durations_filename contains a variety of rhythms (long, short, dotted, tuplet, and dotted tuplet) to test the computation of rhythmic ratios. self.atonal_transposition_filename contains a change of instrument from a non-transposing (Flute) to transposing (Bb Clarinet) in a score with no key / atonal. This ensures that transposition works properly when no key signature is found (Issue #355) self.st_anne_filename contains a 4-voice piece written in two parts. self.whole_measure_rest_forward_filename contains 4 measures: Measures 1 and 2 contain whole note rests in 4/4. The first is a <note>, the second uses a <forward>. The durations must match. Measures 3 and 4 contain whole note rests in 2/4. The first is a <note>, the second uses a <forward>. The durations must match. (Issue #674). self.meter_test_filename contains a different meter in each measure: - 1/4 through 7/4 inclusive - 1/8 through 12/8 inclusive - 2/2 through 4/2 inclusive - Common time and Cut time meters """ def setUp(self): self.maxDiff = None # pylint:disable=invalid-name self.steps_per_quarter = 4 self.flute_scale_filename = os.path.join( testing_lib.get_testdata_dir(), 'flute_scale.xml') self.clarinet_scale_filename = os.path.join( testing_lib.get_testdata_dir(), 'clarinet_scale.xml') self.band_score_filename = os.path.join( testing_lib.get_testdata_dir(), 'el_capitan.xml') self.compressed_filename = os.path.join( testing_lib.get_testdata_dir(), 'flute_scale.mxl') self.multiple_rootfile_compressed_filename = os.path.join( testing_lib.get_testdata_dir(), 'flute_scale_with_png.mxl') self.rhythm_durations_filename = os.path.join( testing_lib.get_testdata_dir(), 'rhythm_durations.xml') self.st_anne_filename = os.path.join( testing_lib.get_testdata_dir(), 'st_anne.xml') self.atonal_transposition_filename = os.path.join( testing_lib.get_testdata_dir(), 'atonal_transposition_change.xml') self.chord_symbols_filename = os.path.join( testing_lib.get_testdata_dir(), 'chord_symbols.xml') self.time_signature_filename = os.path.join( testing_lib.get_testdata_dir(), 'st_anne.xml') self.unmetered_filename = os.path.join( testing_lib.get_testdata_dir(), 'unmetered_example.xml') self.alternating_meter_filename = os.path.join( testing_lib.get_testdata_dir(), 'alternating_meter.xml') self.mid_measure_meter_filename = os.path.join( testing_lib.get_testdata_dir(), 'mid_measure_time_signature.xml') self.whole_measure_rest_forward_filename = os.path.join( testing_lib.get_testdata_dir(), 'whole_measure_rest_forward.xml') self.meter_test_filename = os.path.join( testing_lib.get_testdata_dir(), 'meter_test.xml') super().setUp() def check_musicxml_and_sequence(self, musicxml, sequence_proto): """Compares MusicXMLDocument object against a sequence proto. Args: musicxml: A MusicXMLDocument object. sequence_proto: A NoteSequence proto. """ # Test time signature changes. self.assertEqual(len(musicxml.get_time_signatures()), len(sequence_proto.time_signatures)) for musicxml_time, sequence_time in zip(musicxml.get_time_signatures(), sequence_proto.time_signatures): self.assertEqual(musicxml_time.numerator, sequence_time.numerator) self.assertEqual(musicxml_time.denominator, sequence_time.denominator) self.assertAlmostEqual(musicxml_time.time_position, sequence_time.time) # Test key signature changes. self.assertEqual(len(musicxml.get_key_signatures()), len(sequence_proto.key_signatures)) for musicxml_key, sequence_key in zip(musicxml.get_key_signatures(), sequence_proto.key_signatures): if musicxml_key.mode == 'major': mode = 0 elif musicxml_key.mode == 'minor': mode = 1 # The Key enum in music.proto does NOT follow MIDI / MusicXML specs # Convert from MIDI / MusicXML key to music.proto key music_proto_keys = [11, 6, 1, 8, 3, 10, 5, 0, 7, 2, 9, 4, 11, 6, 1] key = music_proto_keys[musicxml_key.key + 7] self.assertEqual(key, sequence_key.key) self.assertEqual(mode, sequence_key.mode) self.assertAlmostEqual(musicxml_key.time_position, sequence_key.time) # Test tempos. musicxml_tempos = musicxml.get_tempos() self.assertEqual(len(musicxml_tempos), len(sequence_proto.tempos)) for musicxml_tempo, sequence_tempo in zip( musicxml_tempos, sequence_proto.tempos): self.assertAlmostEqual(musicxml_tempo.qpm, sequence_tempo.qpm) self.assertAlmostEqual(musicxml_tempo.time_position, sequence_tempo.time) # Test parts/instruments. seq_parts = collections.defaultdict(list) for seq_note in sequence_proto.notes: seq_parts[seq_note.part].append(seq_note) self.assertEqual(len(musicxml.parts), len(seq_parts)) for musicxml_part, seq_part_id in zip( musicxml.parts, sorted(seq_parts.keys())): seq_instrument_notes = seq_parts[seq_part_id] musicxml_notes = [] for musicxml_measure in musicxml_part.measures: for musicxml_note in musicxml_measure.notes: if not musicxml_note.is_rest: musicxml_notes.append(musicxml_note) self.assertEqual(len(musicxml_notes), len(seq_instrument_notes)) for musicxml_note, sequence_note in zip(musicxml_notes, seq_instrument_notes): self.assertEqual(musicxml_note.pitch[1], sequence_note.pitch) self.assertEqual(musicxml_note.velocity, sequence_note.velocity) self.assertAlmostEqual(musicxml_note.note_duration.time_position, sequence_note.start_time) self.assertAlmostEqual(musicxml_note.note_duration.time_position + musicxml_note.note_duration.seconds, sequence_note.end_time) # Check that the duration specified in the MusicXML and the # duration float match to within +/- 1 (delta = 1) # Delta is used because duration in MusicXML is always an integer # For example, a 3:2 half note might have a durationfloat of 341.333 # but would have the 1/3 distributed in the MusicXML as # 341.0, 341.0, 342.0. # Check that (3 * 341.333) = (341 + 341 + 342) is true by checking # that 341.0 and 342.0 are +/- 1 of 341.333 self.assertAlmostEqual( musicxml_note.note_duration.duration, musicxml_note.state.divisions * 4 * musicxml_note.note_duration.duration_float(), delta=1) def check_musicxml_to_sequence(self, filename): """Test the translation from MusicXML to Sequence proto.""" source_musicxml = musicxml_parser.MusicXMLDocument(filename) sequence_proto = musicxml_reader.musicxml_to_sequence_proto(source_musicxml) self.check_musicxml_and_sequence(source_musicxml, sequence_proto) def check_fmajor_scale(self, filename, part_name): """Verify MusicXML scale file. Verify that it contains the correct pitches (sounding pitch) and durations. Args: filename: file to test. part_name: name of the part the sequence is expected to contain. """ expected_ns = testing_lib.parse_test_proto( music_pb2.NoteSequence, """ ticks_per_quarter: 220 source_info: { source_type: SCORE_BASED encoding_type: MUSIC_XML parser: MAGENTA_MUSIC_XML } key_signatures { key: F time: 0 } time_signatures { numerator: 4 denominator: 4 } tempos { qpm: 120.0 } total_time: 4.0 """) part_info = expected_ns.part_infos.add() part_info.name = part_name expected_pitches = [65, 67, 69, 70, 72, 74, 76, 77] time = 0 for pitch in expected_pitches: note = expected_ns.notes.add() note.part = 0 note.voice = 1 note.pitch = pitch note.start_time = time time += .5 note.end_time = time note.velocity = 64 note.numerator = 1 note.denominator = 4 # Convert MusicXML to NoteSequence source_musicxml = musicxml_parser.MusicXMLDocument(filename) sequence_proto = musicxml_reader.musicxml_to_sequence_proto(source_musicxml) # Check equality self.assertProtoEquals(expected_ns, sequence_proto) def testsimplemusicxmltosequence(self): """Test the simple flute scale MusicXML file.""" self.check_musicxml_to_sequence(self.flute_scale_filename) self.check_fmajor_scale(self.flute_scale_filename, 'Flute') def testcomplexmusicxmltosequence(self): """Test the complex band score MusicXML file.""" self.check_musicxml_to_sequence(self.band_score_filename) def testtransposedxmltosequence(self): """Test the translation from transposed MusicXML to Sequence proto. Compare a transposed MusicXML file (clarinet) to an identical untransposed sequence (flute). """ untransposed_musicxml = musicxml_parser.MusicXMLDocument( self.flute_scale_filename) transposed_musicxml = musicxml_parser.MusicXMLDocument( self.clarinet_scale_filename) untransposed_proto = musicxml_reader.musicxml_to_sequence_proto( untransposed_musicxml) self.check_musicxml_and_sequence(transposed_musicxml, untransposed_proto) self.check_fmajor_scale(self.clarinet_scale_filename, 'Clarinet in Bb') def testcompressedmxlunicodefilename(self): """Test an MXL file containing a unicode filename within its zip archive.""" unicode_filename = os.path.join( testing_lib.get_testdata_dir(), 'unicode_filename.mxl') sequence = musicxml_reader.musicxml_file_to_sequence_proto(unicode_filename) self.assertLen(sequence.notes, 8) def testcompressedxmltosequence(self): """Test the translation from compressed MusicXML to Sequence proto. Compare a compressed MusicXML file to an identical uncompressed sequence. """ uncompressed_musicxml = musicxml_parser.MusicXMLDocument( self.flute_scale_filename) compressed_musicxml = musicxml_parser.MusicXMLDocument( self.compressed_filename) uncompressed_proto = musicxml_reader.musicxml_to_sequence_proto( uncompressed_musicxml) self.check_musicxml_and_sequence(compressed_musicxml, uncompressed_proto) self.check_fmajor_scale(self.flute_scale_filename, 'Flute') def testmultiplecompressedxmltosequence(self): """Test the translation from compressed MusicXML with multiple rootfiles. The example MXL file contains a MusicXML file of the Flute F Major scale, as well as the PNG rendering of the score contained within the single MXL file. """ uncompressed_musicxml = musicxml_parser.MusicXMLDocument( self.flute_scale_filename) compressed_musicxml = musicxml_parser.MusicXMLDocument( self.multiple_rootfile_compressed_filename) uncompressed_proto = musicxml_reader.musicxml_to_sequence_proto( uncompressed_musicxml) self.check_musicxml_and_sequence(compressed_musicxml, uncompressed_proto) self.check_fmajor_scale(self.flute_scale_filename, 'Flute') def testrhythmdurationsxmltosequence(self): """Test the rhythm durations MusicXML file.""" self.check_musicxml_to_sequence(self.rhythm_durations_filename) def testFluteScale(self): """Verify properties of the flute scale.""" ns = musicxml_reader.musicxml_file_to_sequence_proto( self.flute_scale_filename) expected_ns = testing_lib.parse_test_proto( music_pb2.NoteSequence, """ ticks_per_quarter: 220 time_signatures: { numerator: 4 denominator: 4 } tempos: { qpm: 120 } key_signatures: { key: F } source_info: { source_type: SCORE_BASED encoding_type: MUSIC_XML parser: MAGENTA_MUSIC_XML } part_infos { part: 0 name: "Flute" } total_time: 4.0 """) expected_pitches = [65, 67, 69, 70, 72, 74, 76, 77] time = 0 for pitch in expected_pitches: note = expected_ns.notes.add() note.part = 0 note.voice = 1 note.pitch = pitch note.start_time = time time += .5 note.end_time = time note.velocity = 64 note.numerator = 1 note.denominator = 4 self.assertProtoEquals(expected_ns, ns) def test_atonal_transposition(self): """Test that transposition works when changing instrument transposition. This can occur within a single part in a score where the score has no key signature / is atonal. Examples include changing from a non-transposing instrument to a transposing one (ex. Flute to Bb Clarinet) or vice versa, or changing among transposing instruments (ex. Bb Clarinet to Eb Alto Saxophone). """ ns = musicxml_reader.musicxml_file_to_sequence_proto( self.atonal_transposition_filename) expected_ns = testing_lib.parse_test_proto( music_pb2.NoteSequence, """ ticks_per_quarter: 220 time_signatures: { numerator: 4 denominator: 4 } tempos: { qpm: 120 } key_signatures: { } part_infos { part: 0 name: "Flute" } source_info: { source_type: SCORE_BASED encoding_type: MUSIC_XML parser: MAGENTA_MUSIC_XML } total_time: 4.0 """) expected_pitches = [72, 74, 76, 77, 79, 77, 76, 74] time = 0 for pitch in expected_pitches: note = expected_ns.notes.add() note.pitch = pitch note.start_time = time time += .5 note.end_time = time note.velocity = 64 note.numerator = 1 note.denominator = 4 note.voice = 1 self.maxDiff = None self.assertProtoEquals(expected_ns, ns) def test_incomplete_measures(self): """Test that incomplete measures have the correct time signature. This can occur in pickup bars or incomplete measures. For example, if the time signature in the MusicXML is 4/4, but the measure only contains one quarter note, Magenta expects this pickup measure to have a time signature of 1/4. """ ns = musicxml_reader.musicxml_file_to_sequence_proto( self.time_signature_filename) # One time signature per measure self.assertLen(ns.time_signatures, 6) self.assertLen(ns.key_signatures, 1) self.assertLen(ns.notes, 112) def test_unmetered_music(self): """Test that time signatures are inserted for music without time signatures. MusicXML does not require the use of time signatures. Music without time signatures occur in medieval chant, cadenzas, and contemporary music. """ ns = musicxml_reader.musicxml_file_to_sequence_proto( self.unmetered_filename) expected_ns = testing_lib.parse_test_proto( music_pb2.NoteSequence, """ ticks_per_quarter: 220 time_signatures: { numerator: 11 denominator: 8 } tempos: { qpm: 120 } key_signatures: { } notes { pitch: 72 velocity: 64 end_time: 0.5 numerator: 1 denominator: 4 voice: 1 } notes { pitch: 74 velocity: 64 start_time: 0.5 end_time: 0.75 numerator: 1 denominator: 8 voice: 1 } notes { pitch: 76 velocity: 64 start_time: 0.75 end_time: 1.25 numerator: 1 denominator: 4 voice: 1 } notes { pitch: 77 velocity: 64 start_time: 1.25 end_time: 1.75 numerator: 1 denominator: 4 voice: 1 } notes { pitch: 79 velocity: 64 start_time: 1.75 end_time: 2.75 numerator: 1 denominator: 2 voice: 1 } part_infos { name: "Flute" } source_info: { source_type: SCORE_BASED encoding_type: MUSIC_XML parser: MAGENTA_MUSIC_XML } total_time: 2.75 """) self.maxDiff = None self.assertProtoEquals(expected_ns, ns) def test_st_anne(self): """Verify properties of the St. Anne file. The file contains 2 parts and 4 voices. """ ns = musicxml_reader.musicxml_file_to_sequence_proto( self.st_anne_filename) expected_ns = testing_lib.parse_test_proto( music_pb2.NoteSequence, """ ticks_per_quarter: 220 time_signatures { numerator: 1 denominator: 4 } time_signatures { time: 0.5 numerator: 4 denominator: 4 } time_signatures { time: 6.5 numerator: 3 denominator: 4 } time_signatures { time: 8.0 numerator: 1 denominator: 4 } time_signatures { time: 8.5 numerator: 4 denominator: 4 } time_signatures { time: 14.5 numerator: 3 denominator: 4 } tempos: { qpm: 120 } key_signatures: { key: C } source_info: { source_type: SCORE_BASED encoding_type: MUSIC_XML parser: MAGENTA_MUSIC_XML } part_infos { part: 0 name: "Harpsichord" } part_infos { part: 1 name: "Piano" } total_time: 16.0 """) pitches_0_1 = [ (67, .5), (64, .5), (69, .5), (67, .5), (72, .5), (72, .5), (71, .5), (72, .5), (67, .5), (72, .5), (67, .5), (69, .5), (66, .5), (67, 1.5), (71, .5), (72, .5), (69, .5), (74, .5), (71, .5), (72, .5), (69, .5), (71, .5), (67, .5), (69, .5), (72, .5), (74, .5), (71, .5), (72, 1.5), ] pitches_0_2 = [ (60, .5), (60, .5), (60, .5), (60, .5), (64, .5), (62, .5), (62, .5), (64, .5), (64, .5), (64, .5), (64, .5), (64, .5), (62, .5), (62, 1.5), (62, .5), (64, .5), (60, .5), (65, .5), (62, .5), (64, .75), (62, .25), (59, .5), (60, .5), (65, .5), (64, .5), (62, .5), (62, .5), (64, 1.5), ] pitches_1_1 = [ (52, .5), (55, .5), (57, .5), (60, .5), (60, .5), (57, .5), (55, .5), (55, .5), (60, .5), (60, .5), (59, .5), (57, .5), (57, .5), (59, 1.5), (55, .5), (55, .5), (57, .5), (57, .5), (55, .5), (55, .5), (57, .5), (56, .5), (55, .5), (53, .5), (55, .5), (57, .5), (55, .5), (55, 1.5), ] pitches_1_2 = [ (48, .5), (48, .5), (53, .5), (52, .5), (57, .5), (53, .5), (55, .5), (48, .5), (48, .5), (45, .5), (52, .5), (48, .5), (50, .5), (43, 1.5), (55, .5), (48, .5), (53, .5), (50, .5), (55, .5), (48, .5), (53, .5), (52, .5), (52, .5), (50, .5), (48, .5), (53, .5), (55, .5), (48, 1.5), ] part_voice_instrument_program_pitches = [ (0, 1, 1, 7, pitches_0_1), (0, 2, 1, 7, pitches_0_2), (1, 1, 2, 1, pitches_1_1), (1, 2, 2, 1, pitches_1_2), ] for part, voice, instrument, program, pitches in ( part_voice_instrument_program_pitches): time = 0 for pitch, duration in pitches: note = expected_ns.notes.add() note.part = part note.voice = voice note.pitch = pitch note.start_time = time time += duration note.end_time = time note.velocity = 64 note.instrument = instrument note.program = program if duration == .5: note.numerator = 1 note.denominator = 4 if duration == .25: note.numerator = 1 note.denominator = 8 if duration == .75: note.numerator = 3 note.denominator = 8 if duration == 1.5: note.numerator = 3 note.denominator = 4 expected_ns.notes.sort( key=lambda note: (note.part, note.voice, note.start_time)) ns.notes.sort( key=lambda note: (note.part, note.voice, note.start_time)) self.assertProtoEquals(expected_ns, ns) def test_empty_part_name(self): """Verify that a part with an empty name can be parsed.""" xml = br"""<?xml version="1.0" encoding="UTF-8" standalone="no"?> <!DOCTYPE score-partwise PUBLIC "-//Recordare//DTD MusicXML 3.0 Partwise//EN" "http://www.musicxml.org/dtds/partwise.dtd"> <score-partwise version="3.0"> <part-list> <score-part id="P1"> <part-name/> </score-part> </part-list> <part id="P1"> </part> </score-partwise> """ with tempfile.NamedTemporaryFile() as temp_file: temp_file.write(xml) temp_file.flush() ns = musicxml_reader.musicxml_file_to_sequence_proto( temp_file.name) expected_ns = testing_lib.parse_test_proto( music_pb2.NoteSequence, """ ticks_per_quarter: 220 source_info: { source_type: SCORE_BASED encoding_type: MUSIC_XML parser: MAGENTA_MUSIC_XML } key_signatures { key: C time: 0 } tempos { qpm: 120.0 } part_infos { part: 0 } total_time: 0.0 """) self.assertProtoEquals(expected_ns, ns) def test_empty_part_list(self): """Verify that a part without a corresponding score-part can be parsed.""" xml = br"""<?xml version="1.0" encoding="UTF-8" standalone="no"?> <!DOCTYPE score-partwise PUBLIC "-//Recordare//DTD MusicXML 3.0 Partwise//EN" "http://www.musicxml.org/dtds/partwise.dtd"> <score-partwise version="3.0"> <part id="P1"> </part> </score-partwise> """ with tempfile.NamedTemporaryFile() as temp_file: temp_file.write(xml) temp_file.flush() ns = musicxml_reader.musicxml_file_to_sequence_proto( temp_file.name) expected_ns = testing_lib.parse_test_proto( music_pb2.NoteSequence, """ ticks_per_quarter: 220 source_info: { source_type: SCORE_BASED encoding_type: MUSIC_XML parser: MAGENTA_MUSIC_XML } key_signatures { key: C time: 0 } tempos { qpm: 120.0 } part_infos { part: 0 } total_time: 0.0 """) self.assertProtoEquals(expected_ns, ns) def test_empty_doc(self): """Verify that an empty doc can be parsed.""" xml = br"""<?xml version="1.0" encoding="UTF-8" standalone="no"?> <!DOCTYPE score-partwise PUBLIC "-//Recordare//DTD MusicXML 3.0 Partwise//EN" "http://www.musicxml.org/dtds/partwise.dtd"> <score-partwise version="3.0"> </score-partwise> """ with tempfile.NamedTemporaryFile() as temp_file: temp_file.write(xml) temp_file.flush() ns = musicxml_reader.musicxml_file_to_sequence_proto( temp_file.name) expected_ns = testing_lib.parse_test_proto( music_pb2.NoteSequence, """ ticks_per_quarter: 220 source_info: { source_type: SCORE_BASED encoding_type: MUSIC_XML parser: MAGENTA_MUSIC_XML } key_signatures { key: C time: 0 } tempos { qpm: 120.0 } total_time: 0.0 """) self.assertProtoEquals(expected_ns, ns) def test_chord_symbols(self): ns = musicxml_reader.musicxml_file_to_sequence_proto( self.chord_symbols_filename) chord_symbols = [(annotation.time, annotation.text) for annotation in ns.text_annotations if annotation.annotation_type == CHORD_SYMBOL] chord_symbols = list(sorted(chord_symbols, key=operator.itemgetter(0))) expected_beats_and_chords = [ (0.0, 'N.C.'), (4.0, 'Cmaj7'), (12.0, 'F6(add9)'), (16.0, 'F#dim7/A'), (20.0, 'Bm7b5'), (24.0, 'E7(#9)'), (28.0, 'A7(add9)(no3)'), (32.0, 'Bbsus2'), (36.0, 'Am(maj7)'), (38.0, 'D13'), (40.0, 'E5'), (44.0, 'Caug') ] # Adjust for 120 QPM. expected_times_and_chords = [(beat / 2.0, chord) for beat, chord in expected_beats_and_chords] self.assertEqual(expected_times_and_chords, chord_symbols) def test_alternating_meter(self): with self.assertRaises(musicxml_parser.AlternatingTimeSignatureError): musicxml_parser.MusicXMLDocument(self.alternating_meter_filename) def test_mid_measure_meter_change(self): with self.assertRaises(musicxml_parser.MultipleTimeSignatureError): musicxml_parser.MusicXMLDocument(self.mid_measure_meter_filename) def test_unpitched_notes(self): with self.assertRaises(musicxml_parser.UnpitchedNoteError): musicxml_parser.MusicXMLDocument(os.path.join( testing_lib.get_testdata_dir(), 'unpitched.xml')) with self.assertRaises(musicxml_reader.MusicXMLConversionError): musicxml_reader.musicxml_file_to_sequence_proto(os.path.join( testing_lib.get_testdata_dir(), 'unpitched.xml')) def test_empty_archive(self): with tempfile.NamedTemporaryFile(suffix='.mxl') as temp_file: z = zipfile.ZipFile(temp_file.name, 'w') z.close() with self.assertRaises(musicxml_reader.MusicXMLConversionError): musicxml_reader.musicxml_file_to_sequence_proto( temp_file.name) def test_whole_measure_rest_forward(self): """Test that a whole measure rest can be encoded using <forward>. A whole measure rest is usually encoded as a <note> with a duration equal to that of a whole measure. An alternative encoding is to use the <forward> element to advance the time cursor to a duration equal to that of a whole measure. This implies a whole measure rest when there are no <note> elements in this measure. """ ns = musicxml_reader.musicxml_file_to_sequence_proto( self.whole_measure_rest_forward_filename) expected_ns = testing_lib.parse_test_proto( music_pb2.NoteSequence, """ ticks_per_quarter: 220 time_signatures { numerator: 4 denominator: 4 } time_signatures { time: 6.0 numerator: 2 denominator: 4 } key_signatures { } tempos { qpm: 120 } notes { pitch: 72 velocity: 64 end_time: 2.0 numerator: 1 denominator: 1 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 4.0 end_time: 6.0 numerator: 1 denominator: 1 voice: 1 } notes { pitch: 60 velocity: 64 start_time: 6.0 end_time: 7.0 numerator: 1 denominator: 2 voice: 1 } notes { pitch: 60 velocity: 64 start_time: 8.0 end_time: 9.0 numerator: 1 denominator: 2 voice: 1 } total_time: 9.0 part_infos { name: "Flute" } source_info { source_type: SCORE_BASED encoding_type: MUSIC_XML parser: MAGENTA_MUSIC_XML } """) self.assertProtoEquals(expected_ns, ns) def test_meter(self): """Test that meters are encoded properly. Musical meters are expressed as a ratio of beats to divisions. The MusicXML parser uses this ratio in lowest terms for timing purposes. However, the meters should be in the actual terms when appearing in a NoteSequence. """ ns = musicxml_reader.musicxml_file_to_sequence_proto( self.meter_test_filename) expected_ns = testing_lib.parse_test_proto( music_pb2.NoteSequence, """ ticks_per_quarter: 220 time_signatures { numerator: 1 denominator: 4 } time_signatures { time: 0.5 numerator: 2 denominator: 4 } time_signatures { time: 1.5 numerator: 3 denominator: 4 } time_signatures { time: 3.0 numerator: 4 denominator: 4 } time_signatures { time: 5.0 numerator: 5 denominator: 4 } time_signatures { time: 7.5 numerator: 6 denominator: 4 } time_signatures { time: 10.5 numerator: 7 denominator: 4 } time_signatures { time: 14.0 numerator: 1 denominator: 8 } time_signatures { time: 14.25 numerator: 2 denominator: 8 } time_signatures { time: 14.75 numerator: 3 denominator: 8 } time_signatures { time: 15.5 numerator: 4 denominator: 8 } time_signatures { time: 16.5 numerator: 5 denominator: 8 } time_signatures { time: 17.75 numerator: 6 denominator: 8 } time_signatures { time: 19.25 numerator: 7 denominator: 8 } time_signatures { time: 21.0 numerator: 8 denominator: 8 } time_signatures { time: 23.0 numerator: 9 denominator: 8 } time_signatures { time: 25.25 numerator: 10 denominator: 8 } time_signatures { time: 27.75 numerator: 11 denominator: 8 } time_signatures { time: 30.5 numerator: 12 denominator: 8 } time_signatures { time: 33.5 numerator: 2 denominator: 2 } time_signatures { time: 35.5 numerator: 3 denominator: 2 } time_signatures { time: 38.5 numerator: 4 denominator: 2 } time_signatures { time: 42.5 numerator: 4 denominator: 4 } time_signatures { time: 44.5 numerator: 2 denominator: 2 } key_signatures { } tempos { qpm: 120 } notes { pitch: 72 velocity: 64 end_time: 0.5 numerator: 1 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 0.5 end_time: 1.5 numerator: 1 denominator: 2 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 1.5 end_time: 3.0 numerator: 3 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 3.0 end_time: 5.0 numerator: 1 denominator: 1 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 5.0 end_time: 6.5 numerator: 3 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 6.5 end_time: 7.5 numerator: 1 denominator: 2 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 7.5 end_time: 9.0 numerator: 3 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 9.0 end_time: 10.5 numerator: 3 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 10.5 end_time: 12.0 numerator: 3 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 12.0 end_time: 13.5 numerator: 3 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 13.5 end_time: 14.0 numerator: 1 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 14.0 end_time: 14.25 numerator: 1 denominator: 8 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 14.25 end_time: 14.75 numerator: 1 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 14.75 end_time: 15.5 numerator: 3 denominator: 8 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 15.5 end_time: 16.0 numerator: 1 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 16.0 end_time: 16.5 numerator: 1 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 16.5 end_time: 17.0 numerator: 1 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 17.0 end_time: 17.5 numerator: 1 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 17.5 end_time: 17.75 numerator: 1 denominator: 8 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 17.75 end_time: 18.5 numerator: 3 denominator: 8 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 18.5 end_time: 19.25 numerator: 3 denominator: 8 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 19.25 end_time: 20.0 numerator: 3 denominator: 8 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 20.0 end_time: 20.5 numerator: 1 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 20.5 end_time: 21.0 numerator: 1 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 21.0 end_time: 21.75 numerator: 3 denominator: 8 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 21.75 end_time: 22.5 numerator: 3 denominator: 8 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 22.5 end_time: 23.0 numerator: 1 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 23.0 end_time: 24.5 numerator: 3 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 24.5 end_time: 25.25 numerator: 3 denominator: 8 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 25.25 end_time: 26.75 numerator: 3 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 26.75 end_time: 27.25 numerator: 1 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 27.25 end_time: 27.75 numerator: 1 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 27.75 end_time: 29.25 numerator: 3 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 29.25 end_time: 30.0 numerator: 3 denominator: 8 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 30.0 end_time: 30.5 numerator: 1 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 30.5 end_time: 32.0 numerator: 3 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 32.0 end_time: 33.5 numerator: 3 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 33.5 end_time: 34.5 numerator: 1 denominator: 2 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 34.5 end_time: 35.5 numerator: 1 denominator: 2 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 35.5 end_time: 36.5 numerator: 1 denominator: 2 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 36.5 end_time: 37.5 numerator: 1 denominator: 2 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 37.5 end_time: 38.5 numerator: 1 denominator: 2 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 38.5 end_time: 40.5 numerator: 1 denominator: 1 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 40.5 end_time: 42.5 numerator: 1 denominator: 1 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 42.5 end_time: 44.5 numerator: 1 denominator: 1 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 44.5 end_time: 46.5 numerator: 1 denominator: 1 voice: 1 } total_time: 46.5 part_infos { name: "Flute" } source_info { source_type: SCORE_BASED encoding_type: MUSIC_XML parser: MAGENTA_MUSIC_XML } """) self.assertProtoEquals(expected_ns, ns) def test_key_missing_fifths(self): xml = br"""<?xml version="1.0" encoding="UTF-8" standalone="no"?> <!DOCTYPE score-partwise PUBLIC "-//Recordare//DTD MusicXML 3.0 Partwise//EN" "http://www.musicxml.org/dtds/partwise.dtd"> <score-partwise version="3.0"> <part-list> <score-part id="P1"> <part-name/> </score-part> </part-list> <part id="P1"> <measure number="1"> <attributes> <divisions>2</divisions> <key> <!-- missing fifths element. --> </key> <time> <beats>4</beats> <beat-type>4</beat-type> </time> </attributes> <note> <pitch> <step>G</step> <octave>4</octave> </pitch> <duration>2</duration> <voice>1</voice> <type>quarter</type> </note> </measure> </part> </score-partwise> """ with tempfile.NamedTemporaryFile() as temp_file: temp_file.write(xml) temp_file.flush() with self.assertRaises(musicxml_parser.KeyParseError): musicxml_parser.MusicXMLDocument(temp_file.name) def test_harmony_missing_degree(self): xml = br"""<?xml version="1.0" encoding="UTF-8" standalone="no"?> <!DOCTYPE score-partwise PUBLIC "-//Recordare//DTD MusicXML 3.0 Partwise//EN" "http://www.musicxml.org/dtds/partwise.dtd"> <score-partwise version="3.0"> <part-list> <score-part id="P1"> <part-name/> </score-part> </part-list> <part id="P1"> <measure number="1"> <attributes> <divisions>2</divisions> <time> <beats>4</beats> <beat-type>4</beat-type> </time> </attributes> <note> <pitch> <step>G</step> <octave>4</octave> </pitch> <duration>2</duration> <voice>1</voice> <type>quarter</type> </note> <harmony> <degree> <!-- missing degree-value text --> <degree-value></degree-value> </degree> </harmony> </measure> </part> </score-partwise> """ with tempfile.NamedTemporaryFile() as temp_file: temp_file.write(xml) temp_file.flush() with self.assertRaises(musicxml_parser.ChordSymbolParseError): musicxml_parser.MusicXMLDocument(temp_file.name) def test_transposed_keysig(self): xml = br"""<?xml version="1.0" encoding="UTF-8" standalone="no"?> <!DOCTYPE score-partwise PUBLIC "-//Recordare//DTD MusicXML 3.0 Partwise//EN" "http://www.musicxml.org/dtds/partwise.dtd"> <score-partwise version="3.0"> <part-list> <score-part id="P1"> <part-name/> </score-part> </part-list> <part id="P1"> <measure number="1"> <attributes> <divisions>4</divisions> <key> <fifths>-3</fifths> <mode>major</mode> </key> <time> <beats>4</beats> <beat-type>4</beat-type> </time> <clef> <sign>G</sign> <line>2</line> </clef> <transpose> <diatonic>-5</diatonic> <chromatic>-9</chromatic> </transpose> </attributes> <note> <pitch> <step>G</step> <octave>4</octave> </pitch> <duration>2</duration> <voice>1</voice> <type>quarter</type> </note> </measure> </part> </score-partwise> """ with tempfile.NamedTemporaryFile() as temp_file: temp_file.write(xml) temp_file.flush() musicxml_parser.MusicXMLDocument(temp_file.name) sequence = musicxml_reader.musicxml_file_to_sequence_proto(temp_file.name) self.assertLen(sequence.key_signatures, 1) self.assertEqual(music_pb2.NoteSequence.KeySignature.G_FLAT, sequence.key_signatures[0].key) def test_beats_composite(self): xml = br"""<?xml version="1.0" encoding="UTF-8" standalone="no"?> <!DOCTYPE score-partwise PUBLIC "-//Recordare//DTD MusicXML 3.0 Partwise//EN" "http://www.musicxml.org/dtds/partwise.dtd"> <score-partwise version="3.0"> <part-list> <score-part id="P1"> <part-name/> </score-part> </part-list> <part id="P1"> <measure number="1"> <attributes> <divisions>2</divisions> <time> <beats>4+5</beats> <beat-type>4</beat-type> </time> </attributes> <note> <pitch> <step>G</step> <octave>4</octave> </pitch> <duration>2</duration> <voice>1</voice> <type>quarter</type> </note> </measure> </part> </score-partwise> """ with tempfile.NamedTemporaryFile() as temp_file: temp_file.write(xml) temp_file.flush() with self.assertRaises(musicxml_parser.TimeSignatureParseError): musicxml_parser.MusicXMLDocument(temp_file.name) def test_invalid_note_type(self): xml = br"""<?xml version="1.0" encoding="UTF-8" standalone="no"?> <!DOCTYPE score-partwise PUBLIC "-//Recordare//DTD MusicXML 3.0 Partwise//EN" "http://www.musicxml.org/dtds/partwise.dtd"> <score-partwise version="3.0"> <part-list> <score-part id="P1"> <part-name/> </score-part> </part-list> <part id="P1"> <measure number="1"> <attributes> <divisions>2</divisions> <time> <beats>4</beats> <beat-type>4</beat-type> </time> </attributes> <note> <pitch> <step>G</step> <octave>4</octave> </pitch> <duration>2</duration> <voice>1</voice> <type>blarg</type> </note> </measure> </part> </score-partwise> """ with tempfile.NamedTemporaryFile() as temp_file: temp_file.write(xml) temp_file.flush() with self.assertRaises(musicxml_parser.InvalidNoteDurationTypeError): musicxml_parser.MusicXMLDocument(temp_file.name)
Ancestors
- ProtoTestCase
- absl.testing.absltest.TestCase
- absl.third_party.unittest3_backport.case.TestCase
- unittest.case.TestCase
Methods
def check_fmajor_scale(self, filename, part_name)
-
Verify MusicXML scale file.
Verify that it contains the correct pitches (sounding pitch) and durations.
Args
filename
- file to test.
part_name
- name of the part the sequence is expected to contain.
Expand source code
def check_fmajor_scale(self, filename, part_name): """Verify MusicXML scale file. Verify that it contains the correct pitches (sounding pitch) and durations. Args: filename: file to test. part_name: name of the part the sequence is expected to contain. """ expected_ns = testing_lib.parse_test_proto( music_pb2.NoteSequence, """ ticks_per_quarter: 220 source_info: { source_type: SCORE_BASED encoding_type: MUSIC_XML parser: MAGENTA_MUSIC_XML } key_signatures { key: F time: 0 } time_signatures { numerator: 4 denominator: 4 } tempos { qpm: 120.0 } total_time: 4.0 """) part_info = expected_ns.part_infos.add() part_info.name = part_name expected_pitches = [65, 67, 69, 70, 72, 74, 76, 77] time = 0 for pitch in expected_pitches: note = expected_ns.notes.add() note.part = 0 note.voice = 1 note.pitch = pitch note.start_time = time time += .5 note.end_time = time note.velocity = 64 note.numerator = 1 note.denominator = 4 # Convert MusicXML to NoteSequence source_musicxml = musicxml_parser.MusicXMLDocument(filename) sequence_proto = musicxml_reader.musicxml_to_sequence_proto(source_musicxml) # Check equality self.assertProtoEquals(expected_ns, sequence_proto)
def check_musicxml_and_sequence(self, musicxml, sequence_proto)
-
Compares MusicXMLDocument object against a sequence proto.
Args
musicxml
- A MusicXMLDocument object.
sequence_proto
- A NoteSequence proto.
Expand source code
def check_musicxml_and_sequence(self, musicxml, sequence_proto): """Compares MusicXMLDocument object against a sequence proto. Args: musicxml: A MusicXMLDocument object. sequence_proto: A NoteSequence proto. """ # Test time signature changes. self.assertEqual(len(musicxml.get_time_signatures()), len(sequence_proto.time_signatures)) for musicxml_time, sequence_time in zip(musicxml.get_time_signatures(), sequence_proto.time_signatures): self.assertEqual(musicxml_time.numerator, sequence_time.numerator) self.assertEqual(musicxml_time.denominator, sequence_time.denominator) self.assertAlmostEqual(musicxml_time.time_position, sequence_time.time) # Test key signature changes. self.assertEqual(len(musicxml.get_key_signatures()), len(sequence_proto.key_signatures)) for musicxml_key, sequence_key in zip(musicxml.get_key_signatures(), sequence_proto.key_signatures): if musicxml_key.mode == 'major': mode = 0 elif musicxml_key.mode == 'minor': mode = 1 # The Key enum in music.proto does NOT follow MIDI / MusicXML specs # Convert from MIDI / MusicXML key to music.proto key music_proto_keys = [11, 6, 1, 8, 3, 10, 5, 0, 7, 2, 9, 4, 11, 6, 1] key = music_proto_keys[musicxml_key.key + 7] self.assertEqual(key, sequence_key.key) self.assertEqual(mode, sequence_key.mode) self.assertAlmostEqual(musicxml_key.time_position, sequence_key.time) # Test tempos. musicxml_tempos = musicxml.get_tempos() self.assertEqual(len(musicxml_tempos), len(sequence_proto.tempos)) for musicxml_tempo, sequence_tempo in zip( musicxml_tempos, sequence_proto.tempos): self.assertAlmostEqual(musicxml_tempo.qpm, sequence_tempo.qpm) self.assertAlmostEqual(musicxml_tempo.time_position, sequence_tempo.time) # Test parts/instruments. seq_parts = collections.defaultdict(list) for seq_note in sequence_proto.notes: seq_parts[seq_note.part].append(seq_note) self.assertEqual(len(musicxml.parts), len(seq_parts)) for musicxml_part, seq_part_id in zip( musicxml.parts, sorted(seq_parts.keys())): seq_instrument_notes = seq_parts[seq_part_id] musicxml_notes = [] for musicxml_measure in musicxml_part.measures: for musicxml_note in musicxml_measure.notes: if not musicxml_note.is_rest: musicxml_notes.append(musicxml_note) self.assertEqual(len(musicxml_notes), len(seq_instrument_notes)) for musicxml_note, sequence_note in zip(musicxml_notes, seq_instrument_notes): self.assertEqual(musicxml_note.pitch[1], sequence_note.pitch) self.assertEqual(musicxml_note.velocity, sequence_note.velocity) self.assertAlmostEqual(musicxml_note.note_duration.time_position, sequence_note.start_time) self.assertAlmostEqual(musicxml_note.note_duration.time_position + musicxml_note.note_duration.seconds, sequence_note.end_time) # Check that the duration specified in the MusicXML and the # duration float match to within +/- 1 (delta = 1) # Delta is used because duration in MusicXML is always an integer # For example, a 3:2 half note might have a durationfloat of 341.333 # but would have the 1/3 distributed in the MusicXML as # 341.0, 341.0, 342.0. # Check that (3 * 341.333) = (341 + 341 + 342) is true by checking # that 341.0 and 342.0 are +/- 1 of 341.333 self.assertAlmostEqual( musicxml_note.note_duration.duration, musicxml_note.state.divisions * 4 * musicxml_note.note_duration.duration_float(), delta=1)
def check_musicxml_to_sequence(self, filename)
-
Test the translation from MusicXML to Sequence proto.
Expand source code
def check_musicxml_to_sequence(self, filename): """Test the translation from MusicXML to Sequence proto.""" source_musicxml = musicxml_parser.MusicXMLDocument(filename) sequence_proto = musicxml_reader.musicxml_to_sequence_proto(source_musicxml) self.check_musicxml_and_sequence(source_musicxml, sequence_proto)
def testFluteScale(self)
-
Verify properties of the flute scale.
Expand source code
def testFluteScale(self): """Verify properties of the flute scale.""" ns = musicxml_reader.musicxml_file_to_sequence_proto( self.flute_scale_filename) expected_ns = testing_lib.parse_test_proto( music_pb2.NoteSequence, """ ticks_per_quarter: 220 time_signatures: { numerator: 4 denominator: 4 } tempos: { qpm: 120 } key_signatures: { key: F } source_info: { source_type: SCORE_BASED encoding_type: MUSIC_XML parser: MAGENTA_MUSIC_XML } part_infos { part: 0 name: "Flute" } total_time: 4.0 """) expected_pitches = [65, 67, 69, 70, 72, 74, 76, 77] time = 0 for pitch in expected_pitches: note = expected_ns.notes.add() note.part = 0 note.voice = 1 note.pitch = pitch note.start_time = time time += .5 note.end_time = time note.velocity = 64 note.numerator = 1 note.denominator = 4 self.assertProtoEquals(expected_ns, ns)
def test_alternating_meter(self)
-
Expand source code
def test_alternating_meter(self): with self.assertRaises(musicxml_parser.AlternatingTimeSignatureError): musicxml_parser.MusicXMLDocument(self.alternating_meter_filename)
def test_atonal_transposition(self)
-
Test that transposition works when changing instrument transposition.
This can occur within a single part in a score where the score has no key signature / is atonal. Examples include changing from a non-transposing instrument to a transposing one (ex. Flute to Bb Clarinet) or vice versa, or changing among transposing instruments (ex. Bb Clarinet to Eb Alto Saxophone).
Expand source code
def test_atonal_transposition(self): """Test that transposition works when changing instrument transposition. This can occur within a single part in a score where the score has no key signature / is atonal. Examples include changing from a non-transposing instrument to a transposing one (ex. Flute to Bb Clarinet) or vice versa, or changing among transposing instruments (ex. Bb Clarinet to Eb Alto Saxophone). """ ns = musicxml_reader.musicxml_file_to_sequence_proto( self.atonal_transposition_filename) expected_ns = testing_lib.parse_test_proto( music_pb2.NoteSequence, """ ticks_per_quarter: 220 time_signatures: { numerator: 4 denominator: 4 } tempos: { qpm: 120 } key_signatures: { } part_infos { part: 0 name: "Flute" } source_info: { source_type: SCORE_BASED encoding_type: MUSIC_XML parser: MAGENTA_MUSIC_XML } total_time: 4.0 """) expected_pitches = [72, 74, 76, 77, 79, 77, 76, 74] time = 0 for pitch in expected_pitches: note = expected_ns.notes.add() note.pitch = pitch note.start_time = time time += .5 note.end_time = time note.velocity = 64 note.numerator = 1 note.denominator = 4 note.voice = 1 self.maxDiff = None self.assertProtoEquals(expected_ns, ns)
def test_beats_composite(self)
-
Expand source code
def test_beats_composite(self): xml = br"""<?xml version="1.0" encoding="UTF-8" standalone="no"?> <!DOCTYPE score-partwise PUBLIC "-//Recordare//DTD MusicXML 3.0 Partwise//EN" "http://www.musicxml.org/dtds/partwise.dtd"> <score-partwise version="3.0"> <part-list> <score-part id="P1"> <part-name/> </score-part> </part-list> <part id="P1"> <measure number="1"> <attributes> <divisions>2</divisions> <time> <beats>4+5</beats> <beat-type>4</beat-type> </time> </attributes> <note> <pitch> <step>G</step> <octave>4</octave> </pitch> <duration>2</duration> <voice>1</voice> <type>quarter</type> </note> </measure> </part> </score-partwise> """ with tempfile.NamedTemporaryFile() as temp_file: temp_file.write(xml) temp_file.flush() with self.assertRaises(musicxml_parser.TimeSignatureParseError): musicxml_parser.MusicXMLDocument(temp_file.name)
def test_chord_symbols(self)
-
Expand source code
def test_chord_symbols(self): ns = musicxml_reader.musicxml_file_to_sequence_proto( self.chord_symbols_filename) chord_symbols = [(annotation.time, annotation.text) for annotation in ns.text_annotations if annotation.annotation_type == CHORD_SYMBOL] chord_symbols = list(sorted(chord_symbols, key=operator.itemgetter(0))) expected_beats_and_chords = [ (0.0, 'N.C.'), (4.0, 'Cmaj7'), (12.0, 'F6(add9)'), (16.0, 'F#dim7/A'), (20.0, 'Bm7b5'), (24.0, 'E7(#9)'), (28.0, 'A7(add9)(no3)'), (32.0, 'Bbsus2'), (36.0, 'Am(maj7)'), (38.0, 'D13'), (40.0, 'E5'), (44.0, 'Caug') ] # Adjust for 120 QPM. expected_times_and_chords = [(beat / 2.0, chord) for beat, chord in expected_beats_and_chords] self.assertEqual(expected_times_and_chords, chord_symbols)
def test_empty_archive(self)
-
Expand source code
def test_empty_archive(self): with tempfile.NamedTemporaryFile(suffix='.mxl') as temp_file: z = zipfile.ZipFile(temp_file.name, 'w') z.close() with self.assertRaises(musicxml_reader.MusicXMLConversionError): musicxml_reader.musicxml_file_to_sequence_proto( temp_file.name)
def test_empty_doc(self)
-
Verify that an empty doc can be parsed.
Expand source code
def test_empty_doc(self): """Verify that an empty doc can be parsed.""" xml = br"""<?xml version="1.0" encoding="UTF-8" standalone="no"?> <!DOCTYPE score-partwise PUBLIC "-//Recordare//DTD MusicXML 3.0 Partwise//EN" "http://www.musicxml.org/dtds/partwise.dtd"> <score-partwise version="3.0"> </score-partwise> """ with tempfile.NamedTemporaryFile() as temp_file: temp_file.write(xml) temp_file.flush() ns = musicxml_reader.musicxml_file_to_sequence_proto( temp_file.name) expected_ns = testing_lib.parse_test_proto( music_pb2.NoteSequence, """ ticks_per_quarter: 220 source_info: { source_type: SCORE_BASED encoding_type: MUSIC_XML parser: MAGENTA_MUSIC_XML } key_signatures { key: C time: 0 } tempos { qpm: 120.0 } total_time: 0.0 """) self.assertProtoEquals(expected_ns, ns)
def test_empty_part_list(self)
-
Verify that a part without a corresponding score-part can be parsed.
Expand source code
def test_empty_part_list(self): """Verify that a part without a corresponding score-part can be parsed.""" xml = br"""<?xml version="1.0" encoding="UTF-8" standalone="no"?> <!DOCTYPE score-partwise PUBLIC "-//Recordare//DTD MusicXML 3.0 Partwise//EN" "http://www.musicxml.org/dtds/partwise.dtd"> <score-partwise version="3.0"> <part id="P1"> </part> </score-partwise> """ with tempfile.NamedTemporaryFile() as temp_file: temp_file.write(xml) temp_file.flush() ns = musicxml_reader.musicxml_file_to_sequence_proto( temp_file.name) expected_ns = testing_lib.parse_test_proto( music_pb2.NoteSequence, """ ticks_per_quarter: 220 source_info: { source_type: SCORE_BASED encoding_type: MUSIC_XML parser: MAGENTA_MUSIC_XML } key_signatures { key: C time: 0 } tempos { qpm: 120.0 } part_infos { part: 0 } total_time: 0.0 """) self.assertProtoEquals(expected_ns, ns)
def test_empty_part_name(self)
-
Verify that a part with an empty name can be parsed.
Expand source code
def test_empty_part_name(self): """Verify that a part with an empty name can be parsed.""" xml = br"""<?xml version="1.0" encoding="UTF-8" standalone="no"?> <!DOCTYPE score-partwise PUBLIC "-//Recordare//DTD MusicXML 3.0 Partwise//EN" "http://www.musicxml.org/dtds/partwise.dtd"> <score-partwise version="3.0"> <part-list> <score-part id="P1"> <part-name/> </score-part> </part-list> <part id="P1"> </part> </score-partwise> """ with tempfile.NamedTemporaryFile() as temp_file: temp_file.write(xml) temp_file.flush() ns = musicxml_reader.musicxml_file_to_sequence_proto( temp_file.name) expected_ns = testing_lib.parse_test_proto( music_pb2.NoteSequence, """ ticks_per_quarter: 220 source_info: { source_type: SCORE_BASED encoding_type: MUSIC_XML parser: MAGENTA_MUSIC_XML } key_signatures { key: C time: 0 } tempos { qpm: 120.0 } part_infos { part: 0 } total_time: 0.0 """) self.assertProtoEquals(expected_ns, ns)
def test_harmony_missing_degree(self)
-
Expand source code
def test_harmony_missing_degree(self): xml = br"""<?xml version="1.0" encoding="UTF-8" standalone="no"?> <!DOCTYPE score-partwise PUBLIC "-//Recordare//DTD MusicXML 3.0 Partwise//EN" "http://www.musicxml.org/dtds/partwise.dtd"> <score-partwise version="3.0"> <part-list> <score-part id="P1"> <part-name/> </score-part> </part-list> <part id="P1"> <measure number="1"> <attributes> <divisions>2</divisions> <time> <beats>4</beats> <beat-type>4</beat-type> </time> </attributes> <note> <pitch> <step>G</step> <octave>4</octave> </pitch> <duration>2</duration> <voice>1</voice> <type>quarter</type> </note> <harmony> <degree> <!-- missing degree-value text --> <degree-value></degree-value> </degree> </harmony> </measure> </part> </score-partwise> """ with tempfile.NamedTemporaryFile() as temp_file: temp_file.write(xml) temp_file.flush() with self.assertRaises(musicxml_parser.ChordSymbolParseError): musicxml_parser.MusicXMLDocument(temp_file.name)
def test_incomplete_measures(self)
-
Test that incomplete measures have the correct time signature.
This can occur in pickup bars or incomplete measures. For example, if the time signature in the MusicXML is 4/4, but the measure only contains one quarter note, Magenta expects this pickup measure to have a time signature of 1/4.
Expand source code
def test_incomplete_measures(self): """Test that incomplete measures have the correct time signature. This can occur in pickup bars or incomplete measures. For example, if the time signature in the MusicXML is 4/4, but the measure only contains one quarter note, Magenta expects this pickup measure to have a time signature of 1/4. """ ns = musicxml_reader.musicxml_file_to_sequence_proto( self.time_signature_filename) # One time signature per measure self.assertLen(ns.time_signatures, 6) self.assertLen(ns.key_signatures, 1) self.assertLen(ns.notes, 112)
def test_invalid_note_type(self)
-
Expand source code
def test_invalid_note_type(self): xml = br"""<?xml version="1.0" encoding="UTF-8" standalone="no"?> <!DOCTYPE score-partwise PUBLIC "-//Recordare//DTD MusicXML 3.0 Partwise//EN" "http://www.musicxml.org/dtds/partwise.dtd"> <score-partwise version="3.0"> <part-list> <score-part id="P1"> <part-name/> </score-part> </part-list> <part id="P1"> <measure number="1"> <attributes> <divisions>2</divisions> <time> <beats>4</beats> <beat-type>4</beat-type> </time> </attributes> <note> <pitch> <step>G</step> <octave>4</octave> </pitch> <duration>2</duration> <voice>1</voice> <type>blarg</type> </note> </measure> </part> </score-partwise> """ with tempfile.NamedTemporaryFile() as temp_file: temp_file.write(xml) temp_file.flush() with self.assertRaises(musicxml_parser.InvalidNoteDurationTypeError): musicxml_parser.MusicXMLDocument(temp_file.name)
def test_key_missing_fifths(self)
-
Expand source code
def test_key_missing_fifths(self): xml = br"""<?xml version="1.0" encoding="UTF-8" standalone="no"?> <!DOCTYPE score-partwise PUBLIC "-//Recordare//DTD MusicXML 3.0 Partwise//EN" "http://www.musicxml.org/dtds/partwise.dtd"> <score-partwise version="3.0"> <part-list> <score-part id="P1"> <part-name/> </score-part> </part-list> <part id="P1"> <measure number="1"> <attributes> <divisions>2</divisions> <key> <!-- missing fifths element. --> </key> <time> <beats>4</beats> <beat-type>4</beat-type> </time> </attributes> <note> <pitch> <step>G</step> <octave>4</octave> </pitch> <duration>2</duration> <voice>1</voice> <type>quarter</type> </note> </measure> </part> </score-partwise> """ with tempfile.NamedTemporaryFile() as temp_file: temp_file.write(xml) temp_file.flush() with self.assertRaises(musicxml_parser.KeyParseError): musicxml_parser.MusicXMLDocument(temp_file.name)
def test_meter(self)
-
Test that meters are encoded properly.
Musical meters are expressed as a ratio of beats to divisions. The MusicXML parser uses this ratio in lowest terms for timing purposes. However, the meters should be in the actual terms when appearing in a NoteSequence.
Expand source code
def test_meter(self): """Test that meters are encoded properly. Musical meters are expressed as a ratio of beats to divisions. The MusicXML parser uses this ratio in lowest terms for timing purposes. However, the meters should be in the actual terms when appearing in a NoteSequence. """ ns = musicxml_reader.musicxml_file_to_sequence_proto( self.meter_test_filename) expected_ns = testing_lib.parse_test_proto( music_pb2.NoteSequence, """ ticks_per_quarter: 220 time_signatures { numerator: 1 denominator: 4 } time_signatures { time: 0.5 numerator: 2 denominator: 4 } time_signatures { time: 1.5 numerator: 3 denominator: 4 } time_signatures { time: 3.0 numerator: 4 denominator: 4 } time_signatures { time: 5.0 numerator: 5 denominator: 4 } time_signatures { time: 7.5 numerator: 6 denominator: 4 } time_signatures { time: 10.5 numerator: 7 denominator: 4 } time_signatures { time: 14.0 numerator: 1 denominator: 8 } time_signatures { time: 14.25 numerator: 2 denominator: 8 } time_signatures { time: 14.75 numerator: 3 denominator: 8 } time_signatures { time: 15.5 numerator: 4 denominator: 8 } time_signatures { time: 16.5 numerator: 5 denominator: 8 } time_signatures { time: 17.75 numerator: 6 denominator: 8 } time_signatures { time: 19.25 numerator: 7 denominator: 8 } time_signatures { time: 21.0 numerator: 8 denominator: 8 } time_signatures { time: 23.0 numerator: 9 denominator: 8 } time_signatures { time: 25.25 numerator: 10 denominator: 8 } time_signatures { time: 27.75 numerator: 11 denominator: 8 } time_signatures { time: 30.5 numerator: 12 denominator: 8 } time_signatures { time: 33.5 numerator: 2 denominator: 2 } time_signatures { time: 35.5 numerator: 3 denominator: 2 } time_signatures { time: 38.5 numerator: 4 denominator: 2 } time_signatures { time: 42.5 numerator: 4 denominator: 4 } time_signatures { time: 44.5 numerator: 2 denominator: 2 } key_signatures { } tempos { qpm: 120 } notes { pitch: 72 velocity: 64 end_time: 0.5 numerator: 1 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 0.5 end_time: 1.5 numerator: 1 denominator: 2 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 1.5 end_time: 3.0 numerator: 3 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 3.0 end_time: 5.0 numerator: 1 denominator: 1 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 5.0 end_time: 6.5 numerator: 3 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 6.5 end_time: 7.5 numerator: 1 denominator: 2 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 7.5 end_time: 9.0 numerator: 3 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 9.0 end_time: 10.5 numerator: 3 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 10.5 end_time: 12.0 numerator: 3 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 12.0 end_time: 13.5 numerator: 3 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 13.5 end_time: 14.0 numerator: 1 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 14.0 end_time: 14.25 numerator: 1 denominator: 8 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 14.25 end_time: 14.75 numerator: 1 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 14.75 end_time: 15.5 numerator: 3 denominator: 8 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 15.5 end_time: 16.0 numerator: 1 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 16.0 end_time: 16.5 numerator: 1 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 16.5 end_time: 17.0 numerator: 1 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 17.0 end_time: 17.5 numerator: 1 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 17.5 end_time: 17.75 numerator: 1 denominator: 8 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 17.75 end_time: 18.5 numerator: 3 denominator: 8 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 18.5 end_time: 19.25 numerator: 3 denominator: 8 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 19.25 end_time: 20.0 numerator: 3 denominator: 8 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 20.0 end_time: 20.5 numerator: 1 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 20.5 end_time: 21.0 numerator: 1 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 21.0 end_time: 21.75 numerator: 3 denominator: 8 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 21.75 end_time: 22.5 numerator: 3 denominator: 8 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 22.5 end_time: 23.0 numerator: 1 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 23.0 end_time: 24.5 numerator: 3 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 24.5 end_time: 25.25 numerator: 3 denominator: 8 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 25.25 end_time: 26.75 numerator: 3 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 26.75 end_time: 27.25 numerator: 1 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 27.25 end_time: 27.75 numerator: 1 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 27.75 end_time: 29.25 numerator: 3 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 29.25 end_time: 30.0 numerator: 3 denominator: 8 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 30.0 end_time: 30.5 numerator: 1 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 30.5 end_time: 32.0 numerator: 3 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 32.0 end_time: 33.5 numerator: 3 denominator: 4 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 33.5 end_time: 34.5 numerator: 1 denominator: 2 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 34.5 end_time: 35.5 numerator: 1 denominator: 2 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 35.5 end_time: 36.5 numerator: 1 denominator: 2 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 36.5 end_time: 37.5 numerator: 1 denominator: 2 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 37.5 end_time: 38.5 numerator: 1 denominator: 2 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 38.5 end_time: 40.5 numerator: 1 denominator: 1 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 40.5 end_time: 42.5 numerator: 1 denominator: 1 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 42.5 end_time: 44.5 numerator: 1 denominator: 1 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 44.5 end_time: 46.5 numerator: 1 denominator: 1 voice: 1 } total_time: 46.5 part_infos { name: "Flute" } source_info { source_type: SCORE_BASED encoding_type: MUSIC_XML parser: MAGENTA_MUSIC_XML } """) self.assertProtoEquals(expected_ns, ns)
def test_mid_measure_meter_change(self)
-
Expand source code
def test_mid_measure_meter_change(self): with self.assertRaises(musicxml_parser.MultipleTimeSignatureError): musicxml_parser.MusicXMLDocument(self.mid_measure_meter_filename)
def test_st_anne(self)
-
Verify properties of the St. Anne file.
The file contains 2 parts and 4 voices.
Expand source code
def test_st_anne(self): """Verify properties of the St. Anne file. The file contains 2 parts and 4 voices. """ ns = musicxml_reader.musicxml_file_to_sequence_proto( self.st_anne_filename) expected_ns = testing_lib.parse_test_proto( music_pb2.NoteSequence, """ ticks_per_quarter: 220 time_signatures { numerator: 1 denominator: 4 } time_signatures { time: 0.5 numerator: 4 denominator: 4 } time_signatures { time: 6.5 numerator: 3 denominator: 4 } time_signatures { time: 8.0 numerator: 1 denominator: 4 } time_signatures { time: 8.5 numerator: 4 denominator: 4 } time_signatures { time: 14.5 numerator: 3 denominator: 4 } tempos: { qpm: 120 } key_signatures: { key: C } source_info: { source_type: SCORE_BASED encoding_type: MUSIC_XML parser: MAGENTA_MUSIC_XML } part_infos { part: 0 name: "Harpsichord" } part_infos { part: 1 name: "Piano" } total_time: 16.0 """) pitches_0_1 = [ (67, .5), (64, .5), (69, .5), (67, .5), (72, .5), (72, .5), (71, .5), (72, .5), (67, .5), (72, .5), (67, .5), (69, .5), (66, .5), (67, 1.5), (71, .5), (72, .5), (69, .5), (74, .5), (71, .5), (72, .5), (69, .5), (71, .5), (67, .5), (69, .5), (72, .5), (74, .5), (71, .5), (72, 1.5), ] pitches_0_2 = [ (60, .5), (60, .5), (60, .5), (60, .5), (64, .5), (62, .5), (62, .5), (64, .5), (64, .5), (64, .5), (64, .5), (64, .5), (62, .5), (62, 1.5), (62, .5), (64, .5), (60, .5), (65, .5), (62, .5), (64, .75), (62, .25), (59, .5), (60, .5), (65, .5), (64, .5), (62, .5), (62, .5), (64, 1.5), ] pitches_1_1 = [ (52, .5), (55, .5), (57, .5), (60, .5), (60, .5), (57, .5), (55, .5), (55, .5), (60, .5), (60, .5), (59, .5), (57, .5), (57, .5), (59, 1.5), (55, .5), (55, .5), (57, .5), (57, .5), (55, .5), (55, .5), (57, .5), (56, .5), (55, .5), (53, .5), (55, .5), (57, .5), (55, .5), (55, 1.5), ] pitches_1_2 = [ (48, .5), (48, .5), (53, .5), (52, .5), (57, .5), (53, .5), (55, .5), (48, .5), (48, .5), (45, .5), (52, .5), (48, .5), (50, .5), (43, 1.5), (55, .5), (48, .5), (53, .5), (50, .5), (55, .5), (48, .5), (53, .5), (52, .5), (52, .5), (50, .5), (48, .5), (53, .5), (55, .5), (48, 1.5), ] part_voice_instrument_program_pitches = [ (0, 1, 1, 7, pitches_0_1), (0, 2, 1, 7, pitches_0_2), (1, 1, 2, 1, pitches_1_1), (1, 2, 2, 1, pitches_1_2), ] for part, voice, instrument, program, pitches in ( part_voice_instrument_program_pitches): time = 0 for pitch, duration in pitches: note = expected_ns.notes.add() note.part = part note.voice = voice note.pitch = pitch note.start_time = time time += duration note.end_time = time note.velocity = 64 note.instrument = instrument note.program = program if duration == .5: note.numerator = 1 note.denominator = 4 if duration == .25: note.numerator = 1 note.denominator = 8 if duration == .75: note.numerator = 3 note.denominator = 8 if duration == 1.5: note.numerator = 3 note.denominator = 4 expected_ns.notes.sort( key=lambda note: (note.part, note.voice, note.start_time)) ns.notes.sort( key=lambda note: (note.part, note.voice, note.start_time)) self.assertProtoEquals(expected_ns, ns)
def test_transposed_keysig(self)
-
Expand source code
def test_transposed_keysig(self): xml = br"""<?xml version="1.0" encoding="UTF-8" standalone="no"?> <!DOCTYPE score-partwise PUBLIC "-//Recordare//DTD MusicXML 3.0 Partwise//EN" "http://www.musicxml.org/dtds/partwise.dtd"> <score-partwise version="3.0"> <part-list> <score-part id="P1"> <part-name/> </score-part> </part-list> <part id="P1"> <measure number="1"> <attributes> <divisions>4</divisions> <key> <fifths>-3</fifths> <mode>major</mode> </key> <time> <beats>4</beats> <beat-type>4</beat-type> </time> <clef> <sign>G</sign> <line>2</line> </clef> <transpose> <diatonic>-5</diatonic> <chromatic>-9</chromatic> </transpose> </attributes> <note> <pitch> <step>G</step> <octave>4</octave> </pitch> <duration>2</duration> <voice>1</voice> <type>quarter</type> </note> </measure> </part> </score-partwise> """ with tempfile.NamedTemporaryFile() as temp_file: temp_file.write(xml) temp_file.flush() musicxml_parser.MusicXMLDocument(temp_file.name) sequence = musicxml_reader.musicxml_file_to_sequence_proto(temp_file.name) self.assertLen(sequence.key_signatures, 1) self.assertEqual(music_pb2.NoteSequence.KeySignature.G_FLAT, sequence.key_signatures[0].key)
def test_unmetered_music(self)
-
Test that time signatures are inserted for music without time signatures.
MusicXML does not require the use of time signatures. Music without time signatures occur in medieval chant, cadenzas, and contemporary music.
Expand source code
def test_unmetered_music(self): """Test that time signatures are inserted for music without time signatures. MusicXML does not require the use of time signatures. Music without time signatures occur in medieval chant, cadenzas, and contemporary music. """ ns = musicxml_reader.musicxml_file_to_sequence_proto( self.unmetered_filename) expected_ns = testing_lib.parse_test_proto( music_pb2.NoteSequence, """ ticks_per_quarter: 220 time_signatures: { numerator: 11 denominator: 8 } tempos: { qpm: 120 } key_signatures: { } notes { pitch: 72 velocity: 64 end_time: 0.5 numerator: 1 denominator: 4 voice: 1 } notes { pitch: 74 velocity: 64 start_time: 0.5 end_time: 0.75 numerator: 1 denominator: 8 voice: 1 } notes { pitch: 76 velocity: 64 start_time: 0.75 end_time: 1.25 numerator: 1 denominator: 4 voice: 1 } notes { pitch: 77 velocity: 64 start_time: 1.25 end_time: 1.75 numerator: 1 denominator: 4 voice: 1 } notes { pitch: 79 velocity: 64 start_time: 1.75 end_time: 2.75 numerator: 1 denominator: 2 voice: 1 } part_infos { name: "Flute" } source_info: { source_type: SCORE_BASED encoding_type: MUSIC_XML parser: MAGENTA_MUSIC_XML } total_time: 2.75 """) self.maxDiff = None self.assertProtoEquals(expected_ns, ns)
def test_unpitched_notes(self)
-
Expand source code
def test_unpitched_notes(self): with self.assertRaises(musicxml_parser.UnpitchedNoteError): musicxml_parser.MusicXMLDocument(os.path.join( testing_lib.get_testdata_dir(), 'unpitched.xml')) with self.assertRaises(musicxml_reader.MusicXMLConversionError): musicxml_reader.musicxml_file_to_sequence_proto(os.path.join( testing_lib.get_testdata_dir(), 'unpitched.xml'))
def test_whole_measure_rest_forward(self)
-
Test that a whole measure rest can be encoded using
. A whole measure rest is usually encoded as a
with a duration equal to that of a whole measure. An alternative encoding is to use the element to advance the time cursor to a duration equal to that of a whole measure. This implies a whole measure rest when there are no elements in this measure. Expand source code
def test_whole_measure_rest_forward(self): """Test that a whole measure rest can be encoded using <forward>. A whole measure rest is usually encoded as a <note> with a duration equal to that of a whole measure. An alternative encoding is to use the <forward> element to advance the time cursor to a duration equal to that of a whole measure. This implies a whole measure rest when there are no <note> elements in this measure. """ ns = musicxml_reader.musicxml_file_to_sequence_proto( self.whole_measure_rest_forward_filename) expected_ns = testing_lib.parse_test_proto( music_pb2.NoteSequence, """ ticks_per_quarter: 220 time_signatures { numerator: 4 denominator: 4 } time_signatures { time: 6.0 numerator: 2 denominator: 4 } key_signatures { } tempos { qpm: 120 } notes { pitch: 72 velocity: 64 end_time: 2.0 numerator: 1 denominator: 1 voice: 1 } notes { pitch: 72 velocity: 64 start_time: 4.0 end_time: 6.0 numerator: 1 denominator: 1 voice: 1 } notes { pitch: 60 velocity: 64 start_time: 6.0 end_time: 7.0 numerator: 1 denominator: 2 voice: 1 } notes { pitch: 60 velocity: 64 start_time: 8.0 end_time: 9.0 numerator: 1 denominator: 2 voice: 1 } total_time: 9.0 part_infos { name: "Flute" } source_info { source_type: SCORE_BASED encoding_type: MUSIC_XML parser: MAGENTA_MUSIC_XML } """) self.assertProtoEquals(expected_ns, ns)
def testcomplexmusicxmltosequence(self)
-
Test the complex band score MusicXML file.
Expand source code
def testcomplexmusicxmltosequence(self): """Test the complex band score MusicXML file.""" self.check_musicxml_to_sequence(self.band_score_filename)
def testcompressedmxlunicodefilename(self)
-
Test an MXL file containing a unicode filename within its zip archive.
Expand source code
def testcompressedmxlunicodefilename(self): """Test an MXL file containing a unicode filename within its zip archive.""" unicode_filename = os.path.join( testing_lib.get_testdata_dir(), 'unicode_filename.mxl') sequence = musicxml_reader.musicxml_file_to_sequence_proto(unicode_filename) self.assertLen(sequence.notes, 8)
def testcompressedxmltosequence(self)
-
Test the translation from compressed MusicXML to Sequence proto.
Compare a compressed MusicXML file to an identical uncompressed sequence.
Expand source code
def testcompressedxmltosequence(self): """Test the translation from compressed MusicXML to Sequence proto. Compare a compressed MusicXML file to an identical uncompressed sequence. """ uncompressed_musicxml = musicxml_parser.MusicXMLDocument( self.flute_scale_filename) compressed_musicxml = musicxml_parser.MusicXMLDocument( self.compressed_filename) uncompressed_proto = musicxml_reader.musicxml_to_sequence_proto( uncompressed_musicxml) self.check_musicxml_and_sequence(compressed_musicxml, uncompressed_proto) self.check_fmajor_scale(self.flute_scale_filename, 'Flute')
def testmultiplecompressedxmltosequence(self)
-
Test the translation from compressed MusicXML with multiple rootfiles.
The example MXL file contains a MusicXML file of the Flute F Major scale, as well as the PNG rendering of the score contained within the single MXL file.
Expand source code
def testmultiplecompressedxmltosequence(self): """Test the translation from compressed MusicXML with multiple rootfiles. The example MXL file contains a MusicXML file of the Flute F Major scale, as well as the PNG rendering of the score contained within the single MXL file. """ uncompressed_musicxml = musicxml_parser.MusicXMLDocument( self.flute_scale_filename) compressed_musicxml = musicxml_parser.MusicXMLDocument( self.multiple_rootfile_compressed_filename) uncompressed_proto = musicxml_reader.musicxml_to_sequence_proto( uncompressed_musicxml) self.check_musicxml_and_sequence(compressed_musicxml, uncompressed_proto) self.check_fmajor_scale(self.flute_scale_filename, 'Flute')
def testrhythmdurationsxmltosequence(self)
-
Test the rhythm durations MusicXML file.
Expand source code
def testrhythmdurationsxmltosequence(self): """Test the rhythm durations MusicXML file.""" self.check_musicxml_to_sequence(self.rhythm_durations_filename)
def testsimplemusicxmltosequence(self)
-
Test the simple flute scale MusicXML file.
Expand source code
def testsimplemusicxmltosequence(self): """Test the simple flute scale MusicXML file.""" self.check_musicxml_to_sequence(self.flute_scale_filename) self.check_fmajor_scale(self.flute_scale_filename, 'Flute')
def testtransposedxmltosequence(self)
-
Test the translation from transposed MusicXML to Sequence proto.
Compare a transposed MusicXML file (clarinet) to an identical untransposed sequence (flute).
Expand source code
def testtransposedxmltosequence(self): """Test the translation from transposed MusicXML to Sequence proto. Compare a transposed MusicXML file (clarinet) to an identical untransposed sequence (flute). """ untransposed_musicxml = musicxml_parser.MusicXMLDocument( self.flute_scale_filename) transposed_musicxml = musicxml_parser.MusicXMLDocument( self.clarinet_scale_filename) untransposed_proto = musicxml_reader.musicxml_to_sequence_proto( untransposed_musicxml) self.check_musicxml_and_sequence(transposed_musicxml, untransposed_proto) self.check_fmajor_scale(self.clarinet_scale_filename, 'Clarinet in Bb')
Inherited members