Skip to content

Commit 887038b

Browse files
refac: Simplify evaluation of movement to perfect consonances
1 parent d1f4df1 commit 887038b

File tree

2 files changed

+54
-33
lines changed

2 files changed

+54
-33
lines changed

dodecaphony/scoring_functions/harmony.py

+25-33
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,8 @@ def evaluate_absence_of_voice_crossing(
8181
"""
8282
Evaluate absence of voice crossing.
8383
84-
Voice crossing may result in wrong perception of tone row and in incoherence of the fragment.
84+
Voice crossing may result in wrong perception of the tone row
85+
(especially, if voices are played with the same timbre).
8586
8687
:param fragment:
8788
a fragment to be evaluated
@@ -161,8 +162,8 @@ def find_melodic_interval(
161162
:param melodic_line:
162163
melodic line containing the event
163164
:param shift:
164-
-1 if interval of arrival in the event is needed or
165-
1 if interval of departure from the event is needed
165+
-1 if interval of arrival in the event is needed
166+
or 1 if interval of departure from the event is needed
166167
:return:
167168
size of melodic interval (in semitones)
168169
"""
@@ -188,13 +189,13 @@ def evaluate_dissonances_preparation_and_resolution(
188189
:param fragment:
189190
a fragment to be evaluated
190191
:param n_semitones_to_pt_and_ngh_preparation_penalty:
191-
mapping from melodic interval size in semitones to a penalty for moving by this interval
192+
mapping from melodic interval size (in semitones) to a penalty for moving by this interval
192193
to a dissonance considered to be an analogue of passing tone or neighbor dissonance
193194
:param n_semitones_to_pt_and_ngh_resolution_penalty:
194-
mapping from melodic interval size in semitones to a penalty for moving by this interval
195+
mapping from melodic interval size (in semitones) to a penalty for moving by this interval
195196
from a dissonance considered to be an analogue of passing tone or neighbor dissonance
196197
:param n_semitones_to_suspension_resolution_penalty:
197-
mapping from melodic interval size in semitones to a penalty for moving by this interval
198+
mapping from melodic interval size (in semitones) to a penalty for moving by this interval
198199
from a dissonance considered to be an analogue of suspended dissonance
199200
:return:
200201
average over all vertical intervals penalty for their preparation and resolution
@@ -278,8 +279,8 @@ def find_sonority_type(
278279
:param regular_positions:
279280
parameters of regular positions (for example, downbeats or relatively strong beats)
280281
:param ad_hoc_positions:
281-
parameters of ad hoc positions which appear just once (for example, the beginning of
282-
the fragment or the 11th reference beat)
282+
parameters of ad hoc positions which appear just once
283+
(for example, the beginning of the fragment or the 11th reference beat)
283284
:param n_beats:
284285
total duration of a fragment (in reference beats)
285286
:return:
@@ -316,8 +317,8 @@ def evaluate_harmony_dynamic_by_positions(
316317
:param regular_positions:
317318
parameters of regular positions (for example, downbeats or relatively strong beats)
318319
:param ad_hoc_positions:
319-
parameters of ad hoc positions which appear just once (for example, the beginning of
320-
the fragment or the 11th reference beat)
320+
parameters of ad hoc positions which appear just once
321+
(for example, the beginning of the fragment or the 11th reference beat)
321322
:param ranges:
322323
mapping from position type to minimum and maximum desired levels of harmonic stability
323324
:param n_semitones_to_stability:
@@ -441,7 +442,7 @@ def evaluate_motion_to_perfect_consonances(fragment: Fragment) -> float:
441442
:param fragment:
442443
a fragment to be evaluated
443444
:return:
444-
minus one multiplied by fraction of sonorities with wrong motion to perfect consonances
445+
minus one multiplied by average over sonorities number of violations
445446
"""
446447
score = 0
447448
previous_events = [None for _ in fragment.melodic_lines]
@@ -450,6 +451,7 @@ def evaluate_motion_to_perfect_consonances(fragment: Fragment) -> float:
450451
for line_index, (previous_event, current_event) in enumerate(zipped):
451452
if previous_event != current_event:
452453
previous_events[line_index] = previous_event
454+
453455
pairs = itertools.combinations(sonority.non_pause_events, 2)
454456
for first_event, second_event in pairs:
455457
n_semitones = first_event.position_in_semitones - second_event.position_in_semitones
@@ -458,29 +460,19 @@ def evaluate_motion_to_perfect_consonances(fragment: Fragment) -> float:
458460
if interval_type != IntervalTypes.PERFECT_CONSONANCE:
459461
continue
460462

461-
first_previous_event = previous_events[first_event.line_index]
462-
first_event_continues = (
463-
first_previous_event is None
464-
or (
465-
first_previous_event.start_time + first_previous_event.duration
466-
< sonority.start_time
467-
)
468-
)
469-
second_previous_event = previous_events[second_event.line_index]
470-
second_event_continues = (
471-
second_previous_event is None
472-
or (
473-
second_previous_event.start_time + second_previous_event.duration
474-
< sonority.start_time
475-
)
476-
)
463+
first_event_continues = first_event.start_time < sonority.start_time
464+
second_event_continues = second_event.start_time < sonority.start_time
477465
if first_event_continues and second_event_continues:
478466
continue
479467

480468
if first_event_continues:
481469
first_previous_event = first_event
482-
elif second_event_continues:
470+
else:
471+
first_previous_event = previous_events[first_event.line_index]
472+
if second_event_continues:
483473
second_previous_event = second_event
474+
else:
475+
second_previous_event = previous_events[second_event.line_index]
484476

485477
any_previous_pauses = (
486478
first_previous_event.pitch_class == 'pause'
@@ -576,8 +568,8 @@ def evaluate_pitch_class_distribution_among_lines(
576568
"""
577569
Evaluate that pitch classes are distributed among lines according to user specifications.
578570
579-
For example, it is possible to use some pitch classes only in melody and the remaining
580-
pitch classes only in accompaniment.
571+
For example, it is possible to use some pitch classes only in the melody
572+
and the remaining pitch classes only in the accompaniment.
581573
582574
:param fragment:
583575
a fragment to be evaluated
@@ -612,12 +604,12 @@ def evaluate_presence_of_vertical_intervals(
612604
:param intervals:
613605
intervals (in semitones) from top to bottom
614606
:param min_n_weighted_occurrences:
615-
minimal sum of weights of intervallic sonorities occurrences
607+
minimum sum of weights of intervallic sonorities occurrences
616608
:param regular_positions:
617609
parameters of regular positions (for example, downbeats or relatively strong beats)
618610
:param ad_hoc_positions:
619-
parameters of ad hoc positions which appear just once (for example, the beginning of
620-
the fragment or the 11th reference beat)
611+
parameters of ad hoc positions which appear just once
612+
(for example, the beginning of the fragment or the 11th reference beat)
621613
:param position_weights:
622614
mapping from position name to its weight
623615
:return:

tests/scoring_functions/test_harmony.py

+29
Original file line numberDiff line numberDiff line change
@@ -1049,6 +1049,35 @@ def test_evaluate_local_diatonicity_at_all_lines_level(
10491049
# `expected`
10501050
-0.25
10511051
),
1052+
(
1053+
# `fragment`
1054+
Fragment(
1055+
temporal_content=[
1056+
[[2.0, 1.0, 0.5, 0.5]],
1057+
[[2.0, 1.0, 0.5, 0.5]],
1058+
[[1.0, 1.0, 1.0, 1.0]],
1059+
],
1060+
grouped_tone_row_instances=[
1061+
[ToneRowInstance(['G', 'C', 'C#', 'D', 'D#', 'B', 'F', 'F#', 'E', 'A', 'A#', 'A'])],
1062+
],
1063+
grouped_mutable_pauses_indices=[[]],
1064+
grouped_immutable_pauses_indices=[[]],
1065+
n_beats=4,
1066+
meter_numerator=4,
1067+
meter_denominator=4,
1068+
measure_durations_by_n_events=MEASURE_DURATIONS_BY_N_EVENTS,
1069+
line_ids=[1, 2, 3],
1070+
upper_line_highest_position=55,
1071+
upper_line_lowest_position=41,
1072+
tone_row_len=12,
1073+
group_index_to_line_indices={0: [0, 1, 2]},
1074+
mutable_temporal_content_indices=[0, 1, 2],
1075+
mutable_independent_tone_row_instances_indices=[(0, 0)],
1076+
mutable_dependent_tone_row_instances_indices=[]
1077+
),
1078+
# `expected`
1079+
-0.5
1080+
),
10521081
]
10531082
)
10541083
def test_evaluate_motion_to_perfect_consonances(fragment: Fragment, expected: float) -> None:

0 commit comments

Comments
 (0)