pycodedj 0.1.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.
- pycodedj-0.1.0/.github/workflows/workflow.yml +46 -0
- pycodedj-0.1.0/.gitignore +79 -0
- pycodedj-0.1.0/CHANGELOG.md +17 -0
- pycodedj-0.1.0/PKG-INFO +11 -0
- pycodedj-0.1.0/README.ja.md +211 -0
- pycodedj-0.1.0/README.md +211 -0
- pycodedj-0.1.0/docs/manual.ja.md +813 -0
- pycodedj-0.1.0/docs/manual.md +811 -0
- pycodedj-0.1.0/examples/club_set.py +71 -0
- pycodedj-0.1.0/examples/demo.py +23 -0
- pycodedj-0.1.0/examples/hello_sc.py +29 -0
- pycodedj-0.1.0/pyproject.toml +33 -0
- pycodedj-0.1.0/sc/synths.scd +151 -0
- pycodedj-0.1.0/src/pycodedj/__init__.py +3 -0
- pycodedj-0.1.0/src/pycodedj/__main__.py +130 -0
- pycodedj-0.1.0/src/pycodedj/analyzer.py +65 -0
- pycodedj-0.1.0/src/pycodedj/block_parser.py +45 -0
- pycodedj-0.1.0/src/pycodedj/engine.py +36 -0
- pycodedj-0.1.0/src/pycodedj/mapper.py +46 -0
- pycodedj-0.1.0/src/pycodedj/osc_bridge.py +51 -0
- pycodedj-0.1.0/src/pycodedj/watcher.py +108 -0
- pycodedj-0.1.0/tests/__init__.py +0 -0
- pycodedj-0.1.0/tests/test_analyzer.py +95 -0
- pycodedj-0.1.0/tests/test_block_parser.py +66 -0
- pycodedj-0.1.0/tests/test_engine.py +50 -0
- pycodedj-0.1.0/tests/test_mapper.py +78 -0
- pycodedj-0.1.0/tests/test_osc_bridge.py +89 -0
- pycodedj-0.1.0/tests/test_watcher.py +194 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: read
|
|
9
|
+
id-token: write
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
publish:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
with:
|
|
18
|
+
ref: ${{ github.event.release.tag_name }}
|
|
19
|
+
|
|
20
|
+
- uses: actions/setup-python@v5
|
|
21
|
+
with:
|
|
22
|
+
python-version: "3.12"
|
|
23
|
+
|
|
24
|
+
- name: Verify tag matches pyproject.toml version
|
|
25
|
+
run: |
|
|
26
|
+
TAG="${{ github.event.release.tag_name }}"
|
|
27
|
+
PKG_VERSION=$(python -c "
|
|
28
|
+
import tomllib
|
|
29
|
+
with open('pyproject.toml', 'rb') as f:
|
|
30
|
+
print(tomllib.load(f)['project']['version'])
|
|
31
|
+
")
|
|
32
|
+
EXPECTED="v${PKG_VERSION}"
|
|
33
|
+
if [ "$TAG" != "$EXPECTED" ]; then
|
|
34
|
+
echo "Tag '$TAG' does not match pyproject.toml version '$EXPECTED'" >&2
|
|
35
|
+
exit 1
|
|
36
|
+
fi
|
|
37
|
+
echo "Version check passed: $TAG"
|
|
38
|
+
|
|
39
|
+
- name: Install build dependencies
|
|
40
|
+
run: pip install build hatchling
|
|
41
|
+
|
|
42
|
+
- name: Build wheel and sdist
|
|
43
|
+
run: python -m build
|
|
44
|
+
|
|
45
|
+
- name: Publish to PyPI
|
|
46
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# OS
|
|
2
|
+
.DS_Store
|
|
3
|
+
Thumbs.db
|
|
4
|
+
|
|
5
|
+
# Editor
|
|
6
|
+
.vscode/
|
|
7
|
+
.idea/
|
|
8
|
+
*.swp
|
|
9
|
+
*.swo
|
|
10
|
+
|
|
11
|
+
# Environment
|
|
12
|
+
.env
|
|
13
|
+
.env.local
|
|
14
|
+
.env.*.local
|
|
15
|
+
|
|
16
|
+
# Secrets / Keys
|
|
17
|
+
*.pem
|
|
18
|
+
*.key
|
|
19
|
+
*.p12
|
|
20
|
+
*.pfx
|
|
21
|
+
secrets/
|
|
22
|
+
credentials/
|
|
23
|
+
|
|
24
|
+
# Python
|
|
25
|
+
__pycache__/
|
|
26
|
+
*.py[cod]
|
|
27
|
+
*.pyo
|
|
28
|
+
.venv/
|
|
29
|
+
venv/
|
|
30
|
+
env/
|
|
31
|
+
*.egg-info/
|
|
32
|
+
dist/
|
|
33
|
+
build/
|
|
34
|
+
.pytest_cache/
|
|
35
|
+
.mypy_cache/
|
|
36
|
+
.ruff_cache/
|
|
37
|
+
|
|
38
|
+
# Node.js
|
|
39
|
+
node_modules/
|
|
40
|
+
npm-debug.log*
|
|
41
|
+
yarn-debug.log*
|
|
42
|
+
yarn-error.log*
|
|
43
|
+
.pnpm-debug.log*
|
|
44
|
+
|
|
45
|
+
# Logs
|
|
46
|
+
*.log
|
|
47
|
+
logs/
|
|
48
|
+
|
|
49
|
+
# AI Agent
|
|
50
|
+
.claude/settings.local.json
|
|
51
|
+
.claude/todos/
|
|
52
|
+
.claude/mcp*.json
|
|
53
|
+
.codex
|
|
54
|
+
.claude/
|
|
55
|
+
AGENT.md
|
|
56
|
+
|
|
57
|
+
# Claude Code
|
|
58
|
+
CLAUDE.local.md
|
|
59
|
+
CLAUDE.md
|
|
60
|
+
|
|
61
|
+
# LLM / Vector DB
|
|
62
|
+
*.chroma/
|
|
63
|
+
chroma_db/
|
|
64
|
+
vector_store/
|
|
65
|
+
embeddings/
|
|
66
|
+
*.faiss
|
|
67
|
+
*.pkl
|
|
68
|
+
|
|
69
|
+
# Agent state / memory
|
|
70
|
+
agent_state/
|
|
71
|
+
agent_memory/
|
|
72
|
+
sessions/
|
|
73
|
+
checkpoints/
|
|
74
|
+
runs/
|
|
75
|
+
|
|
76
|
+
# Temporary
|
|
77
|
+
tmp/
|
|
78
|
+
temp/
|
|
79
|
+
.cache/
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [0.1.0] - 2026-05-07
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
- `pycodedj eval FILE::LOOP` — evaluate a loop block and send OSC parameters to SuperCollider; prints feedback (`cutoff`, `lfo_rate`, `reverb_mix`, `voice_count`) on success and exits with code 1 on failure
|
|
8
|
+
- `pycodedj watch FILE` — watch a file and re-evaluate all loops on every save; handles atomic saves (vim/Emacs rename-based writes) via `on_moved` and `on_created` in addition to `on_modified`; stops loops that are removed from the file between reloads
|
|
9
|
+
- OSC bridge sends `voice_count` first so SuperCollider creates synths before receiving parameter updates
|
|
10
|
+
- AST-based code analysis maps block nesting depth → filter cutoff, control-flow count → LFO rate, function count → polyphony voices, comment ratio → reverb depth
|
|
11
|
+
- `sc/synths.scd` — SuperCollider synth definitions with a single `OSCFunc(nil path)` handler dispatching all `/pycodedj/loop/<name>/<param>` messages
|
|
12
|
+
- `examples/demo.py` and `examples/club_set.py` bundled in the wheel as package data
|
|
13
|
+
|
|
14
|
+
### Docs
|
|
15
|
+
|
|
16
|
+
- `README.ja.md` and `README.md` with architecture overview, quick start, and OSC address reference
|
|
17
|
+
- `docs/manual.ja.md` and `docs/manual.md` — beginner-friendly full manual covering setup, watch mode, code-to-sound mapping, club set example, and troubleshooting
|
pycodedj-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pycodedj
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Requires-Python: >=3.10
|
|
5
|
+
Requires-Dist: python-osc>=1.8
|
|
6
|
+
Provides-Extra: dev
|
|
7
|
+
Requires-Dist: mypy; extra == 'dev'
|
|
8
|
+
Requires-Dist: pytest; extra == 'dev'
|
|
9
|
+
Requires-Dist: ruff; extra == 'dev'
|
|
10
|
+
Provides-Extra: watch
|
|
11
|
+
Requires-Dist: watchdog>=3.0; extra == 'watch'
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
# PyCodeDJ
|
|
2
|
+
|
|
3
|
+
Pythonコードの構造をリアルタイムに音楽へ変換するライブコーディング環境。ファイルを保存するたびに演奏が変わる。
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## コンセプト
|
|
8
|
+
|
|
9
|
+
PyCodeDJ は「コードを書くこと」と「音を鳴らすこと」を直結させます。
|
|
10
|
+
|
|
11
|
+
関数を増やせばポリフォニーが広がり、ネストを深くすればフィルターが開き、コメントを書き込めば空間が広がる。コードの構造そのものが楽器です。
|
|
12
|
+
|
|
13
|
+
既存の Python ↔ SuperCollider ブリッジ(sc3nb・supriya)との違いは2点です。
|
|
14
|
+
|
|
15
|
+
- **ホットリロード演奏** — ループを止めずにファイルを差し替える。保存が即座に音の変化になる。
|
|
16
|
+
- **コード構造の可聴化** — AST解析で抽出した構造的特徴量(深さ・分岐数・関数数など)を音楽パラメーターへ自動変換する。
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## アーキテクチャ
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
[Python エンジン] →OSC→ [SuperCollider] →音響出力→ スピーカー
|
|
24
|
+
↓ OSC
|
|
25
|
+
[Hydra 等] →映像出力→ スクリーン
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
| 層 | 役割 | 技術 |
|
|
29
|
+
| :--- | :--- | :--- |
|
|
30
|
+
| 制御層 | コード解析・スケジューリング・OSC送出 | Python 3.10+, python-osc, watchdog |
|
|
31
|
+
| 音響層 | リアルタイム音響合成 | SuperCollider (scsynth) |
|
|
32
|
+
| 視覚層 | 音楽データに同期した映像生成 | Hydra または Pyxel |
|
|
33
|
+
|
|
34
|
+
BPMクロックは SuperCollider 側の `TempoClock` が保持します。Python は「次のループで使う設定の更新」をOSCで送るだけに徹し、タイミング精度は SuperCollider に委ねます。
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## コード構造 → 音楽パラメーターのマッピング
|
|
39
|
+
|
|
40
|
+
| コード特徴量 | 音楽パラメーター | 音楽的根拠 |
|
|
41
|
+
| :--- | :--- | :--- |
|
|
42
|
+
| ネストの深さ(最大) | フィルター Cutoff (200–4000 Hz) | 深い構造=複雑さ=音色の明るさ |
|
|
43
|
+
| 制御フロー数(if/for/while) | LFO レート (0.1–5.0 Hz) | 分岐の多さ=揺らぎの速さ |
|
|
44
|
+
| 関数定義数 | ポリフォニー声部数 (1–4) | 関数=独立した声部 |
|
|
45
|
+
| コメント率 | リバーブ Depth (0.0–0.8) | 余白の多さ=空間の広さ |
|
|
46
|
+
|
|
47
|
+
テンポ(BPM)と基音(Pitch)は演奏者が明示的に制御します。保存のたびに楽曲全体の土台が変わることを防ぐためです。
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## インストール
|
|
52
|
+
|
|
53
|
+
**必要なもの**
|
|
54
|
+
|
|
55
|
+
- Python 3.10 以上
|
|
56
|
+
- SuperCollider(scsynth が起動できる環境)
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
pip install 'pycodedj[watch]'
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
`[watch]` を付けると `pycodedj watch` コマンドも使えるようになります。
|
|
63
|
+
|
|
64
|
+
開発用:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
git clone https://github.com/yourname/pycodedj
|
|
68
|
+
cd pycodedj
|
|
69
|
+
pip install -e ".[dev]"
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## クイックスタート
|
|
75
|
+
|
|
76
|
+
**1. SuperCollider を起動し、シンセを読み込む**
|
|
77
|
+
|
|
78
|
+
SuperCollider IDE で `sc/synths.scd` を開いて実行します。
|
|
79
|
+
|
|
80
|
+
**2. ライブコーディングファイルを用意する**
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
# @loop bass interval=2.0
|
|
84
|
+
def bass():
|
|
85
|
+
for i in range(8):
|
|
86
|
+
if i % 2 == 0:
|
|
87
|
+
pass
|
|
88
|
+
|
|
89
|
+
# @loop melody interval=0.5
|
|
90
|
+
def melody():
|
|
91
|
+
x = 1
|
|
92
|
+
y = 2
|
|
93
|
+
return x + y
|
|
94
|
+
|
|
95
|
+
# @loop pad interval=4.0
|
|
96
|
+
def pad():
|
|
97
|
+
# 空間を作る
|
|
98
|
+
# もう少し余白
|
|
99
|
+
pass
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
**3. ブロックを評価する**
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
pycodedj eval demo.py::bass
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
成功すると次のフィードバックが表示されます。
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
[pycodedj] bass cutoff=418Hz lfo=1.08Hz reverb=0.00 voices=1
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
他のループはそのまま鳴り続けます。
|
|
115
|
+
|
|
116
|
+
**4. watch モードでライブコーディング**
|
|
117
|
+
|
|
118
|
+
毎回 eval を打つ代わりに、ファイル保存で全ループを自動再評価できます。
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
pycodedj watch demo.py
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
あとはエディタでコードを書いて保存するだけです。
|
|
125
|
+
|
|
126
|
+
> **`interval` について(現在の MVP):** `interval=2.0` のような値はパーサーが読み取りますが、現時点では OSC では送信されません。ループの繰り返し周期は SuperCollider 側の `TempoClock` で管理します。将来的に SC 側に interval を渡す仕組みを追加する予定です。
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## サンプルファイル
|
|
131
|
+
|
|
132
|
+
| ファイル | 内容 |
|
|
133
|
+
| :--- | :--- |
|
|
134
|
+
| `examples/demo.py` | bass / melody / pad の 3 ループ入門デモ |
|
|
135
|
+
| `examples/club_set.py` | sub_bass / hat_engine / neon_stab / acid_lead / warehouse_air のクラブスタイルデモ |
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## ライブコーディング例
|
|
140
|
+
|
|
141
|
+
### ネストを深くするとフィルターが開く
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
# @loop bass interval=2.0
|
|
145
|
+
def bass():
|
|
146
|
+
for i in range(4): # 制御フロー +1
|
|
147
|
+
for j in range(4): # ネスト深さ +1、制御フロー +1
|
|
148
|
+
if i == j: # ネスト深さ +1、制御フロー +1
|
|
149
|
+
pass
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### 関数を増やすとポリフォニーが広がる
|
|
153
|
+
|
|
154
|
+
```python
|
|
155
|
+
# @loop chord interval=1.0
|
|
156
|
+
def voice_a(): pass
|
|
157
|
+
def voice_b(): pass
|
|
158
|
+
def voice_c(): pass
|
|
159
|
+
def voice_d(): pass
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### コメントを増やすと空間(リバーブ)が広がる
|
|
163
|
+
|
|
164
|
+
```python
|
|
165
|
+
# @loop pad interval=4.0
|
|
166
|
+
# ここに余白を置く
|
|
167
|
+
# もう少し置く
|
|
168
|
+
# 静寂も音楽
|
|
169
|
+
def pad(): pass
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## OSC アドレス仕様
|
|
175
|
+
|
|
176
|
+
SuperCollider との通信に使うアドレスです。
|
|
177
|
+
|
|
178
|
+
| アドレス | 型 | 値域 | 対応パラメーター |
|
|
179
|
+
| :--- | :--- | :--- | :--- |
|
|
180
|
+
| `/pycodedj/loop/<name>/cutoff` | float | 200–4000 Hz | フィルター Cutoff |
|
|
181
|
+
| `/pycodedj/loop/<name>/lfo_rate` | float | 0.1–5.0 Hz | LFO レート |
|
|
182
|
+
| `/pycodedj/loop/<name>/reverb` | float | 0.0–0.8 | リバーブ Depth |
|
|
183
|
+
| `/pycodedj/loop/<name>/voice_count` | int | 1–4 | ポリフォニー声部数 |
|
|
184
|
+
|
|
185
|
+
`<name>` はブロック名(`bass`、`melody` など)です。ループごとに独立したアドレスを持つため、複数ループが同じパラメーターを上書きしません。
|
|
186
|
+
|
|
187
|
+
Hydra 等の外部ビジュアライザーへは同じパラメーターを別ポートに送信します。
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## 動作環境
|
|
192
|
+
|
|
193
|
+
- **推奨OS:** macOS(Core Audio の低遅延性を活用)または Linux(Raspberry Pi 5 等)
|
|
194
|
+
- **Python:** 3.10 以上
|
|
195
|
+
- **SuperCollider:** 3.12 以上
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
## ロードマップ
|
|
200
|
+
|
|
201
|
+
- [x] 仕様設計・マッピング設計
|
|
202
|
+
- [ ] フェーズ0: マッピング仮説の聴取検証
|
|
203
|
+
- [x] フェーズ1: Python → SuperCollider OSC プロトタイプ
|
|
204
|
+
- [x] フェーズ2: ホットリロード・ライブループ実装(`pycodedj watch`)
|
|
205
|
+
- [ ] フェーズ3: Hydra ビジュアライザー統合
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
## ライセンス
|
|
210
|
+
|
|
211
|
+
MIT
|
pycodedj-0.1.0/README.md
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
# PyCodeDJ
|
|
2
|
+
|
|
3
|
+
A live-coding environment that translates Python code structure into music in real time. Every save changes the performance.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Concept
|
|
8
|
+
|
|
9
|
+
PyCodeDJ connects "writing code" directly to "making sound."
|
|
10
|
+
|
|
11
|
+
Add more functions and the polyphony widens. Deepen nesting and the filter opens up. Fill in comments and the space grows. The structure of your code is the instrument.
|
|
12
|
+
|
|
13
|
+
Two things set it apart from existing Python ↔ SuperCollider bridges (sc3nb, supriya):
|
|
14
|
+
|
|
15
|
+
- **Hot-reload performance** — swap out a loop without stopping it. Saving a file becomes an immediate sound change.
|
|
16
|
+
- **Audible code structure** — structural features extracted via AST analysis (depth, branch count, function count, etc.) are automatically mapped to musical parameters.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Architecture
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
[Python engine] →OSC→ [SuperCollider] →audio out→ speakers
|
|
24
|
+
↓ OSC
|
|
25
|
+
[Hydra etc.] →video out→ screen
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
| Layer | Role | Technology |
|
|
29
|
+
| :--- | :--- | :--- |
|
|
30
|
+
| Control | Code analysis, scheduling, OSC dispatch | Python 3.10+, python-osc, watchdog |
|
|
31
|
+
| Audio | Real-time sound synthesis | SuperCollider (scsynth) |
|
|
32
|
+
| Visual | Music-synced visuals | Hydra or Pyxel |
|
|
33
|
+
|
|
34
|
+
BPM clock is held by SuperCollider's `TempoClock`. Python only sends parameter updates over OSC; timing accuracy is delegated to SuperCollider.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Code Structure → Music Parameter Mapping
|
|
39
|
+
|
|
40
|
+
| Code feature | Music parameter | Musical rationale |
|
|
41
|
+
| :--- | :--- | :--- |
|
|
42
|
+
| Max nesting depth | Filter Cutoff (200–4000 Hz) | Deep structure = complexity = brightness |
|
|
43
|
+
| Control-flow count (if/for/while) | LFO rate (0.1–5.0 Hz) | More branches = faster modulation |
|
|
44
|
+
| Function definition count | Polyphony voice count (1–4) | Functions = independent voices |
|
|
45
|
+
| Comment ratio | Reverb depth (0.0–0.8) | More whitespace = more space |
|
|
46
|
+
|
|
47
|
+
Tempo (BPM) and root pitch are controlled explicitly by the performer, to prevent the foundation of the piece from shifting on every save.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Installation
|
|
52
|
+
|
|
53
|
+
**Requirements**
|
|
54
|
+
|
|
55
|
+
- Python 3.10 or later
|
|
56
|
+
- SuperCollider (with scsynth available)
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
pip install 'pycodedj[watch]'
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
The `[watch]` extra enables the `pycodedj watch` command.
|
|
63
|
+
|
|
64
|
+
Development install:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
git clone https://github.com/yourname/pycodedj
|
|
68
|
+
cd pycodedj
|
|
69
|
+
pip install -e ".[dev]"
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Quick Start
|
|
75
|
+
|
|
76
|
+
**1. Boot SuperCollider and load the synths**
|
|
77
|
+
|
|
78
|
+
Open `sc/synths.scd` in the SuperCollider IDE and evaluate it.
|
|
79
|
+
|
|
80
|
+
**2. Write a live-coding file**
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
# @loop bass interval=2.0
|
|
84
|
+
def bass():
|
|
85
|
+
for i in range(8):
|
|
86
|
+
if i % 2 == 0:
|
|
87
|
+
pass
|
|
88
|
+
|
|
89
|
+
# @loop melody interval=0.5
|
|
90
|
+
def melody():
|
|
91
|
+
x = 1
|
|
92
|
+
y = 2
|
|
93
|
+
return x + y
|
|
94
|
+
|
|
95
|
+
# @loop pad interval=4.0
|
|
96
|
+
def pad():
|
|
97
|
+
# make space
|
|
98
|
+
# a little more
|
|
99
|
+
pass
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
**3. Evaluate a block**
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
pycodedj eval demo.py::bass
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
On success, feedback is printed immediately:
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
[pycodedj] bass cutoff=418Hz lfo=1.08Hz reverb=0.00 voices=1
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Other loops keep playing without interruption.
|
|
115
|
+
|
|
116
|
+
**4. Live-code with watch mode**
|
|
117
|
+
|
|
118
|
+
Instead of running eval manually, use watch to re-evaluate all loops on every save:
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
pycodedj watch demo.py
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
From here, just write code and save.
|
|
125
|
+
|
|
126
|
+
> **Note on `interval` (current MVP):** Values like `interval=2.0` are parsed and stored, but are not yet sent over OSC. Loop repeat timing is managed by SuperCollider's `TempoClock`. A mechanism to pass interval to SC is planned for a future release.
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## Example Files
|
|
131
|
+
|
|
132
|
+
| File | Contents |
|
|
133
|
+
| :--- | :--- |
|
|
134
|
+
| `examples/demo.py` | Intro demo with bass / melody / pad |
|
|
135
|
+
| `examples/club_set.py` | Club-style demo: sub_bass / hat_engine / neon_stab / acid_lead / warehouse_air |
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## Live-Coding Examples
|
|
140
|
+
|
|
141
|
+
### Deeper nesting opens the filter
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
# @loop bass interval=2.0
|
|
145
|
+
def bass():
|
|
146
|
+
for i in range(4): # control flow +1
|
|
147
|
+
for j in range(4): # depth +1, control flow +1
|
|
148
|
+
if i == j: # depth +1, control flow +1
|
|
149
|
+
pass
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### More functions = more polyphony
|
|
153
|
+
|
|
154
|
+
```python
|
|
155
|
+
# @loop chord interval=1.0
|
|
156
|
+
def voice_a(): pass
|
|
157
|
+
def voice_b(): pass
|
|
158
|
+
def voice_c(): pass
|
|
159
|
+
def voice_d(): pass
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### More comments = more space (reverb)
|
|
163
|
+
|
|
164
|
+
```python
|
|
165
|
+
# @loop pad interval=4.0
|
|
166
|
+
# leave space here
|
|
167
|
+
# a little more
|
|
168
|
+
# silence is music
|
|
169
|
+
def pad(): pass
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## OSC Address Reference
|
|
175
|
+
|
|
176
|
+
Addresses used to communicate with SuperCollider.
|
|
177
|
+
|
|
178
|
+
| Address | Type | Range | Parameter |
|
|
179
|
+
| :--- | :--- | :--- | :--- |
|
|
180
|
+
| `/pycodedj/loop/<name>/cutoff` | float | 200–4000 Hz | Filter Cutoff |
|
|
181
|
+
| `/pycodedj/loop/<name>/lfo_rate` | float | 0.1–5.0 Hz | LFO rate |
|
|
182
|
+
| `/pycodedj/loop/<name>/reverb` | float | 0.0–0.8 | Reverb depth |
|
|
183
|
+
| `/pycodedj/loop/<name>/voice_count` | int | 1–4 | Polyphony voice count |
|
|
184
|
+
|
|
185
|
+
`<name>` is the loop name (e.g. `bass`, `melody`). Each loop has its own address namespace, so multiple loops never overwrite each other's parameters.
|
|
186
|
+
|
|
187
|
+
External visualisers such as Hydra can receive the same parameters on a separate port.
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## Requirements
|
|
192
|
+
|
|
193
|
+
- **Recommended OS:** macOS (low-latency Core Audio) or Linux (Raspberry Pi 5, etc.)
|
|
194
|
+
- **Python:** 3.10 or later
|
|
195
|
+
- **SuperCollider:** 3.12 or later
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
## Roadmap
|
|
200
|
+
|
|
201
|
+
- [x] Spec design and mapping design
|
|
202
|
+
- [ ] Phase 0: Listening validation of mapping hypotheses
|
|
203
|
+
- [x] Phase 1: Python → SuperCollider OSC prototype
|
|
204
|
+
- [x] Phase 2: Hot-reload live loop implementation (`pycodedj watch`)
|
|
205
|
+
- [ ] Phase 3: Hydra visualiser integration
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
## License
|
|
210
|
+
|
|
211
|
+
MIT
|