pycodedj 0.2.1__tar.gz → 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. {pycodedj-0.2.1 → pycodedj-0.3.0}/CHANGELOG.md +24 -0
  2. {pycodedj-0.2.1 → pycodedj-0.3.0}/PKG-INFO +34 -6
  3. {pycodedj-0.2.1 → pycodedj-0.3.0}/README.ja.md +33 -5
  4. {pycodedj-0.2.1 → pycodedj-0.3.0}/README.md +33 -5
  5. {pycodedj-0.2.1 → pycodedj-0.3.0}/docs/manual.html +60 -2
  6. {pycodedj-0.2.1 → pycodedj-0.3.0}/docs/manual.ja.md +86 -2
  7. {pycodedj-0.2.1 → pycodedj-0.3.0}/docs/manual.md +86 -2
  8. {pycodedj-0.2.1 → pycodedj-0.3.0}/pyproject.toml +1 -1
  9. {pycodedj-0.2.1 → pycodedj-0.3.0}/sc/synths.scd +15 -0
  10. {pycodedj-0.2.1 → pycodedj-0.3.0}/src/pycodedj/__init__.py +1 -1
  11. pycodedj-0.3.0/src/pycodedj/__main__.py +256 -0
  12. {pycodedj-0.2.1 → pycodedj-0.3.0}/src/pycodedj/block_parser.py +11 -4
  13. pycodedj-0.3.0/src/pycodedj/engine.py +103 -0
  14. {pycodedj-0.2.1 → pycodedj-0.3.0}/src/pycodedj/osc_bridge.py +5 -0
  15. {pycodedj-0.2.1 → pycodedj-0.3.0}/src/pycodedj/watcher.py +8 -4
  16. {pycodedj-0.2.1 → pycodedj-0.3.0}/tests/test_block_parser.py +33 -13
  17. pycodedj-0.3.0/tests/test_engine.py +239 -0
  18. {pycodedj-0.2.1 → pycodedj-0.3.0}/tests/test_osc_bridge.py +27 -0
  19. {pycodedj-0.2.1 → pycodedj-0.3.0}/tests/test_watcher.py +17 -0
  20. pycodedj-0.2.1/src/pycodedj/__main__.py +0 -131
  21. pycodedj-0.2.1/src/pycodedj/engine.py +0 -43
  22. pycodedj-0.2.1/tests/test_engine.py +0 -50
  23. {pycodedj-0.2.1 → pycodedj-0.3.0}/.github/workflows/workflow.yml +0 -0
  24. {pycodedj-0.2.1 → pycodedj-0.3.0}/.gitignore +0 -0
  25. {pycodedj-0.2.1 → pycodedj-0.3.0}/LICENSE +0 -0
  26. {pycodedj-0.2.1 → pycodedj-0.3.0}/docs/CNAME +0 -0
  27. {pycodedj-0.2.1 → pycodedj-0.3.0}/docs/index.html +0 -0
  28. {pycodedj-0.2.1 → pycodedj-0.3.0}/examples/club_set.py +0 -0
  29. {pycodedj-0.2.1 → pycodedj-0.3.0}/examples/demo.py +0 -0
  30. {pycodedj-0.2.1 → pycodedj-0.3.0}/examples/hello_sc.py +0 -0
  31. {pycodedj-0.2.1 → pycodedj-0.3.0}/examples/sound_showcase.py +0 -0
  32. {pycodedj-0.2.1 → pycodedj-0.3.0}/src/pycodedj/_loop.py +0 -0
  33. {pycodedj-0.2.1 → pycodedj-0.3.0}/src/pycodedj/analyzer.py +0 -0
  34. {pycodedj-0.2.1 → pycodedj-0.3.0}/src/pycodedj/mapper.py +0 -0
  35. {pycodedj-0.2.1 → pycodedj-0.3.0}/tests/__init__.py +0 -0
  36. {pycodedj-0.2.1 → pycodedj-0.3.0}/tests/test_analyzer.py +0 -0
  37. {pycodedj-0.2.1 → pycodedj-0.3.0}/tests/test_mapper.py +0 -0
