commit 79c15a953c995eb881a0450701093cf57d94268c Author: IceDragon Date: Wed Jan 14 19:25:43 2026 +0200 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..19e7a1a --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +# Ignore JetBrains project files +.idea/ + +# Ignore Python cache directories +__pycache__/ +**/__pycache__/ + +# Ignore compiled Python files +*.pyc +*.pyo +*.pyd + +# Ignore macOS system files +.DS_Store +**/.DS_Store + diff --git a/main.py b/main.py new file mode 100644 index 0000000..2fd9449 --- /dev/null +++ b/main.py @@ -0,0 +1,123 @@ +#!/usr/bin/env python3.11 +import itertools +import sys +import time +import rtmidi +from typing import Tuple + + +G_STATUS_NAMES = { + 0x80: "NOTE_OFF", + 0x90: "NOTE_ON", + 0xa0: "KEY_PRESSR", + 0xb0: "CTRL_CHANGE", + 0xc0: "PROG_CHANGE", + 0xd0: "CHAN_PRESSR", + 0xe0: "PITCH_BEND", + 0xf0: "XXX", +} + + +def note_on(chan: int, note: int, velocity: int) -> Tuple[int, int, int]: + assert 0 < chan <= 16, chan + assert 0 <= note <= 0xFF, note + assert 0 <= velocity <= 0xFF, velocity + msg_byte = 0x90 + (chan - 1) + return msg_byte, note, velocity + + +def note_off(chan: int, note: int) -> Tuple[int, int, int]: + assert 0 < chan <= 16, chan + assert 0 <= note <= 0xFF, note + msg_byte = 0x80 + (chan - 1) + return msg_byte, note, 0 + + +def get_status_name(status: int) -> str: + return G_STATUS_NAMES.get(status & 0xF0) or f'' + + +def test_midi_out() -> int: + midi_out = rtmidi.MidiOut() + ports: list[str] = midi_out.get_ports() + print('Ports:') + for i, port_name in enumerate(ports): + print(f'{i:2d} {port_name}') + + # nanokey_index: int = ([i for i, port_name in enumerate(ports) if 'nanoKEY' in port_name] or [-1])[0] + # if nanokey_index < 0: + # print(f'nanoKEY2 port -> NOT FOUND [{ports}]', file=sys.stderr) + # return 1 + # + # print(f'nanoKEY2 port -> {nanokey_index} ({ports[nanokey_index]})') + # midi_out.open_port(nanokey_index) + midi_out.open_port(0) + with midi_out: + midi_out.send_message(note_on(1, 60, 112)) + time.sleep(0.5) + midi_out.send_message(note_off(1, 60)) + time.sleep(0.1) + + return 0 + + +def handle_midi_event(msg, midi_out): + midi_msg, delay = msg + status, b1, b2 = midi_msg + b_hi = b1 + (b2 << 8) + status_name = get_status_name(status) + # if status_name == "PITCH_BEND": + print(f'[d:{delay:2.2f}] {status_name:11s} [ch {1 + (status & 0x0f)}] -> {b1:02x}[{b1:3d}] {b2:02x}[{b2:3d}] hi: {b_hi}') + + midi_out.send_message([status, b1, 127]) + + +def set_all(midi_out, value): + for ch, btn_id in itertools.product([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 16], range(128)): + midi_out.send_message(note_on(ch, btn_id, value)) + + +def test_midi_in() -> int: + midi_in = rtmidi.MidiIn() + ports: list[str] = midi_in.get_ports() + print('Ports:') + for i, port_name in enumerate(ports): + print(f'{i:2d} {port_name}') + + midi_out = rtmidi.MidiOut() + + port = 1 + midi_in.set_callback(handle_midi_event, midi_out) + midi_in.open_port(port) + midi_out.open_port(port) + try: + set_all(midi_out, 127) + time.sleep(0.5) + set_all(midi_out, 0) + + i = 0 + levels = list(range(3)) + while True: + if i >= len(levels): + i = -len(levels) + 1 + + midi_out.send_message(note_on(3, 2, levels[abs(i)])) + + i += 1 + time.sleep(0.5) + except KeyboardInterrupt: + print('CTRL+C TRIGGERED') + finally: + set_all(midi_out, 0) + midi_in.cancel_callback() + midi_in.close_port() + + return 0 + + +def main() -> int: + return test_midi_in() + + +if __name__ == '__main__': + raise SystemExit(main()) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..0086a37 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +python-rtmidi