Source code for musif.extract.features.density.handler

from statistics import mean
from typing import List, Tuple

from musif.common.sort import sort_dict
from musif.config import Configuration
from musif.extract.common import _filter_parts_data, _part_matches_filter
from musif.extract.constants import (
    DATA_FAMILY_ABBREVIATION,
    DATA_PART_ABBREVIATION,
    DATA_SOUND_ABBREVIATION,
)
from musif.extract.features.core.handler import (
    DATA_MEASURES,
    DATA_NOTES,
    DATA_SOUNDING_MEASURES,
)
from musif.extract.features.prefix import (
    get_family_feature,
    get_family_prefix,
    get_part_feature,
    get_part_prefix,
    get_score_feature,
    get_score_prefix,
    get_sound_feature,
    get_sound_prefix,
)
from musif.extract.basic_modules.scoring.constants import NUMBER_OF_FILTERED_PARTS
from musif.extract.features.tempo.constants import (
    NUMBER_OF_BEATS,
    TIME_SIGNATURE,
    TIME_SIGNATURES,
)
from musif.extract.utils import (
    _get_timesignature_periods,
    _calculate_total_number_of_beats,
)
from musif.logs import lerr
from musif.musicxml import Measure, Note, Part
from musif.musicxml.tempo import get_number_of_beats
from .constants import *
from musif.extract.features.core.constants import (
    NUM_MEASURES,
    NUM_NOTES,
    NUM_SOUNDING_MEASURES,
)
from musif.cache import isinstance


