Source code for drumscript.notation_generator.score_builder

# DrumScript/notation_generator/score_builder.py

"""
Module to build the final score from classified events.
"""

import json
import os
from typing import Any

from drumscript.notation_generator.midi_exporter import export_to_midi

# from drumscript.notation_generator.pdf_exporter import generate_custom_pdf
from drumscript.notation_generator.pdf_exporter import export_pdf


[docs] def build_score( detected_events: list[dict[str, Any]], # tempo: int = 120, # tempo: int, # <-- forces the caller to provide tempo tempo: float, output_path: str = "outputs/score.pdf", quantization_subdivision: int = 16, time_signature: str = "4/4", ): """ Builds a drum score by saving event data to JSON and rendering to PDF. # Builds a drum score by saving the event data to JSON and then # rendering it directly to PDF using the custom engine. # This bypasses MusicXML entirely to ensure WYSIWYG (What You See Is What You Get) results. # Builds a drum score PDF, respecting the provided Time Signature, or assuming default 4/4 if not provided :param detected_events: List of classified drum events. :type detected_events: List[Dict[str, Any]] :param tempo: Tempo in BPM. :type tempo: int :param output_path: Path to save the PDF. :type output_path: str, optional :param quantization_subdivision: Grid for quantization (e.g., 16 for 16th notes). :type quantization_subdivision: int, optional :param time_signature: Time signature string (e.g., "4/4"). :type time_signature: str, optional """ print(f"--- Building Score for: {output_path} [Time Sig: {time_signature}] ---") # --- MUSICAL INTERPRETATION LOGIC --- # Drum algorithms often detect the "double time" tempo (e.g. 130 BPM instead of 65 BPM). # To make the sheet music readable in standard 4/4 time (giving it a "half-time feel" # where the snare lands heavily on beat 3), we halve the raw detected tempo for notation. # tempo = tempo / 2.0 tempo = tempo # --- QUANTIZATION LOGIC # Snap all raw timestamps to a perfect musical grid so notes align vertically if tempo > 0: seconds_per_beat = 60.0 / float(tempo) # If subdivision is 16 (16th notes), that is 4 grid slices per beat grid_step_seconds = seconds_per_beat * (4.0 / quantization_subdivision) for event in detected_events: # raw_time = event['time'] raw_time = event["time_sec"] # Round the raw human time to the nearest perfect grid step quantized_time = round(raw_time / grid_step_seconds) * grid_step_seconds # Overwrite the raw time with the "snapped" perfect time # event['time'] = quantized_time event["time_sec"] = quantized_time # ---------------------------------------------------- # 1. Prepare File Paths # output_path e.g. "outputs/mysong.pdf" base_path = os.path.splitext(output_path)[0] # "outputs/mysong" json_path = f"{base_path}.json" # NEW: Derive specific file paths from the base string pdf_filepath = f"{base_path}.pdf" midi_filepath = f"{base_path}.mid" # 2. Save Transcription Data to JSON # This file serves as the "Source of Truth" for the PDF renderer. try: print(f"Saving to: {json_path}") with open(json_path, "w") as f: json.dump(detected_events, f, indent=4) except Exception as e: print(f" Warning: Could not save JSON transcription: {e}") # 3. Generate Visual PDF (Directly from Data) try: # generate_custom_pdf( export_pdf( detected_events=detected_events, # OLD: output_path=output_path, output_path=pdf_filepath, # Updated to explicitly map to .pdf tempo=tempo, time_signature=time_signature, ) # Success message is handled inside generate_custom_pdf/export_pdf in pdf_exporter.py except Exception as e: print(f"PDF Export Failed: {e}") import traceback traceback.print_exc() # 4. Generate MIDI File try: export_to_midi( classified_events=detected_events, output_path=midi_filepath, # Updated to explicitly map to .mid tempo=tempo, ) except Exception as e: print(f"MIDI Export Failed: {e}") import traceback traceback.print_exc()
# --------------------------------------------------------------------------uncomment during testing # from datetime import datetime # print("\n# ------------------------------------------------------------------------------------") # datetimestamp = datetime.now() # print(f'\ndate/time: {datetimestamp}') # --------------------------------------------------------------------------------------------------