from __future__ import annotations from pathlib import Path OUTPUT = Path(__file__).resolve().parents[1] / "sample_midi" def variable_length(value: int) -> bytes: data = [value & 0x7F] value >>= 7 while value: data.insert(0, (value & 0x7F) | 0x80) value >>= 7 return bytes(data) def track(events: list[tuple[int, bytes]], tempo: int = 500_000, program: int = 0) -> bytes: body = bytearray() body.extend(b"\x00\xff\x51\x03" + tempo.to_bytes(3, "big")) body.extend(b"\x00\xff\x58\x04\x04\x02\x18\x08") body.extend(bytes([0, 0xC0, program])) for delta, message in events: body.extend(variable_length(delta)) body.extend(message) body.extend(b"\x00\xff\x2f\x00") return b"MTrk" + len(body).to_bytes(4, "big") + body def midi_file(events: list[tuple[int, bytes]], tempo: int = 500_000) -> bytes: header = b"MThd" + (6).to_bytes(4, "big") + (0).to_bytes(2, "big") + (1).to_bytes(2, "big") + (480).to_bytes(2, "big") return header + track(events, tempo) def melody(notes: list[int], beats: int = 1, velocity: int = 82) -> list[tuple[int, bytes]]: result: list[tuple[int, bytes]] = [] for note in notes: result.append((0, bytes([0x90, note, velocity]))) result.append((480 * beats, bytes([0x80, note, 0]))) return result def chord_progression(chords: list[list[int]], melody_notes: list[int]) -> list[tuple[int, bytes]]: result: list[tuple[int, bytes]] = [] for index, note in enumerate(melody_notes): chord = chords[(index // 4) % len(chords)] for chord_note in chord: result.append((0, bytes([0x90, chord_note, 52]))) result.append((0, bytes([0x90, note, 86]))) result.append((480, bytes([0x80, note, 0]))) for chord_note in chord: result.append((0, bytes([0x80, chord_note, 0]))) return result def main() -> None: OUTPUT.mkdir(parents=True, exist_ok=True) first_snow = [64, 67, 69, 67, 64, 62, 60, 62, 64, 67, 72, 71, 69, 67, 64, 60] blue_breeze = [60, 62, 64, 67, 69, 67, 64, 62, 64, 65, 67, 72, 69, 67, 64, 60] morning_waltz = [64, 67, 72, 71, 67, 64, 62, 65, 69, 67, 64, 62, 60, 64, 67, 72] (OUTPUT / "first-snow-melody.mid").write_bytes(midi_file(melody(first_snow), 560_000)) (OUTPUT / "first-snow-accompaniment.mid").write_bytes( midi_file(chord_progression([[48, 52, 55], [45, 48, 52], [41, 45, 48], [43, 47, 50]], first_snow), 560_000) ) (OUTPUT / "blue-breeze-walk.mid").write_bytes(midi_file(melody(blue_breeze), 470_000)) (OUTPUT / "little-morning-waltz.mid").write_bytes( midi_file(chord_progression([[48, 52, 55], [43, 47, 50], [45, 48, 52]], morning_waltz), 620_000) ) if __name__ == "__main__": main()