Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added patel features (nPVI and MIV) as custom_features #60

Draft
wants to merge 16 commits into
base: develop
Choose a base branch
from
Binary file removed .DS_Store
Binary file not shown.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,5 @@ docs/source/__tutorial_cache
docs/source/musif.log.2023-01-31
docs/source/.ipynb_checkpoints
dist
.DS_Store
.pdm-python
Binary file removed docs/.DS_Store
Binary file not shown.
4 changes: 4 additions & 0 deletions musif/extract/extract.py
Original file line number Diff line number Diff line change
Expand Up @@ -762,3 +762,7 @@ def _update_score_module_features(
raise FeatureError(
f"In {score_name} while computing {module.__name__}"
) from e
# except Exception:
# score_name = score_data["file"]
# print(f"{score_name} gives trouble, going to the next one")
# pass
2 changes: 1 addition & 1 deletion musif/extract/features/ambitus/handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ def update_score_objects(
parts_data = _filter_parts_data(parts_data, cfg.parts_filter)
if len(parts_data) == 0:
return

for part_data, part_features in zip(parts_data, parts_features):
part = part_data[DATA_PART_ABBREVIATION]
for feature_name in SCORE_FEATURES:
Expand Down
3 changes: 3 additions & 0 deletions musif/extract/features/melody/handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
get_sound_prefix,
)

import pandas as pd

from .constants import *


Expand All @@ -37,6 +39,7 @@ def update_part_objects(
part_features.update(get_interval_stats_features(intervals))



def update_score_objects(
score_data: dict,
parts_data: List[dict],
Expand Down
1 change: 1 addition & 0 deletions musif/extract/features/patel/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .constants import *
3 changes: 3 additions & 0 deletions musif/extract/features/patel/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
MIV = "MIV"
NPVI="nPVI"

58 changes: 58 additions & 0 deletions musif/extract/features/patel/handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
from music21.analysis.patel import nPVI, melodicIntervalVariability

import musif.extract.constants as C
from musif.extract.features.prefix import get_part_feature
from musif.config import ExtractConfiguration
from typing import List


from musif.extract.features.patel.constants import *


def update_part_objects(
score_data: dict, part_data: dict, cfg: ExtractConfiguration, part_features: dict
):
# "update_part_objects from module inside a package given its parent package (score)!"

for measure in part_data["measures"]:
try:
_nPVI = nPVI(measure)
_melodicIntervalVariability = melodicIntervalVariability(measure)
except Exception:
_nPVI = 0.0
_melodicIntervalVariability = 0.0

# print('_nPVI: ', _nPVI)
part_features['NPVI'] = _nPVI

# print('_melodicIntervalVariability: ', _melodicIntervalVariability)
part_features['MIV'] = _melodicIntervalVariability


def update_score_objects(
score_data: dict,
parts_data: List[dict],
cfg: ExtractConfiguration,
parts_features: List[dict],
score_features: dict,
):
# We need to add the data to score_features,
# the dictionary where all final info is stored.
# Otherwise it will not be reflected in the final dataframe.
# "Updating stuffs from module inside a package given its parent package (part)!"

features = {}
for part_data, part_features in zip(parts_data, parts_features):
part_abbreviation = part_data[C.DATA_PART_ABBREVIATION]
# get NPVI
feature_name = get_part_feature(part_abbreviation, NPVI)
features[feature_name] = part_features['NPVI']
# get MIV
feature_name = get_part_feature(part_abbreviation, MIV)
features[feature_name] = part_features['MIV']

score_features.update(features)




75 changes: 75 additions & 0 deletions musif/extract/features/rhythm/handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ def update_part_objects(
notes_duration = [
i for i in notes_duration if i != 0.0
] # remove notes with duration equal to 0


part_features.update(
{
Expand Down Expand Up @@ -164,3 +165,77 @@ def update_score_objects(
score_features.update(features)


def get_motion_features(part_data) -> dict:
notes_midi = []
notes_duration = []
for note in part_data["notes_and_rests"]:
if hasattr(note, "pitch"):
notes_midi.append(note.pitch.midi)
notes_duration.append(note.duration.quarterLength)

notes_midi = np.asarray(notes_midi)
notes_duration = np.asarray(notes_duration)

if len(notes_midi) == 0:
return {
SPEED_AVG_ABS: 0,
ACCELERATION_AVG_ABS: 0,
ASCENDENT_AVERAGE: 0,
DESCENDENT_AVERAGE: 0,
ASCENDENT_PROPORTION: 0,
DESCENDENT_PROPORTION: 0
}

step = 0.125
midis_raw = np.repeat(notes_midi, [i / step for i in notes_duration], axis=0)
spe_raw = np.diff(midis_raw) / step
acc_raw = np.diff(spe_raw) / step

# Absolute means of speed and acceleration
spe_avg_abs = np.mean(abs(spe_raw))
acc_avg_abs = np.mean(abs(acc_raw))

# Rolling mean to smooth the midis by +-1 compasses -- not required for
# statistics based on means but important for detecting increasing sequences
# with a tolerance.
measure = 4
midis_smo_series = pd.Series(midis_raw)
midis_smo = [
np.mean(i.to_list())
for i in midis_smo_series.rolling(2 * measure + 1, center=True)
]

# midis_smo = np.rollmean(midis_raw, k = 2 * compass + 1, align = "center")

# spe_smo = np.diff(midis_smo) / step
# acc_smo = np.diff(spe_smo) / step

# Prolonged ascent/descent chunks in smoothed midis of the aria (allows for
# small violations in the form of decrements/increments that do not
# decrease/increase the rolling mean).

dife = np.diff(midis_smo)

asc = [(k, sum(1 for i in g)) for k, g in groupby(dife > 0)]
dsc = [(k, sum(1 for i in g)) for k, g in groupby(dife < 0)]

asc = [i for b, i in asc if b]
dsc = [i for b, i in dsc if b]

# Average length of ascent/descent chunks of the aria
asc_avg = mean(asc) if asc else np.nan
dsc_avg = mean(dsc) if dsc else np.nan

# Proportion of ascent/descent chunks over the total of the aria
asc_prp = sum(asc) / (len(dife) - 1) if asc else np.nan
dsc_prp = sum(dsc) / (len(dife) - 1) if dsc else np.nan

return {
SPEED_AVG_ABS: spe_avg_abs,
ACCELERATION_AVG_ABS: acc_avg_abs,
ASCENDENT_AVERAGE: asc_avg,
DESCENDENT_AVERAGE: dsc_avg,
ASCENDENT_PROPORTION: asc_prp,
DESCENDENT_PROPORTION: dsc_prp
}

9 changes: 6 additions & 3 deletions musif/extract/features/scale_relative/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,12 @@ def get_emphasised_scale_degrees_relative(
notes_list: list, score_data: dict
) -> List[list]:
harmonic_analysis, tonality = extract_harmony(score_data)
if harmonic_analysis.size == 0:
return None

try:
if harmonic_analysis.size == 0:
return None
except: # noqa: E722
print('harmonic_analysis has NoneType')
return None
measures = [
m for p in score_data[C.DATA_SCORE].parts for m in
p.getElementsByClass(Measure)
Expand Down
12 changes: 7 additions & 5 deletions musif/musicxml/key.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,13 @@ def get_key_and_mode(score: Score) -> Tuple[Key, str, str]:
score : Score
Music21 score to take the info from
"""

score_key = score.analyze("key")
mode, tonality = get_name_from_key(score_key)
return score_key, tonality, mode

try:
score_key = score.analyze("key")
mode, tonality = get_name_from_key(score_key)
return score_key, tonality, mode
except Exception as e:
print(f'{e}')
return 'NA', 'NA', 'NA'

def get_name_from_key(score_key: Key) -> Tuple[str, str]:
"""
Expand Down