mirror of
https://github.com/kennethreitz/pytheory.git
synced 2026-06-05 06:46:14 +00:00
CLI args: --channels, --port, --drums, --buffer
python test_live.py --channels 4 --drums trap --port OP-XY python test_live.py 4217 -c 16 -d house -b 256 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
+27
-15
@@ -19,9 +19,12 @@ def note_name(midi):
|
|||||||
|
|
||||||
|
|
||||||
class LiveTUI:
|
class LiveTUI:
|
||||||
def __init__(self, seed=None, port="OP-XY"):
|
def __init__(self, seed=None, port="OP-XY", n_channels=8,
|
||||||
|
drum_pattern="rock", buffer_size=128):
|
||||||
self.seed = seed or random.randint(0, 9999)
|
self.seed = seed or random.randint(0, 9999)
|
||||||
self.port = port
|
self.port = port
|
||||||
|
self.n_channels = n_channels
|
||||||
|
self.buffer_size = buffer_size
|
||||||
self.engine = None
|
self.engine = None
|
||||||
self.log_lines = []
|
self.log_lines = []
|
||||||
self.max_log = 500
|
self.max_log = 500
|
||||||
@@ -29,20 +32,21 @@ class LiveTUI:
|
|||||||
self.instruments = sorted([k for k in INSTRUMENTS.keys()
|
self.instruments = sorted([k for k in INSTRUMENTS.keys()
|
||||||
if k not in ("808_bass",)])
|
if k not in ("808_bass",)])
|
||||||
self.drum_patterns = sorted(Pattern.list_presets())
|
self.drum_patterns = sorted(Pattern.list_presets())
|
||||||
self.current_drum = "rock"
|
self.current_drum = drum_pattern
|
||||||
self.picks = []
|
self.picks = []
|
||||||
self.bpm = "—"
|
self.bpm = "—"
|
||||||
self.status = "Init"
|
self.status = "Init"
|
||||||
self.status_color = 3 # yellow
|
self.status_color = 3
|
||||||
self._build_engine()
|
self._build_engine()
|
||||||
|
|
||||||
def _build_engine(self):
|
def _build_engine(self):
|
||||||
rng = random.Random(self.seed)
|
rng = random.Random(self.seed)
|
||||||
self.picks = rng.sample(self.instruments, 8)
|
self.picks = rng.sample(self.instruments, min(self.n_channels, len(self.instruments)))
|
||||||
self.engine = LiveEngine(buffer_size=128)
|
self.engine = LiveEngine(buffer_size=self.buffer_size)
|
||||||
for i, inst in enumerate(self.picks, 1):
|
for i, inst in enumerate(self.picks, 1):
|
||||||
self.engine.channel(i, instrument=inst, reverb=0.3)
|
self.engine.channel(i, instrument=inst, reverb=0.3)
|
||||||
self.engine.drums(self.current_drum, volume=0.5)
|
if self.current_drum and self.current_drum not in ("none", "-"):
|
||||||
|
self.engine.drums(self.current_drum, volume=0.5)
|
||||||
self.engine.cc(0, "lowpass", min_val=300, max_val=8000)
|
self.engine.cc(0, "lowpass", min_val=300, max_val=8000)
|
||||||
|
|
||||||
def log(self, msg, color=0):
|
def log(self, msg, color=0):
|
||||||
@@ -516,10 +520,10 @@ class LiveTUI:
|
|||||||
elif verb == "ch" and len(parts) == 2:
|
elif verb == "ch" and len(parts) == 2:
|
||||||
try:
|
try:
|
||||||
n = int(parts[1])
|
n = int(parts[1])
|
||||||
if 1 <= n <= 8:
|
if 1 <= n <= len(self.picks):
|
||||||
self.log(f"Ch {n}: {self.picks[n-1]}", 2)
|
self.log(f"Ch {n}: {self.picks[n-1]}", 2)
|
||||||
else:
|
else:
|
||||||
self.log("Channel 1-8", 4)
|
self.log(f"Channel 1-{len(self.picks)}", 4)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
self.log("ch <1-8> [instrument]", 4)
|
self.log("ch <1-8> [instrument]", 4)
|
||||||
elif verb == "ch" and len(parts) >= 3:
|
elif verb == "ch" and len(parts) >= 3:
|
||||||
@@ -529,8 +533,8 @@ class LiveTUI:
|
|||||||
if inst not in INSTRUMENTS:
|
if inst not in INSTRUMENTS:
|
||||||
self.log(f"Unknown: {inst}", 4)
|
self.log(f"Unknown: {inst}", 4)
|
||||||
return
|
return
|
||||||
if not (1 <= n <= 8):
|
if not (1 <= n <= len(self.picks)):
|
||||||
self.log("Channel 1-8", 4)
|
self.log(f"Channel 1-{len(self.picks)}", 4)
|
||||||
return
|
return
|
||||||
self.picks[n - 1] = inst
|
self.picks[n - 1] = inst
|
||||||
self.engine.channel(n, instrument=inst, reverb=0.3)
|
self.engine.channel(n, instrument=inst, reverb=0.3)
|
||||||
@@ -542,8 +546,8 @@ class LiveTUI:
|
|||||||
n = int(parts[1])
|
n = int(parts[1])
|
||||||
param = parts[2]
|
param = parts[2]
|
||||||
val = float(parts[3])
|
val = float(parts[3])
|
||||||
if not (1 <= n <= 8):
|
if not (1 <= n <= len(self.picks)):
|
||||||
self.log("Channel 1-8", 4)
|
self.log(f"Channel 1-{len(self.picks)}", 4)
|
||||||
return
|
return
|
||||||
if n not in self.engine.channels:
|
if n not in self.engine.channels:
|
||||||
self.log(f"Channel {n} not active", 4)
|
self.log(f"Channel {n} not active", 4)
|
||||||
@@ -708,9 +712,17 @@ class LiveTUI:
|
|||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
seed = int(sys.argv[1]) if len(sys.argv) > 1 else None
|
import argparse
|
||||||
port = sys.argv[2] if len(sys.argv) > 2 else "OP-XY"
|
parser = argparse.ArgumentParser(description="PyTheory Live — real-time MIDI synthesizer")
|
||||||
tui = LiveTUI(seed=seed, port=port)
|
parser.add_argument("seed", nargs="?", type=int, default=None, help="Random seed for instruments")
|
||||||
|
parser.add_argument("--port", "-p", default="OP-XY", help="MIDI port name (default: OP-XY)")
|
||||||
|
parser.add_argument("--channels", "-c", type=int, default=8, help="Number of channels (default: 8)")
|
||||||
|
parser.add_argument("--drums", "-d", default="rock", help="Drum pattern (default: rock, 'none' to disable)")
|
||||||
|
parser.add_argument("--buffer", "-b", type=int, default=128, help="Audio buffer size (default: 128)")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
tui = LiveTUI(seed=args.seed, port=args.port, n_channels=args.channels,
|
||||||
|
drum_pattern=args.drums, buffer_size=args.buffer)
|
||||||
curses.wrapper(tui.run)
|
curses.wrapper(tui.run)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user