@hraness/dawg 0.6.1 → 0.7.0
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.
- package/CHANGELOG.md +92 -0
- package/DAWG.md +343 -116
- package/README.md +27 -25
- package/core/autotune.ts +1119 -0
- package/core/chords.ts +271 -23
- package/core/clips.ts +499 -0
- package/core/diff.ts +182 -104
- package/core/expression.ts +15 -0
- package/core/fx.ts +99 -29
- package/core/instruments.ts +19 -0
- package/core/keys.ts +3 -3
- package/core/loop.ts +5 -0
- package/core/lyrics.ts +297 -0
- package/core/master.ts +3 -3
- package/core/resonators.ts +16 -2
- package/core/routing.ts +165 -0
- package/core/score.ts +832 -13
- package/core/sdk/eval-child.ts +7 -2
- package/core/sdk/eval.ts +35 -6
- package/core/sdk/print.ts +276 -3
- package/core/sdk/sync-lyrics.ts +49 -0
- package/core/sdk/v1.ts +1849 -42
- package/core/sections.ts +292 -26
- package/core/sing.ts +815 -0
- package/core/style-provenance.ts +80 -0
- package/core/styles/africa-mena-southasia.ts +2893 -0
- package/core/styles/americas.ts +3810 -0
- package/core/styles/art.ts +4993 -0
- package/core/styles/base.ts +123 -0
- package/core/styles/cycles.ts +106 -0
- package/core/styles/electronic.ts +2723 -0
- package/core/styles/europe-asia-pacific.ts +2838 -0
- package/core/styles/excerpt.ts +29 -0
- package/core/styles/gamelan.ts +283 -0
- package/core/styles/generate.ts +2199 -0
- package/core/styles/index.ts +515 -0
- package/core/styles/parts.ts +106 -0
- package/core/styles/pop.ts +3189 -0
- package/core/styles/rock.ts +2993 -0
- package/core/styles/roots.ts +4175 -0
- package/core/styles/schema.ts +429 -0
- package/core/styles/taxonomy.ts +940 -0
- package/core/styles/validate.ts +528 -0
- package/core/tempo.ts +32 -2
- package/core/tuning.ts +19 -3
- package/core/vocoder.ts +524 -0
- package/guides/agent.md +29 -0
- package/guides/arrange.md +30 -0
- package/guides/audition.md +20 -14
- package/guides/automation.md +12 -7
- package/guides/chords.md +15 -15
- package/guides/effects.md +17 -16
- package/guides/faders.md +20 -16
- package/guides/files.md +13 -9
- package/guides/getting-started.md +15 -11
- package/guides/keys.md +19 -15
- package/guides/media.md +17 -12
- package/guides/mix.md +12 -6
- package/guides/music.md +23 -8
- package/guides/notes.md +15 -9
- package/guides/performance.md +15 -13
- package/guides/play.md +21 -13
- package/guides/project.md +25 -8
- package/guides/providers.md +20 -14
- package/guides/resample.md +15 -9
- package/guides/rhythm.md +17 -13
- package/guides/sessions.md +17 -7
- package/guides/show-me.md +31 -0
- package/guides/sound.md +25 -9
- package/guides/sounds.md +15 -13
- package/guides/styles.md +31 -0
- package/guides/tempo.md +15 -10
- package/guides/tracks.md +15 -10
- package/guides/tuning.md +32 -0
- package/guides/voice.md +31 -0
- package/guides/web-search.md +18 -8
- package/native/prebuilt/darwin-arm64/libdawg_sink.dylib +0 -0
- package/native/prebuilt/darwin-x64/libdawg_sink.dylib +0 -0
- package/native/prebuilt/linux-arm64/libdawg_sink.so +0 -0
- package/native/prebuilt/linux-x64/libdawg_sink.so +0 -0
- package/native/prebuilt/manifest.json +21 -0
- package/package.json +5 -2
- package/src/agent/agent.ts +126 -14
- package/src/agent/calibration-tools.ts +53 -0
- package/src/agent/clip-tools.ts +453 -0
- package/src/agent/command-agent.ts +369 -0
- package/src/agent/drum-tools.ts +2 -2
- package/src/agent/expression-tools.ts +1 -1
- package/src/agent/gateway.ts +246 -60
- package/src/agent/models.ts +53 -12
- package/src/agent/ops.ts +12 -1
- package/src/agent/pack-tools.ts +1 -1
- package/src/agent/planner.ts +13 -0
- package/src/agent/portable-schema.ts +80 -0
- package/src/agent/preview-tool.ts +4 -1
- package/src/agent/provider.ts +22 -8
- package/src/agent/rhythm-tools.ts +1 -1
- package/src/agent/section-tools.ts +1 -1
- package/src/agent/show-me.ts +497 -0
- package/src/agent/steer.ts +15 -0
- package/src/agent/style-tools.ts +217 -0
- package/src/agent/tool-error.ts +12 -0
- package/src/agent/tools.ts +108 -23
- package/src/agent/usage.ts +2 -2
- package/src/agent/voice-tools.ts +925 -0
- package/src/agent/xcb-agent.ts +11 -7
- package/src/argv.ts +38 -0
- package/src/audio/analysis.ts +253 -0
- package/src/audio/arrange.ts +37 -3
- package/src/audio/autotune-engine.ts +101 -0
- package/src/audio/autotune.ts +640 -0
- package/src/audio/clips.ts +240 -0
- package/src/audio/doctor.ts +86 -0
- package/src/audio/dsp/bandbank.ts +138 -0
- package/src/audio/dsp/envelope.ts +10 -0
- package/src/audio/dsp/follow.ts +120 -0
- package/src/audio/dsp/formant.ts +427 -0
- package/src/audio/dsp/glottal.ts +243 -0
- package/src/audio/dsp/interp.ts +7 -2
- package/src/audio/dsp/lpc.ts +50 -0
- package/src/audio/dsp/periodicity.ts +59 -0
- package/src/audio/dsp/pitch.ts +995 -0
- package/src/audio/dsp/psola.ts +199 -0
- package/src/audio/effects/chain.ts +3 -1
- package/src/audio/effects/common.ts +43 -0
- package/src/audio/effects/convolution.ts +7 -4
- package/src/audio/effects/filter.ts +48 -69
- package/src/audio/effects/formant.ts +263 -0
- package/src/audio/engine.ts +160 -28
- package/src/audio/fit.ts +35 -3
- package/src/audio/instrument-check.ts +59 -43
- package/src/audio/instruments.ts +4 -0
- package/src/audio/keys/calibration.ts +56 -0
- package/src/audio/keys/electric.ts +8 -1
- package/src/audio/keys/engine.ts +13 -1
- package/src/audio/keys/piano.ts +22 -2
- package/src/audio/kits.ts +135 -6
- package/src/audio/live.ts +114 -20
- package/src/audio/native.ts +615 -0
- package/src/audio/preview.ts +30 -2
- package/src/audio/render-worker.ts +2 -0
- package/src/audio/renderer.ts +2 -0
- package/src/audio/resample.ts +2 -1
- package/src/audio/sampler.ts +85 -4
- package/src/audio/samples.ts +20 -2
- package/src/audio/sing/analysis.ts +193 -0
- package/src/audio/sing/engine.ts +949 -0
- package/src/audio/strings/bow.ts +48 -5
- package/src/audio/strings/engine.ts +8 -2
- package/src/audio/synth/oscillators.ts +31 -21
- package/src/audio/synth/voice.ts +34 -1
- package/src/audio/vocoder/bank.ts +314 -0
- package/src/audio/vocoder/carrier.ts +165 -0
- package/src/audio/vocoder/control.ts +68 -0
- package/src/audio/vocoder/detect.ts +50 -0
- package/src/audio/vocoder/index.ts +304 -0
- package/src/audio/vocoder/talkbox.ts +143 -0
- package/src/audio/wav.ts +500 -77
- package/src/audio/winds/engine.ts +5 -1
- package/src/audio/winds/trim.ts +28 -4
- package/src/audio/winds/trims1.ts +297 -0
- package/src/audio/winds/voice.ts +15 -2
- package/src/auth/cli.ts +38 -36
- package/src/auth/credentials.ts +30 -1
- package/src/auth/login.ts +15 -9
- package/src/auth/tui.ts +19 -10
- package/src/commands/arrange.ts +44 -29
- package/src/commands/autotune.ts +421 -0
- package/src/commands/calibration.ts +74 -0
- package/src/commands/clips.ts +887 -0
- package/src/commands/drums.ts +3 -2
- package/src/commands/edit.ts +11 -4
- package/src/commands/expression.ts +1 -1
- package/src/commands/formant.ts +221 -0
- package/src/commands/fx.ts +101 -32
- package/src/commands/grammar.ts +558 -0
- package/src/commands/help.ts +610 -380
- package/src/commands/history.ts +18 -0
- package/src/commands/keys.ts +8 -8
- package/src/commands/modal.ts +1 -1
- package/src/commands/music.ts +1 -1
- package/src/commands/nearest.ts +53 -0
- package/src/commands/pack.ts +9 -2
- package/src/commands/param-range.ts +56 -0
- package/src/commands/parses.ts +130 -0
- package/src/commands/progression.ts +170 -0
- package/src/commands/rhythm.ts +3 -0
- package/src/commands/rig.ts +3 -24
- package/src/commands/sing.ts +478 -0
- package/src/commands/strum.ts +13 -1
- package/src/commands/style.ts +415 -0
- package/src/commands/time.ts +6 -3
- package/src/commands/tuning.ts +3 -3
- package/src/commands/vocal-pitch.ts +616 -0
- package/src/commands/vocal.ts +147 -0
- package/src/commands/vocoder.ts +627 -0
- package/src/commands/wind.ts +2 -2
- package/src/fs/durable.ts +50 -0
- package/src/lang/glossary.ts +493 -0
- package/src/launch-args.ts +163 -0
- package/src/main.ts +1397 -253
- package/src/media/cli.ts +20 -3
- package/src/media/import.ts +3 -1
- package/src/project/check.ts +21 -2
- package/src/project/clip-pins.ts +72 -0
- package/src/project/init.ts +23 -8
- package/src/project/sync.ts +418 -86
- package/src/render.ts +20 -1
- package/src/session/daemon.ts +3 -0
- package/src/session/meta.ts +14 -0
- package/src/session/origin.ts +154 -0
- package/src/session/port.ts +22 -4
- package/src/session/presence.ts +34 -4
- package/src/session/protocol.ts +5 -1
- package/src/session/rebase.ts +18 -5
- package/src/session/receipt.ts +258 -0
- package/src/session/store.ts +65 -43
- package/src/tui/arrange-menu.ts +65 -31
- package/src/tui/audition.ts +1 -1
- package/src/tui/euclid.ts +18 -13
- package/src/tui/fader.ts +228 -41
- package/src/tui/granular-menu.ts +2 -4
- package/src/tui/menu-clips.ts +297 -0
- package/src/tui/menu-time.ts +20 -13
- package/src/tui/menu-voice.ts +405 -0
- package/src/tui/menu.ts +759 -177
- package/src/tui/modal-menu.ts +6 -6
- package/src/tui/performance-menu.ts +5 -2
- package/src/tui/play-chords.ts +4 -2
- package/src/tui/play-mode.ts +15 -1
- package/src/tui/play-session.ts +47 -7
- package/src/tui/sing-menu.ts +278 -0
- package/src/tui/style-menu.ts +104 -0
- package/src/tui/vocoder-menu.ts +244 -0
- package/src/tui/wind-menu.ts +3 -3
- package/src/version.ts +8 -0
- package/src/web/fetch.ts +115 -29
- package/tui/activity.ts +180 -9
- package/tui/app.ts +274 -40
- package/tui/clip-row.ts +132 -0
- package/tui/delight.ts +144 -0
- package/tui/drawer.ts +70 -22
- package/tui/frame-gate.ts +76 -0
- package/tui/grammar.ts +112 -63
- package/tui/guide.ts +42 -4
- package/tui/highway.ts +269 -25
- package/tui/hints.ts +192 -0
- package/tui/input.ts +60 -9
- package/tui/keys.ts +1 -1
- package/tui/play-strip.ts +68 -14
- package/tui/prompt.ts +1 -1
- package/tui/screen.ts +144 -14
- package/tui/theme.ts +27 -0
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent tools for the style engine (core/styles): `list_styles` and
|
|
3
|
+
* `style_info` read the taxonomy and cards; `apply_style` generates a song
|
|
4
|
+
* from a style (optionally blended with a second one) and replaces the
|
|
5
|
+
* score with it in one undo step, the same path as the `style` command.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { DiffError, diffScores } from "../../core/diff.ts";
|
|
9
|
+
import { STYLE_TREE, searchStyles } from "../../core/styles/index.ts";
|
|
10
|
+
import {
|
|
11
|
+
STYLE_LIMITS,
|
|
12
|
+
StyleError,
|
|
13
|
+
styleScore,
|
|
14
|
+
} from "../../core/styles/generate.ts";
|
|
15
|
+
import { STYLE_FAMILIES } from "../../core/styles/taxonomy.ts";
|
|
16
|
+
import { resolveStyle } from "../../core/styles/index.ts";
|
|
17
|
+
import {
|
|
18
|
+
describeStyle,
|
|
19
|
+
findStyle,
|
|
20
|
+
generateFromCommand,
|
|
21
|
+
generatedSummary,
|
|
22
|
+
} from "../commands/style.ts";
|
|
23
|
+
import type { AgentTool } from "./tools.ts";
|
|
24
|
+
|
|
25
|
+
class StyleToolError extends Error {
|
|
26
|
+
constructor(message: string) {
|
|
27
|
+
super(message);
|
|
28
|
+
this.name = "ToolArgumentError";
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const MAX_LIST = 60;
|
|
33
|
+
|
|
34
|
+
function nodeJSON(id: string) {
|
|
35
|
+
const node = STYLE_TREE.get(id)!;
|
|
36
|
+
return {
|
|
37
|
+
id,
|
|
38
|
+
title: node.title,
|
|
39
|
+
...(node.leaf ? {} : { children: node.children.length }),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function knownStyle(value: unknown, name: string): string {
|
|
44
|
+
if (typeof value !== "string" || value.length === 0 || value.length > 120)
|
|
45
|
+
throw new StyleToolError(`${name} must be a style id or name`);
|
|
46
|
+
const id = findStyle(value);
|
|
47
|
+
if (!id) {
|
|
48
|
+
const near = searchStyles(value, 5).map((match) => match.id);
|
|
49
|
+
throw new StyleToolError(
|
|
50
|
+
`no style "${value}"${near.length ? `; did you mean ${near.join(", ")}` : "; call list_styles with a query"}`,
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
return id;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function integer(value: unknown, name: string, min: number, max: number) {
|
|
57
|
+
if (value === undefined) return undefined;
|
|
58
|
+
if (
|
|
59
|
+
typeof value !== "number" ||
|
|
60
|
+
!Number.isInteger(value) ||
|
|
61
|
+
value < min ||
|
|
62
|
+
value > max
|
|
63
|
+
)
|
|
64
|
+
throw new StyleToolError(`${name} must be an integer ${min}..${max}`);
|
|
65
|
+
return value;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export const STYLE_TOOLS = Object.freeze([
|
|
69
|
+
{
|
|
70
|
+
name: "list_styles",
|
|
71
|
+
description:
|
|
72
|
+
"Browse the style taxonomy (read-only). query: ranked search over ids, names, aliases and regions. parent: the children of one style id. Neither: the families and their root styles. Returns ids to pass to style_info and apply_style.",
|
|
73
|
+
parameters: {
|
|
74
|
+
type: "object",
|
|
75
|
+
properties: {
|
|
76
|
+
query: { type: "string", maxLength: 120 },
|
|
77
|
+
parent: { type: "string", maxLength: 120 },
|
|
78
|
+
},
|
|
79
|
+
additionalProperties: false,
|
|
80
|
+
},
|
|
81
|
+
plan(args) {
|
|
82
|
+
for (const key of Object.keys(args))
|
|
83
|
+
if (key !== "query" && key !== "parent")
|
|
84
|
+
throw new StyleToolError(`unknown argument ${key}`);
|
|
85
|
+
let result: unknown;
|
|
86
|
+
let summary: string;
|
|
87
|
+
if (args.query !== undefined) {
|
|
88
|
+
if (typeof args.query !== "string" || !args.query.trim())
|
|
89
|
+
throw new StyleToolError("query must be a non-empty string");
|
|
90
|
+
const matches = searchStyles(args.query, MAX_LIST).map((match) =>
|
|
91
|
+
nodeJSON(match.id),
|
|
92
|
+
);
|
|
93
|
+
result = { query: args.query, matches };
|
|
94
|
+
summary = `styles · ${matches.length} for "${args.query.slice(0, 40)}"`;
|
|
95
|
+
} else if (args.parent !== undefined) {
|
|
96
|
+
const id = knownStyle(args.parent, "parent");
|
|
97
|
+
const node = STYLE_TREE.get(id)!;
|
|
98
|
+
result = {
|
|
99
|
+
parent: id,
|
|
100
|
+
children: node.children.slice(0, MAX_LIST * 2).map(nodeJSON),
|
|
101
|
+
};
|
|
102
|
+
summary = `styles · ${node.children.length} under ${id}`;
|
|
103
|
+
} else {
|
|
104
|
+
result = {
|
|
105
|
+
families: STYLE_FAMILIES.map((family) => ({
|
|
106
|
+
family: family.key,
|
|
107
|
+
title: family.title,
|
|
108
|
+
roots: family.roots.map(nodeJSON),
|
|
109
|
+
})),
|
|
110
|
+
};
|
|
111
|
+
summary = "styles · families";
|
|
112
|
+
}
|
|
113
|
+
const content = JSON.stringify(result);
|
|
114
|
+
return {
|
|
115
|
+
kind: "action",
|
|
116
|
+
summary,
|
|
117
|
+
async run() {
|
|
118
|
+
return { content, summary };
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
name: "style_info",
|
|
125
|
+
description:
|
|
126
|
+
"Describe one style (read-only): taxonomy path, meter, tempo range, groove and swing, tuning and scales, harmony model, texture roles, and children.",
|
|
127
|
+
parameters: {
|
|
128
|
+
type: "object",
|
|
129
|
+
properties: { id: { type: "string", maxLength: 120 } },
|
|
130
|
+
required: ["id"],
|
|
131
|
+
additionalProperties: false,
|
|
132
|
+
},
|
|
133
|
+
plan(args) {
|
|
134
|
+
const id = knownStyle(args.id, "id");
|
|
135
|
+
const node = STYLE_TREE.get(id)!;
|
|
136
|
+
const content = JSON.stringify({
|
|
137
|
+
id,
|
|
138
|
+
lines: describeStyle(resolveStyle(id)),
|
|
139
|
+
children: node.children,
|
|
140
|
+
});
|
|
141
|
+
const summary = `style ${id}`;
|
|
142
|
+
return {
|
|
143
|
+
kind: "action",
|
|
144
|
+
summary,
|
|
145
|
+
async run() {
|
|
146
|
+
return { content, summary };
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
name: "apply_style",
|
|
153
|
+
description: `Replace the whole song with one generated from a style: drums, bass, harmony, melody and form from the style's patterns (one undo step). bars ${STYLE_LIMITS.minBars}..${STYLE_LIMITS.maxBars} (default ${STYLE_LIMITS.defaultBars}); seed picks a variation (same seed, same song). blend + weight (0 is id, 1 is blend) mixes two styles.`,
|
|
154
|
+
parameters: {
|
|
155
|
+
type: "object",
|
|
156
|
+
properties: {
|
|
157
|
+
id: { type: "string", maxLength: 120 },
|
|
158
|
+
bars: {
|
|
159
|
+
type: "integer",
|
|
160
|
+
minimum: STYLE_LIMITS.minBars,
|
|
161
|
+
maximum: STYLE_LIMITS.maxBars,
|
|
162
|
+
},
|
|
163
|
+
seed: { type: "integer", minimum: 0, maximum: STYLE_LIMITS.maxSeed },
|
|
164
|
+
blend: { type: "string", maxLength: 120 },
|
|
165
|
+
weight: { type: "number", minimum: 0, maximum: 1 },
|
|
166
|
+
},
|
|
167
|
+
required: ["id"],
|
|
168
|
+
additionalProperties: false,
|
|
169
|
+
},
|
|
170
|
+
plan(args, context) {
|
|
171
|
+
for (const key of Object.keys(args))
|
|
172
|
+
if (!["id", "bars", "seed", "blend", "weight"].includes(key))
|
|
173
|
+
throw new StyleToolError(`unknown argument ${key}`);
|
|
174
|
+
const id = knownStyle(args.id, "id");
|
|
175
|
+
const bars = integer(
|
|
176
|
+
args.bars,
|
|
177
|
+
"bars",
|
|
178
|
+
STYLE_LIMITS.minBars,
|
|
179
|
+
STYLE_LIMITS.maxBars,
|
|
180
|
+
);
|
|
181
|
+
const seed = integer(args.seed, "seed", 0, STYLE_LIMITS.maxSeed);
|
|
182
|
+
if (args.weight !== undefined && args.blend === undefined)
|
|
183
|
+
throw new StyleToolError("weight needs blend");
|
|
184
|
+
let blend: { id: string; weight: number } | undefined;
|
|
185
|
+
if (args.blend !== undefined) {
|
|
186
|
+
const weight = args.weight ?? 0.5;
|
|
187
|
+
if (
|
|
188
|
+
typeof weight !== "number" ||
|
|
189
|
+
!Number.isFinite(weight) ||
|
|
190
|
+
weight < 0 ||
|
|
191
|
+
weight > 1
|
|
192
|
+
)
|
|
193
|
+
throw new StyleToolError("weight must be 0..1");
|
|
194
|
+
blend = { id: knownStyle(args.blend, "blend"), weight };
|
|
195
|
+
}
|
|
196
|
+
try {
|
|
197
|
+
const generated = generateFromCommand({
|
|
198
|
+
type: "style-apply",
|
|
199
|
+
id,
|
|
200
|
+
...(bars !== undefined ? { bars } : {}),
|
|
201
|
+
...(seed !== undefined ? { seed } : {}),
|
|
202
|
+
...(blend ? { blend } : {}),
|
|
203
|
+
});
|
|
204
|
+
const next = styleScore(generated);
|
|
205
|
+
return {
|
|
206
|
+
kind: "score",
|
|
207
|
+
operations: diffScores(context.score, next),
|
|
208
|
+
summary: generatedSummary(generated).slice(0, 160),
|
|
209
|
+
};
|
|
210
|
+
} catch (error) {
|
|
211
|
+
if (error instanceof StyleError || error instanceof DiffError)
|
|
212
|
+
throw new StyleToolError(error.message);
|
|
213
|
+
throw error;
|
|
214
|
+
}
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
] satisfies AgentTool[]);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The tool-argument error, in its own module so tool files that `tools.ts`
|
|
3
|
+
* spreads (voice-tools.ts) can throw it without an import cycle.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/** A model-supplied argument that the tool refused; never mutates the score. */
|
|
7
|
+
export class ToolArgumentError extends Error {
|
|
8
|
+
constructor(message: string) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.name = "ToolArgumentError";
|
|
11
|
+
}
|
|
12
|
+
}
|
package/src/agent/tools.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
+
import { portableSchema } from "./portable-schema.ts";
|
|
2
|
+
import { midiToPitch } from "../../core/pitch.ts";
|
|
3
|
+
import { isGuideInstrument, vocalChainPatch } from "../../core/clips.ts";
|
|
1
4
|
import { INSTRUMENT_WORDS } from "../../core/instruments.ts";
|
|
5
|
+
import type { TrackVocoder } from "../../core/vocoder.ts";
|
|
2
6
|
import {
|
|
3
7
|
DEFAULT_STRING_PRESET,
|
|
4
8
|
STRING_INSTRUMENT,
|
|
@@ -83,6 +87,8 @@ import {
|
|
|
83
87
|
applyFxCommand,
|
|
84
88
|
effectPatch,
|
|
85
89
|
effectValues,
|
|
90
|
+
FORMANT_VOWEL_HINT,
|
|
91
|
+
formantGotVowel,
|
|
86
92
|
parseEffectName,
|
|
87
93
|
parseParamName,
|
|
88
94
|
parseFxCommand,
|
|
@@ -102,12 +108,15 @@ import { RHYTHM_TOOLS } from "./rhythm-tools.ts";
|
|
|
102
108
|
import { CHORD_TOOLS } from "./chord-tools.ts";
|
|
103
109
|
import { EXPRESSION_TOOLS } from "./expression-tools.ts";
|
|
104
110
|
import { TUNING_TOOLS } from "./tuning-tools.ts";
|
|
111
|
+
import { CALIBRATION_TOOLS } from "./calibration-tools.ts";
|
|
105
112
|
import { MASTER_TOOLS } from "./master-tools.ts";
|
|
113
|
+
import { STYLE_TOOLS } from "./style-tools.ts";
|
|
106
114
|
import { DRUM_TOOLS } from "./drum-tools.ts";
|
|
107
115
|
import { TIME_TOOLS } from "./time-tools.ts";
|
|
108
116
|
import { SECTION_TOOLS } from "./section-tools.ts";
|
|
109
117
|
import { GRANULAR_TOOLS } from "./granular-tools.ts";
|
|
110
118
|
import { RESAMPLE_TOOLS } from "./resample-tool.ts";
|
|
119
|
+
import { VOICE_TOOLS } from "./voice-tools.ts";
|
|
111
120
|
import type { MediaResult, MediaRunContext } from "../media/types.ts";
|
|
112
121
|
import { instrumentPatch, pitchToMidi } from "./ops.ts";
|
|
113
122
|
import { TUNING_LIMITS } from "../../core/tuning.ts";
|
|
@@ -255,17 +264,17 @@ export type ToolContext = Readonly<{
|
|
|
255
264
|
export type AgentTool = Readonly<{
|
|
256
265
|
name: string;
|
|
257
266
|
description: string;
|
|
267
|
+
/**
|
|
268
|
+
* Callable but not advertised: a duplicate kept so older transcripts and
|
|
269
|
+
* models still work. `chatTools` and the xcb catalog leave it out.
|
|
270
|
+
*/
|
|
271
|
+
hidden?: boolean;
|
|
258
272
|
parameters: Record<string, unknown>;
|
|
259
273
|
plan: (args: Record<string, unknown>, context: ToolContext) => ToolPlan;
|
|
260
274
|
}>;
|
|
261
275
|
|
|
262
|
-
|
|
263
|
-
export
|
|
264
|
-
constructor(message: string) {
|
|
265
|
-
super(message);
|
|
266
|
-
this.name = "ToolArgumentError";
|
|
267
|
-
}
|
|
268
|
-
}
|
|
276
|
+
import { ToolArgumentError } from "./tool-error.ts";
|
|
277
|
+
export { ToolArgumentError };
|
|
269
278
|
|
|
270
279
|
const MAX_NOTES_PER_CALL = 128;
|
|
271
280
|
const MAX_EXPLAIN_CHARS = 2_000;
|
|
@@ -404,7 +413,7 @@ export const AGENT_TOOLS: readonly AgentTool[] = Object.freeze([
|
|
|
404
413
|
{
|
|
405
414
|
name: "update_notes",
|
|
406
415
|
description:
|
|
407
|
-
"Move, resize, transpose, re-velocity or detune existing notes by id. Times are in beats.",
|
|
416
|
+
"Move, resize, transpose, re-velocity or detune existing notes by id. Times are in beats. To transpose, prefer transpose (semitones relative to the note's current pitch, e.g. 2 up a whole step, -12 down an octave) over an absolute pitch.",
|
|
408
417
|
parameters: {
|
|
409
418
|
type: "object",
|
|
410
419
|
properties: {
|
|
@@ -424,6 +433,13 @@ export const AGENT_TOOLS: readonly AgentTool[] = Object.freeze([
|
|
|
424
433
|
{ type: "string", maxLength: 4 },
|
|
425
434
|
],
|
|
426
435
|
},
|
|
436
|
+
transpose: {
|
|
437
|
+
type: "integer",
|
|
438
|
+
minimum: -127,
|
|
439
|
+
maximum: 127,
|
|
440
|
+
description:
|
|
441
|
+
"Semitones relative to the current pitch (not with pitch)",
|
|
442
|
+
},
|
|
427
443
|
velocity: { type: "number", minimum: 0, maximum: 1 },
|
|
428
444
|
cents: {
|
|
429
445
|
type: "number",
|
|
@@ -442,6 +458,9 @@ export const AGENT_TOOLS: readonly AgentTool[] = Object.freeze([
|
|
|
442
458
|
},
|
|
443
459
|
plan(args, context) {
|
|
444
460
|
const tpb = context.score.ticksPerBeat;
|
|
461
|
+
// Pitch changes as before→after, so the model sees what it did and
|
|
462
|
+
// does not transpose again from the refreshed brief.
|
|
463
|
+
const moves: string[] = [];
|
|
445
464
|
const operations = list(args, "updates", 1, MAX_NOTES_PER_CALL).map(
|
|
446
465
|
(value, index): ScoreOperation => {
|
|
447
466
|
const update = record(value, `updates[${index}]`);
|
|
@@ -459,8 +478,41 @@ export const AGENT_TOOLS: readonly AgentTool[] = Object.freeze([
|
|
|
459
478
|
});
|
|
460
479
|
if (duration !== undefined)
|
|
461
480
|
patch.durationTicks = Math.max(1, Math.round(duration * tpb));
|
|
481
|
+
const before = context.score.notes.find((n) => n.id === noteId);
|
|
462
482
|
if (update.pitch !== undefined)
|
|
463
483
|
patch.pitch = pitch(update.pitch, `updates[${index}].pitch`);
|
|
484
|
+
// Small models fill every field: transpose 0 beside a pitch means
|
|
485
|
+
// no shift, and a pitch that equals the transposed note agrees.
|
|
486
|
+
if (
|
|
487
|
+
patch.pitch !== undefined &&
|
|
488
|
+
update.transpose !== undefined &&
|
|
489
|
+
update.transpose !== 0 &&
|
|
490
|
+
patch.pitch !== (before?.pitch ?? 0) + Number(update.transpose)
|
|
491
|
+
)
|
|
492
|
+
throw new ToolArgumentError(
|
|
493
|
+
`updates[${index}] sets both pitch and transpose; use one`,
|
|
494
|
+
);
|
|
495
|
+
if (update.transpose !== undefined && patch.pitch === undefined) {
|
|
496
|
+
const shift = update.transpose;
|
|
497
|
+
if (typeof shift !== "number" || !Number.isInteger(shift))
|
|
498
|
+
throw new ToolArgumentError(
|
|
499
|
+
`updates[${index}].transpose must be whole semitones`,
|
|
500
|
+
);
|
|
501
|
+
const moved = (before?.pitch ?? 0) + shift;
|
|
502
|
+
if (moved < 0 || moved > 127)
|
|
503
|
+
throw new ToolArgumentError(
|
|
504
|
+
`updates[${index}].transpose moves ${noteId} to MIDI ${moved}, outside 0..127`,
|
|
505
|
+
);
|
|
506
|
+
patch.pitch = moved;
|
|
507
|
+
}
|
|
508
|
+
if (
|
|
509
|
+
patch.pitch !== undefined &&
|
|
510
|
+
before &&
|
|
511
|
+
patch.pitch !== before.pitch
|
|
512
|
+
)
|
|
513
|
+
moves.push(
|
|
514
|
+
`${noteId} ${midiToPitch(before.pitch)}→${midiToPitch(patch.pitch)}`,
|
|
515
|
+
);
|
|
464
516
|
const velocity = optionalNumber(update, "velocity", {
|
|
465
517
|
min: 0,
|
|
466
518
|
max: 1,
|
|
@@ -479,7 +531,11 @@ export const AGENT_TOOLS: readonly AgentTool[] = Object.freeze([
|
|
|
479
531
|
return {
|
|
480
532
|
kind: "score",
|
|
481
533
|
operations,
|
|
482
|
-
summary: `~${operations.length} note${operations.length === 1 ? "" : "s"}
|
|
534
|
+
summary: `~${operations.length} note${operations.length === 1 ? "" : "s"}${
|
|
535
|
+
moves.length === 0
|
|
536
|
+
? ""
|
|
537
|
+
: ` · pitch ${moves.slice(0, 8).join(", ")}${moves.length > 8 ? ` +${moves.length - 8} more` : ""}`
|
|
538
|
+
}`,
|
|
483
539
|
};
|
|
484
540
|
},
|
|
485
541
|
},
|
|
@@ -520,7 +576,18 @@ export const AGENT_TOOLS: readonly AgentTool[] = Object.freeze([
|
|
|
520
576
|
return {
|
|
521
577
|
kind: "score",
|
|
522
578
|
operations: [
|
|
523
|
-
{
|
|
579
|
+
{
|
|
580
|
+
type: "updateTrack",
|
|
581
|
+
trackId,
|
|
582
|
+
patch: {
|
|
583
|
+
// `vocal` (0.7): the vocal chain fills unset effects.
|
|
584
|
+
...(isGuideInstrument(patch.instrument)
|
|
585
|
+
? vocalChainPatch(track)
|
|
586
|
+
: {}),
|
|
587
|
+
...patch,
|
|
588
|
+
...rig,
|
|
589
|
+
},
|
|
590
|
+
},
|
|
524
591
|
],
|
|
525
592
|
trackId,
|
|
526
593
|
summary:
|
|
@@ -899,7 +966,7 @@ export const AGENT_TOOLS: readonly AgentTool[] = Object.freeze([
|
|
|
899
966
|
},
|
|
900
967
|
{
|
|
901
968
|
name: "set_keys",
|
|
902
|
-
description: `Shape a
|
|
969
|
+
description: `Shape a modeled piano, electric keys (epiano wurli clav) or organ: preset (instrument, keys and its effects), params ${KEYS_SIMPLE.join(" ")}… (pianos: sym 0.5 adds sympathetic bloom under the sustain pedal, good for Chopin and Debussy; epiano: bark bell tone vibe; wurli: trem; clav: pickup mute; null unsets; DAWG.md lists all), or reset. Stored "piano" stays legacy; set_instrument piano writes grand. Organs (not "organ", a legacy synth): presets tonewheel combo pipe… (aliases hammond b3 farfisa church); drawbars "888800008", registers "08880", stops (plenum, flute8…), rotary; params perc percdecay percvol click scanner drive (tonewheel), voice vib vibmod drive (combo), chiff wind trem (pipe). A row the family does not read is refused.`,
|
|
903
970
|
parameters: {
|
|
904
971
|
type: "object",
|
|
905
972
|
properties: {
|
|
@@ -1278,7 +1345,8 @@ export const AGENT_TOOLS: readonly AgentTool[] = Object.freeze([
|
|
|
1278
1345
|
},
|
|
1279
1346
|
{
|
|
1280
1347
|
name: "set_effects",
|
|
1281
|
-
|
|
1348
|
+
hidden: true,
|
|
1349
|
+
description: `Shorthand for set_fx (use set_fx): low-pass filter, delay (beats) and reverb (mix 0.15..0.35 is a room); null removes one.`,
|
|
1282
1350
|
parameters: {
|
|
1283
1351
|
type: "object",
|
|
1284
1352
|
properties: {
|
|
@@ -1438,7 +1506,8 @@ export const AGENT_TOOLS: readonly AgentTool[] = Object.freeze([
|
|
|
1438
1506
|
},
|
|
1439
1507
|
{
|
|
1440
1508
|
name: "add_drums",
|
|
1441
|
-
|
|
1509
|
+
hidden: true,
|
|
1510
|
+
description: `Use set_rhythm. Add one-off drum hits to a kit track (create one with create_track instrument "kit"); prefer set_rhythm for repeating beats. Voices: ${DRUM_VOICES.map((info) => info.voice).join(", ")}. Give explicit hits, and/or patterns that repeat a voice every N beats across the loop.`,
|
|
1442
1511
|
parameters: {
|
|
1443
1512
|
type: "object",
|
|
1444
1513
|
properties: {
|
|
@@ -1490,14 +1559,14 @@ export const AGENT_TOOLS: readonly AgentTool[] = Object.freeze([
|
|
|
1490
1559
|
const track = context.score.tracks.find((t) => t.id === trackId)!;
|
|
1491
1560
|
if (!isDrumInstrument(track.instrument))
|
|
1492
1561
|
throw new ToolArgumentError(
|
|
1493
|
-
`track ${trackId} is ${track.instrument}, not a
|
|
1562
|
+
`track ${trackId} is ${track.instrument}, not a kit; set_instrument kit or create a kit track`,
|
|
1494
1563
|
);
|
|
1495
1564
|
const tpb = context.score.ticksPerBeat;
|
|
1496
1565
|
const loopBeats = loopTicksOf(context.score) / tpb;
|
|
1497
1566
|
const voiceOf = (value: unknown, label: string) => {
|
|
1498
1567
|
const voice =
|
|
1499
1568
|
typeof value === "string" ? parseDrumVoice(value) : undefined;
|
|
1500
|
-
if (!voice) throw new ToolArgumentError(`${label}: unknown drum
|
|
1569
|
+
if (!voice) throw new ToolArgumentError(`${label}: unknown drum`);
|
|
1501
1570
|
return voice;
|
|
1502
1571
|
};
|
|
1503
1572
|
const hits: { voice: DrumVoice; beat: number; velocity: number }[] = [];
|
|
@@ -1962,14 +2031,18 @@ export const AGENT_TOOLS: readonly AgentTool[] = Object.freeze([
|
|
|
1962
2031
|
...CHORD_TOOLS,
|
|
1963
2032
|
...EXPRESSION_TOOLS,
|
|
1964
2033
|
...TUNING_TOOLS,
|
|
2034
|
+
...CALIBRATION_TOOLS,
|
|
1965
2035
|
...DRUM_TOOLS,
|
|
1966
2036
|
...TIME_TOOLS,
|
|
1967
2037
|
...SECTION_TOOLS,
|
|
1968
2038
|
...MEDIA_TOOLS,
|
|
1969
2039
|
...PACK_TOOLS,
|
|
1970
2040
|
...MASTER_TOOLS,
|
|
2041
|
+
...STYLE_TOOLS,
|
|
1971
2042
|
...GRANULAR_TOOLS,
|
|
1972
2043
|
...RESAMPLE_TOOLS,
|
|
2044
|
+
// 0.7 Voice: one array per lane in voice-tools.ts.
|
|
2045
|
+
...VOICE_TOOLS,
|
|
1973
2046
|
// Looks tools up at call time, so it can plan any of the above.
|
|
1974
2047
|
previewSoundTool((name) => findAgentTool(name)),
|
|
1975
2048
|
] satisfies AgentTool[]);
|
|
@@ -2066,14 +2139,16 @@ export function findAgentTool(
|
|
|
2066
2139
|
export function chatTools(
|
|
2067
2140
|
tools: readonly AgentTool[] = AGENT_TOOLS,
|
|
2068
2141
|
): ChatTool[] {
|
|
2069
|
-
return tools
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2142
|
+
return tools
|
|
2143
|
+
.filter((tool) => !tool.hidden)
|
|
2144
|
+
.map((tool) => ({
|
|
2145
|
+
type: "function",
|
|
2146
|
+
function: {
|
|
2147
|
+
name: tool.name,
|
|
2148
|
+
description: tool.description,
|
|
2149
|
+
parameters: portableSchema(tool.parameters),
|
|
2150
|
+
},
|
|
2151
|
+
}));
|
|
2077
2152
|
}
|
|
2078
2153
|
|
|
2079
2154
|
function fxToolCommand(
|
|
@@ -2099,6 +2174,14 @@ function fxToolCommand(
|
|
|
2099
2174
|
throw new ToolArgumentError("set_fx needs on, preset, or params");
|
|
2100
2175
|
}
|
|
2101
2176
|
const params = record(args.params, "params");
|
|
2177
|
+
if (
|
|
2178
|
+
effect === "formant" &&
|
|
2179
|
+
formantGotVowel([
|
|
2180
|
+
...Object.keys(params),
|
|
2181
|
+
...Object.values(params),
|
|
2182
|
+
] as string[])
|
|
2183
|
+
)
|
|
2184
|
+
throw new ToolArgumentError(FORMANT_VOWEL_HINT);
|
|
2102
2185
|
const values: Record<string, number | string | boolean> = {};
|
|
2103
2186
|
for (const [name, value] of Object.entries(params)) {
|
|
2104
2187
|
const param = parseParamName(effect, name);
|
|
@@ -2296,6 +2379,7 @@ function instrumentName(value: unknown): {
|
|
|
2296
2379
|
string?: { preset: string };
|
|
2297
2380
|
modal?: TrackModal;
|
|
2298
2381
|
wind?: TrackWind;
|
|
2382
|
+
vocoder?: TrackVocoder;
|
|
2299
2383
|
} {
|
|
2300
2384
|
const word = typeof value === "string" ? value.trim() : undefined;
|
|
2301
2385
|
if (word === STRING_INSTRUMENT)
|
|
@@ -2309,6 +2393,7 @@ function instrumentName(value: unknown): {
|
|
|
2309
2393
|
(!patch.string &&
|
|
2310
2394
|
!patch.modal &&
|
|
2311
2395
|
!patch.wind &&
|
|
2396
|
+
!patch.vocoder &&
|
|
2312
2397
|
!(AVAILABLE_INSTRUMENTS as readonly string[]).includes(patch.instrument))
|
|
2313
2398
|
)
|
|
2314
2399
|
throw new ToolArgumentError(
|
package/src/agent/usage.ts
CHANGED
|
@@ -232,7 +232,7 @@ export class SpendMeter {
|
|
|
232
232
|
|
|
233
233
|
/**
|
|
234
234
|
* The spend line: `$0.12 session · $0.48 today · opus-5.5 · gateway`;
|
|
235
|
-
* `subscription · sonnet · claude`; `
|
|
235
|
+
* `subscription · sonnet · claude`; `commands only`. Narrow widths
|
|
236
236
|
* drop the today total, then the session total, then the provider.
|
|
237
237
|
*/
|
|
238
238
|
export function spendLine(
|
|
@@ -245,7 +245,7 @@ export function spendLine(
|
|
|
245
245
|
},
|
|
246
246
|
width = 120,
|
|
247
247
|
): string {
|
|
248
|
-
if (options.kind === "offline") return "
|
|
248
|
+
if (options.kind === "offline") return "commands only";
|
|
249
249
|
const tail = [options.model, options.provider].filter(Boolean) as string[];
|
|
250
250
|
const money =
|
|
251
251
|
options.kind === "subscription"
|