[docs]def update_part_objects( score_data: dict, part_data: dict, cfg: Configuration, part_features: dict ): if not _part_matches_filter(part_data[DATA_PART_ABBREVIATION], cfg.parts_filter): return {} notes = part_data[DATA_NOTES] sounding_measures = part_data[DATA_SOUNDING_MEASURES] measures = part_data[DATA_MEASURES] time_signature = score_data[TIME_SIGNATURE].split(",") time_signatures = score_data[TIME_SIGNATURES] if len(time_signature) == 1: part_features.update( { SOUNDING_DENSITY: len(notes) / (get_number_of_beats(time_signature[0]) * len(sounding_measures)) if len(sounding_measures) > 0 else 0, DENSITY: len(notes) / (get_number_of_beats(time_signature[0]) * len(measures)) if len(measures) > 0 else 0, } ) else: periods = _get_timesignature_periods(time_signatures) sounding_measures = range( 0, len(sounding_measures) ) ## TODO: cuando haya repeticiones, revisar esto. Lo hice por un error en la numeracion cuando hay 70x1 (celdillas) sounding_time_signatures = [time_signatures[i] for i in sounding_measures] sounding_periods = _get_timesignature_periods(sounding_time_signatures) part_features.update( { SOUNDING_DENSITY: len(notes) / _calculate_total_number_of_beats(time_signatures, sounding_periods) # the above is 0 (cannot divide by 0) if len(sounding_measures) == 1 # shouldn't we use sounding_time_signature in place of time_signature? if len(sounding_measures) > 0 else 0, DENSITY: len(notes) / _calculate_total_number_of_beats(time_signatures, periods) if len(measures) > 0 else 0, } )
[docs]def update_score_objects( score_data: dict, parts_data: List[dict], cfg: Configuration, parts_features: List[dict], score_features: dict, ): parts_data = _filter_parts_data(parts_data, cfg.parts_filter) if len(parts_features) == 0: return time_signature = score_data[TIME_SIGNATURE].split(",") number_of_beats = get_number_of_beats(time_signature[0]) features = {} for i, part_data in enumerate(parts_data): part_abbreviation = part_data[DATA_PART_ABBREVIATION.lower()] feature_name = get_part_feature(part_abbreviation, SOUNDING_DENSITY) if feature_name in features: features[feature_name] = mean( [features[feature_name], parts_features[i][SOUNDING_DENSITY]] ) else: features[feature_name] = parts_features[i][SOUNDING_DENSITY] feature_name = get_part_feature(part_abbreviation, DENSITY) if feature_name in features: features[feature_name] = mean( [features[feature_name], parts_features[i][DENSITY]] ) else: features[feature_name] = parts_features[i][DENSITY] sound_abbreviations = { part_data[DATA_SOUND_ABBREVIATION] for part_data in parts_data } for sound in sound_abbreviations: num_parts = score_data[get_sound_feature(sound, NUMBER_OF_FILTERED_PARTS)] num_measures = score_features[NUM_MEASURES] * num_parts num_sounding_measures = score_features[ get_sound_feature(sound, NUM_SOUNDING_MEASURES) ] num_notes = score_features[get_sound_feature(sound, NUM_NOTES)] features[get_sound_feature(sound, SOUNDING_DENSITY)] = ( num_notes / number_of_beats / num_sounding_measures if num_sounding_measures != 0 else 0 ) features[get_sound_feature(sound, DENSITY)] = ( num_notes / number_of_beats / num_measures if num_measures != 0 else 0 ) family_abbreviations = { part_data[DATA_FAMILY_ABBREVIATION] for part_data in parts_data } for family in family_abbreviations: num_parts = score_data[get_family_feature(family, NUMBER_OF_FILTERED_PARTS)] num_measures = score_features[NUM_MEASURES] * num_parts num_sounding_measures = score_features[ get_family_feature(family, NUM_SOUNDING_MEASURES) ] num_notes = score_features[get_family_feature(family, NUM_NOTES)] features[get_family_feature(family, SOUNDING_DENSITY)] = ( num_notes / number_of_beats / num_sounding_measures if num_sounding_measures != 0 else 0 ) features[get_family_feature(family, DENSITY)] = ( num_notes / number_of_beats / num_measures if num_measures != 0 else 0 ) num_parts = score_data[get_score_feature(NUMBER_OF_FILTERED_PARTS)] num_measures = score_features[NUM_MEASURES] * num_parts num_sounding_measures = score_features[get_score_feature(NUM_SOUNDING_MEASURES)] num_notes = score_features[get_score_feature(NUM_NOTES)] features[get_score_feature(SOUNDING_DENSITY)] = ( num_notes / number_of_beats / num_sounding_measures if num_sounding_measures != 0 else 0 ) features[get_score_feature(DENSITY)] = ( num_notes / number_of_beats / num_measures if num_measures != 0 else 0 ) score_features.update(features)
[docs]def get_notes_and_measures( part: Part, ) -> Tuple[List[Note], List[Measure], List[Measure]]: notes = [] measures = list(part.measures(0, None)) sounding_measures = [] for measure in measures: if len(measure.notes) > 0: sounding_measures.append(measure) for note in measure.notes: set_ties(note, notes) return notes, sounding_measures, measures
[docs]def set_ties(subject, my_notes_list): """ This function converts tied notes into a unique note """ if subject.tie is None: my_notes_list.append(subject) return if subject.tie.type != "stop" and subject.tie.type != "continue": my_notes_list.append(subject) return if isinstance(my_notes_list[-1], Note): # sum tied notes' length my_notes_list[-1].duration.quarterLength += subject.duration.quarterLength return back_counter = -1 while isinstance(my_notes_list[back_counter], tuple): back_counter -= -1 else: my_notes_list[ back_counter ].duration.quarterLength += ( subject.duration.quarterLength ) # sum tied notes' length across measures
[docs]def calculate_densities(notes_list, measures_list, names_list, cfg: Configuration): density_list = [] try: for i, part in enumerate(names_list): density = round(notes_list[i] / measures_list[i], 3) density_list.append({f"{names_list[i]}": density}) density_dict = dict((key, d[key]) for d in density_list for key in d) density_sorting = cfg.scoring_order density_dict = sort_dict(density_dict, density_sorting) return density_dict except: lerr("Densities problem found: ", exc_info=True) return {}