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]
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]
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
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)
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
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')
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')
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