Module music_df.conversions.symusic_conv
Functions for converting between symusic Score objects and music_df DataFrames.
Functions
def df_to_symusic_score(df: pandas.DataFrame, ticks_per_quarter: int = 480) ‑> ScoreFactory()-
Expand source code
def df_to_symusic_score( df: pd.DataFrame, ticks_per_quarter: int = 480, ) -> symusic.Score: """Convert a music_df DataFrame to a symusic Score. Args: df: A music_df DataFrame with columns: type, onset, release, track, pitch, velocity, other. ticks_per_quarter: Ticks per quarter note for the output Score. Returns: A symusic Score object. """ score = symusic.Score(ticks_per_quarter) # Determine time conversion factor based on df time format # Assume times are in quarter notes (beats) def _to_ticks(time_val) -> int: if time_val is None or pd.isna(time_val): return 0 if isinstance(time_val, fractions.Fraction): return int(time_val * ticks_per_quarter) return int(float(time_val) * ticks_per_quarter) # Determine number of tracks needed if "track" in df.columns: max_track = df["track"].dropna().max() if pd.isna(max_track): n_tracks = 1 else: n_tracks = int(max_track) + 1 else: n_tracks = 1 # Create tracks for _ in range(n_tracks): score.tracks.append(symusic.Track()) # Process rows for _, row in df.iterrows(): event_type = row.get("type", "note") if event_type == "note": track_i = int(row["track"]) if pd.notna(row.get("track")) else 0 onset_ticks = _to_ticks(row["onset"]) release_ticks = _to_ticks(row["release"]) duration_ticks = release_ticks - onset_ticks note = symusic.Note( time=onset_ticks, duration=duration_ticks, pitch=int(row["pitch"]), velocity=int(row.get("velocity", 64)) if pd.notna(row.get("velocity")) else 64, ) score.tracks[track_i].notes.append(note) elif event_type == "tempo": tempo_val = row.get("tempo") if tempo_val is None and isinstance(row.get("other"), dict): tempo_val = row["other"].get("tempo") if tempo_val is not None: score.tempos.append( symusic.Tempo(time=_to_ticks(row["onset"]), qpm=float(tempo_val)) ) elif event_type == "time_signature": other = row.get("other", {}) if isinstance(other, dict): score.time_signatures.append( symusic.TimeSignature( time=_to_ticks(row["onset"]), numerator=int(other.get("numerator", 4)), denominator=int(other.get("denominator", 4)), ) ) elif event_type == "key_signature": other = row.get("other", {}) if isinstance(other, dict): score.key_signatures.append( symusic.KeySignature( time=_to_ticks(row["onset"]), key=int(other.get("key", 0)), tonality=int(other.get("tonality", 0)), ) ) elif event_type == "program_change": track_i = int(row["track"]) if pd.notna(row.get("track")) else 0 other = row.get("other", {}) if isinstance(other, dict) and "program" in other: score.tracks[track_i].program = int(other["program"]) elif event_type == "control_change": track_i = int(row["track"]) if pd.notna(row.get("track")) else 0 other = row.get("other", {}) if isinstance(other, dict): score.tracks[track_i].controls.append( symusic.ControlChange( time=_to_ticks(row["onset"]), number=int(other.get("control", 0)), value=int(other.get("value", 0)), ) ) elif event_type == "pitchwheel": track_i = int(row["track"]) if pd.notna(row.get("track")) else 0 other = row.get("other", {}) if isinstance(other, dict) and "pitch" in other: score.tracks[track_i].pitch_bends.append( symusic.PitchBend( time=_to_ticks(row["onset"]), value=int(other["pitch"]), ) ) return scoreConvert a music_df DataFrame to a symusic Score.
Args
df- A music_df DataFrame with columns: type, onset, release, track, pitch, velocity, other.
ticks_per_quarter- Ticks per quarter note for the output Score.
Returns
A symusic Score object.
def read_midi_symusic(midi_path: str,
time_type: Type = builtins.float,
max_denominator: int = 8192,
display_name: str | None = None,
notes_only: bool = False) ‑> pandas.DataFrame-
Expand source code
def read_midi_symusic( midi_path: str, time_type: Type = float, max_denominator: int = 8192, display_name: str | None = None, notes_only: bool = False, ) -> pd.DataFrame: """Read a MIDI file and return a music_df DataFrame using symusic. This is an alternative to midi_to_table() that uses symusic instead of mido. Args: midi_path: Path to the MIDI file. time_type: The numeric type for time values. Default is float. Use fractions.Fraction for exact timing, or int for raw ticks. max_denominator: Maximum denominator when time_type is fractions.Fraction. display_name: Value for the "filename" column. If None, uses the basename. notes_only: If True, only include note events. Returns: A DataFrame with columns: filename, type, onset, release, track, channel, pitch, velocity, other. """ import os if display_name is None: display_name = os.path.basename(midi_path) score = symusic.Score(midi_path) return symusic_score_to_df( score, time_type=time_type, max_denominator=max_denominator, display_name=display_name, notes_only=notes_only, )Read a MIDI file and return a music_df DataFrame using symusic.
This is an alternative to midi_to_table() that uses symusic instead of mido.
Args
midi_path- Path to the MIDI file.
time_type- The numeric type for time values. Default is float. Use fractions.Fraction for exact timing, or int for raw ticks.
max_denominator- Maximum denominator when time_type is fractions.Fraction.
display_name- Value for the "filename" column. If None, uses the basename.
notes_only- If True, only include note events.
Returns
A DataFrame with columns- filename, type, onset, release, track, channel,
pitch, velocity, other.
def symusic_score_to_df(score: ScoreFactory(),
time_type: Type = builtins.float,
max_denominator: int = 8192,
display_name: str | None = None,
notes_only: bool = False) ‑> pandas.DataFrame-
Expand source code
def symusic_score_to_df( score: symusic.Score, time_type: Type = float, max_denominator: int = 8192, display_name: str | None = None, notes_only: bool = False, ) -> pd.DataFrame: """Convert a symusic Score to a music_df DataFrame. Args: score: A symusic Score object. time_type: The numeric type for time values. Default is float. Use fractions.Fraction for exact timing, or int for raw ticks. max_denominator: Maximum denominator when time_type is fractions.Fraction. display_name: Value for the "filename" column. If None, uses empty string. notes_only: If True, only include note events (no tempos, time signatures, etc.). Returns: A DataFrame with columns: filename, type, onset, release, track, channel, pitch, velocity, other. """ if display_name is None: display_name = "" ticks_per_quarter = score.ticks_per_quarter def _get_time(tick_time: int): if time_type == int: return tick_time if time_type == fractions.Fraction: return fractions.Fraction(tick_time, ticks_per_quarter).limit_denominator( max_denominator=max_denominator ) return time_type(tick_time / ticks_per_quarter) rows: list[dict] = [] # Extract notes from all tracks for track_i, track in enumerate(score.tracks): for note in track.notes: rows.append( { "filename": display_name, "type": "note", "onset": _get_time(note.time), "release": _get_time(note.time + note.duration), "track": track_i, "channel": 0, "pitch": note.pitch, "velocity": note.velocity, "other": None, } ) if not notes_only: # Extract tempo events for tempo in score.tempos: rows.append( { "filename": display_name, "type": "tempo", "onset": _get_time(tempo.time), "release": None, "track": None, "channel": None, "pitch": None, "velocity": None, "other": {"tempo": tempo.qpm}, } ) # Extract time signature events for ts in score.time_signatures: rows.append( { "filename": display_name, "type": "time_signature", "onset": _get_time(ts.time), "release": None, "track": None, "channel": None, "pitch": None, "velocity": None, "other": {"numerator": ts.numerator, "denominator": ts.denominator}, } ) # Extract key signature events for ks in score.key_signatures: rows.append( { "filename": display_name, "type": "key_signature", "onset": _get_time(ks.time), "release": None, "track": None, "channel": None, "pitch": None, "velocity": None, "other": {"key": ks.key, "tonality": ks.tonality}, } ) # Extract markers for marker in score.markers: rows.append( { "filename": display_name, "type": "marker", "onset": _get_time(marker.time), "release": None, "track": None, "channel": None, "pitch": None, "velocity": None, "other": {"text": marker.text}, } ) # Extract track-level events (program changes at onset 0) for track_i, track in enumerate(score.tracks): if track.program is not None: rows.append( { "filename": display_name, "type": "program_change", "onset": _get_time(0), "release": None, "track": track_i, "channel": 0, "pitch": None, "velocity": None, "other": {"program": track.program}, } ) # Extract control changes for cc in track.controls: rows.append( { "filename": display_name, "type": "control_change", "onset": _get_time(cc.time), "release": None, "track": track_i, "channel": 0, "pitch": None, "velocity": None, "other": {"control": cc.number, "value": cc.value}, } ) # Extract pitch bends for pb in track.pitch_bends: rows.append( { "filename": display_name, "type": "pitchwheel", "onset": _get_time(pb.time), "release": None, "track": track_i, "channel": 0, "pitch": None, "velocity": None, "other": {"pitch": pb.value}, } ) if not rows: return pd.DataFrame( columns=[ "filename", "type", "onset", "release", "track", "channel", "pitch", "velocity", "other", ] ) df = pd.DataFrame(rows) return sort_df(df)Convert a symusic Score to a music_df DataFrame.
Args
score- A symusic Score object.
time_type- The numeric type for time values. Default is float. Use fractions.Fraction for exact timing, or int for raw ticks.
max_denominator- Maximum denominator when time_type is fractions.Fraction.
display_name- Value for the "filename" column. If None, uses empty string.
notes_only- If True, only include note events (no tempos, time signatures, etc.).
Returns
A DataFrame with columns- filename, type, onset, release, track, channel,
pitch, velocity, other.
def write_midi_symusic(df: pandas.DataFrame, midi_path: str, ticks_per_quarter: int = 480) ‑> None-
Expand source code
def write_midi_symusic( df: pd.DataFrame, midi_path: str, ticks_per_quarter: int = 480, ) -> None: """Write a music_df DataFrame to a MIDI file using symusic. Args: df: A music_df DataFrame. midi_path: Output path for the MIDI file. ticks_per_quarter: Ticks per quarter note. """ score = df_to_symusic_score(df, ticks_per_quarter=ticks_per_quarter) score.dump_midi(midi_path)Write a music_df DataFrame to a MIDI file using symusic.
Args
df- A music_df DataFrame.
midi_path- Output path for the MIDI file.
ticks_per_quarter- Ticks per quarter note.