@hraness/dawg 0.6.1 → 0.8.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 +118 -0
- package/DAWG.md +454 -140
- package/README.md +30 -26
- package/core/autotune.ts +1119 -0
- package/core/chords.ts +271 -23
- package/core/clips.ts +499 -0
- package/core/diff.ts +184 -104
- package/core/expression.ts +15 -0
- package/core/fx.ts +99 -29
- package/core/ids.ts +52 -0
- package/core/instruments.ts +19 -0
- package/core/keys.ts +3 -3
- package/core/loop.ts +8 -0
- package/core/lyrics.ts +297 -0
- package/core/master.ts +3 -3
- package/core/range.ts +581 -0
- package/core/resonators.ts +16 -2
- package/core/routing.ts +165 -0
- package/core/score.ts +955 -14
- package/core/sdk/eval-child.ts +7 -2
- package/core/sdk/eval.ts +35 -6
- package/core/sdk/print.ts +287 -5
- package/core/sdk/sync-lyrics.ts +49 -0
- package/core/sdk/v1.ts +2132 -46
- package/core/sections.ts +480 -35
- 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 +31 -0
- package/guides/audio.md +31 -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 +15 -7
- package/guides/music.md +23 -8
- package/guides/notes.md +15 -9
- package/guides/panes.md +32 -0
- package/guides/performance.md +15 -13
- package/guides/play.md +21 -13
- package/guides/project.md +26 -8
- package/guides/providers.md +20 -14
- package/guides/resample.md +15 -9
- package/guides/rhythm.md +18 -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/tape.md +32 -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 +6 -2
- package/src/agent/agent.ts +130 -18
- package/src/agent/calibration-tools.ts +53 -0
- package/src/agent/clip-tools.ts +453 -0
- package/src/agent/command-agent.ts +378 -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 +27 -1
- package/src/agent/pack-tools.ts +1 -1
- package/src/agent/planner.ts +25 -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/range-tools.ts +216 -0
- 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 +110 -23
- package/src/agent/usage.ts +2 -2
- package/src/agent/voice-tools.ts +925 -0
- package/src/agent/xcb-agent.ts +13 -10
- package/src/argv.ts +38 -0
- package/src/audio/analysis.ts +253 -0
- package/src/audio/arrange.ts +49 -5
- package/src/audio/audio-command.ts +218 -0
- 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/devices.ts +264 -0
- package/src/audio/doctor.ts +157 -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 +360 -40
- 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 +82 -32
- 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 +560 -0
- package/src/commands/help.ts +774 -366
- package/src/commands/history.ts +139 -10
- package/src/commands/keys.ts +25 -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 +135 -0
- package/src/commands/progression.ts +170 -0
- package/src/commands/range.ts +763 -0
- package/src/commands/resample.ts +13 -10
- package/src/commands/rhythm.ts +3 -0
- package/src/commands/rig.ts +3 -24
- package/src/commands/sample.ts +11 -1
- package/src/commands/sing.ts +474 -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 +617 -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/identity/actor.ts +127 -0
- package/src/lang/glossary.ts +511 -0
- package/src/launch-args.ts +267 -0
- package/src/main.ts +2508 -308
- 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/client.ts +107 -3
- package/src/session/clipboard.ts +79 -0
- package/src/session/daemon.ts +199 -5
- package/src/session/live-host.ts +232 -0
- package/src/session/meta.ts +14 -0
- package/src/session/origin.ts +174 -0
- package/src/session/port.ts +109 -14
- package/src/session/presence.ts +34 -4
- package/src/session/protocol.ts +365 -7
- package/src/session/rebase.ts +63 -12
- package/src/session/receipt.ts +265 -0
- package/src/session/shared-live.ts +131 -0
- package/src/session/store.ts +126 -45
- package/src/tui/arrange-menu.ts +225 -23
- package/src/tui/audition.ts +1 -1
- package/src/tui/euclid.ts +113 -27
- package/src/tui/fader.ts +282 -42
- package/src/tui/granular-menu.ts +2 -4
- package/src/tui/knob-fields.ts +107 -0
- package/src/tui/knob-map.ts +198 -0
- 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 +987 -179
- 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 +243 -28
- package/src/tui/sing-menu.ts +278 -0
- package/src/tui/style-menu.ts +104 -0
- package/src/tui/tape-mode.ts +580 -0
- package/src/tui/tape-view.ts +263 -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 +491 -84
- package/tui/clip-row.ts +132 -0
- package/tui/delight.ts +193 -0
- package/tui/drawer.ts +233 -27
- package/tui/frame-gate.ts +76 -0
- package/tui/grammar.ts +143 -65
- package/tui/guide.ts +50 -5
- package/tui/highway.ts +309 -30
- package/tui/hints.ts +197 -0
- package/tui/hits.ts +7 -1
- package/tui/input.ts +60 -9
- package/tui/keys.ts +1 -1
- package/tui/knobs.ts +268 -0
- package/tui/play-strip.ts +80 -14
- package/tui/prompt.ts +1 -1
- package/tui/screen.ts +151 -18
- package/tui/tape.ts +439 -0
- package/tui/text.ts +35 -2
- package/tui/theme.ts +46 -1
|
@@ -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,16 @@ 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";
|
|
117
|
+
import { RANGE_TOOLS } from "./range-tools.ts";
|
|
109
118
|
import { GRANULAR_TOOLS } from "./granular-tools.ts";
|
|
110
119
|
import { RESAMPLE_TOOLS } from "./resample-tool.ts";
|
|
120
|
+
import { VOICE_TOOLS } from "./voice-tools.ts";
|
|
111
121
|
import type { MediaResult, MediaRunContext } from "../media/types.ts";
|
|
112
122
|
import { instrumentPatch, pitchToMidi } from "./ops.ts";
|
|
113
123
|
import { TUNING_LIMITS } from "../../core/tuning.ts";
|
|
@@ -255,17 +265,17 @@ export type ToolContext = Readonly<{
|
|
|
255
265
|
export type AgentTool = Readonly<{
|
|
256
266
|
name: string;
|
|
257
267
|
description: string;
|
|
268
|
+
/**
|
|
269
|
+
* Callable but not advertised: a duplicate kept so older transcripts and
|
|
270
|
+
* models still work. `chatTools` and the xcb catalog leave it out.
|
|
271
|
+
*/
|
|
272
|
+
hidden?: boolean;
|
|
258
273
|
parameters: Record<string, unknown>;
|
|
259
274
|
plan: (args: Record<string, unknown>, context: ToolContext) => ToolPlan;
|
|
260
275
|
}>;
|
|
261
276
|
|
|
262
|
-
|
|
263
|
-
export
|
|
264
|
-
constructor(message: string) {
|
|
265
|
-
super(message);
|
|
266
|
-
this.name = "ToolArgumentError";
|
|
267
|
-
}
|
|
268
|
-
}
|
|
277
|
+
import { ToolArgumentError } from "./tool-error.ts";
|
|
278
|
+
export { ToolArgumentError };
|
|
269
279
|
|
|
270
280
|
const MAX_NOTES_PER_CALL = 128;
|
|
271
281
|
const MAX_EXPLAIN_CHARS = 2_000;
|
|
@@ -404,7 +414,7 @@ export const AGENT_TOOLS: readonly AgentTool[] = Object.freeze([
|
|
|
404
414
|
{
|
|
405
415
|
name: "update_notes",
|
|
406
416
|
description:
|
|
407
|
-
"Move, resize, transpose, re-velocity or detune existing notes by id. Times are in beats.",
|
|
417
|
+
"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
418
|
parameters: {
|
|
409
419
|
type: "object",
|
|
410
420
|
properties: {
|
|
@@ -424,6 +434,13 @@ export const AGENT_TOOLS: readonly AgentTool[] = Object.freeze([
|
|
|
424
434
|
{ type: "string", maxLength: 4 },
|
|
425
435
|
],
|
|
426
436
|
},
|
|
437
|
+
transpose: {
|
|
438
|
+
type: "integer",
|
|
439
|
+
minimum: -127,
|
|
440
|
+
maximum: 127,
|
|
441
|
+
description:
|
|
442
|
+
"Semitones relative to the current pitch (not with pitch)",
|
|
443
|
+
},
|
|
427
444
|
velocity: { type: "number", minimum: 0, maximum: 1 },
|
|
428
445
|
cents: {
|
|
429
446
|
type: "number",
|
|
@@ -442,6 +459,9 @@ export const AGENT_TOOLS: readonly AgentTool[] = Object.freeze([
|
|
|
442
459
|
},
|
|
443
460
|
plan(args, context) {
|
|
444
461
|
const tpb = context.score.ticksPerBeat;
|
|
462
|
+
// Pitch changes as before→after, so the model sees what it did and
|
|
463
|
+
// does not transpose again from the refreshed brief.
|
|
464
|
+
const moves: string[] = [];
|
|
445
465
|
const operations = list(args, "updates", 1, MAX_NOTES_PER_CALL).map(
|
|
446
466
|
(value, index): ScoreOperation => {
|
|
447
467
|
const update = record(value, `updates[${index}]`);
|
|
@@ -459,8 +479,41 @@ export const AGENT_TOOLS: readonly AgentTool[] = Object.freeze([
|
|
|
459
479
|
});
|
|
460
480
|
if (duration !== undefined)
|
|
461
481
|
patch.durationTicks = Math.max(1, Math.round(duration * tpb));
|
|
482
|
+
const before = context.score.notes.find((n) => n.id === noteId);
|
|
462
483
|
if (update.pitch !== undefined)
|
|
463
484
|
patch.pitch = pitch(update.pitch, `updates[${index}].pitch`);
|
|
485
|
+
// Small models fill every field: transpose 0 beside a pitch means
|
|
486
|
+
// no shift, and a pitch that equals the transposed note agrees.
|
|
487
|
+
if (
|
|
488
|
+
patch.pitch !== undefined &&
|
|
489
|
+
update.transpose !== undefined &&
|
|
490
|
+
update.transpose !== 0 &&
|
|
491
|
+
patch.pitch !== (before?.pitch ?? 0) + Number(update.transpose)
|
|
492
|
+
)
|
|
493
|
+
throw new ToolArgumentError(
|
|
494
|
+
`updates[${index}] sets both pitch and transpose; use one`,
|
|
495
|
+
);
|
|
496
|
+
if (update.transpose !== undefined && patch.pitch === undefined) {
|
|
497
|
+
const shift = update.transpose;
|
|
498
|
+
if (typeof shift !== "number" || !Number.isInteger(shift))
|
|
499
|
+
throw new ToolArgumentError(
|
|
500
|
+
`updates[${index}].transpose must be whole semitones`,
|
|
501
|
+
);
|
|
502
|
+
const moved = (before?.pitch ?? 0) + shift;
|
|
503
|
+
if (moved < 0 || moved > 127)
|
|
504
|
+
throw new ToolArgumentError(
|
|
505
|
+
`updates[${index}].transpose moves ${noteId} to MIDI ${moved}, outside 0..127`,
|
|
506
|
+
);
|
|
507
|
+
patch.pitch = moved;
|
|
508
|
+
}
|
|
509
|
+
if (
|
|
510
|
+
patch.pitch !== undefined &&
|
|
511
|
+
before &&
|
|
512
|
+
patch.pitch !== before.pitch
|
|
513
|
+
)
|
|
514
|
+
moves.push(
|
|
515
|
+
`${noteId} ${midiToPitch(before.pitch)}→${midiToPitch(patch.pitch)}`,
|
|
516
|
+
);
|
|
464
517
|
const velocity = optionalNumber(update, "velocity", {
|
|
465
518
|
min: 0,
|
|
466
519
|
max: 1,
|
|
@@ -479,7 +532,11 @@ export const AGENT_TOOLS: readonly AgentTool[] = Object.freeze([
|
|
|
479
532
|
return {
|
|
480
533
|
kind: "score",
|
|
481
534
|
operations,
|
|
482
|
-
summary: `~${operations.length} note${operations.length === 1 ? "" : "s"}
|
|
535
|
+
summary: `~${operations.length} note${operations.length === 1 ? "" : "s"}${
|
|
536
|
+
moves.length === 0
|
|
537
|
+
? ""
|
|
538
|
+
: ` · pitch ${moves.slice(0, 8).join(", ")}${moves.length > 8 ? ` +${moves.length - 8} more` : ""}`
|
|
539
|
+
}`,
|
|
483
540
|
};
|
|
484
541
|
},
|
|
485
542
|
},
|
|
@@ -520,7 +577,18 @@ export const AGENT_TOOLS: readonly AgentTool[] = Object.freeze([
|
|
|
520
577
|
return {
|
|
521
578
|
kind: "score",
|
|
522
579
|
operations: [
|
|
523
|
-
{
|
|
580
|
+
{
|
|
581
|
+
type: "updateTrack",
|
|
582
|
+
trackId,
|
|
583
|
+
patch: {
|
|
584
|
+
// `vocal` (0.7): the vocal chain fills unset effects.
|
|
585
|
+
...(isGuideInstrument(patch.instrument)
|
|
586
|
+
? vocalChainPatch(track)
|
|
587
|
+
: {}),
|
|
588
|
+
...patch,
|
|
589
|
+
...rig,
|
|
590
|
+
},
|
|
591
|
+
},
|
|
524
592
|
],
|
|
525
593
|
trackId,
|
|
526
594
|
summary:
|
|
@@ -899,7 +967,7 @@ export const AGENT_TOOLS: readonly AgentTool[] = Object.freeze([
|
|
|
899
967
|
},
|
|
900
968
|
{
|
|
901
969
|
name: "set_keys",
|
|
902
|
-
description: `Shape a
|
|
970
|
+
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
971
|
parameters: {
|
|
904
972
|
type: "object",
|
|
905
973
|
properties: {
|
|
@@ -1278,7 +1346,8 @@ export const AGENT_TOOLS: readonly AgentTool[] = Object.freeze([
|
|
|
1278
1346
|
},
|
|
1279
1347
|
{
|
|
1280
1348
|
name: "set_effects",
|
|
1281
|
-
|
|
1349
|
+
hidden: true,
|
|
1350
|
+
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
1351
|
parameters: {
|
|
1283
1352
|
type: "object",
|
|
1284
1353
|
properties: {
|
|
@@ -1438,7 +1507,8 @@ export const AGENT_TOOLS: readonly AgentTool[] = Object.freeze([
|
|
|
1438
1507
|
},
|
|
1439
1508
|
{
|
|
1440
1509
|
name: "add_drums",
|
|
1441
|
-
|
|
1510
|
+
hidden: true,
|
|
1511
|
+
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
1512
|
parameters: {
|
|
1443
1513
|
type: "object",
|
|
1444
1514
|
properties: {
|
|
@@ -1490,14 +1560,14 @@ export const AGENT_TOOLS: readonly AgentTool[] = Object.freeze([
|
|
|
1490
1560
|
const track = context.score.tracks.find((t) => t.id === trackId)!;
|
|
1491
1561
|
if (!isDrumInstrument(track.instrument))
|
|
1492
1562
|
throw new ToolArgumentError(
|
|
1493
|
-
`track ${trackId} is ${track.instrument}, not a
|
|
1563
|
+
`track ${trackId} is ${track.instrument}, not a kit; set_instrument kit or create a kit track`,
|
|
1494
1564
|
);
|
|
1495
1565
|
const tpb = context.score.ticksPerBeat;
|
|
1496
1566
|
const loopBeats = loopTicksOf(context.score) / tpb;
|
|
1497
1567
|
const voiceOf = (value: unknown, label: string) => {
|
|
1498
1568
|
const voice =
|
|
1499
1569
|
typeof value === "string" ? parseDrumVoice(value) : undefined;
|
|
1500
|
-
if (!voice) throw new ToolArgumentError(`${label}: unknown drum
|
|
1570
|
+
if (!voice) throw new ToolArgumentError(`${label}: unknown drum`);
|
|
1501
1571
|
return voice;
|
|
1502
1572
|
};
|
|
1503
1573
|
const hits: { voice: DrumVoice; beat: number; velocity: number }[] = [];
|
|
@@ -1962,14 +2032,19 @@ export const AGENT_TOOLS: readonly AgentTool[] = Object.freeze([
|
|
|
1962
2032
|
...CHORD_TOOLS,
|
|
1963
2033
|
...EXPRESSION_TOOLS,
|
|
1964
2034
|
...TUNING_TOOLS,
|
|
2035
|
+
...CALIBRATION_TOOLS,
|
|
1965
2036
|
...DRUM_TOOLS,
|
|
1966
2037
|
...TIME_TOOLS,
|
|
1967
2038
|
...SECTION_TOOLS,
|
|
2039
|
+
...RANGE_TOOLS,
|
|
1968
2040
|
...MEDIA_TOOLS,
|
|
1969
2041
|
...PACK_TOOLS,
|
|
1970
2042
|
...MASTER_TOOLS,
|
|
2043
|
+
...STYLE_TOOLS,
|
|
1971
2044
|
...GRANULAR_TOOLS,
|
|
1972
2045
|
...RESAMPLE_TOOLS,
|
|
2046
|
+
// 0.7 Voice: one array per lane in voice-tools.ts.
|
|
2047
|
+
...VOICE_TOOLS,
|
|
1973
2048
|
// Looks tools up at call time, so it can plan any of the above.
|
|
1974
2049
|
previewSoundTool((name) => findAgentTool(name)),
|
|
1975
2050
|
] satisfies AgentTool[]);
|
|
@@ -2066,14 +2141,16 @@ export function findAgentTool(
|
|
|
2066
2141
|
export function chatTools(
|
|
2067
2142
|
tools: readonly AgentTool[] = AGENT_TOOLS,
|
|
2068
2143
|
): ChatTool[] {
|
|
2069
|
-
return tools
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2144
|
+
return tools
|
|
2145
|
+
.filter((tool) => !tool.hidden)
|
|
2146
|
+
.map((tool) => ({
|
|
2147
|
+
type: "function",
|
|
2148
|
+
function: {
|
|
2149
|
+
name: tool.name,
|
|
2150
|
+
description: tool.description,
|
|
2151
|
+
parameters: portableSchema(tool.parameters),
|
|
2152
|
+
},
|
|
2153
|
+
}));
|
|
2077
2154
|
}
|
|
2078
2155
|
|
|
2079
2156
|
function fxToolCommand(
|
|
@@ -2099,6 +2176,14 @@ function fxToolCommand(
|
|
|
2099
2176
|
throw new ToolArgumentError("set_fx needs on, preset, or params");
|
|
2100
2177
|
}
|
|
2101
2178
|
const params = record(args.params, "params");
|
|
2179
|
+
if (
|
|
2180
|
+
effect === "formant" &&
|
|
2181
|
+
formantGotVowel([
|
|
2182
|
+
...Object.keys(params),
|
|
2183
|
+
...Object.values(params),
|
|
2184
|
+
] as string[])
|
|
2185
|
+
)
|
|
2186
|
+
throw new ToolArgumentError(FORMANT_VOWEL_HINT);
|
|
2102
2187
|
const values: Record<string, number | string | boolean> = {};
|
|
2103
2188
|
for (const [name, value] of Object.entries(params)) {
|
|
2104
2189
|
const param = parseParamName(effect, name);
|
|
@@ -2296,6 +2381,7 @@ function instrumentName(value: unknown): {
|
|
|
2296
2381
|
string?: { preset: string };
|
|
2297
2382
|
modal?: TrackModal;
|
|
2298
2383
|
wind?: TrackWind;
|
|
2384
|
+
vocoder?: TrackVocoder;
|
|
2299
2385
|
} {
|
|
2300
2386
|
const word = typeof value === "string" ? value.trim() : undefined;
|
|
2301
2387
|
if (word === STRING_INSTRUMENT)
|
|
@@ -2309,6 +2395,7 @@ function instrumentName(value: unknown): {
|
|
|
2309
2395
|
(!patch.string &&
|
|
2310
2396
|
!patch.modal &&
|
|
2311
2397
|
!patch.wind &&
|
|
2398
|
+
!patch.vocoder &&
|
|
2312
2399
|
!(AVAILABLE_INSTRUMENTS as readonly string[]).includes(patch.instrument))
|
|
2313
2400
|
)
|
|
2314
2401
|
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"
|