def change_mode(self,
                 mode_change_frequency=True,
                 note_count_weights=True,
                 natural_harmonic_frequency=True,
                 artificial_harmonic_frequency=True,
                 chord_frequency=True,
                 length_network=True):
     if mode_change_frequency:
         self.mode_change_frequency = round(random.uniform(0.01, 0.2), 3)
     if note_count_weights:
         self.note_count_weights = rand.random_weight_list(
             1, random.randint(6, 15), 0.15)
     if natural_harmonic_frequency:
         self.natural_harmonic_frequency = round(random.uniform(0.04, 0.4),
                                                 3)
     if artificial_harmonic_frequency:
         self.artificial_harmonic_frequency = round(
             rand.weighted_rand([(0.01, 150), (0.2, 20), (0.4, 5),
                                 (0.5, 1)]), 3)
     if chord_frequency:
         self.chord_frequency = round(
             rand.weighted_rand([(0.01, 150), (0.1, 20), (0.2, 0)]), 3)
     if length_network:
         self.length_network.apply_noise(
             round(random.uniform(0.001, 0.05), 5))
    def special_action_1(self):
        """
        Long accented single note on A or B with trailing dim hairpin
        :return: str - image path
        """
        self.refresh_lily()
        # Find pitch
        pitch_set = [-1, 0, 2]
        weighted_pitch_set = []
        i = 0
        while i < len(pitch_set):
            weighted_pitch_set.append((pitch_set[i], 10/(i+1)))
            i += 1
        use_pitch = rand.weighted_rand(weighted_pitch_set, 'discreet')
        # Find length
        use_length = rand.weighted_rand([(5, 2), (13, 8), (25, 1)], do_round=True)
        # Find dynamic
        use_dynamic = rand.weighted_rand([(9, 1), (8, 7), (4, 5), (0, 1)], do_round=True)
        self.note_array.create_note(use_pitch, use_length, dynamic=use_dynamic)
        self.note_array.list[-1].add_articulation(1)
        if self.sustained_playing:
            self.note_array.create_spanner(0, spanner_type='dim', apply_type='start')

        if len(self.clef_list) > 1:
            self.note_array.auto_build_clefs(self.clef_list, self.initial_clef,
                                             self.clef_change_tolerance, self.preferred_clef_extra_weight)
        self.score.note_array = self.note_array
        output_ly_file = lilypond_file.LilypondFile(self.score)
        output_file_name = self.instrument_name + '_' + str(config.FileNameIndex)
        output_png = output_ly_file.save_and_render(output_file_name, view_image=False, autocrop=True, delete_ly=False)
        config.FileNameIndex += 1
        return output_png
    def move_pitch(self, start_pitch):

        if not self.count_intervals_by_slots:
            # Roll interval based on self.weights
            interval = rand.weighted_rand(self.interval_weights,
                                          'interpolated', True)
            # Make sure to invert direction if needed
            if self.direction == -1:
                interval *= -1
            # Add adjusted interval to start_pitch
            approx_pitch = start_pitch + interval
            # If self.pitch_set is defined, fit adjusted interval to it
            if self.pitch_set is not None:
                new_pitch = min(self.pitch_set,
                                key=lambda p: abs(p - approx_pitch))
                return new_pitch
            # Otherwise just return the already found approx_pitch
            else:
                return approx_pitch
        else:
            # If self.count_interval_by_slots is True, then treat interval as steps along self.pitch_set
            pitch_set = sorted(self.pitch_set)
            # If start_pitch isn't in sorted pitch_set, snap it to the closest
            if start_pitch not in pitch_set:
                start_pitch = min(pitch_set,
                                  key=lambda p: abs(p - start_pitch))
            # Find the index in pitch_set to which start_pitch fits now
            start_index = -1
            i = 0
            while i < len(pitch_set):
                if start_pitch == pitch_set[i]:
                    start_index = i
                    break
                i += 1
            if start_index == -1:
                print(
                    "WARNING: matching pitch wasn't found in NoteBehavior.move_pitch()"
                )
            # Find the slot_interval to move along the pitch_set by
            slot_interval = rand.weighted_rand(self.interval_weights,
                                               'interpolated', True)
            # Make sure to invert direction if needed
            if self.direction == -1:
                slot_interval *= -1
            elif self.direction == 0:
                # If direction == 0 (either), roll heads or tails to determine if slot_interval should invert
                if random.randint(0, 1) == 0:
                    slot_interval *= -1
            # Finally, add adjusted slot_interval to start_index, and use it to find the return_pitch, and return it
            return_index = start_index + slot_interval
            # Special handling in can return_index goes out of the bounds of pitch_set: snap to nearest outer
            if return_index < 0:
                return_index = 0
            elif return_index > (len(pitch_set) - 1):
                return_index = len(pitch_set) - 1

            return pitch_set[return_index]
Exemple #4
0
    def pick(self, current_node=None):
        """
        Picks the next node for the network based on a starting node which is
        either explicitly stated or implicitly found according to these rules:

            - if current_node is specified, start from there
            - if current_node is None, start from self.current_node
            - if current_node is None and self.current_node is None, pick from the network's nodes' use weights

        :param current_node: Node object, or None to pick according to self.current_node
        :return: Next node
        """
        self.previous_node = self.current_node
        start_node = current_node
        # If None was passed (default), start from self.current_node
        if start_node is None:
            start_node = self.current_node
        # If start_node is still None (because self.current_node is also None, pick by use weight
        if start_node is None:
            return self.pick_by_use_weight()
        # Otherwise, use a discreet weighted random on start_node.link_list
        self.current_node = weighted_rand([(link.target, link.weight)
                                           for link in start_node.link_list],
                                          'discreet')
        return self.current_node
    def _find_first_pitch(pitch_set, percent_weights):
        """
        Takes a list of pitch slots and a list of percent weights and finds a pitch accordingly.
        Objects in percent_weights are tuples of the form (percent_along_pitch_set between 0.00 and 1.00, weight)
        :param pitch_set: list
        :param percent_weights: list of tuples which are proxy weights of form (percent_along_pitch_set, weight)
        :return: int pitch
        """
        import copy
        percent_weights = copy.copy(percent_weights)
        for i in range(0, len(percent_weights)):
            weight = percent_weights[i]
            assert isinstance(weight, tuple)
            new_x = int(round(weight[0] * len(pitch_set)))
            if weight[0] < 0:
                new_x = 0
            if weight[0] > len(pitch_set) - 1:
                new_x = len(pitch_set) - 1
            weight = (new_x, weight[1])
            percent_weights[i] = weight

        pitch_index = rand.weighted_rand(percent_weights, do_round=True)
        try:
            return pitch_set[pitch_index]
        except IndexError:
            print((
                'IndexError in base_instrument.Instrument._find_first_pitch(): index of '
                + str(pitch_index) +
                ' is invalid! Using pitch_index of 0 instead...'))
            return pitch_set[0]
