From 150c57ed3d9946f7cb15c3197712ae5d53f46a97 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Mon, 30 Mar 2026 01:00:30 -0400 Subject: [PATCH] =?UTF-8?q?Remove=20live=20extras=20from=20pytheory=20?= =?UTF-8?q?=E2=80=94=20split=20to=20pytheory-live=20repo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.6 (1M context) --- pyproject.toml | 4 -- pytheory/live_tui.py | 91 +++++++++++++++++++++++++++++++++++++++++++- pytheory/play.py | 2 +- uv.lock | 30 --------------- 4 files changed, 91 insertions(+), 36 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 6463bc6..7a9cbd1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,9 +26,6 @@ dependencies = [ "rich>=14.3.3", ] -[project.optional-dependencies] -live = ["python-rtmidi>=1.5.8"] - [project.urls] Homepage = "https://github.com/kennethreitz/pytheory" Documentation = "https://pytheory.kennethreitz.org" @@ -37,7 +34,6 @@ Issues = "https://github.com/kennethreitz/pytheory/issues" [project.scripts] pytheory = "pytheory.cli:main" -pytheory-live = "pytheory.live_tui:main" [dependency-groups] dev = ["pytest"] diff --git a/pytheory/live_tui.py b/pytheory/live_tui.py index 46f3e30..7f0fc49 100644 --- a/pytheory/live_tui.py +++ b/pytheory/live_tui.py @@ -163,6 +163,7 @@ class LiveTUI: tab_prefix = "" self.kbd_active = False self._kbd_held = {} # key → last_press_time + self._picker = None # {"channel": int, "index": int, "scroll": int, "filter": str} while self.running: try: @@ -348,6 +349,50 @@ class LiveTUI: except curses.error: pass + # ═══ PICKER OVERLAY ═══ + if self._picker is not None: + filt = self._picker["filter"] + items = [i for i in self.instruments if filt in i] if filt else self.instruments + idx = self._picker["index"] + pw = min(32, w - 4) + ph = min(len(items) + 2, h - 4) + px = (w - pw) // 2 + py = (h - ph) // 2 + # Border + title = f" Ch {self._picker['channel']} " + try: + stdscr.addstr(py, px, "┌" + title + "─" * (pw - 2 - len(title)) + "┐", curses.color_pair(2) | curses.A_BOLD) + for ri in range(1, ph - 1): + stdscr.addstr(py + ri, px, "│" + " " * (pw - 2) + "│", curses.color_pair(2)) + stdscr.addstr(py + ph - 1, px, "└" + "─" * (pw - 2) + "┘", curses.color_pair(2)) + except curses.error: + pass + # Items + vis_h = ph - 2 + scroll = self._picker["scroll"] + if idx < scroll: + scroll = idx + elif idx >= scroll + vis_h: + scroll = idx - vis_h + 1 + self._picker["scroll"] = scroll + for ri in range(vis_h): + li = scroll + ri + if li >= len(items): + break + name = items[li][:pw - 4] + attr = curses.A_REVERSE | curses.color_pair(1) if li == idx else curses.color_pair(0) + try: + padded = f" {name}" + " " * (pw - 3 - len(name)) + stdscr.addstr(py + 1 + ri, px + 1, padded, attr) + except curses.error: + pass + # Filter hint + hint = f"/{filt}" if filt else "type to filter" + try: + stdscr.addstr(py + ph - 1, px + 2, hint[:pw - 4], curses.color_pair(3) | curses.A_DIM) + except curses.error: + pass + stdscr.refresh() # ═══ INPUT ═══ @@ -393,6 +438,35 @@ class LiveTUI: continue + # PICKER MODE + if self._picker is not None: + filt = self._picker["filter"] + items = [i for i in self.instruments if filt in i] if filt else self.instruments + if ch == 27: # Escape cancels + self._picker = None + elif ch == curses.KEY_UP: + self._picker["index"] = max(0, self._picker["index"] - 1) + elif ch == curses.KEY_DOWN: + self._picker["index"] = min(len(items) - 1, self._picker["index"] + 1) + elif ch == 10 or ch == 13: # Enter selects + if items: + inst = items[self._picker["index"]] + n = self._picker["channel"] + self.picks[n - 1] = inst + self.engine.channel(n, instrument=inst, reverb=0.3) + self.log(f"Ch {n} → {inst}", 1) + self._picker = None + elif ch == curses.KEY_BACKSPACE or ch == 127: + if filt: + self._picker["filter"] = filt[:-1] + self._picker["index"] = 0 + self._picker["scroll"] = 0 + elif 32 <= ch < 127: + self._picker["filter"] = filt + chr(ch) + self._picker["index"] = 0 + self._picker["scroll"] = 0 + continue + if ch == 10 or ch == 13: if cmd_buf.strip(): cmd_history.append(cmd_buf) @@ -521,7 +595,10 @@ class LiveTUI: try: n = int(parts[1]) if 1 <= n <= len(self.picks): - self.log(f"Ch {n}: {self.picks[n-1]}", 2) + # Open instrument picker with current instrument pre-selected + current = self.picks[n - 1] + idx = self.instruments.index(current) if current in self.instruments else 0 + self._picker = {"channel": n, "index": idx, "scroll": max(0, idx - 5), "filter": ""} else: self.log(f"Channel 1-{len(self.picks)}", 4) except ValueError: @@ -725,6 +802,18 @@ def main(): drum_pattern=args.drums, buffer_size=args.buffer) curses.wrapper(tui.run) + # Print resume command on exit + cmd_parts = ["pytheory-live", str(tui.seed)] + if tui.port != "OP-XY": + cmd_parts += ["--port", tui.port] + if tui.n_channels != 8: + cmd_parts += ["--channels", str(tui.n_channels)] + if tui.current_drum != "rock": + cmd_parts += ["--drums", tui.current_drum] + if tui.buffer_size != 128: + cmd_parts += ["--buffer", str(tui.buffer_size)] + print(f"\nResume this session with:\n {' '.join(cmd_parts)}\n") + if __name__ == "__main__": main() diff --git a/pytheory/play.py b/pytheory/play.py index aa24727..15da968 100644 --- a/pytheory/play.py +++ b/pytheory/play.py @@ -5421,7 +5421,7 @@ def render_score(score): buzz *= _exp_decay(buzz_len, 25) wave[:buzz_len] = wave[:buzz_len] + buzz.astype(numpy.float32) - mono_hit = wave * vel_scale * 0.7 + mono_hit = wave * vel_scale * 0.7 * drum_part.volume # Sidechain trigger — kick only if hit.sound.value == DrumSound.KICK.value: drum_buf[start:start + hit_len] += mono_hit diff --git a/uv.lock b/uv.lock index 6d04582..c67cb3b 100644 --- a/uv.lock +++ b/uv.lock @@ -699,11 +699,6 @@ dependencies = [ { name = "sounddevice" }, ] -[package.optional-dependencies] -live = [ - { name = "python-rtmidi" }, -] - [package.dev-dependencies] dev = [ { name = "pytest" }, @@ -718,12 +713,10 @@ docs = [ [package.metadata] requires-dist = [ - { name = "python-rtmidi", marker = "extra == 'live'", specifier = ">=1.5.8" }, { name = "rich", specifier = ">=14.3.3" }, { name = "scipy" }, { name = "sounddevice" }, ] -provides-extras = ["live"] [package.metadata.requires-dev] dev = [{ name = "pytest" }] @@ -732,29 +725,6 @@ docs = [ { name = "sphinx" }, ] -[[package]] -name = "python-rtmidi" -version = "1.5.8" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/dd/ee/0f91965dcc471714c69df21e5ca3d94dc81411b7dee2d31ff1184bea07c9/python_rtmidi-1.5.8.tar.gz", hash = "sha256:7f9ade68b068ae09000ecb562ae9521da3a234361ad5449e83fc734544d004fa", size = 368130, upload-time = "2023-11-20T21:55:02.192Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/46/9c/95c0a6a43bd24a17568e1e31008b1fab7e9a2e54c0ed7301e8d5cc9fa109/python_rtmidi-1.5.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:efc07413b30b0039c0d35abe25a81d740c7405124eb58eed141a8f24388e6fe0", size = 148826, upload-time = "2023-11-20T21:54:20.658Z" }, - { url = "https://files.pythonhosted.org/packages/bf/9b/8e452d6edc2c04e3407f542d3185c66ffc2d39c8811cf2b117653a0a4d63/python_rtmidi-1.5.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:844bd12840c9d4e03dfc89b2cd57c55dcbf5ed7246504d69c6c661732249b19c", size = 145363, upload-time = "2023-11-20T21:54:23.023Z" }, - { url = "https://files.pythonhosted.org/packages/b5/48/aa1d4924f7aa238a192d69aa565b315af0037f684c9475e8b860c679a655/python_rtmidi-1.5.8-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:8bbaf7c7164471712a93ac60c8f9ed146b336a294a5103223bbaf8f10709a0bf", size = 293253, upload-time = "2023-11-20T21:54:24.899Z" }, - { url = "https://files.pythonhosted.org/packages/bf/24/32dc239047a56f44d8d8090d55010f85a38ed959ffe517c2e87a2aa34190/python_rtmidi-1.5.8-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:878ce085dfb65c0974810a7e919f73708cbb4c0430c7924b78f25aea1dd4ebee", size = 304051, upload-time = "2023-11-20T21:54:26.82Z" }, - { url = "https://files.pythonhosted.org/packages/9b/0c/cf771eca1b64610e627ca1e67be8390ecdf5e0e1914efbdd9d50ac4c5986/python_rtmidi-1.5.8-cp310-cp310-win_amd64.whl", hash = "sha256:f2138005c6bd3d8b9af05df383679f6d0827d16056e68a941110732310dcb7dd", size = 132157, upload-time = "2023-11-20T21:54:30.059Z" }, - { url = "https://files.pythonhosted.org/packages/4b/0c/23be16b75c90946784b8d233e61db14cf0482def5396821a1ae0bdcd2739/python_rtmidi-1.5.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:30d117193dcad8af67c600c405f53eb096e4ff84849760be14c97270af334922", size = 150205, upload-time = "2023-11-20T21:54:31.849Z" }, - { url = "https://files.pythonhosted.org/packages/4b/12/37d41151b08a292719f05dbeae15475537f8aa291cda34c6634b35916dff/python_rtmidi-1.5.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4e234dca7f9d783dd3f1e9c9c5c2f295f02b7af3085301d6eed3b428cf49d327", size = 146737, upload-time = "2023-11-20T21:54:33.299Z" }, - { url = "https://files.pythonhosted.org/packages/53/5e/b866491545135c699bfbed62f54b93c4d6587afc2bba6e2cbbe898570c32/python_rtmidi-1.5.8-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:271d625c489fffb39b3edc5aba67f7c8e29a04a0a0f056ce19e5a888a08b4c59", size = 294847, upload-time = "2023-11-20T21:54:35.017Z" }, - { url = "https://files.pythonhosted.org/packages/a0/79/1ddb4fb1bdb1a8b8bd62007ca4980344a53b7f29633a7bca1088eed964ce/python_rtmidi-1.5.8-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:46bbf32c8a4bf6c8f0df1c02a68689d0757f13cb7a69f27ccbbed3d7b2365918", size = 305433, upload-time = "2023-11-20T21:54:36.322Z" }, - { url = "https://files.pythonhosted.org/packages/13/ff/2c55797dbf020d462132d1bc5b34d596b400fa197e2a259b8dd2ea2e5954/python_rtmidi-1.5.8-cp311-cp311-win_amd64.whl", hash = "sha256:cfea32c91752fa7aecfe3d6827535c190ba0e646a9accd6604f4fc70cf4b780f", size = 132937, upload-time = "2023-11-20T21:54:38.3Z" }, - { url = "https://files.pythonhosted.org/packages/51/27/887b0378e0a907489a07bdeb808fa5ed349675245c6ee14d9f6d00304f96/python_rtmidi-1.5.8-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5443634597eb340cdec0734f76267a827c2d366f00a6f9195141c78828016ac2", size = 158861, upload-time = "2023-11-20T21:54:39.549Z" }, - { url = "https://files.pythonhosted.org/packages/4d/ec/57cecde253daab896ce53778520cd41eb062641862ebdb0ee6f97511b1d9/python_rtmidi-1.5.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:29d9c9d9f82ce679fecad7bb4cb79f3a24574ea84600e377194b4cc1baacec0e", size = 153416, upload-time = "2023-11-20T21:54:40.835Z" }, - { url = "https://files.pythonhosted.org/packages/6f/5b/dc19c53d9d512b74dc2cca3725591cc612b9465645695a0696352a8c8b54/python_rtmidi-1.5.8-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:25f5a5db7be98911c41ca5bebb262fcf9a7c89600b88fd3c207ceafd3101e721", size = 305696, upload-time = "2023-11-20T21:54:42.037Z" }, - { url = "https://files.pythonhosted.org/packages/f6/92/5a60f56dfb2740e644e932233928947423cd2101895319b331f84527eb31/python_rtmidi-1.5.8-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:cec30924e305f55284594ccf35a71dee7216fd308dfa2dec1b3ed03e6f243803", size = 315579, upload-time = "2023-11-20T21:54:43.339Z" }, - { url = "https://files.pythonhosted.org/packages/93/46/6af077d262f521ea2bf1ab60b8aad72f34fe6dd55af739176605369d449c/python_rtmidi-1.5.8-cp312-cp312-win_amd64.whl", hash = "sha256:052c89933cae4fca354012d8ca7248f4f9e1e3f062471409d48415a7f7d7e59e", size = 129755, upload-time = "2023-11-20T21:54:44.935Z" }, -] - [[package]] name = "pyyaml" version = "6.0.3"