Example #1
0
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
Example #2
0
    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))
Example #3
0
 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)
Example #4
0
    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)
Example #5
0
 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'], '')
Example #6
0
    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)
Example #7
0
 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]))
Example #8
0
 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]))
Example #9
0
    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)
Example #10
0
    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)
Example #11
0
 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)
Example #12
0
 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
Example #13
0
    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)
Example #14
0
    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)
Example #15
0
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)
Example #16
0
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)
Example #17
0
    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)
Example #18
0
    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)))
Example #19
0
    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
Example #20
0
    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),
            ))
Example #21
0
    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
        }])
Example #22
0
#!/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)

Example #23
0
 def make_horizontal_sequence(self):
     from sebastian.core import HSeq, Point
     return HSeq([Point(degree=degree) for degree in self.make_notes()])
Example #24
0
def transpose_degree(point, degree_delta):
    result = Point(point)
    result[DEGREE] = result[DEGREE] + degree_delta
    return result
Example #25
0
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])
Example #26
0
"""
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)
Example #27
0
def make_hseq(notes):
    return HSeq(Point(degree=n, duration_64=d) for n, d in notes)
Example #28
0
#!/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}
Example #29
0
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
Example #30
0
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