@@ -1,5 +1,29 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.3.0] - 2026-05-08
4
+
5
+ ### Features
6
+
7
+ - `pycodedj panic` — 全アクティブループを即時停止。SuperCollider 側でシンセを解放し `~loops` / `~loopParams` を初期化する
8
+ - `pycodedj mute <name>` — ループを消音(停止しない)。再評価時もミュート状態を維持
9
+ - `pycodedj unmute <name>` — ミュート解除、音量を復元
10
+ - `pycodedj solo <name>` — 対象ループ以外を全ミュート(CLI は OSC 直送、完全な状態管理は watch セッション内の `Engine.solo()` 推奨)
11
+ - `pycodedj unsolo` — ソロ解除、solo 前のミュート状態に戻す
12
+ - `pycodedj status` — アクティブループの名前・ミュート状態・音量・カットオフを表示
13
+
14
+ ### Improvements
15
+
16
+ - `ParseResult` dataclass 導入 — `parse_blocks()` が SyntaxError 時に `ParseResult(ok=False, error=...)` を返すようになり、watch モードでコード編集中に構文エラーがあっても演奏中のループが止まらなくなった
17
+ - `Engine` 内部状態を `LoopState` dataclass で管理 — `muted` / `muted_before_solo` フラグを保持し、mute/solo/unsolo の状態を正確に追跡
18
+ - `eval_block` が OSC 送信成功後に内部状態を更新するよう修正(送信失敗時の状態不整合を解消)
19
+ - `Engine.solo()` に未知のループ名ガードを追加(存在しない名前を渡しても全ループがミュートされない)
20
+ - CLI `unmute` が SuperCollider に永続化された `amp=0` を上書きしてから voice_count を送るよう修正
21
+
22
+ ### Docs
23
+
24
+ - README.md / README.ja.md — panic / mute / unmute / status のクイックリファレンスを追加、ロードマップの Sprint 1 を完了に更新
25
+ - `docs/manual.md` / `docs/manual.ja.md` / `docs/manual.html` — セクション 12 に新コマンド 6 つの全リファレンスを追加
26
+
3
27
  ## [0.2.1] - 2026-05-08
4
28
 
5
29
  ### Docs
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pycodedj
3
- Version: 0.2.1
3
+ Version: 0.3.0
4
4
  License: MIT License with Commons Clause
5
5
 
6
6
  "Commons Clause" License Condition v1.0
@@ -191,6 +191,33 @@ pycodedj watch demo.py
191
191
 
192
192
  From here, just write code and save.
193
193
 
194
+ **5. Emergency stop**
195
+
196
+ ```bash
197
+ pycodedj panic
198
+ ```
199
+
200
+ Sends a stop signal to all active loops immediately. Use this if something goes wrong during a performance.
201
+
202
+ **6. Mute / solo**
203
+
204
+ ```bash
205
+ pycodedj mute bass # silence a loop without stopping it
206
+ pycodedj unmute bass # restore its sound
207
+ pycodedj solo pad # mute all loops except pad
208
+ pycodedj unsolo # release solo, restore previous mute state
209
+ ```
210
+
211
+ Note: `mute`, `unmute`, `solo`, and `unsolo` send OSC directly to SuperCollider. For full state management, call `Engine.mute()` / `Engine.solo()` from within a watch session.
212
+
213
+ **7. Loop status**
214
+
215
+ ```bash
216
+ pycodedj status
217
+ ```
218
+
219
+ Prints the name, mute state, amplitude, and filter cutoff of each active loop.
220
+
194
221
  ---
195
222
 
196
223
  ## Example Files
@@ -275,11 +302,12 @@ External visualisers such as Hydra can receive the same parameters on a separate
275
302
 
276
303
  ## Roadmap
277
304
 
278
- - [x] Spec design and mapping design
279
- - [ ] Phase 0: Listening validation of mapping hypotheses
280
- - [x] Phase 1: Python SuperCollider OSC prototype
281
- - [x] Phase 2: Hot-reload live loop implementation (`pycodedj watch`)
282
- - [ ] Phase 3: Hydra visualiser integration
305
+ - [x] Python SuperCollider OSC prototype
306
+ - [x] Hot-reload live loop implementation (`pycodedj watch`)
307
+ - [x] Sprint 1: Live stability (`panic`, SyntaxError recovery, `mute`/`solo`, `status`)
308
+ - [ ] Sprint 2: Music DSL (`pattern()`, `sample()`, `@loop` parameter expansion, mapping modes)
309
+ - [ ] Sprint 3: Sound design and playability (SynthDef cleanup, `bpm`, `list-synths`)
310
+ - [ ] Sprint 4: README and manual refresh, Hydra visualiser integration
283
311
 
