Source code for melody_features.feature_definitions.lexical_diversity

"""Melody-only lexical diversity feature definitions."""

import warnings
from typing import Dict

from ..feature_decorators import both, fantastic, lexical_diversity
from ..melody_tokenizer import FantasticTokenizer
from ..ngram_counter import NGramCounter
from ..core.representations import Melody

DEFAULT_MAX_NGRAM_ORDER = 5


__all__ = [
    "get_mtype_features",
    "get_lexical_diversity_features",
]


[docs] @fantastic @both @lexical_diversity def get_mtype_features(melody: Melody, phrase_gap: float, max_ngram_order: int) -> dict: """Various n-gram statistics for the melody. Parameters ---------- melody : Melody The melody to analyze as a Melody object Returns ------- dict Dictionary containing complexity measures averaged across n-gram lengths """ # Initialize tokenizer and get M-type tokens tokenizer = FantasticTokenizer() # Segment the melody first, using quarters as the time unit segments = tokenizer.segment_melody(melody, phrase_gap=phrase_gap, units="quarters") # Get tokens for each segment all_tokens = [] for segment in segments: segment_tokens = tokenizer.tokenize_melody( segment.pitches, segment.starts, segment.ends ) all_tokens.extend(segment_tokens) # Create a fresh counter for this melody ngram_counter = NGramCounter() ngram_counter.ngram_counts = {} # Explicitly reset the counter ngram_counter.count_ngrams(all_tokens, max_order=max_ngram_order) # Calculate complexity measures for each n-gram length mtype_features = {} # Initialize all features to NaN mtype_features["yules_k"] = float("nan") mtype_features["simpsons_d"] = float("nan") mtype_features["sichels_s"] = float("nan") mtype_features["honores_h"] = float("nan") mtype_features["mean_entropy"] = float("nan") mtype_features["mean_productivity"] = float("nan") # Try to calculate each feature individually if ngram_counter.ngram_counts: try: mtype_features["yules_k"] = ngram_counter.yules_k except Exception as e: warnings.warn(f"Error calculating Yule's K: {str(e)}") try: mtype_features["simpsons_d"] = ngram_counter.simpsons_d except Exception as e: warnings.warn(f"Error calculating Simpson's D: {str(e)}") try: mtype_features["sichels_s"] = ngram_counter.sichels_s except Exception as e: warnings.warn(f"Error calculating Sichel's S: {str(e)}") try: mtype_features["honores_h"] = ngram_counter.honores_h except Exception as e: warnings.warn(f"Error calculating Honoré's H: {str(e)}") try: mtype_features["mean_entropy"] = ngram_counter.mean_entropy except Exception as e: warnings.warn(f"Error calculating mean entropy: {str(e)}") try: mtype_features["mean_productivity"] = ngram_counter.mean_productivity except Exception as e: warnings.warn(f"Error calculating mean productivity: {str(e)}") return mtype_features
[docs] def get_lexical_diversity_features( melody: Melody, phrase_gap: float = 1.5, max_ngram_order: int = DEFAULT_MAX_NGRAM_ORDER, ) -> Dict: """Collect lexical-diversity (m-type) features for a melody. Corpus-dependent lexical-diversity features (e.g. TF/DF correlations) are computed in :func:`get_corpus_features` instead. """ return get_mtype_features( melody, phrase_gap=phrase_gap, max_ngram_order=max_ngram_order )