def values(self, p_map, v_note): if v_note != self.actor_note: raise Exception('v_note {0} not in ScalarConstraint actors.'.format(v_note.note)) policy_context = p_map[self.actor_note].policy_context tones = list(policy_context.harmonic_context.tonality.annotation) tones = tones[:-1] # remove final note (same as first) if len(self.scalar_roles) != 0: tones = [tones[i] for i in self.scalar_roles] if p_map[v_note].note is not None: tone = p_map[v_note].note.diatonic_pitch.diatonic_tone return OrderedSet([self.actor_note]) if tone in tones else None pitch_range = policy_context.pitch_range start_partition = max(ChromaticScale.index_to_location(pitch_range.start_index)[0] - 1, 0) end_partition = min(ChromaticScale.index_to_location(pitch_range.end_index)[0] + 1, ChromaticScale.CHROMATIC_END[0]) valid_set = OrderedSet() for tone in tones: for j in range(start_partition, end_partition + 1): pitch = DiatonicPitch(j, tone) if pitch_range.is_pitch_inbounds(pitch): note = Note(pitch, self.actor_note.base_duration, self.actor_note.num_dots) valid_set.add(note) return valid_set
def values(self, p_map, v_target_note): if v_target_note == self.note_two: up_down = self.up_down v_source_note = self.note_one elif v_target_note == self.note_one: up_down = not self.up_down v_source_note = self.note_two else: raise Exception( 'v_note specification does not match any v_note in constraints.' ) if p_map[v_target_note].note is not None: return OrderedSet([p_map[v_target_note].note]) arg_contextual_note = p_map[v_source_note] target_contextual_note = p_map[v_target_note] if arg_contextual_note.note is None: pitches = p_map.all_tonal_pitches(v_target_note) result = OrderedSet() for p in pitches: result.add( Note(p, v_target_note.base_duration, v_target_note.num_dots)) return result return self.compute_result(arg_contextual_note, target_contextual_note, up_down)
def values(self, p_map, v_note): if v_note != self.actor_note: raise Exception("Illegal v_note {0} for constraints".format(v_note)) if p_map[v_note].note is not None: if p_map[v_note].note.diatonic_pitch.diatonic_tone == self.tone: return {p_map[v_note].note} raise Exception('Fixed Tone Policy Violated has {0} should be {1}'.format( p_map[v_note].note.diatonic_pitch.diatonic_tone, self.tone)) contextual_note = p_map[self.actor_note] policy_context = contextual_note.policy_context pitch_range = contextual_note.policy_context.pitch_range start_partition = max(ChromaticScale.index_to_location(pitch_range.start_index)[0] - 1, 0) end_partition = min(ChromaticScale.index_to_location(pitch_range.end_index)[0] + 1, ChromaticScale.CHROMATIC_END[0]) # Try to find that tone in target's tonality/scale. tone = self.tone for t_str in self.tone.enharmonics(): t = DiatonicToneCache.get_tone(t_str) for scale_tone in PitchScale(policy_context.harmonic_context.tonality, policy_context.pitch_range).tone_scale: if scale_tone == t: tone = t break valid_set = OrderedSet() for i in range(start_partition, end_partition + 1): pitch = DiatonicPitch(i, tone) if pitch_range.is_pitch_inbounds(pitch): note = Note(pitch, self.actor_note.base_duration, self.actor_note.num_dots) valid_set.add(note) return valid_set
def values(self, p_map, v_note): if v_note != self.actor_note: raise Exception("Illegal v_note {0} for constraint".format(v_note)) if p_map[v_note].note is not None: if p_map[v_note].note.diatonic_pitch.chromatic_distance in (p.chromatic_distance for p in self.pitches): return OrderedSet[p_map[v_note].note] raise Exception('Fixed Pitch Select Set Constraint Violated has {0} not in pitch set'.format( p_map[v_note].note.diatonic_pitch)) # Return all pitches (self.pitches) except though, for each # select a representation closest to the target tonality, if it exists. tonality = p_map[self.actor_note].policy_context.harmonic_context.tonality result_pitches = [] for pitch in self.pitches: found_pitch = pitch for p in pitch.enharmonics(): if p.diatonic_tone in tonality.annotation: found_pitch = p break result_pitches.append(found_pitch) result = OrderedSet() for p in result_pitches: result.add(Note(p, self.actor_note.base_duration, self.actor_note.num_dots)) return result
def test_copy(self): s = OrderedSet() for i in range(1, 10): to = TestObject(i) s.add(to) s1 = s.copy() assert id(s1) != id(s) objects = [t.ord_value() for t in s1] assert objects == [1, 2, 3, 4, 5, 6, 7, 8, 9]
def compute_full_result(p_map, v_note, e_set): pitches = p_map.all_tonal_pitches(v_note) e_chrm_list = [n.diatonic_pitch.chromatic_distance for n in e_set] del_set = OrderedSet() for p in pitches: if p.chromatic_distance in e_chrm_list: del_set.add(p) pitches = [p for p in pitches if p not in del_set] return OrderedSet([Note(p, v_note.base_duration, v_note.num_dots) for p in pitches])
def compute_result(self, arg_contextual_note, target_contextual_note, down_steps, up_steps): arg_pitch = arg_contextual_note.note.diatonic_pitch pitches = PitchScale.compute_tonal_pitch_range( target_contextual_note.policy_context.harmonic_context.tonality, arg_pitch, down_steps, up_steps) result = OrderedSet() for pitch in pitches: result.add( Note(pitch, self.note_two.base_duration, self.note_two.num_dots)) return result
def test_union(self): s1 = OrderedSet() for i in range(1, 10): s1.add(TestObject(i)) s2 = OrderedSet() for i in range(100, 110): to = TestObject(i) s2.add(to) s = s1.union(s2) objects = [t.ord_value() for t in s] assert objects == [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109 ]
def values(self, p_map, v_note): """ Compute possible values for v_note's target. :param p_map: :param v_note: :return: Note: Here is why the intervals are reversed for solving for note_one: Suppose x --> [x-a, x + b]. Then for some value y, for t with y-b<=t<=y+a, we have t -->[t-a, t+b], but from the inequalities, t-a<=y<t+b - so the reverse map is [y-b, y+a] <-- y, which is exactly what happens below. """ if v_note == self.note_two: source = self.note_one target = self.note_two up_intvl = self.up_interval down_intvl = self.down_interval elif v_note == self.note_one: source = self.note_two target = self.note_one up_intvl = self.down_interval down_intvl = self.up_interval else: raise Exception( 'v_note specification does not match any v_note in constraints.' ) if p_map[target].note is not None: return OrderedSet([p_map[target].note]) arg_contextual_note = p_map[source] target_contextual_note = p_map[target] if arg_contextual_note.note is None: pitches = p_map.all_tonal_pitches(v_note) result = OrderedSet() for p in pitches: result.add(Note(p, v_note.base_duration, v_note.num_dots)) return result # return {Note(p, v_note.base_duration, v_note.num_dots) for p in pitches} return self.compute_result(arg_contextual_note, target_contextual_note, up_intvl, down_intvl)
def values(self, p_map, v_note): assigned = p_map.assigned_actors(self) unassigned = p_map.unassigned_actors(self) if v_note in unassigned: tonality = p_map[v_note].policy_context.harmonic_context.tonality pitches = PitchScale.compute_tonal_pitches(tonality, self.pitch_range) answer = OrderedSet() for p in pitches: answer.add(Note(p, v_note.base_duration, v_note.num_dots)) return answer # return {Note(p, v_note.base_duration, v_note.num_dots) for p in pitches} if v_note in assigned: return OrderedSet([p_map[v_note].note]) raise Exception( '{0} is not in actor list for pitch range constraints.'.format( v_note.note))
def test_remove(self): s = OrderedSet() fone = None fmid = None flast = None for i in range(1, 10): item = TestObject(i) if i == 1: fone = item elif i == 6: fmid = item elif i == 9: flast = item s.add(item) s.remove(fmid) s.remove(flast) s.remove(fone) objects = [t.ord_value() for t in s] assert objects == [2, 3, 4, 5, 7, 8]
def values(self, pdi, note): position = pdi.correct_position( note.get_absolute_position()) # position should be adjusted ts = pdi.ts_event_sequence.floor_event(position).object beat_position = TimeConversion(pdi.tempo_event_sequence, pdi.ts_event_sequence, Position(pdi.line_duration())).\ position_to_bp(position) num_beats = ts.beats_per_measure if beat_position.beat_fraction > 0 else ts.beats_per_measure - 1 beat_index = (beat_position.beat + 1) % ts.beats_per_measure delta_t = ts.beat_duration if beat_position.beat_fraction == 0 else \ ts.beat_duration * (1 - beat_position.beat_fraction) deltas = OrderedSet() for i in range(0, num_beats): if (self.beat_ids is not None and beat_index in self.beat_ids) or \ (self.beat_type is not None and self.beat_type == ts.beat_type(beat_index)): deltas.add(delta_t) delta_t += ts.beat_duration beat_index = (beat_index + 1) % ts.beats_per_measure return deltas
def values(self, p_map, v_note): """ Return a set of possible pitches that v_note can take that must be in p_map target's chord. :param p_map: :param v_note: :return: """ if v_note != self.actor_note: raise Exception( 'v_note {0} not in ChordalToneConstraint actors.'.format( v_note.note)) policy_context = p_map[self.actor_note].policy_context tones = policy_context.harmonic_context.chord.tones if p_map[v_note].note is not None: note = p_map[v_note].note if note.diatonic_pitch.diatonic_tone in tones: return {note} raise Exception( 'Chordal Pitch Policy Violated has {0} should be member of chord {1}' .format(p_map[v_note].note.diatonic_pitch, policy_context.harmonic_context.chord)) pitch_range = policy_context.pitch_range start_partition = max( ChromaticScale.index_to_location(pitch_range.start_index)[0] - 1, 0) end_partition = min( ChromaticScale.index_to_location(pitch_range.end_index)[0] + 1, ChromaticScale.CHROMATIC_END[0]) valid_set = OrderedSet() for tone in tones: for i in range(start_partition, end_partition + 1): pitch = DiatonicPitch(i, tone[0]) if pitch_range.is_pitch_inbounds(str(pitch)): note = Note(pitch, self.actor_note.base_duration, self.actor_note.num_dots) valid_set.add(note) return valid_set
def compute_result(self, arg_contextual_note, target_contextual_note, up_intvl, down_intvl): """ :param arg_contextual_note: :param target_contextual_note: :param up_intvl: :param down_intvl: :return: """ starting_pitch = arg_contextual_note.note.diatonic_pitch chromatic_distance_start = starting_pitch.chromatic_distance - down_intvl.chromatic_distance chromatic_distance_end = starting_pitch.chromatic_distance + up_intvl.chromatic_distance r_start = max( chromatic_distance_start, target_contextual_note.policy_context.pitch_range.start_index) r_end = min( chromatic_distance_end, target_contextual_note.policy_context.pitch_range.end_index) if r_start > r_end: return OrderedSet() pitch_range = PitchRange(r_start, r_end) pitch_scale = PitchScale( target_contextual_note.policy_context.harmonic_context.tonality, pitch_range) result = OrderedSet() for pitch in pitch_scale.pitch_scale: result.add( Note(pitch, self.note_two.base_duration, self.note_two.num_dots)) # v_result = {Note(pitch, self.note_two.base_duration, self.note_two.num_dots) # for pitch in pitch_scale.pitch_scale} return result
def _policy_values(self, p_map, v_note): """ For v_note, find all note values for its target that satisfy all policies in which v_note is involved. :param p_map: PMap :param v_note: ContextualNote :return: A set of notes with same duration as v_note, varying in pitch. """ pitches = None for p in self.v_policy_map[v_note]: p_values = p.values(p_map, v_note) if p_values is None: returned_pitches = OrderedSet() # None means p cannot be satisfied! else: returned_pitches = OrderedSet() for n in p_values: returned_pitches.add(n.diatonic_pitch) pitches = returned_pitches if pitches is None else pitches.intersection(returned_pitches) retset = OrderedSet() for p in pitches: retset.add(Note(p, v_note.base_duration, v_note.num_dots)) return retset
def values(self, p_map, v_note): """ Find value candidates for v_note target, given p_map. In this constraints, we are more interested in the set of values that v_note target cannot take. :param p_map: :param v_note: :return: """ assigned = p_map.assigned_actors(self) unassigned = p_map.unassigned_actors(self) if len(assigned) == 0: pitches = p_map.all_tonal_pitches(v_note) return {Note(p, v_note.note.base_duration, v_note.note.num_dots) for p in pitches} if v_note in assigned: return {p_map[v_note].note} if v_note not in unassigned: raise Exception('{0} is not in actor list of not equal pitch constraints.'.format(v_note.note)) e_set = OrderedSet() for v in assigned: e_set.add(p_map[v].note) return NotEqualPitchConstraint.compute_full_result(p_map, v_note, e_set)
def test_for_book_example_2(self): print('----- test for book example 2 -----') source_instance_expression = '{<A-Major:i> [sA:4 A A A] qA:4 [iA:4 A] <:iv> qA:4 [sA:4 A A A] qA:4}' target_instance_expression = '{<G-Major:i> wA:4 <:iv> wA:4}' lge = LineGrammarExecutor() source_instance_line, source_instance_hct = lge.parse( source_instance_expression) actors = source_instance_line.get_all_notes() for a in actors: print("{0}".format(a)) target_instance_line, target_instance_hct = lge.parse( target_instance_expression) target_hcs = target_instance_hct.hc_list() for hc in target_hcs: print("{0}".format(hc)) pitch_range = PitchRange( DiatonicPitch.parse('C:4').chromatic_distance, DiatonicPitch.parse('C:6').chromatic_distance) p_map = PMap.create(source_instance_expression, pitch_range, [('G-Major:I', Duration(3, 4)), ('G-Major:IV', Duration(3, 4))]) actors = p_map.actors policies = OrderedSet() policies.add( StepSequenceConstraint( [actors[0], actors[1], actors[2], actors[3]], [1, 1, 1])) policies.add(ChordalPitchConstraint(actors[0])) policies.add(ChordalPitchConstraint(actors[4])) policies.add(ChordalPitchConstraint(actors[8])) policies.add( StepSequenceConstraint( [actors[8], actors[9], actors[10], actors[11]], [1, -1, -1])) policies.add(EqualPitchConstraint([actors[0], actors[12]])) policies.add(EqualPitchConstraint([actors[4], actors[7]])) policies.add( RelativeDiatonicConstraint(actors[4], actors[5], Interval(3, IntervalType.Major), Interval(1, IntervalType.Perfect))) policies.add(StepSequenceConstraint([actors[5], actors[6]], [-1])) # policies.add(ChordalPitchConstraint(actors[7])) solver = PitchConstraintSolver(policies) full_results, partial_results = solver.solve(p_map) print('Results has {0} results.'.format(len(full_results))) for pm in full_results: if str(pm[actors[7]].note.diatonic_pitch) == 'D:4' and str( pm[actors[0]].note.diatonic_pitch) == 'G:4': print("{0}".format(pm))
def _visit(self, p_map, v_note): """ Recursive method used to derive sets of solution to the constraints constraint problem. :param p_map: PMap :param v_note: ContextualNote, source key of PMap :return: A set of pmaps """ if p_map[v_note].note is not None: # setting means visited return {} results = OrderedSet() # list of tuples (v_note, {solution to v_note's policies}) result_values = self._build_potential_values(p_map, {v_note}) if len(result_values) == 0: return results for value in result_values[0][1]: # [0] is for v_note; [1] is the set of values. p_map[v_note].note = value value_results = OrderedSet() # results for this value for v_note + solutions for ALL peers to v_note # Advantage in the following, setting above partially solves each policy v_note is involved in. # For this 'value' for v_note, dive depth first through all v_note peers (all policies v_note is in) # which collectively we call a branch. # peer_candidates are all unassigned actors in v_note's policies. peer_candidates = self._candidate_closure(p_map, v_note) if len(peer_candidates) == 0: # We reached the end of a branch, save the result. if self._full_check_and_validate(p_map): if self.instance_limit != -1 and self.__num_instances >= self.instance_limit: return results self.full_results.append(p_map.replicate()) self.__num_instances = self.__num_instances + 1 else: value_results.add(p_map.replicate()) # We only need a shallow copy else: for c_note in peer_candidates: if self.instance_limit != -1 and self.__num_instances >= self.instance_limit: results = results.union(value_results) return results if len(value_results) == 0: # first time through this loop per value, visit with p_map! value_results = self._visit(p_map, c_note) if len(value_results) == 0: # Indicates failure to assign c_note, move to next value. break # If one peer fails, they all will, for this 'value'! else: value_results_copy = value_results.copy() # for peer c_note, if all r below fails (len(cand_results) == 0) should we also move on to # next value? Add 'found' flag, set after union, after loop, check if False, to break found = False for r in value_results_copy: if r[c_note].note is None: cand_results = self._visit(r, c_note) if len(cand_results) != 0: value_results = value_results.union(cand_results) found = True value_results.remove(r) # r has no c_note assigned, what was returned did! # If not, r's peers cannot be assigned! if found is False: # Same as if part, if c_note produces no results, it cannot be assigned. break # If one peer fails, they all will! results = results.union(value_results) p_map[v_note].note = None return results
def test_ordering(self): s1 = OrderedSet() for i in range(1, 10): s1.add(TestObject(i)) objects = [t.ord_value() for t in s1] assert objects == [1, 2, 3, 4, 5, 6, 7, 8, 9]
def values(self, p_map, v_note): """ Compute possible values for v_note's target. :param p_map: note-->contextual_note :param v_note: Note :return: Candidate Notes. Note: Here is why the intervals are reversed for solving for note_one: Suppose x --> [x-a, x + b]. Then for some value y, for t with y-b<=t<=y+a, we have t -->[t-a, t+b], but from the inequalities, t-a<=y<t+b - so the reverse map is [y-b, y+a] <-- y, which is exactly what happens below. """ if v_note == self.note_two: source = self.note_one target = self.note_two comparative = self.comparative elif v_note == self.note_one: source = self.note_two target = self.note_one comparative = 4 - self.comparative else: raise Exception( 'v_note specification does not match any v_note in constraints.' ) if p_map[target].note is not None: return {p_map[target].note} if p_map[source].note is None: answer_range = p_map[target].policy_context.pitch_range source_pitch = None else: # Establish a pitch range commensurate with comparative. qrange = p_map[target].policy_context.pitch_range source_pitch = p_map[source].note.diatonic_pitch if comparative > 2: answer_range = PitchRange(qrange.start_index, source_pitch.chromatic_distance) elif comparative < 2: answer_range = PitchRange(source_pitch.chromatic_distance, qrange.end_index) else: answer_range = PitchRange(source_pitch.chromatic_distance, source_pitch.chromatic_distance) pitches = PitchScale.compute_tonal_pitches( p_map[target].policy_context.harmonic_context.tonality, answer_range) if comparative == 4 and len(pitches) > 0 and source_pitch is not None and \ source_pitch.chromatic_distance == pitches[-1].chromatic_distance: pitches.pop(-1) if comparative == 0 and len(pitches) > 0 and source_pitch is not None and \ source_pitch.chromatic_distance == pitches[0].chromatic_distance: del pitches[0] answer = OrderedSet() for pitch in pitches: answer.add(Note(pitch, target.base_duration, target.num_dots)) return answer