Exemple #6
0
 def pick_by_use_weight(self):
     """
     :return: Node instance
     """
     self.previous_node = self.current_node
     node = weighted_rand(self.node_list, 'discreet')
     self.current_node = self.find_node_by_name(node)
     return self.current_node
 def play(self):
     if random.uniform(0, 1) < self.mode_change_frequency:
         self.change_mode()
     action = rand.weighted_rand(self.action_weights, run_type='discreet')
     if action == 'listen until':
         return instructions.speaker_boxed_text()
     elif action == 'fermata':
         return instructions.draw_big_fermata(self.fermata_length_weights)
 def __init__(self,
              instrument_name='Flute',
              lowest_note=0,
              highest_note=36,
              upper_tessitura=28,
              max_time_at_high=60,
              initial_clef='treble',
              transposition_int=0,
              sustained_playing=True):
     self.instrument_name = instrument_name
     self.lowest_note = lowest_note
     self.highest_note = highest_note
     self.upper_tessitura = upper_tessitura
     self.max_time_at_high = max_time_at_high  # Longest allowed time to stay in a very high register
     self.initial_clef = initial_clef
     self.transposition_int = transposition_int
     self.note_array = note.NoteArray(
         initial_clef=self.initial_clef,
         transposition_int=self.transposition_int)
     self.score = score.Score(self.note_array)
     self.pitch_network = network.Network()
     self.build_pitch_rules()
     self.length_network = network.Network()
     self.build_length_rules()
     self.clef_list = [initial_clef]
     # True if instrument uses sustained sound (flute, arco string), false if not (piano, percussion, pizz string)
     self.sustained_playing = sustained_playing
     self.special_action_weights = [(0, 80), (1, 7), (2, 4), (3, 1)]
     self.text_instruction_list = text_instructions.shared_list
     self.previous_action = None
     self.clef_change_tolerance = 5
     self.preferred_clef_extra_weight = 4
     self.starting_pitch_weights = [(0, 40), (0.15, 70), (1, 0)]
     self.note_count_weights = rand.random_weight_list(
         1, random.randint(6, 15), 0.15)
     self.mode_change_frequency = 0.03
     self.natural_harmonic_frequency = round(random.uniform(0.04, 0.4), 3)
     self.artificial_harmonic_frequency = round(
         rand.weighted_rand([(0.01, 150), (0.2, 20), (0.4, 5), (0.5, 1)]),
         3)
     self.chord_frequency = round(
         rand.weighted_rand([(0.01, 150), (0.1, 20), (0.2, 0)]), 3)
