def create_phrase(): n = Note2Midi() notes = ["c4", "e4", "g4", "c5", "b4", "g4", "f4", "d4", "c4"] from vectortween.NumberAnimation import NumberAnimation from vectortween.SequentialAnimation import SequentialAnimation increase = NumberAnimation(frm=Dyn.mp, to=Dyn.f) decrease = NumberAnimation(frm=Dyn.f, to=Dyn.ppp) swell_dim = SequentialAnimation([increase, decrease]) increasing_staccato = NumberAnimation(frm=1, to=0.5) properties = { # convert from note names to midi numbers PP.NOTE: Pseq(n.convert2(notes)), # last note is longer than the rest PP.DUR: Pseq([Pconst(Dur.eighth, len(notes) - 1), Pconst(Dur.whole, 1)]), # animate staccato PP.PLAYEDDUR: Ptween(increasing_staccato, 0, 0, len(notes), len(notes)), # volume should linearly go up from mp to f, then go down from f to ppp as the phrase progresses PP.VOL: Ptween(swell_dim, 0, 0, len(notes), len(notes), None), } p = Phrase(properties) p2m = Pat2Midi() p2m.set_tempo(120) total_dur = p2m.add_phrase(p) print(total_dur) p2m.write(outputfile)
def test_ccpropertiesgeneratorsforsection(self): m = Mispel() m.parse(r""" with track 0: a3\pdur[legato]\cc[15,100] b <c\cc{16,23} d4> e\cc[15,90] f a g\cc[16,40] b """) p = m.cc_properties_generators_for_section(0) dur15 = [d for d in Pseq(p[15][PP.ctrl_dur_key(15)], 1)] val15 = [v for v in Pseq(p[15][PP.ctrl_val_key(15)], 1)] dur16 = [d for d in Pseq(p[16][PP.ctrl_dur_key(16)], 1)] val16 = [v for v in Pseq(p[16][PP.ctrl_val_key(16)], 1)] self.assertListEqual(dur15, [0, 0.75, 1.25]) self.assertListEqual(val15, [None, 100, 90]) self.assertListEqual(dur16, [ 0.5, 0.025, 0.025, 0.025, 0.025, 0.025, 0.025, 0.025, 0.025, 0.025, 0.025, 0.025, 0.025, 0.025, 0.025, 0.025, 0.025, 0.025, 0.025, 0.025, 0.025, 0.025, 0.025, 0.025, 0.025, 0.025, 0.025, 0.025, 0.025, 0.025, 0.025, 0.025, 0.025, 0.025, 0.025, 0.025, 0.025, 0.025, 0.025, 0.025, 0.025, 0.5 ]) self.assertListEqual(val16, [ None, 23.0, 23.425, 23.85, 24.275, 24.7, 25.125, 25.55, 25.975, 26.4, 26.825, 27.25, 27.675, 28.1, 28.525, 28.95, 29.375, 29.8, 30.225, 30.65, 31.075, 31.5, 31.925, 32.35, 32.775, 33.2, 33.625, 34.05, 34.475, 34.9, 35.325, 35.75, 36.175, 36.6, 37.025, 37.45, 37.875, 38.3, 38.725, 39.15, 39.575, 40 ])
def test_cc(self): properties = { PP.NOTE: Pconst(67, 5), PP.DUR: Pconst(1 / 2, 5), "D35": Pconst(1 / 4, 10), "V35": Pseq([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 10), "D34": Pconst(1 / 8, 20), "V34": Pseq([ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 ], 20) } p = Phrase(properties) result = [] for event in p: result.append(event) self.assertEqual(len(result), 5 + 10 + 20) self.assertEqual(result[-2]["V35"], 9) self.assertEqual(result[-2]["D35"], 4 * 1 / 4) self.assertEqual(result[-12]["V34"], 19) self.assertEqual(result[-12]["D34"], 4 * 1 / 8)
def playeddur(cls, notes, kind="l2s"): """ :param notes: list of notes that make up the phrase :param kind: one of "l2s" (legato-to-staccato) or "s2l" (staccato-to-legato) :return: pattern that generates the desired playeddur values """ if kind == "l2s": anim = Pseq([ Pconst(PDur.legato, len(notes) - 1), Pconst(PDur.staccato, 1) ], 1) elif kind == "s2l": anim = Pseq([ Pconst(PDur.staccato, len(notes) - 1), Pconst(PDur.legato, 1) ], 1) elif kind == "s2s": anim = Pconst(PDur.staccato, len(notes)) elif kind == "l2l": anim = Pconst(PDur.legato, len(notes)) else: anim = None assert False return anim
def create_phrase(): p = Pat2Midi() n = Note2Midi() # vary the lag to create the rubato feeling lag_inc = NumberAnimation(frm=0, to=0.3, tween=['easeOutQuad']) lag_dec = NumberAnimation(frm=0.3, to=0, tween=['easeInOutCubic']) lag = SequentialAnimation([lag_inc, lag_dec], [1, 3], 2) # vary the volume over time to keep it interesting vol_inc1 = NumberAnimation(frm=Dyn.f, to=Dyn.ff, tween=['linear']) vol_dec1 = NumberAnimation(frm=Dyn.ff, to=Dyn.mf, tween=['linear']) vol_inc2 = NumberAnimation(frm=Dyn.mf, to=Dyn.f, tween=['linear']) vol_dec2 = NumberAnimation(frm=Dyn.f, to=Dyn.mp, tween=['linear']) vol_inc3 = NumberAnimation(frm=Dyn.mp, to=Dyn.mf, tween=['linear']) vol_dec3 = NumberAnimation(frm=Dyn.mf, to=Dyn.pp, tween=['linear']) vol = SequentialAnimation([vol_inc1, vol_dec1, vol_inc2, vol_dec2, vol_inc3, vol_dec3]) # and why not bounce a bit between legato and staccato while we're having fun? legato_to_staccato = NumberAnimation(frm=1, to=0.25, tween=['easeOutBounce']) staccato_to_legato = NumberAnimation(frm=0.1, to=1, tween=['easeOutBounce']) dur = SequentialAnimation([legato_to_staccato, staccato_to_legato]) # animate the tempo tempo_slowdown = NumberAnimation(frm=120, to=80, tween=['easeOutQuad']) # play some random cadenza notes = n.convert2( "a5 b5 c6 a5 e5 d5 c5 d5 e5 c5 a4 g#4 a4 b4 c5 a4 e4 d4 c4 d4 e4 c4 a3 g#3 a3 b3 c4 d4 e4 g#4 a4".split(" ")) notes2 = n.convert2( "d4 e4 f4 d4 a3 g3 f3 g3 a3 b3 c4 d4 c4 d4 e4 c4 g3 f3 e3 f3 g3 a3 b3 c4 a3 b3 c4 d4 e4 g#4 a4".split(" ")) notes.extend(notes2) properties_plain = { PP.NOTE: Pseq(notes, 1), PP.VOL: Pconst(100), PP.PLAYEDDUR: Pconst(0.95), PP.DUR: Pseq([Pconst(Dur.sixteenth_triplet, len(notes2) - 1), Pconst(Dur.half, 1)], 2), PP.LAG: Pconst(0), PP.TEMPO: Pconst(120) } properties_rubato = { PP.NOTE: Pseq(notes, 1), PP.VOL: Ptween(vol, 0, 0, len(notes), len(notes), None), PP.PLAYEDDUR: Ptween(dur, 0, 0, len(notes), len(notes), None), PP.DUR: Pseq([Pconst(Dur.sixteenth_triplet, len(notes2) - 1), Pconst(Dur.half, 1)], 2), PP.LAG: Ptween(lag, 0, 0, len(notes), len(notes), None), PP.TEMPO: Ptween(tempo_slowdown, 0, 0, len(notes), len(notes), None) } ph = Phrase(properties_plain) ph2 = Phrase(properties_rubato) p.add_phrases([ph, ph2], 0, 0, 0) p.write(outputfile)
def add_chords(pat2mid, track, channel): n = Note2Midi() notes = [C(["c3", "e3", "g3"]), C(["b2", "d3", "g3"]), C(["c3", "e3", "g3", "c4"])] from vectortween.NumberAnimation import NumberAnimation decrease = NumberAnimation(frm=Dyn.f, to=Dyn.p) properties = { PP.NOTE: Pseq(n.convert2(notes)), PP.DUR: Pseq([Pconst(Dur.whole, 2), Pconst(Dur.doublewhole, 1)]), PP.PLAYEDDUR: Pconst(1), PP.VOL: Ptween(decrease, 0, 0, len(notes), len(notes), None) } p = Phrase(properties) pat2mid.add_phrase(p, track=track, channel=channel, start_time=0) return pat2mid
def duration_generator_for_section(self, section_id): """ :param section_id: integer :return: an expremigen pattern generating all durations in this section """ return Pseq(self.durations_for_section(section_id), 1)
def note_generator_for_section(self, section_id): """ :param section_id: integer :return: expremigen pattern generating all notes in this section """ return Pseq(self.notes_for_section(section_id), 1)
def perform_cell(input_string, repeats, max_duration, parts, instrument_name, key, check_range=False): total_duration = 0 n = NanoNotation() note_pattern = Pseq(n.midinumbers(input_string), repeats) dur_pattern = Pseq(n.dur(input_string), repeats) for n, d in zip(note_pattern, dur_pattern): if total_duration < max_duration: prop = {} if random.randint(0, 10) > 7: accent = 1.0 prop["articulations"] = "accent" else: accent = 0.6 duration = dur_to_beat(d) if total_duration + duration > max_duration: duration = max_duration - total_duration # cut off the rhythm to remain inside measure if key: prop["key"] = key if check_range and not check_range(instrument_name, n): print( f"Warning: instrument {instrument_name} goes out of range with note {n}" ) if n == REST: scamp.wait(duration) else: parts[instrument_name].play_note( n, 0.7 * accent, duration, properties=prop if prop else None) total_duration += duration else: return
def lag(cls, notes, last_note_is_endnote=False): """ :param last_note_is_endnote: :param notes: list of notes that make up the phrase :return: pattern that generates the desired lag """ if last_note_is_endnote: return Pseq([Pconst(0, len(notes) - 1), Pconst(0.5, 1)], repeats=1) else: return Pconst(0, len(notes))
def test_phrase2(self): n = Note2Midi() notes = ["c4", "e4", "g4", "c5", "b4", "g4", "f4", "d4", "c4"] from vectortween.NumberAnimation import NumberAnimation from vectortween.SequentialAnimation import SequentialAnimation increase = NumberAnimation(frm=Dyn.mp, to=Dyn.f) decrease = NumberAnimation(frm=Dyn.f, to=Dyn.ppp) swell_dim = SequentialAnimation([increase, decrease]) increasing_staccato = NumberAnimation(frm=1, to=0.8) properties = { # convert from note names to midi numbers PP.NOTE: Pseq(n.convert2(notes)), # last note is longer than the rest PP.DUR: Pseq([Pconst(Dur.quarter, len(notes) - 1), Pconst(Dur.whole, 1)]), # animate staccato PP.PLAYEDDUR: Ptween(increasing_staccato, 0, 0, len(notes), len(notes)), # volume should linearly go up from mp to f, then go down from f to ppp as the phrase progresses PP.VOL: Ptween(swell_dim, 0, 0, len(notes), len(notes), None), } p = Phrase(properties) result = [] for event in p: result.append(event) self.assertEqual(len(result), 9) # check that last note longer self.assertEqual(result[7][PP.DUR], 4 * 1 / 4) self.assertEqual(result[8][PP.DUR], 4 * 1) # check that volume increases then decreases self.assertLess(result[0][PP.VOL], result[4][PP.VOL]) self.assertLess(result[8][PP.VOL], result[4][PP.VOL]) self.assertLess(result[8][PP.VOL], result[0][PP.VOL]) # check that staccato increases for i in range(8): self.assertTrue( result[i][PP.PLAYEDDUR] > result[i + 1][PP.PLAYEDDUR]) self.assertEqual(result[8][PP.NOTE], n.lookup("c4"))
def phrase_properties_for_section(self, section_id): """ :param section_id: 0 :return: returns a collection of phrase properties for given section """ pp = { PP.NOTE: self.note_generator_for_section(section_id), PP.VOL: self.dynamics_generator_for_section(section_id), PP.DUR: self.duration_generator_for_section(section_id), PP.PLAYEDDUR: self.pdur_generator_for_section(section_id), PP.LAG: self.lag_generator_for_section(section_id), PP.TEMPO: self.tempo_generator_for_section(section_id) } ccs = self.cc_properties_generators_for_section(section_id) for key in ccs: pp[PP.ctrl_dur_key(key)] = Pseq(ccs[key][PP.ctrl_dur_key(key)], 1) pp[PP.ctrl_val_key(key)] = Pseq(ccs[key][PP.ctrl_val_key(key)], 1) return pp
def add_melody(pat2mid, track, channel): n = Note2Midi() notes = ["c4", "e4", "g4", "c5", "b4", "g4", "f4", "d4", "c4"] from vectortween.NumberAnimation import NumberAnimation from vectortween.SequentialAnimation import SequentialAnimation increase = NumberAnimation(frm=Dyn.mp, to=Dyn.f) decrease = NumberAnimation(frm=Dyn.f, to=Dyn.ppp) swell_dim = SequentialAnimation([increase, decrease]) increasing_staccato = NumberAnimation(frm=1, to=0.5) properties = { # convert from note names to midi numbers PP.NOTE: Pseq(n.convert2(notes)), # last note is longer than the rest PP.DUR: Pseq([Pconst(Dur.quarter, len(notes) - 1), Pconst(Dur.whole, 1)]), # animate staccato PP.PLAYEDDUR: Ptween(increasing_staccato, 0, 0, len(notes), len(notes)), # volume should linearly go up from mp to f, then go down from f to ppp as the phrase progresses PP.VOL: Ptween(swell_dim, 0, 0, len(notes), len(notes), None), } p = Phrase(properties) pat2mid.set_tempo(60) pat2mid.add_phrase(p, track=track, channel=channel, start_time=0) return pat2mid
def property_generator_for_section(self, section_id, symvalue_from_string_fn, property_from_notespec_fn, default_value): """ :param section_id: integer :param symvalue_from_string_fn: function that maps a symbolic specification into a number (e.g. ff => 100) :param property_from_notespec_fn: function that extracts a given property from a parsed notespec :param default_value: value that tracks the default value (may update dynamically, e.g. to track last used octave) :return: returns a python generator generating all values for the given property """ dynamics = self.property_for_section(section_id, property_from_notespec_fn, default_value) patterns = [] for d in dynamics: frm_dyn = d[0] to_dyn = d[1] distance = d[2] try: tweenoptions = d[0][3] except IndexError: tweenoptions = ['linear'] if distance: from_value_type = frm_dyn[0] if from_value_type == 'sym': from_value = symvalue_from_string_fn(frm_dyn[2]) else: from_value = frm_dyn[2] to_value_type = to_dyn[0] if to_value_type == 'sym': to_value = symvalue_from_string_fn(to_dyn[2]) else: to_value = to_dyn[2] animation_type = frm_dyn[1] if animation_type == 'anim': n = Ptween(NumberAnimation(frm=from_value, to=to_value, tween=tweenoptions), 0, 0, distance, distance, None) elif animation_type == 'static': n = Ptween(NumberAnimation(frm=from_value, to=from_value, tween=['linear']), 0, 0, distance, distance, None) else: print(animation_type) assert False patterns.append(n) return Pseq(patterns, 1)
def test_nesting(self): f = [i for i in Pseq([Pseq([1, Pconst(2, 2)], 2), Pseq([3, 4], 2)], 2)] self.assertEqual(f, [1, 2, 2, 1, 2, 2, 3, 4, 3, 4, 1, 2, 2, 1, 2, 2, 3, 4, 3, 4])
def test_normal(self): a = [i for i in Padd(Pconst(4, 3), Pseq([1, 2, 3], 1))] self.assertEqual(a, [5, 6, 7])
def test_nesting(self): a = [ i for i in Padd(Padd(Pseq([1, 2], 2), Pconst(10, 3)), Pseq([4, 5], 2)) ] self.assertEqual(a, [15, 17, 15])
def test_repr(self): tested = "{0}".format(Padd(Pconst(4, 5), Pseq([1, 2, 3], 1))) expected = "Padd(Pconst(4, 5), Pseq([1, 2, 3], 1))" self.assertEqual(tested, expected)
def test_leftlazy(self): a = [i for i in Padd(Pconst(4, int(5e8)), Pseq([1, 2, 3, 70], 2))] self.assertEqual(a, [5, 6, 7, 74, 5, 6, 7, 74])
def test_normal(self): a = [i for i in Pseq([4, 5, 6], 2)] self.assertEqual(a, [4, 5, 6] * 2)
def test_secondempty(self): a = [i for i in Padd(Pconst(4, 3), Pseq([1, 2, 3, 70], 0))] self.assertEqual(a, [])
def dur(cls, durations): return Pseq(durations, repeats=1)
def test_defaultvalue(self): e = [i for i in Pseq(repeats=2)] self.assertEqual(e, [])
def test_withchord(self): f = [i for i in Pseq([Pseq([1, Pconst(2, 2)], 2), Pseq(Pchord([3, 4]), 2)], 2)] self.assertEqual(f, [1, 2, 2, 1, 2, 2, Pchord([3, 4]), Pchord([3, 4]), 1, 2, 2, 1, 2, 2, Pchord([3, 4]), Pchord([3, 4])])
def perform_cell_spicy(input_string, repeats, max_duration, parts, instrument_name, key, check_range=False): total_duration = 0 n = NanoNotation() note_pattern = Pseq(n.midinumbers(input_string), repeats) dur_pattern = Pseq(n.dur(input_string), repeats) if len(n.midinumbers(input_string)) == 1 and repeats <= 1: perform_cell(input_string, repeats, max_duration, parts, instrument_name, key, check_range) else: last_encountered_nd2 = None for nd1, nd2 in pairwise(zip(note_pattern, dur_pattern)): last_encountered_nd2 = nd2 if total_duration < max_duration: prop = {} if random.randint(0, 10) > 7: accent = 1.0 prop["articulations"] = "accent" else: accent = 0.6 n1 = nd1[0] n2 = nd2[0] d = nd1[1] duration = dur_to_beat(d) if total_duration + duration > max_duration: duration = max_duration - total_duration # cut off the rhythm to remain inside measure if key: prop["key"] = key if check_range and not check_range(instrument_name, n): print( f"Warning: instrument {instrument_name} goes out of range with note {n}" ) all_notes, all_durs = transform_note_duration(duration, n1, n2) for n, d in zip(all_notes, all_durs): if n == REST: scamp.wait(d) else: parts[instrument_name].play_note( n, 0.7 * accent, d, properties=prop if prop else None) total_duration += d else: return if last_encountered_nd2 is not None: n = last_encountered_nd2[0] d = last_encountered_nd2[1] if n == REST: scamp.wait(d) else: parts[instrument_name].play_note( n, 0.7 * accent, duration, properties=prop if prop else None) total_duration += d
def note(self, notes): """ :param notes: list of notes that make up the phrase :return: pattern that generates the notes one by one """ return Pseq(self.note2midi.convert2(notes))
def test_secondlonger(self): a = [i for i in Padd(Pconst(4, 3), Pseq([1, 2, 3, 70], 1))] self.assertEqual(a, [5, 6, 7])
def test_rightlazy(self): a = [i for i in Padd(Pconst(4, 3), Pseq([1, 2, 3, 70], int(5e5)))] self.assertEqual(a, [5, 6, 7])
def test_firstlonger(self): a = [i for i in Padd(Pconst(4, 5), Pseq([1, 2, 3], 1))] self.assertEqual(a, [5, 6, 7])
def test_firstempty(self): a = [i for i in Padd(Pconst(4, 0), Pseq([1, 2, 3, 70], 1))] self.assertEqual(a, [])