from copy import deepcopy
from statistics import mean
from typing import List
from xml.dom.minidom import Element
from musif.config import Configuration
from musif.extract.features.prefix import get_part_feature, get_score_feature
from musif.extract.utils import _get_beat_position
from musif.logs import lwarn, pwarn
from musif.musicxml.tempo import get_number_of_beats
from ...constants import DATA_PART_ABBREVIATION
from .constants import *
[docs]def update_part_objects(
score_data: dict, part_data: dict, cfg: Configuration, part_features: dict
):
dynamics = []
beats_section = 0
dyn_mean_weighted = 0
total_beats = 0
total_sounding_beats = 0
dyn_grad = 0
last_dyn = 0
number_of_beats = 1
name = ""
old_beat = 0
dyn = False
first_silence = False
beats_timesignature = get_number_of_beats("4/4")
for measure in part_data["measures"]:
measure_elements = list(measure.elements)
for element in measure_elements:
if (
element.classes[0] == DYNAMIC
and not element.value == "sf"
or (
element.classes[0] == TEXTEXPRESSION
and element.content == ("sotto voce assai" and "dolce")
)
):
if element.classes[0] == TEXTEXPRESSION:
name += element.content
else:
if element.value in DYNAMIC_FIRST_WORD:
name = element.value + name
elif name.strip() in DYNAMIC_LAST_WORD:
name = element.value + name
else:
name += element.value
dyn = True
elif element.classes[0] == TEXTEXPRESSION:
if element.content in DYNAMIC_LAST_WORD:
name += " " + element.content
elif element.content in DYNAMIC_FIRST_WORD:
name = element.content + " "
elif element.classes[0] == TIMESIGNATURE:
beats_timesignature = element.beatCount
number_of_beats = get_number_of_beats(element.ratioString)
elif element.classes[0] == REST and not first_silence:
if element.duration.quarterLength >= number_of_beats:
first_silence = True
new_dyn = 0
dynamics.append(new_dyn)
position = _get_beat_position(
beats_timesignature, number_of_beats, element.beat
)
old_beat = position - _get_beat_position(
beats_timesignature, number_of_beats, 1
)
dyn_mean_weighted += (beats_section + old_beat) * last_dyn
beats_section, dyn_grad, last_dyn = calculate_gradient(
beats_section, dyn_grad, last_dyn, old_beat, new_dyn
)
name = ""
if dyn:
if name in ["fp", "pf"]:
new_dyn = get_dynamic_numeric(name[0])
if new_dyn != last_dyn:
(
beats_section,
dyn_grad,
last_dyn,
first_silence,
dyn_mean_weighted,
) = calculate_dynamics(
dynamics,
beats_section,
dyn_mean_weighted,
dyn_grad,
last_dyn,
number_of_beats,
element,
beats_timesignature,
new_dyn,
)
name = name[1]
if name == "other-dynamics":
file_name = score_data["file"]
lwarn(
f"fsf found in measure {measure.measureNumber} in file {file_name}"
)
name = "f"
new_dyn = get_dynamic_numeric(name.strip())
if new_dyn != last_dyn:
(
beats_section,
dyn_grad,
last_dyn,
first_silence,
dyn_mean_weighted,
) = calculate_dynamics(
dynamics,
beats_section,
dyn_mean_weighted,
dyn_grad,
last_dyn,
number_of_beats,
element,
beats_timesignature,
new_dyn,
)
name = ""
dyn = False
beats_section += number_of_beats
total_beats += number_of_beats
if measure in part_data["sounding_measures"]:
total_sounding_beats += number_of_beats
dyn_mean_weighted += beats_section * dynamics[-1] if len(dynamics) != 0 else 0
part_features.update(
{
DYNMEAN: mean(dynamics) if len(dynamics) != 0 else 0,
DYNMEAN_WEIGHTED: dyn_mean_weighted / total_sounding_beats
if total_sounding_beats != 0
else 0,
DYNGRAD: float(dyn_grad / (len(dynamics) - 1))
if (len(dynamics) - 1) > 0
else 0,
DYNABRUPTNESS: float(dyn_grad / total_sounding_beats)
if total_sounding_beats != 0
else 0,
}
)
[docs]def update_score_objects(
score_data: dict,
parts_data: List[dict],
cfg: Configuration,
parts_features: List[dict],
score_features: dict,
):
features = {}
dyn_means = []
dyn_means_weighted = []
dyn_grads = []
dyn_abruptness = []
for part_data, part_features in zip(parts_data, parts_features):
part = part_data[DATA_PART_ABBREVIATION]
features[get_part_feature(part, DYNMEAN)] = part_features[DYNMEAN]
dyn_means.append(part_features[DYNMEAN])
features[get_part_feature(part, DYNMEAN_WEIGHTED)] = part_features[
DYNMEAN_WEIGHTED
]
dyn_means_weighted.append(part_features[DYNMEAN_WEIGHTED])
features[get_part_feature(part, DYNGRAD)] = part_features[DYNGRAD]
dyn_grads.append(part_features[DYNGRAD])
features[get_part_feature(part, DYNABRUPTNESS)] = part_features[DYNABRUPTNESS]
dyn_abruptness.append(part_features[DYNABRUPTNESS])
dyn_means = [i for i in dyn_means if i != 0.0]
dyn_means_weighted = [i for i in dyn_means_weighted if i != 0.0]
dyn_grads = [i for i in dyn_grads if i != 0.0]
dyn_abruptness = [i for i in dyn_abruptness if i != 0.0]
features.update(
{
get_score_feature(DYNMEAN): mean(dyn_means) if dyn_means else 0,
get_score_feature(DYNMEAN_WEIGHTED): mean(dyn_means_weighted)
if dyn_means_weighted
else 0,
get_score_feature(DYNGRAD): mean(dyn_grads) if dyn_grads else 0,
get_score_feature(DYNABRUPTNESS): mean(dyn_abruptness)
if dyn_abruptness
else 0,
}
)
score_features.update(features)
[docs]def calculate_dynamics(
dynamics,
beats_section,
dyn_mean_weighted,
dyn_grad,
last_dyn,
number_of_beats,
element,
beats_timesignature,
new_dyn,
):
old_beat = calculate_position(number_of_beats, element, beats_timesignature)
dyn_mean_weighted += (beats_section + old_beat) * last_dyn
dynamics.append(new_dyn)
beats_section, dyn_grad, last_dyn = calculate_gradient(
beats_section, dyn_grad, last_dyn, old_beat, new_dyn
)
first_silence = False
return beats_section, dyn_grad, last_dyn, first_silence, dyn_mean_weighted
[docs]def calculate_position(number_of_beats, element, beats_timesignature):
sub_beat = element.elements[0].beat if element.isStream else element.beat
position = _get_beat_position(beats_timesignature, number_of_beats, sub_beat)
old_beat = position - _get_beat_position(beats_timesignature, number_of_beats, 1)
return old_beat # - sum([i.duration.quarterLength for i in bar_section.elements if i.classes[0] == REST]) #all silences in the measure
[docs]def calculate_gradient(beats_section, dyn_grad, last_dyn, old_beat, new_dyn):
if (beats_section + old_beat) > 0:
dyn_grad += abs(new_dyn - last_dyn) / (beats_section + old_beat)
last_dyn = new_dyn
beats_section = -old_beat # number of beats that has old dynamic
return beats_section, dyn_grad, last_dyn
[docs]def get_dynamic_numeric(value):
if value in DYNAMIC_VALUES:
return DYNAMIC_VALUES.get(value)
else:
pwarn(f"Dynamic value was not identified: {value}")
return 40 # average value in case of error