def draw_big_fermata(length_weights, size_factor=1):
    """
    Builds and renders a lilypond file of a large fermata with an approximate timecode.
    The fermata is scaled according to the timecode. Returns the image path.
    :param length_weights: list of tuples for how many seconds the fermata should last
    :param size_factor: int multiplier for the scale of the fermata
    :return: str of new image path
    """
    assert isinstance(length_weights, list)
    # Note that this file path only works if both the lilypond file and the eps file are located in the same
    #   directories relative to each other! This could cause a big confusion in refactoring, but as far as I can tell,
    #   lilypond doesn't accept absolute paths in epsfile syntax
    vector_file_path = '../../Graphics/big_fermata_vector.eps'

    # Convert an input seconds value into a string representation timecode for use in the fermata
    def seconds_to_timecode(seconds):
        minutes, out_seconds = divmod(seconds, 60)
        if minutes == 0:
            minute_string = ''
        else:
            minute_string = str(minutes) + "\"\\char ##x2019\" "
        second_string = str(out_seconds)
        if len(second_string) == 1:
            second_string = "0" + second_string
        second_string += '\"\\char ##x201D'
        return minute_string + second_string

    length = rand.weighted_rand(length_weights, do_round=True)
    fermata_size = length * 0.222 * size_factor
    # Round length to the nearest 10 seconds
    length = int(round(length, -1))
    length_string = seconds_to_timecode(length)
    score = lilypond_file.LilypondFile()
    score.master_string = (
        """\\version "2.18.2"\n
                            \\header { tagline = #" " }
                            \\markup {\n
                            \\center-column {\n
                            \\line {\n
                            \\epsfile #X #""" + str(fermata_size) + ' #"' +
        vector_file_path + '"\n' +
        "}\n\\line {\\abs-fontsize #11\n\\bold {\\italic{ \\concat {" +
        "\n\\override #' (font-name . \"Book Antiqua Bold\")\n" +
        "\\char ##x2248\n\\override #' (font-name . \"CrimsonText bold italic\")\n \""
        + length_string + ' }}}}}}')
    score._string_built = True

    output_file_name = "fermata_" + str(config.FileNameIndex)
    output_png = score.save_and_render(output_file_name,
                                       view_image=False,
                                       autocrop=True,
                                       delete_ly=False)
    config.FileNameIndex += 1
    return output_png
 def auto_add_slurs(self):
     # Set up variables and weights
     slur_length_weights = [(1, 2), (4, 3), (6, 1)]
     slur_gap_weights = [(0, 4), (2, 5), (12, 1)]
     current_index = 0
     note_array_length = len(self.note_array.list)
     while 1 == 1:
         # reset current_index
         # Move current_index forward by slur_gap_weights
         current_index += rand.weighted_rand(slur_gap_weights,
                                             do_round=True)
         if current_index > note_array_length - 1:
             break
         if self.note_array.list[current_index].is_rest:
             current_index += 1
         slur_start = current_index
         # Find ideal stop_index based on slur_length_weights, snapping to before the first rest encountered (if any)
         ideal_stop_index = current_index + rand.weighted_rand(
             slur_length_weights, do_round=True)
         if ideal_stop_index > note_array_length - 1:
             ideal_stop_index = note_array_length - 1
         while current_index < ideal_stop_index:
             current_index += 1
             if self.note_array.list[current_index].is_rest or (
                     self.note_array.list[current_index].pitch_num
                     == self.note_array.list[current_index - 1].pitch_num):
                 current_index -= 1
                 break
         slur_stop = current_index
         if current_index > note_array_length - 1:
             break
         else:
             if slur_start == slur_stop:
                 continue
             else:
                 self.note_array.create_spanner(slur_start, slur_stop,
                                                'slur')
 def play(self):
     if random.uniform(0, 1) < self.mode_change_frequency:
         self.change_mode()
     action = rand.weighted_rand(self.action_weights, run_type='discreet')
     if action == 'listen until':
         return instructions.listen_until_you_hear()
     elif action == 'visualize':
         return instructions.visualize_a_color()
     elif action == 'fermata':
         return instructions.draw_big_fermata(self.fermata_length_weights)
     elif action == 'notice':
         return instructions.notice_something()
     else:
         print(
             'WARNING: Invalid action is being passed to Audience.play(), defaulting to "listen until"...'
         )
         return instructions.listen_until_you_hear()
    def play(self):

        self.refresh_lily()

        # Roll for special actions
        special_action_roll = rand.weighted_rand(self.special_action_weights, 'discreet')
        if special_action_roll != 0:
            if special_action_roll == 1:
                self.previous_action = 'Special Action 1'
                return self.special_action_1()
            elif special_action_roll == 2:
                self.previous_action = 'Special Action 2'
                return self.special_action_2()
            elif special_action_roll == 3 and self.previous_action != 'Special Action 3':
                self.previous_action = 'Special Action 3'
                return self.special_action_3()
        else:
            self.previous_action = 'Normal Play'

        note_count = rand.weighted_rand(self.note_count_weights, do_round=True)
        current_node = self.pitch_network.pick()
        while (not isinstance(current_node, nodes.NoteBehavior)) or (current_node.name == 'rest'):
            current_node = self.pitch_network.pick(current_node)

        # Pick first note randomly
        current_pitch = self._find_first_pitch(current_node.pitch_set, self.starting_pitch_weights)

        for i in range(0, note_count):
            current_dur = self.length_network.pick().get_value()

            while isinstance(current_node, nodes.Action):
                current_node = self.pitch_network.pick()
            if current_node.name == 'rest':
                if current_dur < self.minimum_rest_length:
                    current_dur = self.minimum_rest_length
                self.note_array.create_note(current_pitch, current_dur, is_rest=True)
            else:
                current_pitch = current_node.move_pitch(current_pitch)

                # Special handling for spacing of square noteheads, make a little wider
                if current_pitch in [4, 5, 7]:
                    current_dur += 1

                self.note_array.create_note(current_pitch, current_dur)
                if current_pitch == -3:
                    self.note_array.list[-1].notehead_style_code = 1
                elif current_pitch in [-5, -7]:
                    self.note_array.list[-1].notehead_style_code = 3
                elif current_pitch in [4, 5, 7]:
                    self.note_array.list[-1].notehead_style_code = 4

                # If it's the first note, do first-note rolls
                if i == 0:
                    # Roll for beater changes
                    if (random.uniform(0, 1) < self.beater_change_frequency) or self.current_beater is None:
                        new_beater = self.current_beater
                        while new_beater == self.current_beater:
                            new_beater = random.choice(self.beater_list)
                        self.current_beater = new_beater
                        reverse_mute_dict = {'hard yarn': 230, 'soft yarn': 231, 'soft rubber': 232}
                        self.note_array.list[-1].add_articulation(reverse_mute_dict[self.current_beater], 'over')

            current_node = self.pitch_network.pick(current_node)

            if random.uniform(0, 1) < self.mode_change_frequency:
                self.perc_change_mode()

        self.auto_add_dynamics()
        self.auto_add_slurs()

        self.score.note_array = self.note_array
        output_ly_file = lilypond_file.LilypondFile(self.score)
        output_file_name = self.instrument_name + '_' + str(config.FileNameIndex)
        output_png = output_ly_file.save_and_render(output_file_name, view_image=False, autocrop=True, delete_ly=False)
        config.FileNameIndex += 1
        return output_png
    def play(self):

        self.refresh_lily()

        # Roll for special actions
        special_action_roll = rand.weighted_rand(self.special_action_weights,
                                                 'discreet')
        if special_action_roll != 0:
            if special_action_roll == 1:
                self.previous_action = 'Special Action 1'
                return self.special_action_1()
            elif special_action_roll == 2:
                self.previous_action = 'Special Action 2'
                return self.special_action_2()
            elif special_action_roll == 3 and self.previous_action != 'Special Action 3':
                self.previous_action = 'Special Action 3'
                return self.special_action_3()
        else:
            self.previous_action = 'Normal Play'

        note_count = rand.weighted_rand(self.note_count_weights, do_round=True)
        current_node = self.pitch_network.pick()
        while (not isinstance(current_node, nodes.NoteBehavior)) or (
                current_node.name[:4] == 'rest'):
            current_node = self.pitch_network.pick()
        # Get first pitch
        current_pitch = self._find_first_pitch(current_node.pitch_set,
                                               self.starting_pitch_weights)

        for i in range(0, note_count):
            current_dur = self.length_network.pick().get_value()

            # pick through action (sub-network link) nodes
            while isinstance(current_node, nodes.Action):
                current_node = self.pitch_network.pick()
            if current_node.name[:4] == 'rest':
                self.note_array.create_note(current_pitch,
                                            current_dur,
                                            is_rest=True)
            else:
                current_pitch = current_node.move_pitch(current_pitch)
                self.note_array.create_note(current_pitch, current_dur)

                # With self.natural_harmonic_frequency, if current_pitch can be a natural harmonic, add it.
                if random.uniform(0, 1) < self.natural_harmonic_frequency:
                    harmonic_string_test = self.natural_harmonic_string(
                        current_pitch)
                    if harmonic_string_test is not None:
                        string_code_dict = {
                            'I': 220,
                            'II': 221,
                            'III': 222,
                            'IV': 223,
                            'V': 224,
                            'VI': 225
                        }
                        self.note_array.list[-1].add_articulation(
                            string_code_dict[harmonic_string_test], 'over')
                        self.note_array.list[-1].is_artificial_harmonic = True
                # With self.artifical_harmonic_frequency add an artificial harmonic at the 8ve
                elif (random.uniform(0, 1) < self.artificial_harmonic_frequency
                      and self.allow_artificial_harmonics
                      and (current_pitch < self.upper_tessitura)):
                    self.note_array.list[-1].is_artificial_harmonic = True
                    if isinstance(self.note_array.list[-1].pitch_num, int):
                        self.note_array.list[-1].pitch_num = [
                            self.note_array.list[-1].pitch_num
                        ]
                    assert isinstance(self.note_array.list[-1].pitch_num, list)
                    self.note_array.list[-1].pitch_num.append(
                        self.note_array.list[-1].pitch_num[0] + 12)
                # TODO: elaborate on guitar chords (not just open strings, over 2 strings, etc.)
                # Within self.chord_frequency, add an open-string double-stop
                elif (random.uniform(0, 1) < self.chord_frequency) and (
                        current_pitch < self.upper_tessitura):
                    if isinstance(self.note_array.list[-1].pitch_num, int):
                        self.note_array.list[-1].pitch_num = [
                            self.note_array.list[-1].pitch_num
                        ]
                    assert isinstance(self.note_array.list[-1].pitch_num, list)
                    open_string_pitch = self.get_best_open_string(
                        self.note_array.list[-1].pitch_num[0])
                    self.note_array.list[-1].pitch_num.append(
                        open_string_pitch)

            current_node = self.pitch_network.pick()

            if random.uniform(0, 1) < self.mode_change_frequency:
                self.change_mode()

        self.note_array.auto_build_octave_spanners()
        self.auto_add_dynamics()
        self.auto_add_slurs()
        if len(self.clef_list) > 1:
            self.note_array.auto_build_clefs(self.clef_list, self.initial_clef,
                                             self.clef_change_tolerance,
                                             self.preferred_clef_extra_weight)

        self.score.note_array = self.note_array
        output_ly_file = lilypond_file.LilypondFile(self.score)
        output_file_name = self.instrument_name + '_' + str(
            config.FileNameIndex)
        output_png = output_ly_file.save_and_render(output_file_name,
                                                    view_image=False,
                                                    autocrop=True,
                                                    delete_ly=False)
        config.FileNameIndex += 1
        return output_png