284
312
  ---
285
313
 
@@ -129,6 +129,33 @@ pycodedj watch demo.py
129
129
 
130
130
  あとはエディタでコードを書いて保存するだけです。
131
131
 
132
+ **5. 緊急停止**
133
+
134
+ ```bash
135
+ pycodedj panic
136
+ ```
137
+
138
+ 全アクティブループに停止信号を即時送信します。演奏中に問題が起きたときに使います。
139
+
140
+ **6. ミュート / ソロ**
141
+
142
+ ```bash
143
+ pycodedj mute bass # ループを消音(停止はしない)
144
+ pycodedj unmute bass # 音量を元に戻す
145
+ pycodedj solo pad # pad 以外を全ミュート
146
+ pycodedj unsolo # ソロ解除、ミュート状態を元に戻す
147
+ ```
148
+
149
+ 注意: `mute` / `unmute` / `solo` / `unsolo` は OSC を直接 SuperCollider に送信します。完全な状態管理が必要な場合は watch セッション内で `Engine.mute()` / `Engine.solo()` を呼び出してください。
150
+
151
+ **7. ループのステータス確認**
152
+
153
+ ```bash
154
+ pycodedj status
155
+ ```
156
+
157
+ アクティブなループの名前・ミュート状態・音量・フィルターカットオフを表示します。
158
+
132
159
  ---
133
160
 
134
161
  ## サンプルファイル
@@ -213,11 +240,12 @@ Hydra 等の外部ビジュアライザーへは同じパラメーターを別
213
240
 
214
241
  ## ロードマップ
215
242
 
216
- - [x] 仕様設計・マッピング設計
217
- - [ ] フェーズ0: マッピング仮説の聴取検証
218
- - [x] フェーズ1: Python SuperCollider OSC プロトタイプ
219
- - [x] フェーズ2: ホットリロード・ライブループ実装(`pycodedj watch`)
220
- - [ ] フェーズ3: Hydra ビジュアライザー統合
243
+ - [x] Python → SuperCollider OSC プロトタイプ
244
+ - [x] ホットリロード・ライブループ実装(`pycodedj watch`)
245
+ - [x] Sprint 1: ライブ安定性(`panic`, SyntaxError維持, `mute`/`solo`, `status`)
246
+ - [ ] Sprint 2: 音楽DSL(`pattern()`, `sample()`, `@loop` パラメータ拡張, mapping モード)
247
+ - [ ] Sprint 3: 音色・演奏性(SynthDef整理, `bpm`, `list-synths`)
248
+ - [ ] Sprint 4: README・マニュアル刷新、Hydra ビジュアライザー統合
221
249
 
222
250
  ---
223
251
 
@@ -129,6 +129,33 @@ pycodedj watch demo.py
129
129
 
130
130
  From here, just write code and save.
131
131
 
132
+ **5. Emergency stop**
133
+
134
+ ```bash
135
+ pycodedj panic
136
+ ```
137
+
138
+ Sends a stop signal to all active loops immediately. Use this if something goes wrong during a performance.
139
+
140
+ **6. Mute / solo**
141
+
142
+ ```bash
143
+ pycodedj mute bass # silence a loop without stopping it
144
+ pycodedj unmute bass # restore its sound
145
+ pycodedj solo pad # mute all loops except pad
146
+ pycodedj unsolo # release solo, restore previous mute state
147
+ ```
148
+
149
+ Note: `mute`, `unmute`, `solo`, and `unsolo` send OSC directly to SuperCollider. For full state management, call `Engine.mute()` / `Engine.solo()` from within a watch session.
150
+
151
+ **7. Loop status**
152
+
153
+ ```bash
154
+ pycodedj status
155
+ ```
156
+
157
+ Prints the name, mute state, amplitude, and filter cutoff of each active loop.
158
+
132
159
  ---
133
160
 
134
161
  ## Example Files
@@ -213,11 +240,12 @@ External visualisers such as Hydra can receive the same parameters on a separate
213
240
 
214
241
  ## Roadmap
215
242
 
