def test_create(self): with self.assertRaises(Exception): PitchRange(8, 96) with self.assertRaises(Exception): PitchRange(9, 97) with self.assertRaises(Exception): PitchRange('Ab:0', 'C:8') with self.assertRaises(Exception): PitchRange.create('A:0', 'C#:8')
def compute_closest_scale_tones(tonality, pitch): """ Returns either the pitch if in tonality, or lower/upper pitches in scale to pitch. :param tonality: :param pitch: :return: an array with 1 element if exact match, otherwise closest lower and upper bound pitches in given tonality. """ from tonalmodel.pitch_range import PitchRange chromatic_index = pitch.chromatic_distance pitch_range = PitchRange( max(chromatic_index - 12, ChromaticScale.chromatic_start_index()), min(chromatic_index + 12, ChromaticScale.chromatic_end_index())) pitch_scale = PitchScale(tonality, pitch_range) for i in range(0, len(pitch_scale.pitch_scale)): p = pitch_scale.pitch_scale[i] if p.chromatic_distance < chromatic_index: continue if p.chromatic_distance == chromatic_index: return [p] if i == 0: raise Exception( 'unexpected logic issue in compute_closest_pitch_range {0}, {1]' .format(tonality, pitch)) return [pitch_scale.pitch_scale[i - 1], p] raise Exception( 'unexpected logic fail in compute_closest_pitch_range {0}, {1]'. format(tonality, pitch))
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 policy_creator(modality_type, modality_tone, tertian_chord_txt, low_pitch_txt, hi_pitch_txt): diatonic_tonality = Tonality.create(modality_type, modality_tone) chord = TertianChordTemplate.parse(tertian_chord_txt).create_chord(diatonic_tonality) hc = HarmonicContext(diatonic_tonality, chord, Duration(1, 2)) pitch_range = PitchRange(DiatonicPitch.parse(low_pitch_txt).chromatic_distance, DiatonicPitch.parse(hi_pitch_txt).chromatic_distance) return PolicyContext(hc, pitch_range)
def apply(self, target_hct, window_anchor_pitch, tag_map=None, window_height=None, num_solutions=-1, tunnel_half_interval=Interval(5, IntervalType.Perfect)): """ Apply method for transformation. :param target_hct: Target hct for new target line. :param window_anchor_pitch: Pitch specifying the lowest pitch for the target line window. :param tag_map: map index of source/target note to specified pitch. :param window_height: Height of target pitch window (in semi-tones) - use source line height if None specified. :param num_solutions: Maximum number of solutions to return, -1 == unbounded. :param tunnel_half_interval: half-interval for pitch range on each target tone. :return: MCSResults """ if self.source_hct.duration != target_hct.duration: raise Exception( 'Target hct duration {0} does not match source hct duration {1}.' .format(target_hct.duration, self.source_hct.duration)) window_anchor_pitch = DiatonicPitch.parse(window_anchor_pitch) if isinstance(window_anchor_pitch, str) \ else window_anchor_pitch target_line = self._build_target_line() self.__tunnel_half_interval = tunnel_half_interval source_notes = self.source_line.get_all_notes() target_notes = target_line.get_all_notes() source_to_target = { source_note: target_note for source_note, target_note in zip(source_notes, target_notes) } constraints = self._build_constraints(source_to_target, tag_map) ts_seq, tempo_seq = THarmonicTranscription._build_default_time_sig_tempo( ) height = window_height if window_height else self.height pitch_range = PitchRange( window_anchor_pitch.chromatic_distance, window_anchor_pitch.chromatic_distance + height) solver = MelodicConstraintSolver(target_line, tempo_seq, ts_seq, target_hct, pitch_range, constraints) initial_map = {target_notes[k]: v for k, v in tag_map.items()} if tag_map else None results = solver.solve(initial_map, num_solutions) return results
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 written_pitch_range(self): return PitchRange(self.written_low.chromatic_distance, self.written_high.chromatic_distance)
def sounding_pitch_range(self): return PitchRange(self.sounding_low.chromatic_distance, self.sounding_high.chromatic_distance)
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 create_default(tonality): return PitchScale( tonality, PitchRange(ChromaticScale.chromatic_start_index(), ChromaticScale.chromatic_end_index()))
def compute_tonal_pitch_range(tonality, pitch, lower_index, upper_index): """ Find all pitches within range of tonality based on an arbitrary pitch given as starting point. In all cases, look at the closest pitches (1 or 2) as origin 0, and the lower/upper as counting indices below or up from them. :param tonality: :param pitch: :param lower_index: :param upper_index: :return: """ import math from tonalmodel.pitch_range import PitchRange starting_points = PitchScale.compute_closest_scale_tones( tonality, pitch) # Determine the number of octaves that will cover the given range. up_chrom = max( 0, int( math.ceil(float(abs(upper_index)) / len(tonality.annotation)) * 12) * (-1 if upper_index < 0 else 1)) down_chrom = min( 0, int( math.ceil(float(abs(lower_index)) / len(tonality.annotation)) * 12) * (-1 if lower_index < 0 else 1)) # Compute all pitches within that range low = max(starting_points[0].chromatic_distance + down_chrom, ChromaticScale.chromatic_start_index()) high = min( (starting_points[0].chromatic_distance if len(starting_points) == 1 else starting_points[1].chromatic_distance) + up_chrom, ChromaticScale.chromatic_end_index()) pitch_range = PitchRange(low, high) pitch_scale = PitchScale(tonality, pitch_range).pitch_scale # The first starting point is either the enharmonic equivalent to pitch, or the lower scale pitch to the pitch. # lower_starting_index is the index in pitch_scale for that pitch. lower_starting_index = [ index for index in range(0, len(pitch_scale)) if pitch_scale[index].chromatic_distance == starting_points[0].chromatic_distance ][0] if len(starting_points) == 1: full_range = range( lower_starting_index + lower_index, min(lower_starting_index + upper_index + 1, len(pitch_scale))) return [pitch_scale[i] for i in full_range] else: upper_starting_index = [ index for index in range(0, len(pitch_scale)) if pitch_scale[index].chromatic_distance == starting_points[1].chromatic_distance ][0] lo = lower_index + (lower_starting_index if lower_index <= 0 else upper_starting_index) hi = upper_index + (lower_starting_index if upper_index < 0 else upper_starting_index) full_range = range(lo, hi + 1) return [pitch_scale[i] for i in full_range]