mirror of https://github.com/probonopd/MiniDexed
parent
543fc1ea3e
commit
5d1065178b
@ -0,0 +1,163 @@ |
||||
#!/usr/bin/env python |
||||
# -*- coding: utf-8 -*- |
||||
|
||||
# Send MIDI data to a MIDI device using the `python-rtmidi` library. |
||||
|
||||
try: |
||||
import rtmidi |
||||
except ImportError: |
||||
print("Please install the python-rtmidi library: pip install python-rtmidi") |
||||
exit(1) |
||||
import time |
||||
import sys |
||||
import argparse |
||||
import os |
||||
import signal |
||||
import atexit |
||||
|
||||
def signal_handler(sig, frame): |
||||
print("\nExiting...") |
||||
sys.exit(0) |
||||
signal.signal(signal.SIGINT, signal_handler) |
||||
|
||||
def cleanup_all_notes_off(): |
||||
try: |
||||
midiout = rtmidi.MidiOut() |
||||
ports = midiout.get_ports() |
||||
if ports: |
||||
midiout.open_port(0) |
||||
all_notes_off(midiout, verbose=True) |
||||
time.sleep(0.5) |
||||
midiout.close_port() |
||||
except Exception as e: |
||||
print(f"Cleanup error: {e}") |
||||
|
||||
atexit.register(cleanup_all_notes_off) |
||||
|
||||
# Ask the user which MIDI port to use |
||||
def get_midi_port(midiout): |
||||
print("Available MIDI output ports:") |
||||
ports = midiout.get_ports() |
||||
for i, port in enumerate(ports): |
||||
print(f"{i}: {port}") |
||||
while True: |
||||
try: |
||||
choice = int(input("Select a port by number: ")) |
||||
if 0 <= choice < len(ports): |
||||
return choice |
||||
else: |
||||
print("Invalid choice. Please select a valid port number.") |
||||
except ValueError: |
||||
print("Invalid input. Please enter a number.") |
||||
|
||||
def play_chord(midiout, notes, velocity=100, channel=0, delay=0.25, verbose=True): |
||||
print("Playing chord:", notes) |
||||
for note in notes: |
||||
midiout.send_message([0x90 | channel, note, velocity]) |
||||
time.sleep(delay) |
||||
time.sleep(0.5) |
||||
for note in notes: |
||||
midiout.send_message([0x80 | channel, note, 0]) |
||||
|
||||
def send_sysex(midiout, msg, verbose=True, label=None): |
||||
midiout.send_message(msg) |
||||
if verbose: |
||||
if label: |
||||
print(f"Sent: {label} {msg}") |
||||
else: |
||||
print(f"Sent: {msg}") |
||||
|
||||
def all_notes_off(midiout, verbose=True): |
||||
# Send All Notes Off (CC 123) and individual Note Off for all notes 0-127 on all 16 MIDI channels |
||||
for channel in range(16): |
||||
midiout.send_message([0xB0 | channel, 120, 0]) |
||||
if verbose: |
||||
print(f"Sent: All Sound Off (CC120) on channel {channel+1}") |
||||
|
||||
def send_modwheel(midiout, value, channel=0, verbose=True): |
||||
# Modulation wheel is CC 1 |
||||
midiout.send_message([0xB0 | channel, 1, value]) |
||||
if verbose: |
||||
print(f"Sent: ModWheel CC1 value {value} on channel {channel+1}") |
||||
|
||||
if __name__ == "__main__": |
||||
parser = argparse.ArgumentParser(description="Send MIDI messages to a device.") |
||||
parser.add_argument("-p", "--port", type=str, help="MIDI output port name or number") |
||||
# Always verbose, so remove the verbose switch and set verbose to True |
||||
args = parser.parse_args() |
||||
verbose = True |
||||
|
||||
midiout = rtmidi.MidiOut() |
||||
ports = midiout.get_ports() |
||||
|
||||
if args.port is not None: |
||||
# Allow port to be specified by number or name |
||||
try: |
||||
port_index = int(args.port) |
||||
except ValueError: |
||||
# Try to find by name |
||||
port_index = next((i for i, p in enumerate(ports) if args.port in p), None) |
||||
if port_index is None: |
||||
print(f"Port '{args.port}' not found.") |
||||
sys.exit(1) |
||||
else: |
||||
port_index = get_midi_port(midiout) |
||||
|
||||
if verbose: |
||||
print(f"Using MIDI output port: {ports[port_index]}") |
||||
|
||||
try: |
||||
midiout.open_port(port_index) |
||||
chord_notes = [60, 64, 67] |
||||
# Send all notes off before starting the test program |
||||
all_notes_off(midiout, verbose=verbose) |
||||
while True: |
||||
send_sysex(midiout, [0xF0, 0x43, 0x10, 0x04, 0x05, 0x00, 0xF7], verbose, "Portamento Time 0") |
||||
play_chord(midiout, chord_notes, verbose=verbose) |
||||
send_sysex(midiout, [0xF0, 0x43, 0x10, 0x04, 0x05, 0x02, 0xF7], verbose, "Portamento Time 2") |
||||
time.sleep(1) |
||||
|
||||
# Test Detune |
||||
detune_msgs = [ |
||||
([0xF0, 0x43, 0x10, 0x04, 0x40, 0x2F, 0xF7], "Detune TG1"), |
||||
([0xF0, 0x43, 0x11, 0x04, 0x40, 0x37, 0xF7], "Detune TG2"), |
||||
([0xF0, 0x43, 0x12, 0x04, 0x40, 0x40, 0xF7], "Detune TG3"), |
||||
([0xF0, 0x43, 0x13, 0x04, 0x40, 0x48, 0xF7], "Detune TG4"), |
||||
] |
||||
for msg, label in detune_msgs: |
||||
send_sysex(midiout, msg, verbose, label) |
||||
play_chord(midiout, chord_notes, verbose=verbose) |
||||
for ch in range(0x10, 0x14): |
||||
send_sysex(midiout, [0xF0, 0x43, ch, 0x04, 0x40, 0x40, 0xF7], verbose, f"Detune TG{ch-0x0F} reset") |
||||
time.sleep(1) |
||||
|
||||
# Test Poly and Mono modes |
||||
send_sysex(midiout, [0xF0, 0x43, 0x13, 0x04, 0x02, 0x00, 0xF7], verbose, "TG4 Poly") |
||||
play_chord(midiout, chord_notes, verbose=verbose) |
||||
send_sysex(midiout, [0xF0, 0x43, 0x13, 0x04, 0x02, 0x01, 0xF7], verbose, "TG4 Mono") |
||||
time.sleep(1) |
||||
|
||||
# Test ModWheel sensitivity |
||||
send_sysex(midiout, [0xF0, 0x43, 0x13, 0x04, 0x09, 0x01, 0xF7], verbose, "TG4 ModWheel Sensitivity ON") |
||||
send_modwheel(midiout, 127, channel=0, verbose=verbose) |
||||
play_chord(midiout, chord_notes, verbose=verbose) |
||||
send_modwheel(midiout, 0, channel=0, verbose=verbose) |
||||
send_sysex(midiout, [0xF0, 0x43, 0x13, 0x04, 0x09, 0x00, 0xF7], verbose, "TG4 ModWheel Sensitivity OFF") |
||||
send_modwheel(midiout, 127, channel=0, verbose=verbose) |
||||
play_chord(midiout, chord_notes, verbose=verbose) |
||||
send_modwheel(midiout, 0, channel=0, verbose=verbose) |
||||
time.sleep(1) |
||||
|
||||
# Disable all Tone Generators except TG1 |
||||
send_sysex(midiout, [0xF0, 0x43, 0x13, 0x04, 0x08, 0x00, 0xF7], verbose, "Disable TG2, TG3, TG4") |
||||
play_chord(midiout, chord_notes, verbose=verbose) |
||||
# Enable all Tone Generators |
||||
send_sysex(midiout, [0xF0, 0x43, 0x13, 0x04, 0x08, 0x01, 0xF7], verbose, "Enable TG2, TG3, TG4") |
||||
play_chord(midiout, chord_notes, verbose=verbose) |
||||
time.sleep(1) |
||||
|
||||
# Send all notes off after each test program loop |
||||
all_notes_off(midiout, verbose=verbose) |
||||
except Exception as e: |
||||
print(f"Error: {e}") |
||||
sys.exit(1) |
Loading…
Reference in new issue