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 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 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 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