Exemple #14
0
    def play(self):
        self.refresh_lily()

        # Roll for special actions
        special_action_roll = rand.weighted_rand(self.special_action_weights,
                                                 'discreet')
        if special_action_roll != 0:
            if special_action_roll == 1:
                self.previous_action = 'Special Action 1'
                return self.special_action_1()
            elif special_action_roll == 2:
                self.previous_action = 'Special Action 2'
                return self.special_action_2()
            elif special_action_roll == 3 and self.previous_action != 'Special Action 3':
                self.previous_action = 'Special Action 3'
                return self.special_action_3()
        else:
            self.previous_action = 'Normal Play'

        note_count = rand.weighted_rand(self.note_count_weights, do_round=True)
        current_node = self.pitch_network.pick()
        while (not isinstance(current_node, nodes.NoteBehavior)) or (
                current_node.name[:4] == 'rest'):
            current_node = self.pitch_network.pick()
        # Get first pitch with a fairly strong bias toward the lower pitches in the register
        current_pitch = current_node.pitch_set[rand.weighted_rand(
            [(0, 100), (len(current_node.pitch_set) - 1, 1)], do_round=True)]

        current_time_in_upper_tessitura = 0

        for i in range(0, note_count):
            current_dur = self.length_network.pick().get_value()
            while isinstance(current_node, nodes.Action):
                current_node = self.pitch_network.pick()
            if current_node.name[:4] == 'rest':
                self.note_array.create_note(current_pitch,
                                            current_dur,
                                            is_rest=True)
                current_time_in_upper_tessitura = 0
            else:
                current_pitch = current_node.move_pitch(current_pitch)
                self.note_array.create_note(current_pitch, current_dur)
                current_time_in_upper_tessitura += current_dur

            current_node = self.pitch_network.pick()
            # If the current time in the upper range exceeds the limit, change current_node to a downward pointing one
            if current_time_in_upper_tessitura > self.max_time_at_high:
                if random.randint(0, 1) == 0:
                    current_node = self.pitch_network.node_list[5]
                else:
                    current_node = self.pitch_network.node_list[7]
                current_time_in_upper_tessitura = 0

            if random.uniform(0, 1) < self.mode_change_frequency:
                self.change_mode()

        self.auto_add_slurs()
        self.auto_add_dynamics()
        if len(self.clef_list) > 1:
            self.note_array.auto_build_clefs(self.clef_list, self.clef_list[0],
                                             self.clef_change_tolerance,
                                             self.preferred_clef_extra_weight)
        if self.instrument_name == 'Flute' or self.instrument_name == 'Alto Flute':
            self.note_array.auto_build_octave_spanners()

        self.score.note_array = self.note_array
        output_ly_file = lilypond_file.LilypondFile(self.score)
        output_file_name = self.instrument_name + '_' + str(
            config.FileNameIndex)
        output_png = output_ly_file.save_and_render(output_file_name,
                                                    view_image=False,
                                                    autocrop=True,
                                                    delete_ly=False)
        config.FileNameIndex += 1
        return output_png
if __name__ == '__main__':
    t_network = prebuilt_networks.indenter()

    x = 25
    t_network.pick()

    count = 0
    while count < 1000:
        out_string = ""
        for index in range(0, 200):
            out_string += " "
            if index == x:
                char_num = weighted_rand([(
                    6,
                    5,
                ), (10, 30), (50, 4)],
                                         do_round=True)
                out_string += ('[' * char_num)
                break
        if count % 8 == 0:
            out_string = "-" + out_string[1:]
        x += t_network.current_node.get_value()
        if x < 0:
            x = 5
        if x > 200:
            x = 195

        print(out_string)
        t_network.pick()
        time.sleep(0.05)
Exemple #16
0
    def play(self):

        self.refresh_lily()

        # Roll for special actions
        special_action_roll = rand.weighted_rand(self.special_action_weights, 'discreet')
        if special_action_roll != 0:
            if special_action_roll == 1:
                self.previous_action = 'Special Action 1'
                return self.special_action_1()
            elif special_action_roll == 2:
                self.previous_action = 'Special Action 2'
                return self.special_action_2()
            elif special_action_roll == 3 and self.previous_action != 'Special Action 3':
                self.previous_action = 'Special Action 3'
                return self.special_action_3()
        else:
            self.previous_action = 'Normal Play'

        note_count = rand.weighted_rand(self.note_count_weights, do_round=True)
        current_node = self.pitch_network.pick()
        while (not isinstance(current_node, nodes.NoteBehavior)) or (current_node.name[:4] == 'rest'):
            current_node = self.pitch_network.pick()
        # Get first pitch
        current_pitch = self._find_first_pitch(current_node.pitch_set, self.starting_pitch_weights)

        for i in range(0, note_count):
            current_dur = self.length_network.pick().get_value()

            # pick through action (sub-network link) nodes
            while isinstance(current_node, nodes.Action):
                current_node = self.pitch_network.pick()
            if current_node.name[:4] == 'rest':
                self.note_array.create_note(current_pitch, current_dur, is_rest=True)
            else:
                current_pitch = current_node.move_pitch(current_pitch)
                self.note_array.create_note(current_pitch, current_dur)

                # If it's the first note, roll for pizz/mute changes
                if i == 0:
                    if self.is_currently_pizz and random.randint(0, 6) == 0:
                        self.note_array.list[0].add_articulation(201, 'over')
                        self.is_currently_pizz = False
                        self.sustained_playing = True
                    elif not self.is_currently_pizz and random.randint(0, 16) == 0:
                        self.note_array.list[0].add_articulation(200, 'over')
                        self.is_currently_pizz = True
                        self.sustained_playing = False

                    if self.is_currently_muted and random.randint(0, 15) == 0:
                        self.note_array.list[0].add_articulation(203, 'over')
                        self.is_currently_muted = False
                    elif not self.is_currently_muted and random.randint(0, 15) == 0:
                        self.note_array.list[0].add_articulation(202, 'over')
                        self.is_currently_muted = True

                # Within self.natural_harmonic_frequency, if current_pitch can be a natural harmonic, add it
                if random.uniform(0, 1) < self.natural_harmonic_frequency and self.can_be_harmonic(current_pitch):
                    self.note_array.list[-1].add_articulation(105, 'over')
                # Within self.artificial_harmonic_frequency, add an artificial harmonic at the 4th
                elif (random.uniform(0, 1) < self.artificial_harmonic_frequency and
                            self.allow_artificial_harmonics and (current_pitch < self.upper_tessitura)):
                    self.note_array.list[-1].is_artificial_harmonic = True
                    if isinstance(self.note_array.list[-1].pitch_num, int):
                        self.note_array.list[-1].pitch_num = [self.note_array.list[-1].pitch_num]
                    assert isinstance(self.note_array.list[-1].pitch_num, list)
                    self.note_array.list[-1].pitch_num.append(self.note_array.list[-1].pitch_num[0]+5)
                # Within self.chord_frequency, add an open-string double-stop
                elif (random.uniform(0, 1) < self.chord_frequency) and (current_pitch < self.upper_tessitura):
                    if isinstance(self.note_array.list[-1].pitch_num, int):
                        self.note_array.list[-1].pitch_num = [self.note_array.list[-1].pitch_num]
                    open_string_pitch = self.get_best_open_string(self.note_array.list[-1].pitch_num[0])
                    self.note_array.list[-1].pitch_num.append(open_string_pitch)

            current_node = self.pitch_network.pick()

            if random.uniform(0, 1) < self.mode_change_frequency:
                self.change_mode()

        self.note_array.auto_build_octave_spanners()
        self.auto_add_dynamics()
        self.auto_add_slurs()
        if len(self.clef_list) > 1:
            self.note_array.auto_build_clefs(self.clef_list, self.initial_clef,
                                             self.clef_change_tolerance, self.preferred_clef_extra_weight)

        self.score.note_array = self.note_array
        output_ly_file = lilypond_file.LilypondFile(self.score)
        output_file_name = self.instrument_name + '_' + str(config.FileNameIndex)
        output_png = output_ly_file.save_and_render(output_file_name, view_image=False, autocrop=True, delete_ly=False)
        config.FileNameIndex += 1
        return output_png
