Module music_df.time
A few functions for working with temporal/rhythmic features of music dataframes.
Functions
def appears_to_have_pickup_measure(music_df: pandas.DataFrame) ‑> bool-
Expand source code
def appears_to_have_pickup_measure(music_df: pd.DataFrame) -> bool: """ Caches a "appears_to_have_pickup_measure" boolean attribute in music_df.attrs. If If there is a time signature, and there is at least one bar, we return whether the first bar is shorter than the time signature: >>> df = pd.DataFrame( ... [ ... { ... "type": "time_signature", ... "onset": 0.0, ... "release": float("nan"), ... "other": {"numerator": 4, "denominator": 4}, ... }, ... {"type": "bar", "onset": 0.0, "release": 4.0}, ... {"type": "bar", "onset": 4.0, "release": 8.0}, ... ] ... ) >>> appears_to_have_pickup_measure(df) False >>> df = pd.DataFrame( ... [ ... { ... "type": "time_signature", ... "onset": 0.0, ... "release": float("nan"), ... "other": {"numerator": 4, "denominator": 4}, ... }, ... {"type": "bar", "onset": 0.0, "release": 2.0}, ... {"type": "bar", "onset": 2.0, "release": 6.0}, ... ] ... ) >>> appears_to_have_pickup_measure(df) True If there is no time signature, but there are at least two bars, return whether the first bar is shorter than the second bar. >>> df = pd.DataFrame( ... [ ... {"type": "bar", "onset": 0.0, "release": 4.0}, ... {"type": "bar", "onset": 4.0, "release": 8.0}, ... ] ... ) >>> appears_to_have_pickup_measure(df) False >>> df = pd.DataFrame( ... [ ... {"type": "bar", "onset": 0.0, "release": 4.0}, ... {"type": "bar", "onset": 4.0, "release": 12.0}, ... ] ... ) >>> appears_to_have_pickup_measure(df) True Otherwise, return False. >>> df = pd.DataFrame([{"type": "bar", "onset": 0.0, "release": 4.0}]) >>> appears_to_have_pickup_measure(df) False >>> df = pd.DataFrame() >>> appears_to_have_pickup_measure(df) False >>> df = pd.DataFrame( ... [ ... { ... "type": "time_signature", ... "onset": 0.0, ... "release": float("nan"), ... "other": {"numerator": 4, "denominator": 4}, ... } ... ] ... ) >>> appears_to_have_pickup_measure(df) False """ if "appears_to_have_pickup_measure" in music_df.attrs: return music_df.attrs["appears_to_have_pickup_measure"] def _sub(music_df): if not len(music_df): return False bar_mask = music_df.type == "bar" if bar_mask.sum() < 1: return False if isnan(music_df[bar_mask].iloc[0].release): music_df = add_bar_durs(music_df) first_bar = music_df[bar_mask].iloc[0] first_bar_dur = first_bar.release - first_bar.onset time_sig_mask = music_df.type == "time_signature" if ( not time_sig_mask.any() or (first_time_sig := music_df[time_sig_mask].iloc[0]).onset != 0 ): # Return whether first measure is shorter than second measure if bar_mask.sum() < 2: return False if isnan(music_df[bar_mask].iloc[1].release): music_df = add_bar_durs(music_df) second_bar = music_df[bar_mask].iloc[1] second_bar_dur = second_bar.release - second_bar.onset return first_bar_dur < second_bar_dur time_sig_dur = get_time_sig_dur(first_time_sig.other) return time_sig_dur > first_bar_dur out = _sub(music_df) music_df.attrs["appears_to_have_pickup_measure"] = out return outCaches a "appears_to_have_pickup_measure" boolean attribute in music_df.attrs.
If
If there is a time signature, and there is at least one bar, we return whether the first bar is shorter than the time signature:
>>> df = pd.DataFrame( ... [ ... { ... "type": "time_signature", ... "onset": 0.0, ... "release": float("nan"), ... "other": {"numerator": 4, "denominator": 4}, ... }, ... {"type": "bar", "onset": 0.0, "release": 4.0}, ... {"type": "bar", "onset": 4.0, "release": 8.0}, ... ] ... ) >>> appears_to_have_pickup_measure(df) False>>> df = pd.DataFrame( ... [ ... { ... "type": "time_signature", ... "onset": 0.0, ... "release": float("nan"), ... "other": {"numerator": 4, "denominator": 4}, ... }, ... {"type": "bar", "onset": 0.0, "release": 2.0}, ... {"type": "bar", "onset": 2.0, "release": 6.0}, ... ] ... ) >>> appears_to_have_pickup_measure(df) TrueIf there is no time signature, but there are at least two bars, return whether the first bar is shorter than the second bar.
>>> df = pd.DataFrame( ... [ ... {"type": "bar", "onset": 0.0, "release": 4.0}, ... {"type": "bar", "onset": 4.0, "release": 8.0}, ... ] ... ) >>> appears_to_have_pickup_measure(df) False>>> df = pd.DataFrame( ... [ ... {"type": "bar", "onset": 0.0, "release": 4.0}, ... {"type": "bar", "onset": 4.0, "release": 12.0}, ... ] ... ) >>> appears_to_have_pickup_measure(df) TrueOtherwise, return False.
>>> df = pd.DataFrame([{"type": "bar", "onset": 0.0, "release": 4.0}]) >>> appears_to_have_pickup_measure(df) False >>> df = pd.DataFrame() >>> appears_to_have_pickup_measure(df) False >>> df = pd.DataFrame( ... [ ... { ... "type": "time_signature", ... "onset": 0.0, ... "release": float("nan"), ... "other": {"numerator": 4, "denominator": 4}, ... } ... ] ... ) >>> appears_to_have_pickup_measure(df) False def get_time_sig_dur(time_sig: dict[str, int] | str)-
Expand source code
def get_time_sig_dur(time_sig: dict[str, int] | str): time_sig = _to_dict_if_necessary(time_sig) return time_sig["numerator"] * 4 / time_sig["denominator"] def merge_contiguous_durations(durs: Iterable[tuple[float, float]]) ‑> list[tuple[float, float]]-
Expand source code
def merge_contiguous_durations( durs: Iterable[tuple[float, float]], ) -> list[tuple[float, float]]: """ >>> merge_contiguous_durations([]) [] >>> merge_contiguous_durations([(0.0, 4.0)]) [(0.0, 4.0)] >>> merge_contiguous_durations([(0.0, 4.0), (4.0, 8.0)]) [(0.0, 8.0)] >>> merge_contiguous_durations([(0.0, 4.0), (5.0, 8.0)]) [(0.0, 4.0), (5.0, 8.0)] Overlapping durations are returned as-is. >>> merge_contiguous_durations([(0.0, 4.0), (3.0, 8.0)]) [(0.0, 4.0), (3.0, 8.0)] >>> merge_contiguous_durations([(0.0, 4.0), (5.0, 8.0), (8.0, 9.0), (9.01, 11.0)]) [(0.0, 4.0), (5.0, 9.0), (9.01, 11.0)] """ out = [] prev_onset, prev_release = None, None for onset, release in durs: if prev_release is None: prev_onset = onset elif prev_release is not None and onset != prev_release: out.append((prev_onset, prev_release)) prev_onset = onset prev_release = release if prev_release is not None: out.append((prev_onset, prev_release)) return out>>> merge_contiguous_durations([]) [] >>> merge_contiguous_durations([(0.0, 4.0)]) [(0.0, 4.0)] >>> merge_contiguous_durations([(0.0, 4.0), (4.0, 8.0)]) [(0.0, 8.0)] >>> merge_contiguous_durations([(0.0, 4.0), (5.0, 8.0)]) [(0.0, 4.0), (5.0, 8.0)]Overlapping durations are returned as-is.
>>> merge_contiguous_durations([(0.0, 4.0), (3.0, 8.0)]) [(0.0, 4.0), (3.0, 8.0)]>>> merge_contiguous_durations([(0.0, 4.0), (5.0, 8.0), (8.0, 9.0), (9.01, 11.0)]) [(0.0, 4.0), (5.0, 9.0), (9.01, 11.0)] def time_to_bar_number_and_offset(music_df: pandas.DataFrame, x: float | int | fractions.Fraction) ‑> tuple[int, float]-
Expand source code
def time_to_bar_number_and_offset( music_df: pd.DataFrame, x: float | Fraction | int ) -> tuple[int, float]: """ If there doesn't appear to be a pickup measure, the first measure is `1`. >>> df = pd.DataFrame( ... [ ... { ... "type": "time_signature", ... "onset": 0.0, ... "release": float("nan"), ... "other": {"numerator": 4, "denominator": 4}, ... }, ... {"type": "bar", "onset": 0.0, "release": 4.0}, ... {"type": "bar", "onset": 4.0, "release": 8.0}, ... ] ... ) >>> time_to_bar_number_and_offset(df, 0.0) (1, 0.0) >>> time_to_bar_number_and_offset(df, 3.0) (1, 3.0) >>> time_to_bar_number_and_offset(df, 4.0) (2, 0.0) If there does appear to be a pickup measure, the first measure is numbered 0. (So the first full measure is 1.) >>> df = pd.DataFrame( ... [ ... { ... "type": "time_signature", ... "onset": 0.0, ... "release": float("nan"), ... "other": {"numerator": 4, "denominator": 2}, ... }, ... {"type": "bar", "onset": 0.0, "release": 4.0}, ... {"type": "bar", "onset": 4.0, "release": 12.0}, ... ] ... ) >>> time_to_bar_number_and_offset(df, 0.0) (0, 0.0) >>> time_to_bar_number_and_offset(df, 3.0) (0, 3.0) >>> time_to_bar_number_and_offset(df, 4.0) (1, 0.0) """ bar_mask = music_df.type == "bar" assert bar_mask.any() bars = music_df[bar_mask].reset_index(drop=True) bar_i = get_index_to_item_leq(bars.onset, val=x) assert bar_i % 1 == 0 # type:ignore bar_i = int(bar_i) # type:ignore bar_number = bar_i if appears_to_have_pickup_measure(music_df) else bar_i + 1 bar_onset = float(bars.loc[bar_i, "onset"]) # type:ignore offset = float(x - bar_onset) return bar_number, offsetIf there doesn't appear to be a pickup measure, the first measure is
1.>>> df = pd.DataFrame( ... [ ... { ... "type": "time_signature", ... "onset": 0.0, ... "release": float("nan"), ... "other": {"numerator": 4, "denominator": 4}, ... }, ... {"type": "bar", "onset": 0.0, "release": 4.0}, ... {"type": "bar", "onset": 4.0, "release": 8.0}, ... ] ... ) >>> time_to_bar_number_and_offset(df, 0.0) (1, 0.0) >>> time_to_bar_number_and_offset(df, 3.0) (1, 3.0) >>> time_to_bar_number_and_offset(df, 4.0) (2, 0.0)If there does appear to be a pickup measure, the first measure is numbered 0. (So the first full measure is 1.)
>>> df = pd.DataFrame( ... [ ... { ... "type": "time_signature", ... "onset": 0.0, ... "release": float("nan"), ... "other": {"numerator": 4, "denominator": 2}, ... }, ... {"type": "bar", "onset": 0.0, "release": 4.0}, ... {"type": "bar", "onset": 4.0, "release": 12.0}, ... ] ... ) >>> time_to_bar_number_and_offset(df, 0.0) (0, 0.0) >>> time_to_bar_number_and_offset(df, 3.0) (0, 3.0) >>> time_to_bar_number_and_offset(df, 4.0) (1, 0.0)