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 verify(self, parameter_map): """ Verify that the note pitch of the first proxy and the note pitch of the second proxy meet the constraints diatonic distance. :param parameter_map: :return: """ first_contextual_note = parameter_map[self.note_one] second_contextual_note = parameter_map[self.note_two] if str(first_contextual_note.policy_context.harmonic_context.tonality) != \ str(second_contextual_note.policy_context.harmonic_context.tonality): raise Exception( 'Note one and two of tonal step constraints must match') pitch_scale = PitchScale( first_contextual_note.policy_context.harmonic_context.tonality, first_contextual_note.policy_context.pitch_range) scale = pitch_scale.pitch_scale if first_contextual_note.note is None or second_contextual_note.note is None: return False first_pitch = first_contextual_note.note.diatonic_pitch second_pitch = second_contextual_note.note.diatonic_pitch first_index = scale.index( first_pitch) if first_pitch in scale else None second_index = scale.index( second_pitch) if second_pitch in scale else None if first_index is None or second_index is None: return False return second_index - first_index == self.n_steps * ( 1 if self.up_down == PitchStepConstraint.UP else -1)
def test_low_range(self): ranges = PitchRange.create("A:0", "C:5") for modality_type in SYSTEM_MODALITIES: for validTone in ModalityFactory.create_modality(modality_type).get_valid_root_tones(): tonality = Tonality.create(modality_type, DiatonicFoundation.get_tone(validTone)) pitch_scale = PitchScale(tonality, ranges) print('Scale {0} {1} on {2}: {3}'.format(validTone, modality_type.name, ranges, ','.join(map(get_symbol, pitch_scale.pitch_scale)))) scale_check(pitch_scale, tonality)
def _build_pitch_segment_dict(tonality, scale_origin_str): origin_pitch = DiatonicPitch.parse(scale_origin_str) pitch_range = PitchRange.create( '{0}:{1}'.format(origin_pitch.diatonic_tone.diatonic_symbol, origin_pitch.octave), '{0}:{1}'.format(origin_pitch.diatonic_tone.diatonic_symbol, origin_pitch.octave + 1)) scale = PitchScale(tonality, pitch_range).pitch_scale[0: -1] d = OrderedDict() for p in scale: d[p.diatonic_tone] = p.octave return d
def all_tonal_pitches(self, v_note): """ Compute all tonal pitches for the target of a v_note in p_map. :param v_note: :return: """ target = self[v_note] if target is None: raise Exception( 'Internal construction error, v_note target is None.') policy_context = target.policy_context return PitchScale.compute_tonal_pitches( policy_context.harmonic_context.tonality, policy_context.pitch_range)
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 verify(self, parameter_map): """ Verify that p_map has values satisfying the constraint. :param parameter_map: :return: """ first_contextual_note = parameter_map[self.note_one] second_contextual_note = parameter_map[self.note_two] if first_contextual_note.note is None or second_contextual_note.note is None: return False pitches = PitchScale.compute_tonal_pitch_range( second_contextual_note.policy_context.harmonic_context.tonality, first_contextual_note.note.diatonic_pitch, self.lower_steps, self.upper_steps) return second_contextual_note.note.diatonic_pitch in pitches
def __init__(self, tonality, anchor_pitch=None, anchor_value=None, pitch_unit=1): """ Constructor. :param tonality: The tonality being mapped to. :param anchor_pitch: A DiatonicPitch, in combo with anchor_value is a sample of the mapping. :param anchor_value: A numeric value that maps to anchor_pitch. :param pitch_unit: In the linear map of value to pitches, pitch_unit is the distance between mapping values. """ self.__tonality = tonality self.__pitch_scale = PitchScale(self.tonality, PitchRange.create('A:0', 'C:8')).pitch_scale self.anchor_pitch = self.pitch_scale[0] if anchor_pitch is None else \ DiatonicPitch.parse(anchor_pitch) if isinstance(anchor_pitch, str) else anchor_pitch anchor_index = self.pitch_scale.index(self.anchor_pitch) if anchor_index == -1: raise Exception( 'Anchor pitch \'{0}\' not found in pitch scale for tonality \'{1}\'' .format(self.anchor_pitch, self.tonality)) self.__pitch_unit = pitch_unit self.anchor_value = anchor_value if anchor_value is not None else anchor_index * self.pitch_unit # base value should map to beginning of pitch scale! # recall that pitch unit maps to each pitch, making the scalar scale linear in value! base_value = anchor_value - anchor_index * pitch_unit self.value_to_pitch = OrderedMap() self.pitch_to_value = dict() for i in range(0, len(self.pitch_scale)): pitch = self.pitch_scale[i] value = base_value + i * pitch_unit self.value_to_pitch.insert(value, pitch) self.pitch_to_value[pitch] = value PitchRangeInterpreter.__init__(self)
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 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 compute_note(p_map, assigned_note, unassigned_note): """ For an assigned note and an unassigned note, return for unassigned, a note the same as assigned, but with pitch enharmonic to its tonality. :param p_map: :param assigned_note: :param unassigned_note: :return: """ # select a pitch representation closest to the tonality, if it exists. policy_context = p_map[unassigned_note].policy_context pitch = p_map[assigned_note].note.diatonic_pitch for p in pitch.enharmonics(): for t in PitchScale(policy_context.harmonic_context.tonality, policy_context.pitch_range).tone_scale: if p.diatonic_tone == t: pitch = p break actual_note = Note(pitch, unassigned_note.base_duration, unassigned_note.num_dots) return OrderedSet([actual_note])
def _find_closest_tone(self, tone): # determine if pitch ltr is in tonality, get that tone ll = [t for t in self.tones if t.diatonic_letter == tone.diatonic_letter] if len(ll) == 1: return ll[0], ll[0].augmentation_offset - tone.augmentation_offset # Do something if len(ll) > 1 elif len(ll) > 1: ll.sort(key=lambda x: abs(x.augmentation_offset - tone.augmentation_offset)) return ll[0], ll[0].augmentation_offset - tone.augmentation_offset first_pitch = DiatonicPitch(3, self.tones[0]) last_pitch = DiatonicPitch(5, self.tones[-1]) scale = PitchScale(self.tonality, PitchRange.create(first_pitch, last_pitch)) pitch = DiatonicPitch(4, tone) after_pitch = last_pitch before_pitch = None for p in scale.pitch_scale: if pitch.chromatic_distance <= p.chromatic_distance: after_pitch = p break else: before_pitch = p # distance measured FROM pitch TO near pitch (e.g., -1 before, +1 after) before_distance = before_pitch.chromatic_distance - pitch.chromatic_distance after_distance = after_pitch.chromatic_distance - pitch.chromatic_distance if abs(before_distance) < abs(after_distance): closest_distance = before_distance closest_pitch = before_pitch else: closest_distance = after_distance closest_pitch = after_pitch return closest_pitch.diatonic_tone, closest_distance
def compute_result(self, arg_contextual_note, target_contextual_note, up_down): """ Compute the target note from the arg note basecd on up_down and self.n_steps. :param arg_contextual_note: Given note :param target_contextual_note: Note to find based on constraints. :param up_down: :return: """ if str(arg_contextual_note.policy_context.harmonic_context.tonality) != \ str(target_contextual_note.policy_context.harmonic_context.tonality): raise Exception( 'Note one and two of tonal step constraints must match on tonality' ) starting_pitch = arg_contextual_note.note.diatonic_pitch pitch_scale = PitchScale( target_contextual_note.policy_context.harmonic_context.tonality, target_contextual_note.policy_context.pitch_range) scale = pitch_scale.pitch_scale pitch_index = scale.index( starting_pitch) if starting_pitch in scale else None if pitch_index is None: return None end_index = pitch_index + ( self.n_steps * (1 if up_down == PitchStepConstraint.UP else -1)) if end_index not in range(0, len(scale)): return None end_pitch = scale[end_index] return OrderedSet([ Note(end_pitch, self.note_two.base_duration, self.note_two.num_dots) ])
def test_compute_tonal_pitch_range(self): logging.debug('Start test_compute_tonal_pitch_range') tonality = Tonality.create(ModalityType.Major, DiatonicTone('Ab')) pitch = DiatonicPitch(4, 'B#') pitches = PitchScale.compute_tonal_pitch_range(tonality, pitch, 5, 7) for p in reversed(pitches): print(p) test_pitches = [ 'A:4', 'B:4', 'C:4', 'D:4', 'E:4', 'F:4', 'G:4', 'Ab:4', 'Bb:4', 'Db:4', 'Eb:4', 'C#:4', 'D#:4', 'G#:4', 'A#:4', 'B#:4', 'Cb:4', 'B##:4', 'Cbb:4' ] ranges = [[0, 5], [-5, 0], [-4, 2], [-5, -3], [5, 7]] answers = [ '[Ab:4,Bb:4,C:5,Db:5,Eb:5,F:5,G:5]', '[C:4,Db:4,Eb:4,F:4,G:4,Ab:4,Bb:4]', '[Db:4,Eb:4,F:4,G:4,Ab:4,Bb:4,C:5,Db:5]', '[C:4,Db:4,Eb:4]', '[G:5,Ab:5,Bb:5]', '[Bb:4,C:5,Db:5,Eb:5,F:5,G:5,Ab:5]', '[Db:4,Eb:4,F:4,G:4,Ab:4,Bb:4,C:5]', '[Eb:4,F:4,G:4,Ab:4,Bb:4,C:5,Db:5,Eb:5]', '[Db:4,Eb:4,F:4]', '[Ab:5,Bb:5,C:6]', '[C:4,Db:4,Eb:4,F:4,G:4,Ab:4]', '[Eb:3,F:3,G:3,Ab:3,Bb:3,C:4]', '[F:3,G:3,Ab:3,Bb:3,C:4,Db:4,Eb:4]', '[Eb:3,F:3,G:3]', '[Ab:4,Bb:4,C:5]', '[Db:4,Eb:4,F:4,G:4,Ab:4,Bb:4,C:5]', '[F:3,G:3,Ab:3,Bb:3,C:4,Db:4,Eb:4]', '[G:3,Ab:3,Bb:3,C:4,Db:4,Eb:4,F:4,G:4]', '[F:3,G:3,Ab:3]', '[C:5,Db:5,Eb:5]', '[Eb:4,F:4,G:4,Ab:4,Bb:4,C:5,Db:5]', '[G:3,Ab:3,Bb:3,C:4,Db:4,Eb:4,F:4]', '[Ab:3,Bb:3,C:4,Db:4,Eb:4,F:4,G:4,Ab:4]', '[G:3,Ab:3,Bb:3]', '[Db:5,Eb:5,F:5]', '[F:4,G:4,Ab:4,Bb:4,C:5,Db:5]', '[Ab:3,Bb:3,C:4,Db:4,Eb:4,F:4]', '[Bb:3,C:4,Db:4,Eb:4,F:4,G:4,Ab:4]', '[Ab:3,Bb:3,C:4]', '[Db:5,Eb:5,F:5]', '[G:4,Ab:4,Bb:4,C:5,Db:5,Eb:5]', '[Bb:3,C:4,Db:4,Eb:4,F:4,G:4]', '[C:4,Db:4,Eb:4,F:4,G:4,Ab:4,Bb:4]', '[Bb:3,C:4,Db:4]', '[Eb:5,F:5,G:5]', '[Ab:4,Bb:4,C:5,Db:5,Eb:5,F:5]', '[C:4,Db:4,Eb:4,F:4,G:4,Ab:4]', '[Db:4,Eb:4,F:4,G:4,Ab:4,Bb:4,C:5]', '[C:4,Db:4,Eb:4]', '[F:5,G:5,Ab:5]', '[Bb:4,C:5,Db:5,Eb:5,F:5,G:5]', '[Db:4,Eb:4,F:4,G:4,Ab:4,Bb:4]', '[Eb:4,F:4,G:4,Ab:4,Bb:4,C:5,Db:5]', '[Db:4,Eb:4,F:4]', '[G:5,Ab:5,Bb:5]', '[Db:4,Eb:4,F:4,G:4,Ab:4,Bb:4]', '[F:3,G:3,Ab:3,Bb:3,C:4,Db:4]', '[G:3,Ab:3,Bb:3,C:4,Db:4,Eb:4,F:4]', '[F:3,G:3,Ab:3]', '[Bb:4,C:5,Db:5]', '[Eb:4,F:4,G:4,Ab:4,Bb:4,C:5]', '[G:3,Ab:3,Bb:3,C:4,Db:4,Eb:4]', '[Ab:3,Bb:3,C:4,Db:4,Eb:4,F:4,G:4]', '[G:3,Ab:3,Bb:3]', '[C:5,Db:5,Eb:5]', '[Db:4,Eb:4,F:4,G:4,Ab:4,Bb:4]', '[F:3,G:3,Ab:3,Bb:3,C:4,Db:4]', '[G:3,Ab:3,Bb:3,C:4,Db:4,Eb:4,F:4]', '[F:3,G:3,Ab:3]', '[Bb:4,C:5,Db:5]', '[Eb:4,F:4,G:4,Ab:4,Bb:4,C:5]', '[G:3,Ab:3,Bb:3,C:4,Db:4,Eb:4]', '[Ab:3,Bb:3,C:4,Db:4,Eb:4,F:4,G:4]', '[G:3,Ab:3,Bb:3]', '[C:5,Db:5,Eb:5]', '[Ab:4,Bb:4,C:5,Db:5,Eb:5,F:5]', '[C:4,Db:4,Eb:4,F:4,G:4,Ab:4]', '[Db:4,Eb:4,F:4,G:4,Ab:4,Bb:4,C:5]', '[C:4,Db:4,Eb:4]', '[F:5,G:5,Ab:5]', '[Bb:4,C:5,Db:5,Eb:5,F:5,G:5]', '[Db:4,Eb:4,F:4,G:4,Ab:4,Bb:4]', '[Eb:4,F:4,G:4,Ab:4,Bb:4,C:5,Db:5]', '[Db:4,Eb:4,F:4]', '[G:5,Ab:5,Bb:5]', '[C:5,Db:5,Eb:5,F:5,G:5,Ab:5]', '[Eb:4,F:4,G:4,Ab:4,Bb:4,C:5]', '[F:4,G:4,Ab:4,Bb:4,C:5,Db:5,Eb:5]', '[Eb:4,F:4,G:4]', '[Ab:5,Bb:5,C:6]', '[Bb:3,C:4,Db:4,Eb:4,F:4,G:4,Ab:4]', '[Db:3,Eb:3,F:3,G:3,Ab:3,Bb:3,C:4]', '[Eb:3,F:3,G:3,Ab:3,Bb:3,C:4,Db:4,Eb:4]', '[Db:3,Eb:3,F:3]', '[Ab:4,Bb:4,C:5]', '[Db:5,Eb:5,F:5,G:5,Ab:5,Bb:5]', '[F:4,G:4,Ab:4,Bb:4,C:5,Db:5]', '[G:4,Ab:4,Bb:4,C:5,Db:5,Eb:5,F:5]', '[F:4,G:4,Ab:4]', '[Bb:5,C:6,Db:6]', '[Bb:3,C:4,Db:4,Eb:4,F:4,G:4]', '[Db:3,Eb:3,F:3,G:3,Ab:3,Bb:3]', '[Eb:3,F:3,G:3,Ab:3,Bb:3,C:4,Db:4]', '[Db:3,Eb:3,F:3]', '[G:4,Ab:4,Bb:4]', ] answer_idx = 0 for pitch_str in test_pitches: for r in ranges: pitch = DiatonicPitch.parse(pitch_str) pitches = PitchScale.compute_tonal_pitch_range( tonality, pitch, r[0], r[1]) answer_str = '[' + (','.join(str(p) for p in pitches)) + ']' # print '\'[' + (','.join(str(p) for p in pitches)) + ']\',' answer = answers[answer_idx] answer_idx = answer_idx + 1 print('{0} [{1}, {2}]: {3}'.format(pitch, r[0], r[1], answer_str)) assert answer == answer_str logging.debug('End test_compute_tonal_pitch_range')
def test_compute_closest_scale_tones(self): logging.debug('Start test_compute_closest_scale_tones') tonality = Tonality.create(ModalityType.Major, DiatonicTone('Ab')) test_pitches = [ 'A:4', 'B:4', 'C:4', 'D:4', 'E:4', 'F:4', 'G:4', 'Ab:4', 'Bb:4', 'Db:4', 'Eb:4', 'C#:4', 'D#:4', 'G#:4', 'A#:4', 'B#:4', 'Cb:4', 'B##:4', 'Cbb:4' ] answers = [ '[Ab:4,Bb:4]', '[Bb:4,C:5]', '[C:4]', '[Db:4,Eb:4]', '[Eb:4,F:4]', '[F:4]', '[G:4]', '[Ab:4]', '[Bb:4]', '[Db:4]', '[Eb:4]', '[Db:4]', '[Eb:4]', '[Ab:4]', '[Bb:4]', '[C:5]', '[Bb:3,C:4]', '[Db:5]', '[Bb:3]', ] for pitch_str, answer in zip(test_pitches, answers): pitch = DiatonicPitch.parse(pitch_str) closest = PitchScale.compute_closest_scale_tones(tonality, pitch) # print('{0} ==> [{1}]'.format(pitch, ','.join(str(p) for p in closest))) test_answer = '[' + (','.join(str(p) for p in closest)) + ']' assert answer == test_answer # Do again for C major tonality = Tonality.create(ModalityType.HarmonicMinor, DiatonicTone('C')) test_pitches = [ 'C:4', 'D:4', 'E:4', 'F:4', 'G:4', 'A:4', 'B:4', 'Eb:4', 'Ab:4', 'Db:4', 'Gb:4', 'Bb:4', 'Cb:4', 'B#:4' ] answers = [ '[C:4]', '[D:4]', '[Eb:4,F:4]', '[F:4]', '[G:4]', '[Ab:4,B:4]', '[B:4]', '[Eb:4]', '[Ab:4]', '[C:4,D:4]', '[F:4,G:4]', '[Ab:4,B:4]', '[B:3]', '[C:5]', ] for pitch_str, answer in zip(test_pitches, answers): pitch = DiatonicPitch.parse(pitch_str) closest = PitchScale.compute_closest_scale_tones(tonality, pitch) test_answer = '[' + (','.join(str(p) for p in closest)) + ']' assert answer == test_answer logging.debug('End test_compute_closest_scale_tones')
def test_book_example(self): pitch_range = PitchRange.create("c:4", "c:6") tonality = Tonality.create(ModalityType.MelodicMinor, 'D') pitch_scale = PitchScale(tonality, pitch_range) print('Scale = [{0}]'.format(', '.join(str(pitch) for pitch in pitch_scale.pitch_scale)))
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
def _find_closest_pitch(self, pitch): """ Given a pitch, find the scale pitch closest to it in chromatic distance. :param pitch: :return: 1) closest in half-steps pitch in tonality. 2) chromatic distance measured from closest pitch to given pitch. Note: algorithm looks for tonal pitches with same letter as pitch first, otherwise nearest non-tonal pitch. """ start_octave = max(pitch.octave - 1, ChromaticScale.CHROMATIC_START[0]) end_octave = min(pitch.octave + 1, ChromaticScale.CHROMATIC_END[0]) # Compute the first and last pitches within the start/end octave range. To build a PitchScale first_pitch = None for t in self.tonality.annotation: first_pitch = DiatonicPitch(start_octave, t) if DiatonicFoundation.get_chromatic_distance(first_pitch) >= ChromaticScale.chromatic_start_index(): break last_pitch = None loop_finished = False for o in range(end_octave, end_octave - 2, -1): if loop_finished: break for t in reversed(self.tonality.annotation): last_pitch = DiatonicPitch(o, t) if DiatonicFoundation.get_chromatic_distance(last_pitch) <= ChromaticScale.chromatic_end_index(): loop_finished = True break scale = PitchScale(self.tonality, PitchRange.create(first_pitch, last_pitch)) # determine if pitch ltr is in tonality, get that tone ll = [t for t in self.tones if t.diatonic_letter == pitch.diatonic_tone.diatonic_letter] if len(ll) == 1: pp = DiatonicPitch(pitch.octave, ll[0]) return pp, pitch.chromatic_distance - pp.chromatic_distance # Do something if len(ll) > 1 elif len(ll) > 1: ll.sort(key=lambda x: abs(x.augmentation_offset - pitch.diatonic_tone.augmentation_offset)) pp = DiatonicPitch(pitch.octave, ll[0]) return pp, pitch.chromatic_distance - pp.chromatic_distance before_pitch = first_pitch after_pitch = last_pitch for p in scale.pitch_scale: if pitch.chromatic_distance <= p.chromatic_distance: after_pitch = p break else: before_pitch = p before_distance = pitch.chromatic_distance - before_pitch.chromatic_distance after_distance = pitch.chromatic_distance - after_pitch.chromatic_distance if pitch.diatonic_tone.diatonic_letter == before_pitch.diatonic_tone.diatonic_letter: closest_distance = before_distance closest_pitch = before_pitch elif pitch.diatonic_tone.diatonic_letter == after_pitch.diatonic_tone.diatonic_letter: closest_distance = after_distance closest_pitch = after_pitch else: if abs(before_distance) < abs(after_distance): closest_distance = before_distance closest_pitch = before_pitch else: closest_distance = after_distance closest_pitch = after_pitch return closest_pitch, closest_distance
def values(self, p_map, v_note): """ :param p_map: :param v_note: :return: """ index = self.actors.index(v_note) if v_note in self.actors else None if index is None: raise Exception('Cannot find v_note in constraints actors') if p_map[v_note].note is not None: return OrderedSet([p_map[v_note].note]) # find the first assigned note assigned_index = None for i in range(0, len(self.actors)): if p_map[self.actors[i]].note is not None: assigned_index = i break if assigned_index is None: pitches = p_map.all_tonal_pitches(v_note) return OrderedSet([Note(p, v_note.base_duration, v_note.num_dots) for p in pitches]) known_note = p_map[self.actors[assigned_index]].note if assigned_index < index: for i in range(assigned_index + 1, index + 1): unknown_contextual_note = p_map[self.actors[i]] unknown_note = unknown_contextual_note.note if unknown_note is not None: known_note = unknown_note continue lower_index = self.variance_list[i - 1] if self.variance_list[i - 1] < 0 else 0 upper_index = self.variance_list[i - 1] if self.variance_list[i - 1] > 0 else 0 pitches = PitchScale.compute_tonal_pitch_range( unknown_contextual_note.policy_context.harmonic_context.tonality, known_note.diatonic_pitch, lower_index, upper_index) pitch_index = self.variance_list[i - 1] + (len(pitches) - 1 if self.variance_list[i - 1] < 0 else 0) if pitch_index < 0 or pitch_index >= len(pitches): if pitches is None or len(pitches) == 0: return OrderedSet() pitch = pitches[0] if pitch_index < 0 else pitches[len(pitches) - 1] else: pitch = pitches[pitch_index] known_note = Note(pitch, self.actors[i].base_duration, self.actors[i].num_dots) return OrderedSet([known_note]) for i in range(assigned_index - 1, index - 1, -1): unknown_contextual_note = p_map[self.actors[i]] unknown_note = unknown_contextual_note.note if unknown_note is not None: known_note = unknown_note continue upper_index = -self.variance_list[i] if self.variance_list[i] < 0 else 0 lower_index = -self.variance_list[i] if self.variance_list[i] > 0 else 0 pitches = PitchScale.compute_tonal_pitch_range( unknown_contextual_note.policy_context.harmonic_context.tonality, known_note.diatonic_pitch, lower_index, upper_index) pitch_index = -self.variance_list[i] + (len(pitches) - 1 if self.variance_list[i] > 0 else 0) if pitch_index < 0: pitch_index = 0 elif pitch_index >= len(pitches): pitch_index = len(pitches) - 1 pitch = pitches[pitch_index] known_note = Note(pitch, self.actors[i].base_duration, self.actors[i].num_dots) return OrderedSet([known_note])