def mutate(octave, pitch, velocity, duration, offset, point): #def mutate(seq, pitch=0, duration=0, velocity=0, offset=0, octave=0): """ Transpose a point by an interval, using the Sebastian interval system """ octave_delta = random.randint(-1*octave,octave) rna = { 'midi_pitch':pitch, 'pitch':pitch, 'octave':octave, 'velocity':velocity, DURATION_64:duration, OFFSET_64:offset } new_point = Point() for key in rna: if key in point: if key == "octave": new_value = random.randint(point[key]-rna[key],point[key]+rna[key])+octave_delta if new_value < 0: new_value = random.randint(point[key]-rna[key],point[key]+rna[key])+(octave_delta*-1) print "was negative" else: new_value = random.randint(point[key]-rna[key],point[key]+rna[key]) new_point.__setitem__(key, new_value) return new_point
def test_point_tuple_arbitrary_data(self): """ Ensure points can handle arbitrary data """ from sebastian.core import Point p1 = Point(a=1, b="foo") self.assertEqual(p1.tuple("b", "a"), ("foo", 1))
def test_hseq_doesnt_track_duration_on_append(self): """ Ensure hseq doesnt do offset modification """ from sebastian.core import HSeq, Point s1 = HSeq() s1.append(Point(duration=10)) s1.append(Point(duration=10)) for point in s1: self.assertTrue('offset' not in point)
def test_sequence_ctor_with_merge(self): """ Ensure sequences can be made from merged sequences. """ from sebastian.core import OSeq, Point OffsetSequence = OSeq("offset", "duration") s1 = OffsetSequence(Point(a=1, offset=0), Point( a=2, offset=20)) // OffsetSequence(Point(a=3, offset=10)) self.assertEqual(s1._elements[1]["a"], 3)
def test_lilypond_transform_rhythms(self): """ Ensure points without pitches can render to lilypond """ from sebastian.core.transforms import lilypond from sebastian.core import DURATION_64 from sebastian.core import HSeq, Point h1 = HSeq([Point({DURATION_64: 64}), Point({DURATION_64: 0}), Point()]) h2 = h1 | lilypond() self.assertEqual(h2._elements[0]['lilypond'], r"\xNote c'1") self.assertEqual(h2._elements[1]['lilypond'], '') self.assertEqual(h2._elements[2]['lilypond'], '')
def test_sequence_duration_and_offset_with_append(self): """ Ensure sequences calculate durations correctly on append """ from sebastian.core import OSeq, Point OffsetSequence = OSeq("offset", "duration") s1 = OffsetSequence() s1.append(Point(duration=10)) s1.append(Point(duration=10)) self.assertEqual(20, s1.next_offset()) for point, offset in zip(s1, [0, 10]): self.assertEqual(point['offset'], offset)
def _(sequence): new_elements = [] last_offset = sequence.next_offset() if sequence and sequence[0][OFFSET_64] != 0: old_sequence = OSequence([Point({OFFSET_64: 0})]) + sequence else: old_sequence = sequence for point in old_sequence: new_point = Point(point) new_point[OFFSET_64] = last_offset - new_point[OFFSET_64] - new_point.get(DURATION_64, 0) if new_point != {OFFSET_64: 0}: new_elements.append(new_point) return OSequence(sorted(new_elements, key=lambda x: x[OFFSET_64]))
def _(sequence): new_elements = [] last_offset = sequence.next_offset() if sequence and sequence[0][OFFSET_64] != 0: old_sequence = OSequence([Point({OFFSET_64: 0})]) + sequence else: old_sequence = sequence for point in old_sequence: new_point = Point(point) new_point[OFFSET_64] = last_offset - new_point[ OFFSET_64] - new_point.get(DURATION_64, 0) if new_point != {OFFSET_64: 0}: new_elements.append(new_point) return OSequence(sorted(new_elements, key=lambda x: x[OFFSET_64]))
def test_sequence_ctors(self): """ Ensure the various sequence ctors work ok """ from sebastian.core import OSeq, Point OffsetSequence = OSeq("offset", "duration") p1 = Point(a=3, b="foo") p2 = Point(c=5) s2 = OffsetSequence([p1, p2]) s3 = OffsetSequence(p1, p2) s4 = OffsetSequence(p1) + OffsetSequence(p2) self.assertEqual(s2, s3) self.assertEqual(s3, s4) self.assertEqual(s2, s4)
def test_velocity_from_note_with_invalid_velocities(self): from sebastian.core import OSequence, Point from sebastian.core import OFFSET_64, MIDI_PITCH, DURATION_64 test = OSequence([ Point({ OFFSET_64: o, MIDI_PITCH: m, DURATION_64: d }) for (o, m, d) in [(0, 60, 16), (16, 72, 16), (32, 64, 16)] ]) test[0]['velocity'] = -1 test[1]['velocity'] = 300 from sebastian.midi.write_midi import SMF from io import BytesIO out_fd = BytesIO(bytearray()) expected_bytes = b'MThd\x00\x00\x00\x06\x00\x01\x00\x02\x00\x10MTrk\x00\x00\x00&\x00\xffX\x04\x04\x02\x18\x08\x00\xffY\x02\x00\x00\x00\xffQ\x03\x07\xa1 \x00\xff\x03\ttest song\x00\xff/\x00MTrk\x00\x00\x00\x1f\x00\xc0\x00\x00\x90<\x00\x10\x80<\x00\x00\x90H\xff\x10\x80H\x00\x00\x90@@\x10\x80@\x00\x00\xff/\x00' s = SMF([test]) s.write(out_fd, title="test song") actual_bytes = out_fd.getvalue() self.assertEqual(expected_bytes, actual_bytes)
def note(self, offset, channel, midi_pitch, duration): offset_64 = 16 * offset // self.division duration_64 = 16 * duration // self.division point = Point({ OFFSET_64: offset_64, MIDI_PITCH: midi_pitch, DURATION_64: duration_64 }) self.current_sequence.append(point)
def make_point(self, offset=0): from sebastian.core import Point from sebastian.core import OFFSET_64, DURATION_64 retval = Point({ OFFSET_64: 16 + offset, "pitch": 50 + offset, DURATION_64: 17 + offset, }) return retval
def test_sequence_map(self): """ Ensure map_points applys the function """ from sebastian.core import HSeq, Point s1 = (HSeq(Point(a=3, c=5)) + HSeq([Point(d=x) for x in range(10)]) + HSeq(Point(a=5))) def double_a(point): if 'a' in point: point['a'] *= 2 return point s2 = s1.map_points(double_a) self.assertEqual(s2[0]['a'], 6) self.assertEqual(s2[-1]['a'], 10)
def test_write_midi_multi_tacks(self): """ Writing out test.mid to ensure midi processing works. This isn't really a test. """ from sebastian.core import OSequence, Point from sebastian.core import OFFSET_64, MIDI_PITCH, DURATION_64 test1 = OSequence([ Point({ OFFSET_64: o, MIDI_PITCH: m, DURATION_64: d }) for (o, m, d) in [ (0, 60, 16), (16, 72, 16), (32, 64, 16), (48, 55, 16), ] ]) test2 = OSequence([ Point({ OFFSET_64: o, MIDI_PITCH: m, DURATION_64: d }) for (o, m, d) in [ (0, 55, 16), (16, 55, 16), (32, 64, 16), (48 + 16, 55, 16 * 10), ] ]) from sebastian.midi.write_midi import SMF from io import BytesIO out_fd = BytesIO(bytearray()) expected_bytes = b"""MThd\x00\x00\x00\x06\x00\x01\x00\x03\x00\x10MTrk\x00\x00\x00&\x00\xffX\x04\x04\x02\x18\x08\x00\xffY\x02\x00\x00\x00\xffQ\x03\x07\xa1 \x00\xff\x03\ttest song\x00\xff/\x00MTrk\x00\x00\x00'\x00\xc0\x00\x00\x90<@\x10\x80<\x00\x00\x90H@\x10\x80H\x00\x00\x90@@\x10\x80@\x00\x00\x907@\x10\x807\x00\x00\xff/\x00MTrk\x00\x00\x00(\x00\xc1\x10\x00\x917@\x10\x817\x00\x00\x917@\x10\x817\x00\x00\x91@@\x10\x81@\x00\x10\x917@\x81 \x817\x00\x00\xff/\x00""" s = SMF([test1, test2], instruments=[0, 16]) s.write(out_fd, title="test song") actual_bytes = out_fd.getvalue() self.assertEqual(expected_bytes, actual_bytes)
def debug(sequence): """ adds information to the sequence for better debugging, currently only an index property on each point in the sequence. """ points = [] for i, p in enumerate(sequence): copy = Point(p) copy['index'] = i points.append(copy) return sequence.__class__(points)
def mutate(seq, pitch=0, duration=0, velocity=0, offset=0, octave=0): output = [] octave_delta = random.randint(-1*octave,octave) for point in seq: rna = { 'midi_pitch':pitch, 'pitch':pitch, 'octave':octave, 'velocity':velocity, DURATION_64:duration, OFFSET_64:offset } new_point = Point() for key in rna: if key in point: if key == "octave": new_value = random.randint(point[key]-rna[key],point[key]+rna[key])+octave_delta else: new_value = random.randint(point[key]-rna[key],point[key]+rna[key]) new_point.__setitem__(key, new_value) output.append(new_point) return OSequence(output)
def test_write_midi(self): """ Writing out test.mid to ensure midi processing works. This isn't really a test. """ from sebastian.core import OSequence, Point from sebastian.core import OFFSET_64, MIDI_PITCH, DURATION_64 test = OSequence([ Point({ OFFSET_64: o, MIDI_PITCH: m, DURATION_64: d }) for (o, m, d) in [(0, 60, 16), (16, 72, 16), (32, 64, 16), (48, 55, 16), (64, 74, 16), (80, 62, 16), (96, 50, 16), (112, 48, 16), (128, 36, 16), (144, 24, 16), (160, 40, 16), (176, 55, 16), (192, 26, 16), (208, 38, 16), (224, 50, 16), (240, 48, 16)] ]) from sebastian.midi.write_midi import SMF from io import BytesIO out_fd = BytesIO(bytearray()) expected_bytes = b'MThd\x00\x00\x00\x06\x00\x01\x00\x02\x00\x10MTrk\x00\x00\x00%\x00\xffX\x04\x04\x02\x18\x08\x00\xffY\x02\x00\x00\x00\xffQ\x03\x07\xa1 \x00\xff\x03\x08untitled\x00\xff/\x00MTrk\x00\x00\x00\x87\x00\xc0\x00\x00\x90<@\x10\x80<\x00\x00\x90H@\x10\x80H\x00\x00\x90@@\x10\x80@\x00\x00\x907@\x10\x807\x00\x00\x90J@\x10\x80J\x00\x00\x90>@\x10\x80>\x00\x00\x902@\x10\x802\x00\x00\x900@\x10\x800\x00\x00\x90$@\x10\x80$\x00\x00\x90\x18@\x10\x80\x18\x00\x00\x90(@\x10\x80(\x00\x00\x907@\x10\x807\x00\x00\x90\x1a@\x10\x80\x1a\x00\x00\x90&@\x10\x80&\x00\x00\x902@\x10\x802\x00\x00\x900@\x10\x800\x00\x00\xff/\x00' s = SMF([test], instruments=None) s.write(out_fd) actual_bytes = out_fd.getvalue() self.assertEqual(expected_bytes, actual_bytes)
def test_subseq(self): from sebastian.core.transforms import subseq from sebastian.core import OSeq, Point OffsetSequence = OSeq("offset", "duration") s1 = OffsetSequence( Point(a=2, offset=0), Point(a=2, offset=20), Point(a=2, offset=25), Point(a=2, offset=30), Point(a=2, offset=50), ) s2 = s1 | subseq(20, 30) self.assertEqual( s2, OffsetSequence(Point(a=2, offset=20), Point(a=2, offset=25)))
def _(sequence): if start in _dynamic_markers_to_velocity: start_velocity = _dynamic_markers_to_velocity[start] start_marker = start else: raise ValueError("Unknown start dynamic: %s, must be in %s" % (start, _dynamic_markers_to_velocity.keys())) if end is None: end_velocity = start_velocity end_marker = start_marker elif end in _dynamic_markers_to_velocity: end_velocity = _dynamic_markers_to_velocity[end] end_marker = end else: raise ValueError("Unknown end dynamic: %s, must be in %s" % (start, _dynamic_markers_to_velocity.keys())) retval = sequence.__class__( [Point(point) for point in sequence._elements]) velocity_interval = (float(end_velocity) - float(start_velocity)) / ( len(retval) - 1) if len(retval) > 1 else 0 velocities = [ int(start_velocity + velocity_interval * pos) for pos in range(len(retval)) ] # insert dynamics markers for lilypond if start_velocity > end_velocity: retval[0]["dynamic"] = "diminuendo" retval[-1]["dynamic"] = end_marker elif start_velocity < end_velocity: retval[0]["dynamic"] = "crescendo" retval[-1]["dynamic"] = end_marker else: retval[0]["dynamic"] = start_marker for point, velocity in zip(retval, velocities): point["velocity"] = velocity return retval
def test_oseq_subseq_both_args(self): """ Verify _oseq subseq works correctly """ from sebastian.core import OSeq, Point OffsetSequence = OSeq("offset", "duration") s1 = OffsetSequence( Point(a=2, offset=0), Point(a=2, offset=20), Point(a=2, offset=25), Point(a=2, offset=30), Point(a=2, offset=50), ) s2 = s1.subseq(20, 50) self.assertEqual( s2, OffsetSequence( Point(a=2, offset=20), Point(a=2, offset=25), Point(a=2, offset=30), ))
def test_lilypond_transform(self): """ Ensure that it plays in G major. """ from sebastian.core.transforms import midi_pitch, add, lilypond from sebastian.core import DURATION_64 from sebastian.core import HSeq, Point h1 = HSeq( [Point(pitch=pitch) for pitch in [0, 1, 2, 3, 4, 11, -4, -11]]) positioned = h1 | add({'octave': 4, DURATION_64: 8}) pitched = positioned | midi_pitch() pitched[3]['octave'] = 5 pitched[4]['octave'] = 3 lilyed = pitched | lilypond() import pprint pprint.pprint(list(lilyed)) self.assertEqual(lilyed._elements, [{ 'duration_64': 8, 'lilypond': 'd8', 'midi_pitch': 50, 'octave': 4, 'pitch': 0 }, { 'duration_64': 8, 'lilypond': 'a8', 'midi_pitch': 57, 'octave': 4, 'pitch': 1 }, { 'duration_64': 8, 'lilypond': 'e8', 'midi_pitch': 52, 'octave': 4, 'pitch': 2 }, { 'duration_64': 8, 'lilypond': "b'8", 'midi_pitch': 59, 'octave': 5, 'pitch': 3 }, { 'duration_64': 8, 'lilypond': 'fis,8', 'midi_pitch': 54, 'octave': 3, 'pitch': 4 }, { 'duration_64': 8, 'lilypond': 'fisis8', 'midi_pitch': 55, 'octave': 4, 'pitch': 11 }, { 'duration_64': 8, 'lilypond': 'bes8', 'midi_pitch': 58, 'octave': 4, 'pitch': -4 }, { 'duration_64': 8, 'lilypond': 'beses8', 'midi_pitch': 57, 'octave': 4, 'pitch': -11 }])
#!/usr/bin/env python ## this is the beginning of an experiment for the next level of the algebra from sebastian.core import DURATION_64 from sebastian.core import VSeq, HSeq, Point, OSequence from sebastian.core.transforms import midi_pitch, degree_in_key_with_octave, add from sebastian.core.notes import Key, major_scale from sebastian.midi import write_midi quaver_point = Point({DURATION_64: 8}) quarter_point = Point({DURATION_64: 16}) # this song uses only these notes scale = VSeq(Point(degree=n) for n in [1, 2, 3, 5, 6, 8]) # the following functions all create sequences of eighth notes def h1(scale): return HSeq(scale[i] for i in [5, 4, 3, 4]) | add(quaver_point) def h1_end1(scale): return HSeq(scale[i] for i in [5, 4]) | add(quaver_point) def end(scale): return HSeq(scale[i] for i in [2, 1]) | add(quaver_point)
def make_horizontal_sequence(self): from sebastian.core import HSeq, Point return HSeq([Point(degree=degree) for degree in self.make_notes()])
def transpose_degree(point, degree_delta): result = Point(point) result[DEGREE] = result[DEGREE] + degree_delta return result
from sebastian.core import VSeq, HSeq, Point, OSequence from sebastian.core.transforms import midi_pitch, degree_in_key_with_octave, add from sebastian.core.notes import Key, major_scale from sebastian.midi import write_midi def alberti(triad): """ takes a VSeq of 3 notes and returns an HSeq of those notes in an alberti figuration. """ return HSeq(triad[i] for i in [0, 2, 1, 2]) # an abstract VSeq of 3 notes in root position (degree 1, 3 and 5) root_triad = VSeq(Point(degree=n) for n in [1, 3, 5]) quaver_point = Point({DURATION_64: 8}) # an OSequence with alberti figuration repeated 16 times using quavers alberti_osequence = OSequence(alberti(root_triad) * 16 | add(quaver_point)) C_major = Key("C", major_scale) # note values filled-out for C major in octave 5 then MIDI pitches calculated seq = alberti_osequence | degree_in_key_with_octave(C_major, 5) | midi_pitch() # write to file: write_midi.write("alberti.mid", [seq])
""" The main title theme to HBO's Game of Thrones by Ramin Djawadi """ from sebastian.core import Point, HSeq, OSequence from sebastian.core.transforms import midi_pitch, degree_in_key, add from sebastian.core.notes import Key, major_scale, minor_scale from sebastian.midi import write_midi C_Major = Key("C", major_scale) C_minor = Key("C", minor_scale) motive_degrees = HSeq(Point(degree=n) for n in [5, 1, 3, 4]) motive_rhythm_1 = HSeq(Point(duration_64=n) for n in [16, 16, 8, 8]) motive_rhythm_2 = HSeq( Point(duration_64=n) for n in [48, 48, 8, 8, 32, 32, 8, 8]) motive_1 = motive_degrees & motive_rhythm_1 motive_2 = (motive_degrees * 2) & motive_rhythm_2 # add key and octave seq1 = (motive_1 * 4) | add({"octave": 5}) | degree_in_key(C_minor) seq2 = (motive_1 * 4) | add({"octave": 5}) | degree_in_key(C_Major) seq3 = motive_2 | add({"octave": 4}) | degree_in_key(C_minor) seq = (seq1 + seq2 + seq3) | midi_pitch() | OSequence write_midi.write("game_of_thrones.mid", [seq], instruments=[49], tempo=350000)
def make_hseq(notes): return HSeq(Point(degree=n, duration_64=d) for n, d in notes)
#!/usr/bin/env python from sebastian.core import Point, OSequence, HSeq from sebastian.core import OFFSET_64, MIDI_PITCH, DURATION_64 from sebastian.core.notes import Key, major_scale ## points p1 = Point({ OFFSET_64: 16, MIDI_PITCH: 50, DURATION_64: 16, }) assert p1.tuple(OFFSET_64, DURATION_64) == (16, 16) ## sequences p2 = Point({ OFFSET_64: 32, MIDI_PITCH: 52, DURATION_64: 16, }) s1 = OSequence([p1, p2]) assert s1._elements == [ {DURATION_64: 16, OFFSET_64: 16, MIDI_PITCH: 50}, {DURATION_64: 16, OFFSET_64: 32, MIDI_PITCH: 52}
seq3 = seq1 + seq2 # transpose and reverse seq4 = seq3 | transpose(12) | reverse() # merge seq5 = seq3 // seq4 # play MIDI player.play([seq5]) # write to MIDI write_midi.write("seq5.mid", [seq5]) # contruct a horizontal sequence of scale degrees seq6 = HSeq(Point(degree=degree) for degree in [1, 2, 3, 2, 1]) # put that sequence into C major, octave 4 quavers C_MAJOR = Key("C", major_scale) seq7 = seq6 | add({"octave": 4, DURATION_64: 8}) | degree_in_key(C_MAJOR) # convert to MIDI pitch and play player.play([OSequence(seq7 | midi_pitch())]) # sequence of first four degree of a scale seq8 = HSeq(Point(degree=n) for n in [1, 2, 3, 4]) # add duration and octave seq8 = seq8 | add({DURATION_64: 16, "octave": 5}) # put into C major
def parse_block(token_generator, prev_note_tuple=None, relative_mode=False, offset=0): prev_duration = 16 tie_deferred = False try: while True: token_dict = next(token_generator) command = token_dict["command"] open_brace = token_dict["open_brace"] close_brace = token_dict["close_brace"] if command: if command == "relative": token_dict = next(token_generator) base_note_tuple = note_tuple(token_dict) token_dict = next(token_generator) if not token_dict["open_brace"]: raise Exception( "\\relative must be followed by note then {...} block" ) for obj in parse_block(token_generator, prev_note_tuple=base_note_tuple, relative_mode=True, offset=offset): yield obj last_offset = obj[OFFSET_64] offset = last_offset elif command == "acciaccatura": # @@@ there is much code duplication between here and the # main parsing further on token_dict = next(token_generator) note_value, duration = process_note( token_dict, relative_mode, prev_note_tuple) yield Point({ OFFSET_64: offset - duration / 2, MIDI_PITCH: note_value, DURATION_64: duration / 2 }) token_dict = next(token_generator) note_value, duration = process_note( token_dict, relative_mode, prev_note_tuple) yield Point({ OFFSET_64: offset, MIDI_PITCH: note_value, DURATION_64: duration }) offset += duration prev_duration = duration # @@@ this should be uncommented but I'll wait until a # unit test proves it should be uncommented! # prev_note_tuple = note_base, accidental_change, octave elif open_brace: for obj in parse_block(token_generator): yield obj elif close_brace: raise StopIteration else: duration_marker = token_dict["duration"] rest = token_dict["rest"] tie = token_dict["tie"] if duration_marker is None: duration = prev_duration else: duration = parse_duration(duration_marker) if not rest: if relative_mode: note_base, accidental_change, octave = note_tuple( token_dict, relative_note_tuple=prev_note_tuple) else: note_base, accidental_change, octave = note_tuple( token_dict) note_value = note_base + (12 * octave) + accidental_change if tie_deferred: # if the previous note was deferred due to a tie prev_note_value = prev_note_tuple[0] + ( 12 * prev_note_tuple[2]) + prev_note_tuple[1] if note_value != prev_note_value: raise Exception( "ties are only supported between notes of same pitch" ) duration += prev_duration tie_deferred = False if tie: # if there is a tie following this note, we defer it tie_deferred = True else: yield Point({ OFFSET_64: offset, MIDI_PITCH: note_value, DURATION_64: duration }) prev_note_tuple = note_base, accidental_change, octave if not tie_deferred: offset += duration prev_duration = duration except StopIteration: yield Point({OFFSET_64: offset}) raise StopIteration