Module music_df.scripts.xml_to_csvs
For unlabeled data, we are using the YCAC midi corpus.
But midi data is often un- or strangely quantized. We can use the Musescore quantization algorithm by reading it into musescore and then out again. The issue with that is that musescore abbreviates the releases when writing to midi by small amounts, presumably for performance reasons. If we save the files as xml, however, the note values are complete. Therefore we do that, then use this script to write to csvs.
Functions
def do_file_pair(file_pair, config, output_list, error_file_list)-
Expand source code
def do_file_pair(file_pair, config, output_list, error_file_list): xml_file, output_path = file_pair try: if os.path.exists(output_path) and not config.overwrite: return music_df = read_xml(xml_file) music_df = music_df[music_df.type.isin(config.event_types)] if config.salami_slice: music_df = salami_slice(music_df) music_df = music_df.drop("spelling", axis=1) music_df.to_csv(output_path) # print(f"Wrote {output_path}") output_list.append(xml_file) except Exception as exc: error_file_list.append((xml_file, repr(exc))) def filter_newer_outputs(file_pairs)-
Expand source code
def filter_newer_outputs(file_pairs): filtered_pairs = [] for input_file, output_file in file_pairs: if not os.path.exists(output_file): # output file does not exist, include this pair filtered_pairs.append((input_file, output_file)) elif os.path.getmtime(input_file) > os.path.getmtime(output_file): # input file is newer than output file, include this pair filtered_pairs.append((input_file, output_file)) return filtered_pairs def get_file_pairs(config)-
Expand source code
def get_file_pairs(config): xml_files = get_xml_files(config.input_folder) if config.regex is not None: xml_files = [f for f in xml_files if re.search(config.regex, f)] output_paths = [] for xml_file in xml_files: output_basename = ( xml_file.replace(config.input_folder, "") .lstrip(os.path.sep) .replace(" ", "_") .replace(os.path.sep, "+") .replace(".xml", ".csv") .replace(".mxl", ".csv") .replace(".mscx", ".csv") ) output_path = os.path.join(config.output_folder, output_basename) output_paths.append(output_path) return list(zip(xml_files, output_paths)) def get_xml_files(folder_path)-
Expand source code
def get_xml_files(folder_path): # Use glob.glob with recursive=True to get all matching files return ( glob.glob(os.path.join(folder_path, "**", "*.xml"), recursive=True) + glob.glob(os.path.join(folder_path, "**", "*.mxl"), recursive=True) + glob.glob(os.path.join(folder_path, "**", "*.mscx"), recursive=True) ) def main()-
Expand source code
def main(): config = read_config_oc( config_path=None, cli_args=sys.argv[1:], config_cls=XMLToCSVConfig ) if config.debug: def custom_excepthook(exc_type, exc_value, exc_traceback): traceback.print_exception( exc_type, exc_value, exc_traceback, file=sys.stdout ) pdb.post_mortem(exc_traceback) sys.excepthook = custom_excepthook file_pairs = get_file_pairs(config) file_pairs = filter_newer_outputs(file_pairs) random.seed(config.seed) if config.random_files: random.shuffle(file_pairs) file_pairs = file_pairs[: config.max_files] os.makedirs(config.output_folder, exist_ok=True) if config.num_workers > 1: manager = Manager() output_files = manager.list() error_files = manager.list() with Pool(config.num_workers) as pool: list( tqdm( pool.imap_unordered( partial( do_file_pair, config=config, output_list=output_files, error_file_list=error_files, ), file_pairs, ), total=len(file_pairs), ) ) else: output_files = [] error_files = [] for file_pair in file_pairs: do_file_pair(file_pair, config, output_files, error_files) write_json(config, output_files) if error_files: print("Errors:") for xml_file, exception_str in error_files: print(f"{xml_file}: {exception_str}") print(f"{len(error_files)} total error files") def write_json(config, output_files)-
Expand source code
def write_json(config, output_files): json_path = os.path.join(config.output_folder, "source_files.json") if not config.overwrite and os.path.exists(json_path): with open(json_path, "r") as inf: existing_contents = json.load(inf) with open(json_path, "w") as outf: json.dump(existing_contents + list(output_files), outf) else: with open(json_path, "w") as outf: json.dump(list(output_files), outf, indent=2)
Classes
class XMLToCSVConfig (input_folder: str,
output_folder: str,
max_files: int | None = None,
random_files: bool = False,
salami_slice: bool = False,
seed: int = 42,
event_types: Iterable[str] = <factory>,
overwrite: bool = False,
num_workers: int = 8,
regex: str | None = None,
debug: bool = False)-
Expand source code
@dataclass class XMLToCSVConfig: input_folder: str output_folder: str max_files: int | None = None random_files: bool = False salami_slice: bool = False seed: int = 42 event_types: Iterable[str] = field( default_factory=lambda: {"note", "time_signature", "bar"} ) overwrite: bool = False num_workers: int = 8 regex: str | None = None debug: bool = FalseXMLToCSVConfig(input_folder: str, output_folder: str, max_files: int | None = None, random_files: bool = False, salami_slice: bool = False, seed: int = 42, event_types: Iterable[str] =
, overwrite: bool = False, num_workers: int = 8, regex: str | None = None, debug: bool = False) Instance variables
var debug : boolvar event_types : Iterable[str]var input_folder : strvar max_files : int | Nonevar num_workers : intvar output_folder : strvar overwrite : boolvar random_files : boolvar regex : str | Nonevar salami_slice : boolvar seed : int