ark-audio-agent 0.1.3__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.
- ark_audio_agent-0.1.3/PKG-INFO +221 -0
- ark_audio_agent-0.1.3/README.md +202 -0
- ark_audio_agent-0.1.3/pyproject.toml +45 -0
- ark_audio_agent-0.1.3/pyproject.toml.orig +35 -0
- ark_audio_agent-0.1.3/src/ark_audio_agent/__init__.py +48 -0
- ark_audio_agent-0.1.3/src/ark_audio_agent/agents.py +148 -0
- ark_audio_agent-0.1.3/src/ark_audio_agent/blackboard.py +224 -0
- ark_audio_agent-0.1.3/src/ark_audio_agent/brain.py +398 -0
- ark_audio_agent-0.1.3/src/ark_audio_agent/cli.py +141 -0
- ark_audio_agent-0.1.3/src/ark_audio_agent/llm.py +209 -0
- ark_audio_agent-0.1.3/src/ark_audio_agent/midi_render.py +91 -0
- ark_audio_agent-0.1.3/src/ark_audio_agent/orchestrator.py +144 -0
- ark_audio_agent-0.1.3/src/ark_audio_agent/performance.py +383 -0
- ark_audio_agent-0.1.3/src/ark_audio_agent/report.py +97 -0
- ark_audio_agent-0.1.3/src/ark_audio_agent/synth.py +293 -0
- ark_audio_agent-0.1.3/src/ark_audio_agent/theory.py +271 -0
- ark_audio_agent-0.1.3/src/ark_audio_agent/tools.py +416 -0
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: ark-audio-agent
|
|
3
|
+
Version: 0.1.3
|
|
4
|
+
Summary: Autonomous text-to-music composer driven by a multi-agent ReAct harness (Claude API, with an offline deterministic fallback).
|
|
5
|
+
Keywords: music,generative,agents,react,claude,midi,synthesizer
|
|
6
|
+
Author: Immanuel R
|
|
7
|
+
Author-email: Immanuel R <raj@immanuel.co>
|
|
8
|
+
License: MIT
|
|
9
|
+
Requires-Dist: numpy>=1.24
|
|
10
|
+
Requires-Dist: mido>=1.3
|
|
11
|
+
Requires-Dist: rich>=13.7
|
|
12
|
+
Requires-Dist: anthropic>=0.107.1 ; extra == 'llm'
|
|
13
|
+
Requires-Python: >=3.10
|
|
14
|
+
Project-URL: Homepage, https://github.com/ir-dev/ark-audio-agent
|
|
15
|
+
Project-URL: Repository, https://github.com/ir-dev/ark-audio-agent
|
|
16
|
+
Project-URL: Issues, https://github.com/ir-dev/ark-audio-agent/issues
|
|
17
|
+
Provides-Extra: llm
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
|
|
20
|
+
# ark-audio-agent
|
|
21
|
+
|
|
22
|
+
**Autonomous text → music.** Give it a sentence; it reasons about the feeling,
|
|
23
|
+
composes a structured piece, and renders it to MIDI + audio — with no human in
|
|
24
|
+
the loop.
|
|
25
|
+
|
|
26
|
+
The interesting part is the *harness*: composition is driven by a team of
|
|
27
|
+
specialist agents running a **ReAct loop** (reason → act → observe) over a shared
|
|
28
|
+
musical blackboard. It runs on the **Claude API** when a key is available, and on
|
|
29
|
+
a **fully-offline deterministic music-theory brain** otherwise, so it always
|
|
30
|
+
produces a result.
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
ark-audio "a rainy night in a neon city, lonely but hopeful"
|
|
34
|
+
# → renders/a-rainy-night-in-a-neon-city-lonely-but-hopeful/song.mp3 (+ .wav, .mid, report.md, trace.jsonl)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Why it's built this way
|
|
40
|
+
|
|
41
|
+
Rather than ask one model to emit raw notes (which drifts out of key and is hard
|
|
42
|
+
to steer), the system separates **musical intent** from **musical realization**:
|
|
43
|
+
|
|
44
|
+
- **Agents decide intent** — key, mode, tempo, song form, chord progressions, a
|
|
45
|
+
melody's contour/register/density, instrument roles, drum and bass styles.
|
|
46
|
+
- **Deterministic engines realize it** — pure music-theory functions turn that
|
|
47
|
+
intent into in-key notes, which a `mido` writer turns into MIDI and a
|
|
48
|
+
from-scratch `numpy` synthesizer turns into audio.
|
|
49
|
+
|
|
50
|
+
Because every decision an agent can make is a **tool**, both brains (Claude and
|
|
51
|
+
the offline planner) drive the exact same action surface and produce the exact
|
|
52
|
+
same trace format.
|
|
53
|
+
|
|
54
|
+
### Architecture
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
text prompt
|
|
58
|
+
│
|
|
59
|
+
┌──────────▼───────────────────────────────────────────────┐
|
|
60
|
+
│ Orchestrator (the harness) │
|
|
61
|
+
│ │
|
|
62
|
+
│ Interpreter → Composer → Melodist → Arranger │ each agent runs a
|
|
63
|
+
│ │ │ ReAct loop:
|
|
64
|
+
│ ▼ │ reason (thinking)
|
|
65
|
+
│ Critic ──reject──┐ │ → act (tool call)
|
|
66
|
+
│ │approve │ one targeted │ → observe (result)
|
|
67
|
+
│ │ revision (bounded) │
|
|
68
|
+
└───────────────────────────────┼────────────┘ │
|
|
69
|
+
│ │
|
|
70
|
+
┌───────────────────────────▼───────────────────────┐ │
|
|
71
|
+
│ Tools (the ReAct action space) │ │
|
|
72
|
+
│ set_brief · get_theory · set_global · │ │
|
|
73
|
+
│ set_structure · set_progression · design_melody · │ │
|
|
74
|
+
│ set_instruments · set_bass · set_drums · │ │
|
|
75
|
+
│ analyze_score · finish / approve / request_revision│ │
|
|
76
|
+
└───────────────────────────┬───────────────────────┘ │
|
|
77
|
+
▼ │
|
|
78
|
+
Score blackboard (shared state) │
|
|
79
|
+
└──────────────────────────────┬─────────────────────────────┘
|
|
80
|
+
▼
|
|
81
|
+
performance.py → { melody, harmony, bass, drums } notes
|
|
82
|
+
│
|
|
83
|
+
┌───────────────────┴────────────────────┐
|
|
84
|
+
▼ ▼
|
|
85
|
+
midi_render.py (mido) synth.py (numpy software synth)
|
|
86
|
+
│ │
|
|
87
|
+
song.mid song.wav ──(ffmpeg)──► song.mp3
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### The agents
|
|
91
|
+
|
|
92
|
+
| Agent | Decides | Tools |
|
|
93
|
+
|---|---|---|
|
|
94
|
+
| **Interpreter** | mood, imagery, genre, key/mode/tempo, emotional arc | `set_brief`, `set_title` |
|
|
95
|
+
| **Composer** | time signature, section form + dynamic arc, chord progressions | `get_theory`, `set_global`, `set_structure`, `set_progression` |
|
|
96
|
+
| **Melodist** | per-section melody contour, register, density, syncopation | `design_melody`, `get_theory` |
|
|
97
|
+
| **Arranger** | instruments, per-section bass & drum styles | `set_instruments`, `set_bass`, `set_drums` |
|
|
98
|
+
| **Critic** | judges the whole piece; approves or bounces back one revision | `analyze_score`, `approve`, `request_revision` |
|
|
99
|
+
|
|
100
|
+
Every agent also has `analyze_score` (a sensing action) and a terminal `finish`.
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## Install
|
|
105
|
+
|
|
106
|
+
Uses [uv](https://docs.astral.sh/uv/).
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
uv sync --extra llm # installs the optional anthropic SDK for the Claude brain
|
|
110
|
+
# or, without the Claude brain (offline only):
|
|
111
|
+
uv sync
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
`ffmpeg` is optional — if present, an MP3 is exported alongside the WAV.
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Usage
|
|
119
|
+
|
|
120
|
+
### CLI
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
ark-audio "triumphant sunrise over the mountains"
|
|
124
|
+
ark-audio "a quiet dawn over still water" --offline --seed 7
|
|
125
|
+
ark-audio "lonely synthwave drive at 2am" --out renders/drive --no-mp3
|
|
126
|
+
echo "a storm breaking over the sea" | ark-audio # reads stdin
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Flags: `--offline` (force the deterministic brain), `--seed`, `--out`,
|
|
130
|
+
`--model`, `--effort low|medium|high|xhigh|max`, `--max-revisions`, `--reverb`,
|
|
131
|
+
`--no-mp3`.
|
|
132
|
+
|
|
133
|
+
### Library
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
from ark_audio_agent import compose
|
|
137
|
+
|
|
138
|
+
result = compose("a melancholy rainy afternoon", out_dir="renders/rain", offline=True)
|
|
139
|
+
print(result.outputs["wav"], "-", result.score.title)
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Outputs
|
|
143
|
+
|
|
144
|
+
Each run writes, into the output directory:
|
|
145
|
+
|
|
146
|
+
| File | What |
|
|
147
|
+
|---|---|
|
|
148
|
+
| `song.mid` | Standard MIDI file (open in any DAW / soundfont player) |
|
|
149
|
+
| `song.wav` | 44.1 kHz stereo audio from the built-in synth |
|
|
150
|
+
| `song.mp3` | 192 kbps MP3 (if `ffmpeg` is installed) |
|
|
151
|
+
| `score.json` | the full declarative Score (reload / tweak / re-render) |
|
|
152
|
+
| `brief.json` | compact human/LLM summary of the piece |
|
|
153
|
+
| `trace.jsonl` | every agent step: thought · action · observation |
|
|
154
|
+
| `report.md` | a readable write-up of the brief, structure, and agent trace |
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## The two brains
|
|
159
|
+
|
|
160
|
+
**Claude multi-agent** (default when an API key is set). Each agent runs
|
|
161
|
+
the canonical Messages-API tool-use loop with adaptive thinking; the model
|
|
162
|
+
reasons, calls a tool, reads the observation, and continues until it finishes.
|
|
163
|
+
Defaults to `claude-opus-5-5`. The API call degrades gracefully across
|
|
164
|
+
SDK/account differences (server-side refusal fallback → plain streaming → drop
|
|
165
|
+
effort → drop thinking), so the run never hard-fails on a capability mismatch.
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
export ANTHROPIC_API_KEY=sk-ant-...
|
|
169
|
+
ark-audio "a cathedral of light at the edge of the world" --effort high
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
**Claude on Microsoft Foundry (Azure).** Point the brain at a Foundry resource
|
|
173
|
+
instead and it uses the SDK's `AnthropicFoundry` client with API-key auth. A
|
|
174
|
+
Foundry endpoint takes precedence over `ANTHROPIC_API_KEY`:
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
export ANTHROPIC_FOUNDRY_API_KEY=...
|
|
178
|
+
export ANTHROPIC_FOUNDRY_RESOURCE=my-resource # or ANTHROPIC_FOUNDRY_BASE_URL=https://my-resource.services.ai.azure.com/anthropic/
|
|
179
|
+
export ARK_MODEL=claude-opus-5-5 # your Foundry deployment name
|
|
180
|
+
ark-audio "a cathedral of light at the edge of the world"
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Server-side refusal fallback is a first-party API feature, so it is skipped on
|
|
184
|
+
Foundry. Adaptive thinking and effort still apply.
|
|
185
|
+
|
|
186
|
+
**Deterministic** (offline, always available). A rule-based planner maps the
|
|
187
|
+
text to a feeling via a lexicon, then applies music-theory heuristics to make the
|
|
188
|
+
same tool calls an LLM would. Reproducible given a `--seed`. This is what runs in
|
|
189
|
+
CI and what powers `--offline`.
|
|
190
|
+
|
|
191
|
+
> If no key is found (and `--offline` isn't set), the CLI automatically uses the
|
|
192
|
+
> deterministic brain and says so.
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## How the music is realized
|
|
197
|
+
|
|
198
|
+
- **Theory** (`theory.py`): scales/modes, diatonic chords, Roman-numeral
|
|
199
|
+
resolution, scale-degree → MIDI, equal-temperament frequencies.
|
|
200
|
+
- **Blackboard** (`blackboard.py`): the declarative `Score` the agents edit.
|
|
201
|
+
- **Performance** (`performance.py`): realizes the Score into notes — a
|
|
202
|
+
contour-driven melody that snaps to chord tones on strong beats, chord comping
|
|
203
|
+
or sustained pads, several bass styles (roots, root-fifth, pulse, walking, arp),
|
|
204
|
+
and synthesized drum patterns (four-on-floor, backbeat, breakbeat, …), all
|
|
205
|
+
seeded and reproducible.
|
|
206
|
+
- **Synth** (`synth.py`): additive (band-limited) oscillator voices with ADSR,
|
|
207
|
+
per-instrument patches, synthesized percussion, an FFT-convolution reverb, and
|
|
208
|
+
a soft-clipping master bus.
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
## Development
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
uv run pytest # theory + pipeline tests (offline, no key needed)
|
|
216
|
+
uv run ark-audio "..." --offline
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## License
|
|
220
|
+
|
|
221
|
+
MIT.
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
# ark-audio-agent
|
|
2
|
+
|
|
3
|
+
**Autonomous text → music.** Give it a sentence; it reasons about the feeling,
|
|
4
|
+
composes a structured piece, and renders it to MIDI + audio — with no human in
|
|
5
|
+
the loop.
|
|
6
|
+
|
|
7
|
+
The interesting part is the *harness*: composition is driven by a team of
|
|
8
|
+
specialist agents running a **ReAct loop** (reason → act → observe) over a shared
|
|
9
|
+
musical blackboard. It runs on the **Claude API** when a key is available, and on
|
|
10
|
+
a **fully-offline deterministic music-theory brain** otherwise, so it always
|
|
11
|
+
produces a result.
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
ark-audio "a rainy night in a neon city, lonely but hopeful"
|
|
15
|
+
# → renders/a-rainy-night-in-a-neon-city-lonely-but-hopeful/song.mp3 (+ .wav, .mid, report.md, trace.jsonl)
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Why it's built this way
|
|
21
|
+
|
|
22
|
+
Rather than ask one model to emit raw notes (which drifts out of key and is hard
|
|
23
|
+
to steer), the system separates **musical intent** from **musical realization**:
|
|
24
|
+
|
|
25
|
+
- **Agents decide intent** — key, mode, tempo, song form, chord progressions, a
|
|
26
|
+
melody's contour/register/density, instrument roles, drum and bass styles.
|
|
27
|
+
- **Deterministic engines realize it** — pure music-theory functions turn that
|
|
28
|
+
intent into in-key notes, which a `mido` writer turns into MIDI and a
|
|
29
|
+
from-scratch `numpy` synthesizer turns into audio.
|
|
30
|
+
|
|
31
|
+
Because every decision an agent can make is a **tool**, both brains (Claude and
|
|
32
|
+
the offline planner) drive the exact same action surface and produce the exact
|
|
33
|
+
same trace format.
|
|
34
|
+
|
|
35
|
+
### Architecture
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
text prompt
|
|
39
|
+
│
|
|
40
|
+
┌──────────▼───────────────────────────────────────────────┐
|
|
41
|
+
│ Orchestrator (the harness) │
|
|
42
|
+
│ │
|
|
43
|
+
│ Interpreter → Composer → Melodist → Arranger │ each agent runs a
|
|
44
|
+
│ │ │ ReAct loop:
|
|
45
|
+
│ ▼ │ reason (thinking)
|
|
46
|
+
│ Critic ──reject──┐ │ → act (tool call)
|
|
47
|
+
│ │approve │ one targeted │ → observe (result)
|
|
48
|
+
│ │ revision (bounded) │
|
|
49
|
+
└───────────────────────────────┼────────────┘ │
|
|
50
|
+
│ │
|
|
51
|
+
┌───────────────────────────▼───────────────────────┐ │
|
|
52
|
+
│ Tools (the ReAct action space) │ │
|
|
53
|
+
│ set_brief · get_theory · set_global · │ │
|
|
54
|
+
│ set_structure · set_progression · design_melody · │ │
|
|
55
|
+
│ set_instruments · set_bass · set_drums · │ │
|
|
56
|
+
│ analyze_score · finish / approve / request_revision│ │
|
|
57
|
+
└───────────────────────────┬───────────────────────┘ │
|
|
58
|
+
▼ │
|
|
59
|
+
Score blackboard (shared state) │
|
|
60
|
+
└──────────────────────────────┬─────────────────────────────┘
|
|
61
|
+
▼
|
|
62
|
+
performance.py → { melody, harmony, bass, drums } notes
|
|
63
|
+
│
|
|
64
|
+
┌───────────────────┴────────────────────┐
|
|
65
|
+
▼ ▼
|
|
66
|
+
midi_render.py (mido) synth.py (numpy software synth)
|
|
67
|
+
│ │
|
|
68
|
+
song.mid song.wav ──(ffmpeg)──► song.mp3
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### The agents
|
|
72
|
+
|
|
73
|
+
| Agent | Decides | Tools |
|
|
74
|
+
|---|---|---|
|
|
75
|
+
| **Interpreter** | mood, imagery, genre, key/mode/tempo, emotional arc | `set_brief`, `set_title` |
|
|
76
|
+
| **Composer** | time signature, section form + dynamic arc, chord progressions | `get_theory`, `set_global`, `set_structure`, `set_progression` |
|
|
77
|
+
| **Melodist** | per-section melody contour, register, density, syncopation | `design_melody`, `get_theory` |
|
|
78
|
+
| **Arranger** | instruments, per-section bass & drum styles | `set_instruments`, `set_bass`, `set_drums` |
|
|
79
|
+
| **Critic** | judges the whole piece; approves or bounces back one revision | `analyze_score`, `approve`, `request_revision` |
|
|
80
|
+
|
|
81
|
+
Every agent also has `analyze_score` (a sensing action) and a terminal `finish`.
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## Install
|
|
86
|
+
|
|
87
|
+
Uses [uv](https://docs.astral.sh/uv/).
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
uv sync --extra llm # installs the optional anthropic SDK for the Claude brain
|
|
91
|
+
# or, without the Claude brain (offline only):
|
|
92
|
+
uv sync
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
`ffmpeg` is optional — if present, an MP3 is exported alongside the WAV.
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## Usage
|
|
100
|
+
|
|
101
|
+
### CLI
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
ark-audio "triumphant sunrise over the mountains"
|
|
105
|
+
ark-audio "a quiet dawn over still water" --offline --seed 7
|
|
106
|
+
ark-audio "lonely synthwave drive at 2am" --out renders/drive --no-mp3
|
|
107
|
+
echo "a storm breaking over the sea" | ark-audio # reads stdin
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Flags: `--offline` (force the deterministic brain), `--seed`, `--out`,
|
|
111
|
+
`--model`, `--effort low|medium|high|xhigh|max`, `--max-revisions`, `--reverb`,
|
|
112
|
+
`--no-mp3`.
|
|
113
|
+
|
|
114
|
+
### Library
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
from ark_audio_agent import compose
|
|
118
|
+
|
|
119
|
+
result = compose("a melancholy rainy afternoon", out_dir="renders/rain", offline=True)
|
|
120
|
+
print(result.outputs["wav"], "-", result.score.title)
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Outputs
|
|
124
|
+
|
|
125
|
+
Each run writes, into the output directory:
|
|
126
|
+
|
|
127
|
+
| File | What |
|
|
128
|
+
|---|---|
|
|
129
|
+
| `song.mid` | Standard MIDI file (open in any DAW / soundfont player) |
|
|
130
|
+
| `song.wav` | 44.1 kHz stereo audio from the built-in synth |
|
|
131
|
+
| `song.mp3` | 192 kbps MP3 (if `ffmpeg` is installed) |
|
|
132
|
+
| `score.json` | the full declarative Score (reload / tweak / re-render) |
|
|
133
|
+
| `brief.json` | compact human/LLM summary of the piece |
|
|
134
|
+
| `trace.jsonl` | every agent step: thought · action · observation |
|
|
135
|
+
| `report.md` | a readable write-up of the brief, structure, and agent trace |
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## The two brains
|
|
140
|
+
|
|
141
|
+
**Claude multi-agent** (default when an API key is set). Each agent runs
|
|
142
|
+
the canonical Messages-API tool-use loop with adaptive thinking; the model
|
|
143
|
+
reasons, calls a tool, reads the observation, and continues until it finishes.
|
|
144
|
+
Defaults to `claude-opus-5-5`. The API call degrades gracefully across
|
|
145
|
+
SDK/account differences (server-side refusal fallback → plain streaming → drop
|
|
146
|
+
effort → drop thinking), so the run never hard-fails on a capability mismatch.
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
export ANTHROPIC_API_KEY=sk-ant-...
|
|
150
|
+
ark-audio "a cathedral of light at the edge of the world" --effort high
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
**Claude on Microsoft Foundry (Azure).** Point the brain at a Foundry resource
|
|
154
|
+
instead and it uses the SDK's `AnthropicFoundry` client with API-key auth. A
|
|
155
|
+
Foundry endpoint takes precedence over `ANTHROPIC_API_KEY`:
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
export ANTHROPIC_FOUNDRY_API_KEY=...
|
|
159
|
+
export ANTHROPIC_FOUNDRY_RESOURCE=my-resource # or ANTHROPIC_FOUNDRY_BASE_URL=https://my-resource.services.ai.azure.com/anthropic/
|
|
160
|
+
export ARK_MODEL=claude-opus-5-5 # your Foundry deployment name
|
|
161
|
+
ark-audio "a cathedral of light at the edge of the world"
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Server-side refusal fallback is a first-party API feature, so it is skipped on
|
|
165
|
+
Foundry. Adaptive thinking and effort still apply.
|
|
166
|
+
|
|
167
|
+
**Deterministic** (offline, always available). A rule-based planner maps the
|
|
168
|
+
text to a feeling via a lexicon, then applies music-theory heuristics to make the
|
|
169
|
+
same tool calls an LLM would. Reproducible given a `--seed`. This is what runs in
|
|
170
|
+
CI and what powers `--offline`.
|
|
171
|
+
|
|
172
|
+
> If no key is found (and `--offline` isn't set), the CLI automatically uses the
|
|
173
|
+
> deterministic brain and says so.
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## How the music is realized
|
|
178
|
+
|
|
179
|
+
- **Theory** (`theory.py`): scales/modes, diatonic chords, Roman-numeral
|
|
180
|
+
resolution, scale-degree → MIDI, equal-temperament frequencies.
|
|
181
|
+
- **Blackboard** (`blackboard.py`): the declarative `Score` the agents edit.
|
|
182
|
+
- **Performance** (`performance.py`): realizes the Score into notes — a
|
|
183
|
+
contour-driven melody that snaps to chord tones on strong beats, chord comping
|
|
184
|
+
or sustained pads, several bass styles (roots, root-fifth, pulse, walking, arp),
|
|
185
|
+
and synthesized drum patterns (four-on-floor, backbeat, breakbeat, …), all
|
|
186
|
+
seeded and reproducible.
|
|
187
|
+
- **Synth** (`synth.py`): additive (band-limited) oscillator voices with ADSR,
|
|
188
|
+
per-instrument patches, synthesized percussion, an FFT-convolution reverb, and
|
|
189
|
+
a soft-clipping master bus.
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## Development
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
uv run pytest # theory + pipeline tests (offline, no key needed)
|
|
197
|
+
uv run ark-audio "..." --offline
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## License
|
|
201
|
+
|
|
202
|
+
MIT.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "ark-audio-agent"
|
|
3
|
+
version = "0.1.3"
|
|
4
|
+
description = "Autonomous text-to-music composer driven by a multi-agent ReAct harness (Claude API, with an offline deterministic fallback)."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.10"
|
|
7
|
+
keywords = [
|
|
8
|
+
"music",
|
|
9
|
+
"generative",
|
|
10
|
+
"agents",
|
|
11
|
+
"react",
|
|
12
|
+
"claude",
|
|
13
|
+
"midi",
|
|
14
|
+
"synthesizer",
|
|
15
|
+
]
|
|
16
|
+
dependencies = [
|
|
17
|
+
"numpy>=1.24",
|
|
18
|
+
"mido>=1.3",
|
|
19
|
+
"rich>=13.7",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
[[project.authors]]
|
|
23
|
+
name = "Immanuel R"
|
|
24
|
+
email = "raj@immanuel.co"
|
|
25
|
+
|
|
26
|
+
[project.license]
|
|
27
|
+
text = "MIT"
|
|
28
|
+
|
|
29
|
+
[project.urls]
|
|
30
|
+
Homepage = "https://github.com/ir-dev/ark-audio-agent"
|
|
31
|
+
Repository = "https://github.com/ir-dev/ark-audio-agent"
|
|
32
|
+
Issues = "https://github.com/ir-dev/ark-audio-agent/issues"
|
|
33
|
+
|
|
34
|
+
[project.optional-dependencies]
|
|
35
|
+
llm = ["anthropic>=0.107.1"]
|
|
36
|
+
|
|
37
|
+
[project.scripts]
|
|
38
|
+
ark-audio = "ark_audio_agent.cli:main"
|
|
39
|
+
|
|
40
|
+
[dependency-groups]
|
|
41
|
+
dev = ["pytest>=8.0"]
|
|
42
|
+
|
|
43
|
+
[build-system]
|
|
44
|
+
requires = ["uv_build>=0.12.7,<0.13.0"]
|
|
45
|
+
build-backend = "uv_build"
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "ark-audio-agent"
|
|
3
|
+
version = "0.1.3"
|
|
4
|
+
description = "Autonomous text-to-music composer driven by a multi-agent ReAct harness (Claude API, with an offline deterministic fallback)."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.10"
|
|
7
|
+
authors = [{ name = "Immanuel R", email = "raj@immanuel.co" }]
|
|
8
|
+
license = { text = "MIT" }
|
|
9
|
+
keywords = ["music", "generative", "agents", "react", "claude", "midi", "synthesizer"]
|
|
10
|
+
dependencies = [
|
|
11
|
+
"numpy>=1.24",
|
|
12
|
+
"mido>=1.3",
|
|
13
|
+
"rich>=13.7",
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
[project.urls]
|
|
17
|
+
Homepage = "https://github.com/ir-dev/ark-audio-agent"
|
|
18
|
+
Repository = "https://github.com/ir-dev/ark-audio-agent"
|
|
19
|
+
Issues = "https://github.com/ir-dev/ark-audio-agent/issues"
|
|
20
|
+
|
|
21
|
+
[project.optional-dependencies]
|
|
22
|
+
# Claude-powered multi-agent brain. Without this (or without an API key),
|
|
23
|
+
# the app runs fully offline on the deterministic music-theory brain.
|
|
24
|
+
# 0.107.1 is the first release whose AnthropicFoundry sends API-key auth correctly.
|
|
25
|
+
llm = ["anthropic>=0.107.1"]
|
|
26
|
+
|
|
27
|
+
[project.scripts]
|
|
28
|
+
ark-audio = "ark_audio_agent.cli:main"
|
|
29
|
+
|
|
30
|
+
[dependency-groups]
|
|
31
|
+
dev = ["pytest>=8.0"]
|
|
32
|
+
|
|
33
|
+
[build-system]
|
|
34
|
+
requires = ["uv_build>=0.12.7,<0.13.0"]
|
|
35
|
+
build-backend = "uv_build"
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""ark-audio-agent — autonomous text-to-music via a multi-agent ReAct harness.
|
|
2
|
+
|
|
3
|
+
Quick start (library):
|
|
4
|
+
|
|
5
|
+
from ark_audio_agent import compose
|
|
6
|
+
result = compose("a quiet dawn over still water", out_dir="renders/dawn")
|
|
7
|
+
print(result.outputs["wav"])
|
|
8
|
+
|
|
9
|
+
By default ``compose`` uses the Claude multi-agent brain when an
|
|
10
|
+
``ANTHROPIC_API_KEY`` (or a Microsoft Foundry endpoint and
|
|
11
|
+
``ANTHROPIC_FOUNDRY_API_KEY``) is set, and the fully-offline deterministic
|
|
12
|
+
brain otherwise. Pass ``offline=True`` to force the latter.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
__version__ = "0.1.3"
|
|
18
|
+
|
|
19
|
+
__all__ = ["compose", "Orchestrator", "DeterministicBrain", "RunResult", "__version__"]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def compose(source_text: str, out_dir: str = "renders/piece", *, seed: int = 42,
|
|
23
|
+
offline: bool = False, max_revisions: int = 2, make_mp3: bool = True,
|
|
24
|
+
on_event=None):
|
|
25
|
+
"""Compose a piece from text and render it. Returns a ``RunResult``."""
|
|
26
|
+
from .orchestrator import Orchestrator
|
|
27
|
+
|
|
28
|
+
brain = _default_brain(offline=offline, seed=seed)
|
|
29
|
+
orch = Orchestrator(brain, max_revisions=max_revisions, make_mp3=make_mp3)
|
|
30
|
+
return orch.compose(source_text, out_dir, seed=seed, on_event=on_event)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _default_brain(*, offline: bool, seed: int):
|
|
34
|
+
from .brain import DeterministicBrain
|
|
35
|
+
if offline:
|
|
36
|
+
return DeterministicBrain(seed=seed)
|
|
37
|
+
try:
|
|
38
|
+
from .llm import LLMBrain, anthropic_available
|
|
39
|
+
if anthropic_available():
|
|
40
|
+
return LLMBrain()
|
|
41
|
+
except Exception: # noqa: BLE001
|
|
42
|
+
pass
|
|
43
|
+
return DeterministicBrain(seed=seed)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# re-exports (kept lazy-friendly but convenient)
|
|
47
|
+
from .brain import DeterministicBrain # noqa: E402
|
|
48
|
+
from .orchestrator import Orchestrator, RunResult # noqa: E402
|