Exemple #17
0
def make_parts(parts_list, target_page_count=20, clean_up_temps=True):

    # Start the clock
    clock_start_time = time.time()

    # Make sure input part_list is a list for proper processing
    if not isinstance(parts_list, list):
        parts_list = [parts_list]

    # Set up text material paths relative to config.ResourcesFolder
    audience_text_material_path = os.path.join(config.ResourcesFolder, 'Text Material',
                                               'audience_main_text_material.txt')
    speaker_text_material_path = os.path.join(config.ResourcesFolder, 'Text Material',
                                              'speaker_main_text_material.txt')
    speaker_and_instrument_material_path = os.path.join(config.ResourcesFolder, 'Text Material',
                                                        'speaker_and_instrument_main_text_material.txt')
    long_magnet_string_path = os.path.join(config.ResourcesFolder, 'Text Material', 'shared_string.txt')

    # For use in text parts
    long_shared_text_string = open(long_magnet_string_path).read()
    # The constant bounds built into the unit count weights are designed to build 20-page long (ish) parts
    # This count_multiplier allows for the weights to be adjusted proportionally to get closer to the target_page_count
    count_multiplier = target_page_count / 20.0
    audience_unit_count_weights = rand.random_weight_list(1600 * count_multiplier, 1800 * count_multiplier,
                                                          max_possible_weights=5)
    speaker_unit_count_weights = rand.random_weight_list(1600 * count_multiplier, 1800 * count_multiplier,
                                                         max_possible_weights=5)
    instrument_unit_count_weights = rand.random_weight_list(525 * count_multiplier, 650 * count_multiplier,
                                                            max_possible_weights=5)
    combined_unit_count_weights = rand.random_weight_list(1100 * count_multiplier, 1175 * count_multiplier,
                                                          max_possible_weights=5)

    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    # Main part-making loop - iterate through parts_to_make, building parts accordingly
    for part_name in parts_list:

        # TODO: these four cases of part_name have a LOT in common, might be possible to collapse in a more elegant way?
        if part_name == 'Audience':
            # Audience Parts
            unit_count = rand.weighted_rand(audience_unit_count_weights, do_round=True)
            part_scribe = pdf_scribe.DynamicScribe(audience_text_material_path, instrument_name=part_name)
            part_base_pdf_path = os.path.join(config.ResourcesFolder, 'temp',
                                              part_name + '_base' + str(random.randint(0, 10000000)) + '.pdf')

            # Main document population
            part_scribe.populate_item_list(unit_count, True)
            # Add non-network-driven events
            # Add big text magnet in bold at a random location
            part_scribe.item_list.insert(random.randint(0, len(part_scribe.item_list)-1),
                                         nodes.Word(long_shared_text_string))
            # Add opening few words in italics
            part_scribe.item_list.insert(0, nodes.Word('^here, the leafsighaway, '))
            # Add final fermata of variable size
            final_fermata = text_instructions.draw_big_fermata_without_timecode(random.randint(30, 60))
            part_scribe.item_list.append(nodes.Image(final_fermata, 'center', 0, 0))
            # Render the part
            base_part = part_scribe.render(part_base_pdf_path)

            print(document_tools.part_attach_notes(os.path.realpath(base_part), part_name))

        elif part_name == 'Speaker':
            # Speaker parts #
            unit_count = rand.weighted_rand(speaker_unit_count_weights, do_round=True)
            part_scribe = pdf_scribe.DynamicScribe(speaker_text_material_path, instrument_name=part_name, allow_self_links=True)
            part_base_pdf_path = os.path.join(config.ResourcesFolder, 'temp',
                                              part_name + '_base' + str(random.randint(0, 10000000)) + '.pdf')

            # Main document population
            part_scribe.populate_item_list(unit_count, True)
            # Add non-network-driven events
            # Add big text magnet in bold at a random location
            part_scribe.item_list.insert(random.randint(0, len(part_scribe.item_list)-1),
                                         nodes.Word(long_shared_text_string))
            # Add opening few words in italics
            part_scribe.item_list.insert(0, nodes.Word('^here, the leafsighaway, '))
            # Add a random amount of space from the top of the page
            part_scribe.item_list.insert(0, nodes.Word('\n' *
                                                       rand.weighted_rand([(0, 5), (12, 6), (40, 2), (90, 0)], do_round=True)))

            # Render the part
            base_part = part_scribe.render(part_base_pdf_path)

            print(document_tools.part_attach_notes(os.path.realpath(base_part), part_name))

        elif part_name.endswith('& Speaker'):
            part_instrument_name = part_name[:-10]
            unit_count = rand.weighted_rand(combined_unit_count_weights, do_round=True)
            part_scribe = pdf_scribe.DynamicScribe(speaker_and_instrument_material_path,
                                                   instrument_name=part_instrument_name,
                                                   allow_self_links=True, merge_same_words=True)
            part_scribe.instrument.special_action_weights = [(0, 45), (1, 7), (2, 4), (3, 1)]
            part_base_pdf_path = os.path.join(config.ResourcesFolder, 'temp',
                                              part_name + '_and_Speaker_base' +
                                              str(random.randint(0, 10000000)) + '.pdf')

            # Main document population
            part_scribe.populate_item_list(unit_count, True)
            # Add non-network-driven events
            # Add big text magnet in bold at a random location
            part_scribe.item_list.insert(random.randint(0, len(part_scribe.item_list)-1),
                                         nodes.Word(long_shared_text_string))
            # Add opening few words in italics
            part_scribe.item_list.insert(0, nodes.Word('^here, the leafsighaway, '))
            # Add a random amount of space from the top of the page
            part_scribe.item_list.insert(0, nodes.Word('\n' *
                                                       rand.weighted_rand([(0, 5), (12, 6), (40, 2), (90, 0)], do_round=True)))

            # Render the part
            base_part = part_scribe.render(part_base_pdf_path)
            print(document_tools.part_attach_notes(os.path.realpath(base_part), part_name))

        else:
            # Instrument-only parts
            unit_count = rand.weighted_rand(instrument_unit_count_weights, do_round=True)

            part_scribe = pdf_scribe.InstrumentScribe(part_name)
            part_base_pdf_path = os.path.join(config.ResourcesFolder, 'temp', part_name +
                                              '_base' + str(random.randint(0, 10000000)) + '.pdf')
            base_part = part_scribe.render(unit_count, part_base_pdf_path, update_frequency=1)

            # Merge with part notes and return
            print(document_tools.part_attach_notes(os.path.realpath(base_part), part_name))

        # Clean up part_scribe's temporary files (.ly and .png)
        if clean_up_temps:
            # Wait a little bit so that reportlab has a second to tidy up
            time.sleep(1)
            part_scribe.clean_up_files(True, True)
            subprocess.call(['del', base_part], shell=True)

        # Print time elapsed
        time_elapsed = divmod(time.time() - clock_start_time, 60)
        print("New part built in " + str(int(time_elapsed[0])) + \
              ' minutes and ' + str(int(time_elapsed[1])) + ' seconds')
