def fill_skips(self, voice): """ Add Skips at the end of the bar, so that it is filled. We assume that any elements already added are placed at the correct timepos. """ # nt = short for "next timepos", the timepos to start fill skips to if voice.get_timeposes_of(self): nt = voice.get_timeposes_of(self)[-1] nt = nt + voice.m_tdict[nt]['elem'][0].m_duration.get_rat_value() else: # we get here if the bar is empty nt = self.m_timepos default_skip = Rat(1, 4) # pos within default skip pp = nt - int(nt / default_skip) * default_skip if pp != Rat(0, 1): # Here we add a skip so that the next column will be X times # default_skip voice.set_elem([Skip(Duration.new_from_rat(default_skip - pp))], nt) nt += (default_skip - pp) # And the we fill the bar with Skips as long as default_skip. while nt < self.end(): voice.set_elem([Skip(Duration.new_from_rat(default_skip))], nt) nt += default_skip
def test_bug1(self): """ For each moment in time, all note-off events have to be done before the note-on events. This to avoid problems with the same note being played two times after each other in different tracks. """ t1 = Track() t1.set_patch(3) t1.note(4, 93, 127) t1.note(4, 95, 127) t2 = Track() t2.set_patch(4) t2.note(4, 95, 127) t2.note(4, 97, 127) self.assertEquals(list(MidiEventStream(t1, t2)), [('program-change', 0, 3), ('program-change', 1, 4), ('volume', 0, 100), ('note-on', 0, 93, 127), ('volume', 1, 100), ('note-on', 1, 95, 127), ('notelen-time', Rat(1, 4)), ('note-off', 0, 93, 127), ('note-off', 1, 95, 127), ('note-on', 0, 95, 127), ('note-on', 1, 97, 127), ('notelen-time', Rat(1, 4)), ('note-off', 0, 95, 127), ('note-off', 1, 97, 127)]) self.assertEquals( MidiEventStream(t1, t2).str_repr(details=1), "p0:3 p1:4 v0:100 n0:93 v1:100 n1:95 d1/4 o93 o95 n0:95 n1:97 d1/4 o95 o97" )
def test_1(self): bp = BarProxy(self.score.voice11, Rat(0, 1)) bp.remove_trailing(Rat(1, 4)) bp.pop_last_elem() bp.remove_skips() bp.repack() bp.fill_skips() bp.end()
def test_add_bar(self): self.assertIsInstance(self.score.add_bar(TimeSignature(4, 4)), Bar) self.score.add_bar(TimeSignature(4, 4)) self.score.add_bar(TimeSignature(1, 4)) self.score.add_bar(TimeSignature(1, 4)) self.assertEqual(self.score.m_bars[0].m_timepos, Rat(0, 1)) self.assertEqual(self.score.m_bars[1].m_timepos, Rat(1, 1)) self.assertEqual(self.score.m_bars[2].m_timepos, Rat(2, 1)) self.assertEqual(self.score.m_bars[3].m_timepos, Rat(9, 4))
def test_stem_down(self): self.score.voice11.append(Note.new_from_string("c'8")) self.score.voice11.append(Note.new_from_string("c'8"), const.DOWN) self.assertEqual( self.score.voice11.m_tdict[Rat(0, 1)]['elem'].m_stemdir, const.BOTH) self.assertEqual( self.score.voice11.m_tdict[Rat(1, 8)]['elem'].m_stemdir, const.DOWN)
def test_is_last(self): voice = self.score.voice11 voice.append(Note.new_from_string("c2")) voice.append(Note.new_from_string("d2")) voice.append(Note.new_from_string("f1")) voice.append(Note.new_from_string("g4")) self.assertTrue(voice.is_last(Rat(1, 2))) self.assertFalse(voice.is_last(Rat(0, 1))) self.assertTrue(voice.is_last(Rat(1, 2))) self.assertTrue(voice.is_last(Rat(1, 1))) self.assertFalse(voice.is_last(Rat(2, 1)))
def test_tuplets(self): n1 = Note(MusicalPitch.new_from_notename("g'"), Duration(8, 0, Rat(2, 3))) n2 = Note(MusicalPitch.new_from_notename("g'"), Duration(8, 0, Rat(2, 3))) n3 = Note(MusicalPitch.new_from_notename("g'"), Duration(8, 0, Rat(2, 3))) self.score.voice11.append(n1) self.score.voice11.append(n2) self.score.voice11.append(n3) self.score.voice11.tuplet(Rat(2, 3), const.UP, [n1, n2, n3])
def test_constructor(self): self.assertEquals(float(Rat(1, 4)), 0.25) self.assertEquals(float(Rat(9, 8)), 1.125) self.assertEquals(float(Rat(4, 4)), 1.0) # I was a little surprised by the following, that 4/4 is not # simplified to 1, but I also see that we need it this way # for time signatures. r = Rat(4, 4) self.assertEquals(float(r), 1.0) self.assertEquals(r.m_num, 4) self.assertEquals(r.m_den, 4)
def test_bar_fill_skips(self): n1 = Note(MusicalPitch.new_from_notename("g'"), Duration(4, 0)) self.score.voice11.append(n1) self.score.voice11.append(Rest(Duration.new_from_string("4"))) self.bp.fill_skips() self.assertTrue( isinstance(self.score.voice11.m_tdict[Rat(0, 1)]['elem'][0], Note)) self.assertTrue( isinstance(self.score.voice11.m_tdict[Rat(1, 4)]['elem'][0], Rest)) self.assertTrue( isinstance(self.score.voice11.m_tdict[Rat(1, 2)]['elem'][0], Skip)) self.assertTrue( isinstance(self.score.voice11.m_tdict[Rat(3, 4)]['elem'][0], Skip))
def get_rat_value(self): """ >>> A=Duration(4, 1, Rat(1, 1)) >>> B=Duration(4, 2, Rat(3, 5)) >>> A.get_rat_value(), B.get_rat_value() ((Rat 3/8), (Rat 21/80)) """ d = Rat(1, self.m_nh) if self.m_dots > 0: d = d + Rat(1, self.m_nh * 2) if self.m_dots > 1: d = d + Rat(1, self.m_nh * 4) return d * self.m_tuplet
def on_expose_event(self, darea, event): MusicDisplayer.on_expose_event(self, darea, event) dim = engravers.dimentions[self.m_fontsize] if self.m_cursor is not None: staff_centrum = dim.first_staff_ypos if self.m_cursor == 'erase': return if self.m_cursor == 'notehead': eng = engravers.NoteheadEngraver(Rat(0, 1), "20-tight", 0, self._yp, 2, 0, 0, 0) else: eng = engravers.AccidentalsEngraver(Rat(0, 1), "20-tight", {self._yp: [int(self.m_cursor)]}) eng.m_xpos = 50 eng.engrave(darea, self.black_gc, staff_centrum)
def test_add_partial_bar(self): self.score.add_partial_bar(Duration.new_from_string("4"), TimeSignature(4, 4)) self.score.add_bar(None) self.assertEqual(self.score.m_bars[0].m_timepos, Rat(0, 1)) self.assertEqual(self.score.m_bars[0].end(), Rat(1, 4)) self.assertEqual(self.score.m_bars[1].m_timepos, Rat(1, 4)) self.assertEqual(self.score.get_bar_at(Rat(0, 1)), self.score.m_bars[0]) self.assertEqual(self.score.get_bar_at(Rat(1, 4)), self.score.m_bars[1]) self.score.voice11.append(Note.new_from_string("c4")) self.score.voice11.append(Note.new_from_string("c1")) self.score.voice11.append(Note.new_from_string("c1"))
def test_set_clef(self): self.score.voice11.set_clef("violin") self.assertEqual(self.score.staff1.m_tdict[Rat(0, 1)]['clef'].m_name, "violin") self.score.voice11.set_clef("bass") # The last clef set is remembered self.assertEqual(self.score.staff1.m_tdict[Rat(0, 1)]['clef'].m_name, "bass") self.score.voice11.append(Note.new_from_string("c'8")) self.score.voice11.set_clef("treble") self.assertEqual(self.score.staff1.m_tdict[Rat(0, 1)]['clef'].m_name, "bass") self.assertEqual(self.score.staff1.m_tdict[Rat(1, 8)]['clef'].m_name, "treble")
def display_start_of_music(self): """ Callers must catch exceptions. """ fontsize = self.get_int('config/feta_font_size=20') try: if self.m_t.m_P.get_clue_music(): self.g_music_displayer.display( self.m_t.m_P.get_clue_music().get_mpd_music_string( self.m_t.m_P), fontsize) elif self.m_t.m_P.get_clue_end(): self.g_music_displayer.display(self.m_t.m_P.get_music(), fontsize, self.m_t.m_P.get_clue_end()) else: self.g_music_displayer.display(self.m_t.m_P.get_music(), fontsize, Rat(0, 1)) except mpd.MpdException, e: if self.m_t.m_P.get_clue_music(): e.m_mpd_varname = 'clue_music' else: e.m_mpd_varname = 'music' self.m_t.m_P.get_question()['music'].complete_to_musicdata_coords( self.m_t.m_P, e) if 'm_mpd_badcode' not in dir(e): e.m_mpd_badcode = self.m_t.m_P.get_question()[ e.m_mpd_varname].get_err_context(e, self.m_t.m_P) raise
def test_simple1(self): t = Track() t.note(4, 90, 127) self.assertEqual(list(MidiEventStream(t)), [('program-change', 0, 0), ('volume', 0, 100), ('note-on', 0, 90, 127), ('notelen-time', Rat(1, 4)), ('note-off', 0, 90, 127)])
def concat2(s1, s2): """ Return a new Score object concatenating the two scores. This is intended return value is intended for playback only, since the staffs placed below each other. So the first score will have empty bars at the end, and the last score will have empty bars at the beginning. """ assert isinstance(s1, Score) assert isinstance(s2, Score) ret = s1.copy() if s1.m_bars: start = s1.m_bars[-1].end() else: start = Rat(0, 1) for bar in s2.m_bars: ret.m_bars.append(Bar(bar.m_timesig, ret.m_bars[-1].end())) for staff_idx, staff in enumerate(s2.m_staffs): ret.add_staff(staff_class=staff.__class__) for k in staff.m_tdict: ret.m_staffs[-1].m_tdict[start + k] = staff.m_tdict[k] for voice_idx in range(len(staff.m_voices)): if voice_idx != 0: ret.m_staffs[-1].add_voice() # This line make the music from sc2 continue after the # point where the music from sc1 ends. ret.m_staffs[-1].m_voices[-1].m_length = s1.m_bars[-1].end() for elem in s2.m_staffs[staff_idx].m_voices[voice_idx]: ret.m_staffs[-1].m_voices[-1].append(elem['elem']) ret.create_shortcuts() return ret
def _get_new_bar_timepos(self): """ Return the timepos where the next bar will be added. """ if self.m_bars: return self.m_bars[-1].end() return Rat(0, 1)
def concat(s1, s2): """ Concatenate the two scores, and return a new score. Both scores need to have the exact same staff and voice layout. """ assert isinstance(s1, Score) assert isinstance(s2, Score) if len(s1.m_staffs) != len(s2.m_staffs): raise Score.StaffCountException() if [type(x) for x in s1.m_staffs] != [type(x) for x in s2.m_staffs]: raise Score.StaffTypeException() for st1, st2 in zip(s1.m_staffs, s2.m_staffs): if len(st1.m_voices) != len(st2.m_voices): raise Score.VoiceCountException() ret = s1.copy() if not s1.m_staffs: return s1 # do the adding for bar in s2.m_bars: bar.m_timepos = ret.m_bars[-1].end() ret.m_bars.append(bar) ret.create_shortcuts() if s1.m_bars: start = s1.m_bars[-1].end() else: start = Rat(0, 1) s2.create_shortcuts() # FIXME why? for staff_idx, staff in enumerate(s1.m_staffs): for voice_idx in range(len(staff.m_voices)): for k in s2.m_staffs[staff_idx].m_voices[voice_idx].m_tdict: ret.m_staffs[staff_idx].m_voices[voice_idx].m_tdict[ k + start] = s2.m_staffs[staff_idx].m_voices[ voice_idx].m_tdict[k] return ret
def __init__(self, duration, dots): """duration: integer 1 for 1/1 note 4 for 1/4 etc """ if duration: self.m_duration = Duration(duration, dots, Rat(1, 1)) else: self.m_duration = None
def test_1voice_setpatch(self): t = Track() t.note(4, 90, 127) t.set_patch(3) t.note(4, 91, 127) self.assertEquals(list(MidiEventStream(t)), [ ('program-change', 0, 0), ('program-change', 1, 3), ('volume', 0, 100), ('note-on', 0, 90, 127), ('notelen-time', Rat(1, 4)), ('note-off', 0, 90, 127), ('volume', 1, 100), ('note-on', 1, 91, 127), ('notelen-time', Rat(1, 4)), ('note-off', 1, 91, 127), ])
def remove_trailing(self, voice, duration): """ Remove elements from the end of the bar, until their duration is a least 'duration' long. """ assert isinstance(duration, Rat) total = Rat(0, 1) while total < duration: total += self.pop_last_elem(voice)
def copy(self, parent): """ Return a copy of this Voice object. We make a copy of the dict and the m_length variable, but the dict revers to the same object. """ ret = Voice(parent) ret.m_length = Rat(self.m_length.m_num, self.m_length.m_den) ret.m_tdict = self.m_tdict.copy() return ret
def __init__(self, nh, dots, tuplet=Rat(1, 1)): """ nh - the type of note: 1 2 4 8 16 32 etc dots - the number of dots after the notehead tuplet - for example 2/3 for triplets """ self.m_nh = nh self.m_dots = dots self.m_tuplet = tuplet
def test_rh_1(self): self.assertEqual(f3(r"\staff{c}"), [[True, Rat(1, 4)]]) self.assertEqual(f3(r"\staff{c2}"), [[True, Rat(1, 2)]]) self.assertEqual(f3(r"\staff{c4 c8}"), [ [True, Rat(1, 4)], [True, Rat(1, 8)], ]) self.assertEqual(f3(r"\staff{c4 c4}"), [ [True, Rat(1, 4)], [True, Rat(1, 4)], ]) self.assertEqual(f3(r"\staff{c4. c4}"), [ [True, Rat(3, 8)], [True, Rat(1, 4)], ]) self.assertEqual(f3(r"\staff{c4~ c8 c4}"), [ [True, Rat(3, 8)], [True, Rat(1, 4)], ])
def test_rh_rest(self): self.assertEqual(f3(r"\staff{c4 r8 c4}"), [ [True, Rat(1, 4)], [False, Rat(1, 8)], [True, Rat(1, 4)], ]) self.assertEqual(f3(r"\staff{c4 r8 r16 c4}"), [ [True, Rat(1, 4)], [False, Rat(3, 16)], [True, Rat(1, 4)], ]) self.assertEqual(f3(r"\staff{c4 c8 c8}"), [ [True, Rat(1, 4)], [True, Rat(1, 8)], [True, Rat(1, 8)], ])
def get_free_time(self, voice): """ Return the duration, as a Rat value, on the end of the bar consisting of Rests and Skips. """ d = Rat(0, 1) for timepos in reversed(voice.get_timeposes_of(self)): if not isinstance(voice.m_tdict[timepos]['elem'][0], (Skip, Rest)): break d += voice.m_tdict[timepos]['elem'][0].m_duration.get_rat_value() return d
def test_add_note(self): self.score.voice11.append(Note.new_from_string("c'4")) self.score.voice11.append(Note.new_from_string("c'2")) self.score.voice11.append(Note.new_from_string("c'8")) self.assertIsInstance(self.score.voice11.m_tdict[Rat(0, 1)]['elem'][0], Note) self.assertIsInstance( self.score.voice11.m_tdict[Rat(0, 1)]['elem'][0].w_parent(), Stem) self.assertIsInstance( self.score.voice11.m_tdict[Rat( 0, 1)]['elem'][0].w_parent().w_parent(), Voice) # bar full: self.assertRaises(Voice.BarFullException, self.score.voice11.append, Note.new_from_string("c'4")) self.score.voice11.append(Note.new_from_string("c'8")) self.score.voice11.append([ Note.new_from_string("c'1"), Note.new_from_string("e'1"), Note.new_from_string("g'1"), ])
def calculate_event_times(self): """ Set the variable m_time on each Event. Well actually we don't set it on the Delay events because events of that type does not generate any events when generating music. """ pos = Rat(0, 1) for e in self.m_v: if isinstance(e, Delay): pos += e.m_duration else: e.m_time = pos
def generate_track_for_voice(self, voice, kv, tracktype): D = self.get_event_dict(voice, kv) keys = D.keys() keys.sort() prev_time = Rat(0) ms = tracktype() for k in keys: delta = None if k != Rat(0, 1): delta = k - prev_time prev_time = k for e in D[k]: if e[1] == START_NOTE: if delta: ms.notelen_time(delta) ms.start_note(e[2], const.DEFAULT_VELOCITY) elif e[1] == STOP_NOTE: if delta: ms.notelen_time(delta) ms.stop_note(e[2], const.DEFAULT_VELOCITY) delta = None return ms
def notelen_time(self, notelen): """ To avoid having to alter all code calling this, we interpret notelen in two different ways depending on its type: int: replace to Rat(1, notelen) Rat: the value tell the note length. For example Rat(1, 4) for a quarter note. """ if isinstance(notelen, int): self.m_v.append(Delay(Rat(1, notelen))) else: assert isinstance(notelen, Rat) self.m_v.append(Delay(notelen))