"""Converters — transform generator output to MIDI notes for SongDefinition. rhythm generators → MidiNote list (channel → GM pitch mapping) melodic generators → MidiNote list (note["key"] = pitch directly) """ from __future__ import annotations from src.core.schema import MidiNote # --------------------------------------------------------------------------- # GM drum pitch mapping — channels 10-16 # --------------------------------------------------------------------------- CHANNEL_PITCH: dict[int, int] = { 10: 39, # perc (General MIDI channel 10 = percussion) 11: 36, # kick 12: 38, # snare 13: 37, # rim 14: 50, # perc2 15: 42, # hihat 16: 39, # clap } def rhythm_to_midi(note_dict: dict[int, list[dict]]) -> list[MidiNote]: """Convert rhythm generator output (channel → note list) to MidiNote list. note_dict: {channel: [{"pos", "len", "key", "vel"}, ...]} - channel must be in CHANNEL_PITCH (10-16) - pitch = CHANNEL_PITCH[channel] - start = note["pos"] - duration = note["len"] - velocity = note["vel"] """ midi_notes: list[MidiNote] = [] for channel, notes in note_dict.items(): pitch = CHANNEL_PITCH.get(channel, 60) for note in notes: midi_notes.append(MidiNote( pitch=pitch, start=note["pos"], duration=note["len"], velocity=note["vel"], )) return midi_notes def melodic_to_midi(note_list: list[dict]) -> list[MidiNote]: """Convert melodic generator output (list of note dicts) to MidiNote list. note_list: [{"pos", "len", "key", "vel"}, ...] - pitch = note["key"] (directly used, not mapped) - start = note["pos"] - duration = note["len"] - velocity = note["vel"] """ return [ MidiNote( pitch=note["key"], start=note["pos"], duration=note["len"], velocity=note["vel"], ) for note in note_list ]