216
- - [x] Spec design and mapping design
217
- - [ ] Phase 0: Listening validation of mapping hypotheses
218
- - [x] Phase 1: Python SuperCollider OSC prototype
219
- - [x] Phase 2: Hot-reload live loop implementation (`pycodedj watch`)
220
- - [ ] Phase 3: Hydra visualiser integration
243
+ - [x] Python SuperCollider OSC prototype
244
+ - [x] Hot-reload live loop implementation (`pycodedj watch`)
245
+ - [x] Sprint 1: Live stability (`panic`, SyntaxError recovery, `mute`/`solo`, `status`)
246
+ - [ ] Sprint 2: Music DSL (`pattern()`, `sample()`, `@loop` parameter expansion, mapping modes)
247
+ - [ ] Sprint 3: Sound design and playability (SynthDef cleanup, `bpm`, `list-synths`)
248
+ - [ ] Sprint 4: README and manual refresh, Hydra visualiser integration
221
249
 
222
250
  ---
223
251
 
@@ -428,7 +428,7 @@ def my_sound(volume=0.4):
428
428
 
429
429
  <p>次のように表示されれば成功です。</p>
430
430
 
431
- <pre data-lang="text"><code>usage: pycodedj [-h] {eval,watch} ...</code></pre>
431
+ <pre data-lang="text"><code>usage: pycodedj [-h] {eval,watch,panic,mute,unmute,solo,unsolo,status} ...</code></pre>
432
432
 
433
433
  <h3>SuperCollider をインストールする</h3>
434
434
  <p>SuperCollider の公式サイト(supercollider.github.io)からインストーラーをダウンロードします。</p>
@@ -1312,6 +1312,64 @@ pycodedj eval demo.py::pad --sc-port 57200</code></pre>
1312
1312
  pycodedj watch club_set.py --debounce 0.5
1313
1313
  pycodedj watch myfile.py --sc-host 192.168.1.10</code></pre>
1314
1314
 
1315
+ <h3><code>pycodedj panic</code></h3>
1316
+ <p>全アクティブループに即時停止信号を送ります。演奏中に問題が起きたとき、すべての音をすぐ止めたい場合に使います。SuperCollider 側で実行中のシンセをすべて解放し、ループ状態を初期化します。</p>
1317
+
1318
+ <pre data-lang="text"><code>pycodedj panic [--sc-host HOST] [--sc-port PORT]</code></pre>
1319
+
1320
+ <p><strong>使用例:</strong></p>
1321
+ <pre data-lang="bash"><code id="code-s12-3">pycodedj panic</code></pre>
1322
+
1323
+ <h3><code>pycodedj mute</code></h3>
1324
+ <p>ループを消音します(停止はしません)。ループは引き続き管理され、<code>unmute</code> で音量が復元します。</p>
1325
+
1326
+ <pre data-lang="text"><code>pycodedj mute NAME [--sc-host HOST] [--sc-port PORT]</code></pre>
1327
+
1328
+ <table>
1329
+ <thead>
1330
+ <tr><th>引数・オプション</th><th>説明</th><th>デフォルト</th></tr>
1331
+ </thead>
1332
+ <tbody>
1333
+ <tr><td><code>NAME</code></td><td>ループ名</td><td>—</td></tr>
1334
+ <tr><td><code>--sc-host</code></td><td>SuperCollider のホスト</td><td><code>127.0.0.1</code></td></tr>
1335
+ <tr><td><code>--sc-port</code></td><td>SuperCollider の受信ポート番号</td><td><code>57120</code></td></tr>
1336
+ </tbody>
1337
+ </table>
1338
+
1339
+ <p><strong>使用例:</strong></p>
1340
+ <pre data-lang="bash"><code id="code-s12-4">pycodedj mute bass</code></pre>
1341
+
1342
+ <p><strong>注意:</strong> CLI は OSC を直接 SuperCollider に送信します。ループを再評価してもミュート状態を維持したい場合は、watch セッション内で <code>Engine.mute()</code> を呼び出してください。</p>
1343
+
1344
+ <h3><code>pycodedj unmute</code></h3>
1345
+ <p>ミュートしたループの音量を元に戻します。</p>
1346
+
1347
+ <pre data-lang="text"><code>pycodedj unmute NAME [--sc-host HOST] [--sc-port PORT]</code></pre>
1348
+
1349
+ <p><strong>使用例:</strong></p>
1350
+ <pre data-lang="bash"><code id="code-s12-5">pycodedj unmute bass</code></pre>
1351
+
1352
+ <h3><code>pycodedj solo</code></h3>
1353
+ <p><strong>CLI からはサポートされていません。</strong> Solo は watch プロセスの状態へのアクセスが必要です。watch セッション内で <code>Engine.solo()</code> を直接呼び出してください。</p>
1354
+
1355
+ <h3><code>pycodedj unsolo</code></h3>
1356
+ <p><strong>CLI からはサポートされていません。</strong> watch セッション内で <code>Engine.unsolo()</code> を直接呼び出してください。</p>
1357
+
1358
+ <h3><code>pycodedj status</code></h3>
1359
+ <p>アクティブなループの名前・ミュート状態・音量・フィルターカットオフを表示します。</p>
1360
+
1361
+ <pre data-lang="text"><code>pycodedj status [--sc-host HOST] [--sc-port PORT]</code></pre>
1362
+
1363
+ <p>出力形式:</p>
1364
+ <pre data-lang="text"><code>Loop State Amp Cutoff
1365
+ bass playing 0.40 1340Hz
1366
+ pad muted 0.15 200Hz</code></pre>
1367
+
1368
+ <p><strong>注意:</strong> <code>status</code> は <code>pycodedj watch</code> とは別プロセスで動作するため、watch プロセスの状態を読み取れません。CLI から単独で実行すると「no active loops」と表示されます。</p>
1369
+
1370
+ <p><strong>使用例:</strong></p>
1371
+ <pre data-lang="bash"><code id="code-s12-6">pycodedj status</code></pre>
1372
+
1315
1373
  <h3>ループの書き方</h3>
