def testBeams(self): beam_1 = musicscore_pb2.LineSegment(start=Point(x=10, y=20), end=Point(x=40, y=20)) beam_2 = musicscore_pb2.LineSegment(start=Point(x=70, y=40), end=Point(x=90, y=40)) beam_3 = musicscore_pb2.LineSegment(start=Point(x=70, y=60), end=Point(x=90, y=60)) staff = musicscore_pb2.Staff( staffline_distance=10, center_line=[Point(x=0, y=50), Point(x=100, y=50)], glyph=[ Glyph(type=Glyph.CLEF_TREBLE, x=1, y_position=reader.TREBLE_CLEF_EXPECTED_Y), # 2 eighth notes. Glyph(type=Glyph.NOTEHEAD_FILLED, x=10, y_position=-4, beam=[beam_1]), Glyph(type=Glyph.NOTEHEAD_FILLED, x=40, y_position=-1, beam=[beam_1]), # 1 quarter note. Glyph(type=Glyph.NOTEHEAD_FILLED, x=50, y_position=0), # 2 sixteenth notes. Glyph(type=Glyph.NOTEHEAD_FILLED, x=60, y_position=-2, beam=[beam_2, beam_3]), Glyph(type=Glyph.NOTEHEAD_FILLED, x=90, y_position=2, beam=[beam_2, beam_3]), ]) notes = conversions.page_to_notesequence( reader.ScoreReader().read_page( musicscore_pb2.Page( system=[musicscore_pb2.StaffSystem(staff=[staff])]))) self.assertEqual( notes, music_pb2.NoteSequence(notes=[ Note(pitch=librosa.note_to_midi('E4'), start_time=0, end_time=0.5), Note(pitch=librosa.note_to_midi('A4'), start_time=0.5, end_time=1), Note(pitch=librosa.note_to_midi('B4'), start_time=1, end_time=2), Note(pitch=librosa.note_to_midi('G4'), start_time=2, end_time=2.25), Note(pitch=librosa.note_to_midi('D5'), start_time=2.25, end_time=2.5), ]))
def testDummy(self): # Create a single staff, and a single vertical which is the correct height # of a stem. The vertical has x = 20 and goes from struct = structure.Structure( staff_detector=staves_base.ComputedStaves( staves=[[[10, 50], [90, 50]]], staffline_distance=[12], staffline_thickness=2, staves_interpolated_y=[[50] * 100]), beams=beams.ComputedBeams(np.zeros((0, 2, 2))), verticals=verticals.ComputedVerticals( lines=[[[20, 38], [20, 38 + 12 * 4]]]), connected_components=components.ComputedComponents([])) stems = stems_module.Stems(struct) # Create a Page with Glyphs. input_page = musicscore_pb2.Page(system=[ musicscore_pb2.StaffSystem(staff=[ musicscore_pb2.Staff( staffline_distance=12, center_line=[ musicscore_pb2.Point(x=10, y=50), musicscore_pb2.Point(x=90, y=50) ], glyph=[ # Cannot have a stem because it's a flat. musicscore_pb2.Glyph(type=musicscore_pb2.Glyph.FLAT, x=15, y_position=-1), # On the right side of the stem, the correct distance away. musicscore_pb2.Glyph( type=musicscore_pb2.Glyph.NOTEHEAD_FILLED, x=25, y_position=-1), # Too high for the stem. musicscore_pb2.Glyph( type=musicscore_pb2.Glyph.NOTEHEAD_FILLED, x=25, y_position=4), # Too far right from the stem. musicscore_pb2.Glyph( type=musicscore_pb2.Glyph.NOTEHEAD_FILLED, x=35, y_position=-1), ]) ]) ]) page = stems.apply(input_page) self.assertFalse(page.system[0].staff[0].glyph[0].HasField("stem")) self.assertTrue(page.system[0].staff[0].glyph[1].HasField("stem")) self.assertEqual( page.system[0].staff[0].glyph[1].stem, musicscore_pb2.LineSegment(start=Point(x=20, y=38), end=Point(x=20, y=38 + 12 * 4))) self.assertFalse(page.system[0].staff[0].glyph[2].HasField("stem")) self.assertFalse(page.system[0].staff[0].glyph[3].HasField("stem"))
def apply(self, page): """Detects stems on the page. Using `self.stem_candidates`, finds verticals that align with a notehead glyph, and adds the stems. Args: page: The Page message. Returns: The same page, updated with stems. """ for system in page.system: for staff, staff_ys in zip( system.staff, self.staff_detector.staves_interpolated_y): allowed_distance = np.multiply( _STEM_NOTEHEAD_DISTANCE_STAFFLINE_DISTANCE, staff.staffline_distance) expected_horizontal_distance = np.multiply( _STEM_NOTEHEAD_HORIZONTAL_STAFFLINE_DISTANCE, staff.staffline_distance) for glyph in staff.glyph: if glyph_types.is_stemmed_notehead(glyph): glyph_y = ( staff_ys[glyph.x] - glyph.y_position * staff.staffline_distance / 2.0) # Compute the ideal coordinates for the glyph to be assigned to each # stem. # Clip the glyph_y to the stem start and end y to get the ideal y. ideal_y = np.clip(glyph_y, self.stem_candidates[:, 0, 1], self.stem_candidates[:, 1, 1]) # If the glyph is left of the stem, subtract the expected distance # from the stem x; otherwise, add it. ideal_x = self.stem_candidates[:, 0, 0] + np.where( glyph.x < self.stem_candidates[:, 0, 0], -expected_horizontal_distance, expected_horizontal_distance) stem_distance = np.linalg.norm( np.c_[ideal_x - glyph.x, ideal_y - glyph_y], axis=1) stem = np.argmin(stem_distance) if stem_distance[stem] <= allowed_distance: stem_coords = self.stem_candidates[stem] glyph.stem.CopyFrom( musicscore_pb2.LineSegment( start=musicscore_pb2.Point( x=stem_coords[0, 0], y=stem_coords[0, 1]), end=musicscore_pb2.Point( x=stem_coords[1, 0], y=stem_coords[1, 1]))) return page
def apply(self, page): """Adds beams that intersect with note stems to the page. Beams should intersect with two or more stems. Beams are currently implemented as a bounding box, so we just see whether that box intersects with each stem. Args: page: A Page message. Returns: The same page, with `beam`s added to the `Glyph`s. """ for system in page.system: for staff in system.staff: # Extend the beams by the staffline distance on either side. Beams may # end immediately at a stem, so give an extra allowance for that stem. extended_beams = self.beams.copy() extended_beams[:, COLUMNS.X0] -= staff.staffline_distance extended_beams[:, COLUMNS.X1] += staff.staffline_distance for glyph in staff.glyph: if glyph_types.is_beamed_notehead( glyph) and glyph.HasField('stem'): xs = [glyph.stem.start.x, glyph.stem.end.x] ys = [glyph.stem.start.y, glyph.stem.end.y] stem_bounding_box = np.asarray([[min(*xs), min(*ys)], [max(*xs), max(*ys)]]) overlapping_beams = _get_overlapping_beams( stem_bounding_box, extended_beams) glyph.beam.extend( musicscore_pb2.LineSegment( start=musicscore_pb2.Point(x=beam[COLUMNS.X0], y=beam[COLUMNS.Y0]), end=musicscore_pb2.Point(x=beam[COLUMNS.X1], y=beam[COLUMNS.Y1])) for beam in overlapping_beams) return page
def testChords(self): stem_1 = musicscore_pb2.LineSegment(start=Point(x=20, y=10), end=Point(x=20, y=70)) stem_2 = musicscore_pb2.LineSegment(start=Point(x=50, y=10), end=Point(x=50, y=70)) staff = musicscore_pb2.Staff( staffline_distance=10, center_line=[Point(x=0, y=50), Point(x=100, y=50)], glyph=[ Glyph(type=Glyph.CLEF_TREBLE, x=1, y_position=reader.TREBLE_CLEF_EXPECTED_Y), # Chord of 2 notes. Glyph(type=Glyph.NOTEHEAD_FILLED, x=10, y_position=-4, stem=stem_1), Glyph(type=Glyph.NOTEHEAD_FILLED, x=10, y_position=-1, stem=stem_1), # Note not attached to a stem. Glyph(type=Glyph.NOTEHEAD_FILLED, x=30, y_position=3), # Chord of 3 notes. Glyph(type=Glyph.NOTEHEAD_FILLED, x=40, y_position=0, stem=stem_2), Glyph(type=Glyph.NOTEHEAD_FILLED, x=60, y_position=2, stem=stem_2), Glyph(type=Glyph.NOTEHEAD_FILLED, x=60, y_position=4, stem=stem_2), ]) notes = conversions.page_to_notesequence( reader.ScoreReader().read_page( musicscore_pb2.Page( system=[musicscore_pb2.StaffSystem(staff=[staff])]))) self.assertEqual( notes, music_pb2.NoteSequence(notes=[ # First chord. Note(pitch=librosa.note_to_midi('E4'), start_time=0, end_time=1), Note(pitch=librosa.note_to_midi('A4'), start_time=0, end_time=1), # Note without a stem. Note(pitch=librosa.note_to_midi('E5'), start_time=1, end_time=2), # Second chord. Note(pitch=librosa.note_to_midi('B4'), start_time=2, end_time=3), Note(pitch=librosa.note_to_midi('D5'), start_time=2, end_time=3), Note(pitch=librosa.note_to_midi('F5'), start_time=2, end_time=3), ]))
def testSmallScore(self): score = musicscore_pb2.Score(page=[ musicscore_pb2.Page(system=[ musicscore_pb2.StaffSystem(staff=[ musicscore_pb2.Staff(glyph=[ musicscore_pb2.Glyph( type=musicscore_pb2.Glyph.NOTEHEAD_FILLED, x=10, y_position=0, note=music_pb2.NoteSequence.Note( start_time=0, end_time=1, pitch=71)), musicscore_pb2.Glyph( type=musicscore_pb2.Glyph.NOTEHEAD_EMPTY, x=110, y_position=-6, note=music_pb2.NoteSequence.Note( start_time=1, end_time=2.5, pitch=61)), ]), musicscore_pb2.Staff(glyph=[ musicscore_pb2.Glyph( type=musicscore_pb2.Glyph.NOTEHEAD_WHOLE, x=10, y_position=2, note=music_pb2.NoteSequence.Note( start_time=0, end_time=4, pitch=50)), musicscore_pb2.Glyph( type=musicscore_pb2.Glyph.NOTEHEAD_FILLED, beam=[ musicscore_pb2.LineSegment(), musicscore_pb2.LineSegment() ], x=110, y_position=-4, note=music_pb2.NoteSequence.Note( start_time=4, end_time=4.25, pitch=60)), ]), ]), ]), ]) self.assertEqual( b"""<?xml version='1.0' encoding='UTF-8'?> <!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>Part 1</part-name> </score-part> <score-part id="P2"> <part-name>Part 2</part-name> </score-part> </part-list> <part id="P1"> <measure number="1"> <attributes> <divisions>1024</divisions> <time symbol="common"> <beats>4</beats> <beat-type>4</beat-type> </time> </attributes> <note> <voice>1</voice> <type>quarter</type> <duration>1024</duration> <pitch> <step>B</step> <alter>0</alter> <octave>4</octave> </pitch> </note> <note> <voice>1</voice> <type>half</type> <duration>1536</duration> <pitch> <step>C</step> <alter>1</alter> <octave>4</octave> </pitch> </note> </measure> </part> <part id="P2"> <measure number="1"> <attributes> <divisions>1024</divisions> <time symbol="common"> <beats>4</beats> <beat-type>4</beat-type> </time> </attributes> <note> <voice>1</voice> <type>whole</type> <duration>4096</duration> <pitch> <step>D</step> <alter>0</alter> <octave>3</octave> </pitch> </note> <note> <voice>1</voice> <type>16th</type> <duration>256</duration> <pitch> <step>C</step> <alter>0</alter> <octave>4</octave> </pitch> </note> </measure> </part> </score-partwise> """, musicxml.score_to_musicxml(score))
def testDummy(self): # Create a single staff, and a single vertical which is the correct height # of a stem. The vertical has x = 20 and goes from struct = structure.Structure( staff_detector=staves_base.ComputedStaves( staves=[[[10, 50], [90, 50]], [[11, 150], [91, 150]], [[10, 250], [90, 250]], [[10, 350], [90, 350]]], staffline_distance=[12] * 4, staffline_thickness=2, staves_interpolated_y=[[50] * 100, [150] * 100, [250] * 100, [350] * 100]), beams=beams.ComputedBeams(np.zeros((0, 2, 2))), connected_components=components.ComputedComponents(np.zeros( (0, 5))), verticals=verticals.ComputedVerticals(lines=[ # Joins the first 2 staves. [[10, 50 - 12 * 2], [10, 150 + 12 * 2]], # Another barline, too close to the first one. [[12, 50 - 12 * 2], [12, 150 + 12 * 2]], # This barline is far enough, because the second barline was # skipped. [[13, 50 - 12 * 2], [13, 150 + 12 * 2]], # Single staff barlines are skipped. [[30, 50 - 12 * 2], [30, 50 + 12 * 2]], [[31, 150 - 12 * 2], [31, 150 + 12 * 2]], # Too close to a stem. [[70, 50 - 12 * 2], [70, 50 + 12 * 2]], # Too short. [[90, 50 - 12 * 2], [90, 50 + 12 * 2]], # Another barline which is kept. [[90, 50 - 12 * 2], [90, 150 + 12 * 2]], # Staff 1 has no barlines. # Staff 2 has 2 barlines. [[11, 350 - 12 * 2], [11, 350 + 12 * 2]], [[90, 350 - 12 * 2], [90, 350 + 12 * 2]], ])) barlines = barlines_module.Barlines(struct, close_barline_threshold=3) # Create a Page with Glyphs. input_page = musicscore_pb2.Page(system=[ musicscore_pb2.StaffSystem(staff=[ musicscore_pb2.Staff( staffline_distance=12, center_line=[ musicscore_pb2.Point(x=10, y=50), musicscore_pb2.Point(x=90, y=50) ], glyph=[ # Stem is close to the last vertical on the first staff, so # a barline will not be detected there. musicscore_pb2.Glyph( type=musicscore_pb2.Glyph.NOTEHEAD_FILLED, x=60, y_position=2, stem=musicscore_pb2.LineSegment( start=musicscore_pb2.Point(x=72, y=40), end=musicscore_pb2.Point(x=72, y=80))), ]), musicscore_pb2.Staff(staffline_distance=12, center_line=[ musicscore_pb2.Point(x=10, y=150), musicscore_pb2.Point(x=90, y=150) ]), musicscore_pb2.Staff(staffline_distance=12, center_line=[ musicscore_pb2.Point(x=10, y=250), musicscore_pb2.Point(x=90, y=250) ]), musicscore_pb2.Staff(staffline_distance=12, center_line=[ musicscore_pb2.Point(x=10, y=350), musicscore_pb2.Point(x=90, y=350) ]), ]) ]) page = barlines.apply(input_page) self.assertEqual(3, len(page.system)) self.assertEqual(2, len(page.system[0].staff)) self.assertItemsEqual([10, 13, 90], (bar.x for bar in page.system[0].bar)) self.assertEqual(1, len(page.system[1].staff)) self.assertEqual(0, len(page.system[1].bar)) self.assertEqual(1, len(page.system[2].staff)) self.assertEqual(2, len(page.system[2].bar)) self.assertItemsEqual([11, 90], (bar.x for bar in page.system[2].bar))