def make_assignment_mode_example(): # create a midi file on which to test the assignment modes in load_midi part_1 = score.Part('P1') part_2 = score.Part('P2') part_3 = score.Part('P3') part_1.set_quarter_duration(0, 1) part_2.set_quarter_duration(0, 2) part_3.set_quarter_duration(0, 3) part_1.add(score.TimeSignature(4, 4), 0) part_1.add(score.Note(step='C', octave=4, voice=1), 0, 1) part_1.add(score.Note(step='B', octave=4, voice=2), 0, 2) part_1.add(score.Note(step='B', octave=4, voice=2), 2, 4) part_1.add(score.Note(step='B', octave=4, voice=2), 5, 6) part_1.add(score.Note(step='B', octave=4, voice=3), 7, 10) part_2.add(score.TimeSignature(4, 4), 0) part_2.add(score.Tempo(80), 0) part_2.add(score.Note(step='D', octave=5, voice=1), 0, 1) part_2.add(score.Note(step='E', octave=5, voice=2), 1, 2) part_2.add(score.Note(step='F', octave=5, voice=2), 2, 3) part_2.add(score.Note(step='G', octave=5, voice=2), 3, 4) part_3.add(score.TimeSignature(4, 4), 0) part_3.add(score.Note(step='G', octave=4, voice=1), 0, 3) pg = score.PartGroup(group_name='P1/P2') pg.children = [part_1, part_2] for p in pg.children: p.parent = pg return [pg, part_3]
def make_part_tuplet(): # create a part part = score.Part('My Part') # create contents divs = 12 ts = score.TimeSignature(3, 4) page1 = score.Page(1) system1 = score.System(1) note1 = score.Note(id='n0', step='A', octave=4, voice=1, staff=1) rest1 = score.Rest(voice=1, staff=1) note2 = score.Note(id='n2', step='C', octave=4, voice=1, staff=1) rest2 = score.Rest(id='r0', voice=1, staff=1) # and add the contents to the part: part.set_quarter_duration(0, divs) part.add(ts, 0) part.add(page1, 0) part.add(system1, 0) part.add(note1, 0, 8) part.add(rest1, 8, 16) part.add(note2, 16, 24) part.add(rest2, 24, 36) score.add_measures(part) score.find_tuplets(part) score.set_end_times(part) return part
def test_midi_export_anacrusis(self): part = score.Part("id") # 1 div is 1 quarter part.set_quarter_duration(0, 1) # 4/4 at t=0 part.add(score.TimeSignature(4, 4), 0) # ANACRUSIS # quarter note from t=0 to t=1 part.add(score.Note("c", 4), 0, 1) # incomplete measure from t=0 to t=1 part.add(score.Measure(), 0, 1) # whole note from t=1 to t=5 part.add(score.Note("c", 4), 1, 5) # add missing measures score.add_measures(part) # print(part.pretty()) mid = export_and_read(part, anacrusis_behavior="shift") t = 0 for msg in mid.tracks[0]: t += msg.time if msg.type == "note_on": assert t == 0 break mid = export_and_read(part, anacrusis_behavior="pad_bar") t = 0 for msg in mid.tracks[0]: t += msg.time if msg.type == "note_on": assert t == 3, f"Incorrect time of first note on: {t} (should be 3)" break
def do_test( self, q_times, q_durs, note_ons, note_offs, new_q_time, new_q_dur, new_note_ons, new_note_offs, ): part = score.Part("P0") for q_time, q_dur in zip(q_times, q_durs): part.set_quarter_duration(q_time, q_dur) for i, (note_on, note_off) in enumerate(zip(note_ons, note_offs)): n = score.Note(id="n{}".format(i), step="C", octave=4, voice=1) part.add(n, note_on, note_off) for q_time, q_dur in zip(new_q_time[::-1], new_q_dur[::-1]): part.set_quarter_duration(q_time, q_dur, adjust_times=True) for n, start, end in zip(part.notes, new_note_ons, new_note_offs): msg = "Note onset {} should be {}".format(n.start.t, start) self.assertEqual(n.start.t, start, msg) msg = "Note offset {} should be {}".format(n.end.t, end) self.assertEqual(n.end.t, end, msg)
def test_export_import_pprint(self): # create a part part1 = score.Part('My Part') # create contents divs = 10 ts = score.TimeSignature(3, 4) page1 = score.Page(1) system1 = score.System(1) measure1 = score.Measure(number=1) note1 = score.Note(step='A', octave=4, voice=1, staff=1) rest1 = score.Rest(voice=1, staff=1) note2 = score.Note(step='C', octave=5, alter=-1, voice=2, staff=1) # and add the contents to the part: part1.set_quarter_duration(0, divs) part1.add(ts, 0) part1.add(measure1, 0, 30) part1.add(page1, 0) part1.add(system1, 0) part1.add(note1, 0, 15) part1.add(rest1, 15, 30) part1.add(note2, 0, 30) score.set_end_times(part1) # pretty print the part pstring1 = part1.pretty() with TemporaryFile() as f: # save part to musicxml save_musicxml(part1, f) f.flush() f.seek(0) # load part from musicxml part2 = load_musicxml(f) # pretty print saved/loaded part: pstring2 = part2.pretty() # test pretty printed strings for equality equal = pstring1 == pstring2 if not equal: show_diff(pstring1, pstring2) msg = 'Exported and imported score does not yield identical pretty printed representations' self.assertTrue(equal, msg)
def test_times_2(self): # 6/8 anacrusis part = score.Part("id") # 2 divs is 1 quarter part.set_quarter_duration(0, 2) part.add(score.TimeSignature(6, 8), 0) # ANACRUSIS part.add(score.Note("c", 4), 0, 3) part.add(score.Measure(), 0, 3) part.add(score.Note("c", 4), 3, 9) score.add_measures(part) time_pairs = [(-3, -1.5), (0, 0), (6, 3)] test_time_pairs(part, time_pairs)
def test_notearray_1(self): part = score.Part("P0", "My Part") part.set_quarter_duration(0, 10) part.add(score.TimeSignature(3, 4), start=0) part.add(score.Note(id="n0", step="A", octave=4), start=0, end=10) note_array = part.note_array self.assertTrue(len(note_array) == 1)
def test_times_1(self): # 4/4 anacrusis part = score.Part("id") # 1 div is 1 quarter part.set_quarter_duration(0, 1) # 4/4 at t=0 part.add(score.TimeSignature(4, 4), 0) # ANACRUSIS # quarter note from t=0 to t=1 part.add(score.Note("c", 4), 0, 1) # incomplete measure from t=0 to t=1 part.add(score.Measure(), 0, 1) # whole note from t=1 to t=5 part.add(score.Note("c", 4), 1, 5) # add missing measures score.add_measures(part) time_pairs = [(-1, -1), (0, 0), (4, 4)] test_time_pairs(part, time_pairs)
def do_test(self, q_times, q_durs, note_ons, note_offs, factor, new_note_ons, new_note_offs): part = score.Part("P0") for q_time, q_dur in zip(q_times, q_durs): part.set_quarter_duration(q_time, q_dur) for i, (note_on, note_off) in enumerate(zip(note_ons, note_offs)): n = score.Note(id="n{}".format(i), step="C", octave=4, voice=1) part.add(n, note_on, note_off) part.multiply_quarter_durations(factor) for n, start, end in zip(part.notes, new_note_ons, new_note_offs): msg = "Note {} onset {} should be {}".format( n.id, n.start.t, start) self.assertEqual(n.start.t, start, msg) msg = "Note {} offset {} should be {}".format(n.id, n.end.t, end) self.assertEqual(n.end.t, end, msg)
def create_part_from_spec(spec): """Create a part from a specification of divisions, time signatures and notes. This is a helper function for the TestBeatMap test case. Parameters ---------- spec : dictionary Part specification Returns ------- Part Part instance """ part = score.Part('beatmaptest') # for t, divs in spec['divs']: # part.add(score.Divisions(divs), t) for t, divs in spec['divs']: part.set_quarter_duration(t, divs) for t, num, den in spec['ts']: part.add(score.TimeSignature(num, den), t) # divs_map = part.divisions_map for t, dur in spec['notes']: # sd = score.estimate_symbolic_duration(dur, int(divs_map(t))) part.add(score.Note(step='A', alter=None, octave=4), t, t + dur) score.add_measures(part) return part
def make_part_slur(): # create a part part = score.Part('My Part') # create contents divs = 12 ts = score.TimeSignature(3, 4) page1 = score.Page(1) system1 = score.System(1) note0 = score.Note(id='n0', step='A', octave=4, voice=1, staff=1) note1 = score.Note(id='n1', step='A', octave=4, voice=1, staff=1) note2 = score.Note(id='n2', step='A', octave=4, voice=1, staff=1) note3 = score.Note(id='n3', step='A', octave=4, voice=1, staff=1) note4 = score.Note(id='n4', step='A', octave=3, voice=2, staff=1) note5 = score.Note(id='n5', step='A', octave=3, voice=2, staff=1) slur1 = score.Slur(start_note=note0, end_note=note5) slur2 = score.Slur(start_note=note4, end_note=note3) # and add the contents to the part: part.set_quarter_duration(0, divs) part.add(ts, 0) part.add(page1, 0) part.add(system1, 0) part.add(note0, 0, 12) part.add(note1, 12, 24) part.add(note2, 24, 36) part.add(note3, 36, 48) part.add(note4, 0, 6) part.add(note5, 6, 33) part.add(slur1, slur1.start_note.start.t, slur1.end_note.end.t) part.add(slur2, slur2.start_note.start.t, slur2.end_note.end.t) score.add_measures(part) score.tie_notes(part) score.set_end_times(part) return part
def _handle_note(e, position, part, ongoing, prev_note): # prev_note is used when the current note has a <chord/> tag # get some common features of element if available duration = get_value_from_tag(e, 'duration', int) or 0 # elements may have an explicit temporal offset # offset = get_value_from_tag(e, 'offset', int) or 0 staff = get_value_from_tag(e, 'staff', int) or None voice = get_value_from_tag(e, 'voice', int) or None note_id = get_value_from_attribute(e, 'id', str) symbolic_duration = {} dur_type = get_value_from_tag(e, 'type', str) if dur_type: symbolic_duration['type'] = dur_type dots = len(e.findall('dot')) if dots: symbolic_duration['dots'] = dots actual_notes = get_value_from_tag(e, 'time-modification/actual-notes', int) if actual_notes: symbolic_duration['actual_notes'] = actual_notes normal_notes = get_value_from_tag(e, 'time-modification/normal-notes', int) if normal_notes: symbolic_duration['normal_notes'] = normal_notes chord = e.find('chord') if chord is not None: # this note starts at the same position as the previous note, and has # same duration assert prev_note is not None position = prev_note.start.t articulations_e = e.find('notations/articulations') if articulations_e is not None: articulations = get_articulations(articulations_e) else: articulations = {} pitch = e.find('pitch') if pitch is not None: step = get_value_from_tag(pitch, 'step', str) alter = get_value_from_tag(pitch, 'alter', int) octave = get_value_from_tag(pitch, 'octave', int) grace = e.find('grace') if grace is not None: grace_type, steal_proportion = get_grace_info(grace) note = score.GraceNote(grace_type, step, octave, alter, note_id, voice=voice, staff=staff, symbolic_duration=symbolic_duration, articulations=articulations, steal_proportion=steal_proportion) if (isinstance(prev_note, score.GraceNote) and prev_note.voice == voice): note.grace_prev = prev_note else: note = score.Note(step, octave, alter, note_id, voice=voice, staff=staff, symbolic_duration=symbolic_duration, articulations=articulations) if (isinstance(prev_note, score.GraceNote) and prev_note.voice == voice): prev_note.grace_next = note else: # note element is a rest note = score.Rest(note_id, voice=voice, staff=staff, symbolic_duration=symbolic_duration, articulations=articulations) part.add(note, position, position+duration) ties = e.findall('tie') if len(ties) > 0: tie_key = ('tie', getattr(note, 'midi_pitch', 'rest')) tie_types = set(tie.attrib['type'] for tie in ties) if 'stop' in tie_types: tie_prev = ongoing.get(tie_key, None) if tie_prev: note.tie_prev = tie_prev tie_prev.tie_next = note del ongoing[tie_key] if 'start' in tie_types: ongoing[tie_key] = note notations = e.find('notations') if notations is not None: if notations.find('fermata') is not None: fermata = score.Fermata(note) part.add(fermata, position) note.fermata = fermata starting_slurs, stopping_slurs = handle_slurs(notations, ongoing, note, position) for slur in starting_slurs: part.add(slur, position) for slur in stopping_slurs: part.add(slur, end=position+duration) starting_tups, stopping_tups = handle_tuplets(notations, ongoing, note) for tup in starting_tups: part.add(tup, position) for tup in stopping_tups: part.add(tup, end=position+duration) new_position = position + duration return new_position, note
def create_part(ticks, notes, spellings, voices, note_ids, time_sigs, key_sigs, part_id=None, part_name=None): LOGGER.debug('create_part') part = score.Part(part_id, part_name=part_name) part.set_quarter_duration(0, ticks) clef = score.Clef(number=1, **estimate_clef_properties([pitch for _, pitch, _ in notes])) part.add(clef, 0) for t, name in key_sigs: fifths, mode = key_name_to_fifths_mode(name) part.add(score.KeySignature(fifths, mode), t) LOGGER.debug('add notes') for (onset, pitch, duration), (step, alter, octave), voice, note_id in zip(notes, spellings, voices, note_ids): if duration > 0: note = score.Note(step, octave, alter, voice=int(voice or 0), id=note_id, symbolic_duration=estimate_symbolic_duration(duration, ticks)) else: note = score.GraceNote('appoggiatura', step, octave, alter, voice=int(voice or 0), id=note_id, symbolic_duration=dict(type='quarter')) part.add(note, onset, onset+duration) if not time_sigs: warnings.warn('No time signatures found, assuming 4/4') time_sigs = [(0, 4, 4)] time_sigs = np.array(time_sigs, dtype=np.int) # for convenience we add the end times for each time signature ts_end_times = np.r_[time_sigs[1:, 0], np.iinfo(np.int).max] time_sigs = np.column_stack((time_sigs, ts_end_times)) LOGGER.debug('add time sigs and measures') for ts_start, num, den, ts_end in time_sigs: time_sig = score.TimeSignature(num.item(), den.item()) part.add(time_sig, ts_start.item()) score.add_measures(part) # this is the old way to add measures. Since part comes from MIDI we # only have a single global divs value, which makes add it easier to compute # measure durations: # measure_counter = 1 # # we call item() on numpy numbers to get the value in the equivalent python type # for ts_start, num, den, ts_end in time_sigs: # time_sig = score.TimeSignature(num.item(), den.item()) # part.add(time_sig, ts_start.item()) # measure_duration = (num.item() * ticks * 4) // den.item() # measure_start_limit = min(ts_end.item(), part.last_point.t) # for m_start in range(ts_start, measure_start_limit, measure_duration): # measure = score.Measure(number=measure_counter) # m_end = min(m_start+measure_duration, ts_end) # part.add(measure, m_start, m_end) # measure_counter += 1 # if np.isinf(ts_end): # ts_end = m_end LOGGER.debug('tie notes') # tie notes where necessary (across measure boundaries, and within measures # notes with compound duration) score.tie_notes(part) LOGGER.debug('find tuplets') # apply simplistic tuplet finding heuristic score.find_tuplets(part) LOGGER.debug('done create_part') return part
def part_from_matchfile(mf): part = score.Part('P1', mf.info('piece')) # snotes = sorted(mf.snotes, key=attrgetter('OnsetInBeats')) snotes = sort_snotes(mf.snotes) divs = np.lcm.reduce(np.unique([note.Offset.denominator * (note.Offset.tuple_div or 1) for note in snotes])) part.set_quarter_duration(0, divs) min_time = snotes[0].OnsetInBeats # sorted by OnsetInBeats max_time = max(n.OffsetInBeats for n in snotes) ts = mf.time_signatures beats_map, beat_type_map, min_time_q, max_time_q = make_timesig_maps(ts, max_time) bars = np.unique([n.Bar for n in snotes]) t = min_time t = t * 4 / beat_type_map(min_time_q) offset = t # bar map: bar_number-> start in quarters bar_times = {} for b0, b1 in iter_current_next(bars, start=0): bar_times.setdefault(b1, t) if t < 0: t = 0 else: # multiply by diff between consecutive bar numbers n_bars = b1 - b0 if t <= max_time_q: t += (n_bars * 4 * beats_map(t)) / beat_type_map(t) for note in snotes: # start of bar in quarter units bar_start = bar_times[note.Bar] # offset within bar in quarter units bar_offset = (note.Beat - 1) * 4 / beat_type_map(bar_start) # offset within beat in quarter units beat_offset = (4 * note.Offset.numerator / (note.Offset.denominator * (note.Offset.tuple_div or 1))) # # anacrusis if bar_start < 0: # in case of anacrusis we set the bar_start to -bar_duration (in # quarters) so that the below calculation is correct bar_start = - beats_map(bar_start) * 4 / beat_type_map(bar_start) # note onset in divs onset_divs = int(divs * (bar_start + bar_offset + beat_offset - offset)) # print(note.Anchor, onset_divs, bar_start, bar_offset, beat_offset, offset) articulations = set() if 'staccato' in note.ScoreAttributesList: articulations.add('staccato') if 'accent' in note.ScoreAttributesList: articulations.add('accent') # dictionary with keyword args with which the Note (or GraceNote) will be instantiated note_attributes = dict(step=note.NoteName, octave=note.Octave, alter=note.Modifier, id=note.Anchor, articulations=articulations) staff_nr = next((a[-1] for a in note.ScoreAttributesList if a.startswith('staff')), None) try: note_attributes['staff'] = int(staff_nr) except (TypeError, ValueError): # no staff attribute, or staff attribute does not end with a number note_attributes['staff'] = None note_attributes['voice'] = next((int(a) for a in note.ScoreAttributesList if NUMBER_PAT.match(a)), None) # get rid of this if as soon as we have a way to iterate over the # duration components. For now we have to treat the cases simple # and compound durations separately. if note.Duration.add_components: prev_part_note = None for i, (num, den, tuple_div) in enumerate(note.Duration.add_components): # when we add multiple notes that are tied, the first note will # get the original note id, and subsequent notes will get a # derived note id (by appending, 'a', 'b', 'c',...) if i > 0: # tnote_id = 'n{}_{}'.format(note.Anchor, i) note_attributes['id'] = score.make_tied_note_id(note_attributes['id']) part_note = score.Note(**note_attributes) duration_divs = int(divs * 4 * num / (den * (tuple_div or 1))) assert duration_divs > 0 offset_divs = onset_divs + duration_divs part.add(part_note, onset_divs, offset_divs) if prev_part_note: prev_part_note.tie_next = part_note part_note.tie_prev = prev_part_note prev_part_note = part_note onset_divs = offset_divs else: num = note.Duration.numerator den = note.Duration.denominator tuple_div = note.Duration.tuple_div duration_divs = int(divs * 4 * num / (den * (tuple_div or 1))) offset_divs = onset_divs + duration_divs # notes with duration 0, are also treated as grace notes, even if # they do not have a 'grace' score attribute if ('grace' in note.ScoreAttributesList or note.Duration.numerator == 0): part_note = score.GraceNote('appoggiatura', **note_attributes) else: part_note = score.Note(**note_attributes) part.add(part_note, onset_divs, offset_divs) # add time signatures for (ts_beat_time, ts_bar, (ts_beats, ts_beat_type)) in ts: bar_start_divs = int(divs * (bar_times[ts_bar] - offset)) # in quarters part.add(score.TimeSignature(ts_beats, ts_beat_type), bar_start_divs) # add key signatures for (ks_beat_time, ks_bar, keys) in mf.key_signatures: if len(keys) > 1: # there are multple equivalent keys, so we check which one is most # likely according to the key estimator est_keys = estimate_key(notes_to_notearray(part.notes_tied), return_sorted_keys=True) idx = [est_keys.index(key) if key in est_keys else np.inf for key in keys] key_name = keys[np.argmin(idx)] else: key_name = keys[0] fifths, mode = key_name_to_fifths_mode(key_name) part.add(score.KeySignature(fifths, mode), 0) add_staffs(part) # add_clefs(part) # add incomplete measure if necessary if offset < 0: part.add(score.Measure(number=1), 0, int(-offset * divs)) # add the rest of the measures automatically score.add_measures(part) # print(part.pretty()) score.tie_notes(part) score.find_tuplets(part) if not all([n.voice for n in part.notes_tied]): # print('notes without voice detected') add_voices(part) return part
def _handle_note(e, position, part, ongoing, prev_note, doc_order): # get some common features of element if available duration = get_value_from_tag(e, "duration", int) or 0 # elements may have an explicit temporal offset # offset = get_value_from_tag(e, 'offset', int) or 0 staff = get_value_from_tag(e, "staff", int) or None voice = get_value_from_tag(e, "voice", int) or None # add support of uppercase "ID" tags note_id = (get_value_from_attribute(e, "id", str) if get_value_from_attribute(e, "id", str) else get_value_from_attribute(e, "ID", str)) symbolic_duration = {} dur_type = get_value_from_tag(e, "type", str) if dur_type: symbolic_duration["type"] = dur_type dots = len(e.findall("dot")) if dots: symbolic_duration["dots"] = dots actual_notes = get_value_from_tag(e, "time-modification/actual-notes", int) if actual_notes: symbolic_duration["actual_notes"] = actual_notes normal_notes = get_value_from_tag(e, "time-modification/normal-notes", int) if normal_notes: symbolic_duration["normal_notes"] = normal_notes chord = e.find("chord") if chord is not None: # this note starts at the same position as the previous note, and has # same duration assert prev_note is not None position = prev_note.start.t articulations_e = e.find("notations/articulations") if articulations_e is not None: articulations = get_articulations(articulations_e) else: articulations = {} pitch = e.find("pitch") if pitch is not None: step = get_value_from_tag(pitch, "step", str) alter = get_value_from_tag(pitch, "alter", int) octave = get_value_from_tag(pitch, "octave", int) grace = e.find("grace") if grace is not None: grace_type, steal_proportion = get_grace_info(grace) note = score.GraceNote( grace_type=grace_type, step=step, octave=octave, alter=alter, id=note_id, voice=voice, staff=staff, symbolic_duration=symbolic_duration, articulations=articulations, steal_proportion=steal_proportion, doc_order=doc_order, ) if isinstance(prev_note, score.GraceNote) and prev_note.voice == voice: note.grace_prev = prev_note else: note = score.Note( step=step, octave=octave, alter=alter, id=note_id, voice=voice, staff=staff, symbolic_duration=symbolic_duration, articulations=articulations, doc_order=doc_order, ) if isinstance(prev_note, score.GraceNote) and prev_note.voice == voice: prev_note.grace_next = note else: # note element is a rest note = score.Rest( id=note_id, voice=voice, staff=staff, symbolic_duration=symbolic_duration, articulations=articulations, doc_order=doc_order, ) part.add(note, position, position + duration) ties = e.findall("tie") if len(ties) > 0: tie_key = ("tie", getattr(note, "midi_pitch", "rest")) tie_types = set(tie.attrib["type"] for tie in ties) if "stop" in tie_types: tie_prev = ongoing.get(tie_key, None) if tie_prev: note.tie_prev = tie_prev tie_prev.tie_next = note del ongoing[tie_key] if "start" in tie_types: ongoing[tie_key] = note notations = e.find("notations") if notations is not None: if notations.find("fermata") is not None: fermata = score.Fermata(note) part.add(fermata, position) note.fermata = fermata starting_slurs, stopping_slurs = handle_slurs(notations, ongoing, note, position) for slur in starting_slurs: part.add(slur, position) for slur in stopping_slurs: part.add(slur, end=position + duration) starting_tups, stopping_tups = handle_tuplets(notations, ongoing, note) for tup in starting_tups: part.add(tup, position) for tup in stopping_tups: part.add(tup, end=position + duration) new_position = position + duration return new_position, note