import mido from .midi_utils import has_musical_messages CC_NAMES = { 0: "Bank Select", 1: "Modulation", 2: "Breath Controller", 4: "Foot Controller", 5: "Portamento Time", 7: "Volume", 10: "Pan", 11: "Expression", 64: "Sustain Pedal", 65: "Portamento", 66: "Sostenuto", 67: "Soft Pedal", 71: "Resonance", 72: "Release Time", 73: "Attack Time", 74: "Cutoff Frequency", 91: "Reverb", 93: "Chorus", 94: "Detune", } def get_track_detail(midi: mido.MidiFile, track_index: int) -> dict | None: """Extract detailed time-series data for a specific musical track.""" musical_idx = 0 target_track = None for track in midi.tracks: if not has_musical_messages(track): continue if musical_idx == track_index: target_track = track break musical_idx += 1 if target_track is None: return None ticks_per_beat = midi.ticks_per_beat track_name = f"Track {track_index + 1}" control_changes = {} # cc_number -> list of [tick, value] pitch_bend = [] # list of [tick, value] velocities = [] # list of [tick, velocity] ongoing_notes = {} # (note, channel) -> (start_tick, velocity) notes = [] # list of [note, start, end, velocity] absolute_tick = 0 for msg in target_track: absolute_tick += msg.time if msg.type == 'track_name': track_name = msg.name elif msg.type == 'note_on' and msg.velocity > 0: velocities.append([absolute_tick, msg.velocity]) key = (msg.note, msg.channel) ongoing_notes[key] = (absolute_tick, msg.velocity) elif msg.type == 'note_off' or (msg.type == 'note_on' and msg.velocity == 0): key = (msg.note, msg.channel) if key in ongoing_notes: start_tick, vel = ongoing_notes.pop(key) notes.append([msg.note, start_tick, absolute_tick, vel]) elif msg.type == 'control_change': cc = msg.control if cc not in control_changes: control_changes[cc] = { "name": CC_NAMES.get(cc, f"CC {cc}"), "data": [] } control_changes[cc]["data"].append([absolute_tick, msg.value]) elif msg.type == 'pitchwheel': pitch_bend.append([absolute_tick, msg.pitch]) # Convert cc keys to strings for JSON cc_out = {} for cc_num, cc_data in sorted(control_changes.items()): cc_out[str(cc_num)] = cc_data total_ticks = absolute_tick return { "track_name": track_name, "ticks_per_beat": ticks_per_beat, "total_ticks": total_ticks, "notes": notes, "control_changes": cc_out, "pitch_bend": pitch_bend, "velocities": velocities }