1316
1374
 
1317
1375
  <pre data-lang="python"><code id="code-s12-3">from pycodedj import loop
@@ -1465,7 +1523,7 @@ bridge = OscBridge(audio=OscEndpoint("127.0.0.1", 57120))
1465
1523
  engine = Engine(bridge=bridge)
1466
1524
 
1467
1525
  source = open("demo.py").read()
1468
- blocks = {b.name: b for b in parse_blocks(source)}
1526
+ blocks = {b.name: b for b in parse_blocks(source).blocks}
1469
1527
 
1470
1528
  params = engine.eval_block(blocks["bass"])
1471
1529
  if params is not None:
@@ -117,7 +117,7 @@ pycodedj --help
117
117
  次のように表示されれば成功です。
118
118
 
119
119
  ```
120
- usage: pycodedj [-h] {eval,watch} ...
120
+ usage: pycodedj [-h] {eval,watch,panic,mute,unmute,solo,unsolo,status} ...
121
121
  ```
122
122
 
123
123
  ### SuperCollider をインストールする
@@ -987,6 +987,90 @@ pycodedj watch club_set.py --debounce 0.5
987
987
  pycodedj watch myfile.py --sc-host 192.168.1.10
988
988
  ```
989
989
 
990
+ ### `pycodedj panic`
991
+
992
+ 全アクティブループに即時停止信号を送ります。
993
+
994
+ ```
995
+ pycodedj panic [--sc-host HOST] [--sc-port PORT]
996
+ ```
997
+
998
+ 演奏中に問題が起きたとき、すべての音をすぐ止めたい場合に使います。SuperCollider 側で実行中のシンセをすべて解放し、ループ状態を初期化します。
999
+
1000
+ **使用例:**
1001
+
1002
+ ```bash
1003
+ pycodedj panic
1004
+ ```
1005
+
1006
+ ### `pycodedj mute`
1007
+
1008
+ ループを消音します(停止はしません)。ループは引き続き管理され、unmute で音量が復元します。
1009
+
1010
+ ```
1011
+ pycodedj mute NAME [--sc-host HOST] [--sc-port PORT]
1012
+ ```
1013
+
1014
+ | 引数・オプション | 説明 | デフォルト |
1015
+ | :--- | :--- | :--- |
1016
+ | `NAME` | ループ名 | — |
1017
+ | `--sc-host` | SuperCollider のホスト | `127.0.0.1` |
1018
+ | `--sc-port` | SuperCollider の受信ポート番号 | `57120` |
1019
+
1020
+ **使用例:**
1021
+
1022
+ ```bash
1023
+ pycodedj mute bass
1024
+ ```
1025
+
1026
+ > **注意:** CLI は OSC を直接 SuperCollider に送信します。ループを再評価してもミュート状態を維持したい場合は、watch セッション内で `Engine.mute()` を呼び出してください。
1027
+
1028
+ ### `pycodedj unmute`
1029
+
1030
+ ミュートしたループの音量を元に戻します。
1031
+
1032
+ ```
1033
+ pycodedj unmute NAME [--sc-host HOST] [--sc-port PORT]
1034
+ ```
1035
+
1036
+ **使用例:**
1037
+
1038
+ ```bash
1039
+ pycodedj unmute bass
1040
+ ```
1041
+
1042
+ ### `pycodedj solo`
1043
+
1044
+ > **CLI からはサポートされていません。** Solo は watch プロセスの状態へのアクセスが必要です。watch セッション内で `Engine.solo()` を直接呼び出してください。
1045
+
1046
+ ### `pycodedj unsolo`
1047
+
1048
+ > **CLI からはサポートされていません。** watch セッション内で `Engine.unsolo()` を直接呼び出してください。
1049
+
1050
+ ### `pycodedj status`
1051
+
1052
+ アクティブなループの名前・ミュート状態・音量・フィルターカットオフを表示します。
1053
+
1054
+ ```
1055
+ pycodedj status [--sc-host HOST] [--sc-port PORT]
1056
+ ```
1057
+
1058
+ 出力形式:
1059
+
1060
+ ```
1061
+ Loop State Amp Cutoff
1062
+ bass playing 0.40 1340Hz
1063
+ pad muted 0.15 200Hz
1064
+ ```
1065
+
1066
+ > **注意:** `status` は `pycodedj watch` とは別プロセスで動作するため、watch プロセスの状態を読み取ることができません。CLI から単独で実行すると「no active loops」と表示されます。
1067
+
1068
+ **使用例:**
1069
+
1070
+ ```bash
1071
+ pycodedj status
1072
+ ```
1073
+
990
1074
  ### ループの書き方
991
1075
 
992
1076
  ```python
