"""
Midi Widget Utils
"""
import mido
from mido import MidiFile
from PySide6.QtGui import QColor
from picomidi import Midi, MidiTempo
from picomidi.message.type import MidoMessageType
[docs]
def ticks_to_seconds(ticks: int, tempo: int, ticks_per_beat: int) -> float:
"""
Convert MIDI ticks to seconds.
:param ticks: int
:param tempo: int (μs per quarter note)
:param ticks_per_beat: int
:return: float
"""
return (tempo / float(MidiTempo.MICROSECONDS_PER_SECOND)) * (ticks / ticks_per_beat)
[docs]
def get_total_duration_in_seconds(midi_file: MidiFile) -> float:
"""
get_total_duration_in_seconds
:param midi_file: MidiFile
:return: float
"""
ticks_per_beat = midi_file.ticks_per_beat
current_tempo = Midi.tempo.BPM_120_USEC # default: 120 BPM
time_seconds = 0
last_tick = 0
# Collect all events with absolute ticks
events = []
for track in midi_file.tracks:
abs_tick = 0
for msg in track:
abs_tick += msg.time
events.append((abs_tick, msg))
# Sort all events by tick
events.sort(key=lambda x: x[0])
for abs_tick, msg in events:
delta_ticks = abs_tick - last_tick
time_seconds += (current_tempo / MidiTempo.MICROSECONDS_PER_SECOND) * (
delta_ticks / ticks_per_beat
)
last_tick = abs_tick
if msg.type == MidoMessageType.SET_TEMPO.value:
current_tempo = msg.tempo
return time_seconds
[docs]
def generate_track_colors(n: int) -> list[str]:
"""
Generate visually distinct colors for up to n tracks.
:param n: int Number of tracks
:return:
"""
return [
QColor.fromHsvF(i / max(1, n), 0.7, 0.9) # HSV for distinct hues
for i in range(n)
]
[docs]
def get_first_channel(track: mido.MidiTrack) -> int | None:
"""
Get the first channel from a MIDI track.
:param track: mido.MidiTrack
:return: int | None
"""
for msg in track:
if msg.type in {
MidoMessageType.NOTE_ON.value,
MidoMessageType.NOTE_OFF.value,
MidoMessageType.CONTROL_CHANGE.value,
MidoMessageType.PROGRAM_CHANGE.value,
} and hasattr(msg, "channel"):
return msg.channel
return 0 # default fallback