def next(self, length): self.note.reset() self.duration.reset() self.velocity.reset() try: self.rest.reset() except Exception as e: print e print 'no rest to reset' seq = OSequence() total_length = 0 while total_length < length: next = self.note.next() next_duration = self.duration.next() next_rest = self.rest.next() if total_length+next_duration+next_rest > length: #if there isn't time for this note, fill the rest of the time with silence #print "fill"+str(total_length) duration_left = length - total_length next.update({DURATION_64: duration_left, OFFSET_64: total_length, "velocity": 0}) else: #print "add"+str(total_length) next.update({DURATION_64: next_duration, OFFSET_64: total_length, "velocity": self.velocity.next()}) total_length += next_duration+next_rest seq.append(next) #print 'made it' self.last = seq return self.last
def test_sequence_last_point_empty(self): """ Ensure OSequence.last_point doesn't barf when the sequence is empty """ from sebastian.core import OSequence from sebastian.core import OFFSET_64, DURATION_64 sequence = OSequence([]) self.assertEqual(sequence.last_point(), {DURATION_64: 0, OFFSET_64: 0})
def test_sequence_last_point_empty(self): """ Ensure OSequence.last_point doesn't barf when the sequence is empty """ from sebastian.core import OSequence from sebastian.core import OFFSET_64, DURATION_64 sequence = OSequence([]) self.assertEqual(sequence.last_point(), { DURATION_64: 0, OFFSET_64: 0 })
def test_sequence_last_point(self): """ Ensure that OSequence.last_point returns the highest offset note """ points = [self.make_point(offset=x) for x in range(100, -1, -10)] from sebastian.core import OSequence from sebastian.core import OFFSET_64, MIDI_PITCH, DURATION_64 sequence = OSequence(points) self.assertEqual(sequence.last_point(), { DURATION_64: 17 + 100, OFFSET_64: 16 + 100, MIDI_PITCH: 50 + 100 })
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]))
class SebastianHandler(BaseHandler): def header(self, format, num_tracks, division): self.division = division self.tracks = [None] * num_tracks def track_start(self, track_num): self.current_sequence = OSequence() self.tracks[track_num] = self.current_sequence 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 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 one_file(): seq = OSequence([]) for num, pattern in enumerate(patterns): seq = seq + parse(pattern) * 10 f = open("in_c_all.mid", "w") s = SMF([seq]) s.write(f) f.close()
class SebastianHandler(BaseHandler): def header(self, format, num_tracks, division): self.division = division self.tracks = [None] * num_tracks def track_start(self, track_num): self.current_sequence = OSequence() self.tracks[track_num] = self.current_sequence 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 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 performance(): tracks = [] for track_num in range(8): # 8 tracks seq = OSequence([]) for pattern in patterns: seq += parse(pattern) * random.randint(2, 5) # repeat 2-5 times tracks.append(seq | transpose(random.choice([-12, 0, 12]))) # transpose -1, 0 or 1 octaves f = open("in_c_performance.mid", "w") s = SMF(tracks) s.write(f) f.close()
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_empty_sequence_merge(self): """ Ensure that an empty sequence merge is an identity operation """ s1 = self.make_sequence() from sebastian.core import OSequence s2 = OSequence([]) merged = s1 // s2 from sebastian.core import OFFSET_64, DURATION_64, MIDI_PITCH self.assertEqual(merged._elements, [{ MIDI_PITCH: 50, OFFSET_64: 16, DURATION_64: 17 }, { MIDI_PITCH: 53, OFFSET_64: 19, DURATION_64: 20 }])
def test_make_sequence(self): """ Ensure sequences are composed of notes in the correct order """ p1 = self.make_point() p2 = self.make_point(offset=50) # we need to dedupe somehow from sebastian.core import OSequence from sebastian.core import OFFSET_64, MIDI_PITCH, DURATION_64 sequence = OSequence([p1, p2]) self.assertEqual(sequence._elements, [{ DURATION_64: 17, OFFSET_64: 16, MIDI_PITCH: 50 }, { DURATION_64: 17 + 50, OFFSET_64: 16 + 50, MIDI_PITCH: 50 + 50 }])
def parse(s, offset=0): return OSequence(list(parse_block(tokenize(s), offset=offset)))
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} ] assert s1.last_point() == { MIDI_PITCH: 52, OFFSET_64: 32, DURATION_64: 16 } assert OSequence([]).last_point() == { OFFSET_64: 0, DURATION_64: 0 }
def track_start(self, track_num): self.current_sequence = OSequence() self.tracks[track_num] = self.current_sequence
def make_hseq(notes): return HSeq(Point(degree=n, duration_64=d) for n, d in notes) # produces eighth and quarter notes hlf = 16 qtr = 8 # tuples specify pitch and duration p1 = [(8, qtr), (6, qtr), (5, qtr), (6, qtr)] p2 = [(8, qtr), (6, qtr), (5, hlf)] p3 = [(3, qtr), (2, qtr), (1, hlf)] p4 = [(1, qtr), (6, qtr), (5, qtr), (6, qtr)] p5 = [(1, qtr), (6, qtr), (5, hlf)] partA = p1 + p2 + p1 + p3 partB = p4 + p5 + p4 + p3 parts = partA + partA + partB + partB hseq = make_hseq(parts) oseq = OSequence(hseq) C_major = Key("C", major_scale) # note values filled-out for C major in octave 5 then MIDI pitches calculated seq = oseq | degree_in_key_with_octave(C_major, 5) | midi_pitch() write_midi.write("shortning_bread_2.mid", [seq])
from sebastian.core.notes import Key, major_scale from sebastian.core.transforms import degree_in_key_with_octave, midi_pitch, transpose # Hanon 1 up_degrees = [1, 3, 4, 5, 6, 5, 4, 3] down_degrees = [6, 4, 3, 2, 1, 2, 3, 4] final_degree = [1] sections = [ (up_degrees, 4, range(14)), (down_degrees, 4, range(13, -2, -1)), (final_degree, 32, range(1)), ] hanon_1 = OSequence() for section in sections: pattern, duration_64, offset = section for o in offset: for note in pattern: hanon_1.append({"degree": note + o, DURATION_64: duration_64}) hanon_1 = hanon_1 | degree_in_key_with_octave(Key("C", major_scale), 4) | midi_pitch() hanon_rh_1 = hanon_1 hanon_lh_1 = hanon_1 | transpose(-12) seq = hanon_lh_1 // hanon_rh_1
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])
def end(scale): return HSeq(scale[i] for i in [2, 1]) | add(quaver_point) def h2(scale): return HSeq(scale[i] for i in [0, 4, 3, 4]) | add(quaver_point) def h2_end1(scale): return HSeq(scale[i] for i in [0, 4]) | add(quaver_point) # there's two important quarter notes used at the ends of sections e1 = HSeq(scale[3]) | add(quarter_point) e2 = HSeq(scale[0]) | add(quarter_point) partA = h1(scale) + h1_end1(scale) + e1 + h1(scale) + end(scale) + e2 partB = h2(scale) + h2_end1(scale) + e1 + h2(scale) + end(scale) + e2 # here we see the basic structure of the song oseq = OSequence((partA * 2) + (partB * 2)) C_major = Key("C", major_scale) # note values filled-out for C major in octave 5 then MIDI pitches calculated seq = oseq | degree_in_key_with_octave(C_major, 5) | midi_pitch() # write to file: write_midi.write("shortning_bread_1.mid", [seq])
from sebastian.core.transforms import degree_in_key_with_octave, midi_pitch, transpose # Hanon 1 up_degrees = [1, 3, 4, 5, 6, 5, 4, 3] down_degrees = [6, 4, 3, 2, 1, 2, 3, 4] final_degree = [1] sections = [ (up_degrees, 4, range(14)), (down_degrees, 4, range(13, -2, -1)), (final_degree, 32, range(1)), ] hanon_1 = OSequence() for section in sections: pattern, duration_64, offset = section for o in offset: for note in pattern: hanon_1.append({"degree": note + o, DURATION_64: duration_64}) hanon_1 = hanon_1 | degree_in_key_with_octave(Key("C", major_scale), 4) | midi_pitch() hanon_rh_1 = hanon_1 hanon_lh_1 = hanon_1 | transpose(-12) seq = hanon_lh_1 // hanon_rh_1
def make_sequence(self, offset=0): points = [self.make_point(offset), self.make_point(offset + 3)] from sebastian.core import OSequence return OSequence(points)
# 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 seq8 = seq8 | degree_in_key(C_MAJOR) # annotate with lilypond seq8 = seq8 | lilypond() # write out lilypond file write("example.ly", seq8)