@@ -1048,7 +1132,7 @@ bridge = OscBridge(audio=OscEndpoint("127.0.0.1", 57120))
1048
1132
  engine = Engine(bridge=bridge)
1049
1133
 
1050
1134
  source = open("demo.py").read()
1051
- blocks = {b.name: b for b in parse_blocks(source)}
1135
+ blocks = {b.name: b for b in parse_blocks(source).blocks}
1052
1136
 
1053
1137
  params = engine.eval_block(blocks["bass"])
1054
1138
  if params is not None:
@@ -117,7 +117,7 @@ pycodedj --help
117
117
  If you see this, you're good:
118
118
 
119
119
  ```
120
- usage: pycodedj [-h] {eval,watch} ...
120
+ usage: pycodedj [-h] {eval,watch,panic,mute,unmute,solo,unsolo,status} ...
121
121
  ```
122
122
 
123
123
  ### Installing SuperCollider
@@ -985,6 +985,90 @@ pycodedj watch club_set.py --debounce 0.5
985
985
  pycodedj watch myfile.py --sc-host 192.168.1.10
986
986
  ```
987
987
 
988
+ ### `pycodedj panic`
989
+
990
+ Sends an immediate stop signal to all active loops.
991
+
992
+ ```
993
+ pycodedj panic [--sc-host HOST] [--sc-port PORT]
994
+ ```
995
+
996
+ Use this when something goes wrong during a performance and you need everything to stop now. SuperCollider frees all running synths and clears its loop state.
997
+
998
+ **Example:**
999
+
1000
+ ```bash
1001
+ pycodedj panic
1002
+ ```
1003
+
1004
+ ### `pycodedj mute`
1005
+
1006
+ Silences a loop without stopping it. The loop continues to be tracked; unmuting restores its sound.
1007
+
1008
+ ```
1009
+ pycodedj mute NAME [--sc-host HOST] [--sc-port PORT]
1010
+ ```
1011
+
1012
+ | Argument / option | Description | Default |
1013
+ | :--- | :--- | :--- |
1014
+ | `NAME` | Loop name | — |
1015
+ | `--sc-host` | SuperCollider host | `127.0.0.1` |
1016
+ | `--sc-port` | SuperCollider receive port | `57120` |
1017
+
1018
+ **Example:**
1019
+
1020
+ ```bash
1021
+ pycodedj mute bass
1022
+ ```
1023
+
1024
+ > **Note:** The CLI sends OSC directly to SuperCollider. For full state tracking (so that re-evaluating a loop while muted keeps it silent), call `Engine.mute()` within a watch session instead.
1025
+
1026
+ ### `pycodedj unmute`
1027
+
1028
+ Restores sound for a muted loop.
1029
+
1030
+ ```
1031
+ pycodedj unmute NAME [--sc-host HOST] [--sc-port PORT]
1032
+ ```
1033
+
1034
+ **Example:**
1035
+
1036
+ ```bash
1037
+ pycodedj unmute bass
1038
+ ```
1039
+
1040
+ ### `pycodedj solo`
1041
+
1042
+ > **Not supported from the CLI.** Solo requires access to the watch process state. Call `Engine.solo()` directly within a watch session.
1043
+
1044
+ ### `pycodedj unsolo`
1045
+
1046
+ > **Not supported from the CLI.** Call `Engine.unsolo()` directly within a watch session.
1047
+
1048
+ ### `pycodedj status`
1049
+
1050
+ Prints the name, mute state, amplitude, and filter cutoff for each active loop.
1051
+
1052
+ ```
1053
+ pycodedj status [--sc-host HOST] [--sc-port PORT]
1054
+ ```
1055
+
1056
+ Output format:
1057
+
1058
+ ```
1059
+ Loop State Amp Cutoff
1060
+ bass playing 0.40 1340Hz
1061
+ pad muted 0.15 200Hz
1062
+ ```
1063
+
1064
+ > **Note:** `status` runs in a separate process from `pycodedj watch` and cannot read the watch process state. It will show "no active loops" when invoked from the CLI.
1065
+
1066
+ **Example:**
1067
+
1068
+ ```bash
1069
+ pycodedj status
1070
+ ```
1071
+
988
1072
  ### Loop syntax
989
1073
 
990
1074
  ```python