Exemple #18
0
 def get_value(self, do_round=True):
     return rand.weighted_rand(self.weights,
                               'interpolated',
                               do_round=do_round)
    def render(self,
               steps,
               output_file,
               open_when_finished=False,
               update_frequency=25):

        output_file_path = output_file
        if not os.path.isabs(output_file_path):
            output_file_path = os.path.realpath(output_file_path)

        left_indent = rand.weighted_rand([
            rand.Weight(0, 95),
            rand.Weight(10, 125),
            rand.Weight(60, 55),
            rand.Weight(135, 0)
        ])
        right_indent = rand.weighted_rand([
            rand.Weight(0, 40),
            rand.Weight(10, 80),
            rand.Weight(60, 60),
            rand.Weight(130, 40)
        ])
        # Cycle through all nodes stored in self.item_list, use them to fill the contents of self.flowable_list
        for i in range(steps):
            if i % update_frequency == 0:
                print(('Rendering step:  ' + str(i) + '  -  ' +
                       str(round((i / (steps * 1.0)) * 100, 0)) + '%'))

            left_indent += self.indenter_network_left.pick().get_value()
            right_indent += self.indenter_network_right.pick().get_value()
            if left_indent > config.DocWorkingWidth - config.MinColumnWidth:
                left_indent = config.DocWorkingWidth - config.MinColumnWidth - 30
            if left_indent < 0:
                left_indent = random.randint(0, 15)
            if config.DocWorkingWidth - right_indent < left_indent:
                right_indent = config.DocWorkingWidth - left_indent - 30
            if right_indent < 0:
                right_indent = 0
            dummy_align = rand.weighted_rand(
                [rand.Weight('LEFT', 70),
                 rand.Weight('RIGHT', 8)],
                run_type='discreet')

            if self.instrument.previous_action == 'Special Action 3':
                self.action_network.current_node = self.action_network.node_list[
                    -1]
            play_or_blank = self.action_network.pick().get_value()

            if play_or_blank:
                image_path = self.instrument.play()
                ly_path = image_path[:-4] + ".ly"
                # Add image_path and ly_path to self.file_cleanup_list for optional cleanup later on
                self.file_cleanup_list.append(image_path)
                self.file_cleanup_list.append(ly_path)

                image_line = InlineImage(image_path,
                                         left_indent=left_indent,
                                         right_indent=right_indent,
                                         alignment=dummy_align)
                image_line.add_to_doc(self.flowable_list)
            else:
                self.flowable_list.append(
                    reportlab.platypus.Spacer(16, config.FontSize))

        # Build page header function to be passed later to output_doc.build()
        def build_header_canvas(canvas, doc):
            # Set up positions
            header_y_position = (11 * rl_inch) - 45
            page_number = doc.page
            if page_number % 2 == 0:
                # Left-hand page
                page_number_x_position = 60
            else:
                # Right-hand page
                page_number_x_position = (8.5 * rl_inch) - 60

            canvas.saveState()
            canvas.setFont(config.FontName, 11)
            canvas.drawCentredString((8.5 * rl_inch) / 2, header_y_position,
                                     self.instrument_name)

            canvas.drawString(page_number_x_position, header_y_position,
                              str(page_number))
            canvas.restoreState()

        # Save the doc to an output pdf file
        output_doc = reportlab.platypus.SimpleDocTemplate(output_file_path)
        output_doc.pagesize = (8.5 * rl_inch, 11 * rl_inch)
        if self.draw_header:
            output_doc.build(self.flowable_list,
                             onFirstPage=build_header_canvas,
                             onLaterPages=build_header_canvas)
        else:
            output_doc.build(self.flowable_list)

        # Open the finished pdf file if requested in render() call
        if open_when_finished:
            import subprocess
            subprocess.call(['start', '', output_file_path], shell=True)

        # Return path to the final built pdf
        return output_file
    def auto_add_dynamics(self):
        """
        Takes a completed self.note_array and adds spanners and dynamics
        :return: None
        """
        note_array_length = len(self.note_array.list) - 1

        # Add dynamics #######
        # Add starting dynamic
        starting_dynamic = rand.weighted_rand([(0, 100), (2, 400), (4, 50),
                                               (6, 2), (9, 1)],
                                              do_round=True)
        list_of_dynamics = [
            (0, starting_dynamic)
        ]  # list of tuples of form (attach index, dynamic_key)

        # Add tuples to list_of_dynamics
        for i in range(1, note_array_length - 1):
            # Find next dynamic attachee index based on previous tuple
            next_index = list_of_dynamics[i - 1][0] + rand.weighted_rand(
                [(1, 3), (6, 100), (10, 0)], do_round=True)
            if next_index > note_array_length - 1:
                break
            # Find next dynamic based on distance from previous dynamic
            next_dynamic = list_of_dynamics[i - 1][1] + rand.weighted_rand(
                [(-4, 1), (-1, 10), (0, 0), (1, 10), (4, 1)], do_round=True)
            if next_dynamic > 9:
                next_dynamic = 9
            elif next_dynamic < 0:
                next_dynamic = 0
            # To prevent dynamic repetition, reroll until next_dynamic is different than the previous dynamic
            while next_dynamic == list_of_dynamics[i - 1][1]:
                next_dynamic = list_of_dynamics[i - 1][1] + rand.weighted_rand(
                    [(-4, 1), (-1, 10), (0, 0), (1, 10), (4, 1)],
                    do_round=True)
            list_of_dynamics.append((next_index, next_dynamic))

        # Attach dynamics to Note instances in self.note_array.list
        for dynamic_tuple in list_of_dynamics:
            try:
                if not self.note_array.list[dynamic_tuple[0]].is_rest:
                    self.note_array.list[dynamic_tuple[0]].set_dynamic(
                        dynamic_tuple[1])
            except IndexError:
                print((
                    'Index Error in Instrument.auto_add_dynamics()' +
                    'while attaching dynamics to note instances, skipping this one...'
                ))
                continue

        ############################### Build Hairpins ##################################

        def find_dynamic_beteen(note_array_instance, start_index, stop_index):
            # Returns the index of the left-most dynamic between two indices,
            # returns None if there are no dynamics between
            for test_index in range(start_index + 1, stop_index):
                try:
                    if note_array_instance.list[test_index].dynamic != '':
                        return test_index
                except IndexError:
                    return None
            else:
                return None

        # Randomly get number of hairpin counts based on a weighted rand within bounds of note_array_length
        if self.sustained_playing:
            hairpin_length_weights = [(1, 2), (4, 5), (7, 1), (9, 0)]
        else:
            hairpin_length_weights = [(3, 2), (4, 5), (7, 1), (9, 0)]
        hairpin_gap_weights = [(0, 6), (4, 2), (6, 1), (10, 3)]
        current_index = 0
        current_index += rand.weighted_rand(hairpin_gap_weights, do_round=True)

        while current_index < note_array_length:

            # If we've reached the final real note (at [note_array_length - 1] )
            # and the note is very short, stop adding hairpins
            if (current_index == note_array_length -
                    1) and (self.note_array.list[current_index].length < 3):
                break

            # Find start_index
            start_index = current_index
            # Check that self.note_array[start_index] is not on a rest, moving forward 1 if so
            if self.note_array.list[start_index].is_rest:
                current_index += 1
                start_index = current_index

            # Find stop_index
            current_index += rand.weighted_rand(hairpin_length_weights,
                                                do_round=True)
            # If needed, move stop_index left until there are no in-between dynamics
            between_index = find_dynamic_beteen(self.note_array, start_index,
                                                current_index)
            while between_index is not None:
                between_index = find_dynamic_beteen(self.note_array,
                                                    start_index, current_index)
                if between_index is not None:
                    current_index = between_index
            stop_index = current_index
            # Check that self.note_array[start_index] is not AFTER a rest, moving back 1 if so
            try:
                if self.note_array.list[stop_index - 1].is_rest:
                    current_index -= 1
                    stop_index = current_index
            except IndexError:
                stop_index = note_array_length - 1

            if start_index < 0:
                start_index = 0
            if stop_index > note_array_length:  #################### used to test/put to note_array_length - 1
                stop_index = note_array_length

            ##################### Get hairpin direction #######################
            start_dynamic = None
            stop_dynamic = None

            def find_dynamic(note_array_instance, index, direction):
                # direction = 1 if searching rightward, -1 if searching leftward
                array_end_reached = False
                search_index = index
                while not array_end_reached:
                    try:
                        if note_array_instance.list[search_index].dynamic != '':
                            return note_array_instance.list[
                                search_index].dynamic
                        search_index += direction
                    except IndexError:
                        array_end_reached = True
                return None

            if self.note_array.list[start_index].dynamic == '':
                start_dynamic = find_dynamic(self.note_array, start_index, -1)
            else:
                start_dynamic = self.note_array.list[start_index].dynamic

            if self.note_array.list[stop_index].dynamic == '':
                stop_dynamic = find_dynamic(self.note_array, stop_index, 1)
            else:
                stop_dynamic = self.note_array.list[stop_index].dynamic

            reverse_dynamic_dict = {
                'pppp': 0,
                'ppp': 1,
                'pp': 2,
                'p': 3,
                'mp': 4,
                'mf': 5,
                'f': 6,
                'ff': 7,
                'fff': 8,
                'ffff': 9
            }
            try:
                start_dynamic = reverse_dynamic_dict[start_dynamic]
            except KeyError:
                start_dynamic = random.randint(0, 9)

            try:
                stop_dynamic = reverse_dynamic_dict[stop_dynamic]
            except KeyError:
                stop_dynamic = random.randint(0, 9)

            if start_dynamic > stop_dynamic:
                # dim
                hairpin_direction = -1
            else:
                # cresc
                hairpin_direction = 1

            # Move forward for next hairpin start
            current_index += rand.weighted_rand(hairpin_gap_weights,
                                                do_round=True)

            if start_index == stop_index:
                continue
            else:
                if self.note_array.approximate_distance(
                        start_index, stop_index) < 9:
                    continue
                if hairpin_direction == -1:
                    self.note_array.create_spanner(start_index, stop_index,
                                                   'dim')
                else:
                    self.note_array.create_spanner(start_index, stop_index,
                                                   'cresc')
