audatron 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.
@@ -0,0 +1,165 @@
1
+ Metadata-Version: 2.4
2
+ Name: audatron
3
+ Version: 0.1.0
4
+ Summary: audatron — LLM-driven realtime Audacity: natural language drives track editing, noise removal, and mastering, with a headless DSP engine plus auto-generated Audacity TCL scripts, an agent chat REPL, and an MCP stdio server.
5
+ Author: DREAM PIXELS FORGE
6
+ License: MIT
7
+ Requires-Python: >=3.10
8
+ Description-Content-Type: text/markdown
9
+ Requires-Dist: numpy>=1.26
10
+ Requires-Dist: soundfile>=0.12
11
+ Requires-Dist: typer>=0.12
12
+ Requires-Dist: rich>=13
13
+ Requires-Dist: httpx>=0.27
14
+ Requires-Dist: pydub>=0.25
15
+ Provides-Extra: noisereduce
16
+ Requires-Dist: noisereduce>=3.0; extra == "noisereduce"
17
+ Provides-Extra: test
18
+ Requires-Dist: pytest>=8; extra == "test"
19
+
20
+ # audatron
21
+
22
+ **Natural-language audio production, mirrored live into Audacity.**
23
+
24
+ audatron is a realtime, LLM-driven toolchain: you prompt an agent (or any
25
+ coding tool — Claude Code, Codex, opencode, kimi, pi, qwen code —) in plain
26
+ English, and it drives a real Audacity workflow in your browser: inspect the
27
+ waveform, trim tracks, strip noise, master to a target loudness, and export —
28
+ while the matching Audacity project + TCL/Lua scripts are written so you can
29
+ watch the edits land on the open project.
30
+
31
+ ```
32
+ you (natural language)
33
+ │
34
+ ▼
35
+ ┌──────────────────────────────┐
36
+ │ audatron agent (LLM loop) │ ← Anthropic / OpenAI-compatible / Ollama
37
+ └──────────────────────────────┘
38
+ │ tool calls
39
+ ▼
40
+ ┌──────────────────────────────┐
41
+ │ DSP engine (headless) │ ← trim / gain / normalize / noise / master / export
42
+ └──────────────────────────────┘
43
+ │ every step
44
+ ▼
45
+ ┌──────────────────────────────┐
46
+ │ Audacity bridge │ ← .aup3 project + generated TCL + Lua scripts
47
+ └──────────────────────────────┘
48
+ ```
49
+
50
+ ## Install
51
+
52
+ ```bash
53
+ pip install -e .
54
+ audatron doctor # verify numpy, ffmpeg, Audacity, LLM backends
55
+ ```
56
+
57
+ ## Quickstart
58
+
59
+ ```bash
60
+ # 1. generate a test clip
61
+ audatron demo demo.wav --freq 440 --seconds 3
62
+
63
+ # 2. one-shot: natural language in, mastered wav out
64
+ audatron run "trim to 2s, remove hiss, master to -14 LUFS, export wav" demo.wav --backend anthropic
65
+
66
+ # 3. interactive REPL
67
+ audatron chat demo.wav --backend anthropic
68
+ # you › make it sound like a radio
69
+ # you › sync ← pushes the deck into a live Audacity project
70
+ ```
71
+
72
+ ### The LLM agent is optional
73
+
74
+ Every operation the agent can perform is also available without an LLM:
75
+
76
+ ```bash
77
+ audatron inspect demo.wav # stats + spectral analysis
78
+ audatron sync demo.wav # Audacity project + TCL/Lua, no LLM
79
+ ```
80
+
81
+ ## Audacity integration
82
+
83
+ `audatron sync` writes:
84
+
85
+ | File | Purpose |
86
+ |---------------------------|----------------------------------------------------|
87
+ | `deck.wav` | clean copy of the current deck |
88
+ | `<name>.aup3` | Audacity 3.x XML project (fallback) |
89
+ | `<name>.native.aup3` | **native Audacity 4 SQLite project** (primary) |
90
+ | `audatron.tcl` | generated TCL script (run via Tools → Macros) |
91
+ | `audatron.lua` | generated Lua script (Audacity 2.x scripting) |
92
+ | `audatron.json` | machine-readable manifest of the ops |
93
+
94
+ The `.native.aup3` is a real SQLite database matching Audacity 4's
95
+ on-disk format: four tables (`project`, `sampleblocks`, `autosave`,
96
+ `project_history`), float32 sample blocks sized the way the app writes
97
+ them (up to 262144 samples / 1 MiB), and the app's `dict`/`doc`
98
+ record streams. It opens natively with the waveform + track layout
99
+ intact, no external WAV side-file needed. The XML `.aup3` is kept as
100
+ a fallback for older Audacity builds.
101
+
102
+ Open it in your running Audacity 4:
103
+
104
+ ```
105
+ "C:\Program Files\Audacity 4\bin\Audacity4.exe" <project>\river_master-proj.native.aup3
106
+ ```
107
+
108
+ or let audatron launch it for you:
109
+
110
+ ```
111
+ audatron sync river_master.wav --open-audacity # opens the native project
112
+ audatron sync river_master.wav --run-tcl # + auto-runs the TCL macro
113
+ ```
114
+
115
+ ## MCP server
116
+
117
+ audatron ships an MCP stdio server so **any** MCP-capable coding agent can
118
+ drive it. The tools are the same DSP surface the CLI agent uses.
119
+
120
+ ```bash
121
+ audatron-mcp # newline-delimited JSON-RPC 2.0 over stdio
122
+ ```
123
+
124
+ Wiring it into a coding agent — see **[dev-notes/MCP-SETUP.md](dev-notes/MCP-SETUP.md)** for
125
+ Claude Code, Codex, opencode, kimi, qwen-code, and generic MCP clients.
126
+
127
+ ## Tool surface
128
+
129
+ | Tool | What it does |
130
+ |----------------|--------------|
131
+ | `inspect` | sr, duration, peak, loudness dB, silence ratio |
132
+ | `analyze` | spectral centroid, onsets, silence ranges |
133
+ | `trim` | keep a `[start, end]` window |
134
+ | `gain` | apply ± dB |
135
+ | `normalize` | peak-normalize to a target dBFS |
136
+ | `noise_reduce` | spectral-subtraction denoise (profile from head) |
137
+ | `master` | loudness-normalize to target LUFS + peak ceiling |
138
+ | `export` | write wav / flac / mp3 / ogg |
139
+ | `sync` | generate the Audacity project + scripts |
140
+ | `history` / `undo` | deck state |
141
+ | `load` | replace the deck with a new file |
142
+
143
+ ## Development
144
+
145
+ ```bash
146
+ pip install -e ".[test]"
147
+ python -m pytest -q # 102 tests, ~45s
148
+ ```
149
+
150
+ Layout:
151
+
152
+ ```
153
+ src/audatron/
154
+ dsp.py # headless DSP engine (numpy + soundfile)
155
+ session.py # stateful deck + op history
156
+ tools.py # tool surface shared by CLI agent + MCP
157
+ agent.py # LLM agent loop (Anthropic/OpenAI/Ollama)
158
+ backends.py # LLM provider clients (+ streaming)
159
+ live.py # realtime foreground view (token stream + waveform)
160
+ audacity.py # .aup3 + TCL/Lua generation + Audacity lookup
161
+ aup3.py # native Audacity-4 SQLite .aup3 writer
162
+ mcp_server.py # MCP stdio JSON-RPC server
163
+ cli.py # typer CLI
164
+ tests/ # pytest: DSP, tools, agent (mocked), MCP, e2e, CLI, aup3, live
165
+ ```
@@ -0,0 +1,146 @@
1
+ # audatron
2
+
3
+ **Natural-language audio production, mirrored live into Audacity.**
4
+
5
+ audatron is a realtime, LLM-driven toolchain: you prompt an agent (or any
6
+ coding tool — Claude Code, Codex, opencode, kimi, pi, qwen code —) in plain
7
+ English, and it drives a real Audacity workflow in your browser: inspect the
8
+ waveform, trim tracks, strip noise, master to a target loudness, and export —
9
+ while the matching Audacity project + TCL/Lua scripts are written so you can
10
+ watch the edits land on the open project.
11
+
12
+ ```
13
+ you (natural language)
14
+ │
15
+ ▼
16
+ ┌──────────────────────────────┐
17
+ │ audatron agent (LLM loop) │ ← Anthropic / OpenAI-compatible / Ollama
18
+ └──────────────────────────────┘
19
+ │ tool calls
20
+ ▼
21
+ ┌──────────────────────────────┐
22
+ │ DSP engine (headless) │ ← trim / gain / normalize / noise / master / export
23
+ └──────────────────────────────┘
24
+ │ every step
25
+ ▼
26
+ ┌──────────────────────────────┐
27
+ │ Audacity bridge │ ← .aup3 project + generated TCL + Lua scripts
28
+ └──────────────────────────────┘
29
+ ```
30
+
31
+ ## Install
32
+
33
+ ```bash
34
+ pip install -e .
35
+ audatron doctor # verify numpy, ffmpeg, Audacity, LLM backends
36
+ ```
37
+
38
+ ## Quickstart
39
+
40
+ ```bash
41
+ # 1. generate a test clip
42
+ audatron demo demo.wav --freq 440 --seconds 3
43
+
44
+ # 2. one-shot: natural language in, mastered wav out
45
+ audatron run "trim to 2s, remove hiss, master to -14 LUFS, export wav" demo.wav --backend anthropic
46
+
47
+ # 3. interactive REPL
48
+ audatron chat demo.wav --backend anthropic
49
+ # you › make it sound like a radio
50
+ # you › sync ← pushes the deck into a live Audacity project
51
+ ```
52
+
53
+ ### The LLM agent is optional
54
+
55
+ Every operation the agent can perform is also available without an LLM:
56
+
57
+ ```bash
58
+ audatron inspect demo.wav # stats + spectral analysis
59
+ audatron sync demo.wav # Audacity project + TCL/Lua, no LLM
60
+ ```
61
+
62
+ ## Audacity integration
63
+
64
+ `audatron sync` writes:
65
+
66
+ | File | Purpose |
67
+ |---------------------------|----------------------------------------------------|
68
+ | `deck.wav` | clean copy of the current deck |
69
+ | `<name>.aup3` | Audacity 3.x XML project (fallback) |
70
+ | `<name>.native.aup3` | **native Audacity 4 SQLite project** (primary) |
71
+ | `audatron.tcl` | generated TCL script (run via Tools → Macros) |
72
+ | `audatron.lua` | generated Lua script (Audacity 2.x scripting) |
73
+ | `audatron.json` | machine-readable manifest of the ops |
74
+
75
+ The `.native.aup3` is a real SQLite database matching Audacity 4's
76
+ on-disk format: four tables (`project`, `sampleblocks`, `autosave`,
77
+ `project_history`), float32 sample blocks sized the way the app writes
78
+ them (up to 262144 samples / 1 MiB), and the app's `dict`/`doc`
79
+ record streams. It opens natively with the waveform + track layout
80
+ intact, no external WAV side-file needed. The XML `.aup3` is kept as
81
+ a fallback for older Audacity builds.
82
+
83
+ Open it in your running Audacity 4:
84
+
85
+ ```
86
+ "C:\Program Files\Audacity 4\bin\Audacity4.exe" <project>\river_master-proj.native.aup3
87
+ ```
88
+
89
+ or let audatron launch it for you:
90
+
91
+ ```
92
+ audatron sync river_master.wav --open-audacity # opens the native project
93
+ audatron sync river_master.wav --run-tcl # + auto-runs the TCL macro
94
+ ```
95
+
96
+ ## MCP server
97
+
98
+ audatron ships an MCP stdio server so **any** MCP-capable coding agent can
99
+ drive it. The tools are the same DSP surface the CLI agent uses.
100
+
101
+ ```bash
102
+ audatron-mcp # newline-delimited JSON-RPC 2.0 over stdio
103
+ ```
104
+
105
+ Wiring it into a coding agent — see **[dev-notes/MCP-SETUP.md](dev-notes/MCP-SETUP.md)** for
106
+ Claude Code, Codex, opencode, kimi, qwen-code, and generic MCP clients.
107
+
108
+ ## Tool surface
109
+
110
+ | Tool | What it does |
111
+ |----------------|--------------|
112
+ | `inspect` | sr, duration, peak, loudness dB, silence ratio |
113
+ | `analyze` | spectral centroid, onsets, silence ranges |
114
+ | `trim` | keep a `[start, end]` window |
115
+ | `gain` | apply ± dB |
116
+ | `normalize` | peak-normalize to a target dBFS |
117
+ | `noise_reduce` | spectral-subtraction denoise (profile from head) |
118
+ | `master` | loudness-normalize to target LUFS + peak ceiling |
119
+ | `export` | write wav / flac / mp3 / ogg |
120
+ | `sync` | generate the Audacity project + scripts |
121
+ | `history` / `undo` | deck state |
122
+ | `load` | replace the deck with a new file |
123
+
124
+ ## Development
125
+
126
+ ```bash
127
+ pip install -e ".[test]"
128
+ python -m pytest -q # 102 tests, ~45s
129
+ ```
130
+
131
+ Layout:
132
+
133
+ ```
134
+ src/audatron/
135
+ dsp.py # headless DSP engine (numpy + soundfile)
136
+ session.py # stateful deck + op history
137
+ tools.py # tool surface shared by CLI agent + MCP
138
+ agent.py # LLM agent loop (Anthropic/OpenAI/Ollama)
139
+ backends.py # LLM provider clients (+ streaming)
140
+ live.py # realtime foreground view (token stream + waveform)
141
+ audacity.py # .aup3 + TCL/Lua generation + Audacity lookup
142
+ aup3.py # native Audacity-4 SQLite .aup3 writer
143
+ mcp_server.py # MCP stdio JSON-RPC server
144
+ cli.py # typer CLI
145
+ tests/ # pytest: DSP, tools, agent (mocked), MCP, e2e, CLI, aup3, live
146
+ ```
@@ -0,0 +1,34 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "audatron"
7
+ version = "0.1.0"
8
+ description = "audatron — LLM-driven realtime Audacity: natural language drives track editing, noise removal, and mastering, with a headless DSP engine plus auto-generated Audacity TCL scripts, an agent chat REPL, and an MCP stdio server."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = { text = "MIT" }
12
+ authors = [{ name = "DREAM PIXELS FORGE" }]
13
+ dependencies = [
14
+ "numpy>=1.26",
15
+ "soundfile>=0.12",
16
+ "typer>=0.12",
17
+ "rich>=13",
18
+ "httpx>=0.27",
19
+ "pydub>=0.25",
20
+ ]
21
+
22
+ [project.optional-dependencies]
23
+ noisereduce = ["noisereduce>=3.0"]
24
+ test = ["pytest>=8"]
25
+
26
+ [project.scripts]
27
+ audatron = "audatron.cli:app"
28
+ audatron-mcp = "audatron.mcp_server:main"
29
+
30
+ [tool.setuptools.packages.find]
31
+ where = ["src"]
32
+
33
+ [tool.pytest.ini_options]
34
+ testpaths = ["tests"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
File without changes
@@ -0,0 +1,142 @@
1
+ """audatron.agent — the natural-language agent loop.
2
+
3
+ One `Agent` owns:
4
+ * a LLM backend (Anthropic / OpenAI / Ollama)
5
+ * a Session (the audio deck)
6
+ * a conversation history
7
+ * a trace log (every user turn, assistant turn, tool call, and tool result)
8
+
9
+ `Agent.run(prompt)` executes one user turn to completion: it may call
10
+ tools, observe results, and keep calling until the model stops.
11
+ The trace is returned alongside the final assistant text so the CLI
12
+ and MCP surfaces can render / inspect it.
13
+
14
+ The conversation is kept in one provider-native `messages` list that grows
15
+ as the loop runs — each backend's tool-call / tool-result wire format is
16
+ handled by per-branch appends, so the same Agent works across all three.
17
+ """
18
+ from __future__ import annotations
19
+
20
+ import json
21
+ from dataclasses import dataclass, field
22
+ from typing import Any
23
+
24
+ from .backends import AnthropicBackend, OllamaBackend, OpenAIBackend
25
+ from .session import Session
26
+ from .tools import TOOL_SCHEMAS, dispatch
27
+
28
+
29
+ SYSTEM_PROMPT = """You are audatron, a realtime audio-production agent.
30
+
31
+ You control a headless DSP deck (the "deck") that mirrors a live Audacity
32
+ project. The deck is ALREADY LOADED with the audio the user pointed you at —
33
+ do NOT call `load` unless the user explicitly names a different file. Work
34
+ directly on the current deck.
35
+
36
+ Use the tools to inspect, edit, clean, master, and export audio. After each
37
+ tool call, reason from the result. Keep tool calls minimal: one op per step,
38
+ verify with `inspect` when in doubt. When the user asks for a sync, call
39
+ `sync` to generate the matching Audacity TCL/Lua scripts.
40
+
41
+ Tool results are JSON. Do not invent audio properties — read them from
42
+ `inspect`/`analyze`.
43
+ """
44
+
45
+
46
+ @dataclass
47
+ class TraceEntry:
48
+ role: str # "user" | "assistant" | "tool"
49
+ name: str = "" # tool name when role == "tool"
50
+ text: str = "" # assistant/user text or tool-result JSON
51
+ calls: list[dict] = field(default_factory=list) # tool_calls when role == "assistant"
52
+
53
+ def to_dict(self) -> dict:
54
+ return {"role": self.role, "name": self.name, "text": self.text, "calls": self.calls}
55
+
56
+
57
+ class Agent:
58
+ def __init__(
59
+ self,
60
+ session: Session,
61
+ backend: AnthropicBackend | OpenAIBackend | OllamaBackend,
62
+ max_steps: int = 8,
63
+ ):
64
+ self.session = session
65
+ self.backend = backend
66
+ self.max_steps = max_steps
67
+ self.trace: list[TraceEntry] = []
68
+
69
+ # ------------------------------------------------------------------
70
+ # public API
71
+ # ------------------------------------------------------------------
72
+
73
+ def run(self, prompt: str) -> dict:
74
+ """Execute one user turn. Returns {"text": ..., "trace": [...], "tool_calls": N}."""
75
+ self.trace.append(TraceEntry(role="user", text=prompt))
76
+ messages: list[dict] = [{"role": "user", "content": SYSTEM_PROMPT}, {"role": "user", "content": prompt}]
77
+ tool_calls_total = 0
78
+
79
+ for _step in range(self.max_steps):
80
+ text, calls = self.backend.complete(messages, TOOL_SCHEMAS)
81
+ if not calls:
82
+ self.trace.append(TraceEntry(role="assistant", text=text))
83
+ return {"text": text, "trace": self.trace, "tool_calls": tool_calls_total}
84
+
85
+ self.trace.append(TraceEntry(role="assistant", text=text, calls=calls))
86
+ tool_calls_total += len(calls)
87
+
88
+ if self.backend.name == "anthropic":
89
+ results = []
90
+ for c in calls:
91
+ out = dispatch(self.session, c["name"], c.get("arguments") or {})
92
+ results.append({"type": "tool_result", "tool_use_id": c["id"], "content": json.dumps(out)})
93
+ self.trace.append(TraceEntry(role="tool", name=c["name"], text=json.dumps(out)))
94
+ messages.append({"role": "assistant", "content": _anthropic_blocks(text, calls)})
95
+ messages.append({"role": "user", "content": results})
96
+ elif self.backend.name == "ollama":
97
+ # Ollama chat format: assistant message carries tool_calls (OpenAI-ish,
98
+ # args as dict), tool results use role "tool" with the tool name.
99
+ messages.append({
100
+ "role": "assistant",
101
+ "content": text,
102
+ "tool_calls": [
103
+ {"id": c["id"], "function": {"name": c["name"], "arguments": c.get("arguments") or {}}}
104
+ for c in calls
105
+ ],
106
+ })
107
+ for c in calls:
108
+ out = dispatch(self.session, c["name"], c.get("arguments") or {})
109
+ self.trace.append(TraceEntry(role="tool", name=c["name"], text=json.dumps(out)))
110
+ messages.append({"role": "tool", "name": c["name"], "content": json.dumps(out)})
111
+ else: # openai-compatible
112
+ messages.append({
113
+ "role": "assistant",
114
+ "content": text,
115
+ "tool_calls": [
116
+ {"id": c["id"], "type": "function", "function": {"name": c["name"], "arguments": json.dumps(c.get("arguments") or {})}}
117
+ for c in calls
118
+ ],
119
+ })
120
+ for c in calls:
121
+ out = dispatch(self.session, c["name"], c.get("arguments") or {})
122
+ self.trace.append(TraceEntry(role="tool", name=c["name"], text=json.dumps(out)))
123
+ messages.append({"role": "tool", "tool_call_id": c["id"], "content": json.dumps(out)})
124
+
125
+ # ran out of steps
126
+ return {"text": "stopped at max_steps", "trace": self.trace, "tool_calls": tool_calls_total}
127
+
128
+
129
+ # ---------------------------------------------------------------------------
130
+ # Wire-format helpers
131
+ # ---------------------------------------------------------------------------
132
+
133
+ def _anthropic_blocks(text: str, calls: list[dict]) -> list[dict]:
134
+ blocks: list[dict] = []
135
+ if text:
136
+ blocks.append({"type": "text", "text": text})
137
+ for c in calls:
138
+ blocks.append({"type": "tool_use", "id": c["id"], "name": c["name"], "input": c.get("arguments") or {}})
139
+ return blocks
140
+
141
+
142
+ __all__ = ["Agent", "SYSTEM_PROMPT", "TraceEntry"]