Module music_df.humdrum_export.humdrum_export
Functions
def df2clef(df: pandas.DataFrame)-
Expand source code
def df2clef(df: pd.DataFrame): spines = df_to_spines(df) with _get_temp_paths(len(spines)) as paths: for path, spine in zip(paths, spines): _write_spine(spine, path) collated = collate_spines(paths) return collated def df2hum(df: pandas.DataFrame,
split_point: int = 60,
label_col: str | None = None,
label_mask_col: str | None = None,
label_color_col: str | None = None,
color_col: str | None = None,
color_mask_col: str | None = None,
color_mapping: Mapping[Any, str] | None = None,
color_transparency_col: str | None = None,
n_transparency_levels: int | None = None,
uncolored_val: str | None = None,
number_every_nth_note: int | None = None,
number_specified_notes: List[int] | None = None,
number_notes_offset: int = 0,
quantize: int | None = None) ‑> str-
Expand source code
def df2hum( df: pd.DataFrame, # n_clefs: int = 2, # TODO split_point: int = 60, label_col: t.Optional[str] = None, label_mask_col: t.Optional[str] = None, label_color_col: t.Optional[str] = None, color_col: t.Optional[str] = None, color_mask_col: t.Optional[str] = None, color_mapping: t.Optional[t.Mapping[t.Any, str]] = None, color_transparency_col: t.Optional[str] = None, n_transparency_levels: t.Optional[int] = None, uncolored_val: t.Optional[str] = None, number_every_nth_note: t.Optional[int] = None, number_specified_notes: t.Optional[t.List[int]] = None, number_notes_offset: int = 0, quantize: None | int = None, ) -> str: """ Args: df: pandas DataFrame containing a musical score (see `music_df` package). split_point: int at which to split output into different staves (for treble and bass clefs). Default 60. label_col: an optional column name. The contents of the columns will be used to label the notes. color_col: if provided, notes will be colored based on this column. The unique values in this column will be mapped to different colors, up to 7 different colors, at which point they will repeat in modular fashion. If there are values that shouldn't be colored (i.e., should be left black), this can be indicated with a boolean mask in the column pointed to by the `color_mask_col` argument. color_transparency_col: if provided, the transparency of notes will be indicated with this column. Must be scalar, transparency will be linearly interpolated from its low value to its high value. color_mapping: optional dictionary mapping values (from color_col) to colors. The colors can be indicated w/ hex colors (strings beginning with a "#" character). Missing colors are given default values. There can be at most 7 distinct colors. """ df = spell_df(df) if quantize: df = quantize_df(df, tpq=quantize) df = add_bar_durs(df) df = split_notes_at_barlines( df, min_overhang_dur=(1 / 16) if quantize is None else (1 / quantize) ) if number_every_nth_note: df["note_index"] = -1 df.loc[df.type == "note", "note_index"] = range( # type:ignore number_notes_offset, (df.type == "note").sum() + number_notes_offset, ) df["nth_note_labels"] = df.note_index.astype(str) df.loc[df["note_index"] % number_every_nth_note != 0, "nth_note_labels"] = "" if number_specified_notes: if "nth_note_labels" not in df.columns: df["nth_note_labels"] = "" offset_indices = [n + number_notes_offset for n in number_specified_notes] mask = df.index.isin(df[df.type == "note"].index[offset_indices]) df.loc[mask, "nth_note_labels"] = [str(n) for n in number_specified_notes] if label_mask_col is not None: # (Malcolm 2023-10-20) I'm not sure why we constrain label_color_col to have at # most one color. I think it is so that there is no risk of simultaneous # labels having conflicting colors (since we only have one label # per-time-point and only one color per-label) if label_color_col is not None: assert len(df.loc[df[label_mask_col], label_color_col].unique()) == 1 # TODO: (Malcolm 2023-10-20) what do we do if label_color_col is None? if color_col is not None: color_mapping_inst = ColorMapping( df=df, color_col=color_col, color_mask_col=color_mask_col, color_mapping=color_mapping, n_alpha_levels=n_transparency_levels, uncolored_val=uncolored_val, ) # internal_color_mapping, val_to_color_char = process_color_mapping( # df, color_col, color_mask_col, color_mapping # ) df = color_df(df, color_col, color_transparency_col, color_mapping_inst) else: color_mapping_inst = None postscript = _check_colors(df, color_mapping_inst, uncolored_val) # if the last item in the df is not a barline, there can be problems if # after we split the df by pitch, one or more of the dfs is empty in # the last measure (making the final barline seem 1 measure sooner # in those parts) if df.iloc[-1].type != "bar": last_bar_vals = {"type": ["bar"], "onset": [df.release.max()]} if label_mask_col is not None: last_bar_vals[label_mask_col] = False if color_mask_col is not None: last_bar_vals[color_mask_col] = False last_bar = pd.DataFrame(last_bar_vals) df = pd.concat([df, last_bar]) dfs = split_df_by_pitch(df, split_point) staves = [] for split_df in dfs: if label_mask_col is not None: assert label_color_col is not None assert ( len(split_df.loc[split_df[label_mask_col], label_color_col].unique()) == 1 ) spines = df_to_spines( split_df, label_col=label_col, label_mask_col=label_mask_col, label_color_col=label_color_col, nth_note_label_col=( "nth_note_labels" if (number_every_nth_note or number_specified_notes) else None ), ) if not spines: continue with _get_temp_paths(len(spines)) as paths: for path, spine in zip(paths, spines): _write_spine(spine, path) collated = collate_spines(paths) merged = merge_spines(collated) staves.append(merged) assert staves with _get_temp_paths(len(staves)) as paths: for path, stave in zip(paths, staves): with open(path, "w") as outf: outf.write("\n".join(stave)) collated = collate_spines(paths) collated = number_measures(collated) return collated.strip() + "\n" + "\n".join(postscript)Args
df- pandas DataFrame containing a musical score (see
music_dfpackage). split_point- int at which to split output into different staves (for treble and bass clefs). Default 60.
label_col- an optional column name. The contents of the columns will be used to label the notes.
color_col- if provided, notes will be colored based on this column.
The unique values in this column will be mapped to different
colors, up to 7 different colors, at which point they will
repeat in modular fashion. If there are values that shouldn't
be colored (i.e., should be left black), this can be indicated
with a boolean mask in the column pointed to by the
color_mask_colargument. color_transparency_col- if provided, the transparency of notes will be indicated with this column. Must be scalar, transparency will be linearly interpolated from its low value to its high value.
color_mapping- optional dictionary mapping values (from color_col) to colors. The colors can be indicated w/ hex colors (strings beginning with a "#" character). Missing colors are given default values. There can be at most 7 distinct colors.
def number_measures(humdrum_contents: str)-
Expand source code
def number_measures(humdrum_contents: str): """Note: this function assumes that the input has no measure numbers. >>> humdrum_contents = '''*C:\\t*C:\\t*C:\\t*C: ... *M2/2\\t*M2/2\\t*M2/2\\t*M2/2 ... *met(c|)\\t*met(c|)\\t*met(c|)\\t*met(c|) ... =\\t=\\t=\\t= ... 1C\\t1c\\t1G\\t1e ... =\\t=\\t=\\t= ... 2.B\\t2.d\\t2.e\\t2.g# ... 4E\\t4B\\t4d\\t4g ... =\\t=\\t=\\t= ... 2F\\t2A\\t2c\\t2f ... 2F#\\t2B\\t2A\\t2d# ... =\\t=\\t=\\t= ... 2G\\t2c\\t2G\\t2e ... 4C\\t4c\\t4G\\t4e ... 4C\\t4c\\t4G\\t4e ... =\\t=\\t=\\t= ... 4D\\t4c\\t4A\\t4f# ... 4D\\t4c\\t4A\\t4f# ... 4D\\t4c\\t4A\\t4f# ... 4D\\t4c\\t4A\\t4f# ... ==\\t==\\t==\\t== ... *-\\t*-\\t*-\\t*- ... ''' Visually this doctest appears to be working but I haven't figured out the whitespace normalization to get it to pass yet. >>> number_measures(humdrum_contents) # doctest: +SKIP '''*C: *C: *C: *C: *M2/2 *M2/2 *M2/2 *M2/2 *met(c|) *met(c|) *met(c|) *met(c|) =1 =1 =1 =1 1C 1c 1G 1e =2 =2 =2 =2 2.B 2.d 2.e 2.g# 4E 4B 4d 4g =3 =3 =3 =3 2F 2A 2c 2f 2F# 2B 2A 2d# =4 =4 =4 =4 2G 2c 2G 2e 4C 4c 4G 4e 4C 4c 4G 4e =5 =5 =5 =5 4D 4c 4A 4f# 4D 4c 4A 4f# 4D 4c 4A 4f# 4D 4c 4A 4f# == == == == *- *- *- *- ''' """ output_lines = [] bar_n = 1 for line in humdrum_contents.split("\n"): if not line.startswith("="): output_lines.append(line) continue else: bar_tokens = line.split("\t") if bar_tokens == ["=="] * len(bar_tokens): # Not sure if there should ever be numbered double bars output_lines.append(line) continue assert bar_tokens == ["="] * len(bar_tokens), ( "bar numbers etc. are not implemented" ) numbered_bars = "\t".join([f"={bar_n}"] * len(bar_tokens)) output_lines.append(numbered_bars) bar_n += 1 return "\n".join(output_lines)Note: this function assumes that the input has no measure numbers.
>>> humdrum_contents = '''*C:\t*C:\t*C:\t*C: ... *M2/2\t*M2/2\t*M2/2\t*M2/2 ... *met(c|)\t*met(c|)\t*met(c|)\t*met(c|) ... =\t=\t=\t= ... 1C\t1c\t1G\t1e ... =\t=\t=\t= ... 2.B\t2.d\t2.e\t2.g# ... 4E\t4B\t4d\t4g ... =\t=\t=\t= ... 2F\t2A\t2c\t2f ... 2F#\t2B\t2A\t2d# ... =\t=\t=\t= ... 2G\t2c\t2G\t2e ... 4C\t4c\t4G\t4e ... 4C\t4c\t4G\t4e ... =\t=\t=\t= ... 4D\t4c\t4A\t4f# ... 4D\t4c\t4A\t4f# ... 4D\t4c\t4A\t4f# ... 4D\t4c\t4A\t4f# ... ==\t==\t==\t== ... *-\t*-\t*-\t*- ... '''Visually this doctest appears to be working but I haven't figured out the whitespace normalization to get it to pass yet.
>>> number_measures(humdrum_contents) # doctest: +SKIP '''*C: *C: *C: *C: *M2/2 *M2/2 *M2/2 *M2/2 *met(c|) *met(c|) *met(c|) *met(c|) =1 =1 =1 =1 1C 1c 1G 1e =2 =2 =2 =2 2.B 2.d 2.e 2.g# 4E 4B 4d 4g =3 =3 =3 =3 2F 2A 2c 2f 2F# 2B 2A 2d# =4 =4 =4 =4 2G 2c 2G 2e 4C 4c 4G 4e 4C 4c 4G 4e =5 =5 =5 =5 4D 4c 4A 4f# 4D 4c 4A 4f# 4D 4c 4A 4f# 4D 4c 4A 4f# == == == == *- *- *- *- '''