Exemple #21
0
    def play(self):

        self.refresh_lily()

        # Roll for special actions
        special_action_roll = rand.weighted_rand(self.special_action_weights,
                                                 'discreet')
        if special_action_roll != 0:
            if special_action_roll == 1:
                self.previous_action = 'Special Action 1'
                return self.special_action_1()
            elif special_action_roll == 2:
                self.previous_action = 'Special Action 2'
                return self.special_action_2()
            elif special_action_roll == 3 and self.previous_action != 'Special Action 3':
                self.previous_action = 'Special Action 3'
                return self.special_action_3()
        else:
            self.previous_action = 'Normal Play'

        note_count = rand.weighted_rand(self.note_count_weights, do_round=True)
        current_node = self.pitch_network.pick()
        while (not isinstance(current_node, nodes.NoteBehavior)) or (
                current_node.name[:4] == 'rest'):
            current_node = self.pitch_network.pick()
        # Get first pitch
        current_pitch = self._find_first_pitch(current_node.pitch_set,
                                               self.starting_pitch_weights)

        current_time_in_upper_tessitura = 0

        for i in range(0, note_count):
            current_dur = self.length_network.pick().get_value()
            while isinstance(current_node, nodes.Action):
                current_node = self.pitch_network.pick()
            if current_node.name[:4] == 'rest':
                self.note_array.create_note(current_pitch,
                                            current_dur,
                                            is_rest=True)
                current_time_in_upper_tessitura = 0
            else:
                current_pitch = current_node.move_pitch(current_pitch)
                self.note_array.create_note(current_pitch, current_dur)
                current_time_in_upper_tessitura += current_dur

                # If it's the first note, do first-note rolls
                if i == 0:
                    # Roll for mute changes
                    if (random.uniform(0, 1) < self.mute_change_frequency) or (
                            self.current_mute is None):
                        new_mute = self.current_mute
                        while new_mute == self.current_mute:
                            new_mute = random.choice(self.mute_list)
                        self.current_mute = new_mute
                        reverse_mute_dict = {
                            'senza sord': 203,
                            'straight mute': 204,
                            'practice mute': 205
                        }
                        self.note_array.list[-1].add_articulation(
                            reverse_mute_dict[self.current_mute], 'over')

            current_node = self.pitch_network.pick()
            # If the current time in the upper range exceeds the limit, change current_node to a downward pointing one
            if current_time_in_upper_tessitura > self.max_time_at_high:
                if random.randint(0, 1) == 0:
                    current_node = self.pitch_network.node_list[5]
                else:
                    current_node = self.pitch_network.node_list[7]
                current_time_in_upper_tessitura = 0

            if random.uniform(0, 1) < self.mode_change_frequency:
                self.change_mode()

        self.auto_add_dynamics()
        if len(self.clef_list) > 1:
            self.note_array.auto_build_clefs(self.clef_list, self.initial_clef,
                                             self.clef_change_tolerance,
                                             self.preferred_clef_extra_weight)
        self.auto_add_slurs()

        self.score.note_array = self.note_array
        output_ly_file = lilypond_file.LilypondFile(self.score)
        output_file_name = self.instrument_name + '_' + str(
            config.FileNameIndex)
        output_png = output_ly_file.save_and_render(output_file_name,
                                                    view_image=False,
                                                    autocrop=True,
                                                    delete_ly=False)
        config.FileNameIndex += 1
        return output_png
    def render(self,
               output_file,
               open_when_finished=False,
               update_frequency=25):
        """
        Cycles through every Node in self.item_list[] and creates a word doc accordingly
        :param output_file: path to output to
        :param open_when_finished: Bool
        :param update_frequency: int - give an update via print function every update_frequency steps in the render
        :return: str - path to newly built PDF file
        """

        output_file_path = output_file
        if not os.path.isabs(output_file_path):
            output_file_path = os.path.realpath(output_file_path)

        if not self.network.node_list:
            print("ERROR: trying to walk an empty network, ignoring...")
            return False

        left_indent = rand.weighted_rand([
            rand.Weight(0, 95),
            rand.Weight(10, 125),
            rand.Weight(60, 55),
            rand.Weight(135, 0)
        ])
        right_indent = rand.weighted_rand([
            rand.Weight(0, 40),
            rand.Weight(10, 80),
            rand.Weight(60, 60),
            rand.Weight(130, 40)
        ])

        # Cycle through all nodes stored in self.item_list, use them to fill the contents of self.flowable_list
        current_line = Paragraph()
        for i in range(len(self.item_list)):

            if i % update_frequency == 0:
                print(('Rendering step:  ' + str(i) + '  -  ' +
                       str(round(
                           (i / (len(self.item_list) - 1.0)) * 100)) + '%'))
            # Find indentation and alignment
            left_indent += self.indenter_network_left.pick().get_value()
            right_indent += self.indenter_network_right.pick().get_value()
            if left_indent > config.DocWorkingWidth - config.MinColumnWidth:
                left_indent = config.DocWorkingWidth - config.MinColumnWidth - 30
            if left_indent < 0:
                left_indent = random.randint(0, 30)
                right_indent -= left_indent
            if config.DocWorkingWidth - right_indent < left_indent + config.MinColumnWidth:
                right_indent = config.DocWorkingWidth - left_indent - config.MinColumnWidth - 1
            if right_indent < 0:
                right_indent = random.randint(0, 30)
                left_indent -= right_indent

            dummy_align = rand.weighted_rand([
                rand.Weight('LEFT', 70),
                rand.Weight('RIGHT', 5),
                rand.Weight('JUSTIFY', 15)
            ],
                                             run_type='discreet')

            if isinstance(self.item_list[i], nodes.Word):
                temp_text_unit = TextUnit('')
                temp_string = self.item_list[i].name
                if temp_string[0] == '^':
                    temp_text_unit.italic = True
                    temp_string = temp_string[1:]
                if temp_string[0] == '%':
                    temp_text_unit.bold = True
                    temp_string = temp_string[1:]
                if len(current_line.contents) != 0:
                    temp_string = " " + temp_string
                temp_text_unit.text = temp_string

                current_line.add_text_unit(temp_text_unit)

            elif isinstance(self.item_list[i], nodes.Punctuation):
                temp_string = self.item_list[i].name
                if temp_string == '-':
                    temp_string = ' -'
                current_line.add_text_unit(TextUnit(temp_string))

            elif isinstance(self.item_list[i], nodes.Image):
                if current_line.contents is not None:
                    # Send current_line contents to a new line
                    # current_line.add_to_doc(self.doc, self.base_style)
                    current_line.add_to_doc(self.flowable_list,
                                            self.base_paragraph_style)
                    current_line = Paragraph()
                if self.item_list[i].left_indent is not None:
                    left_indent = self.item_list[i].left_indent
                if self.item_list[i].right_indent is not None:
                    right_indent = self.item_list[i].right_indent
                if self.item_list[i].alignment is not None:
                    dummy_align = self.item_list[i].alignment
                image_line = InlineImage(self.item_list[i].get_value(),
                                         left_indent=left_indent,
                                         right_indent=right_indent,
                                         alignment=dummy_align)
                image_line.add_to_doc(self.flowable_list)

            elif isinstance(self.item_list[i], nodes.Action):
                if self.item_list[i].name == '+':
                    if current_line.contents is not None:
                        # Send current_line contents to a new line
                        current_line.add_to_doc(self.flowable_list,
                                                self.base_paragraph_style)
                        current_line = Paragraph()
                    # Add image with self.instrument
                    if self.instrument is not None:

                        if not self.instrument_is_initialized:
                            self.initialize_instrument()
                        image_path = self.instrument.play()
                        ly_path = image_path[:-4] + ".ly"
                        # Add image_path and ly_path to self.file_cleanup_list for optional cleanup later on
                        self.file_cleanup_list.append(image_path)
                        self.file_cleanup_list.append(ly_path)

                        image_line = InlineImage(image_path,
                                                 left_indent=left_indent,
                                                 right_indent=right_indent,
                                                 alignment=dummy_align)
                        image_line.add_to_doc(self.flowable_list)

            elif isinstance(self.item_list[i], nodes.BlankLine):
                if current_line.contents is not None:
                    # Send current_line contents to a new line
                    current_line.left_indent = left_indent
                    current_line.right_indent = right_indent
                    current_line.alignment = dummy_align
                    current_line.add_to_doc(self.flowable_list,
                                            self.base_paragraph_style)
                    current_line = Paragraph()
                self.flowable_list.append(
                    reportlab.platypus.Spacer(16, config.FontSize))

            elif (i == len(self.item_list) - 1) and (current_line.contents
                                                     is not None):
                current_line.left_indent = left_indent
                current_line.right_indent = right_indent
                current_line.alignment = dummy_align
                current_line.add_to_doc(self.flowable_list,
                                        self.base_paragraph_style)
                current_line = Paragraph()

        # Build page header function to be passed later to output_doc.build()
        def build_header_canvas(canvas, doc):
            # Set up positions
            header_y_position = (11 * rl_inch) - 45
            page_number = doc.page
            if page_number % 2 == 0:
                # Left-hand page
                page_number_x_position = 60
            else:
                # Right-hand page
                page_number_x_position = (8.5 * rl_inch) - 60

            canvas.saveState()
            canvas.setFont(config.FontName, 11)
            canvas.drawCentredString((8.5 * rl_inch) / 2, header_y_position,
                                     self.instrument_name)

            canvas.drawString(page_number_x_position, header_y_position,
                              str(page_number))
            canvas.restoreState()

        # Save the doc to an output pdf file
        output_doc = reportlab.platypus.SimpleDocTemplate(output_file_path)
        output_doc.pagesize = (8.5 * rl_inch, 11 * rl_inch)
        if self.draw_header:
            output_doc.build(self.flowable_list,
                             onFirstPage=build_header_canvas,
                             onLaterPages=build_header_canvas)
        else:
            output_doc.build(self.flowable_list)

        # Open the finished pdf file if requested in render() call
        if open_when_finished:
            import subprocess
            subprocess.call(['start', '', output_file_path], shell=True)

        # Return path to the final built pdf
        return output_file