Source code for musif.extract.features.lyrics.handler
from typing import List
from music21.note import Note
from numpy import mean
from musif.common._constants import VOICE_FAMILY
from musif.config import Configuration
from musif.extract.common import _filter_parts_data
from musif.extract.constants import DATA_FAMILY, DATA_PART_ABBREVIATION
from musif.extract.features.core.handler import DATA_LYRICS, DATA_NOTES
from musif.extract.features.prefix import get_part_feature, get_score_feature
from .constants import *
from musif.extract.features.core.constants import (
DATA_MEASURES,
DATA_SOUNDING_MEASURES,
NUM_NOTES,
)
[docs]def update_part_objects(
score_data: dict, part_data: dict, cfg: Configuration, part_features: dict
):
notes = part_data[DATA_NOTES]
lyrics = part_data[DATA_LYRICS]
if part_data[DATA_FAMILY] == VOICE_FAMILY:
syllabic_ratio = get_syllabic_ratio(notes, lyrics)
voice_reg = get_voice_reg(notes)
voice_presence = len(part_data[DATA_SOUNDING_MEASURES]) / len(
part_data[DATA_MEASURES]
)
part_features.update(
{
SYLLABLES: len(lyrics),
SYLLABIC_RATIO: syllabic_ratio,
VOICE_REG: voice_reg,
VOICE_PRESENCE: voice_presence,
}
)
[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_data) == 0:
return
voice_parts_data = [
part_data for part_data in parts_data if part_data[DATA_FAMILY] == VOICE_FAMILY
]
features = {}
if voice_parts_data:
for part_data in voice_parts_data:
part = part_data[DATA_PART_ABBREVIATION]
features[get_part_feature(part, NUM_NOTES)] = len(part_data[DATA_NOTES])
features[get_part_feature(part, SYLLABIC_RATIO)] = get_syllabic_ratio(
part_data[DATA_NOTES], part_data[DATA_LYRICS]
)
features[get_part_feature(part, SYLLABLES)] = len(part_data[DATA_LYRICS])
features[get_part_feature(part, VOICE_PRESENCE)] = len(
part_data[DATA_SOUNDING_MEASURES]
) / len(part_data[DATA_MEASURES])
features[get_part_feature(part, SYLLABIC_RATIO)] = get_syllabic_ratio(
part_data[DATA_NOTES], part_data[DATA_LYRICS]
)
notes = [
note for part_data in voice_parts_data for note in part_data[DATA_NOTES]
]
lyrics = [
lyrics
for part_data in voice_parts_data
for lyrics in part_data[DATA_LYRICS]
]
features[get_score_feature(SYLLABLES)] = len(lyrics)
features[get_score_feature(SYLLABIC_RATIO)] = get_syllabic_ratio(notes, lyrics)
features[get_score_feature(VOICE_REG)] = get_voice_reg(notes)
return score_features.update(features)
[docs]def get_syllabic_ratio(notes: List[Note], lyrics: List[str]) -> float:
if lyrics is None or len(lyrics) == 0:
return 0.0
return len(notes) / len(lyrics)
[docs]def get_voice_reg(notes: List[Note]) -> float:
if notes:
last_note = (
notes[-1].notes[0].pitch.midi if notes[-1].isChord else notes[-1].pitch.midi
)
# If we wave 2 or more notes at once, we just take the lowest one
distances = [
note[0].pitch.midi if note.isChord else (note.pitch.midi - last_note)
for note in notes
]
return mean(distances)
else:
return "NA"