@@ -1046,7 +1130,7 @@ bridge = OscBridge(audio=OscEndpoint("127.0.0.1", 57120))
1046
1130
  engine = Engine(bridge=bridge)
1047
1131
 
1048
1132
  source = open("demo.py").read()
1049
- blocks = {b.name: b for b in parse_blocks(source)}
1133
+ blocks = {b.name: b for b in parse_blocks(source).blocks}
1050
1134
 
1051
1135
  params = engine.eval_block(blocks["bass"])
1052
1136
  if params is not None:
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "pycodedj"
3
- version = "0.2.1"
3
+ version = "0.3.0"
4
4
  requires-python = ">=3.10"
5
5
  readme = "README.md"
6
6
  license = {file = "LICENSE"}
@@ -669,6 +669,21 @@ s.waitForBoot({
669
669
  // OSCFunc with nil path receives every incoming OSC message and we filter by prefix.
670
670
  // var は SC では実行文より前に宣言しなければならないため、
671
671
  // すべての var をクロージャ先頭にまとめる。
672
+ // ── Panic handler ────────────────────────────────────────────────────────
673
+ // /pycodedj/panic: すべての Synth を即停止し ~loops / ~loopParams を初期化する。
674
+ // OSCFunc は再登録時に上書きされるため、スクリプト再実行時でも安全。
675
+ if (~pycodedjPanic.notNil) { ~pycodedjPanic.free };
676
+ ~pycodedjPanic = OSCFunc({ |msg, time, addr|
677
+ ~loops.keysValuesDo({ |name, synths|
678
+ if (synths.notNil) {
679
+ synths.do({ |synth| synth.set(\gate, 0) });
680
+ };
681
+ });
682
+ ~loops = Dictionary.new;
683
+ ~loopParams = Dictionary.new;
684
+ "PyCodeDJ panic: all loops stopped.".postln;
685
+ }, '/pycodedj/panic');
686
+
672
687
  if (~pycodedjOSC.notNil) {
673
688
  thisProcess.removeOSCRecvFunc(~pycodedjOSC);
674
689
  };
@@ -2,5 +2,5 @@
2
2
 
3
3
  from ._loop import loop
4
4
 
5
- __version__ = "0.2.1"
5
+ __version__ = "0.3.0"
6
6
  __all__ = ["loop"]