Saturday, February 14, 2026

making midis in reaper and playing them over udp


#!/usr/bin/env python3
import time
from socket import AF_INET, SOCK_DGRAM, socket
import mido
import threading
import signal
import sys

# MT32-Pi UDP target
UDP_HOST = "192.168.0.100"
UDP_PORT = 1999

sock = socket(AF_INET, SOCK_DGRAM)

# MIDI file path
midi_file = "/home/bweew/Desktop/midi_export.mid"

# Shared variable for dynamic CC91
dynamic_cc91 = [64] * 16  # default reverb level per channel

# Track currently sounding notes per channel
active_notes = {ch: set() for ch in range(16)}

def cc91_input():
    global dynamic_cc91
    while True:
        try:
            raw = input("Set CC91 (format: channel,value e.g., 0,80): ")
            ch, val = map(int, raw.split(','))
            if 0 <= ch < 16 and 0 <= val <= 127:
                dynamic_cc91[ch] = val
                sock.sendto(bytes([0xB0 | ch, 91, val]), (UDP_HOST, UDP_PORT))
        except Exception:
            print("Invalid input. Format: channel,value")

def send_note_offs():
    for ch, notes in active_notes.items():
        for note in notes:
            sock.sendto(bytes([0x80 | ch, note, 0]), (UDP_HOST, UDP_PORT))
    for ch in range(16):
        sock.sendto(bytes([0xB0 | ch, 123, 0]), (UDP_HOST, UDP_PORT))

def signal_handler(sig, frame):
    print("\nAborting playback, sending note-offs...")
    send_note_offs()
    sys.exit(0)

signal.signal(signal.SIGINT, signal_handler)

# start thread for dynamic CC91 input
threading.Thread(target=cc91_input, daemon=True).start()

print(f"Playing {midi_file} to {UDP_HOST}:{UDP_PORT}... (Ctrl-C to stop)")

while True:
    mid = mido.MidiFile(midi_file)
    active_notes = {ch: set() for ch in range(16)}  # reset notes

    for msg in mid:
        time.sleep(msg.time)

        if msg.type == 'note_on':
            sock.sendto(bytes([0x90 | msg.channel, msg.note, msg.velocity]), (UDP_HOST, UDP_PORT))
            if msg.velocity > 0:
                active_notes[msg.channel].add(msg.note)
            else:
                active_notes[msg.channel].discard(msg.note)
        elif msg.type == 'note_off':
            sock.sendto(bytes([0x80 | msg.channel, msg.note, msg.velocity]), (UDP_HOST, UDP_PORT))
            active_notes[msg.channel].discard(msg.note)
        elif msg.type == 'program_change':
            sock.sendto(bytes([0xC0 | msg.channel, msg.program]), (UDP_HOST, UDP_PORT))
        elif msg.type == 'control_change':
            val = dynamic_cc91[msg.channel] if msg.control == 91 else msg.value
            sock.sendto(bytes([0xB0 | msg.channel, msg.control, val]), (UDP_HOST, UDP_PORT))

    send_note_offs()
    print("Looping playback...")

No comments:

Post a Comment