def get_path(filename): """ get_path takes in a filename, loads the data, and returns the path found by Harmony, or None Parameters data: dictionary mapping n, colors, and swaps for a given initial state of the game """ data = get_harmony_text(filename) # get values from data dict n = data["n"] colors = data["colors"] swaps = data["swaps"] test = data["test"] # this formatting is if filename = "cases/#.in" print "Test {}: {}\n".format(filename[6], test) # load and solve game harmony = Harmony(n, colors, swaps) path = harmony.solve() return path
def testLogic_game_solved(self): """ testLogic_game_solved tests whether a solved game is reported as solved """ n = 2 colors = [0, 0, 1, 1] swaps = [0, 0, 0, 0] harmony = Harmony(n, colors, swaps) self.assertTrue(harmony.game_solved())
def testSearch_none(self): """ testSearch_none tests if None is returned upon an impossible situation """ n = 2 colors = [1, 1, 0, 0] swaps = [0, 0, 0, 0] harmony = Harmony(n, colors, swaps) path = harmony.solve() assert path is None
def testPathfinding_get_swappable_none(self): """ testPathfinding_get_swappable_none tests that get_swappable returns an empty list given no swaps available """ n = 2 colors = [0, 0, 1, 1] swaps = [0, 0, 0, 0] harmony = Harmony(n, colors, swaps) self.assertEqual(harmony.get_swappable(), [])
def testLogic_has_waps_left_none(self): """ testLogic_has_waps_left_none tests whether swaps_left, given no remaining total swaps, returns False """ n = 2 colors = [0, 0, 1, 1] swaps = [0, 0, 0, 0] harmony = Harmony(n, colors, swaps) self.assertFalse(harmony.has_swaps_left())
def __init__(self, *args, **kwargs): """ constructor initializes and stores a 2 by 2 game of Harmony for use in tests """ super(TestHarmonySmall, self).__init__(*args, **kwargs) self.n = 2 colors = [0, 1, 1, 0] swaps = [0, 1, 0, 1] self.harmony = Harmony(self.n, colors, swaps)
def testSearch_complete(self): """ testSearch_complete tests if an empty array is returned upon an already solved game """ n = 2 colors = [0, 0, 1, 1] swaps = [0, 0, 0, 0] harmony = Harmony(n, colors, swaps) path = harmony.solve() self.assertEqual(path, [])
def testPathfinding_valid_moves_many(self): """ testPathfinding_valid_moves_many verifies that the valid_moves locates all valid moves, many in this case """ n = 2 colors = [1, 1, 0, 0] swaps = [1, 1, 1, 1] harmony = Harmony(n, colors, swaps) index = 0 valid = [2] self.assertEqual(valid, harmony.valid_moves(index))
def parse_measure(self, measure, divisions): """ Parses a measure according to the pitch duration token parsing process. :param measure: a measure dict in the format created by src/processing/conversion/xml_to_json.py :param divisions: the number of divisions a quarter note is split into in this song's MusicXML representation :return: a dict containing a list of groups containing the pitch numbers, durations, and bar positions for each harmony in a measure """ parsed_measure = {"groups": []} tick_idx = 0 for group in measure["groups"]: parsed_group = { "harmony": {}, "pitch_numbers": [], "duration_tags": [], "bar_positions": [] } harmony = Harmony(group["harmony"]) parsed_group["harmony"]["root"] = harmony.get_one_hot_root() parsed_group["harmony"][ "pitch_classes"] = harmony.get_seventh_pitch_classes_binary() dur_ticks_list = [] for note_dict in group["notes"]: # want monophonic, so we'll just take the top note if "chord" in note_dict.keys() or "grace" in note_dict.keys(): continue else: pitch_num, dur_tag, dur_ticks = self.parse_note( note_dict, divisions) parsed_group["pitch_numbers"].append(pitch_num) parsed_group["duration_tags"].append(dur_tag) dur_ticks_list.append(dur_ticks) unnorm_barpos = [ tick_idx + sum(dur_ticks_list[:i]) for i in range(len(dur_ticks_list)) ] bar_positions = [ int((dur_ticks / (4 * divisions)) * 96) for dur_ticks in unnorm_barpos ] parsed_group["bar_positions"] = bar_positions parsed_measure["groups"].append(parsed_group) tick_idx += sum(dur_ticks_list) return parsed_measure
def __init__(self, score): print "making score" self.score = score self.bars = [] print "choosing drones" self.drones = choose_drones() print "initializing harmony options" self.harmony = Harmony(self.drones) print "initializing harmonic rhythm options" self.harmonic_rhythm_factory = harmonic_rhythm.HarmonicRhythm() print "making configs for each bar" self.make_bars() print "making section config groupings" self.group_sections() print "creating staves" self.set_staves() print "adjusting soloist entrances" self.adjust_soloist_entrances() print "filling some staves with rests" self.temp_fill_with_rests() print "making harmonic rhythm" self.make_harmonic_rhythm() print "making drones" self.make_drones() print "choosing harmonies" self.choose_harmonies() print "making bassline" self.make_bassline() print "making soloist" self.make_soloist() print "making accompaniment" self.make_accompaniment() print "making piano right hand" self.make_piano_right_hand() print "adding rehearsal marks" self.add_rehearsal_marks() print "adding dynamics" self.add_dynamics() print "adding double barlines" self.add_double_barlines() print "adding final barline" self.add_final_barlines() print "Done!"
def testConstructor_swapping_points_none(self): """ testConstructor_swapping_points_none tests that Harmony extracts the correct starting points, given that there are none """ n = 2 colors = [0, 1, 1, 0] swaps = [0, 0, 0, 0] harmony = Harmony(n, colors, swaps) swapping_points = set() self.assertEqual(swapping_points, set(harmony.swapping_points))
def parse_measure(self, measure, scale_factor, prev_harmony): """ For a measure, returns a set of ticks grouped by associated harmony in. :param measure: a measure dict in the format created by src/processing/conversion/xml_to_json.py :param scale_factor: the scale factor between XML divisions and midi ticks :param prev_harmony: a reference to the last harmony used in case a measure has none :return: a dict containing a list of groups that contains a harmony and the midi ticks associated with that harmony """ parsed_measure = {"groups": []} total_ticks = 0 for group in measure["groups"]: # Set note value for each tick in the measure group_ticks = [] for note in group["notes"]: if not "duration" in note: print("Skipping grace note...") continue divisions = int(note["duration"]["text"]) num_ticks = int(scale_factor * divisions) index = self.get_note_index(note) for i in range(num_ticks): tick = [0 for _ in range(MIDI_RANGE)] tick[index] = 1 group_ticks.append(tick) total_ticks += len(group_ticks) if not group["harmony"]: parsed_measure["groups"].append({ "harmony": prev_harmony, "ticks": group_ticks }) else: harmony = Harmony(group["harmony"]) harmony_dict = { "root": harmony.get_one_hot_root(), "pitch_classes": harmony.get_seventh_pitch_classes_binary() } prev_harmony = harmony_dict parsed_measure["groups"].append({ "harmony": harmony_dict, "ticks": group_ticks }) # Mitigate ticks for chords that occur mid-note for i, group in enumerate(parsed_measure["groups"]): try: if not group["ticks"]: # Handle the case of no harmony at the start of the bar if not 0 in measure["harmonies_start"]: measure["harmonies_start"].insert(0, 0) correct_len_of_prev_harmony = int( scale_factor * (measure["harmonies_start"][i] - measure["harmonies_start"][i - 1])) group["ticks"].extend(parsed_measure["groups"][ i - 1]["ticks"][correct_len_of_prev_harmony:]) parsed_measure["groups"][ i - 1]["ticks"] = parsed_measure["groups"][ i - 1]["ticks"][:correct_len_of_prev_harmony] except: import pdb pdb.set_trace() raise ( "No ticks in the first group of a measure! (in fix for chords mid-note)" ) if total_ticks > TICKS_PER_BEAT * 4: raise Exception("OH NO BRO. YOUR TICKS ARE TOO MUCH YO") i = 0 while total_ticks < TICKS_PER_BEAT * 4: group = parsed_measure["groups"][i] spacer_tick = [0 for _ in range(MIDI_RANGE)] spacer_tick[0] = 1 # fill with rests group["ticks"].append(spacer_tick) i = (i + 1) % len(parsed_measure["groups"]) total_ticks += 1 parsed_measure["num_ticks"] = total_ticks return parsed_measure, prev_harmony
class TestHarmonySmall(unittest.TestCase): def __init__(self, *args, **kwargs): """ constructor initializes and stores a 2 by 2 game of Harmony for use in tests """ super(TestHarmonySmall, self).__init__(*args, **kwargs) self.n = 2 colors = [0, 1, 1, 0] swaps = [0, 1, 0, 1] self.harmony = Harmony(self.n, colors, swaps) ################################ # Testing Harmony constructor ################################ def testConstructor_n(self): """ testConstructor_n tests whether Harmony creates an object knowing its correct size """ self.assertEqual(self.n, self.harmony.n) def testConstructor_grid_size(self): """ testConstructor_grid_size tests whether Harmony initializes colors and swaps of the right size """ size = self.n**2 self.assertEqual(size, len(self.harmony.colors)) self.assertEqual(size, len(self.harmony.swaps)) def testConstructor_swaps_left(self): """ testConstructor_swaps_left tests whether Harmony records the correct number of swaps available upon creation """ self.assertEqual(2, self.harmony.swaps_left) def testConstructor_swapping_points_none(self): """ testConstructor_swapping_points_none tests that Harmony extracts the correct starting points, given that there are none """ n = 2 colors = [0, 1, 1, 0] swaps = [0, 0, 0, 0] harmony = Harmony(n, colors, swaps) swapping_points = set() self.assertEqual(swapping_points, set(harmony.swapping_points)) def testConstructor_swapping_points_exist(self): """ testConstructor_swapping_points_exist tests that Harmony extracts the correct starting points, given that they exist """ swapping_points = set([1, 3]) self.assertEqual(swapping_points, set(self.harmony.swapping_points)) ################################ # Testing index manipulations ################################ def testIndexManip_list_to_grid_index_1(self): """ testIndexManip_list_to_grid_index_1 tests method list_to_grid_index to ensure that indices are being property translated from list to grid, for the corner of the grid. """ index = 3 grid_coords = (1, 1) self.assertEqual(grid_coords, self.harmony.list_to_grid_index(index)) def testIndexManip_list_to_grid_index_2(self): """ testIndexManip_list_to_grid_index_2 tests method list_to_grid_index to ensure that indices are being property translated from list to grid, for an intermediate element in the grid """ index = 2 grid_coords = (1, 0) self.assertEqual(grid_coords, self.harmony.list_to_grid_index(index)) def testIndexManip_grid_to_list_index_1(self): """ testIndexManip_grid_to_list_index_1 tests method grid_to_list_index to ensure that indices are being property translated from grid to list, for the corner of the grid """ index = 3 g = (1, 1) self.assertEqual(index, self.harmony.grid_to_list_index(g)) def testIndexManip_grid_to_list_index_2(self): """ testIndexManip_grid_to_list_index_2 tests method grid_to_list_index to ensure that indices are being property translated from grid to list, for an intermediate element in the grid """ index = 2 g = (1, 0) self.assertEqual(index, self.harmony.grid_to_list_index(g)) def testIndexManip_valid_index_origin(self): """ testIndexManip_valid_index_origin tests method valid_index, given the origin """ index = 0 self.assertTrue(self.harmony.valid_index(index)) def testIndexManip_valid_index_arbitrary(self): """ testIndexManip_valid_index_arbitrary tests method valid_index, given an arbitrary, non-boundary, valid index """ index = randint(1, 2) self.assertTrue(self.harmony.valid_index(index)) def testIndexManip_valid_index_last(self): """ testIndexManip_valid_index_last tests method valid_index, given the last valid index """ index = 3 self.assertTrue(self.harmony.valid_index(index)) def testIndexManip_invalid_index_before(self): """ testIndexManip_invalid_index_before tests method valid_index, given a negative, invalid index """ index = -1 self.assertFalse(self.harmony.valid_index(index)) def testIndexManip_invalid_index_after(self): """ testIndexManip_invalid_index_before tests method valid_index, given a negative, invalid index """ index = 5 self.assertFalse(self.harmony.valid_index(index)) def testIndexManip_indices_in_line_horizontal(self): """ testIndexManip_indices_in_line_horizontal tests whether two horizontal colinear indices are reported as in line with each other """ index1 = 0 index2 = 1 self.assertTrue(self.harmony.indices_in_line(index1, index2)) def testIndexManip_indices_in_line_vertical(self): """ testIndexManip_indices_in_line_vertical tests whether two vertical colinear indices are reported as in line with each other """ index1 = 0 index2 = 2 self.assertTrue(self.harmony.indices_in_line(index1, index2)) def testIndexManip_indices_in_line_nonlinear(self): """ testIndexManip_indices_in_line_nonlinear tests whether two non-linear indices are reported as in not line with each other """ index1 = 0 index2 = 3 self.assertFalse(self.harmony.indices_in_line(index1, index2)) ################################ # Testing basic getter / setter ################################ def testBasic_get(self): """ testBasic_get tests whether Harmony can retrieve correct values, given a list index """ index = 3 value = (0, 1) self.assertEqual(value, self.harmony.get(index)) def testBasic_set_value(self): """ testBasic_get tests whether Harmony can set correct values given a list index and value """ index = 3 old_color, old_swaps = self.harmony.get(index) color, swaps = (1, 1) # should not be equal before setting self.assertNotEqual((color, swaps), self.harmony.get(index)) self.harmony.set_value(index, color, swaps) # should be equal after setting self.assertEqual((color, swaps), self.harmony.get(index)) # restore the original grid self.harmony.set_value(index, old_color, old_swaps) ################################ # Testing game logic ################################ def testLogic_valid_swap(self): """ testLogic_valid_swap tests whether a valid swap is reported as valid """ index1 = 1 index2 = 3 self.assertTrue(self.harmony.valid_swap(index1, index2)) def testLogic_invalid_swap(self): """ testLogic_invalid_swap tests whether a invalid swap is reported as not valid """ index1 = 0 index2 = 2 self.assertFalse(self.harmony.valid_swap(index1, index2)) def testLogic_has_swaps_left_true(self): """ testLogic_has_swaps_left_true tests whether swaps_left, given a positive number of remaining total swaps, returns True """ self.assertTrue(self.harmony.has_swaps_left()) def testLogic_has_waps_left_none(self): """ testLogic_has_waps_left_none tests whether swaps_left, given no remaining total swaps, returns False """ n = 2 colors = [0, 0, 1, 1] swaps = [0, 0, 0, 0] harmony = Harmony(n, colors, swaps) self.assertFalse(harmony.has_swaps_left()) def testLogic_game_unsolved(self): """ testLogic_game_unsolved tests whether an unsolved game is reported as unsolved """ self.assertFalse(self.harmony.game_solved()) def testLogic_game_solved(self): """ testLogic_game_solved tests whether a solved game is reported as solved """ n = 2 colors = [0, 0, 1, 1] swaps = [0, 0, 0, 0] harmony = Harmony(n, colors, swaps) self.assertTrue(harmony.game_solved()) ################################ # Testing pathfinding helpers ################################ def testPathfinding_swap(self): """ testPathfinding_swap tests whether swap correctly swaps two colors, decreases the swaps available for each, and decreases the total number of swaps left """ index1 = 1 index2 = 3 color1, swaps1 = self.harmony.get(index1) color2, swaps2 = self.harmony.get(index2) old_total = self.harmony.swaps_left # swap here, and then observe new color / swaps self.harmony.swap(index1, index2) color1_new, swaps1_new = self.harmony.get(index1) color2_new, swaps2_new = self.harmony.get(index2) new_total = self.harmony.swaps_left # check color swapping self.assertEqual(color1, color2_new) self.assertEqual(color2, color1_new) # check swaps decrease self.assertEqual(swaps1_new, swaps1 - 1) self.assertEqual(swaps2_new, swaps2 - 1) # check total swaps decrease self.assertEqual(new_total, old_total - 2) def testPathfinding_unswap(self): """ testPathfinding_unswap tests whether unswap correctly reverts two colors, re-increases the swaps available, and re-increases the total number of swaps left """ index1 = 1 index2 = 3 color1, swaps1 = self.harmony.get(index1) color2, swaps2 = self.harmony.get(index2) old_total = self.harmony.swaps_left # swap here, and then observe new color / swaps self.harmony.unswap(index1, index2) color1_new, swaps1_new = self.harmony.get(index1) color2_new, swaps2_new = self.harmony.get(index2) new_total = self.harmony.swaps_left # check color swapping self.assertEqual(color1, color2_new) self.assertEqual(color2, color1_new) # check swaps decrease self.assertEqual(swaps1_new, swaps1 + 1) self.assertEqual(swaps2_new, swaps2 + 1) # check total swaps decrease self.assertEqual(new_total, old_total + 2) def testPathfinding_valid_moves_none(self): """ testPathfinding_valid_moves_none verifies that the valid_moves locates no valid moves if there are none """ index = 0 self.assertEqual([], self.harmony.valid_moves(index)) def testPathfinding_valid_moves_one(self): """ testPathfinding_valid_moves_one verifies that the valid_moves locates all valid moves, one in this case """ index = 1 self.assertEqual([3], self.harmony.valid_moves(index)) def testPathfinding_valid_moves_many(self): """ testPathfinding_valid_moves_many verifies that the valid_moves locates all valid moves, many in this case """ n = 2 colors = [1, 1, 0, 0] swaps = [1, 1, 1, 1] harmony = Harmony(n, colors, swaps) index = 0 valid = [2] self.assertEqual(valid, harmony.valid_moves(index)) def testPathfinding_get_swappable_none(self): """ testPathfinding_get_swappable_none tests that get_swappable returns an empty list given no swaps available """ n = 2 colors = [0, 0, 1, 1] swaps = [0, 0, 0, 0] harmony = Harmony(n, colors, swaps) self.assertEqual(harmony.get_swappable(), []) def testPathfinding_get_swappable_many(self): """ testPathfinding_get_swappable_many tests that get_swappable returns a correct list of still swappable states, if there are some """ swappable = set([1, 3]) self.assertEqual(swappable, set(self.harmony.get_swappable())) ################################ # Testing search algorithm ################################ def testSearch_none(self): """ testSearch_none tests if None is returned upon an impossible situation """ n = 2 colors = [1, 1, 0, 0] swaps = [0, 0, 0, 0] harmony = Harmony(n, colors, swaps) path = harmony.solve() assert path is None def testSearch_complete(self): """ testSearch_complete tests if an empty array is returned upon an already solved game """ n = 2 colors = [0, 0, 1, 1] swaps = [0, 0, 0, 0] harmony = Harmony(n, colors, swaps) path = harmony.solve() self.assertEqual(path, []) def testSearch_path_small(self): """ testSearch_path_small tests if a valid path is found, given that it exists and the game does not start solved This path has length 1. """ path = self.harmony.solve() actual_length = 1 self.assertEqual(len(path), actual_length)
class Form(object): def __init__(self, score): print "making score" self.score = score self.bars = [] print "choosing drones" self.drones = choose_drones() print "initializing harmony options" self.harmony = Harmony(self.drones) print "initializing harmonic rhythm options" self.harmonic_rhythm_factory = harmonic_rhythm.HarmonicRhythm() print "making configs for each bar" self.make_bars() print "making section config groupings" self.group_sections() print "creating staves" self.set_staves() print "adjusting soloist entrances" self.adjust_soloist_entrances() print "filling some staves with rests" self.temp_fill_with_rests() print "making harmonic rhythm" self.make_harmonic_rhythm() print "making drones" self.make_drones() print "choosing harmonies" self.choose_harmonies() print "making bassline" self.make_bassline() print "making soloist" self.make_soloist() print "making accompaniment" self.make_accompaniment() print "making piano right hand" self.make_piano_right_hand() print "adding rehearsal marks" self.add_rehearsal_marks() print "adding dynamics" self.add_dynamics() print "adding double barlines" self.add_double_barlines() print "adding final barline" self.add_final_barlines() print "Done!" def make_bars(self): bar_index = 0 for movement_number in Conf.movements: for drone in [True, None]: if drone: drone = self.drones[movement_number] for volume in ["q1", "l1", "q2", "l2"]: n_bars = weighted_choice(Conf.n_bars_options[movement_number], Conf.n_bars_weights[movement_number]) for bar_n in range(n_bars): bar = dict( movement_number=movement_number, movement_name=Conf.movement_names[movement_number], drone=drone, volume=volume, bar_index=bar_index, soloist=Conf.soloist[movement_number], accompanists=Conf.accompanists[movement_number] if volume.startswith("l") else (), dynamics=Conf.dynamics[movement_number][volume[0]], ) bar["dynamics"]["drone"] = Conf.dynamics[movement_number]["drone"] self.bars.append(bar) bar_index += 1 def group_section(self, bars, attr): """Group a list of bar configs by a config attribute""" return [list(group) for key, group in groupby(bars, lambda x: x[attr])] def group_sections(self): self.movement_sections = self.group_section(self.bars, "movement_number") self.drone_sections = self.group_section(self.bars, "drone") self.volume_sections = self.group_section(self.bars, "volume") self.movement_volume_sections = [self.group_section(m, "volume") for m in self.movement_sections] self.harmonic_rhythm = [] self.raw_harmonic_rhythm = [] self.harmonic_rhythm_drones = [] self.harmonies = [] self.unused_harmonies = [] def set_staves(self): self.staves = [] for inst in self.score: if inst.name == "Piano": for staff in inst: self.staves.append(staff) else: self.staves.append(inst) def add_rehearsal_marks(self): for section in self.volume_sections[4::4]: first_bar_index = section[0]["bar_index"] for staff in self.score: if staff.name == "Piano": add_rehearsal_mark(staff[0][first_bar_index]) else: add_rehearsal_mark(staff[first_bar_index]) def add_piano_dynamics(self): piano = self.score["Piano"][1] for section in self.volume_sections: first_bar_conf = section[0] bar_index = first_bar_conf["bar_index"] piano_first_note = piano[bar_index][0] add_dynamic(piano_first_note, first_bar_conf["dynamics"]["piano"]) def add_soloist_dynamics(self): dynamic = None previous_soloist = None for bar in self.bars: i = bar["bar_index"] soloist_name = bar["soloist"] if soloist_name: dyn = bar["dynamics"]["soloist"] if dyn != dynamic or soloist_name != previous_soloist: dynamic = dyn previous_soloist = soloist_name soloist = self.score[soloist_name] first_note = soloist[i][0] add_dynamic(first_note, dynamic) def add_accompanists_dynamics(self): for section in self.volume_sections: first_bar_conf = section[0] bar_index = first_bar_conf["bar_index"] for acc_name in first_bar_conf["accompanists"]: acc = self.score[acc_name] first_note = acc[bar_index][0] add_dynamic(first_note, first_bar_conf["dynamics"]["accompanists"]) def add_synth_dynamics(self): synth = self.score["Synthesizer"] # TODO use the section where the drone is actually sounding, not the square section for section in self.drone_sections: first_bar_conf = section[0] bar_index = first_bar_conf["bar_index"] synth_first_note = synth[bar_index][0] if not isinstance(synth_first_note, Rest): add_dynamic(synth_first_note, first_bar_conf["dynamics"]["drone"]) def add_dynamics(self): self.add_piano_dynamics() self.add_soloist_dynamics() self.add_accompanists_dynamics() self.add_synth_dynamics() def add_double_barlines(self): for section in self.volume_sections[:-1]: last_bar_conf = section[-1] bar_index = last_bar_conf["bar_index"] for staff in self.staves: add_double_barline(staff[bar_index][-1]) def add_final_barlines(self): for staff in self.staves: add_final_barline(staff) def temp_fill_with_rests(self): no = ["Synthesizer", "Piano lower", "Piano upper"] staves = [s for s in self.staves if s.name not in no] for staff in staves: for bar in self.bars: staff.append(get_rest_bar()) def make_harmonic_rhythm(self): for phrase in self.volume_sections: raw, bars = self.harmonic_rhythm_factory.choose(phrase) assert len(bars) == len(phrase) self.raw_harmonic_rhythm.append(raw) self.harmonic_rhythm.append(bars) def choose_harmonies(self): for drone, rhythm in zip(self.harmonic_rhythm_drones, self.raw_harmonic_rhythm): pitches = [self.harmony.choose(d) for d in drone] self.harmonies.append(pitches) self.unused_harmonies.append([p[:] for p in pitches[:]]) def make_piano_right_hand(self): piano_upper = self.score["Piano"][0] previous = [0, 4, 7, 10] for harmonies, unused, rhythm in zip(self.harmonies, self.unused_harmonies, self.raw_harmonic_rhythm): chords = [] for h, unused_h in zip(harmonies, unused): chord = next_piano_right_hand_chord(previous, h, unused_h) previous = chord[:] chords.append(chord) # [unused_h.remove(pitch % 12) for pitch in chord] bars = parse_rhythm(rhythm, chords) piano_upper.extend(bars) def make_bassline(self): previous = -8 piano_lower = self.score["Piano"][1] for harmonies, unused, rhythm in zip(self.harmonies, self.unused_harmonies, self.raw_harmonic_rhythm): pitches = [] for h, unused_h in zip(harmonies, unused): p = next_piano_bass_note(previous, h) previous = p pitches.append(p) unused_h.remove(p % 12) bars = parse_rhythm(rhythm, pitches) piano_lower.extend(bars) def adjust_soloist_entrances(self): movements = self.movement_sections for i, movement in enumerate(movements): next_i = (i + 1) % len(movements) next_soloist = movements[next_i][0]["soloist"] for bar in movements[next_i]: if bar["accompanists"] != (): next_accompanists = bar["accompanists"] break last_section = self.movement_volume_sections[i][-1] for bar_config in last_section: bar_config["accompanists"] = next_accompanists # Pick soloist exit and new soloist entrance # No one should ever write code like this last_sections = self.movement_volume_sections[i][-3:] last_sections[0] = last_sections[0][len(last_sections[0]) / 2 :] last_sections[-1] = last_sections[-1][: len(last_sections[-1]) / 2] lengths = [len(section) for section in last_sections] flat = [] for section in last_sections: for bar in section: flat.append(bar) exit_bar_index = random.choice(range(sum(lengths[:2]))) for bar in flat[exit_bar_index:]: bar["soloist"] = None for bar in last_section[len(last_section) / 2 :]: bar["soloist"] = None possible_entrance_bars = flat[exit_bar_index + 1 :] entrance_bar_index = random.choice(range(len(possible_entrance_bars))) new_solist_bars = possible_entrance_bars[entrance_bar_index:] for bar in new_solist_bars: bar["soloist"] = next_soloist for bar in last_section[len(last_section) / 2 :]: bar["soloist"] = next_soloist def make_accompaniment(self): previous_a = None previous_b = None for harmonies, unused, rhythm, section_configs in zip( self.harmonies, self.unused_harmonies, self.raw_harmonic_rhythm, self.volume_sections ): bar_config = section_configs[0] bar_index = bar_config["bar_index"] accompanists = bar_config["accompanists"] if accompanists: rhythm, harmonies, unused = add_accompaniment_notes(rhythm, harmonies, unused) a_name, b_name = accompanists a = self.score[a_name] b = self.score[b_name] pitches_a = [] pitches_b = [] for h, unused_h in zip(harmonies, unused): pitch_a, pitch_b = next_accompaniment_notes( a_name, b_name, previous_a, previous_b, h, unused_h, bar_config["movement_number"] ) previous_a, previous_b = pitch_a, pitch_b pitches_a.append(pitch_a) pitches_b.append(pitch_b) bars_a = parse_rhythm(rhythm, pitches_a) bars_b = parse_rhythm(rhythm, pitches_b) a[bar_index : bar_index + len(bars_a)] = bars_a b[bar_index : bar_index + len(bars_b)] = bars_b else: previous_a = None previous_b = None def make_soloist(self): soloists = [sec[0]["soloist"] for sec in self.volume_sections] # Is the soloist entering, playing through, exiting, or resting? actions = solo.get_actions(soloists) previous = None previous_soloist_name = None for i, section_configs in enumerate(self.volume_sections): print "\tVolume Section #{}".format(i) soloist_name = soloists[i] if soloist_name != previous_soloist_name: previous = None previous_soloist_name = soloist_name action = actions[i] bar_index = section_configs[0]["bar_index"] movement_number = section_configs[0]["movement_number"] if soloist_name: soloist = self.score[soloist_name] harmonies = self.harmonies[i] unused = self.unused_harmonies[i] rhythm = self.raw_harmonic_rhythm[i] rhythm, harmonies, unused = solo.add_notes(rhythm, harmonies, unused, soloist_name, movement_number) len_rhythm = len(rhythm) rests = [] if action == "enter": enter_index = 0 if len_rhythm == 2: enter_index = 1 if len_rhythm > 2: opts = range(1, len_rhythm - 1) enter_index = random.choice(opts) for _ in harmonies[:enter_index]: rests.append("r") harmonies, unused = harmonies[enter_index:], unused[enter_index:] if action == "exit": exit_index = 0 if len_rhythm == 2: exit_index = 1 if len_rhythm > 2: opts = range(1, len_rhythm - 1) exit_index = random.choice(opts) for _ in harmonies[exit_index:]: rests.append("r") harmonies, unused = harmonies[:exit_index], unused[:exit_index] pitches = [] for h, unused_h in zip(harmonies, unused): pitch = solo.next_soloist_note(soloist_name, previous, h, movement_number, i) if isinstance(pitch, int): previous = pitch pc = pitch % 12 if pc in unused_h: unused_h.remove(pc) elif isinstance(pitch, list): previous = pitch[0] pitches.append(pitch) if action == "enter": pitches = rests + pitches elif action == "exit": pitches = pitches + rests rhythm, pitches = solo.join_some_notes(rhythm, pitches) bars = parse_rhythm(rhythm, pitches) soloist[bar_index : bar_index + len(bars)] = bars if action == "enter" or action == "exit": notes = [] for bar in bars: for note in bar: if not is_rest(note): notes.append(note) if action == "enter": crescendo(notes) elif action == "exit": crescendo(notes, decrescendo=True) else: previous = None def make_drones(self): synth = self.score["Synthesizer"] # Drone 1 bars = [get_one_note_bar(self.drones[0]) for _ in self.drone_sections[0]] tie(bars) synth.extend(bars) for rhythm in self.raw_harmonic_rhythm[:4]: self.harmonic_rhythm_drones.append([self.drones[0] for r in rhythm]) # Rest 1 rest_bars = [get_rest_bar() for _ in self.drone_sections[1]] synth.extend(rest_bars) for rhythm in self.raw_harmonic_rhythm[4:8]: self.harmonic_rhythm_drones.append([None for r in rhythm]) # Drone 2 bars = [get_one_note_bar(self.drones[1]) for _ in self.drone_sections[2]] tie(bars) synth.extend(bars) for rhythm in self.raw_harmonic_rhythm[8:12]: self.harmonic_rhythm_drones.append([self.drones[1] for r in rhythm]) ####################################### #### Rest 2 and Drone 3 A entrance #### ####################################### # First two volume sections are resting rest_bars = [] for section in self.volume_sections[12:14]: rest_bars.extend([get_rest_bar() for _ in section]) for rhythm in self.raw_harmonic_rhythm[12:14]: self.harmonic_rhythm_drones.append([None for r in rhythm]) # The drone comes in at a random point in the third volume section drone_3a = self.drones[2][0] entrance_section = self.raw_harmonic_rhythm[14] start = random.choice(range(len(entrance_section) - 1)) rests = ["r" for duration in entrance_section[:start]] drones = [drone_3a for duration in entrance_section[start:]] pitches = rests + drones entrance_bars = parse_rhythm(entrance_section, pitches=pitches) rhythm = [] for pitch in pitches: if pitch == "r": rhythm.append(None) else: rhythm.append(drone_3a) self.harmonic_rhythm_drones.append(rhythm) # Last volume section is droning drone_bars = [get_one_note_bar(drone_3a) for _ in self.volume_sections[15]] self.harmonic_rhythm_drones.append([drone_3a for dur in self.raw_harmonic_rhythm[15]]) synth.extend(rest_bars + entrance_bars + drone_bars) to_tie = entrance_bars + drone_bars # TODO ties ####################### #### Drone 3 A & B #### ####################### # First volume section is drone 3 A bars_1 = [get_one_note_bar(drone_3a) for _ in self.volume_sections[16]] self.harmonic_rhythm_drones.append([drone_3a for dur in self.raw_harmonic_rhythm[16]]) # Second volume section drone 3 B comes in entrance_section = self.raw_harmonic_rhythm[17] start = random.choice(range(1, len(entrance_section))) a = [drone_3a for duration in entrance_section[:start]] both = [self.drones[2] for duration in entrance_section[start:]] pitches = a + both bars_2 = parse_rhythm(entrance_section, pitches=pitches) self.harmonic_rhythm_drones.append(pitches) # Third and Fourth volume sections both drones bars_3_4 = [] for section in self.volume_sections[18:20]: bars_3_4.extend([get_one_note_bar(self.drones[2]) for _ in section]) for rhythm in self.raw_harmonic_rhythm[18:20]: self.harmonic_rhythm_drones.append([self.drones[2] for dur in rhythm]) # Fifth volume section drone 3 A exits drone_3b = self.drones[2][1] exit_section = self.raw_harmonic_rhythm[20] start = random.choice(range(1, len(exit_section))) both = [self.drones[2] for duration in exit_section[:start]] b = [drone_3b for duration in exit_section[start:]] pitches = both + b bars_5 = parse_rhythm(exit_section, pitches=pitches) self.harmonic_rhythm_drones.append(pitches) # Sixth volume section is drone 3 B bars_6 = [get_one_note_bar(drone_3b) for _ in self.volume_sections[21]] self.harmonic_rhythm_drones.append([drone_3b for dur in self.raw_harmonic_rhythm[21]]) # Seventh volume section drone 3 B exits exit_section = self.raw_harmonic_rhythm[22] start = random.choice(range(1, len(exit_section))) b = [drone_3b for duration in exit_section[:start]] rest = ["r" for duration in exit_section[start:]] pitches = b + rest bars_7 = parse_rhythm(exit_section, pitches=pitches) rhythm_drones = [] for p in pitches: if p == "r": rhythm_drones.append(None) else: rhythm_drones.append(p) self.harmonic_rhythm_drones.append(rhythm_drones) # Eighth volume section is resting bars_8 = [get_rest_bar() for _ in self.volume_sections[23]] self.harmonic_rhythm_drones.append([None for dur in self.raw_harmonic_rhythm[23]]) bars = bars_1 + bars_2 + bars_3_4 + bars_5 + bars_6 + bars_7 + bars_8 synth.extend(bars) to_tie += bars tie(to_tie) # Drone 4 bars = [get_one_note_bar(self.drones[3]) for _ in self.drone_sections[6]] tie(bars) synth.extend(bars) for rhythm in self.raw_harmonic_rhythm[24:28]: self.harmonic_rhythm_drones.append([self.drones[3] for r in rhythm]) # Rest 4 bars = [get_rest_bar() for _ in self.drone_sections[7]] synth.extend(bars) for rhythm in self.raw_harmonic_rhythm[28:]: self.harmonic_rhythm_drones.append([None for r in rhythm])